diff --git a/CLAUDE.md b/CLAUDE.md index 0570bce..9bb3fe0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -513,7 +513,10 @@ faire semblant couterait plus que de l'admettre. **Le reste du travail Android est dans [android-client/README.md](android-client/README.md)**, tenu a jour le -2026-09-17 : les trois facons d'ouvrir le coffre (verrouillage d'ecran, code +2026-09-28 — dont le formulaire d'edition de l'entree entiere, le scan du QR +2FA, la verification a l'ouverture au lieu du retelechargement, et deux pieges +de la page (`view` qui defocalisait les champs, `toast` qui etait un `
`) ; +et avant cela, le 2026-09-17 : les trois facons d'ouvrir le coffre (verrouillage d'ecran, code d'application, mot de passe), la copie de secours `vault-recover.bin` qui survit au schema retire, le scanner de QR (`ThirdParty/ZXing`), le define `AllowCreateLocal` — creer un coffre sur le telephone est reserve au debogage — @@ -794,7 +797,10 @@ bureau virtuel** en mémoire (GDI, octets bruts, jamais sur disque) et le décod par ZXing.Delphi — le même que l'APK, `android-client\ThirdParty\ZXing` dans le chemin de recherche du `.dproj`. Pas de sélection de zone : le décodeur trouve le QR où qu'il soit. **Seul un `otpauth://` repart vers la page** -(`Bridge.onScreenQr`), le reste de l'écran ne quitte pas l'exe. Format `BGR32` +(`Bridge.onScreenQr(texte, trouvé)`), le reste de l'écran ne quitte pas l'exe. +Le second argument dit qu'**un QR a été lu** même s'il n'est pas un code 2FA : +« This QR code is not a 2FA code » plutôt que « aucun QR » (cas vu : un QR +**dynamique** de générateur en ligne encode un lien de suivi, pas le texte). Format `BGR32` et non `BGRA32` : l'octet alpha d'une capture GDI vaut 0, lu comme transparent → image blanche. @@ -1163,6 +1169,25 @@ QR que l'application Android scanne elle-meme. - Telephone : `https://github.com/O/R` <-> URL d'API, comme pour Gitea. Le QR du PC porte aussi la forme courte `github.com/O/R`. +### Ce que le depot laisse voir, et le reseau (2026-09-26) + +- **Nom par defaut `data.json`** (`SYNC_DEFAULT_FILE`, JS et page du + telephone ; `SyncDefaultFile` dans le QR). `vault-sync.json` annoncait un + coffre a qui parcourt la liste des depots. Les configurations existantes + gardent leur nom : c'est l'URL complete qui est enregistree. **Le QR ne + raccourcit que le nom par defaut courant** — un autre nom voyage en URL + complete, sinon le telephone rallongerait vers `data.json` et les deux + appareils ne liraient pas le meme fichier. +- **Messages de commit neutres** : `update from desktop` / `update from phone`. + Le poste ecrivait `sync from `, le telephone decrivait + ce qu'il faisait. Le detail reste dans le journal local. +- **8 s pour se connecter, 30 s pour repondre, trois essais sur status 0** + (poste et telephone). Mesure : `api.github.com` rend plusieurs adresses ; + `140.82.121.5` n'est joignable depuis ce reseau ni par l'application ni par un + navigateur, `140.82.121.6` repond en 0,22 s. Un navigateur passe a la + suivante aussitot ; nous attendions le delai entier. Un 404 ou un 409 sont + des reponses et ne relancent rien. + ### Idee gardee : « Reinitialiser l'historique » du depot de synchro **Rien n'est fait, et le code actuel reste tel quel** (decide le 2026-09-17). @@ -1484,6 +1509,15 @@ après chaque `DELETE /attachments/{id}`. Un petit vault sain ne paie rien. → retombe sur pwd). Fix clipboard : `MinimizeToTray(AClearClipboard=False)` via `keepclip=1` pour ne pas effacer le pwd juste copié quand on re-minimise. + **Boutons par ligne** (2026-09-18) : utilisateur, mot de passe ou note, 2FA — + seulement ceux qui existent. Une **note est toujours copiée**, même en Fill : + tapée, chacun de ses retours à la ligne partirait comme un Entrée. + **Interrupteur Copy / Fill** (2026-09-28, `qsFills()`) : visible seulement + quand une fenêtre cible existe (Ctrl+Shift+Q, bouton Search de la barrette). + Depuis le tray, pas de cible — cliquer le tray donne le premier plan à la + barre des tâches — donc copie seule ; suivre « la dernière fenêtre active » + a été écarté : les frappes partiraient au mauvais endroit si l'utilisateur a + changé d'application entre-temps. Choix retenu en pref DPAPI `qsCopy`. - **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 `` natif diff --git a/android-client/README.md b/android-client/README.md index 7b2921a..8447daa 100644 --- a/android-client/README.md +++ b/android-client/README.md @@ -341,6 +341,81 @@ reste. (code, empreinte) **dans le service** avant de proposer quoi que ce soit : le coffre ne vit que dans l'app, ouverte ou non. +## Modifier une entrée — 2026-09-25 à 09-28 + +**Un formulaire pour toute l'entrée**, ouvert par le crayon de l'en-tête. Le +crayon par ligne a disparu : chaque validation était un enregistrement complet, +donc un commit de plus dans le dépôt. + +- **Un seul envoi, seulement ce qui a changé** : `setfields?f0=&v0=&f1=&v1=…` + → `SaveEntryFields`, plusieurs `PatchEntryField` dans **un** `CommitChange`. + Le filtre des noms est celui de `SaveEntryField` (ce qui traverse le pont se + traite comme une entrée extérieure). +- **La page ne connaît pas le schéma** : elle rend une ligne par champ que + Delphi a marqué modifiable (`e` porte son nom, dans `DetailToJson`). Le champ + `folder` est une liste (`folderSelect`, avec *+ new folder*), pas un champ + libre — une faute de frappe rangerait l'entrée dans un dossier invisible. +- **Mot de passe et secret 2FA naissent masqués** (création et édition), avec + un œil (`data-eye`) pour les révéler. +- **Icône** en tête du formulaire, trois boutons : *From site* (recherche + automatique), *Image…* (sélecteur de photos d'Android via + `TTakePhotoFromLibraryAction`, réduite à 128 px, provenance **custom** — le + poste ne la remplacera jamais par une recherche), *Remove*. Une action + d'icône passe par un enregistrement : le formulaire est rouvert ensuite avec + ce qui y était tapé (`editAgain`). Manifeste : `READ_MEDIA_IMAGES`, et + `READ_EXTERNAL_STORAGE` bornée à `maxSdkVersion="32"`. +- **Scanner le QR 2FA** d'un site : bouton QR du champ 2FA → + `scanqr?for=totp` → même caméra que le scan des réglages (`FScanTotp`). Le + lien `otpauth://` part à la page (`PM.totpFromQr`), qui n'en garde que le + secret ; le journal ne note que le fait. Un autre QR fait dire au bandeau + « pas un code 2FA » et le scan continue. +- **Pas de menu ⋮ ni de crayon sur une entrée en création** : l'écran de + détail est réutilisé, et côté Delphi ces commandes visent la **dernière + entrée ouverte** — *Delete* depuis la création en aurait supprimé une autre. + +### Piège : `view` nomme l'écran, pas ce qu'on y fait + +Un gardien `focusin` défocalise tout ce qui prend le focus hors de l'écran +courant en comparant `view` à l'**identifiant de l'écran**. Le formulaire vit +dans l'écran `detail` ; poser `view = 'edit'` le faisait donc défocaliser +**chaque champ à l'instant où il prenait le focus** — clavier ouvert, pas de +curseur, rien ne rentrait. Trouvé en inspectant la page en direct par le +protocole DevTools (APK debug, `adb forward … localabstract:webview_devtools_remote_…`) : +le champ était atteignable et focalisé, ce qui éliminait le DOM et le CSS. +`view` reste `detail`, un drapeau `editing` dit qu'on modifie, et la flèche +comme la touche retour referment le formulaire avant de quitter l'entrée. + +### Piège : `toast` était un `
` + +Le navigateur expose chaque `id` comme variable globale : `toast` désignait +l'élément `
`, et **sept appels `toast('…')` de la page levaient +« toast is not a function »** — le message ne s'affichait pas et le reste du +gestionnaire ne s'exécutait pas (dont « Tap again to delete »). Une vraie +`function toast(text)` globale l'emporte sur l'id. + +## Synchro : ce qui a changé — 2026-09-25 à 09-26 + +- **L'ouverture ne tire plus l'instantané.** Elle ouvre depuis le cache, puis + `ProbeRemote` pose une requête conditionnelle : si le dépôt a bougé, le + témoin **bleu** du bouton Sync s'allume et l'utilisateur tire quand il veut. + Le journal dit les trois cas : `unchanged`, `it moved`, `no answer (HTTP 0)` + — ce dernier n'est pas « rien n'a changé ». +- **Dépôt vide = création.** Un 404 à la lecture pendant une publication était + traité comme un échec : rien ne partait jamais vers un dépôt neuf. Sans `sha`, + l'API des contenus crée le fichier. +- **Réseau : 8 s pour se connecter, 30 s pour répondre, trois essais**, et + `Connection: close`. Mesuré : `api.github.com` rend plusieurs adresses, + `140.82.121.6` répond en 0,22 s et `140.82.121.5` n'est joignable depuis ce + réseau **ni par l'application ni par un navigateur**. Un navigateur passe à + la suivante aussitôt ; l'application attendait son délai entier. Le poste + applique la même règle. +- **Pas de clavier de recherche pendant une synchro** : synchroniser coffre + fermé ouvre d'abord le coffre, et ce chemin dépliait le clavier. +- **Messages de commit neutres** : `update from phone` partout (le téléphone + écrivait `new entry from phone`, `icon fetched…`, `entries deleted…`). Le + détail reste dans le journal local. +- **Nom par défaut `data.json`** au lieu de `vault-sync.json` — voir CLAUDE.md. + ## Saisie : ce qui a résisté - **`user-select: none` du `body`** était hérité par les champs : le curseur ne