softr-vibe-coding 2.2.0 → 2.4.0

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/CHANGELOG.md CHANGED
@@ -4,6 +4,14 @@ All notable changes to this skill are documented here. Versions follow [Semantic
4
4
 
5
5
  Entries from 1.3.1 onward are generated automatically from git commit subjects between version bumps (see `.github/workflows/publish.yml`). Entries before 1.3.1 were backfilled by hand from the existing commit history.
6
6
 
7
+ ## [2.4.0] - 2026-09-01
8
+ - Replace building-design-md with dembrandt as the Step 1 DESIGN.md generator. New references/dembrandt.md operating guide: MCP install via npx pinned @latest (deliberate deviation from upstream README — bare npx reuses its cache and never re-checks the registry) + one-time install-browser step (CLI self-heals a missing browser since 0.30.0, the MCP server does not — exact error string and remediation documented), the get_design_tokens → get_job_status → get_findings → generate_design_md flow behavior-tested live 2026-09-01 (async job_id default; completion returns the FULL extraction — 65KB for one page — so pass job_id downstream, never the payload; completed jobs kept 1 hour; generate_design_md returns markdown text and writes no file — the agent writes ./DESIGN.md in the project root), multi-page crawl doctrine (pages 3–5 / --crawl 5; live 1-page noise: 46 near-duplicate text tokens, 2.72px spacing steps, white-on-white button sample), DESIGN.md anatomy per Google draft spec 0.4 (name/description/colors/typography/spacing/rounded/components frontmatter — context-named typography tokens with text-N fallback, source URL inside description, no date field; Font URLs vs computed ui-sans-serif cross-check), failed-job remediation (slow retry / CLI firefox for Cloudflare / install-browser), authoring custom-code-header.html from DESIGN.md tokens (provenance claim fixed in native-chrome-styling.md — dembrandt generates no Softr-ready CSS), compute_drift brand QA (drift = did it change, findings = is it good). SKILL.md Step 1 rewritten: found-branch reads dembrandt frontmatter with legacy brand:/source:/extracted: files (incl. Application Patterns/tech_stack) still honoured + regenerate-overwrites note; not-found option A now generates in-session via MCP or CLI instead of ending the skill, with next-session MCP caveat, no-website hand-author path, and permission gate. README: brand pipeline section swapped to dembrandt with migration note for building-design-md users, TL;DR bullet, References entry, tree + file-count refresh
9
+ - airtable-automations.md: Meta API now creates formula/rollup/lookup fields + tables (verified live 2026-08-31); still no automations/views/deletes/option-renames
10
+ - writing.md: sanction cross-table parallel writes + optimistic-move/undo pattern; anti-patterns: FIFO-helper concurrency hazard (found live 2026-08-31)
11
+
12
+ ## [2.3.0] - 2026-08-31
13
+ - Document expanded Softr MCP: deletes, app scaffolding, Workflows suite (26 tools + 418-node catalog), per-application MCP servers
14
+
7
15
  ## [2.2.0] - 2026-08-31
8
16
  - Add editable-settings and static-blocks references: undocumented useLongTextSetting + navigation array-schema type, settings-first doctrine, full-bleed/full-viewport heroes, block-owned fixed headers
9
17
 
package/README.md CHANGED
@@ -17,6 +17,7 @@ This Claude skill teaches Claude Code how to generate complete, polished Softr V
17
17
  - **Complete Vibe Coding API reference** — `useRecords`, `q.select()`, mutations, uploads, metrics, charts, editable settings, `useProxyFetch` for REST APIs
18
18
  - **Editable settings deep-dive** — full hook catalog including verified-undocumented capabilities (`useLongTextSetting`, the `navigation` array-schema type), settings-first design doctrine so clients edit copy/images/links in Content → Settings without re-prompting
19
19
  - **Static marketing blocks** — heroes, landing headers, pricing tables, footers: editorial baseline, full-bleed layouts, full-viewport sizing, block-owned fixed headers with scroll-condensing treatment
20
+ - **Brand pipeline via dembrandt** — no DESIGN.md in the project? The skill generates one on the spot with [dembrandt](https://github.com/dembrandt/dembrandt) (real-browser design-token extraction over MCP: colors, typography, spacing, components from any site you have permission to analyze), writes it to the project root, and builds every block on those tokens — or keeps the default Softr style if you skip it
20
21
  - **All 14 Softr data sources** — Airtable, Softr Database, Google Sheets, HubSpot, Notion, Coda, monday.com, SmartSuite, ClickUp, Xano, Supabase, BigQuery, SQL Database, and REST API — each with field mapping, rate limits, and gotchas
21
22
  - **Helper blocks & cross-block patterns** — Invisible helper blocks for multi-table access via `window` globals + `CustomEvent`, `useWindowData` hook, breadcrumb navigation, saved views architecture
22
23
  - **Advanced integrations** — Shadow DOM CSS isolation for third-party libraries (Leaflet, Mapbox, TinyMCE, Quill, FullCalendar)
@@ -25,7 +26,7 @@ This Claude skill teaches Claude Code how to generate complete, polished Softr V
25
26
  - **Self-validation** — Claude checks Softr platform compatibility and house conventions (inline hook options, correct payload shapes, correct imports, container wrappers or a deliberate full-bleed layout, `getFieldValue()` wrapping, hooks ordering) before delivering code
26
27
  - **Premium visual baseline** — every app-UI block (dashboards, lists, forms, detail pages) ships polished from v1: gradient backgrounds, card elevation, loading skeletons, empty states, error states; static marketing blocks use the editorial baseline instead
27
28
  - **Debug utilities** — Field Inspector, API Response Inspector, and User Inspector blocks for diagnosing data source and permissions issues
28
- - **Softr MCP integration** — when the [official Softr MCP server](https://docs.softr.io/mcp/overview) is installed (`claude mcp add --transport http softr https://mcp.softr.io/mcp`), Claude reads Softr DB schema, field IDs, and dropdown option UUIDs directly, browses connected Airtable / Google Sheets / Notion / Supabase integrations down to field level, and can even create and deploy Vibe Coding blocks straight into your app no more pasting `tablespace-with-tables` JSON or copy-pasting code into Studio. See `references/softr-mcp.md`.
29
+ - **Softr MCP integration** — when the [official Softr MCP server](https://docs.softr.io/mcp/overview) is installed (`claude mcp add --transport http softr https://mcp.softr.io/mcp`), Claude reads Softr DB schema, field IDs, and dropdown option UUIDs directly, browses connected Airtable / Google Sheets / Notion / Supabase integrations down to field level, creates and deploys Vibe Coding blocks straight into your app, manages full record/table/field/database CRUD (deletes included), and can scaffold applications and build **Softr Workflows** against a 418-node trigger/action catalog. Also covers Softr's **per-application MCP servers** (one per published app, scoped to the app's own permissions). See `references/softr-mcp.md`.
29
30
 
30
31
  ---
31
32
 
@@ -88,24 +89,27 @@ Build me a Softr Vibe Coding block that shows a team directory with cards
88
89
 
89
90
  ---
90
91
 
91
- ## Companion skill`building-design-md`
92
+ ## Brand pipelinedembrandt
92
93
 
93
- This skill is **Step 2** of a two-skill brand-to-blocks pipeline:
94
+ This skill is the second half of a brand-to-blocks pipeline:
94
95
 
95
96
  ```
96
- New client → building-design-md (brand → DESIGN.md) → softr-vibe-coding (DESIGN.md → blocks) → shipped Softr app
97
+ New client → dembrandt (website → DESIGN.md) → softr-vibe-coding (DESIGN.md → blocks) → shipped Softr app
97
98
  ```
98
99
 
99
- The upstream skill is [`building-design-md`](https://github.com/leo-softr/design-md-extractor-skill), which extracts a brand foundation (colors, typography, voice) from a website URL or brand guide into a portable `DESIGN.md` file. When that file exists in your project folder, this skill picks it up automatically and applies the brand tokens throughout every block it generates — no re-asking about colors or fonts.
100
+ The DESIGN.md generator is [dembrandt](https://github.com/dembrandt/dembrandt) (MIT, npm) — a real-browser design-token extractor: point it at a website and it extracts colors, typography, spacing, radii, shadows, and component styles, rendered as a portable `DESIGN.md` in Google's DESIGN.md draft format. When that file exists in your project folder, this skill picks it up automatically and applies the brand tokens throughout every block it generates — no re-asking about colors or fonts. When it doesn't, the skill offers to generate one on the spot through the dembrandt MCP server (writing it to the project root), or proceeds with the default Softr style if you skip branding.
100
101
 
101
- **Install both for the full workflow:**
102
+ **Install for the full workflow:**
102
103
 
103
104
  ```bash
104
- npx building-design-md@latest init
105
105
  npx softr-vibe-coding@latest init
106
+ claude mcp add --transport stdio dembrandt -- npx -y --package dembrandt@latest dembrandt-mcp
107
+ npx -y dembrandt@latest install-browser # one-time: fetches the Chromium dembrandt drives
106
108
  ```
107
109
 
108
- Both auto-update on every Claude Code session. Skip the first one if you only want default Softr styling.
110
+ The skill auto-updates on every Claude Code session (SessionStart hook), and the dembrandt server checks the registry for the newest release on every launch (`@latest`), so both stay current. Skip the two dembrandt lines if you only want default Softr styling. Full operating guide (MCP flow, multi-page crawls, DESIGN.md anatomy, drift QA): `references/dembrandt.md`.
111
+
112
+ Previously used the `building-design-md` companion skill? It's retired as of v2.4.0 and can be uninstalled — DESIGN.md files it produced remain fully supported (Step 1 reads their `brand:`/`source:` frontmatter and `Application Patterns`/`tech_stack` sections as before).
109
113
 
110
114
  ---
111
115
 
@@ -184,7 +188,10 @@ softr-vibe-coding/
184
188
  │ ├── softr-mcp.md # Official Softr MCP server — vibe coding block
185
189
  │ │ # tools (create/edit/version/deploy), integrations
186
190
  │ │ # browsing (Airtable/Sheets/Notion/Supabase),
187
- │ │ # Softr DB schema + record tools, auth, permissions
191
+ │ │ # Softr DB schema + record tools incl. deletes,
192
+ │ │ # app management/scaffolding, Workflows suite
193
+ │ │ # (26 tools, 418-node catalog), per-application
194
+ │ │ # MCP servers, auth, permissions
188
195
  │ ├── advanced-integrations.md # Shadow DOM CSS isolation
189
196
  │ │ # Leaflet, Mapbox, TinyMCE, Quill, FullCalendar
190
197
  │ ├── native-chrome-styling.md # Restyle Softr's native shell (header, footer,
@@ -209,6 +216,10 @@ softr-vibe-coding/
209
216
  │ │ # headers, pricing, footers — workflow deltas,
210
217
  │ │ # editorial baseline, full-bleed + full-viewport,
211
218
  │ │ # block-owned header, section anchors
219
+ │ ├── dembrandt.md # DESIGN.md generation with dembrandt
220
+ │ │ # MCP/CLI install (@latest npx + browser step),
221
+ │ │ # extract → poll → findings → generate → write
222
+ │ │ # flow, DESIGN.md anatomy, drift QA
212
223
  │ └── quick-reference.md # Syntax cheat sheet
213
224
  │ # Imports, hook signatures, mutation shapes,
214
225
  │ # field mapping, component skeleton
@@ -246,7 +257,7 @@ softr-vibe-coding/
246
257
 
247
258
  ### How context loading works
248
259
 
249
- Only `SKILL.md` loads into Claude's context when the skill triggers. The data source guides, reference files, and UI/UX guidelines load **on demand** — Claude reads only the files relevant to your specific block. This keeps context lean even with 26 files totaling 6,500+ lines.
260
+ Only `SKILL.md` loads into Claude's context when the skill triggers. The data source guides, reference files, and UI/UX guidelines load **on demand** — Claude reads only the files relevant to your specific block. This keeps context lean even with 30+ files totaling 6,000+ lines.
250
261
 
251
262
  ---
252
263
 
@@ -315,6 +326,7 @@ Pull requests are what make open source great, and we appreciate the spirit behi
315
326
 
316
327
  - [Softr Vibe Coding Developer Guide](https://docs.softr.io/vibe-coding-developer-guide) — Official Softr Vibe Coding documentation
317
328
  - [Softr Data Sources](https://docs.softr.io/data-sources) — Official Softr data source documentation
329
+ - [dembrandt](https://github.com/dembrandt/dembrandt) — Real-browser design-token extractor used for DESIGN.md generation (see `references/dembrandt.md`)
318
330
  - [Impeccable](https://github.com/pbakaus/impeccable) — Design patterns and UI/UX anti-pattern principles by Paul Bakaus (referenced in the UI/UX guidelines)
319
331
  - [Claude Code Skills Documentation](https://code.claude.com/docs/en/skills) — How Claude Code skills work
320
332
 
package/SKILL.md CHANGED
@@ -6,8 +6,9 @@ description: >
6
6
  Also trigger when the user asks to create cards, lists, forms, dashboards, charts, detail pages,
7
7
  or any interactive block intended for Softr — even if they don't say "Vibe Coding" explicitly.
8
8
  If the user mentions Softr in the context of building a custom UI component, creating a JSX block,
9
- or vibe coding, use this skill. Do NOT use for extracting brand tokens or producing a DESIGN.md
10
- (that is the building-design-md skill), or for charts/dashboards with no Softr app involved.
9
+ or vibe coding, use this skill. Do NOT use for standalone brand-token extraction with no Softr app
10
+ involved (that is the dembrandt tool on its own — this skill drives dembrandt in Step 1 when a
11
+ project needs a DESIGN.md), or for charts/dashboards with no Softr app involved.
11
12
  when_to_use: >
12
13
  Triggers on "build me a Softr block", "create a card component", "make a dashboard",
13
14
  "vibe code this", "custom block for Softr", "JSX component for Softr app",
@@ -29,12 +30,14 @@ You generate complete, production-ready Softr Vibe Coding blocks as TypeScript R
29
30
 
30
31
  - **If `./DESIGN.md` is found:** Read its frontmatter and confirm with the user:
31
32
 
32
- > "I found a DESIGN.md in this project (brand: `<name>`, source: `<source>`, extracted: `<date>`). Use its brand tokens for this block?
33
+ > "I found a DESIGN.md in this project (brand: `<name>`, extracted from: `<source URL>`). Use its brand tokens for this block?
33
34
  > 1. Yes — use this DESIGN.md
34
35
  > 2. No — use the default Softr style instead
35
36
  > 3. No — I'll paste a different brand override"
36
37
 
37
- If (1), load every relevant section: `colors`, `typography`, `rounded`, `elevation`, `components`, and especially the `Application Patterns` scaffold. Apply those tokens throughout the block. Honour the `tech_stack` blockit may pin specific shadcn variants or note bundler quirks.
38
+ (Filling the placeholders: in a dembrandt-generated file, `<name>` is the frontmatter `name:` and the source URL sits inside `description:`; legacy files carry `brand:`/`source:`/`extracted:` instead. On request, a stale DESIGN.md can also be regenerated with dembrandt that overwrites `./DESIGN.md`, and legacy scaffolding sections like `Application Patterns`/`tech_stack` are not reproduced.)
39
+
40
+ If (1), load every token section present: `colors`, `typography`, `spacing`, `rounded`, `components` from the YAML frontmatter, plus the body's Layout / Elevation & Depth / Shapes evidence and the Typography section's **Font URLs** (the frontmatter `fontFamily` can name a generic fallback like `ui-sans-serif` while the Font URLs reveal the real brand font — cross-check before picking the font). Sections without extracted evidence are simply absent — don't invent defaults for them. Legacy pre-dembrandt files may instead carry `elevation`, an `Application Patterns` scaffold, or a `tech_stack` block — honour those when present (the `tech_stack` block may pin specific shadcn variants or note bundler quirks). Full format anatomy: [references/dembrandt.md](references/dembrandt.md#designmd-anatomy-what-step-1-reads).
38
41
 
39
42
  If (2), proceed with the default Softr style (see Step 3).
40
43
 
@@ -43,11 +46,11 @@ You generate complete, production-ready Softr Vibe Coding blocks as TypeScript R
43
46
  - **If `./DESIGN.md` is NOT found:** Tell the user:
44
47
 
45
48
  > "No DESIGN.md found in this project. Three options:
46
- > A. **Set up a brand foundation first** — use the `building-design-md` skill to extract brand tokens from the client's website or a brand guide, then come back here. (Recommended for client work. Not installed? Run `npx building-design-md@latest init` in your terminal.)
49
+ > A. **Generate one now with dembrandt** — give me the brand's website URL and I'll extract its design system (real-browser crawl: colors, typography, spacing, components) into `./DESIGN.md`, then build on those tokens. (Recommended for client work.)
47
50
  > B. **Quick brand override** — paste the brand's primary color, accent color, and font name now. I'll apply just those.
48
51
  > C. **Use the default Softr style** — primary `#386AF5`, accent `#FCB500`, Inter font."
49
52
 
50
- Wait for their pick. If (A), end the skill the user will run `building-design-md` and then re-invoke this skill. If (B) or (C), record their choice for Step 3 and continue.
53
+ Wait for their pick. If (A) extracting only a site the user owns or has permission to analyze (a contracted client's own site qualifies) — run the dembrandt pipeline from [references/dembrandt.md](references/dembrandt.md) in this same session — via the dembrandt MCP server when connected (`get_design_tokens` with `pages: 3`–`5` → poll `get_job_status` → `get_findings` sanity check → `generate_design_md`, then **write the returned markdown to `./DESIGN.md` in the project root**), or the CLI fallback when not (`npx -y dembrandt@latest <url> --design-md --crawl 5`, then copy `output/<domain>/DESIGN.md` to `./DESIGN.md`). If neither is available, give the user the install commands from that reference and pause until dembrandt is set up (an MCP server added now connects next session — the CLI is the same-session path). Show the user the extracted brand summary, then continue to Step 2 with those tokens. No public website to extract? Use (B), or offer to hand-author `./DESIGN.md` from whatever brand material the user has (a brand guide PDF, a style sheet) — Step 1 honours any DESIGN.md with token sections, not only dembrandt-generated ones. If (B) or (C), record their choice for Step 3 and continue.
51
54
 
52
55
  Do not silently default to Softr's brand. The user must opt in to defaults explicitly.
53
56
 
@@ -80,7 +83,7 @@ You generate complete, production-ready Softr Vibe Coding blocks as TypeScript R
80
83
  - `fetchNextPage` never called in the render body — only from an event handler (Load More `onClick` with `disabled={isFetching}`, the official pattern) or a guarded `useEffect` (auto-load-all)
81
84
  - Mutations use `recordId` (not `id`) and call `refetch()` in `onSuccess`
82
85
  - `useRecordUpdate` payload is `{ recordId, fields: { ... } }` — nested. `useRecordCreate` payload is **flat** (no `fields` wrapper). The two shapes are asymmetric by design (verified live 2026-08-25)
83
- - Sequential multi-row saves use `await hook.mutateAsync(...)` per row, in order, with stop-on-failure + retry state — `mutateAsync` is fully supported on the current platform (verified 2026-08-25; the old ".mutate() only" Action-parser rule is gone — see [datasources/writing.md](datasources/writing.md))
86
+ - Sequential multi-row saves use `await hook.mutateAsync(...)` per row, in order, with stop-on-failure + retry state — `mutateAsync` is fully supported on the current platform (verified 2026-08-25; the old ".mutate() only" Action-parser rule is gone — see [datasources/writing.md](datasources/writing.md)). Independent writes to **different tables** may run in parallel via `Promise.all`; drag/reassign UIs should be optimistic with an Undo toast — see [writing.md → Parallel writes across tables](datasources/writing.md#parallel-writes-across-tables-the-one-sanctioned-parallelism)
84
87
  - No hardcoded domains in links -- use relative paths (`/page?recordId=...`); same-page anchors written relative too (`/#section`)
85
88
  - Static block: no hardcoded user-visible copy — every string/image/link is an editable setting (see [references/editable-settings.md](references/editable-settings.md#granularity-doctrine-settings-first-static-blocks))
86
89
  - Array-setting rows keyed by **index**, never by a builder-editable field value
@@ -96,7 +99,7 @@ When the user describes their block, figure out which of these areas apply and a
96
99
  - For **Airtable**, the most thorough path is the bundled **`get-airtable-base` shell script** — `bash ~/.claude/skills/softr-vibe-coding/tools/get-airtable-base` (requires `jq` — `brew install jq` on macOS). It prompts for Base ID + PAT, then exports the full schema (every table, every field with both `fld...` IDs and column names, relationships, webhooks, interfaces) to a timestamped Desktop folder. The user pastes `02-schema.json` or the combined `00-bundle.json` into chat. For lighter inspection (just a few fields, runtime-only), suggest the Field Inspector block — empty `q.select({})` works for Airtable. CLI script details in [datasources/airtable.md](datasources/airtable.md#bundled-cli-script-get-airtable-base).
97
100
  - For other non-Softr-DB sources where empty `q.select({})` works, suggest the Field Inspector block.
98
101
  - **Brand colors**: Already resolved in Step 1 (Detect the brand source). Don't re-ask. The brand source is one of:
99
- - **Project's `./DESIGN.md`** (recommended for client work — produced by the `building-design-md` skill)
102
+ - **Project's `./DESIGN.md`** (recommended for client work — generated by dembrandt in Step 1, or already present in the project; see [references/dembrandt.md](references/dembrandt.md))
100
103
  - **User's quick override** (paste of primary + accent + font)
101
104
  - **Default Softr palette** (only when the user explicitly opted in — never as a silent fallback):
102
105
 
@@ -166,11 +169,12 @@ For advanced patterns beyond data fetching, load the relevant reference when the
166
169
  | Quick syntax check — import paths, hook signatures, mutation call shapes, field mapping | [references/quick-reference.md](references/quick-reference.md) |
167
170
  | Small reusable patterns — `localStorage` cross-page state, clipboard copy button | [references/common-patterns.md](references/common-patterns.md) |
168
171
  | Writing Airtable Automation Scripts / Scripting Extension scripts / Airtable formulas — companion to Softr blocks for cross-table cascades and computed values | [references/airtable-automations.md](references/airtable-automations.md) |
169
- | The official **Softr MCP server** — schema discovery and record reads for Softr DB, field-level browsing of connected Airtable / Google Sheets / Notion / Supabase integrations, and **creating, editing, versioning, and deploying Vibe Coding blocks directly** (`get_vibe_coding_docs`, `create_vibe_coding_block`, `connect_vibe_coding_block_data_source`, ...) | [references/softr-mcp.md](references/softr-mcp.md) |
172
+ | The official **Softr MCP server** — Softr DB schema + full record/table/field/database CRUD (deletes included), field-level browsing of connected Airtable / Google Sheets / Notion / Supabase integrations, **creating, editing, versioning, and deploying Vibe Coding blocks directly** (`get_vibe_coding_docs`, `create_vibe_coding_block`, ...), app management/scaffolding, the **Softr Workflows** suite (26 tools, 418-node catalog), and **per-application MCP servers** | [references/softr-mcp.md](references/softr-mcp.md) |
170
173
  | Restyling Softr's **native shell — header / footer / nav / dropdowns / page background** (not a block; it's Softr chrome, done with global Custom Code CSS): stable selectors vs. hashed classes, floating "island" header+footer, the dropdown blank-space grid fix, the multi-layer page-background stacking, restyle-vs-replace | [references/native-chrome-styling.md](references/native-chrome-styling.md) |
171
174
  | Adding a **dynamic date filter or custom filter control to a native List/Grid block** (via a Custom Code Static block, not a Vibe block): drive the block's conditional filter with `{URL_PARAM:…}`, the empty-param "match nothing" wide-range sentinel, inject the control into the filter row and keep it alive across Softr's re-renders | [references/native-block-filters.md](references/native-block-filters.md) |
172
175
  | **Editable settings deep-dive** — full hook catalog (incl. verified-undocumented `useLongTextSetting` and the `navigation` array-schema type), settings-first granularity doctrine, heading-line-split and `-text`/`-link` pairing patterns, naming conventions, rename-resets-value gotcha, empty-media gating, key-by-index rule | [references/editable-settings.md](references/editable-settings.md) |
173
176
  | **Static marketing blocks** — heroes, landing headers, pricing tables, footers: workflow deltas (skip datasources), editorial baseline, full-bleed license, full-viewport sizing, block-owned fixed header + caveats, section anchors | [references/static-blocks.md](references/static-blocks.md) |
177
+ | Generating or refreshing a project **`DESIGN.md` with dembrandt** — MCP + CLI install (`@latest` npx, one-time browser step), the extract → poll → `get_findings` → `generate_design_md` → write-`./DESIGN.md` flow, multi-page crawls, DESIGN.md anatomy (frontmatter tokens, Font URLs), authoring `custom-code-header.html` from tokens, brand-drift QA with `compute_drift` | [references/dembrandt.md](references/dembrandt.md) |
174
178
 
175
179
  ## Code Structure
176
180
 
@@ -380,7 +384,7 @@ var askAi = useNavigationSetting({
380
384
  - `OPEN_CHAT` — opens Softr's AI chat. **No `destination` or `openIn` needed** — it's the cheapest "Ask AI" button to wire up. **GOTCHA:** Softr's AI pulls context from the block that triggered the chat, NOT from the page. If the block has no data source connected, `chat/prepare` returns HTTP 500 ("Failed to prepare AI assistant") even though the chat panel opens. Fix: in Softr Studio, connect the block to whatever data source the AI should read from — even if the block doesn't read or write any records itself, the connection is what gives the AI context. Verified by direct experiment, May 2026: a button-only helper block with no data source caused this exact failure; connecting it to the same table as the main block fixed it without any code change.
381
385
  - `OPEN_URL` — opens an external URL. Needs `destination` (the URL) + `openIn` (`"SELF"` | `"TAB"`).
382
386
  - `OPEN_PAGE` — navigates to a Softr page in-app. Needs `destination` (page path) + `openIn` (`"SELF"` | `"TAB"` | `"MODAL"`).
383
- - `TRIGGER_CUSTOM_WORKFLOW` — runs a Softr workflow. The documented setting shape is `{ action: "TRIGGER_CUSTOM_WORKFLOW" }` with no `destination` (same shape as `OPEN_CHAT`); the builder picks the workflow in the block's settings panel. Not verified live whether a `destination` is also accepted — don't rely on one.
387
+ - `TRIGGER_CUSTOM_WORKFLOW` — runs a Softr workflow. The documented setting shape is `{ action: "TRIGGER_CUSTOM_WORKFLOW" }` with no `destination` (same shape as `OPEN_CHAT`); the builder picks the workflow in the block's settings panel. Not verified live whether a `destination` is also accepted — don't rely on one. The workflow-side receiving end is the Softr Apps trigger **"Run Custom Workflow action triggered"** in the workflow node catalog (name-based match, not wired live) — workflows themselves are buildable via MCP; see [references/softr-mcp.md](references/softr-mcp.md#workflows).
384
388
 
385
389
  When the action navigates to a record-specific page, pass the runtime record id via the `recordId` prop on `<NavigationAction>` (not on the setting) so Softr can resolve dynamic URLs:
386
390
 
@@ -390,7 +394,7 @@ When the action navigates to a record-specific page, pass the runtime record id
390
394
  </NavigationAction>
391
395
  ```
392
396
 
393
- For on-brand custom styling (matching DESIGN.md inline-style conventions instead of shadcn Button), wrap the same pattern with a styled `<button>` — `<NavigationAction>` will render its children into the button's slot. The `asChild` attribute on Button is what enables this slot composition; without it shadcn renders its own native button and ignores `<NavigationAction>`.
397
+ For on-brand custom styling (matching the Step 1 brand tokens' inline-style conventions instead of shadcn Button), wrap the same pattern with a styled `<button>` — `<NavigationAction>` will render its children into the button's slot. The `asChild` attribute on Button is what enables this slot composition; without it shadcn renders its own native button and ignores `<NavigationAction>`.
394
398
 
395
399
  **Third shape — standalone `<NavigationAction>` with `className`** (text-style links, nav items — no Button wrapper). NavigationAction renders its own clickable element and forwards `className` onto it; include UA-style resets since that element ships default chrome. Observed in Studio-AI output 2026-08-31, not in the official developer guide — don't treat className forwarding as officially guaranteed:
396
400
 
@@ -190,7 +190,7 @@ fetch("https://workflows-api.softr.io/v1/workflows/WORKFLOW_ID/executions/EXECUT
190
190
 
191
191
  - Supports GET and POST methods only in the data source connector (no PUT, PATCH, DELETE -- use Softr Action buttons with custom API call actions for those)
192
192
  - Response data must be parseable JSON
193
- - **Proxy payloads are text-only** -- streams, `FormData`, and file uploads through `proxyFetch` fail. For file-to-record uploads use `useUpload` (see [writing.md](writing.md#file-uploads)), which targets the connected record datasource; uploading files to an arbitrary external API needs a different mechanism (e.g. a Softr workflow / webhook receiver)
193
+ - **Proxy payloads are text-only** -- streams, `FormData`, and file uploads through `proxyFetch` fail. For file-to-record uploads use `useUpload` (see [writing.md](writing.md#file-uploads)), which targets the connected record datasource; uploading files to an arbitrary external API needs a different mechanism e.g. a Softr Workflow with a `WEBHOOK` trigger (and `RESPONDED_TO_WEBHOOK` for a custom HTTP reply), buildable via MCP: [../references/softr-mcp.md](../references/softr-mcp.md#workflows)
194
194
  - No 2-way user sync
195
195
  - Requires Business or Enterprise plan
196
196
 
@@ -93,6 +93,8 @@ After `source ~/.zshrc`, just run `get-softr-database <database_id>` from anywhe
93
93
  | Relationship | Yes | Linked records to other Softr Database tables. Write as an **array of record-id strings**, e.g. `[recordId]` (verified 2026-08-25) |
94
94
  | Formula | Read-only | Booleans return as strings: use `=== "1"` for true, `=== "0"` for false |
95
95
 
96
+ This table is the coarse block-side view. The full current catalog is larger — Rating, Duration, Currency, Percent, plus computed Lookup/Rollup/Count and system fields — and is best fetched live via the MCP's `get_schema`, authoritative **for the server you're calling**: the per-app servers currently document a fuller catalog than the workspace server (adding Address, Progress, Time, Date range, Button, and display options like Percent's progress-bar/ring), and even operator names differ between the two — see [../references/softr-mcp.md](../references/softr-mcp.md#softr-database-tools).
97
+
96
98
  ## Rate Limits
97
99
  No API rate limits. Softr Database queries run internally without external API calls, making it the best choice for high-traffic applications.
98
100
 
@@ -211,7 +211,55 @@ Rules that make this safe:
211
211
  to `undefined`.
212
212
  - **Gate the whole flow on the hooks' `enabled` booleans**, same as any mutation UI.
213
213
 
214
- ## File Uploads
214
+ ### Parallel writes across tables (the one sanctioned parallelism)
215
+
216
+ The no-parallel rule above is about *same-table batches*. Writes to **different tables through
217
+ different hooks, with no ordering or id-linking dependency between them**, may run concurrently —
218
+ `Promise.all` of the two `mutateAsync` promises roughly halves the wall-clock of a two-table save
219
+ (verified live 2026-08-31: a Kanban reassignment writing `Wigs.Worker` + a batch of
220
+ `Wig Services.Worker` lines in parallel):
221
+
222
+ ```jsx
223
+ Promise.all([
224
+ updateWig.mutateAsync({ recordId: wigId, fields: { worker: [{ id: targetId }] } }),
225
+ reassignServicesViaHelper(lineTargets), // helper-owned batch on ANOTHER table
226
+ ]).then(onSaved).catch(onFailed);
227
+ ```
228
+
229
+ Checklist before parallelizing:
230
+
231
+ - **Different tables, different hooks.** Same-table batches stay sequential — Airtable's
232
+ per-base rate limit, and stop-on-first-failure semantics that a parallel volley can't give you.
233
+ - **No dependency either way.** If one write needs the other's id (header → lines), it's a
234
+ sequence, not a pair.
235
+ - **Neither side is an event-driven helper that correlates responses by FIFO queue order.**
236
+ Many helper blocks in the wild match `onSuccess` events to requests with `queue.shift()` —
237
+ concurrent dispatches completing out of order mis-pair the correlation ids. Serialize calls
238
+ into such a helper, or upgrade it to batch semantics (one event carries the whole batch, the
239
+ helper chains `mutateAsync` internally and answers once). See
240
+ [anti-patterns.md → Helper Blocks](../references/anti-patterns.md#helper-blocks).
241
+ - **Partial failure is acceptable and handled.** With `Promise.all`, one side can commit while
242
+ the other fails — refetch both tables in the failure path so the UI shows the true state.
243
+
244
+ ### Optimistic moves + undo (perceived speed beats write speed)
245
+
246
+ For drag-and-drop or one-click reassignment UIs, don't make the user watch the writes — a
247
+ multi-row save through helpers takes seconds, and a board that freezes until `refetch()`
248
+ completes reads as broken. The pattern (verified live 2026-08-31 on a custom Kanban):
249
+
250
+ 1. **Override map**: `pendingMoves[recordId] = target` in state; the render layer applies the
251
+ override on top of server-derived data, so the card jumps the moment it's dropped.
252
+ 2. **Background writes** with a small per-card "saving" spinner; the board stays interactive
253
+ (track saving per record id, not one global flag).
254
+ 3. **Revert on failure**: delete the override + `refetch()` → the card snaps back, with an
255
+ error toast.
256
+ 4. **Clear on convergence**: an effect compares each override against fresh server data and
257
+ deletes it once they match — never clear on a timer.
258
+ 5. **Undo**: snapshot the *server* state before mutating (previous lead, each row's previous
259
+ value — per-row, since a batch may have had mixed values), and offer
260
+ `toast.success(msg, { duration: 8000, action: { label: "Undo", onClick: restore } })`.
261
+ The restore is just another optimistic move driven by the snapshot. Snapshot before the
262
+ write, not from the UI — the UI may already be showing an optimistic override.
215
263
 
216
264
  ```jsx
217
265
  import { useUpload } from "@/lib/datasource";
@@ -509,9 +557,10 @@ block, no REST API, and no exposed key. Required reading:
509
557
  deployment routed all of its cross-table writes (header + ledger + audit-log rows) through
510
558
  `from:`-scoped mutation hooks.
511
559
 
512
- Two remaining alternatives, for the cases multi-datasource doesn't cover:
560
+ Three remaining alternatives, for the cases multi-datasource doesn't cover:
513
561
 
514
562
  - **For Airtable backends** — when the cascade logic is heavy, write to the block's own table and let an Airtable automation script handle the cascade. See [../references/airtable-automations.md](../references/airtable-automations.md). Keeps the block simple and lets cross-table logic live next to the data.
563
+ - **For Softr Database backends** — same pattern, native tool: a **Softr Workflow** triggered by the record change (record added / updated / deleted / meets-conditions / enters-view triggers, bulk record update/delete actions, per-item loops, and a custom JS/Python code node). Buildable and publishable via the MCP. See [../references/softr-mcp.md](../references/softr-mcp.md#workflows).
515
564
  - **Softr Database REST API via `fetch()`** — a fallback for what the hooks can't express (e.g. writes from outside a Vibe block, or admin tooling that must bypass block bindings). Details below.
516
565
 
517
566
  **Base URL:** `https://tables-api.softr.io/api/v1/databases/{databaseId}/tables/{tableId}/records`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "softr-vibe-coding",
3
- "version": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "description": "Claude Code skill for generating production-ready Softr Vibe Coding blocks (JSX). Installs into ~/.claude/skills/ and auto-updates on each Claude Code session.",
5
5
  "bin": {
6
6
  "softr-vibe-coding": "./bin/cli.js"
@@ -1,6 +1,6 @@
1
1
  # Airtable Automation Scripts & Formulas
2
2
 
3
- Companion reference for Softr Vibe Coding blocks. Many Softr blocks talk to an Airtable backend, and some flows can't be done from the block side — most commonly **cross-table writes triggered by a record change** (the block can only write to its own configured data source). For those, the right tool is an Airtable Automation Script, written in JavaScript and triggered by Airtable's automation runner.
3
+ Companion reference for Softr Vibe Coding blocks. Many Softr blocks talk to an Airtable backend, and some flows can't be done from the block side — most commonly **cross-table cascades triggered by a record change** (a block writes only to its connected data sources, and even a multi-datasource block shouldn't carry heavy cascade logic — see [datasources/multi-datasource.md](../datasources/multi-datasource.md)). For those, the right tool is an Airtable Automation Script, written in JavaScript and triggered by Airtable's automation runner.
4
4
 
5
5
  This guide covers Airtable's two scripting environments + Airtable formulas. **It is NOT about Softr Vibe Coding** — runtime, API surface, and gotchas are entirely different. Don't apply Softr block rules (shadow DOM isolation, data-hook constraints, etc.) here.
6
6
 
@@ -10,10 +10,11 @@ This guide covers Airtable's two scripting environments + Airtable formulas. **I
10
10
  |---|---|
11
11
  | Build UI inside a Softr page | Softr Vibe Coding block |
12
12
  | React to a record-level event in Airtable (e.g. status change) and cascade to other tables | **Airtable Automation Script** |
13
+ | React to a record-level event in a **Softr Database** and cascade | **Softr Workflow** (record triggers + bulk actions + JS/Python code node) — see [softr-mcp.md → Workflows](softr-mcp.md#workflows) |
13
14
  | Run ad-hoc transformations / bulk fixes | Airtable Scripting Extension |
14
15
  | Compute a derived value displayed in an Airtable cell | Airtable formula field |
15
16
 
16
- If a Softr block needs to write across multiple tables in response to a user click, the **cleanest pattern** is: have the Softr block write to its own table, and let an Airtable automation handle the cascade. See [datasources/writing.md Cross-Table Operations](../datasources/writing.md#cross-table-operations) for the alternative (Softr Database REST API), but the Airtable-side automation is usually simpler.
17
+ For plain multi-table writes from a user click, the default is **multi-datasource from the block itself** (`from:`-scoped mutation hooks — see [datasources/writing.md → Cross-Table Operations](../datasources/writing.md#cross-table-operations)). When the multi-table write is really a **cascade with its own logic** — too heavy to live in the block — the cleanest pattern is: have the block write to its own table, and let the backend cascade: an Airtable automation for Airtable backends, a Softr Workflow for Softr Database backends. writing.md lists all the alternatives, including the Softr Database REST API fallback.
17
18
 
18
19
  ## Two scripting environments — pick the right one
19
20
 
@@ -320,6 +321,15 @@ For Automation Scripts, your ONLY user-visible surfaces are `console.log()` (run
320
321
 
321
322
  For formula fields inside Airtable itself (NOT scripts). Quick rules:
322
323
 
324
+ - **The Meta API can CREATE formula, rollup, and lookup fields — and whole tables** (verified
325
+ live 2026-08-31: a full payroll junction table with two SUM rollups, lookups, and formula
326
+ fields built via `POST /v0/meta/bases/{base}/tables` + `…/fields`; rollup options shape:
327
+ `{recordLinkFieldId, fieldIdInLinkedTable, formula: "SUM(values)"}`). The long-standing
328
+ "computed fields are UI-only" rule is dead. Still API-impossible: **automations**, **views**,
329
+ **field/table deletion**, and **select-option renames** (field name/description PATCH works).
330
+ Corollary: reference other fields by `{fldXXX}` id inside API-created formulas — id references
331
+ survive renames, and the API accepts them even though the UI shows names.
332
+
323
333
  - **NEVER add comments inside Airtable formulas.** Airtable's formula engine doesn't have a comment syntax — anything that looks like a comment will fail to compile.
324
334
  - **Guard against blank inputs by default.** Airtable surfaces `#ERROR!` (or `#NaN!` for divide-by-blank) when arithmetic, date, or many string operations touch a blank field — `{A} * {B}` errors when either is blank, `{A} / {B}` errors when `{B}` is blank, `DATEADD({Start At}, 20, 'minutes')` errors when `{Start At}` is blank, and one upstream `#ERROR!` propagates through every downstream formula that references it. Wrap any formula whose inputs could be empty with an `IF()` guard returning `BLANK()` in the empty branch. Prefer explicit `IF()` / `AND()` guards over a catch-all `IFERROR(<expr>, BLANK())` — explicit guards keep intent readable and don't mask unrelated bugs (a typo'd field name silently returns blank instead of failing loudly). Reach for `IFERROR()` only when inputs come from genuinely uncontrolled sources.
325
335
 
@@ -26,7 +26,7 @@ Run through this catalog before delivering any block. Every row is a violation o
26
26
  |---|---|
27
27
  | `.mutate({ id: ... })` | `.mutate({ recordId: ... })` -- `id` causes 404 |
28
28
  | `updateRecord.mutate({ recordId, status: "..." })` — flat payload | `updateRecord.mutate({ recordId, fields: { status: "..." } })` — fields **must** be nested. The flat form can run at runtime but Softr's Action parser doesn't see field references inside it, so the derived Update Action never gets created. The hook's `enabled` stays `false`, the Save button never lights up, the Actions tab in Studio shows "No actions used in this block yet" — all with no error, no warning. The symptom is a button that does nothing and a console log showing `enabled: false, error: null, status: "idle"`. Use the nested form for EVERY update call, even single-field updates (create payloads are flat — see the dedicated row below). See [datasources/writing.md](../datasources/writing.md#critical-the-userecordupdate-payload-shape-and-the-retired-mutate-only-rule) |
29
- | Sequencing multi-row saves with nested `.then()/.catch()` chains, or firing the rows in parallel | `await hook.mutateAsync(row)` per row, in order — header first, then lines; stop on the first failure with renderable retry state; never re-issue completed writes. `mutateAsync` is **fully supported** on the current platform (verified live 2026-08-25 — this supersedes the May 2026 finding that the Action parser only recognized the literal `.mutate(` token; that limitation is gone, and is worth checking only when maintaining an old app whose Action refuses to derive). Full queue pattern: [datasources/writing.md](../datasources/writing.md#sequential-multi-row-writes-mutateasync) |
29
+ | Sequencing multi-row saves with nested `.then()/.catch()` chains, or firing the rows in parallel | `await hook.mutateAsync(row)` per row, in order — header first, then lines; stop on the first failure with renderable retry state; never re-issue completed writes. `mutateAsync` is **fully supported** on the current platform (verified live 2026-08-25 — this supersedes the May 2026 finding that the Action parser only recognized the literal `.mutate(` token; that limitation is gone, and is worth checking only when maintaining an old app whose Action refuses to derive). Full queue pattern: [datasources/writing.md](../datasources/writing.md#sequential-multi-row-writes-mutateasync). One sanctioned exception: independent writes to **different tables** may run in parallel — see [writing.md → Parallel writes across tables](../datasources/writing.md#parallel-writes-across-tables-the-one-sanctioned-parallelism) |
30
30
  | Assuming `mutation.enabled === false` always means a code bug | `enabled` is BOTH a parser signal AND a permissions signal. Per the official Softr docs, "`enabled` reflects user permissions." When code looks correct and the Actions tab shows the action listed, the cause is almost always permissions. Test by switching "Preview as" in Studio to an Owner / admin; if it then works, the issue is permissions. Three places to check, in priority order: (1) the block's **Visibility** tab (right panel), (2) **Studio → Users → Data Restrictions → Global data restrictions** — an app-wide layer that easily gets overlooked because it's hidden under Users (not on the block); it overlays every block in the app, and a single restriction on the target table will silently disable every mutation against that table for the affected user group, (3) the data-source PAT scope — if granted read-only, every write fails regardless of UI permissions. See [datasources/writing.md](../datasources/writing.md#how-actions-work-studios-actions-tab) |
31
31
  | `deleteRecord.mutate({ id: r.id })` | `deleteRecord.mutate(r.id)` -- just the string |
32
32
  | `var { mutateAsync } = useRecordUpdate({...})` -- destructuring the mutate function off the hook | `var updateRecord = useRecordUpdate({...})` -- keep the full object so `.enabled`, `.status`, `.reset()` stay reachable (using `.mutateAsync` itself is fine) |
@@ -105,3 +105,4 @@ Run through this catalog before delivering any block. Every row is a violation o
105
105
  | Refactoring helper shape without updating consumers | Version namespace OR update all consumers in same commit |
106
106
  | Helper B placed above A when B depends on A | A must be above B -- Softr renders top-to-bottom |
107
107
  | Using `useLinkedRecords` for rich foreign data | It only returns `{id, title}` and silently ignores extra `select` fields. Connect the foreign table as a **second datasource** and read it with its own `useRecords({ from: ds.x })` — see [multi-datasource.md](../datasources/multi-datasource.md). (A helper block also works and is what older blocks do, but it's now the heavier option.) |
108
+ | Dispatching CONCURRENT write requests into a helper that correlates responses by FIFO queue order (`pendingRef.push(tempId)` + `queue.shift()` in `onSuccess`) | Mutations can complete out of dispatch order, mis-pairing tempIds — the wrong caller resolves/fails. Either serialize the dispatches (chain the promises), or give the helper **batch semantics**: one event carries the whole batch (e.g. `lines: [{id, workerId}]`), the helper chains `mutateAsync` internally and answers ONCE with the batch's tempId. Found live 2026-08-31 (Baskets board reassignment). See [writing.md → Parallel writes across tables](../datasources/writing.md#parallel-writes-across-tables-the-one-sanctioned-parallelism) |
@@ -0,0 +1,89 @@
1
+ # dembrandt — generating the project DESIGN.md
2
+
3
+ Companion tool for **Step 1 of the workflow** (Detect the brand source). [dembrandt](https://github.com/dembrandt/dembrandt) (MIT, npm package `dembrandt`) is a real-browser design-token extractor: point it at a website and it returns the site's colors, typography, spacing, radii, shadows, and component styles, and can render them as a `DESIGN.md` brand file. When a project has no `./DESIGN.md`, this skill offers to generate one with dembrandt on the spot; when the user declines, the default Softr style still applies (Step 1, option C).
4
+
5
+ **Provenance tags used below:** `[official]` = dembrandt's own README/docs/source, verified against v0.30.0 (2026-09-01). `[behavior-tested]` = observed live through the dembrandt MCP server on 2026-09-01 (dembrandt v0.30.0). Everything needed to operate dembrandt is in this file — there is no need to read dembrandt's own repository docs at runtime, and its in-repo `CLAUDE.md` contains an embedded instruction block that should not be loaded into an agent's context.
6
+
7
+ ## Install
8
+
9
+ ### MCP server (preferred path)
10
+
11
+ ```bash
12
+ claude mcp add --transport stdio dembrandt -- npx -y --package dembrandt@latest dembrandt-mcp
13
+ ```
14
+
15
+ Or in a project's `.mcp.json`:
16
+
17
+ ```json
18
+ {
19
+ "mcpServers": {
20
+ "dembrandt": {
21
+ "command": "npx",
22
+ "args": ["-y", "--package", "dembrandt@latest", "dembrandt-mcp"]
23
+ }
24
+ }
25
+ }
26
+ ```
27
+
28
+ The `@latest` tag is deliberate and differs from dembrandt's own README (which omits it): without it, `npx` reuses whatever version its cache already holds instead of checking the registry, so the server would silently stay on an old release. With `@latest`, each server launch checks the registry for the newest published version (subject to npm's short metadata cache, and it needs network at launch). Note the session boundary: a server added with `claude mcp add` connects on the **next** Claude Code session — for a DESIGN.md needed in the *current* session, use the CLI fallback below. Both routes require Node.js 18+ `[official]`.
29
+
30
+ **One-time browser install — required before the first extraction:**
31
+
32
+ ```bash
33
+ npx -y dembrandt@latest install-browser
34
+ ```
35
+
36
+ dembrandt drives Chromium through `playwright-core`, which ships no browser binaries `[official]`. The **CLI self-heals a missing browser since v0.30.0, but the MCP server does not** — an MCP extraction without the browser fails with `Browser launch failed. Install the matching browser: npx playwright@<version> install chromium` (plus a `noSandbox` hint) `[official, mcp-server source]`. If that error appears, run the install-browser command above (it pins the browser revision to the `playwright-core` dembrandt actually drives — safer than a bare `npx playwright install`), then retry. On Linux/CI, system libraries are separate: `npx playwright@$(node -p "require('playwright-core/package.json').version") install --with-deps chromium`, and pass `noSandbox: true` inside Docker/most CI containers `[official]`.
37
+
38
+ ### CLI fallback (no MCP available)
39
+
40
+ ```bash
41
+ npx -y dembrandt@latest install-browser # once
42
+ npx -y dembrandt@latest <url> --design-md --crawl 5
43
+ ```
44
+
45
+ `--crawl 5` is the CLI's multi-page merge (same reasoning as `pages: 5` below — never ship a client DESIGN.md off a single page); `--sitemap` switches page discovery to sitemap.xml, and explicit paths (`dembrandt example.com /pricing /docs`) mirror the MCP `paths` option `[official]`.
46
+
47
+ The CLI writes to `output/<domain>/DESIGN.md` **relative to the current directory, not to the project root** `[official]` — copy or move it to `./DESIGN.md` afterwards. (The MCP path has no such detour: the tool returns the markdown and the agent writes `./DESIGN.md` directly.)
48
+
49
+ ## The MCP flow, end to end
50
+
51
+ Tools on the server: 7 extraction tools (`get_design_tokens`, `get_color_palette`, `get_typography`, `get_component_styles`, `get_surfaces`, `get_spacing`, `get_brand_identity`), 5 pure analysis tools (`generate_design_md`, `get_findings`, `compute_drift`, `export_dtcg`, `render_report`), 3 job tools (`get_job_status`, `list_jobs`, `cancel_job`) `[official]`. For Step 1 you need exactly this sequence:
52
+
53
+ 1. **`get_design_tokens({ url, pages: 5 })`** → returns `{ job_id, status: "queued" }` immediately `[behavior-tested]`. Async is the default; `sync: true` exists but blocks 15–40s per page `[official]` — prefer polling.
54
+ - **Always crawl multiple pages for client work** (`pages: 3`–`5`; max 20). Merged multi-page extraction produces a "markedly stronger token set" `[official]`, and the single-page noise is real: a live 1-page run returned 46 near-duplicate typography entries, sub-pixel spacing steps (`2.72px`), and a `button-observed` sample with white text on a white background `[behavior-tested]`. `paths: ["/pricing", "/about"]` names extra pages explicitly; `sitemap: true` discovers them from sitemap.xml (alone it takes up to 20 pages — set `pages` to cap it); failed pages are skipped silently `[official]`.
55
+ - Other options: `slow` (3× timeouts, for JS-heavy SPAs), `mobile` (390×844 viewport; default is 1920×1080), `cookie` / `header` (authenticated staging sites), `userAgent`, `noSandbox`. `darkMode` and `wcag` exist **only** on `get_design_tokens` and `get_color_palette`, not the other extraction tools `[official, mcp source]`. Dark mode is never auto-detected — extract it as a second explicit run if the brand ships one `[official]`.
56
+ 2. **Poll `get_job_status(job_id)`** until `status: "completed"` (states: queued / running / completed / failed / cancelled). A 1-page extraction completed in ~60s live `[behavior-tested]`. **On completion the response embeds the full extraction — 65KB for one page** `[behavior-tested]` — so never re-send that payload anywhere: every downstream tool accepts `job_id` and reads the extraction server-side. Completed jobs are kept for **1 hour** `[official]` — finish the flow in-session, or re-extract. If the job **fails**, read the error and pick the remedy: timeout on a JS-heavy site → retry once with `slow: true`; bot protection / Cloudflare → fall back to the CLI with `--browser=firefox` (after `npx -y dembrandt@latest install-browser firefox`); `Browser launch failed` → run the install-browser command from the Install section, then retry.
57
+ 3. **`get_findings(job_id)`** — cheap sanity check before trusting the tokens: WCAG-contrast and consistency lint with severity, plus category scores. The live run flagged 4 no-visual-hierarchy typography collisions and one AA contrast failure in seconds `[behavior-tested]`. Surface anything severe to the user alongside the brand summary.
58
+ 4. **`generate_design_md(job_id)`** — returns the complete DESIGN.md **as tool-result text; it writes no file** `[behavior-tested]`. The agent writes the returned markdown to **`./DESIGN.md` in the project root**, verbatim. Then show the user a short brand summary (name, palette roles, primary font) and continue the workflow with those tokens.
59
+
60
+ Only extract sites the user owns or has permission to analyze — for contracted client work, the client's own website qualifies; respect robots.txt and the site's ToS `[official]`. Sites behind aggressive bot protection (Cloudflare) may time out — the CLI supports `--browser=firefox` for those (`npx -y dembrandt@latest install-browser firefox` first); the MCP server drives Chromium. Canvas/WebGL-rendered sites cannot be analyzed at all (no DOM to read) `[official]`.
61
+
62
+ ## DESIGN.md anatomy (what Step 1 reads)
63
+
64
+ dembrandt emits Google's DESIGN.md draft format (spec 0.4) `[official]`: YAML frontmatter with machine-readable tokens, then ordered markdown sections. Frontmatter keys, each **omitted entirely when there is no extracted evidence** (never filled with invented defaults) `[official]`:
65
+
66
+ | Key | Shape `[behavior-tested]` |
67
+ |---|---|
68
+ | `name` | Site/brand name (`"Dembrandt"`) |
69
+ | `description` | `"Design tokens extracted from <url>"` — **the source URL lives here**; there are no `source:`/`extracted:` fields and no date in the file |
70
+ | `colors` | Semantic roles → uppercase hex: `primary` / `secondary` / `tertiary` / `surface` / `on-surface` (and `error` when detected `[official, emitter source]`) |
71
+ | `typography` | Context-named tokens — `headline-display` (h1), `headline-lg`/`-md`/`-sm`, `label-lg` (buttons), `label-md` (links), `body-md` (body text), with `text-N` as the fallback for unclassified styles `[official, emitter source]` — each with `fontFamily` / `fontSize` / `fontWeight` / `lineHeight` (sometimes `letterSpacing` / `fontFeature`). A noisy single-page run can land everything in the `text-N` fallback `[behavior-tested]` |
72
+ | `spacing` | `base` plus named steps (`xs`…`xxxxl`) |
73
+ | `rounded` | `sm` / `md` / `lg` / `xl` radii, plus `none` (0) and `full` (pill) when observed `[official, emitter source]` |
74
+ | `components` | `button-observed` / `input-observed` with backgroundColor, textColor, rounded, padding (buttons may add `height`) — values may reference other tokens (`"{rounded.lg}"`) |
75
+
76
+ Body sections in order: `# Design System` → Overview → Colors → Typography → Layout (spacing scale + responsive breakpoints) → Elevation & Depth → Shapes → Components. Two body-only nuggets matter for Softr work `[behavior-tested]`:
77
+
78
+ - **Font URLs** (in the Typography section): direct `.woff2` links to the site's real webfonts. The frontmatter `fontFamily` reports the *computed* value, which can be a generic fallback (`ui-sans-serif`) while the Font URLs reveal the actual brand font — cross-check before declaring the brand font, and use these URLs when authoring page-level `@font-face` CSS.
79
+ - Motion tokens exist in dembrandt's JSON extraction but are **not** part of DESIGN.md `[official]` — don't expect an animation section.
80
+
81
+ Legacy note: DESIGN.md files generated before this pipeline (the retired `building-design-md` skill) carry different frontmatter (`brand:`/`source:`/`extracted:`) and extra sections (`Application Patterns`, `tech_stack`). They remain valid brand sources — Step 1 honours whatever token sections are present rather than demanding the dembrandt shape.
82
+
83
+ ## Authoring `custom-code-header.html` from DESIGN.md
84
+
85
+ dembrandt generates **no Softr-ready CSS** — its only CSS export is a CLI-side Tailwind v4 `@theme` file (`--tailwind`), which Softr's Custom Code header has no use for. When the app needs global brand CSS in Softr's **Settings → Custom Code → Code inside header** (webfont loading, `--brand-*` custom properties, native-chrome restyling), the agent authors that CSS from the DESIGN.md: `<link>`/`@font-face` from the Font URLs, custom properties from `colors`. House convention keeps this CSS in a `custom-code-header.html` file in the project folder — see [native-chrome-styling.md](native-chrome-styling.md). The shadow-DOM rules are unchanged: that global CSS reaches native chrome but never the inside of a block ([anti-patterns.md](anti-patterns.md)).
86
+
87
+ ## Optional: brand-drift QA with `compute_drift`
88
+
89
+ After shipping, dembrandt can score how far the published Softr app drifted from the client's brand: extract the client site (baseline) and the published app (candidate), then `compute_drift({ baselineJobId, candidateJobId })` → a 0–100 score (0 = identical), a stable/drift verdict (threshold 10), and per-token changes `[official]`. Rule of thumb: **drift asks "did it change", findings asks "is it good"** `[official]`. Expect *some* structural drift (Softr's chrome contributes tokens the client site lacks) — read the changed-token list, not just the score.
@@ -137,7 +137,7 @@ Value shapes per action type (full detail in SKILL.md's NavigationAction section
137
137
  - `OPEN_PAGE` — `destination` (page path) + `openIn` (`"SELF"` | `"TAB"` | `"MODAL"`)
138
138
  - `OPEN_URL` — `destination` (URL) + `openIn` (`"SELF"` | `"TAB"`)
139
139
  - `OPEN_CHAT` — no destination (mind the data-source-context gotcha in [anti-patterns.md](anti-patterns.md))
140
- - `TRIGGER_CUSTOM_WORKFLOW` — no destination; builder picks the workflow in Studio
140
+ - `TRIGGER_CUSTOM_WORKFLOW` — no destination; builder picks the workflow in Studio (workflow-side receiving end by name: the "Run Custom Workflow action triggered" trigger — name-based match, not wired live; see [softr-mcp.md](softr-mcp.md#workflows))
141
141
 
142
142
  **[verified-undocumented] `action` is accepted as optional.** Softr's setting validator accepts an initialValue with no `action` key — `{ destination: "/", openIn: "SELF" }` alone — and Studio's own AI emits exactly that shape (verified 2026-08-31: a Studio-generated block with three action-less `useNavigationSetting` initialValues, plus three more action-less link values inside an array setting, saved and rendered its Settings pane). Corroborating in-repo evidence that not every key is mandatory: the validator's own error message for `openIn` ends in *"if provided"* (see [anti-patterns.md](anti-patterns.md#editable-settings)). Two consequences:
143
143
 
@@ -1,6 +1,6 @@
1
1
  # Styling Softr's Native Shell (Header · Footer · Page Background) via Custom Code
2
2
 
3
- **This is NOT about Vibe Coding blocks.** Softr's top bar, navigation, dropdown menus, footer, and the page background are part of the *native app shell* — configured in Softr Studio and rendered in the **main document**, not inside a block's shadow DOM. You **cannot** build or replace the native chrome itself as a Vibe Coding block. To re-skin it, add **CSS to Settings → Custom Code → Code inside header** (the same place brand fonts/tokens live, i.e. the `custom-code-header.html` produced by `building-design-md`). Pure CSS — no markup, no JS — and the native chrome stays in place, so Softr's auth-aware nav (account menu, sign-out, user-group gating) keeps working. (Separate pattern, different problem: a landing page with the native header **hidden** can carry a block-owned in-block header — see [Restyle vs. replace vs. block-owned header](#restyle-vs-replace-vs-block-owned-header).)
3
+ **This is NOT about Vibe Coding blocks.** Softr's top bar, navigation, dropdown menus, footer, and the page background are part of the *native app shell* — configured in Softr Studio and rendered in the **main document**, not inside a block's shadow DOM. You **cannot** build or replace the native chrome itself as a Vibe Coding block. To re-skin it, add **CSS to Settings → Custom Code → Code inside header** (the same place brand fonts/tokens live house convention keeps that CSS in a `custom-code-header.html` file in the project folder and pastes it into the setting; you author it from the project's DESIGN.md tokens, since dembrandt supplies the palette and webfont URLs but generates no Softr-ready CSS — see [dembrandt.md](dembrandt.md#authoring-custom-code-headerhtml-from-designmd)). Pure CSS — no markup, no JS — and the native chrome stays in place, so Softr's auth-aware nav (account menu, sign-out, user-group gating) keeps working. (Separate pattern, different problem: a landing page with the native header **hidden** can carry a block-owned in-block header — see [Restyle vs. replace vs. block-owned header](#restyle-vs-replace-vs-block-owned-header).)
4
4
 
5
5
  This doc covers the **header / nav / dropdowns**, the **footer**, the **floating "island" treatment** for both, and the **page background** — which is trickier than it looks, because Softr stacks the same fill on several layers.
6
6
 
@@ -1,6 +1,8 @@
1
1
  # Softr MCP Server
2
2
 
3
- The official Softr MCP server (`https://mcp.softr.io/mcp`) gives an AI assistant (Claude Code, Claude Desktop, claude.ai, Cursor, ChatGPT, Mistral) direct access to a Softr **workspace**: databases, applications, vibe coding blocks, integrations (external data sources), and workflows. Everything the assistant does happens as the connected user, with their permissions, and shows up in Studio like any other change.
3
+ The official Softr MCP server (`https://mcp.softr.io/mcp`) gives an AI assistant (Claude Code, Claude Desktop, claude.ai, Cursor, ChatGPT, Mistral) direct access to a Softr **workspace**: databases, applications, vibe coding blocks, integrations (external data sources), and workflows. On this workspace server, everything the assistant does happens as the connected user, with their permissions, and shows up in Studio like any other change.
4
+
5
+ **There are now TWO server classes.** Besides the workspace server above, Softr ships **per-application MCP servers** — one server per published app, exposing that app's data (and only what the app's pages actually use) through the app's own permission model. Different tools, different schema doc, and (apparently) a different identity model. See [Per-application MCP servers](#per-application-mcp-servers). (Live-enumerated 2026-08-31; roster facts below marked "roster-verified" mean the tool exists — its behavior was not necessarily exercised.)
4
6
 
5
7
  **This file is a sibling concern to the [../datasources/](../datasources/) guides, which cover in-block data fetching (`useRecords` + `q.select()`).** The MCP runs at chat-build time, not inside the block. For Vibe Coding work it matters twice: it answers "what fields does this table have?" without any paste-ins, and it can create and deploy the block itself — no copy-paste into Studio.
6
8
 
@@ -14,8 +16,11 @@ The official Softr MCP server (`https://mcp.softr.io/mcp`) gives an AI assistant
14
16
  - [Vibe coding block tools](#vibe-coding-block-tools)
15
17
  - [Adopting Studio-AI-generated code](#adopting-studio-ai-generated-code)
16
18
  - [Vibe coding gotchas (official)](#vibe-coding-gotchas-official)
19
+ - [Application management tools](#application-management-tools)
17
20
  - [Browsing integrations (external data sources)](#browsing-integrations-external-data-sources)
18
21
  - [Softr Database tools](#softr-database-tools)
22
+ - [Workflows](#workflows)
23
+ - [Per-application MCP servers](#per-application-mcp-servers)
19
24
  - [Two delivery paths for this skill](#two-delivery-paths-for-this-skill)
20
25
  - [When the MCP is not installed](#when-the-mcp-is-not-installed)
21
26
 
@@ -23,11 +28,11 @@ The official Softr MCP server (`https://mcp.softr.io/mcp`) gives an AI assistant
23
28
 
24
29
  | Area | What the assistant can do | Official docs |
25
30
  |---|---|---|
26
- | Databases | Query, filter, aggregate; create/update records; build tables and fields | https://docs.softr.io/mcp/databases |
27
- | Applications | Read apps, pages, blocks, permissions; preview; publish | https://docs.softr.io/mcp/apps |
31
+ | Databases | Query, filter, aggregate; create/update **and delete** records; build **and delete** tables, fields, databases | https://docs.softr.io/mcp/databases |
32
+ | Applications | **Create whole apps**; manage app users and login settings; swap an app's data source; read apps, pages, blocks, permissions, user groups; preview; publish — see [Application management tools](#application-management-tools) | https://docs.softr.io/mcp/apps |
28
33
  | Vibe coding blocks | Create and edit blocks, manage settings, visibility, versions, data source connections | https://docs.softr.io/mcp/vibe-coding |
29
34
  | Integrations | Browse external data sources connected to the workspace, down to field level | https://docs.softr.io/mcp/integrations |
30
- | Workflows | Build, test, and publish workflows | https://docs.softr.io/mcp/workflows |
35
+ | Workflows | Build, wire, test, and publish workflows — 26 tools and a 418-node trigger/action catalog; see [Workflows](#workflows) | https://docs.softr.io/mcp/workflows |
31
36
 
32
37
  `list_workspaces` is often the first call — it turns "my Sales workspace" into the workspace ID every other tool needs.
33
38
 
@@ -74,6 +79,7 @@ Before writing any block code through the MCP, call `get_vibe_coding_docs` — i
74
79
  | Settings / visibility | `update_vibe_coding_block_settings`, `set_vibe_coding_block_visibility`, `set_vibe_coding_block_action_visibility` |
75
80
  | Versions | `list_vibe_coding_block_versions`, `restore_vibe_coding_block_version`, `duplicate_vibe_coding_block_from_version` |
76
81
  | Data sources | `connect_vibe_coding_block_data_source`, `disconnect_vibe_coding_block_data_source`, `set_vibe_coding_block_data_source_sort`, `set_vibe_coding_block_data_source_record_filters` |
82
+ | Any block | `get_block` (roster-verified 2026-08-31; presumed to read any block type, not just vibe blocks — unconfirmed by a live call on a native block) |
77
83
 
78
84
  Editable settings via MCP are the same fields as the block's **Content → Settings** panel; sort and record filters are the same as the **Source** tab. Duplicating from a version is the safe way to try an alternative — the original keeps working while you experiment on the copy.
79
85
 
@@ -99,6 +105,24 @@ From the official MCP docs — these hold for MCP-driven and Studio-driven edits
99
105
  - **Changing the code resets action permissions.** Any code change rebuilds the block's record actions at default visibility — restrictions to user groups must be re-applied. (This is Hard Constraint 21 in SKILL.md, now officially documented: tighten Action permissions only after the LAST redeploy.)
100
106
  - **A block with an unconnected data source saves without complaint**, then errors when the page loads. If a freshly created block looks broken but the code seems right, check its data source connection first.
101
107
 
108
+ ## Application management tools
109
+
110
+ The Applications area goes well beyond reads (all roster-verified 2026-08-31; behavior not individually exercised):
111
+
112
+ | Group | Tools |
113
+ |---|---|
114
+ | Apps | `list_applications`, `get_application`, `create_application` (create a whole app via MCP), `update_application_data_source` (point/swap the app's data source), `set_application_login` |
115
+ | App users | `add_application_user`, `remove_application_user`, `list_user_groups` |
116
+ | Pages / blocks / permissions | `list_pages`, `get_page`, `get_page_permissions`, `get_access_control`, `get_block` |
117
+ | Publish / preview | `preview_app`, `publish_app` |
118
+ | Workspace | `list_workspaces`, `get_workspace_integrations` (distinct from the [integrations drill-down](#browsing-integrations-external-data-sources) below) |
119
+
120
+ Combined with the database tools (`create_database` / `create_table` / `create_field`) and `create_vibe_coding_block` + `publish_app`, the tool set for scaffolding a full app end to end now exists. (Existence-verified only — that pipeline hasn't been run live; treat the first full scaffold as an experiment, not a routine.)
121
+
122
+ **Etiquette from the server's own instructions:** after changing a block, link the page as `https://studio.softr.io/applications/{applicationId}/pages/{pageId}`; offer `preview_app` or `publish_app`, but **only publish when the user asks**.
123
+
124
+ > **preview_app links are auth tokens.** Per the server's own instructions, a preview link **signs its opener in as the user who requested it** and lasts about a day. Give it only to that user, and mint a fresh one with another `preview_app` call rather than re-sending an old link. Never paste a preview link into a shared channel.
125
+
102
126
  ## Browsing integrations (external data sources)
103
127
 
104
128
  An integration is an external data source connected once per workspace (the builder says "integrations", the tools say "data sources" — same thing). Five read-only tools drill down from workspace to fields; each level needs an ID from the level above:
@@ -124,19 +148,25 @@ Getting this wrong **fails silently** — the code compiles, saves, and looks ri
124
148
 
125
149
  ## Softr Database tools
126
150
 
127
- For Softr's native databases the MCP goes far beyond browsing: `get_schema` (authoritative field-type + filter-operator reference — call it before building tables or filters), database/table/field CRUD, `list_views`, record reads (`list_records`, `search_records`, `get_record`), record writes (`create_record`, `create_records` batch, `update_record`), and `aggregate_data` for grouped summaries.
151
+ For Softr's native databases the MCP goes far beyond browsing: `get_schema` (authoritative field-type + filter-operator reference — call it before creating/updating tables, fields, or filters; the server's own instructions say "do not guess field types, options, or operators"), database/table/field CRUD **including deletes** (`delete_database`, `delete_table`, `delete_field`), `list_views`, record reads (`list_records`, `search_records`, `get_record`), record writes (`create_record`, `create_records` batch, `update_record`, `delete_record`, `delete_records` batch), and `aggregate_data` for grouped summaries.
152
+
153
+ **Call economy (from the server's own instructions):** `get_table` returns a table's metadata AND all its field definitions in one call; `list_fields` returns the fields alone. Call ONE of them once per table and reuse the result — never both. (Sensible extension: re-fetch only after you changed the table's fields yourself.)
154
+
155
+ **get_schema, live-confirmed 2026-08-31:** `readOnlyFieldTypes` = AUTONUMBER, COUNT, CREATED_AT, CREATED_BY, FORMULA, LOOKUP, RECORD_ID, ROLLUP, UPDATED_AT, UPDATED_BY (matches this file's long-standing claim verbatim). The `LINKED_RECORD` value example is `["record-id-1", "record-id-2"]` — independently corroborating the verified string-array write shape in [../datasources/softr-database.md](../datasources/softr-database.md). Operator families include relative-date `IS_WITHIN` / `IS_NOT_WITHIN` ("last 7 days"), ternary `IS_BETWEEN` / `IS_NOT_BETWEEN`, and `AND`/`OR` composites. **Schema-drift caution:** the workspace server's `get_schema` and the [per-application servers'](#per-application-mcp-servers) `get_schema` have drifted — the per-app catalog lists creatable types the workspace one omits (ADDRESS, PROGRESS, TIME, DATE_RANGE, BUTTON), and even the operator NAMES differ between server kinds (workspace `GREATER_THAN` / `DOES_NOT_CONTAIN` vs per-app `GT` / `DOES_NOT_CONTAINS`) — so filter payloads are not portable between them. Always call `get_schema` on the server you are actually using.
128
156
 
129
157
  Known limits and behaviors (per official docs):
130
158
 
131
159
  - Record field keys are **field IDs**, not labels — `list_fields` maps between them.
132
160
  - Computed fields (formula, lookup, rollup, count) and system fields (created/updated time and by, autonumber, record ID) are read-only; a field's type cannot be changed after creation.
133
- - **Nothing can be deleted through the MCP yet** — no record/table/field/database delete tools (docs say deletion is coming). Deletions happen in the builder.
161
+ - **Deletion now exists** (supersedes the earlier "nothing can be deleted through the MCP yet" finding): `delete_record`, `delete_records` (batch), `delete_field`, `delete_table`, and `delete_database` are all in the roster (verified 2026-08-31), and per-app servers add `delete_record` + `batch_delete_records`. Roster-verified only — no destructive call was made, so which Databases permission level gates them and how cascades behave (e.g. deleting a table with linked records) are untested. Treat every delete as irreversible; no soft-delete is documented.
134
162
  - **Attachment writes take a URL and copy the file.** `create_record` / `update_record` accept
135
163
  `{ filename, url }` on an ATTACHMENT field with any publicly reachable URL; Softr fetches it, stores its
136
164
  own copy and generates thumbnails, so backfilling images from another system is one write per record
137
165
  with no upload step. Verified 2026-08-26 — see [../datasources/writing.md](../datasources/writing.md#attachment).
138
166
  - **`update_field` silently ignores `allowMultipleEntries` nested inside `options`** — it is a TOP-LEVEL
139
- field property; the call succeeds and changes nothing (verified 2026-08-26). To flip a LINKED_RECORD
167
+ field property; the call succeeds and changes nothing (verified 2026-08-26; the server has grown
168
+ substantially since — deletes added by 2026-08-31 — so this write surface is worth a re-test when
169
+ next touched live). To flip a LINKED_RECORD
140
170
  field between single and multi, `PUT` it via the Tables API with `allowMultipleEntries` at top level —
141
171
  and always echo `options.inverseLinkFieldId` in that PUT, because omitting it severs the inverse
142
172
  pairing. Full write-up, including the silent on-write clobbering of single-valued link pairs and the
@@ -146,12 +176,56 @@ Known limits and behaviors (per official docs):
146
176
 
147
177
  Typical Vibe Coding uses: "list every field on `Wigs` with id, name, type, and dropdown options", "what's the option id for `Payment status` = 'Partially paid'?", "show 3 sample records so we know value shapes", "verify the field id in my `q.select()` exists". This eliminates the field-id-typo / wrong-option-uuid class of bugs entirely.
148
178
 
179
+ ## Workflows
180
+
181
+ Softr Workflows are automations built from trigger + action nodes, and the MCP can build, wire, test, and publish them — a **26-tool suite** (roster-verified 2026-08-31; `list_node_types` called live, the build/publish loop itself not yet exercised end to end):
182
+
183
+ | Group | Tools |
184
+ |---|---|
185
+ | Workflow lifecycle | `create_workflow`, `get_workflow`, `get_workflow_url`, `list_workflows`, `rename_workflow`, `update_workflow_configuration`, `publish_workflow`, `unpublish_workflow` |
186
+ | Node management | `add_node`, `add_branch_node`, `create_branch`, `delete_node`, `duplicate_node`, `rename_node`, `reorder_node`, `reorder_multiple_nodes`, `replace_node`, `replace_trigger_node`, `update_node_inputs`, `update_node_note`, `update_node_continue_on_error` |
187
+ | Discovery / testing | `list_node_types`, `get_node_specifications`, `get_dynamic_input_options`, `test_node`, `get_node_output` |
188
+
189
+ **The node catalog is huge** — live-enumerated 2026-08-31: **418 node types (58 triggers + 360 actions) across 56 applications.** The parts that matter most for this skill:
190
+
191
+ - **Softr-native triggers:** one-time + recurring schedules, `WEBHOOK` (inbound webhook), `SOFTR_EMAIL_RECEIVED` (inbound email! — delivery mechanism not captured), Softr Databases record events (added / updated / deleted / meets-conditions / enters-view / "run custom workflow on selected records"), Softr Apps events (Add Record form submitted, Edit Record form submitted, form submitted, user added, comment added) — and **"Run Custom Workflow action triggered"**, which by its name is the receiving end of the vibe block's `TRIGGER_CUSTOM_WORKFLOW` NavigationAction (name-based inference; the pairing has not been wired live). See SKILL.md's NavigationAction action-types list.
192
+ - **Softr-native actions:** `BRANCH`, `FILTER`, `WAIT`, `LOOP_ACTION_GROUP` (run each list item through the same steps), `SOFTR_SEND_EMAIL`, `CALL_API` (REST), `WEBPAGE_SCRAPPER`, `PDF_TO_TEXT`, `COMPRESS_FILES` (zip + download link), `TRANSFORM_DATA`, `RESPONDED_TO_WEBHOOK` (custom HTTP response to the webhook caller); Softr DB record CRUD incl. bulk update/delete and find; Softr Apps user management (find / create / delete / deactivate / activate / invite user, send push notification).
193
+ - **`CUSTOM_CODE`:** runs custom **JavaScript or Python** inside a workflow.
194
+ - **AI actions:** Softr AI, OpenAI, Anthropic, Gemini, and Mistral each ship Write / Summarize / Categorize / Custom-prompt nodes; OpenAI adds gpt-image-2 image generation. Pinecone, Firecrawl, Replicate, and Linkup nodes exist too.
195
+ - **Integration apps (top of 56):** Stripe (36 nodes), QuickBooks (24), ActiveCampaign (23), SharePoint (22), Asana (18), Gmail/Attio/Brevo/Resend (12 each), ClickUp/Zendesk (10), Airtable/Notion/Cal.com/HubSpot/Xero/DocuSign/Apollo (9 each), Sheets/Excel (8), monday/SQL/Jira (7), Slack/Telegram (6), plus Salesforce, Coda, Calendly, Twilio, Zoom, Linear, Trello, form tools (Typeform/Tally/Jotform/Fillout), and more.
196
+
197
+ **Mechanics from the server's own instructions:**
198
+
199
+ - Node inputs can embed **references to another node's runtime output**, a loop's current item, or named date/time tokens.
200
+ - **Test-first is mandated:** every testable node needs a test run before its outputs become referenceable by downstream nodes. Each node carries a `testRunMode` — `REAL_ONLY`, `MOCK_ONLY`, or `MOCK_AND_REAL` — so some nodes can only be tested against real side effects while others mock.
201
+ - **Workflows are workspace-level, not part of an app**: `preview_app` / `publish_app` do not apply. Link a workflow as `https://studio.softr.io/workflow/{workflowId}`.
202
+
203
+ **Why this matters to block work:** Softr Workflows are now the Softr-native answer to the "block writes to its own table, backend cascades the rest" pattern — for **Softr Database backends** what [airtable-automations.md](airtable-automations.md) is for Airtable backends. See the cross-table alternatives in [../datasources/writing.md](../datasources/writing.md#cross-table-operations).
204
+
205
+ ## Per-application MCP servers
206
+
207
+ A separate product class from the workspace server (live-observed 2026-08-31 on two connected app servers): **one MCP server per published Softr app**, exposing that app's data to MCP clients. How these servers are provisioned/connected was not captured — check the app's settings in Studio or the official docs when setting one up.
208
+
209
+ **Tools (12):** `list_tables`, `describe_table`, `get_schema`, `get_records`, `get_record`, `get_linked_records`, `get_current_user`, `create_record`, `update_record`, `delete_record`, `batch_update_records`, `batch_delete_records`.
210
+
211
+ **Live-observed semantics:**
212
+
213
+ - **The table catalog is derived from the app itself.** `list_tables` returns only tables the app's pages actually use, each with an `operations` array (`read` / `create` / `update` / `delete`) mirroring the app's configured actions — read-only tables genuinely appear read-only. Each table also carries `context.pages` (which app pages use it) and `operationLabels` (the app's actual button labels: "Add record", "Edit", "Delete").
214
+ - An app may connect **multiple distinct data sources**; always call `list_tables` first for the full catalog before concluding data doesn't exist.
215
+ - `get_current_user` exists, and combined with the action-scoped catalog this implies the server operates in an **app-user context** rather than the builder identity the workspace server uses (inferred — not confirmed by a live `get_current_user` call).
216
+ - **Field keys in records are field IDs, not labels** — same rule as everywhere in Softr DB land; on these servers the mapping tool is `describe_table` (not `list_fields`).
217
+ - Its `get_schema` returns a **richer field-type catalog than the workspace server's**: creatable types add ADDRESS, PROGRESS, TIME, DATE_RANGE, BUTTON; SELECT documents `choices: array<{id, label, color}>` + `allowToAddNewChoice`; LONG_TEXT documents TEXT|HTML|MARKDOWN formats; ATTACHMENT documents `fileType` + `showAs PREVIEW|BADGE`; PERCENT documents `showAs NUMBER|PROGRESS_BAR|PROGRESS_RING`. Read-only list matches the workspace server.
218
+ - **Documented conventions** (unlike the workspace server's silent caps): pagination is `page` (1-based) + `pageSize` (max 100) with `total` and `hasMore` in the response; timestamps are UTC `yyyy-MM-dd'T'HH:mm:ss.SSS'Z'` for reads AND writes; errors return `{code, error, suggestion}` with machine-readable codes (NOT_FOUND, VALIDATION_ERROR, PERMISSION_DENIED, INVALID_REQUEST, INTERNAL_ERROR). Do not assume the workspace server's limits (200-record silent read cap, etc.) transfer here, or vice versa.
219
+ - Filter operators use the per-app naming (`GT`/`LT`/`GTE`/`LTE`, `DOES_NOT_CONTAINS`, `IS_WITHIN` relative dates, `IS_ONE_OF`/`IS_NONE_OF`, `HAS_ALL_OF`/`HAS_NONE_OF` (the latter flagged legacy in the schema), `INLINE_CONTAINS`) with per-operator supported-type lists — see the schema-drift caution in [Softr Database tools](#softr-database-tools).
220
+
221
+ **What they are NOT:** a delivery path for blocks. Per-app servers serve a published app's **data** at runtime; they cannot create, edit, or deploy vibe coding blocks — that stays on the workspace server.
222
+
149
223
  ## Two delivery paths for this skill
150
224
 
151
225
  When generating a block, pick the delivery path by what's connected:
152
226
 
153
- 1. **MCP connected with Applications & Forms full access** — write the `.tsx` file locally first (it remains the source of truth and the reviewable artifact), then offer to deploy it directly: `create_vibe_coding_block` (or `update_vibe_coding_block_code` for edits), then `connect_vibe_coding_block_data_source` to wire up the data. Remember the action-permissions reset gotcha after every code push.
154
- 2. **No MCP (or read-only access)** — classic path: write the `.tsx` file and have the user paste it into Studio's Vibe Coding editor, then connect the data source in the **Source** tab themselves.
227
+ 1. **WORKSPACE MCP server connected with Applications & Forms full access** (a per-application server does not count it cannot create or deploy blocks; see the section above) — write the `.tsx` file locally first (it remains the source of truth and the reviewable artifact), then offer to deploy it directly: `create_vibe_coding_block` (or `update_vibe_coding_block_code` for edits), then `connect_vibe_coding_block_data_source` to wire up the data. Remember the action-permissions reset gotcha after every code push. After deploying, link the Studio page (`https://studio.softr.io/applications/{applicationId}/pages/{pageId}`) and offer `preview_app` / `publish_app` — publish only when asked, and mind the [preview-link auth warning](#application-management-tools).
228
+ 2. **No workspace MCP (or read-only access)** — classic path: write the `.tsx` file and have the user paste it into Studio's Vibe Coding editor, then connect the data source in the **Source** tab themselves.
155
229
 
156
230
  Either way, never deliver code inline in chat (JSX character corruption — see SKILL.md workflow step 5).
157
231