softr-vibe-coding 2.11.1 → 2.12.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
+ ## [2.12.0] - 2026-10-01
8
+ - Add references/browser-checks.md: checking a pushed block in a browser with agent-browser (verified 2026-10-01)
9
+
7
10
  ## [2.11.1] - 2026-10-01
8
11
  - Correct four 2.11.0 statements after an independent fact-check of the transcripts
9
12
 
package/README.md CHANGED
@@ -197,6 +197,12 @@ softr-vibe-coding/
197
197
  │ │ # (Sep 18 2026); Oct 1 2026: tool rename map,
198
198
  │ │ # push verification by sourceSha256, stub tools
199
199
  │ │ # after a resume, update_field/update_table fixes
200
+ │ ├── browser-checks.md # Checking a pushed block in a browser with
201
+ │ │ # the agent-browser CLI (ask before installing):
202
+ │ │ # preview cookie, shadow-DOM refs grepped in the
203
+ │ │ # shell, eval measurements, the records-trigger
204
+ │ │ # write guard proven before any click, what a
205
+ │ │ # click sent, screenshots to disk (Oct 1 2026)
200
206
  │ ├── advanced-integrations.md # Shadow DOM CSS isolation
201
207
  │ │ # Leaflet, Mapbox, TinyMCE, Quill, FullCalendar
202
208
  │ ├── native-chrome-styling.md # Restyle Softr's native shell (header, footer,
package/SKILL.md CHANGED
@@ -96,6 +96,7 @@ You generate complete, production-ready Softr Vibe Coding blocks as TypeScript R
96
96
  - Array-setting rows keyed by **index**, never by a builder-editable field value
97
97
  - Media settings that may start empty (`src: ""`) gated with a conditional render or placeholder — never an unconditional `<img src={setting.src}>`
98
98
  - **Deploying through the MCP:** `errors: null` on a push is not proof — compare the push result's `sourceSha256` with `shasum -a 256` of the file you sent, every byte counted, trailing newline included (Softr stores exactly what it receives; the one-byte drift we once blamed on it was a chunked read on our side). Prove deployed == disk *before* editing the same way, with `vibe_coding_block_get_code` and `includeCode: false`, so a Studio-side change is never overwritten. Fetch the full `sourceCode` only when a digest is missing or the hashes differ. Protocol in [references/softr-mcp.md → Verifying a push](references/softr-mcp.md#verifying-a-push--the-deployed-source-is-the-only-proof)
99
+ - **Then check it in a browser.** Once the push checks pass, check rendering and behaviour in a fresh preview with saves blocked, per [references/browser-checks.md](references/browser-checks.md)
99
100
 
100
101
  ## What to Clarify
101
102
 
@@ -180,6 +181,7 @@ For advanced patterns beyond data fetching, load the relevant reference when the
180
181
  | Small reusable patterns — `localStorage` cross-page state, clipboard copy button | [references/common-patterns.md](references/common-patterns.md) |
181
182
  | 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) |
182
183
  | 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) |
183
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) |
184
186
  | 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) |
185
187
  | **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) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "softr-vibe-coding",
