Repository navigation
v1.2.3 · StartOS package 1.2.3:0
A maintenance release with no new features. The code behind the app was
reorganized (long functions divided into named steps, the web app's big
components split, filler comments and unused code removed) with one rule:
nothing it produces may change. Every figure, database row, Form 8949 field,
report PDF (byte for byte), CSV, import preview, API answer, AI tool output
and error message was compared with 1.2.2-1's on the golden ledger, the
seed ledger, 40 random ledgers in three timezones and about 2,200 bad
inputs; every page and form was recorded before and after. The only
differences are the two fixes below. Recalculation is about 7% faster.
Fixed
- A CSV row with an unknown
fee_usd_typedvalue (anything but yes, no
or blank) crashed the import preview with a server error; it is now a row
error, "Must be yes, no or blank." Found by the new equivalence check's
bad inputs. - Ledger Review's fee check no longer hides errors. Looking for
transfers whose fee value is off the day's price, it passed over a
transfer on any error while getting that day's price, so a broken
database or a bug looked like "nothing to fix". It now passes over only
a day with no price; anything else is reported as an error.
Development
- Code cleanup, no change in behaviour:
the standards are written down indocs/CODE_STYLE.md, andCLAUDE.md
points to them.scripts/equivalence_check.pycompares everything the
app produces (database rows, API answers, Form 8949 field values, report
PDFs byte for byte, CSV files, import previews, MCP tool outputs, the
error message of each of about 2,200 bad inputs) with v1.2.2-1's, and
times recalculation (docs/TESTING.md). - Every Transaction column is in the CSV export or on a list saying why
not (test_csv_export_columns.py), so a new column can't be left out of
the export unnoticed, as three were before 1.2.1. - Comments and docstrings say what each module is for and why the code
does what it does: no file-path headers, banner dividers, history notes
or narration. The Transaction model's column descriptions no longer call
its main fields "legacy". Two pieces of dead code went:main.py's empty
__main__block and an unusedservices.user.delete_user. - Modern type hints (
list[str],dict,X | None) in the app code,
and ruff'sUPrules keep it that way (tests and migrations excepted). - The complete tax report is built one section at a time
(complete_tax_report.py, from one 510-line function), with the paragraph
styles and grid tables it shares with the transaction history in
reports/pdf_layout.py. Its PDFs are byte-identical. - The transaction history and the Form 8949 field mapping are divided
the same way: the history's columns and widths are one table, its PDF
uses the shared layout; a Form 8949 page's checkbox, row fields and line 2
totals are named steps. Same PDFs, CSVs and field values. - The CSV import guide's script keeps its tables as data and builds
one section per method (backend/scripts/generate_csv_instructions_pdf.py,
from one 395-line function); the PDF it writes is byte-identical, and the
committed one is unchanged. - The CSV import checks a row one field at a time (
csv_import.py:
_validate_rowwas 276 lines), with the account rules per type as a
table and each type's own rules in their own function; the template's
sample rows are data. Two branches that could never run are gone.
Checked on 40,728 generated rows: the same errors, warnings, previews and
transactions. - The River import reads each kind of River row in its own step, finds
duplicates in an exact and a rough pass, and the import endpoint checks,
stubs and saves rows in named steps. Same proposals, warnings, matches and
imports (3,000 random files and 3,000 random ledgers compared). - The dashboard's gains (
get_gains_and_losses, 179 lines) add up
realized gains, proceeds, income by source, fees and this year's gain in
one step each. Same figures on every ledger compared, including ones with
sales this year. - Ledger Review, the CSV export, the AI entry dry run, the public price
download, the IRS form filler, the adoption of a pre-migration database
and the AI connector's request andupdate_transactionare divided
into named steps too; the connector's tools, their schemas and outputs
are unchanged. - The ledger engine (
transaction.py) reads as its steps: create and
edit (check, complete, insert or apply, recalculate), the ledger lines per
kind of transaction, FIFO disposal and transfer lot moves, the input checks
in their fixed order and each type's account rules as a table. Two unused
functions are gone. Same lines, lots, disposals, figures and messages on
every ledger and bad input compared; recalculation no slower. - The transaction form (
TransactionForm.tsx, 1,049 lines) keeps the
form's state, saving and deleting; each transaction type's fields, the
shared account and fee fields, the autofill and the save request are their
own files incomponents/transactionForm/. Every form variant renders the
same labels, roles and HTML (e2e/ui-snapshot.e2e.ts). - The Settings page (799 lines) is its sections: Account, Data
Management (with the CSV import's preview) and Backup & Restore are their
own components, next to the ones it already had; the page keeps the one
action at a time and the message line they share. Same page, labels and
messages. - The River import panel (486 lines) keeps the upload and the import;
the preview, one row and its editable cells are their own components, and
the row logic (the accounts a new type implies, the import request) is in
utils/riverImport.tswith a unit test. The two import previews share
one date format and one "server's reason, else…" message helper. Same
preview after every edit a row allows (recorded before and after). - The Dashboard (470 lines) fetches its figures and lays out four
cards, each its own component; the account totals and the gain/loss
colour are inutils/dashboard.tswith a unit test. Same cards, figures
and HTML. - The rest of the frontend over the limit: the transaction form's
mapping to and from the API is one small step per transaction type (the
same results on 40,000 random forms and transactions, compared), the
Transactions page's sorting, paging and day groups are in
utils/transactionList.ts, Privacy & network's "anything changed?" is
one tested function, and the error message for a failed request is read
in two steps (the same message for 20,000 random failures). - Frontend comments and unused code, as in the backend: no file-path
headers, banner dividers, history notes ("NEW", "ADDED", "legacy") or
comments that repeat the code. Gone because nothing used them: three hook
files and four API hooks (src/hooks/keeps the error message),
formatTimestamp, two types, and five dashboard fields the server has
never sent. The built app is byte-identical but for those five fields. - Lint limits each function's size, so the long functions can't grow
back: Python (ruff: complexity 10, 12 branches, 50 statements, 6 returns;
tests and migrations exempt) and the frontend (ESLint: complexity 15 per
function, 400 lines per file; tests exempt). - The StartOS mirror stays current between releases: a docs-only change
tostartos/goes to DigiMonk73/BTCTX-StartOS right away (CLAUDE.md),
since Start9's build there ignores those files. Anything else waits for a
release. - Roadmap: the AI connector will follow the app's version, chosen over
uvx btctx-mcp@latest.
Downloads
- StartOS:
btctx.s9pk, package 1.2.3:0. In StartOS, open Sideload and upload it; installs of earlier versions update in place. - macOS:
BitcoinTX-macOS.dmg(or the.zip). The app is not signed by Apple: the first time, right-click (Control-click) BitcoinTX in Applications and choose Open; on macOS 15 or later, open it once, then go to System Settings > Privacy & Security and click Open Anyway. - Docker:
ghcr.io/digimonk73/btctx-mcp:v1.2.3(amd64 and arm64). - AI connector:
uvx btctx-mcp==1.2.3(PyPI); Settings → Connect an AI Assistant has it ready to paste.