Skip to content
ahmedsaedPublic

About

An expense tracker that keeps its mouth shut.

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

349 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Peace

An expense tracker that keeps its mouth shut.
Peace of mind. Piece of cake.

Every record you make lives in one SQLite file on your phone. No account, no server, no sync, no ads, no analytics — and nothing reaches the network unless you ask it to.

Latest release License Platform

peace.merope.dev · ⬇ Download the APK · Coming soon to Google Play


The records list for a month, grouped by day, with $690 spent, $4,800 of income and $4,110 held in the header The add-record screen: account and category pickers, a note reading Farmer's market, and the calculator keypad showing 42 The budgets screen: Food at 64%, Bills and Installments at 90%, and Transportation $10 over its limit in red
Records
The month is the unit. Every day nets itself.
One-handed entry
Four taps and a thumb.
Budgets
Limits it works out for you.
The analysis screen: a large ring of spending by category with an outer ring breaking each category into its sub-categories, a tooltip reading 37.1% Food on the touched slice, and the ranked list below with Groceries, Restaurants and Coffee under Food Search filtered to the Food category, showing three records and their total The accounts screen: cash and bank balances, and a credit card showing what is owed with the credit still available beneath it
Analysis
Where it went, ranked.
Search
It totals what it finds.
Accounts
A card is debt, not an empty wallet.
A long press on a purchase, showing how much of it has been refunded and a tappable row for the refund itself The recurring payments screen listing two standing orders with their schedules and next dates The portfolio screen showing two asset classes, their target split, and how far each has drifted from it
Refunds
Each one knows what it undid.
Recurring
Proposed, never written behind your back.
Portfolio
Rebalancing that never tells you to sell.
Penny comparing September with August: a bar chart of both months' spending, then a written answer that spending fell, with every amount drawn from the ledger and the changes in travel, entertainment, food and shopping listed Penny answering how coffee spending compares month to month with a six-month bar chart and a two-line summary naming the highest and lowest months A photographed café receipt sent to Penny with 'Log this, I paid with the Visa', and the card it proposes: $13.77 on the Visa under Coffee, dated 5 October, with the photo attached and Add and Decline buttons
Ask Penny
Answers worked out from your ledger, never estimated.
Charts on request
Tap a bar to see what is inside it.
Receipts in, records out
Nothing is written until you tap Add.
The record screen listening: the note field has become a microphone with a check button, a running timer and the line Say it your way A bank's text message notification arriving, and beneath it at the top of Records a waiting record: Metro Market, $18.75, from the bank The welcome screen: the Peace wordmark, the line Peace of mind, piece of cake, three promises about privacy, and a Get started button
Just say it
Arabic, English or both in one sentence.
Bank messages
A card alert becomes a record to approve.
Private by default
No account, no tracking, nothing on until you say so.

The first nine are generated by npm run shots, not taken by hand, so they cannot quietly drift out of date — the flow asserts the totals it is about to photograph, so a wrong screen fails rather than being captured. The ledger is invented, in US dollars with no country's shops in it, because the same images are the website and the Play listing. The Penny row and the voice screenshot come from npm run shots:penny, which talks to the real Gemini API, so its answers are read before they are committed.

What it's for

Peace is built around the month, and around the thing you do fifty times a month: type an amount.

The pickers sit at the top and a calculator keypad at the bottom, so the bottom-right key walks the whole record — account, category, save — and a coffee takes four taps with one thumb. = finishes a sum first if you were adding things up, and the key's label always says which of the three it is about to do. It works in split-screen too, with a bank notification open beside it, because that is where records actually get logged.

Everything else in the app exists to make that ledger tell the truth afterwards. Transfers appear once instead of twice. A refund nets against the thing it reversed rather than inflating both sides of the month. A credit card reads as money owed rather than an emptied wallet. Percentages on the analysis ring add up to exactly 100. Search shows the total of every match, not of the rows that fit on screen.

And it stays on your phone. That is the part most of the alternatives get wrong.

Features

Recording

  • Calculator keypad — type 120 + 35 and save the answer
  • Two-tier categories, so Coffee rolls up into Food everywhere
  • Transfers between your own accounts, written as one atomic pair
  • Refunds that net against the purchase they reverse — long-press it and the account and category come with it
  • Multi-currency, with the rate stored per record: a purchase made when the dollar was fifty pounds stays at fifty forever
  • Credit-limit, foreign-transaction and cash-withdrawal fees, per card
  • Split-screen is a supported layout, not an edge case