3
- "version": "2.11.1",
3
+ "version": "2.12.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"
@@ -0,0 +1,199 @@
1
+ # Checking a pushed block in a browser
2
+
3
+ How to check a deployed block's rendering and behaviour in a Softr preview with the
4
+ [agent-browser](https://github.com/vercel-labs/agent-browser) CLI. **Verified 2026-10-01** with
5
+ agent-browser v0.38.1 on macOS (Node 22) against a real Softr preview; only the commands under
6
+ [Untested but promising](#untested-but-promising) were not run.
7
+
8
+ ## When to use it
9
+
10
+ After a push has passed the hash check in
11
+ [softr-mcp.md → Verifying a push](softr-mcp.md#verifying-a-push--the-deployed-source-is-the-only-proof).
12
+ The hash proves what Softr stored; a browser shows what it does: the layout at a given width, what
13
+ a control does, what a Save would send. **Not for data checks** (read records through the MCP or the
14
+ API), and **not for logged-in Studio or Airtable work**, which needs the user's own session and so
15
+ belongs to the user's own Chrome tool.
16
+
17
+ ## Tool choice and why
18
+
19
+ **agent-browser**, because a check costs fewer tokens and can be made safe:
20
+
21
+ - One browser stays alive between shell commands (a daemon per `--session`), so a check is a few
22
+ short commands rather than one script.
23
+ - `eval` prints only its result. The Playwright MCP's `browser_run_code_unsafe` repeats the whole
24
+ script back in every result, under "### Ran Playwright code".
25
+ - Screenshots go to disk and cost nothing until someone opens one.
26
+ - It aborts requests by URL pattern and lists what a click sent: the write guard below.
27
+
28
+ An in-app or embedded browser pane stops rendering while it is hidden: IntersectionObserver and
29
+ `requestAnimationFrame` never fire, and screenshots time out. It can check scroll- or
30
+ visibility-driven behaviour only while it is visibly open.
31
+
32
+ ## Install
33
+
34
+ Check with `agent-browser --version`. If it is missing, **ask the user before installing**: it is a
35
+ global npm package plus a Chrome for Testing download (182 MB, about 360 MB on disk under
36
+ `~/.agent-browser`). On a yes:
37
+
38
+ ```bash
39
+ npm i -g agent-browser && agent-browser install
40
+ agent-browser doctor # passed every check; a headless launch took about 0.9 s
41
+ ```
42
+
43
+ npm may warn `EBADENGINE`, asking for Node ≥ 24. It ran fine on Node 22: that requirement is for
44
+ building from source, and the CLI is a native binary. If the user declines, drive a headless Chrome
45
+ with Playwright, or use an in-app browser only while it is visibly open.
46
+
47
+ ## Why not the skills.sh `agent-browser` skill
48
+
49
+ vercel-labs also publish an `agent-browser` skill on skills.sh. Do not install it. Its description
50
+ tells the agent to prefer it over every built-in browser tool, and it triggers on generic requests,
51
+ even Slack ones. Yet it is only a stub that loads `agent-browser skills get core` (about 38 KB, some
52
+ 10k tokens). The CLI serves that guide on demand, matched to the installed version: run
53
+ `agent-browser skills get core` yourself, and only when this recipe is not enough.
54
+
55
+ ## The recipe
56
+
57
+ ### 1. Session, preview cookie, page
58
+
59
+ Work from a scratch directory, not the project: nothing is written to the working folder, and
60
+ screenshots go where you tell them.
61
+
62
+ ```bash
63
+ ab() { agent-browser --session softr-check "$@"; } # a function, not a variable: see Gotchas
64
+ ab open '<previewUrl>' >/dev/null # once per session: sets the preview cookie
65
+ ab set viewport 1280 900
66
+ ab open 'https://<subdomain>.preview.softr.app/<page>?recordId=<recordId>&autoUser=true'
67
+ ab wait --load networkidle # works on Softr previews
68
+ ab wait 2500 # 2000–3000 ms more, so the block's data hooks can load
69
+ ```
70
+
71
+ `<previewUrl>` is what the MCP's `application_preview` returns. `open` prints the URL it opened, and
72
+ this one carries a sign-in token, hence `/dev/null`. The direct URL then loads the app itself, not
73
+ the toolbar shell that frames it, so `document` in `eval` is the app's.
74
+
75
+ ### 2. Reaching into the block
76
+
77
+ A block renders inside a shadow root, which CSS selectors and `find` locators do not cross.
78
+ **Refs from the accessibility readout do**: `ab snapshot -i` lists the block's textboxes, buttons
79
+ and checkboxes as `[ref=eN]`, and `ab fill @eN '…'` and `ab click @eN` act inside the block. Grep
80
+ the ref out in the same shell call, so the readout never enters your context:
81
+
82
+ ```bash
83
+ REF=$(ab snapshot -i | grep -o 'textbox "Search by[^[]*\[ref=e[0-9]*' | head -1 | grep -o 'e[0-9]*$'); ab fill "@$REF" 'term'
84
+ ```
85
+
86
+ ### 3. Measuring with `eval`
87
+
88
+ `ab eval "<expr>"`, or `ab eval --stdin < check.js` for anything longer. An async IIFE is awaited,
89
+ and only the result is printed: return `JSON.stringify(...)` to get one JSON-encoded line. Find the
90
+ block's shadow root by text only that block contains:
91
+
92
+ ```js
93
+ (async () => {
94
+ const root = [...document.querySelectorAll('*')].map(e => e.shadowRoot).filter(Boolean)
95
+ .find(s => /<text unique to the block>/.test(s.textContent));
96
+ if (!root) return 'block not found';
97
+ const r = root.querySelector('<selector>').getBoundingClientRect();
98
+ return JSON.stringify({ left: r.left, right: innerWidth - r.right, top: r.top, width: r.width });
99
+ })()
100
+ ```
101
+
102
+ A date input has no ref (role `Date` in the full readout, absent from `-i`) and is React-controlled:
103
+ set it through the native setter, then fire both events, inside the IIFE once `root` is found.
104
+
105
+ ```js
106
+ const i = root.querySelector('input[type="date"]');
107
+ const set = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set;
108
+ set.call(i, '2026-10-15');
109
+ i.dispatchEvent(new Event('input', { bubbles: true }));
110
+ i.dispatchEvent(new Event('change', { bubbles: true }));
111
+ ```
112
+
113
+ ### 4. Block saves before any click, and prove it
114
+
115
+ The preview writes to the live data
116
+ ([softr-mcp.md](softr-mcp.md#testing-as-any-app-user-without-logins--the-preview-as-switcher)), so
117
+ before a check clicks anything that could save:
118
+
119
+ ```bash
120
+ ab network route '*records-trigger*' --abort
121
+ ab eval "fetch('/records-trigger-probe-' + Date.now()).then(r => 'NOT BLOCKED ' + r.status).catch(e => 'blocked: ' + e.message)"
122
+ # blocked: Failed to fetch
123
+ ab eval "fetch('/plain-probe-' + Date.now()).then(r => 'reached ' + r.status).catch(e => 'blocked: ' + e.message)"
124
+ # reached 404 (the control: other requests still get through)
125
+ ```
126
+
127
+ A `useRecordUpdate` save goes out as a PATCH, with the record ID in the path (see Gotchas):
128
+
129
+ ```
130
+ PATCH https://<subdomain>.preview.softr.app/v1/datasource/applications/<app>/pages/<page>/blocks/<block>/datasources/<ds>/records-trigger/<recordId>
131
+ ```
132
+
133
+ Only this update endpoint is verified. Before clicking a create or delete control, learn its URL
134
+ with `ab network requests` where a write is harmless, never on client data, and block that pattern
135
+ too. Do not assume `*records-trigger*` covers it.
136
+
137
+ ### 5. Click, then read what it sent
138
+
139
+ Read the record through the database API, click Save by its ref (step 2), then read the record
140
+ again: `updatedAt` and the field should be unchanged. The aborted request is still logged, so you
141
+ see the payload without it reaching the server:
142
+
143
+ ```bash
144
+ ab network requests --filter records-trigger # lists the save, with its id
145
+ ab network request <id> --json # method: PATCH, postData: {"context":{…},"fields":{"<fieldId>":"2026-10-15"}}
146
+ ```
147
+
148
+ ### 6. Screenshots and cleanup
149
+
150
+ ```bash
151
+ ab screenshot ./empty-1280.png # ✓ Screenshot saved to … (--full for the whole page)
152
+ ab network unroute
153
+ ab close # ✓ Browser closed (no process left behind)
154
+ ```
155
+
156
+ Give a path; without one, it writes to a temp directory. A saved screenshot costs no tokens until
157
+ someone opens it; one shown inline costs about 1.5k. Left alone, the daemon exits after an hour idle.
158
+
159
+ ## Gotchas
160
+
161
+ - **zsh does not word-split.** `AB="agent-browser --session x"; $AB open …` fails with "command not
162
+ found": use the function. Agent shells usually keep no functions or variables between calls, so
163
+ define `ab`, and grep any ref, in the call that uses them; the browser lives on in the daemon.
164
+ - **Selectors stop at the shadow root.** `ab fill 'input[placeholder^="…"]' 'x'` gives
165
+ `✗ Element not found`, and `find` locators fail the same way (upstream issue
166
+ vercel-labs/agent-browser#1266, open since April 2026). Use refs, or `eval`.
167
+ - **Refs change on every page load.** Grep again after each `open` or reload.
168
+ - **Readouts are huge on data pages.** A table-heavy page measured 268 KB in full, 197 KB with `-c`
169
+ and 164 KB with `-i` (or `-i -c`), about 40k tokens: `-i` keeps table cells as context for the
170
+ buttons in them. A small page was 5.8 KB, 2.9 KB with `-i`. Never print one on a data page: grep
171
+ it, or write it to a file.
172
+ - **Saves are PATCH, and the record ID is in the path.** A guard on POST misses them, and so does one
173
+ that looks for the record ID in the body; that one once let a write through. Match the URL, never
174
+ the method or the body.
175
+ - **Plain `ab network request <id>` printed only the URL** of the blocked save. Add `--json`.
176
+ - **A preview serves the version it was minted on.** After every push, mint a fresh one with
177
+ `application_preview` and open it again before checking anything.
178
+ - **The preview URL is a sign-in token.** Never share it ([why](softr-mcp.md#application-management-tools)).
179
+ - **Attachment URLs are re-signed on every read:** compare by id, filename and size, never by URL.
180
+
181
+ ## Measured
182
+
183
+ The same check, an empty-state centring check at two widths plus a screenshot, cost about 650 tokens
184
+ with agent-browser (2026-10-01) against about 1,540 with the Playwright MCP's run-code
185
+ (2026-09-30): about 58% less, mostly because Playwright repeats the script back. Learning the tool
186
+ in a fresh session cost about 1.6k tokens, once, without loading `skills get core`.
187
+
188
+ ## Untested but promising
189
+
190
+ In agent-browser's docs, **not yet tried against a Softr preview**. Try one before relying on it:
191
+
192
+ - `ab pdf <path>`: to check a print layout ([printing.md](printing.md#7-gotchas-and-testing) has
193
+ the verified Playwright route).
194
+ - `--init-script <path>` (before the first navigation) or `ab addinitscript <js>` (at runtime): for
195
+ example, to stub `window.print` before load.
196
+ - `ab screenshot --if-changed`: skips a screenshot that matches the last one.
197
+ - `ab diff snapshot`: compares the current readout with the last one.
198
+ - `ab a11y`: the built-in axe-core accessibility audit.
199
+ - `--allowed-domains <list>`: restricts the session's network to the domains listed.
@@ -293,6 +293,8 @@ definition says, check that `sourceCode` came back `null`.
293
293
  result to a file rather than returning it inline, so compare from that file with a script, never
294
294
  by eye.
295
295
 
296
+ Matching hashes prove what Softr stored, not how the block behaves: for that, check it in a fresh preview with saves blocked, per [browser-checks.md](browser-checks.md).
297
+
296
298
  **Hash the exact bytes, trailing newline included.** Softr stores exactly what it receives: across
297
299
  112 push→fetch pairs between 2026-09-09 and 2026-09-30 the fetched
298
300
  `sourceCode` was byte- and MD5-identical to the text sent, including two pushes sent *without* a