Skip to content

feat(webui): redesign agent workspace - #684

Merged
delete-cloud merged 2 commits into
mainfrom
feat/webui-redesign-p1
Jul 31, 2026
Merged

delete-cloud merged 2 commits into
mainfrom
feat/webui-redesign-p1

Conversation

@delete-cloud

@delete-cloud delete-cloud commented Jul 31, 2026 •

Copy link
Copy Markdown
Owner

三栏工作台重构(承接已关闭的 #683,该 PR 因分支 force-push 历史无法重开,以新 PR 交付同一分支):

布局与视觉

  • 三栏骨架:session 侧栏(状态色点徽章、checkpoint 徽标)/ 中央 turn 流 / 右侧 icon strip 面板(Diff / Memory / Settings / Checkpoints)
  • Linear 式视觉:近黑灰底、等宽字体(代码/路径/id)、单强调色、色点状态徽章;明暗双主题保持可用
  • 移动端适配:drawer 默认收起、header 小屏单列、工具条固定定位

turn 流与审批

  • 工具调用卡片:参数摘要/耗时/状态图标,连续调用分组折叠;thinking 显隐开关
  • 审批卡片:scope=session(本会话不再询问)+ deadline 制倒计时(重放/后台标签不漂移,过期即现 timed-out 态)
  • 会话内 runtime 设置:model/provider/thinking/approval 即时 PATCH

功能补齐(服务端早已支持,前端首次接线)

  • 记忆审核闭环:candidate 队列 + accept/reject(带 reason)
  • 检查点:创建/列表/恢复(restore 前确认,恢复后重放会话)
  • 会话结果面板:final_answer / verification_summary / failure_details

质量过程

  • 两轮独立评审 + codex 交叉验收:共 14 个 P2 修复(含 3 个跨会话竞态、倒计时漂移、异步归属问题)
  • 前端 88/88 vitest,tsc 干净,生产构建通过;服务端契约测试 21/21
  • postmortem:webui 无匹配模式;diff 全部位于 webui/app/src

Summary by CodeRabbit

  • New Features

    • Added responsive sidebars and right-rail panels for diffs, memory, checkpoints, and settings.
    • Added session results with Markdown rendering, verification details, and failure status.
    • Added runtime session settings for provider, model, thinking, and approval policies.
    • Added checkpoint creation, listing, confirmation-based restoration, and memory review actions.
    • Added collapsible tool groups, tool durations, thinking visibility, and approval scopes.
  • Bug Fixes

    • Improved stale-response protection, result retries, approval rollback, and cross-session error isolation.
  • Tests

    • Expanded coverage across session behavior, panels, approvals, checkpoints, settings, timelines, and review workflows.

Add the right-side operational rail, checkpoint and result views, memory review controls, expanded timeline events, and responsive workspace layout. Keep asynchronous session updates scoped to their originating session and cover the new behavior with focused UI tests.
Poll the result endpoint for a bounded window after prompt streams close so terminal details persisted asynchronously are not missed. Cover the eventual-consistency window with an App regression test and verify it through the local production WebUI path.
@coderabbitai

coderabbitai Bot commented Jul 31, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The WebUI adds responsive session navigation, right-rail panels, session results, runtime settings, checkpoint management, memory review actions, approval scopes, timeline enhancements, and guards against stale asynchronous updates.

Changes

WebUI session workspace

Layer / File(s) Summary
Contracts and client APIs
coding-agent/webui/app/src/lib/api.ts, coding-agent/webui/app/src/lib/types.ts, coding-agent/webui/app/src/lib/timeline.ts, coding-agent/webui/app/src/lib/timeline.test.ts
Runtime configuration, memory review, checkpoint, timeline, thinking, and session-result contracts and APIs are added or extended.
Timeline and interaction panels
coding-agent/webui/app/src/components/Timeline.tsx, coding-agent/webui/app/src/components/ReviewQueue.tsx, coding-agent/webui/app/src/components/MemoryPanel.tsx, coding-agent/webui/app/src/components/CheckpointsPanel.tsx, coding-agent/webui/app/src/components/RightRail.tsx, coding-agent/webui/app/src/components/*test.tsx
Timeline tools and approvals gain grouped rendering, thinking filtering, countdowns, and scopes. Review, checkpoint, and right-rail panels support loading, actions, confirmations, and stale-request protection.
Responsive workspace and session display
coding-agent/webui/app/src/App.tsx, coding-agent/webui/app/src/components/Header.tsx, coding-agent/webui/app/src/components/SessionList.tsx, coding-agent/webui/app/src/components/ResultPanel.tsx, coding-agent/webui/app/src/components/DiffPanel.tsx, coding-agent/webui/app/src/components/Composer.tsx, coding-agent/webui/app/src/index.css, coding-agent/webui/app/src/App.test.tsx, coding-agent/webui/app/src/components/*test.tsx
The application uses responsive sidebar and right-rail navigation. Session results, diff layout, thinking visibility, session lifecycle resets, and responsive styling are integrated.
Runtime settings and asynchronous guards
coding-agent/webui/app/src/App.tsx, coding-agent/webui/app/src/components/SettingsPanel.tsx, coding-agent/webui/app/src/components/SettingsPanel.test.tsx, coding-agent/webui/app/src/App.test.tsx
Runtime provider, model, thinking, and approval updates are serialized per field. Result retries, diff sequencing, approval rollback, and cross-session guards are covered by tests.

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

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant App
  participant AgentClient
  participant RightRail
  User->>App: select a session
  App->>AgentClient: load session history and result
  AgentClient-->>App: return session data
  App->>RightRail: show the selected panel
  User->>RightRail: open diff, memory, checkpoints, or settings
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% 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 clearly and concisely describes the primary change: redesigning the web UI agent workspace.
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 feat/webui-redesign-p1

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.

@delete-cloud
delete-cloud merged commit 180d03d into main Jul 31, 2026
3 of 4 checks passed
@delete-cloud
delete-cloud deleted the feat/webui-redesign-p1 branch July 31, 2026 03:11

@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: 4

Caution

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

⚠️ Outside diff range comments (1)
coding-agent/webui/app/src/components/Timeline.tsx (1)

277-315: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Client clock skew can hide the approval controls while the request is still pending.

timedOut derives only from the browser clock against a deadline computed from promptedAt. The server owns the real auto-deny. If the browser clock runs ahead of the server clock, or if promptedAt is skewed, the card renders "timed out (auto-denied)" and removes the Approve, Always allow, and Deny buttons while the server still waits for a decision. The user then cannot respond, and the request stalls until the server timeout.

The authoritative signal is the approval_result event, which already sets item.resolved. Consider keeping the controls available after the local countdown reaches zero, and showing the timed-out label only when item.resolved arrives. A grace period is a lighter alternative.

🛡️ Proposed change: keep controls until the server resolves
-  const timedOut = remaining !== null && remaining <= 0;
-  const done = !!item.resolved || timedOut;
+  const timedOut = remaining !== null && remaining <= 0;
+  // The server owns the auto-deny; only `resolved` closes the card so clock
+  // skew cannot strand a still-pending request.
+  const done = !!item.resolved;

Then render the expired hint next to the still-active controls:

         ) : (
           <div className="mt-3 flex flex-wrap items-center gap-2">
+            {timedOut && (
+              <span className="text-xs text-muted">
+                deadline passed · awaiting server decision
+              </span>
+            )}
             <button

Note that Timeline.test.tsx lines 172-209 assert the current behavior. Update those tests with whichever option you choose.

🤖 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 `@coding-agent/webui/app/src/components/Timeline.tsx` around lines 277 - 315,
Update the approval-card logic around timedOut, done, and the conditional
controls so the local deadline never marks a pending request as resolved or
hides Approve, Always allow, or Deny actions. Treat item.resolved, populated by
the authoritative approval_result event, as the only completion signal; retain
the countdown/expired hint as appropriate while controls remain available until
that signal arrives. Update the affected Timeline.test.tsx assertions to match
the new pending-after-local-expiry behavior.
🧹 Nitpick comments (10)
coding-agent/webui/app/src/components/Timeline.tsx (1)

192-205: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Expose the tool status to assistive technology.

The status indicator uses only color, shape, and a title attribute. Screen readers do not reliably announce title on a span. Add visually hidden text or an aria-label so the running, failed, and done states are available without vision.

♿ Proposed change
         {running ? (
           <span
             className="h-2 w-2 shrink-0 animate-pulse rounded-full bg-accent"
-            title="running"
+            title="running"
+            role="img"
+            aria-label="running"
           />
         ) : item.isError ? (
-          <span className="shrink-0 text-xs text-err" title="failed">
+          <span className="shrink-0 text-xs text-err" role="img" aria-label="failed" title="failed">
             ✗
           </span>
         ) : (
-          <span className="shrink-0 text-xs text-ok" title="done">
+          <span className="shrink-0 text-xs text-ok" role="img" aria-label="done" title="done">
             ✓
           </span>
         )}
🤖 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 `@coding-agent/webui/app/src/components/Timeline.tsx` around lines 192 - 205,
Add accessible status text to the three indicator spans in the Timeline
component’s running/item.isError conditional, ensuring running, failed, and done
states are announced to assistive technology without relying on title, while
preserving the existing visual indicators.
coding-agent/webui/app/src/components/RightRail.tsx (1)

16-30: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add Escape-to-close for the mobile drawer.

Below md the panel is an overlay drawer with a dimming backdrop. The backdrop closes the panel on click only. Keyboard and screen reader users have no equivalent dismissal, and the backdrop still allows Tab into the obscured main content. Add an Escape key handler so keyboard users can dismiss the drawer.

♿ Proposed change
-import type { ReactNode } from "react";
+import { useEffect, type ReactNode } from "react";
 export default function RightRail({ panel, onToggle, children }: Props) {
+  useEffect(() => {
+    if (!panel) return;
+    const onKeyDown = (e: KeyboardEvent) => {
+      if (e.key === "Escape") onToggle(panel);
+    };
+    document.addEventListener("keydown", onKeyDown);
+    return () => document.removeEventListener("keydown", onKeyDown);
+  }, [panel, onToggle]);
+
   return (
🤖 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 `@coding-agent/webui/app/src/components/RightRail.tsx` around lines 16 - 30,
Update the mobile drawer rendering in RightRail to handle Escape key presses by
calling onToggle(panel), while preserving the existing backdrop click behavior.
Limit the keyboard dismissal behavior to the open mobile panel and ensure the
handler is attached to the drawer or its overlay without affecting desktop
rendering.
coding-agent/webui/app/src/components/CheckpointsPanel.test.tsx (1)

77-108: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a case for the empty label.

This test always types a label. CheckpointsPanel passes the raw label state, and AgentClient.captureCheckpoint omits a blank label so the body becomes {}. That branch is the default flow when the user creates a checkpoint without typing a label. Assert that the captured body is {} in that case.

🤖 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 `@coding-agent/webui/app/src/components/CheckpointsPanel.test.tsx` around lines
77 - 108, Add a separate test case near the existing labeled checkpoint test
that creates a checkpoint without changing the “checkpoint label” input. Assert
that the capture callback receives an empty body object ({}) while preserving
the list refresh and capture callback expectations for the unlabeled flow.
coding-agent/webui/app/src/components/ReviewQueue.tsx (1)

131-185: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Complete the ARIA tab pattern.

The buttons declare role="tab" inside a role="tablist", but no element declares role="tabpanel". Screen readers then announce tabs that control nothing. Wrap the records list in a tabpanel and link it with aria-controls and id.

♿ Proposed change
         {TABS.map((t) => (
           <button
             key={t.id}
             role="tab"
+            id={`review-tab-${t.id}`}
+            aria-controls="review-tabpanel"
             aria-selected={tab === t.id}
-      {loading ? (
+      <div id="review-tabpanel" role="tabpanel" aria-labelledby={`review-tab-${tab}`}>
+      {loading ? (
         <div className="px-4 py-3 text-sm text-muted">Loading reviews…</div>

Close the new wrapper after the records list at line 185.

🤖 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 `@coding-agent/webui/app/src/components/ReviewQueue.tsx` around lines 131 -
185, Complete the ARIA tab pattern in the TABS rendering and records content:
add a stable id to the records container, set each tab button’s aria-controls to
that id, and wrap the loading/empty/records conditional in a role="tabpanel"
element referencing the active tab with aria-labelledby. Keep the existing
content and behavior unchanged.
coding-agent/webui/app/src/components/ReviewQueue.test.tsx (1)

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

Add coverage for the failure branches.

The suite exercises only successful responses. ReviewQueue renders two distinct error messages: reviews load failed: from load and review update failed: from transition. Add a test that returns a non-2xx response for the list request and one for the transition request, then assert the rendered error text and that the rows stay actionable.

🤖 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 `@coding-agent/webui/app/src/components/ReviewQueue.test.tsx` around lines 63 -
96, Add failure-branch tests for ReviewQueue: cover a non-2xx list response from
the load flow and assert the rendered “reviews load failed:” message, then cover
a non-2xx transition response and assert “review update failed:” while the
candidate row and its action button remain available. Reuse the existing
stubFetch, record, and client setup patterns, and verify each failure preserves
an actionable row.
coding-agent/webui/app/src/components/Timeline.test.tsx (1)

27-35: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Stub scrollIntoView with vi.spyOn so cleanup restores the prototype.

Directly assigning Element.prototype.scrollIntoView creates a non-spied prototype property, so vi.restoreAllMocks() does not revert it. Add a native-like no-op first if needed, then spy on it so the per-test stub is removed in afterEach.

🤖 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 `@coding-agent/webui/app/src/components/Timeline.test.tsx` around lines 27 -
35, Update the Timeline test setup around beforeEach so
Element.prototype.scrollIntoView is initialized with a native-like no-op when
unavailable, then stub it via vi.spyOn instead of direct assignment. Keep
afterEach’s vi.restoreAllMocks cleanup so the prototype is restored between
tests.
coding-agent/webui/app/src/components/Header.tsx (1)

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

Move PROVIDERS to a shared module.

SettingsPanel.tsx imports PROVIDERS from ./Header (line 4). This makes a panel component depend on a sibling presentation component only for a data constant. Move the list to ../lib/types or a new lib/providers.ts, then import it in both components. This keeps the dependency direction pointing at the library layer.

🤖 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 `@coding-agent/webui/app/src/components/Header.tsx` at line 15, Move the
PROVIDERS constant out of Header into a shared library module such as lib/types
or lib/providers.ts, then update both Header and SettingsPanel imports to use
that module. Remove the presentation-layer export and preserve the existing
provider list unchanged.
coding-agent/webui/app/src/App.tsx (1)

152-161: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move the localStorage write out of the state updater.

React state updater functions must be pure. This updater writes to localStorage as a side effect. React can call an updater more than once (for example under StrictMode in development), so the write can repeat. The file already has the correct pattern for theme at lines 139-146: persist in a useEffect.

♻️ Proposed refactor
+  useEffect(() => {
+    try {
+      localStorage.setItem(THINKING_LS_KEY, showThinking ? "1" : "0");
+    } catch {
+      /* ignore */
+    }
+  }, [showThinking]);
+
   const toggleThinking = useCallback(() => {
-    setShowThinking((v) => {
-      try {
-        localStorage.setItem(THINKING_LS_KEY, v ? "0" : "1");
-      } catch {
-        /* ignore */
-      }
-      return !v;
-    });
+    setShowThinking((v) => !v);
   }, []);

