Files
MindSift/Doku/flaws.md
T
sandrodachandClaude Opus 5.5 51f90eb7e4
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m26s
feat(notes): browser sync engine with offline queue; editor saves through the operation log
Phase 2 of the collaboration plan:
- frontend/src/lib/docSync: TypeScript port of the block tree, operations,
  effects and order keys (kept identical to Go via shared test vectors),
  diff from the editor's flat list to operations, IndexedDB persistence,
  HTTP transport and DocSyncEngine (one request in flight, idempotent
  resends, backoff, 3 s/30 s polling, keepalive flush and leave on exit,
  block leases for focus, 20 s grace and new blocks).
- Text changes carry the version the user saw when typing (first change
  wins when coalescing; a stale focused field keeps its old base), so a
  concurrent edit becomes a conflict copy instead of a silent overwrite.
- DocumentEditor uses the engine instead of the 800 ms whole-document PUT;
  editors (not only owners) can edit, remote changes appear live.
- Server: task IDs of linked blocks are delivered again, linked tasks follow
  ticked/deleted blocks after sync, note tags are derived, title changes
  notify other users; block IDs already used in another note are renamed
  (starter content with fixed IDs was empty for every further user).
- Fix: unlinking tasks from blocks wrote to non-existent columns and never
  worked (error was swallowed).
- Tests: Go/TS order key vectors, diff round trips, engine races (lost
  responses, offline + reload, edits arriving between typing and sending,
  stale focused field, duplicate first block), plus a Playwright test with
  two browsers against the real image (e2e/run.sh).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 12:28:15 +01:00

21 KiB
Raw Blame History

Bekannte Schwachstellen, Risiken & technische Schulden (Flaws)

Stand: Code-Review des Branches dev (Oktober 2026). Jeder Eintrag wurde gegen den Quellcode geprüft; die Fundstellen sind angegeben. Einträge ohne Live-Test sind als nicht live verifiziert markiert.

Schweregrade: Hoch (Datenabfluss oder Rechteumgehung möglich), Mittel (Fehlfunktion oder Schutz greift nicht), Niedrig (Wartbarkeit, Komfort, Hygiene).


Übersicht

ID Bereich Kurzbeschreibung Schwere
FLAW-01 Realtime BroadcastToAll sendet Aufgaben- und Listendaten an alle Verbindungen Behoben
FLAW-02 Realtime Räume ohne Berechtigungsprüfung Behoben
FLAW-03 Aufgaben ReorderTasks ändert beliebige Aufgaben per ID (IDOR) Behoben
FLAW-04 Auth Fest eincodiertes Fallback-JWT_SECRET Hoch
FLAW-05 Benachrichtigungen Discord-/Webhook-URLs ungeprüft (SSRF) Mittel
FLAW-06 Aufgaben Jeder Editor einer Aufgabenliste kann sie per teamId an weitere Gruppen freigeben Mittel
FLAW-07 Auth API-Key-scopes werden nie ausgewertet; ein Key ist ein Vollzugriff Mittel
FLAW-08 Auth Webhook-Ingest mit JWT prüft weder Sperrliste noch token_version noch is_active Mittel
FLAW-09 Auth Kein Rate-Limit, keine Sperre bei Fehlversuchen, Mindestpasswort 4 Zeichen Mittel
FLAW-10 Freigaben sharing_mode wird bei Freigaben nicht durchgesetzt; Benutzerverzeichnis leakt alle Konten Mittel
FLAW-11 Workspace Block-Upsert (ON CONFLICT(id)) prüft das Dokument nicht Behoben
FLAW-12 Benachrichtigungen notify_before_minutes wirkungslos; Alarm feuert nach Umplanung nie wieder Mittel
FLAW-13 Datenbank Keine Schema-Versionierung; Migrationsfehler werden verschluckt; migrations/001_init.sql ist toter Code Mittel
FLAW-14 Backend Fehler von Schreiboperationen werden häufig ignoriert (_, _ =) Mittel
FLAW-15 Realtime WriteTimeout von 30 s beendet SSE-Streams Behoben
FLAW-16 Setup /auth/setup ist bis zum ersten Konto offen; Demo-Modus legt admin/admin an Mittel
FLAW-17 Workspace Bearbeiter-Freigaben für Notizen erst im Backend aktiv, nicht im UI Niedrig
FLAW-18 Suche Nur eigene Gedanken und Dokumente, LIKE ohne Escaping, keine FTS Niedrig
FLAW-19 Qualität Frontend-Tests nur für den Outliner, lint ist ein Platzhalter Niedrig
FLAW-20 Qualität Sehr große Dateien, duplizierte Zugriffslogik Niedrig
FLAW-21 Repo LICENSE fehlt, obwohl README MIT angibt Niedrig
FLAW-22 Netzwerk Token in der URL (WS/SSE), CORS *, CheckOrigin immer true Niedrig
FLAW-23 SQLite Single-Writer-Engpass bei vielen gleichzeitigen Schreibern Niedrig
FLAW-24 Gruppen Organisations-Endpunkte ohne Berechtigungsprüfung Mittel
FLAW-25 Workspace Zusammenführen/Einrücken an einem fremd gesperrten Bullet wird nur teilweise ausgeführt Mittel
FLAW-26 Workspace Nach Enter landet sehr schnell Getipptes noch im vorherigen Bullet Niedrig

