Repository navigation
chore(frontend): low correctness findings from the 2026-09-02 audit #317
Description
Summary
Three low-severity frontend correctness findings outside the RI-exchange module: the cross-tab sign-out listener ignores the localStorage.clear() event shape, provider and payment display tables are indexed by API strings without an own-property check (the same in/prototype-chain shape that LeanerCloud/cloud-commitments-cli#1854 fixed for tab routing), and the YTD Savings tile's sparkline plots the trend-range window rather than year-to-date.
Each item is independently actionable; they are grouped so one sitting can clear them.
Findings
-
Cross-tab sign-out misses a localStorage.clear() (
A11-023)- Location:
frontend/src/api/client.ts:95(listener at:94-105, module comment at:88-92) at 3c0f8ac94048a2c36fce5ccddee54e6c4849a5cd - Failure scenario: The storage listener returns early when
e.keyis falsy. Per the StorageEvent spec,localStorage.clear()fires astorageevent withkey === null, which is exactly the everything-was-wiped case. A tab that clears storage (a browser privacy control, a devtools clear, or any future code path usingclear()) leaves sibling tabs holding their in-memoryauthTokenand rendering a logged-in view against a session the user believes they ended. The module comment presents this listener as the cross-tab sign-out mitigation. - Evidence:
window.addEventListener('storage', (e: StorageEvent) => { if (!e.key || !(STORAGE_KEYS as readonly string[]).includes(e.key)) return; if (e.newValue !== null) return; authToken = ''; apiKey = ''; csrfToken = '';
- Suggested fix: Treat
e.key === nullas a clear-all and run the same reset-and-reload path.
- Location:
-
Provider display tables are indexed by an API string without an own-property check (
A12-048)- Location:
frontend/src/inventory.ts:430(PROVIDER_DISPLAY_NAMES[section.provider]);frontend/src/recommendations.ts:1141(PAYMENT_ORDER[a.payment ?? '']) and:2881(PAYMENT_DISPLAY_LABELS[payment]) at 3c0f8ac94048a2c36fce5ccddee54e6c4849a5cd - Note: PLAUSIBLE only; no known API response supplies such a provider value.
- Failure scenario: The lookups walk the prototype chain, so a section whose
provideris"constructor"resolves to theObjectconstructor function rather than falling through to the??branch, andtitle.textContentrenders the function's source into the card heading. Verdict is PLAUSIBLE, not confirmed: it needs the coverage API to return a provider literally namedconstructor, which could not be established from source. The same shape reached production via tab routing in LeanerCloud/cloud-commitments-cli#1855 before fix(frontend,server): resolve every route to its own page on direct load and refresh cloud-commitments-cli#1854 fixed it. - Evidence:
const providerLabel = PROVIDER_DISPLAY_NAMES[section.provider] ?? section.provider.toUpperCase();
- Suggested fix: Guard each lookup with
Object.prototype.hasOwnProperty.call(TABLE, key), or build the tables withObject.create(null).
- Location:
-
The "YTD Savings" tile's sparkline plots the trend-range window, not year-to-date (
A12-073)- Location:
frontend/src/dashboard.ts:1057(attachSparkline('ytd', ...)); tile value fromdata.ytd_savingsat:308; window fromsavingsTrendRangeat:1022-1031, mutated by the range buttons at:1174-1177at 3c0f8ac94048a2c36fce5ccddee54e6c4849a5cd - Failure scenario: The tile's number comes from
data.ytd_savings, but the sparkline beneath it comes fromloadSavingsTrendChart's data points, whose window is whatever the trend range toggle is set to (7, 30, 90 days or rolling 365). Clicking the "7" range button changes the shape of the line under a label that says YTD, so the number and the line describe different periods. - Evidence:
attachSparkline('ytd', points.map((p: SavingsDataPoint) => p.cumulative_savings || 0));
- Suggested fix: Fetch a separate year-to-date series for the tile, or relabel the sparkline so it is not read as YTD.
- Location:
Found by the 2026-09-02 codebase audit, findings A11-023, A12-048, A12-073, reported by one reviewer and independently confirmed by a second. Full report: docs/audits/codebase-audit-2026-09-02.md.
Activity