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>