Fix Settings crash on cross-window tab drag (APP-5314) - #14957
Fix Settings crash on cross-window tab drag (APP-5314)#14957warp-agent-staging[bot] wants to merge 2 commits into
Conversation
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>
|
@joeywangzr This is ready for your review — it's the fix for the crash you hit dragging Settings between two windows.
Outstanding: no visual verification. The client builds, but the GUI login rejects the verification environment's service-account credential ( 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. |
Description
Fixes APP-5314: dragging the Settings tab from one window to another could panic in
BillingAndUsageDispatchView::as_refwith a "circular view reference" error (reported by @joeywang via Slack).Root cause:
SettingsViewowns every settings page, butrender(viafiltered_pages) only ever embeds the currently active page, so inactive pages are invisible to the render-time parent/child graph thattransfer_view_tree_to_windowwalks. Most pages are created withadd_typed_action_view, which records a structural parent edge and so survive a cross-window transfer regardless of activity.AboutPageViewandBillingAndUsageDispatchVieware created with plainctx.add_view, which records no such edge, andSettingsViewnever overrodeView::child_view_idsto 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 ofAppContext::view, misleadingly worded "circular view reference").Fix: Add
SettingsView::child_view_ids, returning every page's entity id plussearch_editorandcontext_menu. The list is built by iteratingsettings_pages(the single source of truth for the page list) via a new, exhaustiveSettingsPageViewHandle::view_id()match — so a newly added settings page is covered automatically, and omitting a variant fromview_id()fails to compile (mirroring the existing exhaustivechild_view()match), rather than silently rotting a hand-maintained id list.I deliberately did not "fix" this by making
should_render_pagefall back totry_as_refand 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-textTextInputeditors are created via plainctx.add_viewand 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, withInstallationModalBody::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 viagit 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.app/src/settings_view/mod_tests.rs::settings_view_owned_view_ids_covers_pages_and_own_handlescallsSettingsView::owned_view_ids(the exact functionView::child_view_idsdelegates to) directly against realSettingsPage/SettingsPageViewHandle::Aboutvalues built from the realAboutPageView.app/src/settings_view/mcp_servers/installation_modal_tests.rs::child_view_ids_covers_text_input_and_dropdown_variablesdoes the same for the MCP install modal fix, against a realInstallationModalBodyinstance.child_view_ids, so it could not actually catch a regression in the realSettingsView::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 incrates/warpui_core/src/core/transfer_view_tests.rsas a generic transfer-machinery test, renamed and re-commented to say plainly that it is not APP-5314 coverage.SettingsViewend-to-end in a unit test isn't practical:SettingsView::newpulls 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-verificationskill.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 privatewarp-channel-configgenerator 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-8a6c5bd39462I 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
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.