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>
crema-app-aifirst-template
Minimal hybrid traditional + AI-first webapp scaffold built on the
Crema design system. Sibling to
crema-app-template
(the bare scaffold) — this template adds the AI assistant surface, the
command bus, scripts, and a virtual cursor.
The pitch: most surfaces are normal (sidenav, tables, forms) and an Assistant surface where the LLM can actually drive the UI on the user's behalf — click, navigate, fill, submit — through a documented command bus that scripts and remote control share.
Quick start
npm install
npm run dev
Open http://localhost:5173. The app probes a local
LM Studio at http://localhost:1234/v1 and falls back to a MockLLM if it
can't reach one — the UI is always usable.
To use the Assistant with a real model:
- Run LM Studio (or any OpenAI-compatible local server — Ollama, vLLM, etc.). Enable CORS in its server settings.
- Visit
/settingsand confirm the base URL. - Visit
/assistantand click Enable UI Control. - Type "take me to settings" — watch the cursor.
What this template gives you
App shell
app/components/layout/app-shell.tsx. Collapsible left rail (icon-only ↔
expanded with labels, persisted), top appbar (search · scripts · theme · bell
· avatar dropdown), mobile sheet at <md. Brand and user identity come from
app/lib/identity.ts — swap useBrand() / useUser() for a real session
later. Skip-to-main-content link, focus rings, [data-action] slots
throughout. The shell is template code, not a lib — fork it, customize
it, no need to upgrade an upstream.
AI assistant with UI control
/assistant route. Streams from any OpenAI-compatible LLM via
@crema/llm-ui, with mock
fallback. Message bubbles render markdown, fenced ```action blocks in the
model's reply are extracted and run through the command bus. Status bar,
model selector, token meter, retry probe, stop-while-streaming, persistent
UI Control toggle.
Command bus + DSL + virtual cursor
Provided by @crema/action-bus.
JSON commands dispatch to handlers; producers are LLM tool calls, scripts,
and (optional) WebSocket — all funnel through one bus.
window.commandBus.dispatch({ type: "navigate", path: "/resources" })
window.commandBus.listActions() // every visible [data-action] on screen
Plus a plain-text DSL for humans / LLM:
navigate /resources
wait_for resources-table
click row-acme-corp
expect detail-panel to_contain "Acme"
Hit ⌘⇧P for the script runner dialog. See docs/AI_FIRST.md for the full system tour.
Mightypix theme
Default theme is lib-theme-mightypix —
warm cream surfaces, ink-blue accent, Source Serif 4 for assistant prose.
Swap by editing one @import line in app/app.css. Per-route alt themes
via <AppShell theme="…">. See app/components/layout/THEME_CONTRACT.md.
Sibling-cloning
Crema libs are sibling git repos, not npm packages. Before npm install
you need them cloned alongside this template:
your-workspace/
crema-app-aifirst-template/ ← this repo
lib-action-bus/
lib-aifirst-ui/
lib-chat-ui/
lib-llm-ui/
lib-notification-ui/
lib-theme-mightypix/
Use the crema add CLI from
create-crema-app,
or clone manually. tsconfig paths and app.css @source lines are
pre-wired.
Structure
app/
routes/ # 6 routes: Overview, Resources, Activity, Assistant, Library, Settings
components/
layout/ # AppShell, Appbar, ThemeToggle, THEME_CONTRACT.md
assistant/ # MessageBody (markdown + action-block pill)
scripts-dialog.tsx # ⌘⇧P script runner UI
ui/ # shadcn primitives
lib/
identity.ts # useBrand() / useUser() — swap for real session
llm-settings.ts # persisted base URL / context / response cap
page-meta.ts # `pageTitle("…")` for route <title>
utils.ts # shadcn `cn()`
app.css # active theme @import + tailwind + @source lines
root.tsx # ToastProvider + CommandBusProvider
public/
scripts/
demo-tour.script # sample DSL: tour the rail
demo-search.script # sample DSL: focus + fill search
docs/
AI_FIRST.md # full system tour
Dev scripts
| Command | What it does |
|---|---|
npm run dev |
Vite dev server |
npm run build |
Production build |
npm run start |
Serve the built app |
npm run typecheck |
react-router typegen && tsc |
bash start.sh / bash stop.sh |
Run dev server in the background, log to .demo.log |
Conventions to know
[data-action="<id>"]on every interactive element — turns it into something the LLM, scripts, and tests can target. Naming:nav-*,appbar-*,avatar-*,assistant-*,settings-*, etc.- Tokens, not values.
bg-card,text-foreground,var(--primary)— never hex. - Lib-shaped components live as sibling repos. Edits commit to the lib's
own repo. See
CLAUDE.mdfor polyrepo gotchas.
Further reading
docs/AI_FIRST.md— full system tourapp/components/layout/THEME_CONTRACT.md— token contractCLAUDE.md— orientation for an LLM working in this repo
What's not in here
- Real auth —
useUser()returns a static stub - Real backend —
/resources,/activity,/libraryare placeholder routes - Production safety gates around LLM-driven actions —
[data-action-danger]and confirmation flows are sketched indocs/AI_FIRST.mdbut not built
These are deliberate. The template is the framework; domain content is what you add.