fix(chat): preserve drafts across agent switches - #2238
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Team Run ID: 📒 Files selected for processing (2)
Included review availability: Your plan provides up to 8 included reviews per hour; 5 remain after this review. 📝 WalkthroughWalkthroughChangesThe PR adds per-agent new-thread composer drafts. Drafts include message content, attachments, skills, inline items, and editor documents. Drafts restore across agent changes, deselection, remounts, reloads, and submissions. Per-agent composer drafts
Estimated code review effort: 4 (Complex) | ~45 minutes Merge Risk: ⚪ Minimal · up to Draft-related tests now use the real store and isolate browser storage between cases, improving test fidelity without changing production behavior. No current merge-readiness risk remains. Sequence Diagram(s)sequenceDiagram
participant ChatInputBox
participant NewThreadPage
participant useNewThreadComposerDraft
participant useDraftStore
participant localStorage
ChatInputBox->>NewThreadPage: emit message, skills, files, and document changes
NewThreadPage->>useNewThreadComposerDraft: record composer change
useNewThreadComposerDraft->>useDraftStore: update agent draft
useNewThreadComposerDraft->>localStorage: debounce draft persistence
NewThreadPage->>useNewThreadComposerDraft: capture submission snapshot
useNewThreadComposerDraft-->>NewThreadPage: return snapshot for selected agent
NewThreadPage->>useNewThreadComposerDraft: accept successful submission
useNewThreadComposerDraft->>useDraftStore: persist originating agent draft
Suggested reviewers: 🚥 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 |
zerob13
left a comment
There was a problem hiding this comment.
Review: ✅ APPROVE — 0 P1 / 0 P2 / 5 P3 follow-ups
Reviewed at bc4b8c2c0 against merge-base e9f909519 (9 files, +544/-50). Full diff and changed files read in place; dependencies (composerDraftState, composerDraftPersistence, ChatInputBox handle API, useComposerSubmit, useSkillsData) cross-checked at merge-base. Verified in an isolated worktree: typecheck:web, oxfmt --check, oxlint all clean; 50/50 NewThreadPage + 95/95 ChatPage renderer tests pass; the new e2e smoke spec passes against a real build (7.1s, isolated user data).
Key behaviors verified
- PR claim "successful sends consume their submitted snapshot; failed sends and newer edits remain intact" — TRUE.
applyAcceptedComposerSubmission(composerDraftState.ts:94) treats content as unchanged only whenrevisionANDrawMessageAND document all match; any edit during send (revision bump) preserves current text while still subtracting the sent files and their document nodes. Failed/cancelled paths never callacceptSubmission. - Watcher choreography on agent switch is correct. The composable's sync persist fires before the page's sync cancel watcher, capturing the old agent's live editor state before cancellation side effects. The
restoringflag suppresses skills echoes (useSkillsDataclears on agentId change; ACP sessionId re-emit), the post-flush watcher re-seeds, and later echoes are fingerprint no-ops. No clobber path found. - No restore/emit feedback loop.
restoreDocumentSnapshotruns insidesyncEditorContentwith sync meta, so restores never re-emitupdate:modelValue/draft-change;updateDraft's fingerprint check short-circuits any residual echo. - Mid-submission agent switch fully guarded. Ambient values (
agentId,projectDir,targetAcpSessionId) captured at entry before the firstawait;submission.cancelledre-checked after everyawait; thefinallyidentity check prevents a resolving old submission from clobbering the switched-to state. Cross-agent sends consume the origin agent's draft only. - The
ChatPage.vueteardown move is a genuine fix, not churn.disposeComposerSubmit→flushComposerDraftPersistneeds a livechatInputRefto capture the rich document; inonUnmountedthe child would already be unmounted and the persisted draft silently degrades to text-only. The extended ChatPage test pins document + activeSkills surviving unmount. - Attachment pending-check invalidation works.
attachmentFilterTokenbumped on agent switch and unmount; late capability results discarded; raw files re-filtered at submit.
Over-engineering / test assessment
- The 170-line composable mirrors the established
useComposerSubmitpattern at ~1/8 the size; store placement is required (acceptSubmissionmust mutate shared state after the page unmounts). No premature generality; the store changes are purely additive. - Test delta is proportionate, not bloat: +226 in
NewThreadPage.test.tsis six tests, each mapping to a durable category (per-agent restore, unmount-flush persistence, debounce/beforeunload contract, send-resolves-after-remount concurrency, cross-agent consumption, late capability invalidation). The 40-line e2e smoke is the only test proving persistence across a real reload and the deselect→unmount path.
P3 follow-ups (non-blocking)
- E2e spec numbering collision —
34-agent-composer-drafts.smoke.spec.tsreuses prefix34-, already taken by34-prompt-editor-scroll.smoke.spec.ts. Rename to35-. ComposerHandletype duplication —useNewThreadComposerDraft.ts:14-21andNewThreadPage.vue:311-321re-declare the same handle shape. Export the type from the composable and reuse it.- Missing
typeof windowguard —useNewThreadComposerDraft.ts:152-153registerspagehide/beforeunloadunguarded, while siblinguseComposerSubmit.ts:569guards withtypeof window !== 'undefined'. Harmless in the renderer; inconsistent with the pattern being copied. - Orphaned draft keys on agent deletion — deleting an agent leaves
deepchat.composerDraft.v1.new-thread:<id>in localStorage forever (clearComposerDraftFromStoragehas zero callers insrc/). Pre-existing behavior class shared with session drafts — follow-up ticket for both. - Per-keystroke cost note — the post-flush restore watcher (
useNewThreadComposerDraft.ts:115-135) does a fullcopyComposerDraftdeep copy plusJSON.stringifycomparisons on every draft change. Fine in practice (documents carry only file metadata); ChatPage's version caches fingerprints to avoid redundant copies — worth aligning eventually.
Merge-ready as-is; the P3s can be follow-ups.
Switching away from a new conversation now preserves a separate draft for each agent, including text, attachments, selected skills and inline references. Drafts restore when returning or reloading. Successful sends consume their submitted snapshot, while failed sends and newer edits remain intact.
Existing-session drafts are saved before the input editor unmounts, preserving skills and document nodes. Pending attachment checks are invalidated when their originating view changes or unmounts.
Validation:
pnpm run format:check,pnpm run i18n,pnpm run lint, andpnpm run typecheckpassed.pnpm run buildpassed.NODE_OPTIONS=--max-old-space-size=6144 pnpm run test:renderer --silentpassed: 267 files and 2373 tests, including deep-link and onboarding coverage using the real draft store with isolated state. Regressions cover agent isolation, full document restoration, debounce/window-close flushing, late send acceptance and attachment checks.pnpm exec playwright test -c test/e2e/playwright.config.ts test/e2e/specs/34-agent-composer-drafts.smoke.spec.tspassed with isolated Electron user data and no provider requests.Summary by CodeRabbit
New Features
Bug Fixes