Live preview of filter decisions for screens beneath the filter modal#76
Conversation
Toggling selections in the filter panel now updates the dashboard, transactions, and trends screens immediately (debounced), via a preview filter in FilterContext that screens consume transparently. Esc reverts to the committed filter with no history pollution; Enter commits as before.
- Extract the preview debounce window to a named PREVIEW_DEBOUNCE_MS const. - Document why the panel hydrates from `committed` (not `filter`): reading the live value would re-seed the draft from the panel's own preview — a loop. - Note the unmount cleanup is distinct from the debounce cleanup: the latter only cancels a pending timer, which would otherwise strand an already- published non-null preview when closing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Assert the preview reaches the Dashboard it was opened from (via the Expenses total, which no panel row renders). - Assert a burst of draft changes collapses into a single preview query. - Assert previewing never pollutes filter history — commit pushes exactly one level, so one Esc in Transactions returns straight to the original view. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Filter changes — the live preview most of all — can fire queries faster than they resolve. A slow earlier query could land after a faster later one and paint stale rows. Stamp each load with a monotonic token and apply only the newest result; earlier ones are discarded on arrival. Test forces the unfiltered load slow and the filtered load fast, then asserts the fast result survives the stale one that follows (fails without the guard). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extract the Transactions sequence-token guard into a shared useLoadGuard hook and apply it to every filter/period-driven async load on Dashboard (summary, drift, search stats, search-filtered data, merchant drill) and Trends (period totals, match count, search totals), so the live preview can't leave any screen showing stale data from a slow earlier query. Transactions now uses the hook too, so all three screens share one mechanism. Tests: a useLoadGuard unit test pins the token semantics, and a Dashboard race test mirrors the Transactions one (forces the unfiltered summary slow, asserts the faster filtered result survives the stale one that follows). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Good question. The GUI is mostly already covered — The one place it'd be worth adding explicitly is const matchGuard = useLoadGuard();
useEffect(() => {
if (pattern.trim()) {
const token = matchGuard.begin();
void api.rules.countPatternMatches(pattern, matchType)
.then((count) => { if (matchGuard.isLatest(token)) setMatchCount(count); });
} else {
setMatchCount(0);
}
}, [pattern, matchType]);No pressure to take it on in this PR — I'm happy to handle the GUI side in a follow-up. |
|
Handled in #78 — pulled |
|
@tomfunk , I was able to test this functionality out and it is working quite nicely. |
Mirrors the TUI live preview from #76. useFilter now exposes a preview slot; filter returns preview ?? committed so the FilterBar panel can publish a debounced draft as the user toggles, with the underlying Dashboard/Transactions/Trends repainting before Apply. Cancel/X clears the preview on unmount; Apply commits via setFilter as before. The panel hydrates from committed (not the live filter) to avoid re-seeding itself from its own preview. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
One thing to call out --- the
useLoadGuardhelper function is in thetuicode. Would this sort of guard be helpful in the GUI code?Setting this to draft as I haven't completed local testing yet
--- Opus generated PR description follows ---
What & why
The filter panel previously only affected the underlying screens on Enter — you committed a filter blind, then saw the result. This adds live preview: as you toggle categories, accounts, owners, or tags in the panel, the screen behind it re-renders with the draft filter in real time. Esc discards the draft and restores the original view; Enter commits it.
This implements the "Live preview plan" in docs/filter-panel-plan.md.
How it works
The mechanism is deliberately small so consumer screens need zero changes:
Two subtleties worth calling out for review:
Hardening: out-of-order query results
Live preview turns one-query-per-action into a stream of queries as the draft changes. Since the load paths were getX().then(setState) with no cancellation, a slow earlier query could resolve after a faster later one and paint stale data. This PR adds a shared useLoadGuard hook (a monotonic token; only the newest load applies its result) and wires it into every filter/period-driven async load:
Testing
Notes