remarque-tokens 0.26.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENT_RULES.md CHANGED
@@ -58,7 +58,7 @@ These are not guidelines. Agents must follow them literally.
58
58
  ### Typography
59
59
 
60
60
  - Body text is never smaller than `--text-body` (17px / 1.0625rem)
61
- - Prose columns never exceed `--content-reading` (46rem)
61
+ - Prose columns never exceed `--content-reading` (38rem default — ~71 measured characters of 17px Inter)
62
62
  - Display/title text always uses `--font-display` (Newsreader)
63
63
  - Body text always uses `--font-body` (Inter)
64
64
  - Mono text is *only* for: metadata, code, labels, timestamps, technical accents
@@ -70,7 +70,7 @@ These are not guidelines. Agents must follow them literally.
70
70
  - The first screen contains only: title, metadata, and the start of content
71
71
  - Top padding before the main heading is at least `--space-9` (6rem)
72
72
  - The site uses a **single centered column** layout: BaseLayout constrains all content to `content-standard` (72rem) centered. Individual pages do NOT set their own outer width constraints.
73
- - Within the centered column, use `.content-reading` for prose-width sections (46rem, auto-centered within the 72rem column)
73
+ - Within the centered column, use `.content-reading` for prose-width sections (38rem default, auto-centered within the 72rem column)
74
74
  - Do NOT use Tailwind's `max-w-reading` or `max-w-standard` on page sections — these only set max-width without centering. Use the CSS classes `.content-reading` / `.content-standard` instead, which include `margin-inline: auto`
75
75
  - Code blocks (`<pre>`) inherit the width of their parent container — no special wrapping needed when the parent is already constrained
76
76
  - Stacking order is structural, not personalization: never author a bare `z-index` number — reference `tokens-core.css`'s `--z-*` scale (`--z-base`/`--z-sticky`/`--z-dropdown`/`--z-overlay`/`--z-modal`/`--z-toast`/`--z-skip-link`). Tailwind v4 has no `--z-index-*` theme namespace, so use arbitrary values (`z-[var(--z-modal)]`) rather than bare Tailwind numbers. See REMARQUE.md "Stacking."
@@ -186,7 +186,7 @@ Three further archetypes — Reference/Docs, Changelog, Gallery — are specifie
186
186
 
187
187
  1. **Tailwind spacing collision:** Never map Remarque's `--space-N` values onto Tailwind's default numeric spacing keys — in v4 `@theme` that means no `--spacing-9`…`--spacing-12`, and in the v3 config no `spacing: { "5"…"12" }` overrides — or `mt-12` produces 192px instead of 48px. Both shipped artifacts namespace instead: the v3 config exposes `mt-remarque-9` etc., the reference v4 `@theme` uses `--spacing-remarque-N`. Use those, or `var(--space-N)` in arbitrary values.
188
188
 
189
- 2. **Prose alignment:** The essay header and prose body must BOTH use centered max-width (`max-w-reading mx-auto`) to prevent misalignment. The `.content-reading` class auto-centers; match headers with `mx-auto`.
189
+ 2. **Prose alignment:** The essay header and prose body must BOTH sit in the same centered reading column to prevent misalignment — put `.content-reading` on both (it sets `max-width` and `margin-inline: auto`). Not Tailwind's `max-w-reading mx-auto`: it renders the same on screen, but `print.css`'s full-width reset is keyed on `.content-reading`, so a `max-w-reading` header prints indented from its body (the demo shipped exactly that until the 2026-10 design review).
190
190
 
191
191
  3. **Dark mode specificity:** Define `[data-theme="light"]` and `[data-theme="dark"]` overrides in addition to the `:root` defaults. Without an explicit light override, users with `prefers-color-scheme: dark` cannot toggle to light mode. Since 0.24.0 (`light-dark()` + `color-scheme`, REMARQUE.md "Color Scheme & light-dark()"), the shipped default palette's own `[data-theme="light"]` override is just `color-scheme: light` — a consumer palette that still authors the older two-declaration convention needs the fuller override this rule originally describes.
192
192
 
@@ -198,6 +198,8 @@ Three further archetypes — Reference/Docs, Changelog, Gallery — are specifie
198
198
 
199
199
  7. **Tokens must be imported unlayered (Tailwind v4):** never `@import "remarque-tokens" layer(...)`. theme.css's `@theme inline` mappings emit same-named self-referencing declarations inside `@layer theme`; the real token values win only because unlayered declarations beat layered ones. Layering the tokens makes every mapped utility circular and invalid, with no build error.
200
200
 
201
+ 8. **Arbitrary type-scale sizes need the `length:` hint (Tailwind v4):** write `text-[length:var(--text-meta)]`, never `text-[var(--text-meta)]`. Tailwind cannot infer a bare `var()`'s type, treats `text-[…]` as a color, and emits `color: var(--text-meta)` — the size never applies and the invalid color clobbers the element's real color utility, with no build error. `theme.css` deliberately maps no `--text-*` keys (they would collide with the unlayered `.text-meta`/`.text-body` classes). `remarque-audit` fails on the ambiguous form (`srcScans.ambiguousTextSize`). The demo site shipped 134 of these until the 2026-10 design review.
202
+
201
203
  ---
202
204
 
