Skip to content

feat(ui): touch and small-screen support (U-13) - #215

Closed
timurbazhirov wants to merge 2 commits into
claude/uiux-p2-figure-exportfrom
claude/uiux-p2-touch-input
Closed

feat(ui): touch and small-screen support (U-13)#215
timurbazhirov wants to merge 2 commits into
claude/uiux-p2-figure-exportfrom
claude/uiux-p2-touch-input

Conversation

@timurbazhirov

Copy link
Copy Markdown
Member

Stacked on claude/uiux-p2-figure-export — review that one first; this PR's diff is U-13 only.

Touch and small-screen support, the last item of the UI/UX proposal (docs/design/uiux-improvements-2026-08.md, U-13). Kept separate because it is the only item in the set that changes how input the viewer already receives is handled, rather than adding a surface — so it is worth being reviewable, and revertable, on its own.

What was actually wrong

Measured on a 390×844 touch profile before changing anything:

  • The canvas had touch-action: auto, so the browser claimed every touch drag for page scroll and pinch-zoom and sent pointercancel to whatever had begun tracking it. No touch drag ever reached any handler — not orbiting, not moving an atom, not marquee select — even though every pointer handler in the editor is already pointer-event based. A one-finger swipe left the rendered frame byte-identical.
  • The quick-toggle row was 32 px, and it is the primary control surface on a phone.
  • The only surface documenting how to drive the viewer opened on ? alone — unreachable without a keyboard.
  • isMobile was computed from the viewport width and forwarded to exactly one dropdown. That is also the wrong question twice over: a narrow window on a desktop is not touch input, and a touch laptop is touch input at any width.

Changes

  • touch-action: none on the renderer canvas. One declaration, and every existing pointer handler starts working with a finger.
  • Edit mode reserves the first finger, mirroring what it already does with the left mouse button (D-4): touches.ONE comes off the camera and orbit moves to two fingers (DOLLY_ROTATE). There is no right button to move it to. Restored on exit, verified across repeated round trips.
  • utils/inputCapabilities.ts replaces the viewport guess with capability queries read at call time — a window can move to another display and a tablet can gain a keyboard mid-session. hasCoarsePointer gates sizing; hasTouchSupport gates documentation, so a touch laptop gets the gestures listed and keeps its compact chrome.
  • Quick toggles reach 44 px under (pointer: coarse) only.
  • The shortcut sheet is reachable from View ▸ Shortcuts & gestures, gains a visible Close, lists the touch gestures, and puts them first when the pointer is coarse — the sheet reflows to one column there, so group order is scroll distance, and the gestures were sitting below three groups of keyboard shortcuts.
  • Nothing names an input the device lacks. The edit pill reads drag atom = move · 2 fingers = orbit on touch instead of RMB/Del/Esc; the sheet is titled "Shortcuts & gestures" rather than "Keyboard shortcuts" and drops its "? or Esc to close" hint on a coarse pointer.

Verification

  • 32 new tests. Whole suite 457 passing, 32 suites, 0 failing; npm run lint and tsc --noEmit clean.
  • Re-measured on the same 390×844 touch profile: main canvas touch-action: none, every button ≥ 44×44, one-finger drag rotates the camera, the sheet opens from the View menu with the touch group first and lists pinch, pill wording correct, no console errors.
  • Desktop rendering and wording unchanged (checked at 1280 and 1600).

Two of the defects here came only from looking at the rendered result rather than the suite: the sheet still promised ? and Escape on a device with neither and had no visible way out, and the touch group rendered last.

🤖 Generated with Claude Code

https://claude.ai/code/session_0185JNEgEJLfZZEx7jKHgwu5


Generated by Claude Code

claude added 2 commits August 13, 2026 04:00
The proposal's position was that half-support is the worst of the three options,
and the viewer was squarely in it. Verified on a 390x844 touch profile before
changing anything:

- The canvas had `touch-action: auto`, so the browser claimed every touch drag
  for page scroll and pinch-zoom and sent `pointercancel` to whatever had begun
  tracking it. Nothing draggable worked with a finger - not orbiting, not moving
  an atom, not marquee select - even though every handler in the editor is
  already pointer-event based. A one-finger swipe left the rendered frame
  byte-identical.
- The quick-toggle row, the primary control surface on a phone, was 32 px.
- The only surface documenting how to drive the viewer opened on `?` alone.
- `isMobile` was computed from the viewport width and forwarded to exactly one
  dropdown. That is also the wrong question twice over: a narrow window on a
  desktop is not touch input, and a touch laptop is touch input at any width.

Changes:

- `touch-action: none` on the renderer canvas. One declaration, and every
  existing pointer handler starts working with a finger.
- Edit mode reserves the first finger, mirroring what it already does with the
  left mouse button (D-4): while editing, `touches.ONE` is taken off the camera
  and orbit moves to two fingers (DOLLY_ROTATE). There is no right button to
  move it to. Restored on exit, verified across repeated round trips.
- `utils/inputCapabilities.ts` replaces the viewport guess with capability
  queries read at call time - a window can move to another display and a tablet
  can gain a keyboard mid-session. `hasCoarsePointer` gates sizing;
  `hasTouchSupport` gates documentation, because a touch laptop needs the
  gestures listed and should keep its compact chrome.
- Quick toggles grow to 44 px under `(pointer: coarse)` only.
- The shortcut sheet is reachable from View > Shortcuts & gestures, gains a
  visible Close (the previous exits were `?`, Escape, or knowing that tapping
  the backdrop works - none available or discoverable on a phone), lists the
  touch gestures, and puts them first when the pointer is coarse: the sheet
  reflows to one column there, so group order is scroll distance, and the
  gestures were below three groups of keyboard shortcuts.
- Nothing names an input the device lacks. The edit pill reads "drag atom =
  move - 2 fingers = orbit" on touch instead of RMB/Del/Esc; the sheet is titled
  "Shortcuts & gestures" rather than "Keyboard shortcuts" and drops the
  "? or Esc to close" hint on a coarse pointer.

Two of these came only from looking at the rendered result: the sheet still
promised `?` and Escape on a device with neither and had no visible way out, and
the touch group was last.

Re-verified on the same touch profile: main canvas touch-action none, every
button at least 44x44, one-finger drag rotates, the sheet opens from the menu
with touch first and lists pinch, pill wording correct, no console errors.
Desktop rendering and wording unchanged.

32 new tests (440 passing, was 410).
Records touch support against the plan: that it sizes by pointer capability
rather than viewport width, that no bottom sheet was needed because the existing
layout was measured at 390 px and fits, and the touch-action bug it surfaced -
the canvas let the browser claim every touch drag, so none ever reached the
editor's pointer handlers.

Also records why this is a stacked PR: every other item adds a surface, so a
mistake is contained to what it added. This one changes how input the viewer
already receives is handled, which is worth being revertable on its own.
@timurbazhirov timurbazhirov changed the title feat(ui): figure export, GIF sizing and review fixes (U-12) feat(ui): touch and small-screen support (U-13) Aug 13, 2026

Copy link
Copy Markdown
Member Author

Closing — superseded, not dropped.

U-13 ships in #214, which carries the same commits plus the rest of the UI/UX work. The plan is #216 first (everything except U-13), then #214 on top of it, so this stacked branch has nothing left to add.

Nothing here is lost: claude/uiux-p2-touch-input stays on the remote at 7588dab if the split is ever wanted again, and every line of it is in #214.


Generated by Claude Code

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.

2 participants