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:
@@ -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 `<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
|
||||
|
||||
- **UIPI** : process non-élevé ne peut pas SendInput vers process élevé
|
||||
|
||||
Reference in New Issue
Block a user