Files
crema-app-aifirst-template/app/components
jules 3dbf2ac175 feat: auth scaffold, notifications inbox, resources CRUD, vitest baseline, typed API client
Auth
- ~/lib/session.ts: Session type + loadSession/signIn/signOut/hasSession,
  reactive useSession hook (mock backend; replace fetch calls with your
  real auth endpoint when ready)
- routes/login.tsx: form with email/password (mock-validated), bounces
  to ?next= on success
- AppShell: redirects to /login when no session; account-menu Sign out
  now actually signs out; live session.name/email used for the appbar
  avatar (falls back to profile)

Notifications
- ~/lib/notifications.ts: persistent inbox with kinds (info/success/
  warning/error), unreadCount, markRead, markAllRead, dismiss,
  dismissAll; seedIfEmpty for a friendly first-run
- AppShell bell: 320px popover with badge, kind dots, per-row open
  (navigates to href) and dismiss; Mark all read + Clear actions
- Hidden NotificationDispatcher in AppShell so the action bus can
  create real notifications via fill notif-title / notif-body /
  notif-kind / notif-href + click notif-create

Data layer
- ~/lib/api.ts: typed apiFetch<T> + api.get/post/put/patch/del,
  auto-attaches the session token, throws structured ApiError, signs
  out on 401
- ~/lib/resources.ts: example domain entity (CRUD) backed by
  localStorage today; each call is a 1:1 swap for api.get/post/put/del
- routes/resources.tsx: real working table — search, add, inline
  status edit, delete; seeded demo rows on first load

Tests
- vitest + jsdom + @testing-library/react + @testing-library/jest-dom
  + vite-tsconfig-paths installed
- vitest.config.ts (jsdom, globals, ~ aliases via tsconfig-paths)
- vitest.setup.ts (RTL cleanup + localStorage clear between tests)
- app/lib/session.test.ts and resources.test.ts as starter coverage
- npm test / npm run test:watch scripts

UI Control catalog
- Login form, resources CRUD, notifications inbox, and the hidden
  notif-bridge ids tagged so the assistant can drive every new surface

Threads
- ThreadMessage now carries optional agentId so per-message authorship
  survives persona switches and handoffs

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 15:59:31 +10:00
..

components/

Component layers in this project.

ui/          shadcn primitives — token-driven, reskinnable per design system
forms/       composed form widgets
data/        data display (tables, filters, empty states)
layout/      app shell, page chrome, navigation wrappers
marketing/   landing and marketing blocks
[system]/    design-system-specific components (e.g. m3/, apple/)

The one rule

Custom components import from ui/, never the reverse.

ui/ is the primitive layer. It must stay reskinnable by swapping tokens in app/themes/*.css alone. If a component can't be expressed that way (M3 ripple, Apple segmented control, etc.), it belongs in a system-specific folder — not ui/ and not the shared folders above.

Tokens, not values

Every custom component should reference semantic tokens:

  • Colors: bg-primary, text-muted-foreground, border-border
  • Radius: rounded-md, rounded-lg
  • Fonts: font-sans, font-heading

Hardcoded hex, oklch, or px values are a bug — they break theming.