injectAttrs() skipped leading <style> blocks and then inserted the
responsive/visibility/animation attrs before the first '>'. Relay forms
(ContactForm, SubscribeForm, FormContainer with a recipient) start their
toHtml with `<!--WHP-FORM ...-->`, so the attrs landed inside the marker
(`thankyou=""-- data-wsb-r="...">`): the comment was left unterminated,
Preview swallowed the form, and the publish step refused the whole site.
injectAttrs now skips any leading run of whitespace, <style> blocks and
<!--...--> comments (any order, repeated) and targets the first real
element; a string that is only such a run is returned unchanged. The
marker wire format is untouched.
Tests port the backend's strict marker regex from FormRelayRewrite.php
and export each relay form with a recipient + styleMobile/styleTablet +
hideOnMobile + animation through exportBodyHtml.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Save/load keep 30s; publish of a large site legitimately runs longer and was
reported as failed at 30s while the server kept going.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Preview and history Preview opened the composed page (raw site head code
included) as a panel-origin blob: tab, so a customer's <script> ran with the
viewer's panel session and CSRF token (root included). Both now open an empty
shell tab inside the click and render the page in an <iframe srcdoc> with
sandbox="allow-scripts allow-popups allow-forms" (no allow-same-origin):
an opaque origin, no opener. Tests assert the sandbox attribute and scan the
source for any remaining text/html blob / document.write / blob tab path.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Preset buttons in BoxModelSection / BorderEffectsSection now write through
the discrete setters (setPropStyles / setPropStyle), so a keyboard-activated
preset (no pointerup or focusout to close the burst) never merges into the
previous edit of the same key. SpacingControl gains an optional onPreset
for this; margin/padding presets are also one undo step for all four sides
in the media panels.
Refs #7
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- save, publish and load run under a 30s AbortController timeout (raced,
so a stalled body read can't hang either); save/publish resolve to
{ success:false, error:'timeout', message } and load rejects with a
readable Error, so a hung request no longer blocks the save queue
- readApiResult falls back to obj.error when obj.message is absent, so
PHP texts like 'Another save of this site is still in progress...' are
shown instead of 'server returned HTTP 503'
Refs #4
Refs #47
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Single-key shortcuts (1/2/3 device switch) are skipped while any editor
dialog or modal is open (role=dialog/alertdialog, aria-modal, dialog[open],
.modal-backdrop) or focus is inside one. Dialog-like page content inside
the canvas frame does not count.
Refs #39
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The popover is measured once open and clamped to the viewport with an
8px margin; near the bottom edge it flips above the '+'.
Refs #41
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A cache-hit search aborts any older in-flight stock_search and clears
loading; StockPhotoTab only applies the newest request's result
- A bare 404 means 'stock unavailable' only for the stock_search probe;
on stock_import it is an ordinary error (an explicit invalid/unknown
action error still disables the tab)
Refs #55
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Templates reference the site tokens, so with the checkbox off a template
follows the site's own palette. Label and a state-dependent help line now
say so.
Refs #44
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Switches NavStylePanel from nodeProps.style to useResolvedStyle(nodeProps)
and adds OverrideDots on Margin and Padding, the conversion #45 applied
to the other panels. NavStylePanel has no style-based text-alignment
control (Menu alignment is a top-level prop), so no dot there.
Refs #45
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- GuidedStyles classifies Opening Hours / Call Bar / Reviews / Booking to
BusinessStylePanel (#60) and shows a Regenerate button in the type
header, hidden when edit.regenerate-section's when() is false (#58)
- LayersPanel TYPE_ICONS for the four business blocks (FA 4.7) (#60)
- Context menu entries 'Save as section…' and 'Regenerate this section'
invoke site.save-as-section / edit.regenerate-section (#52, #58)
- commands: useCommandState(id) hook (registered/enabled/run)
- Layers: keep dropEffect 'move' on refused targets so drop fires and the
refusal notice shows; renaming to the default name stores no
custom.label (#47)
Refs #60
Refs #58
Refs #52
Refs #47
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Component render/toHtml now use var(--wsb-*) instead of hex so Site Design
palette changes reach FeaturesGrid, Testimonials, Accordion, PricingTable,
CTA/CallToAction, forms, Gallery, Tabs and others. Exemption lists removed.
Refs #44
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Icon button with tooltip 'Search (Ctrl/Cmd+K)' calls openCommandPalette()
- Preview passes fontScanHtml: composedBody and trims metaTitle so the
Preview head matches the published head
Refs #49
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Frontend half of the backend-followups head parity work:
- CSS reset gains html,body{overflow-x:clip} (no phantom h-scroll, sticky
navbar still works)
- buildFontsLink adds per-element preset fonts found in the page HTML
(fontScanHtml option, defaults to the body); own-key preset lookup so
names like 'constructor' behave like the backend's isset
- blank/whitespace design tokens fall back to the defaults
- description/ogTitle/twitterCard are trimmed
- history snapshot preview scans header+body+footer and trims metaTitle
Re-baselines the two TOKEN_VARS-move document hashes (the clip rule is
proven to be the only change) and pins the four complete heads from WHP
scripts/test-site-builder-head-parity.php.
Refs #54
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A page-scope Sitesmith replace with a master zone open loaded the AI page
onto the zone canvas. On Done, its first section became the new master and
the sync rewrote every instance on every page with it, orphaning all
overrides. replaceCurrentPage now returns false inside a master zone and
the apply path reports an error asking the user to click Done first.
Refs #57
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Make a top-level section global (context menu), place instances from the
Section library's new Global tab, edit the master once in its own zone
("Editing global section" banner, Done - update N instances) and every
instance on every page/header/footer is rebuilt from it. Instances are real
inlined subtrees (custom.global on the root, custom.gkey on every node), so
export and published output are unchanged static HTML.
- utils/global-sections.ts: pure model (createMaster, instantiate,
syncInstances, collectOverridesFromInstance, applyOverrides, detach,
findInstanceRoot, master zone round trip). Unchanged input returns the
identical string; sync keeps the instance root id and the instance's own
inner ids per gkey.
- PageContext: __global__:<id> zone (editGlobal / finishGlobal); leaving it
commits the master (rev + 1 only when it changed), syncs every stored
slot and applies the sync to the returned-to canvas as ONE undo step.
- GlobalSectionsContext: makeGlobal / insertInstance / detach / setOverride
/ resetOverride / remove (refused while used), one undo step each.
- Inline text edits inside an instance record an override (text + html)
via useGlobalOverrideCommit; GuidedStyles shows a Global section panel
(Edit master, Detach, content override fields) instead of style panels.
- RenderNode: Global badge, dashed outline, lock icon on inner nodes.
- buildSavePayload: a master zone on the canvas is never saved into a page.
Refs #57
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Registers site.download-zip (WHP mode only; shows in the ⋯ menu and the
palette) via ExportCommand, mounted in CanvasOverlays. useSiteExport POSTs
?action=export_zip with the CSRF header: a zip answer is saved through a
Blob + anchor click, anything else (JSON error, older backend, network
failure, a stream cut short) becomes an error notice.
Refs #42
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Token-based sections (21 total across four families) with tests covering
insert, export (no stray hex, alt on images, relative links) and tree shape.
Refs #52
Adds 17 token-based sections across contact, hours, gallery and local strips,
with a real-editor test covering insert, hex-free export, alt text and links.
Refs #52
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
URL/anchor/tel/mailto edits from LinkPicker now go through
continuousSetProp (key link:<i>:href); a page pick stays a discrete
step via discreteSetProp. source/pageId stripping and the followPages
mergeLinks re-merge remain inside the updater.
Refs #53
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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
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>
- 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>
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