Quellcode durchsuchen

HANDOFF.md: mark Phase 16 shipped

Move the Phase 16 entry from §9 Upcoming → Shipped with SHA
`94b2841`. Reset §9 Upcoming to the "Nothing scheduled"
template. Add `public/assets/js/theme-init.js` to the §3
`public/assets/js/` block and note that `app.js` now also
hosts the theme toggle. Append `94b2841` to the §13 git
history. Rewrite §12's resume prompt — Phases 1–16 shipped,
no scheduled work, with the new jQuery UI dark-mode gap
called out in §10. Test count stays at 88 per §11.

The new §10 entry for the jQuery UI CDN base-theme stylesheet
captures the cosmetic mismatch flagged in the Phase 16 plan's
edge-cases section: the sortable ghost element during drag
operations looks slightly off on a dark body. Fixing it means
self-hosting a custom jQuery UI theme through the Docker
css-builder stage, which is a larger chunk of work than the
cosmetic gap warrants.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
achiappa vor 2 Wochen
Ursprung
Commit
0d738b26d2
1 geänderte Dateien mit 81 neuen und 207 gelöschten Zeilen
  1. 81 207
      HANDOFF.md

+ 81 - 207
HANDOFF.md

@@ -56,7 +56,8 @@ per-cell audit trail.
 │   └── assets/
 │   └── assets/
 │       ├── css/app.css         # GENERATED at image-build time (gitignored)
 │       ├── css/app.css         # GENERATED at image-build time (gitignored)
 │       └── js/
 │       └── js/
-│           ├── app.js              # site-wide; data-href click handler
+│           ├── theme-init.js       # Phase 16: synchronous dark-class set from localStorage (no FOUC)
+│           ├── app.js              # site-wide; data-href click handler + hamburger menu + theme toggle
 │           ├── sprint-planner.js   # /sprints/{id} Arbeitstage + task list
 │           ├── sprint-planner.js   # /sprints/{id} Arbeitstage + task list
 │           └── sprint-settings.js  # /sprints/{id}/settings
 │           └── sprint-settings.js  # /sprints/{id}/settings
 ├── src/
 ├── src/
@@ -360,205 +361,68 @@ with a `BOOTSTRAP_ADMIN` audit row.
       "Phase 15 — Big-screen viewer" section with the six manual
       "Phase 15 — Big-screen viewer" section with the six manual
       scenarios from the plan.
       scenarios from the plan.
 
 
