A. A page/site replace lands on the canvas in loadState's deferred
deserialize, so apply() returned before the canvas changed and the
self-review composed, captured and summarised the PREVIOUS page (the live
eval: every review said "the render doesn't match the page I built").
PageContext.whenCanvasSettled() resolves when the requested load really
ran; apply() awaits it (bounded, 5 s) and returns the applied fingerprint
(from the response tree for replaces). The review checks the live canvas
against it right before composing and skips with a plain note otherwise.
Header/footer/title come from latestChrome() (refs updated in the same
tick), not the modal's one-render-old context.
B. Review outcomes are fixed / fine / mismatch / not-applied / skipped and
quote the model. An empty patch where the model says the render was not
its page is "did not work this time", never "found nothing to fix"; an
ask is not a review; ops naming missing nodes are "none could be
applied" (applyPatch now counts ops that took effect).
E. Pictures from earlier sessions are labelled "saved earlier" and never
preselected; a line above the box lists what goes with THIS message;
multi-file uploads select every file. Clear chat says it deletes the
site's whole conversation for everyone (one thread per site, backend).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The backend now ends a job with RESPONSE_TOO_LONG when the model answer is
cut off at the output cap twice. Without the status in the union it fell
back to the generic ERROR text; now it says nothing was changed and to ask
for one page or section at a time.
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>
Review findings on clean-ai-response.ts:
1. update_props is now cleaned with the TARGET node's component (resolved on the
live canvas by apply()); an unresolvable target gets the strictest rules
(no code, url/src only assets/). code is kept only on an HtmlBlock.
2. cssValueAllowed() is a positive grammar: numbers/lengths/hex/keywords, an
allow-list of functions (gradients, colour functions, calc/clamp, var(--*),
transforms, filters), url() only of assets/<file>, quoted strings only plain
font names; image-set(, src(, cross-fade(, element(, paint(, attr(, env(,
':' or '//' outside url(assets/..), backslashes and control chars rejected.
Regression: every style/colour value in the templates, section families and
palettes passes (clean-ai-response.ordinary.test.ts).
Also: tricky link forms (//host, backslashes, whitespace/control chars,
mixed-case schemes, > 2048) rejected; comments state the server-side mirror is
not implemented (deferred).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
clean-ai-response.ts runs first in useApplyAiResponse().apply() and in
applyRegeneratedSection(): link props allow ''|#anchor|relative|mailto:|tel:|
https:|http: (never //host, backslash, whitespace/control chars, > 2048);
images only assets/<file> or https on images.pexels.com / placehold.co;
videos assets/ or YouTube/Vimeo; the model can never set form destinations,
relay settings or head code (patches keep the user's values); style names
must be CSS identifiers and values may not carry url() (except assets/),
@import, expression(, behavior, -moz-binding, javascript:, < > { } ; \;
anchor ids [A-Za-z0-9_-]. HtmlBlock code keeps its existing sanitisers.
Export boundary: stripLegacyCssVectors() in cssValue/cssPropsToString
(byte-identical for ordinary content: OLS fixture + default design measured).
Server-side mirror not implemented (deferred).
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>
captureFileViolations() pins the one allow-same-origin srcdoc frame: sandbox set
once to exactly allow-same-origin, no script/form/popup/navigation tokens,
srcdoc only from inlineResources(clean.html), sanitizeCaptureHtml called and its
verdict checked before srcdoc. Eight mutated copies of the real source prove
each rule fails. Plus a behavioural jsdom test of the real captureHtml frame.
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>
buildDesignPayload numbered brand colours by list position while the page
publishes them as --wsb-brand-<slot>, so the prompt could name a different
colour than the page renders. It now uses publishedBrandSwatches (same rule as
the publisher) and sends {name?, hex, slot}.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The colour branch pinned the brand head before the <=767px stacking/nav-wrap
rules existed; the bytes are now copied from the merged WHP
generateCompiledHTML output (WHP release/sitesmith-colors).
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>
- <=767px: [class^=cols_] wrappers (every ColumnLayout split) stack; a mobile flexDirection:row override still opts out.
- <=767px: navbars without a hamburger wrap instead of overflowing.
- wrapInDocument emits the same Font Awesome links as the backend sb_font_awesome_link (Preview is a sandboxed srcdoc iframe).
- head-parity pins updated (+ E_icons case).
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>