Skip to content

Raw HTML in markdown renders as source text on mobile #7929

Description

@flamboh

This issue was written entirely by Fable 5 (running in T3 Code).

What happens

Raw HTML embedded in markdown renders as literal source text on mobile. A README that opens with the common centered-icon header:

<p align="center">
  <img src="public/icon-512.png" alt="" width="128" height="128" />
</p>
<h1 align="center">Project Name</h1>

shows up in the mobile file preview and thread feed as raw tags. No image element is ever created — this applies to external https: sources too, so it is a rendering capability gap, not a URL-resolution problem.

What #7857 already solves (and deliberately does not)

PR #7857 fixes workspace image URL resolution everywhere images already render: markdown-syntax images (![](public/icon.png)) now resolve through signed asset URLs on web and mobile (iOS native renderer and the JS fallback renderer), and web also covers raw HTML <img> because the web pipeline parses HTML. Mobile HTML rendering was explicitly scoped out of that PR as a separate capability.

What to look for

  • The native parser already runs with html: true — HTML arrives as html_block / html_inline nodes carrying raw source text (apps/mobile/modules/t3-markdown-text). Nothing needs to change in the parser or native code.
  • A tree transform (same pattern as nativeMarkdownWithPreservedSoftBreaks) that rewrites <img> tags inside those nodes into synthetic image nodes gets the entire existing image pipeline for free — NativeMarkdownImage, the renderImage override, and the signed asset flow from fix(web/mobile): render project images in file markdown previews and harden markdown images #7857. The JS fallback can hook the same logic through an html_block entry in CustomRenderers.
  • Suggested staging: (1) <img> extraction only (~150–250 lines) — caveat: sibling HTML like the <h1 align="center"> still shows as raw text, so blocks render half-fixed; (2) a safe-subset HTML normalizer (p/div/center/h1h6/a/br/b/i → equivalent markdown nodes) so HTML-headed READMEs render properly. Stage 2 carries the real design decisions (tag allowlist, attribute degradation like align, nesting).
  • No sanitization puzzle on mobile: nothing executes — unknown tags simply remain text, so the allowlist is the safety boundary.

Related: SVG images in mobile markdown

Adjacent gap, worth fixing under this issue's umbrella: SVG sources in mobile markdown render as an empty frame because React Native's <Image> cannot decode SVG (external URLs included — this predates #7857, which deliberately excludes .svg from the signed-asset flow rather than fetch a URL the renderer cannot draw).

  • The fix is small (~120–200 lines, no new dependency): react-native-svg is already installed and exports SvgUri. Remove the resolver's .svg exclusion, add an SvgUri-based branch in the two renderer hook points (createWorkspaceImageRenderer, createFallbackMarkdownImageRenderer), and pass textStyle through the renderImage contract so the SVG frame matches the module's default image frame and caption.
  • Caveat 1: extension detection covers workspace .svg files but misses the highest-value case — badge URLs (shields.io) usually have no .svg suffix and only reveal their type via content-type. Covering those needs sniffing or try-SvgUri-first logic; that is real design work beyond the simple version.
  • Caveat 2: SvgUri fetches per mount with no cache — fine for file previews, worth attention in the thread feed given repaint/perf sensitivities.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions