#24 — List Templates #24

Closed
opened 2026-08-18 13:12:54 +02:00 by lena · 0 comments
lena commented 2026-08-18 13:12:54 +02:00 (Migrated from git.butzei.de)

Story: List Templates

As a user creating a new list,
I want to choose from a set of predefined templates,
so that common household lists are ready to use in seconds.

Acceptance criteria:

  • The "New list" flow presents an optional template picker before creating the list
  • At least the following templates are available:
    • Groceries — pre-populated with 5–8 example items (Milk, Eggs, Bread, …)
    • Weekly Chores — recurring household tasks (Vacuum, Take out trash, Clean bathroom, …)
    • Movie Night — a watchlist (example titles from different genres)
    • Blank — no pre-populated todos (always available as the default)
  • Selecting a template creates the list and adds the template todos immediately
  • The user can skip the template picker entirely; the default is "Blank"
  • Template todos are regular todos — they can be edited, deleted, or completed like any other
  • Templates are maintained in a static configuration file, not in the database

Out of scope for this story:

  • User-created or shared templates
  • Templates that import from external sources

Blockers: None

Priority: Could — good onboarding aid; lowers the barrier to creating a first useful list.


Design note — Software Architect

Backend: Common/Types/TodoListTemplate.cs — a plain closed enum (Blank, Groceries, WeeklyChores, MovieNight), same precedent as TodoListColor/TodoListIcon (#23): no validation
logic needed, the type system + JSON deserialization already reject out-of-range values. Per the
AC ("maintained in a static configuration file, not in the database"), the enum carries no seed
data itself — a new static class CqsTodo/Features/TodoLists/TodoListTemplateCatalog.cs maps each
TodoListTemplate to its IReadOnlyList<TodoTitle> seed titles. Nothing is persisted on
TodoListEntity/TodoListDto — the template only affects the moment of creation; the resulting
todos are indistinguishable from any other todo afterward (per AC).

CreateTodoListCommand gains a second parameter: TodoListTemplate Template = TodoListTemplate.Blank
(default preserves every existing call site/test unchanged). After the list + owner membership are
persisted (existing code), CreateTodoListCommandHandler loops the catalog's titles for the chosen
template and calls a newly constructor-injected IHandler<CreateTodoCommand, TodoDto> once per
title — reusing the existing single-todo-creation pipeline (trigger-assigned Nr/SortOrder,
per-todo WS broadcast, activity-feed event) rather than hand-rolling a batch insert. Confirmed via
code research that constructor-injecting a handler resolves the fully decorated chain (DI
decorates at the IHandler<TRequest,TResult> service-registration level, not per call site), so
CreateTodoCommand's AuthorizeTodoListAccessForCurrentUserQuery still runs — it passes because
the owner membership row was already committed earlier in the same handler. This also sidesteps a
real risk: batch-inserting multiple TodoEntity rows in one SaveChangesAsync interacts with the
trg_set_todo_nr_per_list Postgres trigger and EF's triggered-table batching-boundary behavior in
a way this repo has never had a test for — going through the existing single-insert path avoids
relying on that untested interaction.

Frontend: the current "New list" flow is a bare window.prompt() in TodoListMenu.tsx — no
dialog component exists to extend. Replaces it with a new CreateListDialog.tsx (content-only,
mirroring ListAppearanceDialog.tsx's shape), wrapped in the shared Dialog/DialogContent
primitive directly in TodoListMenu.tsx (matching how OwnerActionsMenu.tsx wraps
ListAppearanceDialog). Title input + a template picker grid (role="group", aria-pressed,
same idiom as the colour/icon pickers) with "Blank" pre-selected. Submits
{todoList: {...}, template} to CreateTodoListCommand.

E2E impact: 12 Playwright specs currently do page.once('dialog', d => d.accept(title)) then
page.click('#add-todolist-button'), intercepting the native prompt(). All 12 need updating to
fill the new dialog's title input and click its Create button instead — this sandbox has no
Docker/Playwright stack to run them (same limitation noted in docs/roadmap.md's #46/#52
entries), so these are updated by careful reading, not verified by a real run. Flagged here rather
than silently assumed correct.

Out of scope kept: no DB schema change at all — templates are pure command-time input, never
stored.


QA / Code review — closing notes (2026-07-22)

Arrived at this cycle fully implemented (backend, frontend, e2e migration) but never committed;
verified every AC against the code, confirmed dotnet build, npm run build, and the full frontend
vitest suite (357 tests) are green, and that the Testcontainers-backed backend handler tests fail
only with the known Docker-unavailable-sandbox shape (not a real regression) — see
ai/roles/memory/06_qa_agent_memory.md.

8-angle code review found and fixed two issues same-cycle:

  • TodoListTemplateCatalog.SeedTitles converted from a Dictionary literal to a switch
    expression, so the compiler (CS8509) now forces a decision the next time TodoListTemplate gains
    a member instead of a runtime KeyNotFoundException.
  • Added handler test coverage for the WeeklyChores and MovieNight templates (previously only
    Groceries/Blank were tested).

Two lower-severity findings were drafted as follow-up stories rather than expanding this cycle's
scope: docs/features/new/63_template_seeding_atomicity_and_batching.md (the seed loop isn't
transactional and costs N sequential per-item round trips) and
docs/features/new/64_shared_picker_grid_component.md (the template picker duplicates an existing
selectable-grid pattern from ListAppearanceDialog/LabelPicker).

# Story: List Templates **As a** user creating a new list, **I want to** choose from a set of predefined templates, **so that** common household lists are ready to use in seconds. **Acceptance criteria:** - [x] The "New list" flow presents an optional template picker before creating the list - [x] At least the following templates are available: - **Groceries** — pre-populated with 5–8 example items (Milk, Eggs, Bread, …) - **Weekly Chores** — recurring household tasks (Vacuum, Take out trash, Clean bathroom, …) - **Movie Night** — a watchlist (example titles from different genres) - **Blank** — no pre-populated todos (always available as the default) - [x] Selecting a template creates the list and adds the template todos immediately - [x] The user can skip the template picker entirely; the default is "Blank" - [x] Template todos are regular todos — they can be edited, deleted, or completed like any other - [x] Templates are maintained in a static configuration file, not in the database **Out of scope for this story:** - User-created or shared templates - Templates that import from external sources **Blockers:** None **Priority:** Could — good onboarding aid; lowers the barrier to creating a first useful list. --- ## Design note — Software Architect **Backend:** `Common/Types/TodoListTemplate.cs` — a plain closed enum (`Blank, Groceries, WeeklyChores, MovieNight`), same precedent as `TodoListColor`/`TodoListIcon` (`#23`): no validation logic needed, the type system + JSON deserialization already reject out-of-range values. Per the AC ("maintained in a static configuration file, not in the database"), the enum carries no seed data itself — a new static class `CqsTodo/Features/TodoLists/TodoListTemplateCatalog.cs` maps each `TodoListTemplate` to its `IReadOnlyList<TodoTitle>` seed titles. Nothing is persisted on `TodoListEntity`/`TodoListDto` — the template only affects the moment of creation; the resulting todos are indistinguishable from any other todo afterward (per AC). `CreateTodoListCommand` gains a second parameter: `TodoListTemplate Template = TodoListTemplate.Blank` (default preserves every existing call site/test unchanged). After the list + owner membership are persisted (existing code), `CreateTodoListCommandHandler` loops the catalog's titles for the chosen template and calls a newly constructor-injected `IHandler<CreateTodoCommand, TodoDto>` once per title — reusing the existing single-todo-creation pipeline (trigger-assigned `Nr`/`SortOrder`, per-todo WS broadcast, activity-feed event) rather than hand-rolling a batch insert. Confirmed via code research that constructor-injecting a handler resolves the **fully decorated** chain (DI decorates at the `IHandler<TRequest,TResult>` service-registration level, not per call site), so `CreateTodoCommand`'s `AuthorizeTodoListAccessForCurrentUserQuery` still runs — it passes because the owner membership row was already committed earlier in the same handler. This also sidesteps a real risk: batch-inserting multiple `TodoEntity` rows in one `SaveChangesAsync` interacts with the `trg_set_todo_nr_per_list` Postgres trigger and EF's triggered-table batching-boundary behavior in a way this repo has never had a test for — going through the existing single-insert path avoids relying on that untested interaction. **Frontend:** the current "New list" flow is a bare `window.prompt()` in `TodoListMenu.tsx` — no dialog component exists to extend. Replaces it with a new `CreateListDialog.tsx` (content-only, mirroring `ListAppearanceDialog.tsx`'s shape), wrapped in the shared `Dialog`/`DialogContent` primitive directly in `TodoListMenu.tsx` (matching how `OwnerActionsMenu.tsx` wraps `ListAppearanceDialog`). Title input + a template picker grid (`role="group"`, `aria-pressed`, same idiom as the colour/icon pickers) with "Blank" pre-selected. Submits `{todoList: {...}, template}` to `CreateTodoListCommand`. **E2E impact:** 12 Playwright specs currently do `page.once('dialog', d => d.accept(title))` then `page.click('#add-todolist-button')`, intercepting the native `prompt()`. All 12 need updating to fill the new dialog's title input and click its Create button instead — this sandbox has no Docker/Playwright stack to run them (same limitation noted in `docs/roadmap.md`'s `#46`/`#52` entries), so these are updated by careful reading, not verified by a real run. Flagged here rather than silently assumed correct. **Out of scope kept:** no DB schema change at all — templates are pure command-time input, never stored. --- ## QA / Code review — closing notes (2026-07-22) Arrived at this cycle fully implemented (backend, frontend, e2e migration) but never committed; verified every AC against the code, confirmed `dotnet build`, `npm run build`, and the full frontend vitest suite (357 tests) are green, and that the Testcontainers-backed backend handler tests fail only with the known Docker-unavailable-sandbox shape (not a real regression) — see `ai/roles/memory/06_qa_agent_memory.md`. 8-angle code review found and fixed two issues same-cycle: - `TodoListTemplateCatalog.SeedTitles` converted from a `Dictionary` literal to a `switch` expression, so the compiler (CS8509) now forces a decision the next time `TodoListTemplate` gains a member instead of a runtime `KeyNotFoundException`. - Added handler test coverage for the `WeeklyChores` and `MovieNight` templates (previously only `Groceries`/`Blank` were tested). Two lower-severity findings were drafted as follow-up stories rather than expanding this cycle's scope: `docs/features/new/63_template_seeding_atomicity_and_batching.md` (the seed loop isn't transactional and costs N sequential per-item round trips) and `docs/features/new/64_shared_picker_grid_component.md` (the template picker duplicates an existing selectable-grid pattern from `ListAppearanceDialog`/`LabelPicker`).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
robert/todo#24
No description provided.