🔍 fix: apply the code review — two visible regressions, and the gates behind them

Nine review angles, fourteen verified findings, all but the byte-table
generator applied. The two that mattered most were regressions of mine:

- a:hover repainted button-styled anchors, and in dark theme --accent-text
  equals --accent-solid — hovering the hero CTA drew its label in its own
  background colour. Verified invisible before (computed color == computed
  background) and distinct after: 10.39:1 dark, 6.90:1 light. The buttons and
  the skip link now re-assert their label colours on hover.
- Footer links had lost every non-hover affordance: the text-decoration:none
  carve-out plus body-coloured links left a 2.37:1 shade difference as the
  only cue. The carve-out is deleted — .nav-links a and .brand already
  declare none themselves — and the accent rule is back on the footer.

Also on the page: #ref-filter, the one text input, moves to --border-ui (it
still had the 1.68:1 border the token's own comment calls decorative);
focusable code surfaces get the --accent-on-code ring at -2px offset, inside
the .code overflow clip; #output .out-err drops #ff8095, the last surviving
colour of the deleted indigo palette; the two rgba(255,106,126) washes become
color-mix over --err-line so a grep for the token finds them.

The theme machinery loses a whole block: the dark media query is guarded with
:not([data-theme="light"]), so an explicit light toggle falls through to the
bare :root palette and the 21-token hand-copy in [data-theme="light"] is
gone. Verified in all four system/toggle combinations.

The page stops contradicting the repo: it claimed cereale/min "cannot
tree-shake — nothing left to shake" while src/treeshake.test.ts proves the
opposite on every run. Corrected here and in FRAMEWORKS.md, with the measured
figures (1,837 vs 1,996 bytes for one decorator). The release facts the page
was hand-bumping — both tgz names, "0.4.0 lives in the repository", the
sixty-eight — now fill from meta.js/the bundle like the version badge always
has.

The workflows close three holes:

- The docs sync gate was blind to NEW untracked build outputs (git diff does
  not report them; demonstrated). Both workflows now run check:docs-sync, one
  shared script that fails on anything porcelain reports — which also ends
  the copy-paste divergence between them.
- pages.yml deploys on CI succeeding on main (workflow_run) instead of on the
  push itself, so a deploy implies green tests, not just in-sync docs. The
  deploy job refuses refs other than main, closing the workflow_dispatch
  any-branch deploy, and the build job drops pages/id-token — npm postinstall
  scripts no longer run alongside an OIDC grant.
- The Junie action is pinned to the commit behind v1.7.4 rather than the tag,
  which is the immutability the previous comment promised but a mutable ref
  cannot deliver.

