# @meissa_a/meissa

Token-efficient generative UI for MCP, in one package with four entry points:

| Import | What it is |
| --- | --- |
| `@meissa_a/meissa/renderer` | React component library — renders interactive UI from declarative JSON schemas |
| `@meissa_a/meissa/client` | Client SDK for rendering Meissa content in MCP hosts (React) |
| `@meissa_a/meissa/server` | Server SDK for returning Meissa UI from MCP tools (zero runtime deps) |
| `@meissa_a/meissa/apps` | MCP Apps adapter — serves Meissa UI as sandboxed HTML iframes |

Define your UI as compact tuples or objects, bind live data, and let the renderer handle the rest.

## Installation

```bash
# npm
npm install @meissa_a/meissa react react-dom

# pnpm
pnpm add @meissa_a/meissa react react-dom

# bun
bun add @meissa_a/meissa react react-dom
```

> `react` / `react-dom` are optional peers — only needed for the `renderer` and `client` entry points.

### Requirements

- React 19+
- Tailwind CSS v4 (for styling)

### Tailwind Setup

Add the renderer's source to your Tailwind config so its classes are included:

```css
/* app.css */
@import "tailwindcss";
@source "../node_modules/@meissa_a/meissa/dist/**/*.js";
```

## Quick Start

```tsx
import { MeissaRenderer } from "@meissa_a/meissa/renderer";

const schema = [
  ["heading", "Hello World"],
  ["text", "This is rendered from JSON."],
  ["btn", { label: "Click me", action: "greet" }],
];

function App() {
  return (
    <MeissaRenderer
      schema={schema}
      data={{ user: "Alice" }}
      onAction={(name, args) => console.log(name, args)}
    />
  );
}
```

## API

### `<MeissaRenderer>`

The main entry point. Parses the schema, resolves data bindings, and renders the component tree.

```tsx
<MeissaRenderer
  schema={schema}         // Meissa JSON schema (required)
  data={data}             // Data context for {{bindings}}
  theme={theme}           // Theme overrides
  onAction={handler}      // Action callback (button clicks, form submits)
  onNavigate={handler}    // Navigation callback
  className="..."         // Container className
/>
```

| Prop | Type | Description |
|------|------|-------------|
| `schema` | `MeissaInput` | JSON schema — array of tuples, single node, or full document |
| `data` | `Record<string, unknown>` | Data context for resolving `{{bindings}}` |
| `theme` | `MeissaTheme` | Theme overrides (colors, radius, density, font) |
| `onAction` | `(name: string, args?: Record<string, unknown>) => void` | Called when actions fire (button clicks, form submissions) |
| `onNavigate` | `(uri: string) => void` | Called for navigation events |
| `className` | `string` | CSS class on the container element |

## Schema Format

Schemas use a compact tuple syntax: `[type, primaryProp?, children?]`

```json
["text", "Hello"]
["btn", { "label": "Submit", "variant": "primary" }]
["row", { "gap": 4 }, [["btn", "A"], ["btn", "B"]]]
```

Or verbose object form:

```json
{ "type": "text", "content": "Hello", "bold": true }
```

### Full Document

```json
{
  "v": 1,
  "data": { "user": "Alice", "count": 42 },
  "theme": { "radius": "md", "colors": { "primary": "#6366f1" } },
  "ui": [
    ["heading", "Dashboard"],
    ["stat", { "label": "Users", "value": "{{count}}" }]
  ]
}
```

## Components

24 built-in components covering layout, content, data display, and interactivity:

### Layout

| Component | Shorthand | Description |
|-----------|-----------|-------------|
| `row` | `["row", [children]]` | Horizontal flex container |
| `col` | `["col", [children]]` | Vertical flex container |
| `grid` | `["grid", { cols: 3 }, [children]]` | CSS grid |
| `split` | `["split", { ratio: "1/3" }, [left, right]]` | Two-panel layout |
| `divider` | `["divider"]` or `["divider", "Section"]` | Horizontal rule with optional label |

### Content

| Component | Shorthand | Description |
|-----------|-----------|-------------|
| `text` | `["text", "Hello"]` | Text content with variants |
| `heading` | `["heading", "Title"]` | h1-h6 heading |
| `code` | `["code", "const x = 1"]` | Code block or inline code |
| `img` | `["img", "url"]` | Image |
| `badge` | `["badge", "New"]` | Status indicator |
| `alert` | `["alert", "Warning message"]` | Notification banner |
| `progress` | `["progress", { value: 75 }]` | Progress bar |

### Data Display

| Component | Shorthand | Description |
|-----------|-----------|-------------|
| `stat` | `["stat", { label: "Revenue", value: "{{rev}}" }]` | KPI metric |
| `list` | `["list", ["one", "two", "three"]]` | Ordered/unordered list |
| `table` | `["table", { data: "{{rows}}" }]` | Data table with sorting & pagination |
| `chart` | `["chart", { data: "{{sales}}", type: "bar" }]` | Recharts-powered chart |

