Chris Tate 4 месяцев назад
Родитель
Сommit
0dfe07da45

+ 3 - 3
README.md

@@ -22,7 +22,7 @@ json-render is a **Generative UI** framework: AI generates interfaces from natur
 - **Predictable** - JSON output matches your schema, every time
 - **Fast** - Stream and render progressively as the model responds
 - **Cross-Platform** - React (web) and React Native (mobile) from the same catalog
-- **Batteries Included** - 30+ pre-built shadcn/ui components ready to use
+- **Batteries Included** - 36 pre-built shadcn/ui components ready to use
 
 ## Quick Start
 
@@ -108,7 +108,7 @@ function Dashboard({ spec }) {
 |---------|-------------|
 | `@json-render/core` | Schemas, catalogs, AI prompts, dynamic props, SpecStream utilities |
 | `@json-render/react` | React renderer, contexts, hooks |
-| `@json-render/shadcn` | 30+ pre-built shadcn/ui components (Radix UI + Tailwind CSS) |
+| `@json-render/shadcn` | 36 pre-built shadcn/ui components (Radix UI + Tailwind CSS) |
 | `@json-render/react-native` | React Native renderer with standard mobile components |
 | `@json-render/remotion` | Remotion video renderer, timeline schema |
 
@@ -150,7 +150,7 @@ import { schema, defineRegistry, Renderer } from "@json-render/react";
 import { shadcnComponentDefinitions } from "@json-render/shadcn/catalog";
 import { shadcnComponents } from "@json-render/shadcn";
 
-// Pick components from the 30+ standard definitions
+// Pick components from the 36 standard definitions
 const catalog = defineCatalog(schema, {
   components: {
     Card: shadcnComponentDefinitions.Card,

+ 26 - 0
packages/core/README.md

@@ -157,6 +157,14 @@ const spec = compileSpecStream<MySpec>(jsonlString);
 | `defineSchema(builder, options?)` | Create a schema with spec/catalog structure |
 | `SchemaBuilder` | Builder with `s.object()`, `s.array()`, `s.map()`, etc. |
 
+Schema options:
+
+| Option | Purpose |
+|--------|---------|
+| `promptTemplate` | Custom AI prompt generator |
+| `defaultRules` | Default rules injected before custom rules in prompts |
+| `builtInActions` | Actions always available at runtime, auto-injected into prompts (e.g. `setState`) |
+
 ### Catalog
 
 | Export | Purpose |
@@ -196,12 +204,30 @@ const spec = compileSpecStream<MySpec>(jsonlString);
 | `autoFixSpec(spec)` | Auto-fix common spec issues (returns corrected copy) |
 | `formatSpecIssues(issues)` | Format validation issues as readable strings |
 
+### Actions
+
+| Export | Purpose |
+|--------|---------|
+| `ActionBinding` | Action binding with `action`, `params`, `confirm`, `preventDefault`, etc. |
+| `BuiltInAction` | Built-in action definition with `name` and `description` |
+
+### Chat Mode (Mixed Streams)
+
+| Export | Purpose |
+|--------|---------|
+| `createJsonRenderTransform()` | TransformStream that separates text from JSONL patches in a mixed stream |
+| `pipeJsonRender()` | Server-side helper to pipe a mixed stream through the transform |
+| `SPEC_DATA_PART` / `SPEC_DATA_PART_TYPE` | Constants for filtering spec data parts |
+
+The transform splits text blocks around spec data by emitting `text-end`/`text-start` pairs, ensuring the AI SDK creates separate text parts and preserving correct interleaving of prose and UI in `message.parts`.
+
 ### Types
 
 | Export | Purpose |
 |--------|---------|
 | `Spec` | Base spec type |
 | `Catalog` | Catalog type |
+| `BuiltInAction` | Built-in action type (`name` + `description`) |
 | `VisibilityCondition` | Visibility condition type (used by `$cond`) |
 | `VisibilityContext` | Context for evaluating visibility and prop expressions |
 | `SpecStreamLine` | Single patch operation |

+ 67 - 2
packages/react/README.md

@@ -51,6 +51,8 @@ export const catalog = defineCatalog(schema, {
 
 ### 2. Define Component Implementations
 
+`defineRegistry` conditionally requires the `actions` field only when the catalog declares actions. Catalogs with `actions: {}` can omit it entirely.
+
 ```tsx
 import { defineRegistry, useBoundProp } from "@json-render/react";
 import { catalog } from "./catalog";
@@ -297,7 +299,7 @@ See [@json-render/core](../core/README.md) for full expression syntax.
 
 ## Built-in Actions
 
-The `setState`, `pushState`, and `removeState` actions are handled automatically by `ActionProvider`. They update the state model, which triggers re-evaluation of visibility conditions and dynamic prop expressions:
+The `setState`, `pushState`, and `removeState` actions are built into the React schema and handled automatically by `ActionProvider`. They are injected into AI prompts without needing to be declared in your catalog's `actions`. They update the state model, which triggers re-evaluation of visibility conditions and dynamic prop expressions:
 
 ```json
 {
@@ -321,14 +323,52 @@ When using `defineRegistry`, components receive these props:
 interface ComponentContext<P> {
   props: P;                    // Typed props from the catalog (expressions resolved)
   children?: React.ReactNode;  // Rendered children
-  emit?: (event: string) => void;  // Emit a named event
+  emit: (event: string) => void;   // Emit a named event (always defined)
+  on: (event: string) => EventHandle; // Get event handle with metadata
   loading?: boolean;           // Whether the parent is loading
   bindings?: Record<string, string>;  // State paths for $bindState/$bindItem expressions (e.g. bindings.value)
 }
+
+interface EventHandle {
+  emit: () => void;            // Fire the event
+  shouldPreventDefault: boolean; // Whether any binding requested preventDefault
+  bound: boolean;              // Whether any handler is bound
+}
+```
+
+Use `emit("press")` for simple event firing. Use `on("click")` when you need to check metadata like `shouldPreventDefault` or `bound`:
+
+```tsx
+Link: ({ props, on }) => {
+  const click = on("click");
+  return (
+    <a
+      href={props.href}
+      onClick={(e) => {
+        if (click.shouldPreventDefault) e.preventDefault();
+        click.emit();
+      }}
+    >
+      {props.label}
+    </a>
+  );
+},
 ```
 
 Use `bindings?.value`, `bindings?.checked`, etc. with `useBoundProp()` for two-way bound form components.
 
+### `BaseComponentProps`
+
+For building reusable component libraries that are not tied to a specific catalog (e.g. `@json-render/shadcn`), use the catalog-agnostic `BaseComponentProps` type:
+
+```typescript
+import type { BaseComponentProps } from "@json-render/react";
+
+const Card = ({ props, children }: BaseComponentProps<{ title?: string }>) => (
+  <div>{props.title}{children}</div>
+);
+```
+
 ## Generate AI Prompts
 
 ```typescript
@@ -374,3 +414,28 @@ function App() {
   return <Renderer spec={spec} registry={registry} />;
 }
 ```
+
+## Key Exports
+
+| Export | Purpose |
+|--------|---------|
+| `defineRegistry` | Create a type-safe component registry from a catalog |
+| `Renderer` | Render a spec using a registry |
+| `schema` | Element tree schema (includes built-in actions: `setState`, `pushState`, `removeState`) |
+| `useStateStore` | Access state context |
+| `useStateValue` | Get single value from state |
+| `useBoundProp` | Two-way binding for `$bindState`/`$bindItem` expressions |
+| `useActions` | Access actions context |
+| `useAction` | Get a single action dispatch function |
+| `useUIStream` | Stream specs from an API endpoint |
+
+### Types
+
+| Export | Purpose |
+|--------|---------|
+| `ComponentContext` | Typed component render function context (catalog-aware) |
+| `BaseComponentProps` | Catalog-agnostic base type for reusable component libraries |
+| `EventHandle` | Event handle with `emit()`, `shouldPreventDefault`, `bound` |
+| `ComponentFn` | Component render function type |
+| `SetState` | State setter type |
+| `StateModel` | State model type |

+ 1 - 1
packages/shadcn/README.md

@@ -1,6 +1,6 @@
 # @json-render/shadcn
 
-Pre-built [shadcn/ui](https://ui.shadcn.com/) components for json-render. Drop-in catalog definitions and React implementations for 30+ components built on Radix UI + Tailwind CSS.
+Pre-built [shadcn/ui](https://ui.shadcn.com/) components for json-render. Drop-in catalog definitions and React implementations for 36 components built on Radix UI + Tailwind CSS.
 
 ## Installation
 

+ 17 - 0
skills/json-render-core/SKILL.md

@@ -157,6 +157,20 @@ visibility.always                        // true
 visibility.never                         // false
 ```
 
+## Built-in Actions in Schema
+
+Schemas can declare `builtInActions` -- actions that are always available at runtime and auto-injected into prompts:
+
+```typescript
+const schema = defineSchema(builder, {
+  builtInActions: [
+    { name: "setState", description: "Update a value in the state model" },
+  ],
+});
+```
+
+These appear in prompts as `[built-in]` and don't require handlers in `defineRegistry`.
+
 ## Key Exports
 
 | Export | Purpose |
@@ -169,5 +183,8 @@ visibility.never                         // false
 | `validateSpec` | Validate spec structure |
 | `autoFixSpec` | Auto-fix common spec issues |
 | `createSpecStreamCompiler` | Stream JSONL patches into spec |
+| `createJsonRenderTransform` | TransformStream separating text from JSONL in mixed streams |
 | `parseSpecStreamLine` | Parse single JSONL line |
 | `applySpecStreamPatch` | Apply patch to object |
+| `BuiltInAction` | Type for built-in action definitions (`name` + `description`) |
+| `ActionBinding` | Action binding type (includes `preventDefault` field) |

+ 39 - 5
skills/json-render-react/SKILL.md

@@ -118,13 +118,24 @@ Components receive already-resolved props. For two-way bound props, use the `use
 
 ## Event System
 
-Components use `emit` to fire named events. The element's `on` field maps events to action bindings:
+Components use `emit` to fire named events, or `on()` to get an event handle with metadata. The element's `on` field maps events to action bindings:
 
 ```tsx
-// Component emits a named event
+// Simple event firing
 Button: ({ props, emit }) => (
   <button onClick={() => emit("press")}>{props.label}</button>
 ),
+
+// Event handle with metadata (e.g. preventDefault)
+Link: ({ props, on }) => {
+  const click = on("click");
+  return (
+    <a href={props.href} onClick={(e) => {
+      if (click.shouldPreventDefault) e.preventDefault();
+      click.emit();
+    }}>{props.label}</a>
+  );
+},
 ```
 
 ```json
@@ -135,12 +146,16 @@ Button: ({ props, emit }) => (
 }
 ```
 
+The `EventHandle` returned by `on()` has: `emit()`, `shouldPreventDefault` (boolean), and `bound` (boolean).
+
 ## Built-in Actions
 
-The `setState` action is handled automatically by `ActionProvider` and updates the state model directly, which re-evaluates visibility conditions and dynamic prop expressions:
+The `setState`, `pushState`, and `removeState` actions are built into the React schema and handled automatically by `ActionProvider`. They are injected into AI prompts without needing to be declared in catalog `actions`:
 
 ```json
-{ "action": "setState", "actionParams": { "statePath": "/activeTab", "value": "home" } }
+{ "action": "setState", "params": { "statePath": "/activeTab", "value": "home" } }
+{ "action": "pushState", "params": { "statePath": "/items", "value": { "text": "New" } } }
+{ "action": "removeState", "params": { "statePath": "/items", "index": 0 } }
 ```
 
 Note: `statePath` in action params (e.g. `setState.statePath`) targets the mutation path. Two-way binding in component props uses `{ "$bindState": "/path" }` on the value prop, not `statePath`.
@@ -168,16 +183,35 @@ Input: ({ element, bindings }) => {
 
 `useBoundProp(propValue, bindingPath)` returns `[value, setValue]`. The `value` is the resolved prop; `setValue` writes back to the bound state path (no-op if not bound).
 
+## BaseComponentProps
+
+For building reusable component libraries not tied to a specific catalog (e.g. `@json-render/shadcn`):
+
+```typescript
+import type { BaseComponentProps } from "@json-render/react";
+
+const Card = ({ props, children }: BaseComponentProps<{ title?: string }>) => (
+  <div>{props.title}{children}</div>
+);
+```
+
+## defineRegistry
+
+`defineRegistry` conditionally requires the `actions` field only when the catalog declares actions. Catalogs with `actions: {}` can omit it.
+
 ## Key Exports
 
 | Export | Purpose |
 |--------|---------|
 | `defineRegistry` | Create a type-safe component registry from a catalog |
 | `Renderer` | Render a spec using a registry |
-| `schema` | Element tree schema |
+| `schema` | Element tree schema (includes built-in state actions) |
 | `useStateStore` | Access state context |
 | `useStateValue` | Get single value from state |
 | `useBoundProp` | Two-way binding for `$bindState`/`$bindItem` expressions |
 | `useActions` | Access actions context |
 | `useAction` | Get a single action dispatch function |
 | `useUIStream` | Stream specs from an API endpoint |
+| `BaseComponentProps` | Catalog-agnostic base type for reusable component libraries |
+| `EventHandle` | Event handle type (`emit`, `shouldPreventDefault`, `bound`) |
+| `ComponentContext` | Typed component context (catalog-aware) |

+ 1 - 1
skills/json-render-shadcn/SKILL.md

@@ -5,7 +5,7 @@ description: Pre-built shadcn/ui components for json-render. Use when working wi
 
 # @json-render/shadcn
 
-Pre-built shadcn/ui component definitions and implementations for json-render. Provides 30+ components built on Radix UI + Tailwind CSS.
+Pre-built shadcn/ui component definitions and implementations for json-render. Provides 36 components built on Radix UI + Tailwind CSS.
 
 ## Two Entry Points