npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tini-work/json-render

v1.0.0

Published

Spec-driven UI renderer for React. Build interfaces from JSON; render with any component pack you wire in.

Downloads

242

Readme

@tini-work/json-render

Spec-driven UI renderer for React. Build interfaces from JSON; render with any component pack you wire in.

Wraps @json-render/core + @json-render/react and ships canonical helpers (defineCatalog, defineRegistry), a provider (TiniProvider), state hooks (useTiniStore, useTiniSetState), and an AI surface (useUIStream, streamUISpec, buildSystemPrompt, buildCatalogManifest, specJsonSchema). Ships no components — pair with @tini-work/json-render-shadcn or your own component pack.

Install

pnpm add @tini-work/json-render @tini-work/json-render-shadcn @tini-work/tokens react react-dom

Peer deps: react@^19, react-dom@^19, @tini-work/tokens@workspace:*. Optional peer: ai (Vercel AI SDK) for server-side streamUISpec.

Quick start

import { TiniRenderer, type Spec } from '@tini-work/json-render'
import { registry } from '@tini-work/json-render-shadcn/registry'

const spec: Spec = {
  root: 'card',
  state: { count: 0 },
  elements: {
    card: { type: 'Card', children: ['header', 'content'] },
    header: { type: 'CardHeader', children: ['title'] },
    title: { type: 'CardTitle', children: ['titleText'] },
    titleText: { type: 'Text', props: { value: 'Counter' } },
    content: { type: 'CardContent', children: ['btn'] },
    btn: {
      type: 'Button',
      children: ['btnText'],
      on: { press: { action: 'increment' } },
    },
    btnText: { type: 'Text', props: { value: 'Increment' } },
  },
}

export default function App() {
  return (
    <TiniRenderer
      spec={spec}
      registry={registry}
      handlers={{
        increment: (_params, setState) =>
          setState((prev) => ({
            ...prev,
            count: (prev.count as number) + 1,
          })),
      }}
    />
  )
}

Provider composition

For multi-TiniRenderer trees that share state:

import { TiniProvider, useTiniStore, useTiniSetState } from '@tini-work/json-render'
import { registry } from '@tini-work/json-render-shadcn/registry'

<TiniProvider registry={registry} initialState={{ step: 1 }} handlers={{...}}>
  <Wizard />
  <DebugPanel />
</TiniProvider>

Inside the subtree: const state = useTiniStore().getSnapshot(). Mutate: useTiniSetState()(prev => ({ ...prev, step: 2 })).

State + actions

  • Spec carries seed state under spec.state (RFC-6901 pointer-flat).
  • Read inside any prop: "checked": { "$state": "/notifications" }.
  • Components emit events (declared in their events array). Wire to host handlers via "on": { "press": { "action": "increment" } }.
  • Handlers receive (params, setState, state). setState is React-style; the renderer translates updates back into the per-pointer store via bindSetState.

Known constraint: upstream @json-render/[email protected] exposes emit(event) with no payload. Form input values, sort directions, etc. cannot round-trip via $payload. Handlers must derive next state from current snapshot or read DOM directly.

AI / prompt-to-UI

Client-side hook + server helper (Vercel AI SDK):

// Client
import { useUIStream, TiniRenderer } from '@tini-work/json-render'
import { registry } from '@tini-work/json-render-shadcn/registry'

const { spec, isStreaming, send } = useUIStream({ api: '/api/ui' })

return (
  <>
    <button onClick={() => send('a settings panel')}>Generate</button>
    {spec && <TiniRenderer spec={spec} registry={registry} />}
  </>
)
// Server (e.g. Next.js route handler)
import { streamUISpec } from '@tini-work/json-render'
import { catalog } from '@tini-work/json-render-shadcn/catalog'
import { anthropic } from '@ai-sdk/anthropic'

export async function POST(req: Request) {
  const { prompt } = await req.json()
  return streamUISpec({
    model: anthropic('claude-haiku-4-5'),
    catalog,
    prompt,
  })
}

Custom catalogs

Build your own component pack via the public helpers:

import { defineCatalog, defineRegistry } from '@tini-work/json-render'
import { z } from 'zod'

export const catalog = defineCatalog(z.unknown(), {
  components: {
    MyButton: {
      description: 'A custom button',
      props: z.object({ label: z.string() }),
      slots: ['default'],
      events: ['press'],
    },
  },
})

export const registry = defineRegistry({
  components: {
    MyButton: ({ props, emit }) => (
      <button onClick={() => emit('press')}>{String(props.label ?? '')}</button>
    ),
  },
})

Pass registry to TiniRenderer. Pass catalog to streamUISpec / buildSystemPrompt to ground the LLM.

Public exports

| Module | Exports | |---|---| | . (root) | TiniRenderer, TiniProvider, useTiniStore, useTiniSetState, useUIStream, streamUISpec, defineCatalog, defineRegistry, cn, bindSetState, flattenToPointers, types | | ./ai | buildSystemPrompt, buildCatalogManifest, specJsonSchema, types |

Architecture

  • src/TiniRenderer.tsx — wraps JSONUIProvider + upstream Renderer + bindSetState shim.
  • src/TiniProvider.tsx — explicit provider for multi-renderer composition.
  • src/hooks.tsuseTiniStore, useTiniSetState.
  • src/useUIStream.ts — client hook for prompt-driven streams.
  • src/streamUISpec.ts — server helper for ai-backed structured generation.
  • src/ai.ts — catalog-agnostic buildSystemPrompt, buildCatalogManifest, specJsonSchema.
  • src/lib/define-catalog.ts, define-registry.ts — public builder helpers.
  • src/lib/{cn,bindSetState,flattenToPointers,adapt-registry,catalog-entry}.ts — shared utilities.