Skip to content

fix(dialogs): dialogs that open by themselves take turns - #25674

Open
brennanb2025 wants to merge 33 commits into
mainfrom
brennanb2025/rsc-launch-prompt-turns
Open

brennanb2025 wants to merge 33 commits into
mainfrom
brennanb2025/rsc-launch-prompt-turns

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 31 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​2615 $\color{#cf222e}{\Huge{\mathbf{−}}}$​461 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​2154
Prod 49 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1315 $\color{#cf222e}{\Huge{\mathbf{−}}}$​763 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​552

ELI5

After Orca restarted, a crash report, feature tip and interrupted-chat offer could open together. They used separate checks for whether another dialog was open. A user dialog could replace the tip, and another crash report could replace the report being read.

Automatic dialogs now take turns: crash reports → feature tip → resume offer. A dialog you open yourself appears immediately above shown content and preserves its notes and setup. This stacking stays for now.

What Changed

  • One window-local registry tracks content attached to the screen through its close animation and derives the next automatic request by priority and queue order.
  • The tip has its own host outside the replaceable user modal slot. Crash reports queue by ID; sending belongs to their owner and survives closing the dialog.
  • Tips become seen and launch crash reports are acknowledged only after content appears. Closing never waits for those writes. User-opened resume immediately reuses its keyed entry and prevents a later duplicate launch offer.
  • Removed before-content reservations, repeated queue normalization, manual occupancy hooks, duplicate tip state/writes, the separate resume discovery observer/completion flag, generic tip payload fallback and the unreachable project-added dialog. Kept domain content, behavioral tests and working project/folder default-checkout flows.
  • Preserved lazy first-run onboarding policy, palette focus through close animation and enabled recovery reads when settings are unavailable. The final main merge preserves feat(native-chat): say in the chat why Orca stopped a reply, and offer Continue #25675's in-chat Continue decision markers at their original moments.

Why

The common pattern has no shared reservation before lazy content loads. We chose option B: count a dialog once its content is attached to the screen. A request that never renders cannot silently occupy the slot. In a cold-load race, automatic content may appear while requested user/SSH content loads; the requested dialog then stacks above it.

Intended differences are explicit automatic priority and immediate user/response stacking. Actual startup reads have a 15-second observation budget so an unanswered check becomes unavailable; expiry does not cancel recovery or user actions. No display delays are used.

Ignoring whitespace and excluding tests/test-only support, the comparable review footprint changed +1393/-280 → +1217/-688 (net +1113 → +529): 584 net production lines removed, including 251 in the second cut. The coordination core shrank 578 → 292 lines. The final merged head against current main is +1219/-688, net +531; two integration lines preserve incoming launch-decision timing. Unrelated incoming main changes are not counted as savings. Conventional test-file-pattern counts are +1396/-280 before and +1240/-688 after, including 3 → 21 test-support lines.

This remains a net addition. It buys one content queue/authority, separate crash IDs and send lifetime, a tip that survives user dialogs, and immediate keyed resume. Enforced activity-watcher/catalog responsibility boundaries add 25 counted lines without new behavior or wrappers. The per-area table and API were delivered before push.

Linked Issue

Separate interrupted-chat recovery bug fix. #25676 no longer depends on #25674; it is rebuilt on #25652 and main's existing resume-dialog API. Whichever merges second adapts at its final main merge. If #25676 lands first, this PR registers its status-bar and reconnect notification's “Show chats” opens as user dialogs; if this PR lands first, #25676 adopts this PR's API.

The actual dialog/data owner uses useNativeChatRestartOffer(enabled, { ownsStartupDiscovery: true }); ordinary subscribers keep the default without disposal authority. The separate discovery hook is removed. #25676 was not present in the final fetched main, so no speculative paths were added.

Visual Proof

Archived hidden Windows screenshots at 45319de037d: seeded automatic data, genuine renderer actions and Electron virtual key input. Windows remained hidden/unfocused. These compare user stacking and Escape; they are not screenshots of the old bug.

Automatic prompt User palette above it After Escape: underlying content retained
Crash Palette above crash Crash retained after Escape
Tip Palette above tip Tip retained after Escape
Resume Palette above resume Resume retained after Escape

Cold lazy user request above crash content, then Escape:

Cold user request stacked Crash content retained after Escape
Cold lazy user request stacked Crash retained after cold user request closes

Immediate user-opened resume:

Immediate manual resume

A real encrypted-key SSH error leaves the interrupted-chat offer and its selection intact:

Wrong SSH passphrase above the resume offer

Testing

Final pushed e0f4bd1586a, merged with main e9b3efa7d75c: fresh gated renderer and node typechecks, 22/22 mandatory static checks (including React Doctor), full overlap-file lint and 176 tests in 13 files passed. Final CI passed on this exact head: 17 jobs passed, 11 optional jobs skipped, including both Linux/Windows packages and all ten Node24 unit shards. Mobile Checks and PR test LoC also passed; Headless Node server was skipped. No red job or step remained to classify against main.

The ready-triggered follow-on CI run also passed on the same head: 5 jobs passed, 22 skipped, including the end-to-end app build, Electron native-cache preparation and changed end-to-end specs. Headless Node passed too. The full end-to-end matrix and optional native WSL/IME/SSH CI jobs were skipped. No code, main merge or push followed the ready transition; no red remained to classify.

Fresh narrow source review confirmed that the merge preserves #25675's markers and only changes equivalent Radix imports. It changes no automatic launch call, discovery ownership, priority or content lifetime, so the conditional short live order rerun was not required. Live QA below ran on 45319de037d; it is not a live final-head claim.

Hidden Windows CDP passed crash → tip → resume even with resume data ready first; all three palette stacks and retained notes/selection/terminal; crash-ID queue/deduplication; failed/hanging acknowledgement and discovery; cold lazy requests; and immediate manual resume. Help event delivery and one submission after double-click passed; external submission was stubbed.

A real Docker SSH host with a temporary encrypted key passed Escape over crash, credential Cancel over tip, wrong-key error over resume, correct Unlock, reconnect, Add project and selected branch creation. Direct host Git confirmed the created checkout and exact branch. Local wrong-key, settled checkout/backing results and archived screenshots were inspected; detailed crash/tip SSH DOM and remaining SSH screenshots are still remote.

The six actual pane environment fields pointed inside the run. Before/after guards for C:/Users/neil/.codex/config.toml, C:/Users/neil/.codex/hooks.json, C:/Users/neil/.orca/agent-hooks/codex-hook.cmd and the empty .codex/hooks inventory matched. Initial evidence recorded content hashes/inventory; missing initial size/mtime was disclosed and approved. 60 complete later hash/size/mtime snapshots were independently parsed, all unchanged. Snapshots61–69 followed later QA stages, rather than no work after60. Snapshot61 followed the final resource audit;62–63 were read-only transfer checks;64–65 bracketed closing owned control tabs;66–67 bracketed removing the owned QA checkout;68–69 were further read-only transfer checks. Their nine local event receipts report unchanged; full later payloads have not transferred. Those later QA stages launched no app/agent, ran no install and made no hooks/auth/config edit or restore.

Pending after Windows transport loss: final after-loss real-file fingerprint; final checkout-path/branch and cleanup audit; complete cumulative metadata payload; remaining control-tab closure/private stage directory cleanup and lock release; detailed SSH screenshots not yet transferred. The Windows lock is UNVERIFIABLE, not released. Confirmed cleanup includes owned app/container/daemon/temporary keys/profiles; tab closure and checkout removal were acknowledged, not independently audited after loss. Loss of contact is not evidence of process death. No Windows recovery action is being attempted; the coordinator owns the runtime recovery follow-up. These hygiene/evidence gaps do not hold ready under the coordinator's explicit decision.

M4Air was unreachable. The required isolated Codex/OpenCode driver chain was unavailable, so the permitted scripted CDP fallback was used. Discovery/send/install faults were simulated; renderer input, terminal setup and SSH were real. The displayed installation command was not executed. The changed end-to-end specs passed after marking ready; the full end-to-end matrix and native WSL/IME/SSH CI jobs were skipped. Native Help-menu input, physical OS/macOS focus and separate macOS/Linux/WSL app GUI checks were not exercised. No mobile surface or wire contract changed; emulator QA is not applicable.

  • Automated behavioral tests added/updated
  • Manually tested locally (remote hidden Windows/real SSH used)

Review

Fresh full review of the reduced design and narrow final-main review found no remaining actionable P0–P3. Readiness checklist used without counsel; source review covers lifecycle, performance, security, folder/SSH and cross-platform/compatibility impact. No new persisted fields, RPC/wire contracts, dependency or host execution path. Passing tests are regression evidence, not proof of architecture.

Agent skill upstream boundary

  • Not applicable — no shipped skill changes or copied upstream skill source.

Notes

Review and required checks are complete. The coordinator explicitly authorized marking ready with the pending Windows items stated. No merge or auto-merge.

Deferred: never-rendered tips may be offered on a later launch; crash payloads remain for this window; existing sheets/popovers outside the dialog boundary are out of scope; an overbroad test caption is a deferred nit.

Windows placement refused the Mac parent with selector_not_found/LINEAGE_PARENT_NOT_FOUND. The coordinator approved --no-parent for the owned QA worktree; C:/orca/orca was not its parent.

Checklist

  • This PR is small and focused (focused behavior, substantial reduction, still net +531 production lines)
  • Explained problem, user-facing change, mechanism and alternatives
  • Archived screenshots attached with before/after-action captions
  • Self-reviewed correctness, security and performance
  • Cross-platform, SSH, mobile and compatibility impact considered
  • Static analysis, typechecks, targeted tests and CI Linux/Windows packaging passed

After a restart several dialogs could open by themselves at once and stack:
the resume-interrupted-chats modal, a feature tip, a contextual tour, the
crash report, and the SSH passphrase prompt. Nothing scheduled them.

One arbiter in the UI store now decides which automatic prompt is on screen:
- automatic prompts (resume offer, crash report, feature tip) request a turn,
  shown one at a time by priority; the one on screen keeps it until it closes,
  and an owner that unmounts releases it
- at launch they wait (up to 2 s) for this machine's resume read, so the resume
  offer goes first
- user dialogs and dialogs answering something in flight (SSH credentials,
  confirmations, pinned-tab close, WebAuthn, workspace fallback, onboarding)
  are never delayed: they register as visible, and a prompt on screen steps
  aside until they close
- a feature tip is marked seen, and a launch crash report acknowledged, only
  once actually shown
- tours read the arbiter instead of one shared boolean that any dialog's
  close cleared
Review follow-ups to the dialog turn-taking:
- the shared dialog primitive counts every open dialog, so a dialog that opens
  by itself also waits for dialogs outside the app's modal slot (unsaved
  changes, skill and remote-server updates, notebook setup, skill install);
  a prompt's own dialog is scoped out of the count
- a feature tip is marked seen, and a crash report acknowledged and holding
  its turn, only once its dialog content has rendered; a tip replaced by the
  user before it ever showed keeps its turn and opens again afterwards
- a tip whose dialog fails to render, or whose owner goes away, gives up its
  turn, closing only its own modal-slot entry
- crash reports raised by themselves queue by identity, each taking its own
  turn; a second never replaces the first
- the launch wait's 2 s bound runs from when this machine's resume read
  starts, a machine that holds no chats ends it at once, and a boot backstop
  ends it if neither happens; expiry only changes order, so a resume offer
  read later is still shown after whatever went first
…s belong to the queued item

- A shown automatic prompt stays mounted under a dialog opened over it; its dialog scope hides it
  (data-stepped-aside on the shared dialog primitive), so typed crash notes, a send in flight and the
  CLI tip's setup terminal survive. Only the automatic instance is scoped; dialogs a prompt opens
  inside itself are part of it.
- Being shown is a property of the queued request (id + key), so an owner's next item never inherits
  it and its content's mark is never lost to effect order.
- User dialogs are read from what is rendered (presence mirrored into the store), not the modal
  slot, so a modal that failed to render holds nothing back, including its own crash report.
- CommandDialog counts as a dialog; a queued prompt that cannot show does not block a tour inside
  the dialog holding the screen; the app-level tip owner no longer re-renders on every dialog.
- Help > Report Crash drops that report from the automatic queue; error-boundary reports not yet
  shown coalesce to the newest.
…unt; Help keeps the report on screen

- The app-open tip asks for a turn only while the modal slot is free or its own, so it never
  replaces a user's modal (even one still loading) or answers the hooks trust prompt for them.
- Lazily loaded user dialogs count as on screen while their code loads (DialogLoadingSuspense
  renders the presence marker as its fallback); a failed load still counts as nothing. The tip has
  its own Suspense so its own loading never holds it back.
- Help > Report Crash over the report on screen keeps that same dialog: the surface is keyed by
  report, and closing a report's dialog drops that report from the queue however it was opened.
- When a dialog over a stepped-aside prompt closes and focus fell to the page body, focus returns
  to where it was in the prompt.
…ansitions; the owner settles crash sends

- A user's modal-slot dialog counts as on screen from openModal, before its code or first render,
  unless a modal surface is showing its error fallback (FailedModalSurfaceMarker, mirrored into the
  store) or the slot is a handoff that renders no dialog (project-added). Sidebar dialogs get a
  modal-surface boundary. DialogLoadingSuspense stays only for the SSH prompt, outside the slot.
- Automatic prompt dialog content takes no CSS transitions: duration-200 also transitioned
  visibility, so a prompt stepping aside stayed seen for 200 ms, and on return it was hidden for a
  frame, when focus was put back.
- Crash report sends are owned by CrashReportDialog, keyed by report id: a result settles its report
  (drops it from the queue, closes a dialog showing it) even after the dialog that started it was
  replaced; a report being sent shows as sending wherever it is shown. Help pressed again keeps the
  report its dialog shows.
…d later stack over it

Drops the step-aside machinery: a shown resume offer, crash report or
feature tip is no longer hidden while another dialog is up. A dialog
opened over it stacks on top, as on main. Turn-taking, waiting for
not-yet-shown prompts, and seen/acknowledged-on-render are unchanged.
@brennanb2025

brennanb2025 commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor Author

Review status: ready for review (design B)

The problem. After Orca restarts (especially after an update), several dialogs that open by themselves could appear at the same time and stack on each other: the "Resume interrupted chats?" offer, the crash report, a feature tip, a product tour. Nothing made them take turns.

What changes for the user. These dialogs now appear one at a time, in a fixed order: resume offer, then crash report, then feature tip. A tour starts only when none of them is up or waiting. At launch they wait briefly (up to 2 s) for this computer's resume check, so the resume offer goes first. One that hasn't appeared yet also waits while you have a dialog open (palette, confirmation, SSH passphrase prompt), and appears after you close it. A dialog you open after one is already showing stacks on top of it, as on main. The one underneath keeps everything you typed.

Design change in this round. An earlier version hid ("stepped aside") a self-opening dialog that was already showing whenever another dialog opened, then brought it back. Every review round found a new bug in that hiding: focus lost on return, and a visible flash when the covering dialog's code loaded late (the resume offer appeared, then vanished up to a second later). We removed the hiding. That also removed this round's open bugs about the flash, focus and transitions.

  • Difference, "for now": a dialog you open (or the SSH passphrase prompt) arriving after a self-opening dialog is already on screen stacks over it. This matches main and the common pattern, and may be revisited.
  • Intended difference: one scheduler across dialog types. The common pattern has none; Orca raises several modal prompts by itself after a restart.

Fixed across the review (5 rounds):

  • A shown crash report or tip could be unmounted by another dialog, losing typed notes or a send in flight (a duplicate send was possible), and closing the tip's setup terminal.
  • The "shown" holder was per dialog type, not per queued item, so the next queued crash report could be acknowledged without being seen.
  • A modal that crashed while rendering held its own crash report back.
  • Help > Report Crash over the automatic report remounted it, losing notes and offering a second send.
  • The tip could replace a user's modal while that modal's code was loading, which also skipped a pending hook-trust question.
  • A tour was blocked behind a queued prompt it could never see.
  • Tests failed typecheck.

Deferred (P3 / no user impact found):

  • A sidebar-hosted dialog opened from Settings, Activity or Space is already invisible on main. It now also keeps not-yet-shown self-opening dialogs waiting until you leave that view.
  • A sidebar dialog that fails to render shows the same compact error card as other modal surfaces, inline next to the sidebar.
  • A failed or hanging crash acknowledgement still gates "Don't Send" (same as main).
  • The launch wait uses wall-clock time.
  • A few internal cleanups: one fact stored twice for the SSH prompt, internal-only exports.

Residual, unproven: a tip whose dialog throws every time is never marked seen, so it would retry each launch.

Verified:

  • 15 test files, 159 tests, covering stacking, waiting, order, crash queue and tip eviction. Each new behaviour was checked by breaking the code on purpose and seeing the test fail.
  • tc:web green. oxlint, the formatter and check:code-quality:changed clean.
  • Final readiness checklist: no P0–P2.
  • CI green at 8ea07d1 (first attempt). An earlier head had three reds, none in this PR's files: a 60 s Windows installer-probe timeout, a runner shutdown that killed a test shard, and a main-process timing test. All passed on rerun. Earlier still, main-side lint and typecheck reds blocked the run; main fixed them in test(mobile): split the turn-disclosure test under the 800-line test limit #25933 and fix(ci): restore main's typecheck and lint after #25751 and #24369 #25949.
  • After live QA, main's fix(native-chat): say each chat outcome once and drop internal-only surfaces #24595 ("say each chat outcome once") conflicted in the resume store, status-bar entry and resume test. The merge keeps both: fix(native-chat): say each chat outcome once and drop internal-only surfaces #24595's single outcome toast and its reopen path, now opening as a user-opened dialog, and this PR's launch turn. One silent bad merge in a test was fixed. One behaviour-neutral change keeps the store under its line limit: the opted-in automatic resume is started without awaiting it. A fresh reviewer proved this neutral by running the same tests against the store before and after, across 8 launch cases (auto-resume on or off, lost or answered resume, failed reads, a throwing toast, status-bar and toast reopen). 24 test files and 279 tests pass, and a fresh tc:web is green.
  • Live QA on macOS (M4 Air, hidden window driven over the DevTools protocol, isolated profile) at 7978884, before the fix(native-chat): say each chat outcome once and drop internal-only surfaces #24595 merge. B1–B6 all pass:
    • order resume → crash → tip, one at a time, with no invisible layer after;
    • the SSH prompt and the palette stack over a shown crash report and over the resume offer, with notes kept and nothing remounted;
    • a palette opened before the offer appears keeps the offer waiting until it closes (no flash in 4 timed launches);
    • Help > Report Crash twice and during a send: sent exactly once;
    • a tip replaced by the palette leaves nothing stuck.
  • The resume offer and crash report were real (interrupted Claude chat, real renderer crash). The SSH prompt was simulated through the store.

Not verified:

  • A real SSH passphrase prompt over a live connection.
  • Windows, Linux and WSL live runs. The change is renderer-only and has no platform branches.
  • Focus after a stacked dialog with no trigger closes lands on the page, as on main. Not treated as a defect.

QA process note: Grok was skipped as the QA driver on the M4Air, because it reads its own real profile at startup. The run used opencode (DeepSeek V4.1 Flash). Before a hold, the driver opened the new-tab agent menu once and clicked "Claude". That opened a structured chat through the sanctioned stand-in script, inside the isolated profile. No other agent ran. The rig was then rebuilt with the agent menu banned and every agent except Claude disabled.

@brennanb2025
brennanb2025 marked this pull request as ready for review October 6, 2026 20:21
@brennanb2025
brennanb2025 marked this pull request as draft October 6, 2026 20:22
…2025/rsc-launch-prompt-turns

# Conflicts:
#	src/renderer/src/components/NativeChatResumeOnRestartModal.test.tsx
#	src/renderer/src/components/native-chat-resume-on-restart-store.ts
#	src/renderer/src/components/status-bar/NativeChatResumeStatusSegment.tsx
…main merge

The opted-in launch resume is no longer awaited by the launch read, so the
existing settle hook marks the launch decided on every path; tests reset the
dialog request module themselves.
brennanb2025 added a commit that referenced this pull request Oct 6, 2026
B0 switches automatic prompts to stacking (no step-aside) and brings main
forward to 4c35516, including the per-client restart-offer audience
(#25159). The paired listed dismiss now honours that audience too.
@brennanb2025
brennanb2025 marked this pull request as ready for review October 6, 2026 20:46
@coderabbitai

coderabbitai Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 3b078d22-388c-4580-aa45-b9b52fbc112e
📥 Commits

Reviewing files that changed from the base of the PR and between e0f4bd1 and 0d8f16d.

📒 Files selected for processing (5)
  • src/renderer/src/components/NativeChatResumeOnRestartModal.list.test.tsx
  • src/renderer/src/components/NativeChatResumeOnRestartModal.test.tsx
  • src/renderer/src/components/NativeChatResumeOnRestartModal.tsx
  • src/renderer/src/components/sidebar/index.tsx
  • src/renderer/src/components/ui/command.tsx

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 3 remain after this review.


📝 Walkthrough

Walkthrough

The pull request adds a dialog registry for automatic and user dialogs. Startup hydration coordinates onboarding and feature-tip checks. Crash reports and native-chat resume offers use registry entries and startup discovery. Contextual tours and several UI behaviors now check registered dialog visibility. The app-open feature-tip flow replaces the former feature-tip modal path. The project-added dialog component and modal branch are removed.

Priority: ➖ Normal

Merge Risk: 🔵 Low · up to 0d8f1

A narrow timing race can leave an already-active chat in the resume offer. The PR is mergeable with owner awareness and a follow-up for that stale-offer case.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.53% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 170 functions across 75 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: automatic dialogs now take turns.
Description check ✅ Passed The description is detailed and covers the user impact, implementation, rationale, issue context, visual proof, testing, review, and compatibility considerations. It also reports remaining Windows evi…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

brennanb2025 added a commit that referenced this pull request Oct 6, 2026
One way back to the resume dialog: the status bar entry, a resume toast's
Show and a reconnect toast's Show chats all re-read first and open only
over rows, as main's opener does. A server out of contact, or one whose
re-read fails, opens on its last listing instead of holding the click back.

A paired resume always ends in one toast: the continue call waits as long
as a command does for the host's answer, and when that answer is lost the
chats the server no longer lists count as unconfirmed instead of vanishing.
@brennanb2025
brennanb2025 marked this pull request as draft October 7, 2026 06:23
…ogs show

Replaces the turn-taking layer (presence markers mirrored into the store,
hand registrations, modal-slot inference, failed-surface flag, subtree
exemption, 2 s / 10 s launch timers) with one window-scoped registry of
every dialog: user and response dialogs, the ones the app opens by itself,
and a running tour.

- The shared Dialog and Command roots register while open; the modal slot,
  the SSH prompt, onboarding and each self-opening dialog reserve an entry
  before their code loads and their dialog root adopts it.
- Self-opening dialogs are admitted one at a time when nothing else is up,
  in the order crash report, feature tip, resume offer; each kind also waits
  until every earlier kind's startup check has answered (ready, none or
  unavailable), so a fast late check never shows first. No timers.
- A dialog the user opens stacks over a shown one, which stays mounted; the
  next is admitted only once the previous finished closing.
- The app-open feature tip renders from its own host, not the modal slot,
  so a user modal can no longer replace it.
- Crash reports queue by report id; a second report never replaces the
  first. Help > Report Crash over a report on screen keeps that report.
- Tours go after every self-opening dialog and run inside the composer,
  not over dialogs opened from it.
…e and a late load never swaps a taken-over report
… when settings fail

A failed settings read left the feature-tip startup check pending all session,
so the resume launch offer never showed. Startup now hands the tip check its
inputs when the onboarding read resolves (null when settings or onboarding
could not be read, or startup failed first), instead of when it later shows
onboarding, and the check no longer waits for the CLI probe when no tip is
left either way.
…nted

An admitted crash report or tip whose code was still loading stayed admitted
when a user or SSH dialog opened, then mounted on top of it and took focus.
In dev StrictMode the tip host's simulated unmount also let a queued resume
offer take the tip's turn. Admission is now decided again on every change
until the dialog paints: a dialog opened meanwhile stays on top and the
unpainted one goes back to its place; an earlier kind queued meanwhile goes
first.
…he slot

The app-open tip no longer sets activeModal, so readers that took
activeModal !== 'none' as 'a dialog is up' missed it: Escape on the Tasks page
closed the page instead of the tip, and the usage-percentage callout showed
over it. The readers without a DOM overlay check (Tasks Escape and Cmd+F,
the usage callout, hovered-workspace delete, terminal focus on activation)
now also ask the registry whether any dialog is up.
brennanb2025 added a commit that referenced this pull request Oct 7, 2026
…econnect

The resume dialog now takes its place through the app's one dialog
registry: a launch request is an automatic entry that waits behind the
crash report and the tip, a user request opens at once, and the dialog
draws from its entry's phase, open until its exit animation ends.

The dialog's owner, and only it, answers this launch's startup discovery
from this computer's read; the status entry reads offers without it, and
a paired server's read never holds the order. A launch request reaches
"shown" when the dialog is actually on screen, which is also when what it
lists counts as decided and the restart toasts for it go. A launch
request after the user asked is dropped, as the registry's API states.

Also: the hook no longer re-checks for the user's own chats here; the
store already drops an unseen launch request once none are left. The
unused offer-activity watcher #25674 extracted is removed (this branch
watches per machine), and #25674's behavioral test reads offers per
machine.
@brennanb2025

