Zwei Befunde aus der Integrations-Review:
- Race: der Page-Drain und der Worker machen je ein read-modify-write auf
'quicksave_pending' ohne kontextuebergreifende Atomizitaet. Ein Worker-Append im
await-Fenster des Drains konnte einen bereits gedrainten Eintrag in der Queue belassen,
den ein Folge-Drain erneut in die Inbox schrieb (Duplikat). Jede eingespielte Bookmark
traegt jetzt die Pending-id als srcId; ein erneut auftauchender Eintrag wird uebersprungen
statt doppelt eingefuegt. boards-Write bleibt vor der Queue-Bereinigung -> kein Verlust.
- Validierung: der Drain hat e.url ohne isSafeUrl gepusht, anders als jeder andere
Bookmark-Schreibpfad. isSafeUrl (jetzt im DOM-freien quicksave-core, http/https/ftp)
filtert unsichere/leere Protokolle vor dem Schreiben ins Board.
Beim Trash-Import sortierte combined.sort+slice(-N) rein nach deletedAt: brachte ein
Backup neuere Eintraege mit, fielen aeltere LOKALE Eintraege aus dem Cap — und die sind
die einzige Kopie der geloeschten Daten (Datenverlust). Jetzt haben lokale Eintraege
Vorrang (alle behalten, sind bereits auf TRASH_MAX_ENTRIES gekappt), Importe fuellen nur
den Rest mit den neuesten auf.
Ein reiner Klick/Tap auf den Drag-Handle (ohne echtes Verschieben) hat in onUp den
gegen die Viewport geclampten --board-x/y-Wert zurueckgelesen und als board.pos
persistiert. Bei einem off-screen geclampten Board (nach Fenster-Verkleinerung oder
Import von breiterem Screen) zerstoerte das die wahre Position. Jetzt zaehlt erst eine
Bewegung > 3px als Drag; ohne Bewegung bleibt board.pos unangetastet.
- Render + neuer debounced Resize-Handler clampen --board-x/y gegen den
aktuellen Viewport: ein auf breiterem Fenster platziertes Board rendert
nie mehr off-screen (und damit per Drag unerreichbar). board.pos bleibt
unveraendert, bei spaeterer Verbreiterung wird die Originalposition erreicht.
- drag.js: cleanup() + pointercancel-Listener. Die Klasse .board.dragging
klebte bei Touch-Interrupt/Browser-Geste sonst dauerhaft und legte den
app.js-Sync-Guard (Quick-Save-Render) still.
- main.css: '.board.blurred { position: relative }' entfernt — lag im
utilities-Layer und schlug das absolute Free-Layout (geblurrtes Board fiel
aus seiner Position + war nicht mehr drag-bar).
- data.js: board.pos wird beim JSON-Import durchgereicht (safePos-Validierung
via Number.isFinite), sonst Verlust des frei gesetzten Layouts beim Restore.
- settings.js: _makeTrap bricht ab, wenn ein .dialog-overlay offen ist, damit
der Dialog-keydown-Handler Escape/Tab allein behandelt (kein Doppelschluss,
Fokusfalle bleibt dicht)
- dialog.js: aria-labelledby/-describedby zeigen auf instanz-eindeutige IDs
(Date.now + Modul-Zaehler) statt feste dialogTitle/dialogBody, damit kurz
gestapelte Dialoge dem Screenreader nicht den falschen Titel liefern
- boards.js: Bookmark-keydown reagiert bei role=link nur noch auf Enter, Space
entfernt (Space ist Button-Semantik)
Alle 3 Manifests, newtab.html, data.js, app.js auf 2.1.0.
CHANGELOG-Eintrag mit allen 6 neuen Calculator-Modi.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>