Conversation
…ot assigned (#1200) * fix(work-items): display vendor name in assignedTo column when user not assigned The assignedTo column on the work items list previously only displayed assigned user names, silently ignoring assigned vendors. Updated the column's render function to check assignedUser first, then fall back to assignedVendor, then show '—' if neither is set. Co-Authored-By: Claude frontend-developer (Haiku 4.5) <noreply@anthropic.com> * chore: update review metrics for PR #1200 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Frontend Developer <frontend-developer@cornerstone.local> Co-authored-by: Claude frontend-developer (Haiku 4.5) <noreply@anthropic.com>
…d error handling (#1203) The backup creation flow had multiple issues causing SQLITE_CANTOPEN errors: 1. Used system tar binary which is unavailable in DHI production image — replaced with Node.js tar package (pure JS, no system dependencies) 2. No pre-flight writability check on backup directory — added probe file test 3. SQLite backup and tar errors propagated as generic 500 — wrapped in new BackupFailedError with BACKUP_FAILED error code 4. DB snapshot was written to backup dir instead of data dir, so it was never included in the tar archive — corrected to write into data dir 5. Post-tar cleanup referenced wrong path — corrected to use dbSnapshotPath Fixes #1201 Co-authored-by: Frontend Developer <frontend-developer@cornerstone.local> Co-authored-by: Claude backend-developer (Haiku) <noreply@anthropic.com>
* feat(backup): set sensible default for BACKUP_DIR - Add default value '/backups' to BACKUP_DIR configuration in config.ts - Create /backups directory in Docker deps stage for production stage copying - Copy /backups directory with correct node:node ownership in production stage - Declare /backups as a Docker VOLUME - Add cornerstone-backups volume to docker-compose.yml service - Add backup env vars to commented environment block in docker-compose.yml - Uncomment BACKUP_DIR with updated comment in .env.example Fixes #1199 Co-Authored-By: Claude backend-developer (Haiku 4.5) <noreply@anthropic.com> * refactor(backup): make backupDir required, clean up non-null assertions Make `backupDir` a required field in `AppConfig` since it now always has the '/backups' default. Remove non-null assertions in backups.ts and simplify guard conditions in backupService.ts to only check backupEnabled. Update tests to reflect the new defaults and remove the now-unreachable backupDir-undefined guard test. 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> * test(backup): remove unreachable BACKUP_NOT_CONFIGURED tests With BACKUP_DIR defaulting to /backups, backupEnabled is always true when no env var is set. The 4 tests asserting 503 BACKUP_NOT_CONFIGURED for missing BACKUP_DIR now test an unreachable code path. Co-Authored-By: Claude qa-integration-tester (Haiku 4.5) <noreply@anthropic.com> * docs: update BACKUP_DIR default in CLAUDE.md env var table Reflects the new /backups default added in the previous commit. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(e2e): mock 503 response in backup not-configured E2E test BACKUP_DIR now defaults to /backups, so the testcontainer always has backups enabled. The "not configured" E2E test must now mock the 503 API response instead of relying on the testcontainer not having BACKUP_DIR set. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Frontend Developer <frontend-developer@cornerstone.local> Co-authored-by: Claude backend-developer (Haiku 4.5) <noreply@anthropic.com>
The CLA workflow fails on PRs with agent-authored commits because "Frontend Developer" and "Backend Developer" are not GitHub users and were not in the allowlist. The contributor-assistant action falls back to the git author name when GitHub login is null, so adding these names to the allowlist resolves the check. Fixes #1203 Co-authored-by: Frontend Developer <frontend-developer@cornerstone.local> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Metrics were being written to .claude/metrics/review-metrics.jsonl and as HTML comments on PR reviews. Remove both approaches entirely — no metrics are collected going forward. Co-authored-by: Frontend Developer <frontend-developer@cornerstone.local> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CI test shards now collect coverage and a new Coverage Report job merges shard results into a single artifact (coverage-summary.json + coverage-final.json) retained for 30 days. A zero-dependency merge script (scripts/merge-coverage.mjs) handles the Istanbul JSON merging without adding nyc as a dependency. Agent policy changes prevent coverage regression: - dev-team-lead review now enforces test file parity (blocking) - qa-integration-tester must run --coverage locally before committing - e2e-test-engineer must verify route coverage inventory - Implementation checklist updated with parity + E2E route requirements Co-authored-by: Frontend Developer <frontend-developer@cornerstone.local> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
#1210) * fix(date-range-picker): prevent phase reset after start date selection The DateRangePicker was resetting to 'selecting-start' phase immediately after the user selected a start date. This occurred because the useEffect synced phase from props, and the parent DateFilter component did not call onChange for a complete range until both dates were selected. The fix introduces pendingStartDate internal state to track intermediate (uncommitted-to-parent) selection state. The problematic useEffect is replaced with a narrower version that only responds when the parent externally clears both values (startDate='' AND endDate=''). This way, the component doesn't lose its UI state during the two-phase selection flow until the parent explicitly resets. All internal uses of startDate (prop) that tracked the selection flow are now updated to use pendingStartDate instead. The startDate prop is now only used for initial view calculation and detecting external clear. Co-Authored-By: Claude frontend-developer (Haiku 4.5) <noreply@anthropic.com> * test(date-range-picker): add regression tests for phase-persistence fix (#1178) Cover the pendingStartDate internal state introduced by the fix: - Phase persists to selecting-end immediately after clicking start date within the same mounted instance (regression guard for issue #1178) - Hover range preview and dayDisabled class both use pendingStartDate rather than the startDate prop after a start-date click - Single-instance two-click flow: start → end without a prop update between clicks emits the correct onChange('start', 'end') call - Escape during selecting-end resets pendingStartDate and phase - Clicking before / on pendingStartDate during selecting-end handled - External clear (both props → '') resets phase via rerender - Partial prop update (only startDate changes) does NOT reset phase Update 'advances to selecting-end phase' test to verify phase within the same mounted instance instead of a separate render call. Rewrite 'when startDate prop changes... externally' to use rerender on a single component instance (correct test technique for the narrowed useEffect). Add integration test to DateFilter.test.tsx: single-instance two-click flow verifying DateFilter does not call onChange for partial selections and calls onChange once with full range format after second click. Fixes #1178 Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com> * test(date-range-picker): fix three props sync tests broken by narrowed useEffect The production fix narrowed the reset useEffect so it only fires when both startDate and endDate props are exactly ''. Three tests were written against the old behavior (phase derived from prop values on mount) and now fail: 1. 'when both startDate and endDate props are externally cleared to ""' — was mounting with non-empty props and asserting phase='end' before rerender; fixed by mounting with non-empty props, clicking a day to reach selecting-end internally, then rerendering with empty props so the useEffect fires. 2. 'when endDate prop is set externally, phase remains at selecting-end' — was asserting that mounting with only startDate set produces phase='end' (old behavior). Rewritten to assert that mounting with dates set produces phase='start' (new behavior: phase is internal-only). 3. Regression block 'external clear via rerender' — same root cause as (1); was mounting with non-empty props and asserting 'end' before rerender. Fixed with the same click-first pattern. Fixes #1178 Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com> * chore: trigger CI retry * fix(date-range-picker): restore useEffect branch for external startDate changes The narrowed useEffect only handled the clear case (both props empty), but also needs to handle when startDate changes externally from '' to a value (while endDate stays ''). This is needed for DateFilter integration where localFrom updates asynchronously via useEffect. Also fixes 3 test assertions that didn't account for the useState initial value being derived from the startDate prop. 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> * chore: update review metrics for PR #1210 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Frontend Developer <frontend-developer@cornerstone.local> Co-authored-by: Claude frontend-developer (Haiku 4.5) <noreply@anthropic.com>
Co-authored-by: Frontend Developer <frontend-developer@cornerstone.local> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…2E (#1204) (#1211) * test(photos): add unit tests for usePhotos hook and photoApi client Adds 95%+ statement coverage for client/src/hooks/usePhotos.ts and client/src/lib/photoApi.ts. Tests cover loading/success/error states, uploadPhoto (XHR-based with progress tracking), deletePhoto (optimistic removal), updatePhoto (optimistic update), refresh, and all URL helper functions. Includes upload progress lifecycle, FormData construction, XHR event handling (load/error/abort), and entity change refetch. Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com> * test(invoices): add unit and integration tests for invoice budget line service and standalone invoice routes Adds 95%+ coverage tests for three previously uncovered server files: - invoiceBudgetLineService.test.ts: unit tests for all 5 exported functions (list, create, update, delete, getForInvoice) with full error path coverage - invoiceBudgetLines.test.ts: integration tests for all 4 route handlers (GET/POST/PATCH/DELETE) via app.inject(), including auth, validation, 404, BudgetLineAlreadyLinkedError, and ItemizedSumExceedsInvoiceError scenarios - standaloneInvoices.test.ts: integration tests for GET /api/invoices (pagination, filtering by status/vendorId/amount/date/dueDate/q, all sortBy options) and GET /api/invoices/:invoiceId (cross-vendor lookup, budget lines) Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com> * test(coverage): add unit tests for milestone pages and invoices page Add Jest unit tests for zero-coverage client-side pages and API modules: - MilestonesPage: loading skeleton, populated data, error/empty states, actions menu, delete flow with confirmation modal, client-side filtering - MilestoneDetailPage: loading/404/error states, view mode, edit mode with form pre-fill and validation, save/cancel, delete confirmation, linked items display, unlink buttons - MilestoneCreatePage: form rendering, validation (title required, date required), successful submission with navigation, API error handling, submit button state - InvoicesPage: loading skeleton, populated data (invoice numbers, vendor names, summary cards), error state, empty state, create invoice modal with full validation flow, actions menu (view navigation), API client tests already existed Fixes #1204 Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com> * test(coverage): add unit tests for vendor/trade/area client utilities Add 95%+ coverage unit tests for 10 previously uncovered source files: API client layer: - areasApi.test.ts — fetchAreas, fetchArea, createArea, updateArea, deleteArea - tradesApi.test.ts — fetchTrades, fetchTrade, createTrade, updateTrade, deleteTrade - vendorContactsApi.test.ts — listVendorContacts, createVendorContact, updateVendorContact, deleteVendorContact - davTokensApi.test.ts — getDavTokenStatus, generateDavToken, revokeDavToken, getDavProfileUrl - timelineApi.test.ts — getTimeline Utility: - areaTreeUtils.test.ts — buildTree (depth-first ordering, sortOrder/alpha sort, cycle guard, orphan handling) React hooks: - useAreas.test.ts — CRUD lifecycle, loading/error states, refetch - useTrades.test.ts — CRUD lifecycle, loading/error states, refetch - useVendorContacts.test.ts — empty vendorId guard, optimistic updates, error propagation - useDavToken.test.ts — generate/revoke/clearNewToken lifecycle, status refresh Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com> * test(coverage): add page and API client unit tests for vendors, user management, household items, and work item budgets/milestones Adds five new test files targeting previously uncovered client-side code (Gap 4 + Gap 6): - client/src/lib/workItemBudgetsApi.test.ts (24 tests): fetchWorkItemBudgets, createWorkItemBudget, updateWorkItemBudget, deleteWorkItemBudget — validates URL construction, request bodies, response extraction, and error propagation. - client/src/lib/workItemMilestonesApi.test.ts (21 tests): getWorkItemMilestones, addRequiredMilestone, removeRequiredMilestone, addLinkedMilestone, removeLinkedMilestone — validates HTTP method, URL path with milestoneId, and error handling. - client/src/pages/VendorsPage/VendorsPage.test.tsx (20 tests): loading/data/error states, create vendor modal (validation, API call, error display), action menu, delete confirmation. - client/src/pages/UserManagementPage/UserManagementPage.test.tsx (23 tests): loading/data/error states, client-side filter, action menu (edit disabled for deactivated users), edit modal (pre-fill, validation, API call, cancel), deactivate modal (confirm, error). - client/src/pages/HouseholdItemsPage/HouseholdItemsPage.test.tsx (20 tests): loading/data/error states, action menu, delete confirmation with error handling, graceful degradation when vendor/category fetch fails. Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com> * test(coverage): fix TypeScript duplicate property error in areaTreeUtils.test.ts The makeArea helper used an object literal that set `id` and `name` both explicitly and again via spread `...overrides`, triggering TS2783 (property specified more than once). Refactor to build defaults object first then merge. Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com> * test(coverage): add unit and integration tests for photos service and route Adds 96 tests covering the server-side photo upload infrastructure: - photoService.test.ts (51 tests): unit tests for uploadPhoto, getPhoto, getPhotosForEntity, updatePhoto, reorderPhotos, deletePhoto, deletePhotosForEntity, and getPhotoFilePath. sharp is mocked via jest.unstable_mockModule to avoid native binary dependency. - photos.test.ts (45 tests): integration tests for all 8 photo route handlers using app.inject() with multipart form bodies, mock photoService, and real auth/session via buildApp(). Coverage achieved: - photoService.ts: 97.82% statements, 91.66% branches, 100% functions - photos.ts: 91.91% statements, 85.18% branches, 100% functions/lines (uncovered lines are unreachable auth guards inside route handlers) Part of #1204: close server-side coverage gaps. Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com> * chore(memory): update qa agent memory with Gap 5 test patterns Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com> * test(coverage): fix TypeScript type errors and add server-side route/service tests Fix TypeScript type errors in three client test files: - HouseholdItemsPage.test.tsx: correct ApiClientError arg order (statusCode first), type jest.fn() mocks for useAreas createArea/updateArea/deleteArea methods - UserManagementPage.test.tsx: correct ApiClientError arg order, type jest.fn() mocks for refreshAuth/logout as () => Promise<void> - VendorsPage.test.tsx: correct ApiClientError arg order, type jest.fn() mocks for useTrades createTrade/updateTrade/deleteTrade methods Add 5 server-side integration/unit tests (Gap 4): - householdItemSubsidyPayback.test.ts (route): subsidy payback calculation endpoint - workItemMilestones.test.ts (route): work item milestone dependency routes - householdItemBudgetService.test.ts: HI budget line CRUD and calculations - householdItemWorkItemService.test.ts: HI-to-work-item association service - workItemMilestoneService.test.ts: work item milestone dependency service Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com> * test(coverage): fix HouseholdItemStatus enum values in workItemService test Replace invalid status values 'ordered', 'delivered', and 'cancelled' with valid HouseholdItemStatus enum values: 'purchased', 'arrived', 'scheduled'. HouseholdItemStatus = 'planned' | 'purchased' | 'scheduled' | 'arrived'. Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com> * test(client): fix top-level await import crash in page component tests Move `await import()` into `beforeEach` using the established lazy-load pattern. Jest's --experimental-vm-modules runner does not support top-level await in test files. Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test(client): add invoiceBudgetLinesApi unit tests 33 tests covering fetchInvoiceBudgetLines, createInvoiceBudgetLine, updateInvoiceBudgetLine, and deleteInvoiceBudgetLine. Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test(e2e): add milestones E2E tests and page objects 3 POMs (MilestonesPage, MilestoneCreatePage, MilestoneDetailPage) and 50 test cases covering CRUD flows, responsive layout, dark mode, and smoke tests. Closes Gap 1 E2E coverage. Co-Authored-By: Claude e2e-test-engineer (Sonnet 4.5) <noreply@anthropic.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test(e2e): add invoices, settings/manage E2E tests and page objects - InvoicesPage + InvoiceDetailPage POMs with CRUD tests (list, create, detail, edit, delete, status filter, responsive, dark mode) - HouseholdItemEditPage POM for edit page coverage - Settings/Manage E2E tests covering all 4 tabs (Areas, Trades, Budget Categories, HI Categories) with CRUD, URL deep-linking, responsive - Agent memory updates Closes Gap 2 + Gap 6 E2E coverage. Co-Authored-By: Claude e2e-test-engineer (Sonnet 4.5) <noreply@anthropic.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test(client): fix DataTable dual-render and mock isolation issues in page tests Fix 39 failing tests across VendorsPage, UserManagementPage, and HouseholdItemsPage by addressing three root causes: 1. Missing preferencesApi mock — DataTable's useColumnPreferences hook calls listPreferences() on mount; without a mock the async failure caused repeated re-renders and cascading re-fetches (fetchVendors called 16×, listHouseholdItems 13×, listUsers 3×). 2. Unstable Map reference in useTableState mock — returning `filters: new Map()` inside the mock factory creates a new Map instance on every render, triggering infinite useEffect re-fetch loops in pages that include filters in their dependency array. Fixed by hoisting stable Map constants outside the mock factory. 3. DataTable dual-render (table rows + mobile cards) — getByText/ getByTestId throw "found multiple elements". Replaced with getAllByText/getAllByTestId throughout, and switched toHaveBeenCalledTimes(1) to toHaveBeenCalled() for the same reason. Also: use mockReset() instead of mockClear() in beforeEach to drain stale mockResolvedValueOnce queues from prior tests, and fix button-finder predicates to match actual translation output (t('vendors.buttons.create') = "Add Vendor"; modal footer button text is "Delete Item" not "Delete"). Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com> * test(client): fix duplicate-render and deferred-promise issues in page/hook tests Fix remaining test failures in MilestonesPage, MilestoneCreatePage, MilestoneDetailPage, InvoicesPage, and usePhotos: - MilestonesPage: delete modal confirm button text is "Delete Milestone" (from t('milestones.delete.delete')), not /^delete$/i; update regex to /delete milestone/i. Also apply getAllByText/getAllByTestId pattern for DataTable dual-render (table rows + mobile cards). - MilestoneCreatePage/MilestoneDetailPage: form validation tests use fireEvent.submit() instead of clicking the submit button to bypass native HTML required validation and let the JS handler run. Fix non-404 error tests to assert /not found/i since milestone stays null when fetch fails. Use getAllByText for /completed/i which appears in multiple elements. - InvoicesPage: apply getAllByText/getAllByTestId throughout for DataTable dual-render. Chain mockResolvedValue (not Once) for fetchAllInvoices in error state tests to handle possible re-fetch calls triggered by useTableState effect. Fix amount input label selector to /^amount/i. - usePhotos: use a deferred promise in the progress wrapper test so the upload does not resolve before the progress assertion runs. Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com> * test(client): fix deferred-promise contamination in usePhotos and InvoicesPage usePhotos test 17: replaced immediate Promise.resolve() with a deferred promise to prevent upload resolution from flushing before the progress assertion, fixing the uploadProgress.get() = undefined failure and the result.current = null contamination in tests 18-32. InvoicesPage: switched modal tests from mockResolvedValueOnce to mockResolvedValue so useTableState-driven extra loadInvoices calls don't consume the only mock and cause DataTable error banners to appear alongside modal validation alerts. Fixed getByText/getByTestId → getAllByText/ getAllByTestId for DataTable dual-render duplicates. Changed user.type on number inputs to fireEvent.change for reliable controlled-input state. Fixes #1204 Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com> * test(client): fix areaTreeUtils sortOrder for deterministic ordering Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test(server): fix additionalProperties test assertions for Fastify AJV behavior Fastify's AJV compiler is configured with removeAdditional=true by default, which strips unknown properties from request bodies and querystrings rather than rejecting them with 400. Three tests incorrectly expected 400 when sending unknown fields — corrected to expect the actual strip-and-succeed behavior. - invoiceBudgetLines: POST and PATCH tests with unknownField now expect 201/200 - standaloneInvoices: GET with unknownParam now expects 200 (param stripped) Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com> * test(server): fix householdItemBudgetService tests — add required budgetSourceId The service requires budgetSourceId on create. Added defaultSourceId to all create calls and fixed test assertions to match actual service behavior (budgetSourceId cannot be cleared). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore(memory): update QA agent memory with test fix patterns * fix(e2e): correct milestone API response shape in helpers and smoke tests POST /api/milestones returns MilestoneDetail directly (no { milestone: {...} } wrapper), but createMilestoneViaApi() and Scenarios 4 & 5 were trying to read body.milestone.id, causing TypeError: Cannot read properties of undefined (reading 'id') when running against the real Docker container. Fix: read body.id directly (MilestoneDetail shape per API contract). Co-Authored-By: Claude e2e-test-engineer (Sonnet 4.5) <noreply@anthropic.com> * fix(ci): rename coverage shard files to avoid merge collision Each test shard produces coverage/coverage-final.json. With merge-multiple, files overwrite each other. Rename to coverage-shard-N.json before upload so all shards survive download. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(e2e): fix area/trade delete selectors and Promise.race in invoice POM Area and trade delete buttons in ManagePage only have the text "Delete" with no aria-label containing the entity name. The tests were using `getByRole('button', { name: \`Delete \${areaName}\` })` which would never match. Fix: scope via the itemRow filtered by entity name, then target the "Delete" button within that row. Also replace Promise.race() with Promise.any() in InvoicesPage.waitForLoaded() to prevent the losing waitFor() calls from becoming dangling unhandled rejections after the race resolves, which could cause flaky failures in concurrent shard runs. Fixes #1204 Co-Authored-By: Claude e2e-test-engineer (Sonnet 4.5) <noreply@anthropic.com> * chore(memory): update E2E agent memory with fix patterns --------- Co-authored-by: Frontend Developer <frontend-developer@cornerstone.local> Co-authored-by: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com>
chore: sync main into beta
Detailed Validation1. Backup Feature — Sensible Default for BACKUP_DIR
2. Backup — Improved Error Handling
3. Work Items — Vendor Name Display
4. Date Range Picker — Phase Reset Fix
5. General Regression Check
|
|
🎉 This PR is included in version 2.3.0-beta.6 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.34.1 to 4.35.1. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](github/codeql-action@3869755...c10b806) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.35.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [docker/login-action](https://github.com/docker/login-action) from 4.0.0 to 4.1.0. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](docker/login-action@b45d80f...4907a6d) --- updated-dependencies: - dependency-name: docker/login-action dependency-version: 4.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 7.0.0 to 7.1.0. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](docker/build-push-action@d08e5c3...bcafcac) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-version: 7.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [docker/scout-action](https://github.com/docker/scout-action) from 1.20.3 to 1.20.4. - [Release notes](https://github.com/docker/scout-action/releases) - [Commits](docker/scout-action@8910519...bacf462) --- updated-dependencies: - dependency-name: docker/scout-action dependency-version: 1.20.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [actions/deploy-pages](https://github.com/actions/deploy-pages) from 4.0.5 to 5.0.0. - [Release notes](https://github.com/actions/deploy-pages/releases) - [Commits](actions/deploy-pages@d6db901...cd2ce8f) --- updated-dependencies: - dependency-name: actions/deploy-pages dependency-version: 5.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [actions/configure-pages](https://github.com/actions/configure-pages) from 5.0.0 to 6.0.0. - [Release notes](https://github.com/actions/configure-pages/releases) - [Commits](actions/configure-pages@983d773...45bfe01) --- updated-dependencies: - dependency-name: actions/configure-pages dependency-version: 6.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [dependabot/fetch-metadata](https://github.com/dependabot/fetch-metadata) from 2.5.0 to 3.0.0. - [Release notes](https://github.com/dependabot/fetch-metadata/releases) - [Commits](dependabot/fetch-metadata@21025c7...ffa630c) --- updated-dependencies: - dependency-name: dependabot/fetch-metadata dependency-version: 3.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
|
🎉 This PR is included in version 2.3.0-beta.7 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Co-authored-by: Frank Steiler <frank@steiler.de>
Co-authored-by: Frank Steiler <frank@steiler.de>
|
🎉 This PR is included in version 2.3.0-beta.32 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
…se-proxy setups Two production fixes triggered by the fastify 5.8.5 security patch for CVE-2026-33806 (trustProxy socketAddr null-check). Under the tightened code path, `request.ip` can resolve to `undefined` when the raw TCP socket lacks metadata, which collapses `@fastify/rate-limit`'s default `keyGenerator` (which uses `request.ip`) to a single `undefined` bucket — effectively shared across all unknown-IP callers. Changes: - `server/src/plugins/rateLimitPlugin.ts`: add explicit keyGenerator that falls back through `request.ip` → first x-forwarded-for → x-real-ip → literal "unknown". Unknown-IP requests are still rate-limited (bucketed as "unknown") rather than silently shared. - `server/src/app.ts`: pass `trustProxy: 1` (number of hops) instead of boolean `true`. We sit behind exactly one reverse proxy in production; the number-based path is the more precise and robust semantic, and it's the code path explicitly restored/hardened in 5.8.5 per the CVE advisory. Public `/api/config` still exposes `trustProxy` as boolean — the external contract is unchanged. Only the Fastify constructor arg is narrowed. Unblocks PR #1215 (beta → main promotion) E2E Gates by fixing the `proxy-setup.spec.ts:186` X-Forwarded-For regression. Co-Authored-By: Claude backend-developer (Haiku 4.5) <noreply@anthropic.com>
…se-proxy setups (#1303) Two production fixes triggered by the fastify 5.8.5 security patch for CVE-2026-33806 (trustProxy socketAddr null-check). Under the tightened code path, `request.ip` can resolve to `undefined` when the raw TCP socket lacks metadata, which collapses `@fastify/rate-limit`'s default `keyGenerator` (which uses `request.ip`) to a single `undefined` bucket — effectively shared across all unknown-IP callers. Changes: - `server/src/plugins/rateLimitPlugin.ts`: add explicit keyGenerator that falls back through `request.ip` → first x-forwarded-for → x-real-ip → literal "unknown". Unknown-IP requests are still rate-limited (bucketed as "unknown") rather than silently shared. - `server/src/app.ts`: pass `trustProxy: 1` (number of hops) instead of boolean `true`. We sit behind exactly one reverse proxy in production; the number-based path is the more precise and robust semantic, and it's the code path explicitly restored/hardened in 5.8.5 per the CVE advisory. Public `/api/config` still exposes `trustProxy` as boolean — the external contract is unchanged. Only the Fastify constructor arg is narrowed. Unblocks PR #1215 (beta → main promotion) E2E Gates by fixing the `proxy-setup.spec.ts:186` X-Forwarded-For regression. Co-authored-by: Frank Steiler <frank@steiler.de> Co-authored-by: Claude backend-developer (Haiku 4.5) <noreply@anthropic.com>
|
🎉 This PR is included in version 2.3.0-beta.33 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
…#1297) * fix(filters): support arbitrary hierarchy depth in EnumFilter Rewrites hierarchy handling to use a `childrenOf` adjacency map and DFS `visibleRows` computation, replacing the previous two-level-only approach. Recursive cascade in `handleToggle` now adds/removes all descendants at any depth when a parent is toggled. `isIndeterminate` checks the full subtree via `allDescendantsOf`. Depth-scaled indent applied via inline `--enum-depth` CSS custom property consumed by `.filterCheckboxItem` padding-left calc; removed the old `.filterCheckboxIndented` class. Fixes #1294 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> * test(filters): cover EnumFilter arbitrary-depth hierarchy Adds 22 tests across 14 scenarios: 3-level and 4-level trees, DFS render order, inline --enum-depth CSS custom property values, cascade toggle add/remove (root → all descendants, middle node → subtree only), full uncheck cascade, indeterminate state with grandchildren, Select All excluding __none__ sentinel, sentinel rendering alongside hierarchy, flat-list path (all depths 0), mixed two-root tree, and orphan-option fallthrough at depth 0. Uses element.style.getPropertyValue('--enum-depth') for custom property assertions (JSDOM does not support toHaveStyle for custom properties). Fixes #1294 Co-Authored-By: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com> Co-Authored-By: Claude qa-integration-tester (Sonnet 4.6) <noreply@anthropic.com> * fix(filters): consider self state in isIndeterminate for EnumFilter The previous logic compared descendant selection count to descendant total only, which incorrectly returned false when a parent had exactly one descendant and that descendant was selected — even though the parent itself was not selected. The parent's checkbox would render empty while its entire subtree was checked, which is clearly a mixed state. Now treat the group (self + descendants) as mixed unless either fully checked (self + all descendants) or fully unchecked (self + none of descendants). Fixes #1294 Co-Authored-By: Claude frontend-developer (Haiku 4.5) <noreply@anthropic.com> --------- Co-authored-by: Frank Steiler <frank@steiler.de> Co-authored-by: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
|
🎉 This PR is included in version 2.3.0-beta.34 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
…onse (#1304) The GET /api/budget-sources/:sourceId/budget-lines endpoint was always returning area.ancestors as []. The frontend relies on this chain to build the hierarchical area tree — when lines are deep under a parent area (e.g. items in "Ground Floor" and "Upper Floor" under "House"), "House" never appeared because the response never surfaced it. - Load area map once per request via loadAreaMap(db) - Pass the map into both builders; resolve each line's area ancestors via resolveAreaAncestors(areaId, areaMap) - 5 new unit tests covering root/2-level/3-level hierarchies + color preservation Co-authored-by: Frank Steiler <frank@steiler.de> Co-authored-by: Claude backend-developer (Haiku) <noreply@anthropic.com>
|
🎉 This PR is included in version 2.3.0-beta.35 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
PR #1286 moved Vendors from the Budget subnav to Settings, and the Budget subnav gained an Invoices tab. The budget-overview test at line 101 was still asserting the old tab list ['Overview', 'Vendors', 'Sources', 'Subsidies']. Corrected to the current set ['Overview', 'Invoices', 'Sources', 'Subsidies']. Partner fix to #1293 (which fixed the same stale assertion in invoices.spec.ts). Unblocks PR #1215 E2E Gates. Co-Authored-By: Claude e2e-test-engineer (Sonnet 4.5) <noreply@anthropic.com>
…ove (#1305) PR #1286 moved Vendors from the Budget subnav to Settings, and the Budget subnav gained an Invoices tab. The budget-overview test at line 101 was still asserting the old tab list ['Overview', 'Vendors', 'Sources', 'Subsidies']. Corrected to the current set ['Overview', 'Invoices', 'Sources', 'Subsidies']. Partner fix to #1293 (which fixed the same stale assertion in invoices.spec.ts). Unblocks PR #1215 E2E Gates. Co-authored-by: Frank Steiler <frank@steiler.de> Co-authored-by: Claude e2e-test-engineer (Sonnet 4.5) <noreply@anthropic.com>
|
🎉 This PR is included in version 2.3.0-beta.36 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
…d item work-item references (#1271, #1272, #1273) (#1299) * feat(work-items): include area breadcrumb on diary, invoice, household item work-item references (#1271, #1272, #1273) Extends the area breadcrumb pattern (established in PR #1270 for work-item refs on work-item detail pages) to three additional cross-entity reference surfaces: - #1271: Diary entry detail page — work-item references now include area breadcrumb (areaName + areaId) via DiaryEntry shared type and diaryService - #1272: Invoice detail page budget lines section — work-item references now include area breadcrumb via InvoiceBudgetLine shared type and invoiceBudgetLineService - #1273: Household item detail page dependency references — work-item references now include area breadcrumb via HouseholdItem shared type and householdItemDepService Backend services join the areas table via work_items.area_id. Shared types add nullable areaId/areaName fields. Frontend renders AreaBreadcrumb component consistent with the canonical WorkItemDetailPage pattern. API Contract wiki updated with new response fields. Six new test files added (3 service-level, 3 component-level). Three new E2E specs added. InvoiceDetailPage POM updated. Fixes #1271 Fixes #1272 Fixes #1273 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 frontend-developer (Haiku 4.5) <noreply@anthropic.com> Co-Authored-By: Claude qa-integration-tester (Haiku 4.5) <noreply@anthropic.com> Co-Authored-By: Claude e2e-test-engineer (Sonnet 4.5) <noreply@anthropic.com> * test: fix CI typecheck failures from new required fields on shared types Add the three new required fields (added in this PR) to all pre-existing test fixtures that construct these types inline: - `DiaryEntrySummary.sourceEntityArea: null` in diary test fixtures - `InvoiceBudgetLineDetailResponse.parentItemArea: null` in invoice budget line test fixtures - `HouseholdItemDepPredecessorSummary.area: null` in household item dependency test fixtures Fixes #1299 Co-Authored-By: Claude qa-integration-tester (Sonnet 4.6) <noreply@anthropic.com> * test: fix remaining CI typecheck failures in DiaryEntryCreatePage and area test - Add `sourceEntityArea: null` to `createdEntry` fixture in DiaryEntryCreatePage.test.tsx - Add WorkItemBudgetLine/HouseholdItemBudgetLine type annotations to mock functions in InvoiceBudgetLinesSection.area.test.tsx to resolve `never[]` inference error Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com> --------- Co-authored-by: Frank Steiler <frank@steiler.de> Co-authored-by: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
|
🎉 This PR is included in version 2.3.0-beta.37 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
…assigned" to "No Area" (#1302) * fix(budget): dynamic depth-based indentation for nested items and 'No Area' label Bug 1 fix: Child rows (WorkItemRow, HouseholdItemRow, BudgetLineRow) now respect area depth when nested. Added depth prop to all row components and applied --item-depth inline style to name cells. Updated CSS for .cellLevel2Name and .cellLevel3Name to use calc() expressions driven by --item-depth, matching the pattern used in .cellAreaName. Bug 2 fix: Renamed unassigned area labels from 'Unassigned' to 'No Area' in English translation keys: - overview.costBreakdown.area.unassigned - sources.lines.unassignedArea common.unassigned key left untouched (out of scope per AC4). Co-Authored-By: Claude frontend-developer (Haiku 4.5) <noreply@anthropic.com> * feat(i18n): align German "No Area" label in budget namespace Rename the two German keys that label items without an assigned area from their former "Nicht zugewiesen"/"Nicht zugeordnet" values to the unified glossary-aligned phrase "Kein Bereich" (composing glossary term "Bereich" for "Area" with natural German negation "Kein"). Both keys now use the same phrase, consistent with how `lines.noCategory` uses "Keine Kategorie" for the parallel no-category case. Fixes #1295 Co-Authored-By: Claude translator (Sonnet 4.6) <noreply@anthropic.com> * test(e2e): update budget-overview for 'No Area' rename and add indent depth test Fixes #1295 - Replace all 'Unassigned' references in cost breakdown tests with 'No Area' to match the updated i18n key (bug fix in frontend commit 34f178b) - Update fixture helper name field from 'Unassigned' to 'No Area' for documentation clarity - Add explicit test asserting 'No Area' is visible and 'Unassigned' is absent after expanding the Work Items section in the cost breakdown card - Add bounding-box indent test: Kellerbau item row (depth-1 inside Keller) must have a greater left x-coordinate than the Keller area row (depth-1), verifying the --item-depth CSS custom property produces visible indentation Co-Authored-By: Claude e2e-test-engineer (Sonnet 4.6) <noreply@anthropic.com> * test(budget): update CostBreakdownTable tests for No Area rename and depth prop - Replace all 'Unassigned' text assertions in CostBreakdownTable.test.tsx with 'No Area' to match the renamed i18n key (overview.costBreakdown.area.unassigned and sources.lines.unassignedArea) - Update getButtonByControls helper: 'wi-cat-*-items' now maps to 'Expand No Area' (was 'Expand Unassigned') - Update all area:Unassigned control IDs to area:No Area - Add 6 new depth-driven indent test scenarios (A–F): A: WI at depth 0 has --item-depth: 0 B: WI in depth-1 nested area has --item-depth: 1 C: Budget line inherits parent WI depth (--item-depth: 1) D: HI at depth 1 has --item-depth: 1 E: No Area renders in WI section, Unassigned absent (AC2 regression) F: No Area renders in HI section after expansion Fixes #1295 Co-Authored-By: Claude qa-integration-tester (Sonnet 4.6) <noreply@anthropic.com> * test(budget): update SourceBudgetLinePanel test assertions for renamed i18n key Cascading fix from #1295: the i18n key `sources.lines.unassignedArea` was renamed from "Unassigned" → "No Area". Update the grouping scenario 7 assertions in the SourceBudgetLinePanel unit test to match the new label. Co-Authored-By: Claude qa-integration-tester (Sonnet 4.6) <noreply@anthropic.com> * test(e2e): update budget-source-lines assertions for "No Area" i18n rename The i18n key `sources.lines.unassignedArea` was renamed from "Unassigned" to "No Area" (German: "Kein Bereich"). Update the describe block title, test title, source entity name, visible-text assertion, inline comments, and BudgetSourcesPage POM JSDoc to reflect the new label. Co-Authored-By: Claude e2e-test-engineer (Sonnet 4.6) <noreply@anthropic.com> * chore(memory): update translator memory for #1295 Co-Authored-By: Claude translator (Sonnet 4.6) <noreply@anthropic.com> * docs: add GitHub rate-limit retry policy to CLAUDE.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(e2e): compare computed paddingLeft for cost breakdown indent assertion boundingBox().x of a <td> is the same for all cells in a column regardless of padding-left, because the outer element starts at the column's left edge. The --item-depth CSS variable drives padding-left (not x position), so switch to getComputedStyle().paddingLeft comparison which correctly reflects the visual indent applied by the calc() expression. Co-Authored-By: Claude e2e-test-engineer (Sonnet 4.6) <noreply@anthropic.com> * fix(budget): use valid spacing tokens and scoped selectors for cost breakdown indent The previous rules referenced undefined tokens (--spacing-14, --spacing-20) and had lower specificity than .rowLevelN td shorthand, so they never applied. Scope to .rowLevelN .cellXxxName (spec 0,2,0 beats 0,1,1) and use defined tokens --spacing-12 / --spacing-16 for base offsets. Co-Authored-By: Claude frontend-developer (Haiku 4.5) <noreply@anthropic.com> * chore: trigger CI re-run for PR #1302 Previous push did not trigger Quality Gates workflow. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Frank Steiler <frank@steiler.de> Co-authored-by: Claude frontend-developer (Haiku 4.5) <noreply@anthropic.com>
|
🎉 This PR is included in version 2.3.0-beta.38 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Release Summary
Standalone release promoting beta to main. Includes backup improvements (sensible defaults, JS tar library, error handling), UI fixes (work items vendor display, date range picker), test coverage improvements, and dependency updates.
Changes
Features
BACKUP_DIR(feat(backup): set sensible default for BACKUP_DIR #1202)Fixes
Dependencies
Chores / Refactoring
Tests
Change Inventory
Backend (
server/,shared/)server/src/plugins/config.ts— backup config defaultsserver/src/plugins/config.test.ts— config testsserver/src/routes/backups.ts— backup route improvementsserver/src/routes/backups.test.ts— backup route testsserver/src/services/backupService.ts— JS tar, writability check, error handlingserver/src/services/backupService.test.ts— backup service testsserver/src/errors/AppError.ts— error type updatesshared/src/types/errors.ts— error type definitionsserver/src/routes/*.test.ts— additional test coverage (photos, invoiceBudgetLines, householdItemSubsidyPayback, standaloneInvoices, workItemMilestones)server/src/services/*.test.ts— additional test coverage (calendarIcal, householdItemBudgetService, householdItemWorkItemService, invoiceBudgetLineService, photoService, vendorVcard, workItemMilestoneService)Frontend (
client/)client/src/pages/WorkItemsPage/WorkItemsPage.tsx— vendor name display fixclient/src/components/DateRangePicker/DateRangePicker.tsx— phase reset fixclient/src/components/DateRangePicker/DateRangePicker.test.tsx— date range picker testsclient/src/components/DataTable/filters/DateFilter.test.tsx— date filter testsclient/src/hooks/*.test.ts— test coverage (useAreas, useDavToken, usePhotos, useTrades, useVendorContacts)client/src/lib/*.test.ts— test coverage (areaTreeUtils, areasApi, davTokensApi, invoiceBudgetLinesApi, photoApi, timelineApi, tradesApi, vendorContactsApi, workItemBudgetsApi, workItemMilestonesApi)client/src/pages/*.test.tsx— test coverage (HouseholdItems, Invoices, MilestoneCreate, MilestoneDetail, Milestones, UserManagement, Vendors)E2E Tests (
e2e/)e2e/tests/admin/backup-restore.spec.ts— backup/restore E2E testse2e/tests/invoices/invoices.spec.ts— invoice E2E testse2e/tests/milestones/milestones.spec.ts— milestone E2E testse2e/tests/navigation/settings-manage.spec.ts— settings E2E testse2e/pages/*.ts— page object updates (HouseholdItemEdit, InvoiceDetail, Invoices, MilestoneCreate, MilestoneDetail, Milestones)e2e/fixtures/apiHelpers.ts— API helper updatesDocs / Config
CLAUDE.md— project guide updates.env.example— environment variable documentation.github/workflows/ci.yml— CI coverage reporting.github/workflows/cla.yml— CLA allowlist updateDockerfile— Docker build updatesdocker-compose.yml— compose configuration.gitignore— gitignore updates.claude/— agent definitions, skills, checklists, metrics updatesManual Validation Checklist
BACKUP_DIRset — verify it defaults to a sensible location and backup/restore worksTesting
docker pull steilerdev/cornerstone:betadocker pull steilerdev/cornerstone:pr-<pr-number>(available after CI runs)