Skip to content

feat(chat): sender-built link previews - #1071

Merged
bmc08gt merged 11 commits into
mainfrom
feat/link-preview-send
Oct 16, 2026
Merged

bmc08gt merged 11 commits into
mainfrom
feat/link-preview-send

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Builds on flipcash2 0.19.0 (#1065). Design: code-payments/flipcash-client-orchestrator#49, docs/specs/link-preview-send.md.

Link previews move from the recipient to the sender. The composer fetches the draft's link, and the message carries the preview in TextContent.link_previews, plaintext or sealed. Recipients draw it from the message and fetch nothing.

Sender

  • The composer follows the draft's first web link, including a link in a draft restored when a chat opens. It fetches the page with the existing pinned fetcher, then uploads the page image through the chat photo uploader, sealed in an encrypted chat.
  • A card above the field shows the page image, host, description and title as soon as the page arrives, with a spinner over the image until the upload finishes. If the upload fails, the image comes off the card and the preview goes out without one. The card animates in and out with the same spring as the photo chips.
  • The card's × removes the preview for as long as that exact link stays in the draft. Editing the link itself brings a preview back.
  • At send, the preview goes out only if it is ready, its URL is still a whole link in the text, and its title cleans to something visible. A send never waits on a fetch. The checks are LinkPreviewSendChecks (9f06aeb), run against the shared link_preview_send.json vectors.

Wire and store

  • Proto out and in for plaintext and sealed text, replies included. The sealed path reuses the encrypted-media image rules for the preview image.
  • Stored as a new linkPreviewJson column on conversation_message. Database.schemaVersion 51 → 52, which wipes and rebuilds the store. The linkPreview table is dropped in the same bump, since only the receive-side memo used it.

Recipient

  • The web card is drawn only from the message's preview. It sits at the first place the URL appears in the text as a whole link. A preview whose URL isn't there, or whose title is empty, draws nothing. A Flipcash card in the same text still wins.
  • On read, only the contract's own drops apply, not the composer's fetch rules. So an http: link, or a host iOS wouldn't fetch, still draws if the sender's platform previewed it.
  • The image loads through ChatMediaURLResolver, so a sealed preview image decrypts and caches the same way a sealed photo does.

Removed

The receive-side fetch never shipped, so it is gone with no fallback:

  • the web paths in LinkCardFeed, LinkCardResolver and LinkCardMemo
  • the transcript prefetch and the first-paint wait
  • tap-to-load mode and its chip
  • WebImageDiskCache and the linkPreview table
  • the webLinkPreviews beta flag and its settings row

The page fetcher stays because the composer uses it. There was no hardcoded 17408 ciphertext cap to raise.

Decisions the spec leaves open

  • Edit: an edit keeps the message's stored preview while its URL is still a whole link in the new text. Otherwise the preview is dropped. Nothing is refetched during an edit.
  • Retry: a retried send carries the preview stored on the pending message.
  • Photos with a link: a caption can't carry a preview, so when the draft has photos and a ready preview, the photos go out with no caption and the text follows as its own message with the preview. A reply attaches to the first photo. With no ready preview, the text stays the last photo's caption as before.
  • Share extension is out of scope.

@bmc08gt bmc08gt self-assigned this Oct 15, 2026
@bmc08gt
bmc08gt force-pushed the feat/link-preview-send branch from 38e0bcd to d4e1044 Compare October 15, 2026 21:41
@bmc08gt
bmc08gt changed the base branch from chore/sync-contract-ocp-00ea80d0-flipcash2-1897108a to main October 15, 2026 21:41
@bmc08gt
bmc08gt marked this pull request as ready for review October 16, 2026 01:02
…e store

A text message now holds the sender's `MessageLinkPreview` (url, title,
description, image) next to its text. It goes out in
`TextContent.link_previews`, plaintext and sealed, and is read back from
both, dropping a preview whose url is not a whole link in the text or
whose title is empty. A sealed preview's image is checked like encrypted
media; one uploaded for the other encryption mode is left off.

An edit sends no new fetch: it keeps the stored preview while its link
is still whole in the edited text, and the local rewrite does the same.

The store keeps the preview in a new `conversation_message.linkPreviewJson`
column, so `Database.schemaVersion` goes to 52.
… text

The composer fetches the draft's first web link, uploads the page image through the chat photo uploader (sealed for an encrypted chat), and shows a 56pt thumbnail with a remove control beside the field. A text send attaches the preview only if it is ready, its URL is still a whole link in the text, and its title cleans to something; a send never waits on it.

No preview is built while photos are staged, since a caption carries none, or during an edit. The fetcher no longer reads the webLinkPreviews flag.
…ceive-side fetch

A recipient now draws a web card only from the preview its sender attached: the card sits at the first place the preview's url appears in the text as a whole link, and a preview whose url is not there, or whose title is empty, draws nothing. No fetch rule is applied on read, so a link the composer would not fetch still draws if the sender's platform previewed it. The image loads through the chat photo resolver, so a sealed preview image decrypts the same way a sealed photo does. A Flipcash card in the same text still wins.

Removed with no fallback, since the receive-side fetch never shipped: the web paths in LinkCardFeed, LinkCardResolver and LinkCardMemo, the transcript prefetch and the first-paint wait on the memo, the tap-to-load mode and chip, WebImageDiskCache, the linkPreview table, and the webLinkPreviews beta flag with its settings row. The page fetcher stays for the composer.
…mage uploads

The composer chip only drew the page's image once the upload finished, and a
failed upload left the link glyph, so the sender rarely saw what would be sent.
The fetch and the upload are now two steps: the card appears as soon as the page
is fetched, with the image under a spinner while it uploads. The chip widens to
the field and shows the title, description and host next to the image.

A send while the image is still uploading goes with the title and description
only, as before. A failed upload drops the thumbnail so the card matches what
will be sent.
A caption carries no link preview, so staging a photo hid the preview
card and dropped it from the send. The card now stays up with photos
staged, and when it has a preview the text goes out as its own message
after the photos instead of as the last photo's caption. A reply still
goes on the first photo only.
…to it

The link card's remove button floated alone at the card's trailing edge, unlike a staged photo's. It now sits on the image's top-trailing corner through the same button as the photo chip. The image grows to 80pt and the description wraps to two lines to fill the height.
The draft is restored before the bottom bar starts watching it, so a link already in a saved draft never reached the preview.
@bmc08gt
bmc08gt force-pushed the feat/link-preview-send branch from 3a37fc9 to 38dff33 Compare October 16, 2026 01:08
@bmc08gt
bmc08gt merged commit 3ae8af5 into main Oct 16, 2026
2 of 3 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.

1 participant