brennanb2025 commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor Author

After restart, crash reports, the feature tip and the interrupted-chat offer could compete for the screen. A user dialog could replace the tip, and another report could replace the one being read. Automatic dialogs now take turns crash reports → feature tip → resume offer. User/response dialogs open immediately above shown content and retain its notes/setup; B stacking stays for now.

One content registry replaces before-content reservations and duplicate coordination. Crash reports queue by ID; sends survive closing their surface; acknowledgement/seen writes happen after content appears and never delay close. Review fixed late tip delivery, palette focus through exit, lazy first-run onboarding and enabled recovery reads with missing settings. Removed the dead compatibility dialog, generic tip fallback and separate resume discovery observer/completion flag. The common pattern has no shared lazy pre-content reservation, so the requested option B counts only attached content.

Comparable review size: +1393/-280 → +1217/-688, net +1113 → +529; 584 net production lines cut, including 251 in the second cut. Core 578 → 292. Final main integration preserves #25675's decision markers and makes the current-main footprint +1219/-688, net +531. Incoming main files are not counted as savings. Remaining addition buys the shared queue/content authority, distinct crash/send lifetime, tip survival and immediate keyed resume. Existing responsibility splits add25 counted lines and use no file-length exception.

Production area (whitespace ignored; tests/support excluded) Added Deleted Net
Occupancy consumers and focus 40 8 +32
Root host integration 5 15 -10
Startup discovery/hydration 113 55 +58
Onboarding policy/content 71 53 +18
Resume requests/discovery 229 122 +107
Tours/composer policy 42 62 -20
Removed manual dialog gates 1 38 -37
Crash reports/send ownership 297 109 +188
Feature tip host/content 111 100 +11
Obsolete slot cleanup 10 126 -116
Dialog/Command primitives 8 0 +8
Registry core 292 0 +292
Total vs final main 1219 688 +531

