Files
ThothII/docs/superpowers/specs/2026-06-29-session-ui-refinements-design.md
T
marcopanandClaude Opus 4.8 3d06505819 docs(spec): session UI refinements design
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>
2026-06-29 21:13:28 +02:00

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):

  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). 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 <Transcript> 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 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 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)

  • 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: 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/info list), and <WidgetHost> (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 (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 (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 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 <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, renders transcript), CentralStatus.tsx (last user entry + important messages). Group rename reuses RenameDialog.
  • Retire from the centre: Transcript.tsx is repurposed/inlined into ModelActivityPanel (or ModelActivityPanel renders the same markdown); the Transcript component 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/info messages render centrally; WidgetHost still 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: the SessionBridge mapping a distinct thinking event (today it only maps text_delta and drops others), AND the user's ~/.pi/agent/extensions/aritmolab-provider.mjs to stop flattening thinking_*→text_* (its convertEvent). 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.