Details

FLAW-01: Broadcast an alle Verbindungen (Datenabfluss)

  • Status: Behoben. BroadcastToAll existiert nicht mehr. Aufgabenereignisse gehen an Eigentümer und Benutzer der Liste (GetTaskListUserIDs), Listenereignisse tragen nur noch die ID, Ordnerereignisse gehen an Benutzer bzw. Ordnerbeteiligte (GetSharedFolderUserIDs). Test: TestTaskEventsOnlyReachAuthorizedUsers.
  • Fundstellen: internal/ws/hub.go BroadcastToAll; Aufrufer in internal/api/server.go: handleUpdateThought (TASK_UPDATED), handleDeleteThought (TASK_DELETED), handleReorderTasks (TASK_REORDERED), handleUpdateTaskList, handleCreateTaskListShare, handleDeleteTaskListShare (TASK_LIST_UPDATED), handleDeleteTaskList.
  • Problem: BroadcastToAll schreibt an allClients und sseClients. Dazu zählen auch Verbindungen mit userID = "anonymous", denn handleWebSocket und handleSSE liegen außerhalb der Auth-Gruppe und lassen jede Verbindung zu. Der vollständige Aufgabeninhalt (Titel, Notizen, Unteraufgaben) und die Listen samt Mitgliedern gehen so an jeden, der /api/v1/events öffnet.
  • Maßnahme: Ereignisse an die berechtigten Nutzer adressieren (wie bei DOC_* über GetAuthorizedDocUsers), unauthentifizierte Verbindungen ablehnen.

FLAW-02: Räume ohne Berechtigungsprüfung

  • Status: Behoben. /ws und /events verlangen ein gültiges Token (sonst 401). Jeder Raumbeitritt läuft über Hub.SetRoomAuthorizer → Server.canJoinRoom (Notiz bzw. Liste muss lesbar sein). /events/rooms liegt hinter der Anmeldung und akzeptiert nur die eigene sseId. Tests: TestRealtimeEndpointsRequireAuth, TestSSERoomJoinRequiresAccess, TestRoomJoinsRequireAuthorization.
  • Fundstellen: Hub.readPump (JOIN_ROOM), handleSSERooms (Route /api/v1/events/rooms liegt außerhalb der Auth-Gruppe), Hub.Register (Raum aus ?listId=/?spaceId=).
  • Problem: Wer eine Raum-ID kennt (doc:<id>, Listen-ID), tritt bei und erhält Inhalte wie DOC_BLOCKS_PATCHED oder ITEM_*. Raum-IDs tauchen in Ereignissen auf und sind nicht geheim. Für den SSE-Beitritt reicht die sseId, die der Server selbst im Stream mitsendet.
  • Maßnahme: Beitritt nur nach Zugriffsprüfung (GetDocumentByID, GetListByID); /events/rooms hinter authMiddleware.

