Customization guide
Where to change things, in the order buyers usually want to.
Branding
| What | Where |
|---|---|
| Product name & logo | src/components/logo.tsx (one component, used everywhere) |
| App metadata / titles | src/app/layout.tsx (metadata), per-page metadata exports |
| Design tokens (colors, radius) | src/app/globals.css — :root and .dark blocks; every component reads these variables |
| Fonts | src/app/layout.tsx (next/font definitions) |
| Status color vocabulary | src/components/status.tsx — the single source for status/severity rendering |
| Landing page copy | src/app/page.tsx (in-app) and landing/index.html (sales page) |
The theme system is class-based (next-themes): both light and dark
palettes live in globals.css. Change a token once and every screen —
including the public status page — follows.
Roles & permissions
One file: src/lib/permissions.ts. Add a resource/action to the
statement, grant it to roles, and it is enforced everywhere —
server actions (requirePermission), Better Auth endpoints and
conditional UI (hasPermission) all read this matrix. Roles are plain
strings stored on the membership row; adding a fifth role is additive.
Domain changes
The layering rule is routes → services → tables:
- Schema — add columns/tables in
src/db/schema/*, thennpm run db:generate && npm run db:migrate. - Service — extend the module in
src/modules/<context>/service.ts(functions take(db, actor, input); keep org-scoping in queries). - Action — thin wrapper in
src/app/(app)/<area>/actions.ts: guard → zod parse → service →revalidatePath. - UI — server component fetches via the service; client components call the action and toast on error.
Worked examples to copy from: monitors (full CRUD), incidents (state machine + timeline), status pages (public read model).
Common extensions
- Real email delivery — implement
EmailTransportinsrc/modules/notifications/index.tsand callsetEmailTransportat startup (worker + app). Everything already sends through it. - New check type (TCP/keyword/heartbeat) — extend
src/modules/monitors/check.ts(performHttpCheckhas one call site in the worker), add config columns, surface them inmonitor-form.tsx. - Alert channels (Slack/webhook/PagerDuty) — the worker's
becameDown/becameUpbranches insrc/worker/jobs/monitor-check.tsare the single dispatch point. - More AI actions — follow
src/modules/ai/incident-ai.ts: build a prompt from owned data, add a rate-limited action, keep output in an editable form.
Removing features
Each module is a folder with its routes: deleting AI is removing
src/modules/ai + the two actions + two buttons; deleting status pages
is removing the module, its two route folders and the sidebar link.
Nothing else reaches into them.