Skip to content

#572 Standardize Lucide Iconography Across The App - #597

Open
b-at-neu wants to merge 4 commits into
devfrom
572-standardize-lucide-iconography
Open

#572 Standardize Lucide Iconography Across The App#597
b-at-neu wants to merge 4 commits into
devfrom
572-standardize-lucide-iconography

Conversation

@b-at-neu

Copy link
Copy Markdown
Collaborator

Closes #572

SESSION REQUIRED: touches CLAUDE.md / .claude/** — a dispatched agent can't edit those

Summary

Full iconography sweep. lib/icons.ts holds the whole vocabulary as five separately-exported Record<Enum, LucideIcon> maps (concept, application status, position availability/status, action, state, file type) — an icon only exists if its meaning is a key in one of those maps, so adding one anywhere new is a reviewable vocabulary change. Every call site looks its icon up there instead of importing lucide-react directly.

The sweep does three things: fixes real drift (four deprecated lucide aliases, home/position/application/myApplication carrying inconsistent icons across nav vs. call sites, three hand-rolled copies of EmptyState), adds icons where they were missing (status badges, section headings, labelled action buttons, the error/404/429 fallbacks), and deletes redundant markup the primitives already supply (aria-hidden lucide already emits itself; size-4 a shadcn Button/DropdownMenuItem already forces via CSS).

Changes

  • lib/icons.ts (new) — the vocabulary; tests/unit/icons.test.ts (new) — each map's icons are unique (POSITION_AVAILABILITY_ICONS's closed_by_date/unavailable share one by design, both meaning "closed").
  • app/globals.css — deleted the unused icon-secondary/icon-tertiary tokens.
  • components/ui/badge.tsx, status-badge.tsx — badges can now hold an icon; both status badges render one.
  • components/ui/section-card.tsxSectionCard gets an optional icon; SectionCardEmpty's icon is now required (a deliberate compile break that catches an icon-less empty state).
  • components/ui/empty-state.tsx, warning-callout.tsx, data-table.tsx — drop redundant aria-hidden; WarningCallout defaults to the warning icon; data-table sort affordances use the action vocabulary.
  • components/layouts/nav-items.ts, page-header.tsx — nav resolves the three concept conflicts; back link uses the vocabulary.
  • components/ui/error-fallback.tsx, not-found-fallback.tsx, app/429/page.tsx — each designed state gets its icon.
  • applications-table.tsx, my-applications-widget.tsx, managed-positions-widget.tsx — hand-rolled empty/status markup folded into EmptyState/SectionCard/SectionCardEmpty/PositionStatusBadge.
  • Mechanical alias renames: Loader2→pending, MoreHorizontal→more, UserCircleCircleUser across ~19 files.
  • SectionCard icons on all 9 call sites; icons added to the 9 qualifying inline section headings.
  • Labelled buttons swept against the action vocabulary across ~50 files, including the two known inconsistencies (position-card.tsx's Edit/Applications/View Details/Apply row; global-questions-table.tsx vs. position-questions-section.tsx standardized on icon-only ghost row actions with aria-label).
  • docs/DESIGN.md — new §6 Iconography; dropped the token row from §2; renumbered Do/Don't → §7, Responsive → §8, Agent quick reference → §9; .claude/skills/responsive-pass/SKILL.md's §7 cross-reference fixed to §8.

Known gap, left as direct lucide-react imports (no vocabulary entry exists for these, and inventing one wasn't part of the plan): markdown-field.tsx's Bold/Italic/Heading/List formatting toolbar, mobile-nav.tsx's hamburger Menu, user-menu.tsx's theme-switcher icons (Monitor/Sun/Moon/SunMoon) and dropdown caret (ChevronUp), and options-chip-editor.tsx's CornerDownLeft "press Enter" hint.

Testing plan

  • /positions as admin, manager, applicant and signed-out: cards show a status badge with an icon; the button row is internally consistent; both empty states show the Briefcase icon.
  • /applications — status icons in the table; filter to zero results and confirm the SearchX empty state with a working "Clear filters"; clear filters with no applications at all and confirm the Inbox empty state.
  • /my-applications, /users, /global-questions — each list's empty state shows its concept icon; icon-only row actions (edit/delete) are reachable and correctly labelled by keyboard alone.
  • Home dashboard as each of the three roles: every widget header shows its concept icon, and the My Applications widget's empty state shows an icon and a title it didn't have before.
  • Sidebar and mobile drawer: Positions is Briefcase, Applications is Inbox, My Applications is FileText, Global Questions is ListChecks, Home is House — each matches the icon on the page it opens.
  • Position detail → Edit → question edit/delete: icon-only row actions have an accessible name (screen reader or accessibility inspector) with no doubled name from a leftover sr-only span.
  • Trigger the error boundary (throw in a page), hit an unknown URL, and hit /429: each shows its state icon.
  • Toggle dark mode on /applications and /positions and re-check every badge icon and muted icon for contrast.
  • At 375px: status badges don't wrap or clip, button rows with icons still fit, icon-only row actions are comfortably tappable.

Automated checks

  • npm run prettier:check — pass
  • npm run eslint:check — pass
  • npm run tsc:check — pass
  • npm run test:unit — 234/234 pass (Postgres unavailable in this worktree, so tests/db/** wasn't run locally — CI's run-tests check covers it)

Notes

  • Purely visual/structural diff — no schema, server action, or data-flow changes.
  • SectionCardEmpty.icon becoming required and the status maps being typed Record<Enum, …> are deliberate compile-time guards: a new enum value or a missing icon fails the build rather than shipping silently icon-less.

b-at-neu and others added 4 commits August 24, 2026 18:13
Five Record<Enum, LucideIcon> maps (concept, application/position
status, action, state, file type) as the single allow-listed source
for every icon in the app, plus a uniqueness test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Zero consumers now that lib/icons.ts owns icon tinting via semantic
text- utilities.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
One sweep: status badges, section cards, empty/error/not-found/rate-
limited states, nav, section headings and labelled buttons all look
their icon up in lib/icons.ts instead of importing lucide-react
directly. Fixes the Home/BriefcaseBusiness/Inbox/FileText concept
drift, folds three hand-rolled empty states back into EmptyState /
SectionCard / PositionStatusBadge, renames deprecated lucide aliases
(Loader2, MoreHorizontal, UserCircle), and drops redundant
size-4/aria-hidden props on icons already covered by a shadcn
primitive's own CSS.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New §6 codifies the lib/icons.ts vocabulary as the allow-list, sizing
and aria-hidden rules, and the shadcn-exempt file list; renumbers
Do/Don't, Responsive and Agent quick reference and updates the
responsive-pass skill's now-stale §7 cross-reference.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@b-at-neu b-at-neu added the claude Will be worked on by Claude label Aug 24, 2026
@b-at-neu b-at-neu self-assigned this Aug 24, 2026
@vercel

vercel Bot commented Aug 24, 2026

Copy link
Copy Markdown

Deployment failed for project aplio with the following error:

Resource is limited - try again in 24 hours (more than 100, code: "api-deployments-free-per-day").

Learn More: https://vercel.com/sga-operational-affairs-projects?upgradeToPro=build-rate-limit

@b-at-neu b-at-neu added the ready for review PR ready for review agent label Aug 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

claude Will be worked on by Claude ready for review PR ready for review agent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Standardize Lucide Iconography Across The App

1 participant