Folder Structure
The Turborepo layout: four apps, shared packages, and the rule that packages never depend on apps
Init is a Turborepo monorepo with clear separation between applications and shared packages. Apps consume packages; packages never depend on apps.
Overview
init/
├── apps/
│ ├── web/ # Next.js 16 web app
│ ├── mobile/ # Expo mobile app
│ ├── extension/ # Chrome extension (WXT)
│ └── desktop/ # Tauri desktop app
├── packages/
│ ├── permissions/ # Client-safe auth helpers: role permissions, slugify
│ ├── contract/ # oRPC contract: procedure inputs, outputs, errors
│ ├── service/ # oRPC implementation of the contract + better-auth
│ ├── db/ # Drizzle schema + local Supabase config
│ └── ui/ # Shared React components
├── scripts/ # bootstrap.ts (provision + trim), seed via packages/service
├── package.json # Root workspace scripts
├── turbo.json # Turborepo pipeline + env
└── pnpm-workspace.yaml # Workspaces + version catalogApplications (apps/)
Web App (apps/web/)
Next.js 16 with the App Router:
apps/web/
├── content/
│ └── docs/ # These docs (fumadocs MDX)
├── src/
│ ├── app/
│ │ ├── (auth)/ # Login, register, password reset
│ │ ├── (dashboard)/ # Protected dashboard
│ │ │ └── dashboard/
│ │ │ ├── [slug]/ # Org pages: todos, members, settings, billing
│ │ │ └── account/ # User account settings
│ │ ├── (docs)/ # Docs routes (fumadocs)
│ │ ├── (marketing)/ # Public landing page
│ │ └── api/
│ │ ├── auth/[...all]/ # better-auth handler
│ │ ├── orpc/[[...rest]]/ # oRPC endpoint
│ │ ├── health/ # Liveness check
│ │ └── search/ # Docs search
│ ├── components/ # App-level components (header, nav, theme)
│ ├── lib/ # auth-client, site-config, fumadocs source
│ ├── orpc/ # oRPC client setup (react, server, query-client)
│ └── proxy.ts # Markdown content negotiation; signed-out /dashboard → login
├── next.config.ts
└── package.jsonRoute Conventions
(auth)/,(dashboard)/,(marketing)/,(docs)/- route groups[slug]/- dynamic org routes_components/- colocated components, not routes
Mobile App (apps/mobile/)
Expo + React Native with NativeWind:
apps/mobile/
├── src/
│ ├── app/ # Expo Router (_layout.tsx, index.tsx)
│ ├── utils/ # auth (SecureStore), api (oRPC), base-url
│ └── styles.css
├── app.config.ts
└── package.jsonChrome Extension (apps/extension/)
Built with WXT:
apps/extension/
├── src/
│ ├── entrypoints/ # background, popup, options
│ ├── assets/
│ └── lib/
└── wxt.config.tsDesktop App (apps/desktop/)
A Tauri 2 shell around the web app, not a second frontend: a Rust binary that opens the system webview (WebKit on macOS) on devUrl under pnpm dev:desktop and on frontendDist in a build. The web app talks to it through apps/web/src/lib/desktop-bridge.ts, and only through what its capability grants:
apps/desktop/
├── src-tauri/
│ ├── src/
│ │ ├── lib.rs # the builder: plugins, commands, menu, window
│ │ ├── window.rs # the window tauri.conf.json declares, with its navigation pin
│ │ ├── navigation.rs # which origins load in the window and which open in the browser
│ │ ├── menu.rs # Tauri's default macOS menu, plus Settings… and Check for Updates…
│ │ └── updater.rs # the update flow behind three commands and one event
│ ├── capabilities/ # what the web app may invoke
│ ├── build.rs # the app manifest: every command is a permission
│ ├── Cargo.toml
│ └── tauri.conf.json # identifier, URLs, the window, bundle settings
└── rust-toolchain.toml # the Rust version local checks and CI shareShared Packages (packages/)
Contract Package (packages/contract/)
The API's single source of truth - what each procedure accepts, returns, and can fail with. Clients type against it without importing server code:
packages/contract/src/
├── organization/ # organization-contract.ts, organization-schema.ts
├── todo/ # Example CRUD contract
├── waitlist/ # Waitlist contract
├── base.ts # publicBase, protectedBase, organizationBase
└── index.ts # Exports: contract, ContractClient, RouterInputs/OutputsConvention per feature: *-contract.ts (procedures), *-schema.ts (Zod inputs).
Service Package (packages/service/)
oRPC implementation of the contract, plus better-auth config:
packages/service/src/
├── auth/ # auth.ts (better-auth), auth-schema.ts, utils
├── organization/ # organization-router.ts, -test
├── todo/ # Example CRUD router
├── waitlist/ # Waitlist router
├── root-router.ts # Implements the whole contract
├── orpc.ts # Context, implementer, middleware
├── test-utils.ts # node:test helpers
└── index.ts # Exports: appRouter, AppRouter, createORPCContextConvention per feature: *-router.ts (handlers), *.test.ts (tests).
Permissions Package (packages/permissions/)
Client-safe auth helpers, imported by the server and by web and mobile clients alike:
packages/permissions/src/
├── permissions.ts # better-auth access control: ac, roles, hasPermission, ROLES, roleSchema
└── slug.ts # slugify, FALLBACK_ORGANIZATION_SLUGNo server code (database, better-auth instance, secrets) belongs here.
Database Package (packages/db/)
packages/db/
├── src/
│ ├── drizzle-schema.ts # App tables (waitlist, todo)
│ ├── drizzle-schema-auth.ts # better-auth generated tables
│ ├── drizzle-relations.ts # defineRelations graph over both schema files
│ └── drizzle-client.ts # postgres.js + Drizzle client
├── supabase/config.toml # Local Supabase: Postgres 17 alone, native runtime
├── drizzle.config.ts
└── package.json # db scripts (start, push, reset, studio)UI Package (packages/ui/)
shadcn-style components on Base UI (base-vega registry):
packages/ui/
├── src/
│ ├── components/ # Flat primitives
│ ├── hooks/
│ └── styles/globals.css
├── components.json # shadcn config (style: base-vega)
└── postcss.config.mjsApps import explicit paths:
@repo/ui/components/<name>@repo/ui/globals.css
Add components from the registry:
cd packages/ui && pnpm dlx shadcn@latest add <component>Documentation
No separate docs package - these pages are MDX files in apps/web/content/docs/, rendered by fumadocs (apps/web/src/lib/source.ts).
Configuration Files
package.json- Workspace scripts (dev:web,db:push, ...)turbo.json- Pipeline +globalEnvallow-list for env varspnpm-workspace.yaml- Workspaces and the shared version catalog.oxlintrc.json- Linting (oxlint); formatting is oxfmt
Key Design Principles
- Separation of concerns - Apps own UX; packages own logic, data, and primitives
- Code sharing - One oRPC router, one auth config, one schema for every platform
- Type safety - Types flow from schema through API to UI, no codegen
- Explicit imports -
@repo/ui/components/button, not barrel files
Adding New Features
New API Routes
- Add
packages/contract/src/<feature>/with contract + schema, and register it inpackages/contract/src/index.ts - Add
packages/service/src/<feature>/<feature>-router.tsimplementing it - Register in
packages/service/src/root-router.ts - Consume with full types via the
orpcutils
New UI Components
- Add to
packages/ui/src/components/(orpnpm dlx shadcn@latest add) - Import as
@repo/ui/components/<name>
New Database Tables
- Define in
packages/db/src/drizzle-schema.tswithpgTable.withRLS - Run
pnpm db:push - Types flow everywhere automatically