A shared household app for two roommates, with channels for chores, shopping list, photo posts, and notes. This repository holds the ***plain specifications the application is generated from — not the application source.
The .plain files are the source of truth. Everything under plain_modules/ and dist/ is
generated by the renderer and is gitignored; never edit generated code, change the spec and
render again.
template/medved_shared.plain Concepts and requirements shared by both parts
resources/medved-api.openapi.yaml The JSON API contract — the only agreement between the parts
backend/medved_backend.plain Python 3.13 / FastAPI server, answering the JSON API
backend/config.yaml Renderer configuration for the backend
frontend/medved_frontend.plain TypeScript 5 / React 18 browser application
frontend/config.yaml Renderer configuration for the frontend
test_scripts/ Unit, conformance, and environment-preparation runners
The two modules are independent root modules, deliberately not linked by requires: they
target different languages, and a requires chain copies the required module's generated code as
its starting point. They meet only at resources/medved-api.openapi.yaml. Change an endpoint
there and both parts must be re-rendered.
.claude/ is not committed — it is installed tooling, not project source. Without it the
specs still render, but the authoring rules and skills that constrain how they are written are
missing. Install it into the project:
npx plain-forge@1.0.19 install --agent claude --scope projectThis recreates .claude/rules/ and .claude/skills/. Version 1.0.19 is the version these
specs were authored against; use npx plain-forge install --agent claude --scope project for the
latest instead, or npx plain-forge update to refresh an existing install.
pip install codeplain
export CODEPLAIN_API_KEY=<your key>The renderer needs CODEPLAIN_API_KEY in the environment. It is a secret — never commit it.
Rendering and testing need Python 3.13+ (backend) and Node 20+ with npm (frontend). Ask Claude to
run the check-plain-env skill for a full report against this project, or verify by hand:
python3 --version # 3.13 or newer
node --version # v20 or newer
npm --versionEach part has its own config.yaml sitting next to its module, and relative paths in a config
resolve against that config's directory — so render each part from its own directory:
cd backend && codeplain medved_backend.plain
cd frontend && codeplain medved_frontend.plainValidate without rendering by adding --dry-run. Both parts must pass before either is rendered:
cd backend && codeplain medved_backend.plain --dry-run
cd frontend && codeplain medved_frontend.plain --dry-runGenerated code lands in plain_modules/ and is copied to dist/. Both are gitignored.
The renderer invokes these scripts itself during a render; run them by hand against a rendered build folder to reproduce a failure.
./test_scripts/run_unittests_python.sh <build_folder> # backend unit tests + ruff + mypy
./test_scripts/run_unittests_typescript.sh <build_folder> # frontend unit tests + eslint + tsc
./test_scripts/prepare_environment_python.sh <build_folder> # once, before conformance
./test_scripts/run_conformance_tests_python.sh <build_folder> <conformance_tests_folder>prepare_environment_python.sh must run before the conformance runner — it installs the
dependencies the conformance suite attaches to, and leaves its working folder in place for reuse.
Only the backend has conformance tests; they exercise the running server through its JSON API.
The generated server reads its configuration from environment variables. MEDVED_ACCESS_KEY has
no default and the app refuses to start without it; the rest have documented defaults.
| Variable | Purpose |
|---|---|
MEDVED_ACCESS_KEY |
The household's shared access key. No default — must be set. |
MEDVED_DATABASE_PATH |
Path to the SQLite database file. |
MEDVED_IMAGE_DIR |
Directory holding the stored photo images. |
MEDVED_PORT |
Port the server listens on. |
The two things worth backing up are the SQLite database file and the image directory. Serving the app over plain HTTP on a public address exposes the access key in transit — put a TLS-terminating reverse proxy in front of it.