Editor on touch: long-press lift to move blocks (needs owner decision, DESIGN §19 OPEN) #91

Open
opened 2026-09-25 14:02:05 +00:00 by kayg · 0 comments
Owner

Status: needs an owner decision before a job builds it

DESIGN §19 marks "the block-editor area as a whole (chrome, gestures, which controls live in the package)" as OPEN. DESIGN §35 says to fix the editor's known issues and adapt it, but it does not decide the touch gestures. Do not build this until the owner confirms the decisions below for calternal (they were made for calternal.js). This issue keeps the context so nothing is lost.

Problem

On a phone or a tablet there is no way to move a block in a calternal note. The desktop drag handle (@tiptap/extension-drag-handle, wired in packages/editor/src/extensions.ts) appears on hover only, and the keyboard move (block.moveUp / block.moveDown, Alt+Arrow or Cmd+Shift+Arrow, apps/web/src/lib/shortcuts/registry.ts) needs a hardware keyboard. apps/web/src/lib/notes/NoteEditorSurface.svelte states that the calternal.js block-selection controller, touch grammar and format toolbars were not ported.

Reproduce: open any note at 390x844 with touch emulation (or on an iPhone). Long-press a paragraph: the browser starts a text selection. There is no lift, no drag and no drop target.

What exists in calternal already

  • packages/editor/src/moveBlock.ts: the pure move primitives (moveTopLevelBlock, topLevelIndexAt, sibling moves, moveScope: 'note'). Touch must commit through these, not a second path.
  • packages/editor/src/blockSelectDecoration.ts: the "lift" decoration (transform offset, no layout properties) for the block-selection mode.
  • packages/editor/src/blockHitTest.ts: DOM-box block hit test (use it so card padding and whitespace never lift the nearest block).
  • apps/web/src/lib/selection.ts: the ported four-layer native-selection guard, including the surface-scoped lease for a pending block-lift gesture (the calternal.js#162 fix). Extend it; do not add a second selection-suppression system.
  • apps/web/src/lib/gestures.ts: shared long-press and swipe helpers.
  • Size and hit tokens (--hit, touch-hit), reduced motion ($lib/reducedMotion.svelte.ts).

Decisions made in calternal.js (to confirm for calternal)

  • Long-press to lift and drag is the default block move on touch. It is one gesture: the long-press lifts the block (highlighted row) and moving the finger drags it. A lifted copy follows the finger (transform only); a drop line with an accent segment marks the target; a placeholder keeps the old slot.
  • Two drop modes only: move between blocks (default) and nest the source under the destination. The "third drop mode" was dropped.
  • After a touch drop the moved block stays selected, in reading mode, with no keyboard.
  • Lessons from the calternal.js audit (F1-F16): allow nested list items and multi-block selections; recompute rects on scroll, resize, font load and before commit (cached rects put the drop 350 px from the finger); rAF auto-scroll with speed by edge depth, which also works for a still finger; drop bands at least 12 px; no lift on whitespace; the grip is not in the tab order (the keyboard path stays block.moveUp/Down); on real iOS the long-press must not start text selection or open the keyboard (calternal.js#162 root cause: the editor had selection protection only while unfocused).

Acceptance criteria (after the decision)

  • Chromium touch emulation at 390x844: long-press lifts with no residual selection, drag moves, drop reorders, the new order persists to the .md file and a second client sees it.
  • Nested list items and a multi-block selection move by touch.
  • Drop lands under the finger after auto-scroll (assert the target, not only the indicator).
  • Reduced motion: no lift spring, a cross-fade only.
  • Screen reader: every move (touch and keyboard) is announced through one polite live region fed by the editor's MoveInfo (moveBlock.ts). The host (NoteEditorSurface.svelte) must own that live region; add it if it is missing. The keyboard move still works.
  • Screenshots from a production build, light and dark, of lift and drop states. Real iPhone Safari check by the owner.
  • Gates: bun run check, bun run test in apps/web and packages/editor, bun run test:e2e:layout, quoted verbatim.

Carried over from kayg/calternal.js#125
Carried over from kayg/calternal.js#162

## Status: needs an owner decision before a job builds it DESIGN §19 marks "the block-editor area as a whole (chrome, gestures, which controls live in the package)" as **OPEN**. DESIGN §35 says to fix the editor's known issues and adapt it, but it does not decide the touch gestures. Do not build this until the owner confirms the decisions below for calternal (they were made for calternal.js). This issue keeps the context so nothing is lost. ## Problem On a phone or a tablet there is no way to move a block in a calternal note. The desktop drag handle (`@tiptap/extension-drag-handle`, wired in `packages/editor/src/extensions.ts`) appears on hover only, and the keyboard move (`block.moveUp` / `block.moveDown`, Alt+Arrow or Cmd+Shift+Arrow, `apps/web/src/lib/shortcuts/registry.ts`) needs a hardware keyboard. `apps/web/src/lib/notes/NoteEditorSurface.svelte` states that the calternal.js block-selection controller, touch grammar and format toolbars were not ported. Reproduce: open any note at 390x844 with touch emulation (or on an iPhone). Long-press a paragraph: the browser starts a text selection. There is no lift, no drag and no drop target. ## What exists in calternal already - `packages/editor/src/moveBlock.ts`: the pure move primitives (`moveTopLevelBlock`, `topLevelIndexAt`, sibling moves, `moveScope: 'note'`). Touch must commit through these, not a second path. - `packages/editor/src/blockSelectDecoration.ts`: the "lift" decoration (transform offset, no layout properties) for the block-selection mode. - `packages/editor/src/blockHitTest.ts`: DOM-box block hit test (use it so card padding and whitespace never lift the nearest block). - `apps/web/src/lib/selection.ts`: the ported four-layer native-selection guard, including the surface-scoped lease for a pending block-lift gesture (the calternal.js#162 fix). Extend it; do not add a second selection-suppression system. - `apps/web/src/lib/gestures.ts`: shared long-press and swipe helpers. - Size and hit tokens (`--hit`, `touch-hit`), reduced motion (`$lib/reducedMotion.svelte.ts`). ## Decisions made in calternal.js (to confirm for calternal) - Long-press to lift and drag is the default block move on touch. It is **one** gesture: the long-press lifts the block (highlighted row) and moving the finger drags it. A lifted copy follows the finger (transform only); a drop line with an accent segment marks the target; a placeholder keeps the old slot. - Two drop modes only: move between blocks (default) and nest the source under the destination. The "third drop mode" was dropped. - After a touch drop the moved block stays selected, in reading mode, with no keyboard. - Lessons from the calternal.js audit (F1-F16): allow nested list items and multi-block selections; recompute rects on scroll, resize, font load and before commit (cached rects put the drop 350 px from the finger); rAF auto-scroll with speed by edge depth, which also works for a still finger; drop bands at least 12 px; no lift on whitespace; the grip is not in the tab order (the keyboard path stays `block.moveUp/Down`); on real iOS the long-press must not start text selection or open the keyboard (calternal.js#162 root cause: the editor had selection protection only while unfocused). ## Acceptance criteria (after the decision) - Chromium touch emulation at 390x844: long-press lifts with no residual selection, drag moves, drop reorders, the new order persists to the `.md` file and a second client sees it. - Nested list items and a multi-block selection move by touch. - Drop lands under the finger after auto-scroll (assert the target, not only the indicator). - Reduced motion: no lift spring, a cross-fade only. - Screen reader: every move (touch and keyboard) is announced through one polite live region fed by the editor's `MoveInfo` (`moveBlock.ts`). The host (`NoteEditorSurface.svelte`) must own that live region; add it if it is missing. The keyboard move still works. - Screenshots from a production build, light and dark, of lift and drop states. Real iPhone Safari check by the owner. - Gates: `bun run check`, `bun run test` in `apps/web` and `packages/editor`, `bun run test:e2e:layout`, quoted verbatim. Carried over from kayg/calternal.js#125 Carried over from kayg/calternal.js#162
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/calternal#91
No description provided.