Chat links and link previews (how a reply's links are recognised and shown)
A link or email address written in a reply is turned into the right affordance automatically: a web address becomes a clickable link with a Copy URL button, an email address becomes one copyable unit that is deliberately not a link, and a tweet link becomes a picture card. Hovering an external link shows that page's real title — fetched once and remembered — and a file path or other in-app link is left with no preview.
What it is
When an AI writes a link into a chat reply — a web address, an email address, a tweet, or a file path — Omniscio recognises it and gives it the right shape instead of leaving it as dead text. This page is the single overview of that behaviour: what becomes a link, what becomes a card, where the preview comes from, and how to tell a real web link from an email address at a glance.
Text an AI writes as markdown ([label](https://…)) is already a link and renders
as one. Plain, non-markdown text — operator and system message bubbles, and the
structured question widget — is scanned for bare web addresses and email addresses,
and each match gets its own affordance. What a recognised link then gets depends on
what it points at:
- A normal external web link (
http/https): a clickable link, plus a web-title preview on hover (desktop only). - A tweet / X status link: a tweet screenshot card below the message, on every surface.
- A plain URL inside Team Chat: a full Open Graph preview card.
- An in-app link (a file path, an attachment, an
omniscio://deep link, a saved prompt): no web preview at all — it keeps its own click behaviour.
Where to find it
Everywhere Omniscio renders a message: the session transcript (agent replies and your own messages), the structured question widget, Team Chat, and the inbox alert / drip body viewers. Nothing to turn on — it is automatic the moment the text appears. The hover title preview is desktop-only: a phone or tablet has no hover, so it never appears there and makes no web request.
How it behaves
What becomes a link, and what stays plain
- A web address — either
http:///https://…or a bare domain with a known ending such asexample.com— becomes a clickable, underlined link that opens in your browser, with a small Copy URL button. - An email address (
name@host.com) becomes the whole address in accent colour with a single Copy email address button — and is deliberately not a link. Omniscio's window-open guard only allowshttp/https, so amailto:link would be a dead click; copying the address is the reliable action instead. - The email is matched before the address's own domain, so
owner@mailhost.devis never mistaken for the websitemailhost.dev. That ordering is the whole point: it is what stops an address turning into a wrong website link, a stray copy icon mid-sentence, and a broken mid-word wrap.
Telling a link from an address at a glance
A real web link is underlined, opens your browser, and has a Copy URL button. An email address shows the full address in accent text with a Copy email address button, and clicking the address itself does nothing — it is not a navigation.
Which links get a preview
- Hover any external web link and a tooltip shows that page's real title — the article headline, the repo name, the video title — instead of the bare URL. The title is fetched the first time your pointer settles on the link, then remembered, so each link is looked up at most once. While it loads, and if the page has no title, the tooltip shows the link's domain. In-app links never show a title.
- A tweet / X status link becomes a screenshot card of the tweet right below the message — wherever the link appears, and no matter who wrote it. Click it to open the tweet on X.
- In Team Chat, an ordinary URL unfurls into a card — title, description, thumbnail image and site name, from the page's Open Graph metadata, laid out compactly (a small thumbnail beside the text) so it stays dense.
Where the preview comes from, and whether it is live or cached
- Previews are built by fetching the linked page once, never by an AI call — so they cost nothing in AI spend.
- The hover title and the Team Chat card are fetched in Main (Omniscio's trusted
process), because a link in chat is untrusted content and the on-screen app may not
reach the internet on its own. Every fetch runs through the shared network-safety
guard: it refuses links that point at your own machine or private network, allows
only
http/https, gives up after a few seconds, and reads only the first part of the page. - Everything is cached — a good result for hours, a failure for a few minutes — so re-hovering is instant and a dead link is not re-fetched on every hover. Nothing is written to disk except the tweet screenshot, kept so a given tweet is never rendered (and never paid for) twice.
- Failures are silent. An unreachable or titleless page shows just the domain; a tweet that cannot be rendered leaves the ordinary link standing — no broken image, no error message.
For agents
- Detection / rendering —
LinkifiedText(src/renderer/src/components/ui/LinkifiedText.tsx) scans plain (non-markdown) text with oneURL_REwhose alternatives are email first, then protocol URL, then a bare allow-listed-TLD domain. A match containing@that is not a protocol URL rendersCopyableEmail; everything else rendersCopyableLink(src/renderer/src/components/ui/agent-markdown-helpers.tsx). Consumers: MessageBubble (operator bubbles), SmsViewer, QuestionWidget.CopyableLinkgates the fetch onisExternalWebLink && supportsHover()and arms a 200 ms dwell (LINK_TITLE_DWELL_MS; keyboard focus arms immediately) intouseLinkTitlePreview. - IPC domain
link-preview— channelssrc/shared/ipc-channels/link-preview.ts(link-preview:fetch-title/:unfurl/:tweet-shot), schemassrc/shared/ipc-schemas/link-preview.ts, handlerssrc/main/ipc/link-preview-handlers.ts. Per-window rate limits: fetch-title 60 / 10 s, unfurl and tweet-shot 30 / 10 s; a throttled call degrades to a null / empty result, never an error. - Main services —
src/main/services/link-preview/link-title-fetcher.ts(title:og:title→<title>, 8 s timeout, 100 KB capped read, 6 h positive / 5 min negative TTL, 500-entry cache) andlink-unfurl.ts(full Open Graph metadata, 512 KB cap, 500-char fields,og:imageonly when it is a non-privatehttpsURL). Both fetch throughredirectAwareFetch(the SSRF-safe guard). - Renderer hooks —
useLinkTitlePreview.tsanduseChatLinkUnfurl.ts(30 min TTL, cap 500, in-flight dedup, max 3 URLs per message viasrc/shared/team-chat/extract-urls.ts). - Cards —
LinkPreviewCard.tsx(hero / compact layouts) andTweetShots.tsxbacked bysrc/main/services/tweet-shot/tweet-shot-cache.ts. - Contracts —
linkified-text-contract.mdandlink-title-preview-contract.mdare both anchored tocat-chat-link-rendering; the Team Chat unfurl is the subject ofteam-chat-link-unfurl-contract.md.
Related
- link-hover-title.md — the hover web-title preview in full.
- tweet-cards.md — the tweet screenshot card on every surface.
- media-link-open.md — clicking the file / media links an agent wrote (opens in your OS default app).
- team-chat-part-2.md — the rich link-unfurl cards inside Team Chat.
Last verified 2026-10-07