Skip to content

Open the panel folded where it covers the aim on an upright phone - #1089

Closed
zkWizard wants to merge 3 commits into
mainfrom
codex/uiux-dogfood-20260821-2357
Closed

Open the panel folded where it covers the aim on an upright phone#1089
zkWizard wants to merge 3 commits into
mainfrom
codex/uiux-dogfood-20260821-2357

Conversation

@zkWizard

Copy link
Copy Markdown
Owner

Visible: Open roamingeye.org on a phone held upright (390x844, 360x800, 393x852, 412x915, 320x568). The data panel now opens folded — layer selector and provenance line showing, colour ramp and timeline put away — and the middle of the screen is the globe. Tapping the chevron in the panel's top-left corner brings every row back. On a larger phone (430x932) and at 540x960 nothing changes: the panel opens with every row, exactly as it does today.

The invariant this restores

e2e/hud-aim-clearance.spec.ts already states it, and states why it is not cosmetic:

The aim the keyboard turns the globe under is the camera subpoint. It renders at the exact centre of the canvas and it is the point Enter charts, so unlike a cursor it cannot be moved somewhere roomier without lying about which pixel it names.

That file pins "the centre of the view is globe, not HUD" at 1280px wide, at the short heights where the panel used to climb over the aim. An upright phone breaks the same invariant from the other direction, and no spec covered it.

Measured on 676fba7 before this change, by hit-testing the viewport centre after boot:

viewport centre hit-tests panel top vs centre
320x568 timeline__readout −210px
360x800 legend__bar −84px
390x844 legend__measures −62px
393x852 legend__measures −58px
412x915 layer-selector__current −27px
430x932 globe +12px
540x960 globe +54px

