softr-vibe-coding 2.1.2 → 2.3.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,12 @@ 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.3.0] - 2026-08-31
8
+ - Document expanded Softr MCP: deletes, app scaffolding, Workflows suite (26 tools + 418-node catalog), per-application MCP servers
9
+
10
+ ## [2.2.0] - 2026-08-31
11
+ - Add editable-settings and static-blocks references: undocumented useLongTextSetting + navigation array-schema type, settings-first doctrine, full-bleed/full-viewport heroes, block-owned fixed headers
12
+
7
13
  ## [2.1.2] - 2026-08-26
8
14
  - Document four linked-record write traps verified live 2026-08-26
9
15
  - Fail the build when main moves ahead of npm
package/README.md CHANGED
@@ -15,15 +15,17 @@
15
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
+ - **Editable settings deep-dive** — full hook catalog including verified-undocumented capabilities (`useLongTextSetting`, the `navigation` array-schema type), settings-first design doctrine so clients edit copy/images/links in Content → Settings without re-prompting
19
+ - **Static marketing blocks** — heroes, landing headers, pricing tables, footers: editorial baseline, full-bleed layouts, full-viewport sizing, block-owned fixed headers with scroll-condensing treatment
18
20
  - **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
19
21
  - **Helper blocks & cross-block patterns** — Invisible helper blocks for multi-table access via `window` globals + `CustomEvent`, `useWindowData` hook, breadcrumb navigation, saved views architecture
20
22
  - **Advanced integrations** — Shadow DOM CSS isolation for third-party libraries (Leaflet, Mapbox, TinyMCE, Quill, FullCalendar)
21
23
  - **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
24
  - **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 platform compatibility (inline hook options, correct payload shapes, correct imports, container wrappers, `getFieldValue()` wrapping, hooks ordering) before delivering code
24
- - **Premium visual baseline** — Every block ships polished from v1: gradient backgrounds, card elevation, loading skeletons, empty states, error states
25
+ - **Self-validation** — Claude checks Softr platform compatibility and house conventions (inline hook options, correct payload shapes, correct imports, container wrappers or a deliberate full-bleed layout, `getFieldValue()` wrapping, hooks ordering) before delivering code
26
+ - **Premium visual baseline** — every app-UI block (dashboards, lists, forms, detail pages) ships polished from v1: gradient backgrounds, card elevation, loading skeletons, empty states, error states; static marketing blocks use the editorial baseline instead
25
27
  - **Debug utilities** — Field Inspector, API Response Inspector, and User Inspector blocks for diagnosing data source and permissions issues
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`.
28
+ - **Softr MCP integration** — when the [official Softr MCP server](https://docs.softr.io/mcp/overview) is installed (`claude mcp add --transport http softr https://mcp.softr.io/mcp`), Claude reads Softr DB schema, field IDs, and dropdown option UUIDs directly, browses connected Airtable / Google Sheets / Notion / Supabase integrations down to field level, creates and deploys Vibe Coding blocks straight into your app, manages full record/table/field/database CRUD (deletes included), and can scaffold applications and build **Softr Workflows** against a 418-node trigger/action catalog. Also covers Softr's **per-application MCP servers** (one per published app, scoped to the app's own permissions). See `references/softr-mcp.md`.
27
29
 
28
30
  ---
29
31
 
@@ -161,20 +163,20 @@ Create a contact form that creates records in our Airtable Contacts table
161
163
 
