diff --git a/docs/docs/api/appkit-ui/styling.md b/docs/docs/api/appkit-ui/styling.md
new file mode 100644
index 00000000..3736962b
--- /dev/null
+++ b/docs/docs/api/appkit-ui/styling.md
@@ -0,0 +1,82 @@
+---
+sidebar_position: 4
+---
+
+# Styling
+
+This guide covers how to style AppKit UI components using CSS variables and theming.
+
+## CSS import
+
+In your main CSS file, import the AppKit UI styles:
+
+```css
+@import "@databricks/appkit-ui/styles.css";
+```
+
+This provides a default theme for your app using CSS variables.
+
+## Customizing theme
+
+AppKit UI uses CSS variables for theming, supporting both light and dark modes automatically.
+
+### Full variable list
+
+You can customize the theme by overriding CSS variables. See the [CSS variables](https://github.com/databricks/appkit/blob/main/packages/appkit-ui/src/react/styles/globals.css) for the full list of variables.
+
+:::warning Important
+If you change any variable, you must change it for **both light and dark mode** to ensure consistent appearance across color schemes.
+:::
+
+## Color system
+
+AppKit UI uses the OKLCH color space for better perceptual uniformity. The format is:
+
+```
+oklch(lightness chroma hue)
+```
+
+Where:
+- **lightness**: 0-1 (0 = black, 1 = white)
+- **chroma**: 0-0.4 (saturation)
+- **hue**: 0-360 (color angle)
+
+## Semantic color variables
+
+### Core colors
+
+- `--background` / `--foreground` - Main background and text
+- `--card` / `--card-foreground` - Card backgrounds
+- `--popover` / `--popover-foreground` - Popover/dropdown backgrounds
+
+### Interactive colors
+
+- `--primary` / `--primary-foreground` - Primary actions
+- `--secondary` / `--secondary-foreground` - Secondary actions
+- `--muted` / `--muted-foreground` - Muted/disabled states
+- `--accent` / `--accent-foreground` - Accent highlights
+
+### Status colors
+
+- `--destructive` / `--destructive-foreground` - Destructive actions
+- `--success` / `--success-foreground` - Success states
+- `--warning` / `--warning-foreground` - Warning states
+
+### UI elements
+
+- `--border` - Border colors
+- `--input` - Input field borders
+- `--ring` - Focus ring colors
+- `--radius` - Border radius
+
+### Charts
+
+- `--chart-1` through `--chart-5` - Chart color palette
+
+### Sidebar
+
+- `--sidebar-*` - Sidebar-specific colors
+
+## See also
+
+- [API Reference](/docs/api/appkit-ui) - Complete UI components API documentation
diff --git a/docs/docs/app-management.mdx b/docs/docs/app-management.mdx
index 75d484bf..56c60354 100644
--- a/docs/docs/app-management.mdx
+++ b/docs/docs/app-management.mdx
@@ -1,5 +1,5 @@
---
-sidebar_position: 3
+sidebar_position: 2
---
import Prerequisites from './_prerequisites.mdx';
@@ -16,12 +16,10 @@ See the [Quick start](./index.md) section to create a new Databricks app with Ap
## Configuration
-Before deploying your app, configure it according to your needs. See the [Databricks Apps Configuration](https://docs.databricks.com/aws/en/dev-tools/databricks-apps/configuration) documentation for details on:
-
-- App configuration file (`app.yaml`)
-- Environment variables
-- Authorization and permissions
-- Networking configuration
+Before deploying your app, you need to configure it. See the [Configuration guide](./configuration.mdx) for details on:
+- `app.yaml` configuration and command specification
+- Environment variables and SQL warehouse binding
+- Local development authentication options
## Deploy app
diff --git a/docs/docs/configuration.mdx b/docs/docs/configuration.mdx
new file mode 100644
index 00000000..c7ca9a32
--- /dev/null
+++ b/docs/docs/configuration.mdx
@@ -0,0 +1,145 @@
+---
+sidebar_position: 5
+---
+
+# Configuration
+
+This guide covers environment variables and configuration options for AppKit applications.
+
+## App deployment configuration
+
+### `app.yaml` file
+
+The `app.yaml` file configures your app's runtime environment in Databricks Apps.
+
+**Basic example:**
+
+```yaml
+command:
+ - node
+ - build/index.mjs
+env:
+ - name: DATABRICKS_WAREHOUSE_ID
+ valueFrom: sql-warehouse
+```
+
+### Command specification
+
+Specify how to start your app in the `command` field:
+
+```yaml
+command:
+ - node
+ - build/index.mjs
+```
+
+This runs the production build of your AppKit server (created by `npm run build`).
+
+### Binding a SQL warehouse
+
+To use the analytics plugin, bind a SQL warehouse in your `app.yaml`:
+
+```yaml
+env:
+ - name: DATABRICKS_WAREHOUSE_ID
+ valueFrom: sql-warehouse
+```
+
+This makes the warehouse ID available to your app at runtime. The `valueFrom: sql-warehouse` directive tells Databricks Apps to inject the configured warehouse ID.
+
+For advanced configuration options (authorization, networking, resource limits), see the [Databricks Apps Configuration](https://docs.databricks.com/aws/en/dev-tools/databricks-apps/configuration) documentation.
+
+## Environment variables
+
+### Required for Databricks Apps deployment
+
+These are typically **provided by Databricks Apps runtime** (exact set can vary by platform/version):
+
+| Variable | Description |
+|----------|-------------|
+| `DATABRICKS_HOST` | Workspace URL (e.g. `https://xxx.cloud.databricks.com`) |
+| `DATABRICKS_APP_PORT` | Port to bind (default: `8000`) |
+| `DATABRICKS_APP_NAME` | App name in Databricks |
+
+### Required for SQL queries (analytics plugin)
+
+| Variable | Description | How to Set |
+|----------|-------------|------------|
+| `DATABRICKS_WAREHOUSE_ID` | SQL warehouse ID | In `app.yaml`: `valueFrom: sql-warehouse` |
+
+See the [App deployment configuration](#app-deployment-configuration) section above for details on how to bind this variable.
+
+### Optional variables
+
+| Variable | Description | Default |
+|----------|-------------|---------|
+| `DATABRICKS_WORKSPACE_ID` | Workspace ID | Auto-fetched from API |
+| `NODE_ENV` | `"development"` or `"production"` | — |
+| `FLASK_RUN_HOST` | Host to bind | `0.0.0.0` |
+
+### Telemetry (optional)
+
+| Variable | Description |
+|----------|-------------|
+| `OTEL_EXPORTER_OTLP_ENDPOINT` | OpenTelemetry collector endpoint |
+| `OTEL_SERVICE_NAME` | Service name for traces |
+
+## Local development authentication
+
+For local development, you need to authenticate with Databricks. Choose one of the following options:
+
+### Option 1: Databricks CLI authentication (recommended)
+
+Configure authentication once using the Databricks CLI:
+
+```bash
+databricks auth login --host [host] --profile [profile-name]
+```
+
+If you use `DEFAULT` as the profile name, you can run your dev server directly:
+
+```bash
+npm run dev
+```
+
+To run with a specific profile, set the `DATABRICKS_CONFIG_PROFILE` environment variable:
+
+```bash
+DATABRICKS_CONFIG_PROFILE=my-profile npm run dev
+```
+
+Note: Some Databricks SDK versions use `DATABRICKS_PROFILE` instead of `DATABRICKS_CONFIG_PROFILE`.
+
+### Option 2: Environment variables
+
+```bash
+export DATABRICKS_HOST="https://xxx.cloud.databricks.com"
+export DATABRICKS_TOKEN="dapi..."
+export DATABRICKS_WAREHOUSE_ID="abc123..."
+npm run dev
+```
+
+### Option 3: `.env` file (auto-loaded by AppKit)
+
+Create a `.env` file in your project root (add to `.gitignore`!):
+
+```bash
+DATABRICKS_HOST=https://xxx.cloud.databricks.com
+DATABRICKS_TOKEN=dapi...
+DATABRICKS_WAREHOUSE_ID=abc123...
+```
+
+Then run:
+
+```bash
+npm run dev
+```
+
+## Advanced configuration
+
+For advanced Databricks Apps configuration (authorization, networking, resource limits), refer to the [Databricks Apps Configuration](https://docs.databricks.com/aws/en/dev-tools/databricks-apps/configuration) documentation.
+
+## See also
+
+- [App management](./app-management.mdx) - Deploying and managing apps
+- [Plugins](./plugins.md) - Plugin configuration options
diff --git a/docs/docs/development/_category_.json b/docs/docs/development/_category_.json
new file mode 100644
index 00000000..a127caf5
--- /dev/null
+++ b/docs/docs/development/_category_.json
@@ -0,0 +1,11 @@
+{
+ "label": "Development",
+ "position": 7,
+ "collapsible": true,
+ "collapsed": false,
+ "link": {
+ "type": "generated-index",
+ "title": "Development",
+ "description": "Guides for developing AppKit applications"
+ }
+}
diff --git a/docs/docs/development/llm-guide.mdx b/docs/docs/development/llm-guide.mdx
new file mode 100644
index 00000000..6df10b17
--- /dev/null
+++ b/docs/docs/development/llm-guide.mdx
@@ -0,0 +1,82 @@
+---
+sidebar_position: 8
+---
+
+# LLM Guide
+
+import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
+
+export function LlmsTxtLink({ children = '`llms.txt`' }) {
+ const { siteConfig } = useDocusaurusContext();
+ const llmsTxtUrl = `${siteConfig.baseUrl}llms.txt`;
+ return {children};
+}
+
+This document provides prescriptive guidance for AI coding assistants generating code with Databricks AppKit. It is intentionally opinionated to ensure consistent, production-ready code generation.
+
+:::note
+This file contains just a subset of the LLM guidance.
+To get the complete guidance, see the file for full guidance based on the AppKit documentation.
+:::
+
+## High-level mission
+
+Build **full-stack TypeScript apps** on Databricks using:
+
+- **Backend**: `@databricks/appkit`
+- **Frontend**: `@databricks/appkit-ui`
+- **Analytics**: SQL files in `config/queries/*.sql` executed via the AppKit analytics plugin
+
+This guide is designed to work even when you *do not* have access to the AppKit source repo. Prefer only public package APIs and portable project structures.
+
+## Hard rules (LLM guardrails)
+
+- **Do not invent APIs**. If unsure, stick to the patterns shown in the documentation and only use documented exports from `@databricks/appkit` and `@databricks/appkit-ui`.
+- **`createApp()` is async**. Prefer **top-level `await createApp(...)`**. If you can't, use `void createApp(...)` and do not ignore promise rejection.
+- **Always memoize query parameters** passed to `useAnalyticsQuery` / charts to avoid refetch loops.
+- **Always handle loading/error/empty states** in UI (use `Skeleton`, error text, empty state).
+- **Always use `sql.*` helpers** for query parameters (do not pass raw strings/numbers unless the query expects none).
+- **Never construct SQL strings dynamically**. Use parameterized queries with `:paramName`.
+- **Never use `require()`**. Use ESM `import/export`.
+
+## TypeScript import rules
+
+If your `tsconfig.json` uses `"verbatimModuleSyntax": true`, **always use `import type` for type-only imports** (otherwise builds can fail in strict setups):
+
+```ts
+import type { ReactNode } from "react";
+import { useMemo } from "react";
+```
+
+## LLM checklist (before finalizing code)
+
+### Project setup
+
+- `package.json` has `"type": "module"`
+- `tsx` is in devDependencies for dev server
+- `dev` script uses `NODE_ENV=development tsx watch server/index.ts`
+- `client/index.html` exists with `
` and script pointing to `client/src/main.tsx`
+
+### Backend
+
+- `await createApp({ plugins: [...] })` is used (or `void createApp` with intent)
+- `server()` is included (always)
+- If using SQL: `analytics({})` included + `config/queries/*.sql` present
+- Queries use `:param` placeholders, and params are passed from UI using `sql.*`
+- If query needs workspace scoping: uses `:workspaceId`
+
+### Frontend
+
+- `useMemo` wraps parameters objects
+- Loading/error/empty states are explicit
+- Charts use `format="auto"` unless you have a reason to force `"json"`/`"arrow"`
+- Charts use props (`xKey`, `yKey`, `colors`) NOT children (they're ECharts-based, not Recharts)
+- If using tooltips: root is wrapped with ``
+
+### Never
+
+- Don't build SQL strings manually
+- Don't pass untyped raw params for annotated queries
+- Don't ignore `createApp()`'s promise
+- Don't invent UI components not listed in the documentation
+- Don't pass Recharts children (``, ``, etc.) to AppKit chart components
diff --git a/docs/docs/development/project-setup.mdx b/docs/docs/development/project-setup.mdx
new file mode 100644
index 00000000..7f271ecb
--- /dev/null
+++ b/docs/docs/development/project-setup.mdx
@@ -0,0 +1,235 @@
+---
+sidebar_position: 4
+---
+
+# Project setup
+
+This guide covers the recommended project structure and scaffolding for AppKit applications.
+
+## Canonical project layout
+
+Recommended structure (client/server split):
+
+```
+my-app/
+├── server/
+│ ├── index.ts # backend entry point (AppKit)
+│ └── .env # optional local dev env vars (do not commit)
+├── client/
+│ ├── index.html
+│ ├── vite.config.ts
+│ └── src/
+│ ├── main.tsx
+│ └── App.tsx
+├── config/
+│ └── queries/
+│ └── my_query.sql
+├── app.yaml
+├── package.json
+└── tsconfig.json
+```
+
+### Layout rationale
+
+The AppKit `server()` plugin automatically serves:
+- **Dev**: Vite dev server (HMR) from `client/`
+- **Prod**: static files from `client/dist` (built by Vite)
+
+## Project scaffolding
+
+### `package.json`
+
+```json
+{
+ "name": "my-app",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "NODE_ENV=development tsx watch server/index.ts",
+ "build": "npm run build:server && npm run build:client",
+ "build:server": "tsdown --out-dir build server/index.ts",
+ "build:client": "tsc -b && vite build --config client/vite.config.ts",
+ "start": "node build/index.mjs"
+ },
+ "dependencies": {
+ "@databricks/appkit": "^0.1.2",
+ "@databricks/appkit-ui": "^0.1.2",
+ "react": "^19.2.3",
+ "react-dom": "^19.2.3"
+ },
+ "devDependencies": {
+ "@types/node": "^20.0.0",
+ "@types/react": "^19.0.0",
+ "@types/react-dom": "^19.0.0",
+ "@vitejs/plugin-react": "^5.1.1",
+ "tsdown": "^0.15.7",
+ "tsx": "^4.19.0",
+ "typescript": "~5.6.0",
+ "vite": "^7.2.4"
+ }
+}
+```
+
+### `client/index.html`
+
+```html
+
+
+
+
+
+ My App
+
+
+
+
+
+
+```
+
+### `client/src/main.tsx`
+
+```tsx
+import { StrictMode } from "react";
+import { createRoot } from "react-dom/client";
+import App from "./App";
+
+createRoot(document.getElementById("root")!).render(
+
+
+ ,
+);
+```
+
+### `client/src/App.tsx` (Minimal)
+
+```tsx
+export default function App() {
+ return (
+
+
My App
+
+ );
+}
+```
+
+### `client/vite.config.ts`
+
+```ts
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
+
+export default defineConfig({
+ plugins: [react()],
+});
+```
+
+### `tsconfig.json`
+
+```json
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "jsx": "react-jsx",
+ "strict": true,
+ "skipLibCheck": true,
+ "noEmit": true,
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true
+ },
+ "include": ["server", "client/src"]
+}
+```
+
+### `server/index.ts`
+
+```ts
+import { createApp, server } from "@databricks/appkit";
+
+await createApp({
+ plugins: [server()],
+});
+```
+
+## Running the app
+
+```bash
+# Install dependencies
+npm install
+
+# Development (starts backend + Vite dev server)
+npm run dev
+
+# Production build
+npm run build
+npm start
+```
+
+## Integrating into an existing app
+
+If you already have a React/Vite app and want to add AppKit:
+
+### 1. Install dependencies
+
+```bash
+npm install @databricks/appkit @databricks/appkit-ui react react-dom
+npm install -D tsx tsdown vite @vitejs/plugin-react typescript
+```
+
+If you don't already have a `client/` folder, create one and move your Vite app into it:
+- Move `index.html` → `client/index.html`
+- Move `vite.config.ts` → `client/vite.config.ts`
+- Move `src/` → `client/src/`
+
+### 2. Create `server/index.ts` (New File)
+
+```ts
+import { createApp, server } from "@databricks/appkit";
+
+await createApp({
+ plugins: [server()],
+});
+```
+
+### 3. Update `package.json` Scripts
+
+```json
+{
+ "scripts": {
+ "dev": "NODE_ENV=development tsx watch server/index.ts",
+ "build": "npm run build:server && npm run build:client",
+ "build:server": "tsdown --out-dir build server/index.ts",
+ "build:client": "tsc -b && vite build --config client/vite.config.ts",
+ "start": "node build/index.mjs"
+ }
+}
+```
+
+### 4. Complete setup
+
+AppKit's server plugin will automatically serve your Vite app in dev mode and `client/dist` in production. If your Vite app must stay at the repo root (no `client/` folder), AppKit can still work, but the recommended layout is `client/` + `server/`.
+
+## Adding analytics to an existing app
+
+To add SQL query execution capabilities:
+
+```ts
+// server/index.ts
+import { createApp, server, analytics } from "@databricks/appkit";
+
+await createApp({
+ plugins: [server(), analytics()],
+});
+```
+
+Then create `config/queries/` and add your `.sql` files.
+
+## See also
+
+- [Local development](./local-development.mdx) - Running the dev server
+- [Configuration](../configuration.mdx) - Environment variables
+- [Plugins](../plugins.md) - Plugin configuration
diff --git a/docs/docs/development/type-generation.mdx b/docs/docs/development/type-generation.mdx
new file mode 100644
index 00000000..2de17706
--- /dev/null
+++ b/docs/docs/development/type-generation.mdx
@@ -0,0 +1,108 @@
+---
+sidebar_position: 5
+---
+
+# Type generation
+
+AppKit can automatically generate TypeScript types for your SQL queries, providing end-to-end type safety from database to UI.
+
+## Goal
+
+Generate `client/src/appKitTypes.d.ts` so query keys, parameters, and result rows are type-safe.
+
+## Vite plugin: `appKitTypesPlugin`
+
+The recommended approach is to use the Vite plugin, which watches your SQL files and regenerates types automatically during development.
+
+### Configuration
+
+- `outFile?: string` - Output file path (default: `src/appKitTypes.d.ts`)
+- `watchFolders?: string[]` - Folders to watch for SQL files (default: `["../config/queries"]`)
+
+### Example
+
+```ts
+// client/vite.config.ts
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
+import { appKitTypesPlugin } from "@databricks/appkit";
+
+export default defineConfig({
+ plugins: [
+ react(),
+ appKitTypesPlugin({
+ outFile: "src/appKitTypes.d.ts",
+ watchFolders: ["../config/queries"],
+ }),
+ ],
+});
+```
+
+### Important nuance
+
+When the frontend is served through AppKit in dev mode, AppKit's dev server already includes `appKitTypesPlugin()` internally. You still want it in your client build pipeline if you run `vite build` separately.
+
+## CLI: `appkit-generate-types`
+
+For manual type generation or CI/CD pipelines, use the CLI command:
+
+```bash
+# Requires DATABRICKS_WAREHOUSE_ID (or pass as 3rd arg)
+npx appkit-generate-types [rootDir] [outFile] [warehouseId]
+```
+
+### Examples
+
+- Generate types using warehouse ID from environment
+
+ ```bash
+ npx appkit-generate-types . client/src/appKitTypes.d.ts
+ ```
+
+- Generate types using warehouse ID explicitly
+
+ ```bash
+ npx appkit-generate-types . client/src/appKitTypes.d.ts abc123...
+ ```
+
+- Force regeneration (skip cache)
+
+ ```bash
+ npx appkit-generate-types --no-cache
+ ```
+
+## How it works
+
+The type generator:
+
+1. Scans your `config/queries/` folder for `.sql` files
+2. Parses SQL parameter annotations (e.g., `-- @param startDate DATE`)
+3. Connects to your Databricks SQL Warehouse to infer result column types
+4. Generates TypeScript interfaces for query parameters and results
+5. Creates a `QueryRegistry` type for type-safe query execution
+
+## Using generated types
+
+Once types are generated, your IDE will provide autocomplete and type checking:
+
+```tsx
+import { useAnalyticsQuery } from "@databricks/appkit-ui/react";
+import { sql } from "@databricks/appkit-ui/js";
+
+// TypeScript knows "users_list" is a valid query key
+// and what parameters it expects
+const { data } = useAnalyticsQuery("users_list", {
+ status: sql.string("active"),
+ limit: sql.number(50),
+});
+
+// TypeScript knows the shape of the result rows
+data?.forEach(row => {
+ console.log(row.email); // ✓ autocomplete works
+});
+```
+
+## See also
+
+- [Plugins](../plugins.md) - Analytics plugin configuration
+- [API Reference](/docs/api/appkit-ui) - Complete UI components API documentation
diff --git a/docs/docs/plugins.md b/docs/docs/plugins.md
index f9159cbd..d93e9208 100644
--- a/docs/docs/plugins.md
+++ b/docs/docs/plugins.md
@@ -22,6 +22,63 @@ Provides HTTP server capabilities with development and production modes.
The Server plugin uses the deferred initialization phase to access routes from other plugins.
+#### What it does
+
+- Starts an Express server (default `host=0.0.0.0`, `port=8000`)
+- Mounts plugin routes under `/api//...`
+- Adds `/health` endpoint (returns `{ status: "ok" }`)
+- Serves frontend:
+ - **Development** (`NODE_ENV=development`): runs a Vite dev server in middleware mode
+ - **Production**: auto-detects static frontend directory (checks `dist`, `client/dist`, `build`, `public`, `out`)
+
+#### Minimal server example
+
+The smallest valid AppKit server:
+
+```ts
+// server/index.ts
+import { createApp, server } from "@databricks/appkit";
+
+await createApp({
+ plugins: [server()],
+});
+```
+
+#### Manual server start example
+
+When you need to extend Express with custom routes:
+
+```ts
+import { createApp, server } from "@databricks/appkit";
+
+const appkit = await createApp({
+ plugins: [server({ autoStart: false })],
+});
+
+appkit.server.extend((app) => {
+ app.get("/custom", (_req, res) => res.json({ ok: true }));
+});
+
+await appkit.server.start();
+```
+
+#### Configuration options
+
+```ts
+import { createApp, server } from "@databricks/appkit";
+
+await createApp({
+ plugins: [
+ server({
+ port: 8000, // default: Number(process.env.DATABRICKS_APP_PORT) || 8000
+ host: "0.0.0.0", // default: process.env.FLASK_RUN_HOST || "0.0.0.0"
+ autoStart: true, // default: true
+ staticPath: "dist", // optional: force a specific static directory
+ }),
+ ],
+});
+```
+
### Analytics plugin
Enables SQL query execution against Databricks SQL Warehouses.
@@ -33,7 +90,104 @@ Enables SQL query execution against Databricks SQL Warehouses.
- Built-in caching and retry logic
- Server-Sent Events (SSE) streaming
-Store SQL queries in `config/queries/` directory and use parameterized queries with the [`sql`](api/appkit/Variable.sql.md) helper for type safety.
+#### Basic usage
+
+```ts
+import { analytics, createApp, server } from "@databricks/appkit";
+
+await createApp({
+ plugins: [server(), analytics({})],
+});
+```
+
+#### Where queries live
+
+- Put `.sql` files in `config/queries/`
+- Query key is the filename without `.sql` (e.g. `spend_summary.sql` → `"spend_summary"`)
+
+#### SQL parameters
+
+Use `:paramName` placeholders and optionally annotate parameter types using SQL comments:
+
+```sql
+-- @param startDate DATE
+-- @param endDate DATE
+-- @param limit NUMERIC
+SELECT ...
+WHERE usage_date BETWEEN :startDate AND :endDate
+LIMIT :limit
+```
+
+**Supported `-- @param` types** (case-insensitive):
+- `STRING`, `NUMERIC`, `BOOLEAN`, `DATE`, `TIMESTAMP`, `BINARY`
+
+#### Server-injected parameters
+
+`:workspaceId` is **injected by the server** and **must not** be annotated:
+
+```sql
+WHERE workspace_id = :workspaceId
+```
+
+#### HTTP endpoints
+
+The analytics plugin exposes these endpoints (mounted under `/api/analytics`):
+
+- `POST /api/analytics/query/:query_key`
+- `POST /api/analytics/users/me/query/:query_key`
+- `GET /api/analytics/arrow-result/:jobId`
+- `GET /api/analytics/users/me/arrow-result/:jobId`
+
+#### Format options
+
+- `format: "JSON"` (default) returns JSON rows
+- `format: "ARROW"` returns an Arrow "statement_id" payload over SSE, then the client fetches binary Arrow from `/api/analytics/arrow-result/:jobId`
+
+### Execution context and `asUser(req)`
+
+AppKit manages Databricks authentication via two contexts:
+
+- **ServiceContext** (singleton): Initialized at app startup with service principal credentials
+- **ExecutionContext**: Determined at runtime - either service principal or user context
+
+#### Headers for user context
+
+- `x-forwarded-user`: required in production; identifies the user
+- `x-forwarded-access-token`: required for user token passthrough
+
+#### Using `asUser(req)` for user-scoped operations
+
+The `asUser(req)` pattern allows plugins to execute operations using the requesting user's credentials:
+
+```ts
+// In a custom plugin route handler
+router.post("/users/me/data", async (req, res) => {
+ // Execute as the user (uses their Databricks permissions)
+ const result = await this.asUser(req).query("SELECT ...");
+ res.json(result);
+});
+
+// Service principal execution (default)
+router.post("/system/data", async (req, res) => {
+ const result = await this.query("SELECT ...");
+ res.json(result);
+});
+```
+
+#### Context helper functions
+
+Exported from `@databricks/appkit`:
+
+- `getExecutionContext()`: Returns current context (user or service)
+- `getCurrentUserId()`: Returns user ID in user context, service user ID otherwise
+- `getWorkspaceClient()`: Returns the appropriate WorkspaceClient for current context
+- `getWarehouseId()`: `Promise` (from `DATABRICKS_WAREHOUSE_ID` or auto-selected in dev)
+- `getWorkspaceId()`: `Promise` (from `DATABRICKS_WORKSPACE_ID` or fetched)
+- `isInUserContext()`: Returns `true` if currently executing in user context
+
+#### Development mode behavior
+
+In local development (`NODE_ENV=development`), if `asUser(req)` is called without a user token, it logs a warning and falls back to the service principal.
## Using plugins
@@ -54,35 +208,40 @@ For complete configuration options, see [`createApp`](api/appkit/Function.create
## Creating custom plugins
-Extend the [`Plugin`](api/appkit/Class.Plugin.md) class and export with `toPlugin()`:
+If you need custom API routes or background logic, implement an AppKit plugin.
-```typescript
-import { Plugin, toPlugin } from "@databricks/app-kit";
+### Basic plugin example
-interface MyPluginConfig {
- apiKey?: string;
-}
+Extend the [`Plugin`](api/appkit/Class.Plugin.md) class and export with `toPlugin()`:
-export class MyPlugin extends Plugin {
- name = "myPlugin";
- envVars = ["MY_API_KEY"];
+```typescript
+import { Plugin, toPlugin } from "@databricks/appkit";
+import type express from "express";
- async setup() {
- // Initialize your plugin
- }
+class MyPlugin extends Plugin {
+ name = "my-plugin";
+ envVars = []; // list required env vars here
- async shutdown() {
- // Clean up resources
+ injectRoutes(router: express.Router) {
+ this.route(router, {
+ name: "hello",
+ method: "get",
+ path: "/hello",
+ handler: async (_req, res) => {
+ res.json({ ok: true });
+ },
+ });
}
}
-export const myPlugin = toPlugin(
+export const myPlugin = toPlugin, "my-plugin">(
MyPlugin,
- "myPlugin"
+ "my-plugin",
);
```
-**Key extension points:**
+### Key extension points
+
- **Route injection**: Implement `injectRoutes()` to add custom endpoints using [`IAppRouter`](api/appkit/TypeAlias.IAppRouter.md)
- **Lifecycle hooks**: Override `setup()`, `shutdown()`, and `validateEnv()` methods
- **Shared services**:
@@ -92,6 +251,38 @@ export const myPlugin = toPlugin(
See the [`Plugin`](api/appkit/Class.Plugin.md) API reference for complete documentation.
+## Caching
+
+AppKit provides both global and plugin-level caching capabilities.
+
+### Global cache configuration
+
+```ts
+await createApp({
+ plugins: [server(), analytics({})],
+ cache: {
+ enabled: true,
+ ttl: 3600, // seconds
+ strictPersistence: false,
+ },
+});
+```
+
+Storage auto-selects **Lakebase persistent cache when healthy**, otherwise falls back to in-memory.
+
+### Plugin-level caching
+
+Inside a Plugin subclass:
+
+```ts
+const value = await this.cache.getOrExecute(
+ ["my-plugin", "data", userId],
+ async () => expensiveWork(),
+ userKey,
+ { ttl: 300 },
+);
+```
+
## Plugin phases
Plugins initialize in three phases:
diff --git a/docs/docusaurus.config.ts b/docs/docusaurus.config.ts
index 0e0e7994..cc93a3ab 100644
--- a/docs/docusaurus.config.ts
+++ b/docs/docusaurus.config.ts
@@ -3,6 +3,7 @@ import path from "node:path";
import type { Config } from "@docusaurus/types";
import type * as Preset from "@docusaurus/preset-classic";
import webpack from "webpack";
+import type { PluginOptions } from "@signalwire/docusaurus-plugin-llms-txt/public";
function appKitAliasPlugin() {
return {
@@ -132,6 +133,35 @@ const config: Config = {
},
],
appKitAliasPlugin,
+ [
+ "@signalwire/docusaurus-plugin-llms-txt",
+ // docs: https://github.com/signalwire/docusaurus-plugins/blob/main/packages/docusaurus-plugin-llms-txt/README.md
+ {
+ id: "appkit",
+ markdown: {
+ enableFiles: true,
+ relativePaths: true,
+ includeDocs: true,
+ includeVersionedDocs: false,
+ includeBlog: false,
+ includePages: false,
+ includeGeneratedIndex: true,
+ },
+ llmsTxt: {
+ siteTitle: "AppKit",
+ siteDescription:
+ "Node.js + React SDK for Databricks Apps. Built for humans and AI.",
+ enableLlmsFullTxt: true,
+ },
+ ui: {
+ copyPageContent: {
+ display: {
+ docs: true,
+ },
+ },
+ },
+ } satisfies PluginOptions,
+ ],
],
themeConfig: {
diff --git a/docs/package.json b/docs/package.json
index 9bdca8f3..658df190 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -24,6 +24,7 @@
"@docusaurus/preset-classic": "3.9.2",
"@docusaurus/theme-mermaid": "^3.9.2",
"@mdx-js/react": "^3.0.0",
+ "@signalwire/docusaurus-plugin-llms-txt": "2.0.0-alpha.7",
"@tailwindcss/postcss": "^4.1.18",
"clsx": "^2.0.0",
"docusaurus-lunr-search": "^3.6.0",
diff --git a/docs/static/appkit-ui/styles.gen.css b/docs/static/appkit-ui/styles.gen.css
index 336bd67e..e497c06a 100644
--- a/docs/static/appkit-ui/styles.gen.css
+++ b/docs/static/appkit-ui/styles.gen.css
@@ -24,6 +24,8 @@
--text-base--line-height: calc(1.5 / 1);
--text-lg: 1.125rem;
--text-lg--line-height: calc(1.75 / 1.125);
+ --text-2xl: 1.5rem;
+ --text-2xl--line-height: calc(2 / 1.5);
--text-4xl: 2.25rem;
--text-4xl--line-height: calc(2.5 / 2.25);
--text-7xl: 4.5rem;
@@ -1455,6 +1457,10 @@
.font-sans {
font-family: var(--font-sans);
}
+ .text-2xl {
+ font-size: var(--text-2xl);
+ line-height: var(--tw-leading, var(--text-2xl--line-height));
+ }
.text-4xl {
font-size: var(--text-4xl);
line-height: var(--tw-leading, var(--text-4xl--line-height));
@@ -1653,6 +1659,10 @@
--tw-shadow: 0 1px 2px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.05));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
+ .ring {
+ --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
.ring-0 {
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
diff --git a/llms.txt b/llms.txt
deleted file mode 100644
index b4f576d1..00000000
--- a/llms.txt
+++ /dev/null
@@ -1,1234 +0,0 @@
-# llms.txt — LLM Guide for Building Great Databricks Apps with AppKit
-Project: Databricks AppKit
-
-This document is written *for LLMs* generating code in a brand-new project folder that installs AppKit from npm. It is intentionally prescriptive.
-
-## High-level mission
-
-Build **full-stack TypeScript apps** on Databricks using:
-
-- **Backend**: `@databricks/appkit`
-- **Frontend**: `@databricks/appkit-ui`
-- **Analytics**: SQL files in `config/queries/*.sql` executed via the AppKit analytics plugin
-
-This file is designed to work even when you *do not* have access to the AppKit source repo. Prefer only public package APIs and portable project structures.
-
-## Hard rules (LLM guardrails)
-
-- **Do not invent APIs**. If unsure, stick to the patterns shown in this file and only documented exports from `@databricks/appkit` and `@databricks/appkit-ui`.
-- **`createApp()` is async**. Prefer **top-level `await createApp(...)`**. If you can’t, use `void createApp(...)` and do not ignore promise rejection.
-- **Always memoize query parameters** passed to `useAnalyticsQuery` / charts to avoid refetch loops.
-- **Always handle loading/error/empty states** in UI (use `Skeleton`, error text, empty state).
-- **Always use `sql.*` helpers** for query parameters (do not pass raw strings/numbers unless the query expects none).
-- **Never construct SQL strings dynamically**. Use parameterized queries with `:paramName`.
-- **Never use `require()`**. Use ESM `import/export`.
-
-## TypeScript import rules (when using `verbatimModuleSyntax`)
-
-If your `tsconfig.json` uses `"verbatimModuleSyntax": true`, **always use `import type` for type-only imports** (otherwise builds can fail in strict setups):
-
-```ts
-import type { ReactNode } from "react";
-import { useMemo } from "react";
-```
-
-## Canonical project layout
-
-Recommended structure (client/server split):
-
-```
-my-app/
-├── server/
-│ ├── index.ts # backend entry point (AppKit)
-│ └── .env # optional local dev env vars (do not commit)
-├── client/
-│ ├── index.html
-│ ├── vite.config.ts
-│ └── src/
-│ ├── main.tsx
-│ └── App.tsx
-├── config/
-│ └── queries/
-│ └── my_query.sql
-├── app.yaml
-├── package.json
-└── tsconfig.json
-```
-
-Why this layout:
-
-- The AppKit `server()` plugin automatically serves:
- - **Dev**: Vite dev server (HMR) from `client/`
- - **Prod**: static files from `client/dist` (built by Vite)
-
-## Project scaffolding (start here)
-
-### `package.json`
-
-```json
-{
- "name": "my-app",
- "private": true,
- "version": "0.0.0",
- "type": "module",
- "scripts": {
- "dev": "NODE_ENV=development tsx watch server/index.ts",
- "build": "npm run build:server && npm run build:client",
- "build:server": "tsdown --out-dir build server/index.ts",
- "build:client": "tsc -b && vite build --config client/vite.config.ts",
- "start": "node build/index.mjs"
- },
- "dependencies": {
- "@databricks/appkit": "^0.1.2"
- "@databricks/appkit-ui": "^0.1.2",
- "react": "^19.2.3",
- "react-dom": "^19.2.3"
- },
- "devDependencies": {
- "@types/node": "^20.0.0",
- "@types/react": "^19.0.0",
- "@types/react-dom": "^19.0.0",
- "@vitejs/plugin-react": "^5.1.1",
- "tsdown": "^0.15.7",
- "tsx": "^4.19.0",
- "typescript": "~5.6.0",
- "vite": "^7.2.4"
- }
-}
-```
-
-### `client/index.html`
-
-```html
-
-
-
-
-
- My App
-
-
-
-
-
-
-```
-
-### `client/src/main.tsx`
-
-```tsx
-import { StrictMode } from "react";
-import { createRoot } from "react-dom/client";
-import App from "./App";
-
-createRoot(document.getElementById("root")!).render(
-
-
- ,
-);
-```
-
-### `client/src/App.tsx` (minimal)
-
-```tsx
-export default function App() {
- return (
-
-
My App
-
- );
-}
-```
-
-### `client/vite.config.ts`
-
-```ts
-import { defineConfig } from "vite";
-import react from "@vitejs/plugin-react";
-
-export default defineConfig({
- plugins: [react()],
-});
-```
-
-### `tsconfig.json`
-
-```json
-{
- "compilerOptions": {
- "target": "ES2022",
- "lib": ["ES2022", "DOM", "DOM.Iterable"],
- "module": "ESNext",
- "moduleResolution": "bundler",
- "jsx": "react-jsx",
- "strict": true,
- "skipLibCheck": true,
- "noEmit": true,
- "allowImportingTsExtensions": true,
- "verbatimModuleSyntax": true
- },
- "include": ["server", "client/src"]
-}
-```
-
-### `server/index.ts`
-
-```ts
-import { createApp, server } from "@databricks/appkit";
-
-await createApp({
- plugins: [server()],
-});
-```
-
-### Running the app
-
-```bash
-# Install dependencies
-npm install
-
-# Development (starts backend + Vite dev server)
-npm run dev
-
-# Production build
-npm run build
-npm start
-```
-
-## Integrating into an existing app
-
-If you already have a React/Vite app and want to add AppKit:
-
-### 1. Install dependencies
-
-```bash
-npm install @databricks/appkit @databricks/appkit-ui react react-dom
-npm install -D tsx tsdown vite @vitejs/plugin-react typescript
-
-# If you don't already have a client/ folder, create one and move your Vite app into it:
-# - move index.html -> client/index.html
-# - move vite.config.ts -> client/vite.config.ts
-# - move src/ -> client/src/
-#
-```
-
-### 2. Create `server/index.ts` (new file)
-
-```ts
-import { createApp, server } from "@databricks/appkit";
-
-await createApp({
- plugins: [server()],
-});
-```
-
-### 3. Update `package.json` scripts
-
-```json
-{
- "scripts": {
- "dev": "NODE_ENV=development tsx watch server/index.ts",
- "build": "npm run build:server && npm run build:client",
- "build:server": "tsdown --out-dir build server/index.ts",
- "build:client": "tsc -b && vite build --config client/vite.config.ts",
- "start": "node build/index.mjs"
- }
-}
-```
-
-### 4. That's it
-
-- AppKit's server plugin will automatically serve your Vite app in dev mode and `client/dist` in production.
-- If your Vite app must stay at the repo root (no `client/` folder), AppKit can still work, but the recommended layout is `client/` + `server/`.
-
-### Adding analytics to an existing app
-
-```ts
-// server/index.ts
-import { createApp, server, analytics } from "@databricks/appkit";
-
-await createApp({
- plugins: [server(), analytics()],
-});
-```
-
-Then create `config/queries/` and add your `.sql` files.
-
-## Environment variables
-
-### Required for Databricks Apps deployment
-
-These are typically **provided by Databricks Apps runtime** (exact set can vary by platform/version):
-
-| Variable | Description |
-|----------|-------------|
-| `DATABRICKS_HOST` | Workspace URL (e.g. `https://xxx.cloud.databricks.com`) |
-| `DATABRICKS_APP_PORT` | Port to bind (default: `8000`) |
-| `DATABRICKS_APP_NAME` | App name in Databricks |
-
-### Required for SQL queries (analytics plugin)
-
-| Variable | Description | How to set |
-|----------|-------------|------------|
-| `DATABRICKS_WAREHOUSE_ID` | SQL warehouse ID | In `app.yaml`: `valueFrom: sql-warehouse` |
-
-### Optional
-
-| Variable | Description | Default |
-|----------|-------------|---------|
-| `DATABRICKS_WORKSPACE_ID` | Workspace ID | Auto-fetched from API |
-| `NODE_ENV` | `"development"` or `"production"` | — |
-| `FLASK_RUN_HOST` | Host to bind | `0.0.0.0` |
-
-### Local development
-
-For local development, you need to authenticate with Databricks. Options:
-
-**Option 1: Databricks CLI Auth (recommended)**
-
-```bash
-# Configure once
-databricks auth login --host [host] --profile [profile-name]
-
-# If you used `DEFAULT` as the profile name then you can just run
-
-`npm run dev`
-
-# To run with a specific profile
-DATABRICKS_CONFIG_PROFILE=my-profile npm run dev
-# If your Databricks SDK expects a different variable name, try:
-# DATABRICKS_PROFILE=my-profile npm run dev
-```
-
-**Option 2: Environment variables**
-
-```bash
-export DATABRICKS_HOST="https://xxx.cloud.databricks.com"
-export DATABRICKS_TOKEN="dapi..."
-export DATABRICKS_WAREHOUSE_ID="abc123..."
-npm run dev
-```
-
-**Option 3: `.env` file (auto-loaded by AppKit)**
-
-```bash
-# .env (add to .gitignore!)
-DATABRICKS_HOST=https://xxx.cloud.databricks.com
-DATABRICKS_TOKEN=dapi...
-DATABRICKS_WAREHOUSE_ID=abc123...
-```
-
-### Telemetry (optional)
-
-| Variable | Description |
-|----------|-------------|
-| `OTEL_EXPORTER_OTLP_ENDPOINT` | OpenTelemetry collector endpoint |
-| `OTEL_SERVICE_NAME` | Service name for traces |
-
-## Backend: `@databricks/appkit`
-
-### Minimal server (golden template)
-
-The smallest valid AppKit server:
-
-```ts
-// server/index.ts
-import { createApp, server } from "@databricks/appkit";
-
-await createApp({
- plugins: [server()],
-});
-```
-
-### Server plugin (`server()`)
-
-What it does:
-
-- Starts an Express server (default `host=0.0.0.0`, `port=8000`)
-- Mounts plugin routes under `/api//...`
-- Adds `/health` (returns `{ status: "ok" }`)
-- Serves frontend:
- - **Development** (`NODE_ENV=development`): runs a Vite dev server in middleware mode
- - **Production**: auto-detects static frontend directory (checks `dist`, `client/dist`, `build`, `public`, `out`)
-
-Config (real options):
-
-```ts
-import { createApp, server } from "@databricks/appkit";
-
-await createApp({
- plugins: [
- server({
- port: 8000, // default: Number(process.env.DATABRICKS_APP_PORT) || 8000
- host: "0.0.0.0", // default: process.env.FLASK_RUN_HOST || "0.0.0.0"
- autoStart: true, // default: true
- staticPath: "dist", // optional: force a specific static directory
- }),
- ],
-});
-```
-
-Manual server start (when you need to `.extend()` Express):
-
-```ts
-import { createApp, server } from "@databricks/appkit";
-
-const appkit = await createApp({
- plugins: [server({ autoStart: false })],
-});
-
-appkit.server.extend((app) => {
- app.get("/custom", (_req, res) => res.json({ ok: true }));
-});
-
-await appkit.server.start();
-```
-
-### Analytics plugin (`analytics()`)
-
-Add SQL query execution backed by Databricks SQL Warehouses.
-
-```ts
-import { analytics, createApp, server } from "@databricks/appkit";
-
-await createApp({
- plugins: [server(), analytics({})],
-});
-```
-
-Where queries live:
-
-- Put `.sql` files in `config/queries/`.
-- Query key is the filename without `.sql` (e.g. `spend_summary.sql` → `"spend_summary"`).
-
-SQL parameters:
-
-- Use `:paramName` placeholders.
-- Optionally annotate parameter types using SQL comments:
-
-```sql
--- @param startDate DATE
--- @param endDate DATE
--- @param limit NUMERIC
-SELECT ...
-WHERE usage_date BETWEEN :startDate AND :endDate
-LIMIT :limit
-```
-
-Supported `-- @param` types (case-insensitive):
-
-- `STRING`, `NUMERIC`, `BOOLEAN`, `DATE`, `TIMESTAMP`, `BINARY`
-
-Server-injected params (important):
-
-- `:workspaceId` is **injected by the server** and **must not** be annotated.
-- Example:
-
-```sql
-WHERE workspace_id = :workspaceId
-```
-
-HTTP endpoints exposed (mounted under `/api/analytics`):
-
-- `POST /api/analytics/query/:query_key`
-- `GET /api/analytics/arrow-result/:jobId`
-
-**Query file naming convention determines execution context:**
-
-- `config/queries/.sql` - Executes as service principal (shared cache)
-- `config/queries/.obo.sql` - Executes as user (OBO = On-Behalf-Of, per-user cache)
-
-Formats:
-
-- `format: "JSON"` (default) returns JSON rows
-- `format: "ARROW"` returns an Arrow “statement_id” payload over SSE, then the client fetches binary Arrow from `/api/analytics/arrow-result/:jobId`
-
-### Execution context and `asUser(req)`
-
-AppKit manages Databricks authentication via two contexts:
-
-- **ServiceContext** (singleton): Initialized at app startup with service principal credentials
-- **ExecutionContext**: Determined at runtime - either service principal or user context
-
-**Headers used for user context:**
-
-- `x-forwarded-user`: required in production; identifies the user
-- `x-forwarded-access-token`: required for user token passthrough
-
-**Using `asUser(req)` for user-scoped operations:**
-
-The `asUser(req)` pattern allows plugins to execute operations using the requesting user's credentials:
-
-```ts
-// In a custom plugin route handler
-router.post("/users/me/data", async (req, res) => {
- // Execute as the user (uses their Databricks permissions)
- const result = await this.asUser(req).query("SELECT ...");
- res.json(result);
-});
-
-// Service principal execution (default)
-router.post("/system/data", async (req, res) => {
- const result = await this.query("SELECT ...");
- res.json(result);
-});
-```
-
-**Context helper functions (exported from `@databricks/appkit`):**
-
-- `getExecutionContext()`: Returns current context (user or service)
-- `getCurrentUserId()`: Returns user ID in user context, service user ID otherwise
-- `getWorkspaceClient()`: Returns the appropriate WorkspaceClient for current context
-- `getWarehouseId()`: `Promise` (from `DATABRICKS_WAREHOUSE_ID` or auto-selected in dev)
-- `getWorkspaceId()`: `Promise` (from `DATABRICKS_WORKSPACE_ID` or fetched)
-- `isInUserContext()`: Returns `true` if currently executing in user context
-
-**Development mode behavior:**
-
-In local development (`NODE_ENV=development`), if `asUser(req)` is called without a user token, it logs a warning and falls back to the service principal.
-
-### Custom plugins (backend)
-
-If you need custom API routes or background logic, implement an AppKit plugin.
-
-```ts
-import { Plugin, toPlugin } from "@databricks/appkit";
-import type express from "express";
-
-class MyPlugin extends Plugin {
- name = "my-plugin";
- envVars = []; // list required env vars here
-
- injectRoutes(router: express.Router) {
- this.route(router, {
- name: "hello",
- method: "get",
- path: "/hello",
- handler: async (_req, res) => {
- res.json({ ok: true });
- },
- });
- }
-}
-
-export const myPlugin = toPlugin, "my-plugin">(
- MyPlugin,
- "my-plugin",
-);
-```
-
-### Caching (global + plugin-level)
-
-Global:
-
-```ts
-await createApp({
- plugins: [server(), analytics({})],
- cache: {
- enabled: true,
- ttl: 3600, // seconds
- strictPersistence: false,
- },
-});
-```
-
-- Storage auto-selects **Lakebase persistent cache when healthy**, otherwise falls back to in-memory.
-
-Plugin-level:
-
-```ts
-// inside a Plugin subclass:
-const value = await this.cache.getOrExecute(
- ["my-plugin", "data", userId],
- async () => expensiveWork(),
- userKey,
- { ttl: 300 },
-);
-```
-
-## Frontend: `@databricks/appkit-ui`
-
-### Imports
-
-- React-facing APIs: `@databricks/appkit-ui/react`
-- Non-React utilities (sql markers, arrow, SSE): `@databricks/appkit-ui/js`
-
-```tsx
-import { useAnalyticsQuery, Card, Skeleton } from "@databricks/appkit-ui/react";
-import { sql } from "@databricks/appkit-ui/js";
-```
-
-### `useAnalyticsQuery(queryKey, parameters, options?)`
-
-Facts:
-
-- Uses **SSE** under the hood (not `fetch()` polling).
-- By default it hits `POST /api/analytics/query/:queryKey`.
-- Returns `{ data, loading, error }` where `data` is `null` until loaded.
-- `format` is `"JSON"` or `"ARROW"` (uppercase).
-
-When to use it:
-
-- Use `useAnalyticsQuery` **only** when you need a custom UI (cards/KPIs/forms/conditional rendering).
-- If you just need a standard chart or table, prefer the built-in components (`BarChart`, `LineChart`, `DataTable`, etc.) so you don’t re-implement loading/error/empty states.
-
-Limitations (common LLM pitfall):
-
-- There is **no `enabled` option**. Use conditional rendering to mount/unmount the component.
-- There is **no `refetch()`**. Change `parameters` (memoized) or re-mount to re-run the query.
-
-Recommended usage pattern (memoized params + explicit states):
-
-```tsx
-import { useMemo } from "react";
-import { useAnalyticsQuery, Skeleton } from "@databricks/appkit-ui/react";
-import { sql } from "@databricks/appkit-ui/js";
-
-export function Users() {
- const params = useMemo(
- () => ({
- status: sql.string("active"),
- limit: sql.number(50),
- }),
- [],
- );
-
- const { data, loading, error } = useAnalyticsQuery("users_list", params);
-
- if (loading) return ;
- if (error) return
Error: {error}
;
- if (!data || data.length === 0) return
No results
;
-
- return
{JSON.stringify(data[0], null, 2)}
;
-}
-```
-
-Options:
-
-- `format?: "JSON" | "ARROW"` (default `"JSON"`)
-- `autoStart?: boolean` (default `true`)
-- `maxParametersSize?: number` (default `100 * 1024` bytes)
-
-### `useChartData({ queryKey, parameters, format, transformer })`
-
-- `format` here is **lowercase**: `"json" | "arrow" | "auto"` (default `"auto"`)
-- Auto-selection heuristics:
- - If `parameters._preferArrow === true` → Arrow
- - If `parameters._preferJson === true` → JSON
- - If `parameters.limit` is a number > 500 → Arrow
- - If `parameters.startDate` and `parameters.endDate` exist → Arrow
-
-### Charts (unified query/data API)
-
-All charts support:
-
-- **Query mode**: `queryKey` + `parameters`
-- **Data mode**: `data` (inline JSON, no server)
-
-Available chart components:
-
-- `BarChart`, `LineChart`, `AreaChart`, `PieChart`, `DonutChart`, `HeatmapChart`, `ScatterChart`, `RadarChart`
-
-Avoid double-fetching:
-
-```tsx
-// ❌ Wrong: fetches the same query twice
-// const { data } = useAnalyticsQuery("spend_data", params);
-// return ;
-
-// ✅ Correct: let the chart fetch
-return ;
-```
-
-Query mode (recommended for Databricks-backed analytics):
-
-```tsx
-import { LineChart } from "@databricks/appkit-ui/react";
-import { sql } from "@databricks/appkit-ui/js";
-import { useMemo } from "react";
-
-export function SpendChart() {
- const params = useMemo(
- () => ({
- startDate: sql.date("2024-01-01"),
- endDate: sql.date("2024-12-31"),
- aggregationLevel: sql.string("day"),
- }),
- [],
- );
-
- return (
-
- );
-}
-```
-
-**Chart props reference (important):**
-
-Charts are **self-contained ECharts components**. Configure via props, NOT children:
-
-```tsx
-// ✅ Correct: use props for customization
-
-
-
-```
-
-**❌ CRITICAL: Charts do NOT accept Recharts children**
-
-```tsx
-// ❌ WRONG - AppKit charts are NOT Recharts wrappers
-import { BarChart } from "@databricks/appkit-ui/react";
-import { Bar, XAxis, YAxis, CartesianGrid } from "recharts";
-
-
- // ❌ This will cause TypeScript errors
- // ❌ Not supported
- // ❌ Not supported
-
-
-// ✅ CORRECT - use props instead
-
-```
-
-### SQL helpers (`sql.*`)
-
-Use these to build typed parameters (they return marker objects: `{ __sql_type, value }`):
-
-- `sql.string(value)` → STRING (accepts string|number|boolean)
-- `sql.number(value)` → NUMERIC (accepts number|string)
-- `sql.boolean(value)` → BOOLEAN (accepts boolean|string("true"/"false")|number(1/0))
-- `sql.date(value)` → DATE (accepts Date or `"YYYY-MM-DD"`)
-- `sql.timestamp(value)` → TIMESTAMP (accepts Date, ISO string, or unix time)
-
-Binary parameters (important):
-
-- Databricks SQL Warehouse doesn't support `BINARY` as a parameter type.
-- `sql.binary(value)` returns a **STRING marker containing hex**, so use `UNHEX(:param)` in SQL.
-- `sql.binary` accepts `Uint8Array`, `ArrayBuffer`, or a hex string.
-
-### SQL result types (important)
-
-Databricks SQL JSON results can return some numeric-like fields (especially `DECIMAL`) as strings. If a field behaves like a string at runtime, convert explicitly:
-
-```ts
-const value = Number(row.amount);
-```
-
-If you need more reliable numeric fidelity for large datasets, prefer `format: "ARROW"` and process Arrow on the client.
-
-### `connectSSE` (custom SSE connections)
-
-For custom streaming endpoints (not analytics), use the `connectSSE` utility:
-
-```tsx
-import { connectSSE } from "@databricks/appkit-ui/js";
-import { useEffect, useState } from "react";
-
-function useCustomStream(endpoint: string) {
- const [messages, setMessages] = useState([]);
- const [connected, setConnected] = useState(false);
-
- useEffect(() => {
- const controller = new AbortController();
-
- connectSSE({
- url: endpoint,
- payload: { key: "value" }, // optional: makes it a POST
- onMessage: async ({ data }) => {
- setConnected(true);
- setMessages((prev) => [...prev, data]);
- },
- onError: (error) => {
- console.error("SSE error:", error);
- setConnected(false);
- },
- signal: controller.signal,
- maxRetries: 3, // default: 3
- retryDelay: 2000, // default: 2000ms (exponential backoff)
- timeout: 300000, // default: 5 minutes
- maxBufferSize: 1048576, // default: 1MB
- });
-
- return () => controller.abort();
- }, [endpoint]);
-
- return { messages, connected };
-}
-```
-
-Options:
-
-- `url`: SSE endpoint URL (required)
-- `payload`: Optional request body (if provided, uses POST; otherwise GET)
-- `onMessage({ id, data })`: Called for each SSE message
-- `onError(error)`: Called on connection errors
-- `signal`: AbortSignal to cancel the connection
-- `lastEventId`: Resume from a specific event ID
-- `maxRetries`: Max retry attempts (default: 3)
-- `retryDelay`: Base delay between retries in ms (default: 2000)
-- `timeout`: Connection timeout in ms (default: 300000)
-- `maxBufferSize`: Max buffer size in bytes (default: 1MB)
-
-### `ArrowClient` (advanced Arrow processing)
-
-For low-level Arrow data handling:
-
-```tsx
-import { ArrowClient } from "@databricks/appkit-ui/js";
-
-// Process Arrow buffer
-const table = await ArrowClient.processArrowBuffer(buffer);
-
-// Fetch and process Arrow data in one call
-const table = await ArrowClient.fetchAndProcessArrow(url, headers);
-
-// Extract fields from table
-const fields = ArrowClient.extractArrowFields(table);
-// → [{ name: "date", type: ... }, { name: "value", type: ... }]
-
-// Extract columns as arrays
-const columns = ArrowClient.extractArrowColumns(table);
-// → { date: [...], value: [...] }
-
-// Extract chart data
-const { xData, yDataMap } = ArrowClient.extractChartData(table, "date", ["value", "count"]);
-// → { xData: [...], yDataMap: { value: [...], count: [...] } }
-
-// Auto-detect chart fields from Arrow table
-const detected = ArrowClient.detectFieldsFromArrow(table);
-// → { xField: "date", yFields: ["value"], chartType: "timeseries" }
-```
-
-### DataTable
-
-`DataTable` is a production-ready table integrated with `useAnalyticsQuery`.
-
-Key behaviors:
-
-- `parameters` is required (use `{}` if none)
-- Supports opinionated mode (auto columns) and full-control mode (`children(table)`)
-
-```tsx
-import { DataTable } from "@databricks/appkit-ui/react";
-
-export function UsersTable() {
- return (
-
- );
-}
-```
-
-### UI components (primitives)
-
-AppKit-UI ships shadcn-style primitives. Import from `@databricks/appkit-ui/react`.
-
-Note: Exact exports can vary by AppKit-UI version. Prefer using IDE auto-import/autocomplete to confirm what your installed version exports.
-
-Radix constraint (common bug):
-
-- `SelectItem` cannot have `value=""`. Use a sentinel value like `"all"` or `"none"`.
-
-**Available components:**
-
-`Accordion`, `Alert`, `AlertDialog`, `AspectRatio`, `Avatar`, `Badge`, `Breadcrumb`, `Button`, `ButtonGroup`, `Calendar`, `Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardContent`, `CardFooter`, `Carousel`, `Checkbox`, `Collapsible`, `Command`, `ContextMenu`, `Dialog`, `DialogTrigger`, `DialogContent`, `DialogHeader`, `DialogTitle`, `DialogDescription`, `DialogFooter`, `Drawer`, `DropdownMenu`, `Empty`, `Field`, `Form`, `HoverCard`, `Input`, `InputGroup`, `InputOtp`, `Item`, `Kbd`, `Label`, `Menubar`, `NavigationMenu`, `Pagination`, `Popover`, `Progress`, `RadioGroup`, `Resizable`, `ScrollArea`, `Select`, `SelectTrigger`, `SelectValue`, `SelectContent`, `SelectItem`, `Separator`, `Sheet`, `Sidebar`, `Skeleton`, `Slider`, `Sonner`, `Spinner`, `Switch`, `Table`, `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent`, `Textarea`, `Toggle`, `ToggleGroup`, `Tooltip`, `TooltipTrigger`, `TooltipContent`, `TooltipProvider`
-
-### Card pattern
-
-```tsx
-import {
- Card,
- CardHeader,
- CardTitle,
- CardDescription,
- CardContent,
- CardFooter,
-} from "@databricks/appkit-ui/react";
-
-function MetricCard({ title, value, description }: Props) {
- return (
-
-
- {description}
- {value}
-
-
- {/* Optional content */}
-
-
- {/* Optional footer */}
-
-
- );
-}
-```
-
-### Select pattern
-
-```tsx
-import {
- Select,
- SelectTrigger,
- SelectValue,
- SelectContent,
- SelectItem,
-} from "@databricks/appkit-ui/react";
-
-function DateRangeSelect({ value, onChange }: Props) {
- return (
-
- );
-}
-```
-
-### Tabs pattern
-
-```tsx
-import { Tabs, TabsList, TabsTrigger, TabsContent } from "@databricks/appkit-ui/react";
-
-function Dashboard() {
- return (
-
-
- Overview
- Analytics
-
-
-