fix(desktop): keep the new-task composer's contents when the target changes - #3432
fix(desktop): keep the new-task composer's contents when the target changes#3432shaokeyibb wants to merge 2 commits into
Conversation
…hanges Draft text, staged attachments and staged quotes are all keyed by (profileId, hostId, projectId) since apache#3122, and the workspace picker that changes the project part sits directly under the composer — so choosing a Project re-keyed all three mid-composition and what the user was writing dropped out of view. Carry them to the target the user selects. The buckets stay keyed per target, so apache#3122's Host-scoped new-task state is unchanged; they move with the selection instead of staying behind under the key the user navigated away from. The move is unconditional: the target arrived at holds what was brought to it and nothing else. Carrying only into an empty target would leave copies under every key the composer passed through, and one would resurface later — send the task, come back to an empty composer, pick another target, and the text just sent would reappear as that target's own draft. The carry keys on the new-task target rather than on the composer's active key, so a Session keeps its own draft and attachments when the target moves behind it. Leaving the unresolved slot keeps its existing reload-lease guard: that transition is startup settling, not a choice, and its draft may belong to one specific target. Fixes apache#3408 Generated-by: Claude Code (Claude Opus 5) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Fixes Desktop new-task composer state handoff so that changing the workspace picker target (Project) does not make in-progress composition appear to disappear. This aligns draft text plus staged attachments/quotes with the currently selected new-task target, while keeping Session-scoped state isolated.
Changes:
- Carry the visible new-task draft across
(profileId, hostId, projectId)target changes inChatComposerRegion, with special handling for the unresolved startup key and reload-intent persistence. - Add a generic “pending bucket rekey” pathway for staged attachments and quotes so they move with the selected new-task target.
- Add unit + e2e coverage to ensure the new behavior fails on
mainand passes with the fix.
Required Conclusion
- Is the current solution optimal for the actual problem? Mostly yes: it addresses the root cause (keyed buckets swapping on target change) at the correct ownership points (draft in
ChatComposerRegion, staged items in the staging hooks) with minimal new abstraction. - If applicable, what production code can be deleted? none identified.
- If applicable, what low-quality tests can be deleted or replaced? none identified.
- Is a deeper refactor required, and what should the final structure be? No deeper refactor required; the current split (draft vs staged buckets) is appropriate. The small shared helper hook for staged buckets is a reasonable end state.
- Is the reviewed revision ready to merge? Yes, assuming the inline comment is addressed or explicitly waived by the maintainer.
- What residual risks or verification gaps remain? Minor edge-case risk around object-map key handling in
rekeyPending(prototype-chain key collisions) and typical flake risk in compositor-timing-sensitive e2e; no other verification gaps identified from the diff.
Review-relevant risks / protected areas: No direct impact identified on security/auth boundaries, licensing, releases, or governance in this diff; primary impact is user-visible Desktop behavior for draft/staging preservation.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| apps/desktop/src/renderer/use-new-task-pending-carry.ts | New hook to rekey staged-item buckets when the new-task target changes. |
| apps/desktop/src/renderer/use-app-shell-composer-quotes.ts | Plumbs new-task target key into quote staging; invokes carry hook. |
| apps/desktop/src/renderer/use-app-shell-composer-attachments.ts | Plumbs new-task target key into attachment staging; invokes carry hook. |
| apps/desktop/src/renderer/chat-composer-region.tsx | Carries draft text across target key changes with unresolved/reload-intent handling. |
| apps/desktop/src/renderer/app-shell.tsx | Wires currentNewTaskDraftKey into attachments/quotes staging hooks. |
| apps/desktop/src/renderer/app-shell-pending-attachments.ts | Adds rekeyPending helper for moving staged buckets between keys. |
| apps/desktop/src/main/tests/new-task-pending-carry.test.ts | New unit tests asserting staged attachments/quotes move with target and don’t leak into Sessions. |
| apps/desktop/src/main/tests/chat-composer-region-draft-handoff.test.ts | Expanded unit coverage for draft carry across target changes and reload-intent behavior. |
| apps/desktop/e2e/new-task-draft-target.spec.ts | New e2e test validating real-window wiring: picker selection does not drop visible draft. |
| apps/desktop/e2e/fixtures.ts | Adds a seeded project + a dedicated fixture window to support the new e2e test. |
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Summary
Everything the new-task composer holds — draft text, staged attachments, staged quotes — is
keyed by
(profileId, hostId, projectId)since #3122. The workspace picker that changes theproject part sits directly under the composer, so choosing a Project re-keys all three
mid-composition and what you were writing drops out of view.
They now follow the target you pick. The buckets stay keyed per target, so #3122's
Host-scoped new-task state is unchanged — they just move with the selection instead of being
left behind under the key you navigated away from.
Two things about the approach that are not the obvious choice:
else. Carrying only into an empty target would leave copies under keys the composer passed
through, and one resurfaces later — send the task, come back to an empty composer, pick
another target, and the text you just sent reappears as that target's own draft.
(
activeId ?? newTaskDraftKey). A Session switch changes that active key too, and aSession must keep its own draft and attachments.
Three imports gained
.jsextensions sonode --testcan load the two staging hooks, whichis what the new test needs to reach them.
Fixes #3408
Verification
The same moment in both shots: a draft typed on the new-task surface, immediately after
choosing a different target in the workspace picker (both show 无项目 selected).
e2e/new-task-draft-target.spec.ts(new) — real window: type, then pick a Project. Fails onmainwithReceived: "", passes with the change.chat-composer-region-draft-handoff.test.ts— 2 of 5 fail onmain. The other three guardthe new behaviour, and each fails against a weaker version of this fix.
new-task-pending-carry.test.ts(new) — attachments and quotes; 2 of 4 fail without thecarry.
npm run lint,npm run format:check,npm --workspace @maka/desktop run typecheck: clean.session-workbar.spec.ts:124, which fails on aclean
maincheckout here too.npm --workspace @maka/desktop run test: 13 failures, an identical set on cleanmain(
EBUSYunlinking SQLite files, and a shell-script fake CLI).Not run: any packaged-app check. My host is Windows x64, which CONTRIBUTING lists as an
unsupported target, and that is also what the 13 unit failures above are.
AI use
Select exactly one:
Tool(s) and scope: Claude Code (Claude Opus 5) — root-cause tracing, the fix, the tests and a
draft of this description. Reviewed, run and verified locally by me.
Checklist
Does this PR entail a change in behavior?