Skip to content

release: promote beta to main (v2.13.0 — Bank Report Wizard) - #1958

Merged
steilerDev merged 54 commits into
mainfrom
beta
Aug 3, 2026
Merged

release: promote beta to main (v2.13.0 — Bank Report Wizard)#1958
steilerDev merged 54 commits into
mainfrom
beta

Conversation

@steilerDev

@steilerDev steilerDev commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Promotes beta to main. 50 commits since v2.12.0 (2026-07-06) — the largest single promotion in the project so far.

Headline: Bank Report Wizard (epic, #1876#1879 + three refinement rounds)

A new multi-step wizard at /budget/reports that generates bank-ready funding-request documents: budget overview, claim, and proof-of-funds report types, with a client-side PDF pipeline, an editable HTML preview, per-report language selection, and optional Paperless-ngx upload.

Supporting features

Correctness fixes

i18n and design system

Infrastructure

  • better-sqlite3 13 adopted; install scripts dropped from the Docker deps stage
  • Trailer verification added to CI
  • E2E determinism: shard-3 diary flakes, shard-5 invoice column header, shard-4 locale preference isolation

Acceptance status

All automated gates green: Quality Gates and all 16 E2E Gates shards.

Two issues carry acceptance criteria that no automated test can verify and are still awaiting manual UAT — they are merged to beta but deliberately not marked Done:

Promoting ships both. Everything asserted by a test is verified; the judgement calls on generated-content and rendered-PDF quality are not.


Added after the initial promotion PR (at the user's request: fix the open issues first)

Follow-ups filed rather than forced onto this branch: testPrefix/authenticatedPage decoupling, an env-configurable login rate limit, search-users.spec.ts's unordered 100-row assumption, and two deferred findings from #1960's review (permanently silent failed preference saves; isLoaded as dead API surface).

#1959 — report PDF UX (added at the user's request)

Cover-letter paragraph breaks; area and attachment notes inline in the usage cell; / footnotes replaced by inline (partial) / (less deposit) labels; preview column toggles in the content editor.

Two defects were found in review and fixed before merge:

  • Silent PDF content loss. The inline meta was appended as one unchunked run, and with dontBreakRows pdfmake discards an over-tall row's overflow instead of paginating it. Page count saturated at 2 and went non-monotonic while rendered line count grew linearly — roughly 7 pages of measured content thrown away, with no error. Reachable from ordinary data (attachmentsNote has no maxLength; areaText is unbounded across leaf areas). This was a reintroduction of the Report PDF layout breaks: usage column overflows the page, rows split across page breaks, running header clipped #1929 round-4 regression. Fixed by bounding the whole rendered cell stream rather than usageText alone.
  • The deposit-reduced label split its own brackets. It was the only inline label with an internal space, so at 8pt in the narrow allocated-amount column it wrapped there — (less / deposit) in English, (abzgl. / Abschlag) in German. Both locales now use U+00A0, guarded by a locale-level invariant that rejects any breaking whitespace.

Neither was visible in the diff; both were found by rendering real PDFs and measuring.

Known open item: the column toggles are currently preview-only and labelled as such. The user has since asked that de-selecting a column exclude it from the PDF — that wiring is tracked separately and is not in this promotion.

steilerDev and others added 30 commits July 7, 2026 09:10
…atus (#1834)

Uses node-cron 4.5's validateDetailed() to validate BACKUP_CADENCE up
front with field-level error messages, rethrows scheduled-run failures
so lastRun() accurately reflects outcomes, and exposes the scheduler's
enabled/last-run/next-run state via a new GET
/api/backups/scheduler-status endpoint. The Backups settings page shows
this status (enabled/disabled, last run outcome, next scheduled runs)
alongside the existing backup list.

Fixes #1804

Co-authored-by: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
* fix(auto-itemize): prevent duplicate budget lines on save retry

materializeInlineDrafts() creates budget lines for each included draft
line sequentially. If a create call failed partway through (e.g. a
transient network error or validation error on a later line), the page
state still held all original draft lines. Retrying the save re-ran
materialization from the beginning, re-creating budget lines for rows
that had already been committed to the server on the first attempt.

Fix: MaterializeErr now carries the partially-materialized lines up to
(but not including) the failing line. Both AutoItemizePage and
PaperlessInvoiceReviewPage merge those lines back into component state
via the new mergeMaterializedLines() helper immediately on both the
error and success paths, so a subsequent retry only re-materializes
the lines that are still drafts.

Fixes #1833

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

* chore(agent-memory): update dev-team-lead memory index

Co-Authored-By: Claude dev-team-lead (Sonnet 4.5) <noreply@anthropic.com>

* fix(e2e): mock auto-itemize commit endpoint in Scenario 19 retry test

The paperless-first commit endpoint gates on paperlessEnabled and returns
503 before amount validation in the E2E environment, so the real-400
approach cannot work there. Mock the commit response (400 then 201) while
keeping the work-item budget POSTs real — the actual regression surface.

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

---------

Co-authored-by: Claude frontend-developer (Haiku 4.5) <noreply@anthropic.com>
…ew (#1836)

GET /api/budget/breakdown ignored the includes_vat storage semantics from
migration 0031_fix_vat_storage_semantics.sql, understating net-stored budget
lines by 19% compared to GET /api/budget (overview). Both queries now select
includes_vat, and every projection, subsidy-payback, and per-source total
grosses up net-stored lines (x1.19) before use. budgetOverviewService's
duplicate effective() helper now delegates to the canonical shared
effectivePlannedAmount() to prevent the two services from drifting apart
again.

Fixes #1805

Co-authored-by: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
…s are not rejected (#1837)

* fix(invoices): compare money sums in whole cents so exact itemizations are not rejected

Guards for itemized invoice-budget-line sums, deposit sums, and auto-itemize
commit totals compared IEEE-754 float sums against the invoice total with a
bare `>`. Summing REAL-typed amounts can introduce sub-cent floating-point
noise (e.g. 332.85 + 333.04 + 334.11 === 1000.0000000000001), so exact, valid
itemizations were intermittently rejected. A new exceedsAmount() helper in
server/src/services/shared/money.ts rounds both sides to whole cents before
comparing, eliminating the false positives while still rejecting genuine
one-cent-or-greater overages.

Fixes #1806

Co-Authored-By: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
Co-Authored-By: Claude backend-developer (Haiku 4.5) <noreply@anthropic.com>
Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com>

* fix(invoices): narrow optional error details in deposit guard tests

err.details is typed as optional on AppError, so accessing
err.details.availableHeadroom directly failed tsc --noEmit under strict
mode. Production code always populates details for this guard path; only
the test's access needed narrowing (err.details?.availableHeadroom).

Fixes #1806

Co-Authored-By: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com>

---------

Co-authored-by: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
…urrency display (#1838)

* fix(budget): make VAT rate configurable and localize BudgetLineForm currency display

BudgetLineForm hardcoded the German 19% VAT rate in its computed-total
math and rendered amounts with a raw "€" + toFixed(2) instead of the
project's locale-aware formatCurrency(). Adds a VAT_RATE env var
(default 0.19) threaded through GET /api/config -> LocaleContext,
exactly mirroring the existing CURRENCY pattern, and gives the shared
effectivePlannedAmount()/effectiveLineAmount() helpers an optional
vatRate parameter (default 0.19) so all ~20 existing call sites are
unaffected.

Fixes #1807

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

* fix(budget): add vatRate to AppConfig test fixtures

Six pre-existing server test files construct their own AppConfig-typed
fixture object (mocking fastify.config) instead of calling loadConfig(),
so they didn't pick up the new non-optional vatRate field added for
#1807 and failed tsc project-wide typecheck (Static Analysis, Docker).

Fixes #1807

Co-Authored-By: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com>

* fix(budget): add vatRate to remaining AppConfigResponse literals

AppConfigResponse also gained a non-optional vatRate field for #1807.
LinkedDocumentsSection.tsx's silent-fetch-failure fallback and several
pre-existing LocaleContext.test.tsx mocks constructed AppConfigResponse
literals predating this change and failed the client tsc --noEmit
project-wide typecheck (Static Analysis). The one test intentionally
simulating a response missing vatRate (Scenario 20) is cast instead of
given a value, preserving its test intent.

Fixes #1807

Co-Authored-By: Claude frontend-developer (Haiku 4.5) <noreply@anthropic.com>
Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com>
Co-Authored-By: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>

---------

Co-authored-by: Claude backend-developer (Haiku 4.5) <noreply@anthropic.com>
…ns (#1839)

subsidySummary.totalReductions was accumulated as a flat per-line total that
never respected subsidy_programs.maximum_amount, while the sibling
minTotalPayback/maxTotalPayback fields were correctly capped via
applySubsidyCaps() — producing an internally inconsistent response (e.g. a
10% subsidy capped at 1000 showed totalReductions: 5000 next to
maxTotalPayback: 100). totalReductions is now accumulated per-subsidy and run
through the same applySubsidyCaps() logic before being summed.

Fixes #1808

Co-authored-by: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
Bumps the github-actions group with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [docker/login-action](https://github.com/docker/login-action) | `4.2.0` | `4.4.0` |
| [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) | `4.1.0` | `4.2.0` |
| [docker/build-push-action](https://github.com/docker/build-push-action) | `7.2.0` | `7.3.0` |
| [docker/scout-action](https://github.com/docker/scout-action) | `1.22.0` | `1.23.1` |
| [docker/metadata-action](https://github.com/docker/metadata-action) | `6.1.0` | `6.2.0` |
| [github/codeql-action/upload-sarif](https://github.com/github/codeql-action) | `4.36.2` | `4.36.3` |


Updates `docker/login-action` from 4.2.0 to 4.4.0
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](docker/login-action@650006c...af1e73f)

Updates `docker/setup-buildx-action` from 4.1.0 to 4.2.0
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](docker/setup-buildx-action@d7f5e7f...bb05f3f)

Updates `docker/build-push-action` from 7.2.0 to 7.3.0
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](docker/build-push-action@f9f3042...53b7df9)

Updates `docker/scout-action` from 1.22.0 to 1.23.1
- [Release notes](https://github.com/docker/scout-action/releases)
- [Commits](docker/scout-action@7520205...2688993)

Updates `docker/metadata-action` from 6.1.0 to 6.2.0
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](docker/metadata-action@80c7e94...dc80280)

Updates `github/codeql-action/upload-sarif` from 4.36.2 to 4.36.3
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@8aad20d...54f647b)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: 4.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: docker/setup-buildx-action
  dependency-version: 4.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: docker/build-push-action
  dependency-version: 7.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: docker/scout-action
  dependency-version: 1.23.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: docker/metadata-action
  dependency-version: 6.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: github/codeql-action/upload-sarif
  dependency-version: 4.36.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps the prod-dependencies group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [@fastify/rate-limit](https://github.com/fastify/fastify-rate-limit) | `11.0.0` | `11.1.0` |
| [fastify](https://github.com/fastify/fastify) | `5.8.5` | `5.9.0` |
| [sharp](https://github.com/lovell/sharp) | `0.35.2` | `0.35.3` |
| [i18next](https://github.com/i18next/i18next) | `26.3.3` | `26.3.4` |
| [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) | `7.18.0` | `7.18.1` |


Updates `@fastify/rate-limit` from 11.0.0 to 11.1.0
- [Release notes](https://github.com/fastify/fastify-rate-limit/releases)
- [Commits](fastify/fastify-rate-limit@v11.0.0...v11.1.0)

Updates `fastify` from 5.8.5 to 5.9.0
- [Release notes](https://github.com/fastify/fastify/releases)
- [Commits](fastify/fastify@v5.8.5...v5.9.0)

Updates `sharp` from 0.35.2 to 0.35.3
- [Release notes](https://github.com/lovell/sharp/releases)
- [Commits](lovell/sharp@v0.35.2...v0.35.3)

Updates `i18next` from 26.3.3 to 26.3.4
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](i18next/i18next@v26.3.3...v26.3.4)

Updates `react-router-dom` from 7.18.0 to 7.18.1
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/react-router-dom@7.18.1/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@7.18.1/packages/react-router-dom)

---
updated-dependencies:
- dependency-name: "@fastify/rate-limit"
  dependency-version: 11.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-dependencies
- dependency-name: fastify
  dependency-version: 5.9.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-dependencies
- dependency-name: sharp
  dependency-version: 0.35.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-dependencies
- dependency-name: i18next
  dependency-version: 26.3.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-dependencies
- dependency-name: react-router-dom
  dependency-version: 7.18.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Dependabot's dev-dependencies group bundled the @babel/core,
@babel/preset-react, and @babel/preset-typescript v8.0.1 majors into
PR #1824. Babel 8 breaks the webpack client build because
@babel/preset-typescript@8 no longer strips TypeScript types the same
way, and a piecemeal 3-package major bump cannot produce a coherent
v8 dependency graph without coordinating the rest of the Babel/webpack
toolchain together.

Add an ignore rule holding these three packages at v7 (blocking
semver-major updates only) so Dependabot can recreate #1824 without
the Babel majors. The full Babel 8 migration is tracked separately.

Refs #1823

Co-authored-by: Claude product-architect (Sonnet 4.6) <noreply@anthropic.com>
…#1825)

Replaces the "Do NOT remove worktrees" rule in CLAUDE.md with a
cleanup-on-completion policy: once a session's PR is merged (or the work
is abandoned) and the worktree is clean, remove the worktree and delete
its merged local branch. Safety guards retained: never remove dirty
worktrees, unmerged/unpushed branches, or another session's active
worktree.

This resolves the worktree contradiction flagged in #1819 in the skills'
favor — /develop step 11.5 and /release step 7.4 already instruct
worktree removal and are now consistent with CLAUDE.md.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…tial state on crash (#1842)

better-sqlite3 autocommits each statement individually, so a crash/OOM/docker-stop
mid-sequence could leave dependent multi-row/multi-table writes half-applied with no
rollback. Wraps autoReschedule's write-application phase, reorderSubtasks,
createMilestone, deleteMilestone, deleteWorkItem, and createSubsidyProgram/
updateSubsidyProgram in db.transaction(() => {...}).

Fixes #1809

Co-authored-by: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
…rs (#1843)

DAV token 404 responses hand-rolled a DAV_TOKEN_NOT_FOUND code that
was never in the ErrorCode enum, so the client couldn't translate it.
Reuse NOT_FOUND instead — the client never branched on the DAV-specific
code (the /profile endpoint is only ever hit via a raw <a href download>
link, never through the JS API client).

The Fastify error handler also passed through raw Fastify/plugin
internal codes (FST_ERR_CTP_BODY_TOO_LARGE, FST_REQ_FILE_TOO_LARGE,
etc.) verbatim, or fell back to a made-up REQUEST_ERROR code — neither
mappable by translateApiError(). Added a FASTIFY_ERROR_CODE_MAP for
known codes, with a status-range fallback (VALIDATION_ERROR for <500,
INTERNAL_ERROR for >=500) for anything unmapped. HTTP status codes are
preserved exactly; only the `code` field changes.

While researching the fix, found PAYLOAD_TOO_LARGE was already a live
enum member thrown in production (photos.ts) with zero translations in
either locale — the same class of bug. Added English and German keys
since the new FST_* mapping routes more traffic through that code.

Fixes #1811

Co-authored-by: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
…ith 9 updates (#1841)

* chore(deps-dev): bump the dev-dependencies group across 1 directory with 9 updates

Bumps the dev-dependencies group with 9 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@eslint-react/eslint-plugin](https://github.com/Rel1cx/eslint-react/tree/HEAD/plugins/eslint-plugin) | `5.9.3` | `5.10.4` |
| [conventional-changelog-conventionalcommits](https://github.com/conventional-changelog/conventional-changelog/tree/HEAD/packages/conventional-changelog-conventionalcommits) | `9.3.1` | `10.2.0` |
| [eslint](https://github.com/eslint/eslint) | `10.5.0` | `10.6.0` |
| [prettier](https://github.com/prettier/prettier) | `3.8.5` | `3.9.4` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.62.0` | `8.62.1` |
| [webpack](https://github.com/webpack/webpack) | `5.108.1` | `5.108.3` |
| [webpack-dev-server](https://github.com/webpack/webpack-dev-server) | `5.2.5` | `5.2.6` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `26.0.0` | `26.1.0` |
| [testcontainers](https://github.com/testcontainers/testcontainers-node) | `12.0.3` | `12.0.4` |



Updates `@eslint-react/eslint-plugin` from 5.9.3 to 5.10.4
- [Release notes](https://github.com/Rel1cx/eslint-react/releases)
- [Changelog](https://github.com/Rel1cx/eslint-react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Rel1cx/eslint-react/commits/v5.10.4/plugins/eslint-plugin)

Updates `conventional-changelog-conventionalcommits` from 9.3.1 to 10.2.0
- [Release notes](https://github.com/conventional-changelog/conventional-changelog/releases)
- [Changelog](https://github.com/conventional-changelog/conventional-changelog/blob/master/packages/conventional-changelog-conventionalcommits/CHANGELOG.md)
- [Commits](https://github.com/conventional-changelog/conventional-changelog/commits/conventional-changelog-conventionalcommits-v10.2.0/packages/conventional-changelog-conventionalcommits)

Updates `eslint` from 10.5.0 to 10.6.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](eslint/eslint@v10.5.0...v10.6.0)

Updates `prettier` from 3.8.5 to 3.9.4
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](prettier/prettier@3.8.5...3.9.4)

Updates `typescript-eslint` from 8.62.0 to 8.62.1
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.62.1/packages/typescript-eslint)

Updates `webpack` from 5.108.1 to 5.108.3
- [Release notes](https://github.com/webpack/webpack/releases)
- [Changelog](https://github.com/webpack/webpack/blob/main/CHANGELOG.md)
- [Commits](webpack/webpack@v5.108.1...v5.108.3)

Updates `webpack-dev-server` from 5.2.5 to 5.2.6
- [Release notes](https://github.com/webpack/webpack-dev-server/releases)
- [Changelog](https://github.com/webpack/webpack-dev-server/blob/v5.2.6/CHANGELOG.md)
- [Commits](webpack/webpack-dev-server@v5.2.5...v5.2.6)

Updates `@types/node` from 26.0.0 to 26.1.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `testcontainers` from 12.0.3 to 12.0.4
- [Release notes](https://github.com/testcontainers/testcontainers-node/releases)
- [Commits](testcontainers/testcontainers-node@v12.0.3...v12.0.4)

---
updated-dependencies:
- dependency-name: "@eslint-react/eslint-plugin"
  dependency-version: 5.10.4
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: conventional-changelog-conventionalcommits
  dependency-version: 10.2.0
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: dev-dependencies
- dependency-name: eslint
  dependency-version: 10.6.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: prettier
  dependency-version: 3.9.4
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: typescript-eslint
  dependency-version: 8.62.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: webpack
  dependency-version: 5.108.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: webpack-dev-server
  dependency-version: 5.2.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: "@types/node"
  dependency-version: 26.1.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: testcontainers
  dependency-version: 12.0.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix(deps): dedupe webpack tree and align webpack-dev-server override

Dependabot's lockfile generator nested client/node_modules/webpack and
client/node_modules/webpack-dev-server, causing the client webpack build
to crash with two webpack instances ("The 'compilation' argument must be
an instance of Compilation").

- Bump root override webpack-dev-server 5.2.5 -> 5.2.6 to resolve the
  override/direct-dep version conflict that caused the nesting. This pin
  is a security floor from PR #1774 (GHSA alerts #57/#58); 5.2.6
  preserves it.
- Bump client webpack 5.108.3 -> 5.108.4 so a single copy hoists for
  both client and Docusaurus.
- Regenerate the lockfile via full npm install + npm dedupe. Zero
  nested client/node_modules entries remain, single hoisted
  webpack@5.108.4, and npm audit reports 0 vulnerabilities.

Co-Authored-By: Claude backend-developer (Haiku 4.5) <noreply@anthropic.com>
Co-Authored-By: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Frank Steiler <frank@steiler.dev>
Co-authored-by: Claude backend-developer (Haiku 4.5) <noreply@anthropic.com>
…ilestone panel cluster (#1844)

* fix(i18n): translate milestones/budget/a11y strings and remove dead milestone panel cluster

Wires ~69 hardcoded aria-label/placeholder/title strings and several
untranslated modal/heading strings across 27 components and pages into
i18next, adding 70 new English keys across 9 namespaces (budget, common,
diary, documents, householdItems, photoViewer, schedule, settings,
workItems) with full German translations.

Deletes the components/milestones/ panel cluster (MilestonePanel,
MilestoneForm, MilestoneWorkItemLinker, WorkItemSelector) instead of
translating it: the cluster was dead code, superseded by the routed
MilestonesPage/MilestoneCreatePage/MilestoneDetailPage implementation
and referenced by nothing but its own tests.

During implementation, fixes three bugs the sweep itself introduced and
caught before merge: a duplicate-top-level-key bug in en/diary.json that
silently wiped ~25 pre-existing translation keys, a missing
useTranslation hook in GanttChart.tsx that crashed the component, and
two wrong i18n key paths in SignatureSection.tsx/DiaryEntryDetailPage.tsx
that rendered raw keys instead of translated text.

Fixes #1812

Co-Authored-By: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
Co-Authored-By: Claude frontend-developer (Haiku 4.5) <noreply@anthropic.com>
Co-Authored-By: Claude translator (Sonnet 4.5) <noreply@anthropic.com>
Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com>

* test(e2e): make main-navigation locator locale-aware

The Sidebar's nav landmark aria-label is now translated (part of
#1812's i18n sweep), so the hardcoded English-only locator broke the
German-locale assertion in i18n.spec.ts. Match either translation.

Co-Authored-By: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
Co-Authored-By: Claude e2e-test-engineer (Sonnet 4.5) <noreply@anthropic.com>

* fix(i18n): correct unresolved translation key paths

The ux-designer's PR review of #1844 found 4 t() calls referencing
non-existent keys instead of the correct paths added by the #1812
sweep: TimelinePage toolbar.zoomOut/In -> timeline.toolbar.zoomOutTitle/
zoomInTitle; BudgetSourcesPage budgetSources.* -> sources.form.*;
VendorDetailPage vendorDetail.invoicesAriaLabel -> vendorDetail.invoices.
228 tests pass on the three affected pages.

Independently verified via a scripted resolution check: every t()/tX()
call added on this branch (111 calls across 28 changed .tsx files,
mapped to its component's useTranslation namespace) resolves against
the flattened en key set, with de parity intact -- zero unresolved keys.

Co-Authored-By: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
Co-Authored-By: Claude frontend-developer (Haiku 4.5) <noreply@anthropic.com>

---------

Co-authored-by: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
…ers (#1845)

* fix(i18n): route all date/number display through locale-aware formatters

Sweep of 14 raw toLocale*/Intl./toFixed call sites across 10 components that
bypassed client/src/lib/formatters.ts, rendering en-US dates, decimal
separators, and weekday labels for German-locale users regardless of their
selected locale.

Extends formatters.ts with formatDate's monthStyle option, formatPercent's
locale+digits options, and four new locale-aware helpers (formatWeekdayShort,
formatWeekdayMonthDay, formatFileSize, formatHours, formatDateTimeWithZone),
each exposed through useFormatters(). Reroutes GanttTooltip/GanttHeader,
MiniGanttCard, SignatureCapture's canvas-burned timestamp, BackupsPage file
sizes, SourceUtilizationCard/BudgetSourcesPage percentages, and diary work
durations through the shared formatters. Adds a locale param to
calendarUtils.ts's formatDateForAria, matching its sibling getMonthName/
getDayName convention.

Bonus fix: DocumentCard and DocumentDetailPanel previously called
`new Date(document.created).toLocaleDateString(...)` directly on a
YYYY-MM-DD-only date string, which parses as UTC midnight and can render the
wrong calendar day for users behind UTC in the evening. Routing these through
formatDate (which parses date components directly to avoid this shift) fixes
locale and this latent off-by-one-day bug.

Also fixes a bug found during QA: BudgetSourcesPage destructured
`formatPercent: _formatPercent` (unused-var alias) while the new tooltip code
referenced the bare `formatPercent` binding, causing a ReferenceError on
segment hover. Adds a new direct unit test file for SignatureCapture (closing
a pre-existing test-file-parity gap) and de-DE locale coverage across all
touched components.

Fixes #1813

Co-Authored-By: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
Co-Authored-By: Claude frontend-developer (Haiku 4.5) <noreply@anthropic.com>
Co-Authored-By: Claude translator (Sonnet 4.5) <noreply@anthropic.com>
Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com>

* test(client): provide locale context to suites hitting new formatter dependencies

GanttChart.test.tsx, DocumentBrowser.test.tsx, and DiaryEntryEditPage.test.tsx
render DocumentCard/GanttHeader/DiaryEntryForm without a LocaleProvider (or,
for GanttChart, with a formatters.js mock missing the new
formatWeekdayMonthDay export). Those components now depend on locale context
per the #1813 formatter consolidation, which their existing test setups
didn't anticipate, failing Quality Gates' test job (shards 2/3/5).

Adds a fixed-locale LocaleContext.js mock to DocumentBrowser.test.tsx and
DiaryEntryEditPage.test.tsx (matching GanttChart.test.tsx's existing
pattern), and adds the missing formatWeekdayMonthDay stub to GanttChart's
formatters.js mock. Test-only change, no production code touched.

Fixes #1813

Co-Authored-By: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com>

* test(client): cover transitive locale-context consumers

InvoicePaperlessPickerModal.test.tsx renders the embedded DocumentBrowser ->
DocumentCard (a grandparent consumer, two hops from the directly-changed
component), which the prior consumer-blast-radius fix missed since it only
checked direct importers. Adds the same LocaleContext.js mock block used in
DocumentBrowser.test.tsx to unblock DocumentCard's useFormatters() call.

Followed up with a full transitive-closure walk (grep for importers,
recursively, to fixpoint) across every remaining consumer of the 11
components changed in #1813: LinkedDocumentsSection, LinkedDocumentCard,
DocumentBrowser, GanttChart, CalendarView, DashboardPage, SignatureSection,
DiaryEntryEditPage, DiaryEntryDetailPage, DiaryEntryCard,
HouseholdItemDetailPage, WorkItemDetailPage, InvoiceDetailPage,
SubsidyProgramsPage, TimelinePage, DiaryPage, BudgetSourcesPage,
BackupsPage, and App.test.tsx — 841 tests total, all green, no further gaps.
Test-only change, no production code touched.

Fixes #1813

Co-Authored-By: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com>

---------

Co-authored-by: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
…parity guard test (#1846)

- usePhotos.ts now routes API errors through translateApiError()/t() instead
  of hardcoded English strings (ApiClientError -> errors namespace by code,
  NetworkError/unexpected -> new photoViewer.networkError/unexpectedError
  keys, translated in both en and de).
- Removed 5 orphaned photoAnnotator keys (reset/resetTitle/resetBody/
  resetConfirm/resetComplete) left over from a "reset to original photo"
  feature that was replaced by PhotoViewer's "Clear annotations" entry
  point; also removed the associated dead CSS rules (.resetButton,
  .modalActions, .confirmButton).
- Fixed 5 pre-existing literal duplicate JSON keys (same key twice in the
  same object, silently shadowed by JSON.parse's last-wins behavior) across
  diary.json, householdItems.json, and schedule.json in both locales.
  Verified behavior-preserving: the shadowed (first) occurrence was already
  unreachable at runtime in every case.
- Added client/src/i18n/i18n.parity.test.ts: a generalized en/de key-parity
  test across all 14 namespaces plus a raw-text duplicate-key guard across
  all 28 locale files, so both bug classes fail CI instead of accumulating.

Fixes #1814

Co-authored-by: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
…in CI (#1847)

Stylelint was defined but never executed anywhere (not in npm run lint,
not in CI), leaving 370 latent violations across client/src/**/*.css and
*.module.css. This fixes all of them and wires stylelint into both local
and CI gates so the rules actually enforce design-token usage going
forward, as CLAUDE.md already claimed they did.

- Add missing `color-function-alias-notation: null` to the tokens.css
  stylelint override (resolves 116 violations in tokens.css alone).
- Add ~20 new semantic tokens (--z-raised, --color-warning-badge-bg,
  --color-tooltip-*, --color-photo-*, --shadow-text-overlay) extracted
  1:1 from previously-hardcoded values — no visual changes.
- Fix a real bug found during remediation: InvoiceBudgetLinesSection
  used `@extend .td;` (SCSS syntax) six times in a plain CSS Module,
  which is a silent no-op — six table cells were never receiving their
  intended padding. Replaced with the actual declaration.
- Remove dead/duplicate CSS selectors (HouseholdItemDetailPage had a
  legacy pre-token block fully shadowed by a later tokenized
  reimplementation of the same selectors; CostBreakdownTable had two
  more).
- Replace deprecated `clip`/`page-break-*` properties, modernize
  `:not()` chains, replace hardcoded font-weight/z-index literals with
  scale tokens, and move an inline style to a CSS Module class.
- Chain stylelint into `npm run lint`/`lint:fix`; add a dedicated
  `Stylelint` step to the CI `static-analysis` job (intentionally not
  `npm run lint`, since ESLint's pre-existing 8 errors/50 warnings are
  out of scope for this PR and would break CI immediately).

Fixes #1815

Co-authored-by: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
…ap modal focus (#1848)

* refactor(client): consolidate Badge/Modal reuse, add shared hooks, trap modal focus

- KeyboardShortcutsHelp now renders through the shared Modal (was a hand-rolled
  dialog with no portal, Escape handling, or focus management)
- Modal gains a Tab-cycling focus trap; PhotoMetadataModal's private trap
  implementation is removed in favor of the shared one
- BudgetHealthIndicator, UpcomingMilestonesCard, CriticalPathCard, and
  SubsidyPipelineCard now use the shared Badge component with new
  Badge.module.css variants instead of hand-rolled status spans
- Add useDebounce, useDebouncedCallback, and useClickOutside hooks, and
  migrate 8 duplicated debounce/click-outside implementations onto them
  (SearchPicker, DocumentBrowser, DiaryPage, BudgetOverviewPage,
  DiaryEntryEditPage, DataTableColumnSettings, OverflowMenu)
- Remove stale StatusBadge/HouseholdItemStatusBadge directories (components
  were already consolidated into Badge; only orphaned test files remained)
- Wire the existing (already-translated) i18n key for WorkItemDetailPage's
  subtasks empty state instead of hardcoded English

Deferred to follow-up issues (documented in the PR description): the 25+
ad-hoc FormError/EmptyState bypass sites cited in #1816, DataTableHeader's
click-outside (needs a ref-forwarding change to a sibling component), and a
pre-existing autosave-on-mount bug in DiaryEntryEditPage found by QA while
testing this refactor (unrelated to the debounce-hook migration).

Fixes #1816

Co-Authored-By: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
Co-Authored-By: Claude frontend-developer (Haiku 4.5) <noreply@anthropic.com>
Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com>

* fix(client): memoize useDebouncedCallback return object to prevent effect re-fire loops

useDebouncedCallback returned a brand-new `{ trigger, cancel }` object literal
on every render, even though `trigger`/`cancel` were each individually stable
via useCallback. Any consumer effect that depended on the whole returned
object (not just its members) saw that dependency "change" on every render,
causing the effect to re-fire spuriously.

This was the deterministic root cause of the E2E smoke failure on
tests/budget/budget-overview-no-hero-card.spec.ts:328 (PR #1848): in
BudgetOverviewPage, the debounce-refetch effect depends on
`scheduleFetchBreakdown`, so every render — including the render caused by
the effect's own setIsBreakdownRefetching(true) — re-triggered the effect,
producing an unbounded refetch ping-pong. isBreakdownRefetching never
settled to false, so the `.breakdownRefetching` (pointer-events: none)
overlay stayed applied over the breakdown table, and Playwright's click on
"Expand work item budget by area" timed out with the ancestor PageLayout
content wrapper reported as intercepting pointer events. The same pattern
also silently cancelled pending debounced autosaves in DiaryEntryEditPage on
any unrelated re-render.

Fix: wrap the hook's return value in `useMemo(() => ({ trigger, cancel }),
[trigger, cancel])` so identity is stable whenever trigger/cancel haven't
changed. No page-level code changes were needed — the effect dependency
arrays were already correct per exhaustive-deps; they were only broken by
the hook's instability.

Adds a regression test for object-identity stability in
useDebouncedCallback.test.ts (the existing test only checked
trigger/cancel individually, not the containing object), plus regression
tests in BudgetOverviewPage.test.tsx (refetch settles and stops
re-triggering) and DiaryEntryEditPage.test.tsx (a pending debounced
autosave survives an unrelated re-render).

Co-Authored-By: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
Co-Authored-By: Claude frontend-developer (Haiku 4.5) <noreply@anthropic.com>
Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com>

---------

Co-authored-by: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
…acts (#1849)

Two divergent copies of the docs-writer agent existed (docs-writer.md and
cornerstone-docs-writer.md), causing skills to route to a stale definition.
Merges them into a single docs-writer.md with facts verified against the
repo (site URL/baseUrl, .js config filenames, memory dir path, Opus trailer
convention, actual Docusaurus/React versions) and removes the duplicate.

Fixes #1817

Co-authored-by: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
…trailers, and review gate (#1850)

Fixes #1818

Co-authored-by: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
…olicy, and skill contradictions (#1851)

Fixes #1819

Co-authored-by: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
…d CI layers (#1852)

Fixes #1820

Co-authored-by: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
…etPage, close test-parity gaps

- Document POST /api/invoices/auto-itemize/merge-lines in the wiki API contract
- Remove dead unrouted BudgetPage component
- Add missing AutoItemizePdfPreview test (100% coverage); codify test-parity exemptions and agent-memory commit policy
- Fix stale errorHandler doc-comment

Fixes #1821

Co-Authored-By: Claude backend-developer (Haiku 4.5) <noreply@anthropic.com>
Co-Authored-By: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
Co-Authored-By: Claude frontend-developer (Haiku 4.5) <noreply@anthropic.com>
Co-Authored-By: Claude product-architect (Opus 4.6) <noreply@anthropic.com>
Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com>
- Scope the js-yaml override so gray-matter resolves the GHSA-patched 3.15.0 (keeps yaml.safeLoad) while all other consumers stay on 4.2.0
- Fixes the Docs Deploy failure present since 2026-06-20 (docs build verified locally)

Fixes #1827

Co-Authored-By: Claude backend-developer (Haiku 4.5) <noreply@anthropic.com>
Co-Authored-By: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
…sponse sync

- Fix diary-drafts Scenario 14 timeout budgeting (test.slow only triples the overall timeout) with guaranteed cleanup headroom
- Fix diary-r2-uat Scenario 10 mock-response race via per-transition waitForResponse
- Document maxFailures retry accounting; fix testcontainers 12.0.4 API drift

Fixes #1829

Co-Authored-By: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
Co-Authored-By: Claude e2e-test-engineer (Sonnet 4.5) <noreply@anthropic.com>
…n household items (#1858)

PR #1844 switched the BudgetSection add-line button aria-label to
t('budgetLineForm.addBudgetLineAriaLabel'), but t is namespace-switched to
householdItems on household item pages where that key does not exist, so the
accessible name rendered as the raw i18n key. Resolve it through the
always-budget-namespaced tBudget instead.

Fixes deterministic E2E failures on household-item-detail.spec.ts (all
viewports) and budget/unit-pricing.spec.ts Scenario 3 observed in ad-hoc
run 28934922175.

Fixes #1857

Co-authored-by: Claude frontend-developer (Haiku 4.5) <noreply@anthropic.com>
…ments

- Add refund entry type to invoice deposits: refunds are stored positive, deducted from effective invoice amounts, and net negatively in all budget/claim aggregations (incl. claimed-then-refunded corrections)
- Enforce independent deposit/refund sum invariants with new REFUND_EXCEEDS_INVOICE error; entryType immutable after creation
- Refund UI in invoice deposits section (entry-type choice, negative rows, refund badge) plus new hidden-by-default Effective Amount column; full German translations and E2E/unit coverage

Fixes #1876

Co-Authored-By: Claude backend-developer (Haiku 4.5) <noreply@anthropic.com>
Co-Authored-By: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
Co-Authored-By: Claude e2e-test-engineer (Sonnet 4.5) <noreply@anthropic.com>
Co-Authored-By: Claude frontend-developer (Haiku 4.5) <noreply@anthropic.com>
Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com>
Co-Authored-By: Claude translator (Sonnet 4.5) <noreply@anthropic.com>
…ment attachment typing

- Add optional reference and contact address fields to budget sources (cover-letter recipient data for bank reports)
- Add household name & address app setting (new app_settings mechanism + Household tab on Manage page)
- Add document attachment typing for invoice links (quotation/deposit/invoice tags, untagged default, PATCH retag endpoint, auto-itemize links auto-tagged as invoice)

Fixes #1877

Co-Authored-By: Claude backend-developer (Haiku 4.5) <noreply@anthropic.com>
Co-Authored-By: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
Co-Authored-By: Claude e2e-test-engineer (Sonnet 4.5) <noreply@anthropic.com>
Co-Authored-By: Claude frontend-developer (Haiku 4.5) <noreply@anthropic.com>
Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com>
Co-Authored-By: Claude translator (Sonnet 4.5) <noreply@anthropic.com>
…load endpoints

- Add GET /api/source-reports building per-funding-source report data for budget-overview/claim/proof-of-funds slices: refund-aware per-invoice allocation, split detection, refund-adjustment lines, stage-based document selection (untagged always included), batched Paperless ASN/title enrichment with graceful degradation
- Add transactional POST /api/source-reports/mark-claimed (pending→claimed incl. deposits and refunds, 409 rollback on stale state) and widen the deposit pending→claimed transition
- Add POST /api/paperless/documents multipart upload proxy returning the consumption task UUID, with PAPERLESS_FILTER_TAG applied and untagged fallback

Fixes #1878
Fixes #1884

Co-Authored-By: Claude backend-developer (Haiku 4.5) <noreply@anthropic.com>
Co-Authored-By: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
Co-Authored-By: Claude e2e-test-engineer (Sonnet 4.5) <noreply@anthropic.com>
Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com>
- Add the report wizard at /budget/reports (new Reports tab): use case (budget overview / claim / proof of funds) → source → invoice include/exclude → live PDF preview with attach-documents and cover-letter options, download, optional Paperless upload, and a confirmed mark-as-claimed flow (incl. pending→claimed and 409-stale refetch)
- Client-side PDF pipeline (pdfmake@0.3.11 + pdf-lib@1.17.1, exact-pinned, lazy-loaded): locale/currency-aware cover letter from household + source contact fields, tabular overview with per-status subtotals netting refunds, dual-marker footnotes, and embedded invoice documents with skip-and-footnote resilience — verified end-to-end in browser E2E
- New shared WizardStepper, ReportInvoiceList, ReportPdfPreview components; BUDGET_TABS hoisted; per-source Generate-report action; full German translations; ADR-034 documents the client-side PDF decision

Fixes #1879
Fixes #1886

Co-Authored-By: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
Co-Authored-By: Claude e2e-test-engineer (Sonnet 4.5) <noreply@anthropic.com>
Co-Authored-By: Claude frontend-developer (Haiku 4.5) <noreply@anthropic.com>
Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com>
Co-Authored-By: Claude translator (Sonnet 4.5) <noreply@anthropic.com>
steilerDev and others added 11 commits August 2, 2026 10:21
…sqlite3 13

Closes the two security alerts Dependabot structurally cannot fix (its security PRs target `main`, which requires the `Require head branch == beta` check).

- shell-quote 1.8.4 -> 1.9.0 (quadratic-complexity DoS in parse(), high, dev-only), fixed upstream via concurrently 10.0.4 rather than an override. Supersedes #1869.
- js-yaml 4.2.0 -> 4.3.0 (merge-key quadratic CPU, high, dev-only); the root `overrides` entry was itself pinning the vulnerable version.
- Dependabot ignore rules for `typescript` majors (ts-jest peer is `>=4.3 <7`, breaks `npm ci`, #1882) and `better-sqlite3` majors (v13 dropped prebuild-install, breaks the Docker build, #1889).

npm audit: 10 findings (6 high) -> 7 (3 high).
Corrects #1927, which held better-sqlite3 at 12.x on the incorrect premise that v13 always compiles from source.

- v13 moved to N-API and ships prebuilds INSIDE the npm tarball, including linuxmusl-x64 and linuxmusl-arm64 — both Docker target arches are covered.
- #1889's Docker failure was only that npm synthesizes `node-gyp rebuild` (binding.gyp present), and node-gyp needs python3 to evaluate gyp even though binding.gyp short-circuits to `type: 'none'` when a prebuild exists. Nothing was ever going to compile.
- Fix: `npm ci --omit=dev --ignore-scripts` in the deps stage — better-sqlite3 is the only production dep with an install script, and it needs none (lib/binding.js resolves the prebuild at require() time). Keeps the image toolchain-free.
- Adds a build-time guard so a missing prebuild fails the build instead of crashing production at runtime.
- Removes the better-sqlite3 semver-major ignore rule; the typescript hold stays.

Verified: linuxmusl-arm64 locally (loads and queries inside the Alpine image), linuxmusl-x64 in CI's Docker job.
… header (#1929)

- Constrain the report PDF table to the A4 printable width: every column is a declared
  numeric width (no `*`, no `auto`), so `tableOffsetsTotal(n) + fixedSum(n) + usageWidth(n)`
  is an algebraic identity equal to the printable width for any content.
- Keep table rows whole across page breaks (`dontBreakRows` on the `table` node, where
  pdfmake actually reads it), and bound the whole usage cell — usage text, area line and
  attachments note are independently chunked into their own rows, so an over-tall row can
  never be silently dropped.
- Fit the running header inside its band via a computed `PAGE_TOP_MARGIN`, and fix a latent
  `Malformed table row` crash when an overview row had a falsy status.

Fixes #1929

Co-Authored-By: Claude dev-team-lead <noreply@anthropic.com>
Co-Authored-By: Claude frontend-developer <noreply@anthropic.com>
Co-Authored-By: Claude product-architect <noreply@anthropic.com>
Co-Authored-By: Claude product-owner <noreply@anthropic.com>
Co-Authored-By: Claude qa-integration-tester <noreply@anthropic.com>
Co-Authored-By: Claude ux-designer <noreply@anthropic.com>
…type (#1930)

- Embed report attachments by document tier (quotation -> deposit -> invoice) with a
  per-report-type floor, so a claim report never carries a quotation and a proof-of-funds
  report carries only invoices.
- The rule depends solely on the report type and the document's own type, replacing the
  previous per-invoice status/stage matching; untagged documents are treated as the
  strongest tier so existing data is never silently dropped.
- Filtering stays a single server-side pass, so the wizard's invoice list and the generated
  PDF agree by construction.

Fixes #1930

Co-Authored-By: Claude backend-developer <noreply@anthropic.com>
Co-Authored-By: Claude dev-team-lead <noreply@anthropic.com>
Co-Authored-By: Claude product-architect <noreply@anthropic.com>
Co-Authored-By: Claude product-owner <noreply@anthropic.com>
Co-Authored-By: Claude qa-integration-tester <noreply@anthropic.com>
…pt (#1931)

- Remove the step-4 "Enable AI assistance" opt-in: it gated nothing but a button's
  visibility, and `llmEnabled` from `GET /api/config` already says whether the capability
  exists. The step-5 action now renders directly, relabelled "Enhance with AI" since it
  overwrites deterministic content that already exists rather than generating from nothing.
- Rewrite the report-content prompt to explain *why* each cost was incurred and forbid
  restating vendor, invoice number, date and amount — those are already table columns.
- Give the three length caps a single definition that both the prompt text and the response
  validator derive from; they previously disagreed (150/2000/200 stated vs 200/3000/300
  enforced). Also fixes an inverted language ternary that was wrong in both branches.
- Add a screen-reader description of the overwrite behaviour, replacing the warning lost
  with the removed checkbox.

Fixes #1931

Co-Authored-By: Claude backend-developer <noreply@anthropic.com>
Co-Authored-By: Claude dev-team-lead <noreply@anthropic.com>
Co-Authored-By: Claude e2e-test-engineer <noreply@anthropic.com>
Co-Authored-By: Claude frontend-developer <noreply@anthropic.com>
Co-Authored-By: Claude product-architect <noreply@anthropic.com>
Co-Authored-By: Claude product-owner <noreply@anthropic.com>
Co-Authored-By: Claude qa-integration-tester <noreply@anthropic.com>
Co-Authored-By: Claude translator <noreply@anthropic.com>
Co-Authored-By: Claude ux-designer <noreply@anthropic.com>
…1943)

- Changing the report use case left the previously fetched report and its source
  selected. Step 2's Next button is gated only on `sourceId`, so a user could click
  straight through to step 3 holding a report fetched under the old use case — after
  #1930 that meant quotation-tier documents could reach a claim PDF handed to a bank.
- The reset clears the report, its status, the source and both exclusion sets inside the
  existing discard-confirmation guard, so unsaved edits still prompt first and clear only
  on confirm.
- A one-shot ref stops the `?sourceId=` deep-link effect from re-arming when the report
  is cleared, and a monotonic request token discards in-flight fetches that would
  otherwise re-populate the state the reset just cleared — including out-of-order
  resolutions, which reproduced the original defect.

Fixes #1943

Co-Authored-By: Claude dev-team-lead <noreply@anthropic.com>
Co-Authored-By: Claude e2e-test-engineer <noreply@anthropic.com>
Co-Authored-By: Claude frontend-developer <noreply@anthropic.com>
Co-Authored-By: Claude product-owner <noreply@anthropic.com>
Co-Authored-By: Claude qa-integration-tester <noreply@anthropic.com>
#1939)

- Rename `HEADER_ROW_HEIGHT` to `HEADER_ROW_HEIGHT_MAX`: the value (68pt) is a correct upper
  bound but a 48% over-estimate of the measured 45.81pt, and the old name invited a future
  reader to treat a safety ceiling as a typical height.
- Rescope the `WORST_CASE_CHAR_ADVANCE_EM` comment to what was actually scanned, record that
  `Ѹ` U+0478 exceeds the constant, and derive the resulting `Ѹ` chunk ceilings inline (616 and
  507) so the file states plainly that the usage cap sits 34 characters above its derived
  ceiling and why that is accepted.
- Enumerate every cell-content channel and the bound that closes it, naming `markerText` as
  the one unbounded row-height contributor (documented, deliberately not fixed).
- Move `PDF_STYLES` down into the geometry layer so the font-size literals have one home;
  `merge.ts` re-exports it for existing consumers.

Fixes #1939

Co-Authored-By: Claude dev-team-lead <noreply@anthropic.com>
Co-Authored-By: Claude frontend-developer <noreply@anthropic.com>
Co-Authored-By: Claude product-architect <noreply@anthropic.com>
Co-Authored-By: Claude qa-integration-tester <noreply@anthropic.com>
…invoice affordance (#1933)

- The attachment indicator rendered Feather's `refresh-cw` glyph under a `.paperclip` class, so
  a non-interactive hint read as a refresh button and users clicked it. It is now a paperclip.
- Step 3 had no way to inspect an invoice without leaving the wizard and losing all state. A new
  shared `IconLinkButton` opens the invoice in a new tab from its own grid column, deliberately
  outside the row's checkbox label so activating it can never toggle inclusion.
- The select-all checkbox was centred within a two-column span while every row checkbox was
  start-aligned, so they never shared a vertical axis.
- The deposit Dates cell was a flex container on a `<td>`, whose synthesized baseline did not
  match its siblings' — visible as a stepped row edge when only a due date was present.

Fixes #1933

Co-Authored-By: Claude dev-team-lead <noreply@anthropic.com>
Co-Authored-By: Claude e2e-test-engineer <noreply@anthropic.com>
Co-Authored-By: Claude frontend-developer <noreply@anthropic.com>
Co-Authored-By: Claude qa-integration-tester <noreply@anthropic.com>
Co-Authored-By: Claude translator <noreply@anthropic.com>
Co-Authored-By: Claude ux-designer <noreply@anthropic.com>
- Rebuild the cover letter as a business letter: hierarchy through varied margins, a
  right-aligned date, a distinct subject style, and a real signature block (closing
  salutation, reserved signature space, signatory name) emitted unconditionally.
- Make the signature a first-class editable field. Editing the sender no longer silently
  overwrites an explicit signature, and the sender is now the logged-in person plus the
  household address rather than the household name.
- Keep the body plain text with line breaks per the product decision — no rich-text or
  markdown dependency — and instruct the LLM to write plain prose, since a generated letter
  could otherwise print literal markup characters in a document handed to a bank.
- Fix the oversized reset control in the shared EditableField: intrinsic width, a glyph sized
  from a font token, the 44x44 hit area preserved, and a focus ring that was missing.

Fixes #1932

Co-Authored-By: Claude backend-developer <noreply@anthropic.com>
Co-Authored-By: Claude dev-team-lead <noreply@anthropic.com>
Co-Authored-By: Claude e2e-test-engineer <noreply@anthropic.com>
Co-Authored-By: Claude frontend-developer <noreply@anthropic.com>
Co-Authored-By: Claude product-architect <noreply@anthropic.com>
Co-Authored-By: Claude product-owner <noreply@anthropic.com>
Co-Authored-By: Claude qa-integration-tester <noreply@anthropic.com>
Co-Authored-By: Claude translator <noreply@anthropic.com>
Co-Authored-By: Claude ux-designer <noreply@anthropic.com>
- Carry product-owner review notes that were written after #1932's final commit had landed:
  the #1952 strip-not-reject ruling with its false-positive guards, the #1953 coincidental-
  constant reading, and two reusable review patterns.

Memory only — no production code, tests, or configuration.

Co-Authored-By: Claude product-owner <noreply@anthropic.com>
Fixes the E2E failures that block `E2E Gates` on the `beta` -> `main` promotion. All 16 shards green.

- `invoices.spec.ts:841` had never passed since it was written in #1880: the header lookup compared `innerText()` (which reflects `.tableHeader`'s `text-transform: uppercase`) against a mixed-case label. Fixing that exposed a second bug — the test read its invoice off page 1 of a 37-item list where it wasn't — now fixed by searching first.
- `i18n.spec.ts:305` deleted the locale preference while `localStorage` still held `de`, so `LocaleContext.syncWithServer`'s migration branch re-created the row 13ms later. Now sets German server-side only and asserts `localStorage` is empty before deleting.
- `i18n.spec.ts:131` and `dashboard.spec.ts` failed on genuine cross-test contamination of the shared admin user's preference rows: serial mode and per-test users respectively.

Follow-ups filed: #1957 (shared-admin preference isolation across specs), #1955 (the underlying production debounce race).

Co-Authored-By: Claude dev-team-lead <noreply@anthropic.com>
Co-Authored-By: Claude e2e-test-engineer <noreply@anthropic.com>
Co-Authored-By: Claude product-owner <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 2.13.0-beta.50 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

steilerDev and others added 2 commits August 3, 2026 14:11
A resolving preference save re-triggered useColumnPreferences' load effect and re-applied its own stale payload, discarding any column toggle made after that payload was captured. Enabling column A, pausing longer than the 500ms debounce, then enabling B silently dropped B.

Adds a local-authority guard so the hook stops re-applying store echoes once the user has edited, and a serialized save queue keeping at most one write in flight — the latter is what makes the persisted value order-independent, since two unordered PATCHes could previously leave the stale one as the last write to win.

6 of 10 new tests fail against the pre-fix code; 4 mutation probes confirm each discriminates the specific mechanism.

Fixes #1955

Co-Authored-By: Claude frontend-developer <noreply@anthropic.com>
Co-Authored-By: Claude product-architect <noreply@anthropic.com>
Co-Authored-By: Claude qa-integration-tester <noreply@anthropic.com>
Every E2E spec authenticated as one shared admin, and user_preferences rows are keyed by user id, so under fullyParallel a concurrent test's write landed inside another test's assertions. LocaleContext.syncWithServer treats the server as authoritative, so the victim's UI flipped language mid-test and its localStorage override was deleted by the same sync.

Adds e2e/fixtures/isolatedUser.ts, which overrides Playwright's storageState option so the plain page fixture is already a dedicated user — keeping trace/video/screenshot instrumentation a hand-rolled newContext() loses, and needing no test-body changes. An auto guard fixture asserts /api/auth/me reports that user, so a silent regression fails loudly. Applied to the four colliding specs.

Also scopes household-items no-area Scenario 4 to its own data: it asserted a suite-global "no unassigned item exists" precondition, which the worker-hash change exposed by moving shard boundaries.

Fixes #1957

Co-Authored-By: Claude e2e-test-engineer <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 2.13.0-beta.51 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

…inline notes, column toggles

Cover letter gets real paragraph breaks; area and attachment notes move inline into the usage cell as a grey suffix; the `†`/`‡` footnote markers become inline `(partial)` / `(less deposit)` labels on the allocated amount; the content editor gains preview-only column toggles.

Two defects found in review and fixed here:

- **Silent content loss.** The inline meta was appended as one unchunked run, and with `dontBreakRows` pdfmake discards an over-tall row's overflow rather than paginating it — page count saturated at 2 and went non-monotonic while rendered line count rose linearly, so ~7 pages of measured content were thrown away. `packUsageCellRows()` now bounds the whole rendered cell stream instead of `usageText` alone. Reachable from ordinary data: `attachmentsNote` has no maxLength and `areaText` is unbounded across leaf areas.
- **The deposit-reduced label split its own brackets.** It was the only inline label with an internal space, so it wrapped there — `(less` / `deposit)`. Both locales now use U+00A0 (same glyph advance, no geometry change), with a locale-level invariant so it cannot regress silently.

Also guards `packUsageCellRows`/`splitIntoPageSafeChunks` against a non-positive budget (both hung), and records `Abschlag` in the glossary as a measured-space short form with the 75pt column budget as the reason.

Reviewed by product-architect (approved), ux-designer (blocking wrap finding, fixed), product-owner (footnote ruling → #1965). Follow-ups: #1965-#1972.

Fixes #1959

Co-Authored-By: Claude e2e-test-engineer <noreply@anthropic.com>
Co-Authored-By: Claude frontend-developer <noreply@anthropic.com>
Co-Authored-By: Claude product-architect <noreply@anthropic.com>
Co-Authored-By: Claude product-owner <noreply@anthropic.com>
Co-Authored-By: Claude qa-integration-tester <noreply@anthropic.com>
Co-Authored-By: Claude translator <noreply@anthropic.com>
Co-Authored-By: Claude ux-designer <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 2.13.0-beta.52 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@steilerDev
steilerDev merged commit 563d334 into main Aug 3, 2026
44 checks passed
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 2.13.0 🎉

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