ADR-0052: Dashboard Age filter defaults to "Any", not "Today"
Status: Accepted Date: 2026-06-15 Supersedes: none Superseded by: none
Context
The officer dashboard's FilterBar (frontend/src/components/dashboard/FilterBar.tsx)
exposes five filters: Status, Risk, Age, Country, Tier. Four of them default to a
"show everything" baseline (status="", risk="all", country="all", tier="all").
The Age filter was the lone exception: it defaulted to "today" (introduced
intentionally in commit 3c5ed851, 2026-02-23, "default today", and locked in by
FilterBar.test.tsx). matchesAge defines "today" as days < 1 — created within
the last 24 hours, not calendar-today.
The original intent was a high-volume triage view ("show me today's incoming cases").
In practice the default produced a trap, surfaced when the e2e suite
(comprehensive-fresh-cases, exploratory-full) ran against seed data whose newest
case was older than 24h: the dashboard rendered an empty case list on load while
the stat cards above it still reported open cases. Four factors reinforced each other:
- The default hides active work. A case's creation age is orthogonal to whether
it needs attention; in-progress cases (e.g.
AWAITING_DOCUMENTS,REVIEW_PENDING) created more than 24h ago were hidden by default. - Stats contradicted the list.
StatsHeroreads the full set (allCases) whileCaseListTablereads the filtered set (filteredCases), so "Open Cases: N" sat above an empty table — indistinguishable from data loss. - No active-filter cue.
hasFilterstreated"today"as its baseline (ageRange !== "today"), so neither the "filters active" indicator nor the "Clear filters" button appeared while cases were hidden. - "Clear all" did not recover.
clearAll()reset Age back to"today", so the obvious user recovery still left the list empty.
This affects any session whose newest case is >24h old: PoC/demo tenants, Monday mornings, and officers returning after a day away.
Decision
Default the dashboard Age filter to "all" (Age: Any), aligning it with the other
four filters' "show everything on load" convention. Concretely, in FilterBar.tsx:
useState<AgeRange>("all")(was"today").hasFiltersbaseline becomesageRange !== "all"so the new default is correctly treated as "no filter applied" (otherwise the Clear button would show on load).clearAll()resets Age to"all".
FilterBar.test.tsx is updated to assert the "all" default and that all cases
(including older ones) render on initial load. "Age: Today" remains available as an
explicit, user-selectable quick filter; only the default changes.
Decision context:
- Latency: none — client-side filter over already-fetched cases; no network change.
- Dependency surface: none — no new packages; three-line change in one component.
- Debuggability: improves — the dashboard's default list now matches its own stat cards, removing a "cases vanished" false alarm.
- Reversibility: trivial — revert three lines + three test assertions (single commit).
- Blast radius: one component (
FilterBar) + its test; consumed only by the officer dashboard. Substitutive (changes a default), not additive. - Alternative considered: keep "today" but fix the traps (Option C below) — rejected as more surface area for the same end-state for typical caseloads.
Consequences
Positive
- Officers see their full case list on load; in-progress work is no longer hidden.
- The list no longer contradicts the stat cards — removes a data-loss false alarm.
- Age now follows the same default convention as every other filter (consistency).
Negative
- High-volume tenants that benefited from an implicit "today only" triage view must now select "Age: Today" explicitly. (Judged acceptable: an empty-looking dashboard is a worse default than one extra click for power users.)
- Reverses a previously intentional, test-asserted decision; the three Age-default tests were rewritten.
Neutral
- The
matchesAgesemantics and all Age options are unchanged; only the initial selection differs.
Alternatives Considered
Alternative 1: Keep "today" as the default, fix only the compounding factors
- Make "Clear all" reset to "all", count Age as an active filter (show the indicator and Clear button), and reconcile the stats/list mismatch (e.g. an "N hidden by filters" hint).
- Why rejected: more code and ongoing surface area to reach the same outcome most users want (seeing their cases on load). The triage benefit is marginal for the typical caseload and still leaves a non-obvious default.
Alternative 2: Make the default tenant-configurable
- Store the preferred default Age per tenant/user.
- Why rejected: disproportionate — adds persistence and settings UI for a one-line default. Can be revisited if a high-volume customer specifically needs it.