refactor(renderer): restructure chat main and diagnostics#1994
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR restructures the renderer main-window composition around a new ChangesRenderer architecture and chat boundaries
Estimated code review effort: 4 (Complex) | ~75 minutes Renderer performance diagnostics
Estimated code review effort: 3 (Moderate) | ~30 minutes Sequence Diagram(s)sequenceDiagram
participant RendererPerformanceReporter
participant PerformanceClient
participant performanceRecordRendererRoute
participant RendererPerformanceLogService
RendererPerformanceReporter->>PerformanceClient: submit typed record
PerformanceClient->>performanceRecordRendererRoute: call performance.recordRenderer
performanceRecordRendererRoute->>RendererPerformanceLogService: record validated record
RendererPerformanceLogService-->>performanceRecordRendererRoute: return accepted
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 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 |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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 `@src/renderer/src/apps/chat-main/ChatMainApp.vue`:
- Around line 157-160: Make toast dismissal idempotent in the onOpenChange
handler and its timeout path. Update handleErrorClosed so the active error ID is
checked or ensure queue advancement occurs through only one of these paths,
preventing dismiss() from advancing the error queue twice and skipping an error.
In `@test/renderer/components/ChatTabView.test.ts`:
- Line 104: Update the activeSessionId assignment in the bootstrap payload to
preserve an explicitly provided null bootstrapActiveSessionId, while still
falling back to sessionStore.activeSessionId only when the option is undefined.
Keep the existing activeSession clearing behavior unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 05be7731-9f61-4bd0-84ab-d23f71ea930a
⛔ Files ignored due to path filters (4)
src/renderer/browser/assets/ChromeClose.svgis excluded by!**/*.svgsrc/renderer/browser/assets/ChromeMaximize-1.svgis excluded by!**/*.svgsrc/renderer/browser/assets/ChromeMaximize.svgis excluded by!**/*.svgsrc/renderer/browser/assets/ChromeMinimize.svgis excluded by!**/*.svg
📒 Files selected for processing (19)
.github/workflows/prcheck.ymlAGENTS.mddocs/architecture/baselines/renderer-application-boundaries-baseline.jsondocs/architecture/renderer-application-boundaries/plan.mddocs/architecture/renderer-application-boundaries/spec.mddocs/architecture/renderer-application-boundaries/tasks.mdelectron.vite.config.tspackage.jsonscripts/generate-renderer-architecture-baseline.mjssrc/renderer/src/App.vuesrc/renderer/src/apps/chat-main/ChatMainApp.vuesrc/renderer/src/views/ChatTabView.vuetest/renderer/components/App.startup.test.tstest/renderer/components/ChatTabView.test.tstest/renderer/stores/sessionStore.test.tstsconfig.app.jsontsconfig.app.tsgo.jsonvitest.config.renderer.tsvitest.config.ts
💤 Files with no reviewable changes (3)
- vitest.config.ts
- electron.vite.config.ts
- vitest.config.renderer.ts
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
resources/model-db/providers.json (1)
254651-254736: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winFix the TTS model metadata
resources/model-db/providers.json:254651-254736— thesetts-1entries should declaremodalities.input: ["text"],modalities.output: ["audio"], andtype: "tts".🤖 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 `@resources/model-db/providers.json` around lines 254651 - 254736, Update the tts-1-hd-1106, tts-1-hd, tts-1-1106, and tts-1 entries so modalities.input is ["text"], modalities.output is ["audio"], and each entry includes type set to "tts", while preserving their existing limits, costs, and other metadata.
🤖 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 `@resources/acp-registry/registry.json`:
- Around line 1055-1062: Update the windows-aarch64 entry in the registry
configuration so its cmd value uses the Windows executable name
"./opencode.exe", matching the windows-x86_64 configuration; leave the archive,
args, and checksum unchanged.
In `@resources/model-db/providers.json`:
- Around line 258583-258609: Update the Meta Muse Spark 1.1 entry identified by
id "meta/muse-spark-1.1" to set its type to "chat" instead of "imageGeneration",
leaving the remaining model metadata unchanged.
- Around line 184112-184138: Update the token limits for the
openai/gpt-realtime-whisper entry to set context to 16,000 and output to 2,000,
leaving the surrounding model metadata unchanged.
---
Outside diff comments:
In `@resources/model-db/providers.json`:
- Around line 254651-254736: Update the tts-1-hd-1106, tts-1-hd, tts-1-1106, and
tts-1 entries so modalities.input is ["text"], modalities.output is ["audio"],
and each entry includes type set to "tts", while preserving their existing
limits, costs, and other metadata.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: afa62975-d35d-4784-8f4d-9f37486abd39
⛔ Files ignored due to path filters (2)
src/renderer/src/lib/icons/icon-collections.generated.tsis excluded by!**/*.generated.*src/renderer/src/lib/icons/icon-whitelist.generated.tsis excluded by!**/*.generated.*
📒 Files selected for processing (3)
resources/acp-registry/registry.jsonresources/model-db/providers.jsonscripts/generate-icon-collections.mjs
💤 Files with no reviewable changes (1)
- scripts/generate-icon-collections.mjs
…boundaries # Conflicts: # docs/architecture/baselines/agent-system-layered-runtime-baseline.json
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (1)
src/shared/contracts/routes/performance.routes.ts (1)
41-55: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winSplit this schema by
scope
Use a discriminated union sostartup,workload, andchat-sessiononly accept the fields they actually emit. KeepstartupRunIdas an opaque string; the app treats it that way, so the generator’s exact format should stay out of this contract.🤖 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 `@src/shared/contracts/routes/performance.routes.ts` around lines 41 - 55, Split RendererPerformanceRecordSchema into a scope-discriminated union with dedicated schemas for startup, workload, and chat-session records, restricting each variant to only the fields it emits. Preserve the existing shared validation and strictness, and keep startupRunId as the current opaque bounded string without importing or enforcing generator-specific formatting.
🤖 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 `@src/renderer/src/features/chat-page/composables/useComposerSubmit.ts`:
- Around line 275-307: Update onSubmit and the additional async submission paths
around sendMessageWithOutgoingTurnFeedback to track the composer revision
represented by the submitted text, files, and skills. After each await, clear
message, attachedFiles, and composer skills only when the current revision still
matches that submitted revision; otherwise preserve newer edits, while retaining
the existing submission and failure behavior.
- Around line 223-229: Update the send failure handling around
chatClient.sendMessage in useComposerSubmit so a rejected send preserves
retryable user content: either retain a failed item with the captured payload or
restore that payload without overwriting newer composer edits. Keep the existing
optimistic-message cleanup as appropriate, but do not only log and swallow the
error after callers have cleared the draft.
In `@src/renderer/src/features/chat-page/composables/useListGestures.ts`:
- Around line 181-190: Update markWheelScrollIntent to notify
options.onGestureStart with 'wheel' before updating pagination intent, matching
the keyboard path. Ensure repeated keyboard events do not emit duplicate
'keyboard' start notifications before the corresponding settle-time
onGestureEnd, preserving balanced gesture lifecycle transitions.
In `@src/renderer/src/features/chat-page/composables/useMessageActions.ts`:
- Around line 93-101: Capture options.sessionId() once at the start of
onMessageEditSave and use that captured session for both editUserMessage and
onMessageRetry. Update the retry helper invocation, and its implementation if
needed, to accept and preserve the captured session so the edit and retry remain
bound to the same session across the await.
In `@src/renderer/src/features/chat-page/composables/usePendingInputActions.ts`:
- Around line 38-42: Update the replacement payload in useComposerSubmit when
calling pendingInputStore.updateQueueInput to include the existing queued
inlineItems from target.payload, preserving them while editing text. Keep the
current defaults for files and activeSkills unchanged.
- Around line 65-67: Capture the session ID once before awaiting
steerPendingInput in the pending-input action, then reuse that captured value
for both options.pendingInputStore.steerPendingInput and options.beginPlanTurn.
Update the surrounding try flow so steering and plan state always remain
associated with the same session.
In `@src/renderer/src/features/chat-page/composables/useSessionRestore.ts`:
- Around line 54-70: The restore flow in runRestore and restoreSessionMessages
must handle rejections from both loadMessagesForSession and
pendingInputStore.loadPendingInputs. Attach rejection handling immediately,
ensure failures do not become unhandled promises, and leave the restore/loading
state in its defined failure condition while preserving requestId guards for
stale restores.
In `@src/renderer/src/features/chat-page/composables/useToolInteraction.ts`:
- Around line 48-55: Strengthen parseSubagentProgress to runtime-validate the
parsed payload before returning it: verify every task, waiting interaction, and
action block has the expected object shape and field types, rejecting malformed
nested values such as null tasks. Ensure only validated SubagentProgressPayload
data reaches the rendering/list construction logic around the task processing
flow.
In `@src/renderer/src/platform/performance/rendererPerformance.ts`:
- Around line 61-77: Update setEnabled so enabling with an existing startup run
ID also submits buffered startup records whose startupRunId matches the active
run. Preserve startup records for other or not-yet-active runs, while continuing
to submit pending non-startup records as currently handled.
---
Nitpick comments:
In `@src/shared/contracts/routes/performance.routes.ts`:
- Around line 41-55: Split RendererPerformanceRecordSchema into a
scope-discriminated union with dedicated schemas for startup, workload, and
chat-session records, restricting each variant to only the fields it emits.
Preserve the existing shared validation and strictness, and keep startupRunId as
the current opaque bounded string without importing or enforcing
generator-specific formatting.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 74896967-68de-42e3-ba22-5c74d2c92595
📒 Files selected for processing (82)
docs/architecture/chat-display-model-boundary/plan.mddocs/architecture/chat-display-model-boundary/spec.mddocs/architecture/chat-display-model-boundary/tasks.mddocs/architecture/chatpage-decomposition/spec.mddocs/architecture/chatpage-decomposition/tasks.mddocs/architecture/renderer-application-boundaries/plan.mddocs/architecture/renderer-application-boundaries/tasks.mddocs/architecture/renderer-performance-diagnostics/plan.mddocs/architecture/renderer-performance-diagnostics/spec.mddocs/architecture/renderer-performance-diagnostics/tasks.mddocs/guides/getting-started.mdpackage.jsonresources/acp-registry/registry.jsonresources/model-db/providers.jsonscripts/agent-cleanup-guard.mjsscripts/generate-architecture-baseline.mjssrc/main/app/composition.tssrc/main/app/rendererPerformanceLogService.tssrc/main/app/routes.tssrc/renderer/api/PerformanceClient.tssrc/renderer/src/apps/chat-main/ChatMainApp.vuesrc/renderer/src/components/chat/ChatToolInteractionOverlay.vuesrc/renderer/src/components/chat/MessageList.vuesrc/renderer/src/components/chat/MessageListRow.vuesrc/renderer/src/components/message/MessageBlockAction.vuesrc/renderer/src/components/message/MessageBlockActivityGroup.vuesrc/renderer/src/components/message/MessageBlockAudio.vuesrc/renderer/src/components/message/MessageBlockContent.vuesrc/renderer/src/components/message/MessageBlockError.vuesrc/renderer/src/components/message/MessageBlockImage.vuesrc/renderer/src/components/message/MessageBlockQuestionRequest.vuesrc/renderer/src/components/message/MessageBlockThink.vuesrc/renderer/src/components/message/MessageBlockToolCall.vuesrc/renderer/src/components/message/MessageBlockVideo.vuesrc/renderer/src/components/message/MessageContent.vuesrc/renderer/src/components/message/MessageItemAssistant.vuesrc/renderer/src/components/message/MessageItemUser.vuesrc/renderer/src/components/message/messageActivityGroups.tssrc/renderer/src/composables/message/useMessageWindow.tssrc/renderer/src/composables/useArtifacts.tssrc/renderer/src/features/chat-page/ChatPage.vuesrc/renderer/src/features/chat-page/composables/useChatPageEventBridge.tssrc/renderer/src/features/chat-page/composables/useChatSearch.tssrc/renderer/src/features/chat-page/composables/useComposerSubmit.tssrc/renderer/src/features/chat-page/composables/useDisplayMessages.tssrc/renderer/src/features/chat-page/composables/useListGestures.tssrc/renderer/src/features/chat-page/composables/useMessageActions.tssrc/renderer/src/features/chat-page/composables/useMessageVirtualization.tssrc/renderer/src/features/chat-page/composables/usePendingInputActions.tssrc/renderer/src/features/chat-page/composables/usePlanFloatLifecycle.tssrc/renderer/src/features/chat-page/composables/useSessionRestore.tssrc/renderer/src/features/chat-page/composables/useToolInteraction.tssrc/renderer/src/features/chat-page/composables/useVoiceInput.tssrc/renderer/src/features/chat-page/model/displayMessage.tssrc/renderer/src/platform/performance/rendererPerformance.tssrc/renderer/src/stores/ui/message.tssrc/renderer/src/views/ChatTabView.vuesrc/shared/contracts/routes.tssrc/shared/contracts/routes/performance.routes.tstest/fixtures/mockMessages.tstest/main/app/rendererPerformanceLogService.test.tstest/main/app/routes.test.tstest/renderer/components/App.startup.test.tstest/renderer/components/ChatPage.test.tstest/renderer/components/ChatTabView.test.tstest/renderer/components/MessageList.test.tstest/renderer/components/message/MessageBlockActivityGroup.test.tstest/renderer/components/message/MessageBlockBasics.test.tstest/renderer/components/message/MessageBlockContent.test.tstest/renderer/components/message/MessageBlockMedia.test.tstest/renderer/components/message/MessageBlockToolCall.test.tstest/renderer/components/message/MessageItemAssistant.test.tstest/renderer/components/message/MessageItemUser.test.tstest/renderer/components/message/messageActivityGroups.test.tstest/renderer/composables/useMessageWindow.test.tstest/renderer/features/chat-page/composables/useChatPageEventBridge.test.tstest/renderer/features/chat-page/composables/useMessageActions.test.tstest/renderer/features/chat-page/composables/usePendingInputActions.test.tstest/renderer/features/chat-page/composables/useToolInteraction.test.tstest/renderer/features/chat-page/composables/useVoiceInput.test.tstest/renderer/lib/rendererPerformance.test.tstest/renderer/performance/chatRendering.perf.test.ts
🚧 Files skipped from review as they are similar to previous changes (5)
- package.json
- docs/architecture/renderer-application-boundaries/tasks.md
- resources/acp-registry/registry.json
- test/renderer/components/ChatTabView.test.ts
- src/renderer/src/apps/chat-main/ChatMainApp.vue
There was a problem hiding this comment.
Caution
Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 9
🧹 Nitpick comments (1)
src/shared/contracts/routes/performance.routes.ts (1)
41-55: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winSplit this schema by
scope
Use a discriminated union sostartup,workload, andchat-sessiononly accept the fields they actually emit. KeepstartupRunIdas an opaque string; the app treats it that way, so the generator’s exact format should stay out of this contract.🤖 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 `@src/shared/contracts/routes/performance.routes.ts` around lines 41 - 55, Split RendererPerformanceRecordSchema into a scope-discriminated union with dedicated schemas for startup, workload, and chat-session records, restricting each variant to only the fields it emits. Preserve the existing shared validation and strictness, and keep startupRunId as the current opaque bounded string without importing or enforcing generator-specific formatting.
🤖 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 `@src/renderer/src/features/chat-page/composables/useComposerSubmit.ts`:
- Around line 275-307: Update onSubmit and the additional async submission paths
around sendMessageWithOutgoingTurnFeedback to track the composer revision
represented by the submitted text, files, and skills. After each await, clear
message, attachedFiles, and composer skills only when the current revision still
matches that submitted revision; otherwise preserve newer edits, while retaining
the existing submission and failure behavior.
- Around line 223-229: Update the send failure handling around
chatClient.sendMessage in useComposerSubmit so a rejected send preserves
retryable user content: either retain a failed item with the captured payload or
restore that payload without overwriting newer composer edits. Keep the existing
optimistic-message cleanup as appropriate, but do not only log and swallow the
error after callers have cleared the draft.
In `@src/renderer/src/features/chat-page/composables/useListGestures.ts`:
- Around line 181-190: Update markWheelScrollIntent to notify
options.onGestureStart with 'wheel' before updating pagination intent, matching
the keyboard path. Ensure repeated keyboard events do not emit duplicate
'keyboard' start notifications before the corresponding settle-time
onGestureEnd, preserving balanced gesture lifecycle transitions.
In `@src/renderer/src/features/chat-page/composables/useMessageActions.ts`:
- Around line 93-101: Capture options.sessionId() once at the start of
onMessageEditSave and use that captured session for both editUserMessage and
onMessageRetry. Update the retry helper invocation, and its implementation if
needed, to accept and preserve the captured session so the edit and retry remain
bound to the same session across the await.
In `@src/renderer/src/features/chat-page/composables/usePendingInputActions.ts`:
- Around line 38-42: Update the replacement payload in useComposerSubmit when
calling pendingInputStore.updateQueueInput to include the existing queued
inlineItems from target.payload, preserving them while editing text. Keep the
current defaults for files and activeSkills unchanged.
- Around line 65-67: Capture the session ID once before awaiting
steerPendingInput in the pending-input action, then reuse that captured value
for both options.pendingInputStore.steerPendingInput and options.beginPlanTurn.
Update the surrounding try flow so steering and plan state always remain
associated with the same session.
In `@src/renderer/src/features/chat-page/composables/useSessionRestore.ts`:
- Around line 54-70: The restore flow in runRestore and restoreSessionMessages
must handle rejections from both loadMessagesForSession and
pendingInputStore.loadPendingInputs. Attach rejection handling immediately,
ensure failures do not become unhandled promises, and leave the restore/loading
state in its defined failure condition while preserving requestId guards for
stale restores.
In `@src/renderer/src/features/chat-page/composables/useToolInteraction.ts`:
- Around line 48-55: Strengthen parseSubagentProgress to runtime-validate the
parsed payload before returning it: verify every task, waiting interaction, and
action block has the expected object shape and field types, rejecting malformed
nested values such as null tasks. Ensure only validated SubagentProgressPayload
data reaches the rendering/list construction logic around the task processing
flow.
In `@src/renderer/src/platform/performance/rendererPerformance.ts`:
- Around line 61-77: Update setEnabled so enabling with an existing startup run
ID also submits buffered startup records whose startupRunId matches the active
run. Preserve startup records for other or not-yet-active runs, while continuing
to submit pending non-startup records as currently handled.
---
Nitpick comments:
In `@src/shared/contracts/routes/performance.routes.ts`:
- Around line 41-55: Split RendererPerformanceRecordSchema into a
scope-discriminated union with dedicated schemas for startup, workload, and
chat-session records, restricting each variant to only the fields it emits.
Preserve the existing shared validation and strictness, and keep startupRunId as
the current opaque bounded string without importing or enforcing
generator-specific formatting.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 74896967-68de-42e3-ba22-5c74d2c92595
📒 Files selected for processing (82)
docs/architecture/chat-display-model-boundary/plan.mddocs/architecture/chat-display-model-boundary/spec.mddocs/architecture/chat-display-model-boundary/tasks.mddocs/architecture/chatpage-decomposition/spec.mddocs/architecture/chatpage-decomposition/tasks.mddocs/architecture/renderer-application-boundaries/plan.mddocs/architecture/renderer-application-boundaries/tasks.mddocs/architecture/renderer-performance-diagnostics/plan.mddocs/architecture/renderer-performance-diagnostics/spec.mddocs/architecture/renderer-performance-diagnostics/tasks.mddocs/guides/getting-started.mdpackage.jsonresources/acp-registry/registry.jsonresources/model-db/providers.jsonscripts/agent-cleanup-guard.mjsscripts/generate-architecture-baseline.mjssrc/main/app/composition.tssrc/main/app/rendererPerformanceLogService.tssrc/main/app/routes.tssrc/renderer/api/PerformanceClient.tssrc/renderer/src/apps/chat-main/ChatMainApp.vuesrc/renderer/src/components/chat/ChatToolInteractionOverlay.vuesrc/renderer/src/components/chat/MessageList.vuesrc/renderer/src/components/chat/MessageListRow.vuesrc/renderer/src/components/message/MessageBlockAction.vuesrc/renderer/src/components/message/MessageBlockActivityGroup.vuesrc/renderer/src/components/message/MessageBlockAudio.vuesrc/renderer/src/components/message/MessageBlockContent.vuesrc/renderer/src/components/message/MessageBlockError.vuesrc/renderer/src/components/message/MessageBlockImage.vuesrc/renderer/src/components/message/MessageBlockQuestionRequest.vuesrc/renderer/src/components/message/MessageBlockThink.vuesrc/renderer/src/components/message/MessageBlockToolCall.vuesrc/renderer/src/components/message/MessageBlockVideo.vuesrc/renderer/src/components/message/MessageContent.vuesrc/renderer/src/components/message/MessageItemAssistant.vuesrc/renderer/src/components/message/MessageItemUser.vuesrc/renderer/src/components/message/messageActivityGroups.tssrc/renderer/src/composables/message/useMessageWindow.tssrc/renderer/src/composables/useArtifacts.tssrc/renderer/src/features/chat-page/ChatPage.vuesrc/renderer/src/features/chat-page/composables/useChatPageEventBridge.tssrc/renderer/src/features/chat-page/composables/useChatSearch.tssrc/renderer/src/features/chat-page/composables/useComposerSubmit.tssrc/renderer/src/features/chat-page/composables/useDisplayMessages.tssrc/renderer/src/features/chat-page/composables/useListGestures.tssrc/renderer/src/features/chat-page/composables/useMessageActions.tssrc/renderer/src/features/chat-page/composables/useMessageVirtualization.tssrc/renderer/src/features/chat-page/composables/usePendingInputActions.tssrc/renderer/src/features/chat-page/composables/usePlanFloatLifecycle.tssrc/renderer/src/features/chat-page/composables/useSessionRestore.tssrc/renderer/src/features/chat-page/composables/useToolInteraction.tssrc/renderer/src/features/chat-page/composables/useVoiceInput.tssrc/renderer/src/features/chat-page/model/displayMessage.tssrc/renderer/src/platform/performance/rendererPerformance.tssrc/renderer/src/stores/ui/message.tssrc/renderer/src/views/ChatTabView.vuesrc/shared/contracts/routes.tssrc/shared/contracts/routes/performance.routes.tstest/fixtures/mockMessages.tstest/main/app/rendererPerformanceLogService.test.tstest/main/app/routes.test.tstest/renderer/components/App.startup.test.tstest/renderer/components/ChatPage.test.tstest/renderer/components/ChatTabView.test.tstest/renderer/components/MessageList.test.tstest/renderer/components/message/MessageBlockActivityGroup.test.tstest/renderer/components/message/MessageBlockBasics.test.tstest/renderer/components/message/MessageBlockContent.test.tstest/renderer/components/message/MessageBlockMedia.test.tstest/renderer/components/message/MessageBlockToolCall.test.tstest/renderer/components/message/MessageItemAssistant.test.tstest/renderer/components/message/MessageItemUser.test.tstest/renderer/components/message/messageActivityGroups.test.tstest/renderer/composables/useMessageWindow.test.tstest/renderer/features/chat-page/composables/useChatPageEventBridge.test.tstest/renderer/features/chat-page/composables/useMessageActions.test.tstest/renderer/features/chat-page/composables/usePendingInputActions.test.tstest/renderer/features/chat-page/composables/useToolInteraction.test.tstest/renderer/features/chat-page/composables/useVoiceInput.test.tstest/renderer/lib/rendererPerformance.test.tstest/renderer/performance/chatRendering.perf.test.ts
🚧 Files skipped from review as they are similar to previous changes (5)
- package.json
- docs/architecture/renderer-application-boundaries/tasks.md
- resources/acp-registry/registry.json
- test/renderer/components/ChatTabView.test.ts
- src/renderer/src/apps/chat-main/ChatMainApp.vue
🛑 Comments failed to post (9)
src/renderer/src/features/chat-page/composables/useComposerSubmit.ts (2)
223-229: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
Do not swallow failed sends after clearing the draft.
Both direct-send callers clear composer state before awaiting this helper. When
sendMessagerejects, the optimistic UI is removed and the error is only logged, leaving the user with neither their draft nor actionable feedback. Preserve a retryable failed item or restore the captured payload without overwriting newer edits.🤖 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 `@src/renderer/src/features/chat-page/composables/useComposerSubmit.ts` around lines 223 - 229, Update the send failure handling around chatClient.sendMessage in useComposerSubmit so a rejected send preserves retryable user content: either retain a failed item with the captured payload or restore that payload without overwriting newer composer edits. Keep the existing optimistic-message cleanup as appropriate, but do not only log and swallow the error after callers have cleared the draft.
275-307: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Protect newer composer edits from stale async clears.
These paths snapshot some payload fields, await asynchronous work, then clear the current message, attachments, or skills. Input added while those promises are pending is therefore erased. Track a composer revision and only clear the submitted revision, or remove the captured state before awaiting and restore it safely on failure.
Also applies to: 310-340, 342-381
🤖 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 `@src/renderer/src/features/chat-page/composables/useComposerSubmit.ts` around lines 275 - 307, Update onSubmit and the additional async submission paths around sendMessageWithOutgoingTurnFeedback to track the composer revision represented by the submitted text, files, and skills. After each await, clear message, attachedFiles, and composer skills only when the current revision still matches that submitted revision; otherwise preserve newer edits, while retaining the existing submission and failure behavior.src/renderer/src/features/chat-page/composables/useListGestures.ts (1)
181-190: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Balance wheel and keyboard gesture lifecycle notifications.
Wheel scrolling never calls
onGestureStart('wheel'), while repeated keyboard events callonGestureStart('keyboard')multiple times before the single settle-timeonGestureEnd(). This can leave auto-follow active for wheel input and produce mismatched controller transitions.Proposed fix
function markKeyboardScrollIntent(isUpward: boolean): void { - options.onGestureStart('keyboard') + if (!isListScrolling.value) { + options.onGestureStart('keyboard') + } updateUpwardPaginationIntent(isUpward) markListScrolling() } function markWheelScrollIntent(isUpward: boolean): void { + if (!isListScrolling.value) { + options.onGestureStart('wheel') + } updateUpwardPaginationIntent(isUpward) markListScrolling() }📝 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.function markKeyboardScrollIntent(isUpward: boolean): void { if (!isListScrolling.value) { options.onGestureStart('keyboard') } updateUpwardPaginationIntent(isUpward) markListScrolling() } /** Wheel intent: arm pagination direction and keep the scrolling state warm. */ function markWheelScrollIntent(isUpward: boolean): void { if (!isListScrolling.value) { options.onGestureStart('wheel') } updateUpwardPaginationIntent(isUpward) markListScrolling()🤖 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 `@src/renderer/src/features/chat-page/composables/useListGestures.ts` around lines 181 - 190, Update markWheelScrollIntent to notify options.onGestureStart with 'wheel' before updating pagination intent, matching the keyboard path. Ensure repeated keyboard events do not emit duplicate 'keyboard' start notifications before the corresponding settle-time onGestureEnd, preserving balanced gesture lifecycle transitions.src/renderer/src/features/chat-page/composables/useMessageActions.ts (1)
93-101: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Keep edit and retry bound to the same session.
Line 100 targets the session active when editing starts, but Line 101 rereads
sessionIdafter the await. If the user switches sessions meanwhile, the retry is sent with the old message ID and new session ID. Capture the session once and pass it through the retry helper.🤖 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 `@src/renderer/src/features/chat-page/composables/useMessageActions.ts` around lines 93 - 101, Capture options.sessionId() once at the start of onMessageEditSave and use that captured session for both editUserMessage and onMessageRetry. Update the retry helper invocation, and its implementation if needed, to accept and preserve the captured session so the edit and retry remain bound to the same session across the await.src/renderer/src/features/chat-page/composables/usePendingInputActions.ts (2)
38-42: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Preserve queued
inlineItemswhen editing text.
useComposerSubmitincludesinlineItemsin queued payloads, but this replacement payload omits them. Editing queued text can therefore discard workspace references or other inline metadata.Proposed fix
await options.pendingInputStore.updateQueueInput(options.sessionId(), payload.itemId, { text: payload.text, files: target.payload.files ?? [], - activeSkills: target.payload.activeSkills ?? [] + activeSkills: target.payload.activeSkills ?? [], + inlineItems: target.payload.inlineItems ?? [] })📝 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.await options.pendingInputStore.updateQueueInput(options.sessionId(), payload.itemId, { text: payload.text, files: target.payload.files ?? [], activeSkills: target.payload.activeSkills ?? [], inlineItems: target.payload.inlineItems ?? [] })🤖 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 `@src/renderer/src/features/chat-page/composables/usePendingInputActions.ts` around lines 38 - 42, Update the replacement payload in useComposerSubmit when calling pendingInputStore.updateQueueInput to include the existing queued inlineItems from target.payload, preserving them while editing text. Keep the current defaults for files and activeSkills unchanged.
65-67: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Keep steering and plan state bound to the same session.
The session ID is read before and after the await. If navigation occurs while steering, the request targets the old session but
beginPlanTurntargets the new one.Proposed fix
+ const sessionId = options.sessionId() try { - await options.pendingInputStore.steerPendingInput(options.sessionId(), itemId) - options.beginPlanTurn(options.sessionId()) + await options.pendingInputStore.steerPendingInput(sessionId, itemId) + options.beginPlanTurn(sessionId)📝 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 sessionId = options.sessionId() try { await options.pendingInputStore.steerPendingInput(sessionId, itemId) options.beginPlanTurn(sessionId)🤖 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 `@src/renderer/src/features/chat-page/composables/usePendingInputActions.ts` around lines 65 - 67, Capture the session ID once before awaiting steerPendingInput in the pending-input action, then reuse that captured value for both options.pendingInputStore.steerPendingInput and options.beginPlanTurn. Update the surrounding try flow so steering and plan state always remain associated with the same session.src/renderer/src/features/chat-page/composables/useSessionRestore.ts (1)
54-70: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Handle failures from both restore operations.
runRestore()is invoked with its promise discarded, whileloadMessagesForSessionhas no catch andpendingInputsPromise.then(...)has no rejection handler. Either store failure becomes an unhandled rejection; attach error handling immediately and leave the restore/loading state in a defined condition.🤖 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 `@src/renderer/src/features/chat-page/composables/useSessionRestore.ts` around lines 54 - 70, The restore flow in runRestore and restoreSessionMessages must handle rejections from both loadMessagesForSession and pendingInputStore.loadPendingInputs. Attach rejection handling immediately, ensure failures do not become unhandled promises, and leave the restore/loading state in its defined failure condition while preserving requestId guards for stale restores.src/renderer/src/features/chat-page/composables/useToolInteraction.ts (1)
48-55: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Validate parsed subagent progress before dereferencing it.
The type assertion does not validate JSON. A payload such as
{"tasks":[null]}passes Line 55 and crashes at Line 114, while malformed nested blocks can escape into rendering. Add runtime validation for each task, waiting interaction, and action block before constructing the list.Also applies to: 113-127
🤖 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 `@src/renderer/src/features/chat-page/composables/useToolInteraction.ts` around lines 48 - 55, Strengthen parseSubagentProgress to runtime-validate the parsed payload before returning it: verify every task, waiting interaction, and action block has the expected object shape and field types, rejecting malformed nested values such as null tasks. Ensure only validated SubagentProgressPayload data reaches the rendering/list construction logic around the task processing flow.src/renderer/src/platform/performance/rendererPerformance.ts (1)
61-77: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Flush buffered startup records when enabling with an existing run ID.
If
recordStartup(..., { startupRunId })runs while disabled, its flush is skipped. A latersetEnabled(true)retains those startup records indefinitely because it only submits non-startup records.Proposed fix
if (!enabled) { this.pendingRecords = [] return } + if (this.startupRunId) { + this.flushPendingStartupRecords() + return + } + const pendingNonStartupRecords = this.pendingRecords.filter( (record) => record.scope !== 'startup' )🤖 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 `@src/renderer/src/platform/performance/rendererPerformance.ts` around lines 61 - 77, Update setEnabled so enabling with an existing startup run ID also submits buffered startup records whose startupRunId matches the active run. Preserve startup records for other or not-yet-active runs, while continuing to submit pending non-startup records as currently handled.
Summary
This PR restructures the renderer around explicit main-window ownership, decomposes the chat page into feature-local units, and adds an opt-in local performance diagnostics pipeline. It also introduces architecture-baseline enforcement and refreshes generated runtime metadata.
Renderer application boundaries
apps/chat-main/ChatMainApp.vueas the main-window composition root; retainApp.vueand existing Vite/HTML entries as compatibility shims.ChatMainAppowns shell, IPC, and chrome;ChatTabViewowns bootstrap, route initialization, and initial-session loading.@browserconfiguration.Chat page feature decomposition
ChatPageand its related model/composables intofeatures/chat-page.Local performance diagnostics
<userData>/logs/renderer-performance.ndjsononly when local logging is enabled.Supporting maintenance
Architecture overview
UI layout
Validation
Completed before the later no-local-validation instruction:
pnpm run formatpnpm run typecheckpnpm run i18npnpm run lintA subsequently started full renderer suite was interrupted (exit 130) before it completed. Per the current instruction, no additional local lint, test, or typecheck commands were run after the final review adjustment; rely on the PR’s remote checks for subsequent validation.
Summary by CodeRabbit
New Features
Bug Fixes
Refactor
Tests