diff --git a/CLAUDE.md b/CLAUDE.md
index 84458ab..60560ee 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -22,6 +22,12 @@ Toute modif `index.html` / `js/` / `css/` nécessite :
Sans étape 1, l'exe embarque l'ancienne version des assets — le bug le
plus courant après modif frontend.
+`BuildAssets.ps1` lance `node --check` sur chaque `.js` embarqué **avant**
+de générer `assets.res` : une erreur de syntaxe JS avorte le build (au
+lieu d'embarquer un bundle mort qui ne se révèle qu'après un rebuild
+Delphi complet). Node optionnel — absent = warning, on continue. Toujours
+`node --check js/app.js` après un gros edit JS.
+
## Carte des fichiers
| Rôle | Path |
@@ -689,6 +695,96 @@ champ — ça détruisait le focus sur les forms qui n'étaient pas centrés
brandés. URL hostname-only fonctionne pour les sites dont le titre
contient le hostname (rare).
+## Transport gros fichiers JS→Delphi (chunké — PIÈGE MAJEUR)
+
+Les bridge cmds passent par `window.location.href = 'cmd://...'`. WebView2
+**cape l'URL à ~2 Mo** : un gros payload base64 dans l'URL (attachment,
+export, backup, snapshot de sync) fait dépasser la limite → la navigation
+**vide le document → écran noir**. Symptôme classique quand le vault
+grossit.
+
+Solution : `_streamChunks(reqId, b64, onProgress)` (js/app.js) découpe le
+base64 en morceaux de 1 Mo envoyés **séquentiellement** (chaque chunk
+attend son ack `Bridge.onFileChunkAck` avant le suivant — sinon les
+`location.href` se coalescent et seul le dernier passe). Côté Delphi,
+`file/chunk` accumule dans `FFileSaveChunks[reqId]` (un `TStringBuilder`),
+puis un `*-commit` reconstruit et agit :
+
+- `file/save-commit` → `SaveDecodedFile` (dialogue Save As)
+- `file/write-commit` → `WriteDecodedFile` (write silencieux vers un path)
+- `webdav/put-commit` → PUT WebDAV (avec If-Match)
+
+`Bridge.saveFile` / `writeFile` / `_webdavCall(put)` basculent
+automatiquement sur ce chemin si `b64.length > 1 Mo`, sinon single-shot.
+
+**Bug historique corrigé** : chaque chunk réutilise le même `reqId` ; un
+`setTimeout(30s)` d'un chunk déjà résolu finissait par supprimer le
+resolver du chunk courant → hang permanent. `onFileChunkAck` **clear
+maintenant le timer** (`clearTimeout(r.timer)`) — ne PAS régresser ça.
+
+Tout nouveau transfert de gros payload JS→Delphi DOIT passer par
+`_streamChunks`, jamais mettre le data brut dans l'URL.
+
+## Busy overlay (ops longues)
+
+`showBusy(text)` / `updateBusy(text)` / `hideBusy()` (js/app.js) → overlay
+plein écran avec spinner + texte, `z-index 200`. Utilisé pour export,
+download attachment, backup manuel, rotation master pw. Pattern : `showBusy`
++ `await new Promise(r => setTimeout(r,0))` (laisse peindre l'overlay avant
+de bloquer le thread) puis `finally hideBusy()`. Le spinner utilise
+`--border` (pas `--bg-elev-3` qui n'était pas défini avant — piège CSS :
+un `var()` non défini SANS fallback = déclaration invalide, élément
+invisible).
+
+## Auto-VACUUM SQLite
+
+SQLite ne réduit **jamais** le fichier sur `DELETE` (pages marquées libres,
+fichier gardé). Supprimer de gros attachments laisse `vault.db` gonflé.
+`DB.CompactIfBloated` (PM.Database) fait `VACUUM` **seulement si** > 20 %
+des pages sont libres ET > ~2 Mo récupérables → appelé au démarrage +
+après chaque `DELETE /attachments/{id}`. Un petit vault sain ne paie rien.
+
+## Divers UI / data (session récente)
+
+- **Profil / avatar** : colonne `users.avatar_b64 TEXT` (data URI, non
+ chiffré, cosmétique). Endpoints `GET/POST /avatar`. `state.avatarDataUri`
+ chargé à `enterApp`. Pas de photo → initiale sur couleur déterministe
+ (`avatarColorFor(username)`). Upload downscale 128px JPEG via `FileReader`
+ → `data:` URI (PAS `blob:` — la CSP `img-src 'self' data:` bloque blob).
+ Inclus dans l'export JSON (`avatar_b64`), restauré à l'import si absent.
+- **Quick search — modes fill** (Ctrl+Shift+Q) : Enter/clic-gauche = full
+ (user + Tab + pwd), Shift+Enter/clic-droit = username seul, Ctrl+Enter/
+ Ctrl+clic = password seul. `Bridge.executeAutofill(u, p, hide, 'user')`
+ → `field=user` → `ExecuteAutofill(..., AUsernameOnly=True)`. En copy-mode
+ (tray/palette, pas de HWND cible) : clic = copy pwd, clic-droit = copy user.
+ Fix clipboard : `MinimizeToTray(AClearClipboard=False)` via `keepclip=1`
+ pour ne pas effacer le pwd juste copié quand on re-minimise.
+- **Custom fields combobox éditable** : les champs avec `options` (card
+ brand, expiry…) rendent un `` + menu custom `.so-combo` (flèche =
+ toutes les options, input = frappe libre). PAS un `