diff --git a/android-client/PMA.Store.pas b/android-client/PMA.Store.pas
index 4d19337..de6eb84 100644
--- a/android-client/PMA.Store.pas
+++ b/android-client/PMA.Store.pas
@@ -98,7 +98,8 @@ function HasVault: Boolean;
procedure SaveVault(const APlainJson, AETag, ASyncPwd: string;
ARequireAuth: Boolean = False; AFreshKey: Boolean = False;
const APin: string = ''; const AKey: TBytes = nil;
- const ASalt: TBytes = nil; AIterations: Integer = 0);
+ const ASalt: TBytes = nil; AIterations: Integer = 0;
+ AClearPin: Boolean = False);
/// Le fichier local est-il scelle sous un code d'application ? Lu dans
/// l'en-tete, sans rien dechiffrer.
@@ -887,7 +888,7 @@ end;
procedure SaveVault(const APlainJson, AETag, ASyncPwd: string;
ARequireAuth, AFreshKey: Boolean; const APin: string;
- const AKey, ASalt: TBytes; AIterations: Integer);
+ const AKey, ASalt: TBytes; AIterations: Integer; AClearPin: Boolean);
{$IF DEFINED(ANDROID)}
var
LWrap: TJSONObject;
@@ -908,7 +909,12 @@ begin
//
// Retirer le code demanderait donc un geste explicite, qui n'existe pas
// encore : aujourd'hui on le remplace en le retapant.
- if (APin = '') and VaultNeedsPin then
+ // AClearPin EST LA SEULE SORTIE, et elle se demande. Le refus ci-dessous
+ // protege tous les autres chemins - c'est lui qui a empeche une
+ // synchronisation de defaire un code en silence. Retirer un code reste
+ // possible, mais seulement quand quelqu'un l'a decide et vient de prouver
+ // qu'il le connaissait en ouvrant le coffre.
+ if (APin = '') and VaultNeedsPin and not AClearPin then
begin
FLastStoreError := 'cache sealed under an unlock code; refusing to ' +
'rewrite it without one';
diff --git a/android-client/TESTS.md b/android-client/TESTS.md
index 23b2505..9a54ff7 100644
--- a/android-client/TESTS.md
+++ b/android-client/TESTS.md
@@ -120,12 +120,32 @@ prouve rien.
côte à côte**. Ils demandent deux gestes différents ; les confondre dirait
« quelque chose ne va pas » sans dire quoi.
-### F. Pièces jointes
+### F. Réglages
+
+1. Roue dentée dans la liste, ou *Settings* sur l'accueil → l'écran s'ouvre.
+2. Les trois champs du dépôt doivent porter **les valeurs en place**, pas du
+ vide : ils ne sont qu'une vue des champs que Delphi connaît déjà.
+3. Modifier l'URL, revenir par la flèche, *Sync* → la nouvelle valeur sert.
+ C'est la contre-épreuve du report à la fermeture : un réglage modifié puis
+ refermé **sans quitter le champ** doit compter.
+4. Touche **retour** dans les réglages → elle referme **sans** verrouiller ni
+ revenir à la liste.
+5. **Poser un code** depuis les réglages : taper, *Set this code*, puis ouvrir
+ le coffre → *« Unlock code changed »*. Le sceau se pose toujours à
+ l'ouverture, et nulle part ailleurs.
+6. **Retirer le code** : *Remove the unlock code*, deux appuis → *« Unlock code
+ removed »*. Fermer l'application, rouvrir → **le code n'est plus demandé**.
+7. Contre-épreuve du retrait : verrouiller, puis essayer de retirer le code
+ sans avoir ouvert le coffre → refus *« Open the vault first »*. C'est ce qui
+ empêche quelqu'un tenant le téléphone déverrouillé de lever la protection
+ sans la connaître.
+
+### G. Pièces jointes
1. Ligne *Attachment* → flèche de téléchargement → le fichier apparaît dans
*Téléchargements* sous son nom, accents et arabe compris.
-### G. Code de déverrouillage
+### H. Code de déverrouillage
1. Poser un code, ouvrir le coffre, fermer, rouvrir → il est demandé.
2. Code faux → *« Wrong unlock code »*, et **Open vault reste offert** (le
@@ -135,7 +155,7 @@ prouve rien.
4. Contre-épreuve : taper n'importe quoi dans le champ du code puis *Sync* →
le code **ne doit pas changer**.
-### H. Empaquetage
+### I. Empaquetage
1. `build-apk.ps1 -Release -Keystore ... -Alias ...` → la sortie doit afficher
`Signer #1 certificate DN: CN=R Zakarya`. Si elle affiche `CN=Android
diff --git a/android-client/web/app.html b/android-client/web/app.html
index f9470ce..8033779 100644
--- a/android-client/web/app.html
+++ b/android-client/web/app.html
@@ -305,6 +305,20 @@
font: 11px/1.45 monospace; white-space: pre-wrap; word-break: break-all;
-webkit-user-select: text; user-select: text;
}
+ /* LES REGLAGES SONT UN VOILE, PAS UNE QUATRIEME VUE DU RAIL. Chaque vue du
+ rail coute ses regles de transformation et une place dans la navigation
+ par la touche retour ; les reglages ne sont pas un lieu ou l'on circule,
+ c'est une parenthese qu'on ouvre et qu'on referme. */
+ .sheet { position: fixed; inset: 0; z-index: 80; display: none;
+ flex-direction: column;
+ background: linear-gradient(var(--bg-top), var(--bg-bot)); }
+ body.on-settings .sheet { display: flex; }
+ .sheet h2 { margin: 18px 4px 6px; font-size: 13px; font-weight: 600;
+ color: var(--muted); text-transform: uppercase;
+ letter-spacing: .06em; }
+ .sheet .note { color: var(--muted); font-size: 13px; margin: 6px 4px 0;
+ line-height: 1.45; }
+
[hidden] { display: none !important; }
.toast {
@@ -363,8 +377,9 @@