Browse Source

HANDOFF.md: add Phase 16 plan (dark-mode toggle + light-mode contrast)

Plan-only commit. Phase 16 in §9 Upcoming covers a manual (no auto
system-preference) light / dark theme switch living in the hamburger
menu from Phase 14. Tailwind gets `darkMode: 'class'`; a new
public/assets/js/theme-init.js (synchronous, before the stylesheet)
reads localStorage['sp:theme'] and stamps `dark` on <html> with no
FOUC. Light mode shifts body bg from slate-50 → slate-100 so table
headers (still slate-50) read as a distinct band — the user's
explicit contrast ask. Dark palette: slate-900 body / slate-800 cards
/ slate-700 headers / brighter on-state weekday dots for beamer
legibility. Present view picks up the theme through its own <head>
(same theme-init script). Zero PHPUnit — CSS + ~10 lines JS, same
pattern as Phases 10/13/14/15. §12 resume prompt updated so a fresh
session sees Phase 16 as scheduled.

No code changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
achiappa 2 weeks ago
parent
commit
0d7124a740
1 changed files with 205 additions and 8 deletions
  1. 205 8
      HANDOFF.md

+ 205 - 8
HANDOFF.md

@@ -362,8 +362,203 @@ 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 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'`.
 
 ## 10. Residual known gaps / deferred items
 
@@ -421,12 +616,14 @@ Tell Claude:
 
 > 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); 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
-> → Shipped with its SHA.
+> 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.
 
 Claude should verify what's described here against actual repo state
 before acting — nothing here is load-bearing once it grows stale.