Contact email: newest choice wins by rev, removal wipes the local log

Review follow-ups:
- Each contact_consent event carries a rev that goes up with every change,
  sends are serialized, and `contacts` picks every field from the highest
  rev per copy, so a withdrawal can't lose to an earlier event sent in the
  same second or with a skewed clock.
- Removing the address also replaces it with <removed> in the local
  sent log (earlier contact events and problem reports).
- The prompt is rechecked when the Frame connects, not only at page load.
- No thanks hides the bar only once the dismissal is saved.
- docs/privacy.md: say that the analytics switches don't block a report or
  contact change the person sends deliberately.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
saphidandClaude Opus 5.5 committed 2026-09-30 10:22:45 +10:00
1 parent 19a0d0af18
commit b8ed53f2ff
5 files changed
+129 -37

No files matched your search

+14 -10
View File
@@ -4010,16 +4010,20 @@ async function saveContact(change) {
renderContact(s);
return s;
}
// One time only, never on top of the privacy notice, and only once the Frame has connected.
async function loadContact() {
await telemetryLoaded;
try { renderContact(await api("/api/contact")); } catch { return; }
checkContactPrompt();
}
// One time only, never on top of the privacy notice, and only once the Frame has connected:
// checked at load and again whenever the Frame connects.
async function checkContactPrompt() {
if (!$("contactNotice").hidden || !$("privacyNotice").hidden) return;
let s;
try { s = await api("/api/contact"); } catch { return; }
renderContact(s);
if (s.showPrompt && $("privacyNotice").hidden) {
$("contactNotice").hidden = false;
api("/api/contact/prompt", { prompt: "shown" }).catch(() => {});
}
if (!s.showPrompt) return;
$("contactNotice").hidden = false;
api("/api/contact/prompt", { prompt: "shown" }).catch(() => {});
}
$("contactNotice").onsubmit = async e => {
e.preventDefault();
@@ -4032,9 +4036,9 @@ $("contactNotice").onsubmit = async e => {
} catch (err) { $("cpMsg").textContent = `Couldn't save: ${err.message}`; }
finally { $("cpSave").disabled = false; }
};
$("cpNo").onclick = () => {
$("contactNotice").hidden = true;
api("/api/contact/prompt", { prompt: "dismissed" }).catch(() => {});
$("cpNo").onclick = async () => {
try { await api("/api/contact/prompt", { prompt: "dismissed" }); $("contactNotice").hidden = true; }
catch (err) { $("cpMsg").textContent = `Couldn't save that: ${err.message}. Try again.`; }
};
$("contactForm").onsubmit = async e => {
e.preventDefault();
@@ -4418,7 +4422,7 @@ function onConnection(s) {
$("offline").hidden = true;
const reload = link.reload;
link.reload = false;
refresh().then(() => { if (reload) reloadAll(); });
refresh().then(() => { if (reload) reloadAll(); checkContactPrompt(); });
} else if (s.phase === "failed" && s.error) {
setOnline(false, s.error.message);
} else if (s.phase === "connecting" && prev && prev.phase === "connected") {