# Session UI Refinements — Design **Date:** 2026-06-29 **Status:** Approved (design), pending implementation plan **Layers:** frontend only (no backend/harness changes). ## Problem Three refinements to the session-management UI (built in the 2026-06-29 session-management feature): 1. **Active/Archive is a toggle, should be accordions.** Today a single button at the bottom of the rail swaps the whole view between the active list and the archive (`showArchive` state in [AppShell.tsx](../../../frontend/src/shell/AppShell.tsx)). The two should instead be independent, always-visible collapsible **accordion** sections — the same pattern already used for the group headers. 2. **No way to rename a group.** Groups are derived from the manifest `group` field; there is no UI to rename one. 3. **The central area shows the whole conversation.** Today `` renders the entire streamed `text_delta` model output. The user does not want to see the whole conversation. The central area should show only the essentials, and the verbose model stream ("thinking/working") should be available **on demand** in a **left** panel. ## Decisions (from brainstorming) - **Central area = essentials only:** the **last user input / last reviewer choice**, the gate's **curated "important" messages** (the `notify`/`info` events — currently toasts), and the **active decision widget** (`WidgetHost`). The full streamed `text_delta` is **removed** from the centre. - **Left "Model activity" panel = the verbose model stream, on demand.** The entire `text_delta` stream (today's `transcript`) moves into a **left** drawer, shown only when the user opens it. This subsumes the model's "CoT / thinking": the verbose model output IS the working/thinking. (Fine-grained separation of `thinking_delta` from narration — which would require changes to the `SessionBridge` and the user's pi provider extension — is **deferred**; see Out of scope.) - **The work-in-progress icon moves and becomes the panel trigger.** The `WorkingSpinner` moves from the right-rail header to **just above the chat composer**. Clicking it toggles the left "Model activity" panel. It still spins while `working`. - **"Important phrases" = the gate's `notify`/`info` messages + the active widget's intro** (the realizable-now interpretation, confirmed). No new model-text parsing. - **Active/Archive → two accordions**; **rename group** done client-side by reassigning the group's member sessions (reusing the existing `set-group` mutation). ## Block 1 — Active / Archive accordions Replace the `showArchive` toggle in [AppShell.tsx](../../../frontend/src/shell/AppShell.tsx) with two collapsible sections, both always rendered: - **Active sessions** — expanded by default; contains the existing group accordions + "No group" section (unchanged internals). - **Archive (n)** — collapsed by default; contains the archived sessions list. Each is a collapsible header (same `▸/▾` button + `aria-expanded` pattern as the group headers). State: `activeOpen`/`archiveOpen` booleans (or a small `Record`). The bottom toggle button and `showArchive` state are removed. ## Block 2 — Rename group - **Trigger:** a small **rename affordance on each group header** (an edit/pencil icon button, or a tiny menu) next to the collapse chevron — visible on hover. - **Flow:** opens a dialog (reuse [RenameDialog.tsx](../../../frontend/src/shell/RenameDialog.tsx)) pre-filled with the current group name. On submit with a new non-empty name, reassign every session currently in that group: `for (s of activeList.filter(s => s.group === old)) await setSessionGroup(s.id, newName)`, then invalidate `["sessions"]`. - **Rationale:** groups are a *derived* concept (the manifest `group` field), so renaming = reassigning members via the existing `setSessionGroup` endpoint. No new backend/harness code. Non-atomic across N sessions (a mid-loop failure half-renames); acceptable at this scale, with a `toast.error` on failure. (A future atomic `tht session rename-group` is a possible enhancement, not needed now.) ## Block 3 — Central area redesign + Model-activity panel + WIP icon ### Store ([sessionStore.ts](../../../frontend/src/store/sessionStore.ts)) - Keep accumulating `text_delta` into `transcript` (now consumed by the LEFT panel, not the centre). - Expose the gate `notify`/`info` events for **central** rendering. Concrete rule: the central list holds the `notify`/`info` messages **since the last recorded user entry** — it is **cleared whenever `lastUserEntry` is set** (a new user input/choice). So the centre always shows "the current step": your last action + what the model has surfaced since. (The store already collects these in `toasts`; add a parallel `stepMessages` array cleared on `setLastUserEntry`. Whether to also keep the transient toast is an implementation detail — default: drop the toast for `notify`/`info`, since they now live in the centre.) - Add `lastUserEntry: { kind: "input" | "choice"; text: string } | null` + a setter. Reset with the session. ### Capturing the last user input/choice - [SteerInput.tsx](../../../frontend/src/shell/SteerInput.tsx): on submit, call `setLastUserEntry({ kind: "input", text })`. - [WidgetHost.tsx](../../../frontend/src/shell/WidgetHost.tsx) (and the widget registry's response path): when the reviewer responds, call `setLastUserEntry({ kind: "choice", text: })`. ### Central area ([AppShell.tsx](../../../frontend/src/shell/AppShell.tsx) `
`) - **Remove** `` from the centre. - Render, in order: the **last user entry** (a compact echo), the **important messages** (the `notify`/`info` list), and **``** (the active decision widget — unchanged). - A new small presentational component (e.g. `CentralStatus`) renders the last-user-entry + important-messages; `WidgetHost` stays as-is. ### Model-activity panel (LEFT, on demand) - A new left drawer `ModelActivityPanel` rendering the active session's `transcript` (the streamed model text), reusing `react-markdown` like the old `Transcript`. - Toggled by the WIP icon (below). Header with a title ("Model activity") + a ✕ to close. - **Left-region coexistence:** the existing read-only [SessionDocumentsPanel](../../../frontend/src/shell/SessionDocumentsPanel.tsx) (opened by clicking a rail session) and this Model-activity panel both occupy the left. They are mutually exclusive: opening one closes the other (a single left-drawer region; track which is active). The Model-activity panel is for the **active** session; the docs panel is for a **clicked** (possibly different) session. ### WIP icon move + trigger - Remove `WorkingSpinner` from the right-rail header in [AppShell.tsx](../../../frontend/src/shell/AppShell.tsx) (the `{working && }` block). - Place it **just above the chat composer** (in the sticky composer column, above the input box). It is a **button**: clicking toggles `ModelActivityPanel`. It spins while `working` (active session && no pending widget) and is still visible (non-spinning) otherwise so it remains a usable toggle, or only shown when there is model activity/transcript — pick one in the plan; default: always shown while a session is active, spinning only while `working`. - Keyboard-accessible (it's a real `