# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ### Added #### Bank sync through your own SimpleFIN account (2026-10-01) - **Integrations → Bank sync via SimpleFIN**: paste the setup token from your own SimpleFIN Bridge account; Lumio exchanges it once (`POST /integrations/simplefin/connect`), keeps the access credential encrypted, lists the accounts and pulls them (`POST /integrations/simplefin/sync`) on demand or every six hours. Per-account *Pull* switch and target wallet (`POST /integrations/simplefin/settings`), *Refresh accounts*, disconnect deletes the credential. - Each pull becomes an **OFX statement through the regular import**: dedupe, rules, review inbox and audit are the same as for an uploaded file. Provider transaction ids are document numbers, so nothing is imported twice; the first pull covers 90 days, later ones overlap by a week; pending rows wait until they post. A rejected credential flips the integration to *Needs a new token*. - README positioning: Lumio holds no bank integration of its own; you may connect your own provider account. Enable Banking is not wired (needs an application registered with them). #### Multi-currency: no silent 1.0, manual rates (2026-10-01) - A missing exchange rate is now said out loud: `GET /exchange-rates` answers `missing: true`, the dashboard snapshot lists `missingRates` and shows a banner, Settings → Data → **Exchange rates** lists every currency in the workspace's rows with the rate used (or "no rate") and a field to **set a rate by hand** (`POST /exchange-rates/manual`; it wins over the provider for that day). - The transaction drawer shows the amount **in the workspace currency** with the rate and its date, or that no rate exists and the row counts at face value in totals. #### Import formats: OFX/QFX, QIF, camt.053, MT940, bank CSV presets, mailbox (2026-10-01) - **Four more statement formats**: OFX/QFX (SGML and XML), QIF (date order inferred from the whole file), ISO 20022 camt.053 and SWIFT MT940 (structured `:86:` details). Recognised by extension and content, whatever MIME type the browser sends; a file that only has the extension is refused. - **CSV presets for 22 bank layouts** (Revolut, Wise, N26, Monzo, Starling, Chase, Bank of America, Capital One, American Express, Wells Fargo, PayPal, ING, Rabobank, Sparkasse/DKB, Commerzbank, Nordea, Santander, Barclays, HSBC, Lloyds, Tinkoff): matched by the header row, columns mapped exactly instead of guessed, signed amounts and card-style charges handled per bank. - **Forward a statement to the mailbox**: an export attached to an email in the IMAP inbox goes through statement import instead of the receipt pile (PDFs stay receipts). #### Mobile layer: offline entries and web push (2026-10-01) - **Works without a network**: a service worker caches the app shell and serves an offline page; a manual expense or a receipt photo added while offline is kept on the device (IndexedDB) and sent as soon as the connection is back, with an idempotency key so a retry cannot double-book. A strip at the top shows "Offline" or "N waiting to send" with a *Send now* button. - **Web push**: a new *Push* column in notification settings and *Push on this device* to subscribe the browser or installed app; budget alerts, subscription price changes, "N waiting for review" and the rest reach the phone with the app closed. Digests go as one push. Needs VAPID keys on the server (`npx web-push generate-vapid-keys` → `WEB_PUSH_VAPID_*`); without them the channel says it is off. Dead subscriptions are dropped on the next send. - **Home-screen shortcuts**: *Add expense* and *Scan receipt* on a long-press of the app icon. #### Small-business pack (2026-10-01) - **Bank reconciliation** (`/statements/reconcile`, linked from Payables): open bills and receivables next to the bank rows that look like their payment (same amount and currency, the vendor in the row's text, a date near the due date), one row per bill, confidence shown. *Confirm* links the row and settles the bill through the existing mark-as-paid path. - **AR/AP ageing**: open amounts per direction in current / 1–30 / 31–60 / 61–90 / 90+ days past due, with the largest counterparties. - **Duplicate bills**: open bills with the same counterparty, amount and currency due within a week. - **Dunning reminders**: *Send reminder* on a sent or overdue invoice emails the client (EN/RU) and counts it on the invoice; needs SMTP and a client email, and says so otherwise. - **Owners report** for business subscriptions: every active subscription with owner, monthly cost in the workspace currency, next charge and last review, totals per owner, CSV download (`GET /subscriptions/business-report?format=csv`). #### MCP and AI as a trusted agent (2026-10-01) - **Scoped API keys**: a key names what it may do (the same strings as the permissions the routes check), with *Read only* / *Read and write* / hand-picked presets in the MCP panel. A key is never wider than its owner's role; managing keys, people, workspace settings or integrations is never granted to a key. `GET /api-keys/scopes`; new keys require `scopes`. Keys made before scopes keep their owner's full reach (`scopes: null`). - **Every agent write is on the record and undoable**: requests authenticated with an API key are audited as the actor *Integration* with the key's name and prefix; the in-app assistant's writes carry `X-Lumio-Actor: ai-chat` and are audited as the new actor *AI assistant*. Both record whom they acted for and are undoable wherever rollback knows the entity. The activity log filters by *AI assistant*. - The assistant is told that every number in a reply comes from a tool result, never from an estimate. - Docs: *MCP and API keys* (Claude Code / Claude Desktop setup, scopes) and *Security posture* (threat model, what is encrypted, what leaves the server and when, how to switch AI off entirely). #### Reports: cash-flow map (2026-10-01) - **Cash flow** tab on the reports page: a Sankey of income sources → income → categories → subcategories, a treemap of spending by size (click a category for its subcategories), and a by-category table with the period before and the change. - Period presets or custom dates, a **"compare with the period before"** switch, an **"include transfers and investments"** switch (off by default: they are not spending), category chips with All / None, and a CSV export of the table. - `GET /reports/cash-flow-map?dateFrom&dateTo&compare&includeTransfers&categories&format=csv`. #### Investments v1 (2026-10-01) - **Investment and retirement accounts** under the balance sheet's Investments section, with holdings entered by hand (ticker, class, quantity, price). The account's value is written as today's balance snapshot on every change, so the balance sheet and net worth read the same number. - **Prices by ticker** from Stooq (free, no key; `AAPL` → `AAPL.US`, `VWCE.DE`), crypto from CoinGecko; cached fifteen minutes, egress-guarded. A price that cannot be fetched is simply kept. - **Contributions count as transfers**: "Mark as a contribution" on an expense in the transaction drawer turns it into a one-leg transfer of kind `investment`. It leaves every spending aggregate; the account shows contributed, value and gain. - **Net worth**: ranges 1M / 3M / 6M / 1Y / 3Y / 5Y / All, the all-time high with its date, and an allocation by asset class (stocks, ETFs, funds, bonds, crypto, cash, real estate, other). - Endpoints under `/investments` (accounts, holdings, `refresh-prices`, `contributions`). #### Cash-flow forecast (2026-10-01) - **`/forecast`**: the balance 30, 90 or 365 days ahead, day by day, from unpaid bills, active subscriptions, sent invoices, dated goals (what each needs per month), paydays detected from history and the everyday spending average of the last three months. The low point and its day, the first day the balance goes negative, the closing balance. - **Safe to spend** until the next payday (committed items only, never below zero) for a home workspace; **runway** in months at the current burn for a business one. - **Scenarios**: untick any item ("what if I cancel Netflix"), scale income (50–150%) and spending (50–150%). `GET /forecast?days=&exclude=&incomeFactor=&expenseFactor=`. - The dashboard's cash runway card now opens the forecast. #### Workspace profile: Home or Business (2026-10-01) - A workspace now says what it is for. **Home** hides invoices, the ledger, the tax declaration and custom tables from the menu (and from the welcome tutorial); nothing is deleted and every page still opens by link. **Business** is the default and is how every workspace behaved before. - Chosen when creating a workspace (first step) and changed any time in Settings → Data → Workspace profile (`PATCH /workspaces/:id { profile }`, stored in `settings.profile`). #### Budget mechanics (2026-10-01) - **Rollover**: a budget now says what happens to unspent money. *Resets* is the old behaviour; *Carries over* moves leftover and overspend whole into the next period (the envelope for irregular costs); *Refills to the limit* tops the budget back up but never above it, and still deducts an overspend. The card shows the amount available this period and what was carried in. - **Parent-category budgets**: a budget on "Food" counts "Groceries" and "Restaurants" too, and a spend in a subcategory is checked against every budget up the tree. - **"What would this do"** under the category field of a manual expense: which budgets it pushes over (and by how much), and whether it takes the default account below zero. Advice, never a gate (`GET /budgets/impact`). #### Subscriptions 2.0 (2026-10-01) - **Price change with the yearly effect**: when the last two charges of a known subscription settle on a new price (more than 5% off the old one), the row is flagged, the delta per charge and per year is shown on the card, and the workspace gets a notification saying both numbers. A change is announced once; "Keep" clears the flag. - **Possible duplicates**: two active or detected rows of one service (same domain, or the same name without plan words such as Premium/Basic) are grouped under `GET /subscriptions/duplicates`, counted on the page and marked on the row. - **Cost per use**: a "Used it" tap on the card counts a use; the card then shows the price per use since the first tap, so "is Netflix worth it" has a number. - **Set aside for annual and quarterly bills**: the card offers the monthly amount that covers the next charge by its date; one tap creates a savings goal linked to the subscription (`POST /subscriptions/:id/sinking-fund`, idempotent; `GET /subscriptions/sinking-funds` lists them). - **Bills and invoices in the charge calendar**: open payables appear next to subscriptions with a "bill" label; sent invoices appear as money coming in and stay out of the month totals. - **Switch for "is this a subscription?" prompts** in Settings → Notifications (`subscriptionPrompts`). #### Telegram inbound (2026-10-01) - **Send the bot a photo of a receipt** (or an image as a file): it goes through the same scan pipeline as the in-app camera, lands in the review inbox, and the bot answers with the vendor, the amount and a delete button. PDFs still go to statement import. - **Type an expense**: "coffee 4.50", "taxi 15 EUR", "такси 1500 тг" books a manual expense dated today, unreviewed and uncategorised on purpose, so the category is picked in the review inbox. The reply carries a delete button. `/help` explains both. #### Receipts meet bank rows (2026-10-01) - **Receipt → transaction match:** after parsing, a receipt is matched to the bank row it documents (same money, same direction, within three days, the name agrees; exactly one plausible row) or to the several charges of one order (Amazon bills per shipment). Approving attaches the receipt to that row and copies its image as a transaction attachment instead of booking the expense a second time; `POST /receipts/:id/approve` takes `{transactionId}` to pick another row or `null` to book a new one, `GET /receipts/:id/transaction-matches` lists the candidates. - **Split by line items:** `GET /receipts/:id/split-suggestion` categorises each line (keywords, then the model) and groups them into parts that add up to the transaction; `POST /receipts/:id/split` applies it through the ordinary split. Line categories are remembered on the receipt. - **Receipts in the email body:** order confirmations without an attachment now get their line items (and a total when the amount scan found none) from the stripped text, and a `YYYY-MM-DD` date instead of the raw header. #### Review inbox (2026-10-01) - **One queue for everything that needs a decision** at `/review` (`GET /review-inbox`, `GET /review-inbox/counts`): transactions nobody categorised or the model categorised, receipts without an amount, suspected duplicates, detected subscriptions. Read from the existing rows, so an item leaves the queue the moment it is resolved anywhere. - Keyboard-first on desktop (`j`/`k`, `x`, `a`, `c`, `Esc`), group by payee with one-click group selection, a date range for "everything from the trip → Vacation", approve with a category or as is (`POST /review-inbox/transactions/approve`, which records a manual pick and learns from it), keep or confirm a duplicate (`POST /review-inbox/duplicates/:id/resolve`), approve receipts, confirm or dismiss subscriptions. On mobile, swipe right approves and left skips. - Settings → Processing: "Trust AI picks" keeps the model's categories out of the queue. - A weekly notification "N items are waiting for review" (Monday morning, through the existing "uncategorised items" preference and digest mode). #### Data you can trust: transfers, reimbursements, category provenance (2026-10-01) - **Transfers between your own accounts** are paired automatically after every statement import (same money, different account, within three days, exactly one counterpart) and by hand (`POST /transactions/:id/link-transfer`, `unlink-transfer`, `transfer-candidates`, `POST /transactions/transfers/detect`). Paired rows keep their direction but no longer count as spend or income anywhere: dashboard, reports, budgets, goals, insights, subscriptions, tax drafts. The list filter `type=transfer` shows them; unlinked pairs are never re-paired automatically. - **Reimbursements:** an incoming row can be linked to the expense it pays back (`link-reimbursement`, `unlink-reimbursement`, `reimbursement-candidates`). A full repayment becomes a `reimbursement` pair and leaves the aggregates; a partial one keeps the link and still counts gross. - **Category provenance:** every transaction records which step set its category (`manual`, `rule`, `keyword`, `learned`, `history`, `ai`, `default`) and why (the rule name, the learned payee, the keyword, the model's confidence). The details drawer shows it. The order is fixed and stated in Settings → Processing: your pick → rules → keywords → your corrections → payee history → AI → "Uncategorised". A hand-picked category is sticky and bulk re-classification skips it. - **Switches** in Settings → Processing: AI categorisation (off means no model calls and no reuse of what the model taught), AI merchant names, and learning from corrections. - **Learning guard:** one correction does not replace a payee's established category; the second one does, and what you taught outranks what the model taught at any confidence. ### Changed - Cross-statement duplicate detection no longer matches rows from two different accounts: the same coffee on two cards is two coffees. Overlapping re-imports of one account are still caught. - The AI result is applied only where rules, keywords and your corrections found nothing; the statement import and the custom-table conversion now agree on that order. ### Fixed - Changing a transaction's category through `PUT /transactions/:id` or `bulk-update` answered with the new category but wrote the old one back to the database (TypeORM preferred the loaded relation over the changed id). Category, branch and wallet changes now persist. #### Income tax declaration (2026-09-14) - **Tax declaration wizard** (`/tax-declaration`, `GET/PUT/POST /income-tax/*`) drafts the annual income tax return for the self-employed: disclaimer, profile, category → form-line mapping, data check, draft, PDF/XLSX export, finalize and reopen. Only mappings the user confirmed count. - **Rule packs:** Germany Anlage EÜR (2025–2026), Spain Modelo 100 estimación directa simplificada (2025–2026), Poland PIT-36L (2025–2026), PIT-28 and PIT-36 (2025); every other country gets a generic annual income and expense summary. - **Official FX sources** for Poland (NBP table A) and Italy (Banca d'Italia); a missing rate is reported as an issue instead of defaulting to 1. - **Filing information** — form, deadlines, portal and record retention — for 25 EU countries. #### Receipt locations and self-hosted maps (2026-09-14) - Receipts get a location from a manual pin, the geocoded merchant address, the photo's EXIF GPS or the device position (in-app camera only, after per-device consent); `PATCH/DELETE /receipts/:id/location`. - Optional Compose profiles `maps` (Planetiler + tileserver-gl, four styles) and `geocoder` (Nominatim). The backend proxies tiles (`/maps/styles`, `/maps/tiles/...`); the feature stays off until `TILESERVER_URL` / `GEOCODER_URL` are set. #### Account security (2026-09-14) - Self-service password reset by email (`/auth/forgot-password`, `/auth/reset-password`): single-use 1-hour tokens, all sessions revoked on reset. - Email changes take effect only after the confirmation link sent to the new address is opened. #### Content background (2026-09-14) - A bundled or uploaded photo behind the app content, with adjustable dimming (Settings → General). ### Changed #### Cookie sessions and API hardening (2026-09-14) - **Breaking for API clients:** browsers now authenticate with HttpOnly `access_token` / `refresh_token` cookies, and `/auth/login` no longer returns tokens in the body. Scripts can still send `Authorization: Bearer` or `X-Api-Key`. - Double-submit CSRF protection on cookie-authenticated writes (`csrf_token` cookie → `x-csrf-token` header). - The access token lifetime dropped from 30 days (set in `docker-compose.yml` and the JWT module default) to **30 minutes**; the refresh token stays at 30 days. - New settings: `CORS_ORIGINS`, `AUTH_COOKIE_SAMESITE`, `AUTH_COOKIE_DOMAIN`, `PASSWORD_RESET_TOKEN_SECRET`, `BCRYPT_ROUNDS`. In production the backend refuses to start without a CORS origin. - helmet security headers, Redis-backed rate limit counters, and an egress guard that blocks private and loopback addresses for user-supplied URLs. #### Self-hosted delivery (2026-09-14) - CD builds, scans, signs and publishes images on `v*.*.*` tags (or manual runs) and stops there: no staging/production environments, no post-deploy smoke check, no `staging` branch triggers. #### Aurum-style charts (2026-09-13) - **Cash flow on the Trends tab** is now monthly income/expense bars with a signed net total and an All time / 5 years / 12 mo / This year switcher (`?cf=`), backed by the new `GET /dashboard/cash-flow?range=` endpoint (whole calendar months, empty months zero-filled). - **Net worth, category donuts (Overview and Trends) and the ROI projection** moved from ECharts to Recharts with the same look: no Y axis or grid, year ticks only across calendar years, first/last label row, theme colours via CSS variables. - Chart design follows [Aurum](https://github.com/ZProger/Aurum) by ZProger; reimplemented, no Aurum code included. The goal Sankey, the spend trend forecast and the statements charts stay on ECharts. ### Removed #### Bundled monitoring and Railway (2026-09-14) - The Prometheus + Grafana stack: `docker-compose.observability.yml`, `observability/`, and `make observability` / `make observability-stop`. `/api/v1/metrics` stays; guard it with `METRICS_AUTH_TOKEN` and scrape it with your own collector. - Railway deployment: `RAILWAY.md`, `railway.json`, its Conftest policy and the docs page. ### Fixed #### API URL Path in Document Viewer (2025-01-20) - **Fixed incorrect API URL paths** in `/statements/:id/view` page - Added missing `/api/v1/` prefix to statement and transactions fetch requests - Previously: `${API_URL}/statements/:id` (404 error) - Now: `${API_URL}/api/v1/statements/:id` (works correctly) - Impact: Document viewer page now loads data successfully - Files changed: `frontend/app/statements/[id]/view/page.tsx` ### Added #### Transaction Document Viewer (2025-01-XX) - **New Document View Page** (`/statements/:id/view`) - Beautiful, professionally formatted document for viewing bank statements and transactions - Optimized for viewing and printing - Responsive design for desktop, tablet, and mobile devices - **TransactionDocumentViewer Component** - Rich header with gradient background and bank information - Summary cards showing: starting balance, income, expenses, ending balance - Detailed transaction table with all fields - Visual indicators: color-coded borders (green for income, red for expenses) - Category chips displayed under transaction purpose - Print-optimized styles with color preservation - **Action Panel Features** - Back button to return to previous page - Edit button to switch to edit mode - Print button with browser print dialog integration - **Print Optimization** - A4 format with 15mm margins - Color preservation (gradients, borders, semantic colors) - Smart page breaks (no broken tables/rows) - Hidden UI elements (action panel not printed) - Optimized fonts and spacing for printing - **Data Formatting** - Numbers: localized formatting with thousand separators (e.g., `1 234 567.89 KZT`) - Dates: DD.MM.YYYY format (e.g., `27.11.2025`) - Currency codes displayed alongside amounts - **Documentation** - `DOCUMENT_VIEWER.md` - English technical documentation - `ПРОСМОТР_ДОКУМЕНТА_ТРАНЗАКЦИЙ.md` - Russian user guide - `TESTING_DOCUMENT_VIEWER.md` - Comprehensive testing guide ### Changed #### Storage Page Navigation - **View Button Behavior** - Previously: Clicking eye icon (👁️) in Storage navigated to edit page (`/statements/:id/edit`) - Now: Clicking eye icon navigates to new document view page (`/statements/:id/view`) - Edit page still accessible via "Edit" button in document view or directly from statements list ### Technical Details #### New Files - `frontend/app/components/TransactionDocumentViewer.tsx` - Main document viewer component - `frontend/app/statements/[id]/view/page.tsx` - Document view page wrapper - `docs/DOCUMENT_VIEWER.md` - English documentation - `docs/ПРОСМОТР_ДОКУМЕНТА_ТРАНЗАКЦИЙ.md` - Russian documentation - `docs/TESTING_DOCUMENT_VIEWER.md` - Testing guide #### Modified Files - `frontend/app/storage/page.tsx` - Updated `handleView()` navigation target #### API Endpoints Used - `GET /api/v1/statements/:id` - Fetch statement data - `GET /api/v1/statements/:id/transactions` - Fetch transactions list #### Dependencies - Material-UI components: Box, Paper, Table, Typography, Chip, etc. - Material-UI icons: TrendingUp, TrendingDown, AccountBalance, CalendarToday, Receipt - Next.js navigation: useRouter #### Browser Support - Chrome/Edge (recommended for best print quality) - Firefox - Safari - Opera ### Performance - Fast loading for statements with up to 500 transactions (< 3s) - Acceptable performance for statements with up to 2000 transactions (< 10s) - No memory leaks detected in testing - Smooth scrolling and hover effects ### UX Improvements - **Visual Clarity**: Color-coded transaction types (green/red) - **Information Density**: All data visible at once - **Professional Appearance**: Gradient header, clean layout - **Easy Navigation**: Clear action buttons - **Quick Access**: One click from Storage to document view ### Known Limitations 1. Large statements (>5000 transactions) may have performance issues 2. Safari may not preserve gradient colors when printing 3. PDF export only available through browser (no server-side generation yet) 4. Internet Explorer 11 not supported ### Future Enhancements Planned features: - [ ] Server-side PDF generation - [ ] Configurable column visibility - [ ] Transaction filtering within document - [ ] Watermark support for printed documents - [ ] Charts and visualizations - [ ] Period comparison - [ ] Multiple document templates - [ ] Company logo customization --- ## [Previous versions] (Previous changelog entries would go here) --- **Note**: This project uses semantic versioning. Version numbers follow the pattern MAJOR.MINOR.PATCH where: - MAJOR: Incompatible API changes - MINOR: Backwards-compatible functionality additions - PATCH: Backwards-compatible bug fixes