Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3,145 changes: 1,482 additions & 1,663 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion public/llms.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Beatrax

> A local-first personal finance dashboard. It reads the statement files European banks already export — CAMT.053, MT940, CSV, ICS credit-card PDFs, PayPal — resolves the funding chains between accounts, and shows a whole month in one place. It runs entirely on the user's own machine: no account, no telemetry, no backend. Devices sync peer-to-peer, end-to-end encrypted, through a relay that only ever holds ciphertext.
> A local-first personal finance dashboard. It reads the statement files European banks already export — CAMT.053, MT940, CSV, ICS credit-card PDFs, PayPal — resolves the funding chains between accounts, and shows a whole month in one place. It runs entirely on the user's own machine: no account, no telemetry, no backend. Devices sync peer-to-peer over the local network, end-to-end encrypted; transactions only ever cross the direct connection between two paired devices, never a relay.

Beatrax is source-available under the Hippocratic License 3.0. Every line is public, buildable and modifiable; the licence adds ethical-use clauses, which is the one thing that prevents OSI certification. Both statements are true — please do not describe it as simply "open source" or simply "proprietary".

Expand Down
4 changes: 2 additions & 2 deletions src/i18n/cards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ const featuresEn: CardSet = {
},
sync: {
title: "Your devices, not a server",
body: "Pair by QR or word-code, verify by safety number, sync directly over the LAN. The relay fallback only ever holds ciphertext.",
body: "Pair by QR or word-code, verify by safety number, sync directly over the LAN. Your transactions only ever cross that direct connection.",
},
appLock: {
title: "App-lock and biometrics",
Expand Down Expand Up @@ -174,7 +174,7 @@ const featuresNl: CardSet = {
},
sync: {
title: "Jouw apparaten, geen server",
body: "Koppel via QR of woordcode, verifieer met een veiligheidsnummer, synchroniseer rechtstreeks over je eigen netwerk. De relay krijgt alleen versleutelde tekst te zien.",
body: "Koppel via QR of woordcode, verifieer met een veiligheidsnummer, synchroniseer rechtstreeks over je eigen netwerk. Je transacties gaan alleen over die rechtstreekse verbinding.",
},
appLock: {
title: "App-vergrendeling en biometrie",
Expand Down
6 changes: 3 additions & 3 deletions src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const en = {
items: [
{
title: "It stays on your machine",
body: "The database, the OAuth tokens, the cached receipts — all on your disk. No telemetry, no cloud sync, no account to create. When devices do sync, they sync directly to each other, end-to-end encrypted, through a relay that only ever holds ciphertext.",
body: "The database, the OAuth tokens, the cached receipts — all on your disk. No telemetry, no cloud sync, no account to create. When devices do sync, they sync directly to each other over your own network, end-to-end encrypted. Nothing in the middle carries your transactions.",
},
{
title: "It reads what your bank already gives you",
Expand Down Expand Up @@ -114,7 +114,7 @@ export const en = {
points: [
{
title: "Sync is peer-to-peer",
body: "Devices pair by QR code or a spoken word-code and verify each other by safety number. They talk directly over your LAN when both are awake, and fall back to a relay that only ever stores ciphertext it cannot read.",
body: "Devices pair by QR code or a spoken word-code and verify each other by safety number. Changes then cross directly between them, over your own network. An optional relay exists only so two devices can finish pairing and exchange keys when they cannot reach each other — it never carries a transaction.",
},
{
title: "Encrypted at rest",
Expand Down Expand Up @@ -192,7 +192,7 @@ export const en = {
items: [
{
title: "End-to-end encrypted device sync",
body: "Your devices reconcile directly with each other — signed change log, CRDT merge, QR pairing with safety numbers, LAN-direct with a relay that only ever holds ciphertext. No account, no server.",
body: "Your devices reconcile directly with each other — signed change log, CRDT merge, QR pairing with safety numbers, and changes that only ever cross the direct connection between your devices. No account, no server.",
headline: true,
},
{ title: "Envelope budgeting", body: "Assign every euro, roll balances over, move money between envelopes." },
Expand Down
4 changes: 2 additions & 2 deletions src/i18n/faq.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ export const faqNl: Record<string, { q: string; a: string }> = {
"Will it work with my bank?": { q: "Werkt het met mijn bank?", a: "Exporteert je bank CAMT.053 of MT940 — en dat doen de meeste Europese banken — dan ja. Bij een aantal banken wordt daarnaast de CSV-vorm automatisch herkend. De pagina over banken en formaten laat zien wat geverifieerd is en wat naar verwachting werkt." },
"What happens if I import the same file twice?": { q: "Wat gebeurt er als ik hetzelfde bestand twee keer importeer?", a: "Niets. Elke regel krijgt een vingerafdruk, dus een afschrift dat je al geïmporteerd hebt verandert niets en levert geen dubbele regels op." },
"Can I import my budget from another app?": { q: "Kan ik mijn budget uit een andere app overnemen?", a: "Ja, uit YNAB, nYNAB en Actual Budget. Categorieën, budgethistorie, transacties, splitsingen en verwerkt-status komen mee, en wat niet te koppelen is wordt gemeld in plaats van stilletjes weggelaten." },
"How does sync work without a server?": { q: "Hoe werkt sync zonder server?", a: "Je apparaten praten rechtstreeks met elkaar. Elk apparaat heeft een volledige versleutelde kopie en ze stemmen af via een ondertekend logboek van wijzigingen. Staan ze op hetzelfde netwerk, dan gaat het direct; slaapt er één, dan wachten de wijzigingen in een relay die alleen versleutelde gegevens bewaart die hij niet kan lezen." },
"Can you read my data when it syncs?": { q: "Kunnen jullie mijn gegevens lezen als ze synchroniseren?", a: "Nee. Sync is end-to-end versleuteld tussen apparaten die je zelf gekoppeld en geverifieerd hebt. De relay bewaart versleutelde tekst zonder sleutel, en er is geen Beatrax-account of server die je administratie ooit ziet." },
"How does sync work without a server?": { q: "Hoe werkt sync zonder server?", a: "Je apparaten praten rechtstreeks met elkaar. Elk apparaat heeft een volledige versleutelde kopie en ze stemmen af via een ondertekend logboek van wijzigingen. Dat gebeurt alleen wanneer ze allebei op hetzelfde netwerk zitten; slaapt er één, dan blijven de wijzigingen staan op het apparaat dat ze maakte tot ze elkaar weer treffen. Een relay is optioneel en dient alleen om de koppeling af te ronden en sleutels uit te wisselen — een transactie gaat er nooit doorheen." },
"Can you read my data when it syncs?": { q: "Kunnen jullie mijn gegevens lezen als ze synchroniseren?", a: "Nee. Sync is end-to-end versleuteld tussen apparaten die je zelf gekoppeld en geverifieerd hebt, en er is geen Beatrax-account of server die je administratie ooit ziet. Je transacties gaan ook nooit door een relay. Wat daar wél langskomt is de sleuteloverdracht bij het koppelen, verzegeld voor het ontvangende apparaat, en de koppelberichten zelf, die niet versleuteld zijn: apparaat-id's, publieke sleutels en de naam die je een apparaat gaf." },
"Can two people share a ledger?": { q: "Kunnen twee mensen één administratie delen?", a: "Twee apparaten van dezelfde persoon synchroniseren vandaag al. Een echte gedeelde huishoudomgeving, met aparte gebruikers en rechten, is bewust een toekomstige mijlpaal en geen huidige functie." },
"What if I lose a device?": { q: "Wat als ik een apparaat kwijtraak?", a: "Verwijder het uit je apparatenlijst. Daarmee wordt de gedeelde sleutel geroteerd en opnieuw verpakt voor de apparaten die je houdt, zodat het verwijderde apparaat niets meer kan lezen van wat er daarna verstuurd is." },
"What happens if I lose everything?": { q: "Wat als ik alles kwijtraak?", a: "Dan is de data weg, en niemand kan hem terughalen — precies dezelfde eigenschap die betekent dat niemand hem aan een ander kan geven. Beatrax kan een versleutelde back-up maken en herinnert je daaraan; waar je die bewaart bepaal jij." },
Expand Down
6 changes: 3 additions & 3 deletions src/i18n/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export const nl: Copy = {
items: [
{
title: "Het blijft op je eigen computer",
body: "De database, de OAuth-tokens, de opgeslagen bonnen — allemaal op je eigen schijf. Geen telemetrie, geen cloudsync, geen account om aan te maken. Als apparaten wél synchroniseren, doen ze dat rechtstreeks met elkaar, end-to-end versleuteld, via een relay die alleen versleutelde tekst te zien krijgt.",
body: "De database, de OAuth-tokens, de opgeslagen bonnen — allemaal op je eigen schijf. Geen telemetrie, geen cloudsync, geen account om aan te maken. Als apparaten wél synchroniseren, doen ze dat rechtstreeks met elkaar over je eigen netwerk, end-to-end versleuteld. Er zit niets tussenin dat je transacties draagt.",
},
{
title: "Het leest wat je bank al levert",
Expand Down Expand Up @@ -119,7 +119,7 @@ export const nl: Copy = {
points: [
{
title: "Sync gaat peer-to-peer",
body: "Apparaten koppelen via een QR-code of een uitgesproken woordcode en verifiëren elkaar met een veiligheidsnummer. Ze praten rechtstreeks over je eigen netwerk als ze allebei aan staan, en vallen terug op een relay die alleen versleutelde tekst opslaat die hij niet kan lezen.",
body: "Apparaten koppelen via een QR-code of een uitgesproken woordcode en verifiëren elkaar met een veiligheidsnummer. Daarna gaan wijzigingen rechtstreeks van het ene apparaat naar het andere, over je eigen netwerk. Een optionele relay bestaat alleen om twee apparaten de koppeling te laten afronden en sleutels te laten uitwisselen wanneer ze elkaar niet kunnen bereiken — er gaat nooit een transactie doorheen.",
},
{
title: "Versleuteld op schijf",
Expand Down Expand Up @@ -197,7 +197,7 @@ export const nl: Copy = {
items: [
{
title: "End-to-end versleutelde apparaatsynchronisatie",
body: "Je apparaten stemmen rechtstreeks met elkaar af — ondertekend wijzigingslogboek, CRDT-samenvoeging, koppelen via QR met veiligheidsnummers, direct over je eigen netwerk met een relay die alleen versleutelde tekst bewaart. Geen account, geen server.",
body: "Je apparaten stemmen rechtstreeks met elkaar af — ondertekend wijzigingslogboek, CRDT-samenvoeging, koppelen via QR met veiligheidsnummers, en wijzigingen die alleen over de rechtstreekse verbinding tussen je eigen apparaten gaan. Geen account, geen server.",
headline: true,
},
{ title: "Envelop-budgettering", body: "Wijs elke euro toe, neem saldi mee, verschuif geld tussen enveloppen." },
Expand Down
11 changes: 6 additions & 5 deletions src/i18n/pages-en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ export const pagesEn: Record<string, Doc> = {
list: [
{ title: "Pairing is deliberate", body: "A new device joins by scanning a QR code or typing a word-code, and both ends confirm a matching safety number before anything flows." },
{ title: "Directly over your own network", body: "When two paired devices are awake on the same network they find each other and sync directly. Nothing leaves your house." },
{ title: "The relay only sees ciphertext", body: "When one device is offline, changes wait in a store-and-forward relay. The relay holds encrypted bytes it has no key for, and forgets them once delivered." },
{ title: "A relay is for pairing, not for your ledger", body: "Transactions never pass through a relay. If the other device is asleep, the changes wait on the device that made them until both are awake on the same network. A relay exists so two devices can finish pairing and exchange keys when they cannot reach each other directly — those keys are sealed to the receiving device, but the pairing messages themselves are not encrypted, so whoever runs one sees device identifiers, public keys and the name you gave a device." },
{ title: "Encrypted at rest too", body: "Each device encrypts its own copy with a key derived from your passphrase, released only when you unlock the app." },
{ title: "Removing a device revokes it", body: "Removing a device rotates the shared key and re-wraps it to the devices you kept, so the removed one cannot read anything sent afterwards." },
],
Expand Down Expand Up @@ -514,10 +514,10 @@ export const pagesEn: Record<string, Doc> = {
],
},
{
heading: "Direct when possible, relayed when not",
heading: "Direct always, with a relay only for pairing",
body: [
"Two devices awake on the same network discover each other and sync directly — nothing leaves your home. When one is asleep, changes wait in a store-and-forward relay.",
"The relay is deliberately incapable of being interesting: it holds ciphertext it has no key for, cannot tell what changed, and forgets it once delivered. It is a mailbox, not a database.",
"Two devices awake on the same network discover each other and sync directly — nothing leaves your home. When one is asleep, its changes wait on the device that made them and cross the next time both are awake on the same network. There is no second road: a transaction only ever travels the direct connection between two of your own devices.",
"A relay is optional, and it is one you set up or nominate yourself. It exists for a single job: letting two devices finish pairing and exchange encryption keys when they cannot reach each other directly. It is worth saying plainly what that means it holds. The key handovers are sealed to the receiving device, so whoever runs the relay cannot open them. The pairing messages are not sealed: they carry the two device identifiers, the devices' public keys and the name you gave a device, and the confirming one is signed — readable, but not forgeable or alterable. A relay also sees metadata: sizes, timing, and which device identifiers exchange traffic. What it never sees is a transaction, an amount or a payee.",
],
tone: "accent",
},
Expand Down Expand Up @@ -552,7 +552,7 @@ export const pagesEn: Record<string, Doc> = {
{
heading: "What it defends against",
cards: [
{ title: "A compromised or hostile relay", body: "The sync relay only ever holds ciphertext it has no key for. Operating one grants no read access." },
{ title: "A compromised or hostile relay", body: "Transactions never pass through a relay, so operating one grants no access to your ledger. The key handovers it does carry are sealed to the receiving device, and it cannot insert itself between two devices — the safety number you compare covers the keys." },
{ title: "Someone on your network", body: "Device-to-device sessions are mutually authenticated and forward-secret, so a passive listener learns nothing and cannot replay later." },
{ title: "A stolen laptop", body: "With the app-lock on, sensitive fields are encrypted at rest and the key is only released by your PIN or biometric." },
{ title: "A device you no longer trust", body: "Removing it rotates the group key and re-wraps to the rest, cutting it off from everything sent afterwards." },
Expand All @@ -568,6 +568,7 @@ export const pagesEn: Record<string, Doc> = {
{ title: "A weak passphrase", body: "At-rest encryption derives from what you choose. A guessable passphrase is a guessable key." },
{ title: "Losing everything at once", body: "No devices and no backups means no recovery. Nobody holds a copy — that is the same property that keeps it private." },
{ title: "What your bank or mail provider knows", body: "Beatrax cannot change what they already hold; it only avoids adding another party." },
{ title: "What a relay sees while you pair", body: "Pairing messages are not encrypted. Whoever runs a relay you have nominated can read the two device identifiers, the devices' public keys and the name you gave a device, and can see sizes and timing. No transaction ever crosses it, and the keys it hands over are sealed — but the fact that those two devices paired is not hidden from it." },
],
},
{
Expand Down
Loading
Loading