Final pushed e0f4bd1: 176 tests/13 files, fresh gated renderer + node typechecks, 22/22 mandatory static checks including React Doctor, and full overlap-file lint passed. Final CI passed on this exact head: 17 jobs passed, 11 optional jobs skipped, including both Linux/Windows packages and all ten Node24 unit shards. Mobile Checks and PR test LoC also passed; Headless Node server was skipped. No red job or step remained to classify against main.

The ready-triggered follow-on CI run also passed on the same head: 5 jobs passed, 22 skipped, including the end-to-end app build, Electron native-cache preparation and changed end-to-end specs. Headless Node passed too. The full end-to-end matrix and optional native WSL/IME/SSH CI jobs were skipped. No code, main merge or push followed the ready transition; no red remained to classify. Fresh full reduced-design review and narrow merge review found no remaining actionable P0–P3. Readiness checklist omitted counsel. No altered automatic launch call/ownership/order/lifetime, so the conditional short live-order rerun was not triggered; live QA remains explicitly at 45319de.

Verified live: hidden Windows automatic order with reverse data timing; real virtual-key palette stacks over all3 prompts and retained notes/selection/terminal; crash FIFO/deduplication, failed/hanging acknowledgement/discovery, cold lazy request and immediate manual resume. Real Docker encrypted-key SSH: Escape over crash, credential Cancel over tip, wrong-key error over resume, correct Unlock/reconnect/Add project/selected branch creation; independent host Git confirmed the checkout/branch. Archived verified screenshots are embedded in the PR body. Local wrong-key and settled branch/backing artifacts were inspected; detailed crash/tip SSH DOM and remaining shots are remote.

