Skip to content

Fix Settings crash on cross-window tab drag (APP-5314) - #14957

Draft
warp-agent-staging[bot] wants to merge 2 commits into
masterfrom
fix/app-5314-settings-cross-window-drag-crash
Draft

Fix Settings crash on cross-window tab drag (APP-5314)#14957
warp-agent-staging[bot] wants to merge 2 commits into
masterfrom
fix/app-5314-settings-cross-window-drag-crash

Conversation

@warp-agent-staging

@warp-agent-staging warp-agent-staging Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Description

Fixes APP-5314: dragging the Settings tab from one window to another could panic in BillingAndUsageDispatchView::as_ref with a "circular view reference" error (reported by @joeywang via Slack).

Root cause: SettingsView owns every settings page, but render (via filtered_pages) only ever embeds the currently active page, so inactive pages are invisible to the render-time parent/child graph that transfer_view_tree_to_window walks. Most pages are created with add_typed_action_view, which records a structural parent edge and so survive a cross-window transfer regardless of activity. AboutPageView and BillingAndUsageDispatchView are created with plain ctx.add_view, which records no such edge, and SettingsView never overrode View::child_view_ids to declare its other owned pages. On a cross-window tab drag, those two pages were left orphaned in the source window, and the destination window's render pass panicked trying to access them (the "missing view" branch of AppContext::view, misleadingly worded "circular view reference").

Fix: Add SettingsView::child_view_ids, returning every page's entity id plus search_editor and context_menu. The list is built by iterating settings_pages (the single source of truth for the page list) via a new, exhaustive SettingsPageViewHandle::view_id() match — so a newly added settings page is covered automatically, and omitting a variant from view_id() fails to compile (mirroring the existing exhaustive child_view() match), rather than silently rotting a hand-maintained id list.

I deliberately did not "fix" this by making should_render_page fall back to try_as_ref and skip missing pages — that would hide the orphan while leaving dangling views in the source window, and would resurface as a leak or a crash on a different page later.

Same orphan class in the MCP install modal: an independent review flagged that InstallationModalBody (the MCP server installation modal) has the identical hole: its free-text TextInput editors are created via plain ctx.add_view and only rendered while an install is pending; Cancel leaves the pending state populated without clearing it, so a cross-window drag could orphan those editors too. Fixed the same way, with InstallationModalBody::child_view_ids.

Linked Issue

Linear: APP-5314

Neighbouring/related: APP-5311 (PR #14950) touches Settings/Rules cross-window routing but is a different bug (stale pane locators/event routing, no panic) in different files (app/src/workspace/view.rs, app/src/settings_view/pane_manager.rs). This PR does not touch either file and has no overlap (confirmed via git merge-tree).

Testing

  • cargo test -p warpui_core --lib — all pass (314 passed, 7 ignored).
  • cargo test -p warp --lib settings_view:: — 247 passed, 0 failed.
  • cargo fmt --all -- --check — clean.
  • git diff --check — clean.
  • cargo clippy -p warp -p warpui_core --lib --tests --no-deps -- -D warnings — clean.
  • Regression tests, bound to production code (revised after review — see below):
    • app/src/settings_view/mod_tests.rs::settings_view_owned_view_ids_covers_pages_and_own_handles calls SettingsView::owned_view_ids (the exact function View::child_view_ids delegates to) directly against real SettingsPage/SettingsPageViewHandle::About values built from the real AboutPageView.
    • app/src/settings_view/mcp_servers/installation_modal_tests.rs::child_view_ids_covers_text_input_and_dropdown_variables does the same for the MCP install modal fix, against a real InstallationModalBody instance.
    • Both were verified fail-before/pass-after: I temporarily reverted each production fix and confirmed the corresponding test failed with a clear assertion diff, then restored the fix and confirmed it passed.
    • A prior version of the warpui_core test used a synthetic stand-in type that supplied its own child_view_ids, so it could not actually catch a regression in the real SettingsView::child_view_ids (an independent review caught this and verified it by reverting the production hunk in isolation — the test still passed). That test is kept in crates/warpui_core/src/core/transfer_view_tests.rs as a generic transfer-machinery test, renamed and re-commented to say plainly that it is not APP-5314 coverage.
    • Constructing the real SettingsView end-to-end in a unit test isn't practical: SettingsView::new pulls in singleton models for ~18 pages spanning billing, teams, warp drive, referrals, and MCP servers (some of which kick off live async server calls).

Visual verification — outstanding

This is a user-visible crash fix whose acceptance criterion is "drag Settings between two windows and the app stays up," so I attempted an end-to-end computer-use verification (two windows, Settings open in both, drag Settings from one onto the other) via a remote agent on a 32GB runner, per the ui-verification skill.

Result: blocked, not completed. The build succeeded cleanly, but the GUI could not get past the onboarding/login screen in that environment: the only available credential resolves to a service account, and the server rejects it for the desktop GUI login flow (GraphQlError: Unauthorized: Expected a user account). The private warp-channel-config generator needed for a fully proper channel config also wasn't reachable (private repo, no SSH access from that sandbox), though a workaround got the binary launching. Screenshot of the onboarding blocker: https://oz.staging.warp.dev/artifacts/019ff2e1-0c9b-781d-884a-8a6c5bd39462

I did not fake this or substitute a code walkthrough for it. The rendered-behavior verification (two-window drag, no crash, About/Billing pages render post-transfer) remains outstanding and needs either a user-account API key for staging (not a service-account key) or an already-authenticated verification environment. Everything else in this PR (root cause, fix, and the code-level regression tests above) is independently verified.

Agent Mode

  • Warp Agent Mode - This PR was created via Warp's AI Agent Mode

Conversation: https://staging.warp.dev/conversation/94c72e02-47ca-46e9-9390-b1b007298ba8
Run: https://oz.staging.warp.dev/runs/019ff26c-7491-725d-a624-93d743a20e3c

This PR was generated with Oz.

Dragging the Settings tab from one window to another could panic in
BillingAndUsageDispatchView::as_ref with a "circular view reference"
error.

SettingsView owns every settings page, but only the active page is
embedded during render (filtered_pages), so inactive pages are
invisible to the render-time parent/child graph used when transferring
a view subtree between windows. Most pages are created with
add_typed_action_view, which records a structural parent edge and so
survive the transfer regardless of whether they're active. AboutPageView
and BillingAndUsageDispatchView are created with plain ctx.add_view,
which has no such edge, and SettingsView never overrode
View::child_view_ids to declare its other owned pages. On a
cross-window drag, those two pages were left orphaned in the source
window, and the destination window's render pass panicked trying to
access them.

Add SettingsView::child_view_ids, returning every page's entity id plus
search_editor and context_menu. The list is built by iterating
settings_pages (the single source of truth for the page list) via a
new, exhaustive SettingsPageViewHandle::view_id() match, so a future
settings page addition is covered automatically instead of needing a
separately hand-maintained id list; omitting a variant from view_id()
fails to compile, the same way omitting one from child_view() already
does.

Added a regression test in warpui_core's transfer_view_tests.rs that
mirrors SettingsView's exact ownership shape (a view that renders only
its active child, with a mix of structural and plain add_view
children) and asserts all pages transfer to the destination window.
Constructing the real SettingsView in a unit test isn't practical: it
would require mocking ~18 sub-pages' dependencies spanning billing,
teams, warp drive, and MCP servers.

