The re-review of the fix round surfaced 4 findings; fixing the coverage one
exposed that the C1 migration was ineffective:
- FeedFiltersProvider.loadAccountFilters: run coerceTagModes on the RAW stored
blob, not the object merged over DEFAULT_FILTERS. The merge injects
DEFAULT_TAG_MODES (all "or"), which coerceTagModes prefers over the legacy
scalar `tagMode` — so the migration never fired. Now a persisted legacy
tagMode:'and' actually migrates into tagModes.topic (proven by a new test).
- test_feed_views: the tag_mode_other=and case was vacuous (only chC carried
either "other" tag, so AND == OR). Give chA an "other" tag too so OR-other
{chA,chC} differs from AND-other {chC} — the conjunctive path now narrows.
- DateFilterSelect: reset the custom reveal in useLayoutEffect (not useEffect)
so the correction commits before paint — no one-frame "Custom range" flicker
on the trigger after an external clear.
- SavedViewsProvider: extract the sticky-view tie-break to a pure, tested
lib/savedViews.ts (preferSticky).
New tests: savedViews (preferSticky), FeedFiltersProvider.dom (legacy tagMode
migration on the localStorage/default-view seed).
13 lines
685 B
TypeScript
13 lines
685 B
TypeScript
// Pure selection helpers for saved ("personal") views, split out of SavedViewsProvider so the
|
|
// tie-break logic is unit-testable without mounting the provider (its React Query / context deps).
|
|
|
|
/** Among the views whose filters match the live filters, pick the one to treat as active: prefer the
|
|
* explicitly-applied ("sticky") view when several capture identical filters, so apply(B) isn't
|
|
* silently re-attributed to an earlier duplicate A; otherwise the first match. Null if none match. */
|
|
export function preferSticky<T extends { id: number }>(
|
|
matches: T[],
|
|
stickyId: number | null,
|
|
): T | null {
|
|
return matches.find((v) => v.id === stickyId) ?? matches[0] ?? null;
|
|
}
|