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>