softr-vibe-coding 2.3.0 → 2.4.1
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 +8 -0
- package/README.md +17 -8
- package/SKILL.md +13 -9
- package/datasources/writing.md +49 -1
- package/package.json +1 -1
- package/references/airtable-automations.md +9 -0
- package/references/anti-patterns.md +2 -1
- package/references/dembrandt.md +89 -0
- package/references/native-chrome-styling.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,14 @@ 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.4.1] - 2026-09-01
|
|
8
|
+
- Reconcile the brand pipeline with building-design-md v2.0.0, which now drives dembrandt too. README migration note un-retires the companion: it runs the same dembrandt engine and layers voice & copy register, a resolved fonts block, logo assets, app-pattern scaffolds, and custom-code-header.html on an untouched dembrandt base. references/dembrandt.md legacy note becomes a companion-skill note (v2+ files read natively here; v1.x brand:/source:/extracted: files remain valid). SKILL.md Step 1 offers the fuller foundation alongside its quick raw generation and honours companion-v2 extra sections, preferring a fonts block over computed fontFamily values
|
|
9
|
+
|
|
10
|
+
## [2.4.0] - 2026-09-01
|
|
11
|
+
- Replace building-design-md with dembrandt as the Step 1 DESIGN.md generator. New references/dembrandt.md operating guide: MCP install via npx pinned @latest (deliberate deviation from upstream README — bare npx reuses its cache and never re-checks the registry) + one-time install-browser step (CLI self-heals a missing browser since 0.30.0, the MCP server does not — exact error string and remediation documented), the get_design_tokens → get_job_status → get_findings → generate_design_md flow behavior-tested live 2026-09-01 (async job_id default; completion returns the FULL extraction — 65KB for one page — so pass job_id downstream, never the payload; completed jobs kept 1 hour; generate_design_md returns markdown text and writes no file — the agent writes ./DESIGN.md in the project root), multi-page crawl doctrine (pages 3–5 / --crawl 5; live 1-page noise: 46 near-duplicate text tokens, 2.72px spacing steps, white-on-white button sample), DESIGN.md anatomy per Google draft spec 0.4 (name/description/colors/typography/spacing/rounded/components frontmatter — context-named typography tokens with text-N fallback, source URL inside description, no date field; Font URLs vs computed ui-sans-serif cross-check), failed-job remediation (slow retry / CLI firefox for Cloudflare / install-browser), authoring custom-code-header.html from DESIGN.md tokens (provenance claim fixed in native-chrome-styling.md — dembrandt generates no Softr-ready CSS), compute_drift brand QA (drift = did it change, findings = is it good). SKILL.md Step 1 rewritten: found-branch reads dembrandt frontmatter with legacy brand:/source:/extracted: files (incl. Application Patterns/tech_stack) still honoured + regenerate-overwrites note; not-found option A now generates in-session via MCP or CLI instead of ending the skill, with next-session MCP caveat, no-website hand-author path, and permission gate. README: brand pipeline section swapped to dembrandt with migration note for building-design-md users, TL;DR bullet, References entry, tree + file-count refresh
|
|
12
|
+
- airtable-automations.md: Meta API now creates formula/rollup/lookup fields + tables (verified live 2026-08-31); still no automations/views/deletes/option-renames
|
|
13
|
+
- writing.md: sanction cross-table parallel writes + optimistic-move/undo pattern; anti-patterns: FIFO-helper concurrency hazard (found live 2026-08-31)
|
|
14
|
+
|
|
7
15
|
## [2.3.0] - 2026-08-31
|
|
8
16
|
- Document expanded Softr MCP: deletes, app scaffolding, Workflows suite (26 tools + 418-node catalog), per-application MCP servers
|
|
9
17
|
|
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@ This Claude skill teaches Claude Code how to generate complete, polished Softr V
|
|
|
17
17
|
- **Complete Vibe Coding API reference** — `useRecords`, `q.select()`, mutations, uploads, metrics, charts, editable settings, `useProxyFetch` for REST APIs
|
|
18
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
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
|
|
20
|
+
- **Brand pipeline via dembrandt** — no DESIGN.md in the project? The skill generates one on the spot with [dembrandt](https://github.com/dembrandt/dembrandt) (real-browser design-token extraction over MCP: colors, typography, spacing, components from any site you have permission to analyze), writes it to the project root, and builds every block on those tokens — or keeps the default Softr style if you skip it
|
|
20
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
|
|
21
22
|
- **Helper blocks & cross-block patterns** — Invisible helper blocks for multi-table access via `window` globals + `CustomEvent`, `useWindowData` hook, breadcrumb navigation, saved views architecture
|
|
22
23
|
- **Advanced integrations** — Shadow DOM CSS isolation for third-party libraries (Leaflet, Mapbox, TinyMCE, Quill, FullCalendar)
|
|
@@ -88,24 +89,27 @@ Build me a Softr Vibe Coding block that shows a team directory with cards
|
|
|
88
89
|
|
|
89
90
|
---
|
|
90
91
|
|
|
91
|
-
##
|
|
92
|
+
## Brand pipeline — dembrandt
|
|
92
93
|
|
|
93
|
-
This skill is
|
|
94
|
+
This skill is the second half of a brand-to-blocks pipeline:
|
|
94
95
|
|
|
95
96
|
```
|
|
96
|
-
New client →
|
|
97
|
+
New client → dembrandt (website → DESIGN.md) → softr-vibe-coding (DESIGN.md → blocks) → shipped Softr app
|
|
97
98
|
```
|
|
98
99
|
|
|
99
|
-
The
|
|
100
|
+
The DESIGN.md generator is [dembrandt](https://github.com/dembrandt/dembrandt) (MIT, npm) — a real-browser design-token extractor: point it at a website and it extracts colors, typography, spacing, radii, shadows, and component styles, rendered as a portable `DESIGN.md` in Google's DESIGN.md draft format. When that file exists in your project folder, this skill picks it up automatically and applies the brand tokens throughout every block it generates — no re-asking about colors or fonts. When it doesn't, the skill offers to generate one on the spot through the dembrandt MCP server (writing it to the project root), or proceeds with the default Softr style if you skip branding.
|
|
100
101
|
|
|
101
|
-
**Install
|
|
102
|
+
**Install for the full workflow:**
|
|
102
103
|
|
|
103
104
|
```bash
|
|
104
|
-
npx building-design-md@latest init
|
|
105
105
|
npx softr-vibe-coding@latest init
|
|
106
|
+
claude mcp add --transport stdio dembrandt -- npx -y --package dembrandt@latest dembrandt-mcp
|
|
107
|
+
npx -y dembrandt@latest install-browser # one-time: fetches the Chromium dembrandt drives
|
|
106
108
|
```
|
|
107
109
|
|
|
108
|
-
|
|
110
|
+
The skill auto-updates on every Claude Code session (SessionStart hook), and the dembrandt server checks the registry for the newest release on every launch (`@latest`), so both stay current. Skip the two dembrandt lines if you only want default Softr styling. Full operating guide (MCP flow, multi-page crawls, DESIGN.md anatomy, drift QA): `references/dembrandt.md`.
|
|
111
|
+
|
|
112
|
+
The [`building-design-md`](https://github.com/leo-softr/design-md-extractor-skill) companion skill moved to the same engine: since its v2.0.0 it drives dembrandt too, and layers on what raw extraction can't provide — voice & copy register, resolved font names, logo assets, Softr app-pattern scaffolds, and the `custom-code-header.html` snippet. Run it when a project deserves the full brand foundation; this skill's Step 1 covers the quick raw DESIGN.md. Files from its v1.x (pre-dembrandt) remain fully supported.
|
|
109
113
|
|
|
110
114
|
---
|
|
111
115
|
|
|
@@ -212,6 +216,10 @@ softr-vibe-coding/
|
|
|
212
216
|
│ │ # headers, pricing, footers — workflow deltas,
|
|
213
217
|
│ │ # editorial baseline, full-bleed + full-viewport,
|
|
214
218
|
│ │ # block-owned header, section anchors
|
|
219
|
+
│ ├── dembrandt.md # DESIGN.md generation with dembrandt
|
|
220
|
+
│ │ # MCP/CLI install (@latest npx + browser step),
|
|
221
|
+
│ │ # extract → poll → findings → generate → write
|
|
222
|
+
│ │ # flow, DESIGN.md anatomy, drift QA
|
|
215
223
|
│ └── quick-reference.md # Syntax cheat sheet
|
|
216
224
|
│ # Imports, hook signatures, mutation shapes,
|
|
217
225
|
│ # field mapping, component skeleton
|
|
@@ -249,7 +257,7 @@ softr-vibe-coding/
|
|
|
249
257
|
|
|
250
258
|
### How context loading works
|
|
251
259
|
|
|
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
|
|
260
|
+
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 30+ files totaling 6,000+ lines.
|
|
253
261
|
|
|
254
262
|
---
|
|
255
263
|
|
|
@@ -318,6 +326,7 @@ Pull requests are what make open source great, and we appreciate the spirit behi
|
|
|
318
326
|
|
|
319
327
|
- [Softr Vibe Coding Developer Guide](https://docs.softr.io/vibe-coding-developer-guide) — Official Softr Vibe Coding documentation
|
|
320
328
|
- [Softr Data Sources](https://docs.softr.io/data-sources) — Official Softr data source documentation
|
|
329
|
+
- [dembrandt](https://github.com/dembrandt/dembrandt) — Real-browser design-token extractor used for DESIGN.md generation (see `references/dembrandt.md`)
|
|
321
330
|
- [Impeccable](https://github.com/pbakaus/impeccable) — Design patterns and UI/UX anti-pattern principles by Paul Bakaus (referenced in the UI/UX guidelines)
|
|
322
331
|
- [Claude Code Skills Documentation](https://code.claude.com/docs/en/skills) — How Claude Code skills work
|
|
323
332
|
|
package/SKILL.md
CHANGED
|
@@ -6,8 +6,9 @@ description: >
|
|
|
6
6
|
Also trigger when the user asks to create cards, lists, forms, dashboards, charts, detail pages,
|
|
7
7
|
or any interactive block intended for Softr — even if they don't say "Vibe Coding" explicitly.
|
|
8
8
|
If the user mentions Softr in the context of building a custom UI component, creating a JSX block,
|
|
9
|
-
or vibe coding, use this skill. Do NOT use for
|
|
10
|
-
(that is the
|
|
9
|
+
or vibe coding, use this skill. Do NOT use for standalone brand-token extraction with no Softr app
|
|
10
|
+
involved (that is the dembrandt tool on its own — this skill drives dembrandt in Step 1 when a
|
|
11
|
+
project needs a DESIGN.md), or for charts/dashboards with no Softr app involved.
|
|
11
12
|
when_to_use: >
|
|
12
13
|
Triggers on "build me a Softr block", "create a card component", "make a dashboard",
|
|
13
14
|
"vibe code this", "custom block for Softr", "JSX component for Softr app",
|
|
@@ -29,12 +30,14 @@ You generate complete, production-ready Softr Vibe Coding blocks as TypeScript R
|
|
|
29
30
|
|
|
30
31
|
- **If `./DESIGN.md` is found:** Read its frontmatter and confirm with the user:
|
|
31
32
|
|
|
32
|
-
> "I found a DESIGN.md in this project (brand: `<name>`,
|
|
33
|
+
> "I found a DESIGN.md in this project (brand: `<name>`, extracted from: `<source URL>`). Use its brand tokens for this block?
|
|
33
34
|
> 1. Yes — use this DESIGN.md
|
|
34
35
|
> 2. No — use the default Softr style instead
|
|
35
36
|
> 3. No — I'll paste a different brand override"
|
|
36
37
|
|
|
37
|
-
|
|
38
|
+
(Filling the placeholders: in a dembrandt-generated file, `<name>` is the frontmatter `name:` and the source URL sits inside `description:`; legacy files carry `brand:`/`source:`/`extracted:` instead. On request, a stale DESIGN.md can also be regenerated with dembrandt — that overwrites `./DESIGN.md`, and legacy scaffolding sections like `Application Patterns`/`tech_stack` are not reproduced.)
|
|
39
|
+
|
|
40
|
+
If (1), load every token section present: `colors`, `typography`, `spacing`, `rounded`, `components` from the YAML frontmatter, plus the body's Layout / Elevation & Depth / Shapes evidence and the Typography section's **Font URLs** (the frontmatter `fontFamily` can name a generic fallback like `ui-sans-serif` while the Font URLs reveal the real brand font — cross-check before picking the font). Sections without extracted evidence are simply absent — don't invent defaults for them. Files from the `building-design-md` companion (v2+) and legacy pre-dembrandt files may carry extra sections — a `fonts` block (the resolved brand fonts — prefer it over computed `fontFamily` values), an `Application Patterns` scaffold, a `tech_stack` block, or legacy `elevation` — honour those when present (the `tech_stack` block may pin specific shadcn variants or note bundler quirks). Full format anatomy: [references/dembrandt.md](references/dembrandt.md#designmd-anatomy-what-step-1-reads).
|
|
38
41
|
|
|
39
42
|
If (2), proceed with the default Softr style (see Step 3).
|
|
40
43
|
|
|
@@ -43,11 +46,11 @@ You generate complete, production-ready Softr Vibe Coding blocks as TypeScript R
|
|
|
43
46
|
- **If `./DESIGN.md` is NOT found:** Tell the user:
|
|
44
47
|
|
|
45
48
|
> "No DESIGN.md found in this project. Three options:
|
|
46
|
-
> A. **
|
|
49
|
+
> A. **Generate one now with dembrandt** — give me the brand's website URL and I'll extract its design system (real-browser crawl: colors, typography, spacing, components) into `./DESIGN.md`, then build on those tokens. (Recommended for client work.)
|
|
47
50
|
> B. **Quick brand override** — paste the brand's primary color, accent color, and font name now. I'll apply just those.
|
|
48
51
|
> C. **Use the default Softr style** — primary `#386AF5`, accent `#FCB500`, Inter font."
|
|
49
52
|
|
|
50
|
-
Wait for their pick. If (A)
|
|
53
|
+
Wait for their pick. If (A) — extracting only a site the user owns or has permission to analyze (a contracted client's own site qualifies) — run the dembrandt pipeline from [references/dembrandt.md](references/dembrandt.md) in this same session — via the dembrandt MCP server when connected (`get_design_tokens` with `pages: 3`–`5` → poll `get_job_status` → `get_findings` sanity check → `generate_design_md`, then **write the returned markdown to `./DESIGN.md` in the project root**), or the CLI fallback when not (`npx -y dembrandt@latest <url> --design-md --crawl 5`, then copy `output/<domain>/DESIGN.md` to `./DESIGN.md`). If neither is available, give the user the install commands from that reference and pause until dembrandt is set up (an MCP server added now connects next session — the CLI is the same-session path). Show the user the extracted brand summary, then continue to Step 2 with those tokens. (When the project deserves a fuller foundation — voice & copy register, logo assets, app-pattern scaffolds, the `custom-code-header.html` snippet — offer the `building-design-md` skill (v2+, same dembrandt engine plus those layers) instead of this quick raw generation.) No public website to extract? Use (B), or offer to hand-author `./DESIGN.md` from whatever brand material the user has (a brand guide PDF, a style sheet) — Step 1 honours any DESIGN.md with token sections, not only dembrandt-generated ones. If (B) or (C), record their choice for Step 3 and continue.
|
|
51
54
|
|
|
52
55
|
Do not silently default to Softr's brand. The user must opt in to defaults explicitly.
|
|
53
56
|
|
|
@@ -80,7 +83,7 @@ You generate complete, production-ready Softr Vibe Coding blocks as TypeScript R
|
|
|
80
83
|
- `fetchNextPage` never called in the render body — only from an event handler (Load More `onClick` with `disabled={isFetching}`, the official pattern) or a guarded `useEffect` (auto-load-all)
|
|
81
84
|
- Mutations use `recordId` (not `id`) and call `refetch()` in `onSuccess`
|
|
82
85
|
- `useRecordUpdate` payload is `{ recordId, fields: { ... } }` — nested. `useRecordCreate` payload is **flat** (no `fields` wrapper). The two shapes are asymmetric by design (verified live 2026-08-25)
|
|
83
|
-
- Sequential multi-row saves use `await hook.mutateAsync(...)` per row, in order, with stop-on-failure + retry state — `mutateAsync` is fully supported on the current platform (verified 2026-08-25; the old ".mutate() only" Action-parser rule is gone — see [datasources/writing.md](datasources/writing.md))
|
|
86
|
+
- 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)
|
|
84
87
|
- No hardcoded domains in links -- use relative paths (`/page?recordId=...`); same-page anchors written relative too (`/#section`)
|
|
85
88
|
- 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
89
|
- Array-setting rows keyed by **index**, never by a builder-editable field value
|
|
@@ -96,7 +99,7 @@ When the user describes their block, figure out which of these areas apply and a
|
|
|
96
99
|
- For **Airtable**, the most thorough path is the bundled **`get-airtable-base` shell script** — `bash ~/.claude/skills/softr-vibe-coding/tools/get-airtable-base` (requires `jq` — `brew install jq` on macOS). It prompts for Base ID + PAT, then exports the full schema (every table, every field with both `fld...` IDs and column names, relationships, webhooks, interfaces) to a timestamped Desktop folder. The user pastes `02-schema.json` or the combined `00-bundle.json` into chat. For lighter inspection (just a few fields, runtime-only), suggest the Field Inspector block — empty `q.select({})` works for Airtable. CLI script details in [datasources/airtable.md](datasources/airtable.md#bundled-cli-script-get-airtable-base).
|
|
97
100
|
- For other non-Softr-DB sources where empty `q.select({})` works, suggest the Field Inspector block.
|
|
98
101
|
- **Brand colors**: Already resolved in Step 1 (Detect the brand source). Don't re-ask. The brand source is one of:
|
|
99
|
-
- **Project's `./DESIGN.md`** (recommended for client work —
|
|
102
|
+
- **Project's `./DESIGN.md`** (recommended for client work — generated by dembrandt in Step 1, or already present in the project; see [references/dembrandt.md](references/dembrandt.md))
|
|
100
103
|
- **User's quick override** (paste of primary + accent + font)
|
|
101
104
|
- **Default Softr palette** (only when the user explicitly opted in — never as a silent fallback):
|
|
102
105
|
|
|
@@ -171,6 +174,7 @@ For advanced patterns beyond data fetching, load the relevant reference when the
|
|
|
171
174
|
| 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
175
|
| **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
176
|
| **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) |
|
|
177
|
+
| Generating or refreshing a project **`DESIGN.md` with dembrandt** — MCP + CLI install (`@latest` npx, one-time browser step), the extract → poll → `get_findings` → `generate_design_md` → write-`./DESIGN.md` flow, multi-page crawls, DESIGN.md anatomy (frontmatter tokens, Font URLs), authoring `custom-code-header.html` from tokens, brand-drift QA with `compute_drift` | [references/dembrandt.md](references/dembrandt.md) |
|
|
174
178
|
|
|
175
179
|
## Code Structure
|
|
176
180
|
|
|
@@ -390,7 +394,7 @@ When the action navigates to a record-specific page, pass the runtime record id
|
|
|
390
394
|
</NavigationAction>
|
|
391
395
|
```
|
|
392
396
|
|
|
393
|
-
For on-brand custom styling (matching
|
|
397
|
+
For on-brand custom styling (matching the Step 1 brand tokens' 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>`.
|
|
394
398
|
|
|
395
399
|
**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
400
|
|
package/datasources/writing.md
CHANGED
|
@@ -211,7 +211,55 @@ Rules that make this safe:
|
|
|
211
211
|
to `undefined`.
|
|
212
212
|
- **Gate the whole flow on the hooks' `enabled` booleans**, same as any mutation UI.
|
|
213
213
|
|
|
214
|
-
|
|
214
|
+
### Parallel writes across tables (the one sanctioned parallelism)
|
|
215
|
+
|
|
216
|
+
The no-parallel rule above is about *same-table batches*. Writes to **different tables through
|
|
217
|
+
different hooks, with no ordering or id-linking dependency between them**, may run concurrently —
|
|
218
|
+
`Promise.all` of the two `mutateAsync` promises roughly halves the wall-clock of a two-table save
|
|
219
|
+
(verified live 2026-08-31: a Kanban reassignment writing `Wigs.Worker` + a batch of
|
|
220
|
+
`Wig Services.Worker` lines in parallel):
|
|
221
|
+
|
|
222
|
+
```jsx
|
|
223
|
+
Promise.all([
|
|
224
|
+
updateWig.mutateAsync({ recordId: wigId, fields: { worker: [{ id: targetId }] } }),
|
|
225
|
+
reassignServicesViaHelper(lineTargets), // helper-owned batch on ANOTHER table
|
|
226
|
+
]).then(onSaved).catch(onFailed);
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Checklist before parallelizing:
|
|
230
|
+
|
|
231
|
+
- **Different tables, different hooks.** Same-table batches stay sequential — Airtable's
|
|
232
|
+
per-base rate limit, and stop-on-first-failure semantics that a parallel volley can't give you.
|
|
233
|
+
- **No dependency either way.** If one write needs the other's id (header → lines), it's a
|
|
234
|
+
sequence, not a pair.
|
|
235
|
+
- **Neither side is an event-driven helper that correlates responses by FIFO queue order.**
|
|
236
|
+
Many helper blocks in the wild match `onSuccess` events to requests with `queue.shift()` —
|
|
237
|
+
concurrent dispatches completing out of order mis-pair the correlation ids. Serialize calls
|
|
238
|
+
into such a helper, or upgrade it to batch semantics (one event carries the whole batch, the
|
|
239
|
+
helper chains `mutateAsync` internally and answers once). See
|
|
240
|
+
[anti-patterns.md → Helper Blocks](../references/anti-patterns.md#helper-blocks).
|
|
241
|
+
- **Partial failure is acceptable and handled.** With `Promise.all`, one side can commit while
|
|
242
|
+
the other fails — refetch both tables in the failure path so the UI shows the true state.
|
|
243
|
+
|
|
244
|
+
### Optimistic moves + undo (perceived speed beats write speed)
|
|
245
|
+
|
|
246
|
+
For drag-and-drop or one-click reassignment UIs, don't make the user watch the writes — a
|
|
247
|
+
multi-row save through helpers takes seconds, and a board that freezes until `refetch()`
|
|
248
|
+
completes reads as broken. The pattern (verified live 2026-08-31 on a custom Kanban):
|
|
249
|
+
|
|
250
|
+
1. **Override map**: `pendingMoves[recordId] = target` in state; the render layer applies the
|
|
251
|
+
override on top of server-derived data, so the card jumps the moment it's dropped.
|
|
252
|
+
2. **Background writes** with a small per-card "saving" spinner; the board stays interactive
|
|
253
|
+
(track saving per record id, not one global flag).
|
|
254
|
+
3. **Revert on failure**: delete the override + `refetch()` → the card snaps back, with an
|
|
255
|
+
error toast.
|
|
256
|
+
4. **Clear on convergence**: an effect compares each override against fresh server data and
|
|
257
|
+
deletes it once they match — never clear on a timer.
|
|
258
|
+
5. **Undo**: snapshot the *server* state before mutating (previous lead, each row's previous
|
|
259
|
+
value — per-row, since a batch may have had mixed values), and offer
|
|
260
|
+
`toast.success(msg, { duration: 8000, action: { label: "Undo", onClick: restore } })`.
|
|
261
|
+
The restore is just another optimistic move driven by the snapshot. Snapshot before the
|
|
262
|
+
write, not from the UI — the UI may already be showing an optimistic override.
|
|
215
263
|
|
|
216
264
|
```jsx
|
|
217
265
|
import { useUpload } from "@/lib/datasource";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "softr-vibe-coding",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.1",
|
|
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"
|
|
@@ -321,6 +321,15 @@ For Automation Scripts, your ONLY user-visible surfaces are `console.log()` (run
|
|
|
321
321
|
|
|
322
322
|
For formula fields inside Airtable itself (NOT scripts). Quick rules:
|
|
323
323
|
|
|
324
|
+
- **The Meta API can CREATE formula, rollup, and lookup fields — and whole tables** (verified
|
|
325
|
+
live 2026-08-31: a full payroll junction table with two SUM rollups, lookups, and formula
|
|
326
|
+
fields built via `POST /v0/meta/bases/{base}/tables` + `…/fields`; rollup options shape:
|
|
327
|
+
`{recordLinkFieldId, fieldIdInLinkedTable, formula: "SUM(values)"}`). The long-standing
|
|
328
|
+
"computed fields are UI-only" rule is dead. Still API-impossible: **automations**, **views**,
|
|
329
|
+
**field/table deletion**, and **select-option renames** (field name/description PATCH works).
|
|
330
|
+
Corollary: reference other fields by `{fldXXX}` id inside API-created formulas — id references
|
|
331
|
+
survive renames, and the API accepts them even though the UI shows names.
|
|
332
|
+
|
|
324
333
|
- **NEVER add comments inside Airtable formulas.** Airtable's formula engine doesn't have a comment syntax — anything that looks like a comment will fail to compile.
|
|
325
334
|
- **Guard against blank inputs by default.** Airtable surfaces `#ERROR!` (or `#NaN!` for divide-by-blank) when arithmetic, date, or many string operations touch a blank field — `{A} * {B}` errors when either is blank, `{A} / {B}` errors when `{B}` is blank, `DATEADD({Start At}, 20, 'minutes')` errors when `{Start At}` is blank, and one upstream `#ERROR!` propagates through every downstream formula that references it. Wrap any formula whose inputs could be empty with an `IF()` guard returning `BLANK()` in the empty branch. Prefer explicit `IF()` / `AND()` guards over a catch-all `IFERROR(<expr>, BLANK())` — explicit guards keep intent readable and don't mask unrelated bugs (a typo'd field name silently returns blank instead of failing loudly). Reach for `IFERROR()` only when inputs come from genuinely uncontrolled sources.
|
|
326
335
|
|
|
@@ -26,7 +26,7 @@ Run through this catalog before delivering any block. Every row is a violation o
|
|
|
26
26
|
|---|---|
|
|
27
27
|
| `.mutate({ id: ... })` | `.mutate({ recordId: ... })` -- `id` causes 404 |
|
|
28
28
|
| `updateRecord.mutate({ recordId, status: "..." })` — flat payload | `updateRecord.mutate({ recordId, fields: { status: "..." } })` — fields **must** be nested. The flat form can run at runtime but Softr's Action parser doesn't see field references inside it, so the derived Update Action never gets created. The hook's `enabled` stays `false`, the Save button never lights up, the Actions tab in Studio shows "No actions used in this block yet" — all with no error, no warning. The symptom is a button that does nothing and a console log showing `enabled: false, error: null, status: "idle"`. Use the nested form for EVERY update call, even single-field updates (create payloads are flat — see the dedicated row below). See [datasources/writing.md](../datasources/writing.md#critical-the-userecordupdate-payload-shape-and-the-retired-mutate-only-rule) |
|
|
29
|
-
| Sequencing multi-row saves with nested `.then()/.catch()` chains, or firing the rows in parallel | `await hook.mutateAsync(row)` per row, in order — header first, then lines; stop on the first failure with renderable retry state; never re-issue completed writes. `mutateAsync` is **fully supported** on the current platform (verified live 2026-08-25 — this supersedes the May 2026 finding that the Action parser only recognized the literal `.mutate(` token; that limitation is gone, and is worth checking only when maintaining an old app whose Action refuses to derive). Full queue pattern: [datasources/writing.md](../datasources/writing.md#sequential-multi-row-writes-mutateasync) |
|
|
29
|
+
| Sequencing multi-row saves with nested `.then()/.catch()` chains, or firing the rows in parallel | `await hook.mutateAsync(row)` per row, in order — header first, then lines; stop on the first failure with renderable retry state; never re-issue completed writes. `mutateAsync` is **fully supported** on the current platform (verified live 2026-08-25 — this supersedes the May 2026 finding that the Action parser only recognized the literal `.mutate(` token; that limitation is gone, and is worth checking only when maintaining an old app whose Action refuses to derive). Full queue pattern: [datasources/writing.md](../datasources/writing.md#sequential-multi-row-writes-mutateasync). One sanctioned exception: independent writes to **different tables** may run in parallel — see [writing.md → Parallel writes across tables](../datasources/writing.md#parallel-writes-across-tables-the-one-sanctioned-parallelism) |
|
|
30
30
|
| Assuming `mutation.enabled === false` always means a code bug | `enabled` is BOTH a parser signal AND a permissions signal. Per the official Softr docs, "`enabled` reflects user permissions." When code looks correct and the Actions tab shows the action listed, the cause is almost always permissions. Test by switching "Preview as" in Studio to an Owner / admin; if it then works, the issue is permissions. Three places to check, in priority order: (1) the block's **Visibility** tab (right panel), (2) **Studio → Users → Data Restrictions → Global data restrictions** — an app-wide layer that easily gets overlooked because it's hidden under Users (not on the block); it overlays every block in the app, and a single restriction on the target table will silently disable every mutation against that table for the affected user group, (3) the data-source PAT scope — if granted read-only, every write fails regardless of UI permissions. See [datasources/writing.md](../datasources/writing.md#how-actions-work-studios-actions-tab) |
|
|
31
31
|
| `deleteRecord.mutate({ id: r.id })` | `deleteRecord.mutate(r.id)` -- just the string |
|
|
32
32
|
| `var { mutateAsync } = useRecordUpdate({...})` -- destructuring the mutate function off the hook | `var updateRecord = useRecordUpdate({...})` -- keep the full object so `.enabled`, `.status`, `.reset()` stay reachable (using `.mutateAsync` itself is fine) |
|
|
@@ -105,3 +105,4 @@ Run through this catalog before delivering any block. Every row is a violation o
|
|
|
105
105
|
| Refactoring helper shape without updating consumers | Version namespace OR update all consumers in same commit |
|
|
106
106
|
| Helper B placed above A when B depends on A | A must be above B -- Softr renders top-to-bottom |
|
|
107
107
|
| Using `useLinkedRecords` for rich foreign data | It only returns `{id, title}` and silently ignores extra `select` fields. Connect the foreign table as a **second datasource** and read it with its own `useRecords({ from: ds.x })` — see [multi-datasource.md](../datasources/multi-datasource.md). (A helper block also works and is what older blocks do, but it's now the heavier option.) |
|
|
108
|
+
| Dispatching CONCURRENT write requests into a helper that correlates responses by FIFO queue order (`pendingRef.push(tempId)` + `queue.shift()` in `onSuccess`) | Mutations can complete out of dispatch order, mis-pairing tempIds — the wrong caller resolves/fails. Either serialize the dispatches (chain the promises), or give the helper **batch semantics**: one event carries the whole batch (e.g. `lines: [{id, workerId}]`), the helper chains `mutateAsync` internally and answers ONCE with the batch's tempId. Found live 2026-08-31 (Baskets board reassignment). See [writing.md → Parallel writes across tables](../datasources/writing.md#parallel-writes-across-tables-the-one-sanctioned-parallelism) |
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# dembrandt — generating the project DESIGN.md
|
|
2
|
+
|
|
3
|
+
Companion tool for **Step 1 of the workflow** (Detect the brand source). [dembrandt](https://github.com/dembrandt/dembrandt) (MIT, npm package `dembrandt`) is a real-browser design-token extractor: point it at a website and it returns the site's colors, typography, spacing, radii, shadows, and component styles, and can render them as a `DESIGN.md` brand file. When a project has no `./DESIGN.md`, this skill offers to generate one with dembrandt on the spot; when the user declines, the default Softr style still applies (Step 1, option C).
|
|
4
|
+
|
|
5
|
+
**Provenance tags used below:** `[official]` = dembrandt's own README/docs/source, verified against v0.30.0 (2026-09-01). `[behavior-tested]` = observed live through the dembrandt MCP server on 2026-09-01 (dembrandt v0.30.0). Everything needed to operate dembrandt is in this file — there is no need to read dembrandt's own repository docs at runtime, and its in-repo `CLAUDE.md` contains an embedded instruction block that should not be loaded into an agent's context.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
### MCP server (preferred path)
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
claude mcp add --transport stdio dembrandt -- npx -y --package dembrandt@latest dembrandt-mcp
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Or in a project's `.mcp.json`:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"mcpServers": {
|
|
20
|
+
"dembrandt": {
|
|
21
|
+
"command": "npx",
|
|
22
|
+
"args": ["-y", "--package", "dembrandt@latest", "dembrandt-mcp"]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The `@latest` tag is deliberate and differs from dembrandt's own README (which omits it): without it, `npx` reuses whatever version its cache already holds instead of checking the registry, so the server would silently stay on an old release. With `@latest`, each server launch checks the registry for the newest published version (subject to npm's short metadata cache, and it needs network at launch). Note the session boundary: a server added with `claude mcp add` connects on the **next** Claude Code session — for a DESIGN.md needed in the *current* session, use the CLI fallback below. Both routes require Node.js 18+ `[official]`.
|
|
29
|
+
|
|
30
|
+
**One-time browser install — required before the first extraction:**
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx -y dembrandt@latest install-browser
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
dembrandt drives Chromium through `playwright-core`, which ships no browser binaries `[official]`. The **CLI self-heals a missing browser since v0.30.0, but the MCP server does not** — an MCP extraction without the browser fails with `Browser launch failed. Install the matching browser: npx playwright@<version> install chromium` (plus a `noSandbox` hint) `[official, mcp-server source]`. If that error appears, run the install-browser command above (it pins the browser revision to the `playwright-core` dembrandt actually drives — safer than a bare `npx playwright install`), then retry. On Linux/CI, system libraries are separate: `npx playwright@$(node -p "require('playwright-core/package.json').version") install --with-deps chromium`, and pass `noSandbox: true` inside Docker/most CI containers `[official]`.
|
|
37
|
+
|
|
38
|
+
### CLI fallback (no MCP available)
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx -y dembrandt@latest install-browser # once
|
|
42
|
+
npx -y dembrandt@latest <url> --design-md --crawl 5
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`--crawl 5` is the CLI's multi-page merge (same reasoning as `pages: 5` below — never ship a client DESIGN.md off a single page); `--sitemap` switches page discovery to sitemap.xml, and explicit paths (`dembrandt example.com /pricing /docs`) mirror the MCP `paths` option `[official]`.
|
|
46
|
+
|
|
47
|
+
The CLI writes to `output/<domain>/DESIGN.md` **relative to the current directory, not to the project root** `[official]` — copy or move it to `./DESIGN.md` afterwards. (The MCP path has no such detour: the tool returns the markdown and the agent writes `./DESIGN.md` directly.)
|
|
48
|
+
|
|
49
|
+
## The MCP flow, end to end
|
|
50
|
+
|
|
51
|
+
Tools on the server: 7 extraction tools (`get_design_tokens`, `get_color_palette`, `get_typography`, `get_component_styles`, `get_surfaces`, `get_spacing`, `get_brand_identity`), 5 pure analysis tools (`generate_design_md`, `get_findings`, `compute_drift`, `export_dtcg`, `render_report`), 3 job tools (`get_job_status`, `list_jobs`, `cancel_job`) `[official]`. For Step 1 you need exactly this sequence:
|
|
52
|
+
|
|
53
|
+
1. **`get_design_tokens({ url, pages: 5 })`** → returns `{ job_id, status: "queued" }` immediately `[behavior-tested]`. Async is the default; `sync: true` exists but blocks 15–40s per page `[official]` — prefer polling.
|
|
54
|
+
- **Always crawl multiple pages for client work** (`pages: 3`–`5`; max 20). Merged multi-page extraction produces a "markedly stronger token set" `[official]`, and the single-page noise is real: a live 1-page run returned 46 near-duplicate typography entries, sub-pixel spacing steps (`2.72px`), and a `button-observed` sample with white text on a white background `[behavior-tested]`. `paths: ["/pricing", "/about"]` names extra pages explicitly; `sitemap: true` discovers them from sitemap.xml (alone it takes up to 20 pages — set `pages` to cap it); failed pages are skipped silently `[official]`.
|
|
55
|
+
- Other options: `slow` (3× timeouts, for JS-heavy SPAs), `mobile` (390×844 viewport; default is 1920×1080), `cookie` / `header` (authenticated staging sites), `userAgent`, `noSandbox`. `darkMode` and `wcag` exist **only** on `get_design_tokens` and `get_color_palette`, not the other extraction tools `[official, mcp source]`. Dark mode is never auto-detected — extract it as a second explicit run if the brand ships one `[official]`.
|
|
56
|
+
2. **Poll `get_job_status(job_id)`** until `status: "completed"` (states: queued / running / completed / failed / cancelled). A 1-page extraction completed in ~60s live `[behavior-tested]`. **On completion the response embeds the full extraction — 65KB for one page** `[behavior-tested]` — so never re-send that payload anywhere: every downstream tool accepts `job_id` and reads the extraction server-side. Completed jobs are kept for **1 hour** `[official]` — finish the flow in-session, or re-extract. If the job **fails**, read the error and pick the remedy: timeout on a JS-heavy site → retry once with `slow: true`; bot protection / Cloudflare → fall back to the CLI with `--browser=firefox` (after `npx -y dembrandt@latest install-browser firefox`); `Browser launch failed` → run the install-browser command from the Install section, then retry.
|
|
57
|
+
3. **`get_findings(job_id)`** — cheap sanity check before trusting the tokens: WCAG-contrast and consistency lint with severity, plus category scores. The live run flagged 4 no-visual-hierarchy typography collisions and one AA contrast failure in seconds `[behavior-tested]`. Surface anything severe to the user alongside the brand summary.
|
|
58
|
+
4. **`generate_design_md(job_id)`** — returns the complete DESIGN.md **as tool-result text; it writes no file** `[behavior-tested]`. The agent writes the returned markdown to **`./DESIGN.md` in the project root**, verbatim. Then show the user a short brand summary (name, palette roles, primary font) and continue the workflow with those tokens.
|
|
59
|
+
|
|
60
|
+
Only extract sites the user owns or has permission to analyze — for contracted client work, the client's own website qualifies; respect robots.txt and the site's ToS `[official]`. Sites behind aggressive bot protection (Cloudflare) may time out — the CLI supports `--browser=firefox` for those (`npx -y dembrandt@latest install-browser firefox` first); the MCP server drives Chromium. Canvas/WebGL-rendered sites cannot be analyzed at all (no DOM to read) `[official]`.
|
|
61
|
+
|
|
62
|
+
## DESIGN.md anatomy (what Step 1 reads)
|
|
63
|
+
|
|
64
|
+
dembrandt emits Google's DESIGN.md draft format (spec 0.4) `[official]`: YAML frontmatter with machine-readable tokens, then ordered markdown sections. Frontmatter keys, each **omitted entirely when there is no extracted evidence** (never filled with invented defaults) `[official]`:
|
|
65
|
+
|
|
66
|
+
| Key | Shape `[behavior-tested]` |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `name` | Site/brand name (`"Dembrandt"`) |
|
|
69
|
+
| `description` | `"Design tokens extracted from <url>"` — **the source URL lives here**; there are no `source:`/`extracted:` fields and no date in the file |
|
|
70
|
+
| `colors` | Semantic roles → uppercase hex: `primary` / `secondary` / `tertiary` / `surface` / `on-surface` (and `error` when detected `[official, emitter source]`) |
|
|
71
|
+
| `typography` | Context-named tokens — `headline-display` (h1), `headline-lg`/`-md`/`-sm`, `label-lg` (buttons), `label-md` (links), `body-md` (body text), with `text-N` as the fallback for unclassified styles `[official, emitter source]` — each with `fontFamily` / `fontSize` / `fontWeight` / `lineHeight` (sometimes `letterSpacing` / `fontFeature`). A noisy single-page run can land everything in the `text-N` fallback `[behavior-tested]` |
|
|
72
|
+
| `spacing` | `base` plus named steps (`xs`…`xxxxl`) |
|
|
73
|
+
| `rounded` | `sm` / `md` / `lg` / `xl` radii, plus `none` (0) and `full` (pill) when observed `[official, emitter source]` |
|
|
74
|
+
| `components` | `button-observed` / `input-observed` with backgroundColor, textColor, rounded, padding (buttons may add `height`) — values may reference other tokens (`"{rounded.lg}"`) |
|
|
75
|
+
|
|
76
|
+
Body sections in order: `# Design System` → Overview → Colors → Typography → Layout (spacing scale + responsive breakpoints) → Elevation & Depth → Shapes → Components. Two body-only nuggets matter for Softr work `[behavior-tested]`:
|
|
77
|
+
|
|
78
|
+
- **Font URLs** (in the Typography section): direct `.woff2` links to the site's real webfonts. The frontmatter `fontFamily` reports the *computed* value, which can be a generic fallback (`ui-sans-serif`) while the Font URLs reveal the actual brand font — cross-check before declaring the brand font, and use these URLs when authoring page-level `@font-face` CSS.
|
|
79
|
+
- Motion tokens exist in dembrandt's JSON extraction but are **not** part of DESIGN.md `[official]` — don't expect an animation section.
|
|
80
|
+
|
|
81
|
+
Companion-skill note: the `building-design-md` skill (v2+) drives this same dembrandt pipeline and layers more on top — voice & copy register, a resolved `fonts` block, logo `assets`, Softr `tech_stack`, and an Application Patterns scaffold — appended around an untouched dembrandt base, so its files read here natively. Files from its v1.x (pre-dembrandt) carry different frontmatter (`brand:`/`source:`/`extracted:`) and old-format sections; they remain valid brand sources — Step 1 honours whatever token sections are present rather than demanding the dembrandt shape.
|
|
82
|
+
|
|
83
|
+
## Authoring `custom-code-header.html` from DESIGN.md
|
|
84
|
+
|
|
85
|
+
dembrandt generates **no Softr-ready CSS** — its only CSS export is a CLI-side Tailwind v4 `@theme` file (`--tailwind`), which Softr's Custom Code header has no use for. When the app needs global brand CSS in Softr's **Settings → Custom Code → Code inside header** (webfont loading, `--brand-*` custom properties, native-chrome restyling), the agent authors that CSS from the DESIGN.md: `<link>`/`@font-face` from the Font URLs, custom properties from `colors`. House convention keeps this CSS in a `custom-code-header.html` file in the project folder — see [native-chrome-styling.md](native-chrome-styling.md). The shadow-DOM rules are unchanged: that global CSS reaches native chrome but never the inside of a block ([anti-patterns.md](anti-patterns.md)).
|
|
86
|
+
|
|
87
|
+
## Optional: brand-drift QA with `compute_drift`
|
|
88
|
+
|
|
89
|
+
After shipping, dembrandt can score how far the published Softr app drifted from the client's brand: extract the client site (baseline) and the published app (candidate), then `compute_drift({ baselineJobId, candidateJobId })` → a 0–100 score (0 = identical), a stable/drift verdict (threshold 10), and per-token changes `[official]`. Rule of thumb: **drift asks "did it change", findings asks "is it good"** `[official]`. Expect *some* structural drift (Softr's chrome contributes tokens the client site lacks) — read the changed-token list, not just the score.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Styling Softr's Native Shell (Header · Footer · Page Background) via Custom Code
|
|
2
2
|
|
|
3
|
-
**This is NOT about Vibe Coding blocks.** Softr's top bar, navigation, dropdown menus, footer, and the page background are part of the *native app shell* — configured in Softr Studio and rendered in the **main document**, not inside a block's shadow DOM. You **cannot** build or replace the native chrome itself as a Vibe Coding block. To re-skin it, add **CSS to Settings → Custom Code → Code inside header** (the same place brand fonts/tokens live
|
|
3
|
+
**This is NOT about Vibe Coding blocks.** Softr's top bar, navigation, dropdown menus, footer, and the page background are part of the *native app shell* — configured in Softr Studio and rendered in the **main document**, not inside a block's shadow DOM. You **cannot** build or replace the native chrome itself as a Vibe Coding block. To re-skin it, add **CSS to Settings → Custom Code → Code inside header** (the same place brand fonts/tokens live — house convention keeps that CSS in a `custom-code-header.html` file in the project folder and pastes it into the setting; you author it from the project's DESIGN.md tokens, since dembrandt supplies the palette and webfont URLs but generates no Softr-ready CSS — see [dembrandt.md](dembrandt.md#authoring-custom-code-headerhtml-from-designmd)). Pure CSS — no markup, no JS — and the native chrome stays in place, so Softr's auth-aware nav (account menu, sign-out, user-group gating) keeps working. (Separate pattern, different problem: a landing page with the native header **hidden** can carry a block-owned in-block header — see [Restyle vs. replace vs. block-owned header](#restyle-vs-replace-vs-block-owned-header).)
|
|
4
4
|
|
|
5
5
|
This doc covers the **header / nav / dropdowns**, the **footer**, the **floating "island" treatment** for both, and the **page background** — which is trickier than it looks, because Softr stacks the same fill on several layers.
|
|
6
6
|
|