Standards
Single source of truth for how rr-based projects are built. Two surfaces, one data file — the Docs tab is for humans, the AI Prompt tab is for pasting into your AI agent so it follows the same rules.
Every rr-based app targets the same modern Next + React + Convex baseline. Drift = compatibility risk for the rr slice catalog.
Pin Next ^16 and React ^19 in package.json. No `middleware.ts` — use `proxy.ts` instead.
Why: Next 16 deprecates middleware.ts and ships App Router + Cache Components as the default.
// package.json "next": "^16.0.0", "react": "^19.0.0"
Use Tailwind v4 with `@tailwindcss/postcss`. Bridge a v3 config via `@config` only during migration.
Use Convex self-hosted via Docker Compose on the same Dokploy node. Pin `convex` ^1.16 minimum.
Why: Self-hosted = zero per-user cost, full schema portability, deploy via `npx convex deploy --env-file …`.
Use `@convex-dev/auth` for sessions. NO Clerk. Custom auth slices are allowed only when @convex-dev/auth is documented as insufficient.
Every feature is a vertical slice that owns its full stack. No deep cross-slice imports.
Each feature lives at `frontend/slices/<slug>/` (UI + types) + optionally `convex/features/<slug>/` (schema + queries + mutations).
frontend/slices/cta/ ├── components/ ├── lib/ ├── views/ ├── config.ts ├── index.ts └── slice.json slice.contract.ts slice.manifest.json
Other slices import via `@/features/<own-slug>` only. Never reach into `@/features/foo/lib/internal-thing.ts`.
Why: Deep imports lock you into another slice's internal layout. Barrels are the contract.
Every slice ships `slice.json` (schema-validated metadata), `slice.contract.ts` (typed DSL), and `slice.manifest.json` (CLI distribution payload).
Portable slices NEVER hardcode consumer-specific URLs, env names, or copy. Hardcode = lift blocker.
// BAD
const SITE = "https://rahmanef.com";
// GOOD
export function HeroView({ siteUrl }: { siteUrl: string }) { … }Every `mutation()` / `query()` reachable from the client must declare `args:` with `v.*` validators.
Why: Convex's audit-bp marks missing validators as P0 — anything goes from a crafted client without them.
`ctx.db.query(...).collect()` scans the table. Use `.withIndex(...).take(N)` or paginate.
Why: Bare collects bypass the query-budget guardrails and degrade as the table grows.
// BAD
await ctx.db.query("posts").collect();
// GOOD
await ctx.db.query("posts").withIndex("by_createdAt").order("desc").take(50);Call `requireUser` / `requireAdmin` from `convex/_shared/auth.ts` inside the handler. Never trust route-layer gates alone.
Why: Convex HTTP queries are directly reachable — Next.js layout gates don't protect them.
Every query that filters or orders should use `.withIndex(...)`. Add the index in the schema's `defineTable(…).index(…)`.
Next 16 renamed middleware to proxy. Move logic to `proxy.ts` at the project root.
Never use `<a href="/internal">` or `<img src=…>`. Use `<Link>` / `<Image>` so Next can prefetch + optimise.
Any value prefixed `NEXT_PUBLIC_` is exposed in the client bundle. Never put secrets, API keys, or admin emails there.
SSG marketing pages should opt into Cache Components via `"use cache"` + `cacheLife` / `cacheTag`. Enable `experimental.cacheComponents` in next.config.mjs first.
`'use server'` exports MUST verify the caller before mutating state. Treat them like public API endpoints.
Files are read more than written. Keep them small, single-purpose, and composable so consumers can grok + reuse + replace pieces without reading the whole thing.
Hard cap: no source file may exceed 200 lines (excl. pure data exports like `lib/content/*.ts` catalog arrays, `*/seed.ts`, theme presets, and `_generated/`). If a component, route, or module is approaching the cap, split before shipping. Audit gate: `audit:file-size`.
Why: Large files hide concerns, resist diff review, force consumers to scroll instead of compose. The cap forces extraction of reusable pieces — composition over accumulation.
// BAD: 400-line PostEditor.tsx with toolbar + body + sidebar + status panel // GOOD: PostEditor.tsx (≤200) composes <Toolbar/> + <EditorBody/> + <SidebarMeta/> + <StatusPanel/> from neighbour files
One default export OR one cohesive cluster of named exports per file. If you find yourself prefixing exports (`createX`, `parseX`, `serializeX`, `validateX`) — those are 4 files, not 4 exports.
Why: Single-responsibility files are testable in isolation, replaceable without ripple, and reusable without context.
If a UI pattern (filter pills, status badge, picker grid) repeats — extract to `components/` or `shared/`. If two slices need the same util — promote to `shared/<name>/utils/`.
Why: Duplication compounds: the third copy is where bug-fix divergence starts. Extract on the SECOND occurrence, not the third.
Prefer config-driven + props-driven code. Replace switch/if-chains with lookup maps. Replace literal arrays with derived selectors. Replace inline copy with `labels` props.
Why: Dynamic code adapts when the consumer customizes; hardcoded code forces them to fork.
// BAD
if (kind === 'admin') return <AdminLink/>;
if (kind === 'user') return <UserLink/>;
// GOOD
const LINKS = { admin: AdminLink, user: UserLink };
const Link = LINKS[kind];
return <Link/>;When adding a feature, ask: can I add a new file that COMPOSES with the existing one, instead of editing the existing one bigger?
Why: Open-closed principle in practice. Existing file stays small + tested; new file is the one that changes.
All UI builds on shadcn primitives. Never use raw `<button>`, `<dialog>`, `<input type=date|file>` directly — wrap with `ResponsiveDialog`, `DateField`, `FileUpload`.
Use `bg-background` / `text-foreground` / `border-border` etc. Tailwind theme tokens make preset swaps work.
Layout breakpoints climb up — start at single-column on mobile, layer `md:` / `lg:` modifiers.
When tests/typecheck/validate are green, push direct to main. NO PRs for solo work. Dokploy auto-deploys on push.
Why: PRs add ceremony without review benefit when the solo dev is also the reviewer.
`feat(scope): subject` / `fix(scope): subject` / `chore(scope): subject`. Body explains the WHY.
End every AI-assisted commit message with `Co-Authored-By: Claude … <noreply@anthropic.com>` so authorship is honest.
No GitHub Actions cloud minutes. Local CI via pre-push hook or `/sc-git ci`; Dokploy auto-builds on push.
rr publishes TWO different installable kinds. They install to different paths and answer different needs — confusing them is the #1 source of "the output looks nothing like the docs" reports.
A TEMPLATE (catalog: `lib/content/layouts.ts`, e.g. `personal-brand-os`, `agency-studio-os`) is a whole-app starter — public marketing routes + admin dashboard + Convex schema. Install with `npx rr add <template-slug>` (defaults to `--at root` → routes promoted to `app/(public)/` + `app/admin/`, hardcoded `/preview/<slug>` path constants in nav-config/site-config/robots/sitemap auto-rewritten). Pass `--at preview` only for sandbox demos that keep the `/preview/<slug>` URL prefix.
Why: Templates are NOT vertical slices — they don't ship `slice.json` + `slice.contract.ts` + `slice.manifest.json`. They're monolithic scaffolds you fork and customize.
A SLICE (catalog: `lib/content/slices.ts`, e.g. `comments`, `doku-payment`, `ai-chat`) is one self-contained feature. Install with `npx rr add <slice-slug>` — CLI copies files into `frontend/slices/<slug>/` + (optionally) `convex/features/<slug>/`. Each slice ships the metadata trio (`slice.json` + `slice.contract.ts` + `slice.manifest.json`) and is props-driven so it composes with the rest of your app.
Why: Slices are mix-and-match. The trio is what makes a slice composable — without it the CLI can't audit dep peers, env, RBAC scopes, or table collisions.
CLI auto-detects kind via catalog lookup and prints `[TEMPLATE]` or `[SLICE]` in the banner. Trust the banner — if you expected a slice and got `[TEMPLATE]`, you used the wrong slug.
Before pushing UP to rr (slice path only), strip consumer-specific URLs, env names, role enums, and table coupling. Replace with props or env-configured allowlists.
New slice in rr needs: catalog entry in `lib/content/slices.ts` + `slice.json` + `slice.contract.ts` + `slice.manifest.json`. Validate with `npm run validate:all` (chain includes `audit:slices` + `audit:templates`).
Add ChatGPT / Claude / Cursor connector support via `npx rr add create-your-mcp` — DON'T roll your own OAuth/PKCE.