# Site Builder — Five User-Reported Issues Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Fix five reported site-builder problems — elements stranded outside the page, dead colour controls on the HTML block, no way to blank a page, an incomplete Layers tree, and no in-builder way to report a bug. **Architecture:** Editor work lands in the Craft.js app at `/workspace/site-builder/craft/`. Every non-trivial behaviour is first extracted as a **pure function** in `src/utils/` or a small registry module, unit-tested with no React and no Craft instance, then wired into a panel. Reporting adds one endpoint to the existing WHP site-builder API, one MySQL table, and one root-only admin page in `/workspace/whp/`. **Tech Stack:** Vite 6, React 18, TypeScript 5 (strict), @craftjs/core 0.2.x, CodeMirror 6 (lazy-loaded), vitest + jsdom, PHP 8 + PDO/MySQL, Bootstrap 5 (admin page). ## Global Constraints - **Design spec:** `docs/superpowers/specs/2026-08-08-site-builder-user-reported-issues-design.md`. Read it before starting. - **Active source is `/workspace/site-builder/craft/`.** Never edit `/workspace/site-builder/` top level — that is the dead GrapesJS builder. - **No `@/` alias in tests.** `vitest.config.ts` defines no `resolve.alias`. Use relative imports in all source and test files. - **No `@testing-library/react`.** This repo renders with `createRoot` + `act` from `react-dom/test-utils`. For a component needing only `useNode`, mock `@craftjs/core` (pattern: `src/components/media/ImageBlock.render.test.tsx`). For anything needing a real editor, use `renderEditorHarness()` from `src/test-utils/editorHarness.tsx`. - **Run tests with:** `cd /workspace/site-builder/craft && npx vitest run `. - **New dependencies: none.** The HTML formatter is hand-written; do not add `js-beautify`, `prettier`, `html2canvas`, or an Emmet package. - **Every component prop change must honour both sides:** the React `render` *and* the static `.toHtml(props, childrenHtml)`. `HtmlBlock` is the exception this plan creates deliberately (Task 1). - **PHP shell/CLI rule:** verify anything path-sensitive through the web path, not bare `php -r` — CLI has an empty `open_basedir`. - **SQL migrations are idempotent** (`CREATE TABLE IF NOT EXISTS`), go in `sql/migrations/staging/`, and never hand-create versioned directories. - **Commit after every task.** Co-author trailer: `Co-Authored-By: Claude Opus 5 (1M context) `. --- ## File Structure **Created in `/workspace/site-builder/craft/`:** | File | Responsibility | |---|---| | `src/utils/orphan-repair.ts` | Pure: find nodes unreachable from `ROOT` in serialized craft state; reattach them. | | `src/utils/orphan-repair.test.ts` | Tests for the above. | | `src/utils/format-html.ts` | Pure: indent-only HTML prettifier. | | `src/utils/format-html.test.ts` | Tests for the above. | | `src/utils/console-buffer.ts` | Global ring buffer of the last 20 console/window errors. | | `src/utils/console-buffer.test.ts` | Tests for the above. | | `src/utils/build-stamp.ts` | Safe accessor for the `__EDITOR_BUILD__` compile-time define. | | `src/utils/report-payload.ts` | Pure: assemble + size-cap an issue-report payload. | | `src/utils/report-payload.test.ts` | Tests for the above. | | `src/panels/left/layers-virtual-rows.ts` | Registry + pure derivation of virtual child rows for array-prop composites. | | `src/panels/left/layers-virtual-rows.test.ts` | Tests for the above. | | `src/panels/left/LayerFocusContext.tsx` | Context carrying "scroll array item N of node X into view". | | `src/panels/right/styles/HtmlCodeField.tsx` | The Edit HTML button + modal (moved out of `GenericPropsEditor`). | | `src/panels/right/styles/HtmlToolbar.tsx` | Insert/colour/format toolbar for the Edit HTML modal. | | `src/panels/right/styles/HtmlStylePanel.tsx` | HTML block's only style panel — the Edit HTML control alone. | | `src/panels/right/styles/HtmlStylePanel.test.tsx` | Asserts no colour/style controls render. | | `src/panels/topbar/ReportIssueModal.tsx` | Report-an-issue form + submit. | | `src/panels/topbar/ReportIssueModal.test.tsx` | Tests for the above. | **Modified in `craft/`:** `src/components/basic/HtmlBlock.tsx`, `src/panels/right/GuidedStyles.tsx`, `src/panels/right/styles/GenericPropsEditor.tsx`, `src/panels/right/styles/index.ts`, `src/ui/CodeEditor.tsx`, `src/state/PageContext.tsx`, `src/panels/left/LayersPanel.tsx`, `src/panels/left/PagesPanel.tsx`, `src/panels/right/SiteDesignPanel.tsx`, `src/panels/topbar/TopBar.tsx`, `src/panels/topbar/TopBarOverflowMenu.tsx`, `src/main.tsx`, `vite.config.ts`, `src/styles/editor.css`. **Created in `/workspace/whp/`:** `sql/migrations/staging/create-site-builder-reports.sql`, `scripts/test-site-builder-report-validation.php`, `web-files/pages/site-builder-reports.php`. **Modified in `/workspace/whp/`:** `web-files/api/site-builder.php`, `web-files/includes/` sidebar, `web-files/libs/permission_manager.php`, `web-files/index.php` (page registration), `DOCS_FOR_AGENTS/DATABASE_SCHEMA.md`. --- ## Phase 1 — HTML block (spec item 2) ### Task 1: HtmlBlock stops applying `style` in the editor The reported symptom is that colours show in the builder and never reach the live page. `toHtml()` already ignores `style`; the render does not. Fix the render so both sides agree. **Files:** - Modify: `src/components/basic/HtmlBlock.tsx:65-78` - Test: `src/components/basic/HtmlBlock.render.test.tsx` (create) - Test: `src/components/basic/HtmlBlock.toHtml.test.ts` (extend) **Interfaces:** - Consumes: nothing from earlier tasks. - Produces: `HtmlBlock` render no longer reads `props.style`. `HtmlBlockProps` keeps its `style?: CSSProperties` field so stored state stays loadable. - [ ] **Step 1: Write the failing render test** Create `src/components/basic/HtmlBlock.render.test.tsx`: ```tsx import { describe, test, expect, vi } from 'vitest'; import React from 'react'; import { createRoot, Root } from 'react-dom/client'; import { act } from 'react-dom/test-utils'; vi.mock('@craftjs/core', () => ({ useNode: (collect?: (node: any) => any) => { const node = { events: { selected: false } }; return { connectors: { connect: (el: any) => el, drag: (el: any) => el }, actions: { setProp: vi.fn() }, ...(collect ? collect(node) : {}), }; }, })); import { HtmlBlock } from './HtmlBlock'; let container: HTMLDivElement; let root: Root; function render(ui: React.ReactElement) { container = document.createElement('div'); document.body.appendChild(container); act(() => { root = createRoot(container); root.render(ui); }); } describe('HtmlBlock render ignores the style prop (matches toHtml)', () => { test('a stored backgroundColor/color/padding is NOT applied to the wrapper', () => { render( React.createElement(HtmlBlock as any, { code: '

