You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
As a homeowner with a Paperless invoice linked to a Cornerstone invoice, I want the auto-itemize workflow to live on a dedicated full page launched from the linked-document card, with a side-by-side document preview and editable extracted rows, so that I can validate the LLM's interpretation against the underlying document and edit invoice metadata in the same flow without juggling a cramped modal inside the budget-lines section.
The current auto-itemize trigger lives as a button + cramped modal inside InvoiceBudgetLinesSection. This redesign relocates the trigger to the per-document LinkedDocumentCard (the natural place — "I want to itemize this document"), turns the modal into a dedicated full page, adds a side-by-side document preview so the user can verify the LLM's reading, and lets the user reconcile invoice metadata (currency, total, dates, vendor) with LLM-suggested values in the same flow. Per-row budget-line linking must reuse the existing two-step picker from InvoiceBudgetLinesSection.tsx — no parallel implementation.
This is a presentation-layer rework. The LLM service, extraction logic, and request/response shapes from #1547 are not touched (one open question to the architect: do we need a new endpoint that atomically updates invoice metadata + creates budget lines, or are two sequential calls acceptable — see Open Questions).
Acceptance Criteria
Group 1 — Trigger placement and visibility (replaces #1547 AC 1-5)
Given the invoice detail page renders a LinkedDocumentCard for a Paperless document AND entityType === 'invoice' AND config.autoItemizeEnabled === true, when the card is rendered, then an "Itemize" button appears on the card next to (or replacing the position of) the renamed "Details" button.
Given the same card is rendered for a non-invoice entity (work item, household item, diary, etc.), when the card is rendered, then the "Itemize" button is NOT present, regardless of autoItemizeEnabled.
Givenconfig.autoItemizeEnabled === false, when an invoice's LinkedDocumentCard is rendered, then the "Itemize" button is NOT present (the "Details" button remains).
Given the redesign is in effect, when I open InvoiceDetailPage, then the "Auto-itemize" button is NOT present in the budget-lines section header (the trigger has moved entirely to the document card).
Given a LinkedDocumentCard in any context (invoice, work item, household item, diary), when it renders, then the button previously labelled "View" is labelled "Details" (i18n key updated; underlying DocumentDetailPanel inline expand/collapse behavior is unchanged).
Group 2 — Navigation and page lifecycle
Given I click the "Itemize" button on a LinkedDocumentCard for invoice :invoiceId and document :documentId, when the click is handled, then the app navigates to a dedicated full page (route TBD — suggest /budget/invoices/:invoiceId/auto-itemize?documentId=N) — NOT a modal overlay.
Given the dedicated auto-itemize page mounts, when the page-load effect runs, then it immediately issues POST /api/invoices/:invoiceId/auto-itemize with { paperlessDocumentId, dryRun: true, mode: 'append' } (no "Start" button) and shows an "Analyzing…" indicator (skeleton or spinner with descriptive text) while the request is in flight.
Given the page is in the "Analyzing…" state, when the LLM provider is unreachable, then the page shows the LLM_UNREACHABLE error (same i18n key as Auto-itemize invoices from Paperless OCR documents #1547 AC 11) with a Retry button that re-issues the dry-run.
Given I navigate away and back to the page for the same invoice/document, when the page remounts, then the LLM dry-run runs again from scratch — no per-document persistence of prior LLM output, no draft state, no cached suggestions.
Given the dry-run returns { lines: [] }, when the page renders, then it shows an empty-state message "No line items detected" with a Cancel button that returns to invoice detail.
Group 3 — Page layout and document preview
Given the dry-run resolves successfully, when the page renders, then the layout includes a document preview (image/PDF render of the underlying Paperless document) alongside the editable form. The preview reuses the existing DocumentDetailPanel (or its underlying rendering component) used by the "Details" button — not a parallel implementation.
Given the page renders on a desktop viewport, when I view it, then the document preview and the editable form are presented side-by-side. On smaller viewports the layout MAY stack (mobile-friendly), but the preview must remain accessible.
Group 4 — Editable invoice metadata with LLM suggestions
Given the dry-run response includes a suggested value for an invoice-level field (currency, total amount, invoice date, due date, vendor name — whichever the LLM proposes) that differs from the value currently stored on the invoice, when the page renders, then an "LLM suggested {{value}}" badge appears next to that field with a one-click "Apply" affordance that copies the suggestion into the field.
Given the LLM's suggested value matches the current stored value (or the LLM did not suggest a value), when the page renders, then no badge is shown for that field.
Given the page is rendered (regardless of LLM suggestions), when I focus any invoice metadata field, then it is freely editable — the user always has manual override.
Given I click "Apply" on a suggestion badge, when the click is handled, then the field value updates to the suggested value, the badge disappears, and standard field validation runs.
Group 5 — Per-row editing and budget-line linking (reuses existing two-step picker)
Given the dry-run resolves with N lines, when the page renders, then each line appears as an editable row with these fields: description, quantity, unit, unitPrice, totalAmount, includesVat, vatRate. The same fields and validation rules as the current AutoItemizePreviewModal row editor apply.
Given an extracted row, when I open its budget-line picker, then the picker reuses the same components and logic as the two-step picker in InvoiceBudgetLinesSection.tsx (today: type select → work item / household item → search existing OR create inline via BudgetLineForm). This is mandatory — a parallel implementation is not acceptable.
Given the budget-line picker, when I pick an existing work-item or household-item budget line, then on save the auto-itemized row is linked to that existing budget line (no new work_item_budgets / household_item_budgets row is created — only an invoice_budget_lines junction row).
Given the budget-line picker, when I choose "Create new" and fill the inline BudgetLineForm, then on save a new budget line is created (with origin = 'auto', parent work-item / household-item assignment per the picker) AND linked to the invoice via invoice_budget_lines.
Given each row, when I view it, then an include/exclude checkbox controls whether that row participates in save (same behavior as the current modal).
Given the rows table, when I view it, then a mode selector (append / replace) controls whether existing auto-extracted lines on the invoice are kept or removed on save — semantics identical to Auto-itemize invoices from Paperless OCR documents #1547 AC 8-9 (replace removes only origin = 'auto' rows; origin = 'manual' lines are preserved).
Given the rows table, when the included rows' Σ totalAmount differs from the invoice total (current or LLM-applied) by > 1%, then a non-blocking variance indicator is shown on the totals row (same threshold as Auto-itemize invoices from Paperless OCR documents #1547 AC 14).
Group 6 — Save and cancel semantics
Given I click Save, when the request(s) complete successfully, then invoice metadata is updated (if changed), included rows are created/linked as budget lines, any inline-created work items / household items are created, append/replace semantics are honored, and the app navigates back to /budget/invoices/:invoiceId (the invoice detail page) which reflects the new state.
Given I click Save and the backend returns an error (e.g., ITEMIZED_SUM_EXCEEDS_INVOICE, validation error, transient 5xx), when the response lands, then the page stays mounted, an inline error banner appears, all user edits (rows, metadata, include/exclude state, mode selector) are preserved, and the user can retry without re-running the LLM.
Given I click Cancel, when the click is handled, then the app navigates back to /budget/invoices/:invoiceId without saving any changes (no metadata update, no budget-line creation). If the user has unsaved edits, a confirmation prompt is shown (standard "discard changes?" pattern).
Group 7 — Deletion of obsolete components
Given this story is merged, when the diff is reviewed, thenclient/src/components/budget/AutoItemizePreviewModal.tsx is deleted.
Given this story is merged, when the diff is reviewed, thenclient/src/components/budget/DocumentPickerModal.tsx is deleted (the per-document trigger on LinkedDocumentCard removes the need for a multi-document picker — the user picks which document to itemize by clicking that document's card).
Given this story is merged, when I view InvoiceBudgetLinesSection.tsx, then the "Auto-itemize" button (currently lines 917-936) and all wiring for the old picker/preview modal are removed; the section is back to "+ Add Itemization" as the sole header action.
Given this story is merged, when I view client/src/i18n/en/budget.json and client/src/i18n/de/budget.json, then orphaned keys for the deleted modals (autoItemize.modalTitle, autoItemize.docPickerTitle, etc.) are removed and any new keys for the dedicated page are added under a clear namespace (translator owns DE).
Per-document persistence of LLM output is NOT introduced. Each visit re-runs the dry-run. Saving still creates persistent budget lines + invoice metadata changes; only the LLM's proposed output is ephemeral.
Bulk multi-document itemize. One document per page visit. (If multiple linked documents need itemizing, the user clicks "Itemize" on each in turn.)
Open Questions (flag to product-architect)
Atomic save endpoint vs two calls. The current POST /api/invoices/:invoiceId/auto-itemize (commit mode, dryRun: false) creates budget lines but does NOT update invoice metadata. The new page lets the user edit metadata (currency, total, dates, vendor) alongside line items. Options:
(a) Two sequential client calls — PATCH /api/invoices/:invoiceId (metadata) then POST /api/invoices/:invoiceId/auto-itemize (commit). Risk: partial-success state if the second call fails.
(b) New atomic endpoint that accepts both metadata patch + line items in one transaction.
(c) Extend the existing POST /api/invoices/:invoiceId/auto-itemize commit payload with an optional invoicePatch field.
Route shape. Suggest /budget/invoices/:invoiceId/auto-itemize?documentId=N. Architect to confirm vs /budget/invoices/:invoiceId/documents/:documentId/auto-itemize (RESTful path-only) or another convention consistent with the existing React Router layout.
Inline-created work items / household items. When a user uses the per-row picker to create a new work item or household item inline (Group 5 AC 21), is that creation committed eagerly (separate API call on inline-form submit, same as today's BudgetLineForm behavior) or batched into the save? Today's behavior is eager creation — likely keep that, but confirm.
Document preview component reuse. Spec calls for reusing DocumentDetailPanel. Architect to confirm that component is suitable for the side-by-side preview context (vs. needing a new layout variant) — if a new variant is needed, that's an additional small component spec but not a redesign.
Vendor field on invoice metadata. Invoice metadata edit includes vendor. Vendors are a managed entity (FK to vendors table). LLM-suggested vendor name needs a resolution strategy: match by name → fuzzy match → "create new vendor" inline? Could be deferred to a follow-up if too large, in which case vendor is read-only on this page.
No new backend extraction logic. The dev-team-lead should not route any spec to add LLM-related code.
Test authorship: all unit, integration, and E2E tests written by qa-integration-tester / e2e-test-engineer per CLAUDE.md.
Translator owns DE keys for any new i18n entries (and removal of orphaned keys per AC 31).
Shared component policy: the per-row budget-line picker must reuse WorkItemPicker / HouseholdItemPicker / BudgetLineForm exactly. The "Details" rename on LinkedDocumentCard is a single i18n key change — keep all existing behavior.
Existing implementation reference (file:line)
client/src/pages/InvoiceDetailPage/InvoiceBudgetLinesSection.tsx:917-936 — current auto-itemize button to remove
client/src/components/budget/AutoItemizePreviewModal.tsx — to be deleted
client/src/components/budget/DocumentPickerModal.tsx — to be deleted
client/src/components/documents/LinkedDocumentCard.tsx:74-82 — "View" button to rename + add "Itemize" sibling
client/src/components/documents/LinkedDocumentsSection.tsx:336-344 — existing DocumentDetailPanel rendering pattern (reuse on new page)
As a homeowner with a Paperless invoice linked to a Cornerstone invoice, I want the auto-itemize workflow to live on a dedicated full page launched from the linked-document card, with a side-by-side document preview and editable extracted rows, so that I can validate the LLM's interpretation against the underlying document and edit invoice metadata in the same flow without juggling a cramped modal inside the budget-lines section.
Parent Epic: #1547
Priority: Should Have
Description
The current auto-itemize trigger lives as a button + cramped modal inside
InvoiceBudgetLinesSection. This redesign relocates the trigger to the per-documentLinkedDocumentCard(the natural place — "I want to itemize this document"), turns the modal into a dedicated full page, adds a side-by-side document preview so the user can verify the LLM's reading, and lets the user reconcile invoice metadata (currency, total, dates, vendor) with LLM-suggested values in the same flow. Per-row budget-line linking must reuse the existing two-step picker fromInvoiceBudgetLinesSection.tsx— no parallel implementation.This is a presentation-layer rework. The LLM service, extraction logic, and request/response shapes from #1547 are not touched (one open question to the architect: do we need a new endpoint that atomically updates invoice metadata + creates budget lines, or are two sequential calls acceptable — see Open Questions).
Acceptance Criteria
Group 1 — Trigger placement and visibility (replaces #1547 AC 1-5)
LinkedDocumentCardfor a Paperless document ANDentityType === 'invoice'ANDconfig.autoItemizeEnabled === true, when the card is rendered, then an "Itemize" button appears on the card next to (or replacing the position of) the renamed "Details" button.autoItemizeEnabled.config.autoItemizeEnabled === false, when an invoice'sLinkedDocumentCardis rendered, then the "Itemize" button is NOT present (the "Details" button remains).InvoiceDetailPage, then the "Auto-itemize" button is NOT present in the budget-lines section header (the trigger has moved entirely to the document card).LinkedDocumentCardin any context (invoice, work item, household item, diary), when it renders, then the button previously labelled "View" is labelled "Details" (i18n key updated; underlyingDocumentDetailPanelinline expand/collapse behavior is unchanged).Group 2 — Navigation and page lifecycle
LinkedDocumentCardfor invoice:invoiceIdand document:documentId, when the click is handled, then the app navigates to a dedicated full page (route TBD — suggest/budget/invoices/:invoiceId/auto-itemize?documentId=N) — NOT a modal overlay.POST /api/invoices/:invoiceId/auto-itemizewith{ paperlessDocumentId, dryRun: true, mode: 'append' }(no "Start" button) and shows an "Analyzing…" indicator (skeleton or spinner with descriptive text) while the request is in flight.LLM_UNREACHABLEerror (same i18n key as Auto-itemize invoices from Paperless OCR documents #1547 AC 11) with a Retry button that re-issues the dry-run.LLM_INVALID_RESPONSEerror (same i18n key as Auto-itemize invoices from Paperless OCR documents #1547 AC 12) with a Retry button.{ lines: [] }, when the page renders, then it shows an empty-state message "No line items detected" with a Cancel button that returns to invoice detail.Group 3 — Page layout and document preview
DocumentDetailPanel(or its underlying rendering component) used by the "Details" button — not a parallel implementation.Group 4 — Editable invoice metadata with LLM suggestions
Group 5 — Per-row editing and budget-line linking (reuses existing two-step picker)
AutoItemizePreviewModalrow editor apply.InvoiceBudgetLinesSection.tsx(today: type select → work item / household item → search existing OR create inline viaBudgetLineForm). This is mandatory — a parallel implementation is not acceptable.work_item_budgets/household_item_budgetsrow is created — only aninvoice_budget_linesjunction row).BudgetLineForm, then on save a new budget line is created (withorigin = 'auto', parent work-item / household-item assignment per the picker) AND linked to the invoice viainvoice_budget_lines.origin = 'auto'rows;origin = 'manual'lines are preserved).Group 6 — Save and cancel semantics
/budget/invoices/:invoiceId(the invoice detail page) which reflects the new state.ITEMIZED_SUM_EXCEEDS_INVOICE, validation error, transient 5xx), when the response lands, then the page stays mounted, an inline error banner appears, all user edits (rows, metadata, include/exclude state, mode selector) are preserved, and the user can retry without re-running the LLM./budget/invoices/:invoiceIdwithout saving any changes (no metadata update, no budget-line creation). If the user has unsaved edits, a confirmation prompt is shown (standard "discard changes?" pattern).Group 7 — Deletion of obsolete components
client/src/components/budget/AutoItemizePreviewModal.tsxis deleted.client/src/components/budget/DocumentPickerModal.tsxis deleted (the per-document trigger onLinkedDocumentCardremoves the need for a multi-document picker — the user picks which document to itemize by clicking that document's card).InvoiceBudgetLinesSection.tsx, then the "Auto-itemize" button (currently lines 917-936) and all wiring for the old picker/preview modal are removed; the section is back to "+ Add Itemization" as the sole header action.client/src/i18n/en/budget.jsonandclient/src/i18n/de/budget.json, then orphaned keys for the deleted modals (autoItemize.modalTitle,autoItemize.docPickerTitle, etc.) are removed and any new keys for the dedicated page are added under a clear namespace (translator owns DE).Out-of-Scope
server/src/services/invoiceAutoItemizeService.tsand the OpenAI-compatible gateway from BudgetExtractionService with OpenAI-compatible LLM gateway #1546 stay untouched.confidence = 'invoice'(same as Auto-itemize invoices from Paperless OCR documents #1547).Open Questions (flag to product-architect)
POST /api/invoices/:invoiceId/auto-itemize(commit mode,dryRun: false) creates budget lines but does NOT update invoice metadata. The new page lets the user edit metadata (currency, total, dates, vendor) alongside line items. Options:PATCH /api/invoices/:invoiceId(metadata) thenPOST /api/invoices/:invoiceId/auto-itemize(commit). Risk: partial-success state if the second call fails.POST /api/invoices/:invoiceId/auto-itemizecommit payload with an optionalinvoicePatchfield./budget/invoices/:invoiceId/auto-itemize?documentId=N. Architect to confirm vs/budget/invoices/:invoiceId/documents/:documentId/auto-itemize(RESTful path-only) or another convention consistent with the existing React Router layout.BudgetLineFormbehavior) or batched into the save? Today's behavior is eager creation — likely keep that, but confirm.DocumentDetailPanel. Architect to confirm that component is suitable for the side-by-side preview context (vs. needing a new layout variant) — if a new variant is needed, that's an additional small component spec but not a redesign.vendorstable). LLM-suggested vendor name needs a resolution strategy: match by name → fuzzy match → "create new vendor" inline? Could be deferred to a follow-up if too large, in which case vendor is read-only on this page.Notes
WorkItemPicker/HouseholdItemPicker/BudgetLineFormexactly. The "Details" rename onLinkedDocumentCardis a single i18n key change — keep all existing behavior.Existing implementation reference (file:line)
client/src/pages/InvoiceDetailPage/InvoiceBudgetLinesSection.tsx:917-936— current auto-itemize button to removeclient/src/components/budget/AutoItemizePreviewModal.tsx— to be deletedclient/src/components/budget/DocumentPickerModal.tsx— to be deletedclient/src/components/documents/LinkedDocumentCard.tsx:74-82— "View" button to rename + add "Itemize" siblingclient/src/components/documents/LinkedDocumentsSection.tsx:336-344— existingDocumentDetailPanelrendering pattern (reuse on new page)client/src/pages/InvoiceDetailPage/InvoiceBudgetLinesSection.tsx:1154+— two-step picker (WorkItemPicker / HouseholdItemPicker + inlineBudgetLineFormcreate) — reuse thisserver/src/routes/invoiceAutoItemize.ts— backend route (reuse, possibly extend per Open Question 1)server/src/services/invoiceAutoItemizeService.ts— extraction service (DO NOT modify)shared/src/types/invoiceAutoItemize.ts— request/response types (may need extension per Open Question 1)shared/src/types/config.ts—AppConfigResponse.autoItemizeEnabled(already gates visibility)