|
@@ -58,6 +58,7 @@ per-cell audit trail.
|
|
|
│ └── js/
|
|
│ └── js/
|
|
|
│ ├── theme-init.js # Phase 16: synchronous dark-class set from localStorage (no FOUC)
|
|
│ ├── 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
|
|
│ ├── 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-planner.js # /sprints/{id} Arbeitstage + task list
|
|
|
│ └── sprint-settings.js # /sprints/{id}/settings
|
|
│ └── sprint-settings.js # /sprints/{id}/settings
|
|
|
├── src/
|
|
├── src/
|
|
@@ -420,196 +421,71 @@ with a `BOOTSTRAP_ADMIN` audit row.
|
|
|
dark, admin-pages contrast sweep, private-window
|
|
dark, admin-pages contrast sweep, private-window
|
|
|
localStorage denial fallback).
|
|
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
|
|
### 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
|
|
## 10. Residual known gaps / deferred items
|
|
|
|
|
|
|
@@ -675,15 +551,13 @@ vendor/bin/phpunit
|
|
|
Tell Claude:
|
|
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
|
|
|
|
|
-> 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
|
|
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.
|
|
@@ -691,6 +565,9 @@ before acting — nothing here is load-bearing once it grows stale.
|
|
|
## 13. Git history (as of this handoff)
|
|
## 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
|
|
94b2841 Phase 16: dark-mode toggle + light-mode contrast cleanup
|
|
|
0d7124a HANDOFF.md: add Phase 16 plan (dark-mode toggle + light-mode contrast)
|
|
0d7124a HANDOFF.md: add Phase 16 plan (dark-mode toggle + light-mode contrast)
|
|
|
d4738d7 HANDOFF.md: note buildTaskRow owner-dropdown hotfix on Phase 10
|
|
d4738d7 HANDOFF.md: note buildTaskRow owner-dropdown hotfix on Phase 10
|