languages.greeting (de/en/sv geseedet), neue pair_categories-Tabelle
(abgeleitet aus statement- und objektverknüpften Wörtern via
word_categories) inkl. Backfill für bereits veröffentlichte Pairs.
derivePairCategories() wird beim Publish (pairs + pipeline) aufgerufen.
/auth/me liefert language_target_greeting, /auth/stats liefert
categories[] mit Punkten je Kategorie fürs Profil.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GET /auth/feed schließt jetzt Pairs aus user_pair_progress (cross-session)
sowie per ?exclude=<uuids> übergebene, bereits geladene Pairs (In-Session)
aus. Leere Antwort signalisiert dem Client: keine weiteren Karten.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Der LLM-Fallback hatte Objektwörter auch dann verlinkt, wenn sie nur
Bestimmungswort eines anderen Dings waren (z.B. "jordgubbsfältet"/Erdbeerfeld
als Erdbeere). Regel jetzt explizit:
- behalten: Wort, Beugung/Mehrzahl/bestimmte Form, Kopf-Kompositum
("Landschildkröte"=Schildkröte), Synonym ("Stiefel"/"Lederstiefel"=Schuh)
- entfernen: Objektwort nur als Bestimmungswort ("Erdbeerfeld" != Erdbeere)
- locateSurfaceLLM-Prompt um diese Regel + Beispiele geschärft (verhindert
künftiges Fehl-Tagging).
- Neuer cleanup-Modus: POST /api/pipeline/retag-objects {"cleanup":true}
prüft bestehende Objekt-Tokens per LLM und entfernt die falschen. Eindeutig
gute Formen (exakt/Lemma+Endung) werden ohne LLM behalten.
- Helfer in objectTagging.js: objectTokensInSentence, isSimpleObjectForm, untagToken.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Objekt-Hervorhebung (Chip + Bildregion) hängt an {{label.o:uuid}}-Tokens im
Satz. Bisher entstanden die nur aus dem LLM-Nomen-Markup, das Haiku oft
ausließ -> Objekt blieb un-getokt (z.B. "ryggsäcken"/Rucksack), obwohl korrekt
verlinkt.
- src/lib/objectTagging.js: deterministischer, flexions-toleranter Tagger
(schwed. bestimmte Form -en/-et/...), idempotent, schützt bestehende Tokens.
- generatePairs.resolveNounMarkup: Sweep als Sicherheitsnetz + titel_sv im Lookup.
- pipeline.retagPair/retagObjects: per-Pair Nachtokenisierung (Hybrid-LLM-Fallback
nur für in anderer Sprache bestätigte Objekte), Backfill über Bild/alle Bilder.
- POST /api/pipeline/retag-objects (dry_run/use_llm/picture_id).
Ändert nur Satz-Textfelder -> Audio/Alignment bleiben gültig.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Neue Tabelle user_daily_activity (Tagesverlauf) + Spalte daily_goal_ep
- POST /auth/progress schreibt Tagesaktivität mit
- GET /auth/me liefert daily_goal_ep
- Neuer GET /auth/stats: Tagesverlauf, Tagesziel, Totals, echte Skills je answer_type
- Neuer PUT /auth/goal zum Setzen des Tagesziels
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Pair-Generierung markiert Nomen per [surface|lemma]-Markup und löst sie zu
{{label.o:objectId}} / {{label.w:wordId}} auf (Words werden auto-erstellt)
- Pipeline übersetzt + vertont Placeholder-Wörter aus den Sätzen mit
- translateText halluziniert keine ⟦PHn⟧-Tokens mehr (kein Token-Prompt ohne
Tokens, defensives Strippen); TTS/Review lösen geleakte Tokens auf
- POST /api/pipeline/repair-tokens repariert bestehende Sätze + Audios
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
DELETE /pairs/:id räumt jetzt unreferenzierte Fragen/Statements samt
Audio-Dateien (DB + S3) mit auf. DELETE /pictures/:id löscht zusätzlich
die nur mit diesem Bild verknüpften Objekte inkl. deren Pairs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Alle Pairs eines Bildes (de/en/sv) gehen zusammen mit dem Bild an Sonnet
zur Prüfung von Rechtschreibung, Übersetzungs-Konsistenz und Plausibilität.
Korrekturen werden vor der Audio-Erzeugung angewendet; vorhandene Audios
korrigierter Zellen werden invalidiert. Review-Fehler sind nicht fatal.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Die geseedete sv-Voice 'XXCqsM8I9KhqA7jLGj1U' existiert bei ElevenLabs
nicht — jede schwedische Audio-Generierung schlug mit voice_not_found
fehl (de/en haben eigene, gültige Account-Voices).
- Seed + Migration: sv → Premade 'Charlotte' (XB0fDUnXU5powFXDhCwa,
schwedischer Akzent, in jedem Account verfügbar); Bestandsdaten mit
der defekten ID werden beim Boot automatisch korrigiert
- voices.js: Fallback auf Premade 'Sarah' statt der toten ID
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- GET /api/tts-settings/voices/available listet die Account-Stimmen
(Grundlage für Voice-Auswahl im CMT statt Freitext-IDs)
- Audio-Batch/-Fill-Fehler enthalten jetzt das ElevenLabs-Detail
(z.B. voice_not_found) statt nur 'ElevenLabs error'
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- callClaude: Retry mit Backoff bei Überlast/Rate-Limit/Netzfehler
(429/500/503/529) — wahrscheinliche Ursache der fehlenden SV-Übersetzung
- Translate-Step pro Pair gekapselt: ein Fehler reißt nicht mehr den ganzen
Lauf ab, Fehlversuche werden gezählt (pipeline_progress.translateFailures)
- translatePair als wiederverwendbarer Helfer extrahiert
- POST /pipeline/picture/:id/translate-fill: fehlende Übersetzungen
(Sätze + Antwort-Wörter) eines Bildes nachholen
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Übersetzungs-Modell auf Sonnet (env TRANSLATE_MODEL, Default claude-sonnet-4-5).
- Neue translateWords(): übersetzt die Wörter eines word-Pairs gemeinsam in
einem Call, mit der Frage als Kontext → korrekte Bedeutung mehrdeutiger
Wörter (z.B. 'Ranke' → 'ranka' statt 'klänge'), konsistente Gruppe.
- POST /pairs/:id/translate nutzt translateWordGroup für word-Typ und nimmt
{ overwrite:true } entgegen, um falsche bestehende Zielsprachen neu zu
übersetzen (Quellsprache bleibt unangetastet); fillMissingRow erhält
overwrite-Flag.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Übersetzungs-Kern (Claude + Platzhalter-Schutz) nach src/lib/translate.js
ausgelagert; claude.js importiert von dort (Endpoints unverändert).
- Neuer Endpoint POST /pairs/:id/translate: füllt fehlende Sprachen für
Frage, Statements bzw. (bei word-Typ) verlinkte Wörter und liefert das
3-sprachige Inhalts-Bündel fürs Review-Modal.
- POST /pairs/:id/review hebt verlinkte Objekte + Bilder zusätzlich auf
'reviewed' (idempotent).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Leere Hüllen-Zeilen (in allen Sprachen leer, z.B. word-Typ-Statements ohne
Satztext) wurden als fehlend gezählt, obwohl translate-missing sie mangels
Quelltext nie anrührt. Dadurch zeigte die UI viele offene Übersetzungen, der
Button meldete aber 'Fertig: 0 übersetzt'. total/missing basieren jetzt auf
dem übersetzbaren Bestand (Text in mind. einer Sprache).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- pictures: reviewed-Status (Constraint + ALLOWED_STATUSES + Auto-Trigger beim Object-Linking)
- objects: STATUSES um reviewed erweitert; Auto-Trigger draft→reviewed wenn Pair verlinkt
- pairs/statements/questions: STATUSES um reviewed (Phase-1-Lücke)
- pairs: POST /:id/review kaskadiert Pair+Frage+Statements (verlangt alle 3 Sprachen)
- words: Auto requested→translated wenn alle titel_* gefüllt (POST+PATCH)
- audios computeUnits: nur vertonbar wenn ALLE 3 Sprachen pro Feld gefüllt
- claude: translate-text/translate-row/translate-missing mit Placeholder-Schutz
(⟦PHn:label⟧-Tokenisierung, Label übersetzt, UUID erhalten); translation-coverage
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- tts_settings (voice/model/speed/... pro Sprache) + Seed de/en/sv; Route /api/tts-settings
- audios: Stimme/Parameter aus tts_settings; Coverage zählt jetzt auch draft/translated
- pairs: GET /publishability (Readiness, sortierbar nach 'am wenigsten fehlt'),
POST /:id/publish (kaskadiert question/statements→published, validiert Bild+Audio je Sprache)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- reviewed-Status für objects/questions/statements/pairs (Constraints)
- feed: nur fertige Inhalte (published + Bild + Audio-Gate), audio_url
- pairs: Publish-Gating (draft→published = 409)
- audios: source_table/source_id/source_field/language + Unique-Index;
generate-for, generate-batch, GET /coverage; voices.js (Voice je Sprache)
- auth: POST /auth/progress, /auth/me mit total_ep/streak/level;
users_public EP-Spalten + user_pair_progress.earned_points
- claude: POST /generate-words; words POST akzeptiert status
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extends prompt to 40 pairs: adds 10 × word type with positive_words
and negative_words arrays. Difficulty easy/medium maps to level 1/2.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
POST /api/claude/generate-pairs — proxies image + objects to Claude
Haiku and returns 30 structured pairs (text/yes_no/question, easy/medium)
as JSON. Keeps the Anthropic API key server-side via ANTHROPIC_API_KEY.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Labels are now embedded in sentence text — no DB lookup needed
- Objects fetch selections directly from objects table
- Pictures resolved via object_pairs join instead of sentence UUID scan
- Simpler placeholderMap: only type + selections for objects
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Runs at startup (idempotent) — only touches rows that still contain bare
{{uuid}} placeholders. Looks up each UUID in words first, then objects,
and rewrites to {{label.w:uuid}} or {{label.o:uuid}} accordingly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pictures are now fetched through the canonical pair→object relationship
(object_pairs table) rather than by guessing objects from sentence
placeholder UUIDs. Removes the word→object indirect lookup hack added
previously. One query covers all pairs in the batch.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Word-type placeholders in sentences (type='word') were never matched
against object_pictures, so those cards always had no image.
Now queries object_words by word_id to find associated objects,
adds them to resolvedObjectIds, and pickPicture checks wordObjectMap
as a fallback.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add bbox_x/y/w/h FLOAT columns to object_pictures (0–1 percentage range)
- Include type ('word'|'object') and bbox in feed placeholder response
- Fix picture query to use DISTINCT ON instead of LIMIT 1
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- GET /auth/feed?lang=sv&limit=20 (JWT, end-user allowed)
- Resolves {{uuid}} placeholders to word labels in all languages
- Includes picture URLs, pos/neg words per statement
- Fix migration seed: use full unique index (non-partial) for ON CONFLICT
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- users_public gets user_id FK (1:1 link to auth user)
- Seed languages: en, sv alongside existing de
- POST /auth/register + /auth/login now include needsProfile flag
- New JWT-authed endpoints (end-user allowed):
GET /auth/languages public language list
GET /auth/check-username
GET /auth/me full profile join
POST /auth/profile one-time profile creation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Convert TEXT[] back to TEXT (take first element of existing arrays)
- Valid values: yes_no, text, question, word
- API validation updated for single string
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- pictures: add objects_created (bool) + objects_created_at (auto timestamp)
GET /pictures supports ?objects_created=true/false filter
PATCH /pictures/:id allows setting objects_created
- db-migrate: seed German language, link to all existing users
- auth/login: include native_lang (from languages table) in response + JWT
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- pictures.js: move GET/POST/DELETE /:id/words/* BEFORE GET /:id
so /:id/words is not shadowed; add POST /:id/words/:wordId
- words.js: move GET /:id after sub-routes; add GET /:id/pictures
and GET /:id/categories
- objects.js: move GET /:id after sub-routes (/:id/words, /:id/pairs,
/:id/pictures); add GET /:id/pictures
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- GET /api/objects?picture_id=X filter via object_pictures join
- GET /api/objects/:id/words — full word details
- GET /api/objects/:id/pairs — pairs with nested question + statements
- GET /api/words?titel_de=X case-insensitive filter
- GET /api/pictures/:id/words — full word details
- DELETE /api/pictures/:id/words/:wordId — convenience unlink
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix broken rename migration array (sed had corrupted from values to _sv)
- Add languages table with status lifecycle and trilingual titles
- Add user_names table with unique lowercase index
- Add users_public table linking to user_names and languages (native/target)
- Wire all three new routes under /api/languages, /api/user-names, /api/users-public
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- is_blocked BOOLEAN (default true), INET type for IP validation
- Indexes on email/username/phone/ip for fast registration checks
- POST /api/blocklist/check — checks all fields in one request, returns 403 if blocked
- Auto-timestamps on block/unblock, email stored lowercase
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>