hello

', style: { backgroundColor: 'rgb(255, 0, 0)', color: 'rgb(0, 0, 255)', padding: '40px' }, }), ); const wrapper = container.firstElementChild as HTMLElement; expect(wrapper.style.backgroundColor).toBe(''); expect(wrapper.style.color).toBe(''); expect(wrapper.style.padding).toBe(''); }); test('the editor affordances survive: minHeight is kept, content still renders', () => { render(React.createElement(HtmlBlock as any, { code: '

hello

', style: {} })); const wrapper = container.firstElementChild as HTMLElement; expect(wrapper.style.minHeight).toBe('40px'); expect(wrapper.innerHTML).toContain('hello'); }); }); ``` - [ ] **Step 2: Run it and verify it fails** Run: `npx vitest run src/components/basic/HtmlBlock.render.test.tsx` Expected: FAIL — the first test reports `backgroundColor` as `rgb(255, 0, 0)` because the current render spreads `...style`. - [ ] **Step 3: Make the render ignore `style`** In `src/components/basic/HtmlBlock.tsx`, replace the component body's `React.createElement` call: ```tsx export const HtmlBlock: UserComponent = ({ code = '' }) => { const { connectors: { connect, drag }, selected } = useNode((node) => ({ selected: node.events.selected })); const clean = useMemo(() => purifyHtml(code), [code]); const setRef = (ref: HTMLElement | null): void => { if (ref) connect(drag(ref)); }; // The `style` prop is deliberately NOT applied. `toHtml()` emits only the // purified `code`, so anything styled here would show in the editor and // vanish on the live page -- the exact bug this block was reported for. // Wrapper styling belongs in the user's own markup (see the Edit HTML // toolbar's colour control). `style` stays on the props interface so // already-saved sites keep deserializing cleanly. return React.createElement('div', { ref: setRef, style: { minHeight: '40px', outline: selected ? '2px solid #3b82f6' : 'none', }, dangerouslySetInnerHTML: { __html: clean }, }); }; ``` - [ ] **Step 4: Add the matching export assertion** Append to `src/components/basic/HtmlBlock.toHtml.test.ts`: ```ts test('toHtml never emits the style prop (the other half of the render/export contract)', () => { const out = (HtmlBlock as any).toHtml( { code: '

hi

', style: { backgroundColor: '#ff0000', padding: '40px' } }, '', ); expect(out.html).toBe('

hi

'); expect(out.html).not.toContain('background'); expect(out.html).not.toContain('40px'); }); ``` - [ ] **Step 5: Run both files and verify they pass** Run: `npx vitest run src/components/basic/HtmlBlock` Expected: PASS, all tests in `HtmlBlock.test.ts`, `HtmlBlock.toHtml.test.ts`, `HtmlBlock.render.test.tsx`. - [ ] **Step 6: Commit** ```bash cd /workspace/site-builder git add craft/src/components/basic/HtmlBlock.tsx craft/src/components/basic/HtmlBlock.render.test.tsx craft/src/components/basic/HtmlBlock.toHtml.test.ts git commit -m "fix(site-builder): HtmlBlock render stops applying style so editor matches published output Co-Authored-By: Claude Opus 5 (1M context) " ``` --- ### Task 2: Move `HtmlCodeField` into its own file Pure extraction, no behaviour change. Isolating it first keeps Task 3 (the panel) and Task 6 (the toolbar) from both editing `GenericPropsEditor.tsx`. **Files:** - Create: `src/panels/right/styles/HtmlCodeField.tsx` - Modify: `src/panels/right/styles/GenericPropsEditor.tsx:1-118` **Interfaces:** - Produces: `export const HtmlCodeField: React.FC<{ value: string; onChange: (v: string) => void }>` — renders a `CollapsibleSection` titled "HTML Code" containing an "Edit HTML" button that opens a `Modal` with a `CodeEditor`. - [ ] **Step 1: Create the new file** Create `src/panels/right/styles/HtmlCodeField.tsx` with the exact `HtmlCodeField` component currently living at `GenericPropsEditor.tsx:28-92`, plus its imports: ```tsx import React, { useState } from 'react'; import { CollapsibleSection, sectionGap } from './shared'; import { Modal } from '../../../ui/Modal'; import { CodeEditor } from '../../../ui/CodeEditor'; /* "Edit HTML" modal for the HtmlBlock `code` prop. `code` is raw HTML (potentially many lines, embedded