Code Placement Guide
Section titled “Code Placement Guide”Answer: “If I am adding X, where does it go?”
For structural context see 00-repo-map.md. For dependency rules see 03-dependency-rules.md. For category definitions (app / worker / feature / domain / platform / contract / shared UI) see 02-package-categories.md.
Ownership rules by area
Section titled “Ownership rules by area”Use these before creating a file.
| Area | Owns | Put code here when… | Keep out |
|---|---|---|---|
apps/dashboard | Dashboard routes, page composition, auth, server functions, app-shell wiring | the code is dashboard-specific or route-owned | generic shared libraries and worker runtime code |
apps/marketing | Public marketing site | the code is public marketing content/pages | backend worker runtime code |
apps/docs | Public documentation site | the code is docs site content/pages | backend worker runtime code |
packages/feature-* | Reusable dashboard feature queries, mutations, models, feature-safe UI slices | the code is reused across multiple dashboard screens and should stay buildable outside the app shell | route/page composition and direct DB/runtime ownership |
packages/platform-* | Cross-feature platform infrastructure | the code supports multiple features or apps without being domain-specific | dashboard-only page logic |
packages/domain-* | Pure business/domain logic | the code is framework-light, portable, and business-centered | Cloudflare runtime adapters and UI wiring |
workers/* | Worker entrypoints, worker features, runtime adapters | the code depends on queues, Durable Objects, worker env bindings, or worker orchestration | browser-facing app code |
Fast rules
Section titled “Fast rules”- Keep user-facing deployable entrypoints in
apps/. - Keep reusable libraries in
packages/. - Keep dashboard page composition in
apps/dashboard, even when the data layer lives inpackages/feature-*. - Keep worker domain logic in
workers/*/src/features/and worker adapters inworkers/*/src/infra/. - Move code to
packages/only when there is a real shared owner, not a hypothetical future reuse case.
Quick lookup table
Section titled “Quick lookup table”| What you are adding | Where it goes | Notes |
|---|---|---|
| New dashboard page (UI) | apps/dashboard/src/features/{domain}/pages/ | Route file is thin; page component lives here |
| New TanStack route file | apps/dashboard/src/routes/{-$locale}/(dashboard)/ | Locale-prefixed, file-based |
| Route loader | Inside the route file | Keep loaders small; delegate to server functions |
Server function (createServerFn) | apps/dashboard/src/server/functions/{domain}/index.ts | Grouped by domain |
| Dashboard UI component (domain-specific) | apps/dashboard/src/features/{domain}/components/ | Owned by the feature |
| Generic UI primitive (button, input, table, skeleton) | apps/dashboard/src/shared/ui/ | shared UI category; no feature imports allowed |
| App-shell layout component (dashboard page header, shell framing) | apps/dashboard/src/app/layouts/ | app-shell category; may be consumed across features |
| Shared UI primitive (used by 2+ features) | apps/dashboard/src/shared/ui/ | shadcn-style; no feature imports allowed |
| Business-named component (PeopleTable, PublicationsFilters, SyncStatusBadge) | owning feature components/ | feature-owned UI; may depend on shared primitives |
| Shared app-wide hook (used by 2+ features) | apps/dashboard/src/hooks/ | Only if genuinely cross-feature |
| Domain hook (used by 1 feature) | apps/dashboard/src/features/{domain}/hooks/ | Keep it in the feature |
| TanStack Query keys | packages/platform-query/src/ for shell-level; packages/feature-{domain}/src/query-keys.ts for domain | Do not scatter inline |
| TanStack Query options / hooks | packages/feature-{domain}/src/queries.ts | Co-located with the feature |
| API transport wrapper (calls server fn) | packages/feature-{domain}/src/api.ts | Dynamic import() + call() pattern |
| Business/domain rule | packages/feature-{domain}/src/ or apps/dashboard/src/features/{domain}/ | Prefer extracted package if reused across surfaces |
| Zod schema for API input/output | packages/schemas/src/index.ts | Shared schemas only |
| TypeScript interface / shared type | packages/types/src/ | Interfaces only; no logic |
| DB schema change | packages/db/src/schema.ts → run pnpm db:drizzle:generate | All 5 shards share the same schema |
| D1 migration SQL | migrations/ + Drizzle migration files in packages/db/drizzle/ | Run schema gen first |
| Cloudflare binding or runtime adapter | apps/dashboard/src/server/ or packages/platform-cloudflare/src/ | Never in client-safe folders |
| Auth logic (session, CSRF, OAuth) | apps/dashboard/src/server/auth/ | Do not split auth across lib |
| Ingestion pipeline feature | workers/ingestion-process/src/, workers/ingestion-orchestrator/src/, or packages/platform-ingestion/src/ | Worker entrypoints own routing; shared ingestion runtime lives in @platform/ingestion |
| Worker infrastructure adapter | workers/*/src/infra/ | Keep runtime adapters with the owning worker |
| Public API route | workers/consumer-api/src/ | Read-only; no writes |
| OpenAPI / integrations contract | packages/api-contract/src/ | Versioned; generates docs |
| Integration runtime helper | packages/integration-core/src/ | Shared across integration flows |
| Event bus event or handler | packages/platform-events/src/ | Typed events, registry, cache invalidation |
| i18n locale string | apps/dashboard/src/locales/{locale}/ → synced via packages/platform-i18n | Run locale key check after adding |
| E2E test | tests/features/{domain}.mjs or tests/features/{domain}/cases/ | Node harness, .mjs files |
| Unit/integration test (Vitest) | Co-located with source file as {file}.test.ts(x) | Same directory as the file under test |
| Deploy script | scripts/deploy/ | Bash; use scripts/lib/common.sh helpers |
| DB or migration script | scripts/db/ | Bash or .mjs |
| CI orchestration script | scripts/test/ci.mjs | Node ESM |
| Operational runbook | apps/docs/src/content/docs/guides/ | Public-facing; use for production ops |
| Internal runbook / ADR | docs/ | Internal; not published |
| In-flight design doc | plans/ | Temporary; delete when work lands |
| Admin CLI command | cli/src/commands/ | legaciti binary |
| Ingestion tracing primitives | packages/platform-ingestion/src/infra/telemetry/tracing.ts | Shared tracing helpers reused by split ingestion workers |
Decision tree
Section titled “Decision tree”Is this UI code?
Section titled “Is this UI code?”Is it domain/business named or tied to one feature's data model? YES → apps/dashboard/src/features/{domain}/components/ NO → Is it app-shell framing (header, dashboard page intro, nav chrome)? YES → apps/dashboard/src/app/layouts/ NO → Is it a low-level primitive (button, card, input, dialog, table primitive)? YES → apps/dashboard/src/shared/ui/ NO → keep with current owner and document a narrow exceptionIs this query code?
Section titled “Is this query code?”Is it a query key or retry config shared across multiple features? YES → packages/platform-query/src/ NO → Is it query options/hooks for one domain? YES → packages/feature-{domain}/src/queries.ts NO → Is it a shell-level key (top nav, global state)? YES → packages/platform-query/src/shell-query-keys.tsIs this server/runtime code?
Section titled “Is this server/runtime code?”Does it touch Cloudflare bindings, D1, KV, auth sessions, or SES? YES → apps/dashboard/src/server/{...} NO → Is it a pure utility? YES → apps/dashboard/src/lib/ (app helpers) or packages/utils/ (truly shared)Is this a shared type or schema?
Section titled “Is this a shared type or schema?”Is it a TypeScript interface only (no logic)? YES → packages/types/src/Is it a Zod schema for request/response validation? YES → packages/schemas/src/index.tsIs it a DB column/table shape via Drizzle? YES → packages/db/src/schema.tsConcrete placement examples
Section titled “Concrete placement examples”| If you are adding… | Put it here | Why |
|---|---|---|
| New dashboard page route | apps/dashboard/src/routes/{-$locale}/(dashboard)/ | route declaration stays app-local |
| Dashboard page component | apps/dashboard/src/features/{domain}/pages/ | page composition belongs to the app shell |
| Dashboard-only toast hook | apps/dashboard/src/features/{domain}/hooks/ | tied to one dashboard feature owner |
| Reusable query options for publications | packages/feature-publications/src/queries.ts | reusable feature data layer |
| Query key shared across multiple features | packages/platform-query/src/ | platform-level concern |
| Pure publication normalization helper | packages/utils/src/ | framework-light shared utility |
| Shared affiliation policy | packages/domain-people/src/ | domain logic, not UI wiring |
| Cloudflare D1 adapter for a worker | workers/*/src/infra/ | worker runtime ownership |
| Queue-consumer business flow | workers/ingestion-process/src/, workers/ingestion-orchestrator/src/, or packages/platform-ingestion/src/ | worker entrypoints own routing; shared ingestion runtime lives in @platform/ingestion |
| Public API route handler | workers/consumer-api/src/ | worker-owned deployable surface |
What NOT to do
Section titled “What NOT to do”- Do not add Cloudflare bindings access to
src/lib/orsrc/features/ - Do not statically import server functions from client code (Oxlint blocks this)
- Do not let shared UI components import from feature modules (Oxlint blocks this)
- Do not edit
apps/dashboard/src/routeTree.gen.ts— auto-generated by TanStack Router - Do not add business logic to route files; delegate to feature page components
- Do not centralize code into
lib“just in case”; keep it in the feature until it is genuinely shared - Do not add new production code to
workers/my-api/— it is DEPRECATED - Do not add dashboard page components or route-option bundles to
packages/feature-*— dashboard pages belong inapps/dashboard/src/features/{domain}/pages/