Active/Archive accordions; rename group (client-side reassign); central area shows only last user entry + gate notify/info + active widget; the verbose model stream moves to a left on-demand panel toggled by the WIP icon (moved above the composer). Frontend-only; fine thinking-separation deferred. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
9.6 KiB
9.6 KiB
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):
- 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
(
showArchivestate in AppShell.tsx). The two should instead be independent, always-visible collapsible accordion sections — the same pattern already used for the group headers. - No way to rename a group. Groups are derived from the manifest
groupfield; there is no UI to rename one. - The central area shows the whole conversation. Today
<Transcript>renders the entire streamedtext_deltamodel 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/infoevents — currently toasts), and the active decision widget (WidgetHost). The full streamedtext_deltais removed from the centre. - Left "Model activity" panel = the verbose model stream, on demand. The entire
text_deltastream (today'stranscript) 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 ofthinking_deltafrom narration — which would require changes to theSessionBridgeand the user's pi provider extension — is deferred; see Out of scope.) - The work-in-progress icon moves and becomes the panel trigger. The
WorkingSpinnermoves from the right-rail header to just above the chat composer. Clicking it toggles the left "Model activity" panel. It still spins whileworking. - "Important phrases" = the gate's
notify/infomessages + 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-groupmutation).
Block 1 — Active / Archive accordions
Replace the showArchive toggle in 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)
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
groupfield), so renaming = reassigning members via the existingsetSessionGroupendpoint. No new backend/harness code. Non-atomic across N sessions (a mid-loop failure half-renames); acceptable at this scale, with atoast.erroron failure. (A future atomictht session rename-groupis a possible enhancement, not needed now.)
Block 3 — Central area redesign + Model-activity panel + WIP icon
Store (sessionStore.ts)
- Keep accumulating
text_deltaintotranscript(now consumed by the LEFT panel, not the centre). - Expose the gate
notify/infoevents for central rendering. Concrete rule: the central list holds thenotify/infomessages since the last recorded user entry — it is cleared wheneverlastUserEntryis 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 intoasts; add a parallelstepMessagesarray cleared onsetLastUserEntry. Whether to also keep the transient toast is an implementation detail — default: drop the toast fornotify/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: on submit, call
setLastUserEntry({ kind: "input", text }). - WidgetHost.tsx (and the widget registry's
response path): when the reviewer responds, call
setLastUserEntry({ kind: "choice", text: <chosen option label(s) / free text> }).
Central area (AppShell.tsx <main>)
- Remove
<Transcript />from the centre. - Render, in order: the last user entry (a compact echo), the important messages
(the
notify/infolist), and<WidgetHost>(the active decision widget — unchanged). - A new small presentational component (e.g.
CentralStatus) renders the last-user-entry + important-messages;WidgetHoststays as-is.
Model-activity panel (LEFT, on demand)
- A new left drawer
ModelActivityPanelrendering the active session'stranscript(the streamed model text), reusingreact-markdownlike the oldTranscript. - Toggled by the WIP icon (below). Header with a title ("Model activity") + a ✕ to close.
- Left-region coexistence: the existing read-only SessionDocumentsPanel (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
WorkingSpinnerfrom the right-rail header in AppShell.tsx (the{working && <WorkingSpinner …/>}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 whileworking(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 whileworking. - Keyboard-accessible (it's a real
<button>with an aria-label).
Components summary
- Modify:
AppShell.tsx(accordions, central area, WIP icon move, left-panel wiring),sessionStore.ts(lastUserEntry, expose important messages),SteerInput.tsx+WidgetHost.tsx(record last user entry). - Create:
ModelActivityPanel.tsx(left drawer, renderstranscript),CentralStatus.tsx(last user entry + important messages). Group rename reusesRenameDialog. - Retire from the centre:
Transcript.tsxis repurposed/inlined intoModelActivityPanel(orModelActivityPanelrenders the same markdown); theTranscriptcomponent can be deleted if no longer referenced.
Testing (vitest + RTL + MSW)
- Rail: Active and Archive render as independent accordions; collapsing one does not swap the other; archived sessions appear under the Archive accordion, not the active list.
- Rename group: the header rename action opens the dialog; submitting reassigns each member
via
setSessionGroup(member.id, newName)and invalidates the query; the group header shows the new name after refresh. - Central area:
<Transcript>/full model stream is NOT in the centre; the last user input is echoed after submitting a steer; a reviewer choice is echoed;notify/infomessages render centrally;WidgetHoststill renders the active widget. - Model-activity panel: hidden by default; clicking the WIP icon opens it and it shows the streamed model text; opening it closes the documents panel (and vice-versa).
- WIP icon: rendered above the composer (not in the rail header); spins while
working; is a keyboard-activatable button.
Out of scope / deferred
- Fine-grained thinking separation. Splitting the model's
thinking_delta(reasoning) from its narration into a separate sidebar stream is deferred. It would require: theSessionBridgemapping a distinct thinking event (today it only mapstext_deltaand drops others), AND the user's~/.pi/agent/extensions/aritmolab-provider.mjsto stop flatteningthinking_*→text_*(itsconvertEvent). It also needs live verification, currently blocked by DNS for the inference host. The whole-text_delta-stream-to-sidebar approach here achieves the user's goal without those cross-boundary changes. - No backend/harness changes. No atomic group-rename command. No change to how the model stream is produced.