For agents: an index of these docs is at /docs/llms.txt, and this page as markdown is at /docs/conventions.md.
Conventions
The stack choices Signet assumes.
Decisions made 2026-09-22, from the audit of 23 products in ~/projects.
| Concern | Choice | Why |
|---|---|---|
| Language | TypeScript, strict | 8,775 .ts + 4,599 .tsx vs 123 .js/.jsx. Not a decision so much as an observation. |
| Framework | Vite + React SPA | 13 of 23. Next.js only 2, both incidental. |
| Styling | Tailwind v4 + @dapplelight/signet-tokens |
Already 12 of 23; v3 stragglers should move. |
| Database | Drizzle | Led 3 families to Prisma's 1, and the only ORM that runs properly on Workers. app-i migrates off Prisma eventually. |
| Toast | sonner | Already in 3 families. RoomToast and the hand-rolled one go. Building our own means re-solving stacking, swipe and a11y. |
| Lint + format | Biome | One binary replacing eslint + prettier + a five-package plugin graph. Biome covers the react-hooks rules (useExhaustiveDependencies, useHookAtTopLevel), so the objection to it did not hold. |
| Forms | TanStack Form + zod | Zero of 23 used any form library. Coherent with the TanStack Query already in 7 families, and @dapplelight/signet-lib/form closes the loop with the server error envelope. |
| Backend | Hono + Workers | 4 families on Hono, 5 on wrangler. |
| Errors | @dapplelight/signet-lib/api envelope |
{ success: false, error, code?, details? }, written by @dapplelight/signet-server/http. |
| Analytics | Cloudflare, two tiers | See packages/lib/ANALYTICS.md. Nothing in app-a or app-e. |
Why Biome, specifically
The eslint config in the existing repos crashes on current plugin versions:
TypeError: Cannot read properties of undefined (reading 'recommended')reactHooks.configs.flat.recommended was removed in eslint-plugin-react-hooks
5.2. Those repos only still work because their lockfiles pin older versions —
copying that config into anything new breaks immediately. It had been
copy-pasted into exactly 8 of 23 repos, identical counts for all five packages.
Biome removes the plugin graph that rotted.
Dependency versions
Pinned to latest as of 2026-09-22 and verified by building, not by assumption. Three majors broke real code and were fixed rather than pinned back:
- TypeScript 7 removed
baseUrl. The scaffold'stsconfig.app.jsonnow usespathsalone. - @tanstack/react-table 9 made features opt-in and renamed the API.
data-table.tsxnow declarescolumnSizingFeature,columnResizingFeature,columnVisibilityFeatureandrowSelectionFeatureexplicitly; the core row model is built in and no longer passed. The per-columnTValuegeneric is gone from the public surface, because v9'suseTableis typed againstColumnDef<…, unknown>. - react-day-picker 10 renamed the
tableclassname slot tomonth_grid.
Run bun run test in every package after any bump. The suites caught all three.
Signet ships a default for everything it owns
Where Signet defines a behaviour — an error code, the envelope's message, the 429 text, a guard's failure, a validation message, a retry policy, a notification template — it ships a working, product-neutral default and an override point: an option, a spreadable object, or a callback. An app then writes only what its product needs to say differently, never the same boilerplate every other app writes.
Example: DEFAULT_ERROR_COPY in @dapplelight/signet-lib/api. An app spreads it,
overrides a few entries and adds its own codes, and satisfies Record<ErrorCode | AppErrorCode, ErrorCopy> keeps it complete at compile time.
A default must not change the app's behaviour in a way it did not ask for. A
default that weakens security or delays revocation is not a default; it is an
option (see betterAuthDefaults).