Defer the JSON API until something consumes it

Supersedes decision 17, which expected endpoints to appear one at a time.
Nothing calls /api at all, so even that would be handlers with no callers and
golden tests guarding shapes nothing reads.

The contract stays in the spec as a design — it is what stops the shape
changing under a future client — marked as not built so the spec doesn't
claim behaviour the code lacks. later.md records what to build first when a
consumer appears.
This commit is contained in:
Esa Kataja
2026-07-31 22:38:03 +03:00
parent f1e907bac3
commit 69eea8d707
3 changed files with 29 additions and 3 deletions
+7
View File
@@ -178,3 +178,10 @@ says so.
preference. The limiter is keyed by email rather than IP (a proxy would mean trusting preference. The limiter is keyed by email rather than IP (a proxy would mean trusting
`X-Forwarded-For`) and locks the *attempt rate*, not the account, so nobody can lock someone `X-Forwarded-For`) and locks the *attempt rate*, not the account, so nobody can lock someone
else out by trying. else out by trying.
43. **The JSON API is deferred entirely, not built on demand.** Decision 17 kept the contract fixed
and expected handlers to appear one at a time; in practice nothing consumes `/api` at all, so
even that trickle would be handlers with no callers, plus golden tests guarding shapes nothing
reads. The contract in [spec.md](./spec.md) stays as the design — it is what stops the shape
changing under a future client — and the first endpoint gets built the day something actually
calls it. Both surfaces being thin adapters over one data function is already true of the page
handlers, so adding the JSON side later stays a one-line-per-route job.
+17
View File
@@ -151,6 +151,23 @@ Notes for whoever builds it:
--- ---
## The JSON API
Designed and specified in [spec.md §8](./spec.md) — object shapes, endpoints, error codes,
pagination — and deliberately not implemented, because nothing calls it (decision 43).
When something does:
- Build only the endpoints that consumer needs, as `jsonOf(...)` adapters over the same data
functions the pages already use, so the domain rules cannot diverge between the surfaces.
- Add the golden-file tests at the same time, one per object shape. They are what makes a renamed
field a test failure rather than a silent break in a client you cannot update.
- CORS is a one-line middleware, added the day the consumer is on a different origin. Not before.
- The most likely first consumer is a native client (see above), and the endpoints it needs are
login, the queue, a song with its reviews, and posting a review — four routes, not twenty-one.
---
## Filters on the browse list ## Filters on the browse list
`/songs` is newest-first with no filters. Once there are a couple of hundred songs, "which ones `/songs` is newest-first with no filters. Once there are a couple of hundred songs, "which ones
+5 -3
View File
@@ -509,8 +509,10 @@ file.
## 8. API contract ## 8. API contract
Fixed before implementation, because the shape is the expensive thing to change once a client is **Not built.** Nothing consumes `/api` — the browser talks HTML to the page surface — so this
installed somewhere you cannot update. section is a design, not a description of running code (decision 43). It stays here because the
shape is the expensive thing to change once a client is installed somewhere you cannot update, and
the first endpoint is one line over a data function that already exists.
**Conventions** **Conventions**
@@ -727,4 +729,4 @@ panel, so the admin surface comes first — before a single member can exist.
this step.** this step.**
5. **YouTube path** — yt-dlp metadata and download, slotted into a pipeline that already works. 5. **YouTube path** — yt-dlp metadata and download, slotted into a pipeline that already works.
6. **Stats, profiles, avatars, palaute.** 6. **Stats, profiles, avatars, palaute.**
7. **API endpoints and golden tests**, once something wants them. 7. **API endpoints and golden tests** — deferred until something wants them (decision 43).