+- [x] **Phase 16 — Dark-mode toggle + light-mode contrast
+      cleanup** (`94b2841`). Two small palette issues addressed
+      at once: (a) both body and table-header bands used
+      `bg-slate-50`, so table headers blended into the page —
+      body bumps one shade cooler to `bg-slate-100` (the
+      user's explicit ask), `<thead>` bands stay at `bg-slate-50`
+      and now read as a distinct lighter strip; (b) no dark
+      palette existed at all, painful for the Phase 15 beamer
+      view in dim conference rooms. Manual toggle only — no
+      `prefers-color-scheme` auto-detect. `tailwind.config.js`
+      gains `darkMode: 'class'`. New
+      `public/assets/js/theme-init.js` (8 lines, synchronous
+      `<script src>` in `<head>` before the stylesheet) reads
+      `localStorage['sp:theme']` inside a try/catch and sets
+      `<html class="dark">` if the value is `'dark'` — no FOUC.
+      `views/layout.php` and `views/sprints/present.php` both
+      include the init script; the present route emits its own
+      `<!doctype html>`, hence two tags. `public/assets/js/app.js`
+      grows a third vanilla-JS IIFE (~17 lines) wiring
+      `[data-theme-toggle]`: toggle the class on `<html>`, write
+      `sp:theme`, stamp the `[data-theme-label]` text; writes
+      wrapped in try/catch so private-window denials no-op. The
+      toggle lives in the hamburger menu in `views/layout.php`
+      as a new "Theme" row above the `<hr>` divider, visible to
+      admins and non-admins alike (theme is a personal
+      preference, not an admin action); the divider now always
+      renders (previously only admins saw one) because the
+      Theme row always renders. Every view file (`views/layout`,
+      `views/home`, `views/auth/local`, `views/workers/index`,
+      `views/users/index`, `views/sprints/{new,settings,show,
+      present}`, `views/audit/index`) gets a systematic
+      `dark:` sweep: body/card/header surfaces on the
+      `slate-900/800/700` ramp, borders on `slate-700` (600 for
+      inputs), primary text `slate-100`, secondary `slate-400`,
+      inputs `dark:bg-slate-800 dark:border-slate-600
+      dark:text-slate-100 dark:focus:ring-slate-500`, links
+      `dark:text-blue-400 dark:hover:text-blue-300`, success /
+      error / amber flash chips on `*-900` backgrounds with
+      `*-200` text and `*-800` borders, admin badge
+      `dark:bg-amber-900 dark:text-amber-200`, Phase 12 weekday
+      dots active `dark:bg-green-400` / off `dark:bg-slate-600`,
+      capacity "available" red `dark:text-red-400`, audit action
+      chips similarly remapped. `assets/css/input.css` needed no
+      edits — it carries no colour classes (only the
+      `.focus-auto-hidden` utility and the `.beamer-root`
+      typography block, both colour-free). Strict CSP stays
+      intact (theme-init.js is a standard `<script src>` under
+      `script-src 'self'`). Sign-out form block untouched — still
+      a native POST with the `_csrf` hidden input. Zero PHPUnit
+      — same pattern as Phases 10, 13, 14, 15; the change is CSS
+      class additions plus ~25 lines of vanilla JS without a unit
+      surface the existing harness can reach. Tests at 88 / 208.
+      ACCEPTANCE.md gains a "Phase 16 — Dark mode + light
+      contrast" section with the six scenarios from the plan
+      (light-band separation, toggle flip, reload persistence
+      under Network throttling + no FOUC, present view inherits
+      dark, admin-pages contrast sweep, private-window
+      localStorage denial fallback).
+
 ### Upcoming
 ### Upcoming
 
 
-- [ ] **Phase 16 — Dark-mode toggle + light-mode contrast cleanup**
-
-      **Problem.** Two things are slightly off in the current theme:
-        1. Every page uses `bg-slate-50` on `<body>` and the table
-           `<thead>` bands *also* use `bg-slate-50` — so the headers
-           blend into the page. Cards (white) pop, but table
-           headers vanish.
-        2. Users working in low-light rooms or on projectors/beamers
-           (Phase 15!) want a dark palette. Today there's no switch,
-           no CSS scoping for it, and Tailwind's dark variants are
-           disabled — adding `dark:` classes right now would be
-           dead weight.
-
-      **Goal.** One toggle, global per-browser, that flips the whole
-      app between a **light** and **dark** palette. In light mode,
-      push the page background one shade cooler so the table header
-      bands visibly separate from the page; in dark mode, pick a
-      neutral-slate ramp that's legible at a distance on a beamer
-      without being pure black. No system-preference auto-detect —
-      the user asked for manual control.
-
-      **Scope (plan).**
-
-      1. **Tailwind config (`tailwind.config.js`).**
-         - Add `darkMode: 'class'` so `dark:` variants activate only
-           when `<html class="dark">` is set. No change to the
-           content globs — the JIT already scans `views/**/*.php`
-           and `public/assets/js/**/*.js`, so every new `dark:bg-…`
-           class we type ends up in the compiled CSS.
-
-      2. **Theme init — new `public/assets/js/theme-init.js`.**
-         - Tiny synchronous script (≤ 6 lines, no deps). Reads
-           `localStorage.getItem('sp:theme')` and, if it is
-           `'dark'`, calls
-           `document.documentElement.classList.add('dark')`. Empty /
-           missing / any other value means light.
-         - Loaded from `<head>` **synchronously** (no `defer`, no
-           `async`) right before the stylesheet, so there's no FOUC
-           flash between bright and dark on page load.
-         - Same file is injected by both `views/layout.php` and
-           `views/sprints/present.php` (Phase 15 emits its own
-           `<!doctype html>`). Since the CSP already allows
-           `script-src 'self'`, the new file just needs a standard
-           `<script src>` tag — no inline, still CSP-clean.
-
-      3. **Light-mode rebalance (the user's explicit ask).**
-         - Body: `bg-slate-50 → bg-slate-100`. Slightly cooler page
-           gray.
-         - Table headers stay at `bg-slate-50`, so against the new
-           page background they now read as a distinct lighter band.
-           No structural change — just the body tint shifts.
-         - Card sections (`bg-white`) already pop above slate-100 —
-           unchanged.
-         - Hamburger-menu dropdown and sprint toolbar bg stay white
-           — the existing shadow + border already separate them.
-
-      4. **Dark-mode palette.**
-         - Ramp (all Tailwind defaults so they work with the vendored
-           build):
-             * body: `dark:bg-slate-900`
-             * cards / panels: `dark:bg-slate-800`
-             * table header bands: `dark:bg-slate-700`
-             * borders: `dark:border-slate-700`
-             * primary text: `dark:text-slate-100`
-             * secondary text: `dark:text-slate-400`
-             * inputs / selects / textareas:
-               `dark:bg-slate-800 dark:border-slate-600
-               dark:text-slate-100 dark:focus:ring-slate-500`
-             * links: `dark:text-blue-400 dark:hover:text-blue-300`
-             * success flash: `dark:bg-green-900 dark:text-green-200
-               dark:border-green-800`
-             * error flash: `dark:bg-red-900 dark:text-red-200
-               dark:border-red-800`
-             * admin badge: `dark:bg-amber-900 dark:text-amber-200`
-             * Phase 12 weekday dots: active
-               `dark:bg-green-400`, off `dark:bg-slate-600` (brighter
-               on-state so they still read from the back of a room)
-             * capacity "available" red:
-               `dark:text-red-400`
-         - Apply these in one sweep across every view plus
-           `public/assets/css/app.css`'s input classes (`input.css`
-           is the source; it's the only hand-edited CSS).
-
-      5. **Toggle UI — hamburger menu (Phase 14).**
-         - Above the `<hr>` that separates admin items from Sign out
-           in `views/layout.php`, insert a **Theme** row:
-           ```html
-           <button type="button" role="menuitem" data-theme-toggle
-                   class="w-full text-left px-3 py-2 text-sm
-                          text-slate-700 hover:bg-slate-50 flex
-                          items-center justify-between
-                          dark:text-slate-200 dark:hover:bg-slate-700">
-             <span>Theme</span>
-             <span data-theme-label class="text-slate-500
-                                           dark:text-slate-400">
-               Light
-             </span>
-           </button>
-           ```
-         - Non-admin users also get this row (not gated on
-           `$currentUser->isAdmin`) — theme is a personal preference,
-           not an admin action.
-         - The Phase 14 menu-controller in `app.js` already closes
-           the dropdown on any `role="menuitem"` click; we piggyback
-           on that and add a separate handler that flips the class.
-
-      6. **JS — extend `public/assets/js/app.js`.**
-         - ~10 new lines. On click of `[data-theme-toggle]`:
-           toggle `dark` on `<html>`, persist
-           `localStorage['sp:theme']` (`'dark'` or `'light'`),
-           update the `[data-theme-label]` text. On boot (after
-           theme-init.js already decided the initial state), read
-           the class state and stamp the label so the hamburger
-           opens with the correct word showing.
-
-      7. **Sweep — views to touch.**
-         - `views/layout.php` — body bg, header border/bg, hamburger
-           button colours, menu panel, badge, status chip, main
-           container.
-         - `views/home.php` — sprint-row hover states, empty
-           banner.
-         - `views/auth/local.php` — login card + inputs.
-         - `views/workers/index.php`, `views/users/index.php` —
-           list tables + buttons.
-         - `views/sprints/new.php`, `settings.php` — form cards,
-           inputs, "Apply" button, weekday checkboxes.
-         - `views/sprints/show.php` — Arbeitstage table, capacity
-           card, task toolbar, dropdowns, task table, weekday dots,
-           focus/reset buttons.
-         - `views/sprints/present.php` — same task-list markup as
-           show; reuses input.css. Also needs to include
-           theme-init.js in its own `<head>`.
-         - `views/audit/index.php` — filter pills, audit rows,
-           collapsible diffs (if present).
-         - `assets/css/input.css` — base form-control layer: add
-           `dark:` siblings for every `class` used by form inputs.
-
-      8. **Edge cases.**
-         - **FOUC on a slow connection.** theme-init.js is
-           synchronous + before the stylesheet, so the class is set
-           before any style resolves. Should look seamless. Test
-           with devtools throttling on the acceptance walkthrough.
-         - **Private-mode / localStorage denied.** `try/catch`
-           around the read and write; default to light.
-         - **Dark-mode beamer view.** People projecting in a
-           dim conference room will appreciate this most. Make sure
-           the Phase 15 present route picks up the class. Verified
-           by adding theme-init.js to present.php's own `<head>`.
-         - **Existing inline styles that use Tailwind colours**
-           (e.g. `bg-green-500` on the weekday dots) stay — we add
-           `dark:bg-green-400` next to them. Tailwind handles both
-           states from the same class list.
-         - **Third-party CSS — jQuery UI base theme.** The
-           code.jquery.com base CSS still loads from CDN and is not
-           dark-aware. jQuery UI only shows up during drag
-           operations (worker reorder, task reorder); its ghost
-           element looks slightly out of place in dark mode. Accept
-           and note in §10 deferred items.
-
-      9. **Tests.** Zero PHPUnit. The phase is CSS class additions
-         plus ~10 lines of vanilla JS — pattern-matches Phases 10,
-         13, 14, 15 which all skipped automated coverage. Manual
-         acceptance in `ACCEPTANCE.md` under a new
-         "Phase 16 — Dark mode + light contrast" section:
-           (a) fresh page load in light mode: body is cooler than
-               the table headers (visible band separation);
-           (b) click Theme in the hamburger menu → whole app flips
-               to dark; label reads "Dark";
-           (c) reload the page → dark persists, no flash of light
-               before styles apply;
-           (d) open `/sprints/{id}/present` in a new tab → picks up
-               dark too (theme-init.js included in its head);
-           (e) back to light mode, open Workers / Users / Audit /
-               Settings pages: no stray white-on-white or
-               unreadable text anywhere;
-           (f) private-window (localStorage denied) → defaults to
-               light, toggle no-ops without throwing.
-
-      **Out of scope.**
-      - `prefers-color-scheme` auto-detection. User explicitly
-        asked for manual control only.
-      - Per-user server-side theme preference (storing in the
-        `users` table). Browser-local is enough for now.
-      - High-contrast accessibility mode (WCAG AAA). Separate
-        concern; revisit if someone needs it.
-      - Re-theming jQuery UI. See edge cases; deferred to §10.
-      - A third "sepia" / "paper" mode. Two modes only.
-
-      **Spec alignment.**
-      - §3 layout: add `public/assets/js/theme-init.js` to the
-        `public/assets/js/` block.
-      - §4 / §5 / §7: no changes — no new schema, math, or audit
-        surface.
-      - §6 Routes: unchanged — CSS + JS only.
-      - §11 phpunit count stays at 88.
-      - Strict CSP (§ Phase 11) stays intact — theme-init.js is a
-        standard `<script src>` under `script-src 'self'`.
+Nothing scheduled.
 
 
 ## 10. Residual known gaps / deferred items
 ## 10. Residual known gaps / deferred items
 
 
@@ -572,6 +436,15 @@ with a `BOOTSTRAP_ADMIN` audit row.
   are E_DEPRECATED but still emit — harmless, and silenced by
   are E_DEPRECATED but still emit — harmless, and silenced by
   `ini_set('display_errors','0')` in production. Upstream library needs a
   `ini_set('display_errors','0')` in production. Upstream library needs a
   release.
   release.
+- **jQuery UI CDN CSS is not dark-aware.** The base theme on
+  `code.jquery.com/ui/1.13.3/themes/base/jquery-ui.css` is a light-only
+  stylesheet. It only shows up during drag operations (worker reorder on
+  `/sprints/{id}/settings`, sprint-worker / task reorder on
+  `/sprints/{id}`); the sortable ghost element reads slightly out of
+  place on a `dark:bg-slate-900` body. Accepted for now — the alternative
+  is self-hosting a custom jQuery UI theme inside the Docker css-builder
+  stage, which is a larger chunk of work than the cosmetic mismatch
+  warrants.
 - **Manual acceptance walkthrough** ([ACCEPTANCE.md](ACCEPTANCE.md))
 - **Manual acceptance walkthrough** ([ACCEPTANCE.md](ACCEPTANCE.md))
   hasn't been executed end-to-end by a human yet — it's a documentary
   hasn't been executed end-to-end by a human yet — it's a documentary
   follow-up that should happen in the running container.
   follow-up that should happen in the running container.
@@ -615,15 +488,13 @@ vendor/bin/phpunit
 Tell Claude:
 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–15
-> are shipped (see §9); Phase 16 (manual dark-mode toggle + light-
-> mode contrast cleanup) is planned but not implemented — the
-> scope is in §9 under Upcoming. Other outstanding items are in
-> §10 (mostly a human-run acceptance walkthrough in the running
-> container). If I ask you to work Phase 16, 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.
+> Read `HANDOFF.md`, the git log, and `ACCEPTANCE.md`. Phases 1–16
+> are shipped (see §9) — 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
+> maintenance rule in §14 — commit code, then commit a HANDOFF.md
+> update separately that marks the new work 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
 before acting — nothing here is load-bearing once it grows stale.
 before acting — nothing here is load-bearing once it grows stale.
@@ -631,6 +502,9 @@ before acting — nothing here is load-bearing once it grows stale.
 ## 13. Git history (as of this handoff)
 ## 13. Git history (as of this handoff)
 
 
 ```
 ```
+94b2841 Phase 16: dark-mode toggle + light-mode contrast cleanup
+0d7124a HANDOFF.md: add Phase 16 plan (dark-mode toggle + light-mode contrast)
+d4738d7 HANDOFF.md: note buildTaskRow owner-dropdown hotfix on Phase 10
 7c298d3 Fix: buildTaskRow owner dropdown was empty until a page refresh
 7c298d3 Fix: buildTaskRow owner dropdown was empty until a page refresh
 c70e442 HANDOFF.md: mark Phase 15 shipped
 c70e442 HANDOFF.md: mark Phase 15 shipped
 d1dda4f Phase 15: big-screen (beamer) task viewer at /sprints/{id}/present
 d1dda4f Phase 15: big-screen (beamer) task viewer at /sprints/{id}/present