Skip to content

feat(markdown): agent-authored widgets, read-only (AGENT-WIDGETS-001 PR-1) - #17

Merged
mfethe1 merged 4 commits into
product/mainfrom
feature/agent-widgets/pr1-schema-registry
Sep 11, 2026
Merged

feat(markdown): agent-authored widgets, read-only (AGENT-WIDGETS-001 PR-1)#17
mfethe1 merged 4 commits into
product/mainfrom
feature/agent-widgets/pr1-schema-registry

Conversation

@mfethe1

@mfethe1 mfethe1 commented Sep 5, 2026

Copy link
Copy Markdown
Owner

Agents publish structured JSON; the client owns the rendering. First half of
AGENT-WIDGETS-001 — read-only widgets, no interaction yet.

Spec: ~/buzz-program/specs/AGENT-WIDGETS-001-plan.md

Format

```buzz-widget
{"v":1,"type":"metric","metrics":[{"label":"Open PRs","value":"2","delta":"+1"}]}
```

Widget type lives inside the JSON, not the fence info string: extractLanguage
(CodeBlock.tsx:49) matches /language-(\S+)/ and keeps only the first token, so a
fence of buzz-widget metric would arrive as language-buzz-widget with metric
silently dropped. Found by probing the real renderer while writing the spec.

Why not HTML

Raw HTML is already inert — no rehypeRaw, and the Tauri CSP forbids it
(object-src 'none', frame-ancestors 'none', no unsafe-eval). That is the
correct posture for a federated app where agent content arrives from machines the
user does not control, so this PR keeps it and never introduces an HTML path.
Values render as text through JSX; no dangerouslySetInnerHTML anywhere.

Degradation

Unknown type, malformed JSON, or an oversized payload (>32KB) falls back to a plain
code block. Content stays readable on older clients and non-desktop surfaces rather
than rendering blank.

Changes

  • widgets/schema.ts — validation + client-side type allowlist (table, metric)
  • widgets/fenceText.ts — recover exact fence text from fragmented React children
  • widgets/WidgetView.tsx — renderers matching MarkdownTable styling
  • markdown.tsx — dispatch in the existing pre handler (single insertion point)

Verification

  • 24 new tests, all passing: schema acceptance/rejection, fence-text recovery,
    and end-to-end render through the real react-markdown pipeline
  • Security tests assert hostile payloads stay data: <img onerror> and <script>
    render escaped, and __proto__ in a payload does not pollute the prototype
  • pnpm run check exit 0; pnpm typecheck clean; biome clean
  • Full desktop suite: 6606/6607

Pre-existing failure, not from this PR: the useKnownAgentPubkeys provenance
test fails on product/main too — verified by re-running it with this work stashed.

Review notes

  • noArrayIndexKey is suppressed in 4 spots with justifications: agent-supplied
    rows/cells carry no stable identity. Follows the existing repo convention
    (CustomHarnessForm.tsx, TeamSnapshotImportDialog.tsx).
  • Sensitive path (renderer), so Path B. Not merging without review.

Next (PR-2)

Interaction: widget actions emit KIND_AGENT_OBSERVER_FRAME (24200) back to the
agent, which already subscribes via subscribe_observer_controls. Adds kanban
and checklist. No new transport needed.

…GETS-001 PR-1)

Agents publish structured JSON in a `buzz-widget` fence; the client owns
the rendering via allowlisted React components. Ships `table` and `metric`.

No HTML path: raw HTML stays inert (no rehypeRaw, CSP forbids it), and
payload strings render as escaped text through JSX. Widget type lives in
the JSON, not the fence info string, because extractLanguage keeps only
the first info-string token.

Unknown types, bad JSON, and oversized payloads degrade to a plain code
block so content stays readable on old clients and other surfaces.

Tests: 24 new (schema, fence-text recovery, end-to-end render incl. XSS-
as-data and prototype pollution). typecheck + biome clean.

Pre-existing unrelated failure on product/main: useKnownAgentPubkeys
provenance test (verified failing with this work stashed).
@coderabbitai

coderabbitai Bot commented Sep 5, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Team

Run ID: bc1d83b4-1fc7-4ca1-8f99-d19c3d98f02d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

mack and others added 3 commits September 6, 2026 09:09
The desktop file-size ratchet forbids growing an already-oversized file;
markdown.tsx was 1862 lines at base and the widget dispatch pushed it to
1871. Move the fence-dispatch rule into markdown/widgets/MarkdownFence.tsx
beside the widget code it belongs to, leaving markdown.tsx one line SMALLER
than base. Behavior is unchanged: widget fences render WidgetView, invalid
payloads and all other languages fall through to MarkdownCodeBlock.
biome lint/style/useImportType: the module uses React only for the
ReactNode type. Matches the convention in AppHuddleBar.tsx and
SidebarSection.tsx.
@mfethe1
mfethe1 merged commit 9e9bfb6 into product/main Sep 11, 2026
40 of 49 checks passed
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