Verified: every fix confirmed in a rendered browser in both themes; 72
contrast pairs still pass; no overflow at 20 widths; 268 tests, build,
check:types, check:docs, actionlint all green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SAcqrz3FcadkYr3xG32CjK
This commit is contained in:
Claude
2026-08-17 16:23:46 +00:00
parent f4c5e214f9
commit 4b910f19bf
8 changed files with 107 additions and 60 deletions
+27 -26
View File
@@ -27,9 +27,10 @@
which already owns amber. Two amber families would make "our brand" and "needs
your attention" the same colour.
Every token below that differs between themes must be written in ALL FOUR places:
this block, the prefers-color-scheme block, [data-theme="light"] and
[data-theme="dark"]. Miss one and the toggle silently serves the light value. */
Every token below that differs between themes must be written in THREE places: this
block (the light palette), the prefers-color-scheme block, and [data-theme="dark"].
The dark media block is guarded with :not([data-theme="light"]), so an explicit light
toggle falls straight through to these values — there is no light copy to keep in sync. */
:root {
--bg: #faf7f0;
--bg-raised: #fffdf7;
@@ -95,7 +96,7 @@
}
@media (prefers-color-scheme: dark) {
:root {
:root:not([data-theme="light"]) {
--bg: #12100b;
--bg-raised: #1a1710;
--bg-sunken: #16130d;
@@ -120,16 +121,9 @@
}
}
/* The toggle wins over the media query in both directions. */
/* An explicit light choice: the guarded media block above no longer matches, so the
bare :root palette wins on its own. Only the UA hint needs stating. */
:root[data-theme="light"] {
--bg: #faf7f0; --bg-raised: #fffdf7; --bg-sunken: #f2ede1;
--text: #1a1712; --text-muted: #5c5346; --text-faint: #6e6353;
--border: #e6dfd1; --border-strong: #cfc5b2; --border-ui: #8e8269;
--accent: #8a6238; --accent-text: #7a5530; --accent-soft: #f1e9dc;
--accent-solid: #6b4a28; --on-accent: #fffdf7;
--bad: #a82820; --bad-soft: #faebe7; --ok: #256b3d; --ok-soft: #e8f2e9;
--warn: #8a5a05; --warn-soft: #fbf1dc;
--shadow: 0 1px 2px rgba(40, 30, 14, .05), 0 8px 24px -12px rgba(40, 30, 14, .20);
color-scheme: light;
}
:root[data-theme="dark"] {
@@ -173,7 +167,6 @@ p { margin: 0 0 1rem; }
--accent-text and --text-muted sit 1.14:1 apart — colour alone would lose them. */
a { color: var(--text); text-decoration-color: var(--accent); text-decoration-thickness: 2px; text-underline-offset: .16em; }
a:hover { color: var(--accent-text); text-decoration-color: currentColor; }
.nav-links a, .foot-grid a, .brand { text-decoration: none; }
code, kbd, pre { font-family: var(--mono); }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
@@ -200,6 +193,7 @@ section { padding-block: clamp(3rem, 6vw, 5.5rem); }
background: var(--accent-solid); color: var(--on-accent); padding: .6rem 1rem; border-radius: 0 0 var(--radius) 0;
}
.skip:focus { left: 0; }
.skip:hover { color: var(--on-accent); }
.sr-only {
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
@@ -249,9 +243,12 @@ header.nav {
padding: .75rem 1.15rem; border-radius: var(--radius); text-decoration: none; cursor: pointer; border: 1px solid transparent;
}
.btn-primary { background: var(--accent-solid); color: var(--on-accent); box-shadow: var(--shadow); }
.btn-primary:hover { filter: brightness(1.08); }
/* Re-assert label colours on hover: the base a:hover (0,1,1) outranks these classes
(0,1,0), and in dark theme --accent-text equals --accent-solid — a hovered label
painted in its own background. */
.btn-primary:hover { filter: brightness(1.08); color: var(--on-accent); }
.btn-secondary { background: var(--bg-raised); color: var(--text); border-color: var(--border-ui); }
.btn-secondary:hover { border-color: var(--text-faint); }
.btn-secondary:hover { border-color: var(--text-faint); color: var(--text); }
.fact-row {
display: flex; flex-wrap: wrap; gap: .4rem .5rem; margin-top: 1.5rem;
font-size: .8125rem; color: var(--text-muted);
@@ -290,7 +287,7 @@ header.nav {
/* The page's one visual device: the line the compiler refuses. */
.ln--error {
background: rgba(255, 106, 126, .09);
background: color-mix(in srgb, var(--err-line) 9%, transparent);
text-decoration: underline wavy var(--err-line);
text-decoration-skip-ink: none;
text-underline-offset: .32em;
@@ -298,7 +295,7 @@ header.nav {
.tsc-error {
display: flex; gap: .6rem; align-items: flex-start;
margin: 0; padding: .7rem .9rem; border-top: 1px solid var(--code-border);
background: rgba(255, 106, 126, .08); color: var(--err-text);
background: color-mix(in srgb, var(--err-line) 8%, transparent); color: var(--err-text);
font: 500 .78125rem/1.5 var(--mono);
}
.tsc-error .mark { color: var(--err-line); flex-shrink: 0; }
@@ -348,13 +345,16 @@ header.nav {
overflow: auto;
}
#editor:focus { outline: 2px solid var(--accent-on-code); outline-offset: -2px; }
/* Scrollable code samples are keyboard-focusable in Chromium; the page-level ring is
unreadable on the dark slab and its +2px offset would be clipped by .code overflow. */
.code :focus-visible, #output:focus-visible { outline: 2px solid var(--accent-on-code); outline-offset: -2px; }
#output {
flex: 1; min-height: 400px; margin: 0; overflow: auto;
background: var(--code-bg); color: var(--code-text); border: 1px solid var(--code-border);
border-top: none; border-radius: 0 0 var(--radius-lg) var(--radius-lg);
padding: 1.1rem 1.15rem; font: .84375rem/1.75 var(--mono); white-space: pre-wrap; word-break: break-word;
}
#output .out-err { color: #ff8095; }
#output .out-err { color: var(--err-text); }
#output .out-dim { color: var(--code-faint); }
.pg-note { font-size: .8125rem; color: var(--text-muted); margin: .9rem 0 0; }
@@ -370,7 +370,7 @@ td.note { white-space: normal; color: var(--text-muted); font-size: .875rem; }
.ref-bar { display: flex; flex-wrap: wrap; gap: .75rem; align-items: center; margin-bottom: 1.5rem; }
#ref-filter {
flex: 1; min-width: 210px; font: .9375rem var(--sans); padding: .6rem .85rem;
border: 1px solid var(--border-strong); border-radius: var(--radius);
border: 1px solid var(--border-ui); border-radius: var(--radius);
background: var(--bg-raised); color: var(--text);
}
#ref-count { font-size: .875rem; color: var(--text-muted); font-variant-numeric: tabular-nums; }
@@ -817,7 +817,7 @@ export default defineConfig({
<p class="compare-label"><span class="pill pill-bad">not on npm yet</span> installing it today</p>
<div class="code">
<div class="code-head"><span class="name">shell</span></div>
<pre><code data-lang="text">git clone https://github.com/avalon-vanguard/cereale
<pre><code data-lang="text" id="install-shell">git clone https://github.com/avalon-vanguard/cereale
cd cereale
npm install &amp;&amp; npm run build
npm pack # → cereale-0.4.0.tgz
@@ -851,9 +851,10 @@ npm install ../cereale/cereale-0.4.0.tgz</code></pre>
&lt;/script&gt;</code></pre>
</div>
<p class="pg-note">
<strong>If you are using a bundler, do not use it.</strong> It is the whole library in one
file, so nothing can be dropped from it. The default entry point tree-shakes; this one
cannot, because there is nothing left to shake.
<strong>If you are using a bundler, prefer the default entry.</strong> The flat file keeps
its purity annotations, so a bundler can still drop the rules you did not import — that
is pinned by a test — but the per-module build shakes slightly leaner (1,837 bytes
against 1,996 for one decorator) and is the canonical route.
</p>
</div>
</div>
@@ -866,7 +867,7 @@ npm install ../cereale/cereale-0.4.0.tgz</code></pre>
<p class="eyebrow">Bundle size</p>
<h2>You pay for the decorators you name</h2>
<p class="lede">
Sixty-eight decorators is a lot to ship to a browser, so none of the ones you did not
<b class="js-dec-count">68</b> decorators is a lot to ship to a browser, so none of the ones you did not
import are shipped. Minified bytes, measured through all three bundlers.
</p>
</div>
@@ -998,7 +999,7 @@ npm install ../cereale/cereale-0.4.0.tgz</code></pre>
</div>
<div class="note-item">
<h3>It is not on npm yet</h3>
<p>0.4.0 lives in the repository. <code class="inline-code">npm install cereale</code> does not
<p><span class="js-version">0.4.0</span> lives in the repository. <code class="inline-code">npm install cereale</code> does not
resolve to this library — build it from source until it is published.</p>
</div>
</div>
+16
View File
@@ -39,6 +39,22 @@
if (versionEl && meta.version) versionEl.textContent = 'v' + meta.version;
var nodeEl = document.getElementById('node-req');
if (nodeEl && meta.node) nodeEl.textContent = meta.node.replace('>=', '≥').replace('.0.0', '');
if (meta.version) {
Array.prototype.forEach.call(document.querySelectorAll('.js-version'), function (el) {
el.textContent = meta.version;
});
// The install snippet names the tarball npm pack produces; keep it tied to the
// same package.json fact the badge uses instead of hand-bumping it each release.
var shell = document.getElementById('install-shell');
if (shell) {
shell.textContent = shell.textContent.replace(/cereale-[\d.]+\.tgz/g, 'cereale-' + meta.version + '.tgz');
}
}
if (decoratorCount) {
Array.prototype.forEach.call(document.querySelectorAll('.js-dec-count'), function (el) {
el.textContent = String(decoratorCount);
});
}
/* ------------------------------------------------------- highlighting */
var TOKENS = [