softr-vibe-coding 2.10.0 → 2.11.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,9 @@ 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.11.0] - 2026-10-01
8
+ - Track Softr's 2026-10-01 MCP release: renamed tools, sourceSha256 push checks, stub-tool root cause
9
+
7
10
  ## [2.10.0] - 2026-09-30
8
11
  - Add the print-in-a-new-window rule and references/printing.md (verified live 2026-09-30)
9
12
  - Add right-edge placement and the schema-less workspace-tool correction (2026-09-30)
package/README.md CHANGED
@@ -194,7 +194,9 @@ softr-vibe-coding/
194
194
  │ │ # MCP servers, auth, permissions; what the server
195
195
  │ │ # enforces on block data endpoints, "Preview as"
196
196
  │ │ # role testing, search-replace on 100KB+ blocks
197
- │ │ # (Sep 18 2026)
197
+ │ │ # (Sep 18 2026); Oct 1 2026: tool rename map,
198
+ │ │ # push verification by sourceSha256, stub-tool
199
+ │ │ # root cause, update_field/update_table fixes
198
200
  │ ├── advanced-integrations.md # Shadow DOM CSS isolation
199
201
  │ │ # Leaflet, Mapbox, TinyMCE, Quill, FullCalendar
200
202
  │ ├── native-chrome-styling.md # Restyle Softr's native shell (header, footer,
@@ -316,7 +318,7 @@ The skill enforces these automatically, but good to know (verified live against
316
318
  - Create payloads are **flat**; update payloads are `{ recordId, fields: {...} }` — asymmetric by design
317
319
  - `mutateAsync` is fully supported — it's the tool for sequential multi-row saves
318
320
  - SELECT fields write by option **label string**; linked records write as arrays of record-id strings
319
- - Every code recompile resets the block's auto-registered Actions to default permissions — tighten permissions after the last redeploy, and note there is **no cosmetic-edit exemption**: an edit that changes only a comment resets them too. **Always read the permissions back to confirm** — the default for a create action is `ALL_USERS` (publicly writable), and the MCP call that re-tightens it can fail with no fallback
321
+ - Every code recompile resets the block's auto-registered Actions to default permissions — tighten permissions after the last redeploy, and note there is **no cosmetic-edit exemption**: an edit that changes only a comment resets them too. **Always read the permissions back to confirm** — a create action defaults to the block's own visibility, so on a block everyone can see it comes back publicly writable, and the MCP call that re-tightens it can fail with no fallback
320
322
  - **Blocks cannot import each other**, so two blocks that must look alike will drift — each one looks correct in isolation while the set does not. Repeated page chrome (back button, title, primary action) must sit at the same offset on every page, and a loading skeleton must track the REST state of whatever it stands in for
321
323
  - No `import React from 'react'` — use named imports (`import { useState } from "react"`)
322
324
  - Must use `export default function Block()`
package/SKILL.md CHANGED
@@ -20,7 +20,7 @@ allowed-tools: Read Write Glob Grep Bash
20
20
 
21
21
  # Softr Vibe Coding Block Generator
22
22
 
23
- You generate complete, production-ready Softr Vibe Coding blocks as TypeScript React files. A Vibe Coding block is a single file with a default-exported React component, compiled by Softr's server and run in the browser inside a Softr app. The current platform compiles TypeScript with modern syntax — optional chaining (`?.`), nullish coalescing (`??`), arrow functions, `const`, generics — plus shadcn/ui from `@/components/ui/*`, lucide-react, sonner, and date-fns (verified live against the builder MCP's `get_vibe_coding_docs` and a 15-block production deployment, 2026-08-25).
23
+ You generate complete, production-ready Softr Vibe Coding blocks as TypeScript React files. A Vibe Coding block is a single file with a default-exported React component, compiled by Softr's server and run in the browser inside a Softr app. The current platform compiles TypeScript with modern syntax — optional chaining (`?.`), nullish coalescing (`??`), arrow functions, `const`, generics — plus shadcn/ui from `@/components/ui/*`, lucide-react, sonner, and date-fns (verified live against the builder MCP's `vibe_coding_block_get_docs` and a 15-block production deployment, 2026-08-25).
24
24
 
25
25
  > **Scope note — blocks vs. native chrome.** A block is page *content*, rendered inside a shadow DOM. Softr's global **header / top bar / nav / dropdown menus** are native chrome (configured in Studio, rendered in the main document) — you **cannot** build or replace them as a block. To restyle them, add CSS to Settings → Custom Code → Code inside header. See [references/native-chrome-styling.md](references/native-chrome-styling.md). One nuance: on a landing page where the native header is **hidden**, a hero block CAN render its own fixed in-block header (`position: fixed` inside the shadow root anchors to the viewport — verified 2026-08-31 from Softr's own Studio-AI output); pattern + caveats in [references/static-blocks.md](references/static-blocks.md#block-owned-landing-page-header).
26
26
 
@@ -65,7 +65,7 @@ You generate complete, production-ready Softr Vibe Coding blocks as TypeScript R
65
65
 
66
66
  5. **Write the complete block file** (`.tsx` preferred; `.jsx` also compiles) to the project sub-folder and tell the user the full path. Create the sub-folder if it doesn't exist yet. The file must be fully self-contained, **visually polished from the first version**, and ready to paste into Softr's Vibe Coding editor. Styling is not an afterthought -- it ships in v1. **Never deliver code inline in chat.** Copy-pasting JSX from chat corrupts characters (`>`, `>=`, `=>`, quotes), causing compilation errors that are hard to debug. Always write to a file.
67
67
 
68
- **Delivery path:** if the official Softr MCP server is connected with Applications & Forms full access, offer to deploy the block directly after writing the file — `create_vibe_coding_block` (or `update_vibe_coding_block_code` for edits) plus `connect_vibe_coding_block_data_source` to wire the data. The local `.tsx` file stays the source of truth. Remember: every code push resets the block's Action permissions to defaults (Hard Constraint 21), and a block whose data source isn't connected saves fine but errors at page load. Details in [references/softr-mcp.md](references/softr-mcp.md).
68
+ **Delivery path:** if the official Softr MCP server is connected with Applications & Forms full access, offer to deploy the block directly after writing the file — `vibe_coding_block_create` (or `vibe_coding_block_update_code` for edits) plus `vibe_coding_block_connect_data_source` to wire the data. The local `.tsx` file stays the source of truth. Remember: every code push resets the block's Action permissions to defaults (Hard Constraint 21), and a block whose data source isn't connected saves fine but errors at page load. Details in [references/softr-mcp.md](references/softr-mcp.md).
69
69
 
70
70
  6. **Self-validate before delivering.** Before presenting the code as complete, verify. (Data-hook items apply only to data-connected blocks; static marketing blocks swap in the checklist deltas from [references/static-blocks.md](references/static-blocks.md#workflow-deltas).)
71
71
  - Every data hook is called with an **inline options object literal** — `useRecords({ ... })` written through a variable or wrapper function fails to compile (verified live 2026-08-25). Share `q.select` mappings between hooks, never whole options objects
@@ -95,7 +95,7 @@ You generate complete, production-ready Softr Vibe Coding blocks as TypeScript R
95
95
  - 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))
96
96
  - Array-setting rows keyed by **index**, never by a builder-editable field value
97
97
  - Media settings that may start empty (`src: ""`) gated with a conditional render or placeholder — never an unconditional `<img src={setting.src}>`
98
- - **Deploying through the MCP:** `errors: null` on a push is not proof — fetch the block's `sourceCode` back and byte-compare it to the file you sent, trailing newline included (Softr stores exactly what it receives; the one-byte drift we once blamed on it was a chunked read on our side), and prove deployed == disk *before* editing so a Studio-side change is never overwritten. Protocol in [references/softr-mcp.md → Verifying a push](references/softr-mcp.md#verifying-a-push--the-deployed-source-is-the-only-proof)
98
+ - **Deploying through the MCP:** `errors: null` on a push is not proof — compare the push result's `sourceSha256` with `shasum -a 256` of the file you sent, every byte counted, trailing newline included (Softr stores exactly what it receives; the one-byte drift we once blamed on it was a chunked read on our side). Prove deployed == disk *before* editing the same way, with `vibe_coding_block_get_code` and `includeCode: false`, so a Studio-side change is never overwritten. Fetch the full `sourceCode` only when a digest is missing or the hashes differ. Protocol in [references/softr-mcp.md → Verifying a push](references/softr-mcp.md#verifying-a-push--the-deployed-source-is-the-only-proof)
99
99
 
100
100
  ## What to Clarify
101
101
 
@@ -103,7 +103,7 @@ When the user describes their block, figure out which of these areas apply and a
103
103
 
104
104
  - **Data source type**: Is it Airtable, Softr Database, REST API, or another source? This determines the data fetching approach. **Load the relevant data source guide** from the [datasources/](datasources/) directory before writing code.
105
105
  - **Data source fields**: For Airtable/Softr Database, you need actual field IDs. For REST APIs, you access the raw API response directly. If the user doesn't know field IDs:
106
- - For **Softr Database**, the cleanest path is the official **Softr MCP server** — ask whether they have it installed (`claude mcp list` shows it as `softr` or similar). If yes, query schema directly with the MCP tools instead of asking for paste-ins. The same server also browses connected **Airtable, Google Sheets, Notion, and Supabase** integrations down to field level (`list_data_sources` → ... → `list_data_source_table_fields`), so prefer it for those sources too when available — see [references/softr-mcp.md](references/softr-mcp.md). If no MCP, the next-best option for Softr DB is the bundled **`get-softr-database` CLI script** — tell the user to run `python3 ~/.claude/skills/softr-vibe-coding/tools/get-softr-database.py <database_id>` (it prompts for their Softr API key and exports the full schema to `~/Desktop/softr-database-<id>-<timestamp>.json` — Python stdlib only, nothing to install) and paste the resulting JSON into chat. As a final fallback, ask them to paste the `tablespace-with-tables` network response (DevTools -> Network -> filter that string while on Studio's Data tab) — same JSON content, different acquisition path. Optionally tell them they can install the MCP once with `claude mcp add --transport http softr https://mcp.softr.io/mcp` for future sessions. Full MCP details in [references/softr-mcp.md](references/softr-mcp.md); CLI script details in [datasources/softr-database.md](datasources/softr-database.md#bundled-cli-script-get-softr-database); fallback paste-in workflows in [datasources/fields.md](datasources/fields.md#field-inspector-block).
106
+ - For **Softr Database**, the cleanest path is the official **Softr MCP server** — ask whether they have it installed (`claude mcp list` shows it as `softr` or similar). If yes, query schema directly with the MCP tools instead of asking for paste-ins. The same server also browses connected integrations down to field level — **Airtable, Google Sheets, Notion, Supabase**, and per its tool descriptions (2026-10-01) many more, from Excel and monday to HubSpot and SQL databases (`integration_list` → ... → `integration_list_table_fields`) — so prefer it for those sources too when available — see [references/softr-mcp.md](references/softr-mcp.md). If no MCP, the next-best option for Softr DB is the bundled **`get-softr-database` CLI script** — tell the user to run `python3 ~/.claude/skills/softr-vibe-coding/tools/get-softr-database.py <database_id>` (it prompts for their Softr API key and exports the full schema to `~/Desktop/softr-database-<id>-<timestamp>.json` — Python stdlib only, nothing to install) and paste the resulting JSON into chat. As a final fallback, ask them to paste the `tablespace-with-tables` network response (DevTools -> Network -> filter that string while on Studio's Data tab) — same JSON content, different acquisition path. Optionally tell them they can install the MCP once with `claude mcp add --transport http softr https://mcp.softr.io/mcp` for future sessions. Full MCP details in [references/softr-mcp.md](references/softr-mcp.md); CLI script details in [datasources/softr-database.md](datasources/softr-database.md#bundled-cli-script-get-softr-database); fallback paste-in workflows in [datasources/fields.md](datasources/fields.md#field-inspector-block).
107
107
  - 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).
108
108
  - For other non-Softr-DB sources where empty `q.select({})` works, suggest the Field Inspector block.
109
109
  - **Brand colors**: Already resolved in Step 1 (Detect the brand source). Don't re-ask. The brand source is one of:
@@ -179,7 +179,7 @@ For advanced patterns beyond data fetching, load the relevant reference when the
179
179
  | **Printing** anything from a block — always a new window/tab holding its own document, never `window.print()` on the page or an in-page print view: the escaped HTML builder, pop-up-safe opening from the click, print-when-ready (stylesheets, fonts and images, capped), Print disabled until the data has loaded, the `?print=1` deep link from another page, paper layout (shared `<colgroup>`, `vertical-align: middle`, tick boxes) | [references/printing.md](references/printing.md) |
180
180
  | Small reusable patterns — `localStorage` cross-page state, clipboard copy button | [references/common-patterns.md](references/common-patterns.md) |
181
181
  | 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) |
182
- | 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) |
182
+ | The official **Softr MCP server** — Softr DB schema + full record/table/field/database CRUD (deletes included), field-level browsing of connected integrations (Airtable / Google Sheets / Notion / Supabase and more), **creating, editing, versioning, and deploying Vibe Coding blocks directly** (`vibe_coding_block_get_docs`, `vibe_coding_block_create`, ...), push verification by `sourceSha256`, app management/scaffolding, the **Softr Workflows** suite (28 tools, 418-node catalog), and **per-application MCP servers**. Tool names changed on 2026-10-01; the file carries the old → new map | [references/softr-mcp.md](references/softr-mcp.md) |
183
183
  | 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) |
184
184
  | 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) |
185
185
  | **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) |
@@ -209,7 +209,7 @@ export default function Block() {
209
209
  }
210
210
  ```
211
211
 
212
- Wrap the outermost layout in `container` and `content` divs by default — these constrain width to match the Softr app's max width settings so the block aligns with neighboring native blocks. Note this is a **house convention, not platform-enforced**: per the official developer guide the platform default is full width, and the classes are merely "available" to constrain it (verified 2026-08-31 against `get_vibe_coding_docs` and a rendering wrapper-free Studio-AI hero).
212
+ Wrap the outermost layout in `container` and `content` divs by default — these constrain width to match the Softr app's max width settings so the block aligns with neighboring native blocks. Note this is a **house convention, not platform-enforced**: per the official developer guide the platform default is full width, and the classes are merely "available" to constrain it (verified 2026-08-31 against `vibe_coding_block_get_docs` and a rendering wrapper-free Studio-AI hero).
213
213
 
214
214
  **Exceptions (omit the wrappers deliberately):**
215
215
  - Blocks inside Softr column containers — Softr controls layout.
@@ -559,14 +559,23 @@ Non-negotiable rules. Most are enforced by the Softr platform (compiler, validat
559
559
  **Any save counts, including one whose only change is a comment** -- there is no "cosmetic edit"
560
560
  exemption; a `search_replace` that rewrites nothing but a code comment rebuilds the Actions exactly
561
561
  like a full rewrite does (verified live 2026-09-09, on two blocks at once).
562
+ The same rebuild runs on a Save in Studio's code editor, an AI-assistant edit and a version restore
563
+ (per Softr, 2026-10-01), and only `OPEN_CHAT` / `TRIGGER_CUSTOM_WORKFLOW` actions survive it.
564
+ **Record before, re-apply after, read back** -- before the push, note every action with
565
+ `isDefaultVisibility: false` (`vibe_coding_block_get_settings` lists them); after it, re-apply each
566
+ by `actionType` (+ `dataSourceId`), never by action id, which every compile regenerates. Each
567
+ update replaces that action's whole permission, so send the complete state.
562
568
  **Re-read the permissions after EVERY push and confirm they actually changed** -- do not assume the
563
- restore worked. Softr's default for a `genericActions` ADD_RECORD is `ALL_USERS`, i.e. writable by
564
- logged-OUT visitors, and the MCP call that re-tightens it (`set_vibe_coding_block_action_visibility`)
565
- can itself fail with no fallback (see the array-argument quirk in
569
+ restore worked. The default for ADD_RECORD follows the block's own visibility, so on a block
570
+ everyone can see it comes back `ALL_USERS`, i.e. writable by logged-OUT visitors (UPDATE/DELETE
571
+ reset to logged-in users). The MCP call that re-tightens it (`vibe_coding_block_set_action_visibility`)
572
+ can itself fail with no fallback. It fails when the client holds only a stub of the tool
573
+ (name-only description, no properties), which happens after a session resume, so check the
574
+ loaded definition and start a fresh session if it is a stub (see
566
575
  [references/softr-mcp.md](references/softr-mcp.md#the-array-argument-rejection-and-why-it-is-a-security-issue)).
567
576
  A push that returns `errors: null` can still have left public write access on the block.
568
- **If any action is still `ALL_USERS`, report it WITH its severity and let the builder decide.**
569
- Check the page's own VIEW permission first (`get_page_permissions`): a page gated to logged-in
577
+ **If any action is still broader than intended, report it WITH its severity and let the builder decide.**
578
+ Check the page's own VIEW permission first (`application_page_get_permissions`): a page gated to logged-in
570
579
  users makes an open action housekeeping, a public page makes it a real hole. Surface the list
571
580
  either way -- page, block, action type, data source -- and note that a human sets them on the
572
581
  block's Actions tab. Do not unilaterally block a publish; it is not your app.
@@ -617,7 +626,7 @@ Non-negotiable rules. Most are enforced by the Softr platform (compiler, validat
617
626
 
618
627
  ## Style Conventions
619
628
 
620
- **The current platform compiles TypeScript with modern syntax** — optional chaining (`?.`), nullish coalescing (`??`), arrow functions, `const`/`let`, generics — verified live against `get_vibe_coding_docs` and a 15-block production deployment on 2026-08-25. Write new blocks in modern TS (`.tsx`); it matches what Softr's own Studio AI emits.
629
+ **The current platform compiles TypeScript with modern syntax** — optional chaining (`?.`), nullish coalescing (`??`), arrow functions, `const`/`let`, generics — verified live against `vibe_coding_block_get_docs` and a 15-block production deployment on 2026-08-25. Write new blocks in modern TS (`.tsx`); it matches what Softr's own Studio AI emits.
621
630
 
622
631
  History, kept so old guidance elsewhere is recognizable as superseded: until mid-2026 this skill mandated `var` + `function(){}` and banned `?.` / `??` because the then-current bundler failed on them (verified April 2026; a Studio-scaffolded block already contradicted it by July 2026). That platform behavior is gone. Existing var-style blocks remain valid — the compiler accepts both styles — so don't churn a working block just to modernize its syntax, and don't "fix" modern syntax back to var-style when editing.
623
632
 
@@ -152,7 +152,7 @@ The second connection above is for **reads**. Actions are filed per table:
152
152
  under the **first** connection's `dataSourceId`.
153
153
 
154
154
  So **point every write at the table's first connection**, and expect one action per table and
155
- operation in `get_vibe_coding_block_settings` / the Actions tab — that is the row you re-tighten
155
+ operation in `vibe_coding_block_get_settings` / the Actions tab — that is the row you re-tighten
156
156
  after each push. Details in [writing.md](writing.md#actions-register-per-table-not-per-hook-or-connection).
157
157
 
158
158
  ## Getting the datasource ids — ask for CODE, never for a value
@@ -93,14 +93,14 @@ 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).
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 from the MCP, authoritative **for the server you're calling** (`database_get_field_reference` on the workspace server, `get_schema` on a per-app server): the per-app servers document a fuller catalog than the workspace server (adding Address, Progress, Time, Date range, Button, and display options like Percent's progress-bar/ring — still missing from the workspace reference on 2026-10-01), and the two have also differed on operator names — see [../references/softr-mcp.md](../references/softr-mcp.md#softr-database-tools).
97
97
 
98
98
  ## Rate Limits
99
99
  No API rate limits. Softr Database queries run internally without external API calls, making it the best choice for high-traffic applications.
100
100
 
101
101
  ## Gotchas
102
102
  - **Formula boolean values are strings.** A formula that evaluates to true returns `"1"`, not `true`. Always compare with `=== "1"` or `=== "0"`.
103
- - **Field IDs are opaque codes.** You cannot guess them from column names. Look them up via the ranked list above (MCP `list_fields` / bundled CLI / network inspector / Studio field drawer) — the generic Field Inspector block does NOT work for Softr Database.
103
+ - **Field IDs are opaque codes.** You cannot guess them from column names. Look them up via the ranked list above (MCP `database_list_fields` / bundled CLI / network inspector / Studio field drawer) — the generic Field Inspector block does NOT work for Softr Database.
104
104
  - **Relationships** work similarly to linked records in Airtable but use Softr's internal record IDs.
105
105
 
106
106
  ## Best For
@@ -377,10 +377,12 @@ and schema work, and **none of them raises an error**.
377
377
  enforcement only fires on the next write. If one side must hold many links, both sides
378
378
  must allow multiple entries; enforce any one-parent rule in the UI, not the schema.
379
379
 
380
- 2. **`allowMultipleEntries` is a TOP-LEVEL field property, not part of `options`.** The
381
- workspace MCP's `update_field` silently ignores it when nested inside `options` — the
382
- call succeeds and changes nothing. A Tables API `PUT /fields/{id}` with the property at
383
- top level works.
380
+ 2. **In a Tables API field PUT, `allowMultipleEntries` is a TOP-LEVEL field property, not part
381
+ of `options`.** A `PUT /fields/{id}` with the property at top level works. Do not reach for the
382
+ MCP's old `update_field` here: per Softr, until 2026-10-01 it wrote `allowMultipleEntries: false`
383
+ on every call whatever it was sent, which is what looked like "ignored" when we tested it. Since
384
+ then `database_update_field` takes the setting **inside `options`** and keeps it when you leave
385
+ it out (per Softr; not yet re-tested by us).
384
386
 
385
387
  3. **A Tables API field PUT that omits `options.inverseLinkFieldId` SEVERS the inverse
386
388
  pairing** — it comes back `null` and the two sides stop mirroring each other. Always
@@ -547,7 +549,7 @@ Details that matter in practice:
547
549
  - A bulk import of images from another system (Airtable, a vendor CDN, a CSV of image links) is therefore
548
550
  a plain loop of record writes with no download-and-re-upload stage.
549
551
 
550
- _Write shape verified live 2026-08-26 on Softr Database via the MCP `update_record`: a 20,990-byte
552
+ _Write shape verified live 2026-08-26 on Softr Database via the workspace MCP's `update_record` (now `database_update_record`): a 20,990-byte
551
553
  `image/jpeg` behind an extensionless ImageKit URL came back as a Softr-hosted S3 object of identical size
552
554
  and type, with small/medium/large thumbnails generated. Copy-not-link confirmed. The in-block
553
555
  `useRecordUpdate` / `createRecord` path takes the same shape, but external-URL ingestion was not separately
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "softr-vibe-coding",
3
- "version": "2.10.0",
3
+ "version": "2.11.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"
@@ -43,7 +43,9 @@ Run through this catalog before delivering any block. Every row is a violation o
43
43
  | Passing a data hook's options through a variable or wrapper function: `useRecords(buildOpts())` | **Fails to compile** — the options object must be an inline literal at the call site (verified live 2026-08-25; hit in production, fixed by making the wrapper take the hook's *result* instead). Share `q.select` mappings between hooks, never whole options objects |
44
44
  | Tightening Actions-tab permissions before the block's final redeploy | Every code recompile **resets the auto-registered Actions to default permissions** (verified live 2026-08-25). Tighten permissions after the LAST redeploy, and re-check after any future one |
45
45
  | Assuming a comment-only edit is "safe" and leaves Action permissions alone | There is no cosmetic-edit exemption. Any save recompiles, and every recompile rebuilds the Actions at default visibility — a `search_replace` changing nothing but a code comment resets them exactly like a rewrite (verified live 2026-09-09, on two blocks at once). Re-check after EVERY push, including cosmetic ones |
46
- | Treating the permission-restore call as done because you issued it | Read the permissions back with `get_vibe_coding_block_settings` and confirm each one changed. `set_vibe_coding_block_action_visibility` can fail outright on the array-argument serialization quirk, and it has **no fallback** — the default for a `genericActions` ADD_RECORD is `ALL_USERS`, so a routine push silently leaves the block publicly writable while returning `errors: null`. Verified live 2026-09-09: four ADD_RECORD actions left open across two blocks. Report the list with its severity — check the page's VIEW permission with `get_page_permissions`, since a logged-in-gated page makes this housekeeping while a public page makes it a real hole — and let the builder decide whether it holds their release. A human sets them on the block's Actions tab |
46
+ | Treating the permission-restore call as done because you issued it | Read the permissions back with `vibe_coding_block_get_settings` and confirm each one changed. `vibe_coding_block_set_action_visibility` can fail outright on the array-argument rejection (the client holding only a stub of the tool, typically after a session resume), and it has **no fallback** — the default for ADD_RECORD follows the block's own visibility, so on a block everyone can see, a routine push silently leaves it publicly writable while returning `errors: null`. Verified live 2026-09-09: four ADD_RECORD actions left open across two blocks. Report the list with its severity — check the page's VIEW permission with `application_page_get_permissions`, since a logged-in-gated page makes this housekeeping while a public page makes it a real hole — and let the builder decide whether it holds their release. A human sets them on the block's Actions tab |
47
+ | Calling an array-argument MCP tool (`vibe_coding_block_set_action_visibility`, `vibe_coding_block_update_code_search_replace`) when its loaded definition is a stub — description identical to the tool name, schema `{"type":"object"}` with no properties | A stub declares no types, so the array is sent as a JSON string and rejected (since 2026-10-01 the error reads "Parameter '…' must be an array of objects, but a string was sent"). Stubs appear after a session resume. Start a fresh session, which reloads the real definitions, then call. Root cause established 2026-10-01 — see [softr-mcp.md](softr-mcp.md#the-array-argument-rejection-and-why-it-is-a-security-issue) |
48
+ | Verifying a push by pulling the whole block source back into the model's context | Compare `sourceSha256` from the push result (or from `vibe_coding_block_get_code` with `includeCode: false`) with `shasum -a 256` of the file you sent; fetch the full source only when the digest is missing or differs. Available since 2026-10-01 — see [softr-mcp.md](softr-mcp.md#verifying-a-push--the-deployed-source-is-the-only-proof) |
47
49
  | Splitting one table's writes across several `useRecordUpdate` hooks (or across two connections of the same table) to get separately-permissioned Actions | Actions register per **TABLE**: the hooks merge into ONE UPDATE_RECORD action whose field list is the union, and a hook pointed at a second connection of the table is still filed under the FIRST connection's dataSourceId (verified live 2026-09-18). Point writes at the table's first connection; expect one action per table + operation when re-tightening permissions. See [datasources/writing.md](../datasources/writing.md#actions-register-per-table-not-per-hook-or-connection) |
48
50
  | Treating Studio's Actions tab as a separately-managed configuration to keep in sync with code | Actions auto-derive from your `useRecordCreate`/`useRecordUpdate`/`useRecordDelete` + `q.select` on every save. The Actions tab is a read-only inspector; there is no manual delete control. To change an Action, change the code |
49
51
  | One alias in a write-side `q.select` referencing a renamed / non-existent Airtable column | Softr's Action parser silently rejects the **entire** create/update Action — not just the bad alias. Symptoms: Studio's Actions tab shows "No actions used in this block yet", `createRecord.enabled` / `updateRecord.enabled` stays `false`, `.mutate()` calls dispatch but resolve immediately to "not yet ready". Every OTHER field in the same `q.select()` is also lost, even the ones that map cleanly. Diagnostic: bisect the `q.select` — strip down to a known-good minimal set, confirm the Action appears in Studio, then add fields back in halves until it drops out. The culprit is in the last half added. Once narrowed to a single field, grep its name against the freshest Airtable schema export to catch the rename / trailing-space / case-mismatch. Verified 2026-05-21: a `"Photos"` column on Wigs was renamed to `"Before Photos"`, the helper that wrote `photos: "Photos"` had its entire Action disabled even though 11 other fields in the same `q.select` were fine. See [datasources/airtable.md](../datasources/airtable.md#maintainability-gotcha) |
@@ -2,7 +2,7 @@
2
2
 
3
3
  Editable settings are the hooks from `@/lib/editable-settings` that surface a block's content in Studio's **Content → Settings** pane, so builders (and clients) edit text, images, links, and lists **without re-prompting or touching code**. Softr's own pitch: "make simple text and image edits directly without re-prompting." This file is the deep-dive; SKILL.md keeps the compact signatures.
4
4
 
5
- **Provenance discipline.** Every behavior below is tagged either **[official]** (in Softr's Vibe Coding Developer Guide, re-fetchable via the MCP's `get_vibe_coding_docs`) or **[verified-undocumented]** (absent from the official guide but proven working — source and date given). Keep the tags when editing this file: they're what stops a future docs-based review from false-positiving working code (the same failure class as the useRecord-via-Studio-binding incident), and what tells you which behaviors could silently change under you since Softr never promised them.
5
+ **Provenance discipline.** Every behavior below is tagged either **[official]** (in Softr's Vibe Coding Developer Guide, re-fetchable via the MCP's `vibe_coding_block_get_docs`) or **[verified-undocumented]** (absent from the official guide but proven working — source and date given). Keep the tags when editing this file: they're what stops a future docs-based review from false-positiving working code (the same failure class as the useRecord-via-Studio-binding incident), and what tells you which behaviors could silently change under you since Softr never promised them.
6
6
 
7
7
  ## Contents
8
8
 
@@ -497,9 +497,9 @@ has attached to the popup. With Playwright attached over CDP (seen 2026-09-30) t
497
497
  through to the 4s cap, which looks exactly like a broken wait. Use real URLs or `data:` URLs in
498
498
  the popup, or mock on a page that loaded normally (the `?print=1` path), where routing works.
499
499
 
500
- **Preview links pin the app version they were minted on.** A `preview_app` session keeps serving
501
- the version it was opened on, so after pushing a change, mint a fresh preview link before you
502
- verify anything; otherwise you are testing the old Print. (What else a preview link is, and why
500
+ **Preview links pin the app version they were minted on.** An `application_preview` link carries
501
+ `&version=<n>` in its URL and keeps serving that version, by design. After pushing a change, mint a
502
+ fresh preview link before you verify anything; otherwise you are testing the old Print. (What else a preview link is, and why
503
503
  it is never shared: [softr-mcp.md](softr-mcp.md#application-management-tools).)
504
504
 
505
505
  **A browser that is not painting does not run the page.** A hidden browser pane or a background
@@ -8,9 +8,12 @@ The official Softr MCP server (`https://mcp.softr.io/mcp`) gives an AI assistant
8
8
 
9
9
  > Historical note: this file was previously named `softr-database-mcp.md` and described a databases-only server with granular scopes. That server has since grown into the workspace-wide MCP documented here; the old "does NOT cover external sources" limitation is gone (see [Integrations](#browsing-integrations-external-data-sources)).
10
10
 
11
+ > **The workspace server's tools were renamed on 2026-10-01** — noun first: `get_vibe_coding_block_code` is now `vibe_coding_block_get_code`, `publish_app` is `application_publish`, `get_schema` is `database_get_field_reference`. This file uses the new names throughout. Notes, prompts or scripts written before then use the old ones; translate them with the [old → new map](#tool-names--the-2026-10-01-rename). The Workflows tools kept their names, and the per-application servers are a separate tool set — `list_tables`, `get_record`, `update_record` and `get_schema` there are NOT the old workspace tools.
12
+
11
13
  ## Contents
12
14
 
13
15
  - [What it covers](#what-it-covers)
16
+ - [Tool names — the 2026-10-01 rename](#tool-names--the-2026-10-01-rename)
14
17
  - [Connection and auth](#connection-and-auth)
15
18
  - [Permissions model](#permissions-model)
16
19
  - [Vibe coding block tools](#vibe-coding-block-tools) — incl. [what the server enforces on a block's data endpoints](#what-the-server-enforces-on-a-blocks-data-endpoints)
@@ -34,7 +37,122 @@ The official Softr MCP server (`https://mcp.softr.io/mcp`) gives an AI assistant
34
37
  | Integrations | Browse external data sources connected to the workspace, down to field level | https://docs.softr.io/mcp/integrations |
35
38
  | 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 |
36
39
 
37
- `list_workspaces` is often the first call — it turns "my Sales workspace" into the workspace ID every other tool needs.
40
+ `workspace_list` is often the first call — it turns "my Sales workspace" into the workspace ID every other tool needs. The server's own instructions now start from `application_list` (applications and their workspace IDs) and `database_list` (databases), and keep `workspace_list` for turning a workspace name into an ID.
41
+
42
+ ## Tool names — the 2026-10-01 rename
43
+
44
+ On 2026-10-01 Softr renamed every workspace-server tool outside Workflows to **area first, then verb**:
45
+ `vibe_coding_block_*`, `application_*` (pages are `application_page_*`), `database_*`,
46
+ `integration_*` and `workspace_*`. 79 tools were renamed and one was added
47
+ (`application_update_pwa_settings`); the 28 Workflows tools and `get_workspace_integrations` kept their names. The
48
+ map below was checked against the tool lists the server delivered on 2026-09-30 (old) and 2026-10-01 (new).
49
+
50
+ Most new names are the old words reordered. These are the ones you would not guess:
51
+
52
+ | Old | New |
53
+ |---|---|
54
+ | `get_schema` | `database_get_field_reference` |
55
+ | `aggregate_data` | `database_aggregate_records` |
56
+ | `get_access_control` | `application_get_access_overview` |
57
+ | `search_databases` | `database_search` |
58
+ | `list_integrations` (earlier `list_data_sources`) | `integration_list` |
59
+ | `list_data_source_databases` / `_schemas` / `_tables` / `_table_fields` | `integration_list_databases` / `_schemas` / `_tables` / `_table_fields` |
60
+ | `get_page`, `get_block`, `list_pages`, `create_page`, `get_page_permissions` | `application_page_get`, `application_page_get_block`, `application_page_list`, `application_page_create`, `application_page_get_permissions` |
61
+ | `preview_app`, `publish_app` | `application_preview`, `application_publish` |
62
+ | `create_database`, `get_database`, `list_databases`, `update_database`, `delete_database` | `database_create`, `database_get`, `database_list`, `database_update`, `database_delete` |
63
+
64
+ <details>
65
+ <summary>Full old → new map (79 tools)</summary>
66
+
67
+ | Area | Old | New |
68
+ |---|---|---|
69
+ | Vibe coding blocks | `get_vibe_coding_docs` | `vibe_coding_block_get_docs` |
70
+ | | `create_vibe_coding_block` | `vibe_coding_block_create` |
71
+ | | `delete_vibe_coding_block` | `vibe_coding_block_delete` |
72
+ | | `get_vibe_coding_block_code` | `vibe_coding_block_get_code` |
73
+ | | `get_vibe_coding_block_settings` | `vibe_coding_block_get_settings` |
74
+ | | `update_vibe_coding_block_code` | `vibe_coding_block_update_code` |
75
+ | | `update_vibe_coding_block_code_search_replace` | `vibe_coding_block_update_code_search_replace` |
76
+ | | `update_vibe_coding_block_settings` | `vibe_coding_block_update_settings` |
77
+ | | `set_vibe_coding_block_visibility` | `vibe_coding_block_set_visibility` |
78
+ | | `set_vibe_coding_block_action_visibility` | `vibe_coding_block_set_action_visibility` |
79
+ | | `list_vibe_coding_block_versions` | `vibe_coding_block_list_versions` |
80
+ | | `restore_vibe_coding_block_version` | `vibe_coding_block_restore_version` |
81
+ | | `duplicate_vibe_coding_block_from_version` | `vibe_coding_block_duplicate_from_version` |
82
+ | | `connect_vibe_coding_block_data_source` | `vibe_coding_block_connect_data_source` |
83
+ | | `disconnect_vibe_coding_block_data_source` | `vibe_coding_block_disconnect_data_source` |
84
+ | | `set_vibe_coding_block_data_source_sort` | `vibe_coding_block_set_data_source_sort` |
85
+ | | `set_vibe_coding_block_data_source_record_filters` | `vibe_coding_block_set_data_source_record_filters` |
86
+ | Applications | `list_applications` | `application_list` |
87
+ | | `get_application` | `application_get` |
88
+ | | `create_application` | `application_create` |
89
+ | | `set_application_name` | `application_set_name` |
90
+ | | `set_application_subdomain` | `application_set_subdomain` |
91
+ | | `set_application_domain` | `application_set_domain` |
92
+ | | `set_application_login` | `application_set_login` |
93
+ | | `configure_application_sign_up` | `application_configure_sign_up` |
94
+ | | `configure_application_email_sender` | `application_configure_email_sender` |
95
+ | | `update_application_data_source` | `application_update_data_source` |
96
+ | | `get_access_control` | `application_get_access_overview` |
97
+ | | `list_application_users` | `application_list_users` |
98
+ | | `add_application_user` | `application_add_user` |
99
+ | | `remove_application_user` | `application_remove_user` |
100
+ | | `set_application_user_activation` | `application_set_user_activation` |
101
+ | | `list_user_groups` | `application_list_user_groups` |
102
+ | | `create_user_group` | `application_create_user_group` |
103
+ | | `update_user_group` | `application_update_user_group` |
104
+ | | `delete_user_group` | `application_delete_user_group` |
105
+ | | `create_user_connection` | `application_create_user_connection` |
106
+ | | `get_user_connection` | `application_get_user_connection` |
107
+ | | `remove_user_connection` | `application_remove_user_connection` |
108
+ | | `list_pages` | `application_page_list` |
109
+ | | `get_page` | `application_page_get` |
110
+ | | `create_page` | `application_page_create` |
111
+ | | `get_block` | `application_page_get_block` |
112
+ | | `get_page_permissions` | `application_page_get_permissions` |
113
+ | | `preview_app` | `application_preview` |
114
+ | | `publish_app` | `application_publish` |
115
+ | | *(new)* | `application_update_pwa_settings` |
116
+ | Databases | `list_databases` | `database_list` |
117
+ | | `search_databases` | `database_search` |
118
+ | | `get_database` | `database_get` |
119
+ | | `create_database` | `database_create` |
120
+ | | `update_database` | `database_update` |
121
+ | | `delete_database` | `database_delete` |
122
+ | | `get_schema` | `database_get_field_reference` |
123
+ | | `list_tables` | `database_list_tables` |
124
+ | | `get_table` | `database_get_table` |
125
+ | | `create_table` | `database_create_table` |
126
+ | | `update_table` | `database_update_table` |
127
+ | | `delete_table` | `database_delete_table` |
128
+ | | `list_fields` | `database_list_fields` |
129
+ | | `create_field` | `database_create_field` |
130
+ | | `update_field` | `database_update_field` |
131
+ | | `delete_field` | `database_delete_field` |
132
+ | | `list_views` | `database_list_views` |
133
+ | | `list_records` | `database_list_records` |
134
+ | | `search_records` | `database_search_records` |
135
+ | | `get_record` | `database_get_record` |
136
+ | | `create_record` | `database_create_record` |
137
+ | | `create_records` | `database_create_records` |
138
+ | | `update_record` | `database_update_record` |
139
+ | | `delete_record` | `database_delete_record` |
140
+ | | `delete_records` | `database_delete_records` |
141
+ | | `aggregate_data` | `database_aggregate_records` |
142
+ | Integrations | `list_integrations` | `integration_list` |
143
+ | | `list_data_source_databases` | `integration_list_databases` |
144
+ | | `list_data_source_schemas` | `integration_list_schemas` |
145
+ | | `list_data_source_tables` | `integration_list_tables` |
146
+ | | `list_data_source_table_fields` | `integration_list_table_fields` |
147
+ | Workspace | `list_workspaces` | `workspace_list` |
148
+ | | `list_workspace_email_senders` | `workspace_list_email_senders` |
149
+
150
+ </details>
151
+
152
+ **Per-application servers are a different tool set, and are not covered by this map.** Their
153
+ `list_tables`, `get_record`, `create_record`, `update_record`, `delete_record` and `get_schema` share
154
+ names with the old workspace tools but belong to [those servers](#per-application-mcp-servers). When
155
+ translating an old note, check which server a call went to before renaming it.
38
156
 
39
157
  ## Connection and auth
40
158
 
@@ -70,22 +188,36 @@ For block-building work you need **Applications & Forms: Full access** (to creat
70
188
 
71
189
  ## Vibe coding block tools
72
190
 
73
- Before writing any block code through the MCP, call `get_vibe_coding_docs` — it returns the current version of the [Vibe Coding Developer Guide](https://docs.softr.io/vibe-coding-developer-guide), which is the authority on hook signatures if it and this skill ever disagree.
191
+ Before writing any block code through the MCP, call `vibe_coding_block_get_docs` — it returns the current version of the [Vibe Coding Developer Guide](https://docs.softr.io/vibe-coding-developer-guide), which is the authority on hook signatures if it and this skill ever disagree.
74
192
 
75
193
  | Group | Tools |
76
194
  |---|---|
77
- | Create / read | `get_vibe_coding_docs`, `create_vibe_coding_block`, `get_vibe_coding_block_code`, `get_vibe_coding_block_settings` |
78
- | Edit code | `update_vibe_coding_block_code` (full replace), `update_vibe_coding_block_code_search_replace` (targeted edit) |
79
- | Settings / visibility | `update_vibe_coding_block_settings`, `set_vibe_coding_block_visibility`, `set_vibe_coding_block_action_visibility` |
80
- | Versions | `list_vibe_coding_block_versions`, `restore_vibe_coding_block_version`, `duplicate_vibe_coding_block_from_version` |
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) |
195
+ | Create / read | `vibe_coding_block_get_docs`, `vibe_coding_block_create`, `vibe_coding_block_get_code`, `vibe_coding_block_get_settings` |
196
+ | Edit code | `vibe_coding_block_update_code` (full replace), `vibe_coding_block_update_code_search_replace` (targeted edit) |
197
+ | Settings / visibility | `vibe_coding_block_update_settings`, `vibe_coding_block_set_visibility`, `vibe_coding_block_set_action_visibility` |
198
+ | Versions | `vibe_coding_block_list_versions`, `vibe_coding_block_restore_version`, `vibe_coding_block_duplicate_from_version` |
199
+ | Data sources | `vibe_coding_block_connect_data_source`, `vibe_coding_block_disconnect_data_source`, `vibe_coding_block_set_data_source_sort`, `vibe_coding_block_set_data_source_record_filters` |
200
+ | Any block | `application_page_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) |
83
201
 
84
202
  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.
85
203
 
204
+ **Which read to use** (from the tools' own descriptions, 2026-10-01):
205
+
206
+ - `vibe_coding_block_get_settings` returns the block's settings, its `actions` (type, `dataSourceId`,
207
+ mapped fields, `permission`, `isDefaultVisibility`) and its wired `dataSources` — everything except
208
+ the source. It is the cheap read before any permission, sort, filter or settings change, and for
209
+ checking what a restore or duplicate kept.
210
+ - `vibe_coding_block_get_code` adds the source. Its `dataSources` list is the only reliable answer to
211
+ "is a datasource actually wired to this block?" — the compiler never sees the wiring — and each entry's
212
+ `fieldReferenceKey` (`id` or `name`) says how that source's fields must be referenced in `q.select()`.
213
+ - `vibe_coding_block_get_code` with **`includeCode: false`** skips the source text but still returns
214
+ `sourceSha256` and `sourceBytes` — about 1 KB however large the block is. Use it to [verify a
215
+ push](#verifying-a-push--the-deployed-source-is-the-only-proof) (verified live 2026-10-01).
216
+ - Push results now report `sourceSha256` and `sourceBytes` too, for the source Softr actually stored.
217
+
86
218
  ### Which edit tool: full replace vs. targeted search-replace
87
219
 
88
- `update_vibe_coding_block_code` sends the whole file; `update_vibe_coding_block_code_search_replace`
220
+ `vibe_coding_block_update_code` sends the whole file; `vibe_coding_block_update_code_search_replace`
89
221
  sends only the fragments that change. This is not just a bandwidth choice — it changes what can go wrong.
90
222
 
91
223
  **Reach for search-replace when ONE source file is deployed to SEVERAL blocks.** Datasource UUIDs are
@@ -105,12 +237,14 @@ context. Keep the local mirror in step mechanically rather than by hand:
105
237
  1. Prove deployed == disk first ([below](#verifying-a-push--the-deployed-source-is-the-only-proof)).
106
238
  2. Write the ops once, as data. Send them to the tool, and apply the **identical** ops to the local
107
239
  mirror with a script that asserts each `search` occurs exactly once before replacing it.
108
- 3. Several rounds of ops are fine — **byte-verify once at the end**: fetch `sourceCode`, compare to
109
- the mirror, and a mismatch means an op landed differently on one side.
240
+ 3. Several rounds of ops are fine — **verify once at the end**: compare the `sourceSha256` of the
241
+ last push result with the mirror's SHA-256. A mismatch means an op landed differently on one
242
+ side. The digest describes the source Softr stored after merging your edits, not the edits you
243
+ sent, which is what makes it usable here: on this path you never see the merged file yourself.
110
244
 
111
245
  One encoding trap: JSON `\uXXXX` escapes inside the ops are **decoded to the real characters** on
112
246
  Softr's side (`"—"` is stored as `—`). The mirror must therefore hold raw UTF-8 — apply the
113
- ops to it *after* JSON-decoding them, never as the escaped text, or the final byte comparison
247
+ ops to it *after* JSON-decoding them, never as the escaped text, or the final comparison
114
248
  fails on every non-ASCII character.
115
249
 
116
250
  **Reach for the full replace when the change is structural** — reordering JSX, moving logic between
@@ -120,19 +254,29 @@ has drifted from the deployed block in ways you have not enumerated.
120
254
 
121
255
  ### Verifying a push — the deployed source is the only proof
122
256
 
123
- `update_vibe_coding_block_code` returning `errors: null, warnings: null` proves the code **compiled**.
257
+ `vibe_coding_block_update_code` returning `errors: null, warnings: null` proves the code **compiled**.
124
258
  It does not prove the block now holds the code you meant to send. Verified 2026-09-09: a push of a
125
259
  67KB block came back clean and had silently dropped one blank line at a read-chunk boundary — valid
126
260
  JavaScript, so the compiler had nothing to say. Only a byte comparison caught it. Treat every push as
127
- unverified until you have pulled the source back down and compared it.
261
+ unverified until the deployed source is proven identical to your file.
262
+
263
+ **Since 2026-10-01 that proof is a hash, not a download.** Push results (create, full replace,
264
+ search-replace) and `vibe_coding_block_get_code` carry `sourceSha256` and `sourceBytes`: the SHA-256
265
+ of the UTF-8 source Softr persisted, and its length in bytes. A failed compile stores nothing and
266
+ reports neither field. With `includeCode: false`, `vibe_coding_block_get_code` returns the digest and
267
+ `sourceCode: null`. Verified live 2026-10-01: a deployed block's `sourceSha256` equalled
268
+ `shasum -a 256` of the file last pushed to it, and the read came back at about 1 KB for a 15 KB
269
+ block. (The digest on push results is per Softr's release notes; no push of ours has shown it yet.)
270
+ Right after that release our client's copy of the tool definition did not declare `includeCode`, so
271
+ the argument went out as the string `"false"` and the server still honoured it. Whatever the loaded
272
+ definition says, check that `sourceCode` came back `null`.
128
273
 
129
274
  **The protocol, per block:**
130
275
 
131
- 1. **Before editing, prove deployed == disk.** Call `get_vibe_coding_block_code`, extract
132
- `sourceCode`, and compare it byte-for-byte to your local mirror. If they differ, someone changed
133
- the block in Studio since your last push — stop and reconcile; do not overwrite work you have not
134
- seen. (Large results are persisted to a file by most clients rather than returned inline; compare
135
- from that file with a script, never by eye.)
276
+ 1. **Before editing, prove deployed == disk.** Call `vibe_coding_block_get_code` with
277
+ `includeCode: false` and compare `sourceSha256` and `sourceBytes` to `shasum -a 256 <file>` and
278
+ `wc -c < <file>`. If they differ, someone changed the block in Studio since your last push: fetch
279
+ the full source, diff it against yours and reconcile. Do not overwrite work you have not seen.
136
280
  2. Edit the local file. Run a parser and `no-undef` lint on it first — `node --check` does **not**
137
281
  accept a `.jsx` extension, so use esbuild (`esbuild file.jsx --loader:.jsx=jsx --jsx=automatic
138
282
  --log-level=error --outfile=/dev/null`) plus eslint with `@babel/eslint-parser`. The bugs that
@@ -141,9 +285,14 @@ unverified until you have pulled the source back down and compared it.
141
285
  3. Push the **entire** file — or, for a targeted patch on a large block, send search-replace ops
142
286
  and apply the identical ops to the mirror
143
287
  ([recipe above](#which-edit-tool-full-replace-vs-targeted-search-replace)).
144
- 4. **Fetch it back and compare again.** Identical, or you are not done: diff, fix, re-push.
145
-
146
- **Compare byte for byte, trailing newline included.** Softr stores exactly what it receives: across
288
+ 4. **Compare the push result's `sourceSha256` with the hash of what you meant to deploy.**
289
+ Identical, or you are not done: diff, fix, re-push. If the result carries no digest (an older
290
+ server instance can answer during a rollout), read it with `includeCode: false`. If that has none
291
+ either, fall back to fetching `sourceCode` and comparing byte for byte. Most clients save a large
292
+ result to a file rather than returning it inline, so compare from that file with a script, never
293
+ by eye.
294
+
295
+ **Hash the exact bytes, trailing newline included.** Softr stores exactly what it receives: across
147
296
  58 push→fetch pairs between 2026-08-26 and 2026-09-10 (14 blocks, 16–161 KB each) the fetched
148
297
  `sourceCode` was byte- and MD5-identical to the text sent, including two pushes sent *without* a
149
298
  final newline and stored without one. The "deployed block is one byte shorter" we chased on
@@ -154,20 +303,21 @@ means re-send, whatever the byte.
154
303
 
155
304
  **One file, two blocks, two datasource pairs.** When the same source is deployed to two pages, the
156
305
  local file holds ONE page's `datasource.define()` pair. Push it as-is to that block; for the other,
157
- build the swapped text in a scratch location, push that, and verify each block against its own
158
- expectation (disk for the first, disk-with-swap for the second). Never save the swapped copy over
306
+ build the swapped text in a scratch location, push that, and verify each block against the hash of
307
+ its own expected text (disk for the first, disk-with-swap for the second). Never save the swapped copy over
159
308
  the local mirror — the mirror records which page it belongs to, and the block's header comment
160
309
  records the other page's pair. Search-replace would avoid the swap altogether
161
310
  ([above](#which-edit-tool-full-replace-vs-targeted-search-replace)) — when the client can send its
162
311
  array argument ([below](#the-array-argument-rejection-and-why-it-is-a-security-issue)).
163
312
 
164
313
  **Do not read a 100KB block into a model's context to push it.** The full-replace tool takes the
165
- whole file as a string parameter, so the source has to pass through whatever is making the call. A
166
- large multi-block deploy is safer farmed out one file per subagent — a fresh context per file means
167
- no compaction can land mid-file — and the byte comparison is what makes that delegation safe, not
168
- trust in the agent. The steps that need judgement are the *edit* and the *review of the diff*; the
169
- fetch, the compare and the push itself are mechanical, and can run on the cheapest tier available
170
- without lowering the bar, because a wrong result fails loudly rather than plausibly.
314
+ whole file as a string parameter, so the source has to pass through whatever is making the call.
315
+ Verification no longer has to: the digest is a few hundred bytes. A large multi-block deploy is still
316
+ safer farmed out one file per subagent — a fresh context per file means no compaction can land
317
+ mid-file — and the hash comparison is what makes that delegation safe, not trust in the agent. The
318
+ steps that need judgement are the *edit* and the *review of the diff*; the hashing, the compare and
319
+ the push itself are mechanical, and can run on the cheapest tier available without lowering the bar,
320
+ because a wrong result fails loudly rather than plausibly.
171
321
 
172
322
  **What a push also resets.** Every code push puts the block's derived Actions back on Softr's
173
323
  default permissions (see the next section for why that can be a security problem and how to verify
@@ -177,54 +327,86 @@ so nobody chases the reset after every round; if it is not, re-tighten and read
177
327
  ### The array-argument rejection, and why it is a security issue
178
328
 
179
329
  **Several workspace-server tools take an array argument, and a call that sends it as a JSON *string*
180
- is rejected** by Jackson before it reaches any business logic:
330
+ is rejected** before it reaches any business logic. Since 2026-10-01 the error names the parameter
331
+ (wording from Softr's release notes):
332
+
333
+ ```
334
+ Parameter 'updates' must be an array of objects, but a string was sent. It looks like JSON
335
+ encoded as a string — send the value itself, not a string containing it.
336
+ ```
337
+
338
+ Before that, the same rejection came back as a bare Jackson message:
181
339
 
182
340
  ```
183
341
  Cannot deserialize value of type `java.util.ArrayList<java.util.Map<String,Object>>`
184
342
  from String value (token `JsonToken.VALUE_STRING`)
185
343
  ```
186
344
 
187
- | Tool | Array argument | Fallback if it fails |
188
- |---|---|---|
189
- | `update_vibe_coding_block_code_search_replace` | `operations` | Use `update_vibe_coding_block_code` (full replace) |
190
- | `set_vibe_coding_block_action_visibility` | `updates` | **NONE — a human must fix it in Studio** |
191
-
192
- **Where the string comes from — corrected 2026-09-10.** The first write-up of this (2026-09-09)
193
- blamed the server for advertising an empty schema. The transcripts say otherwise: the workspace
194
- server's schema declares both parameters as `type: array`, all 13 rejected calls had sent a JSON
195
- string, and all 84 successful calls to the same two tools had sent a real array — same day, same
196
- shapes, different payload type. The stringification happened on the client side, most likely on
197
- calls made while the tool definitions had not been loaded into the model's context (deferred
198
- schemas), so there was no type to serialise against. **Load the tool's schema before calling it,
199
- and pass arrays as arrays.** (Empty schemas are real on Softr's *per-application* MCP servers —
200
- every tool there is advertised as `{"type":"object"}` with a name-only description.)
201
-
202
- **Correction, 2026-09-30: the workspace server's tools can arrive schema-less too.** In one
203
- session every workspace tool loaded through ToolSearch showed only `{"type":"object"}` with a
204
- name-only description, and a `search_replace` call written with a real array was still sent as a
205
- string and rejected with the error above. Nothing was written, so the failure is safe, but no
206
- amount of care on the caller's side gets an array through a schema-less tool. Look at the
207
- loaded schema before relying on an array argument: if it has no `properties`, go straight to the
208
- fallback in the table — a full replace, one file per subagent for a large block, byte-verified.
209
-
210
- **Why the second row is a security problem, not an inconvenience.** Every code push resets the
211
- block's auto-registered Actions to Softr's defaults, and the default for a `genericActions`
212
- **ADD_RECORD is `ALL_USERS`** — writable by logged-OUT visitors — while UPDATE_RECORD and
213
- DELETE_RECORD default to `LOGGED_IN_USERS` in the same response. The documented remedy is to
214
- re-tighten with `set_vibe_coding_block_action_visibility`. When that call is the one that fails, a
215
- routine cosmetic push silently leaves public write access on the block, and nothing in the push
216
- result says so: the push itself returns `errors: null, warnings: null`. Verified live 2026-09-09 —
217
- one push left four ADD_RECORD actions open across two blocks.
345
+ | Tool | Array argument | First fix | Fallback if it still fails |
346
+ |---|---|---|---|
347
+ | `vibe_coding_block_update_code_search_replace` | `operations` | Start a fresh session (below) | Use `vibe_coding_block_update_code` (full replace) |
348
+ | `vibe_coding_block_set_action_visibility` | `updates` | Start a fresh session (below) | **NONE — a human must fix it in Studio** |
349
+
350
+ **Where the string comes from — root cause found 2026-10-01: tool stubs in a resumed session.**
351
+ When Claude Code resumes a session, MCP connector tools it already knew can come back as **stubs**:
352
+ the description is just the tool name and the input schema is `{"type":"object"}`, with no
353
+ properties. They stay stubs until the connector delivers its definitions again, which a fresh session
354
+ does (so did reconnecting the connector, once). A stub declares no types, so an array argument
355
+ goes out as a JSON string and Softr rejects it. Nothing is written, so the failure is safe, but no
356
+ amount of care on the caller's side gets an array through a stub. The evidence, from the complete
357
+ transcripts of one build:
358
+
359
+ - On 2026-09-09 every successful array call came before that session was resumed, and every
360
+ rejected one came after.
361
+ - On 2026-09-30, in a resumed session, every Softr tool definition the client recorded was a stub.
362
+ - A fresh session on 2026-10-01 loaded the full definitions.
363
+
364
+ This replaces two earlier explanations in this file: that the model "had not loaded the tool
365
+ definitions" (2026-09-10), and that the workspace server's tools "can arrive schema-less"
366
+ (2026-09-30). Both were describing the stubs without knowing where they came from. The old remark
367
+ that Softr's *per-application* servers advertise empty schemas is withdrawn too. Every per-app
368
+ definition we ever recorded had the same stub signature, and Softr reports that those servers
369
+ publish full schemas.
370
+
371
+ **The rule:** before any array-argument call, look at the tool's loaded definition (ToolSearch shows
372
+ it). If the description is just the tool name and there are no `properties`, do not make the call:
373
+ start a fresh session first. That matters most for `vibe_coding_block_set_action_visibility`, which
374
+ has no fallback. For a code edit, a full replace is an acceptable stopgap: one file per subagent for
375
+ a large block, hash-verified.
376
+
377
+ **Why the second row is a security problem, not an inconvenience.** Every code push rebuilds the
378
+ block's auto-registered Actions at Softr's default permissions. Per Softr (2026-10-01), the default
379
+ for **ADD_RECORD follows the block's own visibility**. On a block everyone can see, it comes back
380
+ `ALL_USERS`, writable by logged-OUT visitors. UPDATE_RECORD and DELETE_RECORD are always reset to
381
+ `LOGGED_IN_USERS`. That is what we saw on 2026-09-09, when one push left four ADD_RECORD actions
382
+ open across two blocks. The remedy is to re-apply the permissions with
383
+ `vibe_coding_block_set_action_visibility`. When that call is the one that fails, a routine cosmetic
384
+ push silently leaves public write access on the block. Nothing in the push result says so: the push
385
+ itself returns `errors: null, warnings: null`.
386
+
387
+ This is the platform's behaviour, not an MCP quirk. Per Softr, Studio rebuilds the Actions the same
388
+ way: on a Save in the code editor, an AI-assistant edit, a search-replace or a version restore. Only
389
+ `OPEN_CHAT` and `TRIGGER_CUSTOM_WORKFLOW` actions survive a recompile intact. As of 2026-10-01 nothing
390
+ preserves explicitly set permissions across a recompile, so every recompile needs the restore below.
218
391
 
219
392
  **So treat permission restoration as a step that must be VERIFIED, never assumed:**
220
393
 
221
- 1. Push the code.
222
- 2. Call `set_vibe_coding_block_action_visibility` for every action that needs tightening.
223
- 3. **Read the permissions back with `get_vibe_coding_block_settings` and confirm each one actually
394
+ 1. **Before the push, record what was set.** `vibe_coding_block_get_settings` lists each action
395
+ with its `actionType`, `dataSourceId`, `permission` and `isDefaultVisibility`. Every action with
396
+ `isDefaultVisibility: false` was set by someone, and the push will discard it.
397
+ 2. Push the code.
398
+ 3. **Re-apply** with `vibe_coding_block_set_action_visibility`. Address each action by `actionType`,
399
+ plus `dataSourceId` when the block has several actions of that type. Never address one by its
400
+ action id, because every compile issues new ids. Each update **replaces** that action's whole
401
+ permission (`predefinedGroup`, plus `customGroups` and `recordCondition` where used), so send the
402
+ complete intended state, not a delta. These rules come from the tool's own description
403
+ (2026-10-01).
404
+ 4. **Read the permissions back with `vibe_coding_block_get_settings` and confirm each one actually
224
405
  changed.** A successful-looking sequence is not evidence; the failure is an argument rejection, so
225
406
  the call errors rather than lying, but an agent that batches calls can easily miss which one failed.
226
- 4. If any action is still `ALL_USERS`, **report it and let the builder decide.** Check the page's own
227
- VIEW permission first with `get_page_permissions`, because that is what sets the severity:
407
+ 5. If any action is still broader than intended (typically ADD_RECORD at `ALL_USERS`), **report it
408
+ and let the builder decide.** Check the page's own VIEW permission first with
409
+ `application_page_get_permissions`, because that is what sets the severity:
228
410
  - **Page VIEW is gated** (e.g. `LOGGED_IN_USERS`) — an anonymous visitor cannot load the page at
229
411
  all, so exploiting the open action means calling its endpoint directly, and the realistic worst
230
412
  case is junk records rather than data exposure or deletion. Housekeeping: worth fixing on the
@@ -252,10 +434,11 @@ one push left four ADD_RECORD actions open across two blocks.
252
434
  cannot distinguish housekeeping from a breach gets tuned out, and then it is worth nothing on the
253
435
  day it matters.
254
436
 
255
- Do not improvise around a rejection. `update_vibe_coding_block_settings` is not a substitute: its schema
256
- is equally empty, it writes far more than one permission, and guessing its payload risks clobbering the
257
- block's data source connections. Restoring an older block version is not a substitute either — it
258
- reverts the code along with the permissions, undoing the change you just pushed.
437
+ Do not improvise around a rejection. `vibe_coding_block_update_settings` is not a substitute: it writes
438
+ far more than one permission, and guessing its payload risks clobbering the block's data source
439
+ connections. Restoring an older block version is not a substitute either. It reverts the code you just
440
+ pushed, and per Softr a restore recompiles like any other save, so its Actions come back at the
441
+ defaults anyway.
259
442
 
260
443
  Both edit paths recompile, so both reset Action permissions either way (Hard Constraint 21).
261
444
 
@@ -269,7 +452,7 @@ and these are the gates that actually exist on them:
269
452
  | Gate | Enforced server-side? |
270
453
  |---|---|
271
454
  | **Page VIEW permission** | **Yes.** A viewer who cannot view the page gets **403** ("block/action visibility rules…") from the block's datasource endpoint — crafting the request by hand does not get around it |
272
- | **The connection's Source conditions** (Source tab / `set_vibe_coding_block_data_source_record_filters`) | **Yes — and they are the only server-side ROW gate** |
455
+ | **The connection's Source conditions** (Source tab / `vibe_coding_block_set_data_source_record_filters`) | **Yes — and they are the only server-side ROW gate** |
273
456
  | A `where` filter in the block's code | No — it is a request parameter the caller controls |
274
457
  | Which fields the block *renders*, a second / conditional `q.select`, `enabled: false` on `useRecords` | No — the endpoint returns the union of the connection's read selects to anyone allowed to call it (see [multi-datasource.md](../datasources/multi-datasource.md#one-connection--one-read-payload-the-union-of-its-selects)) |
275
458
 
@@ -289,7 +472,7 @@ permission rather than uniformly critical.
289
472
 
290
473
  ## Adopting Studio-AI-generated code
291
474
 
292
- When you pull a Studio-AI-generated block via `get_vibe_coding_block_code` to adopt into a project repo as source of truth: its output renders fine but ships with predictable defects. **Functional patterns in Studio output are platform-support evidence** (it surfaces undocumented capabilities before the docs do — see SKILL.md's "Platform truth sources"); **its code hygiene is not a pattern to imitate.** Cleanup pass before committing:
475
+ When you pull a Studio-AI-generated block via `vibe_coding_block_get_code` to adopt into a project repo as source of truth: its output renders fine but ships with predictable defects. **Functional patterns in Studio output are platform-support evidence** (it surfaces undocumented capabilities before the docs do — see SKILL.md's "Platform truth sources"); **its code hygiene is not a pattern to imitate.** Cleanup pass before committing:
293
476
 
294
477
  - **Run a formatter** — Studio output ships inconsistent indentation (observed: statements at column 0 inside a 4-space-indented component).
295
478
  - **Hoist and consolidate brand hexes** into module-scope constants; flag near-duplicate hexes as probable unintended drift (observed: `#AE5E3D` vs `#B4603D` for one terracotta in a single block).
@@ -305,31 +488,48 @@ From the official MCP docs — these hold for MCP-driven and Studio-driven edits
305
488
 
306
489
  - **A broken block can't be saved.** Code is validated before storage; on failure the block keeps its last working state and nothing is lost.
307
490
  - **A version is a snapshot of the whole block** — code, settings, visibility, AND data source connections. Setting-only changes don't create a version.
308
- - **Rolling back reverts more than the code.** Restoring a version also restores settings, visibility, and data source connections as they were at that point.
309
- - **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.)
491
+ - **Rolling back reverts more than the code.** Restoring a version also restores settings, visibility, and data source connections as they were at that point. Action permissions are the exception: per Softr (2026-10-01) a restore recompiles, so the restored block's Actions come back at the default permissions, not as they were.
492
+ - **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.) The defaults: ADD_RECORD follows the block's own visibility, while UPDATE_RECORD and DELETE_RECORD are reset to logged-in users (per Softr, 2026-10-01).
310
493
  - **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.
311
494
 
312
495
  ## Application management tools
313
496
 
314
- The Applications area goes well beyond reads (all roster-verified 2026-08-31; behavior not individually exercised):
497
+ The Applications area goes well beyond reads (roster as delivered 2026-10-01; behavior not individually exercised unless stated):
315
498
 
316
499
  | Group | Tools |
317
500
  |---|---|
318
- | 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` |
319
- | App users | `add_application_user`, `remove_application_user`, `list_user_groups` |
320
- | Pages / blocks / permissions | `list_pages`, `get_page`, `get_page_permissions`, `get_access_control`, `get_block` |
321
- | Publish / preview | `preview_app`, `publish_app` |
322
- | Workspace | `list_workspaces`, `get_workspace_integrations` (distinct from the [integrations drill-down](#browsing-integrations-external-data-sources) below) |
323
-
324
- 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.)
325
-
326
- **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**.
327
-
328
- > **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.
501
+ | Apps | `application_list`, `application_get`, `application_create` (create a whole app via MCP), `application_set_name`, `application_set_subdomain`, `application_set_domain`, `application_set_login`, `application_configure_sign_up`, `application_configure_email_sender`, `application_update_data_source` (point/swap the app's data source), `application_update_pwa_settings` (installable-app name, short name and theme colour; new 2026-10-01) |
502
+ | App users and groups | `application_list_users`, `application_add_user`, `application_remove_user`, `application_set_user_activation`, `application_list_user_groups`, `application_create_user_group`, `application_update_user_group`, `application_delete_user_group`, `application_create_user_connection`, `application_get_user_connection`, `application_remove_user_connection` |
503
+ | Pages / blocks / permissions | `application_page_list`, `application_page_get`, `application_page_create`, `application_page_get_block`, `application_page_get_permissions`, `application_get_access_overview` (user groups plus counts of redirections and data restrictions) |
504
+ | Publish / preview | `application_preview`, `application_publish` |
505
+ | Workspace | `workspace_list`, `workspace_list_email_senders`, `get_workspace_integrations` (distinct from the [integrations drill-down](#browsing-integrations-external-data-sources) below) |
506
+
507
+ Combined with the database tools (`database_create` / `database_create_table` / `database_create_field`) and `vibe_coding_block_create` + `application_publish`, 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.)
508
+
509
+ **Etiquette from the server's own instructions:** after changing a block, link the page as `https://studio.softr.io/applications/{applicationId}/pages/{pageId}`; offer `application_preview` or `application_publish`, but **only publish when the user asks**.
510
+
511
+ > **application_preview 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 `application_preview` call rather than re-sending an old link. Never paste a preview link into a shared channel.
512
+ >
513
+ > **A preview link also pins the app version.** Its URL carries `&version=<n>`, so it keeps serving the version it was minted for. That is by design, not a caching bug. After every push, mint a new link before you check anything.
514
+
515
+ **Reading pages and blocks:**
516
+
517
+ - `application_page_get` lists a page's blocks **in page order, with no `order` field**. That field
518
+ was always `null` and was removed on 2026-10-01 (verified live that day; the tool's description
519
+ still mentions it). For a block nested in a column or tab container, the container's slots set its
520
+ position, not its place in the list.
521
+ - **A block created over MCP still lands at the bottom of the page**, and no tool places or reorders
522
+ blocks yet. Softr has said placement will come later. Until then, a human drags it into place in
523
+ Studio. Say so when you hand the block over.
524
+ - **Timestamps are UTC with a `Z`.** Since 2026-10-01, timestamps such as `publishedAt` or a
525
+ version's `createdAt` are ISO-8601 UTC with millisecond precision on both server kinds (verified
526
+ on `vibe_coding_block_list_versions` that day). Before then, studio-side timestamps came back with
527
+ no zone designator and nine fractional digits (`2026-09-09T22:34:11.157881061`). They were UTC, so
528
+ read any older logged value as UTC, never as local time.
329
529
 
330
530
  ### Testing as any app user without logins — the "Preview as" switcher
331
531
 
332
- *Verified live 2026-09-18.* The `preview_app` link does not open the app directly: it opens a
532
+ *Verified live 2026-09-18.* The `application_preview` link does not open the app directly: it opens a
333
533
  **toolbar shell** with a **"Preview as" user switcher**, and runs the draft app in an **iframe**
334
534
  whose URL carries `?autoUser=true`. That is a complete role-testing rig — every user group, no
335
535
  passwords, no test accounts to create:
@@ -360,70 +560,105 @@ the shell if a selector stops matching rather than assuming the feature is gone.
360
560
 
361
561
  ## Browsing integrations (external data sources)
362
562
 
363
- 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:
563
+ An integration is anything connected once per workspace. **A data source is one kind of integration**: one that holds records, such as Softr's own databases (`SOFTR_TABLES`), Airtable, Google Sheets, Notion or a SQL database. A proxy-only integration (Gmail, Slack, OpenAI, …) has no records and nothing to browse. Each entry from `integration_list` carries `capabilities` (`DATA`, `PROXY`, or both) that says which kind it is, so read that rather than guessing from the type. Five read-only tools drill down from workspace to fields; each level needs an ID from the level above:
364
564
 
365
565
  ```
366
- list_data_sources workspace's integrations
367
- └── list_data_source_databases a base, spreadsheet, or database
368
- └── list_data_source_schemas SQL schemas — Supabase only (usually just `postgres`)
369
- └── list_data_source_tables tables or sheets
370
- └── list_data_source_table_fields fields, types, options, primary field
566
+ integration_list the workspace's integrations: id, name, type, capabilities
567
+ └── integration_list_databases the top level of one data source: an Airtable base, a spreadsheet,
568
+ │ an Excel workbook, a Notion database, a Coda doc, a SQL database, …
569
+ └── integration_list_schemas only for SUPABASE, POSTGRESQL, SQL_SERVER, SNOWFLAKE (a SQL schema),
570
+ │ GOOGLE_BIGQUERY (a dataset), SMARTSUITE (a solution), CLICKUP (a space)
571
+ └── integration_list_tables tables or sheets
572
+ └── integration_list_table_fields fields, types, options, primary field
371
573
  ```
372
574
 
373
- **Only five integration types are browsable/connectable through MCP today:** Softr Databases, Airtable, Google Sheets, Notion, and Supabase. Anything else still appears in `list_data_sources` but must be connected through the block's **Source** tab in Studio, with schema discovery via the manual workflows in [../datasources/fields.md](../datasources/fields.md#field-inspector-block).
374
-
375
- `list_data_source_table_fields` also tells you **how fields must be referenced in `q.select()`**:
575
+ **Which types can be browsed and connected through MCP.** Per the tools' own descriptions
576
+ (2026-10-01): `SOFTR_TABLES`, `AIRTABLE`, `GOOGLE_SHEET`, `MICROSOFT_EXCEL`, `NOTION`, `MONDAY`,
577
+ `SMARTSUITE`, `CLICKUP`, `CODA`, `HUBSPOT`, `SALESFORCE`, `ZOHO_CRM`, `REST_API`, `GOOGLE_BIGQUERY`, and
578
+ the SQL vendors `SUPABASE`, `POSTGRESQL`, `MYSQL`, `MARIADB`, `SQL_SERVER`, `SNOWFLAKE` and `XANO_SQL`.
579
+ Anything else `integration_list` returns is proxy-only. This replaces the five types (Softr Databases,
580
+ Airtable, Google Sheets, Notion, Supabase) this file listed on 2026-08-31. Only the Softr Databases path
581
+ has been exercised end to end by us. For the flat sources (HubSpot, Salesforce, Zoho CRM, REST API)
582
+ the "database" and the "table" are the same object, so `integration_list_tables` echoes back what you
583
+ picked. For Softr's own databases these tools only resolve IDs for
584
+ `vibe_coding_block_connect_data_source`; for records, fields and aggregates use the
585
+ [database tools](#softr-database-tools).
586
+
587
+ **How fields must be referenced in `q.select()`.** Once a source is wired to a block, the
588
+ authoritative answer is that block's own `dataSources[].fieldReferenceKey` (`id` or `name`) in
589
+ `vibe_coding_block_get_code`. Before that, `integration_list_table_fields` tells you, and this is what
590
+ we verified earlier:
376
591
 
377
592
  | Integration | Reference fields by |
378
593
  |---|---|
379
594
  | Airtable, Google Sheets, Notion | Name |
380
595
  | Softr Databases, Supabase | ID (for Supabase, the SQL column name) |
381
596
 
382
- Getting this wrong **fails silently** — the code compiles, saves, and looks right in the builder, then returns nothing at page load. If a block renders but its data is empty, check this first.
597
+ For the types added since, read `fieldReferenceKey` rather than guessing. Getting this wrong **fails silently** — the code compiles, saves, and looks right in the builder, then returns nothing at page load. If a block renders but its data is empty, check this first.
383
598
 
384
599
  ## Softr Database tools
385
600
 
386
- 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.
601
+ For Softr's native databases the MCP goes far beyond browsing: `database_get_field_reference` (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** (`database_delete`, `database_delete_table`, `database_delete_field`), `database_list_views`, record reads (`database_list_records`, `database_search_records`, `database_get_record`), record writes (`database_create_record`, `database_create_records` batch, `database_update_record`, `database_delete_record`, `database_delete_records` batch), `database_aggregate_records` for grouped summaries, and `database_search` to find a database by name when the account has many.
387
602
 
388
- **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.)
603
+ **Call economy (from the server's own instructions):** `database_get_table` returns a table's metadata AND all its field definitions in one call; `database_list_fields` returns the fields alone. Call ONE of them once per table and reuse the result — never both — and re-fetch only after you changed the table's fields yourself.
389
604
 
390
- **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.
605
+ **`database_get_field_reference` (was `get_schema`).** It describes the whole product, not one table: it takes no table ID and returns the same content every time. Live-confirmed 2026-08-31: `readOnlyFieldTypes` = AUTONUMBER, COUNT, CREATED_AT, CREATED_BY, FORMULA, LOOKUP, RECORD_ID, ROLLUP, UPDATED_AT, UPDATED_BY. On 2026-10-01 the list was the same without COUNT, which no longer appears in either the read-only list or the field types. 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). On 2026-10-01 it listed `allowMultipleEntries` among the available options of SELECT and LINKED_RECORD. 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:** this reference 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 and BUTTON were still absent from the workspace reference on 2026-10-01, although, per Softr, the workspace server returns fields of those types. The operator NAMES differed too (workspace `GREATER_THAN` / `DOES_NOT_CONTAIN` vs per-app `GT` / `DOES_NOT_CONTAINS`, observed 2026-08-31); per Softr the per-app names were realigned on 2026-09-09, which we have not re-checked. Do not assume a filter payload is portable between the two kinds: call the reference of the server you are actually using.
391
606
 
392
607
  Known limits and behaviors (per official docs):
393
608
 
394
- - Record field keys are **field IDs**, not labels — `list_fields` maps between them.
609
+ - Record field keys are **field IDs**, not labels — `database_list_fields` maps between them.
395
610
  - 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.
396
- - **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.
397
- - **Attachment writes take a URL and copy the file.** `create_record` / `update_record` accept
611
+ - **Deletion now exists** (supersedes the earlier "nothing can be deleted through the MCP yet" finding): `database_delete_record`, `database_delete_records` (batch), `database_delete_field`, `database_delete_table`, and `database_delete` are all in the roster (verified 2026-08-31 under their old names), 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.
612
+ - **Attachment writes take a URL and copy the file.** `database_create_record` / `database_update_record` accept
398
613
  `{ filename, url }` on an ATTACHMENT field with any publicly reachable URL; Softr fetches it, stores its
399
614
  own copy and generates thumbnails, so backfilling images from another system is one write per record
400
615
  with no upload step. Verified 2026-08-26 — see [../datasources/writing.md](../datasources/writing.md#attachment).
401
- - **`update_field` silently ignores `allowMultipleEntries` nested inside `options`** — it is a TOP-LEVEL
402
- field property; the call succeeds and changes nothing (verified 2026-08-26; the write surface was
403
- re-touched live 2026-09-01 and the silent-no-op failure class held — see the SELECT-choices bullet
404
- below). To flip a LINKED_RECORD
405
- field between single and multi, `PUT` it via the Tables API with `allowMultipleEntries` at top level —
406
- and always echo `options.inverseLinkFieldId` in that PUT, because omitting it severs the inverse
407
- pairing. Full write-up, including the silent on-write clobbering of single-valued link pairs and the
408
- truthy-`[]` empty-link read shape:
616
+ - **Before 2026-10-01, every `update_field` call damaged the field it touched.** Per Softr, the
617
+ handler ignored the `options` it was sent and wrote `allowMultipleEntries: false` on every call, so
618
+ a multi-select became a single-select and a multi-link a single-link. It also cleared the field's
619
+ default value, and blanked its description whenever none was sent. This file used to say the tool
620
+ "silently ignores `allowMultipleEntries`" (2026-08-26) and "silently drops added SELECT choices"
621
+ (2026-09-01); both were the visible half of that. If the old tool was ever run against a table you
622
+ care about, read those fields back and check them.
623
+ - **Since 2026-10-01, `database_update_field` applies `options` on top of the current field** (per
624
+ Softr; not yet re-tested by us). Each key you pass replaces that key's stored value. Keys you leave
625
+ out are kept, `allowMultipleEntries` and the default value included. Unknown keys are rejected,
626
+ `allowMultipleEntries` now goes **inside `options`**, and an omitted description is left alone. A key
627
+ you pass still replaces its whole value: sending `choices` replaces the choice list, and the tool's
628
+ own description warns that narrowing it can orphan choices already held in records.
629
+ - **The proven way to add a SELECT choice is the Tables API, with a full body** (verified
630
+ 2026-09-09): `PUT /api/v1/databases/{db}/tables/{t}/fields/{f}` with
631
+ `{ name, type, options: { choices: [...], allowToAddNewChoice } }`, every existing choice carrying
632
+ its own `id` and the new one none (the server assigns it). A partial body returned 400 then. The
633
+ array order is kept, so a new choice can be slotted where it belongs rather than appended. Two other
634
+ routes: add it in Studio, or, when the field has `allowToAddNewChoice` enabled, the first record write
635
+ with an unknown label creates the choice.
636
+ - **Flipping a LINKED_RECORD between single and multi through the Tables API** (verified
637
+ 2026-08-26): `PUT` the field with `allowMultipleEntries` at top level, and always echo
638
+ `options.inverseLinkFieldId` in that PUT, because omitting it severs the inverse pairing. Full
639
+ write-up, including the silent on-write clobbering of single-valued link pairs and the truthy-`[]`
640
+ empty-link read shape:
409
641
  [../datasources/writing.md](../datasources/writing.md#linked-record-write-traps-verified-live-2026-08-26).
410
- - **`update_field` on a SELECT field silently drops added choices** — the call succeeds and echoes the
411
- OLD choice set back, no error (verified live 2026-09-01; the second known silent no-op on this write
412
- surface, joining the `allowMultipleEntries` finding above). New choices must be added in Studio —
413
- or, when the field has `allowToAddNewChoice` enabled, the first record write with an unknown label
414
- auto-creates the choice.
415
- - Limits: 100 records per `create_records` call, 200 records per read (silently capped, not an error), 2 group-by fields in `aggregate_data`. For big tables prefer a filter or aggregate over paging.
642
+ Read any field back after changing it, whichever route you used.
643
+ - **`database_update_table` sends only what you pass, since 2026-10-01.** Before then, per Softr,
644
+ `update_table` wrote an empty value over whichever of name and description it was not given, so a
645
+ plain rename blanked the description. Now a blank name is ignored and a blank description clears it.
646
+ A table's `updatedAt` also advances on every write now. It never did before, so an older `updatedAt`
647
+ is no evidence that nothing changed.
648
+ - **Field descriptions are readable** since 2026-10-01 (per Softr). Before then a description
649
+ could be written but no read returned it.
650
+ - Limits: 100 records per `database_create_records` call, 200 records per read (silently capped, not an error), 2 group-by fields in `database_aggregate_records`. For big tables prefer a filter or aggregate over paging.
416
651
 
417
652
  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.
418
653
 
419
654
  ## Workflows
420
655
 
421
- 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; the full build → wire → test → publish loop **exercised end to end 2026-09-01** — 10 production workflows built live through MCP; see the build-loop findings below):
656
+ Softr Workflows are automations built from trigger + action nodes, and the MCP can build, wire, test, and publish them — a **28-tool suite** (26 roster-verified 2026-08-31, plus `test_workflow` and `update_node_retry` in the 2026-10-01 roster; these tools kept their names in the 2026-10-01 rename; the full build → wire → test → publish loop **exercised end to end 2026-09-01** — 10 production workflows built live through MCP; see the build-loop findings below):
422
657
 
423
658
  | Group | Tools |
424
659
  |---|---|
425
- | Workflow lifecycle | `create_workflow`, `get_workflow`, `get_workflow_url`, `list_workflows`, `rename_workflow`, `update_workflow_configuration`, `publish_workflow`, `unpublish_workflow` |
426
- | 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` |
660
+ | Workflow lifecycle | `create_workflow`, `get_workflow`, `get_workflow_url`, `list_workflows`, `rename_workflow`, `update_workflow_configuration`, `publish_workflow`, `unpublish_workflow`, `test_workflow` |
661
+ | 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`, `update_node_retry` |
427
662
  | Discovery / testing | `list_node_types`, `get_node_specifications`, `get_dynamic_input_options`, `test_node`, `get_node_output` |
428
663
 
429
664
  **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:
@@ -438,7 +673,7 @@ Softr Workflows are automations built from trigger + action nodes, and the MCP c
438
673
 
439
674
  - Node inputs can embed **references to another node's runtime output**, a loop's current item, or named date/time tokens.
440
675
  - **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. See the test-safety rules under build-loop findings below before testing anything against a production workspace.
441
- - **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}`.
676
+ - **Workflows are workspace-level, not part of an app**: `application_preview` / `application_publish` do not apply. Link a workflow as `https://studio.softr.io/workflow/{workflowId}`.
442
677
 
443
678
  **Build-loop findings (verified live 2026-09-01, first end-to-end production build — 10 workflows):**
444
679
 
@@ -457,17 +692,19 @@ Softr Workflows are automations built from trigger + action nodes, and the MCP c
457
692
 
458
693
  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.
459
694
 
460
- **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`.
695
+ **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`. These are this server's own names, last enumerated by us in late August 2026; the 2026-10-01 rename of the workspace server did not touch them.
696
+
697
+ **If the tool definitions arrive empty, do not conclude the server sent them that way.** Every definition of these tools our client ever recorded had a name-only description and an `{"type":"object"}` schema, the same signature as the stubs described [above](#the-array-argument-rejection-and-why-it-is-a-security-issue). Unlike the workspace stubs, these were stubs even at times when the same client held real workspace definitions, so their cause is not settled. Per Softr, these servers publish full schemas and descriptions. A tool with no arguments (`list_tables`, `get_schema`, `get_current_user`) works either way; before relying on one that takes arguments, start a fresh session and check the loaded definition again.
461
698
 
462
699
  **Live-observed semantics:**
463
700
 
464
- - **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").
701
+ - **The table catalog is derived from the app itself.** `list_tables` returns only tables a block on one of the app's pages is bound to, so an empty list means nothing is bound yet, not that the app has no data. Each entry's `tableId` and `tableName` came back as the same long `key=value` resource string (reported to Softr in September 2026; not among its 2026-10-01 fixes). Every table it does return comes 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").
465
702
  - An app may connect **multiple distinct data sources**; always call `list_tables` first for the full catalog before concluding data doesn't exist.
466
703
  - `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).
467
- - **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`).
704
+ - **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 `database_list_fields`).
468
705
  - 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.
469
- - **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.
470
- - 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).
706
+ - **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 (the workspace server has used the same format since 2026-10-01); 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.
707
+ - Filter operators, as observed 2026-08-31, used 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. Per Softr the per-app operator names were realigned on 2026-09-09, so call this server's `get_schema` before building a filter rather than trusting either list — see the schema-drift caution in [Softr Database tools](#softr-database-tools).
471
708
 
472
709
  **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.
473
710
 
@@ -475,7 +712,7 @@ A separate product class from the workspace server (live-observed 2026-08-31 on
475
712
 
476
713
  When generating a block, pick the delivery path by what's connected:
477
714
 
478
- 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).
715
+ 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: `vibe_coding_block_create` (or `vibe_coding_block_update_code` for edits), then `vibe_coding_block_connect_data_source` to wire up the data. Verify every push by its `sourceSha256` ([protocol](#verifying-a-push--the-deployed-source-is-the-only-proof)), and remember the action-permissions reset after every code push. A newly created block lands at the bottom of its page, so tell the user to drag it into place in Studio. After deploying, link the Studio page (`https://studio.softr.io/applications/{applicationId}/pages/{pageId}`) and offer `application_preview` / `application_publish` — publish only when asked, and mind the [preview-link auth warning](#application-management-tools).
479
716
  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.
480
717
 
481
718
  Either way, never deliver code inline in chat (JSX character corruption — see SKILL.md workflow step 5).