Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 45 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,17 @@ jobs:
- name: Parse HTML and check local links
run: scripts/check-site.py

# ── Design gates ──────────────────────────────────────────────────
# Three checks, all driven by design.config.json. They are generalized
# copies of the /ferrosa-design skill's reference implementations; the
# skill lives in a private repo this runner cannot check out, so they are
# vendored here rather than fetched. Keep the two in step — see the
# header comment in design.config.json.

# The design system states in prose that it meets WCAG 2.1 AA in both
# themes. This makes that arithmetic. It exits non-zero on the first
# token pair below threshold, so a palette edit cannot quietly walk the
# themes. This makes that arithmetic, across every source that declares
# tokens and every theme block each one ships. It exits non-zero on the
# first pair below threshold, so a palette edit cannot quietly walk the
# contrast back down the way it did before #11.
#
# There were two of these. The Ferrosa Memory system was retired along
Expand All @@ -67,7 +75,41 @@ jobs:
- name: Check design system contrast
run: |
node --version
node scripts/check-brand-contrast.mjs
node scripts/check-contrast.mjs design.config.json

# The remaining two gates measure the RENDERED page, so they need the
# site served and a browser. ubuntu-latest ships Chrome at a stable path;
# assert it rather than discovering its absence as a confusing probe
# failure three steps later.
- name: Serve the site
run: |
test -x /usr/bin/google-chrome || {
echo "::error::google-chrome not found on the runner. The anatomy and whitespace gates need it."
exit 1
}
(cd docs && python3 -m http.server 8779 >/dev/null 2>&1 &)
for i in $(seq 1 20); do
curl -sf -o /dev/null http://127.0.0.1:8779/index.html && break
sleep 0.5
done
curl -sf -o /dev/null http://127.0.0.1:8779/index.html || {
echo "::error::static server never came up on 127.0.0.1:8779"
exit 1
}

# One component, one computed style, across every page that shares it.
# This is the gate that catches what a stylesheet audit cannot: which
# rule WON. It found the Forge hero rendering at 64px against 48px
# everywhere else, and fifteen buttons falling through to a ported
# stylesheet because they carried a modifier class without its base.
- name: Check page anatomy
run: node scripts/measure-anatomy.mjs design.config.json

# Dead vertical space, by sampling what actually paints. Measuring
# element boxes instead reports wrapping inline elements as spanning
# every line they touch, which buries the real gaps in false positives.
- name: Check for dead vertical space
run: node scripts/find-empty-bands.mjs design.config.json

- name: Check whitespace
run: git diff --check
Expand Down
39 changes: 30 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,18 +78,39 @@ Run the docs checks:
scripts/generate-example-docs.sh
python3 scripts/build-docs-nav.py
scripts/check-site.py
node scripts/check-brand-contrast.mjs
node scripts/check-contrast.mjs design.config.json

# the two rendered-page gates need the site served and Chrome
(cd docs && python3 -m http.server 8779 &)
node scripts/measure-anatomy.mjs design.config.json
node scripts/find-empty-bands.mjs design.config.json

