Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 45 additions & 45 deletions bundle-size-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,25 @@
{
"name": "@databricks/appkit",
"tarball": {
"packed": 1297373,
"unpacked": 4453717
"packed": 1307872,
"unpacked": 4486933
},
"dist": {
"total": {
"raw": 4438327,
"gzip": 1520942
"raw": 4471543,
"gzip": 1534838
},
"js": {
"raw": 1320443,
"gzip": 468638
"raw": 1331429,
"gzip": 473518
},
"types": {
"raw": 474763,
"gzip": 172113
"raw": 477100,
"gzip": 173213
},
"maps": {
"raw": 2632326,
"gzip": 876373
"raw": 2652219,
"gzip": 884289
},
"css": {
"raw": 0,
Expand All @@ -31,22 +31,22 @@
"raw": 10795,
"gzip": 3818
},
"fileCount": 895
"fileCount": 905
},
"entries": [
{
"id": ".",
"gzip": 111090,
"gzip": 111208,
"composition": {
"initialGzip": 108517,
"initialGzip": 108635,
"lazyGzip": 2573,
"totalGzip": 111090,
"own": 351738,
"totalGzip": 111208,
"own": 352050,
"nodeModules": null,
"chunks": [
{
"label": "index.js",
"gzip": 103847,
"gzip": 103965,
"kind": "initial"
},
{
Expand All @@ -64,17 +64,17 @@
},
{
"id": "./beta",
"gzip": 105499,
"gzip": 108126,
"composition": {
"initialGzip": 105014,
"initialGzip": 107641,
"lazyGzip": 485,
"totalGzip": 105499,
"own": 316145,
"totalGzip": 108126,
"own": 323071,
"nodeModules": null,
"chunks": [
{
"label": "beta.js",
"gzip": 84463,
"gzip": 87090,
"kind": "initial"
},
{
Expand Down Expand Up @@ -127,12 +127,12 @@
},
{
"id": "./testing",
"gzip": 79051,
"gzip": 79171,
"composition": {
"initialGzip": 45993,
"lazyGzip": 33058,
"totalGzip": 79051,
"own": 228781,
"initialGzip": 45994,
"lazyGzip": 33177,
"totalGzip": 79171,
"own": 229093,
"nodeModules": null,
"chunks": [
{
Expand All @@ -142,7 +142,7 @@
},
{
"label": "index.js",
"gzip": 10286,
"gzip": 10287,
"kind": "initial"
},
{
Expand All @@ -152,7 +152,7 @@
},
{
"label": "index.js",
"gzip": 28625,
"gzip": 28744,
"kind": "lazy"
},
{
Expand Down Expand Up @@ -209,25 +209,25 @@
{
"name": "@databricks/appkit-ui",
"tarball": {
"packed": 406741,
"unpacked": 1606513
"packed": 430550,
"unpacked": 1680523
},
"dist": {
"total": {
"raw": 1602568,
"gzip": 546534
"raw": 1676578,
"gzip": 574683
},
"js": {
"raw": 439037,
"gzip": 150094
"raw": 463670,
"gzip": 159291
},
"types": {
"raw": 273603,
"gzip": 100719
"raw": 277992,
"gzip": 102934
},
"maps": {
"raw": 873530,
"gzip": 292465
"raw": 918518,
"gzip": 309202
},
"css": {
"raw": 16398,
Expand All @@ -237,7 +237,7 @@
"raw": 0,
"gzip": 0
},
"fileCount": 554
"fileCount": 570
},
"entries": [
{
Expand Down Expand Up @@ -316,17 +316,17 @@
},
{
"id": "./react/beta",
"gzip": 4994,
"gzip": 11530,
"composition": {
"initialGzip": 4994,
"initialGzip": 21461,
"lazyGzip": 0,
"totalGzip": 4994,
"own": 12631,
"nodeModules": 0,
"totalGzip": 21461,
"own": 31792,
"nodeModules": 30230,
"chunks": [
{
"label": "beta.js",
"gzip": 4994,
"gzip": 21461,
"kind": "initial"
}
]
Expand Down
5 changes: 5 additions & 0 deletions docs/docs/api/appkit/Variable.design.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions docs/docs/api/appkit/index.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions docs/docs/api/appkit/typedoc-sidebar.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

129 changes: 129 additions & 0 deletions docs/docs/plugins/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
---
sidebar_position: 20
---

# Design plugin

<!-- AUTO-GENERATED: stability-banner-start -->
:::warning Beta plugin
This plugin is currently **beta**. APIs may change between minor releases. Import from `@databricks/appkit/beta`. See [Plugin Stability Tiers](./stability.md).
:::
<!-- AUTO-GENERATED: stability-banner-end -->

A dev-only design canvas and element picker for working with a coding agent. It only runs with `NODE_ENV=development`. In any other environment it does nothing, and `/design` returns 404.

## Basic usage

```ts
import { createApp, server } from "@databricks/appkit";
import { design } from "@databricks/appkit/beta";

await createApp({
plugins: [server(), design()],
});
```

## Configuration

| Option | Type | Description |
| ------ | ---- | ----------- |
| `builderOrigin` | `string \| string[]` | Origin(s) of the app builder that embeds the app, for example `"https://builder.example.com"`. When `/design` runs inside that window, each send is also posted to it (see [Embedding in an app builder](#embedding-in-an-app-builder)). Must be a bare origin; `"*"` is rejected. |

## The `/design` canvas

`/design` renders every PascalCase export of every file under `client/src/design/`, with no props, one frame each. Put two kinds of components there:

- **Drafts**: design-only UI that may later be moved into the real app.
- **Previews**: wrappers that render the app's real components with mock data.

```tsx
// client/src/design/OrderCard.preview.tsx
import { OrderCard } from "../components/OrderCard";

export function OrderCardShipped() {
return <OrderCard order={{ id: "42", status: "shipped", total: 99 }} />;
}
```

A frame that throws shows its error in place. It doesn't break the rest of the canvas.

## Selecting elements

Selecting only happens on `/design`; app pages are left untouched. Click **Select** in the canvas header, then click elements to add or remove them (Escape stops selecting). Each selected element gets a numbered outline that matches its row in the send panel. Add an optional note and click **Send to agent**.

Each element is traced back to its source `file:line:col`: the dev server tags native JSX elements with `data-source`, and the design plugin also tags component call sites (such as `<Button>` from a library) with `data-appkit-source`. The picker reads these, plus the nearest React component and its current props, from React's fiber tree. Screenshots include images, canvases, and CSS background images. Web fonts and cross-origin resources served without CORS are not embedded.

## Handing selections to the agent

Each send is appended to `.appkit/design-selection.json`, so selections survive restarts. Add `.appkit/` to `.gitignore`.

```json
{
"selections": [
{
"id": "80b453fd-…",
"ts": "2026-10-20T12:22:45.026Z",
"note": "make the button primary",
"url": "/design",
"elements": [
{
"component": "Button",
"stack": ["Button", "ProbeCard"],
"source": "client/src/design/Probe.tsx:5:4",
"props": { "variant": "secondary" },
"tag": "button",
"text": "Probe button",
"screenshot": ".appkit/design-screenshots/80b453fd-…-0.jpeg"
}
]
}
]
}
```

Screenshots are saved as image files next to it, so an agent can open them directly. The agent can read the file itself, or use these routes:

| Method | Path | Description |
| ------ | ---- | ----------- |
| `GET` | `/api/design/selection` | Read all pending selections |
| `POST` | `/api/design/selection` | Add a selection (used by the picker) |
| `DELETE` | `/api/design/selection` | Clear selections and screenshots once applied |
| `GET` | `/api/design/screenshots/:name` | A saved screenshot image |

## Embedding in an app builder

When the app runs inside an app builder (in an iframe, or in a window it opened) and `builderOrigin` is set, each send is also posted to that window (`window.parent`, else `window.opener`) with `window.postMessage`. The message is targeted at exactly the configured origins, so the browser drops it if the window is on any other origin. The file and the routes above work the same either way.

```ts
import type { DesignSelectionMessage } from "@databricks/appkit-ui/react/beta";

// In the app builder:
window.addEventListener("message", (event) => {
if (event.origin !== "http://localhost:8000") return; // the app's dev server
const message = event.data as DesignSelectionMessage;
if (message?.type !== "appkit:design-selection" || message.version !== 1) return;
// message.note, message.selections[i].source, .props, .screenshot, ...
});
```

```ts
interface DesignSelectionMessage {
type: "appkit:design-selection";
version: 1;
id?: string; // id in .appkit/design-selection.json
url: string; // page the elements were picked on
note?: string;
selections: Array<{
component?: string; // nearest React component
stack?: string[]; // owning components, innermost first
source?: string; // "file:line:col"
props?: Record<string, unknown>;
tag: string;
text?: string;
screenshot?: string; // JPEG data URL
screenshotUrl?: string; // same image, GET from the dev server
}>;
}
```

The screenshot is inline as a data URL. `screenshotUrl` points at `GET /api/design/screenshots/:name` on the app's dev server.
Loading
Loading