Summary
frontend/taskdeck-web/src/store/metricsStore.ts has separate board-metrics and forecast surfaces, but each asynchronous method commits every response, error, toast, and finally without identifying the request or credential lifetime that owns it. $reset() clears visible state but does not invalidate requests already in flight.
This is a source-traced finding on main 307c3b8b50bec1cb0bfaea3e570a942bcb1d4451. MetricsView automatically fires both requests whenever board or date-range selection changes, so overlapping requests are reachable through ordinary UI interaction.
Reachable races
- An older metrics response can replace the result for the newly selected board or date range.
- An older forecast response can replace the current forecast independently.
- A stale failure can overwrite a newer success with an obsolete error/toast.
- An older
finally can clear the same lane's loading state while the current request remains pending.
$reset(), logout/login, same-user token rotation, or demo-session replacement can be followed by a late response that repopulates retired-session data.
Metrics and forecast are independent lanes: completion in one must not clear loading or state ownership in the other. Original transport promises should still settle to their callers. This concerns truthful client state, not request cancellation or a server authorization bypass.
Expected contract
- Metrics and forecast each have their own latest-request owner within one credential epoch.
- Starting a new request retires only the prior owner in that lane.
$reset() and identity/token/auth/demo replacement synchronously invalidate both lanes before clearing state.
- Stale success, failure, toast, and
finally paths cannot alter the replacement lifetime.
- Independent metrics/forecast concurrency, existing error semantics, demo messages, API contracts, and public store API remain unchanged.
Acceptance
- Real Pinia/Vitest deferred regressions for reverse-settling metrics and forecast reads.
- Same-lane stale failure and loading-ownership coverage.
- Proof that metrics and forecast remain independently concurrent.
$reset() before late success and late failure leaves both surfaces clear and emits no stale toast.
- Same-user token rotation clears both surfaces and suppresses late settlements.
- Existing metrics store, Metrics view, router-stability, and demo suites remain green.
- Frontend lint, typecheck, build, full Vitest on Ubuntu and Windows, and complete exact-head hosted CI pass.
Audit provenance
Found while continuing the 2026-09-21 asynchronous state-ownership pass. Searches for an open issue naming metricsStore.ts found no existing owner.
Summary
frontend/taskdeck-web/src/store/metricsStore.tshas separate board-metrics and forecast surfaces, but each asynchronous method commits every response, error, toast, andfinallywithout identifying the request or credential lifetime that owns it.$reset()clears visible state but does not invalidate requests already in flight.This is a source-traced finding on
main307c3b8b50bec1cb0bfaea3e570a942bcb1d4451.MetricsViewautomatically fires both requests whenever board or date-range selection changes, so overlapping requests are reachable through ordinary UI interaction.Reachable races
finallycan clear the same lane's loading state while the current request remains pending.$reset(), logout/login, same-user token rotation, or demo-session replacement can be followed by a late response that repopulates retired-session data.Metrics and forecast are independent lanes: completion in one must not clear loading or state ownership in the other. Original transport promises should still settle to their callers. This concerns truthful client state, not request cancellation or a server authorization bypass.
Expected contract
$reset()and identity/token/auth/demo replacement synchronously invalidate both lanes before clearing state.finallypaths cannot alter the replacement lifetime.Acceptance
$reset()before late success and late failure leaves both surfaces clear and emits no stale toast.Audit provenance
Found while continuing the 2026-09-21 asynchronous state-ownership pass. Searches for an open issue naming
metricsStore.tsfound no existing owner.