softr-vibe-coding 2.15.1 → 2.15.3
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 +10 -0
- package/README.md +3 -1
- package/SKILL.md +2 -2
- package/package.json +1 -1
- package/references/anti-patterns.md +1 -0
- package/references/browser-checks.md +89 -10
- package/references/date-picker.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,16 @@ 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.3] - 2026-10-08
|
|
8
|
+
- Release 2.15.3
|
|
9
|
+
- anti-patterns: unicode escapes in pushed source
|
|
10
|
+
|
|
11
|
+
## [2.15.2] - 2026-10-08
|
|
12
|
+
- Release 2.15.2
|
|
13
|
+
- browser-checks: run checks in the client's time zone
|
|
14
|
+
- Release 2.15.1
|
|
15
|
+
- softr-mcp: unicode escapes come back decoded on push
|
|
16
|
+
|
|
7
17
|
## [2.15.1] - 2026-10-08
|
|
8
18
|
- Release 2.15.1
|
|
9
19
|
- 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.
|
|
3
|
+
"version": "2.15.3",
|
|
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"
|
|
@@ -50,6 +50,7 @@ Run through this catalog before delivering any block. Every row is a violation o
|
|
|
50
50
|
| Treating the permission-restore call as done because you issued it | Read the permissions back with `vibe_coding_block_get_settings` and confirm each one changed. `vibe_coding_block_set_action_visibility` can fail outright on the array-argument rejection (the client holding only a stub of the tool, typically after a session resume), and it has **no fallback** — the default for ADD_RECORD follows the block's own visibility, so on a block everyone can see, a routine push silently leaves it publicly writable while returning `errors: null`. Verified live 2026-09-09: four ADD_RECORD actions left open across two blocks. Report the list with its severity — check the page's VIEW permission with `application_page_get_permissions`, since a logged-in-gated page makes this housekeeping while a public page makes it a real hole — and let the builder decide whether it holds their release. A human sets them on the block's Actions tab |
|
|
51
51
|
| Calling an array-argument MCP tool (`vibe_coding_block_set_action_visibility`, `vibe_coding_block_update_code_search_replace`) when its loaded definition is a stub — description identical to the tool name, schema `{"type":"object"}` with no properties | A stub declares no types, so the array is sent as a JSON string and rejected (since 2026-10-01 the error reads "Parameter '…' must be an array of objects, but a string was sent"). Stubs have appeared after a session was resumed, and subagents spawned from that session inherit them. Start a fresh top-level session, which reloaded the real definitions when we tried it, then call. Found 2026-10-01 — see [softr-mcp.md](softr-mcp.md#the-array-argument-rejection-and-why-it-is-a-security-issue) |
|
|
52
52
|
| Verifying a push by pulling the whole block source back into the model's context | Compare `sourceSha256` from the push result (or from `vibe_coding_block_get_code` with `includeCode: false`) with `shasum -a 256` of the file you sent; fetch the full source only when the digest is missing or differs. Available since 2026-10-01 — see [softr-mcp.md](softr-mcp.md#verifying-a-push--the-deployed-source-is-the-only-proof) |
|
|
53
|
+
| Pushing source that holds `\uXXXX` escapes through the MCP — e.g. an accent-folding regex `/[\u0300-\u036f]/g` | The escapes are stored decoded to the characters themselves, so exactly those pushes come back with a `sourceSha256` that differs from your file (verified 2026-10-08). Write the characters raw, with a comment saying why (spell "backslash-u" out in it), once you have checked the raw character means the same in that spot — or build from code points: `new RegExp("[" + String.fromCharCode(0x300) + "-" + String.fromCharCode(0x36f) + "]", "g")`. Lone surrogates (`\udc00-\udfff`) survive as written. Find escapes before a push with `grep -n '\\u[0-9a-fA-F]\{4\}' <file>` — see [softr-mcp.md](softr-mcp.md#unicode-escapes-come-back-decoded) |
|
|
53
54
|
| Splitting one table's writes across several `useRecordUpdate` hooks (or across two connections of the same table) to get separately-permissioned Actions | Actions register per **TABLE**: the hooks merge into ONE UPDATE_RECORD action whose field list is the union, and a hook pointed at a second connection of the table is still filed under the FIRST connection's dataSourceId (verified live 2026-09-18). Point writes at the table's first connection; expect one action per table + operation when re-tightening permissions. See [datasources/writing.md](../datasources/writing.md#actions-register-per-table-not-per-hook-or-connection) |
|
|
54
55
|
| Treating Studio's Actions tab as a separately-managed configuration to keep in sync with code | Actions auto-derive from your `useRecordCreate`/`useRecordUpdate`/`useRecordDelete` + `q.select` on every save. The Actions tab is a read-only inspector; there is no manual delete control. To change an Action, change the code |
|
|
55
56
|
| One alias in a write-side `q.select` referencing a renamed / non-existent Airtable column | Softr's Action parser silently rejects the **entire** create/update Action — not just the bad alias. Symptoms: Studio's Actions tab shows "No actions used in this block yet", `createRecord.enabled` / `updateRecord.enabled` stays `false`, `.mutate()` calls dispatch but resolve immediately to "not yet ready". Every OTHER field in the same `q.select()` is also lost, even the ones that map cleanly. Diagnostic: bisect the `q.select` — strip down to a known-good minimal set, confirm the Action appears in Studio, then add fields back in halves until it drops out. The culprit is in the last half added. Once narrowed to a single field, grep its name against the freshest Airtable schema export to catch the rename / trailing-space / case-mismatch. Verified 2026-05-21: a `"Photos"` column on Wigs was renamed to `"Before Photos"`, the helper that wrote `photos: "Photos"` had its entire Action disabled even though 11 other fields in the same `q.select` were fine. See [datasources/airtable.md](../datasources/airtable.md#maintainability-gotcha) |
|
|
@@ -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.
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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
|
-
###
|
|
221
|
+
### 7. Click, then read what it sent
|
|
143
222
|
|
|
144
|
-
Read the record through the database API, click Save by its ref (step
|
|
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
|
-
###
|
|
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
|
|
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#
|
|
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
|