Neither existing rule can see this. The spacing trim (#980) is gated at 720px of height and the landscape auto-fold (#1027) at 460px, and every modern phone in portrait is 800–932px tall, clearing both. The reason is that the panel's height does not follow the window's — it is a fixed ~360px — so what decides the outcome is the proportion, which no height threshold reads.

Why a measurement and not a breakpoint

A breakpoint would be a guess at the panel's height, and the panel's height is the variable: it grows with the caption text, which CI's wider font metrics already prove is not a constant. So the fold asks the layout directly, as the boot curtain lifts — if the panel's top has landed above the middle of the window, the panel opens folded.

That is also what keeps this from being a taste call. It fires only where the invariant is already broken, and the spec asserts both directions: the two viewports that were already clear must still keep every row, so the rule cannot quietly drift into a blanket rule about narrow windows.

Two details that follow the notes already in the file:

  • Scoped to (max-width: 540px) — the same width arm the fold control renders at, so the gesture back is always on screen. A default that cannot be reversed is what the landscape note in main.ts explicitly refuses to ship.
  • Decided behind the loader, from the same first-load-done branch that dismisses it, so nothing opens and then folds a frame later. It uses the forward-declared let hook idiom already in the file for foldHudForOverlay.

Scientific content is untouched. The fold keeps the layer selector and the provenance line, so the product ID and the month are rendered in the default state and no citation ends up behind a gesture — the same defence #1023 and #1027 made.

Verified

  • New spec e2e/portrait-aim-clearance.spec.ts — 3 tests, 18.2s locally. Asserts the aim is the globe and the panel is folded at the two covered sizes; that the folded panel still shows #layer-selector and #provenance, that the fold control is on screen with aria-expanded="false", and that clicking it restores the legend and timeline; and that 430x932 / 540x960 keep every row.
  • Red-checked against origin/main (restored src/main.ts from origin/main by cp, rebuilt, re-ran): fails with exactly Error: the aim is covered by the HUD at 390x844 plus the aria-expanded assertion. The third test passes on main by design — it is the no-over-reach guard, not a red-check candidate.
  • Full local Playwright suite (--project=chromium, CI=1) — reported in a comment below; this is a layout-affecting change, so it is owed and was run.
  • npx tsc --noEmit clean for touched files; full npm test — 288 files / 4627 tests passed; npx prettier --write on all three files.
  • Bundle: entry chunk index-*.js 53.6 kB gzip (budget 60 kB), ok — logic only, no new module.

Backlog

Closes the "Should the panel also open folded on a phone held upright?" item in docs/BACKLOG.md, moved to Done with the measurements above. That item asked for a full local e2e run for exactly this reason, and noted the netbook-band and legend questions alongside it — both are about viewports wider than 540px, so this does not reach them and they stay open.

🤖 Generated with Claude Code

zkWizard and others added 3 commits August 21, 2026 19:07
hud-aim-clearance.spec.ts states the invariant: the aim is the camera
subpoint, it renders at the exact centre of the canvas and it is the point
Enter charts, so the centre of the view belongs to the globe, not the HUD.
That file pins it at 1280px wide. An upright phone breaks it — measured on
676fba7 the centre hit-tests legend__measures at 390x844 and 393x852,
legend__bar at 360x800, layer-selector__current at 412x915 and
timeline__readout at 320x568, the panel's top standing above the middle of
the window by 62, 58, 84, 27 and 210px.

Neither existing rule can see it: the spacing trim is gated at 720px of
height and the landscape auto-fold at 460px, and every modern phone in
portrait clears both. The panel's height does not follow the window's, so
the proportion is what decides the outcome and no height threshold reads it.

Measured rather than keyed to a breakpoint, because a breakpoint would be a
guess at the panel's height and that height is the variable — it grows with
the caption text. As the boot curtain lifts, if the panel's top has landed
above the centre of the window the panel opens folded. It fires on exactly
the viewports above and leaves the rest alone: 430x932 clears by 12px and
540x960 by 54px and both keep every row, which the spec asserts alongside
the fold so the rule cannot drift into a blanket rule about narrow windows.

Scoped to the same 540px width arm the fold control renders at, so the way
back is always on screen, and the fold keeps the layer selector and the
provenance line, so the product ID and the month survive it.

Closes the upright-phone item in docs/BACKLOG.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@zkWizard

Copy link
Copy Markdown
Owner Author

Closing this myself: the full local e2e run the backlog item asked for is exactly what disqualified the fix, and it did so for a reason worth recording rather than patching around.

4 failures / 227 passed, all four caused by this change. Three are older specs that encode the current default as their premise (hud-collapse.spec.ts:177 and :230, probe-overlap-phone.spec.ts:80) — those would be legitimate repairs. The fourth is not:

a11y.spec.ts:515"timeline steppers clear AA without reaching the scrubber" — fails with the steppers measuring 0x0, because a folded panel does not render the timeline at all. That suite's touch-target list is evaluated at 390x844 and treats "not rendered" as a failure, so the repo has a standing commitment that the timeline steppers are reachable on exactly the viewport this PR changes.

So there are two shipped commitments in direct conflict at 390x844 — "the centre of the view is globe, not HUD", and "the timeline steppers are reachable at 390x844" — and folding the whole panel does not resolve that, it just picks a side. My PR body argued this was not a taste call because an invariant was already broken. That was incomplete: there is an opposing invariant, and trading the time scrubber out of a time-series viewer's default view on the most common mobile form factor is precisely the "reader's default" decision the backlog item said should not be guessed at. The measurements stand; the remedy does not follow from them.

The measurement that points at the better fix. Panel rows at 390x844 / 360x800 / 412x915 measure identically: legend 132px, timeline 102px, layer selector 32px, provenance 15px, panel 360px. Folding only the legend takes about 140px out with the row gap, which moves the panel's top down to clear the aim by +78px at 390x844, +56px at 360x800 and +113px at 412x915 — while the timeline, its steppers, the layer selector and the provenance line all stay in the default view. That keeps both commitments instead of trading one for the other, and it needs a second collapsed variant rather than reusing .controls.is-collapsed, which is more than this run's remaining timebox.

The backlog item is therefore still open and is now better specified than it was; I have not left the docs/BACKLOG.md edit on main. 320x568 stays out of reach of that approach (covered by 210px, more than the legend is worth) and belongs with the existing short-window band question.

Verified along the way and worth keeping: the defect reproduces on the live site identically to local — legend__measures at 390x844 (-62px), legend__bar at 360x800 (-84px), globe at 430x932 (+12px).

@zkWizard zkWizard closed this Aug 22, 2026
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