- Force font-size:16px !important on Styles-sheet/topbar/Sitesmith inputs
inside the mobile media query so inline 12px/14px styles stop triggering
iOS zoom-on-focus.
- Lift sheet-open + Templates/Head Code modal-open state out of private
useState into a shared MobileChromeContext (EditorShell), so Phase B can
open/close sheets from outside MobilePanelBar.
- Add an explicit z-index layer scale, portal TemplateModal to
document.body (was trapped under the tab bar inside .topbar's stacking
context), align Sitesmith to the same --z-modal layer, and make opening
a sheet close any open modal. Also fix modal backdrops swallowing tab
bar taps (mirrors the sheet backdrop's existing tab-bar cutout).
- Drop BottomSheet's incorrect aria-modal; mobile-aware AssetsPanel empty
state copy.
- Tests: useIsMobile (matchMedia mock incl. legacy fallback + cleanup),
MobileChromeContext invariants (one sheet open, sheet closes modals),
MobilePanelBar wiring.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Makes the Craft.js editor usable on phones (≤768px) without touching desktop
layout/behavior: a useIsMobile() hook gates a bottom tab bar + sheets (hosting
the existing Blocks/Pages/Layers/Assets/Styles panels unchanged) in place of
the side panels, a collapsed TopBar with a "..." overflow menu, 44px touch
targets, 16px inputs, dvh/safe-area-aware sizing, and small copy/overflow
fixes (empty-canvas hint, Templates modal tabs + close button).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
TemplateModal's addTemplateComponents() built each template component via
React.createElement(Component, comp.props) without ever passing
comp.children, silently dropping every nested children array authored in
templates/definitions.ts (header/footer Container > Logo/Menu/TextBlock,
page Section > Heading/TextBlock/ButtonLink). The resulting Craft.js node had
nodes: [], so the header/footer zone preview (ZonePreview -> exportBodyHtml)
rendered as an empty strip, and published output was affected the same way.
Fix converts each TemplateComponent to a SerializedTreeNode and reuses
craft-tree.ts's buildNodeTree (sanitize -> flatten -> materialize) -- the
same tested tree pipeline already used for AI-generated content -- instead
of hand-rolling a React-element tree, since a naive nested-children fix via
parseReactElement crashes any component with an internal SHELL_INNER linked
canvas (Section/BackgroundSection/FormContainer) or linked columns
(ColumnLayout). Also fixes two latent bugs in buildNodeTree itself, only
surfaced by exercising it against a real Craft.js editor for the first time:
data.type must be the actual resolved component reference (not a string or
{resolvedName} object) for correct rendering, and the synthesized SHELL_INNER
node needs data.name set for actions.addNodeTree's own validation.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Close button (already an FA fa-times icon-button) gains a hover
background/color; category pills gain the same hover-background
treatment when not active (they were already at the brief's target 12px
font-size). Template card hover-lift (translateY(-2px) + shadow) was
already implemented — no change needed there.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Unicode emoji/glyphs (Sitesmith's sparkle, lock, close X) render as
tofu on systems without an emoji font. Swap for the FA4 glyphs the
rest of the chrome already uses:
- SitesmithButton/SitesmithModal: sparkle -> fa-magic, lock -> fa-lock
- ContextMenu "Ask Sitesmith" entry: sparkle -> fa-magic (via new
optional MenuItem.icon field)
- TemplateModal/SitesmithModal close buttons, PagesPanel delete,
AssetsPanel delete/cancel: ✕ -> fa-times / fa-trash
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New src/ui/Modal.tsx ({ open, onClose, title?, children, width?,
closeOnEscape?, closeOnBackdropClick?, backdropStyle?, backdropProps? })
owns the backdrop, Escape-to-close, backdrop-click-to-close, and a new
body-scroll-lock while open. Adopted by TemplateModal, HeadCodeModal, and
SitesmithModal; each keeps its own panel styling/header/footer as children
since those differ per modal.
- TemplateModal: Escape/backdrop-click still close the confirm-template
sub-dialog first via a wrapped onClose passed to Modal; the header's X
button keeps using the raw onClose prop (always fully closes), matching
prior asymmetric behavior.
- HeadCodeModal: straightforward adoption, no prior custom close logic.
- SitesmithModal: previously had no `open` prop, no Escape-to-close, and no
backdrop-click-to-close. Preserved via open (always mounted-open by its
parent already), closeOnEscape={false}, closeOnBackdropClick={false}; role
and aria-modal are passed through via backdropProps to keep them on the
same backdrop element as before.
Body scroll-lock while a modal is open is a small new addition (requested by
the task) applied uniformly; it has no visible effect since each modal's
opaque fixed-position backdrop already fully covers the viewport.
Added a light Modal.test.tsx (renders children, Escape/backdrop-click close
behavior, scroll-lock) using the existing react-dom/client + act harness.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The template-load sequence (clear canvas -> add components -> add
pages -> apply header/footer) ran as a chain of setTimeout callbacks,
but onClose() fired synchronously right after kicking it off. The
modal disappeared immediately, so "Loading..." never had a chance to
show, and the staged mutations kept running against a dialog the
caller had already dismissed.
Convert the staged steps to async/await (a promise-based `wait()`
replaces the setTimeout chaining) and only call onClose() in a
finally block once the whole sequence has settled. Add a mountedRef
guard so the trailing setLoading(false) is skipped if the component
happens to unmount mid-sequence. End result (pages/header/footer/
design applied) is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rebuilt the visual site builder from scratch using Craft.js, React 18,
and TypeScript. The new editor renders directly in the DOM (no iframe),
supports 40+ components, multi-page with shared header/footer, 16
templates, full-spectrum color/gradient controls, custom head code
injection, save/publish workflow, and auto-save.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>