-
Notifications
You must be signed in to change notification settings - Fork 653
Description
Problem
parseSpecStreamLine in @json-render/core silently drops any line where JSON.parse fails, with no attempt at recovery:
function parseSpecStreamLine(line) {
const trimmed = line.trim();
if (!trimmed || !trimmed.startsWith("{")) return null;
try {
const patch = JSON.parse(trimmed);
if (patch.op && patch.path !== void 0) {
return patch;
}
return null;
} catch {
return null; // ← silently dropped, no recovery
}
}Since this package is specifically designed to parse AI-generated JSONL output, it should be resilient to the most common LLM generation error: brace miscounting.
Real-world example
When asking Claude Sonnet 4.6 to generate a dashboard with MetricTile, BarChart, PieChart, and DataTable components (all using dataSource with a nested {sql: "..."} object), 7 out of 12 spec lines were generated with exactly one extra trailing }:
// What the model generated (INVALID — extra closing brace):
{"op":"add","path":"/elements/metric-payments","value":{"type":"MetricTile","props":{"title":"Payments Collected","format":"currency","dataSource":{"sql":"SELECT ..."}}},"children":[]}}
// What it should have generated (VALID):
{"op":"add","path":"/elements/metric-payments","value":{"type":"MetricTile","props":{"title":"Payments Collected","format":"currency","dataSource":{"sql":"SELECT ..."}},"children":[]}}
The pattern is consistent: every element with a dataSource property (which adds an extra level of {} nesting inside props) confuses the model's brace tracking. The children field ends up outside the value object with an extra } appended.
Result: Only the page header card rendered. All 4 metric tiles, both charts, and the data table were silently dropped — making the dashboard appear completely broken with no error feedback.
Suggested fix
Add a simple recovery step when JSON.parse fails — try trimming trailing } characters one at a time:
function parseSpecStreamLine(line) {
const trimmed = line.trim();
if (!trimmed || !trimmed.startsWith("{")) return null;
try {
const patch = JSON.parse(trimmed);
if (patch.op && patch.path !== void 0) return patch;
return null;
} catch {
// Recovery: try removing extra trailing braces (common LLM error)
let attempt = trimmed;
while (attempt.endsWith("}")) {
attempt = attempt.slice(0, -1);
try {
const patch = JSON.parse(attempt);
if (patch.op && patch.path !== void 0) return patch;
} catch {
continue;
}
}
return null;
}
}This would have recovered all 7 broken lines in the example above. A more comprehensive approach could also handle missing trailing braces (try appending }) or other common malformations.
Additional suggestion
Consider emitting a warning or callback when recovery is needed (or when a line is ultimately dropped), so developers can observe generation quality and debug rendering issues more easily. Currently the silent return null makes these failures invisible.
Environment
@json-render/core@0.12.0- Model: Claude Sonnet 4.6 via Cloudflare AI Gateway
- Streaming mode with
createMixedStreamParser