Verified environment guard: actual six pane fields stayed inside the run. Before/after content hashes for C:/Users/neil/.codex/config.toml, C:/Users/neil/.codex/hooks.json and C:/Users/neil/.orca/agent-hooks/codex-hook.cmd, plus empty .codex/hooks inventory, matched. Initial size/mtime omission was disclosed and approved. 60 complete later hash/size/mtime snapshots were independently parsed, unchanged. Snapshots61–69 followed later QA stages, rather than no work after60. Snapshot61 followed the final resource audit;62–63 were read-only transfer checks;64–65 bracketed closing owned control tabs;66–67 bracketed removing the owned QA checkout;68–69 were further read-only transfer checks. Their9 actual local event receipts say unchanged; full later payloads are not transferred. Those later QA stages launched no app/agent, ran no install and made no hooks/auth/config edit or restore. No file contents are published.

Pending, not passed: final after-loss real-file fingerprint; checkout-path/branch and cleanup audit; complete cumulative metadata payload; remaining control-tab closure/private stage directory cleanup and lock release; untransferred detailed SSH screenshots. Owned app/container/daemon/temporary keys/profiles cleanup is confirmed; tab closure/checkout removal are acknowledgements rather than a final path audit. Windows lock remains UNVERIFIABLE. No recovery actions on the shared runtime; coordinator is taking recovery to Brennan and explicitly decided these hygiene/evidence items do not hold ready.

