From d4ed861d88c80290868a8b2cc7724d86a9bf61ce Mon Sep 17 00:00:00 2001 From: Shinyaigeek Date: Tue, 14 Jul 2026 11:49:50 +0900 Subject: [PATCH] feat(react-ui): allow rich content components in StepsItem details MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit StepsItem's details prop was typed z.string() in the genui schema, so LLMs could only express procedural steps as a single markdown string — no CodeBlock, Image, or other content components, even though the runtime and the React component layer (details: React.ReactNode) already handle them. Widen the schema to z.union([z.string(), z.array(StepsItemDetailsChildUnion)]) where the new union allows TextContent, MarkDownRenderer, CodeBlock, Image, and ImageBlock. The union is intentionally narrower than SectionContentChildUnion: procedural steps need prose, code, and images, and referencing Steps from its own item schema would be circular. String details keep rendering through MarkDownRenderer, so existing programs are unaffected; the renderer's existing renderNode branch already renders element arrays. Co-Authored-By: Claude Fable 5 --- .../react-ui/src/genui-lib/Steps/index.tsx | 7 ++++--- .../react-ui/src/genui-lib/Steps/schema.ts | 19 ++++++++++++++++++- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/react-ui/src/genui-lib/Steps/index.tsx b/packages/react-ui/src/genui-lib/Steps/index.tsx index f31681b48..ef84e1183 100644 --- a/packages/react-ui/src/genui-lib/Steps/index.tsx +++ b/packages/react-ui/src/genui-lib/Steps/index.tsx @@ -7,12 +7,13 @@ import { MarkDownRenderer } from "../../components/MarkDownRenderer"; import { Steps as OpenUISteps, StepsItem as OpenUIStepsItem } from "../../components/Steps"; import { StepsItemSchema } from "./schema"; -export { StepsItemSchema } from "./schema"; +export { StepsItemDetailsChildUnion, StepsItemSchema } from "./schema"; export const StepsItem = defineComponent({ name: "StepsItem", props: StepsItemSchema, - description: "title and details text for one step", + description: + "One step: title, plus details as markdown text or an array of content components (e.g. CodeBlock, Image)", component: () => null, }); @@ -32,7 +33,7 @@ export const Steps = defineComponent({ typeof details === "string" ? ( ) : ( - (renderNode(details) as React.ReactElement) + (renderNode(details) as React.ReactNode) ); return ( diff --git a/packages/react-ui/src/genui-lib/Steps/schema.ts b/packages/react-ui/src/genui-lib/Steps/schema.ts index 312b68c59..8d82a598d 100644 --- a/packages/react-ui/src/genui-lib/Steps/schema.ts +++ b/packages/react-ui/src/genui-lib/Steps/schema.ts @@ -1,6 +1,23 @@ import { z } from "zod/v4"; +import { CodeBlock } from "../CodeBlock"; +import { Image } from "../Image"; +import { ImageBlock } from "../ImageBlock"; +import { MarkDownRenderer } from "../MarkDownRenderer"; +import { TextContent } from "../TextContent"; + +// Content allowed inside a StepsItem's details. +// Kept narrower than SectionContentChildUnion: procedural steps need prose, +// code, and images — and referencing Steps here would be circular. +export const StepsItemDetailsChildUnion = z.union([ + TextContent.ref, + MarkDownRenderer.ref, + CodeBlock.ref, + Image.ref, + ImageBlock.ref, +]); + export const StepsItemSchema = z.object({ title: z.string(), - details: z.string(), + details: z.union([z.string(), z.array(StepsItemDetailsChildUnion)]), });