softr-vibe-coding 1.5.2 → 1.6.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,9 @@ 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.6.0] - 2026-06-03
8
+ - Document useNavigationBlocker for form-dirty navigation guards in Softr SPA mode
9
+
7
10
  ## [1.5.2] - 2026-05-27
8
11
  - 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
12
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "softr-vibe-coding",
3
- "version": "1.5.2",
3
+ "version": "1.6.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,7 @@ 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). |
65
66
 
66
67
  ## Permissions
67
68
 
@@ -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.
@@ -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