External Text Rendering
Plugin boundary for converting pasted source text into editable Canvas cells.
HEAD a63778eAll text hosts consume one compiler contract:
source + explicit kind -> CharGraph compiler -> Protocol row spans -> host
chargraph runs structural syntax such as Markdown, Mermaid, fenced data, math,
and block layout. chardesk, ansi, and plain are explicit compiled or
literal inputs and never enter structural recognition. Canvas paste, document
import, Slides chargraph fences, CLI, Viewer, and Blackboard share this
contract. Rendering consumes compiled documents and does not select syntax.
document/public.ts
owns the plugin registry, active profile, persistence, and conversion result. actions
only snapshots clipboard data and passes the result to Canvas. The compact boundary groups
adjacent graphemes with one style; legacy callers may explicitly materialize cells. Renderer
settings affect future paste and never reinterpret existing cells.
Rendering has four stages: syntax decode, structural transform, style composition, and cell
layout. ANSI is a decoder backed by @chardesk/protocol; Markdown delegates parsing and character
layout to @chardesk/chargraph/markdown. CharGraph renderers return styled fragments with source
origins and diagnostics. The compositor projects explicit ANSI spans through those origins before
protocol layout, which remains the only stage that owns grapheme segmentation, CJK width, tabs,
and coordinates. ANSI/OSC8 serialization is an explicit CharGraph output boundary for CLI or copy,
not an internal intermediate representation.
Auto composes explicit ANSI and Markdown instead of selecting one renderer for the whole source.
An ESC-less empty reset ([m) is ambiguous evidence and cannot activate ANSI by itself, preventing
Markdown links beginning with [m from losing characters. Standard escapes, OSC 8, and non-empty
supported SGR are explicit. Forced ANSI still accepts [m. When both syntaxes style one range,
ANSI foreground, background, and OSC 8 href win; text attributes merge; Markdown controls visible
structure. Raw is a fallback transformer: it runs only when no decoder or structural transformer
recognized the source, while forced Raw selects that same plugin directly. ANSI-only input therefore
materializes styled cells without passing through Raw. TextRenderResult.pipeline records the
applied stages.
Markdown renders Codex-style headings, inline code, links, blockquotes, lists, thematic breaks, fenced code, and GFM tables at natural content width. Layout never depends on the viewport, zoom, or paste position. Disabled inline rules consume delimiters without styling; disabled block rules preserve their source slice. Raw mode preserves all delimiters. Renderer results may be asynchronous, and clipboard commands await them before mutating Canvas. Inputs at or above 50,000 UTF-16 code units render in a module worker. Worker tasks may be cancelled, and a worker failure is surfaced instead of retrying a large render on the main thread. The protocol layout is a streaming grapheme pass; a 10,000-line result stays as row spans until Canvas applies one compact CellPlane operation.
CharGraph owns the browser-safe Markdown core adapted from marked-terminal, Shiki code
highlighting, and independent Mermaid and math renderers. Each built-in Markdown module registers
its parser extensions, feature identities and color defaults, and semantic style contribution once.
The module registry derives the default extension manifest, public feature contracts, and render
options; the theme layer only aggregates module contributions. Markdown syntax extensions
may contribute marked tokenizers and transform tokens or fenced languages into the same fragment
IR. CharGraph's built-in extension manifest registers Alert, Diff, JSON/YAML trees, Mermaid,
Cartesian charts, and math once for rendering and detection. The document feature registry binds those engine features to
Canvas labels, settings groups, color-row layout, and legacy profile aliases; it does not own parser
registration or renderer styles. Unsupported, invalid, or oversized Mermaid input falls back to the complete fenced
source with a diagnostic; disabling Mermaid keeps the inner source as an ordinary code block.
Neither renderer imports terminal capability detection or Node polyfills. Markdown consumes
marked tokens through a character renderer rather than its HTML renderer. Unsupported images and
raw HTML preserve their original source with a diagnostic; generated HTML cannot leak into Canvas
output.
The chardesk CLI is a headless host of the same
contracts. CharGraph input becomes styled fragments and Protocol layout; .chardesk input enters
at Protocol decode. check stops at that compiled document. render sends it to a plain, ANSI,
ESC-less CharDesk, or PNG backend; only PNG creates the shared Rendering model and uses its Canvas
painter with vendored fonts on Node Canvas. Node resolves a vendored font shard per grapheme; the
CLI isolates the native PNG painter so a backend signal becomes raster-backend-crash. ANSI serialization is an explicit output backend, never
an internal step toward PNG. Browser workers, profiles, clipboard commands, and persistence remain
application concerns.
Container extensions may call the context's renderBlocks capability to render child marked
tokens through the same rules, styles, source mapping, diagnostics, and extension set. GitHub Alerts
use marked-alert for tokenization and render NOTE, TIP, IMPORTANT, WARNING, and CAUTION
as a colored left rail while preserving nested Markdown. Unified diff and patch fences classify
metadata, hunks, added, deleted, and context lines; additions and deletions receive semantic foreground
and lightly mixed background colors. Diff does not infer a programming language, compare words, or
apply patches.
JSON and YAML tree extensions turn json, jsonc, yaml, and yml fences into the same compact
Unicode key tree. JSON uses jsonc-parser offsets; strict JSON and comment-tolerant JSONC remain
separate dialects. YAML uses yaml document nodes, preserves document order, anchors, aliases, and
merge keys without resolving aliases, and rejects complex mapping keys that the compact grammar
cannot represent. The two features have independent switches and semantic color slots. Disabling a
tree returns the fence to Shiki; invalid, unsupported, or bounded-out input preserves the complete
fenced source with a diagnostic. Tree conversion is limited to fenced Markdown and does not detect
bare clipboard data. Connectors, keys, array indices, strings, numbers, booleans, nulls, and empty
containers have separate roles; YAML adds a reference role for anchors, aliases, and tags. Keys use
color without font emphasis. Previous keyword overrides expand across boolean, null, empty, and
YAML reference roles when profiles are decoded.
Math uses Temml's public TeX-to-MathML output and a DOM-free MathML adapter. Inline $…$ and
\(…\) become compact Unicode; block $$…$$, \[…\], and math/tex/latex fences become a
baseline-aware two-dimensional character box before flattening to fragments. Layout cells preserve
MathML semantics through fragment output: identifiers use italic content, numbers and text remain
upright, operators use the accent role, and generated fraction, radical, and fence glyphs use the
muted structure role. MathML invisible operators U+2061–U+2064 are removed before visible cells
are created. Inline and block rules have independent switches and share one Math palette
with content, operator, and structure slots. The adapter owns character-cell layout only; it never
parses TeX or exposes Temml's private syntax tree. Invalid or bounded-out formulas preserve their
complete Markdown source with a diagnostic and the Renderer Theme danger color. Previous inline
and block foreground overrides migrate to shared content, preferring inline when both exist.
TextRenderProfile.renderTheme stores optional semantic token overrides; the resolved Renderer
Theme is independent from the host UI theme. Profile v2 stores each rule's enabled state and local
color overrides under features. CharGraph's Renderer Theme owns the shared token palette;
Markdown and Mermaid own their semantic defaults and style factories. Canvas and the CharGraph
showcase are separate hosts of those contracts.
rendering/features.ts owns settings labels, host grouping, compatibility, and persistence; its
public registry derives identity and slot defaults from CharGraph contracts. Display settings and
settings search derive from that registry rather than parallel UI maps.
Style precedence is explicit ANSI, feature color override, Renderer Theme token, then inherited paste
style. Each feature definition owns its slots' token or inheritance defaults.
Quote and heading colors belong to their markers; inline code owns foreground and background
slots; table headers own foreground and background slots while separators are independent.
Fenced-code scopes remain owned by Shiki. CharGraph derives their token colors from the resolved
Renderer Theme; the bare library API keeps github-light as its compatibility fallback. Mermaid emits semantic roles for node, edge,
label, arrow, container, chart grid, and data series cells; the document registry maps those roles
to Renderer Theme tokens and feature-local overrides. Node fill remains transparent by default.
Persisted single-foreground Mermaid settings expand across the new foreground roles, while profile
v1 rules and global color keys migrate to feature-local profile v2 settings on first read. Like every renderer
setting, style changes affect future paste only because Canvas stores materialized cells.
Verification lives in
runtime.test.ts
and packages/chargraph/src/markdown.test.ts, plus the clipboard integration tests.