refactor(chat,#1158): DRY adapter base default renderMessageElement#1189
Merged
Conversation
added 2 commits
May 14, 2026 11:45
…MessageAdapter Three adapters (TextMessageAdapter, URLCardAdapter, ToolOutputAdapter) had byte-identical override bodies of the form: parseContent, createAdapterWrapper, renderContent, template.innerHTML, wrapper.appendChild(fragment). That is now the default body of AbstractMessageAdapter.renderMessageElement. The overrides are deleted; the live message-content slot still never sees innerHTML (the parse happens on a detached template), and Lit-managed reactive children inside the message bubble keep their state. ImageMessageAdapter retains its custom override -- it builds img nodes via property assignment to keep src and alt out of any HTML-parse path and does not go through renderContent to string. Net minus 61 lines. Closes #1158.
10 tasks
…enderelement-default # Conflicts: # src/eslint-baseline.txt # src/widgets/chat/adapters/URLCardAdapter.ts
Linux CI ratchet failed because eslint-baseline.linux.txt was still at 5461 while the macOS baseline (and current count on both platforms) is 5459. The ratchet requires CURRENT == BASELINE strictly, so the -2 improvement from #1189 needed to land in BOTH platform files. Sibling: 8b51729 (chore(eslint-baseline): ratchet -2) updated eslint-baseline.txt; this commit completes the platform symmetry. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…enderelement-default # Conflicts: # src/eslint-baseline.linux.txt # src/eslint-baseline.txt
…merge After merging origin/canary into the branch, baselines (mac=5455, linux=5456) need to drop by the #1189 deletion delta (-2) to mac=5453, linux=5454. macOS verified locally by precommit: "Current: 5453 errors". Linux value is +1 vs Mac per established platform skew; CI will surface the exact number if it's off. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Lifts the byte-identical
renderMessageElementbody from three subclasses(
TextMessageAdapter,URLCardAdapter,ToolOutputAdapter) intoAbstractMessageAdapteras the new default. Subclass overrides are deleted;ImageMessageAdapterretains its custom override (it builds<img>nodesvia property assignment to keep
src/altout of any HTML-parse path anddoes not go through
renderContentto string).Net -61 LOC.
Pattern
Three subclass overrides did the same thing:
That is now the base default body. The detached-template path (so the live
message-content slot never sees
innerHTMLdirectly) is preservedidentically -- Lit-managed reactive children still survive sibling updates.
ImageMessageAdapteris the natural-different outlier that proves theabstraction is right: it doesn't fit the string-render pattern, so it stays
as a custom override. Three things compress to a primitive; the fourth
proves the primitive is real.
What's in the PR
AbstractMessageAdapter.renderMessageElement-- replacedreturn nulldefault with the full DRY body. Class-name in error log resolves via
this.constructor?.nameso each subclass logs under its own name.TextMessageAdapter-- override deleted; one-line comment points atbase default.
URLCardAdapter-- override deleted; one-line comment points at theXSS-hardening follow-up (
#1159).ToolOutputAdapter-- override deleted; one-line comment notes thatescapeHtmlatrenderContentinterpolation sites still applies.eslint-baseline.txt-- ratcheted -2 (deleted code carried 2 baselineerrors).
Why ImageMessageAdapter stays custom
ImageMessageAdapter.renderMessageElementbuilds the wrapper's childrenvia DOM APIs (
document.createElement('img')+img.src = url/img.alt = ...)so the
src/altvalues never pass through any HTML parse step. That'sintentional defense-in-depth for the media path; the base default's
template.innerHTML = contentHtmlwould re-parse the image markup, whichis the exact thing this adapter was written to avoid. Keep it custom.
Tests
npm run build:ts-- clean.renderMessageElementshape directly; theadapter unit suite is a sub-card (see precommit gate too thin: only browser-ping runs; no chat-roundtrip / adapter unit / persona-reply smoke #1186).
Followups
renderContentinterpolation sites for
originalText,title,description,siteName). Now visibly called out in the URLCardAdapter comment.fire when
widgets/chat/adapters/changes.Closes #1158.