Skip to content

fix(frontend): show RI exchange failure reasons, stop double-escaping - #1348

Merged
IAmJSD merged 2 commits into
LeanerCloud:mainfrom
IAmJSD:ui/issue-321
Oct 24, 2026
Merged

IAmJSD merged 2 commits into
LeanerCloud:mainfrom
IAmJSD:ui/issue-321

Conversation

@IAmJSD

@IAmJSD IAmJSD commented Oct 24, 2026 •

Copy link
Copy Markdown
Collaborator

A failed RI exchange showed only a red failed badge in Exchange History, so operators had to read backend logs to find out why no money was spent. The target picker's option labels were also escaped twice, so any &, < or quote rendered as an entity. This PR shows the stored AWS error under the badge and assigns the raw labels. The third finding, A12-066 (the "type a UUID" copy with no UUID field), is already fixed on main by #1377.

Closes #321

What changed

  • frontend/src/riexchange.ts: renderExchangeHistory adds a <div class="status-detail"> with the escaped rec.error under the status badge whenever the record has an error (A12-069).
  • frontend/src/riexchange.ts: the AWS-offering and Cost Explorer alternative option labels in openExchangeModal now assign the raw strings to textContent; both escapeHtml calls are removed (A12-067).
  • frontend/src/styles/components.css: .status-detail shares the muted small subline style of .history-approver (the line Purchase History shows under its status badges) and adds max-width: 18rem and overflow-wrap: anywhere so long AWS errors wrap.
  • New frontend/src/__tests__/riexchange-audit-321.test.ts.

