Ver Fonte

HANDOFF.md: mark Phase 17 shipped

Move the Phase 17 entry from §9 Upcoming → Shipped with SHA
`b457896`. Reset §9 Upcoming to the "Nothing scheduled"
template. Add `public/assets/js/number-stepper.js` to the §3
`public/assets/js/` block. Append `0d738b2` (Phase 16 ship
marker, missed in that commit's own HANDOFF pass), `712bcc5`
(Phase 17 plan), and `b457896` (Phase 17 code) to the §13 git
history. Rewrite §12's resume prompt — Phases 1–17 shipped, no
scheduled work — mirroring the "post-Phase-16" template from
the `0d738b2` commit. Test count stays at 88 per §11; no new
§10 gaps introduced by Phase 17.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
achiappa há 2 semanas atrás
pai
commit
735aa4f280
1 ficheiros alterados com 74 adições e 197 exclusões
  1. 74 197
      HANDOFF.md

+ 74 - 197
HANDOFF.md

@@ -58,6 +58,7 @@ per-cell audit trail.
 │       └── js/
 │           ├── 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
+│           ├── number-stepper.js   # Phase 17: custom popover stepper for [data-stepper] number inputs
 │           ├── sprint-planner.js   # /sprints/{id} Arbeitstage + task list
 │           └── sprint-settings.js  # /sprints/{id}/settings
 ├── src/
@@ -420,196 +421,71 @@ with a `BOOTSTRAP_ADMIN` audit row.
       dark, admin-pages contrast sweep, private-window
       localStorage denial fallback).
 
+- [x] **Phase 17 — Hide native number spinners + custom 0.5-step
+      stepper popover** (`b457896`). Three classes of number input —
+      day cells, RTB cells, task assignment cells — deal in half-day
+      increments (or 0.05 for RTB). Browsers rendered each as
+      `<input type="number">` with tiny native up/down spinner
+      arrows: visually noisy in a dense table, inconsistent across
+      Chrome / Firefox / Safari, useless on touch. Hidden app-wide
+      via a two-rule `@layer base` block in `assets/css/input.css`
+      (`-webkit-*-spin-button { appearance: none; }` +
+      `-moz-appearance: textfield`). Week-count and reserve-percent
+      inputs lose arrows too — fine, keyboard typing is their usual
+      path. For the three opt-in cell types, a new
+      `public/assets/js/number-stepper.js` (~180 lines, single IIFE,
+      vanilla JS, no jQuery dep) delegates `click` + `focusin` on
+      `document` to `input[data-stepper]`; on match, lazily builds a
+      single `.stepper-popover` DOM node (role="dialog", `−` /
+      `<output>` / `+` / hidden `<input type="range">`) and anchors
+      it next to the bound input. Reads `step` / `min` / `max` off
+      the input (default step=1); when both min and max parse as
+      finite, un-hides the range slider and wires it; otherwise
+      `[data-assign]` gets just the +/− buttons. `clampToStep(current,
+      delta, step, min, max)` pure helper — adds delta, clamps to
+      [min,max] when finite, quantises via `Math.round(next/step) *
+      step` with a ~1e-9 epsilon tolerance so `0.6 + 0.05 = 0.65`
+      lands cleanly. Every mutation mirrors into `input.value` and
+      dispatches a bubbling synthetic `input` event, so
+      sprint-planner.js's existing recomputeRow / row-total handlers
+      fire live. On popover close (outside `pointerdown`, Escape,
+      Tab-away, or clicking a *different* stepper input) the helper
+      dispatches `change` — the existing debounced save pipeline
+      (PATCH `/sprints/{id}/week-cells`, `/workers/{sw_id}`, or
+      `/tasks/{id}/assignments`) fires once, same audit semantics as
+      typed edits. ArrowUp / ArrowDown while focused on the input
+      step by `step` (restores the shortcut the CSS reset just
+      disabled). Position: below the input with a 4px gap unless
+      the input sits in the lower 25% of the viewport, then above;
+      horizontal clamp to viewport with 4px margins. Outside-click
+      uses `pointerdown` (not `click`) so a Safari scroll gesture
+      starting inside the popover doesn't dismiss it. `data-stepper`
+      stamped on the admin-branch `[data-day]` / `[data-rtb]` /
+      `[data-assign]` inputs in `views/sprints/show.php`, the
+      `[data-rtb]` input in `views/sprints/settings.php`, the
+      `[data-assign]` input in `views/sprints/present.php`, and the
+      JS-built assignment cells in `sprint-planner.js::buildTaskRow`
+      — non-admin `<span>` branches stay clean. Popover styled in
+      `assets/css/input.css` under `@layer components` with
+      Tailwind `@apply` on the Phase 16 palette (bg-white /
+      dark:bg-slate-800 + slate-100/200/600/700 hover/border/text
+      siblings + accent-slate-600/400 on the range). Both
+      `views/layout.php` and `views/sprints/present.php` load the
+      new module via `<script src="/assets/js/number-stepper.js"
+      defer>` after sprint-planner.js — strict CSP stays intact, no
+      inline handlers, no new external hosts. Zero PHPUnit — pure
+      CSS + vanilla JS over existing markup, same pattern as Phases
+      10 / 13 / 14 / 15 / 16. Tests at 88 / 208. ACCEPTANCE.md
+      gains a "Phase 17 — Number stepper popover" section with the
+      five scenarios from the plan (no native arrows on any number
+      input, day-cell stepper at 0.5 step, RTB stepper at 0.05
+      step, task-assignment stepper on both show and present views
+      with no range slider because no max, Escape + outside-click +
+      dark-mode polish).
+
 ### Upcoming
 
