Add a chat-render fresh-ui item that parses fenced chart/donut/table/stats/line blocks out of assistant markdown into a typed RenderPart[] tree, guaranteed to reload byte-identical.
Context
Part of epic #238 · cluster fresh-ui · wave v1-min · depends-on FB0, FA1.
This sits directly downstream of FB0 (the chat-primitive manifest registration) and FA1 (the
message/part contract in @netscript/fresh/ai); it is the first slice that turns raw assistant
text into renderable design-system output, and every later generative-UI slice (FB5's recursive
JSON renderer, the eventual chat page assembly) parses through this seam rather than re-inventing
block detection.
Problem / motivation
Today there is no supported path from an LLM response to a rendered chart, donut, table, stats
block, or line chart in fresh-ui. eis-chat's prior art hand-rolls markdown-fence sniffing and
string-templates the result straight into JSX per call site, with no shared parser, no reload
guarantee, and no seam other pages can reuse. Doctrine requires generative UI to be owned by
fresh-ui's curated vocabulary (not ad-hoc per-app parsing), and FB0 only registers the primitives —
nothing yet turns model output into instances of them. Without this slice, FB0's primitives are
inert copy-paste components with no ingestion path.
Scope
Ships
packages/fresh-ui/src/chat/parse-blocks.ts (or equivalent src/chat/ module) exporting:
parseBlocks(input: string): RenderPart[]
blockToText(part: RenderPart): string (the inverse projection used for the reload check and
for plain-text fallback/export)
- the
RenderPart discriminated union (chart, donut, table, stats, line, and a text
fallback member for non-fenced prose)
- Fenced-block grammar recognizing
chart, donut, table, stats, ```line info-strings,
parsing their body (JSON or the minimal DSL agreed with FA1's message contract) into the
corresponding DS primitive's prop shape (ChartDatum[], table rows/columns, stats entries).
- Reload-fidelity guarantee:
parseBlocks(blockToText(parseBlocks(input))) produces a RenderPart[]
deep-equal to the first parse, for every fixture covering the five block kinds plus mixed
text+block input.
- Unit tests under
packages/fresh-ui/tests/chat/parse-blocks.test.ts covering each block kind,
malformed-fence fallback to text, and the roundtrip-fidelity property.
Out of scope
- Recursive/nested JSON-driven generic renderer — owned by FB5.
- Registering the chat primitives themselves in the manifest/
ai collection — owned by FB0.
- The message/part wire contract and streaming assembly — owned by FA1 (fresh/ai).
- Actually mounting
RenderPart[] into a chat page/island — deferred to a later fresh-ui chat-page
slice, not this one.
Public surface
- New exports (subject to final file layout, all under
packages/fresh-ui/src/chat/):
parseBlocks, blockToText, RenderPart, and the per-kind part interfaces
(ChartRenderPart, DonutRenderPart, TableRenderPart, StatsRenderPart, LineRenderPart,
TextRenderPart).
- New registry manifest item
chat-render (kind lib, copyOwnership: app-owned-after-copy) added
to the ai collection created by FB0, with files pointing at the shipped module(s) and
dependencies on the existing chart/donut/table/stats primitive items.
Acceptance & fitness gates
Dependencies
Depends on FB0 (chat-primitive manifest registration, ai collection) and FA1 (fresh/ai
message/part contract) — GitHub numbers tracked in the #238 DAG checklist. Not blocked by #239.
Size
M
Add a
chat-renderfresh-ui item that parses fenced chart/donut/table/stats/line blocks out of assistant markdown into a typedRenderPart[]tree, guaranteed to reload byte-identical.Context
Part of epic #238 · cluster fresh-ui · wave v1-min · depends-on FB0, FA1.
This sits directly downstream of FB0 (the chat-primitive manifest registration) and FA1 (the
message/part contract in
@netscript/fresh/ai); it is the first slice that turns raw assistanttext into renderable design-system output, and every later generative-UI slice (FB5's recursive
JSON renderer, the eventual chat page assembly) parses through this seam rather than re-inventing
block detection.
Problem / motivation
Today there is no supported path from an LLM response to a rendered chart, donut, table, stats
block, or line chart in fresh-ui. eis-chat's prior art hand-rolls markdown-fence sniffing and
string-templates the result straight into JSX per call site, with no shared parser, no reload
guarantee, and no seam other pages can reuse. Doctrine requires generative UI to be owned by
fresh-ui's curated vocabulary (not ad-hoc per-app parsing), and FB0 only registers the primitives —
nothing yet turns model output into instances of them. Without this slice, FB0's primitives are
inert copy-paste components with no ingestion path.
Scope
Ships
packages/fresh-ui/src/chat/parse-blocks.ts(or equivalentsrc/chat/module) exporting:parseBlocks(input: string): RenderPart[]blockToText(part: RenderPart): string(the inverse projection used for the reload check andfor plain-text fallback/export)
RenderPartdiscriminated union (chart,donut,table,stats,line, and atextfallback member for non-fenced prose)
chart,donut,table,stats, ```line info-strings,parsing their body (JSON or the minimal DSL agreed with FA1's message contract) into the
corresponding DS primitive's prop shape (
ChartDatum[], table rows/columns, stats entries).parseBlocks(blockToText(parseBlocks(input)))produces aRenderPart[]deep-equal to the first parse, for every fixture covering the five block kinds plus mixed
text+block input.
packages/fresh-ui/tests/chat/parse-blocks.test.tscovering each block kind,malformed-fence fallback to
text, and the roundtrip-fidelity property.Out of scope
aicollection — owned by FB0.RenderPart[]into a chat page/island — deferred to a later fresh-ui chat-pageslice, not this one.
Public surface
packages/fresh-ui/src/chat/):parseBlocks,blockToText,RenderPart, and the per-kind part interfaces(
ChartRenderPart,DonutRenderPart,TableRenderPart,StatsRenderPart,LineRenderPart,TextRenderPart).chat-render(kindlib,copyOwnership: app-owned-after-copy) addedto the
aicollection created by FB0, withfilespointing at the shipped module(s) anddependencieson the existing chart/donut/table/stats primitive items.Acceptance & fitness gates
@module+ JSDoc on every exported symbol;parse-blocks.tsmodexport count stays at or under the 20-export limit.
deno task publish:dry-runsucceeds for@netscript/fresh-uiWITHOUT--allow-slow-typesafter this slice lands.deno task arch:checkpasses — the parser stays a pure domain/application module with nonew adapter-layer or
@netscript/*runtime dependency.parseBlocks -> blockToText -> parseBlocksyieldsa deep-equal
RenderPart[]for chart, donut, table, stats, and line blocks, plus one mixedtext+multi-block fixture.
deno task checkanddeno task testpass scoped topackages/fresh-ui.netscript ui:add ai(registered by FB0) resolves and includeschat-renderalongside thechat primitive items it depends on.
Dependencies
Depends on FB0 (chat-primitive manifest registration,
aicollection) and FA1 (fresh/aimessage/part contract) — GitHub numbers tracked in the #238 DAG checklist. Not blocked by #239.
Size
M