Commit Graph
20 Commits
Author SHA1 Message Date
ClaudeandClaude Haiku 4.5 13b46da2e0 fix(logos): Fokus auf die Überschrift ohne Scrollen
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-10-05 06:06:04 +00:00
ClaudeandClaude Opus 5.5 e0f11f905e fix(editor): Vorschau und Miniaturen laden ein ausgeblendetes, eingeblendetes oder gelöschtes Logo neu
Blendete ein Admin das gewählte Logo im Picker aus (oder ein), forgetLogo leerte zwar den Zwischenspeicher, aber der Editor behielt seine schon
geladenen Logo-Bilder: Vorschau und Miniaturen zeigten weiter den alten Stand, bis sich Logo, Details oder Marke änderten.

- logo-picker.ts: neuer Rückruf `onLogoChanged(id)` (Typ LogoPickerHandlers). Der Picker ruft ihn, wenn die Infozeile ein Logo aus- oder eingeblendet hat
  (nach patchRow, die Zeile ist dann schon aktuell) und nach dem Löschen (nach onSelect(null)).
- editor-page.ts: Ist das gemeldete Logo das gewählte, verwirft der Editor seinen Logo-Zustand samt Sperren (logos, pendingKey, failedKey), blendet den Hinweis
  aus und ruft ensureLogos neu: Vorschau und alle vier Miniaturen werden mit dem neuen Stand gezeichnet, der Hinweis „nicht mehr verfügbar“ folgt. Ein
  anderes oder kein gewähltes Logo löst nichts aus; ein noch laufender Ladevorgang wird überholt (Token).
- editor-page.ts, followDownload: Stand die Karte nach einem Ladefehler ohne Logo da (`failed`), kehrte der Download früh zurück, obwohl er die Logos
  geladen hatte: das PNG hatte das Logo, die Vorschau nicht. Jetzt übernimmt die Vorschau es (Hinweis passend, failedKey leer).
- editor-page.ts, doSave: forgetLogo(config.logo) vor dem Laden der Bilder, wie Download und Veröffentlichen; ein inzwischen ausgeblendetes Logo kommt nicht
  mehr ins Vorschaubild der gespeicherten Karte.
- editor-page.ts: Beim Ladefehler für eine neue Auswahl blieb der Hinweis „Das gewählte Logo ist nicht mehr verfügbar.“ der früheren Auswahl neben dem neuen
  Fehler stehen; die Logos der früheren Auswahl und ihr Hinweis fallen jetzt sofort weg (andere Hinweise bleiben).
- Tests: Picker (Rückruf nach Aus-/Einblenden und Löschen mit Reihenfolge, nicht bei Fehlern, Herz, Melden, nach dem Aufräumen) und Editor (Aus- und Einblenden,
  anderes oder kein Logo, überholter Ladevorgang, nach Ladefehler, mit dem echten Zwischenspeicher; Download-Übernahme, Speichern, alter Hinweis neben Fehler).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 05:49:04 +00:00
ClaudeandClaude Opus 5.5 0e4759a05a fix(logos): Infozeile zeigt laufende Aktionen an, Fehler gehören zur Auswahl, Gründe nach „Einblenden“ verworfen
- logo-info.ts, run: Die Aktion merkt sich die Auswahl beim Start. Ein Fehler, der erst eintrifft, nachdem der Nutzer ein anderes Logo gewählt hat,
  erschien sonst in dessen Meldezeile (render() leert sie nur beim Wechsel, nicht danach). Jetzt steht er nur da, wenn noch dasselbe Logo gewählt ist.
  Ergebnisse, die an einer ID hängen (patchRow, removeLogo, forgetLogo), gelten wie bisher dem Logo, für das die Aktion lief.
- logo-info.ts: Solange eine Aktion läuft (Rückfrage, Dialog oder Anfrage), trägt die Infozeile aria-busy="true" und die anderen Aktionsknöpfe sind
  gesperrt. Der gedrückte Knopf bleibt bedienbar und behält den Fokus (ein gesperrter verlöre ihn, und der Picker holt den Fokus von „Löschen“ nur,
  wenn er in der Infozeile liegt). Danach ist alles wieder frei, auch nach einem Fehler und nach dem Verlassen der Seite.
- logo-info.ts, renderReports: Bei 0 Meldungen (etwa nach „Einblenden“, das sie löscht) wird der Gründe-Zwischenspeicher geleert. Kamen später genau so
  viele Meldungen hinzu wie vorher (gleicher Schlüssel <id>:<n>), wurden die alten, längst gelöschten Gründe gezeigt.
- Tests: Zustand während jeder Aktion (Löschen mit Rückfrage und Anfrage, Melden, Ausblenden, Fehler, abort), Fokus, wirkungslose Klicks; Fehler nach
  Wechsel der Auswahl (anderes Logo, „Kein Logo“, Aus-/Einblenden) und bei gleicher Auswahl; Gründe nach „Einblenden“ samt später Antwort.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 05:49:04 +00:00
ClaudeandClaude Opus 5.5 02b01c9deb fix(logos): Picker hält den Fokus bei „Mehr laden“/„Nochmal versuchen“, Raster als Liste, Cursor ohne Vorgänger
- logo-picker.ts: Verschwindet „Mehr laden“ oder „Nochmal versuchen“, während es den Fokus hat, geht der Fokus weiter statt auf der Seite verloren zu
  gehen: an den Knopf, der seinen Platz einnimmt („Nochmal versuchen“ nach einem Fehler beim Nachladen), sonst an die erste Kachel, die der Ladevorgang
  angehängt hat, sonst an die Überschrift „Logo unten rechts“ (jetzt tabindex −1). Ein gesperrter Knopf verliert den Fokus, darum bleibt „Mehr laden“
  während des Ladens bedienbar, solange es ihn hat (ein zweiter Druck ist wirkungslos, load prüft es); ohne Fokus bleibt es gesperrt wie bisher.
  Wer den Fokus woanders hat, bekommt ihn nicht weggenommen.
- logo-picker.ts: das Raster hat role="list" (list-style: none nimmt dem <ul> sonst die Listensemantik) und kein eigenes aria-labelledby mehr; der
  Name steht an der Gruppe. Das lokale `const select` in createTile verdeckte die Funktion select() und heißt jetzt selectButton.
- logo-picker.ts, removeLogo: War das gelöschte Logo der Cursor und steht kein früheres mehr in der Liste, wurde der Cursor verworfen und „Mehr laden“
  verschwand samt aller weiteren Seiten. Jetzt beginnt die Ansicht wieder bei Seite 1 (load('reset')).
- Tests: Fokus in allen Fällen (letzte Seite, weitere Seite, Fehler, Retry bei leerer und gefüllter Liste, Fokus woanders, nur schon bekannte Logos,
  nach dem Aufräumen), role/aria-labelledby/tabindex, Cursor ohne Vorgänger (auch in der Admin-Ansicht „Gemeldet“) und die Fälle ohne Neuladen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 05:49:04 +00:00
ClaudeandClaude Opus 5.5 8167a18857 fix(a11y): Datei-Feld nativ „required“, ungültige Felder verweisen auf die Fehlerzeile
- logo-dialog.ts: das Datei-Feld trägt `required` statt `aria-required` (für ein Datei-Feld gilt aria-required nicht; das Formular hat novalidate und
  prüft selbst, es ändert sich nichts am Ablauf).
- logo-dialog.ts und publish-dialog.ts: die Fehlerzeile hat eine ID (logo-upload-error, publish-error); solange ein Feld ungültig ist, steht sie in
  seinem aria-describedby, hinter dem Hinweis des Feldes (Datei, Anzeigename, Tags). Ein Screenreader liest den Fehler so mit dem Feld vor.
  Beim nächsten Senden fällt der Verweis mit aria-invalid weg, der Hinweis bleibt. Im Dialog „Logo hochladen“ löscht auch eine neue Dateiwahl die
  Markierung aller Felder (sie löscht ja den Fehlertext, auf den sie verwiesen).
- editor/dom.ts: setFieldInvalid(control, errorId, invalid) für beide Dialoge; eigene Tests (dom.test.ts).
- Tests: required statt aria-required; ID einmalig; je Feld Verweis samt Hinweis in Reihenfolge, Rücknahme, neue Dateiwahl; der vorhandene Hilfsfall
  `invalid` beider Dialoge prüft den Verweis für jedes Feld mit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 05:49:04 +00:00
ClaudeandClaude Opus 5.5 29c9b2efaf fix(logos): SVG-Wurzelelement richtig finden, Quell-Canvas und Pixelpuffer früh freigeben, Konstanten umbenannt
- prepare-logo.ts, sizeSvg: Das Wurzelelement wird nicht mehr mit der ersten Fundstelle von „<svg“ gesucht. rootStart überspringt Byte-Order-Marke,
  Leerraum, XML-Deklaration und Anweisungen (<?…?>), Kommentare und den DOCTYPE (auch mit eigener DTD in […] samt Zeichenketten, Kommentaren und
  Anweisungen darin) und verlangt dahinter `<svg` mit Leerraum, „/“ oder „>“. Ein auskommentiertes <svg …> oder eines im DOCTYPE wurde sonst
  umgeschrieben, ein `<svg:svg …>` (Präfix) oder `<svg-x` zerstörte das Tag. Beides bleibt jetzt unverändert (quadratisch), wie ein Text ohne
  Wurzelelement. Ein Durchlauf ohne Zurücksetzen, auch ein feindlicher Prolog braucht nur linear viel Zeit.
- prepare-logo.ts, prepareLogo: renderLogo zeichnet das Quellbild auf den Quell-Canvas, liest den Pixelpuffer einmal als Argument von planLogo
  (keine Referenz danach) und setzt den Quell-Canvas im finally auf 0 × 0, noch vor dem PNG und auch bei Fehlern. Das Ergebnis ist gleich.
- prepare-logo.ts: LOGO_MAX_W/LOGO_MAX_H (720/250, die Dateigrenze) heißen LOGO_FILE_MAX_W/LOGO_FILE_MAX_H und kollidieren nicht mehr im Namen mit
  LOGO_MAX_H aus card/geometry.ts (125, Höhe auf der Karte). Einziger Verwender ist der Test.
- Tests: Prolog-Fälle (Kommentar, Anweisung, DOCTYPE mit DTD, BOM, mehrere, Präfix, Bindestrich, nicht endender Prolog, feindliche Eingaben mit
  Zeitgrenze); Quell-Canvas ist frei (auch vor toBlob, bei leer, zu klein, Zeichenfehler, fehlendem Ergebnis-Canvas, fehlendem PNG), der Pixelpuffer
  wird einmal gelesen. Die vorhandenen Größenprüfungen lesen die Größe beim Zeichnen (`used`), weil der Quell-Canvas am Ende 0 × 0 ist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 05:49:04 +00:00
ClaudeandClaude Opus 5.5 76c3c3aea2 fix(logos): nur „Datei nicht gefunden“ gilt als fehlendes Logo, nicht genutzte Bilder werden freigegeben
- logo-asset.ts: isFileMissing galt für jedes 404 und für jeden Fehler mit dem Typ storage_file_not_found. Jetzt nur noch ein 404 mit diesem Typ
  (oder ohne bzw. mit leerem Typ), wie isRowMissing in logos.ts. Ein fehlender Bucket (404 storage_bucket_not_found) und andere Fehler sind echte
  Fehler: sie werden weitergereicht und nicht zwischengespeichert, statt stillschweigend „Logo nicht verfügbar“ zu melden.
