Skip to content

Add a code fence renderer plugin slot and rebuild inline-vis on it - #5387

Merged
SawyerHood merged 4 commits into
mainfrom
code-fence-renderer-slot
Oct 12, 2026
Merged

SawyerHood merged 4 commits into
mainfrom
code-fence-renderer-slot

Conversation

@SawyerHood

Copy link
Copy Markdown
Collaborator

Human comments

What was wrong

Plugins had no supported way to render a multi-line block inside an assistant reply. messageDirective is single-line leaf syntax and stays literal until it closes, so a plugin that wanted generative UI had to scrape rendered pre > code elements and overlay its own UI (#5268). Separately, the built-in inline-vis plugin showed HTML in a fixed 224px card and Markdown in a box that scrolled inside the thread.

What changed

New slot: app.slots.experimental_codeFenceRenderer({ id, languages, component }). A plugin claims fence languages and renders those fenced code blocks in assistant and nested-agent message Markdown. The first two commits are @ComicBit's prototype from #5268 (registration validation, slot store, plugin guide card, test harness support). The third commit rebuilds the rendering side, and the contract differs from the issue's proposal:

  • status: "streaming" | "closed" | "unclosed" replaces complete, so a renderer can tell a block that is still being written from one the reply never closed.
  • The component for a fence is mounted once and kept for the whole reply; there is no remount when the fence closes or the reply finishes. The Markdown tree renders a placeholder, and a host around the message's Markdown (CodeFenceRendererHost) owns the component and portals it into a container that follows the placeholder from the streaming tail to the settled region. Where the browser has Element.moveBefore, an iframe inside the component is not reloaded by that move; elsewhere component state survives and the iframe reloads once.
  • A fence is identified by the source offset of its opening delimiter, the same way images are, so the settled text is not re-parsed to number fences. index (ordinal among fences of the same language) is computed at the message level.
  • A language claimed by two plugins renders BB's code block and logs a warning, matching messageDirective.
  • A throw affects one fence only, and BB tries the component again when that fence's code or status changes, so a renderer that fails on a half-streamed body recovers on the next chunk. PluginSlotMount gains crashResetKey for this; slots that do not pass it behave as before.
  • mermaid stays a core special case inside Original, because it renders on every Markdown surface and this slot is scoped to messages.

@get-bb/plugin-sdk goes to 0.6.40. The plugin guide card, the authoring skill references, bb-guide-plugins.md, and the docs/api_to_audit.md entry describe the new contract and list what to audit before stabilizing. No server/daemon wire fields change.

inline-vis (fourth commit):

  • An HTML file shown with ::inline-vis{file="…"} is now a borderless sandboxed frame on the thread background that fits the page's height and follows bb's theme variables live, with a hover action to open the file. preparePreview returns the file's content and sha256 for this.
  • A fenced inline-vis code block whose body is an HTML page renders the same way through the new slot, with no file. A status line shows while the block streams, and a hover action reveals the source.
  • A Markdown file shows in a card with its file name, copy and open actions, and a preview that expands in place ("Show more") instead of scrolling inside the card.
  • The bundled skill teaches both forms, the layout rules, and the theme variables.

Behaviour changes for existing users of the directive:

  • An HTML page used to get a fixed 224px frame; it now fits its content and height is a cap.
  • height accepts 80–2000 (was 120–1200). For Markdown it is the preview height, 256px by default (was a 224px scrolling box).
  • The header toggle that hid a Markdown card, and its remembered bb.inline-vis.collapsed preference, are removed.

How you verified

  • pnpm exec turbo run typecheck lint --filter='...[origin/main]' --continue: 137 tasks pass.
  • pnpm exec turbo run test for @bb/app, @get-bb/plugin-sdk, bb-plugin-inline-vis, bb-plugin-plugin-api-docs, @bb/templates, @bb/domain, bb-plugin-bb-guide: all pass (5,731 app tests, 369 SDK, 51 inline-vis, 73 plugin guide, 47 templates, 230 domain, 7 bb-guide).
  • Server plugin suites (builtin-registry, plugin-app-bundle, bb-official-generator, sdk-compat, plugin-authoring-docs): pass.
  • node apps/app/scripts/check-bundle-budget.mjs after building @bb/app, node .github/workflows/check-plugin-sdk-version.mjs, and node packages/plugin-sdk/scripts/check-npm-version-guard.mjs: pass.
  • New tests in markdown-code-fence-renderers.test.tsx cover: one component instance and its state kept from the first streamed line through the tail-to-settled move to the finished reply; index across the two streaming regions; status for unclosed, closed, list-nested, blockquoted, tilde, and over-indented fences; a crash isolated to one fence and retried when its code changes; a contested language falling back to BB's code block; and claims surviving the sanitized HTML pipeline.
  • Live, in a worktree dev app with a Claude Haiku thread: a reply containing an inline-vis block, an HTML file directive, and Markdown file directives rendered in light and dark themes and at phone width. During a streamed reply the status line showed, the page's iframe was inserted once, moved from the streaming tail to the settled region without being disconnected, and kept its document to the end of the reply.

Not verified: Safari and the mobile WebView (no moveBefore, so an iframe reloads once when its block settles), and clicking the copy and open-in-sidebar actions in the live app (tests cover both).

Fixes #5268

🤖 Generated with Claude Code

AGENT GENERATED

ComicBit and others added 4 commits October 12, 2026 20:59
… fence

Changes the experimental_codeFenceRenderer contract and how the app hosts it:

- `status: "streaming" | "closed" | "unclosed"` replaces `complete`, so a
  renderer can tell a block that is still being written from one the reply
  never closed.
- The component for a fence is mounted once and kept for the whole reply. The
  Markdown tree renders a placeholder; a host around the message's Markdown
  owns the component and portals it into a container that follows the
  placeholder from the streaming tail to the settled region. Where the
  browser has Element.moveBefore, an iframe inside it is not reloaded.
- A fence is identified by the source offset of its opening delimiter, so the
  settled text is no longer re-parsed to number fences. `index` is computed
  at the message level.
- A language claimed by two plugins renders BB's code block.
- A throw affects one fence, and the component is tried again when that
  fence's code or status changes. PluginSlotMount gains a crash reset key.

Bumps @get-bb/plugin-sdk to 0.6.40 and updates the plugin guide, authoring
skill, and audit entry.

Co-Authored-By: Claude Code <noreply@anthropic.com>
…n card

- An HTML file shown with the directive is now a borderless sandboxed frame
  on the thread background that fits the page's height and follows bb's
  theme, with a hover action to open the file.
- A fenced `inline-vis` code block whose body is an HTML page renders the
  same way through the code fence renderer slot, with no file. A status line
  shows while the block streams, and a hover action reveals the source.
- A Markdown file shows in a card with its file name, copy and open actions,
  and a preview that expands in place instead of scrolling inside the card.
  The header-only collapse and its remembered preference are removed.
- `height` accepts 80 to 2000: a cap for HTML, the preview height for
  Markdown.

Co-Authored-By: Claude Code <noreply@anthropic.com>
@SawyerHood
SawyerHood marked this pull request as ready for review October 12, 2026 21:28
@SawyerHood
SawyerHood merged commit 406c009 into main Oct 12, 2026
88 of 89 checks passed
@SawyerHood
SawyerHood deleted the code-fence-renderer-slot branch October 12, 2026 21:28
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.

Feature request: plugin slot to render fenced code blocks in assistant messages (app.slots.experimental_codeFenceRenderer)

2 participants