softr-vibe-coding 1.5.2 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,12 @@ All notable changes to this skill are documented here. Versions follow [Semantic
4
4
 
5
5
  Entries from 1.3.1 onward are generated automatically from git commit subjects between version bumps (see `.github/workflows/publish.yml`). Entries before 1.3.1 were backfilled by hand from the existing commit history.
6
6
 
7
+ ## [1.7.0] - 2026-06-04
8
+ - Add references/native-chrome-styling.md — restyle Softr's native header/top bar/nav/dropdowns via global Custom Code CSS (target stable .softr-* / ARIA-Radix selectors instead of regenerated f8f11e5_* hashes, the dropdown column-grid blank-space fix, icon/label color-inherit, restyle-vs-replace tradeoffs); add SKILL.md scope note + Reference Guides row; add two anti-patterns rows; update README; bump to 1.7.0
9
+
10
+ ## [1.6.0] - 2026-06-03
11
+ - Document useNavigationBlocker for form-dirty navigation guards in Softr SPA mode
12
+
7
13
  ## [1.5.2] - 2026-05-27
8
14
  - Document BLANK-guard convention for Airtable formulas — Airtable surfaces #ERROR! / #NaN! when arithmetic, date, or string operations touch a blank field and propagates the error through every downstream formula; add a quick-rule bullet calling out the failure modes (multiply/divide-by-blank, DATEADD on blank date), show the IF({field}, <expr>, BLANK()) and AND()-guarded shapes, note why explicit guards beat catch-all IFERROR (don't mask typos), and re-render the common-patterns block with guards applied to the date examples; bump to 1.5.2
9
15
 
package/README.md CHANGED
@@ -18,6 +18,7 @@ This Claude skill teaches Claude Code how to generate complete, polished Softr V
18
18
  - **All 14 Softr data sources** — Airtable, Softr Database, Google Sheets, HubSpot, Notion, Coda, monday.com, SmartSuite, ClickUp, Xano, Supabase, BigQuery, SQL Database, and REST API — each with field mapping, rate limits, and gotchas
19
19
  - **Helper blocks & cross-block patterns** — Invisible helper blocks for multi-table access via `window` globals + `CustomEvent`, `useWindowData` hook, breadcrumb navigation, saved views architecture
20
20
  - **Advanced integrations** — Shadow DOM CSS isolation for third-party libraries (Leaflet, Mapbox, TinyMCE, Quill, FullCalendar)
21
+ - **Native header styling** — re-skin Softr's native top bar, nav, and dropdown menus via global Custom Code CSS (stable selectors vs. hashed classes, the dropdown blank-space grid fix, restyle-vs-replace) — distinct from blocks
21
22
  - **UI/UX design guidelines** — 26 sections covering visual hierarchy, color, typography, spacing, motion design, accessibility, responsive patterns, and an AI slop anti-pattern checklist
22
23
  - **Self-validation** — Claude checks for Softr bundler compatibility (no optional chaining, correct imports, container wrappers, `getFieldValue()` wrapping, hooks ordering) before delivering code
23
24
  - **Premium visual baseline** — Every block ships polished from v1: gradient backgrounds, card elevation, loading skeletons, empty states, error states
@@ -184,6 +185,9 @@ softr-vibe-coding/
184
185
  │ │ # install, scope limitations)
185
186
  │ ├── advanced-integrations.md # Shadow DOM CSS isolation (69 lines)
186
187
  │ │ # Leaflet, Mapbox, TinyMCE, Quill, FullCalendar
188
+ │ ├── native-chrome-styling.md # Restyle Softr's native header/nav/dropdowns
189
+ │ │ # via global Custom Code CSS — stable selectors,
190
+ │ │ # dropdown grid blank-space fix, restyle vs replace
187
191
  │ ├── anti-patterns.md # Categorized violation catalog (86 lines)
188
192
  │ │ # Data access, mutations, hooks, layout,
189
193
  │ │ # permissions, helper blocks
package/SKILL.md CHANGED
@@ -20,6 +20,8 @@ allowed-tools: Read Write Glob Grep Bash
20
20
 
21
21
  You generate complete, production-ready Softr Vibe Coding blocks as JSX files. A Vibe Coding block is a JavaScript file with a default-exported React component that runs exclusively in the browser inside a Softr app.
22
22
 
23
+ > **Scope note — blocks vs. native chrome.** A block is page *content*, rendered inside a shadow DOM. Softr's global **header / top bar / nav / dropdown menus** are native chrome (configured in Studio, rendered in the main document) — you **cannot** build or replace them as a block. To restyle them, add CSS to Settings → Custom Code → Code inside header. See [references/native-chrome-styling.md](references/native-chrome-styling.md).
24
+
23
25
  ## Your Workflow
24
26
 
25
27
  1. **Detect the brand source (always run first, before any block work).** Check if a `./DESIGN.md` file exists in the project folder you're about to work in.
@@ -155,6 +157,7 @@ For advanced patterns beyond data fetching, load the relevant reference when the
155
157
  | Small reusable patterns — `localStorage` cross-page state, clipboard copy button | [references/common-patterns.md](references/common-patterns.md) |
156
158
  | 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) |
