Commit Graph
447 Commits
Author SHA1 Message Date
shadowdaoandClaude Opus 5.5 52b453eecc fix(sitesmith): honest self-review wording when changes were dropped or not applied
Live 2026.11.8: the server's review lockdown stripped every op, and the editor said
"made no changes. Sitesmith said: Image band narrowed..." (a fix that never happened). With
the backend's review_ops {proposed, kept}:
- all dropped -> new status "dropped": "Sitesmith proposed N changes, but none passed the
  safety checks, so nothing on your page was changed. What it proposed (not applied): ..."
- some dropped -> the "made fixes" note says how many were dropped and labels the quote as
  possibly describing them;
- not-applied / claimed-no-ops quotes are labelled as not applied instead of "Sitesmith said".
"Sitesmith said" is kept only where the quote describes what actually happened.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 18:33:02 -08:00
shadowdaoandClaude Opus 5.5 274ae9fc33 fix(sitesmith): placehold.co backgrounds lose their label (it covered the hero headline)
Live case 5 (2026.11.8): BackgroundSection.bgImage =
placehold.co/1920x1080/101a2b/fbfaf7?text=Team+collaborating+... printed the label across
the hero headline on the published page and in the review render. The allow-list cleaner now
rewrites a placehold.co URL used as a background (bgImage, backgroundImageUrl, CallToAction
bgValue, ContentSlider slide imageSrc) to https://placehold.co/WxH/BG/BG (no query, text colour
= box colour, so neither the label nor the "W x H" fallback shows). Runs after the host
allow-list; ordinary image placeholders keep their label.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 18:31:12 -08:00
shadowdaoandClaude Opus 5.5 2dcf8b7feb fix(sitesmith): a partial whole-site build never offers a bare destructive "Replace site"
Live case 2 (2026.11.8): the daily limit stopped a site build after page 1, the dialog still
said "Replace your entire site?" and accepting deleted the owner's other page. The server now
reports site_build {planned, built, missing}; when pages are missing the confirm becomes
"Sitesmith built only N of M pages" with:
- "Add these pages" (default focus): each built page replaces the page at the same address,
  new ones are appended, every other page is kept (PageContext mergeAiPages / addAiPages), and
  the new header is repaired to link the kept pages too;
