From ebfab9f10221387c97cbf0cc8db92af0b6be6c4e Mon Sep 17 00:00:00 2001 From: Leonid Svyatov Date: Tue, 4 Aug 2026 16:43:05 +0300 Subject: [PATCH 1/2] fix(site): derive every homepage rule claim from the standard The rule contract panel was transcribed by hand. Under the heading "Read the bar before you adopt it", it gave R-CI-01's Check as "The workflow declares both trigger paths." The real Check turns on a pull_request trigger carrying no branches: filter, and says outright that a configuration carrying one fails. So the panel told a repository with branches: [main] that it passed, and the one element on the page offering verification was the one element nothing verified. The workflow trace card carried the same defect against R-SEC-01, dropping the composite actions the rule also covers. Both panels now read their fields from the parsed standard, including the fixing skill and forge scope that happened to be right. leadClause quotes the first sentence of a Check where a panel is too small to hold the whole clause, which is the only abridgement that cannot say something the standard does not, and each panel links to the full text. tests/homepage-claims.test.ts holds the sources to deriving what they quote, rather than to matching a string that would need updating with the rule. --- site/scripts/generate.mjs | 13 ++++ site/src/content/docs/index.mdx | 29 ++++++--- site/src/lib/content.mjs | 9 +++ tests/homepage-claims.test.ts | 107 ++++++++++++++++++++++++++++++++ 4 files changed, 148 insertions(+), 10 deletions(-) create mode 100644 tests/homepage-claims.test.ts diff --git a/site/scripts/generate.mjs b/site/scripts/generate.mjs index e675e41..f7b7835 100755 --- a/site/scripts/generate.mjs +++ b/site/scripts/generate.mjs @@ -84,6 +84,19 @@ export function parseRules(text) { }) } +/** + * The first sentence of a rule's Check, for the two homepage panels that are + * too small to hold the whole clause. Quoting a prefix is the only abridgement + * that cannot say something the standard does not: the panel used to carry a + * hand-written summary of R-CI-01's Check, and it dropped the `branches:` + * filter condition, so it told a repository carrying one that it passed. + * @param {string} check + */ +export function leadClause(check) { + const end = check.indexOf(". ") + return end === -1 ? check : check.slice(0, end + 1) +} + /** @param {Record} fields @param {string} body */ export function frontmatter(fields, body) { const head = Object.entries(fields) diff --git a/site/src/content/docs/index.mdx b/site/src/content/docs/index.mdx index 2b6e598..4f8f384 100644 --- a/site/src/content/docs/index.mdx +++ b/site/src/content/docs/index.mdx @@ -13,7 +13,7 @@ tableOfContents: false --- import DomainRack from "../../components/DomainRack.astro"; -import { rules, sampleRule } from "../../lib/content.mjs"; +import { FORGE_LABEL, contractRule, leadClause, rules, sampleRule } from "../../lib/content.mjs"; @@ -50,10 +50,12 @@ import { rules, sampleRule } from "../../lib/content.mjs"; Observable evidence {/* The rule's own Check line runs to 400 characters, which in a card this size is a wall nobody reads and which forced the other three cards to - match its height. The leading clause is quoted here and the link goes - to the whole thing. */} -

Every external uses: line resolves to a 40-character commit SHA.

- Read the full check + match its height. Its first sentence is quoted, and the link goes to + the whole thing. The sentence used to be written out here in a shorter + form of its own, which read as a quotation and was not one: it dropped + the composite actions the rule also covers. */} +

{leadClause(sampleRule.check)}

