Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit 44d804ada316d06b43da1f78d0c8361cb879dc55
parent 63a88a5d059cee15354771f1c73b4aeed9f60d78
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Mon, 28 Sep 2026 19:52:58 -0400

common, export, homepage: each social key clips its icon's paint and a refused icon's label is bounded; e2e proves the parser invariant and that nothing is fetched (re-review R1, R2, R4, R7)

- R4: a key (the homepage's row, the export footer's links) is
  overflow-hidden with contain: paint, so an icon paints in its box only; the
  focus ring is the key's own shadow, outside that clip.
- R7: a refused icon's text fallback is capped at 10rem with an ellipsis, its
  full label in title.
- svg-vectors.spec: every accepted vector and real shape, rendered as a page
  renders it and parsed as a whole document by Chromium, leaves the element
  after the link outside the icon, runs nothing and requests nothing.
- social.spec: the hostile stored icons now include the eight that loaded
  from elsewhere; the page makes no request to another origin. The e2e's
  gradient icon is the regression shape.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Diffstat:
Mcommon/components/SocialLinks.tsx | 9+++++++--
Mexport/app/components/Footer.tsx | 5+++--
Mhomepage/e2e/fixture-social.ts | 12+++++-------
Mhomepage/e2e/social.spec.ts | 12++++++++++--
Ahomepage/e2e/svg-vectors.spec.ts | 42++++++++++++++++++++++++++++++++++++++++++
5 files changed, 67 insertions(+), 13 deletions(-)

