fix: detach submitted homepage drafts#1043
Conversation
📝 WalkthroughWalkthroughCaptures submitted homepage drafts at submit time, immediately clears owner-backed portable/pinned snapshots, adds scope-targeted context.replaceAll and hasDraft APIs, gates failure restores on scope activity/dirtiness, and adds unit and e2e tests validating draft detach/restore behavior and cross-project isolation. ChangesHomepage Draft Portability During Async Submission
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 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 |
There was a problem hiding this comment.
Suggested priority: P2 (includes user-path files (packages/app/src/components/prompt-input/submit.test.ts, packages/app/src/components/prompt-input/submit.ts)).
P1/P0 are reserved for maintainer confirmation. Please relabel manually if this is a release blocker, security issue, data-loss risk, or updater/runtime failure.
There was a problem hiding this comment.
Code Review
This pull request refactors the prompt submission flow to immediately clear portable and pinned drafts upon submission, using a captured draft state to restore the input in case of an asynchronous submission failure. It also adds comprehensive unit and E2E tests to validate these behaviors. The review feedback highlights a potential bug in the restoration logic where checking prompt.dirty() globally could inadvertently discard a failed background draft if the user has started typing in a different session. Scoping this check to the active session is recommended to prevent this issue.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/app/src/components/prompt-input/submit.ts (1)
650-659:⚠️ Potential issue | 🟠 Major | ⚡ Quick winSkip visible-editor side effects when restoring into an inactive scope.
This restore path now writes into
promptScope, but it still always resets mode/popover and focuses the currently mounted editor. If the async failure lands after the user has navigated elsewhere, the current workspace/session can lose focus and have its composer state changed even though the draft was restored to a different scope.Suggested fix
- input.setMode(mode) - input.setPopover(null) - requestAnimationFrame(() => { - const editor = input.editor() - if (!editor) return - editor.focus() - const cursorPrompt = owned.kind === "route" ? currentPrompt : prompt.current() - setCursorPosition(editor, input.promptLength(cursorPrompt)) - input.queueScroll() - }) + if (isActivePromptScope(promptScope)) { + input.setMode(mode) + input.setPopover(null) + requestAnimationFrame(() => { + const editor = input.editor() + if (!editor) return + editor.focus() + const cursorPrompt = owned.kind === "route" ? currentPrompt : prompt.current() + setCursorPosition(editor, input.promptLength(cursorPrompt)) + input.queueScroll() + }) + }🤖 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 `@packages/app/src/components/prompt-input/submit.ts` around lines 650 - 659, When restoring a draft into promptScope, avoid applying visible-editor side effects if that scope is not the currently active one: wrap the mode/popover reset and the requestAnimationFrame block with a guard that checks the restored scope against the active scope (e.g., compare promptScope to the current active prompt scope/session). Only call input.setMode(...), input.setPopover(null), requestAnimationFrame(...) and perform editor.focus(), setCursorPosition(...), and input.queueScroll() when the restored scope matches the active scope (so use the same scope identity used elsewhere to determine active workspace/session, e.g., the value used by prompt.current() or a global activePromptScope).
🤖 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 `@packages/app/src/components/prompt-input/submit.ts`:
- Around line 626-629: shouldRestoreOwnerDraft currently treats image-only
drafts as empty because it only checks prompt.dirty() and
prompt.context.items().length; update it to also detect existing image
attachments and avoid restoring over them. Inside shouldRestoreOwnerDraft
(referencing promptScope, isActivePromptScope, ownerHasNewDraft, prompt.dirty(),
and prompt.context.items()), add a check for image attachments—e.g., use an
existing API like prompt.context.hasImages() or inspect prompt.context.items()
for items with type 'image'—and return false if images are present so the
active-route restore guard won’t overwrite an image-only draft.
---
Outside diff comments:
In `@packages/app/src/components/prompt-input/submit.ts`:
- Around line 650-659: When restoring a draft into promptScope, avoid applying
visible-editor side effects if that scope is not the currently active one: wrap
the mode/popover reset and the requestAnimationFrame block with a guard that
checks the restored scope against the active scope (e.g., compare promptScope to
the current active prompt scope/session). Only call input.setMode(...),
input.setPopover(null), requestAnimationFrame(...) and perform editor.focus(),
setCursorPosition(...), and input.queueScroll() when the restored scope matches
the active scope (so use the same scope identity used elsewhere to determine
active workspace/session, e.g., the value used by prompt.current() or a global
activePromptScope).
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: a5c0f5cb-baf8-48ae-8d1c-0ba9b47b6eb6
📒 Files selected for processing (5)
packages/app/e2e/projects/workspace-new-session.spec.tspackages/app/src/components/prompt-input/submit.test.tspackages/app/src/components/prompt-input/submit.tspackages/app/src/context/prompt.test.tspackages/app/src/context/prompt.tsx
Wrap each matrix shard's `unit` step in `.github/workflows/windows-advisory.yml` in a process-level retry (max_attempts=2) so a single transient Windows-advisory failure does not turn the advisory red, while persistent regressions stay visible. Root cause / goal - `windows-advisory` was ~40% red over the last ten `dev` runs from transient flake unrelated to merged PR diffs: PR #1043 hit Effect-TS sleep-based timing assertions in `packages/opencode/test/session/run-state.test.ts` on Windows's coarser wall clock; PR #1028 hit a Bun 1.3.14 native segfault on `windows-latest` (`watcher.node` stack frames). Linux `ci` remains the load-bearing required gate; this workflow is advisory and non-blocking. Change boundary - `.github/workflows/windows-advisory.yml`: bash `for` loop with `attempts=2`, each attempt runs in a subshell `( ${{ matrix.command }} )` so `cd packages/...` in some matrix.command values does not leak across attempts. First-attempt failure is exported via `first_exit_code` and surfaced in `$GITHUB_STEP_SUMMARY`. Recovered-on-retry and final-failed outcomes also emit run-level `::notice` / `::warning` annotations so flake stays observable in the Actions UI annotations panel, not only inside the collapsed step summary. - `packages/opencode/test/github/ci-workflow.test.ts`: dedicated workflow contract self-test `retries the Windows unit step once on transient failure` pins retry budget, subshell scoping, first-attempt-failure summary, recovery summary, `::notice` / `::warning` annotation strings, and final exit-by-last-attempt semantics. Existing self-test substrings preserved. Verification - `actionlint .github/workflows/windows-advisory.yml`: ok. - `bun test test/github/ci-workflow.test.ts` from `packages/opencode/`: 17 pass / 0 fail / 305 expect() calls. - CI on the PR: all required `ci` jobs green; `windows-advisory` green across all 5 matrix shards (app / opencode-session / opencode-config-project / opencode-server-tools / desktop). One natural rerun during the PR also exercised the retry path on a transient `actions/cache@v5` failure on `opencode-config-project` and recovered on attempt 2. Review follow-ups - P3 (workflow self-test pin): addressed in commit 30dfa2d. - 待验证 (real CI evidence of retry): partially addressed — group marker `Windows unit attempt 1 of 2` observed on runner; full recovered-on-retry path is exercised only by natural flake and remains an in-prod observation. - P2 (recovered-on-retry visibility): addressed in commit f9a2c65 with `::notice` / `::warning` run-level annotations. Residual risk - Retry hides the first-attempt failure at the check-result level. The ::notice annotation and step-summary entry preserve visibility for humans reviewing the run. Required Linux `ci` gate is untouched. - Upload unit artifacts step uploads JUnit XML from the last attempt that wrote it; on retry recovery the artifact reflects attempt 2. Acceptable for an advisory signal. Deferred work - Rewriting `Effect.sleep`-based timing in `run-state.test.ts` to deterministic primitives is deferred until after the 2026-06-15 `windows-latest` -> `windows-2025-vs2026` runner migration, since flake patterns will shift.
Summary
Detach submitted homepage drafts from portable and pinned draft owners as soon as submit clears the visible editor. Keep the submitted payload in the submit closure for failure recovery instead of leaving it in the live draft owner.
Why
A sent homepage prompt could reappear after switching to another project or workspace while the async send was still settling. The live portable owner could be consumed by the new empty homepage before the old success cleanup ran, causing the submitted prompt to look like an unsent draft again.
Related Issue
Fixes #1041
Human Review Status
Pending
Review Focus
Please focus on the submit lifecycle around
clearInput, async failure restore, and whether owner-backed drafts now correctly represent only editable unsent state.Risk Notes
No storage schema, dependency, docs, release notes, permissions, generated content, or platform packaging surface changed.
Skipped conditional checklist items:
How To Verify
Screenshots or Recordings
The visible behavior is covered by the Playwright test above: type a homepage prompt, submit while the assistant response is held pending, navigate to another project homepage, and assert the destination composer remains empty. No static screenshot is attached because the expected visual state is an empty editor after navigation.
Checklist
bug,enhancement,task,documentation. Type labels are author-added; the labeler bot does NOT assign them. Add the label in the GitHub UI, then tick this.app,ui,platform,harness,ci. The labeler bot assigns these on PR open based on changed paths. Confirm the bot's choice (or override if wrong), then tick this.P0,P1,P2,P3. The priority-triage bot suggests one on PR open. Confirm or override, then tick this.Pending,Approved by @<reviewer>, orNot required: <reason>(default isPending; "not required" is restricted to bot-authored low-risk PRs).dev, and my PR title and commit messages use Conventional Commits in English.Summary by CodeRabbit
Bug Fixes
Tests