Skip to content

Let a phone held upright reach the fold that already exists - #1037

Merged
zkWizard merged 1 commit into
mainfrom
codex/uiux-backlog-portrait-fold-20260818-2104
Aug 18, 2026
Merged

Let a phone held upright reach the fold that already exists#1037
zkWizard merged 1 commit into
mainfrom
codex/uiux-backlog-portrait-fold-20260818-2104

Conversation

@zkWizard

Copy link
Copy Markdown
Owner

Visible: On a phone held upright, a fold control now appears in the top-left
corner of the bottom panel. Tapping it puts the colour ramp and the timeline
away, and the middle of the screen becomes the globe instead of the legend.
Tapping again brings both rows back. Nothing folds on its own — the panel still
opens at exactly the height it does today.

The defect

The fold shipped in #1023 and was extended to landscape phones in #1027. Both
the button and the collapsed state were declared inside @media (max-height: 720px), which is the right scope for the case they were written for — a short
window — but it means a phone held upright never reaches either one. A
portrait phone is 844–932px tall, so it clears the threshold and reads as a
roomy viewport.

The panel's height does not care about that. Measured on the live site today:

Viewport Panel height % of viewport Centre of view hit-tests
360x800 373px 46.6% DIV.legend__bar
390x844 373px 44.2% SPAN.legend__measures
412x915 373px 40.7% (legend)

So the ratio is worst on the smallest phones, and on all of them the aim point
lands inside the panel rather than on the globe — at 390x844 you have to go 80px
above centre to reach the canvas.

The control that answers this already existed and was simply out of reach:
.controls__collapse was display: none at these widths, and clicking it
programmatically changed nothing, because the .controls.is-collapsed rule was
inside the height query too. The one form factor with the worst ratio was the
only one that could not fold.

The change

src/style.css — the fold rules move out of the height-only query into
@media (max-height: 720px), (max-width: 540px), and the coarse-pointer sizing
gains the matching arm. 540px is the breakpoint the stylesheet already uses for
phones in eight other places; no new breakpoint is introduced.

Deliberately not in scope:

  • The spacing trim stays height-only. Give the middle of the view back to the globe on short windows #980's gap/padding reduction is
    still @media (max-height: 720px) alone, so the panel's expanded height is
    unchanged at every viewport. This is a reachability fix, not a height fix.
  • No default changes. The panel opens expanded in portrait, as it does now.
    Whether it should open folded on a tall narrow screen is a separate and
    larger question — it is the one docs/BACKLOG.md frames around short
    viewports and has never asked about narrow-tall ones. This PR does not answer
    it; it only makes the gesture available.
  • The button and the collapsed state stay in one query, so the invariant the
    stylesheet's own comment promises still holds: a viewport that grows out of
    the query restores every row and removes the control in the same breath. A
    reader cannot end up with rows hidden and nothing to bring them back.

Provenance is untouched: what the fold keeps is the layer selector and the
provenance line, so the product ID and the month stay rendered in both states.
The new spec reads the citation before folding and asserts it survived.

Verification

e2e/hud-collapse.spec.ts gains a test.describe("on a phone held upright")
block at 390x844 with hasTouch, asserting:

  1. the button is visible and aria-expanded="true" (default unchanged);
  2. folding hides the legend and timeline, keeps the layer selector and the
    provenance text verbatim, and document.elementFromPoint(centre) becomes
    globe; unfolding restores both rows;
  3. the control is ≥44px on a coarse pointer at this orientation too;
  4. folding in portrait then widening to 1280x900 hides the control and
    restores the rows.

Full local e2e suite run (CI=1, Chromium) — the HUD trap makes anything near
this panel layout-affecting, so the whole suite ran, not just the new spec.
a11y.spec.ts matters here in particular: it runs at 390x844 with hasTouch,
which is exactly the viewport where this button now renders for the first time,
and its 24px AA floor and 44px coarse-pointer checks both cover it.

Also updated the stale comment in hud-collapse.spec.ts that explained the 44px
guarantee could not live in a11y.spec.ts "because the button is not rendered
at 390x844" — that reason no longer holds after this change.

CSS-only on the runtime side; entry chunk unchanged at 52.4 kB gzip (budget 60),
no chunk reports FAIL.

🤖 Generated with Claude Code

The fold from #1023 declares both the button and the collapsed state inside
@media (max-height: 720px). A phone in portrait is 844-932px tall, so it
clears that threshold and reads as a roomy viewport -- but the panel is a
fixed 373px, which is 46.6% of a 360x800 screen and 44.2% of a 390x844 one,
and the centre of the view hit-tests the legend rather than the globe. The
form factor with the worst ratio was the only one that could not fold: the
button was display:none there and .controls.is-collapsed was inert.

Move the fold rules into @media (max-height: 720px), (max-width: 540px), and
give the coarse-pointer sizing the matching arm so the 44px target holds at
both orientations. 540px is the breakpoint the stylesheet already uses for
phones in eight other places.

The spacing trim stays height-only, so the expanded panel measures exactly
what it did before and nothing folds on its own -- this makes the gesture
reachable, it does not change a default. Keeping the button and the collapsed
state in one query preserves the invariant the stylesheet promises: a viewport
that grows out of the query restores every row and removes the control
together, so no reader is left with rows hidden and nothing to undo them.

What the fold keeps is unchanged, so the product ID and the month stay
rendered in both states; the new spec reads the citation before folding and
asserts it survived verbatim.

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

Copy link
Copy Markdown
Owner Author

Local verification (Chromium, CI=1), run from the worktree before and after
the spec was added:

  • Full suite: 174 passed, 4 skipped on chromium, 0 failures. The 108
    reported failures are all [webkit] and [firefox] — the advisory lanes,
    failing locally only because this run installed Chromium alone.
  • Targeted re-run covering every spec that touches this panel or a phone
    viewport — hud-collapse, a11y, hud-aim-clearance, landscape-overlays,
    layer-caption-phone, toolbar-height-continuity: 57 passed, including
    the three new on a phone held upright tests.

a11y.spec.ts is the one worth calling out: it runs at 390x844 with hasTouch,
which is exactly the viewport where this button renders for the first time, and
both its 24px AA floor and its 44px coarse-pointer check pass with it present.

Gates: tsc --noEmit clean, npm test 4563/4563 passed across 297 files,
prettier --check clean, npm run build reports ok on every chunk with the
entry at 52.4 kB gzip against a 60 kB budget.

The soak and visual jobs are gated if: github.event_name != 'pull_request'
(.github/workflows/ci.yml), so they do not run on this PR — but this change is
CSS scoped to viewports narrower than 540px, which neither job exercises.

@zkWizard
zkWizard merged commit 9388854 into main Aug 18, 2026
13 checks passed
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