git diff --check
```

`check-brand-contrast.mjs` reads the `--ferrosa-*` token blocks out of
`docs/design-system.html` **and** `theme/examples.css`, and asserts every
foreground/background pair meets WCAG 2.1 AA in both themes — including the
syntax-highlight colours against the code background. It prints
`99 combinations checked, 0 below AA.` and exits non-zero on the first pair below
threshold. It needs Node 18 or newer and uses only the standard library. (The
Ferrosa Memory design system was retired along with its page, and its `--fm-`
checker went with it.)
### Design gates

Three checks, all driven by `design.config.json`. They need Node 18+ and (for
the last two) Chrome — no npm install. Each exits non-zero on failure. They are
generalized copies of the `/ferrosa-design` skill's reference implementations,
vendored here because that skill lives in a repo CI cannot check out; keep the
two in step.

| Script | Asserts |
|---|---|
| `check-contrast.mjs` | Every foreground/background pair meets WCAG 2.1 AA, in **every theme block each source declares** — dark, explicit light, and `prefers-color-scheme` — across `docs/design-system.html`, `theme/examples.css` and `docs/site.css`, including syntax colours on the code background. Prints `183 combinations checked, 0 below AA.` |
| `measure-anatomy.mjs` | A shared component computes **identically** across the pages that share it, at desktop and phone widths. It measures computed style in a real browser, so it catches which rule *won* — a specificity tie, a ported bare-element selector — which no stylesheet audit can see. |
| `find-empty-bands.mjs` | No band of dead vertical space over 200px, found by sampling what actually paints rather than by measuring element boxes. |

`check-contrast.mjs` replaced `check-brand-contrast.mjs`, which located theme
blocks with `indexOf` against the raw file. `site.css` documents its three theme
selectors in its header comment, so those mentions were found first: the dark
block resolved to a zero-length slice and every dark pair was silently skipped,
while the third block ran to EOF and gave each token its last value anywhere in
the file. It reported 127 clean comparisons while checking none of the site's
default palette. Blocks are now located in a comment-stripped copy — 183 real
comparisons. (The Ferrosa Memory design system was retired along with its page,
and its `--fm-` checker went with it.)

This repo now OWNS docs/ (the marketing site moved off ferrosadb/ferrosa). `sync-from-ferrosa.sh` no longer pulls docs/ by default — only the example SOURCES (sources/ferrosa/examples) track the engine repo. Use `--with-docs` only for a deliberate full re-mirror.

Expand Down
111 changes: 111 additions & 0 deletions design.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
{
"_comment": "Design gates for www.ferrosadb.com. Consumed by scripts/check-contrast.mjs, scripts/measure-anatomy.mjs and scripts/find-empty-bands.mjs, all three of which are generalized copies of the /ferrosa-design skill's reference implementations. Paths are relative to this file.",

"baseUrl": "http://127.0.0.1:8779",
"docRoot": "docs",
"viewports": ["1440x900", "390x844"],
"whitespaceThreshold": 200,

"tokenSources": [
{ "label": "design system", "path": "docs/design-system.html", "prefix": "ferrosa-" },
{ "label": "examples theme", "path": "theme/examples.css", "prefix": "ferrosa-" },
{
"label": "site stylesheet",
"path": "docs/site.css",
"prefix": "",
"alias": {
"muted": "text-muted",
"subtle": "text-subtle",
"on-primary": "text-on-primary"
}
}
],

"checks": [
["text", "bg", 4.5, "body text"],
["text", "surface", 4.5, "body text on a surface"],
["text-muted", "surface", 4.5, "muted text"],
["text-subtle", "surface", 4.5, "subtle text"],
["text-subtle", "bg", 4.5, "subtle text on the page"],
["primary", "surface", 4.5, "primary as a link or label"],
["accent", "surface", 4.5, "accent as code or data"],
["success", "surface", 4.5, "status word"],
["warning", "surface", 4.5, "status word"],
["danger", "surface", 4.5, "status word"],
["text-on-primary", "primary", 4.5, "primary button label"],
["border", "surface", 3.0, "component boundary (1.4.11)"],
["border", "bg", 3.0, "component boundary (1.4.11)"],

["text-muted", "code-bg", 4.5, "code text"],
["primary", "code-bg", 4.5, "keyword"],
["accent", "code-bg", 4.5, "identifier / inline code"],
["success", "code-bg", 4.5, "string literal"],
["warning", "code-bg", 4.5, "numeric literal"],
["danger", "code-bg", 4.5, "tag"],
["text-subtle", "code-bg", 4.5, "comment"],
["code-text", "code-bg", 4.5, "code body"],
["syn-kw", "code-bg", 4.5, "keyword"],
["syn-fn", "code-bg", 4.5, "identifier"],
["syn-str", "code-bg", 4.5, "string"],
["syn-num", "code-bg", 4.5, "number"],
["syn-attr", "code-bg", 4.5, "attribute"],
["syn-op", "code-bg", 4.5, "operator"],
["syn-com", "code-bg", 4.5, "code comment"]
],

"pages": [
"index.html",
"database/index.html",
"forge/index.html",
"ferrosa-memory/index.html",
"ferrosa-memory/how-it-works.html",
"ferrosa-memory/how-to-use.html",
"ferrosa-memory/compare.html",
"ferrosa-memory/tools.html",
"ferrosa-memory/getting-started.html"
],

"uniform": [
{
"name": "hero h1 (landings)",
"selector": ".hero-copy h1",
"properties": ["fontSize", "letterSpacing", "lineHeight", "fontWeight", "marginTop"],
"expectCount": 1,
"pages": ["index.html", "database/index.html", "forge/index.html", "ferrosa-memory/index.html"]
},
{
"name": "hero h1 (sub-pages)",
"selector": ".hero-compact .hero-copy h1",
"properties": ["fontSize", "letterSpacing", "lineHeight", "fontWeight", "marginTop"],
"expectCount": 1,
"pages": [
"ferrosa-memory/how-it-works.html",
"ferrosa-memory/how-to-use.html",
"ferrosa-memory/compare.html",
"ferrosa-memory/tools.html",
"ferrosa-memory/getting-started.html"
]
},
{
"name": "icon chip",
"selector": ".feature-icon, .pain-icon",
"properties": ["backgroundColor", "borderColor", "color", "borderRadius"],
"uniformBox": true
},
{
"name": "primary button",
"selector": ".btn-primary",
"properties": ["backgroundColor", "color", "fontSize", "borderRadius", "paddingTop", "paddingLeft"]
},
{
"name": "secondary button",
"selector": ".btn-secondary",
"properties": ["color", "fontSize", "borderRadius", "paddingTop", "paddingLeft"]
},
{
"name": "nav",
"selector": "body > nav, body > header nav",
"properties": ["position", "height"]
}
]
}
Loading