Skip to content

feat(workbench): render inline json-render generative UI specs - #6902

Open
M4n5ter wants to merge 2 commits into
rust-previewfrom
feat/json-render
Open

M4n5ter wants to merge 2 commits into
rust-previewfrom
feat/json-render

Conversation

@M4n5ter

@M4n5ter M4n5ter commented Nov 14, 2026 •

Copy link
Copy Markdown
Member
English

Summary

Add native inline Generative UI rendering for ```spec Markdown code blocks inside Maka conversation flows on both Desktop (GPU) and Terminal (TUI), powered by @json-render/core, @json-render/directives, and a 63-component native NSH registry.

  • In-place streaming without remounting or sibling collisions: MarkdownBody and BotReply split top-level spec fences via splitSpecMarkdown into stable slot-keyed segments while preserving canonical sourceId byte offsets on leading Markdown text. Each SpecView stays mounted in place across streaming chunks so active <Input> focus, uncontrolled local component signals (Show spec source, Accordion, Tabs, Carousel), and user-modified $state survive progressive patches—even when multiple sibling spec blocks share overlapping JSONL prefixes in the same message.
  • 63 native NSH components + nsh/base/plot charts: Covers shadcn + ink layout/display/navigation/overlay/form controls (Card, Stack, Grid, Tabs, Accordion, Dialog, Drawer, Table with natural text & unit-domain numeric sorting across duration/byte/currency/percent values and per-column sortable, Input, NumberInput with draft-safe clamping, Select, MultiSelect, Slider, ConfirmInput, etc.) plus 7 native charts (LineChart, AreaChart, BarChart, PieChart, ScatterChart, Heatmap, Sparkline). Runtime App.js uses lightweight SPEC_COMPONENT_NAMES so catalog.ts and schema.ts stay out of the runtime bundle.
  • Strict client-only boundary: Specs execute locally inside the conversation view with 7 built-in client actions (setState, pushState, removeState, toggleState, resetState, validateForm, copyText), watch cycle protection (MAX_WATCH_CASCADE = 16), directives ($cond, $template, $format, $math, $concat, $count, $truncate, $pluralize, $join), and computed helpers (sum, avg, min, max, count, join). Link and Markdown links route through the workbench openLink handler (guarded by row ownership), and Image / Markdown images route through MarkdownImageView (host.markdownImageSource + host.readFile).
  • Standalone bundled skill & CI test stability: Ships authoring rules in skills/maka-generative-ui/SKILL.md verified against buildSpecCatalogPrompt(), and waits for label:New chat before typing in pending_composer_selects_slash_skills_with_enter_and_tab to eliminate a headless startup race.

Verification

  • Workbench & CI unit tests (node --test products/maka/workbench/tests/*.test.mjs scripts/ci.test.mjs): 182/182 workbench tests pass, covering catalog/registry/names lockstep, zero runtime imports of catalog.ts / schema.ts / zod, SKILL.md prompt & directive integrity, splitSpecMarkdown sibling prefix overlap & nested outer fences, SpecSession idempotent incremental patch compilation, committedDoc immutability, user-touched $state survival alongside /state key removals and shrunk arrays, and Table unit-domain sorting (ms/s, B/KB/MB/GB/TB, currency/percent isolation).
  • Headless Desktop & Terminal UI integration tests (cargo test -q -p maka-workbench --lib spec_view_tests & cargo test -q -p maka-workbench --lib pending_composer_selects_slash_skills_with_enter_and_tab): Passed on both Idiom::Desktop and Idiom::Terminal at 1080x1600 viewport—verifying active $bindState <Input> NodeId focus & subsequent keystrokes across mid-stream chunks, Sparkline streaming insertion, Show spec source & uncontrolled Accordion signal survival, side-by-side prefix-overlapping sibling spec blocks in one message, Table alphabetical/natural/duration/byte sorting & per-column sortable: false, NumberInput draft typing & min clamping, Tabs value="default" single-panel rendering, cyclic watch termination at cascade cap, Link openLink callback, duplicate-key repeat + pushState + validateForm, and parse-error fallback recovery.
  • Bundled skill, product build, typecheck, formatting & notices:
    • cargo test -q -p maka-skills --test integration bundled_generative_ui_skill — passed.
    • cargo run -q -p maka-workbench --example ui_types -- <tmp> + tsc -p <tmp>/tsconfig.json --noEmit — passed.
    • npm --workspace @maka-agent/product run build && npm --workspace @maka-agent/product run typecheck && npm --workspace @maka-agent/product test — 30 plugin bundles + 4 product tests passed; verified catalog.ts / defineCatalog are absent from conversations/bundle.js.
    • node scripts/rust/notices.mjs check-reviewed, node scripts/rust/dependencies.mjs check, node scripts/asf-license-headers.mjs check, biome check, and rustfmt --edition 2024 --check — all passed.
  • Not run: Live interactive LLM desktop session against an external model provider.

AI use

Select exactly one:

  • No generative tool made a substantive contribution
  • Generative tooling made a substantive contribution

Tool(s) and scope: Google Antigravity authored the spec splitter/session state engine, native NSH renderer/components/charts, bundled maka-generative-ui skill, and unit/headless regression tests.

Checklist

  • Tests cover the change and fail without it
  • Format, typecheck and the affected suites pass locally

Does this PR entail a change in behavior?

  • Yes — described under Summary above
  • No
简体中文

Summary

在 Maka 会话消息流中新增对内联 ```spec Markdown 代码块的原生 Generative UI 渲染支持,同时覆盖桌面端(GPU)与终端(TUI),基于 @json-render/core、@json-render/directives 与 63 个原生 NSH 组件实现。

  • 流式原地更新,消除重挂载失焦与兄弟块前缀串台:MarkdownBody 与 BotReply 通过 splitSpecMarkdown 将顶层 spec 围栏拆分为带稳定槽位 key 的分段,同时保留起始 Markdown 段落的 sourceId 字节偏移语义。流式追加期间 SpecView 原地保持挂载,<Input> 焦点与后续按键输入、组件局部非受控信号(源码展开态、Accordion、Tabs、Carousel)以及用户修改过的 $state 均不丢失;同一条消息内存在多个前缀重叠的 spec 块时亦互不抢占。
  • 63 个原生 NSH 组件与 nsh/base/plot 图表:完整覆盖 shadcn + ink 的布局、展示、导航、浮层与表单控件(含支持自然排序、跨单位域数值换算排序与列级 sortable 的 Table、支持编辑草稿态失焦钳制的 NumberInput 等)以及 7 类原生图表(LineChart、AreaChart、BarChart、PieChart、ScatterChart、Heatmap、Sparkline)。运行时 App.js 仅依赖轻量常量 SPEC_COMPONENT_NAMES,将 catalog.ts 与 schema.ts 隔离在测试与 prompt 生成侧。
  • 纯前端本地执行与安全边界:仅开放 7 个内置本地状态动作(setState、pushState、removeState、toggleState、resetState、validateForm、copyText),内置 watch 级联步数上限保护(MAX_WATCH_CASCADE = 16),支持全部标准动态指令与 $computed(含 join)。Link 与 Markdown 链接统一经 workbench openLink(含行归属校验)打开;Image 与 Markdown 图片统一走 MarkdownImageView(host.markdownImageSource + host.readFile)受控管线。
  • 独立内置 Skill 与测试稳定性修复:内置 skills/maka-generative-ui/SKILL.md 并与 buildSpecCatalogPrompt() 严格同步校验;同时在 pending_composer_selects_slash_skills_with_enter_and_tab 中补齐 wait_for("label:New chat"),消除无头测试启动竞态。

