Skip to content

Icon system upgrade: token scale, alias dedup, brand redesign, PWA/OG assets, a11y rule#519

Merged
BigSimmo merged 12 commits into
mainfrom
claude/icon-design-review-393584
Jul 12, 2026
Merged

Icon system upgrade: token scale, alias dedup, brand redesign, PWA/OG assets, a11y rule#519
BigSimmo merged 12 commits into
mainfrom
claude/icon-design-review-393584

Conversation

@BigSimmo

Copy link
Copy Markdown
Owner

Icon system upgrade — review, normalize, redesign, optimise

A full pass over the app's icons: normalize the set, tokenize sizing, redesign the brand mark, fill the app/PWA/social asset gaps, and enforce icon a11y. The system was already disciplined (one library, clean registries), so this is normalize + optimise + gap-fill, delivered as small, reviewable commits.

What changed

  1. Icon-size token scale--spacing-icon-* in @theme (12/14/16/20/24px) generating size-icon-* utilities (parallel to --spacing-tapsize-tap), plus a check-icon-scale guard in verify:cheap.
  2. Alias dedup (zero-pixel) — collapsed 5 deprecated↔renamed Lucide pairs to one canonical name each (AlertTriangleTriangleAlert, CheckCircle2CircleCheck, …), verified as the same icon module in lucide-react@1.22.0.
  3. Retired the 4.5/18px half-step; guard flipped to --strict.
  4. Brand mark redesign ("Refined Pulse") single-sourced in src/lib/brand-mark.ts → drives BrandMark, app/icon.svg (codegen-guarded via brand:check), and the image routes below.
  5. Filled brand-asset gaps via next/og (no committed binaries): app/apple-icon.tsx (fixes the appleWebApp.capable mismatch), app/icons/[variant] (192/512 + maskable), app/manifest.ts (installable PWA), app/opengraph-image.tsx, and metadataBase.
  6. Responsive scaling on section-heading + empty-state icons (size-icon-md sm:size-icon-lg); composer & mode-home hero already scale.
  7. Fixed mismatched glyphs — facet Medication TargetPill, Service SparklesShieldCheck, PopulationUsers; favourite formsFileSignature.
  8. Decorative-icon a11y rule — a local ESLint rule (auto-fixable) requiring lucide JSX icons to declare aria-hidden or an accessible name; eslint --fix added aria-hidden to 267 decorative icons. Safe by construction (a bare lucide <svg> has no accessible name).

Verification

npm run verify:cheap green (runtime, github-actions, sitemap, brand:check, type-scale, icon-scale, lint, typecheck), 1657 vitest pass, production build green (all image routes prerender; generated apple/maskable/OG PNGs inspected), and live Chromium screenshots at desktop + mobile. Merged current main (incl. #512's type-scale fix) and re-applied the alias + aria conventions to the merged code.

Clinical governance

N/A — pure UI / iconography / brand work. No changes to ingestion, answer generation, search/ranking, source governance, document-access logic, privacy, production config, or clinical output. The DocumentViewer change is aria-hidden-only; document-search facet icons are presentation.

Open for follow-up (left unchanged, not blocking)

Two mode glyphs (Services: ShieldCheck vs Route/Waypoints; Differentials: BrainCircuit vs GitCompare/Stethoscope) and the medication risk/contra/safe sections that share ShieldCheck — surfaced for a design/clinical call rather than changed unilaterally.

🤖 Generated with Claude Code

BigSimmo and others added 9 commits July 12, 2026 01:44
Introduce a documented icon glyph size scale in the @theme block of
globals.css (size-icon-xs..xl → 12/14/16/20/24px), parallel to how
--spacing-tap generates size-tap. This retires the convention-only
h-4 w-4 literal and lets a step carry a responsive variant
(size-icon-md sm:size-icon-lg).

Adds scripts/check-icon-scale.mjs (report mode) wired into verify:cheap.
It flags unambiguous drift — the retired 4.5 (18px) half-step and
arbitrary 12–24px icon sizes — but deliberately does NOT ban raw
h-4 w-4, which also sizes non-icons (ToggleSwitch knob, status dots).

Documents the scale in design-system.md §2 (Type & icon scale).
Additive only: no call sites migrated yet.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Five deprecated↔renamed alias families were used side-by-side. Collapse
each onto one canonical name, verified against lucide-react@1.22.0 as
the same icon module (zero-pixel):

  AlertTriangle → TriangleAlert    AlertCircle → CircleAlert
  CheckCircle2  → CircleCheck       XCircle     → CircleX
  HelpCircle    → CircleHelp

102 whole-word renames across 24 production files. Mockups keep their
imports (out of scope). typecheck / lint / prettier green; no call site
renders differently.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…--strict

Resolve every h-4.5/w-4.5 (18px) icon size — the one off-4px-grid drift
in the icon scale. Lucide glyphs snap to size-icon-lg (20px, preserving
their "bigger than default" intent); non-icon 18px boxes (account-setup
indicator / grid) snap to h-5; the two responsive sites tokenize as
size-icon-md sm:size-icon-lg. One arbitrary h-[18px] ShieldCheck glyph
also moves to size-icon-lg.

Simplify check-icon-scale.mjs to enforce the 4.5 half-step only (dropped
the arbitrary-px rule, which false-flagged non-icon 18px count badges)
and flip it to --strict in verify:cheap so the half-step can't return.

Verified: strict guard green, Tailwind emits .size-icon-* from the
--spacing-icon-* tokens; typecheck / lint / prettier / 1648 vitest green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adopt the "Refined Pulse" direction: one clean, optically-centred QRS
spike, no trailing dot, slightly rounder tile. Define the geometry once
in src/lib/brand-mark.ts and derive every surface from it so the mark
can't drift across the favicon, the in-app <BrandMark>, app/icon.svg,
and (next) the app-icon / OG image routes:

  - BrandMark renders the exported geometry as token-themed JSX
    (var(--clinical-accent) → light/dark/forced-colors adaptive).
  - app/icon.svg is generated by scripts/generate-brand-assets.ts;
    brand:check (wired into verify:cheap) guards it from drift, the
    same pattern as sitemap:check.
  - brandMarkSvg()/brandMarkInner() are ready for the PR5 image routes.

favicon.ico (a multi-res binary) still carries the previous mark: the
toolchain has no rasteriser, so it needs offline regeneration (noted in
the script header and design-system.md §10). The SVG icon is primary in
modern browsers.

typecheck / lint / prettier / brand:check green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fill the app-icon asset gaps, all derived from the single brand-mark
source via next/og ImageResponse (no committed binaries):

  - app/apple-icon.tsx (180x180, opaque teal) — fixes the
    appleWebApp.capable:true mismatch (iOS was falling back to a page
    screenshot for "Add to Home Screen").
  - app/icons/[variant] — PWA icon set: icon-192/512 (transparent
    "any") + maskable-192/512 (full-bleed teal, mark inside the ~72%
    safe zone).
  - app/manifest.ts — installable PWA; icons reference the SVG + PNG
    set; theme/background match the light default.
  - app/opengraph-image.tsx (1200x630) — branded link unfurls; text
    uses ImageResponse's bundled default font (no font asset needed).
  - layout.tsx: metadataBase (NEXT_PUBLIC_SITE_URL, localhost fallback)
    so OG image URLs resolve absolutely.

Verified with a production build: all eight routes prerender; inspected
the generated apple / maskable / any / OG PNGs — the mark renders
correctly and the maskable safe-zone is respected. typecheck / lint /
prettier green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PanelHeading (section header) and EmptyState glyphs now scale
size-icon-md → sm:size-icon-lg: a compact 16px on mobile, 20px from the
sm breakpoint up for desktop hierarchy. Glyph-only inside fixed tiles,
so no layout shift. With the composer (globals.css media queries) and
the mode-home hero already responsive, this covers the four high-value
roles the plan scoped; body / badge / table icons stay fixed by design.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Correct icons that didn't match their concept or the mode registry:
  - documentFacetIcons: Medication Target→Pill (matches prescribing),
    Service Sparkles→ShieldCheck (Sparkles is the answer glyph; match
    the services mode), Population FileText→Users.
  - fallbackIconByType (favourites): forms FileText→FileSignature via
    appModeIcons.forms, so a favourited form matches the forms mode.

Higher-visibility / clinical choices (services & differentials mode
glyphs, and differentiating the medication risk/contra/safe sections
that currently share ShieldCheck) are surfaced for review rather than
changed unilaterally. typecheck / lint / prettier green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…sites)

