浏览代码

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 2 周之前
父节点
当前提交
0d738b26d2
共有 1 个文件被更改,包括 81 次插入207 次删除
  1. 81 207
      HANDOFF.md

+ 81 - 207
HANDOFF.md

@@ -56,7 +56,8 @@ per-cell audit trail.
 │   └── assets/
 │       ├── css/app.css         # GENERATED at image-build time (gitignored)
 │       └── 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-settings.js  # /sprints/{id}/settings
 ├── src/
@@ -360,205 +361,68 @@ with a `BOOTSTRAP_ADMIN` audit row.
       "Phase 15 — Big-screen viewer" section with the six manual
       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
 
-- [ ] **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
 
@@ -572,6 +436,15 @@ with a `BOOTSTRAP_ADMIN` audit row.
   are E_DEPRECATED but still emit — harmless, and silenced by
   `ini_set('display_errors','0')` in production. Upstream library needs a
   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))
   hasn't been executed end-to-end by a human yet — it's a documentary
   follow-up that should happen in the running container.
@@ -615,15 +488,13 @@ vendor/bin/phpunit
 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); 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
 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)
 
 ```
+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
 c70e442 HANDOFF.md: mark Phase 15 shipped
 d1dda4f Phase 15: big-screen (beamer) task viewer at /sprints/{id}/present