Logo-Kacheln, Logo-Infozeile und die Detailseite einer Community-Karte zeigen den Benutzernamen des Kontos (Hilfsfunktion byline: Profil,
sonst alter Anzeigename, sonst „Anonym“). Die Namen kommen je geladener Seite in einer Abfrage nach, ein Fehler dabei bleibt ohne Fehlertext,
ein eigenes Umbenennen aktualisiert die eigenen Einträge sofort.
Die Felder „Anzeigename“ in den Dialogen „Logo hochladen“ und „Veröffentlichen“ fallen weg, ebenso account/display-name.ts und der
localStorage-Schlüssel; der Client schickt displayName: ''. Vor dem Hochladen und Veröffentlichen verlangt requireUsername() einen Namen
(„Abbrechen“: es passiert nichts); antwortet der Dienst mit 403 usernameRequired, öffnet sich der Namensdialog, danach ist ein neuer Versuch möglich.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Angemeldet wird per E-Mail: Das Namensfeld der Registrierung und das Feld im
Namensdialog sind ein Spitzname, kein Anmeldename. Mit `username` würden
Passwortmanager den Namen statt der E-Mail als Anmeldenamen speichern und später
in die Anmeldung füllen. Tests angepasst, dazu einer, der prüft, dass in keiner
Ansicht des Login-Dialogs ein Feld `autocomplete="username"` trägt.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Neu `account/profile.ts`: Muster, Sperrliste und Schlüssel (ein Test vergleicht
sie mit `deploy/api/common.js`), Profile lesen (`getUsername`, `usernamesFor`
gesammelt in einer Abfrage und zwischengespeichert, bei einem Fehler nur die
bekannten Namen), `isUsernameTaken`, `saveUsername` (POST /api/profil,
Zwischenspeicher + `onUsernameChange`), `myUsername`/`loadMyUsername` und der
Merker `aircard.usernamePrompted`. `IDS.profiles`.
- Neu `account/username-dialog.ts`: Dialog „Benutzername wählen“ in den Modi
prompt („Später“), required („Abbrechen“) und change (vorbelegt), Fehler am
Feld, Busy-Sperre, höchstens ein Dialog zur Zeit; dazu `requireUsername()`.
- Registrieren: Pflichtfeld „Benutzername“ vor der E-Mail. Muster, Sperrliste und
Verfügbarkeit werden vor dem Anlegen des Kontos geprüft (kein halbes Konto);
danach wird der Name gespeichert, scheitert das, schließt der Login-Dialog und
der Namensdialog fragt.
- Konto-Menü: „@name“ oben, die E-Mail klein darunter, Eintrag „Benutzername
ändern“ bzw. „Benutzername wählen“.
- main.ts: Name nach Start und Anmeldung lesen; ohne Namen einmal pro Sitzung der
Dialog „prompt“ (nicht, wenn der Name nicht lesbar ist).
- Texte in `en.ts`, Stile in `account.css`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Task 10: alle sichtbaren Texte von account/, editor/, card/ und der Seite „Passwort neu setzen“ (Texte, aria-label, title, Meldungen,
Rückfragen) gehen durch t()/msg(); en.ts bekommt 87 Einträge.
- account/: Konto-Menü, Login-Dialog (Titel, Reiter, Felder, Meldungen) und errors.ts (errorMessage übersetzt beim Zurückgeben).
validatePassword nutzt {min}/{max}. Der Login-Dialog wird nur einmal gebaut: die Beschriftung des Schließen-Knopfs wird bei jedem
Öffnen in der aktuellen Sprache gesetzt. LOGIN_NEEDED und DEFAULT_CARD_NAME bleiben deutsche Konstanten (msg), angezeigt werden sie
mit t(); der Standardname einer neuen Karte folgt damit der Sprache („My card“).
- editor/: Vorlagen-, Options- und Vorschauspalte, Bild-Upload, Speichern, Download, Rückfragen. Konstanten (upload.ts, download.ts,
editor-page.ts) bleiben deutsche Strings per msg(); validateImageFile, pngSizeWarning und das Speichern („Speichern fehlgeschlagen –
{reason}“) liefern den Text schon übersetzt, Fehler tragen weiter die deutsche Konstante (Vergleiche mit READ_MESSAGE/PIXELS_MESSAGE
gehen weiter) und werden beim Anzeigen mit t(error.message) übersetzt. Zoom „100 %“ / „100%“ und „Bearbeitet: {name}“ mit Platzhaltern.
- card/: Bezeichnungen der Marken (msg) und die Meldungen des Renderers (msg, Anzeige im Editor über t()).
- pages/reset-page.ts, account/login-dialog.ts: Die Meldung „Die Passwörter stimmen nicht überein.“ ist nicht mehr der Schlüssel zum
Fokus: es zählt die Bedingung selbst, nicht der Vergleich mit dem (nun übersetzten) Text.
- i18n/coverage.test.ts: Heuristik für deutsche Text-Literale außerhalb von t()/msg() (Umlaut/ß, mindestens zwei Wörter, einzelnes
großgeschriebenes Wort; gelesen wird der Syntaxbaum) für account/, editor/, card/ und pages/reset-page.ts; Ausnahmen mit Grund, jede
muss noch zutreffen; Scanner mit eigenen Tests. Die Liste der Bereiche ist eine Konstante für Task 11.
- i18n/english-ui.test.ts (neu): Stichproben auf Englisch (Konto-Menü, Login-Dialog, Passwort-Seite, Editor inkl. Fehlerwege) und
„kein deutscher Wörterbuchtext auf der englischen Seite“.
- test-setup.ts: Wer Module mit vi.resetModules() frisch lädt, bekam eine neue Kopie von i18n.ts mit der Browsersprache (happy-dom: en-US);
der Browser meldet im Test jetzt Deutsch, damit auch diese Tests deutsch beginnen. main.test.ts: nach dem Wechsel auf Englisch heißt
der Knopf des Konto-Menüs „Sign in“.
- Tests: Vitest 53 Dateien, 1779 Tests grün, tsc und Build sauber.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
- 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>
- 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>
- Router: onRouteChange meldet jede neue Seite (vor dem Aufbau). Die Kopfzeile markiert damit den aktuellen Navigationslink
mit aria-current="page" („Community“ auch auf der Detailseite) und leert das Suchfeld, wenn die neue Seite nicht die
Startseite mit ?q= ist (bisher blieb „testbank“ auf /gestalten stehen).
- Spenden-Knopf in der Kopfzeile trägt title „Unterstützen“ (zwischen 600 und 1179 px nur Symbol).
- .detail-msg:empty und .auth-msg:empty sind nicht mehr display: none (Live-Regionen standen nicht im Zugänglichkeitsbaum),
sondern ohne Platz im Layout, aber gerendert; Test liest die CSS-Dateien.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
formatDate liegt jetzt in pages/format.ts und wird von Meine Karten und der
Detailseite genutzt; LOGIN_NEEDED kommt aus account/with-login.ts. Ausblenden,
Löschen und Zurückziehen prüfen Admin- bzw. Autor-Recht beim Ausführen (auch nach
der Rückfrage). Ein werfendes showModal im Melden-Dialog zeigt einen Hinweis statt
eines unbehandelten Fehlers.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- gallery.ts: Galerie aus Appwrite lesen (Abfragen für Liste, Suche, Cursor, Admin-Filter),
getGalleryCard (404 und ungültige ID ergeben null), eigene Veröffentlichungen, Meldungen, Bilder
- api.ts: Aufrufe von aircard-api mit JWT (Cache je Nutzer, 10 Minuten, geleert bei Auth-Änderung,
genau eine Wiederholung bei 401), ApiError mit code, Netz-/Proxy-Fehler als 503
- publish.ts: rendern, zwei Uploads in gallery-images, API; bei Fehler genau die zwei Dateien
dieses Versuchs löschen; normalizeTags
- cards.ts: eigenständiges fetchFile(bucketId, fileId); CardsApi.fetchFile bleibt Hülle
- appwrite.ts: IDS für gallery, reports, gallery-images
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Router: Routen home, gestalten, cards, community/<id>, reset; Umleitung
/?karte=<id> nach /gestalten?karte=<id>; Titel je Route (titleFor);
Hash-Ziele (/#community) scrollen den Anker ins Bild, nur-Hash-Wechsel
bauen die Seite nicht neu auf.
- Kopfzeile: Navigation, Suche (Enter -> /?q=...#community), Spenden-Knopf,
Hell/Dunkel-Schalter, Menue unter 900 px, Konto als Avatar-Kreis mit Menue.
- Theme: data-theme auf <html> (Skript im <head> gegen Aufblitzen), helle
Tokens, danger-/warn-/ok-text-Tokens statt fester Farben.
- Spenden-Banner (schliessbar); ohne DONATE_URL bleiben Banner und Knopf weg.
- User.labels und isAdmin; Platzhalter fuer Startseite und Detailseite.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Gäste/abgelaufene Sitzung: Appwrite antwortet mit 404; withLogin und
openSavedCard melden dann an (Dialog, einmal wiederholen) statt
„nicht gefunden“ zu zeigen.
- Öffnen einer Karte fragt vor dem Verwerfen eines ungespeicherten Entwurfs
nach; ein fehlgeschlagenes Öffnen setzt einen gefüllten Editor nicht zurück.
- Update ohne Bildwechsel schickt kein imageFileId mehr (veralteter Stand
zeigte sonst auf eine gelöschte Datei).
- Uploads werden nur nach eindeutiger Ablehnung (4xx ohne 408/429) gelöscht,
nicht bei Netzfehler, 5xx oder 429.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Editor zeigt „Bearbeitet: <Name>“, solange eine gespeicherte Karte gebunden ist,
und bietet „Neue Karte“ (setzt zurück, bricht Laden ab, entfernt ?karte=;
fragt bei ungespeicherten Änderungen per eigenem Dialog „Verwerfen?“)
- EditorState.savedConfig als Vergleichsbasis für ungespeicherte Änderungen
- bindEditorToAuth: Abmelden leert den Editor, Kontowechsel A -> B entkoppelt die
Karte (Entwurf bleibt), Anmelden ohne Vorgänger und abgelaufene Sitzung behalten
den Entwurf; auth.onLogout unterscheidet eigenes Abmelden von bemerktem Ablauf
- Speichern nimmt den Schnappschuss pro Klick (auch für die Wiederholung nach 401)
- save: row_not_found beim Update legt eine neue Karte an (inkl. Aufräumen)
- Karten-Seite: Editor-Abgleich auch nach dem Verlassen der Seite, Löschen-Dialog
wird beim Verlassen geschlossen, keine doppelten Vorschau-URLs
- confirm-dialog als gemeinsames Modul (Löschen, Verwerfen)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- account/cards.ts: Kartendienst (neu/bestehend speichern mit Aufräumen halber
Uploads, Umbenennen, Löschen, Laden) über ein schmales CardsApi-Interface,
Appwrite-Anschluss mit 22.4.1-Objektparametern, fetchFile über
client.prepareRequest (Projekt-Header + Cookie, kein getFileView)
- account/with-login.ts: bei 401 Nutzer neu prüfen, einmal anmelden, wiederholen
- editor/save.ts: Speichern-Ablauf (ein Lauf zur Zeit, 401 -> Login -> einmal
wiederholen, deutsche Fehlermeldung)
- Editor: Speichern mit Schnappschuss und 480-px-Vorschau, Karte öffnen über
?karte=<id>, Bildwechsel während des Speicherns gesperrt
- pages/cards-page.ts: /karten mit Kacheln, Öffnen, Herunterladen, Umbenennen,
Löschen (eigener Dialog), Abmelde- und Leer-Ansicht
- Tests für Dienst, withLogin, Speichern-Ablauf, Karten-Seite und Editor-Anbindung
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- login(): bei user_session_already_exists aktuelle Sitzung beenden und genau
einmal neu versuchen; Meldung für den Restfall in errorMessage
- requireLogin(): schlägt showModal fehl, wird aufgeräumt und der Fehler geworfen,
damit spätere Aufrufe nicht an einem toten Promise hängen
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- appwrite 22.4.1 (exakt gepinnt); Client mit Endpoint aus VITE_APPWRITE_ENDPOINT
bzw. location.origin + /v1, Projekt aus VITE_APPWRITE_PROJECT_ID bzw. aircard
- auth.ts: getCurrentUser (401 -> null, ungültige Antwort -> Fehler), login, register,
logout, Passwort-Link anfordern/abschließen, onAuthChange, validatePassword
- errors.ts: deutsche Meldungen nach type, dann code (Tabelle aus dem Plan)
- Login-Dialog (Anmelden/Registrieren/Passwort vergessen) als gemeinsames <dialog>,
Konto-Menü im Seitenkopf, Seite /passwort-neu
- Tests: errors, auth, Dialog, Passwort-neu-Seite
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>