carlos / todomvc-bakeoff Public

Clone
git clone https://amadan.net/carlos/todomvc-bakeoff

Plain git — no account needed to clone.

Download

Download this file

1# 🤖 The bakeoff contract
2
3Every app in the bakeoff answers the same requests the same way, so one
4test suite and one measurement harness can drive all of them. This file
5is binding. If something here seems wrong, say so in your `STATUS.md`
6rather than deviating.
7
8## Pages
9
10| Request | Response |
11|---|---|
12| `GET /` | The whole list |
13| `GET /active` | Only items not completed |
14| `GET /completed` | Only completed items |
15| `GET /<filter>?edit=<id>` | The same page, with that item's `li` given the class `editing` and its `input.edit` inside a form that posts to `/todos/<id>/edit` |
16| `GET /static/base.css`, `GET /static/index.css` | `shared/base.css` and `shared/index.css`, byte for byte |
17| `GET /healthz` | 200 |
18| `GET /api/version` | 200, the body is the build version |
19
20The filter links are `href="/"`, `href="/active"` and `href="/completed"`,
21and the current one has the class `selected`. `#/` routes aren't used.
22
23## Actions
24
25Every action is an HTML form POST (`application/x-www-form-urlencoded`).
26Without any special request headers, each one answers **303 See Other**
27back to the current filter.
28
29| Request | Fields | Effect |
30|---|---|---|
31| `POST /todos` | `title`, `filter` | Adds an item. The title is trimmed; if nothing is left, nothing is added |
32| `POST /todos/<id>/toggle` | `filter` | Flips that item between active and completed |
33| `POST /todos/<id>/edit` | `title`, `filter` | Renames it. The title is trimmed; if nothing is left, the item is deleted |
34| `POST /todos/<id>/destroy` | `filter` | Deletes it |
35| `POST /todos/toggle-all` | `filter` | Marks every item completed. If every item already was, marks them all active |
36| `POST /todos/clear-completed` | `filter` | Deletes every completed item |
37
38- `filter` is `all`, `active` or `completed`. The 303 goes to `/`,
39 `/active` or `/completed` to match. A missing or unknown value goes
40 to `/`.
41- A POST naming an `<id>` that isn't in the caller's own list answers
42 **404**. Nothing changes, in that list or anyone else's.
43- A POST with an `Origin` header that isn't this app's own origin
44 answers **403**. The app's own origin is `https://$CARLOS_HOST` on
45 Carloku, and `http://<listen address>` when run with `--listen`. A
46 POST with no `Origin` header is allowed. There's no CSRF token.
47- Your JavaScript may call these same endpoints with a request header of
48 your choosing (for example `Accept: application/json` or a custom
49 header) and get HTML fragments or JSON back. Without that header, the
50 behaviour above must not change.
51
52## Markup
53
54Start from `shared/reference.html`, which is TodoMVC's own template.
55With JavaScript on, your page must look like it. The suite compares
56screenshots, masking only `footer.info`.
57
58- Every `li` in `ul.todo-list` carries `data-id="<id>"`, has the class
59 `completed` when completed, and has `editing` while being edited.
60- `section.main` and `footer.footer` are hidden when the list is empty
61 (all filters considered). `button.clear-completed` is hidden when
62 nothing is completed.
63- The counter reads `<strong>N</strong> item left` or
64 `<strong>N</strong> items left`.
65- The footer line "Created by …" reads
66 `Created by <a href="https://bakeoff.bfj.oncarlos.com">the CARLOS bakeoff</a> · <LANGUAGE>`,
67 where `<LANGUAGE>` is your setup's name, e.g. `Rust` or
68 `Go + Rastrillo`.
69
70### Controls that must work without JavaScript
71
72The suite drives a page with JavaScript off through exactly these:
73
74| Action | Control |
75|---|---|
76| Add | `input.new-todo` in a form posting to `/todos`. Pressing Enter submits it |
77| Toggle one | A submit control matching `[data-action="toggle"]` inside that item's `li`, in a form posting to `/todos/<id>/toggle` |
78| Edit | A link `a[data-action="edit"]` inside that item's `li`, going to `?edit=<id>` on the current filter. The edit view's `input.edit` submits on Enter |
79| Delete | `button.destroy`, a submit button in a form posting to `/todos/<id>/destroy` |
80| Toggle all | A submit control matching `[data-action="toggle-all"]`, in a form posting to `/todos/toggle-all` |
81| Clear completed | `button.clear-completed`, a submit button in a form posting to `/todos/clear-completed` |
82
83These extra controls may be visible when JavaScript is off, styled to
84fit. When JavaScript is on, the page must match the reference, so hide
85or replace them.
86
87### With JavaScript on
88
89The page behaves like TodoMVC's own apps. Double-click a label to edit.
90Enter or blur saves, and Escape cancels. An empty title deletes. Clicking
91`input.toggle` or `input.toggle-all` takes effect without a full page
92load, and the counter, filters, "Clear completed" button and toggle-all
93state stay correct. `input.new-todo` is cleared after adding.
94
95## Lists
96
97- The whole app is one instance. Each visitor gets their own list through
98 a cookie set on their first visit. The cookie holds a random value with
99 at least 128 bits of randomness and is `HttpOnly; SameSite=Lax;
100 Path=/`, plus `Secure` when served over https.
101- A cookie value the app doesn't recognise, garbled or not, gets a fresh
102 empty list, never an error.
103- Titles are shown as text, never as HTML. Any Unicode is kept exactly as
104 typed, and titles up to at least 10,000 characters work.
105- `Content-Length`, when sent, is the number of bytes in the body.
106