Why this approach

  • The error is visible text rather than a title tooltip (the issue's other option): tooltips are hard to discover, unreachable by keyboard and invisible on touch screens. It also matches how Purchase History shows a failed execution's error under its badge.
  • The reason goes through escapeHtml because the history table is built as an HTML string.
  • The tests live in a new small file instead of growing riexchange.test.ts (about 1400 lines).

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.

Before After
failed-exchange before failed-exchange after

Exchange History with one failed record carrying an AWS error and one completed record. Before: the failed row shows only a failed badge, with no reason. After: the AWS error (AcceptReservedInstancesExchangeQuote: InvalidParameterValue: ...) appears under the badge and wraps without squeezing the other columns.

Verification

Run on this branch's head 38bd2768:

  • Merged with main at 5b52c4e; conflicts in frontend/src/riexchange.ts resolved, see the merge commit.
  • npm run lint: 0 errors. Typecheck and build pass.
  • Jest: 3867 of 3868 passed, 1 skipped.
  • New riexchange-audit-321.test.ts (4 tests): AWS offering label keeps &, quotes and <> with no &amp;; CE alternative label is not pre-escaped; a failed record shows its error as text (markup in it is not rendered) under the failed badge; a record without an error gets no detail line. 3 of the 4 fail on main. Before the merge, the file passed with --randomize.
  • Playwright (e2e-local.sh): 200 passed.
  • Secrets scan clean (planted key flagged); scripts/test-git-secrets-allowlist.sh: 137 PASS.

Notes for review

  • A12-067 and A12-069 are done here; A12-066 is done on main via fix(frontend): give the RI exchange target picker a Retry on failure #1377.
  • Visual: the error is shown in full as muted small text under the badge, capped at 18rem wide; long AWS messages wrap over 2-3 lines.
  • No screenshot for A12-067: real AWS instance and offering types never contain &, < or quotes, so the double escaping only shows with made-up data. Jest covers it.

Overlaps

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

…ing picker labels

Two of the three 2026-09-02 audit findings for riexchange.ts:

- A12-069: a failed exchange's error (set by FailRIExchange) was never
  rendered, so the history row showed only a "failed" badge and the
  operator had to read backend logs to see why. Show the escaped reason
  under the badge, capped in width so a long AWS error wraps instead of
  squeezing the other columns.
- A12-067: target-picker option labels were passed through escapeHtml
  and then assigned to textContent, which escapes on its own, so any &,
  < or quote rendered as an entity. Assign the raw strings.

A12-066 (the "type a UUID" copy) is left to the dedicated issue LeanerCloud#740.

Refs LeanerCloud#321

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@IAmJSD IAmJSD added triaged Item has been triaged priority/p3 Polish / idea / may never ship severity/low Minor harm urgency/eventually No deadline impact/few Limited audience effort/s Hours type/bug Defect labels Oct 24, 2026
@coderabbitai

coderabbitai Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

  • Run on-demand review

This review includes 3 billable files and costs up to $0.75.

Or wait 59 minutes for your next included review.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
Commits

Reviewing files that changed from the base of the PR and between 5b52c4e and 38bd276.

Files selected for processing: 3
  • frontend/src/__tests__/riexchange-audit-321.test.ts
  • frontend/src/riexchange.ts
  • frontend/src/styles/components.css
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

Conflicts:
- frontend/src/riexchange.ts (Cost Explorer alternative option label in
  openExchangeModal): kept main's formatOfferingCost(alt) currency-aware
  cost (LeanerCloud#1343) and LeanerCloud#321's raw instance_type assigned to textContent (main
  still wrapped it in escapeHtml, which double-escapes in textContent).
- frontend/src/riexchange.ts (renderExchangeHistory row): kept main's
  em dash for an empty payment_due (LeanerCloud#1343) and LeanerCloud#321's errorLine under the
  status badge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@IAmJSD

IAmJSD commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): APPROVE 38bd2768336655743046e46c12e05ad5d3089b67

Blocking:

@IAmJSD

IAmJSD commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

Local verification at 38bd2768 merged onto current main (2433f6dd; merge-tree f99b971c; Linux, Node, headless Chromium):

  • This branch's own checks passed at this head: lint, typecheck, build, the full jest suite, the full Playwright e2e suite, and the git-secrets checks (see Verification in the PR body).
  • The combined tree of every UI PR in this merge wave (tree dc090cc2, main plus 17 PRs) passes npm ci, lint, typecheck, build, jest (1 skipped, 4097 passed, 4098 total), and the full Playwright suite (232 passed).
  • CI is green on 38bd2768 (CI Success, Run pre-commit hooks).
  • The independent review above approves this exact head.
  • Nothing was applied to any cloud.

@IAmJSD
IAmJSD marked this pull request as ready for review October 24, 2026 19:00
@IAmJSD
IAmJSD merged commit 2d805d3 into LeanerCloud:main Oct 24, 2026
29 checks passed
@IAmJSD

IAmJSD commented Oct 24, 2026

Copy link
Copy Markdown
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 38bd2768.

IAmJSD added a commit to IAmJSD/cloud-commitments-platform that referenced this pull request Oct 24, 2026
Conflicts:
- frontend/src/plans.ts: imports kept both (status-badge + main's
  purchase-row-guards). Planned-purchase status cell keeps main's LeanerCloud#181
  layout (.status-badges wrapper, Overdue badge, .overdue-hint) with this
  branch's statusBadgeHtml(status) as the status pill; the Overdue badge is
  now badgeHtml('danger', 'Overdue', ...) instead of the retired
  .status-badge class.
- frontend/src/riexchange.ts: imports kept both (skeletonText from main +
  statusBadgeHtml). History status cell uses statusBadgeHtml(rec.status)
  followed by main's LeanerCloud#1348 error line (.status-detail).
- frontend/src/history.ts: imports kept both (status-badge + main's
  local-date).

Semantic fixes in this merge:
- __tests__/riexchange-audit-321.test.ts (main): expects the shared
  .badge.badge-danger with "Failed" instead of .status-badge "failed".
- __tests__/css.test.ts: the retired-variant check is now /\.status-badge\b/
  so main's LeanerCloud#181 .status-badges wrapper rule does not trip it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/s Hours impact/few Limited audience priority/p3 Polish / idea / may never ship severity/low Minor harm triaged Item has been triaged type/bug Defect urgency/eventually No deadline

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chore(frontend/riexchange): low correctness findings from the 2026-09-02 audit

1 participant