# Finance Ledger > πŸ‡¬πŸ‡§ English Β· [πŸ‡©πŸ‡ͺ Deutsch](https://github.com/johannes-kaindl/finance-ledger/blob/main/README.de.md) An Obsidian plugin that renders hledger journals as filterable tables with balance and category dashboards, transaction triage and categorizer-rule management β€” fed by a companion Python importer. [![License: AGPL-3.0](https://img.shields.io/badge/license-AGPL--3.0-blue.svg)](https://github.com/johannes-kaindl/finance-ledger/blob/main/LICENSE) [![Docs: CC BY-SA 4.0](https://img.shields.io/badge/docs-CC%20BY--SA%204.0-lightgrey.svg)](https://github.com/johannes-kaindl/finance-ledger/blob/main/LICENSE-DOCS) [![Release](https://img.shields.io/github/v/release/johannes-kaindl/finance-ledger?label=release)](https://github.com/johannes-kaindl/finance-ledger/releases) ![Platform](https://img.shields.io/badge/platform-Obsidian%20%7C%20Desktop%2BMobile-lightgrey) Obsidian with an account note open on the left and the Finance hub on the right: a filterable transaction table with date, payee, account chips, amounts and tags. > Plugin ID: `finance-ledger` (until 2026-06-10: `finance`). ## Status **As of 2026-08-17 β€” importer port, stages E0–E3:** the built-in import now writes `journal.ledger`, `accounts.ledger`, `opening_balances.ledger` **and** the account and contract notes β€” without Python. It only touches what it produced itself: `anfangssaldo_eur`, `created`, foreign frontmatter fields and everything below the `AUTO-GENERATED` marker survive every run. Verified by `npm run smoke:gui` against a running Obsidian (21/21; control run with the patch path removed: 15/20). Reports and dimension notes (E4–E7) still come from the Python importer, run from a terminal. **As of 2026-10-03 β€” the subprocess is gone entirely:** the import dialog, its anti-duplicate preview and the re-import button no longer start the Python importer. The plugin loads no Node module at all (`tests/bundle.test.ts` guards the built `main.js`), which is what the Obsidian store review flags as *Shell Execution* and *Direct Filesystem Access*. **Before that, as of 2026-08-04 β€” stages E0+E1:** CSV import runs **inside the plugin itself** (TypeScript, no Python subprocess). Verified byte-for-byte against the Python importer on real data: `journal.ledger` identical (1,576 transactions, 12 CSVs). Reproducible with `npm run parity`. **As of 2026-06-10 (post phase 1 of the publication track):** slices 1–10 plus the F15 design system merged. Mobile readiness (`Platform.isMobile` guards) and the design system (KSP palette + finance tokens + light mode) integrated. **Slice-10 detail-pages layer:** the importer writes 7 additional wikilink axes, 2 new note classes (transaction types + mandates) and a life-area layer into the vault. Plugin code unchanged (tolerant of extended note schemas). **Tests:** 684 green. **Bundle size:** ~165 kB (`main.js`). ## Features ### Views - **Ledger viewer** β€” sortable and filterable table of every transaction in `journal.ledger`, with click-through navigation to category, account and payee notes - **Balance overview** β€” **as-of-aware**: shows `opening balance (as-of date per account) + transactions after that date`. Transactions before the as-of date are filtered out, so the bootstrap workflow never double-counts. Accounts without `anfangssaldo_eur:` get a TBC marker. - **Category overview** β€” hierarchical aggregate of all categories with percentage share - **TBC triage** β€” every `:tbc:` transaction with a one-click action: *assign account, save categorizer rule, remove tag* - **Finance dashboard** β€” five-card overview (balances, TBC backlog, recurring items, top spending categories, upcoming contract payments) ### Actions - **Rebuild journal from CSVs (built-in)** β€” a command that produces `journal.ledger` and `accounts.ledger` inside the plugin: no Python, no `uv`, no subprocess. Works on mobile too. Account configuration comes from a `konten.yaml` in the vault (setting: *accounts file*). - **CSV import modal** β€” add several CSVs, see an anti-duplicate preview per file, then run the import. All of it inside the plugin: no Python, no subprocess, no temporary files outside the vault β€” so it works on mobile too. - **Re-import** β€” re-runs the built-in import with a UI lock and counter reset - **Snapshot before writing rules** β€” a dated copy of the rule folder, kept under grandfather-father-son rotation (the 6 newest of today, plus the oldest of each of the last 7 days, 4 weeks and 6 months) - **Categorizer rule modal** β€” define a new pattern rule with a live match counter and conflict check; writes to `categorizer-rules/` - **Account suggestions** β€” type-ahead built from `accounts.ledger` plus a frontmatter crawl, deduplicated - **Deep-link URI** β€” `obsidian://finance?mode=ledger&filter=…` to drive the filters from outside ### What it looks like Dashboard tab with cards for account balances, recent activity, quick navigation, quick actions and the largest spending categories of the current month. Balances tab: per account the opening balance with its as-of date, the movement since that date and the current balance. Categories tab: spending categories as a hierarchy with amount and percentage share. To-classify tab: four unassigned transactions with amount and a Classify button each, with the total row below. ## How it works The plugin **does not compute from raw data** β€” it reads the hledger journal the importer wrote and turns it into views. The one place where it does compute is the account balance: ### As-of-aware balance logic The plugin parses `opening_balances.ledger` with a small dedicated parser (`src/aggregator/openingBalances.ts`): ```typescript parseOpeningBalances(text: string): Map ``` `computeSaldo(account)` = `opening.amount + Sum(tx where tx.date > opening.standAm)`. Bootstrap workflow: you put the current bank balance and today's date into the account note's frontmatter (`anfangssaldo_eur` + `anfangssaldo_stand_am`). The importer turns that into `opening_balances.ledger`. The plugin then filters out transactions before the as-of date. ## Requirements - **Obsidian 1.8.7 or newer**, desktop and mobile (`isDesktopOnly: false`). - An **hledger journal in the vault** β€” `journal.ledger`, `accounts.ledger` and optionally `opening_balances.ledger`. Without a journal the views show nothing. - For the **built-in** journal rebuild from CSVs: a `konten.yaml` in the vault. No Python, no `uv` β€” this path works on mobile as well. - Only for the **re-import through the companion repository** (reports and note generators): desktop, `uv`, and a checkout of the Python importer. ## Install **Recommended β€” from the community store:** Obsidian β†’ Settings β†’ Community plugins β†’ Browse β†’ search for **Finance Ledger** β†’ Install β†’ Enable. Updates arrive through Obsidian like for any other plugin. **Alternatively β€” via [AnySource Sideloader](https://git.jkaindl.de/jkaindl/anysource-sideloader)**, which installs and updates plugins from any git forge. Subscribe to this catalog once: ``` https://git.jkaindl.de/jkaindl/obsidian-catalog/raw/branch/main/catalog.json ``` Finance Ledger then appears in the sideloader's plugin list and updates like any other plugin β€” no manual copying, and every download is checksum-verified. **By hand**, if you would rather not use either: 1. Download `main.js`, `manifest.json` and `styles.css` from the [latest release](https://github.com/johannes-kaindl/finance-ledger/releases). 2. Copy them into `/.obsidian/plugins/finance-ledger/`. 3. Obsidian β†’ Settings β†’ Community plugins β†’ enable **Finance Ledger**. Updates then have to be repeated by hand β€” the store and the sideloader exist to avoid exactly that. From source: `npm install && npm run build` produces the same files; `npm run deploy` puts them straight into a configured vault (see *Build and deploy*). ## Usage The pie-chart icon in the ribbon opens the dashboard. Everything else lives in the command palette: | Command | View | |---|---| | `Open finance dashboard` | five-card overview: balances, TBC backlog, recurring items, top spending, upcoming contracts | | `Open ledger viewer` | the filterable transaction table | | `Open balance overview` | balance per account, corrected for the as-of date | | `Open category overview` | hierarchical category aggregate | | `Open TBC triage` | the open `:tbc:` transactions with one-click assignment | | `Rebuild journal from CSVs (built-in)` | rebuilds `journal.ledger` and `accounts.ledger` β€” no external process | | `Finance: import CSV` | multi-file upload with deduplication (desktop only) | The usual loop: rebuild or import the journal β†’ work through **TBC triage** (each assignment also writes a categorizer rule, so the same transaction lands by itself next time) β†’ read the dashboard. From outside, the plugin can be driven through `obsidian://finance?mode=ledger&filter=…`. ## Configuration Settings β†’ Community plugins β†’ **Finance Ledger**: The plugin settings: an Amount display section with a live preview showing income in green and expenses in red, a sign-convention dropdown, and three colour-scheme tiles (Classic selected, Monochrome, Inverted), followed by the vault path setting for the finance project folder. - **Amount display**: sign mode (*intuitive*: income +, expenses βˆ’ Β· *accounting*: raw, as in hledger) plus a colour scheme (classic / monochrome / inverted) shown as swatch tiles with a live preview. Cash flow follows the setting, balances stay sign-based; colour follows the account type and is orthogonal to the sign. - Vault-relative paths to ledger, accounts, contracts and categorizer rules - `uv` binary path, with auto-detect fallback - Filter presets (create, edit, delete β€” stored locally) ## Documentation The full documentation lives in [`docs/`](https://github.com/johannes-kaindl/finance-ledger/blob/main/docs/README.md): - **[Getting started](https://github.com/johannes-kaindl/finance-ledger/blob/main/docs/getting-started.md)** β€” from the install to your first balance overview. - **[Troubleshooting](https://github.com/johannes-kaindl/finance-ledger/blob/main/docs/troubleshooting.md)** β€” the exact message you see, what it means, what to do. - **[Design system](https://github.com/johannes-kaindl/finance-ledger/blob/main/docs/design.md)** β€” tokens and where they are wired. ## Design system The plugin uses three layers: - **Obsidian CSS variables** for layout, typography, borders and surfaces (theme-agnostic) - **`--fl-*` tokens** for finance-specific semantics (credit/debit/TBC colours, account-type accents, money display, typed card top borders) - the **KSP signal palette** as the foundation of the `--fl-*` tokens, with light-mode corrections for AA contrast Wired into five views β€” balance overview, finance dashboard, ledger view, TBC triage and category overview. Money values carry `.fl-money` plus a sign colour, account chips a `data-type` outline, cards a `data-card` top border, status dots `.fl-txn-state`. The light-mode bridge dims the signal colours to keep AA contrast. Detailed documentation: [`docs/design.md`](https://github.com/johannes-kaindl/finance-ledger/blob/main/docs/design.md) (high level plus wiring state) and [`docs/design/README.md`](https://github.com/johannes-kaindl/finance-ledger/blob/main/docs/design/README.md) (canonical token files). ## Mobile status - **No desktop-only paths left (as of 2026-10-03):** the CSV import modal, the preview and the re-import all run inside the plugin. The Python subprocess and the Node filesystem access are gone, so the platform guards that went with them are gone too. ⚠️ Not yet verified on a physical phone or tablet β€” the code path is free of Node APIs and covered by tests, but the file picker on mobile has not been exercised. - **Still desktop-bound:** reports and dimension notes (monthly/quarterly/yearly reports, category, payee, transaction-type and mandate notes). Those come from the Python importer, run from a terminal β€” the plugin offers the full command as a copy button. - **Mobile icons:** on iPhone and iPad some placeholder icons are still visible after the last icon fix β€” diagnosis is pending on a desktop with mobile dev tools. ## The companion importer The Python CLI importer that produces the journal is a separate project and is **not published yet**. Its output in the vault lives under `//Ledger/` plus the note folders (German names, as the code expects them) `10-Konten`, `20-VertrΓ€ge`, `30-Sparziele`, `40-Monatsberichte`, `45-Kategorien`, `55-Categorizer-Rules`, `60-EmpfΓ€nger`, `70-Quartalsberichte`, `80-Jahresberichte` and `05-Bases`. Everything the plugin needs beyond that journal β€” the built-in rebuild from CSVs β€” it does on its own. ### Output note schema (marker system) Every note the importer writes (account, report, category, payee, rule) uses a marker pattern that protects user edits: ``` --- frontmatter --- auto section (tables, mermaid charts, bases embeds, cross-references) ## πŸ“Œ Notes user edit zone β€” untouched on re-runs. ``` On top of that: mermaid charts (pie for top categories, bar and xy-line for trends) and collapsible bases embeds inside `[!quote]` callouts. ### Budget layer Monthly reports carry a budget section: target (12-month average) / actual / forecast (linear extrapolation) / difference / traffic light (🟒 below 90 % of target, 🟑 90–110 %, πŸ”΄ above 110 %). A caveat warning appears when the average rests on fewer than 12 months. ## Build and deploy ```bash npm install # once npm test # vitest run (684 green) npm run build # esbuild β†’ main.js (repo root) npm run dev # esbuild --watch (inline sourcemap, no minify) npm run deploy # build + copy manifest.json, main.js, styles.css into a vault ``` `npm run deploy` copies into the target set through an environment variable: ```bash export OBSIDIAN_PLUGIN_DIR="/.obsidian/plugins/finance-ledger" npm run deploy ``` Then in Obsidian: Settings β†’ Community plugins β†’ turn off safe mode β†’ enable **Finance Ledger**. On updates, run `npm run deploy` again and reload the plugin (toggle it off and on, or `Cmd+R`). ## Repository layout | Path | Purpose | |------|---------| | `src/` | TypeScript sources (views, ui, parser, resolver, state, types, utils, aggregator, categorizer rules) | | `src/aggregator/openingBalances.ts` | as-of-aware parser for `opening_balances.ledger` | | `src/aggregator/saldo.ts` | as-of-aware `computeSaldo` | | `tests/` | vitest specs (684 tests green) | | `docs/design/` | canonical design-system source of truth | | `docs/design.md` | high-level design-system explanation | | `styles.css` | plugin styles with tokens and utilities | | `manifest.json` | Obsidian plugin manifest (id: `finance-ledger`) | | `main.js` | esbuild output (committed, as Obsidian plugins require) | | `package.json` | npm scripts | | `esbuild.config.mjs` | bundle config | | `vitest.config.ts` | test config | | `tsconfig.json` | TypeScript config | | `AGENTS.md` | architecture conventions for coding agents | | `CHANGELOG.md` | release notes | ## License - **Code:** AGPL-3.0-or-later ([`LICENSE`](https://github.com/johannes-kaindl/finance-ledger/blob/main/LICENSE)) - **Documentation and prose:** CC BY-SA 4.0 ([`LICENSE-DOCS`](https://github.com/johannes-kaindl/finance-ledger/blob/main/LICENSE-DOCS)) Copyright Β© 2026 Johannes Kaindl.