softr-vibe-coding 1.1.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.
@@ -0,0 +1,256 @@
1
+ # Writing Data
2
+
3
+ Record mutations, file uploads, linked record format, and cross-table operations.
4
+
5
+ ## Table of Contents
6
+
7
+ - [How Actions Work (Studio's Actions Tab)](#how-actions-work-studios-actions-tab)
8
+ - [Record Mutations](#record-mutations)
9
+ - [File Uploads](#file-uploads)
10
+ - [Linked Record Format for Mutations](#linked-record-format-for-mutations)
11
+ - [Writing to Field Types](#writing-to-field-types)
12
+ - [Cross-Table Operations](#cross-table-operations)
13
+
14
+ ## How Actions Work (Studio's Actions Tab)
15
+
16
+ Every Vibe Coding block in Softr Studio has an **Actions tab** alongside Chat / Source / Content / Visibility. The Actions tab is a **read-only inspector** of the Create / Update / Delete operations the platform inferred from your code.
17
+
18
+ Each Action's "FIELDS USED" list mirrors the aliases in your `q.select()` mapping. Actions are not a separately-managed system:
19
+
20
+ - The platform parses your block's source on every save
21
+ - For each `useRecordCreate` / `useRecordUpdate` / `useRecordDelete` hook, it auto-derives a corresponding Action
22
+ - The fields list comes from your `q.select` aliases used in the mutation payload
23
+ - Cosmetic AND structural code edits both update the Action automatically -- you do NOT need to re-prompt the AI assistant after editing code
24
+ - There is no manual delete control; to remove an Action, remove the mutation hook from the code
25
+
26
+ The `enabled` boolean on a mutation hook is a combined signal — it's `true` only when BOTH conditions are met:
27
+
28
+ 1. **The Action was successfully derived from the code** (parser side). Causes of failure here:
29
+ - The hook is declared after a conditional `return`, so it doesn't run on every render
30
+ - `q.select` is built dynamically rather than from string-literal mappings
31
+ - The block hasn't been connected to a data source yet
32
+ - `useRecordUpdate.mutate()` is called with a flat payload (`{ recordId, status: ... }`) instead of the nested shape (`{ recordId, fields: { status: ... } }`) -- the parser ignores flat field references, so no Action gets created
33
+
34
+ 2. **The currently logged-in (or previewed-as) user has permission to perform the action on the data source** (permissions side). Per the official Softr docs, "`enabled` reflects user permissions." So even with a perfectly-derived Action, `enabled` will be `false` if the previewed user's group lacks write access to the connected table.
35
+
36
+ **Critical debugging implication:** when `enabled` stays `false` and Studio's Actions tab DOES show the action listed (parser succeeded), the cause is permissions — not code. Three places to check, in order:
37
+
38
+ - **Block's Visibility tab** (right panel) — confirm the previewed user's group is allowed to see / interact with this block.
39
+ - **Studio → Users → Data Restrictions → Global data restrictions** — an app-wide layer that limits what data users can interact with across the whole app, applied on top of block-level settings. Easy to miss because it's under Users (not on the block). If a Global restriction exists on the target table for the user's group, every mutation against that table in every block silently fails — no error, just `enabled: false`. Open this tab and either confirm there are no restrictions or that the worker / target group has the access they need.
40
+ - **Data-source connection PAT scope** — if the PAT was granted with read-only scope, every write fails regardless of UI permissions. Reconnect with write scope if needed.
41
+
42
+ Verified by direct experiment (April 2026): adding a new field to `q.select` + `mutate()` payload and saving the code propagates to the Actions tab automatically and writes successfully to the database with no manual configuration.
43
+
44
+ Because the parser only inspects your hooks and `q.select` mappings (not the JSX tree), inputs rendered conditionally inside `<Dialog>`, `<Sheet>`, or any subtree gated by state are still bound to the Action correctly. Verified by direct experiment, May 2026.
45
+
46
+ ## Record Mutations
47
+
48
+ All mutation hooks expose an `enabled` boolean. You must check it before rendering any mutation UI or calling the mutate function.
49
+
50
+ ### useRecordCreate
51
+
52
+ ```jsx
53
+ import { useRecordCreate, q } from "@/lib/datasource";
54
+ import { toast } from "sonner";
55
+
56
+ var createRecord = useRecordCreate({
57
+ fields: q.select({ name: "FIELD_ID1", email: "FIELD_ID2" }),
58
+ onSuccess: function(newRecord) { toast.success("Created!"); },
59
+ onError: function(error) { toast.error(error.message); },
60
+ });
61
+
62
+ // Usage (gate on enabled):
63
+ if (createRecord.enabled) {
64
+ createRecord.mutate({ name: "Jane", email: "jane@example.com" });
65
+ }
66
+ ```
67
+
68
+ ### useRecordUpdate
69
+
70
+ ```jsx
71
+ import { useRecordUpdate, q } from "@/lib/datasource";
72
+
73
+ var updateRecord = useRecordUpdate({
74
+ fields: q.select({ status: "FIELD_ID1" }),
75
+ onSuccess: function(updatedRecord) {
76
+ refetch().then(function() { toast.success("Updated!"); });
77
+ },
78
+ onError: function(error) { toast.error(error.message); },
79
+ });
80
+
81
+ // Usage -- MUST use recordId, NOT id, AND fields MUST be nested:
82
+ updateRecord.mutate({
83
+ recordId: "RECORD_ID",
84
+ fields: { status: "active" },
85
+ });
86
+ ```
87
+
88
+ #### CRITICAL: Two parser requirements for `useRecordUpdate`
89
+
90
+ Softr's Action parser is strict about both the **method name** and the **payload shape**. Get either wrong and the Action is never derived, `enabled` stays `false`, and any UI gated on it silently does nothing — no error, no warning, console just shows `enabled: false, error: null, status: "idle"`.
91
+
92
+ **Requirement 1 — Call `.mutate()`, NOT `.mutateAsync()`.** The parser scans for the literal `.mutate(` token to detect mutation call sites. `.mutateAsync()` runs fine at runtime (it's just a Promise wrapper), but the parser ignores it and no Update Action gets created. Pass per-call success/error handlers as the second argument (react-query convention):
93
+
94
+ ```jsx
95
+ // CORRECT — parser sees `.mutate(`, derives the Action
96
+ updateRecord.mutate(
97
+ { recordId: id, fields: { status: optionId } },
98
+ {
99
+ onSuccess: function() { toast.success("Saved"); },
100
+ onError: function(err) { toast.error(err.message); },
101
+ }
102
+ );
103
+
104
+ // WRONG — parser ignores `.mutateAsync(`, Action never created
105
+ updateRecord.mutateAsync({ recordId: id, fields: { status: optionId } })
106
+ .then(function() { toast.success("Saved"); });
107
+ ```
108
+
109
+ **Requirement 2 — Payload must be `{ recordId, fields: {...} }` — not flat.** Field values must be nested inside a `fields: {...}` object. The flat form (`mutate({ recordId, status: "active" })`) can succeed at runtime, but the parser doesn't see field references inside it, so no Action is derived:
110
+
111
+ ```jsx
112
+ // CORRECT
113
+ updateRecord.mutate({ recordId: id, fields: { status: optionId } }, { onSuccess, onError });
114
+
115
+ // WRONG — Action parser ignores this, hook stays disabled
116
+ updateRecord.mutate({ recordId: id, status: optionId }, { onSuccess, onError });
117
+ ```
118
+
119
+ Verified by direct experiment (May 2026): Softr's Studio AI assistant emits both `.mutate()` AND the nested payload shape — and that combination is what produces a derived Update Action. Switching either back to its alternative form (`.mutateAsync()` or flat payload) disables the hook.
120
+
121
+ ### useRecordDelete
122
+
123
+ ```jsx
124
+ import { useRecordDelete } from "@/lib/datasource";
125
+
126
+ var deleteRecord = useRecordDelete({
127
+ onSuccess: function(result) {
128
+ refetch().then(function() { toast.success("Deleted!"); });
129
+ },
130
+ onError: function(error) { toast.error(error.message); },
131
+ });
132
+
133
+ // Usage -- pass just the string, NOT an object:
134
+ deleteRecord.mutate("RECORD_ID");
135
+ ```
136
+
137
+ ### Three q.select() Mappings Pattern
138
+
139
+ For blocks that read, create, and update, use separate mappings:
140
+
141
+ ```jsx
142
+ // 1. READ -- includes everything (formulas, lookups, linked records)
143
+ var select = q.select({
144
+ name: "FIELD_1", email: "FIELD_2",
145
+ score: "FORMULA_FIELD", // read-only OK here
146
+ department: "LOOKUP_FIELD", // read-only OK here
147
+ });
148
+
149
+ // 2. CREATE -- only writable fields
150
+ var createFields = q.select({ name: "FIELD_1", email: "FIELD_2" });
151
+
152
+ // 3. UPDATE -- only writable fields that can be edited
153
+ var updateFields = q.select({ name: "FIELD_1", email: "FIELD_2" });
154
+ ```
155
+
156
+ ## File Uploads
157
+
158
+ ```jsx
159
+ import { useUpload } from "@/lib/datasource";
160
+
161
+ var upload = useUpload();
162
+
163
+ // Single file:
164
+ upload.uploadAsync(file).then(function(results) {
165
+ var result = results[0];
166
+ if (result.status === "completed") {
167
+ // result.url = uploaded file URL, result.file.name = original filename
168
+ }
169
+ });
170
+
171
+ // Combine with record creation:
172
+ upload.uploadAsync(file).then(function(results) {
173
+ var result = results[0];
174
+ if (result.status === "completed") {
175
+ createRecord.mutate({
176
+ name: "Document",
177
+ attachment: { filename: result.file.name, url: result.url },
178
+ });
179
+ }
180
+ });
181
+ ```
182
+
183
+ ## Linked Record Format for Mutations
184
+
185
+ ```jsx
186
+ // CORRECT -- Array of { id } objects
187
+ createRecord.mutate({
188
+ parentAccount: [{ id: "RECORD_ID_1" }],
189
+ teamMembers: [{ id: "MEMBER_1" }, { id: "MEMBER_2" }],
190
+ });
191
+
192
+ // WRONG -- Plain string or array of strings
193
+ parentAccount: "RECORD_ID_1" // Won't work
194
+ teamMembers: ["MEMBER_1"] // Won't work
195
+ ```
196
+
197
+ ## Writing to Field Types
198
+
199
+ 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.
200
+
201
+ ### Dropdown / Single Select (Softr Database)
202
+
203
+ Write the option's UUID as a **plain string**, not an object:
204
+
205
+ ```jsx
206
+ // CORRECT -- plain string UUID
207
+ createRecord.mutate({
208
+ status: "822b8d69-3af4-47b4-90eb-3a80c5d1b85c",
209
+ });
210
+
211
+ // WRONG -- object form (returned on read, but rejected on write)
212
+ createRecord.mutate({
213
+ status: { id: "822b8d69-3af4-47b4-90eb-3a80c5d1b85c", label: "Active" },
214
+ });
215
+
216
+ // WRONG -- display label
217
+ createRecord.mutate({
218
+ status: "Active",
219
+ });
220
+ ```
221
+
222
+ Option UUIDs are stable. Three ways to retrieve them:
223
+
224
+ - **AI scaffolding** -- Softr's AI assistant in Studio inlines them automatically into `<SelectItem value="...">` when generating a form
225
+ - **Network inspector** -- DevTools -> Network -> filter `tablespace-with-tables` returns the full `choices` array for any SELECT field (see [fields.md](fields.md#field-inspector-block) for the full technique). Pasting this JSON into an AI assistant chat is the most reliable way to share UUIDs without transcription errors.
226
+ - **Runtime scan** -- learn them at runtime from already-loaded records (useful when the block must work in environments where UUIDs are not known at code time)
227
+
228
+ Verified by direct experiment (April 2026) for `useRecordCreate`. The same pattern is expected to apply to `useRecordUpdate` but has not been independently verified.
229
+
230
+ ### Linked Record
231
+
232
+ Array of `{ id }` objects. See "Linked Record Format for Mutations" above.
233
+
234
+ ### Text / Email / URL / Phone
235
+
236
+ Plain string. To clear a value, both `null` and `""` work for Softr Database text fields (verified by direct experiment, May 2026, for `useRecordUpdate`). Behavior on other data sources has not been independently verified.
237
+
238
+ ## Cross-Table Operations
239
+
240
+ `useRecordCreate`, `useRecordUpdate`, and `useRecordDelete` only work with the block's configured datasource. To write to a different table, use the **Softr Database REST API** via `fetch()`:
241
+
242
+ **Base URL:** `https://tables-api.softr.io/api/v1/databases/{databaseId}/tables/{tableId}/records`
243
+
244
+ **Authentication:** `Softr-Api-Key` header with a Personal Access Token.
245
+
246
+ **Create:** POST with `{ fields: { fieldId1: "value1" } }`
247
+ **Read:** GET with `?limit=200&fieldNames=true`
248
+ **Update:** PATCH `/{recordId}` with `{ fields: { fieldId1: "newValue" } }`
249
+
250
+ Notes:
251
+ - API key is exposed in client-side code -- acceptable for internal portals only
252
+ - When updating linked records or multi-selects, read existing values first, merge, then write
253
+ - Use `fieldNames=true` on GET for human-readable field names
254
+ - Rate limits: Reads 40 req/s, Writes 30 req/s
255
+
256
+ Verified by direct experiment (May 2026): POST to this endpoint with field IDs as keys writes successfully, returning HTTP 200 and the full record JSON. The endpoint uses the same field-ID format and the same value shapes as `useRecordCreate` -- plain string UUID for dropdown writes; the response returns the dropdown value as a `{id, label}` object (matching the read shape).
@@ -0,0 +1,37 @@
1
+ # Xano
2
+
3
+ ## Overview
4
+ Xano is a no-code backend-as-a-service (BaaS) built on PostgreSQL. Available on Professional plans and above.
5
+
6
+ ## Connection Setup
7
+ 1. In your Xano workspace, enable the **Database Connector** add-on.
8
+ 2. Note the public IP address and credentials (Host, User, Password) from the Database Connector settings.
9
+ 3. In the Softr admin, go to your data source settings and select Xano.
10
+ 4. Enter the credentials: Host, User, and Password.
11
+ 5. Use **Full Access** credentials (recommended) for read/write, or **Read-Only** for display-only apps.
12
+ 6. **Whitelist Softr IP addresses** in your Xano firewall settings:
13
+ - `3.120.79.212`
14
+ - `3.123.159.186`
15
+ - `52.58.246.121`
16
+ 7. Once connected, all workspaces and tables in that Xano instance are available.
17
+
18
+ ## Vibe Coding Field IDs
19
+ Use the Field Inspector block to determine exact field IDs for this data source.
20
+
21
+ ## Supported Fields
22
+ Field support follows PostgreSQL column types since Softr connects via direct database access. Standard column types (text, integer, boolean, timestamp, etc.) are writable. Computed/generated columns are read-only.
23
+
24
+ ## Rate Limits
25
+ No Softr-imposed rate limits. Xano handles concurrency at the database level. Unlimited records and unlimited concurrent users.
26
+
27
+ ## Gotchas
28
+ - **Direct database access, not API.** Softr connects to Xano's PostgreSQL database directly via the Database Connector. It does NOT go through Xano's custom API endpoints. Business logic in Xano API stacks will not execute on Softr reads/writes.
29
+ - **Database Connector must be enabled.** This is a separate add-on in Xano -- the connection will fail without it.
30
+ - **IP whitelisting is required.** If you skip whitelisting the three Softr IPs, connections will be refused.
31
+ - **Full Access vs Read-Only credentials** determine whether Softr can write data. Choose based on your use case.
32
+
33
+ ## Best For
34
+ - Scalable applications with complex backend logic in Xano
35
+ - Apps that need unlimited records and concurrent users
36
+ - Projects where Xano handles business logic via API stacks and Softr provides the frontend
37
+ - Teams wanting a dedicated backend with PostgreSQL power and a no-code UI layer
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "softr-vibe-coding",
3
+ "version": "1.1.0",
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
+ "bin": {
6
+ "softr-vibe-coding": "./bin/cli.js"
7
+ },
8
+ "files": [
9
+ "bin/",
10
+ "SKILL.md",
11
+ "ui-ux-guidelines.md",
12
+ "references/",
13
+ "datasources/",
14
+ "LICENSE",
15
+ "README.md"
16
+ ],
17
+ "keywords": [
18
+ "claude",
19
+ "claude-code",
20
+ "claude-skill",
21
+ "softr",
22
+ "vibe-coding",
23
+ "no-code",
24
+ "jsx",
25
+ "react"
26
+ ],
27
+ "homepage": "https://github.com/leo-softr/Softr-Vibe-Coding-Block-Claude-Skill#readme",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/leo-softr/Softr-Vibe-Coding-Block-Claude-Skill.git"
31
+ },
32
+ "bugs": {
33
+ "url": "https://github.com/leo-softr/Softr-Vibe-Coding-Block-Claude-Skill/issues"
34
+ },
35
+ "author": "leo-softr <leo@softr.io>",
36
+ "license": "MIT",
37
+ "engines": {
38
+ "node": ">=18"
39
+ }
40
+ }
@@ -0,0 +1,69 @@
1
+ # Advanced Integrations
2
+
3
+ Patterns for embedding third-party libraries that ship their own global CSS inside a Softr Vibe Coding block without style bleed.
4
+
5
+ ## CSS Isolation via Shadow DOM
6
+
7
+ **Problem:** Third-party libraries ship CSS with generic selectors like `.marker`, `.editor`, `.btn`. Inside a Softr page, those selectors bleed into the rest of the page or get clobbered by Softr's own styles. Scoping manually is impractical.
8
+
9
+ **Solution:** Mount the library inside a **Shadow DOM** attached to a host element. The shadow root is a fully isolated DOM subtree -- the library's CSS cannot leak out, and Softr's styles cannot leak in. React renders the host wrapper; the library lives inside the shadow.
10
+
11
+ ```jsx
12
+ import { useEffect, useRef } from "react";
13
+
14
+ export default function Block() {
15
+ var hostRef = useRef(null);
16
+
17
+ useEffect(function() {
18
+ var host = hostRef.current;
19
+ if (!host || host.shadowRoot) return; /* already initialized */
20
+
21
+ var shadow = host.attachShadow({ mode: "open" });
22
+
23
+ /* Inject the library's CSS inside the shadow */
24
+
25
+ /* Option A: external stylesheet from CDN */
26
+ var link = document.createElement("link");
27
+ link.rel = "stylesheet";
28
+ link.href = "https://cdn.example.com/somelib/style.css";
29
+ shadow.appendChild(link);
30
+
31
+ /* Option B: inline <style> for custom overrides */
32
+ var style = document.createElement("style");
33
+ style.textContent = ".marker { color: red; }";
34
+ shadow.appendChild(style);
35
+
36
+ /* Create the container where the library mounts */
37
+ var innerDiv = document.createElement("div");
38
+ innerDiv.id = "lib-root";
39
+ innerDiv.style.width = "100%";
40
+ innerDiv.style.height = "500px";
41
+ shadow.appendChild(innerDiv);
42
+
43
+ /* Initialize the library against innerDiv */
44
+ // var map = L.map(innerDiv).setView([0, 0], 2);
45
+ // var editor = new TinyMCE({ target: innerDiv });
46
+ }, []);
47
+
48
+ /* To query elements inside the shadow later: */
49
+ // var el = hostRef.current.shadowRoot.getElementById("lib-root");
50
+
51
+ return <div ref={hostRef} style={{ width: "100%" }} />;
52
+ }
53
+ ```
54
+
55
+ ## Key Points
56
+
57
+ - Use `attachShadow({ mode: "open" })`. The `open` mode lets you access the shadow later via `host.shadowRoot`. Always use `open`.
58
+ - **Guard against double-initialization.** React re-renders (and StrictMode) can fire the effect twice. Check `if (host.shadowRoot) return;` before calling `attachShadow` -- it throws if called on an element that already has a shadow root.
59
+ - **Query inside the shadow** via `host.shadowRoot.getElementById(...)` or `.querySelector(...)`. `document.getElementById` will NOT find elements inside a shadow root.
60
+ - **CSS is fully isolated in both directions.** Softr's fonts, resets, and utility classes do not reach inside. If you need Softr's font inside the shadow, redeclare it in the injected `<style>`.
61
+ - **Events bubble out** of the shadow root normally (click, input, etc.), so React event handling on the host still works for most libraries.
62
+
63
+ ## When NOT to Use Shadow DOM
64
+
65
+ If the library is React-native (shadcn, Recharts, lucide-react) and uses inline styles or scoped classes, skip the shadow -- it adds complexity for no benefit. Reserve this pattern for libraries that inject global CSS.
66
+
67
+ ## Libraries Where This Pattern Saves Hours
68
+
69
+ Leaflet, Mapbox GL, TinyMCE, Quill, CKEditor, FullCalendar, Grapesjs, Froala, and any CDN-distributed widget from a non-React world.