241 lines
8.4 KiB
Markdown
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.
|