Four live repos, plus one retired. The split is by audience, not
technology: curate-tenant is everything a tenant touches, and everything
else is ours.
Published content was originally going to sit with the platform app, on that same reasoning. It ended up in its own repo for two reasons: content contributors — writers, vendors authoring guides — would otherwise hold write access to the super-admin app’s code, and the content stack is a separate toolchain on its own cadence.
| Repo | Holds | Deploys to |
|---|---|---|
curate-server |
Go API + background worker (one image), all Terraform, the engineering docs | AWS ECS, ap-east-2 |
curate-tenant |
Consumer SPA, tenant-admin SPA, and the tenant edge Worker | Cloudflare Workers |
curate-platform |
Platform super-admin SPA and its edge Worker | Cloudflare Workers |
curate-content |
Marketing site, public guides, and this handbook | Cloudflare Workers |
curate-documentation |
Retired — UAT-era artifacts and the pre-migration doc site | — |
curate-server — the command centre
The backend, but also the coordination point for everything else: it holds the only Terraform root and every document that describes more than one repo. When you need to know how the system behaves, this is where the answer lives.
Two binaries share one image and the same internal packages — the API server
and a background worker. Postgres, S3, KMS, and Resend hang off them. Its
docs/ folder is the source of record described below.
curate-tenant — everything a tenant hostname serves
One origin per tenant, one Worker serving all of it:
| Path | App | Users |
|---|---|---|
/ |
apps/consumer |
families and visitors |
/admin |
apps/admin |
operator staff |
/api/* |
proxied to the backend | — |
- apps/
- consumer/ family-facing SPA — react-router, Tailwind 4
- admin/ tenant-admin SPA, mounted under /admin
- emails/ React Email templates — isolated, hands off to the backend
- packages/
- api/ typed API client — openapi-fetch + generated types
- ui/ shared UI components
- worker/ serves both SPAs and proxies /api
- .gitlab-ci.yml quality gates only — deploys run on Workers Builds
Two invariants live here and are easy to break by accident:
- Same-origin
/apieverywhere. Never introduce an absolute API host. - Admin is mounted at
/admin, which means three things must agree: the Vitebase, the routerbasename, and the Worker’s/adminmount.
curate-platform — the surfaces we operate
The super-admin SPA (tenant onboarding, admin assignment) and nothing else.
Its packages/api and packages/ui are deliberate copies of
curate-tenant’s — duplicated rather than published to a registry, which is
overhead the team size does not justify while the platform app changes rarely.
Revisit if the copies ever drift painfully.
curate-content — published content, no application
Two standalone Astro projects, each its own deploy:
- sites/
- external/ → curate.memorial
- src/pages/ marketing pages
- src/content/docs/ public role guides — zh-TW default, en prefixed
- internal/ → internal.curate.memorial — Access-gated (this site)
- src/content/docs/ this handbook
- external/ → curate.memorial
- .docs-synced the curate-server commit this site last absorbed
The directories are named for audience and access — open versus gated —
because that is the axis that actually separates them. Naming them
marketing and docs would have described half of external each and gone
stale immediately.
No auth, no API calls, nothing to break against a backend.
Where documentation lives
Authored in curate-server/docs/; this site follows. Engineering docs
live in the repo where the work happens, so a doc can ride the same merge
request as the change that invalidates it. That folder is a working set —
files are deleted, merged, and summarized freely as understanding improves.
Pages here are authored views, organized for reading rather than mirrored
from files, which is why no page tells you which source file it came from.
The single coupling is a watermark, .docs-synced at the curate-content
root, holding the curate-server commit this site last absorbed. From
curate-server, make docs-drift prints how far behind the site is;
an update pass ends with make docs-mark.
If a page here and current curate-server/docs disagree, the repo wins.
See How this handbook works on the handbook home.
Every other repo keeps exactly one README.md as its front door. Anything
bigger — or anything describing more than that repo — is authored in
curate-server/docs.