Files
opencode/specs/session-composer-refactor-plan.md

241 lines
8.4 KiB
Markdown

# Session Composer Refactor Plan
## Goal
Improve structure, ownership, and reuse for the bottom-of-session composer area without changing user-visible behavior.
Scope:
- `packages/ui/src/components/dock-prompt.tsx`
- `packages/app/src/components/session-todo-dock.tsx`
- `packages/app/src/components/question-dock.tsx`
- `packages/app/src/pages/session/session-prompt-dock.tsx`
- related shared UI in `packages/app/src/components/prompt-input.tsx`
## Decisions Up Front
1. **`session-prompt-dock` should stay route-scoped.**
It is session-page orchestration, so it belongs under `pages/session`, not global `src/components`.
2. **The orchestrator should keep blocking ownership.**
A single component should decide whether to show blockers (`question`/`permission`) or the regular prompt input. This avoids drift and duplicate logic.
3. **Current component does too much.**
Split state derivation, permission actions, and rendering into smaller units while preserving behavior.
4. **There is style duplication worth addressing.**
The prompt top shell and lower tray (`prompt-input.tsx`) visually overlap with dock shells/footers and todo containers. We should extract reusable dock surface primitives.
---
## Phase 0 (Mandatory Gate): Baseline E2E Coverage
No refactor work starts until this phase is complete and green locally.
### 0.1 Deterministic test harness
Add a test-only way to put a session into exact dock states, so tests do not rely on model/tool nondeterminism.
Proposed implementation:
- Add a guarded e2e route in backend (enabled only when a dedicated env flag is set by e2e-local runner).
- New route file: `packages/opencode/src/server/routes/e2e.ts`
- Mount from: `packages/opencode/src/server/server.ts`
- Gate behind env flag (for example `OPENCODE_E2E=1`) so this route is never exposed in normal runs.
- Add seed helpers in app e2e layer:
- `packages/app/e2e/actions.ts` (or `fixtures.ts`) helpers to:
- seed question request for a session
- seed permission request for a session
- seed/update todos for a session
- clear seeded blockers/todos
- Update e2e-local runner to set the flag:
- `packages/app/script/e2e-local.ts`
### 0.2 New e2e spec
Create a focused spec:
- `packages/app/e2e/session/session-composer-dock.spec.ts`
Test matrix (minimum required):
1. **Default prompt dock**
- no blocker state
- assert prompt input is visible and focusable
- assert blocker cards are absent
2. **Blocked question flow**
- seed question request for session
- assert question dock renders
- assert prompt input is not shown/active
- answer and submit
- assert unblock and prompt input returns
3. **Blocked permission flow**
- seed permission request with patterns + optional description
- assert permission dock renders expected actions
- assert prompt input is not shown/active
- test each response path (`once`, `always`, `reject`) across tests
- assert unblock behavior
4. **Todo dock transitions and collapse behavior**
- seed todos with `pending`/`in_progress`
- assert todo dock appears above prompt and can collapse/expand
- update todos to all completed/cancelled
- assert close animation path and eventual hide
5. **Keyboard focus behavior while blocked**
- with blocker active, typing from document context must not focus prompt input
- blocker actions remain keyboard reachable
Notes:
- Prefer stable selectors (`data-component`, `data-slot`, role/name).
- Extend `packages/app/e2e/selectors.ts` as needed.
- Use `expect.poll` for async transitions.
### 0.3 Gate commands (must pass before Phase 1)
Run from `packages/app` (never from repo root):
```bash
bun test:e2e:local -- e2e/session/session-composer-dock.spec.ts
bun test:e2e:local -- e2e/prompt/prompt.spec.ts e2e/prompt/prompt-multiline.spec.ts e2e/commands/input-focus.spec.ts
bun test:e2e:local
```
If any fail, stop and fix before refactor.
---
## Phase 1: Structural Refactor (No Intended Behavior Changes)
### 1.1 Colocate session-composer files
Create a route-local composer folder:
```txt
packages/app/src/pages/session/composer/
session-composer-region.tsx # rename/move from session-prompt-dock.tsx
session-composer-state.ts # derived state + actions
session-permission-dock.tsx # extracted from inline JSX
session-question-dock.tsx # moved from src/components/question-dock.tsx
session-todo-dock.tsx # moved from src/components/session-todo-dock.tsx
index.ts
```
Import updates:
- `packages/app/src/pages/session.tsx` imports `SessionComposerRegion` from `pages/session/composer`.
### 1.2 Split responsibilities
- Keep `session-composer-region.tsx` focused on rendering orchestration:
- blocker mode vs normal mode
- relative stacking (todo above prompt)
- handoff fallback rendering
- Move side-effect/business pieces into `session-composer-state.ts`:
- derive `questionRequest`, `permissionRequest`, `blocked`, todo visibility state
- permission response action + in-flight state
- todo close/open animation state
### 1.3 Remove duplicate blocked logic in `session.tsx`
Current `session.tsx` computes `blocked` independently. Make the composer state the single source for blocker status consumed by both:
- page-level keydown autofocus guard
- composer rendering guard
### 1.4 Keep prompt gating in orchestrator
`session-composer-region` should remain responsible for choosing whether `PromptInput` renders when blocked.
Rationale:
- this is layout-mode orchestration, not prompt implementation detail
- keeps blocker and prompt transitions coordinated in one place
### 1.5 Phase 1 acceptance criteria
- No intentional behavior deltas.
- Phase 0 suite remains green.
- `session-prompt-dock` no longer exists as a large mixed-responsibility component.
- Session composer files are colocated under `pages/session/composer`.
---
## Phase 2: Reuse + Styling Maintainability
### 2.1 Extract shared dock surface primitives
Create reusable shell/tray wrappers to remove repeated visual scaffolding:
- primary elevated surface (prompt top shell / dock body)
- secondary tray surface (prompt bottom bar / dock footer / todo shell)
Proposed targets:
- `packages/ui/src/components` for shared primitives if reused by both app and ui components
- or `packages/app/src/pages/session/composer` first, then promote to ui after proving reuse
### 2.2 Apply primitives to current components
Adopt in:
- `packages/app/src/components/prompt-input.tsx`
- `packages/app/src/pages/session/composer/session-todo-dock.tsx`
- `packages/ui/src/components/dock-prompt.tsx` (where appropriate)
Focus on deduping patterns seen in:
- prompt elevated shell styles (`prompt-input.tsx` form container)
- prompt lower tray (`prompt-input.tsx` bottom panel)
- dock prompt footer/body and todo dock container
### 2.3 De-risk style ownership
- Move dock-specific styling out of overly broad files (for example, avoid keeping new dock-specific rules buried in unrelated message-part styling files).
- Keep slot names stable unless tests are updated in the same PR.
### 2.4 Optional follow-up (if low risk)
Evaluate extracting shared question/permission presentational pieces used by:
- `packages/app/src/pages/session/composer/session-question-dock.tsx`
- `packages/ui/src/components/message-part.tsx`
Only do this if behavior parity is protected by tests and the change is still reviewable.
### 2.5 Phase 2 acceptance criteria
- Reduced duplicated shell/tray styling code.
- No regressions in blocker/todo/prompt transitions.
- Phase 0 suite remains green.
---
## Implementation Sequence (single branch)
1. **Step A - Baseline safety net**
- Add e2e harness + new session composer dock spec + selector/helpers.
- Must pass locally before any refactor work proceeds.
2. **Step B - Phase 1 colocation/splitting**
- Move/rename files, extract state and permission component, keep behavior.
3. **Step C - Phase 1 dedupe blocked source**
- Remove duplicate blocked derivation and wire page autofocus guard to shared source.
4. **Step D - Phase 2 style primitives**
- Introduce shared surface primitives and migrate prompt/todo/dock usage.
5. **Step E (optional) - shared question/permission presentational extraction**
---
## Rollback Strategy
- Keep each step logically isolated and easy to revert.
- If regressions occur, revert the latest completed step first and rerun the Phase 0 suite.
- If style extraction destabilizes behavior, keep structural Phase 1 changes and revert only Phase 2 styling commits.