FLAW-03: ReorderTasks ohne Zugriffsprüfung (IDOR)

  • Status: Behoben. handleReorderTasks lehnt die ganze Anfrage ab, sobald eine Aufgabe nicht zugreifbar (404) oder schreibgeschützt (403) ist. Test: TestReorderRejectsForeignTasks.
  • Fundstelle: Store.ReorderTasks (UPDATE thoughts SET sort_order … WHERE id = ?) und handleReorderTasks.
  • Problem: Die SQL-Anweisung ist weder auf user_id noch auf die Liste eingeschränkt. Jeder angemeldete Nutzer kann sortOrder, kanbanStatus und damit isCompleted fremder Aufgaben ändern, sofern er die ID kennt. Die Leser-Prüfung für Aufgabenlisten überspringt Aufgaben, auf die der Nutzer gar keinen Zugriff hat (GetThoughtByID liefert dann einen Fehler), und schützt davor nicht.
  • Maßnahme: Jede ID gegen accessibleTaskListIDsQuery bzw. user_id prüfen und bei Verstoß ablehnen.

FLAW-04: Fallback-JWT-Secret

  • Fundstelle: internal/config/config.go: getEnv("JWT_SECRET", "mindsift-default-selfhosted-jwt-secret-key-2026"); docker-compose.yml und .env.example liefern ebenfalls einen Platzhalter.
  • Problem: Wer JWT_SECRET nicht setzt (oder den Platzhalter behält), läuft mit einem öffentlich bekannten Schlüssel. Damit lassen sich gültige Tokens für jede Benutzer-ID fälschen. install.sh erzeugt zwar ein Secret, andere Installationswege nicht.
  • Maßnahme: Start verweigern oder Secret beim ersten Start erzeugen und in /data ablegen.

