Skip to content

feat: session transcript calm-surface redesign - #271

Merged
pedronauck merged 9 commits into
mainfrom
session-ui
Jul 29, 2026
Merged

pedronauck merged 9 commits into
mainfrom
session-ui

Conversation

@pedronauck

@pedronauck pedronauck commented Jul 29, 2026 •

Copy link
Copy Markdown
Member

Implements SESSION-REDESIGN-SPEC.md entirely — the session transcript disappears behind the conversation: one neutral ramp, status as glyphs, collapse as the resting state, decisions docked to the composer. All 7 build-order phases (§11) landed; the §8 coverage matrix was re-checked row by row.

What changed

1 · Timeline derivation (web/src/components/assistant-ui/)

  • Collapse on settle: a settled tool run collapses the moment it settles — even mid-turn — into a semantic summary row ("Ran 2 commands · Edited 3 files", fixed category order, distinct-file counts), ported from the synara toolCallGroup engine onto SessionTimelineToolPart.
  • Live tail: only the trailing run of the active turn stays open — last 4 calls with a +N previous tool calls toggle above the visible tail (ACTIVE_WORK_VISIBLE_LIMIT = 4; SETTLED_WORK_VISIBLE_LIMIT deleted).
  • Failures never hide: failed/interrupted calls break summary runs and stay individually visible, collapsed, with the red × glyph + error-first-line preview. Row text never turns red.
  • Marker clustering: consecutive same-kind runtime events merge into one marker with a ×N count (keyed on type:kind; clarifications/permissions/goal prompts never cluster).
  • 500-line-cap splits: session-timeline-summary.ts, session-timeline-stable.ts, session-timeline-markers.ts; row estimates retuned (trow ≈26px, marker ≈24px).

2 · packages/ui primitives (story + test each)

  • New: Marker/MarkerMeta, Receipt, Dock (compound: Head/Eyebrow/Title/Count/Deadline/Body/Pre/Meta/Actions/Status/Key), Choice* (rows with key chips + free-text form).
  • ToolCallRow retone: tinted icon wells and the runtimeError danger heading deleted; new stat slot for per-file +a −d; success check and running spinner are grey — only the failure × carries a hue.
  • CodeBlock tone recolor path deleted (ring/tint/full-text recolor); density/copy kept.
  • Tokens: --color-chat-fill-user (4.5% ink) + --color-chat-fill-code added to tokens.css; DESIGN.md regenerated via make codegen.

3 · Transcript components

  • User message → borderless 4.5%-ink bubble, 176px clamp with fade mask + "Show more"; assistant stays naked markdown; meta reveals on hover.
  • Thinking → live shimmer label with auto-opened body; settled Thought row with first-line preview (updates eyebrow deleted).
  • Working row → three 4px stepped dots + self-ticking Working for Ns (text-node mutation, no per-second React commits).
  • Runtime events → one-line markers (raw kind as mono meta — never a pill); SessionDataEventCard payload-preview card deleted; message errors → danger marker.
  • Session resume failure → the one allowed banner (28% hairline / 4% wash, ids as body text, Esc dismisses).
  • Changed files → Edited N files +A −D line expanding to bare mono file lines (cap 8 + +N more).
  • Tool details → .trow__detail rail grammar: unified ± text diffs (Edit/Write), stderr as danger text (Bash), bare mono result lines (Grep/Glob), plain JSON (generic), new TodoWrite plan renderer (done/active/pending glyphs, never JSON).
  • Tool result artifact → quiet --info "Open full result" with faint byte meta; pages load in place; load failure is a danger text line + Retry (fetch/pagination semantics verbatim).

4 · Decisions dock to the composer

  • Pending permissions/clarifications leave the transcript and dock above the composer (fused corners), one at a time with a mono 1/N queue counter — permissions first.
  • Permission dock: buttons only for the decisionOptions the runtime offers; keys 1–4 map to the four ACP decisions; reject-always rides the reject split menu and key 4 fires with the menu closed; digit shortcuts ignore focused inputs.
  • Clarification dock: 30px choice rows on keys 1–9, or the free-text form when no choices ship (Enter submits, Shift+Enter breaks); the deadline hint is static mono — it never ticks; submitting/retryable errors are quiet dock status lines.
  • The transcript keeps one-line receipts for both outcomes — the allowed receipt is new (rejections previously left the only trace).
  • Deleted: sticky PermissionPrompt, ClarificationCard (+ their stories/tests); replaced by permission-data-part.tsx, dock components + hooks, and canonical suites (session-decision-dock.test.tsx, permission-data-part.test.tsx).

5 · Goal strip + window-head actions

  • GoalStatusChip/meters/controls deleted → SessionGoalStrip: state dot (accent pulse active · warning blocked/paused · success done · faint moved) + GOAL kicker + objective + turn 3/20 · ctx 41% mono facts, expanding to quiet key/value rows (Contract · Run link · Context tokens + nudge · Last verdict · Node/cause · moved-session link · Draft goal command / Draft replacement staging /goal … into the composer, never auto-sending). Progress bars dropped — the number is the information; pending/unknown context reads ctx —, never an invented %.
  • Exactly one state-gated goal action rides the OS window head via the topbar slot (Approve > Resume > Pause > Clear-on-terminal), wired through useSessionGoalHeader (head instance reads the shared cache; the strip owns loop-stream reconciliation).
  • Goal prompts render as one marker line; a failed goal read renders a banner, not a full-bleed tinted bar.

6 · Composer + queued strip

  • .composer grammar: no zone top border, hairline → line-strong on hover → accent-dim on focus, elevated fill, 28px accent send disc, ghost circular stop, quiet ⏎ send / ⏎ queue hint. Queue/Interrupt/steer logic verbatim.
  • Queued strip → 32px quiet rows (queue glyph · preview · Steer · edit · remove); dock/strip/composer fuse corners as one shape.

Authorized deltas (spec updated in-diff, §11.7)

  1. No runtime selector on the composer bar — truthful UI: the daemon exposes no session-level runtime state or change surface (no session-update operation; SessionPayload carries no model/reasoning/speed; speed exists only on createSession). The variation-A selector ships on the create-contract surfaces (session create dialog / agent settings — carried branch WIP in this PR). Spec §3/§6 updated.
  2. Dock hosts inside SessionThread's composer zone (where the composer actually lives); capability intact.
  3. .liferule stays vocabulary-only — no runtime lifecycle data source exists beyond markers.
  4. Dock eyebrow uses the locked Eyebrow contract (11/600/-0.005em, L-022) instead of the prototype's .14em tracking.

Carried branch WIP (pre-existing on session-ui)

