Doku & Aufräumen: CLAUDE.md/ARCHITECTURE.md, Tag-Schema, Legacy-Views weg
CLAUDE.md (Kurzform: was zu tun/lassen ist) und ARCHITECTURE.md (vollständige Repo-Karte mit Verzeichnis, Datenfluss, View-Inventar, Updater-Pipeline, Schwachstellen) als neue Onboarding-Dokumente. Tag-Schema in Doku und Skript-Kommentar an die tatsächliche Konvention angeglichen: Gitea-Tag ohne v-Prefix (latest.json-URL nutzt /releases/download/<VERSION>/). Betrifft scripts/release.sh, README.md und ARCHITECTURE.md §9+§10. Legacy-Views Contacts.jsx und Clients.jsx entfernt — durch Persons.jsx ersetzt, in NAV_ITEMS nicht mehr verlinkt, kein Import mehr im Code. ARCHITECTURE.md §5/§12/§14 entsprechend aktualisiert. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+396
@@ -0,0 +1,396 @@
|
||||
# RAPPORT — Architektur-Übersicht
|
||||
|
||||
> Studio-Management für Architekturbüros. Tauri 2.x Desktop-App, React 19 Frontend, Rust-Backend (minimal), localStorage-only Persistierung. Solo-Dev: Karim.
|
||||
> Dieses Dokument ist die **Karte** der Codebase. Es ersetzt nicht das Lesen einzelner Dateien, soll aber verhindern, dass jede Session bei Null anfangen muss.
|
||||
|
||||
---
|
||||
|
||||
## 1. Mentales Modell in einem Absatz
|
||||
|
||||
RAPPORT ist eine **monolithische SPA**: ein React-Root in [App.jsx](src/App.jsx) hält den **gesamten** App-State in einem `useState({...})`, persistiert ihn synchron in `localStorage` unter dem Key `studio_data_v1`, und übergibt ihn als Props an lazy-geladene Views. Es gibt **kein Routing-Framework** (View-Wechsel via String-State), **kein State-Library** (kein Redux/Context/Zustand), **kein TypeScript**, **kein CSS-Framework** (alles inline `style={{}}` oder ein 700-Zeilen `<style>`-Block in App.jsx). Der **Rust-Teil** ist 109 Zeilen und macht nur drei Dinge: System-Tray, Window-Hide-on-Close, Plugin-Registrierung (Updater, Process, Log). Es gibt **keine `#[tauri::command]`** — Frontend ↔ Backend kommuniziert nur über das Event `rapport:navigate` (Tray → Frontend). Alle Daten sind im WebView-`localStorage`, nichts wird in Rust gespeichert.
|
||||
|
||||
**Konsequenz:** Wenn etwas mit Daten passiert, ist es JS. Wenn etwas mit dem Fenster/Tray/Update passiert, ist es Rust. Es gibt keine dritte Stelle.
|
||||
|
||||
---
|
||||
|
||||
## 2. Verzeichnis-Karte
|
||||
|
||||
```
|
||||
APP/
|
||||
├── src/ React-Frontend (kein TS, nur .jsx)
|
||||
│ ├── main.jsx Entry: createRoot().render(<App />)
|
||||
│ ├── App.jsx Root: State, Navigation, Auth, Migrationen, Sidebar, Modals [823 Z.]
|
||||
│ ├── constants.js STORAGE_KEY, NAV_ITEMS, defaultData, SIA-Phasen, Statusfarben [252 Z.]
|
||||
│ ├── utils.js Business-Logik: Kalkulation, Format, QR-Bill, Hash, CSV, Lohn [678 Z.]
|
||||
│ ├── views/ 31 Top-Level-Screens, lazy-geladen
|
||||
│ ├── components/ UI.jsx (StatusBadge, Modal, FormField, …), UpdateNotifier, UpdatesSupport
|
||||
│ ├── print/ PrintComponents.jsx — alle Druckansichten (Rechnung, QR, Brief, …)
|
||||
│ ├── utils/updater.js Wrapper um @tauri-apps/plugin-updater + plugin-process
|
||||
│ ├── assets/ hero.png
|
||||
│ ├── App.css, index.css Globale CSS-Reset + Variablen
|
||||
│ └── index.jsx
|
||||
│
|
||||
├── src-tauri/ Rust-Backend (Tauri 2.10.3)
|
||||
│ ├── src/main.rs 6 Zeilen — delegiert zu app_lib::run()
|
||||
│ ├── src/lib.rs 103 Zeilen — Tray, Window-Events, Plugins
|
||||
│ ├── Cargo.toml tauri, plugin-updater, plugin-process, plugin-log, serde
|
||||
│ ├── tauri.conf.json Updater-URL, Public-Key, CSP, Bundle-Targets
|
||||
│ ├── capabilities/default.json core:default, core:webview:allow-print, updater:default, process:allow-restart
|
||||
│ ├── icons/ .icns, .ico, mehrere PNGs (icon.png = Quelle)
|
||||
│ ├── gen/ AUTO-GENERIERT — nie editieren
|
||||
│ ├── target/ Cargo-Build-Output (~2 GB) — nie editieren
|
||||
│ └── build.rs Ruft tauri_build::build()
|
||||
│
|
||||
├── scripts/release.sh Build + Sign + latest.json
|
||||
├── latest.json Updater-Manifest (im Repo, weil über main.HTTP abgerufen)
|
||||
├── package.json version, scripts (dev, build, lint), Deps
|
||||
├── eslint.config.js Flat-Config, sehr minimal
|
||||
├── vite.config.js Nur @vitejs/plugin-react
|
||||
├── index.html Vite-Entry
|
||||
├── public/ favicon.svg, icons.svg
|
||||
├── README.md Setup-Doku (Release-Sektion veraltet, siehe §6)
|
||||
└── .claude/ Lokale Claude-Code-Einstellungen (gitignored)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Datenfluss — Wie Updates wirklich passieren
|
||||
|
||||
```
|
||||
User-Interaktion in View
|
||||
│
|
||||
▼ onChange / onClick
|
||||
View ruft eine der zwei Props auf:
|
||||
│
|
||||
├── update(key, value) → save({ ...data, [key]: value }) // Top-Level-Field
|
||||
└── saveAll(newData) → save(newData) // Atomar
|
||||
│
|
||||
▼
|
||||
setData(newData)
|
||||
localStorage.setItem("studio_data_v1", JSON.stringify(newData))
|
||||
│
|
||||
▼
|
||||
React re-rendert die Hierarchie
|
||||
```
|
||||
|
||||
**Wichtig:**
|
||||
- `save()` schreibt **synchron** bei jedem Update — kein Debouncing. Bei großen `data`-Objekten kann das spürbar werden.
|
||||
- Es gibt **kein Backup, kein Conflict-Resolution**. Wenn der User zwei App-Fenster hat (passiert kaum, weil Single-Window), überschreibt das letzte gewinnt.
|
||||
- Kein Schema-Validator beim Laden — wenn `localStorage` korrupt ist, crasht es im Render.
|
||||
|
||||
**`data` Top-Level-Struktur** (definiert in [constants.js](src/constants.js)):
|
||||
```
|
||||
{
|
||||
settings, // Studio-Daten: Name, IBAN, MWST, Stundensätze, Rollen, …
|
||||
persons[], // Kunden + Partner (vereint seit v0.5)
|
||||
projects[], // Projekte mit SIA-Phasen / Budget / Billing-Type
|
||||
timeEntries[], // Stundeneinträge
|
||||
invoices[], quotes[],
|
||||
expenses[], internalExpenses[],
|
||||
employees[], absences[], ferienEntries[], lohnEntries[],
|
||||
protocols[], deliveryNotes[], letterTemplates[],
|
||||
dashboardTemplates[], appRoles[], users[]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Navigation & State-Verwaltung
|
||||
|
||||
**Navigation** ist eine String-State-Machine in [App.jsx](src/App.jsx):
|
||||
- `const [view, setView] = useState("dashboard")`
|
||||
- Jede View ist ein String-Identifier (`"dashboard"`, `"projects"`, `"time"`, …)
|
||||
- `navigate(newView)` schiebt auf einen Ref-basierten History-Stack (Browser-ähnliches Vor/Zurück)
|
||||
- Drill-down via `selectedProjectId` etc. (zweiter State neben `view`)
|
||||
|
||||
**Modals:** Inkonsistent — manche Views nutzen `modal: { type, id }`, andere haben mehrere separate `useState` (siehe [Invoices.jsx](src/views/Invoices.jsx): `setModal`, `setNewInvModal`, `setAkontoModal`). Es gibt keinen zentralen Modal-Manager.
|
||||
|
||||
**Sessions/Auth:**
|
||||
- `sessionStorage.rapport_user` — angemeldeter User (gestrippt von Credentials)
|
||||
- Login per PBKDF2-SHA-256 (100k Iter, 16-Byte Salt) — siehe `hashPassword`, `verifyPassword` in [utils.js](src/utils.js)
|
||||
- Lockout: 5 Fehlversuche → 60s Sperre ([Login.jsx](src/views/Login.jsx))
|
||||
- Auto-Upgrade: legacy plaintext-Passwörter werden beim ersten erfolgreichen Login zu PBKDF2 migriert ([App.jsx:143-161](src/App.jsx#L143-L161))
|
||||
|
||||
**Weitere `localStorage`-Keys (alle `rapport_*`-prefixed):**
|
||||
| Key | Zweck |
|
||||
|---|---|
|
||||
| `studio_data_v1` | **Die Hauptdaten** (alles außer Settings unten) |
|
||||
| `rapport_dark` | Dark Mode (`"1"`/`"0"`) |
|
||||
| `rapport_sidebar_collapsed` | Sidebar collapsed (`"1"`/`"0"`) |
|
||||
| `rapport_zoom` | UI-Zoom-Faktor |
|
||||
| `rapport_changelog_seen` | Zuletzt gesehene Changelog-Version |
|
||||
| `rapport_v0_5_migrated` | Marker für abgeschlossene Migration |
|
||||
| `rapport_update_skipped_version` | Vom User übersprungene Update-Version |
|
||||
| `rapport_update_last_check` | ISO-Timestamp letzter Update-Check |
|
||||
| `sessionStorage.rapport_user` | Aktive Session |
|
||||
|
||||
---
|
||||
|
||||
## 5. Views — Inventar
|
||||
|
||||
Alle Views liegen in [src/views/](src/views/), werden in [App.jsx](src/App.jsx) per `React.lazy()` geladen und über Props `{ data, update, saveAll, modal, setModal, currentUser, … }` versorgt.
|
||||
|
||||
| View | Zeilen | Zweck | Komplexität |
|
||||
|---|---:|---|---|
|
||||
| [Projects.jsx](src/views/Projects.jsx) | 1781 | List + Detail, SIA-Phasen, Quoten-Zuordnung, Nachträge | **Sehr hoch** |
|
||||
| [Time.jsx](src/views/Time.jsx) | 1538 | Wochen-Grid mit Drag&Drop, Tag/Woche/Monat, Absenzen | **Sehr hoch** |
|
||||
| [Invoices.jsx](src/views/Invoices.jsx) | 1467 | Rechnungen, Akonto, QR-Bill, Mahnungen, Planer | **Sehr hoch** |
|
||||
| [Employees.jsx](src/views/Employees.jsx) | 1298 | Multi-Tab: Stammdaten, Absenzen, Ferien, Lohnabschluss | **Sehr hoch** |
|
||||
| [Quotes.jsx](src/views/Quotes.jsx) | 980 | Offerten: SIA / manuell / frei, Übernahme als Projekt | Hoch |
|
||||
| [Protocols.jsx](src/views/Protocols.jsx) | 978 | Sitzungsprotokolle (Beschluss/Info/Aufgabe), Mahnung-Modul | Hoch |
|
||||
| [Expenses.jsx](src/views/Expenses.jsx) | 914 | Mitarbeiter-Spesen + interne Ausgaben, Bild-Upload | Hoch |
|
||||
| [Settings.jsx](src/views/Settings.jsx) | 869 | 7 Tabs: Studio, Dokumente, Team, Kalender, System, Support, Profil | **Sehr hoch** |
|
||||
| [StudioBudget.jsx](src/views/StudioBudget.jsx) | 847 | Revenue-Sparklines, Aggregation Rechnungen/Quoten | Hoch |
|
||||
| [Dashboard.jsx](src/views/Dashboard.jsx) | 762 | Drag&Drop Widget-Layout, Template-System | Hoch |
|
||||
| [Persons.jsx](src/views/Persons.jsx) | 682 | Kunden + Partner (seit v0.5 vereint) | Mittel |
|
||||
| [Setup.jsx](src/views/Setup.jsx) | 423 | 7-Step-Wizard für Neuinstallation | Mittel |
|
||||
| [Pinboard.jsx](src/views/Pinboard.jsx) | 417 | Blog-artige Notizen, kategorisiert | Mittel |
|
||||
| [Accounting.jsx](src/views/Accounting.jsx) | 374 | CSV-Export, Jahreszahlen, MwSt-Berechnung | Mittel |
|
||||
| [Payroll.jsx](src/views/Payroll.jsx) | 344 | Monats-Lohnzettel, BVG-Sätze, Abzüge | Mittel |
|
||||
| [DeliveryNotes.jsx](src/views/DeliveryNotes.jsx) | 294 | Lieferscheine | Niedrig |
|
||||
| [Login.jsx](src/views/Login.jsx) | 197 | Login + Brute-Force-Lockout | Niedrig |
|
||||
| [Documents.jsx](src/views/Documents.jsx) | 194 | Router zu Protokolle/Lieferscheine/Briefe | Niedrig |
|
||||
| [MigrationScreen.jsx](src/views/MigrationScreen.jsx) | 141 | v0.5-Migration-Wizard (wenn alte Daten erkannt) | Niedrig |
|
||||
| [Letters.jsx](src/views/Letters.jsx) | 114 | Brieftemplates mit Placeholdern (`{{client}}`, …) | Niedrig |
|
||||
|
||||
---
|
||||
|
||||
## 6. utils.js — Business-Logik-Bibliothek
|
||||
|
||||
[utils.js](src/utils.js) ist isoliert und gut testbar. Die wichtigsten Gruppen:
|
||||
|
||||
**Kalkulation:**
|
||||
- `calcSIAHours(baukosten, schwierigkeit, phasen)` — SIA-102-Formel `p = Z1 + Z2/∛B`
|
||||
- `calcManualHours(phases, roles)` — Stundenansatz × Stunden pro Rolle
|
||||
- `deriveQuoteBudget(linkedQuotes, allQuotes, roles)` — Aggregiert Offerten zu Projekt-Budget
|
||||
- `calcLohn(emp, monat, spesen, bonus)` — AHV/ALV/BVG/NBU/KTG/Quellensteuer
|
||||
|
||||
**Crypto / Auth:**
|
||||
- `hashPassword(password, saltHex)` — PBKDF2-SHA-256, 100k Iter, via Web Crypto
|
||||
- `verifyPassword(password, user)` — Constant-Time
|
||||
- `withHashedPassword(user, password)` — Upgrade legacy → hashed
|
||||
- `stripCredentials(user)` — Entfernt `password`, `passwordHash`, `passwordSalt`
|
||||
|
||||
**Sicherheit:**
|
||||
- `sanitizeHtml(html)` — Allowlist (`<p>`, `<br>`, `<b>`, `<a>`, …), blockiert `<script>`, `javascript:`, `on*`-Handler
|
||||
|
||||
**Formatierung (de-CH):**
|
||||
- `formatCHF(amount)` → `"CHF 1'234.50"`
|
||||
- `formatDate(iso)` → `"14. Mai 2025"`
|
||||
- `roundCHF(amount)` → 5-Rappen-Rundung
|
||||
- `formatHours(minutes)` → `"2h 30m"`
|
||||
|
||||
**Schweizer QR-Rechnung:**
|
||||
- `isQRIban(iban)` — IID-Range 30000–31999
|
||||
- `formatIban(iban)` → 4er-Blöcke
|
||||
- `generateQRReference(invoiceNumber)` → 27-stellige Referenz mit Mod10-Prüfziffer
|
||||
- `mod10(input)` — Schweizer Modulo-10-Algorithmus
|
||||
|
||||
**Templates / Nummerngenerierung:**
|
||||
- `applyProjectNumberFormat`, `applyProtoNumberFormat` — Template-Syntax wie `{YYYY}/{NN}`
|
||||
- `parseSeqFromNumber`, `nextProtoSeq`
|
||||
- `buildReminderLetter(inv, nr, …)` — Mahnungstexte (1./2./3. Mahnung)
|
||||
- `buildPdfName(format, content, settings)` — Sanitierter Dateiname
|
||||
|
||||
**Sonstiges:**
|
||||
- `exportBuchhaltungCSV(data, year)` — Voller Jahresexport
|
||||
- `migrateDashboardLayout(val)` — Alte Widget-IDs → Row-basiertes Layout
|
||||
- `getFeiertageForYear`, `getWorkdaysInMonth`, `getSollStunden`
|
||||
|
||||
---
|
||||
|
||||
## 7. Print-Modul
|
||||
|
||||
[src/print/PrintComponents.jsx](src/print/PrintComponents.jsx) (~1200 Zeilen) exportiert `<PrintView>`, das via `setPrintContent(...)` aus App.jsx getriggert wird.
|
||||
|
||||
**Content-Typen:** `invoice`, `invoice+qr`, `qrbill`, `quote`, `letter`, `lieferschein`, `protokoll`, `lohn`, `studioBudget`, `buchhaltung`, `projectDetail`, `projectsOverview`, `mitarbeiterOverview`, `timeReport`.
|
||||
|
||||
**Druck-Trigger:** `getCurrentWebviewWindow().print()` (Tauri WebView) oder Fallback `window.print()` (Browser).
|
||||
|
||||
**Schweizer QR-Rechnung:** Lib `swissqrbill` (lokal installiert), erzeugt SVG für 100% akkuraten Druck. Format: 105mm × 210mm, separate `@page`-Regel.
|
||||
|
||||
**Styles:** Inline + `@page`, `print-color-adjust: exact`. Margins konfigurierbar über `settings.pageMargin{Top,Bottom,Left,Right}`.
|
||||
|
||||
---
|
||||
|
||||
## 8. Rust-Backend ([src-tauri/src/lib.rs](src-tauri/src/lib.rs))
|
||||
|
||||
**Alles in 103 Zeilen.** Keine `#[tauri::command]`. Kein Filesystem, kein HTTP, keine DB.
|
||||
|
||||
**Was er macht:**
|
||||
1. **System-Tray** mit 5 Nav-Items (`nav:dashboard`, `nav:time`, `nav:projects`, `nav:buchhaltung`, `nav:settings`) + `show` + `quit` ([lib.rs:47-60](src-tauri/src/lib.rs#L47-L60))
|
||||
2. **Tray-Click** → Fenster anzeigen + fokussieren ([lib.rs:81-90](src-tauri/src/lib.rs#L81-L90))
|
||||
3. **Tray-Nav-Click** → `emit("rapport:navigate", "<view>")` ans Frontend ([lib.rs:77](src-tauri/src/lib.rs#L77))
|
||||
4. **Window-Close (X)** → Hide statt Quit, gesteuert durch `Arc<AtomicBool> is_quitting` ([lib.rs:25-35](src-tauri/src/lib.rs#L25-L35))
|
||||
5. **Plugins registrieren:** `updater`, `process` (für Relaunch nach Update), `log` (nur Debug)
|
||||
|
||||
**Frontend lauscht** in [App.jsx:191](src/App.jsx#L191):
|
||||
```js
|
||||
listen("rapport:navigate", (event) => setView(event.payload))
|
||||
```
|
||||
|
||||
**Capabilities** ([src-tauri/capabilities/default.json](src-tauri/capabilities/default.json)) — bewusst minimal:
|
||||
- `core:default`
|
||||
- `core:webview:allow-print` (für `window.print()`)
|
||||
- `updater:default`
|
||||
- `process:allow-restart` (für Relaunch nach Update)
|
||||
- **Nichts** für `fs:*`, `shell:*`, `http:*`, `dialog:*`, `clipboard:*`
|
||||
|
||||
**Tauri-Plugins (Cargo.toml):**
|
||||
- `tauri-plugin-updater` v2
|
||||
- `tauri-plugin-process` v2
|
||||
- `tauri-plugin-log` v2
|
||||
- `serde` 1.0, `serde_json` 1.0
|
||||
|
||||
**Bekannte Fragilitäten:**
|
||||
- `app.default_window_icon().unwrap()` — panicked, wenn Icon fehlt ([lib.rs:64](src-tauri/src/lib.rs#L64))
|
||||
- Hardcoded `"main"`-Label für Window, Hardcoded `"nav:"`-Prefix — wenn Frontend Konventionen ändert, bricht Tray
|
||||
- Keine Tests in Rust
|
||||
|
||||
---
|
||||
|
||||
## 9. Updater-Pipeline End-to-End
|
||||
|
||||
```
|
||||
release.sh (lokal, manuell aufgerufen)
|
||||
├─ liest VERSION aus tauri.conf.json + package.json (Mismatch → Exit)
|
||||
├─ ⚠️ Cargo.toml wird NICHT geprüft
|
||||
├─ Lädt Private Key aus ~/.tauri/rapport_updater.key (kein Passwort)
|
||||
├─ npx tauri build (mit TAURI_SIGNING_PRIVATE_KEY env)
|
||||
├─ findet .app.tar.gz + .sig in src-tauri/target/release/bundle/macos/
|
||||
└─ schreibt latest.json (Repo-Root) mit version, signature, url, pub_date
|
||||
└─ url zeigt auf Gitea Release Asset (manuell hochzuladen)
|
||||
|
||||
User (manuell):
|
||||
├─ Gitea-Webinterface: Release mit Tag <VERSION> (ohne v-Prefix) erstellen
|
||||
├─ .app.tar.gz (+ optional .dmg) hochladen
|
||||
└─ git add latest.json && git commit && git push origin main
|
||||
|
||||
App-Start (in jeder installierten Version):
|
||||
├─ UpdateNotifier.jsx: setTimeout 1.5s → checkForAppUpdate({ silent: true })
|
||||
├─ Tauri-Plugin GET https://git.kgva.ch/karim/RAPPORT/raw/branch/main/latest.json
|
||||
├─ Verifiziert Signature gegen pubkey aus tauri.conf.json (Minisign)
|
||||
├─ Vergleicht latest.json.version mit getVersion()
|
||||
└─ Wenn neuer → Modal mit "Installieren / Später / Diese Version überspringen"
|
||||
|
||||
Installation:
|
||||
├─ update.downloadAndInstall(onProgress) // lädt von url in latest.json
|
||||
└─ relaunch() (via plugin-process)
|
||||
```
|
||||
|
||||
**Updater-Komponenten im Frontend:**
|
||||
- [src/utils/updater.js](src/utils/updater.js) (49 Z.) — Wrapper, kapselt Skip-Logik in `localStorage`
|
||||
- [src/components/UpdateNotifier.jsx](src/components/UpdateNotifier.jsx) (163 Z.) — Auto-Check beim Start, Modal mit Progress-Bar
|
||||
- [src/components/UpdatesSupport.jsx](src/components/UpdatesSupport.jsx) (197 Z.) — Settings-Tab "Updates & Support", manueller Check, ignoriert Skip
|
||||
- Custom DOM-Event: `window.dispatchEvent(new CustomEvent("rapport:check-update"))` — UpdatesSupport triggert manuell
|
||||
|
||||
**Aktueller `latest.json`:** nur `darwin-aarch64` (Apple Silicon). **Kein Intel-Build, kein Windows-Build, kein Linux-Build.** Wer auf x86_64-Mac oder anderem OS installiert, bekommt keine Updates.
|
||||
|
||||
**Signatur-Setup (Minisign):**
|
||||
- Private Key: `~/.tauri/rapport_updater.key` (User-Home, **niemals** im Repo, gitignored via `*.key`)
|
||||
- Public Key: base64 in `tauri.conf.json` → `plugins.updater.pubkey`
|
||||
- Kein Passwort (`TAURI_SIGNING_PRIVATE_KEY_PASSWORD=""`)
|
||||
|
||||
---
|
||||
|
||||
## 10. Build & Release-Workflow
|
||||
|
||||
**Versions-Bump betrifft drei Dateien — alle drei müssen synchron sein:**
|
||||
1. [package.json](package.json) → `"version"`
|
||||
2. [src-tauri/tauri.conf.json](src-tauri/tauri.conf.json) → `"version"`
|
||||
3. [src-tauri/Cargo.toml](src-tauri/Cargo.toml) → `[package] version`
|
||||
|
||||
**Zusätzlich für jeden Release:**
|
||||
4. [src/App.jsx](src/App.jsx) → Changelog-Entry in `CHANGELOGS`-Array (hardcoded in JSX)
|
||||
5. [src/App.jsx](src/App.jsx) → `rapport_changelog_seen`-Vergleichswert (im Changelog-Modal-Close-Handler)
|
||||
|
||||
> ⚠️ `release.sh` prüft nur 1+2. **Cargo.toml-Mismatch bleibt unbemerkt.**
|
||||
|
||||
**Dev-Workflow:**
|
||||
```bash
|
||||
npm run dev # Vite-Server auf http://localhost:3000
|
||||
npx tauri dev # Native Window + HMR
|
||||
npm run lint # ESLint (manuell — kein Pre-Commit-Hook)
|
||||
```
|
||||
|
||||
**Release-Workflow:**
|
||||
```bash
|
||||
# 1. Versionen in package.json, tauri.conf.json, Cargo.toml + Changelog-Entry hochziehen
|
||||
# 2. Commit
|
||||
# 3. Release-Script:
|
||||
./scripts/release.sh
|
||||
# 4. In Gitea-UI: Release <VERSION> erstellen (Tag OHNE v-Prefix — latest.json-URL nutzt /<VERSION>/), .app.tar.gz hochladen
|
||||
# 5. git add latest.json && git commit -m "Release X.Y.Z" && git push origin main
|
||||
# 6. git tag -a <VERSION> -m "..." && git push origin <VERSION>
|
||||
```
|
||||
|
||||
> Die [README.md](README.md)-Release-Sektion erwähnt `scripts/release.sh` nicht und ist veraltet.
|
||||
|
||||
---
|
||||
|
||||
## 11. Konventionen
|
||||
|
||||
**Sprache:**
|
||||
- **UI-Strings: Deutsch** ("Zeiterfassung", "Buchhaltung", "Beenden")
|
||||
- **Code-Identifier: Englisch** (`isQuitting`, `setView`, `currentUser`)
|
||||
- **Wenig Inline-Kommentare** — wenn vorhanden, meist Deutsch
|
||||
|
||||
**Naming:**
|
||||
- Komponenten/Views: PascalCase, eine Datei = ein Default-Export (ggf. mit Named-Exports für Sub-Views)
|
||||
- Utils: camelCase
|
||||
- Dateien: PascalCase für Components/Views, lowercase für constants/utils
|
||||
|
||||
**Styling:**
|
||||
- Inline-Styles dominieren (über 200 in [Invoices.jsx](src/views/Invoices.jsx) allein)
|
||||
- Globale Klassen: `.btn`, `.card`, `.pill`, `.filter-bar`, `.modal` — definiert im `<style>`-Block in [App.jsx](src/App.jsx)
|
||||
- CSS-Variablen für Theming: `--bg`, `--text`, `--border`, … (Dark Mode via `data-theme`-Attribut)
|
||||
- **Kein** Tailwind, **kein** CSS-Module, **kein** styled-components
|
||||
|
||||
**ESLint** ([eslint.config.js](eslint.config.js)): Flat-Config mit `js.configs.recommended`, `reactHooks.configs.flat.recommended`, `reactRefresh.configs.vite`. Kein Prettier, kein Husky, kein lint-staged.
|
||||
|
||||
**Imports:** Stdlib oben (React), dann Constants/Utils, dann lokale Components. Keine Pfad-Aliase (`~/`, `@/` werden **nicht** verwendet — relative Pfade `../foo`).
|
||||
|
||||
---
|
||||
|
||||
## 12. Wo es weh tut — Realistische Schwachstellen
|
||||
|
||||
1. **Vier "God Components"** über 1200 Zeilen ([Projects](src/views/Projects.jsx), [Time](src/views/Time.jsx), [Invoices](src/views/Invoices.jsx), [Employees](src/views/Employees.jsx)) — Refactoring riskant ohne Tests, Sub-Komponenten sind intern definiert statt extrahiert.
|
||||
2. **App.jsx ist 823 Zeilen** und macht: Auth, State, Migration, Sidebar, Modals, Changelog, About, Print-Routing, Hotkeys, Navigation-History, Theme. Jede Änderung an App.jsx ist hochriskant — sie betrifft alles.
|
||||
3. **Inline-Styles ohne Konvention** — Spacing/Farben sind über das Projekt verstreut, kein Design-Token-System.
|
||||
4. **Modal-State chaotisch** — manche Views haben `{type,id}`, andere mehrere `useState`. Kein zentraler Manager.
|
||||
5. **Keine Tests.** Nichts. Kein Vitest, kein Cypress, kein Rust-Test. Kalkulationen in `utils.js` wären leicht testbar.
|
||||
6. **Kein TypeScript.** Bei 18k Zeilen JSX ohne Types ist jedes Schema-Refactor Risiko.
|
||||
7. **Kein Error-Boundary** — wenn eine lazy-geladene View crasht, weißer Screen.
|
||||
8. **`localStorage` ohne Schema-Validierung** — korrupte Daten crashen im Render.
|
||||
9. **Keine CI**, keine Pre-Commit-Hooks. Linting muss man sich selbst merken.
|
||||
10. **Updater nur für Apple Silicon** — wenn User x86_64-Mac/Windows/Linux hat, kein Update.
|
||||
11. **README-Release-Sektion veraltet** — erwähnt `scripts/release.sh` nicht.
|
||||
12. **`release.sh` prüft Cargo.toml-Version nicht** — Inkonsistenz bleibt unbemerkt.
|
||||
13. **`.unwrap()` im Tray-Icon-Load** in [lib.rs:64](src-tauri/src/lib.rs#L64) — Startup-Panic möglich, wenn Icon fehlt.
|
||||
|
||||
---
|
||||
|
||||
## 13. Wenn-du-anfasst-Hinweise
|
||||
|
||||
| Bereich | Risiko | Notiz |
|
||||
|---|---|---|
|
||||
| `App.jsx` State/Auth/Migration | **Sehr hoch** | Touch nur mit klarem Auftrag, betrifft alles |
|
||||
| `constants.js` `defaultData` Shape | **Hoch** | Schema-Änderung erfordert Migration (siehe Beispiele in App.jsx:56-122) |
|
||||
| `utils.js` Kalkulationen | **Hoch** | Ohne Tests — Änderung an `calcSIAHours`, `calcLohn`, `generateQRReference`, `mod10` → manuell durchrechnen |
|
||||
| `print/PrintComponents.jsx` | **Hoch** | SwissQR-Bill ist Pixel-genau — Layout-Bugs sichtbar erst im Druck |
|
||||
| Views (Invoices/Projects/Time) | **Hoch** | Lange Files mit Edge-Cases (Mahnung, Akonto, Drag&Drop) |
|
||||
| `Settings.jsx` Permissions | **Hoch** | Tangiert Rollen/Berechtigungen, Dashboard-Templates |
|
||||
| `Login.jsx` Hash-Logik | **Hoch** | PBKDF2 + Migration, sicherheitsrelevant |
|
||||
| `lib.rs` Tray/Window | **Mittel** | Wenn Nav-IDs geändert werden, müssen Frontend + Rust synchron bleiben |
|
||||
| `tauri.conf.json` Updater | **Sehr hoch** | Public Key ändern bricht alle bestehenden Installationen |
|
||||
| `release.sh` | **Sehr hoch** | Falsche Änderung → defekte Updates beim User |
|
||||
| Neue Util / neue View | Niedrig | Isoliert, safe — kopiere bestehende, entferne was du nicht brauchst |
|
||||
|
||||
---
|
||||
|
||||
## 14. Offene Fragen / Nicht-Validiertes
|
||||
|
||||
- Wo werden Bild-Uploads (Receipts in Expenses, Logo in Settings) gespeichert? Vermutlich Base64 in `data` → wächst `localStorage` unkontrolliert.
|
||||
- Wie groß darf `data` werden, bevor `localStorage` (5–10 MB Limit) bricht? Aktuell ohne Monitoring.
|
||||
- PDF-Export: aktuell nur `window.print()` → User-PDF-Dialog. Kein direkter File-Save.
|
||||
- Multi-User-Workflow: `users[]` in `data`, aber nur ein Browser-localStorage → keine echte Mehrfachnutzung.
|
||||
Reference in New Issue
Block a user