HouseBook — Family Finance Portal

A self-hosted household finance portal. Local-first, no cloud, no ads, no tracking. Single HTML file backed by a Python HTTP server and a SQLite database.

Getting Started

The portal is three files in one folder:

  1. Open a terminal in the folder containing those three files.
  2. Run python server.py (or python3 server.py on macOS/Linux).
  3. The server prints all reachable IP addresses. Open http://localhost:8765 in your browser, or use a network IP for phone access.
  4. Enter the 4-digit PIN (default: 1478). To change it, create a pin.txt file with your PIN on the first line and restart the server.

For access outside your home network, install Tailscale on both devices. The portal uses relative URLs and works from any hostname.

Important: If you see a "Disconnected" badge or the PIN screen won't proceed, server.py is not running. The badge polls every 15 seconds and updates automatically.

Session behaviour

The PIN sets a server-side session cookie (HttpOnly, 30-day expiry). Sessions are cleared when the server restarts — you will be asked for your PIN again after a restart. A hard browser refresh does not require re-entering the PIN as long as the cookie is valid. If the session expires while a form is open, the PIN overlay appears without destroying your form data — re-enter the PIN and continue.


📊 Dashboard ↑ Top

Summary of household finances for a chosen period. All figures convert to the selected display currency using the FX rates in Configuration.

Period selector

Options: This Month, Full Year, Last Month, Last Quarter, Last 3 Months, Last 6 Months, All Time, Custom Range. Defaults to All Time. Shared with Analytics and Insights.

Account chips

One chip per active account showing its native-currency balance. Drag to reorder (order persisted in DB). Click a chip to jump to that account's transactions. Balances are starting balance + all transactions.

Charts


📈 Analytics ↑ Top

Deeper charts: spending trend, income vs expense over time, top merchants. Period and currency shared globally with Dashboard and Insights. Recalculates from DB.transactions on every visit — always current.


💡 Insights ↑ Top

Automatically derived observations: unusual spending months, largest single expenses, category trends. Recalculates on every visit.


≡ All Transactions ↑ Top

Period filter

Options: This Month, Last Month, Last Quarter, Last 3 Months, Last 6 Months, Full Year (+ year picker), All Time, Custom Range. Defaults to All Time.

Other filters

FilterBehaviour
AccountShows transactions for one account or all
TypeExpense / Income / Transfer / All
CurrencyFilter to a single currency (totals update accordingly)
CategoryDropdown of all categories (Parent › Child). Selecting a parent shows all its sub-category transactions too
SearchLive text search across title, remarks, and category

Totals header

Shows filtered transaction count, total income (green), and total expense (red), all in the selected display currency. Updates on every filter change and when the global display currency is changed.

Actions


+ Add Transaction ↑ Top

All adds are server-first — the transaction is not shown in the list until the server confirms the POST. If the server is unreachable, the add fails with a clear error and the form stays filled.

Fields

FieldNotes
TypeExpense / Income / Transfer. Changing type updates the category dropdown
DateDefaults to today
AccountActive accounts only. Currency auto-derived
CategoryRequired for Expense and Income. Only leaf categories shown — no parent categories. Filtered by type (Expense pickers show Expense categories only)
TitleFree text. As you type (2+ characters), matching titles from past transactions are suggested via autocomplete
AmountPositive number; sign applied by type
RemarksOptional free text
Amount fields only accept digits and a decimal point.

⇧ Import Transactions ↑ Top

Bulk-import transactions from a PDF bank statement or a CSV file. All parsing is done in-browser — no file is sent to any server.

PDF import (Suyool / Neo)

  1. Select account and parser type
  2. Upload PDF (drag-and-drop or browse)
  3. Click Parse & Preview
  4. Review rows — filter by type, check/uncheck
  5. Click ⇧ Import Selected

CSV import

Select CSV File as the source. Required headers (in any order):

date, account, currency, type, category, title, amount, remarks

Click Download Sample CSV for a ready-to-use template. The same preview, dedup check, and Import Selected flow applies to CSV as to PDF.

Duplicate detection

