carlos / todomvc-bakeoff Public
🤖 The bakeoff contract
Every app in the bakeoff answers the same requests the same way, so one test suite and one measurement harness can drive all of them. This file is binding. If something here seems wrong, say so in your STATUS.md rather than deviating.
Pages
| Request | Response | |---|---| | GET / | The whole list | | GET /active | Only items not completed | | GET /completed | Only completed items | | 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 | | GET /static/base.css, GET /static/index.css | shared/base.css and shared/index.css, byte for byte | | GET /healthz | 200 | | GET /api/version | 200, the body is the build version |
The filter links are href="/", href="/active" and href="/completed", and the current one has the class selected. #/ routes aren't used.
Actions
Every action is an HTML form POST (application/x-www-form-urlencoded). Without any special request headers, each one answers 303 See Other back to the current filter.
| Request | Fields | Effect | |---|---|---| | POST /todos | title, filter | Adds an item. The title is trimmed; if nothing is left, nothing is added | | POST /todos/<id>/toggle | filter | Flips that item between active and completed | | POST /todos/<id>/edit | title, filter | Renames it. The title is trimmed; if nothing is left, the item is deleted | | POST /todos/<id>/destroy | filter | Deletes it | | POST /todos/toggle-all | filter | Marks every item completed. If every item already was, marks them all active | | POST /todos/clear-completed | filter | Deletes every completed item |
filterisall,activeorcompleted. The 303 goes to/,/activeor/completedto match. A missing or unknown value goes to/.- A POST naming an
<id>that isn't in the caller's own list answers 404. Nothing changes, in that list or anyone else's. - A POST with an
Originheader that isn't this app's own origin answers 403. The app's own origin ishttps://$CARLOS_HOSTon Carloku, andhttp://<listen address>when run with--listen. A POST with noOriginheader is allowed. There's no CSRF token. - Your JavaScript may call these same endpoints with a request header of your choosing (for example
Accept: application/jsonor a custom header) and get HTML fragments or JSON back. Without that header, the behaviour above must not change.
Markup
Start from shared/reference.html, which is TodoMVC's own template. With JavaScript on, your page must look like it. The suite compares screenshots, masking only footer.info.
- Every
liinul.todo-listcarriesdata-id="<id>", has the classcompletedwhen completed, and haseditingwhile being edited. section.mainandfooter.footerare hidden when the list is empty (all filters considered).button.clear-completedis hidden when nothing is completed.- The counter reads
<strong>N</strong> item leftor<strong>N</strong> items left. - The footer line "Created by …" reads
Created by <a href="https://bakeoff.bfj.oncarlos.com">the CARLOS bakeoff</a> · <LANGUAGE>, where<LANGUAGE>is your setup's name, e.g.RustorGo + Rastrillo.
Controls that must work without JavaScript
The suite drives a page with JavaScript off through exactly these:
| Action | Control | |---|---| | Add | input.new-todo in a form posting to /todos. Pressing Enter submits it | | Toggle one | A submit control matching [data-action="toggle"] inside that item's li, in a form posting to /todos/<id>/toggle | | 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 | | Delete | button.destroy, a submit button in a form posting to /todos/<id>/destroy | | Toggle all | A submit control matching [data-action="toggle-all"], in a form posting to /todos/toggle-all | | Clear completed | button.clear-completed, a submit button in a form posting to /todos/clear-completed |
These extra controls may be visible when JavaScript is off, styled to fit. When JavaScript is on, the page must match the reference, so hide or replace them.
With JavaScript on
The page behaves like TodoMVC's own apps. Double-click a label to edit. Enter or blur saves, and Escape cancels. An empty title deletes. Clicking input.toggle or input.toggle-all takes effect without a full page load, and the counter, filters, "Clear completed" button and toggle-all state stay correct. input.new-todo is cleared after adding.
Lists
- The whole app is one instance. Each visitor gets their own list through a cookie set on their first visit. The cookie holds a random value with at least 128 bits of randomness and is
HttpOnly; SameSite=Lax; Path=/, plusSecurewhen served over https. - A cookie value the app doesn't recognise, garbled or not, gets a fresh empty list, never an error.
- Titles are shown as text, never as HTML. Any Unicode is kept exactly as typed, and titles up to at least 10,000 characters work.
Content-Length, when sent, is the number of bytes in the body.