Making sense of it

  • Records grouped by day, each day netted, with the month's expense, income and running position in the header
  • A category ring and a ranked breakdown of where the money went — filterable by tag, so "where did the kitchen money go" is still a ring
  • Budgets per category, suggested from what you actually spend — no blank screen asking for nineteen numbers on day one
  • Tags — the axis categories cannot be: many per record, crossing categories, and archivable when the project they name is over
  • Free-text search across notes, categories, accounts and tags, with filters and a total
  • Accounts, with balances derived from the ledger rather than stored
  • Balance corrections for when the bank disagrees — they move your position without ever counting as spending

Less typing (optional, off until you add your own API key)

  • Photograph a receipt and have it read into the form — shop, total, and the basket line by line
  • Bank notifications captured and offered as records to approve, never written behind your back
  • Cash out of an ATM is recognised as a transfer, not an expense, so it isn't counted twice
  • The finance half of each prompt is yours to edit — "the card ending 0042 is Kenana" resolves against your real accounts

Penny, the assistant (optional, off until you switch it on, on your own Gemini key)

  • Ask in plain words — "How much did fuel cost me in December?", "How does my coffee compare month to month?" — and get answers worked out from your ledger, never estimated
  • Charts it draws from the same totals every screen uses; tap a bar to see the records inside it, save it as an image or a CSV
  • Reports as PDFs: a verdict, takeaways, the change from last period, and charts — saved to a folder you pick
  • Hand it a receipt photo or a PDF invoice and ask about it, or have it logged with the file attached
  • It can add, edit and delete records, categories, tags, accounts, budgets and recurring payments, refund a purchase and reverse a transfer — each change is a card you approve first, and deletes ask twice and can be undone
  • Every amount it mentions is cited from your data and drawn by the app, so hiding amounts hides its answers too

Keeping it

  • Receipts and PDF invoices attached to records, content-addressed and carried inside the backup
  • Backups are a plain zip: peace.db beside an attachments/ folder — unzip it anywhere and your data is there without this app
  • Undo last restore, because a backup you cannot get back out of is only half a safety net
  • Optional Google Drive backup, sealed with a passphrase, where everything needed to open the file travels inside it
  • CSV export that carries both legs of a transfer, so per-account balances in the file add up

Also in the drawer

  • Recurring payments — set while entering the record, then proposed each month for you to approve, edit or dismiss
  • Portfolio rebalancing, deliberately not wired into the ledger, which never tells you to sell
  • An optional nightly reminder that names what is actually waiting — bank messages to read, repeats due — and by default stays quiet on days you have already recorded something — switchable, if you would rather be asked every day regardless. Scheduled on the phone by Android; no push service, no server, nothing leaves the device

Private by default

The whole app makes four outbound calls, and there are no others. Two stay off until you switch them on, one happens only when you tap a button, and one is a once-a-day version check.

Talks to When Default
api.frankfurter.dev you tap to fetch an exchange rate never load-bearing — the field stays manual if it fails
generativelanguage.googleapis.com reading a receipt or a bank message, and asking Penny — which sends your question and what it reads from the ledger to answer it off — needs your own Gemini key, and Penny has its own switch
googleapis.com/drive backup to Drive's private app folder off — needs you to connect it
api.github.com checking once a day whether a newer build exists one quiet line in the drawer, nothing when it fails

A few things that follow from taking that seriously:

  • One tap hides every amount. The eye in the header masks each figure on every screen, for reading your ledger with someone beside you. It persists, so the eye stays in the header as the way back. Shoulder privacy only — it does not touch screenshots or the recents thumbnail.
  • No account and no server. There is nothing to sign up for and nothing to be locked out of.
  • Bank alerts are read, not your inbox. Android hands over the notification your messaging app already showed — the same text you read off a lock screen. No READ_SMS, no conversation history. The sender list starts empty and reads nothing until you name a bank.
  • The microphone is blocked in the manifest. RECORD_AUDIO is on the app's blocked list, so the permission cannot be requested even by accident.
  • Your Gemini key lives in the device keystore, never in the settings table — that table travels inside every backup.
  • Disconnecting Drive signs out, it never revokes. Revoking would make Google delete the app folder, so "pause backups" would silently destroy every backup you had.

Install

Google Play is coming, starting with a closed test; the listing is drafted in store/. The Play build installs as dev.merope.peace and Play keeps it updated.

Or download the latest APK from the Releases page. Every merge to main publishes a signed arm64 build there, and it upgrades an existing install in place — no uninstall, no data loss. It installs as Peace OSS (dev.merope.peace.oss), beside a Play install rather than over it, and checks GitHub for updates itself. Moving between the two is backup and restore.

Warning

