Skip to content

Instantly share code, notes, and snippets.

@adg29
Created April 8, 2026 13:09
Show Gist options
  • Select an option

  • Save adg29/8264cdb14188955f3f2ae051e2d4ec88 to your computer and use it in GitHub Desktop.

Select an option

Save adg29/8264cdb14188955f3f2ae051e2d4ec88 to your computer and use it in GitHub Desktop.
Recalls V2 Filter Persistence — Handoff Doc

Handoff: Recalls V2 Filter Persistence — fix/recalls-v2-filter-persistence

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


TL;DR

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.


Chronological Context

2026-04-07 (~20:09 UTC) — Setup

Alan asked to check out dev, pull latest, and create a feature branch for evaluation + bug fix off dev.

  • Branch: fix/recalls-v2-filter-persistence created off dev
  • Frontend repo: ~/clawd/clarium/repos/clarium-frontend

2026-04-07 (~20:15 UTC) — Problem Identification

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/recalls queue, 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.

2026-04-07 (~20:16 UTC) — Analysis

Codebase investigation found two established patterns for filter persistence:

Pattern 1 — Current (Recalls V2): Manual Jotai atom + redirect in loader

In recalls-index.route.tsx + utils.ts:

  • A atomWithStorage atom (recallParamsV2Atom) backed by localStorage via createCleaningStorage
  • 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 status array serialization (repeated ?status= params) was identified as a likely edge case bug.

Pattern 2 — Standard (DRM Items Queue): syncRouteParamsV2 utility

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) via getAll
  • Used by disruption-monitor/items/items-index.route.tsx with a single call

Root Cause of the Bug

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.

2026-04-07 (~21:57 UTC) — Decision

Alan approved Option B — migrate to syncRouteParamsV2 (the idiomatic, current standard).

"Option B — Migrate to syncRouteParamsV2 (idiomatic, recommended) — yes feature branch and implement please"

2026-04-07 (~21:58 UTC) — Agent Launched

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.

2026-04-08 (~12:43 UTC) — Committed + PR Opened

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


Implementation Summary

Commit: cf33427e0 on fix/recalls-v2-filter-persistence-clean

Files Changed

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)

What the schema covers (recallsV2FlatSchema)

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,
})

What the loader now does

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

Implementation Plan (for reference — already done)

Files to touch

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

Reference Implementation

Look at how DRM items does it:

  • src/routes/disruption-monitor/items/items-index.route.tsx — loader usage
  • src/routes/disruption-monitor/items/table-utils.ts — flat schema definition
  • src/utils/sync-route-params/sync-route-params-v2.ts — the utility itself

Key Tricky Bit: status Array

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.

Atoms to Remove

After migrating:

  • recallParamsV2Atom — remove from utils.ts
  • jotaiStore.get(recallParamsV2Atom) / jotaiStore.set(recallParamsV2Atom, ...) — remove from loader
  • Unused jotaiStore import — clean up

Assumptions

  • The syncRouteParamsV2 utility is the current standard — use it, don't re-invent
  • localStorage key for recalls v2 should be something like 'recalls-v2' or 'recall-queue-v2' (pick a stable, unique key)
  • filter-drawer-v2.tsx and filter-pills-v2.tsx may or may not read from the atom — inspect before removing
  • TypeScript must pass (strict)

What's Done

  • Bug identified and reproduced
  • Root cause understood
  • Approach agreed (Option B — syncRouteParamsV2)
  • recallsV2FlatSchema defined in utils.ts
  • Loader in recalls-index.route.tsx migrated to syncRouteParamsV2
  • jotaiStore / recallParamsV2Atom removed from loader
  • TypeScript clean (tsc --noEmit no errors)
  • Committed to clean branch off latest dev
  • PR #2482 open: https://github.com/clariumhealth/clarium-frontend/pull/2482

What's NOT Done

  • PR review + merge
  • QA verification: set filters → navigate to recall detail → back → filters preserved

Agent Prompt

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 });
  1. Remove recallParamsV2Atom and jotaiStore usage from the loader (now handled by the utility).

  2. Check filter-drawer-v2.tsx and filter-pills-v2.tsx — if they read from recallParamsV2Atom, update them to read from URL params instead (look at how DRM components do it).

Must pass:

  • 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

Then:

  • Commit with message: fix(recalls): migrate filter persistence to syncRouteParamsV2
  • Push to fix/recalls-v2-filter-persistence
  • Open PR to dev: title fix(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*
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment