Commit Graph
57 Commits
Author SHA1 Message Date
shadowdao 1d34d1b3b1 Merge issue-53 (Refs #53)
# Conflicts:
#	craft/src/styles/editor.css
2026-10-07 12:09:09 -07:00
shadowdao d39e1ecc31 Merge issue-56 (Refs #56)
# Conflicts:
#	craft/src/editor/CanvasOverlays.tsx
#	craft/src/styles/editor.css
2026-10-07 12:06:59 -07:00
shadowdao 0a59294fee Merge issue-54-fe (Refs #54)
# Conflicts:
#	craft/src/styles/editor.css
2026-10-07 12:06:59 -07:00
shadowdao 4a81d0b89f Merge issue-51-fe (Refs #51)
# Conflicts:
#	craft/src/editor/CanvasOverlays.tsx
#	craft/src/styles/editor.css
2026-10-07 12:06:59 -07:00
shadowdao 1eaf8e39fb Merge issue-49 (Refs #49)
# Conflicts:
#	craft/src/styles/editor.css
2026-10-07 12:06:59 -07:00
shadowdao eb87a39619 Merge issue-48 (Refs #48)
# Conflicts:
#	craft/src/styles/editor.css
2026-10-07 12:06:59 -07:00
shadowdao c0fd183c11 feat(site-builder): pre-publish checklist and canonical link
Publish now runs client-side checks (image alt, meta description, title
length, H1, empty links, contrast, noindex landing, empty page) over the
current state and shows a grouped checklist with Fix links and
"Publish anyway". Adds ExportOptions.canonicalUrl -> <link rel="canonical">
after the favicon line in both Preview and export.

Refs #54
2026-10-07 12:05:28 -07:00
shadowdaoandClaude Sonnet 5.5 a9c8f4d65a feat(site-builder): version history modal with preview and restore
Adds a site.history command (WHP mode, only when history_list answers) opening
a modal of saves/publishes by day. Preview opens the snapshot re-exported with
its own design; Restore site / Restore one page load the snapshot as unsaved
state (history cleared, dirty marked) so the user still clicks Save.

Refs #51

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-07 12:04:17 -07:00
shadowdaoandClaude Sonnet 5.5 0bd6b74ba5 feat(site-builder): copy/paste style and saved style presets
Refs #48

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-07 12:03:27 -07:00
shadowdaoandClaude Sonnet 5.5 a80667dfe6 feat(site-builder): multi-select with bulk styling and mixed-type actions
Shift/cmd/ctrl-click multi-select via DefaultEventHandlers, same-type bulk
styling as one undo step, mixed-type Delete/Duplicate/Move block, grouped
undo for array-aware edit.* commands.

Refs #50

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-07 12:03:26 -07:00
shadowdaoandClaude Opus 5.5 52477bff45 feat(site-builder): menus that follow pages, with dropdowns
- utils/nav-links.ts: shared NavLink type (source/pageId/children),
  pageHref, buildPageLinks, mergeLinks, adoptPageLinks, rewriteHeaderLinks
- PageData.nav {show, parentId, label} + updatePageNav; a nav-sync effect in
  PageProvider materialises page-following links into the stored header
  (or the live header nodes while it is being edited); string/identity
  guards keep it a no-op when nothing changed
- Navbar/Menu: followPages prop (default false; the default header seed is
  true), one-level dropdowns (hover/focus-within + inline click toggle,
  indented in the hamburger layout), hover/CTA colours default to the
  primary token
- NavStylePanel: Follow pages toggle (adopts matching manual links, keeps
  custom text as the page's menu label), read-only page rows with Hide from
  menu, sub-link editor for manual links, LinkPicker records pageId
- Page Settings: Show in menu / Menu label / Nest under; Pages panel badge
- useWhpApi: nav passes through pages_craft_state on save and load

Legacy navs (no followPages) are never rewritten.

Refs #53

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:03:16 -07:00
shadowdaoandClaude Sonnet 5.5 1512cb7645 feat(site-builder): Cmd+K command palette
Refs #49

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-07 12:03:07 -07:00
shadowdao 092c8f6747 feat(site-builder): drag padding handles on the canvas
Edge handles on a selected Section/Container/Columns write padding via the
per-device setter as one undo burst; Alt = opposite side, Shift = all four,
Ctrl/Cmd snaps to 8px, Escape cancels.

Refs #56
2026-10-07 12:02:32 -07:00
shadowdao 5430f13877 Merge issue-45 (Refs #45)
# Conflicts:
#	craft/src/panels/right/styles/ButtonStylePanel.tsx
#	craft/src/panels/right/styles/shared.tsx
#	craft/src/styles/editor.css
2026-10-07 11:58:40 -07:00
shadowdao 68a3734eaf Merge issue-47 (Refs #47)
# Conflicts:
#	craft/src/styles/editor.css
2026-10-07 11:58:26 -07:00
shadowdao 2b41cd46ba Merge issue-39 (Refs #39)
# Conflicts:
#	craft/src/styles/editor.css
2026-10-07 11:58:26 -07:00
shadowdao c2a90cd7a7 feat(site-builder): tablet/mobile style overrides that never touch desktop
On tablet/mobile the useNodeProp style setters now route responsive keys
(spacing, font size, line height, letter spacing, alignment, size, flex
layout) into styleTablet/styleMobile; colours and other keys still write
style. An empty value removes the override. Continuous-burst keys include
the device so a desktop drag and a mobile drag never merge.

- shared.tsx: device-aware setters, new setResponsiveStyleProp /
  clearResponsiveStyleProp exports, setPropStylesContinuous
- responsive-panel.tsx: useResolvedStyle, useOverrideState, DeviceBanner
- OverrideDot: filled dot resets the override (one undo step), hollow dot
  marks an inherited value; wired into Text/Button/Container/Image/Media/
  BackgroundSection panels and both box-model sections
- Columns: "Stack on this device" toggle (flexDirection override)
- GuidedStyles: device banner under the type header
- RenderNode: every component node renders through one stable
  ResolvedRender so gaining/losing an override or switching device no
  longer remounts the node (the remount dropped inline-edit focus/caret
  and blanked a selected Heading)

Refs #45
2026-10-07 11:51:53 -07:00
shadowdao 2dd362e858 Merge issue-44 (Refs #44)
# Conflicts:
#	craft/src/styles/editor.css
2026-10-07 11:47:58 -07:00
shadowdao c68e459430 feat(site-builder): complete shortcut set on the command registry with a cheat sheet
Core editor shortcuts become registry commands run by the single
useCommandShortcuts dispatcher: undo/redo, delete, duplicate (after +
select), copy/paste, Alt+arrow move, 1/2/3 device, Cmd+S/Shift+P/P site
commands and Cmd+/ cheat sheet. Tab/Esc no longer fire from buttons or
dialogs. Context menu labels come from formatShortcut.

Refs #39
2026-10-07 11:47:55 -07:00
shadowdao 98e32a68b9 Merge issue-46 (Refs #44 #46)
# Conflicts:
#	craft/src/editor/CanvasOverlays.tsx
#	craft/src/styles/editor.css
2026-10-07 11:47:54 -07:00
shadowdaoandClaude Sonnet 5.5 8edc426986 feat(site-builder): layers drag reorder, rename, hide/show, collapse
Refs #47

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-07 11:47:10 -07:00
shadowdao d8ff295093 fix(site-builder): rebuild editor.css after union merge interleaved #41/#34 regions 2026-10-07 11:42:32 -07:00
shadowdaoandClaude Opus 5.5 7e8812a752 feat(site-builder): inline bold/italic/underline/links on Heading and Text
Select text inside a selected Heading or Text block and a floating
toolbar offers B / I / U / Link / Unlink (execCommand; mousedown is
prevented so the selection survives; mod+b/i/u handled on the editable).

Storage: new `html` prop holding sanitised inline HTML, with `text` kept
as the plain mirror for AI summaries, search and legacy content.
Unmarked edits store html '' so legacy nodes keep their shape. The
allowlist (b strong i em u a br span; style=color only, hex/rgb/
var(--wsb-*); href via DOMPurify URI check AND safeUrl, unsafe links
unwrapped; target only _blank with forced rel=noopener) runs on commit,
on canvas render and again at export. If `text` was changed without
`html` (Sitesmith update_props), the stale html is ignored.

Each edit commits once (blur, or deselect if no blur came first) as one
setProp = one undo step; the old blur+deselect double commit is gone.
Heading colour default now tokenRef('text'). Sitesmith canvas summary
skips `html`.

Refs #46

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 11:41:56 -07:00
shadowdao 74df8a3b70 Merge issue-34 (Refs #34) 2026-10-07 11:41:22 -07:00
shadowdao d756a723de Merge issue-43 (Refs #43)
# Conflicts:
#	craft/src/editor/CanvasOverlays.tsx
2026-10-07 11:41:22 -07:00
shadowdao 817028df89 Merge issue-41 (Refs #41) 2026-10-07 11:40:54 -07:00
shadowdaoandClaude Sonnet 5.5 fad34f3e71 feat(site-builder): actionable empty canvas and inline add for empty containers
Refs #41

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-07 11:39:34 -07:00
shadowdao 22c59eb970 feat(site-builder): tablet/mobile canvas emulates published hide, stacking and full-width
Frame gets data-device; RenderNode mirrors hide flags and selection onto the
dom; editor.css emulates the exported breakpoints inside the narrowed frame.
Also: data-wsb-overridden counts only set values, responsive scope ids are
zone-prefixed (h-/b-/f-) so ROOT no longer collides, and comment delimiters
are stripped from responsive declarations.

Refs #37
2026-10-07 11:39:25 -07:00
shadowdaoandClaude Sonnet 5.5 af460f23e6 feat(site-builder): precise drop indicator with target glow and empty-container label
Restyle Craft's insertion bar via the Editor indicator prop and mirror the
live placement onto the DOM (data-drop-target/-error/-empty, is-dragging).

Refs #43

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-07 11:38:22 -07:00
shadowdao dd7a50cf3d feat(site-builder): token-aware colour controls and Try a palette
Colour controls offer the site palette first, show the resolved colour for
var(--wsb-*) values with a token chip, and keep the stored reference.
autoTextColor resolves tokens. Site Design gains six one-click palettes.

Refs #44
2026-10-07 11:38:17 -07:00
shadowdaoandClaude Sonnet 5.5 2a6055d863 feat(site-builder): selection breadcrumb, Esc selects parent, Tab cycles siblings
Refs #34

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-07 11:36:40 -07:00
shadowdao 136e3ce6ea fix(site-builder): restore closing brace lost in #36/#38 editor.css merge 2026-10-07 11:31:10 -07:00
shadowdao 1f5cac2cca Merge issue-38 (Refs #38)
# Conflicts:
#	craft/src/styles/editor.css
2026-10-07 11:30:20 -07:00
shadowdao e5119174e7 feat(site-builder): Element/Site tabs in the right panel
Refs #36
2026-10-07 11:25:28 -07:00
shadowdao 7b082c8d2e feat(site-builder): searchable blocks panel, remembered collapse state and left tab
Refs #38
2026-10-07 11:23:00 -07:00
shadowdao 2180f9a776 Merge W0-registry-notices (Refs #38 #41 #49 #48 #57 #58)
# Conflicts:
#	craft/src/state/NoticeContext.tsx
#	craft/src/styles/editor.css
2026-10-07 11:20:30 -07:00
shadowdao ae871c9cdc Merge W0-commands (Refs #39 #49 #34)
# Conflicts:
#	craft/src/styles/editor.css
2026-10-07 11:20:25 -07:00
shadowdaoandClaude Opus 5.5 f8ac1fbe42 feat(site-builder): token module, canvas token variables, palette swatch row
Make var(--wsb-*) a first-class style value in the editor:

- utils/tokens.ts: TokenKey, TOKEN_VARS (moved from html-export.ts, same
  order/content as the backend), COLOR_TOKENS palette order, tokenRef,
  isTokenRef, tokenKeyOf, resolveTokenValue and buildCanvasTokenCss, which
  defines the 16 vars + token base CSS scoped to .canvas-device-frame
  (never :root) with every value through cssValue().
- editor/CanvasTokenStyle.tsx: <style data-wsb-canvas-tokens> driven by
  useSiteDesign(), mounted in App inside SiteDesignProvider, so design
  edits recolour token-backed values on the canvas instantly.
- panels/right/styles/TokenSwatchRow.tsx: palette-first swatch row that
  emits token references; .token-swatch styles appended to editor.css.
- html-export.ts imports TOKEN_VARS; export output pinned byte-identical
  to the pre-move baseline by hash in html-export.test.ts.

Refs #44

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 11:16:29 -07:00
shadowdaoandClaude Sonnet 5.5 73aa35ac96 feat(site-builder): command registry, shortcut matcher, tree walkers and top-bar command menu
Library only; nothing is mounted. Adds minimal DeviceContext/NoticeContext
stubs (owned by W0-breakpoints / W0-registry-notices) that expose the
agreed interfaces.

Refs #39 #49 #34

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-07 11:14:52 -07:00
shadowdaoandClaude Sonnet 5.5 21c2776c88 feat(site-builder): block registry, notices, local prefs, project extras, overlay slot
Add an exported block registry (keywords, search, insertBlock) consumed by
BlocksPanel, a NoticeProvider/NoticeHost toast stack, a guarded localStorage
prefs helper, a ProjectExtras store persisted as project.json 'extras', and a
CanvasOverlays mount point.

Refs #38
Refs #41
Refs #49
Refs #48
Refs #57
Refs #58

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-07 11:14:51 -07:00
shadowdaoandClaude Opus 5.5 97ccbee461 fix(site-builder): serialise all saves; honest pill for conflicts and new sites
A manual Save during Publish sent a second save with the same base_rev; the
loser 409'd against its own tab (and could abort the publish).

- useWhpApi: every save runs through one in-flight promise (SaveGuard
  saveInFlight). A save requested while one is out waits, then sends only
  if still dirty, with the rev the first returned; payload is read from the
  editor at send time. Forced (empty/overwrite) saves always send.
- Save buttons are disabled while publishing.
- Pill: a conflict reads 'Unsaved changes' (modal open) from Save and from
  Publish alike; a never-saved site (load rev null) reads 'Not saved yet'.
- Autosave interval reads runSave through a ref, so editing can't keep
  restarting it and starve autosave.
- Fix stale 'Throws like exportBodyHtml' comment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 10:35:11 -07:00
shadowdaoandClaude Sonnet 5.5 a6db5438b2 feat(site-builder): detect two-tab save conflicts; Reload latest / Keep mine modal
Refs #31

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-07 10:19:07 -07:00
shadowdaoandClaude Sonnet 5.5 9418444916 fix(site-builder): honest save state (dirty tracking, status pill, beforeunload, error notice)
Track dirty from Craft node changes and page/design/header/footer state, cleared
only by a successful save. Persistent Unsaved/Saving/Saved/Published/Save failed
pill, beforeunload prompt while dirty or saving, one persistent error notice,
resp.ok/non-JSON handling for save and publish, and publish refuses to run
after a failed save. Draft badge no longer flips on a no-op autosave.

Refs #30

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-07 10:07:03 -07:00
shadowdao a406ff8548 fix(site-builder): fail load on unreadable page state; disable editing controls until loaded
Refs #28
2026-10-07 10:00:11 -07:00
shadowdao 66e669fa01 fix(site-builder): never save before a successful load; guard empty overwrites
Refs #28
2026-10-07 09:57:11 -07:00
shadowdaoandClaude Opus 5 2438777462 feat(site-builder): Layers shows array-prop items, unplaced nodes, and scrolls
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 07:15:17 -07:00
shadowdaoandClaude Opus 4.8 d460e8ac33 topbar: surface publish warnings instead of discarding them
handlePublish's JSON response has always included a `warnings` array
(e.g. the contact-form relay's "submissions will not be delivered"
notice), but nothing in the editor ever read it. Adds a PublishWarnings
banner, held in its own state independent of the 3s publishStatus
flash so the customer has time to read it, rendered in both the
desktop and mobile TopBar branches.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 21:52:11 -07:00
shadowdaoandClaude Opus 4.8 77f35c4e9e fix(builder): duplicate inserts+selects after source; select-parent/styles-sheet/canvas-pad mobile fixes
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>
2026-07-13 08:41:33 -07:00
shadowdaoandClaude Opus 4.8 2c8425ffb0 feat(builder): mobile-B touch editing -- selection toolbar, tap-to-add, swipe-dismiss
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>
2026-07-13 08:07:51 -07:00
shadowdaoandClaude Opus 4.8 2a8a26687b fix(builder): mobile-A2 hardening -- 16px inputs, shared sheet/modal chrome, z-scale + portal
- 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>
2026-07-13 07:30:12 -07:00