203
205
  ## Quality Checklist
@@ -207,7 +209,7 @@ Before considering any implementation complete, verify:
207
209
  - [ ] Fonts load: Newsreader for display, Inter for body, JetBrains Mono for meta
208
210
  - [ ] Fonts are self-hosted (no Google CDN requests in network tab)
209
211
  - [ ] Body text is ≥17px on all viewports
210
- - [ ] Prose column is ≤46rem on desktop
212
+ - [ ] Prose column is ≤ `--content-reading` on desktop, and body lines measure a median of 66–72 rendered characters (REMARQUE.md "Measure Compensation" — count characters, not `ch`)
211
213
  - [ ] Prose header and body are aligned (both centered with `mx-auto` or both left-aligned)
212
214
  - [ ] Line height for body text is 1.75
213
215
  - [ ] Dark mode is readable and independently tuned (not just inverted)
@@ -219,11 +221,12 @@ Before considering any implementation complete, verify:
219
221
  - [ ] Every image has a 1px border and mono caption
220
222
  - [ ] Every page maps to an archetype (Essay, Dossier, Notebook, Landing, Reference/Docs, Changelog, Gallery)
221
223
  - [ ] If the Essay uses sidenotes/a TOC rail (`remarque-tokens/essay`): `.remarque-sidenote-ref`/`.remarque-sidenote` alternate in strict DOM order, the rail never intrudes into `.remarque-prose`'s own measure, and the page renders correctly with `essay.css`'s `@media` block deleted (the narrow-viewport/no-JS fallback)
222
- - [ ] If a Landing/archive page uses the Broadsheet pattern (`remarque-tokens/broadsheet`): entry numerals are generated from `data-entry-number` via `attr()` (not `counter()`), the entry list stays a `<ul>` (not `<ol>` — the numeral is `aria-hidden` and decorative), and every kicker/dateline row uses `font-variant-caps: all-small-caps`, never `text-transform: uppercase`
223
- - [ ] If a page uses form controls (`remarque-tokens/forms`): checkboxes/radios are styled with `accent-color` only — never `appearance: none` plus a hand-drawn replacement; every control (input, button, checkbox/radio label) is ≥44×44px; validation state lives on `.remarque-field[data-state]` AND a real `aria-invalid`/`aria-describedby` pair on the input, not `data-state` alone; disabled controls use `--color-disabled`, never a state color
224
+ - [ ] If a Landing/archive page uses the Broadsheet pattern (`remarque-tokens/broadsheet`): entry numerals are generated from `data-entry-number` via `attr()` (not `counter()`), the entry list stays a `<ul>` (not `<ol>` — the numeral is `aria-hidden` and decorative), and every kicker/dateline row uses the label voice: tracked capitals (`text-transform: uppercase` + `--tracking-caps`), because the default mono face has no small caps; `font-variant-caps: all-small-caps` only on a label face that has `smcp` + `c2sc` (REMARQUE.md "Small Caps"; `remarque-audit` fails synthesized small caps)
225
+ - [ ] If a page uses form controls (`remarque-tokens/forms`): checkboxes/radios are styled with `accent-color` only — never `appearance: none` plus a hand-drawn replacement; every control (input, button, checkbox/radio label) is ≥44×44px; every input collecting known personal data has a valid `autocomplete` token and paste is never blocked (WCAG 3.3.7/3.3.8); validation state lives on `.remarque-field[data-state]` AND a real `aria-invalid`/`aria-describedby` pair on the input, not `data-state` alone; disabled controls use `--color-disabled`, never a state color
224
226
  - [ ] If a page uses the Palette Deck (`remarque-tokens/deck`): `data-palette` is set/cleared on the same root element as `data-theme` (they compose, never conflict); the FOUC-safe restore is a synchronous classic `<script>` physically in `<head>` (never `type="module"`, which defers); the switcher control itself is a native, keyboard-operable element (e.g. `.remarque-input` `<select>`) at ≥44×44px, not a hand-rolled widget; every registered palette was generated via `remarque-theme --scope`, never hand-authored
225
227
  - [ ] Mobile version is roomy — not a compressed desktop layout
226
- - [ ] Mobile nav links have ≥44px touch targets
228
+ - [ ] Mobile nav links have ≥44px touch targets (meets WCAG 2.5.8 AA; exceeds it, and meets 2.5.5 AAA)
229
+ - [ ] WCAG 2.2 AA (not 2.1): any consumer sticky/fixed chrome has `scroll-padding-top`/`-bottom` so focus is never obscured (2.4.11); inputs collecting known personal data carry a valid `autocomplete` token and never block paste (3.3.7, 3.3.8); `cd site && npx playwright test tests/a11y.spec.ts` (axe-core) passes. See REMARQUE.md "WCAG 2.2 AA Criterion Map"
227
230
  - [ ] No pure white or pure black backgrounds
228
231
  - [ ] `npm run audit` passes (contrast, gamut, font floors, hardcoded colors)
229
232
  - [ ] Muted text placed on `--color-surface` still meets 4.5:1 (check the surface pairing, not just bg)
@@ -235,6 +238,7 @@ Before considering any implementation complete, verify:
235
238
  - [ ] `<html lang="en">` attribute set
236
239
  - [ ] The site looks more like a book than a web app
