NavStylePanel (Navbar/Menu/Logo/Footer):
- LinkPicker: dropdown of the site's pages (read-only via usePages()) plus
manual URL / #anchor / tel: / mailto: entry, wired into every link-href
field (standalone Logo href, Navbar logoUrl, Navbar/Menu link items).
- "Sync links with Pages" button in the Links section: repopulates the
links array from the current pages list (label = page name, href = '/'
for the landing page else '/{slug}'), preserving any existing CTA link.
Regression-fix vs the legacy GrapesJS builder, which had this.
- `download` checkbox per link (Navbar/Menu links, standalone Logo href)
emits the `download` attribute on export for links to files.
- Links/Colors sections now gate on the component actually carrying a
`links`/color prop, so Footer (no links array) no longer shows a dead
"Add Link" editor.
- Box-model (Margin/Padding via SpacingControl, Border & Effects via
BorderControl + box-shadow presets + opacity), AnimationControl, and
VisibilityControl added for all four owned components, backed by new
animation/animationDelay/hideOnDesktop/hideOnTablet/hideOnMobile props
(with blank/default values in each component's .craft.props).
Tests: NavStylePanel.test.tsx (new, 17 tests: LinkPicker modes, sync
preserves CTA, download toggle, box-model/animation/visibility wiring) +
extended Navbar/Menu/Logo/Footer .toHtml.test.ts (download attribute,
craft.props defaults). Full suite: 683/683 passing. `npm run build` green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a single cssValue() sanitizer (src/utils/escape.ts) that strips
<>{};"'\ and neutralizes url(), safe for both style="..." attribute and
<style>...</style> element contexts. Applies it at every raw user-prop
CSS-value interpolation sink found via grep across src/components (colors,
sizes, gaps interpolated directly into style strings/<style> blocks),
including the highest-risk <style>-context sinks: ColumnLayout gap,
Menu/Navbar hover and background colors. Also Number()-coerces the
`columns` grid-template-columns sinks in Gallery/Testimonials/NumberCounter
as defense in depth. Regression tests assert </style><script> payloads are
neutralized and normal colors still render.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Migrates Menu, ColumnLayout, Countdown, Gallery, Tabs, InputField, and
TextareaField from Math.random()/content-hash scope ids to the threaded
Craft node id (via scopeId()), keeping every emitted element id,
aria-controls/aria-labelledby/for, and inline <script> function
name/getElementById() call consistently scoped per component.
Resolves the two Important id-collision review findings:
- Tabs: tabId was djb2(anchorId||labels) -- two default Tabs instances
produced identical aria-controls/aria-labelledby ids, so one instance's
arrow-key script clobbered the other's tab/panel wiring.
- InputField/TextareaField: fieldId was `field-${name}` -- two fields
sharing a (often default) name produced duplicate <label for>/<input
id> pairs, breaking the for/id association for one of them.
Also eliminates the remaining Math.random() scope ids in Menu (hover CSS
class scope) and ColumnLayout (nth-child width CSS class scope), so no
export component is non-deterministic anymore.
All fall back to a stable content hash (never Math.random) for legacy
2-arg toHtml() call sites without a node id.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Each component defined a .craft.related.settings panel that was never
rendered -- the right panel renders only GuidedStyles (per-type
*StylePanel components), never .related.settings. Removed all dead
settings components across every component, their settings-only helpers
(including the dead uploadToWhp/showBrowser/handleBrowse asset-browse
blocks in Logo/Navbar/VideoBlock/FeaturesGrid, and CtasEditor in
_cta-helpers), and dropped the now-empty related keys. Render output,
.craft props/rules, and toHtml statics are unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Every user-controlled URL emitted by a component's static toHtml (href,
src, action, and CSS url()) now runs through escapeAttr(safeUrl(...))
before hitting the exported HTML string, closing the XSS gaps flagged
in the A2 review (PricingTable buttonHref was fully unescaped, Gallery/
HeroSimple/ImageBlock/VideoBlock/etc. lacked scheme filtering) plus a
few more found via a grep sweep of href=/src=/action=/url( inside
toHtml template strings: MapEmbed's iframe src and the shared
form-relay-wiring fallback form action (a javascript: form action
executes on submit).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace divergent, buggy local esc/escapeHtml helpers across 26 files with
imports from src/utils/escape (escapeHtml/escapeAttr). Attribute call sites use
escapeAttr, text-content sites use escapeHtml. Several toHtml outputs now
correctly escape & where old local escapers omitted it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The prior null-safe esc patch only matched 'const esc =' declarations;
Menu/Navbar/Logo use 'function esc(str: string)' syntax and slipped
through. Patched those three to coerce non-strings the same way.
Added "Clear chat" button in the modal header that appears when there's
any message history. Confirms with the user before posting to the new
clear_history endpoint, which deletes all messages + the thread row
for the current site (usage rows are preserved for billing).
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>