fix: render emojis and HTML entities in MCP-created screens - #38
Merged
Conversation
MCP-created screens rendered HTML through Satori, which ships no emoji font and has no built-in entity decoder. Agent-authored HTML that used emojis or numeric entities (e.g. ● for status-bar dots) rendered as tofu boxes or literal text. Two fixes: 1. Wire Satori's `loadAdditionalAsset` callback to resolve emoji graphemes to Twemoji SVGs fetched from jsDelivr (version-pinned to jdecked/twemoji@15.1.0). New `emoji-loader.js` adds a three-layer cache (in-flight promise dedupe, in-memory resolved Map, on-disk ~/.cache/drawd-mcp/emoji/<code>.svg), a 3 s AbortSignal timeout, and a transparent-SVG fallback so renders never hang or crash. Handles keycaps, ZWJ families, regional flags, skin-tone modifiers, and the canonical/fallback filename split (rainbow flag keeps FE0F while eye-in-speech drops it — retry covers both). 2. Preprocess HTML with `decodeSafeEntities` before satori-html parses it: decode all numeric entities (&#ddd; / &#xhhh;) and a whitelist of safe named entities whose decoded form contains no HTML metachar ( , …, •, “/”, arrows, etc.). Leaves &/</>/"/' untouched to avoid breaking the HTML parser. Bumps mcp-server to v1.2.2.
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
Fixes two rendering bugs that made MCP-created screens look broken whenever an agent included emojis or HTML entities in the source HTML.
Problem 1 — Emojis render as tofu boxes
Satori ships with no emoji glyphs. It only renders characters present in the fonts passed to it, and
satori-renderer.jsloads only Inter Regular/Bold. Any emoji an agent wrote (screen titles, status icons, heart/check indicators) came out as missing-glyph boxes in the resulting PNG.Problem 2 — HTML numeric/named entities render as literal text
satori-htmldoesn't decode HTML entities. Agents commonly write●for a black circle,“/”for curly quotes,…for ellipsis — all were landing in rendered PNGs as literal●text.Changes
1. Twemoji integration via Satori's
loadAdditionalAssetcallbackNew
mcp-server/src/renderer/emoji-loader.js:https://cdn.jsdelivr.net/gh/jdecked/twemoji@15.1.0/assets/svg/<codepoint>.svg(version-pinned to the maintained fork, never@latest).Map→ on-disk at~/.cache/drawd-mcp/emoji/<code>.svg. A batch render with 50 identical checkmarks triggers exactly one fetch; subsequent process restarts hit the disk cache.AbortSignal.timeouton fetch; transparent 1×1 SVG fallback on error so renders never hang or crash.1️⃣), ZWJ families (👨👩👧👦), regional flags (🇯🇵), skin-tone modifiers (👋🏻). The canonical TwemojitoCodePointrule ("keep FE0F iff ZWJ present") doesn't match jdecked's asset filenames consistently — rainbow flag keeps FE0F but eye-in-speech drops it — sogetEmojiCodereturns[primary, fallback]andloadEmojiSvgretries with FE0F fully stripped on 404.2. HTML entity pre-decoding
New
decodeSafeEntities()helper insatori-renderer.js, called beforesatori-htmlparses the markup:&#ddd;/&#xhhh;) — always safe, output is a single non-meta character. ,…,—,•,©,“/”,←/→,✓/✗, etc.&,<,>,",'untouched — decoding those before HTML parsing would break the parser.3. Version bump
mcp-server→v1.2.2.Test plan
getEmojiCodecodepoint encodings match real Twemoji filenames (heart, keycap, flag JP, skin-tone, ZWJ family, man technologist, rainbow flag, eye-in-speech).npm run buildsucceeds;dist/index.js152 KB → 154 KB (+2 KB for both fixes combined)..drawdfile re-rendered through the patched pipeline — status bar shows●●●●instead of literal●,"xyzzy"with real curly quotes instead of“xyzzy”, full-color Twemoji glyphs throughout.Out of scope
&/</>/"/'— would require post-parse VDOM walk; current whitelist covers the typography symbols agents actually use.