Skip to content

slides: the toolbar scrolls instead of being cut off on a 320px phone - #274

Open
iamgeo92 wants to merge 1 commit into
nyblnet:mainfrom
iamgeo92:topbar-scroll-narrow
Open

slides: the toolbar scrolls instead of being cut off on a 320px phone#274
iamgeo92 wants to merge 1 commit into
nyblnet:mainfrom
iamgeo92:topbar-scroll-narrow

Conversation

@iamgeo92

@iamgeo92 iamgeo92 commented Aug 10, 2026

Copy link
Copy Markdown

The problem

Below 700px applyPhoneChrome folds most of the bar into the + and ⋯ menus.
That works down to about 360px. It does not work at 320px — an iPhone SE, or
any iPhone with Display Zoom turned on, which turns a 375/390pt phone into a
320pt one.

Even fully folded the bar needs ~356px: six 44px touch targets plus the mark and
the title. And the editor column is explicitly clipped:

.ed-root {
  /* Nothing in this column may leak sideways. Without this, a topbar wider than
     the viewport turned into DOCUMENT scroll — on a phone the bar needed 681px
     of a 402px screen, so swiping the toolbar dragged the whole canvas off. */
  overflow: hidden;
}

That rule is right — but with nothing else in place, the surplus is simply cut
off
. Measured at 320×568:

bar scrollWidth 356 · clientWidth 320 · overflow 36px
cut off: "More actions" (⋯)  — 36px past the edge

⋯ is not a nice-to-have on a phone. It is where applyPhoneChrome puts Redo,
Comment, Export PDF, Share, Language, Help and the entire save-as list
— 23
rows. On a 320px screen none of them had any route at all.

The fix

