@svgrid/mcp 2.5.0 → 2.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/data.js CHANGED
@@ -2674,6 +2674,12 @@ export const docs = [
2674
2674
  "title": "Enterprise evaluation",
2675
2675
  "markdown": "# Enterprise evaluation\r\n\r\nThe `@svgrid/enterprise` package is soft-gated; you can evaluate every\r\nfeature in production-equivalent code paths without contacting\r\nsales. This page is the playbook.\r\n\r\n![Soft-gated evaluation: install, try every feature while a watermark shows, then set a license key when ready, with no gated-off code paths.](/docs-media/enterprise-evaluation.svg)\r\n\r\n## Step 1: Install\r\n\r\n```bash\r\npnpm add @svgrid/enterprise\r\n# Add the peers for the features you want to evaluate:\r\npnpm add jszip # xlsx export/import\r\npnpm add pdfmake # pdf export\r\n```\r\n\r\n`jszip` and `pdfmake` are lazy-loaded by @svgrid/enterprise - they're only\r\nrequired if you actually invoke the matching feature.\r\n\r\n## Step 2: Install the evaluation key\r\n\r\n```ts\r\nimport { setLicenseKey } from '@svgrid/enterprise'\r\nsetLicenseKey('SVENTERPRISE-DEV-DEMO')\r\n```\r\n\r\nThis suppresses the watermark in local dev. **For staging /\r\nproduction evaluation, request an evaluation key** at\r\n[svgrid.com/contact](https://svgrid.com/contact/) - no sales call\r\nrequired.\r\n\r\nThe evaluation key is a real key with a 30-day expiry. Behaves\r\nidentically to a paid license; lets you ship internal staging /\r\ndemo deployments to evaluators without the watermark.\r\n\r\n### What unlicensed looks like\r\n\r\nWith no key set, Enterprise stays fully functional but nudges you:\r\n\r\n- A small **\"www.svgrid.com\" watermark** in the corner of each grid\r\n (fades after 5 seconds).\r\n- The first time you actually invoke a Enterprise feature (export, import,\r\n print, AI), a one-time **upgrade card** appears in the bottom-right\r\n naming that feature, with a one-click link to start a free trial. It\r\n shows at most once per session.\r\n\r\nBoth are pure DOM - **no network calls, no cookies, no web storage**\r\n(see [security](../help/security.md)). `setLicenseKey()` with any\r\nvalid key suppresses them before they appear. To remove the upgrade\r\ncard programmatically (e.g. you render your own upgrade UI), call:\r\n\r\n```ts\r\nimport { dismissUpgradePrompt } from '@svgrid/enterprise'\r\ndismissUpgradePrompt()\r\n```\r\n\r\n## Step 3: Wire up\r\n\r\n```svelte\r\n<script lang=\"ts\">\r\n import { SvGrid, type SvGridApi } from '@svgrid/grid'\r\n import { installEnterprise, type EnterpriseGridApi } from '@svgrid/enterprise'\r\n\r\n let api = $state<EnterpriseGridApi<typeof features, Order> | null>(null)\r\n</script>\r\n\r\n<SvGrid\r\n data={rows} columns={columns} features={features}\r\n onApiReady={(next: SvGridApi<typeof features, Order>) => {\r\n api = installEnterprise(next)\r\n }}\r\n/>\r\n\r\n<button onclick={() => api?.exportData({ format: 'xlsx' })}>Export</button>\r\n```\r\n\r\nThat's the integration. The Community grid is unchanged; Enterprise\r\naugments the api object.\r\n\r\n## Step 4: Try the features (30-min tour)\r\n\r\n| Feature | One-line evaluation |\r\n| ------- | -------------------------------------------------------------------- |\r\n| Export | `api.exportData({ format: 'xlsx', filename: 'data' })` |\r\n| Pivot | `const pivot = createPivotModel(rows, { rows: ['region'], cols: ['quarter'], values: [{ field: 'amount', agg: 'sum' }] })` |\r\n| Import | `<input type=\"file\" onchange={(e) => api.importData({ file: e.target.files[0] }).then(r => api.addRows(r.rows))}>` |\r\n| AI | `setAIProvider(yourAdapter); const plan = await api.ai.filter('show last quarter > $10k')` |\r\n\r\nEach Enterprise feature has a fully-working demo in the gallery\r\n([56-60 + 51 + 52 + 53](https://svgrid.com/demos/)) that you can\r\nread end-to-end.\r\n\r\n## Step 5: Performance + budget check\r\n\r\nBundle sizes (gzip):\r\n\r\n| Surface | Size | Notes |\r\n| --------------- | ----- | ---------------------------------- |\r\n| Community only | 80 kB | Renderer + engine (+ 9 kB CSS) |\r\n| + Enterprise export | +12 kB| + `jszip` peer when xlsx is used |\r\n| + Enterprise pdf | +90 kB| + `pdfmake` peer when pdf is used |\r\n| + Enterprise pivot | +6 kB | Pure TS, no peers |\r\n| + Enterprise import | +5 kB | + `jszip` for xlsx import |\r\n\r\nSubpath imports (`@svgrid/enterprise/export`, `@svgrid/enterprise/pivot`, etc.)\r\nensure you only pay for what you use.\r\n\r\n## Step 6: Decide\r\n\r\n- Shipping one production app? **Single Application Developer License**\r\n ($599 per developer).\r\n- Shipping multiple apps across your org? **Multiple Application\r\n Developer License** ($999 per developer).\r\n- Large team (5+), multi-year, NDA, or PO? **Enterprise / volume**\r\n (contact sales).\r\n\r\nEach is a perpetual license + 1 year of updates and support that renews\r\nautomatically; cancel anytime.\r\n\r\n[Full pricing](https://svgrid.com/pricing/).\r\n\r\n## Migrating from another grid mid-evaluation\r\n\r\nIf you're swapping out an existing grid, see the\r\n[migration guides](../help/migrating-from-ag-grid.md) - typically a half-day\r\nport for a 5-grid app.\r\n\r\n## See also\r\n\r\n- [Enterprise licensing](./licensing.md) - what each tier covers\r\n- [Enterprise support](./support.md) - what you get with each tier\r\n- [Missing features](../help/missing-features.md) - the honest gap list\r\n"
2676
2676
  },
2677
+ {
2678
+ "slug": "enterprise/getting-started",
2679
+ "path": "docs/enterprise/getting-started.md",
2680
+ "title": "Enterprise getting started: a complete example",
2681
+ "markdown": "# Enterprise getting started: a complete example\r\n\r\nOne page, one file, from an empty folder to a working grid with Excel, PDF and\r\nCSV export. Nothing is elided - every command and every line below was run\r\nend to end against the published packages.\r\n\r\nIf you already have a project, skip to [step 2](#2-install).\r\n\r\n## 1. Create a project\r\n\r\n```bash\r\nnpx sv create svgrid-trial --template minimal --types ts --no-add-ons\r\ncd svgrid-trial\r\n```\r\n\r\nThose flags skip every interactive prompt, so the sequence is copy-pasteable.\r\nFor a plain Vite app instead, `npm create @svgrid@latest` scaffolds one with\r\nthe grid already wired in - the component below drops into either.\r\n\r\n## 2. Install\r\n\r\n```bash\r\nnpm install @svgrid/grid @svgrid/enterprise\r\nnpm install jszip pdfmake # optional peers, for Excel and PDF export\r\n```\r\n\r\n`jszip` and `pdfmake` are optional peer dependencies, lazy-loaded the first\r\ntime you actually call an export. Skip them if you only need CSV, TSV or HTML;\r\n`exportData` throws a message naming the missing package if you call a format\r\nwhose peer is absent.\r\n\r\n**No build configuration is required.** If you are on `@svgrid/enterprise`\r\n2.5.x or earlier, see [older versions](#older-versions) below - those releases\r\nshipped TypeScript source and needed two `optimizeDeps` entries in\r\n`vite.config.js`.\r\n\r\n## 3. The component\r\n\r\nReplace the contents of `src/routes/+page.svelte` (or `src/App.svelte` in a\r\nVite project) with this. It is self-contained: licence, theme, grid and\r\nexports, with nothing else to wire up.\r\n\r\n```svelte\r\n<script>\r\n import { SvGrid } from '@svgrid/grid'\r\n import { installEnterprise, setLicenseKey } from '@svgrid/enterprise'\r\n // One of 20 themes that ship with the package. Swap the id for material,\r\n // nord, dracula, fluent, carbon, ag-alpine, and so on. Each carries a full\r\n // light AND dark palette; dark activates on <html data-theme=\"dark\">.\r\n import '@svgrid/grid/themes/shadcn.css'\r\n\r\n // Once, before any Enterprise feature runs. Use your own key here; see\r\n // ./evaluation.md for how to get an evaluation key.\r\n setLicenseKey('SVENTERPRISE-DEV-DEMO')\r\n\r\n let api = $state(null)\r\n let status = $state('')\r\n\r\n // Capabilities are boolean props - `sortable`, `filterable`, `editable`,\r\n // `groupable`, `pageable` - and each injects the feature it needs. For finer\r\n // control, register features explicitly with `tableFeatures({ ... })` and\r\n // pass them as `features`.\r\n\r\n // Starts 'light' rather than reading the DOM, so this file is safe to render\r\n // on the server too. The effect below syncs it once we are in the browser.\r\n let theme = $state('light')\r\n\r\n $effect(() => {\r\n const saved = localStorage.getItem('theme')\r\n if (saved) theme = saved\r\n })\r\n\r\n $effect(() => {\r\n document.documentElement.dataset.theme = theme\r\n try {\r\n localStorage.setItem('theme', theme)\r\n } catch (e) {\r\n // Private mode / storage disabled. The toggle still works for this tab.\r\n }\r\n })\r\n\r\n // Your data. Swap for a fetch() in onMount, a load function, or props.\r\n let rows = $state([\r\n { id: 1, name: 'Ada Lovelace', team: 'Engineering', salary: 145000, active: true },\r\n { id: 2, name: 'Alan Turing', team: 'Research', salary: 160000, active: true },\r\n { id: 3, name: 'Grace Hopper', team: 'Engineering', salary: 152000, active: false },\r\n { id: 4, name: 'Katherine Johnson', team: 'Data', salary: 138000, active: true },\r\n { id: 5, name: 'Edsger Dijkstra', team: 'Research', salary: 149000, active: false },\r\n ])\r\n\r\n const columns = [\r\n { field: 'name', header: 'Name', editorType: 'text', width: 200 },\r\n { field: 'team', header: 'Team', editorType: 'text', width: 150 },\r\n {\r\n field: 'salary',\r\n header: 'Salary',\r\n width: 130,\r\n align: 'right',\r\n format: { type: 'currency', currency: 'USD' },\r\n },\r\n { field: 'active', header: 'Active', editorType: 'checkbox', width: 90 },\r\n ]\r\n\r\n // installEnterprise() augments the grid API with the Pro methods:\r\n // exportData, importData, print, pivot, AI.\r\n async function run(label, fn) {\r\n if (!api) return\r\n try {\r\n await fn()\r\n status = `${label} ready`\r\n } catch (err) {\r\n status = `${label} failed: ${err instanceof Error ? err.message : String(err)}`\r\n }\r\n }\r\n</script>\r\n\r\n<main>\r\n <header>\r\n <div>\r\n <h1>SvGrid Enterprise</h1>\r\n <p>Sort, filter, select, and double-click a cell to edit.</p>\r\n </div>\r\n <button\r\n type=\"button\"\r\n onclick={() => (theme = theme === 'dark' ? 'light' : 'dark')}\r\n aria-label=\"Switch to {theme === 'dark' ? 'light' : 'dark'} mode\"\r\n >\r\n {theme === 'dark' ? 'Light' : 'Dark'}\r\n </button>\r\n </header>\r\n\r\n <div class=\"grid-shell\">\r\n <SvGrid\r\n data={rows}\r\n {columns}\r\n sortable\r\n filterable\r\n editable\r\n selectionMode=\"row\"\r\n showRowSelection={true}\r\n showRowNumbers={true}\r\n rowHeight={38}\r\n containerHeight=\"100%\"\r\n fitColumns={true}\r\n onApiReady={(next) => (api = installEnterprise(next))}\r\n />\r\n </div>\r\n\r\n <div class=\"actions\">\r\n <button type=\"button\" onclick={() => run('CSV', () => api.exportData({ format: 'csv', filename: 'team' }))}>\r\n Export CSV\r\n </button>\r\n <button type=\"button\" onclick={() => run('Excel', () => api.exportData({ format: 'xlsx', filename: 'team' }))}>\r\n Export Excel\r\n </button>\r\n <button type=\"button\" onclick={() => run('PDF', () => api.exportData({ format: 'pdf', filename: 'team' }))}>\r\n Export PDF\r\n </button>\r\n <button type=\"button\" onclick={() => run('Print', () => api.print())}>Print</button>\r\n {#if status}<span class=\"status\">{status}</span>{/if}\r\n </div>\r\n\r\n <p class=\"hint\">\r\n Export respects the current sort, filter and grouping. Change the theme\r\n import at the top of this file to re-skin the grid and this page together.\r\n </p>\r\n</main>\r\n\r\n<style>\r\n /* Page chrome reads the same --sg-* tokens as the grid, so it re-themes with it. */\r\n :global(body) {\r\n margin: 0;\r\n background: var(--sg-bg);\r\n color: var(--sg-fg);\r\n font-family: var(--sg-font, system-ui, sans-serif);\r\n }\r\n\r\n main { max-width: 760px; margin: 3rem auto; padding: 0 1rem; }\r\n header { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; }\r\n h1 { margin: 0; font-size: 1.4rem; }\r\n p { color: var(--sg-muted); }\r\n .actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; margin-top: 1rem; }\r\n\r\n button {\r\n flex: none;\r\n padding: 0.4rem 0.8rem;\r\n border: 1px solid var(--sg-border);\r\n border-radius: var(--sg-radius, 6px);\r\n background: var(--sg-bg-subtle, transparent);\r\n color: var(--sg-fg);\r\n font: inherit;\r\n font-size: 0.85rem;\r\n cursor: pointer;\r\n }\r\n button:hover { background: var(--sg-row-hover-bg); }\r\n button:focus-visible { outline: 2px solid var(--sg-accent); outline-offset: 2px; }\r\n\r\n .status { font-size: 0.82rem; color: var(--sg-muted); }\r\n .grid-shell { height: 320px; }\r\n .hint { font-size: 0.85rem; }\r\n</style>\r\n```\r\n\r\n## 4. Run it\r\n\r\n```bash\r\nnpm run dev\r\n```\r\n\r\nOpen <http://localhost:5173>. You should get a five-row grid: click a header to\r\nsort, use the funnel for Excel-style filtering, double-click a cell to edit,\r\nand the four buttons write real files.\r\n\r\n## What the three pieces do\r\n\r\n| Line | Why it's there |\r\n| --- | --- |\r\n| `setLicenseKey(...)` | Runs once, before any Enterprise call. Without it the pack still works, but watermarks and nudges the app. See [licensing](./licensing.md). |\r\n| `installEnterprise(next)` | Wraps the `SvGridApi` from `onApiReady` and returns it with `exportData`, `importData`, `print`, pivot and AI attached. The `<SvGrid>` component itself stays Community. |\r\n| `import '@svgrid/grid/themes/shadcn.css'` | Optional. Declares the `--sg-*` tokens for one of 20 presets. Without it the grid still renders, using the built-in fallbacks. |\r\n\r\n## Server-side rendering\r\n\r\nThe component above is SSR-safe as written: `theme` starts at a literal, and\r\nevery DOM and `localStorage` access sits inside `$effect`, which only runs in\r\nthe browser. No `export const ssr = false` is needed.\r\n\r\nIf you move DOM access to module scope or into component initialisation, it\r\nwill run on the server and throw. Keep it in `$effect` or `onMount`.\r\n\r\n## Older versions\r\n\r\n`@svgrid/enterprise` 2.5.x and earlier shipped TypeScript source rather than a\r\nbuilt bundle. Vite's dependency pre-bundler cannot parse the `.svelte.ts` rune\r\nmodules in it, so those versions need two entries in `vite.config.js`:\r\n\r\n```js\r\noptimizeDeps: {\r\n // Without this the dev server fails to start:\r\n // RolldownError ... Unexpected token (on `import type`)\r\n exclude: ['@svgrid/grid', '@svgrid/enterprise'],\r\n // Excluding it also stops ITS imports being pre-bundled, and jszip and\r\n // pdfmake are CommonJS. Without this, xlsx and pdf export fail with\r\n // \"JSZip is not a constructor\" / \"pdfMake.createPdf is not a function\".\r\n include: ['jszip', 'pdfmake/build/pdfmake', 'pdfmake/build/vfs_fonts'],\r\n},\r\n```\r\n\r\n2.6.0 moved the package to a built `dist`, so neither entry is needed. Upgrading\r\nis the better fix.\r\n\r\n## Next\r\n\r\n- [Evaluation playbook](./evaluation.md) - what unlicensed looks like, and how\r\n to get an evaluation key.\r\n- [Licensing](./licensing.md) - key formats, seats, renewals.\r\n- [Data export](../help/export.md) - styles, headers, images, multi-sheet.\r\n- [Data import](../help/import.md) - column mapping and per-row validation.\r\n"
2682
+ },
2677
2683
  {
2678
2684
  "slug": "enterprise/licensing",
2679
2685
  "path": "docs/enterprise/licensing.md",
@@ -2702,7 +2708,7 @@ export const docs = [
2702
2708
  "slug": "enterprise/studio/ai-generation",
2703
2709
  "path": "docs/enterprise/studio/ai-generation.md",
2704
2710
  "title": "AI generation",
2705
- "markdown": "# AI generation\r\n\r\nThe `@svgrid/mcp` server exposes Studio to AI coding agents (Claude Code, Cursor,\r\nCodex, ...) through the Model Context Protocol. Ask your agent to build a screen\r\nfor a table and it introspects, scaffolds, and verifies - producing the same code\r\nthe [CLI](./cli.md) and [designer](./designer.md) do.\r\n\r\n![The generated files: schema module, +server.ts API route, and +page.svelte screen, with svgrid:managed markers.](/docs-media/studio-generated-code.png)\r\n\r\n## How it fits together\r\n\r\nThe MCP server makes **no model calls of its own**. It hands your agent a set of\r\ntools; the agent's own model decides when to call them. So the loop is:\r\n\r\n```\r\nyou -> your agent (its model) -> svgrid MCP tools -> files on disk\r\n ^ |\r\n +-------- svelte-check verify <----------+\r\n```\r\n\r\nYour schema and data stay on your machine; nothing is sent to our servers.\r\n\r\n## Configure the MCP server\r\n\r\nAdd it to your agent's MCP config (the key is passed as an env var, since the\r\nserver runs in a Node process):\r\n\r\n```jsonc\r\n{\r\n \"mcpServers\": {\r\n \"svgrid\": {\r\n \"command\": \"npx\",\r\n \"args\": [\"@svgrid/mcp\"],\r\n \"env\": { \"SVGRID_LICENSE_KEY\": \"SVENTERPRISE-...\" }\r\n }\r\n }\r\n}\r\n```\r\n\r\nThe same block works across hosts - only the file it lives in differs:\r\n\r\n| Host | Config location |\r\n| --- | --- |\r\n| Claude Code | `.mcp.json` at the project root, or `claude mcp add` |\r\n| Cursor | `.cursor/mcp.json` |\r\n| Windsurf | `~/.codeium/windsurf/mcp_config.json` |\r\n| Codex / other | the host's `mcpServers` config |\r\n\r\nRestart (or reload) the agent so it picks up the server, then confirm the\r\n`svgrid` tools are listed.\r\n\r\n## The tools\r\n\r\nAlongside the read-only knowledge tools (examples, docs, API reference), the\r\nserver exposes two generation tools:\r\n\r\n| Tool | What it does |\r\n| --- | --- |\r\n| `introspect_source` | Infer an `EntitySchema` from a Drizzle schema file (`kind:\"drizzle\"`) or sample JSON rows (`kind:\"json\"`). Returns a **draft** to review. |\r\n| `scaffold_entity` | Generate the SvelteKit files from an `EntitySchema`. The output is **compile-verified** (the generated page is run through the Svelte compiler) before it comes back, and each file carries `svgrid:managed` markers. |\r\n\r\n## Drive the whole project model\r\n\r\nBeyond single screens, the server exposes the full\r\n[project model](./concepts.md#the-project-model) - the same\r\n`studio.config.json` the visual designer edits - as a set of `studio_*` tools.\r\nYour agent can build a complete multi-screen app, or continue editing one the\r\ndesigner produced, and hand it back:\r\n\r\n| Tool | What it does |\r\n| --- | --- |\r\n| `studio_new_project` | Start a new, empty project |\r\n| `studio_load_project` | Load an existing `studio.config.json` to continue editing it |\r\n| `studio_describe_project` | Summarize the current project: entities, screens + block ids, theme, RBAC, auth, deploy |\r\n| `studio_get_config` | Return the project as a `studio.config.json` string - write it to disk and the designer opens it (round-trip) |\r\n| `studio_capabilities` | List what can be added: block kinds, UI component keys, theme presets, source kinds, deploy targets |\r\n| `studio_add_entity` | Add an entity + its default screen, from an `EntitySchema`, a Drizzle source, or sample JSON rows |\r\n| `studio_add_screen` | Add an entity-bound screen (default grid) or a freestanding page |\r\n| `studio_add_block` | Add a data block (grid, chart, kpi, gauge, tree, tabs, accordion, pivot, board, calendar, detail, master-detail, filter, record, lookup, dashboard) to a screen |\r\n| `studio_add_component` | Add a UI component block (button, badge, alert, card, stat, timeline, sparkline, chip, ...) with prop overrides |\r\n| `studio_update_block` | **Configure** an existing block - columns, editing mode, export buttons, grouping, chart dimension/measure, row links, format rules - plus its width, height, and class |\r\n| `studio_remove_block` | Remove a block from a screen |\r\n| `studio_move_block` | Reorder a block within its screen |\r\n| `studio_update_screen` | Rename a screen, change its route or nav entry, or set `renderMode` (`ssr` for an idiomatic `+page.server.ts` load + form actions, `spa` for the client page) |\r\n| `studio_remove_screen` | Remove a screen and its blocks |\r\n| `studio_set_screen_layout` | Switch a screen between `grid`, `stack`, `split`, `dock`, and `canvas` layouts |\r\n| `studio_set_entity_source` | Bind an entity to a data source: `sql`, `supabase`, `rest`, `pglite`, or `memory` |\r\n| `studio_set_theme` | Set the theme preset, light/dark mode, and accent color |\r\n| `studio_set_access` | Configure [RBAC](./access-control.md): roles gating screens and create/update/delete actions |\r\n| `studio_set_auth` | Configure the [auth starter](./auth.md): protect, register, user admin, 2FA, email, OAuth (`github` / `google` / `oidc`) |\r\n| `studio_set_data_layer` | Turn the typed Drizzle data layer (schema + repositories + migrations) on or off |\r\n| `studio_set_tenancy` | Turn [multi-tenancy](./access-control.md#multi-tenancy) on/off - scopes every row to the caller's tenant, enforced server-side; `sharedEntities` stay global |\r\n| `studio_set_job` | Schedule a background job (`email` digest or `code`) - emits the guarded `/api/cron` route + the platform schedule; omit `cron` to remove one |\r\n| `studio_set_deploy_target` | Set `auto` / `vercel` / `netlify` / `cloudflare` / `node` - picks the adapter and emits CI/CD config |\r\n| `studio_validate` | Validate the current project; returns errors + warnings |\r\n| `studio_generate_app` | Emit the full runnable SvelteKit app - every file, ready to write and `svelte-check` |\r\n\r\nA prompt that exercises the loop end to end:\r\n\r\n> \"Using the svgrid MCP: new project 'Support desk'. Add a `tickets` entity from\r\n> these sample rows, a dashboard screen with a KPI and a chart over tickets,\r\n> RBAC with an agent role that cannot delete, dark theme, then validate and\r\n> generate the app.\"\r\n\r\n## Step by step\r\n\r\n1. **Point it at a source.** A Drizzle schema file, or a handful of sample rows.\r\n2. **Introspect.** The agent calls `introspect_source` and shows you the drafted\r\n `EntitySchema` - field names, types, primary key, guessed formats.\r\n3. **Refine (optional).** Correct a type, mark a field hidden or read-only, add\r\n validation - in chat, or later in the [visual designer](./app-designer.md).\r\n4. **Scaffold.** The agent calls `scaffold_entity`; the files come back already\r\n run through the Svelte compiler.\r\n5. **Verify.** The agent runs your project's `svelte-check`; if anything fails it\r\n iterates. This is the loop that keeps AI output trustworthy.\r\n\r\n## Prompts that work\r\n\r\nFrom a Drizzle schema:\r\n\r\n> \"Using the svgrid MCP, build a CRUD screen for the `customers` table in\r\n> `src/lib/db/schema.ts`.\"\r\n\r\nFrom sample data, when there is no schema yet:\r\n\r\n> \"Here are five example rows of our invoices. Use the svgrid MCP to introspect a\r\n> schema, then scaffold a CRUD screen at `/invoices`.\"\r\n>\r\n> ```json\r\n> [{ \"id\": \"INV-1\", \"customer\": \"Acme\", \"amount\": 4200, \"paid\": true, \"due\": \"2026-07-01\" }]\r\n> ```\r\n\r\nRefining before you commit:\r\n\r\n> \"Show me the drafted schema first. Mark `internalNotes` hidden, make `email`\r\n> required, and set `status` to an enum of draft/sent/paid before scaffolding.\"\r\n\r\n## What comes back\r\n\r\n`scaffold_entity` writes three files (the same layout as the CLI and designer):\r\n\r\n```\r\nsrc/lib/customers.schema.ts # the EntitySchema + row type\r\nsrc/routes/api/customers/+server.ts # createKitHandlers data endpoint\r\nsrc/routes/customers/+page.svelte # the grid + edit-panel screen\r\n```\r\n\r\nEach carries `svgrid:managed` markers so a re-generation updates the managed\r\nregions and leaves your hand-written code untouched. See\r\n[code generation](./code-generation.md) for the anatomy of each file.\r\n\r\n## Bring your own key\r\n\r\nThe generator uses **your** agent's model and API key - your schema and data\r\nnever touch our servers. The MCP server itself makes no model calls; it provides\r\nintrospection + scaffolding + verification tools that the host agent drives.\r\n\r\n## Licensing\r\n\r\nGeneration is soft-gated: it runs unlicensed and prepends a one-line commercial\r\nnotice, and the generated app carries the usual watermark until you call\r\n`setLicenseKey()`. Set `SVGRID_LICENSE_KEY` in the MCP config to license it. See\r\n[licensing](../licensing.md#studio-data-app-generator).\r\n\r\n## See also\r\n\r\n- [The Studio CLI](./cli.md) - the deterministic, no-AI path\r\n- [Visual app designer](./app-designer.md) - refine an AI draft by hand before generating\r\n- [Code generation](./code-generation.md) - the anatomy of the emitted files\r\n- [MCP server](../../help/mcp-server.md) - full MCP reference\r\n"
2711
+ "markdown": "# AI generation\r\n\r\nThe `@svgrid/mcp` server exposes Studio to AI coding agents (Claude Code, Cursor,\r\nCodex, ...) through the Model Context Protocol. Ask your agent to build a screen\r\nfor a table and it introspects, scaffolds, and verifies - producing the same code\r\nthe [CLI](./cli.md) and [designer](./designer.md) do.\r\n\r\n![The generated files: schema module, +server.ts API route, and +page.svelte screen, with svgrid:managed markers.](/docs-media/studio-generated-code.png)\r\n\r\n## How it fits together\r\n\r\nThe MCP server makes **no model calls of its own**. It hands your agent a set of\r\ntools; the agent's own model decides when to call them. So the loop is:\r\n\r\n```\r\nyou -> your agent (its model) -> svgrid MCP tools -> files on disk\r\n ^ |\r\n +-------- svelte-check verify <----------+\r\n```\r\n\r\nYour schema and data stay on your machine; nothing is sent to our servers.\r\n\r\n## Configure the MCP server\r\n\r\nAdd it to your agent's MCP config (the key is passed as an env var, since the\r\nserver runs in a Node process):\r\n\r\n```jsonc\r\n{\r\n \"mcpServers\": {\r\n \"svgrid\": {\r\n \"command\": \"npx\",\r\n \"args\": [\"@svgrid/mcp\"],\r\n \"env\": { \"SVGRID_LICENSE_KEY\": \"SVENTERPRISE-...\" }\r\n }\r\n }\r\n}\r\n```\r\n\r\nThe same block works across hosts - only the file it lives in differs:\r\n\r\n| Host | Config location |\r\n| --- | --- |\r\n| Claude Code | `.mcp.json` at the project root, or `claude mcp add` |\r\n| Cursor | `.cursor/mcp.json` |\r\n| Windsurf | `~/.codeium/windsurf/mcp_config.json` |\r\n| Codex / other | the host's `mcpServers` config |\r\n\r\nRestart (or reload) the agent so it picks up the server, then confirm the\r\n`svgrid` tools are listed.\r\n\r\n## The tools\r\n\r\nAlongside the read-only knowledge tools (examples, docs, API reference), the\r\nserver exposes two generation tools:\r\n\r\n| Tool | What it does |\r\n| --- | --- |\r\n| `introspect_source` | Infer an `EntitySchema` from a Drizzle schema file (`kind:\"drizzle\"`) or sample JSON rows (`kind:\"json\"`). Returns a **draft** to review. |\r\n| `scaffold_entity` | Generate the SvelteKit files from an `EntitySchema`. The output is **compile-verified** (the generated page is run through the Svelte compiler) before it comes back, and each file carries `svgrid:managed` markers. |\r\n\r\n## Drive the whole project model\r\n\r\nBeyond single screens, the server exposes the full\r\n[project model](./concepts.md#the-project-model) - the same\r\n`studio.config.json` the visual designer edits - as a set of `studio_*` tools.\r\nYour agent can build a complete multi-screen app, or continue editing one the\r\ndesigner produced, and hand it back:\r\n\r\n| Tool | What it does |\r\n| --- | --- |\r\n| `studio_new_project` | Start a new, empty project |\r\n| `studio_load_project` | Load an existing `studio.config.json` to continue editing it |\r\n| `studio_describe_project` | Summarize the current project: entities, screens + block ids, theme, RBAC, auth, deploy |\r\n| `studio_get_config` | Return the project as a `studio.config.json` string - write it to disk and the designer opens it (round-trip) |\r\n| `studio_capabilities` | List what can be added: block kinds, UI component keys, theme presets, source kinds, deploy targets |\r\n| `studio_add_entity` | Add an entity + its default screen, from an `EntitySchema`, a Drizzle source, or sample JSON rows |\r\n| `studio_add_screen` | Add an entity-bound screen (default grid) or a freestanding page |\r\n| `studio_add_block` | Add a data block (grid, chart, kpi, gauge, tree, tabs, accordion, pivot, board, calendar, detail, master-detail, filter, record, lookup, dashboard) to a screen |\r\n| `studio_add_component` | Add a UI component block (button, badge, alert, card, stat, timeline, sparkline, chip, ...) with prop overrides |\r\n| `studio_update_block` | **Configure** an existing block - columns, editing mode, export buttons, grouping, chart dimension/measure, row links, format rules - plus its width, height, and class |\r\n| `studio_remove_block` | Remove a block from a screen |\r\n| `studio_move_block` | Reorder a block within its screen |\r\n| `studio_update_screen` | Rename a screen, change its route or nav entry, or set `renderMode` (`ssr` for an idiomatic `+page.server.ts` load + form actions, `spa` for the client page) |\r\n| `studio_remove_screen` | Remove a screen and its blocks |\r\n| `studio_set_screen_layout` | Switch a screen between `grid`, `stack`, `split`, `dock`, and `canvas` layouts |\r\n| `studio_set_form_layout` | Arrange an entity's [create/edit form](./edit-forms.md): column count + titled sections, or `\"suggest\": true` to have them proposed from the field names |\r\n| `studio_set_field_conditions` | Make a form field [value-driven](./edit-forms.md#fields-that-react-to-the-answers) - shown, required, or locked depending on the other answers |\r\n| `studio_set_entity_source` | Bind an entity to a data source: `sql`, `supabase`, `rest`, `pglite`, or `memory` |\r\n| `studio_set_theme` | Set the theme preset, light/dark mode, and accent color |\r\n| `studio_set_access` | Configure [RBAC](./access-control.md): roles gating screens and create/update/delete actions |\r\n| `studio_set_auth` | Configure the [auth starter](./auth.md): protect, register, user admin, 2FA, email, OAuth (`github` / `google` / `oidc`) |\r\n| `studio_set_data_layer` | Turn the typed Drizzle data layer (schema + repositories + migrations) on or off |\r\n| `studio_set_tenancy` | Turn [multi-tenancy](./access-control.md#multi-tenancy) on/off - scopes every row to the caller's tenant, enforced server-side; `sharedEntities` stay global |\r\n| `studio_set_job` | Schedule a background job (`email` digest or `code`) - emits the guarded `/api/cron` route + the platform schedule; omit `cron` to remove one |\r\n| `studio_set_deploy_target` | Set `auto` / `vercel` / `netlify` / `cloudflare` / `node` - picks the adapter and emits CI/CD config |\r\n| `studio_validate` | Validate the current project; returns errors + warnings |\r\n| `studio_generate_app` | Emit the full runnable SvelteKit app - every file, ready to write and `svelte-check` |\r\n\r\nA prompt that exercises the loop end to end:\r\n\r\n> \"Using the svgrid MCP: new project 'Support desk'. Add a `tickets` entity from\r\n> these sample rows, a dashboard screen with a KPI and a chart over tickets,\r\n> RBAC with an agent role that cannot delete, dark theme, then validate and\r\n> generate the app.\"\r\n\r\n## Step by step\r\n\r\n1. **Point it at a source.** A Drizzle schema file, or a handful of sample rows.\r\n2. **Introspect.** The agent calls `introspect_source` and shows you the drafted\r\n `EntitySchema` - field names, types, primary key, guessed formats.\r\n3. **Refine (optional).** Correct a type, mark a field hidden or read-only, add\r\n validation - in chat, or later in the [visual designer](./app-designer.md).\r\n4. **Scaffold.** The agent calls `scaffold_entity`; the files come back already\r\n run through the Svelte compiler.\r\n5. **Verify.** The agent runs your project's `svelte-check`; if anything fails it\r\n iterates. This is the loop that keeps AI output trustworthy.\r\n\r\n## Prompts that work\r\n\r\nFrom a Drizzle schema:\r\n\r\n> \"Using the svgrid MCP, build a CRUD screen for the `customers` table in\r\n> `src/lib/db/schema.ts`.\"\r\n\r\nFrom sample data, when there is no schema yet:\r\n\r\n> \"Here are five example rows of our invoices. Use the svgrid MCP to introspect a\r\n> schema, then scaffold a CRUD screen at `/invoices`.\"\r\n>\r\n> ```json\r\n> [{ \"id\": \"INV-1\", \"customer\": \"Acme\", \"amount\": 4200, \"paid\": true, \"due\": \"2026-07-01\" }]\r\n> ```\r\n\r\nRefining before you commit:\r\n\r\n> \"Show me the drafted schema first. Mark `internalNotes` hidden, make `email`\r\n> required, and set `status` to an enum of draft/sent/paid before scaffolding.\"\r\n\r\n## What comes back\r\n\r\n`scaffold_entity` writes three files (the same layout as the CLI and designer):\r\n\r\n```\r\nsrc/lib/customers.schema.ts # the EntitySchema + row type\r\nsrc/routes/api/customers/+server.ts # createKitHandlers data endpoint\r\nsrc/routes/customers/+page.svelte # the grid + edit-panel screen\r\n```\r\n\r\nEach carries `svgrid:managed` markers so a re-generation updates the managed\r\nregions and leaves your hand-written code untouched. See\r\n[code generation](./code-generation.md) for the anatomy of each file.\r\n\r\n## Bring your own key\r\n\r\nThe generator uses **your** agent's model and API key - your schema and data\r\nnever touch our servers. The MCP server itself makes no model calls; it provides\r\nintrospection + scaffolding + verification tools that the host agent drives.\r\n\r\n## Licensing\r\n\r\nGeneration is soft-gated: it runs unlicensed and prepends a one-line commercial\r\nnotice, and the generated app carries the usual watermark until you call\r\n`setLicenseKey()`. Set `SVGRID_LICENSE_KEY` in the MCP config to license it. See\r\n[licensing](../licensing.md#studio-data-app-generator).\r\n\r\n## See also\r\n\r\n- [The Studio CLI](./cli.md) - the deterministic, no-AI path\r\n- [Visual app designer](./app-designer.md) - refine an AI draft by hand before generating\r\n- [Code generation](./code-generation.md) - the anatomy of the emitted files\r\n- [MCP server](../../help/mcp-server.md) - full MCP reference\r\n"
2706
2712
  },
2707
2713
  {
2708
2714
  "slug": "enterprise/studio/api",
@@ -2714,7 +2720,7 @@ export const docs = [
2714
2720
  "slug": "enterprise/studio/app-designer",
2715
2721
  "path": "docs/enterprise/studio/app-designer.md",
2716
2722
  "title": "Visual app designer",
2717
- "markdown": "# Visual app designer\r\n\r\n`SvStudioDesigner` is the grid-centric visual **data-app** designer - compose a\r\nmulti-entity app by arranging data-bound blocks on a canvas, then generate a\r\nrunnable SvelteKit project. It's the app-level companion to the single-entity\r\n[schema designer](./designer.md): where that authors one `EntitySchema`, this\r\ncomposes **screens** across **many entities**.\r\n\r\nGrid-centric by design: the blocks are schema-driven and data-bound (a grid, a\r\nchart, a pivot, a dashboard, a KPI, master-detail, a faceted filter panel, a\r\nrecord panel, a lookup) - not arbitrary layout components. Point it at a\r\ndatabase, get a CRUD app - kept to data views.\r\n\r\n> **Just want to open it?** `npx @svgrid/studio designer` launches this designer\r\n> in your browser, auto-saves your work to `studio.config.json`, and writes the\r\n> generated app to a folder - no host app needed. See\r\n> [Launch the designer](./launch.md).\r\n\r\n## Starting a new app\r\n\r\n**New app** in the top bar walks you from nothing to a working CRUD app: pick\r\nwhere the data comes from, choose the tables, choose the pages, open the result.\r\n\r\n1. **Start** - sample data, your own data, or a blank set of tables you name.\r\n2. **Data** - connect a database (the table picker shows row counts and lets you\r\n preview rows before importing), read a **Supabase** project, pick a starter\r\n dataset, point at a REST endpoint, or paste an OpenAPI document.\r\n3. **Screens** - tick which pages each table gets (list, form, record page,\r\n dashboard) and how rows are edited: a popup form, in the grid, or on the\r\n record page.\r\n4. **Done** - name it and open it. It arrives as one undo step, so Ctrl+Z puts\r\n the previous design back.\r\n\r\nConnecting to a live SQL database needs the local designer\r\n(`npx @svgrid/studio dev`) because database drivers run on your machine, not in a\r\nbrowser tab. **Supabase is the exception**: it serves its own REST API, so the\r\nwizard reads your tables with just the project URL and the anon key - it is the\r\none real database that works from\r\n[svgrid.com/studio](https://svgrid.com/studio) with nothing installed. Row-level\r\nsecurity still applies, so the app sees exactly what the browser may see. The\r\nother paths work there too, and you can rebind to any database later with\r\n**Use my data**.\r\n\r\nThe terminal equivalent is [`svgrid-studio init`](./cli.md#init) - the same\r\ngenerator, the same app. The questions differ slightly: the CLI also asks for a\r\ntheme and light/dark, while the wizard offers a dashboard page per table (pick\r\nyour theme in the designer afterwards).\r\n\r\n## How the screen is laid out\r\n\r\nHere is the real designer with a small Sales App open:\r\n\r\n![The visual app designer: a Pages and Entities rail on the left, Design / Code tabs above a live grid preview (a customer grid with status chips) in the middle, and a screen properties panel on the right.](/docs-media/studio-app-designer.png)\r\n\r\nYou do not need to understand the internals to use it. The same layout, labelled:\r\n\r\n![The visual designer's layout: a screens list on the left, a palette of blocks to add, a live preview in the middle, a properties panel on the right, and a Generate app button in the top bar.](/docs-media/studio-designer-anatomy.svg)\r\n\r\n- **Screens** (far left) - the pages of your app. Click one to edit it; **+ Add\r\n screen** makes a new one.\r\n- **Blocks** - the pieces you drop onto a screen: a **grid** (a table of records),\r\n a **chart**, a **pivot**, a **KPI** number, a **dashboard**, a **filter panel**,\r\n and a **record panel**. Click or drag one onto the preview.\r\n- **Live preview** (middle) - your screen with **real data**, updating as you\r\n change things. What you see is what the app will look like.\r\n- **Properties** (right) - tune the selected block, or - with nothing selected -\r\n edit the entity's **fields** and pick its **data source**.\r\n- **Generate app** (top right) - when it looks right, one click writes the whole,\r\n runnable app.\r\n\r\nThe rest of this page is the detailed reference for each area, aimed at developers\r\nembedding or scripting the designer. If you just want to build an app, everything\r\nabove is done by pointing and clicking - see\r\n[Launch the designer](./launch.md).\r\n\r\n## What it edits: the project model\r\n\r\nThe designer reads and writes a `StudioProject` - the declarative model behind\r\nthe whole app:\r\n\r\n```ts\r\nimport { createProject } from '@svgrid/enterprise'\r\n\r\n// One default screen (grid + edit form) per entity, in-memory.\r\nlet project = $state(createProject([customerSchema, orderSchema], { title: 'Sales App' }))\r\n```\r\n\r\n```svelte {nocheck}\r\n<script lang=\"ts\">\r\n import { SvStudioDesigner } from '@svgrid/enterprise'\r\n</script>\r\n\r\n<SvStudioDesigner {project} onChange={(p) => (project = p)} />\r\n```\r\n\r\nThe designer is a single IDE-style frame: a **title bar** (app name + accent +\r\nundo/redo + Import/Load/Save/Generate), a **screen tab strip** (switch, close, or\r\nadd a screen), the three work panels, and a **status bar** (validity, entity /\r\nscreen / block counts, current selection, data source).\r\n\r\n- **Rail** (left) - switch and add screens; each screen is bound to an entity.\r\n- **Screen tabs** - the open screens as a document strip; click to switch, the\r\n **x** to remove one, the **+** to add one (from the current template).\r\n- **Palette** - the block kinds. **Drag** one onto the canvas to add it (or click).\r\n- **Canvas** - the screen's blocks in a responsive **12-column** grid, previewed\r\n live with the real components. **Drag a block** to reorder it; **drag its right\r\n edge** to set its width (1-12 columns), use the ⅓ / ½ / ⅔ / full quick buttons\r\n in its header, or the **Layout > Width** slider. **Drag a block's bottom edge**\r\n to make its region taller or shorter - grid, chart, pivot, and master-detail\r\n blocks are height-resizable, and the chosen height flows through to the generated app\r\n (also set it precisely under **Layout > Height** in the inspector).\r\n- **Inspector** (right) - edit the selected block: a grid's **editing mode** +\r\n behavior + **column config** (see below), a chart's group-by / measure / reduce\r\n / type. With no block selected, edit the **page** (title / route /\r\n **nav** settings), the entity's **data source** (see below), and the **entity's\r\n fields** - add, rename, retype, flag (PK / required / read-only), pick a\r\n **relation's target entity + label field**, and **drag to reorder**. The rail\r\n sets the **default source kind** for new entities, the **app layout**, and adds\r\n screens from a **template** (CRUD, dashboard, master-detail, empty).\r\n- **Top bar** - rename the app, set an **accent color** (themes the whole app),\r\n add a **New entity** from scratch, **Import CSV** (drop in a spreadsheet - see\r\n below), **Connect DB** (the launcher's live-database wizard - see\r\n [Launch the designer](./launch.md)), **Import schema** (paste a Drizzle / Prisma\r\n schema to add its entities), **Save / Load** the design as `studio.config.json`,\r\n and **Generate app**. With no entities yet, the canvas shows an **onboarding**\r\n screen offering the same ways to start.\r\n- **✨ Copilot** (when the host wires it) - describe a change in plain English\r\n (\"add an orders screen with a revenue chart\", \"make mrr required\") and the AI\r\n edits your project. It's a host hook: `<SvStudioDesigner onCopilot={...} />`\r\n receives `{ prompt, project }` and returns the edited `StudioProject` - your AI\r\n keys stay server-side. The result is validated before it applies, and it's one\r\n **Ctrl/Cmd+Z** away.\r\n\r\nThe three panels are **resizable** (drag the dividers). Every edit is **undoable**\r\n(Ctrl/Cmd+Z, Ctrl+Shift+Z / Ctrl+Y to redo); **Delete** removes the selected\r\nblock, **Ctrl/Cmd+D** (or the **⧉** header button) **duplicates** it, and\r\n**Escape** deselects. **Preview app** opens the whole app full-screen with a\r\n**Desktop / Tablet / Mobile** device-width toggle to check responsiveness. **Generate app** opens the output in a\r\n**file-tree viewer** modal (scrollable, Copy per file) and a **Download .zip** of\r\nthe **complete runnable SvelteKit + Vite project** - unzip, `npm install`,\r\n`npm run dev`. The zip includes `package.json` (with the right driver deps),\r\n`vite.config.ts`, `svelte.config.js`, `tsconfig.json`, the app shell, and every\r\ngenerated screen.\r\n\r\n## The grid (and how it edits)\r\n\r\nThe grid is the core block, so **editing is a grid property**, not a separate\r\nblock. Select a grid and set its **Editing mode**:\r\n\r\n- **Popup form** - double-click a row to edit it in a modal / drawer / inline\r\n panel (pick the **Form style**); a **+ New** button adds rows. This is the\r\n default.\r\n- **Inline** - edit cells right in the grid (Excel-style); each change saves via\r\n the data source.\r\n- **Read-only** - no editing.\r\n\r\nThe grid's property editor also covers **Behavior** (Sortable, Filtering + search,\r\nRow selection, Cell range selection, Striped rows, Totals footer row, Density),\r\n**Paging** (Paginate on / off, Page size, **Pager position** - bottom / top / both,\r\nand the **Page size options** for the selector), and per-**Column** settings -\r\nexpand a column to set its **header**, **width**, **alignment**, **pin** (left /\r\nright), plus show / hide + reorder. There is no standalone \"Edit form\" block - the\r\ngrid owns editing end to end.\r\n\r\n### Export toolbar\r\n\r\n**Export toolbar** adds a button bar above the grid. Six options, in two groups:\r\n\r\n| Button | Runs through | Adds to the generated app |\r\n| ------ | ------------ | ------------------------- |\r\n| Export CSV / Export JSON / Copy | the free grid API | nothing |\r\n| Export Excel (.xlsx) | `@svgrid/enterprise` | `jszip` |\r\n| Export PDF | `@svgrid/enterprise` | `pdfmake` |\r\n| Print | `@svgrid/enterprise` | nothing |\r\n\r\nThe Excel export is real OOXML - typed number and date cells, styled headers, a\r\nfrozen header row - not a renamed CSV. PDF is paginated with a repeating header,\r\nand Print opens the browser's print dialog on a paginated layout.\r\n\r\nAll six export what the user currently sees: the visible columns, in their\r\ncurrent order, over the filtered and sorted rows. The optional dependencies are\r\ndeclared only for the buttons you switch on, so a CSV-only app installs neither.\r\nThe canvas preview runs the same code the generated app does, so you can try a\r\nreal export before generating.\r\n\r\n## The analytical + companion blocks\r\n\r\nBeyond the grid, every block is still bound to the `EntitySchema` - these are data\r\nviews, not generic widgets:\r\n\r\n| Block | What it renders | Inspector |\r\n| --- | --- | --- |\r\n| **Chart** | A chart (`SvSchemaChart`) - bar, pie, line, area, radar, funnel, waterfall, or treemap. | Group-by dimension, measure, reduce, type. |\r\n| **Pivot** | A full pivot table (`SvPivotDesigner`) the end user can re-pivot live. | Row + column dimensions (checkboxes), a measure, and its aggregate. |\r\n| **Dashboard** | A schema-driven KPI + chart board (`SvSchemaDashboard`). | - |\r\n| **KPI** | A single reduced metric tile. | Label, measure, reduce. |\r\n| **Gauge** | A radial gauge (`SvGauge`) of one reduced measure within a range - utilization, progress, scores. | Label, measure, reduce, min / max, unit. |\r\n| **Tree** | A hierarchical tree (`SvTree`) built from the entity's own rows via a self-referential parent. | A label field + a parent field (a row's link to its parent row). |\r\n| **Tabs** | A tabbed container (`SvTabs`) that **groups display blocks** into tabs - e.g. an Overview tab of KPIs + a Details tab with a chart. | Add / rename / remove tabs; per tab, add child blocks (charts, KPIs, gauges, pivots, trees). |\r\n| **Accordion** | A collapsible-sections container (like Tabs, stacked vertically). | Add / rename / remove sections; child blocks per section. |\r\n| **Master / detail** | A row that expands into a nested grid of related records. | Child entity + foreign key. |\r\n| **Board** | A kanban board of the entity's rows, one lane per value of a group-by field, with drag between lanes. | Group-by, card title / subtitle / badge fields, open-screen drill. |\r\n| **Calendar** | A month event-calendar: each row with a date lands on its day, labelled and optionally color-coded. | Date field, title field, color field, open-screen drill. |\r\n| **Detail** | A full record \"detail page\": header, metric row, field sections, and related-record tabs - the 360 view a row action or drill-through opens. | Title / subtitle / status / metric fields, sections, related child entities. |\r\n| **Form** | A standalone create / edit form for the entity. | Presentation (drawer / modal / inline). |\r\n| **Filter panel** | A faceted sidebar that **filters the screen's grid** - enum / boolean facets pick a value, text facets search. | Title + which fields become facets. |\r\n| **Record panel** | Shows the row **selected in the grid** - a read-only field list, or an inline edit form. | Editable on / off, and (read-only) which fields to show. |\r\n| **Lookup** | Marks a relation field as a searchable picker in the edit form. | The relation field. |\r\n| **UI component** | A component from the SvGrid UI kit dropped from the toolbox - entity-agnostic, works on freestanding pages too. Grouped as Actions, Inputs, Display, Feedback, Layout, and Navigation, and covering headings and prose (heading, text, link, quote, code, keyboard key, list) as well as controls, pickers, and date/time inputs. | The component's own props (extracted from the component's own types, with its JSDoc as the tooltip), plus data bindings. |\r\n\r\nThe **filter** and **record** panels wire to the grid on the same screen: the\r\nfilter panel calls the grid controller's `setFilter`, and clicking a grid row\r\npublishes it to the record panel. So a common layout is a **filter panel + grid +\r\nrecord panel** three-up - list, narrow, inspect - all generated for you.\r\n\r\nThe grid, chart, pivot, and master-detail blocks are **height-resizable** (drag\r\nthe block's bottom edge, or set **Layout > Height**), and the chosen height flows\r\ninto the generated app. The filter and record panels size to their content.\r\n\r\n### Conditional formatting\r\n\r\nA grid's inspector has a **Conditional formatting** section: add no-code rules\r\nthat style a cell by its value - pick a field, a comparison (`=`, `<`, `>`,\r\n`contains`, `is empty`, ...), a value, and a **text color / fill / bold**. Rules\r\nrender **live in the canvas** and compile to the grid's built-in\r\n`conditionalFormats` rule engine in the generated app (e.g. negative `mrr` red,\r\n`status = overdue` filled). It's the same engine you'd use by hand - the designer\r\njust authors the rules.\r\n\r\n### Navigation & row actions\r\n\r\nThe grid's **Navigation & actions** section wires flow between screens:\r\n**drill-through** (row click opens another screen, filtered to the clicked value)\r\nand **row action buttons** (Edit / Delete / Open). A chart can drill too. See\r\n[Navigation & row actions](./navigation.md) for the full picture.\r\n\r\n## Data sources (per entity)\r\n\r\nEach entity binds to **its own backend** - the designer is not limited to one\r\ndata source per app. In the inspector (no block selected), the **Data source**\r\nsection binds the screen's entity to:\r\n\r\n- **In-memory** - seeded sample rows, runs with no backend (the default).\r\n- **Local database (no setup)** - a real, persistent Postgres ([PGlite](https://pglite.dev))\r\n running in the browser and saved to IndexedDB, so rows survive reloads with zero\r\n backend. Same SQL as production - swap to a hosted **SQL** source later without\r\n touching the schema. See [Local database](./local-database.md).\r\n The builder is a draggable, resizable, maximizable panel, and you can open it for\r\n any entity straight from the **Data model** dialog's **Configure** button.\r\n\r\n- **REST API** - a request builder: **method**, **base URL**, **path** (path\r\n params auto-derive from `{tokens}`), and **Query / Path / Header** tabs. **Send**\r\n runs it live and shows a **response table** plus the real rows in the canvas\r\n grid; **Import fields** rewrites the entity's schema to match the response keys.\r\n An **API format** picker (Manual / Offset + Limit / DummyJSON / json-server)\r\n wires a wire-format adapter so the grid does **real server-side paging and sort**;\r\n Manual keeps the rows-path / total-path mapping for a single fetched page.\r\n- **Supabase** - **List tables** reads your project's tables so you pick one, and\r\n **Import schema** pulls its real columns, primary key, foreign keys, and enums\r\n into the entity; **Preview** shows live rows (works in the online designer, since\r\n Supabase is HTTP). The project **URL + anon key are a shared connection** you set\r\n once for every Supabase entity. A **Live updates (Realtime)** toggle emits a live\r\n subscription. **Generate app** emits `createClient` in `connections.ts` reading\r\n `PUBLIC_SUPABASE_URL` / `PUBLIC_SUPABASE_ANON_KEY` from `.env` (the key is never\r\n inlined) and adds `@supabase/supabase-js`; access is protected by your RLS\r\n policies.\r\n- **SQL** - paste a connection string (the dialect is auto-detected) or fill the\r\n guided form, set the **Schema** (Postgres search path). In the **local** designer\r\n (`npx @svgrid/studio designer`) **Preview data** runs a real `SELECT` and shows\r\n your actual rows on the canvas, and a missing `pg` / `mysql2` / ... driver is a\r\n **one-click install**. **Generate app** emits a connected\r\n `src/routes/api/<table>/+server.ts` (the dialect's driver, reading\r\n `DATABASE_URL`) and points the grid at it; the driver dep is added for you. In\r\n the **online** designer, bind the entity and generate - the app connects for real\r\n at runtime. To scaffold from an existing DB via CLI: `npx @svgrid/studio add\r\n <table> --db <dialect> --url …`.\r\n\r\n**Generate app** then emits the matching adapter per entity in `src/lib/data.ts`\r\n(`createRestDataSource` / `createSqlDataSource` / `createSupabaseDataSource` /\r\n`createInMemoryDataSource`). SQL and Supabase entities read their connection from a\r\ngenerated `src/lib/connections.ts` (or the `+server.ts` route) - the SQL driver and\r\nthe Supabase client are wired for you; the only manual step is setting the\r\nconnection in `.env` (the bundle ships a `.env.example`). See\r\n[Databases](./databases.md) and [REST API](./rest-api.md).\r\n\r\n## Import a CSV / spreadsheet\r\n\r\nThe fastest way to start from **your own data**: **Import CSV** in the top bar\r\ntakes a `.csv` file and turns it into a running screen. The designer parses the\r\nfile (quoted fields, embedded newlines, and CRLF included), **infers a type per\r\ncolumn** from its values (number, boolean, date, or text - thousands separators\r\nand `yes/no/true/false` are understood), ensures a **primary key** (it reuses an\r\n`id` column or synthesizes one), and adds an entity with a full CRUD screen,\r\n**seeded with the real rows**. Unsafe headers (`First Name`, `E-mail`) become safe\r\nfield keys and the note tells you what was renamed.\r\n\r\nImported rows ship **in-memory** by default (no dependencies), so the app runs\r\nimmediately. Switch that entity's **Data source** to **Local database** to make\r\nthe same imported rows **persist** across reloads - the seed carries over. This is\r\nall client-side: `csvToEntity(name, text)` is a pure function exported from\r\n`@svgrid/enterprise`, so the same import works in the CLI and your own tools.\r\n\r\n> **Large files:** the imported rows are stored as the entity's seed, so they are\r\n> embedded in `studio.config.json` when you **Save** the design. That is fine for\r\n> reference data and samples; for a large dataset, import a representative sample\r\n> and point the entity at a **database** (Local database / SQL) for the full data.\r\n\r\n## Pages and layout\r\n\r\n- **Pages** - each screen is a route. In the inspector's **Page** section, toggle\r\n **Show in navigation**, set a **nav label** and **nav order**, or start a page\r\n from the **Empty** template. Hidden pages stay routable but drop out of the nav.\r\n- **Render mode** - eligible screens (a single plain grid, or read-only block\r\n screens, on a memory / SQL source) can switch from the default client page to\r\n **SSR**: an idiomatic `+page.server.ts` with `load` + form actions. The rules\r\n are in [Code generation](./code-generation.md#render-mode-spa-or-ssr-per-screen).\r\n- **App layout** - the rail's **App layout** section themes the generated shell:\r\n **Sidebar** or **Top navigation**, a **brand** name, a **company logo**\r\n (uploaded - stored inline and shown in the nav in place of the brand text), a\r\n **footer**, and (for the sidebar) the **nav position** (left / right). This\r\n drives the generated `src/routes/+layout.svelte`. The generated shell is\r\n **responsive**: on phones the sidebar collapses to a hamburger drawer, the\r\n top-nav links scroll, and each screen's block grid stacks to one column.\r\n\r\n## Save, regenerate, round-trip\r\n\r\nThe `StudioProject` is the persisted design. **Save config** exports a\r\n`studio.config.json`; regenerate the app from it any time:\r\n\r\n```bash\r\nnpm create @svgrid/studio@latest my-app -- --project ./studio.config.json\r\n```\r\n\r\nOr programmatically:\r\n\r\n```ts\r\nimport { serializeProject, parseProject, emitStudioProject } from '@svgrid/enterprise'\r\n\r\nconst json = serializeProject(project) // save the design\r\nconst project2 = parseProject(json) // reopen it\r\nconst files = emitStudioProject(project2) // -> the app's source files\r\n```\r\n\r\n**The exported app carries its own design.** The downloaded zip includes a\r\n`studio.config.json` at its root. To keep editing the app visually after you've\r\nworked on it locally, open the designer and **Load** that file - entities,\r\nscreens, blocks, theme, RBAC, i18n, and now the logo all come back exactly as\r\ngenerated. Because the designer regenerates the files under `src/`, keep any\r\nhand-written code in **new** files/modules you import, so a re-generate never\r\noverwrites it (or use the CLI's `svgrid:managed` markers - see\r\n[Code generation](./code-generation.md)).\r\n\r\n## Generate the app\r\n\r\n**Generate app** emits `src/lib/schemas.ts`, `src/lib/data.ts` (the right adapter\r\nper entity, plus `src/lib/connections.ts` when any entity is SQL / Supabase-bound),\r\nand one `src/routes/<route>/+page.svelte` **per screen** that composes that\r\nscreen's blocks with their config - a grid (with your visible columns, in order) +\r\nedit modal, plus any charts / pivots / dashboard / KPI tiles, and filter / record\r\npanels wired to the grid, all bound to the data - and the\r\nnav layout (sidebar or top-nav) + home. The pages are self-contained (they use\r\n`@svgrid/grid` + `@svgrid/enterprise` directly), so the output runs as a standard\r\nSvelteKit app.\r\n\r\n```ts\r\nimport { emitStudioProject } from '@svgrid/enterprise'\r\nconst files = emitStudioProject(project) // [{ path, contents, description }, ...]\r\n```\r\n\r\n## See also\r\n\r\n- [Code behind (Code view)](./code-behind.md) - write TypeScript against a typed `ctx` (grid API, events, `ctx.grid.sortable = true`, lifecycle)\r\n- [Sample apps + bind your data](./samples.md) - start from a ready-made app, then point it at your database\r\n- [Launch the designer](./launch.md) - `npx @svgrid/studio designer` (auto-save + generate to a folder)\r\n- [Schema designer](./designer.md) - author a single entity\r\n- [Dashboards](./dashboards.md) · [Databases](./databases.md) - the blocks + data sources\r\n- [CLI](./cli.md) / [Drizzle](./drizzle.md) / [Prisma](./prisma.md) - import a schema to design from\r\n"
2723
+ "markdown": "# Visual app designer\r\n\r\n`SvStudioDesigner` is the grid-centric visual **data-app** designer - compose a\r\nmulti-entity app by arranging data-bound blocks on a canvas, then generate a\r\nrunnable SvelteKit project. It's the app-level companion to the single-entity\r\n[schema designer](./designer.md): where that authors one `EntitySchema`, this\r\ncomposes **screens** across **many entities**.\r\n\r\nGrid-centric by design: the blocks are schema-driven and data-bound (a grid, a\r\nchart, a pivot, a dashboard, a KPI, master-detail, a faceted filter panel, a\r\nrecord panel, a lookup) - not arbitrary layout components. Point it at a\r\ndatabase, get a CRUD app - kept to data views.\r\n\r\n> **Just want to open it?** `npx @svgrid/studio designer` launches this designer\r\n> in your browser, auto-saves your work to `studio.config.json`, and writes the\r\n> generated app to a folder - no host app needed. See\r\n> [Launch the designer](./launch.md).\r\n\r\n## Starting a new app\r\n\r\n**New app** in the top bar walks you from nothing to a working CRUD app: pick\r\nwhere the data comes from, choose the tables, choose the pages, open the result.\r\n\r\n1. **Start** - sample data, your own data, or a blank set of tables you name.\r\n2. **Data** - connect a database (the table picker shows row counts and lets you\r\n preview rows before importing), read a **Supabase** project, pick a starter\r\n dataset, point at a REST endpoint, or paste an OpenAPI document.\r\n3. **Screens** - tick which pages each table gets (list, form, record page,\r\n dashboard) and how rows are edited: a popup form, in the grid, or on the\r\n record page.\r\n4. **Done** - name it and open it. It arrives as one undo step, so Ctrl+Z puts\r\n the previous design back.\r\n\r\nConnecting to a live SQL database needs the local designer\r\n(`npx @svgrid/studio dev`) because database drivers run on your machine, not in a\r\nbrowser tab. **Supabase is the exception**: it serves its own REST API, so the\r\nwizard reads your tables with just the project URL and the anon key - it is the\r\none real database that works from\r\n[svgrid.com/studio](https://svgrid.com/studio) with nothing installed. Row-level\r\nsecurity still applies, so the app sees exactly what the browser may see. The\r\nother paths work there too, and you can rebind to any database later with\r\n**Use my data**.\r\n\r\nThe terminal equivalent is [`svgrid-studio init`](./cli.md#init) - the same\r\ngenerator, the same app. The questions differ slightly: the CLI also asks for a\r\ntheme and light/dark, while the wizard offers a dashboard page per table (pick\r\nyour theme in the designer afterwards).\r\n\r\n## How the screen is laid out\r\n\r\nHere is the real designer with a small Sales App open:\r\n\r\n![The visual app designer: a Pages and Entities rail on the left, Design / Code tabs above a live grid preview (a customer grid with status chips) in the middle, and a screen properties panel on the right.](/docs-media/studio-app-designer.png)\r\n\r\nYou do not need to understand the internals to use it. The same layout, labelled:\r\n\r\n![The visual designer's layout: a screens list on the left, a palette of blocks to add, a live preview in the middle, a properties panel on the right, and a Generate app button in the top bar.](/docs-media/studio-designer-anatomy.svg)\r\n\r\n- **Screens** (far left) - the pages of your app. Click one to edit it; **+ Add\r\n screen** makes a new one.\r\n- **Blocks** - the pieces you drop onto a screen: a **grid** (a table of records),\r\n a **chart**, a **pivot**, a **KPI** number, a **dashboard**, a **filter panel**,\r\n and a **record panel**. Click or drag one onto the preview.\r\n- **Live preview** (middle) - your screen with **real data**, updating as you\r\n change things. What you see is what the app will look like.\r\n- **Properties** (right) - tune the selected block, or - with nothing selected -\r\n edit the entity's **fields** and pick its **data source**.\r\n- **Generate app** (top right) - when it looks right, one click writes the whole,\r\n runnable app.\r\n\r\nThe rest of this page is the detailed reference for each area, aimed at developers\r\nembedding or scripting the designer. If you just want to build an app, everything\r\nabove is done by pointing and clicking - see\r\n[Launch the designer](./launch.md).\r\n\r\n## What it edits: the project model\r\n\r\nThe designer reads and writes a `StudioProject` - the declarative model behind\r\nthe whole app:\r\n\r\n```ts\r\nimport { createProject } from '@svgrid/enterprise'\r\n\r\n// One default screen (grid + edit form) per entity, in-memory.\r\nlet project = $state(createProject([customerSchema, orderSchema], { title: 'Sales App' }))\r\n```\r\n\r\n```svelte {nocheck}\r\n<script lang=\"ts\">\r\n import { SvStudioDesigner } from '@svgrid/enterprise'\r\n</script>\r\n\r\n<SvStudioDesigner {project} onChange={(p) => (project = p)} />\r\n```\r\n\r\nThe designer is a single IDE-style frame: a **title bar** (app name + accent +\r\nundo/redo + Import/Load/Save/Generate), a **screen tab strip** (switch, close, or\r\nadd a screen), the three work panels, and a **status bar** (validity, entity /\r\nscreen / block counts, current selection, data source).\r\n\r\n- **Rail** (left) - switch and add screens; each screen is bound to an entity.\r\n- **Screen tabs** - the open screens as a document strip; click to switch, the\r\n **x** to remove one, the **+** to add one (from the current template).\r\n- **Palette** - the block kinds. **Drag** one onto the canvas to add it (or click).\r\n- **Canvas** - the screen's blocks in a responsive **12-column** grid, previewed\r\n live with the real components. **Drag a block** to reorder it; **drag its right\r\n edge** to set its width (1-12 columns), use the ⅓ / ½ / ⅔ / full quick buttons\r\n in its header, or the **Layout > Width** slider. **Drag a block's bottom edge**\r\n to make its region taller or shorter - grid, chart, pivot, and master-detail\r\n blocks are height-resizable, and the chosen height flows through to the generated app\r\n (also set it precisely under **Layout > Height** in the inspector).\r\n- **Inspector** (right) - edit the selected block: a grid's **editing mode** +\r\n behavior + **column config** (see below), a chart's group-by / measure / reduce\r\n / type. With no block selected, edit the **page** (title / route /\r\n **nav** settings), the entity's **data source** (see below), and the **entity's\r\n fields** - add, rename, retype, flag (PK / required / read-only), pick a\r\n **relation's target entity + label field**, and **drag to reorder**. The rail\r\n sets the **default source kind** for new entities, the **app layout**, and adds\r\n screens from a **template** (CRUD, dashboard, master-detail, empty).\r\n- **Top bar** - rename the app, set an **accent color** (themes the whole app),\r\n add a **New entity** from scratch, **Import CSV** (drop in a spreadsheet - see\r\n below), **Connect DB** (the launcher's live-database wizard - see\r\n [Launch the designer](./launch.md)), **Import schema** (paste a Drizzle / Prisma\r\n schema to add its entities), **Save / Load** the design as `studio.config.json`,\r\n and **Generate app**. With no entities yet, the canvas shows an **onboarding**\r\n screen offering the same ways to start.\r\n- **✨ Copilot** (when the host wires it) - describe a change in plain English\r\n (\"add an orders screen with a revenue chart\", \"make mrr required\") and the AI\r\n edits your project. It's a host hook: `<SvStudioDesigner onCopilot={...} />`\r\n receives `{ prompt, project }` and returns the edited `StudioProject` - your AI\r\n keys stay server-side. The result is validated before it applies, and it's one\r\n **Ctrl/Cmd+Z** away.\r\n\r\nThe three panels are **resizable** (drag the dividers). Every edit is **undoable**\r\n(Ctrl/Cmd+Z, Ctrl+Shift+Z / Ctrl+Y to redo); **Delete** removes the selected\r\nblock, **Ctrl/Cmd+D** (or the **⧉** header button) **duplicates** it, and\r\n**Escape** deselects. **Preview app** opens the whole app full-screen with a\r\n**Desktop / Tablet / Mobile** device-width toggle to check responsiveness. **Generate app** opens the output in a\r\n**file-tree viewer** modal (scrollable, Copy per file) and a **Download .zip** of\r\nthe **complete runnable SvelteKit + Vite project** - unzip, `npm install`,\r\n`npm run dev`. The zip includes `package.json` (with the right driver deps),\r\n`vite.config.ts`, `svelte.config.js`, `tsconfig.json`, the app shell, and every\r\ngenerated screen.\r\n\r\n## The grid (and how it edits)\r\n\r\nThe grid is the core block, so **editing is a grid property**, not a separate\r\nblock. Select a grid and set its **Editing mode**:\r\n\r\n- **Popup form** - double-click a row to edit it in a modal / drawer / inline\r\n panel (pick the **Form style**); a **+ New** button adds rows. This is the\r\n default.\r\n- **Inline** - edit cells right in the grid (Excel-style); each change saves via\r\n the data source.\r\n- **Read-only** - no editing.\r\n\r\nThe grid's property editor also covers **Behavior** (Sortable, Filtering + search,\r\nRow selection, Cell range selection, Striped rows, Totals footer row, Density),\r\n**Paging** (Paginate on / off, Page size, **Pager position** - bottom / top / both,\r\nand the **Page size options** for the selector), and per-**Column** settings -\r\nexpand a column to set its **header**, **width**, **alignment**, **pin** (left /\r\nright), plus show / hide + reorder.\r\n\r\n**Editing an existing record is a grid property**, not a block - the grid owns it\r\nend to end. The one thing a grid cannot give you is a form with no grid behind\r\nit, which is what the **Form** block is for (see below).\r\n\r\n### Form builder\r\n\r\n**Open form builder** - on the grid's **Form** tab, and on the entity's **Form\r\nlayout** section - opens the form in a room of its own. The canvas draws the form\r\nas it will look, with real labels, control shapes, and column spans, and you drag\r\nthe fields around on it directly. Click a field to rename it, change its control,\r\nadd help text, span it across the row, or give it a rule so it only appears (or\r\nonly becomes required, or locks) once another answer calls for it. **Group these\r\nfor me** sections an unarranged form in one click, and **Try it** swaps in the\r\nlive panel so you can type into the form and watch a condition fire.\r\n\r\nIt edits the *entity*, not the block, so the arrangement follows the entity\r\neverywhere it is rendered - including a server-rendered screen. See\r\n[edit forms](./edit-forms.md#building-one-without-writing-it).\r\n\r\n### Export toolbar\r\n\r\n**Export toolbar** adds a button bar above the grid. Six options, in two groups:\r\n\r\n| Button | Runs through | Adds to the generated app |\r\n| ------ | ------------ | ------------------------- |\r\n| Export CSV / Export JSON / Copy | the free grid API | nothing |\r\n| Export Excel (.xlsx) | `@svgrid/enterprise` | `jszip` |\r\n| Export PDF | `@svgrid/enterprise` | `pdfmake` |\r\n| Print | `@svgrid/enterprise` | nothing |\r\n\r\nThe Excel export is real OOXML - typed number and date cells, styled headers, a\r\nfrozen header row - not a renamed CSV. PDF is paginated with a repeating header,\r\nand Print opens the browser's print dialog on a paginated layout.\r\n\r\nAll six export what the user currently sees: the visible columns, in their\r\ncurrent order, over the filtered and sorted rows. The optional dependencies are\r\ndeclared only for the buttons you switch on, so a CSV-only app installs neither.\r\nThe canvas preview runs the same code the generated app does, so you can try a\r\nreal export before generating.\r\n\r\n## The analytical + companion blocks\r\n\r\nBeyond the grid, every block is still bound to the `EntitySchema` - these are data\r\nviews, not generic widgets:\r\n\r\n| Block | What it renders | Inspector |\r\n| --- | --- | --- |\r\n| **Form** | A standalone **create** form (`SvGridEditPanel`, inline and blank) - a \"New ticket\" page, an intake screen. Its fields, sections and rules come from the entity, so a form you designed once renders the same anywhere. | Heading, submit label, and what happens after saving: blank it for another entry, or go to a screen. Plus **Open form builder**. |\r\n| **Chart** | A chart (`SvSchemaChart`) - bar, pie, line, area, radar, funnel, waterfall, or treemap. | Group-by dimension, measure, reduce, type. |\r\n| **Pivot** | A full pivot table (`SvPivotDesigner`) the end user can re-pivot live. | Row + column dimensions (checkboxes), a measure, and its aggregate. |\r\n| **Dashboard** | A schema-driven KPI + chart board (`SvSchemaDashboard`). | - |\r\n| **KPI** | A single reduced metric tile. | Label, measure, reduce. |\r\n| **Gauge** | A radial gauge (`SvGauge`) of one reduced measure within a range - utilization, progress, scores. | Label, measure, reduce, min / max, unit. |\r\n| **Tree** | A hierarchical tree (`SvTree`) built from the entity's own rows via a self-referential parent. | A label field + a parent field (a row's link to its parent row). |\r\n| **Tabs** | A tabbed container (`SvTabs`) that **groups display blocks** into tabs - e.g. an Overview tab of KPIs + a Details tab with a chart. | Add / rename / remove tabs; per tab, add child blocks (charts, KPIs, gauges, pivots, trees). |\r\n| **Accordion** | A collapsible-sections container (like Tabs, stacked vertically). | Add / rename / remove sections; child blocks per section. |\r\n| **Master / detail** | A row that expands into a nested grid of related records. | Child entity + foreign key. |\r\n| **Board** | A kanban board of the entity's rows, one lane per value of a group-by field, with drag between lanes. | Group-by, card title / subtitle / badge fields, open-screen drill. |\r\n| **Calendar** | A month event-calendar: each row with a date lands on its day, labelled and optionally color-coded. | Date field, title field, color field, open-screen drill. |\r\n| **Detail** | A full record \"detail page\": header, metric row, field sections, and related-record tabs - the 360 view a row action or drill-through opens. | Title / subtitle / status / metric fields, sections, related child entities. |\r\n| **Form** | A standalone create / edit form for the entity. | Presentation (drawer / modal / inline). |\r\n| **Filter panel** | A faceted sidebar that **filters the screen's grid** - enum / boolean facets pick a value, text facets search. | Title + which fields become facets. |\r\n| **Record panel** | Shows the row **selected in the grid** - a read-only field list, or an inline edit form. | Editable on / off, and (read-only) which fields to show. |\r\n| **Lookup** | Marks a relation field as a searchable picker in the edit form. | The relation field. |\r\n| **UI component** | A component from the SvGrid UI kit dropped from the toolbox - entity-agnostic, works on freestanding pages too. Grouped as Actions, Inputs, Display, Feedback, Layout, and Navigation, and covering headings and prose (heading, text, link, quote, code, keyboard key, list) as well as controls, pickers, and date/time inputs. | The component's own props (extracted from the component's own types, with its JSDoc as the tooltip), plus data bindings. |\r\n\r\nThe **filter** and **record** panels wire to the grid on the same screen: the\r\nfilter panel calls the grid controller's `setFilter`, and clicking a grid row\r\npublishes it to the record panel. So a common layout is a **filter panel + grid +\r\nrecord panel** three-up - list, narrow, inspect - all generated for you.\r\n\r\nThe grid, chart, pivot, and master-detail blocks are **height-resizable** (drag\r\nthe block's bottom edge, or set **Layout > Height**), and the chosen height flows\r\ninto the generated app. The filter and record panels size to their content.\r\n\r\n### Conditional formatting\r\n\r\nA grid's inspector has a **Conditional formatting** section: add no-code rules\r\nthat style a cell by its value - pick a field, a comparison (`=`, `<`, `>`,\r\n`contains`, `is empty`, ...), a value, and a **text color / fill / bold**. Rules\r\nrender **live in the canvas** and compile to the grid's built-in\r\n`conditionalFormats` rule engine in the generated app (e.g. negative `mrr` red,\r\n`status = overdue` filled). It's the same engine you'd use by hand - the designer\r\njust authors the rules.\r\n\r\n### Navigation & row actions\r\n\r\nThe grid's **Navigation & actions** section wires flow between screens:\r\n**drill-through** (row click opens another screen, filtered to the clicked value)\r\nand **row action buttons** (Edit / Delete / Open). A chart can drill too. See\r\n[Navigation & row actions](./navigation.md) for the full picture.\r\n\r\n## Data sources (per entity)\r\n\r\nEach entity binds to **its own backend** - the designer is not limited to one\r\ndata source per app. In the inspector (no block selected), the **Data source**\r\nsection binds the screen's entity to:\r\n\r\n- **In-memory** - seeded sample rows, runs with no backend (the default).\r\n- **Local database (no setup)** - a real, persistent Postgres ([PGlite](https://pglite.dev))\r\n running in the browser and saved to IndexedDB, so rows survive reloads with zero\r\n backend. Same SQL as production - swap to a hosted **SQL** source later without\r\n touching the schema. See [Local database](./local-database.md).\r\n The builder is a draggable, resizable, maximizable panel, and you can open it for\r\n any entity straight from the **Data model** dialog's **Configure** button.\r\n\r\n- **REST API** - a request builder: **method**, **base URL**, **path** (path\r\n params auto-derive from `{tokens}`), and **Query / Path / Header** tabs. **Send**\r\n runs it live and shows a **response table** plus the real rows in the canvas\r\n grid; **Import fields** rewrites the entity's schema to match the response keys.\r\n An **API format** picker (Manual / Offset + Limit / DummyJSON / json-server)\r\n wires a wire-format adapter so the grid does **real server-side paging and sort**;\r\n Manual keeps the rows-path / total-path mapping for a single fetched page.\r\n- **Supabase** - **List tables** reads your project's tables so you pick one, and\r\n **Import schema** pulls its real columns, primary key, foreign keys, and enums\r\n into the entity; **Preview** shows live rows (works in the online designer, since\r\n Supabase is HTTP). The project **URL + anon key are a shared connection** you set\r\n once for every Supabase entity. A **Live updates (Realtime)** toggle emits a live\r\n subscription. **Generate app** emits `createClient` in `connections.ts` reading\r\n `PUBLIC_SUPABASE_URL` / `PUBLIC_SUPABASE_ANON_KEY` from `.env` (the key is never\r\n inlined) and adds `@supabase/supabase-js`; access is protected by your RLS\r\n policies.\r\n- **SQL** - paste a connection string (the dialect is auto-detected) or fill the\r\n guided form, set the **Schema** (Postgres search path). In the **local** designer\r\n (`npx @svgrid/studio designer`) **Preview data** runs a real `SELECT` and shows\r\n your actual rows on the canvas, and a missing `pg` / `mysql2` / ... driver is a\r\n **one-click install**. **Generate app** emits a connected\r\n `src/routes/api/<table>/+server.ts` (the dialect's driver, reading\r\n `DATABASE_URL`) and points the grid at it; the driver dep is added for you. In\r\n the **online** designer, bind the entity and generate - the app connects for real\r\n at runtime. To scaffold from an existing DB via CLI: `npx @svgrid/studio add\r\n <table> --db <dialect> --url …`.\r\n\r\n**Generate app** then emits the matching adapter per entity in `src/lib/data.ts`\r\n(`createRestDataSource` / `createSqlDataSource` / `createSupabaseDataSource` /\r\n`createInMemoryDataSource`). SQL and Supabase entities read their connection from a\r\ngenerated `src/lib/connections.ts` (or the `+server.ts` route) - the SQL driver and\r\nthe Supabase client are wired for you; the only manual step is setting the\r\nconnection in `.env` (the bundle ships a `.env.example`). See\r\n[Databases](./databases.md) and [REST API](./rest-api.md).\r\n\r\n## Import a CSV / spreadsheet\r\n\r\nThe fastest way to start from **your own data**: **Import CSV** in the top bar\r\ntakes a `.csv` file and turns it into a running screen. The designer parses the\r\nfile (quoted fields, embedded newlines, and CRLF included), **infers a type per\r\ncolumn** from its values (number, boolean, date, or text - thousands separators\r\nand `yes/no/true/false` are understood), ensures a **primary key** (it reuses an\r\n`id` column or synthesizes one), and adds an entity with a full CRUD screen,\r\n**seeded with the real rows**. Unsafe headers (`First Name`, `E-mail`) become safe\r\nfield keys and the note tells you what was renamed.\r\n\r\nImported rows ship **in-memory** by default (no dependencies), so the app runs\r\nimmediately. Switch that entity's **Data source** to **Local database** to make\r\nthe same imported rows **persist** across reloads - the seed carries over. This is\r\nall client-side: `csvToEntity(name, text)` is a pure function exported from\r\n`@svgrid/enterprise`, so the same import works in the CLI and your own tools.\r\n\r\n> **Large files:** the imported rows are stored as the entity's seed, so they are\r\n> embedded in `studio.config.json` when you **Save** the design. That is fine for\r\n> reference data and samples; for a large dataset, import a representative sample\r\n> and point the entity at a **database** (Local database / SQL) for the full data.\r\n\r\n## Pages and layout\r\n\r\n- **Pages** - each screen is a route. In the inspector's **Page** section, toggle\r\n **Show in navigation**, set a **nav label** and **nav order**, or start a page\r\n from the **Empty** template. Hidden pages stay routable but drop out of the nav.\r\n- **Render mode** - eligible screens (a single plain grid, or read-only block\r\n screens, on a memory / SQL source) can switch from the default client page to\r\n **SSR**: an idiomatic `+page.server.ts` with `load` + form actions. The rules\r\n are in [Code generation](./code-generation.md#render-mode-spa-or-ssr-per-screen).\r\n- **App layout** - the rail's **App layout** section themes the generated shell:\r\n **Sidebar** or **Top navigation**, a **brand** name, a **company logo**\r\n (uploaded - stored inline and shown in the nav in place of the brand text), a\r\n **footer**, and (for the sidebar) the **nav position** (left / right). This\r\n drives the generated `src/routes/+layout.svelte`. The generated shell is\r\n **responsive**: on phones the sidebar collapses to a hamburger drawer, the\r\n top-nav links scroll, and each screen's block grid stacks to one column.\r\n\r\n## Save, regenerate, round-trip\r\n\r\nThe `StudioProject` is the persisted design. **Save config** exports a\r\n`studio.config.json`; regenerate the app from it any time:\r\n\r\n```bash\r\nnpm create @svgrid/studio@latest my-app -- --project ./studio.config.json\r\n```\r\n\r\nOr programmatically:\r\n\r\n```ts\r\nimport { serializeProject, parseProject, emitStudioProject } from '@svgrid/enterprise'\r\n\r\nconst json = serializeProject(project) // save the design\r\nconst project2 = parseProject(json) // reopen it\r\nconst files = emitStudioProject(project2) // -> the app's source files\r\n```\r\n\r\n**The exported app carries its own design.** The downloaded zip includes a\r\n`studio.config.json` at its root. To keep editing the app visually after you've\r\nworked on it locally, open the designer and **Load** that file - entities,\r\nscreens, blocks, theme, RBAC, i18n, and now the logo all come back exactly as\r\ngenerated. Because the designer regenerates the files under `src/`, keep any\r\nhand-written code in **new** files/modules you import, so a re-generate never\r\noverwrites it (or use the CLI's `svgrid:managed` markers - see\r\n[Code generation](./code-generation.md)).\r\n\r\n## Generate the app\r\n\r\n**Generate app** emits `src/lib/schemas.ts`, `src/lib/data.ts` (the right adapter\r\nper entity, plus `src/lib/connections.ts` when any entity is SQL / Supabase-bound),\r\nand one `src/routes/<route>/+page.svelte` **per screen** that composes that\r\nscreen's blocks with their config - a grid (with your visible columns, in order) +\r\nedit modal, plus any charts / pivots / dashboard / KPI tiles, and filter / record\r\npanels wired to the grid, all bound to the data - and the\r\nnav layout (sidebar or top-nav) + home. The pages are self-contained (they use\r\n`@svgrid/grid` + `@svgrid/enterprise` directly), so the output runs as a standard\r\nSvelteKit app.\r\n\r\n```ts\r\nimport { emitStudioProject } from '@svgrid/enterprise'\r\nconst files = emitStudioProject(project) // [{ path, contents, description }, ...]\r\n```\r\n\r\n## See also\r\n\r\n- [Code behind (Code view)](./code-behind.md) - write TypeScript against a typed `ctx` (grid API, events, `ctx.grid.sortable = true`, lifecycle)\r\n- [Sample apps + bind your data](./samples.md) - start from a ready-made app, then point it at your database\r\n- [Launch the designer](./launch.md) - `npx @svgrid/studio designer` (auto-save + generate to a folder)\r\n- [Schema designer](./designer.md) - author a single entity\r\n- [Dashboards](./dashboards.md) · [Databases](./databases.md) - the blocks + data sources\r\n- [CLI](./cli.md) / [Drizzle](./drizzle.md) / [Prisma](./prisma.md) - import a schema to design from\r\n"
2718
2724
  },
2719
2725
  {
2720
2726
  "slug": "enterprise/studio/audit-log",
@@ -2804,7 +2810,7 @@ export const docs = [
2804
2810
  "slug": "enterprise/studio/edit-forms",
2805
2811
  "path": "docs/enterprise/studio/edit-forms.md",
2806
2812
  "title": "Edit forms & validation",
2807
- "markdown": "# Edit forms & validation\r\n\r\n`SvGridEditPanel` is the create / edit form for a row. It renders itself from an\r\n`EntitySchema`, validates input, and hands you a ready payload to save. It\r\npresents as a right-hand **drawer**, a centered **modal**, or **inline**, and\r\nfollows the grid's light / dark theme.\r\n\r\n![The create/edit modal with built-in validation - \"Email must be a valid email\".](/docs-media/studio-edit-modal.png)\r\n\r\n## Usage\r\n\r\n```svelte\r\n<script lang=\"ts\">\r\n import { SvGridEditPanel } from '@svgrid/enterprise'\r\n let editing = $state<Customer | null | undefined>(undefined) // undefined = closed, null = create\r\n\r\n async function save({ mode, id, values }) {\r\n if (mode === 'create') await controller.createRow(values)\r\n else if (id) await controller.updateRow(id, values)\r\n editing = undefined\r\n }\r\n</script>\r\n\r\n{#if editing !== undefined}\r\n <SvGridEditPanel\r\n {schema}\r\n row={editing}\r\n presentation=\"modal\"\r\n onSubmit={save}\r\n onCancel={() => (editing = undefined)}\r\n />\r\n{/if}\r\n```\r\n\r\n## Props\r\n\r\n| Prop | Type | Description |\r\n| --- | --- | --- |\r\n| `schema` | `EntitySchema<TData>` | Drives the fields, validation, and payload. |\r\n| `row` | `TData \\| null` | Row to edit; `null` to create. |\r\n| `presentation` | `'drawer'` \\| `'modal'` \\| `'inline'` | Default `'drawer'` (right slide-over). |\r\n| `title` | `string` | Heading override. |\r\n| `submitLabel` | `string` | Save-button label override. |\r\n| `onSubmit` | `(payload) => void \\| Promise` | Called with a validated `{ mode, id, values }`. Throw to surface an error. |\r\n| `onCancel` | `() => void` | Called on cancel / close (Esc, backdrop, or the X). |\r\n\r\n## Presentation\r\n\r\n- **`drawer`** (default) - slides in from the right, full height.\r\n- **`modal`** - centered popup with a blurred backdrop.\r\n- **`inline`** - renders in the page flow (used by the designer preview).\r\n\r\nDrawer and modal animate in / out, close on **Esc** or backdrop click, and trap\r\nto a dialog role.\r\n\r\n## Validation\r\n\r\n**When it speaks up.** A field is checked when the user leaves it, not while they\r\nare still typing, so a form never scolds you for a value you have not finished\r\nentering. Once a field is showing an error it re-checks on every keystroke, so a\r\ncorrection clears the message straight away instead of making you submit again to\r\nfind out. A failed submit marks every field as visited, focuses the first one that\r\nneeds fixing, and lists them all in a summary at the top of the form that jumps to\r\na field when clicked.\r\n\r\nEach control carries `aria-invalid`, and its message (or its hint, when there is\r\nno error) is wired up with `aria-describedby`, so a screen reader announces the\r\nproblem with the field rather than leaving it to be discovered.\r\n\r\n**Closing a form with unsaved edits asks first.** Cancel, Escape, or a click on\r\nthe backdrop shows *Discard your changes?* in the footer, with **Keep editing**\r\nand **Discard**; a second Escape confirms. An untouched form closes immediately.\r\n\r\nThe save is blocked while anything fails. Three layers, in order:\r\n\r\n1. **Required** - non-empty for `required` fields.\r\n2. **Built-in constraints** - number validity + `min` / `max`,\r\n `minLength` / `maxLength`, `format: 'email' | 'url'`, and `pattern` (see\r\n [The EntitySchema](./schema.md#built-in-validation)).\r\n3. **Standard Schema** - any Zod / Valibot / ArkType validator on `field.validate`.\r\n\r\n```ts\r\n{ field: 'email', type: 'text', required: true, format: 'email' }\r\n{ field: 'mrr', type: 'number', min: 0 }\r\n{ field: 'name', type: 'text', minLength: 2, maxLength: 60 }\r\n```\r\n\r\nNo external library is required for the built-in rules - add a Standard Schema\r\nvalidator only when you need custom logic.\r\n\r\n### No-code rules\r\n\r\n`EntitySchema.validations` states cross-field rules as data, so the same rule\r\nruns in the form and in a generated app's server route:\r\n\r\n```ts\r\nvalidations: [\r\n { field: 'endsAt', op: 'gte', compareTo: 'startsAt', message: 'End must be after start' },\r\n { field: 'code', op: 'minLen', value: 4, message: 'Code needs 4+ characters' },\r\n]\r\n```\r\n\r\nOperators: `eq`, `ne`, `lt`, `lte`, `gt`, `gte`, `required`, `minLen`, `maxLen`.\r\nUse `compareTo` to compare against another field instead of a fixed `value`.\r\n\r\n## Laying the form out\r\n\r\n`EntitySchema.form` says how the form is arranged. It lives on the schema, not on\r\nthe component, so a form you have *built* travels with the entity: it round-trips\r\nthrough `studio.config.json`, generates into an app, and draws the same in the\r\nedit panel and in a server-rendered form.\r\n\r\n```ts\r\nconst customers: EntitySchema = {\r\n name: 'customers',\r\n fields: [/* ... */],\r\n form: {\r\n columns: 2,\r\n sections: [\r\n { title: 'Contact', description: 'How we reach them.', fields: ['name', 'email', 'phone'] },\r\n { title: 'Billing', columns: 1, fields: ['plan', 'vatNumber'] },\r\n // A whole section can be conditional, the same way a field is.\r\n { title: 'Cancellation', fields: ['reason', 'notes'],\r\n visibleWhen: { kind: 'cmp', column: 'status', op: 'equals', value: 'cancelled' } },\r\n ],\r\n },\r\n}\r\n```\r\n\r\n- `fields` gives both the grouping and the order.\r\n- A field in no section still renders, in a trailing untitled group. A form never\r\n silently drops one.\r\n- A section whose fields are all hidden disappears with them, heading included.\r\n- `columns` on a section overrides the form's for that group alone; a field with\r\n `input.span = 2` spans the full width.\r\n\r\n`SvGridEditPanel`'s `columns` and `sections` props still win when passed, for a\r\none-off arrangement of an otherwise shared schema.\r\n\r\n**Server-rendered screens follow the same layout.** A screen with\r\n`renderMode: 'ssr'` renders its sections, descriptions and column counts, marks\r\n`span: 2` fields full-width, shows each field's hint, and states the field's own\r\nconstraints (`minlength`, `maxlength`, `min`, `max`, `pattern`, and an `email` /\r\n`url` input type) as native HTML attributes so the browser catches an obvious\r\nmistake before a round-trip. The action re-checks all of it server-side\r\nregardless, so the attributes save a trip but never decide anything.\r\n\r\n## Fields that react to the answers\r\n\r\nA field can appear, lock, or become required based on what the user has already\r\nentered - the form asks for a reason only when it needs one, and never asks\r\ntwice.\r\n\r\nEach condition is a `PredicateExpr`: **data, not a function**, so it survives a\r\nround-trip through `studio.config.json`, generates into an app unchanged, and can\r\nbe edited in a UI.\r\n\r\n```ts\r\n{\r\n field: 'otherReason',\r\n type: 'text',\r\n when: {\r\n // Only asked for - and only demanded - when the reason is \"other\".\r\n visible: { kind: 'cmp', column: 'reason', op: 'equals', value: 'other' },\r\n required: { kind: 'cmp', column: 'reason', op: 'equals', value: 'other' },\r\n },\r\n}\r\n{\r\n field: 'approver',\r\n type: 'text',\r\n // Locked until the order is big enough to need sign-off.\r\n when: { disabled: { kind: 'cmp', column: 'total', op: 'lessThan', value: 1000 } },\r\n}\r\n```\r\n\r\nThree rules make this safe to rely on:\r\n\r\n- A field hidden by `visible` is **skipped by validation** and **left out of the\r\n submitted payload**. It can never block a save the user cannot fix, and a value\r\n they can no longer see is never written back. The generated SSR route applies\r\n the same rule to a posted form, so both paths save the same fields.\r\n- `required` **replaces** the static `required` flag rather than adding to it, so\r\n a rule can make a normally required field optional as well as the reverse.\r\n- A malformed condition falls back to showing and enabling the field. A broken\r\n rule degrades to an ordinary form instead of hiding data.\r\n\r\nConditions are the same expressions the alert rules use, so `parsePredicate` and\r\nthe `SvExpressionEditor` component both work on them:\r\n\r\n```ts\r\nimport { parsePredicate } from '@svgrid/enterprise'\r\nwhen: { visible: parsePredicate('reason = \"other\"') }\r\n```\r\n\r\nA section whose fields are all hidden disappears along with them, heading\r\nincluded.\r\n\r\nThree [sample apps](./samples.md) ship this, so you can open one and watch it\r\nwork: **CRM** asks a lost deal what it lost to, **Support desk** demands a\r\nresolution before a ticket can be resolved or closed (and only then asks for a\r\nCSAT rating), and **Insurance Claims** requires a justification to deny a claim\r\nand freezes the amount and deductible once it has been paid.\r\n\r\n## Controls\r\n\r\nThe form renders each field with a control from the **editor suite**, not a bare\r\nnative input: numbers use `SvNumberInput` (spinners, min/max/step), booleans a\r\n`SvSwitchButton`, colors `SvColorInput`, passwords `SvPasswordInput` (strength\r\nmeter), ratings a `SvSlider`, dates and date-times a `SvDateTimePicker` (masked\r\ninput + calendar dropdown), enums a themed **dropdown** (`SvGridDropdown`), and\r\nJSON a textarea. The default follows the field type; override per field with\r\n`input.editorType`.\r\n\r\nBeyond the grid's cell editors, the form also offers a few **form-only** controls\r\nvia `input.editorType`: `phone` (`SvPhoneInput`), `country` (`SvCountryInput`),\r\n`mask` (`SvMaskedInput`, with an `input.mask` pattern like `'(999) 000-0000'`),\r\nand `slider`. In the [visual designer](./app-designer.md) each field has a\r\n**Control** picker (scoped to what fits its type) plus a **Wide** toggle\r\n(`input.span = 2`), so you pick the editor without touching code.\r\n\r\n```ts\r\n{ field: 'mrr', type: 'number', input: { editorType: 'slider' } }\r\n{ field: 'brand', type: 'text', input: { editorType: 'color' } }\r\n{ field: 'phone', type: 'text', input: { editorType: 'phone' } }\r\n{ field: 'ssn', type: 'text', input: { editorType: 'mask', mask: '999-99-9999' } }\r\n```\r\n\r\nForm-only editors degrade to a safe in-cell editor when the same field shows in a\r\ngrid (`slider` → number, `phone`/`country`/`mask` → text), so columns stay valid.\r\n\r\n**File / image upload.** Give a field an `upload` config and it renders\r\n`SvFileInput` (a picker with an image preview). With no handler it stores an\r\ninline data URL (no backend needed); pass an `uploads` handler that pushes to\r\nstorage and returns the URL:\r\n\r\n```svelte\r\n{ field: 'avatar', type: 'text', upload: { image: true, accept: 'image/*' } }\r\n\r\n<SvGridEditPanel {schema} row={editing}\r\n uploads={{ avatar: async (file) => await putToStorage(file) }} onSubmit={save} />\r\n```\r\n\r\n**Cascading (dependent) fields.** Compute a field's options from the current\r\nvalues with `dependentOptions` - the field clears when it stops being valid\r\n(e.g. City depends on Country):\r\n\r\n```svelte\r\n<SvGridEditPanel {schema} row={editing}\r\n dependentOptions={{ city: (values) => citiesByCountry[values.country] ?? [] }}\r\n onSubmit={save} />\r\n```\r\n\r\nSee the [rich fields demo](https://svgrid.com/demos/198-studio-form-fields/).\r\n\r\nEnum and `relation` fields use a custom dropdown whose panel **portals to\r\n`document.body`** (position: fixed), so it opens *above* a drawer or modal and\r\nnever grows the form (no scrollbar) - unlike a native `<select>` or an in-flow\r\npopup.\r\n\r\n## The modal is a movable window\r\n\r\nWith `presentation=\"modal\"`, the panel is a floating window: **drag** it by the\r\nheader, **resize** it from its edges (the content resizes with it), **maximize /\r\nrestore**, and **pin** it to any edge (left / top / bottom / right) - handy for\r\nkeeping the form docked beside the grid while you work. Pin again to unpin.\r\n\r\nSet **`persistKey`** to remember the window layout (pin / size / maximized) in\r\n`localStorage`, so it reopens where the user left it:\r\n\r\n```svelte\r\n<SvGridEditPanel {schema} row={editing} presentation=\"modal\" persistKey=\"customers\" ... />\r\n```\r\n\r\nOpen the editor on **double-click** (`onRowDoubleClick`), not single-click, so a\r\nclick can still select or interact with a row without popping the form.\r\n\r\n## See also\r\n\r\n- [The EntitySchema](./schema.md) - fields + validation constraints\r\n- [Master-detail](./master-detail.md) · [Data binding](./data-binding.md)\r\n"
2813
+ "markdown": "# Edit forms & validation\r\n\r\n`SvGridEditPanel` is the create / edit form for a row. It renders itself from an\r\n`EntitySchema`, validates input, and hands you a ready payload to save. It\r\npresents as a right-hand **drawer**, a centered **modal**, or **inline**, and\r\nfollows the grid's light / dark theme.\r\n\r\n![The create/edit modal with built-in validation - \"Email must be a valid email\".](/docs-media/studio-edit-modal.png)\r\n\r\n## Usage\r\n\r\n```svelte\r\n<script lang=\"ts\">\r\n import { SvGridEditPanel } from '@svgrid/enterprise'\r\n let editing = $state<Customer | null | undefined>(undefined) // undefined = closed, null = create\r\n\r\n async function save({ mode, id, values }) {\r\n if (mode === 'create') await controller.createRow(values)\r\n else if (id) await controller.updateRow(id, values)\r\n editing = undefined\r\n }\r\n</script>\r\n\r\n{#if editing !== undefined}\r\n <SvGridEditPanel\r\n {schema}\r\n row={editing}\r\n presentation=\"modal\"\r\n onSubmit={save}\r\n onCancel={() => (editing = undefined)}\r\n />\r\n{/if}\r\n```\r\n\r\n## Props\r\n\r\n| Prop | Type | Description |\r\n| --- | --- | --- |\r\n| `schema` | `EntitySchema<TData>` | Drives the fields, validation, and payload. |\r\n| `row` | `TData \\| null` | Row to edit; `null` to create. |\r\n| `presentation` | `'drawer'` \\| `'modal'` \\| `'inline'` | Default `'drawer'` (right slide-over). |\r\n| `title` | `string` | Heading override. |\r\n| `submitLabel` | `string` | Save-button label override. |\r\n| `onSubmit` | `(payload) => void \\| Promise` | Called with a validated `{ mode, id, values }`. Throw to surface an error. |\r\n| `onCancel` | `() => void` | Called on cancel / close (Esc, backdrop, or the X). |\r\n\r\n## Presentation\r\n\r\n- **`drawer`** (default) - slides in from the right, full height.\r\n- **`modal`** - centered popup with a blurred backdrop.\r\n- **`inline`** - renders in the page flow (used by the designer preview).\r\n\r\nDrawer and modal animate in / out, close on **Esc** or backdrop click, and trap\r\nto a dialog role.\r\n\r\n## Validation\r\n\r\n**When it speaks up.** A field is checked when the user leaves it, not while they\r\nare still typing, so a form never scolds you for a value you have not finished\r\nentering. Once a field is showing an error it re-checks on every keystroke, so a\r\ncorrection clears the message straight away instead of making you submit again to\r\nfind out. A failed submit marks every field as visited, focuses the first one that\r\nneeds fixing, and lists them all in a summary at the top of the form that jumps to\r\na field when clicked.\r\n\r\nEach control carries `aria-invalid`, and its message (or its hint, when there is\r\nno error) is wired up with `aria-describedby`, so a screen reader announces the\r\nproblem with the field rather than leaving it to be discovered.\r\n\r\n**Closing a form with unsaved edits asks first.** Cancel, Escape, or a click on\r\nthe backdrop shows *Discard your changes?* in the footer, with **Keep editing**\r\nand **Discard**; a second Escape confirms. An untouched form closes immediately.\r\n\r\nThe save is blocked while anything fails. Three layers, in order:\r\n\r\n1. **Required** - non-empty for `required` fields.\r\n2. **Built-in constraints** - number validity + `min` / `max`,\r\n `minLength` / `maxLength`, `format: 'email' | 'url'`, and `pattern` (see\r\n [The EntitySchema](./schema.md#built-in-validation)).\r\n3. **Standard Schema** - any Zod / Valibot / ArkType validator on `field.validate`.\r\n\r\n```ts\r\n{ field: 'email', type: 'text', required: true, format: 'email' }\r\n{ field: 'mrr', type: 'number', min: 0 }\r\n{ field: 'name', type: 'text', minLength: 2, maxLength: 60 }\r\n```\r\n\r\nNo external library is required for the built-in rules - add a Standard Schema\r\nvalidator only when you need custom logic.\r\n\r\n### No-code rules\r\n\r\n`EntitySchema.validations` states cross-field rules as data, so the same rule\r\nruns in the form and in a generated app's server route:\r\n\r\n```ts\r\nvalidations: [\r\n { field: 'endsAt', op: 'gte', compareTo: 'startsAt', message: 'End must be after start' },\r\n { field: 'code', op: 'minLen', value: 4, message: 'Code needs 4+ characters' },\r\n]\r\n```\r\n\r\nOperators: `eq`, `ne`, `lt`, `lte`, `gt`, `gte`, `required`, `minLen`, `maxLen`.\r\nUse `compareTo` to compare against another field instead of a fixed `value`.\r\n\r\n## Where a form comes from\r\n\r\nThree places, and which one you want depends on the record:\r\n\r\n| You want to | Use |\r\n| --- | --- |\r\n| Edit an existing row from a list | A **grid** with **Editing mode: Popup form**. Double-click a row. |\r\n| Edit whichever row is selected on the screen | A **record panel** with editing on - inline, drawer, or modal. |\r\n| Create a new record, with no grid behind it | A **Form** block, dragged from the Components rail. |\r\n\r\nAll three render the same `SvGridEditPanel` against the same `EntitySchema.form`,\r\nso a form designed once looks the same in every one of them.\r\n\r\nThe **Form** block is create-only by design. It is blank on load, submits, and\r\ncreates a row; after saving it either blanks itself for the next entry (the\r\ndefault, with a \"Saved\" confirmation) or opens another screen. Give it a heading\r\nand a submit label in the inspector - \"Report a problem\" / \"Send it\" reads better\r\nthan \"New Ticket\" / \"Create\" on a page somebody was sent to.\r\n\r\nAn inline form **fills whatever it is placed in**, so a form block is as wide as\r\nits block. Set the block's **Width** to *Narrow* or *Wide* when a full-width form\r\nis more than a reader wants to cross; in code that is `SvGridEditPanel`'s\r\n`formSize`.\r\n\r\n## Building one without writing it\r\n\r\nEverything below this line is authorable in the [visual designer](./app-designer.md).\r\nSelect the entity and open **Form layout -> Open form builder** (the grid block's\r\n**Form** tab has the same button). It edits the entity, so what you build there is\r\nwhat a generated app and a server-rendered screen render.\r\n\r\nOnce it is open the entity name in the title is a picker, so you can build any\r\nentity's form without going back out to find a page that uses it.\r\n\r\n**The canvas is the form.** It draws real labels, real control shapes, and the\r\nreal column grid, so arranging the form and looking at it are the same act - a\r\nfield you span shows as spanned, a `textarea` is tall, a switch is small.\r\n\r\n- **Drag a field** anywhere on the canvas, or select one and use ↑ / ↓ (announced\r\n for screen readers). Fields no section claims sit in a trailing **Not in a\r\n section** group, because that is exactly where the form puts them.\r\n- **Sections** take a title, a line of guidance, and their own column count.\r\n Hover one for its tools: columns, **Rule** (show the whole section only when a\r\n condition holds), **Fold**, reorder, and remove - which removes the heading\r\n only, never the fields under it. On an unarranged form, **Group these for me**\r\n proposes a grouping from the field names; it only appears when there is a real\r\n grouping to make.\r\n- **Click a field** and the right pane fills, in two tabs. **Field**: label,\r\n control, an `enum`'s choices, default value, placeholder, help text, span the\r\n full row, always required, and *Remove from this form*. Controls that need more\r\n say so - a mask gets its pattern, a number or slider gets its range, step,\r\n decimals and affixes - and nothing else is shown, so picking a control never\r\n leaves you with nowhere to configure it. **Rules**: the *Shown when* /\r\n *Required when* / *Locked when* conditions plus the cross-field checks that\r\n blame this field - the same `validations` rules, edited here because they are\r\n form logic. The tab carries a count, and so does the field on the canvas.\r\n- **Ctrl-click** adds a field to the selection and **Shift-click** takes a range\r\n within one lane; dragging any of them moves the whole set together, as does\r\n ↑ / ↓. With more than one selected the right pane offers what is genuinely\r\n bulk - move them all to a section, or remove them all - because the Field and\r\n Rules editors are single-field by nature. Escape drops back to one.\r\n- **+ Add field** builds a form from nothing without leaving for the schema\r\n inspector. Name and type up front; a name already on the entity is refused.\r\n Each section has its own **+ Field** that drops straight into it.\r\n- **Remove from this form is reversible.** Removed fields collect in a **Hidden\r\n from this form** tray under the canvas, and *Restore* puts one back where it\r\n was - removing never edited the section, only the field's visibility.\r\n- **Show rules** (on whenever the form has any) plays the conditions against the\r\n sample record right on the canvas: a field a rule hides goes dim with a\r\n *hidden* badge, a locked one gets a padlock, and a rule-required one gets the\r\n asterisk. Dimmed fields stay selectable and draggable - you are arranging the\r\n design, not the record. **Existing** / **New** switches which record you are\r\n simulating.\r\n- **Width** draws the form at the size it will really have. The window itself\r\n drags by its header, resizes from its corner, and maximizes on a double-click.\r\n\r\n### Folding a long form\r\n\r\n**Fold** on a section cycles through three states: not foldable, foldable, and\r\nfoldable-and-starts-folded. A foldable section's heading becomes a disclosure\r\nbutton carrying a count, so a long form opens at a readable length instead of a\r\nwall of inputs.\r\n\r\nFolding is a **display state, not a condition**. The fields are still filled in\r\nand still validated - use `visibleWhen` when you actually want them gone. If a\r\nfolded section holds an error the form opens it, so a rejected submit can never\r\npoint at something the user cannot see.\r\n\r\nServer-rendered screens get the same thing as a native `<details>`, so it folds\r\nwith JavaScript off, and a section that starts folded opens itself when the\r\nserver sends back an error for one of its fields.\r\n\r\n```ts\r\nsections: [\r\n { title: 'Contact', fields: ['name', 'email'] },\r\n { title: 'Billing', fields: ['vatNumber', 'poNumber'], collapsible: true, collapsed: true },\r\n]\r\n```\r\n\r\n### Asking one step at a time\r\n\r\n`form.steps` turns the sections into a wizard - **Ask one step at a time** in the\r\nbuilder. Each section is a step, so the sections *are* the design; there is no\r\nsecond list to keep in sync.\r\n\r\n```ts\r\nform: {\r\n steps: true,\r\n sections: [\r\n { title: 'Who', fields: ['name', 'email'] },\r\n { title: 'Company', fields: ['company', 'role'] },\r\n { title: 'Billing', fields: ['vatNumber'] },\r\n ],\r\n}\r\n```\r\n\r\nFour things make it behave:\r\n\r\n- **Next validates only the step you are on**, so a long form fails early and\r\n locally instead of dumping every error at the end. Back never validates -\r\n going backwards is always allowed.\r\n- A section hidden by `visibleWhen` is **skipped**, so the step count follows the\r\n answers rather than showing an empty step.\r\n- Fields in no section **join the last step** rather than becoming an untitled\r\n one of their own. Every step should be deliberate.\r\n- A submit that fails on an earlier step **jumps back to it**, so the focus never\r\n lands off-screen.\r\n\r\nOne section is a page, not a one-step wizard, and `collapsible` is ignored while\r\nstepping - a step is already one group at a time.\r\n\r\n**Server-rendered screens render the steps as ordinary sections.** Stepping\r\nthrough a `<form>` without JavaScript would mean a round-trip per step and\r\nsomewhere to hold the half-finished record. The server validates everything\r\neither way.\r\n\r\n**Try it** swaps the canvas for the live edit panel, so you can type into the form\r\nand watch a condition fire. Toggle **Existing** / **New**: the values differ, so\r\nthe conditions do too.\r\n\r\nNote that a grid block can still override the arrangement for one screen. When it\r\ndoes, the builder says so and offers to drop the override.\r\n\r\n### From an agent\r\n\r\nTwo MCP tools drive the same model, so an agent can build the form too:\r\n\r\n| Tool | What it does |\r\n| --- | --- |\r\n| `studio_set_form_layout` | Set the column count and the sections. Pass `\"suggest\": true` instead of `sections` to have them proposed from the field names. The reply reports what actually landed, including anything that fell through to the trailing group. |\r\n| `studio_set_field_conditions` | Set a field's `visible` / `required` / `disabled` conditions. A condition you do not name is left alone; pass `null` to clear one. |\r\n\r\nSee the [MCP server](./ai-generation.md) for the rest of the `studio_*` tools.\r\n\r\n## Laying the form out\r\n\r\n`EntitySchema.form` says how the form is arranged. It lives on the schema, not on\r\nthe component, so a form you have *built* travels with the entity: it round-trips\r\nthrough `studio.config.json`, generates into an app, and draws the same in the\r\nedit panel and in a server-rendered form.\r\n\r\n```ts\r\nconst customers: EntitySchema = {\r\n name: 'customers',\r\n fields: [/* ... */],\r\n form: {\r\n columns: 2,\r\n sections: [\r\n { title: 'Contact', description: 'How we reach them.', fields: ['name', 'email', 'phone'] },\r\n { title: 'Billing', columns: 1, fields: ['plan', 'vatNumber'] },\r\n // A whole section can be conditional, the same way a field is.\r\n { title: 'Cancellation', fields: ['reason', 'notes'],\r\n visibleWhen: { kind: 'cmp', column: 'status', op: 'equals', value: 'cancelled' } },\r\n ],\r\n },\r\n}\r\n```\r\n\r\n- `fields` gives both the grouping and the order.\r\n- A field in no section still renders, in a trailing untitled group. A form never\r\n silently drops one.\r\n- A section whose fields are all hidden disappears with them, heading included.\r\n- `columns` on a section overrides the form's for that group alone; a field with\r\n `input.span = 2` spans the full width.\r\n\r\n`SvGridEditPanel`'s `columns` and `sections` props still win when passed, for a\r\none-off arrangement of an otherwise shared schema.\r\n\r\n**Server-rendered screens follow the same layout.** A screen with\r\n`renderMode: 'ssr'` renders its sections, descriptions and column counts, marks\r\n`span: 2` fields full-width, shows each field's hint, and states the field's own\r\nconstraints (`minlength`, `maxlength`, `min`, `max`, `pattern`, and an `email` /\r\n`url` input type) as native HTML attributes so the browser catches an obvious\r\nmistake before a round-trip. The action re-checks all of it server-side\r\nregardless, so the attributes save a trip but never decide anything.\r\n\r\n## Fields that react to the answers\r\n\r\nA field can appear, lock, or become required based on what the user has already\r\nentered - the form asks for a reason only when it needs one, and never asks\r\ntwice.\r\n\r\nEach condition is a `PredicateExpr`: **data, not a function**, so it survives a\r\nround-trip through `studio.config.json`, generates into an app unchanged, and can\r\nbe edited in a UI.\r\n\r\n```ts\r\n{\r\n field: 'otherReason',\r\n type: 'text',\r\n when: {\r\n // Only asked for - and only demanded - when the reason is \"other\".\r\n visible: { kind: 'cmp', column: 'reason', op: 'equals', value: 'other' },\r\n required: { kind: 'cmp', column: 'reason', op: 'equals', value: 'other' },\r\n },\r\n}\r\n{\r\n field: 'approver',\r\n type: 'text',\r\n // Locked until the order is big enough to need sign-off.\r\n when: { disabled: { kind: 'cmp', column: 'total', op: 'lessThan', value: 1000 } },\r\n}\r\n```\r\n\r\nThree rules make this safe to rely on:\r\n\r\n- A field hidden by `visible` is **skipped by validation** and **left out of the\r\n submitted payload**. It can never block a save the user cannot fix, and a value\r\n they can no longer see is never written back. The generated SSR route applies\r\n the same rule to a posted form, so both paths save the same fields.\r\n- `required` **replaces** the static `required` flag rather than adding to it, so\r\n a rule can make a normally required field optional as well as the reverse.\r\n- A malformed condition falls back to showing and enabling the field. A broken\r\n rule degrades to an ordinary form instead of hiding data.\r\n\r\nConditions are the same expressions the alert rules use, so `parsePredicate` and\r\nthe `SvExpressionEditor` component both work on them:\r\n\r\n```ts\r\nimport { parsePredicate } from '@svgrid/enterprise'\r\nwhen: { visible: parsePredicate('reason = \"other\"') }\r\n```\r\n\r\nA section whose fields are all hidden disappears along with them, heading\r\nincluded.\r\n\r\nThree [sample apps](./samples.md) ship this, so you can open one and watch it\r\nwork: **CRM** asks a lost deal what it lost to, **Support desk** demands a\r\nresolution before a ticket can be resolved or closed (and only then asks for a\r\nCSAT rating), and **Insurance Claims** requires a justification to deny a claim\r\nand freezes the amount and deductible once it has been paid.\r\n\r\n## Controls\r\n\r\nThe form renders each field with a control from the **editor suite**, not a bare\r\nnative input: numbers use `SvNumberInput` (spinners, min/max/step), booleans a\r\n`SvSwitchButton`, colors `SvColorInput`, passwords `SvPasswordInput` (strength\r\nmeter), ratings a `SvSlider`, dates and date-times a `SvDateTimePicker` (masked\r\ninput + calendar dropdown), enums a themed **dropdown** (`SvGridDropdown`), and\r\nJSON a textarea. The default follows the field type; override per field with\r\n`input.editorType`.\r\n\r\nBeyond the grid's cell editors, the form also offers a few **form-only** controls\r\nvia `input.editorType`: `phone` (`SvPhoneInput`), `country` (`SvCountryInput`),\r\n`mask` (`SvMaskedInput`, with an `input.mask` pattern like `'(999) 000-0000'`),\r\nand `slider`. In the [visual designer](./app-designer.md) each field has a\r\n**Control** picker (scoped to what fits its type) plus a **Wide** toggle\r\n(`input.span = 2`), so you pick the editor without touching code.\r\n\r\n```ts\r\n{ field: 'mrr', type: 'number', input: { editorType: 'slider' } }\r\n{ field: 'brand', type: 'text', input: { editorType: 'color' } }\r\n{ field: 'phone', type: 'text', input: { editorType: 'phone' } }\r\n{ field: 'ssn', type: 'text', input: { editorType: 'mask', mask: '999-99-9999' } }\r\n```\r\n\r\nForm-only editors degrade to a safe in-cell editor when the same field shows in a\r\ngrid (`slider` → number, `phone`/`country`/`mask` → text), so columns stay valid.\r\n\r\n**File / image upload.** Give a field an `upload` config and it renders\r\n`SvFileInput` (a picker with an image preview). With no handler it stores an\r\ninline data URL (no backend needed); pass an `uploads` handler that pushes to\r\nstorage and returns the URL:\r\n\r\n```svelte\r\n{ field: 'avatar', type: 'text', upload: { image: true, accept: 'image/*' } }\r\n\r\n<SvGridEditPanel {schema} row={editing}\r\n uploads={{ avatar: async (file) => await putToStorage(file) }} onSubmit={save} />\r\n```\r\n\r\n**Cascading (dependent) fields.** Compute a field's options from the current\r\nvalues with `dependentOptions` - the field clears when it stops being valid\r\n(e.g. City depends on Country):\r\n\r\n```svelte\r\n<SvGridEditPanel {schema} row={editing}\r\n dependentOptions={{ city: (values) => citiesByCountry[values.country] ?? [] }}\r\n onSubmit={save} />\r\n```\r\n\r\nSee the [rich fields demo](https://svgrid.com/demos/198-studio-form-fields/).\r\n\r\nEnum and `relation` fields use a custom dropdown whose panel **portals to\r\n`document.body`** (position: fixed), so it opens *above* a drawer or modal and\r\nnever grows the form (no scrollbar) - unlike a native `<select>` or an in-flow\r\npopup.\r\n\r\n## The modal is a movable window\r\n\r\nWith `presentation=\"modal\"`, the panel is a floating window: **drag** it by the\r\nheader, **resize** it from its edges (the content resizes with it), **maximize /\r\nrestore**, and **pin** it to any edge (left / top / bottom / right) - handy for\r\nkeeping the form docked beside the grid while you work. Pin again to unpin.\r\n\r\nSet **`persistKey`** to remember the window layout (pin / size / maximized) in\r\n`localStorage`, so it reopens where the user left it:\r\n\r\n```svelte\r\n<SvGridEditPanel {schema} row={editing} presentation=\"modal\" persistKey=\"customers\" ... />\r\n```\r\n\r\nOpen the editor on **double-click** (`onRowDoubleClick`), not single-click, so a\r\nclick can still select or interact with a row without popping the form.\r\n\r\n## See also\r\n\r\n- [The EntitySchema](./schema.md) - fields + validation constraints\r\n- [Master-detail](./master-detail.md) · [Data binding](./data-binding.md)\r\n"
2808
2814
  },
2809
2815
  {
2810
2816
  "slug": "enterprise/studio/getting-started",
@@ -3038,7 +3044,7 @@ export const docs = [
3038
3044
  "slug": "help/ai-toolkit",
3039
3045
  "path": "docs/help/ai-toolkit.md",
3040
3046
  "title": "AI Toolkit",
3041
- "markdown": "# AI Toolkit\n\nEverything SvGrid ships for building with language models, in one place.\nThe toolkit spans two axes: **AI inside your running app** (helpers your\nusers invoke - natural-language filter, smart fill, summarise, classify)\nand **AI inside your editor** (the MCP server + grounding files that make\nClaude, Cursor, and friends write correct SvGrid code).\n\nNothing here bundles a model. SvGrid is **model-agnostic and\nbring-your-own-key**: you register one adapter and keep full control of\nmodel choice, routing, and what data leaves the browser.\n\n<div data-docs-demo=\"51-ai-assistant\" data-height=\"560\"></div>\n\n## The two surfaces\n\n| | AI in your app (runtime) | AI in your editor (build time) |\n| --- | --- | --- |\n| **Who invokes it** | your end users | you and your coding agent |\n| **What it does** | filter / fill / summarise / classify / export the live grid | scaffold columns, generate CRUD screens, answer API questions |\n| **Package** | `@svgrid/enterprise` (`api.ai.*`) | `@sv-grid/mcp-server`, `@svgrid/mcp`, grounding files |\n| **Needs a model key** | yes - the one you register | no - your agent brings its own |\n| **Deep dive** | [AI assistant](./ai.md) | [MCP server](./mcp-server.md) · [LLM grounding](./llm-grounding.md) |\n\nMost teams use both: the MCP server to write the grid, the in-grid\nhelpers to power features inside it.\n\n## How it works\n\nThe grid never calls a model directly. Every runtime AI call routes\nthrough a single async **provider** you register once at app boot:\n\n```ts\nimport { setAIProvider, type AIProvider } from '@svgrid/grid'\n\nconst provider: AIProvider = async ({ prompt, responseFormat, signal, task }) => {\n const r = await fetch('/api/ai', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ prompt, responseFormat, task }),\n signal,\n })\n if (!r.ok) throw new Error(`AI provider returned ${r.status}`)\n return r.text()\n}\n\nsetAIProvider(provider)\n```\n\nThree design choices keep bad model output from becoming a silent wrong answer:\n\n- **Structured JSON, validated.** Helpers request `responseFormat: 'json'`\n and the grid `JSON.parse`s the reply. It strips a single markdown code\n fence automatically, so a model that wraps output in ` ```json ... ``` `\n still parses. On malformed output you get a typed error, not a silent\n wrong result.\n- **The prompt is grounded in your columns.** Before each call the grid\n embeds the live column schema (names, types, sampled values) into the\n prompt, so the model picks real field names instead of inventing them.\n- **A hallucination guard on the way back.** If a model still returns a\n column that does not exist, the clause is dropped rather than passed to\n `setFilter` - you lose a clause, never crash the page.\n\nThe provider shape is deliberately tiny (one async call, `text` or `json`\nresponse) so the same adapter drives an OpenAI `chat.completions` call, an\nAnthropic `messages` call, a self-hosted endpoint, or a server-side proxy.\nNo model client is ever bundled into your grid.\n\n> **Just evaluating?** The package ships a deterministic `mockAIProvider`\n> that returns plausible canned shapes per task. Wire it in with\n> `setAIProvider(mockAIProvider)` and every helper works end to end with\n> no key. The demo above runs on it.\n\n## In-grid helpers\n\n`installEnterprise(api)` - the same call you use for export and print -\naugments your `SvGridApi` with an `ai` namespace. Six helpers, all\nmodel-agnostic:\n\n```ts\napi.ai.filter(query, opts?) // NL sentence -> filter + sort plan\napi.ai.smartFill(opts) // 1-2 examples -> proposed column values\napi.ai.summarize(opts) // row/selection/group/all -> text + bullets\napi.ai.classify(opts) // free-text cells -> a clean enum label\napi.ai.export(query, opts?) // NL sentence -> filter + group + format, then export\napi.ai.findAnomalies(opts?) // scan a slice -> outliers + severity\n```\n\n### Natural-language filter\n\nThe highest-leverage feature: replace a dozen per-column filter operators\nwith one search box.\n\n```ts\nconst plan = await api.ai.filter('accounts losing momentum in EMEA, by NPS')\n// {\n// filters: [\n// { field: 'region', operator: 'equals', value: 'EMEA' },\n// { field: 'nps', operator: 'lessThan', value: '30' },\n// ],\n// sort: [{ field: 'nps', desc: false }],\n// rationale: 'EMEA region, low NPS, sorted ascending.',\n// }\n```\n\nBy default it **returns the plan without applying it**, so you can show a\n\"here is what I would do, accept?\" preview and surface the `rationale`.\nPass `{ apply: true }` to commit straight to the grid.\n\n### Smart fill\n\nThe killer feature for spreadsheet-style entry: type one or two examples\nin a column, let the model propose the rest.\n\n```ts\nconst result = await api.ai.smartFill({\n field: 'tier',\n examples: [\n { input: { company: 'Northwind' }, output: 'enterprise' },\n { input: { company: 'Helios' }, output: 'growth' },\n ],\n})\n// result.predictions: [{ rowIndex, value, confidence }, ...]\n```\n\nYou choose what to do with the predictions - accept-all, accept-per-cell\nwith a confidence pill, or write them onto the row for review.\n\n### Summarise, classify, export, anomalies\n\n- **`summarize`** drops a slice (row / selection / group / all) into the\n model and returns a paragraph, bullets, and the fields the story leans\n on. Large slices are sampled uniformly to stay under a token budget.\n- **`classify`** buckets free-text cells into a known set of labels, and\n filters out any prediction not in your `classes` list so the output is a\n clean enum.\n- **`export`** turns \"export EU orders from Q2 as a grouped PDF by\n country\" into a `{ format, filters, sort, groupBy }` plan and hands it to\n the exporter - self-contained, so the download is correct regardless of\n the grid's current view.\n- **`findAnomalies`** scans a slice for outliers and inconsistent values,\n each tagged `low | medium | high`. Pairs naturally with export: find the\n odd rows, then export just those.\n\nFull API, response shapes, and the license gate are on the\n[AI assistant](./ai.md) page.\n\n## Build an agent that drives the grid\n\nThe imperative `SvGridApi` is a clean tool surface - each method becomes\none function a model can call. Three patterns, in order of how much agency\nyou hand over:\n\n1. **Read-only summary agent** - the model describes the current view\n (`api.getDisplayedRows()`), no tool calling.\n2. **Stateful UI agent** - the model calls `setFilter` / `setSort` /\n `setGroupBy` in response to natural language, bounded by a max-turns loop.\n3. **Autonomous workflow agent** - the grid is one node in a longer chain\n (import -> enrich -> human approval -> export), and the visible table is\n the state a human can audit between steps.\n\n```ts\n// Pattern 2, sketched: each SvGridApi method is one tool the model can call.\nswitch (call.function.name) {\n case 'setFilter': api.setFilter(args.columnId, args); break\n case 'setSort': api.setSort(args.columnId, args.direction); break\n case 'setGroupBy': api.setGroupBy(args.columnIds); break\n case 'clearAllFilters': api.clearAllFilters(); break\n}\n```\n\nFull worked code, the sandboxing rules (whitelist tools, validate every\nargument against the shipped JSON Schemas, bound the loop), and the common\nfailure modes are on the [Agents](./agents.md) page.\n\n## MCP server: let your coding agent write the grid\n\nThe [MCP server](./mcp-server.md) exposes SvGrid to AI clients (Claude\nDesktop, Cursor, Zed, Continue, custom agents) over the Model Context\nProtocol. It grounds the model in the schemas the library actually ships,\nso your assistant retrieves version-pinned facts instead of hallucinating\nan API from its training cutoff. No API key, all local.\n\n```json\n{\n \"mcpServers\": {\n \"sv-grid\": { \"command\": \"npx\", \"args\": [\"-y\", \"@sv-grid/mcp-server\"] }\n }\n}\n```\n\nIt registers callable tools - `searchDocs`, `getDocPage`, `scaffoldColumns`\n(sample row -> `ColumnDef[]`), `validateColumns`, `previewExport`,\n`listDemos` - plus read-only resources (the docs manifest and JSON Schemas)\nand pre-built prompts (`/svgrid:scaffold-grid`, `/svgrid:refactor-to-pivot`,\n`/svgrid:wire-server-side`).\n\nFor **Studio** (turning a database or schema into a CRUD data-app), the\nseparate [`@svgrid/mcp`](../enterprise/studio/ai-generation.md) server adds\n`introspect_source` and `scaffold_entity`. The generated screen is run\nthrough the Svelte compiler before it comes back, and each file carries\n`svgrid:managed` markers so a re-generation updates the managed regions and\nleaves your hand-written code untouched.\n\n## Ground any model, no MCP required\n\nIf you are not on an MCP client, four static artefacts ship with the docs\nso any model can ground itself in current facts:\n\n| File | Use for |\n| --- | --- |\n| [`/llms.txt`](/llms.txt) | First-pass context: the topic map with one-line summaries |\n| [`/llms-full.txt`](/llms-full.txt) | Deep grounding: every doc page concatenated |\n| [`/docs.json`](/docs.json) | Programmatic crawling: section tree + per-page metadata |\n| [`/schemas/index.json`](/schemas/index.json) | Validation: machine-checkable `ColumnDef`, `<SvGrid>` props, export options |\n\nUpload `llms-full.txt` into a custom GPT or Claude project, drop a rules\nblock into `.cursorrules`, or fetch the topic map into your own agent's\nsystem prompt at boot. All four are regenerated on every commit and served\nfrom the docs origin. Full recipes are on the\n[LLM grounding](./llm-grounding.md) page.\n\n## Best practices\n\n**Prompting the in-grid helpers.** These are handled for you - the grid\nalready embeds the column schema and samples rows before each call - but\nif you customise the prompt on your provider side:\n\n- Keep the live column set in front of the model every turn; it is the\n single biggest defence against invented field names.\n- Include a few sample rows so the model learns value shapes (region codes,\n date formats, enum spellings).\n- State the column ids exactly, including case - ids are case-sensitive\n (`snake_case` vs `PascalCase` matters).\n\n**Previewing before committing.** `filter` and `export` default to\nreturning a plan without touching the grid. Show the `rationale`, let the\nuser confirm, then apply. This is the pattern that makes NL features feel\ntrustworthy rather than magic-that-sometimes-breaks.\n\n**Cost routing.** The `task` tag (`filter | smart-fill | summarize |\nclassify`) and the `maxOutputTokens` hint let you route a cheap model for\nfilters and a stronger one for summaries from inside your one adapter.\n\n**Data handling.** The grid makes no network calls of its own - the AI\nhelpers send exactly the prompt you construct to the adapter you configure.\nRoute through your own `/api/ai` proxy if you need to redact, log, or keep\ndata within a boundary before it reaches a provider.\n\n## Examples\n\n- **[Demo 51 - AI assistant](../../examples/src/demos/51-ai-assistant.svelte)** -\n all six helpers wired to the mock provider, per-cell accept with\n confidence pills.\n- **[AI Smart Paste](./ai-smart-paste.md)** - parse vCard / Markdown /\n signature blocks / CSV into typed rows, with email-typo correction and\n phone normalisation.\n\n## API reference\n\n| Symbol | Package | What it is |\n| --- | --- | --- |\n| `setAIProvider(p)` | `@svgrid/enterprise` | Register the model adapter every AI call routes through. `null` clears it. |\n| `mockAIProvider` | `@svgrid/enterprise` | Deterministic canned provider for demos and tests. |\n| `type AIProvider` | `@svgrid/enterprise` | `(req: AIRequest) => Promise<string>` - the one function you implement. |\n| `api.ai.filter` / `smartFill` / `summarize` / `classify` / `export` / `findAnomalies` | `@svgrid/enterprise` | The in-grid helpers, added by `installEnterprise(api)`. |\n| `scaffoldColumns`, `validateColumns`, `previewExport`, ... | `@sv-grid/mcp-server` | Build-time MCP tools your coding agent calls. |\n| `introspect_source`, `scaffold_entity` | `@svgrid/mcp` | Studio generation tools (schema -> CRUD screen). |\n\nAuto-generated per-symbol reference: [`@svgrid/enterprise` · `ai.ts`](../reference/auto/svgrid-enterprise-ai.md).\n\n## See also\n\n- [AI assistant](./ai.md) - the in-grid helpers in full, with response shapes\n- [Agents](./agents.md) - build an agent that drives the live grid\n- [Agent Skill](./skill.md) - always-on, project-aware context and house style for coding assistants\n- [MCP server](./mcp-server.md) - turnkey integration for Claude Desktop / Cursor / Zed\n- [LLM grounding](./llm-grounding.md) - the static files any model reads\n- [AI generation - Studio](../enterprise/studio/ai-generation.md) - scaffold CRUD data-apps from a schema\n\n## Frequently asked questions\n\n### What AI features does SvGrid have?\n\nTwo kinds. At runtime, `@svgrid/grid` ships six model-agnostic helpers\nfree - natural-language filter, smart fill, summarise, classify, export,\nand anomaly detection. At build time, an MCP\nserver plus grounding files let your coding agent write correct SvGrid code\nand scaffold CRUD screens.\n\n### Which model does SvGrid use?\n\nNone by default - it is bring-your-own. You register one adapter for\nOpenAI, Anthropic Claude, a local model, or a server proxy, and the grid\nroutes every AI call through it. A deterministic mock provider ships so you\ncan evaluate the whole flow without a key.\n\n### Is my grid data sent to a model provider?\n\nOnly if you wire one up and invoke a helper. SvGrid itself makes no network\ncalls; the AI helpers send exactly the prompt you construct to the adapter\nyou configure, so you decide what leaves the browser and can proxy it\nthrough your own backend first.\n\n### Do I need the MCP server to use the AI features?\n\nNo. The in-grid helpers and the grounding files work without it. The MCP\nserver is the turnkey path for desktop AI clients; for a custom in-app\nagent you call `SvGridApi` directly.\n"
3047
+ "markdown": "# AI Toolkit\n\nEverything SvGrid ships for building with language models, in one place.\nThe toolkit spans two axes: **AI inside your running app** (helpers your\nusers invoke - natural-language filter, smart fill, summarise, classify)\nand **AI inside your editor** (the MCP server + grounding files that make\nClaude, Cursor, and friends write correct SvGrid code).\n\nNothing here bundles a model. SvGrid is **model-agnostic and\nbring-your-own-key**: you register one adapter and keep full control of\nmodel choice, routing, and what data leaves the browser.\n\n<div data-docs-demo=\"51-ai-assistant\" data-height=\"560\"></div>\n\n## The two surfaces\n\n| | AI in your app (runtime) | AI in your editor (build time) |\n| --- | --- | --- |\n| **Who invokes it** | your end users | you and your coding agent |\n| **What it does** | filter / fill / summarise / classify / export the live grid | scaffold columns, generate CRUD screens, answer API questions |\n| **Package** | `@svgrid/enterprise` (`api.ai.*`) | `@svgrid/mcp`, grounding files |\n| **Needs a model key** | yes - the one you register | no - your agent brings its own |\n| **Deep dive** | [AI assistant](./ai.md) | [MCP server](./mcp-server.md) · [LLM grounding](./llm-grounding.md) |\n\nMost teams use both: the MCP server to write the grid, the in-grid\nhelpers to power features inside it.\n\n## How it works\n\nThe grid never calls a model directly. Every runtime AI call routes\nthrough a single async **provider** you register once at app boot:\n\n```ts\nimport { setAIProvider, type AIProvider } from '@svgrid/grid'\n\nconst provider: AIProvider = async ({ prompt, responseFormat, signal, task }) => {\n const r = await fetch('/api/ai', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ prompt, responseFormat, task }),\n signal,\n })\n if (!r.ok) throw new Error(`AI provider returned ${r.status}`)\n return r.text()\n}\n\nsetAIProvider(provider)\n```\n\nThree design choices keep bad model output from becoming a silent wrong answer:\n\n- **Structured JSON, validated.** Helpers request `responseFormat: 'json'`\n and the grid `JSON.parse`s the reply. It strips a single markdown code\n fence automatically, so a model that wraps output in ` ```json ... ``` `\n still parses. On malformed output you get a typed error, not a silent\n wrong result.\n- **The prompt is grounded in your columns.** Before each call the grid\n embeds the live column schema (names, types, sampled values) into the\n prompt, so the model picks real field names instead of inventing them.\n- **A hallucination guard on the way back.** If a model still returns a\n column that does not exist, the clause is dropped rather than passed to\n `setFilter` - you lose a clause, never crash the page.\n\nThe provider shape is deliberately tiny (one async call, `text` or `json`\nresponse) so the same adapter drives an OpenAI `chat.completions` call, an\nAnthropic `messages` call, a self-hosted endpoint, or a server-side proxy.\nNo model client is ever bundled into your grid.\n\n> **Just evaluating?** The package ships a deterministic `mockAIProvider`\n> that returns plausible canned shapes per task. Wire it in with\n> `setAIProvider(mockAIProvider)` and every helper works end to end with\n> no key. The demo above runs on it.\n\n## In-grid helpers\n\n`installEnterprise(api)` - the same call you use for export and print -\naugments your `SvGridApi` with an `ai` namespace. Six helpers, all\nmodel-agnostic:\n\n```ts\napi.ai.filter(query, opts?) // NL sentence -> filter + sort plan\napi.ai.smartFill(opts) // 1-2 examples -> proposed column values\napi.ai.summarize(opts) // row/selection/group/all -> text + bullets\napi.ai.classify(opts) // free-text cells -> a clean enum label\napi.ai.export(query, opts?) // NL sentence -> filter + group + format, then export\napi.ai.findAnomalies(opts?) // scan a slice -> outliers + severity\n```\n\n### Natural-language filter\n\nThe highest-leverage feature: replace a dozen per-column filter operators\nwith one search box.\n\n```ts\nconst plan = await api.ai.filter('accounts losing momentum in EMEA, by NPS')\n// {\n// filters: [\n// { field: 'region', operator: 'equals', value: 'EMEA' },\n// { field: 'nps', operator: 'lessThan', value: '30' },\n// ],\n// sort: [{ field: 'nps', desc: false }],\n// rationale: 'EMEA region, low NPS, sorted ascending.',\n// }\n```\n\nBy default it **returns the plan without applying it**, so you can show a\n\"here is what I would do, accept?\" preview and surface the `rationale`.\nPass `{ apply: true }` to commit straight to the grid.\n\n### Smart fill\n\nThe killer feature for spreadsheet-style entry: type one or two examples\nin a column, let the model propose the rest.\n\n```ts\nconst result = await api.ai.smartFill({\n field: 'tier',\n examples: [\n { input: { company: 'Northwind' }, output: 'enterprise' },\n { input: { company: 'Helios' }, output: 'growth' },\n ],\n})\n// result.predictions: [{ rowIndex, value, confidence }, ...]\n```\n\nYou choose what to do with the predictions - accept-all, accept-per-cell\nwith a confidence pill, or write them onto the row for review.\n\n### Summarise, classify, export, anomalies\n\n- **`summarize`** drops a slice (row / selection / group / all) into the\n model and returns a paragraph, bullets, and the fields the story leans\n on. Large slices are sampled uniformly to stay under a token budget.\n- **`classify`** buckets free-text cells into a known set of labels, and\n filters out any prediction not in your `classes` list so the output is a\n clean enum.\n- **`export`** turns \"export EU orders from Q2 as a grouped PDF by\n country\" into a `{ format, filters, sort, groupBy }` plan and hands it to\n the exporter - self-contained, so the download is correct regardless of\n the grid's current view.\n- **`findAnomalies`** scans a slice for outliers and inconsistent values,\n each tagged `low | medium | high`. Pairs naturally with export: find the\n odd rows, then export just those.\n\nFull API, response shapes, and the license gate are on the\n[AI assistant](./ai.md) page.\n\n## Build an agent that drives the grid\n\nThe imperative `SvGridApi` is a clean tool surface - each method becomes\none function a model can call. Three patterns, in order of how much agency\nyou hand over:\n\n1. **Read-only summary agent** - the model describes the current view\n (`api.getDisplayedRows()`), no tool calling.\n2. **Stateful UI agent** - the model calls `setFilter` / `setSort` /\n `setGroupBy` in response to natural language, bounded by a max-turns loop.\n3. **Autonomous workflow agent** - the grid is one node in a longer chain\n (import -> enrich -> human approval -> export), and the visible table is\n the state a human can audit between steps.\n\n```ts\n// Pattern 2, sketched: each SvGridApi method is one tool the model can call.\nswitch (call.function.name) {\n case 'setFilter': api.setFilter(args.columnId, args); break\n case 'setSort': api.setSort(args.columnId, args.direction); break\n case 'setGroupBy': api.setGroupBy(args.columnIds); break\n case 'clearAllFilters': api.clearAllFilters(); break\n}\n```\n\nFull worked code, the sandboxing rules (whitelist tools, validate every\nargument against the shipped JSON Schemas, bound the loop), and the common\nfailure modes are on the [Agents](./agents.md) page.\n\n## MCP server: let your coding agent write the grid\n\nThe [MCP server](./mcp-server.md) exposes SvGrid to AI clients (Claude\nDesktop, Cursor, Zed, Continue, custom agents) over the Model Context\nProtocol. It grounds the model in the schemas the library actually ships,\nso your assistant retrieves version-pinned facts instead of hallucinating\nan API from its training cutoff. No API key, all local.\n\n```json\n{\n \"mcpServers\": {\n \"svgrid\": { \"command\": \"npx\", \"args\": [\"-y\", \"@svgrid/mcp\"] }\n }\n}\n```\n\nIt registers callable tools - `list_examples`, `get_example_source`,\n`list_docs`, `get_doc`, `search_docs`, and `get_api_reference` - so the\nagent reads real demo source and current docs instead of guessing.\n\nFor **Studio** (turning a database or schema into a CRUD data-app), the\n[same server](../enterprise/studio/ai-generation.md) adds\n`introspect_source`, `scaffold_entity`, and 27 `studio_*` project-model\ntools. The generated screen is run\nthrough the Svelte compiler before it comes back, and each file carries\n`svgrid:managed` markers so a re-generation updates the managed regions and\nleaves your hand-written code untouched.\n\n## Ground any model, no MCP required\n\nIf you are not on an MCP client, four static artefacts ship with the docs\nso any model can ground itself in current facts:\n\n| File | Use for |\n| --- | --- |\n| [`/llms.txt`](/llms.txt) | First-pass context: the topic map with one-line summaries |\n| [`/llms-full.txt`](/llms-full.txt) | Deep grounding: every doc page concatenated |\n| [`/docs.json`](/docs.json) | Programmatic crawling: section tree + per-page metadata |\n| [`/schemas/index.json`](/schemas/index.json) | Validation: machine-checkable `ColumnDef`, `<SvGrid>` props, export options |\n\nUpload `llms-full.txt` into a custom GPT or Claude project, drop a rules\nblock into `.cursorrules`, or fetch the topic map into your own agent's\nsystem prompt at boot. All four are regenerated on every commit and served\nfrom the docs origin. Full recipes are on the\n[LLM grounding](./llm-grounding.md) page.\n\n## Best practices\n\n**Prompting the in-grid helpers.** These are handled for you - the grid\nalready embeds the column schema and samples rows before each call - but\nif you customise the prompt on your provider side:\n\n- Keep the live column set in front of the model every turn; it is the\n single biggest defence against invented field names.\n- Include a few sample rows so the model learns value shapes (region codes,\n date formats, enum spellings).\n- State the column ids exactly, including case - ids are case-sensitive\n (`snake_case` vs `PascalCase` matters).\n\n**Previewing before committing.** `filter` and `export` default to\nreturning a plan without touching the grid. Show the `rationale`, let the\nuser confirm, then apply. This is the pattern that makes NL features feel\ntrustworthy rather than magic-that-sometimes-breaks.\n\n**Cost routing.** The `task` tag (`filter | smart-fill | summarize |\nclassify`) and the `maxOutputTokens` hint let you route a cheap model for\nfilters and a stronger one for summaries from inside your one adapter.\n\n**Data handling.** The grid makes no network calls of its own - the AI\nhelpers send exactly the prompt you construct to the adapter you configure.\nRoute through your own `/api/ai` proxy if you need to redact, log, or keep\ndata within a boundary before it reaches a provider.\n\n## Examples\n\n- **[Demo 51 - AI assistant](../../examples/src/demos/51-ai-assistant.svelte)** -\n all six helpers wired to the mock provider, per-cell accept with\n confidence pills.\n- **[AI Smart Paste](./ai-smart-paste.md)** - parse vCard / Markdown /\n signature blocks / CSV into typed rows, with email-typo correction and\n phone normalisation.\n\n## API reference\n\n| Symbol | Package | What it is |\n| --- | --- | --- |\n| `setAIProvider(p)` | `@svgrid/enterprise` | Register the model adapter every AI call routes through. `null` clears it. |\n| `mockAIProvider` | `@svgrid/enterprise` | Deterministic canned provider for demos and tests. |\n| `type AIProvider` | `@svgrid/enterprise` | `(req: AIRequest) => Promise<string>` - the one function you implement. |\n| `api.ai.filter` / `smartFill` / `summarize` / `classify` / `export` / `findAnomalies` | `@svgrid/enterprise` | The in-grid helpers, added by `installEnterprise(api)`. |\n| `search_docs`, `get_doc`, `list_examples`, `get_example_source`, `get_api_reference`, `list_docs` | `@svgrid/mcp` | Build-time MCP tools your coding agent calls. |\n| `introspect_source`, `scaffold_entity`, `studio_*` | `@svgrid/mcp` | Studio generation tools (schema -> CRUD screen, project model). |\n\nAuto-generated per-symbol reference: [`@svgrid/enterprise` · `ai.ts`](../reference/auto/svgrid-enterprise-ai.md).\n\n## See also\n\n- [AI assistant](./ai.md) - the in-grid helpers in full, with response shapes\n- [Agents](./agents.md) - build an agent that drives the live grid\n- [Agent Skill](./skill.md) - always-on, project-aware context and house style for coding assistants\n- [MCP server](./mcp-server.md) - turnkey integration for Claude Desktop / Cursor / Zed\n- [LLM grounding](./llm-grounding.md) - the static files any model reads\n- [AI generation - Studio](../enterprise/studio/ai-generation.md) - scaffold CRUD data-apps from a schema\n\n## Frequently asked questions\n\n### What AI features does SvGrid have?\n\nTwo kinds. At runtime, `@svgrid/grid` ships six model-agnostic helpers\nfree - natural-language filter, smart fill, summarise, classify, export,\nand anomaly detection. At build time, an MCP\nserver plus grounding files let your coding agent write correct SvGrid code\nand scaffold CRUD screens.\n\n### Which model does SvGrid use?\n\nNone by default - it is bring-your-own. You register one adapter for\nOpenAI, Anthropic Claude, a local model, or a server proxy, and the grid\nroutes every AI call through it. A deterministic mock provider ships so you\ncan evaluate the whole flow without a key.\n\n### Is my grid data sent to a model provider?\n\nOnly if you wire one up and invoke a helper. SvGrid itself makes no network\ncalls; the AI helpers send exactly the prompt you construct to the adapter\nyou configure, so you decide what leaves the browser and can proxy it\nthrough your own backend first.\n\n### Do I need the MCP server to use the AI features?\n\nNo. The in-grid helpers and the grounding files work without it. The MCP\nserver is the turnkey path for desktop AI clients; for a custom in-app\nagent you call `SvGridApi` directly.\n"
3042
3048
  },
3043
3049
  {
3044
3050
  "slug": "help/ai",
@@ -3470,13 +3476,13 @@ export const docs = [
3470
3476
  "slug": "help/llm-grounding",
3471
3477
  "path": "docs/help/llm-grounding.md",
3472
3478
  "title": "Use sv-grid docs as LLM context",
3473
- "markdown": "# Use sv-grid docs as LLM context\r\n\r\nThis page is the \"how do I make ChatGPT / Claude / Cursor write good\r\nsv-grid code?\" guide. Three pre-built artefacts ship with the docs\r\nspecifically so models can ground themselves in current, accurate\r\ninformation instead of hallucinating from training data.\r\n\r\n## The four files\r\n\r\n| File | Format | Size | Use for |\r\n| ---------------------------------------- | ---------- | ------ | ---------------------------------------------------------------------- |\r\n| [`/llms.txt`](/llms.txt) | Plain text | ~10 kB | First-pass context: the topic map with one-line summaries |\r\n| [`/llms-full.txt`](/llms-full.txt) | Plain text | ~700 kB | Deep grounding: every doc page concatenated |\r\n| [`/docs.json`](/docs.json) | JSON | ~80 kB | Programmatic crawling: section tree, per-page metadata, demo links |\r\n| [`/schemas/index.json`](/schemas/index.json) | JSON | ~30 kB | Validation: machine-checkable shape of `ColumnDef`, `<SvGrid>` props, export options |\r\n\r\nAll four are regenerated on every commit by `tools/build-docs-index.mjs`\r\nand `tools/build-schemas.mjs`. They live at the docs origin\r\n(`https://svgrid.com/...`) so you can fetch them at runtime.\r\n\r\n## Recipe 1: Drop into a custom GPT / Claude project\r\n\r\nThe simplest way. Both ChatGPT (custom GPTs) and Claude (projects)\r\nlet you upload reference files that ride along with every chat.\r\n\r\n1. Save [`/llms-full.txt`](/llms-full.txt) locally.\r\n2. In ChatGPT: *Create custom GPT → Configure → Knowledge → Upload files*.\r\n3. In Claude: *Project → Project knowledge → Add document*.\r\n4. Add this system instruction:\r\n\r\n```\r\nYou are a sv-grid expert. Ground every answer in the attached\r\nllms-full.txt. If a question references an API not in the document,\r\nsay so and ask the user to upgrade rather than inventing one. Prefer\r\nthe smallest working example. When showing columns, follow the\r\ncolumn-def.json schema exactly.\r\n```\r\n\r\n5. (Optional) Upload `column-def.json` and `svgrid-options.json`\r\n alongside so the model can self-check generated config.\r\n\r\nThat's it. The next time you ask \"how do I export only selected rows\r\nto xlsx?\" the model answers from the doc text, not from its\r\nyear-old training cutoff.\r\n\r\n## Recipe 2: Cursor / Continue / Cody rules file\r\n\r\nMost IDE assistants honour a `.cursorrules` / `.continuerules` /\r\n`.aider.conf.yml` file in the repo root. Drop in:\r\n\r\n```\r\n# .cursorrules\r\n\r\nWhen generating sv-grid code:\r\n- Read context from https://svgrid.com/llms.txt before answering.\r\n- For column definitions, generate against\r\n https://svgrid.com/schemas/column-def.json (Draft 2020-12 JSON Schema).\r\n- Use Svelte 5 runes ($state, $derived, $effect) - never legacy stores.\r\n- Use `editorType: 'list'` with `editorOptions` for dropdowns,\r\n not raw <select> elements.\r\n- Always type the grid as\r\n `SvGrid<typeof features, RowType>` so column inference works.\r\n- The two npm packages are `@svgrid/grid` (MIT) and `@svgrid/enterprise`\r\n (commercial). Never import from `@sv-grid/core` or `svelte-grid`,\r\n which are different projects.\r\n```\r\n\r\n## Recipe 3: Programmatic grounding in your own agent\r\n\r\nIf you're building a custom agent (OpenAI Agents SDK, Anthropic SDK,\r\nLangChain, custom), fetch the docs once at boot:\r\n\r\n```ts\r\nconst [topicMap, schemas] = await Promise.all([\r\n fetch('https://svgrid.com/llms.txt').then((r) => r.text()),\r\n fetch('https://svgrid.com/schemas/index.json').then((r) => r.json()),\r\n])\r\n\r\nconst systemPrompt = `You write Svelte 5 code that uses sv-grid.\r\n\r\nDOCS INDEX (use these URLs to look up specifics):\r\n${topicMap}\r\n\r\nSCHEMAS available for validation:\r\n${JSON.stringify(schemas, null, 2)}\r\n\r\nFor deep API questions, fetch https://svgrid.com/llms-full.txt or\r\nthe specific page from the index above.`\r\n```\r\n\r\nNow hand the model a tool that can fetch arbitrary `/docs.json` paths\r\non demand, and it can answer any sv-grid question with current data.\r\n\r\n## Recipe 4: MCP server (best for daily-driver chat)\r\n\r\nIf your workflow centers on Claude Desktop / Cursor / Zed, the\r\n[MCP server](./mcp-server.md) is the single line of config that\r\nexposes all four files PLUS callable tools (`scaffoldColumns`,\r\n`validateColumns`, `previewExport`). Skip Recipes 1-3 and use the\r\nMCP server instead.\r\n\r\n## What's IN the grounding files\r\n\r\nEvery file is exhaustive but tightly scoped to sv-grid surface area:\r\n\r\n- **API surface**: every prop on `<SvGrid>`, every method on\r\n `SvGridApi`, every field on `ColumnDef`\r\n- **Features**: when to use sorting / filtering / grouping / pagination\r\n feature toggles, and the trade-offs\r\n- **Enterprise tier**: export, import, pivot - each documented as\r\n if it were free, with the licensing call-out at the top of the page\r\n- **Recipes**: 25+ copy-paste patterns from the cookbook\r\n- **Migrations**: how to translate concepts from other data grids\r\n- **Errors**: every typed error the library throws, with the trigger\r\n and the fix\r\n\r\n## What's NOT in the grounding files\r\n\r\n- **Internal implementation**: virtualizer math, headless engine\r\n pipeline internals - not part of the public surface\r\n- **Future / roadmap**: deliberately excluded so the model never\r\n confuses ambition with reality\r\n- **CSS class hashes**: Svelte mangles class names. The\r\n `--sg-*` tokens are stable and documented; the class names are not.\r\n\r\n## Keeping the grounding fresh\r\n\r\nRe-fetch on every model turn for chat tools; cache for ~24h for\r\nagent loops. The docs are versioned - if you pin to a specific\r\nversion, append a `?v=1.6.0` query string when fetching from the\r\norigin (rejected if the major changes; we serve a 410).\r\n\r\n## See also\r\n\r\n- [MCP server](./mcp-server.md) - the easiest way to wire all this in\r\n- [Agents](./agents.md) - building an agent that DRIVES the grid (not just describes it)\r\n- [API stability](./api-stability.md) - what we promise to keep stable across versions\r\n"
3479
+ "markdown": "# Use sv-grid docs as LLM context\r\n\r\nThis page is the \"how do I make ChatGPT / Claude / Cursor write good\r\nsv-grid code?\" guide. Three pre-built artefacts ship with the docs\r\nspecifically so models can ground themselves in current, accurate\r\ninformation instead of hallucinating from training data.\r\n\r\n## The four files\r\n\r\n| File | Format | Size | Use for |\r\n| ---------------------------------------- | ---------- | ------ | ---------------------------------------------------------------------- |\r\n| [`/llms.txt`](/llms.txt) | Plain text | ~10 kB | First-pass context: the topic map with one-line summaries |\r\n| [`/llms-full.txt`](/llms-full.txt) | Plain text | ~700 kB | Deep grounding: every doc page concatenated |\r\n| [`/docs.json`](/docs.json) | JSON | ~80 kB | Programmatic crawling: section tree, per-page metadata, demo links |\r\n| [`/schemas/index.json`](/schemas/index.json) | JSON | ~30 kB | Validation: machine-checkable shape of `ColumnDef`, `<SvGrid>` props, export options |\r\n\r\nAll four are regenerated on every commit by `tools/build-docs-index.mjs`\r\nand `tools/build-schemas.mjs`. They live at the docs origin\r\n(`https://svgrid.com/...`) so you can fetch them at runtime.\r\n\r\n## Recipe 1: Drop into a custom GPT / Claude project\r\n\r\nThe simplest way. Both ChatGPT (custom GPTs) and Claude (projects)\r\nlet you upload reference files that ride along with every chat.\r\n\r\n1. Save [`/llms-full.txt`](/llms-full.txt) locally.\r\n2. In ChatGPT: *Create custom GPT → Configure → Knowledge → Upload files*.\r\n3. In Claude: *Project → Project knowledge → Add document*.\r\n4. Add this system instruction:\r\n\r\n```\r\nYou are a sv-grid expert. Ground every answer in the attached\r\nllms-full.txt. If a question references an API not in the document,\r\nsay so and ask the user to upgrade rather than inventing one. Prefer\r\nthe smallest working example. When showing columns, follow the\r\ncolumn-def.json schema exactly.\r\n```\r\n\r\n5. (Optional) Upload `column-def.json` and `svgrid-options.json`\r\n alongside so the model can self-check generated config.\r\n\r\nThat's it. The next time you ask \"how do I export only selected rows\r\nto xlsx?\" the model answers from the doc text, not from its\r\nyear-old training cutoff.\r\n\r\n## Recipe 2: Cursor / Continue / Cody rules file\r\n\r\nMost IDE assistants honour a `.cursorrules` / `.continuerules` /\r\n`.aider.conf.yml` file in the repo root. Drop in:\r\n\r\n```\r\n# .cursorrules\r\n\r\nWhen generating sv-grid code:\r\n- Read context from https://svgrid.com/llms.txt before answering.\r\n- For column definitions, generate against\r\n https://svgrid.com/schemas/column-def.json (Draft 2020-12 JSON Schema).\r\n- Use Svelte 5 runes ($state, $derived, $effect) - never legacy stores.\r\n- Use `editorType: 'list'` with `editorOptions` for dropdowns,\r\n not raw <select> elements.\r\n- Always type the grid as\r\n `SvGrid<typeof features, RowType>` so column inference works.\r\n- The two npm packages are `@svgrid/grid` (MIT) and `@svgrid/enterprise`\r\n (commercial). Never import from `@sv-grid/core` or `svelte-grid`,\r\n which are different projects.\r\n```\r\n\r\n## Recipe 3: Programmatic grounding in your own agent\r\n\r\nIf you're building a custom agent (OpenAI Agents SDK, Anthropic SDK,\r\nLangChain, custom), fetch the docs once at boot:\r\n\r\n```ts\r\nconst [topicMap, schemas] = await Promise.all([\r\n fetch('https://svgrid.com/llms.txt').then((r) => r.text()),\r\n fetch('https://svgrid.com/schemas/index.json').then((r) => r.json()),\r\n])\r\n\r\nconst systemPrompt = `You write Svelte 5 code that uses sv-grid.\r\n\r\nDOCS INDEX (use these URLs to look up specifics):\r\n${topicMap}\r\n\r\nSCHEMAS available for validation:\r\n${JSON.stringify(schemas, null, 2)}\r\n\r\nFor deep API questions, fetch https://svgrid.com/llms-full.txt or\r\nthe specific page from the index above.`\r\n```\r\n\r\nNow hand the model a tool that can fetch arbitrary `/docs.json` paths\r\non demand, and it can answer any sv-grid question with current data.\r\n\r\n## Recipe 4: MCP server (best for daily-driver chat)\r\n\r\nIf your workflow centers on Claude Desktop / Cursor / Zed, the\r\n[MCP server](./mcp-server.md) is the single line of config that\r\nexposes the same grounding PLUS callable tools (`search_docs`,\r\n`get_doc`, `get_example_source`, `get_api_reference`, and the SvGrid\r\nStudio generators). Skip Recipes 1-3 and use the MCP server instead.\r\n\r\n## What's IN the grounding files\r\n\r\nEvery file is exhaustive but tightly scoped to sv-grid surface area:\r\n\r\n- **API surface**: every prop on `<SvGrid>`, every method on\r\n `SvGridApi`, every field on `ColumnDef`\r\n- **Features**: when to use sorting / filtering / grouping / pagination\r\n feature toggles, and the trade-offs\r\n- **Enterprise tier**: export, import, pivot - each documented as\r\n if it were free, with the licensing call-out at the top of the page\r\n- **Recipes**: 25+ copy-paste patterns from the cookbook\r\n- **Migrations**: how to translate concepts from other data grids\r\n- **Errors**: every typed error the library throws, with the trigger\r\n and the fix\r\n\r\n## What's NOT in the grounding files\r\n\r\n- **Internal implementation**: virtualizer math, headless engine\r\n pipeline internals - not part of the public surface\r\n- **Future / roadmap**: deliberately excluded so the model never\r\n confuses ambition with reality\r\n- **CSS class hashes**: Svelte mangles class names. The\r\n `--sg-*` tokens are stable and documented; the class names are not.\r\n\r\n## Keeping the grounding fresh\r\n\r\nRe-fetch on every model turn for chat tools; cache for ~24h for\r\nagent loops. The docs are versioned - if you pin to a specific\r\nversion, append a `?v=1.6.0` query string when fetching from the\r\norigin (rejected if the major changes; we serve a 410).\r\n\r\n## See also\r\n\r\n- [MCP server](./mcp-server.md) - the easiest way to wire all this in\r\n- [Agents](./agents.md) - building an agent that DRIVES the grid (not just describes it)\r\n- [API stability](./api-stability.md) - what we promise to keep stable across versions\r\n"
3474
3480
  },
3475
3481
  {
3476
3482
  "slug": "help/mcp-server",
3477
3483
  "path": "docs/help/mcp-server.md",
3478
3484
  "title": "MCP server",
3479
- "markdown": "# MCP server\r\n\r\nThe sv-grid MCP server lets AI clients (Claude Desktop, Cursor, Zed,\r\nContinue, custom agents) query the documentation, scaffold column\r\ndefinitions, and preview exports - all grounded in the same schemas\r\nthe library ships with. No API key required; everything runs locally\r\nagainst your installed copy.\r\n\r\n![An AI coding agent calls the @svgrid/mcp server over the Model Context Protocol, which runs grid tools and returns structured JSON results back to the agent.](/docs-media/grid-mcp.svg)\r\n\r\n> **What is MCP?** Model Context Protocol is the open standard\r\n> ([modelcontextprotocol.io](https://modelcontextprotocol.io)) for\r\n> exposing tools / resources / prompts to LLM clients. sv-grid ships an\r\n> MCP server out of the box so the model your team already uses can\r\n> \"see\" the grid without you having to copy-paste docs into prompts.\r\n\r\n## Install\r\n\r\n```bash\r\n# Inside any project that already depends on @svgrid/grid\r\npnpm add -D @sv-grid/mcp-server\r\n```\r\n\r\nThe server is a Node binary. Run it on demand from the package's\r\n`bin` field - no daemon to maintain.\r\n\r\n## Wire it into your AI client\r\n\r\n### Claude Desktop\r\n\r\nEdit `~/Library/Application Support/Claude/claude_desktop_config.json`\r\n(macOS) or `%APPDATA%\\Claude\\claude_desktop_config.json` (Windows):\r\n\r\n```json\r\n{\r\n \"mcpServers\": {\r\n \"sv-grid\": {\r\n \"command\": \"npx\",\r\n \"args\": [\"-y\", \"@sv-grid/mcp-server\"]\r\n }\r\n }\r\n}\r\n```\r\n\r\nRestart Claude Desktop. Type `@sv-grid` in any chat to confirm the\r\ntools are exposed.\r\n\r\n### Cursor\r\n\r\n`Settings → MCP → Add new MCP server`:\r\n\r\n```json\r\n{ \"command\": \"npx\", \"args\": [\"-y\", \"@sv-grid/mcp-server\"] }\r\n```\r\n\r\n### Zed\r\n\r\n`~/.config/zed/settings.json`:\r\n\r\n```json\r\n{\r\n \"context_servers\": {\r\n \"sv-grid\": { \"command\": { \"path\": \"npx\", \"args\": [\"-y\", \"@sv-grid/mcp-server\"] } }\r\n }\r\n}\r\n```\r\n\r\n### Custom agents (OpenAI Agents SDK, Anthropic SDK, LangChain)\r\n\r\nPoint your client's MCP transport at:\r\n\r\n```\r\nnpx -y @sv-grid/mcp-server\r\n```\r\n\r\nAny client that speaks MCP stdio works.\r\n\r\n## Tools exposed\r\n\r\nThe server registers six tools. All return structured JSON; none\r\nrequire an API key or network access.\r\n\r\n### `searchDocs`\r\n\r\nGround the model in the doc set without dumping the whole corpus.\r\n\r\n```ts\r\nsearchDocs({ query: string, limit?: number }):\r\n Array<{ path, title, summary, score, snippet }>\r\n```\r\n\r\nBacked by the same `docs.json` manifest you can fetch directly.\r\n\r\n### `getDocPage`\r\n\r\nPull the full markdown of one page by URL or path.\r\n\r\n```ts\r\ngetDocPage({ path: '/help/pivot.md' }): { title, source, demoIds }\r\n```\r\n\r\n### `scaffoldColumns`\r\n\r\nGenerate a `ColumnDef[]` from a sample row. Picks reasonable widths,\r\ninferred editor types, sensible header labels, and format options for\r\nnumbers / currencies / ISO dates.\r\n\r\n```ts {nocheck}\r\nscaffoldColumns({\r\n sampleRow: { id: 'r1', sellDate: '2026-05-12', price: 1499.99, currency: 'USD' },\r\n inferFormat?: boolean, // default true\r\n language?: 'ts' | 'js', // default 'ts'\r\n})\r\n// → { code: string, columns: ColumnDef[] }\r\n```\r\n\r\n### `validateColumns`\r\n\r\nCheck a `ColumnDef[]` payload against `column-def.json`. Returns the\r\nlist of issues with file / line hints (when the input is a code\r\nstring). Useful for agents that generate columns and want a self-check\r\nbefore showing the result.\r\n\r\n```ts\r\nvalidateColumns({ columns: ColumnDef[] | string }):\r\n { valid: boolean, issues: Array<{ path, message, severity }> }\r\n```\r\n\r\n### `previewExport`\r\n\r\nDry-run an `api.exportData({...})` call. Returns the rows + header\r\nlayout the exporter WOULD write, without actually triggering a\r\ndownload. Useful when an agent is composing a multi-sheet export and\r\nwants to verify column ordering before committing.\r\n\r\n```ts\r\npreviewExport({ format: 'xlsx', rows: [...], columns: [...] }):\r\n { sheets: Array<{ label, header, rows }> }\r\n```\r\n\r\n### `listDemos`\r\n\r\nReturns every demo in `examples/src/demos/` with its title, blurb,\r\ncategory, source path, and the prompt sidecar (see\r\n[LLM grounding](./llm-grounding.md)).\r\n\r\n```ts\r\nlistDemos({ category?: string }):\r\n Array<{ id, title, blurb, category, source, prompt }>\r\n```\r\n\r\n## Resources exposed\r\n\r\nIn addition to tools, the server exposes three MCP **resources** -\r\nread-only documents the client can browse:\r\n\r\n| URI | Content |\r\n| ------------------------------ | ------------------------------------------------- |\r\n| `svgrid://docs/llms.txt` | Topic map (see [llms.txt](/llms.txt)) |\r\n| `svgrid://docs/llms-full.txt` | Concatenated full text of every doc |\r\n| `svgrid://docs/manifest` | `docs.json` route manifest |\r\n| `svgrid://schemas/column-def` | JSON Schema for `ColumnDef` |\r\n| `svgrid://schemas/svgrid-options` | JSON Schema for `<SvGrid>` props |\r\n| `svgrid://schemas/export-options` | JSON Schema for `api.exportData({...})` |\r\n\r\n## Prompts exposed\r\n\r\nPre-built MCP prompts you can invoke directly from a chat:\r\n\r\n- **`/svgrid:scaffold-grid`** - paste a sample row, get a complete\r\n `<SvGrid>` + `tableFeatures` + `ColumnDef[]` setup\r\n- **`/svgrid:refactor-to-pivot`** - hand it a flat-grid component, get\r\n a pivot-grid version\r\n- **`/svgrid:wire-server-side`** - convert client-side data to a\r\n server-side adapter with sort / filter / paginate round-trips\r\n\r\n## Verifying it works\r\n\r\nAfter wiring the server, ask your model: *\"What MCP tools do you have\r\nfrom sv-grid?\"* You should see all six tools listed. If not, check\r\nyour client's MCP log; the most common issue is `npx` not being on\r\nPATH (use the absolute path to the binary instead).\r\n\r\n## Security model\r\n\r\n- The server runs **locally**. No telemetry, no outbound network calls.\r\n- File reads are scoped to your project's `node_modules/@sv-grid/*`\r\n and any `docs/` folder you explicitly pass via the `--docs <dir>` flag.\r\n- `scaffoldColumns` and `previewExport` are pure functions - they\r\n inspect input and emit text. They never write to disk or fetch from\r\n the network.\r\n- See [security](./security.md) for the general supply-chain posture.\r\n\r\n## Building your own MCP integrations\r\n\r\nThe same `docs.json` + JSON Schemas + `llms.txt` files the server uses\r\nare also accessible directly from your docs site\r\n([https://svgrid.com](https://svgrid.com)):\r\n\r\n```ts\r\nconst docs = await fetch('https://svgrid.com/docs.json').then((r) => r.json())\r\nconst schemas = await fetch('https://svgrid.com/schemas/index.json').then((r) => r.json())\r\nconst llms = await fetch('https://svgrid.com/llms-full.txt').then((r) => r.text())\r\n```\r\n\r\nIf you don't want to run the MCP server, building these into your\r\nagent's system prompt gives ~80% of the same value.\r\n\r\n## See also\r\n\r\n- [LLM grounding](./llm-grounding.md) - the same files used by the MCP server, but documented for direct LLM consumption\r\n- [Agents](./agents.md) - how to build an AI agent that drives the live grid\r\n- [AI assistant](./ai.md) - the in-grid AI features (filter / smart-fill / classify / summarise), free in @svgrid/grid\r\n\r\n## Frequently asked questions\r\n\r\n### What is the sv-grid MCP server?\r\n\r\nA Model Context Protocol server that lets AI clients (Claude Desktop, Cursor,\r\nZed, Continue, custom agents) query SvGrid's documentation, scaffold column\r\ndefinitions, and preview exports - all grounded in the schemas the library\r\nships with, so the model answers from current facts instead of guessing.\r\n\r\n### Do I need an API key to run it?\r\n\r\nNo. The MCP server runs locally against your installed copy of SvGrid. There is\r\nno key and no external call.\r\n\r\n### How does it help AI assistants write better SvGrid code?\r\n\r\nIt exposes example sources, the docs, and the API reference as MCP tools, so the\r\nassistant retrieves accurate, version-pinned answers rather than hallucinating\r\nan API from training data.\r\n"
3485
+ "markdown": "# MCP server\n\nThe SvGrid MCP server lets AI clients (Claude Code, Claude Desktop,\nCursor, Zed, Codex, custom agents) query the documentation, read real\ndemo source, and scaffold SvelteKit CRUD apps - all grounded in the\nfiles this repository ships. No API key required; everything runs\nlocally over stdio.\n\n![An AI coding agent calls the @svgrid/mcp server over the Model Context Protocol, which runs grid tools and returns structured JSON results back to the agent.](/docs-media/grid-mcp.svg)\n\n> **What is MCP?** Model Context Protocol is the open standard\n> ([modelcontextprotocol.io](https://modelcontextprotocol.io)) for\n> exposing tools to LLM clients. SvGrid ships an MCP server so the\n> model your team already uses can \"see\" the grid without you having\n> to copy-paste docs into prompts.\n\nThe package is [`@svgrid/mcp`](https://www.npmjs.com/package/@svgrid/mcp)\non npm, and it is listed in the official MCP registry as\n`com.svgrid/svgrid`.\n\n## Install\n\nNo install step is required - `npx` fetches it on demand:\n\n```bash\n# One-shot, from any project\nnpx -y @svgrid/mcp\n```\n\nTo pin it as a dev dependency instead:\n\n```bash\npnpm add -D @svgrid/mcp\n```\n\nThe server is a Node binary (`svgrid-mcp`) that speaks MCP over stdio.\nThere is no daemon to maintain.\n\n## Wire it into your AI client\n\n### Claude Code\n\nOne command:\n\n```bash\nclaude mcp add svgrid -- npx -y @svgrid/mcp\n```\n\nThen run `/mcp` in a session and you will see `svgrid` listed.\n\nTo share the server with your team, add `--scope project`. That writes\na `.mcp.json` at the repository root which you can commit, so everyone\nwho clones the repo gets the same tooling with no per-machine setup:\n\n```bash\nclaude mcp add svgrid --scope project -- npx -y @svgrid/mcp\n```\n\n### Claude Desktop\n\nEdit `~/Library/Application Support/Claude/claude_desktop_config.json`\n(macOS) or `%APPDATA%\\Claude\\claude_desktop_config.json` (Windows):\n\n```json\n{\n \"mcpServers\": {\n \"svgrid\": {\n \"command\": \"npx\",\n \"args\": [\"-y\", \"@svgrid/mcp\"]\n }\n }\n}\n```\n\nRestart Claude Desktop, then ask *\"using svgrid, build me a grid that\ngroups by department\"* to confirm the tools are exposed.\n\n### Cursor\n\n`Settings -> MCP -> Add new MCP server`:\n\n```json\n{ \"command\": \"npx\", \"args\": [\"-y\", \"@svgrid/mcp\"] }\n```\n\n### Zed\n\n`~/.config/zed/settings.json`:\n\n```json\n{\n \"context_servers\": {\n \"svgrid\": {\n \"command\": \"npx\",\n \"args\": [\"-y\", \"@svgrid/mcp\"],\n \"env\": {}\n }\n }\n}\n```\n\n### VS Code\n\nCreate `.vscode/mcp.json` in the workspace. Note that VS Code uses\n`servers` rather than the `mcpServers` wrapper:\n\n```json\n{\n \"servers\": {\n \"svgrid\": {\n \"type\": \"stdio\",\n \"command\": \"npx\",\n \"args\": [\"-y\", \"@svgrid/mcp\"]\n }\n }\n}\n```\n\n### Custom agents (OpenAI Agents SDK, Anthropic SDK, LangChain)\n\nPoint your client's MCP stdio transport at:\n\n```\nnpx -y @svgrid/mcp\n```\n\nAny client that speaks MCP stdio works.\n\n## Tools exposed\n\nThe server registers 35 tools: 8 for documentation, examples, and\nscaffolding, plus 27 `studio_*` tools that drive the SvGrid Studio\nproject model. All run locally; none require an API key or a network\ncall.\n\n### Documentation and examples\n\nThese six are free and need no license key.\n\n#### `list_examples`\n\nList every demo with `id`, `title`, and a one-line blurb. Use it to\ndiscover what exists before fetching source.\n\n```ts\nlist_examples(): Array<{ id, title, blurb, path }>\n```\n\n#### `get_example_source`\n\nReturn the full `.svelte` source of one demo, verbatim, including\nimports - the same file a user would copy into a project.\n\n```ts\nget_example_source({ id: '11-stock-market' }): string\n```\n\n#### `list_docs`\n\nList every documentation page with slug and title. Slugs use forward\nslashes, for example `help/columns/column-definitions`.\n\n```ts\nlist_docs(): Array<{ slug, title }>\n```\n\n#### `get_doc`\n\nReturn the markdown of a single page by slug.\n\n```ts\nget_doc({ slug: 'getting-started' }): string\n```\n\n#### `search_docs`\n\nCase-insensitive substring search across all docs. Returns matching\nslugs with a one-line excerpt around the first hit.\n\n```ts\nsearch_docs({ query: 'row virtualization', limit?: 10 })\n```\n\n#### `get_api_reference`\n\nThe curated public-API surface, grouped by category (components,\nheadless, scheduler, data ops, export, row models, features,\nvirtualization, accessibility, utilities).\n\n```ts\nget_api_reference(): string\n```\n\n### SvGrid Studio (commercial)\n\nThese tools generate application code. They still run without a\nlicense key, but generated files are prefixed with a comment pointing\nat [pricing](https://svgrid.com/pricing/). Set `SVGRID_LICENSE_KEY` in\nthe MCP server's environment for licensed use (see\n[Licensing](#licensing) below).\n\n#### `introspect_source`\n\nInfer a draft `EntitySchema` from a data source: either a Drizzle\nschema file (`kind: \"drizzle\"`, `source`: the file text) or sample\nrows (`kind: \"json\"`, `rows`, `name`). Review and refine the draft\nbefore scaffolding.\n\n```ts\nintrospect_source({ kind: 'drizzle', source: '...' })\nintrospect_source({ kind: 'json', rows: [...], name: 'orders' })\n```\n\n#### `scaffold_entity`\n\nGenerate runnable SvelteKit files from an `EntitySchema`: the `$lib`\nschema module, a `+server.ts` API route using `createKitHandlers`, and\na `+page.svelte` with `SvGrid` and `SvGridEditPanel`.\n\n```ts\nscaffold_entity({ schema, route?, apiRoute? }):\n Array<{ path, contents, description }>\n```\n\nGenerated bodies are wrapped in `svgrid:managed` markers, so\nregeneration preserves your edits outside them. After writing the\nfiles, run the project's own `svelte-check` or `tsc` to verify they\ncompile.\n\n#### The `studio_*` tools\n\n27 tools let an agent build and edit the same validated project model\nthe visual designer uses, then generate the app:\n\n| Area | Tools |\n| ---- | ----- |\n| Project | `studio_new_project`, `studio_load_project`, `studio_describe_project`, `studio_validate`, `studio_capabilities`, `studio_get_config`, `studio_generate_app` |\n| Entities | `studio_add_entity`, `studio_set_entity_source` |\n| Screens | `studio_add_screen`, `studio_update_screen`, `studio_remove_screen`, `studio_set_screen_layout` |\n| Blocks and components | `studio_add_block`, `studio_update_block`, `studio_move_block`, `studio_remove_block`, `studio_add_component` |\n| Forms | `studio_set_form_layout`, `studio_set_field_conditions` |\n| Platform | `studio_set_auth`, `studio_set_access`, `studio_set_tenancy`, `studio_set_data_layer`, `studio_set_deploy_target`, `studio_set_theme`, `studio_set_job` |\n\nCall `studio_capabilities` first: it reports exactly what the\ninstalled version supports, so the agent does not have to guess.\n\n## Licensing\n\nThe documentation and example tools are free. The Studio code\ngenerators are part of the commercial offering: they run unlicensed,\nbut prepend a notice comment to generated files. To license them, set\nthe key in your MCP client's server config:\n\n```json\n{\n \"mcpServers\": {\n \"svgrid\": {\n \"command\": \"npx\",\n \"args\": [\"-y\", \"@svgrid/mcp\"],\n \"env\": { \"SVGRID_LICENSE_KEY\": \"SVENTERPRISE-...\" }\n }\n }\n}\n```\n\n## Verifying it works\n\nAfter wiring the server, ask your model: *\"What MCP tools do you have\nfrom svgrid?\"* You should see the documentation tools and the\n`studio_*` set. If not, check your client's MCP log; the most common\nissue is `npx` not being on PATH (use the absolute path to the binary\ninstead).\n\n## Security model\n\n- The server runs **locally** over stdio. No telemetry, no outbound\n network calls, no API key.\n- It serves a documentation and example corpus bundled into the\n package at build time, so answers are pinned to the version you\n installed.\n- The Studio tools return generated files as data. Writing them to\n disk is your client's decision, not the server's.\n- See [security](./security.md) for the general supply-chain posture.\n\n## Building your own MCP integrations\n\nThe same docs manifest, JSON Schemas, and `llms.txt` files are also\nserved directly from the docs site:\n\n```ts\nconst docs = await fetch('https://svgrid.com/docs.json').then((r) => r.json())\nconst schemas = await fetch('https://svgrid.com/schemas/index.json').then((r) => r.json())\nconst llms = await fetch('https://svgrid.com/llms-full.txt').then((r) => r.text())\n```\n\nIf you do not want to run the MCP server, building these into your\nagent's system prompt gives most of the same grounding.\n\n## See also\n\n- [LLM grounding](./llm-grounding.md) - the same files used by the MCP server, but documented for direct LLM consumption\n- [Agents](./agents.md) - how to build an AI agent that drives the live grid\n- [AI assistant](./ai.md) - the in-grid AI features (filter / smart-fill / classify / summarise), free in @svgrid/grid\n\n## Frequently asked questions\n\n### What is the SvGrid MCP server?\n\nA Model Context Protocol server that lets AI clients (Claude Code, Claude\nDesktop, Cursor, Zed, custom agents) query SvGrid's documentation, read real\ndemo source, and scaffold SvelteKit CRUD apps - grounded in the files the\npackage ships, so the model answers from current facts instead of guessing.\n\n### Do I need an API key to run it?\n\nNo. The MCP server runs locally over stdio. There is no key and no external\ncall. A `SVGRID_LICENSE_KEY` is optional and only affects the commercial\nStudio code generators.\n\n### How does it help AI assistants write better SvGrid code?\n\nIt exposes example sources, the docs, and the API reference as MCP tools, so\nthe assistant retrieves accurate, version-pinned answers rather than\nhallucinating an API from training data. That matters most for Svelte 5, where\nmodels routinely mix in outdated Svelte 4 syntax.\n"
3480
3486
  },
3481
3487
  {
3482
3488
  "slug": "help/migrating-from-ag-grid",
@@ -12,7 +12,7 @@
12
12
  * The server holds one in-memory "current project" per session; `studio_get_config`
13
13
  * / `studio_generate_app` are the outputs.
14
14
  */
15
- import { createProject, parseProject, serializeProject, validateProject, addEntity, addScreen, addFreestandingScreen, addBlock, addComponentBlock, updateBlock, removeBlock, moveBlock, updateScreen, removeScreen, setScreenLayout, setEntityDataSource, setJob, setTenancy, setTheme, setAuth, setDataLayer, setDeployTarget, introspectDrizzle, introspectJson, flattenBlocks, blockPalette, UI_COMPONENT_REGISTRY, uiComponentSpec, studioThemes, emitStudioAppBundle, checkLicenseKey, entityDataSource, } from '@svgrid/enterprise/studio';
15
+ import { createProject, parseProject, serializeProject, validateProject, addEntity, addScreen, addFreestandingScreen, addBlock, addComponentBlock, updateBlock, removeBlock, moveBlock, updateScreen, removeScreen, setScreenLayout, setEntityForm, setFieldConditions, formPlan, suggestFormSections, setEntityDataSource, setJob, setTenancy, setTheme, setAuth, setDataLayer, setDeployTarget, introspectDrizzle, introspectJson, flattenBlocks, blockPalette, UI_COMPONENT_REGISTRY, uiComponentSpec, studioThemes, emitStudioAppBundle, checkLicenseKey, entityDataSource, } from '@svgrid/enterprise/studio';
16
16
  // ---- session state --------------------------------------------------------
17
17
  let project = null;
18
18
  function requireProject() {
@@ -223,6 +223,35 @@ export const projectTools = [
223
223
  required: ['screenId', 'layout'],
224
224
  },
225
225
  },
226
+ {
227
+ name: 'studio_set_form_layout',
228
+ description: 'Arrange an entity\'s create/edit form: column count and titled sections. `sections` is an array of { title?, description?, columns?: 1|2|3, fields: string[], visibleWhen?: PredicateExpr }; `fields` gives both the grouping and the order, and a field left out of every section still renders in a trailing untitled group. Omit `sections` and pass "suggest": true to have them proposed from the field names. The layout lives on the entity, so it renders the same in the edit panel, the generated app, and a server-rendered form.',
229
+ inputSchema: {
230
+ type: 'object',
231
+ properties: {
232
+ entity: { type: 'string' },
233
+ columns: { type: 'number', enum: [1, 2, 3] },
234
+ sections: { type: 'array', items: { type: 'object' }, description: 'The FormSection list. Replaces the current one.' },
235
+ suggest: { type: 'boolean', description: 'Propose sections from the field names instead of passing them.' },
236
+ },
237
+ required: ['entity'],
238
+ },
239
+ },
240
+ {
241
+ name: 'studio_set_field_conditions',
242
+ description: 'Make a form field value-driven: `visible`, `required`, and `disabled` conditions, each a PredicateExpr over the other fields, e.g. { "kind": "cmp", "column": "status", "op": "equals", "value": "cancelled" }. A field hidden by `visible` is skipped by validation and left out of the saved record; `required` REPLACES the field\'s static required flag (so it can make a required field optional too). Pass a condition as null to clear it, or omit every condition to clear all three. Conditions are data, so they generate into the app and re-run server-side.',
243
+ inputSchema: {
244
+ type: 'object',
245
+ properties: {
246
+ entity: { type: 'string' },
247
+ field: { type: 'string' },
248
+ visible: { type: ['object', 'null'], description: 'PredicateExpr, or null to clear.' },
249
+ required: { type: ['object', 'null'], description: 'PredicateExpr, or null to clear.' },
250
+ disabled: { type: ['object', 'null'], description: 'PredicateExpr, or null to clear.' },
251
+ },
252
+ required: ['entity', 'field'],
253
+ },
254
+ },
226
255
  {
227
256
  name: 'studio_set_entity_source',
228
257
  description: 'Bind an entity to a data source. `source` is an EntityDataSource, e.g. { "kind": "sql", "table": "customers", "dialect": "postgres" } | { "kind": "memory" } | { "kind": "pglite", "table": "..." } | { "kind": "supabase", ... } | { "kind": "rest", ... }.',
@@ -506,6 +535,59 @@ export function handleProjectTool(name, args) {
506
535
  project = setScreenLayout(p, screenId, layout);
507
536
  return confirm(`Screen ${screenId} now uses the ${layout} layout.`);
508
537
  }
538
+ case 'studio_set_form_layout': {
539
+ const p = requireProject();
540
+ const entity = String(args.entity ?? '');
541
+ const schema = p.entities.find((e) => e.name === entity);
542
+ if (!schema)
543
+ return fail(`No entity "${entity}".`);
544
+ const columns = args.columns === undefined ? schema.form?.columns : Number(args.columns);
545
+ if (columns !== undefined && ![1, 2, 3].includes(columns))
546
+ return fail('columns must be 1, 2, or 3.');
547
+ let sections = schema.form?.sections;
548
+ if (args.suggest) {
549
+ sections = suggestFormSections(schema);
550
+ if (!sections.length)
551
+ return fail(`Nothing to suggest for "${entity}" - too few form fields to be worth grouping.`);
552
+ }
553
+ else if (args.sections !== undefined) {
554
+ if (!Array.isArray(args.sections))
555
+ return fail('sections must be an array of FormSection objects.');
556
+ sections = args.sections;
557
+ }
558
+ // Plan before storing: a name that resolves to nothing (a typo, or a
559
+ // field since renamed) is dropped here rather than persisted into
560
+ // `studio.config.json` for a later reader to puzzle over. The reply
561
+ // reports the plan, so the agent sees what actually landed.
562
+ const plan = formPlan(schema, sections);
563
+ project = setEntityForm(p, entity, { columns, sections: plan.sections });
564
+ const placed = plan.sections.map((s) => `${s.title ?? '(untitled)'}: ${s.fields.join(', ') || '(empty)'}`);
565
+ return confirm(`"${entity}" form: ${columns ?? 2} columns, ${plan.sections.length} section(s).` +
566
+ (placed.length ? `\n${placed.join('\n')}` : '') +
567
+ (plan.unassigned.length ? `\nUnsectioned (render last): ${plan.unassigned.join(', ')}` : ''));
568
+ }
569
+ case 'studio_set_field_conditions': {
570
+ const p = requireProject();
571
+ const entity = String(args.entity ?? '');
572
+ const field = String(args.field ?? '');
573
+ const schema = p.entities.find((e) => e.name === entity);
574
+ if (!schema)
575
+ return fail(`No entity "${entity}".`);
576
+ if (!schema.fields.some((f) => f.field === field))
577
+ return fail(`No field "${field}" on "${entity}".`);
578
+ const current = schema.fields.find((f) => f.field === field).when ?? {};
579
+ const keys = ['visible', 'required', 'disabled'];
580
+ // Absent = leave as it was; null = clear it. Without that distinction an
581
+ // agent setting one condition would silently drop the other two.
582
+ const given = keys.filter((k) => args[k] !== undefined);
583
+ const when = given.length
584
+ ? Object.fromEntries(keys.map((k) => [k, args[k] === undefined ? current[k] : (args[k] || undefined)]))
585
+ : undefined;
586
+ const next = setFieldConditions(p, entity, field, when);
587
+ project = next;
588
+ const set = keys.filter((k) => next.entities.find((e) => e.name === entity).fields.find((f) => f.field === field).when?.[k]);
589
+ return confirm(set.length ? `"${entity}.${field}" is now conditional on: ${set.join(', ')}.` : `Cleared the conditions on "${entity}.${field}".`);
590
+ }
509
591
  case 'studio_set_entity_source': {
510
592
  const p = requireProject();
511
593
  const entity = String(args.entity ?? '');
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "type": "commercial",
6
6
  "url": "https://svgrid.com/pricing"
7
7
  },
8
- "version": "2.5.0",
8
+ "version": "2.6.1",
9
9
  "description": "Model Context Protocol (MCP) server for SvGrid. Exposes example sources, docs, and API reference to AI assistants.",
10
10
  "license": "SEE LICENSE IN LICENSE",
11
11
  "author": "jQWidgets <sales@jqwidgets.com>",
@@ -32,7 +32,7 @@
32
32
  "dependencies": {
33
33
  "@modelcontextprotocol/sdk": "^1.0.4",
34
34
  "zod": "^3.23.8",
35
- "@svgrid/enterprise": "^2.5.0"
35
+ "@svgrid/enterprise": "^2.6.1"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/node": "^22.10.7",
package/server.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "name": "com.svgrid/svgrid",
4
4
  "title": "SvGrid",
5
5
  "description": "Version-pinned Svelte 5 data grid APIs, 373 demo sources, and SvelteKit app scaffolding.",
6
- "version": "2.5.0",
6
+ "version": "2.6.1",
7
7
  "websiteUrl": "https://svgrid.com/docs/help/mcp-server/",
8
8
  "repository": {
9
9
  "url": "https://github.com/sv-grid/sv-grid",
@@ -15,7 +15,7 @@
15
15
  "registryType": "npm",
16
16
  "registryBaseUrl": "https://registry.npmjs.org",
17
17
  "identifier": "@svgrid/mcp",
18
- "version": "2.5.0",
18
+ "version": "2.6.1",
19
19
  "runtimeHint": "npx",
20
20
  "transport": {
21
21
  "type": "stdio"