Before import, every row is compared against existing transactions. Matches on date + account + amount + (title or remarks) are skipped. The result card shows the count of skipped duplicates.


△ Monthly Budgets ↑ Top

Track current-month spending against monthly USD limits, sorted biggest-first.


☐ By Category ↑ Top

Expense breakdown by category for a chosen period, sorted by amount descending.


🏠 Accounts ↑ Top

Manage accounts. Click any account name (shown in blue) to view all its transactions (defaults to All Time).

Columns

ColumnNotes
NameClickable — opens that account's transactions with All Time filter
CurrencyNative currency of the account
TypeBank Account, Cash, Credit Card, Wallet, etc.
Starting BalanceMoney that existed before tracking began
Current BalanceStarting balance + sum of all transactions in native currency
DescriptionFree-text note
StatusActive or Archived

Archive vs delete

Editing

Renaming an account cascades to all transactions, recurring items, and income sources. Changing the currency does not update existing transactions (each transaction records the currency at the time it happened).


🏷 Categories ↑ Top

Manages the category hierarchy. Maximum two levels: a parent can have multiple sub-categories; sub-categories cannot have children.

Category types

TypeBadgePurpose
ExpenseExpenseShown in Expense and Transfer pickers; Budget page
IncomeIncomeShown in Income pickers; Household Income category picker
NeutralNeutralFor parent categories containing both Expense and Income sub-categories. Neutral parents are not assignable to transactions directly

Storage standard

Uniqueness rules

Actions


⚙ Configuration ↑ Top

Household Name

Name and subtitle shown in the header. Stored in localStorage (per browser).

Exchange Rates

All rates are units per 1 USD. EUR, AED, and CAD are auto-fetched from exchangerate-api.com on each load — their fields are read-only in the UI. LBP and LOL must be entered manually and are saved to localStorage. Click Save LBP & LOL Rates to persist them. To reset to defaults, clear the values and save again.

PIN

Default: 1478. To change: create pin.txt in the same folder with your new PIN on the first line, then restart the server. The PIN is validated server-side; a session cookie (HttpOnly, 30-day) is issued on success. Sessions are cleared on server restart.

The PIN provides household-level access control on a trusted network. Do not expose the server to the public internet without additional protection.

Backup

Creates a timestamped set of all app files in a backups/ folder. Files included: portal.html, mobile.html, server.py, hhfinance.db, documentation.html, housebook_server.log. Timestamp format: YYYYMMDD-HHMMSS. Recent Backups list shows the 10 most recent sets, sorted by the ISO timestamp inside the manifest JSON.


💰 Household Income ↑ Top

Define recurring expected income streams. This is a planning layer — the transactions table remains the system of record for actual income received.

Fields

FieldNotes
EarnerFather / Mother / Joint / Other
TypeSalary, Bonus, Freelance, Rental, Investment, Gift, Refund, Other
AccountRequired. Destination account. Currency auto-derived from account
CategoryRequired. Income-type leaf categories only
Amount + CurrencyExpected gross; currency is read-only (from account)
FrequencyWeekly / Bi-weekly / Monthly / Quarterly / Annually / One-off
Pay DayDay of month 1–31
ReliabilityGuaranteed / Likely / Variable / Uncertain — drives the summary card split

Link to Repeating Items

Non-One-off sources auto-create a linked Repeating Item. The link is bidirectional — edits to either side sync back. Deleting an income source server-side cascades to the linked repeating item. One-off sources post a single Income transaction immediately with source='income-one-off'.


↻ Repeating Items ↑ Top

Templates for recurring Expense, Income, and Transfer transactions. Transactions are never posted automatically — use Generate Due to review and confirm.

Generate Due flow

  1. Click ⏱ Generate Due — shows all items where next due date ≤ today
  2. Adjust date and amount per item as needed
  3. Check/uncheck items to include or skip
  4. Click Post Selected — transactions created with source='recurring'
Category required: Expense and Income items require a leaf category. Transfer items do not.
Linked income sources: Items created from an income source redirect to the Household Income page when edited. Changes sync back to the income source automatically.


