Appearance
0001 — Monorepo with three-layer structure ​
Status: Accepted
Date: 2026-06-17
ADO work item: AB#3073
Deciders: Kristopher Turner (platform owner)
Context ​
Heritage Community Hub is one member application delivered as three clients of the same platform API: the web app (apps/web, React + Vite, installable PWA), the iOS app (React Native + Expo, later Epic AB#3077), and the Android app (React Native + Expo, later Epic AB#3139). The web app is part of the Platform Epic (AB#3074) and is the first usable client — it opens directly to the sign-in / register screen at heritageva.app, with no public or marketing site in front of it.
All three clients share a single backend API and a common domain model. Three independent repositories would require maintaining three copies of API contracts, auth logic, and domain types in sync. Any drift between surfaces produces bugs that are hard to trace and expensive to fix.
The project starts with a very small team (one primary developer). Coordination overhead of multiple repos is not justified at this stage. Workspace tooling options evaluated: Turborepo, Nx, plain npm workspaces.
Decision ​
We will keep all application code in one Git repository, organized into three layers via npm workspaces:
apps/(deployable surfaces),packages/(shared code), andinfrastructure/(IaC). npm workspaces is chosen over Turborepo/Nx to minimize tooling complexity at team size 1–3.
text
heritage-community-hub/
├── apps/
│ ├── api/ # backend — containerized Node.js API (Azure Container Apps)
│ ├── web/ # React + Vite — first client; installable PWA; hosted on Azure SWA (heritageva.app)
│ ├── mobile/ # React Native + Expo — iOS (AB#3077) + Android (AB#3139), later Epics
│ └── landing/ # retired interim placeholder — entry point is apps/web sign-in/register
├── packages/
│ ├── shared-types/ # domain types + API contracts (single source of truth)
│ ├── api-client/ # typed SDK wrapping the API
│ ├── shared-utils/ # validation, formatting, RBAC helpers
│ ├── shared-config/ # shared constants
│ └── ui/ # design tokens + shared design system
├── infrastructure/ # Bicep IaC
├── database/ # schema, migrations, seeds
└── docs/ # ADRs, architecture guidesNote:
apps/*andpackages/*are planned, not yet scaffolded. Created in Phase 1 (AB#3074). The three clients (web, iOS, Android) all consume the sameapi-clientSDK — no surface holds business logic.
Alternatives considered ​
| Option | Pros | Cons | Why not chosen |
|---|---|---|---|
| Monorepo — npm workspaces (chosen) | No extra tooling; built into npm/Node; zero learning curve | No build caching; slower CI as repo grows | — chosen |
| Monorepo — Turborepo | Remote build caching; task graph | Adds dependency; overkill at team size <5 | Can be added later if CI becomes slow |
| Monorepo — Nx | Powerful project graph; code generators | Steep learning curve; heavy config | Not justified at current team size |
| Multi-repo (one per surface) | Clean deploy boundaries | Type drift risk; cross-cutting PRs require N repos; overhead impractical at <5 devs | Revisit only if team hits 10+ devs or divergent stacks |
Consequences ​
Positive ​
- A feature implemented once (API endpoint + shared type) is available on web and mobile without duplication.
packages/shared-typesis the single source of truth for the API contract — web, mobile, and API all import from the same definition.- Atomic PRs: an API shape change, web client update, and mobile client update land in one commit.
- Uniform linting, security scanning, and standards enforcement across the whole platform.
Negative / trade-offs ​
- Mobile has a separate release cadence (App Store + Play Store review). Handled via a separate pipeline, not a separate repo (EAS Build in GitHub Actions — see ADR 0004).
- npm workspaces has no built-in build caching. CI must use path-based filtering (changed-paths filter in GitHub Actions) to avoid rebuilding everything on every commit.
Risks ​
- Tight coupling creep — shared code that should go in
packages/ends up duplicated inapps/webandapps/mobile. Mitigation: enforce no direct cross-app imports via ESLintimport/no-restricted-paths. - CI build time — grows linearly without caching. Mitigation: add Turborepo if CI exceeds 10 min. The workspace structure is already Turborepo-compatible.
- Revisit trigger — multi-repo becomes defensible at 10+ developers or if web and mobile need genuinely divergent technology stacks.
Update (2026-06-17) — tooling and conventions decided ​
After a best-practices research pass, the platform owner confirmed the following, which refine the original decision (the monorepo + three-layer structure stands; the tooling specifics are now fixed):
- Package manager: pnpm (with
nodeLinker: hoistedso React Native / Metro resolves workspace packages). Supersedes the original "npm workspaces" choice. Conservative fallback: npm, if Expo EAS Build (which historically assumes Yarn) proves difficult with pnpm — validate the EAS path early. - Build orchestrator: Turborepo (free/MIT; local task caching + affected-only CI). Adopted now rather than "later" — it is free and removes the npm-workspaces "no caching" trade-off noted above.
- Feature code placement: start with feature folders inside each surface (
apps/<surface>/src/features/<feature>/); promote a slice topackages/features/*only when it is genuinely shared across web and mobile (avoid premature abstraction). - UI mockups: built as real React components in
packages/ui+ Storybook (not throwaway HTML); the oldmockups/folder is retired. Tracked as ADO AB#3160. - Documentation: two buckets — internal repo docs (
docs/internal/, not public) and member-facing in-app docs (docs/member/, served in-app after sign-in, including roadmap/changelog). No public docs tier (closed community). See the platform strategy "Documentation model". - Cost constraint: every tooling choice must stay free/cheap (all of the above are free).
These decisions are tracked in ADO under AB#3161 and mirrored in pmo/platform-strategy.md.
Three-client model (2026-06-20): the platform has one member application and three clients — web (apps/web), iOS (apps/mobile, AB#3077), and Android (apps/mobile, AB#3139). The web app is the first client, part of Platform Epic AB#3074, and is an installable PWA hosted at heritageva.app. There is no public-facing site or marketing site at any URL. This supersedes any earlier reference to apps/landing as an interim public surface.
Update (2026-06-18) — internal docs organized by document type (Diátaxis reversed) ​
The docs/internal/ tree was briefly organized by the Diátaxis framework (quadrants: tutorials/, how-to/, reference/, explanation/). This was reversed as unintuitive — design and architecture docs filed under "explanation," build docs under "how-to," etc. did not read like what they were. Internal docs are now organized by what each document is:
adr/— architecture decision recordsdesign/— data model, auth/RBAC, API contracts, security HIGH-item designsarchitecture/— system architecture (+ diagram), provider abstraction, notification transport, technical overviewsbuild/— local development, CI/CD, workspace setupimplementation/— API framework, repository adapters, RBAC implementationoperations/— runbooks (observability, backup/DR, cost governance)overview/— executive summary, ministry overview, action plan, repository strategy
21 docs + the architecture diagram were moved via git mv (history preserved) and all cross-references updated. Commit 1554ff5.