json-ui
Version: 1.0.0 | Last Updated: 2026-01-29
You are an expert at the json-ui package — a JSON-to-HTML report renderer with React component support, bilingual i18n, and a CLI tool. Help users by:
- Writing components: Add new component types following existing patterns
- Rendering reports: Generate HTML from JSON report definitions
- Debugging: Fix rendering, build, or i18n issues
- Answering questions: Explain architecture, component catalog, data flow
Quick Reference
| Task |
File |
Pattern |
| Define component schema |
src/catalog.ts |
Add Zod schema + export in catalog object |
| Render component (HTML) |
src/cli.ts |
Add case in renderNode() switch |
| Render component (React) |
src/components/index.tsx |
Export React FC using catalog types |
| Add i18n text |
Any JSON |
{ "en": "Hello", "zh": "你好" } or plain "Hello" |
| Build |
terminal |
pnpm build (uses tsup, outputs ESM + DTS) |
| Render report |
terminal |
json-ui render report.json [-o out.html] [--no-open] |
Documentation
Refer to local source files for detailed documentation:
packages/json-ui/src/catalog.ts - All Zod schemas and type definitions
packages/json-ui/src/cli.ts - HTML renderer and CLI entry point
packages/json-ui/src/components/index.tsx - React component implementations
IMPORTANT: Documentation Completeness Check
Before answering questions, Claude MUST:
- Read the relevant source file(s) listed above
- If file read fails: Inform user "本地文档不完整,建议更新"
- Still answer based on SKILL.md patterns + built-in knowledge
Architecture
JSON Report Format
Reports are trees of nodes:
{
"type": "Report",
"props": { "title": "My Report", "theme": "auto" },
"children": [
{
"type": "Section",
"props": { "title": "Overview", "icon": "bulb" },
"children": [
{ "type": "Abstract", "props": { "text": "..." } }
]
}
]
}
Three Rendering Layers
| Layer |
File |
Output |
Use Case |
| Zod Schemas |
catalog.ts |
Type definitions |
Validation, type safety |
| HTML Renderer |
cli.ts |
Static HTML string |
CLI render command |
| React Components |
components/index.tsx |
React elements |
Embedded usage |
Data Flow
JSON file → CLI parse → renderNode() recursion → HTML string → file write → browser open
Component Catalog (38 types)
Layout
| Component |
Key Props |
Description |
Report |
title?, theme |
Root wrapper, 800px max-width |
Section |
title, icon?, collapsible? |
Collapsible section with header |
Grid |
cols, gap |
CSS grid layout |
Card |
variant, padding, shadow |
Card container |
Paper Info
| Component |
Key Props |
Description |
PaperHeader |
title, arxivId, date, categories? |
Paper title + metadata |
AuthorList |
authors, layout?, maxVisible? |
Author names + affiliations |
Abstract |
text, highlights?, maxLength? |
Abstract with keyword highlighting |
TagList |
tags, variant |
Tag/category pills |
Content
| Component |
Key Props |
Description |
ContributionList |
items, numbered? |
Numbered contributions with badges |
MethodOverview |
steps, showConnectors? |
Step-by-step method pipeline |
Highlight |
text, type, source? |
Blockquote (quote/important/warning/code) |
KeyPoint |
icon, title, description |
Icon + title + description |
CodeBlock |
code, language, showLineNumbers? |
Syntax-highlighted code |
Prose |
content |
Markdown content block |
Callout |
type, title?, content |
Info/tip/warning/important/note box |
Rich Content
| Component |
Key Props |
Description |
Image |
src, alt?, caption?, width? |
Single image |
Figure |
images, caption?, label? |
Multi-image figure |
Formula |
latex, block?, label? |
LaTeX formula |
DefinitionList |
items |
Term-definition pairs |
Theorem |
type, number?, title?, content |
Theorem/lemma/proposition |
Algorithm |
title, steps, caption? |
Algorithm pseudocode |
ResultsTable |
columns, rows, highlights? |
Results with best-cell highlighting |
Data Display
| Component |
Key Props |
Description |
Metric |
label, value, trend?, icon? |
Single metric card |
MetricsGrid |
metrics, cols? |
Grid of metric cards |
Table |
columns, rows, striped?, caption? |
Data table |
Interactive
| Component |
Key Props |
Description |
LinkButton |
href, label, icon?, external? |
Styled link button |
LinkGroup |
links, layout? |
Group of link buttons |
Brand
| Component |
Key Props |
Description |
BrandHeader |
badge?, poweredBy?, showBadge? |
AI-generated badge header |
BrandFooter |
timestamp, attribution?, disclaimer? |
Footer with attribution |
I18n System
Backward-Compatible Bilingual Strings
The I18nString type accepts both plain strings and bilingual objects:
// catalog.ts
export const I18nString = z.union([
z.string(),
z.object({ en: z.string(), zh: z.string() }),
]);
JSON Usage
// Plain string (backward compatible)
{ "title": "Hello World" }
// Bilingual object
{ "title": { "en": "Hello World", "zh": "你好世界" } }
HTML Rendering (cli.ts)
For HTML output, i18n strings render as dual spans:
// renderI18n() outputs:
<span class="i18n-en">Hello</span><span class="i18n-zh">你好</span>
// CSS controls visibility:
html[lang="en"] .i18n-zh { display: none; }
html[lang="zh"] .i18n-en { display: none; }
For HTML attributes (alt, title) where only a plain string works:
// resolveI18n() picks one language:
const alt = resolveI18n(props.alt, 'en'); // returns plain string
React Rendering (components/index.tsx)
// Use <I18nText> component for JSX:
<I18nText value={props.title} />
// Use resolveI18nStr() for plain string contexts:
const altText = resolveI18nStr(props.alt, 'en');
Language Switcher
- Fixed top-right button: EN | 中文
- Toggles
<html lang="en|zh"> attribute
- Persists choice via
localStorage.getItem('json-ui-lang')
Key Patterns
Pattern 1: Adding a New Component
- Define schema in
catalog.ts:
export const MyWidgetSchema = z.object({
label: I18nString, // Use I18nString for user-visible text
count: z.number(), // Use z.string()/z.number() for data
variant: VariantType.default('default'),
});
// Add to catalog object:
export const catalog = {
// ...existing...
MyWidget: MyWidgetSchema,
} as const;
// Export type:
export type MyWidgetProps = z.infer<typeof MyWidgetSchema>;
- Add HTML renderer in
cli.ts renderNode() switch:
case 'MyWidget': {
const { label, count, variant } = props;
return `<div class="my-widget ${variant}">
<span>${renderI18n(label)}</span>
<strong>${escapeHtml(String(count))}</strong>
</div>`;
}
- Add React component in
components/index.tsx:
export const MyWidget: React.FC<MyWidgetProps> = ({ label, count, variant = 'default' }) => (
<div className={`my-widget ${variant}`}>
<span><I18nText value={label} /></span>
<strong>{count}</strong>
</div>
);
Pattern 2: Handling I18n in Special Cases
For text that needs processing (e.g., Abstract highlights):
// HTML (cli.ts) - process each language separately:
if (isI18n(text)) {
return `<span class="i18n-en">${processText(text.en)}</span>
<span class="i18n-zh">${processText(text.zh)}</span>`;
} else {
return processText(String(text));
}
// React (components/index.tsx):
if (typeof text === 'object' && 'en' in text && 'zh' in text) {
return (
<>
<span className="i18n-en" dangerouslySetInnerHTML={{ __html: processText(text.en) }} />
<span className="i18n-zh" dangerouslySetInnerHTML={{ __html: processText(text.zh) }} />
</>
);
}
Common Errors
| Error |
Cause |
Solution |
Type 'I18nStringType' is not assignable to 'ReactNode' |
Passing i18n object directly to JSX |
Wrap with <I18nText value={...} /> |
Property 'length' does not exist on type 'I18nStringType' |
Calling string methods on i18n value |
Use type guard: typeof text === 'string' ? text : text.en |
| Images not loading from arxiv |
crossorigin="anonymous" on <img> |
Remove crossorigin; keep only referrerpolicy="no-referrer" |
| Language switcher not working |
Missing CSS rules or JS |
Ensure html[lang] .i18n-* CSS rules and toggle JS are in template |
| Build fails with type errors |
Schema changed but components not updated |
Update all three files: catalog, cli, components |
CRITICAL: Image Handling
Do NOT use crossorigin="anonymous" on <img> tags.
Sites like arxiv.org do not send CORS headers. Adding crossorigin="anonymous" causes the browser to require CORS, which fails and blocks the image.
<!-- WRONG - breaks images from arxiv and similar sites -->
<img src="..." referrerpolicy="no-referrer" crossorigin="anonymous" />
<!-- CORRECT -->
<img src="..." referrerpolicy="no-referrer" />
Chinese Translation Guidelines
When writing Chinese translations for ML/AI papers:
| Wrong |
Correct |
Reason |
| 评论器 |
价值函数(critic) |
Standard ML term |
| 运行估计 |
滑动估计 |
Running estimate = 滑动估计 |
| 重加权因子 |
加权系数 |
More natural Chinese |
| 不断演化的 |
动态更新的 |
Clearer meaning |
| 简单修复 |
改动小 |
Academic tone |
Build & CLI
# Build (ESM + DTS via tsup)
cd packages/json-ui && pnpm build
# Render report to HTML
node dist/cli.js render example-report-rich.json
# With options
node dist/cli.js render report.json -o output.html --no-open
When Writing Code
- Always use
I18nString for user-visible text properties in schemas
- Always handle both string and
{en, zh} forms in renderers
- Never use
crossorigin="anonymous" on img tags
- Keep
referrerpolicy="no-referrer" on img tags for privacy
- Test with
pnpm build after any schema or component changes
- Update all three layers (catalog, cli, components) when adding components
1---2name: json-ui3description: CRITICAL: Use for json-ui component rendering and development. Triggers on: json-ui, json render, component catalog, report render, HTML report, I18nString, i18n, bilingual, language switch, dual language, PaperHeader, AuthorList, Abstract, MetricsGrid, Section, Highlight, Zod schema, catalog.ts, cli.ts, components/index.tsx, "how to add a component", "how to render JSON", JSON 渲染, 组件目录, 报告渲染, 多语言, 中英文切换4---5
6# json-ui
7
8> **Version:** 1.0.0 | **Last Updated:** 2026-01-29
9
10You are an expert at the json-ui package — a JSON-to-HTML report renderer with React component support, bilingual i18n, and a CLI tool. Help users by:
11- **Writing components**: Add new component types following existing patterns
12- **Rendering reports**: Generate HTML from JSON report definitions
13- **Debugging**: Fix rendering, build, or i18n issues
14- **Answering questions**: Explain architecture, component catalog, data flow
15
16## Quick Reference
17
18| Task | File | Pattern |
19|------|------|---------|
20| Define component schema | `src/catalog.ts` | Add Zod schema + export in `catalog` object |
21| Render component (HTML) | `src/cli.ts` | Add `case` in `renderNode()` switch |
22| Render component (React) | `src/components/index.tsx` | Export React FC using catalog types |
23| Add i18n text | Any JSON | `{ "en": "Hello", "zh": "你好" }` or plain `"Hello"` |
24| Build | terminal | `pnpm build` (uses tsup, outputs ESM + DTS) |
25| Render report | terminal | `json-ui render report.json [-o out.html] [--no-open]` |
26
27## Documentation
28
29Refer to local source files for detailed documentation:
30- `packages/json-ui/src/catalog.ts` - All Zod schemas and type definitions
31- `packages/json-ui/src/cli.ts` - HTML renderer and CLI entry point
32- `packages/json-ui/src/components/index.tsx` - React component implementations
33
34## IMPORTANT: Documentation Completeness Check
35
36**Before answering questions, Claude MUST:**
371. Read the relevant source file(s) listed above
382. If file read fails: Inform user "本地文档不完整,建议更新"
393. Still answer based on SKILL.md patterns + built-in knowledge
40
41## Architecture
42
43### JSON Report Format
44
45Reports are trees of nodes:
46
47```json
48{
49 "type": "Report",
50 "props": { "title": "My Report", "theme": "auto" },
51 "children": [
52 {
53 "type": "Section",
54 "props": { "title": "Overview", "icon": "bulb" },
55 "children": [
56 { "type": "Abstract", "props": { "text": "..." } }
57 ]
58 }
59 ]
60}
61```
62
63### Three Rendering Layers
64
65| Layer | File | Output | Use Case |
66|-------|------|--------|----------|
67| Zod Schemas | `catalog.ts` | Type definitions | Validation, type safety |
68| HTML Renderer | `cli.ts` | Static HTML string | CLI `render` command |
69| React Components | `components/index.tsx` | React elements | Embedded usage |
70
71### Data Flow
72
73```
74JSON file → CLI parse → renderNode() recursion → HTML string → file write → browser open
75```
76
77## Component Catalog (38 types)
78
79### Layout
80
81| Component | Key Props | Description |
82|-----------|-----------|-------------|
83| `Report` | `title?, theme` | Root wrapper, 800px max-width |
84| `Section` | `title, icon?, collapsible?` | Collapsible section with header |
85| `Grid` | `cols, gap` | CSS grid layout |
86| `Card` | `variant, padding, shadow` | Card container |
87
88### Paper Info
89
90| Component | Key Props | Description |
91|-----------|-----------|-------------|
92| `PaperHeader` | `title, arxivId, date, categories?` | Paper title + metadata |
93| `AuthorList` | `authors, layout?, maxVisible?` | Author names + affiliations |
94| `Abstract` | `text, highlights?, maxLength?` | Abstract with keyword highlighting |
95| `TagList` | `tags, variant` | Tag/category pills |
96
97### Content
98
99| Component | Key Props | Description |
100|-----------|-----------|-------------|
101| `ContributionList` | `items, numbered?` | Numbered contributions with badges |
102| `MethodOverview` | `steps, showConnectors?` | Step-by-step method pipeline |
103| `Highlight` | `text, type, source?` | Blockquote (quote/important/warning/code) |
104| `KeyPoint` | `icon, title, description` | Icon + title + description |
105| `CodeBlock` | `code, language, showLineNumbers?` | Syntax-highlighted code |
106| `Prose` | `content` | Markdown content block |
107| `Callout` | `type, title?, content` | Info/tip/warning/important/note box |
108
109### Rich Content
110
111| Component | Key Props | Description |
112|-----------|-----------|-------------|
113| `Image` | `src, alt?, caption?, width?` | Single image |
114| `Figure` | `images, caption?, label?` | Multi-image figure |
115| `Formula` | `latex, block?, label?` | LaTeX formula |
116| `DefinitionList` | `items` | Term-definition pairs |
117| `Theorem` | `type, number?, title?, content` | Theorem/lemma/proposition |
118| `Algorithm` | `title, steps, caption?` | Algorithm pseudocode |
119| `ResultsTable` | `columns, rows, highlights?` | Results with best-cell highlighting |
120
121### Data Display
122
123| Component | Key Props | Description |
124|-----------|-----------|-------------|
125| `Metric` | `label, value, trend?, icon?` | Single metric card |
126| `MetricsGrid` | `metrics, cols?` | Grid of metric cards |
127| `Table` | `columns, rows, striped?, caption?` | Data table |
128
129### Interactive
130
131| Component | Key Props | Description |
132|-----------|-----------|-------------|
133| `LinkButton` | `href, label, icon?, external?` | Styled link button |
134| `LinkGroup` | `links, layout?` | Group of link buttons |
135
136### Brand
137
138| Component | Key Props | Description |
139|-----------|-----------|-------------|
140| `BrandHeader` | `badge?, poweredBy?, showBadge?` | AI-generated badge header |
141| `BrandFooter` | `timestamp, attribution?, disclaimer?` | Footer with attribution |
142
143## I18n System
144
145### Backward-Compatible Bilingual Strings
146
147The `I18nString` type accepts both plain strings and bilingual objects:
148
149```typescript
150// catalog.ts
151export const I18nString = z.union([
152 z.string(),
153 z.object({ en: z.string(), zh: z.string() }),
154]);
155```
156
157### JSON Usage
158
159```json
160// Plain string (backward compatible)
161{ "title": "Hello World" }
162
163// Bilingual object
164{ "title": { "en": "Hello World", "zh": "你好世界" } }
165```
166
167### HTML Rendering (cli.ts)
168
169For HTML output, i18n strings render as dual spans:
170
171```typescript
172// renderI18n() outputs:
173<span class="i18n-en">Hello</span><span class="i18n-zh">你好</span>
174
175// CSS controls visibility:
176html[lang="en"] .i18n-zh { display: none; }
177html[lang="zh"] .i18n-en { display: none; }
178```
179
180For HTML attributes (alt, title) where only a plain string works:
181
182```typescript
183// resolveI18n() picks one language:
184const alt = resolveI18n(props.alt, 'en'); // returns plain string
185```
186
187### React Rendering (components/index.tsx)
188
189```typescript
190// Use <I18nText> component for JSX:
191<I18nText value={props.title} />
192
193// Use resolveI18nStr() for plain string contexts:
194const altText = resolveI18nStr(props.alt, 'en');
195```
196
197### Language Switcher
198
199- Fixed top-right button: EN | 中文
200- Toggles `<html lang="en|zh">` attribute
201- Persists choice via `localStorage.getItem('json-ui-lang')`
202
203## Key Patterns
204
205### Pattern 1: Adding a New Component
206
2071. **Define schema** in `catalog.ts`:
208```typescript
209export const MyWidgetSchema = z.object({
210 label: I18nString, // Use I18nString for user-visible text
211 count: z.number(), // Use z.string()/z.number() for data
212 variant: VariantType.default('default'),
213});
214
215// Add to catalog object:
216export const catalog = {
217 // ...existing...
218 MyWidget: MyWidgetSchema,
219} as const;
220
221// Export type:
222export type MyWidgetProps = z.infer<typeof MyWidgetSchema>;
223```
224
2252. **Add HTML renderer** in `cli.ts` `renderNode()` switch:
226```typescript
227case 'MyWidget': {
228 const { label, count, variant } = props;
229 return `<div class="my-widget ${variant}">
230 <span>${renderI18n(label)}</span>
231 <strong>${escapeHtml(String(count))}</strong>
232 </div>`;
233}
234```
235
2363. **Add React component** in `components/index.tsx`:
237```typescript
238export const MyWidget: React.FC<MyWidgetProps> = ({ label, count, variant = 'default' }) => (
239 <div className={`my-widget ${variant}`}>
240 <span><I18nText value={label} /></span>
241 <strong>{count}</strong>
242 </div>
243);
244```
245
246### Pattern 2: Handling I18n in Special Cases
247
248For text that needs processing (e.g., Abstract highlights):
249
250```typescript
251// HTML (cli.ts) - process each language separately:
252if (isI18n(text)) {
253 return `<span class="i18n-en">${processText(text.en)}</span>
254 <span class="i18n-zh">${processText(text.zh)}</span>`;
255} else {
256 return processText(String(text));
257}
258
259// React (components/index.tsx):
260if (typeof text === 'object' && 'en' in text && 'zh' in text) {
261 return (
262 <>
263 <span className="i18n-en" dangerouslySetInnerHTML={{ __html: processText(text.en) }} />
264 <span className="i18n-zh" dangerouslySetInnerHTML={{ __html: processText(text.zh) }} />
265 </>
266 );
267}
268```
269
270## Common Errors
271
272| Error | Cause | Solution |
273|-------|-------|---------|
274| `Type 'I18nStringType' is not assignable to 'ReactNode'` | Passing i18n object directly to JSX | Wrap with `<I18nText value={...} />` |
275| `Property 'length' does not exist on type 'I18nStringType'` | Calling string methods on i18n value | Use type guard: `typeof text === 'string' ? text : text.en` |
276| Images not loading from arxiv | `crossorigin="anonymous"` on `<img>` | Remove `crossorigin`; keep only `referrerpolicy="no-referrer"` |
277| Language switcher not working | Missing CSS rules or JS | Ensure `html[lang] .i18n-*` CSS rules and toggle JS are in template |
278| Build fails with type errors | Schema changed but components not updated | Update all three files: catalog, cli, components |
279
280## CRITICAL: Image Handling
281
282**Do NOT use `crossorigin="anonymous"` on `<img>` tags.**
283
284Sites like arxiv.org do not send CORS headers. Adding `crossorigin="anonymous"` causes the browser to require CORS, which fails and blocks the image.
285
286```html
287<!-- WRONG - breaks images from arxiv and similar sites -->
288<img src="..." referrerpolicy="no-referrer" crossorigin="anonymous" />
289
290<!-- CORRECT -->
291<img src="..." referrerpolicy="no-referrer" />
292```
293
294## Chinese Translation Guidelines
295
296When writing Chinese translations for ML/AI papers:
297
298| Wrong | Correct | Reason |
299|-------|---------|--------|
300| 评论器 | 价值函数(critic) | Standard ML term |
301| 运行估计 | 滑动估计 | Running estimate = 滑动估计 |
302| 重加权因子 | 加权系数 | More natural Chinese |
303| 不断演化的 | 动态更新的 | Clearer meaning |
304| 简单修复 | 改动小 | Academic tone |
305
306## Build & CLI
307
308```bash
309# Build (ESM + DTS via tsup)
310cd packages/json-ui && pnpm build
311
312# Render report to HTML
313node dist/cli.js render example-report-rich.json
314
315# With options
316node dist/cli.js render report.json -o output.html --no-open
317```
318
319## When Writing Code
320
3211. Always use `I18nString` for user-visible text properties in schemas
3222. Always handle both string and `{en, zh}` forms in renderers
3233. Never use `crossorigin="anonymous"` on img tags
3244. Keep `referrerpolicy="no-referrer"` on img tags for privacy
3255. Test with `pnpm build` after any schema or component changes
3266. Update all three layers (catalog, cli, components) when adding components