Foundations
True Black / Rose is the permanent default: black canvas, warm dark panels, the original rose foregrounds and dark-red actions. Exact roles and accessibility rules remain authoritative.
Identity
What the j3w1 theme is, where it came from, and the five rules that keep a port recognisable.
True-black canvas, editor and terminal interiors (#000000), neutral near-black panels and raised layers, rose interface text and terminal/agent prose, and distinct syntax and status hues (D-041). Red marks identity, focus, selection and danger; ordinary application content and terminal prose use rose; white is reserved for document headings, exceptional highlights and contrast-critical roles. Keep 1px square borders, zero radii and one monospace family for chrome, reading and code. Darkness is the canvas; readable content is the priority.
Five rules keep an implementation recognisable:
- Preserve the true-black base, rose application and terminal text hierarchy and restrained red identity. Use semantic colours to distinguish code, additions, warnings and information.
- Use the role assigned to a token, never the token whose colour happens to look closest.
- Keep content, selection, keyboard focus, diagnostics and changed text distinguishable from one another. Selection is a fill; focus is a ring.
- Nothing is rounded, blurred, glowing or gradient-filled. Elevation is a 1px border and, for modals, a backdrop.
- Preserve the host application’s behaviour, keyboard handling and accessibility settings. Geometry and typography limits of a host are documented deviations, not reasons to rewrite it.
Where it came from
The palette began on a Manjaro i3 workstation (j3w1/1w3j): a pywal ramp
from a red wallpaper, frozen and tuned slot by slot in config/Xresources.
The i3 configuration gave the rest: 1px borders, 14/−2 gaps, tabbed layouts,
dunst toasts framed in #ae1914, dmenu selections in #630f0d. Its web
version, j3w1/j3w1.github.io, named the roles this specification uses and
wrote the first contrast contract. Both are pinned in
references/sources.json. This repository is the authority: later changes in
either source are proposals, not automatic updates.
What “modern” means here
Clear structure, responsive layout, strong keyboard and screen-reader support, exact states, pleasant controls. Not pill buttons, glass panels, gradients, neon glows, a Material-style redesign, or red on every surface; the site draws such misreadings as labelled non-examples.
One exception (D-014): the generated preview beside each visible CSS hex literal on the spec page. It adds no palette, role or geometry rule, and text, copying, keyboard order and no-JS access stay intact.
- A 16px circle with its theme-derived 1px boundary; alpha shows over a checkerboard of theme tokens.
- On CSS
:hoverit becomes a 17.4px square (fast duration, ease-out on transform and border-radius; none under reduced motion). - Mouse and pen pointers also open a non-interactive popup with the exact
value; touch and unknown pointers do not. CSS
:hoveralone drives both, never thehoverorany-hovermedia features, which can report no hover despite a mouse. - The popup follows the pointer inside the circle at an 8px gap, flips when needed and keeps an 8px viewport gutter. Tracking has no animation.
- It closes when the pointer leaves the circle, on an outside press, Escape, loss of window focus, and on page or nested scrolling unless the circle is still under the last pointer position. It never captures input, holds itself open or adds a keyboard stop; token tables and the token inspector keep the persistent information.
Profiles
default— the approved everyday composition: true-black interiors, neutral application layers, rose application text and terminal/agent prose, red interaction accents, and semantic green, amber, blue, violet and cyan for coding and terminal content (D-041).code.syntax.*is coloured by default;code.hued.*remains the compatible semantic alias family for ports that already consume it.heritage-ansi— the sixteen Xresources slots and their historical assignments, exact, including those that fail the contrast floor. For archives and fidelity, never new interface text.extended— the earlier proposed, unapproved overlay for syntax, terminal and chart colours. D-041 updates the default directly; it does not promote this profile.
Naming
Roles are color.<group>.<role>, from a closed group list: surface, text,
border, interaction, action, status, code, diagnostic, diff,
terminal, chart, icon. Primitives are color.primitive.<family>.<step>,
the step rising with luminance; roles alias primitives, and primitives carry
provenance. A CSS custom property is the path with dashes for dots:
color.interaction.focus.ring → --color-interaction-focus-ring.
Foundations
Surfaces, text ladder, borders, interaction states, focus, typography, spacing, density, elevation, motion, icons and layout, with the rules that govern each role.
Values live in tokens/; this document says what each role means and where
it may be used. Every rule is normative for the default profile.
Surfaces
surface.desktop and surface.canvas are true black (#000000). Readable
content sits on the canvas or on:
default(#0d0707) for panels and cards;raised(#140b0b) for sticky headers, menus and popovers;overlay(#1c1010) for dialogs and drawers;accent(#3d0c0a) for the user’s own content set apart from its neighbours, such as outgoing chat messages; secondary text on it usestext.muted, nevertext.subtle. Red links cap its brightness: at this hue it stays at or below#3f0c0a(D-035);sunken(#000000) for reading and detail panes;inputfor form-control fills;- true-black
chromeand warm rose-blackchrome-altfor window frames and bars.
D-041 keeps ordinary application layers warm rose-black, without a cool blue cast; surface.accent remains a
bounded red message surface, not a tint for every panel.
The layers differ only slightly, so controls also carry border.control.
Never tint photos, video, document content or user-defined data colours.
Text ladder
| Role | Use |
|---|---|
text.prose | long-form reading; aliases rose text.default |
text.heading | white document/content titles and H1–H6 |
text.highlight | deliberate exceptional white highlights |
text.bright | brighter rose UI labels |
text.default | rose interface and body text |
text.muted | secondary text; the darkest colour permitted for ordinary chrome text |
text.subtle | metadata, comments, line numbers |
text.disabled | disabled controls only (exempt under WCAG 1.4.3; at least 3:1 by policy) |
text.accent | restrained red accent text on canvas and default surfaces |
text.accent-strong | red emphasis suitable for raised surfaces |
text.link | underlined links; hover uses text.link-hover on interaction.hover.bg |
D-041 uses rose for ordinary application text and terminal/agent prose
(text.default, #e99499); terminal prose and prompts use bright rose
(terminal.fg, #ffa2a7) for clearer small text. White is limited to document
headings, exceptional highlights and contrast-critical roles. Exact values
and ratios per surface are in the token and contrast reports. Metadata and
comments people need to read use text.subtle, never a decorative border or
historical ANSI primitive.
Borders
Every border is 1px (border.width.default). 2px (border.width.emphasis) is
only for the selected indicator bar, the sticky-header rule and the invalid
border. The quote rule, which marks a block rather than bounding a control, is
the one wider edge, at twice the emphasis width.
border.divider(#482b2b) separates rows and sections.border.default(#482b2b) is decorative, never the only visible edge of a control.border.control(#c81a1a) is the edge of every form control at rest.border.active(#e53935) marks the focused or active control.border.overlay(#c81a1a) frames every floating layer.
Styles: solid by default; dashed only for focus, drop targets and read-only
fields; dotted for hints; double for conflicts. Radius is 0 everywhere,
including checkboxes, switches, chips, avatars, badges and tooltips.
Interaction states
Every interactive component declares which of these apply and demonstrates every applicable one, including the combinations. Precedence when several apply: disabled > loading > invalid > selected > pressed > hover; focus-visible is always drawn on top and never suppressed by another state.
| State | Visual | Non-colour channel |
|---|---|---|
| default | rest tokens | — |
hover (@media (hover: hover) only) | background → interaction.hover.bg; links use it too with text.link-hover, menu items → interaction.hover.bg-strong | link underline thickens 1px → 2px; cursor |
| active / pressed | background → interaction.pressed.bg; border → border.active | border change; no translation |
| focus-visible | the ring (below) | the ring itself; never a glow or a colour shift alone |
| selected | fill interaction.selection.bg with interaction.selection.text; tabs and navigation use a 2px border.selected-indicator | aria-selected / aria-current; a check glyph in lists |
| selected + focus-visible | the selection fill and the ordinary ring (rule 1; D-027) | both visible at once |
| text selected, host keeps the text colour | translucent interaction.text-selection.tint (D-036), which also suits hosts that paint the fill over their content; where the host can recolour selected text, interaction.text-selection.bg with interaction.text-selection.text | the host’s selection handles |
| selected, container inactive | interaction.selection.inactive-bg with interaction.selection.inactive-text; no ring | lightness drop between the two fills |
| disabled | text.disabled, border.disabled, interaction.disabled.bg; no hover or pressed styling; still focusable when aria-disabled | disabled / aria-disabled; cursor: not-allowed (rule 5) |
| invalid | border → status.danger.border at 2px; message in status.danger.text with the ✕ glyph; aria-invalid; aria-describedby | border width 1 → 2px; glyph; message |
| invalid + focus-visible | the 2px danger border and focus.ring-contrast at focus.offset-invalid | double boundary |
| read-only | text.default; 1px dotted border.divider bottom; no fill; focusable; selectable | readonly; dotted edge |
| loading | size unchanged; a static ⋯ glyph replaces the leading icon; aria-busy | glyph; no animation under reduced motion |
| busy / indeterminate | static 45° stripes of action.primary.bg on interaction.pressed.bg; aria-valuetext | stripes |
| checked / mixed | fill action.primary.bg, glyph ✓ or – in action.primary.text | glyph |
| expanded / collapsed | SVG chevron rotated 0 / 90° within the motion budget | aria-expanded |
| current | 2px border.selected-indicator and text.bright | indicator bar |
| drop-target / dragging | dashed interaction.drop-target border; interaction.marquee fill | dashed pattern |
Rules that apply everywhere:
- Selection is a fill; focus is a ring. Neither borrows the other’s form. The ring must reach 3:1 on whatever it sits over:
#e53935falls below it onaction.primary.bgandstatus.danger.fill, so the ring recolours on those fills; it meets it oninteraction.selection.bg(D-027), so a selected row keeps the ordinary control ring. - Focus ring (D-006; component mapping clarification D-015). Controls and rows:
outline: 1px dashed {color.interaction.focus.ring}; outline-offset: -2px. Focusable containers (panes, windows, dialogs, cards):outline: 2px solid {color.interaction.focus.ring-container}; outline-offset: -3px. On filled surfaces use the component’s explicit ring mapping: primary buttons and checked controls usefocus.ring-contrast(the control ring ininteraction.focus.ring-container, D-040) where declared; destructive buttons use their declared on-fill text role. The selection fill is the exception and takes the ordinary ring, on the measurement in rule 1. Keep the control or container geometry independently of the colour mapping.:focus:not(:focus-visible)draws nothing. Forced-colors mode usesHighlight. A host focus indicator is never removed without this replacement. - Never colour-only. Every state and every status carries a second channel: a glyph (
✕ ! ✓ i ·), a border width, an underline pattern, or an ARIA state. Charts carry pattern fills. - Hover changes fills and borders, never text colour alone, and only on hover-capable pointers.
- Disabled never uses opacity. Opacity leaks the background and breaks contrast accounting.
- Contrast floor. Text ≥ 4.5:1 and control boundaries, rings and meaningful graphics ≥ 3:1 against the actual background of the state, measured after compositing translucent colours.
- Motion. Durations are 80 / 120 / 150 ms at most, on opacity, transform and background only; under
prefers-reduced-motion: reduceevery duration is 0.01 ms, nothing slides or shimmers, and no behaviour waits ontransitionend.
Actions
Four button tones:
primary, the only filled button at rest:action.primary.bg(#7d1310) withaction.primary.text(bright rose; D-041).secondary, an outline:border.control, texttext.bright.tertiary, a ghost or icon button:text.default, hoverinteraction.hover.bg.destructive, an outline inaction.destructive.text(#f73f35) that fills on hover withaction.destructive.hover-bg(#dc282e) and#f9faf9text. A filled variant exists for confirm dialogs.
Destructive actions also need a confirmation step or an undo; the colour is not the safeguard.
Status, diagnostics and diffs (D-001)
D-041 updates the palette while preserving the D-001 semantic roles.
Five statuses: red danger, amber warning, green success, blue info,
and grey neutral. Each has text, fill, on-fill, tint and border
roles, a required glyph (✕ ! ✓ i ·) and its own underline or border pattern
(wavy, dashed, solid, dotted, double). Use the explicit on-fill role on a
status fill, not ordinary body text. Coding and terminal roles also have
violet and cyan; these distinguish syntax and named terminal colours rather
than tinting ordinary application chrome. Every use follows its role and
measures contrast on the actual surface.
Diagnostics:
- error: wavy
#e53935underline,#f73f35text,#e53935stripe,#2b0e0dline background; - warning: wavy amber;
- info: dotted blue;
- hint: dotted
text.subtle; - unused:
text.mutedwith a dotted underline.
Diffs:
- added:
#0f1a0eline background, green gutter mark and+; word-level additions as a 2px green underline (no fill, so text contrast is untouched); - removed:
#2b0e0dbackground, red gutter mark and−; word-level removals underlined and struck through; - modified:
#1f1a0cbackground, amber gutter mark and~; - conflicts: a double
#f73f35border.
Always keep textual markers and existing status icons.
Code and terminal
The surface and interior roles remain distinct in scope, but D-041 makes
surface.code, surface.terminal, code.bg, code.gutter-bg, terminal.bg
and default ANSI 0 all true black (#000000). This replaces the earlier
#0c0909 interior assignment without changing historical ANSI primitives.
Editor surfaces:
code.current-lineuses the neutral default layer;code.line-numberuses the readable subtle tier; active numbers and the caret step up to the normal and bright text tiers;code.selection-bgis a subdued red selection fill;code.search-match-bgmarks matches, with the current match inverted bycode.search-current-bgandcode.search-current-text;code.bracket-matchis a 1px accent border;code.indent-guideis a neutral separator andcode.whitespaceis a decorative neutral mark.
Syntax in default is semantic: violet keywords, green strings, amber
numbers/constants/attributes/escapes, blue functions, cyan types/properties,
coral tags/invalid text, rose variables/operators, muted punctuation and
subtle italic comments. Headings use the bright text tier. The existing
code.hued.* roles resolve to the corresponding semantic syntax assignments;
ports need no separate palette. Invalid text takes a wavy underline and
deprecated text a line-through. Languages inherit these roles; a
language-specific override needs a written reason. Selection and diff fills
must be checked with the syntax foregrounds they retain.
Terminal slots:
heritage-ansicarries the sixteenXresourcesslots exactly.color.primitive.ansi.*remains byte-for-byte historical; the heritage profile explicitly restores its historical slot and background assignments.defaultuses the semantic sixteen (D-041): black, red, green, amber, blue, violet, cyan and rose, followed by their bright variants. Slots 7 and 15 both use bright rose#ffa2a7for readable terminal text. Slot 0 is the true-black background; slots 1–15, including neutral bright black, target at least 4.5:1 on it. Slots 2 and 10 remain green so programs using ANSI additions can be read correctly.extendedremains a proposed overlay, not an approved alternative needed to obtain coloured syntax or terminal slots.
Programs choose the slots and may emit 256-colour or true-colour values that
bypass them. A theme cannot repair host dimming or independently recolour
text that shares a host theme key with a background. Port guides record
those limits; Claude Code’s shared word-highlight and edit-summary keys are
one example. Their dedicated diff.added.shared-word and
diff.removed.shared-word roles balance summary-on-black and ordinary
rose-on-highlight at about 3:1. These roles apply only where a host combines
those two uses; they do not replace standard diff emphasis or relax the
ordinary-text contrast floor. Syntax foregrounds require separate host review.
Typography
One family: SauceCodePro NFM (Source Code Pro in the Nerd Fonts build),
falling back to Source Code Pro, Cascadia Mono, Consolas, Liberation Mono,
DejaVu Sans Mono and monospace. The site records size-adjust metrics for
the fallbacks; no font file is distributed. Weights 400 and 700, plus italic.
Scale (size/line-height in px): ui-sm 12/16, ui-md 13/18, ui-lg 14/20,
reading 15/24, code 13/19, terminal 13/19 with −0.5px letter-spacing,
h1 20/28 bold, h2 16/24 bold, h3 13/18 bold in text.heading, caption
11/16 only with text.muted or lighter. D-037: display 32/40 for
page heroes and metric figures and title 24/32 for page titles above h1;
components keep h1–h3. Hosts keep the size the user chose; tiny UI text is
a metric of the reference implementation, not a rule.
Spacing and density (D-009)
Spacing steps: 0, 1, 2, 4, 8, 12, 16, 24, 32, 48 px. compact is the default
density (D-038): it applies wherever no data-density is set, in components,
the site and copied bundles. Set data-density="comfortable" on any subtree to
opt in to the larger mode:
| Mode | Control | Row | Type | Horizontal padding | Gaps | Use |
|---|---|---|---|---|---|---|
compact | 24px | 28px | ui-sm | 8px | 2px | matches the workstation |
comfortable | 32px | 36px | ui-md | 12px | 4px | everyday applications |
Both meet the 24×24 CSS-pixel target minimum; compact needs 4px between
neighbouring targets. Table rows always use the comfortable row height (a data
row is content, and compact rows are harder to scan); controls inside a row
follow the page density. The site’s 3px i3 gap, 14/−2 window gaps and 28px bar
are window-manager geometry, not theme tokens.
Elevation and layering
No shadows, except shadow.floating (0 6px 22px rgb(0 0 0 / 55%)) on the i3
floating-window specimen. A floating layer is a 1px border.overlay on
surface.raised; a modal adds surface.backdrop (rgb(0 0 0 / 65%)).
Stacking: canvas 0 < raised 1 < popover 10 < drawer 20 < dialog 30 < toast 40 <
skip link 1000.
A popup opened by hovering follows the pointer:
- It sits 8px from the cursor, flips to the cursor’s other side instead of crossing a viewport edge, and keeps 8px from every edge.
- It moves again on pointer move, scroll and resize.
- A popup with nothing to operate takes no pointer events; one with a control or link stays reachable and open while the pointer is inside it.
- Either kind closes when the pointer leaves both the trigger and the popup; the reachable kind waits 300 ms first, so the pointer can cross between them, and stays while keyboard focus is on its trigger or inside it.
Opened from the keyboard, the same popup anchors to its trigger: below it, or above when there is no room below. This is a placement rule only; what opens a popup and what kind it is belong to the component’s contract.
Icons
SVG line icons on a 16px grid, 1.5px stroke, stroke: currentColor,
fill: none, sizes 12 / 16 / 20. No icon font is needed; Nerd Font glyphs
appear only in terminal and heritage specimens. Icon-only controls carry
aria-label and a tooltip. icon.decorative (#986f72) is for glyphs that
mean nothing on their own.
Layout
Reading measure 72ch, content column 680px, application layout 1440px. Breakpoints 320 (reflow floor), 600, 900, 1280.
- Components use container queries.
- Sidebars collapse to drawers below 900px.
- Data tables scroll horizontally, with a sticky first column, below 600px.
- The page never scrolls horizontally at 320px.
- Right-to-left mirrors breadcrumb separators, pagination, sidebar and drawer sides, wizard progression, affixes and tree indentation.
Typography semantics (D-033)
All content titles and H1–H6 use text.heading; size, weight and spacing carry
the hierarchy. Chrome and window titles use text.default or text.bright;
body and long-form prose use text.default and text.prose. Bold inherits the
current semantic colour, including inside headings, muted text, links and
selections, and never requests white by itself. D-041 keeps ordinary application text and brighter UI labels rose,
with white reserved for document headings, exceptional highlights and
contrast-critical roles. Terminal and agent prose uses rose terminal.fg; the semantic hierarchy
and inherited bold rule remain. Selection and on-fill roles are explicit and
must be measured against their actual backgrounds. The historical D-033
migration audit is docs/rose-typography-migration.md.
Accessibility
Contrast targets, focus and keyboard contracts, reduced motion, forced colours, and what an automated pass does and does not prove.
What is claimed
WCAG 2.2 criteria are measurable design targets for the theme’s roles. A green contrast report and a clean automated scan show that the specified colours and the reference markup meet them; they are not a conformance claim for any adopting application. Keyboard operation, focus management, labels, reflow and manual review stay the consumer’s job. The theme’s own limits are under Limitations.
Contrast
- Ordinary text: at least 4.5:1 against the actual background of its state, measured on resolved sRGB values after compositing translucent colours.
- Large text (18.66px bold or 24px regular and above): at least 3:1.
- Control boundaries, focus rings, meaningful icons and chart marks: at least 3:1 (WCAG 1.4.11).
- Disabled text is exempt under WCAG 1.4.3; house policy still needs 3:1
(
text.disabled), below the ordinary-text floor by design. - Never round a value into a pass. The report keeps the unrounded ratio and
shows two decimals:
4.4996reads 4.50 and fails. - Heritage values that fail (
#a3676bas text,#7d1310anywhere as text) are flagged$deprecatedin theheritage-ansiprofile, never shown as ordinary passes.
exports/contrast.md lists every declared pair (the global pairs in
spec/contrast.json and every component’s stateTokens and contrast[]
entries) with its computed ratio. Waived pairs carry their reason; a waiver is
valid only for decorative graphics that are never the only boundary of a
control.
Text hierarchy
The colour roles are in spec/foundations.md, Typography semantics (D-033);
D-041 uses rose application text and terminal/agent prose; white is limited
to document headings, exceptional highlights and contrast-critical roles. Bold inherits its
context. Selection and
contrast-critical on-fill roles are checked against their actual
backgrounds. Heading level is conveyed through structure, size and weight,
never colour alone.
Focus
Focus is never lost and never invisible. The ring is defined once, in
spec/foundations.md, Interaction states, rule 2 (D-006, D-015).
:focuswithout:focus-visibledraws nothing: pointer clicks stay quiet, keyboard focus stays visible.- The state matrices render every declared combination, including
selected+focus-visibleandinvalid+focus-visible, where rings disappear in practice.
Keyboard
Native controls keep native keyboard behaviour. Custom widgets follow the
WAI-ARIA Authoring Practices pattern in their aria.apg:
- roving tabindex in toolbars, tabs, trees and listboxes;
aria-activedescendantin comboboxes;- focus trapping and
Escapein dialogs and drawers; - arrow keys in radio groups and menus.
Positive tabindex is banned. Every icon-only control has an accessible name.
The site’s own controls (search, filters, inspector, copy) work with the
keyboard, and the no-JS build keeps every normative section readable.
Motion
Motion follows spec/foundations.md, Interaction states, rule 7. Under
prefers-reduced-motion: reduce:
- toasts, dialogs and drawers appear in place;
- indeterminate progress shows static stripes;
- loading glyphs do not spin;
- skeletons do not shimmer;
- tab indicators do not slide.
Forced colours and high contrast
Under forced-colors: active, rings use Highlight and selections
Highlight / HighlightText. Borders survive because they are real borders,
not box-shadows; pattern fills keep charts and diffs readable.
Reflow, zoom and text
Components reflow at 320 CSS pixels without horizontal page scrolling, at 200%
browser zoom and at 200% text-only zoom. Rings and borders stay 1px / 2px CSS.
Targets are at least 24×24 CSS pixels, with 4px spacing in compact density.
Long labels wrap, or truncate with a visible affordance; the stress fixtures
(English, Spanish, Chinese, Arabic with dir="rtl") exercise this.
Colour is never the only channel
Every status has a glyph; every state a border, pattern or ARIA change. Diffs
keep their + − ~ markers. Charts carry pattern fills and a table fallback.
The heritage-ansi terminal palette has less hue difference by design, a
recorded limit.
Limitations (recorded, not hidden)
- The theme specifies colours, states and reference markup. It cannot make a host application’s own focus management, labels or reading order correct.
- The automated scan uses axe-core with the WCAG 2.x A and AA rule sets and does not test every success criterion. Manual checks (keyboard walks, screen-reader reading, zoom) need their own execution evidence; a missing protocol stays not run.
- The default semantic terminal palette targets 4.5:1 for slots 1–15 on
true-black
terminal.bg(D-041); ANSI 0 is the background. Inheritage-ansi, slots 1, 4, 6, 8, 9, 10, 12, 13, 14 and 15 fail 4.5:1. Host dimming, non-default backgrounds and explicit true-colour output need their own measurements. - Editors and diff viewers may preserve syntax foregrounds over selection or change fills. Check those actual pairs, not only syntax on the canvas. Claude Code reuses word-highlight backgrounds as edit-summary foregrounds; its port documents the resulting limitation separately from normal diff line contrast.
interaction.text-selection.tint(D-036) requires the host’s compositing behavior to be known. A tint under text and the same tint painted over text have different contrast. The global pairs composite it over the canvas; Telegram’s tests cover its raised and accent surfaces. Those measurements do not certify every host, selected link or syntax span. The earlier rose-palette selection measurements are historical after D-041 and cannot be carried forward as current results.
Verification evidence and freshness
Source coverage. The coverage ledger records source facts: specified,
demonstrated and test implemented. exports/coverage.json schemaVersion 1
adds testImplemented and keeps tested as a compatibility alias meaning the
test file exists; component JSON and family totals use the same definitions.
No coverage Boolean proves an execution or WCAG conformance.
Execution evidence. The static verification report gives per-test outcomes and per-component summaries. Evidence schemaVersion 1 records, for each result:
- the method (automated or manual), category and component;
- the states and variants it explicitly covers (empty lists mean “unspecified”, never “all”);
- browser and version, OS, viewport, profile, density and JavaScript setting;
- a run or protocol reference.
The recorded profile and density are the page’s declared data-profile and
data-density when the test ends; tests that switch them say so in their
scope. One browser engine never stands for
another. Skipped, failed, not run and not applicable are separate outcomes,
and every non-pass has a reason. Manual screen-reader and keyboard claims need
protocol records. The automated suite targets Chromium; other engines stay not
run.
Freshness. A claim is current only while the tested artifact’s digest and a broad source fingerprint both match. The fingerprint covers tracked contract sources, schemas, generators, site code, tests, agent guidance, CI configuration and the package lockfile, so an unrelated change can stale more evidence than strictly needed. A stale record keeps its real outcome and reference but no longer shows a current pass.
How the report is built.
- A build first publishes a static not-run report and a subject manifest.
- The browser reporter checks that source and specimen assets still match that subject before it publishes execution evidence.
- Report generation writes only under
dist/verification/and checks that every specimen byte is unchanged. - The report’s own presentation has a separate static and browser gate; it never certifies itself.
Run timestamps and revisions live only in this post-test evidence, outside the committed exports and their digests, so no hash depends on itself.
Run it locally.
- Build the site.
- Run the browser suite.
- Run
npm run verification:report, thennpm run verification:check, thennpm run test:verification.
A local run names its local execution and digests and is never presented as hosted CI evidence.
Record a manual protocol. Pass a schema-valid evidence document to
node scripts/verification-report.mjs <repo-relative-evidence.json>, carrying
the real tested digests, the browser and environment, and a protocol:
reference with the protocol identifier and recorded observations. Keep the
protocol with the evidence; never copy private artifacts into this public
repository.