124 lines
7.4 KiB
Markdown
124 lines
7.4 KiB
Markdown
|
|
# Simple Todo List — product specification
|
|||
|
|
|
|||
|
|
This is the contract the project is built against. It is written for the agent that scaffolds the
|
|||
|
|
repository, but it doubles as the human-readable brief: every requirement below is meant to be
|
|||
|
|
implementable and verifiable.
|
|||
|
|
|
|||
|
|
This is the **minimal demo** template. The goal is the smallest website that is genuinely useful,
|
|||
|
|
so the spec is deliberately short and the non-goals matter as much as the goals.
|
|||
|
|
|
|||
|
|
Values in angle brackets (`<Project name>`, `<accent>`) come from the project-creation dialog and
|
|||
|
|
are recorded in the project record (`.w4c/project.json` → `fields`); replace them as you read.
|
|||
|
|
|
|||
|
|
## 1. Goal
|
|||
|
|
|
|||
|
|
A single static page that manages a todo list — add a todo, mark it done or undone, delete it,
|
|||
|
|
clear the completed ones, filter by all / active / done — and remembers the list in the browser
|
|||
|
|
between visits. It must be **usable the moment it is served**: no install step, no build step, no
|
|||
|
|
backend and no account. `<Project name>` is the page title; `<accent>` is the single accent colour.
|
|||
|
|
|
|||
|
|
### Non-goals (do not build)
|
|||
|
|
|
|||
|
|
- No backend, API, database or authentication.
|
|||
|
|
- No framework, package manager, bundler or third-party runtime — plain HTML, CSS and JavaScript.
|
|||
|
|
- No accounts, sharing, sync between devices or server persistence.
|
|||
|
|
- No service worker / offline caching, no analytics, no ads.
|
|||
|
|
- No admin panel, control panel, workflows or database layer.
|
|||
|
|
|
|||
|
|
## 2. The screen
|
|||
|
|
|
|||
|
|
One route, `/` (the static `index.html`). It contains:
|
|||
|
|
|
|||
|
|
- a **header** with `<Project name>`;
|
|||
|
|
- a **composer** — a labelled text input plus an Add control (Enter also submits);
|
|||
|
|
- a **list** of todos, each row: a checkbox/toggle ("done"), the title, a Delete control
|
|||
|
|
(visible or on row hover/focus — always reachable by keyboard);
|
|||
|
|
- a **footer** with the count of remaining active todos, the filters (All / Active / Done) and a
|
|||
|
|
**Clear completed** action shown only when at least one todo is done;
|
|||
|
|
- an explicit **empty state** when there are no todos (and a distinct message when the filter has
|
|||
|
|
no matches), and a **hidden/disabled** Delete/Checkbox state that never leaves a row inaccessible.
|
|||
|
|
|
|||
|
|
No routing beyond the single page; `/404` is not required.
|
|||
|
|
|
|||
|
|
## 3. Data model
|
|||
|
|
|
|||
|
|
One entity, persisted as a JSON array in `localStorage`:
|
|||
|
|
|
|||
|
|
- **Todo** — `id` (unique, e.g. a stringified timestamp or `crypto.randomUUID()`), `title`
|
|||
|
|
(non-empty, trimmed), `done` (boolean), `createdAt` (ISO timestamp).
|
|||
|
|
|
|||
|
|
Storage contract: a single documented key holds the array (e.g. `simple-todo.items`). A missing,
|
|||
|
|
empty or unparseable value is treated as an empty list — never throw. Unknown fields are ignored;
|
|||
|
|
a malformed stored item is dropped, not rendered as a broken row. The filter selection is UI state
|
|||
|
|
and does **not** need to persist (persisting just the filter is optional).
|
|||
|
|
|
|||
|
|
## 4. Key flows
|
|||
|
|
|
|||
|
|
1. **Add.** The user types a non-empty title and presses Add (or Enter): the todo appears at the
|
|||
|
|
top of the list, the input clears and refocuses. Whitespace-only input is rejected (the Add
|
|||
|
|
control is disabled and the input stays unchanged).
|
|||
|
|
2. **Toggle.** Clicking the checkbox / row toggles `done`; the title gets a struck-through done
|
|||
|
|
style and the remaining count updates.
|
|||
|
|
3. **Delete.** The Delete control removes the row; the list and the empty state update immediately.
|
|||
|
|
4. **Clear completed.** Shown only when at least one todo is done; removes every done todo after
|
|||
|
|
the built-in confirmation (a native `confirm()` is acceptable) and updates the list.
|
|||
|
|
5. **Filter.** All / Active / Done switches the visible rows; the active filter is visibly marked
|
|||
|
|
and exposed to assistive tech (`aria-pressed` or equivalent). The footer count reflects the
|
|||
|
|
active todos regardless of the filter.
|
|||
|
|
6. **Persist.** Every mutation writes the list to `localStorage`; reloading the page restores the
|
|||
|
|
list exactly (order, done state, titles) and the correct remaining count.
|
|||
|
|
|
|||
|
|
## 5. Functional requirements
|
|||
|
|
|
|||
|
|
- **Input validation:** titles are trimmed; empty/whitespace titles are never added; long titles
|
|||
|
|
wrap instead of overflowing; a sensible maximum is enforced (e.g. 200 characters, hinted to the
|
|||
|
|
user) rather than silently truncating stored data.
|
|||
|
|
- **Rendering:** the list is rendered from the stored array (a single `render()` function is
|
|||
|
|
enough); the DOM never accumulates stale rows across re-renders.
|
|||
|
|
- **Counts:** the "X active" count updates on every add / toggle / delete / clear / filter change.
|
|||
|
|
- **Keyboard:** everything the mouse can do is reachable and operable with the keyboard; focus is
|
|||
|
|
visible and focus order follows the visual order. The composer supports Enter to add.
|
|||
|
|
- **Safe text:** todo titles are inserted as **text**, never as HTML (no `innerHTML` with user
|
|||
|
|
input), so a title like `<img src=x onerror=alert(1)>` renders as literal text.
|
|||
|
|
- **No network:** the page makes no requests; it works when opened directly from the filesystem.
|
|||
|
|
|
|||
|
|
## 6. Non-functional requirements
|
|||
|
|
|
|||
|
|
- **Dependency-free:** plain HTML + CSS + JavaScript. ES modules or one `<script>` are both fine;
|
|||
|
|
a `package.json` / bundler is not. The page must run from `file://` and from a static server.
|
|||
|
|
- **Performance:** a single small page; first render well under 1 s on a normal machine; no
|
|||
|
|
render-blocking third-party assets.
|
|||
|
|
- **Responsive:** usable from 360px wide up to desktop; no horizontal scroll at 390×844.
|
|||
|
|
- **Accessibility (AA):** semantic landmarks (`header`/`main`/`footer`), a real `<label>` for the
|
|||
|
|
composer input, the checkbox as an accessible control (or a button with `aria-pressed`),
|
|||
|
|
≥ AA contrast for the `<accent>` colour and the done style, visible focus.
|
|||
|
|
- **Resilience:** corrupt/empty storage degrades to an empty list; no unhandled errors in the
|
|||
|
|
console during normal use.
|
|||
|
|
- **Security:** no secrets; user input is rendered as text only.
|
|||
|
|
|
|||
|
|
## 7. Acceptance criteria (definition of done)
|
|||
|
|
|
|||
|
|
- [ ] The project is a static site: no `package.json`, no bundler, no third-party runtime, no network calls.
|
|||
|
|
- [ ] Served at `/`, the page loads with no console errors and shows the empty state on a fresh browser.
|
|||
|
|
- [ ] Adding, toggling, deleting and clearing todos all work; the remaining count is always correct.
|
|||
|
|
- [ ] The All / Active / Done filter works, marks the active filter and shows a distinct no-match state.
|
|||
|
|
- [ ] Reloading restores the list exactly from `localStorage`; a corrupted stored value degrades to an empty list instead of throwing.
|
|||
|
|
- [ ] A title containing HTML is rendered as literal text (no injection).
|
|||
|
|
- [ ] Whitespace-only titles cannot be added; long titles wrap and do not overflow.
|
|||
|
|
- [ ] Everything is operable by keyboard alone, with visible focus and AA contrast.
|
|||
|
|
- [ ] Usable at 390×844 with no horizontal scroll.
|
|||
|
|
- [ ] The README quickstart (how to serve it, how to use it) works on a clean clone.
|
|||
|
|
- [ ] The dev server (template `devServer` section) serves the page at its port.
|
|||
|
|
|
|||
|
|
## 8. Suggested build order
|
|||
|
|
|
|||
|
|
This is a small project — keep it top-down and verify each step before the next:
|
|||
|
|
|
|||
|
|
1. **Skeleton** — `index.html` with the header, composer, list and footer, styled with the accent
|
|||
|
|
colour; empty state visible. Serve it and confirm it loads.
|
|||
|
|
2. **Model + storage** — the `Todo` shape, load/save to `localStorage` (with the corrupt-value
|
|||
|
|
fallback), and rendering the list from the stored array.
|
|||
|
|
3. **Interactions** — add, toggle, delete, clear-completed, filters, counts, keyboard support.
|
|||
|
|
4. **Polish + quality** — responsive layout, accessibility pass, injection-safe rendering, README
|
|||
|
|
quickstart, and walk every acceptance box.
|