Repository navigation
fix(frontend): stop rendering placeholder values on pending purchases - #1329
Merged
Merged
Conversation
A pending execution that carries no recommendations comes back from
/api/history as count 0, term 0, "0 commitment(s)", provider/region
"multiple" and $0 costs, and the Approval queue and Purchase History
tables printed those as data ("0 Years", "0", "Multiple", "$0"). A real
mixed-term basket (term collapsed to 0) also showed "0 Years", because
formatTerm only blanks null/NaN.
Rows with no commitments now render dashes and a "No recommendations
attached" label (the same copy Plans uses since LeanerCloud#609), and a 0 term
renders as a dash, including in the Term column-filter labels. The
guards live in a small lib module so the already-large history.ts only
gains call sites.
Refs LeanerCloud#181
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A scheduled purchase whose date had passed without running still showed
a plain "Pending" badge, which reads as on schedule. Pending/notified
rows past their date now get an "Overdue" badge next to the status and a
short next-action hint ("Not run on its date. Run now or pause."); users
without run permission see only "Not run on its date." The date check
compares calendar days so a date-only value is not shifted by UTC
parsing. Paused rows are not flagged.
Refs LeanerCloud#181
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Conflicts: - frontend/src/history.ts (Purchase History row and Approval queue row): kept LeanerCloud#181's no-commitment guards (dashOr/MUTED_DASH, NO_RECS_HTML in the Type/Service cell, termCell(p) dash for a 0 term) and main's changes (count escaped with a null fallback from LeanerCloud#1318, upfrontCell(p, amortize) with the "(in monthly)" note from LeanerCloud#1345, amortize hoisted out of the row loop). A no-commitment row renders a muted dash <td> instead of upfrontCell; real rows use upfrontCell unchanged. 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:00
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/history.ts (Purchase History and approval-queue rows): kept main's no-commitments handling (LeanerCloud#1329: hasNoCommitments, dashOr, NO_RECS_HTML, termCell) and this branch's num classes on the count/upfront/monthly/savings cells and NO_VALUE for the missing monthly cost, created-by, account and payment cells. The upfront placeholder for an empty row also gets class num, matching upfrontCell. - frontend/src/recommendations.ts (sortable header): kept main's LeanerCloud#755 header structure (plain <th> with aria-sort, sort-btn and filter button siblings) and added this branch's num class for numeric columns. 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
… main added Main's LeanerCloud#1329 (no-commitments rows) added a muted "-" placeholder (MUTED_DASH) for Purchase History and approval-queue cells and a "-" Term column-filter label for a 0 term. They now use NO_VALUE ("—"), like the rest of the absent values on these tables, so a no-commitments row no longer mixes "-" with "—". purchases-placeholders-181.test.ts expects "—". Refs LeanerCloud#739 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
IAmJSD
added a commit
that referenced
this pull request
Oct 24, 2026
…#1376) * fix(frontend): one money formatter, one absent-value placeholder, right-aligned money columns Money was printed three ways: formatCurrency (separators), the cost period cells' `$${x.toFixed(n)}` (so a yearly figure read "$123456"), and savings-history's private helper. Absent values used "--" (from formatCurrency and about a dozen literals), "—" and, in the Purchases tables, "-"; the empty Home showed "--" in savings green next to a bold "—". All money columns were left-aligned, so digits did not line up. - utils.ts: export NO_VALUE ("—"); formatCurrency returns it for null/NaN/Infinity. - recommendations.ts: formatCostForPeriod and formatScaledRange go through formatCurrency with the per-period precision, so every period groups thousands. - dashboard.ts, history.ts, plans.ts, recommendations.ts: every "--" / "-" placeholder becomes NO_VALUE; an absent figure is no longer styled as a saving. - tables.css: th.num/td.num right-align with tabular-nums; the Opportunities numeric columns (by COLUMN_DEFS kind) and the Purchase History and approval queue count/money columns use it. - index.html: the Purchases savings stat cards start at "—", not "$0.00". The savings-history module's own formatter is left to #171/#749. Refs #739 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(frontend): use the one absent-value placeholder in code main added Since this branch was cut, main added placeholders that bypass NO_VALUE: - plans.ts formatRampProgress (#1327) returned "--" for a missing step count; it now returns NO_VALUE like the other plan-card fields. - trends.ts (new Trends sub-tab, #1317) showed "--" on its Latest and Average savings stat cards; they now use NO_VALUE, matching the monthly table, whose null cells come from formatCurrency and already render it. - trends.test.ts, trends.spec.ts, plans.test.ts and nullable-purchase-cost.spec.ts expected the old "--"/"-" text from formatCurrency(null) and the muted monthly cell; they now expect "—". Refs #739 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(frontend): use the one absent-value placeholder in Purchases code main added Main's #1329 (no-commitments rows) added a muted "-" placeholder (MUTED_DASH) for Purchase History and approval-queue cells and a "-" Term column-filter label for a 0 term. They now use NO_VALUE ("—"), like the rest of the absent values on these tables, so a no-commitments row no longer mixes "-" with "—". purchases-placeholders-181.test.ts expects "—". Refs #739 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- 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
…Retry confirms Main's LeanerCloud#181 guards (LeanerCloud#1329) stopped the Approval queue and History rows from printing the placeholder values of an execution with no recommendations: count 0, "0 commitment(s)", "multiple" provider/service/ region and $0 costs. The Cancel and Retry confirm summary added for LeanerCloud#728 still printed them, so a row reading "No recommendations attached" opened a confirm reading "multiple 0 commitment(s) in multiple ... Upfront: $0". The summary now uses NO_RECOMMENDATIONS_LABEL for such a row and leaves out upfront, service and region, drops a "multiple" region the same way as a "multiple" provider, and only shows a term isRealTerm accepts. Tests: Cancel and Retry on the EMPTY_PENDING fixture shape, plus a real row checked with the real currency formatter ("Upfront: $4,200"). The Cancel describe is renamed to the Approval queue, where Cancel now lives. Refs LeanerCloud#728 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
IAmJSD
added a commit
that referenced
this pull request
Oct 24, 2026
…#1369) * fix(frontend): fix stale and inconsistent admin copy The header said "Logout" (a noun) where it means "Log out". The Purchasing policies tab opened on a panel headed "Purchasing Settings", and the service overrides dialog pointed at "Settings → Purchasing", a path that no longer exists. The add-override dialog said "Only AWS services are supported today" although Azure and GCP overrides are supported, and the Azure Workload Identity Federation option claimed it uses an RSA certificate assertion, contradicting the secret-free OIDC federated credential the form actually configures. Refs #728 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(frontend): pause toasts while hovered or focused, lengthen override Undo Toasts kept counting down while the pointer was over them or focus was on their buttons, so a long message or an Undo action could vanish mid-read (WCAG 2.2.1 Timing Adjustable). The countdown now pauses on hover and on focus inside the toast, and resumes with the time that was left (at least one second) when both leave. The override-delete Undo toast goes from 5 s to 10 s so there is time to reach Undo. The 30 s default timeout is unchanged: it was set as an explicit owner choice, so shortening success toasts or making errors sticky is left for the owner to decide. Refs #728 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(frontend): name the purchase in History Cancel and Retry confirms "Cancel this pending purchase?" and "Retry this failed purchase?" did not say which row they applied to, so in a long table it was easy to confirm the wrong one. The confirm now opens with a summary of the row (provider, service, resource and count, region, account, term, upfront cost and date) above the existing explanation, built by a small helper module so history.ts does not grow further. Adds a Playwright spec for the named confirm and for a toast staying while hovered. Refs #728 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(frontend): refresh the ladder timeline after kill-switch and config save The planned-purchases timeline captured the configs and the global kill-switch state when it was mounted, so after enabling laddering or saving an account config it kept saying "Ladder planning is disabled" or "No ladder configuration" until a page reload. ladder.ts now calls refreshLadderTimeline() after both, which updates the mounted timeline's inputs and reloads it while keeping the selected provider, account and filters. Refs #728 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(frontend): click Cancel in the Approval queue in the #728 e2e Main's #1331 made the Approval queue the only place to approve or cancel a pending purchase; the History table now shows a 'Review in queue' link instead. The confirm-summary and toast-hover spec still clicked Cancel in #history-list, so it timed out after the merge. It now clicks the queue's Cancel button. findHistoryRow already falls back to the queue's rows, so the confirm still names the purchase. Refs #728 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(frontend): use the "Log out" label in tests merged from main Main's crosstab-storage-clear.spec.ts (#317) waited for a button named "Logout", which this branch renames to "Log out". Update it, the jest fixtures in auth.test.ts and mobile-nav.test.ts, and the control names in mobile-header-drawer.spec.ts so no test refers to the old label. Refs #728 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(frontend): apply #181 no-commitments guards to Cancel/Retry confirms Main's #181 guards (#1329) stopped the Approval queue and History rows from printing the placeholder values of an execution with no recommendations: count 0, "0 commitment(s)", "multiple" provider/service/ region and $0 costs. The Cancel and Retry confirm summary added for #728 still printed them, so a row reading "No recommendations attached" opened a confirm reading "multiple 0 commitment(s) in multiple ... Upfront: $0". The summary now uses NO_RECOMMENDATIONS_LABEL for such a row and leaves out upfront, service and region, drops a "multiple" region the same way as a "multiple" provider, and only shows a term isRealTerm accepts. Tests: Cancel and Retry on the EMPTY_PENDING fixture shape, plus a real row checked with the real currency formatter ("Upfront: $4,200"). The Cancel describe is renamed to the Approval queue, where Cancel now lives. Refs #728 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.
A pending execution with no recommendations comes back from
/api/historywith count 0, term 0, "0 commitment(s)", provider/region "multiple" and $0 costs, and the Purchases page (Approval queue and Purchase History) printed those as data. A real mixed-term basket also showed "0 Years". On Plans, a pending scheduled purchase past its date showed a plain "Pending" badge with no sign it missed its run. This PR guards those renders and flags overdue planned purchases with a next action.Closes #181
What changed
e63a94b9): newfrontend/src/lib/purchase-row-guards.ts(hasNoCommitmentsfor count 0,isRealTerm,isPastScheduledDate, and the shared "No recommendations attached" copy). Infrontend/src/history.ts, both the Purchase History table and the Approval queue render a no-commitment row with muted dashes for provider, service, region, count, term, payment, costs and savings, and "No recommendations attached" in the Type (history) or Service (queue) cell. A 0 term renders as a dash everywhere, including the Term column-filter labels. Real rows keep main's upfront cell with its "(in monthly)" note (chore(frontend): low money-path findings from the 2026-09-02 audit #318); a no-commitment row gets a muted dash in its place.23a900a1):frontend/src/plans.tsrenderPlannedPurchaseRowadds an "Overdue" badge (the existingstatus-badge badge-dangerclasses, as on plan cards) next to Pending/Notified whenscheduled_dateis before today, plus a hint "Not run on its date. Run now or pause." (or just "Not run on its date." without run permission).frontend/src/styles/tables.cssadds.status-badgesand.overdue-hint.src/__tests__/purchases-placeholders-181.test.tsandsrc/__tests__/plans-overdue-181.test.ts.Why this approach
The backend contract for these synthetic rows is deliberate (
handler_history.gocomments say a 0 term should render blank), so the guard belongs in the renderer, as the issue asks ("guard the render regardless").count == 0is the reliable marker: synthetic rows set count to the number of recommendations, and a real purchase never has 0.formatTermitself is unchanged (a unit test pinsformatTerm(0) == '0 Years'and other callers pass real terms); the call sites that can see 0 are guarded instead. The overdue check comparesYYYY-MM-DDtext against the local date to avoid the UTC off-by-one thatnew Date('YYYY-MM-DD')causes. On Plans, "Various (Multiple)", 0 count and $0 for no-recommendation steps and the "0 Years" term were already handled by #609 and the existingterm > 0guard.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.
/purchases/historywith an empty pending execution, a mixed-term basket and a normal row. Before: the empty execution shows "Multiple", Count 0, "0 commitment(s)", "0 Years" and $0 in both the Approval queue and Purchase History, and the mixed-term basket shows "0 Years". After: the empty execution shows dashes and "No recommendations attached" in both tables, the mixed-term basket keeps its real count and costs with a dash for term, and the normal row is unchanged./plansplanned purchases with one pending step dated 2026-09-15 (past) and one in 2027. Before: both show only "Pending". After: the past step shows "Pending" plus "Overdue" with "Not run on its date. Run now or pause."; the future step is unchanged.Verification
Run on this branch's head
686288c2, fromfrontend/:frontend/src/history.tsresolved, see the merge commit.npm run lint: 0 errors. Typecheck and build pass.purchases-placeholders-181.test.ts(6): the helpers including date edge cases; history and Approval queue rows driven throughloadHistorywith the real formatters; the mixed-term basket.plans-overdue-181.test.ts(3): overdue vs future pending, the copy without run permission, paused rows not flagged.--randomize.e2e-local.sh: 200 passed.scripts/test-git-secrets-allowlist.sh: 137 PASS, 0 FAIL.Notes for review
isPlanOverdueparses the date as UTC midnight, so in UTC-negative zones it can say "Overdue" on the scheduled day while the row here does not. HavingisPlanOverduereuseisPastScheduledDatewould align them; not changed here.docs/ui-ux-review.md) is no longer in the repo, so the fix follows the issue text.Overlaps
frontend/src/history.tsand also touchesplans.tsandtables.css. Both change how absent values render in the same rows, so the dash style should be reconciled with whatever fix(frontend): three money formatters, mixed placeholders, left-aligned money columns #739 standardizes when combining.frontend/src/plans.ts.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