softr-vibe-coding 2.15.1 → 2.15.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,12 @@ 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.15.2] - 2026-10-08
8
+ - Release 2.15.2
9
+ - browser-checks: run checks in the client's time zone
10
+ - Release 2.15.1
11
+ - softr-mcp: unicode escapes come back decoded on push
12
+
7
13
  ## [2.15.1] - 2026-10-08
8
14
  - Release 2.15.1
9
15
  - softr-mcp: unicode escapes come back decoded on push
package/README.md CHANGED
@@ -220,7 +220,9 @@ softr-vibe-coding/
220
220
  │ │ # shell, eval measurements, the records-trigger
221
221
  │ │ # write guard proven before any click, what a
222
222
  │ │ # click sent, screenshots to disk (Oct 1 2026);
223
- │ │ # testing Custom Code header CSS (Oct 5 2026)
223
+ │ │ # testing Custom Code header CSS (Oct 5 2026);
224
+ │ │ # the client's time zone via TZ at launch, and
225
+ │ │ # date-only values shown vs stored (Oct 8 2026)
224
226
  │ ├── advanced-integrations.md # Shadow DOM CSS isolation
225
227
  │ │ # Leaflet, Mapbox, TinyMCE, Quill, FullCalendar
226
228
  │ ├── native-chrome-styling.md # Restyle Softr's native shell (header, footer,
package/SKILL.md CHANGED
@@ -103,7 +103,7 @@ You generate complete, production-ready Softr Vibe Coding blocks as TypeScript R
103
103
  - Array-setting rows keyed by **index**, never by a builder-editable field value
104
104
  - Media settings that may start empty (`src: ""`) gated with a conditional render or placeholder — never an unconditional `<img src={setting.src}>`
105
105
  - **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 the text that reaches it; the one-byte drift we once blamed on it was a chunked read on our side). No `\uXXXX` escapes in pushed source: they arrive decoded to the characters and the hashes differ, so write the characters themselves (with a comment saying why) or build them from code points (`String.fromCharCode(0x300)`); lone surrogates are the exception ([why](references/softr-mcp.md#unicode-escapes-come-back-decoded)). 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)
106
- - **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)
106
+ - **Then check it in a browser.** Once the push checks pass, check rendering and behaviour in a fresh preview with saves blocked, in the client's time zone (a check east of UTC misses date-only values that users west of it see a day early), per [references/browser-checks.md](references/browser-checks.md)
107
107
 
108
108
  ## What to Clarify
109
109
 
@@ -190,7 +190,7 @@ For advanced patterns beyond data fetching, load the relevant reference when the
190
190
  | Small reusable patterns — `localStorage` cross-page state, clipboard copy button, measuring the block's own width (not the window's), clearing Softr's sticky top bar and phone tab bar, an in-block modal above Softr's bars (instead of shadcn `Dialog`) | [references/common-patterns.md](references/common-patterns.md) |
191
191
  | 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) |
192
192
  | 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) |
193
- | **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, testing Custom Code header CSS | [references/browser-checks.md](references/browser-checks.md) |
193
+ | **Checking a pushed block in a browser** — rendering and behaviour in a Softr preview with the agent-browser CLI (ask before installing it): running it in the client's time zone (`TZ` when the session starts) and comparing shown date-only values with the stored ones, 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, testing Custom Code header CSS | [references/browser-checks.md](references/browser-checks.md) |
194
194
  | 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, and the **app frame** for sidebar apps (top bar + sidebar as one frame in the theme colour, the content as one paper sheet with a pinned rounded corner, blocks transparent, scoped with `:has()` so Log in / 404 keep Softr's colours) | [references/native-chrome-styling.md](references/native-chrome-styling.md) |
195
195
  | 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) |
196
196
  | **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.15.1",
