Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions apps/web/app/(main)/docs/api/core/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -273,8 +273,8 @@ compiler.reset();`}</Code>
</p>
<Code lang="typescript">{`import { compileSpecStream } from '@json-render/core';

const jsonl = \`{"op":"set","path":"/root","value":{}}
{"op":"set","path":"/root/type","value":"Card"}\`;
const jsonl = \`{"op":"add","path":"/root","value":{}}
{"op":"add","path":"/root/type","value":"Card"}\`;

const spec = compileSpecStream<MySpec>(jsonl);`}</Code>

Expand All @@ -285,22 +285,36 @@ const spec = compileSpecStream<MySpec>(jsonl);`}</Code>
} from '@json-render/core';

// Parse a single line
const patch = parseSpecStreamLine('{"op":"set","path":"/root","value":{}}');
const patch = parseSpecStreamLine('{"op":"add","path":"/root","value":{}}');

// Apply patch to object (mutates in place)
const obj = {};
applySpecStreamPatch(obj, patch);`}</Code>

<h3 className="text-lg font-semibold mt-8 mb-4">SpecStream Types</h3>
<p className="text-sm text-muted-foreground mb-4">
Fully compliant with{" "}
<a
href="https://datatracker.ietf.org/doc/html/rfc6902"
className="underline"
target="_blank"
rel="noopener noreferrer"
>
RFC 6902
</a>
:
</p>
<Code lang="typescript">{`interface SpecStreamLine {
op: 'set' | 'add' | 'replace' | 'remove';
op: 'add' | 'remove' | 'replace' | 'move' | 'copy' | 'test';
path: string;
value?: unknown;
value?: unknown; // Required for add, replace, test
from?: string; // Required for move, copy
}

interface SpecStreamCompiler<T> {
push(chunk: string): { result: T; newPatches: SpecStreamLine[] };
getResult(): T;
getPatches(): SpecStreamLine[];
reset(): void;
}`}</Code>

Expand Down
53 changes: 37 additions & 16 deletions apps/web/app/(main)/docs/streaming/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ export default function StreamingPage() {
format where each line is a JSON patch operation that progressively
builds your spec:
</p>
<Code lang="json">{`{"op":"set","path":"/root","value":"root"}
{"op":"set","path":"/elements/root","value":{"type":"Card","props":{"title":"Dashboard"},"children":["metric-1","metric-2"]}}
{"op":"set","path":"/elements/metric-1","value":{"type":"Metric","props":{"label":"Revenue"}}}
{"op":"set","path":"/elements/metric-2","value":{"type":"Metric","props":{"label":"Users"}}}`}</Code>
<Code lang="json">{`{"op":"add","path":"/root","value":"root"}
{"op":"add","path":"/elements/root","value":{"type":"Card","props":{"title":"Dashboard"},"children":["metric-1","metric-2"]}}
{"op":"add","path":"/elements/metric-1","value":{"type":"Metric","props":{"label":"Revenue"}}}
{"op":"add","path":"/elements/metric-2","value":{"type":"Metric","props":{"label":"Users"}}}`}</Code>

<h2 className="text-xl font-semibold mt-12 mb-4">useUIStream Hook</h2>
<p className="text-sm text-muted-foreground mb-4">
Expand All @@ -43,26 +43,47 @@ function App() {
});
}`}</Code>

<h2 className="text-xl font-semibold mt-12 mb-4">Patch Operations</h2>
<h2 className="text-xl font-semibold mt-12 mb-4">
Patch Operations (RFC 6902)
</h2>
<p className="text-sm text-muted-foreground mb-4">
Supported operations:
SpecStream uses{" "}
<a
href="https://datatracker.ietf.org/doc/html/rfc6902"
className="underline"
target="_blank"
rel="noopener noreferrer"
>
RFC 6902 JSON Patch
</a>{" "}
operations:
</p>
<ul className="list-disc list-inside text-sm text-muted-foreground space-y-2 mb-4">
<li>
<code className="text-foreground">set</code> — Set the value at a path
(creates if needed)
<code className="text-foreground">add</code> — Add a value at a path
(creates or replaces for objects, inserts for arrays)
</li>
<li>
<code className="text-foreground">remove</code> — Remove the value at
a path
</li>
<li>
<code className="text-foreground">replace</code> — Replace an existing
value at a path
</li>
<li>
<code className="text-foreground">add</code> — Add to an array at a
path
<code className="text-foreground">move</code> — Move a value from one
path to another (requires{" "}
<code className="text-foreground">from</code>)
</li>
<li>
<code className="text-foreground">replace</code> — Replace value at a
path
<code className="text-foreground">copy</code> — Copy a value from one
path to another (requires{" "}
<code className="text-foreground">from</code>)
</li>
<li>
<code className="text-foreground">remove</code> — Remove value at a
path
<code className="text-foreground">test</code> — Assert that a value at
a path equals the given value
</li>
</ul>

Expand Down Expand Up @@ -170,8 +191,8 @@ async function processStream(reader: ReadableStreamDefaultReader) {
</p>
<Code lang="typescript">{`import { compileSpecStream } from '@json-render/core';

