Skip to main content
Version: v5

Release notes

This page carries the hand-written account of each release, opening at 5.0 and never looking further back: the headline changes, and the heads-up items that actually matter when you upgrade. The exhaustive, commit-level list — every dependency bump included — is generated by Release Please in CHANGELOG.md.

The version 4 history is not merged in here. It stays with the version 4 documentation, where the releases it announces can still be run.

5.0 — One embedded store, one interface

Breaking release

Version 5 changes where your data lives, and there is no upgrade path from version 4. Crossing is a deliberate move you make once, described end to end in Coming from version 4 — including the reconciliation of the figures you were reading, and how to go back.

Your portfolio is a dated event ledger, and only that

What you bought, sold, received and paid in is the whole of what you declare. Positions, prices, the weighted average cost, the performance series — everything else is derived from that ledger and recomputed from it. There is no second place where a figure is stated, and no file to keep in step with another.

A first event can simply be typed. A broker export is handed over from the page or by curl, read once, and never looked at again: the file is a gesture, not a mount, so a row that came out of a spreadsheet is corrected and deleted like any other, and nothing anywhere asks a row where it came from.

Import your events

One embedded store replaces InfluxDB and Grafana

Version 4 ran three containers and asked you to hold a token that two of them had to agree on. Version 5 runs one container with one process, and everything it knows lives in a single embedded DuckDB file under /data.

There is no datastore to provision, no token to rotate, no datasource to point at anything, and no query language between you and your figures.

The app has its own interface

Grafana is gone, and with it the dashboards you were expected to import. The five sections — Dashboard, Shares, Accounts, Ledger and Settings — are served by the app itself on one address, and they explain their own conventions: the weighted average cost, the carrying price, and the four terms the gain is made of are defined on the figure rather than in a page you have to go and find.

There is one interface and one socket. The Prometheus exporter and its port are gone: /api is the front's interface rather than a contract held for anybody else.

Read your figures

Settings live in the store, and are answered in the app

settings.yaml and config.yaml are not read any more, and no environment variable declares a portfolio. Accounts are created in the app, the reporting currency is asked once, and the answers are stored beside the events they apply to.

Settings

It runs without Docker

The app no longer forks: the web interface and the scheduler share one process that is started directly. That is what makes uv run python -m application.boot a supported way to run it — on Linux and on macOS alike, where the previous architecture segfaulted the moment a price was fetched.

Install without Docker

Heads-up when upgrading

Nothing is migrated, and nothing needs to be. Version 4's InfluxDB history stays in InfluxDB; version 5 starts from an empty store and rebuilds the price history from Yahoo! Finance back to your first purchase. What you carry across is your events, and the reconciliation table says which figures are expected to coincide afterwards and which are not.

Both containers keep running until you stop them. Version 5 is a different image with different mounts and a different port; it does not touch version 4's volumes, and version 4 goes on working while you look around. Removing the old volumes is a separate, deliberate step, and Coming from version 4 holds the commands.

There is one mount, and it is /data. The config directory, the events directory and the read-only bind mounts of version 4 have no equivalent: a file is handed to the app through the import, not through the filesystem.

Nothing listens on 3000 or 8181 any more. One port is published, and it serves the interface, the API and the health probe. A reverse proxy, a firewall rule or a bookmark written against Grafana or InfluxDB has nothing behind it.

An external Prometheus scrape stops working. /metrics is gone rather than deprecated. If you were alerting on sb_share_price or sb_price_staleness, those series end here.

The restart policy belongs to your docker run command. Nothing inside the container respawns anything, so a process that dies takes the container with it and --restart unless-stopped is what decides what happens next.

The ledger refuses what it cannot compute with. A quantity, a price, an amount or a fee that is not a finite number is refused at the door, and so is a negative fee on a granted line or a dividend — a fee is money leaving, and a negative one conjures cash out of nothing and a negative cost basis with it. The refusal names the row and the column, so an event file that another tool accepted may be refused here, and correcting the cell is the whole of the fix.

That refusal reads the whole ledger, not only the new line. The check runs over every stored event each time the app rebuilds its picture, which is what makes it a property of the ledger rather than of the import — so a store written by an earlier build of version 5, holding a value this release refuses, stops at startup instead of serving figures computed from it. Only a preview install can be in that state: 5.0 is the first release of this major. Export the events, correct the offending cell, and import them into a fresh store.