- "Replace site and delete K pages" with the names it deletes (only when K > 0);
- Cancel.
A complete site build keeps "Replace your entire site?" and now names the pages it deletes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 18:29:56 -08:00
shadowdaoandClaude Opus 5.5 37935abad8 fix(sitesmith): judge ContactForm labels against the form's own card, not the section
The ContactForm component paints its own card (default style.backgroundColor = bg token).
Contrast repair judged labelColor against the section behind it, so white labels on the
white default card (inside a dark section) went unrepaired (eval r3 case 6, ttm-likes).
A ContactForm with no style prop now resolves to the bg token; an explicit style without
a background stays transparent and is still judged against the section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 18:20:58 -08:00
shadowdaoandClaude Opus 5.5 69568e5979 docs: Sitesmith design spec (was untracked) with the amended per-send call cap for page-by-page site builds
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 17:31:16 -08:00
shadowdaoandClaude Opus 5.5 04c0b0a08a sitesmith capture: show the whole page as side-by-side strips (max 9000 px)
The capture stopped at 3000 CSS px, so the reviewer never saw the bottom or footer. Now the full page
height H is rendered at its normal width (1280). H <= 3000: one image, as before. Otherwise
N = min(3, ceil(H/3000)) equal-height strips (each <= 3000 px) are laid out side by side, left to
right (strip 1 = page top), with 24 px #808080 gutters, in one JPEG; H > 9000 shows the top 9000 px
(cropped). Geometry is the pure stripGeometry() (unit-tested); JPEG quality steps 0.8 -> 0.5 before
"too large" (3 MB cap, server limit 8 MB). CaptureResult gains strips/pageHeight; runSelfReview
returns them and says when only the top of a long page was reviewed. Real eval pages: 2 strips,
~0.8-1.1 s, 150-270 KB in Chromium and Firefox; e2e checks strip order/alignment/gutters/cropping.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 17:27:56 -08:00
shadowdaoandClaude Opus 5.5 41d0721028 sitesmith capture: draw placehold.co images locally as labelled boxes
The capture never fetches external images, so placehold.co placeholders came out as a blank grey box
(Chromium) or alt text (Firefox) and the reviewer reported a false "screenshot mismatch". checkUrl now
maps a strictly parsed placehold.co URL (https, host exactly placehold.co, /WxH with 1-4 digits, up to
two 3/6-digit hex colours, optional image extension) to a locally generated SVG data URL of the same
intrinsic size: URL bg colour (default #cccccc), ?text= label (default "W x H") in the fg colour
(default #969696), XML-escaped. Off-spec placehold.co URLs become a plain #cccccc box (no label), or
the old 1x1 grey image when the size is unknown. Works for <img> (own CSS: width, aspect-ratio,
object-fit) and CSS url() backgrounds. Only an SVG byte-identical to our generator passes the
second sanitiser pass. makePolicy/allow-list, sandbox and CSP unchanged; fetchBounded refuses
placehold.co outright. e2e: fidelity samples the boxes/labels, security asserts zero placehold.co
requests (incl. hostile ?text=) in Chromium and Firefox.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 17:24:06 -08:00
shadowdaoandClaude Opus 5.5 9781e02043 sitesmith: report-only check that the primary CTA uses the accent colour; modal wiring
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 17:21:14 -08:00
shadowdaoandClaude Opus 5.5 65d388ff89 sitesmith: a replacing header keeps links to existing pages; accurate chrome dialog
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 17:21:14 -08:00
shadowdaoandClaude Opus 5.5 201486f357 sitesmith: site_pages in requests, per-page build phases, 10 minute cap
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 17:21:14 -08:00
shadowdaoandClaude Opus 5.5 28119f0666 sitesmith capture: keep a var() border shorthand that a border-top overrides
`border:1px solid var(--x); border-top:4px solid ...` (the AI's accent-top card) lost its left/right/
bottom sides in the self-review capture in Chromium AND Firefox: once a later declaration overrides
one longhand, the CSSOM can no longer serialise the var() shorthand (it and its remaining longhands
read back as ""), so the 4c1c8db recovery had nothing to emit. Recover such shorthands from the
declaration block's source text (inline value; innermost blocks of a sheet, matched to rules by
cssText), re-parse each ALONE through the CSSOM and judge it with the unchanged value/URL gate.
e2e-capture-fidelity gains the card (var/var, var/literal, stylesheet) sampled on all four sides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 17:19:07 -08:00
shadowdaoandClaude Opus 5.5 6e48cb82b7 sitesmith: form text repairs pick neutrals; note when uploads were not used
- contrast repair: ContactForm labelColor / inputTextColor are repaired to a
  neutral (text/bg/muted token, black, white), never the accent. Measured on
  the ttm-likes eval response in Chromium: before, dark typed text, UA-grey
  placeholders and an invalid border on the dark inputs; after, white text,
  placeholders at 70% of it, visible border.
- a page/site build that shows none of the owner's offered uploads gets an
  honest note (the brief now requires one in the hero or a feature).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:43:01 -08:00
shadowdaoandClaude Opus 5.5 ed50732d58 sitesmith: readable form inputs on dark sections + sRGB color-mix() in contrast repair
Eval 2026-11-01 (ttm-likes): a ContactForm on a dark band had dark typed text
and UA-grey placeholders on a color-mix() input surface, and its inputBorder
"1px solid X" came out as the invalid "1px solid 1px solid X".

- ContactForm: new inputTextColor prop (typed text); placeholders now follow
  the input's own text colour via a scoped form[data-wsb-form] ::placeholder
  rule (export + editor canvas); an inputBorder that is a whole border is used
  as-is. Without inputTextColor the legacy derivation is unchanged.
- contrast repair: resolves opaque color-mix(in srgb, A p%, B) surfaces (the
  brief prescribes these tints; translucent / other spaces stay unchecked),
  checks ContactForm labelColor against its section and the derived input text
  colour against an explicitly set inputBg, writing inputTextColor on failure.
- catalog generator: (rebased onto main 01a78c9, which already omits the
  clock-derived Countdown.targetDate default via VOLATILE_DEFAULTS + the
  date-frozen drift test; this commit's duplicate inline check was dropped.)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:43:01 -08:00
shadowdaoandClaude Opus 5.5 0c9faf0495 sitesmith: AI-built pages render full-bleed (page root padding 0)
The page root Container's default 20px padding framed every AI-built section
in a white margin (visible in every eval screenshot). New AI page/site
replaces now set padding 0 on the page root unless the model set padding
itself. Saved pages, section inserts, patches and the Container default are
unchanged, so existing sites do not move.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:42:49 -08:00
shadowdaoandClaude Opus 5.5 0fe7f07162 sitesmith: a page request replaces the page, never the whole site
Eval 2026-11-01: "Build my home page following the layout of the attached
mockup" came back as scope "site", and confirming replaced every page. The
editor now decides from what the owner asked (sitesmith/replace-plan.ts):

- scope "site" is honoured (with the existing confirm) only for an explicit
  whole-site request ("build me a website", "the whole site", "every page",
  "a 4 page site"); otherwise the first page replaces the current page and a
  note says so.
- the shared header/footer are replaced with a page only when they are still
  the untouched starter ones ("MySite" + "Get Started") AND the owner says yes
  in a new dialog mode ("Use the new header and footer" / "Keep my header and
  footer"); customised chrome is always kept, with a note.
- the editor sends chrome_default:true so the backend can let the model
  restyle the starter chrome on a page request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:42:49 -08:00
shadowdaoandClaude Opus 5.5 648d6a3a51 sitesmith self-review: never present a claimed fix that sent no ops
An empty patch whose message claims a change ("Fixed the hero ...") is now
status 'claimed-no-ops' with a warning note: "Sitesmith's reply describes a fix,
but it sent no changes, so nothing on your page was changed." The structured
render_mismatch flag from newer backends is read first; the existing wording
check still covers older replies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:42:49 -08:00
shadowdaoandClaude Opus 5.5 4c1c8db97e sitesmith self-review: keep var() shorthands (background/border) in the capture
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>
2026-11-01 16:42:49 -08:00
shadowdaoandClaude Sonnet 5.5 01a78c9b3d catalog: omit the wall-clock-derived Countdown.targetDate default so the generated catalog (and drift test) no longer flip daily
Add a date-independence test (freezes Date at two different days via a preload).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 16:28:48 -08:00
shadowdaoandClaude Opus 5.5 96670b94f8 Legacy grids: published head stacks pre-hook card grids on phones (no re-save)
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>
2026-11-01 16:26:11 -08:00
shadowdaoandClaude Opus 5.5 322742aa81 e2e-sitesmith: follow the honest Clear chat confirm label
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 15:38:54 -08:00
shadowdaoandClaude Opus 5.5 96274a6aa8 fix(sitesmith): contrast repair resolves the real background (gradients, shorthand, section props)
Contrast auto-repair read only style.backgroundColor / bgColor, so white text on a
gradient hero (style.background) was "repaired" against the white page colour.

- Resolve style.background (solid shorthand, gradient, url), HeroSimple bgGradient*/
  bgColor defaults, CallToAction bgValue, BackgroundSection bgColor, opaque image overlay.
- Gradients: every stop is checked, worst stop decides; unparseable stops (color-mix,
  unresolved token, translucent, layered) = not checked. An unresolvable background no
  longer falls through to an outer/page colour.
- A repair is applied only if it reaches the target and strictly beats the old colour
  against the worst stop.
- clamp() font sizes use their minimum for the large-text test.
- Patch path: ancestor background now comes from effectiveBackgroundOf (which learns
  style.background shorthand and section props) instead of a private walker.
- Tests: real ttm-likes and ols-home responses as fixtures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 15:31:34 -08:00
shadowdaoandClaude Opus 5.5 c71a6b64a6 Fix ImageBlock (any non-Container) directly inside ColumnLayout exporting an empty div
Craft's <Element id=col-N is={Container}> only adopts an existing linkedNode whose
type is Container; otherwise it creates a fresh empty Container and orphans ours.
flattenTreeForCraft now wraps a non-Container column child in a pre-created Container.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 15:31:34 -08:00
shadowdaoandClaude Opus 5.5 2b82424d7f Sitesmith: self-review sees the page it just built, reports honestly; per-message pictures
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>
2026-11-01 15:31:34 -08:00
shadowdaoandClaude Opus 5.5 99d5855f6a Sitesmith: RESPONSE_TOO_LONG status gets its own message
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>
2026-11-01 15:31:34 -08:00
shadowdaoandClaude Opus 5.5 542f5efc78 Mobile guard: drop the HtmlBlock table rule; colour e2e baseline = first 2026.11.4-head commit
- 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>
2026-11-01 15:16:53 -08:00
shadowdaoandClaude Opus 5.5 cfeeef37ce Mobile guard: HtmlBlock wrapper never gets a box; phone net is media + per-table/pre scroll only
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>
2026-11-01 15:09:22 -08:00
shadowdaoandClaude Opus 5.5 bb4fb7548b Mobile guard: real-browser overflow suite, card-grid + HtmlBlock mobile hooks, wide custom HTML publish warning
- 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>
2026-11-01 14:58:39 -08:00
shadowdaoandClaude Opus 5.5 9f9be5f41a Sitesmith modal: Clear chat explains a refusal and is disabled while a job runs
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>
2026-11-01 12:16:59 -08:00
shadowdaoandClaude Opus 5.5 465cf17bb0 Sitesmith: self-review render via review_render_upload; one-line-per-node summaries; real-browser check of cleaned output
- 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>
2026-11-01 11:47:04 -08:00
shadowdaoandClaude Opus 5.5 4a34781ab6 Sitesmith cleaner: patches use the target component's rules; CSS values checked against a positive grammar
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>
2026-11-01 11:38:14 -08:00
shadowdaoandClaude Opus 5.5 2b8b0557b2 Sitesmith: allow-list cleaner for model responses before they reach the canvas; export strips attack-only CSS (G8)
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>
2026-11-01 11:31:42 -08:00
shadowdaoandClaude Opus 5.5 93b3cc35af Colour e2e: baseline commit overridable (COLOR_BASE_COMMIT)
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>
2026-11-01 11:23:17 -08:00
shadowdaoandClaude Opus 5.5 a113f25a62 Guard test: the capture-frame exception now fails on allow-scripts, a skipped or ignored sanitiser, or src= (G7)
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>
2026-11-01 11:10:30 -08:00
shadowdaoandClaude Opus 5.5 6c406ca99b Sitesmith: self-review is intent 'review' (not a mockup); typed statuses with specific messages (G3, G6)
- 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>
2026-11-01 11:10:30 -08:00
shadowdaoandClaude Opus 5.5 7a58921ba3 Sitesmith design payload: brand colours carry their published slot (G1)
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>
2026-11-01 11:10:30 -08:00
shadowdaoandClaude Opus 5.5 3413c7816d Head parity: re-pin E_brand_swatches after the mobile-parity merge
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>
2026-11-01 10:56:02 -08:00
shadowdaoandClaude Opus 5.5 9f48b8b134 Merge feat/sitesmith-catalog into release/sitesmith-colors
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 10:55:05 -08:00
shadowdaoandClaude Opus 5.5 ae092990ca Merge feat/sitesmith-ui into release/sitesmith-colors
Conflict: craft/package.json scripts (kept mobile, colour and sitesmith e2e entries).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 10:55:05 -08:00
shadowdaoandClaude Opus 5.5 57734fbe51 Merge feat/sitesmith-hook into release/sitesmith-colors
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 10:54:46 -08:00
shadowdaoandClaude Opus 5.5 6decf8cdb0 Merge feat/color-system into release/sitesmith-colors
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>
2026-11-01 10:54:46 -08:00
shadowdao f6b90d393f Merge fix/mobile-parity into release/sitesmith-colors 2026-11-01 10:54:16 -08:00
shadowdaoandClaude Sonnet 5.5 cfa8ab2e40 Sitesmith modal: load assets once; clearer oversize-image message
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 10:52:56 -08:00
shadowdaoandClaude Sonnet 5.5 304850fcd3 Sitesmith e2e: Firefox + Chromium end-to-end test of the modal (npm run test:browser:sitesmith)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 10:48:12 -08:00
shadowdaoandClaude Sonnet 5.5 a406daae1e Sitesmith e2e: reusable mock editor + Sitesmith backend server for real-browser tests
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 10:48:12 -08:00
shadowdaoandClaude Sonnet 5.5 8a481a87d8 Sitesmith modal: flow tests (phases, cancel, resume, errors, vision off, sites/assets payload, self-review round trip)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 10:43:23 -08:00
shadowdaoandClaude Sonnet 5.5 784d85346e Sitesmith: tests for payload, design, attachments, panels, self-review runner
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 10:40:55 -08:00
shadowdaoandClaude Sonnet 5.5 79159dcb28 Sitesmith modal: live job status, cancel, resume, attachments, Sites I like, asset awareness, self-review toggle, design in the request
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 10:39:47 -08:00
shadowdaoandClaude Sonnet 5.5 f0b3dd1dba Sitesmith capture: CSS judged via CSSOM (no regex), single URL gate (no % / dot segments / backslash), all resources inlined through a bounded no-credentials fetch, CSP img-src data: only; bypass-class proof tests (unit + Firefox/Chromium)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 10:37:56 -08:00
shadowdaoandClaude Sonnet 5.5 c4f45541a3 Sitesmith: contrast repair wired into apply-ai-response; scoped guard exception for the script-less capture frame
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 10:32:50 -08:00
shadowdaoandClaude Sonnet 5.5 197b9c46ab Sitesmith self-review capture: sanitise page HTML before it reaches the frame (inert DOMParser allow-list, fixed-point guard), CSP + script-less sandbox, real-browser hostile-page proof; UI building blocks (attachments, sites I like, asset awareness, design payload)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 10:31:29 -08:00