fix(admin/pads): apply filter chip server-side, before pagination#7798
Conversation
Before: PadPage's filter chip (`active`/`recent`/`empty`/`stale`) ran
on the client AFTER the 12-row page slice was already on screen. On a
deployment with hundreds of pads it produced obviously wrong results
— click "empty pads" on page 1 with 100 empties and only the 0–12
empties within the current page passed the filter. thm reported this
on a 3.1.0 deployment.
Move the filter into `PadSearchQuery` so the `/settings` socket can
apply it before slicing:
1. pattern filter on names (cheap)
2. hydrate metadata for the matching pad universe iff a non-`all`
filter is set or a non-`padName` sort is requested
3. apply filter chip on the hydrated set
4. sort + slice → `total` reflects the filtered universe so the
pagination footer makes sense
The original handler also had a 4-way `if/else if` that duplicated the
hydrate-and-sort loop per `sortBy`. Folded those into one pipeline
with a single comparator switch.
Client side, `PadPage.tsx`:
- drop the client-side `filteredResults` filter (server already filters)
- chip click writes `filter` into searchParams (debounced refetch) and
resets `currentPage` to 0
- older clients that don't send `filter` keep working — server defaults
to `all`
Stats cards (totalUsers/activeCount/emptyCount) still count the visible
page only — that's a pre-existing UI limitation tracked separately.
Closes the regression thm reported.
Test plan
- `tsc --noEmit` clean (server + admin)
- New backend spec `padLoadFilter.ts` exercises filter:empty with
small `limit` to lock in the bug-fix, plus all/active/omitted cases
- `5 passing` locally on Node 25
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
Review Summary by QodoApply admin pads filter server-side before pagination
WalkthroughsDescription• Move filter chip logic server-side before pagination to fix regression - Filter now applied in /settings socket before offset/limit slice - total reflects filtered universe, pagination footer now correct • Refactor duplicated sort-by logic into single unified pipeline - Consolidate 4-way if/else into one comparator switch - Optimize metadata hydration only when needed (filter or non-name sort) • Client-side filter chip now writes to searchParams for round-trip - Chip click resets currentPage to 0 and triggers debounced refetch - Older clients without filter field gracefully default to 'all' • Add comprehensive backend test suite for filter regression - 5 test cases covering empty/active/omitted/all/recent scenarios Diagramflowchart LR
A["Client: Chip Click"] -->|"filter in searchParams"| B["Server: padLoad Handler"]
B -->|"1. Pattern Filter"| C["Candidate Names"]
C -->|"2. Decide Hydration"| D{"Filter or Non-Name Sort?"}
D -->|"Yes"| E["Hydrate All Metadata"]
D -->|"No"| F["Fast Path: Name Sort Only"]
E -->|"3. Apply Filter Chip"| G["Filtered Metadata"]
G -->|"4. Sort + Slice"| H["Results with Correct Total"]
F -->|"Sort + Slice"| H
H -->|"Pagination Footer Reflects Filtered Universe"| I["Client: Display Results"]
File Changes1. admin/src/utils/PadSearch.ts
|
Code Review by Qodo
1.
|
1. Functional setState updaters for every searchParams mutation (Qodo bug 1). The debounced pattern handler captured a render-time snapshot of searchParams; a faster chip click or sort change in between would be silently reverted when the debounce fired. Now every mutation merges against the latest state. 2. Concurrency-limited hydration (Qodo bug 3). The earlier draft issued Promise.all over the full candidate set, fanning out to thousands of in-flight padManager.getPad() reads on busy deployments. New mapWithConcurrency() caps concurrent loads at 16 — empirically enough to saturate a single ueberDB driver without pushing the event loop into back-pressure. 3. Test cleanup deletes the injected test-admin (Qodo bug 4). The original snapshot/restore pattern saved `settings.users` by reference; reassigning the same reference in after() left the inserted key in place and could leak into later backend specs. 4. Document the new `filter` field on the `padLoad` socket query in admin/README.md (Qodo rule violation 2). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Qodo's remaining Windows-with-Plugins (24) failure is the pre-existing Node 24 + Windows post-suite hard-kill flake — process exits 255 after the last completed test ( |
Summary
active/recent/stale.PadPage.tsx:81–88ranfilteredResults = pads.results.filter(...)on the already-paginated payload frompadLoad. Filter chip → pagination order was inverted.PadSearchQueryand apply it server-side in the/settingssocket'spadLoadhandler, beforeoffset/limit.totalthen reflects the filtered universe and the pagination footer makes sense.Server-side refactor
The old handler had a 4-way
if/else if (query.sortBy === …)that duplicated the hydrate-then-sort loop per sort key. Folded into one pipeline:allfilter or a non-padNamesort is requested (preserves the existing fast path for the default browse)totalbecomes the filtered totalClient-side
filteredResultsclient-side filterfilterintosearchParams(so it round-trips through the debounced refetch) and resetscurrentPageto 0filterkeep working — server defaults toallCompatibility
PadSearchQuery.filteris optional. Older clients (e.g. cached admin SPA) emit without it; server treats missing/unknown asall. No protocol break.Known limitation (out of scope)
The stats cards (
totalUsers/activeCount/emptyCount) sum from the current page slice — a pre-existing limitation; making them reflect the global universe needs a separate aggregate endpoint.Test plan
tsc --noEmitclean (server + admin)tests/backend/specs/admin/padLoadFilter.ts— 5 passing locally on Node 25:filter:"empty"returns only revisionNumber===0 from the full setfilter:"empty"withlimit=2still reportstotal=5(the regression thm hit)filteromitted → falls back toallfilter:"all"equals no-filter behaviourfilter:"active"excludes pads withuserCount === 0🤖 Generated with Claude Code