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
FinvestLens — native Apple double-entry accounting
Platforms
macOS, iPadOS, iOS
Document status
Requirements baseline v1.3 (Aug 2026 — register disclosure styles and row height, settled platform floors, the NAS validation, and the attachment-matching requirements FR-AI-10…12 / FR-CLI-06)
Author
Christine Tham
License
GNU GPL v3.0
This PRD states intended requirements, not build status. What has been implemented is recorded in the plan and implemented.md; what remains open is in deferred.md.
1. Introduction
FinvestLens is a native Swift application for personal and small-business accounting on Apple platforms. It reimplements the core accounting engine of GnuCash — a mature, free double-entry bookkeeping package — using modern Apple technologies (SwiftUI for the interface, a native SQLite document format for storage) while remaining interoperable with GnuCash through XML import/export. It is native-first: it opens and saves its own document format rather than adopting GnuCash's on-disk format.
This PRD defines the product's purpose, scope, target users, and detailed functional and non-functional requirements. It is grounded in the GnuCash Tutorial and Concepts Guide and Help Manual, which describe the domain model and feature set FinvestLens targets.
1.1 Purpose
Give Apple-ecosystem users a first-class, native accounting application that preserves the rigour of GnuCash's double-entry engine while feeling genuinely at home on Mac, iPad, and iPhone, and never locks users in — data moves freely to and from GnuCash. Beyond parity, FinvestLens draws on three other tools for what GnuCash lacks: the consumer-grade planning and guidance that made Microsoft Money approachable (forecasting, budgeting with rollover, bill reminders, proactive alerts, long-range planning — §5.16, study); the automation and organization of Firefly III (a rules engine, tags, savings goals, an operator search language, modern bank sync — §5.17, study); and the connected-data, engagement, and financial-wellness ideas of Frollo, adapted to Australia's Open Banking / CDR and to a local-first stance (study). The positioning is a four-way synthesis: GnuCash's rigor + Money's planning + Firefly's automation + Frollo's connectivity & wellness, natively on Apple platforms. All of it sits on the accounting core as read-models, guided workflows, or optional/consented connectors — never weakening double-entry integrity, and never compromising the app's offline, private, local-first core.
1.2 Background: the GnuCash domain model
GnuCash is built on classical double-entry bookkeeping. The essential concepts FinvestLens must reproduce:
Book — the top-level container for a set of accounts and their data (a "file").
Account — a named bucket with a type (Asset, Bank, Cash, Credit Card, Liability, Equity, Income, Expense, Receivable, Payable, Stock, Mutual Fund, Trading). Accounts form a hierarchy (tree).
Transaction — a dated economic event, composed of two or more splits that must sum to zero (the double-entry invariant).
Split — one leg of a transaction: an amount posted to a specific account, with a value (in the transaction's currency) and a quantity (in the account's commodity). Carries a reconciliation state (n/c/y — not/cleared/reconciled) and optional memo/action.
Commodity — a currency or a security (stock, fund). Accounts are denominated in a commodity.
Price — a commodity's value in another commodity at a point in time (the price/quote database).
Lot — a grouping of splits used to track cost basis for capital-gains calculation.
Scheduled Transaction — a template plus a recurrence rule that generates future transactions.
G5 — Provide a shared codebase across macOS, iPadOS, and iOS with platform-adaptive UI.
2.2 Non-goals
NG1 — Binary/on-disk compatibility with GnuCash's own backends (its XML file, or its SQLite/MySQL/PostgreSQL stores). GnuCash XML is an import/export interchange format; FinvestLens's native store is its own .finvestlens SQLite document.
NG2 — Reproducing GnuCash's exact GTK UI or workflows. FinvestLens follows the Apple Human Interface Guidelines.
NG3 — Reimplementing the GnuCash Scheme/Guile scripting or Python bindings.
NG4 — Providing financial or investment advice. FinvestLens is a record-keeping tool, not an advisor.
NG5 — Executing trades, payments, or fund transfers. FinvestLens records transactions; it does not move money.
3. Target users and personas
Persona
Description
Primary needs
Migrating GnuCash user
Existing GnuCash user on Windows/Linux/older Mac wanting a native Apple app
Lossless import/export, familiar model, feature parity for their workflow
Personal-finance keeper
Individual tracking bank accounts, credit cards, income/expenses, budgets
Fast transaction entry, reconciliation, clear reports, iCloud sync across devices
Ledger 3 journal import/export; the read-only finlens CLI and the write-side finlab (balance/register/print…) — design in ledger-design.md
Planning features that layer onto earlier phases (bill reminders, cash-flow forecast, alerts, budgets, payee rules, portfolio, dashboard, onboarding) are scheduled within P4–P7 — see §5.16 and the enhancement study. An optional on-device Apple Intelligence layer (§5.18, FR-AI-01…12) adds PDF statement/invoice/dividend import, auto-categorisation, and budget/forecast narration over the same engine — see Architecture §11.
5. Functional requirements
Requirement IDs are stable references. Pri = priority; Phase = target phase.
5.1 Core accounting engine
ID
Requirement
Pri
Phase
FR-ENG-01
Represent monetary amounts with Swift-native Foundation.Decimal (wrapped in a Money type with its commodity), avoiding binary floating-point error. Bit-exact parity with GnuCash's gnc_numeric is not required; small rounding differences are acceptable.
Must
P0
FR-ENG-02
Model Account with: name, type, code, description, notes, parent, commodity, commodity scaling (SCU), hidden/placeholder flags, and a stable GUID.
Must
P0
FR-ENG-03
Support the full set of GnuCash account types: Asset, Bank, Cash, Credit, Liability, Equity, Income, Expense, Receivable, Payable, Stock, Mutual Fund, Trading, Root.
Must
P0
FR-ENG-04
Model Transaction with date posted, date entered, description, number, notes, currency, and ≥2 splits.
Must
P0
FR-ENG-05
Model Split with account, memo, action, value (transaction currency), quantity (account commodity), reconcile state, reconcile date, and GUID.
Must
P0
FR-ENG-06
Enforce the double-entry invariant: the sum of split values in a transaction must be zero (balanced) before commit.
Model Commodity (currency or security): namespace, mnemonic/symbol, full name, fraction/SCU, and quote source metadata.
Must
P0
FR-ENG-09
Model Price entries (commodity, currency, date, value, source, type) forming a price database.
Must
P5
FR-ENG-10
Model Lots to associate splits for cost-basis / capital-gains tracking.
Should
P5
FR-ENG-11
Assign and preserve GUIDs for all first-class objects to enable stable round-tripping with GnuCash.
Must
P0
FR-ENG-12
Keep the engine free of UI and persistence dependencies so it is unit-testable in isolation.
Must
P0
5.2 Native document format, persistence, and shared-storage safety
FinvestLens is a document-based app with its own native file format (a single SQLite.finvestlens file, managed via GRDB). The in-memory engine model is the source of truth; SwiftData is not used. GnuCash XML is an import/export interchange format only (§5.3–5.4). See Architecture §5.2 & §6.
ID
Requirement
Pri
Phase
FR-DAT-01
Open and save the app's own native document — a single .finvestlensSQLite file (via GRDB) — with its own registered file type/UTI.
Must
P1
FR-DAT-02
Provide a SQLite schema (GRDB migrations) mapping to the engine model, preserving imported GUIDs and KVP slots.
Must
P1
FR-DAT-03
Support multiple documents (open, create, switch, recent files); each document is self-contained.
Should
P1
FR-DAT-04
Handle schema migration across app versions without data loss.
Must
P1
FR-DAT-05
Ensure transactional integrity: partial/invalid transactions never persist; the double-entry invariant holds in storage.
Must
P1
FR-DAT-06
Allow a document to live on a network share (NAS) and enforce single-writer locking via an application-level lock file (holder metadata + heartbeat + stale-lock detection); offer read-only open when locked elsewhere.
Must
P1
FR-DAT-07
Edit against a local working copy; write back to the document only on explicit Save (⌘S) or autosave — never continuous background sync. Write-back is atomic under file coordination and never leaves the document corrupted on crash or conflicting write.
Must
P1
FR-DAT-08
Detect and surface external-change conflicts (out-of-band edits / bypassed lock) instead of silently overwriting.
Should
P1
FR-DAT-09
Support discarding a working session: closing without saving (or Revert) abandons unsaved changes; the on-disk document reflects only the last save. Retain the opened-state snapshot to enable Revert to opened version.
Must
P1
FR-DAT-10
Autosave is user-configurable (interval, or off). Provide crash recovery from the local working copy.
Should
P2
5.3 GnuCash XML import (primary interoperability)
ID
Requirement
Pri
Phase
FR-IMP-01
Import the GnuCash XML file format, both uncompressed and gzip-compressed.
Must
P1
FR-IMP-02
Parse and map: book, commodities, accounts (hierarchy), transactions, splits, and prices.
Must
P1
FR-IMP-03
Import scheduled transactions (sx:) and their recurrence rules.
Preserve all GUIDs, slots/key-value data, and unrecognised elements sufficiently to round-trip.
Must
P1
FR-IMP-07
Report a clear import summary (counts, warnings, unsupported elements) and fail safely on malformed files.
Must
P1
FR-IMP-08
Validate that the double-entry invariant holds on imported data; surface imbalances rather than silently altering data.
Must
P1
FR-IMP-08a
Check & Repair reports transactions posted in an impossible year. A mistyped year is silent in a way a mistyped amount is not: the transaction still balances, still reconciles, still exports — it simply leaves the period it belongs to, so it vanishes from every report that bounds by date and no total ever looks wrong. One plausible-year range (DatePlausibility.years, 1900–2200) serves both the scrubber and the QIF importer, which previously disagreed — the importer's own floor of 1500 existed only to stop yyyy reading a two-digit year, and would not have caught 1525. These findings are reported, never repaired: which year was meant exists only in the owner's head, and a guess moves real money into a real period, so Clean Up leaves them alone and is disabled when they are the only finding. (Aug 2026 — a real book carried 1525-01-31 for a year, entered ninety seconds after its December neighbour and ninety before its February one.)
Must
P2
5.4 GnuCash XML export
ID
Requirement
Pri
Phase
FR-EXP-01
Export to GnuCash XML (compressed and uncompressed) that GnuCash desktop can reopen.
Must
P3
FR-EXP-02
Achieve round-trip fidelity: import → export → re-import yields structurally equivalent data (GUIDs, slots, relationships) for all supported object types; monetary amounts match within a rounding tolerance (exact gnc_numeric parity is not required). Verified by automated tests.
Must
P3
FR-EXP-03
Preserve GUIDs, slots, and unmodified elements captured on import.
Must
P3
FR-EXP-04
Emit valid namespaced XML matching the GnuCash schema version FinvestLens targets.
Must
P3
5.5 Chart of accounts
ID
Requirement
Pri
Phase
FR-COA-01
Display accounts as a hierarchical tree with balances (in account commodity and, where relevant, converted to a base currency).
Must
P2
FR-COA-02
Create, edit, move (reparent), hide, and delete accounts, with guards against deleting accounts that hold transactions.
Must
P2
FR-COA-03
Provide account templates / new-book assistant to set up a starter chart of accounts by locale/business type.
Should
P4
FR-COA-04
Mark accounts as placeholder (no direct postings) and hidden.
Should
P2
FR-COA-05
Show totals, subtotals, and net worth roll-ups.
Should
P2
FR-COA-06
Support account codes and renumber sub-accounts (bulk re-code by hierarchy).
Could
P2
5.6 Transaction register (ledger)
ID
Requirement
Pri
Phase
FR-REG-01
Provide a register/ledger view per account for entering and editing transactions, with running balance.
Must
P2
FR-REG-02
Support simple (two-split) and split (multi-split) transaction entry, enforcing balance.
Must
P2
FR-REG-03
One register, three disclosure styles(Aug 2026: the Jul 2026 "one register, no styles" simplification was reversed — see register-ux-research.md): a single table whose rows disclose their full detail — notes, tags and every leg. Basic Ledger discloses only the row you open by its triangle, Auto Details discloses the selected row, Transaction Journal discloses them all; these are GnuCash's SplitRegisterStyle values. Columns sort by heading click. Column widths are derived from their contents — measured in the column's own font, never narrower than its own heading — not fixed in points, and a column is resized by dragging its ruler anywhere down the register, not only in the header strip: a rule drawn the full height is a rule the user expects to grab. Dragged widths persist. A column whose content varies widely is sized to a high percentile rather than its widest value, and the outliers truncate: on a real book the longest account name is four times the median, so sizing Transfer to it spent Description's width on one row in a thousand. Num is not a column — it is blank on nearly every row, so it lives on its own line in the disclosed detail beside Notes and Tags, still fully editable. The row's two affordances sit together in gutters at the leading edge of the Date cell and read left to right — disclosure caret, then edit pencil, then the date — rather than putting the date between them. The reconcile glyph means the same thing on every line: an unknown state draws nothing, so a heading and the legs beneath it never render one state two ways. Cleared and reconciled are two points on one scale — the app's accent, outline then filled — not two colours; frozen and voided keep distinct colours because they are outside the progression rather than further along it. The whole-book journal is the All Transactions sidebar destination (FR-REG-09).
Should
P2
FR-REG-04
Provide autofill of payee/description and last-used splits, and keyboard-driven entry. (Jul 2026 redesign: ⌘N focuses the register's entry bar — ⇧⌘N opens the full split editor — and QuickFill completes inline as ghost text, accepted with Tab, filling the transfer account and amount.)
Should
P2
FR-REG-05
Support transfer between accounts (Transfer Funds dialog), duplicate, delete, void, and mark reconcile state inline.
Must
P2
FR-REG-06
Find/Search transactions by date, amount, payee, memo, account, reconcile state (multi-criteria search dialog). (Upgraded to an operator query language in FR-FIND-01.)
Should
P2
FR-REG-07
Handle multi-currency/commodity transactions with per-split exchange rates.
Must
P5
FR-REG-08
Transaction operations: add reversing transaction, jump to the other account's register, copy/paste, and remove splits.
Should
P2
FR-REG-09
Provide a whole-book journal view (combined register across all accounts) — presented as the All Transactions sidebar destination (GnuCash's General Ledger, renamed per the plain-language principle, §8).
Could
P2
FR-REG-10
Attach/associate an external file or URL to a transaction (document link / "paperclip").
Could
P6
FR-ACC-02
Placeholder and hidden cascade down a subtree. Setting either on a parent offers to apply it to every account beneath it, in one undoable action — a chart of accounts is reorganised by branch, and doing it row by row is where a half-hidden subtree comes from.
Should
P2
FR-ACC-03
Open Subaccounts: a parent's register may show the postings of its whole subtree in one register, not only its own. GnuCash's Open Subaccounts, and the reason the register's summary and entry bar have to know which it is showing.
Should
P2
FR-ACC-04
Deleting an account says where its contents go. An account with postings, subaccounts, or both cannot simply vanish: the delete asks which account inherits the transactions and which the children, and refuses where no valid destination exists.
Must
P2
FR-REG-11
Print checks from transactions with configurable check formats.
Could
P4
FR-REG-12
Row height is measured, not fixed. The register's default row is derived from the display's physical point density (CGDisplayScreenSize), correcting half-way towards a row of constant physical size and never so tall that thirty transactions would not fit the screen; text and glyphs scale with the row. Overridable — Automatic, Compact, Standard, Comfortable, Spacious — from Settings ▸ Appearance ▸ Register, the register's View ▾ menu, and the View menu bar. iOS publishes no physical-size API and leaves this to Dynamic Type. (Aug 2026; measurements and the reasoning for the half-way correction in register-ux-research.md §4.)
Should
P6
| FR-REG-13 | Dates fit the space they are given. The register applies FR-PLT-07 with a ceiling of short — it is a dense table, so it never spells a month out however wide the window. Its Date column is sized to the widest date it actually holds, in the font actually in use, including the disclosure gutter that shares its cell. Where the window is too narrow to afford that and leave Description readable, the register steps down the form ladder (24/12/2026 → 24/12/26) rather than truncating; the choice is re-made on every resize, for the whole column at once so no two rows disagree about their format. The year is never dropped: a register spanning years becomes unreadable exactly where the running balance depends on knowing which year a row is in. (Aug 2026 — every one of the three orders overflowed a fixed 80pt column, so no complete date was ever visible.) | Should | P2 |
| FR-REG-14 | Text can be copied out of the register. ⌘C copies the selected rows as tab-separated text, in the columns currently on screen and in their displayed order; right-click keeps its transaction actions. Plain ⌘C is free because Copy Transaction is ⇧⌘C. Every other surface is already covered by .textSelection(.enabled) applied app-wide in AppearanceModifier — but the register draws with Core Text, which that modifier cannot reach, and it is the surface people most want to copy from. (Aug 2026 — asked for three times: "text are generally not able to be copied to clipboard", "I thought you were going to make all text copyable", "I couldn't copy any other text such as filename".) | Should | P2 |
5.7 Reconciliation
ID
Requirement
Pri
Phase
FR-REC-01
Provide a reconciliation workflow: statement date and ending balance, mark cleared items, show running difference, finish when reconciled to zero. (Jul 2026 redesign: auto-clear runs as the opening move — the flow starts at "matched N of M, review the rest" — the difference remaining is the headline, unmatched rows sort first, and Finish explains itself while disabled.)
Must
P4
FR-REC-02
Persist reconcile state (n/c/y) and reconcile dates on splits.
Must
P4
FR-REC-03
Support opening-balance reconciliation and re-opening a previous reconciliation.
Should
P4
5.8 Scheduled (recurring) transactions
ID
Requirement
Pri
Phase
FR-SCH-01
Create scheduled transactions from a template with recurrence rules (daily, weekly, monthly, yearly, nth-weekday, etc.).
Should
P4
FR-SCH-02
Support variables/formulas in scheduled splits (e.g. loan payment components).
Could
P4
FR-SCH-03
Notify/remind and allow review before posting upcoming scheduled transactions ("since last run" assistant).
Should
P4
FR-SCH-04
Provide a loan/mortgage assistant to generate an amortized scheduled transaction.
Could
P5
5.9 Investments, commodities, and prices
ID
Requirement
Pri
Phase
FR-INV-01
Support stock and mutual-fund accounts denominated in a security commodity.
Must
P5
FR-INV-02
Provide a Price Editor / database: manual entry and listing of commodity prices over time.
Must
P5
FR-INV-03
Retrieve online price quotes for securities and currencies via pluggable providers, with user-triggered and scheduled refresh, a user-chosen default and fallback order. Results populate the Price DB. (Jul 2026 redesign: a one-click Update Prices (⌘⇧U) fills every security's missing history with the default provider — from the menu, the dashboard's Up Next card, or the Prices toolbar — with determinate progress and a completion toast; "last updated" shows in the Prices header.)
Should
P5
FR-INV-03a
Provide a keyless "yfinance-like" Yahoo provider (no API key) for current and historical quotes — the default out-of-box source, with Stooq as a second keyless fallback. (Corrected 11 Aug 2026: this row also claimed dividends and splits. It never fetched either — YahooQuoteProvider uses only v8/finance/chart. Dividends and corporate actions are FR-INV-19/FR-INV-21 under P11.)
Should
P5
FR-INV-03b
Support keyed providers where the user enters an API key (stored in the Keychain), including EODHD, Alpha Vantage, Finnhub, and Twelve Data. Keys are entered by the user in Settings and sent only to that provider.
Should
P5
FR-SEC-01
Secrets live in the system Keychain, never in the document, never in UserDefaults, and never in a log or an error message. Quote API keys are the only secrets the app holds; the store is protocol-backed so tests can substitute an in-memory one without a Keychain entitlement.
Must
P5
FR-INV-03c
Support EODHD specifically for historical prices of delisted securities (and deep multi-decade history).
Should
P5
FR-INV-03d
Support historical price backfill over a date range (not just latest), to populate the Price DB for valuation and reports.
Should
P5
FR-INV-03e
Surface a clear terms-of-use notice for unofficial/keyless sources (e.g. Yahoo endpoints are unaffiliated, personal-use); let users pick sanctioned keyed providers instead.
Should
P5
FR-INV-04
Provide a Stock Transaction Assistant guiding buy/sell, dividends (cash and reinvested), return of capital, fees, and stock splits/mergers through a step-by-step flow.
Should
P5
FR-INV-05
Compute capital gains/losses using lots (cost basis) via a Lots Editor, rather than manual computation.
Should
P5
FR-INV-06
Value holdings and portfolios in a chosen base currency using the price database.
Should
P5
FR-INV-07
Provide a Security Editor to add/edit commodities (securities & currencies) and configure their online-quote source.
Should
P5
The Investments hub (P11)
Agreed 11 Aug 2026. Full rationale, surfaces and phasing in
investments-design.md; FR-INV-02's "listing of
commodity prices over time" is narrowed by FR-INV-29 — prices are listed
per security and exported, never as one book-wide table.
ID
Requirement
Pri
Phase
FR-INV-08
Replace Prices & Securities with an Investments destination, promoted out of Records.
Should
P11
FR-INV-09
Show price-database health: value-weighted coverage of held positions, per-holding freshness, last run, failures, next scheduled run.
Should
P11
FR-INV-10
Judge freshness against the exchange's observed trading days, not elapsed calendar days.
Should
P11
FR-INV-11
Present a holdings table: units, last price, age, market value, allocation, return since holding.
Should
P11
FR-INV-12
Draw a price sparkline per holding, showing missing data as breaks rather than interpolating over them.
Should
P11
FR-INV-13
Offer a needs-attention worklist — classes of problem with counts and inline fixes; empty on a healthy book.
Should
P11
FR-INV-14
Show return since holding per security, from the existing lot engine (FR-RPT-02a).
Should
P11
FR-INV-15
Provide a security detail page: profile, price history, financials, dividends, transactions, lots, prices, settings.
Should
P11
FR-INV-16
Overlay the user's own buys, sells, average cost and holding periods on the price chart.
Should
P11
FR-INV-17
Fetch a company profile, Yahoo by default and a keyed provider when one is configured.
Fetch declared dividend history, with yield on cost.
Could
P11
FR-INV-20
Reconcile declared dividends against recorded income, reporting: declared-not-recorded, recorded-not-declared, amount mismatch, and missing DRP units.
Could
P11
FR-INV-21
Detect corporate actions — a provider-reported split with no matching book transaction is flagged, since prices before it are inconsistent with units held.
Could
P11
FR-INV-22
Let the user choose the provider for a refresh, per run and per security.
Should
P11
FR-INV-23
Refetch one security or all, either filling gaps or replacing history.
Should
P11
FR-INV-24
Hide closed positions by default, with a control to reveal them.
Should
P11
FR-INV-25
Include closed positions in a fetch where their held period contains gaps, so historical valuations stay correct without paying for dead symbols daily.
Should
P11
FR-INV-26
Report price gaps, flagging those that fall inside a holding period — the only ones that corrupt a valuation.
Should
P11
FR-INV-27
Make price provenance visible — the recorded source of every price, on the row and on the chart.
Should
P11
FR-INV-28
Flag implausible prices (decimal slips, wrong-currency entries) against their neighbours.
Could
P11
FR-INV-29
Export a security's prices to CSV. No book-wide price list is shown at any altitude.
Should
P11
FR-INV-30
Treat manual valuation as a first-class category — securities no provider can price get inline entry and an expected cadence, not permanent failures.
Should
P11
FR-INV-31
Provide a FIIG provider for Australian corporate bonds, matched by ISIN, fetched as one batch, with daily history from its per-bond endpoint. Prices are published as percent of par and scaled to each security's own unit.
Could
P11
FR-INV-38
Supply a security's exchange suffix from its namespace where the mnemonic lacks one, so a GnuCash-shaped commodity (ASX + WMX) reaches providers as WMX.AX.
Should
P11
FR-INV-39
Fetch company profile and financials for every security in one command, not only per security.
Should
P11
FR-INV-41
Add a security by looking it up through a price provider — search by name or ticker, with the exchange-qualified identifier, name and type filled in from the result, then its prices and (where served) profile and financials fetched. A security is distinct from an account: several accounts may hold the same security.
Should
P11
FR-INV-42
Price unlisted managed funds and trusts whose unit prices are published only on the manager's own website, keyed by the fund's page slug rather than a ticker, and read the fund's profile — inception, asset class, benchmark, timeframe, APIR, ARSN, fees — from the same request. Such a page is written for people, not machines: its published dates may be inconsistent within one table (day-first above the 12th, month-first below), so the parser resolves the whole column to a monotonic series rather than trusting any single row. Implemented for Wilson Asset Management.
Should
P13
FR-INV-40
Record that a security has no public price — a retail super or managed-fund unit, a private holding — so no provider is ever asked for it. Distinct from no longer trading: these still trade, their price simply arrives on a statement.
Should
P11
FR-INV-32
Treat ISIN as a first-class, editable identifier (GnuCash cmdty:xcode).
Should
P11
FR-INV-33
Show FX-rate health alongside price health, so a foreign holding cannot be silently unvalued.
Should
P11
FR-INV-34
Preview a refresh before it runs — which securities, which gaps, how many requests.
Could
P11
FR-INV-35
Keep fetched fundamentals in a sidecar cache, never in the book, so the GnuCash XML round-trip is untouched.
Must
P11
FR-INV-36
Judge "can a provider price this" on evidence — a provider-sourced price in the book — rather than on GnuCash's cmdty:get_quotes, which records only what Finance::Quote could reach.
Should
P11
FR-INV-37
Let a security be marked no longer trading: its last price is final, so it is not fetched, not judged for freshness, and not counted in valuation coverage. Reversible.
Should
P11
5.10 Multiple currencies
ID
Requirement
Pri
Phase
FR-CUR-01
Support accounts and transactions in any ISO currency and user-defined commodities.
Must
P5
FR-CUR-02
Handle currency-crossing transactions with explicit exchange rates per split, enterable in the register — a transaction's own currency and the rate it implies.
Must
P5
FR-CUR-03
Optionally use trading accounts for multi-currency balancing (GnuCash's trading-accounts model).
Could
P5
FR-CUR-04
Retrieve currency exchange rates as prices.
Should
P5
5.11 Reports and charts
ID
Requirement
Pri
Phase
FR-RPT-01
Provide core reports: Account Summary / Balance Sheet, Income & Expense (Profit & Loss), Net Worth over time, Transaction Report, Cash Flow. (Jul 2026 report redesign — statements present at annual-report standard: a hierarchical face built from the user's own top-level groups with detail in numbered notes that tie back to it; ASC 274 personal-statement ordering (assets by liquidity, liabilities by maturity); materiality folding into "Other" with cash-and-equivalents and Uncategorised protected; accounting typography — Note column, prior-period comparative, parenthesised negatives, single rule above subtotals, double rule under closing figures. Titles use the personal-statements vocabulary: Statement of Financial Position (net worth on the face, the equity view reconciled in a note with a currency-translation line), Income Statement, Statement of Changes in Net Worth. The Trial Balance presents the same way with Debit/Credit columns, grouped by category, its unrealised valuation adjustment on the face. The presentation layer only arranges the engine's verified figures — identity tests enforce that no dollar moves.)
Should
P4
FR-RPT-02
Provide investment reports: Portfolio value, Advanced Portfolio, Price scatter.
Could
P5
FR-RPT-03
Provide charts (bar/line/pie) for income/expense, net worth, and portfolio, following the app's data-visualization design system.
Should
P4
FR-RPT-04
Allow date-range, account selection, and currency options; save report configurations.
Should
P4
FR-RPT-05
Export/print reports to PDF and share via the platform share sheet.
Should
P4
FR-RPT-06
Financial Year Pack(Jul 2026): a one-export EOFY bundle — the Income Statement, Statement of Financial Position, and Statement of Changes in Net Worth (annual-report presentation, FR-RPT-01), then Capital Gains and a Dividends & Franking summary (per security: franked / unfranked / imputation credits, grossed-up total) — for a chosen financial year, as a single PDF. The Reports gallery also keeps Recents (last five opened).
Should
P6
FR-RPT-07
Presentation decks(Jul 2026): results presented as 16:9 slide decks — a Financial Review (highlights, net-worth waterfall bridge, income/spending analysis vs prior year, cash flow, portfolio, dividends, capital gains, financial position) and an Investment Review built on fund-factsheet/brokerage-report structure (overview, allocation + concentration, mark-to-market leaders, income with yield, realised gains split at the one-year CGT boundary, return decomposition over money in). Slides appear only with meaningful data; every slide has a deterministic action title; on-device insights may rewrite it but must pass a deterministic number validator (FR-AI-09). Paged with keyboard, exportable as landscape PDF.
Should
P6
5.12 Budgets
ID
Requirement
Pri
Phase
FR-BUD-01
Create budgets with per-account, per-period planned amounts.
Could
P4
FR-BUD-02
Show budget vs. actual comparison reports.
Could
P4
5.13 Business features (SMB)
ID
Requirement
Pri
Phase
FR-BUS-01
Manage Customers and Vendors (contact, terms, tax table, currency).
Could
P7
FR-BUS-02
Manage Employees and expense vouchers.
Could
P7
FR-BUS-03
Create, post, and print Invoices (A/R) and Bills (A/P).
Could
P7
FR-BUS-04
Support Jobs, Billing Terms, and Sales Tax Tables.
Could
P7
FR-BUS-05
Record and apply customer/vendor payments; track A/R and A/P aging.
Could
P7
FR-BUS-06
Store company/business information used on documents.
Could
P7
5.14 Bank/financial file import (core) and extended formats
CSV, QIF, and OFX/QFX import are first-class, core features — the primary way users bring in bank, card, and brokerage data. They are reimplemented natively in Swift (not ported from GnuCash's importers); see Architecture §5.8a. All three feed the shared Import Matcher.
Import OFX / QFX files — native Swift parser handling OFX v1 (SGML) and OFX v2 (XML) and Quicken's QFX variant (bank, credit-card, and investment statements).
Must
P4
FR-XIO-03
Import CSV transactions and prices with a configurable column mapping and preview.
Must
P4
FR-XIO-05
Provide a Generic Transaction Import Matcher: match incoming transactions to existing ones, detect duplicates, and assign destination accounts with a confidence-based UI (shared by CSV/QIF/OFX and later formats).
Must
P4
FR-XIO-08
Support save/load of CSV import settings (column-mapping profiles) for repeat imports.
Should
P4
FR-XIO-14
A duplicate flag must rest on evidence, not on amount and date alone. Repeating an amount is ordinary activity — a subscription, a daily coffee, a transfer chunked by a payment limit — so for a payee transaction (one whose other legs are income/expense, where the payee is the identity) a same-amount candidate whose narrative contradicts the row's is not a duplicate. Contradiction, not agreement: a side that says nothing cannot disagree, so a description-less statement row is still recognised on re-import. Transfers between the user's own accounts are exempt — one side reading "Card payment" where the other reads "Direct Debit" is routine. The date-drift window is two days, not four: four reached into the previous period and flagged recurring amounts, while a hand-entry drifts a day or two from the bank's posting date. The review shows which existing entry each flag matched.
Should
P4
FR-XIO-13
Work a bank CSV's shape out from its own header rather than asking for a column map: find the header row (exports carry title lines, account blocks and blank rows above it), map columns by name, infer the date format, and skip the preamble. Two safeguards: the header is found by evidence, never assumed positionally, and a candidate is accepted only if the rows beneath it actually parse — a file the app has not understood falls back to the manual mapping rather than importing something wrong. Name the export where the header identifies one (e.g. Wise), and always let the user override.
Should
P4
FR-XIO-12
Remember which account a bank's statements belong to. Record the account identifier a statement carries (OFX BANKID/ACCTID, CAMT <Acct> IBAN or <Othr><Id>, MT940 :25:) in the account's online_id slot — GnuCash's own key, so the mapping round-trips and a book shared with GnuCash keeps working in both. Match it on later imports by prefix, longest wins, ambiguity refused (GnuCash test_acct_online_id_match). Learned from the account the user actually confirmed, and never overwritten once set.
Should
P4
FR-XIO-11
Pre-fill the import target account, and say where the suggestion came from: a remembered identifier (FR-XIO-12) first, then the statement's own file name where it identifies exactly one account (banks name exports after the account, e.g. ANZ VISA.ofx), otherwise the register the user was viewing. Only accounts a statement can post to are considered — never income/expense/equity. The match abstains on ambiguity: two accounts fitting equally well means no suggestion, because the wrong target posts a whole statement into the wrong account. The field is pre-filled, never locked, and a suggestion never overrides a choice the user has already made.
Should
P4
FR-XIO-06
Export CSV for accounts, transactions, and prices.
Should
P4
FR-XIO-04
Import MT940 / MT942 and CAMT.053 (ISO 20022) bank statement formats — native parsers feeding the Import Matcher, with extension + content-sniffing format detection.
Could
P8
FR-XIO-09
Import the Ledger 3 plain-text journal format (transactions, postings, costs @/@@, states, tags/metadata, aux dates, prices, the living directive set), with per-line errors, an import summary, and balance-assertion verification. Policies for the features that defy a strict engine (unbalanced virtuals, automated entries) are recorded in ledger-design.md §4.
Could
P10
FR-XIO-10
Export a Ledger 3 journal the real ledger binary parses, deterministic and round-trippable (GUIDs/types/states/tags via ledger-legal metadata comments; securities/FX as @@ total-cost postings; prices as P lines).
Could
P10
FR-XIO-07
Online bank sync via modern aggregation APIs — SimpleFIN / GoCardless (Nordigen) and, for Australia, the Consumer Data Right (CDR / Open Banking) via an accredited intermediary — feeding the Import Matcher. Optional, explicitly consented, cloud-mediated; the app stays fully functional offline. (Skipped from the phase plan 24 Jul 2026 — deferred.md §5; revisit only on strong demand.)
Won't-for-now
—
5.15 Platform integration
Import/export is a desktop-class capability. GnuCash XML import/export, bank-file import (CSV/QIF/OFX-QFX), CSV export, and report/PDF export are macOS (and iPadOS where feasible) features. On iOS (iPhone), it is acceptable for FinvestLens to support only opening an existing book and creating/editing new books — import and export need not be offered. This keeps the compact iPhone experience focused on quick entry and review; users move data in and out on the Mac (or iPad), with the same document opened everywhere via Files/iCloud (FR-PLT-02).
ID
Requirement
Pri
Phase
FR-PLT-01
Adaptive SwiftUI UI: multi-column/sidebar on macOS & iPad, compact navigation on iPhone.
Must
P2
FR-PLT-02
File-level sync: because the document is a file, support iCloud Documents / Files placement with NSFilePresenter-based external-change handling and NSFileVersion conflict resolution (consistent with the NAS write-back path). (Replaces the earlier SwiftData+CloudKit plan.)
Register FinvestLens's own.finvestlens document type (UTI, public.database) and register as an importer/exporter for GnuCash file types.
Must
P1/P3
FR-PLT-05
Standard document behaviors (open/save/save-as, autosave, recent files, versions) across macOS/iPadOS/iOS.
Should
P1
FR-PLT-06
Platform capability scoping. GnuCash XML import/export, bank-file import, CSV export, and report/PDF export are provided on macOS (and iPadOS as feasible). On iOS (iPhone) these are out of scope; iOS supports opening existing books and creating/editing new ones only.
Must
P6
FR-PLT-07
Dates: the user picks the order, the app picks the form. The only date preference is the component order — D/M/Y (Australian), M.D.Y (United States), Y-M-D (Japanese). How much of a date is written out is never asked of the user; the app decides it from context and available space, choosing the richest of four forms that fits: full (weekday + spelled month), long (spelled month), short (numeric, four-digit year), compact (numeric, two-digit year). Context sets a ceiling and space chooses downward from it — a dense table never rises above short, because a column of a thousand rows is read by scanning and prose does not scan; a heading with a line to itself may use full. Every form exists in all three orders. A date is never truncated: when even the tersest form will not fit, the tersest form is still shown, because 16/12/20… hides the year a running balance depends on. Whatever form is displayed must be typeable back into the same field, and a two-digit year means the current century — never the year 25. This applies everywhere a date is displayed, not only in the register.
Should
P2
5.15a Navigation: modes, sidebar, tabs (P12)
The Jul 2026 redesign moved feature areas out of modal launchers and into the
sidebar, which was right and is not reversed here. What it left behind is a
sidebar that is areas and collections at once — thirteen destinations above
a 565-account tree — so the account list is present in Reports, absent the
moment you type in the filter, and pinned to the window's bottom edge. Design,
alternatives and the guidance behind each decision: navigation-design.md.
ID
Requirement
Pri
Phase
FR-NAV-01
Modes. The app is organised into modes — Overview, Accounts, Investments, Reports, Business, Planning, Records — each with its own sidebar collection, its own open tabs, and its own selection, all preserved while the user is elsewhere.
Must
P12
FR-NAV-02
The mode selector lives in the window toolbar and is visible at all times. Not a split-view rail: a sidebar is expected to be hideable, and a mode indicator that disappears with it defeats its purpose. One item per mode, never allowed to reach the system's overflow menu; the name under the symbol, and the symbol alone at widths too narrow to hold the words (ModeLabelFit). (This asked for "one segmented control" until Aug 2026. The build is deliberately not one: the system's own toolbar customisation — required by FR-NAV-03, so a mode can be added or removed — works on toolbar items, and a single segmented control is one item that cannot be split. The requirement was describing an implementation, and the implementation it described could not satisfy the requirement beside it.)
Must
P12
FR-NAV-03
Five modes on the toolbar by default (Overview · Accounts · Investments · Reports · Business); the set is customisable through the system's own toolbar customisation. Every mode is always in the View menu with ⌘1…⌘n, so a mode absent from the toolbar is never unreachable. Modes are never hidden or disabled because their content is empty.
Must
P12
FR-NAV-04
One sidebar per mode, showing one kind of thing, two levels deep: a section heading over its instances. Accounts shows favourites and the account tree; Investments securities and portfolios; Reports standard/custom/favourites; Planning budgets, goals and scheduled; Business its customers, vendors and invoices; Records its rules, emergency records and audit log. No mode's sidebar is a list of commands.
Must
P12
FR-NAV-05
A tabbed interface within the detail pane — several open items sharing one mode sidebar. Not macOS window tabs, which carry a whole window each and would duplicate the sidebar per tab.
Must
P12
FR-NAV-06
Opening a tab is deliberate. A single click on a sidebar row replaces the current tab's content; a new tab requires double-click, ⌘-click, a context-menu "Open in New Tab", or the new-tab button. Selecting something already open focuses its existing tab rather than duplicating it. A placeholder account opens nothing — it expands, as it has no register. Each mode's home tab is first and not closeable. The open set is desk state, restored on open, never in the document.
Must
P12
FR-NAV-07
Overview is the launch page and reports across every mode. Every mode must be able to contribute at least one card, and the default board carries one from each — which requires a Business card (receivables/overdue), the one mode that contributes none today.
Must
P12
FR-NAV-08
Overview is a board of views, its sidebar listing views with their cards nested underneath: choosing a view shows that board, choosing a card shows that card full-window. Standard views plus the user's own; a favourite is a saved custom view, not a separate concept.
Must
P12
FR-NAV-09
Selecting changes what you see; drilling in changes where you are. Choosing a view or a card never switches mode. Clicking through a card to its underlying data does, as does an explicit "Open " button on the board. The mode selector must never change except by a gesture that asked to move.
Must
P12
FR-NAV-10
Every card opens full-window with a close button returning to the board, and a card that is not on the current view is reachable from a toolbar list that opens it full-window until closed. Nothing is unreachable for having not fitted.
Must
P12
FR-NAV-11
One period selector, everywhere. A single control governs the Overview board and every mode, and supplies the default period a report opens with — replacing the dashboard's private period and the reports' own.
Should
P12
FR-NAV-12
Sidebar manners, common to every mode. Sections sort by criterion (name, code, balance, type, and for accounts a derived earliest-transaction date, labelled as derived) or by hand, the manual order stored in the account's own kvp so GnuCash ignores it harmlessly. Dragging between siblings reorders; dragging onto a row re-parents — a book edit, undoable, and visually distinct from a reorder while dragging. Dragging is disabled while a criterion sort is active. One context menu serves selected and unselected rows alike.
Features that add consumer-grade planning and guidance on top of the accounting engine — none compromises double-entry integrity; each is a projection or guided workflow over the engine. See the enhancement study.
ID
Requirement
Pri
Phase
FR-PLAN-01
Bill reminders & Financial Calendar: track recurring bills/deposits (over scheduled transactions) with due dates, pay/skip/enter, overdue flags, and a calendar view.
Should
P4
FR-PLAN-02
Cash-flow forecast: project future account balances from scheduled bills/deposits over a horizon.
Should
P4
FR-PLAN-03
What-if scenarios: model a one-off change (large purchase, income change, extra payment) and see the effect on projected cash flow, without altering actual data.
Alerts (Advisor-FYI style): a rules engine raising proactive alerts — bill due, projected low/negative balance, over budget, price target hit, unusual spend — delivered via in-app, notifications, and widgets.
Should
P6
FR-PLAN-06
Payee management + auto-categorization rules: user-editable payee rename and category/account assignment rules, applied on import (complements the Import Matcher). (Realized by the general rules engine — see FR-RULE-01.)
Should
P4
FR-PLAN-07
Portfolio enhancements: watch lists (securities not held), asset-allocation breakdown, and rate-of-return / performance (extends FR-INV-*).
Should
P5
FR-PLAN-08
Home dashboard: an overview surfacing balances, upcoming bills, budget status, net-worth trend, and alerts; drives Home-screen widgets. (Jul 2026 redesign: the dashboard is a non-scrolling tile board — it packs prioritised, content-aware cards into the actual window (columns from width, unit rows from height, stretched flush), drops what doesn't fit, and never scrolls; cards with nothing to say yield their tile; panels are user-hideable.)
Should
P6
FR-PLAN-09
Onboarding / setup assistant: friendly first-run flow to create accounts and a starter chart of accounts (broadens FR-COA-03).
Should
P4
FR-PLAN-10
Debt Reduction Planner: order liabilities, apply extra payments, and compute payoff date/interest saved (snowball & avalanche strategies).
Could
P9
FR-PLAN-11
Lifetime Planner: long-range financial/retirement projection from income, expenses, assets, retirement accounts, taxes, inflation, and life events → projected net worth and goal feasibility over a lifetime.
Could
P9
FR-PLAN-12
Tax estimator & tax-line tagging: estimate liability, project capital gains, tag tax-related categories, track deductions (complements TXF export).
Could
P9
FR-PLAN-13
Insights & comparison reports: spending-by-category trends and period-vs-period comparisons with plain-language summaries.
Could
P9
FR-PLAN-14
Time & mileage tracking for small-business use (extends FR-BUS-*).
Could
P7
FR-PLAN-15
Emergency Records Organizer: a secure area to store key records (insurance, accounts, contacts).
Could
P9
FR-PLAN-16
Financial wellbeing score (Frollo-inspired): an explainable indicator of financial health (savings rate, spending trends, debt ratios, cash buffer) surfaced on the dashboard — transparent, not a black box.
Could
P9
FR-PLAN-17
Financial summary export ("passport") (Frollo-inspired): a curated, user-initiated PDF snapshot of net worth, income, and expenses for sharing (e.g. loan applications).
Automation and organization features that layer onto the engine. See the enhancement study.
ID
Requirement
Pri
Phase
FR-RULE-01
Rules engine: ordered rule groups of rules; each rule has triggers (strict = all / non-strict = any) over transaction fields and actions (set category/budget/tags/description/notes, convert type, link to bill, allocate to a savings goal). Supports a stop-processing flag. (Supersedes FR-PLAN-06.)
Should
P4
FR-RULE-02
Rules run on create/update, on import, and manually; can be applied to historical transactions over a date/account range with a preview before committing.
Should
P5
FR-TAG-01
Tags: cross-cutting labels on transactions (optional date/location), independent of the account/category hierarchy; usable in search and rules.
Should
P2
FR-GOAL-01
Savings goals (piggy banks): divide an asset account's balance into named goals; add/remove money; link transfers so they auto-allocate; group goals; optionally link a goal to a bill.
Should
P5
FR-FIND-01
Operator search language: a query syntax (type:, from:/to:, amount, category:, tag:, date operators with d/w/m/y offsets, notes/attachment operators, negation with -), with saved searches. (As built, the search language lives in the app layer and rule triggers use structured field/operator criteria rather than sharing the textual grammar — recorded Jul 2026.)(Upgrades FR-REG-06.)
Should
P4
FR-FIND-02
Find Account (⌘I): type a few letters and land on the account, however deep it is buried — GnuCash's Find Account, for the keyboard. The sidebar filter covers browsing; this exists because browsing a 565-account tree with the mouse is not a way to reach one you can name.
Should
P2
FR-FIND-03
Bulk operations on search results: the rows a search returns are a selection, and the same transaction operations apply to all of them at once — categorise, reconcile, tag, edit in bulk. A result list you cannot act on is a report, not a search.
Should
P4
FR-BILL-01
Bill matching: bills carry an expected amount/range and interval; transactions auto-match to bills; surface paid / unpaid / overdue status. (Extends FR-PLAN-01.)
Should
P4
FR-BUD-03
Auto-budgets: budgets that auto-replenish each period (fixed or rollover); support a zero-based budgeting workflow. (Extends FR-BUD-*, FR-PLAN-04.)
Could
P4
FR-RULE-03
Default category taxonomy + heuristic auto-categorisation (Frollo-inspired): ship a standard category set and auto-suggest categories/merchant-name cleanup on import, complementing the rules engine and Import Matcher (optional on-device enrichment later).
Should
P4
FR-GOAL-02
Savings challenges (Frollo-inspired): gamified, time-boxed savings challenges layered on savings goals, with in-app prompts/notifications.
Two command-line binaries: the strictly read-only reporter finlens (FR-CLI-01…05) over .finvestlens books, Ledger journals and GnuCash files, and the write-side maintenance tool finlab (FR-CLI-06, lab.md), modelled on Ledger 3's commands, query language and report shapes. Design: ledger-design.md.
ID
Requirement
Pri
Phase
FR-CLI-01
Provide the core reporting commands — balance, register, print, csv, accounts, payees, commodities, prices, pricedb, stats, equity, cleared, source — with ledger's layouts (tree balance with chain elision and grand total, running-total register, canonical print).
Could
P10
FR-CLI-02
Support ledger's query idioms (account regexes with implicit OR, and/or/not, @payee, %tag, #code, =note), smart dates, -b/-e/-p period expressions, state filters, and sort/display options per the core-80 subset in ledger-cli-reference.md.
Could
P10
FR-CLI-03
Operate strictly read-only: open books via a read-only store connection with no lock, no working copy, and no writes — safe against a book the app has open (verified by test).
Must (within P10)
P10
FR-CLI-04
Valuation flags (-V, -X, -B, -H) over the book's price database and journal-implied costs.
Could
P10
FR-CLI-05
An interactive REPL (no-command invocation): sources loaded once, one report command per prompt, with push/pop/reload as in ledger.
Could
P10
FR-CLI-06
Headless maintenance is a separate binary(Aug 2026): any command-line operation that writes to a book — GnuCash import, price refresh, document ingestion, data repair — ships as its own tool (finlab, lab.md), never as a mode of the reporting CLI. This is what makes FR-CLI-03's read-only guarantee unconditional: a tool that cannot write is a different kind of tool from one that can, and the difference is worth a second binary. The maintenance tool drives the same application model the app does, so a headless run exercises the shipping code rather than a parallel implementation free to drift from it.
Could
P10
5.18 On-device intelligence (Apple Intelligence)
An optional layer that runs entirely on-device over Apple's Foundation Models framework — no financial data leaves the device — and never mutates the book without review. The contract is the model proposes; deterministic code disposes: model output is typed, parsed tolerantly, resolved against the real chart of accounts, and arithmetically cross-checked before a reviewed result is applied. Every entry point is availability-gated and degrades gracefully when Apple Intelligence is unavailable. See Architecture §11.
ID
Requirement
Pri
Phase
FR-AI-01
PDF statement import: extract transactions from PDF bank/card statements (text extraction with an OCR fallback for scans) and stage them through the Import Matcher for review; matched register splits may be marked cleared (light reconciliation).
Could
P4
FR-AI-02
Auto-categorisation: propose an account/category for uncategorised transactions after deterministic rules, history, and heuristics — in import review and a dedicated Auto-Categorise panel.
Could
P4
FR-AI-03
Invoice splitting: read an invoice's line items and turn them into categorised splits on a transaction.
Could
P7
FR-AI-04
Dividend statement import: extract dividend components (franked/unfranked amounts, franking/imputation credits) and book them, including gross-up, after review.
Could
P5
FR-AI-05
Budget suggestion: derive per-line budget proposals from deterministic spending statistics for a reviewed, per-line apply.
Could
P4
FR-AI-06
Forecast narration: turn computed cash-flow-forecast facts into a plain-language headline and insights in the Cash Flow report.
Could
P5
FR-AI-07
Smart Import (multi-PDF): classify each dropped PDF and route it — statements to FR-AI-01 review, dividend statements to a verified booking, invoices matched to their transaction, split by line item, and re-dated to the invoice's economic date.
Could
P7
FR-AI-08
Document links — a link, never a copy. A statement, invoice or receipt stays where the user filed it; attaching one records where it is, via GnuCash's assoc_uri slot, round-trippable through GnuCash XML. The path is stored relative to whichever configured document folder contains the file — Settings ▸ Documents takes a primary and a secondary, so receipts and statements can live in separate archives — and absolute only when it is under neither. The one exception is a document that has no original to point at (bytes pasted or scanned in), which is written into the document folder first and then linked the same way. (Corrected Aug 2026: this requirement previously read "copy applied statements/invoices into the document folder", which contradicted the instruction it came from and was built as written — one ingest duplicated 189 receipts onto a NAS beside the book and pointed every link at the duplicate.)
Could
P6
FR-AI-09
Slide narration(Jul 2026): for the presentation decks (FR-RPT-07), turn each slide's deterministic facts pack into an investor-deck action title and a one-to-two-sentence insight. Output is disposed deterministically: every numeric token must round-match a listed figure (raw or k/m-scaled), a listed delta percent, a label numeral, or a calendar year — otherwise the story is rejected and the deterministic title stands. The deck must read fully with Apple Intelligence off.
Could
P6
FR-AI-10
Attachment matching (batch)(Aug 2026): given a folder of receipts and statements, read each one and find the transaction it belongs to — a money leg of the amount read, moving the right way, within a bounded window of the document's own date — then attach the file (FR-AI-08) and categorise it (FR-AI-03). A document is never allowed to invent a transaction to match: one that fits nothing is reported with the amounts and date it was searched on, so the reason is diagnosable. Two documents may not claim one transaction, and a document already linked is skipped rather than re-read.
Could
P7
FR-AI-11
Foreign-currency receipts(Aug 2026): a card charged abroad posts in the book's currency, so a receipt brought home shares no figure with its transaction. Where the issuer records the original amount in the transaction narrative, match on that figure, exactly — no exchange rate, no tolerance, and nothing for the user to tag. Converting at a supplied rate is a fallback only, off by default, and must abstain when more than one transaction in the window fits, since an approximate match on money is worse than none.
Could
P7
FR-AI-12
Cash receipts(Aug 2026): a receipt paid in cash has no transaction to match and never will. Read the tender from the document and, when told which cash account to use, enter the purchase — date and vendor from the receipt, the file attached, the category left to FR-AI-02. The account is never inferred: a receipt records that notes changed hands, not whose, so it is supplied by the user. Detection must be asymmetric — a card marker outranks a cash one, because mistaking a card purchase for cash creates a transaction that double-counts when its statement imports.
Could
P7
6. Non-functional requirements
ID
Requirement
NFR-01 Correctness
Monetary math uses native Decimal (no binary-float error), rounded to each commodity's fraction. No transaction may persist unbalanced (within one minor unit). Round-trip import/export preserves structure/GUIDs/slots losslessly; amounts match within a rounding tolerance (test-enforced).
NFR-02 Performance
Interactions: register/account open under 100 ms, any body pass under 16 ms, and anything over ~300 ms shows determinate progress. Documents: a book of 100k+ postings opens, scrolls and saves without the interface stalling, and a large GnuCash import reports progress throughout. Measured results are recorded in implemented.md. A save is bound by serialisation, not by IO.
NFR-03 Data integrity & safety
Never lose or silently mutate user data; guard destructive actions; keep unrecognised imported data for round-tripping. Local-first; works fully offline.
NFR-04 Platform support
macOS, iPadOS, iOS on a shared codebase. Minimum versions are macOS 26.5 / iPadOS 26.5 / iOS 26.5 — settled by the frameworks the product depends on rather than by a support window: the on-device model (FoundationModels), Vision 26 document reading, and the Swift 6.2 concurrency the packages are written against all require 26. Not every capability need be present on every platform: import/export is desktop-class (macOS/iPadOS); iPhone is open/create/edit only (FR-PLT-06).
No financial data leaves the device except via user-initiated iCloud sync or export. No trades or transfers. Optional local authentication (Face ID / Touch ID) to open a book.
NFR-08 Testability
Engine and import/export covered by unit tests; a synthetic round-trip corpus in CI, plus an env-gated harness (FL_ROUNDTRIP_FILE) run locally against real GnuCash books — real books are gitignored and never enter the repository.
Numeric representation. Amounts use native Decimal (wrapped in Money with a commodity), rounded to the commodity's fraction — not GnuCash's rational gnc_numeric. Every split stores both value (transaction currency) and quantity (account commodity), enabling multi-currency and share accounting.
GUID preservation. Every first-class object retains its GnuCash GUID on import and re-emits it on export. Unrecognised elements and key-value "slots" are preserved to protect round-trip fidelity.
Interchange, not database compatibility. FinvestLens reads/writes the GnuCash XML format only. The GnuCash SQL backends are out of scope (NG1).
8. UX principles (platform-specific)
macOS — Multi-pane document window: accounts sidebar, register main area, inspector; toolbar, keyboard shortcuts; standard document lifecycle. GnuCash's menu set (File, Edit, View, Transaction, Business, Reports, Tools, Windows, Help) maps to a native macOS menu bar; the distinct GnuCash windows (Account Tree, Register/General Journal, Report, Reconcile, Scheduled Transactions) map to panes/sheets/tabs.
iOS — Compact, navigation-stack UI optimized for quick transaction entry and review; widgets and Shortcuts for glanceable balances. Open an existing book or create/edit a new one; import and export (GnuCash, bank files, CSV, PDF) are not offered on iPhone — those flows live on macOS/iPadOS (FR-PLT-06).
Shared — One design language; Dark Mode; SF Symbols; native controls; charts consistent with the project's data-visualization standards.
Plain language (Jul 2026 redesign) — UI strings avoid GnuCash jargon: Repair Book (Check & Repair), Close Financial Year (Period-End Close), Group (placeholder account), Show Details (Double Line), All Transactions (General Ledger), Out of balance (Imbalance, in the editor). Engine names and the GnuCash file format keep GnuCash's vocabulary — the sweep is strings-only.
One feedback surface (Jul 2026 redesign) — long operations (quote fetches, price updates, saves) report progress and completion through a single bottom-of-window status overlay; failures that used to vanish in try? route there too.
Session restoration (Jul 2026 redesign) — where you were (sidebar destination, selected account, dashboard period) survives relaunch, stored per book outside the document: desk state must never dirty the book.
Statements read like statements; working papers read like working papers (Jul 2026 report redesign) — the statement reports present at annual-report standard (face and notes, no raw account paths, accounting rules and typography — FR-RPT-01), while operational reports (transactions, reconciliation, lots) deliberately keep their tabular working-paper form. Presentation arranges verified figures; it never computes new ones.
9. Milestones (mapped to phases)
P0 Foundation — engine types, Decimal-based Money, double-entry invariant, unit tests. (FR-ENG-*)
P10 Ledger CLI & interchange — Ledger 3 journal import/export; the finlens read-only CLI (and, from Aug 2026, the finlab maintenance tool). (FR-XIO-09/10, FR-CLI-*; design: ledger-design.md)
10. Success metrics
Interoperability: ≥99% of objects in a representative corpus of real GnuCash files round-trip without loss; zero balance-integrity failures.
Correctness: 0 unbalanced transactions persisted; engine test coverage ≥90% of core logic.
Performance: a book of 100k+ postings opens and scrolls within the NFR-02 latencies — validated 10 Aug 2026 on a real 46,578-transaction / 103,365-posting book, on local and SMB storage.
Adoption signal (post-launch): migrating GnuCash users can import, use daily, and export back successfully.
11. Risks and open questions
#
Risk / question
Notes
R1
GnuCash XML schema breadth (business objects, slots, SX) is large.
Prioritise core objects; preserve-and-passthrough unknowns to protect round-trip.
R2
Exact-decimal performance at scale.
Closed — the hand-written rational was rejected in favour of Decimal (architecture §5.1). Numbers in implemented.md.
R3
Online quote sources change/rate-limit.
Make quote sources pluggable; degrade gracefully offline.
R4
SQLite safety/locking on network shares (SMB/NFS).
App-level lock file + heartbeat, local working copy, coordinated atomic write-back (Architecture §6); closed by a load test on a real SMB share: direct mode not built (the working copy costs 0 ms locally and saves 11.6× remotely). One residual — finlens still reads across the wire (deferred.md §1).
Q1
Minimum OS versions?
Resolved — macOS/iPadOS/iOS 26.5, set by the frameworks the product depends on (FoundationModels, Vision 26, Swift 6.2), not by a support window. See NFR-04.
Q2
Which GnuCash XML schema version to target for export?
Match current stable GnuCash (v5-era) format.
Q3
Product/trademark naming relative to GnuCash.
FinvestLens is unaffiliated; avoid implying endorsement.
GRDB (SQLite) for the native .finvestlens document store — the single external dependency. Everything else is first-party Apple frameworks or hand-written native Swift (see Architecture §9).
Apple frameworks: SwiftUI + AppKit/UIKit (UI); Foundation (Decimal, NSFileCoordinator/NSFilePresenter/NSFileVersion, URLSession); Swift Charts; App Intents (Shortcuts); UniformTypeIdentifiers (the .finvestlens UTI); Observation; CryptoKit (document fingerprint); Compression (gzip for GnuCash XML); Security (Keychain for quote API keys); LocalAuthentication (optional book lock); and — for the on-device intelligence layer (§5.18) — FoundationModels, Vision, and PDFKit.
A quote-retrieval mechanism for FR-INV-03 / FR-CUR-04 — native pluggable providers over URLSession (no external SDK).
A corpus of real/sample GnuCash XML files for round-trip CI (NFR-08).
14. Traceability
Every requirement carries a stable FR-* / NFR-* ID and a target phase, so implementation tasks, tests, and this PRD stay linked. Round-trip fidelity (FR-EXP-02) and the double-entry invariant (FR-ENG-06) are the two hardest gates and must have dedicated automated tests.
15. Glossary
Term
Meaning
Book
Top-level container for accounts and data (a file).
Account
Typed, named bucket in a hierarchy, denominated in a commodity.
Transaction
Dated event of ≥2 balanced splits.
Split
One leg of a transaction posted to an account (value + quantity).
Commodity
A currency or a security (stock/fund).
Price
A commodity's value in another commodity at a date.
Lot
Grouping of splits for cost-basis / capital-gains tracking.
SCU
Smallest Currency Unit / commodity fraction (e.g. cents = 1/100).
Reconcile state
Split status: not (n), cleared (c), reconciled (y).
Note: This PRD is grounded in the public GnuCash documentation but is an independent specification for FinvestLens. FinvestLens is not affiliated with or endorsed by the GnuCash project.