Teilen-Blatt, HIG-Blaetter, geteilte Ansichtseinstellungen (2026.8.31.1)

Share-Erweiterung: ein PDF aus Mail landet ueber Teilen -> DataMetric360 als
Tankbeleg. Alles Native liegt versioniert unter companion-app/native/, weil
ios/ gitignored ist und npx cap add ios es sonst wieder frisst;
scripts/ios-teilen-einrichten.mjs haengt es bei jedem Bau ins Xcode-Projekt
und ist in ios-signieren.sh eingehaengt. Der pbxproj-Teil ist auf keinem Mac
erprobt - er bricht vor dem Schreiben ab, und docs/SHARE_EXTENSION.md
beschreibt dieselben Handgriffe fuer Xcode.

"In den Kalender uebernehmen" ging in der App nie: WKWebView ignoriert das
download-Attribut, der Klick lief ins Leere. Auf dem Geraet jetzt
@capacitor/filesystem plus @capacitor/share.

useTheme/useBoden legten je Aufrufer eigenen Zustand an - fuenf Kopien. Der
Schalter aenderte nur seine eigene, data-theme in App.tsx blieb stehen, die
Tag/Nacht-Umschaltung wirkte erst nach einem Neustart. Jetzt ein Speicher je
Einstellung ueber useSyncExternalStore. Zwei Regressionstests, gegen den
alten Stand als fehlschlagend nachgewiesen.

Alle Popups nach Apple HIG: neue Bausteine Sheet und ActionSheet, blickdicht
und mit abgedunkeltem Schleier, in beiden Codebasen. Popup portalierte nach
document.body - ausserhalb von [data-theme] - und war deshalb im Tagmodus
fast unsichtbar; Portal zielt jetzt auf .ads-root.

ImagePlaceholder merkte sich "fehlgeschlagen" ohne die Adresse: nach einem
leeren Galerieplatz zeigten auch die mit Foto nur noch den Platzhalter.
Vier Regressionstests, ebenfalls gegen den alten Stand geprueft.

Bildadressen tragen jetzt einen Cache-Brecher - HA liefert /local/ mit 31
Tagen Cache-Vorgabe aus, im Panel eingefuegte Radfotos erschienen in der App
deshalb nicht.

uebersichtsbild: Panel speichert den Namen, die App verglich gegen den
Dateinamen - der Vergleich traf nie zu. Kanonisch ist der Name.

