Zum Inhalt

Änderungen

T17 – Standort: Kern (GPS, Geo-Funktionen)

2026-10-24

  • Neu: LocationService (src/app/core/location/location.service.ts) bestimmt den eigenen Standort. Er holt auf Android die Berechtigung (location oder coarseLocation), fragt den GPS mit hoher Genauigkeit ab und setzt den Fix in myFix. Daneben gibt es die Signale locating und error mit den Codes location-denied, location-timeout und location-unavailable.
  • Im Browser (und im Mock) gibt es keinen echten GPS: nach 600 ms liefert der Dienst den Demo-Fix aus Luzern (47.05021, 8.30934, Genauigkeit 8 m). Der GPS wartet höchstens 20 Sekunden; ein Fix gilt 10 Minuten lang als frisch (isFresh).
  • Neu: src/app/core/location/geo.ts mit reinen Funktionen für Entfernung (Haversine), Anfangskurs, die acht Himmelsrichtungen und die Texte für Distanz, Koordinate, Genauigkeit und Alter des Fixes. Die Koordinaten runden auf sechs Nachkommastellen (roundCoord).
  • Das Android-Manifest hat die Berechtigungen ACCESS_COARSE_LOCATION und ACCESS_FINE_LOCATION; der GPS ist als optionales Feature deklariert, damit die App auch ohne GPS läuft. Das Plugin @capacitor/geolocation ist eingebunden.
  • Das Senden und Empfangen von Standorten ändert sich nicht: das lief schon mit T23 über die Host-API (msg.send mit type: "geo").
  • Noch ohne Oberfläche: die Standort-Screens folgen mit T18, der Live-Standort mit T19.

T06 – Natives USB-Serial-Plugin, UsbSerialTransport und Keystore-Plugin

2026-10-24

  • Auf Android spricht die App die Antenne jetzt über das native Capacitor-Plugin UsbSerial (Java) an: 115200 Baud, 8N1, kein Flow Control, DTR und RTS sofort auf low.
  • Das Plugin sucht die Geräte über UsbSerialProber (inklusive CdcAcmSerialDriver), fragt die USB-Berechtigung an und meldet Daten, Abziehen und Lesefehler als Ereignisse. Die Reject-Codes übersetzt der Transport in permission-denied, open-failed und no-device.
  • Der Platzhalter UnavailableSerialTransport ist entfallen: Auf Android liefert UsbSerialTransport, im Browser weiterhin das simulierte Gerät. Die empfangenen Bytes dekodiert der Transport mit einem TextDecoder im Stream-Modus, damit Mehrbyte-Zeichen über Paketgrenzen hinweg ganz bleiben.
  • Das native Keystore-Plugin OffgridKeystore legt den HMAC-Schlüssel offgrid_unlock nicht exportierbar im Android Keystore an und rechnet nur den HMAC; X25519, HKDF und AES-GCM bleiben in TypeScript.
  • Nach einem resume der App verbindet sich die Antenne erneut, wenn sie disconnected ist. Der Manifest verlangt USB-Host.
  • Neu getestet: UsbSerialTransport gegen ein gemocktes Plugin (Fehlercodes, UTF-8 über Chunk-Grenzen, detached).

T09 – Screens Verbinden und Name, Onboarding-Guard

2026-10-24

  • Der Screen „Verbinden“ startet die Antennenverbindung, zeigt den Fortschritt als Live-Schrittliste und bietet „Weiter“ sowie „Erneut suchen“. „SCHRITT 1 VON 2“ erscheint nur im Onboarding.
  • „Erneut suchen“ entsperrt ein gesperrtes Gerät neu (unlock()), statt connect() zu rufen: Bei einem gesperrten, aber verbundenen Gerät kehrt connect() sofort zurück, ein fehlgeschlagenes Entsperren war darüber nicht wiederholbar. In allen anderen Zuständen sucht die Schaltfläche die Antenne weiterhin mit connect().
  • Der Screen „Name“ prüft den Anzeigamen nach 1 bis 24 UTF-8-Bytes, richtet ein frisches Gerät ein oder gibt den Namen an ein entsperrtes Gerät. Danach schliesst er das Onboarding ab und wechselt zu den Kontakten.
  • Der funktionale Onboarding-Guard schützt alle Routen ausser /verbinden und /name. Ohne abgeschlossenes Onboarding führt er zu /verbinden; bei einem laufenden App-Start wartet er auf die geladenen Einstellungen.
  • Mit localStorage og.noseed = '1' startet die Browser-Demo ohne Demo-Daten und mit einem frischen Mock-Gerät.
  • Neue Tests prüfen Guard, Verbindungszustände, Navigation, Bytegrenzierung, Geräteeinrichtung und die sichtbaren Fehlertexte beider Screens.

T10 – Screen Kontakte