Note: the effect also writes on mount. If you must avoid the initial write, gate it with a ref.

🤖 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 `@coding-agent/webui/app/src/App.tsx` around lines 152 - 161, Update
toggleThinking in App.tsx so its state updater only returns the toggled value
and performs no localStorage writes. Add a useEffect tied to showThinking that
persists the current value using THINKING_LS_KEY, following the existing theme
persistence pattern.
coding-agent/webui/app/src/App.test.tsx (2)

1243-1273: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a /result branch to the fetch stub.

loadSession calls refreshResult, which issues GET /sessions/{id}/result. stubRailFetch has no branch for that path, so the stub reaches throw new Error(\unexpected fetch ${url}`)on every session selection. The tests still pass only becauserefreshResult` swallows the rejection. This hides a real request from the stub and makes future failures hard to read. Add an explicit branch that returns an empty result.

♻️ Proposed addition
         if (url.includes(`/sessions/${active.session_id}/memory/reviews`)) {
           return Promise.resolve(jsonResponse([]));
         }
+        if (url.endsWith(`/sessions/${active.session_id}/result`)) {
+          return Promise.resolve(
+            jsonResponse({
+              session_id: active.session_id,
+              status: "completed",
+              turn_status: "idle",
+              final_answer: null,
+              verification_summary: null,
+              failure_details: null,
+            }),
+          );
+        }
🤖 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 `@coding-agent/webui/app/src/App.test.tsx` around lines 1243 - 1273, Add an
explicit `/sessions/${active.session_id}/result` branch in `stubRailFetch`,
returning an empty result response before the unexpected-URL error. Keep the
existing session, runs, memory, diff, and patch branches unchanged.

1915-1930: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace the bare setTimeout(0) flush with await act(async () => {}).

Lines 1927, 2005, and 2257 flush pending work with await new Promise((resolve) => setTimeout(resolve, 0));. Two problems follow. First, the state updates that resolve afterwards run outside act(), so React can emit act warnings and the assertion can read a stale DOM. Second, each of these flushes is followed by a negative assertion (queryByText(...) is null). If one macrotask tick does not drain the promise chain, the assertion passes for the wrong reason and stops guarding the race. await act(async () => {}) drains the microtask queue inside React's batching boundary.

♻️ Proposed change (apply at lines 1927, 2005, and 2257)
-    await new Promise((resolve) => setTimeout(resolve, 0));
+    await act(async () => undefined);
     expect(screen.getByText("b-file.ts")).toBeTruthy();
     expect(screen.queryByText("a-file.ts")).toBeNull();

Import act from @testing-library/react, as SettingsPanel.test.tsx already does.

🤖 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 `@coding-agent/webui/app/src/App.test.tsx` around lines 1915 - 1930, In the
affected race-condition tests, replace each setTimeout(0) promise flush with an
empty await act(async () => {}) call so pending promise-driven state updates are
drained inside React’s act boundary before assertions. Add the act import from
`@testing-library/react`, and update all three flush sites near the stale-fetch
assertions, preserving the existing assertions and test flow.
🤖 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 `@coding-agent/webui/app/src/components/MemoryPanel.tsx`:
- Around line 15-19: Update the prop comment above client, sessionId, and
onReviewsChanged to state that the interactive review queue is added alongside
the static accepted-memories list, not that it replaces it; preserve the
existing rendering behavior.

In `@coding-agent/webui/app/src/components/ReviewQueue.tsx`:
- Around line 63-79: Reset ReviewQueue’s initializedRef and clear its loaded
review records whenever sessionId changes, so the first-load effect runs for the
new session and stale records are not displayed. Update the session-aware logic
around the ReviewQueue effect and state, or alternatively key the ReviewQueue
child in MemoryPanel by sessionId to force a remount; preserve the existing
loading behavior for each session.

In `@coding-agent/webui/app/src/components/SettingsPanel.tsx`:
- Around line 26-27: In SettingsPanel, add an effect that updates both provider
and model state from providerName and modelName whenever those props change,
preserving empty-string fallbacks for undefined values and keeping the existing
local state initialization.

In `@coding-agent/webui/app/src/index.css`:
- Line 16: Update the --font-mono declaration to quote the SFMono-Regular and
Menlo font-family names, while leaving the generic ui-monospace and monospace
keywords unquoted.

---

Outside diff comments:
In `@coding-agent/webui/app/src/components/Timeline.tsx`:
- Around line 277-315: Update the approval-card logic around timedOut, done, and
the conditional controls so the local deadline never marks a pending request as
resolved or hides Approve, Always allow, or Deny actions. Treat item.resolved,
populated by the authoritative approval_result event, as the only completion
signal; retain the countdown/expired hint as appropriate while controls remain
available until that signal arrives. Update the affected Timeline.test.tsx
assertions to match the new pending-after-local-expiry behavior.

---

Nitpick comments:
In `@coding-agent/webui/app/src/App.test.tsx`:
- Around line 1243-1273: Add an explicit `/sessions/${active.session_id}/result`
branch in `stubRailFetch`, returning an empty result response before the
unexpected-URL error. Keep the existing session, runs, memory, diff, and patch
branches unchanged.
- Around line 1915-1930: In the affected race-condition tests, replace each
setTimeout(0) promise flush with an empty await act(async () => {}) call so
pending promise-driven state updates are drained inside React’s act boundary
before assertions. Add the act import from `@testing-library/react`, and update
all three flush sites near the stale-fetch assertions, preserving the existing
assertions and test flow.

In `@coding-agent/webui/app/src/App.tsx`:
- Around line 152-161: Update toggleThinking in App.tsx so its state updater
only returns the toggled value and performs no localStorage writes. Add a
useEffect tied to showThinking that persists the current value using
THINKING_LS_KEY, following the existing theme persistence pattern.

In `@coding-agent/webui/app/src/components/CheckpointsPanel.test.tsx`:
- Around line 77-108: Add a separate test case near the existing labeled
checkpoint test that creates a checkpoint without changing the “checkpoint
label” input. Assert that the capture callback receives an empty body object
({}) while preserving the list refresh and capture callback expectations for the
unlabeled flow.

In `@coding-agent/webui/app/src/components/Header.tsx`:
- Line 15: Move the PROVIDERS constant out of Header into a shared library
module such as lib/types or lib/providers.ts, then update both Header and
SettingsPanel imports to use that module. Remove the presentation-layer export
and preserve the existing provider list unchanged.

In `@coding-agent/webui/app/src/components/ReviewQueue.test.tsx`:
- Around line 63-96: Add failure-branch tests for ReviewQueue: cover a non-2xx
list response from the load flow and assert the rendered “reviews load failed:”
message, then cover a non-2xx transition response and assert “review update
failed:” while the candidate row and its action button remain available. Reuse
the existing stubFetch, record, and client setup patterns, and verify each
failure preserves an actionable row.

In `@coding-agent/webui/app/src/components/ReviewQueue.tsx`:
- Around line 131-185: Complete the ARIA tab pattern in the TABS rendering and
records content: add a stable id to the records container, set each tab button’s
aria-controls to that id, and wrap the loading/empty/records conditional in a
role="tabpanel" element referencing the active tab with aria-labelledby. Keep
the existing content and behavior unchanged.

In `@coding-agent/webui/app/src/components/RightRail.tsx`:
- Around line 16-30: Update the mobile drawer rendering in RightRail to handle
Escape key presses by calling onToggle(panel), while preserving the existing
backdrop click behavior. Limit the keyboard dismissal behavior to the open
mobile panel and ensure the handler is attached to the drawer or its overlay
without affecting desktop rendering.

In `@coding-agent/webui/app/src/components/Timeline.test.tsx`:
- Around line 27-35: Update the Timeline test setup around beforeEach so
Element.prototype.scrollIntoView is initialized with a native-like no-op when
unavailable, then stub it via vi.spyOn instead of direct assignment. Keep
afterEach’s vi.restoreAllMocks cleanup so the prototype is restored between
tests.

In `@coding-agent/webui/app/src/components/Timeline.tsx`:
- Around line 192-205: Add accessible status text to the three indicator spans
in the Timeline component’s running/item.isError conditional, ensuring running,
failed, and done states are announced to assistive technology without relying on
title, while preserving the existing visual indicators.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b18d70cb-17f6-4afa-b04d-49d616eb8783

📥 Commits

Reviewing files that changed from the base of the PR and between 5c421ed and 07668af.

📒 Files selected for processing (24)
  • coding-agent/webui/app/src/App.test.tsx
  • coding-agent/webui/app/src/App.tsx
  • coding-agent/webui/app/src/components/CheckpointsPanel.test.tsx
  • coding-agent/webui/app/src/components/CheckpointsPanel.tsx
  • coding-agent/webui/app/src/components/Composer.tsx
  • coding-agent/webui/app/src/components/DiffPanel.tsx
  • coding-agent/webui/app/src/components/Header.tsx
  • coding-agent/webui/app/src/components/MemoryPanel.tsx
  • coding-agent/webui/app/src/components/ResultPanel.test.tsx
  • coding-agent/webui/app/src/components/ResultPanel.tsx
  • coding-agent/webui/app/src/components/ReviewQueue.test.tsx
  • coding-agent/webui/app/src/components/ReviewQueue.tsx
  • coding-agent/webui/app/src/components/RightRail.tsx
  • coding-agent/webui/app/src/components/SessionList.test.tsx
  • coding-agent/webui/app/src/components/SessionList.tsx
  • coding-agent/webui/app/src/components/SettingsPanel.test.tsx
  • coding-agent/webui/app/src/components/SettingsPanel.tsx
  • coding-agent/webui/app/src/components/Timeline.test.tsx
  • coding-agent/webui/app/src/components/Timeline.tsx
  • coding-agent/webui/app/src/index.css
  • coding-agent/webui/app/src/lib/api.ts
  • coding-agent/webui/app/src/lib/timeline.test.ts
  • coding-agent/webui/app/src/lib/timeline.ts
  • coding-agent/webui/app/src/lib/types.ts

Comment on lines +15 to +19
// When provided, the static accepted list is replaced by the interactive
// review queue (status tabs + accept/reject transitions).
client?: AgentClient;
sessionId?: string;
onReviewsChanged?: () => void;

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the prop comment: the accepted list is not replaced.

The comment states that the static accepted list is replaced by the review queue. The static "Accepted memories" section at lines 91-130 still renders whenever ReviewQueue renders, so accepted memories appear twice. Either update the comment to describe the queue as an addition, or skip the static section when the queue is active.

📝 Proposed comment fix
-  // When provided, the static accepted list is replaced by the interactive
-  // review queue (status tabs + accept/reject transitions).
+  // When provided, the interactive review queue (status tabs plus
+  // accept/reject transitions) renders above the static accepted list.
   client?: AgentClient;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// When provided, the static accepted list is replaced by the interactive
// review queue (status tabs + accept/reject transitions).
client?: AgentClient;
sessionId?: string;
onReviewsChanged?: () => void;
// When provided, the interactive review queue (status tabs plus
// accept/reject transitions) renders above the static accepted list.
client?: AgentClient;
sessionId?: string;
onReviewsChanged?: () => void;
🤖 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 `@coding-agent/webui/app/src/components/MemoryPanel.tsx` around lines 15 - 19,
Update the prop comment above client, sessionId, and onReviewsChanged to state
that the interactive review queue is added alongside the static
accepted-memories list, not that it replaces it; preserve the existing rendering
behavior.

Comment on lines +63 to +79
useEffect(() => {
if (!expanded || initializedRef.current) return;
initializedRef.current = true;
let cancelled = false;
void (async () => {
const candidates = await load("candidate");
if (cancelled || candidates === null) return;
if (candidates.length === 0) {
tabRef.current = "accepted";
setTab("accepted");
await load("accepted");
}
})();
return () => {
cancelled = true;
};
}, [expanded, load]);

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Reset the first-load guard when sessionId changes.

initializedRef is never reset. load is recreated when sessionId changes, so this effect reruns, but the guard on line 64 returns immediately. The panel then keeps showing the review records of the previous session, and the accept and reject actions post against the new sessionId.

MemoryPanel renders ReviewQueue without a key, so the component stays mounted across a session switch.

🐛 Proposed fix: clear the guard and the records on session change
   const tabRef = useRef<ReviewStatus>(tab);
+
+  // A session switch keeps this component mounted; drop the loaded records so
+  // the first expand of the new session refetches instead of showing stale rows.
+  useEffect(() => {
+    initializedRef.current = false;
+    setRecords([]);
+    setError(null);
+    setRejectingId(null);
+    setReason("");
+    tabRef.current = "candidate";
+    setTab("candidate");
+  }, [sessionId]);

An alternative is to key the child in coding-agent/webui/app/src/components/MemoryPanel.tsx at line 89 with key={sessionId}, which remounts it and resets all local state.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
if (!expanded || initializedRef.current) return;
initializedRef.current = true;
let cancelled = false;
void (async () => {
const candidates = await load("candidate");
if (cancelled || candidates === null) return;
if (candidates.length === 0) {
tabRef.current = "accepted";
setTab("accepted");
await load("accepted");
}
})();
return () => {
cancelled = true;
};
}, [expanded, load]);
useEffect(() => {
initializedRef.current = false;
setRecords([]);
setError(null);
setRejectingId(null);
setReason("");
tabRef.current = "candidate";
setTab("candidate");
}, [sessionId]);
useEffect(() => {
if (!expanded || initializedRef.current) return;
initializedRef.current = true;
let cancelled = false;
void (async () => {
const candidates = await load("candidate");
if (cancelled || candidates === null) return;
if (candidates.length === 0) {
tabRef.current = "accepted";
setTab("accepted");
await load("accepted");
}
})();
return () => {
cancelled = true;
};
}, [expanded, load]);
🤖 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 `@coding-agent/webui/app/src/components/ReviewQueue.tsx` around lines 63 - 79,
Reset ReviewQueue’s initializedRef and clear its loaded review records whenever
sessionId changes, so the first-load effect runs for the new session and stale
records are not displayed. Update the session-aware logic around the ReviewQueue
effect and state, or alternatively key the ReviewQueue child in MemoryPanel by
sessionId to force a remount; preserve the existing loading behavior for each
session.

