Replaces the two native date inputs behind Custom with a picker in the style of Fathom's: eleven presets down the left beside two month calendars, an inline range highlight, and a live preview as you drag from one day to the other.
Why
The old control was two <input type="date"> fields with no presets and no calendar, and the mobile audit found its popover landed off-screen at phone width (dashboard.stx:2242). This is also the datepicker style requested directly.
How it fits the existing model
- The URL stays the only state. Presets the server already buckets —
7d,30d,90d,1y,all— go out as?range=, so the chart keeps its bucket-granularity logic. A hand-picked span goes out as?from=&to=, exactly what the server block already parses (isCustom). Nothing about how a range is read changes. - No new DOM access. The picker reads the current params through
useRoute().queryand writes withnavigate()— it touches nowindow/document/locationand trips none of stx's DOM guards (the one match in the file is a comment). - Self-contained logic. The date math — calendar grid, day arithmetic across DST and year boundaries, preset resolution, and the query it builds — lives between
#region puremarkers so the unit test evaluates exactly the shipped text, not a copy.
Mobile
At ≤1023px the second calendar drops to one, the presets become a wrapped chip row, and the panel is bounded by the visible viewport (max-height: calc(100dvh - 6rem), overflow-y: auto, overscroll-behavior: contain) — the same treatment as the merged nav fixes.
Verification
tests/unit/date-range-picker.test.ts— 15 cases: 42-cell grid (leading Sunday, leap Feb, Dec→Jan rollover, future-day flag), day shifts across both US DST changes and year ends, all eleven presets (including last-month on Jan 1), and the exact query string produced (custom replaces preset and vice-versa;site/filters/comparepreserved). 90/90 across this plus the payload-hygiene and fathom-import suites. pickier clean.- Rendered against the built page on an unguarded harness (since the authenticated dashboard needs an API backend that isn't available standalone): the runtime expands the
x-forover thederived()month to 84 cells across two calendars, marks today, disables future days, and fills a?from&torange with solid endpoints — confirmed in light and dark, and the phone layout collapses to one calendar with chip presets and no horizontal overflow.
The one thing I could not do here is click through the picker inside the real dashboard, because that page's client auth-gate redirects to sign-in without an /api/me backend locally — the same limit noted on the recent dashboard PRs. The logic is covered by unit tests and the runtime rendering by the harness.