9.1 KiB
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
activityLogasprompt; - chain-of-thought/model reasoning arrives through
activity_deltaasthinking; - the streamed model response arrives through
text_deltaasassistant.
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:
- A mixed activity sequence renders only
prompt,thinking, andassistant, in chronological order, with human-facing labels; status, tool, gate, lifecycle, and unknown kinds remain hidden. - Prompt and assistant bodies use the readable Markdown path, thinking keeps its quieter tone, and hidden updates cannot trigger bottom-follow.
- The panel's empty state and existing near-bottom follow behavior are based only on visible entries.
- 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.
- The separator exposes the required ARIA values and supports arrows, Shift+arrows, Home, and End.
- Valid saved widths are restored; corrupt or out-of-range values fall back or clamp safely.
- The narrow-screen layout uses an overlay drawer and does not compress the central column.
- 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
activityLogor 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.