Skip to content

fix: detach submitted homepage drafts#1043

Merged
Astro-Han merged 7 commits into
devfrom
codex/i1041-submit-draft-lifecycle
Jun 1, 2026
Merged

fix: detach submitted homepage drafts#1043
Astro-Han merged 7 commits into
devfrom
codex/i1041-submit-draft-lifecycle

Conversation

@Astro-Han
Copy link
Copy Markdown
Owner

@Astro-Han Astro-Han commented Jun 1, 2026

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:

  • Platform impact: not applicable because this is renderer prompt-state logic only.
  • Docs/release/dependency/local-file surface: not applicable.

How To Verify

Diff check: git diff --check passed
Related unit tests: bun test --preload ./happydom.ts src/components/prompt-input/submit.test.ts src/context/prompt.test.ts src/components/prompt-input/portable-draft.test.ts src/components/prompt-input/pinned-draft.test.ts src/components/prompt-input/draft-isolation.integration.test.ts src/components/prompt-input/submit-ownership.test.ts passed, 115 tests
App typecheck: bun run typecheck passed
Visible user path: bunx playwright test e2e/projects/workspace-new-session.spec.ts --grep "sent homepage prompt" passed, 1 Chromium test

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

  • Type label — this PR carries exactly one of 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.
  • Routing labels — this PR carries at least one of 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.
  • Priority label — this PR carries exactly one of P0, P1, P2, P3. The priority-triage bot suggests one on PR open. Confirm or override, then tick this.
  • Human Review Status above is set to Pending, Approved by @<reviewer>, or Not required: <reason> (default is Pending; "not required" is restricted to bot-authored low-risk PRs).
  • I linked the related issue, or stated in Summary why there is no issue.
  • I described the review focus and any meaningful risks.
  • I replaced the example block in How To Verify with the real verification steps and the key result for each.
  • I did not introduce unrelated refactors, dependencies, generated files, or file changes beyond the stated scope.
  • (conditional) I manually checked visible UI or copy changes when needed, with screenshots or recordings. Leave unticked only if no visible UI or copy changed.
  • (conditional) I considered macOS and Windows impact for platform, packaging, updater, signing, paths, shell, or permissions changes. Leave unticked only if no platform/packaging surface was touched.
  • (conditional) I called out docs, release notes, dependencies, permissions, credentials, deletion behavior, generated content, or local file changes when relevant. Leave unticked only if none of those surfaces was touched.
  • I reviewed the final diff for unrelated changes and suspicious dependency changes.
  • I am targeting dev, and my PR title and commit messages use Conventional Commits in English.

Summary by CodeRabbit

  • Bug Fixes

    • Homepage prompts no longer persist or carry over between projects while replies are pending.
    • Improved restoration of drafts and prompt context when asynchronous submissions fail.
    • Stronger isolation of prompt context between sessions to prevent unintended carry-over.
  • Tests

    • Added end-to-end and unit tests to ensure deterministic async submit and draft lifecycle behavior.

@Astro-Han Astro-Han added bug Something isn't working P2 Medium priority app Application behavior and product flows labels Jun 1, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 1, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

Captures 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.

Changes

Homepage Draft Portability During Async Submission

Layer / File(s) Summary
Prompt binding API: hasDraft & context.replaceAll(target)
packages/app/src/context/prompt.tsx, packages/app/src/context/prompt.test.ts
Expose hasDraft(target?: Scope) and extend context.replaceAll(items, target?) to target explicit sessions; tests updated to verify targeted context replacement and structural hasDraft behavior.
Submit lifecycle: capture submittedDraft & immediate clear
packages/app/src/components/prompt-input/submit.ts
Capture submittedDraft at submit time; clearInput() immediately resets UI and clears owner-backed portable/pinned drafts at the captured revision; restoreInput() restores from the captured submittedDraft when shouldRestoreOwnerDraft() permits and uses prompt.context.replaceAll(...) under the prompt scope.
Submit tests: async gate and draft lifecycle coverage
packages/app/src/components/prompt-input/submit.test.ts
Test harness adds a controllable promptAsyncGate, tracks prompt set/reset/context replace calls and draft checks, resets portable/pinned test stores, and includes many new tests covering detaching drafts before async settles, restoring on async failure when eligible, and non-restore scenarios.
E2E: cross-project prompt isolation while pending
packages/app/e2e/projects/workspace-new-session.spec.ts
New E2E verifies a sent homepage prompt with a pending assistant reply does not carry to another project's homepage while the reply is held; uses a promise gate to hold assistant reply, navigates projects, asserts prompt absence, then releases and cleans up.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Astro-Han/pawwork#657: Modifies prompt submit pipeline in packages/app/src/components/prompt-input/submit.ts, related submit flow changes.
  • Astro-Han/pawwork#329: Related changes to prompt submission/session-targeting and submit lifecycle plumbing.

Poem

🐇 I held the prompt until the gate swung wide,
Captured the draft where its true self hides.
Cleared the owners, scoped the restore with care,
Cross-project hops now find nothing there.
Tests gate the async — the composer sleeps sound and fair.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'fix: detach submitted homepage drafts' is concise, follows Conventional Commits, and accurately summarizes the main change of detaching submitted drafts from draft owners to prevent reappearance after workspace switching.
Linked Issues check ✅ Passed The changes directly address the requirements in issue #1041: detaching submitted drafts from portable/pinned owners (preventing reappearance), preserving unsent draft portability, fixing the draft-owner lifecycle during submit, and addressing the navigation race condition.
Out of Scope Changes check ✅ Passed All changes are in-scope: focused modifications to prompt submission lifecycle, draft ownership logic, and context restoration. No unrelated refactors, dependency changes, or generated files. The test additions and implementation changes directly support the stated objectives.
Description check ✅ Passed The PR description comprehensively covers all required sections with sufficient detail: clear summary of changes, problem context, linked issue, human review status, review focus, risk notes, verification steps with actual results, and a completed checklist.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/i1041-submit-draft-lifecycle

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added the ui Design system and user interface label Jun 1, 2026
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread packages/app/src/components/prompt-input/submit.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Skip 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

📥 Commits

Reviewing files that changed from the base of the PR and between 05d8ba1 and 3fd8b14.

📒 Files selected for processing (5)
  • packages/app/e2e/projects/workspace-new-session.spec.ts
  • packages/app/src/components/prompt-input/submit.test.ts
  • packages/app/src/components/prompt-input/submit.ts
  • packages/app/src/context/prompt.test.ts
  • packages/app/src/context/prompt.tsx

Comment thread packages/app/src/components/prompt-input/submit.ts Outdated
@Astro-Han Astro-Han merged commit 010c6d6 into dev Jun 1, 2026
41 of 42 checks passed
@Astro-Han Astro-Han deleted the codex/i1041-submit-draft-lifecycle branch June 1, 2026 11:36
Astro-Han added a commit that referenced this pull request Jun 1, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app Application behavior and product flows bug Something isn't working P2 Medium priority ui Design system and user interface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Sent homepage prompt can reappear after switching workspaces

1 participant