Minimal personal ledger web app (FastAPI + Jinja2). Records add/withdraw movements in a newline-delimited JSON ledger, supports import/export, settings snapshots, and a protected reset.
Live demo (non-persistent): https://savion.onrender.com — usually starts in ~20 seconds (may take up to 1 minute).
Docker (recommended):
docker compose up --build -dOr build/run manually:
docker build -t savion .
docker run -d --name savion -p 8000:8000 -e DATA_DIR=/data -v savion_data:/data savionPull prebuilt image from Docker Hub:
docker pull rh45one/savion:latest
docker run -d --name savion -p 8000:8000 -e DATA_DIR=/data -v savion_data:/data rh45one/savion:latestDocker Compose (pull from Docker Hub):
version: '3.8'
services:
savion:
image: rh45one/savion:latest
ports:
- '8000:8000'
environment:
- DATA_DIR=/data
volumes:
- savion_data:/data
volumes:
savion_data:Local (development):
pip install fastapi uvicorn jinja2 pydantic python-multipart
uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload-
DATA_DIRenvironment variable (default/data). -
Files stored in
DATA_DIR:ledger.jsonl— newline-delimited JSON ledger.settings.json— app settings snapshot.
-
Languages:
en,es,fr,zh,pt,ja,de,it(selectable in Settings). Translations are inapp/translations.json.
- Setup: fresh initial balance or import a JSONL ledger.
- Movements: add/withdraw with
delta,amount,description,resulting_balance,timestamp, andtags. - Tags: define tag types and colors in Settings; use tags on movements; filter by tags; color-coded chips.
- Non-destructive edits: edit description and tags inline; changes are stored as
editentries. - Soft delete: delete a movement with inline confirm; adds a
deleteentry and removes its effect from balances and listings. - Settings: theme, fade interpolation, currency (EUR/USD/GBP), timezone, language, and tag types. Balance visibility toggle is persisted.
- Themes:
dark,light,dracula,winclassic,system1— with responsive and readable UI across variants. - Summary: monthly summaries with net change and entry count. Each month is expandable to reveal that month’s total Income and total Expenses.
- Recent Movements: filter by text, action, date range, amount range, and tags. Choose display size (50/100/200/500/All).
- Responsive UI: mobile-first layout, accessible keyboard interactions, and compact grids for small screens.
- Export: returns
ledger.jsonlwith asettingssnapshot appended; filenamesavion-ledger-<UTC timestamp>.jsonl. - Import: validates JSONL lines; applies any
settingsentries tosettings.json; import fails if no valid entries found. - Reset: protected by typing
RESETand solving a simple math challenge; logs aresetentry and deletesledger.jsonl. - Health check:
GET /healthzreturns basic status. - Concurrency: file writes guarded by an internal thread lock to avoid concurrent corruption.
- Built-in languages:
en,es,fr,zh,pt,ja,de,it. - Summary totals use translated labels for Income/Expenses.
- Add custom translations in
app/translations.json.
- Balance visibility toggle on Home (eye/eye-off). Hidden mode preserves the currency symbol.
- Numbers use thousands separators consistently.
- Mobile navigation with hamburger menu; accessible focus states; ESC closes editors.
GET /— main UI (requires setup).GET /summary— monthly summary view (expand a month to see totals for income and expenses).GET /setup,POST /setup— initial setup or ledger import.POST /movement— submit a movement (action:add/withdraw,amount, optionaldescription).POST /movement/edit— non-destructive edit ofdescriptionandtagsfor a movement.POST /movement/delete— soft-delete a movement by timestamp.GET /export— download ledger (appendssettingssnapshot before exporting).GET /reset,POST /reset— reset workflow with verification.GET /settings,POST /settings— view and update UI settings.POST /toggle-balance— toggle the persisted “show balance” preference from the home page.GET /healthz— health check.
Notes:
- The home page supports optional query parameters for filtering and display size, e.g.
/?q=groceries&action=add&limit=100. Uselimit=allto show all filtered items.
Each line in ledger.jsonl is a JSON object. Common kind values:
setup—{ "kind":"setup", "timestamp":..., "initial_balance": <number> }movement—{ "kind":"movement", "timestamp":..., "action":"add"|"withdraw", "amount":<number>, "delta":<signed>, "description":"...", "resulting_balance":<number> }edit—{ "kind":"edit", "timestamp":..., "target_ts":..., "new_description":"...", "new_tags":["..."] }delete—{ "kind":"delete", "timestamp":..., "target_ts":... }settings— settings snapshot saved as a ledger entry.reset— record indicating a reset occurred.
Entries are validated when read; malformed lines are ignored.
- Summary: expandable monthly sections now reveal total Income and total Expenses per month.
- Summary and cards: improved mobile responsiveness; all card surfaces now honor theme
--cardfor readability (incl. Windows Classic/System 1). - Themes: added
winclassicandsystem1; improved tinting for status pills across themes. - Recent Movements: inline edit (description/tags) and soft delete with confirmation; keyboard and ESC support.
- Filters: added tag filter; preserved filters when changing display size.
- Internationalization: added translation keys for Summary Income/Expenses across all built-in languages.
- If the app shows the setup page: ensure
ledger.jsonlexists and contains at least one validsetup/movement/resetentry. - Import requires valid JSONL (one JSON object per line). Malformed lines are skipped; import fails if none are valid.
- Permissions: when running in Docker, mount a volume or host directory to
/dataand ensure the container user can read/write it. - Movement list shows 50 items by default for performance. Use the selector at the bottom of the list to change to 100/200/500 or All.
- Main server implementation:
app/main.py(FastAPI). Templates:app/templates/. Static assets:app/static/. - Data model classes:
MovementandSettingsEntry(Pydantic models inapp/main.py). - The app is intentionally dependency-light; add a
requirements.txtfor reproducible installs.
MIT License — see LICENSE for full text.