diff --git a/docs/superpowers/specs/2026-07-09-collection-pool-nearby-sort-design.md b/docs/superpowers/specs/2026-07-09-collection-pool-nearby-sort-design.md new file mode 100644 index 0000000..9a7e0d2 --- /dev/null +++ b/docs/superpowers/specs/2026-07-09-collection-pool-nearby-sort-design.md @@ -0,0 +1,92 @@ +# Collection Pool: Nearby sort works on Ready/Pending tabs + +**Goal:** The `Nearby suppliers` sort should work the same on every filter tab (`All`, `Ready to collect`, `Pending`). The current code only produces a visible difference when supplier coordinates exist and cluster-sort keys differ. + +## Scope + +### In Scope + +- **Harden the Nearby sort** so it's visibly different from the Operational sort even when supplier coordinates are missing. Today, every row without coordinates gets `clusterSortKey = "zzz"`, so the cluster-diff is always 0 and the sort silently falls through to the operational sort. Make the difference explicit. +- **Smoke test** that the sort actually changes the order when `sortMode` flips from `operational` to `nearby`. Today there's no test for this; a row's `clusterSortKey` can vary by supplier but nothing pins the behaviour down. + +### Out of Scope + +- Changing what "Operational" sorts by (it sorts by assignment state + updated timestamp — that's fine). +- Adding a "ready first" sort variant. +- Anything to do with the supplier map page. + +## Current State + +`buildVisibleRows` in `src/app/(app)/collection-pool/view-model.ts` (lines 79–125) filters by `readyFilter` and then sorts. The sort code: + +```ts +.sort((a, b) => { + if (sortMode === "nearby") { + const clusterDiff = (a.clusterSortKey ?? "zzz").localeCompare(b.clusterSortKey ?? "zzz"); + if (clusterDiff !== 0) return clusterDiff; + } + // ... falls through to operational sort +}); +``` + +If a row has no `clusterSortKey`, both `a` and `b` get `"zzz"` and the cluster-diff is 0, so the sort silently degrades to operational. There's no test that catches this. + +The sort is also "stable" enough on tiny filtered sets that the user can't tell the two sort modes apart. On a real `Pending` tab with one row, the result is identical regardless of `sortMode`. + +## User Confirmed Rules + +- "Ready" — go ahead with the fix. + +## Behavior + +### Sort difference + +Even when supplier coordinates are absent, the two sort modes should be *visibly different* in the row order. The fix: when the user picks `Nearby` and a row has no `clusterSortKey`, demote the row to the END of the list (rather than the start, where `"zzz"` currently sorts it). This way: + +- Rows with coordinates group by cluster at the top (the existing behavior). +- Rows without coordinates go to the bottom in a single "no coordinates" tail. +- Operational sort stays exactly the same. + +The smallest change: when `sortMode === "nearby"` and a row's `clusterSortKey` is missing, treat its sort key as `"\uffff"` (a character that sorts after all printable text). Rows with coordinates bubble to the top; rows without sink to the bottom. The `localeCompare` order is deterministic. + +### Tie-breaker + +When two rows have the same `clusterSortKey`, the existing tie-breaker (operational sort by assignment state + updated timestamp) already keeps things stable. No change needed. + +## Testing + +### Unit tests (in `src/app/(app)/collection-pool/view-model.test.ts`) + +Three new tests: + +1. **Operational sort** is stable on the same input regardless of `readyFilter`. +2. **Nearby sort** groups rows by `clusterSortKey` ascending. +3. **Nearby sort** with mixed present/missing `clusterSortKey` puts rows without coordinates at the END of the list. + +Run: + +```bash +npm run test:run -- 'src/app/(app)/collection-pool/view-model' +``` + +Expected: existing tests + 3 new = all green. + +### Smoke check + +On the dev server, with the existing seed data: + +1. Open the Collection Pool page on `All`. Click `Nearby suppliers`. Rows with coordinates should reorder; rows without coordinates should sink to the bottom. +2. Switch to `Ready to collect`. Sort should still work. +3. Switch to `Pending`. Sort should still work. +4. Switch back to `Operational`. Sort should match the previous behavior (assignment state + updated). + +## Acceptance Criteria + +- `Nearby` sort produces a visibly different order from `Operational` on every filter tab. +- Rows without supplier coordinates consistently land at the end of the list under `Nearby` sort. +- All targeted tests pass; full suite non-regressive. + +## Risk / Rollback + +- Low risk. The change is one line in the sort comparator. Existing behaviour for rows with coordinates is unchanged. +- Rollback: revert the line. \ No newline at end of file