vercel-labs / vercel-labs/json-render

Documentation mismatch: useUIStream expects JSONL patch format but API route example outputs plain JSON

Open
#42 2 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
16.8k
Forks
901
Avg merge
3h 14m
Merged PRs (30d)
4

Description

Problem

The documentation for API route setup in AI SDK Integration and Streaming shows a simple example that directly returns result.textStream:

// From docs (https://json-render.dev/docs/ai-sdk)
export async function POST(req: Request) {
  const { prompt } = await req.json();
  const systemPrompt = generateCatalogPrompt(catalog);

  const result = streamText({
    model: 'anthropic/claude-haiku-4.5',
    system: systemPrompt,
    prompt,
  });

  return new Response(result.textStream, {
    headers: { 'Content-Type': 'text/plain; charset=utf-8' },
  });
}

I followed this pattern with a minor difference (using @ai-sdk/openai provider instead of AI Gateway string format):

// My implementation
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { generateCatalogPrompt } from '@json-render/core';
import { catalog } from '@/lib/catalog';

export async function POST(req: Request) {
  const { prompt } = await req.json();
  const systemPrompt = generateCatalogPrompt(catalog);

  const result = streamText({
    model: openai('gpt-5.2'),  // Using @ai-sdk/openai provider
    system: systemPrompt,
    prompt,
  });

  return new Response(result.textStream, {
    headers: { 'Content-Type': 'text/plain; charset=utf-8' },
  });
}

Actual Output

The AI model outputs nested JSON (not JSONL patch format):

{
  "type": "Card",
  "props": {
    "title": "Claim Form"
  },
  "children": [
    {
      "type": "Text",
      "props": {
        "text": "Please review your details and submit your claim."
      }
    },
    {
      "type": "Button",
      "props": {
        "text": "Submit Claim",
        "action": {
          "type": "submit"
        }
      }
    }
  ]
}

Expected Format by useUIStream

However, looking at the source code of useUIStream, it expects JSONL patch format (one JSON patch per line):

{"op":"set","path":"/root","value":"el-1"}
{"op":"add","path":"/elements/el-1","value":{"key":"el-1","type":"Card","props":{...}}}
{"op":"add","path":"/elements/el-2","value":{"key":"el-2","type":"Text","props":{...},"parentKey":"el-1"}}

The hook parses each line as a patch operation:

// From @json-render/react source
const lines = buffer.split("\n");
for (const line of lines) {
  const patch = parsePatchLine(line);
  if (patch) {
    currentTree = applyPatch(currentTree, patch);
  }
}

Result

When following the documentation example:

  1. AI model outputs nested JSON (as shown above)
  2. useUIStream tries to parse each line as a JSON patch
  3. Parsing fails because the format doesn't match
  4. tree remains { root: "", elements: {} } (empty)
  5. Nothing renders on the page

Environment

  • @json-render/core: ^0.3.0
  • @json-render/react: ^0.2.0
  • ai: ^6.0.44
  • @ai-sdk/openai: latest

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Compare the API route examples in the linked AI SDK Integration and Streaming documentation with the useUIStream parsing behavior in @json-render/react, especially parsePatchLine. Update the examples so the documented response format matches what the hook consumes, then verify that the resulting stream produces a rendered tree rather than an empty one.

Written by the indexing model from the issue text.

Assessment

Tech stack
react, typescript
Domain
documentation, frontend
Issue type
Documentation
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.