The bar scrolls sideways instead of being clipped.

  • The clipped button becomes a partly visible button, which is the standard
    affordance for "there is more this way".
  • overscroll-behavior-x: contain keeps a sideways flick on the bar from
    rubber-banding the page behind it. .ed-root's overflow: hidden still does
    its job: the bar scrolls itself, never the document.
  • It keeps working if the bar ever gains another control — unlike shrinking
    targets below the 44px the codebase deliberately chose ("a 36px icon is a
    mis-tap on glass
    ").

The part worth reviewing: a scroll container clips in both axes, so the
menus hanging off + and ⋯ would now be cut off by the bar itself. They are
positioned against the viewport (position: fixed) instead. That costs
nothing extra to anchor sideways, because the existing phone rule already pins
these menus to the end of the screen rather than to their trigger:

.ed-topbar .ed-menu { inset-inline-start: auto; inset-inline-end: 0; … }

The one thing CSS cannot work out is where the bar ends — its height moves
with the safe-area insets, which differ per device and change on rotation — so
editor.ts publishes it as --ed-bar-bottom from a ResizeObserver.

All of this is inside the existing @media (max-width: 700px) block. Above
700px the bar is not a scroller and the menus are still absolutely positioned
under their trigger.

Before / after — 320×568

Same gesture both sides: swipe the toolbar, then tap where ⋯ is.

toolbar scroll, before and after

before — ⋯ is 36px off the edge, swiping does nothing after — swipe reaches it; 23 actions open
before: cutOff=36px  scrolled=false  menuRows=0
after:  cutOff=0px   scrolled=true   menuRows=23

How this was verified

Chrome DevTools iPhone emulation with real touch dispatch — including a genuine
14-step swipe on the bar, not a synthetic scroll assignment:

case 320×568 390×664
bar is a horizontal scroller pass pass
--ed-bar-bottom is published as a px value pass pass
⋯ can be scrolled fully inside the bar pass pass
…and is the topmost element at its own centre (nothing overlaps it) pass pass
⋯ menu opens and is not clipped away pass pass
⋯ menu is viewport-positioned (position: fixed) pass pass
⋯ menu sits inside the screen (no sideways overflow) pass pass
⋯ menu has its rows (23) pass pass

Desktop regression at 1280×800: bar overflow-x is still visible and the bar
does not overflow — pass.

Gates:

node_modules/.bin/tsc -b                                        clean
npm run build:single                                            ok (664KB shell)
node scripts/shell-gate.mjs …/Bento_Slides.bento.html           splice contract OK

No new user-facing strings, so no i18n catalog changes. No format or kernel
change; nothing under sync/.

Caveat: verified under Chromium's iPhone emulation, not on physical iOS
hardware. The one place real Safari could differ is momentum scrolling on the
bar (-webkit-overflow-scrolling: touch is set) — worth a glance on a device,
though the reachability fix does not depend on it.


Changelog entry — not in the diff, on purpose

CHANGELOG.md is this repo's named conflict magnet (docs/PARALLEL-WORK.md §3). With several of these open at once, and [Unreleased] being emptied every time a release is cut, a changelog hunk made every one of them conflict on that file and nothing else — twice over. They carry no CHANGELOG.md change so they stay mergeable; here is the entry to drop in at release time, or I'll add it back in whatever form you prefer.

- **Fix: on a 320px phone the end of the toolbar was cut off and unreachable.**
  Below 700px the bar folds most of itself into the + and ⋯ menus, but even
  fully folded it needs about 356px — six 44px touch targets, the mark and the
  title. An iPhone SE, or any iPhone with Display Zoom turned on, is 320px wide.
  The editor column sets `overflow: hidden` so an over-wide bar can never become
  document scroll, and the consequence was that the surplus was simply clipped:
  ⋯ hung 36px past the edge, and with it Redo, Comment, Export PDF, Share,
  Language, Help and the entire save-as list — none of which have another route
  on a phone. The bar now scrolls sideways instead of being cut off, so the
  partly-visible button is its own invitation to swipe, and the two menus that
  hang off it are anchored to the screen so the new scroll container cannot clip
  them either. Nothing changes above 700px.

@iamgeo92
iamgeo92 force-pushed the topbar-scroll-narrow branch 2 times, most recently from 4eb0d5a to ae80c21 Compare August 10, 2026 02:28
Folding is not enough at the narrow end. Even fully folded the bar needs
~356px — six 44px targets plus the mark and the title — and an iPhone SE,
or any iPhone with Display Zoom on, is 320px wide.

.ed-root sets overflow:hidden so an over-wide bar can never turn into
document scroll, which is right; the consequence was that the surplus
was simply clipped. The ... button hung 36px past the edge, taking Redo,
Comment, Export PDF, Share, Language, Help and the whole save-as list
with it — none of which have another route on a phone.

The bar now scrolls itself, so the clipped button becomes a
partly-visible one, which is its own affordance, and the fix keeps
working if the bar ever gains another control.

A scroll container clips in both axes, so the menus hanging off + and
... would have been cut off by the bar itself. They are positioned
against the viewport instead, which costs nothing extra to anchor
because the phone rules already pin them to the end of the screen rather
than to their trigger. The one thing CSS cannot know is where the bar
ends — its height moves with the safe-area insets, per device and on
rotation — so editor.ts publishes it as --ed-bar-bottom.
@iamgeo92

Copy link
Copy Markdown
Author

@nyblnet — could you cast an eye over this one?

At 320px (an iPhone SE, or any iPhone with Display Zoom on) the bar needs ~356px even fully folded, and .ed-root's overflow: hidden — correctly — turns the surplus into clipping. ⋯ hung 36px past the edge, and with it Redo, Comment, Export PDF, Share, Language, Help and the whole save-as list: 23 rows with no other route on a phone.

The part worth your judgement is the second half: a scroll container clips in both axes, so the two menus hanging off the bar would have been cut off by it. They're anchored to the viewport instead, which costs nothing sideways because your existing phone rule already pins them to the screen's end — but it does mean editor.ts publishes the bar's height as --ed-bar-bottom, since it moves with the safe-area insets.

Two files, +42.

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