Skip to content

feat: disappearing messages support - #522

Merged
boxdot merged 3 commits into
boxdot:mainfrom
cwedgwood:cw/disappearing-messages
Apr 26, 2026
Merged

feat: disappearing messages support#522
boxdot merged 3 commits into
boxdot:mainfrom
cwedgwood:cw/disappearing-messages

Conversation

@cwedgwood

Copy link
Copy Markdown
Contributor

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

Tested: incoming/outgoing timers, timer disable, persistence across
restarts, start-on-view with 10s dwell, countdown UI, expiration sweep.

@freswa

freswa commented Apr 5, 2026

Copy link
Copy Markdown

Thank you for taking the time to implement this feature!

The timer is sent on outgoing messages, so it's not reset every time a message is written in gurk. That's great! But the messages are not yet deleted in gurk itself. I set the timer to 30s in the "Note to self" chat. While the desktop client deleted the message just fine, gurk still has it saved even after a restart.

Edit: The general "delete message from view" logic seems to be fine. If I delete a message manually in the desktop client, it is deleted in gurk as well.

@freswa

freswa commented Apr 5, 2026

Copy link
Copy Markdown

I tested the feature with "Note to Self" chat where it currently doesn't expire anything. I can confirm this works with regular chats though. Thank you!

@cwedgwood

Copy link
Copy Markdown
Contributor Author

I tested the feature with "Note to Self" chat where it currently doesn't expire anything. I can confirm this works with regular chats though. Thank you!

Both should work now. There are multiple parallel paths we could refactor to avoid some of this.

I made a note to consider this later once we see what if anything can be merged here.

@freswa

freswa commented Apr 5, 2026

Copy link
Copy Markdown

Works like a charm. Lgtm!

Implements Signal's disappearing messages (expiring message timers).

## Outgoing messages
- Read expire_timer from Channel, set on outgoing DataMessage
- Fixes messages sent from gurk not carrying the timer (boxdot#79)

## 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
- channels: add expire_timer column
- messages: add expire_timer and expires_at columns
- Partial index on messages.expires_at

Closes boxdot#79
The Private note message match arm did not extract expire_timer from
the DataMessage (swallowed by ..). Messages sent to Note to Self
never had their timer set, so they never expired.

Extract expire_timer, update the channel timer, and set it on the
Message struct — same pattern as the sync-sent and incoming DataMessage
arms.
The countdown label was only rendered in the else branch (non-deleted
messages). Move add_expire_countdown() outside the if/else so tombstones
show [message deleted] [30s] or [message deleted] [expired].
@cwedgwood
cwedgwood force-pushed the cw/disappearing-messages branch from fa27670 to 679a8da Compare April 25, 2026 03:11
@cwedgwood

Copy link
Copy Markdown
Contributor Author

@boxdot rebased

@boxdot
boxdot merged commit 4f661b7 into boxdot:main Apr 26, 2026
9 checks passed
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.

"Disappearing Messages" feature

3 participants