Skip to content

Markdown preview: run zeron-visual fences as native visuals - #911

Open
jsgrrchg wants to merge 4 commits into
zeronsh:mainfrom
jsgrrchg:zeron/markdown-visual-fences
Open

jsgrrchg wants to merge 4 commits into
zeronsh:mainfrom
jsgrrchg:zeron/markdown-visual-fences

Conversation

@jsgrrchg

@jsgrrchg jsgrrchg commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Agents (and people) can now embed native Zeron visuals directly in a Markdown document. A fenced block whose language is zeron-visual runs as a Luau visual in the Files Markdown preview, with the same runtime, theme tokens and controls as show_visual in chat.

```zeron-visual
--! title: Latency by region
local s = ui.state { metric = "p95" }
return function()
  return ui.column { gap = 16, ui.text "..." }
end
```

Elsewhere (GitHub, other editors, chat replies, iOS) the fence is ordinary code, so documents stay portable.

image

What changed

  • Preview (markdown_preview.rs, render.rs, visuals.rs): fences render without a code-block frame, with a quiet "Show source / Show visual" toggle. A document runs at most 8 visuals, each starting lazily when its row first renders. Truncated previews run none, and documents outside the workspace show a "Run visual" button instead of running on their own.
  • Determinism: math.random is seeded from the fence's code and its position among identical fences (seed_for_document), so a fence keeps its data and live state when surrounding text changes.
  • zeron visual check <file>: runs every fence of a .md/.markdown (or a whole .luau) the way the preview will and maps errors to document lines, so an agent that cannot see the preview can still validate its work. Exits 1 on any failure.
  • Fence extraction (crates/markdown): fenced_code_blocks() reuses parse_full's parser, options and normalization, so the CLI and the preview always agree on what a fence is (and therefore on seeds).
  • Docs: docs/mcp.md documents the feature; the show_visual tool description mentions fences and visual check.

Fixes found while building this

  1. Nested visual id collisions. Nested block indices used base-100 arithmetic, so two fences in nested quotes (or list item 0 / child 10 vs item 1 / child 0) shared an id and could show each other's state. Replaced by a single preorder walk (visit_blocks) shared by rendering and the fence helpers. This also fixes the same collision for code-block scroll handles.
  2. Visuals not released past the limit. Adding a fence above the first 8 left the displaced visual alive. sync_visuals now retains only allowed visuals.
  3. CLI/preview divergence. The hand-written line scanner disagreed with the preview parser on tab-indented list fences, HTML blocks, indented code and unclosed list fences. It is replaced by the shared parser, which removes about 200 lines.

Performance

Measured on Linux, release build, RTX 3090, comparing this branch against its base:

  • Documents without visuals: the render path is within noise of the base (a language check in render_code_block); sync_visuals and row_visuals cost about 0.1 ms and 0.7 ms upper bound for a 6000-block document.
  • Per visual: ~0.9 ms to start, 0.04–0.14 ms per render; canvas draw 0.3 ms/frame, animated scene Lua 1.4 ms/frame.
  • Scene on GPU 4.3 ms/frame at 720x320 @2x; eight concurrently animated scenes sustain ~60 fps each on this GPU.

Known limitations

  • Fences take no assets (s:model, textures, asset environments fail with the runtime's usual error).
  • Lua runs on the main thread, hence the cap of 8 visuals per document.
  • Animated visuals request a frame every display frame; a frame-rate cap and pausing when inactive are possible follow-ups.

Test plan

  • cargo test -p zeron-visual -p zeron-markdown and cargo test -p zeron-ui --lib markdown
  • cargo test -p zeron --bin zeron visual_cli
  • Open a .md with several zeron-visual fences in Files > Preview: they render, toggle source, and keep state while editing text around them
  • Nested fences (quote inside quote, long lists) render independently
  • A document outside the workspace shows "Run visual" and runs nothing until clicked
  • zeron visual check doc.md reports a deliberate error at the right document line

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

Fenced ```zeron-visual blocks in Markdown documents run as Luau visuals
in the Files preview, with a Show source / Show visual toggle, a cap of 8
live visuals per document, and a Run visual button for documents outside
the workspace.

Add `zeron visual check <file>`, which runs a document's fences (or a
.luau script) with the preview's seeds and maps errors to document lines,
backed by a line scanner in zeron_visual::document.
@jsgrrchg
jsgrrchg marked this pull request as draft October 15, 2026 04:39
@jsgrrchg

Copy link
Copy Markdown
Contributor Author

The engine-tests failure looks unrelated to this PR: the branch doesn't touch crates/engine, and the same base commit (6ac88599) passed Windows tests on main.

Only one test failed (464/465 passed), on the Windows runner:

doc_host::remote_delivery::tests::failed_remote_wake_survives_row_ack_and_host_restart

  • doc_host_delivery.rs:245: the mock edge server received a request that wasn't POST /device/remote/nudge
  • doc_host_delivery.rs:302: "row delivery and wake delivery are independent"

The test enqueues a chat update and acknowledges it right after nudging the remote host. My guess (not verified) is that on a slow runner the host sometimes delivers that update to the mock server before the ack lands, which trips both assertions. If so, it's a timing race in the test rather than a product bug. I'll re-run the failed job; if it fails again I'll open a separate issue for it.

@jsgrrchg
jsgrrchg marked this pull request as ready for review October 15, 2026 04:58
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