diff --git a/android-client/PLAN.md b/android-client/PLAN.md
index 4c57c79..60a8e84 100644
--- a/android-client/PLAN.md
+++ b/android-client/PLAN.md
@@ -776,10 +776,20 @@ téléphone qu'on voudra ensuite ouvrir sur le poste demandera un import.
**Sans retour depuis le téléphone**, et c'est délibéré : la corbeille du
poste est un drapeau `deleted` qui ne voyage pas dans la synchronisation, donc
- une suppression qui s'y arrêterait ne se propagerait nulle part. D'où le
- bouton en bas d'écran, en deux temps, qui se désarme seul au bout de quatre
- secondes — un bouton armé indéfiniment se fait toucher par mégarde bien plus
- tard.
+ une suppression qui s'y arrêterait ne se propagerait nulle part.
+
+ **Et elle ne va pas non plus dans la corbeille du poste** — vérifié dans le
+ code serveur : recevoir une pierre tombale déclenche
+ `DELETE FROM vault_entries`, un effacement dur. Supprimer depuis le téléphone
+ retire donc la ligne des deux côtés, en contournant la corbeille. **Le seul
+ filet est la sauvegarde du poste.** Il n'y a rien à afficher comme corbeille
+ ici : le téléphone n'en reçoit jamais le contenu.
+
+ L'action vit dans le **menu de l'entrée** (les trois points, en tête de
+ détail), et non plus en bouton pleine largeur au bas de chaque écran : rouge
+ et présent en permanence pour un geste qu'on fait trois fois par an. Deux
+ gestes délibérés — ouvrir, puis confirmer — et rien qui traîne sous les yeux
+ le reste du temps.
4. **Réconcilier avec la synchronisation** — fait le 2026-08-21.
Jusqu'ici les deux situations ne se rencontraient pas : un coffre local
diff --git a/android-client/web/app.html b/android-client/web/app.html
index b9fd75d..5d6ef63 100644
--- a/android-client/web/app.html
+++ b/android-client/web/app.html
@@ -201,6 +201,20 @@
padding: 10px 10px 12px 14px; margin-bottom: 8px;
}
.field .cap { color: var(--muted); font-size: 12px; margin-bottom: 4px; }
+ /* LE MENU DE L'ENTREE. Il ne contient qu'une action, et c'est voulu : elle
+ est sans retour, et une action sans retour ne doit pas etre a portee d'un
+ appui distrait. Deux gestes deliberes - ouvrir, puis confirmer - valent
+ mieux qu'un bouton rouge en permanence sous les yeux. */
+ .menu { position: absolute; right: 12px; top: 58px; z-index: 60;
+ background: var(--bg-top); border-radius: var(--radius-s);
+ border: 1px solid rgba(255,255,255,.14);
+ box-shadow: 0 8px 24px rgba(0,0,0,.35);
+ min-width: 190px; overflow: hidden; }
+ .menu button { display: block; width: 100%; text-align: left; border: 0;
+ background: none; color: var(--text); font: inherit;
+ padding: 14px 16px; }
+ .menu button:active { background: rgba(255,255,255,.10); }
+ .menu button.danger { color: #ffb4b4; }
.field .line { display: flex; align-items: center; gap: 8px; }
.field .val {
flex: 1; min-width: 0; word-break: break-all;
@@ -396,8 +410,10 @@
+
+
@@ -630,6 +646,8 @@ var ICONS = {
edit: '',
eye: ''
+ '',
+ dots: ''
+ + '',
check: '',
close: '',
// Un arc, pas un cercle : c'est l'ouverture qui rend la rotation visible.
@@ -684,15 +702,8 @@ function renderDetail(d) {
+ ico('edit', 'Edit') + '' : '')
+ '';
}
- // SUPPRIMER, tout en bas et en deux temps. Sans retour possible depuis le
- // telephone - il n'y a pas de corbeille ici - donc ni en tete d'ecran ni a
- // portee d'un appui distrait. Le premier appui ne fait qu'armer ; c'est la
- // confirmation, faute de pouvoir en demander une au moteur (une invite
- // JavaScript exige un WebChromeClient que FMX ne fournit pas).
- html += '
'
- + '
';
document.getElementById('fields').innerHTML = html;
+ closeMenu();
document.body.classList.add('on-detail');
view = 'detail';
send('state?view=detail');
@@ -703,24 +714,6 @@ document.getElementById('fields').addEventListener('click', function (ev) {
// l'intention. Tout ce qui suit lit et ecrit sur le bouton.
var t = ev.target.closest('button') || ev.target;
var k = t.getAttribute('data-show');
- if (t.id === 'del') {
- if (t.getAttribute('data-armed') === null) {
- t.setAttribute('data-armed', '1');
- t.textContent = 'Really delete - tap again';
- // Desarme tout seul : un bouton reste arme indefiniment se fait toucher
- // par megarde bien plus tard, et la suppression est sans retour.
- setTimeout(function () {
- if (!t.parentNode) return;
- t.removeAttribute('data-armed');
- t.textContent = 'Delete entry';
- }, 4000);
- return;
- }
- t.disabled = true;
- t.textContent = 'Deleting...';
- send('delentry');
- return;
- }
if (k !== null) {
var cell = document.querySelector('[data-v="' + k + '"]');
var hidden = cell.classList.contains('mask');
@@ -1041,7 +1034,61 @@ document.getElementById('fields').addEventListener('click', function (ev) {
});
document.getElementById('new').addEventListener('click', openNewEntry);
+// LE MENU DE L'ENTREE, et la suppression qui y vit.
+//
+// Elle etait un bouton pleine largeur au bas de chaque detail : rouge, present
+// en permanence, pour une action qu'on fait trois fois par an. Ici il faut
+// l'ouvrir, puis confirmer - deux gestes deliberes, et rien qui traine sous
+// les yeux le reste du temps.
+//
+// SANS RETOUR, toujours : il n'y a pas de corbeille sur le telephone, et la
+// pierre tombale fait disparaitre l'entree du poste aussi, sans passer par la
+// sienne. Le seul filet est la sauvegarde du poste.
+function closeMenu() {
+ var m = document.getElementById('menu');
+ m.hidden = true;
+ m.innerHTML = '';
+}
+
+function openMenu() {
+ var m = document.getElementById('menu');
+ m.innerHTML = '';
+ m.hidden = false;
+}
+
+document.getElementById('kebab').innerHTML = ico('dots', 'Entry menu');
+document.getElementById('kebab').addEventListener('click', function (ev) {
+ ev.stopPropagation();
+ if (document.getElementById('menu').hidden) openMenu(); else closeMenu();
+});
+
+document.getElementById('menu').addEventListener('click', function (ev) {
+ var b = ev.target.closest('button');
+ if (!b) return;
+ if (b.getAttribute('data-del') !== null) {
+ // Le premier appui n'arme que la confirmation. Ouvrir le menu etait deja
+ // delibere ; ceci ferme la porte a l'appui qui derape dans la liste.
+ b.removeAttribute('data-del');
+ b.setAttribute('data-delconfirm', '1');
+ b.textContent = 'Confirm delete';
+ return;
+ }
+ if (b.getAttribute('data-delconfirm') === null) return;
+ b.disabled = true;
+ b.textContent = 'Deleting...';
+ send('delentry');
+});
+
+// Un appui n'importe ou ailleurs referme le menu - y compris sur le contenu,
+// sinon il resterait ouvert pendant qu'on lit l'entree.
+document.addEventListener('click', function (ev) {
+ if (document.getElementById('menu').hidden) return;
+ if (ev.target.closest('#menu, #kebab')) return;
+ closeMenu();
+});
+
function goList() {
+ closeMenu();
document.body.classList.remove('on-detail');
document.body.classList.add('on-list');
view = 'list';