Client-side cashflow forecasting and visualization
Cashflow Simulator is a zero-backend, client-side SPA that lets you model recurring income and expenses over time. Add events with any frequency (daily through annual), set a date range, and instantly see daily cashflows and running balance in an interactive chart. All data stays in your browser — no signup, no server.
- Recurring events — daily, weekly, monthly, quarterly, semi-annual, annual frequencies
- Interactive chart — bar chart for daily cashflows + stepped line for running balance
- Multi-currency — USD, EUR, GBP, BRL support with per-event currency and portfolio currency
- Fullscreen chart — expand to full screen with ESC to exit
- Inline editing — click any row to edit; add events with the "+" row
- CSV import — bulk import events with automatic date range expansion
- CSV export — download events or filtered results
- localStorage persistence — events and settings survive page reloads
- Dark mode — follows OS preference, toggle in header
- Period presets — 1 month, 3 months, 6 months, 1 year, 3 years, custom
- Zero backend — all simulation runs in the browser; no data leaves your machine
docker compose up --buildOpen http://localhost:8080.
GHCR_OWNER=macedot IMAGE_TAG=latest docker compose uppython3 -m http.server 8080
# Open http://localhost:8080No environment variables required for the app itself — all configuration is in the UI.
| Setting | Default | Description |
|---|---|---|
| Initial balance | 0 |
Starting balance for simulation |
| Portfolio currency | USD |
Display currency (USD, EUR, GBP, BRL) |
| Period preset | 1 year |
Default simulation date range |
Import CSV with columns: name,startDate,endDate,frequency,value,currency
name,startDate,endDate,frequency,value,currency
Salary,2025-01-01,2025-12-31,monthly,5000,USD
Rent,2025-01-01,2025-12-31,monthly,-1500,USDThe currency column is optional — defaults to USD when omitted.
- Node.js 20+
- npm
- Python 3 (for dev server)
npm install
python3 -m http.server 8080
# Open http://localhost:8080npm test # Vitest unit tests
npm run test:watch # Watch mode
npm run test:coverage # With coverage (thresholds: 80%)
npm run test:e2e # Playwright E2E tests
npm run lint # ESLint
npm run typecheck # TypeScript strict mode (JSDoc types)┌──────────────────────────────────────────────┐
│ Browser │
│ ┌──────────────────────────────────────────┐ │
│ │ index.html (Vue 3 SPA, ~1300 lines) │ │
│ │ ┌───────────────┐ ┌─────────────────┐ │ │
│ │ │ Events Table │ │ Chart.js │ │ │
│ │ │ (inline CRUD) │ │ Bar + Line │ │ │
│ │ └───────────────┘ └─────────────────┘ │ │
│ │ ┌────────────────────────────────────┐ │ │
│ │ │ src/cashflow.js │ │ │
│ │ │ runSimulation / event generation │ │ │
│ │ │ Pure functions, zero dependencies │ │ │
│ │ └────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────┘ │
│ localStorage: events + settings │
│ CDN: Vue 3, Chart.js, PapaParse, Tailwind │
└──────────────────────────────────────────────┘
How it works:
- Events — user adds income/expense events with name, dates, frequency, value, and optional currency
- Simulation —
runSimulation()generates one entry per calendar day, applying recurring event occurrences - Chart — Chart.js renders income (green bars), expenses (red bars), and balance (blue stepped line)
- Persistence — events and settings saved to
localStorageon every change - Import/Export — PapaParse handles CSV parsing; export generates CSV from current events or results
- Dark mode — Tailwind 2.x CDN (no
dark:variants) — CSS custom properties +!importantoverrides on.dark
| Service | Base Image | Notes |
|---|---|---|
cashflow |
nginx:alpine-slim |
Static file server, non-root (UID nginx), read-only rootfs. Published to ghcr.io/macedot/cashflow-js |
- Zero backend — no server, no API, no database. All computation and storage is client-side
- Non-root container — runs as
nginxuser inside the container - Read-only rootfs — container filesystem is read-only with tmpfs for nginx runtime
- No-new-privileges — kernel privilege escalation disabled
- Resource limits — 64MB memory, 0.25 CPU
- Privacy — no analytics, no tracking, no cookies beyond localStorage for app data
GitHub Actions runs lint, typecheck, and tests on every push. On published releases (non-prerelease), a Docker image is built and pushed to GHCR with version tag + latest.
This project is licensed under the GNU Affero General Public License v3.0.