- logo-asset.ts: dekodierte ImageBitmaps, die nicht bleiben, werden mit close() freigegeben: das Zwischenbild eines einfarbigen Logos nach dem
  Einfärben (auch wenn das Füllen scheitert), Bilder über der Pixelgrenze und Bilder ohne Fläche. Das Bild eines mehrfarbigen Logos bleibt offen,
  es ist das Ergebnis; ein `Image` (Ersatzweg ohne createImageBitmap) hat kein close und wird übersprungen.
- Tests: die Fehlerfälle (Bucket, Projekt, andere 404, Typ ohne 404) mit dem Nachweis, dass nichts zwischengespeichert wird; close() in allen
  Fällen samt Reihenfolge (erst nach dem Einfärben).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 05:32:51 +00:00
ClaudeandClaude Opus 5.5 738358fcac fix(logos): hochgeladenes Logo trägt die Datei-ID der Datei und die ID des Dienstes
Die API vergibt die Zeilen-ID des Logos jetzt selbst (Hash aus Konto und Datei, nicht mehr die Datei-ID). uploadPreparedLogo baute das neue Logo
aber noch mit `fileId: id`: das Bild des frisch hochgeladenen Logos wäre aus einer Datei geladen worden, die es nicht gibt.

- upload-logo.ts: `id` aus der Antwort der API, `fileId` aus der angelegten Datei (`$id`). Kommentare ohne „Zeilen-ID = Datei-ID“ (auch logo-api.ts).
- Test: Dienst-ID und Datei-ID sind verschieden und werden nicht vertauscht (Fakes und Standard-Abhängigkeiten).
- Sonst nirgends in web/src: Auswahl und Karten-Konfiguration nehmen `logo.id`, das Bild kommt über `logo.fileId` (loadLogoBlob), beides lief schon getrennt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 05:08:39 +00:00
ClaudeandClaude Opus 5.5 4232afdf35 fix(logos): unerwartete Fehler beim Vorbereiten eines Logos in die Konsole
Schlussreview F6. prepareLogo machte aus jedem Fehler außer seinen eigenen („leer“, „zu klein“ …) die Meldung „Das Logo konnte nicht
gelesen werden.“ und verlor dabei den Anlass (Fehler des Browsers beim Zeichnen, Dekodierfehler): bei einer Rückmeldung eines Nutzers
war nichts mehr nachzuvollziehen.

- Vor dem Umwandeln steht jetzt genau eine console.warn mit dem Originalfehler. Der Text für den Nutzer bleibt unverändert (in beiden
  Sprachen). Fehler mit eigener Meldung (LogoError: Typ, Größe, zu viele Pixel, leer, zu klein, kein Canvas, kein PNG) sind erwartet und
  warnen nicht.
- Tests: Browserfehler beim Zeichnen (Raster und SVG), Dekodierfehler, Englisch, keine Warnung bei den erwarteten Fehlern und bei
  Erfolg. Die Tests der Gruppe „Bild und Canvas gestellt“ ersetzen console.warn durch eine Attrappe, damit die Ausgabe sauber bleibt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 01:20:28 +00:00
ClaudeandClaude Opus 5.5 df1cf29d42 fix(logos): 404 „logoGone“ beim Löschen gilt als Erfolg
Schlussreview F5. Geht die Antwort einer Löschanfrage verloren und der Nutzer klickt noch einmal auf „Löschen“, antwortet der Dienst mit
404 `logoGone` (das Logo ist ja schon weg). Die Infozeile zeigte dann „Dieses Logo ist nicht (mehr) verfügbar.“ als Fehler, die Kachel
blieb stehen.

- Dieser Fehler (ApiError mit Status 404 und Schlüssel `logoGone`) gilt wie ein gelungenes Löschen: forgetLogo, die Kachel verschwindet,
  eine Auswahl wird aufgehoben (onSelect(null)), kein Fehlertext – wie bei „Zurückziehen“ einer schon zurückgezogenen Karte.
- Jeder andere Fehler bleibt einer, auch ein 404 mit anderem oder ohne Schlüssel (etwa ein Dienst, der die Anfrage nicht kennt).
- Tests in logo-info.test.ts (Hochlader, Admin, Seite verlassen, Fehler, die Fehler bleiben) und im Picker mit Raster und Auswahl.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 01:19:17 +00:00
ClaudeandClaude Opus 5.5 ec4e80c62c fix(logos): „Mehr laden“ beginnt von vorn, wenn der Cursor nicht mehr existiert
Schlussreview F2. Hat jemand das Logo gelöscht oder ausgeblendet, hinter dem „Mehr laden“ ansetzt (der Cursor), antwortet Appwrite mit
400 `general_cursor_not_found`. „Nochmal versuchen“ wiederholte dann denselben Cursor und scheiterte immer wieder.

- Dieser Fehlertyp beim Nachladen löst jetzt `load('reset')` aus: die erste Seite der aktuellen Ansicht (gleiche Sortierung, gleicher Filter)
  wird neu geladen, die Kachel des gewählten Logos bleibt stehen. Kein Fehlertext, kein „Nochmal versuchen“ für den toten Cursor.
- Jeder andere Fehler beim Nachladen und jeder Fehler beim Laden der ersten Seite bleibt wie bisher (Meldung und „Nochmal versuchen“);
  die erste Seite hat keinen Cursor, ein Neuladen im Kreis ist ausgeschlossen.