Verification

  • Workbench 与 CI 单元测试(node --test products/maka/workbench/tests/*.test.mjs scripts/ci.test.mjs):182 项测试全部通过,覆盖组件白名单/注册表/Catalog 一致性、运行时零 catalog.ts / schema.ts / zod 直接引用、SKILL.md 变量与动作契约完整性、splitSpecMarkdown 同消息前缀重叠双块与外层围栏嵌套、SpecSession 幂等增量编译、committedDoc 不可变性、用户态 $state 保留、/state 删键与数组缩容同步,以及 Table 时间/字节/货币/百分比同域换算排序与异域隔离。
  • 桌面端与终端无头 UI 集成测试(cargo test -q -p maka-workbench --lib spec_view_tests 与 cargo test -q -p maka-workbench --lib pending_composer_selects_slash_skills_with_enter_and_tab):在 1080x1600 视口下同时通过 Idiom::Desktop 与 Idiom::Terminal 验证——覆盖流式分片追加期间 $bindState <Input> 原生节点 NodeId 保活与焦点连续输入、Sparkline 动态插入、源码展开态与非受控 Accordion 局部状态保活、同消息前缀重叠双 spec 块并存、Table 文本/自然/时间/字节换算排序与列级 sortable: false、NumberInput 草稿输入与 min 提交钳制、Tabs value="default" 单次渲染、循环 watch 级联熔断、Link openLink 回调、重复 key repeat + pushState + validateForm,以及非法 spec 报错回退与恢复。
  • 内置 Skill、产品构建、类型检查、格式化与许可证校验:
    • cargo test -q -p maka-skills --test integration bundled_generative_ui_skill — 通过。
    • cargo run -q -p maka-workbench --example ui_types -- <tmp> + tsc -p <tmp>/tsconfig.json --noEmit — 通过。
    • npm --workspace @maka-agent/product run build && npm --workspace @maka-agent/product run typecheck && npm --workspace @maka-agent/product test — 30 个插件包构建及 4 项产品测试通过,确认 conversations/bundle.js 不含 catalog.ts / defineCatalog。
    • node scripts/rust/notices.mjs check-reviewed、node scripts/rust/dependencies.mjs check、node scripts/asf-license-headers.mjs check、biome check、rustfmt --edition 2024 --check — 全部通过。
  • 未执行项:连接外部真实模型服务商的桌面窗口手工端到端对话验证。

AI use

Select exactly one:

  • No generative tool made a substantive contribution
  • Generative tooling made a substantive contribution

Tool(s) and scope: Google Antigravity 编写了 spec 分段解析与增量状态引擎、NSH 原生渲染器/组件/图表、内置 maka-generative-ui skill 以及单元与无头集成回归测试。

Checklist

  • Tests cover the change and fail without it
  • Format, typecheck and the affected suites pass locally

Does this PR entail a change in behavior?

  • Yes — described under Summary above
  • No

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review notice: This comment was posted by an automated review agent operated by Astro-Han. It is not an independent human review and does not replace one.

I checked the per-block session isolation at 0804ba7719b209df66411107083aafcb5293fc9e: MarkdownBody.tsx shares one SpecSessionPool across its Markdown fences, and SpecView.tsx acquires a session by source text and subscribes to that session's frame/store. There is one P2 in the active-session handoff branch of products/maka/workbench/app/spec/session.ts:350–357: a second independent fence whose complete JSONL source extends the first fence's source reuses the still-mounted first session. Its input edits and compiled frame consequently overwrite the first block. Details are inline.

I ran the five existing spec.test.mjs tests unchanged against the current catalog/schema/session modules and integrity-verified versions from this commit's lockfile; all five passed. Three additional module tests passed for unrelated/equal active siblings and detached remount state, but failed for concurrent prefix siblings (2 passed, 1 failed). In that failing case the second block inherited private A, editing it changed the first block's answer to edited B, and the first frame changed to Second form. A control that disabled only the active-session prefix handoff made all three pass. That control isolates the cause; it is not a complete remount-preserving fix. Fence identity or an explicit remount handoff needs to distinguish these cases.

This is a focused review of session allocation and its Markdown wiring. I did not complete the 33-file review or run native NSH Desktop/Terminal rendering, layout, accessibility, chart, Host, or full CI acceptance. The new dependency/skill/catalog and remaining rendering behavior still need their full checks.


Automated review notice: This comment was posted by an automated review agent operated by Astro-Han. It is not an independent human review and does not replace one.

}
}
} else if (
candidate.refCount === 1 &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Keep concurrent prefix-sharing fences in separate sessions

This branch treats a single attached session as a remount whenever a new source extends its text, but a separate completed spec fence in the same Markdown message can have exactly that relationship. MarkdownBody passes one pool to every fence, so mounting A and then independent B = A's valid JSONL plus a label patch returns the same SpecSession for both while both are still mounted. With the unchanged production session module, B inherited A's entered answer; editing B changed A's answer, and ingesting B changed A's subscribed frame to B's label. The existing sibling test uses different source prefixes and misses this case. Use stable fence identity or an explicit handoff signal so source-prefix similarity alone cannot merge two live blocks, while retaining legitimate streaming remount behavior.

Automated review notice: This comment was posted by an automated review agent operated by Astro-Han. It is not an independent human review and does not replace one.

Render inline `spec` Markdown fences inside conversation flows on both
Desktop and Terminal using `@json-render/core`, `@json-render/directives`,
and a 63-component native NSH registry (including `nsh/base/plot` charts).

Split top-level `spec` fences into stable keyed segments in `MarkdownBody`
and `BotReply` so streaming updates keep native inputs, focus, and local
component signals mounted in place without sibling block collisions. Keep
specs strictly client-only (`Link` and `Markdown` links route through the
workbench `openLink` handler; `Image` and `Markdown` images route through
`MarkdownImageView`), keep `catalog.ts` out of runtime `App.js`, and ship
authoring rules as the standalone bundled `maka-generative-ui` skill.

Also wait for the composer input before typing in
`pending_composer_selects_slash_skills_with_enter_and_tab` to avoid a
headless startup race.

Generated-by: Google Antigravity

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review notice: This comment was posted by an automated review agent operated by Astro-Han. It is not an independent human review and does not replace one.

I rechecked the session-isolation fix at d48e18b95f136e542eaa61f0ba6dfdc3e4d4a045 and withdraw the P2 reported in my previous review. SpecSessionPool has been removed. MarkdownBody and BotReply split top-level spec fences into stable slot keys, and each mounted SpecView creates its own SpecSession. Source-prefix similarity can no longer select another live block's session through the old pool.

I ran all five unchanged tests from this commit's spec.test.mjs against its production modules and lockfile-matched dependencies; all five passed. An additional controlled composition test using the actual splitSpecMarkdown and SpecSession modules also passed: two prefix-sharing sibling fences kept distinct sessions, A retained private A and its original frame, and B retained its edited value as its source grew and its closing fence arrived. Replacing only session allocation with the previous production pool made that same test fail: the two blocks shared a session, A's answer became edited B, and A's frame changed to B's label.

The composition test models the keyed allocation read in the production JSX; it does not execute NSH's renderer. I have not independently verified native focus/remount behavior, Desktop/Terminal interactions, all 63 components, the expanded image/Host paths, or the full 35-file change. The author's headless/native results remain the author's evidence. This follow-up clears the specific old session-aliasing finding; it is not full PR acceptance. CI for this head was still running when checked.


Automated review notice: This comment was posted by an automated review agent operated by Astro-Han. It is not an independent human review and does not replace one.

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review notice: This comment was posted by an automated review agent operated by Astro-Han. It is not an independent human review and does not replace one.

I reviewed the five-file Table-sorting increment from d48e18b95f136e542eaa61f0ba6dfdc3e4d4a045 to 0e9548708fa89d464564713dc85e8e9dbda6aba6. There is one P2 in products/maka/workbench/app/spec/sort.ts:135–140: selecting numeric-unit comparison only for matching pairs, but natural-text comparison for other pairs, makes the comparator non-transitive. A supported Table column containing 1 s, 900 ms, and 2 forms a three-way ordering cycle. Sorting the same rows from all six input permutations produces three different orders. The inline comment gives the exact comparisons.

All six current spec.test.mjs tests passed unchanged against the production modules and lockfile-matched dependencies. My additional consistency test failed with the actual compareTableCells module. The same six permutations produced one consistent order with the extracted previous production sorting callback, and with a control that selected natural comparison for the whole mixed-domain column. The controls establish the pair-dependent comparison problem; they are not a complete sorting-policy implementation.

The session allocator and its SpecView, MarkdownBody, and BotReply wiring are byte-for-byte unchanged from the previous fix, so the old session-aliasing P2 remains withdrawn. This review is limited to the sorting increment and that source comparison. I did not run NSH's native renderer, focus/remount or Desktop/Terminal interaction tests, or complete acceptance of the full 36-file PR and its image/Host paths. CI for this head was still running when checked.


Automated review notice: This comment was posted by an automated review agent operated by Astro-Han. It is not an independent human review and does not replace one.

const dir = asc ? 1 : -1;
const an = parseSortableNumber(a);
const bn = parseSortableNumber(b);
if (an !== null && bn !== null && an.domain === bn.domain) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Use one consistent ordering across the entire Table column

Table rows are converted to strings without requiring a shared unit domain. For a column containing 1 s, 900 ms, and 2, the production comparator returns positive values for all three comparisons: compareTableCells("1 s", "900 ms") === 100, compareTableCells("900 ms", "2") === 898, and compareTableCells("2", "1 s") === 1. The first pair uses converted duration values while the other pairs use natural text, creating a cycle. With the actual module, the six input permutations of these same rows produce three different ascending orders, so a row refresh can change the displayed order even though the sort direction and values are unchanged. Choose a comparison policy once for the column, or a consistent domain ordering, rather than switching policies for each pair; add a mixed-domain transitivity/permutation regression.

Automated review notice: This comment was posted by an automated review agent operated by Astro-Han. It is not an independent human review and does not replace one.

This branch has not been deployed

No deployments
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.

2 participants