Play Protect blocks this APK, and will keep blocking it. Peace declares a NotificationListenerService to read bank alerts, and Google automatically blocks sideloaded apps that use notification access. The dialog has no "Install anyway", and it appears on every update rather than only the first.

Two ways through, both on the phone's side:

  • adb install -r peace-<version>-build<n>.apk — a different install path, not subject to it
  • Pause Play Protect (Play Store → profile → Play Protect → settings), install, switch it back on
Why there is no manifest fix for this

The declaration is the capability — Google classes notification access alongside READ_SMS and accessibility services, so no amount of manifest tuning avoids the block while the feature exists. The real escape is distributing through Play itself, and an internal testing track would do it: notification access is governed by the ordinary "necessary for core functionality" policy, not the near-total ban that applies to READ_SMS. Keeping that door open is exactly why Peace reads notifications rather than the inbox.

Settings → About shows the exact version, build number and commit, so "which build am I running" is answerable from the phone. A build made from uncommitted code is marked -dirty.

Deliberately not doing

A short list, because what an app refuses to do is as much a description of it as what it does.

Multi-device sync The ledger stays on the phone. Sync means a server, an account, and a copy of your spending somewhere you don't control.
Budget carry-over An unspent limit rolling into next month is a limit that gets easier every time you fail to use it. What carries between months is money — the header's Now does that instead, and reconciles exactly with the Accounts total.
Voice entry Dropped rather than deferred. Photographing a receipt answers the same question better, and a second capture path would be a second set of parsing rules to keep honest.
One cross-currency total Valuing a whole balance needs today's rate, and the only honest rates here belong to past records. Accounts shows one line per currency held instead of one authoritative-looking number.
Settings that do nothing A control for a preference nothing reads is a switch that silently lies. Each row ships in the same change as the code that honours it.
The Play Store — for now Sideloading is what lets Peace read bank notifications without a restricted-permission review. An internal testing track stays possible; a public listing would mean asking Google's permission to keep the feature.

There are 27 more decisions like these, each with the reasoning and usually the bug that caused it, in docs/decisions.md.

Under the hood

Expo SDK 57 · React Native 0.86 · React 19.2 · TypeScript · expo-router · NativeWind · Drizzle ORM + expo-sqlite · react-native-svg · fflate · Zustand · Jest · Maestro

992 unit tests and 20 Maestro end-to-end flows. No chart library and no UI kit — every icon and the category ring are hand-drawn SVG paths, which is how the APK avoided 11.7 MB of Skia for a donut. The one piece of native code is a local Expo module in modules/notification-listener/: a NotificationListenerService and about four functions.

Money is stored as integer minor units, everywhere, converted only at the render boundary. Balances are derived, never stored. All writes go through src/db/repo/, which enforces what SQLite cannot — category depth, atomic transfer pairs, and which side of the ledger a row is on.

Building it yourself

npm install
npm test              # 992 unit tests, no device needed
npm run typecheck
npm run emu           # boot the headless Android emulator
npm start             # Metro dev server
npm run apk           # signed release APK (arm64; apk:emu for x86_64)

The toolchain installs entirely under $HOME — no sudo, no system packages. Read AGENTS.md before any native build: it has the environment, the verification workflow, and the memory constraints that will OOM the machine if you get the order wrong.

Missing signing credentials fall back to debug signing rather than failing, so a fresh clone builds a working APK. It just is not upgrade-compatible with the released one.

Expo has changed. SDK 57 and RN 0.86 are newer than most training data and most tutorials. Read the versioned docs rather than writing APIs from memory.

Documentation

Document What it holds
docs/decisions.md Why Peace behaves the way it does — 27 decisions and the reasoning behind each
AGENTS.md Toolchain, verification workflow, environment constraints and the OOM traps
docs/roadmap.md The six stages it was built in, and what shipped differently from the plan
docs/design-system.md The "Ledger" palette, its rules, the app icon, and split-screen density
docs/research/mymoney.md The reference app whose daily feel Peace learned from
docs/research/credit-cards.md Why card accounts drift, and the fix

License

Copyright © 2026 Ahmed Saed. Licensed under the GNU General Public License v3.0.

You can use Peace, read it, change it, and share it — including commercially. The one condition is the point of choosing this licence: if you distribute Peace or anything built from it, you have to release your source under the GPL too. Improvements come back rather than disappearing into someone's closed-source fork.

The name "Peace" and the app icon are not covered by that licence and remain reserved. Fork the code freely; ship it under your own name. This is the same arrangement Mozilla uses for Firefox, and it exists so nobody can put a lookalike on a store and have it taken for this one.

Contributions are welcome and land under the same licence.

About

An expense tracker that keeps its mouth shut.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages