Commit Graph

2 Commits

Author SHA1 Message Date
shadowdao 8029126ab7 deterministic + unique ids in 6 components (node-derived scope)
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>
2026-07-12 14:35:12 -07:00
shadowdao 7179287087 fix(builder): sanitize HtmlBlock/Countdown/Gallery JS contexts
Entity-escaping alone doesn't protect JS-string or raw-HTML sinks:

- HtmlBlock.toHtml exported props.code raw; now runs it through the
  same purifyHtml (DOMPurify) config already used for the live editor
  preview, so <script>/on*= payloads can't survive export either.
- Countdown.toHtml interpolated targetDate directly into
  `new Date("${targetDate}")` inside an inline <script> -- a value
  like `2026-01-01");alert(1)//` broke out of the string literal. Now
  validated against a strict date/datetime shape and JSON.stringify'd
  before embedding, falling back to `new Date()` for anything invalid.
- Gallery.toHtml's lightbox used
  `onclick="${id}_open('${esc(img.src)}')"`, which a single quote in
  img.src could break out of. Replaced with a `data-lb-src` attribute
  per thumbnail and one delegated click listener on the grid
  (`e.target.closest('[data-lb-src]')`) instead of a per-item inline
  handler string.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 12:06:07 -07:00