docs: document chunked file transport, busy overlay, VACUUM, shutdown, UI features

CLAUDE.md was missing most of this session's work. Added the gotchas a
fresh session most needs:
- Chunked JS→Delphi transport (_streamChunks) + the WebView2 URL-limit
  black-screen trap + the resolved-chunk stale-timeout hang (don't
  regress the clearTimeout in onFileChunkAck).
- Busy overlay helpers + the undefined-CSS-var trap (--bg-elev-3).
- Auto-VACUUM (SQLite never shrinks on DELETE).
- Clean shutdown / WAL (WM_QUERYENDSESSION).
- WebView2 nav race (cold-start black screen).
- node --check build gate.
- UI/data: profile avatar (users.avatar_b64 + /avatar), quick-search
  fill modes + username-only autofill + keepclip, editable custom-field
  combobox, settings search, password reveal on prompts.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-07-04 17:49:53 +01:00
parent ac909f4f09
commit 73e4e37f19
+96
View File
@@ -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 Sans étape 1, l'exe embarque l'ancienne version des assets — le bug le
plus courant après modif frontend. 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 ## Carte des fichiers
| Rôle | Path | | 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 brandés. URL hostname-only fonctionne pour les sites dont le titre
contient le hostname (rare). 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 `<input>` + menu custom `.so-combo` (flèche =
toutes les options, input = frappe libre). PAS un `<datalist>` natif
(il filtre au texte tapé → n'affiche que ce qui matche, déroutant).
- **Settings search** : `#settingsSearch` filtre les `.setting-row` par
texte ; match sur le label de section garde toute la section. Esc dans
la search avec query = clear ; Esc vide = ferme Settings.
- **Password reveal** : `promptDialog` a un œil en mode `password` → tous
les prompts (export, import, backup pwd, recovery, sync pwd) le montrent.
## Clean shutdown (WAL)
`WM_QUERYENDSESSION` / `WM_ENDSESSION` captés dans la fenêtre message-only
(`PM.Bridge`) → `FShutdownPending`. `FormCloseQuery` bypasse le
minimize-to-tray sur arrêt/redémarrage/logoff Windows → `FServer.Free`
checkpoint le WAL proprement (sinon Windows force-kill après timeout →
résidus `-shm`/`-wal`). Kill brutal (Task Manager) laisse les résidus, mais
SQLite récupère au prochain open.
## WebView2 nav race (écran noir cold-start)
Le timer de navigation (1.5 s) ne consomme `FPendingURL` que si
`FBrowserInitialized` (set dans `OnInitialized`) — sinon il ré-arme (borné
10 essais). Sans ça, un tick timer avant l'init du moteur Navigate() dans
le vide ET vide `FPendingURL` → écran noir permanent sur cold-start lent.
## Contraintes / pièges connus ## Contraintes / pièges connus
- **UIPI** : process non-élevé ne peut pas SendInput vers process élevé - **UIPI** : process non-élevé ne peut pas SendInput vers process élevé