Switch custom date range picker to two-click calendar selection#20
Conversation
- Replace custom tab date inputs with embedded calendar range picking - Show pending from/to endpoint state and commit only complete ordered ranges - Add browser coverage for calendar-only custom selection and swapped clicks
roborev: Combined Review (
|
Review finding on 51537a4: seed() unconditionally cleared customPending, so a controlled incomplete custom selection (from set, to empty — a state the trigger label already supports via customRangeLabel) reopened with the From endpoint armed. The next calendar click then overwrote the existing start instead of completing the range the consumer handed in, silently discarding their chosen start date. Seeding now arms the To endpoint whenever from is set without to, and the custom tab's month anchors to the start date in that state (falling back to today only when both ends are empty), so reopening lands on the month the pick began in. Validation: svelte-check 0/0, bun test checks/ (143), fmt:check, the 3 date-range-picker browser specs. Generated with Claude Code (claude-fable-5) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
roborev: Combined Review (
|
Review finding on c02a37d: the first custom-tab click is deliberately local — emitting {from, to: ""} through onSelect would push an unresolvable range into consumers that reload data on every commit — but seed() reseeded from the committed selection on every open, so an Escape or outside click between the two picks silently threw away the chosen start date. seed() now returns early onto the Custom tab while a draft is pending, so the start survives dismiss/reopen. The draft is keyed to the selection object it was started against and is dropped when the controlled selection changes while closed, so an externally swapped-in range can never resurface a stale start date. Validation: svelte-check 0/0, bun test checks/ (143), fmt, the 4 date-range-picker browser specs including a new dismiss/reopen spec. Generated with Claude Code (claude-fable-5) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
roborev: Combined Review (
|
…ontract Roborev findings on 51537a4 and 118b0ef. The stale-draft guard compared the controlled selection by object identity, silently assuming replace-only consumers: a parent mutating a $state selection in place while the popover was closed would keep a stale draft alive, and one recreating an equivalent object would drop a draft the user still wanted. The guard now snapshots a canonical value key of the selection when the draft starts and compares by value on reopen, so neither consumer style misbehaves. The docs still promised onSelect 'on every edit', which the two-click custom flow deliberately broke — the first click is internal so consumers that reload data per commit never see an unresolvable half-range. The controlled contract now reads commit-only, selection is documented replace-only, and the mid-pick draft lifecycle (survives tab switches and dismiss/reopen, cleared by any commit, dropped on external value change) is spelled out instead of implied. Skipped: a browser spec for the external-selection-change-while-closed path — the demo only changes selection through the picker itself, so that path isn't drivable without demo-only test hooks. Validation: svelte-check 0/0, bun test checks/ (143), fmt, the 5 date-range-picker browser specs including a new tab-switch-while- pending spec. Generated with Claude Code (claude-fable-5) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
roborev: Combined Review (
|
Review finding on 52add7c: seed()'s full-reseed path could arm a pending custom draft (from a controlled incomplete custom selection) while leaving pendingFor pointing at an OLDER draft's key. The sequence that breaks: draft started under selection A keys pendingFor=key(A); the app swaps in an incomplete custom selection B while closed, so reopen reseeds and re-arms customPending from B but the key still reads key(A); the app then swaps back to A, and the next reopen sees key(A)===pendingFor and adopts B's stale customFrom/customTo as A's draft. seed() now sets pendingFor = customPending ? selectionKey(selection) : null on the reseed path too, so the key always tracks the selection the armed draft actually belongs to. Adds demo hooks (External buttons that replace the committed selection while closed) and three browser specs the external-change paths were previously undrivable without: draft dropped on a differing external selection, the stale-key regression above, and a controlled incomplete custom selection reopening armed to complete. Validation: svelte-check 0/0, bun test checks/ (143), fmt:check, lint, check:usage 0 findings, the 8 date-range-picker browser specs. Generated with Claude Code (claude-fable-5) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
roborev: Combined Review (
|
Review found that the reopen guard did not cover controlled selection changes while the DateRangePicker popover stayed open. In that state, the next custom calendar click could complete a draft that belonged to the old selection and overwrite the newer parent-controlled range. The pending draft is now rechecked against the current selection key at completion time, so a mismatch restarts the custom draft instead of committing stale bounds. Validation: date-range-picker Playwright spec with a temporary isolated config on port 4298; bun run check; bun test checks/. Generated with Codex (GPT-5) Co-authored-by: Codex <noreply@openai.com>
roborev: Combined Review (
|
Review found that checking a pending draft only on reopen or completion misses a closed-popover A-to-B-to-A sequence: once the parent briefly controls a different selection, the old draft no longer belongs to the restored key. The picker now clears pending draft state as soon as the controlled selection diverges from pendingFor, so a later return to the same key cannot resurrect an old start date. Validation: date-range-picker Playwright spec with a temporary isolated config on port 4298; bun run check; bun test checks/. Generated with Codex (GPT-5) Co-authored-by: Codex <noreply@openai.com>
roborev: Combined Review (
|
Review found that invalidating a stale pending draft only cleared the internal pending flags. When the panel was still open, the Custom tab could continue showing the dropped start date and one-day highlight even though the controlled selection had already changed. Invalidation now reseeds the custom readout and calendar state from the current controlled selection while leaving the active tab alone, so the open panel reflects the external range immediately and still treats the next click as a fresh pick. Validation: date-range-picker Playwright spec with a temporary isolated config on port 4298; bun run check; bun test checks/. Generated with Codex (GPT-5) Co-authored-by: Codex <noreply@openai.com>
roborev: Combined Review (
|
Review found that draft invalidation reseeded only the Custom tab's fields. With the panel open, calUnit/calAnchor/calMonth kept the pre-drift selection's values, so switching to the Calendar tab after an external selection change showed the old period — and a unit-pill click would commit that stale anchor over the newer parent-controlled range. Invalidation now runs the same full reseed the popover performs on open (extracted as reseedFromSelection), minus the tab switch: the active tab is deliberately preserved so an open panel doesn't jump tabs under the user. Validation: full Playwright suite (47 passed) including a new drift-to-calendar regression spec driven by a new demo hook; bun run check; bun test; fmt/lint clean. Generated with Claude Code (claude-fable-5) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
Fixed in e4bb046: draft invalidation now runs the same full reseed as popover open (extracted as generated by a clanker |
roborev: Combined Review (
|
Summary
Testing