2026-10-24

  • Neu: Der Screen „Kontakte“ (/kontakte) listet die Kontakte mit letzter Nachricht, Vorschau, Zeit, ungelesenen Nachrichten und Signalbalken. Jede Zeile ist ein Link in den Chat.
  • Neu: Die Antennen-Statuszeile zeigt „Antenne verbunden“ mit der Frequenz des Geräts oder „Antenne getrennt“ mit „· Tippen zum Verbinden“. Die Frequenz liest die App beim Gerät ab (radio.get), nicht aus einem festen Wert.
  • Neu: Ausstehende Kontakte (der Schlüssel fehlt noch) stehen als eigene, nicht klickbare Zeile mit „Schlüssel ausstehend“ hinter den Kontakten.
  • Neu: Der Leerzustand mit „Noch keine Kontakte“, der Erklärung zum QR-Handshake und dem Button „Ersten Kontakt hinzufügen“.
  • Neu im MessagingService: das Signal contacts (je Kontakt die letzte Nachricht, die Anzahl der ungelesenen Nachrichten und das Signal-Level, sortiert nach letzter Nachricht) und unreadCount(contactId). Die Liste lädt bei jeder Datenänderung neu.
  • Das Signal-Level einer Kontaktzeile kommt aus der Feldstärke und dem Alter der letzten eingehenden Nachricht: Älter als 15 Minuten zählt als „Ausser Reichweite“.
  • Neue Tests prüfen Reihenfolge, Signalbalken, Vorschauen, Badge, die Statuszeile, den Leerzustand und das Hochschieben eines Kontakts bei einer neuen Nachricht.

T02 – UI-Basis 2: Zustandskomponenten

2026-10-24

  • Neun Zustandskomponenten liegen unter src/app/ui/: og-avatar, og-signal-bars, og-banner mit Aktion und Hinweis, og-step-list, og-message-bubble, og-composer und og-qr. Der Barrel src/app/ui/index.ts exportiert sie zusammen mit ihren Typen.
  • og-avatar zeigt ein 5 × 5-Schlüsselraster in fünf Grössen und im Zustand pending. avatarPattern(seed) erzeugt deterministisch 25 spiegelsymmetrische Zellwerte mit mindestens fünf aktiven Zellen.
  • og-signal-bars zeigt die Stufen 0 bis 4 in den Grössen md und sm. og-step-list unterstützt die Zustände done, active und todo mit Puls oder Spinner; bei reduzierter Bewegung sind die Animationen ausgeschaltet.
  • og-message-bubble bildet eingehende und ausgehende Nachrichten in den Zuständen queued, sent, delivered und failed ab. Der fehlgeschlagene Zustand bietet „Erneut senden“ und löst retry aus.
  • og-composer zählt Bytes ausschliesslich über den Input countBytes, sperrt leere oder zu lange Texte standardmässig und sendet mit Enter. Mit blockOverLimit=false bleibt das Senden bei einer Überschreitung möglich.
  • og-qr erzeugt aus data ein SVG-Raster mit dem Paket qrcode und den Typen aus @types/qrcode. Die benötigten Farbwerte sind als einzige Hex-Ausnahme in ui/qr/qr-colors.ts ausgelagert.

2026-10-24

  • Die Antenne (Heltec V3) besitzt jetzt Identität, Kontaktschlüssel, Verschlüsselung, Zähler und Kontaktliste. Die App ist Oberfläche und Verlaufsspeicher und spricht die Host-API „Offgrid Link v1“ (JSON Lines über USB-Serial). Verbindliche Quelle sind die Spezifikationen des Firmware-Teams; docs/esp-schnittstelle.md verweist darauf.
  • Neu: SerialClient (Zeilen, ids, höchstens 4 Anfragen in Flight, Zeitlimits 2 s und 5 s, sys.hello alle 500 ms, Heartbeat alle 5 s), AntennaPort mit den Zuständen disconnected, unprovisioned, locked und unlocked sowie das Mock-Gerät MockDevice, das für den Browser dieselben Befehle, Antworten und Ereignisse liefert.
  • Neu: KeystoreService hält das Entsperr-Geheimnis (Android Keystore bzw. Arbeitsspeicher im Browser). Das Token wird mit X25519, HKDF und AES-256-GCM zu 60 Bytes verpackt und stimmt mit dem Testvektor der Spezifikation überein. Das native Plugin folgt mit T06.
  • Nachrichten gehen als Klartext über msg.send; längere Texte als 83 Bytes teilt die App in mehrere Nachrichten. Der Status kommt aus msg.status, eingehende Nachrichten aus msg.received. maxContentBytes gibt das gemeldete Limit unverändert zurück.
  • Das Pairing macht die Antenne: pair.begin liefert den QR-Text, pair.complete den Kontakt samt Fingerabdruck. Der Fingerabdruck ersetzt das frühere Vergleichsbild.
  • Die Datenbank hat kein Schlüssel-Feld mehr: contacts spiegelt die Kontakte der Antenne, messages hat ctr und msg_ref statt der Funk-Id, die Tabelle handshakes entfällt. Migration 1 wurde dafür einmalig umgeschrieben (noch keine veröffentlichte App).
  • Entfernt: die App-seitige Verschlüsselung von Nachrichten und das Vergleichsbild aus Schlüsseln. Skills und Doku wurden auf den neuen Vertrag umgestellt.

T04 – Datenbank, Repositories, Settings, App-Start