Mobile Version

A lightweight, touch-optimised companion to the portal, served from the same server at /mobile. Designed for quick transaction entry and balance checks on a phone.


Overview ↑ Top

The mobile version is a single HTML file (mobile.html) served by the same server.py at port 8765. Access it at http://<server-ip>:8765/mobile or the short alias /m.

What it shares with the portal

What it does not have


Home ↑ Top

Summary card showing net worth, period metrics, and recent transactions.

Period selector

Chips: This Month, Last Month, Last 3 Months, This Year, All Time. Controls income/expense metrics. Account balances are always all-time cumulative regardless of period.

Balance display

FX rates

On startup: applies localStorage overrides (shared with portal), then fetches live EUR/AED/CAD rates from exchangerate-api.com. LBP uses the portal's manual rate. Dashboard re-renders after live rates arrive.

Recent transactions

10 most recent transactions. Tapping a row opens a read-only detail sheet with all fields. The sheet has an Edit button that opens the edit form (with auth guard).


Transactions ↑ Top

Full scrollable list, rendered in batches of 30 with a "Load more" button.

Filters

FilterBehaviour
SearchLive text search across title, category, remarks
Period chipsThis Month, Last Month, Last 3 Months, This Year, All Time
Type chipsAll, Expense, Income, Transfer

Row actions (swipe left or ⋯ button)


Add / Edit Transaction ↑ Top

Tap the centre + tab. Edit via row action or the detail sheet. All saves are server-first — form stays filled on error.

Fields by type

TypeFields
ExpenseDate, Account, Category (expense leaf only), Title, Amount, Remarks
IncomeDate, Account, Category (income leaf only), Title, Amount, Remarks
TransferDate, From Account, To Account (both alphabetically sorted), Amount deducted, Amount received, Exchange Rate (auto), Title, Remarks

Category picker excludes parent categories and filters by type. Transfer exchange rate is auto-calculated from account currencies; manual override supported via the Rate field.

Validation

Date must be a valid calendar date (e.g. 31 April is rejected). Title, Amount (positive), and Category (for Expense/Income) are required.


Settings ↑ Top

Via the ☰ header button on the Home view.



Architecture

Technical reference for file layout, server API, database schema, and operations.


File Inventory ↑ Top

FilePurpose
portal.htmlSingle-page web UI. All HTML, CSS, JavaScript inline. ~290 KB unminified.
mobile.htmlMobile companion UI. Served at /mobile and /m. Touch-optimised, transaction entry and balance view only.
server.pyPython HTTP server on port 8765 (all interfaces). Auth, DB access, backups, error logging.
hhfinance.dbSQLite 3 database. Eight tables: accounts, categories, transactions, budgets, income_sources, recurring_items, meta, plus SQLite internal tables.
documentation.htmlThis file.
housebook_server.logRotating server log (1 MB × 3 files). Records startup, auth events, DB errors, and JS errors from the browser. Created on first request. Included in backups.
pin.txtOptional. PIN on first line. Absent → default PIN 1478. Restart server after changing.
backups/Auto-created on first backup. Timestamped file sets + manifest JSON per set.

CDN dependencies (requires internet on first load)


Server API ↑ Top

All endpoints respond with JSON. Require a valid session cookie except /api/ping, /api/auth, /api/log, and the HTML/favicon static files.

MethodPathPurpose
GET/api/pingHealth check — no auth; returns {ok:true, ts:...}
POST/api/authValidate PIN; sets session cookie on success
POST/api/logReceive client-side error logs (no auth)
GET/api/transactionsAll transactions (JOINs categories for display value). No row limit.
POST/api/transactionsInsert one or many. Auto-resolves category_id. Returns {ok:true, ids:[...]}
PUT/api/transactions/:idUpdate one transaction. Auto-resolves category_id.
DELETE/api/transactions/:idDelete one transaction
GET/POST/PUT/DELETE/api/accounts[/:name]CRUD accounts. PUT cascades name change to all referencing tables.
POST/api/accounts/reorderSave dashboard display order
GET/POST/PUT/DELETE/api/categories[/:name]CRUD categories. PUT cascades name/parent changes to all tables.
POST/api/categories/:name/reassignBulk-reassign transactions from one category to another (used before delete)
GET/POST/PUT/DELETE/api/income_sources[/:id]CRUD income sources. DELETE cascades to linked recurring item.
GET/POST/PUT/DELETE/api/recurring_items[/:id]CRUD recurring items
GET/POST/DELETE/api/budgets[/:category]Budget envelopes
GET/POST/api/backupList or create backup sets

Error handling

DB-locked errors return HTTP 503 with {"error":"database is locked..."} — client shows "Database busy" toast. Other server errors return HTTP 500. Network errors (server unreachable) are detected by the health poll and by failed fetch() calls; the badge switches to "Disconnected" and all writes are blocked.


Database Schema ↑ Top

SQLite 3. Auto-patched on every startup via idempotent ALTER TABLE statements — no manual migrations ever needed. See schema.sql for a standalone creation script. All timestamps are UTC ISO-8601 with a Z suffix.

accounts

id               INTEGER PRIMARY KEY AUTOINCREMENT
name             TEXT    NOT NULL UNIQUE
currency         TEXT    NOT NULL DEFAULT 'USD'
description      TEXT    DEFAULT NULL          -- NULL when not provided
is_active        INTEGER DEFAULT 1             -- 1 = active, 0 = inactive
starting_balance REAL    DEFAULT NULL
account_type     TEXT    DEFAULT 'Bank Account'
sort_order       INTEGER DEFAULT NULL          -- display order on dashboard
created_at       TEXT    DEFAULT (datetime('now'))
updated_at       TEXT    DEFAULT (datetime('now'))

categories

id         INTEGER PRIMARY KEY AUTOINCREMENT
name       TEXT    NOT NULL               -- leaf name only, not full path
type       TEXT    NOT NULL DEFAULT 'E'  -- 'E' Expense | 'I' Income | 'N' Neutral
parent     TEXT    DEFAULT NULL          -- parent category name; NULL = top-level
parent_id  INTEGER DEFAULT NULL         -- FK → categories.id (denormalised)
created_at TEXT    DEFAULT (datetime('now'))
updated_at TEXT    DEFAULT (datetime('now'))
-- No UNIQUE constraint on name alone — sub-categories under different parents
-- may share a leaf name. Uniqueness is effectively (name, parent).

transactions

id          INTEGER PRIMARY KEY AUTOINCREMENT
date        TEXT    NOT NULL              -- YYYY-MM-DD
account     TEXT    NOT NULL
currency    TEXT    NOT NULL DEFAULT 'USD'
type        TEXT    NOT NULL DEFAULT 'Expense'  -- 'Expense' | 'Income' | 'Transfer'
category    TEXT    DEFAULT NULL         -- 'Parent > Child' for sub-cats, leaf for top-level
category_id INTEGER DEFAULT NULL        -- FK → categories.id; NULL for legacy rows
title       TEXT    NOT NULL
amount      REAL    NOT NULL             -- negative = expense/outward, positive = income/inward
remarks     TEXT    DEFAULT NULL
source      TEXT    DEFAULT 'manualport'
            -- 'manualport'   added manually via portal
            -- 'manualmob'    added manually via mobile
            -- 'recurring'    posted by Generate Due
            -- 'income-one-off' posted when a one-off income source is saved
            -- 'import-neo'   imported from Neo/Bank Audi PDF
            -- 'import-suyool' imported from Suyool PDF
            -- 'import-csv'    imported from CSV file
            -- 'manual'       legacy value (pre-split)
created_at  TEXT    DEFAULT (datetime('now'))
updated_at  TEXT    DEFAULT (datetime('now'))

budgets

id          INTEGER PRIMARY KEY AUTOINCREMENT
category    TEXT    NOT NULL UNIQUE      -- full stored category path
category_id INTEGER DEFAULT NULL        -- FK → categories.id
amount_usd  REAL    NOT NULL DEFAULT 0  -- monthly target in USD
created_at  TEXT    DEFAULT (datetime('now'))
updated_at  TEXT    DEFAULT (datetime('now'))

