Sfoglia il codice sorgente

SPEC.md: mark Phase 18 shipped (task-status colours + filter)

Move the Phase 18 entry into Shipped (§9) with SHA 9cb7669, refresh §3
for the new SettingsController / AppSettingsRepository / migration 003 /
views/settings/, note the new schema column + check constraint in §4 and
the new /settings + status-write routes in §6, bump the test count in
§11 to 105/265, append the SHA in §13, and update the §12 resume prompt
to cover Phases 1–18.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
chiappa 3 giorni fa
parent
commit
205876a1ef
1 ha cambiato i file con 79 aggiunte e 9 eliminazioni
  1. 79 9
      SPEC.md

+ 79 - 9
SPEC.md

@@ -65,7 +65,8 @@ per-cell audit trail.
 ├── src/
 │   ├── Auth/            LocalAdmin, OidcClient, SessionGuard
 │   ├── Controllers/     AuthController, WorkerController, SprintController,
-│   │                    TaskController, AuditController, UserController
+│   │                    TaskController, AuditController, UserController,
+│   │                    SettingsController
 │   ├── Db/              Connection, Migrator
 │   ├── Domain/          User, Worker, Sprint, SprintWeek, SprintWorker,
 │   │                    SprintWorkerDay, Task, TaskAssignment
@@ -73,24 +74,27 @@ per-cell audit trail.
 │   ├── Repositories/    UserRepository, WorkerRepository, SprintRepository,
 │   │                    SprintWeekRepository, SprintWorkerRepository,
 │   │                    SprintWorkerDayRepository, TaskRepository,
-│   │                    TaskAssignmentRepository, AuditRepository
+│   │                    TaskAssignmentRepository, AuditRepository,
+│   │                    AppSettingsRepository
 │   └── Services/        AuditLogger, CapacityCalculator
 ├── migrations/          001_init.sql (full schema per spec §3)
 │                        002_sprint_week_active_days.sql (Phase 12 — mask column)
+│                        003_task_status_and_app_settings.sql (Phase 18 — task-cell status + KV)
 ├── views/               layout.php, home.php, auth/local.php,
 │                        workers/index.php, users/index.php,
 │                        sprints/{new,show,settings,present}.php,