2026-10-24

  • Auf Android speichert die App Kontakte, Nachrichten, Koordinaten, Handshakes und Einstellungen in der SQLite-Datenbank offgrid. (Die Tabelle für Handshakes entfiel mit T23.) Migration 1 legt das Schema fest; PRAGMA user_version steuert spätere Migrationen.
  • Im Browser arbeiten dieselben Repositories auf Daten im Arbeitsspeicher. Die Plattform-Weiche in app.config.ts wählt SQLite oder Memory und stellt für jede Variante dieselbe Instanz bereit.
  • Kontakte, Nachrichten, Koordinaten, Handshakes und Einstellungen haben jeweils ein abstraktes Repository sowie eine SQLite- und eine Memory-Implementierung. Das Löschen eines Kontakts entfernt in beiden Varianten seine Nachrichten und Koordinaten.
  • Der Browser startet mit Demo-Kontakten und sechs Nachrichten (mit T23: vier Kontakte, „Jonas Keller“ ist ausstehend). Mit localStorage og.noseed = '1' startet er leer. DataChangedService meldet Datenänderungen über eine Version.
  • SettingsService stellt Anzeigename, Install-Id, Hintergrundempfang, Vibration und Onboarding-Status bereit. Die Install-Id wird beim ersten Start erzeugt und danach weiterverwendet.
  • Der App-Start öffnet die Datenbank, lädt die Einstellungen und bereitet auf Android Statusleiste und Splashscreen vor. Der Antennenstart kam mit T23 dazu.

T03 – Zeitformate, Hex/Base64, Signal-Level

2026-10-23

  • Alle Zeiten der App werden in der Zeitzone Europe/Zurich formatiert: Uhrzeit, „Gestern“, Wochentag, Datum für die Tagestrenner im Chat und „zuletzt …“ in der Kontaktliste.
  • Die Frequenz der Antenne erscheint als MHz-Text (z. B. „868 MHz“).
  • Hex- und Base64-Hilfen für Bytes. (Die ursprünglich gebauten Dienste für Schlüssel und Vergleichsbild entfielen mit T23, weil die Antenne Schlüssel und Verschlüsselung übernimmt.)
  • Die Signalstärke wird aus Feldstärke und letztem Kontakt zu fünf Leveln; ein Signal älter als 15 Minuten zählt als „Ausser Reichweite“.
  • Die Länge von Texten und Anzeigenamen wird in UTF-8-Bytes ohne JSON-Escapes gerechnet. Der Gerätevertrag „OffGrid Link“ setzt maximal 83 Bytes für Textinhalte (§7.5) und 24 Bytes für Anzeigenamen (§7.2/§7.3).

T01 – UI-Basis 1: Grundkomponenten

2026-10-23

  • 13 og--Grundkomponenten liegen unter src/app/ui/: Icon, Button, Link-Button, Icon-Button, Topbar, Abschnittstitel, Karte, Info-Zeile, Statuspunkt, Badge, Eingabefeld, Schalter und Segmentumschalter.
  • Alle Komponenten sind Standalone, nutzen OnPush und kommen ohne Services aus; src/app/ui/index.ts exportiert sie alle.
  • Farben, Radien und Grössen kommen ausschliesslich aus den Design-Tokens, die Schriften aus den Typografie-Mixins in src/theme/typography.scss.
  • og-button rendert ein <button>, og-link-button und og-icon-button mit Route ein <a>.
  • og-switch trägt die Rolle switch, og-segmented die Rolle tablist – beide mit den passenden ARIA-Attributen.
  • Die Zustandskomponenten (Avatar, Signalbalken, Banner, Schrittliste, Nachrichtenblase, Composer, QR) folgen mit T02.

T21 – CI-Checks (lokal + GitHub Actions) und Doku-Grundgerüst (MkDocs → GitHub Pages)

2026-10-23

  • npm run check führt Lint, Tests und Build in einem Durchgang aus – derselbe Ablauf wie in der CI.
  • GitHub Actions prüft jeden PR gegen main mit den Checks app-checks und docs-check.
  • Nach jedem Merge auf main wird die Doku-Website auf GitHub Pages veröffentlicht.
  • Die Doku liegt in doku/ (MkDocs Material) mit Befehlen npm run docs:install, docs:build, docs:serve und docs:screens.
  • Playwright ist als Entwicklungs-Abhängigkeit installiert und erzeugt die Screenshots der Screens für die Doku.

T00 – Projekt-Setup und Theme

2026-10-23

  • Leeres, lauffähiges Ionic-Angular-Capacitor-Projekt (Angular 22, Ionic 9, Capacitor 8) für Android, zoneless und mit Signalen.
  • Dunkles OffGrid-Theme: die Design-Tokens in src/theme/tokens.scss sind die Quelle der Wahrheit für Farben, Radien, Abstände und Grössen.
  • Alle Seiten und Routen der App sind als Stubs angelegt, ohne Texte und ohne Guard.
  • Die Typen für Kontakt, Nachricht, Handshake, Antenne, Settings und Fehlercodes liegen in src/app/core/models/.
  • Die drei Prüfbefehle npm run lint, npm run test:ci und npm run build laufen fehlerfrei.