Add a local ESLint rule (eslint-rules/require-lucide-icon-aria.mjs, wired
into eslint.config.mjs as an error, mockups exempt): a lucide-react icon
rendered as JSX must declare aria-hidden (decorative) or an accessible
name (aria-label / aria-labelledby / role / title). Enforces the
codebase's own convention so a decorative glyph can't silently reach the
accessibility tree, and prevents regressions.

The rule is auto-fixable; `eslint --fix` added aria-hidden="true" to 267
decorative icons across 33 files. Safe by construction: a bare lucide
<svg> has no accessible name, so hiding it never strips a control's name
(an icon-only button lacking a label was already unlabeled — a separate,
runtime-axe concern, not one this change can regress).

Verified: lint / typecheck / prettier / 1648 vitest / production build
all green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…view-393584

# Conflicts:
#	src/components/DocumentViewer.tsx
@supabase

supabase Bot commented Jul 12, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project sjrfecxgysukkwxsowpy because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@BigSimmo, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 1 minute

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a3eb69f1-2520-4cf6-8d47-01a02bd87c2c

📥 Commits

Reviewing files that changed from the base of the PR and between c3828ce and 370cd7b.

⛔ Files ignored due to path filters (1)
  • src/app/icon.svg is excluded by !**/*.svg
📒 Files selected for processing (58)
  • docs/design-system.md
  • eslint-rules/require-lucide-icon-aria.mjs
  • eslint.config.mjs
  • package.json
  • scripts/check-icon-scale.mjs
  • scripts/generate-brand-assets.ts
  • src/app/apple-icon.tsx
  • src/app/globals.css
  • src/app/icons/[variant]/route.tsx
  • src/app/layout.tsx
  • src/app/manifest.ts
  • src/app/not-found.tsx
  • src/app/opengraph-image.tsx
  • src/components/ClinicalDashboard.tsx
  • src/components/DocumentManagementActions.tsx
  • src/components/DocumentOrganizationBadges.tsx
  • src/components/DocumentViewer.tsx
  • src/components/applications-launcher-page.tsx
  • src/components/clinical-dashboard/ClinicalSidebar.tsx
  • src/components/clinical-dashboard/DocumentManagerPanel.tsx
  • src/components/clinical-dashboard/account-setup-dialog.tsx
  • src/components/clinical-dashboard/answer-content.tsx
  • src/components/clinical-dashboard/answer-result-surface.tsx
  • src/components/clinical-dashboard/answer-status.tsx
  • src/components/clinical-dashboard/auth-panel.tsx
  • src/components/clinical-dashboard/badges.tsx
  • src/components/clinical-dashboard/brand.tsx
  • src/components/clinical-dashboard/clinical-output-helpers.tsx
  • src/components/clinical-dashboard/dashboard-nav.tsx
  • src/components/clinical-dashboard/dashboard-shell.tsx
  • src/components/clinical-dashboard/document-admin.tsx
  • src/components/clinical-dashboard/document-search-results.tsx
  • src/components/clinical-dashboard/document-ui.tsx
  • src/components/clinical-dashboard/evidence-panels.tsx
  • src/components/clinical-dashboard/favourites-command-library-page.tsx
  • src/components/clinical-dashboard/favourites-hub.tsx
  • src/components/clinical-dashboard/master-search-header.tsx
  • src/components/clinical-dashboard/medication-prescribing-workspace.tsx
  • src/components/clinical-dashboard/medication-record-page.tsx
  • src/components/clinical-dashboard/mode-action-popup.tsx
  • src/components/clinical-dashboard/output-panel.tsx
  • src/components/clinical-dashboard/relevance.tsx
  • src/components/clinical-dashboard/settings-dialog.tsx
  • src/components/clinical-dashboard/source-actions.tsx
  • src/components/clinical-dashboard/universal-search-command-surface.tsx
  • src/components/clinical-dashboard/visual-evidence.tsx
  • src/components/differentials/diagnosis-map-panel.tsx
  • src/components/differentials/differential-detail-page.tsx
  • src/components/differentials/differential-presentation-workflow-page.tsx
  • src/components/document-search-live-opener.tsx
  • src/components/forms/form-detail-page.tsx
  • src/components/forms/forms-search-results-page.tsx
  • src/components/route-error-boundary.tsx
  • src/components/services/service-detail-page.tsx
  • src/components/ui-primitives.tsx
  • src/components/ui/sheet.tsx
  • src/lib/brand-image.tsx
  • src/lib/brand-mark.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/icon-design-review-393584
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch claude/icon-design-review-393584

Comment @coderabbitai help to get the list of available commands.

@BigSimmo
BigSimmo enabled auto-merge (squash) July 12, 2026 16:00

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3319417d71

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/app/layout.tsx
BigSimmo and others added 3 commits July 13, 2026 00:07
CI lints with --max-warnings 0. The inline {/* eslint-disable-next-line
@next/next/no-img-element */} placed inside a JSX expression container
does not work as a directive — it was reported unused while the rule
still flagged the <img>, failing the zero-warnings gate. Switch to the
file-level /* eslint-disable @next/next/no-img-element */ the codebase
already uses for these next/og image routes (cf. answer-content.tsx,
DocumentViewer.tsx). Verified with `npm run lint -- --max-warnings 0`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The file-level /* eslint-disable @next/next/no-img-element */ was
reported "unused" on CI's fresh checkout but "used" locally: the Next
plugin only fires this rule when a local .next build dir is present, so
the directive flipped between used/unused and tripped --max-warnings 0.

