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 +7 -0
- package/README.md +5 -4
- package/SKILL.md +2 -2
- package/bin/cli.js +1 -1
- package/datasources/fields.md +5 -3
- package/datasources/softr-database.md +53 -2
- package/package.json +2 -1
- package/references/anti-patterns.md +1 -0
- package/references/native-chrome-styling.md +111 -3
- package/tools/get-softr-database.py +145 -0
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
|
|
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
|
|
189
|
-
│ │ #
|
|
190
|
-
│ │ #
|
|
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,
|
|
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 /
|
|
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
|
|
package/datasources/fields.md
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
28
|
-
3. **
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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()
|