Weiter: Navigationspfeil statt gleichschenkligem Dreieck auf der Streckenlinie
(die Kerbe unterscheidet Kopf und Ende), CI-Symbol tour-s als Fahrten-Icon,
Datumsfelder zeigen ihren Wert ohne erstes Antippen, feste Beispielnamen im
Setup, Kopf-Gegengewicht fuer mittige Titel, Standort-Blatt mit festem
Fussabstand, NSLocationWhenInUseUsageDescription, watchPosition fuer die
Live-Ortung, Art-Pille wieder als Knopf, Zwischenablage fuer Tankbelege.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-31 14:24:52 +02:00
parent 26f72930c3
commit b1411625aa
58 changed files with 2846 additions and 368 deletions
+120
View File
@@ -6068,3 +6068,123 @@ rendering bug in a view.
`custom_components/audi_dashboard/` directly. Do not reintroduce a copy.
- Security principle: HA is never publicly exposed; only narrowly scoped surfaces (MQTT broker,
proxy allowlist) may be exposed, each by explicit decision.
---
## AR. Teilen-Blatt, Kalender und der Rest des Owner-Stapels vom 2026-08-31
Alles unter `companion-app/`, sofern nicht anders vermerkt; Panel-Anteile nach der Paritätsregel.
### Share-Erweiterung: alles versioniert, damit `cap add ios` sie nicht wieder frisst
Ein PDF aus Mail über **Teilen → DataMetric360** landet als Tankbeleg in der App. Der Weg im
Ganzen steht in `companion-app/docs/SHARE_EXTENSION.md`; das Wesentliche hier:
- Eine Erweiterung ist ein eigener Prozess. Der einzige gemeinsame Boden mit der App ist eine
**App-Gruppe** (`group.app.datametric360`). Das PDF reist Base64-kodiert durch die geteilten
`UserDefaults`, weil `@capacitor/preferences` genau die liest, sobald man ihm die Gruppe nennt —
eine Datei im Gruppen-Container bräuchte eigenen nativen Code auf der App-Seite, also ein
zusätzliches Plugin für einen Weg, den es sonst nicht gäbe. Preis: 4-MB-Grenze, sauber gemeldet.
- **`companion-app/ios/` ist gitignored.** Alles Native liegt deshalb unter
`companion-app/native/` und wird von `scripts/ios-teilen-einrichten.mjs` bei jedem Bau wieder
eingespielt — dieselbe Regel, die schon die Team-Kennung und den Standort-Schlüssel in
`ios-signieren.sh` getrieben hat. Das Skript ist idempotent und in `ios-signieren.sh`
eingehängt.
- **Der pbxproj-Teil ist auf keinem Mac erprobt** (geschrieben ohne Zugriff auf macOS). Er bricht
vor dem Schreiben ab, wenn etwas nicht passt, und `docs/SHARE_EXTENSION.md` beschreibt dieselben
Handgriffe für Xcode. Nicht als „fertig verifiziert" behandeln, bevor es auf dem Mac gelaufen ist.
- Einmalig im Apple-Portal nötig: App-Gruppe anlegen, `app.datametric360.teilen` anlegen, beide
App-IDs der Gruppe zuordnen. Ohne das schlägt das Signieren fehl.
- Die App-Seite ist geprüft (6 Tests in `src/daten/geteilterBeleg.test.ts`): Eintrag → `File`,
genau einmal abgeholt, liegengebliebene und unlesbare Einträge verworfen.
### „In den Kalender übernehmen" ging in der App nie
`<a download>` mit Blob-Adresse — **WKWebView ignoriert `download`**, der Klick lief ins Leere.
Auf dem Gerät jetzt `@capacitor/filesystem` (Cache) plus `@capacitor/share`; im Browser und im
Panel bleibt der Download. Betrifft Reifen-Wechseltermin und Service-Werkstatttermin.
### Ein Wert, viele Kopien: `useTheme`/`useBoden` waren pro Aufrufer eigener Zustand
`useTheme()` legte bei jedem Aufruf ein eigenes `useState` an — fünf unabhängige Kopien
(`App.tsx`, `Einstellungen.tsx`, `Typenschild.tsx` ×2). Der Schalter änderte nur seine eigene;
`data-theme` in `App.tsx` blieb stehen, die Umschaltung wirkte erst nach einem Neustart. Der
Boden-Schalter hatte denselben Fehler. Jetzt `useSyncExternalStore` über einen Speicher je
Einstellung in `theme.ts` — Reacts eigener Weg für Zustand außerhalb des Komponentenbaums, ohne
Kontext-Anbieter und ohne geänderte Aufrufstellen. Zwei Regressionstests, gegen den alten Stand
als fehlschlagend nachgewiesen.
**Regel:** eine Einstellung, die mehr als eine Komponente liest, gehört nicht in `useState` je
Aufrufer.
### `uebersichtsbild`: Panel und App speicherten Verschiedenes ins selbe Feld
Panel schreibt den **Namen** („Seitenansicht"), die App verglich gegen den **Dateinamen**. Der
Vergleich konnte nie zutreffen — die App zeigte immer den ersten Platz. Kanonisch ist der Name
(die ältere, bereits gespeicherte Form; dieselbe Entscheidung wie beim Wartungsplan, Abschnitt AE).
Korrigiert in `Einstellungen.tsx`, `Uebersicht.tsx` und `profilAdapter.ts`.
### Popups nach Apple HIG
Neu im Design-System: `Sheet` (Formularblatt) und `ActionSheet` (Auswahlblatt, „Abbrechen"
abgesetzt in eigener Karte). Beide blickdicht, mit abgedunkeltem Schleier, sichere Zone unten frei,
Escape schließt. Umgestellt: SmartDeal, Tankbeleg, Bildmenü, Datensatz, Historien-Import — und im
Panel `.bildmenu`, `.sdpopup`, `.beleg-popup` samt Schleier. Das Setup-Fenster bleibt: es füllt
ohnehin die volle Höhe und ist damit bereits die HIG-Form „Vollbild".
`menueLage` („ansicht"/„rad") ist entfallen — ein Aktionsblatt kommt immer von unten.
### `Popup` portalierte nach `document.body` — außerhalb des Themes
Die Farbwerte hängen an `[data-theme]` auf `.ads-root`. Alles, was nach `document.body` portaliert
wird, erbt deshalb **immer** den Nachtmodus: im Tagmodus war jedes Fenster eine zu 5 % deckende
weiße Fläche auf weißem Grund. Betraf alle vier bestehenden Dialoge. `Popup`/`Sheet`/`ActionSheet`
zielen jetzt auf `.ads-root`.
**Regel:** kein Portal nach `document.body`. `position: fixed` bleibt davon unberührt — `.ads-root`
setzt kein `transform`.
### `ImagePlaceholder` merkte sich „fehlgeschlagen" ohne die Adresse
In der Galerie unter „Mein Audi" bleibt dieselbe Komponente stehen und bekommt nur eine neue
`src`. Ein einmal gesetztes `true` galt damit für alle folgenden Bilder — nach einem leeren Platz
zeigten auch die mit Foto nur noch den Platzhalter. Jetzt wird die Adresse gemerkt. Das Panel hatte
die Falle nie: `bildWeiter()` nimmt `bild-fehlt` ausdrücklich wieder weg. Vier Regressionstests,
gegen den alten Stand als fehlschlagend nachgewiesen.
### Bildadressen ohne Cache-Brecher
HA liefert `/local/` mit `Cache-Control: public, max-age=2678400`**31 Tage** (nachgemessen).
Das Panel hängt seit jeher `?v=` an, die App nicht: ein im Panel eingefügtes Radfoto erschien in
der App nicht, ein ersetztes blieb das alte, ein 404 blieb ein 404. `bildUrl()` trägt jetzt einen
Bildstand, erneuert bei Start und nach jedem Hochladen/Löschen.
### Datumsfelder zeigten ihren Wert erst nach dem ersten Antippen
Alle sieben hängen an Werten, die asynchron eintreffen; WKWebView zieht den inneren Aufbau eines
`<input type="date">` bei einer späteren Wertänderung nicht zuverlässig nach. Neue gemeinsame
Komponente `Datumsfeld`, deren Schlüssel genau einmal wechselt (leer → gefüllt). Bewusst nicht der
Wert selbst als Schlüssel: dann entstünde das Feld bei jeder Auswahl neu und die Datumsauswahl
schlösse mitten in der Bedienung. **Auf dem Gerät zu bestätigen** — WebKit-spezifisch, im Chromium
der Vorschau nicht nachstellbar.
### Setup: „(z. B. …)" bleibt stehen
Der Hinweis kam aus der gerade zugeordneten Entität und änderte sich mit jeder Auswahl mit.
Vorgeschichte: bis 2026-08-28 stand dort ein ausgedachtes Beispiel, das für zwei Felder falsch war.
Beide Beanstandungen zusammen: **fest, aber nicht ausgedacht** — die 22 Beispiele stehen jetzt als
`beispiel` im `FELDER`-Katalog und sind die abgelesenen Namen der laufenden Integration.
`entitaetIdKurz()` und zwei Helfer (rund 60 Zeilen) haben keinen Aufrufer mehr und sind entfallen.
### Weiteres aus demselben Stapel
Fahrtrichtung als Dreiecke statt Punkte auf der Streckenlinie (Abstand nach Strecke, nicht nach
Stützpunkten — die Punktdichte hängt an der Geschwindigkeit); Art-Pille auf der Einzelfahrt ist
wieder ein Knopf (im Panel seit jeher, in der App ein totes `<span>`); Kopf-Gegengewicht, damit der
Titel auf Unterseiten wirklich mittig steht (gemessen: 17 px zu weit rechts, beide Codebasen);
Standort-Blatt ohne `min-height`, 34 px Fußabstand (18 war zu wenig, 7689 zu viel); Nebel über dem
Fahrzeugfoto von 42 %/78 % auf 26 %/96 %; FIN statt „Fahrgestellnummer"; Solldruckfelder auf 58 px
(die Klasse stand im Markup, hatte aber keine Regel); Markenlogos einheitlich auf Zeilenhöhe 16 px;
`watchPosition` statt `getCurrentPosition` für die Live-Ortung, mit Abmeldung beim Verlassen der
Seite; `NSLocationWhenInUseUsageDescription` in `ios-signieren.sh` (in der ausgelieferten `.ipa`
fehlte jeder Location-Schlüssel — deshalb fragte iOS nie).