softr-vibe-coding 2.1.0 → 2.1.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 CHANGED
@@ -4,6 +4,14 @@ 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.1.2] - 2026-08-26
8
+ - Document four linked-record write traps verified live 2026-08-26
9
+ - Fail the build when main moves ahead of npm
10
+
11
+ ## [2.1.1] - 2026-08-26
12
+ - Release 2.1.1
13
+ - Document external-URL attachment ingestion (copy, not link)
14
+
7
15
  ## [2.1.0] - 2026-08-25
8
16
  - Bump to 2.1.0 — official dev-guide sync (field-name rule for Airtable/Notion/Sheets, PHONE sanitization, useProxyFetch datasource routing, useCurrentUser properties) + workspace-wide Softr MCP reference with direct block deployment (softr-mcp.md replaces softr-database-mcp.md)
9
17
  - Sync with the official Vibe Coding developer guide and the workspace-wide Softr MCP server (28 verified fixes). Field-name rule extended: Airtable AND Notion AND Google Sheets use field NAMES in q.select (Softr DB/Supabase use IDs; wrong form fails silently with empty data) — fixed in Hard Constraint 11, notion.md, helper-blocks.md publisher template (was modeling fldXXX IDs), fields.md. New PHONE write rule: + followed by digits only, Monday.com hard-rejects formatted values — official sanitizePhone added to writing.md (new Phone section), monday.md, anti-patterns.md. useProxyFetch is datasource-scoped: alias as its ARGUMENT (useProxyFetch(ds.store)), throws when omitted with >1 source; proxy payloads are text-only (no FormData/streams/uploads) — added to rest-api.md, multi-datasource.md, quick-reference.md, anti-patterns.md. fetchNextPage rule relaxed to match the guide's canonical Load More onClick: never in the render body, event handler or guarded useEffect both fine (SKILL.md x2, README, anti-patterns.md). useCurrentUser({ properties }) exposes custom user fields under user.properties — window.__softr_current_user narrowed to userGroups/role only; id only present with user sync (reading.md, quick-reference.md, fields.md). useLinkedRecords count default 100 max 1000. useUpload: isUploading + result.error handling + multi-file pattern. Linked-record field-type row corrected to string-array shape. TRIGGER_CUSTOM_WORKFLOW takes no destination per the documented type. references/softr-mcp.md REPLACES softr-database-mcp.md: the MCP is now workspace-wide — vibe coding block tools (get_vibe_coding_docs, create/edit/versions, data source wiring + the five official gotchas incl. action-permission reset on every code change), integrations browsing down to field level (Airtable/Sheets/Notion/Supabase/Softr DB only), databases tools (no deletes yet; 100-create/200-read/2-group-by limits), three-area bundled permission levels replacing the old granular scopes. SKILL.md workflow now offers direct MCP deployment alongside paste-into-Studio. Description gains negative scope (building-design-md, non-Softr dashboards); reading/writing/fields now linked directly from SKILL.md instead of only via the shared-patterns index; README tree gains the tools/ dir; retired "no ?." mention dropped from airtable-automations.md; common-patterns var-style sentence reworded to legacy-but-valid
@@ -291,6 +291,43 @@ parentAccount: "RECORD_ID_1"
291
291
  string-array shape is the verified current form on Softr Database; if a linked-record write
292
292
  fails on an Airtable-backed block, try the `[{ id }]` object shape before deeper debugging.
293
293
 
294
+ ### Linked-record write traps (verified live 2026-08-26)
295
+
296
+ Four Softr Database behaviors proven by direct experiment on a live production build — a
297
+ 49-link backfill across a self-referencing parent/child pair. They bite hardest in backfills
298
+ and schema work, and **none of them raises an error**.
299
+
300
+ 1. **A single-valued link pair is enforced ON WRITE — and it clobbers silently.** With
301
+ `allowMultipleEntries: false` on the inverse side, writing child N's link to a parent
302
+ silently unlinks child N−1: no error, the earlier link just vanishes (16 of 49 backfill
303
+ links disappeared this way before it was caught). Pre-existing multi links DO survive a
304
+ flip from multi to single, which is why a scratch-table spike that flips an
305
+ already-linked pair reports the setting as "cosmetic only" and misleads — the
306
+ enforcement only fires on the next write. If one side must hold many links, both sides
307
+ must allow multiple entries; enforce any one-parent rule in the UI, not the schema.
308
+
309
+ 2. **`allowMultipleEntries` is a TOP-LEVEL field property, not part of `options`.** The
310
+ workspace MCP's `update_field` silently ignores it when nested inside `options` — the
311
+ call succeeds and changes nothing. A Tables API `PUT /fields/{id}` with the property at
312
+ top level works.
313
+
314
+ 3. **A Tables API field PUT that omits `options.inverseLinkFieldId` SEVERS the inverse
315
+ pairing** — it comes back `null` and the two sides stop mirroring each other. Always
316
+ echo `inverseLinkFieldId` inside the `options` you send, and re-read BOTH sides after
317
+ any linked-record field PUT to confirm the pairing survived.
318
+
319
+ 4. **An empty multi-entry link reads back as `[]`, which is truthy in JS.** Any
320
+ "already linked — skip" guard written as `if (value)` matches every record after a
321
+ single→multi flip, so a resumable backfill re-processes nothing or skips everything.
322
+ Test presence explicitly:
323
+
324
+ ```jsx
325
+ const hasLink = Array.isArray(v) ? v.length > 0 : !!v;
326
+ ```
327
+
328
+ (The populated read shapes are in [fields.md](fields.md); this is the empty case, and
329
+ it becomes a write trap the moment a backfill uses it as a skip condition.)
330
+
294
331
  ## Writing to Field Types
