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 singlename → componentfacade, 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 (decorativearia-hiddenby default,role="img"+ accessible name whenaria-label/titleis passed) is baked into the facade once. - ~207 Lucide + ~41 ionicons call sites across
ionic_frontend/srcmigrated 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×24currentColor-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.vuerefreshed into two labelled tiers ("Chrome — Lucide" / "Identity — Custom");PageHeader's demo now renders a custom glyph (GoalIcon) through its pre-existing genericicon: Componentprop, 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
| Phase | Name | Status | Plans | Highlights |
|---|---|---|---|---|
| 1 | <AppIcon> facade + icon-system consolidation | Shipped | 1 | The foundation every later phase builds on; ~248 call sites migrated, ionicons retired |
| 2 | Pilot custom identity glyphs | Shipped | 1 | 9 glyphs authored and registered; Streak reworked to a cairn per Josh's direct feedback |
| 3 | Design-system decoupling + Icons gallery refresh | Shipped | 1 | Two-tier gallery; PageHeader proven to accept a custom glyph with zero prop changes |
| 4 | Docs + contribution guide | Shipped | 1 | Full 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 whenstreak/coachflipped from Lucide to custom with zero migration work. streakis a sibling ofFlameIcon, not a replacement. Josh's explicit direction during Phase 2: the new inline cairn glyph should read "every day adds up," while the existing gradientFlameIconcelebration art stays exactly as it was for hero/celebration surfaces. Both are registered, under different names (streakvsstreak-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 thatWhatsNewSpotlight.vuestill 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_systempinslucide-vue-next@^1.0.0;ionic_frontendpins^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).
| Requirement | Phase | Status |
|---|---|---|
<AppIcon> facade + registry, tree-shaking-safe | 1 | Satisfied |
| Migrate ~207 Lucide + ~41 ionicons sites, collapse ionicons | 1 | Satisfied |
| 9 pilot identity glyphs, authored + registered | 2 | Satisfied |
Two-tier gallery + PageHeader custom-glyph proof | 3 | Satisfied |
| Iconography contribution guide + decision record + CHANGELOG | 4 | Satisfied |
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.vuestill importsSparkles/Xdirectly fromlucide-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_systemandionic_frontendpin different Lucide majors (^1.0.0vs^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.mdindex 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.
Related Artifacts
- Roadmap: v4.18-iconography-system-ROADMAP.md
- Strategy UI-SPEC: .planning/phases/v4.18-phase-1-iconography-strategy/UI-SPEC.md
- Milestone Audit: v4.18-iconography-system-MILESTONE-AUDIT.md
- Feature docs: docs/development/iconography-system.md
- Merge PRs: #1816 (facade + migration) · #1818 (pilot glyphs) · #1819 (gallery decoupling) · #1820 (docs)
Related Commits
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