- /study-Listing entfernt, Route redirectet auf /decks
- Study-Tab aus Nav entfernt; /study/*-Pfade highlighten Decks-Tab
- /decks/new und /decks/new-ai zusammengeführt: ein Formular mit
zwei Buttons (Anlegen + ✨ Mit KI generieren), new-ai redirectet
- Inline NewDeckCard: Formular klappt in der Kachel auf (gleiche
Größe, scrollbar), Fach als Toggle-Picker, alle Felder volle Breite
- DeckStack: Text linksbündig, Beschreibung ohne Clamp, Titel weiter oben
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@mana/shared-ui-2@0.1.0 wurde heute publiziert. Cards' einziger
shared-ui-Konsument war PillTabGroup im Header (Routen-Nav +
DE/EN-Switcher). Drop-in-Migration:
- apps/web/package.json: @mana/shared-ui + @mana/shared-icons raus
(letzteres war nur shared-ui-Transitive, in Cards-Code nirgends
direkt importiert), @mana/shared-ui-2 ^0.1.0 rein.
- Header.svelte: Import wechselt von @mana/shared-ui auf
@mana/shared-ui-2. primaryColor-Prop entfernt — shared-ui-2
PillTabGroup nutzt --color-primary direkt aus dem 12-Token-Set.
Bridge-Aliase in app.css bleiben — Cards' Eigen-Komponenten
(Header-Logo, Modals, Marketplace, Routen) nutzen historisch
--color-card, --color-popover, --color-accent etc. Diese Tokens
existieren im 12-Token-Mana-Set NICHT, aber die Aliase mappen sie
weiter aufs 12er-Set. Aliase-Kommentar präzisiert: nicht mehr für
shared-ui@0.1.x (raus), sondern für Cards-eigenen Code, bis Cards
in eigenem Refactor-Sprint auf das 12er-Vokabular umzieht.
Type-check-Pipeline schrumpft drastisch: von 3994 Files (mit
shared-ui@0.1.x's 176 Quellfiles + transitive) auf 439 Files (nur
Cards + shared-ui-2's kleinerer Surface). Build sauber, weniger
JS im Output-Bundle.
Cards ist damit die erste Vereins-App, die operativ auf
shared-ui-2 läuft — End-to-End-Beweis dass das System trägt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cards is the first app on the new 12-token mana-vereinsweite
theming system (mana/docs/THEMING.md). Forest-Variant aus
@mana/themes/variants/forest.css konsumiert via app.css-Import,
data-theme="forest" in app.html.
Token-Welt umgestellt — 158 renames + 304 hsl-wraps in 17 Files
(Python-Refactor, BSD-sed war zu unzuverlässig):
- --color-bg → --color-background
- --color-fg → --color-foreground
- --color-muted → --color-muted-foreground
- --color-primary-fg → --color-primary-foreground
- --color-danger → --color-error
- bare var(--color-X) → hsl(var(--color-X)) durchgängig
Bridge-Aliase in app.css mappen die shared-ui@0.1.x-Erwartungen
(card, accent, surface-elevated-*, …) auf das 12er-Set. Mit
shared-ui@2.0-Refactor entfällt diese Sektion. --brand-cards-forest
als App-Identitäts-Hex separiert von Theme-Tokens.
Header konsumiert PillTabGroup aus @mana/shared-ui@0.1.1 für die
Routen-Navigation (Decks/Lernen/Library/Import/Stats) und den
DE/EN-Sprach-Switcher — visuell konsistent mit Vereins-Standard.
Cards' primary-Grün wurde dabei von 142 76% 36% (alter Live-Stand)
auf 142 76% 28% verdunkelt, damit primary-foreground/primary-
Kontrast WCAG-AA-konform (≥4.5) ist. Der alte Live-Stand hatte
Ratio 3.35.
i18n: deck_stack.aria_label, deck_detail.fan_aria, deck_detail.
card_open, decks.card_count_more, study_session.manage_link.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Echte Anmeldung gegen auth.mana.how/api/v1/auth/login statt
Dev-Stub-User-ID. accessToken (EdDSA-JWT, 15 min TTL) + Profil
(email, name, tier) leben in localStorage; jeder API-Call schickt
`Authorization: Bearer <jwt>`. Bei 401 wird die Session lokal
geleert — User landet beim nächsten Render auf der Login-Page.
`devUser.id` bleibt eine Vereinfachte UI-Sentinel (gibt id wenn
JWT ODER Dev-Stub aktiv) — alle existierenden Importer
funktionieren unverändert. Dev-Stub-Pfad bleibt als Fallback für
Tests + Anki-Importer-Migration. Filename `dev-stub.svelte.ts`
behalten, Inhalt komplett umgebaut (Sprint 10d wäre der Rename).
Account-Page zeigt Email + Name + Tier statt nur UUID. Header
zeigt Email statt UUID. Login-Form auf Landing-Page mit Email +
Passwort, error-Anzeige, autocomplete-Hints für Browser-Manager.
uploadMedia (multipart) angepasst: Bearer first, X-User-Id-Stub
als Fallback.
svelte-check 384 files 0 errors, 7 Web-Tests grün, prod-Build
sauber.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Globaler :focus-visible-Outline (var(--color-primary), 2px) — Tailwind 4
strippt die Browser-Defaults, ohne Fokus-Ring sind Tastatur-Nutzer blind.
.sr-only-Utility (Standard-Rezept) und .skip-link in app.css. prefers-
reduced-motion: schaltet alle Transitions/Animationen auf 0.01ms.
Layout: Skip-Link "Zum Inhalt springen" → #main, main bekommt tabindex="-1"
und id, html-lang wird via $effect reaktiv mit i18n.current synchronisiert
(Initial-SSR rendert "de", Browser zieht nach).
Header: nav-aria-label aus i18n (common.main_nav), Locale-Switcher-Label
aus common.language_switcher. ToastStack: role=region + aria-live=polite,
einzelne Toasts role=alert (error) bzw. status (success/warning), Schließen-
Button-Label i18n-konform.
Hover-only Delete-Buttons (Decks-Liste, Deck-Detail-Karten) bekommen
focus-visible:opacity-100 — bisher waren sie für Tastatur-Nutzer
unsichtbar. opacity-0 statt hidden, damit Tab-Order intakt bleibt.
svelte-check 379 files 0 errors, prod-Build sauber.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Schmale Eigen-Lösung statt svelte-i18n: $lib/i18n mit de.ts + en.ts
(je ~150 Strings, flat-with-nesting), index.svelte.ts liefert reaktiven
i18n-Store als Svelte-5-Rune plus t()/tn()-Helper. Locale wird in
localStorage persistiert, Default = navigator.language (DE/EN-Erkennung),
Fallback = de.
Header bekommt einen DE/EN-Toggle-Switcher (role=group, aria-pressed).
Alle Routes (decks, decks/[id], decks/new, cards/new, cards/[id]/edit,
study, study/[deckId], import, account, stats, +page) und alle
Komponenten (Header, AnkiImport, InboxBanner) ziehen jetzt durch t().
tn(key, n) wählt zwischen `<key>_one` (n=1) und `<key>` — minimaler
Plural-Helper, reicht für DE/EN-MVP. Komplexere Pluralregeln (FR/IT)
bräuchten Intl.PluralRules, kommen wenn die Locales dazukommen.
Begleitend ein paar A11y-Vorbereitungen mitgenommen: aria-label am
Locale-Switcher, role=group für Grade-Buttons, role=button +
keyboard-handler für die Anki-Dropzone, role=progressbar mit
aria-valuemin/max/now, role=alert für Error-States,
aria-live=polite für Lade-Meldungen, sr-only-Heading im
Study-Card-Render, aria-hidden für rein dekorative Elemente.
svelte-check 379 files 0 errors, 94 Tests grün (41 Domain + 48 API
+ 5 Web).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Neuer Endpoint GET /api/v1/me/stats liefert in einem Aufruf alle
Aggregate für die Stats-UI:
- total_decks / total_cards / total_reviews / due_now
- state_counts pro FSRS-Zustand (new/learning/review/relearning)
- reviewed_per_day für die letzten 7 Tage (Quelle: reviews.last_review,
via to_char(day, 'YYYY-MM-DD') auf Postgres-Seite gruppiert)
- streak_days (rückwärts ab heute bis zum ersten Tag ohne Review)
study_sessions wird aktuell NICHT befüllt — der Schema-Slot existiert
seit Phase 3, aber der Session-Tracker kommt erst, wenn das Lern-
Flow-Layer ausgebaut wird. last_review reicht für jetzt.
/stats-Page rendert vier KPI-Cards, einen 7-Tage-Säulen-Chart per
CSS-Heights, plus eine FSRS-State-Distribution. Header-Nav um
"Statistik" ergänzt.
E2E-Smoke gegen lokale Postgres bestätigt: bestehender Cloze-User
zeigt 1 Deck, 1 Karte, 2 Reviews, 2 due, alle "new"-State, 0
Streak — passt zum gestern eingespielten Smoke-User.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Neuer User-JWT-Pfad GET/POST /api/v1/me/{export,delete} — gespiegelte
DSGVO-Logik aus dem Service-Key-Pfad, aber gegen die eigene User-ID
gated. buildUserExport extrahiert in dsgvo.ts und wird von beiden
Routern geteilt.
/account-Page zeigt User-ID, Logout, JSON-Daten-Export (Download als
Blob), und einen rot-markierten Account-Delete-Knopf mit "LÖSCHEN"-
Confirmation. Logout im Header verlinkt jetzt auf /account statt
direkt clear() — der User sieht zuerst, was an seinem Account hängt.
Andere mana-Apps werden nicht mit gelöscht — der UI-Hinweistext zeigt
auf die spätere Verein-DSGVO-Sammelanfrage über mana-admin.
48 API-Tests grün (+2 neue auth-gate-Tests für /me), web type-check
374 files 0 errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Strategie-B-Ausnahme: parse.ts (Anki-Format-Parser via JSZip + sql.js)
und AnkiImport.svelte (UI-Stages) sind aus mana-monorepo portiert,
mit Source-Comment-Header dokumentiert. Anki-Format ist standalone
Parser-Logik, kein Architektur-Schmuggel.
Neuer server-authoritative import.ts schreibt direkt gegen die
cards-api ($lib/api/decks + cards) — keine Stores, keine Dexie.
Anki "::"-Hierarchie wird zu " / "-Strings flach. Fallback-Deck
"Anki-Import" für Karten ohne explizites Deck. Cloze-Karten kommen
first-class durch (Sub-Index pro Cluster, Sprint 8a/8b).
Phase-8-MVP-Scope: Bilder + Audio werden gedroppt (Option A) — der
sanitizeAnkiHtml entfernt <img> und [sound:…] ersatzlos. Späterer
Media-Pfad (lokaler Cards-Upload oder mana-media nach Phase 2) ist
additiv.
Neue Route /import + Top-Nav-Link. Hermetic Vitest (5 Cases): baut
zur Laufzeit ein Mini-.apkg via sql.js + JSZip und prüft den
Parser-Output (basic, basic-reverse, cloze, sanitize, dedupe auf
Note-Ebene). svelte-check 0 errors, prod-Build sauber.
sql-wasm.wasm liegt in static/ (660kB) — fix für sql.js 1.14.1, vom
Browser einmal geladen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>