Skip to content

v4.51 — Design Convention Enforcement

Four already-locked design conventions now hold by construction instead of by per-call-site discipline, and the three places their locked sources disagreed with each other are resolved.

Summary

Before this milestone, four design conventions that Objectuve had already locked in desi-design-context.md and DESIGN.md — reduced-motion press feedback, the glass surface recipe, display type, and the 4px spacing grid — were each expressed multiple different ways across the codebase at once, with no mechanism that would ever collapse them back to one. Reduced-motion press feedback alone had six live variants across 266 call sites in 116 files. Worse, the audit tool built to find this kind of drift (capture-hallmark-evidence.mjs's hallmark gates) was itself wrong on one point: its gate A7 reported the codebase's compliance with the sanctioned 44px WCAG tap-target floor as debt against the 4px grid, because 44 had never been added to its spacing allowlist.

This milestone shipped five phases, ordered by leverage. Phase 1 replaced every per-call-site reduced-motion decoration with a single global CSS rule, closing a real disagreement between the two locked source documents about what the rule even was. Phase 2 converged glass surfaces and light-alpha usage onto the canonical recipe and minted the two display-type tokens the audit had found but not fixed. Phase 3 — the milestone's only user-visible phase — brought three dead affordances and one rendered copy defect (the app's own "AI"/"indigo" naming rules being contradicted in its own Design System showcase page) up to the pattern the rest of the app already uses for gated features. Phase 4 snapped 26 hand-computed off-grid geometry values onto the token scale, including the tap-target floor itself. Phase 5 fixed the measuring instrument last, on purpose, so its re-run would measure the result of the first four phases rather than a stale baseline.

The work mattered less for any single surface looking better and more for closing a structural gap: without Phase 1's global rule, every future press-scale would need its own reduced-motion decoration, forever, and every future PR would be one more chance to get it wrong. The same is true of the glass recipe and the grid. This milestone converts four instructions that only worked if every contributor remembered them into rules the build itself now enforces or the design system's tokens make the correct choice the easy one.

Goal

Make the design system's already-locked conventions hold by construction instead of by per-call-site discipline, and close the three places where the locked sources contradict each other so that "follow the rule" is an answerable instruction.

Scope — What Shipped

  • Global reduced-motion rule — one @media (prefers-reduced-motion: reduce) block in ionic_frontend/src/theme/tailwind.css neutralizing --tw-scale-x/--tw-scale-y on :hover/:active/:focus/:focus-visible, replacing per-call-site motion-reduce:/motion-safe: decorations across 266 sites, including code not yet written. A byte-identical duplicate block in App.vue — invisible to the existing guard test — was deleted.
  • Converged glass recipe.glass-card/.glass-sheet utility classes in tailwind.css, absorbing 67 existing hand-rolled call sites onto one canonical recipe.
  • Two minted type tokensleading-display and tracking-eyebrow, replacing 17 + 9 arbitrary leading-[1.1]/tracking-[0.12em] values.
  • Three honest affordancesAchievements.vue's Share control now honestly aria-disabled-gated (its dead Print control deleted outright); PartnerInvitePage.vue's unconditional dead end replaced with a real flag-off state distinct from its live accept flow; DesignSystem.vue's rendered copy corrected from "AI Insight Cards" / "indigo" to "Coach Insight Cards" / "Coach Purple."
  • Off-grid geometry snapped to the token scalePublicGoal.vue and PublicProfile.vue's min-h-[46px] tap targets corrected to the sanctioned min-h-[44px]; the PublicGoal.vue timeline spine derived from the token scale instead of hand-solved.
  • Gate A7 fixedcapture-hallmark-evidence.mjs now exempts the 44px tap-target floor and container-width utilities (max-w/max-h) from its spacing-grid scrutiny, without touching the global ALLOWED_SPACING_PX allowlist itself.

Phases

PhaseNameStatusPlansHighlights
1Reduced-motion press feedback — rule it, then enforce it onceShipped1One global CSS rule replaces 266 per-call-site decorations; reconciled two locked sources that disagreed on the rule itself
2Token canon — glass recipe, light alpha, and the two type gapsShipped2Converged 67 glass call sites onto .glass-card; minted leading-display and tracking-eyebrow
3Honest UI — three dead affordances and one rendered copy defectShipped1Fixed the app's own "AI"/"indigo" naming-rule violation in its own Design System showcase page
4Off-grid geometry — snap 26 hand-computed values to the scaleShipped1Snapped tap targets from 46px to the sanctioned 44px; deferred one structural fix as a recorded follow-on
5Fix the measuring instrument, then measureShipped1Gate A7's ionic_views hit count: 162 → 42, with the residual fully decomposed and accounted for

Key Decisions

  • Neutralize-only over opacity-fallback for reduced motion, and it was never a real choice. The compliant in-repo precedent the audit told Codi to copy removed the transform and put nothing in its place — a reading desi-design-context.md's "opacity-only fade" language seemed to reject. Desi ruled neutralize-only, but the decision turned out to be forced rather than chosen: tailwind.css already forces transition-duration: 0.01ms !important globally, so any per-element opacity fade would collapse to an instant snap — the exact artifact class being removed, plus a new "looks disabled" defect. DESIGN.md (not desi-design-context.md, a derived file never listed in the precedence chain) was extended to cover input-triggered transforms explicitly.
  • Global *:active/*:hover selector over per-call-site opt-in, with the ancestor-matching risk paid for up front. A blanket selector is the only shape that covers unwritten code, but :hover/:active match ancestors, not just the triggering element — two JS-driven celebration pulses would have flickered on any descendant hover/press. Both were gated on useReducedMotion in the same PR rather than deferred.
  • Gate A7's fix scoped to exactly two exemptions, not a rewrite of the allowlist. The global 4px-grid ALLOWED_SPACING_PX set was deliberately left untouched — the bug was gate A7 flagging the sanctioned 44px tap floor and container widths, not the grid concept itself. A wider fix would have quietly suppressed real off-grid findings alongside the false positives.
  • LifeAreaSlide.vue's structural fix deferred, not silently dropped. Removing its min-h-[170px]/min-h-[140px] hand-solved heights without first switching to CSS grid auto-rows-fr would collapse the lone last-row "Something else" tile. Recorded as a follow-on in UI-SPEC v4.51 Phase 4 §8.4 with an in-code comment pointing at it, rather than fixed partially or left unmentioned.
  • Phase 5 (the tooling fix) shipped last, deliberately. Fixing the measuring instrument before the four content phases would have measured a stale baseline; running it last means its before/after count (162 → 42) is the milestone's own verification number, not a separate claim.

Requirements Coverage

19 / 19 requirements satisfied (from v4.51-design-convention-enforcement-MILESTONE-AUDIT.md).

CategoryCountStatus
MOTION-1..44All satisfied
TOKEN-1..55All satisfied
HONEST-1..44All satisfied
GRID-1..33All satisfied (one item, LifeAreaSlide.vue's structural fix, recorded as a deliberate follow-on, not a gap)
TOOL-1..33All satisfied

Full requirements detail: v4.51-design-convention-enforcement-ROADMAP.md on GitHub.

Outcomes

  • A press-scale written today needs no reduced-motion decoration — the global rule in tailwind.css covers it automatically, on every current and future call site.
  • A new elevated surface has one correct glass recipe to reach for (.glass-card/.glass-sheet), not four competing light-alpha values to guess between.
  • The app's own Design System showcase page no longer contradicts the app's own naming rules — no "AI" label, no "indigo" where the brand says Coach Purple.
  • Every visible tap target on PublicGoal/PublicProfile meets the sanctioned 44px floor exactly, not an off-grid approximation.
  • The monthly hallmark audit's gate A7 number is trustworthy — a min-h-[44px] no longer reads as debt, and the residual 42 hits are fully decomposed (40 real, out-of-scope findings; 2 documented deliberate keeps) rather than an unexplained number every future audit would have to re-derive by hand.

Tech Debt

  • (Phase 4) LifeAreaSlide.vue's min-h-[170px]/min-h-[140px] hand-solved heights are a recorded follow-on (auto-rows-fr structural fix, UI-SPEC v4.51 Phase 4 §8.4) — not done in this milestone.
  • (surfaced by Phase 5, pre-existing, out of scope) 40 real off-allowlist spacing values (on the 4px grid but absent from DESIGN.md's curated scale — e.g. 72px avatar frames on Coach.vue/PartnerInvitePage.vue) were never covered by Phase 4's 26-value sweep. Worth a follow-up issue if the team wants it closed.
  • (surfaced during Phase 4, not filed) Off-grid values outside Phase 4's ruled six surfaces (ThemeCreator.vue, several components/teams/*.vue, AddEventButton.vue), plus ~200 off-scale icon sizes shipping through AppIcon's :size prop — invisible to the harvester, which scans classes, not props.
  • (out of scope, tracked elsewhere) teams/** stacked-opacity contrast — already OBJ-3149, in progress.
  • fa5ca1574 — Phase 1: neutralize input-state scale transforms under reduced motion
  • d69db49c4 — Phase 2: converge glass surfaces onto .glass-card / .glass-sheet
  • 78a9e0ace6 — Phase 2: migrate arbitrary tracking/leading values to tokens
  • c3440f73b — Phase 3: honest gating for Share, delete dead Print control
  • c073297b2 — Phase 4: snap off-grid geometry to the token scale
  • b3eb9f571 — Phase 5: scope gate A7 to exclude tap-target floor and container widths

Last updated: 2026-09-06

Loading…