Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
6ff5759
Unify budget-line creation form on invoice pages (#1402)
steilerDev May 10, 2026
82265ae
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] May 10, 2026
3fb948a
feat(invoice): add deposit support (schema, CRUD API, cascade) (#1403…
steilerDev May 10, 2026
a46f7a9
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] May 10, 2026
e1c47f2
chore(deps): bump the github-actions group with 2 updates (#1397)
dependabot[bot] May 11, 2026
c145a9a
chore(deps-dev): bump the dev-dependencies group with 6 updates (#1398)
dependabot[bot] May 11, 2026
073c94d
chore(deps): bump the prod-dependencies group with 5 updates (#1399)
dependabot[bot] May 11, 2026
f689f1a
chore(deps): bump fast-uri from 3.1.0 to 3.1.2 (#1400)
dependabot[bot] May 11, 2026
472cb8f
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] May 11, 2026
f61ed78
feat(invoice,budget): invoice deposits UI + deposit-aware budget roll…
steilerDev May 11, 2026
86712c1
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] May 11, 2026
7ce0ebd
fix(invoice): make status breakdown summary deposit-aware (#1412)
steilerDev May 12, 2026
dd271d1
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] May 12, 2026
0b0b36f
chore(invoice,budget): dedupe split helper, extract OverflowMenu, sur…
steilerDev May 12, 2026
d5eb3cb
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] May 12, 2026
f8e5db9
chore(i18n): bump glossary lastUpdated to 2026-05-12 (#1416)
steilerDev May 12, 2026
be35805
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] May 12, 2026
3de3c99
chore(deps): drop dead transitive lockfile entries (audit fix) (#1417)
steilerDev May 12, 2026
df4c770
chore(deps): bump github/codeql-action in the github-actions group (#…
dependabot[bot] May 15, 2026
3acb2c4
chore(deps-dev): bump @protobufjs/utf8 from 1.1.0 to 1.1.1 (#1418)
dependabot[bot] May 15, 2026
573d39c
chore(deps): bump the prod-dependencies group with 6 updates (#1410)
dependabot[bot] May 15, 2026
c24cf2b
chore(deps-dev): bump the dev-dependencies group with 6 updates (#1409)
dependabot[bot] May 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .claude/agent-memory/e2e-test-engineer/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,21 @@
> Detailed notes live in topic files. This index links to them.
> See: `e2e-pom-patterns.md`, `e2e-parallel-isolation.md`, `story-epic08-e2e.md`, `story-933-dav-vendor-contacts.md`, `milestones-e2e.md`, `story-1248-mass-move.md`

## InvoiceBudgetLinesSection Picker (Issue #1401, 2026-05-10)

- Picker modal: `role="dialog"`, `aria-labelledby="picker-title"` — same modal for BOTH the invoice edit modal and the picker.
- Step 1 WorkItemPicker: `getByPlaceholder('Search work items...')` inside the modal; results in `role="listbox"` → `role="option"` items.
- Step 2 "Create Budget Line" button text: exact `"Create Budget Line"` — appears in empty-state OR below existing list (only one visible at a time).
- BudgetLineForm IDs: `#budget-description`, `#budget-planned-amount`, `#budget-quantity`, `#budget-unit`, `#budget-unit-price`, `#budget-confidence`, `#budget-category`, `#budget-source`, `#budget-vendor`.
- Mode toggle buttons: "Direct Amount" (default), "Unit Pricing" — plain `type="button"`.
- Submit text: `"Add Line"` (isEditing=false) / `"Saving..."` — NOT "Save Changes".
- On success: component calls `closePicker()` → modal unmounts. On ITEMIZED_SUM_EXCEEDS_INVOICE error: form closes, reverts to list view, error in `pickerState.error` (rendered as `role="alert"` inside modal).
- Error message for exceeds: `"Linking this budget line would exceed the invoice total."` — test `.toContainText('exceed the invoice total')`.
- `createBudgetSourceViaApi(page, { name, totalAmount })` — NOT `createBudgetSourceViaApi(page, name, { ... })`.
- InvoiceGroup badge on WI detail: `[class*="invoiceLink"]` inside `budgetSection`; text = `#InvoiceNumber` or `"Invoice"` if no number.
- `pickerErrorBanner` is scoped to `budgetLinePickerModal` via `locator('[role="alert"]')` — avoids confusion with the page-level error banner.
- Test file: `e2e/tests/invoices/invoice-budget-line-create-and-link.spec.ts` (5 scenarios, no @smoke tag).

## Budget Overview Hero Card Removed (Issues #1389/#1390, 2026-04-29)

- `<section aria-label="Budget overview">` (heroCard) is **gone** from BudgetOverviewPage.tsx after #1389.
Expand Down
1 change: 1 addition & 0 deletions .claude/agent-memory/product-owner/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ All 12 stories merged. Paperless-ngx links for invoices are EPIC-08; budget repo
- **2026-02-27** — 11 issues #328-#338 (EPIC-06 + EPIC-05 sub-issues, 6 bugs / 5 stories)
- **2026-04-28** — 5 standalone UI bugs #1369-#1373 (no active parent epic; all related epics closed): #1369 hide-linked filter on Paperless picker, #1370 disable scroll-wheel on numeric inputs, #1371 "Includes VAT" parity for direct-amount budget lines, #1372 vendor in invoice picker, #1373 "Claimed" total on Budget Invoices summary. All Todo. Only EPIC-16 (Floor Plans) is currently open and is unrelated to these.
- **2026-04-29** — 2 standalone Budget Overview bugs #1389-#1390 (no parent epic): #1389 remove Budget Health hero card from `/budget/overview` (full deletion incl. helpers, state, CSS classes, i18n keys, hero-card-only tests), #1390 source-name badge missing from print preview (mobile media query hides label on print-width pages). Both Todo.
- **2026-05-10** — 1 standalone story #1401 (no parent epic; EPIC-15 closed): unify budget-line creation form on invoice/quotation flow with item-side rich form (qty × unit price, VAT incl/excl, vendor, confidence) and auto-link new line to invoice with planned amount as itemizedAmount. See [standalone-bugs-and-stories.md](standalone-bugs-and-stories.md). Todo.

## Patterns and Conventions

Expand Down
30 changes: 30 additions & 0 deletions .claude/agent-memory/product-owner/standalone-bugs-and-stories.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
name: Standalone budget/invoice bugs and stories (no active parent epic)
description: Index of standalone Todo items for budget, invoice, and quotation flows after EPIC-15 closed. Useful when a future invoice/budget epic is opened.
type: project
---

After EPIC-15 (#602, Budget-Line Invoice Linking Rework) closed in 2026-03, several invoice/budget improvements landed as standalone issues without a parent epic. The only currently open epic is EPIC-16 (Floor Plans, unrelated). When a new invoice/budget epic is created, consider linking these as sub-issues:

**Why:** the natural parent (EPIC-15) is closed, so we accept ungrouped stories rather than re-opening a closed epic. Cluster of related work signals a future epic.

**How to apply:** when triaging a new invoice/budget user-reported improvement, check this list — if it's growing (≥4 items), propose a new epic at the next planning cycle.

## Items

- **#1369** — hide-linked filter on Paperless picker (Todo, 2026-04-28 batch)
- **#1370** — disable scroll-wheel on numeric inputs (Todo, 2026-04-28 batch)
- **#1371** — "Includes VAT" parity for direct-amount budget lines (Todo, 2026-04-28 batch)
- **#1372** — vendor in invoice picker (Todo, 2026-04-28 batch)
- **#1373** — "Claimed" total on Budget Invoices summary (Todo, 2026-04-28 batch)
- **#1389** — remove Budget Health hero card from /budget/overview (Todo, 2026-04-29 batch)
- **#1390** — source-name badge missing from print preview (Todo, 2026-04-29 batch)
- **#1401** — Unify budget-line creation form on invoice/quotation flow and auto-link with planned amount (Todo, 2026-05-10) — **new story** addressing form parity gap between invoice picker create-form and item-side rich form, plus auto-link after create.

## Related code references

- Slim invoice-side form: `client/src/pages/InvoiceDetailPage/InvoiceBudgetLinesSection.tsx` (lines ~744–883, `handleCreateBudgetLine` at ~232)
- Rich item-side form: `client/src/components/budget/BudgetLineForm.tsx`
- Shared form-state hook: `client/src/hooks/useBudgetSection.ts` (`BudgetLineFormState`, `emptyForm`, VAT multiplier logic in `handleSaveBudgetLine`)
- Shared API contract: `CreateBudgetLineRequest` in `shared/src/types/budget.ts` already supports quantity/unit/unitPrice/includesVat/vendorId
- Invoice-line link API: `client/src/lib/invoiceBudgetLinesApi.ts`, `server/src/routes/invoiceBudgetLines.ts`
12 changes: 12 additions & 0 deletions .claude/agent-memory/qa-integration-tester/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@

ALL client tests using `jest.unstable_mockModule('../../lib/formatters.js', ...)` fail locally in this worktree with `useLocale must be used within a LocaleProvider`. This is a pre-existing environment issue — tests pass in CI. **Do not attempt to fix by changing mocks or adding LocaleProvider** — the tests are structurally correct and the mock works in CI. Just commit and let CI validate. The issue is specific to this worktree's Jest module resolution environment.

**Also**: TypeScript errors like `TS2305: Module '@cornerstone/shared' has no exported member 'effectivePlannedAmount'` appear when `budgetConstants.ts` is transitively imported. These ALSO only fail locally (shared dist is stale). CI builds shared correctly. Same pattern — commit and let CI validate.

## Story #1401 — InvoiceBudgetLinesSection Auto-Link Tests (2026-05-10)

When a component gains new module-level dependencies (e.g., `fetchVendors`, `BudgetLineForm`), existing tests BREAK in CI with runtime errors because those modules aren't mocked. Pattern: check CI logs (`gh api repos/.../jobs/ID/logs`) to identify which error is new (runtime unmocked call) vs pre-existing (TS type error).

**BudgetLineForm mock pattern**: Mock at module boundary with `jest.unstable_mockModule('../../components/budget/BudgetLineForm.js', ...)`. The mock renders a `<form data-testid="budget-line-form">` with controlled inputs for `form.description` and `form.plannedAmount`. `onFormChange` is wired to `onChange` handlers so tests can drive component state. `budgetCategories !== undefined` renders `[data-testid="has-categories"]` to test the work_item vs household_item branch.

**Key test pattern for submit-path**: Always set `form-planned-amount` to a valid value via `fireEvent.change` before `fireEvent.submit` — initial form state has `plannedAmount: ''` which triggers the NaN validation guard and returns early without calling any APIs.

**Old describe block replacement**: When an implementation changes (old inline form → new BudgetLineForm component), existing tests that tested old implementation internals (specific selectors, labels, headings) must be replaced with tests using the new mock's testids. Do NOT try to keep old tests that query DOM elements no longer rendered.

## Story #1360 — Server-Side Source Filter Tests (2026-04-25)

**CostBreakdownTable.test.tsx**: Replaced the 12-test `describe('Source filter — aggregate consistency (#1358)')` block with 4-test `describe('Server-driven render path (#1360)')`. The 12 old tests tested deleted client-side helpers (`computePerSourcePayback`, `computeFilteredAggregates`, `visibleLineIds`). Removal strategy: Python `content.replace()` on large block — incremental Edit tool calls left orphaned code. The `buildBreakdownWithTwoSources()` helper was replaced by `buildServerFilteredBreakdown()`.
Expand Down
1 change: 1 addition & 0 deletions .claude/agent-memory/translator/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ Action labels in German follow the pattern: `{Noun} {Verb}` with capitalised fir
- `de/budget.json` — `sources.lines.noCategory` orphan deleted 2026-04-19 (Issue #1313); `sources.lines.invoiceStatus.*`, `sources.lines.underArea`, `sources.lines.typeColumnHeader`, `sources.lines.statusColumnHeader` added 2026-04-19 (Issue #1313)
- `de/budget.json` — Issue #1356 (2026-04-25): `sourceFilter` rework — removed `label`, `allSources`, `clearAriaLabel`, `chipSelected`, `chipNotSelected`, `activeAnnouncement`; added `statusAnnouncement`; added new blocks `sourceRow.*` and `availableFunds.*`
- **Pre-existing gap** (as of 2026-04-25, outside #1356 scope): `sources.lines.typeColumnHeader` and `sources.lines.statusColumnHeader` exist in `en` but not `de` — needs a dedicated spec to fix
- `de/budget.json` — `invoiceDetail.budgetLines` block added 2026-05-10 (Issue #1401): `createFormLegend` + `autoLinkedSuccess`
- Always check key parity when picking up a new translator spec

## Backup/Restore Terminology (2026-03-22)
Expand Down
16 changes: 8 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ jobs:
# --- Restore caches ---
- name: Restore browser cache
id: browser-cache
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: ~/.cache/ms-playwright
key: playwright-v3-${{ steps.playwright-version.outputs.version }}-${{ runner.os }}
Expand All @@ -282,7 +282,7 @@ jobs:

- name: Restore apt cache
id: apt-cache
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: /var/cache/apt/archives
key: apt-v3-playwright-${{ steps.playwright-version.outputs.version }}-${{ runner.os }}
Expand All @@ -295,7 +295,7 @@ jobs:

- name: Save browser cache
if: steps.browser-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: ~/.cache/ms-playwright
key: playwright-v3-${{ steps.playwright-version.outputs.version }}-${{ runner.os }}
Expand Down Expand Up @@ -332,7 +332,7 @@ jobs:

- name: Save apt cache
if: steps.apt-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: /var/cache/apt/archives
key: apt-v3-playwright-${{ steps.playwright-version.outputs.version }}-${{ runner.os }}
Expand Down Expand Up @@ -392,7 +392,7 @@ jobs:
run: npm ci -w e2e

- name: Restore browser cache
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: ~/.cache/ms-playwright
key: playwright-v3-${{ needs.e2e-warmup.outputs.playwright-version }}-${{ runner.os }}
Expand All @@ -401,7 +401,7 @@ jobs:
run: sudo chown -R $(id -u):$(id -g) /var/cache/apt/archives

- name: Restore apt cache
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: /var/cache/apt/archives
key: apt-v3-playwright-${{ needs.e2e-warmup.outputs.playwright-version }}-${{ runner.os }}
Expand Down Expand Up @@ -473,7 +473,7 @@ jobs:
run: npm ci -w e2e

- name: Restore browser cache
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: ~/.cache/ms-playwright
key: playwright-v3-${{ needs.e2e-warmup.outputs.playwright-version }}-${{ runner.os }}
Expand All @@ -482,7 +482,7 @@ jobs:
run: sudo chown -R $(id -u):$(id -g) /var/cache/apt/archives

- name: Restore apt cache
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: /var/cache/apt/archives
key: apt-v3-playwright-${{ needs.e2e-warmup.outputs.playwright-version }}-${{ runner.os }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ jobs:
summary: true

- name: Upload SARIF to GitHub Security
uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4
if: always()
with:
sarif_file: scout-results.sarif
Expand Down
12 changes: 6 additions & 6 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@
},
"dependencies": {
"@cornerstone/shared": "*",
"i18next": "26.0.5",
"react": "19.2.5",
"react-dom": "19.2.5",
"react-i18next": "17.0.4",
"react-router-dom": "7.14.1"
"i18next": "26.0.10",
"react": "19.2.6",
"react-dom": "19.2.6",
"react-i18next": "17.0.7",
"react-router-dom": "7.15.0"
},
"devDependencies": {
"@babel/core": "7.29.0",
Expand All @@ -30,7 +30,7 @@
"html-webpack-plugin": "5.6.7",
"mini-css-extract-plugin": "2.10.2",
"style-loader": "4.0.0",
"webpack": "5.105.0",
"webpack": "5.106.2",
"webpack-cli": "7.0.2",
"webpack-dev-server": "5.2.3"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ const baseInvoice: Invoice = {
notes: null,
budgetLines: [],
remainingAmount: 5000,
deposits: [],
finalPaymentAmount: 5000,
createdBy: null,
createdAt: '2026-01-10T00:00:00.000Z',
updatedAt: '2026-01-10T00:00:00.000Z',
Expand Down
129 changes: 129 additions & 0 deletions client/src/components/OverflowMenu/OverflowMenu.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
.wrapper {
position: relative;
display: flex;
align-items: center;
justify-content: center;
}

.trigger {
background: none;
border: none;
padding: var(--spacing-2);
min-width: 44px;
min-height: 44px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: var(--color-text-secondary);
font-size: var(--font-size-lg);
border-radius: var(--radius-md);
transition: var(--transition-button);
}

.trigger:hover:not(:disabled) {
background-color: var(--color-bg-tertiary);
color: var(--color-text-primary);
}

.trigger:focus-visible {
outline: none;
box-shadow: var(--shadow-focus);
}

.trigger:disabled {
opacity: 0.5;
cursor: not-allowed;
}

@media (prefers-reduced-motion: reduce) {
.trigger {
transition: none;
}
}

.menu {
position: absolute;
background-color: var(--color-bg-primary);
border: 1px solid var(--color-border-strong);
border-radius: var(--radius-md);
box-shadow: var(--shadow-md);
z-index: var(--z-dropdown);
min-width: 160px;
overflow: hidden;
}

.menuBottom {
top: 100%;
right: 0;
margin-top: var(--spacing-1);
}

.menuTop {
bottom: 100%;
right: 0;
margin-bottom: var(--spacing-1);
}

.item {
display: flex;
width: 100%;
padding: var(--spacing-2-5) var(--spacing-3);
background: none;
border: none;
text-align: left;
cursor: pointer;
color: var(--color-text-primary);
font-size: var(--font-size-sm);
transition: var(--transition-button);
align-items: center;
gap: var(--spacing-2);
}

.item:hover:not(:disabled) {
background-color: var(--color-bg-secondary);
}

.item:focus-visible {
outline: none;
background-color: var(--color-bg-secondary);
box-shadow: inset 0 0 0 3px var(--color-focus-ring);
}

.item:disabled {
opacity: 0.5;
cursor: not-allowed;
}

@media (prefers-reduced-motion: reduce) {
.item {
transition: none;
}
}

.itemIcon {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}

.itemDanger {
color: var(--color-danger-text-on-light);
}

.itemDanger:hover:not(:disabled) {
background-color: var(--color-danger-bg);
}

.itemDanger:focus-visible {
outline: none;
background-color: var(--color-bg-secondary);
box-shadow: inset 0 0 0 3px var(--color-focus-ring-danger);
}

@media (max-width: 767px) {
.item {
padding: var(--spacing-3) var(--spacing-4);
}
}
Loading
Loading