Entscheidungen des Controllers zu Task 6 (Bedenken 2 bis 4).
- logos/prepare-logo.ts: sizeSvg(svg) ersetzt sizeSvgRoot und liefert { svg, w, h }. Das Seitenverhältnis kommt aus der brauchbaren
viewBox des Wurzelelements, sonst aus dessen Breite und Höhe (Pixel oder ohne Einheit), sonst ist es quadratisch; Prozent und
andere Einheiten, 0 und negative Werte zählen als unbrauchbar. Ohne brauchbare viewBox, aber mit Größe, wird viewBox="0 0 <Breite>
<Höhe>" ergänzt (eine unbrauchbare viewBox wird dabei ersetzt, nie doppelt geschrieben), damit der Inhalt auf das Raster skaliert
statt in Originalgröße oben links zu stehen. Eine brauchbare viewBox bleibt immer unberührt. Gelesen werden nur die Attribute des
Wurzelelements (vorher die erste viewBox irgendwo im Text, etwa die eines inneren <symbol>); Anführungszeichen " und ' gehen
beide. Tokenizer und Längenparser laufen linear (zwei quadratische Entwürfe fielen im Zeittest durch).
- logos/prepare-logo.ts: Raster und SVG werden über decodeImage aus editor/upload.ts geladen: erst die Maße lesen und gegen
40 Megapixel prüfen, erst dann dekodieren (vorher dekodierte createImageBitmap zuerst). decodeImages Text für „zu groß“ wird zum
Text des Logos, jeder andere Fehler bleibt „Das Logo konnte nicht gelesen werden.“ Die eigene Ladefunktion für SVG und
createImageBitmap entfallen. LOGO_SOURCE_MAX_PIXELS ist jetzt MAX_IMAGE_PIXELS (eine Quelle, Wert 40 000 000 per Test festgehalten).
- logos/prepare-logo.ts: SVG_FALLBACK_SIZE entfernt (nichts benutzte es).
- card/marks.ts: neu viewBoxAspect(value) mit den Regeln aus svgAspect; svgAspect ruft es auf (Verhalten unverändert), so stehen die
viewBox-Regeln nur einmal da.
- Tests: sizeSvg als Tabelle (Größe ohne Einheit, px, hochkant, Prozent, mm/em/pt/auto, nur Breite oder Höhe, 0 und negativ,
vorhandene viewBox bleibt, unbrauchbare viewBox wird ersetzt, innere Elemente zählen nicht, XML-Kopf, „>“ im Wert, Zeitgrenze),
prepareLogo mit gestelltem Image: Zahl der Pixel wird vor decode() geprüft (8001 x 5000 und 20 000 x 20 000 ohne decode, genau
8000 x 5000 mit), SVG nur mit Breite und Höhe wird 2048 x 614 gerastert und bekommt viewBox="0 0 200 60"; marks.test.ts für
viewBoxAspect. Vitest 47 Dateien, 1478 Tests grün, tsc sauber.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AirCard-IOS
Inoffizielles Hobbyprojekt, mit dem man eigene Kartenmotive für Apple Wallet unter https://aircard-ios.org gestalten kann. Bilder hochladen, Vorlage und Netzwerk-Logo wählen, herunterladen – oder mit Konto speichern. Keine Verbindung zu Apple, Visa, Mastercard, UnionPay, American Express, JCB oder Discover.
Aufbau
web/ – Vite + TypeScript ohne Framework. Frontend-Module in src/:
card/– Geometrie, Zuschnitt, Canvas-Rendering (keine DOM-Abhängigkeiten, einzeln testbar)editor/– UI (Vorlagen, Vorschau, Download, Speichern)account/– Appwrite-Client, Auth, Kartenverwaltungpages/– Routen (/,/karten,/passwort-neuvia History-API)
deploy/ – Webhook-Router (Port 8087), Build-Skript (deploy.sh), nginx-Config, Traefik-Route für Appwrite.
docs/ – Spec, Plan, Appwrite-Konfiguration.
Lokal entwickeln
cd web
npm ci
npm run dev # Entwicklungsserver http://localhost:5173
Login und Speichern brauchen die Appwrite-API. Ohne Einstellung gilt als Endpoint ${location.origin}/v1 (der Entwicklungsserver hat keinen Proxy dafür, es würde also der Vite-Server selbst gefragt). Für die lokale Entwicklung deshalb in web/.env.local (steht über .env* in .gitignore) den Endpoint setzen:
VITE_APPWRITE_ENDPOINT=https://appwrite.webklar.com/v1
Die Projekt-ID ist als Standard im Code hinterlegt (6ac2130a00289343d6d5, VITE_APPWRITE_PROJECT_ID überschreibt sie). Damit Appwrite den lokalen Host akzeptiert, muss die Web-Plattform localhost in der Appwrite-Console eingetragen sein (Projekt → Overview → Integrations → Platforms). Der Produktions-Build bekommt VITE_APPWRITE_ENDPOINT=https://aircard-ios.org/v1 aus der Container-Umgebung (deploy/compose-snippet.yml).
Tests
Im Frontend (web/):
cd web
npx vitest run # Unit-Tests (card/*, editor/*, account/*, pages/*)
npx tsc --noEmit # TypeScript-Prüfung
Im Repo-Wurzelverzeichnis:
node --test deploy/webhook-server.test.js # Webhook-Server (deploy.sh wird dort durch einen Stub ersetzt)
node --test deploy/deploy.test.js # deploy.sh (echtes Skript, lokaler git-Remote, npm-Stub)
node --test web/scripts/appwrite-setup.test.mjs # Appwrite-Schema-Skript
Deploy
Automatisch: Push auf main → Gitea-Webhook → Container webklar-aircard-build führt npm ci und vite build aus → Ergebnis nach out/ → nginx webklar-aircard liefert.
Manuell:
docker exec webklar-aircard-build sh /deploy.sh
Starten nur mit:
docker compose up -d aircard-build aircard aircard-api
aircard-api ist der kleine API-Dienst der Community-Galerie (deploy/api/, Route aircard-ios.org/api, siehe deploy/api/README.md).
Die Appwrite-API wird über /home/webklar/apps/traefik/dynamic/aircard.yml geroutet (Vorlage: deploy/traefik/aircard.yml): Requests auf aircard-ios.org/v1 mit Header X-Appwrite-Project: 6ac2130a00289343d6d5 gehen an den Appwrite-Container. Ohne diesen Header antwortet der nginx der Seite mit 404.
Appwrite
Projekt „AirCard“ auf appwrite.webklar.com:
- Projekt-ID (öffentlich, von Appwrite generiert):
6ac2130a00289343d6d5. Sie steht indeploy/traefik/aircard.yml, in/home/webklar/apps/.env(AIRCARD_APPWRITE_PROJECT_ID) und als Standard inweb/src/account/appwrite.ts. - Datenbank:
aircard, Tabelle:cards, Bucket:card-images(das Skript unten legt sie an und prüft sie) - Console-Einstellungen laut
docs/appwrite-console.md(Soll-Stand, nicht maschinell geprüft;--checkprüft nur das Schema): Auth mit E-Mail + Passwort, Sitzungsdauer 30 Tage, max. 5 Sitzungen pro Nutzer, Nutzerlimit 2000, Web-Plattformenaircard-ios.orgundlocalhost. Schritte 3 und 4 dort (Auth-Härtung, Dienste abschalten) sind optional und am Live-Projekt unbestätigt.
Schema prüfen:
node --env-file=/home/webklar/apps/.env web/scripts/appwrite-setup.mjs --check
Schema erstmalig anlegen:
node --env-file=/home/webklar/apps/.env web/scripts/appwrite-setup.mjs
Umgebungsvariablen (in /home/webklar/apps/.env):
AIRCARD_APPWRITE_PROJECT_ID– Projekt-ID (idempotent im Skript lesbar)AIRCARD_APPWRITE_API_KEY– API-Keyaircard-provision(nur für Setup)
Hinweis
Inoffizielles Hobbyprojekt. Alle Marken gehören ihren Inhabern.