162
164
  ```
163
165
  softr-vibe-coding/
164
- ├── SKILL.md # Main skill (330 lines)
166
+ ├── SKILL.md # Main skill
165
167
  │ # Workflow, code structure, visual baseline,
166
168
  │ # components, settings, 21 hard constraints
167
169
 
168
- ├── ui-ux-guidelines.md # Design reference (746 lines)
170
+ ├── ui-ux-guidelines.md # Design reference
169
171
  │ # 26 sections: hierarchy, color, typography,
170
172
  │ # spacing, motion, accessibility, AI slop checklist
171
173
 
172
174
  ├── references/ # Advanced patterns (loaded on demand)
173
- │ ├── helper-blocks.md # Cross-block communication (370 lines)
175
+ │ ├── helper-blocks.md # Cross-block communication
174
176
  │ │ # Invisible helper blocks, window globals,
175
177
  │ │ # CustomEvent, useWindowData hook, breadcrumbs,
176
178
  │ │ # saved views, companion field helpers
177
- │ ├── airtable-automations.md # Airtable automation scripting (350 lines)
179
+ │ ├── airtable-automations.md # Airtable automation scripting
178
180
  │ │ # "Run a script" automation action vs.
179
181
  │ │ # Scripting Extension, cross-table cascades,
180
182
  │ │ # batch update gotchas, field-ID discipline,
@@ -182,8 +184,11 @@ softr-vibe-coding/
182
184
  │ ├── softr-mcp.md # Official Softr MCP server — vibe coding block
183
185
  │ │ # tools (create/edit/version/deploy), integrations
184
186
  │ │ # browsing (Airtable/Sheets/Notion/Supabase),
185
- │ │ # Softr DB schema + record tools, auth, permissions
186
- ├── advanced-integrations.md # Shadow DOM CSS isolation (69 lines)
187
+ │ │ # Softr DB schema + record tools incl. deletes,
188
+ # app management/scaffolding, Workflows suite
189
+ │ │ # (26 tools, 418-node catalog), per-application
190
+ │ │ # MCP servers, auth, permissions
191
+ │ ├── advanced-integrations.md # Shadow DOM CSS isolation
187
192
  │ │ # Leaflet, Mapbox, TinyMCE, Quill, FullCalendar
188
193
  │ ├── native-chrome-styling.md # Restyle Softr's native shell (header, footer,
189
194
  │ │ # nav, dropdowns, page background) via global
@@ -192,13 +197,22 @@ softr-vibe-coding/
192
197
  │ ├── native-block-filters.md # Dynamic date / URL-param filters + custom filter
193
198
  │ │ # controls on native List/Grid blocks — wide-range
194
199
  │ │ # sentinel, inject into filter row, survive re-renders
195
- │ ├── anti-patterns.md # Categorized violation catalog (86 lines)
200
+ │ ├── anti-patterns.md # Categorized violation catalog
196
201
  │ │ # Data access, mutations, hooks, layout,
197
- │ │ # permissions, helper blocks
198
- │ ├── common-patterns.md # Small reusable patterns (102 lines)
199
- │ │ # localStorage cross-page state,
200
- │ │ # clipboard copy button
201
- └── quick-reference.md # Syntax cheat sheet (207 lines)
202
+ │ │ # permissions, editable settings, helper blocks
203
+ │ ├── common-patterns.md # Small reusable patterns
204
+ │ │ # localStorage cross-page state, clipboard copy,
205
+ │ │ # navigation blocker, scroll-condensing header,
206
+ │ # auth-aware CTA, image masks, blobs, dot lists
207
+ │ ├── editable-settings.md # Settings deep-dive: full hook catalog incl.
208
+ │ │ # verified-undocumented useLongTextSetting +
209
+ │ │ # "navigation" array-schema type, granularity
210
+ │ │ # doctrine, naming, rename-resets gotcha
211
+ │ ├── static-blocks.md # Static marketing archetype: heroes, landing
212
+ │ │ # headers, pricing, footers — workflow deltas,
213
+ │ │ # editorial baseline, full-bleed + full-viewport,
214
+ │ │ # block-owned header, section anchors
215
+ │ └── quick-reference.md # Syntax cheat sheet
202
216
  │ # Imports, hook signatures, mutation shapes,
203
217
  │ # field mapping, component skeleton
204
218
 
@@ -212,11 +226,11 @@ softr-vibe-coding/
212
226
  ├── multi-datasource.md # Several data sources in ONE block: datasource.define(),
213
227
  │ # the from: parameter, getting the datasource UUIDs
214
228
  ├── reading.md # useRecords, filtering, sorting, pagination,
215
- │ # metrics, charts, current user (198 lines)
229
+ │ # metrics, charts, current user
216
230
  ├── writing.md # Mutations, sequential write queues, uploads,
217
231
  │ # linked record format, cross-table writes
218
232
  ├── fields.md # getFieldValue(), field type shapes, record
219
- │ # structure, debug utilities (160 lines)
233
+ │ # structure, debug utilities
220
234
  ├── rest-api.md # useProxyFetch + useQuery (full docs)
221
235
  ├── softr-database.md # Native DB — field IDs, no rate limits
222
236
  ├── airtable.md # Column names, PAT vs OAuth, rate limits
@@ -235,7 +249,7 @@ softr-vibe-coding/
235
249
 
236
250
  ### How context loading works
237
251
 
238
- Only `SKILL.md` loads into Claude's context when the skill triggers (~330 lines). The data source guides, reference files, and UI/UX guidelines load **on demand** — Claude reads only the files relevant to your specific block. This keeps context lean even with 24 files totaling 3,100+ lines.
252
+ Only `SKILL.md` loads into Claude's context when the skill triggers. The data source guides, reference files, and UI/UX guidelines load **on demand** — Claude reads only the files relevant to your specific block. This keeps context lean even with 26 files totaling 6,500+ lines.
239
253
 
240
254
  ---
241
255
 
@@ -272,7 +286,7 @@ The skill enforces these automatically, but good to know (verified live against
272
286
  - Every code recompile resets the block's auto-registered Actions to default permissions — tighten permissions after the last redeploy
273
287
  - No `import React from 'react'` — use named imports (`import { useState } from "react"`)
274
288
  - Must use `export default function Block()`
275
- - Must wrap layout in `<div className="container py-0"><div className="content">`
289
+ - Wrap layout in `<div className="container py-0"><div className="content">` for app/content blocks (house convention for width alignment with native blocks) — the platform default is actually full width, so full-bleed marketing blocks (heroes, banners, footers) legitimately omit the wrappers and own their gutters
276
290
  - 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
291
  - `fetchNextPage` never in the render body (infinite loop) — call it from an event handler (Load More `onClick`) or a guarded `useEffect`
278
292
  - All hooks declared before any conditional `return` — React error #310
package/SKILL.md CHANGED
@@ -21,7 +21,7 @@ allowed-tools: Read Write Glob Grep Bash
21
21
 
22
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).
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
+ > **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).
25
25
 
26
26
  ## Your Workflow
27
27
 
@@ -51,24 +51,24 @@ You generate complete, production-ready Softr Vibe Coding blocks as TypeScript R
51
51
 
52
52
  Do not silently default to Softr's brand. The user must opt in to defaults explicitly.
53
53
 
54
- 2. **Understand what the user wants to build.** They will describe it in plain language. Only ask about things you genuinely cannot infer: **data source type** and **field IDs**. For everything else, make sensible defaults and flag your assumptions.
54
+ 2. **Understand what the user wants to build — and fork on archetype.** They will describe it in plain language. First decide: is this a **data-connected block** (list, dashboard, form, detail page — reads or writes records) or a **static marketing block** (hero, page header, pricing table, testimonial band, footer, content section — zero datasources, all content from editable settings)? For static blocks, read [references/static-blocks.md](references/static-blocks.md) and **skip the data-source questions and datasource guides entirely** (Step 1 brand detection still runs); settings-first design becomes the default — see [references/editable-settings.md](references/editable-settings.md#granularity-doctrine-settings-first-static-blocks). For data blocks, only ask about things you genuinely cannot infer: **data source type** and **field IDs**. For everything else, make sensible defaults and flag your assumptions.
55
55
 
56
56
  3. **Apply defaults for the rest, don't ask.** Infer these from context instead of asking:
57
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.
58
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.
59
59
  - **Filename**: Derive from the block purpose (e.g., `partner-invite.tsx`, `team-directory.tsx`). The user can rename later.
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
+ 4. **Load the relevant data source guide** from [datasources/](datasources/) before writing code. Read the specific guide for the user's data source type. (Static marketing blocks: skip this step — read [references/static-blocks.md](references/static-blocks.md) instead.)
62
62
 
63
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
64
 
65
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).
66
66
 
67
- 6. **Self-validate before delivering.** Before presenting the code as complete, verify:
67
+ 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).)
68
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
69
69
  - All imports use named imports (no `import React from 'react'`)
70
70
  - `export default function Block()` is present
71
- - Container + content wrappers present (`<div className="container py-0"><div className="content">`)
71
+ - Container + content wrappers present (`<div className="container py-0"><div className="content">`) — OR a deliberate full-bleed layout recorded in the `// BLOCK PLACEMENT:` comment (see "Block Placement & Page Spacing")
72
72
  - `// BLOCK PLACEMENT:` comment present at top of file with wrapper classes matching the placement (see "Block Placement & Page Spacing")
73
73
  - Loading, error, and empty states all handled
74
74
  - Mutation calls gated behind `enabled` check (if using mutations)
@@ -81,7 +81,10 @@ You generate complete, production-ready Softr Vibe Coding blocks as TypeScript R
81
81
  - Mutations use `recordId` (not `id`) and call `refetch()` in `onSuccess`
82
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
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))
84
- - No hardcoded domains in links -- use relative paths (`/page?recordId=...`)
84
+ - No hardcoded domains in links -- use relative paths (`/page?recordId=...`); same-page anchors written relative too (`/#section`)
85
+ - Static block: no hardcoded user-visible copy — every string/image/link is an editable setting (see [references/editable-settings.md](references/editable-settings.md#granularity-doctrine-settings-first-static-blocks))
86
+ - Array-setting rows keyed by **index**, never by a builder-editable field value
87
+ - Media settings that may start empty (`src: ""`) gated with a conditional render or placeholder — never an unconditional `<img src={setting.src}>`
85
88
 
86
89
  ## What to Clarify
87
90
 
@@ -108,7 +111,7 @@ When the user describes their block, figure out which of these areas apply and a
108
111
  Softr logo assets (for blocks that need Softr branding):
109
112
  - Icon + wordmark (SVG): `https://cdn.brandfetch.io/idytCFzVcY/theme/dark/logo.svg`
110
113
  - Icon only (PNG): `https://cdn.brandfetch.io/idytCFzVcY/w/1024/h/1024/theme/dark/icon.png`
111
- - **Layout and style**: Cards vs. table vs. list? How many columns? Apply the Premium Visual Baseline regardless.
114
+ - **Layout and style**: Cards vs. table vs. list? How many columns? Apply the Premium Visual Baseline for app-UI blocks; static marketing blocks use the editorial baseline in [references/static-blocks.md](references/static-blocks.md#editorial-baseline-replaces-the-premium-visual-baseline) instead.
112
115
  - **Interactivity**: Create/edit/delete? Filtering? Sorting? Pagination?
113
116
  - **User context**: Does it need to know who's logged in?
114
117
  - **Settings**: Should anything be editable by the Softr builder (titles, images, toggle sections)?
@@ -163,9 +166,11 @@ For advanced patterns beyond data fetching, load the relevant reference when the
163
166
  | Quick syntax check — import paths, hook signatures, mutation call shapes, field mapping | [references/quick-reference.md](references/quick-reference.md) |
164
167
  | Small reusable patterns — `localStorage` cross-page state, clipboard copy button | [references/common-patterns.md](references/common-patterns.md) |
165
168
  | 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) |
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) |
169
+ | The official **Softr MCP server** — Softr DB schema + full record/table/field/database CRUD (deletes included), field-level browsing of connected Airtable / Google Sheets / Notion / Supabase integrations, **creating, editing, versioning, and deploying Vibe Coding blocks directly** (`get_vibe_coding_docs`, `create_vibe_coding_block`, ...), app management/scaffolding, the **Softr Workflows** suite (26 tools, 418-node catalog), and **per-application MCP servers** | [references/softr-mcp.md](references/softr-mcp.md) |
167
170
  | 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) |
168
171
  | Adding a **dynamic date filter or custom filter control to a native List/Grid block** (via a Custom Code Static block, not a Vibe block): drive the block's conditional filter with `{URL_PARAM:…}`, the empty-param "match nothing" wide-range sentinel, inject the control into the filter row and keep it alive across Softr's re-renders | [references/native-block-filters.md](references/native-block-filters.md) |
172
+ | **Editable settings deep-dive** — full hook catalog (incl. verified-undocumented `useLongTextSetting` and the `navigation` array-schema type), settings-first granularity doctrine, heading-line-split and `-text`/`-link` pairing patterns, naming conventions, rename-resets-value gotcha, empty-media gating, key-by-index rule | [references/editable-settings.md](references/editable-settings.md) |
173
+ | **Static marketing blocks** — heroes, landing headers, pricing tables, footers: workflow deltas (skip datasources), editorial baseline, full-bleed license, full-viewport sizing, block-owned fixed header + caveats, section anchors | [references/static-blocks.md](references/static-blocks.md) |
169
174
 
170
175
  ## Code Structure
171
176
 
@@ -190,9 +195,11 @@ export default function Block() {
190
195
  }
191
196
  ```
192
197
 
193
- Always wrap the outermost layout in `container` and `content` divs — these constrain width to match the Softr app's max width settings.
198
+ Wrap the outermost layout in `container` and `content` divs by default — these constrain width to match the Softr app's max width settings so the block aligns with neighboring native blocks. Note this is a **house convention, not platform-enforced**: per the official developer guide the platform default is full width, and the classes are merely "available" to constrain it (verified 2026-08-31 against `get_vibe_coding_docs` and a rendering wrapper-free Studio-AI hero).
194
199
 
195
- **Exception:** Blocks inside Softr column containers — omit wrappers so Softr controls layout.
200
+ **Exceptions (omit the wrappers deliberately):**
201
+ - Blocks inside Softr column containers — Softr controls layout.
202
+ - Full-bleed marketing blocks (heroes, banner bands, footers) — backgrounds and decorative shapes run edge-to-edge; the block then owns its own gutters (`px-6 md:px-12 lg:px-16`) and inner max-widths, and records the choice in the `// BLOCK PLACEMENT:` comment. See [references/static-blocks.md](references/static-blocks.md#full-bleed-layout-license).
196
203
 
197
204
  ## Block Placement & Page Spacing
198
205
 
@@ -229,7 +236,7 @@ The `// BLOCK PLACEMENT:` marker is intentionally stable so it can be grepped an
229
236
 
230
237
  ### Spacing values (defaults)
231
238
 
232
- **Container** (always): `<div className="container py-0">`
239
+ **Container** (default — omitted by full-bleed blocks and blocks inside column containers; see table below): `<div className="container py-0">`
233
240
 
234
241
  **Inner wrapper** classes by block position:
235
242
 
@@ -239,6 +246,7 @@ The `// BLOCK PLACEMENT:` marker is intentionally stable so it can be grepped an
239
246
  | Middle block | `py-3 px-8` | 12px + Softr separator + 12px ≈ 24px between blocks |
240
247
  | Last block (footer-adjacent) | `pt-3 pb-12 px-8` | 12px top + 48px bottom for footer breathing room |
241
248
  | Standalone (only block on page) | `pt-3 pb-12 px-8` | Treat like a last block |
249
+ | Full-bleed (hero / banner / footer) | none — no container/content; block owns gutters `px-6 md:px-12 lg:px-16` | Edge-to-edge backgrounds; see [references/static-blocks.md](references/static-blocks.md#full-bleed-layout-license) |
242
250
 
243
251
  **Back button** (when present at the top of a block — typically on detail pages): wrap in `<div className="mt-6 mb-4">`. The `mt-6` (24px) adds breathing room above the button independent of wrapper padding; `mb-4` (16px) sits between the button and the first card. Apply this regardless of whether the block is first or mid-page.
244
252
 
@@ -246,10 +254,16 @@ The `// BLOCK PLACEMENT:` marker is intentionally stable so it can be grepped an
246
254
 
247
255
  **Net page rhythm**: between-block gaps (12 + 12 = 24px) match within-block card gaps (`mb-6` = 24px), so the page reads as one consistent vertical rhythm.
248
256
 
257
+ ### Full-viewport hero blocks
258
+
259
+ A hero may size itself to the viewport — vh units inside a block resolve against the real window (blocks are shadow DOM in the main document, not iframes). The Studio-verified responsive shape is `min-h-screen lg:min-h-0 lg:h-screen` (natural height on mobile, locked viewport height on desktop). Three rules: (1) `h-screen` fills the window only when the native header is hidden on that page — with a native header above, a 100vh block overflows by the header height; use `min-h-[calc(100vh-<px>)]` when native chrome stays; (2) hard `h-screen` + `overflow-hidden` + centered flex **clips settings-grown content unrecoverably** — prefer `lg:min-h-screen` unless the locked look is explicitly wanted; (3) the standard spacing table above does not apply — the hero owns all its spacing. Extend the placement comment: `// BLOCK PLACEMENT: full-viewport hero, native header hidden, owns all spacing`. Full detail in [references/static-blocks.md](references/static-blocks.md#full-viewport-hero-sizing).
260
+
249
261
  ## Premium Visual Baseline
250
262
 
251
263
  **Every block must look polished in its first version.** Styling is not a follow-up task — it is a core requirement of every code generation. Apply ALL of the following by default unless the user explicitly requests a minimal/plain style.
252
264
 
265
+ **Scope: this is the app-UI baseline** — dashboards, lists, forms, detail pages. Static marketing blocks (heroes, landing sections, footers) use the **editorial baseline** in [references/static-blocks.md](references/static-blocks.md#editorial-baseline-replaces-the-premium-visual-baseline) instead — typographic hierarchy and brand-exact values, no gradient wrapper/cards/skeletons/empty states (nothing loads).
266
+
253
267
  Refer to [ui-ux-guidelines.md](ui-ux-guidelines.md) for full design principles.
254
268
 
255
269
  ### 1. Gradient background wrapper
@@ -303,9 +317,11 @@ Adjust the top gradient color to complement the user's brand.
303
317
  **Tailwind CSS** is pre-configured. **Semantic color tokens** preferred:
304
318
  `bg-background`, `bg-card`, `bg-primary`, `bg-secondary`, `bg-muted`, `bg-accent`, `bg-destructive`, `border`, `border-input`
305
319
 
320
+ **Arbitrary values compile in full** — the platform's Tailwind build is JIT, so the whole arbitrary-value syntax works, including opacity modifiers on arbitrary hex (`bg-[#FAF5EC]/85`), variant + arbitrary + opacity combined (`hover:bg-[#6E7A5C]/10`), negative arbitrary values (`-top-[22%]`, `hover:-translate-y-[1px]`), arbitrary object-position (`object-[62%_25%]`), arbitrary z (`z-[1]`), and vw sizing (verified 2026-08-31 from rendering Studio-AI output). Classes must be **static source strings** — never template-interpolate (`` bg-[${x}] ``); JIT extracts classes by static scan (standard-Tailwind inference, not Softr-verified). When to reach for them vs. the scale: see the editorial lane in [ui-ux-guidelines.md](ui-ux-guidelines.md) §7. (This covers arbitrary *values* and standard variants; arbitrary *selector* variants like `[&_svg]:` have at least one known bundler failure — see the SelectTrigger row in [references/anti-patterns.md](references/anti-patterns.md#layout--styling).)
321
+
306
322
  **Font classes:** `font-heading`, `font-sans`, `font-mono`
307
323
 
308
- **Conditional classNames:** `import { cn } from "@/lib/utils";`
324
+ **Conditional classNames:** `import { cn } from "@/lib/utils";` — template-literal conditionals (`` className={`base ${cond ? "a" : "b"}`} ``) are equally valid (Studio AI emits them); prefer `cn()` when merging many groups or de-duplicating conflicting classes.
309
325
 
310
326
  **DO NOT USE:** CSS modules, styled-components, or CSS file imports.
311
327
 
@@ -364,7 +380,7 @@ var askAi = useNavigationSetting({
364
380
  - `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.
365
381
  - `OPEN_URL` — opens an external URL. Needs `destination` (the URL) + `openIn` (`"SELF"` | `"TAB"`).
366
382
  - `OPEN_PAGE` — navigates to a Softr page in-app. Needs `destination` (page path) + `openIn` (`"SELF"` | `"TAB"` | `"MODAL"`).
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.
383
+ - `TRIGGER_CUSTOM_WORKFLOW` — runs a Softr workflow. The documented setting shape is `{ action: "TRIGGER_CUSTOM_WORKFLOW" }` with no `destination` (same shape as `OPEN_CHAT`); the builder picks the workflow in the block's settings panel. Not verified live whether a `destination` is also accepted — don't rely on one. The workflow-side receiving end is the Softr Apps trigger **"Run Custom Workflow action triggered"** in the workflow node catalog (name-based match, not wired live) — workflows themselves are buildable via MCP; see [references/softr-mcp.md](references/softr-mcp.md#workflows).
368
384
 
369
385
  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:
370
386
 
@@ -376,6 +392,19 @@ When the action navigates to a record-specific page, pass the runtime record id
376
392
 
377
393
  For on-brand custom styling (matching DESIGN.md inline-style conventions instead of shadcn Button), wrap the same pattern with a styled `<button>` — `<NavigationAction>` will render its children into the button's slot. The `asChild` attribute on Button is what enables this slot composition; without it shadcn renders its own native button and ignores `<NavigationAction>`.
378
394
 
395
+ **Third shape — standalone `<NavigationAction>` with `className`** (text-style links, nav items — no Button wrapper). NavigationAction renders its own clickable element and forwards `className` onto it; include UA-style resets since that element ships default chrome. Observed in Studio-AI output 2026-08-31, not in the official developer guide — don't treat className forwarding as officially guaranteed:
396
+
397
+ ```jsx
398
+ <NavigationAction
399
+ navigation={item.link}
400
+ className="text-[15px] text-[#2A2520] hover:text-[#AE5E3D] transition-colors bg-transparent border-none cursor-pointer no-underline"
401
+ >
402
+ {item.label}
403
+ </NavigationAction>
404
+ ```
405
+
406
+ **Brand hex on a shadcn `<Button>` — the middle path.** To keep `<Button asChild>` (slot composition, focus ring, disabled handling) while guaranteeing an exact brand color, set the color as an inline style: `<Button asChild className="... shadow-none" style={{ backgroundColor: "#B4603D" }}>`. The precedence consequence: with an inline `backgroundColor`, **no `hover:bg-*` class can ever repaint it** (inline style outranks all classes, and ui-ux-guidelines' "hover handled by shadcn Button" no longer holds) — do hover feedback with `hover:opacity-90` / `hover:-translate-y-[1px]` instead. Alternative: a static `bg-[#HEX]` class also works (arbitrary values compile, and shadcn's `cn()`/tailwind-merge resolves it against the variant's `bg-primary`), in which case `hover:bg-*` stays available.
407
+
379
408
  **Any public npm package** auto-installs on import: `import { format } from "date-fns";`
380
409
 
381
410
  ### React Hooks — Critical Import Rule
@@ -393,13 +422,18 @@ useState(null); // without import = ReferenceError
393
422
 
394
423
  ## Editable Settings
395
424
 
396
- Hooks from `@/lib/editable-settings` let Softr builders customize blocks. Each `name` must be unique.
425
+ Hooks from `@/lib/editable-settings` let Softr builders customize blocks via **Content → Settings**. Each `name` must be unique — and `name` is the **persistence key**: renaming it in a later edit silently resets the builder's saved value to `initialValue`. Compact signatures below; the deep-dive (granularity doctrine, naming conventions, undocumented hooks/types, empty-media gating) is [references/editable-settings.md](references/editable-settings.md).
397
426
 
398
427
  ```jsx
399
428
  import { useTextSetting } from "@/lib/editable-settings";
400
429
  var title = useTextSetting({ name: "title", label: "Title", initialValue: "Welcome", required: false });
401
430
  // Returns: string
402
431
 
432
+ import { useLongTextSetting } from "@/lib/editable-settings";
433
+ var description = useLongTextSetting({ name: "description", label: "Description", initialValue: "..." });
434
+ // Returns: string — multi-line textarea in the Settings pane. UNDOCUMENTED officially but verified
435
+ // working 2026-08-31 (Studio-AI output). Render with `whitespace-pre-line` or builder line breaks collapse.
436
+
403
437
  import { useImageSetting } from "@/lib/editable-settings";
404
438
  var image = useImageSetting({ name: "hero", label: "Hero Image", initialValue: { src: "https://...", alt: "Hero" } });
405
439
  // Returns: { src: string, alt: string }
@@ -417,6 +451,7 @@ import { useNavigationSetting } from "@/lib/editable-settings";
417
451
  var nav = useNavigationSetting({ name: "cta", label: "CTA", initialValue: { action: "OPEN_PAGE", destination: "/pricing", openIn: "SELF" } });
418
452
  // Returns: { action, destination, openIn }
419
453
  // `openIn` MUST be one of: "SELF" (same tab), "TAB" (new tab), or "MODAL". Any other value (e.g. "SAME_TAB", "NEW_TAB") fails Softr's setting validator with: "useNavigationSetting(): The 'initialValue.openIn' property in the 'navigation' setting must be \"SELF\", \"TAB\", or \"MODAL\" if provided".
454
+ // The validator ACCEPTS an initialValue with no `action` key (Studio AI emits { destination, openIn } alone — verified 2026-08-31). Keep emitting an explicit `action` when generating, but don't flag its absence as a defect when reviewing existing blocks.
420
455
 
421
456
  import { useBooleanSetting } from "@/lib/editable-settings";
422
457
  var show = useBooleanSetting({ name: "toggle", label: "Show header", initialValue: false });
@@ -432,13 +467,19 @@ var features = useArraySetting({
432
467
  },
433
468
  initialValue: [{ title: "Fast", description: "Blazing fast.", icon: { icon: "zap" } }],
434
469
  });
435
- // Schema types: "text", "image", "video", "vibeCodingBlockIcon"
470
+ // Schema types: "text", "image", "video", "vibeCodingBlockIcon" — plus UNDOCUMENTED-but-working
471
+ // "navigation" (verified 2026-08-31 via Studio-AI output; renders per-item link pickers, item values
472
+ // carry the useNavigationSetting shape → pass to <NavigationAction navigation={item.link}>).
473
+ // Give navigation-typed schema entries a per-field initialValue, or guard the render — builder-added
474
+ // rows otherwise start with that field undefined.
436
475
  // No nested arrays. Don't put vibeCodingBlockIcon as first field.
476
+ // Key rendered rows by INDEX (key={index}), never by an editable field — builder-added rows all start
477
+ // at the schema default, so value keys duplicate immediately. See references/editable-settings.md.
437
478
  ```
438
479
 
439
480
  ## Hard Constraints
440
481
 
441
- Non-negotiable rules enforced by the Softr platform:
482
+ Non-negotiable rules. Most are enforced by the Softr platform (compiler, validator, or runtime); the ones marked **[house]** are conventions this skill enforces on itself — the platform accepts violations, but the rule exists for consistency or safety:
442
483
 
443
484
  1. **Browser-only** — No server-side code, no Node.js APIs.
444
485
  2. **Static field mappings** — `q.select()` keys and values must be string literals.
@@ -448,7 +489,7 @@ Non-negotiable rules enforced by the Softr platform:
448
489
  6. **Array setting icon placement** — Never put `vibeCodingBlockIcon` as first field.
449
490
  7. **No nested arrays in settings** — Use text with separator, split in code.
450
491
  8. **Default export required** — `export default function Block()`.
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").
492
+ 9. **Container wrapping [house]** — Wrap in `<div className="container py-0"><div className="content">` by default so the block's width matches native blocks. NOT platform-enforced: the platform default is full width and the wrappers are officially optional (verified 2026-08-31). Deliberate full-bleed blocks (heroes, banner bands, footers) and blocks inside column containers omit them — see "Block Placement & Page Spacing" and [references/static-blocks.md](references/static-blocks.md#full-bleed-layout-license). Vertical padding lives on the inner wrapper and depends on block placement.
452
493
  10. **Inline options literals for data hooks** — `useRecords` fails to compile when its options
453
494
  object is passed through a variable or wrapper function. Build the options object inline at the
454
495
  call site; share `q.select` mappings between hooks, not whole options objects. (Same
@@ -464,10 +505,10 @@ Non-negotiable rules enforced by the Softr platform:
464
505
  14. **React functional components only** — No class components.
465
506
  15. **Do NOT `import React from 'react'`** — Use named imports for hooks.
466
507
  16. **No CSS modules or styled-components** — Tailwind only.
467
- 17. **setTimeout for scroll** -- Wrap programmatic scroll in `setTimeout(fn, 0)`.
508
+ 17. **setTimeout for scroll [house]** -- Wrap **programmatic** scroll commands (`scrollIntoView`, `scrollTo`) in `setTimeout(fn, 0)`. This is about ISSUING scrolls only — LISTENING to window scroll (`window.addEventListener("scroll", ...)`) needs no wrapper and works normally from block code; see [references/common-patterns.md](references/common-patterns.md#scroll-condensing-fixed-header-landing-page-hero).
468
509
  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.
469
510
  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.
470
- 20. **Relative paths in navigation** -- Use `/page-name?recordId=...`, never hardcoded domains like `app.client.com/page`.
511
+ 20. **Relative paths in navigation [house]** -- Use `/page-name?recordId=...`, never hardcoded domains like `app.client.com/page`. The platform accepts absolute self-domain URLs as navigation setting values (a Studio-generated hero shipped its CTA configured as `https://<app>.softr.app/#section` — observed in the Settings pane, 2026-08-31), but they break on custom-domain publish — rewrite them relative, including hash anchors (`/#section`).
471
512
  21. **Recompiles reset Action permissions** -- Every code recompile/redeploy resets the block's
472
513
  auto-registered Actions to **default permissions**. Do the Actions-tab permission tightening pass
473
514
  only after the LAST redeploy, and re-check it after any future one. Verified live 2026-08-25
@@ -481,6 +522,8 @@ History, kept so old guidance elsewhere is recognizable as superseded: until mid
481
522
 
482
523
  Still house conventions: the field-value helper is named `getFieldValue()`, with property priority `label` -> `name` -> `title`.
483
524
 
525
+ **Platform truth sources.** Blocks emitted by Softr's own Studio AI are known-good evidence of what the current platform accepts — they compile and render live, and they surface undocumented hooks and setting types before the official docs catch up (this is how `useLongTextSetting` and the `navigation` array-schema type were discovered, 2026-08-31). Mine them for capabilities, but **never copy them verbatim**: fix React keys on settings-array loops (index, never a builder-editable label), consolidate near-duplicate hardcoded hexes into brand/DESIGN.md tokens, add the missing mobile nav / media-setting guards, and normalize formatting. Functional patterns in Studio output are platform-support evidence; its formatting and code hygiene are not patterns to imitate. (This is distinct from the no-churn rule above — cleaning up a block you're ADOPTING into the repo as source of truth is required; modernizing a deployed working block's syntax is still churn.) Adoption checklist: [references/softr-mcp.md](references/softr-mcp.md#adopting-studio-ai-generated-code). Note the inverse rule stays too: Studio's chat **prose** fabricates facts (datasource UUIDs) — trust its code, not its answers.
526
+
484
527
  ## Anti-Patterns Checklist
485
528
 
486
529
  Before delivering any block, run through [references/anti-patterns.md](references/anti-patterns.md) — a categorized catalog of every violation observed in production (data access, mutations, hooks, layout, helper blocks).
@@ -190,7 +190,7 @@ fetch("https://workflows-api.softr.io/v1/workflows/WORKFLOW_ID/executions/EXECUT
190
190
 
191
191
  - Supports GET and POST methods only in the data source connector (no PUT, PATCH, DELETE -- use Softr Action buttons with custom API call actions for those)
192
192
  - Response data must be parseable JSON
193
- - **Proxy payloads are text-only** -- streams, `FormData`, and file uploads through `proxyFetch` fail. For file-to-record uploads use `useUpload` (see [writing.md](writing.md#file-uploads)), which targets the connected record datasource; uploading files to an arbitrary external API needs a different mechanism (e.g. a Softr workflow / webhook receiver)
193
+ - **Proxy payloads are text-only** -- streams, `FormData`, and file uploads through `proxyFetch` fail. For file-to-record uploads use `useUpload` (see [writing.md](writing.md#file-uploads)), which targets the connected record datasource; uploading files to an arbitrary external API needs a different mechanism e.g. a Softr Workflow with a `WEBHOOK` trigger (and `RESPONDED_TO_WEBHOOK` for a custom HTTP reply), buildable via MCP: [../references/softr-mcp.md](../references/softr-mcp.md#workflows)
194
194
  - No 2-way user sync
195
195
  - Requires Business or Enterprise plan
196
196
 
@@ -93,6 +93,8 @@ After `source ~/.zshrc`, just run `get-softr-database <database_id>` from anywhe
93
93
  | Relationship | Yes | Linked records to other Softr Database tables. Write as an **array of record-id strings**, e.g. `[recordId]` (verified 2026-08-25) |
94
94
  | Formula | Read-only | Booleans return as strings: use `=== "1"` for true, `=== "0"` for false |
95
95
 
96
+ This table is the coarse block-side view. The full current catalog is larger — Rating, Duration, Currency, Percent, plus computed Lookup/Rollup/Count and system fields — and is best fetched live via the MCP's `get_schema`, authoritative **for the server you're calling**: the per-app servers currently document a fuller catalog than the workspace server (adding Address, Progress, Time, Date range, Button, and display options like Percent's progress-bar/ring), and even operator names differ between the two — see [../references/softr-mcp.md](../references/softr-mcp.md#softr-database-tools).
97
+
96
98
  ## Rate Limits
97
99
  No API rate limits. Softr Database queries run internally without external API calls, making it the best choice for high-traffic applications.
98
100
 
@@ -509,9 +509,10 @@ block, no REST API, and no exposed key. Required reading:
509
509
  deployment routed all of its cross-table writes (header + ledger + audit-log rows) through
510
510
  `from:`-scoped mutation hooks.
511
511
 
512
- Two remaining alternatives, for the cases multi-datasource doesn't cover:
512
+ Three remaining alternatives, for the cases multi-datasource doesn't cover:
513
513
 
514
514
  - **For Airtable backends** — when the cascade logic is heavy, write to the block's own table and let an Airtable automation script handle the cascade. See [../references/airtable-automations.md](../references/airtable-automations.md). Keeps the block simple and lets cross-table logic live next to the data.
515
+ - **For Softr Database backends** — same pattern, native tool: a **Softr Workflow** triggered by the record change (record added / updated / deleted / meets-conditions / enters-view triggers, bulk record update/delete actions, per-item loops, and a custom JS/Python code node). Buildable and publishable via the MCP. See [../references/softr-mcp.md](../references/softr-mcp.md#workflows).
515
516
  - **Softr Database REST API via `fetch()`** — a fallback for what the hooks can't express (e.g. writes from outside a Vibe block, or admin tooling that must bypass block bindings). Details below.
516
517
 
517
518
  **Base URL:** `https://tables-api.softr.io/api/v1/databases/{databaseId}/tables/{tableId}/records`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "softr-vibe-coding",
3
- "version": "2.1.2",
3
+ "version": "2.3.0",
4
4
  "description": "Claude Code skill for generating production-ready Softr Vibe Coding blocks (JSX). Installs into ~/.claude/skills/ and auto-updates on each Claude Code session.",
5
5
  "bin": {
6
6
  "softr-vibe-coding": "./bin/cli.js"
@@ -1,6 +1,6 @@
1
1
  # Airtable Automation Scripts & Formulas
2
2
 
3
- Companion reference for Softr Vibe Coding blocks. Many Softr blocks talk to an Airtable backend, and some flows can't be done from the block side — most commonly **cross-table writes triggered by a record change** (the block can only write to its own configured data source). For those, the right tool is an Airtable Automation Script, written in JavaScript and triggered by Airtable's automation runner.
3
+ Companion reference for Softr Vibe Coding blocks. Many Softr blocks talk to an Airtable backend, and some flows can't be done from the block side — most commonly **cross-table cascades triggered by a record change** (a block writes only to its connected data sources, and even a multi-datasource block shouldn't carry heavy cascade logic — see [datasources/multi-datasource.md](../datasources/multi-datasource.md)). For those, the right tool is an Airtable Automation Script, written in JavaScript and triggered by Airtable's automation runner.
4
4
 
5
5
  This guide covers Airtable's two scripting environments + Airtable formulas. **It is NOT about Softr Vibe Coding** — runtime, API surface, and gotchas are entirely different. Don't apply Softr block rules (shadow DOM isolation, data-hook constraints, etc.) here.
6
6
 
@@ -10,10 +10,11 @@ This guide covers Airtable's two scripting environments + Airtable formulas. **I
10
10
  |---|---|
11
11
  | Build UI inside a Softr page | Softr Vibe Coding block |
12
12
  | React to a record-level event in Airtable (e.g. status change) and cascade to other tables | **Airtable Automation Script** |
13
+ | React to a record-level event in a **Softr Database** and cascade | **Softr Workflow** (record triggers + bulk actions + JS/Python code node) — see [softr-mcp.md → Workflows](softr-mcp.md#workflows) |
13
14
  | Run ad-hoc transformations / bulk fixes | Airtable Scripting Extension |
14
15
  | Compute a derived value displayed in an Airtable cell | Airtable formula field |
15
16
 
16
- If a Softr block needs to write across multiple tables in response to a user click, the **cleanest pattern** is: have the Softr block write to its own table, and let an Airtable automation handle the cascade. See [datasources/writing.md Cross-Table Operations](../datasources/writing.md#cross-table-operations) for the alternative (Softr Database REST API), but the Airtable-side automation is usually simpler.
17
+ For plain multi-table writes from a user click, the default is **multi-datasource from the block itself** (`from:`-scoped mutation hooks — see [datasources/writing.md → Cross-Table Operations](../datasources/writing.md#cross-table-operations)). When the multi-table write is really a **cascade with its own logic** — too heavy to live in the block — the cleanest pattern is: have the block write to its own table, and let the backend cascade: an Airtable automation for Airtable backends, a Softr Workflow for Softr Database backends. writing.md lists all the alternatives, including the Softr Database REST API fallback.
17
18
 
18
19
  ## Two scripting environments — pick the right one
19
20
 
@@ -72,8 +72,9 @@ Run through this catalog before delivering any block. Every row is a violation o
72
72
  | Relying on the page background on a **dark** brand, and shipping a block whose own canvas is unpainted | The row above assumes a light app whose page bg is already correct. On a dark brand it inverts: `custom-code-header.html`'s `body { background-color: #000 !important }` does NOT cross the shadow-DOM boundary, and a default Softr page is white — so a block with white text, a white-only logo, or a white primary button renders **invisibly on white**. Verified July 2026: a black-canvas feedback form shipped with its wordmark and its Submit button both white-on-white; the button was there and clickable, just unseeable. On a dark brand, paint `backgroundColor` on the block's own outer wrapper AND set the Softr page background to the same value — two identical pure blacks composite with no seam, so the double-paint concern above doesn't bite. Painting it in the block also keeps it correct if the custom-code snippet is ever removed. |
73
73
  | Setting only `html, body { background }` in `custom-code-header.html` and expecting the app to change colour | Softr paints the same page fill on **four stacked layers**: `html`, `body`, `#page-content`, and a **class-less wrapper div** nested inside `#page-content`. Styling one gets covered by the ones above it, so `body` alone appears to do nothing. Paint the backdrop on `html`, then clear the duplicates: `body, #page-content { background: transparent }` plus `#page-content div:not(.softr-topbar):not(.softr-topbar *)`. The `:not()` exclusion is required — the nav renders inside `#page-content` and that id's specificity out-ranks `.softr-topbar` rules, so a blanket clear silently flattens the dropdown panel. Full recipe in [native-chrome-styling.md](native-chrome-styling.md). |
74
74
  | `document.getElementById(...)` / `document.querySelector(...)` to find an element inside the block — for example, a hidden `<input type="file">` triggered by a visible "Upload" button via `getElementById('myInput').click()` | Vibe Coding blocks render inside a shadow DOM. The global `document` traversal stops at the shadow boundary, so id/selector lookups for elements inside the block return `null`. The user-visible symptom is a control that does nothing — no error, no file picker, no focus, no scroll — because the chained `.click()` / `.focus()` / `.scrollIntoView()` was called on `null`. Use a **React `useRef`** instead: `var inputRef = useRef(null)`, then `<input ref={inputRef} />` and `<button onClick={function() { if (inputRef.current) inputRef.current.click(); }}>`. Refs hold direct node references and don't depend on DOM traversal, so they work regardless of which DOM tree the node lives in. This applies to every "trigger a hidden element" pattern: hidden file inputs, programmatic focus, scroll-into-view, `.click()` on a non-visible button. |
75
+ | Inlining brand hexes at every point of use (the signature failure of Studio-AI-generated styling) | Hoist the palette to module-scope constants — `const BRAND = { terracotta: "#B4603D", ink: "#211C18" }` — and reference those. Inlined hexes drift into near-duplicates: observed live 2026-08 in one Studio-emitted hero, `#AE5E3D` vs `#B4603D` for the same terracotta and three near-identical near-blacks. The literals-only static-analysis rule applies ONLY to `datasource.define()` / `q.select()` / data-hook options — style objects and JS expressions use constants freely (the BRAND_INK/BRAND_CANVAS rows above and helper-blocks.md's `BRAND_PRIMARY` are existing house precedent). Constants reach the DOM via inline `style` or by choosing between STATIC class strings — never template-interpolated into arbitrary classes (`` bg-[${BRAND.x}] ``): Tailwind's JIT extracts classes by static source scan (standard-Tailwind inference, not Softr-verified) |
75
76
  | Using `window.addEventListener("beforeunload", ...)` as the only unsaved-changes guard in a form block | Softr is a SPA. Internal nav (Softr's nav bar, sidebar links, `<NavigationAction>`) changes the route via the client-side router — `beforeunload` only fires on full page unload (tab close, refresh, external link), so the warning silently misses every in-app navigation. Use `useNavigationBlocker(isDirty)` from `@/lib/use-navigation-blocker` instead; it covers SPA nav AND browser unload with one API. Softr's Vibe Coding bundler often wires this automatically when a form is detected as dirty — you only need to add it manually for advanced cases (multi-step forms, custom dirty tracking, blocking on non-form state). See [common-patterns.md](common-patterns.md#navigation-blocker-for-unsaved-changes). |
76
- | Targeting Softr's hashed build classes (e.g. `.f8f11e5_m9ntthp`) when restyling the native header/nav from `custom-code-header.html` | Softr regenerates the hash on every deploy, so the rule silently dies. Target stable hooks: `.softr-topbar`, `.softr-nav-link`, `.softr-nav-button`, `.softr-nav-logo`, `#topbar-root`; for dropdown menus (no `softr-*` class) use the Radix/ARIA attrs `[role="menu"]` / `[role="menuitem"]` / `[role="group"]` / `[aria-expanded="true"]`, scoped under `.softr-topbar`. The native header is Softr chrome (main document), not a block — it can't be built as a Vibe Coding block. See [native-chrome-styling.md](native-chrome-styling.md). |
77
+ | Targeting Softr's hashed build classes (e.g. `.f8f11e5_m9ntthp`) when restyling the native header/nav from `custom-code-header.html` | Softr regenerates the hash on every deploy, so the rule silently dies. Target stable hooks: `.softr-topbar`, `.softr-nav-link`, `.softr-nav-button`, `.softr-nav-logo`, `#topbar-root`; for dropdown menus (no `softr-*` class) use the Radix/ARIA attrs `[role="menu"]` / `[role="menuitem"]` / `[role="group"]` / `[aria-expanded="true"]`, scoped under `.softr-topbar`. The native header is Softr chrome (main document), not a block — it can't be built as a Vibe Coding block. (A landing page with the native header HIDDEN may instead ship a block-owned in-block header — see [static-blocks.md](static-blocks.md#block-owned-landing-page-header); that's a different pattern, not a rebuild of native chrome.) See [native-chrome-styling.md](native-chrome-styling.md). |
77
78
  | Softr nav dropdown panel shows a tall blank gap below the items, and `height: auto` won't shrink it | The items sit in a CSS grid Softr sets to `grid-auto-flow: column` with pre-sized empty row tracks (`grid-template-rows: 60px 60px…`). Override the flow on `.softr-topbar [role="menu"] [role="group"]`: `grid-auto-flow: row !important; grid-template-rows: none !important; grid-auto-rows: auto !important` (leave `grid-template-columns` to preserve the menu width). Verified June 2026. See [native-chrome-styling.md](native-chrome-styling.md). |
78
79
  | Setting the page background on `body` (or any single element) — it appears to do nothing | Softr paints the SAME page fill on `html`, `body`, `#page-content`, AND a deeper class-less wrapper div, stacked — so styling one gets covered. Paint your backdrop on `html`, then clear the duplicates above it: `body`, `#page-content`, and `#page-content div` — but EXCLUDE the header subtree with `:not(.softr-topbar):not(.softr-topbar *)` (it renders inside `#page-content`, and `#page-content`'s id specificity would otherwise flatten the dropdown panel). Verified June 2026. See [native-chrome-styling.md](native-chrome-styling.md). |
79
80
 
@@ -88,6 +89,11 @@ Run through this catalog before delivering any block. Every row is a violation o
88
89
  | Anti-Pattern | Correct Approach |
89
90
  |---|---|
90
91
  | `useNavigationSetting` with `openIn: "SAME_TAB"` / `"NEW_TAB"` / any other string | `openIn` must be exactly `"SELF"`, `"TAB"`, or `"MODAL"` -- Softr's setting validator rejects unknown values at save time with: *"The 'initialValue.openIn' property in the 'navigation' setting must be \"SELF\", \"TAB\", or \"MODAL\" if provided"* |
92
+ | Flagging a navigation initialValue with no `action` key as a defect during review | The validator ACCEPTS `{ destination, openIn }` alone — Studio AI emits this shape and it saves/renders fine (verified 2026-08-31). Keep emitting an explicit `action` when GENERATING (deterministic; click-time default is unverified inference), but don't fail reviews over its absence. See [editable-settings.md](editable-settings.md#usenavigationsetting) |
93
+ | `key={item.label}` (or any builder-editable field) when mapping `useArraySetting` items | `key={index}` (matching the official docs' own example). Array-item fields are builder-editable and NOT unique — the schema gives every newly added row the same default value, so two clicks of Add Item = duplicate keys = broken reorder/update rendering in the Settings live preview. Index keys are safe here: setting rows are stateless display rows fully re-rendered on every settings change. Studio's own AI gets this wrong — don't copy it |
94
+ | A `{ type: "navigation" }` array-schema entry with no per-field `initialValue`, rendered unguarded | Builder-added rows start with that field `undefined` → `<NavigationAction navigation={undefined}>` (behavior unverified; dead element at best). Seed a per-field `initialValue` in the schema, or guard: `{item.link && <NavigationAction ...>}`. See [editable-settings.md](editable-settings.md#usearraysetting) |
95
+ | Unconditional `<img src={setting.src}>` against a media setting that may start empty (`initialValue: { src: "" }`) | Gate it (`{setting.src && <img .../>}`) or render a same-size placeholder — an empty-string src triggers React's re-download warning and shows a broken/empty band at the slot's fixed height until the builder uploads. The empty state is platform-normal (the official docs' own array example seeds `{ src: "", alt: "" }`), so this is the default path, not an edge case |
96
+ | Rendering a `useLongTextSetting` value in a plain `<p>` | Add `whitespace-pre-line` (or split on `\n`) — the Settings pane gives builders a multi-line textarea, and HTML collapses their line breaks to spaces otherwise. See [editable-settings.md](editable-settings.md#text-hooks-usetextsetting-and-uselongtextsetting) |
91
97
 
92
98
  ## Helper Blocks
93
99