237
240
  - [ ] Tailwind spacing utilities (`mt-12`, `pt-8`) produce expected values (not overridden by `@theme`)
241
+ - [ ] Arbitrary type-scale sizes use `text-[length:var(--text-*)]` — verify a computed `font-size` on one meta element, not just the class name (Pitfall #8)
238
242
 
239
243
  ---
240
244
 
@@ -258,7 +262,7 @@ npx remarque-audit --palette <file> --src <dir> --json
258
262
  version: string; // the remarque-tokens package version running the audit
259
263
  palette: string; // the --palette path as given
260
264
  src: string; // the --src path as given
261
- passed: boolean; // true iff failures.length === 0
265
+ passed: boolean; // true iff failures.length === 0 (warnings do not count)
262
266
  contrast: Array<{
263
267
  theme: 'light' | 'dark';
264
268
  fg: string; // fg token name, no leading --
@@ -280,11 +284,33 @@ npx remarque-audit --palette <file> --src <dir> --json
280
284
  unverifiableFontSize: Array<{ file: string; line: number; snippet: string }>;
281
285
  hardcodedColors: Array<{ file: string; line: number; snippet: string }>;
282
286
  oklchLiteral: Array<{ file: string; line: number; snippet: string }>;
287
+ ambiguousTextSize: Array<{ file: string; line: number; snippet: string }>; // Tailwind text-[var(--text-*)] (Pitfall #8)
283
288
  };
284
289
  failures: string[]; // every failure as a human-readable message, same text the non-JSON run prints
290
+ warnings: Array<{ // palette-coherence warnings (REMARQUE.md "Palette Coherence"); added in 0.28.0, additive
291
+ id: string; // 'text-tier-monotonic' | 'accent-vs-fg' | 'accent-vs-error' | 'accent-vs-viz-1' | 'error-vs-success-deutan' | 'surface-step' | 'viz-cvd-normal' | 'viz-cvd-protan' | 'viz-cvd-deutan' | 'viz-cvd-tritan'
292
+ theme: 'light' | 'dark';
293
+ tokens: string[]; // the token names involved, no leading --
294
+ actual: number; // measured value (ΔE2000, or the contrast shortfall for text tiers), 2dp
295
+ threshold: number; // the minimum it was compared to
296
+ message: string; // human-readable, same text the non-JSON run prints
297
+ }>;
298
+ fontFeatures: Array<{ // font-feature requests the face can't render (REMARQUE.md "Small Caps"); added in 0.28.0, additive
299
+ file: string; // 'remarque-tokens/<module>.css' for the package's own modules, else the --src-relative path
300
+ line: number; selector: string;
301
+ family: string | null; // first family of the rule's font-family (or --font-body when the rule sets none)
302
+ inferred: boolean; // true when the family was inferred as --font-body
303
+ tag: string; // OpenType feature tag, e.g. 'smcp', 'onum'
304
+ via: string; // the declaration that asked for it
305
+ status: 'missing' | 'conditional' | 'unverifiable'; // only 'missing' is also a failure
306
+ }>;
285
307
  }
286
308
  ```
287
309
 
310
+ `--font-features <manifest.json>` (repeatable) adds your own faces to the check, in the shape of the package's `fonts/features.json` (`{ families: { "<family>": { figures: { style, spacing }, files: { "<file>": { features: [tags] } } } } }`). Faces missing from every manifest are reported `unverifiable` and never fail.
311
+
312
+ `warnings` never affects `passed` or the exit code in this release — only `failures` does. The warning set is expected to grow and to be promoted to failures in a later minor, so treat an unknown `id` as informational.
313
+
288
314
  `contrast` and `gamut` list EVERY check performed (passing and failing);
289
315
  only `srcScans` and `failures` are violation-only lists, since a source
290
316
  scan that finds nothing has nothing to report per line.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,111 @@ All notable changes to `remarque-tokens` are documented here. Token value
4
4
  changes always state the design rationale — downstream sites pin against
5
5
  these entries when syncing.
6
6
 
7
+ ## 0.28.0 — 2026-10-24
8
+
9
+ The "truth" minor of the 2026-10 roadmap (#133): it makes the spec's claims true on the default stack. It was researched with Open Design and nexus-agents, ratified 7–0 (`vote-1792819887868-esau595`), and landed as one PR per issue, each with its own regression test proven red on 0.27.0. **Consumer-visible changes:**
10
+ - labels render in tracked capitals instead of synthesized small caps (#124);
11
+ - the broadsheet masthead box widens to `--content-standard`, with its title sized from that box (#121);
12
+ - the essay column moves onto the page axis (#119);
13
+ - long strings wrap (#122);
14
+ - RTL and non-Latin scripts get logical properties and `:lang()` guards (#123);
15
+ - `remarque-audit` gains a failing font-feature check (#124) and palette-coherence warnings (#125);
16
+ - `html` reserves the scrollbar gutter.
17
+
18
+ No token was removed or renamed, and no token value changed.
19
+
20
+ ### Changed — Node engines
21
+
22
+ - **`engines.node` raised from `>=18` to `>=22`** (#120). Node 18 and 20 are end-of-life, and CI now tests 22 and 24. The CSS itself has no Node requirement; this covers the bins (`remarque-audit`, `remarque-drift`, `remarque-theme`).
23
+
24
+ ### Changed — labels in tracked capitals; font-feature gate (#124; consumer-visible, ratified 7–0)
25
+
26
+ - **Labels set in real capitals, not synthesized small caps.** `.text-label` and every module label (broadsheet kickers and datelines, the TOC summary, the footnotes heading, `.remarque-field-label`) now use `text-transform: uppercase` + `--tracking-caps` instead of `font-variant-caps: all-small-caps`. None of the shipped fonts has `smcp` or `c2sc` (nor do their full upstream releases), so the browser faked small caps by shrinking capitals: a 14px label drew its caps at about 9.9px, under the 13px floor. Labels keep the 14px size. **Labels look different**: larger capitals in the same mono voice. If your label face has real `smcp` + `c2sc`, you can restore `font-variant-caps: all-small-caps; text-transform: none` yourself (REMARQUE.md "Small Caps"). Ratified 7–0 in `vote-1792819660668-a5kb6q3`. This replaces the #51 rule, rewritten in the same change in REMARQUE.md, AGENT_RULES.md, the registry description and the new-page skill.
27
+ - **Oldstyle figures are conditional.** `.remarque-prose` and the broadsheet entry numerals keep `oldstyle-nums`, now documented as "when the face has `onum`". Inter and Newsreader don't, so default figures are lining, and this always was the rendering. A face with `onum` (the flagship's Source Serif 4) still gets text figures.
28
+ - **New `remarque-audit` check: font features.** Every feature a stylesheet asks for (`font-variant-caps`, `font-variant-numeric`, `font-variant`, `font-feature-settings`), in the package's modules and in every `.css` under `--src`, is checked against the face the rule renders in, from the new shipped manifest `fonts/features.json` (OpenType GSUB+GPOS tags per face, generated with fontTools by `scripts/font-features.py`). A request the face can't render **fails**, unless the rule carries a `remarque-feature: conditional` comment. The check accounts for default figures (lining faces need no `lnum`; tabular faces no `tnum`). `--font-features <manifest>` adds a consumer's faces; faces in no manifest are reported, never failed. New JSON key `fontFeatures` (additive). The check landed red on the 0.27.0 tree first: 8 failures across 6 small-caps rules and 2 oldstyle rules.
29
+ - `scripts/test-font-features.mjs` covers manifest freshness (a sha256 per shipped woff2, so re-subsetting a font without regenerating the manifest fails), each status, and the CLI end to end, including a consumer manifest clearing a request.
30
+ - REMARQUE.md "Wrapping & Optical Defaults" gains a feature-support table (webstatus.dev, 2026-10-24): `text-wrap: pretty` and `hanging-punctuation` have no Firefox support, and `initial-letter` is Chromium plus prefixed Safari.
31
+
32
+ ### Documentation — DTCG (#132)
33
+
34
+ - **The #99 DTCG trigger fired on 2025-10-28.** The Format, Color and Resolver modules were published as Final Community Group Reports. REMARQUE.md's "DTCG Conformance" now records this, retires the rule against fixing `tokens.json` toward unratified drafts, and does not word the gate as waiting for a W3C Recommendation (a CG cannot produce one).
35
+ - **All three divergences are documented**, in REMARQUE.md, the generated `tokens.json` `$extensions.remarque.dtcg` note, the `tokens.schema.json` description and the README: oklch() color strings, per-token light/dark nesting, and the new third one, CSS-string dimension/duration values (`clamp()` has no DTCG form).
36
+ - **A demand gate replaces the ratification gate.** A `tokens.dtcg.json` export is built only when a consumer asks for a DTCG 2025.10-shaped file, or Style Dictionary or Terrazzo documents full 2025.10 support including resolver output. Nothing is built now.
37
+ - Metadata only: no token value, name, CSS output or `tokens.d.ts` change. `test-types.mjs` §6 now requires at least three divergences, a dimension/duration aspect, and no `gatedOn` still saying "ratifying". Ships with 0.28.0.
38
+
39
+ ### Fixed — RTL and non-Latin scripts (#123; LTR rendering unchanged)
40
+
41
+ - **essay.css: wide sidenotes float to `inline-start`.** A physical `float: left` paired with a logical negative `margin-inline-start` printed every note on top of the paragraph under `dir="rtl"` (measured at 1440px: note at x 400–560 over prose at 400–1008). It now lands in the right-hand gutter in RTL, and `float: left` stays as the fallback for engines without logical float values.
42
+ - **A logical-property sweep** of every physical left/right left in the modules. The blockquote bar, list indent, `dd` and table header/caption alignment, numeric cells (`text-align: end`), the details marker margin, the prose and lead drop caps (`float: inline-start`, logical padding), and the broadsheet numerals now mirror. In RTL the blockquote bar had stayed on the left, and lists and `dd` kept the browser's 40px start indent on the right. The closed disclosure marker flips under `:dir(rtl)`.
43
+ - **Script-aware guards.** Under `:lang(ar|fa|ur)` the tracking tokens are 0 (Remarque's `-0.02em` display tracking breaks cursive joining). Under `:lang(zh|ja|ko)` display tracking is 0 and heading leading is 1.35–1.4. The decorative italics set upright for `ar`, `fa`, `ur`, `he`, `zh`, `ja` and `ko`, and `em` uses weight instead. REMARQUE.md's Latin-only note now says what is and isn't guarded.
44
+ - New `site/tests/rtl.spec.ts` gates the RTL sidenotes and rail, the prose mirroring and the script guards, with an English control. 4 of its 5 tests fail on 0.27.0.
45
+ - `budgets.json` was raised. `tokens-core.css` and `broadsheet.css` passed their 10% headroom (brotli 3627 vs 3600; gzip 7301 vs 7300). The budget test caught it, as intended.
46
+
47
+ ### Fixed — page axis and the masthead (#119, #121; consumer-visible)
48
+
49
+ - **broadsheet.css: the masthead title is sized from its nameplate, not the viewport (#121, a 0.27.0 regression).** The title grew at `9vw` inside a box capped at `--content-reading`. When that token went to 38rem in 0.27.0, the box stopped growing at about 640px of viewport while the title kept growing to 7.5rem, so a four-word title wrapped to four lines at 1280px. `.remarque-masthead` is now an inline-size container with a `--content-standard` (72rem) box, and the title rate is `9cqi`, with `9vw` kept as the fallback; the bounds are unchanged. At 1280px and up the title is about 98px, four- to seven-word titles hold to two lines, and the title stays above the `--text-display` lead title. A 38rem box would have put the masthead below the lead, which is why the nameplate widened instead (ratified 7–0, `vote-1792889632787-bm0nogp`). The lead and entry list stay at the reading measure. **If you set `--remarque-masthead-size` yourself, nothing changes for you.**
50
+ - **essay.css: the reading column sits on the page axis (#119).** At 80rem and up the grid was `1fr | reading | 10–12rem`, so the column was centered only when the left slack happened to equal the rail: 48px off-axis at 1440px on the demo. Both outer tracks are now `minmax(--space-11, 1fr)` and the rail is capped at `--space-12` plus its focus-ring padding. Sidenotes and the rail are unchanged.
51
+ - **tokens-core.css: `html { scrollbar-gutter: stable; }`.** On classic-scrollbar platforms a centered column no longer shifts by about 15px between short and long pages. Overlay scrollbars (macOS, phones) are unaffected.
52
+ - New gates in `site/tests/page-axis.spec.ts`: the essay column is centered like a plain page at 1280, 1440 and 1920px; sidenotes and the rail clear the prose; a four-word masthead title is at most two lines and larger than the lead title at 768, 1280 and 1920px.
53
+
54
+ ### Fixed — long strings reflow (#122; consumer-visible)
55
+
56
+ - **`html { overflow-wrap: break-word; }` in `tokens-core.css`.** No layer set `overflow-wrap`, so one unbreakable string (a URL, a hash, a package path, long inline `code`) widened the whole page at phone width, failing WCAG 1.4.10. Measured on the demo at 320px with fixtures: 788px of page overflow in prose, 1291px across the broadsheet titles. It changes nothing for text that fits, and it inherits into consumer chrome too.
57
+ - **`overflow-wrap: anywhere` on flex labels** (TOC rail links and summary, `.remarque-button`, `.remarque-checkbox`, `.remarque-radio`). Their text is an anonymous flex item whose min-content is its longest word, which `break-word` can't shrink: a long TOC entry pushed the rail 528px wide.
58
+ - `<pre>` is unchanged on purpose: it keeps `white-space: pre` and scrolls inside its own box. REMARQUE.md "Wrapping & Optical Defaults" documents the difference.
59
+ - New `site/tests/reflow.spec.ts`: every demo page at 320 CSS px; long-string fixtures in prose, broadsheet titles, forms messages, buttons and labels, and the TOC rail; and the WCAG 1.4.12 text-spacing override set, with no clipped controls, TOC entries or sidenotes. The WCAG 2.2 criterion map now points at it.
60
+
61
+ ### Accessibility — WCAG 2.2 AA (#126)
62
+
63
+ - **Spec moves from WCAG 2.1 AA to 2.2 AA.** REMARQUE.md's accessibility section and enforcement checklist now cite 2.2, with a new "WCAG 2.2 AA Criterion Map" (criterion, mechanism, enforcement) and a "Regulatory Context" paragraph (EN 301 549 V4.1.1, the EAA, Section 508). 4.1.1 Parsing is noted as removed in 2.2. No CSS output change.
64
+ - The 44px touch-target line now cites 2.5.8 (AA) as the criterion met, with 2.5.5 (AAA) as the exceeded one; before, it cited only 2.5.5.
65
+ - **2.4.11 note:** consumer sticky/fixed chrome needs `scroll-padding-top`.
66
+ - **Forms contract (3.3.7 / 3.3.8):** inputs that collect known personal data carry a valid `autocomplete` token and never block paste. The forms markup sample now shows `autocomplete="email"`. Mirrored in AGENT_RULES.md.
67
+ - **axe-core gate.** `site/tests/a11y.spec.ts` runs `@axe-core/playwright` (4.13.0) with the wcag2a/2aa/21a/21aa/22aa tags over every demo page in both themes, plus a forced-colors pass, and fails on serious/critical violations. The only exclusion is `color-contrast` in the forced-colors pass (justified in the spec).
68
+ - Demo fix found by axe: the inline link in the Notebook page's body text was distinguished by color alone (2.13:1 against the surrounding text in dark); it is now underlined (`link-in-text-block`).
69
+
70
+ ### CI and release (not part of the package; #120, roadmap Step 0)
71
+
72
+ - **Every root gate now runs on pull requests.** The gates moved verbatim from `deploy.yml` into a reusable `.github/workflows/ci.yml` (Node 22 and 24). `deploy.yml` and `release.yml` both `need` it. Before this, pull requests ran only visual regression, and a `v*` tag ran only `audit.mjs`, so a tag on a commit with a stale `tokens.json` or a failing fixture suite would have published.
73
+ - **`release.yml`** pins npm to an exact version instead of `npm@latest` and creates a GitHub Release whose notes are this file's matching section. A tag with no `## X.Y.Z ` section fails before publishing.
74
+ - **`npm test`** chains the same gates for local runs. `scripts/test-pack.mjs` now strips the parent's `npm_*` environment from its child `npm`/`npx` calls; under `npm test` they inherited npm's local prefix and installed into this repo instead of the scratch consumer.
75
+ - **`.github/dependabot.yml`**: weekly version updates for the root and `site/` npm trees and for the SHA-pinned actions, with a 7-day cooldown. `@williamzujkowski/oklch-terminal-themes` stays a hand bump because `palette-golden.mjs` gates it.
76
+ - `main` is now branch-protected and requires the CI and visual-regression checks.
77
+ - **`scripts/test-budget.mjs`** (picked up by `npm test` and run in CI): gzip and brotli size budgets for every shipped CSS file plus the effective bare `tokens.css` import, against a committed, unshipped `budgets.json` (current size + 10%; `--update` rewrites it), and a module-closure check that every `var(--x)` in `prose`/`essay`/`broadsheet`/`forms`/`print` resolves to a core, palette or module-local custom property.
78
+
79
+ ### Added — palette coherence warnings (#125)
80
+
81
+ - **`remarque-audit` now checks palette coherence**, as **warnings** for this minor (printed in the human output and in `--json` under a new additive `warnings` array; `passed` and the exit code are unchanged). Rules: monotonic text tiers (`fg` ≥ `fg-muted` ≥ `muted` contrast); accent vs `fg` ≥ 3:1 or ΔE2000 ≥ 15; accent vs `error` and vs `viz-1` ΔE ≥ 8; `error` vs `success` ΔE ≥ 5 under deuteranopia; adjacent `bg`/`surface`/`bg-subtle`/`code-bg` steps ΔE ≥ 3; minimum pairwise ΔE ≥ 5 across `--color-viz-1..6` under normal, protan, deutan and tritan simulation. Thresholds sit in one block, `THRESHOLDS` in `scripts/lib/palette-coherence.mjs`; rationale in REMARQUE.md "Palette Coherence". The warnings are slated to become failures in a later minor.
82
+ - **No new runtime dependency.** The audit ships to consumers who do not install culori, so the color math (OKLCH to sRGB, Machado CVD matrices, CIEDE2000) is implemented in the new shipped `scripts/lib/palette-coherence.mjs`; `scripts/test-coherence-math.mjs` cross-checks it against culori's `differenceCiede2000` and `filterDeficiency*` so the two cannot drift.
83
+ - **`remarque-theme` enforces the accent guards when generating.** If the default accent source collides with the derived error color (ΔE < 8) or with `fg`, it rotates to the nearest-hue other chromatic ANSI color that clears both, and it keeps `fg` at least as high-contrast as `fg-muted`. This fixes the Palette Deck's gruvbox (accent == error) and rose-pine (accent ≈ fg, fg-muted above fg) palettes on regeneration. `scripts/test-theme.mjs` now asserts the guards across the whole terminal-theme corpus.
84
+ - The default palette emits warnings today (viz-1 near accent, dark `surface` == `bg-subtle`, viz slots under CVD). That is the evidence base for #128; no default token values change here.
85
+
86
+ ## 0.27.0 — 2026-10-23
87
+
88
+ Design review of the system and its demo site, run with Open Design's `design-review` methodology (gstack's 10-category / ~80-item rubric, calibrated against REMARQUE.md and AGENT_RULES.md): five independent reviewers, about 50 findings, AI-slop grade A throughout. The defects were in implementation, not identity. Two identity-level calls were put to a nexus consensus vote, and both passed 7–0. Closes #115 and #116.
89
+
90
+ ### Changed — default reading measure (consumer-visible; Governance "token value" minor case)
91
+
92
+ - **`--content-reading` default 46rem → 38rem.** REMARQUE.md calls the *measured character count* the invariant: a median of 66–72 per line. 46rem was documented as "≈ 70 ch at 17px Inter", but measured over rendered body lines (one `Range` per character, the paragraph's last line dropped) it set a **median of 86** (p90 93, past Butterick's 90). The claim came from `ch` arithmetic, and `ch` is the width of "0". 38rem measures a median of 71 (p90 76). Source Serif's reference consumer independently landed on 38rem ≈ 73 (#116). Ratified 7–0 (vote-1792782673313-gff1bdc): fix the value and keep the invariant. **Every consumer that doesn't override `--content-reading` gets a narrower prose column.** To keep the old measure, set `--content-reading: 46rem` in your palette override. It's palette tier, so one line does it.
93
+ - REMARQUE.md "Measure Compensation" is now stated in measured median/p90 characters, and documents how to measure them. AGENT_RULES, the README, the Butterick conformance row and the tailwind comment are updated. A new computed-style gate asserts that the demo essays measure a median of 66–72.
94
+ - **Essay sidenote gutter `--space-10` → `--space-11` (8 → 10rem).** At 8rem a mono note held about 15 characters per line, so a two-sentence note ran 19 lines tall. 10rem still clears the 80rem breakpoint with a 46rem column.
95
+
96
+ ### Added
97
+
98
+ - **`remarque-audit` fails on `text-[var(--text-*)]`** (new `srcScans.ambiguousTextSize`; AGENT_RULES Pitfall #8). Tailwind v4 can't infer a bare `var()`'s type. It compiles `text-[var(--text-meta)]` to **`color: var(--text-meta)`**, so no size is ever set and the invalid color clobbers the element's real color utility, with no build error. The demo shipped 134 of these: every date, lede, footer line and code block rendered at 17px full contrast instead of the 14px muted meta register. Use `text-[length:var(--text-meta)]`. `theme.css` documents why it deliberately maps no `--text-*` keys: they would collide with the unlayered `.text-meta`/`.text-body` classes.
99
+ - **Prose `a:visited`** shifts only `text-decoration-color` to `--color-fg-muted`. The link text keeps the accent, per the one-accent rule. There's no new token, so the upstream themes, `remarque-theme`, the audit and the golden gate are untouched. Ratified 7–0 (vote-1792782672108-js2he54).
100
+
101
+ ### Fixed
102
+
103
+ - **essay.css: TOC rail focus rings were clipped (#115).** The sticky rail's `overflow-y` made it a scroll container, which clipped the left edge of every link's 2px+2px ring and three sides of the summary's. The rail now pads past the ring's reach and pulls back by the same amount. TOC links and the summary get a 44px min-height (they were 18px tall below 80rem), and the disclosure marker sits beside its label.
104
+ - **broadsheet.css:** the lead's drop cap renders only from 40rem up. On a phone column the 3.5em italic capital left the deck a sliver about 20 characters wide.
105
+ - **prose.css:** `th.num`/`td.num` never wrap a value ("44–80" was breaking after its en dash).
106
+ - **AGENT_RULES Pitfall #2** recommended `max-w-reading mx-auto`, which its own Layout section forbids. That pattern also escapes `print.css`'s full-width reset, so printed headers were indented. It now recommends `.content-reading`.
107
+
108
+ ### Demo site (not part of the package)
109
+
110
+ No horizontal scroll at 375px on any page; nav "you are here" (`aria-current`) and Components reachable on mobile; the theme toggle exposes `aria-pressed`; an on-brand 404; onward links from essays, the dossier and Start; typographic quotes throughout; 44px hit areas; no autofocus scroll-jump on Components; the Specimen states the real meta/micro sizes; the tokens page prints its oklch values with tabular numerals; titles share their content's axis. New computed-style regression gates cover each of these, and the visual baselines were regenerated.
111
+
7
112
  ## 0.26.0 — 2026-07-23
8
113
 
9
114
  `remarque-new-page` skill (closes #108) + `token-drift.yml` optional audit input (closes #110) — the last two items of the skill-suite/CI program.
package/README.md CHANGED
@@ -14,7 +14,7 @@ Most developer sites inherit the visual language of SaaS dashboards or component
14
14
 
15
15
  - **Typography is the interface** — not color, not illustration, not animation. Three fonts (Newsreader, Inter, JetBrains Mono), each with a strict role.
16
16
  - **17px minimum body text** — one pixel above the industry default. The difference in reading comfort is immediate.
17
- - **46rem reading column** — derived from the typographic standard of 45-75 characters per line.
17
+ - **38rem reading column** — ~71 measured characters per line at 17px Inter, inside the typographic standard of 45–75.
18
18
  - **OKLCH color space** — perceptually uniform. A lightness of 0.50 actually looks mid-brightness.
19
19
  - **Self-hosted fonts** — no Google CDN dependency. Strict CSP. GDPR-compliant.
20
20
  - **AI-native** — designed to be consumed by Claude Code, Cursor, Copilot, and other AI coding tools with zero aesthetic drift.
@@ -54,7 +54,7 @@ Add `--json` to either `remarque-audit` or `remarque-drift` for a single structu
54
54
  @import "remarque-tokens/theme.css";
55
55
  ```
56
56
 
57
- **Tailwind v3** projects use the shipped config (`remarque-tokens/tailwind`) instead. A machine-readable token inventory ships as `remarque-tokens/tokens.json` (generated from the CSS — core/palette tiers, light+dark values) for tooling and AI agents, with a published JSON Schema at `remarque-tokens/tokens.schema.json` (also referenced by tokens.json's own `$schema` field) so tooling can validate it structurally instead of hand-parsing. tokens.json is conformant in spirit with the [Design Tokens Community Group](https://www.designtokens.org/) format (`$value`/`$type` on every token), with two deliberate divergences — see REMARQUE.md's "DTCG Conformance" section. Prefer copy-paste? Grab `fonts.css`, `tokens.css`, `tokens-core.css`, `tokens-palette.css`, and `fonts/` directly — `tokens.css` aggregates the two tier files, so all three CSS files travel together. Use string-form `@import './tokens.css'` only (some bundlers silently drop `@import url(...)` for local files).
57
+ **Tailwind v3** projects use the shipped config (`remarque-tokens/tailwind`) instead. A machine-readable token inventory ships as `remarque-tokens/tokens.json` (generated from the CSS — core/palette tiers, light+dark values) for tooling and AI agents, with a published JSON Schema at `remarque-tokens/tokens.schema.json` (also referenced by tokens.json's own `$schema` field) so tooling can validate it structurally instead of hand-parsing. tokens.json is conformant in spirit with the [Design Tokens Community Group](https://www.designtokens.org/) format (`$value`/`$type` on every token), with three deliberate divergences (oklch() color strings, per-token light/dark nesting, CSS-string dimension/duration values) — see REMARQUE.md's "DTCG Conformance" section. Prefer copy-paste? Grab `fonts.css`, `tokens.css`, `tokens-core.css`, `tokens-palette.css`, and `fonts/` directly — `tokens.css` aggregates the two tier files, so all three CSS files travel together. Use string-form `@import './tokens.css'` only (some bundlers silently drop `@import url(...)` for local files).
58
58
 
59
59
  ## For AI Agents
60
60
 
@@ -118,6 +118,25 @@ Output" shape changes) to pick up the current playbook.
118
118
  | `tailwind.config.js` | Tailwind CSS **v3** configuration (v4 projects use an `@theme` block instead) |
119
119
  | `package.json` | npm package manifest for `remarque-tokens` |
120
120
 
121
+ ### Sizes
122
+
123
+ Gzip (level 9) / brotli (max) bytes per shipped stylesheet, enforced by `scripts/test-budget.mjs` against `budgets.json` (current size + 10%, repo-only, not in the package). Bare `remarque-tokens` imports core + palette + prose.
124
+
125
+ | File | gzip | brotli |
126
+ |------|-----:|-------:|
127
+ | `tokens.css` (effective: core + palette + prose) | 15,788 | 13,269 |
128
+ | `tokens-core.css` | 4,790 | 3,995 |
129
+ | `tokens-palette.css` | 7,036 | 5,848 |
130
+ | `prose.css` | 4,949 | 4,139 |
131
+ | `essay.css` | 7,914 | 6,680 |
132
+ | `broadsheet.css` | 7,463 | 6,241 |
133
+ | `forms.css` | 7,709 | 6,378 |
134
+ | `print.css` | 2,529 | 2,093 |
135
+ | `theme.css` | 1,744 | 1,452 |
136
+ | `fonts.css` | 612 | 484 |
137
+
138
+ Minimal import: `tokens-core.css` + `tokens-palette.css` alone (about 12 KB gzip) work without `prose.css`; the same test checks that every `var(--x)` in the optional modules resolves to a core or palette token.
139
+
121
140
  ## Three-Font System
122
141
 
123
142
  | Role | Font | Usage |
@@ -146,7 +165,7 @@ See the [live demo](https://williamzujkowski.github.io/remarque/) for the origin
146
165
  |----------|-------|-----|
147
166
  | Body size | 17px | Measurably improves reading comfort over 16px default |
148
167
  | Line height | 1.75 | Generous — this is what makes prose readable |
149
- | Reading width | 46rem | ~70 chars/line at 17px Inter. Upper end of comfortable range for technical content |
168
+ | Reading width | 38rem | ~71 measured chars/line at 17px Inter (median; p90 76) |
150
169
  | Color space | OKLCH | Perceptual uniformity. Consistent across hues without manual tuning |
151
170
  | Top padding | 6rem min | The breathing room that says "this is editorial, not a dashboard" |
152
171
  | Border radius | 0.5rem max | Restrained. No "friendly SaaS" over-rounding |
@@ -191,6 +210,10 @@ deliberately stayed local.
191
210
  | Ambient/decorative animation | Site-local only, never in system modules — and the `prefers-reduced-motion` guard is mandatory, not advisory. A future audit scan may flag unguarded infinite animations (as a heuristic check, not a soundness proof). |
192
211
  | Landing-page entry counts | Permitted as meta-voice data in the Landing archetype **only when computed from real inventory at build time**. A hardcoded count is a violation — counts drift (see the upstream count-drift bug class this system just fixed in its own theme provider). |
193
212
 
213
+ ## Development
214
+
215
+ `npm test` runs every gate CI runs: the token audit, the freshness checks for the generated files (`tokens.json`, the registry, the packaged skills), the palette golden gate, and every `scripts/test-*.mjs` fixture suite, including the consumer pack smoke test, which needs the network. `.github/workflows/ci.yml` runs the same gates on every pull request on Node 22 and 24, and both the Pages deploy and the npm release `need` it, so nothing deploys or publishes from a red commit.
216
+
194
217
  ## Named For
195
218
 
196
219
  In fine art printmaking, a *remarque* is a small original drawing made by the artist in the margin of a proof print. Found only in limited editions, it is the mark that says: this was made by someone specific, with intention.