softr-vibe-coding 1.7.0 → 1.9.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.
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
+ ## [1.9.0] - 2026-06-04
8
+ - Bundle get-softr-database CLI script for schema export; document in SKILL.md, softr-database.md, fields.md
9
+ - Bump publish workflow to Node 24-based action majors — actions/checkout@v4 -> @v6, actions/setup-node@v4 -> @v6 (clears the Node 20 deprecation; GitHub forces Node 20 actions to Node 24 on 2026-06-16). No version bump, so this run skips publish.
10
+
11
+ ## [1.8.0] - 2026-06-04
12
+ - Expand references/native-chrome-styling.md to the full native shell — add Footer (semantic <footer> target + 160px/overflow-wrap contact-column email-wrap fix), floating "island" header/footer treatment, and Page background (Softr stacks the same fill on html/body/#page-content/inner-wrapper, so paint on html + clear the stack, EXCLUDING the .softr-topbar subtree so the dropdown panel survives) + a Console background-finder snippet; broaden SKILL.md Reference Guides row + README; add anti-patterns row for the page-background stacking; bump to 1.8.0
13
+
7
14
  ## [1.7.0] - 2026-06-04
8
15
  - Add references/native-chrome-styling.md — restyle Softr's native header/top bar/nav/dropdowns via global Custom Code CSS (target stable .softr-* / ARIA-Radix selectors instead of regenerated f8f11e5_* hashes, the dropdown column-grid blank-space fix, icon/label color-inherit, restyle-vs-replace tradeoffs); add SKILL.md scope note + Reference Guides row; add two anti-patterns rows; update README; bump to 1.7.0
9
16
 
package/README.md CHANGED
@@ -18,7 +18,7 @@ This Claude skill teaches Claude Code how to generate complete, polished Softr V
18
18
  - **All 14 Softr data sources** — Airtable, Softr Database, Google Sheets, HubSpot, Notion, Coda, monday.com, SmartSuite, ClickUp, Xano, Supabase, BigQuery, SQL Database, and REST API — each with field mapping, rate limits, and gotchas
19
19
  - **Helper blocks & cross-block patterns** — Invisible helper blocks for multi-table access via `window` globals + `CustomEvent`, `useWindowData` hook, breadcrumb navigation, saved views architecture
20
20
  - **Advanced integrations** — Shadow DOM CSS isolation for third-party libraries (Leaflet, Mapbox, TinyMCE, Quill, FullCalendar)
21
- - **Native header styling** — re-skin Softr's native top bar, nav, and dropdown menus via global Custom Code CSS (stable selectors vs. hashed classes, the dropdown blank-space grid fix, restyle-vs-replace) — distinct from blocks
21
+ - **Native shell styling** — re-skin Softr's native top bar, **footer**, nav, dropdowns, and **page background** via global Custom Code CSS (stable selectors vs. hashed classes, floating "island" header/footer, the dropdown grid fix, the multi-layer page-background stacking, restyle-vs-replace) — distinct from blocks
22
22
  - **UI/UX design guidelines** — 26 sections covering visual hierarchy, color, typography, spacing, motion design, accessibility, responsive patterns, and an AI slop anti-pattern checklist
23
23
  - **Self-validation** — Claude checks for Softr bundler compatibility (no optional chaining, correct imports, container wrappers, `getFieldValue()` wrapping, hooks ordering) before delivering code
24
24
  - **Premium visual baseline** — Every block ships polished from v1: gradient backgrounds, card elevation, loading skeletons, empty states, error states
@@ -185,9 +185,10 @@ softr-vibe-coding/
185
185
  │ │ # install, scope limitations)
186
186
  │ ├── advanced-integrations.md # Shadow DOM CSS isolation (69 lines)
187
187
  │ │ # Leaflet, Mapbox, TinyMCE, Quill, FullCalendar
188
- │ ├── native-chrome-styling.md # Restyle Softr's native header/nav/dropdowns
189
- │ │ # via global Custom Code CSS — stable selectors,
190
- │ │ # dropdown grid blank-space fix, restyle vs replace
188
+ │ ├── native-chrome-styling.md # Restyle Softr's native shell (header, footer,
189
+ │ │ # nav, dropdowns, page background) via global
190
+ │ │ # Custom Code CSS stable selectors, floating
191
+ │ │ # islands, dropdown grid fix, multi-layer page-bg
191
192
  │ ├── anti-patterns.md # Categorized violation catalog (86 lines)
192
193
  │ │ # Data access, mutations, hooks, layout,
193
194
  │ │ # permissions, helper blocks
package/SKILL.md CHANGED
@@ -86,7 +86,7 @@ When the user describes their block, figure out which of these areas apply and a
86
86
 
87
87
  - **Data source type**: Is it Airtable, Softr Database, REST API, or another source? This determines the data fetching approach. **Load the relevant data source guide** from the [datasources/](datasources/) directory before writing code.
88
88
  - **Data source fields**: For Airtable/Softr Database, you need actual field IDs. For REST APIs, you access the raw API response directly. If the user doesn't know field IDs:
89
- - For **Softr Database**, the cleanest path is the **Softr Database MCP server** — ask whether they have it installed (`claude mcp list` shows it as `softr` or similar). If yes, query schema directly with the MCP tools instead of asking for paste-ins. If no, fall back to asking them to paste the `tablespace-with-tables` network response (DevTools -> Network -> filter that string while on Studio's Data tab) — the JSON contains every field ID, type, and dropdown option UUID. Optionally tell them they can install the MCP once with `claude mcp add --transport http softr https://mcp.softr.io/mcp` for future sessions. Full MCP details in [references/softr-database-mcp.md](references/softr-database-mcp.md); fallback paste-in workflows in [datasources/fields.md](datasources/fields.md#field-inspector-block).
89
+ - For **Softr Database**, the cleanest path is the **Softr Database MCP server** — ask whether they have it installed (`claude mcp list` shows it as `softr` or similar). If yes, query schema directly with the MCP tools instead of asking for paste-ins. If no, the next-best option is the bundled **`get-softr-database` CLI script** — tell the user to run `python3 ~/.claude/skills/softr-vibe-coding/tools/get-softr-database.py <database_id>` (it prompts for their Softr API key and exports the full schema to `~/Desktop/softr-database-<id>-<timestamp>.json` — Python stdlib only, nothing to install) and paste the resulting JSON into chat. As a final fallback, ask them to paste the `tablespace-with-tables` network response (DevTools -> Network -> filter that string while on Studio's Data tab) — same JSON content, different acquisition path. Optionally tell them they can install the MCP once with `claude mcp add --transport http softr https://mcp.softr.io/mcp` for future sessions. Full MCP details in [references/softr-database-mcp.md](references/softr-database-mcp.md); CLI script details in [datasources/softr-database.md](datasources/softr-database.md#bundled-cli-script-get-softr-database); fallback paste-in workflows in [datasources/fields.md](datasources/fields.md#field-inspector-block).
90
90
  - For **Airtable** and other sources where empty `q.select({})` works, suggest the Field Inspector block.
91
91
  - **Brand colors**: Already resolved in Step 1 (Detect the brand source). Don't re-ask. The brand source is one of:
92
92
  - **Project's `./DESIGN.md`** (recommended for client work — produced by the `building-design-md` skill)
@@ -157,7 +157,7 @@ For advanced patterns beyond data fetching, load the relevant reference when the
157
157
  | Small reusable patterns — `localStorage` cross-page state, clipboard copy button | [references/common-patterns.md](references/common-patterns.md) |
158
158
  | 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) |
159
159
  | AI-assisted Softr DB schema discovery / field-ID lookup / record reads via the official Softr MCP server (sibling to the in-block `useRecords` workflow) | [references/softr-database-mcp.md](references/softr-database-mcp.md) |
160
- | Restyling Softr's **native header / top bar / nav / dropdown menus** (not a block it's Softr chrome, done with global Custom Code CSS): stable selectors vs. hashed classes, the dropdown blank-space grid fix, restyle-vs-replace tradeoffs | [references/native-chrome-styling.md](references/native-chrome-styling.md) |
160
+ | 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 | [references/native-chrome-styling.md](references/native-chrome-styling.md) |
161
161
 
162
162
  ## Code Structure
163
163
 
package/bin/cli.js CHANGED
@@ -10,7 +10,7 @@ var SETTINGS_FILE = path.join(os.homedir(), '.claude', 'settings.json');
10
10
  var PACKAGE_ROOT = path.resolve(__dirname, '..');
11
11
 
12
12
  var SKILL_FILES = ['SKILL.md', 'ui-ux-guidelines.md', 'README.md', 'LICENSE'];
13
- var SKILL_DIRS = ['references', 'datasources'];
13
+ var SKILL_DIRS = ['references', 'datasources', 'tools'];
14
14
 
15
15
  var HOOK_COMMAND = 'npx -y --prefer-online ' + SKILL_NAME + '@latest sync';
16
16
 
@@ -111,15 +111,17 @@ export default function Block() {
111
111
 
112
112
  1. **Softr Database MCP server (recommended for AI-assisted workflows)** -- if you're collaborating with an AI assistant (Claude Code, Claude Desktop, Cursor, ChatGPT, Mistral) to write Vibe Coding blocks, the official Softr MCP server is the cleanest path. The AI calls schema/list-fields tools directly against your workspace and reads back every field's `id`, `name`, `type`, and dropdown option UUIDs -- no copy-paste, no transcription errors. Full setup, scopes, and scope limitations (Softr DB only -- does NOT cover Airtable / external sources) in [../references/softr-database-mcp.md](../references/softr-database-mcp.md).
113
113
 
114
- 2. **Network inspector (full schema in one shot, no MCP needed)** -- in Studio's Data tab with browser DevTools open, filter Network requests by `tablespace-with-tables`. The Response JSON contains every table's complete schema, including:
114
+ 2. **`get-softr-database` CLI script (bundled, no MCP needed)** -- a Python CLI bundled with this skill at `~/.claude/skills/softr-vibe-coding/tools/get-softr-database.py`. Exports the full schema (every table, every field, all dropdown option UUIDs) to `~/Desktop/softr-database-<id>-<timestamp>.json`. Run with `python3 ~/.claude/skills/softr-vibe-coding/tools/get-softr-database.py <database_id>` (prompts for API key) or set `SOFTR_API_KEY=xxx` env var to skip the prompt. Stdlib only, no `pip install`. Best when you want a portable JSON dump for sharing in chat, archiving, or diffing across schema versions. Full usage in [softr-database.md](softr-database.md#bundled-cli-script-get-softr-database).
115
+
116
+ 3. **Network inspector (full schema in one shot, no MCP needed)** -- in Studio's Data tab with browser DevTools open, filter Network requests by `tablespace-with-tables`. The Response JSON contains every table's complete schema, including:
115
117
  - Each field's `id`, `name`, `type`, and `options`
116
118
  - For dropdown / SELECT fields: the full `choices` array with every option's `id` (UUID), `label`, and `color`
117
119
 
118
120
  Use this when scaffolding a block that needs many field IDs at once, or to look up dropdown option UUIDs needed for write payloads. **When working with an AI assistant without the MCP installed**, paste this JSON response into the chat -- second-best way to share accurate field IDs and dropdown UUIDs in one shot.
119
121
 
120
- 3. **Inline in Studio (one field at a time)** -- in the Data tab, click a field's name to open its edit drawer. The field ID appears next to the "Field name" label (e.g. `ID: 37fts`). Fastest for spot-checking a single field.
122
+ 4. **Inline in Studio (one field at a time)** -- in the Data tab, click a field's name to open its edit drawer. The field ID appears next to the "Field name" label (e.g. `ID: 37fts`). Fastest for spot-checking a single field.
121
123
 
122
- 4. **Softr Database REST API with `fieldNames=true`** -- runtime inspection from inside a Vibe Coding block (internal-portal blocks only, since this exposes a PAT in client code):
124
+ 5. **Softr Database REST API with `fieldNames=true`** -- runtime inspection from inside a Vibe Coding block (internal-portal blocks only, since this exposes a PAT in client code):
123
125
 
124
126
  ```jsx
125
127
  import { useEffect, useState } from "react";
@@ -24,11 +24,62 @@ q.select({ name: "First Name" })
24
24
  Find field IDs in this order of preference:
25
25
 
26
26
  1. **Softr Database MCP** (recommended when working with an AI assistant) — the AI calls schema/list-fields tools directly. See [../references/softr-database-mcp.md](../references/softr-database-mcp.md).
27
- 2. **Network inspector** — DevTools -> Network -> filter `tablespace-with-tables` for the full schema including dropdown option UUIDs. Paste the JSON into chat to share with an AI when the MCP isn't installed.
28
- 3. **Inline in Studio** — click a field's name in the Data tab; the ID appears in the field-edit drawer.
27
+ 2. **`get-softr-database` CLI script (bundled)** — a Python CLI bundled with this skill at `~/.claude/skills/softr-vibe-coding/tools/get-softr-database.py`. Exports the full schema (every table, field, dropdown option UUID) to `~/Desktop/softr-database-<id>-<timestamp>.json`. Stdlib only, no `pip install`. See [Bundled CLI script](#bundled-cli-script-get-softr-database) below.
28
+ 3. **Network inspector** — DevTools -> Network -> filter `tablespace-with-tables` for the full schema including dropdown option UUIDs. Paste the JSON into chat to share with an AI when the MCP isn't installed.
29
+ 4. **Inline in Studio** — click a field's name in the Data tab; the ID appears in the field-edit drawer.
29
30
 
30
31
  The generic Field Inspector pattern with empty `q.select({})` does NOT work for Softr Database — see [fields.md](fields.md#field-inspector-block).
31
32
 
33
+ ## Bundled CLI script: `get-softr-database`
34
+
35
+ A Python CLI bundled with this skill that exports a complete Softr Tables database schema (every table, every field, all dropdown option UUIDs) to a timestamped JSON file on your Desktop. Stdlib only — no `pip install` required.
36
+
37
+ **Script location after `npx softr-vibe-coding@latest init`:**
38
+
39
+ ```
40
+ ~/.claude/skills/softr-vibe-coding/tools/get-softr-database.py
41
+ ```
42
+
43
+ **Run it directly:**
44
+
45
+ ```bash
46
+ python3 ~/.claude/skills/softr-vibe-coding/tools/get-softr-database.py <database_id>
47
+ ```
48
+
49
+ It prompts for your Softr API key (input hidden via `getpass`). To skip the prompt entirely, pass via env var:
50
+
51
+ ```bash
52
+ SOFTR_API_KEY=xxx python3 ~/.claude/skills/softr-vibe-coding/tools/get-softr-database.py <database_id>
53
+ ```
54
+
55
+ Run with no args to be prompted for both the database ID and API key.
56
+
57
+ **Output:** `~/Desktop/softr-database-<databaseId>-<YYYYMMDD-HHMMSS>.json` containing:
58
+
59
+ ```json
60
+ {
61
+ "exportedAt": "...",
62
+ "source": "https://tables-api.softr.io/api/v1",
63
+ "databaseId": "...",
64
+ "database": { /* full database metadata */ },
65
+ "tableCount": N,
66
+ "fieldCount": M,
67
+ "tables": [ /* every table with its full fields[] array */ ]
68
+ }
69
+ ```
70
+
71
+ **Optional alias** for a shorter command. Add to your `~/.zshrc` or `~/.bashrc`:
72
+
73
+ ```bash
74
+ alias get-softr-database='python3 ~/.claude/skills/softr-vibe-coding/tools/get-softr-database.py'
75
+ ```
76
+
77
+ After `source ~/.zshrc`, just run `get-softr-database <database_id>` from anywhere.
78
+
79
+ **Get your Softr API key:** Softr workspace settings → API keys → create a new key with read access to the target database.
80
+
81
+ **When to use vs the MCP:** the MCP server is better for AI-assisted workflows (the assistant calls schema tools directly without any user action). This CLI script is better when you want a portable JSON file — for sharing in chat, archiving alongside your project, diffing across schema versions, or pasting a single big blob into Claude. The two approaches don't conflict; many projects use both.
82
+
32
83
  ## Supported Fields
33
84
 
34
85
  | Field Type | Writable | Notes |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "softr-vibe-coding",
3
- "version": "1.7.0",
3
+ "version": "1.9.0",
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"
@@ -11,6 +11,7 @@
11
11
  "ui-ux-guidelines.md",
12
12
  "references/",
13
13
  "datasources/",
14
+ "tools/",
14
15
  "LICENSE",
15
16
  "README.md",
16
17
  "CHANGELOG.md"
@@ -65,6 +65,7 @@ Run through this catalog before delivering any block. Every row is a violation o
65
65
  | Using `window.addEventListener("beforeunload", ...)` as the only unsaved-changes guard in a form block | Softr is a SPA. Internal nav (Softr's nav bar, sidebar links, `<NavigationAction>`) changes the route via the client-side router — `beforeunload` only fires on full page unload (tab close, refresh, external link), so the warning silently misses every in-app navigation. Use `useNavigationBlocker(isDirty)` from `@/lib/use-navigation-blocker` instead; it covers SPA nav AND browser unload with one API. Softr's Vibe Coding bundler often wires this automatically when a form is detected as dirty — you only need to add it manually for advanced cases (multi-step forms, custom dirty tracking, blocking on non-form state). See [common-patterns.md](common-patterns.md#navigation-blocker-for-unsaved-changes). |
66
66
  | Targeting Softr's hashed build classes (e.g. `.f8f11e5_m9ntthp`) when restyling the native header/nav from `custom-code-header.html` | Softr regenerates the hash on every deploy, so the rule silently dies. Target stable hooks: `.softr-topbar`, `.softr-nav-link`, `.softr-nav-button`, `.softr-nav-logo`, `#topbar-root`; for dropdown menus (no `softr-*` class) use the Radix/ARIA attrs `[role="menu"]` / `[role="menuitem"]` / `[role="group"]` / `[aria-expanded="true"]`, scoped under `.softr-topbar`. The native header is Softr chrome (main document), not a block — it can't be built as a Vibe Coding block. See [native-chrome-styling.md](native-chrome-styling.md). |
67
67
  | Softr nav dropdown panel shows a tall blank gap below the items, and `height: auto` won't shrink it | The items sit in a CSS grid Softr sets to `grid-auto-flow: column` with pre-sized empty row tracks (`grid-template-rows: 60px 60px…`). Override the flow on `.softr-topbar [role="menu"] [role="group"]`: `grid-auto-flow: row !important; grid-template-rows: none !important; grid-auto-rows: auto !important` (leave `grid-template-columns` to preserve the menu width). Verified June 2026. See [native-chrome-styling.md](native-chrome-styling.md). |
68
+ | Setting the page background on `body` (or any single element) — it appears to do nothing | Softr paints the SAME page fill on `html`, `body`, `#page-content`, AND a deeper class-less wrapper div, stacked — so styling one gets covered. Paint your backdrop on `html`, then clear the duplicates above it: `body`, `#page-content`, and `#page-content div` — but EXCLUDE the header subtree with `:not(.softr-topbar):not(.softr-topbar *)` (it renders inside `#page-content`, and `#page-content`'s id specificity would otherwise flatten the dropdown panel). Verified June 2026. See [native-chrome-styling.md](native-chrome-styling.md). |
68
69
 
69
70
  ## Permissions
70
71
 
@@ -1,6 +1,8 @@
1
- # Styling Softr's Native Chrome (Header / Top Bar / Nav / Dropdowns)
1
+ # Styling Softr's Native Shell (Header · Footer · Page Background) via Custom Code
2
2
 
3
- **This is NOT about Vibe Coding blocks.** Softr's top bar, navigation, and its dropdown menus are *native chrome* — configured in Softr Studio and rendered in the **main document**, not inside a block's shadow DOM. You **cannot** build or replace the global header as a Vibe Coding block. To re-skin it, add **CSS to Settings → Custom Code → Code inside header** (the same place brand fonts/tokens live, i.e. the `custom-code-header.html` produced by `building-design-md`). Pure CSS — no markup, no JS — and the native bar stays in place, so Softr's auth-aware nav (account menu, sign-out, user-group gating) keeps working.
3
+ **This is NOT about Vibe Coding blocks.** Softr's top bar, navigation, dropdown menus, footer, and the page background are part of the *native app shell* — configured in Softr Studio and rendered in the **main document**, not inside a block's shadow DOM. You **cannot** build or replace them as a Vibe Coding block. To re-skin them, add **CSS to Settings → Custom Code → Code inside header** (the same place brand fonts/tokens live, i.e. the `custom-code-header.html` produced by `building-design-md`). Pure CSS — no markup, no JS — and the native chrome stays in place, so Softr's auth-aware nav (account menu, sign-out, user-group gating) keeps working.
4
+
5
+ This doc covers the **header / nav / dropdowns**, the **footer**, the **floating "island" treatment** for both, and the **page background** — which is trickier than it looks, because Softr stacks the same fill on several layers.
4
6
 
5
7
  > **Mirror of the block rule.** Global `custom-code-header.html` CSS reaches native chrome (main document) but **not** blocks (shadow DOM). Inside a block you apply brand styles inline; for native chrome you apply them with this global CSS. (See [anti-patterns.md](anti-patterns.md) for the block side.)
6
8
 
@@ -100,9 +102,115 @@ Center each item's text and drop the empty description slot Softr reserves:
100
102
  .softr-topbar [role="menu"] [role="menuitem"] div:empty { display: none !important; }
101
103
  ```
102
104
 
105
+ ## Floating "island" header
106
+
107
+ To turn a full-bleed bar into a floating, rounded "island" (inset from the edges): make the sticky root + Softr's Studio wrappers transparent so only the bar paints, then constrain + round + shadow the bar itself.
108
+
109
+ ```css
110
+ /* The sticky root and its Studio wrappers paint full-width — clear them so only the
111
+ bar shows, floating over the page. */
112
+ #topbar-root,
113
+ #topbar-root > div,
114
+ #topbar-root > div > div { background: transparent !important; }
115
+
116
+ #topbar-root { padding: 24px 16px 0 !important; } /* gap above + at the sides */
117
+
118
+ .softr-topbar {
119
+ max-width: 1200px !important;
120
+ margin: 0 auto !important; /* center the island */
121
+ border-radius: 22px !important;
122
+ box-shadow: 0 12px 30px rgba(0, 0, 0, 0.28) !important;
123
+ }
124
+
125
+ /* Optional: group the nav cluster toward the right-of-center */
126
+ .softr-topbar [role="menubar"] { justify-content: flex-end !important; }
127
+ ```
128
+
129
+ ## Footer
130
+
131
+ **The native footer has NO `softr-*` class — only `f8f11e5_*` hashes.** Target the semantic **`<footer>`** element instead. Safe: Vibe Coding blocks are shadow-DOM isolated, so `footer { … }` reaches only Softr's native footer, never a block.
132
+
133
+ ```css
134
+ footer {
135
+ max-width: 1200px !important;
136
+ margin: 24px auto !important; /* inset → elevated "island" card */
137
+ border-radius: 22px !important;
138
+ background-color: #02006C !important; /* e.g. navy, to match a navy header island */
139
+ color: #FFFFFF !important;
140
+ box-shadow: 0 14px 34px rgba(0, 0, 0, 0.18) !important;
141
+ }
142
+ footer * { color: inherit !important; } /* recolor all footer text/links white in one shot */
143
+ ```
144
+
145
+ **Footer contact column wraps the email mid-word.** Softr pins that column to a fixed `width: 160px` with `overflow-wrap: break-word`, so a long email splits across lines. Fix by no-wrapping the links (target by their stable `tel:` / `mailto:` href) and letting the column grow to content:
146
+
147
+ ```css
148
+ footer a[href^="tel:"],
149
+ footer a[href^="mailto:"] { white-space: nowrap !important; }
150
+ footer [role="list"],
151
+ footer [role="list"] > div,
152
+ footer [role="list"] > div > div {
153
+ width: auto !important;
154
+ min-width: max-content !important;
155
+ max-width: none !important;
156
+ }
157
+ ```
158
+
159
+ ## Page background
160
+
161
+ **The trickiest one — Softr paints the SAME fill on FOUR stacked layers:** `html`, `body`, `#page-content` (stable id; classes `content spr-content-root`), AND a deeper **class-less wrapper div** nested a few levels inside `#page-content`. Style any one layer and the ones above cover it — this is why setting `body` alone appears to "do nothing."
162
+
163
+ **Pattern: paint the backdrop on the bottom layer (`html`), then clear the duplicate fills off everything stacked above it.**
164
+
165
+ ```css
166
+ /* 1. Paint the backdrop on the bottom layer. A layered "combo" reads premium:
167
+ soft glow + faint dot-grid + base gradient. background-image order = top→bottom. */
168
+ html {
169
+ background-color: #F0F3FC !important; /* fallback base */
170
+ background-image:
171
+ radial-gradient(75rem 42rem at 50% -12%, rgba(155, 35, 208, 0.08), transparent 60%), /* glow */
172
+ radial-gradient(rgba(2, 0, 108, 0.045) 1px, transparent 1.6px), /* dot-grid */
173
+ linear-gradient(180deg, #E7EAFB 0%, #F2F4FD 45%, #FAFBFF 100%) !important; /* gradient */
174
+ background-size: 100% 100%, 24px 24px, 100% 100% !important;
175
+ background-repeat: no-repeat, repeat, no-repeat !important;
176
+ background-attachment: fixed !important; /* calm while content scrolls */
177
+ }
178
+
179
+ /* 2. Clear the duplicate fills stacked above <html> so the backdrop shows through —
180
+ but EXCLUDE the header subtree (see gotcha). The inner content wrapper is
181
+ class-less and nested deep, so clear ALL divs inside #page-content. */
182
+ body,
183
+ #page-content { background-color: transparent !important; background-image: none !important; }
184
+ #page-content div:not(.softr-topbar):not(.softr-topbar *) {
185
+ background-color: transparent !important;
186
+ background-image: none !important;
187
+ }
188
+ ```
189
+
190
+ **Gotcha — don't clear the header into oblivion.** The header (`#topbar-root` → `.softr-topbar`, *including its dropdown panel*) renders **inside** `#page-content`, so a blanket `#page-content div { background: transparent }` flattens the dropdown's white panel too. And `#page-content`'s **id specificity (1,0,1) out-specifies** class/attr rules like `.softr-topbar [role="menu"]` (0,2,0) — so the clear wins silently and your earlier menu styling vanishes. Always exclude the header subtree: `:not(.softr-topbar):not(.softr-topbar *)`. (Cards are shadow-DOM blocks → their backgrounds are untouched; the footer is a `<footer>`, not a div → safe.)
191
+
103
192
  ## Finding the element to target
104
193
 
105
- DevTools can't right-click a menu that closes on blur. Freeze it: in the Console run `setTimeout(function () { debugger; }, 4000)`, open the menu within 4s, then — paused — element-pick the panel and read the **Computed** tab to see which element holds a fixed `height` / `grid-template-rows`. Resume with the ▶ button. ("Emulate a focused page" in the Elements `:hov` menu is a lighter alternative for blur-close menus.)
194
+ **Transient menus** (close on blur) — DevTools can't right-click them. Freeze: in the Console run `setTimeout(function () { debugger; }, 4000)`, open the menu within 4s, then — paused — element-pick the panel and read the **Computed** tab. Resume with the ▶ button. ("Emulate a focused page" in the Elements `:hov` menu is a lighter alternative.)
195
+
196
+ **Which element paints a background** — the element-picker keeps grabbing a *transparent* overlay sitting on top, so scan instead. Paste this in the Console; it lists `html`, `body`, and every large element with a real background color (tag / id / class / color / size), so you can spot the actual painter and its stable hook:
197
+
198
+ ```js
199
+ (function () {
200
+ var hits = [];
201
+ var all = document.querySelectorAll('html, body, body *');
202
+ for (var i = 0; i < all.length; i++) {
203
+ var el = all[i], bg = getComputedStyle(el).backgroundColor, r = el.getBoundingClientRect();
204
+ if (bg !== 'rgba(0, 0, 0, 0)' && bg !== 'transparent' &&
205
+ (el === document.documentElement || el === document.body || (r.width > 1200 && r.height > 400))) {
206
+ hits.push({ tag: el.tagName.toLowerCase(), id: el.id || '',
207
+ cls: (typeof el.className === 'string' ? el.className : ''),
208
+ bg: bg, size: Math.round(r.width) + 'x' + Math.round(r.height) });
209
+ }
210
+ }
211
+ console.table(hits);
212
+ })();
213
+ ```
106
214
 
107
215
  ## Restyle vs. replace
108
216
 
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ get-softr-database
4
+ ==================
5
+ Export a full Softr Tables database **schema** (every table, every field, and all
6
+ of their details) to a timestamped JSON file on your Desktop.
7
+
8
+ What it does
9
+ ------------
10
+ Given a Softr API key and a database ID, it calls two endpoints:
11
+
12
+ GET /api/v1/databases/{id} -> database metadata
13
+ GET /api/v1/databases/{id}/tables -> all tables, each with its full
14
+ `fields` array (type, options,
15
+ choices, formulas, AI settings, ...)
16
+
17
+ and writes the combined result to:
18
+
19
+ ~/Desktop/softr-database-<databaseId>-<YYYYMMDD-HHMMSS>.json
20
+
21
+ Usage
22
+ -----
23
+ get-softr-database # prompts for DB ID, then API key (hidden)
24
+ get-softr-database <database_id> # prompts only for the API key
25
+ SOFTR_API_KEY=xxx get-softr-database <database_id> # fully non-interactive
26
+
27
+ The API key may also be supplied via the SOFTR_API_KEY environment variable, so
28
+ it never has to be typed (or stored) in plain text.
29
+
30
+ Only the Python standard library is used — no `pip install` required.
31
+ """
32
+
33
+ import os
34
+ import sys
35
+ import json
36
+ import getpass
37
+ import datetime
38
+ import urllib.request
39
+ import urllib.error
40
+
41
+ API_BASE = "https://tables-api.softr.io/api/v1"
42
+ TIMEOUT_SECONDS = 60
43
+
44
+
45
+ def api_get(path, api_key):
46
+ """GET a Softr Tables API path and return the parsed JSON body."""
47
+ req = urllib.request.Request(
48
+ API_BASE + path,
49
+ headers={
50
+ "Softr-Api-Key": api_key,
51
+ "Content-Type": "application/json",
52
+ },
53
+ method="GET",
54
+ )
55
+ try:
56
+ with urllib.request.urlopen(req, timeout=TIMEOUT_SECONDS) as resp:
57
+ return json.loads(resp.read().decode("utf-8"))
58
+ except urllib.error.HTTPError as e:
59
+ body = e.read().decode("utf-8", "replace")
60
+ hint = ""
61
+ if e.code in (401, 403):
62
+ hint = "\n (Check that the API key is correct and has access to this database.)"
63
+ elif e.code == 404:
64
+ hint = "\n (Check that the database ID is correct.)"
65
+ raise SystemExit(f"\n[x] HTTP {e.code} on GET {path}\n {body}{hint}")
66
+ except urllib.error.URLError as e:
67
+ raise SystemExit(f"\n[x] Network error on GET {path}: {e.reason}")
68
+ except json.JSONDecodeError:
69
+ raise SystemExit(f"\n[x] Could not parse JSON response from GET {path}")
70
+
71
+
72
+ def prompt_database_id():
73
+ if len(sys.argv) > 1 and sys.argv[1].strip():
74
+ return sys.argv[1].strip()
75
+ try:
76
+ value = input("Softr database ID: ").strip()
77
+ except (EOFError, KeyboardInterrupt):
78
+ raise SystemExit("\n[x] Cancelled.")
79
+ if not value:
80
+ raise SystemExit("[x] No database ID provided.")
81
+ return value
82
+
83
+
84
+ def prompt_api_key():
85
+ value = os.environ.get("SOFTR_API_KEY", "").strip()
86
+ if value:
87
+ return value
88
+ try:
89
+ value = getpass.getpass("Softr API key (input hidden): ").strip()
90
+ except (EOFError, KeyboardInterrupt):
91
+ raise SystemExit("\n[x] Cancelled.")
92
+ if not value:
93
+ raise SystemExit("[x] No API key provided.")
94
+ return value
95
+
96
+
97
+ def output_dir():
98
+ desktop = os.path.join(os.path.expanduser("~"), "Desktop")
99
+ return desktop if os.path.isdir(desktop) else os.path.expanduser("~")
100
+
101
+
102
+ def main():
103
+ database_id = prompt_database_id()
104
+ api_key = prompt_api_key()
105
+
106
+ print(f"\n-> Fetching database {database_id} ...")
107
+ db = api_get(f"/databases/{database_id}", api_key).get("data", {}) or {}
108
+ print(f" Database: {db.get('name', '(unknown)')} "
109
+ f"({db.get('tablesCount', '?')} tables reported)")
110
+
111
+ print("-> Fetching tables and fields ...")
112
+ tables = api_get(f"/databases/{database_id}/tables", api_key).get("data", []) or []
113
+ total_fields = sum(len(t.get("fields", []) or []) for t in tables)
114
+ print(f" Retrieved {len(tables)} tables, {total_fields} fields total.")
115
+
116
+ now = datetime.datetime.now()
117
+ payload = {
118
+ "exportedAt": now.isoformat(timespec="seconds"),
119
+ "source": API_BASE,
120
+ "databaseId": database_id,
121
+ "database": db,
122
+ "tableCount": len(tables),
123
+ "fieldCount": total_fields,
124
+ "tables": tables,
125
+ }
126
+
127
+ stamp = now.strftime("%Y%m%d-%H%M%S")
128
+ filename = f"softr-database-{database_id}-{stamp}.json"
129
+ path = os.path.join(output_dir(), filename)
130
+
131
+ with open(path, "w", encoding="utf-8") as f:
132
+ json.dump(payload, f, indent=2, ensure_ascii=False)
133
+
134
+ print(f"\n[ok] Saved schema to:\n {path}")
135
+
136
+ # Brief per-table summary so the result is readable at a glance.
137
+ if tables:
138
+ print("\n Tables:")
139
+ for t in tables:
140
+ print(f" - {t.get('name', '(unnamed)')}: "
141
+ f"{len(t.get('fields', []) or [])} fields")
142
+
143
+
144
+ if __name__ == "__main__":
145
+ main()