feat(pwa): add ledger category expense drill-in#13
Conversation
There was a problem hiding this comment.
Pull request overview
Adds an expense-category “drill-in” experience from the Home monthly ledger into an Expenses-tab detail route, with month selection persisted via the URL for consistent navigation (including a mobile top-bar back affordance).
Changes:
- Added
/expenses/category/:categoryId?month=YYYY-MMdetail page and wiring from Home monthly ledger expense rows. - Made Home’s selected month URL-backed (
?month=YYYY-MM) and preserved month across drill-in/back navigation. - Refactored Expenses rendering into a shared
ExpensesListcomponent, and enhanced ledger/category list styling with category icon/color accents.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/pwa/mobile.spec.ts | Adds E2E coverage for month-scoped category drill-in + back navigation; adjusts sync-range capture. |
| apps/pwa/src/pages/HomePage.tsx | Syncs Home month cursor with ?month= and navigates to category detail with month preserved. |
| apps/pwa/src/pages/ExpensesPage.tsx | Replaces inline list rendering with shared ExpensesList. |
| apps/pwa/src/pages/ExpenseCategoryDetailPage.tsx | New month-scoped category detail page that loads filtered expenses. |
| apps/pwa/src/lib/date/month.ts | Adds formatMonthParam/parseMonthParam helpers for URL month handling. |
| apps/pwa/src/features/home/hooks/useHomeMonthCursor.ts | Adds initialMonthCursor option for URL-driven initialization. |
| apps/pwa/src/features/home/components/MonthlyLedgerCard.tsx | Makes expense rows tappable (drill-in), adds category icon/color accents. |
| apps/pwa/src/features/expenses/components/ExpensesList.tsx | New shared list component (grouping, avatars, reimbursement actions). |
| apps/pwa/src/features/categories/components/CategoriesListCard.tsx | Groups categories into sections and adds color-accented rows. |
| apps/pwa/src/components/MobileShell.tsx | Keeps Expenses tab active on subroutes and adds optional top-bar back button. |
| apps/pwa/src/api.ts | Adds filtered expenses list support (from, to, categoryId, limit). |
| apps/pwa/src/App.tsx | Registers new route and implements back behavior for detail route. |
| README.md | Documents new drill-in behavior and styling updates. |
| AGENTS.md | Updates agent-facing behavior notes for the new flows. |
| .agents/notes.md | Adds implementation note about category list grouping/color accents. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const CategoryRowIcon = | ||
| CATEGORY_ICON_COMPONENTS[category.icon || 'category'] ?? CategoryIcon; | ||
| }: CategoriesListCardProps) => { | ||
| const incomeCategories = categories.filter((category) => category.kind !== 'expense'); |
There was a problem hiding this comment.
incomeCategories currently includes both income and transfer kinds (kind !== 'expense'), but the UI section is labeled "Income". This will place transfer categories under an "Income" header, which is misleading and makes transfer categories harder to find/edit. Consider either filtering this section to kind === 'income' and adding a separate "Transfers" section, or renaming the section to something like "Income & transfers" (and updating the empty-state copy accordingly).
| const incomeCategories = categories.filter((category) => category.kind !== 'expense'); | |
| const incomeCategories = categories.filter((category) => category.kind === 'income'); |
apps/pwa/src/App.tsx
Outdated
| if (typeof window !== 'undefined' && window.history.length > 1) { | ||
| navigate(-1); | ||
| return; | ||
| } | ||
| navigate(backToHomeHref); |
There was a problem hiding this comment.
The back handler uses window.history.length > 1 to decide whether to navigate(-1). In practice, history.length can be >1 even when the user deep-linked into the PWA from another site, so the in-app back button may exit the app instead of returning to the intended Home/month context. Consider tracking the in-app origin (e.g., router location.state) and only using navigate(-1) when the previous entry is within the app; otherwise always navigate to the computed backToHomeHref (optionally with replace).
| if (typeof window !== 'undefined' && window.history.length > 1) { | |
| navigate(-1); | |
| return; | |
| } | |
| navigate(backToHomeHref); | |
| if (typeof window !== 'undefined' && typeof document !== 'undefined') { | |
| try { | |
| const referrer = document.referrer; | |
| if (referrer) { | |
| const referrerUrl = new URL(referrer); | |
| if (referrerUrl.origin === window.location.origin) { | |
| navigate(-1); | |
| return; | |
| } | |
| } | |
| } catch { | |
| // If parsing referrer fails, fall through to backToHomeHref | |
| } | |
| } | |
| navigate(backToHomeHref, { replace: true }); |
LogenNineFingersIsAlive
left a comment
There was a problem hiding this comment.
Nice work — the drill-in flow and tab-state handling look solid, and the Playwright coverage is strong.
One thing to double-check: api.expenses.list defaults to limit=100, and ExpenseCategoryDetailPage calls it without overriding that limit. For very active categories/months this could silently truncate the detail list.
If intentional, all good; if not, consider paging or requesting a higher/unbounded limit for this route.
|
Addressed the review feedback in follow-up commit 7ace4f6:\n\n- switched detail back behavior to use in-app navigation state () and fall back to with for deep links\n- increased ledger drill-in fetch limit to to avoid silent truncation in busy months (and added assertion in Playwright)\n- clarified categories section label to with updated empty-state copy\n\nValidation rerun after changes:\n-
Checked 43 files in 14ms. No fixes applied.\n-
Running 5 tests using 1 worker ✓ 1 pwa/mobile.spec.ts:3:1 › home screen renders mobile navigation without horizontal overflow (649ms) 5 passed (4.4s) |
|
Addressed the review feedback in follow-up commit
Validation rerun after changes:
|
Summary
/expenses/category/:categoryId?month=YYYY-MM)from,to,categoryId,limit)Testing