softr-vibe-coding 2.13.3 → 2.13.5
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 +9 -0
- package/README.md +21 -12
- package/SKILL.md +96 -14
- package/datasources/fields.md +25 -3
- package/datasources/multi-datasource.md +13 -3
- package/datasources/reading.md +29 -6
- package/datasources/writing.md +5 -1
- package/package.json +1 -1
- package/references/anti-patterns.md +15 -8
- package/references/browser-checks.md +156 -1
- package/references/common-patterns.md +223 -1
- package/references/dembrandt.md +7 -2
- package/references/helper-blocks.md +6 -2
- package/references/native-chrome-styling.md +163 -14
- package/references/quick-reference.md +38 -0
- package/references/searchable-dropdown.md +28 -1
- package/references/softr-mcp.md +36 -25
- package/references/static-blocks.md +7 -4
- package/ui-ux-guidelines.md +38 -4
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,15 @@ 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.13.5] - 2026-10-06
|
|
8
|
+
- Release 2.13.5
|
|
9
|
+
- Correct the MCP FILTER-condition claim and eight other conflicts found in the 2026-10-06 audit
|
|
10
|
+
|
|
11
|
+
## [2.13.4] - 2026-10-06
|
|
12
|
+
- Release 2.13.4
|
|
13
|
+
- Add in-block modal above Softr's bars (shadcn Dialog sits under the top bar)
|
|
14
|
+
- Add app frame and app-page layout for Softr sidebar navigation
|
|
15
|
+
|
|
7
16
|
## [2.13.3] - 2026-10-05
|
|
8
17
|
- Release 2.13.3
|
|
9
18
|
- Anchor CHANGELOG entries at the commit that introduced the published version
|
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ This Claude skill teaches Claude Code how to generate complete, polished Softr V
|
|
|
21
21
|
- **All 14 Softr data sources** — Airtable, Softr Database, Google Sheets, HubSpot, Notion, Coda, monday.com, SmartSuite, ClickUp, Xano, Supabase, BigQuery, SQL Database, and REST API — each with field mapping, rate limits, and gotchas
|
|
22
22
|
- **Helper blocks & cross-block patterns** — Invisible helper blocks for multi-table access via `window` globals + `CustomEvent`, `useWindowData` hook, breadcrumb navigation, saved views architecture
|
|
23
23
|
- **Advanced integrations** — Shadow DOM CSS isolation for third-party libraries (Leaflet, Mapbox, TinyMCE, Quill, FullCalendar)
|
|
24
|
-
- **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
|
|
24
|
+
- **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), plus the **app frame** for apps with Softr's sidebar navigation: the bars' colour as one frame around a paper content sheet, with blocks full-bleed, transparent and laid out by their own width — distinct from blocks
|
|
25
25
|
- **UI/UX design guidelines** — 26 sections covering visual hierarchy, color, typography, spacing, motion design, accessibility, responsive patterns, and an AI slop anti-pattern checklist
|
|
26
26
|
- **Self-validation** — Claude checks Softr platform compatibility and house conventions (inline hook options, correct payload shapes, correct imports, container wrappers or a deliberate full-bleed layout, `getFieldValue()` wrapping, hooks ordering) before delivering code
|
|
27
27
|
- **Premium visual baseline** — every app-UI block (dashboards, lists, forms, detail pages) ships polished from v1: gradient backgrounds, card elevation, loading skeletons, empty states, error states; static marketing blocks use the editorial baseline instead
|
|
@@ -169,7 +169,7 @@ Create a contact form that creates records in our Airtable Contacts table
|
|
|
169
169
|
softr-vibe-coding/
|
|
170
170
|
├── SKILL.md # Main skill
|
|
171
171
|
│ # Workflow, code structure, visual baseline,
|
|
172
|
-
│ # components, settings,
|
|
172
|
+
│ # components, settings, 29 hard constraints
|
|
173
173
|
│
|
|
174
174
|
├── ui-ux-guidelines.md # Design reference
|
|
175
175
|
│ # 26 sections: hierarchy, color, typography,
|
|
@@ -190,32 +190,37 @@ softr-vibe-coding/
|
|
|
190
190
|
│ │ # browsing (Airtable/Sheets/Notion/Supabase),
|
|
191
191
|
│ │ # Softr DB schema + record tools incl. deletes,
|
|
192
192
|
│ │ # app management/scaffolding, Workflows suite
|
|
193
|
-
│ │ # (
|
|
193
|
+
│ │ # (28 tools, 418-node catalog), per-application
|
|
194
194
|
│ │ # MCP servers, auth, permissions; what the server
|
|
195
195
|
│ │ # enforces on block data endpoints, "Preview as"
|
|
196
196
|
│ │ # role testing, search-replace on 100KB+ blocks
|
|
197
197
|
│ │ # (Sep 18 2026); Oct 1 2026: tool rename map,
|
|
198
198
|
│ │ # push verification by sourceSha256, stub tools
|
|
199
|
-
│ │ # after a resume, update_field/update_table fixes
|
|
199
|
+
│ │ # after a resume, update_field/update_table fixes;
|
|
200
|
+
│ │ # Oct 6 2026: MCP-written FILTER conditions are
|
|
201
|
+
│ │ # inert (set them in Studio), Workflows tools as
|
|
202
|
+
│ │ # workflow_*, denied by-id fetch per backend
|
|
200
203
|
│ ├── browser-checks.md # Checking a pushed block in a browser with
|
|
201
204
|
│ │ # the agent-browser CLI (ask before installing):
|
|
202
205
|
│ │ # preview cookie, shadow-DOM refs grepped in the
|
|
203
206
|
│ │ # shell, eval measurements, the records-trigger
|
|
204
207
|
│ │ # write guard proven before any click, what a
|
|
205
|
-
│ │ # click sent, screenshots to disk (Oct 1 2026)
|
|
208
|
+
│ │ # click sent, screenshots to disk (Oct 1 2026);
|
|
209
|
+
│ │ # testing Custom Code header CSS (Oct 5 2026)
|
|
206
210
|
│ ├── advanced-integrations.md # Shadow DOM CSS isolation
|
|
207
211
|
│ │ # Leaflet, Mapbox, TinyMCE, Quill, FullCalendar
|
|
208
212
|
│ ├── native-chrome-styling.md # Restyle Softr's native shell (header, footer,
|
|
209
213
|
│ │ # nav, dropdowns, page background) via global
|
|
210
214
|
│ │ # Custom Code CSS — stable selectors, floating
|
|
211
|
-
│ │ # islands, dropdown grid fix, multi-layer page-bg
|
|
215
|
+
│ │ # islands, dropdown grid fix, multi-layer page-bg,
|
|
216
|
+
│ │ # app frame for sidebar apps (Oct 5 2026)
|
|
212
217
|
│ ├── native-block-filters.md # Dynamic date / URL-param filters + custom filter
|
|
213
218
|
│ │ # controls on native List/Grid blocks — wide-range
|
|
214
219
|
│ │ # sentinel, inject into filter row, survive re-renders
|
|
215
220
|
│ ├── anti-patterns.md # Categorized violation catalog
|
|
216
221
|
│ │ # Data access, mutations, hooks, layout,
|
|
217
222
|
│ │ # permissions, editable settings, helper blocks
|
|
218
|
-
│ ├── common-patterns.md # Small reusable patterns (localStorage state, clipboard, nav blocker, drag-to-reorder, create → open, clickable row + inner link, keyboard picker)
|
|
223
|
+
│ ├── common-patterns.md # Small reusable patterns (localStorage state, clipboard, nav blocker, drag-to-reorder, create → open, clickable row + inner link, keyboard picker, measure the block not the window, clear Softr's sticky bars, a modal above Softr's bars)
|
|
219
224
|
│ │ # localStorage cross-page state, clipboard copy,
|
|
220
225
|
│ │ # navigation blocker, scroll-condensing header,
|
|
221
226
|
│ │ # auth-aware CTA, image masks, blobs, dot lists
|
|
@@ -240,7 +245,8 @@ softr-vibe-coding/
|
|
|
240
245
|
│ │ # (Sep 30 2026)
|
|
241
246
|
│ ├── quick-reference.md # Syntax cheat sheet
|
|
242
247
|
│ │ # Imports, hook signatures, mutation shapes,
|
|
243
|
-
│ │ # field mapping, component skeleton
|
|
248
|
+
│ │ # field mapping, component skeleton,
|
|
249
|
+
│ │ # Softr navigation variables, container queries
|
|
244
250
|
│ └── searchable-dropdown.md # THE dropdown pattern for blocks
|
|
245
251
|
│ # why native <select> and shadcn <Select> both
|
|
246
252
|
│ # break in the shadow DOM, composedPath()
|
|
@@ -268,11 +274,14 @@ softr-vibe-coding/
|
|
|
268
274
|
├── reading.md # useRecords, filtering, sorting, pagination,
|
|
269
275
|
│ # metrics, charts, current user; no detail-page
|
|
270
276
|
│ # auto-scoping, useRecords ignores enabled:false,
|
|
271
|
-
│ # server-side linked-record filters (Sep 18 2026)
|
|
277
|
+
│ # server-side linked-record filters (Sep 18 2026);
|
|
278
|
+
│ # where/orderBy aliases resolve per hook (Oct 6 2026)
|
|
272
279
|
├── writing.md # Mutations, sequential write queues, uploads,
|
|
273
|
-
│ # linked record format, cross-table writes
|
|
280
|
+
│ # linked record format, cross-table writes;
|
|
281
|
+
│ # Actions register per table (Sep 18 2026)
|
|
274
282
|
├── fields.md # getFieldValue(), field type shapes, record
|
|
275
|
-
│ # structure, debug utilities
|
|
283
|
+
│ # structure, debug utilities; date-only fields
|
|
284
|
+
│ # parsed as local dates (Oct 6 2026)
|
|
276
285
|
├── rest-api.md # useProxyFetch + useQuery (full docs)
|
|
277
286
|
├── softr-database.md # Native DB — field IDs, no rate limits
|
|
278
287
|
├── airtable.md # Column names, PAT vs OAuth, rate limits
|
|
@@ -331,7 +340,7 @@ The skill enforces these automatically, but good to know (verified live against
|
|
|
331
340
|
- No `import React from 'react'` — use named imports (`import { useState } from "react"`)
|
|
332
341
|
- Must use `export default function Block()`
|
|
333
342
|
- 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
|
|
334
|
-
-
|
|
343
|
+
- One `useRecords` per **connection** (house rule) — a block can connect to several sources, including the same table twice; declare them with `datasource.define()` and pass `from:` on every hook
|
|
335
344
|
- `fetchNextPage` never in the render body (infinite loop) — call it from an event handler (Load More `onClick`) or a guarded `useEffect`
|
|
336
345
|
- All hooks declared before any conditional `return` — React error #310
|
|
337
346
|
- Every field value rendered in JSX must pass through `getFieldValue()`
|
package/SKILL.md
CHANGED
|
@@ -22,7 +22,7 @@ allowed-tools: Read Write Glob Grep Bash
|
|
|
22
22
|
|
|
23
23
|
You generate complete, production-ready Softr Vibe Coding blocks as TypeScript React files. A Vibe Coding block is a single file with a default-exported React component, compiled by Softr's server and run in the browser inside a Softr app. The current platform compiles TypeScript with modern syntax — optional chaining (`?.`), nullish coalescing (`??`), arrow functions, `const`, generics — plus shadcn/ui from `@/components/ui/*`, lucide-react, sonner, and date-fns (verified live against the builder MCP's `vibe_coding_block_get_docs` and a 15-block production deployment, 2026-08-25).
|
|
24
24
|
|
|
25
|
-
> **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
|
+
> **Scope note — blocks vs. native chrome.** A block is page *content*, rendered inside a shadow DOM. Softr's global **header / top bar / sidebar / phone tab 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, or to paint them into one app frame around the content, add CSS to Settings → Custom Code → Code inside header. See [references/native-chrome-styling.md](references/native-chrome-styling.md) (app frame: [App frame (navigation layout)](references/native-chrome-styling.md#app-frame-navigation-layout); the blocks that sit inside it: [App pages beside Softr navigation](#app-pages-beside-softr-navigation)). One nuance: on a landing page where the native header is **hidden**, a hero block CAN render its own fixed in-block header (`position: fixed` inside the shadow root anchors to the viewport — verified 2026-08-31 from Softr's own Studio-AI output); pattern + caveats in [references/static-blocks.md](references/static-blocks.md#block-owned-landing-page-header).
|
|
26
26
|
|
|
27
27
|
## Your Workflow
|
|
28
28
|
|
|
@@ -72,11 +72,14 @@ You generate complete, production-ready Softr Vibe Coding blocks as TypeScript R
|
|
|
72
72
|
- Multi-datasource block: every `select:` / `fields:` value is a **plain module-scope identifier** — no ternary, no inline `q.select({...})` inside the hook options (the query returns `fields: {}`; Hard Constraint 24)
|
|
73
73
|
- Detail page: the record is fetched with `useRecord({ select, recordId, enabled: !!recordId })` using `useCurrentRecordId()`, and the code checks `data.id === recordId` before rendering — never `useRecords({ count: 1 })`, which returns the table's FIRST row (Hard Constraint 25)
|
|
74
74
|
- No list query relies on `enabled: false` — `useRecords` fetches anyway; conditional list queries live in a child component mounted only when needed, or carry a match-nothing `where` (Hard Constraint 26)
|
|
75
|
+
- Every alias a hook's `where` / `orderBy` names is in **that hook's own** `select` — anything else crashes the block at runtime (Hard Constraint 29)
|
|
76
|
+
- Date-only values are parsed with `toLocalDate()`, never `new Date()` — midnight UTC renders a day early west of Greenwich ([datasources/fields.md](datasources/fields.md#date-only-fields-arrive-as-midnight-utc))
|
|
75
77
|
- No field is "hidden" from some viewers by a conditional / second `select` on the same connection — the browser receives the union of every read select on that connection (Hard Constraint 23)
|
|
76
78
|
- All imports use named imports (no `import React from 'react'`)
|
|
77
79
|
- `export default function Block()` is present
|
|
78
80
|
- 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")
|
|
79
81
|
- `// BLOCK PLACEMENT:` comment present at top of file with wrapper classes matching the placement (see "Block Placement & Page Spacing")
|
|
82
|
+
- App page beside Softr's sidebar / top-bar navigation (the header code paints the frame): the block is **full-bleed** with **no background of its own** and no gradient/rounded page panel; every layout threshold is a container query on the block's width (`@container` on a wrapper + `@min-[NNrem]:`), with **no `sm:`/`md:`/`lg:` for layout**; sticky elements clear the top bar with `calc(var(--nav-height, 0px) + …)`. See [App pages beside Softr navigation](#app-pages-beside-softr-navigation)
|
|
80
83
|
- Loading, error, and empty states all handled
|
|
81
84
|
- Mutation calls gated behind `enabled` check (if using mutations)
|
|
82
85
|
- Field access uses `record.fields.alias` (not `record.alias`)
|
|
@@ -90,6 +93,7 @@ You generate complete, production-ready Softr Vibe Coding blocks as TypeScript R
|
|
|
90
93
|
- Sequential multi-row saves use `await hook.mutateAsync(...)` per row, in order, with stop-on-failure + retry state — `mutateAsync` is fully supported on the current platform (verified 2026-08-25; the old ".mutate() only" Action-parser rule is gone — see [datasources/writing.md](datasources/writing.md)). Independent writes to **different tables** may run in parallel via `Promise.all`; drag/reassign UIs should be optimistic with an Undo toast — see [writing.md → Parallel writes across tables](datasources/writing.md#parallel-writes-across-tables-the-one-sanctioned-parallelism)
|
|
91
94
|
- No hardcoded domains in links -- use relative paths (`/page?recordId=...`); same-page anchors written relative too (`/#section`)
|
|
92
95
|
- **No `<select>` and no shadcn `<Select>`** — both break inside a block's shadow DOM (native hands the list to the OS; shadcn portals outside the shadow root and arrives unstyled). Use the `Combo` pattern in [references/searchable-dropdown.md](references/searchable-dropdown.md) — **searchable by default** for every framed filter or form field whatever the option count; `bare` inline editors are click-only; `searchable={false}` only on a short fixed enum the user is setting (a status, a location, a group-by)
|
|
96
|
+
- App page with Softr navigation: **no shadcn `<Dialog>` / `<Sheet>`** — its overlay is z-50, under Softr's top bar (z-index 800), and it portals out of the shadow root. Use the in-block modal (`fixed inset-0 z-[1000]`, rendered as a sibling of the block's `@container` wrapper, with its own Escape, focus trap, scroll lock and focus return) in [references/common-patterns.md → A modal above Softr's bars](references/common-patterns.md#a-modal-above-softrs-bars) (measured live 2026-10-06)
|
|
93
97
|
- No clipping class (`overflow-hidden`, `overflow-*-auto`, `truncate`, `line-clamp-*`) on any element that contains a `Combo` — its panel is absolutely positioned in local DOM, so a clipping `<td>` cuts the menu to the row's height; bound an over-wide chip at the chip (`min-w-0 truncate`), and never `scrollIntoView` inside the panel. See [references/searchable-dropdown.md](references/searchable-dropdown.md#the-four-things-that-will-bite-you), item 4
|
|
94
98
|
- Any **Print** control opens a **new window with its own document** — `window.open` straight from the click, an escaped standalone HTML printout written into it, `print()` once its stylesheets, fonts and images are in, the button disabled until the data has fully loaded. No `window.print()` on the Softr page, no in-page print view (Hard Constraint 28). See [references/printing.md](references/printing.md)
|
|
95
99
|
- 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))
|
|
@@ -124,6 +128,7 @@ When the user describes their block, figure out which of these areas apply and a
|
|
|
124
128
|
- Icon + wordmark (SVG): `https://cdn.brandfetch.io/idytCFzVcY/theme/dark/logo.svg`
|
|
125
129
|
- Icon only (PNG): `https://cdn.brandfetch.io/idytCFzVcY/w/1024/h/1024/theme/dark/icon.png`
|
|
126
130
|
- **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.
|
|
131
|
+
- **Navigation layout**: Does the app use Softr's **sidebar / top-bar navigation**, and where does the menu live — in the sidebar or in the top bar? Does the app's header code paint an app frame ([native-chrome-styling.md → App frame (navigation layout)](references/native-chrome-styling.md#app-frame-navigation-layout)), or should it? Beside a sidebar, an app page is full-bleed and lays out by its own width — see [App pages beside Softr navigation](#app-pages-beside-softr-navigation). With the menu in the top bar, the sidebar renders as an empty column in the theme colour (seen 2026-10-05); moving the menu into the sidebar is a Studio change (untested).
|
|
127
132
|
- **Interactivity**: Create/edit/delete? Filtering? Sorting? Pagination?
|
|
128
133
|
- **User context**: Does it need to know who's logged in?
|
|
129
134
|
- **Settings**: Should anything be editable by the Softr builder (titles, images, toggle sections)?
|
|
@@ -175,14 +180,14 @@ For advanced patterns beyond data fetching, load the relevant reference when the
|
|
|
175
180
|
| 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) |
|
|
176
181
|
| Embedding third-party libraries with their own CSS (Leaflet, Mapbox, TinyMCE, Quill, FullCalendar) | [references/advanced-integrations.md](references/advanced-integrations.md) |
|
|
177
182
|
| Debugging a broken block, checking patterns before delivery, full violation catalog | [references/anti-patterns.md](references/anti-patterns.md) |
|
|
178
|
-
| Quick syntax check — import paths, hook signatures, mutation call shapes, field mapping | [references/quick-reference.md](references/quick-reference.md) |
|
|
183
|
+
| Quick syntax check — import paths, hook signatures, mutation call shapes, field mapping, Softr navigation variables (`--nav-height` etc.), container-query syntax | [references/quick-reference.md](references/quick-reference.md) |
|
|
179
184
|
| Any **dropdown / picker / combobox** in a block — why shadcn `<Select>` and native `<select>` both fail inside the shadow DOM, the `composedPath()` click-outside, sorting A→Z inside the component, multi-token filtering, **searchable by default** regardless of option count (`bare` inline editors click-only; `searchable={false}` only for a short fixed enum being set), the `bare` inline-editor variant | [references/searchable-dropdown.md](references/searchable-dropdown.md) |
|
|
180
185
|
| **Printing** anything from a block — always a new window/tab holding its own document, never `window.print()` on the page or an in-page print view: the escaped HTML builder, pop-up-safe opening from the click, print-when-ready (stylesheets, fonts and images, capped), Print disabled until the data has loaded, the `?print=1` deep link from another page, paper layout (shared `<colgroup>`, `vertical-align: middle`, tick boxes) | [references/printing.md](references/printing.md) |
|
|
181
|
-
| Small reusable patterns — `localStorage` cross-page state, clipboard copy button | [references/common-patterns.md](references/common-patterns.md) |
|
|
186
|
+
| Small reusable patterns — `localStorage` cross-page state, clipboard copy button, measuring the block's own width (not the window's), clearing Softr's sticky top bar and phone tab bar, an in-block modal above Softr's bars (instead of shadcn `Dialog`) | [references/common-patterns.md](references/common-patterns.md) |
|
|
182
187
|
| 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) |
|
|
183
188
|
| The official **Softr MCP server** — Softr DB schema + full record/table/field/database CRUD (deletes included), field-level browsing of connected integrations (Airtable / Google Sheets / Notion / Supabase and more), **creating, editing, versioning, and deploying Vibe Coding blocks directly** (`vibe_coding_block_get_docs`, `vibe_coding_block_create`, ...), push verification by `sourceSha256`, app management/scaffolding, the **Softr Workflows** suite (28 tools, 418-node catalog), and **per-application MCP servers**. Tool names changed on 2026-10-01; the file carries the old → new map | [references/softr-mcp.md](references/softr-mcp.md) |
|
|
184
|
-
| **Checking a pushed block in a browser** — rendering and behaviour in a Softr preview with the agent-browser CLI (ask before installing it): the preview cookie, reaching into the block's shadow DOM through accessibility refs, measuring with `eval`, blocking and proving the save endpoint before any click, reading what a click sent, screenshots to disk | [references/browser-checks.md](references/browser-checks.md) |
|
|
185
|
-
| 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) |
|
|
189
|
+
| **Checking a pushed block in a browser** — rendering and behaviour in a Softr preview with the agent-browser CLI (ask before installing it): the preview cookie, reaching into the block's shadow DOM through accessibility refs, measuring with `eval`, blocking and proving the save endpoint before any click, reading what a click sent, screenshots to disk, testing Custom Code header CSS | [references/browser-checks.md](references/browser-checks.md) |
|
|
190
|
+
| 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, and the **app frame** for sidebar apps (top bar + sidebar as one frame in the theme colour, the content as one paper sheet with a pinned rounded corner, blocks transparent, scoped with `:has()` so Log in / 404 keep Softr's colours) | [references/native-chrome-styling.md](references/native-chrome-styling.md) |
|
|
186
191
|
| 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) |
|
|
187
192
|
| **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) |
|
|
188
193
|
| **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) |
|
|
@@ -213,9 +218,12 @@ export default function Block() {
|
|
|
213
218
|
|
|
214
219
|
Wrap the outermost layout in `container` and `content` divs by default — these constrain width to match the Softr app's max width settings so the block aligns with neighboring native blocks. Note this is a **house convention, not platform-enforced**: per the official developer guide the platform default is full width, and the classes are merely "available" to constrain it (verified 2026-08-31 against `vibe_coding_block_get_docs` and a rendering wrapper-free Studio-AI hero).
|
|
215
220
|
|
|
221
|
+
The wrappers also bring **window-based gutters**. Inside a Vibe host, `.container`'s side padding is `--container-x`, which `:host([data-container-padding-x=…])` sets on WINDOW media queries: `regular` is 16px, then 24px from a 576px window (the 768px step keeps 24px), more from 992px (`0 32px` measured on a 744px block at a 1024px window); `tab` 0px, `container` 8px, `none` 0px (read from the block's compiled CSS and measured, 2026-10-05). On a full-width page that is harmless; beside Softr's sidebar the gutter steps on a width the block doesn't have — one reason app pages there drop the wrappers.
|
|
222
|
+
|
|
216
223
|
**Exceptions (omit the wrappers deliberately):**
|
|
217
224
|
- Blocks inside Softr column containers — Softr controls layout.
|
|
218
|
-
- 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
|
|
225
|
+
- 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`, window breakpoints: right on a full-width page, wrong beside a sidebar) 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).
|
|
226
|
+
- App pages beside Softr's sidebar / top-bar navigation, inside a frame the header code paints — the block is full-bleed AND transparent (no background, no page panel), owns container-query gutters (`px-4` → `@min-[40rem]:px-6` → `@min-[64rem]:px-10`) and lays out by its own width. See [App pages beside Softr navigation](#app-pages-beside-softr-navigation).
|
|
219
227
|
|
|
220
228
|
## Block Placement & Page Spacing
|
|
221
229
|
|
|
@@ -228,6 +236,7 @@ Blocks rarely live alone — most Softr pages stack 2–4 blocks vertically, oft
|
|
|
228
236
|
- Is there a Softr header immediately above this block?
|
|
229
237
|
- Is there a Softr footer immediately below this block?
|
|
230
238
|
- Is there a Back button at the top of this block?
|
|
239
|
+
- Does the page show Softr's sidebar / top-bar navigation, with header code painting an app frame? If so, the block follows [App pages beside Softr navigation](#app-pages-beside-softr-navigation) instead of the wrapper table below.
|
|
231
240
|
|
|
232
241
|
**Detail pages — always ask about the back button AND its fallback URL.** A "detail page" is any block that reads a single record by URL recordId (i.e. it calls `useCurrentRecordId()` / `useRecord()`, or the user describes it as the target of a `/page?recordId=...` link). Users almost always want a back button there but rarely think to mention it, and shipping the page without one is the most common UX gap on these screens. So even if every other placement detail is clear, ask both:
|
|
233
242
|
|
|
@@ -271,7 +280,7 @@ The `// BLOCK PLACEMENT:` marker is intentionally stable so it can be grepped an
|
|
|
271
280
|
|
|
272
281
|
### Spacing values (defaults)
|
|
273
282
|
|
|
274
|
-
**Container** (default — omitted by full-bleed blocks and blocks inside column containers; see table below): `<div className="container py-0">`
|
|
283
|
+
**Container** (default — omitted by full-bleed blocks, app pages beside Softr navigation and blocks inside column containers; see table below): `<div className="container py-0">`
|
|
275
284
|
|
|
276
285
|
**Inner wrapper** classes by block position:
|
|
277
286
|
|
|
@@ -281,7 +290,8 @@ The `// BLOCK PLACEMENT:` marker is intentionally stable so it can be grepped an
|
|
|
281
290
|
| Middle block | `py-3 px-8` | 12px + Softr separator + 12px ≈ 24px between blocks |
|
|
282
291
|
| Last block (footer-adjacent) | `pt-3 pb-12 px-8` | 12px top + 48px bottom for footer breathing room |
|
|
283
292
|
| Standalone (only block on page) | `pt-3 pb-12 px-8` | Treat like a last block |
|
|
284
|
-
| 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) |
|
|
293
|
+
| Full-bleed (hero / banner / footer) | none — no container/content; block owns gutters `px-6 md:px-12 lg:px-16` | Edge-to-edge backgrounds on a full-width page; see [references/static-blocks.md](references/static-blocks.md#full-bleed-layout-license) |
|
|
294
|
+
| App page beside Softr navigation (header code paints the frame) | none — no container/content, no background; `@container` root, then `px-4 pt-5 pb-12 @min-[40rem]:px-6 @min-[40rem]:pt-6 @min-[64rem]:px-10 @min-[64rem]:pt-8` | The only block on the page, on the header's paper sheet; gutters follow the block's width, not the window's; see [App pages beside Softr navigation](#app-pages-beside-softr-navigation) |
|
|
285
295
|
|
|
286
296
|
**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.
|
|
287
297
|
|
|
@@ -289,13 +299,70 @@ The `// BLOCK PLACEMENT:` marker is intentionally stable so it can be grepped an
|
|
|
289
299
|
|
|
290
300
|
**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.
|
|
291
301
|
|
|
302
|
+
### App pages beside Softr navigation
|
|
303
|
+
|
|
304
|
+
When the app uses Softr's **sidebar / top-bar navigation** and the app's header code paints the frame (the top bar and sidebar colour around the page, the content as one paper sheet with a rounded corner tucked under them; recipe in [native-chrome-styling.md → App frame (navigation layout)](references/native-chrome-styling.md#app-frame-navigation-layout)), the blocks on its app pages take a different shape. Verified live 2026-10-05 on a three-page HubSpot-backed app (a dashboard, an accounts list + detail, a request form): the header code on the published app and in the preview, the blocks measured in the preview.
|
|
305
|
+
|
|
306
|
+
**Full-bleed, and the block paints nothing behind itself [house].**
|
|
307
|
+
- No `container` / `content` wrappers, no background colour on the block's root, no gradient or rounded panel around the page (Premium Visual Baseline §1 does not apply). A panel on top of the sheet brings back the "card floating on a white page" look the frame exists to remove; Leo rejected exactly that. The block's own cards, borders and dividers stay the block's job, because header CSS cannot reach inside the shadow root.
|
|
308
|
+
- The transparency comes from the header code, not the block. A Vibe host paints the Studio theme background (white by default) through its compiled `:host` rule even when the block sets none; the frame recipe clears it with a main-document rule on the host, `[data-role="vibe-block-root"]` (the `:host` rule isn't `!important`, so it loses). Without that header code, a block that paints nothing still sits on white (verified 2026-10-05).
|
|
309
|
+
- **One block per page, and it owns the page's `h1`**; its panel titles are `h2`. No separate welcome or title block above it. The pattern was built and verified only with one block per page. If a page must hold more, only the first block takes the top padding and only the last takes `pb-12`; the rest use `pt-0` / `pb-0` with the same side gutters (untested).
|
|
310
|
+
- **The same shell on every app page**, so the header lands in the same place (the cross-page chrome rule above):
|
|
311
|
+
|
|
312
|
+
```tsx
|
|
313
|
+
// FONT and C.ink: the brand's UI font and ink colour (DESIGN.md tokens)
|
|
314
|
+
<div ref={rootRef} className="@container" style={{ color: C.ink, fontFamily: FONT }}>
|
|
315
|
+
<div className="px-4 pt-5 pb-12 @min-[40rem]:px-6 @min-[40rem]:pt-6 @min-[64rem]:px-10 @min-[64rem]:pt-8">
|
|
316
|
+
{/* the h1 header row, then the page */}
|
|
317
|
+
</div>
|
|
318
|
+
</div>
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Measured padding (top, sides, bottom): `32px 40px 48px` on a 1160px block (a 1440px window minus a 280px sidebar), `20px 16px 48px` on a 390px block.
|
|
322
|
+
- Why the wrappers go: `.container`'s gutters step on the window (see the note under [Code Structure](#code-structure)).
|
|
323
|
+
|
|
324
|
+
**Lay out by the block's width, never the window's.** Softr's sidebar takes 57–360px of the window (280px by default, 57px collapsed, 200–360px when dragged), so `sm:` / `md:` / `lg:` fire on a width the block doesn't have: at a 1024px window the block is 744px wide, and at 768px with the sidebar open it is 488px. Measured 2026-10-05: an `lg:` five-column chart row fired on a 744px block and cut off its bar labels. Below a 768px window Softr swaps the top bar and sidebar for a bottom tab bar (767 = tab bar, 768 = top bar + sidebar).
|
|
325
|
+
- Use Tailwind container queries: `@container` on a wrapper, `@min-[NNrem]:` on what's inside it. Softr's Tailwind compiles them (verified live 2026-10-05). Syntax in [quick-reference.md → Container queries](references/quick-reference.md#container-queries).
|
|
326
|
+
- A container query never resolves against the element that carries `@container`, only against the nearest ancestor container. So `@container` goes on a wrapper, and the `@min-…` classes go on its descendants.
|
|
327
|
+
- Nest another `@container` on a pane (a detail panel, a side column) so its inner rows and strips follow the pane, not the page.
|
|
328
|
+
- Thresholds are BLOCK widths. From that app (the numbers are the project's; the method is the reusable part):
|
|
329
|
+
|
|
330
|
+
| Part | Narrow | Wide | From (block width) |
|
|
331
|
+
|---|---|---|---|
|
|
332
|
+
| Figure strip | 2 columns | 4 columns | `@min-[52rem]:` |
|
|
333
|
+
| Chart row | stacked | 5-column grid, spans 2 + 3 | `@min-[64rem]:` |
|
|
334
|
+
| Form + side list | stacked | `grid-cols-[minmax(0,44rem)_minmax(18rem,22rem)]` | `@min-[56rem]:` |
|
|
335
|
+
| List + detail | list, then detail | side by side | 860px, decided in JS: a 340px list + 20px gap + at least 440px of detail + the shell's padding |
|
|
336
|
+
|
|
337
|
+
Work each threshold out from the parts it has to fit (the list + detail row shows how), then check it at real block widths with the real fonts. A padding change moves where text wraps: after the switch to this shell, a figure-strip skeleton keyed to the old wrap point no longer matched the loaded cells (measured 2026-10-05 across block widths with the real fonts).
|
|
338
|
+
- When CSS can't decide (which component tree to render, how many chart ticks fit), measure the block: [common-patterns.md → Measure the block, not the window](references/common-patterns.md#measure-the-block-not-the-window). Measure in `useLayoutEffect`; a passive `useEffect` paints the first frame at the window's width, and beside a sidebar the layout visibly flips.
|
|
339
|
+
|
|
340
|
+
**Clear Softr's sticky bars with the host's variables.** Softr's top bar is sticky (56px, z-index 800), and on phones so is the tab bar (about 57px as rendered, though Softr's variable says 55px; z-index 800). The Vibe host maps the bar and sidebar sizes onto `--nav-height`, `--sidebar-width` and `--bottombar-height`, each falling back to `0px` (values in [quick-reference.md → Softr navigation variables](references/quick-reference.md#softr-navigation-variables)). Use them only inside CSS `calc()`, e.g. a sticky list pane:
|
|
341
|
+
|
|
342
|
+
```tsx
|
|
343
|
+
<section className="sticky …" style={{ top: "calc(var(--nav-height, 0px) + 16px)", maxHeight: "calc(100dvh - var(--nav-height, 0px) - 32px)" }}>
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
A plain `sticky top-4` slid under the top bar; with the variable the pane sits at 72px (measured 2026-10-05). JS that scrolls the window or sizes a popover has to clear the bars too: [common-patterns.md → Clear Softr's sticky bars](references/common-patterns.md#clear-softrs-sticky-bars).
|
|
347
|
+
|
|
348
|
+
**Check it where the header code runs.** Header code applies on the published app and in Softr's preview (verified 2026-10-05, seen after a publish; whether the preview shows a pasted but unpublished change is untested, so inject it instead), not in the Studio editor canvas, so judge the page in the preview: [browser-checks.md → Testing Custom Code header CSS](references/browser-checks.md#testing-custom-code-header-css).
|
|
349
|
+
|
|
350
|
+
**Record it in the placement comment:**
|
|
351
|
+
|
|
352
|
+
```tsx
|
|
353
|
+
// BLOCK PLACEMENT: the only block on <page>; it owns the page's h1. FULL-BLEED by design: no container/content
|
|
354
|
+
// wrappers and no background of its own; the app's header code paints the frame and the paper sheet.
|
|
355
|
+
// Spacing: px-4 pt-5 pb-12, then px-6 pt-6 from a 40rem block and px-10 pt-8 from 64rem (same shell on every page).
|
|
356
|
+
// LAYOUT follows the block's own width (@container queries), never the window.
|
|
357
|
+
```
|
|
358
|
+
|
|
292
359
|
### Full-viewport hero blocks
|
|
293
360
|
|
|
294
361
|
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).
|
|
295
362
|
|
|
296
363
|
## Premium Visual Baseline
|
|
297
364
|
|
|
298
|
-
**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.
|
|
365
|
+
**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 (one carve-out: §1's gradient wrapper is dropped on app pages inside a header-painted frame).
|
|
299
366
|
|
|
300
367
|
**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).
|
|
301
368
|
|
|
@@ -309,6 +376,8 @@ Refer to [ui-ux-guidelines.md](ui-ux-guidelines.md) for full design principles.
|
|
|
309
376
|
```
|
|
310
377
|
Adjust the top gradient color to complement the user's brand.
|
|
311
378
|
|
|
379
|
+
**Exception — app pages inside a header-painted frame.** When the app's header code paints the frame and the paper sheet beside Softr's sidebar navigation, drop this wrapper and any rounded page panel: the block paints no background at all. A panel on the sheet recreates the "card floating on a white page" look the frame exists to remove (rejected by Leo, 2026-10-05). The cards inside the block (§3) stay. See [App pages beside Softr navigation](#app-pages-beside-softr-navigation).
|
|
380
|
+
|
|
312
381
|
### 2. Header section
|
|
313
382
|
- Icon in a colored rounded square (`h-10 w-10 rounded-xl` with brand primary, white icon)
|
|
314
383
|
- Title at `text-2xl font-bold`
|
|
@@ -538,7 +607,7 @@ Non-negotiable rules. Most are enforced by the Softr platform (compiler, validat
|
|
|
538
607
|
6. **Array setting icon placement** — Never put `vibeCodingBlockIcon` as first field.
|
|
539
608
|
7. **No nested arrays in settings** — Use text with separator, split in code.
|
|
540
609
|
8. **Default export required** — `export default function Block()`.
|
|
541
|
-
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.
|
|
610
|
+
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), app pages beside Softr's sidebar navigation inside a frame the header code paints (full-bleed, no background, laid out by container queries — see [App pages beside Softr navigation](#app-pages-beside-softr-navigation)) 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.
|
|
542
611
|
10. **Inline options literals for data hooks** — `useRecords` fails to compile when its options
|
|
543
612
|
object is passed through a variable or wrapper function. Build the options object inline at the
|
|
544
613
|
call site; share `q.select` mappings between hooks, not whole options objects. (Same
|
|
@@ -547,9 +616,15 @@ Non-negotiable rules. Most are enforced by the Softr platform (compiler, validat
|
|
|
547
616
|
changing the wrapper to take the hook's *result* instead.)
|
|
548
617
|
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).
|
|
549
618
|
12. **Record fields nested under `fields`** — Access via `record.fields.alias`, not `record.alias`.
|
|
550
|
-
13. **
|
|
551
|
-
|
|
552
|
-
|
|
619
|
+
13. **One `useRecords` per connection [house]** — not a documented platform limit (Softr's developer
|
|
620
|
+
guide shows one `useRecords` per datasource but states no rule; checked 2026-10-06). Read each
|
|
621
|
+
connection once and filter client-side when the table is small and every viewer may see all of
|
|
622
|
+
it anyway. When the data must differ, add connections rather than queries: a second table gets
|
|
623
|
+
its own connection, and so does a private read of the same table (Hard Constraint 23). A
|
|
624
|
+
server-side `where` is better for large tables and linked children, but it is a request
|
|
625
|
+
parameter, never access control. A query mounted in a child component (Hard Constraint 26) is
|
|
626
|
+
that connection's one read; don't also read the connection in the parent. Declare connections
|
|
627
|
+
with `datasource.define()` and pass `from:` on every hook. See
|
|
553
628
|
[datasources/multi-datasource.md](datasources/multi-datasource.md). Multiple `useMetric` calls OK.
|
|
554
629
|
14. **React functional components only** — No class components.
|
|
555
630
|
15. **Do NOT `import React from 'react'`** — Use named imports for hooks.
|
|
@@ -583,7 +658,9 @@ Non-negotiable rules. Most are enforced by the Softr platform (compiler, validat
|
|
|
583
658
|
A push that returns `errors: null` can still have left public write access on the block.
|
|
584
659
|
**If any action is still broader than intended, report it WITH its severity and let the builder decide.**
|
|
585
660
|
Check the page's own VIEW permission first (`application_page_get_permissions`): a page gated to logged-in
|
|
586
|
-
users makes an open action housekeeping, a public page makes it a real hole.
|
|
661
|
+
users makes an open action housekeeping, a public page makes it a real hole. Report the block's own
|
|
662
|
+
Visibility with it -- it gates the block's reads and sets ADD_RECORD's default, but whether it
|
|
663
|
+
refuses writes on its own is untested. Surface the list
|
|
587
664
|
either way -- page, block, action type, data source -- and note that a human sets them on the
|
|
588
665
|
block's Actions tab. Do not unilaterally block a publish; it is not your app.
|
|
589
666
|
22. **Blocks cannot import each other -- cross-block consistency is discipline, not architecture [house]**
|
|
@@ -633,6 +710,11 @@ Non-negotiable rules. Most are enforced by the Softr platform (compiler, validat
|
|
|
633
710
|
them takes global CSS across Softr's page structure as well as print CSS in the block. Never
|
|
634
711
|
an in-page "print view" either (Leo rejected it by name). Verified live 2026-09-30. See
|
|
635
712
|
[references/printing.md](references/printing.md).
|
|
713
|
+
29. **`where` / `orderBy` may only name aliases from the same hook's `select`** -- aliases resolve per
|
|
714
|
+
hook, not per connection. Naming any other alias crashes the whole block at runtime ("Could not
|
|
715
|
+
find an alias for subject \"undefined\"") although the push compiles clean. Seen live
|
|
716
|
+
2026-09-18 on a `useMetric`. See
|
|
717
|
+
[datasources/reading.md](datasources/reading.md#filter-and-sort-aliases-must-be-in-the-same-hooks-select).
|
|
636
718
|
|
|
637
719
|
## Style Conventions
|
|
638
720
|
|
package/datasources/fields.md
CHANGED
|
@@ -32,13 +32,35 @@ Property priority: `label` first (most common in Softr formatted fields), then `
|
|
|
32
32
|
|
|
33
33
|
Apply `getFieldValue()` everywhere you read fields:
|
|
34
34
|
- Table cells, badges, tooltips: `<td>{getFieldValue(f.subject)}</td>`
|
|
35
|
-
- Date parsing: `
|
|
35
|
+
- Date parsing: `toLocalDate(getFieldValue(f.dueDate))` for date-only fields ([below](#date-only-fields-arrive-as-midnight-utc)), `new Date(...)` for real timestamps -- raw value might be `{label: "2025-12-01"}`
|
|
36
36
|
- Number parsing: `parseInt(getFieldValue(f.count), 10)` -- formula numbers come back as objects
|
|
37
37
|
- String methods: `getFieldValue(f.status).toLowerCase()`
|
|
38
38
|
- Inside `useMemo` normalizers, BEFORE storing into state -- prevents the object from propagating
|
|
39
39
|
|
|
40
40
|
For companion helpers (`getLinkedNames`, `getLinkedItems`) used in helper block consumers, see [../references/helper-blocks.md](../references/helper-blocks.md).
|
|
41
41
|
|
|
42
|
+
### Date-only fields arrive as midnight UTC
|
|
43
|
+
|
|
44
|
+
*Verified live 2026-09-18 (Softr Database).* A date field without a time arrives as midnight UTC.
|
|
45
|
+
`new Date()` parses it as UTC, so anywhere west of Greenwich (New York, for one) date-fns renders
|
|
46
|
+
the **previous day**. Parse date-only values as local dates, and keep the default parse for real
|
|
47
|
+
timestamps:
|
|
48
|
+
|
|
49
|
+
```jsx
|
|
50
|
+
var DATE_ONLY_RE = /^(\d{4})-(\d{2})-(\d{2})(?:[T ]00:00(?::00(?:\.0+)?)?(?:Z|\+00:00)?)?$/;
|
|
51
|
+
|
|
52
|
+
function toLocalDate(raw) {
|
|
53
|
+
if (typeof raw === "string") {
|
|
54
|
+
var m = raw.match(DATE_ONLY_RE);
|
|
55
|
+
if (m) return new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
|
|
56
|
+
}
|
|
57
|
+
return new Date(raw);
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
A real timestamp at exactly midnight UTC matches the pattern too, so call `toLocalDate` only on
|
|
62
|
+
fields you know are date-only.
|
|
63
|
+
|
|
42
64
|
## Debugging Error #31
|
|
43
65
|
|
|
44
66
|
If React crashes with error #31 ("Objects are not valid as a React child"), open console on the first record that crashes and run:
|
|
@@ -78,7 +100,7 @@ var name = f.firstName || "";
|
|
|
78
100
|
|
|
79
101
|
## Debug Utilities
|
|
80
102
|
|
|
81
|
-
Two throwaway diagnostic blocks you can drop into a page to diagnose data problems. Neither is meant for production -- delete or hide them once the issue is resolved. During development, drop them on a `/debug` page
|
|
103
|
+
Two throwaway diagnostic blocks you can drop into a page to diagnose data problems. Neither is meant for production -- delete or hide them once the issue is resolved. During development, drop them on a `/debug` page whose VIEW permission is limited to admins. A page that is merely left out of the navigation is still reachable by URL, and its blocks' endpoints answer anyone the page lets in (see [softr-mcp.md](../references/softr-mcp.md#what-the-server-enforces-on-a-blocks-data-endpoints)).
|
|
82
104
|
|
|
83
105
|
### Field Inspector Block
|
|
84
106
|
|
|
@@ -123,7 +145,7 @@ export default function Block() {
|
|
|
123
145
|
|
|
124
146
|
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
147
|
|
|
126
|
-
5. **Softr Database REST API with `fieldNames=true`** -- runtime inspection from inside a Vibe Coding block (
|
|
148
|
+
5. **Softr Database REST API with `fieldNames=true`** -- runtime inspection from inside a Vibe Coding block (only on a page limited to admins, and removed afterwards: the PAT sits in the block's source, and anyone who can load the page can read it and use it against the whole database, past every page, block and Source-condition gate):
|
|
127
149
|
|
|
128
150
|
```jsx
|
|
129
151
|
import { useEffect, useState } from "react";
|
|
@@ -94,7 +94,7 @@ it behaves as a **union** of both branches, not a choice between them. Which is
|
|
|
94
94
|
*Verified live 2026-09-18; the block-visibility gate 2026-10-05.*
|
|
95
95
|
|
|
96
96
|
The records endpoint is per block + connection —
|
|
97
|
-
`/blocks/<blockId>/datasources/<
|
|
97
|
+
`/blocks/<blockId>/datasources/<connection>/records` (`<connection>` was recorded as the connection's id in the 2026-09-18 Softr Database capture and seen as its alias in a 2026-10-05 HubSpot capture; unresolved, and it only matters when reading a network log) — and it returns the **UNION of every field
|
|
98
98
|
named by any READ `q.select` attributed to that connection**. Two selects on one connection do
|
|
99
99
|
NOT produce two payloads: every read hook on that connection gets all the fields, for every
|
|
100
100
|
viewer.
|
|
@@ -146,8 +146,10 @@ user and logged-out visitors 403 on list and by-id. The gate belongs to the bloc
|
|
|
146
146
|
connect the same table to an ungated block and it is open again. See
|
|
147
147
|
[../references/softr-mcp.md](../references/softr-mcp.md#what-the-server-enforces-on-a-blocks-data-endpoints).
|
|
148
148
|
|
|
149
|
-
|
|
150
|
-
|
|
149
|
+
Two selects on different connections may reuse an alias name (`customer` on both) without
|
|
150
|
+
colliding. Aliases resolve **per hook**, though: a hook's `where` / `orderBy` may name only aliases
|
|
151
|
+
from that hook's own `select`, or the block crashes at runtime — see
|
|
152
|
+
[reading.md](reading.md#filter-and-sort-aliases-must-be-in-the-same-hooks-select).
|
|
151
153
|
|
|
152
154
|
## Mutation Actions register per TABLE, not per connection
|
|
153
155
|
|
|
@@ -206,3 +208,11 @@ right tool for:
|
|
|
206
208
|
`crew-feedback-form.jsx` — a public feedback form that reads a person from **People** by an
|
|
207
209
|
`email` URL param, resolves a **Shifts** record from a job-code param, and writes a row to
|
|
208
210
|
**Feedback** linking both. One block, three sources, no helpers, no `window` globals.
|
|
211
|
+
|
|
212
|
+
**Read it against the rules above before copying it.** On a public page every logged-out visitor
|
|
213
|
+
may call the People connection, and the endpoint returns every row the Source conditions allow,
|
|
214
|
+
with every field the block's read selects name. The `email` URL param narrows nothing on the
|
|
215
|
+
server: it ends up in a `where`, which is a request parameter the caller controls, and there is no
|
|
216
|
+
logged-in user for a Source condition to match. Select only what the form must show, assume every
|
|
217
|
+
row of it is public, and if that is not acceptable resolve the person server-side (a Softr
|
|
218
|
+
Workflow) instead of reading People from the block.
|
package/datasources/reading.md
CHANGED
|
@@ -65,7 +65,7 @@ var isRefetching = result.isRefetching;
|
|
|
65
65
|
var items = (data && data.pages) ? data.pages.flatMap(function(p) { return p.items; }) : [];
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
**
|
|
68
|
+
**House rule: one `useRecords` per connection.** It is not a documented platform limit — Hard Constraint 13 in SKILL.md says when to filter client-side, when to add a connection, and when a server-side `where` is the better choice. Multiple `useMetric` calls ARE allowed.
|
|
69
69
|
|
|
70
70
|
**CRITICAL:** The options object must be an **inline literal** at the call site. Passing it
|
|
71
71
|
through a variable or a wrapper function (`useRecords(buildOpts())`) **fails to compile** —
|
|
@@ -80,7 +80,9 @@ A block can connect to **several data sources** and call `useRecords` once per s
|
|
|
80
80
|
*Verified live 2026-09-18 (network capture).* `useRecords({ ..., enabled: false })` **fetches
|
|
81
81
|
anyway** — with a literal `false` and with a variable alike. `useRecord` is different: it honours
|
|
82
82
|
`enabled: false` and issues no request. (`useLinkedRecords`, `useMetric` and `useChartData` were
|
|
83
|
-
not probed — don't assume either behaviour for them.)
|
|
83
|
+
not probed — don't assume either behaviour for them.) The official developer guide still
|
|
84
|
+
describes `enabled` on `useRecords` as an "optional boolean to defer loading" (checked
|
|
85
|
+
2026-10-06); the capture says otherwise, so trust it until a newer one does not.
|
|
84
86
|
|
|
85
87
|
So `enabled` cannot make a list query conditional, and it cannot keep a query away from viewers
|
|
86
88
|
who should not run it. Two things that do work:
|
|
@@ -171,7 +173,7 @@ var result = useLinkedRecords({
|
|
|
171
173
|
field: "category", // the ALIAS from q.select(), NOT the raw field ID
|
|
172
174
|
sortOrder: "ASC", // "ASC" | "DESC"
|
|
173
175
|
search: "", // optional search string
|
|
174
|
-
enabled: true, //
|
|
176
|
+
enabled: true, // documented as deferral; not verified live (see the useRecords note)
|
|
175
177
|
count: 50, // optional page size — default 100, max 1000
|
|
176
178
|
});
|
|
177
179
|
|
|
@@ -257,6 +259,26 @@ where: q.and(
|
|
|
257
259
|
)
|
|
258
260
|
```
|
|
259
261
|
|
|
262
|
+
### Filter and sort aliases must be in the same hook's select
|
|
263
|
+
|
|
264
|
+
*Seen live 2026-09-18 (Softr Database, on a `useMetric`).* Aliases are resolved **per hook**, not
|
|
265
|
+
per connection. A `where` or `orderBy` that names an alias missing from that hook's own `select`
|
|
266
|
+
crashes the whole block at runtime ("Could not find an alias for subject \"undefined\"" and
|
|
267
|
+
Softr's "Oh snap" panel), although the push compiled clean:
|
|
268
|
+
|
|
269
|
+
```jsx
|
|
270
|
+
// WRONG — "status" is not in this hook's select: compiles, then crashes the block
|
|
271
|
+
var countSelect = q.select({ orderNo: "FIELD_ID1" });
|
|
272
|
+
var open = useMetric({ select: countSelect, metric: metric.count(), where: q.text("status").is("Open") });
|
|
273
|
+
|
|
274
|
+
// CORRECT — every alias the where / orderBy names is in the hook's own select
|
|
275
|
+
var countSelect = q.select({ orderNo: "FIELD_ID1", status: "FIELD_ID2" });
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Adding the field to the select also adds it to the connection's read payload
|
|
279
|
+
([multi-datasource.md](multi-datasource.md#one-connection--one-read-payload-the-union-of-its-selects)),
|
|
280
|
+
so put a filter on a private field on the connection that is allowed to carry it.
|
|
281
|
+
|
|
260
282
|
### Filtering by a linked record (server-side)
|
|
261
283
|
|
|
262
284
|
*Verified live 2026-09-18 (Softr Database, network capture).* A linked-record field filters on
|
|
@@ -275,9 +297,10 @@ var comments = useRecords({
|
|
|
275
297
|
```
|
|
276
298
|
|
|
277
299
|
On the wire the alias is resolved to the field id:
|
|
278
|
-
`{ subject: <fieldId>, type: "ARRAY", operator: "HAS_ALL_OF", value: [orderId] }`.
|
|
279
|
-
|
|
280
|
-
name for different fields and each filter
|
|
300
|
+
`{ subject: <fieldId>, type: "ARRAY", operator: "HAS_ALL_OF", value: [orderId] }`. Aliases resolve
|
|
301
|
+
**per hook** ([above](#filter-and-sort-aliases-must-be-in-the-same-hooks-select)), so two selects on
|
|
302
|
+
different connections may use the same alias name for different fields, and each filter resolves
|
|
303
|
+
against its own hook's `select`.
|
|
281
304
|
|
|
282
305
|
`orderId` must be a real id when the hook runs — `useRecords` cannot be switched off with
|
|
283
306
|
`enabled: false` ([above](#userecords-ignores-enabled-false)), so mount this query in a child
|
package/datasources/writing.md
CHANGED
|
@@ -85,6 +85,10 @@ Because the parser only inspects your hooks and `q.select` mappings (not the JSX
|
|
|
85
85
|
|
|
86
86
|
All mutation hooks expose an `enabled` boolean. You must check it before rendering any mutation UI or calling the mutate function.
|
|
87
87
|
|
|
88
|
+
The snippets below write `fields: q.select({...})` inline for brevity, as single-datasource
|
|
89
|
+
shorthand. In a block with more than one connection, hoist every `fields:` select to module scope
|
|
90
|
+
and pass the identifier ([multi-datasource.md](multi-datasource.md#select-must-be-a-plain-module-scope-identifier)).
|
|
91
|
+
|
|
88
92
|
### useRecordCreate
|
|
89
93
|
|
|
90
94
|
```jsx
|
|
@@ -677,7 +681,7 @@ Three remaining alternatives, for the cases multi-datasource doesn't cover:
|
|
|
677
681
|
**Update:** PATCH `/{recordId}` with `{ fields: { fieldId1: "newValue" } }`
|
|
678
682
|
|
|
679
683
|
Notes:
|
|
680
|
-
- API key
|
|
684
|
+
- The API key ships in the block's source. Anyone who can load the page can read it and use it against the whole database, past page VIEW, block Visibility and Source conditions alike. Use this only on a page limited to a trusted group, and only for what the hooks or a Softr Workflow cannot do
|
|
681
685
|
- When updating linked records or multi-selects, read existing values first, merge, then write
|
|
682
686
|
- Use `fieldNames=true` on GET for human-readable field names
|
|
683
687
|
- Rate limits: Reads 40 req/s, Writes 30 req/s
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "softr-vibe-coding",
|
|
3
|
-
"version": "2.13.
|
|
3
|
+
"version": "2.13.5",
|
|
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"
|