# Pyramid – Arbeitsprotokoll Neueste Einträge OBEN. Jede Claude-Session schreibt hier nach jedem abgeschlossenen Schritt (und beim Abbruch mitten im Schritt den Zwischenstand). Format: ``` ## JJJJ-MM-TT – Kurztitel **Erledigt:** was fertig ist (mit Commit-Hash) **Offen/Nächster Schritt:** der konkrete nächste Handgriff **Stolperfallen:** was schiefging und wie es gelöst wurde (damit es nie zweimal passiert) **Fragen an Bernd:** (optional) gesammelte Richtungsfragen, die nicht blockieren ``` --- ## 2026-07-03 – M2: Modul-Schnitt definiert (docs/M2_MODULE_SCHNITT.md) **Erledigt:** Ausgehend von der Ist-Analyse (Eintrag darunter) die konkreten Modul-Grenzen + öffentliche Schnittstellen entworfen, dokumentiert in `docs/M2_MODULE_SCHNITT.md`. Reine Planung, kein Code geändert. Wichtigste Erkenntnis beim genaueren Lesen von `voip_manager.dart` und `livekit_call_manager.dart`: „Calls" ist architektonisch **kein einzelnes Feature**, sondern zwei unabhängige Mechanismen, die getrennt bleiben sollten: - `PyramidVoipManager` = natives Matrix-1:1-VoIP (`m.call.*`, direkte WebRTC-Peer-Connection, kein SFU) - `LiveKitCallManager` = SFU-basierte Voice-Channels (LiveKit-Raum, Presence via Matrix-State-Event, Screensharing, Mehrpersonen) – das ist der Discord-artige Kanal aus M3/M4 Modul-Liste im Dokument: `call_signaling`, `voice_channel`, `call_ui`, `settings` (aufgesplittet in Sektionsdateien), `verification` (neu, aus Settings herausgelöst – SAS/QR/Recovery-Key), `storage` (Ausbau der bestehenden `settings_prefs.dart`-Abstraktion zur Pflicht), `auth` (schon relativ sauber, kein akuter Bedarf), `push` (schon getrennt, eigener `sqflite`-Zugriff in `background_push.dart` ist bewusst so, kein Refactoring-Kandidat), `chat_timeline`/`rooms` (niedrigste Priorität, da strukturell am wenigsten verwoben – Hauptproblem dort ist Dateigröße/Duplikate, nicht Kopplung). Jeweils mit Dart-Interface-Skizze bzw. Datei-Zielliste. Empfohlene Umsetzungsreihenfolge: 1) Call-Pilot (`call_signaling` + `voice_channel` + `call_ui`, deckt gleich zwei ROADMAP-Punkte ab), 2) `settings_modal.dart` aufsplitten (geringes Risiko, reine Datei-Teilung), 3) `verification` herauslösen, 4) `storage`-Fassade schrittweise erzwingen, 5) `chat_timeline`/`rooms` zuletzt. **Offen/Nächster Schritt:** Wartet auf Bernds Entscheidung, ob der Call-Pilot wie vorgeschlagen als Nächstes umgesetzt wird (siehe Frage unten). Erst danach den ROADMAP-Punkt „Call-Schicht entwirren" wirklich anfassen – das ist die erste Stelle mit echtem Verhaltensrisiko in M2 (CLAUDE.md: nach Refactoring- Schritten immer App starten + Kernflows prüfen, hier zusätzlich brisant, weil GUI-Test auf dem Pi nicht möglich ist). **Stolperfallen:** Der erste Ist-Analyse-Durchgang (Subagent) hatte einen falschen Befund (`rooms_provider.dart`-Import in `app_state.dart` sei toter Code) – beim genaueren Lesen für den Modul-Schnitt widerlegt (`voiceParticipantsProvider` nutzt `roomListProvider` daraus). Bereits im Ist-Analyse-Eintrag korrigiert. Lehre: Subagent-Ist-Analysen sind ein guter Startpunkt, aber vor jedem darauf aufbauenden Schritt einzelne Befunde gegenlesen, nicht blind übernehmen. **Fragen an Bernd:** - Call-Pilot wie in `docs/M2_MODULE_SCHNITT.md` vorgeschlagen (zuerst `call_signaling`/`voice_channel`/`call_ui` entwirren) als nächster Umsetzungsschritt in Ordnung, oder lieber zuerst `settings_modal.dart` aufsplitten (geringeres Risiko, aber deckt keinen ROADMAP-Punkt aus M2 direkt ab)? Ohne GUI-Zugriff auf dem Pi kann der riskantere Call-Umbau hier ohnehin nicht praktisch getestet werden – das spricht eher für „auf dem PC anfangen", falls Zeitdruck besteht. --- ## 2026-07-03 – M2: Ist-Analyse der Architektur geschrieben **Erledigt:** M1 hat aktuell keinen auf dem Pi bearbeitbaren Punkt mehr offen (SQLCipher wartet auf Bernds Priorisierung, Härtetest braucht GUI/PC – siehe Fragen an Bernd unten und Eintrag 2026-07-03 „3 schon vorhanden, 1 echte Lücke"). Deshalb mit M2 weitergemacht, erster Punkt: Ist-Analyse. Per Code-Lektüre (Zeilenzahlen, Import-Graph, grep) folgende Schwachstellen gefunden: - **Gott-Dateien:** `widgets/settings_modal.dart` mit 5541 Zeilen / 50+ Klassen bündelt mindestens 8 unabhängige Bereiche (Profil, Benachrichtigungen, Voice/TURN, E2EE-Verifizierung inkl. SAS/QR, Sessions, Account, Erscheinungsbild, Updates). Weitere große Dateien: `message_group.dart` (2780), `rooms_panel.dart` (2555), `space_admin_dialog.dart` (2278), `chat_view.dart` (2116), `document_viewer.dart` (1773), `voice_channel.dart` (1123), `app_shell.dart` (1073). - **Call-Schicht unsauber verteilt statt hinter einer Schnittstelle:** `core/voip_manager.dart` (Matrix-VoIP-Signalisierung), `core/livekit_call_manager.dart` (LiveKit-Transport), `features/call/voice_channel.dart` + `mini_call_widget.dart` (UI) – aber `chat_view.dart`, `matrix_client.dart`, `settings_modal.dart` greifen alle DIREKT auf die Manager zu statt über eine gemeinsame Call-Fassade. Vier verschiedene Module importieren `voice_channel.dart` direkt. Genau das Gegenteil des „austauschbare Bausteine"-Ziels aus CLAUDE.md. - **State-Management gemischt:** 2 zentrale `ChangeNotifier`-Singletons (`voip_manager.dart`, `livekit_call_manager.dart`) statt Riverpod-`Notifier`, eingebunden über `app_state.dart` (`callStateProvider`, `voipStateProvider`). Migration sinnvoll, aber riskant wegen breiter Call-Sites und enger SDK-Callback-Kopplung (LiveKit-`Room`-Events, Matrix-`WebRTCDelegate`) – sollte zusammen mit der Call-Fassade angegangen werden, nicht isoliert. - **`core/` ist nicht rein generisch:** importiert Feature-Code direkt (`app.dart`/`router.dart` → `features/auth/*`, `matrix_client.dart` instanziiert `PyramidVoipManager` direkt). `app_state.dart` selbst ist ein Sammelbecken für Theme-, Call-, Voice-, Share-Target- und Spaces-Provider aus praktisch allen Features – zentraler Kopplungspunkt, der jedem Modulschnitt im Weg steht. (Korrektur beim genaueren Hinsehen für den Modul-Schnitt-Punkt: der `rooms_provider.dart`-Import dort ist entgegen der ersten Vermutung KEIN toter Altlast-Import – `voiceParticipantsProvider` nutzt `roomListProvider` daraus. Zeigt: Subagent-Befunde vor dem Weiterbauen selbst nachprüfen.) - **Keine gemeinsame Storage-Schnittstelle:** `SharedPreferences.getInstance()` wird an 12 Stellen direkt aufgerufen, obwohl `core/settings_prefs.dart` bereits eine `StateNotifier`-Abstraktion (`BoolPref`, `StringSetPref`) anbietet – wird meist umgangen. `sqflite` wird zusätzlich unabhängig in `background_push.dart` nochmal geöffnet (eigene DB-Instanz fürs Background-Isolate). `media_cache.dart` ist dagegen sauber gekapselt – gutes Vorbild für die anderen Speicherzugriffe. - **Toter Code bestätigt:** `features/chat/message_bubble.dart` (178 Zeilen, Klasse `MessageBubble`) wird nirgends instanziiert – von `message_group.dart` abgelöst, kann beim Refactoring entfernt werden. - **Duplikate:** `_formatTime`/`_formatDate` unabhängig implementiert in `pinned_messages_panel.dart:206`, `message_group.dart:322` UND `:475` (zwei Mal im selben File!), sowie im toten `message_bubble.dart:80/165`. **Modul-Kandidaten für den nächsten Punkt („Modul-Schnitt definieren"):** 1. `call_signaling` (Matrix-VoIP) – `core/voip_manager.dart` 2. `call_transport` (LiveKit) – `core/livekit_call_manager.dart` 3. `call_ui` (nur UI, konsumiert 1+2 über Interface) – `features/call/*` 4. `settings/*` aufsplitten: profile, encryption_verification, sessions, notifications, appearance, account – aus `widgets/settings_modal.dart` 5. `verification` als eigenständiges Feature (SAS/QR/Recovery-Key), nicht Teil von Settings – wiederverwendbar (z. B. künftig beim Login) 6. `storage` – gemeinsame Fassade, `settings_prefs.dart` als Pflichtzugang statt 12 Direktzugriffsstellen 7. `app_state.dart` auflösen in feature-lokale Provider-Dateien 8. `chat_timeline` – `message_group.dart` + `pinned_messages_panel.dart`, dabei toten Code (`message_bubble.dart`) entfernen, `_formatTime`/`_formatDate` in `chat/format_utils.dart` zusammenführen **Offen/Nächster Schritt:** M2, zweiter Punkt – „Modul-Schnitt definieren": aus der obigen Kandidatenliste konkrete Modul-Interfaces (Dart-Abstract-Classes o. ä.) entwerfen, dann EIN Modul zuerst umbauen (Vorschlag: `call_*`, da am klarsten abgegrenzt und am dringendsten laut CLAUDE.md-Leitprinzip) als Pilot, bevor der Rest folgt. Kein Codeumbau in diesem Schritt, nur Schnittstellen-Design. **Stolperfallen:** Keine – reine Lesearbeit, kein Risiko für Bestandsdaten. Analyse wurde von einem Explore-Subagent erstellt und stichprobenartig plausibilisiert (Zeilenzahlen/Imports selbst nachgezählt), nicht jede Einzelbehauptung im Detail nachverifiziert. **Fragen an Bernd:** - SQLCipher-Priorität weiterhin offen (siehe Eintrag „3 schon vorhanden, 1 echte Lücke" weiter unten) – blockiert nicht, aber ohne Antwort bleibt der M1-Punkt bis zum nächsten PC-Praxistest liegen. - Für M2: Soll ich die Call-Schicht als erstes Pilot-Modul umbauen (mein Vorschlag oben), oder hast du eine andere Präferenz (z. B. erst die `settings_modal.dart` aufsplitten, weil die am unübersichtlichsten ist)? --- ## 2026-07-03 – M1: Restliche Punkte geprüft – 3 schon vorhanden, 1 echte Lücke gefunden **Erledigt:** - **Logout-Warnung bei fehlendem Key-Backup** (Commit b5762ea): neue Funktion `isKeyBackupMissing(client)` in `bootstrap_dialog.dart` (prüft Cross-Signing + `keyManager.isCached()`). Beide Logout-Dialoge in `settings_modal.dart` zeigen jetzt eine rote Klartext-Warnung statt des neutralen Textes, wenn kein Backup eingerichtet ist. - **Drei ROADMAP-Punkte waren bereits umgesetzt** (nur nicht abgehakt) – durch Code-Lektüre verifiziert, nicht neu gebaut: - Key-Backup einrichten/wiederherstellen: `bootstrap_dialog.dart` deckt beides über den `Bootstrap`-Zustandsautomaten des matrix-SDK ab (`askNewSsss` für Neueinrichtung, `askUnlockSsss` fragt auf einem neuen Gerät nach dem Recovery-Key). Wird automatisch getriggert, wenn Cross-Signing fehlt (`app_shell.dart:_triggerBootstrap`). - Geräte-/Sessionverwaltung: `_SessionsSection` in `settings_modal.dart` – Liste, Umbenennen, SAS/QR-Geräteverifizierung, Einzel- und Massen-Abmeldung (mit Passwort-Reauth bei UIA-Anforderung). - **Echte Sicherheitslücke gefunden:** `sqlcipher_flutter_libs` ist eine Abhängigkeit in `pubspec.yaml`, wird aber **nirgends im Code benutzt**. `matrix_client.dart` (Haupt-App) und `background_push.dart` (Push-Hintergrund) öffnen `pyramid.sqlite` beide über den normalen, unverschlüsselten `sqflite`/`sqflite_common_ffi`-Factory. Die Krypto-DB (Access-Token, gepickelter Olm-Account, Megolm-Sessions, komplette Nachrichtenhistorie) liegt also **im Klartext** auf der Platte – nur durch Androids App-Sandbox geschützt, nicht durch eine eigene Verschlüsselung. **Offen/Nächster Schritt:** SQLCipher-Anbindung NICHT blind auf dem Pi umgesetzt – siehe Stolperfalle unten. Braucht einen eigenen, vorsichtig geplanten PC-Termin: Backup der Test-DB, Migrationscode (bestehende Klartext-DB einmalig nach SQLCipher überführen), Test auf einem Gerät mit echten Daten, erst dann Rollout. **Fragen an Bernd:** - Priorität der SQLCipher-Lücke: Soll das vor M2 (Refactoring) angegangen werden, oder reicht die App-Sandbox als Schutz erstmal aus (Angreifer bräuchte Root oder physischen Zugriff + ADB, um die Datei überhaupt zu lesen)? Die Migration bestehender Installationen (Uta!) ist der aufwändige/riskante Teil, nicht die Verschlüsselung selbst. **Stolperfallen:** - Eine DB-Verschlüsselungs-Migration ist genau die Art Änderung, die laut CLAUDE.md nicht "mal eben" auf dem Pi ohne Testgerät gemacht werden darf: scheitert die Migration (falscher Schlüssel, falsche PRAGMA-Reihenfolge, Timing zwischen Haupt-App und Push-Hintergrund-Client), sind Utas Nachrichten und Krypto-Schlüssel unwiederbringlich weg. Deshalb hier nur dokumentiert, nicht implementiert. --- ## 2026-07-03 – M1: Push-Regression vermutlich dieselbe Ursache wie Random-Logout **Erledigt:** Vor dem Blick in den Push-Code die Vermutung geprüft, ob die Push-Entschlüsselungs-Regression und der Uta-Random-Logout-Bug (siehe Eintrag weiter unten) zusammenhängen – Ergebnis: **sehr wahrscheinlich ja, exakt dieselbe Ursache.** - `client.getEventByPushNotification(...)` (aufgerufen in `_bgDecryptAndShow`, `background_push.dart:84`) ruft als ALLERERSTES `ensureNotSoftLoggedOut()` auf (matrix-Paket `client.dart:1878`). Diese Funktion prüft, ob der Access-Token in < 1 Minute abläuft, und löst dann `_handleSoftLogout()` aus — **dieselbe Funktion**, die ohne Refresh-Token (unser Zustand vor Commit 9dc83f7) sofort einen echten `client.logout()` auslöst (siehe Eintrag unten). - Der Hintergrund-Client aus `_buildClient()` liest **dieselbe** `pyramid.sqlite` wie der Haupt-Client. Ein `logout()`/`clear()` aus dem Hintergrund-Isolat würde also die komplette Session (inkl. Krypto-Schlüssel) für die GESAMTE App zerstören – nicht nur für den einen Push. Das erklärt, warum das Problem als dauerhafte Regression erscheint ("seit dem letzten Update nie wieder"): Nach dem ersten Auto-Logout-Ereignis (ausgelöst beim Sync ODER beim Entschlüsseln eines Pushs) sind die Schlüssel weg, jede folgende Entschlüsselung schlägt fehl → `BgEngine` zeigt nur noch den „Neue Nachricht"-Platzhalter (Fallback nach 6 s), nie mehr den echten Text. - Der bereits committete Fix (`refreshToken: true` beim Login, 9dc83f7) behebt damit vermutlich BEIDE Symptome gleichzeitig: der Refresh-Token liegt in der gemeinsamen DB, also kann auch der Hintergrund-Client ihn lesen und einen drohenden Soft-Logout durch stillen Refresh abwenden, statt die Session zu zerstören. **Offen/Nächster Schritt:** Gehört zum selben Praxistest wie der Random-Logout-Punkt oben – zusätzlich beobachten, ob nach einer Weile (mehrere Stunden/Tage) echte Push-Inhalte statt Platzhalter ankommen. Schlägt das fehl, liegt eine zweite, unabhängige Ursache vor (dann `docs/NOTIFICATIONS.md` + Memory „Pyramid Push" erneut konsultieren, insbesondere die 3 dort gelisteten Fallstricke der Hintergrund-Entschlüsselung). **Stolperfallen:** Diese Verbindung wurde rein durch Lesen des matrix-SDK- Quellcodes hergeleitet (`~/.pub-cache/hosted/pub.dev/matrix-6.2.0/lib/src/client.dart`), nicht durch Beobachtung auf einem echten Gerät – bewusst als Vermutung markiert, bis der PC-Praxistest sie bestätigt. --- ## 2026-07-03 – M1: Uta-Random-Logout – Ursache gefunden + Fix (UNGETESTET/Pi) **Erledigt:** - Alle Logout-Auslöser im Code kartiert: - 3 bewusste Nutzer-Logout-Buttons in `settings_modal.dart` (alle hinter Bestätigungsdialog) – kein Kandidat für "random" - Der eigentliche Verdächtige: `matrix`-Paket (v6.2.0) `Client._innerSync()` behandelt `M_UNKNOWN_TOKEN` vom `/sync`-Endpunkt so: bei `soft_logout: true` → `_handleSoftLogout()` → versucht `refreshAccessToken()` → **das SDK hatte nie einen Refresh-Token angefordert** (`client.login(...)` in `login_notifier.dart` rief ohne `refreshToken: true` auf) → `refreshAccessToken()` wirft sofort "No refresh token available" → Catch-Block ruft `await logout()` auf: ein **echter, destruktiver Logout**, ausgelöst rein durch einen normalen Soft-Logout (z. B. abgelaufenes Access-Token), ganz ohne Nutzerinteraktion. Quelle: `~/.pub-cache/hosted/pub.dev/matrix-6.2.0/lib/src/client.dart:2374-2384` und `:2496-2509`. - **Logging (Commit 63e4919):** neue `lib/core/auth_log.dart` schreibt jeden Login-Status-Wechsel (`client.onLoginStateChanged`) sowie SDK-Warnungen/-Fehler (`Logs().onLog`) in `auth_log.txt` im App-Support-Verzeichnis – übersteht App-Neustart. Zusätzlich loggen alle 3 Nutzer-Logout-Buttons sich selbst, damit sich künftig unterscheiden lässt: Nutzer-Logout vs. SDK-Auto-Logout. Reine Zusatzfunktion, keine Verhaltensänderung. - **Fix (Commit 9dc83f7):** `client.login(...)` fordert jetzt `refreshToken: true` an. Damit kann das SDK einen Soft-Logout künftig durch stillen Token-Refresh überstehen, statt die Sitzung zu zerstören. Unterstützt der Server keine Refresh-Tokens, bleibt das Verhalten exakt wie vorher (`response.refreshToken` ist dann `null`) – der Fix kann also nichts verschlimmern. **Offen/Nächster Schritt:** 1. **Praxistest auf dem PC (zwingend vor Haken in ROADMAP!):** Login → Nachrichten senden/lesen → Logout → erneuter Login → alte verschlüsselte Nachrichten noch lesbar? Dabei prüfen, ob `auth_log.txt` (App-Support-Verzeichnis) die Login-Status-Wechsel sauber mitschreibt. 2. Danach: den nächsten Punkt in M1 angehen (Push-Entschlüsselungs-Regression). 3. **Wichtig für Bernd/Uta:** Der Fix wirkt nur für NEUE Logins. Utas bereits bestehende Sitzung bekommt erst nach einem einmaligen manuellen Neu-Login einen Refresh-Token und ist bis dahin weiterhin anfällig. Sobald der PC-Test grün ist, sollte Uta sich einmal aus- und wieder einloggen. **Stolperfallen:** - Konnte auf dem Pi nicht mit echtem Login/Logout getestet werden (kein GUI, kein Matrix-Testaccount zur Hand) – Fix beruht auf genauem Lesen des matrix-SDK-Quellcodes, nicht auf Beobachtung im Betrieb. Deshalb zwingend vor dem nächsten Haken in ROADMAP am PC nachprüfen. - `chat_provider.dart:42` nutzt weiterhin das deprecated `Timeline.onUpdate` (Re-Entschlüsseln nach Schlüssel-Empfang) – hängt mit demselben Sync-Bereich zusammen, aber bewusst nicht angefasst, um nicht zwei riskante Änderungen im selben Bereich gleichzeitig ungetestet zu committen. --- ## 2026-07-03 – M0 abgeschlossen: Ordnung, flutter analyze sauber, README **Erledigt:** - Liegen gebliebene Deprecation-Fixes aus einer abgebrochenen Session committet (`withOpacity`→`withValues`, `activeColor`→`activeThumbColor`, `cacheExtent`→ `ScrollCacheExtent`, `PublicRoomsChunk`→`PublishedRoomsChunk`) – Commit 057ac1e - Restliche `flutter analyze`-Hinweise behoben – Commit 401bd6a: - `document_viewer.dart`: `Matrix4.translate/scale` → `translateByDouble`/ `scaleByDouble` (PDF-Zoom, gleiches Verhalten) - `rooms_panel.dart`: `onReorder` → `onReorderItem` (Index-Korrektur jetzt intern, manuelles `newIdx--` entfernt); `implementation_imports`-Hinweis kommentiert (SpaceChild wird vom `matrix`-Paket nicht öffentlich exportiert, kein Fix möglich) - `voip_manager.dart`: `getSources()`-Override kommentiert (Pflicht-Override der abstrakten `MediaDevices`-Klasse, die die Methode selbst deprecated hat) - `settings_modal.dart`: zwei `BuildContext`-nach-`await`-Stellen mit `context.mounted` abgesichert (Geräte umbenennen, Passwortabfrage bei Massenlogout) - **Bewusst NICHT gefixt:** `chat_provider.dart:42` (`Timeline.onUpdate` deprecated, Ersatz wäre `client.onSync` + Raum-Filter). Hängt am E2EE-Redecrypt-Pfad (Re-Entschlüsseln nach Schlüssel-Empfang) – laut CLAUDE.md heiliger Bereich, Umbau nur mit dediziertem Login→Nachrichten→Logout→Login-Test, nicht nebenbei. - `lib/features/calls/` (Leiche) geprüft: existiert nicht mehr, nur `lib/features/call/` mit den echten Dateien – Punkt war bereits erledigt, nur nicht abgehakt. - README.md durch echte Projektbeschreibung ersetzt (Funktionsumfang, Build, Struktur) – Commit 74d38fd - ROADMAP.md: M0 komplett abgehakt. **Offen/Nächster Schritt:** M1, erster Punkt – Uta-Random-Logout-Bug untersuchen (Logging an allen Logout-Pfaden einbauen, dann Ursache finden). Direkt danach die Push-Entschlüsselungs-Regression (`docs/NOTIFICATIONS.md` + Memory „Pyramid Push" vorher lesen). **Stolperfallen:** - Kein `test/`-Ordner vorhanden → `flutter test` läuft ins Leere, keine automatisierte Absicherung. `flutter analyze` lief sauber (0 Fehler/Warnungen), aber die Lint-Fixes in `document_viewer.dart` (PDF-Zoom) und `rooms_panel.dart` (Drag-Reorder der Raumliste) sind **UNGETESTET** in echter UI – nächster Windows-Lauf sollte kurz PDF-Zoom und Raum-Umsortieren per Drag prüfen. - Der `ignore:`-Kommentar für einen deprecated-Aufruf muss auf der Zeile *unmittelbar* über der betroffenen Codezeile stehen, nicht nur "in der Nähe" – sonst wirkt er nicht (bei `voip_manager.dart` erst falsch über `@override` gesetzt, dann korrigiert). --- ## 2026-07-03 – Arbeitsstruktur angelegt + Vollbackup (Setup, noch kein Code) **Erledigt:** - Vollbackup inkl. Git-Historie: `MatrixPi\backups\pyramid-kopie_backup_2026-07-03.tar.gz` (510 MB) - `CLAUDE.md` (Arbeitsregeln), `ROADMAP.md` (M0–M7 nach Bernds Prioritäten), dieses Protokoll - Klargestellt: `C:\Users\nordm\pyramid - Kopie` ist das EINZIGE aktuelle Repo; `C:\Users\nordm\pyramid` und `MatrixPi\pyramid` sind veraltete April-Stände (Commit c9af913) **Offen/Nächster Schritt:** M0, Punkt 2 – die ~140 uncommitteten Dateien mit `git status` sichten und in thematische Commits aufteilen (diese drei neuen Dateien mit committen), dann `git push origin master`. **Stolperfallen:** - Letzter Commit ist vom 2026-04-28 (d706ace), gearbeitet wurde aber bis mindestens 2026-06-12 → zwischen Commits und Realität liegen 6 Wochen. Genau deshalb: ab jetzt kleine Commits nach jedem Schritt. - Git-Remote (Gitea auf dem Pi) enthält Zugangsdaten in der URL – funktioniert, aber nur erreichbar, wenn der Pi läuft (unterwegs: WireGuard nötig).