const jsonl = \`{"op":"set","path":"/root","value":{"type":"Card"}}
{"op":"set","path":"/root/props","value":{"title":"Hello"}}\`;
const jsonl = \`{"op":"add","path":"/root","value":{"type":"Card"}}
{"op":"add","path":"/root/props","value":{"title":"Hello"}}\`;

const spec = compileSpecStream<MySpec>(jsonl);
// { root: { type: "Card", props: { title: "Hello" } } }`}</Code>
Expand Down
5 changes: 3 additions & 2 deletions apps/web/app/api/generate/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,9 @@ USER REQUEST: ${sanitizedPrompt}
IMPORTANT: The current UI is already loaded. Output ONLY the patches needed to make the requested change:
- To add a new element: {"op":"add","path":"/elements/new-key","value":{...}}
- To modify an existing element: {"op":"set","path":"/elements/existing-key","value":{...}}
- To update the root: {"op":"set","path":"/root","value":"new-root-key"}
- To modify an existing element: {"op":"replace","path":"/elements/existing-key","value":{...}}
- To remove an element: {"op":"remove","path":"/elements/old-key"}
- To update the root: {"op":"replace","path":"/root","value":"new-root-key"}
- To add children: update the parent element with new children array
DO NOT output patches for elements that don't need to change. Only output what's necessary for the requested modification.`;
Expand Down
2 changes: 1 addition & 1 deletion apps/web/components/demo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const SIMULATION_STAGES: SimulationStage[] = [
},
},
},
stream: '{"op":"set","path":"/root","value":"card"}',
stream: '{"op":"add","path":"/root","value":"card"}',
},
{
tree: {
Expand Down
2 changes: 1 addition & 1 deletion examples/remotion/tsconfig.tsbuildinfo

Large diffs are not rendered by default.

14 changes: 8 additions & 6 deletions packages/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,16 @@ while (streaming) {
const finalSpec = compiler.getResult();
```

