-
-
Notifications
You must be signed in to change notification settings - Fork 1
Pocket Money
Per-kid virtual pocket-money accounts with configurable interest, saving goals, and an avatar that evolves as the kid saves. Self-hosted, no fintech, no monthly fee. Built as the fifth SurfacePlugin.
Nothing. The plugin is purely local — no external bank API. Parents control everything from /settings/pocket-money.
- Toggle a person on
/settings/peopleto "Is a child". Existing rows default to false. - Open
/settings/pocket-moneyand tap Create next to the kid's name. - Set the APR (default 10%), weekly allowance, allowance day-of-week, interest commit day-of-week, max-eligible balance cap.
- The
/pocket-moneypage now shows up; the kid can add goals, propose spends, and watch their avatar grow.
- Daily cron at 00:30 UTC computes
min(balance, max_eligible) × (apr_bps ÷ 10000) ÷ 365per account, accumulates intopending_interest_cents. Floor-rounded so the system never overpays. - Hourly cron commits the pending amount daily (was weekly in earlier versions — kids and parents felt the long lag as "interest is broken"). A 23h per-account dedup guard prevents double-commit if the cron re-fires inside the same UTC day. Each commit drops a single
interesttransaction and triggers the coin-shower animation on next kid-view load.
The forecast panel on /settings/pocket-money shows projected balance at 1 / 3 / 6 / 12 months at the current APR + allowance, simulated day-by-day with the same math. Use it to dial APR up or down before the kid notices.
Withdrawals reduce the principal that interest is calculated on, but they don't claw back interest the kid has already earned. Concretely:
-
Principal moves immediately. The withdrawal subtracts from
balance_centson commit. The next day's accrual reads the lower balance — the kid earns less from day +1. -
Pending interest is untouched. Interest accrued earlier in the day sits in
pending_interest_centsand still commits at the next daily run. A kid can't game the system by withdrawing right before commit — at most a few cents are at stake. -
Avatar tier is preserved.
lifetime_saved_centsis monotonic — only positive non-adjustment transactions bump it. Withdrawals never touch it, so spending money doesn't visibly demote the avatar. -
The eligibility cap clamps the principal, not the balance. If balance is above
max_balance_eligible_cents, withdrawals down to the cap don't change the interest rate — the kid was already only earning on the eligible portion.
Worked example: €100 balance, 10% APR, €500 cap, daily commit.
- Day 1 accrues ⌊100 × 1000 / (10000 × 365)⌋ = 2¢ → committed at next daily run → balance €100.02.
- Day 2 kid withdraws €30 (after the day-1 commit). Balance drops to €70.02.
- Day 2 accrues ⌊70 × 1000 / 3650000⌋ = 1¢ → committed daily.
Daily commits eliminate the "kid times a withdrawal right before commit" edge case that existed under the old weekly model — at most one day's accrual is ever sitting in pending.
Tier promotes when lifetime_saved_cents (cumulative deposits + interest, NOT affected by withdrawals) crosses these thresholds:
| Stage | Lifetime saved |
|---|---|
| 1 | €0 (start) |
| 2 | €2 |
| 3 | €5 |
| 4 | €15 |
| 5 | €40 |
| 6 | €100 |
| 7 | €300 |
| 8 | €1000 |
Each promotion plays a once-per-event radial-burst animation. Withdrawals don't downlevel — the kid keeps their progress.
Add via /pocket-money → "Add goal". Three image-lookup modes: catalog search (reuses the shopping-item catalog), URL paste, or local upload. One goal is is_primary and drives the kid view's progress bar; the queue auto-promotes on completion. When a goal hits 100%, the kid sees a "🎉 You can buy this!" button → creates a withdrawal request → parent confirms in the inbox at /settings/pocket-money. Confirmation deducts the balance and marks the goal bought.
The plugin ships with five species (dragon, cat, astronaut, plant, wizard) but the catalog is open-ended. Adding a sixth (e.g. "robot", "knight", "pirate") is a three-file change — no DB migration, no code change, no settings UI edit:
-
Drop 8 SVG files into
webapp/public/pocket-money/avatars/named<id>-1.svgthrough<id>-8.svg. Each represents the avatar at one tier (stage 1 = starting, stage 8 = max). Any SVG works; sourcing from a CC-permissive emoji pack like Noto Emoji is the easy path. -
Add an entry to
webapp/src/plugins/pocket-money/catalog/avatars.jsonunder thespeciesarray — copy the existing dragon entry and change theid+srcpaths. -
Add 9 i18n keys to
webapp/messages/en.jsonandde.json:pocketMoney.species.<id>.{label, tier1, tier2, …, tier8}. Keep EN+DE in lockstep (the CI parity check enforces it).
The new species automatically shows up in the create-account picker at /settings/pocket-money, in the kid-view stage caption, in the stages sheet, and is accepted by the API. Existing accounts on other species are untouched.
The set of stages and lifetime-saved thresholds is shared across all species and lives at webapp/src/lib/pocket-money/types.ts (TIER_THRESHOLDS_CENTS). Species must currently have exactly that many stages.
- Cosmetics shop, badges, streak counter — deliberately not in scope (see
docs/superpowers/specs/2026-05-10-pocket-money-plugin-design.md) - Todos
reward_centsintegration (chore → auto-credit) — possible follow-up - Sibling co-op goals
- Multi-currency per family
- Custom parent-uploaded avatar art (catalog SVGs are designer-replaceable per file in
webapp/public/pocket-money/avatars/)
Toggle off at /settings/plugins → Pocket Money. Nav entry, dashboard widget, and settings page disappear. Account data, transactions, goals, withdrawal requests are preserved.
Kinboard on GitHub · Sponsor · Buy me a coffee · Report a bug · MIT-licensed
Getting started
Operations
Integrations
Kiosk hardware
Built-in features
- Dashboard
- Calendar
- Shopping
- Recipes & meal planning
- Tasks & todos
- Notes
- Birthdays
- School schedule
- Smart home & energy
- Screensaver
- Family members
- Devices
- Notifications
- Themes
Plugins (per-family on/off)
Contributing