The rule that defines the product
On a spreadsheet a contribution looks like money out and a yield looks like money in. That is correct bookkeeping and ruinous reporting: adding those rows to the cash flow inflates income and expense at once, and destroys any sense of what you actually spend and save. So every row falls into one of five buckets, and only two of them feed the cash flow.
export type Bucket =
| "income" // feeds the cash flow, as money in
| "expense" // feeds the cash flow, as money out
| "invest_contrib" // a contribution is not spending
| "invest_withdraw" // a withdrawal is not income
| "invest_yield"; // a yield is not income eitherIt reads your sheet, not a new format
Paste a OneDrive share link and the reader converts it into a download URL, guesses the header row and the columns by keyword, and shows you the first rows to confirm. .xlsx, .csv and .tsv all work; the .xlsx reader is written in-house on top of fflate, so the only real dependency is a zip decoder.
date description amount segment asset
04/09/2026 Mercado R$ 312,45 Mercado
05/09/2026 Aporte Tesouro (R$ 500,00) Investimento Tesouro Selic
10/09/2026 Salário R$ 8.400,00 Renda
# dd/mm/yyyy, "R$ 1.234,56" and parentheses-as-negative are read as they are.
# The filled "asset" column is the strongest signal that a row is an investment.No database, on purpose
Nothing is imported and nothing is stored: add a line in the sheet, click refresh, the dashboard moves. The fetch is cached for 60 seconds and guarded against SSRF, and the only persisted state is a local config file: the link, the tab, the column mapping and the classification rules. Delete it and the app is new again.
Five screens, and charts that cannot go off-palette
A month panel, an investments screen where contributions and yields are the subject rather than noise, a credit-card screen, the full filterable ledger (including the rows the reader could not interpret) and the configuration. The charts are hand-written SVG and never read a hex value: every colour comes from a custom property, so light and dark themes cannot drift apart and no component can escape the palette.
Almost nothing works; the hard part is telling which. Research first, testnet money only.