13 — Naming Conventions
Section titled “13 — Naming Conventions”Generated: 2026-04-22 Status: Active — canonical guide for naming across the monorepo.
This document standardizes naming patterns across apps, workers, packages, folders, files, and exports. The goal is to make code location, ownership, and responsibility predictable.
Quick Reference
Section titled “Quick Reference”| What | Pattern | Example | Notes |
|---|---|---|---|
| App name | kebab-case | dashboard, marketing | no app- prefix |
| Worker name | worker-{name} | worker-ingestion-process | lowercase, always worker- prefix |
| Worker package name | @workers/{name} | @workers/ingestion-process | workspace package identity |
| Feature package | feature-{domain} | feature-people | extracted client domains |
| Domain package | {domain} or domain-{name} | db, utils, domain-auth | domain/business/runtime logic |
| Platform package | platform-{capability} | platform-query | technical/infrastructure |
| Contract package | {purpose} or *-contract | schemas, types | contracts, APIs, boundaries |
| Feature folder (app) | kebab-case, plural | src/features/publications | plural when domain-oriented |
| Subfolder (feature) | kebab-case, singular | api/, queries/, pages/ | owning responsibility |
| File (query keys) | query-keys.ts | query-keys.ts | singular responsibility |
| File (query options) | queries.ts | queries.ts | plural (multiple queries) |
| File (mutations) | mutations.ts | mutations.ts | plural (multiple mutations) |
| File (API wrapper) | api.ts | api.ts | singular, focus |
| File (types) | types.ts | types.ts | singular, focus |
| File (validation) | {entity}-validators.ts | person-validators.ts | entity-specific validators |
| File (policy/perms) | {entity}-policy.ts | workspace-permissions.ts | domain-specific rules |
| File (adapter) | {service}-adapter.ts | cloudflare-adapter.ts | runtime-bound integration |
| File (repository) | repository.ts | repository.ts | data access abstraction |
| Query key object | {domain}Keys (singular) | publicationKeys | singular domain + “Keys” |
| Query factory | {domain}QueryOptions | publicationsQueryOptions | pascal case, plural domain |
| Mutation hook | use{Action}{Entity}Mutation | useUpdatePersonMutation | action + entity, use prefix |
| Custom hook | use{Feature}{Purpose} | useOrcidImport | use prefix, clear intent |
| Page component | {Feature}Page | AdminUsersPage | PascalCase, no “Index” suffix |
| Feature component | {Feature}{Component} | PeopleTable | PascalCase, feature-owned |
| Shared UI component | {Generic}{Role} | Button, Dialog, Tabs | PascalCase, generic UI names |
| Route/page file | {route-name}.tsx | admin-users.tsx | kebab-case, matches URL |
| Layout component | {Domain}Layout | DashboardLayout | PascalCase, clear purpose |
| Hook file (shared) | use{Purpose}.ts | useLocalizedContent.ts | kebab or camelCase, use prefix |
| Utility file | {responsibility}.ts | search-params.ts | specific responsibility |
Category-Level Naming
Section titled “Category-Level Naming”1. Apps
Section titled “1. Apps”Pattern: {name} (kebab-case, no prefix)
Location: apps/{name}/
Current examples:
dashboard✓marketing✓docs✓
Deprecated (ambiguous status):
legaciti.org/— static content, no package.json → document intention or movemy.legaciti.org/— pre-monorepo, no package.json → deferred: document canonical homedocs.legaciti.org/— build artifact only, no package.json → deferred: confirm deployment model
Rule: Apps are user-facing or operator-facing deployables. Name should clearly identify the product or service.
2. Workers
Section titled “2. Workers”Pattern: worker-{name} (kebab-case, always worker- prefix)
Location: workers/{name}/
Workspace package pattern: @workers/{name}
Current examples:
worker-ingestion-process✓worker-consumer-api✓worker-rate-limiter✓worker-log-processor✓
Current package examples:
@workers/ingestion-process✓@workers/consumer-api✓@workers/rate-limiter✓@workers/log-processor✓
Rule: Workers are Cloudflare runtime deployables. Folder/runtime names keep the worker- prefix. Workspace package identities use the @workers/ namespace.
3. Feature Packages (Extracted)
Section titled “3. Feature Packages (Extracted)”Pattern: feature-{domain} (kebab-case, always feature- prefix)
Location: packages/feature-{domain}/
Current examples:
feature-activity✓feature-people✓feature-publications✓
Rule: Feature packages are reusable client-side modules extracted from the dashboard. The feature- prefix clearly marks them as domain code, not infrastructure. Domain name should match route/page/component names where possible.
4. Domain Packages
Section titled “4. Domain Packages”Pattern: {domain} or domain-{name} (kebab-case)
Location: packages/{domain}/ or packages/domain-{name}/
Current examples (inconsistent):
db— no prefix, but core infrastructure ⚠ deferred renameutils— no prefix, too broad ⚠ deferred rename (high-impact)domain-auth— explicit prefix, client-side policy logic ✓domain-people— explicit prefix, client-side policy logic ✓
Rules:
- Domain packages contain business/domain logic not tied to runtime or platform concerns.
- Use
domain-prefix only when the package contains policy, permission, or entity-specific rules that need to be separated from runtime concerns. - Packages like
dbandutilsare legacy naming; future domain packages should use clearer names:db→ stay as-is for now (high-impact rename deferred)utils→ stay as-is for now (high-impact rename deferred); consider subpackages for distinct domains
5. Platform Packages
Section titled “5. Platform Packages”Pattern: platform-{capability} (kebab-case, always platform- prefix)
Location: packages/platform-{name}/
Current examples:
platform-query✓ — TanStack Query infrastructureplatform-telemetry✓ — metrics/timing infrastructureplatform-events✓ — event bus infrastructureplatform-i18n✓ — internationalization infrastructureplatform-cloudflare✓ — Cloudflare runtime adapters
Rule: Platform packages provide technical capabilities and infrastructure abstractions. The platform- prefix marks them as non-domain code. They may be reusable across multiple features or domains.
6. Contract Packages
Section titled “6. Contract Packages”Pattern: {purpose} or *-contract (kebab-case, no mandatory prefix but purpose-clear)
Location: packages/{name}/
Current examples (inconsistent):
schemas✓ — Zod validation schemas (purpose clear)types✓ — TypeScript type definitions (purpose clear)api-contract✓ — API boundary specification (purpose clear with suffix)integration-core⚠ — mixes contract + runtime; see below
Rule: Contract packages define boundaries between subsystems (API, schema, type contracts). Naming should make their role obvious. If boundary + runtime logic are mixed, document clearly or split.
Deferred: integration-core — currently mixes contract definitions with runtime logic. Document or split in future cleanup.
7. Host Tooling
Section titled “7. Host Tooling”Pattern: host-{purpose} (kebab-case, host- prefix)
Location: packages/host-{name}/
Current examples:
host-tsc-shims✓ — TypeScript path resolution shims
Rule: Host packages are build-time or dev-time tooling. The host- prefix marks them as build infrastructure, not runtime code.
Folder Naming Within Apps/Packages
Section titled “Folder Naming Within Apps/Packages”App Structure (Dashboard)
Section titled “App Structure (Dashboard)”src/ app/ Shell composition, layouts, middleware routes/ File-based routing (TanStack Router) features/ Feature modules (domain code) shared/ Truly shared across features ui/ Shared UI primitives (shadcn-style) hooks/ Shared React hooks (cross-feature) transport/ Shared API transport wrappers utils/ Shared utilities (low-level, pure helpers) server/ Server functions, auth, runtime logic lib/ App glue, shell configuration (narrow use)Key rules:
app/— app-wide shell concerns (layout, middleware, composition)routes/— TanStack Router file-based routes, thin orchestration onlyfeatures/— feature-local business logicshared/— code reused across multiple features (check before adding here)server/— server functions, Cloudflare bindings, auth, request contextlib/— narrow use only: locale bootstrap, shell glue, monitoring registry- ⚠ Avoid: generic
lib/helpers/, broadlib/utils/, catch-alllib/common/
Feature Structure (Both App-Local and Packages)
Section titled “Feature Structure (Both App-Local and Packages)”{feature}/ api.ts Transport wrappers queries.ts Query factories + keys (or queries/ subfolder) query-keys.ts Query key definitions (may be inlined in queries.ts) mutations.ts Mutation hooks, invalidation logic types.ts Feature-local TypeScript interfaces model/ Pure business logic, validators, mappers components/ Reusable feature UI components pages/ Page/screen entry components hooks/ Feature-specific React hooks (if needed) index.ts Public exportsRules:
- Each subfolder has a single owning responsibility.
- Files are not separated arbitrarily; proportionality matters (small features may inline or omit subfolders).
- No catch-all folders within features (
helpers/,utils/,common/). - All business logic goes to
model/or to feature-specific files with clear names.
File Naming by Responsibility
Section titled “File Naming by Responsibility”Query and Data Fetching
Section titled “Query and Data Fetching”| Pattern | File Name | Export Name Example | Notes |
|---|---|---|---|
| Query key factory | query-keys.ts | publicationKeys | singular domain + “Keys” |
| Query option factories | queries.ts | publicationsQueryOptions | may split into queries/list.ts, queries/detail.ts |
| List query options | queries/list.ts | publicationsListQueryOptions | when split |
| Detail query options | queries/detail.ts | publicationDetailQueryOptions | when split |
| Transport wrappers | api.ts | getPublications() | call server functions |
Mutations and Writes
Section titled “Mutations and Writes”| Pattern | File Name | Export Name Example | Notes |
|---|---|---|---|
| Mutation hooks | mutations.ts | useUpdatePublicationMutation | action + entity + “Mutation” |
| Mutation invalidation logic | mutations.ts | (part of hook) | included in mutation file |
Types and Validation
Section titled “Types and Validation”| Pattern | File Name | Export Name Example | Notes |
|---|---|---|---|
| Feature types | types.ts | Publication | DTO + form types |
| Entity validators | {entity}-validators.ts | publicationValidators | responsibility-specific |
| Form validators (feature) | form-validators.ts | formValidators | when not entity-specific |
| Search param helpers | search-params.ts | parseSearchParams | responsibility-specific |
| Mappers/transformers | mappers.ts | toPublicationDTO | responsibility-specific |
| Display/view-model helpers | model/{file}.ts | (various) | kept in model/ subfolder |
Domain and Policy Logic
Section titled “Domain and Policy Logic”| Pattern | File Name | Export Name Example | Notes |
|---|---|---|---|
| Workspace permissions | workspace-permissions.ts | canEditPublication() | domain-specific rules |
| Entity-specific permissions | {entity}-policy.ts | publicationVisibilityPolicy | entity-focused, domain-owned |
| Membership/affiliation rules | {entity}-membership.ts | getActiveAffiliation() | entity rules |
| Business-specific validators | {domain}-validators.ts | personSlugValidator | domain-specific validation |
Runtime and Infrastructure
Section titled “Runtime and Infrastructure”| Pattern | File Name | Export Name Example | Notes |
|---|---|---|---|
| Cloudflare adapter | cloudflare-adapter.ts | initCloudflareClient() | runtime-bound integration |
| Database/repository | repository.ts | PublicationRepository | data access abstraction |
| Cache layer | cache.ts | cachePublication() | caching abstraction |
| Event bus | event-bus.ts | emitPublicationUpdated() | event infrastructure |
| Request context | request-context.ts | getRequestUser() | runtime request state |
| Permission/trust checking | permissions.ts | isUserAdmin() | access control logic |
| Trusted origins | trusted-origins.ts | TRUSTED_ORIGINS | security configuration |
Routes and Pages
Section titled “Routes and Pages”| Pattern | File Name | Export Name Example | Notes |
|---|---|---|---|
| Page component | {Feature}Page.tsx | export function AdminUsersPage() | matches route |
| Layout component | {Domain}Layout.tsx | export function DashboardLayout() | app-wide or feature-wide |
| Route integration file | N/A in new patterns | (deprecated) | avoid; keep routes thin |
Hooks and Utilities
Section titled “Hooks and Utilities”| Pattern | File Name | Export Name Example | Notes |
|---|---|---|---|
| Custom React hook | use{Purpose}.ts | export function useOrcidImport() | use prefix mandatory |
| Shared hook | use{Purpose}.ts | export function useLocalizedContent() | shared across features |
| Low-level utility | {responsibility}.ts | formatDate() | pure functions, no side effects |
| String formatting | formatters.ts or specific | formatPersonName() | responsibility-specific |
| Parsing/normalization | parsers.ts or specific | parseDoi() | responsibility-specific |
| Helpers (avoid) | (not recommended) | — | use specific responsibility names instead |
Export and Symbol Naming
Section titled “Export and Symbol Naming”Query Keys and Options
Section titled “Query Keys and Options”Rule: Query key objects use singular domain name + “Keys”. Query option factories use plural domain + “QueryOptions”.
// Query keys — singular domainexport const publicationKeys = { all: ["publications"] as const, list: (search) => ["publications", search] as const, detail: (doi) => ["publication-detail", doi] as const,} as const;
// Query options — plural domainexport const publicationsQueryOptions = (search) => queryOptions({ queryKey: publicationKeys.list(search), queryFn: () => getPublications(search), });
export const publicationDetailQueryOptions = (doi) => queryOptions({ queryKey: publicationKeys.detail(doi), queryFn: () => getPublication(doi), });Mutation Hooks
Section titled “Mutation Hooks”Rule: Mutation hooks use use{Action}{Entity}Mutation (PascalCase action and entity).
export function useUpdatePersonMutation() { /* ... */}export function useDeletePublicationMutation() { /* ... */}export function useUploadPersonPhotoMutation() { /* ... */}Custom Hooks
Section titled “Custom Hooks”Rule: Custom hooks use use{Purpose} (clear intent, use prefix).
export function useOrcidImport() { /* ... */}export function useIngestionActivityToasts() { /* ... */}export function usePersonModalFieldLocks() { /* ... */}Page and Component Names
Section titled “Page and Component Names”Rule: Page components use {Feature}Page (PascalCase, “Page” suffix). Feature components use {Feature}{Component} (PascalCase, feature name + role).
// Pagesexport function AdminUsersPage() { /* ... */}export function LoginPage() { /* ... */}export function WorkspaceSettingsPage() { /* ... */}
// Feature componentsexport function PeopleTable({ people }: PeopleTableProps) { /* ... */}export function PublicationsFilters({ onChange }: PublicationsFiltersProps) { /* ... */}
// Shared UI (generic names, no feature prefix)export function Button({ children, onClick }: ButtonProps) { /* ... */}export function Dialog({ isOpen, onClose }: DialogProps) { /* ... */}Shared UI Components
Section titled “Shared UI Components”Rule: Shared UI primitives use generic UI role names (no feature prefix). These are shadcn-style primitives.
// src/components/ui/ — generic, feature-agnostic namesexport function Button() { /* ... */}export function Dialog() { /* ... */}export function Tabs() { /* ... */}export function Card() { /* ... */}export function Input() { /* ... */}export function Select() { /* ... */}Singular vs Plural Conventions
Section titled “Singular vs Plural Conventions”Feature Domains
Section titled “Feature Domains”Rule: Feature folder names are plural when representing a domain area (collection of entities or concerns).
features/ publications/ — plural (domain area, multiple publications) people/ — plural (domain area, multiple people) projects/ — plural (domain area, multiple projects) workspace-settings/ — compound, domain-oriented admin/ — noun, domain-oriented (admin area, not "admins")Types and Entities
Section titled “Types and Entities”Rule: TypeScript types and interfaces use singular names (represent one entity).
export interface Publication {} // singular entityexport interface Person {} // singular entityexport interface Project {} // singular entityQuery Keys and Options
Section titled “Query Keys and Options”Rule: Query key objects use singular domain names. Query option factories use plural when they fetch collections, singular for single resources.
// Query keys — singular domainexport const publicationKeys = { list: (search) => ["publications", search], // plural inside, singular object name detail: (doi) => ["publication-detail", doi], // singular};
// Query option factoriesexport const publicationsQueryOptions = (search) => /* list */export const publicationDetailQueryOptions = (doi) => /* detail */
// Function names inside api.ts can be plural when returning collectionsexport async function getPublications(search) { }export async function getPerson(orcid) { }Mutation Hooks
Section titled “Mutation Hooks”Rule: Mutation hooks use singular entity names (action on one entity at a time).
export function useUpdatePersonMutation() {} // singular: update one personexport function useDeletePublicationMutation() {} // singular: delete one publicationNaming Patterns to Avoid
Section titled “Naming Patterns to Avoid”Discouraged Names (and Preferred Alternatives)
Section titled “Discouraged Names (and Preferred Alternatives)”| Anti-Pattern | Problem | Preferred Alternative |
|---|---|---|
lib/helpers.ts | Too vague, mixed purpose | Name by responsibility: search-params.ts, formatters.ts |
lib/utils.ts | Too broad, hides responsibility | Name by responsibility: date-utils.ts, string-utils.ts or split |
shared/common.ts | Unclear what is “common” | Name by feature or responsibility |
helpers/ | Folder, vague contents | Name by responsibility: formatters/, validators/ |
common/ | Folder, vague contents | Use shared/ only for cross-feature reuse |
misc/ | Folder, unclear purpose | Avoid; organize by responsibility |
stuff.ts | No responsibility indicated | Avoid; choose clear name |
Generic shared/utils/ | Not all shared code should be “utils” | shared/hooks/, shared/transport/, shared/ui/ |
When to Use “Shared”
Section titled “When to Use “Shared””Rule: Use shared/ only for code that is actually reused across multiple features. Do not create shared/ speculatively.
Good candidates for shared/:
- UI primitives reused across features (
shared/ui/) - Cross-feature hooks (
shared/hooks/) - Transport wrappers shared across features (
shared/transport/)
Bad candidates for shared/:
- Feature-specific utilities (keep in feature)
- Generic catch-alls for utilities (name by responsibility instead)
- Single-use helpers (may not belong in shared yet)
Naming Rules for Common File Types
Section titled “Naming Rules for Common File Types”Search Param Helpers
Section titled “Search Param Helpers”File: search-params.ts
Exports:
export const publicationSearchSchema = z.object(/* ... */);export function parsePublicationSearch(params: URLSearchParams) {}export function stringifyPublicationSearch(search: PublicationSearch) {}export const DEFAULT_PUBLICATION_SEARCH = { /* ... */};Form Validators
Section titled “Form Validators”File: form-validators.ts or {entity}-validators.ts
Exports:
export const personSlugValidator = (slug: string) => { // validation logic};
export const personNameValidator = z.string().min(1).max(255);Mappers/Transformers
Section titled “Mappers/Transformers”File: mappers.ts (feature-level) or model/mappers.ts
Exports:
export function toPublicationDTO(publication: PublicationRecord): Publication {}export function fromFormToPublication( formData: PublicationFormData,): Partial<Publication> {}Adapter/Repository Files
Section titled “Adapter/Repository Files”File: {service}-adapter.ts or repository.ts
Exports:
// Adapter pattern (runtime integration)export class CloudflareAdapter {}export class SESEmailAdapter {}
// Repository pattern (data access)export class PublicationRepository {}export async function getPublication(doi: string) {}Summary of Key Patterns
Section titled “Summary of Key Patterns”| Concept | Pattern | Example |
|---|---|---|
| Domain names | kebab-case, clear | publications, people |
| Feature packages | feature-{domain} | feature-publications |
| Platform packages | platform-{capability} | platform-query |
| Domain packages | name or domain-{name} | db, domain-auth |
| Query key objects | singular domain + Keys | publicationKeys |
| Query options | plural domain + QueryOptions | publicationsQueryOptions |
| Mutation hooks | use{Action}{Entity}Mutation | useUpdatePersonMutation |
| Page components | {Feature}Page | AdminUsersPage |
| Feature components | {Feature}{Role} | PeopleTable |
| Shared UI | generic names | Button, Dialog |
| Feature folders | plural, kebab-case | src/features/publications/ |
| Responsibility files | specific names | query-keys.ts, mappers.ts |
| Avoid | generic names | no helpers.ts, utils.ts |
Migration Strategy
Section titled “Migration Strategy”Phase 1: Documentation ✓
Section titled “Phase 1: Documentation ✓”- Define conventions (this document)
- Communicate to team
Phase 2: High-Value Low-Risk Renames
Section titled “Phase 2: High-Value Low-Risk Renames”- Rename
apps/dashboard/src/shared/utils/utils.ts→apps/dashboard/src/shared/utils/common.ts(or split further) - Update any imports
Phase 3: Feature File Normalization (Deferred)
Section titled “Phase 3: Feature File Normalization (Deferred)”- Standardize feature file names across extracted packages
- Ensure all follow the canonical shape from
08-feature-internals.md - May involve renaming responsibility files like
personFieldResyncChrome.ts
Phase 4: Package-Level Renames (Deferred High-Impact)
Section titled “Phase 4: Package-Level Renames (Deferred High-Impact)”- Rename
packages/utils→ more specific name (e.g.,packages/database-utils,packages/domain-utilssplit) - Rename
packages/db→ clearer name (e.g.,packages/database) - Clarify
integration-coresplit (contract vs runtime)
Phase 5: Deprecated App Cleanup (Deferred)
Section titled “Phase 5: Deprecated App Cleanup (Deferred)”- Clarify or remove
apps/legaciti.org/,apps/my.legaciti.org/,apps/docs.legaciti.org/ - Document canonical home for each
Validation Checklist
Section titled “Validation Checklist”When adding new code, check:
- Package name follows category pattern (feature-, platform-, domain-, etc.)
- Folder name communicates responsibility or domain
- File name is specific, not generic (
search-params.tsnotutils.ts) - Exports follow naming convention (query keys singular, options plural, hooks with
use, pages withPage) - No catch-all folders (helpers/, common/, misc/)
- Shared code is actually shared across multiple features
- Domain logic is clearly separated from UI logic
- Runtime logic lives in
src/server/not in generic folders
Exceptions and Deferred Items
Section titled “Exceptions and Deferred Items”Deferred High-Impact Renames (Document for Future)
Section titled “Deferred High-Impact Renames (Document for Future)”-
packages/utils— Too broad; should split into responsibility-specific packages or rename to clearer name- Current:
@platform/utils— DOI normalization, sharding, JSON Patch - Future: consider
@platform/database-utils,@platform/domain-utils, or namespace under feature - Status: DEFERRED — high-impact rename; deferred to next cleanup pass
- Current:
-
packages/db— Too generic; should rename to@platform/databaseor similar- Current:
@platform/db— Drizzle ORM, D1 schema, client - Future:
@platform/databaseor keep as-is with better docs - Status: DEFERRED — core dependency; high-impact rename; deferred to next cleanup pass
- Current:
-
packages/integration-core— Mixes contract definitions with runtime logic- Current: Contains both API boundary specs and runtime code
- Future: Split into
integration-contract(boundaries only) andintegration-runtime(implementation) - Status: DEFERRED — requires architectural split; not blocking
-
Deprecated apps (
legaciti.org/,my.legaciti.org/,docs.legaciti.org/)- Current: No package.json, unclear status in monorepo
- Future: Document canonical deployment model or archive
- Status: DEFERRED — clarify intent and document in architecture
-
Feature file normalization (e.g.,
personFieldResyncChrome.ts)- Current: Some feature files use unclear naming patterns
- Future: Align with
08-feature-internals.mdstandard shape - Status: DEFERRED — incremental, per-feature cleanup
References
Section titled “References”- 02-package-categories.md — Package category definitions
- 08-feature-internals.md — Feature module standard structure
- 04-dashboard-structure.md — Dashboard folder layout
- AGENTS.md — Dashboard-specific guidelines