App-Review: Bugfixes, Härtung und Refactorings aus dem Funktions-Audit - #20
Merged
Merged
Conversation
…ntegrity - env: DATABASE_URL/APP_URL no longer fall back to dev defaults in production - rate limiting for ics/search/upload/push API routes (in-memory fixed window) - push subscribe can no longer take over another account's subscription row - upload: malformed x-file-name header returns 400 instead of 500 - assignment material linking now filters by owner, not just module - storage: separator-safe path containment (local), reject backslashes (s3) - reminders: bound recurring-event and assignment queries to the live window - schema: real FK for thesis supersededById, folder sibling unique index mirrored in Drizzle schema, orphan cleanup for polymorphic generation rows - extraction: size caps for binary document parsers, UTF-8-safe truncation Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UrdkggYqhNCCZVHyJrdwBR
…alidation - batch poller now atomically claims a finished batch (running -> applying) before ingesting, so overlapping cron runs can no longer double-insert the same results; stale claims are reclaimed after 15 minutes - OpenAI batch tasks use max_completion_tokens (max_tokens is rejected by newer models) and strict json_schema with a strict-compatible schema - multiple-choice questions with a correctIndex outside their options are rejected at every persistence path (live generation, batch ingest, chat write tool) - calendar write tool validates endsAt like startsAt - ANN reindex skips mixed-dimension embeddings instead of failing the rebuild Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UrdkggYqhNCCZVHyJrdwBR
…e errors - the chat route now rebuilds the model context from the persisted DB history instead of trusting the client-supplied message list; a request can no longer spoof prior assistant turns or bypass the mode prompt - tool-confirmation continuations merge only the executed/rejected outcome onto the stored assistant message (text and tool inputs stay authoritative) - new bounds: 32k chars per user message, 40-message model window - assistant persistence and usage logging survive client disconnects (consumeStream) so stored history matches what was streamed - incomplete write-tool calls are dropped at model conversion instead of producing provider errors for dangling tool_use blocks - chat UI shows a translated, friendly error instead of raw error.message Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UrdkggYqhNCCZVHyJrdwBR
The app registered /sw.js, but no such file existed and no build step produced one — the whole PWA story (offline caching, installability behaviour, web push) was dead, and the push-enable button hung forever waiting on serviceWorker.ready. - public/sw.js: versioned via the registration URL (?v=<app version>), network-first navigations with offline fallback page, cache-first for immutable build assets/icons, push + notification-click + pushsubscriptionchange handlers, API traffic never cached - public/offline.html: minimal theme-aware offline fallback - sw-register: version-stamped registration, logs failures - notification settings: serviceWorker.ready raced against a timeout with a translated error, optimistic channel toggles roll back on save failure, 409 from the push API triggers a fresh subscription (shared-device case) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UrdkggYqhNCCZVHyJrdwBR
- quiz generate dialog: 'select a module first' error instead of the bare field label as the message - login: differentiated errors for unverified email and rate limiting; passkey failures show a translated message instead of raw error text - icon buttons (sidebar module/semester menus, materials grips/menus, sidebar resize handle) announce their action, not just the entity name - calendar right-click menu: viewport-clamped position, Escape-only close, menu roles and focus so it is keyboard-operable - outbox: isNetworkError no longer treats every TypeError as offline - study session logging failures are logged instead of fully swallowed - loading.tsx skeletons for program/module routes, due review, settings, admin, material viewer and plan pages - thesis-create type placeholder moved to i18n Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UrdkggYqhNCCZVHyJrdwBR
… parallel module page - new FormDialog extracts the copy-pasted create/edit dialog shell (dialog, pending state, error toast, cancel/submit footer) — deck/quiz/plan create and edit dialogs plus the plan generate dialog now share it; the two generate dialogs with job progress keep their special structure - module sub-tab list (segments + icons) lives once in src/config/module-tabs.ts, consumed by the sidebar tree and ModuleTabs - module overview page fetches its independent lookups with Promise.all instead of six serial awaits Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UrdkggYqhNCCZVHyJrdwBR
Resolves the upload-route conflict: keep main's registerUploadedFile-based route and re-apply the review branch's additions (per-user rate limit and the malformed x-file-name guard). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UrdkggYqhNCCZVHyJrdwBR
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Kritischer App-Review (UI/UX · Backend · AI) — Befunde & Fixes
Vollständiger Funktions- und Architektur-Review der App aus drei Perspektiven, mit anschließender Umsetzung aller Befunde. Baseline vor den Änderungen war grün (Typecheck, Lint, 102 Tests); nach den Änderungen: Typecheck ✓, Lint ✓, 123 Tests ✓ (21 neue), Produktions-Build ✓.
Kritische Bugs (behoben)
1. Die gesamte PWA-/Push-Story war funktionslos
sw-register.tsxregistrierte/sw.js— die Datei existierte nirgends und wurde von keinem Build-Schritt erzeugt. Folgen: Registrierung 404te in Produktion,navigator.serviceWorker.readyresolvte nie, und der „Push aktivieren"-Button in den Einstellungen hing für immer im Spinner. Push-Notifications (Reminder-Feature!) waren damit komplett tot.Fix: Echter
public/sw.js(Push + Notification-Click +pushsubscriptionchange, Network-first-Navigation mit Offline-Fallback-Seite, Cache-first für immutable Build-Assets,/api/**nie gecacht). Versionierung über die Registrierungs-URL (?v=<App-Version>) — jede Release invalidiert die Caches automatisch, ohne Build-Skript. Die Push-Einstellungen laufen jetzt mit Timeout + verständlicher Fehlermeldung statt Endlos-Spinner.2. Chat-Route vertraute der Client-History vollständig
/api/ai/chatnahmbody.messageskomplett vom Client: Assistant-Turns spoofbar, Mode-System-Prompt umgehbar, unbegrenzte Token-Kosten pro Request (kein Cap auf Größe/Anzahl), und bei Client-Disconnect divergierte die gespeicherte History (User-Message persistiert, Assistant-Antwort nicht).Fix: Die DB ist jetzt Source of Truth. Der Client liefert nur seine letzte Message; Tool-Bestätigungen (executed/rejected) werden gezielt in die gespeicherte Assistant-Message gemerged — Text und Tool-Inputs bleiben serverseitig autoritativ. Caps: 32k Zeichen pro User-Message, 40-Message-Fenster.
consumeStream()sorgt dafür, dass Persistenz + Usage-Logging auch bei Abbruch laufen.3. Batch-Polling konnte Ergebnisse doppelt einfügen
Der 5-Minuten-Cron
poll-batcheslief ohne Lock. Überlappende Läufe (langsames Apply, oder Web-Tier mitWORKERS_IN_PROCESS=trueplus dediziertem Worker) konnten dieselben Batch-Ergebnisse doppelt einfügen und doppelt Usage loggen — der Deduper hilft nicht, weil beide Läufe vor dem Commit des jeweils anderen seeden.Fix: Atomarer Claim (
running → applying) vor dem Ingest; verwaiste Claims werden nach 15 min reklamiert; bei Fehlern wird der Claim freigegeben, damit der nächste Lauf erneut ingestiert.4.
DATABASE_URLfiel in Produktion still auf die Dev-DB zurückenv.tshatte fürDATABASE_URL/APP_URLhartcodierte localhost-Fallbacks auch in Produktion (anders alsBETTER_AUTH_SECRET/ENCRYPTION_KEY). Fix: Fail-fast in Produktion.Weitere Backend-/AI-Härtung
/api/ics/[token](unauthentifizierte Capability-URL → Token-Bruteforce),/api/search,/api/materials/upload,/api/push. Hinweis: Der Login war entgegen erstem Befund bereits geschützt — better-auth limitiert Sign-in/Sign-up in Produktion per Default (3 Req/10 s).onConflictDoUpdateauf den uniqueendpointkonnte die Row eines anderen Accounts überschreiben. Jetzt 409 + clientseitiges Re-Subscribe (Shared-Device-Fall bleibt funktionsfähig).max_tokens→max_completion_tokens(neuere Modelle lehnenmax_tokensab) undstrict: truemit strict-kompatiblem Schema — vorher konnten Items still als „failed" enden.correctIndexwird jetzt an allen drei Persistenzpfaden (Live-Generierung, Batch-Ingest, Chat-Write-Tool) gegenoptions.lengthvalidiert — vorher waren Multiple-Choice-Fragen mit unmöglicher richtiger Antwort speicherbar.endsAtim Kalender-Tool wird wiestartsAtvalidiert.x-file-name-Header 400 statt 500.thesisProject.supersededById, der COALESCE-Unique-Index der Ordner-Siblings ist jetzt auch im Drizzle-Schema deklariert, und Generation-Rows (polymorphertargetId, kein FK möglich) werden beim Löschen von Deck/Quiz aufgeräumt (inkl. einmaliger Orphan-Cleanup-Migration).startsWith(root)akzeptierte auchroot-evil), S3-Keys lehnen Backslashes ab.setMaterials(Assignments) filtert Materialien jetzt explizit aufuserIdstatt nur transitiv über das Modul.Frontend/UX
error.message-Interna mehr.loading.tsx-Skeletons für alle Modul-Unterrouten,study/due, Settings, Admin, Material-Viewer und Plan-Seiten (vorher fror die alte Seite beim Navigieren ein).isNetworkErrorwertete jedenTypeErrorals „offline" — echte Client-Bugs landeten still in der Offline-Outbox statt sichtbar zu werden.Architektur-Refactorings
FormDialog: Die Copy-Paste-Hülle der Create/Edit-Dialoge (Deck/Quiz/Plan) ist in eine gemeinsame Komponente extrahiert; die Generate-Dialoge mit Job-Progress behalten bewusst ihre Sonderstruktur.src/config/module-tabs.ts) für Sidebar und Tab-Leiste — vorher zwei driftende Kopien.awaits →Promise.all(TTFB).Bewusst NICHT geändert (geprüft, begründet belassen)
unpack-zipmitretryLimit: 0: dokumentierter Trade-off (nicht idempotent).Migration
drizzle/0035_fair_killmonger.sql— bereinigt hängende Thesis-Supersede-Pointer, fügt das FK hinzu, legt den Folder-Index idempotent an (IF NOT EXISTS, existiert auf Bestands-DBs bereits) und räumt Generation-Orphans auf.npm run db:migratewie üblich.🤖 Generated with Claude Code
https://claude.ai/code/session_01UrdkggYqhNCCZVHyJrdwBR
Generated by Claude Code