Repo: clariumhealth/clarium-frontend
Branch: fix/recalls-v2-filter-persistence-clean
GitHub: https://github.com/clariumhealth/clarium-frontend/tree/fix/recalls-v2-filter-persistence-clean
PR: https://github.com/clariumhealth/clarium-frontend/pull/2482
Status: ✅ Implemented — awaiting review + merge
Prepared: 2026-04-08
The Recalls V2 queue (feature-flag gated at src/routes/recalls) was not persisting filters when a user navigated away and returned. This is now fixed. PR #2482 is open, based cleanly off dev, and ready for review + merge.
Alan asked to check out dev, pull latest, and create a feature branch for evaluation + bug fix off dev.
- Branch:
fix/recalls-v2-filter-persistencecreated offdev - Frontend repo:
~/clawd/clarium/repos/clarium-frontend
Alan reported the bug:
"We have an issue where we do not persist filters between navigation in the recalls queue. The recalls queue I am referring to is the flag-gated
src/routes/recallsqueue, call it v2. Persisting the filters is expected behavior and we have to fix it."
Reference point: PR #2479 — DRM v2 event queue — as an example of how filter persistence is done elsewhere.
Codebase investigation found two established patterns for filter persistence:
In recalls-index.route.tsx + utils.ts:
- A
atomWithStorageatom (recallParamsV2Atom) backed bylocalStorageviacreateCleaningStorage - The route loader manually calls
jotaiStore.get(recallParamsV2Atom)to read stored params - If URL has no params, redirects with stored params
- Writes new params back via
jotaiStore.set(recallParamsV2Atom, params) - Verbose, manual, error-prone. The
statusarray serialization (repeated?status=params) was identified as a likely edge case bug.
In src/utils/sync-route-params/sync-route-params-v2.ts:
- Generic
syncRouteParamsV2(request, { storageKey, schema })utility - Automatically: reads
localStorage, validates via Zod schema, redirects if no URL params, persists on navigation - Handles arrays (e.g.
status) viagetAll - Used by
disruption-monitor/items/items-index.route.tsxwith a single call
recallParamsV2Atom logic only writes to the atom when URL params are present. On nav-back, the URL is bare, the redirect fires — but edge cases around the status array (repeated params via p.append('status', s)) mean the round-trip through getDecodedStatusParams may not restore correctly. Components may also get out of sync.
Alan approved Option B — migrate to syncRouteParamsV2 (the idiomatic, current standard).
"Option B — Migrate to syncRouteParamsV2 (idiomatic, recommended) — yes feature branch and implement please"
A Claude Code agent was launched on the branch to implement the migration. The edits were applied to the working tree but not committed at that time.
Changes were committed and pushed. Initial PR #2481 was opened but closed immediately because the branch contained mixed commits from parallel activity-history work.
A clean branch (fix/recalls-v2-filter-persistence-clean) was created off the latest dev commit. The filter persistence commit was cherry-picked onto it cleanly.
PR #2482 opened: https://github.com/clariumhealth/clarium-frontend/pull/2482
Commit: cf33427e0 on fix/recalls-v2-filter-persistence-clean
| File | Change |
|---|---|
src/routes/recalls/utils.ts |
Added recallsV2FlatSchema (+22 lines) |
src/routes/recalls/recalls-index.route.tsx |
Replaced manual jotai/redirect loader with syncRouteParamsV2 (-107/+80) |
z.object({
sort, page, page_size, // pagination
string_search, // text search
status: z.array(z.string()), // multi-value — key fix
assignee, source, classification, urgency,
initiated_start, initiated_end,
posted_date_start, posted_date_end,
total_items_min, affected_items_min,
product_type, show_client_recalls,
})const synched = await syncRouteParamsV2(request, {
storageKey: 'recalls-v2',
schema: recallsV2FlatSchema,
});
if (synched.kind === 'redirect') throw redirect(synched.redirectTo);
const params = synched.params;
// ... use params directly| File | What to do |
|---|---|
src/routes/recalls/utils.ts |
Define recallsV2FlatSchema (Zod) — mirrors what DRM items has in table-utils.ts |
src/routes/recalls/recalls-index.route.tsx |
Replace manual jotai loader logic with syncRouteParamsV2 call |
(maybe) src/routes/recalls/filter-drawer-v2.tsx |
Check if it reads from recallParamsV2Atom directly — remove if so |
(maybe) src/routes/recalls/filter-pills-v2.tsx |
Same check |
Look at how DRM items does it:
src/routes/disruption-monitor/items/items-index.route.tsx— loader usagesrc/routes/disruption-monitor/items/table-utils.ts— flat schema definitionsrc/utils/sync-route-params/sync-route-params-v2.ts— the utility itself
The status filter is a multi-select that serializes as repeated URL params (?status=Open&status=In+Progress). syncRouteParamsV2 handles arrays via getAll. Your Zod schema must use z.array(z.string()) for status and the utility needs to pick it up correctly.
Look at how recallsV2FlatSchema should handle it — DRM items queue has a similar multi-value field to reference.
After migrating:
recallParamsV2Atom— remove fromutils.tsjotaiStore.get(recallParamsV2Atom)/jotaiStore.set(recallParamsV2Atom, ...)— remove from loader- Unused
jotaiStoreimport — clean up
- The
syncRouteParamsV2utility is the current standard — use it, don't re-invent localStoragekey for recalls v2 should be something like'recalls-v2'or'recall-queue-v2'(pick a stable, unique key)filter-drawer-v2.tsxandfilter-pills-v2.tsxmay or may not read from the atom — inspect before removing- TypeScript must pass (strict)
- Bug identified and reproduced
- Root cause understood
- Approach agreed (Option B —
syncRouteParamsV2) -
recallsV2FlatSchemadefined inutils.ts - Loader in
recalls-index.route.tsxmigrated tosyncRouteParamsV2 -
jotaiStore/recallParamsV2Atomremoved from loader - TypeScript clean (
tsc --noEmitno errors) - Committed to clean branch off latest
dev - PR #2482 open: https://github.com/clariumhealth/clarium-frontend/pull/2482
- PR review + merge
- QA verification: set filters → navigate to recall detail → back → filters preserved
Copy-paste this to hand off to the next coding agent:
You are implementing a bug fix in the `clariumhealth/clarium-frontend` repo on branch `fix/recalls-v2-filter-persistence`.
## The Bug
The Recalls V2 queue (feature-flag gated, lives at `src/routes/recalls/`) does not persist filters when navigating away and back. This is a regression vs. expected behavior.
## The Fix (already decided — implement Option B)
Migrate from the current manual Jotai pattern to `syncRouteParamsV2`, the codebase standard used by DRM items queue.
### Reference implementation to study first:
- `src/routes/disruption-monitor/items/items-index.route.tsx` — how the loader uses `syncRouteParamsV2`
- `src/routes/disruption-monitor/items/table-utils.ts` — how the flat Zod schema is defined
- `src/utils/sync-route-params/sync-route-params-v2.ts` — the utility itself
### What to change:
1. **`src/routes/recalls/utils.ts`** — Add `recallsV2FlatSchema` (Zod), mirroring DRM items' `flatSchema`. Must cover all filter params including the `status` array (multi-select, repeated URL params like `?status=Open&status=In+Progress`). Use `z.array(z.string())` for status.
2. **`src/routes/recalls/recalls-index.route.tsx`** — In the loader, replace the manual `jotaiStore.get(recallParamsV2Atom)` / `jotaiStore.set(recallParamsV2Atom, ...)` / `redirect()` logic with a single:
```ts
await syncRouteParamsV2(request, { storageKey: 'recalls-v2', schema: recallsV2FlatSchema });
-
Remove
recallParamsV2AtomandjotaiStoreusage from the loader (now handled by the utility). -
Check
filter-drawer-v2.tsxandfilter-pills-v2.tsx— if they read fromrecallParamsV2Atom, update them to read from URL params instead (look at how DRM components do it).
tsc --noEmit(TypeScript strict)- No runtime errors on navigation: go to recalls queue → set filters → navigate to a recall detail → go back → filters should still be set
- Commit with message:
fix(recalls): migrate filter persistence to syncRouteParamsV2 - Push to
fix/recalls-v2-filter-persistence - Open PR to
dev: titlefix(recalls): persist filters across navigation using syncRouteParamsV2
Repo: https://github.com/clariumhealth/clarium-frontend Branch: fix/recalls-v2-filter-persistence (base: dev)
---
*Handoff created by Sol (OpenClaw agent) on 2026-04-08*