- Replace the `slot` / `add-on` discriminated-union split with one `Category` shape carrying two orthogonal fields: `cardinality` (`"one"` for single-choice, `"many"` for coexisting) and `home` (`"app"` / `"repo"` / `"package"`) — the old `packageDir` + `repoScoped` pair is dropped - `Module` is no longer a discriminated union; it carries a single `category` field referencing a `Category` id; `KNOWN_SLOTS`, `KNOWN_ADDONS`, `ADDON_CATEGORIES`, and all related helpers are replaced by a single `CATEGORIES` array and derived `categoryHome` / `categoryLabel` / `PACKAGE_DIRS` lookups - Manifest schema bumped to version `0.2` — `modules` is now one record keyed by category holding arrays; `cardinality: "one"` categories are enforced to ≤ 1 entry at install time; no migration (stanza is pre-1.0 and unpublished) - Peer/constraint logic is now emergent from cardinality: the resolver treats only `cardinality: "one"` categories as peers, so `testing` (many) can never accidentally become a peer; install routing is a single `categoryHome` lookup shared by the CLI runner, codemod runner, and web preview - Update all CLI commands (`add`, `remove`, `init`, `list`, `search`), the wizard, `codemod-runner`, `registry-loader`, all web builder/detail/search components, server functions, OG route, sitemap, analytics, and selection logic to use the new category API - Update all first-party `module.ts` files, `registry-build.ts`, tests, and docs (`AGENTS.md`, `REGISTRY.md`, `SKILL.md`) to reflect the unified taxonomy
7.3 KiB
First-party module registry
This is the canonical roadmap for the first-party modules stanza ships. Each
entry maps to a registry/modules/<category>-<id>/ directory. Update this file
when a module lands, gets renamed, or is dropped.
Legend: [x] added · [ ] planned
Categories
Every module fills exactly one category. A category has two independent,
explicit properties (CATEGORIES in module.ts):
cardinality—"one"(single-choice: framework, auth) or"many"(coexisting: testing, deploy). This is the only thing that decides single- vs multi-select in the wizard/web and length in the manifest.home— where the module's output lands:app(manifest.appDir),repo(monorepo root), orpackage(packages/<dir>/).
Constraint-bearing is emergent, not a category property: a category is a
peer candidate only if some module declares peers/match against it, and the
resolver only treats cardinality: "one" categories as peers (PEER_CATEGORIES).
| Cardinality | Categories | Examples |
|---|---|---|
| one (single) | framework, styling, db, orm, auth, tooling, api, ai, ui, payments |
next vs tanstack-start; drizzle vs prisma |
| many (coexist) | testing, deploy, email, monorepo |
vitest + playwright together |
A Module carries a single category field (no kind/slot/category
discriminator). The manifest stores everything in one modules record keyed by
category, each holding an array (Partial<Record<CategoryId, StanzaModuleRecord[]>>); cardinality: "one" categories are kept to ≤ 1 record
at install time. selectedOne/selectedAll read it ergonomically.
framework
- tanstack-start — TanStack Start on Vite (no Vinxi). Provides
web,react,ssr,node - next — Next.js 16 (App Router). Provides
web,react,ssr,rsc,node,edge - nuxt — Vue. Will require relaxing the React-implicit assumption in many peer modules (capability tag
vue) - svelte — SvelteKit. Capability
svelte - solid — SolidStart. Capability
solid
api
New slot (single-choice, constraint-bearing). Optional layer between the framework and the database/services.
- trpc — tRPC v11; per-framework adapters (next, tanstack-start, nuxt, svelte, solid)
- orpc — oRPC
ai
New slot (single-choice, constraint-bearing).
- vercel-ai-sdk —
aipackage + provider sub-recipes - tanstack-ai — TanStack AI
auth
- better-auth — headless, peers
orm: [drizzle, prisma],framework: [next, tanstack-start] - clerk — hosted UI, peers
framework: [next](TanStack Start adapter planned) - workos — WorkOS AuthKit
orm
- drizzle — Drizzle ORM 0.45, peers
db: [postgres, sqlite] - prisma — Prisma 7, peers
db: [postgres, sqlite]
db
- postgres —
postgresdriver 3.4.x - sqlite —
better-sqlite312.x
styling
- tailwind — Vanilla Tailwind v4, adapters per framework
- shadcn-radix — classic shadcn/ui (Radix primitives)
- shadcn-base — shadcn on react-base-ui
payments
New slot (single-choice, constraint-bearing). In addition to adding example files to the framework app, each of these have a plugin for better-auth -- add those too if better-auth is selected, either before or after this selection.
- stripe — Checkout Sessions + webhooks
- polar — Polar SDK
- autumn
- dodo payments
cardinality: many, app-scoped.
- resend — Resend SDK + React Email templates
tooling
cardinality: one, repo-scoped. Lint/format toolchain — single-choice because the three toolchains are mutually exclusive substitutes. Bears no outbound dispatch constraints (nothing peers on tooling) but consumes a framework peer where the config varies.
- eslint-prettier — ESLint flat config + Prettier; per-framework adapters (next, tanstack-start)
- biome — Biome (lint + format), framework-agnostic
- oxlint-oxfmt — Oxlint + oxfmt, framework-agnostic
testing
cardinality: many, app-scoped. Vitest and Playwright are independent and routinely coexist.
- vitest — unit + integration; per-framework adapters (next, tanstack-start),
jsdom+ RTL,test/test:watchscripts - playwright — e2e; per-framework
webServer(next dev/vite dev),test:e2e/test:e2e:uiscripts (disjoint from vitest's)
deploy
cardinality: many, repo-scoped.
- vercel —
vercel.json+ framework-specific output, add the nitro vite plugin for tanstack start - cloudflare — Workers / Pages adapter per framework
- railway —
railway.toml+ Dockerfile - docker — generic
Dockerfile+ compose for self-host
monorepo
cardinality: many, repo-scoped. Currently hardcoded in bootstrapShell as Turborepo; becomes a real category when a second option (Nx, Moonrepo) lands.
- turborepo — Turbo 2.x (current default; not yet a configurable choice)
packageManager
Not a slot — a top-level field in stanza.json (packageManager: "pnpm" | "bun" | "npm"). Wizard prompts; codemods only touch package.json, never lockfiles.
- pnpm (default)
- bun
- npm
- yarn — needs lockfile/workspace handling that differs from the others
Install homes (package extraction)
A category's home places its modules' output in one of three places:
app(framework,styling,testing,email) — files land inmanifest.appDir(e.g.apps/web/). For categories that wire the app shell or test it.package(auth,db,orm) — files land inpackages/<dir>/, named@<manifest.name>/<dir>, and the app gets aworkspace:*dep.dbandormshare a singlepackages/db/package so the ORM client sits next to the schema it queries.repo(tooling,deploy,monorepo) — config files land at the repo root and scripts/devDeps merge into the rootpackage.json, because one config governs every workspace.
The mapping lives in the canonical CATEGORIES array as the home tagged union. categoryHome(id) and PACKAGE_DIRS are derived views. When adding a category, pick the home that matches: data layer/payments → package; app shell/router → app; repo-wide tooling → repo.
Adding a category
A one-line edit: append a Category entry to CATEGORIES with { id, label, description, cardinality, home }. CategoryId, KNOWN_CATEGORIES, PEER_CATEGORIES, and PACKAGE_DIRS all derive from it — nothing else to keep in sync. Order is topological: a category must appear after every category it can peer on, and many (leaf) categories come last. Authoring a module sets a single category field on defineModule; the runner/CLI/web handle single- vs multi-choice off the category's cardinality. Existing stanza.json files don't break (new categories are optional).