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.md
source 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

TaskFilePattern
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 textAny JSON
{ "en": "Hello", "zh": "你好" }
or plain
"Hello"
Buildterminal
pnpm build
(uses tsup, outputs ESM + DTS)
Render reportterminal
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:

  1. Read the relevant source file(s) listed above
  2. If file read fails: Inform user "本地文档不完整,建议更新"
  3. 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

LayerFileOutputUse Case
Zod Schemas
catalog.ts
Type definitionsValidation, type safety
HTML Renderer
cli.ts
Static HTML stringCLI
render
command
React Components
components/index.tsx
React elementsEmbedded usage

Data Flow

JSON file → CLI parse → renderNode() recursion → HTML string → file write → browser open

Component Catalog (38 types)

Layout

ComponentKey PropsDescription
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

ComponentKey PropsDescription
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

ComponentKey PropsDescription
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

ComponentKey PropsDescription
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

ComponentKey PropsDescription
Metric
label, value, trend?, icon?
Single metric card
MetricsGrid
metrics, cols?
Grid of metric cards
Table
columns, rows, striped?, caption?
Data table

Interactive

ComponentKey PropsDescription
LinkButton
href, label, icon?, external?
Styled link button
LinkGroup
links, layout?
Group of link buttons

Brand

ComponentKey PropsDescription
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

  1. 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>;
  1. 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>`;
}
  1. 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

ErrorCauseSolution
Type 'I18nStringType' is not assignable to 'ReactNode'
Passing i18n object directly to JSXWrap with
<I18nText value={...} />
Property 'length' does not exist on type 'I18nStringType'
Calling string methods on i18n valueUse 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 workingMissing CSS rules or JSEnsure
html[lang] .i18n-*
CSS rules and toggle JS are in template
Build fails with type errorsSchema changed but components not updatedUpdate 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:

WrongCorrectReason
评论器价值函数(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

  1. Always use
    I18nString
    for user-visible text properties in schemas
  2. Always handle both string and
    {en, zh}
    forms in renderers
  3. Never use
    crossorigin="anonymous"
    on img tags
  4. Keep
    referrerpolicy="no-referrer"
    on img tags for privacy
  5. Test with
    pnpm build
    after any schema or component changes
  6. Update all three layers (catalog, cli, components) when adding components