SpecStream format (each line is a JSON patch):
SpecStream format uses [RFC 6902 JSON Patch](https://datatracker.ietf.org/doc/html/rfc6902) operations (each line is a patch):

```jsonl
{"op":"set","path":"/root/type","value":"Card"}
{"op":"set","path":"/root/props","value":{"title":"Hello"}}
{"op":"set","path":"/root/children/0","value":{"type":"Button","props":{"label":"Click"}}}
{"op":"add","path":"/root/type","value":"Card"}
{"op":"add","path":"/root/props","value":{"title":"Hello"}}
{"op":"add","path":"/root/children/0","value":{"type":"Button","props":{"label":"Click"}}}
```

All six RFC 6902 operations are supported: `add`, `remove`, `replace`, `move`, `copy`, `test`.

### Low-Level Utilities

```typescript
Expand All @@ -134,8 +136,8 @@ import {
} from "@json-render/core";

// Parse a single line
const patch = parseSpecStreamLine('{"op":"set","path":"/root","value":{}}');
// { op: "set", path: "/root", value: {} }
const patch = parseSpecStreamLine('{"op":"add","path":"/root","value":{}}');
// { op: "add", path: "/root", value: {} }

// Apply a patch to an object
const obj = {};
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -493,8 +493,8 @@ export function generateSystemPrompt<
}

// Output format
lines.push("OUTPUT FORMAT (JSONL):");
lines.push('{"op":"set","path":"/root","value":"element-key"}');
lines.push("OUTPUT FORMAT (JSONL, RFC 6902 JSON Patch):");
lines.push('{"op":"add","path":"/root","value":"element-key"}');
lines.push(
'{"op":"add","path":"/elements/key","value":{"type":"...","props":{...},"children":[...]}}',
);
Expand All @@ -504,8 +504,8 @@ export function generateSystemPrompt<
// Rules
lines.push("RULES:");
const baseRules = [
"First line sets /root to root element key",
"Add elements with /elements/{key}",
'First line sets /root to root element key: {"op":"add","path":"/root","value":"<key>"}',
'Add elements with /elements/{key}: {"op":"add","path":"/elements/<key>","value":{...}}',
"Remove elements with op:remove - also update the parent's children array to exclude the removed key",
"Children array contains string keys, not objects",
"Parent first, then children",
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ export {
resolveDynamicValue,
getByPath,
setByPath,
addByPath,
removeByPath,
findFormValue,
// SpecStream - streaming format for building specs
// SpecStream - streaming format for building specs (RFC 6902)
parseSpecStreamLine,
applySpecStreamPatch,
compileSpecStream,
Expand Down
12 changes: 6 additions & 6 deletions packages/core/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -545,10 +545,10 @@ function generatePrompt<TDef extends SchemaDefinition, TCatalog>(
lines.push("");
lines.push("Example output (each line is a separate JSON object):");
lines.push("");
lines.push(`{"op":"set","path":"/root","value":"card-1"}
{"op":"set","path":"/elements/card-1","value":{"type":"Card","props":{"title":"Dashboard"},"children":["metric-1","chart-1"]}}
{"op":"set","path":"/elements/metric-1","value":{"type":"Metric","props":{"label":"Revenue","valuePath":"analytics.revenue","format":"currency"},"children":[]}}
{"op":"set","path":"/elements/chart-1","value":{"type":"Chart","props":{"type":"bar","dataPath":"analytics.salesByRegion"},"children":[]}}`);
lines.push(`{"op":"add","path":"/root","value":"card-1"}
{"op":"add","path":"/elements/card-1","value":{"type":"Card","props":{"title":"Dashboard"},"children":["metric-1","chart-1"]}}
{"op":"add","path":"/elements/metric-1","value":{"type":"Metric","props":{"label":"Revenue","valuePath":"analytics.revenue","format":"currency"},"children":[]}}
{"op":"add","path":"/elements/chart-1","value":{"type":"Chart","props":{"type":"bar","dataPath":"analytics.salesByRegion"},"children":[]}}`);
lines.push("");

// Components section
Expand Down Expand Up @@ -591,8 +591,8 @@ function generatePrompt<TDef extends SchemaDefinition, TCatalog>(
lines.push("RULES:");
const baseRules = [
"Output ONLY JSONL patches - one JSON object per line, no markdown, no code fences",
'First line sets root: {"op":"set","path":"/root","value":"<root-key>"}',
'Then add each element: {"op":"set","path":"/elements/<key>","value":{...}}',
'First line sets root: {"op":"add","path":"/root","value":"<root-key>"}',
'Then add each element: {"op":"add","path":"/elements/<key>","value":{...}}',
"ONLY use components listed above",
"Each element value needs: type, props, children (array of child keys)",
"Use unique keys for the element map entries (e.g., 'header', 'metric-1', 'chart-revenue')",
Expand Down
Loading