income_sources

id                INTEGER PRIMARY KEY AUTOINCREMENT
name              TEXT    NOT NULL
earner            TEXT    DEFAULT 'Joint'
source_type       TEXT    DEFAULT 'Salary'
account           TEXT    DEFAULT NULL
amount            REAL    NOT NULL DEFAULT 0
currency          TEXT    NOT NULL DEFAULT 'USD'
frequency         TEXT    DEFAULT 'Monthly'
                  -- 'One-off' | 'Daily' | 'Weekly' | 'Bi-Weekly' | 'Monthly'
                  -- | 'Bi-Monthly' | 'Quarterly' | 'Semi-Annual' | 'Annual'
pay_day           TEXT    DEFAULT NULL   -- day-of-month or description
start_date        TEXT    DEFAULT NULL   -- YYYY-MM-DD
end_date          TEXT    DEFAULT NULL   -- YYYY-MM-DD; NULL = no end
reliability       TEXT    DEFAULT 'Likely'  -- 'Guaranteed' | 'Likely' | 'Variable'
category          TEXT    DEFAULT NULL   -- full stored category path
category_id       INTEGER DEFAULT NULL  -- FK → categories.id
is_active         INTEGER DEFAULT 1
notes             TEXT    DEFAULT NULL
recurring_item_id INTEGER DEFAULT NULL  -- FK → recurring_items.id (auto-linked on save)
created_at        TEXT    DEFAULT (datetime('now'))
updated_at        TEXT    DEFAULT (datetime('now'))

recurring_items

id                  INTEGER PRIMARY KEY AUTOINCREMENT
name                TEXT    NOT NULL
type                TEXT    NOT NULL DEFAULT 'Expense'  -- 'Expense' | 'Income' | 'Transfer'
account             TEXT    DEFAULT NULL
to_account          TEXT    DEFAULT NULL   -- Transfer destination account
category            TEXT    DEFAULT NULL   -- full stored category path
category_id         INTEGER DEFAULT NULL  -- FK → categories.id
amount              REAL    NOT NULL DEFAULT 0
currency            TEXT    NOT NULL DEFAULT 'USD'
frequency           TEXT    DEFAULT 'Monthly'
pay_day             TEXT    DEFAULT NULL
start_date          TEXT    DEFAULT NULL   -- YYYY-MM-DD
end_date            TEXT    DEFAULT NULL   -- YYYY-MM-DD; NULL = no end
last_generated_date TEXT    DEFAULT NULL   -- YYYY-MM-DD of most recent posted transaction
to_amount           REAL    DEFAULT NULL   -- Transfer received amount (when different currency)
linked_income_id    INTEGER DEFAULT NULL  -- FK → income_sources.id
is_active           INTEGER DEFAULT 1
notes               TEXT    DEFAULT NULL
created_at          TEXT    DEFAULT (datetime('now'))
updated_at          TEXT    DEFAULT (datetime('now'))

meta

key   TEXT PRIMARY KEY
value TEXT

-- Known migration keys (all one-time, guarded so they only run once):
-- utc_migration_v1                timestamps shifted from local time to UTC
-- null_blank_remarks_v1           empty-string remarks converted to NULL
-- categories_name_unique_drop_v1  UNIQUE constraint removed from categories.name
-- category_fk_v1                  category_id / parent_id FK columns added and back-filled
-- drop_color_v1                   legacy categories.color column removed
Sign convention: Income and inward transfers are positive; expenses and outward transfers are negative. A Transfer always creates two paired rows with opposite signs. Optional text fields store NULL, not empty string, when not provided.

Running the Portal ↑ Top

Requirements

cd /path/to/housebook
python server.py

The server prints all reachable network IPs on startup. Navigate to http://localhost:8765 and enter your PIN.

Auto-patching

Every startup runs all schema migrations idempotently. New columns are added safely; existing data is never modified. Migration state tracked in the meta table.


Browser Storage ↑ Top

Only three small items in localStorage — everything else is in the DB:

KeyContents
FXManually saved LBP and LOL exchange rates (JSON). EUR/AED/CAD are never written here — always dynamic.
hhName / hhSubHousehold name and subtitle from Configuration

Backup & Restore ↑ Top

Via the portal

Settings → Backup creates a timestamped set in backups/. All five app files are copied. A manifest JSON is written per set for the Recent Backups list.

Manual

cp hhfinance.db backups/hhfinance_$(date +%Y%m%d).db

Restore

Stop the server. Replace hhfinance.db with a backup copy. Restart the server.

Export to CSV

From Transactions, use ⇩ CSV to export all filtered transactions.


Troubleshooting ↑ Top

Badge shows "Disconnected" / PIN screen won't proceed

server.py is not running or not reachable on port 8765. Start or restart it. The badge polls every 15 seconds and will recover automatically.

PIN accepted but data doesn't load

After successful PIN entry the server calls loadData(). If this fails (DB locked, network issue), an error toast appears. Check housebook_server.log for details.

"Database busy" toast

Another process has the SQLite file locked (e.g. DB Browser with the file open). Close the other tool and retry. The portal will not queue or lose your action — just retry after the lock clears.

Server crashes on startup

Import shows "0 transactions parsed"

Check the diagnostic breakdown (dates found vs amounts found). If "no amounts" is high, the PDF text layer may not match the expected format. The preview table also shows per-row skip reasons.

Exchange rates don't update

Saved LBP and LOL values take precedence over defaults. EUR, AED, and CAD always come from the live API fetch and are never overridden.

Category shows blank when editing a transaction

The stored category value doesn't match any option in the dropdown. This can happen if the category was renamed or deleted after the transaction was created. Re-select the correct category and save.




History & Planning

Version changelog and upcoming work.


📝 Changelog

Last updated: 2026-05-29 — current version: 3.6
v3.6 — 2026-05-29
  • Session management: PIN is now shown before a form opens (proactive guard via withAuth()) rather than after submission — form data is never lost due to an expired session
  • Mid-action session renewal: if a 401 occurs while a form is open, the PIN overlay appears and re-authenticates without destroying the form; user sees "Session renewed — please retry your action" and can re-submit immediately
  • Server connectivity: 15-second background health poll (GET /api/ping, no auth required); badge switches to "Disconnected" within 15 seconds of the server going down; all writes blocked with clear message while disconnected
  • DB-locked errors now return HTTP 503 (previously caused a silent socket drop); client shows "Database busy — try again" toast; error logged to housebook_server.log
  • All unhandled server exceptions caught, logged, and returned as JSON 500/503 — no more silent connection drops
  • Parent categories (those with sub-categories) excluded from all transaction category pickers — only leaf sub-categories and standalone top-level categories can be assigned to transactions
  • Category type Neutral added for parent categories that contain both Expense and Income sub-categories
  • Categories type filter includes Neutral option; Neutral badge shown in table
  • Category rename cascade handles all three cases: leaf rename, parent rename (OldParent > * prefix), and parent change (detach/attach/re-parent)
  • Detaching a sub-category cascades all stored Parent > Child values in transactions to plain Child
  • Period filters: All Time is now the default in Dashboard, Analytics, Insights, and Transactions
  • Period filters: This Month added to all screens; wired to Period._relRange
  • Transactions period selector unified with Dashboard/By-Category (This Month, Last Month, Last Quarter, Last 3 Months, Last 6 Months, Full Year, All Time, Custom Range)
  • Transactions: TT.init() now calls filter() — previously bypassed period filter and showed all rows
  • Navigating to Transactions from Accounts or Categories no longer resets the period; defaults to All Time with the clicked account/category pre-filtered
  • Add/Edit Transaction: category_id now included in POST/PUT payloads; server auto-resolves text to id
  • Recurring Items / Income Sources POST and PUT: category_id resolved and stored server-side
  • Edit Transaction: save is server-first — modal stays open and shows error on failure; "Updated!" only shown after server confirms
  • Add Transaction: transaction only added to memory after server POST confirms — no false "saved" appearance when server is down
  • Backup: timestamp separator changed to - (e.g. 20260529-143000); sorted by manifest timestamp_iso
