softr-vibe-coding 2.8.0 → 2.8.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 CHANGED
@@ -4,6 +4,10 @@ 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.8.1] - 2026-09-10
8
+ - Verifying a push: errors:null is not proof — fetch-back byte-compare, deployed==disk pre-check, two-block swaps (2.8.1)
9
+ - Inventory + keyboard-picker snippet reconciled with the shipped projects-table shape
10
+
7
11
  ## [2.8.0] - 2026-09-09
8
12
  - Dropdowns searchable by default, reset controls only when dirty, create → open the new record
9
13
 
package/README.md CHANGED
@@ -185,7 +185,7 @@ softr-vibe-coding/
185
185
  │ │ # Scripting Extension, cross-table cascades,
186
186
  │ │ # batch update gotchas, field-ID discipline,
187
187
  │ │ # Airtable formulas
188
- │ ├── softr-mcp.md # Official Softr MCP server — vibe coding block
188
+ │ ├── softr-mcp.md # Official Softr MCP server — vibe coding block (+ push verification protocol)
189
189
  │ │ # tools (create/edit/version/deploy), integrations
190
190
  │ │ # browsing (Airtable/Sheets/Notion/Supabase),
191
191
  │ │ # Softr DB schema + record tools incl. deletes,
@@ -204,7 +204,7 @@ softr-vibe-coding/
204
204
  │ ├── anti-patterns.md # Categorized violation catalog
205
205
  │ │ # Data access, mutations, hooks, layout,
206
206
  │ │ # permissions, editable settings, helper blocks
207
- │ ├── common-patterns.md # Small reusable patterns
207
+ │ ├── common-patterns.md # Small reusable patterns (localStorage state, clipboard, nav blocker, drag-to-reorder, create → open, clickable row + inner link, keyboard picker)
208
208
  │ │ # localStorage cross-page state, clipboard copy,
209
209
  │ │ # navigation blocker, scroll-condensing header,
210
210
  │ │ # auth-aware CTA, image masks, blobs, dot lists
package/SKILL.md CHANGED
@@ -89,6 +89,7 @@ You generate complete, production-ready Softr Vibe Coding blocks as TypeScript R
89
89
  - 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))
90
90
  - Array-setting rows keyed by **index**, never by a builder-editable field value
91
91
  - Media settings that may start empty (`src: ""`) gated with a conditional render or placeholder — never an unconditional `<img src={setting.src}>`
92
+ - **Deploying through the MCP:** `errors: null` on a push is not proof — fetch the block's `sourceCode` back and byte-compare it to the file you sent (trailing newline normalised, nothing else), and prove deployed == disk *before* editing so a Studio-side change is never overwritten. Protocol in [references/softr-mcp.md → Verifying a push](references/softr-mcp.md#verifying-a-push--the-deployed-source-is-the-only-proof)
92
93
 
93
94
  ## What to Clarify
