Skip to content

feat: image message TTL and inline display#944

Open
chaehyun2 wants to merge 4 commits intoslopus:mainfrom
chaehyun2:feat/image-ttl
Open

feat: image message TTL and inline display#944
chaehyun2 wants to merge 4 commits intoslopus:mainfrom
chaehyun2:feat/image-ttl

Conversation

@chaehyun2
Copy link
Copy Markdown

Summary

  • Image TTL: Images are sent as separate encrypted messages with expiresIn (outside encryption). Server stores expiresAt and auto-deletes expired messages every 5 minutes. Default TTL is 3 days.
  • Server: expiresAt nullable field on SessionMessage, index for efficient cleanup, v3 POST accepts expiresIn per message
  • Web: sendMessage() splits images into separate ephemeral messages with groupId (inside encryption) linking to text message
  • CLI: routeIncomingMessage() buffers image messages by groupId, attaches to matching text message
  • Inline display: Image thumbnails (120x120) shown in user message bubbles, click to expand full-size with dark overlay

How it works

  1. Web sends image as separate message: {role: 'user', content: {type: 'images', groupId, images}} with expiresIn: 259200 (3 days)
  2. Text message includes imageGroupId inside encrypted payload
  3. Server sees expiresIn → sets expiresAt → cleanup job deletes after TTL
  4. CLI buffers image message by groupId → when text arrives, merges images → sends to Claude
  5. Server can't read content (E2E encrypted) but can act on TTL metadata

Test plan

  • Send image from web → CLI receives and passes to Claude
  • Image thumbnails display in chat bubbles
  • Click thumbnail → full-size overlay
  • Server auto-deletes image messages after TTL
  • Mobile app ignores unknown message types gracefully
  • Text-only messages work as before

🤖 Generated with Claude Code

Chaehyun Lee and others added 4 commits March 31, 2026 02:05
Images are now sent as separate encrypted messages with a TTL marker
(expiresIn) outside the encryption layer. Server stores expiresAt and
a cleanup job deletes expired messages every 5 minutes.

Server: expiresAt field on SessionMessage, TTL cleanup in main.ts,
  v3 POST accepts expiresIn per message
Web: sendMessage splits images into separate ephemeral message with
  groupId (inside encryption) and expiresIn: 300 (outside encryption)
CLI: routeIncomingMessage buffers image messages by groupId, attaches
  to matching text message before passing to UserMessageSchema

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
Show image thumbnails (120x120) in user message bubbles.
Click thumbnail to expand full-size with dark overlay.
Images stored in ephemeral messageImageStore keyed by localId.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
Reducer assigns new internal IDs, losing the original localId needed
to look up images in messageImageStore. Pass realID as localId in
user messages, and add id-based fallback lookup in MessageView.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
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.

1 participant