8.4 KiB
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.tsxpackages/app/src/components/session-todo-dock.tsxpackages/app/src/components/question-dock.tsxpackages/app/src/pages/session/session-prompt-dock.tsx- related shared UI in
packages/app/src/components/prompt-input.tsx
Decisions Up Front
-
session-prompt-dockshould stay route-scoped. It is session-page orchestration, so it belongs underpages/session, not globalsrc/components. -
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. -
Current component does too much. Split state derivation, permission actions, and rendering into smaller units while preserving behavior.
-
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.
- New route file:
- Add seed helpers in app e2e layer:
packages/app/e2e/actions.ts(orfixtures.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):
-
Default prompt dock
- no blocker state
- assert prompt input is visible and focusable
- assert blocker cards are absent
-
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
-
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
-
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
- seed todos with
-
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.tsas needed. - Use
expect.pollfor async transitions.
0.3 Gate commands (must pass before Phase 1)
Run from packages/app (never from repo root):
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:
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.tsximportsSessionComposerRegionfrompages/session/composer.
1.2 Split responsibilities
- Keep
session-composer-region.tsxfocused 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
- derive
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-dockno 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/componentsfor shared primitives if reused by both app and ui components- or
packages/app/src/pages/session/composerfirst, then promote to ui after proving reuse
2.2 Apply primitives to current components
Adopt in:
packages/app/src/components/prompt-input.tsxpackages/app/src/pages/session/composer/session-todo-dock.tsxpackages/ui/src/components/dock-prompt.tsx(where appropriate)
Focus on deduping patterns seen in:
- prompt elevated shell styles (
prompt-input.tsxform container) - prompt lower tray (
prompt-input.tsxbottom 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.tsxpackages/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)
-
Step A - Baseline safety net
- Add e2e harness + new session composer dock spec + selector/helpers.
- Must pass locally before any refactor work proceeds.
-
Step B - Phase 1 colocation/splitting
- Move/rename files, extract state and permission component, keep behavior.
-
Step C - Phase 1 dedupe blocked source
- Remove duplicate blocked derivation and wire page autofocus guard to shared source.
-
Step D - Phase 2 style primitives
- Introduce shared surface primitives and migrate prompt/todo/dock usage.
-
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.