- Tests mit einem Fehler {code: 400, type: 'general_cursor_not_found'}: Neuladen von Seite 1 mit Anfragefolge, Ansicht „Gemeldet“ bleibt,
  gewählte Kachel bleibt, Neuladen scheitert selbst, anderer Fehlertyp, Fehlertyp beim ersten Laden.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 01:12:59 +00:00
ClaudeandClaude Opus 5.5 202254c09e fix(editor): Karte ohne Logo zeichnen, wenn ein Logo nicht lädt; erneute Wahl versucht es noch einmal
Schlussreview F1. Scheiterte das Laden der Bilder (Netz, Server), bricht draw() ab, solange logos.key nicht zur Auswahl passt: Vorschau
und Miniaturen blieben leer, und ein erneuter Klick auf dieselbe Kachel änderte nichts, weil failedKey dem Schlüssel entsprach.

- Nach einem echten Ladefehler lädt der Editor einmal die Marke allein (loadCardAssets mit logo: null) und zeichnet die Karte damit ohne
  Logo (`failed`); scheitert auch das, fehlt die Marke ebenfalls. LOGO_ERROR bleibt stehen, die Auswahl auch. Ein anderes Logo wird nie mit
  den Bildern der gescheiterten Auswahl gezeichnet (der Schlüssel entscheidet wie bisher).
- Der Picker meldet jeden Klick, auch auf das schon gewählte Logo: ist es das Logo, dessen Laden scheiterte, hebt der Editor die Sperre
  (failedKey) auf und versucht es noch einmal. Ein geladenes Logo wird dabei nicht neu geladen, ein Fehlschlag wiederholt sich nicht im Kreis.
- Tests: Karte ohne Logo nach dem Fehler (Vorschau und vier Miniaturen), Wiederholung per Klick (Erfolg und erneuter Fehlschlag, kein Loop),
  Marke lädt auch nicht, anderes Logo nach dem Fehler, Klick auf geladenes Logo lädt nichts neu; Picker meldet den Klick auf die gewählte
  Kachel. Zwei bestehende Tests angepasst: „nicht gezeichnet nach Fehler“ gilt nicht mehr, und die Zahl der loadCardAssets-Aufrufe zählt
  jetzt den zusätzlichen Lauf für die Marke nicht mehr mit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 01:12:11 +00:00
ClaudeandClaude Opus 5.5 7e66cd0f0f feat(i18n): Community, Startseite und Meine Karten auf Englisch
Task 11: alle sichtbaren Texte von community/, pages/home-page.ts, pages/community-page.ts, pages/cards-page.ts und pages/format.ts
(Texte, aria-label, title, Platzhalter, Alt-Texte, Rückfragen, Meldungen) gehen durch t()/msg(); en.ts bekommt 82 Einträge (214 → 296).

- Konstanten bleiben deutsche Strings per msg() und gehen beim Anzeigen durch t() (publish.ts, publish-dialog.ts, gallery.ts und die Seiten);
  geworfene Fehler tragen weiter den deutschen Text, damit die Vergleiche gehen. Funktionen, die Text für Nutzer liefern, übersetzen beim
  Zurückgeben (validatePublishForm, publishFailureText, formatDate, reportsLabel). Template-Literale sind Platzhalter geworden ({max},
  {reason}, {name}, {title}, {query}, {count}); der deutsche Text bleibt Zeichen für Zeichen derselbe.
- API-Fehler: jeder ApiError wird mit apiErrorText() angezeigt (Veröffentlichen-Dialog, Detailseite, Meine Karten), nach dem Code des Dienstes
  statt nach dem deutschen Servertext. Logik, die err.message vergleicht (publishCard, 404 beim Zurückziehen), bleibt unverändert.
- Bekannte Reste aus Task 10 behoben: LOGIN_NEEDED ging in publish-dialog.ts, community-page.ts und cards-page.ts ohne t() an die Oberfläche;
  die Meldungen zu Bild lesen/Pixelgrenze/fehlendem Logo (cards-page.ts, GERMAN_MESSAGES in publish-dialog.ts) erscheinen jetzt übersetzt;
  die Zahl der Meldungen in der Logo-Infozeile („3 reports“).
- pages/format.ts: formatDate liefert Deutsch dd.mm.yyyy und Englisch „Oct 4, 2026“ (Intl.DateTimeFormat en-US), reportsLabel „1 report“ /
  „3 reports“. „Herausgeber“ heißt als Bank „Issuer / bank“.
- logos/prepare-logo.ts: der Helfer problem() ist weg, die sechs Stellen schreiben new LogoError(t(MELDUNG)) selbst (gleiches Verhalten);
  so sieht der neue Prüfer unten jede Verwendung einer Konstante.
- i18n/coverage.test.ts: Die Heuristik für deutsche Literale deckt jetzt die ganze Oberfläche ab (account/, card/, community/, editor/, logos/,
  pages/, shell/, main.ts, i18n/api-messages.ts). Sie erkennt zusätzlich Wörter mit Klammer („Ausgeblendet (gemeldet)“) und nimmt rel und
  Namen in Klammerschreibweise (headers['X-Aircard-Jwt']) aus. Acht begründete Ausnahmen kommen dazu (zwei AppwriteException-Meldungen,
  vier interne Logo-Fehler, der Sprachknopf in der Zielsprache). Neu: ein Prüfer über den Syntaxbaum, der verlangt, dass jede Konstante
  mit msg() nur in t(), new Error(), Vergleichen, case und Listen/Mengen vorkommt (auch über Importe und Umbenennungen); ohne Ausnahmen.
