npm package: @prophetdo/beta
Description: The official client SDK for building Prophet app front-ends
Latest version: 0.0.18-beta

# @prophetdo/beta

The official client SDK for building [Prophet](https://prophet.do) app front-ends. This package combines the Prophet client, React bindings, SDK, UI components, and styles into a single install.

## Install

```bash
npm install @prophetdo/beta
```

## Quick Start

Import the base styles in your app entry point, then wrap your app in `ProphetApp`:

```tsx
// main.tsx
import "@prophetdo/beta/styles.css";
import { ProphetApp } from "@prophetdo/beta/react";
import { App } from "./App";

function main() {
  return (
    <ProphetApp
      org="your-org"
      app="your-app"
      platform="https://beta.prophet.do"
    >
      <App />
    </ProphetApp>
  );
}
```

```tsx
// App.tsx
import { useSubscribe, useMutation, useCurrentUser } from "@prophetdo/beta/react";
import { Button, Card, Input } from "@prophetdo/beta/ui";

function App() {
  const { data: todos } = useSubscribe("listTodos", { list_id: "default" });
  const addTodo = useMutation("addTodo");
  const { user } = useCurrentUser();

  return (
    <Card>
      <h1>Welcome, {user?.name}</h1>
      <ul>
        {todos?.map((todo) => (
          <li key={todo.id}>{todo.title}</li>
        ))}
      </ul>
      <Button onClick={() => addTodo({ list_id: "default", title: "New todo" })}>
        Add Todo
      </Button>
    </Card>
  );
}
```

## Package Exports

### `@prophetdo/beta`

Core client for connecting to a Prophet app over WebSockets.

```ts
import { createProphetClient, ProphetClient } from "@prophetdo/beta";
```

| Export                | Description                                       |
| --------------------- | ------------------------------------------------- |
| `createProphetClient` | Factory to create a configured `ProphetClient`    |
| `ProphetClient`       | Core client class for subscriptions and mutations |
| `ProphetError`        | Error class for Prophet client errors             |
| `WebSocketManager`    | Low-level WebSocket connection manager            |
| `applyChanges`        | Utility to apply change entries to a data set     |

### `@prophetdo/beta/react`

React hooks and components for building Prophet apps.

```tsx
import { ProphetApp, useSubscribe, useMutation } from "@prophetdo/beta/react";
```

#### Components

| Component      | Description                                                    |
| -------------- | -------------------------------------------------------------- |
| `ProphetApp`   | Top-level wrapper that connects to your Prophet app            |
| `Switch`       | Client-side route switch                                       |
| `Route`        | Declares a route pattern and renders children when matched     |
| `Link`         | Client-side navigation link                                    |

#### Hooks

| Hook              | Description                                               |
| ----------------- | --------------------------------------------------------- |
| `useSubscribe`    | Subscribe to a live query — returns reactive data         |
| `useMutation`     | Get a function to execute a mutation                      |
| `useBatchMutation`| Execute multiple mutations in a single batch              |
| `useCurrentUser`  | Get the currently authenticated user                      |
| `useUser`         | Look up a user by ID                                      |
| `useMembers`      | Subscribe to members/presence on an entity                |
| `usePresence`     | Track and broadcast presence state                        |
| `useFile`         | Load a file record by ID                                  |
| `useUploadFile`   | Get a function to upload files                            |
| `useAI`           | Access the AI client for server-side AI endpoints         |
| `useLocation`     | Read and set the current client-side location             |
| `useRoute`        | Match the current location against a route pattern        |
| `useParams`       | Read route parameters from the matched route              |

### `@prophetdo/beta/ui`

Pre-built UI components based on [shadcn/ui](https://ui.shadcn.com/) and Radix UI primitives, styled with Tailwind CSS and designed to work with Prophet's theme.

```tsx
import { Button, Dialog, DialogTrigger, DialogContent, Input, Select } from "@prophetdo/beta/ui";
```

| Component | Exports | Built on |
| --- | --- | --- |
| **Avatar** | `Avatar`, `AvatarImage`, `AvatarFallback` | `@radix-ui/react-avatar` |
| **Badge** | `Badge`, `badgeVariants` | — |
| **Button** | `Button`, `buttonVariants` | `@radix-ui/react-slot` |
| **Calendar** | `Calendar` | `react-day-picker` |
| **Card** | `Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardContent`, `CardFooter` | — |
| **ColorPicker** | `ColorPicker` | — |
| **Command** | `Command`, `CommandInput`, `CommandList`, `CommandEmpty`, `CommandGroup`, `CommandItem`, `CommandSeparator`, `CommandShortcut` | `cmdk` |
| **Dialog** | `Dialog`, `DialogTrigger`, `DialogContent`, `DialogHeader`, `DialogFooter`, `DialogTitle`, `DialogDescription`, `DialogClose` | `@radix-ui/react-dialog` |
| **DropdownMenu** | `DropdownMenu`, `DropdownMenuTrigger`, `DropdownMenuContent`, `DropdownMenuItem`, `DropdownMenuSeparator`, `DropdownMenuLabel`, `DropdownMenuGroup`, `DropdownMenuSub`, `DropdownMenuSubTrigger`, `DropdownMenuSubContent` | `@radix-ui/react-dropdown-menu` |
| **Input** | `Input` | — |
| **Label** | `Label` | `@radix-ui/react-label` |
| **Popover** | `Popover`, `PopoverTrigger`, `PopoverContent` | `@radix-ui/react-popover` |
| **RadioGroup** | `RadioGroup`, `RadioGroupItem` | `@radix-ui/react-radio-group` |
| **ScrollArea** | `ScrollArea`, `ScrollBar` | `@radix-ui/react-scroll-area` |
| **Select** | `Select`, `SelectTrigger`, `SelectValue`, `SelectContent`, `SelectItem`, `SelectGroup`, `SelectLabel`, `SelectSeparator` | `@radix-ui/react-select` |
| **Separator** | `Separator` | `@radix-ui/react-separator` |
| **Switch** | `Switch` | `@radix-ui/react-switch` |
| **Table** | `Table`, `TableHeader`, `TableBody`, `TableFooter`, `TableRow`, `TableHead`, `TableCell`, `TableCaption` | — |
| **Tabs** | `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent` | `@radix-ui/react-tabs` |
| **Textarea** | `Textarea` | — |
| **Tooltip** | `Tooltip`, `TooltipTrigger`, `TooltipContent`, `TooltipProvider` | `@radix-ui/react-tooltip` |

A `cn` utility is also exported for merging Tailwind classes:

```tsx
import { cn } from "@prophetdo/beta/ui";
```

### `@prophetdo/beta/share`

Pre-built sharing UI: a `ShareButton` trigger and a configurable `ShareDialog` for managing permissions on any entity. The dialog integrates with Prophet's user system automatically and supports any scope model your app defines.

```tsx
import { ShareButton, ShareDialog } from "@prophetdo/beta/share";
```

| Export | Description |
| --- | --- |
| `ShareButton` | A styled button with the share icon. Accepts `variant` (`"ghost"` \| `"outline"`) and an optional `label`. |
| `ShareDialog` | A configurable permissions dialog. Apps provide scopes, current members, and mutation callbacks. |

#### ShareButton

Drop-in share trigger:

```tsx
import { ShareButton } from "@prophetdo/beta/share";

<ShareButton onClick={() => setShareOpen(true)} />
<ShareButton variant="outline" label="Share" onClick={() => setShareOpen(true)} />
```

#### ShareDialog

The dialog handles user search, org-wide access, and member management. You configure it by providing your app's scopes and wiring up mutations. If the built-in props don't cover your needs (e.g. custom permission levels, approval workflows, or app-specific UI), treat this component as a starting point — copy the source into your app and customize it to match your exact permission and scope model.

```tsx
import { useState } from "react";
import { useMembers, useMutation } from "@prophetdo/beta/react";
import { ShareButton, ShareDialog } from "@prophetdo/beta/share";
import type { ShareScope } from "@prophetdo/beta/share";

// Define the scopes your app supports
const SCOPES: ShareScope[] = [
  { name: "editor", label: "Editor" },
  { name: "viewer", label: "Viewer" },
];

function DocumentToolbar({ documentId }: { documentId: string }) {
  const [shareOpen, setShareOpen] = useState(false);
  const { data: members } = useMembers(DocumentMembers, { entity: documentId });
  const shareUser = useMutation("shareDocument");
  const unshareUser = useMutation("unshareDocument");
  const shareOrg = useMutation("shareDocumentOrg");
  const unshareOrg = useMutation("unshareDocumentOrg");

  return (
    <>
      <ShareButton variant="outline" label="Share" onClick={() => setShareOpen(true)} />

      <ShareDialog
        open={shareOpen}
        onOpenChange={setShareOpen}
        title="Share Document"
        scopes={SCOPES}
        members={members ?? {}}
        onShareUser={(userId, scope) =>
          shareUser({ document: documentId, user: userId, scope })
        }
        onUnshareUser={(userId) =>
          unshareUser({ document: documentId, user: userId })
        }
        onShareOrg={(scope) =>
          shareOrg({ document: documentId, scope })
        }
        onUnshareOrg={() =>
          unshareOrg({ document: documentId })
        }
      />
    </>
  );
}
```

**Props reference:**

| Prop | Type | Description |
| --- | --- | --- |
| `open` | `boolean` | Whether the dialog is visible |
| `onOpenChange` | `(open: boolean) => void` | Called when dialog should open/close |
| `title` | `string` | Dialog header text (default: `"Share"`) |
| `scopes` | `ShareScope[]` | Available roles, e.g. `[{ name: "editor", label: "Editor" }]` |
| `members` | `Record<string, string[]>` | Current members keyed by scope. Use `"authenticatedUsers"` for org-wide. |
| `ownerScope` | `string` | Which scope represents owners (default: `"owner"`) — owners can't be removed |
| `onShareUser` | `(userId, scope) => void` | Called when a user is invited |
| `onUnshareUser` | `(userId) => void` | Called when a user is removed |
| `onShareOrg` | `(scope) => void` | Called when org-wide access is granted. Omit to hide org sharing. |
| `onUnshareOrg` | `() => void` | Called when org-wide access is revoked |

### `@prophetdo/beta/styles.css`

Base stylesheet that includes Prophet's theme variables, Tailwind base layers, and bundled font-face rules (Barlow, Zain, JetBrains Mono). Import this once in your app entry point:

```tsx
import "@prophetdo/beta/styles.css";
```

### `@prophetdo/beta/sdk`

The platform SDK for managing Prophet apps, deployments, users, files, and secrets programmatically.

```ts
import { ProphetAPI } from "@prophetdo/beta/sdk";

const api = new ProphetAPI({
  baseUrl: "https://beta.prophet.do",
  token: "your-api-token",
});

const apps = await api.listApps();
```

## Usage Examples

### Subscribing to Data

`useSubscribe` takes a query name (defined in your Prophet manifest) and its arguments, and returns live-updating data:

```tsx
import { useSubscribe } from "@prophetdo/beta/react";

function NotesList({ folderId }: { folderId: string }) {
  const { data: notes, error } = useSubscribe("listNotes", { folder_id: folderId });

  if (error) return <div>Error: {error.message}</div>;
  if (!notes) return <div>Loading...</div>;

  return (
    <ul>
      {notes.map((note) => (
        <li key={note.id}>{note.title}</li>
      ))}
    </ul>
  );
}
```

### Running Mutations

`useMutation` returns a callable function for the named mutation:

```tsx
import { useMutation } from "@prophetdo/beta/react";

function CreateNote({ folderId }: { folderId: string }) {
  const createNote = useMutation("createNote");

  const handleCreate = async () => {
    const result = await createNote({ folder_id: folderId, title: "Untitled" });
    console.log("Created:", result);
  };

  return <button onClick={handleCreate}>New Note</button>;
}
```

### Batch Mutations

Use `useBatchMutation` when you need to execute multiple mutations atomically:

```tsx
import { useBatchMutation } from "@prophetdo/beta/react";

function BulkActions() {
  const { mutate, flush } = useBatchMutation();

  const archiveAll = async (ids: string[]) => {
    for (const id of ids) {
      mutate("archiveNote", { id });
    }
    await flush();
  };

  return <button onClick={() => archiveAll(selectedIds)}>Archive Selected</button>;
}
```

### Presence

Track who's online and broadcast cursor positions or other ephemeral state:

```tsx
import { usePresence } from "@prophetdo/beta/react";

function Cursors({ documentId }: { documentId: string }) {
  const { peers, update } = usePresence("document", documentId, {
    cursor: { x: 0, y: 0 },
  });

  const handleMouseMove = (e: React.MouseEvent) => {
    update({ cursor: { x: e.clientX, y: e.clientY } });
  };

  return (
    <div onMouseMove={handleMouseMove}>
      {peers.map((peer) => (
        <div
          key={peer.userId}
          style={{ position: "absolute", left: peer.data.cursor.x, top: peer.data.cursor.y }}
        >
          {peer.userName}
        </div>
      ))}
    </div>
  );
}
```

### Client-Side Routing

Prophet includes a lightweight client-side router:

```tsx
import { Switch, Route, Link } from "@prophetdo/beta/react";

function App() {
  return (
    <nav>
      <Link href="/">Home</Link>
      <Link href="/settings">Settings</Link>
    </nav>
    <Switch>
      <Route path="/" exact>
        <HomePage />
      </Route>
      <Route path="/notes/:id">
        <NoteDetail />
      </Route>
      <Route path="/settings">
        <Settings />
      </Route>
    </Switch>
  );
}
```

```tsx
import { useParams } from "@prophetdo/beta/react";

function NoteDetail() {
  const { id } = useParams<{ id: string }>();
  const { data: notes } = useSubscribe("getNote", { id });
  // ...
}
```

## License

MIT