## Outcome PR1 should center on the two active household routes plus the still-direct budget-status route: - `/household/review` → `frontend/src/pages/HouseholdReviewPage.vue` - `/household/transactions` → `frontend/src/pages/HouseholdTransactionsPage.vue` - `/planning/budget/status` → `frontend/src/pages/BudgetStatusPage.vue` The current tree is clean at exact `origin/main` commit `785e18b`. ## Key findings ### 1. Review row actions are currently no-ops In `HouseholdReviewPage.vue:82-86`, the menu actions for transfer, split, duplicate, and ignore all call `showDetails(...)`. The drawer explicitly says nothing changed. Only ordinary category confirmation has a real preview/confirm path. Required behavior: - Keep `Details` read-only. - Replace the other menu entries with an item-scoped action drawer: - **Ignore**: reason → preview → confirm. - **Internal transfer**: required target/account or counterpart selection → neutral-impact preview → confirm. - **Split**: editable split rows; require exact amount/currency reconciliation → preview → confirm. - **Duplicate**: 1. “Als Dublette ignorieren” 2. “Trotz Treffer separat buchen” with mandatory reason and compatible category/account. - **Credit-card settlement**: dedicated zero-budget-effect flow; select/confirm card and bank sides or an existing proposed counterpart. Never expose an ordinary expense-category confirmation for this case. `item_token` is intentionally opaque; the frontend must not derive or expose `transaction_candidate_id`. ### 2. Existing household APIs cannot execute item-specific special actions `frontend/src/api/household.ts` currently provides only: - ordinary batch category preview/confirm; - legacy classification-group preview/confirm. The group API operates on every row in a classification and is not suitable for row menus. The older candidate API in `frontend/src/api/budget.ts` requires private candidate IDs. PR1 therefore depends on an item-token-scoped backend contract. Recommended frontend-facing shape: ```text POST /api/budget/household/review/items/{item_token}/preview POST /api/budget/household/review/items/{item_token}/confirm ``` Payload should include `action`, action-specific fields, and on confirm both fingerprints plus `confirm: true`. If the backend PR chooses different paths, only the wrappers should change; the page should consume typed household API functions. Do not wire the UI to: - `/transaction-candidates/{candidate_id}/...` - the existing classification-group `/household/review/preview|confirm` - direct unpreviewed writes. ### 3. Special-case guards already exist but need preservation Backend-generated `can_confirm` and `menu_actions` are authoritative. The page already blocks special cases from ordinary batch confirmation. Preserve and strengthen that boundary: - Render only server-advertised actions. - Never send a special item through `previewHouseholdReviewBatch`. - Disable action confirmation until all action-specific inputs are valid. - Clear previews whenever action, reason, account, category, counterpart, or split lines change. - On `409`, discard the preview and reload the row/list. - Credit-card settlements and transfers must preview as zero income/expense effect. - Duplicate override requires an explicit reason. - Split totals must match the original signed amount exactly. - Keep account hints masked and technical identifiers out of visible text. ### 4. Household transaction cards have no drawer or stable write identity `HouseholdTransactionsPage.vue:23-26` renders passive cards. `HouseholdTransaction` in `frontend/src/api/household.ts:23-35` lacks both a transaction token/ID and `category_id`. To implement an auditable category change, the household transaction response must add an opaque stable token and category ID, plus a fingerprint-bound update contract. Recommended shape: ```text POST /api/budget/household/transactions/{transaction_token}/category/preview POST /api/budget/household/transactions/{transaction_token}/category/confirm ``` Frontend behavior: - Make each card an accessible 44px-minimum button or add a full-width “Details” button. - Open a focus-trapped transaction drawer. - Show date, signed amount, merchant, masked account/source, type, review status, and current category. - Permit category changes only for eligible income/expense transactions. - Require a note/reason and visible preview before confirmation. - Disable category editing for transfers, settlements, reversals, unavailable/stale records, or incompatible category types. - Reload while retaining the active filters after confirmation. Do not reuse `updateBudgetTransaction()` directly: it is ID-based and lacks the required preview/confirm boundary. ### 5. Responsive budget status work is isolated to `BudgetStatusPage.vue` At 820px the current Tailwind `lg` classes already select cards, but there is no explicit 820px acceptance boundary, and the category detail remains a custom fixed aside without full modal behavior. Required changes: - Add semantic classes/data-test hooks for status title, compact list/table, detail surface, close control, chart, and transaction rows. - At `max-width: 820px`: - show only the compact card/list surface; - hide the wide table; - make category detail `position: fixed; inset: 0; width: 100%; max-width: none`; - account for safe-area padding; - keep only the detail body scrolling; - trap focus, close on Escape, restore trigger focus, and make background inert; - ensure category title wraps at word boundaries without clipping or arbitrary character breaks; - make cards, close button, month bars, and transaction links at least 44×44px; - prevent chart labels, long category names, values, and transaction descriptions from increasing page width. - Prefer extracting/reusing modal mechanics rather than extending the current raw aside. `DetailDrawer.vue` is reusable conceptually, but its current tablet bottom-sheet/desktop-side-drawer geometry does not satisfy full-screen-at-820 without a variant. ## Precise file plan ### Modify 1. **`frontend/src/api/household.ts`** - Add typed review-item action request/preview/confirm contracts and clients. - Extend `HouseholdReviewItem` with only server-approved action metadata needed by forms. - Extend `HouseholdTransaction` with opaque transaction token, `category_id`, eligibility/guard metadata. - Add transaction-category preview/confirm clients. 2. **`frontend/src/pages/HouseholdReviewPage.vue`** - Replace `showDetails` calls for material actions with an action-state controller. - Add forms for ignore, transfer, split, duplicate choice/override, and credit-card settlement. - Keep one shared fingerprint-bound preview/confirm surface. - Preserve focus trap, inert background, masked fields, stale-response handling, and batch isolation. 3. **`frontend/src/pages/HouseholdTransactionsPage.vue`** - Make each transaction item operable. - Add transaction detail/category-change drawer. - Add compatible category loading/selection, reason, preview, confirm, reload, and special-case guards. - Use stable transaction token as the Vue key instead of date/merchant/index. 4. **`frontend/src/pages/BudgetStatusPage.vue`** - Replace the raw category aside with an accessible responsive detail surface. - Add compact status-card markup and controlled title/transaction wrapping. - Add explicit 820px styling and 44px target sizing. 5. **`frontend/src/style.css`** - Add shared no-overflow rules and explicit `@media (max-width: 820px)` budget-status behavior. - Avoid global `overflow-x:hidden` as the only proof; constrain the actual children. - Add safe-area and touch-target utilities if shared across the three pages. 6. **Potential shared component: `frontend/src/components/household/HouseholdActionDrawer.vue`** - Recommended if both review actions and transaction details need the same backdrop, focus trap, Escape handling, inert background, scroll containment, and 820px full-screen behavior. - Do not overload the position-oriented `DetailDrawer.vue` unless a tested `fullscreenAt820` variant remains backward compatible. 7. **`frontend/src/pages/HouseholdUx.test.ts`** - Update shared API mocks and existing special-case assertions if tests remain consolidated. ### Add focused tests 1. **`frontend/src/api/household.test.ts`** - Exact item-token endpoint and body assertions. - Confirm includes exact action inputs and both preview fingerprints. - Exact transaction-category endpoint/body assertions. - Assert no candidate ID appears in URL or payload. 2. **`frontend/src/pages/HouseholdReviewActions.test.ts`** - Each advertised action calls the correct item-specific preview. - No action writes before explicit confirm. - Duplicate presents both choices; override requires reason/category. - Credit-card settlement does not offer ordinary category confirmation and displays zero budget effect. - Split cannot preview until totals reconcile. - Changing any form input invalidates the prior preview. - `can_confirm=false` and absent `menu_actions` remain guarded. - `409` closes/discards preview and reloads. - Escape, focus trap, inert background, and return focus. - No private ID/source filename/raw diagnostic leakage. 3. **`frontend/src/pages/HouseholdTransactionsActions.test.ts`** - Card opens the correct transaction-token drawer. - Current category is shown. - Compatible category + reason produces preview, then confirm. - Transfer/settlement/reversal rows cannot be recategorized. - Stale preview is invalidated when category or reason changes. - Success reloads with existing filters. - Stable transaction token is used as the row key/identity. - Drawer accessibility and privacy assertions. 4. **`frontend/src/pages/BudgetStatusResponsive.test.ts`** - Structural assertions for compact list/table/detail hooks. - Detail has dialog semantics, accessible name, Escape close, focus restoration, and inert background. - Every custom interactive control uses the 44px utility/class. - Long synthetic category/transaction labels remain text-wrapped, not truncated into inaccessible content. ### Browser-level responsive acceptance Vitest/jsdom cannot prove actual overflow or pixel target size. Add a small Playwright gate, preferably: - `frontend/playwright.config.ts` - `frontend/tests/budget-status-responsive.spec.ts` - `frontend/package.json` / `frontend/package-lock.json` Using only synthetic intercepted API data, test at exactly **820×1180** and a narrower mobile viewport: - `document.documentElement.scrollWidth <= clientWidth` - wide status table hidden, compact list visible - category detail fills the viewport - detail body scrolls without scrolling the background - long category title wraps and remains inside the viewport - every visible action target has `width >= 44` and `height >= 44` - keyboard Escape and focus restoration work - no network response contains private production data. ## Verification commands ```bash npm --prefix frontend test -- \ src/api/household.test.ts \ src/pages/HouseholdReviewActions.test.ts \ src/pages/HouseholdTransactionsActions.test.ts \ src/pages/BudgetStatusResponsive.test.ts npm --prefix frontend run typecheck npm --prefix frontend run build npm --prefix frontend exec playwright test tests/budget-status-responsive.spec.ts make PYTHON=.venv/bin/python verify git diff --check ``` ## Issues / dependencies - The current backend does **not** expose safe item-token review-action or transaction-category preview/confirm endpoints. That contract must land before PR1 can honestly make these actions executable. - The legacy group action API is too broad, and the candidate API leaks private implementation identity; neither is an acceptable shortcut. - No Playwright/browser test infrastructure currently exists, so true 820px overflow and target-size acceptance requires adding a minimal synthetic-only browser gate. - Read-only analysis only: no files were created or modified.