94
95
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "softr-vibe-coding",
3
- "version": "2.8.0",
3
+ "version": "2.8.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"
@@ -513,4 +513,6 @@ function onKeyDown(e) {
513
513
  <input autoFocus value={query} onChange={function (e) { setQuery(e.target.value); setActive(0); }} onKeyDown={onKeyDown} … />
514
514
  ```
515
515
 
516
+ Make the rows themselves focusable too (`tabIndex={0}`, an `onKeyDown` that opens on Enter only when `event.target === event.currentTarget`, so an Enter on the anchor inside the row is not handled twice), and let `onMouseEnter` *and* `onFocus` both move the highlight onto the row — the highlight is the single answer to "which record does Enter open", whichever device last touched it. That is the shape `projects-table.jsx` shipped on 2026-09-10.
517
+
516
518
  Paint the highlighted row with the same colour the mouse hover gets (`data-active="true"` + `bg-[#FFF7EF]`) and scroll it into view when it moves (`querySelector('[data-active="true"]').scrollIntoView({ block: "nearest" })` in a `useEffect` on `activeIdx`) — the Combo in [searchable-dropdown.md](searchable-dropdown.md#one-flat-row-list-for-the-keyboard) does the same. Reset `active` to 0 whenever the query changes: the old index points at a row that may no longer be in the list. `autoFocus` is right only when the block *is* the page's reason to exist — an index page whose first act is always a search; on a page with content above the table, a focus steal scrolls the page to the box.
@@ -101,6 +101,57 @@ components, adding a hook — where being sure of "the exact current text" of a
101
101
  is harder than being sure of the whole file. Also use it when the local file is the source of truth and
102
102
  has drifted from the deployed block in ways you have not enumerated.
103
103
 
104
+ ### Verifying a push — the deployed source is the only proof
105
+
106
+ `update_vibe_coding_block_code` returning `errors: null, warnings: null` proves the code **compiled**.
107
+ It does not prove the block now holds the code you meant to send. Verified 2026-09-09: a push of a
108
+ 67KB block came back clean and had silently dropped one blank line at a read-chunk boundary — valid
109
+ JavaScript, so the compiler had nothing to say. Only a byte comparison caught it. Treat every push as
110
+ unverified until you have pulled the source back down and compared it.
111
+
112
+ **The protocol, per block:**
113
+
114
+ 1. **Before editing, prove deployed == disk.** Call `get_vibe_coding_block_code`, extract
115
+ `sourceCode`, and compare it byte-for-byte to your local mirror. If they differ, someone changed
116
+ the block in Studio since your last push — stop and reconcile; do not overwrite work you have not
117
+ seen. (Large results are persisted to a file by most clients rather than returned inline; compare
118
+ from that file with a script, never by eye.)
119
+ 2. Edit the local file. Run a parser and `no-undef` lint on it first — `node --check` does **not**
120
+ accept a `.jsx` extension, so use esbuild (`esbuild file.jsx --loader:.jsx=jsx --jsx=automatic
121
+ --log-level=error --outfile=/dev/null`) plus eslint with `@babel/eslint-parser`. The bugs that
122
+ actually bite Softr blocks are semantic — `useRecordUpdate({ select: … })` instead of `fields:`,
123
+ an invented identifier — and the push is the first thing that reports them.
124
+ 3. Push the **entire** file.
125
+ 4. **Fetch it back and compare again.** Identical, or you are not done: diff, fix, re-push.
126
+
127
+ **Tolerate exactly one difference: the trailing newline.** Softr sometimes strips the file's final
128
+ `\n` on save and sometimes keeps it — stripped on every push on 2026-09-09, kept on every push on
129
+ 2026-09-10, same app, same tools. A comparison that demands byte equality will report a phantom
130
+ mismatch on some days; one that ignores *all* whitespace will miss the dropped-blank-line case above.
131
+ Compare with the trailing newline normalised and nothing else.
132
+
133
+ **One file, two blocks, two datasource pairs.** When the same source is deployed to two pages, the
134
+ local file holds ONE page's `datasource.define()` pair. Push it as-is to that block; for the other,
135
+ build the swapped text in a scratch location, push that, and verify each block against its own
136
+ expectation (disk for the first, disk-with-swap for the second). Never save the swapped copy over
137
+ the local mirror — the mirror records which page it belongs to, and the block's header comment
138
+ records the other page's pair. Search-replace would avoid the swap altogether
139
+ ([above](#which-edit-tool-full-replace-vs-targeted-search-replace)) — when the client can send its
140
+ array argument ([below](#the-array-argument-serialization-quirk-and-why-it-is-a-security-issue)).
141
+
142
+ **Do not read a 100KB block into a model's context to push it.** The full-replace tool takes the
143
+ whole file as a string parameter, so the source has to pass through whatever is making the call. A
144
+ large multi-block deploy is safer farmed out one file per subagent — a fresh context per file means
145
+ no compaction can land mid-file — and the byte comparison is what makes that delegation safe, not
146
+ trust in the agent. The steps that need judgement are the *edit* and the *review of the diff*; the
147
+ fetch, the compare and the push itself are mechanical, and can run on the cheapest tier available
148
+ without lowering the bar, because a wrong result fails loudly rather than plausibly.
149
+
150
+ **What a push also resets.** Every code push puts the block's derived Actions back on Softr's
151
+ default permissions (see the next section for why that can be a security problem and how to verify
152
+ the restoration). If page-level visibility is the access control in your app, record that decision
153
+ so nobody chases the reset after every round; if it is not, re-tighten and read back.
154
+
104
155
  ### The array-argument serialization quirk, and why it is a security issue
105
156
 
106
157
  **Several tools on this server take an array argument, and some MCP clients serialize it as a JSON