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.tsxpackages/app/src/components/prompt-input.tsxsession-prompt-dock should stay route-scoped.
It is session-page orchestration, so it belongs under pages/session, not global src/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.
No refactor work starts until this phase is complete and green locally.
Add a test-only way to put a session into exact dock states, so tests do not rely on model/tool nondeterminism.
Proposed implementation:
packages/opencode/src/server/routes/e2e.tspackages/opencode/src/server/server.tsOPENCODE_E2E=1) so this route is never exposed in normal runs.packages/app/e2e/actions.ts (or fixtures.ts) helpers to:packages/app/script/e2e-local.tsCreate a focused spec:
packages/app/e2e/session/session-composer-dock.spec.tsTest matrix (minimum required):
Default prompt dock
Blocked question flow
Blocked permission flow
once, always, reject) across testsTodo dock transitions and collapse behavior
pending/in_progressKeyboard focus behavior while blocked
Notes:
data-component, data-slot, role/name).packages/app/e2e/selectors.ts as needed.expect.poll for async transitions.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.
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.tsx imports SessionComposerRegion from pages/session/composer.session-composer-region.tsx focused on rendering orchestration:
session-composer-state.ts:
questionRequest, permissionRequest, blocked, todo visibility statesession.tsxCurrent session.tsx computes blocked independently. Make the composer state the single source for blocker status consumed by both:
session-composer-region should remain responsible for choosing whether PromptInput renders when blocked.
Rationale:
session-prompt-dock no longer exists as a large mixed-responsibility component.pages/session/composer.Create reusable shell/tray wrappers to remove repeated visual scaffolding:
Proposed targets:
packages/ui/src/components for shared primitives if reused by both app and ui componentspackages/app/src/pages/session/composer first, then promote to ui after proving reuseAdopt 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-input.tsx form container)prompt-input.tsx bottom panel)Evaluate extracting shared question/permission presentational pieces used by:
packages/app/src/pages/session/composer/session-question-dock.tsxpackages/ui/src/components/message-part.tsxOnly do this if behavior parity is protected by tests and the change is still reviewable.
Step A - Baseline safety net
Step B - Phase 1 colocation/splitting
Step C - Phase 1 dedupe blocked source
Step D - Phase 2 style primitives
Step E (optional) - shared question/permission presentational extraction