+ Read the full check
  • 03 · Fix @@ -77,18 +79,25 @@ import { rules, sampleRule } from "../../lib/content.mjs";

    The standard states every current opinion as a numbered rule with a reason, a check, one fixing skill, and forge scope.

    Read all {rules.length} rules + {/* Every field here comes from the rule. The panel used to be transcribed by + hand, under a heading inviting the reader to check the bar before adopting + it, and its Check said "The workflow declares both trigger paths." The real + one turns on a `branches:` filter, which a configuration can carry and + still fail. Nothing failed when the two diverged, so the one element on the + page that offered verification was the one element nothing verified. */}
    Rule contract - R-CI-01 + {contractRule.id}
    Statement -

    CI runs on every push to the default branch and on every change request.

    +

    {contractRule.statement}.

    Check -

    The workflow declares both trigger paths.

    +

    {leadClause(contractRule.check)}

    + Read the full check
    - Fixed by oss-ci - GitHub and GitLab + Fixed by {contractRule.fixedBy} + {FORGE_LABEL[contractRule.forges]}
    diff --git a/site/src/lib/content.mjs b/site/src/lib/content.mjs index cd62642..c835d59 100644 --- a/site/src/lib/content.mjs +++ b/site/src/lib/content.mjs @@ -6,6 +6,10 @@ import readmeText from "../../../README.md?raw" import standardText from "../../../skills/oss-audit/STANDARD.md?raw" import { FORGE_LABEL, parseRules, skillSummaries } from "../../scripts/generate.mjs" +// Re-exported so a page quoting the standard reaches for one module rather than +// two, and so nothing on the homepage has a reason to transcribe a rule by hand. +export { FORGE_LABEL, leadClause } from "../../scripts/generate.mjs" + export const rules = parseRules(standardText) export const domains = [...new Set(rules.map((rule) => rule.section))].map((section) => { @@ -20,6 +24,11 @@ export const domains = [...new Set(rules.map((rule) => rule.section))].map((sect export const sampleRule = rules.find((rule) => rule.id === "R-SEC-01") +// The rule the homepage's contract panel takes apart. A second area, so the page +// does not show the same rule twice, and read from the standard rather than +// transcribed: see leadClause for what the transcription cost. +export const contractRule = rules.find((rule) => rule.id === "R-CI-01") + /** @param {number} n */ const plural = (n) => `${n} ${n === 1 ? "rule" : "rules"}` diff --git a/tests/homepage-claims.test.ts b/tests/homepage-claims.test.ts new file mode 100644 index 0000000..f102114 --- /dev/null +++ b/tests/homepage-claims.test.ts @@ -0,0 +1,107 @@ +import { describe, expect, test } from "bun:test" +import { readFileSync } from "node:fs" +import { leadClause, parseRules } from "../site/scripts/generate.mjs" + +// The homepage quotes the standard in three places: the hero's example route, +// the workflow trace, and the rule-contract panel. Two of the three used to be +// transcribed by hand, and one of those transcriptions was wrong. Under the +// heading "Read the bar before you adopt it", the panel gave R-CI-01's Check as +// "The workflow declares both trigger paths." The real Check turns on a +// `pull_request` trigger carrying no `branches:` filter, and says outright that +// a configuration carrying one fails the rule rather than passing it. So the +// panel told a repository with `branches: [main]` that it passed. Nothing +// failed: the page's factual accuracy was the one thing in this repository no +// check covered, while the rest of it enforces R-DOC-04 against exactly this. +// +// These tests hold the page to deriving what it quotes. They do not compare +// rendered output, which would need a build; they hold the sources to the +// shape that makes rendered output correct by construction. +const standard = readFileSync("skills/oss-audit/STANDARD.md", "utf8") +const rules = parseRules(standard) + +const HOMEPAGE = ["site/src/content/docs/index.mdx", "site/src/components/HomeHero.astro"] +const sources = HOMEPAGE.map((path) => ({ path, text: readFileSync(path, "utf8") })) + +// Comment bodies are stripped first. They quote the wrong old wording on +// purpose, to record what went wrong, and a check that punished them would +// teach the next person to delete the explanation instead of the defect. +const withoutComments = (text: string) => text.replaceAll(/\{?\/\*[\s\S]*?\*\/\}?/g, "") + +describe("rule claims on the homepage", () => { + test("the standard supplies the two rules the homepage takes apart", () => { + // A guard on the guard: if either ID is renamed, the tests below would + // silently check an undefined rule and pass. + expect(rules.map((rule) => rule.id)).toContain("R-SEC-01") + expect(rules.map((rule) => rule.id)).toContain("R-CI-01") + }) + + for (const rule of rules) { + test(`${rule.id}'s lead clause is a verbatim prefix of its Check`, () => { + const lead = leadClause(rule.check) + expect(rule.check.startsWith(lead)).toBe(true) + expect(lead.length).toBeGreaterThan(0) + // A prefix that runs to the whole Check quotes correctly but abridges + // nothing, which is the failure the panels reach for it to avoid. + // R-SEC-01 and R-CI-01 are the two the homepage renders. + if (rule.id === "R-SEC-01" || rule.id === "R-CI-01") { + expect(lead.length).toBeLessThan(rule.check.length) + } + }) + } + + // A label naming a rule field, as the three quoting blocks write it: a bare + // Statement or Check, or one numbered by the workflow trace's step counter. + const FIELD = /<(?:code|dt|span[^>]*)>\s*(?:\d+\s*·\s*)?(Check|Statement)\s*<\/(?:code|dt|span)>/g + + for (const { path, text } of sources) { + test(`${path} writes out no rule field by hand`, () => { + const body = withoutComments(text) + const handwritten: string[] = [] + for (const label of body.matchAll(FIELD)) { + const after = body.slice(label.index + label[0].length).slice(0, 400) + const open = /<(p|dd)[^>]*>/.exec(after) + if (!open) continue + const value = after.slice(open.index + open[0].length).trimStart() + // The field's value has to be an expression: either the rule property + // directly, or a component handed one. A literal sentence here is the + // defect, whatever it says, because nothing downstream can tell whether + // it still matches the rule it claims to quote. + const derived = value.startsWith("{") || /^<[A-Z][^>]*\{/.test(value) + if (!derived) handwritten.push(`${label[1]}: ${value.slice(0, 72)}`) + } + expect(handwritten).toEqual([]) + }) + + test(`${path} reads each rule field it renders from the rule`, () => { + const body = withoutComments(text) + // Rendering a rule means naming its fields as properties. A page that + // stopped doing this would pass the check above by having nothing left + // to check, so the derivation itself is asserted. + const rendered = /\{(contractRule|sampleRule)[?.]/.test(body) + if (!rendered) return + expect(body).toMatch(/\{(contractRule|sampleRule)\??\.statement\}/) + expect(body).toMatch(/leadClause\((contractRule|sampleRule)\.check\)/) + }) + } + + test("the contract panel reads every field of its rule, including forge scope", () => { + // The panel's Fixed by and forge scope were transcribed too, and they were + // right. Being right by luck is what this stops: the rule that changes a + // fixing skill changes nothing on the homepage today. + const body = withoutComments(readFileSync("site/src/content/docs/index.mdx", "utf8")) + expect(body).toMatch(/\{contractRule\.fixedBy\}/) + expect(body).toMatch(/FORGE_LABEL\[contractRule\.forges\]/) + }) + + test("both quoting panels link to the full check", () => { + // An abridgement is honest only where the whole clause is one click away. + for (const { path, text } of sources) { + const body = withoutComments(text) + if (!/leadClause\(/.test(body)) continue + expect({ path, linked: /#\$\{[a-zA-Z]+\.id\.toLowerCase\(\)\}-check/.test(body) }).toEqual({ + path, + linked: true, + }) + } + }) +}) From 9fc24137e1229c760ef329b82022a9060bd9b8ef Mon Sep 17 00:00:00 2001 From: Leonid Svyatov Date: Tue, 4 Aug 2026 16:43:19 +0300 Subject: [PATCH 2/2] fix(site): reconcile the radius scale and correct homepage semantics Eleven border-radius values ran across 24 declarations, most of them a fraction of a pixel apart: a control at 5.6px beside one at 6px, a panel at 8px beside one at 8.8px. Nothing documented a reason for any gap and no two of the near-identical pairs ever appeared together, so the spread was drift. Four tokens replace them, named for the roles they serve: indicator, control, panel, and faceplate. Starlight, Expressive Code, and Pagefind round four more things from their own component styles at 4px and 3.2px, and each now takes the step its role names. DESIGN.md records the scale and the rule that governs adding to it. Semantics on the maintenance map: the example route's statement was an h2, so a SHA-pinning rule was the second heading on the site, ahead of every section. It is a paragraph now and the route is labelled by its own head. FaIcon serialized the literal string aria-label="undefined" onto every icon the site draws. A screen reader heard "Scanning now." for an activity nobody had started on a repository nobody had supplied. The lit bay's border measured 2.66:1 in light mode against WCAG 1.4.11's 3:1 for state, and now takes the same green three steps down at 5.43:1. The oss-audit rail named itself and never said what it produces, and both its captions were hidden below 34rem. The whole strip is the link now and carries the definition. The map's body no longer reserves 35rem of height it did not always fill. --- site/astro.config.mjs | 13 +- site/src/components/FaIcon.astro | 5 +- site/src/components/HomeHero.astro | 48 ++++-- site/src/styles/custom.css | 240 ++++++++++++++++++++++------- 4 files changed, 237 insertions(+), 69 deletions(-) diff --git a/site/astro.config.mjs b/site/astro.config.mjs index de87a12..c47254b 100644 --- a/site/astro.config.mjs +++ b/site/astro.config.mjs @@ -93,7 +93,18 @@ export default defineConfig({ useStarlightUiThemeColors: false, styleOverrides: { borderColor: "var(--kit-line)", - borderRadius: "0.5rem", + // The panel step, taken from the same token the stylesheet uses + // rather than repeated as a number here. Expressive Code emits this + // straight into --ec-brdRad and draws the frame at that plus its + // border width, so a block's outer edge measures 9px and its content + // measures the 8px the scale asks for. That pixel is the border, not + // a fifth radius. + // + // Changing this needs the Astro cache cleared to see the effect. The + // dev server keeps serving the previously generated Expressive Code + // stylesheet, and the stale one renders every block at 0px, which + // reads as a broken value rather than as a stale build. + borderRadius: "var(--kit-radius-panel)", frames: { frameBoxShadowCssValue: "none" }, }, }, diff --git a/site/src/components/FaIcon.astro b/site/src/components/FaIcon.astro index 3c91e6e..5e234c6 100644 --- a/site/src/components/FaIcon.astro +++ b/site/src/components/FaIcon.astro @@ -40,7 +40,10 @@ if (!glyph) throw new Error(`FaIcon: no icon mapped for "${name}"`) const svg = icon(glyph, { attributes: { "aria-hidden": label ? "false" : "true", - "aria-label": label, + // Spread rather than assigned: an undefined label serialized to the literal + // string aria-label="undefined" on every icon the site draws. Masked while + // the icon stays hidden, and live the moment one is given a name. + ...(label && { "aria-label": label }), focusable: "false", }, }).html.join("") diff --git a/site/src/components/HomeHero.astro b/site/src/components/HomeHero.astro index 9dd4bc0..9e9761d 100644 --- a/site/src/components/HomeHero.astro +++ b/site/src/components/HomeHero.astro @@ -9,7 +9,7 @@ FORM: user-pinned lateral control surface based on approved composition A; no co import DomainIcon from "./FaIcon.astro" import InlineCode from "./InlineCode.astro" import InstallCommand from "./InstallCommand.astro" -import { domains, sampleRule } from "../lib/content.mjs" +import { domains, leadClause, sampleRule } from "../lib/content.mjs" ---
    @@ -31,12 +31,17 @@ import { domains, sampleRule } from "../lib/content.mjs"
    -
    - Cross-domain scan - oss-audit + {/* The rail named oss-audit and never said what it produces. Its two + captions read "Cross-domain scan" and "Every standard domain", both were + hidden below 34rem, and the definition a reader needed sat a thousand + pixels down the page in the domain rack. It says it here now, and the + whole strip is the link rather than the one word inside it. */} + + oss-audit - Every standard domain -
    + Evidence and gaps across every standard domain + +
    @@ -67,11 +72,14 @@ import { domains, sampleRule } from "../lib/content.mjs" {/* The lit bay used to say so in green and in nothing else. The marker is a shape rather than a second tint, and the word - behind it is what a reader who cannot see either one gets. */} + behind it is what a reader who cannot see either one gets. + That word said "Scanning now.", which described an activity + no visitor had started on a repository none had supplied. + The bay is lit because the route below expands it. */} {domain.area === "SEC" && ( <>