Skip to content

feat(chat): teams message type coverage, attachment mentions, in-app message links (t181-t184) - #49

Merged
duongdev merged 4 commits into
mainfrom
feat/t181-teams-message-type-coverage
Aug 8, 2026
Merged

feat(chat): teams message type coverage, attachment mentions, in-app message links (t181-t184)#49
duongdev merged 4 commits into
mainfrom
feat/t181-teams-message-type-coverage

Conversation

@duongdev

@duongdev duongdev commented Aug 7, 2026

Copy link
Copy Markdown
Owner

What

Four tasks that together close the gap between what Teams web displays and what CDP Chats displays, plus the two bugs found while testing it.

A live probe of 1963 raw Teams messages across 20 conversations measured what the renderer was actually dropping, rather than guessing from the reported samples. Every payload was replayed through the real core/teams-render.js and diffed raw-vs-rendered, which is how the largest gap (206 messages) surfaced — nobody had reported it.

Task Change
t181 Render five message classes that used to arrive empty or lossy
t182 Mentions and reply quotes now survive an attachment send
t183 Teams message links resolve in-app instead of bouncing to the Teams web client
t184 A jump into a media-heavy thread lands on target and stays there

t181 — message-type coverage

Class Before After Corpus
Image upload filename chip inline picture + lightbox 11
Loop / Fluid embed nothing at all chip with a working link 13
Swift adaptive card a chip reading "Card" the card's real title + text 206
Video-only body blank bubble the video 3
Forwarded block indistinguishable from a reply labelled "Forwarded" 5

Two payload facts worth recording, both non-obvious:

  • An image upload arrives in properties.files, not as an inline <img> — a completely different code path from a pasted image, which is why it degraded to a chip. Its preview is AMS-hosted, so the existing isValidAmsUrl proxy already covers it; the SharePoint shareUrl on the same object is not proxyable and stays a link-out.
  • A Loop embed's body span is empty — the payload lives entirely in properties.cards[].content.componentUrl. That URL is third-party, so it is scheme-guarded to http(s) before it reaches an href (see Security below).

t182 — mentions and quotes through attachments

Attaching a file silently dropped every mention and reply quote on the message. The composer collected them, then the frontend explicitly zeroed them out — because the upload signature had nowhere to put them (text?: string and nothing else).

The fix threads an UploadOpts ({text, html, quotes, mentions}) through the full stack and gives the Teams properties payload a single owner: the new pure core/teams-send-props.js, shared by all four senders. Previously only teamsReply built that blob, which is the structural reason the three upload paths couldn't carry mentions.

Two details that are load-bearing:

  • The caption rides as HTML emitted verbatim, not escaped text. An escaped caption turns a mention span back into visible markup.
  • In a multi-file chain the caption follows the first upload that actually lands, not the first attempted (carriesCaption in chat/src/lib/send-chain.ts). Keying on the loop index meant a failing first upload took the caption, mentions and quotes down with it — the same class of silent drop this task exists to remove. Found while writing the task's own tests.

t183 — message links resolve in-app

A Teams /l/message/{convId}/{msgId} permalink, or one of our own /chat/c/{convId}?msg= links, now jumps inside the app. Only the real Teams hosts and our own origin qualify — a foreign origin's /chat/c/ is somebody else's app. Modifier clicks pass through untouched, so ⌘-click still opens a tab.

In-app links also look different (dotted underline + a marker). A link that behaves differently from every other link has to signal that; the marker is a CSS ::after so it never lands in copied text or a screen reader.

t184 — jump landing holds through media load

Jumping into a thread full of images landed correctly and then drifted, because media above the target has no reserved box and grows when it decodes. The landing now re-seats on every in-pane media load for a 3s settle window. jumpToUnread already had this treatment; the message jump did not.

Security

The mutation check earned its keep: componentUrl comes from a third-party Fluid card and flowed straight into a chip href with no scheme guard. A javascript: payload would have ridden through. Guard added, with a test covering javascript:, data: and file:.

chat/src/lib/sanitize-message.ts is unchanged — every tag and attribute involved was already allowlisted, so no new XSS surface.

Verification

  • pnpm test2538 passed (186 files); typecheck, build and Biome clean
  • Mutation checks: 10/10 and 19/19 killed. Each fix is reverted in turn and the suite must go red — proof the tests actually test the behavior
  • Corpus regression (scripts/t181-corpus-regression.mjs): the 1963-message probe corpus replayed through the renderer, all five gap classes report 0
  • Verified in the mock stack by DOM and computed-style queries, not screenshots — the vision pass reported false negatives twice, so the numbers are read directly (image naturalWidth, lightbox role="dialog", defaultPrevented per click type, scroll offsets before/after settle)
  • Each of the four commits was checked out and independently verified green, so git bisect stays meaningful

Known ceiling

store.ts persists the rendered message body, so a renderer fix only reaches messages that get re-fetched — the newest ~30 of a thread. Older DB-served pages keep the old body until they're re-swept. A backfill pass is logged in docs/memories/ideas.md alongside four other deliberate deferrals (link unfurls, call transcripts, <cite> styling, mentions in edits), each with the reason it was skipped.

@dokploy-2026-03-01-6ei3s7

dokploy-2026-03-01-6ei3s7 Bot commented Aug 7, 2026

Copy link
Copy Markdown

Dokploy Preview Deployment

Name Status Preview Updated (UTC)
cdp-browser-app ✅ Done Preview URL 2026-08-07T12:44:27.994Z

@duongdev
duongdev merged commit 511a466 into main Aug 8, 2026
2 checks passed
@duongdev
duongdev deleted the feat/t181-teams-message-type-coverage branch August 8, 2026 07:20
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