Variation-A runtime selector anatomy (provider chips, reasoning slider, speed switch + PR #267 session-create speed), session-create dialog wiring, and the opendesign prototype refresh.

Verification

  • make verify green on this exact tree — all 12 stages (codegen-check, installer-check, source-size, Bun lint/typecheck/test, web build, Go fmt/lint/test/build, boundaries). Web: 516/516 test files; packages/ui: 632 tests.
  • 17 Storybook CDP captures inspected for the new/retoned surfaces (dock, goal strip, markers, receipts, trow statuses, clarification, thinking) — grey-check/red-×-only color budget confirmed visually.
  • Suites rewritten to the new contracts (never weakened): timeline logic (30 cases incl. collapse-on-settle mid-turn, distinct-file counts, marker clustering), decision dock keyboard/gating, goal strip states + staging, head-action mutual exclusivity.

QA impact (flagged, not retested)

  • New untested scenarios: ET-web-session-transcript-calm-grammar, ET-web-session-permission-dock, ET-web-session-goal-strip.
  • Reset to untested with impact notes: GL-001, GL-005, GL-006, GL-008, GL-use-response-as-goal (goal controls relocated to the window head; Clear now terminal-gated).

Compozy Impact Audit

  • Native tools: no impact — rendering only; labels/icons stay in the presentation layer (tool-labels.ts, toTimelineParts checked).
  • Extensibility and hooks: no impact — ACP/UIMessagePart wire shapes untouched; MCP/generic tools render through the same trow + detail path (session-data-renderers checked).
  • Workspace data isolation: no impact — the dock derives pending permissions from the session's own transcript and reuses the existing session-scoped clarifications/goal queries.
  • Official Compozy skill: no impact — visual layer only; no CLI/HTTP surface or behavior change.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added docked permission/clarification panels with digit-key shortcuts, choice selection (including free-text), and submit/retry flows.
    • Introduced “fast” runtime speed selection in the runtime selector (and reflected in the composer).
    • Shipped new UI primitives for docks, markers/receipts, choices, and a reusable transcript disclosure control.
  • Improvements
    • Refreshed session timeline: clustered markers, capped changed-files disclosure, clearer diff-stat previews, and improved anchored navigation with “scroll to latest.”
    • Updated composer, goal strips, tool-call rows, and tool renderers for more consistent transcript styling.
  • Bug Fixes
    • Fixed turn folding active vs interrupted detection and aligned reduced-motion/streaming indicators.
  • Breaking Changes
    • Code blocks no longer support tone variants.

@vercel

vercel Bot commented Jul 29, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
compozy-site Ready Ready Preview, Comment Jul 29, 2026 3:25pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 29, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR adds reusable UI primitives, refactors session transcript and decision surfaces, introduces goal strips and actions, updates tool rendering and runtime selection, adds runtime speed requests, and moves skill discovery into bounded filesystem scanners.

Changes

Session UI and runtime interactions

Layer / File(s) Summary
Shared UI primitives and contracts
packages/ui/src/components/custom/*, packages/ui/src/index.ts, packages/ui/src/tokens.css
Adds Choice, Dock, Marker, and Receipt primitives, removes CodeBlock tone support, updates tool-row status/stat contracts, and adds transcript tokens.
Timeline derivation and transcript composition
web/src/components/assistant-ui/*, web/src/styles.css
Adds marker clustering, settled tool summaries, stable rows, transcript disclosures, active-session states, message components, scroll anchoring, working indicators, and updated animations.
Decision docks and goal surfaces
web/src/systems/session/components/{permission-dock,clarification-dock,session-decision-dock}.tsx, web/src/systems/session/components/goal/*
Adds docked permission and clarification flows, terminal receipts, goal strips, state-gated head actions, and shared danger banners.
Tool details and session state
web/src/systems/session/{components, hooks, lib, stores}/*
Adds tool-detail rendering, TodoWrite support, standardized tool errors, diff statistics, permission derivation, clarification submission, and optional fast speed persistence.
Runtime selector
web/src/systems/runtime/components/runtime-selector/*
Replaces provider and reasoning surfaces, moves favorite toggles into model rows, and adds accessible runtime speed controls.
Skill discovery
internal/skillscan/*
Separates directory and filesystem skill traversal into bounded scanners with root validation, candidate limits, filtering, and sorted results.

Estimated code review effort: 5 (Critical) | ~120 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.72% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main theme of the PR: a session transcript calm-surface redesign across the UI and related transcript logic.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch session-ui

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 29, 2026 •

Copy link
Copy Markdown
Contributor

React Doctor found no new issues. 🎉

Reviewed by React Doctor for commit f7c1770.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 19

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
web/src/components/assistant-ui/session-composer.tsx (1)

59-74: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

onSteerPrompt is declared and passed through but never used.

SessionComposerProps.onSteerPrompt (line 26) is passed by SessionThread (onSteerPrompt={onSteerPrompt}), but SessionComposer's destructuring at Line 59-74 never picks it up, and no Steer control exists in the busy-controls block (only Queue/Interrupt/Stop are rendered). The busy-input "Steer" action is silently non-functional even though its sibling handlers (onQueuePrompt, onInterruptPrompt) are fully wired.

🐛 Suggested fix
 export function SessionComposer({
   composerState,
   contentInset,
   canPrompt,
   onCancelPrompt,
   onQueuePrompt,
   onInterruptPrompt,
+  onSteerPrompt,
   isBusyInputPending = false,

…and add a Steer button alongside Queue/Interrupt using handleBusyInputAction(onSteerPrompt, "Couldn't steer prompt."), mirroring the existing pattern.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/components/assistant-ui/session-composer.tsx` around lines 59 - 74,
Wire the existing onSteerPrompt prop into SessionComposer by destructuring it
and adding a Steer control in the busy-controls block alongside Queue and
Interrupt. Invoke it through handleBusyInputAction with the error message
"Couldn't steer prompt.", matching the existing sibling-handler pattern.
web/src/components/assistant-ui/session-working-row.tsx (1)

56-67: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reduced motion drops the elapsed-time information, not just the animation.

The motion branch surfaces "Working for Xs"; the reduced-motion branch shows only "Working…". Reduced-motion users lose a factual runtime signal rather than just the movement. Rendering the timer without the dots (and, if the 1s tick is the concern, at a coarser interval) preserves parity.

♿ Suggested change
       <div
         role="status"
         aria-label="Working"
         data-testid="session-working-row"
         className="flex min-h-[22px] items-center gap-2 px-1 py-0.5 text-[11.5px] text-subtle"
       >
-        <span>Working…</span>
+        <span className="tabular-nums">
+          {startedAt !== undefined ? (
+            <>
+              Working for <WorkingTimer startedAt={startedAt} />
+            </>
+          ) : (
+            "Working…"
+          )}
+        </span>
       </div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/components/assistant-ui/session-working-row.tsx` around lines 56 -
67, Update the reducedMotion branch in SessionWorkingRow to retain the
elapsed-time display shown by the animated branch, rendering “Working for Xs”
without the animation dots. Preserve the existing reduced-motion layout and
accessibility attributes; only adjust the displayed status and timer update
behavior as needed.
🧹 Nitpick comments (20)
web/src/systems/session/components/thinking-block.tsx (2)

101-102: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Custom comparator is now equivalent to the default. With updateCount gone, the props are exactly thinking and thinkingComplete, so plain memo(...) gives the same behavior with less to maintain.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/systems/session/components/thinking-block.tsx` around lines 101 -
102, Remove the redundant custom comparator from the memoized thinking block
component, identified by the comparator comparing prev.thinking and
next.thinkingComplete. Use plain memo(...) now that the component only receives
thinking and thinkingComplete props.

41-87: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The two trigger branches duplicate the same button contract.

Both branches share type, data-testid, aria-expanded, and the same onClick; only the inner content and classes differ. Extracting a single button with a conditional body would keep the two states from drifting (e.g. one branch gaining a handler the other lacks).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/systems/session/components/thinking-block.tsx` around lines 41 - 87,
The live and non-live branches in the thinking trigger duplicate the same button
contract. Refactor the conditional around a single button element, keeping type,
data-testid, aria-expanded, onClick, and shared structural behavior on that
button; conditionally render the existing live or completed inner content and
retain each state’s distinct classes.
web/src/systems/session/components/__tests__/tool-call-card.test.tsx (1)

151-164: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer the data-status/aria-label contract over class-literal assertions.

getAttribute("class")).toContain("text-subtle") couples the suite to a Tailwind literal that any token rename breaks, while the row already exposes data-status and the accessible label as the real contract. If "no signal hue" must be gated, consider asserting the absence of text-success/text-danger instead of the presence of a specific utility.

As per coding guidelines, "Avoid CSS-literal, snapshot, generated, or prose tests unless they are the product contract and no stronger gate exists."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/systems/session/components/__tests__/tool-call-card.test.tsx` around
lines 151 - 164, Update the assertions in the running and success cases of the
tool-call card tests to rely on the existing data-status and aria-label contract
instead of asserting the text-subtle CSS class. If verifying the absence of a
signal hue is necessary, assert that text-success and text-danger are not
present while preserving the current status and accessible-label checks.

Source: Coding guidelines

web/src/systems/session/components/__tests__/thinking-block.test.tsx (1)

81-96: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Alignment test pins Tailwind literals rather than a behavioral invariant.

ml-[25px]/pl-[11px] will break on any rail retune even when the layout stays correct. If the 25px rail is the contract, consider expressing it as a token/utility class (or a data-* rail marker) that the test can assert stably.

As per coding guidelines, "Avoid CSS-literal, snapshot, generated, or prose tests unless they are the product contract and no stronger gate exists."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/systems/session/components/__tests__/thinking-block.test.tsx` around
lines 81 - 96, Update the ThinkingBlock alignment test around the “Should align
the reasoning body…” case to assert a stable rail token, utility class, or data
attribute instead of the literal ml-[25px] and pl-[11px] Tailwind values. Add or
reuse the corresponding semantic marker in ThinkingBlock so the test verifies
the shared detail-rail alignment contract while retaining the existing border
and visual-style assertions.

Source: Coding guidelines

web/src/systems/session/components/session-resume-failure.tsx (1)

46-66: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Two copies of the same session-level danger banner. Both files hand-roll an identical shell (border-danger/28 bg-danger/4, icon + bold title + text-[12px] text-muted body) with the same ad-hoc font size instead of a token, so any retune must be applied twice.

  • web/src/systems/session/components/session-resume-failure.tsx#L46-L66: extract the banner shell (icon, title, body slot) into a reusable component and replace text-[12px] with the matching typography token.
  • web/src/systems/session/components/goal/session-goal-header.tsx#L25-L39: render that shared banner instead of re-declaring the wrapper/icon/title/body markup.

As per coding guidelines, "Use design tokens from packages/ui/src/tokens.css and generated DESIGN.md".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/systems/session/components/session-resume-failure.tsx` around lines
46 - 66, The session-level danger banner is duplicated and uses an ad-hoc body
font size. In
web/src/systems/session/components/session-resume-failure.tsx#L46-L66, extract
the shared icon, title, and body banner shell into a reusable component and
replace text-[12px] with the matching typography token from the design system;
in web/src/systems/session/components/goal/session-goal-header.tsx#L25-L39,
render that shared component instead of duplicating the wrapper markup.

Source: Coding guidelines

web/src/systems/session/components/runtime-activity-notice.tsx (1)

214-219: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use tone instead of data-marker-tone for the progress marker.

Marker exposes data-tone={tone} and its icon styling is keyed on MarkerTone, whose union is neutral | danger | warning | info. The progress branch is leaving tone at "neutral" while setting a non-contract data-marker-tone="progress", so the glyph will render as neutral; pass a valid tone value if this is meant to affect styling.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/systems/session/components/runtime-activity-notice.tsx` around lines
214 - 219, Update the progress Marker in the runtime activity notice to use its
supported tone prop instead of data-marker-tone. Pass a valid MarkerTone value,
such as info, so the marker and Activity icon receive the intended styling;
leave the existing role, test ID, and icon unchanged.
web/src/systems/runtime/components/runtime-selector/provider-chips.tsx (1)

62-70: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Home/End rely on wrap-around instead of moving directly.

Home passes the last chip and steps forward, End passes the first and steps back, so both depend on the modulo wrap to land on the intended edge. A direct edge move is easier to read and cannot break if wrapping ever changes.

♻️ Optional: explicit edge focus
+  const focusChip = (target: RailFilter) => {
+    onRail(target);
+    groupRef.current
+      ?.querySelector<HTMLButtonElement>(`[data-rail="${CSS.escape(String(target))}"]`)
+      ?.focus();
+  };
+
   const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
     if (searching) return;
     if (event.key === "ArrowRight" || event.key === "ArrowDown") {
       event.preventDefault();
       moveFocus(railFilter, 1);
     } else if (event.key === "ArrowLeft" || event.key === "ArrowUp") {
       event.preventDefault();
       moveFocus(railFilter, -1);
     } else if (event.key === "Home") {
       event.preventDefault();
-      const order = getChipOrder(providers);
-      moveFocus(order[order.length - 1] ?? railFilter, 1);
+      const order = getChipOrder(providers);
+      if (order[0] !== undefined) focusChip(order[0]);
     } else if (event.key === "End") {
       event.preventDefault();
-      const order = getChipOrder(providers);
-      moveFocus(order[0] ?? railFilter, -1);
+      const order = getChipOrder(providers);
+      const last = order[order.length - 1];
+      if (last !== undefined) focusChip(last);
     }
   };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/systems/runtime/components/runtime-selector/provider-chips.tsx`
around lines 62 - 70, Update the Home and End handlers in the provider-chips
keyboard navigation logic to move directly to the corresponding edge: Home
should focus the first chip and End should focus the last chip, using the
appropriate non-wrapping direction or edge-focus behavior. Preserve the
railFilter fallback when no providers exist.
web/src/systems/runtime/components/runtime-selector/selector-footer.tsx (1)

26-34: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Tighten mode to the reasoning-mode union.

mode: string lets any value reach data-reasoning-mode, which tests and CSS treat as a contract.

♻️ Optional typing tweak
-  mode: string;
+  mode: RuntimeReasoningState["mode"];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/systems/runtime/components/runtime-selector/selector-footer.tsx`
around lines 26 - 34, Update the FooterNote mode prop to use the existing
reasoning-mode union type instead of string, ensuring only supported values
reach data-reasoning-mode. Reuse the established union symbol from the
surrounding runtime selector types and preserve the component’s existing
behavior.
web/src/components/assistant-ui/session-composer-queued-prompts.tsx (1)

15-98: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Native <button>s replace the shared Button primitive.

The Steer/Edit/Remove controls were previously built on @compozy/ui's Button and are now hand-rolled <button> elements with a bespoke ICON_BUTTON_CLASS, duplicating hover/focus/disabled treatment that the shared primitive already centralizes.

♻️ Suggested direction
-import { CornerDownRight, ListPlus, Pencil, Trash2 } from "lucide-react";
+import { Button } from "`@compozy/ui`";
+import { CornerDownRight, ListPlus, Pencil, Trash2 } from "lucide-react";

Then reintroduce Button (with an icon-only size/variant if one doesn't already exist) for Edit/Remove/Steer instead of raw <button> + manual class strings.

As per coding guidelines, "Reuse primitives exported by packages/ui/src/index.ts and import them from @compozy/ui; generic primitives belong in packages/ui, while domain composites belong in web/src/systems/<domain>/."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/components/assistant-ui/session-composer-queued-prompts.tsx` around
lines 15 - 98, The SessionComposerQueuedPrompts controls currently duplicate
shared button styling with native buttons and ICON_BUTTON_CLASS. Reintroduce the
Button primitive from `@compozy/ui` for the Steer, Edit, and Remove actions, using
existing icon-only sizing and variants where available; remove the bespoke
class-based button styling while preserving their labels, icons, disabled state,
test IDs, and callbacks.

Source: Coding guidelines

web/src/components/assistant-ui/session-message-parts.tsx (1)

21-27: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Stale name and dead data prop.

The component no longer renders a card (it renders a one-line Marker) and data is now required-but-unread while both callers (session-timeline-render.tsx Line 69, session-user-message.tsx Line 22) still pass it. Renaming to SessionDataEventMarker and dropping data in the same change keeps the contract honest.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/components/assistant-ui/session-message-parts.tsx` around lines 21 -
27, The component name and props no longer match its marker-only implementation.
Rename SessionDataEventCard to SessionDataEventMarker, remove the unused
required data prop from its parameter type, and update both callers in the
session timeline and user message components to use the renamed component
without passing data.
web/src/components/assistant-ui/__tests__/session-timeline.logic.test.ts (1)

650-658: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the turn-boundary split of marker clusters.

deriveBaseRows splits marker clusters on either a kind change or a turnId change (session-timeline.logic.ts Line 271), but only the kind-change branch is exercised. A same-kind pair across two turns is a cheap extra case.

♻️ Suggested extra test
   it("Should split marker clusters when the kind changes", () => {
     const rows = deriveSessionRows([
       markerEvent("m1", "provider-retry"),
       markerEvent("m2", "compaction"),
     ]);
 
     expect(rows).toHaveLength(2);
     expect(rows.map(row => (row.kind === "data" ? row.count : row.kind))).toEqual([1, 1]);
   });
+
+  it("Should split same-kind markers that belong to different turns", () => {
+    const first = markerEvent("m1", "provider-retry");
+    const second = { ...markerEvent("m2", "provider-retry"), turnId: "turn-2" };
+    const rows = deriveSessionRows([first, second]);
+
+    expect(rows).toHaveLength(2);
+    expect(rows.map(row => (row.kind === "data" ? row.count : row.kind))).toEqual([1, 1]);
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/components/assistant-ui/__tests__/session-timeline.logic.test.ts`
around lines 650 - 658, Add a test alongside the existing “Should split marker
clusters when the kind changes” case that passes two same-kind marker events
with different turnId values to deriveSessionRows, then assert they produce two
separate single-item rows. This should exercise the turnId-change boundary in
deriveBaseRows while preserving the existing kind-change coverage.
web/src/components/assistant-ui/session-timeline-render.tsx (1)

122-172: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

One disclosure grammar, three copies. The summary row, the changed-files roll-up, and the turn fold each hand-roll the same button shell plus rotating chevron; the shared root cause is that no disclosure component owns this shape, so the calm-surface grammar now has to be kept in sync by hand.

  • web/src/components/assistant-ui/session-timeline-render.tsx#L122-L172: extract the button shell + chevron into a local TranscriptDisclosure (props: expanded, onToggle, icon, label, trailing) and use it for SessionWorkSummaryRow and SessionTurnFoldRowView.
  • web/src/components/assistant-ui/session-changed-files-row.tsx#L63-L107: replace the inline button/chevron markup with the same TranscriptDisclosure, keeping DiffStat as the trailing slot.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/components/assistant-ui/session-timeline-render.tsx` around lines 122
- 172, The disclosure button and rotating chevron are duplicated across three
transcript surfaces. In
web/src/components/assistant-ui/session-timeline-render.tsx:122-172, add a local
TranscriptDisclosure component accepting expanded, onToggle, icon, label, and
trailing, then use it in SessionWorkSummaryRow and SessionTurnFoldRowView; in
web/src/components/assistant-ui/session-changed-files-row.tsx:63-107, replace
the inline disclosure markup with TranscriptDisclosure while passing DiffStat as
trailing.
web/src/components/assistant-ui/session-changed-files-row.tsx (1)

63-107: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Ad-hoc typography and spacing literals instead of design tokens. These rows introduce a new family of hand-picked values (text-[11.5px], text-[13.5px], text-[12px], gap-[7px], min-h-[22px], ml-[25px], pl-[11px], size-[18px]) rather than tokens; the shared root cause is that the calm-surface transcript scale was never added to packages/ui/src/tokens.css. As per coding guidelines, "Use design values from packages/ui/src/tokens.css and DESIGN.md; do not invent colors, typography, radius, spacing, or motion values."

  • web/src/components/assistant-ui/session-changed-files-row.tsx#L63-L107: replace the text-[11px]/text-[11.5px]/gap-[7px]/ml-[25px]/pl-[11px] literals with the transcript scale tokens.
  • web/src/components/assistant-ui/session-timeline-render.tsx#L103-L172: replace the text-[11.5px], text-[12px], gap-[7px], size-[18px], ml-[25px] literals with the same tokens.
  • web/src/components/assistant-ui/session-working-row.tsx#L62-L83: replace text-[11.5px], min-h-[22px], gap-[3px] with tokens.
  • web/src/components/assistant-ui/session-user-message.tsx#L51-L74: replace text-[13.5px], text-[11px], py-[7px] with tokens, and align USER_CLAMP_MAX_PX with the max-h-44 token rather than two parallel constants.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/components/assistant-ui/session-changed-files-row.tsx` around lines
63 - 107, Replace the ad-hoc typography and spacing values with shared
calm-surface transcript tokens, adding the missing scale to
packages/ui/src/tokens.css as needed. In
web/src/components/assistant-ui/session-changed-files-row.tsx lines 63-107,
update the ChangedFiles row layout; in
web/src/components/assistant-ui/session-timeline-render.tsx lines 103-172,
update the timeline row styles; and in
web/src/components/assistant-ui/session-working-row.tsx lines 62-83, update the
working row styles. In web/src/components/assistant-ui/session-user-message.tsx
lines 51-74, replace the listed text and padding literals and align
USER_CLAMP_MAX_PX with the max-h-44 token.

Source: Coding guidelines

web/src/components/assistant-ui/session-working-row.tsx (1)

34-38: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse TypingDots instead of forking the dot animation here.

TypingDots is still exported from @compozy/ui, but this component renders three working dots with local .session-working-dot styling and custom delay delays. Use the shared primitive, or extend TypingDots with the required styling/delay props instead of duplicating the pattern.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/components/assistant-ui/session-working-row.tsx` around lines 34 -
38, Replace the locally defined WORKING_DOT_DELAYS and duplicated working-dot
rendering in the session working row with the exported TypingDots component from
`@compozy/ui`. Reuse TypingDots directly, or extend it with the required styling
and animation-delay props so the existing appearance is preserved without local
dot-animation logic.

Source: Coding guidelines

web/src/systems/session/lib/pending-permissions.ts (2)

33-49: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Recomputed over the whole transcript on every render.

SessionDecisionDock calls this unmemoized (session-decision-dock.tsx Line 33), so every render walks all messages and all parts. On a long live-tailing transcript this is avoidable work; wrap the call in useMemo keyed on messages at the call site.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/systems/session/lib/pending-permissions.ts` around lines 33 - 49, The
derivePendingPermissions call in SessionDecisionDock should be memoized with
useMemo, using messages as its dependency so permission derivation only reruns
when the transcript changes. Keep derivePendingPermissions unchanged and update
the call site in SessionDecisionDock.

66-82: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicated subject derivation.

permissionReceiptSubject in web/src/systems/session/components/permission-data-part.tsx (Lines 22-29) repeats the command→resource branches verbatim, differing only by omitting the JSON fallback. Extract a shared helper with a flag/variant so the two stay in sync.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/systems/session/lib/pending-permissions.ts` around lines 66 - 82,
Extract the duplicated command→resource subject derivation from
permissionSubject and permissionReceiptSubject into a shared helper, supporting
the JSON fallback only where needed. Update both functions to use this helper so
their subject-selection logic remains synchronized.
web/src/systems/session/hooks/use-session-goal-header.ts (1)

31-39: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Fourth positional parameter forces undefined placeholders at call sites.

useSessionGoalHeader(WORKSPACE_ID, SESSION_ID, undefined, { stream: false }) (see web/src/systems/session/components/__tests__/session-goal-header.test.tsx Line 118) shows the cost. Folding onPrefillComposer into the options object keeps the signature stable as more flags appear.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/systems/session/hooks/use-session-goal-header.ts` around lines 31 -
39, Update the useSessionGoalHeader hook signature to move onPrefillComposer
into the existing options object, alongside stream, and adjust all call sites
and related tests to pass it by name without undefined positional placeholders.
Preserve the current callback behavior and stream option semantics.
web/src/systems/session/components/goal/session-goal-strip.tsx (1)

119-128: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Scope aria-live to the status facts, not the whole disclosure.

The section wraps both the trigger and the expandable body, so every expand/collapse announces the full body (contract, run, context, node rows) as a live update. Move aria-live="polite" onto the facts span (Line 149-154) so only the changing state text is announced.

♻️ Proposed change
       aria-label="Goal status"
-      aria-live="polite"
       data-testid="session-goal-strip"
         <span
           data-testid="goal-strip-facts"
+          aria-live="polite"
           className="shrink-0 font-mono text-badge text-subtle tabular-nums"
         >
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/systems/session/components/goal/session-goal-strip.tsx` around lines
119 - 128, Move aria-live="polite" from the section element to the facts span
around the status text (the span near lines 149-154). Keep the section’s
existing data attributes and disclosure structure unchanged so only changing
status facts are announced, not expandable content.
web/src/systems/session/components/__tests__/session-decision-dock.test.tsx (1)

154-155: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace the arbitrary setTimeout(10) waits with a deterministic flush.

Both negative assertions (Lines 154-155 and 181-182) rely on a 10 ms sleep, which is flaky on loaded CI and adds real wall time. Awaiting a microtask flush (await Promise.resolve()) or an await waitFor on an observable post-condition is deterministic.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/systems/session/components/__tests__/session-decision-dock.test.tsx`
around lines 154 - 155, Replace the arbitrary 10 ms setTimeout waits in the
negative assertion cases with deterministic synchronization, such as awaiting
Promise.resolve() or waitFor on an observable post-condition. Update both
assertion blocks involving approveSession while preserving their existing
not.toHaveBeenCalled expectations.
web/src/systems/session/components/__tests__/session-goal-strip.test.tsx (1)

128-141: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Only the draft affordance is covered; the replace branch is untested.

prefillCommand's replace path builds /goal replace <expectedRunId> <objective> — a format regression there silently stages a wrong command. Add a case asserting the replace string and the "Draft replacement" label.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/systems/session/components/__tests__/session-goal-strip.test.tsx`
around lines 128 - 141, The session goal strip tests only cover the draft
affordance; add a test for the replace branch in SessionGoalStrip. Render with a
replace composerAffordance containing an expectedRunId and objective, assert the
button is labeled “Draft replacement,” and verify clicking it calls
onPrefillComposer with `/goal replace <expectedRunId> <objective>`.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/ui/src/components/custom/__tests__/tool-call-row.test.tsx`:
- Around line 91-106: Update the “Should render the per-file diff stat slot
between text and trailing glyphs” test to provide preview content and locate the
preview, stat slot, and trailing status glyph in the rendered DOM. Assert their
document order is preview before stat before status glyph, while retaining the
existing stat text assertion.
- Around line 8-13: Update the status tone assertions driven by GLYPH_STATUSES
to inspect the element returned by statusGlyph(container) for each status,
rather than using the aggregate classesOf helper on the whole container;
preserve the existing expected tone values.

In `@packages/ui/src/components/custom/choice.tsx`:
- Around line 108-116: Add colocated stories demonstrating the new public
primitives: create a Choice story for
packages/ui/src/components/custom/choice.tsx:108-116, a Dock story for
packages/ui/src/components/custom/dock.tsx:183-197, a Marker story for
packages/ui/src/components/custom/marker.tsx:78-78, and a Receipt story for
packages/ui/src/components/custom/receipt.tsx:64-64. Use each component’s
existing story conventions and keep the current exports and tests intact.
- Around line 13-105: Update ChoiceList, ChoiceRow, ChoiceKey, ChoiceHint,
ChoiceFree, ChoiceFreeTextarea, and ChoiceFreeBar in
packages/ui/src/components/custom/choice.tsx (lines 13-105) to use
React.forwardRef with refs matching each wrapped DOM element, while preserving
intrinsic props, className merging, and prop spreading. Apply the same
forwarded-ref wrapper contract to DockRoot and its DOM-backed subcomponents in
packages/ui/src/components/custom/dock.tsx (lines 18-181), Marker and MarkerMeta
in packages/ui/src/components/custom/marker.tsx (lines 30-76), and the Receipt
root in packages/ui/src/components/custom/receipt.tsx (lines 29-62); no other
behavior should change.

In `@web/src/components/assistant-ui/timeline-row-estimates.ts`:
- Around line 49-51: Update the changed-files branch in the row estimate logic
to use the shared exported CHANGED_FILES_VISIBLE_CAP, matching
SessionChangedFilesRowView’s maximum of eight visible files plus one “+N more”
line when additional files exist. Replace the full row.files.length-based
calculation while preserving the collapsed estimate and normal behavior for
collections within the cap.

In `@web/src/styles.css`:
- Around line 65-79: Update the gradient in .session-shimmer to replace the
hardcoded rgba(246, 246, 248, 0.9) highlight with the existing design-token
variable that best matches the intended shine color, using tokens defined in
packages/ui/src/tokens.css.

In `@web/src/systems/runtime/components/runtime-selector/model-row.tsx`:
- Around line 126-146: Update the favorite indicator styling in the model-row
component so the unfavorited star is visible for coarse pointers without
changing its current desktop hover/highlight behavior. Adjust the conditional
opacity classes in the `data-favorite-indicator` span while preserving its
existing click handling and 24px hit target.

In `@web/src/systems/session/components/clarification-dock.tsx`:
- Around line 62-77: Update the ChoiceRow key in the dock.choices.map rendering
within the clarification dock to use the choice index instead of the
server-supplied choice label, preserving the existing row behavior and
rendering.

In `@web/src/systems/session/components/permission-dock.tsx`:
- Around line 129-138: Add disabled={isSubmitting} to the reject-always button
in the permission decision menu, alongside the existing decision buttons, so
repeated clicks cannot trigger another submission while the current decision is
in flight.
- Around line 158-160: Update the isSubmitting branch in the permission dock to
pass an explicit live-region role to Dock.Status: use role="status" for normal
submissions and role="alert" when the submitting state represents a retryable
danger status, reusing the existing danger-state condition.

In `@web/src/systems/session/components/session-decision-dock.tsx`:
- Around line 22-38: Update the session decision dock’s clarifications query
handling around useSessionClarifications to explicitly render a quiet
Dock.Status error state when clarifications.error is present, while preserving
the existing pending clarification and permission rendering for successful
responses.

In `@web/src/systems/session/components/session-resume-failure.tsx`:
- Around line 36-44: Scope the Escape handling in handleEscape so the
session-resume failure banner only dismisses when the event has not already been
handled by another UI control, preserving Escape behavior for dialogs, popovers,
and the runtime selector. Keep the existing document listener lifecycle
unchanged.

In `@web/src/systems/session/components/thinking-block.tsx`:
- Around line 88-98: Update the expanded thinking-content div in the thinking
block to be keyboard focusable and accessible, adding an appropriate tabindex
and accessible label while preserving its existing scrolling behavior and
styling.

In `@web/src/systems/session/components/tool-call-card.tsx`:
- Around line 65-85: Update previewFor to always return a non-empty preview for
failed tool calls, including failures where toolResult.error is absent. For Bash
failures, prefer the first line of stderr and otherwise fall back to stderr; for
other failed tools, use available stderr before the generic "Tool call failed"
message, while preserving the existing error and success-summary behavior.

In `@web/src/systems/session/components/tool-renderers/bash-content.tsx`:
- Around line 36-39: Update the bash content renderer’s errorText handling
alongside output and stderr: apply clampLines to result.error before rendering,
include its line count in totalLines, and ensure overflow reflects combined
output, stderr, and error lines so the “Show full output” count and truncation
behavior are accurate.

In `@web/src/systems/session/components/tool-renderers/generic-content.tsx`:
- Around line 28-41: Update GenericContent’s hasResult check to recognize every
non-empty field covered by toolResultIsEmpty, including preview, artifacts,
stderr, filePath, and structuredPatch, while preserving the existing stdout,
content, and error checks. Ensure GenericContent renders formatResult(result)
whenever tool-call-card.tsx mounts the expanded body via hasOutput, preventing
blank expanded content.

In `@web/src/systems/session/components/tool-renderers/todo-content.tsx`:
- Around line 71-87: Update the todo list rendering in the todos.map callback to
use a unique, stable key for each item instead of item.content alone, preventing
collisions when multiple todos share the same text. Prefer an existing unique
todo identifier or the item’s stable position if no identifier is available;
leave the displayed content unchanged.

In `@web/src/systems/session/hooks/use-permission-dock.ts`:
- Around line 61-70: Complete the reject split menu dismissal and keyboard
behavior: in web/src/systems/session/hooks/use-permission-dock.ts lines 61-70,
register touchstart alongside mousedown and add a menuOpen-gated keydown
listener that closes on Escape; in
web/src/systems/session/components/permission-dock.tsx lines 123-140, move focus
to the menuitem when opening and restore focus to the trigger when closing,
preserving the existing menu/menuitem roles.

In `@web/src/systems/session/lib/pending-permissions.ts`:
- Around line 60-62: Update the supported-decision filtering logic near
FALLBACK_PERMISSION_DECISIONS so an explicitly advertised, non-empty set with no
recognized canonical decisions returns an empty list rather than all fallback
decisions. Preserve the fallback-to-all behavior only when
permission.supportedDecisions is absent or empty, ensuring the UI never renders
decisions unsupported by the runtime.

---

Outside diff comments:
In `@web/src/components/assistant-ui/session-composer.tsx`:
- Around line 59-74: Wire the existing onSteerPrompt prop into SessionComposer
by destructuring it and adding a Steer control in the busy-controls block
alongside Queue and Interrupt. Invoke it through handleBusyInputAction with the
error message "Couldn't steer prompt.", matching the existing sibling-handler
pattern.

In `@web/src/components/assistant-ui/session-working-row.tsx`:
- Around line 56-67: Update the reducedMotion branch in SessionWorkingRow to
retain the elapsed-time display shown by the animated branch, rendering “Working
for Xs” without the animation dots. Preserve the existing reduced-motion layout
and accessibility attributes; only adjust the displayed status and timer update
behavior as needed.

---

Nitpick comments:
In `@web/src/components/assistant-ui/__tests__/session-timeline.logic.test.ts`:
- Around line 650-658: Add a test alongside the existing “Should split marker
clusters when the kind changes” case that passes two same-kind marker events
with different turnId values to deriveSessionRows, then assert they produce two
separate single-item rows. This should exercise the turnId-change boundary in
deriveBaseRows while preserving the existing kind-change coverage.

In `@web/src/components/assistant-ui/session-changed-files-row.tsx`:
- Around line 63-107: Replace the ad-hoc typography and spacing values with
shared calm-surface transcript tokens, adding the missing scale to
packages/ui/src/tokens.css as needed. In
web/src/components/assistant-ui/session-changed-files-row.tsx lines 63-107,
update the ChangedFiles row layout; in
web/src/components/assistant-ui/session-timeline-render.tsx lines 103-172,
update the timeline row styles; and in
web/src/components/assistant-ui/session-working-row.tsx lines 62-83, update the
working row styles. In web/src/components/assistant-ui/session-user-message.tsx
lines 51-74, replace the listed text and padding literals and align
USER_CLAMP_MAX_PX with the max-h-44 token.

In `@web/src/components/assistant-ui/session-composer-queued-prompts.tsx`:
- Around line 15-98: The SessionComposerQueuedPrompts controls currently
duplicate shared button styling with native buttons and ICON_BUTTON_CLASS.
Reintroduce the Button primitive from `@compozy/ui` for the Steer, Edit, and
Remove actions, using existing icon-only sizing and variants where available;
remove the bespoke class-based button styling while preserving their labels,
icons, disabled state, test IDs, and callbacks.

In `@web/src/components/assistant-ui/session-message-parts.tsx`:
- Around line 21-27: The component name and props no longer match its
marker-only implementation. Rename SessionDataEventCard to
SessionDataEventMarker, remove the unused required data prop from its parameter
type, and update both callers in the session timeline and user message
components to use the renamed component without passing data.

In `@web/src/components/assistant-ui/session-timeline-render.tsx`:
- Around line 122-172: The disclosure button and rotating chevron are duplicated
across three transcript surfaces. In
web/src/components/assistant-ui/session-timeline-render.tsx:122-172, add a local
TranscriptDisclosure component accepting expanded, onToggle, icon, label, and
trailing, then use it in SessionWorkSummaryRow and SessionTurnFoldRowView; in
web/src/components/assistant-ui/session-changed-files-row.tsx:63-107, replace
the inline disclosure markup with TranscriptDisclosure while passing DiffStat as
trailing.

In `@web/src/components/assistant-ui/session-working-row.tsx`:
- Around line 34-38: Replace the locally defined WORKING_DOT_DELAYS and
duplicated working-dot rendering in the session working row with the exported
TypingDots component from `@compozy/ui`. Reuse TypingDots directly, or extend it
with the required styling and animation-delay props so the existing appearance
is preserved without local dot-animation logic.

In `@web/src/systems/runtime/components/runtime-selector/provider-chips.tsx`:
- Around line 62-70: Update the Home and End handlers in the provider-chips
keyboard navigation logic to move directly to the corresponding edge: Home
should focus the first chip and End should focus the last chip, using the
appropriate non-wrapping direction or edge-focus behavior. Preserve the
railFilter fallback when no providers exist.

In `@web/src/systems/runtime/components/runtime-selector/selector-footer.tsx`:
- Around line 26-34: Update the FooterNote mode prop to use the existing
reasoning-mode union type instead of string, ensuring only supported values
reach data-reasoning-mode. Reuse the established union symbol from the
surrounding runtime selector types and preserve the component’s existing
behavior.

In `@web/src/systems/session/components/__tests__/session-decision-dock.test.tsx`:
- Around line 154-155: Replace the arbitrary 10 ms setTimeout waits in the
negative assertion cases with deterministic synchronization, such as awaiting
Promise.resolve() or waitFor on an observable post-condition. Update both
assertion blocks involving approveSession while preserving their existing
not.toHaveBeenCalled expectations.

In `@web/src/systems/session/components/__tests__/session-goal-strip.test.tsx`:
- Around line 128-141: The session goal strip tests only cover the draft
affordance; add a test for the replace branch in SessionGoalStrip. Render with a
replace composerAffordance containing an expectedRunId and objective, assert the
button is labeled “Draft replacement,” and verify clicking it calls
onPrefillComposer with `/goal replace <expectedRunId> <objective>`.

In `@web/src/systems/session/components/__tests__/thinking-block.test.tsx`:
- Around line 81-96: Update the ThinkingBlock alignment test around the “Should
align the reasoning body…” case to assert a stable rail token, utility class, or
data attribute instead of the literal ml-[25px] and pl-[11px] Tailwind values.
Add or reuse the corresponding semantic marker in ThinkingBlock so the test
verifies the shared detail-rail alignment contract while retaining the existing
border and visual-style assertions.

In `@web/src/systems/session/components/__tests__/tool-call-card.test.tsx`:
- Around line 151-164: Update the assertions in the running and success cases of
the tool-call card tests to rely on the existing data-status and aria-label
contract instead of asserting the text-subtle CSS class. If verifying the
absence of a signal hue is necessary, assert that text-success and text-danger
are not present while preserving the current status and accessible-label checks.

In `@web/src/systems/session/components/goal/session-goal-strip.tsx`:
- Around line 119-128: Move aria-live="polite" from the section element to the
facts span around the status text (the span near lines 149-154). Keep the
section’s existing data attributes and disclosure structure unchanged so only
changing status facts are announced, not expandable content.

In `@web/src/systems/session/components/runtime-activity-notice.tsx`:
- Around line 214-219: Update the progress Marker in the runtime activity notice
to use its supported tone prop instead of data-marker-tone. Pass a valid
MarkerTone value, such as info, so the marker and Activity icon receive the
intended styling; leave the existing role, test ID, and icon unchanged.

In `@web/src/systems/session/components/session-resume-failure.tsx`:
- Around line 46-66: The session-level danger banner is duplicated and uses an
ad-hoc body font size. In
web/src/systems/session/components/session-resume-failure.tsx#L46-L66, extract
the shared icon, title, and body banner shell into a reusable component and
replace text-[12px] with the matching typography token from the design system;
in web/src/systems/session/components/goal/session-goal-header.tsx#L25-L39,
render that shared component instead of duplicating the wrapper markup.

In `@web/src/systems/session/components/thinking-block.tsx`:
- Around line 101-102: Remove the redundant custom comparator from the memoized
thinking block component, identified by the comparator comparing prev.thinking
and next.thinkingComplete. Use plain memo(...) now that the component only
receives thinking and thinkingComplete props.
- Around line 41-87: The live and non-live branches in the thinking trigger
duplicate the same button contract. Refactor the conditional around a single
button element, keeping type, data-testid, aria-expanded, onClick, and shared
structural behavior on that button; conditionally render the existing live or
completed inner content and retain each state’s distinct classes.

In `@web/src/systems/session/hooks/use-session-goal-header.ts`:
- Around line 31-39: Update the useSessionGoalHeader hook signature to move
onPrefillComposer into the existing options object, alongside stream, and adjust
all call sites and related tests to pass it by name without undefined positional
placeholders. Preserve the current callback behavior and stream option
semantics.

In `@web/src/systems/session/lib/pending-permissions.ts`:
- Around line 33-49: The derivePendingPermissions call in SessionDecisionDock
should be memoized with useMemo, using messages as its dependency so permission
derivation only reruns when the transcript changes. Keep
derivePendingPermissions unchanged and update the call site in
SessionDecisionDock.
- Around line 66-82: Extract the duplicated command→resource subject derivation
from permissionSubject and permissionReceiptSubject into a shared helper,
supporting the JSON fallback only where needed. Update both functions to use
this helper so their subject-selection logic remains synchronized.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0cddedbb-5563-429b-936b-2b6d2127dd83

📥 Commits

Reviewing files that changed from the base of the PR and between 29dad49 and bbadb53.

⛔ Files ignored due to path filters (32)
  • .codex/hooks.json is excluded by !**/*.json
  • DESIGN.md is excluded by !**/*.md
  • docs/design/opendesign/session/SESSION-REDESIGN-SPEC.md is excluded by !docs/design/**/*, !**/*.md
  • docs/design/opendesign/session/_vc-capture-fast.html is excluded by !docs/design/**/*
  • docs/design/opendesign/session/_vc-capture-normal.html is excluded by !docs/design/**/*
  • docs/design/opendesign/session/runtime-selector-variations.html is excluded by !docs/design/**/*
  • docs/design/opendesign/session/runtime-selector-variations.html.artifact.json is excluded by !docs/design/**/*, !**/*.json
  • docs/design/opendesign/session/session-chat.html is excluded by !docs/design/**/*
  • docs/design/opendesign/session/session-design-system.html is excluded by !docs/design/**/*
  • docs/design/opendesign/session/session.css is excluded by !docs/design/**/*
  • docs/qa/scenarios/ET-web-session-goal-strip.md is excluded by !**/*.md
  • docs/qa/scenarios/ET-web-session-permission-dock.md is excluded by !**/*.md
  • docs/qa/scenarios/ET-web-session-transcript-calm-grammar.md is excluded by !**/*.md
  • docs/qa/scenarios/GL-001.md is excluded by !**/*.md
  • docs/qa/scenarios/GL-005.md is excluded by !**/*.md
  • docs/qa/scenarios/GL-006.md is excluded by !**/*.md
  • docs/qa/scenarios/GL-008.md is excluded by !**/*.md
  • docs/qa/scenarios/GL-use-response-as-goal.md is excluded by !**/*.md
  • packages/ui/src/components/custom/stories/choice.stories.tsx is excluded by !**/*.stories.tsx
  • packages/ui/src/components/custom/stories/code-block.stories.tsx is excluded by !**/*.stories.tsx
  • packages/ui/src/components/custom/stories/dock.stories.tsx is excluded by !**/*.stories.tsx
  • packages/ui/src/components/custom/stories/marker.stories.tsx is excluded by !**/*.stories.tsx
  • packages/ui/src/components/custom/stories/receipt.stories.tsx is excluded by !**/*.stories.tsx
  • web/src/systems/runtime/components/stories/runtime-selector.stories.tsx is excluded by !**/*.stories.tsx
  • web/src/systems/session/components/goal/stories/goal-status-chip.stories.tsx is excluded by !**/*.stories.tsx
  • web/src/systems/session/components/goal/stories/session-goal-strip.stories.tsx is excluded by !**/*.stories.tsx
  • web/src/systems/session/components/stories/clarification-card.stories.tsx is excluded by !**/*.stories.tsx
  • web/src/systems/session/components/stories/clarification-data-part.stories.tsx is excluded by !**/*.stories.tsx
  • web/src/systems/session/components/stories/clarification-dock.stories.tsx is excluded by !**/*.stories.tsx
  • web/src/systems/session/components/stories/permission-dock.stories.tsx is excluded by !**/*.stories.tsx
  • web/src/systems/session/components/stories/permission-prompt.stories.tsx is excluded by !**/*.stories.tsx
  • web/src/systems/session/components/stories/thinking-block.stories.tsx is excluded by !**/*.stories.tsx
📒 Files selected for processing (105)
  • packages/ui/src/components/custom/__tests__/choice.test.tsx
  • packages/ui/src/components/custom/__tests__/code-block.test.tsx
  • packages/ui/src/components/custom/__tests__/dock.test.tsx
  • packages/ui/src/components/custom/__tests__/marker.test.tsx
  • packages/ui/src/components/custom/__tests__/receipt.test.tsx
  • packages/ui/src/components/custom/__tests__/tool-call-row.test.tsx
  • packages/ui/src/components/custom/choice.tsx
  • packages/ui/src/components/custom/code-block.tsx
  • packages/ui/src/components/custom/dock.tsx
  • packages/ui/src/components/custom/marker.tsx
  • packages/ui/src/components/custom/receipt.tsx
  • packages/ui/src/components/custom/tool-call-row.tsx
  • packages/ui/src/components/custom/tool-call-status-icon.tsx
  • packages/ui/src/index.ts
  • packages/ui/src/tokens.css
  • web/src/components/assistant-ui/__tests__/session-thread.test.tsx
  • web/src/components/assistant-ui/__tests__/session-timeline.logic.test.ts
  • web/src/components/assistant-ui/session-changed-files-row.tsx
  • web/src/components/assistant-ui/session-composer-queued-prompts.tsx
  • web/src/components/assistant-ui/session-composer.tsx
  • web/src/components/assistant-ui/session-message-parts.tsx
  • web/src/components/assistant-ui/session-thread.tsx
  • web/src/components/assistant-ui/session-timeline-changed-files.ts
  • web/src/components/assistant-ui/session-timeline-fold.ts
  • web/src/components/assistant-ui/session-timeline-markers.ts
  • web/src/components/assistant-ui/session-timeline-render.tsx
  • web/src/components/assistant-ui/session-timeline-stable.ts
  • web/src/components/assistant-ui/session-timeline-summary.ts
  • web/src/components/assistant-ui/session-timeline.logic.ts
  • web/src/components/assistant-ui/session-user-message.tsx
  • web/src/components/assistant-ui/session-working-row.tsx
  • web/src/components/assistant-ui/timeline-row-estimates.ts
  • web/src/lib/api-contract.ts
  • web/src/styles.css
  • web/src/systems/os/apps/session/use-session-window-controller.tsx
  • web/src/systems/runtime/components/runtime-selector/__tests__/runtime-selector.test.tsx
  • web/src/systems/runtime/components/runtime-selector/model-list.tsx
  • web/src/systems/runtime/components/runtime-selector/model-row.tsx
  • web/src/systems/runtime/components/runtime-selector/provider-chips.tsx
  • web/src/systems/runtime/components/runtime-selector/reasoning-bar.tsx
  • web/src/systems/runtime/components/runtime-selector/reasoning-slider.tsx
  • web/src/systems/runtime/components/runtime-selector/runtime-selector.tsx
  • web/src/systems/runtime/components/runtime-selector/selector-footer.tsx
  • web/src/systems/runtime/components/runtime-selector/speed-switch.tsx
  • web/src/systems/runtime/components/runtime-selector/trigger.tsx
  • web/src/systems/runtime/components/runtime-selector/use-runtime-selector.ts
  • web/src/systems/session/components/__tests__/clarification-card.test.tsx
  • web/src/systems/session/components/__tests__/goal-head-action.test.tsx
  • web/src/systems/session/components/__tests__/goal-status-chip.test.tsx
  • web/src/systems/session/components/__tests__/permission-data-part.test.tsx
  • web/src/systems/session/components/__tests__/permission-prompt.test.tsx
  • web/src/systems/session/components/__tests__/runtime-activity-notice.test.tsx
  • web/src/systems/session/components/__tests__/session-chat-runtime-provider.test.tsx
  • web/src/systems/session/components/__tests__/session-create-dialog.test.tsx
  • web/src/systems/session/components/__tests__/session-decision-dock.test.tsx
  • web/src/systems/session/components/__tests__/session-goal-header.test.tsx
  • web/src/systems/session/components/__tests__/session-goal-strip.test.tsx
  • web/src/systems/session/components/__tests__/session-resume-failure.test.tsx
  • web/src/systems/session/components/__tests__/thinking-block.test.tsx
  • web/src/systems/session/components/__tests__/tool-call-card.test.tsx
  • web/src/systems/session/components/clarification-card.tsx
  • web/src/systems/session/components/clarification-data-part.tsx
  • web/src/systems/session/components/clarification-dock.tsx
  • web/src/systems/session/components/clarification-receipt.tsx
  • web/src/systems/session/components/goal/goal-head-action.tsx
  • web/src/systems/session/components/goal/goal-status-chip.tsx
  • web/src/systems/session/components/goal/goal-status-controls.tsx
  • web/src/systems/session/components/goal/goal-status-meters.tsx
  • web/src/systems/session/components/goal/goal-status-types.ts
  • web/src/systems/session/components/goal/session-goal-header.tsx
  • web/src/systems/session/components/goal/session-goal-strip.tsx
  • web/src/systems/session/components/permission-data-part.tsx
  • web/src/systems/session/components/permission-dock.tsx
  • web/src/systems/session/components/permission-prompt.tsx
  • web/src/systems/session/components/runtime-activity-notice.tsx
  • web/src/systems/session/components/session-create-dialog-host.tsx
  • web/src/systems/session/components/session-create-dialog.tsx
  • web/src/systems/session/components/session-decision-dock.tsx
  • web/src/systems/session/components/session-resume-failure.tsx
  • web/src/systems/session/components/thinking-block.tsx
  • web/src/systems/session/components/tool-call-card.tsx
  • web/src/systems/session/components/tool-renderers/__tests__/renderers.test.tsx
  • web/src/systems/session/components/tool-renderers/bash-content.tsx
  • web/src/systems/session/components/tool-renderers/detail-pre.tsx
  • web/src/systems/session/components/tool-renderers/edit-content.tsx
  • web/src/systems/session/components/tool-renderers/expanded-tool-content.tsx
  • web/src/systems/session/components/tool-renderers/generic-content.tsx
  • web/src/systems/session/components/tool-renderers/search-content.tsx
  • web/src/systems/session/components/tool-renderers/todo-content.tsx
  • web/src/systems/session/components/tool-renderers/write-content.tsx
  • web/src/systems/session/components/tool-result-artifact.tsx
  • web/src/systems/session/hooks/__tests__/use-session-create-dialog.test.tsx
  • web/src/systems/session/hooks/use-clarification-dock.ts
  • web/src/systems/session/hooks/use-permission-dock.ts
  • web/src/systems/session/hooks/use-session-create-dialog.ts
  • web/src/systems/session/hooks/use-session-goal-header.ts
  • web/src/systems/session/hooks/use-session-topbar-slot.tsx
  • web/src/systems/session/index.ts
  • web/src/systems/session/lib/editable-target.ts
  • web/src/systems/session/lib/message-parts.ts
  • web/src/systems/session/lib/pending-permissions.ts
  • web/src/systems/session/lib/session-create-draft.ts
  • web/src/systems/session/lib/session-data-renderers.tsx
  • web/src/systems/session/lib/tool-diff-stat.ts
  • web/src/systems/session/stores/session-create-store.ts
💤 Files with no reviewable changes (11)
  • web/src/systems/session/components/tests/clarification-card.test.tsx
  • web/src/systems/session/components/goal/goal-status-meters.tsx
  • web/src/systems/runtime/components/runtime-selector/reasoning-bar.tsx
  • web/src/systems/session/components/clarification-card.tsx
  • web/src/systems/session/components/goal/goal-status-chip.tsx
  • web/src/systems/session/components/tests/permission-prompt.test.tsx
  • web/src/systems/session/components/permission-prompt.tsx
  • web/src/systems/session/components/goal/goal-status-types.ts
  • web/src/systems/session/components/goal/goal-status-controls.tsx
  • web/src/systems/session/components/tests/goal-status-chip.test.tsx
  • packages/ui/src/components/custom/code-block.tsx

Comment thread packages/ui/src/components/custom/__tests__/tool-call-row.test.tsx
Comment thread packages/ui/src/components/custom/__tests__/tool-call-row.test.tsx
Comment thread packages/ui/src/components/custom/choice.tsx
Comment thread packages/ui/src/components/custom/choice.tsx
Comment thread web/src/components/assistant-ui/timeline-row-estimates.ts
Comment thread web/src/systems/session/components/tool-renderers/bash-content.tsx
Comment thread web/src/systems/session/components/tool-renderers/todo-content.tsx
Comment thread web/src/systems/session/hooks/use-permission-dock.ts Outdated
Comment thread web/src/systems/session/lib/pending-permissions.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/src/components/assistant-ui/scroll-to-bottom-pill.tsx`:
- Around line 24-39: Update the button in the scroll-to-bottom pill component to
set its disabled state whenever visible is false, alongside the existing
tabIndex and pointer-events handling. Preserve normal click and keyboard
behavior when visible is true, and ensure hidden pills cannot retain an active
focus target.

In `@web/src/components/assistant-ui/session-thread-messages.tsx`:
- Around line 77-103: Update the messageCount === 0 path in
session-thread-messages.tsx and ThreadStatePane so isSessionRunning is passed
through and an active session renders the active/loading surface instead of
resolving successful empty data to ThreadEmpty. Preserve the existing error,
retry, and inactive empty-transcript behavior; do not rely on the
threadEmptyWhileActive telemetry as the user-facing fallback.

In `@web/src/components/assistant-ui/session-thread.tsx`:
- Around line 27-30: Add a Compozy Cross-Surface Impact Audit near the session
surface composition documentation, explicitly checking the web transcript and
session-decision surfaces, packages/site documentation impact, workspace scope,
and cross-workspace leakage; provide evidence supporting each no-impact
conclusion and document any required impacts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a96a4fe6-9768-4a08-a844-779706781b76

📥 Commits

Reviewing files that changed from the base of the PR and between bbadb53 and ed03abb.

⛔ Files ignored due to path filters (1)
  • web/src/systems/session/components/assistant-ui/stories/session-thread.stories.tsx is excluded by !**/*.stories.tsx
📒 Files selected for processing (5)
  • web/src/components/assistant-ui/scroll-to-bottom-pill.tsx
  • web/src/components/assistant-ui/session-assistant-message.tsx
  • web/src/components/assistant-ui/session-thread-messages.tsx
  • web/src/components/assistant-ui/session-thread-viewport.tsx
  • web/src/components/assistant-ui/session-thread.tsx

Comment thread web/src/components/assistant-ui/scroll-to-bottom-pill.tsx
Comment thread web/src/components/assistant-ui/session-thread-messages.tsx
Comment thread web/src/components/assistant-ui/session-thread.tsx
Comment thread packages/ui/src/components/custom/dock.tsx Outdated
Comment thread packages/ui/src/components/custom/dock.tsx Outdated
Comment thread packages/ui/src/components/custom/dock.tsx Outdated
Comment thread packages/ui/src/components/custom/dock.tsx Outdated
Comment thread packages/ui/src/components/custom/dock.tsx Outdated
Comment thread packages/ui/src/components/custom/dock.tsx Outdated
Comment thread packages/ui/src/components/custom/dock.tsx Outdated
Comment thread packages/ui/src/components/custom/dock.tsx Outdated
Comment thread web/src/components/assistant-ui/session-timeline-render.tsx Outdated
Comment thread packages/ui/src/components/custom/dock.tsx Outdated
Comment thread packages/ui/src/components/custom/dock.tsx Outdated
Comment thread web/src/systems/session/components/permission-dock.tsx Outdated
Comment thread web/src/systems/session/components/permission-dock.tsx Outdated
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 29, 2026
@pedronauck

Copy link
Copy Markdown
Member Author

Review remediation batch published in ae4f28c.

Resolved and verified:

  • all current inline CodeRabbit and React Doctor threads (35/35);
  • direct Steer, reduced-motion elapsed time, duplicate marker boundaries, changed-files cap, calm transcript tokens/disclosures, shared Button/TypingDots primitives, provider rail Home/End, reasoning-mode typing, deterministic async tests, Goal replacement, accessible Thinking detail, status contracts, shared danger banner, Goal options API, and synchronized permission subjects;
  • tool-result preservation, non-empty failure previews, combined Bash clamping, complete GenericContent result detection, duplicate-safe Todo keys, permission menu touch/Escape/focus, and strict runtime decision filtering;
  • Storybook Session handlers now provide the successful empty clarification projection exposed by the new error state.

Verified skips:

  • no React.forwardRef migration: React 19 ref-as-prop already preserves the public ref contract and forwardRef is deprecated;
  • no duplicate Choice/Dock/Marker/Receipt stories: all four already exist;
  • no manual useMemo around permission derivation: React Compiler owns that optimization and the lint contract rejects manual memoization;
  • no invented permission danger-submission branch: the current hook has only normal submission; existing retryable clarification danger already uses role=alert;
  • duplicate-safe occurrence keys were used instead of raw array indexes to satisfy stable identity and repository lint;
  • progress Marker uses the supported neutral tone to preserve the approved calm grammar;
  • Thinking uses the compiler directly rather than retaining redundant memo;
  • the Compozy Impact Audit remains in the PR body and docs/design/opendesign/session/SESSION-REDESIGN-SPEC.md, not in a source comment.

Fresh evidence:

  • make verify: PASS, exit 0;
  • React Doctor: 100/100, zero findings in the changed Web/UI scope;
  • visual contracts: 2/2 PASS, zero blocking divergences, with canonical tokens taking precedence over prototype literals;
  • QA owner scenarios are already flagged untested for the next QA cycle.

@pedronauck

Copy link
Copy Markdown
Member Author

@greptile-apps review the latest head ae4f28c, including the remediation batch.

@greptile-apps

greptile-apps Bot commented Jul 29, 2026 •

Copy link
Copy Markdown

Greptile Summary

The PR redesigns the session transcript and decision workflow around a calmer, collapsed presentation.

  • Derives semantic tool summaries, clustered runtime markers, live-tail visibility, changed-file statistics, and compact tool-result renderers.
  • Adds shared Marker, Receipt, Dock, Choice, and transcript-oriented UI primitives and tokens.
  • Moves permission and clarification requests into a composer-adjacent decision dock with queued keyboard interactions.
  • Replaces goal status controls with a goal strip and one state-dependent window-head action.
  • Updates composer, queued prompts, runtime selection surfaces, stories, tests, design references, and QA scenarios.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
web/src/components/assistant-ui/session-timeline.logic.ts Refactors transcript derivation into specialized summary, marker, stable-row, folding, and changed-file helpers.
web/src/systems/session/components/session-decision-dock.tsx Coordinates the permission-first queue and clarification fallback above the session composer.
web/src/systems/session/hooks/use-permission-dock.ts Implements offered-decision gating, submission state, and digit-key permission shortcuts.
web/src/systems/session/hooks/use-clarification-dock.ts Implements bounded-choice shortcuts and free-text clarification submission behavior.
web/src/systems/session/hooks/use-session-goal-header.ts Reconciles goal stream state through the shared query cache and exposes state-gated goal commands.
web/src/systems/session/components/goal/session-goal-strip.tsx Replaces legacy goal controls and meters with expandable state, context, verdict, and command-staging details.
packages/ui/src/components/custom/dock.tsx Introduces the compound dock primitive used by session decision surfaces.
web/src/systems/runtime/components/runtime-selector/runtime-selector.tsx Expands the create-time runtime selector anatomy with provider, reasoning, and speed controls.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  Events[Session messages and runtime events] --> Timeline[Timeline derivation]
  Timeline --> Transcript[Collapsed transcript rows and markers]
  Events --> Pending{Pending decision?}
  Pending -->|Permission| PermissionDock[Permission dock]
  Pending -->|Clarification| ClarificationDock[Clarification dock]
  PermissionDock --> Composer[Composer zone]
  ClarificationDock --> Composer
  Goal[Goal query and loop stream] --> GoalStrip[Session goal strip]
  Goal --> HeadAction[Single window-head action]
Loading

Reviews (6): Last reviewed commit: "fix: repair session UI integration contr..." | Re-trigger Greptile

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/src/components/assistant-ui/session-composer.tsx (1)

269-282: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Use the shared Button primitive for Stop.

This generic control bypasses @compozy/ui while the same component already uses Button for adjacent actions. Preserve the behavior and styling through Button props/classes instead.

Proposed fix
-                  <button
+                  <Button
                     type="button"
+                    variant="ghost"
+                    size="sm"
                     onClick={onCancelPrompt}
                     aria-label="Stop generation"
                     data-testid="composer-stop-button"
                     className={cn(
                       "inline-flex size-7 items-center justify-center rounded-full",
@@
                   >
                     <Square className="size-3 fill-current" />
-                  </button>
+                  </Button>

As per coding guidelines, “Reuse primitives exported by packages/ui/src/index.ts and import them from @compozy/ui.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/components/assistant-ui/session-composer.tsx` around lines 269 - 282,
Replace the native button used for the Stop action in the session composer with
the shared Button primitive imported from `@compozy/ui`. Preserve onCancelPrompt,
type, aria-label, data-testid, the Square icon, and equivalent styling by
transferring the existing classes and appropriate Button props.

Source: Coding guidelines

🧹 Nitpick comments (3)
packages/ui/src/components/custom/dock-key.tsx (1)

10-13: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace the ad-hoc spacing and typography values.

ml-[3px] and text-[9px] bypass the shared design system in this public primitive. Use documented token-backed utilities instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/components/custom/dock-key.tsx` around lines 10 - 13, Update
the kbd element in the dock-key primitive to replace the arbitrary ml-[3px] and
text-[9px] utilities with documented, token-backed spacing and typography
utilities from the shared design system, while preserving the existing className
composition and props.

Source: Coding guidelines

packages/ui/src/components/custom/dock-actions.tsx (1)

7-15: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace arbitrary pixel utilities with shared design tokens.

The new Dock primitives encode spacing, typography, and sizing directly in arbitrary Tailwind values instead of using the repository’s token system.

  • packages/ui/src/components/custom/dock-actions.tsx#L7-L15: replace gap-[7px].
  • packages/ui/src/components/custom/dock-count.tsx#L7-L16: replace text-[10px].
  • packages/ui/src/components/custom/dock-deadline.tsx#L7-L16: replace text-[10px].
  • packages/ui/src/components/custom/dock-meta.tsx#L7-L19: replace mt-[5px] and text-[11px].
  • packages/ui/src/components/custom/dock-pre.tsx#L7-L21: replace max-h-[130px] and text-[11px].
  • packages/ui/src/components/custom/dock-status.tsx#L13-L26: replace mt-[7px] and text-[11px].

As per coding guidelines, design values must come from packages/ui/src/tokens.css and DESIGN.md.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/components/custom/dock-actions.tsx` around lines 7 - 15,
Replace the arbitrary Tailwind values in DockActions, DockCount, DockDeadline,
DockMeta, DockPre, and DockStatus with the corresponding shared design-token
utilities defined by tokens.css and DESIGN.md: gap-[7px], text-[10px], mt-[5px],
text-[11px], max-h-[130px], and mt-[7px]. Apply the required changes in
packages/ui/src/components/custom/dock-actions.tsx (lines 7-15), dock-count.tsx
(7-16), dock-deadline.tsx (7-16), dock-meta.tsx (7-19), dock-pre.tsx (7-21), and
dock-status.tsx (13-26), preserving each component’s existing layout and
behavior.

Source: Coding guidelines

web/src/components/assistant-ui/transcript-disclosure.tsx (1)

49-56: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Arbitrary size value instead of a scale/token step.

size-[11px] doesn't correspond to a spacing/size scale step (nearest are size-2.5/size-3). Coding guidelines call for using tokens/scale values rather than inventing sizing values, though similar bracketed pixel values already exist elsewhere in the codebase.

♻️ Suggested tweak
-          className={cn(
-            "size-[11px] shrink-0 text-faint transition-transform duration-slow ease-out motion-reduce:transition-none",
-            expanded ? "rotate-90" : null
-          )}
+          className={cn(
+            "size-3 shrink-0 text-faint transition-transform duration-slow ease-out motion-reduce:transition-none",
+            expanded ? "rotate-90" : null
+          )}

Based on coding guidelines: "Use design values from packages/ui/src/tokens.css and DESIGN.md; do not invent colors, typography, radius, spacing, or motion values."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/components/assistant-ui/transcript-disclosure.tsx` around lines 49 -
56, Replace the arbitrary `size-[11px]` class on the `ChevronRight` in the
transcript disclosure with the nearest approved size token, using `size-2.5` or
`size-3` according to the intended icon dimensions; leave the remaining classes
and rotation behavior unchanged.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/src/systems/session/mocks/handlers.ts`:
- Around line 253-260: Update the clarification-response handler using
sessionById to validate that the fixture session’s workspace_id matches
params.workspace_id before returning 200. Return the existing 404 response for
missing or cross-workspace sessions, while preserving the empty clarifications
payload for valid matches. Add a regression test covering an existing session
requested with a different workspace_id and assert it returns 404.

---

Outside diff comments:
In `@web/src/components/assistant-ui/session-composer.tsx`:
- Around line 269-282: Replace the native button used for the Stop action in the
session composer with the shared Button primitive imported from `@compozy/ui`.
Preserve onCancelPrompt, type, aria-label, data-testid, the Square icon, and
equivalent styling by transferring the existing classes and appropriate Button
props.

---

Nitpick comments:
In `@packages/ui/src/components/custom/dock-actions.tsx`:
- Around line 7-15: Replace the arbitrary Tailwind values in DockActions,
DockCount, DockDeadline, DockMeta, DockPre, and DockStatus with the
corresponding shared design-token utilities defined by tokens.css and DESIGN.md:
gap-[7px], text-[10px], mt-[5px], text-[11px], max-h-[130px], and mt-[7px].
Apply the required changes in packages/ui/src/components/custom/dock-actions.tsx
(lines 7-15), dock-count.tsx (7-16), dock-deadline.tsx (7-16), dock-meta.tsx
(7-19), dock-pre.tsx (7-21), and dock-status.tsx (13-26), preserving each
component’s existing layout and behavior.

In `@packages/ui/src/components/custom/dock-key.tsx`:
- Around line 10-13: Update the kbd element in the dock-key primitive to replace
the arbitrary ml-[3px] and text-[9px] utilities with documented, token-backed
spacing and typography utilities from the shared design system, while preserving
the existing className composition and props.

In `@web/src/components/assistant-ui/transcript-disclosure.tsx`:
- Around line 49-56: Replace the arbitrary `size-[11px]` class on the
`ChevronRight` in the transcript disclosure with the nearest approved size
token, using `size-2.5` or `size-3` according to the intended icon dimensions;
leave the remaining classes and rotation behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e49b4fd0-66aa-4281-9ce7-6dd9766eae2f

📥 Commits

Reviewing files that changed from the base of the PR and between ed03abb and ae4f28c.

⛔ Files ignored due to path filters (1)
  • DESIGN.md is excluded by !**/*.md
📒 Files selected for processing (70)
  • packages/ui/src/components/custom/__tests__/tool-call-row.test.tsx
  • packages/ui/src/components/custom/dock-actions.tsx
  • packages/ui/src/components/custom/dock-body.tsx
  • packages/ui/src/components/custom/dock-count.tsx
  • packages/ui/src/components/custom/dock-deadline.tsx
  • packages/ui/src/components/custom/dock-eyebrow.tsx
  • packages/ui/src/components/custom/dock-head.tsx
  • packages/ui/src/components/custom/dock-key.tsx
  • packages/ui/src/components/custom/dock-meta.tsx
  • packages/ui/src/components/custom/dock-pre.tsx
  • packages/ui/src/components/custom/dock-status.tsx
  • packages/ui/src/components/custom/dock-title.tsx
  • packages/ui/src/components/custom/dock.tsx
  • packages/ui/src/tokens.css
  • web/src/components/assistant-ui/__tests__/session-thread.test.tsx
  • web/src/components/assistant-ui/__tests__/session-timeline.logic.test.ts
  • web/src/components/assistant-ui/scroll-to-bottom-pill.tsx
  • web/src/components/assistant-ui/session-changed-files-row.tsx
  • web/src/components/assistant-ui/session-composer-queued-prompts.tsx
  • web/src/components/assistant-ui/session-composer.tsx
  • web/src/components/assistant-ui/session-message-parts.tsx
  • web/src/components/assistant-ui/session-thread-messages.tsx
  • web/src/components/assistant-ui/session-thread-states.tsx
  • web/src/components/assistant-ui/session-timeline-changed-files.ts
  • web/src/components/assistant-ui/session-timeline-render.tsx
  • web/src/components/assistant-ui/session-user-message.tsx
  • web/src/components/assistant-ui/session-working-row.tsx
  • web/src/components/assistant-ui/timeline-row-estimates.ts
  • web/src/components/assistant-ui/transcript-disclosure.tsx
  • web/src/styles.css
  • web/src/systems/os/apps/session/use-session-window-controller.tsx
  • web/src/systems/runtime/components/runtime-selector/__tests__/runtime-selector.test.tsx
  • web/src/systems/runtime/components/runtime-selector/model-row.tsx
  • web/src/systems/runtime/components/runtime-selector/provider-chips.tsx
  • web/src/systems/runtime/components/runtime-selector/selector-footer.tsx
  • web/src/systems/session/components/__tests__/runtime-activity-notice.test.tsx
  • web/src/systems/session/components/__tests__/session-decision-dock.test.tsx
  • web/src/systems/session/components/__tests__/session-goal-header.test.tsx
  • web/src/systems/session/components/__tests__/session-goal-strip.test.tsx
  • web/src/systems/session/components/__tests__/session-resume-failure.test.tsx
  • web/src/systems/session/components/__tests__/thinking-block.test.tsx
  • web/src/systems/session/components/__tests__/tool-call-card.test.tsx
  • web/src/systems/session/components/clarification-dock.tsx
  • web/src/systems/session/components/goal/session-goal-header-container.tsx
  • web/src/systems/session/components/goal/session-goal-header.tsx
  • web/src/systems/session/components/goal/session-goal-strip.tsx
  • web/src/systems/session/components/permission-data-part.tsx
  • web/src/systems/session/components/permission-dock.tsx
  • web/src/systems/session/components/runtime-activity-notice.tsx
  • web/src/systems/session/components/session-danger-banner.tsx
  • web/src/systems/session/components/session-decision-dock.tsx
  • web/src/systems/session/components/session-resume-failure.tsx
  • web/src/systems/session/components/thinking-block.tsx
  • web/src/systems/session/components/tool-call-card.tsx
  • web/src/systems/session/components/tool-renderers/__tests__/renderers.test.tsx
  • web/src/systems/session/components/tool-renderers/bash-content.tsx
  • web/src/systems/session/components/tool-renderers/generic-content.tsx
  • web/src/systems/session/components/tool-renderers/todo-content.tsx
  • web/src/systems/session/hooks/use-permission-dock.ts
  • web/src/systems/session/hooks/use-reject-menu-elements.ts
  • web/src/systems/session/hooks/use-session-goal-header.ts
  • web/src/systems/session/lib/message-parts.ts
  • web/src/systems/session/lib/message-schemas.ts
  • web/src/systems/session/lib/pending-permissions.ts
  • web/src/systems/session/lib/permission-subject.ts
  • web/src/systems/session/lib/session-thread-repository.ts
  • web/src/systems/session/lib/tool-part-error.ts
  • web/src/systems/session/mocks/__tests__/handlers.test.ts
  • web/src/systems/session/mocks/handlers.ts
  • web/src/systems/session/types.ts
💤 Files with no reviewable changes (1)
  • web/src/systems/session/components/tests/runtime-activity-notice.test.tsx
🚧 Files skipped from review as they are similar to previous changes (28)
  • web/src/systems/session/components/tests/session-resume-failure.test.tsx
  • web/src/systems/session/components/permission-data-part.tsx
  • web/src/systems/os/apps/session/use-session-window-controller.tsx
  • web/src/components/assistant-ui/session-user-message.tsx
  • web/src/systems/session/components/tests/session-goal-strip.test.tsx
  • web/src/systems/session/lib/pending-permissions.ts
  • web/src/systems/session/hooks/use-session-goal-header.ts
  • web/src/components/assistant-ui/scroll-to-bottom-pill.tsx
  • web/src/systems/session/components/session-resume-failure.tsx
  • web/src/systems/session/components/tests/thinking-block.test.tsx
  • packages/ui/src/components/custom/tests/tool-call-row.test.tsx
  • web/src/components/assistant-ui/session-thread-messages.tsx
  • web/src/styles.css
  • web/src/systems/session/components/session-decision-dock.tsx
  • web/src/systems/session/components/goal/session-goal-strip.tsx
  • web/src/systems/session/components/tests/tool-call-card.test.tsx
  • web/src/systems/session/components/clarification-dock.tsx
  • web/src/systems/session/components/tool-renderers/todo-content.tsx
  • web/src/systems/runtime/components/runtime-selector/model-row.tsx
  • web/src/systems/runtime/components/runtime-selector/selector-footer.tsx
  • web/src/components/assistant-ui/timeline-row-estimates.ts
  • web/src/systems/session/components/tool-call-card.tsx
  • web/src/systems/session/components/goal/session-goal-header.tsx
  • web/src/components/assistant-ui/tests/session-timeline.logic.test.ts
  • web/src/components/assistant-ui/session-timeline-changed-files.ts
  • web/src/systems/session/components/permission-dock.tsx
  • web/src/components/assistant-ui/session-timeline-render.tsx
  • web/src/systems/runtime/components/runtime-selector/tests/runtime-selector.test.tsx

Comment thread web/src/systems/session/mocks/handlers.ts
@pedronauck

Copy link
Copy Markdown
Member Author

@greptile-apps review the latest head a0edbad, including the workspace-scoping follow-up.

This branch was successfully deployed

1 active deployment
Preview — f7c17707 Deployed Jul 29, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant