Skip to content

release: promote beta to main#1215

Open
steilerDev wants to merge 84 commits intomainfrom
beta
Open

release: promote beta to main#1215
steilerDev wants to merge 84 commits intomainfrom
beta

Conversation

@steilerDev
Copy link
Copy Markdown
Owner

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

Fixes

Dependencies

Chores / Refactoring

Tests

Change Inventory

Backend (server/, shared/)

  • server/src/plugins/config.ts — backup config defaults
  • server/src/plugins/config.test.ts — config tests
  • server/src/routes/backups.ts — backup route improvements
  • server/src/routes/backups.test.ts — backup route tests
  • server/src/services/backupService.ts — JS tar, writability check, error handling
  • server/src/services/backupService.test.ts — backup service tests
  • server/src/errors/AppError.ts — error type updates
  • shared/src/types/errors.ts — error type definitions
  • server/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 fix
  • client/src/components/DateRangePicker/DateRangePicker.tsx — phase reset fix
  • client/src/components/DateRangePicker/DateRangePicker.test.tsx — date range picker tests
  • client/src/components/DataTable/filters/DateFilter.test.tsx — date filter tests
  • client/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 tests
  • e2e/tests/invoices/invoices.spec.ts — invoice E2E tests
  • e2e/tests/milestones/milestones.spec.ts — milestone E2E tests
  • e2e/tests/navigation/settings-manage.spec.ts — settings E2E tests
  • e2e/pages/*.ts — page object updates (HouseholdItemEdit, InvoiceDetail, Invoices, MilestoneCreate, MilestoneDetail, Milestones)
  • e2e/fixtures/apiHelpers.ts — API helper updates

Docs / Config

  • CLAUDE.md — project guide updates
  • .env.example — environment variable documentation
  • .github/workflows/ci.yml — CI coverage reporting
  • .github/workflows/cla.yml — CLA allowlist update
  • Dockerfile — Docker build updates
  • docker-compose.yml — compose configuration
  • .gitignore — gitignore updates
  • .claude/ — agent definitions, skills, checklists, metrics updates

Manual Validation Checklist

  • Backup defaults: Start app without BACKUP_DIR set — verify it defaults to a sensible location and backup/restore works
  • Backup error handling: Try creating a backup to an unwritable directory — verify clear error message
  • Work items vendor display: Create a work item assigned to a vendor (no user) — verify vendor name shows in the assignedTo column
  • Date range picker: Open a date range picker, select a start date — verify the phase dropdown doesn't reset
  • General: Navigate through main pages — verify no regressions

Testing

  • DockerHub beta image: docker pull steilerdev/cornerstone:beta
  • PR-specific image: docker pull steilerdev/cornerstone:pr-<pr-number> (available after CI runs)

steilerDev and others added 15 commits March 25, 2026 19:44
…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
@steilerDev
Copy link
Copy Markdown
Owner Author

Detailed Validation

1. Backup Feature — Sensible Default for BACKUP_DIR

  1. Start the application without setting BACKUP_DIR
  2. Navigate to Settings → Backup
  3. Trigger a manual backup
  4. Verify the backup completes successfully with the default directory
  5. Verify the backup file is created and can be downloaded/restored

2. Backup — Improved Error Handling

  1. Set BACKUP_DIR to a path that doesn't exist or isn't writable (e.g., /nonexistent/path)
  2. Attempt to create a backup
  3. Verify a clear, user-friendly error message is shown (not a raw stack trace)
  4. Verify the error indicates the directory is not writable or doesn't exist

3. Work Items — Vendor Name Display

  1. Create or find a work item that is assigned to a vendor but has no user assigned
  2. Navigate to the Work Items list page
  3. Verify the "Assigned To" column shows the vendor name instead of being blank
  4. Verify work items with both user and vendor assignments still display correctly

4. Date Range Picker — Phase Reset Fix

  1. Open any form with a date range picker (e.g., work item scheduling)
  2. Select a construction phase from the dropdown
  3. Click to select a start date
  4. Verify the phase dropdown retains the selected phase (does not reset)
  5. Select an end date and verify the phase is still correct

5. General Regression Check

  1. Navigate through: Dashboard → Work Items → Budget → Timeline → Household Items
  2. Verify all pages load without errors
  3. Check that filters and sorting work on list pages
  4. Verify dark mode toggle still works correctly

@github-actions
Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 2.3.0-beta.6 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

steilerdev-cornerstone-bot bot and others added 8 commits March 26, 2026 21:27
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>
@github-actions
Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 2.3.0-beta.7 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

steilerDev and others added 2 commits April 19, 2026 10:39
Co-authored-by: Frank Steiler <frank@steiler.de>
Co-authored-by: Frank Steiler <frank@steiler.de>
@github-actions
Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 2.3.0-beta.32 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

steilerDev pushed a commit that referenced this pull request Apr 19, 2026
…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>
@github-actions
Copy link
Copy Markdown
Contributor

🎉 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>
@github-actions
Copy link
Copy Markdown
Contributor

🎉 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>
@github-actions
Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 2.3.0-beta.35 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

steilerDev pushed a commit that referenced this pull request Apr 19, 2026
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>
@github-actions
Copy link
Copy Markdown
Contributor

🎉 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>
@github-actions
Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 2.3.0-beta.37 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

steilerdev-cornerstone-bot bot and others added 2 commits April 19, 2026 16:33
…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>
@github-actions
Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 2.3.0-beta.38 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant