Release notes

Audit & release notes

A design system isn't done at launch — it's done when it stays right under pressure. This page is the public artifact of every maintenance pass since v1.0 shipped — each finding, decision, and fix, in the open. Newest first.

Cumulative totals · all releases v1.0 → v1.6 · latest pass Receipts (documentation and record pass)
Findings logged
26
Across a11y, motion, contrast, naming, color, and architecture.
Fixes shipped
23
Tokens, component patches, and a three-tier architecture.
Deferred
3
Out-of-scope or needs broader RFC.
Breaking changes
0
No class removed since the initial public release.

Why this page exists

Most of the systems I work on live behind NDAs. I can't show the dashboards, the monitoring panels, or the operator tools that pay the bills. Tiny Wire is the public-safe twin — same constraints, same density, same warm operational tone — built so the process is visible without leaking a single line of client work.

This audit is the proof. It shows what shipping a v1.0 and then maintaining it actually looks like: catching the regressions, defending the decisions, and writing down enough context that the next person (or the next quarter's me) doesn't have to relitigate them.

v1.6 — Receipts

2026-09-20 · documentation and record pass, zero breaking changes.

Every earlier release audited what the CSS does. This one audits what the documents say. A record layer built alongside the system — a page per consumer, per promotion candidate, per sameness call — got checked against the four consumer repos by script, and the script disagreed with the documents eight times. What follows is mostly claims that were true when written and quietly stopped being true.

One thing shipped in lib/: the stat tile, as Card parts (.card-value, .card-delta), because three consumers and both docs pages had each built it from inline styles. Everything else is the paperwork catching up — and then being wired so it cannot fall behind again. The counts on the README, the introduction, the components page, this page's tiles, and the hub's tables are rendered from the source files now. Typing one is overwritten.

IDFindingSeverityStatus
F-024 The hub's registry claimed all four consumers matched the source with no drift. Two did not: .card-value / .card-delta landed on main after v1.5 with no version bump, so both static consumers held a faithful copy of their pin while the source moved on. The registry gained a Drift column and the claim is now rendered from the record, not typed. Medium Fixed
F-025 The introduction page said the brand is forest green. v1.5 moved brand to cobalt — this page said so, one click away. A stale claim on the first page a reader sees. Medium Fixed
F-026 Finding IDs F-013–F-018 meant different findings on this page and on the A11y check page, and the changelog referenced both. Renumbered: this page's later findings start at F-018, the checker keeps F-013–F-017, and there is one ID space. Medium Fixed
F-027 The README's token count read “~180” and had not been recounted since v1.4. lib/tokens.css declares 186. The count is now rendered from the file. Low Fixed
F-028 Six class families shipped in components.css with no section on the components page — .dot, .monitoring-dot, .separator, .spinner, .status-bar, .kbd. Not cosmetic: a consumer rebuilt .status-stat locally because it could not find it. All six are documented, and the renderer now fails loudly when a shipped family has no home. Medium Fixed
F-029 CONTRIBUTING told contributors to add new tokens to lib/globals.css. Tokens are declared in lib/tokens.css; globals.css is a compatibility shim. Anyone following the guide would have put a token where nothing reads it. Low Fixed
F-030 Tiny Wire.html sat at the repo root — 1,579 lines, a single-file copy of the system carrying its own hardcoded token values from a mix of releases, linked from nowhere. Removed; git history keeps it. Low Fixed
F-031 No release had ever been tagged, and /VERSION had been edited exactly once. A consumer pinned to “v1.5” could not be checked against v1.5, because nothing in the repo could produce it. This release is tagged, and every release from here is. Medium Fixed

v1.5 — Reserve green

2026-06-14 · semantic-color pass, zero breaking changes (back-compat aliases kept).

Building a conceptual attestation prototype on the system surfaced the finding the manual eye had normalized: green was doing double duty — the brand accent and the semantic "healthy / attested / safe" signal. When the brand mark, primary buttons, focus rings, and the "this is safe" chip all share one green, the trust signal stops popping. v1.5 reserves green strictly for a new --success family (attested / approved / safe / healthy / running) and moves brand + all interaction onto the existing cobalt — so the rarer green now reads as a real verdict. Token names are unchanged and the old .alert-brand / .banner-brand / .tag-brand remain as aliases, so the prototype inherited the entire shift through the semantic tokens — no per-screen edits. A free win fell out of it: moving brand off green closed the longest-deferred a11y finding (brand-on-white text, 3.54:1 → cobalt 8.3:1).

IDFindingSeverityStatus
F-022 Green carried both the brand accent and the "safe / attested" status meaning — overloaded, so the trust signal never popped. Reserved green for a new --success family; brand + all interaction moved to cobalt; back-compat aliases kept. Medium Fixed
F-023 The green-as-text contrast issue moved with the role: --success (green-500) as text on light tints is still ~3.5:1. Guidance shipped — --success-dark for text, bare --success for non-text (dots, borders, fills); a lint rule against bare --success on text is queued. Low Deferred · lint rule

v1.4 — Live demos

2026-06-13 · docs-experience pass, no token or class changes.

A structured UX critique (FLOWIE) found the docs were full of false affordances — demos that looked interactive but did nothing — plus wayfinding gaps and a scroll order that didn't track the nav. All fixed without touching the CSS-only system: the docs gained a thin vanilla-JS layer so every demo is honest (tabs, accordion, popover, command search, dialog/sheet now work), code blocks copy in one click, long pages get an auto table of contents + back-to-top, Foundations gets token search, and every page's scroll order now equals its sidebar order. The orphaned sidebar-rail page and the dead "view diff" links were fixed too. Full list in the changelog.

v1.3 — Pedigree pass

2026-06-12 · additive, zero breaking changes.

A systematic rigor + signature pass. The neutrals consolidated into one coherent warm-neutral ramp; a single cobalt accent replaced the two overlapping blues and now owns focus; the warning brown became a disciplined amber; and the whole set was restructured into a three-tier primitive → semantic → component architecture — values only, every public token name preserved, so downstream prototypes inherit it unbroken. Type moved to a 1.2 modular display scale with em tracking and a mandatory tabular-numerals token; the default radius sharpened 10→8; floating overlays dropped their solid border for a hairline and let elevation do the lifting. Four issues surfaced and were fixed in the same pass:

IDFindingSeverityStatus
F-018 Dark --text-tertiary failed AA on --surface-inset (4.05:1). Lifted #948682 → #A0928E; the live checker now passes every pair in both themes. High Fixed
F-019 lib/tokens.js had drifted from globals.css — three values (light/dark tertiary, dark disabled) shipped pre-AA hexes to JS consumers. Resynced. Medium Fixed
F-020 The .select chevron was a hardcoded #8A7E7D (a stale token value) that read low-contrast in dark mode. Repointed to a mid-neutral legible in both themes. Medium Fixed
F-021 Floating overlays stacked a solid border and a full elevation shadow — a doubled container signal. Border → --border-hairline; elevation does the lifting. Low Fixed

v1.2 — Sidebar rail

2026-06-11 · additive, zero breaking changes.

Added .sidebar--rail — the collapsed, icon-only form of the app sidebar (56px, driven by --sidebar-w). It reuses every existing .sidebar* rule and token: labels hide, icons center and grow to 20px, the active state is unchanged. See the Sidebar component and the rail demo; the full entry lives in the changelog. Scope was the rail only — the deferred a11y findings below remain queued for a later release.

v1.1 — Findings

Twelve issues, grouped by theme. Click a filter to scope the list. Every fixed finding links to the line that changed.

The live checker's five findings from the same release, F-013 – F-017, are on the A11y check page and keep those IDs. The audit's later findings start at F-018 — one ID space across both pages.

Filter
F-001 Critical Accessibility Motion
No prefers-reduced-motion support
Fixed

The monitoring dot ripple, the skeleton shimmer, the modal entrance, the toast slide-up, and the cross-fade on theme change all kept running regardless of OS reduced-motion settings.

A user with a vestibular disorder visiting the docs site would see ambient pulses they can't turn off. This is a WCAG 2.3.3 fail.

Added a global @media (prefers-reduced-motion: reduce) rule in globals.css that crushes every animation and transition to ~1ms. Components don't need to opt in.

Ambient pulses freeze, entrances become instant, but state changes still happen — just without the choreography.

lib/globals.css · +9 lines • View on GitHub ↗
Try disabling motion in your OS — this dot stops pulsing.
F-002 Critical Accessibility
Focus ring overrode invalid-state borders
Fixed

The global :focus-visible rule used border-color: var(--brand) !important, which silently overrode the red border on an aria-invalid="true" input the moment a user focused it.

So the only state where the user most needs to know the field is wrong — when they're standing on it — was hiding the signal.

Dropped the !important border override. The 3px shadow ring (--shadow-focus) is now the universal focus indicator, and components own their own border colour — so invalid inputs stay red whether focused or not.

Before — focused invalid
After — focused invalid
lib/globals.css · −1 line • View on GitHub ↗
F-003 High Accessibility Contrast
.btn-link read as disabled text
Fixed

The link button used --text-tertiary (#8A7E7D on white = 3.85:1 — failing WCAG AA for body copy at 4.5:1). It also visually clashed with disabled-text styling, so users hesitated to click it.

Switched to --info (#1B4079) — a deep navy that carries link semantics across the system and clears AA comfortably. Hover now lifts to --text-primary with a thicker underline.

Before
Read more 3.85:1
After
9.4:1
lib/components.css · btn-link • View on GitHub ↗
F-004 High Motion
Global colour transition slowed every hover
Fixed

v1.0 had a permanent :root * rule that transitioned background-color, border-color, color, fill, and stroke over 220ms — so the cross-fade on theme change would be smooth.

But that rule also ran on every hover, focus, and click — slowing the intended 150ms button hover and making icon colour changes feel laggy.

Demoted to a one-shot .theme-swapping class the toggle adds on click and removes after 280ms. Theme cross-fade still looks smooth; every other interaction is back to snap.

Toggle the theme — notice the swap still feels deliberate, but button hovers are now instant.

lib/globals.css + docs/docs.js • View on GitHub ↗
F-005 High Contrast
Dark-mode disabled text was invisible
Fixed

In dark mode, --text-disabled (#5F5450) on --surface-muted (#2A211E) landed at ~2.3:1. Disabled buttons looked empty. Even users without low vision struggled to read the label.

Lightened to #756763 (~4.1:1) — still visibly de-emphasised, but the label survives. Light-mode --text-disabled already passed; left alone.

Before
After
lib/globals.css · --text-disabled (dark) • View on GitHub ↗
F-006 Medium Contrast
Tertiary text was borderline on AA
Fixed

--text-tertiary (#8A7E7D on white) landed at 3.85:1 — fine for UI elements, but the same token was being used for helper text and placeholders, where AA wants 4.5:1 for normal body copy.

Darkened one step to #7A6E6D (4.65:1). Visually still reads as a third-tier label; functionally now AA-compliant. Cascades to every helper, hint, secondary nav link, and breadcrumb.

lib/globals.css · --text-tertiary • View on GitHub ↗
F-007 Medium Accessibility
Switch and slider had no keyboard focus ring
Fixed

Both controls killed their default outline in v1.0 but never added a custom ring. The global :focus-visible shadow was clipped by the switch's own background fill, and never reached the slider thumb at all. Keyboard users couldn't tell what was focused.

Switch now carries a two-stop shadow (surface gap + ring) to clear its background. Slider explicitly applies a 4px ring to both the -webkit- and -moz-range-thumb pseudos.

After — tab onto these
lib/components.css · .switch, .slider • View on GitHub ↗
F-008 Medium Naming
Missing .banner-warn
Fixed

Banners shipped with danger, brand, and info — but no warning variant. Meanwhile .alert-warn and .chip-warn both exist. A consumer reaching for "banner-warn" by analogy would get nothing.

Added it. Inherits from the same --warning token; same border + tint formula as the other banner variants.

lib/components.css · +5 lines • View on GitHub ↗
F-009 Low Naming
.dot-amber wasn't amber
Fixed

The .dot-amber class pointed at --warning — which is a warm brown, not amber. Anyone naming a status by colour and getting brown out is going to file a bug.

Added a semantic .dot-warn for the brown, and re-pointed .dot-amber at the actual --chart-amber token (#B98326). Existing consumers using .dot-amber will see a colour shift — flagged in the changelog as a deprecation.

.dot-warn
.dot-amber
.dot-red
lib/components.css · .dot-* • View on GitHub ↗
F-010 Low Accessibility
No indeterminate checkbox state
Fixed

The "header" checkbox in a data table — the one that's neither fully checked nor fully empty when some rows are selected — had no visual treatment. Native browsers render a dash; v1.0 didn't style it.

Added .checkbox:indeterminate with a centred 8×2 white dash on the brand-green fill. Matches the checked state's visual weight.

lib/components.css · +5 lines • View on GitHub ↗
F-011 Medium Accessibility
Toast / spinner / skeleton ARIA hooks not documented
Deferred · backlog

Live-region components ship as CSS only — no role="status", aria-live, or aria-busy guidance in the docs. Consumers who copy the markup verbatim ship inaccessible feedback.

Adding placeholder selectors in CSS would lie about coverage. The real fix is doc updates with full examples per component — a writing pass, not a styling pass. On the backlog alongside the toast queue manager.

Tracking issue: #TW-014.

docs/components.html · backlog
F-012 Medium Accessibility
Dialog has no focus-trap implementation
Deferred · v2.0

Tab order escapes the dialog into the page behind it. The dialog also doesn't restore focus to the trigger on close. Without JS, this is unavoidable — v1.0 is CSS-only by design.

Fixing it properly means shipping a small JS controller (data-dialog-trigger + data-dialog-target), which is a v2.0 question: do we want Tiny Wire to have behaviour, or stay CSS-only? Drafting an RFC instead of shipping something half-baked.

RFC · TW-018 (drafting)

How I audit

Three passes, three goals. I run this same loop on every system I maintain, internal or public.

01
Read the contract

Walk every component and ask: what does the class promise? Every CSS rule that contradicts the promise — silent overrides, missing states, name/value drift — gets logged.

02
Stress the edges

Tab through every interactive element. Toggle to dark mode mid-interaction. Trigger every aria-invalid, disabled, and indeterminate. The edges are where v1.0 leaks.

03
Defend what stays

Not every finding is fixed in the same release. Deferred items get a tracking ID, a scheduled version, and a reason — so the next quarter's me doesn't relitigate them from scratch.

Decisions

A design system is as much about what you refuse to add as what's in. These came up during audits and were decided; each says when. The findings above are the checked part of this page — this section is the judgment part.

A third type weight ramp

Bricolage + DM Sans is the system. Adding a mono display family "for code-forward dashboards" was floated and rejected — every font is a maintenance contract.

Decided at v1.1 · 2026-05-20
A separate "compact" density

Tiny Wire is already dense by default (13px base, 32px controls). A "compact" override would double the surface area of every state matrix. If 13px is too big, override the token at scope.

Decided at v1.1 · 2026-05-20
Hand-drawn iconography

The system doesn't draw an icon set — owning a set means maintaining a set. Amended: it names one library, Phosphor, and will ship size and stroke conventions, so consumers stop hand-drawing. Still no bundled glyphs.

Decided at v1.1 · 2026-05-20 · amended 2026-09-18
A second brand colour

The temptation is real — every operational tool has a "secondary accent" moment. But every additional brand-mapped token doubles the override matrix for re-skinning. Override --brand at the consumer's scope instead.

Decided at v1.1 · 2026-05-20

What's next

Not a roadmap. The candidates table in the Design System Hub is what's next — every row is something a real app built, counted, and sorted by how many apps built it. Deferred findings above keep their IDs until they close.

Every release ships with a page like this. The point isn't the changelog — every repo has one of those. The point is to make the reasoning visible: what changed, what didn't, and what I refused to add.