diff --git a/docs/plans/standalone-prompt-input-quill.md b/docs/plans/standalone-prompt-input-quill.md
new file mode 100644
index 0000000000..42999fa4a5
--- /dev/null
+++ b/docs/plans/standalone-prompt-input-quill.md
@@ -0,0 +1,379 @@
+# Design: Standalone composer input in quill + question tool
+
+Status: design-review (no code yet)
+Author: Adam Leith
+Scope: `@posthog/quill` (source in `posthog/posthog` at `packages/quill`) + `@posthog/ui` in `posthog/code`
+
+## Goal
+
+Move the presentational *shell* of `code`'s prompt input into `@posthog/quill` as a
+reusable, host-agnostic composer, so every surface renders the same box. Then add a
+**question tool** surface that reuses that same shell, so when the agent asks a
+question the composer *appears to stay pinned at the bottom* and becomes a
+"talk about it" input — visually identical to the message composer, even though it
+is a different element with different behavior.
+
+### Non-goals (this pass)
+
+- No editor engine in quill. Tiptap stays in `code`. Quill owns layout only.
+- No change to draft/attachment/mode/skills logic. Those stay in `code` as slotted children.
+- No new "grow" mechanism — quill's `InputGroup` already grows for block-aligned addons.
+
+## Decisions locked in review
+
+1. **Design/API only** for now — this document. Implementation follows in a later pass.
+2. **Pure presentational shell** — quill owns `InputGroup` composition, addon slots,
+ toolbar row, submit/stop button styling, states, and grow behavior. All tiptap / ACP /
+ store logic stays in `code` and is passed as children/slots.
+3. **Question surface = same shell.** The message composer and the question surface must
+ look and sit identically so the bottom input reads as continuous. They may be different
+ React elements, but they render the same quill shell.
+4. **Motion is opt-out, never forced.** Quill ships a sensible *default* transition (e.g.
+ the box expanding upward when a question tool activates) but never mandates it. Consumers
+ can drop to `transition: none` — or any curve/duration — without fighting the library.
+ See §2c.
+
+---
+
+## 1. What exists today
+
+`packages/ui/src/features/message-editor/components/PromptInput.tsx` (473 lines) is
+**already** built on quill's `InputGroup` / `InputGroupAddon` / `InputGroupButton`. Anatomy:
+
+```
+InputGroup (focus ring, bash-mode ring, cursor-text, grows via block addons)
+├─ InputGroupAddon align="block-start" → headerAddon slot +
+├─ div.cli-editor-scroll (min-h 50px, max-h 200px|45vh, overflow-y)
+│ └─ ← domain (tiptap)
+└─ InputGroupAddon align="block-end" (toolbar)
+ ├─ {modelSelector} {reasoningSelector}
+ │ {messagingModeToggle} {bash indicator} ← domain, some already slots
+ └─ span.ml-auto → {historyButton} + submit/stop
+ (+ optional sibling outside the box)
+```
+
+**Already slot-shaped** (passed as `ReactNode` props): `modelSelector`,
+`reasoningSelector`, `messagingModeToggle`, `historyButton`, `headerAddon`. The refactor
+formalizes and completes this pattern.
+
+**Domain guts that must stay in `code` and be passed in:**
+
+| Piece | Why it can't be in quill |
+| --- | --- |
+| `useTiptapEditor` + `` | Tiptap, ACP SDK, `draftStore`, skills, commands |
+| `AttachmentMenu`, `AttachmentsBar` | host file API, chip insertion |
+| `ModeSelector` | ACP `SessionConfigOption`, `sessionStore` |
+| `ModelSelector`, `ReasoningLevelSelector`, `SteerQueueToggle` | already slotted |
+| `SlotMachineSubmit`, history dialog | app state / settings |
+| hotkeys, focus mgmt, submit/cancel logic, `EditorHandle` imperative API | app behavior |
+
+**Consumers to keep working (5):** `SessionView` (main chat composer), `TaskInput`
+(new-task), `PiSessionView`, `FreeformGenerateBar` (canvas), `ChannelHomeComposer` (channels).
+
+### The "grow" is already free
+
+`packages/quill/packages/primitives/src/input-group.css`:
+
+```css
+.quill-input-group:has(> [data-align='block-start']),
+.quill-input-group:has(> [data-align='block-end']) {
+ flex-direction: column;
+ height: auto; /* container grows to fit children */
+}
+```
+
+So "the input grows to fit the question tool's children" needs no new mechanism — the
+question content is a `block-start` addon inside the same `InputGroup`. **The
+`InputGroup` primitive is enough** for the growth, confirming the hunch.
+
+### The question tool today
+
+- `permissions/QuestionPermission.tsx` parses the tool's `_meta` questions and renders
+ `primitives/action-selector/ActionSelector` (option rows, single/multi-select, step
+ tabs, inline custom input, submit/cancel; selection + keyboard nav live in
+ `useActionSelectorState`).
+- `session-update/QuestionToolView.tsx` renders the collapsed transcript row.
+- In `SessionView.tsx` the composer slot is an **either/or**: a pending question renders
+ the `ActionSelector` card **instead of** `PromptInput` (`:646` vs `:655`). This is the
+ swap we want to replace with continuity.
+
+---
+
+## 2. Proposed quill additions
+
+Layer: **`@posthog/quill-components`** (composed primitives), not a new primitive — the
+composer composes `InputGroup` + `Button`, which is exactly the components layer's job.
+The question-option row and step tabs reuse existing primitives (`Item`, `Tabs`) where possible.
+
+### 2a. `Composer` — the shell (compositional API)
+
+Mirrors quill's composition-over-props ethos (like `Card` / `Item`). Every part is a thin
+wrapper over an `InputGroup` slot so the box, grow, and focus states come for free.
+
+```tsx
+ {/* InputGroup + variants + grow */}
+ {/* block-start addon: attachments, context chips, question tool */}
+ {editor} {/* scroll area; children = from code */}
+ {/* block-end addon */}
+ {/* attach, mode, model, reasoning */}
+ {/* history + */}
+
+
+```
+
+| Component | Wraps | Owns |
+| --- | --- | --- |
+| `Composer` | `InputGroup` | focus/hover ring, `cursor-text`, `state` variant, grow, `onClick`-to-focus escape (via `onFocusRequest` callback — quill can't reach the editor, so it exposes the intent) |
+| `ComposerHeader` | `InputGroupAddon align="block-start"` | top padding, full-width stacking |
+| `ComposerBody` | scroll `div` | `min-h`, `max-h` (`size` prop: `default` 200px / `large` 45vh), `overflow-y`, text size |
+| `ComposerToolbar` | `InputGroupAddon align="block-end"` | 1px padding, row layout |
+| `ComposerToolbarStart` / `ComposerToolbarEnd` | `span` | left cluster / `ml-auto` right cluster |
+| `ComposerSubmit` | `InputGroupButton` | sizing + variant presets; children = icon |
+
+**`Composer` props (presentational only):**
+
+| Prop | Type | Purpose |
+| --- | --- | --- |
+| `state` | `"default" \| "accent" \| "invalid"` | `accent` = the blue bash-mode ring; `invalid` = destructive ring. Default relies on `InputGroup` focus-within. |
+| `size` | `"default" \| "large"` | forwards to `ComposerBody` max-height |
+| `motion` | `"default" \| "none"` | ergonomic sugar for the grow/expand transition (§2c). `default` = quill's default curve; `none` = no transition. Never forces animation — just sets the underlying custom property / `data-motion` attribute, which the consumer can also set directly. |
+| `onFocusRequest` | `() => void` | click on empty chrome → code focuses its editor (quill has no editor ref) |
+| `className`, `...div` | | passthrough |
+
+**`ComposerSubmit` props:**
+
+| Prop | Type | Purpose |
+| --- | --- | --- |
+| `intent` | `"send" \| "stop"` | `send` = primary; `stop` = destructive. Icon passed as children (code uses phosphor; quill stays icon-agnostic). |
+| `disabled`, `onClick`, `aria-label` | | standard |
+
+Icons: `code` passes phosphor `ArrowUp` / `Stop` as children — quill does not import an
+icon set for these, keeping it host-agnostic (quill's own examples use lucide, but the
+button accepts any node).
+
+**Why a `Composer` wrapper instead of "just use `InputGroup`":** the five call sites each
+re-hand-roll the scroll `div` classes, the `ml-auto` submit cluster, the focus-ring
+className, and the `block-start`/`block-end` addon choreography. `Composer` makes that one
+opinionated, tested, storybook'd surface — and, crucially, is what lets the **question
+surface reuse the identical shell** (section 3) rather than approximating it.
+
+### 2b. Question-option primitives (probably mostly existing quill)
+
+The presentational question surface is: an optional step-tab strip, a title + question
+line, a list of selectable option rows (single or multi, each with a label + description),
+and submit/cancel actions. Most of this already exists in quill:
+
+| Need | Existing quill | Gap |
+| --- | --- | --- |
+| selectable option rows | `Item variant="pressable"` + `ItemContent`/`ItemTitle`/`ItemDescription`, `tone` | a selected/checked visual + radio/checkbox affordance + keyboard roving |
+| multi-select checkmark | `ItemGroup` + `Checkbox` | compose |
+| step tabs | `Tabs variant="line"` | completed-state dot |
+| custom "Other" input | `Composer` body itself (see §3) | — |
+
+Proposal: add a thin **`QuestionOptions` / `QuestionOption`** pair (or a `selectable`
+variant on `Item`) that renders the option row with a radio/checkbox slot, description, and
+`data-selected`/keyboard support — and a `QuestionSteps` wrapper over `Tabs` with a
+`completed` dot. Selection state, keyboard handling, and step advancement **stay in
+`code`** (today's `useActionSelectorState`); quill only styles the rows.
+
+Open call for review: this may be light enough that we keep the whole `ActionSelector`
+option-row rendering in `code` and only ship `Composer`. See Open Questions Q2.
+
+### 2c. Motion policy — defaults, not mandates
+
+The most visible motion is the box **growing upward** when a question tool activates
+(default → question mode) and shrinking back. Quill should own a *good default* for this
+but must never impose it — a consumer that wants an instant, non-animated expand (or a
+different curve) must get there without `!important` battles or re-implementing the shell.
+
+**Mechanism (matches quill's existing tunable pattern, e.g. `--quill-shimmer-base`):**
+
+1. The transition is declared against a **CSS custom property with a built-in default**,
+ not a hardcoded literal:
+
+ ```css
+ .quill-composer {
+ /* grow/shrink of the block-addon stack; height:auto isn't natively animatable, so
+ the implementation uses the grid-rows 0fr↔1fr (or interpolate-size) technique on
+ the addon wrapper — the point here is the *transition* is overridable. */
+ transition: var(--quill-composer-motion, grid-template-rows 150ms ease, height 150ms ease);
+ }
+ ```
+
+2. Consumers override at any level, no library fork:
+
+ ```css
+ /* kill it everywhere */
+ :root { --quill-composer-motion: none; }
+ /* or per-instance */
+ /* sets --quill-composer-motion: none */
+
+ ```
+
+3. `prefers-reduced-motion: reduce` collapses the default to `none` automatically — but an
+ explicit consumer value still wins, so reduced-motion is a floor, not an override of intent.
+
+**Rules this encodes:**
+
+- Quill never writes a bare `transition:` the consumer can't reach. Every animated property
+ routes through a custom property with a default, so "go to `transition: none`" is a
+ one-line override, not a fight.
+- The `motion` prop is *only* sugar over that property — the CSS custom property and
+ `data-motion` attribute remain the source of truth, so app-wide theming (set the property
+ once at the root) and per-instance opt-out both work.
+- Same policy applies to any other Composer motion we add (submit-button state cross-fade,
+ focus ring). Default provided, always overridable, reduced-motion respected.
+- This does **not** change today's behavior on the InputGroup primitive itself, which only
+ transitions `color`/`background`/`border` and grows instantly — the Composer *adds* an
+ opt-out-able grow transition on top; leaving `motion` unset on a plain `InputGroup` keeps
+ the current instant grow.
+
+---
+
+## 3. The question surface — continuity at the bottom
+
+Requirement from review: *the input seemingly stays at the bottom and becomes the
+"talk about it" input when a question is available; it must look exactly the same.*
+
+Reconciliation: **one `Composer` shell always occupies the bottom.** Its *contents* change
+with mode. There is no swap of the visible box — only its header and toolbar action swap.
+
+### Message mode (default)
+
+```
+Composer
+├─ ComposerHeader → attachments (when present)
+├─ ComposerBody → tiptap editor ("Type a message…")
+└─ ComposerToolbar → [attach · mode · model · reasoning] … [history · Send]
+```
+
+### Question mode (a question tool is pending)
+
+```
+Composer ← same shell, same position, grows upward
+├─ ComposerHeader → + question title/text +
+├─ ComposerBody → tiptap editor, placeholder "Talk about it…" ← the "talk about it" input
+└─ ComposerToolbar → [mode?] … [Submit answer] (Send becomes Submit; Enter in body = discuss)
+```
+
+- The **options render in `ComposerHeader`** (block-start addon) so the box grows upward
+ from the pinned bottom — nothing below moves, the input appears to stay put and expand.
+ The grow uses the default, overridable transition from §2c (a consumer can set
+ `motion="none"` for an instant expand).
+- The **body stays a live editor** as the "talk about it" / free-text / "Other" answer.
+ Typing + Enter there submits a discussion message (or the custom answer); the toolbar's
+ **Submit answer** commits the selected option(s). This is exactly the "becomes the talk
+ about it input" behavior.
+- Because both modes render the **same `Composer`**, the transition is a content change,
+ not a remount — true visual continuity, no fl. The user's "it may be a different
+ element but must look identical" is satisfied by shared shell: even if `code` chooses to
+ mount a distinct `` component, it renders `Composer` internally, so it
+ is pixel-identical to ``.
+
+### Recommended code structure (later pass)
+
+- `MessageComposer` (rename/thin-wrap today's `PromptInput`) → renders `Composer` with the
+ message-mode slots. Keeps `EditorHandle`, hotkeys, submit logic.
+- `QuestionComposer` → renders `Composer` with question-mode slots; drives selection via
+ today's `useActionSelectorState` + `questionDraftStore`; shares the same `useTiptapEditor`
+ for the "talk about it" body.
+- `SessionView` composer slot stops doing either/or of *different-looking* cards; it always
+ renders a `Composer`-shaped element, switching message ↔ question contents.
+
+---
+
+## 4. `PromptInput` before / after
+
+**Before:** `PromptInput` owns the box classes, scroll div, toolbar layout, submit cluster,
+and grow choreography inline, plus all domain wiring.
+
+**After:** `PromptInput` (→ `MessageComposer`) keeps only domain wiring and passes slots:
+
+```tsx
+
+ {headerAddon && {headerAddon}}
+ {attachments.length > 0 && (
+
+ )}
+
+
+
+ {!hideDefaultToolbar && <>{onModeChange && }{modelSelector}{reasoningSelector}{messagingModeToggle}{isBashMode && }>}
+
+
+ {!hideDefaultToolbar && historyButton}
+ {inStopMode
+ ?
+ : !slotMachineMode && }
+
+
+
+```
+
+`SlotMachineSubmit` stays a `code`-side sibling. The `EditorHandle` `useImperativeHandle`,
+all `useHotkeys`, `useDraftStore`, skills effect, and callbacks are unchanged.
+
+---
+
+## 5. Migration plan (phased)
+
+1. **Quill:** add `Composer*` (+ optional `QuestionOptions`/`QuestionSteps`) to
+ `packages/quill/packages/components`, export from that package's `src/index.ts` (flows to
+ `@posthog/quill` on build). Storybook stories for message mode, grown/attachments mode,
+ question mode. Update `packages/components/AGENTS.md` (lint-staged enforces this).
+2. **Local sync into `code`:** build quill workspace → `npm pack` → point the
+ `pnpm-workspace.yaml` override at the `.local-quill/*.tgz` → `pnpm install` (the
+ `quill-code` skill loop). Temporary; reverted before merge.
+3. **`code`:** refactor `PromptInput` → `MessageComposer` over `Composer` with **zero
+ behavior change**. Land + verify all 5 consumers unchanged (Storybook + `PromptInput.test`
+ + `test-electron-app`).
+4. **`code`:** add `QuestionComposer` over `Composer`; switch `SessionView`'s composer slot
+ from the either/or swap to the shared-shell content switch. Migrate `QuestionPermission`
+ rendering into it.
+5. **Publish** the quill version, bump the catalog pin in `code`, drop the tarball override.
+6. Roll the shared shell into the other consumers as desired.
+
+Steps 3 and 4 are independently shippable; 4 depends on 3.
+
+---
+
+## 6. Risks / open questions
+
+- **Q1 — layer.** `Composer` as a quill *component* (composed primitives) vs a *block*
+ (product pattern). Recommendation: component, since it's a generic input shell, not a
+ product-specific card. Confirm.
+- **Q2 — how much of the question tool goes to quill.** Ship only `Composer` and keep the
+ `ActionSelector` option-row rendering in `code`? Or extract `QuestionOptions`/`QuestionSteps`
+ to quill too? Recommendation: start with `Composer` only (unblocks the continuity win);
+ extract option rows to quill later if reuse appears. The "talk about it" continuity does
+ **not** require the option rows to be in quill.
+- **Q3 — icons.** Quill stays icon-agnostic for `ComposerSubmit` (children). `code` keeps
+ phosphor. OK?
+- **Q4 — bash-mode / focus ring.** Encoded as `state="accent"` on `Composer` vs a
+ `className` passthrough. Recommendation: a small named `state` set so the ring is a quill
+ concern, not a magic className. Confirm the variant vocabulary (`default`/`accent`/`invalid`).
+- **Q5 — `onFocusRequest`.** Quill can't hold the editor ref, so click-on-chrome-to-focus is
+ surfaced as a callback. Acceptable seam?
+- **Q6 — `EditorHandle`.** Stays entirely in `code`; quill exposes no imperative handle. Confirm.
+- **Q7 — shipped motion default.** The grow transition is always overridable (§2c); the
+ question is what quill ships as the *default* — a subtle animated expand, or `none` (motion
+ strictly opt-in per instance)? Recommendation: ship a subtle default expand + honor
+ reduced-motion, since the continuity effect is the point; `code` can still set `motion="none"`
+ anywhere it wants instant. Confirm.
+
+---
+
+## Appendix: file map
+
+Quill (new, in `posthog/posthog`):
+- `packages/quill/packages/components/src/composer.tsx` (+ `.css`, `.stories.tsx`)
+- (maybe) `packages/quill/packages/components/src/question-options.tsx`
+- export additions in `packages/quill/packages/components/src/index.ts`
+- `packages/quill/packages/components/AGENTS.md` update
+
+Code (later passes, in `posthog/code`):
+- `packages/ui/src/features/message-editor/components/PromptInput.tsx` → `MessageComposer`
+- `packages/ui/src/features/permissions/QuestionComposer.tsx` (new)
+- `packages/ui/src/features/sessions/components/SessionView.tsx` (composer slot switch)
+- unchanged domain: `useTiptapEditor`, `draftStore`, `AttachmentMenu`, `ModeSelector`,
+ `useActionSelectorState`, `questionDraftStore`