Kaynağa Gözat

SPEC.md: mark Phase 22 shipped (per-task hamburger menu)

§9 Shipped: full Phase 22 entry with the SHA + design notes (modal,
move/copy semantics, click-pickup reorder, bidirectional linked-task
chips, schema additions).

§3 / §4: note migration 004 and the three new tasks columns
(description, url, linked_task_id with ON DELETE SET NULL).

§6: add POST /tasks/{id}/move and POST /tasks/{id}/copy to the
JSON endpoint table.

§12: bump the resume prompt's phase ceiling to 22 with a one-liner
on what the phase covers.

§13: prepend c2dad80 (and the missed e2f19d6 + 62bb8b2 from the
last two phases) to the git history block.

Tests stay at 148 / 406 — no new test surface this phase, same
pattern as Phases 10 / 13 / 14 / 15 / 16 / 17 / "tab+close"
follow-up etc.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
chiappa 2 gün önce
ebeveyn
işleme
be9162011f
1 değiştirilmiş dosya ile 69 ekleme ve 3 silme
  1. 69 3
      SPEC.md

+ 69 - 3
SPEC.md

@@ -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