FLAW-05: SSRF über Alarm-URLs

  • Fundstellen: handleUpdateUserSettings (keine Validierung), sendDiscordNotification, sendCustomWebhookNotification.
  • Problem: Jeder Nutzer kann beliebige URLs hinterlegen. Der Server sendet POST-Anfragen dorthin, auch an interne Adressen (http://169.254.169.254, Docker-Netz, localhost). Über handleTestUserAlarm lässt sich das sofort auslösen.
  • Maßnahme: Nur https, Discord-Host prüfen, private/Loopback-Adressen beim Verbinden sperren.

FLAW-06: Editor kann Aufgabenliste weiterteilen

  • Fundstelle: Store.UpdateTaskList (teamID-Zweig ruft AddTaskListShare); handleUpdateTaskList prüft nur, dass der Nutzer kein Leser ist.
  • Problem: POST /task-lists/{id}/shares ist Eigentümern vorbehalten. Über PUT /task-lists/{id} mit teamId kann aber jeder Editor die Liste einer weiteren Gruppe zuweisen (mit Schreibrecht) und die Zuweisung der bisherigen Gruppe aufheben.
  • Maßnahme: teamId-Änderungen nur für den Eigentümer; das Legacy-Feld task_lists.team_id zugunsten von task_list_shares abschaffen.

FLAW-07: API-Key-Scopes ohne Wirkung

  • Fundstellen: Tabelle api_keys.scopes (Standard ingest,read), authMiddleware.
  • Problem: Ein Key mit Präfix mindsift_sec_ authentifiziert als der Benutzer, mit allen Rechten: alle /api/v1-Routen, bei Admins auch /admin/*, außerdem das Anlegen weiterer Keys. Für Webhooks (nur Aufgaben anlegen) ist das überdimensioniert.
  • Maßnahme: Scopes auswerten; Keys standardmäßig nur auf /inbox und /webhooks/ingest beschränken.

FLAW-08: Webhook-Ingest umgeht Sitzungsprüfung

  • Fundstelle: handleWebhookIngest, JWT-Zweig.
  • Problem: Das Token wird nur kryptografisch validiert. IsTokenRevoked, token_version und is_active werden nicht geprüft; ein ausgeloggtes oder deaktiviertes Konto kann weiter Aufgaben anlegen (bis Token-Ablauf nach 30 Tagen).
  • Maßnahme: Dieselbe Prüfkette wie in authMiddleware verwenden (gemeinsame Funktion statt Kopien; siehe auch authenticateUserFromReq).

FLAW-09: Kein Schutz gegen Brute-Force

  • Fundstellen: handleLogin, handleRegister, handleFirstBootSetup.
  • Problem: Weder Rate-Limit noch Sperre; middleware.RealIP vertraut X-Forwarded-For ungeprüft. Mindestpasswortlänge ist 4 Zeichen. Der Login-Pfad ist so über das Internet raten- und durchprobierbar.
  • Maßnahme: Rate-Limit je IP und Konto, längeres Mindestpasswort.

FLAW-10: sharing_mode wird nicht durchgesetzt

  • Fundstellen: Store.GetDiscoverableUsers, Store.GetCollaborators; Share-Handler in server.go.
  • Problem:
    1. Der Modus beeinflusst nur GetDiscoverableUsers. In teams_only liefert die Funktion trotzdem alle Benutzer (if … mode == "open" || mode == "teams_only" { return GetAllUsers() }).
    2. GET /collaborators ignoriert den Modus komplett und liefert alle aktiven Konten mit E-Mail-Adresse.
    3. Keine der Share-Funktionen prüft den Modus. strict_private und teams_only verhindern Freigaben nicht, wenn man die Ziel-ID kennt.
  • Maßnahme: Eine zentrale Funktion canShareWith(owner, target) und in allen Share-Handlern verwenden; Verzeichnisse darauf einschränken.

FLAW-11: Block-Upsert prüft das Dokument nicht

  • Status: Behoben. Alle Blockänderungen laufen über Store.applyDocChange: Einfügen mit einer Block-ID aus einer anderen Notiz wird abgelehnt (exists), und der Upsert ändert nur Zeilen derselben Notiz (WHERE blocks.document_id = excluded.document_id). Test: TestInsertCannotReuseBlockIDOfAnotherNote.
  • Fundstelle: Store.PatchDocumentBlocks, INSERT INTO blocks … ON CONFLICT(id) DO UPDATE.
  • Problem: blocks.id ist global eindeutig. Existiert die ID bereits in einem anderen Dokument, überschreibt der Konflikt-Zweig dessen Inhalt, ohne document_id zu prüfen. Voraussetzung: Schreibrecht im eigenen Dokument und Kenntnis einer fremden Block-ID.
  • Maßnahme: DO UPDATE … WHERE blocks.document_id = excluded.document_id; fremde IDs ablehnen.

FLAW-12: Alarm-Logik

  • Fundstellen: startAlarmTicker, GetDueTasksForAlarm, UpdateThoughtDetails, UserSettingsView.
  • Probleme:
    • notify_before_minutes (Einstellungen: „15 Minuten vorher“ usw.) wird nirgends gelesen. Fälligkeiten haben nur ein Datum (YYYY-MM-DD), eine Vorlaufzeit ist damit nicht abbildbar.
    • Verglichen wird mit time.Now() in der Zeitzone des Containers (ohne TZ also UTC).
    • alarm_sent wird nie zurückgesetzt. Wird die Fälligkeit nach einem Alarm verschoben, gibt es keinen neuen.
    • Schlagen alle Kanäle fehl, wird jede Minute erneut gesendet (kein Backoff, keine Dead-Letter-Queue).
  • Maßnahme: Fälligkeit mit Uhrzeit speichern, alarm_sent bei Änderung von due_date zurücksetzen, Wiederholungen begrenzen.

FLAW-13: Migrationen

  • Fundstellen: Store.migrate in internal/storage/sqlite.go, migrations/001_init.sql.
  • Probleme:
    • Es gibt keine Versionstabelle. Alle ALTER TABLE-Anweisungen laufen bei jedem Start erneut; Fehler werden pauschal ignoriert (_, _ = s.db.Exec(q)), echte Fehler fallen nicht auf.
    • Mehrere UPDATE/DROP TABLE-Bereinigungen laufen bei jedem Start.
    • migrations/001_init.sql wird nur in das Image kopiert, aber nirgends gelesen, und ist gegenüber dem tatsächlichen Schema veraltet.
  • Maßnahme: schema_migrations-Tabelle mit nummerierten, einmalig ausgeführten Schritten; die SQL-Datei entweder einbetten oder entfernen.

FLAW-14: Verschluckte Fehler

  • Fundstellen: u. a. PatchDocumentBlocks (Upserts/Deletes), AddTaskListShare, RemoveTaskListShare, viele _, _ = s.db.Exec.
  • Problem: Scheitert ein Schreibzugriff (z. B. SQLITE_BUSY), antwortet die API trotzdem mit Erfolg und broadcastet den Stand, der nie gespeichert wurde. Fehler lassen sich nur schwer nachvollziehen.
  • Beispiel (behoben): Das Lösen der Verknüpfung zwischen Notiz-Block und Aufgabe („Verknüpfung aufheben“, Löschen eines verknüpften Blocks) schrieb in nicht existierende Spalten thoughts.doc_id/block_id. Der Fehler wurde verschluckt, die Funktion war nie wirksam. Korrigiert; im neuen Sync-Weg gleicht ReconcileDocTaskLinks Häkchen und gelöschte Blöcke ab.
  • Maßnahme: Mehrstufige Änderungen in Transaktionen bündeln und Fehler zurückgeben.

FLAW-15: WriteTimeout und SSE

  • Status: Behoben. ServeSSE hebt die Schreibfrist für die eigene Antwort auf (http.ResponseController.SetWriteDeadline). Test mit 1-s-Timeout: TestSSEStreamOutlivesWriteTimeout. Der Test hat den Abbruch im alten Code bestätigt.
  • Fundstelle: cmd/server/main.go WriteTimeout: 30 * time.Second.
  • Problem: Go beendet nach diesem Zeitraum das Schreiben der Antwort. Für den SSE-Fallback (ServeSSE) bedeutet das voraussichtlich einen Verbindungsabbruch alle 30 s. WebSockets sind durch Hijacking nicht betroffen. Der Client verbindet sich neu, daher fällt es kaum auf.
  • Maßnahme: Im SSE-Handler http.ResponseController.SetWriteDeadline(time.Time{}) setzen oder WriteTimeout für diese Route aufheben.

FLAW-16: Ersteinrichtung und Demo-Modus

  • Fundstellen: handleFirstBootSetup, Store.EnsureDemoAccounts, config.Load (MINDA_DEMO, MINDSIFT_DEMO, DEMO_MODE).
  • Probleme: Solange kein Konto existiert, kann jeder POST /auth/setup aufrufen und wird Administrator. Mit gesetztem Demo-Flag entstehen Konten admin/admin und user/user; versehentlich auf einer öffentlichen Instanz aktiv wäre das eine Komplettübernahme.
  • Maßnahme: Setup per einmaligem Token aus dem Server-Log absichern; Demo-Modus nur zusammen mit expliziter Warnung/Bestätigung.

FLAW-17: Schreibgeschützte Dokumentfreigaben

  • Stand: Die Zwangsmigration auf read ist entfernt, das Backend speichert und prüft Bearbeiter-Freigaben (DocRole, /docs/{id}/sync). Offen bis Phase 5 des Kollaborationsplans: Freigabe-Dialog bietet nur „Nur Lesen“, der alte Editor (PUT /documents/{id}) schreibt nur für den Eigentümer.
  • Fundstellen: Migration in Store.migrate (UPDATE doc_shares SET permission = 'read' …, ebenso doc_team_shares, workspace_folder_shares), PatchDocumentBlocks/UpdateDocument (nur Eigentümer).
  • Hinweis: Das ist offenbar gewollt: Nur der Eigentümer schreibt in ein Dokument, Empfänger lesen. Es gibt daher kein kollaboratives Bearbeiten. Das Schema (CHECK(permission IN ('read','write'))), die Zweige für write im Code und frühere Dokumentation suggerieren etwas anderes. Es gibt weder Sperren noch Konfliktauflösung, noch Cursor-Presence.
  • Maßnahme: Entweder Schreibfreigaben mit Konfliktbehandlung ergänzen oder den toten Code und das Schema bereinigen.

FLAW-18: Suche

  • Fundstelle: Store.SearchAll.
  • Probleme: Durchsucht nur eigene Gedanken (30) und eigene Dokumente (20); geteilte Dokumente, Aufgaben aus geteilten Listen, Einkaufslisten und Blöcke bleiben außen vor. LIKE '%…%' ohne Escaping von %/_, keine Indizes, keine Relevanz.
  • Maßnahme: FTS5-Index über Blöcke/Gedanken; Berechtigungen mitberücksichtigen.

FLAW-19: Geringe Frontend-Testabdeckung

  • Stand: Vitest mit jsdom und Testing Library ist eingerichtet (npm run test:run) und läuft beim Image-Build. Getestet sind bisher nur Outliner-Funktionen (Backspace-Zusammenführen, Cursor-Position beim Klicken).
  • Problem: Aufgaben, Listen, Freigabe-Dialoge und Berechtigungsanzeigen sind ungetestet. "lint" in frontend/package.json ist weiterhin ein Platzhalter (echo).
  • Maßnahme: Tests für kritische Pfade ergänzen, ESLint als echtes lint-Skript.

FLAW-20: Dateigröße und Duplikate

  • internal/storage/sqlite.go (≈ 5.600 Zeilen), internal/api/server.go (≈ 3.000), WorkspaceView.tsx (≈ 2.300), TasksView.tsx (≈ 2.500), api.ts (≈ 1.500).
  • Die Zugriffsabfrage für Aufgabenlisten steht dreimal fast identisch im Code (accessibleTaskListIDsQuery, GetTaskLists, GetTaskListByID). Dokumentzugriff ist ähnlich verteilt. Änderungen an Berechtigungen müssen an mehreren Stellen erfolgen und werden leicht vergessen.
  • Maßnahme: In Teilmodule zerlegen (Auth, Dokumente, Aufgaben, Listen, Teams), Zugriffsprüfung zentralisieren.

FLAW-21: Lizenzdatei fehlt

  • README und Badge nennen die MIT-Lizenz und verlinken auf LICENSE; die Datei existiert nicht im Repository.

FLAW-22: Netzwerk-Details

  • authenticateUserFromReq akzeptiert das Token als ?token= (nötig für WebSocket/EventSource, die keine Header setzen). Tokens landen dadurch in Proxy- und Zugriffslogs.
  • AllowedOrigins: ["*"] (Auth läuft über Authorization-Header, nicht über Cookies, das Risiko ist daher begrenzt) und CheckOrigin liefert immer true.
  • Ein einmal verbundener WebSocket/SSE-Stream wird bei Logout oder Token-Widerruf nicht getrennt.
  • Maßnahme: Kurzlebiges, einmaliges Stream-Ticket statt Langzeit-JWT in der URL; Origin gegen BASE_URL prüfen.

FLAW-23: SQLite-Schreibengpass

  • Eine SQLite-Datei (WAL, busy_timeout, bis zu 8 offene Verbindungen) erlaubt nur einen gleichzeitigen Schreiber. Für Familien und kleine Teams unkritisch; bei vielen aktiven Editoren sind SQLITE_BUSY-Fehler möglich, die wegen FLAW-14 stumm bleiben.

FLAW-24: Organisations-Endpunkte ohne Berechtigungsprüfung

  • Fundstellen: handleDeleteOrganization, handleGetOrgTeams, handleCreateOrgTeam (und Store.DeleteOrganization) in server.go bzw. sqlite.go.
  • Problem: Diese Handler prüfen weder Eigentümer noch Mitgliedschaft. Jeder angemeldete Nutzer kann mit bekannter ID eine fremde Organisation löschen, deren Gruppen auflisten oder dort Gruppen anlegen. Gruppenbezogene Endpunkte (/teams/{id}…) prüfen dagegen korrekt auf Gruppen-Admin. Das Datenmodell kennt Organisationen nur noch rudimentär; im UI werden Gruppen direkt über POST /teams angelegt (orgId Standard default).
  • Maßnahme: Organisationen entweder konsequent absichern (Eigentümer/Server-Admin) oder entfernen.

FLAW-25: Teilweise ausgeführte Struktur-Aktionen an gesperrten Bullets

  • Fundstellen: handleMergeWithPrevious und handleIndentBlock in OutlinerTree.tsx; Sperrregeln in internal/collab (Verschieben/Löschen gesperrter Blöcke wird abgelehnt).
  • Problem: Eine Editor-Aktion besteht oft aus mehreren Operationen. Ist einer der beteiligten Bullets von jemand anderem gesperrt, lehnt der Server nur diese Operation ab. Beispiel aus dem Browser-Test: Backspace am Zeilenanfang hängt den Text oben an (angewendet), das Löschen des unteren Bullets wird abgelehnt – der Text steht dann doppelt da. Einrücken eines fremd gesperrten Bullets wird ohne Hinweis zurückgenommen. Es geht kein Text verloren.
  • Maßnahme (Phase 3): Gesperrte Bullets im Editor anzeigen und Struktur-Aktionen, die sie berühren, gar nicht erst ausführen.

FLAW-26: Fokus-Verzögerung nach Enter

  • Fundstelle: focusBlockInput in OutlinerTree.tsx setzt den Fokus erst im nächsten Animation-Frame.
  • Problem: Zeichen, die innerhalb dieses Frames getippt werden (automatisiert oder sehr schnelles Tippen), landen noch im vorherigen Bullet. Bei fremd gesperrtem Bullet entsteht daraus eine Konfliktkopie.
  • Maßnahme: Fokus nach dem Anlegen synchron setzen (useLayoutEffect).

Aus der früheren Fassung entfernt

Die frühere flaws.md führte als „gelöst“, was im Code nicht existiert: In-Memory-DocRooms, clientId-Echo-Schutz, Cursor-Presence, 1,5-s-Debounce-Flush, internal/ws/docroom.go, frontend/src/lib/useDocumentSync.ts und Yjs/CRDT. Der Code enthält nur noch internal/ws/hub.go (Räume, Benutzer-Kanäle, SSE-Fallback). Die CRDT-Tabellen werden beim Start gelöscht (DROP TABLE document_crdt_*). Dokumente werden per REST gespeichert (PATCH /documents/{id}/blocks) und als DOC_BLOCKS_PATCHED verteilt. Die frühere Aussage zur Token-Versionierung (token_version, revoked_tokens) trifft dagegen zu und ist in sharing-and-permissions.md und architecture.md beschrieben.