|
@@ -422,7 +422,194 @@ with a `BOOTSTRAP_ADMIN` audit row.
|
|
|
|
|
|
|
|
### Upcoming
|
|
### Upcoming
|
|
|
|
|
|
|
|
-Nothing scheduled.
|
|
|
|
|
|
|
+- [ ] **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.
|
|
|
|
|
|
|
|
## 10. Residual known gaps / deferred items
|
|
## 10. Residual known gaps / deferred items
|
|
|
|
|
|
|
@@ -489,12 +676,14 @@ 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–16
|
|
> 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.
|
|
|
|
|
|
|
+> 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.
|
|
|
|
|
|
|
|
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.
|