@reyanshrastogi/lathe
v0.0.2
Published
A protocol and reference implementation for user-customizable web app interfaces. Apps declare schemas, actions, and primitive components. Users (via an LLM agent) compose those primitives into custom views, parameterized by a theme token vocabulary. The
Readme
Lathe
A protocol and reference implementation for user-customizable web app interfaces. Apps declare schemas, actions, and primitive components. Users (via an LLM agent) compose those primitives into custom views, parameterized by a theme token vocabulary. The runtime renders user-emitted view specs against live app data with no code generation.
npm: @reyanshrastogi/lathe | Version: 0.0.1 | Status: v0 — local storage, single-user, no hosted backend
Key principles
- The agent never emits code. All agent output is JSON validated against Zod schemas. View structure and theming are pure configuration. The runtime is deterministic React reading config.
- No LLM in the rendering hot path. Inference fires only on explicit customization events. Once a view spec exists, rendering is React-speed with zero AI involvement.
- The primitive vocabulary is fixed and small. The agent composes from a known set of primitives; it cannot invent new ones. Developers may extend the set via
registerPrimitive. - Theme and view are separate. Two storage keys, two prompt branches, two validation pipelines. Visual styling never bleeds into composition logic.
- BYOK for inference. Users provide their own provider API key. No hosted backend.
- Permissions live in actions, not in the agent. The agent only sees schemas and actions the current user is authorized for. Actions revalidate permissions on execution.
Quick start
npm install @reyanshrastogi/lathe
# peer deps
npm install react react-dom zod dexie
# install the provider SDK you plan to use (at least one)
npm install @google/generative-ai # Gemini (recommended default)1. Define your app
import { defineApp } from '@reyanshrastogi/lathe'
import type { DataSource } from '@reyanshrastogi/lathe'
import { z } from 'zod'
const TaskSchema = z.object({
id: z.string(),
title: z.string(),
status: z.enum(['todo', 'in_progress', 'done']),
priority: z.enum(['low', 'medium', 'high']),
assignee: z.string(),
due_date: z.string(),
})
const tasksSource: DataSource<z.infer<typeof TaskSchema>> = {
schema: TaskSchema,
list: async () => fetchTasks(),
subscribe: (callback) => { /* optional live updates */ return () => {} },
}
export const myApp = defineApp({
id: 'my-app',
data: { tasks: tasksSource as DataSource },
actions: {
updateStatus: {
fn: async ({ id, status }) => { /* mutate */ },
argsSchema: z.object({ id: z.string(), status: z.string() }),
description: 'Update the status of a task',
},
},
defaultView: {
type: 'Kanban',
source: 'tasks',
props: {
groupBy: 'status',
columns: ['todo', 'in_progress', 'done'],
card: {
type: 'Card',
props: { title: '$item.title', subtitle: '$item.assignee', badge: '$item.priority' },
},
onMove: { action: 'updateStatus', args: { id: '$item.id', status: '$column' } },
},
},
})2. Render the customizable region
import { CustomizableRegion } from '@reyanshrastogi/lathe'
import { myApp } from './app'
export function App() {
return <CustomizableRegion app={myApp} userId="user_123" />
}3. Configure API keys
Create .env.local in your project root:
VITE_DEFAULT_PROVIDER=gemini
GEMINI_API_KEY=your_key_hereThe user's provider selection and key are stored locally in IndexedDB. No key leaves the browser.
Primitives reference
All 22 built-in primitives are available to the agent. The agent receives the full library spec (type, description, prop schema as JSON Schema, capabilities) on every customization call.
| Primitive | Capabilities | Description |
|-----------|-------------|-------------|
| List | list, sorting, filtering | Vertical list rendering each item using a nested primitive |
| Table | list, sorting, grouping | Tabular data display with sortable columns |
| Kanban | list, grouping, navigation | Board view grouping items by a field with drag-and-drop support |
| Calendar | list, navigation | Month-grid calendar displaying items by date field |
| Card | single | Compact display of one item with title, subtitle, badge, and avatar |
| Detail | single, navigation | Full detail view of a single item with labeled fields and action buttons |
| Form | form | Form for creating or editing an item with typed fields and a submit action |
| Stats | list | Dashboard metric cards showing aggregated values (count, sum, avg, min, max) from a data source |
| Grid | list, sorting, filtering | Responsive multi-column card grid; like List but arranges items in columns |
| Split | — | Layout primitive splitting the region into two sections (horizontal or vertical) with a configurable ratio |
| Timeline | list, sorting, filtering | Chronological event list grouped by date; use for activity feeds or history logs |
| Tabs | navigation | Tabbed container showing one panel at a time; each tab has a label and a child ViewSpec |
| Accordion | navigation | Collapsible sections container; use for FAQs, grouped settings, or hierarchical content |
| Tree | list, navigation | Hierarchical tree view with expand/collapse; specify labelField and childrenField |
| Stack | — | Flex layout container stacking children vertically or horizontally with configurable gap and alignment |
| Flex | — | Flex container with wrap, justify, and alignment control for responsive card grids or toolbars |
| Section | — | Titled section container wrapping a single child ViewSpec; adds headings, subtitles, optional borders |
| Gallery | list | Image grid gallery; specify imageField for URLs and titleField for captions |
| Gauge | single | Progress bar or ring gauge showing a numeric value from min to max |
| Markdown | single | Renders markdown-formatted content as styled HTML; content can be literal or a $item.field binding |
| Chart | list, filtering | SVG data visualization (bar, line, area, pie); aggregates by xField and yField |
| Canvas | — | Free-layout grid canvas placing child ViewSpecs at (x, y) coordinates; the composition primitive for dashboards |
Registering custom primitives
import { registerPrimitive } from '@reyanshrastogi/lathe'
import type { PrimitiveDefinition } from '@reyanshrastogi/lathe'
registerPrimitive({
type: 'MyPrimitive',
component: MyPrimitiveComponent,
propSchema: MyPropsSchema,
description: 'What this primitive does, for the agent',
capabilities: ['single'],
} satisfies PrimitiveDefinition<MyProps>)The agent receives the updated library spec on the next call.
Theme system
The agent can modify visual appearance independently of view composition. Themes are stored separately from view specs and validated before application.
Token groups:
- colors — 13 required semantic tokens (
background,foreground,primary,primaryForeground,card,cardForeground,accent,accentForeground,muted,mutedForeground,destructive,destructiveForeground,border) plus optionalgradientBackgroundandgradientCardCSS gradient overrides - typography —
fontFamily(sans|serif|mono| custom string),baseSize(12–20 px),scaleRatio(1.05–1.5),baseWeight(400 | 500), optionalfontUrlfor Google Fonts - spacing —
baseUnit(2–8 px),density(compact|comfortable|spacious) - shape —
radius(0–2 rem),borderWidth(1 | 2) - effects —
shadowDepth(flat|soft|elevated), optionalanimation(none|subtle|energetic), optionalshadowColor
Token values are written as CSS variables on the CustomizableRegion root. WCAG AA contrast (4.5:1) is validated for every foreground/background pair before a theme patch is accepted.
Setting a default theme:
defineApp({
// ...
defaultTheme: {
colors: { primary: '#6d28d9', primaryForeground: '#ffffff' },
shape: { radius: 0.5 },
effects: { shadowDepth: 'soft', animation: 'subtle' },
},
})Agent / BYOK
Lathe ships four provider implementations. Install the SDK for the provider you want to use.
| Provider | Package | Model |
|----------|---------|-------|
| gemini | @google/generative-ai | gemini-2.5-flash |
| groq | groq-sdk | llama-3.3-70b-versatile |
| openai | openai | gpt-4o-mini |
| anthropic | @anthropic-ai/sdk | claude-sonnet-4-7 |
The provider and API key are stored locally by the user (IndexedDB ConfigRecord). The CustomizableRegion sidebar handles provider selection UI.
Using a provider programmatically:
import { generatePatch, GeminiProvider } from '@reyanshrastogi/lathe'
const provider = new GeminiProvider(process.env.GEMINI_API_KEY)
const patch = await generatePatch(agentInput, provider)Implementing a custom provider:
import type { ModelProvider } from '@reyanshrastogi/lathe'
const myProvider: ModelProvider = {
id: 'custom',
generate: async ({ system, user, schema }) => { /* ... */ },
}defineApp API
defineApp({
id: string, // unique app ID; used as IndexedDB key prefix
data: Record<string, DataSource>, // named data sources
actions: Record<string, Action>, // named actions (mutation endpoints)
defaultView: ViewSpec, // rendered before any customization
defaultTheme?: Partial<Theme>, // overrides for the built-in defaults
customPrimitives?: PrimitiveDefinition[], // optional extensions
})DataSource:
interface DataSource<T> {
schema: z.ZodType<T>
list: (params: ListParams) => Promise<T[]>
get?: (id: string) => Promise<T>
subscribe?: (callback: (items: T[]) => void) => () => void
relations?: Record<string, { source: string; on: string }>
}Action:
interface Action<TArgs> {
fn: (args: TArgs) => Promise<unknown>
argsSchema: z.ZodType<TArgs>
description: string // shipped to the agent; be descriptive
requires?: string[] // permission identifiers checked at execution time
}Development
The repo is a Yarn/npm workspace. The example/ directory is a working task tracker demo.
# Run the example app
cd example
npm run devSet your API key and provider in example/.env.local:
VITE_DEFAULT_PROVIDER=gemini
GEMINI_API_KEY=your_key_hereBuilding the library:
npm run build # outputs to dist/
npm run test # vitest
npm run tsc # type-check onlyStatus
v0. Single-user. No hosted backend. Storage is local IndexedDB (Dexie). Inference is BYOK. The primitive vocabulary is intentionally fixed at 22 entries for v0; composition over invention is the design constraint.
See SPEC.md for the full protocol reference (type contracts, view-spec grammar, theme token vocabulary, agent I/O schemas) and BUILDPLAN.md for the phased implementation plan.