3
+ "version": "2.15.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"
@@ -4,7 +4,8 @@ How to check a deployed block's rendering and behaviour in a Softr preview with
4
4
  [agent-browser](https://github.com/vercel-labs/agent-browser) CLI. **Verified 2026-10-01** with
5
5
  agent-browser v0.38.1 on macOS (Node 22) against a real Softr preview; only the commands under
6
6
  [Untested but promising](#untested-but-promising) were not run. [Testing Custom Code header
7
- CSS](#testing-custom-code-header-css) was verified 2026-10-05, except where it says otherwise.
7
+ CSS](#testing-custom-code-header-css) was verified 2026-10-05, except where it says otherwise, and
8
+ [the client's time zone](#1-the-clients-time-zone) on 2026-10-08.
8
9
 
9
10
  ## When to use it
10
11
 
@@ -55,13 +56,54 @@ even Slack ones. Yet it is only a stub that loads `agent-browser skills get core
55
56
 
56
57
  ## The recipe
57
58
 
58
- ### 1. Session, preview cookie, page
59
+ ### 1. The client's time zone
60
+
61
+ Run every check in the time zone of the app's users, never the machine's. A headless Chrome takes
62
+ the zone of the computer it runs on, and a date-only value arrives as midnight UTC
63
+ ([fields.md → Date-only fields arrive as midnight UTC](../datasources/fields.md#date-only-fields-arrive-as-midnight-utc)):
64
+ a block that parses it with `new Date()` or `parseISO` shows the right day east of UTC and the day
65
+ before west of it. On 2026-10-08 three blocks of an app for Oregon had passed checks run in
66
+ Europe/Athens (UTC+3) while showing their users in America/Los_Angeles every date-only value a day
67
+ early. A test-data load found it, not the checks.
68
+
69
+ agent-browser has no time-zone setting (v0.38.1: no flag, and `set` offers none). Chrome takes its
70
+ zone from `TZ` in the environment of the daemon that launches it, so put `TZ` in the `ab` function:
71
+ then the command that starts the daemon carries it, whichever one that is. Check the zone straight
72
+ after launch:
73
+
74
+ ```bash
75
+ ab() { TZ=America/Los_Angeles agent-browser --session softr-check "$@"; } # the client's IANA zone
76
+ ab open 'about:blank' >/dev/null
77
+ ab eval "Intl.DateTimeFormat().resolvedOptions().timeZone + ' ' + new Date().getTimezoneOffset()"
78
+ # "America/Los_Angeles 420" (minutes behind UTC: 420 in summer time, 480 in winter)
79
+ # "Europe/Athens -180" = no TZ reached the daemon: the machine's zone
80
+ ```
81
+
82
+ - **`TZ` counts only when the session's daemon starts.** Against a daemon already running,
83
+ `TZ=America/Los_Angeles agent-browser … eval` still printed `Europe/Athens`. And `ab close` alone
84
+ is not enough: the daemon outlives it by about a second, and an `open` in that second came back in
85
+ the old zone. To change zone, close, wait until `agent-browser session list` no longer shows the
86
+ session, then open again; or use a new session name.
87
+ - **Which zone:** the client's, from the project notes, as an IANA name (`America/Los_Angeles`,
88
+ `America/New_York`). If the users span zones, run the date check ([step 5](#5-date-only-values-against-the-stored-ones))
89
+ in each.
90
+ - **Verified 2026-10-08** with agent-browser 0.38.1 on macOS: `about:blank`, `example.com` and a new
91
+ tab all reported `America/Los_Angeles` and 420, and a mock block rendering
92
+ `new Date("2026-10-05T00:00:00.000Z")` showed Oct 5 in Athens and Oct 4 in Los Angeles. The zone
93
+ belongs to the browser, not the page, so it holds on a Softr preview too (inferred, not run
94
+ there).
95
+ - **Other browsers.** With Playwright (the fallback under [Install](#install)), pass
96
+ `timezoneId: 'America/Los_Angeles'` to `browser.newContext()` (its documented option; not run
97
+ here). An in-app Browser pane or the user's own Chrome runs in its machine's zone unless
98
+ something overrides it: run the probe there before trusting any date it shows.
99
+
100
+ ### 2. Session, preview cookie, page
59
101
 
60
102
  Work from a scratch directory, not the project: nothing is written to the working folder, and
61
103
  screenshots go where you tell them.
62
104
 
63
105
  ```bash
64
- ab() { agent-browser --session softr-check "$@"; } # a function, not a variable: see Gotchas
106
+ ab() { TZ=America/Los_Angeles agent-browser --session softr-check "$@"; } # step 1; a function, not a variable: see Gotchas
65
107
  ab open '<previewUrl>' >/dev/null # once per session: sets the preview cookie
66
108
  ab set viewport 1280 900
67
109
  ab open 'https://<subdomain>.preview.softr.app/<page>?recordId=<recordId>&autoUser=true'
@@ -73,7 +115,7 @@ ab wait 2500 # 2000–3000 ms more, so the block's dat
73
115
  this one carries a sign-in token, hence `/dev/null`. The direct URL then loads the app itself, not
74
116
  the toolbar shell that frames it, so `document` in `eval` is the app's.
75
117
 
76
- ### 2. Reaching into the block
118
+ ### 3. Reaching into the block
77
119
 
78
120
  A block renders inside a shadow root, which CSS selectors and `find` locators do not cross.
79
121
  **Refs from the accessibility readout do**: `ab snapshot -i` lists the block's textboxes, buttons
@@ -84,7 +126,7 @@ the ref out in the same shell call, so the readout never enters your context:
84
126
  REF=$(ab snapshot -i | grep -o 'textbox "Search by[^[]*\[ref=e[0-9]*' | head -1 | grep -o 'e[0-9]*$'); ab fill "@$REF" 'term'
85
127
  ```
86
128
 
87
- ### 3. Measuring with `eval`
129
+ ### 4. Measuring with `eval`
88
130
 
89
131
  `ab eval "<expr>"`, or `ab eval --stdin < check.js` for anything longer. An async IIFE is awaited,
90
132
  and only the result is printed: return `JSON.stringify(...)` to get one JSON-encoded line. Find the
@@ -115,7 +157,44 @@ A block that uses the brand `DatePicker` ([date-picker.md](date-picker.md)) has
115
157
  field is a button with a ref in `-i`. Click it, then click the day by its ref; each day is a button
116
158
  named like `Thursday, October 15, 2026`.
117
159
 
118
- ### 4. Block saves before any click, and prove it
160
+ ### 5. Date-only values against the stored ones
161
+
162
+ On every block that shows a date-only field, compare a few shown days with the stored ones, in
163
+ the client's zone ([step 1](#1-the-clients-time-zone)):
164
+
165
+ 1. Read three or more records with the MCP's `database_list_records` and note each date-only value.
166
+ Its first ten characters are the stored day: `"2026-10-05T00:00:00.000Z"` is 5 October.
167
+ 2. Read the same records on the page, by a value that names each one (`innerText` keeps a space
168
+ between table cells; `textContent` runs them together):
169
+
170
+ ```js
171
+ (() => {
172
+ const root = [...document.querySelectorAll('*')].map(e => e.shadowRoot).filter(Boolean)
173
+ .find(s => /<text unique to the block>/.test(s.textContent));
174
+ if (!root) return 'block not found';
175
+ const rows = [...root.querySelectorAll('tr, li, [role="row"]')]; // the block's row element
176
+ return JSON.stringify(['<name 1>', '<name 2>', '<name 3>'].map(n => {
177
+ const row = rows.find(e => e.textContent.includes(n));
178
+ return row ? row.innerText.replace(/\s+/g, ' ').trim().slice(0, 160) : n + ': not found';
179
+ }));
180
+ })()
181
+ ```
182
+
183
+ 3. Each shown day must be the stored day. Every one a day early means the block parses date-only
184
+ values with `new Date()` or `parseISO`: switch it to `toLocalDate`
185
+ ([fields.md](../datasources/fields.md#date-only-fields-arrive-as-midnight-utc)).
186
+
187
+ - **A zone west of UTC is what exposes the midnight-UTC bug.** Midnight UTC on 5 October is still
188
+ 5 October anywhere at or east of UTC, and 4 October at 5 pm in Los Angeles. A check in Athens
189
+ passes a block that is wrong for every user west of UTC, as three blocks did (step 1).
190
+ - **Check each place the block shows the date:** the list, the detail view, and the value an edit
191
+ form opens with. A form that opens a day early can save the wrong day when the user only changes
192
+ another field.
193
+ - **A "today" taken as the UTC day is wrong only part of the day.**
194
+ `new Date().toISOString().slice(0, 10)` is tomorrow from 5 pm in Los Angeles (4 pm in winter), so
195
+ a morning check misses it: look for it in the source instead.
196
+
197
+ ### 6. Block saves before any click, and prove it
119
198
 
120
199
  The preview writes to the live data
121
200
  ([softr-mcp.md](softr-mcp.md#testing-as-any-app-user-without-logins--the-preview-as-switcher)), so
@@ -139,9 +218,9 @@ Only this update endpoint is verified. Before clicking a create or delete contro
139
218
  with `ab network requests` where a write is harmless, never on client data, and block that pattern
140
219
  too. Do not assume `*records-trigger*` covers it.
141
220
 
142
- ### 5. Click, then read what it sent
221
+ ### 7. Click, then read what it sent
143
222
 
144
- Read the record through the database API, click Save by its ref (step 2), then read the record
223
+ Read the record through the database API, click Save by its ref (step 3), then read the record
145
224
  again: `updatedAt` and the field should be unchanged. The aborted request is still logged, so you
146
225
  see the payload without it reaching the server:
147
226
 
@@ -150,7 +229,7 @@ ab network requests --filter records-trigger # lists the save, with its id
150
229
  ab network request <id> --json # method: PATCH, postData: {"context":{…},"fields":{"<fieldId>":"2026-10-15"}}
151
230
  ```
152
231
 
153
- ### 6. Screenshots and cleanup
232
+ ### 8. Screenshots and cleanup
154
233
 
155
234
  ```bash
156
235
  ab screenshot ./empty-1280.png # ✓ Screenshot saved to … (--full for the whole page)
@@ -177,7 +256,7 @@ a test surface: header code is not known to render there.
177
256
 
178
257
  ### 1. Before pasting: inject it into the preview
179
258
 
180
- Open the page as in [step 1](#1-session-preview-cookie-page), as the user whose navigation you are
259
+ Open the page as in [step 2](#2-session-preview-cookie-page), as the user whose navigation you are
181
260
  styling: on the preview origin run `fetch('/studio/impersonate/<softrUserId>')`, then open the page
182
261
  again ([how](softr-mcp.md#testing-as-any-app-user-without-logins--the-preview-as-switcher)). Then
183
262
  inject the file exactly as it will be pasted, tagged so that a re-run replaces it:
@@ -353,7 +353,7 @@ no discard prompt, focus is on the date field's trigger); the second Escape reac
353
353
  inside the modal: focus lands on the next field, not on the modal panel (lesson 6).
354
354
 
355
355
  **4. The saved value is byte-identical to the native version's.** Block saves first
356
- ([browser-checks.md → Block saves before any click, and prove it](browser-checks.md#4-block-saves-before-any-click-and-prove-it)),
356
+ ([browser-checks.md → Block saves before any click, and prove it](browser-checks.md#6-block-saves-before-any-click-and-prove-it)),
357
357
  pick a day, save, and read the aborted request's payload: the field holds the same string the native
358
358
  field sent for that day (`"2026-10-15"`, not a timestamp). Clear sends what an emptied native field
359
359
  sent, unless the block deliberately changed it (one LCDB block now saves a cleared optional date as