Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
747f000
fix(reports): guard in-flight AI generation against use-case changes …
steilerDev Aug 3, 2026
66429c3
fix(budget): show actual invoiced amount in source lines and used total
steilerDev Aug 3, 2026
6dc2c66
fix(reports): reinstate legend sentences for split and deposit-reduce…
steilerDev Aug 3, 2026
0146afb
fix(data-table): surface failed column-preference saves as error toas…
steilerDev Aug 4, 2026
ed2b7b3
fix(reports): running header timestamp and German word-break (#1937, …
steilerDev Aug 4, 2026
90dc830
fix(budget): make budget-source drill-down deposit-aware (#1897)
steilerDev Aug 4, 2026
ae902e5
fix(e2e): make email-search test self-contained and filtering-asserti…
steilerDev Aug 4, 2026
a3a30e1
fix(e2e): column-visibility E2E coverage + decouple testPrefix from a…
steilerDev Aug 4, 2026
ccde679
fix(server): calendar-drift test fixtures + LLM plain-prose enforceme…
steilerDev Aug 4, 2026
217cb40
fix(reports): remove dead attachmentsNote override and refactor Repor…
steilerDev Aug 4, 2026
a5aa1dd
feat(auth): make rate limits configurable via AUTH_RATE_LIMIT_MAX/WINDOW
steilerDev Aug 4, 2026
679e19c
chore(deps): bump the github-actions group across 1 directory with 3 …
dependabot[bot] Aug 4, 2026
56421af
chore(deps): bump the prod-dependencies group across 1 directory with…
dependabot[bot] Aug 4, 2026
ef13462
chore(deps): bump dev-dependencies and dedupe webpack lockfile
dependabot[bot] Aug 4, 2026
56980ed
fix(deps): remediate GHSA-rhx6-c78j-4q9w in brace-expansion
steilerDev Aug 4, 2026
4f23feb
docs(memory): record #1973 column-visibility rulings and the rejected…
steilerDev Aug 4, 2026
18a87be
fix(deps): remediate GHSA-g4rg-993r-mgx8 in undici (credential leak/S…
steilerDev Aug 4, 2026
3216e23
fix(llm): rename to llmGateway, extract computeIncludedTotal to share…
steilerDev Aug 4, 2026
c1b4f5b
feat(subsidies): add No Category option to applicable categories
steilerDev Aug 4, 2026
3134518
fix(security): remove custom keyGenerator to restore IPv6 normalizati…
steilerDev Aug 4, 2026
f612888
fix(rate-limit): extract rateLimitKeyGenerator, add IPV6_SUBNET const…
steilerDev Aug 4, 2026
b69a81f
fix(reports): harden pageFooter locale contract via ReportContentLabe…
steilerDev Aug 4, 2026
329812f
fix(reports): route usage-cell grey meta suffix through per-token run…
steilerDev Aug 5, 2026
61c3b30
feat(reports): lang attribute for mixed-locale preview, attachment note
steilerDev Aug 5, 2026
1d5314f
fix(e2e): correct mockInvoicesFullSummary fixture for InvoiceStatusBr…
steilerDev Aug 5, 2026
4b5df10
refactor(reports): remove TFunction from reportPdf/* (ADR-034 enforce…
steilerDev Aug 5, 2026
debe5e4
fix(reports): falsifiable _minWidth overflow and legend tests for ove…
steilerDev Aug 5, 2026
7cfb45c
feat(reports): wire report column visibility toggles through to the g…
steilerDev Aug 5, 2026
d7d2d73
feat(reports): add splitKind to distinguish deposit-driven splits fro…
steilerDev Aug 6, 2026
74ccfaf
docs(wiki): remove nonexistent OIDC_REDIRECT_URI and correct the OIDC…
steilerDev Aug 6, 2026
7461b1a
docs: document the auth rate-limit environment variables (#1990)
steilerDev Aug 6, 2026
76a5613
feat(reports): report wizard code-quality follow-ups (#1912)
steilerDev Aug 6, 2026
7b18068
fix(e2e): scope diary type-chip waits to the request under assertion …
steilerDev Aug 6, 2026
875bf5e
feat(reports): merge runt continuation chunks and mark continuation r…
steilerDev Aug 6, 2026
64297df
feat(reports): length limits on the report wizard's editable override…
steilerDev Aug 6, 2026
5e7f073
refactor(reports): split LETTER_SUBJECT_FONT_SIZE from SUBHEADER_FONT…
steilerDev Aug 6, 2026
399bcce
refactor(config): reject non-integer numeric env vars at startup (#1991)
steilerDev Aug 6, 2026
66600d2
test(reports): guard the derived Uk ceiling against geometry drift (#…
steilerDev Aug 6, 2026
bc48391
docs(memory): record the pickup-time premise-verification lesson
steilerDev Aug 6, 2026
d0abba9
chore(agents): rework agentic setup for lean single-session delivery
steilerDev Aug 6, 2026
e255fc1
docs: update documentation for release
steilerDev Aug 6, 2026
946749d
chore: update implementation checklist with lessons learned
steilerDev Aug 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion .claude/agent-memory/docs-writer/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
- `getting-started/` -- index, docker-setup, first-login, configuration
- `guides/work-items/` -- index, creating-work-items, tags, notes-and-subtasks, dependencies, keyboard-shortcuts
- `guides/users/` -- index, oidc-setup, admin-panel
- `guides/budget/` -- index, categories, financing-sources, work-item-budgets, vendors-and-invoices, subsidies, budget-overview
- `guides/budget/` -- index, categories, financing-sources, work-item-budgets, vendors-and-invoices, subsidies, budget-overview, bank-reports (added PR #2041, see [Bank Report Wizard docs](bank-report-wizard-docs.md))
- `guides/timeline/` -- index, gantt-chart, milestones, calendar-view
- `guides/documents/` -- index, setup, browsing-documents, linking-documents
- `guides/household-items/` -- index, creating-editing-items, budget-and-invoices, work-item-linking, delivery-and-dependencies
Expand Down Expand Up @@ -103,6 +103,20 @@ Three user-facing changes documented (no new pages, no sidebar changes):

Pre-existing lint baseline in worktree: ~8 eslint ERRORS in production/test .ts across client/server/e2e (photoService OrientationSummary unused, usePaperless import() type, etc.) -- NOT introduced by docs changes (docs edits are markdown-only; eslint doesn't lint .md). Likely a local `npm install --ignore-scripts` artifact since beta CI requires lint green. Do NOT touch those files as docs-writer.

## Release: PR #2041 (Bank Report Wizard docs gap, subsidies drift fix, .env.example drift fix)

Full env-var scan (`grep` for `getValue('...')` in `server/src/`) confirmed 3 vars missing/wrong in `.env.example`: `AUTH_RATE_LIMIT_MAX` and `AUTH_RATE_LIMIT_WINDOW` (new, added commented-out under Server section near `TRUST_PROXY`/`EXTERNAL_URL`) and `VAT_RATE` (pre-existing gap, added commented-out under Localization near `CURRENCY`). `CLAUDE.md`'s env-var table was already current for all three (added in an earlier commit within the same 40-commit range) -- always diff `.env.example` against `server/src/plugins/config.ts` directly rather than assuming the two docs surfaces drift together.

New page `guides/budget/bank-reports.md` closes a total docs gap for the Bank Report Wizard feature -- see [Bank Report Wizard docs](bank-report-wizard-docs.md) for what it covers and why the gap existed. Same pass also rewrote stale portions of `subsidies.md` (multi-category + "No Category" + real 5-status enum) -- same memory file has the detail.

## Build Note (still true)

`npm run docs:build` fails in worktrees with webpack `ProgressPlugin` ValidationError (node_modules corruption, NOT content). Build reaches the webpack bundling stage, so MDX/content/link loading succeeded. Validate internal links/anchors statically with grep instead; CI does the real build.

Update: in the `batch-develop-1973` worktree (2026-08), `npm run docs:build` completed successfully end-to-end (only the expected pre-existing screenshot-image warnings) -- the corruption is worktree-instance-specific, not universal. Still try the build first; fall back to static grep validation only if it actually fails.

**CI is green-by-vacuity on docs-only PRs.** `Detect Changes` routes a docs-only diff so every real CI job (including the `onBrokenAnchors: 'throw'` docs build) skips, and the required wrapper gate still reports success. `Quality Gates` passing on a docs-only PR is NOT evidence the anchors/links resolve -- the real `docusaurus build` with strict broken-link/anchor checking only runs at release time. Always run `npm run docs:build` locally yourself after any edit touching links or headings; don't rely on CI for this on docs-only PRs.

## configuration.md sections

`docs/src/getting-started/configuration.md` has an `## Authentication Rate Limiting` section (added for #1990) documenting `AUTH_RATE_LIMIT_MAX`/`AUTH_RATE_LIMIT_WINDOW` (defaults 20 / `15 minutes`, `ms`-format window, startup-failure-on-invalid-value caveat, and household-NAT-vs-internet-exposed tuning guidance), cross-linked with the `## Reverse Proxy` section's `TRUST_PROXY` explanation (bucket-by-proxy-IP vs bucket-by-client-IP). The setup-endpoint's fixed 5/15min limit is mentioned as non-configurable, per issue Notes. This is the only general env-var reference table on the docs site -- `guides/users/oidc-setup.md` has a small OIDC-scoped var table but it's not a second general reference.
40 changes: 40 additions & 0 deletions .claude/agent-memory/docs-writer/bank-report-wizard-docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
name: bank-report-wizard-docs
description: Bank Report Wizard had zero docs-site coverage until PR #2041 (2026-08-06); new guide at guides/budget/bank-reports.md, plus the subsidies.md drift that was fixed in the same pass
metadata:
type: project
---

# Bank Report Wizard docs gap (closed 2026-08-06, PR #2041)

**Why this mattered:** EPIC-07 (Reporting and Export) had been checked off in the roadmap for a long
time, and the feature (`client/src/pages/ReportWizardPage`, route `/budget/reports`) had grown into a
large, actively-developed area (see `product-owner`'s `bank-report-wizard.md` memory for the full
mini-epic history), but `docs/src/` and `docs/sidebars.js` had **no page for it at all** -- not even a
stub. A release task that assumed "extend the existing reports docs" surfaced the gap.

**How to apply:** The gap is now closed -- `docs/src/guides/budget/bank-reports.md` (sidebar position 9,
registered in `docs/sidebars.js` under the Budget category, cross-linked from `guides/budget/index.md`).
It documents the wizard as a single comprehensive page (index-style, no sub-pages, matching the
`guides/backup/index.md` pattern) covering: the 3 report types (Budget Overview / Claim / Proof of
Funds) and their invoice-status eligibility, the 5 wizard steps (Report Type, Budget Source, Select
Invoices, Settings, Preview & Export), column visibility toggles, AI-assisted generation
("Enhance with AI", gated on `llmEnabled`), marking invoices claimed, and long-content/multi-page PDF
handling. **Before extending this page**, re-derive current UI/copy from
`client/src/i18n/en/budget.json` (`sourceReports` key) and `ReportWizardPage.tsx` rather than trusting
this page alone to stay current -- the feature has a long history of fast iteration (see
`[[release-notes-drift]]` if that file exists, or the product-owner memory directly).

## Related fix in the same pass: subsidies.md was stale, not just missing "No Category"

`docs/src/guides/budget/subsidies.md` described a **single** "Budget Category" field and a **4-status**
lifecycle (Pending/Approved/Rejected/Disbursed) that no longer matched the shipped `SubsidyProgram`
type (`shared/src/types/subsidyProgram.ts`): categories are actually **multi-select**
(`applicableCategories: BudgetCategory[]`, empty = universal) with an independent `includesNoCategoryItems`
("No Category") checkbox, and the real status enum is
`eligible | applied | approved | received | rejected` (only `approved`/`received` count toward budget
math). This predated the "No Category" release task -- rewrote the whole Creating/Statuses/How-it-affects
sections rather than just appending the new checkbox, since the old text would have stayed actively
wrong. **Lesson: when a task says "add feature X to this doc", verify the doc's existing claims against
the current type/schema before touching it — drift compounds silently on release cycles that only ever
append.**
2 changes: 2 additions & 0 deletions .claude/agent-memory/e2e-test-engineer/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@

- [issue-1959-inline-meta-and-labels.md](issue-1959-inline-meta-and-labels.md) — #1959 reversed #1923's †/‡ footnotes → inline `(partial)`/`(less deposit)` and merged area+attachments into one `.usageMetaText` line; POM renames, rewritten scenarios 2/17/18/20 + AI 8, attachment-tier facts, untested column toggles.
- [claim-deposit-scope-1922.md](claim-deposit-scope-1922.md) — PR #1922 invoice/deposit claim-scope split: `handleMarkClaimed`'s two-array submit, server-truth success-banner counts, the three "deposit surfaces the invoice" shapes, `claimNothingClaimable` guard.
- [issue-1973-column-visibility-pdf.md](issue-1973-column-visibility-pdf.md) — column-visibility toggles wired to the PDF (supersedes #1966): reducer reset facts (use-case change resets `hiddenColumns`, `SET_ATTACH_DOCUMENTS` doesn't), Scenarios 28-33, size-diff PDF-consequence proof pattern, `goBack()` viewport-independent multi-step walk, new `columnToggleGroup`/`usageHiddenAttachmentsWarning` POM locators.
- [issue-1911-splitkind-e2e.md](issue-1911-splitkind-e2e.md) — #1911 `splitKind` field: incoming spec said "no E2E changes needed", was wrong — fixed Scenario 18's mis-seeded fixture AND found Scenario 17 independently affected (zero-contribution-line case) by cross-referencing unit-test ACs; new AC-3.2 regression-guard sibling test; POM docblock corrections.

## Open follow-ups to flag to orchestrator

Expand Down
126 changes: 126 additions & 0 deletions .claude/agent-memory/e2e-test-engineer/issue-1911-splitkind-e2e.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
---
name: issue-1911-splitkind-e2e
description: Issue #1911 (SourceReportInvoice.splitKind) E2E fallout in reportWizardEditableContent.spec.ts — dev-team-lead's "no E2E changes needed" spec conclusion was wrong; two scenarios (17 AND 18) were affected, found by cross-referencing server/client unit-test ACs, not by running the browser suite.
metadata:
type: project
---

## What #1911 actually changed (semantics, not just a field addition)

`server/src/services/sourceReportService.ts` step f: `isSplit`/`isDepositReduced` used to be
derived client-side from `invoice.isSplit(raw) && budgetLines.length>0` /
`invoice.isSplit(raw) && deposits.length>0 && !ownTagged`. Both gates were unsound (claim reports
drop zero-contribution budget lines; a foreign-tagged deposit never appears in `deposits[]` at
all). #1911 replaced them with a purely server-derived `splitKind: 'lines'|'deposits'|'both'|null`
(SQL computes `has_foreign_line_source`/`has_foreign_deposit_source` — "this arm contains a source
≠ the reported one", untagged deposits excluded from the deposit arm entirely since the query
filters `budget_source_id IS NOT NULL`). `buildReportContent.ts`: `row.isSplit ⟺ splitKind ∈
{'lines','both'}`, `row.isDepositReduced ⟺ splitKind ∈ {'deposits','both'}`. `row.isDeposit`
(constituted-deposit badge) trigger is UNCHANGED and now independent of the other two — all three
can co-occur on one row (old code's implicit either/or is gone).

## Two E2E scenarios were affected, not one — the incoming spec only caught the second

`e2e/tests/budget/reportWizardEditableContent.spec.ts`:

- **Scenario 18** ("split + deposit-reduced labels"): `invoice3`'s deposit was seeded
`budgetSourceId: null` (untagged) with a comment claiming that produces `isDepositReduced: true`
— true under the OLD buggy code, false under the fix (this was the literal bug #1911 exists to
fix). Retagged to `otherSourceId` (a source ≠ the reported one) to get the genuine
`splitKind: 'both'` shape.
- **Scenario 17** ("constituted-deposit row … carries NO marker/label"): NOT flagged by the
incoming spec at all, found by grepping every `createDepositViaApi(...budgetSourceId...)` call
site per the task's own hint ("tagged to the reported source" is one of the two shapes to
check). Its invoice has budget lines ENTIRELY on a different source (A) and a deposit tagged to
the reported source (B) itself. Under the OLD code: `isSplit(row)` was gated by
`budgetLines.length>0` for B, which is 0 → false → no `(partial)`. Under NEW code:
`has_foreign_line_source` is true (A's line is foreign to B) regardless of whether B itself has
ANY line contribution → `splitKind: 'lines'` → `isSplit(row)` **true**. This is the "AC 3.1
zero-contribution-line regression case" explicitly called out in
`client/src/lib/reportContent/buildReportContent.test.ts` (search that phrase) — a unit test
already asserted this new behavior; the E2E suite just hadn't been told. Net: the row now shows
BOTH the "Deposit" badge AND `(partial)`, plus one footnote entry (previously zero).

**How I found it without a live browser**: cross-referenced `server/src/services/
sourceReportService.test.ts` (search `Story #1891 regression: invoice with lines only for source B
+ a deposit tagged to source A → isSplit true in both A and B reports`, line ~346) against the
E2E fixture shape — that unit test's `resultB.invoices[0].isSplit` assertion is `true`, which is
the DB-level raw `isSplit` (unchanged by #1911) that Scenario 17's OLD stale comment claimed was
`false`. Then confirmed via `buildReportContent.test.ts`'s "AC 3.1 (regression, #1898/claim
zero-contribution-line drop)" test that the row-level flag inherits this. Static/unit-test
cross-referencing caught a bug the incoming E2E spec missed — worth doing whenever a spec claims
"no E2E changes needed" for a semantic (not just additive) server change.

## Regression-guard test added

A new sibling `test()` inside Scenario 18's `describe` block (not folded into the existing test,
so `footnoteItems` can assert count 1 cleanly — the existing test's count is 2 for unrelated
reasons): a split invoice (lines on two sources) + an UNTAGGED deposit must show `(partial)` but
never `(less deposit)`, with exactly one footnote entry. This is literally the fixture shape
Scenario 18's `invoice3` used to have (before being retagged) — without a standalone guard, the
AC 3.2 over-inclusive bug could regress silently since every other test in the file now uses
either no deposit, an own-tagged deposit, or a genuinely-foreign-tagged one.

## POM docblock corrections

`e2e/pages/ReportWizardPage.ts` had TWO now-false invariants baked into JSDoc comments (found by
reading the file, not just the test): `depositBadge()`'s doc claimed a constituted-deposit row
"carries no inline note of its own" and `inlineNote()`'s doc claimed such a row "gets NEITHER —
it gets the inline depositBadge instead". Both corrected with an "Issue #1911" note; also added a
dedicated "Issue #1911" paragraph to the class docstring (same location/style as the existing
"Issue #1965" paragraph) so a future reader hits the corrected model before writing a new fixture
against stale assumptions.

## Reusable lesson

When a dev-team-lead spec says a server change is "purely additive, no E2E changes needed" for a
field that DRIVES existing conditional rendering, don't take it on faith — grep every fixture that
feeds the changed derivation (here: every `createDepositViaApi` call with `budgetSourceId` null or
equal to the reported/requested source) and check its assertions against the NEW derivation logic,
not just the ONE scenario the spec happened to mention. The unit test suite (already written and
green) is a fast, authoritative way to derive "what SHOULD this fixture shape now assert" without
needing a live browser.

## PR #2015 review round: two red shards, both self-inflicted, neither a production defect

`product-architect` and `product-owner` independently traced both E2E failures to stale
assertions I wrote, not to product behavior:

- **Badge-vs-note DOM order isn't a fact worth asserting.** Scenario 17's rewrite added
`toContainText('€150.00 (partial)')`, but the `depositBadge` renders BETWEEN the amount and the
note (`ReportContentEditor.tsx`: value → badge → split note → deposit-reduced note, in that
literal JSX order), so the DOM text is `€150.00Deposit (partial)` and the substring can never
match. Fix was to **delete the assertion**, not rewrite it to encode the ordering — two sibling
assertions already pin the same fact against the `inlineNote` locator directly (count 1, text
`(partial)`), and hardcoding badge-before-note relative order is exactly the kind of brittleness
this area (already reshuffled twice: #1959, #1911) keeps punishing.
- **Get the money math from the actual formula, not intuition.** Scenario 18's invoice3 retag (see
above) changed the deposit from untagged to tagged-to-`otherSourceId`, but I left the OLD
expected amount (`€75.00`) on the row assertion below it. The correct value is **€56.25** —
`depositAggregateUtils.ts`'s `splitByDepositsExcludingTagged`: `residualFraction` ALWAYS
subtracts every deposit (tagged or not) from the invoice total in the denominator
((200−50)/200=0.75), but `depositFractions` (which gets ADDED back per line) only includes
UNTAGGED deposits — a tagged one is filtered out entirely (it's handled by Rail B, on a
different source's row). So `75 × 0.75 = 56.25`, full stop, no returned fraction. Contrast the
sibling negative-control test's untagged deposit (60/90 split, 25 untagged deposit, invoice
150): residual `(150−25)/150=0.8333` PLUS the returned `depositFraction` `25/150=0.1667` sum to
exactly 1.0 (true whenever there's exactly one deposit, tagged-or-not doesn't matter to the
sum-to-1 property when it's the ONLY deposit and it's untagged) → `60 × 1.0 = 60`, i.e. the
original `€60.00` assertion was already correct and needed no change, only a comment.
- **The arithmetic proves the fix in both directions** — worth stating explicitly in test comments
next to both numbers, not just implied: foreign-tagged deposit → allocation genuinely drops
(75→56.25), so "claimed separately" is true; untagged deposit → residual + returned fraction net
to the FULL original amount (no drop at all), so the pre-#1911 "claimed separately" label on
that shape was literally false to a bank recipient. This is why the AC 3.2 negative-control test
exists, and it's a stronger justification than "the bug fired on the wrong condition" — worth
reaching for in future PR descriptions/comments on this area, not just re-deriving silently.
- **Lesson**: when a fixture retag changes the underlying formula's inputs, don't assume "keep
every other assertion the same, just add the new one" — re-derive EVERY downstream numeric
assertion from the actual utility function (not from a coordinator's or reviewer's restated
number without checking it against the source), and put the derivation in a comment so a future
reader (or reviewer) can tell "the fixture changed and the arithmetic followed" apart from
"the assertion was made convenient." I re-verified the €56.25 figure independently against
`depositAggregateUtils.ts` rather than taking two reviewers' restated arithmetic on faith — it
checked out, but the habit is the point: derive, don't just relay.

See [[story-1879-report-wizard]], [[issue-1959-inline-meta-and-labels]].
Loading
Loading