release: promote beta to main (v2.13.0 — Bank Report Wizard) - #1958
Merged
Conversation
…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>
…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>
…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>
Contributor
|
🎉 This PR is included in version 2.13.0-beta.50 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
steilerDev
temporarily deployed
to
dockerhub-beta
August 3, 2026 07:22 — with
GitHub Actions
Inactive
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>
Contributor
|
🎉 This PR is included in version 2.13.0-beta.51 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
steilerDev
temporarily deployed
to
dockerhub-beta
August 3, 2026 12:13 — with
GitHub Actions
Inactive
7 tasks
…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>
Contributor
|
🎉 This PR is included in version 2.13.0-beta.52 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
steilerDev
temporarily deployed
to
dockerhub-beta
August 3, 2026 17:25 — with
GitHub Actions
Inactive
37 tasks
Contributor
|
🎉 This PR is included in version 2.13.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Promotes
betatomain. 50 commits sincev2.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/reportsthat 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
node-cron4.5 validation and exposed backup scheduler status (feat(backups): adopt node-cron 4.5 validation and expose scheduler status #1834)Correctness fixes
VAT_RATEmade configurable (fix(budget): make VAT rate configurable and localize BudgetLineForm currency display #1838)maximumAmountcaps applied to subsidy total reductions (fix(budget): apply maximumAmount caps to subsidySummary.totalReductions #1839)i18n and design system
ErrorCodemembers (fix(server): map non-enum error codes to translatable ErrorCode members #1843)Infrastructure
better-sqlite313 adopted; install scripts dropped from the Docker deps stageAcceptance status
All automated gates green:
Quality Gatesand all 16E2E Gatesshards.Two issues carry acceptance criteria that no automated test can verify and are still awaiting manual UAT — they are merged to
betabut 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)
useColumnPreferences' load effect, which re-applied that PATCH's own stale payload; enabling one column, pausing past the 500ms debounce, then enabling a second silently dropped the second. Fixed at the shared-hook level (all sixDataTablepages) with a local-authority guard plus a serialized single-writer save queue, the latter making the persisted value independent of response order. 6 of 10 new tests fail against the pre-fix code; 4 mutation probes confirm each discriminates the specific mechanism.e2e/fixtures/isolatedUser.tsoverrides Playwright'sstorageStateoption so the plainpagefixture is already a dedicated user, with anautoguard fixture that fails loudly if the mechanism ever silently regresses. Also fixed a suite-global precondition inhousehold-items/no-area-filter.spec.tsthat the change exposed by shifting shard boundaries.text-transformvsinnerText()case mismatch, not theuseColumnPreferencesrace it pointed at.Follow-ups filed rather than forced onto this branch:
testPrefix/authenticatedPagedecoupling, 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;isLoadedas 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:
dontBreakRowspdfmake 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 (attachmentsNotehas no maxLength;areaTextis 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 thanusageTextalone.(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.