caracms 0.1.0 → 0.3.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/README.md +247 -3
- package/bin/caracms.mjs +32 -16
- package/bin/cloudflare-ips.mjs +38 -0
- package/bin/gate.mjs +277 -4
- package/bin/init.mjs +65 -20
- package/bin/templates/catch-all.astro.txt +3 -2
- package/package.json +1 -1
- package/skills/caracms-adopt/SKILL.md +17 -3
- package/skills/caracms-adopt/look-same.mjs +24 -11
- package/skills/caracms-blocks/SKILL.md +69 -4
- package/skills/caracms-content/SKILL.md +2 -1
- package/skills/caracms-tina/SKILL.md +431 -0
- package/skills/caracms-tina/field-kinds.md +88 -0
- package/skills/caracms-tina/tina-to-caracms.mjs +1011 -0
- package/src/block-runs.ts +84 -0
- package/src/cli/validate.ts +10 -5
- package/src/components/BlockRunTree.astro +45 -0
- package/src/components/Blocks.astro +20 -1
- package/src/components/Form.astro +76 -10
- package/src/components/Head.astro +4 -2
- package/src/components/Media.astro +27 -1
- package/src/components/RichText.astro +12 -4
- package/src/components/Text.astro +32 -9
- package/src/components/form-client.ts +7 -4
- package/src/components/sitemap.xml.ts +2 -1
- package/src/config.ts +32 -1
- package/src/content-schema.ts +7 -4
- package/src/content.ts +28 -0
- package/src/document.ts +15 -2
- package/src/editor/client/block-list/summary.ts +7 -1
- package/src/editor/client/editor.ts +24 -6
- package/src/editor/client/fields/HiddenToggle.tsx +51 -0
- package/src/editor/client/fields/LinkField.tsx +17 -12
- package/src/editor/client/fields/ReferenceField.tsx +6 -0
- package/src/editor/client/fields/RichTextField.tsx +38 -32
- package/src/editor/client/fields/host.tsx +5 -0
- package/src/editor/client/fields/parts.tsx +2 -1
- package/src/editor/client/fields/rich-text-doc.ts +28 -2
- package/src/editor/client/fields/rich-text-schema.ts +41 -2
- package/src/editor/client/live/publish.tsx +20 -10
- package/src/editor/client/nav/DocRow.tsx +6 -0
- package/src/editor/client/overlays/rich-text-bar.ts +6 -6
- package/src/editor/client/pane/levels/DocumentLevel.tsx +3 -0
- package/src/editor/client/pane/parts.tsx +34 -2
- package/src/editor/client/preview/driver.ts +5 -5
- package/src/editor/client/preview/field-text.ts +40 -2
- package/src/editor/client/preview/page-edit.ts +2 -2
- package/src/editor/client/preview/page-editor-registry.ts +4 -3
- package/src/editor/client/preview/page-editor.ts +3 -3
- package/src/editor/client/state/document-store.ts +20 -2
- package/src/editor/client/state/keeper.ts +51 -6
- package/src/editor/client/state/live.ts +45 -6
- package/src/editor/middleware.ts +3 -2
- package/src/editor/routes/try-again.ts +2 -1
- package/src/editor/server/admin-props.ts +2 -1
- package/src/editor/server/mode.ts +13 -3
- package/src/editor/styles/editor.css +2 -0
- package/src/editor/styles/levels/live.css +1 -1
- package/src/editor/types.ts +27 -6
- package/src/fields.ts +63 -15
- package/src/files-route.ts +9 -4
- package/src/forms/endpoint.ts +11 -16
- package/src/forms/rules.ts +9 -1
- package/src/head.ts +17 -4
- package/src/index.ts +53 -7
- package/src/links.ts +35 -0
- package/src/live/README.md +5 -2
- package/src/live/auth/auth.ts +4 -1
- package/src/live/auth/guard.ts +3 -2
- package/src/live/auth/index.ts +6 -7
- package/src/live/auth/login.ts +17 -8
- package/src/live/auth/middleware.ts +7 -3
- package/src/live/auth/request.ts +92 -11
- package/src/live/auth/views.ts +9 -4
- package/src/live/autosaves.ts +59 -16
- package/src/live/backend.ts +54 -24
- package/src/live/branches.ts +19 -0
- package/src/live/drafts-store.ts +4 -3
- package/src/live/editor.ts +92 -67
- package/src/live/git.ts +112 -34
- package/src/live/github.ts +128 -13
- package/src/live/locks.ts +92 -26
- package/src/live/mode.ts +177 -18
- package/src/live/notices.ts +100 -0
- package/src/live/redis.ts +161 -39
- package/src/live/runtime.ts +15 -4
- package/src/live/status-cache.ts +219 -0
- package/src/live/status.ts +14 -2
- package/src/live/timings.ts +24 -0
- package/src/media.ts +40 -7
- package/src/read.ts +2 -1
- package/src/rich-text.ts +30 -5
- package/src/serialize.ts +10 -1
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: caracms-tina
|
|
3
|
+
description: Port a site built on self-hosted Tina (a Caracode Tina template site, with `tina/`, `server.mjs` and pages as JSON read through `src/lib/sections.ts`) onto caracms — strip Tina, run the reference conversion script (`tina-to-caracms.mjs`, re-runnable on newer content), rewrite each block onto `Block`, `Text`, `RichText` and `Media` so text is typed on the page, carry page-wide rules and cards into the page component, deploy to a branch environment that can't publish to the live site, and prove visitors' pages look the same. Use when porting a Tina site, re-running its conversion, or adapting the script.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Porting a Tina site onto caracms
|
|
7
|
+
|
|
8
|
+
**The bar: visitors' pages look the same.** The port changes what editors see, not what
|
|
9
|
+
visitors see. Any redesign is separate work, after the switch.
|
|
10
|
+
|
|
11
|
+
**The port is real, and the live site keeps moving.** The client keeps editing in Tina
|
|
12
|
+
while the port's branch is open. So content is never converted by hand: the site's own
|
|
13
|
+
copy of the conversion script writes `content/`, and at the switch Tina is frozen, the
|
|
14
|
+
script is re-run on the latest `main`, and the branch is merged. Anything you'd fix in a
|
|
15
|
+
converted file, fix in the script (or the site's adapt table) and re-run.
|
|
16
|
+
|
|
17
|
+
Read with this skill:
|
|
18
|
+
|
|
19
|
+
- `caracms-blocks`: blocks, fields, collections, the page component, forms.
|
|
20
|
+
- `caracms-adopt`: `init` in detail, the look-same script (section 9), motion under the
|
|
21
|
+
preview (section 8). This skill is the Tina-specific order; adopt's page-by-page
|
|
22
|
+
method doesn't apply, since the script converts every page at once.
|
|
23
|
+
- `caracms-content`: the content files the script writes.
|
|
24
|
+
- Beside this file: **`tina-to-caracms.mjs`**, the reference script (its header is its
|
|
25
|
+
manual), and **`field-kinds.md`**, the mapping table: how each Tina field kind becomes a
|
|
26
|
+
caracms field and value.
|
|
27
|
+
|
|
28
|
+
Commands run from the site's root: `npx caracms …` (or `pnpm caracms …`).
|
|
29
|
+
|
|
30
|
+
**Swapping in a new packed tarball** at the same `file:` path (`vendor/caracms.tgz`):
|
|
31
|
+
`pnpm install`, even with `--force`, keeps the old copy, since the lockfile holds the old
|
|
32
|
+
file's integrity. Remove and add it again:
|
|
33
|
+
`pnpm remove caracms && pnpm add caracms@file:vendor/caracms.tgz`.
|
|
34
|
+
|
|
35
|
+
## 0. Before anything moves: the block table
|
|
36
|
+
|
|
37
|
+
A template site has its own block templates (`_template` names, camelCase). Propose the
|
|
38
|
+
**old-to-new block table** to Rich and get it approved before content moves: each Tina
|
|
39
|
+
template, the caracms block key it becomes, and whether it maps one to one, splits or
|
|
40
|
+
is flattened. Keys are kebab case (`textMedia` → `text-media`, the script's default);
|
|
41
|
+
rename where the editor's label reads better (`features` → `steps`, `fullMedia` →
|
|
42
|
+
`wide-media`). The approved renames go into the script's adapt table (`blockRenames`).
|
|
43
|
+
|
|
44
|
+
Also list, for the same approval: the fields that will be **edited in the sidebar only**
|
|
45
|
+
(display text fitted to its box, split-letter headings, a word drawn as artwork), and
|
|
46
|
+
anything the template does that caracms has no place for yet.
|
|
47
|
+
|
|
48
|
+
## 1. The order
|
|
49
|
+
|
|
50
|
+
Work on a branch (`caracms-port`, say) in a worktree of the site. Commit after each step.
|
|
51
|
+
|
|
52
|
+
1. **Bump Astro on its own commit** to the version caracms needs (its peer dependency:
|
|
53
|
+
`npm view caracms peerDependencies`, or the tarball's `package.json` while the package
|
|
54
|
+
is unpublished or the port runs on a packed tarball). Build and check the site still looks the same
|
|
55
|
+
before anything else changes. If the site pins its own `vite`, bump it with Astro:
|
|
56
|
+
left behind, it leaves a second Vite in the tree and `astro check` fails in the Astro
|
|
57
|
+
config. **Bump `.nvmrc` and `engines.node` in `package.json` with Astro** (Astro 7.4
|
|
58
|
+
needs Node 22.23.4 or later, and so does caracms): a host that reads them builds on
|
|
59
|
+
the older Node otherwise (Railway fails with exit 1 and no error text in the build
|
|
60
|
+
log).
|
|
61
|
+
2. **Strip Tina** (section 3): `tina/`, `server.mjs`, `server/`, Tina's routes and admin,
|
|
62
|
+
**including the untracked `tina/__generated__/` and `public/admin/`** (they're in
|
|
63
|
+
`.gitignore`; delete them from disk too, or `init` and the build trip on them).
|
|
64
|
+
3. **Delete `content/`** (git keeps it). `init` refuses a `content/` that isn't caracms's.
|
|
65
|
+
4. **Switch the adapter to standalone:** `@astrojs/node` with `mode: 'standalone'`.
|
|
66
|
+
`caracms start` serves `dist/server/entry.mjs` and refuses any other mode.
|
|
67
|
+
5. **`npx caracms init`**, read the dry run, then **`npx caracms init --write`** and install
|
|
68
|
+
again (caracms-adopt → section 1). It links the four skills, writes the config, the
|
|
69
|
+
CLAUDE.md block and the health route. **A template site already has a
|
|
70
|
+
`.railway/railway.ts` that describes production: leave it alone and never apply it**
|
|
71
|
+
(applying it can switch production's CDN off). `init` treats a site that has one as
|
|
72
|
+
a Railway site and leaves the file untouched, which is the expected outcome. Set the
|
|
73
|
+
branch environment up with the Railway CLI or MCP, scoped to that environment
|
|
74
|
+
(section 9).
|
|
75
|
+
6. **Copy the script and run it** (section 2) against a **built** checkout of `main`.
|
|
76
|
+
First run with `--scaffold-config` for a starting `caracms.config.ts`. It names a
|
|
77
|
+
component per block before any is written, so nothing builds until they exist: point
|
|
78
|
+
every block's `component` at one stub first, then swap each in as it's rewritten.
|
|
79
|
+
7. **Rewrite the blocks** onto caracms's components (section 4), the page component
|
|
80
|
+
(section 5), the routes (section 6), forms (section 8).
|
|
81
|
+
8. **`npx caracms validate`**: no errors and no warnings, the missing-`w`/`h` warning
|
|
82
|
+
included (section 7 fills sizes in).
|
|
83
|
+
9. **Look-same** every public page against `main` at the commit the script last ran on
|
|
84
|
+
(section 11), then the branch environment (section 9).
|
|
85
|
+
|
|
86
|
+
## 2. The conversion script
|
|
87
|
+
|
|
88
|
+
Copy `node_modules/caracms/skills/caracms-tina/tina-to-caracms.mjs` into the site's own
|
|
89
|
+
`scripts/`. The site's copy is the one that runs; it's committed with the site.
|
|
90
|
+
|
|
91
|
+
**Adapt only the table under `ADAPT HERE`** (the script's header says what each entry is):
|
|
92
|
+
the approved `blockRenames`, `fieldDefaults` (values Tina's files leave out but the
|
|
93
|
+
template's mapper code read as set: a toggle that defaulted on, a per-block background
|
|
94
|
+
default that isn't the first option), `addressFields`, `headingFields`, `docTitleAlt`,
|
|
95
|
+
the file-link pattern, and Tina's home file. **`addressFields` matters:** Tina's schema has
|
|
96
|
+
no marker for a link field (a custom component serialises to `ui: {}`), so only strings
|
|
97
|
+
named `href` are known to be addresses; list every other one. Everything below the table is generic and
|
|
98
|
+
driven by Tina's generated schema; never add per-block code. If a block needs something
|
|
99
|
+
the table can't say, that's a gap in the script: change the generic part, and say so in
|
|
100
|
+
your report so it reaches the package's copy.
|
|
101
|
+
|
|
102
|
+
Run it from the site's root:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
node scripts/tina-to-caracms.mjs --from ../share-web-main --scaffold-config
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
- `--from` is a checkout of the Tina site's `main` that has been **built** (`pnpm build`,
|
|
109
|
+
or the template's `build:local`), so `tina/__generated__/_schema.json` exists. Only that
|
|
110
|
+
schema is needed: the site's own build may fail there for lack of env, and that's fine.
|
|
111
|
+
If `build:local` stops on a busy port 9000 (Tina's datalayer), pass
|
|
112
|
+
`--datalayer-port <another>` to the Tina site's own `build:local` / `tinacms build`, not
|
|
113
|
+
to this script (it rejects unknown arguments).
|
|
114
|
+
- Run it after `caracms init --write` and an install: it borrows the installed package's
|
|
115
|
+
image sniffer and S3 signer.
|
|
116
|
+
- `--scaffold-config` writes a starting `caracms.config.ts` from Tina's schema, with
|
|
117
|
+
Tina's labels and help. **Once.** After that the config and the block components are
|
|
118
|
+
the site's own, hand-maintained, and a normal run never touches them (section 4 says
|
|
119
|
+
which options only you can add).
|
|
120
|
+
- `--recipients <address>` replaces every form's recipients in Site Settings with one
|
|
121
|
+
address. **Always pass it for a branch environment**: the converted Site Settings name
|
|
122
|
+
the client, and a test submission would email them. The run at the switch leaves it out.
|
|
123
|
+
|
|
124
|
+
**What it owns: `content/`, nothing else.** Each run deletes `content/` and writes it again,
|
|
125
|
+
laid out the way the editor saves it, so the same Tina content gives byte-identical files
|
|
126
|
+
and `validate` finds nothing to reformat. **A re-run deletes `content/` whole**, so anything
|
|
127
|
+
published from the branch environment's editor is replaced by it. That is by design: Tina
|
|
128
|
+
stays the source until the switch. Beside it, it only adds to
|
|
129
|
+
`scripts/tina-media-sizes.json` (images' `w`/`h`, commit it) and copies file-link objects
|
|
130
|
+
in the bucket to caracms file keys (skipped when the key exists; it never deletes).
|
|
131
|
+
Without `S3_*` set, it still writes `content/` and lists the copies still to make (a
|
|
132
|
+
rulebook PDF, say): run it again with the environment's bucket credentials to make them.
|
|
133
|
+
|
|
134
|
+
To pick up newer Tina content: pull `main` in the `--from` checkout, build it, re-run,
|
|
135
|
+
commit `content/` and the size cache, and look-same again against that commit.
|
|
136
|
+
|
|
137
|
+
What the script converts (the full table is `field-kinds.md`): `_template` → `type`,
|
|
138
|
+
the section list → `blocks`, the home file → `index.json`, Tina's Markdown rich text →
|
|
139
|
+
caracms's (Tina's escapes such as `A\&M` and `mailto\:` undone), an image and its `…Alt`
|
|
140
|
+
sibling → one media value, a Mux clip → a video value, `linkLabel` + `href` → one
|
|
141
|
+
`link()`, references → document paths, Site Settings (`formNotifications` →
|
|
142
|
+
`formRecipients`, `adminBranding` → `brand`, `adminAccess` → `editors`, `defaultSeo` →
|
|
143
|
+
`seo`), file links → caracms file addresses, and Tina's `hidden` → `"hidden": true`
|
|
144
|
+
(section 6).
|
|
145
|
+
|
|
146
|
+
## 3. Tina's pieces and what replaces each
|
|
147
|
+
|
|
148
|
+
| The template's | Goes | Replaced by |
|
|
149
|
+
|---|---|---|
|
|
150
|
+
| `tina/`, `tina/__generated__/`, Tina's dependencies | yes | `caracms.config.ts` |
|
|
151
|
+
| `public/admin/`, `public/admin-previews/` | yes | `/admin` (caracms's editor) |
|
|
152
|
+
| `server.mjs`, `server/` (Express: forms, downloads, the admin, previews) | yes | `caracms start`; forms → `/_caracms/form` (section 8); downloads → caracms file addresses `/files/<12 hex>/<name>` |
|
|
153
|
+
| `src/pages/[...slug].astro` and other collection routes reading Tina | yes | caracms routes (section 6) |
|
|
154
|
+
| `src/lib/sections.ts` (the mappers: Tina document → component props) | yes, except page-wide logic | blocks draw from their own data; page-wide rules → the page component (section 5) |
|
|
155
|
+
| `Sections.astro` / `PageView` | yes | the page component and `<Blocks>` |
|
|
156
|
+
| `data-tina-field` on every element | yes | caracms's markers, added by `Block`, `Text`, `RichText`, `Media` in a preview only |
|
|
157
|
+
| `islandPreview.ts`, Tina's preview islands | yes | caracms's preview; site scripts listen for `caracms:rendered` (section 4) |
|
|
158
|
+
| `@astrojs/sitemap` | yes | caracms's `/sitemap.xml` (needs `SITE_URL`) |
|
|
159
|
+
| `.template-sync`, engine-sync scripts, the deploy-gate workflow, `@caracode/pins` | yes | nothing (`.carapin/` can stay as history) |
|
|
160
|
+
| The header, footer, motion, fonts, analytics tags, design-system routes, the site's own `src/lib/` logic | **stays** | |
|
|
161
|
+
|
|
162
|
+
**Tests:** delete tests of Tina's machinery, of the mappers, of deleted files, and of
|
|
163
|
+
`data-tina-field`. A test that guards site behaviour that still exists is rewritten
|
|
164
|
+
against caracms's content format; the template's helper that reads committed content is
|
|
165
|
+
usually the one to rewrite first. **Docs:** the engine and Tina docs go (Tina behaviour,
|
|
166
|
+
publishing, admin login, the media pipeline, downloads). The site's own half of
|
|
167
|
+
`docs/decisions.md` stays, rewritten where it describes Tina. CLAUDE.md loses the
|
|
168
|
+
engine/site split; `init` added its caracms block.
|
|
169
|
+
|
|
170
|
+
## 4. Rewriting a block onto caracms's components
|
|
171
|
+
|
|
172
|
+
**The default: every block draws through caracms's components**, keeping the template's
|
|
173
|
+
markup and classes exactly. That's what makes its text typed on the page in `/admin`.
|
|
174
|
+
(Keeping the template's components behind a reverse adapter, feeding them props from
|
|
175
|
+
caracms's data, is faster to look-same but leaves every field sidebar-only. Use it only
|
|
176
|
+
as a stepping stone, a block at a time.)
|
|
177
|
+
|
|
178
|
+
```astro
|
|
179
|
+
---
|
|
180
|
+
import Block from 'caracms/components/Block.astro';
|
|
181
|
+
import Media from 'caracms/components/Media.astro';
|
|
182
|
+
import RichText from 'caracms/components/RichText.astro';
|
|
183
|
+
import Text from 'caracms/components/Text.astro';
|
|
184
|
+
import type { BlockProps } from 'caracms/document';
|
|
185
|
+
import type { TextMediaData } from './text-media.ts';
|
|
186
|
+
|
|
187
|
+
type Props = BlockProps<TextMediaData>;
|
|
188
|
+
const { block, cms } = Astro.props;
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
<Block cms={cms} class={`section bg-${block.background}`} id={block.anchor || undefined}>
|
|
192
|
+
<Text cms={cms} field="heading" value={block.heading} class="heading text-balance" lineClass="block text-balance" />
|
|
193
|
+
<RichText cms={cms} field="body" value={block.body} class="prose" />
|
|
194
|
+
<Media cms={cms} field="media" value={block.media} sizes="(min-width: 1024px) 50vw, 100vw" class="media" />
|
|
195
|
+
</Block>
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
- **`Block`** is the block's root (`as="section"` by default; `as` and every attribute
|
|
199
|
+
pass through). Drop the template's `data-tina-field`.
|
|
200
|
+
- **`Text`** for a plain text field: `as` sets the tag; a field declared
|
|
201
|
+
`text({ heading: true })` is the page's `h1` when it's the first heading, else `h2`.
|
|
202
|
+
- **Lines** (`text({ lines: true })` in the config, `lineClass` on `Text`): for a heading
|
|
203
|
+
the template drew one element per typed line (its `typography.ts`/`Heading.astro`).
|
|
204
|
+
Two lines or more draw a `<span class={lineClass}>` each, blank lines draw nothing,
|
|
205
|
+
and a single line is drawn bare, as the template's `ui/Heading.astro` does. Typed on
|
|
206
|
+
the page, Shift+Enter still stores `\n`.
|
|
207
|
+
- **`RichText`** for rich text: semantic HTML with no classes, inside one wrapper (`as`,
|
|
208
|
+
default `div`). Style it from the wrapper's class (`.prose p`, `.prose strong`).
|
|
209
|
+
- **A rich-text heading** (`richText({ heading: true })`): for a heading the template
|
|
210
|
+
drew from rich text with bold as a highlight colour and each paragraph a line
|
|
211
|
+
(`ui/RichLine.astro`). It draws inline runs joined by `<br>` inside its own heading
|
|
212
|
+
tag (`h1` when it's the page's first heading), never `<p>`. It takes bold and line
|
|
213
|
+
breaks only, typed on the page. The template's highlight class becomes site CSS on
|
|
214
|
+
`strong` inside it: `.statement strong { color: var(--highlight); }`.
|
|
215
|
+
- **`Media`** for an image or a Mux clip: one `<img>` with a srcset (or the clip's
|
|
216
|
+
video). Fluid: `sizes` (and `ratio` to crop); fixed: `width`/`height`. For flat
|
|
217
|
+
artwork the template served as PNG (WebP smears it), declare the field
|
|
218
|
+
`media({ format: 'png' })`: every width is then `@png`.
|
|
219
|
+
- **YouTube**: a `link({ addressOnly: true })`; the block parses the id from the address.
|
|
220
|
+
- **Sidebar-only fields** (fitted display text, split letters, a word drawn as artwork):
|
|
221
|
+
draw the value yourself (`{block.word}`) instead of through `Text`, so it isn't typed
|
|
222
|
+
on the page. Text the template splits into letters for motion can be drawn unsplit in
|
|
223
|
+
the preview (`cms.preview`), where motion is off, and then typed on the page.
|
|
224
|
+
- **Help:** Tina's long block descriptions go into the block's `help` (one or two
|
|
225
|
+
sentences, shown at the top of the block in the sidebar). Field `help` stays short.
|
|
226
|
+
- **Site scripts and the preview:** the preview re-renders a block in place after each
|
|
227
|
+
edit (a morph, no reload). A script that measures or fits text (`fitText.ts`) re-runs
|
|
228
|
+
on it: `document.addEventListener('caracms:rendered', run)` (fired on the frame's
|
|
229
|
+
`document` after each morph, never on public pages). Motion follows caracms-adopt →
|
|
230
|
+
section 8.
|
|
231
|
+
|
|
232
|
+
The options only you can add (`--scaffold-config` doesn't know them): `heading: true` on
|
|
233
|
+
the block's heading field, `lines: true`, `richText({ heading: true })`,
|
|
234
|
+
`media({ format: 'png' })`, `media({ play: 'loop' })` for a hero's loop, `allowNoLink: true`
|
|
235
|
+
on buttons that may be a label alone ("Coming soon": `validate` warns on one until it's
|
|
236
|
+
set), select defaults from mapper code, and trimmed `help`.
|
|
237
|
+
|
|
238
|
+
## 5. The page component: page-wide rules and cards
|
|
239
|
+
|
|
240
|
+
The template's page-wide logic (`pageRules.ts`, `pageParts()`) moves into the page
|
|
241
|
+
component (`page:` in the config). It gets `document`, `collection`, `path` (the page's
|
|
242
|
+
path in its collection, `null` for none), `preview`, `data`, `settings` (Site
|
|
243
|
+
Settings), `listPage` and `read`.
|
|
244
|
+
|
|
245
|
+
- **A block sees only its own data.** Page-wide rules the Tina mappers applied across
|
|
246
|
+
blocks (dropping an anchor already used higher up the page, say) belong here, through
|
|
247
|
+
`<Blocks>`'s context or its `wrap` rule. Check whether today's content needs each one
|
|
248
|
+
before carrying it over.
|
|
249
|
+
- **Titles.** Tina template sites show Site Settings' title on every page without its own
|
|
250
|
+
`seo.title`, not `<page> · <brand>`: set `titleFallback: 'site'` in the config
|
|
251
|
+
(caracms-adopt → Keep the old title format). Check the `<title>` lines in look-same's
|
|
252
|
+
HTML diff.
|
|
253
|
+
- **Time zone.** A Tina template site keeps its zone in the `SITE_TIMEZONE` variable;
|
|
254
|
+
caracms reads `timeZone` in the config (default UTC) for the editor's and the inbox's
|
|
255
|
+
times. Copy production's value into the config.
|
|
256
|
+
- **The h1 and eager loading** are caracms's own: the first block with a heading holds
|
|
257
|
+
the `h1`, and the first two blocks' images load eagerly (`cms.first`, `cms.eager`). Drop
|
|
258
|
+
the template's versions.
|
|
259
|
+
- **Cards** (a run of neighbouring blocks drawn inside a band, each card holding several
|
|
260
|
+
blocks, the anchor `id` on the card): give `<Blocks>` a `wrap` rule. It gets the blocks
|
|
261
|
+
that render (`{ index, type, data }`, `data` as the block gets it) and returns runs:
|
|
262
|
+
`{ from, to, component, props, runs }`, document indices inclusive, with `runs` nested
|
|
263
|
+
inside (the cards inside a band). Each block inside keeps its own index, context and
|
|
264
|
+
preview markers, so it's selected, typed into and morphed as anywhere else.
|
|
265
|
+
|
|
266
|
+
```astro
|
|
267
|
+
---
|
|
268
|
+
import Blocks from 'caracms/components/Blocks.astro';
|
|
269
|
+
import type { BlockRun, PageProps, WrapBlocks } from 'caracms/document';
|
|
270
|
+
import CardBand from './CardBand.astro';
|
|
271
|
+
import Card from '../components/ui/Card.astro';
|
|
272
|
+
|
|
273
|
+
type Props = PageProps;
|
|
274
|
+
const { document, collection, preview = false, listPage } = Astro.props;
|
|
275
|
+
|
|
276
|
+
/** The template's pageParts(), on the blocks' `card` field: "start" opens a card, "continue" joins the one above. */
|
|
277
|
+
const cards: WrapBlocks = (blocks) => {
|
|
278
|
+
const bands: { from: number; to: number; cards: BlockRun[] }[] = [];
|
|
279
|
+
for (const b of blocks) {
|
|
280
|
+
const spot = b.data.card as string | undefined;
|
|
281
|
+
if (!spot || spot === 'none') continue;
|
|
282
|
+
let band = bands.at(-1);
|
|
283
|
+
// A band is a run of neighbouring carded blocks: a gap (an uncarded block) starts a new one.
|
|
284
|
+
if (!band || band.to !== b.index - 1) bands.push((band = { from: b.index, to: b.index, cards: [] }));
|
|
285
|
+
const card = band.cards.at(-1);
|
|
286
|
+
if (spot === 'start' || !card) band.cards.push({ from: b.index, to: b.index, component: Card, props: { id: (b.data.anchor as string) || undefined } });
|
|
287
|
+
else band.cards[band.cards.length - 1] = { ...card, to: b.index };
|
|
288
|
+
band.to = b.index;
|
|
289
|
+
}
|
|
290
|
+
return bands.map((band) => ({ from: band.from, to: band.to, component: CardBand, runs: band.cards }));
|
|
291
|
+
};
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
<Blocks document={document} collection={collection} preview={preview} listPage={listPage} wrap={cards} />
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
A wrapper component renders `<slot />` where the blocks go. Rules that break the runs'
|
|
298
|
+
order (overlapping, out of order, a card outside its band) stop the render with a
|
|
299
|
+
sentence: that's a bug in the rule. The block's anchor `id` goes on the card, not the
|
|
300
|
+
block, when the template put it there: section links jump to those ids.
|
|
301
|
+
|
|
302
|
+
## 6. Routes, hidden pages and the 404
|
|
303
|
+
|
|
304
|
+
`init` writes the pages' catch-all (`src/pages/[...path].astro`) once Tina's
|
|
305
|
+
`[...slug].astro` is gone (it refuses while another route catches the top of the site).
|
|
306
|
+
Write one route per other collection with an address (`src/pages/games/[slug].astro`) the
|
|
307
|
+
same way: `listDocuments` + `readDocument` + `RenderDocument`. Every `getStaticPaths`
|
|
308
|
+
leaves out **`e.warning`** (a file that can't be opened) and **`e.hidden`** (a hidden page,
|
|
309
|
+
or one under it):
|
|
310
|
+
|
|
311
|
+
```ts
|
|
312
|
+
const entries = (await listDocuments(site, 'pages')).filter((e) => !e.warning && !e.hidden);
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
**Hidden pages:** Tina's `hidden` becomes `"hidden": true` at the top of the document,
|
|
316
|
+
after its title. A hidden page isn't served (visitors get the site's own `404.astro`),
|
|
317
|
+
isn't in the sitemap, isn't offered by the link picker, and isn't in a block's
|
|
318
|
+
`cms.read.documents(…)`; pages nested under it are hidden with it. It stays editable,
|
|
319
|
+
previewable and publishable in `/admin`, marked "Hidden" in the pages list, with a
|
|
320
|
+
"Hidden from the site" switch in its page settings. The home page can't be hidden (the
|
|
321
|
+
script warns and leaves it shown). A link to a hidden page is a warning, not an error,
|
|
322
|
+
in the editor and in `validate`, naming the page.
|
|
323
|
+
|
|
324
|
+
## 7. Images
|
|
325
|
+
|
|
326
|
+
- **Keep the bucket keys.** The template's images are imgproxy URLs over the media bucket
|
|
327
|
+
(`…/unsafe/plain/s3://<bucket>/uploads/…`); the script keeps each `uploads/…` key, so no
|
|
328
|
+
image is copied and the bucket only grows.
|
|
329
|
+
- **`w`/`h` are found once** and cached in `scripts/tina-media-sizes.json` (commit it). The
|
|
330
|
+
first run needs the images reachable (their Tina URLs) or the bucket (`S3_*`); after
|
|
331
|
+
that the cache answers. `validate` warns on any image still missing its size.
|
|
332
|
+
- **imgproxy:** caracms signs every URL (`CARACMS_MEDIA_URL`, `IMGPROXY_KEY`,
|
|
333
|
+
`IMGPROXY_SALT`, `S3_BUCKET`). The template's production imgproxy is unsigned and its
|
|
334
|
+
URLs are cached by the CDN, so **never give it a key while Tina runs**: stand up a new,
|
|
335
|
+
signed imgproxy beside it, reading the same bucket. Without the media variables the
|
|
336
|
+
preview draws no images, and a build stops with a sentence naming them.
|
|
337
|
+
|
|
338
|
+
## 8. Forms
|
|
339
|
+
|
|
340
|
+
Both of a template's usual forms fit caracms's own: define them in the site's forms
|
|
341
|
+
(caracms-blocks → forms), and their submissions go to the inbox in `/admin`, with a
|
|
342
|
+
notification to Site Settings' `formRecipients`. A form drawn with `<Form>` is styled from
|
|
343
|
+
the site's CSS to the template's markup; its hooks (caracms-blocks → Forms: `submitClass`,
|
|
344
|
+
`thanks`, `errors`, `actions`, `labelPlaceholders`, attributes on the form, a textarea's
|
|
345
|
+
`rows`) carry the template's button classes, its wording and its layout, so no wrapper or
|
|
346
|
+
copied CSS is needed for them.
|
|
347
|
+
|
|
348
|
+
A form that keeps its own markup (a footer's newsletter signup) posts to caracms's
|
|
349
|
+
endpoint by its **wire contract**:
|
|
350
|
+
|
|
351
|
+
- `POST /_caracms/form`, urlencoded (or `multipart/form-data` when the form has a files
|
|
352
|
+
field; JSON is accepted too).
|
|
353
|
+
- `form`: the form's id. `return`: the page's path, where a no-script post comes back to.
|
|
354
|
+
- Each field by its name. `fax`: the honeypot, an empty text input hidden from people
|
|
355
|
+
(`tabindex="-1"`, off-screen); filled in, the post is dropped and answered as sent.
|
|
356
|
+
- `cf-turnstile-response`: Turnstile's token, added by Cloudflare's widget when the site
|
|
357
|
+
has `TURNSTILE_SITE_KEY` (at build) and `TURNSTILE_SECRET_KEY` (at run time). Without
|
|
358
|
+
the secret the endpoint accepts and logs it.
|
|
359
|
+
- Answers: with `Accept: application/json`, `200 {"ok":true}` or the status with
|
|
360
|
+
`{"error":"…"}` (400 a field, 403 the bot check, 429 too many, 502 storing failed);
|
|
361
|
+
without, a `303` to `<return>?sent=1`, or the error as plain text.
|
|
362
|
+
|
|
363
|
+
Not `/api/forms` with `source`/`redirectTo` (the template's): nothing stays a site
|
|
364
|
+
endpoint. The template's existing submissions stay in its own bucket and need no
|
|
365
|
+
conversion: caracms's inbox reads the template's records as they are (`<id>.json` with
|
|
366
|
+
`v: 1`, files at `<id>/<n>-<name>`).
|
|
367
|
+
|
|
368
|
+
## 9. The branch environment
|
|
369
|
+
|
|
370
|
+
The port is deployed from its branch to its own environment, **never production**. The
|
|
371
|
+
template sites run on Railway, so this section is written for it. A site on another host
|
|
372
|
+
needs the same pieces there (the caracms README's Hosting section has each host):
|
|
373
|
+
|
|
374
|
+
- A new environment in the site's Railway project (`caracms`), with its own `web`, Redis, a
|
|
375
|
+
signed imgproxy and its own submissions bucket. It reads the live media bucket (caracms
|
|
376
|
+
only ever adds to a bucket). Its variables copy production's where they carry over
|
|
377
|
+
(`RESEND_API_KEY`, `FORMS_FROM`, the site's `MUX_TOKEN_*`, `CARACMS_ALLOW` from the
|
|
378
|
+
template's admin allow list), and it deploys from the port branch.
|
|
379
|
+
- **`CARACMS_MAIN_BRANCH` and `CARACMS_DRAFTS_BRANCH`** name caracms's two branches
|
|
380
|
+
(default `main` and `drafts`). Set them to the port branch and its own drafts branch
|
|
381
|
+
(`caracms-port`, `caracms-drafts`) **before anyone signs in**: a Publish there then
|
|
382
|
+
commits to the port branch and redeploys the environment, and the live site's `main`
|
|
383
|
+
never moves. With the defaults, the first Publish would land caracms's content on the
|
|
384
|
+
live site's `main`.
|
|
385
|
+
- A GitHub credential on the environment, as for any caracms site: a fine-grained token
|
|
386
|
+
on the site's repo, or the App (the caracms README → Hosting → GitHub).
|
|
387
|
+
- Convert with `--recipients <agency address>`, so test submissions never reach the client.
|
|
388
|
+
- **Never `railway config apply` against production**: production's variables are set
|
|
389
|
+
with `railway variables --set` (a template's Railway file can switch its CDN off).
|
|
390
|
+
|
|
391
|
+
**The switch** (its own task, later): Tina is frozen, the script is re-run on the latest
|
|
392
|
+
`main` without `--recipients`, the branch is merged, production's variables move to
|
|
393
|
+
caracms's (the branch variables unset), and Tina's imgproxy and variables retire.
|
|
394
|
+
|
|
395
|
+
## 10. Before calling it done
|
|
396
|
+
|
|
397
|
+
- `npx caracms validate`: no errors, no warnings.
|
|
398
|
+
- `astro check` and `astro build` pass; the site's remaining tests pass.
|
|
399
|
+
- In `/admin`: every heading, intro, body, caption and button label the template marked
|
|
400
|
+
with `data-tina-field` can be typed on the page, except the sidebar-only fields you
|
|
401
|
+
listed; rich headings keep bold as the highlight after an edit; blocks inside cards
|
|
402
|
+
select and morph; a hidden page 404s publicly and edits in `/admin`.
|
|
403
|
+
|
|
404
|
+
## 11. Look-same
|
|
405
|
+
|
|
406
|
+
Run caracms-adopt's look-same script (section 9) on every public page, the 404 included,
|
|
407
|
+
at 1440 and 390, against `main` at the commit the script last ran on (never production
|
|
408
|
+
as it is now: the content keeps moving). Build that baseline with **production's** media
|
|
409
|
+
base URL: a local build takes it from `.dev.env`, which may point at the template's
|
|
410
|
+
imgproxy, and then images render unresized and nothing compares. Pass the template's
|
|
411
|
+
attributes and random ids:
|
|
412
|
+
|
|
413
|
+
```bash
|
|
414
|
+
node .claude/skills/caracms-adopt/look-same.mjs --playwright /tmp/look-same --old <main's build> --new <the port's build> --pages <list> --out <dir> --ignore-attr data-tina-field,data-tina-island --mask-ids auto
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
If an element varies from run to run on the old site itself (a wordmark fitted to its box
|
|
418
|
+
while the display font loads), add `--css '<rules>'` to hide it or pin its size on both
|
|
419
|
+
sides (caracms-adopt → section 9).
|
|
420
|
+
|
|
421
|
+
**A 1px crop difference on a responsive image** is a known cause of a small pixel share.
|
|
422
|
+
caracms rounds each srcset width's crop height from the ratio, while an Astro `getImage()`
|
|
423
|
+
site scaled every width from the top width's rounded height (1280 × 875 where caracms
|
|
424
|
+
draws 1280 × 876 at 962:658). Match it in the site, not in caracms: the Share port reads
|
|
425
|
+
the image's ratio in `ladderRatio()` (`src/lib/media.ts`) and keeps the design ratio in
|
|
426
|
+
the frame's CSS.
|
|
427
|
+
|
|
428
|
+
Passing means a pixel share of 0.00% and no height change at both widths, and the same
|
|
429
|
+
status on both sides (a hidden page answers 404 on both). Review the HTML diff: every
|
|
430
|
+
difference should be one the rewrite explains (caracms's wrappers, Tina's attributes
|
|
431
|
+
gone).
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Tina field kinds → caracms
|
|
2
|
+
|
|
3
|
+
How `tina-to-caracms.mjs` maps each kind of Tina field. The script reads Tina's own generated schema (`tina/__generated__/_schema.json`) and applies this table to every field it finds, so nothing is written per block. The same table drives the content it writes and the starting config `--scaffold-config` writes.
|
|
4
|
+
|
|
5
|
+
"Schema path" below is how the script's adapt table names a field: `blocks.<template>.<field>` for a block's, `<collection>.<field>` for a collection's, `siteSettings.<field>` for Site Settings', with dots for nesting (`blocks.columns.items.media`).
|
|
6
|
+
|
|
7
|
+
## Fields
|
|
8
|
+
|
|
9
|
+
| Tina field | caracms field | Stored value |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| `string` | `text()` | copied as it is |
|
|
12
|
+
| `string`, `ui.component: 'textarea'` | `text({ multiline: true })` | copied (`\n` between lines) |
|
|
13
|
+
| `string` whose name is in the adapt table's `headingFields` (in a block) | `text({ heading: true, … })` | copied |
|
|
14
|
+
| `string` with `options` | `select({ options })` (Tina's labels kept) | copied. A blank or unknown value is left out, so caracms's default applies (the first option, as Tina's "blank reads as the first option"), or the adapt table's `fieldDefaults` value |
|
|
15
|
+
| `string`, `list`, with `options` | `list(select({ options }))` | copied |
|
|
16
|
+
| `string`, `list` | `list(text())` | copied |
|
|
17
|
+
| `string` named `href`, or listed in `addressFields` | `link({ addressOnly: true })` | `{ "href": "…" }` |
|
|
18
|
+
| `number` | `number()` | copied |
|
|
19
|
+
| `boolean` | `toggle()` | copied; `fieldDefaults` fills a missing one (a default that lived in the mapper's code) |
|
|
20
|
+
| `datetime` | `date()` | copied |
|
|
21
|
+
| `image` with a sibling `<name>Alt` string | `media({ only: 'image' })` | `{ key, alt, w, h }`: the pair becomes one value, the `…Alt` field goes |
|
|
22
|
+
| `image` with no alt sibling | `media({ only: 'image' })` | `{ key, alt: "", decorative: true, w, h }`, or the document's title as alt when listed in `docTitleAlt` |
|
|
23
|
+
| `object` with `kind` and an image `src` (the template's `media()`) | `media()`, or `media({ only: 'image' })` when it has no `playbackId` | an image: `{ key, alt, focal, w, h }` (Tina's whole-percent `focusX`/`focusY` become `focal` 0–1). A Mux clip: `{ video, play, poster, name, duration }` (`playAs` → `play`, `posterTime` → `poster`) |
|
|
24
|
+
| `linkLabel` + `href` side by side | one `link()` named `link`, where `linkLabel` stood | `{ label, href }` |
|
|
25
|
+
| `object` list of `{ label, href, style }` | `buttons({ styles })` | `[{ label, href, style }]` |
|
|
26
|
+
| `object` list of `{ label, href }` | `list(link())` | `[{ label, href }]` |
|
|
27
|
+
| `object` list of one field (after the pairs merge) that is text, a reference or an image | a list of that field | flattened: `[{ line: "a" }]` → `["a"]`, `[{ game: "content/games/x.json" }]` → `["x"]`, `[{ image, imageAlt }]` → `[{ key, alt, w, h }]` |
|
|
28
|
+
| `object` list (anything else) | `list({ … })`, `itemLabel` the first text field | a list of objects, keys in schema order |
|
|
29
|
+
| `object` | `group({ … })` | an object, keys in schema order |
|
|
30
|
+
| `reference` | `reference({ to })` | the document's path in its collection: `content/games/x.json` → `x` (Tina's home file → `index`) |
|
|
31
|
+
| `rich-text` | `richText({ headings, links })`: headings from Tina's `overrides.headingLevels` (h2 and h3 only), links on when the toolbar has `link`; Tina's default (no overrides) is h2, h3 and links | caracms's rich text (below) |
|
|
32
|
+
| `displayOnly` | — | dropped |
|
|
33
|
+
| a collection's `isTitle` field | the document's own `title` | copied |
|
|
34
|
+
|
|
35
|
+
Every label and help (`description`) is carried into the scaffolded config. A template's own description (`ui.description`) becomes its block's `help`, cut to a sentence or two; where the site also put it in front of a field's help ("About this block: …"), the field keeps only its own.
|
|
36
|
+
|
|
37
|
+
## Rich text
|
|
38
|
+
|
|
39
|
+
Tina stores Markdown in a JSON string. Each blank-line-separated chunk becomes caracms blocks:
|
|
40
|
+
|
|
41
|
+
- a line starting `## ` or `### ` → `{ "h2": … }` / `{ "h3": … }`; any other heading level becomes a paragraph, with a warning (caracms never holds an `h1`);
|
|
42
|
+
- runs of `* `, `- ` or `+ ` lines → `{ "ul": [ … ] }`, runs of `1. ` lines → `{ "ol": [ … ] }`; an indented item is flattened into its list, with a warning (caracms has no nested lists);
|
|
43
|
+
- everything else → `{ "p": … }`.
|
|
44
|
+
|
|
45
|
+
Inline, `**bold**`, `*italic*` and `[text](href)` carry over (Tina's `__bold__` and `_italic_` become `**`/`*`). Tina's two forms of a line break, a trailing `\` or two spaces before a newline, become `\n`, and so does a bare newline inside a paragraph. **Tina's escapes are undone** (`A\&M` → `A&M`, `mailto\:` → `mailto:`) except before `*`, `[`, `]` and `\`, the only characters caracms escapes. Units are trimmed, so a rich heading's trailing `\n` goes, and an empty value becomes `[]`.
|
|
46
|
+
|
|
47
|
+
## Documents and collections
|
|
48
|
+
|
|
49
|
+
- A Tina collection with a list of templates (a section builder) → `kind: 'sections'`: `_template` → `type`, the templates list (`sections`) → `blocks`, its other fields are page fields (stored after `title`, before `blocks`). Any other collection → `kind: 'fields'`.
|
|
50
|
+
- The collection's key and folder are its Tina folder in kebab case (`content/clientLogos` → `client-logos`).
|
|
51
|
+
- Tina's `home.json` (the adapt table's `homeFile`) → `index.json`. Folders under a collection are kept.
|
|
52
|
+
- **Block types** take the adapt table's `blockRenames`, and otherwise go from camelCase to kebab case (`textMedia` → `text-media`).
|
|
53
|
+
- `seo { metaTitle, metaDescription, ogImage, ogImageAlt }` → caracms's `seo { title, description, image }`.
|
|
54
|
+
- **Hidden:** a section builder's boolean `hidden` → `"hidden": true` at the top level of the document, beside `title` and `blocks` (absent means shown). The home page can't be hidden: it's left shown, with a warning.
|
|
55
|
+
- Every file starts with its `$schema` (`../../schemas/<collection>.schema.json`), as `caracms schemas` stamps it.
|
|
56
|
+
- A key in a Tina file that its schema no longer has is left out, with a warning.
|
|
57
|
+
|
|
58
|
+
## Site Settings
|
|
59
|
+
|
|
60
|
+
| Tina (`siteSettings`) | caracms (`content/site-settings.json`) |
|
|
61
|
+
|---|---|
|
|
62
|
+
| `adminAccess.allow[].entry` | `editors` |
|
|
63
|
+
| `adminBranding { name, accent }` | `brand { name, accent }`. The logo and favicon aren't carried (a warning says so): upload them in the editor |
|
|
64
|
+
| `defaultSeo` | `seo` |
|
|
65
|
+
| `googleTag` | `googleTag` |
|
|
66
|
+
| `formNotifications.<form>[].address` | `formRecipients.<form>`; `--recipients <address>` makes every form's list that one address |
|
|
67
|
+
| `submissionsLink` | dropped (caracms's inbox is in the editor) |
|
|
68
|
+
| everything else | Site Settings' own fields, by the table above |
|
|
69
|
+
|
|
70
|
+
## Images and files
|
|
71
|
+
|
|
72
|
+
- **Images keep their bucket objects.** The key is the URL's part after `s3://<bucket>/` (`https://img…/unsafe/plain/s3://share-media-…/uploads/photos/a.jpg` → `uploads/photos/a.jpg`). An image URL that isn't in the bucket is left out, with a warning.
|
|
73
|
+
- **`w` and `h`** come from `scripts/tina-media-sizes.json`, read first. A key it lacks is fetched once (its Tina URL, else the bucket with the site's `S3_*` settings), sized by caracms's own sniffer (EXIF orientation included), and added to the file. Commit the file: re-runs then need no network for images.
|
|
74
|
+
- **File links** the Tina site served itself (`/files/<name>`, from `uploads/<name>`; the adapt table's `fileLinks`) become caracms file addresses, `/files/<hex>/<name>`, wherever an address can be: links, buttons, address fields and rich text. `<hex>` is the first 12 hex of the SHA-256 of the source key, so the address is the same on every run. The object is copied in the bucket to `uploads/<hex>/<name>` (the name slugified as caracms's upload names are), and the copy is skipped when that key exists.
|
|
75
|
+
|
|
76
|
+
## The adapt table
|
|
77
|
+
|
|
78
|
+
At the top of the script, under "ADAPT HERE". Everything else is generic.
|
|
79
|
+
|
|
80
|
+
| Entry | What it's for |
|
|
81
|
+
|---|---|
|
|
82
|
+
| `blockRenames` | the approved block table: a Tina template's caracms key where it isn't the kebab-case rule |
|
|
83
|
+
| `fieldDefaults` | a value Tina's files leave out but the site's mapper code reads as set: a per-block background default that isn't the first option, a toggle that reads as on |
|
|
84
|
+
| `addressFields` | strings that are addresses but aren't named `href` (social links, a privacy link, a YouTube link). Tina's schema doesn't say which strings are links |
|
|
85
|
+
| `headingFields` | text field names that are headings (scaffold only) |
|
|
86
|
+
| `docTitleAlt` | bare images whose alt is the document's title (a client's logo) |
|
|
87
|
+
| `fileLinks` | the Tina site's own file route and the bucket key behind it |
|
|
88
|
+
| `homeFile` | Tina's home page file |
|