-│                        audit/index.php
+│                        settings/index.php, audit/index.php
 ├── tests/               TestCase + Services/ + Repositories/ + Controllers/ + Cascade/
 │                                 + Domain/ + Db/
 └── data/                SQLite + sessions directory (volume-mounted, gitignored)
 ```
 
-## 4. Schema (migrations/001_init.sql + 002_sprint_week_active_days.sql)
+## 4. Schema (migrations/001..003)
 
 Tables (already applied): `users`, `workers`, `sprints`, `sprint_weeks`,
 `sprint_workers`, `sprint_worker_days`, `tasks`, `task_assignments`,
-`audit_log`, plus the `schema_version` tracking table.
+`audit_log`, `app_settings` (Phase 18 — KV store for global flags),
+plus the `schema_version` tracking table.
 
 `sprint_weeks.active_days_mask INTEGER NOT NULL DEFAULT 31` (Phase 12) is
 a 5-bit mask — bit0=Mo, bit1=Di, bit2=Mi, bit3=Do, bit4=Fr — and is the
@@ -108,6 +112,8 @@ Value constraints enforced in PHP (not SQL):
 - `sprint_weeks.active_days_mask` ∈ 0..31 (bits Mo..Fr).
 - `sprint_worker_days.days` ∈ {0, 0.5, …, 5}.
 - `task_assignments.days` ≥ 0, no hard upper bound.
+- `task_assignments.status` ∈ {`zugewiesen`, `gestartet`, `abgeschlossen`,
+  `abgebrochen`} (Phase 18). DB CHECK constraint enforces this.
 - `reserve_fraction`, `rtb` ∈ [0, 1].
 
 FK cascades (every cascade path now snapshot-audits before the parent delete
@@ -161,6 +167,8 @@ Pages (HTML):
 | GET    | `/sprints/{id}/present`     | signed-in      |
 | GET    | `/sprints/{id}/settings`    | admin          |
 | GET    | `/audit`                    | admin          |
+| GET    | `/settings`                 | admin          |
+| POST   | `/settings`                 | admin (form CSRF via `_csrf`) |
 
 JSON (admin-only, CSRF via `X-CSRF-Token` header; envelope per spec §7):
 | Method | Path                                         | What          |
@@ -177,7 +185,8 @@ JSON (admin-only, CSRF via `X-CSRF-Token` header; envelope per spec §7):
 | POST   | `/sprints/{id}/tasks/reorder`                | reorder tasks |
 | PATCH  | `/tasks/{id}`                                | title/owner/priority |
 | DELETE | `/tasks/{id}`                                | delete task (audits cascaded assignments) |
-| PATCH  | `/tasks/{id}/assignments`                    | batch assignment cells |
+| PATCH  | `/tasks/{id}/assignments`                    | batch assignment cells (days only) |
+| PATCH  | `/tasks/{id}/assignments/status`             | batch cell status — **any signed-in user**; gated by `app_settings.task_status_enabled` (403 when off) |
 
 Response envelope:
 - Success: `{"ok": true, "data": …}`
@@ -602,6 +611,63 @@ with a `BOOTSTRAP_ADMIN` audit row.
       ArrowUp/ArrowDown still steps via browser default. No
       schema / route / PHP changes; tests untouched at 88 / 208.
 
+- [x] **Phase 18 — Per-cell task-status colours + filter +
+      global toggle** (`9cb7669`). Each task-assignment cell on
+      both `/sprints/{id}` and `/sprints/{id}/present` now carries
+      a workflow status — `zugewiesen` (transparent, default),
+      `gestartet` (yellow), `abgeschlossen` (green), `abgebrochen`
+      (red) — picked from a chevron-only `<select
+      data-assign-status>` next to the day input/span. The cell is
+      wrapped in `<span class="assign-cell assign-status-{state}"
+      data-assign-cell data-status="{state}">`; `sprint-planner.js`
+      mirrors the chosen value into the wrapper class + `data-
+      status` and queues a save through a new
+      `pendingStatus`/`flushStatus` debounced pipeline that hits
+      `PATCH /tasks/{id}/assignments/status` (400 ms, same
+      semantics as the days pipeline). New "Status" multi-select
+      filter sits between Owners and Focus in the toolbar; a row
+      passes when at least one cell is in the picked set, with a
+      special-case rule that the default `zugewiesen` only matches
+      when `days > 0` (so picking it doesn't include every task).
+      State persists in `localStorage`
+      (`sp:{sprintId}:statusFilter`, `:beamer`-namespaced for the
+      present view) and is wiped by the existing Reset button.
+      Schema: `migrations/003_task_status_and_app_settings.sql`
+      adds `task_assignments.status TEXT NOT NULL DEFAULT
+      'zugewiesen'` with a CHECK constraint, and creates a new
+      `app_settings(key TEXT PK, value TEXT NOT NULL, updated_at
+      TEXT NOT NULL)` KV table seeded with
+      `('task_status_enabled', '0')` so the feature is opt-in.
+      `App\Repositories\AppSettingsRepository` (get/getBool/set)
+      reads it; `SprintController::loadSprintPage()` passes
+      `taskStatusEnabled` + `statusGrid` into both views, which
+      conditionally render the per-cell selectors and the toolbar
+      Status filter. New admin-only `/settings` page (linked from
+      the hamburger menu, `App\Controllers\SettingsController`)
+      flips the toggle via a native form POST with `_csrf`;
+      audit row `entity_type='app_setting'`. New
+      `PATCH /tasks/{id}/assignments/status` is the **first non-
+      admin write surface** in the app — gated by
+      `SessionGuard::requireAuthJson` (auth + CSRF, no admin) plus
+      `app_settings.task_status_enabled` (403 when off). Existing
+      `PATCH /tasks/{id}/assignments` stays admin-only and days-
+      only; `TaskAssignmentRepository::upsert` preserves status,
+      `::upsertStatus` preserves days and inserts a `days=0` row
+      when the cell didn't exist (so a state can be tracked before
+      any work is assigned). Per-cell audit semantics unchanged
+      — one row per changed cell. The four `.assign-status-*`
+      class names are interpolated server-side, so
+      `tailwind.config.js` gains a `safelist` keeping them in the
+      build (Tailwind was silently dropping them otherwise — the
+      `:not()` reference happened to keep `zugewiesen` in but the
+      other three vanished). Strict CSP unchanged. Tests:
+      105 / 265 (was 88 / 208) — `+TaskAssignmentTest` (status
+      enum + audit-snapshot shape), `+AppSettingsRepositoryTest`
+      (seeded flag, get/set roundtrip, no-op equality, default
+      fallback), `+TaskAssignmentRepositoryTest` (upsertStatus's
+      four cases, days writes preserving status,
+      `InvalidArgumentException` guard, `statusGridForSprint`).
+
 ### Upcoming
 
 Nothing scheduled.
@@ -662,7 +728,7 @@ for f in $(git ls-files '*.php'); do php -l "$f" | tail -1 | sed "s|^|$f: |"; do
 Run the test suite:
 ```bash
 vendor/bin/phpunit
-# → OK (88 tests, 208 assertions)
+# → OK (105 tests, 265 assertions)
 ```
 
 ## 12. How to resume in a fresh Claude session
@@ -670,10 +736,12 @@ vendor/bin/phpunit
 Tell Claude:
 
 > Working on `/Users/achiappa/Development/claude_code_private/sprint_planer_web`.
-> Read `SPEC.md`, the git log, and `ACCEPTANCE.md`. Phases 1–17
+> Read `SPEC.md`, the git log, and `ACCEPTANCE.md`. Phases 1–18
 > are shipped (see §9; the Phase-17 slider popover was later
 > removed — typed entry is now the only edit path on number
-> inputs) — nothing is currently scheduled. Outstanding
+> inputs; Phase 18 added per-cell task-status colours + filter
+> + a new /settings page, all gated by a global flag that's off
+> by default). Nothing is currently scheduled. Outstanding
 > items are in §10 (mostly a human-run acceptance walkthrough in the
 > running container, plus the jQuery UI dark-mode cosmetic gap noted
 > there). If I ask you to plan or work a new phase, follow the
@@ -686,6 +754,8 @@ before acting — nothing here is load-bearing once it grows stale.
 ## 13. Git history (as of this writing)
 
 ```
+9cb7669 Phase 18: per-cell task-status colours + filter + global toggle
+da726bd SPEC.md: note number-stepper popover removal
 e551705 Remove number-stepper slider popover
 c5eef6a Docs: rename HANDOFF.md to SPEC.md, add admin manual, refresh README
 fd2f0df changed docker compose port