Development
Worktrees, testing, documentation, and production builds.
Parallel worktrees
make worktree-add WORKTREE_PATH="$PWD/.worktree/example" WORKTREE_BRANCH=feat/exampleFrom the new worktree, run make worktree-up. Each worktree gets a unique
.localhost hostname, port allocation, and Compose project. Stack identity is
recorded under .local/, so shutdown cannot target a different checkout.
make worktree-logs: inspect this stack.make worktree-down: stop it without deleting volumes.make worktree-clean: explicitly delete this stack's volumes and identity.make worktree-remove WORKTREE_PATH="$PWD/.worktree/example": remove a clean, stopped worktree from a retained checkout.
Do not run all-worktree stop or cleanup commands while other sessions are working.
For Stripe Connect, use the development sandbox settings in config.mk and a
key in the worktree's private .env. The worktree helper copies regular parent
.env files privately at creation; never copy generated database credentials or
listener secrets yourself. With Stripe configured, startup refreshes the fixture
and starts the stack's own Connect listener. Worktrees share sandbox resources.
See Stripe Connect for configuration and webhook contracts.
Quality checks
Backend imports follow routes/GraphQL -> services -> repository -> db.
Shared contracts live in domain/, generated backend contracts in
domain/generated.ts, and startup-only composition in bootstrap/. Repositories
own SQL, transaction locking, and persisted retry state; services own use-case
and provider orchestration. HTTP responses and the GraphQL schema are unchanged.
Use ~/ imports across backend directories and direct ./ imports within one
directory. Backend try/catch/finally is rejected in favor of typed Effect recovery
and resource scopes. Runtime-exporting implementations need tests; domain,
database infrastructure, generated code, and startup composition are exempt.
Both frontends enforce @/ imports across directories. API/GraphQL modules cannot
import UI or state/hooks, hooks cannot import UI, and lower modules cannot import
app entrypoints. Exported React components need companion tests and stories;
exported hooks need tests. Story fixtures use the local GraphQL schema and reject
mutations, so they need neither a running API nor credentials.
make storybook-build builds both frontends. Run make storybook for the admin
preview or bun run --filter @champagne/consumer storybook --port 6007 for the
consumer preview. Component render tests supplement existing interaction tests.
Tailwind and CSS lint policies are unchanged.
make check runs code generation drift checks, type checking, formatting, linting
and unused-code detection. Tooling contract tests under scripts/*.test.ts are CI-only;
do not run them locally. make test runs backend and frontend
tests plus a disposable PostgreSQL integration suite. The database tests verify
autocommit, rollback, seeding, and GraphQL persistence. CI additionally builds and
smoke-tests the production Docker image against its own PostgreSQL instance.
The visual E2E studio runs only on explicit local request, outside make test
and CI. Launch it with make e2e-studio in a task worktree.
Documentation
make docs
make docs-checkFumadocs runs on Next.js for authoring and exports static HTML, assets, and a
client-side search index. Content lives in docs/content/docs/; source and
navigation configuration live in docs/source.config.ts and the collection's
meta.json. make docs-check typechecks, builds, and validates internal links
and heading anchors without crawling external sites.
The Help links of the back office and member area open the guide on the
build's VITE_DOCS_ORIGIN. make dev and make worktree-up run a docs service
with hot reload at http://docs.<DEV_HOST>:<HTTP_PORT>, the local default unless
DOCS_ORIGIN is set in config.mk or config.local.mk;
deployed images take the GitHub environment's DOCS_ORIGIN, and fall back to
the production documentation without it.
Dockerfile.docs serves the export with Caddy at
docs.champagne.dev-ukab.fr. Terraform owns
its separate Scaleway container, DNS, and custom-domain binding. Main-branch
CI/CD builds and deploys both the app and docs when the documentation container
ID is configured. No Fumadocs account, API key, or Node.js runtime is required
on the documentation container.
Status page
The documentation site serves the status page at /en/status and /fr/status, with an Atom
feed at status/feed.xml. It lives on the documentation container so it stays reachable when
the app or the API is down.
The page checks the API from the reader's browser every minute through the documentation's
/api/graphql proxy. Incidents are declared in docs/lib/incidents.ts; pushing the change to
main publishes it with the next documentation deploy.
export const incidents: readonly Incident[] = [
{
id: "2026-11-09-api-outage",
title: { en: "API errors", fr: "Erreurs de l'API" },
impact: "major",
services: ["api", "memberSites"],
updates: [
{
at: "2026-11-09T14:05:00Z",
phase: "investigating",
message: { en: "Requests to the API fail.", fr: "Les requêtes vers l'API échouent." },
},
],
},
];Append an update each time the situation changes, oldest first, through investigating,
identified, monitoring and resolved: an incident stays open until its last update is
resolved. impact is degraded, partial or major, and services lists the affected
rows among backOffice, memberSites, api and docs. The id is the incident's anchor
and feed entry, so keep it stable.
Production image
docker build -t champagne:local .
# Terraform supplies production runtime connection settings and secrets.Caddy serves the built frontend and React Router fallback on port 8080, and
proxies /api/* to Elysia on internal port 3000. The backend serves GraphQL only.
A supervisor shuts down both processes together. Terraform supplies production
connection settings and generated credentials. Startup runs drizzle-kit push;
no SQL files are needed. See Database.
Frontend data and navigation
React Query executes the generated people operation, owns caching and request
cancellation, and exposes loading/error/refresh state to the HeroUI table. Fresh
results are reused for 30 seconds. React Router redirects / to the first section the signed-in staff member can open
and shows a not-found page for unknown routes. Caddy serves the HTML entrypoint for
browser deep links while missing assets and unknown API routes stay 404s.
The codegen configuration is scripts/codegen.ts. Project-specific testing and
observability guidance is in robot-docs/testing.md and
robot-docs/observability.md.
Markdown and LLM access
Every documentation page offers Copy Markdown and an Open menu. The menu links to the page's GitHub source, its Markdown export, and supported AI tools. The Markdown actions use the static export, including the page's title, description, and canonical source URL. Cards, steps, and callouts are rendered as readable Markdown rather than copied HTML or JSX.
- llms.txt: a generated index linking to the Markdown version of each page, followed by the French user guide.
- llms-full.txt: all published documentation in one plain-text file.
- Introduction Markdown: the homepage's Markdown export.
Nested pages use /markdown/<language>/<page-slugs>.md. The exports are generated from the
same public content collection during the static build and served by Caddy;
they do not require a Node.js server, API key, or an AI provider. make docs-check
verifies the index links and full-text coverage alongside regular page links.
Storybook
The admin and consumer previews include component stories with local fixtures;
they need no backend or database. make storybook uses port 6006 with
--exact-port and fails if it is occupied. Coordinate with the owning session
rather than interrupting it. The consumer command is listed under Quality checks.
make storybook-build produces both web/storybook-static/ and
consumer/storybook-static/ and runs in CI. Storybook is not publicly deployed.