Skip to content

Package Categories

Migrated from root technical docs.

Canonical category model for all top-level code areas in this monorepo.

Seven categories are defined: app · worker · feature · domain · platform · contract · shared UI

Related docs:


CategoryLocation patternDeployableReactCF RuntimeD1/KV
appapps/{name}/yesyesyes (SSR + server fns)yes (via server fns)
workerworkers/{name}/yesnoyesyes (directly)
featurepackages/feature-{name}/ or apps/*/src/features/{name}/no (library)yesnono
domainpackages/{name}/ (db, utils)no (library)nonoyes (db only)
platformpackages/platform-{name}/no (library)conditionallyconditionallyno
contractpackages/{name}/ (schemas, types, api-contract, integration-core)no (library)nonono
shared UIapps/dashboard/src/components/no (in-app)yesnono

PathCategoryImport aliasNotes
apps/dashboard/app@apps/dashboardTanStack Start + Cloudflare Worker
apps/marketing/app@apps/marketingAstro, public site only
apps/docs/app@apps/docsStarlight, Cloudflare Pages
apps/legaciti.org/⚠ deprecatedNo package.json; legacy static content — see §Ambiguities
apps/my.legaciti.org/⚠ deprecatedNo package.json; pre-monorepo artifact — see §Ambiguities
apps/docs.legaciti.org/⚠ deprecatedNo package.json; build artifact only
workers/ingestion-orchestrator/worker@workers/ingestion-orchestratorQueue orchestrator
workers/ingestion-process/worker@workers/ingestion-processHeavy ingestion processor
workers/notification/worker@workers/notificationNotification queue consumer
packages/platform-ingestion/platform@platform/ingestionShared ingestion runtime extracted from the legacy worker package
workers/consumer-api/worker@workers/consumer-apiHono HTTP, read-only
workers/rate-limiter/worker@workers/rate-limiterDurable Object
workers/log-processor/worker@workers/log-processorLog tail consumer
packages/feature-activity/feature@legaciti/feature-activityextracted
packages/feature-people/feature@legaciti/feature-peopleextracted
packages/feature-publications/feature@legaciti/feature-publicationsextracted
apps/dashboard/src/features/admin/feature(in-app)not yet extracted
apps/dashboard/src/features/auth/feature(in-app)auth client concerns
apps/dashboard/src/features/bug-reports/feature(in-app)not yet extracted
apps/dashboard/src/features/dashboard/feature(in-app)shell/home view
apps/dashboard/src/features/integrations/feature(in-app)not yet extracted
apps/dashboard/src/features/site-tools/feature(in-app)not yet extracted
apps/dashboard/src/features/workspace-settings/feature(in-app)app-owned settings UI + data/query layer
apps/dashboard/src/features/admin-users/feature(in-app)not yet extracted
apps/dashboard/src/features/email-templates/feature(in-app)not yet extracted
apps/dashboard/src/features/version-sync/feature(in-app)not yet extracted
packages/db/domain@platform/dbDrizzle ORM, D1 schema
packages/utils/domain@platform/utilsDOI normalization, sharding, JSON Patch
packages/platform-cloudflare/platform (server)@legaciti/platform-cloudflareCF runtime adapters
packages/platform-query/platform (client)@legaciti/platform-queryTanStack Query infra
packages/platform-telemetry/platform (client)@legaciti/platform-telemetrymetrics/timing
packages/platform-events/platform (client)@legaciti/platform-eventsevent bus
packages/platform-i18n/platform (client)@legaciti/platform-i18ni18next bootstrap
packages/schemas/contract@platform/schemasZod schemas
packages/types/contract@platform/typesTS interfaces
packages/api-contract/contract@platform/api-contractOpenAPI, integrations API
packages/integration-core/contract@platform/integration-core⚠ contract + runtime mix — see §Ambiguities
apps/dashboard/src/components/ui/shared UI@/*src/components/ui/shadcn primitives
apps/dashboard/src/components/shared UI@/*src/components/app-wide shared components
packages/host-tsc-shims/tooling@host/*TS path shims only
tooling/toolingVitest coverage preset
cli/tooling/CLIlegaciti binadmin CLI binary
src/monitoring/deletedRemoved 2026-04-23; canonical: packages/platform-ingestion/src/infra/telemetry/


What it is: A user-facing or operator-facing deployable application. Owns a domain, an entry point, and a complete runtime stack. Assembles features, shared UI, and platform packages into a coherent product experience.

Location: apps/{name}/ (no worker- prefix)

Current apps: dashboard, marketing, docs

Owns:

  • Route definitions and layouts (file-based routing)
  • App shell: navigation, auth gate, error boundaries, global providers
  • Server functions (createServerFn) — dashboard only
  • Auth configuration and session management — dashboard only
  • Email sending — dashboard only
  • In-app feature modules (src/features/{domain}/) not yet extracted to packages
  • App-specific configuration (vite.config.ts, wrangler.jsonc, astro.config.mjs)

Must not own:

  • Business logic that needs to be shared with a second app or worker
  • Cloudflare binding access from client-side code paths (only in src/server/)

Deployable: Yes — each app deploys as a distinct Cloudflare Worker or Pages project

Allowed dependencies:

  • feature packages (@legaciti/feature-*)
  • platform packages (@legaciti/platform-*)
  • contract packages (@platform/schemas, @platform/types, @platform/api-contract)
  • domain packages (@platform/db, @platform/utils) — server-side only
  • shared UI (in-app via @/*)

What it is: A backend-only Cloudflare Worker with no user-facing HTML surface. Workers process events (queues), serve raw APIs (Hono), or implement Cloudflare primitives (Durable Objects, log tail). Each is a deployable unit with its own wrangler config.

Location: workers/{name}/

Current workers: ingestion-orchestrator, ingestion-process, notification, consumer-api, rate-limiter, log-processor

Workspace package names: @workers/ingestion-orchestrator, @workers/ingestion-process, @workers/notification, @workers/consumer-api, @workers/rate-limiter, @workers/log-processor

Internal structure:

src/
index.ts Entrypoint — Queue, Hono, or DurableObject export
features/ Pure domain logic — no CF imports, no `env`
{domain}/
infra/ CF adapters — DB, HTTP, rate-limiter, telemetry

Owns:

  • Worker entrypoint and routing/queue binding logic
  • Domain-specific pipeline logic (src/features/) — pure TypeScript, testable without CF bindings
  • Infrastructure adapters (src/infra/) — D1 queries, external HTTP clients, CF API wrappers

Must not own:

  • React, Vite, or any browser/UI code
  • Dashboard-specific server functions or auth sessions
  • Logic that logically belongs in a shared feature or domain package

Deployable: Yes — wrangler deploy, excluded from Turbo pipeline

Allowed dependencies:

  • @platform/db, @platform/schemas, @platform/types, @platform/utils
  • @legaciti/platform-cloudflare
  • @platform/api-contract@workers/consumer-api only
  • @platform/integration-core@apps/dashboard only
  • External npm packages (Hono, citation-js, etc.)

What it is: A product domain module encapsulating UI, state management, query hooks, and the server-function transport layer for one domain (e.g., publications, people, projects). Features are consumed by apps; they do not run independently.

Two forms — same category:

FormLocationWhen to use
In-appapps/dashboard/src/features/{domain}/Always the canonical home for dashboard pages and screen composition
Extracted packagepackages/feature-{name}/Reusable data/logic layer (queries, mutations, API wrappers, types, reusable components)

The distinction is critical: In-app features own the screen. Extracted packages own the logic. An extracted package is not the canonical home for dashboard page components or route-option bundles — those always belong in the app.

Canonical internal structure (both forms):

{domain}/
api.ts Transport: dynamic import() + call() wrappers for server fns
queries.ts TanStack Query options and hooks
query-keys.ts Query key factory
mutations.ts Mutation hooks
types.ts Domain-local TS types
components/ Domain-specific UI components (reusable across screens)
pages/ Page-level components — IN-APP ONLY; must not be in packages
hooks/ Domain-specific React hooks
index.ts Public API — explicit exports only

Owns:

  • All UI and state management for one product domain — reusable components only
  • Domain-local types (if not shared across multiple features)
  • The API transport adapter calling server functions
  • Query helpers, mutation hooks, search param schemas

Must not own:

  • Dashboard page components — these belong in apps/dashboard/src/features/{domain}/pages/
  • Route-option bundles (*RouteOptions, *-route.tsx, *-routes.tsx) — these belong in the app
  • Dashboard-screen-specific orchestration — this belongs in the app
  • Cloudflare binding access (D1, KV, SES) — belongs in server functions
  • Static imports of server functions — use api.ts dynamic import() + call() pattern
  • Shared UI primitives — use @host/components/ui/* or @/*
  • Other features’ internal modules — import via their index.ts only

Deployable: No — library

Allowed dependencies:

  • @legaciti/platform-* (query, events, telemetry, i18n)
  • @platform/schemas, @platform/types, @platform/utils
  • @platform/api-contract
  • @host/components/ui/* or @/* — shared UI
  • Other @legaciti/feature-* via index.ts (sparingly)
  • React, TanStack Query, TanStack Router

What it is: A package containing core business or data-access logic that has no UI and no Cloudflare-runtime-specific concerns (callers supply the binding). Domain packages are the lowest stable layer; both apps and workers depend on them.

Location: packages/{name}/ legacy (db, utils) or packages/domain-{name}/ new

Current domain packages: db, utils

Owns:

  • Data model definitions (Drizzle schema, table types, migrations config)
  • Reusable query builders and shard routing helpers
  • Pure business utilities (DOI normalization, JSON Patch application, sanitization)

Must not own:

  • React or any UI framework import
  • Cloudflare binding access (callers pass env.DB_0 in; db does not hold the binding)
  • HTTP fetching or external API calls
  • Feature or application logic

Deployable: No — library

Allowed dependencies:

  • @platform/types, @platform/schemas
  • External npm packages only (Drizzle, fast-json-patch, Zod, etc.)
  • No @legaciti/* packages

What it is: Technical/runtime infrastructure shared across multiple app or worker surfaces. Platform packages abstract over framework patterns, Cloudflare APIs, telemetry, events, and i18n bootstrap. They contain no product or domain logic.

Sub-types:

Sub-typeRuntimeCurrent packages
Client platformBrowser / Reactplatform-query, platform-telemetry, platform-events, platform-i18n
Server/edge platformCloudflare Worker runtimeplatform-cloudflare

Location: packages/platform-{name}/

Owns:

  • Technical abstractions: query key factories, retry config, event bus, metrics, locale bootstrap
  • Cloudflare runtime helpers shared across ≥2 workers (server platform only)
  • No product feature logic or domain rules

Must not own:

  • Feature business logic or domain-specific knowledge
  • App-specific configuration
  • Imports from packages/feature-*

Deployable: No — library

Allowed dependencies (client platform):

  • @platform/types, @platform/schemas
  • React, TanStack Query, i18next, etc.
  • No @legaciti/feature-*, no CF runtime APIs

Allowed dependencies (server platform):

  • @platform/types, @platform/schemas, @platform/utils
  • Cloudflare Workers runtime APIs
  • No React, no feature packages

What it is: Lightweight packages that define typed boundaries between systems. Contract packages contain Zod schemas, TypeScript interfaces, and OpenAPI specifications. They are imported by almost all other categories and must stay thin.

Location: packages/{name}/ (schemas, types, api-contract, integration-core)

Current contract packages: schemas, types, api-contract, integration-core

Owns:

  • TypeScript interfaces and type aliases shared across multiple surfaces
  • Zod schemas for API input/output validation
  • OpenAPI specification for versioned external APIs (api-contract)
  • Runtime validators derived from schemas

Must not own:

  • Business logic or data transformations
  • UI code or React
  • Cloudflare runtime APIs
  • DB schema (that is packages/db)

Deployable: No — library; should have minimal/zero runtime overhead

Allowed dependencies:

  • Other contract packages (typesschemas is permitted)
  • Zod, TypeScript utility libraries
  • No app, feature, domain, or platform imports

What it is: Reusable presentational components, form primitives, layout shells, and charts that are not tied to any one product feature. These live inside apps/dashboard/src/components/ rather than a standalone package because they are coupled to the dashboard’s Tailwind config and shadcn theme and are not needed by any other app today.

Location: apps/dashboard/src/components/

Sub-areas:

  • ui/ — shadcn/ui primitives: button, card, dialog, input, select, table, badge, etc.
  • layout/ — page and shell layout wrappers
  • form/ — reusable form field compositions
  • feedback/ — loading, error, and empty-state patterns
  • Top-level components (e.g., BarSeriesChart.tsx) — domain-agnostic data visualization

Owns:

  • Visual primitives with no domain or feature knowledge
  • Tailwind-variant styled wrappers
  • Generic layout, feedback, and chart patterns

Must not own:

  • Imports from src/features/{domain}/ or packages/feature-*
  • D1, KV, or server-function calls
  • Domain-specific labels or hardcoded business data

Deployable: No — bundled with the dashboard app

Allowed dependencies:

  • Tailwind CSS, Radix UI, shadcn/ui
  • React
  • @platform/types for prop types if needed

┌─────┬────────┬─────────┬────────┬──────────┬──────────┬──────────┐
may import → │ app │ worker │ feature │ domain │ platform │ contract │ sharedUI │
───────────────────┼─────┼────────┼─────────┼────────┼──────────┼──────────┼──────────┤
app │ │ ✗ │ ✓ │ svr ✓ │ ✓ │ ✓ │ ✓ │
worker │ ✗ │ ✗ │ ✗ │ ✓ │ svr ✓ │ ✓ │ ✗ │
feature │ ✗ │ ✗ │ idx ✓ │ ✗ │ cli ✓ │ ✓ │ ✓ │
domain │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │ ✓ │ ✗ │
platform (client) │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │ ✓ │ ✗ │
platform (server) │ ✗ │ ✗ │ ✗ │ ✓ │ ✗ │ ✓ │ ✗ │
contract │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │ peer ✓ │ ✗ │
shared UI │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │ ✓ │ ✗ │
───────────────────┴─────┴────────┴─────────┴────────┴──────────┴──────────┴──────────┘
✓ = allowed ✗ = prohibited svr = server-side paths only
cli = client platform only idx = via public index.ts only peer = same-category peers only

Observed mix: DOI normalization (domain/publication logic), D1 shard routing (infrastructure concern), JSON Patch helpers (generic utility). Three different concerns under one name.

Preferred category: domain (primary), with generic utility elements

Recommendation: No immediate action. If utils grows past ~600 lines, move D1 routing helpers into packages/db and leave pure utilities in utils.


packages/integration-core — contract vs platform

Section titled “packages/integration-core — contract vs platform”

Observed mix: Sits under @platform/integration-core but contains runtime request/response helpers alongside type definitions, making it part contract and part platform (server).

Preferred category: contract for type/schema surface; if the runtime-helper portion grows, extract it into packages/platform-integrations.

Current treatment: Mapped as contract with runtime helpers co-located.


apps/dashboard/src/features/workspace-settings/ — ownership resolved

Section titled “apps/dashboard/src/features/workspace-settings/ — ownership resolved”

Current state: apps/dashboard/src/features/workspace-settings/ is the single canonical owner for workspace-settings UI and data/query code.

Category: feature (in-app canonical)


src/monitoring/ — ✅ deleted (2026-04-23)

Section titled “src/monitoring/ — ✅ deleted (2026-04-23)”

The stale src/monitoring/tracing.ts was a diverged duplicate of packages/platform-ingestion/src/infra/telemetry/tracing.ts. The root file and the dead companion test tests/monitoring/tracing.test.ts were deleted. The canonical implementation lives in the worker package with co-located tests.


apps/dashboard server functions — acting as domain/data-access layer

Section titled “apps/dashboard server functions — acting as domain/data-access layer”

Observed mix: apps/dashboard/src/server/functions/ mixes HTTP entry-point concerns (auth checks, response shaping) with D1 query logic inline.

Preferred split: Server functions own the HTTP contract and orchestration; reusable D1 query helpers belong in packages/db.

Recommendation: Incrementally extract repeated D1 query patterns into packages/db query helpers. No immediate refactor required.


apps/legaciti.org/ and apps/my.legaciti.org/ — undocumented legacy directories

Section titled “apps/legaciti.org/ and apps/my.legaciti.org/ — undocumented legacy directories”

Observed mix: Two directories exist under apps/ with no package.json: apps/legaciti.org/ (has src/content and dist; predates apps/marketing/) and apps/my.legaciti.org/ (has a nested apps/ tree; predates apps/dashboard/). apps/docs.legaciti.org/ contains only build output.

Preferred category: Not applicable — these are deprecated remnants.

Recommendation: Confirm whether any content in apps/legaciti.org/src/ was migrated to apps/marketing/. If so, delete all three directories. If any content was not migrated, move it first. Tracked as a cleanup item.


Namespace inconsistency: @platform/* vs @legaciti/platform-*

Section titled “Namespace inconsistency: @platform/* vs @legaciti/platform-*”

Observed mix: Domain and contract packages use @platform/* (legacy), while platform packages use @legaciti/platform-*. New packages should prefer @legaciti/ for consistency but no renames are needed now.


SignalCategory
Serves HTML or has a React/Astro UI surfaceapp
Uses TanStack Router, SSR, or auth sessionsapp
Is a queue consumer, log tail, or Durable Object onlyworker
Has no user-facing HTML surfaceworker
Exposes a JSON API consumed by browsersapp (server fn) or worker (Hono) depending on auth requirements

In-app feature vs extracted feature package

Section titled “In-app feature vs extracted feature package”

Extract to packages/feature-{name}/ when:

  • A second app or worker surface needs to consume it
  • The feature has >5 files and is under active development
  • It benefits from isolated type-check and lint in CI
  • It has a stable public API imported by other features

Keep in apps/dashboard/src/features/{domain}/ when:

  • Truly dashboard-only and small (≤5 files)
  • Involves app-shell-specific concerns (auth guards, version sync, admin layout)
  • Is a thin wrapper with minimal reusable UI

When business logic should become domain code

Section titled “When business logic should become domain code”

Move to packages/db or packages/utils when the same query or transformation is used by ≥2 server functions or workers. Keep in the server function when it is a one-off orchestration mixing auth, caching, and DB.

When infrastructure logic belongs in platform code

Section titled “When infrastructure logic belongs in platform code”

Make it packages/platform-{name} when it is a technical abstraction (retry logic, event bus, metrics) used by ≥2 features with no product knowledge. Keep it in the app when it is app-specific configuration of a platform primitive.

When typed boundaries belong in contract packages

Section titled “When typed boundaries belong in contract packages”
  • Zod schema for cross-process validation → packages/schemas
  • TypeScript interface shared across workers + apps → packages/types
  • OpenAPI spec or external consumer types → packages/api-contract
  • Internal-to-one-feature types → stay local, not exported from index.ts

When a component belongs in shared UI vs a feature

Section titled “When a component belongs in shared UI vs a feature”

Shared UI (apps/dashboard/src/components/):

  • Generic visual primitive (button, input, table, card)
  • Domain-agnostic layout, chart, or feedback component
  • Used by ≥2 features, or has no feature-specific props

Feature component (src/features/{domain}/components/):

  • Renders domain data (publication card, researcher row)
  • Imports domain-local types, query hooks, or mutations
  • Used only within one feature

CategoryPatternExamples
appapps/{name}/apps/dashboard, apps/marketing
workerworkers/{name}/workers/ingestion-process
feature (extracted)packages/feature-{name}/packages/feature-publications
domain (new)packages/domain-{name}/packages/domain-doi
platform (client)packages/platform-{name}/packages/platform-query
platform (server)packages/platform-{name}/packages/platform-cloudflare
contract (types)packages/types (shared) or packages/{scope}-types
contract (schemas)packages/schemas (shared) or packages/{scope}-schemas
contract (API)packages/{name}-contractpackages/api-contract
shared UI (extracted)packages/ui-{name}/packages/ui-primitives
toolingtooling/ or packages/{name}-shimstooling/, packages/host-tsc-shims

Namespace alignment:

  • @legaciti/platform-* — all platform packages (preferred going forward)
  • @legaciti/feature-* — all feature packages
  • @platform/* — legacy domain and contract packages (existing; do not rename)
  • New packages should use @legaciti/ namespace for consistency