UI & Customization

Custom Emojis & Metadata Budgets

Discord-like syntax formatting, WebP serialization, tombstone deletions, and pad-relative limits.

Overview

Wiltkey supports user-generated custom emojis, invoked in chats using the :name: format. Emojis are encoded as base64-encoded WebP bytes and synced to peers over standard message lanes. Because keystream bytes cannot be reused or reclaimed, custom emojis are restricted by a soft metadata budget, and deleting an emoji results in a cryptographic tombstone.

How it Works

  1. Inline Syncing: Custom emojis are not sent via a side-channel. Defining an emoji writes an emoji_def message to our outgoing lane. Deleting an emoji writes an emoji_delete tombstone message. These consume pad bytes and propagate via standard resync sweeps. Upon receipt, they are parsed and merged into the local CustomEmojiStore rather than rendered as chat bubbles.
  2. Pad-Relative Metadata Budget: To prevent custom emojis from exhausting a device's storage, the client computes a soft metadata budget proportional to the total pad size:
    budgetBytes = totalPadBytes * 0.02 (Capped at 1MB)
    If a pad is too small to yield at least 50KB (customEmojiThreshold), custom emojis are disabled.
  3. Tombstone Deletion ("Junk Data"): When deleting an emoji, the image bytes are cleared in memory, but a dead slot marked deleted = true remains. The size of the deleted image is stashed in reservedBytes:
    approxBytes = deleted ? reservedBytes : imageB64.length
    This stashed size continues to count against the metadata budget. The tombstone's creation timestamp is bumped to the deletion time, ensuring it wins the union-merge and propagates to the peer.
  4. Union-Merge & Trimming: During sync, mergeAll merges incoming emoji lists. If the merged list exceeds the metadata budget, the oldest emojis (sorted by createdAtMs) are permanently trimmed until the list fits the budget.

Key Files & Symbols

File Path Symbol Name Description
lib/core/custom_emoji.dart CustomEmoji Represents an emoji name, WebP string, timestamp, and tombstone state.
lib/core/custom_emoji.dart CustomEmojiStore Handles local storage (SharedPreferences) and union-merges.
lib/core/chat_metadata.dart ChatMetaStore.budgetFor() Computes the pad-relative metadata budget: 2% of the pad, capped at 1MB.
lib/core/state_emoji.dart defineEmoji() / deleteChatEmoji() Writes emoji creation/tombstone messages into the outgoing lane.

Gotchas & Edge Cases

🛑 TOMBSTONES DO NOT RECLAIM SPACE
Because the keystream bytes utilized to transmit the WebP image cannot be reused, deleting a custom emoji does not reclaim metadata budget bytes. It freezes the cost in place as a tombstone. This prevents a malicious peer from continuously adding and deleting emojis to exhaust the receiver's budget.