Skip to content

v4.44 — Gradient Stop Ink Contract

Every stop of every gradient token now carries the same measured ink contract as its first stop, and a guard fails the build the moment a new one doesn't.

Summary

Before this milestone, no guard in the tree had ever looked at a gradient token's individual stops. --gradient-gold's second stop — a raw HSL literal, hsl(38 92% 50%) — had never been measured against the ink painted on top of it, and scored 3.97:1: below the 4.5:1 AA floor, in all 20 theme×mode contexts, every day since the token was authored. The filed defect (OBJ-2868) named that one instance. The milestone's real finding, from Phase 1's census, was that the gap was never gold-shaped: design_system/css/tokens.css carried 40 --gradient-* declarations across 22 token names, and the tint-ink guard saw zero of them. A raw-literal gradient stop declared in :root matches neither the guard's hue-keyed value shape nor its same-block ink-co-location model — the two together made every gradient stop in the token file structurally invisible, not merely unmeasured.

Six phases (five planned, Phase 3 split into 3a/3b mid-milestone by Phase 1's own sizing) shipped in three calendar days, gated for two of those days behind v4.42 Tint Alpha Governance closing (Phase 4 extends the same two files v4.42 was actively rewriting). Phase 1 resolved the one prerequisite question — which of two four-way-divergent --gradient-gold declarations the shipped app actually serves — from a real vite build artifact rather than reasoning about Tailwind's layer handling, then built a call-site-first measurement harness that overturned two of its own upstream naming-convention assumptions in both directions: --gradient-coach pairs with literal text-white at zero --coach-foreground call sites, and --gradient-primary has four text-white outliers hitting 1.66:1 in dark mode — the worst fixable number in the whole census. Phase 2 applied Desi's ruling for gold's stop B (hsl(43 96% 66%), holding hue and saturation byte-identical to stop A — the same shape --gradient-primary and --gradient-accent already travel in) and deleted four dead shadow declarations in variables.css that Phase 1 proved were already unreachable in every mode. Phases 3a and 3b ran in parallel, sweeping the gold consumers and repairing the coach/primary ink pairings respectively, with deliberately asymmetric fix shapes — a background swap for coach CTAs, an ink-class swap for primary outliers — that the plan explicitly warned not to generalize from one to the other. Phase 4 built the guard, catching itself before implementation: a pre-dispatch addendum found that the parser extension alone would produce zero behaviour change, because tokens.css was never inside the scanned root list and every declaration there is a custom property the guard's background-property check rejects by construction. The real scope — widen the scan surface, add a custom-property-aware path, build the cross-file token→call-site ink model the same-block guard structurally lacked — was proven non-vacuous with a red-then-green control before any allowlist assertion was trusted. Phase 5 wrote the rule into DESIGN.md, the honest deferral into the AA-violation ledger, and closed the milestone.

Goal

Every stop of every gradient token carries the same ink contract as its first stop, that contract is measured rather than assumed, and a guard fails the build when a new gradient stop is written that nothing has measured.

Scope — What Shipped

  • Token repair: --gradient-gold stop B re-valued from hsl(38 92% 50%) to hsl(43 96% 66%) (design_system/css/tokens.css:220) — 3.97:1 → 5.63:1 against its own ink, worst number now 5.02:1 at the untouched stop A. variables.css's four dead shadow declarations (--gradient-gold/--gradient-primary/--gradient-accent/--gradient-coach) deleted; DESIGN.md's recorded value moved with the token in the same commit.
  • Call-site repairs: the coach gradient's four CTA buttons (CoachRoadmapIntro.vue, CoachGoalHero.vue, GoalQualityCheck.vue, CoachSuggestSheet.vue) repointed at a newly-ported --gradient-coach-ink background token (3.96/4.31:1 → 7.01/7.24:1); four --gradient-primary sites (GoalEventForm.vue ×2, RoadmapComposer.vue, AllyLookupModal.vue) swapped from literal text-white to text-primary-foreground (worst 1.66:1 dark-mode fixed).
  • Guard: ionic_frontend/tests/unit/tintInkGuard.spec.ts gained a gradient-stop scanner — design_system/css added to SCAN_ROOTS, a custom-property-aware declaration path, one-level var()-alias resolution, and a cross-file token→call-site ink model built from Phase 1's hand-verified pairing ledger. Completion gate is a 9-entry, 6-token stop-keyed allowlist (GRADIENT_INK_ALLOWLIST), proven non-vacuous with a deliberately-broken-then-repaired control stop.
  • Docs: DESIGN.md § Accessibility hard rules — v4.44 Gradient Stop Ink Contract (new); docs/development/accessibility.md § App-Side AA-Violation Ledger (new OBJ-2868/OBJ-2869/OBJ-2876 entry); docs/reference/desi-design-context.md § Anti-patterns (new bullet: don't assume a gradient token's ink is its -foreground sibling without checking the real call site); CHANGELOG.md [Unreleased].
  • Filed, not built here: OBJ-2876 (six supporter-theme --gradient-cover-*/--gradient-tone-* pairs below the 3:1 glyph floor, worst 1.64:1 — architectural, allowlisted with an expiry condition), OBJ-2928 (--gradient-hero's two inked consumers outside Phase 1's census scope), OBJ-2930 (crew git-identity instability, surfaced twice this milestone as --admin merge bypasses — explicitly not v4.44 scope).

Phases

PhaseNameStatusPlansHighlights
1Resolve the effective token, census every gradient stopShipped1Proved tokens.css is the effective declaration from a real build artifact; overturned two naming-convention assumptions in both directions; Desi ruled gold's repair value.
2Repair --gradient-gold, delete the shadow declarationsShipped1Applied the ruled stop B; deleted four dead shadow declarations; caught and fixed a harness self-check that had baked in the pre-fix value as "known-good."
3aSweep the gold consumersShipped1Propagated the repaired stop B across mockups, marketing CSS, and a design-system value that had drifted into its own hardcoded string literal; fixed a generator hardcoded to a personal laptop path.
3bPort --gradient-coach-ink, repair coach and primary ink pairingsShipped1Zero deviations from plan. Background swap for coach CTAs, ink-class swap for primary outliers — deliberately asymmetric fix shapes.
4Teach the tint-ink scanners raw-literal gradient stopsShipped1Found the parser-only fix would be vacuous before writing it; widened the scan surface and built a cross-file ink model; proved the guard non-vacuous with a red-then-green control.
5The rule, the docs, and closeShipped1Wrote the DESIGN.md clause, the honest AA-ledger deferral entry, and closed the milestone — including recovering from a self-inflicted close-gate sequencing deadlock.

Key Decisions

  • Gold's stop B moves on lightness only, matching its sibling gradients — Desi ruled hsl(43 96% 66%) over OBJ-2868's own filed recommendation (hsl(38 92% 59%), which cleared by only 0.03 and kept an off-hue drift that violated DESIGN.md's existing v4.33 lightness-only rule). The ruled value holds hue and saturation byte-identical to stop A and travels +10 L — the same shape --gradient-primary and --gradient-accent already use.
  • The four-way token drift resolves by deletion, not convergencevariables.css's shadow declarations were proven dead code in every mode (Tailwind v3 flattens @layer base, so tokens.css's source-order-later declaration always wins) and marketing_landing/ was measured to contain zero references to the token. Converging the dead values instead of deleting them would have left more places for drift to restart.
  • Coach CTAs get a background swap; primary outliers get an ink-class swap — not the same fix generalized twice. The coach defect was in the gradient itself (a darker -ink variant token already existed on the marketing site and had simply never been ported to the app); the primary defect was in the literal text-white class overriding the correct text-primary-foreground that thirty-four other call sites already used correctly.
  • The guard's real scope was found before implementation, not discovered by a passing-but-vacuous test suite. A pre-dispatch addendum ran the real scanner against the real token file and found the named parser fix alone changes nothing — tokens.css was outside the scan surface, and every declaration in it is a custom property the guard's background-property check rejects. The phase's estimate and scope were both revised from that measurement before any code was written.
  • The one unrepaired gap (supporter-theme cover/tone pairs) is architectural, not a missed sweep — those gradients are tuned against the default theme's --primary-foreground; supporter light themes redeclare that ink near-black while leaving the gradients unchanged. Fixing it re-opens v4.33 Phase 3's token architecture, which is a milestone-sized problem of its own. Named, owned, allowlisted, and given an explicit expiry condition (OBJ-2876) rather than silently absorbed or left to look like an oversight.

Requirements Coverage

3 / 3 goal clauses satisfied, all six phases' acceptance criteria satisfied (see .planning/milestones/v4.44-gradient-stop-ink-contract-MILESTONE-AUDIT.md).

Goal clauseStatus
Every stop of every gradient token carries the same ink contract as its first stop
That contract is measured rather than assumed
A guard fails the build when a new gradient stop is written that nothing has measured

Outcomes

Every gradient token declared in design_system/css/tokens.css — 22 token names, 40 declarations — now has its stops measured against the real ink painted at its real call sites, not against a guessed -foreground sibling. Three real AA-contrast defects are fixed and independently re-verified exact in all 20 theme×mode contexts: the gold achievement gradient (XP chips, capstone banners, the tour outro badge), the coach CTA gradient (four buttons), and four text-white outliers on the primary gradient. A guard now fails the build the moment a new gradient stop is written with no measured ink contract behind it — proven non-vacuous, not merely proven to pass. One honest exception (six supporter-theme pairs, architectural, not a stop-tuning problem) is named, allowlisted, and carries an expiry condition rather than being rounded up as done.

Tech Debt

  • (OBJ-2876) Six supporter-theme --gradient-cover-*/--gradient-tone-* token×stop pairs remain below the 3:1 glyph floor under supporter light themes (worst 1.64:1). Allowlisted in the guard; expires the moment a new --gradient-cover-*/--gradient-tone-* token is added without being scoped first.
  • (OBJ-2928) --gradient-hero has two live inked consumers in design_system/src/pages/BrandOverview.vue outside Phase 1's ionic_frontend/src-scoped census. Not a Phase 4 defect — the design-system reference app is not the shipped product — but named rather than left implicit.
  • (OBJ-2930, not v4.44 scope) Crew git commit-author identity drifted across three domains in three consecutive merges this milestone, defeating a GitHub ruleset and forcing --admin merge bypasses twice. Filed as a standing crew-infrastructure defect, explicitly excluded from this milestone's close.
  • Roadmap: v4.44-gradient-stop-ink-contract-ROADMAP.md
  • Milestone Audit: v4.44-gradient-stop-ink-contract-MILESTONE-AUDIT.md
  • Source issue: OBJ-2868 — "gradient-gold token has no ink contract for stop B (3.99:1, blast radius beyond OBJ-2663)," filed by Desi while fixing OBJ-2663 (Legendary gradient chip ink contrast) and deliberately scoped out as "a real defect in a shared token needing its own phase, not a drive-by fix."
  • Coordination anchor: OBJ-2869 (parent milestone issue)
  • Follow-ups filed: OBJ-2876 (supporter-theme cover/tone deferral), OBJ-2928 (--gradient-hero scope gap), OBJ-2930 (crew git-identity instability)
  • 10061ee69 — Phase 1: Resolve the effective token, census every gradient stop (PR #2610)
  • ac747f3a0 — Phase 2: Repair --gradient-gold, delete the shadow declarations (PR #2630)
  • 6c9289cb1 — Phase 3b: Port --gradient-coach-ink, repair coach and primary ink pairings (PR #2629)
  • fb44b865d — Phase 3a: Sweep the gold consumers (PR #2633)
  • da8cb6a1e — Phase 4: Teach the tint-ink scanners raw-literal gradient stops (PR #2635)
  • 26c2508b2 — Phase 5: The rule, the docs, and close (PR #2638)

Last updated: 2026-08-26

Loading…