Repository navigation
feat(desktop): ChatV3Pane — flagged chat pane on the new spine - #6207
Conversation
📝 WalkthroughWalkthroughAdds a feature-flagged Chat v3 pane for desktop workspaces. It includes session wiring, harness selection, draft persistence, streaming Markdown, transcript rendering, approvals, tool output, pagination, retries, and outbox handling. ChangesChat v3 desktop experience
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant PaneRegistry
participant ChatV3Pane
participant ChatTransport
participant SessionClient
participant SessionView
PaneRegistry->>ChatV3Pane: register and render workspace pane
ChatV3Pane->>ChatTransport: create session with workspace and harness
ChatV3Pane->>SessionClient: create client for sessionId
SessionClient->>SessionView: provide session state and stream updates
SessionView->>ChatTransport: send prompts and approval decisions
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
Greptile SummaryThe PR adds a feature-flagged Chat V3 desktop pane backed by the shared chat client, including session creation and selection, transcript rendering, approvals, tool output, streaming Markdown, and draft persistence.
Confidence Score: 4/5The PR should not merge until failed first-message creation, failed session initialization, and the unbounded unregistered draft persistence are corrected. A transient host-service or query failure can erase a user's first prompt or leave the pane permanently loading, while the new per-session draft keys violate the desktop persistence lifecycle and guardrail. Files Needing Attention: ChatV3Pane.tsx, NewSessionView.tsx, SessionView.tsx, Composer.tsx, and packages/chat/src/react/useChatSession/useChatSession.ts
|
| Filename | Overview |
|---|---|
| apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/ChatV3Pane.tsx | Coordinates session creation and first-prompt forwarding, but does not preserve or report the prompt when asynchronous creation fails. |
| apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Composer/Composer.tsx | Implements echo-confirmed clearing for established sessions, while its dynamic localStorage persistence lacks required bounds and cleanup. |
| packages/chat/src/react/useChatSession/useChatSession.ts | Returns outbox entries from sendPrompt, but initialization rejection leaves consumers permanently loading. |
| apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/Transcript.tsx | Adds anchored scrolling, collapse overrides, approval targeting, pagination, and retryable outbox rendering without an accepted defect. |
| apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/hooks/useSessionClient/useSessionClient.ts | Wires host-service tRPC and reconnect-token WebSockets and closes clients when their identity changes. |
| apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/usePaneRegistry.tsx | Registers the chat-v3 pane only while its PostHog feature flag is enabled. |
Sequence Diagram
sequenceDiagram
participant User
participant Pane as ChatV3Pane
participant Host as chat-v3 tRPC
participant Hook as useChatSession
participant Stream as WebSocket stream
User->>Pane: Submit first prompt
Pane->>Host: createSession
Host-->>Pane: sessionId
Pane->>Hook: Mount SessionView
Hook->>Host: getSession + getItems
Hook->>Stream: subscribe from cursor
Stream-->>Hook: envelopes and deltas
Hook-->>Pane: snapshot, outbox, status
Comments Outside Diff (1)
-
packages/chat/src/react/useChatSession/useChatSession.ts, line 161-183 (link)Seed failure leaves loading stuck
When
getSessionorgetItemsrejects while the host service is unavailable, the discardedseed()promise never transitions the hook out ofloading, leaving the pane on an indefinite spinner with its composer disabled and no retry or error state.Knowledge Base Used: Desktop App (Electron main + renderer)
Prompt To Fix With AI
This is a comment left during a code review. Path: packages/chat/src/react/useChatSession/useChatSession.ts Line: 161-183 Comment: **Seed failure leaves loading stuck** When `getSession` or `getItems` rejects while the host service is unavailable, the discarded `seed()` promise never transitions the hook out of `loading`, leaving the pane on an indefinite spinner with its composer disabled and no retry or error state. **Knowledge Base Used:** [Desktop App (Electron main + renderer)](https://app.greptile.com/personal-org-6810/-/custom-context/knowledge-base/superset-sh/superset/-/docs/desktop-app.md) --- For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Prompt To Fix All With AI
### Issue 1
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/NewSessionView/NewSessionView.tsx:47-50
**First prompt clears before creation**
When session creation rejects because the host service is starting or unavailable, this callback has already returned `null`, causing `Composer` to erase the persisted first prompt even though no session or recoverable outbox entry was created.
### Issue 2
packages/chat/src/react/useChatSession/useChatSession.ts:161-183
**Seed failure leaves loading stuck**
When `getSession` or `getItems` rejects while the host service is unavailable, the discarded `seed()` promise never transitions the hook out of `loading`, leaving the pane on an indefinite spinner with its composer disabled and no retry or error state.
### Issue 3
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/SessionView/SessionView.tsx:74-76
**Session drafts grow without cleanup**
Each session creates a distinct `chat-v3-draft:${sessionId}` localStorage key, but these writers are not registered, bounded, or reconciled when sessions are deleted. Abandoned drafts therefore accumulate indefinitely in the renderer's shared synchronous localStorage quota and violate the persisted-key guardrail.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (1): Last reviewed commit: "feat(desktop): ChatV3Pane behind chat-v3..." | Re-trigger Greptile
| onSend={(content) => { | ||
| onSend(content); | ||
| return null; | ||
| }} |
There was a problem hiding this comment.
First prompt clears before creation
When session creation rejects because the host service is starting or unavailable, this callback has already returned null, causing Composer to erase the persisted first prompt even though no session or recoverable outbox entry was created.
Knowledge Base Used: Desktop App (Electron main + renderer)
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/NewSessionView/NewSessionView.tsx
Line: 47-50
Comment:
**First prompt clears before creation**
When session creation rejects because the host service is starting or unavailable, this callback has already returned `null`, causing `Composer` to erase the persisted first prompt even though no session or recoverable outbox entry was created.
**Knowledge Base Used:** [Desktop App (Electron main + renderer)](https://app.greptile.com/personal-org-6810/-/custom-context/knowledge-base/superset-sh/superset/-/docs/desktop-app.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| <Composer | ||
| disabled={session.status !== "ready"} | ||
| draftKey={`chat-v3-draft:${sessionId}`} |
There was a problem hiding this comment.
Session drafts grow without cleanup
Each session creates a distinct chat-v3-draft:${sessionId} localStorage key, but these writers are not registered, bounded, or reconciled when sessions are deleted. Abandoned drafts therefore accumulate indefinitely in the renderer's shared synchronous localStorage quota and violate the persisted-key guardrail.
Knowledge Base Used: Desktop App (Electron main + renderer)
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/SessionView/SessionView.tsx
Line: 74-76
Comment:
**Session drafts grow without cleanup**
Each session creates a distinct `chat-v3-draft:${sessionId}` localStorage key, but these writers are not registered, bounded, or reconciled when sessions are deleted. Abandoned drafts therefore accumulate indefinitely in the renderer's shared synchronous localStorage quota and violate the persisted-key guardrail.
**Knowledge Base Used:** [Desktop App (Electron main + renderer)](https://app.greptile.com/personal-org-6810/-/custom-context/knowledge-base/superset-sh/superset/-/docs/desktop-app.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
🧹 Preview Cleanup CompleteThe following preview resources have been cleaned up:
Thank you for your contribution! 🎉 |
There was a problem hiding this comment.
16 issues found across 56 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/hooks/useSessionClient/useSessionClient.ts">
<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/hooks/useSessionClient/useSessionClient.ts:26">
P1: ChatV3 cannot create/list sessions or receive live updates because the URLs wired here are not served by host-service. Register the chat-v3 tRPC and WebSocket routes (with authentication) before targeting these endpoints.</violation>
</file>
<file name="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolCallRow/ToolCallRow.tsx">
<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolCallRow/ToolCallRow.tsx:20">
P2: All tool calls render through the same generic card, so the pane ignores the protocol’s `toolKind`-specific rendering behavior for read/edit/execute/fetch/other calls. Dispatch the row on `item.toolKind` and keep a generic fallback for unsupported kinds.</violation>
<violation number="2" location="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolCallRow/ToolCallRow.tsx:24">
P3: Hovering an expanded tool card shows the closed-direction chevron because this `ToolHeader` call does not provide the current open state. Passing the actual collapsible state or making the shared header read its parent state would keep the expansion affordance consistent.</violation>
</file>
<file name="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolContentList/components/TextContent/TextContent.tsx">
<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolContentList/components/TextContent/TextContent.tsx:3">
P2: Tool text content is effectively unbounded in the protocol (z.string(), no truncation), and unlike the sibling TerminalContent (20k-char clamp + max-h-80) this <pre> renders it fully with no height cap, so a large tool output (e.g. read file) can mount one huge DOM node — a real perf risk given this pane's deliberate streaming/scroll optimizations. Consider clamping character count and adding a max-height with vertical overflow, matching the terminal's convention.</violation>
</file>
<file name="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/SessionView/SessionView.tsx">
<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/SessionView/SessionView.tsx:39">
P3: The first-prompt effect reruns on every session render, including each live-stream snapshot update, because it depends on the freshly-created `session` object rather than the fields it reads. Narrow the dependencies to `session.status` and `session.sendPrompt` (plus the actual props) so streaming does not repeatedly schedule this effect.</violation>
<violation number="2" location="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/SessionView/SessionView.tsx:55">
P2: A failed initial session request leaves the pane showing the loading spinner forever because this view has no error/retry branch and the hook never transitions out of `"loading"` on seed failure. Expose a load-error state with retry or render an actionable failure here.</violation>
<violation number="3" location="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/SessionView/SessionView.tsx:65">
P2: Loading older messages, answering an approval, or cancelling a turn can produce an unhandled promise rejection, with no user-facing failure state, when the transport request fails. Catch these action promises and surface or log the failure through the pane’s error handling.</violation>
</file>
<file name="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/SessionHeader/SessionHeader.tsx">
<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/SessionHeader/SessionHeader.tsx:36">
P2: Long session titles can make the header overflow and hide the status or connection indicator because the picker trigger is untruncated and the header adds several non-shrinking items; constraining the picker with a `min-w-0`/ellipsis treatment while reserving space for the indicators would keep the header usable.</violation>
</file>
<file name="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/TurnGroupSection/TurnGroupSection.tsx">
<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/TurnGroupSection/TurnGroupSection.tsx:90">
P2: Expanding or collapsing a tool run can affect a different run after older messages are loaded because this key is position-based rather than tied to the run’s items. A stable key derived from the tool IDs, shared with `Transcript.expandAll`, would preserve each user override across timeline updates.</violation>
</file>
<file name="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/NewSessionView/NewSessionView.tsx">
<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/NewSessionView/NewSessionView.tsx:32">
P3: Screen-reader users cannot tell which harness will be used for the next session because selection is conveyed only through the visual button variant. Add `aria-pressed={candidate === harness}` to each harness button.</violation>
<violation number="2" location="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/NewSessionView/NewSessionView.tsx:49">
P1: A failed new-session request clears the user's draft before the session exists. The send path should retain the draft until session creation and first-prompt delivery are acknowledged, rather than adapting this asynchronous operation to Composer's immediate-success `null` result.</violation>
</file>
<file name="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Composer/Composer.tsx">
<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Composer/Composer.tsx:39">
P2: Abandoned chat drafts can grow renderer localStorage without a size bound or cleanup path: each session/workspace gets a separate key and unsent drafts survive indefinitely. Add a bounded/expiring reconciliation strategy or move entity-scoped draft persistence to the supported SQLite layer.</violation>
<violation number="2" location="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Composer/Composer.tsx:39">
P2: The desktop persisted-key registry check will fail for this new `localStorage` writer because the Composer file is not registered. Add the `chat-v3-draft:*` key family to the registry with its bounds and deletion policy.</violation>
<violation number="3" location="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Composer/Composer.tsx:48">
P1: Switching sessions before a sent message is echoed can resurrect that message as a draft: the unmount cleanup saves the pending sent text, but the pending outbox/echo state is destroyed with the old session client. Make cleanup/send state durable across the session switch or remove/mark the draft when dispatching so an accepted prompt cannot be duplicated.</violation>
<violation number="4" location="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Composer/Composer.tsx:95">
P2: The send and cancel controls are unlabeled icon-only buttons, so assistive-technology users cannot identify their actions. Add explicit labels such as `Send message` and `Cancel turn`.</violation>
</file>
<file name="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/Transcript.tsx">
<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/Transcript.tsx:144">
P2: A newly queued or failed prompt can be rendered below the visible transcript without being brought into view; in particular, a failed send can leave its Retry and Discard controls inaccessible unless the user manually scrolls. Scrolling only newly added outbox rows into view would preserve the no-per-token streaming-scroll behavior while keeping pending failures actionable.</violation>
</file>
Architecture diagram
sequenceDiagram
participant Renderer as Desktop Renderer
participant Registry as usePaneRegistry
participant PostHog as PostHog Flags
participant Pane as ChatV3Pane
participant WsClient as WorkspaceClient
participant Session as SessionClient (@superset/chat)
participant Runtime as Chat Host (/chat-v3)
participant Composer as Composer
participant Transcript as Transcript + MarkdownView
Renderer->>Registry: render panes for workspace
Registry->>PostHog: NEW: read FEATURE_FLAGS.CHAT_V3 via useFeatureFlagEnabled
alt CHAT_V3 flag enabled
Registry->>Renderer: NEW: register "chat-v3" pane (legacy chat pane untouched)
else CHAT_V3 flag disabled
Registry-->>Renderer: chat-v3 pane not registered
end
Renderer->>Pane: mount ChatV3Pane(sessionId, workspaceId)
Pane->>WsClient: read hostUrl + getWsToken()
Note over Pane,Session: NEW: tRPC httpBatchLink to /chat-v3/trpc (type-only ChatRouter, host-service headers)
Pane->>Session: create SessionClient with transport/stream wiring
opt no active session
Composer->>Pane: send first prompt (UserContent)
Pane->>Runtime: tRPC createSession(workspaceId, harness)
Runtime-->>Pane: sessionId
Pane->>Session: NEW: create client for sessionId with pendingFirstPrompt
end
Session->>Runtime: NEW: open WebSocket /chat-v3 (per-reconnect token from getWsToken)
Session->>Runtime: tRPC prompt (flush pending first prompt)
Runtime-->>Session: stream session events (items, turn state, approvals)
Session-->>Transcript: snapshot with timeline groups + approvals + outbox
Note over Session,Transcript: SessionHeader mirrors WS connection and session status badges
Transcript->>Transcript: CHANGED: render grouped turns (content-visibility offscreen, anchor to latest user turn)
alt streaming tail has open fence
Transcript->>Transcript: render tail as plain pre until fence closes
else tail is closed markdown
Transcript->>Transcript: NEW: per-block memoized Streamdown (never whole message)
end
User->>Composer: enter prompt
Composer->>Session: sendPrompt(content)
Session-->>Composer: CHANGED: returns OutboxEntry(clientId)
alt outbox flush succeeds
Session->>Runtime: tRPC prompt (flush outbox)
Runtime-->>Session: stream echo confirms outbox entry removal
Session-->>Composer: outbox entry gone
Composer->>Composer: clear draft only if text is unchanged (echo-confirmed)
else outbox flush fails
Session-->>Composer: entry marked failed
Composer->>Session: retryPrompt(clientId) / discardPrompt(clientId)
end
User->>Transcript: respond to pending approval
Transcript->>Session: respondToApproval(approvalId, decision)
Session->>Runtime: tRPC respondToApproval(...)
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| const client = createTRPCClient<ChatRouter>({ | ||
| links: [ | ||
| httpBatchLink({ | ||
| url: `${hostUrl}/chat-v3/trpc`, |
There was a problem hiding this comment.
P1: ChatV3 cannot create/list sessions or receive live updates because the URLs wired here are not served by host-service. Register the chat-v3 tRPC and WebSocket routes (with authentication) before targeting these endpoints.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/hooks/useSessionClient/useSessionClient.ts, line 26:
<comment>ChatV3 cannot create/list sessions or receive live updates because the URLs wired here are not served by host-service. Register the chat-v3 tRPC and WebSocket routes (with authentication) before targeting these endpoints.</comment>
<file context>
@@ -0,0 +1,77 @@
+ const client = createTRPCClient<ChatRouter>({
+ links: [
+ httpBatchLink({
+ url: `${hostUrl}/chat-v3/trpc`,
+ headers: () => getHostServiceHeaders(hostUrl),
+ }),
</file context>
| draftKey={`chat-v3-draft:new:${workspaceId}`} | ||
| onSend={(content) => { | ||
| onSend(content); | ||
| return null; |
There was a problem hiding this comment.
P1: A failed new-session request clears the user's draft before the session exists. The send path should retain the draft until session creation and first-prompt delivery are acknowledged, rather than adapting this asynchronous operation to Composer's immediate-success null result.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/NewSessionView/NewSessionView.tsx, line 49:
<comment>A failed new-session request clears the user's draft before the session exists. The send path should retain the draft until session creation and first-prompt delivery are acknowledged, rather than adapting this asynchronous operation to Composer's immediate-success `null` result.</comment>
<file context>
@@ -0,0 +1,56 @@
+ draftKey={`chat-v3-draft:new:${workspaceId}`}
+ onSend={(content) => {
+ onSend(content);
+ return null;
+ }}
+ outbox={[]}
</file context>
| return () => { | ||
| const latest = valueRef.current; | ||
| if (latest === "") window.localStorage.removeItem(draftKey); | ||
| else window.localStorage.setItem(draftKey, latest); |
There was a problem hiding this comment.
P1: Switching sessions before a sent message is echoed can resurrect that message as a draft: the unmount cleanup saves the pending sent text, but the pending outbox/echo state is destroyed with the old session client. Make cleanup/send state durable across the session switch or remove/mark the draft when dispatching so an accepted prompt cannot be duplicated.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Composer/Composer.tsx, line 48:
<comment>Switching sessions before a sent message is echoed can resurrect that message as a draft: the unmount cleanup saves the pending sent text, but the pending outbox/echo state is destroyed with the old session client. Make cleanup/send state durable across the session switch or remove/mark the draft when dispatching so an accepted prompt cannot be duplicated.</comment>
<file context>
@@ -0,0 +1,109 @@
+ return () => {
+ const latest = valueRef.current;
+ if (latest === "") window.localStorage.removeItem(draftKey);
+ else window.localStorage.setItem(draftKey, latest);
+ };
+ }, [draftKey]);
</file context>
| return `${((item.completedAtMs - item.startedAtMs) / 1000).toFixed(1)}s`; | ||
| } | ||
|
|
||
| export function ToolCallRow({ item }: { item: ToolCall }) { |
There was a problem hiding this comment.
P2: All tool calls render through the same generic card, so the pane ignores the protocol’s toolKind-specific rendering behavior for read/edit/execute/fetch/other calls. Dispatch the row on item.toolKind and keep a generic fallback for unsupported kinds.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolCallRow/ToolCallRow.tsx, line 20:
<comment>All tool calls render through the same generic card, so the pane ignores the protocol’s `toolKind`-specific rendering behavior for read/edit/execute/fetch/other calls. Dispatch the row on `item.toolKind` and keep a generic fallback for unsupported kinds.</comment>
<file context>
@@ -0,0 +1,53 @@
+ return `${((item.completedAtMs - item.startedAtMs) / 1000).toFixed(1)}s`;
+}
+
+export function ToolCallRow({ item }: { item: ToolCall }) {
+ const duration = durationLabel(item);
+ return (
</file context>
| @@ -0,0 +1,7 @@ | |||
| export function TextContent({ text }: { text: string }) { | |||
| return ( | |||
| <pre className="overflow-x-auto whitespace-pre-wrap rounded-md bg-muted/50 p-2 font-mono text-xs"> | |||
There was a problem hiding this comment.
P2: Tool text content is effectively unbounded in the protocol (z.string(), no truncation), and unlike the sibling TerminalContent (20k-char clamp + max-h-80) this
renders it fully with no height cap, so a large tool output (e.g. read file) can mount one huge DOM node — a real perf risk given this pane's deliberate streaming/scroll optimizations. Consider clamping character count and adding a max-height with vertical overflow, matching the terminal's convention.Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolContentList/components/TextContent/TextContent.tsx, line 3: <comment>Tool text content is effectively unbounded in the protocol (z.string(), no truncation), and unlike the sibling TerminalContent (20k-char clamp + max-h-80) this <pre> renders it fully with no height cap, so a large tool output (e.g. read file) can mount one huge DOM node — a real perf risk given this pane's deliberate streaming/scroll optimizations. Consider clamping character count and adding a max-height with vertical overflow, matching the terminal's convention.</comment> <file context> @@ -0,0 +1,7 @@ +export function TextContent({ text }: { text: string }) { + return ( + <pre className="overflow-x-auto whitespace-pre-wrap rounded-md bg-muted/50 p-2 font-mono text-xs"> + {text} + </pre> </file context>
| useEffect(() => { | ||
| const timer = setTimeout(() => { | ||
| if (value === "") window.localStorage.removeItem(draftKey); | ||
| else window.localStorage.setItem(draftKey, value); |
There was a problem hiding this comment.
P2: The desktop persisted-key registry check will fail for this new localStorage writer because the Composer file is not registered. Add the chat-v3-draft:* key family to the registry with its bounds and deletion policy.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Composer/Composer.tsx, line 39:
<comment>The desktop persisted-key registry check will fail for this new `localStorage` writer because the Composer file is not registered. Add the `chat-v3-draft:*` key family to the registry with its bounds and deletion policy.</comment>
<file context>
@@ -0,0 +1,109 @@
+ useEffect(() => {
+ const timer = setTimeout(() => {
+ if (value === "") window.localStorage.removeItem(draftKey);
+ else window.localStorage.setItem(draftKey, value);
+ }, DRAFT_DEBOUNCE_MS);
+ return () => clearTimeout(timer);
</file context>
| snapshot={snapshot} | ||
| /> | ||
| ))} | ||
| {outbox.map((entry) => ( |
There was a problem hiding this comment.
P2: A newly queued or failed prompt can be rendered below the visible transcript without being brought into view; in particular, a failed send can leave its Retry and Discard controls inaccessible unless the user manually scrolls. Scrolling only newly added outbox rows into view would preserve the no-per-token streaming-scroll behavior while keeping pending failures actionable.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/Transcript.tsx, line 144:
<comment>A newly queued or failed prompt can be rendered below the visible transcript without being brought into view; in particular, a failed send can leave its Retry and Discard controls inaccessible unless the user manually scrolls. Scrolling only newly added outbox rows into view would preserve the no-per-token streaming-scroll behavior while keeping pending failures actionable.</comment>
<file context>
@@ -0,0 +1,181 @@
+ snapshot={snapshot}
+ />
+ ))}
+ {outbox.map((entry) => (
+ <div
+ className="flex flex-col items-end gap-1 self-end"
</file context>
| const duration = durationLabel(item); | ||
| return ( | ||
| <Tool defaultOpen={item.status === "running"}> | ||
| <ToolHeader |
There was a problem hiding this comment.
P3: Hovering an expanded tool card shows the closed-direction chevron because this ToolHeader call does not provide the current open state. Passing the actual collapsible state or making the shared header read its parent state would keep the expansion affordance consistent.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolCallRow/ToolCallRow.tsx, line 24:
<comment>Hovering an expanded tool card shows the closed-direction chevron because this `ToolHeader` call does not provide the current open state. Passing the actual collapsible state or making the shared header read its parent state would keep the expansion affordance consistent.</comment>
<file context>
@@ -0,0 +1,53 @@
+ const duration = durationLabel(item);
+ return (
+ <Tool defaultOpen={item.status === "running"}>
+ <ToolHeader
+ state={STATE_BY_STATUS[item.status]}
+ title={item.title}
</file context>
| firstPromptSentRef.current = true; | ||
| session.sendPrompt(pendingFirstPrompt); | ||
| onFirstPromptSent(); | ||
| }, [pendingFirstPrompt, session, onFirstPromptSent]); |
There was a problem hiding this comment.
P3: The first-prompt effect reruns on every session render, including each live-stream snapshot update, because it depends on the freshly-created session object rather than the fields it reads. Narrow the dependencies to session.status and session.sendPrompt (plus the actual props) so streaming does not repeatedly schedule this effect.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/SessionView/SessionView.tsx, line 39:
<comment>The first-prompt effect reruns on every session render, including each live-stream snapshot update, because it depends on the freshly-created `session` object rather than the fields it reads. Narrow the dependencies to `session.status` and `session.sendPrompt` (plus the actual props) so streaming does not repeatedly schedule this effect.</comment>
<file context>
@@ -0,0 +1,85 @@
+ firstPromptSentRef.current = true;
+ session.sendPrompt(pendingFirstPrompt);
+ onFirstPromptSent();
+ }, [pendingFirstPrompt, session, onFirstPromptSent]);
+
+ const runningTurnId = useMemo(() => {
</file context>
| key={candidate} | ||
| onClick={() => onHarnessChange(candidate)} | ||
| size="sm" | ||
| variant={candidate === harness ? "secondary" : "ghost"} |
There was a problem hiding this comment.
P3: Screen-reader users cannot tell which harness will be used for the next session because selection is conveyed only through the visual button variant. Add aria-pressed={candidate === harness} to each harness button.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/NewSessionView/NewSessionView.tsx, line 32:
<comment>Screen-reader users cannot tell which harness will be used for the next session because selection is conveyed only through the visual button variant. Add `aria-pressed={candidate === harness}` to each harness button.</comment>
<file context>
@@ -0,0 +1,56 @@
+ key={candidate}
+ onClick={() => onHarnessChange(candidate)}
+ size="sm"
+ variant={candidate === harness ? "secondary" : "ghost"}
+ >
+ {candidate}
</file context>
… session wiring New pane kind "chat-v3" (the "chat" registry swap stays for the flag-flip PR), gated by the chat-v3 PostHog flag. Transcript renders timeline turn groups with content-visibility offscreen containment, anchor-user-turn scrolling and approval scroll-into-view — no bottom chasing; MarkdownView is per-block memoized over splitBlocks with plain pre while a fence is open; toolKind-dispatched rows fall back to UnknownItemRow; Composer persists drafts debounced and clears only on clientId echo. Wiring: package-owned tRPC client at hostUrl/chat-v3/trpc typed off ChatRouter (type-only via tsconfig paths), WS stream via buildStreamUrl + token query param from the workspace client. useChatSession.sendPrompt now returns the outbox entry so the composer can watch for its echo. Claude-Session: https://claude.ai/code/session_01XRFCEPsfmJHu2SenDKYJ2h
bcc5334 to
ef3c57b
Compare
There was a problem hiding this comment.
Actionable comments posted: 10
🧹 Nitpick comments (3)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/SessionView/SessionView.tsx (1)
32-39: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueNarrow the effect dependencies to the values it reads.
useChatSessionreturns a new object on every render, sosessionchanges identity each render and this effect re-runs each render.firstPromptSentRefkeeps the behavior correct, but the extra runs are avoidable. Depend onsession.statusandsession.sendPromptinstead.♻️ Proposed refactor
const firstPromptSentRef = useRef(false); + const sendPrompt = session.sendPrompt; + const status = session.status; useEffect(() => { if (!pendingFirstPrompt || firstPromptSentRef.current) return; - if (session.status !== "ready") return; + if (status !== "ready") return; firstPromptSentRef.current = true; - session.sendPrompt(pendingFirstPrompt); + sendPrompt(pendingFirstPrompt); onFirstPromptSent(); - }, [pendingFirstPrompt, session, onFirstPromptSent]); + }, [pendingFirstPrompt, status, sendPrompt, onFirstPromptSent]);🤖 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 `@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/SessionView/SessionView.tsx around lines 32 - 39, Update the effect in SessionView to depend on the specific session values it reads: session.status and session.sendPrompt, rather than the unstable session object. Keep the existing pendingFirstPrompt, onFirstPromptSent, readiness check, ref guard, and prompt-sending behavior unchanged.apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/NewSessionView/NewSessionView.tsx (1)
26-37: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMap harness ids to display labels and expose the selected state.
The buttons render the raw ids
claude-codeandcodex. Add a label map so the UI shows product names. Addaria-pressedso assistive technology reports which harness is selected. Thevariantchange alone conveys selection only visually.♻️ Proposed refactor
+const HARNESS_LABELS: Record<HarnessId, string> = { + "claude-code": "Claude Code", + codex: "Codex", +}; + <div className="ml-auto flex items-center gap-1"> {HARNESSES.map((candidate) => ( <Button + aria-pressed={candidate === harness} key={candidate} onClick={() => onHarnessChange(candidate)} size="sm" variant={candidate === harness ? "secondary" : "ghost"} > - {candidate} + {HARNESS_LABELS[candidate]} </Button> ))} </div>Line 42 and line 52 also interpolate the raw id into prose. Use the label map there too.
🤖 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 `@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/NewSessionView/NewSessionView.tsx around lines 26 - 37, Update NewSessionView’s harness rendering to use a label map for HARNESSES, displaying product names instead of raw ids in the buttons and the prose at lines 42 and 52. Add aria-pressed to each harness Button based on whether candidate matches harness, while preserving the existing variant and onHarnessChange behavior.apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/TurnGroupSection/TurnGroupSection.tsx (1)
32-60: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMove
ItemRowinto its own component folder.This file defines both
ItemRowandTurnGroupSection. PutItemRowinItemRow/ItemRow.tsxwith anindex.tsbarrel export, or inline its switch intoTurnGroupSection.As per coding guidelines, “Use one folder per component” and “Use one component per file.”
🤖 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 `@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/TurnGroupSection/TurnGroupSection.tsx around lines 32 - 60, Separate the ItemRow component from TurnGroupSection by moving the ItemRow function and its related imports into an ItemRow/ItemRow.tsx component file, then add an index.ts barrel export and update TurnGroupSection imports accordingly. Preserve the existing rowKindForItem guard and item-kind switch 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
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/ChatV3Pane.tsx:
- Around line 24-32: Update the createSession callback to track whether session
creation is pending, return early while a creation is in progress, and serialize
rapid submissions. Handle rejected wiring.transport.createSession calls within
the callback, and only update pending prompt and session ID state after
successful creation; clear the pending state on both success and failure.
- Around line 39-41: Update the ChatV3Pane session/prompt state so
pendingFirstPrompt stores both the created sessionId and prompt content. In
SessionView, pass the pending content only when its stored sessionId matches the
active sessionId; clear the pending prompt when starting or selecting another
session, while preserving normal prompt handling.
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Composer/Composer.tsx:
- Around line 27-34: Replace the unbounded session-scoped localStorage
persistence around the Composer draft state and draftKey with
createElectronSQLitePersistence, or add a bounded cleanup mechanism for
chat-v3-draft:* keys that removes abandoned session drafts. Document the key
family’s bound, deletion owner/path, and feature-removal behavior near the
relevant registry or persistence declaration.
- Around line 94-106: Add accessible names to both icon-only Buttons in the
Composer component: provide a clear cancel label for the button using
onCancelTurn and a clear send label for the button using send, while preserving
their existing handlers, disabled state, variants, and icons.
- Around line 52-61: Update the pending-send handling in Composer’s useEffect so
the textarea and draft localStorage are cleared only after the tracked clientId
is confirmed as successfully sent, not merely removed from outbox. Distinguish
confirmation from discard using the existing entry status or a
clear-only-on-success signal, while preserving the valueRef check and pendingRef
cleanup.
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/MarkdownView/MarkdownView.tsx:
- Around line 6-20: Move the memoized MarkdownBlock component into a new
MarkdownBlock/MarkdownBlock.tsx file, preserving its props, Streamdown
configuration, and rendering behavior. Add an index.ts barrel export for
MarkdownBlock, then update MarkdownView to import and use the extracted
component instead of defining it locally.
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/NewSessionView/NewSessionView.tsx:
- Around line 45-53: Update the NewSessionView Composer onSend flow and its
ChatV3Pane createSession callback to return a success boolean, resolving true
only after session creation succeeds and false on failure. Have Composer.send
await this result and clear the textarea and draft key only when it is true; on
failure, preserve the draft and render a user-visible error with select-text
cursor-text classes, avoiding an unhandled rejection.
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolContentList/components/TerminalContent/TerminalContent.tsx:
- Around line 22-24: Update the <pre> element in TerminalContent to include the
select-text and cursor-text classes alongside its existing classes, allowing
users to select and copy persistent terminal output.
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/TurnGroupSection/TurnGroupSection.tsx:
- Around line 127-130: Make all persistent error messages selectable by adding
the select-text cursor-text classes to the failed-turn message in
TurnGroupSection.tsx:127-130, the outbox-failed rendering in
Transcript.tsx:153-157, and the error notice rendering in NoticeRow.tsx:4-15;
leave non-error states unchanged.
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/utils/rowKind/rowKind.test.ts:
- Around line 9-40: Update the rowKind test cases to import and use the RowKind
type, changing cases to Array<[Item, RowKind]>. Remove the expected as never
cast and pass expected directly to toBe so the compiler validates each expected
row kind.
---
Nitpick comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/NewSessionView/NewSessionView.tsx:
- Around line 26-37: Update NewSessionView’s harness rendering to use a label
map for HARNESSES, displaying product names instead of raw ids in the buttons
and the prose at lines 42 and 52. Add aria-pressed to each harness Button based
on whether candidate matches harness, while preserving the existing variant and
onHarnessChange behavior.
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/SessionView/SessionView.tsx:
- Around line 32-39: Update the effect in SessionView to depend on the specific
session values it reads: session.status and session.sendPrompt, rather than the
unstable session object. Keep the existing pendingFirstPrompt,
onFirstPromptSent, readiness check, ref guard, and prompt-sending behavior
unchanged.
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/TurnGroupSection/TurnGroupSection.tsx:
- Around line 32-60: Separate the ItemRow component from TurnGroupSection by
moving the ItemRow function and its related imports into an ItemRow/ItemRow.tsx
component file, then add an index.ts barrel export and update TurnGroupSection
imports accordingly. Preserve the existing rowKindForItem guard and item-kind
switch behavior unchanged.
🪄 Autofix
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: 0fa7c212-35e4-4506-90aa-f285f445e9f3
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (57)
apps/desktop/package.jsonapps/desktop/src/renderer/lib/persisted-keys/persisted-key-registry.test-data.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/ChatV3Pane.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Composer/Composer.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Composer/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/MarkdownView/MarkdownView.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/MarkdownView/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/MarkdownView/utils/planMarkdown/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/MarkdownView/utils/planMarkdown/planMarkdown.test.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/MarkdownView/utils/planMarkdown/planMarkdown.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/NewSessionView/NewSessionView.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/NewSessionView/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/SessionHeader/SessionHeader.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/SessionHeader/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/SessionPicker/SessionPicker.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/SessionPicker/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/SessionView/SessionView.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/SessionView/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/Transcript.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/AgentMessageRow/AgentMessageRow.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/AgentMessageRow/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ApprovalRow/ApprovalRow.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ApprovalRow/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/NoticeRow/NoticeRow.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/NoticeRow/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/PlanRow/PlanRow.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/PlanRow/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ReasoningRow/ReasoningRow.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ReasoningRow/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolCallRow/ToolCallRow.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolCallRow/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolContentList/ToolContentList.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolContentList/components/DiffContent/DiffContent.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolContentList/components/DiffContent/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolContentList/components/TerminalContent/TerminalContent.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolContentList/components/TerminalContent/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolContentList/components/TextContent/TextContent.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolContentList/components/TextContent/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolContentList/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/TurnGroupSection/TurnGroupSection.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/TurnGroupSection/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/UnknownItemRow/UnknownItemRow.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/UnknownItemRow/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/UserMessageRow/UserMessageRow.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/UserMessageRow/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/utils/rowKind/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/utils/rowKind/rowKind.test.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/utils/rowKind/rowKind.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/hooks/useSessionClient/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/hooks/useSessionClient/useSessionClient.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/usePaneRegistry.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/types.tsapps/desktop/tsconfig.jsonpackages/chat/src/client/subscribeToSession/subscribeToSession.test.tspackages/chat/src/react/useChatSession/useChatSession.ts
| const createSession = useCallback( | ||
| async (content: UserContent[] | null) => { | ||
| const created = await wiring.transport.createSession({ | ||
| commandId: crypto.randomUUID(), | ||
| workspaceId, | ||
| harness, | ||
| }); | ||
| setPendingFirstPrompt(content); | ||
| onSessionIdChange(created.sessionId); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Serialize session creation and handle failures.
Each submission creates a new commandId, but this callback has no pending guard. Rapid submissions can create multiple remote sessions. A rejected createSession call is also unhandled because the caller discards its promise. Track creation state, prevent duplicate submission, and catch the failure before updating pane state.
🤖 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
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/ChatV3Pane.tsx
around lines 24 - 32, Update the createSession callback to track whether session
creation is pending, return early while a creation is in progress, and serialize
rapid submissions. Handle rejected wiring.transport.createSession calls within
the callback, and only update pending prompt and session ID state after
successful creation; clear the pending state on both success and failure.
| activeSessionId={sessionId} | ||
| onNewSession={() => onSessionIdChange(null)} | ||
| onSelect={onSessionIdChange} |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Bind pendingFirstPrompt to its created session.
pendingFirstPrompt has no session ID. If the user selects another session before the created client becomes ready, SessionView sends that prompt to the newly selected session. Store { sessionId, content } and pass the content only when it matches the active session. Clear it when the user starts or selects another session.
🤖 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
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/ChatV3Pane.tsx
around lines 39 - 41, Update the ChatV3Pane session/prompt state so
pendingFirstPrompt stores both the created sessionId and prompt content. In
SessionView, pass the pending content only when its stored sessionId matches the
active sessionId; clear the pending prompt when starting or selecting another
session, while preserving normal prompt handling.
| const [value, setValue] = useState( | ||
| () => window.localStorage.getItem(draftKey) ?? "", | ||
| ); | ||
| const valueRef = useRef(value); | ||
| valueRef.current = value; | ||
| const pendingRef = useRef<{ clientId: string; sentText: string } | null>( | ||
| null, | ||
| ); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Bound the per-session draft keys or move them to SQLite.
draftKey is chat-v3-draft:${sessionId} for session views, so each session creates its own localStorage key. This file removes a key only when the user clears or sends that draft in that mounted Composer. A user who types text and abandons the session leaves the key behind permanently. No cap, TTL, or entity reconciliation deletes stranded session drafts, and the registry entry declares the family without a bound or deletion owner.
Add one of the following:
- an LRU or fixed cap over
chat-v3-draft:*keys, - a boot sweep that deletes draft keys for sessions that no longer exist,
- or
createElectronSQLitePersistencefor session-scoped drafts.
Also document the bound and deletion owner for the key family.
Based on learnings from the coding guidelines: "Use localStorage only for small singleton UI state; entity-scoped unbounded data or payloads larger than a few KB belongs in SQLite via createElectronSQLitePersistence" and "For every renderer localStorage key, document its bound, deletion owner or path, and behavior when the feature is removed; use caps/LRU, TTL, entity reconciliation, or a fixed-size singleton as appropriate."
🤖 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
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Composer/Composer.tsx
around lines 27 - 34, Replace the unbounded session-scoped localStorage
persistence around the Composer draft state and draftKey with
createElectronSQLitePersistence, or add a bounded cleanup mechanism for
chat-v3-draft:* keys that removes abandoned session drafts. Document the key
family’s bound, deletion owner/path, and feature-removal behavior near the
relevant registry or persistence declaration.
Source: Coding guidelines
| useEffect(() => { | ||
| const pending = pendingRef.current; | ||
| if (!pending) return; | ||
| if (outbox.some((entry) => entry.clientId === pending.clientId)) return; | ||
| pendingRef.current = null; | ||
| if (valueRef.current === pending.sentText) { | ||
| setValue(""); | ||
| window.localStorage.removeItem(draftKey); | ||
| } | ||
| }, [outbox, draftKey]); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Inspect OutboxEntryState members and discard/confirm transitions.
fd -t f 'outbox.ts' packages/chat/src | xargs rg -n -C3 'OutboxEntryState|state\s*[:=]\s*"|discard|confirm'Repository: superset-sh/superset
Length of output: 1823
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/\$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Composer/Composer.tsx"
echo "== file exists =="
test -f "$FILE" && echo yes || echo no
echo "== composer outline =="
ast-grep outline "$FILE" --view expanded | sed -n '1,220p'
echo "== composer relevant lines =="
cat -n "$FILE" | sed -n '1,140p'
echo "== references to clear/draft/pending text and outbox discard/confirm =="
rg -n -C3 'discards?|discardPrompt|onDiscard|confirm|pendingRef|sentText|setValue|localStorage.removeItem|outbox' apps/desktop/src/renderer packages -g '*.ts' -g '*.tsx' | sed -n '1,240p'Repository: superset-sh/superset
Length of output: 27039
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate ChatV3Pane files =="
fd -t f 'ChatV3Pane|Transcript|Outbox' apps/desktop/src/renderer packages -i | sed -n '1,200p'
echo "== ChatV3Pane references to discard/confirm/outbox =="
rg -n -C4 'outbox|confirm|discard|onConfirm|onDiscard|onDiscardPrompt' apps/desktop/src/renderer -g '*.ts' -g '*.tsx' | sed -n '1,260p'
echo "== focused app files =="
for name in "ChatV3Pane.tsx" "Transcript.tsx" "ChatV3Pane.test.tsx"; do
echo "-- $name --"
fd -t f "$name" apps/desktop/src/renderer -i | while read -r f; do
echo "FILE: $f"
rg -n -C4 'outbox|confirm|discard|onConfirm|onDiscard|onDiscardPrompt' "$f" || true
done
done
echo "== chat package transcript-related references =="
rg -n -C4 'outbox|confirm|discard|pendingRef|sentText|onDiscardPrompt|onConfirm' packages apps -g '*.ts' -g '*.tsx' -g '*.test-data.ts' || trueRepository: superset-sh/superset
Length of output: 50377
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/\$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Composer/Composer.tsx"
PANEDIR="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workcase/\$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane"
APPFILES="apps/desktop/src/renderer"
echo "== exact Composer file =="
wc -l "$FILE"
sed -n '1,130p' "$FILE"
echo "== exact ChatV3Pane file candidates =="
fd -i 'ChatV3Pane\.tsx$' "$APPFILES" | while read -r p; do
echo "---- $p ----"
rg -n -C5 'outbox|confirm|discard|onConfirm|onDiscard|onDiscardPrompt' "$p" || true
done
echo "== exact Transcript file candidates =="
fd -i 'Transcript\.tsx$' "$APPFILES" | while read -r p; do
echo "---- $p ----"
rg -n -C5 'outbox|confirm|discard|onConfirm|onDiscard|onDiscardPrompt' "$p" || true
done
echo "== focused app renderer search exact terms =="
rg -n -w -C3 '\'outbox\\'|\'confirm\\'|\'discard\\'|onConfirm|onDiscard|onDiscardPrompt|outbox' "$APPFILES" -g '*.ts' -g '*.tsx' | sed -n '1,260p'
echo "== focused package chat-outbox exact search =="
find packages -path '*chat*' -type f \( -name '*.ts' -o -name '*.tsx' \) -print0 | xargs -0 rg -n -w -C3 'OutboxEntryState|OutboxEntry|confirm\\(|discard\\(|outbox' | sed -n '1,220p' | rg -n -C5 'packages/chat|outbox|confirm|discard|OutboxEntry' || true
echo "== behavioral probe for current effect logic ==
python3 - <<'PY'
def current_clears(pending, outbox, value, sent_text):
if not pending:
return False
if any(entry == pending for entry in outbox):
return False
if value == sent_text:
return True
return False
for state, present, edited, failed in [
("inflight", True, False, False),
("failed", False, False, False),
("failed", False, True, False),
("queued", False, False, True),
]:
outbox = [pending := {"clientId": "x", "state": state}] if present else []
print(state, "present" if present else "absent", "edited" if edited else "same", "changed" if (outbox and pending["state"]!=state) else "same state", current_clears({"clientId": "x", "sentText": ""}, outbox, "" if not edited else "edited", ""))
PY
echo "== exact diff for current effect around lines 52-61 =="
cat -n "$FILE" | sed -n '48,64p'Repository: superset-sh/superset
Length of output: 5847
Clear the composer draft only on confirmed sends.
Composer tracks the sent clientId and clears both the textarea and draft localStorage when that entry leaves outbox. outbox can lose an entry for confirmation or discard, and Transcript exposes discard for failed outbox entries. If the user discards a failed prompt without editing the text, this path deletes the user’s message payload. Track the entry through confirmation, or pass a clear-only-on-success signal, and avoid clearing on discard.
🤖 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
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Composer/Composer.tsx
around lines 52 - 61, Update the pending-send handling in Composer’s useEffect
so the textarea and draft localStorage are cleared only after the tracked
clientId is confirmed as successfully sent, not merely removed from outbox.
Distinguish confirmation from discard using the existing entry status or a
clear-only-on-success signal, while preserving the valueRef check and pendingRef
cleanup.
| const MarkdownBlock = memo(function MarkdownBlock({ | ||
| block, | ||
| }: { | ||
| block: string; | ||
| }) { | ||
| return ( | ||
| <Streamdown | ||
| className="[&>*:first-child]:mt-0 [&>*:last-child]:mb-0" | ||
| linkSafety={{ enabled: false }} | ||
| mode="streaming" | ||
| > | ||
| {block} | ||
| </Streamdown> | ||
| ); | ||
| }); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Move MarkdownBlock into its own component file.
MarkdownBlock is a separate component. Place it in MarkdownBlock/MarkdownBlock.tsx and add an index.ts barrel export.
As per coding guidelines, “Use one component per file.”
🤖 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
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/MarkdownView/MarkdownView.tsx
around lines 6 - 20, Move the memoized MarkdownBlock component into a new
MarkdownBlock/MarkdownBlock.tsx file, preserving its props, Streamdown
configuration, and rendering behavior. Add an index.ts barrel export for
MarkdownBlock, then update MarkdownView to import and use the extracted
component instead of defining it locally.
Source: Coding guidelines
| <Composer | ||
| draftKey={`chat-v3-draft:new:${workspaceId}`} | ||
| onSend={(content) => { | ||
| onSend(content); | ||
| return null; | ||
| }} | ||
| outbox={[]} | ||
| placeholder={`Start a ${harness} session`} | ||
| /> |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
Do not clear the draft before session creation succeeds.
onSend returns null unconditionally, so Composer clears the textarea and deletes the draft key immediately. ChatV3Pane implements this callback as (content) => void createSession(content), and createSession awaits wiring.transport.createSession without a catch. If session creation fails, three things happen:
- The typed prompt is already erased and the draft key is already removed.
- The rejection surfaces only as an unhandled promise rejection.
- The pane stays on
NewSessionView, so the user sees an empty composer and no error.
Change onSend to report success, and keep the draft until creation resolves. Report the failure to the user. Follow the desktop rule that rendered error messages carry select-text cursor-text.
🐛 Proposed direction
- onSend: (content: UserContent[]) => void;
+ onSend: (content: UserContent[]) => Promise<boolean>; <Composer
draftKey={`chat-v3-draft:new:${workspaceId}`}
- onSend={(content) => {
- onSend(content);
- return null;
- }}
+ onSend={onSend}
outbox={[]}
placeholder={`Start a ${harness} session`}
/>Composer.send then needs to await the result and clear only when it is true.
Based on learnings from the coding guidelines: "Rendered error messages must include select-text cursor-text classes so users can copy them; Sonner toasts are exempt."
🤖 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
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/NewSessionView/NewSessionView.tsx
around lines 45 - 53, Update the NewSessionView Composer onSend flow and its
ChatV3Pane createSession callback to return a success boolean, resolving true
only after session creation succeeds and false on failure. Have Composer.send
await this result and clear the textarea and draft key only when it is true; on
failure, preserve the draft and render a user-visible error with select-text
cursor-text classes, avoiding an unhandled rejection.
Source: Coding guidelines
| <pre className="max-h-80 overflow-auto whitespace-pre-wrap rounded-md bg-muted/50 p-2"> | ||
| {output} | ||
| </pre> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Make terminal diagnostics selectable.
A failed command can expose its only diagnostic in this <pre>. Add select-text cursor-text so users can copy the output.
As per coding guidelines, “Rendered error messages must include select-text cursor-text classes.” The tooltip exception does not apply because this is persistent content.
Proposed fix
- <pre className="max-h-80 overflow-auto whitespace-pre-wrap rounded-md bg-muted/50 p-2">
+ <pre className="max-h-80 cursor-text select-text overflow-auto whitespace-pre-wrap rounded-md bg-muted/50 p-2">📝 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.
| <pre className="max-h-80 overflow-auto whitespace-pre-wrap rounded-md bg-muted/50 p-2"> | |
| {output} | |
| </pre> | |
| <pre className="max-h-80 cursor-text select-text overflow-auto whitespace-pre-wrap rounded-md bg-muted/50 p-2"> | |
| {output} | |
| </pre> |
🤖 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
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolContentList/components/TerminalContent/TerminalContent.tsx
around lines 22 - 24, Update the <pre> element in TerminalContent to include the
select-text and cursor-text classes alongside its existing classes, allowing
users to select and copy persistent terminal output.
Sources: Coding guidelines, Learnings
| {group.turn?.status === "failed" && ( | ||
| <div className="text-xs text-destructive"> | ||
| Turn failed{group.turn.error ? `: ${group.turn.error.message}` : ""} | ||
| </div> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Make persistent error text selectable.
These persistent error messages do not include select-text cursor-text. Users cannot reliably select and copy them.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/TurnGroupSection/TurnGroupSection.tsx#L127-L130: Addselect-text cursor-textto the failed-turn message.apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/Transcript.tsx#L153-L157: Addselect-text cursor-textwhen the outbox state is"failed".apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/NoticeRow/NoticeRow.tsx#L4-L15: Addselect-text cursor-textwhenitem.noticeKind === "error".
As per coding guidelines, “Rendered error messages must include select-text cursor-text classes so users can copy them.”
📍 Affects 3 files
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/TurnGroupSection/TurnGroupSection.tsx#L127-L130(this comment)apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/Transcript.tsx#L153-L157apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/NoticeRow/NoticeRow.tsx#L4-L15
🤖 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
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/TurnGroupSection/TurnGroupSection.tsx
around lines 127 - 130, Make all persistent error messages selectable by adding
the select-text cursor-text classes to the failed-turn message in
TurnGroupSection.tsx:127-130, the outbox-failed rendering in
Transcript.tsx:153-157, and the error notice rendering in NoticeRow.tsx:4-15;
leave non-error states unchanged.
Source: Coding guidelines
| const cases: Array<[Item, string]> = [ | ||
| [{ ...base, kind: "user_message", content: [] }, "user_message"], | ||
| [{ ...base, kind: "agent_message", text: "" }, "agent_message"], | ||
| [{ ...base, kind: "reasoning", text: "" }, "reasoning"], | ||
| [ | ||
| { | ||
| ...base, | ||
| kind: "tool_call", | ||
| title: "ls", | ||
| toolKind: "execute", | ||
| toolName: "Bash", | ||
| status: "running", | ||
| content: [], | ||
| }, | ||
| "tool_call", | ||
| ], | ||
| [{ ...base, kind: "plan", entries: [] }, "plan"], | ||
| [ | ||
| { | ||
| ...base, | ||
| kind: "approval_request", | ||
| targetItemId: null, | ||
| title: "Allow?", | ||
| status: "pending", | ||
| }, | ||
| "approval_request", | ||
| ], | ||
| [{ ...base, kind: "notice", noticeKind: "info" }, "notice"], | ||
| ]; | ||
| for (const [item, expected] of cases) { | ||
| expect(rowKindForItem(item)).toBe(expected as never); | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Keep the expected row kind type-safe.
expected as never disables the compiler check for expected row kinds. Import RowKind, type cases as Array<[Item, RowKind]>, and pass expected directly to toBe.
As per coding guidelines, “Avoid any unless necessary; preserve type safety across JavaScript/TypeScript code.”
Proposed fix
+import type { RowKind } from "./rowKind";
- const cases: Array<[Item, string]> = [
+ const cases: Array<[Item, RowKind]> = [
...
- expect(rowKindForItem(item)).toBe(expected as never);
+ expect(rowKindForItem(item)).toBe(expected);📝 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.
| const cases: Array<[Item, string]> = [ | |
| [{ ...base, kind: "user_message", content: [] }, "user_message"], | |
| [{ ...base, kind: "agent_message", text: "" }, "agent_message"], | |
| [{ ...base, kind: "reasoning", text: "" }, "reasoning"], | |
| [ | |
| { | |
| ...base, | |
| kind: "tool_call", | |
| title: "ls", | |
| toolKind: "execute", | |
| toolName: "Bash", | |
| status: "running", | |
| content: [], | |
| }, | |
| "tool_call", | |
| ], | |
| [{ ...base, kind: "plan", entries: [] }, "plan"], | |
| [ | |
| { | |
| ...base, | |
| kind: "approval_request", | |
| targetItemId: null, | |
| title: "Allow?", | |
| status: "pending", | |
| }, | |
| "approval_request", | |
| ], | |
| [{ ...base, kind: "notice", noticeKind: "info" }, "notice"], | |
| ]; | |
| for (const [item, expected] of cases) { | |
| expect(rowKindForItem(item)).toBe(expected as never); | |
| } | |
| import type { RowKind } from "./rowKind"; | |
| const cases: Array<[Item, RowKind]> = [ | |
| [{ ...base, kind: "user_message", content: [] }, "user_message"], | |
| [{ ...base, kind: "agent_message", text: "" }, "agent_message"], | |
| [{ ...base, kind: "reasoning", text: "" }, "reasoning"], | |
| [ | |
| { | |
| ...base, | |
| kind: "tool_call", | |
| title: "ls", | |
| toolKind: "execute", | |
| toolName: "Bash", | |
| status: "running", | |
| content: [], | |
| }, | |
| "tool_call", | |
| ], | |
| [{ ...base, kind: "plan", entries: [] }, "plan"], | |
| [ | |
| { | |
| ...base, | |
| kind: "approval_request", | |
| targetItemId: null, | |
| title: "Allow?", | |
| status: "pending", | |
| }, | |
| "approval_request", | |
| ], | |
| [{ ...base, kind: "notice", noticeKind: "info" }, "notice"], | |
| ]; | |
| for (const [item, expected] of cases) { | |
| expect(rowKindForItem(item)).toBe(expected); | |
| } |
🤖 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
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/utils/rowKind/rowKind.test.ts
around lines 9 - 40, Update the rowKind test cases to import and use the RowKind
type, changing cases to Array<[Item, RowKind]>. Remove the expected as never
cast and pass expected directly to toBe so the compiler validates each expected
row kind.
Source: Coding guidelines
There was a problem hiding this comment.
♻️ Duplicate comments (1)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/NewSessionView/NewSessionView.tsx (1)
45-52: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy liftDo not clear the draft before session creation succeeds.
onSendinvokes the async callback and always returnsnull.Composertreatsnullas success and removes the textarea value and persisted draft immediately. Ifwiring.transport.createSessionrejects, the draft is lost and the rejected promise is unhandled.Return an acknowledged async result from
onSend. Clear the draft only after creation succeeds. On failure, preserve the draft and render a persistent error withselect-text cursor-textclasses.Proposed direction
- onSend: (content: UserContent[]) => void; + onSend: (content: UserContent[]) => Promise<boolean>; ... - onSend={(content) => { - onSend(content); - return null; - }} + onSend={onSend}🤖 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 `@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/NewSessionView/NewSessionView.tsx around lines 45 - 52, Update the Composer onSend handler in NewSessionView so it returns the session-creation promise and acknowledges success only after onSend completes, allowing failures to preserve the draft instead of being treated as successful. Handle rejected creation by rendering a persistent error message with select-text cursor-text classes, while keeping the draft intact.Sources: Coding guidelines, Learnings
🤖 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.
Duplicate comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/NewSessionView/NewSessionView.tsx:
- Around line 45-52: Update the Composer onSend handler in NewSessionView so it
returns the session-creation promise and acknowledges success only after onSend
completes, allowing failures to preserve the draft instead of being treated as
successful. Handle rejected creation by rendering a persistent error message
with select-text cursor-text classes, while keeping the draft intact.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 425284ba-f67d-47be-9624-868c592e4094
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (57)
apps/desktop/package.jsonapps/desktop/src/renderer/lib/persisted-keys/persisted-key-registry.test-data.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/ChatV3Pane.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Composer/Composer.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Composer/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/MarkdownView/MarkdownView.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/MarkdownView/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/MarkdownView/utils/planMarkdown/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/MarkdownView/utils/planMarkdown/planMarkdown.test.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/MarkdownView/utils/planMarkdown/planMarkdown.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/NewSessionView/NewSessionView.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/NewSessionView/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/SessionHeader/SessionHeader.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/SessionHeader/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/SessionPicker/SessionPicker.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/SessionPicker/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/SessionView/SessionView.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/SessionView/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/Transcript.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/AgentMessageRow/AgentMessageRow.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/AgentMessageRow/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ApprovalRow/ApprovalRow.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ApprovalRow/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/NoticeRow/NoticeRow.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/NoticeRow/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/PlanRow/PlanRow.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/PlanRow/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ReasoningRow/ReasoningRow.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ReasoningRow/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolCallRow/ToolCallRow.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolCallRow/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolContentList/ToolContentList.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolContentList/components/DiffContent/DiffContent.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolContentList/components/DiffContent/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolContentList/components/TerminalContent/TerminalContent.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolContentList/components/TerminalContent/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolContentList/components/TextContent/TextContent.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolContentList/components/TextContent/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolContentList/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/TurnGroupSection/TurnGroupSection.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/TurnGroupSection/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/UnknownItemRow/UnknownItemRow.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/UnknownItemRow/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/UserMessageRow/UserMessageRow.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/UserMessageRow/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/utils/rowKind/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/utils/rowKind/rowKind.test.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/utils/rowKind/rowKind.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/hooks/useSessionClient/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/hooks/useSessionClient/useSessionClient.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/usePaneRegistry.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/types.tsapps/desktop/tsconfig.jsonpackages/chat/src/client/subscribeToSession/subscribeToSession.test.tspackages/chat/src/react/useChatSession/useChatSession.ts
🚧 Files skipped from review as they are similar to previous changes (55)
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/NoticeRow/index.ts
- apps/desktop/package.json
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/UnknownItemRow/index.ts
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/index.ts
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/UserMessageRow/index.ts
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolContentList/components/TerminalContent/TerminalContent.tsx
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/UserMessageRow/UserMessageRow.tsx
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/utils/rowKind/rowKind.ts
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/utils/rowKind/index.ts
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/utils/rowKind/rowKind.test.ts
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/hooks/useSessionClient/index.ts
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/TurnGroupSection/index.ts
- apps/desktop/src/renderer/lib/persisted-keys/persisted-key-registry.test-data.ts
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Composer/index.ts
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/types.ts
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolContentList/components/TerminalContent/index.ts
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/MarkdownView/utils/planMarkdown/index.ts
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/usePaneRegistry.tsx
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolContentList/components/TextContent/TextContent.tsx
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ApprovalRow/index.ts
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolContentList/components/DiffContent/index.ts
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/NewSessionView/index.ts
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/SessionPicker/index.ts
- packages/chat/src/client/subscribeToSession/subscribeToSession.test.ts
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/MarkdownView/index.ts
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/SessionView/index.ts
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/MarkdownView/MarkdownView.tsx
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/MarkdownView/utils/planMarkdown/planMarkdown.test.ts
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolContentList/components/TextContent/index.ts
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/SessionHeader/index.ts
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolCallRow/ToolCallRow.tsx
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolContentList/components/DiffContent/DiffContent.tsx
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ReasoningRow/index.ts
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/SessionView/SessionView.tsx
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/ChatV3Pane.tsx
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/AgentMessageRow/index.ts
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolCallRow/index.ts
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/AgentMessageRow/AgentMessageRow.tsx
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/index.ts
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/PlanRow/index.ts
- packages/chat/src/react/useChatSession/useChatSession.ts
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/SessionPicker/SessionPicker.tsx
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/NoticeRow/NoticeRow.tsx
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/hooks/useSessionClient/useSessionClient.ts
- apps/desktop/tsconfig.json
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolContentList/index.ts
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ApprovalRow/ApprovalRow.tsx
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ToolContentList/ToolContentList.tsx
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/TurnGroupSection/TurnGroupSection.tsx
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/Transcript.tsx
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/PlanRow/PlanRow.tsx
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Composer/Composer.tsx
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/ReasoningRow/ReasoningRow.tsx
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/Transcript/components/UnknownItemRow/UnknownItemRow.tsx
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatV3Pane/components/MarkdownView/utils/planMarkdown/planMarkdown.ts
Chunk C of plans/chat-v3-pane-mount.md, stacked on #6205. New pane kind "chat-v3" (old pane untouched — swap is the flag-flip PR), registered only when the chat-v3 PostHog flag is on. Full §3 tree: anchor-user-turn transcript (content-visibility offscreen, zero streaming scroll writes except the anchor + approval force-scroll), turn-group collapsing with user-override-wins, toolKind-dispatched rows + open-union fallback, per-block memoized MarkdownView with fence-gated highlighting (Streamdown per block, never whole-message), plain-textarea composer with echo-confirmed draft clearing, harness picker + separate session-status/connection indicators. ai-elements reused per the four never-reuse rules. Wiring: workspace-client host URL, tRPC at /chat-v3/trpc (type-only ChatRouter via tsconfig paths — no runtime dep), WS with per-reconnect token per wsAuth. 117 desktop tests green.
Known merge-time reconciliations: adopt FEATURE_FLAGS.CHAT_V3 from #6203's shared constant (this branch couldn't see it); CDP checklist from the build report covers relay affinity (primeRelayAffinity), real line-diff rendering (follow-up), scroll/perf under live streaming.
https://claude.ai/code/session_01XRFCEPsfmJHu2SenDKYJ2h
Summary by cubic
Adds a new desktop pane for the chat v3 experience, gated by the PostHog chat-v3 flag (via FEATURE_FLAGS.CHAT_V3). Ships transcript/composer/session wiring to the new chat runtime and fixes draft persistence by registering the composer draft key.
New Features
chat-v3-draft:*(key registered), echo-based draft clearing; cancel button while a turn is running./chat-v3/trpc, WS streaming under/chat-v3with token from the workspace client on reconnect.useChatSession.sendPromptnow returns the createdOutboxEntryused for echo-based draft clearing.subscribeToSessionto usescope_idafter the rename.Migration
useChatSession.sendPromptto handle the returnedOutboxEntry(wasvoid)./chat-v3/trpcand WS under/chat-v3, and enable the chat-v3 PostHog flag for rollout.@superset/chat(and tsconfig path for@superset/chat-runtime).Written for commit 0d3bcd0. Summary will update on new commits.
Summary by CodeRabbit