Mobile Phase B: touch editing (selection toolbar, tap-to-add) + duplicate fix #10

Merged
jknapp merged 2 commits from mobile-phase-b into main 2026-07-13 15:43:29 +00:00
Owner

Completes the mobile MVP: the editor is now usable on a phone (Fable verified full touch build/edit loop). All mobile gated ≤768px; desktop unchanged; no toHtml change.

  • Shared useNodeActions hook (extracted from ContextMenu); on-canvas bottom-fixed selection toolbar (Move Up/Down · Duplicate · Delete[2-tap] · Select Parent · Edit Styles→Styles sheet), hidden while a sheet is open.
  • Tap-to-add from the Blocks sheet (insert after selection, close sheet, select+scroll to new node).
  • 44px layer rows, HeadCodeModal portaled to body, BottomSheet swipe-to-dismiss, visualViewport on-screen-keyboard handling.
  • Fixes two live pre-existing bugs surfaced by real-touch testing: a structuredClone DataCloneError that silently broke Duplicate/Paste for every component (Craft data.type is a component function), and a collector-staleness lag. Duplicate now inserts after the source + selects it (was appending off-screen).
  • 609 tests (+ real-touch Playwright verification).

🤖 Generated with Claude Code

Completes the mobile MVP: the editor is now usable on a phone (Fable verified full touch build/edit loop). All mobile gated ≤768px; desktop unchanged; no toHtml change. - Shared `useNodeActions` hook (extracted from ContextMenu); on-canvas bottom-fixed selection toolbar (Move Up/Down · Duplicate · Delete[2-tap] · Select Parent · Edit Styles→Styles sheet), hidden while a sheet is open. - Tap-to-add from the Blocks sheet (insert after selection, close sheet, select+scroll to new node). - 44px layer rows, HeadCodeModal portaled to body, BottomSheet swipe-to-dismiss, visualViewport on-screen-keyboard handling. - **Fixes two live pre-existing bugs** surfaced by real-touch testing: a `structuredClone` DataCloneError that silently broke Duplicate/Paste for every component (Craft `data.type` is a component function), and a collector-staleness lag. Duplicate now inserts after the source + selects it (was appending off-screen). - 609 tests (+ real-touch Playwright verification). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jknapp added 2 commits 2026-07-13 15:43:28 +00:00
Phase B makes the Craft.js editor genuinely usable by touch on top of Phase
A's responsive shell, gated entirely behind useIsMobile()/<=768px:

- Extract useNodeActions(nodeId) out of ContextMenu.tsx (move/duplicate/
  delete/select-parent), shared by the desktop right-click menu (behavior
  unchanged) and the new mobile MobileSelectionToolbar.
- MobileSelectionToolbar: bottom-fixed selection toolbar (Move Up/Down,
  Duplicate, Select Parent, Edit Styles, two-tap Delete confirm), hidden
  while a sheet is open.
- BlocksPanel: tap-to-add on mobile (insert after selection, close sheet,
  select + scroll the new node into view); desktop drag/double-click
  unchanged.
- LayersPanel rows >=44px on mobile; HeadCodeModal portaled to document.body
  (same fix TemplateModal already had); BottomSheet gets swipe-to-dismiss
  and on-screen-keyboard clearance via a new useVisualViewportInsets hook.

Also fixes two pre-existing bugs surfaced only by driving a real Craft.js
document with Playwright touch input (masked by tests that mock
@craftjs/core): regenerateTreeIds structuredClone'd a live node's whole
data object, including the component function reference in data.type,
throwing DataCloneError and silently breaking Duplicate/Paste for every
node type; and an earlier useNodeActions draft cached canMoveUp/canMoveDown
inside a useEditor collector closed over nodeId, which goes stale for one
render whenever the selection changes without an unrelated store event.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ship-blocking fix (Fable consult): useNodeActions.duplicate() appended the
regenerated tree at the end of the parent while leaving the ORIGINAL
selected, so duplicating a top-level section landed the copy off-screen at
the bottom of the page with no visible change -- shared by both the mobile
selection toolbar and the desktop right-click ContextMenu. Now inserts the
copy immediately after the source (actions.addNodeTree(tree, parentId,
sourceIndex + 1)) and selects it (actions.selectNode(tree.rootNodeId)),
falling back to append-at-end if the source's index can't be resolved.
Mobile also scrolls the new node into view.

Three cheap fast-follows:
- canSelectParent on useNodeActions (false when the node's parent is ROOT
  or missing); MobileSelectionToolbar disables "Select Parent" instead of
  dead-ending on a page-wide ROOT outline with no toolbar of its own.
- Opening the Styles sheet on mobile now scrolls the selected node above
  the 65dvh sheet; a temporary generous bottom-padding class handles the
  case where the node is the last thing on the page and there'd otherwise
  be no room left to scroll it into view.
- Canvas gets bottom padding equal to the fixed selection toolbar's height
  while it's visible, so the last section of a short page isn't stuck
  permanently underneath it.

Desktop duplicate behavior improves (inserts after + selects) via the
shared hook; no toHtml changes. Verified live via Playwright at 375px and
1280px (screenshots in craft/scratchpad/mobileB2/).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jknapp merged commit b9552faa30 into main 2026-07-13 15:43:29 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: cloud-hosting-platform/site-builder#10