157
159
  | AI-assisted Softr DB schema discovery / field-ID lookup / record reads via the official Softr MCP server (sibling to the in-block `useRecords` workflow) | [references/softr-database-mcp.md](references/softr-database-mcp.md) |
160
+ | Restyling Softr's **native header / top bar / nav / dropdown menus** (not a block — it's Softr chrome, done with global Custom Code CSS): stable selectors vs. hashed classes, the dropdown blank-space grid fix, restyle-vs-replace tradeoffs | [references/native-chrome-styling.md](references/native-chrome-styling.md) |
158
161
 
159
162
  ## Code Structure
160
163
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "softr-vibe-coding",
3
- "version": "1.5.2",
3
+ "version": "1.7.0",
4
4
  "description": "Claude Code skill for generating production-ready Softr Vibe Coding blocks (JSX). Installs into ~/.claude/skills/ and auto-updates on each Claude Code session.",
5
5
  "bin": {
6
6
  "softr-vibe-coding": "./bin/cli.js"
@@ -62,6 +62,9 @@ Run through this catalog before delivering any block. Every row is a violation o
62
62
  | Relying on `custom-code-header.html` (Softr → Settings → Custom Code → Code inside header) to apply brand fonts/colors INSIDE a Vibe Coding block | Vibe Coding blocks render inside a shadow DOM. CSS custom properties (`--brand-*`) pierce that boundary, but `html, body { font-family: ... !important }` rules **do not** — `<html>` and `<body>` don't exist inside the shadow root. Apply brand fonts/colors at the block's **own outermost wrapper** via inline style: `style={{ fontFamily: "'Manrope', system-ui, sans-serif", color: BRAND_INK }}` on the outer `<div>` so every descendant inherits brand defaults. Override per-element with explicit inline `fontFamily` (e.g., `"'Fraunces', Georgia, serif"` on h1/h2). Google `<link>` tags in the page head DO load `@font-face` globally — the fonts are available inside shadow DOM, they just need to be applied. |
63
63
  | Painting `backgroundColor: BRAND_CANVAS` on a Vibe Coding block's outer wrapper when `custom-code-header.html` already sets `body { background-color: var(--brand-canvas) !important }` | Don't double-paint. If the body bg is already the brand canvas, the block leaves its own backgroundColor unset and the page bg shows through. Painting the same color twice produces a visible seam — Softr's content wrapper sits between `<body>` and the Vibe Coding block, and the two backgrounds composite slightly differently due to sub-pixel rendering, transparency stacking, or wrapper paddings. Set fontFamily and color on the block's wrapper (those don't inherit cleanly through shadow DOM), but **leave backgroundColor unset** — let the page bg flow through. The exception: if the block needs a brand-tinted *section* (e.g., a card-style admin shell that's different from the page bg), paint that bg explicitly on its specific container, not on the outer wrapper. |
64
64
  | `document.getElementById(...)` / `document.querySelector(...)` to find an element inside the block — for example, a hidden `<input type="file">` triggered by a visible "Upload" button via `getElementById('myInput').click()` | Vibe Coding blocks render inside a shadow DOM. The global `document` traversal stops at the shadow boundary, so id/selector lookups for elements inside the block return `null`. The user-visible symptom is a control that does nothing — no error, no file picker, no focus, no scroll — because the chained `.click()` / `.focus()` / `.scrollIntoView()` was called on `null`. Use a **React `useRef`** instead: `var inputRef = useRef(null)`, then `<input ref={inputRef} />` and `<button onClick={function() { if (inputRef.current) inputRef.current.click(); }}>`. Refs hold direct node references and don't depend on DOM traversal, so they work regardless of which DOM tree the node lives in. This applies to every "trigger a hidden element" pattern: hidden file inputs, programmatic focus, scroll-into-view, `.click()` on a non-visible button. |
65
+ | Using `window.addEventListener("beforeunload", ...)` as the only unsaved-changes guard in a form block | Softr is a SPA. Internal nav (Softr's nav bar, sidebar links, `<NavigationAction>`) changes the route via the client-side router — `beforeunload` only fires on full page unload (tab close, refresh, external link), so the warning silently misses every in-app navigation. Use `useNavigationBlocker(isDirty)` from `@/lib/use-navigation-blocker` instead; it covers SPA nav AND browser unload with one API. Softr's Vibe Coding bundler often wires this automatically when a form is detected as dirty — you only need to add it manually for advanced cases (multi-step forms, custom dirty tracking, blocking on non-form state). See [common-patterns.md](common-patterns.md#navigation-blocker-for-unsaved-changes). |
66
+ | Targeting Softr's hashed build classes (e.g. `.f8f11e5_m9ntthp`) when restyling the native header/nav from `custom-code-header.html` | Softr regenerates the hash on every deploy, so the rule silently dies. Target stable hooks: `.softr-topbar`, `.softr-nav-link`, `.softr-nav-button`, `.softr-nav-logo`, `#topbar-root`; for dropdown menus (no `softr-*` class) use the Radix/ARIA attrs `[role="menu"]` / `[role="menuitem"]` / `[role="group"]` / `[aria-expanded="true"]`, scoped under `.softr-topbar`. The native header is Softr chrome (main document), not a block — it can't be built as a Vibe Coding block. See [native-chrome-styling.md](native-chrome-styling.md). |
67
+ | Softr nav dropdown panel shows a tall blank gap below the items, and `height: auto` won't shrink it | The items sit in a CSS grid Softr sets to `grid-auto-flow: column` with pre-sized empty row tracks (`grid-template-rows: 60px 60px…`). Override the flow on `.softr-topbar [role="menu"] [role="group"]`: `grid-auto-flow: row !important; grid-template-rows: none !important; grid-auto-rows: auto !important` (leave `grid-template-columns` to preserve the menu width). Verified June 2026. See [native-chrome-styling.md](native-chrome-styling.md). |
65
68
 
66
69
  ## Permissions
67
70
 
@@ -6,6 +6,7 @@ Small reusable patterns that come up across Vibe Coding blocks but don't warrant
6
6
 
7
7
  - [Cross-Page State with localStorage + URL Parameters](#cross-page-state-with-localstorage--url-parameters)
8
8
  - [Clipboard Copy Button](#clipboard-copy-button)
9
+ - [Navigation Blocker for Unsaved Changes](#navigation-blocker-for-unsaved-changes)
9
10
 
10
11
  ## Cross-Page State with localStorage + URL Parameters
11
12
 
@@ -100,3 +101,63 @@ Usage:
100
101
  ```
101
102
 
102
103
  The `aria-label` is required because the button has no visible text, only an icon. Without it the button is not screen-reader accessible.
104
+
105
+ ## Navigation Blocker for Unsaved Changes
106
+
107
+ Softr apps use SPA-mode client-side navigation — when a user clicks a link in Softr's nav bar, sidebar, or any `<NavigationAction>`, the route changes without a full page reload. The browser's standard `beforeunload` event only fires for tab close / refresh / browser back-forward / external nav, so the classic dirty-form warning misses every internal Softr click.
108
+
109
+ Softr's `useNavigationBlocker` hook intercepts BOTH internal SPA navigation AND browser-level unload with a single API. Import it from `@/lib/use-navigation-blocker`.
110
+
111
+ **Boolean form — simplest case:**
112
+
113
+ ```jsx
114
+ import { useState } from "react";
115
+ import { useNavigationBlocker } from "@/lib/use-navigation-blocker";
116
+
117
+ export default function Block() {
118
+ var [isDirty, setIsDirty] = useState(false);
119
+
120
+ useNavigationBlocker(isDirty);
121
+
122
+ function handleFieldChange(newValue) {
123
+ setIsDirty(true);
124
+ /* ... update form state ... */
125
+ }
126
+
127
+ /* ... form rendering ... */
128
+ }
129
+ ```
130
+
131
+ **Callback form — when you need to read a ref without re-running on every render:**
132
+
133
+ ```jsx
134
+ import { useRef } from "react";
135
+ import { useNavigationBlocker } from "@/lib/use-navigation-blocker";
136
+
137
+ export default function Block() {
138
+ var dirtyRef = useRef(false);
139
+
140
+ useNavigationBlocker(function() { return dirtyRef.current; });
141
+
142
+ function handleFieldChange() {
143
+ dirtyRef.current = true;
144
+ /* ... update local state without re-rendering the hook ... */
145
+ }
146
+
147
+ /* ... rest ... */
148
+ }
149
+ ```
150
+
151
+ The hook automatically handles:
152
+
153
+ - Browser's "Leave site?" dialog on tab close / refresh / external nav.
154
+ - Softr's in-app confirmation modal when the user clicks an internal Softr link or `<NavigationAction>`.
155
+ - Letting navigation through if the user confirms; cancelling if they decline.
156
+
157
+ **Most form blocks don't need to wire this manually** — Softr's Vibe Coding bundler often adds the blocker automatically when it detects form dirty state. You only need to add it explicitly for advanced cases:
158
+
159
+ - Multi-step forms where the dirty state spans several panels.
160
+ - Manual dirty tracking that doesn't go through standard form-state hooks.
161
+ - Blocks where you want to block on something other than form dirtiness (e.g., a pending background upload).
162
+
163
+ **Asking Softr to add the blocker automatically:** when generating or refining a form block in the Vibe Coding editor, you can prompt with "Block the navigation when the form is dirty" and Softr will wire `useNavigationBlocker` for you — useful when you don't want to write the import + hook call yourself.
@@ -0,0 +1,111 @@
1
+ # Styling Softr's Native Chrome (Header / Top Bar / Nav / Dropdowns)
2
+
3
+ **This is NOT about Vibe Coding blocks.** Softr's top bar, navigation, and its dropdown menus are *native chrome* — configured in Softr Studio and rendered in the **main document**, not inside a block's shadow DOM. You **cannot** build or replace the global header 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, i.e. the `custom-code-header.html` produced by `building-design-md`). Pure CSS — no markup, no JS — and the native bar stays in place, so Softr's auth-aware nav (account menu, sign-out, user-group gating) keeps working.
4
+
5
+ > **Mirror of the block rule.** Global `custom-code-header.html` CSS reaches native chrome (main document) but **not** blocks (shadow DOM). Inside a block you apply brand styles inline; for native chrome you apply them with this global CSS. (See [anti-patterns.md](anti-patterns.md) for the block side.)
6
+
7
+ ## Selector discipline — the #1 rule
8
+
9
+ Softr's rendered markup carries two kinds of classes:
10
+
11
+ - **Hashed build classes** like `f8f11e5_m9ntthp` — **NEVER target these.** Softr regenerates the hash on every deploy, so your rules silently die.
12
+ - **Stable hooks** — target these instead:
13
+
14
+ | Element | Stable selector |
15
+ |---|---|
16
+ | Sticky root wrapper | `#topbar-root` |
17
+ | The bar itself | `.softr-topbar` (also `[data-testid="topbar"]`) |
18
+ | Logo image | `.softr-nav-logo` |
19
+ | Nav links (Home, etc.) | `.softr-nav-link` |
20
+ | Nav buttons / dropdown triggers | `.softr-nav-button` |
21
+ | Overflow "…" trigger | `.softr-nav-category` |
22
+ | Active / current link | `.softr-nav-link[data-active="true"]` |
23
+ | Open dropdown trigger | `.softr-nav-button[aria-expanded="true"]` |
24
+
25
+ **Dropdown menus have NO `softr-*` class** — they're **Radix UI**, so target ARIA / Radix attributes (stable across deploys):
26
+
27
+ | Element | Stable selector |
28
+ |---|---|
29
+ | Menubar (the row of items) | `[role="menubar"]` |
30
+ | Open dropdown panel | `[role="menu"]` (+ `[data-state="open"]`, `[data-side="bottom"]`) |
31
+ | A menu item | `[role="menuitem"]` |
32
+ | Items group inside the panel | `[role="group"]` |
33
+ | Keyboard-highlighted item | `[role="menuitem"][data-highlighted]` |
34
+
35
+ Scope dropdown rules under `.softr-topbar` (Softr renders the header menu *inside* the nav) so they don't bleed into other Radix menus elsewhere in the app. Use `!important` + the `.softr-topbar` scope to beat Softr's own class rules.
36
+
37
+ ## Recipe: restyle the top bar
38
+
39
+ ```css
40
+ /* Bar surface */
41
+ .softr-topbar {
42
+ background-color: #02006C !important; /* your brand deep color */
43
+ border-bottom: 1px solid #1E1666 !important;
44
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25) !important;
45
+ }
46
+
47
+ /* Nav items: brand font, pill, your text color */
48
+ .softr-topbar .softr-nav-link,
49
+ .softr-topbar .softr-nav-button {
50
+ font-family: var(--brand-font-display) !important;
51
+ color: #FFFFFF !important;
52
+ border-radius: 9999px !important;
53
+ }
54
+
55
+ /* Icons (currentColor SVGs) + labels carry their OWN color — force them to follow the link.
56
+ Without this, labels render in Softr's default muted grey even after you set `color`. */
57
+ .softr-topbar .softr-nav-link *,
58
+ .softr-topbar .softr-nav-button * { color: inherit !important; }
59
+
60
+ /* Spacing — pills can end up touching; margin works regardless of the menubar's display type */
61
+ .softr-topbar [role="menubar"] .softr-nav-link,
62
+ .softr-topbar [role="menubar"] .softr-nav-button { margin: 0 4px !important; }
63
+
64
+ /* Single out ONE item as a CTA by its href (the only stable way to target one nav item) */
65
+ .softr-topbar .softr-nav-link[href*="your-form-host"] {
66
+ background: #9B23D0 !important;
67
+ color: #FFFFFF !important;
68
+ box-shadow: var(--brand-shadow-cta-glow) !important;
69
+ }
70
+ ```
71
+
72
+ ### Gotchas (verified June 2026)
73
+
74
+ - **Nav font defaults to Inter.** Your brand `@font-face`/`<link>` loads globally, but the bar's `font-family` is set on Softr's classes — you must target `.softr-nav-link` / `.softr-nav-button` to change it.
75
+ - **Icon + label color** comes from Softr's classes, so a plain `color:` on the link often doesn't take — use the `* { color: inherit !important }` trick above (SVGs use `currentColor`, so they follow too).
76
+ - **Custom code renders on the PUBLISHED app only — not in the Studio editor.** The header looks unchanged in the builder; always verify on the live app.
77
+ - **Account avatar on a dark bar:** Softr's logged-in account button can blend into a dark bar — give it a contrasting ring if you darken the surface.
78
+
79
+ ## Gotcha: dropdown panel has a tall blank gap below the items
80
+
81
+ Softr lays dropdown items in a **CSS grid** with `grid-auto-flow: column` and a **fixed set of pre-sized row tracks** (`grid-template-rows: 60px 60px 60px…`). With only 2–3 items the extra rows stay empty → a tall panel with dead space. **`height: auto` does NOT fix it** — the grid template defines those tracks. Override the flow instead:
82
+
83
+ ```css
84
+ .softr-topbar [role="menu"],
85
+ .softr-topbar [role="menu"] > div,
86
+ .softr-topbar [role="menu"] [role="group"] {
87
+ height: auto !important;
88
+ min-height: 0 !important;
89
+ grid-auto-flow: row !important; /* one item per row */
90
+ grid-template-rows: none !important; /* drop the reserved empty tracks */
91
+ grid-auto-rows: auto !important;
92
+ }
93
+ /* leave grid-template-columns alone — it sets the menu width */
94
+ ```
95
+
96
+ Center each item's text and drop the empty description slot Softr reserves:
97
+
98
+ ```css
99
+ .softr-topbar [role="menu"] [role="menuitem"] { display: flex !important; align-items: center !important; }
100
+ .softr-topbar [role="menu"] [role="menuitem"] div:empty { display: none !important; }
101
+ ```
102
+
103
+ ## Finding the element to target
104
+
105
+ DevTools can't right-click a menu that closes on blur. Freeze it: in the Console run `setTimeout(function () { debugger; }, 4000)`, open the menu within 4s, then — paused — element-pick the panel and read the **Computed** tab to see which element holds a fixed `height` / `grid-template-rows`. Resume with the ▶ button. ("Emulate a focused page" in the Elements `:hov` menu is a lighter alternative for blur-close menus.)
106
+
107
+ ## Restyle vs. replace
108
+
109
+ **Restyle the native bar (recommended):** robust, global, keeps Softr's auth-aware nav (account menu, user-group-gated items) and stays editable in Studio.
110
+
111
+ **Replace it** (hide `#topbar-root`, inject a fully custom HTML/JS header globally): only if you need structure the native nav can't do — e.g. multi-column mega-menus with icon cards. It's **fragile**: you lose Softr's logged-in account menu + user-group gating, you must re-init the JS on every SPA route change (Softr swaps pages without a full reload), and the custom header won't render in the Studio editor. Steer users to restyle unless the structure genuinely requires replacement.
@@ -18,6 +18,9 @@ import { useTextSetting, useImageSetting, useVideoSetting, useArraySetting,
18
18
  useVibeCodingBlockIconSetting, useNavigationSetting,
19
19
  useBooleanSetting } from "@/lib/editable-settings";
20
20
 
21
+ // NAVIGATION GUARD (form unsaved-changes warning that also works on Softr's SPA nav)
22
+ import { useNavigationBlocker } from "@/lib/use-navigation-blocker";
23
+
21
24
  // REACT
22
25
  import { useState, useEffect, useMemo, useCallback, useRef } from "react";
23
26
 
@@ -185,6 +188,18 @@ useEffect(function() {
185
188
  }, [result.hasNextPage, result.isFetchingNextPage, result.status, result.fetchNextPage]);
186
189
  ```
187
190
 
191
+ ## Navigation Blocker (unsaved-changes warning)
192
+
193
+ ```jsx
194
+ // Boolean form: simplest case
195
+ useNavigationBlocker(isDirty);
196
+
197
+ // Callback form: reads from a ref without re-running on every render
198
+ useNavigationBlocker(function() { return dirtyRef.current; });
199
+ ```
200
+
201
+ Catches BOTH Softr's in-app SPA navigation (nav bar, sidebar, `<NavigationAction>`) AND browser unload (tab close, refresh, external links). A plain `window.addEventListener("beforeunload", ...)` does NOT catch Softr's in-app nav. See [common-patterns.md](common-patterns.md#navigation-blocker-for-unsaved-changes).
202
+
188
203
  ## Component Skeleton
189
204
 
190
205
  ```jsx