Comment on lines +26 to +27
const [provider, setProvider] = useState(providerName ?? "");
const [model, setModel] = useState(modelName ?? "");

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Sync provider and model when the props change.

useState(providerName ?? "") and useState(modelName ?? "") read the props only on mount. App.tsx keys SettingsPanel by sessionId (line 715), so the panel does not remount while the same session stays active. A checkpoint restore rewinds session runtime settings, as the warning in CheckpointsPanel.tsx states, and App.tsx then reloads the session summary. The new providerName and modelName props arrive, but these two fields keep showing the pre-restore values. Add an effect that resyncs both fields when the props change.

🔧 Proposed fix
   const [provider, setProvider] = useState(providerName ?? "");
   const [model, setModel] = useState(modelName ?? "");
+
+  // Runtime settings can change outside this panel (for example a checkpoint
+  // restore), so track the reported session values.
+  useEffect(() => {
+    setProvider(providerName ?? "");
+  }, [providerName]);
+  useEffect(() => {
+    setModel(modelName ?? "");
+  }, [modelName]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const [provider, setProvider] = useState(providerName ?? "");
const [model, setModel] = useState(modelName ?? "");
const [provider, setProvider] = useState(providerName ?? "");
const [model, setModel] = useState(modelName ?? "");
// Runtime settings can change outside this panel (for example a checkpoint
// restore), so track the reported session values.
useEffect(() => {
setProvider(providerName ?? "");
}, [providerName]);
useEffect(() => {
setModel(modelName ?? "");
}, [modelName]);
🤖 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 `@coding-agent/webui/app/src/components/SettingsPanel.tsx` around lines 26 -
27, In SettingsPanel, add an effect that updates both provider and model state
from providerName and modelName whenever those props change, preserving
empty-string fallbacks for undefined values and keeping the existing local state
initialization.

--color-ok: #3fb950;
--color-err: #f85149;
--color-warn: #d29922;
--font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix the Stylelint value-keyword-case failure.

Stylelint reports two errors on this line for SFMono-Regular and Menlo. Quote the multi-word and proper-name families. The rule ignores quoted strings, and quoting is the recommended form for font family names that are not generic keywords.

🔧 Proposed fix
-  --font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;
+  --font-mono: ui-monospace, "SFMono-Regular", "Menlo", monospace;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
--font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;
--font-mono: ui-monospace, "SFMono-Regular", "Menlo", monospace;
🧰 Tools
🪛 Stylelint (17.14.1)

[error] 16-16: Expected "SFMono-Regular" to be "sfmono-regular" (value-keyword-case)

(value-keyword-case)


[error] 16-16: Expected "Menlo" to be "menlo" (value-keyword-case)

(value-keyword-case)

🤖 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 `@coding-agent/webui/app/src/index.css` at line 16, Update the --font-mono
declaration to quote the SFMono-Regular and Menlo font-family names, while
leaving the generic ui-monospace and monospace keywords unquoted.

Source: Linters/SAST tools

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