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 inionic_frontend/src/theme/tailwind.cssneutralizing--tw-scale-x/--tw-scale-yon:hover/:active/:focus/:focus-visible, replacing per-call-sitemotion-reduce:/motion-safe:decorations across 266 sites, including code not yet written. A byte-identical duplicate block inApp.vue— invisible to the existing guard test — was deleted. - Converged glass recipe —
.glass-card/.glass-sheetutility classes intailwind.css, absorbing 67 existing hand-rolled call sites onto one canonical recipe. - Two minted type tokens —
leading-displayandtracking-eyebrow, replacing 17 + 9 arbitraryleading-[1.1]/tracking-[0.12em]values. - Three honest affordances —
Achievements.vue's Share control now honestlyaria-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 scale —
PublicGoal.vueandPublicProfile.vue'smin-h-[46px]tap targets corrected to the sanctionedmin-h-[44px]; thePublicGoal.vuetimeline spine derived from the token scale instead of hand-solved. - Gate A7 fixed —
capture-hallmark-evidence.mjsnow exempts the 44px tap-target floor and container-width utilities (max-w/max-h) from its spacing-grid scrutiny, without touching the globalALLOWED_SPACING_PXallowlist itself.
Phases
| Phase | Name | Status | Plans | Highlights |
|---|---|---|---|---|
| 1 | Reduced-motion press feedback — rule it, then enforce it once | Shipped | 1 | One global CSS rule replaces 266 per-call-site decorations; reconciled two locked sources that disagreed on the rule itself |
| 2 | Token canon — glass recipe, light alpha, and the two type gaps | Shipped | 2 | Converged 67 glass call sites onto .glass-card; minted leading-display and tracking-eyebrow |
| 3 | Honest UI — three dead affordances and one rendered copy defect | Shipped | 1 | Fixed the app's own "AI"/"indigo" naming-rule violation in its own Design System showcase page |
| 4 | Off-grid geometry — snap 26 hand-computed values to the scale | Shipped | 1 | Snapped tap targets from 46px to the sanctioned 44px; deferred one structural fix as a recorded follow-on |
| 5 | Fix the measuring instrument, then measure | Shipped | 1 | Gate 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.cssalready forcestransition-duration: 0.01ms !importantglobally, 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(notdesi-design-context.md, a derived file never listed in the precedence chain) was extended to cover input-triggered transforms explicitly. - Global
*:active/*:hoverselector 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/:activematch ancestors, not just the triggering element — two JS-driven celebration pulses would have flickered on any descendant hover/press. Both were gated onuseReducedMotionin 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_PXset 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 itsmin-h-[170px]/min-h-[140px]hand-solved heights without first switching to CSS gridauto-rows-frwould 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).
| Category | Count | Status |
|---|---|---|
| MOTION-1..4 | 4 | All satisfied |
| TOKEN-1..5 | 5 | All satisfied |
| HONEST-1..4 | 4 | All satisfied |
| GRID-1..3 | 3 | All satisfied (one item, LifeAreaSlide.vue's structural fix, recorded as a deliberate follow-on, not a gap) |
| TOOL-1..3 | 3 | All 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.csscovers 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/PublicProfilemeets 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'smin-h-[170px]/min-h-[140px]hand-solved heights are a recorded follow-on (auto-rows-frstructural 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.72pxavatar frames onCoach.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, severalcomponents/teams/*.vue,AddEventButton.vue), plus ~200 off-scale icon sizes shipping throughAppIcon's:sizeprop — invisible to the harvester, which scans classes, not props. - (out of scope, tracked elsewhere)
teams/**stacked-opacity contrast — already OBJ-3149, in progress.
Related Artifacts
- Roadmap: v4.51-design-convention-enforcement-ROADMAP.md
- Milestone Audit: v4.51-design-convention-enforcement-MILESTONE-AUDIT.md
- Phase artifacts: .planning/phases/v4.51-phase-*/
- Gate A7 gotcha: docs/operations/hallmark-gate-a7-spacing-allowlist-gotcha.md
- Phase PRs: #2834 (Phase 1) · #2848 + #2849 (Phase 2) · #2937 (Phase 3) · #2938 (Phase 4) · #2939 (Phase 5)
Related Commits
fa5ca1574— Phase 1: neutralize input-state scale transforms under reduced motiond69db49c4— Phase 2: converge glass surfaces onto.glass-card/.glass-sheet78a9e0ace6— Phase 2: migrate arbitrary tracking/leading values to tokensc3440f73b— Phase 3: honest gating for Share, delete dead Print controlc073297b2— Phase 4: snap off-grid geometry to the token scaleb3eb9f571— Phase 5: scope gate A7 to exclude tap-target floor and container widths
Last updated: 2026-09-06