- Tests: format.test.ts (beide Sprachen), Englisch-Blöcke in publish-dialog, gallery-grid, home-page, community-page, cards-page und
  logo-info (Texte, Fehlerwege, „Publishing failed – This card is already published.“, kein deutscher Wörterbuchtext auf der Seite). Die
  Hilfen texts()/germanLeft() liegen jetzt in i18n/english-check.ts (vorher in english-ui.test.ts). Der bestehende Englisch-Test in
  cards-page.test.ts sucht den Knopf „Download“ statt „Herunterladen“.
- Vitest 53 Dateien, 1812 Tests grün (vorher 1779), tsc und Build sauber.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 00:34:10 +00:00
ClaudeandClaude Opus 5.5 a75be2d44c feat(logos): Infozeile mit Melden, Löschen und Admin-Aktionen
Task 9: die Infozeile zum gewählten Logo unter dem Raster (Spec § 3 und § 6).

- logos/logo-info.ts (neu): „<Name> · von <Anzeigename> · ♥ <Zahl>“ und „Melden“ (auch für Gäste; ein ausgeblendetes Logo lässt
  sich nicht melden). Der Hochlader sieht „Löschen“ mit Rückfrage (Titel mit Namen, Folgen im Text), Admins „<n> Meldungen“ mit den
  Gründen als Text (listLogoReports, einmal je Logo und Stand), „Ausblenden“/„Einblenden“ und „Löschen“. Gelöscht: deleteLogo,
  forgetLogo, Kachel weg, onSelect(null); ein-/ausgeblendet: setLogoVisibility, forgetLogo (ein Null-Bild bleibt nicht im Zwischen-
  speicher), die Zeile des Pickers wird fortgeschrieben (Einblenden setzt reportCount auf 0). Gewähltes Logo nicht auffindbar: „Das
  gewählte Logo ist nicht mehr verfügbar.“ (ein Netzfehler von getLogo sagt nichts). Zugleich die Meldezeile (role="alert") für Fehler
  von Herz, Hochladen und diesen Aktionen; ein Wechsel der Auswahl räumt sie ab. Immer nur eine Aktion zugleich; die Rechte werden
  nach der Rückfrage noch einmal geprüft; der Aufbau steht einmal da, update() ändert nur Texte und Sichtbarkeit (Fokus bleibt).
- logos/logo-picker.ts: hängt die Infozeile als Letztes unter das Raster (info.el). Herz- und Upload-Fehler gehen in die Infozeile,
  der Statusbereich des Pickers bleibt den Ladefehlern der Liste vorbehalten (ein Klick auf ein Herz löscht ihren Text nicht mehr).
  Aus dem Review von Task 8: requireLogin, das wirft, zeigt den Grund statt einer unbehandelten Ablehnung; select() nach dispose()
  ist wirkungslos. Löschen entfernt Kachel, Zeile, Herz und Eintrag der Liste, setzt den Cursor von „Mehr laden“ auf das Logo davor
  (Appwrite lehnt einen Cursor auf eine gelöschte Zeile ab), hebt die Auswahl über onSelect(null) auf und holt den Fokus nach
  „Kein Logo“, wenn er auf „Löschen“ lag. LogoPickerDeps erweitert um reportLogo, deleteLogo, setLogoVisibility, listLogoReports,
  confirm und reportDialog.
- community/report-dialog.ts: subject 'card' (Standard, unverändert) oder 'logo': Überschrift „Logo melden“, Einleitung „dieses
  Logo“, vier Gründe ohne „Echte Kartendaten“, Duplikat-Dank „Danke, du hast dieses Logo schon gemeldet.“. Der ganze Dialog geht
  durch t()/msg(); Fehler des Dienstes über apiErrorText.
- logos/logos.css: das Herz sitzt in der Textzeile unter der Bildfläche (nicht mehr auf dem Logo, folgt dem Theme), die Kachel füllt
  die Zelle (height: 100%), damit „Kein Logo“ so hoch ist wie sein Nachbar; Stile der Infozeile.
- editor/editor-page.ts: Aus dem Review von Task 8: der Ladefehler der Logos verschwindet, sobald ein Logo danach lädt, aber nur
  der, den der Logo-Zustand selbst ins Fehlerfeld geschrieben hat; jeder andere Text im Feld bleibt.
- i18n/en.ts: englische Texte zu allen neuen Beschriftungen und zum Melden-Dialog.
- Tests: logo-info.test.ts (neu), logo-picker.test.ts (Infozeile, Löschen, Ein-/Ausblenden, Fehler, Cursor, Fokus), report-dialog.test.ts
  (Logo und Englisch), editor-page.test.ts (Fehlerfeld). Vitest 52 Dateien, 1764 Tests grün, tsc und Build sauber.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 23:21:33 +00:00
ClaudeandClaude Opus 5.5 4cf9fe192d feat(editor): Community-Logos unten rechts mit Herzen und Sortierung
Task 8: der Bereich „Logo unten rechts“ in der Optionsspalte des Editors (Spec § 3 und § 5).

