spaces: the drags, the pinch and the menus a phone could not reach - #431
Open
nyblnet wants to merge 1 commit into
Open
spaces: the drags, the pinch and the menus a phone could not reach#431nyblnet wants to merge 1 commit into
nyblnet wants to merge 1 commit into
Conversation
Four of this app's gestures were mouse-only, and three of them were absent on a touch screen rather than merely awkward: reordering a block, nesting a page and moving an issue card are HTML5 drag-and-drop, which never fires from a finger, and a canvas card's grip listened for mousedown. Nesting a page had no fallback at all. New spaces/src/touch.ts. Press-and-hold synthesises the dnd events the existing handlers already listen for — a real DataTransfer, dragstart on the source, dragover/dragleave/drop on whatever is under the finger — so every drop rule and every highlight in editor.ts is reached unchanged, and a fourth draggable thing gets touch support with no edit there. The canvas grip is replayed as the mouse stream it already handles. A finger that moves before the hold is scrolling and nothing is preventDefault-ed, so scrolling is untouched. Holding at the edge of a scroller drags the view along, because at 390px one of the board's six columns is on screen. Also measured on the built shell at 390x800 and fixed: - The Insert menu laid out 19 items 1000px tall with five below the viewport and no way to scroll; the More menu lost its last five the same way. Both cap and scroll now. Not a phone-only bug: the last Insert item was off an 860px laptop window too. - The graph could be panned by one finger and never zoomed - wheel was the only zoom. Two fingers zoom about the point between them; the second finger also ends the one-finger drag it interrupts. - A board or wide table that ran out of content handed the rest of a sideways swipe to the browser's back gesture. Contained. - "Wide" is 80% of the window, which left a 257px column inside a 390px phone. It takes the whole width below 850px and is byte-identical above it - measured at 1400px, 872px before and after. - The sharing button was 35x29 where every other bar control is 40x40 on a coarse pointer. Desktop is untouched by construction: everything added is a touch listener, a coarse-pointer rule, or a class that only exists during a touch drag. No new strings, so no catalog work.
Build size
Updated: |
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.
What was actually broken
Measured on the built shell (
dist-single/Bento_Spaces.bento.html) served overhttp://and driven at 390×800 and 320×760, with(pointer: coarse)forced.The findings, in the order they matter:
Absent, not degraded — four mouse-only gestures
grip.draggable = true(dnd)a.draggable = true(dnd)card.draggable = true(dnd)grip.addEventListener('mousedown')Menus with items nobody can reach. At 390×800 the Insert menu laid out 19
items 1000px tall, bottom at y=1053 — Table, Link to the web, Image and Video or
audio 253px below the screen. It is
position: absoluteinside a fixed bar, sothe page cannot be scrolled to them and
overflowwasvisible. ⋯ was 928pxtall, bottom at 981, losing its last five. Also true on a desktop: at
1400×860 the last Insert item sat at bottom 891 and
elementFromPointat itscentre did not return it.
Overscroll chaining.
.sp-boardholds 1452px of columns in a 257px box at390px with
overscroll-behavior-x: auto— every swipe that reaches the endhands the rest of the gesture to the browser's back-navigation. Same for
.sp-tb-wrapand.sp-view-tablewrap.A third of a phone given to margin.
page.width: 'wide'setsmax-width: min(1500px, 80%)inline. Measured at 390px:.sp-page-inner283pxstarting at x=53, and with the gutter's 26px reserve a 257px text column
inside a 390px screen. "Use this width for every page" is a one-tap per-screen
preference, so this is easy to land in.
One control below the touch minimum. With the coarse rules applied at 320px,
five bar controls measured 40×40 and the sharing button 35×29 — it is a
.sp-live, and the rule names.sp-btn.The graph could be moved and never scaled. Its canvas already has
touch-action: noneand its pan is on pointer events, so one finger worked;zoom was on
wheelalone.What was already fine, and is not touched
Worth recording, because the brief's "two touch listeners vs fourteen" count
understates the phone work already in this app — most of it is CSS and pointer
events, which that grep does not see.
320px with coarse targets applied it fits:
scrollWidth == clientWidth,Save's right edge at 311.
under the drawer's 50 and click-to-dismiss wired.
.sp-sheetbottom sheet withmax-height: 62vhandits own scroll — measured 374×354 at (8, 438), fully inside the viewport.
reasoning about the OS selection callout written down in
formatbar.ts.contentEditable; there is nodblclickgate to defeat.The fix
New
spaces/src/touch.ts. The press-and-hold drag synthesises the dnd eventsthe existing handlers already listen for — a real
DataTransfer(the source'sown
dragstartfills it, sotypes/getDataanswer truthfully),dragstarton the source,
dragover/dragleaveonelementFromPoint,drop,dragend.Every drop rule, payload check and
.sp-drop/.sp-droplinehighlight ineditor.tsis reached unchanged; a fourth draggable thing added later needs noedit here. The canvas grip is replayed as the mouse stream
startDragalreadyhandles rather than forked.
A finger that moves before the hold is scrolling: nothing is
preventDefault-eduntil the hold has fired. Holding at the edge of a scroller drags the view along
— without it the board drag is a rig trick, since one of six columns is on
screen at 390px. That creep is a
setInterval, notrequestAnimationFrame,because rAF is throttled to zero in an occluded tab (CLAUDE.md's testing note)
and a timer degrades to slow rather than to nothing.
Verification
Browser, on the built shell, marker-checked before every measurement
(
.sp-ddmenucomputedmax-height: 736px/overflow-y: autoand thebody.sp-touchdragrule exist only in the new build):sd-pindex 1 → 5, landing after the drop targetsd-writing.parentundefined→sd-linkstodo→backlogon drop{x:4,y:6}→{x:6.1,y:29.3}, rendered offset followsscrollLeft0 → 36 with the finger parked (3 ticks; the tab's timer is clamped to ~1s)dragstart, neithertouchmovedefaultPreventedsp-bar, fitssp-bar, fitsNo console errors. No new user-visible strings, so no catalog changes and no
build-spaces-i18n.mjsrun — verified by grepping the diff for addedt('…').Neither
spaces/README.mdnordocs/spaces-agents.mddescribes any behaviourthis changes. The format is untouched.
What I could not verify synthetically, stated plainly
TouchEventsdispatched at my own listeners in
touch.ts. That is legitimate here — nothird-party library sits between them — and the proof is always the model or
the rendered geometry changing, never a style read back. But iOS's and
Android's own long-press, selection-handle and callout behaviour around a
press-and-hold has not been on a device.
(pointer: coarse)CSS. The rig forces the JSmatchMediaanswer; itcannot make Chrome evaluate coarse media queries. Coarse-only rules were
measured by injecting their declarations and re-measuring layout, which tests
the geometry and not the query.
synthetically, so "desktop dnd still works" rests on the diff rather than a
measurement: no
draggableattribute, mouse listener or handler was changed,and everything added is a
touch*listener, a(pointer: coarse)rule, or aclass applied only while a touch drag is live.
navigator.vibrateon the hold is fire-and-forget and absent on desktopChrome.
Deliberately left
insertion point from
clientYand that path works, but I only measured thecross-column move.
a swipe is a nicety with its own gesture-conflict argument.
editor.tsmousedown) — panels are drawers on a phoneand cannot be resized there by design.
touch.ts..github/workflows/ci.ymlis the standing conflictmagnet and the queue is contended; per START-HERE this belongs in a small
follow-up, and I have filed it.