npm package: @obieg-zero/sdk
Description: Plugin SDK for Obieg Zero — types, UI components, store/plugin contracts
Latest version: 0.5.1

# @obieg-zero/sdk

Plugin SDK for [Obieg Zero](https://github.com/obieg-zero/core-host) — browser-native document flow engine.

## Plugin contract

A plugin is a function that receives `{ React, store, sdk, ui, icons }` and returns `{ id, label, icon }`.
Plugins run in a sandbox — no `fetch`, no `import()`, no direct DOM. All data goes through `store`, all UI through `ui`.

```tsx
import type { PluginFactory } from '@obieg-zero/sdk'

const plugin: PluginFactory = ({ store, sdk, ui, icons }) => {
  store.registerType('task', [
    { key: 'title', label: 'Tytuł', required: true },
    { key: 'done', label: 'Gotowe' },
  ], 'Zadania')

  function Center() {
    const tasks = store.usePosts('task')
    return <ui.Page><ui.Stack>
      {tasks.map(t => <ui.ListItem key={t.id} label={t.data.title}
        action={<ui.RemoveButton onClick={() => store.remove(t.id)} />} />)}
      <ui.Button onClick={() => store.add('task', { title: 'Nowe', done: false })}>+ Dodaj</ui.Button>
    </ui.Stack></ui.Page>
  }

  sdk.registerView('tasks.center', { slot: 'center', component: Center })
  return { id: 'tasks', label: 'Zadania', icon: icons.CheckSquare }
}
export default plugin
```

## store — data (Zustand + IndexedDB)

| Method | Description |
|--------|-------------|
| `add(type, data, opts?)` | Create record. Returns `PostRecord`. opts: `{ id?, parentId? }` |
| `get(id)` | Get record by ID (sync) |
| `update(id, data)` | Merge data into record |
| `remove(id)` | Delete record + children (cascade) |
| `usePost(id)` | React hook — single record by ID |
| `usePosts(type)` | React hook — all records of type, sorted by createdAt |
| `useChildren(parentId, type?)` | React hook — children of a record |
| `getPosts(type)` | Sync query (for factory/callbacks, not components) |
| `registerType(type, schema, label?, opts?)` | Register type with schema. opts: `{ strict?: boolean }` |
| `setOption(key, value)` | Persistent key-value config |
| `useOption(key)` | React hook — read option |
| `importJSON(nodes)` | Bulk import `[{ type, data, children? }]` |
| `writeFile(postId, name, data)` | Attach file (OPFS) |
| `readFile(postId, name)` | Read attached file |

Store przechowuje dane aplikacji. Stan pluginów (specs, labels, licencja) jest w OPFS — patrz SDK plugin management.

### PostRecord

```ts
{ id: string, type: string, parentId: string | null, data: Record<string, any>, createdAt: number, updatedAt: number }
```

Parent-child: set `parentId` via `opts.parentId` in `add()`. Query children with `useChildren(parentId)`. `remove()` cascades to children.

## sdk — host services

| Method | Description |
|--------|-------------|
| `registerView(id, { slot, component })` | Register UI in slot: `left`, `center`, `right`, `footer` |
| `registerParser(id, { accept, targetType, parse })` | Register file parser (e.g. CSV → records) |
| `registerAction(id, { node })` | Register toolbar action |
| `uploadFile(parentId)` | Open file dialog, create event, store in OPFS |
| `downloadFile(postId, filename)` | Download attached file |
| `shared` | Zustand store shared between plugins. `shared(s => s.x)`, `shared.setState({x})` |
| `create(initializer)` | Create plugin-scoped Zustand store |
| `useForm(defaults, opts?)` | Form state helper with validation |
| `log(text, level?)` | Write to host log panel |
| `installPlugin(spec, label?)` | Install plugin + persist to OPFS |
| `uninstallPlugin(spec)` | Uninstall plugin + remove from OPFS |
| `getInstalledPlugins()` | List installed plugins `[{ spec, label }]` |
| `setStoreAuth(auth)` | Set store license (persisted to OPFS) |
| `getStoreAuth()` | Get current store license |

## ui — components (props only, no className)

**Layout:** `Stack` `Row` `Page` `Box` `Stage` `StageLayout`
**Nav:** `Tabs` `Cell`
**Form:** `Button` `Input` `Select` `Field`
**Data:** `Text` `Value` `Badge` `Heading` `StepHeading` `Card` `Stats` `Stat` `Table` `ListItem` `CheckItem`
**Actions:** `Placeholder` `Spinner` `FileAction` `RemoveButton` `Divider`

## Security

**Sandbox:** Plugins run as ES modules evaluated via blob URL. They have no access to `fetch`, `import()`, `localStorage`, or raw DOM (`className`). All I/O goes through `store` (data) and `ui` (rendering).

**Integrity (SRI):** `config.json` supports `integrity` field per plugin — deployer pins SHA-256 hash, loader verifies on every fetch. Use tagged versions (`@vX.Y.Z`) in production — they are immutable on GitHub and cached in OPFS.

**Store plugins (`store://`):** Served via authenticated Cloudflare Worker from private GitHub repos. License key required for paid plugins.

**Plugin persistence (OPFS):** Installed plugins, labels, and license are stored in OPFS `meta.json` — survives IndexedDB/localStorage clearing. Plugin code is cached in OPFS for offline use.

## icons

All [react-feather](https://feathericons.com/) icons: `icons.Briefcase`, `icons.DollarSign`, `icons.Upload`, etc.

## Slots

A plugin with a `center` view appears in the navigation. All slots of the **active** plugin are rendered.

```
┌──────────┬──────────────────────┬──────────┐
│  left    │      center          │  right   │
│          │                      │          │
├──────────┴──────────────────────┴──────────┤
│                  footer                     │
└─────────────────────────────────────────────┘
```