Measured in Chromium and Firefox: the CSSOM exposes `background: var(--x)` and
`border: 1px solid var(--x)` only as pending-substitution longhands whose values
read back as "", so the capture sanitiser (which re-serialises the parsed
declarations) dropped them. The self-review screenshot then showed dark heroes,
image placeholders and card borders as white, and the model "fixed" things that
were fine on the real page.
The sanitiser now emits the declared shorthand once, at its first longhand,
through the same value/URL gate as every other declaration. Sandbox, CSP and
the single URL allow-list are unchanged.
- scripts/e2e-capture-fidelity.mjs: real-browser test through the whole capture
path (sanitise -> inline -> sandboxed frame -> html-to-image), decodes the
JPEG and samples pixels. Failed 9/12 checks per browser before, passes after.
- e2e-capture-security: hostile var() shorthand / var() fallback url() probes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stored pages (generated at editor-save time) from before release 2026.11.5 carry
FeaturesGrid / Testimonials / NumberCounter grids without data-wsb-grid, so a
republish kept repeat(N,1fr) on phones (OLS home: body 594px wide at 360).
The <=767px block now matches the exact inline styles those emitters stored
(whole-attribute signatures; no spaces, as in git history and every stored
OLS/TTM page). Gallery shares the prefix with a free gap and keeps its columns:
the two colliding signatures carry :not(:has(> * > Gallery thumbnail)) in their
own rule (a browser without :has drops only that rule). Customer grids never
match. Mirrored byte-for-byte by WHP generateCompiledHTML (fix/legacy-grid-backend).
Tests: vitest selector contract (legacy shapes match, == current emitters minus
the hook, Gallery and customer grids never match, pretty == minified); head
parity pins updated; new real-browser suite test:browser:legacy-grid (Chromium +
Firefox, 360/390/768/1280) on the stored OLS home page + synthetic legacy grids;
colors suite folds the new rules back like the mobile-guard ones.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Tables inside HtmlBlocks are left alone on phones: display:block (the only way to make one scroll)
shrank a fitting width:100% table's rows from 349px to 77px at 390. Wide tables stay clipped as before.
The <pre> rule (max-width:100%;overflow-x:auto, no display change) and the media rules stay.
- Browser suite: width:100% table still spans the screen (fails on cfeeef3's rule: first row 78px); the
10-column bare table moved to htmlwide (clipped, page never scrolls sideways).
- e2e-color-system: default COLOR_BASE_COMMIT ab7a100 -> 69fd34b, the first commit with the 2026.11.4
published head (ColumnLayout stacking + Navbar wrap) and none of the colour feature. ab7a100 predates
that head, so check 5 failed on main itself. This branch's own head change (mobile guard) is folded back
by exact string, asserted present once, before comparing, so the check still proves the colour feature
changed nothing else. Passes on 69fd34b; fails (as it should) against ab7a100.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of bb4fb75: overflow-x:auto on [data-wsb-html] forced overflow-y:auto (clipped dropdowns/shadows),
broke position:sticky inside customer HTML, and display:block!important merged multi-root blocks that are
direct flex/grid items. Now the wrapper stays display:contents on every viewport and no rule targets it;
at <=767px only its descendants are touched: media max-width:100% (img/video height:auto), and a table or
<pre> becomes its own sideways scroller (not when already inside an inline overflow wrapper). No
overflow-wrap:anywhere (it shrank min-content); body{overflow-wrap:break-word} inherits through the wrapper.
Deliberately wide customer markup (fixed widths, px grids) is clipped as before; the publish warning is
its guard, and its wording now says the excess is cut off. width= attributes are no longer flagged (they
size only media and tables, which the net handles). stripMediaBlocks lowercases once.
Browser suite: htmlblocks probes at every width (wrapper display:contents, sticky sticks, absolutely
positioned dropdown visible on top, multi-root block = separate flex items / grid cells, bare table
scrolls on its own); new htmlwide doc asserts wide blocks stay clipped and the page never scrolls sideways.
All probes fail on bb4fb75's net and pass now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- scripts/e2e-mobile-guard.mjs (npm run test:browser:mobile-guard): Chromium + Firefox at 360/390/768/1280 over
exporter-built pages (navbar, columns, FeaturesGrid/Testimonials/NumberCounter/Gallery/Pricing/Reviews, big
image, long word/URL, wide customer HtmlBlocks). Asserts body.scrollWidth and every visible box/text right edge
(html,body{overflow-x:clip} hides overflow from documentElement.scrollWidth), stacking on phones, columns kept
on tablet, navbar wrap, intentional overflow-x:auto still scrolls, and 1280 box + pixel identity with the hooks
removed (optional cross-commit baseline).
- OLS home overflow was FeaturesGrid (inline repeat(3,1fr)), not an HtmlBlock. FeaturesGrid, Testimonials and
NumberCounter grids carry data-wsb-grid; the <=767px head block makes them one column.
- HtmlBlock output is exported inside <div data-wsb-html style="display:contents"> (applied after attr
injection, toHtml().html unchanged); on phones it becomes a max-width:100% sideways-scrolling box, media
shrink, long words break. body{overflow-wrap:break-word} on phones. Navbar link wrap now up to 991px
(8 links + long logo overflowed at 768).
- Head parity pins updated (mirror in WHP generateCompiledHTML); editor.css mirrors for the mobile frame.
- publish-checks: warn when an HtmlBlock's inline/<style> CSS sets widths, flex sizes or grids over 390px or a
3+ column grid with no phone @media rule; says what is not checked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reproduced in the e2e mock (now stateful: history + clear_history with the
server's 409 while a job is live): the normal two-step clear works (first
click arms the confirm by design), but a confirm while a job is live -- e.g.
the self-review follow-up that starts right after a send -- was refused by the
server and the modal showed the raw code 'job_in_progress', so the chat looked
like it would not clear on the first try. Now the server's sentence is shown,
and the button is disabled (with a tooltip) while a send, resume or self-review
is running. e2e scenarios 9, 9b (failed before), 9c (Firefox + Chromium).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The render is uploaded with the new review_render_upload action (backend kind
review_render, the only image a review job accepts); mocks follow.
- summarizeCanvas() never lets a newline or a quote into a node line (the server
validates review summaries line by line); test.
- test:browser:sitesmith-cleaned (Firefox + Chromium): a page published from a
cleaned ordinary Sitesmith response requests nothing outside the allow-listed
hosts (own assets, fonts, Font Awesome CDN, images.pexels.com, placehold.co)
and has no inline handlers or javascript:/data: URL attributes; the same
response uncleaned is the control (requests example.com). All requests are
answered or aborted locally.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the release branch mobile-parity's <=767px head rules land before the
colour merge, so 'identical to ab7a100' cannot hold; run with
COLOR_BASE_COMMIT=<commit before the colour merge> there. Measured: vs ab7a100
the only difference is those two documented rules.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The self-review follow-up sends intent:'review' + review_image_ids instead of
putting the render in mockup_ids, so the backend builds a critique+patch
instruction instead of 'build toward this mockup'. Render still deleted after.
- types/sitesmith.ts: SitesmithStatus = server statuses (adds IMAGES_BUSY,
TOO_MANY_JOBS, SERVER_BUSY, ...) + client statuses (TIMEOUT, ABORTED, ...);
network status strings are narrowed with a type guard (unknown -> ERROR);
the hook builds SendResultErr without casts.
- statusMessages.ts: one specific message per status (a Record over the union,
so a new status without a message fails tsc), used by the modal.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts: package.json (kept both test:browser:mobile and the colour scripts);
head-parity test (kept E_icons from mobile-parity/main AND the E_brand_swatches /
F_swatches_no_brand cases from color-system; pinned bytes re-derived later
against the merged WHP script).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
npm run test:browser:mobile; MOBILE_PARITY_RECORD/BASELINE compare desktop boxes against a base-commit snapshot.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Found by the real-editor e2e: picking over the header or its logo box returned the page colour behind it.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
craft/scripts/gen-sitesmith-catalog.mjs emits a compact JSON catalog (props with
TS types/enums/defaults, token-aware colour props, animation + icon support,
child rules, section recipes, templates, palettes) for the WHP panel prompt.
src/sitesmith/catalog.drift.test.ts regenerates and compares to the committed
whp file so drift fails.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Published pages/Preview ship a head reset (*{margin:0;padding:0}, img block,
a inherit, body line-height 1.6 / #1f2937 / 16px); the editor canvas had none,
so h1-h6/p margins, line-height and inherited size/colour differed.
- editor.css: one commented block scoping the reset to .canvas-device-frame,
every selector zero-specificity (:where) so inline styles / class rules win
exactly as when published; editor chrome untouched (snapshot identical).
- tokens.ts: canvas heading/link token rules made zero-specificity too.
- utils/css-reset.ts: single source for the reset declarations (html-export
imports it; test asserts byte-identity with the backend head and that the
editor.css block carries the same declarations).
- scripts/e2e-canvas-parity.mjs (npm run test:browser:canvas): real editor vs
published HTML in Firefox + Chromium, boxes within 1px + computed styles.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
HeroSimple emitted 'background' together with backgroundImage/Size/Position
that were undefined. React assigns '' for those on first render and Chromium
and Firefox then wipe the shorthand's gradient (background-image: none), so
the canvas hero rendered white. Build the style with only the relevant keys
(shared by render and toHtml); exported HTML is byte-identical.
Adds a real-browser (Chromium+Firefox) regression test: npm run test:browser:bg,
and toHtml byte-identity assertions.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>