carlos / todomvc-bakeoff Public

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

Plain git — no account needed to clone.

Download

Download this file

🤖 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 |

  • filter is all, active or completed. The 303 goes to /, /active or /completed to 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 Origin header that isn't this app's own origin answers 403. The app's own origin is https://$CARLOS_HOST on Carloku, and http://<listen address> when run with --listen. A POST with no Origin header 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/json or 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 li in ul.todo-list carries data-id="<id>", has the class completed when completed, and has editing while being edited.
  • section.main and footer.footer are hidden when the list is empty (all filters considered). button.clear-completed is hidden when nothing is completed.
  • The counter reads <strong>N</strong> item left or <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. Rust or Go + 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=/, plus Secure when 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.