docs: Spec und Plan Benutzernamen
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
13b46da2e0
commit
a65c075004
2 files changed
+222
No files matched your search
@@ -0,0 +1,113 @@
|
||||
# AirCard-IOS Benutzernamen – Implementierungsplan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Eindeutige Benutzernamen pro Konto (Pflicht, änderbar), angezeigt als „von @name“ an Logos und Community-Karten.
|
||||
|
||||
**Architecture:** Neue öffentliche Tabelle `profiles` (Zeile = Konto, Unique-Index auf dem kleingeschriebenen Namen), geschrieben nur über `aircard-api` (`POST /api/profil`, neues Modul `deploy/api/profiles.js`); Hochladen/Veröffentlichen verlangen serverseitig ein Profil. Der Client liest Profile direkt aus Appwrite, fragt den Namen beim Registrieren bzw. nach dem Anmelden ab und zeigt ihn statt des alten Anzeigenamens.
|
||||
|
||||
**Tech Stack:** wie bisher (TypeScript, Vite, Vitest + happy-dom, appwrite 22.4.1; Node 22 ohne Abhängigkeiten; Appwrite 1.8.1 TablesDB).
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-10-05-aircard-benutzernamen-design.md`
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- Arbeitskopie `/home/webklar/apps/aircard-ios.org/dev`, Branch `main`, direkt committen, **nicht pushen** (Controller pusht).
|
||||
- Keine neuen Abhängigkeiten; Versionen unverändert; deutsche Kommentare im Stil des Bestands.
|
||||
- Alle neuen sichtbaren Texte über `t()`/`msg()` mit Eintrag in `web/src/i18n/en.ts` (Schlüssel eindeutig, Vorhandenes wiederverwenden); Tests laufen auf Deutsch; `coverage.test.ts` (Abdeckung, Heuristik, AST-Wache) bleibt grün.
|
||||
- Namensmuster genau `^[A-Za-z0-9][A-Za-z0-9._-]{2,19}$`; Schlüssel = `toLowerCase()`; Sperrliste genau wie Spec § 2.
|
||||
- Tabelle `profiles`: Zeilen-ID = Nutzer-ID, Spalten `username` (string 20, Pflicht), `usernameKey` (string 20, Pflicht), Index `username_key` unique auf `usernameKey`, rowSecurity an, keine Tabellenrechte, Zeilenrecht genau `['read("any")']`.
|
||||
- API-Codes und Texte genau wie Spec § 4 (`usernameInvalid`, `usernameReserved`, `usernameTaken`, `usernameRequired`); Client-Tabelle `API_MESSAGES` bleibt im Gleichlauf mit `deploy/api/common.js` `MSG`.
|
||||
- `sessionStorage`-Schlüssel `aircard.usernamePrompted`; jeder Storage-Zugriff in try/catch.
|
||||
- Tests: `cd web && npx vitest run && npx tsc --noEmit && npm run build`; `node --test "deploy/api/*.test.js"`; `node --test web/scripts/appwrite-setup.test.mjs`.
|
||||
- Commits enden mit `Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>`.
|
||||
- Subagents: `sonnet` (Umsetzung/Review), `haiku` für Kleinstes; nie Subagents aus Subagents; keine Browser-/Headless-Prüfungen (macht der Controller live).
|
||||
|
||||
## Review Focus
|
||||
|
||||
- **Zwei Konten wollen gleichzeitig denselben Namen**: genau eines bekommt ihn, das andere 409 `usernameTaken` (Unique-Index, nicht nur Vorabprüfung). Test in Task 1.
|
||||
- **Registrierung mit vergebenem Namen**: Fehler am Feld, **bevor** das Konto angelegt wird (kein halbes Konto). Test in Task 2.
|
||||
- **Altes Konto ohne Namen lädt hoch / veröffentlicht**: erst der Namensdialog; „Abbrechen“ → nichts passiert; Server würde ohne Profil 403 liefern. Tests in Tasks 1 und 3.
|
||||
- **Umbenennen**: alter Name sofort für andere frei, eigene Kacheln/Infozeile/Detailseite zeigen sofort den neuen. Tests in Tasks 1 und 3.
|
||||
- **Profil nicht lesbar / gelöscht / Netzfehler bei der Anzeige**: Fallback auf alten Anzeigenamen bzw. „Anonym“, keine Fehlermeldung, kein Absturz. Test in Task 3.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Server – Schema `profiles`, `POST /api/profil`, Pflicht beim Hochladen/Veröffentlichen
|
||||
|
||||
**Files:**
|
||||
- Modify: `web/scripts/appwrite-setup.mjs` (+ Test): Tabelle `profiles` nach `logo_reports` mit Spalten und Unique-Index aus den Global Constraints.
|
||||
- Create: `deploy/api/profiles.js`, `deploy/api/profiles.test.js`
|
||||
- Modify: `deploy/api/common.js` (`MSG` + 4 Texte, Muster/Sperrliste/Schlüssel-Helfer), `deploy/api/app.js` (Routen einhängen; `publish` prüft Profil), `deploy/api/logos.js` (`hochladen` prüft Profil), `deploy/api/fake-appwrite.js` (Tabelle `profiles`, Unique-Index → 409 bei doppeltem `usernameKey`), `deploy/api/app.test.js`/`logos.test.js` (Profil in bestehenden Hochladen-/Veröffentlichen-Tests anlegen; neue 403-Tests), `deploy/api/README.md`, `deploy/README.md` (sechs Dateien kopieren)
|
||||
|
||||
**Interfaces:**
|
||||
- Produces:
|
||||
```js
|
||||
// common.js
|
||||
const USERNAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{2,19}$/;
|
||||
function usernameKey(name) // name.toLowerCase()
|
||||
function isReservedUsername(name) // Schlüssel in der Sperrliste
|
||||
// profiles.js
|
||||
function createProfileRoutes({ appwrite, say, withLock, requireUser }) // → [['POST /api/profil', {name:'profil', json:true, run}]]
|
||||
async function hasProfile(appwrite, userId) // getRow('profiles', userId) !== null — von logos.js und app.js genutzt
|
||||
```
|
||||
|
||||
- [ ] **Step 1: Tests schreiben:** anlegen → 200 `{username}`, Zeile ID = Nutzer-ID, Rechte genau `['read("any")']`, `usernameKey` klein; ändern → alter Schlüssel frei (anderes Konto kann ihn danach nehmen); „kenso“ → „Kenso“ beim selben Konto erlaubt; vergeben → 409 `usernameTaken`; Rennen zweier Konten (Verzögerung im Fake) → genau ein 200, ein 409; gesperrt (`Admin`, `AirCard-IOS`) → 400 `usernameReserved`; Muster (`ab`, `_abc`, 21 Zeichen, `a b`, Umlaut) → 400 `usernameInvalid`; ohne JWT 401; `hochladen` und `veroeffentlichen` ohne Profil → 403 `usernameRequired` (vor allen anderen Prüfungen, nichts angelegt); Setup-Skript: `profiles` samt Unique-Index wird angelegt/geprüft.
|
||||
- [ ] **Step 2:** `node --test "deploy/api/*.test.js"` und `node --test web/scripts/appwrite-setup.test.mjs` → FAIL.
|
||||
- [ ] **Step 3:** Umsetzen.
|
||||
- [ ] **Step 4:** beide Suiten grün; `cd web && npx vitest run` grün (Gleichlauftest `API_MESSAGES` erst in Task 2 — falls er hier rot wird, die vier Texte in `web/src/i18n/api-messages.ts` + `en.ts` gleich mit ergänzen).
|
||||
- [ ] **Step 5:** Commit `feat(api): Benutzernamen (Profil-Tabelle, /api/profil, Pflicht beim Hochladen und Veröffentlichen)`.
|
||||
|
||||
### Task 2: Client – Profil-Daten, Dialog „Benutzername wählen“, Registrieren, Konto-Menü
|
||||
|
||||
**Files:**
|
||||
- Create: `web/src/account/profile.ts` (+ Test), `web/src/account/username-dialog.ts` (+ Test)
|
||||
- Modify: `web/src/account/appwrite.ts` (`IDS.profiles = 'profiles'`), `web/src/account/login-dialog.ts` (+ Test), `web/src/account/account-menu.ts` (+ Test), `web/src/main.ts` (+ Test: Abfrage einmal pro Sitzung nach Start/Anmeldung), `web/src/i18n/api-messages.ts`, `web/src/i18n/en.ts`, `web/src/account/account.css`
|
||||
|
||||
**Interfaces:**
|
||||
- Produces:
|
||||
```ts
|
||||
// account/profile.ts
|
||||
export const USERNAME_PATTERN: RegExp;
|
||||
export function usernameKey(name: string): string;
|
||||
export function validateUsername(name: string): string | null; // übersetzter Text oder null (Muster, Sperrliste)
|
||||
export function getUsername(userId: string): Promise<string | null>; // Profil lesen, 404 → null
|
||||
export function usernamesFor(userIds: readonly string[]): Promise<Map<string, string>>; // gesammelt, zwischengespeichert, Fehler → leere Map
|
||||
export function isUsernameTaken(name: string): Promise<boolean>; // Abfrage equal('usernameKey', key)
|
||||
export function saveUsername(name: string): Promise<string>; // POST /api/profil, aktualisiert Cache + meldet Änderung
|
||||
export function myUsername(): string | null; // Cache des angemeldeten Kontos (null = keiner/unbekannt)
|
||||
export function loadMyUsername(): Promise<string | null>; // nach Anmeldung/Start
|
||||
export function onUsernameChange(fn: (userId: string, username: string) => void): () => void;
|
||||
// account/username-dialog.ts
|
||||
export function usernameDialog(opts: { mode: 'prompt' | 'required' | 'change'; signal?: AbortSignal }): Promise<string | null>; // null = Später/Abbrechen
|
||||
export function requireUsername(): Promise<boolean>; // hat Namen → true; sonst Dialog 'required'
|
||||
```
|
||||
- [ ] **Step 1: Tests:** `validateUsername` (Grenzen 3/20, erstes Zeichen, erlaubte Zeichen, Sperrliste case-insensitiv); `usernamesFor` sammelt IDs, eine Abfrage pro Aufruf, Cache, Fehler → leere Map; Registrieren: Feld „Benutzername“ Pflicht, Vorabprüfung (Muster, Sperre, `isUsernameTaken`) **vor** `register()`, Fehler am Feld; nach erfolgreichem `register` → `saveUsername`; scheitert das, schließt der Login-Dialog trotzdem erfolgreich und der Namensdialog erscheint; Dialog: Modi prompt („Später“), required („Abbrechen“), change (vorbelegt), Speichern ruft `saveUsername`, 409 → Text am Feld, Busy-Sperre; Konto-Menü: „@name“ + E-Mail klein, Eintrag „Benutzername ändern“ bzw. „Benutzername wählen“; `main.ts`: nach Start mit Sitzung bzw. nach Anmeldung ohne Namen genau einmal pro Sitzung Dialog `prompt`.
|
||||
- [ ] **Step 2:** `cd web && npx vitest run src/account src/main.test.ts` → FAIL.
|
||||
- [ ] **Step 3:** Umsetzen (Dialog nach dem Muster von `logo-dialog.ts`/`publish-dialog.ts`, Texte nur als Text).
|
||||
- [ ] **Step 4:** `cd web && npx vitest run && npx tsc --noEmit && npm run build` grün.
|
||||
- [ ] **Step 5:** Commit `feat(account): Benutzername beim Registrieren, im Konto-Menü und als einmalige Abfrage`.
|
||||
|
||||
### Task 3: Client – Anzeige „von @name“, Anzeigename-Felder weg, Pflicht vor Hochladen/Veröffentlichen
|
||||
|
||||
**Files:**
|
||||
- Modify: `web/src/logos/logo-picker.ts`, `web/src/logos/logo-info.ts`, `web/src/pages/community-page.ts`, `web/src/logos/logo-dialog.ts`, `web/src/logos/upload-logo.ts`, `web/src/community/publish-dialog.ts`, `web/src/community/publish.ts`, `web/src/pages/cards-page.ts` (Veröffentlichen-Aufruf), Tests dazu; `web/src/i18n/en.ts`
|
||||
- Delete: `web/src/account/display-name.ts` (+ Test)
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: Task 2 (`usernamesFor`, `myUsername`, `onUsernameChange`, `requireUsername`, `currentUserId`).
|
||||
- Byline-Regel (eine Hilfsfunktion, z. B. in `profile.ts`): `byline(ownerId, legacyDisplayName, names)` → `t('von @{name}', {name})` wenn Profil, sonst `t('von {name}', {name: legacy})` wenn Altname, sonst `t('von {name}', {name: t('Anonym')})`.
|
||||
|
||||
- [ ] **Step 1: Tests:** Kacheln/Infozeile/Detailseite zeigen „von @name“ (eine `usernamesFor`-Abfrage pro geladener Seite), Fallback Altname bzw. „Anonym“, Fehler beim Nachschlagen → Fallback ohne Fehlertext; eigene Umbenennung (`onUsernameChange`) aktualisiert eigene Einträge sofort; Dialoge „Logo hochladen“ und „Veröffentlichen“ ohne Anzeigename-Feld, senden `displayName: ''`; vor Hochladen bzw. Veröffentlichen `requireUsername()` → false = nichts passiert; Serverantwort 403 `usernameRequired` → Namensdialog `required`, danach erneuter Versuch möglich; `display-name.ts` und `aircard.displayName` nirgends mehr genutzt.
|
||||
- [ ] **Step 2:** `cd web && npx vitest run src/logos src/community src/pages` → FAIL.
|
||||
- [ ] **Step 3:** Umsetzen.
|
||||
- [ ] **Step 4:** `cd web && npx vitest run && npx tsc --noEmit && npm run build` grün.
|
||||
- [ ] **Step 5:** Commit `feat(community): „von @name“ statt Anzeigename, Benutzername vor dem Hochladen und Veröffentlichen`.
|
||||
|
||||
### Task 4: Betrieb und Abnahme (Controller)
|
||||
|
||||
- [ ] Schema: Setup-Skript `--check` → anlegen → `--check` Exit 0 (Unique-Index `username_key`).
|
||||
- [ ] API: Backup, sechs Dateien kopieren, `docker compose restart aircard-api`, Gesundheit + `POST /api/profil` ohne JWT = 401 `unauthorized`.
|
||||
- [ ] Push `main` → Build → neues Bundle live.
|
||||
- [ ] Live als Gast: Kacheln/Detailseite zeigen „von …“ (Altdaten „Anonym“). Kenso: registrieren bzw. Abfrage nach Anmeldung, Name ändern, Name am eigenen Logo sehen.
|
||||
- [ ] Review (ein `sonnet`-Reviewer über alles), Fix-Welle nur für Critical/Important, Memory, Bericht.
|
||||
@@ -0,0 +1,109 @@
|
||||
# AirCard-IOS – Benutzernamen – Design
|
||||
|
||||
Stand: 2026-10-05 · Status: Entwurf im Chat von Kenso freigegeben („ja passt, mach weiter“), Spec + Plan + Umsetzung ohne weitere Rückfrage · baut auf `2026-10-04-aircard-logos-sprache-design.md` auf (live, main 13b46da)
|
||||
|
||||
## 1. Ziel und Rahmen
|
||||
|
||||
Jedes Konto bekommt einen eindeutigen Benutzernamen. Logos und Community-Karten zeigen „von @name“ statt „Anonym“, damit man Leute wiedererkennt.
|
||||
|
||||
| Punkt | Entscheidung (Kenso) |
|
||||
|---|---|
|
||||
| Eindeutig | Ja, jeder Name nur einmal; Groß-/Kleinschreibung zählt beim Vergleich nicht. |
|
||||
| Pflicht | Ja, auch für bestehende Konten. Registrieren nur mit Name; alte Konten werden nach dem Anmelden einmal gefragt; Hochladen und Veröffentlichen gehen erst mit Name. |
|
||||
| Anzeige | Immer der Benutzername; das Feld „Anzeigename“ fällt weg; gilt auch für vorhandene Einträge des Kontos. |
|
||||
| Ändern | Ja, im Konto-Menü; danach überall der neue Name, der alte wird frei. |
|
||||
|
||||
## 2. Regeln für den Namen
|
||||
|
||||
- Muster: `^[A-Za-z0-9][A-Za-z0-9._-]{2,19}$`. Das sind 3–20 Zeichen, das erste ein Buchstabe oder eine Zahl, danach Buchstaben, Zahlen, `.`, `_`, `-`.
|
||||
- Schlüssel für die Eindeutigkeit: der Name in Kleinbuchstaben (`usernameKey`). Gespeichert und angezeigt wird der Name, wie er eingegeben wurde.
|
||||
- Gesperrte Schlüssel: `admin`, `administrator`, `root`, `system`, `support`, `moderator`, `mod`, `aircard`, `aircardios`, `aircard-ios`, `aircard.ios`, `aircard_ios`, `webklar`, `anonym`, `anonymous`, `null`, `undefined`, `team`, `staff`, `official`, `offiziell`, `hilfe`, `help`, `kontakt`, `contact`, `info`.
|
||||
- Anzeige mit `@` davor: „von @name“ bzw. „by @name“.
|
||||
|
||||
## 3. Daten (Appwrite, Datenbank `aircard`, Setup-Skript erweitert)
|
||||
|
||||
**Tabelle `profiles`** (rowSecurity an, keine Tabellenrechte für Clients):
|
||||
|
||||
| Spalte | Typ | Inhalt |
|
||||
|---|---|---|
|
||||
| `username` | string 20, Pflicht | Name wie eingegeben |
|
||||
| `usernameKey` | string 20, Pflicht | Name klein geschrieben |
|
||||
|
||||
- Zeilen-ID = Nutzer-ID (eine Zeile pro Konto).
|
||||
- Index `username_key`: **unique** auf `usernameKey`. Damit kann auch bei Gleichzeitigkeit kein Name doppelt vergeben werden.
|
||||
- Zeilenrecht genau `['read("any")']`, öffentlich lesbar. Schreiben nur über die API.
|
||||
|
||||
## 4. API-Dienst `aircard-api`
|
||||
|
||||
Eigenes Modul `deploy/api/profiles.js`, eingebunden wie `logos.js`. Der Betrieb kopiert künftig sechs Dateien: `appwrite.js`, `common.js`, `app.js`, `logos.js`, `profiles.js`, `server.js`.
|
||||
|
||||
`POST /api/profil` `{username}`, Nutzer-JWT:
|
||||
|
||||
- 401 ohne Anmeldung.
|
||||
- 400 `usernameInvalid` bei falschem Muster.
|
||||
- 400 `usernameReserved` bei gesperrtem Schlüssel.
|
||||
- 409 `usernameTaken`, wenn ein anderes Konto den Schlüssel hat. Das wird vorab per Abfrage geprüft; bei einem Rennen meldet Appwrite 409 auf dem Unique-Index, das wird ebenfalls zu `usernameTaken`.
|
||||
- Sonst: Hat das Konto schon eine Zeile, wird sie aktualisiert (der alte Name ist damit frei); sonst wird sie mit ID = Nutzer-ID und `read("any")` angelegt. Antwort `200 {username}`.
|
||||
- Groß-/Kleinschreibung des eigenen Namens ändern („kenso“ → „Kenso“) ist erlaubt.
|
||||
- Sperre je Konto (`profile:<userId>`).
|
||||
|
||||
**Pflicht serverseitig:** `POST /api/logos/hochladen` und `POST /api/veroeffentlichen` antworten **403 `usernameRequired`**, wenn das Konto keine Zeile in `profiles` hat. Die Prüfung läuft nach der Anmeldung und vor allen anderen Prüfungen. Bestehende Logos und Karten bleiben unberührt.
|
||||
|
||||
Neue `MSG`-Texte:
|
||||
|
||||
- `usernameInvalid`: „Der Benutzername muss 3–20 Zeichen lang sein, mit einem Buchstaben oder einer Zahl beginnen und darf nur Buchstaben, Zahlen, Punkt, Unterstrich und Bindestrich enthalten.“
|
||||
- `usernameReserved`: „Dieser Benutzername ist nicht erlaubt.“
|
||||
- `usernameTaken`: „Dieser Benutzername ist schon vergeben.“
|
||||
- `usernameRequired`: „Bitte wähle zuerst einen Benutzernamen.“
|
||||
|
||||
## 5. Oberfläche
|
||||
|
||||
- **Registrieren** (Login-Dialog, Reiter „Registrieren“): neues Pflichtfeld „Benutzername“ vor der E-Mail, `autocomplete="username"`, Hinweis „3–20 Zeichen: Buchstaben, Zahlen, . _ -“.
|
||||
- Vor dem Anlegen des Kontos werden Muster, Sperrliste und Verfügbarkeit geprüft (Abfrage auf `profiles`). Ein Fehler steht am Feld.
|
||||
- Danach: Konto anlegen → anmelden → `POST /api/profil`.
|
||||
- Scheitert dieser letzte Schritt (Rennen, Netz), ist man trotzdem angemeldet, und der Dialog „Benutzername wählen“ erscheint.
|
||||
- **Dialog „Benutzername wählen“** mit Feld, „Speichern“ und „Später“ bzw. „Abbrechen“:
|
||||
- Nach dem Anmelden oder beim Seitenstart mit Sitzung, wenn das Konto keinen Namen hat. Einmal pro Sitzung (Merker in `sessionStorage` `aircard.usernamePrompted`, Zugriffe in try/catch), mit „Später“.
|
||||
- Vor dem Hochladen eines Logos und vor dem Veröffentlichen, wenn kein Name da ist. Hier gibt es „Abbrechen“ statt „Später“; ohne Namen geht es nicht weiter.
|
||||
- Antwortet der Server mit `usernameRequired`, erscheint der Dialog ebenfalls.
|
||||
- **Konto-Menü**: Statt der E-Mail steht „@name“ oben (die E-Mail darunter klein). Neuer Eintrag „Benutzername ändern“, der den Dialog vorbelegt öffnet. Ohne Namen heißt der Eintrag „Benutzername wählen“.
|
||||
- **Anzeige „von @name“**: Logo-Kacheln, Logo-Infozeile, Detailseite einer Community-Karte.
|
||||
- Der Name wird über `ownerId` in `profiles` nachgeschlagen, gesammelt je Seite (`equal('$id', ids)`) und zwischengespeichert.
|
||||
- Ohne Profil: der früher gespeicherte `displayName` (ohne `@`), sonst „Anonym“.
|
||||
- Nach einer eigenen Namensänderung aktualisieren Kacheln, Infozeile und Detailseite die eigenen Einträge sofort.
|
||||
- **Weg fällt** das Feld „Anzeigename“ in den Dialogen „Logo hochladen“ und „Veröffentlichen“ samt `account/display-name.ts` und dem `localStorage`-Schlüssel `aircard.displayName`. Der Client schickt `displayName: ''`; die API nimmt das Feld weiter an (Altbestand).
|
||||
|
||||
Alle Texte gibt es auf Deutsch und Englisch.
|
||||
|
||||
## 6. Fehlerbehandlung
|
||||
|
||||
| Fall | Meldung |
|
||||
|---|---|
|
||||
| Muster falsch / gesperrt / vergeben | die drei `MSG`-Texte aus § 4, am Feld |
|
||||
| Server meldet `usernameRequired` | Dialog „Benutzername wählen“ |
|
||||
| Netz/API weg beim Speichern | „Der Dienst ist gerade nicht erreichbar.“, Dialog bleibt offen |
|
||||
| Profil lässt sich nicht lesen (Anzeige) | Fallback `displayName` bzw. „Anonym“, kein Fehlertext |
|
||||
|
||||
## 7. Tests
|
||||
|
||||
- **node:test:**
|
||||
- `/api/profil`: anlegen, ändern, Groß/klein am eigenen Namen, vergeben 409, gesperrt 400, Muster 400, ohne JWT 401.
|
||||
- Rennen mit zwei Konten: genau eines bekommt den Namen.
|
||||
- Zeilenrechte genau `read("any")`.
|
||||
- Hochladen und Veröffentlichen ohne Profil → 403 `usernameRequired`.
|
||||
- Setup-Skript deckt `profiles` mit Unique-Index ab.
|
||||
- **Vitest:**
|
||||
- Prüfung des Namens (Muster, Sperrliste, Schlüssel).
|
||||
- Registrieren mit Vorabprüfung und Fehlern am Feld.
|
||||
- Dialog: Später, Abbrechen, Speichern, Fehler.
|
||||
- Einmal-pro-Sitzung-Abfrage.
|
||||
- Konto-Menü mit „@name“ und Ändern.
|
||||
- Anzeige „von @name“ mit Fallbacks; sofortige Aktualisierung nach Umbenennen.
|
||||
- Dialoge ohne „Anzeigename“.
|
||||
- Hochladen und Veröffentlichen fragen vorher nach dem Namen.
|
||||
- Gleichlauf der API-Codes, i18n-Abdeckung.
|
||||
- **Live:** Gast sieht „von …“-Zeilen. Kenso registriert bzw. wird gefragt, ändert den Namen und sieht ihn an seinem Logo.
|
||||
|
||||
## 8. Nicht im Umfang
|
||||
|
||||
Profilseiten, Suche nach Benutzernamen, Moderation von Namen, Konto löschen, Avatar-Bilder, Benutzername als Anmeldename (angemeldet wird weiter per E-Mail).
|
||||
Reference in new issue
Block a user