softr-vibe-coding 2.8.0 → 2.8.2
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 +7 -0
- package/README.md +2 -2
- package/SKILL.md +1 -0
- package/package.json +1 -1
- package/references/common-patterns.md +2 -0
- package/references/softr-mcp.md +77 -18
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,13 @@ 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.2] - 2026-09-10
|
|
8
|
+
- Corrections on evidence: Softr never strips the trailing newline; the array-argument rejection was client-side stringification, not an empty server schema (2.8.2)
|
|
9
|
+
|
|
10
|
+
## [2.8.1] - 2026-09-10
|
|
11
|
+
- Verifying a push: errors:null is not proof — fetch-back byte-compare, deployed==disk pre-check, two-block swaps (2.8.1)
|
|
12
|
+
- Inventory + keyboard-picker snippet reconciled with the shipped projects-table shape
|
|
13
|
+
|
|
7
14
|
## [2.8.0] - 2026-09-09
|
|
8
15
|
- Dropdowns searchable by default, reset controls only when dirty, create → open the new record
|
|
9
16
|
|
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 included (Softr stores exactly what it receives; the one-byte drift we once blamed on it was a chunked read on our side), 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.
|
|
3
|
+
"version": "2.8.2",
|
|
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.
|
package/references/softr-mcp.md
CHANGED
|
@@ -101,35 +101,94 @@ 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
|
-
###
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
+
**Compare byte for byte, trailing newline included.** Softr stores exactly what it receives: across
|
|
128
|
+
58 push→fetch pairs between 2026-08-26 and 2026-09-10 (14 blocks, 16–161 KB each) the fetched
|
|
129
|
+
`sourceCode` was byte- and MD5-identical to the text sent, including two pushes sent *without* a
|
|
130
|
+
final newline and stored without one. The "deployed block is one byte shorter" we chased on
|
|
131
|
+
2026-09-09 was our own read: an agent that reads a large file in chunks can drop the final
|
|
132
|
+
newline (or a blank line at a chunk boundary) before transmission. A comparison that normalises
|
|
133
|
+
the trailing newline hides exactly that class of error — so do not normalise anything; a mismatch
|
|
134
|
+
means re-send, whatever the byte.
|
|
135
|
+
|
|
136
|
+
**One file, two blocks, two datasource pairs.** When the same source is deployed to two pages, the
|
|
137
|
+
local file holds ONE page's `datasource.define()` pair. Push it as-is to that block; for the other,
|
|
138
|
+
build the swapped text in a scratch location, push that, and verify each block against its own
|
|
139
|
+
expectation (disk for the first, disk-with-swap for the second). Never save the swapped copy over
|
|
140
|
+
the local mirror — the mirror records which page it belongs to, and the block's header comment
|
|
141
|
+
records the other page's pair. Search-replace would avoid the swap altogether
|
|
142
|
+
([above](#which-edit-tool-full-replace-vs-targeted-search-replace)) — when the client can send its
|
|
143
|
+
array argument ([below](#the-array-argument-serialization-quirk-and-why-it-is-a-security-issue)).
|
|
144
|
+
|
|
145
|
+
**Do not read a 100KB block into a model's context to push it.** The full-replace tool takes the
|
|
146
|
+
whole file as a string parameter, so the source has to pass through whatever is making the call. A
|
|
147
|
+
large multi-block deploy is safer farmed out one file per subagent — a fresh context per file means
|
|
148
|
+
no compaction can land mid-file — and the byte comparison is what makes that delegation safe, not
|
|
149
|
+
trust in the agent. The steps that need judgement are the *edit* and the *review of the diff*; the
|
|
150
|
+
fetch, the compare and the push itself are mechanical, and can run on the cheapest tier available
|
|
151
|
+
without lowering the bar, because a wrong result fails loudly rather than plausibly.
|
|
152
|
+
|
|
153
|
+
**What a push also resets.** Every code push puts the block's derived Actions back on Softr's
|
|
154
|
+
default permissions (see the next section for why that can be a security problem and how to verify
|
|
155
|
+
the restoration). If page-level visibility is the access control in your app, record that decision
|
|
156
|
+
so nobody chases the reset after every round; if it is not, re-tighten and read back.
|
|
157
|
+
|
|
158
|
+
### The array-argument rejection, and why it is a security issue
|
|
159
|
+
|
|
160
|
+
**Several workspace-server tools take an array argument, and a call that sends it as a JSON *string*
|
|
161
|
+
is rejected** by Jackson before it reaches any business logic:
|
|
108
162
|
|
|
109
163
|
```
|
|
110
164
|
Cannot deserialize value of type `java.util.ArrayList<java.util.Map<String,Object>>`
|
|
111
165
|
from String value (token `JsonToken.VALUE_STRING`)
|
|
112
166
|
```
|
|
113
167
|
|
|
114
|
-
Root cause: the server advertises an **empty schema** for its tools (`{"type":"object"}`, no property
|
|
115
|
-
definitions), so a client has no type information to serialize against. Confirmed 2026-09-09 on:
|
|
116
|
-
|
|
117
168
|
| Tool | Array argument | Fallback if it fails |
|
|
118
169
|
|---|---|---|
|
|
119
170
|
| `update_vibe_coding_block_code_search_replace` | `operations` | Use `update_vibe_coding_block_code` (full replace) |
|
|
120
171
|
| `set_vibe_coding_block_action_visibility` | `updates` | **NONE — a human must fix it in Studio** |
|
|
121
172
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
173
|
+
**Where the string comes from — corrected 2026-09-10.** The first write-up of this (2026-09-09)
|
|
174
|
+
blamed the server for advertising an empty schema. The transcripts say otherwise: the workspace
|
|
175
|
+
server's schema declares both parameters as `type: array`, all 13 rejected calls had sent a JSON
|
|
176
|
+
string, and all 84 successful calls to the same two tools had sent a real array — same day, same
|
|
177
|
+
shapes, different payload type. The stringification happened on the client side, most likely on
|
|
178
|
+
calls made while the tool definitions had not been loaded into the model's context (deferred
|
|
179
|
+
schemas), so there was no type to serialise against. **Load the tool's schema before calling it,
|
|
180
|
+
and pass arrays as arrays.** (Empty schemas are real on Softr's *per-application* MCP servers —
|
|
181
|
+
every tool there is advertised as `{"type":"object"}` with a name-only description — but the
|
|
182
|
+
workspace server is not affected.)
|
|
183
|
+
|
|
184
|
+
**Why the second row is a security problem, not an inconvenience.** Every code push resets the
|
|
185
|
+
block's auto-registered Actions to Softr's defaults, and the default for a `genericActions`
|
|
186
|
+
**ADD_RECORD is `ALL_USERS`** — writable by logged-OUT visitors — while UPDATE_RECORD and
|
|
187
|
+
DELETE_RECORD default to `LOGGED_IN_USERS` in the same response. The documented remedy is to
|
|
188
|
+
re-tighten with `set_vibe_coding_block_action_visibility`. When that call is the one that fails, a
|
|
189
|
+
routine cosmetic push silently leaves public write access on the block, and nothing in the push
|
|
190
|
+
result says so: the push itself returns `errors: null, warnings: null`. Verified live 2026-09-09 —
|
|
191
|
+
one push left four ADD_RECORD actions open across two blocks.
|
|
133
192
|
|
|
134
193
|
**So treat permission restoration as a step that must be VERIFIED, never assumed:**
|
|
135
194
|
|