-- [ ] **Phase 17 — Hide native number spinners + custom 0.5-step stepper popover**
-
-      **Problem.** Three classes of number input in the app all deal in
-      **half-day increments**:
-        1. the Arbeitstage per-worker day cells on `/sprints/{id}`
-           (`[data-day]`, 0..5 step 0.5);
-        2. the task assignment cells on `/sprints/{id}` and
-           `/sprints/{id}/present` (`[data-assign]`, ≥ 0 step 0.5);
-        3. the per-worker RTB cells (`[data-rtb]`, 0..1 step 0.05).
-      Browsers render these as `<input type="number">` with native
-      up/down spinner arrows attached — the arrows are small, visually
-      noisy inside a dense table, and behave inconsistently across
-      Chrome / Firefox / Safari. On touch devices they often do
-      nothing. The team would rather see a clean number on the page
-      and get a tap-friendly stepper on click.
-
-      **Goal.** Hide every native spinner across the app, and for the
-      three half-step (or 0.05-step for RTB) cell types, show a small
-      **popover stepper** when the input is clicked/focused. The
-      popover has a large current-value readout, `−` / `+` buttons
-      that step by the input's own `step` attribute, and — when
-      `min` and `max` are both set — a range slider for quick gross
-      moves. Typing numerically still works; the popover is an
-      alternative input method, not a replacement.
-
-      **Scope (plan).**
-
-      1. **CSS — `assets/css/input.css`.**
-         Hide the native spinner on every `input[type="number"]`. Two
-         rules under `@layer base`:
-         ```css
-         input[type="number"]::-webkit-outer-spin-button,
-         input[type="number"]::-webkit-inner-spin-button {
-             -webkit-appearance: none;
-             margin: 0;
-         }
-         input[type="number"] {
-             -moz-appearance: textfield;
-             appearance: textfield;
-         }
-         ```
-         Applies app-wide — the week-count and reserve-percent inputs
-         on Settings / New-sprint also lose their arrows, which is
-         fine; they're edited rarely and keyboard typing is the usual
-         path.
-
-      2. **Opt-in attribute — `data-stepper`.**
-         Only the half-day cells get the popover. Stamp
-         `data-stepper` on:
-           - `[data-day]` in `views/sprints/show.php` (Arbeitstage
-             per-worker day cells; admin-editable).
-           - `[data-rtb]` in `views/sprints/show.php` and
-             `views/sprints/settings.php` (admin-editable).
-           - `[data-assign]` in `views/sprints/show.php` and the
-             JS-built row in `public/assets/js/sprint-planner.js`
-             (`buildTaskRow`); same data-stepper on the template.
-         Don't stamp it on `n_weeks` / `reserve_fraction` —
-         keyboard-only is right there.
-
-      3. **New JS module — `public/assets/js/number-stepper.js`.**
-         Vanilla JS, ≤ 120 lines, loaded with `defer` from
-         `views/layout.php` and `views/sprints/present.php` **after**
-         `sprint-planner.js`. Exposes no globals; single IIFE.
-         - On `document`, delegate `click` + `focus` on
-           `input[data-stepper]` → open the popover anchored to that
-           input. Only one popover open at a time; clicking another
-           stepper input moves it.
-         - Popover DOM (built once, reused):
-           ```
-           <div class="stepper-popover" hidden role="dialog"
-                aria-label="Set value">
-             <button data-stepper-dec aria-label="Decrease">−</button>
-             <output data-stepper-value>0</output>
-             <button data-stepper-inc aria-label="Increase">+</button>
-             <input type="range" data-stepper-range hidden>
-           </div>
-           ```
-           Styled with Tailwind utility classes in the template (or a
-           `@layer components` block in `input.css` so they compile).
-           Rounded card, shadow-lg, 1px border; `dark:bg-slate-800`
-           etc. for Phase 16 compatibility.
-         - Reads `min`, `max`, `step` from the bound input. When
-           both `min` and `max` are finite numbers, un-hide the
-           `<input type="range">` and wire it to set the value on
-           `input` event. Otherwise only the +/− buttons show.
-         - `+` / `−` mutate `input.value` via a small pure helper
-           `clampToStep(current, delta, step, min, max)` — step up
-           or down by exactly `step`, clamp into `[min, max]` when
-           defined, and quantise with
-           `Math.round(v / step) * step` (with a tiny float-epsilon
-           tolerance to avoid `0.30000000000000004` artefacts).
-         - After every value mutation, dispatch a synthetic `input`
-           event (for live recompute in sprint-planner.js) and —
-           only on popover close — dispatch `change` (to fire the
-           existing debounced save pipeline).
-         - Close on: outside-click, Escape (focus returns to the
-           input), Tab that leaves the popover + input, and a second
-           click on a *different* stepper input (auto-reopens
-           there).
-         - Position: below the input unless the input sits in the
-           bottom ~25% of the viewport, then above. Horizontal clamp
-           to viewport with a 4px margin.
-         - Keyboard while focused on the input: ArrowUp / ArrowDown
-           also step by `step` (replaces the native spinner's
-           behaviour that we just disabled). That way keyboard
-           users don't lose a shortcut.
-
-      4. **Integration with existing save logic.**
-         `sprint-planner.js` already listens for
-         `blur change` on `[data-day]` / `[data-rtb]` /
-         `[data-assign]`. The stepper dispatches `change` on
-         popover-close (and mirrors the value into `input.value`
-         before dispatching). No edits required in `sprint-planner.js`
-         except verifying that it reads `.value` every time the
-         handler fires — spot-check current code:
-         `recomputeRow()` and `queueCell()` both do `Number($el.val())`,
-         so we're fine.
-
-      5. **Dark mode.**
-         The popover uses Tailwind utility classes with `dark:`
-         siblings matching Phase 16 conventions
-         (`bg-white dark:bg-slate-800`, border `slate-200 /
-         slate-600`, text `slate-900 / slate-100`, hover `slate-100
-         / slate-700`). Range slider uses
-         `accent-slate-600 dark:accent-slate-400` so the Tailwind
-         `accent-color` picks up theme-appropriate thumb colour.
-
-      6. **Edge cases.**
-         - **Read-only cells (non-admin users).** Non-admin users
-           see `<span class="font-mono">` rather than an `<input>`
-           for these cells (see `show.php`). No stepper needed; the
-           attribute simply doesn't get stamped.
-         - **Firefox range-input support.** `<input type="range">`
-           with float `step=0.5` is well-supported; quantisation
-           already handled in `clampToStep()`.
-         - **Safari momentum scroll mid-popover.** Outside-click
-           handler uses `pointerdown` so a scroll gesture that
-           starts inside the popover doesn't immediately close it.
-         - **iOS tap-then-type.** Tapping a number input opens the
-           popover; the native keyboard does NOT auto-appear. To
-           type, focus the input and start typing — still works.
-         - **Batch paste of non-numeric text.** Input remains a
-           native `type="number"`, so browsers reject non-numeric
-           pastes as today.
-         - **Concurrent popover + jQuery UI drag.** The worker /
-           task row drag handlers (`handle` span) are separate from
-           number inputs — no conflict.
-
-      7. **Tests.** No PHPUnit — pure CSS + vanilla JS over existing
-         markup. Add a handful of cheap pure-JS unit assertions as
-         a *sanity matrix* only if `clampToStep` is extracted to
-         be testable without the DOM. Practically: skip, same
-         pattern as Phases 10 / 13 / 14 / 15 / 16.
-         ACCEPTANCE.md gains a
-         "Phase 17 — Number stepper popover" section with five
-         scenarios:
-           (a) no native spinner arrows visible on any number input;
-           (b) clicking a day cell pops the stepper; +/−
-               increments by 0.5; value persists after blur;
-           (c) RTB cell pops the stepper; +/− increments by 0.05;
-           (d) task assignment cell in both `/sprints/{id}` and
-               `/sprints/{id}/present` pops the stepper; no min
-               no range slider shown;
-           (e) Escape closes the popover and returns focus;
-               outside-click closes too; dark mode matches the
-               theme.
-
-      **Out of scope.**
-      - Touching non-half-step inputs (`n_weeks`, `reserve_fraction`)
-        beyond hiding the native arrows.
-      - Keyboard-shortcut stepping from anywhere on the page —
-        ArrowUp/Down is scoped to the focused input only.
-      - Replacing `<input type="number">` with a contenteditable
-        div or a third-party picker library.
-      - Undo/redo stack per cell.
-      - Long-press or drag-to-scroll on the buttons for fast
-        stepping. +/− buttons only step by one `step` per click.
-
-      **Spec alignment.**
-      - §3 layout: add `number-stepper.js` to the `public/assets/js/`
-        block.
-      - §4 / §5 / §7: no changes.
-      - §6 Routes: unchanged.
-      - §11 phpunit count: stays 88.
-      - Strict CSP (Phase 11) stays intact — standard `<script src>`,
-        no inline handlers.
-      - Dark mode (Phase 16): popover uses `dark:` variants
-        throughout.
+Nothing scheduled.
 
 ## 10. Residual known gaps / deferred items
 
@@ -675,15 +551,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–16
-> are shipped (see §9); Phase 17 (hide native number spinners + add
-> a custom 0.5-step stepper popover on day / RTB / assignment cells)
-> is planned but not implemented — scope in §9 Upcoming. Other
-> outstanding items are in §10 (human-run acceptance walkthrough,
-> jQuery UI dark-mode cosmetic gap). If I ask you to work Phase 17,
-> 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–17
+> 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.
@@ -691,6 +565,9 @@ before acting — nothing here is load-bearing once it grows stale.
 ## 13. Git history (as of this handoff)
 
 ```
+b457896 Phase 17: hide native number spinners + custom 0.5-step stepper popover
+712bcc5 HANDOFF.md: add Phase 17 plan (number-stepper popover + hide native spinners)
+0d738b2 HANDOFF.md: mark Phase 16 shipped
 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