|
|
@@ -108,6 +108,7 @@ per-cell audit trail.
|
|
|
├── 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)
|
|
|
+│ 004_task_metadata_and_links.sql (Phase 22 — task description/url + linked_task_id)
|
|
|
├── views/ (Twig 3) layout.twig, layout-bare.twig, home.twig,
|
|
|
│ auth/local.twig, workers/index.twig,
|
|
|
│ users/index.twig, audit/index.twig,
|
|
|
@@ -122,13 +123,18 @@ per-cell audit trail.
|
|
|
(volume-mounted, gitignored)
|
|
|
```
|
|
|
|
|
|
-## 4. Schema (migrations/001..003)
|
|
|
+## 4. Schema (migrations/001..004)
|
|
|
|
|
|
Tables (already applied): `users`, `workers`, `sprints`, `sprint_weeks`,
|
|
|
`sprint_workers`, `sprint_worker_days`, `tasks`, `task_assignments`,
|
|
|
`audit_log`, `app_settings` (Phase 18 — KV store for global flags),
|
|
|
plus the `schema_version` tracking table.
|
|
|
|
|
|
+Phase 22 (migration 004) adds three columns to `tasks`:
|
|
|
+`description TEXT NOT NULL DEFAULT ''`, `url TEXT NOT NULL DEFAULT ''`,
|
|
|
+and `linked_task_id INTEGER REFERENCES tasks(id) ON DELETE SET NULL`
|
|
|
+— set on a copy and pointed at the source. Plus index `idx_tasks_linked`.
|
|
|
+
|
|
|
`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
|
|
|
source of truth for "is this a workday this week." `max_working_days`
|
|
|
@@ -224,6 +230,8 @@ JSON (admin-only, CSRF via `X-CSRF-Token` header; envelope per spec §7):
|
|
|
| DELETE | `/tasks/{id}` | delete task (audits cascaded assignments) |
|
|
|
| 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) |
|
|
|
+| POST | `/tasks/{id}/move` | move task to another sprint (drops assignments, audited) — Phase 22 |
|
|
|
+| POST | `/tasks/{id}/copy` | clone task into another sprint with `linked_task_id = source.id` — Phase 22 |
|
|
|
|
|
|
Response envelope:
|
|
|
- Success: `{"ok": true, "data": …}`
|
|
|
@@ -950,6 +958,56 @@ with a `BOOTSTRAP_ADMIN` audit row.
|
|
|
/sprints/{id}/weeks` are unchanged — the JSON endpoint is kept for
|
|
|
back-compat but the UI no longer calls it. +5 tests, 148 total.
|
|
|
|
|
|
+- [x] **Phase 22 — Per-task hamburger menu: move / copy / edit / reorder**
|
|
|
+ (`c2dad80`). Replaces the SortableJS drag handle on the task table
|
|
|
+ with a per-row hamburger button (admin only) that gathers every
|
|
|
+ task-level action: *Edit details…* (modal with a `description`
|
|
|
+ textarea, ≤8000 chars plain text, plus a `url` input — empty or
|
|
|
+ `http(s)://`, ≤2048 chars), *Move to sprint…* (submenu of every
|
|
|
+ other sprint), *Copy to sprint…* (same submenu), *Move (pick up)*
|
|
|
+ (click-to-pickup → row follows cursor Y with an amber drop
|
|
|
+ indicator → click-anywhere-to-drop, Escape cancels), *Delete*
|
|
|
+ (the standalone × column was folded in). Worker-row SortableJS
|
|
|
+ stays put — only the task-table draggable was removed. Title cell
|
|
|
+ gains: a small external-link anchor when `url` is set
|
|
|
+ (`target="_blank" rel="noopener"`, visible to all), a description
|
|
|
+ marker that opens a read-only popover (so non-admins can read
|
|
|
+ descriptions without the edit modal), and bidirectional linked-
|
|
|
+ task chips ("← Sprint X" for the source row, "→ Sprint Y" for
|
|
|
+ every row that copies back). Two new admin-only JSON endpoints:
|
|
|
+ `POST /tasks/{id}/move` (audits each `task_assignment` DELETE
|
|
|
+ before the cascade, then `UPDATE` on `tasks.sprint_id` — task
|
|
|
+ lands at MAX+1 in the destination's `sort_order`) and `POST
|
|
|
+ /tasks/{id}/copy` (creates a fresh task with title / owner /
|
|
|
+ priority / description / url cloned and `linked_task_id =
|
|
|
+ source.id`; assignments are dropped per the design call).
|
|
|
+ `PATCH /tasks/{id}` accepts `description` and `url` alongside
|
|
|
+ the prior whitelist; URL is validated server-side
|
|
|
+ (`^https?://` or empty). Schema: `migrations/004_task_metadata_
|
|
|
+ and_links.sql` adds `tasks.description TEXT NOT NULL DEFAULT ''`,
|
|
|
+ `tasks.url TEXT NOT NULL DEFAULT ''`, `tasks.linked_task_id
|
|
|
+ INTEGER REFERENCES tasks(id) ON DELETE SET NULL`, and
|
|
|
+ `idx_tasks_linked` — existing rows stay valid without backfill.
|
|
|
+ `TaskRepository::linkedSummariesForTasks()` resolves both
|
|
|
+ directions in two queries off the per-sprint task list;
|
|
|
+ `SprintController::loadSprintPage` passes `sprintChoices` (every
|
|
|
+ sprint except this one) + `linkedMap` into the show + present
|
|
|
+ views, and the JS reads `sprintChoices` from a JSON-encoded
|
|
|
+ `data-sprint-choices` attribute on `data-task-section`. The
|
|
|
+ details modal, hamburger menu, description popover, and
|
|
|
+ pickup indicator are all single body-attached nodes built lazily
|
|
|
+ by `sprint-planner.js` — no Alpine, no htmx swap, strict CSP
|
|
|
+ stays intact (no inline handlers, no new external hosts). New
|
|
|
+ `@layer components` block in `assets/css/input.css` defines
|
|
|
+ `.task-menu` + `.task-menu-{item,sub-item,...}`, `.task-modal-
|
|
|
+ {overlay,panel,header,body,footer}`, `.task-pickup-{active,
|
|
|
+ indicator}`, and `.task-desc-popover`; the Phase 15 beamer rule
|
|
|
+ grows a `[data-task-menu-trigger]` hide so the present view
|
|
|
+ stays read-only. Tests: 148 / 406 — unchanged. The new fields
|
|
|
+ default empty/NULL so existing `TaskRepository` / cascade /
|
|
|
+ status tests keep passing without modification; the migrator
|
|
|
+ picks up 004 on next request.
|
|
|
+
|
|
|
### Upcoming
|
|
|
|
|
|
Nothing scheduled.
|
|
|
@@ -1022,7 +1080,7 @@ docker run --rm -v "$(pwd):/app" -w /app sprint_planer_web-app:latest \
|
|
|
Tell Claude:
|
|
|
|
|
|
> Working on `/Users/achiappa/Development/claude_code_private/sprint_planer_web`.
|
|
|
-> Read `SPEC.md`, the git log, and `ACCEPTANCE.md`. Phases 1–20
|
|
|
+> Read `SPEC.md`, the git log, and `ACCEPTANCE.md`. Phases 1–22
|
|
|
> are shipped (see §9; Phase 17's slider popover was removed —
|
|
|
> typed entry is now the only edit path on number inputs; Phase 18
|
|
|
> added per-cell task-status colours + filter + a new /settings page
|
|
|
@@ -1030,7 +1088,12 @@ Tell Claude:
|
|
|
> stack to Twig 3 + Tailwind 3 + Alpine CSP + htmx + SortableJS and
|
|
|
> removed jQuery + jQuery UI completely; Phase 20 added a two-step
|
|
|
> XLSX import wizard at `/sprints/import` powered by PhpSpreadsheet,
|
|
|
-> with a colour-coded cell → status mapping). Nothing is currently
|
|
|
+> with a colour-coded cell → status mapping; Phase 21 derives the
|
|
|
+> sprint week count from start/end dates; Phase 22 replaced the task
|
|
|
+> drag handle with a per-row hamburger menu — Edit details / Move
|
|
|
+> across sprints / Copy across sprints (with bidirectional linked-
|
|
|
+> task chips) / click-pickup reorder / Delete; tasks gained
|
|
|
+> description / url / linked_task_id columns). Nothing is currently
|
|
|
> scheduled. Outstanding items are in §10 (mostly a human-run
|
|
|
> acceptance walkthrough in the running container). If I ask you to
|
|
|
> plan or work a new phase, follow the maintenance rule in §14 —
|
|
|
@@ -1043,6 +1106,9 @@ before acting — nothing here is load-bearing once it grows stale.
|
|
|
## 13. Git history (as of this writing)
|
|
|
|
|
|
```
|
|
|
+c2dad80 Phase 22: per-task hamburger menu — move/copy/edit/reorder
|
|
|
+e2f19d6 Phase 21: derive sprint week count from start/end dates
|
|
|
+62bb8b2 SPEC.md: mark Phase 20 shipped (XLSX import wizard)
|
|
|
8876239 Phase 20: XLSX import wizard (phpspreadsheet + colour→status)
|
|
|
2813019 Sprint view: tabs (Arbeitstage / Tasks) + smart Close on present
|
|
|
10ea4b8 Cell popover: replace per-cell status select with slider + status pills
|