diff --git a/docs/superpowers/specs/2026-07-15-resizable-model-activity-cte-density-design.md b/docs/superpowers/specs/2026-07-15-resizable-model-activity-cte-density-design.md new file mode 100644 index 00000000..b495f7f5 --- /dev/null +++ b/docs/superpowers/specs/2026-07-15-resizable-model-activity-cte-density-design.md @@ -0,0 +1,174 @@ +# Resizable Model Activity Timeline and Dense CTE Plan Design + +**Date:** 2026-07-15 + +## Goal + +Restore the left **Model activity** panel as a readable chronological view of the original +question, model reasoning, and model response; make the panel width adjustable with the mouse; +and substantially reduce the perceived vertical padding of F6 CTE cards while retaining useful +horizontal breathing room. + +This design supersedes the left-panel visibility and CTE-density sections of +`2026-07-15-central-live-log-cte-density-design.md`. The compact central live log remains in +place. Backend, SSE, workflow, persistence, and model-provider contracts do not change. + +## Root causes + +The session store and stream already retain all three events required by the panel: + +- the submitted question is appended to `activityLog` as `prompt`; +- chain-of-thought/model reasoning arrives through `activity_delta` as `thinking`; +- the streamed model response arrives through `text_delta` as `assistant`. + +The regression is at the rendering boundary. `ModelActivityPanel` currently allows only +`thinking` and `status`, so it explicitly hides the question and model response while admitting +the `F1 STATUS INFO` rows the user does not want. + +The panel/conversation split is also fixed in two separate components: the panel uses `w-2/5` and +the conversation uses `w-3/5`. There is no separator, resize state, pointer handling, keyboard +interaction, clamping, responsive fallback, or persistence. + +The shared `Card` primitive is not secretly adding height to F6: `CtePlanViewer` already overrides +its root padding and gaps. The remaining perceived vertical excess is mainly caused by a 20 px +content gap, repeated 8 px heading margins, relaxed line heights, and table/filter rows with 8 px +padding on both vertical edges. + +## Model activity projection + +The left panel uses an explicit, default-deny allowlist: + +| Activity kind | Visible | Presentation | +| --- | --- | --- | +| `prompt` | Yes | **Question** label and readable body. | +| `thinking` | Yes | **Reasoning** label and Markdown body in a quieter tone. | +| `assistant` | Yes | **Response** label and Markdown body. | +| `status` | No | Excluded, including neutral, warning, and error entries. | +| `tool` | No | Low-level implementation detail. | +| `gate` | No | The corresponding form or artifact remains central. | +| `lifecycle` | No | Process/turn implementation detail. | +| unknown future kinds | No | Default-deny prevents accidental exposure. | + +Visible entries keep their existing chronological order. The panel does not reconstruct or merge +separate data sources: it filters the authoritative in-memory `activityLog`. This preserves the +actual interleaving of question, reasoning, and response. + +Each visible entry is rendered as a compact semantic row with a human-facing label instead of raw +kind/status/level metadata. Phase is shown only when present and visually subordinate to the +label. `prompt`, `thinking`, and `assistant` bodies support Markdown and long-value wrapping. +Rows use an 8 px vertical rhythm rather than the current 12 px. The panel follows appended visible +content only while the reader is already near the bottom; manual upward scrolling remains stable. + +The central live log continues to show the assistant transcript while the model is working. Its +small bounded scrolling box is intentionally retained, even though assistant output is also +available in the full left timeline: the two surfaces serve different purposes, glanceable current +progress centrally and complete readable activity history on demand. + +## Resizable split + +On desktop, the fixed percentage widths are replaced by one width owned by `AppShell` and applied +to the left panel. The conversation column consumes the remaining space with `flex-1 min-w-0`. + +The split contract is: + +- default panel width: 384 px; +- minimum panel width: 288 px; +- maximum panel width: the smaller of 576 px and `containerWidth - 512 px`; +- minimum central width: 512 px whenever the container is wide enough to satisfy both minima; +- stored width is clamped again whenever container geometry changes. + +A full-height vertical separator sits between the panel and central column. Its visible line stays +subtle, while a 12–16 px transparent hit area makes it easy to acquire. Hover, focus, and active +drag strengthen the divider. The cursor is `col-resize`, text selection is suppressed during a +drag, and pointer capture keeps the interaction stable when the pointer leaves the hit area. + +The separator is keyboard operable and exposes `role="separator"`, vertical orientation, current +value, and min/max values: + +- Left/Right changes width by 16 px; +- Shift+Left/Right changes width by 48 px; +- Home moves to the minimum; +- End moves to the maximum. + +The last committed width is persisted globally in browser `localStorage` after pointer release or +keyboard interaction. It is not stored per session and never reaches the backend. Closing the +panel remains ephemeral; reopening it in the same or a later browser session restores the clamped +saved width. + +Below the desktop breakpoint, resizing is disabled and the activity panel becomes a bounded +overlay drawer, up to 90 viewport percent and 384 px wide. The central area remains full width, +preventing the two minimum widths from creating overflow on narrow screens. + +## Compact CTE plan + +The change remains local to `CtePlanViewer`; the shared `Card` component, content schema, and grid +topology do not change. Horizontal padding stays at 12 px below `sm` and 16 px from `sm` upward. + +Vertical density changes target the actual sources of height: + +- header title/purpose gap: 12 px to 8 px; +- body section gap: 20 px to 12 px; +- dependency/key grid gap: 16 px to 12 px; +- repeated section heading margin: 8 px to 4 px; +- table and filter row padding: 8 px to 4 px per vertical edge; +- filter internal spacing: 12 px to 8 px; +- filter grid gap: 12 px to 8 px; +- filter detail gap and divider padding: reduced to 4 px; +- rationale divider padding and heading margin: reduced to 4 px; +- relaxed line heights inside dense rows: replaced by compact fixed line heights. + +Header and main content outer padding remain 8 px per vertical edge so the card border does not +feel cramped. Chips, badges, borders, long-identifier wrapping, semantic sections, and the 12 px +gap between distinct CTE cards stay unchanged. + +## Error and edge-case behavior + +- If a provider emits no `thinking`, the question and response still render in the panel. +- If assistant text has not arrived yet, the question and available reasoning still render. +- Hidden status/tool/gate/lifecycle events do not affect the panel empty state or scroll-follow. +- Unknown activity kinds remain hidden. +- A corrupt or unavailable stored width falls back to 384 px. +- A formerly valid stored width is clamped after viewport or container resizing. +- When the container cannot satisfy both desktop minima, the responsive overlay behavior prevents + horizontal overflow. +- Long Markdown, SQL identifiers, table names, filters, and rationales wrap without horizontal + scrolling. + +## Testing and verification + +Frontend RED/GREEN tests must prove: + +1. A mixed activity sequence renders only `prompt`, `thinking`, and `assistant`, in chronological + order, with human-facing labels; status, tool, gate, lifecycle, and unknown kinds remain hidden. +2. Prompt and assistant bodies use the readable Markdown path, thinking keeps its quieter tone, + and hidden updates cannot trigger bottom-follow. +3. The panel's empty state and existing near-bottom follow behavior are based only on visible + entries. +4. The desktop split starts at 384 px, clamps to its computed min/max, updates through pointer + capture, and gives the central column the remaining width. +5. The separator exposes the required ARIA values and supports arrows, Shift+arrows, Home, and End. +6. Valid saved widths are restored; corrupt or out-of-range values fall back or clamp safely. +7. The narrow-screen layout uses an overlay drawer and does not compress the central column. +8. CTE cards retain their semantic structure and horizontal padding while exact vertical gaps, + row padding, margins, and line heights match the compact contract. + +Verification runs the focused activity-panel, AppShell/split, and CTE viewer tests first, followed +by the complete frontend Vitest suite, `npx tsc -b`, `npm run build`, the Impeccable layout detector, +and `git diff --check`. + +## Deployment + +Only the frontend is affected. Rebuild and force-recreate only the frontend Compose service. If +and only if the Vite entry hash changes, restart the portal web container to invalidate its cached +manifest. Do not restart the core/backend or terminate an unrelated active Pi session. Verify the +running frontend image and entry hash, container health, and that the core start timestamp is +unchanged. + +## Out of scope + +- Removing any event kind from `activityLog` or changing the session store fold. +- Backend, SSE, replay, persistence, workflow, or model-provider changes. +- Showing status, tool, gate, lifecycle, or unknown events in the left panel. +- Removing or expanding the compact central live log. +- Per-session or backend persistence of panel width. +- Changing CTE content, ordering, colors, schema, or the shared card design system.