Skip to content

v4.18 — Iconography System

One facade over three icon sources, and nine new glyphs that finally look like Objectuve.

Summary

Before this milestone, Objectuve ran three parallel, unfaceted icon systems with no single call site: Lucide (lucide-vue-next, ~207 sites / 133 glyphs), ionicons (~41 sites / ~65 names, heavily overlapping Lucide — the same "flame" or "trophy" shipped from two different libraries depending on the file), and bespoke custom SVG-Vue (icons/coach/, icons/enneagram/, gamification/streak/FlameIcon.vue). An audit on the source issue (OBJ-1787) found that the surfaces users actually perceive as "Objectuve" — coach personas, enneagram types, the streak flame — were already custom; most Lucide usage was generic chrome nobody reads as brand.

Josh confirmed Option B — a tiered hybrid on 2026-07-26: keep Lucide for the ~115 chrome glyphs, invest in custom SVG-Vue glyphs only for a pilot set of 9 "identity" concepts, and land a central <AppIcon name="…"> facade first so any future glyph swap is a one-line registry edit instead of a repo-wide sweep. Four phases shipped in a single day (2026-07-27): the facade and full migration (Phase 1), the 9 pilot glyphs (Phase 2), a two-tier gallery refresh proving the design-system decoupling (Phase 3), and a full contribution guide (Phase 4). All four merged directly to master.

Objectuve's icon system is now, in code, a single facade over a registry — Chrome (Lucide) or Identity (custom), with a documented decision rule for which bucket a new glyph belongs in, and a working example (streak/coach) already proving the one-line-swap promise.

Goal

Give Objectuve a coherent, ownable icon system without wholesale-replacing Lucide. Concretely: collapse the three parallel icon systems on the floor today (Lucide ~207 sites / 133 glyphs, ionicons ~41 sites / ~65 names, bespoke custom SVG-Vue) behind a single <AppIcon name="…"> facade; author a pilot set of 9 custom "identity" glyphs (Goal, Coach, Streak, Achievement, XP, Allies, Growth, Journey, Done) in a formalized Objectuve visual language; keep Lucide for the ~115 "chrome" glyphs where users perceive no brand; and document the system so future contributors know when to reach for custom vs. Lucide. After this milestone, swapping any glyph to custom is a one-line registry edit, and the ionicons/Lucide duplication is gone.

Scope — What Shipped

  • New ionic_frontend/src/components/icons/AppIcon.vue + registry.ts — a single name → component facade, named-import-only (never a Lucide barrel import) so tree-shaking survives; an unknown name throws in dev and warns once in prod; the accessibility contract (decorative aria-hidden by default, role="img" + accessible name when aria-label/title is passed) is baked into the facade once.
  • ~207 Lucide + ~41 ionicons call sites across ionic_frontend/src migrated behind <AppIcon>; ionicons fully retired from the codebase — zero ionicons imports remain in app components.
  • Nine new custom Tier-1 identity glyphs — GoalIcon, CoachIcon, StreakIcon, AchievementIcon, XpIcon, AlliesIcon, GrowthIcon, JourneyIcon, DoneIcon — each a 24×24 currentColor-stroke SVG with exactly one semantic-token accent node, a Storybook story (Inline/OnChip/Sizes/Dark), and a unit spec.
  • design_system/src/pages/Icons.vue refreshed into two labelled tiers ("Chrome — Lucide" / "Identity — Custom"); PageHeader's demo now renders a custom glyph (GoalIcon) through its pre-existing generic icon: Component prop, proving the decoupling end-to-end with zero prop-type changes.
  • New docs/development/iconography-system.md — the <AppIcon> API, the three formalized visual-language tiers (line glyph / branded chip / token-gradient) with the single-accent-node rule, a full "authoring a new custom glyph" walkthrough, the Chrome-vs-Identity decision rule, and a documented Known Limitations section.
  • CHANGELOG [Unreleased] entry, plus 3 stale doc references from the migration corrected (docs/development/accessibility.md, .claude/agents/accessibility-auditor.md, docs/product/phase-7-teams.md).

Phases

PhaseNameStatusPlansHighlights
1<AppIcon> facade + icon-system consolidationShipped1The foundation every later phase builds on; ~248 call sites migrated, ionicons retired
2Pilot custom identity glyphsShipped19 glyphs authored and registered; Streak reworked to a cairn per Josh's direct feedback
3Design-system decoupling + Icons gallery refreshShipped1Two-tier gallery; PageHeader proven to accept a custom glyph with zero prop changes
4Docs + contribution guideShipped1Full authoring guide + decision rule + decision record; disclosed the one facade gap honestly

