summaryrefslogtreecommitdiffhomepage
path: root/specs/session-composer-refactor-plan.md
blob: 08fb0d8323ae98c302504da55ca0f2ba0b595499 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# 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.