Skip to content

Spend Center: exercise the charts for the first time, fix unreadable bars (#533) - #534

Merged
richard-devbot merged 2 commits into
mainfrom
claude/spend-visuals-533
Aug 1, 2026
Merged

Spend Center: exercise the charts for the first time, fix unreadable bars (#533)#534
richard-devbot merged 2 commits into
mainfrom
claude/spend-visuals-533

Conversation

@richard-devbot

@richard-devbot richard-devbot commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Closes #533 · third implementation child of epic #500.

What this child turned out to be

It was filed to build spend visuals. Recon found the budget burn meter and per-stage bars already shipped — but no canonical fixture ever wrote metrics.json, so their drawn path had never rendered in a test or a demo. The work became: exercise them, pin them, and fix what that exposed.

  • fixtureSpendRun — real cost telemetry (cumulative + per-stage cost and tokens) under a project cap.
  • Seven source tests pinning the drawn path and the honest-null path (no meter without telemetry, no bars without stage cost, existing copy verbatim), plus a browser journey rendering both side by side: a run with spend next to one without.

The defect the first real render exposed

.stage-bar-row fixed its label/value columns at 180px/110px, so inside the Spend Center's narrow panel the track collapsed to nine pixels — a 3× cost difference drew as 4px vs 3px:

06-architecture  declared 39.4%  track 9px  fill 4px
07-code          declared  100%  track 9px  fill 9px
08-testing       declared 35.2%  track 9px  fill 3px

Percentages correct, chart unreadable — invisible to markup assertions (the #526 lesson). Flexible columns now keep the track dominant wherever the component sits; the label ellipses instead of the data vanishing. Pinned by geometry: track ≥ 80px and a visible delta between largest and smallest stage.

Dropped, deliberately

The issue also proposed an amber "approaching cap" band at 80%. dashboard-money-pages already forbids exactly that — "uses the exact loop boundary without an invented near threshold" — and it is right: run_budget_usd is enforced in code and the loop stops at the cap, so 80% is a judgment the harness never makes and the UI must not assert. I implemented it, hit that test, and reverted. The prohibition is now pinned in this child's own suite so the idea isn't revived by the next reader of #533.

Verification

Core 1921/1921 · browser 32/32 (2 new) · lint 0 · typecheck 0 · validate 196 · security green · live-verified (screenshot in thread: $6.20 measured against the cap, three stage bars with exact values, and the no-telemetry run correctly showing "— of $25.00").

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved stage-bar layouts so labels, progress tracks, and values resize smoothly across screen sizes.
    • Long cost values now remain on one line and truncate cleanly instead of wrapping.
    • Improved spend and budget visualizations for telemetry, capped runs, empty states, and per-stage costs.
  • Tests

    • Added coverage for spend reporting, budget limits, stage costs, telemetry, and responsive bar rendering.

…bars (#533)

This child was filed to BUILD spend visuals. Recon found the budget burn
meter and per-stage bars already shipped — but no canonical fixture ever
wrote metrics.json, so their drawn path had never rendered in a test or a
demo. So the work became: exercise them, pin them, and fix what that
exposed.

fixtureSpendRun adds real cost telemetry (cumulative + per-stage cost and
tokens) under a project cap. Seven source tests now pin the drawn path AND
the honest-null path (no meter without telemetry, no bars without stage
cost, existing copy verbatim) plus a browser journey rendering both side
by side: a run with spend beside one without.

The first real render exposed a genuine defect: .stage-bar-row fixed its
label/value columns at 180px/110px, so inside the Spend Center's narrow
panel the track collapsed to NINE pixels — a 3x cost difference drew as
4px vs 3px. The percentages were correct and the chart was unreadable,
which markup assertions cannot see (the #526 lesson). Flexible columns
keep the track dominant wherever the component sits; the label ellipses
instead of the data vanishing. Pinned by geometry: track >= 80px and a
visible delta between the largest and smallest stage.

DROPPED, deliberately: the issue also proposed an amber "approaching cap"
band at 80%. dashboard-money-pages already forbids exactly that — "uses
the exact loop boundary without an invented near threshold" — and it is
right: run_budget_usd is enforced in code and the loop stops AT the cap,
so 80% is a judgment the harness never makes and the UI must not assert.
I implemented it, hit that test, and reverted; the prohibition is now
pinned in this child's own suite so the idea is not revived by a future
reader of #533.

Closes #533. Part of epic #500.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@strix-security

strix-security Bot commented Aug 1, 2026

Copy link
Copy Markdown

Strix is installed on this repository, but we couldn't run this PR security review because this workspace's trial has ended. Add a card to resume code reviews here.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ea2c0606-b07b-45eb-ad8f-e8fb3c406112

📥 Commits

Reviewing files that changed from the base of the PR and between ec08bdb and 03a5aba.

📒 Files selected for processing (4)
  • src/observability/dashboard/ui/styles.js
  • tests/browser/dashboard-533-spend.test.js
  • tests/helpers/dashboard-fixtures.js
  • tests/hub-spend-visuals-533.test.js

📝 Walkthrough

Walkthrough

Changes

Spend Center visual validation

Layer / File(s) Summary
Spend fixture and renderer validation
tests/helpers/dashboard-fixtures.js, tests/hub-spend-visuals-533.test.js
Added deterministic spend telemetry and coverage for budget states, burn meters, empty states, and per-stage cost bars.
Browser Spend Center coverage
tests/browser/dashboard-533-spend.test.js
Added browser checks for run listings, spend messaging, persisted costs, and proportional stage-bar dimensions.
Responsive stage-bar layout
src/observability/dashboard/ui/styles.js
Replaced fixed side columns with flexible bounded columns and added value-text ellipsis handling.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers: richardsongunde

Poem

A rabbit checks each stage-bar line,
With spend and caps in neat design.
Long values tuck away from view,
While cost proportions render true.
The dashboard hops through tests anew.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the Spend Center test coverage and the fix for unreadable stage bars.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/spend-visuals-533

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Spend Center: add spend telemetry fixture and fix stage bar readability

🐞 Bug fix 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Add a spend run fixture that persists metrics.json cost/token telemetry under a cap.
• Pin Spend Center burn meter and per-stage bars for telemetry and no-telemetry paths.
• Fix per-stage bar grid sizing so tracks remain readable in narrow panels.
Diagram

graph TD
  F["fixtureSpendRun (metrics.json)"] --> H["Browser harness"] --> P["Spend Center page"] --> C["Spend visuals (meter + bars)"] --> S["stage-bar-row CSS grid"]
  U["Hub render tests"] --> R["DOM-less renderer"] --> P
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use `auto 1fr auto` grid columns plus `min-width` on the track
  • ➕ More semantically maps to label/track/value without tuning max widths
  • ➕ Easier to reason about than nested minmax constraints
  • ➖ Still needs explicit minimums somewhere; risk of reintroducing track collapse
  • ➖ Less control over truncation behavior of the label/value columns
2. Adopt container queries for Spend Center panel width
  • ➕ Adapts precisely to the panel’s actual container size (not viewport)
  • ➕ Could avoid separate breakpoint tuning for 900px layouts
  • ➖ Requires broader CSS support decisions and more infra/testing surface area
  • ➖ Overkill for a single responsive grid row problem

Recommendation: Keep the PR’s approach: minmax-based grid columns are a low-risk, CSS-only fix that makes the track the dominant element in constrained layouts, and the new browser geometry assertions prevent silent regressions that markup-only tests miss. Container queries are a reasonable future enhancement if more components need panel-relative breakpoints.

Files changed (4) +243 / -2

Bug fix (1) +8 / -2
styles.jsMake stage bar grid columns flexible to prevent track collapse +8/-2

Make stage bar grid columns flexible to prevent track collapse

• Replaces fixed label/value column widths with minmax-based sizing for '.stage-bar-row', keeping the bar track usable in narrow panels. Updates the small-screen media query to preserve minimum track width while allowing label/value truncation.

src/observability/dashboard/ui/styles.js

Tests (3) +235 / -0
dashboard-533-spend.test.jsAdd browser journey tests for Spend Center telemetry and bar readability +71/-0

Add browser journey tests for Spend Center telemetry and bar readability

• Introduces two browser tests that navigate to the Spend Center and validate both the telemetry-present and telemetry-absent paths. Adds a geometry-based assertion ensuring bar tracks are wide enough and that large vs small stages show a visible pixel delta.

tests/browser/dashboard-533-spend.test.js

dashboard-fixtures.jsAdd 'fixtureSpendRun' that persists real metrics.json spend telemetry +45/-0

Add 'fixtureSpendRun' that persists real metrics.json spend telemetry

• Adds a new canonical fixture that writes 'metrics.json' with cumulative cost/tokens and per-stage breakdowns under a configured run budget. Creates stage tasks and validations so the Spend Center can render realistic meter and bar visuals.

tests/helpers/dashboard-fixtures.js

hub-spend-visuals-533.test.jsCharacterize Spend Center burn meter and stage bars; pin no-threshold behavior +119/-0

Characterize Spend Center burn meter and stage bars; pin no-threshold behavior

• Adds DOM-less renderer tests that assert burn meter rendering when telemetry exists and honest empty states when it does not. Also pins the deliberate non-feature: no invented "approaching cap" threshold wording or styling.

tests/hub-spend-visuals-533.test.js

@qodo-code-review

qodo-code-review Bot commented Aug 1, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (4)

Context used
✅ Compliance rules (platform): 300 rules
✅ Skills: 17 invoked
  code-review-pr
  claude-api
  documentation-writing
  pptx
  docx
  performance-monitoring
  security-compliance
  cso
  plan-eng-review
  design-review
  prompt-engineering
  mcp-builder
  qa-testing
  code-patterns
  xlsx
  security-owasp
  testing-qa

Grey Divider


Remediation recommended

1. Stage value wrapping uncontrolled ✓ Resolved 🐞 Bug ≡ Correctness
Description
The updated .stage-bar-row grid caps the value column at 96px (80px under 900px), but
.stage-bar-value has no overflow/ellipsis policy, so longer values can wrap or overflow and reduce
chart readability. This is especially relevant for Run Analytics stage money bars where the value
string can include both cost and token totals.
Code

src/observability/dashboard/ui/styles.js[R1301-1305]

+.stage-bar-row { display: grid; grid-template-columns: minmax(64px, 148px) minmax(90px, 1fr) minmax(56px, 96px); gap: 10px; align-items: center; }
.stage-bar-label { font-size: 11px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.stage-bar-track { height: 12px; background: var(--soft); border-radius: 4px; overflow: hidden; }
.stage-bar-fill { height: 100%; background: var(--blue); border-radius: 4px; }
.stage-bar-value { font-size: 11px; color: var(--muted); text-align: right; }
Relevance

●●● Strong

Small, goal-aligned CSS hardening for readability; value column now narrower so nowrap/ellipsis
likely accepted.

PR-#528

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The CSS change narrows the value column but does not add any overflow handling for
.stage-bar-value. Run Analytics generates potentially-long value strings (cost plus token totals)
that will not reliably fit in the new max widths.

src/observability/dashboard/ui/styles.js[1294-1310]
src/observability/dashboard/ui/pages/run-analytics.js[53-77]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new grid sizing makes the value column narrower, but the value cell doesn’t constrain overflow. Long strings (notably cost + tokens) can wrap or overflow, harming readability and potentially reintroducing “unreadable bar row” issues in other pages that use `.stage-bar-row`.

## Issue Context
`run-analytics` builds `.stage-bar-value` strings that can include both USD and compact token totals.

## Fix Focus Areas
- src/observability/dashboard/ui/styles.js[1294-1310]
- src/observability/dashboard/ui/pages/run-analytics.js[53-77]

### Suggested fix sketch
- Add truncation/containment for `.stage-bar-value`:
 - `overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;`
- Alternatively (or additionally) increase the max width of the third column if preserving full value text is required in narrow layouts.
- Keep the existing `title=...` attributes (already present in run-analytics) so full details remain accessible.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Browser test null deref ✓ Resolved 🐞 Bug ☼ Reliability
Description
tests/browser/dashboard-533-spend.test.js dereferences querySelector() results and the result of
Array.find() without asserting they exist, so regressions can fail with a TypeError instead of a
clear assertion message. This reduces test reliability and makes failures harder to diagnose.
Code

tests/browser/dashboard-533-spend.test.js[R56-70]

+  const bars = await page.evaluate(() => [...document.querySelectorAll('#cost-budget-stages .stage-bar-row')].map((row) => ({
+    label: row.querySelector('.stage-bar-label').textContent.trim(),
+    trackPx: Math.round(row.querySelector('.stage-bar-track').getBoundingClientRect().width),
+    fillPx: Math.round(row.querySelector('.stage-bar-fill').getBoundingClientRect().width),
+  })));
+  assert.equal(bars.length, 3);
+  for (const bar of bars) {
+    assert.ok(bar.trackPx >= 80,
+      `the bar track needs room to mean anything — ${bar.label} got ${bar.trackPx}px`);
+  }
+  // $3.55 vs $1.25 must be visibly different, not 4px vs 3px.
+  const biggest = bars.find((bar) => bar.label.includes('07-code'));
+  const smallest = bars.find((bar) => bar.label.includes('08-testing'));
+  assert.ok(biggest.fillPx - smallest.fillPx >= 20,
+    `a ~3x cost difference must be visible — ${biggest.fillPx}px vs ${smallest.fillPx}px`);
Relevance

●●● Strong

Clear precedent: team accepted adding guards to avoid TypeErrors from missing DOM/find() results in
browser tests.

PR-#528

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The test directly dereferences potentially-null DOM nodes and potentially-undefined .find()
results, which can throw TypeError on regressions; PR #528 documents the same pattern and the
recommended guards.

tests/browser/dashboard-533-spend.test.js[25-71]
PR-#528

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The browser test assumes DOM structure/labels exist and directly dereferences them. When markup changes or a regression removes a bar/label, the test will crash with a TypeError (inside page.evaluate or when reading biggest/smallest), producing poor diagnostics.

## Issue Context
This is the same failure mode previously accepted as a bug pattern: guard with explicit `assert.ok(...)` before dereferencing so failures stay actionable.

## Fix Focus Areas
- tests/browser/dashboard-533-spend.test.js[25-71]

### Suggested fix sketch
- In `openSpendCenter()`, return booleans for each click and assert they’re true.
- In the geometry test:
 - When mapping rows, safely handle missing elements (or throw a clear error string) instead of `row.querySelector(...).textContent`.
 - Add `assert.ok(biggest, ...)` and `assert.ok(smallest, ...)` before using `.fillPx`.
 - Optionally assert `Number.isFinite(trackPx/fillPx)` to avoid silent NaNs.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Seed overwrites project budget ✓ Resolved 🐞 Bug ≡ Correctness
Description
seedSpend calls fixtureSpendRun() and then fixtureBlockedRun(), but both call projectShell() and
write the project-level .rstack/budget.json, so fixtureBlockedRun overwrites the spend fixture’s
explicit runBudgetUsd=10 with the default 25. This makes the composed fixture order-dependent and
can silently change which cap the Spend Center renders in the browser journey.
Code

tests/browser/dashboard-533-spend.test.js[R22-24]

+// Real cost telemetry beside a run with none: both paths on one page.
+const seedSpend = async (root) => { await fixtureSpendRun(root); await fixtureBlockedRun(root); };
+
Relevance

●●● Strong

Deterministic fixture correctness bug (order-dependent budget overwrite); such test reliability
fixes are typically accepted.

PR-#532

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new seed composes two fixtures; one explicitly sets a $10 cap while the other resets the project
budget to its default ($25), because both invoke projectShell() which writes .rstack/budget.json.

tests/browser/dashboard-533-spend.test.js[20-37]
tests/helpers/dashboard-fixtures.js[29-36]
tests/helpers/dashboard-fixtures.js[145-148]
tests/helpers/dashboard-fixtures.js[295-307]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new browser seed composes two fixtures that each reinitialize project configuration (including budget). This causes the final budget cap to depend on call order, which can invalidate the intent of the spend fixture (and future assertions).

## Issue Context
- `fixtureSpendRun()` sets `runBudgetUsd: 10` via `projectShell`.
- `fixtureBlockedRun()` calls `projectShell(root)` with the default budget (25), overwriting the cap.

## Fix Focus Areas
- tests/browser/dashboard-533-spend.test.js[20-37]
- tests/helpers/dashboard-fixtures.js[29-36]
- tests/helpers/dashboard-fixtures.js[145-179]
- tests/helpers/dashboard-fixtures.js[295-338]

### Suggested fix options (pick one)
1) **Reverse the seed order** so the final cap matches the spend fixture:
  - `await fixtureBlockedRun(root); await fixtureSpendRun(root);`
2) Add a small composition helper (recommended):
  - Add an option like `fixtureBlockedRun(root, { skipProjectShell: true })` and use it from `seedSpend`.
3) After seeding both runs, explicitly rewrite `.rstack/budget.json` to the intended cap so the test is order-independent.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
4. Magic numbers in spend tests 📜 Skill insight ⚙ Maintainability
Description
dashboard-533-spend.test.js introduces multiple hard-coded numeric thresholds (e.g., timeouts and
pixel thresholds) instead of named constants, reducing clarity and making future tuning harder. This
violates the requirement to replace magic numbers with named constants.
Code

tests/browser/dashboard-533-spend.test.js[R33-70]

+  await page.waitForFunction(() => {
+    const el = document.getElementById('page-cost-budget');
+    return el && el.offsetParent !== null;
+  }, null, { timeout: 5000 });
+}
+
+suite.browserTest('the spend page draws real telemetry and stays honest about the run without it (#533)', { seed: seedSpend, tmpPrefix: 'rstack-browser-533-' }, async ({ page, server }) => {
+  await openSpendCenter(page, server);
+  const spend = await page.evaluate(() => ({
+    meters: [...document.querySelectorAll('#page-cost-budget .budget-fill')].length,
+    rows: [...document.querySelectorAll('#page-cost-budget .budget-row')].map((row) => row.textContent.replace(/\s+/g, ' ').trim()),
+    bars: [...document.querySelectorAll('#cost-budget-stages .stage-bar-row')].map((row) => row.textContent.replace(/\s+/g, ' ').trim()),
+  }));
+  assert.equal(spend.meters, 1, 'exactly one burn meter — the run WITH telemetry');
+  assert.equal(spend.rows.length, 2, 'both runs are listed under the cap');
+  assert.ok(spend.rows.some((row) => /no spend recorded yet against this cap/.test(row)),
+    'the run without telemetry says so instead of showing a 0% meter');
+  assert.equal(spend.bars.length, 3, 'three stages carry persisted cost');
+  assert.ok(spend.bars.some((bar) => bar.includes('$3.55')), 'with exact persisted values');
+});
+
+suite.browserTest('per-stage bars are actually readable, not 9px slivers (#533)', { seed: seedSpend, tmpPrefix: 'rstack-browser-533-' }, async ({ page, server }) => {
+  await openSpendCenter(page, server);
+  const bars = await page.evaluate(() => [...document.querySelectorAll('#cost-budget-stages .stage-bar-row')].map((row) => ({
+    label: row.querySelector('.stage-bar-label').textContent.trim(),
+    trackPx: Math.round(row.querySelector('.stage-bar-track').getBoundingClientRect().width),
+    fillPx: Math.round(row.querySelector('.stage-bar-fill').getBoundingClientRect().width),
+  })));
+  assert.equal(bars.length, 3);
+  for (const bar of bars) {
+    assert.ok(bar.trackPx >= 80,
+      `the bar track needs room to mean anything — ${bar.label} got ${bar.trackPx}px`);
+  }
+  // $3.55 vs $1.25 must be visibly different, not 4px vs 3px.
+  const biggest = bars.find((bar) => bar.label.includes('07-code'));
+  const smallest = bars.find((bar) => bar.label.includes('08-testing'));
+  assert.ok(biggest.fillPx - smallest.fillPx >= 20,
+    `a ~3x cost difference must be visible — ${biggest.fillPx}px vs ${smallest.fillPx}px`);
Relevance

●● Moderate

Mixed precedent: timeouts often extracted, but layout/pixel-threshold literals in browser tests were
also rejected.

PR-#509
PR-#531

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1400588 requires numeric literals used in conditions/timeouts to be extracted into
named constants. The added test uses { timeout: 5000 } and pixel thresholds like >= 80 and `>=
20` directly in assertions.

tests/browser/dashboard-533-spend.test.js[33-36]
tests/browser/dashboard-533-spend.test.js[62-70]
Skill: code-patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`tests/browser/dashboard-533-spend.test.js` uses hard-coded numeric literals (e.g., `5000`, `80`, `20`) in test logic.

## Issue Context
Compliance requires replacing magic numbers with named constants to make intent explicit and avoid scattered tuning.

## Fix Focus Areas
- tests/browser/dashboard-533-spend.test.js[33-36]
- tests/browser/dashboard-533-spend.test.js[62-70]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

5. Tests lack AAA separation 📜 Skill insight ▣ Testability
Description
New tests do not clearly separate Arrange, Act, and Assert phases (via comments or blank-line
structure), making them harder to scan and maintain. This violates the AAA-pattern testing
requirement.
Code

tests/hub-spend-visuals-533.test.js[R60-75]

+test('burn stays two-state — an "approaching" band would invent a judgment (#533)', () => {
+  // This child originally proposed an amber "approaching cap" band at 80%.
+  // The codebase had already decided against exactly that
+  // (dashboard-money-pages: "uses the exact loop boundary without an
+  // invented near threshold"), and it is right: run_budget_usd is enforced
+  // in code — the loop checks actual spend before every iteration and stops
+  // AT the cap. 80% is a judgment the harness never makes, so painting it
+  // would be the UI asserting a signal the system does not have. Pinned here
+  // so the idea is not re-implemented by a future reader of this issue.
+  const html = renderer().budgetGovernanceHtml(spendModel([
+    measurement({ costUsd: 8.6, cap: { status: 'within_cap', runBudgetUsd: 10, usedPercent: 86, remainingUsd: 1.4 } }),
+  ]));
+  assert.match(html, /budget-fill ok/, 'below the cap is one state, whatever the percentage');
+  assert.match(html, /within enforced cap/, 'and it says exactly that');
+  assert.doesNotMatch(html, /approaching|near|warning/i, 'no invented middle threshold');
+});
Relevance

● Weak

Close precedent: refactors to enforce AAA separation in tests were rejected as guideline-only.

PR-#530

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1400638 requires tests to follow the Arrange-Act-Assert pattern with clear
separation. In the added test, setup, execution, and assertions are not explicitly separated into
distinct AAA phases.

tests/hub-spend-visuals-533.test.js[60-75]
Skill: testing-qa

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
New tests should be structured into clear Arrange/Act/Assert phases.

## Issue Context
Compliance expects AAA separation (ideally with comments or spacing) to keep tests readable and maintainable.

## Fix Focus Areas
- tests/hub-spend-visuals-533.test.js[60-75]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Missing regression attribution header 📜 Skill insight ⚙ Maintainability
Description
A new Spend Center/browser visuals regression test references issue #533, but its header comment
is missing required attribution details like the date the regression was found and the QA report
path, violating the regression test attribution requirement and reducing traceability.
Code

tests/hub-spend-visuals-533.test.js[R1-12]

+/**
+ * #533 — Spend Center visuals (epic #500, child 3).
+ *
+ * Finding that reshaped this child: the budget meter and per-stage bars
+ * ALREADY existed — but no canonical fixture wrote metrics.json, so their
+ * drawn path had never rendered in a test or a demo. Most of the work here
+ * is therefore characterization (pin what was shipped-but-unexercised) plus
+ * one genuine gap: burn was two-state (green until exhausted), so a run at
+ * 97% of its cap looked exactly as comfortable as one at 5%.
+ *
+ * owner: RStack developed by Richardson Gunde
+ */
Relevance

● Weak

Close precedent: requests to add regression header attribution metadata (date/report path) were
explicitly rejected.

PR-#524

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1400067 requires regression tests to include an attribution comment containing the
issue ID, what broke, the date found, and a QA report path. In the newly added test file, the header
comment mentions #533, but the cited header lines do not include any date information or a
Report:/QA report reference, demonstrating the attribution metadata is incomplete.

tests/hub-spend-visuals-533.test.js[1-12]
tests/browser/dashboard-533-spend.test.js[1-11]
Skill: qa-testing

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The test header comment does not include the full regression attribution metadata required by the checklist (issue id, what broke, date found, QA report path).

## Issue Context
This file acts as a regression/characterization test for issue `#533`, but it currently documents only the issue reference and omits when the regression was found and where the QA report lives, which hurts traceability for why the test exists and how it was validated.

## Fix Focus Areas
- tests/hub-spend-visuals-533.test.js[1-12]
- tests/browser/dashboard-533-spend.test.js[1-11]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Non-verb function names added 📜 Skill insight ⚙ Maintainability
Description
New helper functions renderer, measurement, and spendModel do not follow the required
verb-noun naming pattern, reducing clarity/consistency. This violates the project naming compliance
rule for JS/TS functions.
Code

tests/hub-spend-visuals-533.test.js[R24-56]

+function renderer() {
+  const document = {
+    getElementById: () => null,
+    createElement: () => ({ innerHTML: '', style: {}, appendChild() {}, setAttribute() {}, className: '' }),
+    querySelector: () => null,
+    querySelectorAll: () => [],
+    addEventListener() {},
+  };
+  return new Function('document', `${libScript}\n${runAnalyticsScript}\n${costBudgetScript}\nreturn {
+    budgetGovernanceHtml, stageCostAcrossRunsHtml,
+  };`)(document);
+}
+
+function measurement(overrides = {}) {
+  return {
+    runId: 'run-fx-spend',
+    availability: 'available',
+    costUsd: 6.2,
+    metricsSource: 'persisted',
+    measuredAt: null,
+    cap: { status: 'within_cap', runBudgetUsd: 10, usedPercent: 62, remainingUsd: 3.8 },
+    ...overrides,
+  };
+}
+
+function spendModel(runs) {
+  return {
+    businessFlex: {
+      configuredPolicy: { projects: [{ budget: { availability: 'configured', runBudgetUsd: 10 } }] },
+      observedConsumption: { runCount: runs.length, totalCostUsd: 6.2, runs },
+    },
+  };
+}
Relevance

● Weak

Close precedent: verb-noun function renaming in tests was rejected; naming rule not enforced.

PR-#511

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1399694 requires verb-noun function naming. The added helpers are declared as
function renderer(), function measurement(...), and function spendModel(...), which are not
verb-led action names.

tests/hub-spend-visuals-533.test.js[24-56]
Skill: code-patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new helper functions are named with noun-only/vague identifiers (`renderer`, `measurement`, `spendModel`) rather than a verb-noun action pattern.

## Issue Context
Compliance requires JS/TS function names to start with an action verb (e.g., `create`, `build`, `make`, `render`) followed by a noun.

## Fix Focus Areas
- tests/hub-spend-visuals-533.test.js[24-56]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment on lines +33 to +70
await page.waitForFunction(() => {
const el = document.getElementById('page-cost-budget');
return el && el.offsetParent !== null;
}, null, { timeout: 5000 });
}

suite.browserTest('the spend page draws real telemetry and stays honest about the run without it (#533)', { seed: seedSpend, tmpPrefix: 'rstack-browser-533-' }, async ({ page, server }) => {
await openSpendCenter(page, server);
const spend = await page.evaluate(() => ({
meters: [...document.querySelectorAll('#page-cost-budget .budget-fill')].length,
rows: [...document.querySelectorAll('#page-cost-budget .budget-row')].map((row) => row.textContent.replace(/\s+/g, ' ').trim()),
bars: [...document.querySelectorAll('#cost-budget-stages .stage-bar-row')].map((row) => row.textContent.replace(/\s+/g, ' ').trim()),
}));
assert.equal(spend.meters, 1, 'exactly one burn meter — the run WITH telemetry');
assert.equal(spend.rows.length, 2, 'both runs are listed under the cap');
assert.ok(spend.rows.some((row) => /no spend recorded yet against this cap/.test(row)),
'the run without telemetry says so instead of showing a 0% meter');
assert.equal(spend.bars.length, 3, 'three stages carry persisted cost');
assert.ok(spend.bars.some((bar) => bar.includes('$3.55')), 'with exact persisted values');
});

suite.browserTest('per-stage bars are actually readable, not 9px slivers (#533)', { seed: seedSpend, tmpPrefix: 'rstack-browser-533-' }, async ({ page, server }) => {
await openSpendCenter(page, server);
const bars = await page.evaluate(() => [...document.querySelectorAll('#cost-budget-stages .stage-bar-row')].map((row) => ({
label: row.querySelector('.stage-bar-label').textContent.trim(),
trackPx: Math.round(row.querySelector('.stage-bar-track').getBoundingClientRect().width),
fillPx: Math.round(row.querySelector('.stage-bar-fill').getBoundingClientRect().width),
})));
assert.equal(bars.length, 3);
for (const bar of bars) {
assert.ok(bar.trackPx >= 80,
`the bar track needs room to mean anything — ${bar.label} got ${bar.trackPx}px`);
}
// $3.55 vs $1.25 must be visibly different, not 4px vs 3px.
const biggest = bars.find((bar) => bar.label.includes('07-code'));
const smallest = bars.find((bar) => bar.label.includes('08-testing'));
assert.ok(biggest.fillPx - smallest.fillPx >= 20,
`a ~3x cost difference must be visible — ${biggest.fillPx}px vs ${smallest.fillPx}px`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

3. Magic numbers in spend tests 📜 Skill insight ⚙ Maintainability

dashboard-533-spend.test.js introduces multiple hard-coded numeric thresholds (e.g., timeouts and
pixel thresholds) instead of named constants, reducing clarity and making future tuning harder. This
violates the requirement to replace magic numbers with named constants.
Agent Prompt
## Issue description
`tests/browser/dashboard-533-spend.test.js` uses hard-coded numeric literals (e.g., `5000`, `80`, `20`) in test logic.

## Issue Context
Compliance requires replacing magic numbers with named constants to make intent explicit and avoid scattered tuning.

## Fix Focus Areas
- tests/browser/dashboard-533-spend.test.js[33-36]
- tests/browser/dashboard-533-spend.test.js[62-70]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread tests/browser/dashboard-533-spend.test.js Outdated
Comment thread tests/browser/dashboard-533-spend.test.js
Comment thread src/observability/dashboard/ui/styles.js Outdated
Qodo, all three real:

1. Seed order silently changed the chart. Every fixture's projectShell
   rewrites .rstack/budget.json, so composing spend-then-blocked let the
   default $25 cap overwrite the spend fixture's explicit $10. I SAW this
   during live verification — the demo rendered $25 — and waved it off as a
   harmless artifact; it is a real order dependency. The seed now runs
   fixtureSpendRun last and the journey asserts the expected cap, so a
   future reordering fails loudly instead of quietly redrawing the meter.
2. The browser test dereferenced querySelector/find results unguarded, so
   a regression would surface as a TypeError instead of a diagnosis.
3. .stage-bar-value sits in a bounded column but had no overflow policy —
   Run Analytics prints cost AND tokens there, which would wrap and
   re-inflate the row. It ellipses now.

Refs #533 (PR #534 review follow-up).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@richard-devbot

Copy link
Copy Markdown
Owner Author

All three Qodo findings applied — all three were real.

The second one is worth calling out: every fixture's projectShell rewrites .rstack/budget.json, so my composed seed let fixtureBlockedRun's default $25 cap overwrite the spend fixture's explicit $10. I saw that during live verification — the screenshot in the PR body shows "of $25.00" — and dismissed it as a harmless demo artifact rather than the order dependency it is. Fixed by seeding the spend fixture last and asserting the expected cap in the journey, so any future reordering fails loudly instead of silently redrawing the meter.

Also: guarded the browser test's derefs so regressions report a diagnosis rather than a TypeError, and gave .stage-bar-value an ellipsis policy (Run Analytics prints cost AND tokens into that bounded column, which would have wrapped).

Core 1921/1921, browser 32/32, lint clean. — Claude Main

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Spend Center visuals: budget burn gauge + spend-by-stage bars (epic #500, child 3)

2 participants