Not exercised: the full CI end-to-end matrix and optional native WSL/IME/SSH jobs were skipped (changed end-to-end specs passed); native Help-menu input/physical OS or macOS focus, separate macOS/Linux/WSL app GUI, mobile emulator (no changed mobile surface/wire contract). Startup data/discovery faults and external submit/install results were simulated; renderer/terminal/SSH interaction was real, and the displayed installer was never executed. M4Air was unreachable. Cross-host parent refusal selector_not_found/LINEAGE_PARENT_NOT_FOUND is recorded; coordinator approved --no-parent, never C:/orca/orca. Required private Codex/OpenCode chain was unavailable, so permitted scripted CDP fallback was used.

Deferred: never-rendered tip can reappear on another launch; closed crash payloads retained for the window; existing sheets/popovers outside the dialog boundary; an overbroad test caption (nit). No unresolved proven severity issue. #25676 is independent; whichever merges second adapts at its final main merge.

Marking ready under the coordinator's explicit direction after final CI; never merged and auto-merge remains off.

@brennanb2025
brennanb2025 marked this pull request as ready for review October 8, 2026 00:38

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 89c954a9-5766-48e5-a66b-bbcd64fca51f
📥 Commits

Reviewing files that changed from the base of the PR and between 8ea07d1 and e0f4bd1.

