|
@@ -312,8 +312,204 @@ with a `BOOTSTRAP_ADMIN` audit row.
|
|
|
|
|
|
|
|
### Upcoming
|
|
### 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
|
|
## 10. Residual known gaps / deferred items
|
|
|
|
|
|
|
@@ -371,11 +567,13 @@ Tell Claude:
|
|
|
|
|
|
|
|
> Working on `/Users/achiappa/Development/claude_code_private/sprint_planer_web`.
|
|
> Working on `/Users/achiappa/Development/claude_code_private/sprint_planer_web`.
|
|
|
> Read `HANDOFF.md`, the git log, and `ACCEPTANCE.md`. Phases 1–14
|
|
> 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.
|
|
> → Shipped with its SHA.
|
|
|
|
|
|
|
|
Claude should verify what's described here against actual repo state
|
|
Claude should verify what's described here against actual repo state
|