v3.5 — 2026-05-29
  • Category FK migration: category_id integer FK added to all tables; categories.parent_id added (FK to self). Category renames no longer cascade text — FK stays valid automatically
  • Category storage standard: sub-categories always stored as Parent > Child; top-level as plain name. No exceptions
  • GET /api/transactions JOINs categories — category response field is always the computed display value
  • UNIQUE constraint on categories.name removed; sub-categories under different parents may share a leaf name
  • Category delete: transaction count matched against full stored path; reassign dropdown uses stored values
  • Fix Missing Categories dropdown: deduplicated using catStoredValue()
  • /api/transactions: no row limit; fetches all rows by default
  • /favicon.ico served without auth
v3.2–3.4 — 2026-05-26
  • Server + Portal: rotating error log (housebook_server.log); front-end JS errors forwarded via POST /api/log
  • Categories: sortable headers; Delete with reassign; count badge; click name → filter transactions
  • Transactions: category filter uses fCat dropdown, not text search; prefix match for parent clicks
  • Budgets: fixed silent save failure for special-character category names; migrated to DB
  • Repeating Items ↔ Household Income: bidirectional sync; delete cascades server-side
  • Household Income: Account field mandatory; currency auto-derived from account
  • Title autocomplete: past transaction titles suggested as you type
  • Refresh button: spinning animation + green ✓ on success
  • Display currency: dynamically built from USD, EUR, LBP plus any active account currencies — changing it updates all totals immediately
  • Import: Neo Bank Audi parser added; source tags corrected; blank remarks stored as NULL
  • Import: renamed to "Import Transactions"; CSV import added with sample template download
  • PIN: server-enforced session cookie (HttpOnly, 30-day); default PIN 1478
  • Backup: housebook_server.log included in backup set
v3.0–3.1 — 2026-05-19
  • New Repeating Items and Household Income pages
  • Budgets migrated from localStorage to SQLite
  • Dashboard chip reorder; Backup page; FX rate status labels
  • Analytics period shared globally with Dashboard and Insights

🗺 Roadmap — Phase 1 (Next)

The portal is production-ready for personal use as of v3.6. Phase 1 focuses on UX polish to make the existing feature set feel finished. Items are independently shippable.

Quick wins

ItemDescriptionSize
Undo toast5-second window before a delete is committed — tap Undo to cancelS
Keyboard shortcutsn = new transaction, / = focus search, Esc = close modal, arrow keys in paginationS
Loading skeletonsPlaceholder rows during data fetch instead of blank screensS
Empty statesHelpful CTAs when a list is empty ("No transactions yet — add one or import")S
Bulk selectCheckbox column in Transactions → bulk delete / bulk re-categoriseS
Sticky table headersHeaders stay visible while scrolling long transaction listsS
Quick filter chips"This month", "Last 7 days", "Unreviewed" chips instead of always opening the date pickerS
Duplicate detectionWarn before saving if a similar transaction exists within 3 daysS

Information design

ItemDescriptionSize
Dashboard v2Net worth trend line, cash flow waterfall, top spending merchantsM
Trend indicators"Groceries this month: $420 ▲12% vs avg" on every category rowM
Smart category suggestionsFuzzy match + frequency-based ranking as you type a transaction titleM
Per-account drill-downAccount page with its own mini-dashboard, not just a filtered transaction listM

Performance

ItemDescriptionSize
Virtual scrollingRender only visible rows in the transactions table (currently all ~26k rows rendered)M
IndexedDB cacheCache DB.transactions locally so reloads are instantM
Lazy-load chartsDefer Chart.js (200KB+) until Analytics is first openedS
Debounce searchFilter only after 200ms pause instead of on every keystrokeS
Phase 2 (after Phase 1): Envelope budgeting — named buckets with monthly allocations, rollover rules, savings goals, and spending velocity indicators. This is the killer household feature.


HouseBook — local-first, no cloud, no ads, no tracking.
Version 3.6 — updated 2026-05-29.