📒 Files selected for processing (76)
  • src/renderer/src/App.tsx
  • src/renderer/src/app-shell/AppRootSurfaces.tsx
  • src/renderer/src/app-shell/onboarding-contextual-tours.test.tsx
  • src/renderer/src/app-shell/self-opening-dialogs.test.tsx
  • src/renderer/src/app-shell/startup-tip-check.test.tsx
  • src/renderer/src/app-shell/use-app-startup-hydration.ts
  • src/renderer/src/app-shell/use-onboarding-and-feature-tips.ts
  • src/renderer/src/components/NativeChatResumeOnRestartModal.test.tsx
  • src/renderer/src/components/NativeChatResumeOnRestartModal.tsx
  • src/renderer/src/components/NewWorkspaceComposerModal.tsx
  • src/renderer/src/components/WorktreeBaseFallbackDialog.test.tsx
  • src/renderer/src/components/WorktreeBaseFallbackDialog.tsx
  • src/renderer/src/components/browser-webauthn-account-dialog.test.tsx
  • src/renderer/src/components/browser-webauthn-account-dialog.tsx
  • src/renderer/src/components/confirmation-dialog.test.tsx
  • src/renderer/src/components/confirmation-dialog.tsx
  • src/renderer/src/components/contextual-tours/ContextualTourOverlay.remeasure.test.tsx
  • src/renderer/src/components/contextual-tours/ContextualTourOverlay.tsx
  • src/renderer/src/components/contextual-tours/ContextualTourOverlay.visibility.test.tsx
  • src/renderer/src/components/contextual-tours/contextual-tour-gate.test.ts
  • src/renderer/src/components/contextual-tours/contextual-tour-gate.ts
  • src/renderer/src/components/contextual-tours/use-contextual-tour.ts
  • src/renderer/src/components/crash-report/CrashReportDialog.test.tsx
  • src/renderer/src/components/crash-report/CrashReportDialog.tsx
  • src/renderer/src/components/crash-report/CrashReportDialogSurface.overflow.test.tsx
  • src/renderer/src/components/crash-report/CrashReportDialogSurface.tsx
  • src/renderer/src/components/error-boundaries/RecoverableRenderErrorBoundary.tsx
  • src/renderer/src/components/feature-tips/AppOpenFeatureTip.test.tsx
  • src/renderer/src/components/feature-tips/AppOpenFeatureTip.tsx
  • src/renderer/src/components/feature-tips/FeatureTipsModal.tsx
  • src/renderer/src/components/feature-tips/feature-tip-modal-state.test.ts
  • src/renderer/src/components/feature-tips/feature-tip-modal-state.ts
  • src/renderer/src/components/feature-tips/feature-tip-startup-gate.test.ts
  • src/renderer/src/components/feature-tips/feature-tip-startup-gate.ts
  • src/renderer/src/components/feature-tips/feature-tip-telemetry.test.ts
  • src/renderer/src/components/feature-tips/feature-tip-telemetry.ts
  • src/renderer/src/components/link-routing-preference-dialog.tsx
  • src/renderer/src/components/native-chat-resume-offer-activity-watch.ts
  • src/renderer/src/components/native-chat-resume-on-restart-dialog.ts
  • src/renderer/src/components/native-chat-resume-on-restart-store.ts
  • src/renderer/src/components/onboarding/OnboardingFlow.tsx
  • src/renderer/src/components/sidebar/ProjectAddedDialog.test.tsx
  • src/renderer/src/components/sidebar/ProjectAddedDialog.tsx
  • src/renderer/src/components/sidebar/hovered-workspace-delete.ts
  • src/renderer/src/components/sidebar/index.tsx
  • src/renderer/src/components/sidebar/worktree-list/navigation/use-reveal-requests.test.tsx
  • src/renderer/src/components/status-bar/UsagePercentageDisplayChangeNotice.test.tsx
  • src/renderer/src/components/status-bar/UsagePercentageDisplayChangeNotice.tsx
  • src/renderer/src/components/ui/command.tsx
  • src/renderer/src/components/ui/dialog.tsx
  • src/renderer/src/components/use-task-page-global-effects.dialogs.test.tsx
  • src/renderer/src/components/use-task-page-global-effects.ts
  • src/renderer/src/components/use-task-page-search-actions.ts
  • src/renderer/src/lazy-modal-mount-state.test.ts
  • src/renderer/src/lazy-modal-mount-state.ts
  • src/renderer/src/lib/dialog-registry-entry.test.tsx
  • src/renderer/src/lib/dialog-registry-entry.tsx
  • src/renderer/src/lib/react-error-boundary-reporting.ts
  • src/renderer/src/lib/workspace-activation-terminal-focus.test.ts
  • src/renderer/src/lib/workspace-activation-terminal-focus.ts
  • src/renderer/src/startup/startup-deferred-catalog-refresh.test.ts
  • src/renderer/src/startup/startup-deferred-catalog-refresh.ts
  • src/renderer/src/startup/startup-discovery-read.ts
  • src/renderer/src/startup/startup-runtime-session-hosts.ts
  • src/renderer/src/store/dialog-registry-state.test.ts
  • src/renderer/src/store/dialog-registry-state.ts
  • src/renderer/src/store/dialog-registry-test-state.ts
  • src/renderer/src/store/dialog-registry.ts
  • src/renderer/src/store/slices/ui-contextual-tours.test.ts
  • src/renderer/src/store/slices/ui-slice-test-harness.ts
  • src/renderer/src/store/slices/ui/ui-slice-contract-contextual.ts
  • src/renderer/src/store/slices/ui/ui-slice-modal-actions.ts
  • src/renderer/src/store/slices/ui/ui-slice-tour-actions.ts
  • src/shared/usage-percentage-display-change-notice.test.ts
  • src/shared/usage-percentage-display-change-notice.ts
  • tests/e2e/add-project-default-checkout.spec.ts