Key Decisions

  • Option B — tiered hybrid, not a full redraw or a Lucide reskin. Rejected: Option A (redraw all ~133 chrome glyphs — high cost, no brand payoff) and Option C (theme Lucide app-wide — low payoff, still reads as Lucide). Confirmed by Josh 2026-07-26.
  • Facade before glyphs. Phase 1 built <AppIcon> + the registry before any custom glyph existed, specifically so a name's underlying component becomes a one-line edit rather than a call-site sweep — proven true in Phase 2 when streak/coach flipped from Lucide to custom with zero migration work.
  • streak is a sibling of FlameIcon, not a replacement. Josh's explicit direction during Phase 2: the new inline cairn glyph should read "every day adds up," while the existing gradient FlameIcon celebration art stays exactly as it was for hero/celebration surfaces. Both are registered, under different names (streak vs streak-flame).
  • Allies reworked mid-flight. The original concept read ambiguously as "waves"; Desi reworked it to a clear clasp/handshake form before Codi implemented it, per the priority fix flagged in the strategy UI-SPEC.
  • Honest disclosure over a silent gap. Phase 4's first docs draft claimed <AppIcon> is "the single call site for every icon" — Roy caught that WhatsNewSpotlight.vue still imports Lucide directly. Rather than pull a code fix into a docs-only phase, Dori corrected the claim with a documented ::: warning Known exception — the gap is real, disclosed, and scoped as a follow-up, not silently hidden.
  • Version skew left unreconciled, on purpose. design_system pins lucide-vue-next@^1.0.0; ionic_frontend pins ^0.562.0. Flagged during Phase 3 as a genuine risk and explicitly not fixed this milestone, per the scope guardrail against forking or force-aligning dependencies mid-milestone.

Requirements Coverage

5 / 5 requirements satisfied (per the milestone audit).

RequirementPhaseStatus
<AppIcon> facade + registry, tree-shaking-safe1Satisfied
Migrate ~207 Lucide + ~41 ionicons sites, collapse ionicons1Satisfied
9 pilot identity glyphs, authored + registered2Satisfied
Two-tier gallery + PageHeader custom-glyph proof3Satisfied
Iconography contribution guide + decision record + CHANGELOG4Satisfied

Outcomes

Every icon in ionic_frontend/src (with one documented exception) resolves through a single facade backed by a registry, instead of three unfaceted libraries. Nine identity glyphs now carry Objectuve's visual language at the highest-traffic brand-perceived sites (Coach, Goal, Streak, Achievement, XP, Allies, Growth, Journey, Done), while the ~115 chrome glyphs nobody reads as brand stay on Lucide undisturbed. A future glyph swap — chrome to identity, or the reverse — is a one-line registry edit, not a repo-wide sweep, and that promise is already proven in shipped code. design_system's Icons gallery documents both tiers side by side, live at design.objectuve.com. A new contributor has a complete, source-verified guide for authoring the next custom glyph, including the exact non-negotiables Storybook and the unit specs enforce.

All four phases merged directly to master on 2026-07-27; the work rides the next Wednesday release train to production per this repo's beta-policy cadence — no separate rollout flag gates any of it, since there is no user-facing behavior change beyond specific glyphs' visual identity.

Tech Debt

  • (Phase 1) ionic_frontend/src/components/tour/WhatsNewSpotlight.vue still imports Sparkles/X directly from lucide-vue-next, bypassing the facade — the one call site the Phase 1 migration missed. Documented as a known exception, not silently omitted.
  • (Phase 3) design_system and ionic_frontend pin different Lucide majors (^1.0.0 vs ^0.562.0) — flagged as a risk, deliberately left unreconciled as a tracked fast-follow.
  • (scope, by design) The remaining ~9 identity-tier concepts identified in the Phase 1 audit (an Award/Medal/Crown rank family, a Heart-family "care" collapse, Star, a Flag/journey-metaphor set) are an explicit, deferred fast-follow — not part of this milestone's pilot.
  • (process) The milestone's own hand-authored ROADMAP file was never actually ticked during flight, despite comments on the coordination anchor claiming otherwise after each phase — corrected retroactively by the milestone audit. Separately, the project-wide .planning/ROADMAP.md index was also missing an entry for v4.18 (and, pre-existing, for v4.13/v4.15/v4.16/v4.17) — this milestone's close adds only the v4.18 entry.
  • d7fcb38a2 — [Codi] feat(icons): AppIcon facade + registry, migrate achievements/allies/analytics/coach + ui-misc batches (OBJ-1795) (#1816)
  • ed94e7b81 — [Codi] feat(icons): author 9 pilot identity glyphs, register in AppIcon (OBJ-1796, ICON-GLYPHS-1) (#1818)
  • ae4cb56e7 — [Codi] feat(design-system): two-tier icon gallery + PageHeader custom-glyph proof (OBJ-1797, ICON-DS-1) (#1819)
  • 043cadae1 — [Dori] docs(icons): iconography system guide + contribution docs + stale-ref cleanup (OBJ-1798) (#1820)

Last updated: 2026-08-03

Loading…