diff --git a/common/components/SocialLinks.tsx b/common/components/SocialLinks.tsx @@ -34,10 +34,14 @@ export type SocialLinksPlacement = "header" | "footer"; // Renders nothing when there is nothing to show, so a caller can drop the // column or the group around it on the same condition. // Complete literal class strings (Tailwind v4 scans them as written). +// A key clips what it holds (`overflow-hidden`, `contain: paint`): an icon +// paints inside its 36 px box and nowhere else. The focus ring is the key's +// own box-shadow, outside that clip. A refused icon's text fallback is capped +// at 10rem and ends in an ellipsis, its full label in `title`. const KEY = - "inline-flex size-9 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring not-forced-colors:focus-visible:outline-none pointer-coarse:size-11 [&_svg]:size-5 [&_svg]:shrink-0"; + "inline-flex size-9 shrink-0 items-center justify-center overflow-hidden [contain:paint] rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring not-forced-colors:focus-visible:outline-none pointer-coarse:size-11 [&_svg]:size-5 [&_svg]:shrink-0"; const TEXT_KEY = - "inline-flex h-9 shrink-0 items-center rounded-md px-2 text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring not-forced-colors:focus-visible:outline-none pointer-coarse:h-11"; + "inline-block h-9 max-w-40 shrink-0 truncate rounded-md px-2 text-sm leading-9 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring not-forced-colors:focus-visible:outline-none pointer-coarse:h-11 pointer-coarse:leading-[2.75rem]"; export function SocialLinks({ links, @@ -84,6 +88,7 @@ export function SocialLinks({ target="_blank" rel="noopener noreferrer" data-social-icon-refused="" + title={link.label} className={TEXT_KEY} > {link.label} diff --git a/export/app/components/Footer.tsx b/export/app/components/Footer.tsx @@ -107,7 +107,7 @@ export default function Footer() { aria-label={link.label} target="_blank" rel="noopener noreferrer" - className="inline-block w-5 h-5 text-muted-foreground hover:text-brand transition-colors [&_svg]:w-full [&_svg]:h-full" + className="inline-block w-5 h-5 overflow-hidden [contain:paint] text-muted-foreground hover:text-brand transition-colors [&_svg]:w-full [&_svg]:h-full" dangerouslySetInnerHTML={{ __html: sizeSocialSvg(svg) }} /> ) : ( @@ -115,7 +115,8 @@ export default function Footer() { href={link.url} target="_blank" rel="noopener noreferrer" - className="text-muted-foreground hover:text-brand transition-colors" + title={link.label} + className="inline-block max-w-40 truncate align-bottom text-muted-foreground hover:text-brand transition-colors" > {link.label} </a> diff --git a/homepage/e2e/fixture-social.ts b/homepage/e2e/fixture-social.ts @@ -3,6 +3,7 @@ import { normalizeSocialSvg, type SocialLink, } from "../../common/lib/settingsSchema"; +import { REAL_SHAPES } from "../../common/lib/socialSvg.vectors"; // THE HOMEPAGE E2E'S SOCIAL LINKS, never the operator's. // @@ -35,13 +36,10 @@ import { export const FIXTURE_SETTINGS_NAME = ".e2e-settings.json"; -const LEAF = - `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">` + - `<defs><linearGradient id="leaf-grad" x1="0" y1="0" x2="0" y2="1">` + - `<stop offset="0" stop-color="#E0A030"/><stop offset="1" stop-color="#1C9A5B"/>` + - `</linearGradient></defs>` + - `<path fill="url(#leaf-grad)" d="M12 2c4.9 3.6 8 7.6 8 11.3A8 8 0 0 1 4 13.3C4 9.6 7.1 5.6 12 2z"/>` + - `<circle fill="#333333" cx="12" cy="15" r="2.4"/></svg>`; +// The gradient icon is the checker's regression shape (socialSvg.vectors.ts +// REAL_SHAPES): a drawing program's root, a gradient with `style` stops, a dark +// outline under the body (`paint-order`), a negative-origin viewBox. +const LEAF = REAL_SHAPES.gradient_outlined; const BUBBLE = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">` + diff --git a/homepage/e2e/social.spec.ts b/homepage/e2e/social.spec.ts @@ -10,6 +10,7 @@ import { writeRawFixtureSettings, } from "./fixture-social"; import { themeToggle } from "./helpers"; +import { ADVERSARIAL, LOADS_ELSEWHERE } from "../../common/lib/socialSvg.vectors"; // THE SOCIAL ROW: in the header at every width (at most four links), and in the // footer (every link). The links are the e2e's own (fixture-social.ts): Leaf (a @@ -396,15 +397,21 @@ test("no links: no row in the header, and no Elsewhere column in the footer", as // A settings.json edited by hand (never through a save) holding icons that // would run script: none is inlined, none runs, and each link still renders — // as its label — while the good icons beside them render as icons. -test("a stored icon that would run script is not inlined: no script runs, and the link shows its label", async ({ +test("a stored icon that would run script or load from elsewhere is not inlined: nothing runs, nothing is fetched, the link shows its label", async ({ page, }) => { - const hostile = FIXTURE_HOSTILE_SVGS.map((svg, i) => ({ + const hostile = [...FIXTURE_HOSTILE_SVGS, ...LOADS_ELSEWHERE.map((n) => ADVERSARIAL[n])].map((svg, i) => ({ label: `Hostile ${i + 1}`, url: `https://hostile-${i + 1}.example/`, svg, })); writeRawFixtureSettings(settingsFile(), [...FIXTURE_SOCIAL_TRIO.slice(0, 1), ...hostile]); + const elsewhere: string[] = []; + page.on("request", (r) => { + if (new URL(r.url()).origin !== new URL(page.url() || "http://localhost").origin && !r.url().startsWith("data:")) { + elsewhere.push(r.url()); + } + }); await page.goto("/"); await page.waitForLoadState("load"); await page.waitForTimeout(300); @@ -419,4 +426,5 @@ test("a stored icon that would run script is not inlined: no script runs, and th // The good icon beside them is still an icon. await expect(footer.getByRole("link", { name: "Leaf", exact: true }).locator("svg")).toHaveCount(1); expect(await page.evaluate(() => document.querySelectorAll("[data-social-links] img, [data-social-links] image").length)).toBe(0); + expect(elsewhere.filter((u) => !u.startsWith(new URL(page.url()).origin)), "requests to another origin").toEqual([]); }); diff --git a/homepage/e2e/svg-vectors.spec.ts b/homepage/e2e/svg-vectors.spec.ts @@ -0,0 +1,42 @@ +import { test, expect } from "@playwright/test"; +import { ADVERSARIAL, REAL_SHAPES } from "../../common/lib/socialSvg.vectors"; +import { safeSocialSvg, scopeSvgIds, sizeSocialSvg } from "../../common/lib/socialLinks"; + +// THE INVARIANT THE READ PATH DEPENDS ON, checked with Chromium's own HTML +// parser: for every icon the checker accepts (the review's adversarial battery +// and the shapes real icons take), rendered as a page renders it and parsed as +// a whole document the way the static HTML carries it, the element after the +// link stays OUTSIDE the icon, no script runs, and nothing is requested from +// anywhere. + +const accepted = Object.entries({ ...ADVERSARIAL, ...REAL_SHAPES }) + .map(([name, raw]) => { + const safe = safeSocialSvg(raw); + return safe ? { name, html: sizeSocialSvg(scopeSvgIds(safe, "sl_S_1_-0")) } : null; + }) + .filter((x): x is { name: string; html: string } => x !== null); + +test("every accepted icon leaves the page after it outside the icon, runs nothing and fetches nothing", async ({ + page, +}) => { + expect(accepted.length).toBeGreaterThan(10); + const requests: string[] = []; + await page.route("**/*", (route) => { + requests.push(route.request().url()); + return route.abort(); + }); + for (const { name, html } of accepted) { + await page.setContent( + `<!doctype html><html><body><header><a id="l" href="#x">${html}</a><b id="after">after</b></header><main id="main">m</main></body></html>`, + ); + await page.waitForTimeout(50); + const r = await page.evaluate(() => ({ + afterInSvg: !!document.getElementById("after")?.closest("svg"), + afterHolder: document.getElementById("after")?.parentElement?.tagName ?? null, + mainInSvg: !!document.getElementById("main")?.closest("svg"), + x: (window as unknown as { __x?: unknown }).__x ?? null, + })); + expect(r, name).toEqual({ afterInSvg: false, afterHolder: "HEADER", mainInSvg: false, x: null }); + } + expect(requests, "requests").toEqual([]); +});