feat(web): tool call panel — desktop right sidebar, mobile bottom sheet #145

Closed
opened 2026-07-12 04:03:09 +02:00 by dominik.polakovics · 1 comment

Tool call detail moves out of the chat column into a dedicated panel. Chips and group summaries stop expanding inline and become buttons that open it. Grilled 2026-07-12; decisions below are settled.

Container

  • Desktop (≥1024px): in-layout right sidebar — the chat column squeezes to make room (same idiom as the persistent left rail, same breakpoint, so the two side panels appear together). Fixed width clamp(320px, 30vw, 480px), no resize handle in v1.
  • Mobile (<1024px): modal bottom sheet over a scrim (InstallSheet idiom from #142: scrim and card are siblings, dismiss handler on the scrim only). Max-height ~80–85dvh, grabber bar, content scrolls inside. Dismiss: scrim tap, ✕, or drag-down following #140's touch conventions (inner scroller wins the gesture unless at scrollTop 0).
  • Crossing 1024px while open: the panel stays open and re-renders in the other container — shared state, pure render switch.
  • No history entry. Hardware back / edge-swipe navigates away from the run page even with the sheet up (standard web behavior; explicitly decided against pushState games).
  • Esc closes on desktop; ✕ closes everywhere.
  • a11y: sheet is role="dialog" aria-modal="true"; sidebar is a complementary region (not modal).

Navigation inside the panel

Two pages, same model in both containers:

  • Tapping a group ("12 tool calls") opens the panel at a list page: one row per tool (title + status mark, thinking stays folded out per #68). Tapping a row pushes that tool's detail page with a ‹ back affordance returning to the list.
  • Tapping a lone chip (not part of a group) opens directly at its detail page, no back affordance — one tap always shows what you tapped.
  • Detail page v1 = exactly today's chip expansion, roomier: header (title + status), literal input/output <pre> blocks, monospace, horizontal scroll inside the panel. Rich per-tool rendering is the follow-up issue.
  • Tapping a different chip/group while the panel is open swaps content in place (resets to that target's entry page).

Liveness

  • The panel renders from the same messages() store as the chat. Group identity = first tool's seq, detail identity = tool seq — same stable-key trick as decision 12, so SSE refetches preserve panel state. New tools append to an open list page, statuses flip, an open detail's output grows in place.
  • The panel never opens or retargets itself — user taps only. No auto-follow of the newest tool.

Chat side

  • Group coalescing (decisions 7–12) stays; only the in-place <details> expansion is superseded. ToolChip / ToolGroupView summaries become buttons.
  • The chip/group line whose content is open in the panel gets a subtle selected style (desktop shows both at once — the highlight answers "which run is the sidebar showing"). Cleared on close.
  • The pending-dialog interactive card is untouched — it stays inline in the transcript.
  • Panel state is local to RunChat and resets on route navigation.

Out of scope (follow-up issue)

Rich view kinds (diff/command/write), the ToolInfo.view union, and the raised detail truncation limit.

Tool call detail moves out of the chat column into a dedicated panel. Chips and group summaries stop expanding inline and become buttons that open it. Grilled 2026-07-12; decisions below are settled. ## Container - **Desktop (≥1024px)**: in-layout right sidebar — the chat column squeezes to make room (same idiom as the persistent left rail, same breakpoint, so the two side panels appear together). Fixed width `clamp(320px, 30vw, 480px)`, no resize handle in v1. - **Mobile (<1024px)**: modal bottom sheet over a scrim (InstallSheet idiom from #142: scrim and card are siblings, dismiss handler on the scrim only). Max-height ~80–85dvh, grabber bar, content scrolls inside. Dismiss: scrim tap, ✕, or drag-down following #140's touch conventions (inner scroller wins the gesture unless at scrollTop 0). - Crossing 1024px while open: the panel stays open and re-renders in the other container — shared state, pure render switch. - **No history entry.** Hardware back / edge-swipe navigates away from the run page even with the sheet up (standard web behavior; explicitly decided against pushState games). - Esc closes on desktop; ✕ closes everywhere. - a11y: sheet is `role="dialog" aria-modal="true"`; sidebar is a complementary region (not modal). ## Navigation inside the panel Two pages, same model in both containers: - Tapping a **group** ("12 tool calls") opens the panel at a **list page**: one row per tool (title + status mark, thinking stays folded out per #68). Tapping a row pushes that tool's **detail page** with a ‹ back affordance returning to the list. - Tapping a **lone chip** (not part of a group) opens **directly at its detail page, no back affordance** — one tap always shows what you tapped. - Detail page v1 = exactly today's chip expansion, roomier: header (title + status), literal input/output `<pre>` blocks, monospace, horizontal scroll inside the panel. Rich per-tool rendering is the follow-up issue. - Tapping a different chip/group while the panel is open swaps content in place (resets to that target's entry page). ## Liveness - The panel renders from the same `messages()` store as the chat. Group identity = first tool's `seq`, detail identity = tool `seq` — same stable-key trick as decision 12, so SSE refetches preserve panel state. New tools append to an open list page, statuses flip, an open detail's output grows in place. - The panel **never opens or retargets itself** — user taps only. No auto-follow of the newest tool. ## Chat side - Group coalescing (decisions 7–12) stays; only the in-place `<details>` expansion is superseded. `ToolChip` / `ToolGroupView` summaries become buttons. - The chip/group line whose content is open in the panel gets a subtle selected style (desktop shows both at once — the highlight answers "which run is the sidebar showing"). Cleared on close. - The pending-dialog interactive card is untouched — it stays inline in the transcript. - Panel state is local to RunChat and resets on route navigation. ## Out of scope (follow-up issue) Rich view kinds (diff/command/write), the `ToolInfo.view` union, and the raised detail truncation limit.
Author
Owner

This was generated by AI while landing a PR.

Landed PR #147 (afk/145) — merged into main.

Verdict: PASS. Verification relied on the repo's own CI (ci / native, run #160, success in 7m48s: build, tsc, eslint, prettier, 727 vitest tests) — not re-run. origin/main was an ancestor of the head, so the merge was conflict-free. Title is Conventional Commits; Closes #145 present and honored (this issue auto-closed on merge).

Diff review (web-only, +2129/−253): sheetGesture.ts is a clean pure Y-axis transposition of #140's drawer machine; ToolPanel.tsx resolves its target live by immutable seq so output grows across SSE refetches, with on(key) avoiding reset-on-refetch and dialog/aria-modal scoped to the sheet; RunChat.tsx swaps the inline <details> expansion for panel-opening buttons, relocates toolStatusMark, nulls dissolved selections, and coordinates Esc via defaultPrevented.

Caveat (non-blocking): the CSS layout reflow could not be eyeballed in the sandbox (no browser); CI vouches for build/lint/tests, and live visual confirmation is on the author's documented residual list.

Follow-up: stacked child #148 (afk/146, Closes #146) is next in order.

> *This was generated by AI while landing a PR.* **Landed PR #147** (`afk/145`) — merged into `main`. **Verdict: PASS.** Verification relied on the repo's own CI (`ci / native`, run #160, success in 7m48s: build, tsc, eslint, prettier, 727 vitest tests) — not re-run. `origin/main` was an ancestor of the head, so the merge was conflict-free. Title is Conventional Commits; `Closes #145` present and honored (this issue auto-closed on merge). Diff review (web-only, +2129/−253): `sheetGesture.ts` is a clean pure Y-axis transposition of #140's drawer machine; `ToolPanel.tsx` resolves its target live by immutable seq so output grows across SSE refetches, with `on(key)` avoiding reset-on-refetch and dialog/aria-modal scoped to the sheet; `RunChat.tsx` swaps the inline `<details>` expansion for panel-opening buttons, relocates `toolStatusMark`, nulls dissolved selections, and coordinates Esc via `defaultPrevented`. Caveat (non-blocking): the CSS layout reflow could not be eyeballed in the sandbox (no browser); CI vouches for build/lint/tests, and live visual confirmation is on the author's documented residual list. Follow-up: stacked child **#148** (`afk/146`, Closes #146) is next in order.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Cloonar/coding-lab#145
No description provided.