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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: CI

on:
push:
branches: [master]
branches: [master, develop]
pull_request:

concurrency:
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ npm run dev:mock

Der Mock-Modus verwendet ausschließlich anonyme Repository-Daten. Für Google OAuth, Picker, Sheets, PostgreSQL und Vercel Functions gilt die [Produktions-Setup-Anleitung](docs/anleitungen/produktions-setup.md).

Der Integrationsstand auf `develop` ist unter [accura-preview.kiumu.app](https://accura-preview.kiumu.app/) mit derselben anonymen, bereits angemeldeten Mock-Sitzung verfügbar. Pull Requests zielen standardmäßig auf `develop`; `master` bleibt der bewusst freizugebende Produktionsstand.

## Prüfen

```bash
Expand Down
4 changes: 4 additions & 0 deletions agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ Ich bevorzuge einfache, gezielte Lösungen. Unnötige Komplexität muss unbeding
## Git, Linear und externe Aktionen

* Sei extrem vorsichtig mit destruktiven Commands, die nicht ausdrücklich angewiesen wurden.
* `master` ist der Produktionsbranch. `develop` ist der dauerhafte Integrationsbranch und läuft mit anonymen Mock-Daten unter `https://accura-preview.kiumu.app/`.
* Erstelle Arbeitsbranches grundsätzlich von `develop` und richte Pull Requests gegen `develop`, sofern ich nicht ausdrücklich einen Produktionsrelease oder ein anderes Ziel beauftrage.
* Ein Release nach `master` ist ein eigener bewusster Schritt. Merge oder pushe weder `develop` noch `master` ohne ausdrücklichen Auftrag.
* Die Mock-Preview beweist UI-, PWA- und Clientverhalten mit anonymen Daten, aber keine realen Google-, PostgreSQL- oder Vercel-Function-Abläufe.
* Lösche keine Dateien, Daten, Branches oder Konfigurationen, wenn der Auftrag dies nicht eindeutig verlangt.
* Erstelle keine Commits, pushe keine Branches, merge keine Pull Requests und führe keine Deployments durch, sofern ich das nicht ausdrücklich beauftragt habe.
* Bearbeite keine Tasks in Linear und ändere keine anderen externen Systeme, wenn ich nur um Analyse, Prüfung oder einen Vorschlag gebeten habe.
Expand Down
32 changes: 32 additions & 0 deletions build/financeRuntimeMode.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { describe, expect, it } from 'vitest';
import { resolveMockApiEnabled } from './financeRuntimeMode';

describe('resolveMockApiEnabled', () => {
it.each(['VERCEL_ENV', 'VITE_VERCEL_ENV'] as const)('uses anonymous mock data when %s identifies a preview build', (variable) => {
expect(resolveMockApiEnabled({
command: 'build',
environment: { [variable]: 'preview' },
})).toBe(true);
});

it('keeps the explicit local development mock', () => {
expect(resolveMockApiEnabled({
command: 'serve',
environment: { VITE_USE_MOCK_API: 'true' },
})).toBe(true);
});

it('uses the real API for ordinary local development', () => {
expect(resolveMockApiEnabled({ command: 'serve', environment: {} })).toBe(false);
});

it.each(['production', undefined])('cannot enable mocks in a non-preview build through VITE_USE_MOCK_API (%s)', (vercelEnvironment) => {
expect(resolveMockApiEnabled({
command: 'build',
environment: {
VERCEL_ENV: vercelEnvironment,
VITE_USE_MOCK_API: 'true',
},
})).toBe(false);
});
});
20 changes: 20 additions & 0 deletions build/financeRuntimeMode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
type BuildEnvironment = Record<string, string | undefined>;

type ResolveMockApiOptions = {
command: 'build' | 'serve';
environment?: BuildEnvironment;
};

/**
* The decision is compiled into the bundle so production cannot switch data
* sources at runtime and does not ship the anonymous fixture chunk.
*/
export function resolveMockApiEnabled({
command,
environment = process.env,
}: ResolveMockApiOptions): boolean {
if (command === 'build') {
return (environment.VERCEL_ENV ?? environment.VITE_VERCEL_ENV) === 'preview';
}
return environment.VITE_USE_MOCK_API === 'true';
}
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ Diese Seite ist der zentrale Index und damit die Single Source of Truth (SSOT) f

### Entscheidungen, Vorlagen und historische Pfade

- [ADR-Index](entscheidungen/README.md), [0001](entscheidungen/0001-google-sheets-als-datenquelle.md), [0002](entscheidungen/0002-versionierte-domaenengrenze-und-integer-cents.md), [0003](entscheidungen/0003-serverseitiger-google-zugriff-und-drive-file.md), [0004](entscheidungen/0004-single-user-sicherheitsmodell.md), [0005](entscheidungen/0005-last-known-good-und-offline.md), [0006](entscheidungen/0006-provider-selektoren-und-view-model.md), [0007](entscheidungen/0007-vite-pwa-und-vercel-functions.md), [0008](entscheidungen/0008-material-design-und-dynamische-farben.md), [0009](entscheidungen/0009-ereignisgesteuerte-aktualisierung.md), [0010](entscheidungen/0010-gehaltsbezogene-faelligkeitsprojektion.md), [0011](entscheidungen/0011-lokaler-privacy-modus.md), [0012](entscheidungen/0012-app-vorschau-und-lokaler-pin-lock.md)
- [ADR-Index](entscheidungen/README.md), [0001](entscheidungen/0001-google-sheets-als-datenquelle.md), [0002](entscheidungen/0002-versionierte-domaenengrenze-und-integer-cents.md), [0003](entscheidungen/0003-serverseitiger-google-zugriff-und-drive-file.md), [0004](entscheidungen/0004-single-user-sicherheitsmodell.md), [0005](entscheidungen/0005-last-known-good-und-offline.md), [0006](entscheidungen/0006-provider-selektoren-und-view-model.md), [0007](entscheidungen/0007-vite-pwa-und-vercel-functions.md), [0008](entscheidungen/0008-material-design-und-dynamische-farben.md), [0009](entscheidungen/0009-ereignisgesteuerte-aktualisierung.md), [0010](entscheidungen/0010-gehaltsbezogene-faelligkeitsprojektion.md), [0011](entscheidungen/0011-lokaler-privacy-modus.md), [0012](entscheidungen/0012-app-vorschau-und-lokaler-pin-lock.md), [0013](entscheidungen/0013-postgresql-als-finanzquelle.md), [0014](entscheidungen/0014-google-oauth-nur-als-identitaet.md)
- [Dokumentationsseite](vorlagen/dokumentationsseite.md), [ADR](vorlagen/adr.md), [Fonts](fonts/README.md)
- Historische Einstiegspunkte: [Designsystem](design-system.md), [Sicherheit und Datenfluss](security-and-data-flow.md), [Schema](finance-data-schema-v1.md), [Google-Setup](google-oauth-vercel-setup.md)

Expand Down
7 changes: 7 additions & 0 deletions docs/anleitungen/lokale-entwicklung.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ npm run dev:mock

Vite nennt die lokale URL. Dieser Modus lädt ausschließlich anonyme Daten aus `src/mocks`, simuliert Sitzung/API und Picker und benötigt weder Google noch PostgreSQL. Die Umschaltung ist nur aktiv, wenn Vite im Entwicklungsmodus läuft und `VITE_USE_MOCK_API=true` gesetzt ist. Ein Produktionsbuild verwendet den Mock nicht.

## Develop- und Vercel-Preview

Der dauerhafte Integrationsbranch `develop` wird unter `https://accura-preview.kiumu.app/` als Vercel Preview bereitgestellt. Alle Vercel-Preview-Deployments verwenden automatisch dieselbe anonyme, bereits angemeldete Mock-Sitzung wie `npm run dev:mock`. Damit benötigen weder `develop` noch Pull-Request-Previews Google- oder PostgreSQL-Secrets.

Die Trennung ist fail-closed: Nur Vercels exakte Umgebung `preview` aktiviert den Build-Mock automatisch. `production` verwendet unabhängig von `VITE_USE_MOCK_API` immer die reale API. Production und Preview besitzen außerdem verschiedene Origins und dadurch getrennte Cookies, IndexedDB- und Service-Worker-Speicher.

## Realer lokaler Serverfluss

Plain `npm run dev` startet nur Vite und stellt `api/` nicht bereit. Für echte Vercel Functions:
Expand Down Expand Up @@ -48,4 +54,5 @@ npx vercel dev --listen 3000
- npm-Skripte: [package.json](../../package.json)
- Vite/PWA: [vite.config.ts](../../vite.config.ts)
- Mock-API: [src/mocks/mockFinanceApi.ts](../../src/mocks/mockFinanceApi.ts)
- Modusauflösung: [build/financeRuntimeMode.ts](../../build/financeRuntimeMode.ts)
- Mock-Workbook: [src/mocks/anonymousWorkbook.ts](../../src/mocks/anonymousWorkbook.ts)
4 changes: 3 additions & 1 deletion docs/anleitungen/testen-und-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ Chromes nativer Installationsdialog, Android-Launcher, Task-Switcher und OS-Spla

## Release-Entscheidung

Ein Release erfolgt nur, wenn automatische Prüfungen grün, Änderungen und Migrationen verstanden, Secrets/Finanzwerte ausgeschlossen und relevante manuelle Szenarien abgenommen sind. GitHub-CI prüft Pushes auf `master` und Pull Requests mit Lint, Unit, Build und Smoke. Reale Google-/Datenbankabläufe bleiben außerhalb CI.
Arbeitsbranches entstehen von `develop` und werden per Pull Request dorthin integriert. Vercel stellt sie und den dauerhaften `develop`-Stand mit anonymer, bereits angemeldeter Mock-Sitzung bereit. Dieser Pfad darf keine realen Google-/Datenbankabläufe vortäuschen.

Ein Produktionsrelease von `develop` nach `master` erfolgt nur als eigener bewusster Schritt, wenn automatische Prüfungen grün, Änderungen und Migrationen verstanden, Secrets/Finanzwerte ausgeschlossen und relevante reale Szenarien abgenommen sind. GitHub-CI prüft Pushes auf `develop` und `master` sowie Pull Requests mit Lint, Unit, Build und Smoke. Reale Google-/Datenbankabläufe bleiben außerhalb CI.

## Nachweis

Expand Down
4 changes: 3 additions & 1 deletion docs/architektur/backend-und-sicherheit.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

Die Vercel Functions sind Backend-for-Frontend und Sicherheitsgrenze. Sie verifizieren die einzige erlaubte Identität, verwalten Google-Token und Datenbankverbindung, lesen Sheets, validieren das Finance-Schema und liefern eine kleine same-origin JSON-API. Der Browser spricht Google nur beim bewusst geöffneten Picker direkt an.

Dies beschreibt den aktuell implementierten Übergangsstand. Im verbindlichen Zielbild aus [ADR 0013](../entscheidungen/0013-postgresql-als-finanzquelle.md) und [ADR 0014](../entscheidungen/0014-google-oauth-nur-als-identitaet.md) liest der Server `FinanceDataV1` ownergebunden aus PostgreSQL. Google bleibt nur Identitätsanbieter; Picker, `drive.file`, Sheets-Laufzeitzugriff und persistierte Refresh-Tokens entfallen beim Cutover. Bis dahin bleibt der alte Pfad betriebsfähig, wird aber nicht weiter ausgebaut.

## OAuth-Sequenz

```mermaid
Expand Down Expand Up @@ -71,7 +73,7 @@ Widerrufene oder abgelaufene Google-Grants werden als `reconnect_required` abgeb

## Begründung und Nachweis

Siehe [ADR 0003](../entscheidungen/0003-serverseitiger-google-zugriff-und-drive-file.md) und [ADR 0004](../entscheidungen/0004-single-user-sicherheitsmodell.md).
Für den aktuellen Übergangsstand siehe die ersetzte [ADR 0003](../entscheidungen/0003-serverseitiger-google-zugriff-und-drive-file.md). Das Zielbild steht in [ADR 0013](../entscheidungen/0013-postgresql-als-finanzquelle.md) und [ADR 0014](../entscheidungen/0014-google-oauth-nur-als-identitaet.md); [ADR 0004](../entscheidungen/0004-single-user-sicherheitsmodell.md) bleibt gültig.

- Konfiguration: [api/_lib/config.ts](../../api/_lib/config.ts)
- HTTP-Grenze: [api/_lib/http.ts](../../api/_lib/http.ts)
Expand Down
6 changes: 4 additions & 2 deletions docs/architektur/finanz-domaene.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@

Die Tabelle enthält Quellen und zeitbezogene Snapshots, keine UI-Gesamtsummen. Der Parser validiert Beziehungen und normalisiert Geld in Integer-Cents. Reine Selektoren wählen den fachlich gültigen Stand und berechnen Summen. Das View-Model ergänzt lokalisierte Texte und Screen-Strukturen. React rendert diese Ausgabe.

## Sheets- und Finance-Datenpipeline
Im beschlossenen Zielbild ist `FinanceDataV1` die quellenunabhängige Domänengrenze: Der einmalige Sheet-Import erzeugt denselben Vertrag wie das PostgreSQL-Repository. Die folgenden Sheets-Schritte beschreiben den aktuell implementierten Übergangsstand. Quelle, Owner-Zuordnung und SQL-Grenze legt [ADR 0013](../entscheidungen/0013-postgresql-als-finanzquelle.md) fest.

## Aktuelle Sheets- und Finance-Datenpipeline

```mermaid
flowchart LR
Expand Down Expand Up @@ -73,7 +75,7 @@ Das View-Model erzeugt die vier Screenmodelle gemeinsam; dadurch teilen UI und T

Die Demnächst-Logik nimmt monatliche Wiederholung an und berücksichtigt weder einmalige Termine noch Feiertags-/Bankarbeitstagverschiebungen. Bis ein Nutzerprofil existiert, liefert der Browser nach Möglichkeit eine IANA-Zeitzone wie `Europe/Berlin`; fehlt sie, wird der lokale Gerätekalender verwendet. Das View-Model wird beim Sichtbarwerden der App und nach dem nächsten lokalen Tageswechsel mit einem neuen Projektionstag berechnet. Tests übergeben feste ISO-Daten und bleiben dadurch deterministisch. Negative `safeToSpendCents` sind möglich und werden nicht künstlich auf null begrenzt. Dasselbe gilt für reale negative Konto- und Pocketstände sowie den daraus abgeleiteten aktuellen Gesamtbestand.

Bei einer späteren Migration zu PostgreSQL gelten dieselben fachlichen Grenzen: reine Kalendertage wie Snapshot-, Fälligkeits- und Meilensteindaten werden als `date` gespeichert; tatsächliche Ereigniszeitpunkte wie Synchronisationen oder Änderungen als `timestamptz`. Ob Client, Anwendungsserver oder Datenbank den Projektionstag ableitet, wird mit der Datenbankarchitektur entschieden. Für die Finanz-Domäne bleibt er ein expliziter Eingabewert und darf weder stillschweigend aus dem Snapshot-Stichtag noch aus der Server- oder Datenbank-Session-Zeitzone entstehen.
Beim beschlossenen Wechsel zu PostgreSQL gelten dieselben fachlichen Grenzen: reine Kalendertage wie Snapshot- und Fälligkeitsdaten werden als `date` gespeichert; tatsächliche Ereigniszeitpunkte wie Synchronisationen oder Änderungen als `timestamptz`. Die Monats- oder Tagespräzision eines Meilensteins muss separat erhalten bleiben. Für die Finanz-Domäne bleibt der Projektionstag ein expliziter Eingabewert und darf weder stillschweigend aus dem Snapshot-Stichtag noch aus der Server- oder Datenbank-Session-Zeitzone entstehen.

Mit dem Onboarding wird die dort bestätigte IANA-Zeitzone als Heimatzeitzone im Nutzerprofil gespeichert. Die Gerätezeitzone darf den initialen Wert vorschlagen, ändert die Heimatzeitzone bei Reisen aber nicht automatisch. Nutzer können sie bewusst korrigieren. IANA-Zonen werden validiert; feste UTC-Offsets reichen wegen Sommerzeit und Regeländerungen nicht aus.

Expand Down
10 changes: 8 additions & 2 deletions docs/architektur/ueberblick.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@
> **Kanonisch für:** Systemkontext, Vertrauensgrenzen und Architektur-Gesamtbild.
> **Verwandte Dokumente:** [Frontend](frontend.md), [Backend und Sicherheit](backend-und-sicherheit.md), [Quellcode-Karte](../referenz/quellcode-karte.md)

## Mentales Modell
## Verbindliche Richtung

Der beschlossene Quellenwechsel trennt Finanzquelle und Identität: PostgreSQL wird die einzige produktive Finanzquelle, Google Sheets bleibt ein einmaliges Importformat und Google OAuth dient danach nur noch der Anmeldung. Der vollständige Browservertrag bleibt `FinanceDataV1`; `owner_id` existiert ausschließlich in der Persistenz. Finanzberechnungen und Snapshot-Auswahl bleiben außerhalb von SQL. Verbindlich sind [ADR 0013](../entscheidungen/0013-postgresql-als-finanzquelle.md) und [ADR 0014](../entscheidungen/0014-google-oauth-nur-als-identitaet.md).

Dieses Zielbild ist noch nicht vollständig implementiert. Bis Schema, Import und Cutover abgeschlossen sind, gilt der folgende Abschnitt als Beschreibung des laufenden Systems; neue Arbeit darf daraus keine fortbestehende Bindung an Sheets ableiten.

## Aktuell implementierter Übergangsstand

`accura` besteht aus einer React-PWA im Browser, same-origin Vercel Functions, PostgreSQL und Google-Diensten. Die Tabelle ist die Finanzquelle; PostgreSQL speichert nur die Verbindung. Der Server bildet die Sicherheits- und Validierungsgrenze. Der Browser erhält ausschließlich normalisierte Finanzdaten und zeigt daraus abgeleitete View-Models.

Expand Down Expand Up @@ -50,7 +56,7 @@ Google Sheets → Vercel Function → Tabellenparser → `FinanceDataV1` → Pro

## Architekturentscheidungen

Die Gründe sind in [ADRs](../entscheidungen/README.md) festgehalten. Besonders zentral sind Google Sheets als Quelle, versionierte Integer-Cent-Domäne, serverseitiger Google-Zugriff, Single-User-Sicherheit und Last-known-good-Offline.
Die Gründe sind in [ADRs](../entscheidungen/README.md) festgehalten. Für die nächste Arbeit sind PostgreSQL als Finanzquelle, Google OAuth nur als Identität, die versionierte Integer-Cent-Domäne, Single-User-Sicherheit und Last-known-good-Offline zentral. ADR 0001 und ADR 0003 erklären nur noch den implementierten historischen Ausgangspunkt.

## Grenzen und Sicherheitsannahmen

Expand Down
2 changes: 1 addition & 1 deletion docs/entscheidungen/0001-google-sheets-als-datenquelle.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
> **Kanonisch für:** Begründung von Google Sheets als Quelldatenspeicher.
> **Verwandte Dokumente:** [Schema v1](../referenz/finance-data-schema-v1.md), [ADR-Index](README.md)

- **Status:** Angenommen
- **Status:** Ersetzt durch [ADR 0013](0013-postgresql-als-finanzquelle.md)

## Kontext

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
> **Kanonisch für:** Begründung des serverseitigen Tokenflusses und `drive.file`-Scopes.
> **Verwandte Dokumente:** [Web-Sicherheit und OAuth](../grundlagen/web-sicherheit-und-oauth.md), [ADR-Index](README.md)

- **Status:** Angenommen
- **Status:** Ersetzt durch [ADR 0014](0014-google-oauth-nur-als-identitaet.md)

## Kontext

Expand Down
Loading
Loading