Acceptance checklist (spec §10) — walkthrough
This is the checklist from the spec. Run it against a fresh container after
Phase 7 lands. Expected outcome is noted next to each step; edit this file
(and open a bug) if any step deviates.
Setup
docker compose down && rm -rf data/app.sqlite
docker compose up --build
.env should have EITHER valid ENTRA_* values OR LOCAL_ADMIN_EMAIL +
LOCAL_ADMIN_PASSWORD set. Below, "sign in" means whichever flow you
configured.
Steps
Fresh container, empty DB → sign in → you're the admin.
- Open http://localhost:8080.
- If OIDC configured, click Sign in with Microsoft → complete Entra
round-trip. If local admin configured, click Sign in as local admin
and enter the env credentials.
- Expected: header shows your display name +
admin badge.
SELECT is_admin FROM users LIMIT 1 in data/app.sqlite returns 1.
audit_log has rows: CREATE user / BOOTSTRAP_ADMIN user / LOGIN user.
Create a sprint with 4 weeks.
- Header → New sprint → name "Sprint 1", reasonable dates, reserve 20%,
weeks = 4 → Create sprint.
- Expected: redirect to
/sprints/1; empty-state banner says "No workers".
Clicking Settings opens the settings page. The weeks table shows
4 rows with max_working_days 5.0 each and ISO week numbers computed
from the start date.
Add 6 workers, reorder by dragging — reload persists order.
- Header → Workers → add Alice, Bob, Carol, Dan, Eve, Frank.
- Back to
/sprints/1/settings → add all 6 to the sprint via Add →
buttons.
- Drag the "≡" handles to reorder (e.g. reverse order).
- Reload the page.
- Expected: the reordered sequence persists.
audit_log shows UPDATE sprint_worker rows for every moved row
(unchanged rows emit nothing thanks to the no-op rule).
Fill Arbeitstage; Ressourcen / Reserven / Available update live.
- Open
/sprints/1 → edit a day cell (0.5-step input).
- Blur the input.
- Expected: Σ column updates immediately, the capacity strip updates
(
Ressourcen, − Reserven, Available). Status pill flashes "Saved N
cell(s)". audit_log has one CREATE sprint_worker_days row per edited
cell.
Add tasks, assign days — Tot updates; over-commit turns Available red
but still saves.
- In Section B: click + Add task → focus jumps to the title input →
type "Report for Q1", set priority 1, assign 10 days to one worker
whose capacity is 8.
- Expected:
Tot cell shows 10; that worker's Available in the capacity
strip goes negative and turns red. The assignment is persisted
(reload still shows 10); audit_log has CREATE task + CREATE
task_assignment.
Sort task table by Owner, then by a worker column; clear → original
drag order returns.
- Click the
Owner header — rows sort asc, header shows "↑".
- Click again — desc, "↓".
- Click any worker column header — sorts by that cell's days.
- Click the same header a third time (or any other until cleared) — the
rows return to
data-sort-order (the drag-persisted order).
- Expected: no 500s, no layout shift, drag is disabled while a sort is
active.
Filter tasks: Prio=1, Owner=X, free-text "report".
- Set prio filter to "Prio 1 only", owner to a specific worker, type
"report" into the search box.
- Expected: only matching rows stay visible. The "No tasks match the
current filters" banner appears when every row is hidden.
Rename a worker in /workers — reflected everywhere.
- Header → Workers → change "Alice" to "Alice Cooper" → Save.
- Open
/sprints/1 in another tab.
- Expected: Arbeitstage row label, task list column header, and the
Owner dropdowns all show the new name.
audit_log has an UPDATE
worker row.
/audit shows one row per change with diffs visible.
- Header → Audit log.
- Expected: reverse-chronological table with 50/page pagination. Every
change you made is there. Each row's "before / after"
<details> opens
to show pretty-printed JSON snapshots.
- Filter by user / action / entity type / date range / entity_id substring
— narrow the list.
Sign out; unauthenticated → redirect to /auth/login.
- Click Sign out.
- Expected: session cleared,
audit_log has a LOGOUT user row.
- Try visiting
/workers or /sprints/1/settings while anonymous →
redirected to /auth/login (and then Entra, or the local form).
Spot-check (spec §12)
If you have Tool_Sprint_PlanningSample.xlsx handy:
- Pick a worker with known weekly availability (e.g. 4-4-5-5-2).
Ressourcen should match Excel: sum across weeks.
− Reserven should match: round_to_0.5(Ressourcen * 0.8) with reserve = 20 %.
- For a prio-1 task totalling N days on that worker,
Available should be
(Ressourcen − Reserven) − N, and turn red when negative.
Security headers (spec §9)
With the app running, curl -I http://localhost:8080/ should report:
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Content-Security-Policy: default-src 'self'; script-src 'self' …; …
Strict-Transport-Security appears only when APP_BASE_URL uses https://.
Phase 13 — Focus filter + reset
Runs against a sprint with at least 4 workers and a handful of tasks —
set up via steps 3–5 above if starting from scratch. All scenarios below
are pure client-side UI; no audit rows are written and no API requests
should fire (verify with the Network panel open).
Focus on a worker with assignments → only their tasks + only their
non-zero columns remain.
- Pick a sprint worker (say Bob) who is the assignee on two tasks
out of five.
- Toolbar → Focus select → choose Bob.
- Expected:
- Only the two tasks where Bob's cell is > 0 stay visible.
- Any worker column where every visible row is 0 collapses. Bob's
own column stays visible (by definition > 0).
- The Reset button appears to the left of the search box.
- Reloading the page preserves the focus (localStorage key
sp:{sprintId}:focusWorker).
Focus on a worker with no assignments → empty-filter banner + every
sw column collapses.
- Add a new worker to the sprint who has no task assignments (or pick
an existing one — e.g. Frank — and zero out all their cells
first).
- Toolbar → Focus → choose that worker.
- Expected:
- Task rows all hidden → the "No tasks match the current filters"
banner shows (NOT the "No tasks yet" empty-state row).
- Every worker column in the task-list header is collapsed (no sw
column has a non-zero visible row).
- Title / Owner / Prio / Tot columns stay visible.
Stacked filters AND together.
- Type "report" in Search, set Prio to "Prio 1 only", check
one owner in Owners, and pick a Focus worker.
- Expected: only rows that satisfy every predicate show —
title matches, prio = 1, owner matches, focus worker's assignment
> 0. Flipping any one filter off broadens the set as expected.
Reset clears everything and the button hides.
- With all four filters from (3) active (plus at least one column
hidden via the Columns dropdown), click Reset.
- Expected:
- Search empties, Prio resets to "All prios", Owners count clears,
Focus returns to "All workers", every column re-appears in the
Columns dropdown (all checkboxes re-checked), every row visible.
- The Reset button itself disappears.
- Reload the page — nothing returns (all five localStorage keys
cleared/reset to empty state).