softr-vibe-coding 1.11.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,16 @@ 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.1.0] - 2026-08-25
8
+ - Bump to 2.1.0 — official dev-guide sync (field-name rule for Airtable/Notion/Sheets, PHONE sanitization, useProxyFetch datasource routing, useCurrentUser properties) + workspace-wide Softr MCP reference with direct block deployment (softr-mcp.md replaces softr-database-mcp.md)
9
+ - Sync with the official Vibe Coding developer guide and the workspace-wide Softr MCP server (28 verified fixes). Field-name rule extended: Airtable AND Notion AND Google Sheets use field NAMES in q.select (Softr DB/Supabase use IDs; wrong form fails silently with empty data) — fixed in Hard Constraint 11, notion.md, helper-blocks.md publisher template (was modeling fldXXX IDs), fields.md. New PHONE write rule: + followed by digits only, Monday.com hard-rejects formatted values — official sanitizePhone added to writing.md (new Phone section), monday.md, anti-patterns.md. useProxyFetch is datasource-scoped: alias as its ARGUMENT (useProxyFetch(ds.store)), throws when omitted with >1 source; proxy payloads are text-only (no FormData/streams/uploads) — added to rest-api.md, multi-datasource.md, quick-reference.md, anti-patterns.md. fetchNextPage rule relaxed to match the guide's canonical Load More onClick: never in the render body, event handler or guarded useEffect both fine (SKILL.md x2, README, anti-patterns.md). useCurrentUser({ properties }) exposes custom user fields under user.properties — window.__softr_current_user narrowed to userGroups/role only; id only present with user sync (reading.md, quick-reference.md, fields.md). useLinkedRecords count default 100 max 1000. useUpload: isUploading + result.error handling + multi-file pattern. Linked-record field-type row corrected to string-array shape. TRIGGER_CUSTOM_WORKFLOW takes no destination per the documented type. references/softr-mcp.md REPLACES softr-database-mcp.md: the MCP is now workspace-wide — vibe coding block tools (get_vibe_coding_docs, create/edit/versions, data source wiring + the five official gotchas incl. action-permission reset on every code change), integrations browsing down to field level (Airtable/Sheets/Notion/Supabase/Softr DB only), databases tools (no deletes yet; 100-create/200-read/2-group-by limits), three-area bundled permission levels replacing the old granular scopes. SKILL.md workflow now offers direct MCP deployment alongside paste-into-Studio. Description gains negative scope (building-design-md, non-Softr dashboards); reading/writing/fields now linked directly from SKILL.md instead of only via the shared-patterns index; README tree gains the tools/ dir; retired "no ?." mention dropped from airtable-automations.md; common-patterns var-style sentence reworded to legacy-but-valid
10
+ - Bump to 2.0.0 — verified 2026-08 platform contract: modern TypeScript, mutateAsync write queues, label-string SELECT writes, flat create payloads, inline hook options, Actions reset on recompile
11
+
12
+ ## [1.12.0] - 2026-07-22
13
+ - Bump to 1.12.0 — multi-datasource blocks, Airtable rating writability, dark-brand canvas + page-background anti-patterns
14
+ - Document the Airtable rating field as writable (plain integer 0-max; 0 clears it, so AVERAGE formulas skip it) — it was absent from the Supported Fields table, which made it the main unknown when building a star-rating form. Add two dark-brand styling anti-patterns: a block on a dark brand MUST paint its own backgroundColor because custom-code-header's body rule doesn't cross the shadow-DOM boundary and a default Softr page is white, so white text/logos/buttons render invisibly on white (the existing don't-double-paint row assumed a light app and inverts here); and setting html,body alone doesn't change the page background because Softr paints the same fill on FOUR stacked layers — html, body, #page-content, and a class-less wrapper div inside it — so paint html then clear the duplicates, excluding the .softr-topbar subtree
15
+ - Add datasources/multi-datasource.md — a block can now connect to SEVERAL data sources: datasource.define({alias: 'uuid'}) plus a from: parameter on every data hook (useRecords, useRecord, useLinkedRecords, useFieldOptions, useMetric, useChartData, useRecordCreate/Update/Delete; not useUpload/useCurrentRecordId, which are app-level). Omitting from: throws once >1 source is connected; with exactly one you can skip define entirely. The define() values must be INLINE STRING LITERALS — hoisting them into constants fails to compile with 'datasource.define() object values must be string literals', same static-analysis rule as q.select(). Ids are plain UUIDs, not the table id and not the ds_id_N placeholder shape in Softr's docs; obtain them by asking Studio's AI chat to WRITE CODE, never to recite a value — asked three times in prose for the same three connected tables it returned three different confidently-worded sets, once recycling another table's uuid. This supersedes the one-table-per-block limit: relax Hard Constraint #13 to one useRecords per DATASOURCE, reframe helper-blocks.md around genuinely cross-BLOCK jobs (triggering another block, sharing computed state) with the old rationale kept as history, and retarget the useLinkedRecords anti-pattern at a second datasource rather than a helper
16
+
7
17
  ## [1.11.2] - 2026-07-08
8
18
  - Bump to 1.11.2 — publish recordId-less useRecord note and input.textAsync correction
9
19
  - Correct input.textAsync in airtable-automations.md — it is not a real method in EITHER Airtable scripting environment; calling it in the Scripting Extension throws TypeError: input.textAsync is not a function. buttonsAsync is the only interactive runtime prompt; free-text values (e.g. an API key) go through an input.config({...}) setting at the top or a hardcoded constant. Also generalize the Automation Scripts bullet to "no interactive prompts".
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Softr Vibe Coding Block — Claude Skill
2
2
 
3
- > Turn Claude into a Softr Vibe Coding expert — generate production-ready JSX blocks with polished UI, correct data fetching, and all 14 Softr data sources supported out of the box.
3
+ > Turn Claude into a Softr Vibe Coding expert — generate production-ready React blocks (TSX/JSX) with polished UI, correct data fetching, and all 14 Softr data sources supported out of the box.
4
4
 
5
5
  ![Claude Code Skill](https://img.shields.io/badge/Claude_Code-Skill-blue)
6
6
  ![Version](https://img.shields.io/npm/v/softr-vibe-coding?label=version&color=green)
@@ -12,7 +12,7 @@
12
12
 
13
13
  ## TL;DR
14
14
 
15
- This Claude skill teaches Claude Code how to generate complete, polished Softr Vibe Coding blocks as `.jsx` files. It includes:
15
+ This Claude skill teaches Claude Code how to generate complete, polished Softr Vibe Coding blocks as `.tsx` / `.jsx` files (the current platform compiles TypeScript with modern syntax — verified live August 2026). It includes:
16
16
 
17
17
  - **Complete Vibe Coding API reference** — `useRecords`, `q.select()`, mutations, uploads, metrics, charts, editable settings, `useProxyFetch` for REST APIs
18
18
  - **All 14 Softr data sources** — Airtable, Softr Database, Google Sheets, HubSpot, Notion, Coda, monday.com, SmartSuite, ClickUp, Xano, Supabase, BigQuery, SQL Database, and REST API — each with field mapping, rate limits, and gotchas
@@ -20,10 +20,10 @@ This Claude skill teaches Claude Code how to generate complete, polished Softr V
20
20
  - **Advanced integrations** — Shadow DOM CSS isolation for third-party libraries (Leaflet, Mapbox, TinyMCE, Quill, FullCalendar)
21
21
  - **Native shell styling** — re-skin Softr's native top bar, **footer**, nav, dropdowns, and **page background** via global Custom Code CSS (stable selectors vs. hashed classes, floating "island" header/footer, the dropdown grid fix, the multi-layer page-background stacking, restyle-vs-replace) — distinct from blocks
22
22
  - **UI/UX design guidelines** — 26 sections covering visual hierarchy, color, typography, spacing, motion design, accessibility, responsive patterns, and an AI slop anti-pattern checklist
23
- - **Self-validation** — Claude checks for Softr bundler compatibility (no optional chaining, correct imports, container wrappers, `getFieldValue()` wrapping, hooks ordering) before delivering code
23
+ - **Self-validation** — Claude checks for Softr platform compatibility (inline hook options, correct payload shapes, correct imports, container wrappers, `getFieldValue()` wrapping, hooks ordering) before delivering code
24
24
  - **Premium visual baseline** — Every block ships polished from v1: gradient backgrounds, card elevation, loading skeletons, empty states, error states
25
25
  - **Debug utilities** — Field Inspector, API Response Inspector, and User Inspector blocks for diagnosing data source and permissions issues
26
- - **Softr Database MCP integration** — when the [official Softr MCP server](https://docs.softr.io/mcp-server) is installed (`claude mcp add --transport http softr https://mcp.softr.io/mcp`), Claude reads Softr DB schema, field IDs, and dropdown option UUIDs directly — no more pasting `tablespace-with-tables` JSON. See `references/softr-database-mcp.md`.
26
+ - **Softr MCP integration** — when the [official Softr MCP server](https://docs.softr.io/mcp/overview) is installed (`claude mcp add --transport http softr https://mcp.softr.io/mcp`), Claude reads Softr DB schema, field IDs, and dropdown option UUIDs directly, browses connected Airtable / Google Sheets / Notion / Supabase integrations down to field level, and can even create and deploy Vibe Coding blocks straight into your app — no more pasting `tablespace-with-tables` JSON or copy-pasting code into Studio. See `references/softr-mcp.md`.
27
27
 
28
28
  ---
29
29
 
@@ -91,7 +91,7 @@ Build me a Softr Vibe Coding block that shows a team directory with cards
91
91
  This skill is **Step 2** of a two-skill brand-to-blocks pipeline:
92
92
 
93
93
  ```
94
- New client → building-design-md (brand → DESIGN.md) → softr-vibe-coding (DESIGN.md → JSX blocks) → shipped Softr app
94
+ New client → building-design-md (brand → DESIGN.md) → softr-vibe-coding (DESIGN.md → blocks) → shipped Softr app
95
95
  ```
96
96
 
97
97
  The upstream skill is [`building-design-md`](https://github.com/leo-softr/design-md-extractor-skill), which extracts a brand foundation (colors, typography, voice) from a website URL or brand guide into a portable `DESIGN.md` file. When that file exists in your project folder, this skill picks it up automatically and applies the brand tokens throughout every block it generates — no re-asking about colors or fonts.
@@ -152,8 +152,8 @@ Create a contact form that creates records in our Airtable Contacts table
152
152
 
153
153
  1. **Asks only what it needs** — data source type and field IDs. Everything else (folder, colors, filename) uses smart defaults.
154
154
  2. **Loads the relevant guides** — reads the specific data source guide (Airtable, REST API, etc.) and reference files (helper blocks, Shadow DOM) as needed.
155
- 3. **Generates a complete `.jsx` file** — production-ready, visually polished, with loading/error/empty states. Never delivers code inline (prevents JSX character corruption).
156
- 4. **Self-validates** — checks 13 items including Softr bundler compatibility, `getFieldValue()` wrapping, hooks ordering, and mutation patterns before delivering code.
155
+ 3. **Generates a complete block file** (`.tsx` preferred) — production-ready, visually polished, with loading/error/empty states. Never delivers code inline (prevents JSX character corruption).
156
+ 4. **Self-validates** — runs a platform-compatibility checklist (`getFieldValue()` wrapping, hooks ordering, payload shapes, inline hook options) before delivering code.
157
157
 
158
158
  ---
159
159
 
@@ -163,7 +163,7 @@ Create a contact form that creates records in our Airtable Contacts table
163
163
  softr-vibe-coding/
164
164
  ├── SKILL.md # Main skill (330 lines)
165
165
  │ # Workflow, code structure, visual baseline,
166
- │ # components, settings, 20 hard constraints
166
+ │ # components, settings, 21 hard constraints
167
167
 
168
168
  ├── ui-ux-guidelines.md # Design reference (746 lines)
169
169
  │ # 26 sections: hierarchy, color, typography,
@@ -179,10 +179,10 @@ softr-vibe-coding/
179
179
  │ │ # Scripting Extension, cross-table cascades,
180
180
  │ │ # batch update gotchas, field-ID discipline,
181
181
  │ │ # Airtable formulas
182
- │ ├── softr-database-mcp.md # Softr Database MCP server (sibling to
183
- │ │ # datasources/softr-database.md; AI-assisted
184
- │ │ # schema discovery, field-ID lookup, OAuth
185
- │ │ # install, scope limitations)
182
+ │ ├── softr-mcp.md # Official Softr MCP server vibe coding block
183
+ │ │ # tools (create/edit/version/deploy), integrations
184
+ │ │ # browsing (Airtable/Sheets/Notion/Supabase),
185
+ │ │ # Softr DB schema + record tools, auth, permissions
186
186
  │ ├── advanced-integrations.md # Shadow DOM CSS isolation (69 lines)
187
187
  │ │ # Leaflet, Mapbox, TinyMCE, Quill, FullCalendar
188
188
  │ ├── native-chrome-styling.md # Restyle Softr's native shell (header, footer,
@@ -202,13 +202,19 @@ softr-vibe-coding/
202
202
  │ # Imports, hook signatures, mutation shapes,
203
203
  │ # field mapping, component skeleton
204
204
 
205
+ ├── tools/ # Bundled CLI scripts (run, not read)
206
+ │ ├── get-airtable-base # Full Airtable base schema export (bash + jq)
207
+ │ └── get-softr-database.py # Full Softr DB schema export (Python stdlib)
208
+
205
209
  └── datasources/ # Data source guides (loaded on demand)
206
210
  ├── overview.md # Comparison matrix, selection guide
207
- ├── shared-patterns.md # Index → reading, writing, fields
211
+ ├── shared-patterns.md # Index → multi-datasource, reading, writing, fields
212
+ ├── multi-datasource.md # Several data sources in ONE block: datasource.define(),
213
+ │ # the from: parameter, getting the datasource UUIDs
208
214
  ├── reading.md # useRecords, filtering, sorting, pagination,
209
215
  │ # metrics, charts, current user (198 lines)
210
- ├── writing.md # Mutations, uploads, linked record format,
211
- │ # cross-table REST API writes (146 lines)
216
+ ├── writing.md # Mutations, sequential write queues, uploads,
217
+ │ # linked record format, cross-table writes
212
218
  ├── fields.md # getFieldValue(), field type shapes, record
213
219
  │ # structure, debug utilities (160 lines)
214
220
  ├── rest-api.md # useProxyFetch + useQuery (full docs)
@@ -254,17 +260,21 @@ Only `SKILL.md` loads into Claude's context when the skill triggers (~330 lines)
254
260
 
255
261
  ---
256
262
 
257
- ## Key Softr Bundler Constraints
263
+ ## Key Softr Platform Constraints
258
264
 
259
- The skill enforces these automatically, but good to know:
265
+ The skill enforces these automatically, but good to know (verified live against the platform, August 2026):
260
266
 
261
- - No optional chaining (`?.`) or nullish coalescing (`??`) Softr's bundler fails on these
267
+ - Modern TypeScript compiles — optional chaining, nullish coalescing, arrows, `const`, generics are all fine (the old `var`-only / no-`?.` rules are retired)
268
+ - Data hook options must be **inline object literals** — `useRecords(opts)` with a variable or wrapper fails to compile
269
+ - Create payloads are **flat**; update payloads are `{ recordId, fields: {...} }` — asymmetric by design
270
+ - `mutateAsync` is fully supported — it's the tool for sequential multi-row saves
271
+ - SELECT fields write by option **label string**; linked records write as arrays of record-id strings
272
+ - Every code recompile resets the block's auto-registered Actions to default permissions — tighten permissions after the last redeploy
262
273
  - No `import React from 'react'` — use named imports (`import { useState } from "react"`)
263
- - No arrow functions in JSX callback props — use `function() {}`
264
274
  - Must use `export default function Block()`
265
- - Must wrap layout in `<div className="container py-6"><div className="content">`
266
- - Only ONE `useRecords` call per block (use helper blocks for multi-table)
267
- - `fetchNextPage` only inside `useEffect`in render body causes infinite loops
275
+ - Must wrap layout in `<div className="container py-0"><div className="content">`
276
+ - Only ONE `useRecords` call per **datasource** — but a block can connect to several sources; declare them with `datasource.define()` and pass `from:` on every hook
277
+ - `fetchNextPage` never in the render body (infinite loop) call it from an event handler (Load More `onClick`) or a guarded `useEffect`
268
278
  - All hooks declared before any conditional `return` — React error #310
269
279
  - Every field value rendered in JSX must pass through `getFieldValue()`
270
280
  - Mutations use `recordId` (not `id`) and always call `refetch()` in `onSuccess`
package/SKILL.md CHANGED
@@ -1,12 +1,13 @@
1
1
  ---
2
2
  name: softr-vibe-coding
3
3
  description: >
4
- Generate custom Softr Vibe Coding blocks as complete JSX components. Use this skill whenever the user
4
+ Generate custom Softr Vibe Coding blocks as complete React components (TSX/JSX). Use this skill whenever the user
5
5
  mentions Softr, Vibe Coding, Softr blocks, or wants to build a custom UI component for a Softr app.
6
6
  Also trigger when the user asks to create cards, lists, forms, dashboards, charts, detail pages,
7
7
  or any interactive block intended for Softr — even if they don't say "Vibe Coding" explicitly.
8
8
  If the user mentions Softr in the context of building a custom UI component, creating a JSX block,
9
- or vibe coding, use this skill.
9
+ or vibe coding, use this skill. Do NOT use for extracting brand tokens or producing a DESIGN.md
10
+ (that is the building-design-md skill), or for charts/dashboards with no Softr app involved.
10
11
  when_to_use: >
11
12
  Triggers on "build me a Softr block", "create a card component", "make a dashboard",
12
13
  "vibe code this", "custom block for Softr", "JSX component for Softr app",
@@ -18,7 +19,7 @@ allowed-tools: Read Write Glob Grep Bash
18
19
 
19
20
  # Softr Vibe Coding Block Generator
20
21
 
21
- You generate complete, production-ready Softr Vibe Coding blocks as JSX files. A Vibe Coding block is a JavaScript file with a default-exported React component that runs exclusively in the browser inside a Softr app.
22
+ You generate complete, production-ready Softr Vibe Coding blocks as TypeScript React files. A Vibe Coding block is a single file with a default-exported React component, compiled by Softr's server and run in the browser inside a Softr app. The current platform compiles TypeScript with modern syntax — optional chaining (`?.`), nullish coalescing (`??`), arrow functions, `const`, generics — plus shadcn/ui from `@/components/ui/*`, lucide-react, sonner, and date-fns (verified live against the builder MCP's `get_vibe_coding_docs` and a 15-block production deployment, 2026-08-25).
22
23
 
23
24
  > **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).
24
25
 
@@ -55,14 +56,16 @@ You generate complete, production-ready Softr Vibe Coding blocks as JSX files. A
55
56
  3. **Apply defaults for the rest, don't ask.** Infer these from context instead of asking:
56
57
  - **Project folder**: Derive from the block description (e.g., "partner-portal", "client-dashboard"). If the user has already specified a folder in this session, reuse it.
57
58
  - **Brand colors**: Use whatever was chosen in Step 1 — DESIGN.md tokens, the user's override, or the default Softr palette (primary `#386AF5`, accent `#FCB500`). Never silently fall back to defaults.
58
- - **Filename**: Derive from the block purpose (e.g., `partner-invite.jsx`, `team-directory.jsx`). The user can rename later.
59
+ - **Filename**: Derive from the block purpose (e.g., `partner-invite.tsx`, `team-directory.tsx`). The user can rename later.
59
60
 
60
61
  4. **Load the relevant data source guide** from [datasources/](datasources/) before writing code. Read the specific guide for the user's data source type.
61
62
 
62
- 5. **Write the complete `.jsx` file** 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.
63
+ 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.
64
+
65
+ **Delivery path:** if the official Softr MCP server is connected with Applications & Forms full access, offer to deploy the block directly after writing the file — `create_vibe_coding_block` (or `update_vibe_coding_block_code` for edits) plus `connect_vibe_coding_block_data_source` to wire the data. The local `.tsx` file stays the source of truth. Remember: every code push resets the block's Action permissions to defaults (Hard Constraint 21), and a block whose data source isn't connected saves fine but errors at page load. Details in [references/softr-mcp.md](references/softr-mcp.md).
63
66
 
64
67
  6. **Self-validate before delivering.** Before presenting the code as complete, verify:
65
- - No optional chaining (`?.`) or nullish coalescing (`??`)
68
+ - 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
66
69
  - All imports use named imports (no `import React from 'react'`)
67
70
  - `export default function Block()` is present
68
71
  - Container + content wrappers present (`<div className="container py-0"><div className="content">`)
@@ -74,10 +77,10 @@ You generate complete, production-ready Softr Vibe Coding blocks as JSX files. A
74
77
  - All hooks declared before any conditional `return` -- prevents React error #310
75
78
  - Sub-components (FieldLabel, TextInput, ChipButton, SectionCard, etc.) defined at **module scope**, NOT inside `Block()` -- prevents inputs losing focus after one keystroke (each render creates a new component identity, React unmounts/remounts the `<input>`)
76
79
  - When a custom DESIGN.md is in use, brand `fontFamily` (and any non-inherited brand defaults) set as an **inline style on the block's outermost wrapper** `<div>`, not relied on from `custom-code-header.html` -- Vibe Coding blocks render inside a shadow DOM and `html, body` rules don't cross that boundary. Per-element overrides (e.g. Fraunces serif on h1) still set inline at the element.
77
- - `fetchNextPage` only inside `useEffect`, never in render body
80
+ - `fetchNextPage` never called in the render body — only from an event handler (Load More `onClick` with `disabled={isFetching}`, the official pattern) or a guarded `useEffect` (auto-load-all)
78
81
  - Mutations use `recordId` (not `id`) and call `refetch()` in `onSuccess`
79
- - `useRecordUpdate` calls use `.mutate(payload, { onSuccess, onError })` — **NOT** `.mutateAsync(...).then(...).catch(...)`. Softr's Action parser only recognizes the `.mutate(` token; `.mutateAsync(` is invisible to it and the Action never gets derived (`enabled` stays `false`, Actions tab shows "No actions used in this block yet")
80
- - `useRecordUpdate` payload is `{ recordId, fields: { ... } }`nested, not flat. Field references inside flat payloads are invisible to the parser, same silent-failure mode as the `.mutateAsync` issue
82
+ - `useRecordUpdate` payload is `{ recordId, fields: { ... } }` — nested. `useRecordCreate` payload is **flat** (no `fields` wrapper). The two shapes are asymmetric by design (verified live 2026-08-25)
83
+ - Sequential multi-row saves use `await hook.mutateAsync(...)` per row, in order, with stop-on-failure + retry state`mutateAsync` is fully supported on the current platform (verified 2026-08-25; the old ".mutate() only" Action-parser rule is gone see [datasources/writing.md](datasources/writing.md))
81
84
  - No hardcoded domains in links -- use relative paths (`/page?recordId=...`)
82
85
 
83
86
  ## What to Clarify
@@ -86,7 +89,7 @@ When the user describes their block, figure out which of these areas apply and a
86
89
 
87
90
  - **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.
88
91
  - **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:
89
- - For **Softr Database**, the cleanest path is the **Softr Database 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. If no, the next-best option 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-database-mcp.md](references/softr-database-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).
92
+ - For **Softr Database**, the cleanest path is the official **Softr MCP server** — ask whether they have it installed (`claude mcp list` shows it as `softr` or similar). If yes, query schema directly with the MCP tools instead of asking for paste-ins. The same server also browses connected **Airtable, Google Sheets, Notion, and Supabase** integrations down to field level (`list_data_sources` → ... → `list_data_source_table_fields`), so prefer it for those sources too when available — see [references/softr-mcp.md](references/softr-mcp.md). If no MCP, the next-best option for Softr DB is the bundled **`get-softr-database` CLI script** — tell the user to run `python3 ~/.claude/skills/softr-vibe-coding/tools/get-softr-database.py <database_id>` (it prompts for their Softr API key and exports the full schema to `~/Desktop/softr-database-<id>-<timestamp>.json` — Python stdlib only, nothing to install) and paste the resulting JSON into chat. As a final fallback, ask them to paste the `tablespace-with-tables` network response (DevTools -> Network -> filter that string while on Studio's Data tab) — same JSON content, different acquisition path. Optionally tell them they can install the MCP once with `claude mcp add --transport http softr https://mcp.softr.io/mcp` for future sessions. Full MCP details in [references/softr-mcp.md](references/softr-mcp.md); CLI script details in [datasources/softr-database.md](datasources/softr-database.md#bundled-cli-script-get-softr-database); fallback paste-in workflows in [datasources/fields.md](datasources/fields.md#field-inspector-block).
90
93
  - 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).
91
94
  - For other non-Softr-DB sources where empty `q.select({})` works, suggest the Field Inspector block.
92
95
  - **Brand colors**: Already resolved in Step 1 (Detect the brand source). Don't re-ask. The brand source is one of:
@@ -141,7 +144,9 @@ Softr supports 14 data sources. **Before writing any data-fetching code, read th
141
144
  | SQL Database | [datasources/sql-database.md](datasources/sql-database.md) | `useRecords` + `q.select()` |
142
145
  | REST API | [datasources/rest-api.md](datasources/rest-api.md) | `useProxyFetch` + `useQuery` |
143
146
 
144
- For shared data fetching patterns (useRecords, mutations, uploads, metrics, charts), see [datasources/shared-patterns.md](datasources/shared-patterns.md).
147
+ **A block can connect to MORE THAN ONE of these at a time.** Declare them with `datasource.define({ alias: "uuid" })` and pass `from: ds.alias` on every data hook — read two tables and write to a third from a single block. Required reading before building anything multi-table: [datasources/multi-datasource.md](datasources/multi-datasource.md). (This replaces the old one-table-per-block limit and the invisible-helper-block workaround.)
148
+
149
+ Shared data patterns, linked directly (read the one the task needs): **reading** records, filtering, sorting, pagination, metrics, charts, current user — [datasources/reading.md](datasources/reading.md); **writing** — mutations, sequential write queues, uploads, field-type write shapes — [datasources/writing.md](datasources/writing.md); **field values** — `getFieldValue()`, field shapes, debug blocks — [datasources/fields.md](datasources/fields.md). ([datasources/shared-patterns.md](datasources/shared-patterns.md) is the thin index of the same set.)
145
150
 
146
151
  For data source comparison and selection guidance, see [datasources/overview.md](datasources/overview.md).
147
152
 
@@ -151,13 +156,14 @@ For advanced patterns beyond data fetching, load the relevant reference when the
151
156
 
152
157
  | If the task involves... | Load reference |
153
158
  |---|---|
154
- | Cross-block communication, multi-table data access, invisible helper blocks, window globals, breadcrumbs | [references/helper-blocks.md](references/helper-blocks.md) |
159
+ | Reading/writing **several tables from one block** `datasource.define()`, the `from:` parameter, obtaining the datasource UUIDs (and why Studio's chat invents them) | [datasources/multi-datasource.md](datasources/multi-datasource.md) |
160
+ | Cross-*block* communication, window globals, breadcrumbs, publishing shared computed state. *(Multi-table reads no longer need a helper — use a second datasource.)* | [references/helper-blocks.md](references/helper-blocks.md) |
155
161
  | Embedding third-party libraries with their own CSS (Leaflet, Mapbox, TinyMCE, Quill, FullCalendar) | [references/advanced-integrations.md](references/advanced-integrations.md) |
156
162
  | Debugging a broken block, checking patterns before delivery, full violation catalog | [references/anti-patterns.md](references/anti-patterns.md) |
157
163
  | Quick syntax check — import paths, hook signatures, mutation call shapes, field mapping | [references/quick-reference.md](references/quick-reference.md) |
158
164
  | Small reusable patterns — `localStorage` cross-page state, clipboard copy button | [references/common-patterns.md](references/common-patterns.md) |
159
165
  | 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) |
160
- | AI-assisted Softr DB schema discovery / field-ID lookup / record reads via the official Softr MCP server (sibling to the in-block `useRecords` workflow) | [references/softr-database-mcp.md](references/softr-database-mcp.md) |
166
+ | The official **Softr MCP server** — schema discovery and record reads for Softr DB, field-level browsing of connected Airtable / Google Sheets / Notion / Supabase integrations, and **creating, editing, versioning, and deploying Vibe Coding blocks directly** (`get_vibe_coding_docs`, `create_vibe_coding_block`, `connect_vibe_coding_block_data_source`, ...) | [references/softr-mcp.md](references/softr-mcp.md) |
161
167
  | 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) |
162
168
  | 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) |
163
169
 
@@ -358,7 +364,7 @@ var askAi = useNavigationSetting({
358
364
  - `OPEN_CHAT` — opens Softr's AI chat. **No `destination` or `openIn` needed** — it's the cheapest "Ask AI" button to wire up. **GOTCHA:** Softr's AI pulls context from the block that triggered the chat, NOT from the page. If the block has no data source connected, `chat/prepare` returns HTTP 500 ("Failed to prepare AI assistant") even though the chat panel opens. Fix: in Softr Studio, connect the block to whatever data source the AI should read from — even if the block doesn't read or write any records itself, the connection is what gives the AI context. Verified by direct experiment, May 2026: a button-only helper block with no data source caused this exact failure; connecting it to the same table as the main block fixed it without any code change.
359
365
  - `OPEN_URL` — opens an external URL. Needs `destination` (the URL) + `openIn` (`"SELF"` | `"TAB"`).
360
366
  - `OPEN_PAGE` — navigates to a Softr page in-app. Needs `destination` (page path) + `openIn` (`"SELF"` | `"TAB"` | `"MODAL"`).
361
- - `TRIGGER_CUSTOM_WORKFLOW` — runs a Softr workflow. Needs the workflow id in `destination`.
367
+ - `TRIGGER_CUSTOM_WORKFLOW` — runs a Softr workflow. The documented setting shape is `{ action: "TRIGGER_CUSTOM_WORKFLOW" }` with no `destination` (same shape as `OPEN_CHAT`); the builder picks the workflow in the block's settings panel. Not verified live whether a `destination` is also accepted — don't rely on one.
362
368
 
363
369
  When the action navigates to a record-specific page, pass the runtime record id via the `recordId` prop on `<NavigationAction>` (not on the setting) so Softr can resolve dynamic URLs:
364
370
 
@@ -443,29 +449,37 @@ Non-negotiable rules enforced by the Softr platform:
443
449
  7. **No nested arrays in settings** — Use text with separator, split in code.
444
450
  8. **Default export required** — `export default function Block()`.
445
451
  9. **Container wrapping** — Always wrap in `<div className="container py-0"><div className="content">`. Vertical padding lives on the inner wrapper and depends on block placement (see "Block Placement & Page Spacing").
446
- 10. **No optional chaining or nullish coalescing** — Softr's bundler fails on `?.` and `??`. Use:
447
- - `(user && user.email) || ""` instead of `user?.email ?? ""`
448
- - `(data && data.pages) ? data.pages.flatMap(function(p) { return p.items; }) : []`
449
- 11. **Airtable: use column names, not fld... IDs** See [datasources/airtable.md](datasources/airtable.md).
452
+ 10. **Inline options literals for data hooks** — `useRecords` fails to compile when its options
453
+ object is passed through a variable or wrapper function. Build the options object inline at the
454
+ call site; share `q.select` mappings between hooks, not whole options objects. (Same
455
+ static-analysis family as the `q.select()` / `datasource.define()` literal rules. Verified live
456
+ 2026-08-25 — hit in a production block whose options came from a wrapper function; the fix was
457
+ changing the wrapper to take the hook's *result* instead.)
458
+ 11. **Airtable, Notion, Google Sheets: use field NAMES, not IDs** — `q.select()` values are field names for these three sources; Softr Database and Supabase use field IDs (Supabase = SQL column name). Getting this wrong fails silently: the block compiles and saves, then renders empty. See [datasources/airtable.md](datasources/airtable.md).
450
459
  12. **Record fields nested under `fields`** — Access via `record.fields.alias`, not `record.alias`.
451
- 13. **ONE `useRecords` per block** — Filter client-side. Multiple `useMetric` calls OK.
460
+ 13. **ONE `useRecords` per datasource** — filter client-side rather than issuing several queries against
461
+ the same table. A block CAN connect to multiple data sources and call `useRecords` once per source;
462
+ declare them with `datasource.define()` and pass `from:` on every hook. See
463
+ [datasources/multi-datasource.md](datasources/multi-datasource.md). Multiple `useMetric` calls OK.
452
464
  14. **React functional components only** — No class components.
453
465
  15. **Do NOT `import React from 'react'`** — Use named imports for hooks.
454
466
  16. **No CSS modules or styled-components** — Tailwind only.
455
467
  17. **setTimeout for scroll** -- Wrap programmatic scroll in `setTimeout(fn, 0)`.
456
- 18. **`fetchNextPage` inside `useEffect` only** -- Calling it during render causes infinite re-render loops. The component calls `fetchNextPage`, which updates data, which triggers re-render, which calls `fetchNextPage` again.
468
+ 18. **Never call `fetchNextPage` in the render body** -- render calls `fetchNextPage`, which updates data, which triggers re-render, which calls it again: infinite loop. Call it from an event handler (the official Load More pattern: `<button onClick={() => fetchNextPage()} disabled={isFetching}>`) or from a guarded `useEffect` when intentionally auto-loading all pages.
457
469
  19. **All hooks before any conditional `return`** -- Hooks must be called in the same order every render. A hook declared after a conditional `return` causes React error #310.
458
470
  20. **Relative paths in navigation** -- Use `/page-name?recordId=...`, never hardcoded domains like `app.client.com/page`.
471
+ 21. **Recompiles reset Action permissions** -- Every code recompile/redeploy resets the block's
472
+ auto-registered Actions to **default permissions**. Do the Actions-tab permission tightening pass
473
+ only after the LAST redeploy, and re-check it after any future one. Verified live 2026-08-25
474
+ across a 15-block deployment. See [datasources/writing.md](datasources/writing.md#how-actions-work-studios-actions-tab).
459
475
 
460
- ## Style Conventions (preferred, not enforced)
476
+ ## Style Conventions
461
477
 
462
- The conventions below improve consistency across the skill's examples but are NOT enforced by the Softr bundler. Softr's AI assistant in Studio outputs `const` and arrow functions, and both compile and run fine. Adopting these conventions makes hand-written blocks visually uniform with the rest of the skill, but blocks generated by Softr's AI work without conversion.
478
+ **The current platform compiles TypeScript with modern syntax** optional chaining (`?.`), nullish coalescing (`??`), arrow functions, `const`/`let`, generics verified live against `get_vibe_coding_docs` and a 15-block production deployment on 2026-08-25. Write new blocks in modern TS (`.tsx`); it matches what Softr's own Studio AI emits.
463
479
 
464
- - Prefer `var` over `const` / `let`
465
- - Prefer `function() {}` over arrow functions in JSX callbacks and component props
466
- - Field-value helper property priority: `label` -> `name` -> `title`
480
+ 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.
467
481
 
468
- If you're editing a block originally generated by Softr's AI assistant, you can convert to skill style for consistency or leave the AI's syntax as-is. Both produce a working block. Only `?.` and `??` (Hard Constraint #10) are actual bundler blockers verified by direct experiment, April 2026.
482
+ Still house conventions: the field-value helper is named `getFieldValue()`, with property priority `label` -> `name` -> `title`.
469
483
 
470
484
  ## Anti-Patterns Checklist
471
485
 
@@ -120,6 +120,7 @@ This script reads only **metadata**, never records. To inspect record contents i
120
120
  | Number | Yes | |
121
121
  | Date | Yes | |
122
122
  | Checkbox | Yes | |
123
+ | Rating | Yes | Star field. **Writable** — send a plain integer `0`–`max`; `0` clears it (Airtable treats a cleared rating as empty, so `AVERAGE` formulas skip it). Reads back as a number. Verified by direct experiment, July 2026: a create Action with three `rating` fields derived cleanly and wrote all three. |
123
124
  | Single Select | Yes | Returns as `{ label, id }` object. Use `getFieldValue()` helper to extract the label. |
124
125
  | Multiple Select | Yes | Array of `{ label, id }` objects |
125
126
  | Attachment | Yes | Array of `{ filename, id, type, url }` objects |
@@ -84,7 +84,7 @@ Two throwaway diagnostic blocks you can drop into a page to diagnose data proble
84
84
 
85
85
  Use when records load but fields come back empty, or when you're not sure which Field IDs exist on a table.
86
86
 
87
- **Important caveat for Softr Database:** `q.select({})` returns record IDs with empty `fields: {}` -- it does NOT dump all fields. Verified by direct experiment, April 2026. Use one of the alternatives below for Softr DB. The empty-select pattern still works for Airtable and other sources where field IDs come back automatically.
87
+ **Important caveat for Softr Database:** `q.select({})` returns record IDs with empty `fields: {}` -- it does NOT dump all fields. Verified by direct experiment, April 2026. Use one of the alternatives below for Softr DB. The empty-select pattern still works for Airtable and other sources where the field keys come back automatically (for Airtable / Notion / Google Sheets those keys are field NAMES — the same names `q.select()` uses; see the name-vs-ID rule in [../references/softr-mcp.md](../references/softr-mcp.md#browsing-integrations-external-data-sources)).
88
88
 
89
89
  For Airtable and other non-Softr-DB sources:
90
90
 
@@ -111,7 +111,7 @@ export default function Block() {
111
111
 
112
112
  **For Softr Database, find field IDs via:**
113
113
 
114
- 1. **Softr Database MCP server (recommended for AI-assisted workflows)** -- if you're collaborating with an AI assistant (Claude Code, Claude Desktop, Cursor, ChatGPT, Mistral) to write Vibe Coding blocks, the official Softr MCP server is the cleanest path. The AI calls schema/list-fields tools directly against your workspace and reads back every field's `id`, `name`, `type`, and dropdown option UUIDs -- no copy-paste, no transcription errors. Full setup, scopes, and scope limitations (Softr DB only -- does NOT cover Airtable / external sources) in [../references/softr-database-mcp.md](../references/softr-database-mcp.md).
114
+ 1. **Softr MCP server (recommended for AI-assisted workflows)** -- if you're collaborating with an AI assistant (Claude Code, Claude Desktop, Cursor, ChatGPT, Mistral) to write Vibe Coding blocks, the official Softr MCP server is the cleanest path. The AI calls schema/list-fields tools directly against your workspace and reads back every field's `id`, `name`, `type`, and dropdown option UUIDs -- no copy-paste, no transcription errors. The same server also browses connected Airtable / Google Sheets / Notion / Supabase integrations down to field level. Full setup and permissions in [../references/softr-mcp.md](../references/softr-mcp.md).
115
115
 
116
116
  2. **`get-softr-database` CLI script (bundled, no MCP needed)** -- a Python CLI bundled with this skill at `~/.claude/skills/softr-vibe-coding/tools/get-softr-database.py`. Exports the full schema (every table, every field, all dropdown option UUIDs) to `~/Desktop/softr-database-<id>-<timestamp>.json`. Run with `python3 ~/.claude/skills/softr-vibe-coding/tools/get-softr-database.py <database_id>` (prompts for API key) or set `SOFTR_API_KEY=xxx` env var to skip the prompt. Stdlib only, no `pip install`. Best when you want a portable JSON dump for sharing in chat, archiving, or diffing across schema versions. Full usage in [softr-database.md](softr-database.md#bundled-cli-script-get-softr-database).
117
117
 
@@ -119,7 +119,7 @@ export default function Block() {
119
119
  - Each field's `id`, `name`, `type`, and `options`
120
120
  - For dropdown / SELECT fields: the full `choices` array with every option's `id` (UUID), `label`, and `color`
121
121
 
122
- Use this when scaffolding a block that needs many field IDs at once, or to look up dropdown option UUIDs needed for write payloads. **When working with an AI assistant without the MCP installed**, paste this JSON response into the chat -- second-best way to share accurate field IDs and dropdown UUIDs in one shot.
122
+ Use this when scaffolding a block that needs many field IDs at once, or to see a SELECT field's full choice list. (Write payloads take the option **label**, not the UUID — verified 2026-08-25, see [writing.md](writing.md#dropdown--single-select-softr-database) — but the exported labels are exactly what you need for a correct write vocabulary.) **When working with an AI assistant without the MCP installed**, paste this JSON response into the chat -- second-best way to share accurate field IDs and choice lists in one shot.
123
123
 
124
124
  4. **Inline in Studio (one field at a time)** -- in the Data tab, click a field's name to open its edit drawer. The field ID appears next to the "Field name" label (e.g. `ID: 37fts`). Fastest for spot-checking a single field.
125
125
 
@@ -182,7 +182,7 @@ export default function Block() {
182
182
 
183
183
  ### User Inspector Block
184
184
 
185
- Use when debugging permissions, user groups, or the `useCurrentUser()` vs `window.__softr_current_user` distinction. Renders both side-by-side as JSON. This catches a common surprise: `userGroups` only lives on the `window.__softr_current_user` object, not on `useCurrentUser()`.
185
+ Use when debugging permissions, user groups, or the `useCurrentUser()` vs `window.__softr_current_user` distinction. Renders both side-by-side as JSON. This catches a common surprise: `userGroups` only lives on the `window.__softr_current_user` object, not on `useCurrentUser()`. (Custom user-record fields, by contrast, ARE available through the hook — `useCurrentUser({ properties: { alias: "FIELD_ID" } })` exposes them under `user.properties`; see [reading.md](reading.md#current-user).)
186
186
 
187
187
  ```jsx
188
188
  import { useCurrentUser } from "@/lib/user";
@@ -23,7 +23,7 @@ Use the Field Inspector block to determine exact field IDs for your monday.com b
23
23
  | Dropdown | Yes | |
24
24
  | Checkbox | Yes | |
25
25
  | Email | Yes | |
26
- | Phone | Yes | |
26
+ | Phone | Yes | Writes rejected unless value is `+` followed by digits only — no spaces, dashes, or parentheses. See Gotchas |
27
27
  | Link | Yes | |
28
28
  | People | Yes | |
29
29
  | File | Yes | |
@@ -42,6 +42,13 @@ monday.com enforces API rate limits based on your monday.com plan tier. Softr re
42
42
  - **API token authentication only.** Uses a Personal API Token from monday.com's admin settings, not OAuth.
43
43
  - **Connected Boards** (linked records between boards) are supported but may have limitations depending on the board configuration.
44
44
  - **Mirror columns are read-only.** These reflect data from connected boards and cannot be written to from Softr.
45
+ - **Phone writes are strict.** monday.com is the datasource the official Softr guide names as rejecting formatted phone values. Send unformatted international format only (`+` then digits, e.g. `+12125550100`) and sanitize before `mutate()`:
46
+
47
+ ```tsx
48
+ const sanitizePhone = (raw: string) => raw.replace(/[^\d+]/g, "").replace(/(?!^)\+/g, "");
49
+ ```
50
+
51
+ See [writing.md](writing.md#phone).
45
52
  - **Status columns** use monday.com's internal label/index system. Map these appropriately in your block logic for display purposes.
46
53
  - **Subitems** may require additional configuration to surface correctly in Softr blocks.
47
54
 
@@ -0,0 +1,108 @@
1
+ # Multiple Data Sources in One Block
2
+
3
+ A Vibe Coding block can connect to **several data sources at once**. Declare them with
4
+ `datasource.define()` and target one per hook with `from:`.
5
+
6
+ This supersedes the old one-table-per-block limit. Blocks that needed a second table used to
7
+ require an invisible helper block publishing to a `window` global — that workaround is no
8
+ longer necessary for plain multi-table reads. See [../references/helper-blocks.md](../references/helper-blocks.md)
9
+ for what helper blocks are still genuinely for.
10
+
11
+ ## The pattern
12
+
13
+ ```jsx
14
+ import { datasource, useRecords, useRecordCreate, q } from "@/lib/datasource";
15
+
16
+ var ds = datasource.define({
17
+ people: "74d2cbfd-f2cb-4f5c-82d9-0d3a0651e531",
18
+ shifts: "ec7a6311-f6c3-4c99-881d-aae308148716",
19
+ feedback: "52461ab9-9912-4e15-bcf4-8838d38c64ea",
20
+ });
21
+
22
+ var peopleSelect = q.select({ email: "Email", firstName: "First name" });
23
+ var shiftSelect = q.select({ jobCode: "Job Code" });
24
+ var feedbackCreateFields = q.select({ comments: "Comments", crewMember: "Crew Member" });
25
+
26
+ export default function Block() {
27
+ var people = useRecords({ from: ds.people, select: peopleSelect, count: 20 });
28
+ var shifts = useRecords({ from: ds.shifts, select: shiftSelect, count: 5 });
29
+
30
+ var createFeedback = useRecordCreate({
31
+ from: ds.feedback,
32
+ fields: feedbackCreateFields,
33
+ onSuccess: function () { /* … */ },
34
+ });
35
+ // …
36
+ }
37
+ ```
38
+
39
+ **`from:` is required on every data hook once a block has more than one source.** Omitting it
40
+ throws. With exactly one source you can skip `datasource.define` and omit `from` entirely —
41
+ the hooks default to that source.
42
+
43
+ Applies to: `useRecords`, `useRecord`, `useLinkedRecords`, `useFieldOptions`, `useMetric`,
44
+ `useChartData`, `useRecordCreate`, `useRecordUpdate`, `useRecordDelete`.
45
+
46
+ Does **not** apply to `useUpload` and `useCurrentRecordId` — those are app-level and take no `from`.
47
+
48
+ `useProxyFetch` (REST API sources) is **also datasource-scoped**, but takes the alias as its **function argument** rather than a `from:` option — `useProxyFetch(ds.store)`. With a single datasource `useProxyFetch()` works bare; once the block has more than one, omitting the alias throws, exactly like omitting `from:` on a record hook. See [rest-api.md](rest-api.md#multiple-datasources).
49
+
50
+ ## The values must be inline string literals
51
+
52
+ Softr statically analyses `datasource.define()`, exactly like `q.select()`. Hoisting the ids
53
+ into constants fails to compile:
54
+
55
+ ```jsx
56
+ // WRONG — "datasource.define() object values must be string literals"
57
+ var PEOPLE_DS_ID = "74d2cbfd-…";
58
+ var ds = datasource.define({ people: PEOPLE_DS_ID });
59
+
60
+ // CORRECT — literals, in place
61
+ var ds = datasource.define({ people: "74d2cbfd-…" });
62
+ ```
63
+
64
+ The error text is explicit, so this one fails fast rather than silently — but it's an easy
65
+ reflex to hoist "magic strings" into named constants, and that reflex is wrong here.
66
+
67
+ ## Getting the datasource ids — ask for CODE, never for a value
68
+
69
+ The id is a plain **UUID**. It is *not* the underlying table id (`tbl…` in Airtable), and not
70
+ the `ds_id_1` shape used as a placeholder in Softr's own developer guide.
71
+
72
+ **Studio's AI chat fabricates these when asked in prose.** Verified 2026-07-22: asked three
73
+ times for the ids of the same three connected tables, it returned three different sets, once
74
+ recycling a previously-mentioned table's uuid for a different table. All three answers were
75
+ confidently worded. None were flagged as uncertain.
76
+
77
+ Ask it to **write code** instead:
78
+
79
+ ```
80
+ Write a datasource.define call covering every data source connected to this
81
+ block, plus one useRecords per source. Output code only.
82
+ ```
83
+
84
+ Code generation is bound to the block's real connections, so the ids come out correct — the
85
+ same reason Softr's assistant reliably inlines real select options when scaffolding a form but
86
+ invents values when asked to recite one.
87
+
88
+ **Then verify by running it.** The scaffold renders a list per source; if real rows appear
89
+ under each heading, every alias maps to the table you think it does. A wrong uuid fails safe
90
+ (it matches no datasource, so the block errors) — but a *swapped* pair of correct uuids does
91
+ not, and only running it will catch that.
92
+
93
+ ## When you still want a helper block
94
+
95
+ Multi-datasource removes the need for helpers as a *data-access* workaround. They remain the
96
+ right tool for:
97
+
98
+ - **Cross-block communication** — one block triggering or feeding another on the same page.
99
+ - **Publishing computed state** — expensive derivations shared by several consumers.
100
+ - **Rich foreign data via `useLinkedRecords`** — that hook still only returns `{id, title}`
101
+ and silently ignores extra fields in `select`. Reading the foreign table directly with its
102
+ own `from:` is now the simpler fix.
103
+
104
+ ## Worked example
105
+
106
+ `crew-feedback-form.jsx` — a public feedback form that reads a person from **People** by an
107
+ `email` URL param, resolves a **Shifts** record from a job-code param, and writes a row to
108
+ **Feedback** linking both. One block, three sources, no helpers, no `window` globals.
@@ -10,8 +10,8 @@ Document and database platform used as a data source for Softr. Requires a Basic
10
10
  4. If a database does not appear in the list: open Notion, click the ellipsis menu (...) on the database page, go to Connections, find Softr, and click Allow.
11
11
  5. Multiple Notion workspaces can be connected to a single Softr app.
12
12
 
13
- ## Vibe Coding Field IDs
14
- Use the Field Inspector block to determine exact field IDs for Notion database properties. Property names in Notion are typically used as field IDs.
13
+ ## Vibe Coding Field References
14
+ `q.select()` values for Notion are the database's **property NAMES**, not internal IDs same rule as Airtable and Google Sheets (official developer guide). Getting this wrong fails silently: the block compiles and saves, then renders with empty data. Use the Field Inspector block to confirm the exact property names when unsure.
15
15
 
16
16
  ## Supported Fields
17
17
 
@@ -32,7 +32,7 @@
32
32
  - Data is connected to **dynamic blocks** (List, Grid, Table, Kanban, Chart, Form, etc.) in the Softr Studio.
33
33
  - **Multiple data sources** can coexist in a single Softr app, even on the same page.
34
34
  - Softr IP addresses to whitelist for secured databases: `3.120.79.212`, `3.123.159.186`, `52.58.246.121`
35
- - **AI-assisted schema discovery via MCP:** Softr Database is the only source with a first-party MCP server (`https://mcp.softr.io/mcp`). When installed, the AI reads schema and field IDs directly from your workspace -- no manual paste-in. External sources still need the manual workflows in [fields.md](fields.md). See [softr-database.md](softr-database.md#ai-assisted-workflows-softr-database-mcp-server).
35
+ - **AI-assisted schema discovery via MCP:** the official Softr MCP server (`https://mcp.softr.io/mcp`) reads schema and field references directly from the workspace -- Softr Databases in full, plus browsable field-level access to connected **Airtable, Google Sheets, Notion, and Supabase** integrations. Other external sources still need the manual workflows in [fields.md](fields.md). See [../references/softr-mcp.md](../references/softr-mcp.md).
36
36
 
37
37
  ## User Sync Availability
38
38