- logos/logo-picker.ts (neu): createLogoPicker baut Kopf (Überschrift, Plakette „Community“, „+ Hochladen“, Satz zur Community), den
  Umschalter „Beliebt“/„Neu“ (Admins zusätzlich „Gemeldet“/„Ausgeblendet“, je eine Ansicht mit sort und filter) und das Raster:
  erste Kachel „Kein Logo“, je Logo eine Kachel mit Bild, Name, „von <Anzeigename>“ bzw. „von Anonym“ und einem eigenen Herz-Knopf
  neben dem Auswahlknopf. „Mehr laden“ hängt die nächste Seite an (Duplikate nur einmal), Leer-Text und Ladefehler mit „Nochmal
  versuchen“ (wiederholt genau den gescheiterten Schritt); nur die jüngste Antwort einer Ansicht zählt. Die Auswahl gehört dem
  Editor: ein Klick meldet onSelect, markiert wird über select(); ein Logo außerhalb der Liste wird nach dem Laden der ersten Seite
  per getLogo geholt und als zweite Kachel angeheftet (steht es in der Liste, gibt es keine Anfrage). selected() liefert die Zeile
  für die Infozeile in Task 9. Herzen: optimistisch mit Rücksprung und Fehlertext (role="alert"); solange eine Anfrage für ein
  Logo läuft, bleiben weitere Klicks auf sein Herz wirkungslos; Gäste öffnen erst den Login (abgebrochen: nichts passiert, kein
  Fehlertext); die Herzen einer Seite kommen mit genau deren IDs, beim Anmelden für alle gezeigten Logos, beim Abmelden aus; eine
  späte Antwort überschreibt kein inzwischen selbst geschaltetes Herz. „Hochladen“ meldet Gäste zuerst an, öffnet den Dialog (mit
  Signal, das beim Verlassen der Seite schließt), setzt das neue Logo als erste Kachel, ruft rememberLogo und onSelect(id).
- logos/logo-image.ts (neu): das PNG einer Kachel laden, auf die Pixelgrenze prüfen (MAX_LOGO_PIXELS, so wie checkGalleryImage die
  Galerie-Bilder prüft; darüber, nicht lesbar oder Ladefehler: neutraler Platzhalter statt Absturz) und zeigen: mehrfarbig als <img>,
  einfarbig als weiße Maske, immer auf dem dunklen Kartengrund. Die Object-URL gibt die zurückgegebene Funktion frei; eine Datei, die
  erst danach eintrifft, wird nicht mehr angezeigt.
- logos/logo-asset.ts: MAX_LOGO_PIXELS wird exportiert (eine Quelle für Renderer und Kacheln).
- logos/logos.css: Kopf, Umschalter, Raster (ab 1024 px zwei Spalten, sonst auto-fill ab 140 px), Kacheln mit Bildfläche auf #1B2738,
  Herz (gefüllt, wenn gedrückt), Leer- und Fehlerzeilen; Bild und Maske teilen sich die Regeln mit der Dialog-Vorschau.
- editor/editor-page.ts: der Picker sitzt in options.logoSlot, die Wahl setzt config.logo, sync ruft picker.select(config.logo), das
  Verlassen der Seite ruft dispose. Übernommen aus dem Review von Task 5: failedKey blockiert ein Logo nicht mehr für immer, eine
  andere Wahl (Logo, Details, Marke) hebt die Sperre auf; derselbe Schlüssel wird weiter nicht im Kreis neu versucht.
- editor/dom.ts: Symbole heart und plus. i18n/en.ts: englische Texte zu allen neuen Beschriftungen.
- Tests: logo-picker.test.ts (Aufbau, Sortierung, Mehr laden, Auswahl und angeheftetes Logo, Herzen mit Rennen und Login, Leer und
  Ladefehler, Hochladen, Kachelbilder, dispose, Englisch), logo-image.test.ts, editor-page.test.ts (Verdrahtung, Regressionstest zu
  failedKey). Vitest 51 Dateien, 1666 Tests grün, tsc und Build sauber.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 22:40:37 +00:00
ClaudeandClaude Opus 5.5 e22919f4dc feat(logos): Dialog „Logo hochladen“ und gemerkter Anzeigename
Task 7: der Dialog zum Hochladen eines Community-Logos (Spec § 4) und der zuletzt benutzte Anzeigename, den er mit dem
Veröffentlichen-Dialog teilt.

