Actionbook json-ui
install
source · Clone the upstream repo
git clone https://github.com/actionbook/actionbook
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/actionbook/actionbook "$T" && mkdir -p ~/.claude/skills && cp -r "$T/playground/json-ui-skill" ~/.claude/skills/actionbook-actionbook-json-ui && rm -rf "$T"
manifest:
playground/json-ui-skill/SKILL.mdsource content
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 | | Add Zod schema + export in object |
| Render component (HTML) | | Add in switch |
| Render component (React) | | Export React FC using catalog types |
| Add i18n text | Any JSON | or plain |
| Build | terminal | (uses tsup, outputs ESM + DTS) |
| Render report | terminal | |
Documentation
Refer to local source files for detailed documentation:
- All Zod schemas and type definitionspackages/json-ui/src/catalog.ts
- HTML renderer and CLI entry pointpackages/json-ui/src/cli.ts
- React component implementationspackages/json-ui/src/components/index.tsx
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 | | Type definitions | Validation, type safety |
| HTML Renderer | | Static HTML string | CLI command |
| React Components | | 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 |
|---|---|---|
| | Root wrapper, 800px max-width |
| | Collapsible section with header |
| | CSS grid layout |
| | Card container |
Paper Info
| Component | Key Props | Description |
|---|---|---|
| | Paper title + metadata |
| | Author names + affiliations |
| | Abstract with keyword highlighting |
| | Tag/category pills |
Content
| Component | Key Props | Description |
|---|---|---|
| | Numbered contributions with badges |
| | Step-by-step method pipeline |
| | Blockquote (quote/important/warning/code) |
| | Icon + title + description |
| | Syntax-highlighted code |
| | Markdown content block |
| | Info/tip/warning/important/note box |
Rich Content
| Component | Key Props | Description |
|---|---|---|
| | Single image |
| | Multi-image figure |
| | LaTeX formula |
| | Term-definition pairs |
| | Theorem/lemma/proposition |
| | Algorithm pseudocode |
| | Results with best-cell highlighting |
Data Display
| Component | Key Props | Description |
|---|---|---|
| | Single metric card |
| | Grid of metric cards |
| | Data table |
Interactive
| Component | Key Props | Description |
|---|---|---|
| | Styled link button |
| | Group of link buttons |
Brand
| Component | Key Props | Description |
|---|---|---|
| | AI-generated badge header |
| | 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
attribute<html lang="en|zh"> - 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
switch:renderNode()
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 |
|---|---|---|
| Passing i18n object directly to JSX | Wrap with |
| Calling string methods on i18n value | Use type guard: |
| Images not loading from arxiv | on | Remove ; keep only |
| Language switcher not working | Missing CSS rules or JS | Ensure 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
on crossorigin="anonymous"
tags.<img>
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
for user-visible text properties in schemasI18nString - Always handle both string and
forms in renderers{en, zh} - Never use
on img tagscrossorigin="anonymous" - Keep
on img tags for privacyreferrerpolicy="no-referrer" - Test with
after any schema or component changespnpm build - Update all three layers (catalog, cli, components) when adding components