### Interactive

| Component | Shorthand | Description |
|-----------|-----------|-------------|
| `btn` | `["btn", "Click me"]` | Button with action dispatch |
| `input` | `["input", { name: "email", type: "email" }]` | Text input |
| `select` | `["select", { name: "role", options: [...] }]` | Dropdown select |
| `form` | `["form", { submit: "save" }, [children]]` | Form container |
| `card` | `["card", "Title", [children]]` | Card container |
| `tabs` | `["tabs", { items: {...} }]` | Tabbed panels |
| `quiz` | `["quiz", { question: "2+2?", choices: ["3","4"], answer: 1 }]` | Self-grading question — click marks green/red, reveals the answer, locks, shows justifications |
| `exam` | `["exam", "Midterm", [quiz, quiz]]` | Groups `quiz` children with a live progress/score header |

## Data Binding

Use `{{path}}` syntax to bind data into any string prop:

```json
["text", "Hello, {{user.name}}!"]
["stat", { "value": "{{metrics.revenue}}", "label": "Revenue" }]
```

### Filters

Chain filters with `|`:

```json
["text", "{{price | currency}}"]
["text", "{{name | uppercase}}"]
["text", "{{description | truncate}}"]
```

Built-in filters: `currency`, `percent`, `number`, `uppercase`, `lowercase`, `truncate`, `relative`, `date`, `default`.

### Comparisons

Expressions resolve to booleans:

```json
["text", { "content": "Premium", "if": "{{plan == 'pro'}}" }]
["alert", { "content": "Low stock", "if": "{{count < 10}}" }]
```

## Conditionals

### `if` — Remove from DOM

```json
["text", { "content": "Admin only", "if": "{{isAdmin}}" }]
```

### `show` — Toggle visibility (keeps DOM element)

```json
["text", { "content": "Loading...", "show": "{{loading}}" }]
```

### `switch` — Multi-branch

```json
["switch", "{{status}}", {
  "active": ["badge", { "content": "Active", "variant": "success" }],
  "inactive": ["badge", { "content": "Inactive", "variant": "default" }],
  "_": ["badge", "Unknown"]
}]
```

## Iteration

Loop over arrays with `each`:

```json
["each", "{{users}}", ["card", "{{$.name}}", [
  ["text", "{{$.email}}"]
]]]
```

Context variables inside each:
- `$` — current item
- `$i` — index (0-based)
- `$first` — `true` for first item
- `$last` — `true` for last item

## Theming

Pass a `theme` prop or include `theme` in the document:

```tsx
<MeissaRenderer
  schema={schema}
  theme={{
    radius: "lg",
    density: "relaxed",
    font: "Inter, sans-serif",
    colors: {
      primary: "#6366f1",
      destructive: "#ef4444",
      success: "#10b981",
      bg: "#ffffff",
      fg: "#0f172a",
      muted: "#64748b",
      border: "#e2e8f0",
    },
  }}
/>
```

## Actions

Components dispatch actions via `onAction`:

```tsx
<MeissaRenderer
  schema={[
    ["btn", { "label": "Delete", "action": "deleteItem", "args": { "id": 42 } }],
    ["form", { "submit": "saveUser" }, [
      ["input", { "name": "email", "type": "email" }],
      ["btn", { "label": "Save", "submit": true }]
    ]]
  ]}
  onAction={(action, args) => {
    // action: "deleteItem" | "saveUser"
    // args: { id: 42 } | { email: "..." }
  }}
/>
```

## Advanced Usage

### Custom Component Registry

```tsx
import { COMPONENT_REGISTRY, ComponentRenderer } from "@meissa_a/meissa/renderer";

// Add a custom component
COMPONENT_REGISTRY["my-widget"] = ({ node, children }) => (
  <div className="my-widget">{children}</div>
);
```

### Using Core Pipeline Directly

```tsx
import { parse, normalize, resolveBindings } from "@meissa_a/meissa/renderer";

// Parse raw schema
const doc = parse(rawJson);

// Normalize a single tuple
const node = normalize(["btn", { label: "Click", variant: "ghost" }]);

// Resolve bindings manually
const value = resolveBindings("{{user.name}}", { user: { name: "Alice" } });
// → "Alice"
```

## TypeScript

All types are exported:

```tsx
import type {
  MeissaRendererProps,
  MeissaInput,
  MeissaDocument,
  NormalizedNode,
  MeissaTheme,
  ComponentProps,
  MeissaComponent,
  ActionHandler,
  NavigateHandler,
} from "@meissa_a/meissa/renderer";
```

## Development

```bash
# Install dependencies
bun install

# Run tests
bun test

# Type check
bun run typecheck

# Build (ESM + CJS)
bun run build

# Watch mode
bun run dev
```

## License

MIT