295
332
 
296
333
  Different Softr field types accept different value shapes in mutation payloads. The shape returned when you READ a field is often different from the shape you must SEND when you WRITE.
@@ -414,9 +451,36 @@ createRecord.mutate({
414
451
  });
415
452
  ```
416
453
 
417
- To upload a file before writing it to a record, see [File Uploads](#file-uploads) above for the full `useUpload` flow.
454
+ To upload a file from the user's machine before writing it to a record, see
455
+ [File Uploads](#file-uploads) above for the full `useUpload` flow.
456
+
457
+ #### A file already on the public web needs no upload step
458
+
459
+ Hand the attachment field any publicly reachable URL and Softr fetches the file, stores **its own copy**,
460
+ and generates preview thumbnails. The saved record points at Softr's bucket rather than the original host,
461
+ so the source can later move or delete the file without breaking the record:
462
+
463
+ ```jsx
464
+ // No useUpload — the URL is the upload
465
+ { photo: { filename: "lamp.jpg", url: "https://cdn.example.com/abc123" } }
466
+ ```
418
467
 
419
- _Shape matches the example shown in [File Uploads](#file-uploads). Not yet independently verified across all data sources._
468
+ Details that matter in practice:
469
+
470
+ - The `filename` you supply is what Softr stores and serves. It does not have to match anything at the
471
+ source, and the **source URL needs no file extension** — Softr sniffs the real content type on fetch.
472
+ - Softr reads the URL **server-side**, so the file must be reachable without auth headers. A signed or
473
+ expiring link works only while it is still valid.
474
+ - The original URL is **not** retained on the record. Store it in a separate URL field when provenance or
475
+ a later re-pull matters.
476
+ - A bulk import of images from another system (Airtable, a vendor CDN, a CSV of image links) is therefore
477
+ a plain loop of record writes with no download-and-re-upload stage.
478
+
479
+ _Write shape verified live 2026-08-26 on Softr Database via the MCP `update_record`: a 20,990-byte
480
+ `image/jpeg` behind an extensionless ImageKit URL came back as a Softr-hosted S3 object of identical size
481
+ and type, with small/medium/large thumbnails generated. Copy-not-link confirmed. The in-block
482
+ `useRecordUpdate` / `createRecord` path takes the same shape, but external-URL ingestion was not separately
483
+ tested there, nor on other data sources._
420
484
 
421
485
  ### Text / Email / URL
422
486
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "softr-vibe-coding",
3
- "version": "2.1.0",
3
+ "version": "2.1.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"
@@ -118,6 +118,17 @@ Known limits and behaviors (per official docs):
118
118
  - Record field keys are **field IDs**, not labels — `list_fields` maps between them.
119
119
  - Computed fields (formula, lookup, rollup, count) and system fields (created/updated time and by, autonumber, record ID) are read-only; a field's type cannot be changed after creation.
120
120
  - **Nothing can be deleted through the MCP yet** — no record/table/field/database delete tools (docs say deletion is coming). Deletions happen in the builder.
121
+ - **Attachment writes take a URL and copy the file.** `create_record` / `update_record` accept
122
+ `{ filename, url }` on an ATTACHMENT field with any publicly reachable URL; Softr fetches it, stores its
123
+ own copy and generates thumbnails, so backfilling images from another system is one write per record
124
+ with no upload step. Verified 2026-08-26 — see [../datasources/writing.md](../datasources/writing.md#attachment).
125
+ - **`update_field` silently ignores `allowMultipleEntries` nested inside `options`** — it is a TOP-LEVEL
126
+ field property; the call succeeds and changes nothing (verified 2026-08-26). To flip a LINKED_RECORD
127
+ field between single and multi, `PUT` it via the Tables API with `allowMultipleEntries` at top level —
128
+ and always echo `options.inverseLinkFieldId` in that PUT, because omitting it severs the inverse
129
+ pairing. Full write-up, including the silent on-write clobbering of single-valued link pairs and the
130
+ truthy-`[]` empty-link read shape:
131
+ [../datasources/writing.md](../datasources/writing.md#linked-record-write-traps-verified-live-2026-08-26).
121
132
  - Limits: 100 records per `create_records` call, 200 records per read (silently capped, not an error), 2 group-by fields in `aggregate_data`. For big tables prefer a filter or aggregate over paging.
122
133
 
123
134
  Typical Vibe Coding uses: "list every field on `Wigs` with id, name, type, and dropdown options", "what's the option id for `Payment status` = 'Partially paid'?", "show 3 sample records so we know value shapes", "verify the field id in my `q.select()` exists". This eliminates the field-id-typo / wrong-option-uuid class of bugs entirely.