💤 Files with no reviewable changes (12)
  • src/renderer/src/components/link-routing-preference-dialog.tsx
  • src/renderer/src/components/sidebar/ProjectAddedDialog.test.tsx
  • src/renderer/src/components/feature-tips/feature-tip-modal-state.test.ts
  • src/renderer/src/components/confirmation-dialog.test.tsx
  • src/renderer/src/components/feature-tips/feature-tip-modal-state.ts
  • src/renderer/src/components/sidebar/ProjectAddedDialog.tsx
  • src/renderer/src/components/feature-tips/feature-tip-telemetry.test.ts
  • src/renderer/src/components/feature-tips/feature-tip-telemetry.ts
  • src/renderer/src/components/WorktreeBaseFallbackDialog.tsx
  • src/renderer/src/components/browser-webauthn-account-dialog.tsx
  • src/renderer/src/components/confirmation-dialog.tsx
  • src/renderer/src/components/contextual-tours/contextual-tour-gate.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 1 remain after this review.

Comment on lines +87 to +98
if (previous !== key) {
seen.set(sessionId, key)
// A first sighting is news only if newer than the list; a change to a known chat always is,
// since the host may have answered the list just before the change was delivered here.
changed ||= previous !== undefined || summary.updatedAt > offer.listedAt
}
}
if (changed && offeredChatRefresh === null) {
offeredChatRefresh = setTimeout(() => {
offeredChatRefresh = null
refresh()
}, OFFERED_CHAT_REFRESH_DELAY_MS)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Retain activity that cannot be refreshed during an action.

If the debounce fires while a continue or dismiss action is in flight, refreshAfterOfferedChatActivity() declines the read. The watcher has already updated seen, so it will not retry that activity change. If the action's host answer predates activity in another offered chat, that chat remains in the offer until another event triggers a read. Keep a pending refresh and run it after the action settles.

Comment on lines +351 to +352
} else if (enabled || !holds) {
const asked = getNativeChatResumeOnRestartDialogRequest() !== null

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

set -eu
head_rev='e0f4bd1586a321e8989ce168cf90c88584b77a64'
base_rev='e9b3efa7d75cf36244e2a1a03bfdd6f27a114f5b'
git cat-file -e "$head_rev^{commit}"
git cat-file -e "$base_rev^{commit}"
printf '%s\n' '--- changed file at reviewed head ---'
git show "$head_rev:src/renderer/src/components/native-chat-resume-on-restart-store.ts" | nl -ba | sed -n '1,460p'
printf '%s\n' '--- registry file at reviewed head ---'
git show "$head_rev:src/renderer/src/store/dialog-registry-state.ts" | nl -ba | sed -n '1,360p'
printf '%s\n' '--- relevant references at reviewed head ---'
git grep -n -E 'useNativeChatRestartOffer|native-chat-resume|ownsStartupDiscovery|readLocalStructuredAgentSessionsHeld|settle.*startup|startup.*settle' "$head_rev" -- 'src/renderer/src' || true
printf '%s\n' '--- PR diff for relevant files ---'
git diff --unified=30 "$base_rev" "$head_rev" -- \
  src/renderer/src/components/native-chat-resume-on-restart-store.ts \
  src/renderer/src/store/dialog-registry-state.ts

Repository: stablyai/orca

Length of output: 43030


🏁 Script executed:

set -eu
h=e0f4bd1586a321e8989ce168cf90c88584b77a64
b=e9b3efa7d75cf36244e2a1a03bfdd6f27a114f5b
git cat-file -e "$h^{commit}"
git cat-file -e "$b^{commit}"
git show "$h:src/renderer/src/components/native-chat-resume-on-restart-store.ts" | nl -ba | sed -n '280,410p'
git show "$h:src/renderer/src/store/dialog-registry-state.ts" | nl -ba | sed -n '1,280p'
git grep -n -E 'useNativeChatRestartOffer|ownsStartupDiscovery|native-chat-resume|readLocalStructuredAgentSessionsHeld' "$h" -- 'src/renderer/src' || true
git diff --unified=20 "$b" "$h" -- src/renderer/src/components/native-chat-resume-on-restart-store.ts src/renderer/src/store/dialog-registry-state.ts

Repository: stablyai/orca

Length of output: 42082


🏁 Script executed:

set -eu
h=e0f4bd1586a321e8989ce168cf90c88584b77a64
printf '%s\n' '--- discovery implementation and references ---'
git grep -n -E 'function readStartupDiscovery|const readStartupDiscovery|readStartupDiscovery\\(' "$h" -- 'src/renderer/src' || true
printf '%s\n' '--- relevant startup tests ---'
git show "$h:src/renderer/src/app-shell/self-opening-dialogs.test.tsx" | nl -ba | sed -n '690,845p'
printf '%s\n' '--- registry store and consumers ---'
git grep -n -E 'selectAdmittedDialog|selectDialogPhase|startupSources|settleStartupSource|nextAutomaticDialog' "$h" -- 'src/renderer/src/store' 'src/renderer/src/app-shell' 'src/renderer/src/lib' || true
printf '%s\n' '--- startup discovery files ---'
git ls-tree -r --name-only "$h" | grep -E 'startup-discovery|dialog-registry' || true

Repository: stablyai/orca

Length of output: 20424


🏁 Script executed:

set -eu
h=e0f4bd1586a321e8989ce168cf90c88584b77a64
printf '%s\n' '--- startup discovery helper ---'
git show "$h:src/renderer/src/startup/startup-discovery-read.ts" | nl -ba
printf '%s\n' '--- local structured session helper references ---'
git grep -n -F -- 'readLocalStructuredAgentSessionsHeld' "$h" -- 'src/renderer/src' || true
printf '%s\n' '--- local structured session helper owner candidates ---'
git ls-tree -r --name-only "$h" | grep 'local-structured-chats' || true

Repository: stablyai/orca

Length of output: 1923


Settle a true held-session result.

When enabled is false and readStartupDiscovery(readLocalStructuredAgentSessionsHeld()) resolves to true, the callback skips settleStartupSource(...) because enabled || !holds is false. readStartupDiscovery preserves the resolved boolean; the promise continuation does run, so a separate subscription is not needed.

The source can remain pending, and selectTourBlocked blocks non-forced tours while any startup source is pending. Add an explicit settlement path for holds === true.

brennanb2025 added a commit that referenced this pull request Oct 8, 2026
…ct toast, one status entry, one dialog grouped by machine

When a paired Orca server restarts while chats on it are mid-reply, this
desktop now finds out: each verified connection to a server reads its
restart offers, a toast names the server and offers Resume or Show
chats, the status-bar entry counts the user's own interrupted chats on
every machine, and the resume dialog lists every machine with its own
row, tri-state box and workspaces. A server that predates the offer
capability shows nothing.

Rebuilt on #25652 alone: this branch no longer depends on #25674. The
dialog opens through main's request/consume/subscribe API; a request
carries only the machine it was opened for, and a request while one is
open keeps the open dialog (its focus and the user's ticks). The
offer-store, actions, toast, status entry, machine rows, Select all,
own-only Dismiss, outcome toast, listed dismissal, origin, per-machine
fence and the 195 s continue budget are this branch's own work, carried
over unchanged.
…the one end-of-review sync)

#25652's resume-dialog tests open the dialog as the user and start past
the startup checks, like the other resume-dialog tests here.
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Main merged in once, at the end of review (0d8f16d), after #25652 (resume dialog shows a tree grouped by machine) landed and conflicted with this PR.

  • The only conflict was in NativeChatResumeOnRestartModal.test.tsx. I kept main's new shared test helpers and this PR's way of opening the dialog, with the origin set to 'user'.
  • feat(native-chat): show each interrupted chat's workspace as its read-only sidebar card #25652 added NativeChatResumeOnRestartModal.list.test.tsx, which opened the dialog without an origin. I gave it the same setup as the other resume-dialog tests here: it opens the dialog as the user and resets the dialog registry so each test starts past the startup checks.
  • No production code changed in the merge. The resume dialog still enters the dialog registry the same way.
  • Checks: both typechecks pass with no errors, the 28 related test files pass (282 tests), and all 22 CI static checks pass. CI on 0d8f16d: 24 passed, 0 failed.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant