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 +3 -0
- package/README.md +4 -2
- package/SKILL.md +21 -12
- package/datasources/multi-datasource.md +1 -1
- package/datasources/softr-database.md +2 -2
- package/datasources/writing.md +7 -5
- package/package.json +1 -1
- package/references/anti-patterns.md +3 -1
- package/references/editable-settings.md +1 -1
- package/references/printing.md +3 -3
- package/references/softr-mcp.md +365 -128
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
|
|
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 `
|
|
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 — `
|
|
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 —
|
|
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
|
|
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
|
|
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 `
|
|
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.
|
|
564
|
-
|
|
565
|
-
|
|
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
|
|
569
|
-
Check the page's own VIEW permission first (`
|
|
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 `
|
|
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 `
|
|
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
|
|
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 `
|
|
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
|
package/datasources/writing.md
CHANGED
|
@@ -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.
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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
|
|
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.
|
|
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 `
|
|
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 `
|
|
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
|
|
package/references/printing.md
CHANGED
|
@@ -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.**
|
|
501
|
-
|
|
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
|
package/references/softr-mcp.md
CHANGED
|
@@ -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
|
-
`
|
|
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 `
|
|
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 | `
|
|
78
|
-
| Edit code | `
|
|
79
|
-
| Settings / visibility | `
|
|
80
|
-
| Versions | `
|
|
81
|
-
| Data sources | `
|
|
82
|
-
| Any 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
|
-
`
|
|
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 — **
|
|
109
|
-
the mirror
|
|
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
|
|
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
|
-
`
|
|
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
|
|
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 `
|
|
132
|
-
`
|
|
133
|
-
|
|
134
|
-
|
|
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. **
|
|
145
|
-
|
|
146
|
-
|
|
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
|
|
158
|
-
|
|
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.
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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**
|
|
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
|
-
| `
|
|
190
|
-
| `
|
|
191
|
-
|
|
192
|
-
**Where the string comes from —
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
session every
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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.
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
227
|
-
|
|
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. `
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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 / `
|
|
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 `
|
|
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 (
|
|
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 | `
|
|
319
|
-
| App users | `
|
|
320
|
-
| Pages / blocks / permissions | `
|
|
321
|
-
| Publish / preview | `
|
|
322
|
-
| Workspace | `
|
|
323
|
-
|
|
324
|
-
Combined with the database tools (`
|
|
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 `
|
|
327
|
-
|
|
328
|
-
> **
|
|
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 `
|
|
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
|
|
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
|
-
|
|
367
|
-
└──
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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
|
-
**
|
|
374
|
-
|
|
375
|
-
`
|
|
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: `
|
|
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):** `
|
|
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
|
-
|
|
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 — `
|
|
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): `
|
|
397
|
-
- **Attachment writes take a URL and copy the file.** `
|
|
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
|
-
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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 **
|
|
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**: `
|
|
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
|
|
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 `
|
|
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
|
|
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: `
|
|
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).
|