|
|
@@ -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.
|