Move it to a deterministic eslint.config.mjs override (rule off for
src/lib/brand-image.tsx + src/app/opengraph-image.tsx). A config rule
setting can't be an "unused directive", so it holds in every
environment. Verified with .next removed + `npm run lint -- --max-warnings 0`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@BigSimmo
BigSimmo merged commit 2f80720 into main Jul 12, 2026
19 checks passed
BigSimmo added a commit that referenced this pull request Jul 12, 2026
…afety (#523)

Follow-up to #519, applying the icon-gallery review items:

- Services mode: ShieldCheck → Route. The mode is "service records +
  referral pathways"; ShieldCheck read as safety/trust. Changed at the
  single source (app-mode-icons.ts, so nav/search/favourites follow) plus
  the matching documentFacetIcons "Service" entry.
- Medication record sections differentiated (risk/contra/safe all shared
  ShieldCheck): risk → TriangleAlert, contra → Ban, safe → ShieldCheck.
  Aligned with the app's semantic-tone safety icons (warning/danger);
  "contra" already carries a danger tone (medications.ts), which Ban matches.
- Differentials kept as BrainCircuit: evaluated GitCompare (reads as a
  dev/git glyph in a clinical app) and Stethoscope (already used for
  "Clinical role" in settings) — neither beats BrainCircuit for
  distinctiveness + clinical legibility.

Verified: prettier, lint --max-warnings 0, typecheck, check:icon-scale,
brand:check green; no snapshot tests reference these maps (CI runs the
full vitest + build gate).

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@BigSimmo
BigSimmo deleted the claude/icon-design-review-393584 branch July 13, 2026 16:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant