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 releasesv1.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.
ID
Finding
Severity
Status
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).
ID
Finding
Severity
Status
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:
ID
Finding
Severity
Status
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-001CriticalAccessibilityMotion
No prefers-reduced-motion support
Fixed
What I found
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.
The fix
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.
Try disabling motion in your OS — this dot stops pulsing.
F-002CriticalAccessibility
Focus ring overrode invalid-state borders
Fixed
What I found
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.
The fix
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.
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.
The fix
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.
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.
The fix
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.
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.
The fix
Lightened to #756763 (~4.1:1) — still visibly de-emphasised, but the label survives. Light-mode --text-disabled already passed; left alone.
--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.
The fix
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.
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.
The fix
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.
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.
The fix
Added it. Inherits from the same --warning token; same border + tint formula as the other banner variants.
Maintenance window starts at 02:00 UTC. Estimated 20 minutes.
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.
The fix
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.
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.
The fix
Added .checkbox:indeterminate with a centred 8×2 white dash on the brand-green fill. Matches the checked state's visual weight.
Toast / spinner / skeleton ARIA hooks not documented
Deferred · backlog
What I found
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.
Why deferred
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-012MediumAccessibility
Dialog has no focus-trap implementation
Deferred · v2.0
What I found
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.
Why deferred
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.