Unwind the host-mode stack when navigating to a page you are already inside (CS-12434) - #5688
Draft
burieberry wants to merge 3 commits into
Draft
Unwind the host-mode stack when navigating to a page you are already inside (CS-12434)#5688burieberry wants to merge 3 commits into
burieberry wants to merge 3 commits into
Conversation
Contributor
Preview deploymentsHost Test Results 1 files ±0 1 suites ±0 2h 12m 16s ⏱️ - 26m 10s Results for commit 645d5cd. ± Comparison against earlier commit a5a4d31. Realm Server Test Results 1 files ±0 1 suites ±0 13m 45s ⏱️ -28s Results for commit 645d5cd. ± Comparison against earlier commit a5a4d31. |
The published site does not go through addToHostModeStack: templates/index.gts calls HostModeStateService#pushCard, a separate stack with the same push-only behaviour. Fixing only the operator-mode service left boxel.ai — the case that matters — still stacking a duplicate of the page you came from. Tests use the existing ViewCardDemo fixture, which is already a cycle (index → secondary → tertiary → index), so tertiary's button is a real "up" link: one test walks down two levels and clicks through to the root, the other seeds a trail and unwinds to its middle. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The stack closed its top card on any click outside itself. Combined with the unwind, that undid the navigation it was reacting to: opening a card already in the trail unwinds to it, leaving that card on top, and the same click then read as an outside-click and closed it. The trail emptied, serialize() returned undefined, the hostModeStack param dropped out of the URL and the whole stack unmounted. Dismiss is now scoped to the scrim itself and ignores clicks landing inside a stack item, so the gesture is defined by where it applies rather than by exclusion. The breadcrumbs exception drops out — breadcrumbs render outside the scrim and never reached the handler. Only host mode was affected. Host submode drives the trail from stack items and breadcrumbs, so no click outside the stack existed there; a real visitor could not hit it in either mode, since the scrim covers the primary card and only a synthetic click reaches through it. Tests: the unwind by the route a visitor can take (a stack item targeting a card below it) in both modes, and dismiss-by-scrim in both. Host submode had no unwind coverage at all, so addToHostModeStack was untested. The existing query-param assertion read window.location, which is the test runner's URL and never carries app params; the new one reads currentURL. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
burieberry
force-pushed
the
cs-12434-host-mode-stack-unwind-on-ancestor
branch
from
August 5, 2026 00:57
a5a4d31 to
645d5cd
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
CS-12434
Why
Host mode only ever pushes onto its stack, so following an in-page link back to a page you are already inside stacks a second copy of it instead of unwinding to it.
Found on the boxel.ai blog: from the index, opening a post pushes it correctly, but the post's "← Back to Boxel Blog" link then pushes the index on top of the post — index → post → index — rather than closing the post and returning to the root. It applies to any host-mode realm with an "up" link.
Cards are only ever handed
viewCard; no pop or truncate is exposed to them, so realm content cannot work around it.What
Opening a card already behind you in the trail is a navigation back to it, so unwind rather than push. Pushing stays correct for a card not yet on the trail:
Applied to both stacks, which is the part worth reviewing. They are separate implementations and only the second one governs the published site:
OperatorModeStateService#addToHostModeStack— host submode in the workbench (host-submode.gts→viewCard)HostModeStateService#pushCard— the published site (templates/index.gts→viewCard)Dismiss had to move with it
The stack closed its top card on any click outside itself, which undid the unwind: the same click that unwound to a card also read as an outside-click and closed it. The trail emptied,
serialize()returnedundefined,hostModeStackdropped out of the URL and the whole stack unmounted.Dismiss is now scoped to the scrim and ignores clicks landing inside a stack item (
stack.gts). The breadcrumbs exception drops out — breadcrumbs render outside the scrim and never reached the handler.Scope, since it affects how much of this needs scrutiny: only host mode was reachable. Submode drives the trail from stack items and breadcrumbs, so no outside-click existed there — verified by reverting the fix, where the submode test still passes while the host-mode one fails. And no visitor could hit it in either mode, because the scrim covers the primary card; only a synthetic
click()reaches through. So this was a latent bug the new behaviour exposed, not a production break.Testing
Run locally against the dev stack: host mode 26/26, host submode 40/40.
lint:hbs,lint:types, eslint and prettier clean.The
ViewCardDemofixtures are already cycles — index → secondary → tertiary → index, and 1 → 2 → 3 → 1 in submode — so the "up" link is genuine rather than contrived.host-mode-test.gts[secondary, tertiary], viewssecondary, assertstertiarycloses andsecondarystays on tophost-submode-test.gts— the unwind, and dismiss by scrim. That side had no unwind coverage at all, soaddToHostModeStackwas shipping untested.One assertion fixed while here: the query-param check read
window.location, which in an acceptance test is the test runner's own URL and never carries app params, so it passed regardless of behaviour. The new assertions readcurrentURL().Notes
Not a regression. The behaviour has been push-only since the stack landed in #3361 and
viewCardwas wired to it in #3381; no commit has touched those function bodies since. So this is new behaviour rather than a revert — worth a glance at those PRs if there was original intent I have missed.🤖 Generated with Claude Code