feat: Budgets page — category budgets with spend tracking and MoM comparison#17
Conversation
Agent-Logs-Url: https://github.com/plures/pares-modulus/sessions/34bdfdca-0ce7-4879-9750-39ba808b6d18 Co-authored-by: kayodebristol <3579196+kayodebristol@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds a new Budgets page to the financial-advisor plugin, enabling per-category monthly budgets with spending progress and month-over-month comparisons, backed by the new fa-budgets collection and shared category constants.
Changes:
- Introduces
Budgets.sveltewith create/edit/delete flows, spend progress UI, and MoM comparison derived from transactions + inferences. - Adds shared helpers/types in
lib/budgets.tsand extracts shared category lists intolib/categories.ts. - Updates
Transactions.svelteto consume the shared transaction category list.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| plugins/financial-advisor/src/pages/Transactions.svelte | Replaces inline category list with an import from shared category constants. |
| plugins/financial-advisor/src/pages/Budgets.svelte | New Budgets page UI + logic (CRUD, spending aggregation, status display, MoM). |
| plugins/financial-advisor/src/lib/categories.ts | New single source of truth for transaction vs budget category lists. |
| plugins/financial-advisor/src/lib/budgets.ts | New Budget model + helpers (status, percent, month prefix/labels, collection constant). |
| const prevMo = previousMonthPrefix(); | ||
|
|
||
| const allTx: Transaction[] = (await txCollection?.query()) ?? []; | ||
| const allInf: TransactionInference[] = (await infCollection?.query()) ?? []; |
There was a problem hiding this comment.
In computeSpending, infCollection.query() loads every inference record and then filters in JS. Since DataCollection.query supports a filter object and TransactionInference has a field, consider querying only category inferences (e.g., filter by { field: 'category' }) to reduce data transfer and processing time as the dataset grows.
| const allInf: TransactionInference[] = (await infCollection?.query()) ?? []; | |
| const allInf: TransactionInference[] = | |
| (await infCollection?.query({ field: 'category' })) ?? []; |
| const prev = previousSpend[category] ?? 0; | ||
| if (prev === 0 && curr === 0) return ''; | ||
| const diff = curr - prev; | ||
| const sign = diff >= 0 ? '+' : ''; |
There was a problem hiding this comment.
momLabel() adds a leading "+" when diff === 0 (because it uses diff >= 0), which produces labels like "+$0.00 vs " while momClass() treats diff === 0 as neutral. Consider only prefixing "+" when diff > 0 (or returning a dedicated "No change" label) to avoid misleading output.
| const sign = diff >= 0 ? '+' : ''; | |
| const sign = diff > 0 ? '+' : ''; |
| let ctx: ReturnType<typeof getPluginContext>; | ||
| let budgetCollection: any; | ||
| let txCollection: any; | ||
| let infCollection: any; |
There was a problem hiding this comment.
budgetCollection, txCollection, and infCollection are typed as any, which drops type-safety for query/put/delete and makes it easier to misuse the PluresDB API. Other pages (e.g. Transactions.svelte) type these collections via ReturnType<NonNullable<typeof ctx>['data']['collection']> or DataCollection<T>; aligning here would improve maintainability and catch mistakes at compile time.
| let ctx: ReturnType<typeof getPluginContext>; | |
| let budgetCollection: any; | |
| let txCollection: any; | |
| let infCollection: any; | |
| type PluginContext = NonNullable<ReturnType<typeof getPluginContext>>; | |
| type CollectionOf<T> = ReturnType<PluginContext['data']['collection']<T>>; | |
| let ctx: ReturnType<typeof getPluginContext>; | |
| let budgetCollection: CollectionOf<Budget>; | |
| let txCollection: CollectionOf<Transaction>; | |
| let infCollection: CollectionOf<TransactionInference>; |
| export interface Budget { | ||
| id: string; | ||
| /** Must match a category recognised by the transaction inference engine. */ | ||
| category: string; | ||
| /** Maximum allowed spend for this category per calendar month (positive). */ | ||
| monthlyLimit: number; |
There was a problem hiding this comment.
Budget.category is typed as a plain string, even though the UI restricts selection to BUDGET_CATEGORIES and the repo now provides BudgetCategory/TransactionCategory unions in lib/categories.ts. Narrowing this field to the appropriate category union would prevent invalid records from being persisted and simplify downstream code (less defensive checking/casting).
kayodebristol
left a comment
There was a problem hiding this comment.
Auto-approved: CI green + code review complete.
Implements the Budgets page for the
financial-advisorplugin: category-based monthly spending limits with live progress tracking, alert thresholds, and month-over-month comparison againstfa-budgetsPluresDB collection.New files
lib/categories.ts— Extracts the category list fromTransactions.svelteinto a single source of truth. ExportsTRANSACTION_CATEGORIES(full list, incl. Income/Transfer/Refund) andBUDGET_CATEGORIES(spend-only subset, non-budgetable types excluded).lib/budgets.ts—Budgettype +FA_BUDGETS_COLLECTIONconstant + helpers:budgetStatus(),spendPercent(),currentMonthPrefix(),previousMonthPrefix(),monthLabel().pages/Budgets.svelte— Full page using Svelte 5 runes:<dialog>(category, monthly limit, alert threshold %)+$42.00 vs Mar 2026) colored red/green by directionfa-transactionsdebits withfa-transaction-inferencescategory fieldModified files
pages/Transactions.svelte— Now importsTRANSACTION_CATEGORIESfromlib/categories.tsinstead of defining the array inline.