- logos/logo-dialog.ts (neu): logoUploadDialog öffnet ein eigenes <dialog> nach dem Muster von publish-dialog.ts (showModal, Fokus
  zurück an den Auslöser, Texte nur als Text; beim Hochladen sind Abbrechen, Esc und Hintergrund wirkungslos, nur das Signal schließt
  immer; ein zweites Absenden wird ignoriert). Die gewählte Datei geht erst durch validateLogoFile, dann durch prepare (Standard
  prepareLogo); währenddessen ist „Hochladen“ gesperrt, eine neue Wahl macht die laufende Verarbeitung hinfällig. Das fertige PNG
  erscheint als Object-URL zweimal, auf dunklem und hellem Grund; „Einfarbig“ (nur wählbar mit transparenten Bereichen, bei jeder
  neuen Datei zurückgesetzt) zeigt stattdessen eine Maske auf dieselbe URL, gefüllt mit TONE_COLOR (Weiß bzw. #111111). Die URL wird
  beim Schließen und beim Ersetzen freigegeben. Der Name wird aus dem Dateinamen ohne Endung vorbelegt (höchstens 40 Zeichen, nie
  mitten in einem Emoji), ein selbst getippter Name bleibt. Hochladen über uploadPreparedLogo mit withLogin (abgelaufene Sitzung →
  Anmeldung); Fehler erscheinen als „Hochladen fehlgeschlagen – <Grund>“ (ApiError über apiErrorText, Texte von prepareLogo im
  Wortlaut, sonst errorMessage), eine abgebrochene Anmeldung als „Bitte melde dich an“. validateLogoForm prüft Datei, Name (leer,
  Steuerzeichen, 40), Anzeigename (Steuerzeichen, 30) und Pflicht-Häkchen mit den Texten aus Spec § 4 und § 11.
- logos/logos.css (neu): Breite des Dialogs, Datei-Feld, die zwei Vorschauflächen und die Maske; Hülle, Felder und Knöpfe kommen aus
  community.css und account.css.
- account/display-name.ts (neu): DISPLAY_NAME_KEY (aircard.displayName), rememberedDisplayName und rememberDisplayName; jeder Zugriff
  in try/catch, ein leerer Name entfernt den Schlüssel.
- community/publish-dialog.ts: der Anzeigename ist aus dem gemerkten Namen vorbelegt und wird nach erfolgreichem Veröffentlichen
  gemerkt; CONTROL_CHARACTERS und DISPLAY_NAME_MAX werden exportiert (der Logo-Dialog prüft mit denselben Regeln).
- i18n/en.ts: englische Texte zu allen neuen Beschriftungen, Hinweisen und Meldungen („Einfarbig“ heißt „Single color“).
- Tests: logo-dialog.test.ts (validateLogoForm, Aufbau, Dateiwahl mit Vorschau, Namensvorschlag, Maske, Object-URLs, veraltete
  Verarbeitung, Prüfung, Senden mit Doppelklick und Sperren, Fehlertexte in beiden Sprachen, Standardwerte, Schließen),
  display-name.test.ts, publish-dialog.test.ts (Vorbelegung und Speichern). Vitest 49 Dateien, 1566 Tests grün, tsc und Build sauber.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 22:00:12 +00:00
ClaudeandClaude Opus 5.5 56d841b8e2 fix(logos): SVG-Größe aus Breite und Höhe mit ergänzter viewBox, Pixelgrenze vor dem Dekodieren
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>
2026-10-04 21:26:30 +00:00
ClaudeandClaude Opus 5.5 0f66136d82 feat(logos): Logo zuschneiden, verkleinern und hochladen
Task 6: Verarbeitung und Hochladen eines Logos als Ablauf ohne Oberfläche (der Dialog folgt in Task 7).

- logos/prepare-logo.ts (neu): validateLogoFile (PNG, JPG, WebP, SVG; ohne Typ zählt die Endung .svg; höchstens 5 MB), die reinen
  Helfer trimBox (Alpha ≤ 8 gilt als leer), fitWithin (nie größer, gerundet, mindestens 1), svgRasterSize (längste Seite genau 2048,
  ungültiges Seitenverhältnis quadratisch), hasTransparency und planLogo (Ausschnitt und Zielgröße, „leer“ und „zu klein“ nach der
  fertigen Größe) sowie sizeSvgRoot. prepareLogo fügt sie zusammen: Raster über createImageBitmap (Breite × Höhe wird vor dem
  Zeichnen gegen 40 Megapixel geprüft, das Bild danach freigegeben), SVG nur als Bild aus einer Object-URL (Seitenverhältnis aus
  der viewBox, Größe ins Wurzelelement geschrieben), Zuschnitt, Verkleinern auf höchstens 720 × 250, PNG, hasTransparency auf den
  Endpixeln. Jeder Fehler beim Dekodieren oder Zeichnen heißt „Das Logo konnte nicht gelesen werden.“; alle Meldungen sind schon
  übersetzt (t() beim Auslösen), die deutschen Konstanten LOGO_*_MESSAGE stehen über msg().
- sizeSvgRoot ersetzt eine eigene width/height des Wurzelelements, statt ein zweites Attribut anzuhängen (doppelte Attribute machen
  ein SVG ungültig, so scheiterten viele echte SVGs). Es macht genau einen Versuch ab dem ersten „<svg“: ein Suchmuster, das an
  jedem „<svg“ neu ansetzt, brauchte bei einer Datei voller offener „<svg x="“ quadratisch viel Zeit (160 kB: 6 s).
- logos/upload-logo.ts (neu): uploadPreparedLogo legt logo.png mit read("any") und delete("user:<ich>") in den Bucket logo-images,
  ruft uploadLogo und liefert das neue CommunityLogo (ID = Datei-ID, likeCount 0). mono gilt nur bei durchsichtigen Stellen. Wie
  beim Veröffentlichen wird nur bei eindeutigem Scheitern aufgeräumt (genau die Datei dieses Versuchs, ein Fehler dabei überdeckt
  nie den eigentlichen), bei offenem Ausgang bleibt die Datei. Ausnahme: 409 mit serverCode logoTaken löscht nichts, ein anderes
  Logo trägt diese Datei-ID.
- i18n/en.ts: englische Texte zu den sechs neuen Meldungen.
- Tests: prepare-logo.test.ts (Helfer einzeln, dazu prepareLogo mit gestelltem Dekoder und Canvas: Zuschnitt, Skalierung, Rasterung,
  Fehlertexte, 40-Megapixel-Grenze, SVG nie in der Seite) und upload-logo.test.ts (Reihenfolge, Rechte, Felder, mono, Aufräumen je
  Status, logoTaken, Standard-Abhängigkeiten gegen den Bucket logo-images). Vitest 47 Dateien, 1452 Tests grün, tsc sauber.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 21:05:38 +00:00
ClaudeandClaude Opus 5.5 0be5abd954 feat(card): Marken-Logos entfernt, Community-Logo unten rechts im Renderer
- card/config.ts: network und das Apple-Logo entfallen. CardConfig.logo ist die ID eines Community-Logos (Muster ID_PATTERN,
  sonst null), DEFAULT_CONFIG.logo = null, serializeConfig schreibt {v, template, logo, details, brand, crop}. Ein altes network
  wird ignoriert, brand 'apple' fällt auf den Standard. ID_PATTERN steht jetzt hier (die reine Konfiguration prüft die Logo-ID,
  ohne Appwrite zu laden), account/cards.ts reicht sie weiter; single-sources.test.ts sorgt für genau eine Definition.
- card/geometry.ts: fitLogo und LOGO_MAX_H (125) ersetzen fitNetwork, Layout.network heißt Layout.logo.
- card/marks.ts: nur noch die AirCard-IOS-Marken (BRAND_MARKS, getBrandMark, svgAspect, tintSvg, loadMarkImage). Die 19 SVGs
  der Netzwerke und von Apple sind per git rm gelöscht, ein Test hält assets/marks auf die zwei AirCard-Dateien.
- card/render.ts: LogoAsset, CardAssets.logo statt network, LoadedAssets.logoMissing. loadCardAssets(config, image, loadLogo =
  loadLogoAsset): ohne Logo kein Aufruf, null vom Lader ergibt logoMissing, ein Ladefehler wirft. Der Renderer zeichnet das
  Logo mit fitLogo(w / h) unten rechts, wenn Vorlage und Bilder eines haben.
- logos/logo-asset.ts (neu): loadLogoAsset(id, tone) holt Zeile (getLogo) und PNG (loadLogoBlob) und dekodiert mit
  createImageBitmap, sonst über ein Image. Einfarbige Logos werden in einem eigenen Canvas mit source-in in der Details-Farbe
  gefüllt, mehrfarbige bleiben. Fehlende Zeile, ausgeblendetes Logo (auch für Hochlader und Admins), Datei 404, unlesbare
  Datei und mehr als 1 000 000 Pixel ergeben null. Je (id, tone) ein Ladevorgang, ein Fehler bleibt nicht im Cache.
  rememberLogo, forgetLogo und LOGO_MISSING nach der Schnittstelle des Plans.
- editor/options-panel.ts: keine Netzwerk-Kacheln mehr, Überschrift „Logo oben links“ mit Symbol, Text und Kein Logo (ohne
  Apple), dazu logoSlot als leerer Platz oben für Task 8 (editor.css: Details rückt bei leerem Platz wie bisher nach oben).
- editor/editor-page.ts: logoKey aus Logo, Details und Marke, der Logo-Zustand hält missing. Fehlt das Logo, wird die Karte
  ohne es gezeichnet und der Hinweis LOGO_MISSING gezeigt, solange das so bleibt; ein Wechsel auf ein vorhandenes Logo oder
  auf kein Logo nimmt nur diesen Hinweis weg (PNG-Größe und „Bild fehlt“ stehen getrennt und bleiben). Kein LOGO_ERROR bei
  fehlendem Logo, nur bei echtem Ladefehler, auch beim Download.
- community/publish.ts: LOGO_MISSING_PUBLISH, renderForGallery (jetzt exportiert) bricht bei logoMissing vor dem Rendern ab,
  es wird nichts hochgeladen. publish-dialog.ts lässt den Text im Wortlaut durch.
- i18n/en.ts: englische Texte zu LOGO_MISSING, LOGO_MISSING_PUBLISH und „Logo oben links“.
- Tests: config, geometry, marks, render und die Fixtures von cards, theme, cards-page auf das neue Modell; neu
  logo-asset.test.ts (28) und options-panel.test.ts (10); editor-page (14 neue), publish und publish-dialog erweitert.
  Vitest 45 Dateien, 1319 Tests grün, tsc und Build sauber.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 20:32:45 +00:00
ClaudeandClaude Opus 5.5 3051b3b3f9 feat(logos): Datenschicht, API-Aufrufe und übersetzbare API-Fehler
- logos/logos.ts (Muster community/gallery.ts): CommunityLogo, toLogo, logoQueries (Beliebt/Neu, Admin-Filter Gemeldet und
  Ausgeblendet, Cursor), listLogos (nextCursor nur bei voller Seite von 24), getLogo (ungültige ID oder 404 -> null),
  myHearts (eigene Herzen einer Seite, ohne IDs keine Anfrage), listLogoReports (ohne Melder-Hash), loadLogoBlob.
- logos/logo-api.ts: uploadLogo, setHeart, reportLogo (ohne Anmeldung), deleteLogo, setLogoVisibility über apiPost.
  uploadLogo behandelt das Limit (429 tooManyLogos) wie publishCard als eindeutige Ablehnung (die hochgeladene Datei darf
  aufgeräumt werden); eine Antwort ohne id (und bei setHeart ohne Zustand und Zähler) ist 503 mit offenem Ausgang.
- account/appwrite.ts: IDS um logos, logoLikes, logoReports und logoBucket.
- community/api.ts: ApiError.serverCode (der Schlüssel aus {error, code}; code bleibt der HTTP-Status, message der deutsche
  Text). Client-Fehler tragen unavailable (auch 502/503/504 vom Proxy, vor dem Lesen des Bodys), generic, jwtUnsupported,
  alreadyPublished, tooManyPublications. Neu: unavailableError() und apiErrorText(err). Die deutschen Texte stehen einmal
  in api-messages.ts, die Vergleiche in publishCard laufen unverändert über err.message.
- i18n/api-messages.ts: API_MESSAGES (alle 33 Texte von MSG plus die Client-Codes generic und jwtUnsupported) und
  apiMessage: bekannter Code -> t(Text); unbekannt: Deutsch der Servertext, Englisch eine Meldung je Status (400, 403, 404,
  sonst allgemein). Der Code badRequest trägt auch die dynamischen Prüftexte des Dienstes, Deutsch zeigt dann den
  genaueren Servertext.
- i18n/en.ts: englische Texte zu allen API_MESSAGES.
- Tests: api-messages.test.ts lädt MSG aus deploy/api/common.js (createRequire) und prüft den Gleichlauf in beide
  Richtungen; logos.test.ts, logo-api.test.ts und neue Fälle in api.test.ts (der Helfer freshApi stellt die frische
  i18n-Instanz auf Deutsch). 156 neue Tests, die bisherigen unverändert grün (1285 gesamt).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 19:59:08 +00:00