Repository navigation
fix(frontend): storage-clear sign-out, safe lookups, real YTD sparkline - #1344
Merged
Merged
Conversation
…s, real YTD sparkline
- Cross-tab sign-out ignored the storage event localStorage.clear()
fires (key === null), so a tab whose storage was wiped elsewhere kept
a signed-in view. A null key now clears in-memory auth and reloads,
unless the tab is already signed out (A11-023).
- PROVIDER_DISPLAY_NAMES, PAYMENT_ORDER and PAYMENT_DISPLAY_LABELS were
indexed by API strings through the prototype chain, so a provider
named "constructor" rendered "function Object() { [native code] }" as
a card heading. Lookups go through a new lib/own-property ownValue()
helper (A12-048).
- The YTD Savings tile's sparkline reused the trend chart's points, so
the 7d/30d/90d/All toggle reshaped a line labelled YTD. It now plots
its own 1 January (UTC, as the backend's ytd_savings uses) to now
series with the same chip scope (A12-073).
Refs LeanerCloud#317
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Conflicts: - frontend/src/__tests__/dashboard.test.ts: import block. Kept this branch's loadYtdSparkline import from ../dashboard and main's separate formatTrendTooltipTitle import from ../modules/chart-utils (LeanerCloud#1323). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Collaborator
Author
|
Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): APPROVE Blocking:
|
Collaborator
Author
|
Local verification at
|
IAmJSD
marked this pull request as ready for review
October 24, 2026 19:02
Collaborator
Author
|
Marked ready and merged by IAmJSD's agent, at IAmJSD's request (IAmJSD reviewed the before/after screenshots; cristim is traveling), after the independent review and local verification above, all at |
IAmJSD
added a commit
to IAmJSD/cloud-commitments-platform
that referenced
this pull request
Oct 24, 2026
Conflicts: - frontend/src/app.ts: import block; kept this branch's wireTablistKeyboard import and main's initPurchasingNav import (LeanerCloud#1336). - frontend/src/inventory.ts: import block; kept this branch's syncTabStops import and main's ownValue import (LeanerCloud#1344). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
IAmJSD
added a commit
to IAmJSD/cloud-commitments-platform
that referenced
this pull request
Oct 24, 2026
No conflicts. Checked loadSavingsTrendChart in dashboard.ts: the sparse (<2 points) path from LeanerCloud#188 sits after main's empty-state path and is independent of main's new loadYtdSparkline (LeanerCloud#1344), so both behaviours are kept as merged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
IAmJSD
added a commit
to IAmJSD/cloud-commitments-platform
that referenced
this pull request
Oct 24, 2026
Conflicts:
- frontend/src/dashboard.ts (loadSavingsTrendChart empty and error
paths): kept this branch's removeChartTable(SAVINGS_TREND_TABLE_ID) and
took main's removal of attachSparkline('ytd', []) there; since LeanerCloud#1344 the
YTD sparkline has its own series loaded by loadYtdSparkline(), so the
trend chart's empty/error state no longer clears it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
IAmJSD
added a commit
to IAmJSD/cloud-commitments-platform
that referenced
this pull request
Oct 24, 2026
No textual conflicts. Checked main's changes next to this branch's: LeanerCloud#1344 moved the YTD sparkline into loadYtdSparkline(), which looks up .kpi-tile-spark[data-spark-key="ytd"]; lib/kpi-tile.ts still renders that slot for the YTD tile. CSS added by LeanerCloud#1336/LeanerCloud#1332/LeanerCloud#1388 (sticky section nav z-index 5, table scroll shadows z-index 9, sort buttons) sits below the dialog z-index ladder and does not touch card or dialog surfaces. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three low-severity correctness findings from the 2026-09-02 audit. A tab stayed signed in after another tab ran
localStorage.clear(), a provider namedconstructorrenderedfunction Object() { [native code] }as a coverage card heading, and the YTD Savings sparkline followed the trend chart's 7d/30d/90d/All toggle while the number above it stayed year-to-date. This PR fixes all three.Closes #317
What changed
frontend/src/api/client.ts:installStorageListenertreatse.key === null(theclear()event) as a clear-all. It drops the in-memory token, API key and CSRF token and reloads. A tab that is already signed out is not reloaded (A11-023).frontend/src/lib/own-property.tsexportsownValue(table, key), ahasOwnProperty-guarded lookup. It is used forPROVIDER_DISPLAY_NAMESininventory.ts, and forPAYMENT_ORDER(3 sites) andPAYMENT_DISPLAY_LABELS(2 sites) inrecommendations.ts(A12-048).frontend/src/dashboard.ts: newloadYtdSparkline(), called fromloadDashboardnext toloadSavingsTrendChart. It fetches/history/analyticsfrom 1 January 00:00 UTC to now with the same provider/account scope (hourly, daily or weekly interval depending on elapsed days), with a generation guard and a skip when the tile is absent.loadSavingsTrendChartno longer touches the YTD sparkline (A12-073).crosstab-session.test.ts,own-property.test.ts,inventory.test.ts,dashboard.test.ts, and a new Playwright spectests-e2e/crosstab-storage-clear.spec.ts.Why this approach
yearStartincalculateCommitmentMetrics. The cost is one extra analytics request per Home load and per chip change, not per range click.navigation.ts(fix(frontend,server): resolve every route to its own page on direct load and refresh cloud-commitments-cli#1854). Main also has privateown()copies inlib/plan-service-ids.tsandcommitmentOptions.ts. This PR does not switch those over; they can move toownValuein a follow-up.Object.create(null)tables would work too, but the protection would be invisible at the call site.Screenshots
Screenshots were taken before this branch was merged with main; before = main at a14dc8f. After is this branch. Both are headless Chromium at 1440x900 against the repo's mocked e2e API.
localStorage.clear(), this tab still shows the signed-in Home view. On the branch it signs out and lands on CUDly Login.constructoris titledfunction Object() { [native code] }. On the branch it falls through to the uppercase fallback,CONSTRUCTOR.Verification
Run on this branch's head
f4bd10c2.frontend/src/__tests__/dashboard.test.tsresolved, see the merge commit.npm run lint: 0 errors. Typecheck and build clean.--randomize. A full-file random seed of the dashboard/inventory suites fails 2 tests on unmodified main as well, so that order dependence is not from this branch.crosstab-session.test.ts(a null key now signs out, and a signed-out tab does not reload),own-property.test.ts(constructor,toString,__proto__,hasOwnProperty,valueOfand''returnundefined),inventory.test.ts(providersconstructorandtoStringrenderCONSTRUCTOR/TOSTRING),dashboard.test.ts(YTD request starts 1 January UTC with the chip scope and draws the series, a 7-day trend click no longer redraws the tile, a failure hides it, a missing tile skips the fetch; the chip-change test now expects 2 analytics calls).crosstab-storage-clear.spec.ts(two pages in one context;clear()in tab B puts tab A on CUDly Login).scripts/test-git-secrets-allowlist.sh: 137 PASS.Notes for review
/history/analyticscalls (trend and YTD) per load and per chip change. If an extra call is not wanted, the alternative from the issue is to relabel the sparkline (for example "trend for selected range").dashboard.tswas already over the 500-line guideline (1194 lines on main) and is 1223 on this branch.loadYtdSparklinesits next toattachSparkline, which is module-private.riexchange.tsproviderLabel(PROVIDER_LABELS[provider]) has the same prototype-lookup shape. It is left alone because the issue scopes itself to code outside the RI-exchange module, and RI-exchange audit issues own that file.trends.ts:167,PROVIDER_LABELS[r.provider] ?? r.provider) has the same shape too. It is not changed here and can useownValuein a follow-up.clear()happens while a signed-in tab is mid-edit, that tab reloads. This matches the existing per-key sign-out.Overlaps
Textual conflicts with the open branches for #744 (#1381,
dashboard.ts, savings-chart summary nearloadSavingsTrendChart) and #730 (#1371,inventory.tsimports and section header).Note for the UI owner
This changes UI code that @cristim owns. IAmJSD reviewed the before/after screenshots and asked for it to be merged; any design point you disagree with can be a follow-up.
🤖 Generated with Claude Code