Skip to content

feat: message deletion support (delete for everyone + delete for me) - #519

Merged
boxdot merged 1 commit into
boxdot:mainfrom
cwedgwood:cw/message-deletion
Apr 18, 2026
Merged

feat: message deletion support (delete for everyone + delete for me)#519
boxdot merged 1 commit into
boxdot:mainfrom
cwedgwood:cw/message-deletion

Conversation

@cwedgwood

@cwedgwood cwedgwood commented Apr 2, 2026

Copy link
Copy Markdown
Contributor

Implements full message deletion via the Signal protocol, with parity
to the official Android/iOS clients.

Closes #458

Delete for everyone (DataMessage.delete)

  • Handle incoming remote deletes from other users
  • Handle sync deletes from our own devices (uses message_channel()
    to resolve the channel in a single code path for all channel types)
  • Add send_delete() to SignalManager trait for outgoing deletes
  • Add DeleteMessage command with ctrl+d keybinding in message_selected mode
  • Deleted messages render as [message deleted] in italic gray

Delete for me (SyncMessage.deleteForMe)

  • Handle incoming deleteForMe sync messages (full message removal)
  • Add send_delete_for_me() to SignalManager for outgoing deleteForMe
  • Note to Self deletion uses deleteForMe (full removal, no tombstone)
  • Second ctrl+d on a tombstone sends deleteForMe to clean it up,
    matching Android behavior where tombstones can be dismissed

Storage changes

  • Add deleted field to Message struct and migration 004
  • Add delete_message() (tombstone) and remove_message() (full removal)
    to Storage trait with implementations for Sqlite, MemCache, Forgetful

UI changes

  • Track message removal in the view index to prevent off-by-one selection
    when multiple messages are deleted in sequence
  • Document delete_message command in README

Tested manually: receiving deletions in group chats, direct messages,
and Note to Self; sending deletions from gurk; delete-for-me on
tombstones; verified other users' messages cannot be deleted.

Implements full message deletion via the Signal protocol.

## Delete for everyone (DataMessage.delete)
- Handle incoming remote deletes from other users
- Handle sync deletes from our own devices (uses message_channel()
  to resolve the channel in a single code path for all channel types)
- Add send_delete() to SignalManager trait for outgoing deletes
- Add DeleteMessage command with ctrl+d keybinding in message_selected mode
- Deleted messages render as '[message deleted]' in italic gray

## Delete for me (SyncMessage.deleteForMe)
- Handle incoming deleteForMe sync messages (full message removal)
- Add send_delete_for_me() to SignalManager for outgoing deleteForMe
- Note to Self and tombstone deletion use deleteForMe (full removal)
- Regular channels use delete-for-everyone (tombstone)
- Second ctrl+d on a tombstone sends deleteForMe to clean it up

## Storage changes
- Add 'deleted' field to Message struct and migration 004
- Add delete_message() (tombstone) and remove_message() (full removal)
  to Storage trait with implementations for Sqlite, MemCache, Forgetful
- Update sqlx offline query cache

## UI changes
- Track message removal in the view index to prevent off-by-one selection
- Document delete_message command in README

Closes boxdot#458
@cwedgwood
cwedgwood force-pushed the cw/message-deletion branch from 4274515 to 17c1405 Compare April 3, 2026 05:31
@cwedgwood cwedgwood changed the title feat: message deletion (delete for everyone) feat: message deletion support (delete for everyone + delete for me) Apr 3, 2026
@freswa

freswa commented Apr 6, 2026

Copy link
Copy Markdown

Tested. Lgtm

@boxdot boxdot left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

Comment thread src/storage/mod.rs
@boxdot
boxdot merged commit 7246fcd into boxdot:main Apr 18, 2026
9 checks passed
boxdot pushed a commit that referenced this pull request Apr 26, 2026
Implements Signal's disappearing messages (expiring message timers).

Depends on #519 (message deletion) — uses `remove_message()` from that
PR.
Closes #79

## Outgoing messages
- Read `expire_timer` from Channel, set on outgoing `DataMessage`
- Messages sent from gurk now carry the correct timer value

## Incoming messages
- Extract `expire_timer` from `DataMessage` (both direct and sync-sent)
- Update per-channel timer (stored in DB, persists across restarts)
- Handle timer disable (`expire_timer: None` clears channel timer)
- Per-message `expire_timer` persisted for correct activation

## Timer activation (start-on-view)
- Messages show `[pending]` until channel is viewed for 10+ seconds
- After dwell, `expires_at` is set and countdown begins
- New messages arriving in an already-viewed channel activate eagerly
- Activation runs once per channel selection (no repeated scanning)
- Outgoing messages activate immediately (sender starts on send)

## Expiration
- Sweep every 5 seconds removes messages past `expires_at`
- Only scans channels with `expire_timer` enabled
- Partial index on `expires_at` for efficient queries

## UI
- Channel title shows timer setting (e.g. `[1m]`, `[1w]`)
- Per-message countdown (`[pending]`, `[45s]`, `[expired]`)
- `format_duration_short()` helper for consistent formatting

## Schema (migration 005)
- `channels`: add `expire_timer` column
- `messages`: add `expire_timer` and `expires_at` columns
- Partial index on `messages.expires_at`

## Known limitations
- Timer starts on receive for viewed channels, not on first read of
  each individual message (matches Signal Desktop behavior)
- Attachment files on disk are not cleaned up when messages expire
  (tracked separately)
- Works around whisperfish/presage#392 (group timer clobbered during
  upsert) by storing the timer independently on the channel

Tested: incoming/outgoing timers, timer disable, persistence across
restarts, start-on-view with 10s dwell, countdown UI, expiration sweep.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Delete message

3 participants