Bläddra i källkod

HANDOFF.md: add Phase 15 plan (big-screen task viewer)

Plan-only commit. The Phase 15 entry in §9 Upcoming describes a new
GET /sprints/{id}/present route that renders the task list only, at
full viewport width, without site-header chrome — optimised for group
discussions around a beamer. Reuses the existing sprint-planner.js
filter/focus/columns plumbing untouched; adds a small namespaced
localStorage scope for beamer-only hidden-columns so the regular page
state is not clobbered. Seeds owner / prio / tot as hidden by default
in presentation mode. Includes a JS-measured fallback that rotates
worker headers vertically when the table overflows the viewport.
Admins can edit in place; viewers see read-only text. §12 resume
prompt updated so a fresh session sees Phase 15 as scheduled.

No code changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
achiappa 2 veckor sedan
förälder
incheckning
48c56b77db
1 ändrade filer med 205 tillägg och 7 borttagningar
  1. 205 7
      HANDOFF.md

+ 205 - 7
HANDOFF.md

@@ -312,8 +312,204 @@ with a `BOOTSTRAP_ADMIN` audit row.
 
 ### Upcoming
 
-- Nothing scheduled. When a new phase is ready, drop its plan
-  here and flip the checkbox when it ships (see §14).
+- [ ] **Phase 15 — Big-screen (beamer) task viewer**
+
+      **Problem.** Sprint-planning stand-ups and retro discussions happen
+      around a projector / TV at roughly 1920×1080 or 3840×2160. The
+      regular `/sprints/{id}` page is tuned for editing from a laptop:
+      `max-w-7xl` centred on the page, full header chrome, the
+      Arbeitstage matrix and capacity summary stacked on top of the
+      task list. On a beamer this wastes 40%+ of the screen width
+      and forces horizontal scroll once a sprint has more than ~7
+      workers — exactly the moment when everyone needs to see every
+      column at once.
+
+      **Goal.** A dedicated **presentation route** that strips the
+      chrome, dumps the planning sections the group isn't discussing,
+      and renders only the task list at full-viewport width with all
+      worker columns visible without horizontal scroll. Filters,
+      focus, and column-visibility reuse the existing Phase 10/13
+      plumbing as-is. Admins can still edit cells in-place (discussion
+      edits persist instantly); viewers see read-only text, same as
+      today.
+
+      **Scope (plan).**
+
+      1. **Route + controller.**
+         - New route in `public/index.php`:
+           `GET /sprints/{id}/present` → `SprintController::present(...)`.
+           Auth: `SessionGuard::requireAuth($this->users)` — anyone
+           signed-in can view, same as `show()`.
+         - New `SprintController::present(Request $req, array $params)`
+           method. Its body is ~90% a copy of `show()` — load the
+           sprint, weeks, sprint workers, day grid, tasks, task grid,
+           capacity, owner choices — but renders a different view.
+           To avoid drift with `show()`, extract the data-loading
+           fan-out into a private `loadSprintPage(int $id): ?array`
+           helper that both methods call. Return `null` when the
+           sprint is missing; let each caller render the appropriate
+           404. (This is the only controller-level refactor; audit
+           and route wiring are unchanged.)
+
+      2. **View — `views/sprints/present.php`.**
+         - Does **not** extend the shared `views/layout.php`. Instead
+           the view emits its own `<!doctype html>` with a minimal
+           `<head>` that reuses the same compiled
+           `/assets/css/app.css`, the same jQuery + jQuery UI CDN
+           tags from `layout.php`, and `/assets/js/sprint-planner.js`
+           with `defer`. No nav header, no page padding — the entire
+           viewport belongs to the task table.
+         - Root wrapper:
+           `<body class="bg-white"><main class="min-h-screen w-screen overflow-hidden beamer-root">...`.
+           `.beamer-root` is a new scoping class (see §3 below).
+         - Mount the existing `[data-sprint-root]` + `data-sprint-id`
+           + `data-csrf` + `data-reserve-fraction` so
+           `sprint-planner.js` auto-wires filters, focus, reset,
+           column visibility, and cell-save debounce without a
+           single line of new JS.
+         - Render **only** the `<section data-task-section>` block
+           (toolbar + table). Drop the Arbeitstage matrix, the
+           capacity summary, and the footer hint. A thin top bar
+           shows the sprint name + date range + a "Close" link back
+           to `/sprints/{id}` (so a viewer can escape without
+           `history.back`). Include the CSRF meta and the status chip
+           so error flashes still surface.
+         - Leave the toolbar as-is: search, prio filter, owner
+           multi-select, focus, columns, reset, + add-task (admin
+           only). In presentation mode, tasks often need live
+           captures — we keep the button.
+
+      3. **CSS — `assets/css/input.css`.**
+         - Add a `beamer-root` scoping class under `@layer
+           components` (keeps Tailwind's JIT scan happy without
+           scattering one-off classes across the view). Inside the
+           scope:
+             * `table { table-layout: fixed; font-size: clamp(0.75rem, 0.95vw, 1.05rem); }`
+             * Smaller cell padding (`td, th { padding: 0.25rem 0.35rem; }`).
+             * Hide non-discussion columns **by default** via extra
+               `data-col` values on the toolbar's hidden-columns
+               set: on first page load, seed
+               `localStorage['sp:{sprintId}:hiddenCols']` with
+               `["owner","prio","tot"]` if the key has never been
+               set in presentation mode. Use a second key —
+               `sp:{sprintId}:hiddenCols:beamer` — so the regular
+               `/sprints/{id}` view's hidden-columns pick isn't
+               clobbered. Implementing this cleanly needs one tiny
+               JS touch: see §4.
+             * `.handle { display: none; }` — drag-reorder is wrong
+               for a shared screen; also hides the delete buttons
+               (pattern: add `.px-1.py-1.text-right button[data-delete-task] { display: none; }`).
+         - "No horizontal scroll" rule: if the rendered table is
+           wider than the viewport, rotate worker-column headers
+           vertically. Detection is pure CSS — use a container
+           query on `.beamer-root section[data-task-section]`
+           (`@container (max-width: calc(100vw))`). Too clever;
+           fall back to a JS-side switch that measures
+           `table.scrollWidth > containerWidth` once on boot and
+           toggles a `.beamer-vertical-headers` class on the root.
+           In the CSS, `.beamer-vertical-headers thead th[data-sort-col^="sw-"] { writing-mode: vertical-rl; transform: rotate(180deg); padding: 0.5rem 0.25rem; }`.
+           See §4 — 10 lines of new JS.
+
+      4. **JS — `public/assets/js/sprint-planner.js` (minimal touch).**
+         - Detect the beamer root on boot. If
+           `$root.closest('.beamer-root').length > 0` (or a new
+           `$root.data('beamer') === 1`), use the `:beamer`
+           localStorage namespace for `hiddenCols` (and optionally
+           `focusWorker` + `ownerFilterSet`). Cleanest: take the
+           storage-key prefix as a variable derived from a single
+           boolean `isBeamer`.
+         - On boot in beamer mode, if
+           `localStorage[beamerHiddenColsKey]` is missing, seed with
+           `["owner","prio","tot"]`, persist, then call
+           `applyColumnVisibility()`. Respects the user's later
+           overrides.
+         - After `applyFilters()` completes for the first time in
+           beamer mode, measure `table.scrollWidth` vs
+           `container.clientWidth`. If it still overflows, add
+           `.beamer-vertical-headers` to the root and re-measure; if
+           it still overflows (very wide sprint), log a console
+           warning but leave horizontal scroll available — hard
+           visual failure is worse than a small scroll.
+         - Every other handler (cell save, focus, reset) keeps
+           working unchanged.
+
+      5. **Entry point — `views/sprints/show.php`.**
+         - Next to the "Settings" button in the sprint header, add an
+           anchor:
+           `<a href="/sprints/{id}/present" target="_blank" rel="noopener"
+           class="inline-flex items-center gap-2 rounded-md border
+           border-slate-300 bg-white text-slate-700 px-3 py-2 text-sm
+           hover:bg-slate-100">Present</a>`.
+           Visible to every signed-in user, not just admins.
+
+      6. **A11y / polish.**
+         - The vertical-text rotation applies `aria-label` stays the
+           plain text, so screen readers read it horizontally.
+         - Dark mode / high-contrast theme is deferred (see out of
+           scope).
+         - Focus outline stays visible so keyboard users can still
+           navigate.
+
+      7. **Edge cases.**
+         - **Archived sprints** render fine — read-only for
+           non-admins via the same `$currentUser->isAdmin` gate.
+         - **Zero workers / zero tasks** renders the existing
+           empty-state banner inside the viewport.
+         - **Sprint ID mismatch** — 404 via the same path `show()`
+           uses.
+         - **Printing** — out of scope; press `Ctrl+P` if curious,
+           the CSS doesn't have `@media print` rules yet.
+
+      8. **Tests.**
+         - No PHPUnit additions expected — the new method is mostly
+           a re-use of existing repositories. If the extraction of
+           `loadSprintPage()` is done, add **one** controller-level
+           sanity test that `present` renders HTTP 200 for a seeded
+           sprint and 404 for an unknown one. This keeps the count
+           climbing only if refactoring benefits from a guard.
+           Target: **88 → 90** if the helper is extracted; stay at
+           **88** if it isn't.
+         - `ACCEPTANCE.md` gains a "Phase 15 — Big-screen viewer"
+           section with six scenarios:
+             (a) open `/sprints/{id}/present` on an admin account:
+                 no horizontal scroll at 1920×1080; all worker
+                 columns visible;
+             (b) same, but at 3840×2160 (HiDPI beamer);
+             (c) apply focus + owner filter + search: all still
+                 work and column auto-hide still fires;
+             (d) admin edits a cell in present mode; reload
+                 `/sprints/{id}`; value persisted;
+             (e) non-admin user opens the URL: read-only, same
+                 layout;
+             (f) very wide sprint (12+ workers): vertical header
+                 rotation kicks in, table still fits.
+
+      **Out of scope.**
+      - TV-optimised high-contrast / dark theme. Separate concern;
+        revisit if a user complains about legibility in a dim room.
+      - Auto-scroll / ticker mode. Group discussions steer
+        themselves; no one has asked.
+      - Remote-control navigation (keyboard arrows to cycle
+        tasks/filters). Keep the filter toolbar as the single
+        interaction surface.
+      - Live-update push / websockets. If one admin edits a cell
+        while the beamer is projected, a manual reload picks it up
+        — acceptable for a stand-up cadence.
+      - Mobile layout variant. The presentation view is for large
+        screens; on phones, `/sprints/{id}` is already the right
+        shape.
+
+      **Spec alignment.**
+      - §3 layout: add `views/sprints/present.php` to the `views/`
+        line.
+      - §4 / §5 / §7: no changes — no new schema, math, or audit
+        surface.
+      - §6 Routes: add the new `GET /sprints/{id}/present` row
+        (signed-in, any auth level).
+      - §11 phpunit count: 88 → 90 if the controller refactor
+        adds tests, else stays 88.
+      - Strict CSP (§ Phase 11) stays intact — no inline handlers,
+        no new external hosts.
 
 ## 10. Residual known gaps / deferred items
 
@@ -371,11 +567,13 @@ Tell Claude:
 
 > Working on `/Users/achiappa/Development/claude_code_private/sprint_planer_web`.
 > Read `HANDOFF.md`, the git log, and `ACCEPTANCE.md`. Phases 1–14
-> are shipped (see §9); no scheduled work — §9 Upcoming is empty.
-> Outstanding items are in §10 (mostly a human-run acceptance
-> walkthrough in the running container). If a new phase comes up,
-> follow the maintenance rule in §14 — commit code, then commit a
-> HANDOFF.md update separately that moves the entry from Upcoming
+> are shipped (see §9); Phase 15 (big-screen / beamer task viewer at
+> `/sprints/{id}/present`) is planned but not implemented — the
+> scope is in §9 under Upcoming. Outstanding items are in §10
+> (mostly a human-run acceptance walkthrough in the running
+> container). If I ask you to work Phase 15, follow the maintenance
+> rule in §14 — commit code, then commit a HANDOFF.md update
+> separately that moves the entry from Upcoming
 > → Shipped with its SHA.
 
 Claude should verify what's described here against actual repo state