| name | ppt-typespec |
| description | Author TypeSpec at docs/api/typespec/ to drive the OpenAPI spec and the generated Rust + TS clients. |
| when_to_use | The plan adds, removes, or changes an API endpoint — anything that needs to ripple through to the Rust server and frontend client. |
| mode | both |
| capabilities | ["C6"] |
| tags | ["backend","frontend","infra"] |
PPT TypeSpec
docs/api/typespec/ is the contract source-of-truth.
Pipeline (two steps):
cd docs/api/typespec && npx tsp compile . — compiles the TypeSpec
into the OpenAPI spec at docs/api/generated/openapi.yaml (per
tspconfig.yaml's emitter-output-dir: {project-root}/../generated).
This step is what regenerates the spec; the justfile recipe doesn't run it.
just generate-api — wraps pnpm generate-api + pnpm generate-reality-api
inside frontend/, which consumes docs/api/generated/openapi.yaml to
regenerate the two TS API clients (@ppt/api-client, @ppt/reality-api-client).
Run both. Step 1 alone leaves the TS clients stale; step 2 alone leaves
the OpenAPI YAML stale. Backend handlers consume the spec via utoipa
annotations.
When to invoke
The plan changes an endpoint shape, adds a route, or modifies a response /
error contract. Any contract change starts here, then ripples to
backend handlers and frontend client.
What it gives you
- TypeSpec source layout
- Regeneration pipeline (
just generate-api)
- Breaking-change protocol — when to bump the API version
Inputs
- A target domain file under
docs/api/typespec/domains/
Layout
docs/api/typespec/
├── main.tsp # entry — composes domains
├── tspconfig.yaml # emitter config
├── shared/ # shared models / scalars
├── domains/
│ ├── announcements.tsp
│ ├── auth.tsp
│ ├── buildings.tsp
│ ├── compliance.tsp
│ ├── documents.tsp
│ ├── faults.tsp
│ ├── listings.tsp
│ ├── organizations.tsp
│ ├── rentals.tsp
│ ├── units.tsp
│ └── voting.tsp
# Note: TypeSpec emits to `docs/api/generated/openapi.yaml` (NOT `tsp-output/`)
# per tspconfig.yaml's `emitter-output-dir: {project-root}/../generated`.
├── package.json # @typespec/* deps
└── package-lock.json
Steps
- Edit the domain file under
docs/api/typespec/domains/<area>.tsp.
- Recompile the OpenAPI spec —
just generate-api alone does NOT do this:
cd docs/api/typespec && npx tsp compile .
This refreshes docs/api/generated/openapi.yaml.
- Regenerate the TS clients:
just generate-api
Which wraps pnpm generate-api + pnpm generate-reality-api inside
frontend/, consuming the freshly-recompiled
docs/api/generated/openapi.yaml. Commit both the regenerated TS clients
(frontend/packages/api-client/, frontend/packages/reality-api-client/)
and the regenerated docs/api/generated/openapi.yaml.
- Update backend handlers to match the new contract. utoipa
annotations on Rust handlers must stay consistent — see
ppt-rust-backend.
- Update frontend callers of the regenerated client.
- Validate via the CI workflow locally before pushing:
cd docs/api/typespec && npx tsp compile .
Breaking-change protocol
Anything that's a non-additive change to an existing operation
(removing/renaming fields, changing types, status-code changes) is
breaking and goes through these gates:
- Bump the API version (and per-product
VERSION).
- Document the change in the PR body under
## Breaking changes.
- Confirm all client consumers in this repo are migrated in the same PR
(the generated TS clients update automatically; backend handlers do
not).
- CI workflow
api-validation.yml enforces spec validity — failures
here block merge.
Additive changes (new optional field, new operation) are safe and don't
need a version bump.
Deterministic verification
test -d docs/api/typespec/node_modules && echo OK
(cd docs/api/typespec && npx --no-install tsp --version) >/dev/null 2>&1 && echo OK
test -f docs/api/typespec/main.tsp && echo OK
for d in announcements auth buildings compliance documents faults listings organizations rentals units voting; do
test -f "docs/api/typespec/domains/$d.tsp" || { echo "missing: $d"; exit 1; }
done
echo OK
Smoke check (single command)
cd docs/api/typespec && npx --no-install tsp compile . --no-emit 2>/dev/null || cd docs/api/typespec && npx --no-install tsp --version >/dev/null
The compile + no-emit form is preferred but not all tsp versions support
--no-emit; the fallback just verifies the compiler is reachable. Either
exit 0 means the toolchain is wired.
After-task verification
just generate-api && just check-frontend
Cross-references
ppt-rust-backend — utoipa annotations,
handler updates
ppt-frontend — regenerated
client locations
.github/workflows/api-validation.yml — CI gate