- from_report applies its change and reads the id and rev together, so a
removal made while that change is sending is newer than the report; the
report's redaction window now starts before the address is saved.
- A report with a different address replaces the saved one with follow-up
questions only: update notices aren't carried over to an address nobody
agreed them for, and the form says so before sending.
- Settings refreshes after every report send, whatever the box shows by then.
- privacy.md: a report with follow-up ticked also saves and sends the address.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Ticking follow-up questions on a report makes that address the contact
email (follow-up ticked, update choice unchanged), so Settings shows it
and Remove my email withdraws it like any other.
- Reports carry contact_rev; the inbox takes a report's follow-up
permission back when a later change from that copy (higher rev) no
longer agrees, whatever the clocks say.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- A report with follow-up ticked carries this copy's contact id, and the
inbox marks its permission withdrawn when a later choice from that copy
no longer agrees to follow-up questions at that address.
- The one-time prompt never appears in a visit that showed the privacy
notice, even if the Frame connects just after it's dismissed.
- Saving contact details isn't headset work: it can't hold up switching
headsets or be refused after a switch.
- Consent flags must be JSON true/false; "false" is no longer consent.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Third review follow-ups:
- A sender that found nothing waiting checks again after letting go of the
send lock, so a change saved in that moment is sent, not left for a retrier.
- A report is compared with a removal using its full-precision start time, so
a report sent after the address was removed is logged as sent.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Second review follow-ups:
- Saving returns once the choice is stored; a send already under way picks up
the newest change, or the background retry is woken.
- A problem report still being sent when its address is removed is logged as
<removed>, checked under the same lock the removal holds.
- The prompt re-checks the privacy notice after fetching its state.
- docs/privacy.md: offline contact changes are sent later by themselves; the
prompt never follows straight after the privacy notice.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
Problem reports arrive with no way to reply. People can now leave an email
address with two separate opt-ins: occasional update notices, and follow-up
questions from the maintainer.
- ui/frame_contact.py keeps the address and choices locally and sends each
change privately to PostHog as a contact_consent event under its own random
contact id; removing the address sends a withdrawal without it. Changes made
offline wait and are retried.
- A one-time, dismissible prompt appears after the Frame first connects; No
thanks and showing it once are both remembered.
- Privacy & updates gains a Contact email section to add, change or remove it.
- The report form's contact field now goes with a report only when "may
contact me with follow-up questions" is ticked (contact_followup).
- frame_report.py contacts [updates|followup] lists who agreed to what,
using the newest event per copy.
- docs/privacy.md says what is collected, why, where and how to remove it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>