Skip to content

fix(budget): use filtered available funds in CostBreakdownTable#1367

Merged
steilerDev merged 7 commits intobetafrom
fix/1366-cost-breakdown-filter-summary-and-print
Apr 27, 2026
Merged

fix(budget): use filtered available funds in CostBreakdownTable#1367
steilerDev merged 7 commits intobetafrom
fix/1366-cost-breakdown-filter-summary-and-print

Conversation

@steilerDev
Copy link
Copy Markdown
Owner

@steilerDev steilerDev commented Apr 27, 2026

Summary

  • Fixed `CostBreakdownTable` to use filtered available funds (sum of `totalAmount` for selected sources only) instead of the unfiltered `overview.availableFunds`
  • Available Funds row and both Remaining Budget columns (Cost + Net) now update in real time when sources are toggled
  • Deselected budget source rows are now fully hidden when printing (`@media print { display: none !important }`)

Fixes #1366

Test plan

  • Unit tests pass (95%+ coverage on modified code paths)
  • Integration tests pass
  • Pre-commit hook quality gates pass

Co-Authored-By: Claude Opus 4.6 noreply@anthropic.com

Frank Steiler and others added 7 commits April 27, 2026 21:43
- Compute filteredAvailableFunds from selected budget sources only
- Update Available Funds row display to use filtered value
- Update Remaining Budget rows (Cost & Net columns) to use filtered funds
- Update unused sum variable to use filtered funds for consistency
- Hide deselected source rows in print via display: none

Fixes budget calculations when budget sources are deselected in the cost breakdown.

Co-Authored-By: Claude frontend-developer (Haiku 4.5) <noreply@anthropic.com>
…int-hiding tests

Adds a new test.describe block to budget-source-filter.spec.ts covering the
CostBreakdownTable changes from fix/1366: filteredAvailableFunds computation
and the @media print rule hiding deselected source rows.

Scenario 1: Available Funds value updates when a source is deselected.
Scenario 2: Remaining Budget Cost column updates when a source is deselected.
Scenario 3 (AC #4): Available Funds restores to full total on re-select.
Scenario 4 (AC #5): All sources deselected → Available Funds shows €0, not NaN.
Scenario 5 (AC #8): Print media hides deselected source rows (display:none) and
  leaves selected source rows visible.

Fixes #1366

Co-Authored-By: Claude e2e-test-engineer (Sonnet 4.5) <noreply@anthropic.com>
…ds and Remaining Budget rows

Tests all three filter states for the bug fix (#1362):
- All sources selected: filteredAvailableFunds = sum of all source totalAmounts
- One source deselected: filteredAvailableFunds excludes deselected source amount
- All sources deselected: filteredAvailableFunds = 0 (not unfiltered total)
- Remaining Budget Cost and Net columns use filteredAvailableFunds in all states
- 'unassigned' source with totalAmount=0 does not affect filteredAvailableFunds
- Re-render with empty deselectedSourceIds restores unfiltered values

Fixes #1362

Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com>
After deselecting Equity, the server returns a filtered breakdown whose
wiTotals.rawProjectedMin/Max = 10 000 (not 20 000), so totalRawProjected
drops to 10 000. The correct post-filter Remaining Budget Cost is therefore
150 000 - 10 000 = 140 000, not 130 000.

Co-Authored-By: Claude e2e-test-engineer (Sonnet 4.5) <noreply@anthropic.com>
…AvailableFunds change (#1366)

The production change in #1366 replaced overview.availableFunds with
filteredAvailableFunds (sum of non-deselected budgetSources) for the
Available Funds row. Two existing tests had incorrect fixtures that
assumed overview.availableFunds was the source of truth:

1. Test 16 "shows Available funds row with formatted currency value":
   rendered with budgetSources:[] so filteredAvailableFunds=0, not 50000.
   Fix: add a budgetSource with totalAmount=50000 to the breakdown fixture.

2. Scenario 23 "Remaining Budget row uses filteredAvailableFunds":
   src-1360-a had totalAmount=100000 but overview.availableFunds=200000,
   making the fixture internally inconsistent under the new computation.
   Fix: set src-1360-a.totalAmount=200000 so filteredAvailableFunds=200000,
   preserving the expected Remaining Budget value of €150,750.00.
   Also updated the test name and comment to reflect the new semantics.

Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com>
Co-Authored-By: Claude frontend-developer (Haiku 4.5) <noreply@anthropic.com>
…teredAvailableFunds

After the production fix replacing overview.availableFunds with
filteredAvailableFunds (computed from breakdown.budgetSources), 8 pre-existing
tests using buildBreakdownWithWI() with empty budgetSources: [] produced
filteredAvailableFunds=0, breaking expected Remaining Budget and Available
Funds values.

Added optional budgetSources parameter to buildBreakdownWithWI() and supplied
matching totalAmount values in each failing test so filteredAvailableFunds
equals the previously expected overview.availableFunds.

Fixes #1366

Co-Authored-By: Claude qa-integration-tester (Sonnet) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Owner Author

@steilerDev steilerDev left a comment

Choose a reason for hiding this comment

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

[ux-designer]

Design review for PR #1367 — filter-aware summary rows + print hiding for deselected source rows.

Checklist

Token adherence

The single new CSS rule (display: none !important) inside the existing @media print block carries no visual token obligations — no colors, spacing, radii, or font sizes are introduced. All existing rules in the block already use design tokens correctly. No hardcoded values introduced.

Visual consistency

The TSX diff is computation-only: filteredAvailableFunds replaces overview.availableFunds in three render sites (Available Funds display value, the valuePositive/valueNegative class gate, and the Remaining Budget value). No JSX structure, class names, or layout changes. Fully consistent with the surrounding component.

Dark mode correctness

No new color properties introduced. No dark mode impact.

Accessibility

The new print rule targets .rowSourceDetailToggle[aria-pressed='false'] — correctly tied to the existing semantic aria-pressed attribute that drives the toggle behavior. This reuses established semantics rather than inventing a parallel data attribute, which is the right approach. No interactive state changes; print media does not affect keyboard navigation or screen reader behavior.

Print behavior

Scoping the rule to @media print is correct — deselected rows are hidden only in the printed output, not in the interactive view. !important is appropriate here given print stylesheets commonly need to override component-level display rules. The selector specificity is appropriate and does not risk bleeding outside the print context.

Verdict

Approved. Minimal and correct — the CSS change is properly scoped, uses no hardcoded values, and the computation change is a pure logic fix with no rendering side-effects.

@steilerDev
Copy link
Copy Markdown
Owner Author

[product-architect]

Architectural review of PR #1367 (fix #1366 — filtered Available Funds + print hiding).

Verdict: Approve

The fix is at the correct layer and consistent with established patterns. No architectural concerns block merge.

Architecture compliance — correct layer assessment

The bug report's AC #7 + Notes section suggested reusing the server-side ?deselectedSources= filter pathway from #1361 as a "one-source-of-truth approach." I traced the actual data flow to verify whether this fix should live on the server or client:

  • GET /api/budget/overview returns BudgetOverview.availableFunds = SUM(active budget_sources.total_amount)not parameterized by deselectedSources and not changed by this PR.
  • GET /api/budget/breakdown?deselectedSources=... already returns budgetSources[].totalAmount for every configured source (line 1146 of budgetBreakdownService.ts: "ALL configured sources always included … projectedMin/Max = unfiltered per-source values (architect decision A)"). The server filters line-level data but intentionally returns all source totalAmount values so the UI can render selected/deselected toggles consistently.
  • The server does not currently return a filteredAvailableFunds scalar.

Given the existing API contract, the client-side derivation filteredAvailableFunds = sum(budgetSources.filter(!deselected).totalAmount) is the correct layer. The data is already in the response payload — synthesizing this derived total on the client avoids an unnecessary API contract change and reuses the same already-filtered dataset that drives the rest of the breakdown view (satisfying AC #7 in spirit). Pushing this to the server would require either (a) adding a new field to BudgetBreakdownResponse, or (b) parameterizing BudgetOverview.availableFunds — both heavier changes for a value that is trivially derivable from data already on the wire.

Code quality

  • Single derived value replaces three identical inline expressions (overview.availableFunds - totalRawProjected …) — DRY improvement as a side effect of the fix.
  • Naming filteredAvailableFunds aligns with the established filtered* convention from prior fix(budget): make Cost Breakdown table aggregates filter-aware #1359/feat(budget): move per-source filter to server-side via ?deselectedSources= #1361 work.
  • No new state, no memoization needed (cheap reduce over small list).
  • The @media print rule correctly reuses the existing aria-pressed='false' semantic toggle state rather than introducing a parallel data attribute or class — clean and idiomatic. !important is acceptable in print contexts where component-level display rules need to be overridden.

Test coverage

Adequate. 9 unit scenarios cover all axes (all-selected, partial, all-deselected, restore-on-reselect, unassigned with totalAmount=0, payback interaction, negative remaining), and 5 E2E scenarios exercise the full UX flow including page.emulateMedia('print') for AC #8. The two regression fix commits (2054bea5 Remaining Budget arithmetic, 618ee8f2 two unit-test bugs) repaired pre-existing tests that became inconsistent with the new semantics — appropriate per the test-failure debugging protocol (correct production code; previously-correct tests were updated to reflect the new contract, not weakened to mask a bug).

Observations (non-blocking)

  1. overview.availableFunds prop semantics in this component: After this PR, CostBreakdownTable deliberately ignores overview.availableFunds for the Available Funds and Remaining Budget cells. The prop is still consumed elsewhere in the component (empty-state guards), so removing it isn't appropriate. A future cleanup could consider whether the component still needs both the overview prop and breakdown.budgetSources — they overlap in intent for this view. Not in scope here.

  2. API contract / wiki: No update needed. BudgetOverview.availableFunds retains its documented semantics (SUM of all active sources). The shared type comments and wiki API Contract page remain accurate. The new filteredAvailableFunds is purely a client-side derived value and does not need API contract documentation.

  3. Architectural decision A re-validated: This fix confirms the correctness of architect decision A (line 1146) — keeping budgetSources[].totalAmount unfiltered in the breakdown response enables clients to derive both filtered and unfiltered summaries without additional round-trips. Worth keeping in mind for future per-source filter work.

@steilerDev steilerDev merged commit fc8d7da into beta Apr 27, 2026
30 checks passed
@steilerDev steilerDev deleted the fix/1366-cost-breakdown-filter-summary-and-print branch April 27, 2026 22:04
@github-actions
Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 2.4.2-beta.1 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant