Composable Cooking — compositional meal-planning app (Technique × Flavor × Ingredients × Form × Finishing → Meal). Migrated from GitHub.
  • Python 51%
  • TypeScript 47.3%
  • Shell 0.8%
  • Makefile 0.3%
  • CSS 0.3%
  • Other 0.2%
Find a file
bullitt186 c5143a9039
Some checks failed
Build and deploy / Build and push images (push) Successful in 22s
CI / Quality gate (push) Successful in 4m57s
CI / End-to-end (push) Failing after 1m17s
feat(nutrition): show target direction
2026-08-25 08:33:10 +02:00
.forgejo/workflows ci: use git.stahmer.net (HTTPS) for registry push 2026-08-23 07:12:40 +02:00
apps feat(nutrition): show target direction 2026-08-25 08:33:10 +02:00
data feat: add personal meal nutrition assessment 2026-08-25 07:27:50 +02:00
docs feat(nutrition): show target direction 2026-08-25 08:33:10 +02:00
packages feat(nutrition): show target direction 2026-08-25 08:33:10 +02:00
scripts feat: implement the first composable cooking vertical slice 2026-08-21 19:38:01 +02:00
.coverage-threshold feat: implement the first composable cooking vertical slice 2026-08-21 19:38:01 +02:00
.dockerignore feat: implement the first composable cooking vertical slice 2026-08-21 19:38:01 +02:00
.env.example feat: US-041 — AI-generated recipe images via OpenRouter 2026-08-24 09:36:18 +02:00
.gitignore revert: restore repository state to 6147d05 2026-08-22 20:34:18 +02:00
.pre-commit-config.yaml feat: implement the first composable cooking vertical slice 2026-08-21 19:38:01 +02:00
AGENTS.md Harden week planning and document visual system 2026-08-24 12:43:46 +02:00
CLAUDE.md feat: implement the first composable cooking vertical slice 2026-08-21 19:38:01 +02:00
CODEX_SCAFFOLDING_PROMPT.md feat: implement the first composable cooking vertical slice 2026-08-21 19:38:01 +02:00
compose.yaml feat: add personal meal nutrition assessment 2026-08-25 07:27:50 +02:00
IDEAS.md docs: track IDEAS.md 2026-08-22 23:28:07 +02:00
Makefile feat: add personal meal nutrition assessment 2026-08-25 07:27:50 +02:00
package-lock.json feat: implement the first composable cooking vertical slice 2026-08-21 19:38:01 +02:00
package.json feat: implement the first composable cooking vertical slice 2026-08-21 19:38:01 +02:00
README.md feat: add personal meal nutrition assessment 2026-08-25 07:27:50 +02:00

Composable Cooking

Technique × Flavor Profile × Ingredients × Form Factor × Finishing → Meal

A mobile-first web application that composes meals from reusable culinary patterns instead of searching a recipe database. Tell it what is in your kitchen and how long you have; it returns ranked, explained candidates and resolves one into exact quantities, ordered instructions and macros.

Composition, constraints and nutrition are deterministic. No LLM is involved in the core loop, and none is required to run the application.

Prerequisites

  • Docker and Docker Compose — the only requirement to run the application
  • For development: uv, Python 3.14, Node.js 22

First start

cp .env.example .env
docker compose up --build

The API container migrates the database and loads the curated knowledge base before it starts serving, so a clean checkout comes up working.

Web app http://localhost:3000
API docs (Swagger) http://localhost:8000/docs
OpenAPI document http://localhost:8000/openapi.json
Health http://localhost:8000/health

make up and make down wrap the same thing.

Architecture

apps/web (Next.js)  ──REST/OpenAPI──▶  apps/api (FastAPI)  ──▶  PostgreSQL
                                              │
                                     CompositionEngine
                                    (pure, deterministic)

The API is independent of the web app, so a native iOS client can be added later against the same contract.

Path Contents
apps/api/ FastAPI backend, domain model, composition and nutrition engines, persistence, migrations, seed loader, tests
apps/web/ Next.js mobile-first frontend, component tests, Playwright suite
packages/ontology/ LinkML schema — the authoritative vocabulary
packages/api-client/ TypeScript client generated from the OpenAPI contract
data/ Curated cooking knowledge as reviewable YAML
docs/ Design, user stories, implementation plan, ADRs
scripts/ Validation and generation helpers

Read docs/DESIGN.md, docs/USER_STORIES.md and docs/adr/ before making architectural changes. AGENTS.md is the working agreement for this repository.

How composition works

MealPattern and Meal are deliberately distinct. A pattern is an abstract blueprint with no quantities; a meal is a concrete materialization with exact grams, instructions, servings, nutrition and provenance back to its pattern.

The engine identifies eligible patterns, resolves a primary protein, checks technique and flavor compatibility, satisfies the form factor's required component roles, adds finishing components whose ingredients are available, calculates nutrition, applies hard constraints, and scores the survivors with explicit weights. Every step is deterministic: identical requests against identical data produce identical candidates in identical order.

Ranking weights live in exactly one place — apps/api/src/composable_cooking/composition/weights.py.

Candidates are not stored. A candidate ID is a content hash of the composition, and materialization replays the request to recompute it (ADR-006).

Development

make install        # backend and frontend dependencies
make install-hooks  # the local quality gate

Run the stack outside Docker, with PostgreSQL still in Compose:

docker compose up -d db
make migrate
make seed
make backfill-nutrition  # idempotent update for saved meals from older versions

cd apps/api && .venv/bin/uvicorn composable_cooking.main:app --reload
npm run dev --workspace apps/web

To run the API with no database at all — useful for frontend work — set COMPOSABLE_COOKING_LOAD_CATALOG_FROM_DATABASE=false and it reads the curated YAML directly.

Configuration is environment-driven and documented in .env.example. Never commit a real .env.

Seed data

The curated knowledge base lives in data/ as YAML and is reviewed in Git. It is deliberately small but recombinant: blackening appears as a bowl, a wrap and a salad, the post-cook marinade appears as a salad and a pasta salad, and every pattern accepts any of the four proteins.

IDs are stable and prefixed: ingredient:chicken-breast, technique:blackening, flavor:cajun, form:bowl, pattern:blackened-protein-bowl.

Ingredients marked pantry: true — oil, garlic, spices, lemon — are always assumed available and never count toward ingredient coverage.

make ontology   # validate the LinkML schema, seed files and cross-references
make seed       # load into PostgreSQL (idempotent)

make ontology checks two different things: LinkML validates each file's shape, and a Python validator checks what LinkML cannot — that every reference resolves across files, that each pattern can satisfy its form factor, and that declared roles agree with the ingredients they point at.

API client

FastAPI's OpenAPI document is authoritative for the HTTP contract. The frontend imports generated types and never declares an API shape by hand.

make api-client   # regenerate after any request or response schema change

Commit packages/api-client/openapi.json and packages/api-client/src/schema.ts. CI regenerates and fails if they are stale.

Tests

make test        # backend pytest + frontend vitest
make coverage    # backend suite with the coverage ratchet
make e2e         # Playwright primary journey (needs a migrated database)
make check       # the full gate — run this before handing work off

make check runs pre-commit, lint, type checks, ontology validation, both test suites, coverage and the frontend build. CI runs the same command.

The end-to-end suite needs the database up and migrated:

docker compose up -d db && make migrate && make seed
make e2e-install   # once, to fetch the browser
make e2e

The backend coverage ratchet in scripts/check_api_coverage.sh blocks any drop below the high-water mark in .coverage-threshold and requires at least 80% coverage for every measured backend file. Add tests rather than lowering it.

Deploying on the internet

The app is safe to expose publicly, behind your own reverse proxy — no account system is required to run it (see ADR-011 for the optional accounts layer), and hardening for internet-facing traffic is built in rather than configured on top. HTTPS is the only supported public deployment path; there is no guidance here for exposing this over plain HTTP.

Set these environment variables for the API when deploying publicly (apps/api/src/composable_cooking/config.py is authoritative):

Variable Set to Why
COMPOSABLE_COOKING_CORS_ALLOW_ORIGINS Your real public origin(s), e.g. ["https://cooking.example.com"] Defaults to the localhost dev origin, which no public browser can use.
COMPOSABLE_COOKING_DEVICE_COOKIE_SECURE true Marks the anonymous device-token cookie Secure; only meaningful once the app is actually served over HTTPS.
COMPOSABLE_COOKING_SESSION_SECRET_KEY A real random secret Signs the account session cookie (ADR-011). Defaults to a fixed dev value — anyone deploying accounts publicly must override this.
COMPOSABLE_COOKING_OPENROUTER_API_KEY Your real OpenRouter key Enables AI recipe images (US-041). Unset disables the feature with a clear error, not a crash. Never commit a real key.

Everything else is already active with no configuration:

  • Rate limiting on write endpoints and composition requests (apps/api/src/composable_cooking/api/rate_limit.py) — an unauthenticated device token is a bearer-style credential, so abuse protection applies regardless of accounts.
  • Security response headersX-Content-Type-Options, X-Frame-Options, Referrer-Policy, and Strict-Transport-Security (sent only when your reverse proxy forwards X-Forwarded-Proto: https, since HSTS must never be sent over a connection that isn't actually TLS). If your reverse proxy also sets its own copies of these headers, check for duplicates rather than letting both write conflicting values.

None of this changes behavior for a trusted local-network deployment — a LAN install with the default settings works exactly as it did before.

License

MIT.