Frontend half of Sitesmith, the new AI site builder addon. Adds a chat modal to the Craft.js editor that takes a natural-language prompt and applies the AI's response (a replace, patch, or ask) to the editor state. Backed by the WHP-side PR: cloud-hosting-platform/whp#18
HtmlBlock hardened with DOMPurify (closes pre-existing XSS hole; the previous component rendered unsanitized markup directly into the DOM). Iframes restricted to YouTube/Vimeo/Maps; <script>, <form>, <style>, on* event handlers all stripped.
Vitest added as the unit-test framework (Playwright continues for E2E)
canvas-summary.ts — compresses current canvas state to a ~1.5K-token textual summary the AI reads each turn (excludes style props, asset URLs, opaque HtmlBlock contents)
TypeScript types (src/types/sitesmith.ts): SitesmithStatus, SitesmithSummary, SitesmithMessage, SitesmithResponse (discriminated union over replace/patch/ask), SitesmithPatchOp (5-variant union), SerializedTreeNode
useSitesmith hook — entitlement, history, send (adapts to the existing useEditorConfig().whpConfig shape)
apply-ai-response.ts — applies all 5 patch ops (update_props / replace_node / insert_after / insert_before / delete_node) plus full replace flows for scope=site, page, section. Includes serializeTreeForCraft to convert AI's nested tree into the Craft.js flat-nodes shape, and findNodeIdByAiNodeId for stable AI-handle → Craft-id resolution.
PageContext extended with 4 new methods: replaceAllPages, replaceCurrentPage, setHeader, setFooter (mirrors existing setHeaderCraftState / setFooterCraftState / setPagesCraftState pattern)
Sitesmith UI panel (src/panels/sitesmith/):
SitesmithButton.tsx — topbar button with locked/capped visual states
LayersPanel prefers props.aiName when present (falls back to existing displayName for non-AI nodes)
Playwright E2E suite (tests/sitesmith.spec.ts): 4 scenarios — locked, capped, bonus-credit override, full build+patch (preserves manual edit across turns)
Backward compatibility
The HtmlBlock DOMPurify change is the one behavior shift: existing customer content that included <script> / event handlers in HtmlBlock will be stripped on render. Discussed with operator — only one test site uses HtmlBlock at the moment, no impact.
All other changes are pure additions to the editor — new panels, new buttons, new utilities, new tests.
Bundle grew from ~460KB to ~786KB JS (DOMPurify + Sitesmith UI). Within reason for a feature this size.
## Summary
Frontend half of **Sitesmith**, the new AI site builder addon. Adds a chat modal to the Craft.js editor that takes a natural-language prompt and applies the AI's response (a `replace`, `patch`, or `ask`) to the editor state. Backed by the WHP-side PR: https://repo.anhonesthost.net/cloud-hosting-platform/whp/pulls/18
**Spec / plan are in the WHP repo:**
- `docs/superpowers/specs/2026-05-23-sitesmith-ai-builder-design.md`
- `docs/superpowers/plans/2026-05-23-sitesmith-ai-builder.md`
## What's in this PR
- **HtmlBlock hardened with DOMPurify** (closes pre-existing XSS hole; the previous component rendered unsanitized markup directly into the DOM). Iframes restricted to YouTube/Vimeo/Maps; `<script>`, `<form>`, `<style>`, `on*` event handlers all stripped.
- **Vitest added** as the unit-test framework (Playwright continues for E2E)
- **`canvas-summary.ts`** — compresses current canvas state to a ~1.5K-token textual summary the AI reads each turn (excludes style props, asset URLs, opaque HtmlBlock contents)
- **TypeScript types** (`src/types/sitesmith.ts`): `SitesmithStatus`, `SitesmithSummary`, `SitesmithMessage`, `SitesmithResponse` (discriminated union over replace/patch/ask), `SitesmithPatchOp` (5-variant union), `SerializedTreeNode`
- **`useSitesmith` hook** — entitlement, history, send (adapts to the existing `useEditorConfig().whpConfig` shape)
- **`apply-ai-response.ts`** — applies all 5 patch ops (update_props / replace_node / insert_after / insert_before / delete_node) plus full `replace` flows for scope=site, page, section. Includes `serializeTreeForCraft` to convert AI's nested tree into the Craft.js flat-nodes shape, and `findNodeIdByAiNodeId` for stable AI-handle → Craft-id resolution.
- **PageContext extended** with 4 new methods: `replaceAllPages`, `replaceCurrentPage`, `setHeader`, `setFooter` (mirrors existing `setHeaderCraftState` / `setFooterCraftState` / `setPagesCraftState` pattern)
- **Sitesmith UI panel** (`src/panels/sitesmith/`):
- `SitesmithButton.tsx` — topbar button with locked/capped visual states
- `SitesmithModal.tsx` — full-screen modal orchestrating chat + apply
- `MessageList.tsx`, `ChatInput.tsx`, `UpgradeBanner.tsx`, `ScopeConfirmDialog.tsx`
- **TopBar mounts the button + modal**
- **LayersPanel prefers `props.aiName`** when present (falls back to existing `displayName` for non-AI nodes)
- **Playwright E2E suite** (`tests/sitesmith.spec.ts`): 4 scenarios — locked, capped, bonus-credit override, full build+patch (preserves manual edit across turns)
## Backward compatibility
- The HtmlBlock DOMPurify change is the one behavior shift: existing customer content that included `<script>` / event handlers in HtmlBlock will be stripped on render. Discussed with operator — only one test site uses HtmlBlock at the moment, no impact.
- All other changes are pure additions to the editor — new panels, new buttons, new utilities, new tests.
- Bundle grew from ~460KB to ~786KB JS (DOMPurify + Sitesmith UI). Within reason for a feature this size.
## Test plan
- [x] `npm run test:unit` — 17 unit tests pass:
- 5 DOMPurify sanitizer tests (script tags, event handlers, javascript: URLs, YouTube allow, form/input strip)
- 3 canvas-summary tests (line-per-node, excludes style, truncation)
- 9 apply-ai-response tests (tree serialize, ID generation, node finder)
- [x] `npx tsc --noEmit` — clean
- [x] `npm run build` — bundle succeeds (786KB JS / 20KB CSS)
Follow-up (Task 23 — needs staging deploy + test user creation):
- [ ] Build dist via `npm run build` and copy to `/docker/whp/web/site-builder/` on staging
- [ ] Create 4 staging test users via WHP admin: `sitesmith_disabled`, `sitesmith_enabled`, `sitesmith_capped`, `sitesmith_bonus` — each owning one site
- [ ] Set entitlements per the SQL in `tests/sitesmith.spec.ts` header comment
- [ ] `PLAYWRIGHT_BASE_URL=https://192.168.1.105:8080 SITESMITH_TEST_PASSWORD=... npm run test:e2e:sitesmith`
- [ ] Manual E2E checklist (in WHP-PR plan Task 23 Step 4)
## Co-Authored-By
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Closes XSS hole in HtmlBlock by sanitizing user/AI-supplied markup
through DOMPurify before passing to dangerouslySetInnerHTML. Adds
Vitest + jsdom for unit testing with 5 passing tests covering script
stripping, on-event handler removal, javascript: URL blocking, iframe
allowlist, and form/input stripping.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Frontend half of Sitesmith, the new AI site builder addon. Adds a chat modal to the Craft.js editor that takes a natural-language prompt and applies the AI's response (a
replace,patch, orask) to the editor state. Backed by the WHP-side PR: cloud-hosting-platform/whp#18Spec / plan are in the WHP repo:
docs/superpowers/specs/2026-05-23-sitesmith-ai-builder-design.mddocs/superpowers/plans/2026-05-23-sitesmith-ai-builder.mdWhat's in this PR
<script>,<form>,<style>,on*event handlers all stripped.canvas-summary.ts— compresses current canvas state to a ~1.5K-token textual summary the AI reads each turn (excludes style props, asset URLs, opaque HtmlBlock contents)src/types/sitesmith.ts):SitesmithStatus,SitesmithSummary,SitesmithMessage,SitesmithResponse(discriminated union over replace/patch/ask),SitesmithPatchOp(5-variant union),SerializedTreeNodeuseSitesmithhook — entitlement, history, send (adapts to the existinguseEditorConfig().whpConfigshape)apply-ai-response.ts— applies all 5 patch ops (update_props / replace_node / insert_after / insert_before / delete_node) plus fullreplaceflows for scope=site, page, section. IncludesserializeTreeForCraftto convert AI's nested tree into the Craft.js flat-nodes shape, andfindNodeIdByAiNodeIdfor stable AI-handle → Craft-id resolution.replaceAllPages,replaceCurrentPage,setHeader,setFooter(mirrors existingsetHeaderCraftState/setFooterCraftState/setPagesCraftStatepattern)src/panels/sitesmith/):SitesmithButton.tsx— topbar button with locked/capped visual statesSitesmithModal.tsx— full-screen modal orchestrating chat + applyMessageList.tsx,ChatInput.tsx,UpgradeBanner.tsx,ScopeConfirmDialog.tsxprops.aiNamewhen present (falls back to existingdisplayNamefor non-AI nodes)tests/sitesmith.spec.ts): 4 scenarios — locked, capped, bonus-credit override, full build+patch (preserves manual edit across turns)Backward compatibility
<script>/ event handlers in HtmlBlock will be stripped on render. Discussed with operator — only one test site uses HtmlBlock at the moment, no impact.Test plan
npm run test:unit— 17 unit tests pass:npx tsc --noEmit— cleannpm run build— bundle succeeds (786KB JS / 20KB CSS)Follow-up (Task 23 — needs staging deploy + test user creation):
npm run buildand copy to/docker/whp/web/site-builder/on stagingsitesmith_disabled,sitesmith_enabled,sitesmith_capped,sitesmith_bonus— each owning one sitetests/sitesmith.spec.tsheader commentPLAYWRIGHT_BASE_URL=https://192.168.1.105:8080 SITESMITH_TEST_PASSWORD=... npm run test:e2e:sitesmithCo-Authored-By
🤖 Generated with Claude Code