Skip to content

fix(app): take the gate's coach marks out of #splash's stacking context - #565

Merged
efiten merged 2 commits into
efiten:masterfrom
khagele:fix/562-splash-ring
Aug 30, 2026
Merged

fix(app): take the gate's coach marks out of #splash's stacking context#565
efiten merged 2 commits into
efiten:masterfrom
khagele:fix/562-splash-ring

Conversation

@khagele

@khagele khagele commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Closes #562

What was happening

The cold-start gate draws three coach marks, each a small ring on its target with a thin leader
line to a box beside it. Two of the three lines ran up to the top bar and stopped at nothing,
because their rings were painted underneath it.

Measured on ae9ba97 at 412x915 with the gate showing, document.elementFromPoint at each
ring's centre:

coach mark anchor ring at element on top
cm-controls #filter-pill 174, 44 #topbar
cm-menu #settings-btn 372, 42 #topbar
cm-fabs #layer-toggle 339, 775 the ring itself

Why raising the z-index could not fix it

#splash is position: fixed with a z-index, so it is a stacking context. Its children
compete only inside it and are capped at its own 550, whatever they declare for themselves. The
gate lifts #topbar to 560 so Settings stays reachable, which puts the bar above the whole
#splash subtree. .splash-ring already declared 561 and it made no difference.

The rail's ring was fine only because its target sits at 500.

The change

The nine coach-mark elements become siblings of #splash instead of children. That is the whole
fix. The z-index values are untouched: once the elements are out of the trapped context, the
existing 561 is already above the bar's 560.

Both places that would tempt someone to "fix" this by raising numbers now carry a comment saying
why that cannot work.

What is deliberately unchanged

The decision the gate was built on stands: the FABs stay under the scrim, visible through it for
the coach mark to point at and untouchable, and #settings-btn remains the one live control.
Measured after the change:

  • elementFromPoint at each of the three ring centres returns the ring itself.
  • The ring positions are identical to before, so only the painting order moved.
  • #settings-btn is still clickable.
  • #layer-toggle still resolves to #splash, so the rail is still inert.

The regression this invites

With the elements outside #splash, hiding #splash no longer hides them. It does not have to:
refreshSplash() sets hidden on all nine on every state change, and that is the only place
#splash's own hidden is set, so the two cannot drift. Verified on the running app that the
marks disappear when the gate is dismissed and stay hidden when the "?" tour is reopened.

The outside-tap handler on #splash is unaffected: it returns unless e.target === e.currentTarget,
so it never fired on these elements anyway, and it is gated on the tour.

Tests

splash.test.js gains three assertions: that the gate's bar really does outrank #splash (the
premise), that no coach-mark element sits inside #splash, and that the three classes declare a
z-index above the gate's bar.

Mutation-checked by moving one ring back inside #splash, which fails with
cm-menu-ring is outside #splash's stacking context. The premise and z-index assertions pass
before and after by design; they are guards against the two ways this can regress, not the
red-to-green pair.

  • app: 851 passed, build clean
  • web: 504 passed
  • eslint: clean

Changelog

One entry, app only, in both copies.

🤖 Generated with Claude Code

khagele and others added 2 commits August 29, 2026 17:11
The cold-start gate draws three coach marks, each a ring on its target with
a thin leader line to a box. Two of the three rings were painted underneath
the top bar, so the line ran up to the bar's edge and stopped at nothing.

#splash is position: fixed with a z-index, which makes it a stacking
context. Its children are capped at its own 550 whatever they declare, and
the gate lifts #topbar to 560 so Settings stays reachable. The rings on
#filter-pill and #settings-btn therefore landed under the bar; only the
rail's ring, whose target sits at 500, was visible. Raising .splash-ring's
z-index cannot fix that from inside #splash, so the nine elements become
siblings of it instead.

The design decision the gate was built on is unchanged: the FABs stay under
the scrim, visible through it and untouchable, and #settings-btn stays the
one live control. Measured at 412x915 after the change, elementFromPoint at
each ring's centre returns the ring itself (it returned #topbar for two of
them before), the ring positions are byte-identical, #settings-btn is still
clickable and #layer-toggle still resolves to the scrim.

refreshSplash() already sets `hidden` on all nine on every state change, so
moving them out of #splash does not change when they appear: verified that
they hide when the gate is dismissed and stay hidden when the "?" tour is
reopened.

Tests: app 851 passed, web 504 passed, app build clean. The new assertions
in splash.test.js were mutation-checked by moving one ring back inside
#splash.

Closes efiten#562

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts:
#	app/changelog.json
#	web/changelog.json

@efiten efiten left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Rebased onto master for you (changelog conflicts only, both copies merged and identical afterwards, 45 entries) and pushed.

The diagnosis is the kind that only yields to measurement: #splash is position: fixed with a z-index, so it is a stacking context, and a ring nested inside it cannot rise above a bar the gate lifts to a higher layer, whatever z-index the ring declares. elementFromPoint at each ring's centre returning #topbar is the right instrument and the right evidence.

Both halves are pinned, checked by breaking them:

  • Nesting one ring back inside the #splash aside turns keeps every coach-mark element out of #splash red. That is the actual bug, and the test catches it structurally rather than by measuring pixels, which is what makes it durable.
  • Dropping .splash-ring's z-index below the gate top bar turns the ordering test red.

That second one matters as much as the first: taking the marks out of the stacking context is only half the fix, since siblings still need to sort above the bar. Two independent guards for one behaviour is right here.

app/ 882 tests and a clean build with master merged in, eslint clean.

Reading a z-index out of the stylesheet in a unit test is unusual, and I think justified here: the alternative is a browser test for a paint-order property, and this catches the regression at the layer where it would be introduced.

@efiten
efiten merged commit 6fba59e into efiten:master Aug 30, 2026
6 checks passed
@github-actions github-actions Bot mentioned this pull request Aug 30, 2026
efiten pushed a commit that referenced this pull request Aug 30, 2026
🤖 I have created a release *beep* *boop*
---


<details><summary>app: 1.24.0</summary>

##
[1.24.0](app-v1.23.0...app-v1.24.0)
(2026-08-30)


### Features

* **app:** give the theme three states and a memory, and end the
Settings tab ([#566](#566))
([e67c319](e67c319))
* **app:** keep the SNR the repeater heard us at
([#489](#489))
([0cd0a7c](0cd0a7c))
* **app:** make every recorded reception audible, one instrument per
type (+ sound tweaks)
([#470](#470))
([6244c0f](6244c0f))
* **web:** gate the point layer, and say what an account opens
([#513](#513))
([71856f1](71856f1))


### Bug Fixes

* **app:** take the gate's coach marks out of #splash's stacking context
([#565](#565))
([6fba59e](6fba59e))
</details>

<details><summary>web: 1.20.0</summary>

##
[1.20.0](web-v1.19.0...web-v1.20.0)
(2026-08-30)


### Features

* **app:** give the theme three states and a memory, and end the
Settings tab ([#566](#566))
([e67c319](e67c319))
* **app:** keep the SNR the repeater heard us at
([#489](#489))
([0cd0a7c](0cd0a7c))
* **app:** make every recorded reception audible, one instrument per
type (+ sound tweaks)
([#470](#470))
([6244c0f](6244c0f))
* **web:** gate the point layer, and say what an account opens
([#513](#513))
([71856f1](71856f1))


### Bug Fixes

* **app:** take the gate's coach marks out of #splash's stacking context
([#565](#565))
([6fba59e](6fba59e))
</details>

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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.

app: two of the splash gate's three pointing rings are drawn under the top bar

2 participants