Co-Authored-By: Warp Agent <agent@warp.dev>
…l orphan

Three revisions from independent review of PR #14957:

1. The original warpui_core regression test exercised a synthetic
   SettingsViewLike type that supplied its own child_view_ids, so it could
   not fail when the real SettingsView::child_view_ids regressed (verified:
   reverting the production hunk left the test passing). Fix: extract
   SettingsView::owned_view_ids as the literal body child_view_ids delegates
   to (a plain function of settings_pages/search_editor/context_menu rather
   than &self), and add a real-code test in settings_view/mod_tests.rs that
   calls it directly against real SettingsPage/AboutPageView values.
   Verified fail-before/pass-after by temporarily breaking the function.

   Kept the original synthetic test in transfer_view_tests.rs, renamed and
   re-commented to describe it honestly as a generic transfer-machinery
   test (child_view_ids works for a mix of structural/non-structural
   children), not an APP-5314 regression test.

2. The same orphan class exists in InstallationModalBody: its free-text
   TextInput editors are created via plain ctx.add_view and only rendered
   while an install is pending, and Cancel leaves the pending
   server/inputs populated without clearing them, so a cross-window drag
   could orphan them exactly like AboutPageView did. Added
   InstallationModalBody::child_view_ids plus a real-code regression test
   (installation_modal_tests.rs), with the same fail-before/pass-after
   verification.

3. Attempted visual verification via a remote computer-use agent on a
   32GB runner. Blocked before reaching the repro: the environment's only
   credential resolves to a service account (server rejects
   "Expected a user account"), and the private warp-channel-config
   generator isn't reachable, so the GUI can't get past onboarding. Not a
   code issue. Documented in the PR description as an outstanding item
   per the reviewer's guidance not to fake or substitute a code
   walkthrough for visual proof.

Co-Authored-By: Warp Agent <agent@warp.dev>
@warp-agent-staging

Copy link
Copy Markdown
Contributor Author

@joeywangzr This is ready for your review — it's the fix for the crash you hit dragging Settings between two windows.

SettingsView now declares the page views it owns, so a cross-window tab transfer carries them along instead of stranding BillingAndUsageDispatchView and AboutPageView in the source window. An adversarial review of the first revision turned up two things, both addressed here: the same orphan class existed in the MCP installation modal's variable inputs, and the original regression test did not actually exercise production code. The test now binds to SettingsView::owned_view_ids and was verified to fail before the fix and pass after.

Outstanding: no visual verification. The client builds, but the GUI login rejects the verification environment's service-account credential (Unauthorized: Expected a user account), so the two-window drag was never exercised against a running app. This is documented in the PR description along with what would unblock it. Since you can reproduce the original crash in one move, a quick check on your local dev build would close that gap.

Note: this hand-off would normally have gone to the Slack thread (link), but the Slack and Linear integrations lost auth partway through this run, so it's posted here instead.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant