Repository navigation
Add the stale devices page - #36
Conversation
Design for issue #2: a page listing devices whose last sync is older than N days, with never-synced devices in their own block below.
Three tasks, each test first: the query, the table component, then the page, route and registries.
Drop the debounce, the local state and the effect: the URL is the only home of N. Fold the join assertions into the first query test and the relative-date cases into the first component test.
They are a standing inventory problem rather than today's call list, so the block shows its count and opens on a click. The fold lives in its own component, so the page keeps no state and the behaviour is tested.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Two syncs can share a timestamp, and the lateral had no tie-break, so the planner chose which one the page called the last user. The device's staleness was right either way, but the name to call could change between two loads. listRecentSyncs already guards this the same way. The never-synced devices all tie on a null last sync, so the serial is what orders that block; nothing pinned it. Three tests added.
useQuery is keyed on the threshold, so every keystroke swapped in an empty cache entry: the result block unmounted behind a spinner and the never-synced section, which owns its own open state, sprang back folded. placeholderData holds the previous rows until the next ones arrive. Also: the plural of one day, an integer-only number box, and the fold triangle hidden from screen readers, which announced it as a glyph.
The plan is the archive of what was built, and the fold and the review fixes had left it behind. ADR 0015 claimed the back button steps through thresholds; replace: true means it deliberately does not.
|
Review round done. No Critical findings; the three Important ones are fixed in 53c78c6, 7612328 and 07ca4e4.
Minors taken: the plural of "1 day", Minors left: the district 59 tests green, |
Closes #2.
What changes for the user
A new Stale devices page, at
/stale-devices, lists every device whose last sync is olderthan N days — oldest first, with its serial, facility, district, last sync and last user. A
supervisor can now see the absence of a sync, which
/syncsnever showed: a device that stoppedthree weeks ago simply drifted off the top of that list.
Devices that never synced have no sync row at all, so every join written so far dropped them.
They get their own block below, folded by default and showing its count: a standing inventory
problem rather than today's call list.
The number of days lives in the URL (
?days=14), so a supervisor can bookmark a view or send itto a colleague. A hand-edited or missing value falls back to 7 instead of erroring.
How it works
src/features/stale-devices/api/queries.ts. Each device's newest sync comes froma
LEFT JOIN LATERAL ... LIMIT 1: a subquery allowed to refer to the row it is joined to, theshape a Django developer writes with
SubqueryandOuterRef. It walks the existingdevice_sync (device_id, synced_at desc)index, yields exactly one row per device, andLEFTkeeps the never-synced devices with nulls.
nowis a parameter, nevernow()in the SQL, so atest can choose the date.
StaleDeviceTabletakes props and is rendered twice, once per block; a nulllastSyncedAtrenders as "never".NeverSyncedSectionowns the open/closed state, which keepsthe page free of state and puts the fold where the repository says to test it.
replace, so there is nouseState, nouseEffect, and the back button is not filled with every digit typed.src/features/router.tsand one insrc/features/nav.ts.Tests
Queries on an in-process Postgres, components in jsdom. The route, the page and the tRPC procedure
get none, per
CLAUDE.md. The boundary cases are the interesting ones: a device exactly at thecutoff (excluded — "older than", not "older or equal"), a device judged on its newest sync rather
than an older one, and a never-synced device returned whatever N is.
pnpm test— 56 tests, 15 files, green.pnpm exec tsc --noEmit— clean.ADRs
0015-page-state-lives-in-the-url.md0016-lateral-join-for-the-latest-row.mdOut of scope
Filtering by district or user (#17, #19), the map (#3), a device detail page (#6). No pagination:
the fleet is 200 devices, so the whole list fits.
Spec:
docs/superpowers/specs/2026-09-18-stale-devices-design.mdPlan:
docs/superpowers/plans/2026-09-18-stale-devices.md