@typeroll/mcp-server 0.14.0 → 0.15.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +44 -1
- package/dist/tools/page-blocks.js +1 -1
- package/package.json +1 -1
- package/skills/tr-new-site.md +156 -164
package/AGENTS.md
CHANGED
|
@@ -84,7 +84,28 @@ maps to one HTTP endpoint; the actual logic runs in the customer's portal
|
|
|
84
84
|
before working with blocks — never hardcode block ids or field names,
|
|
85
85
|
the available set is per-site.
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
Block-library specifics worth knowing (template_capabilities_version
|
|
88
|
+
0.15.0):
|
|
89
|
+
- **`core/media_card`** — image + text side by side (image left/right,
|
|
90
|
+
width third/two-fifths/half, heading + richtext + button, optional
|
|
91
|
+
card background/radius; stacks image-on-top below 720px). Use it for
|
|
92
|
+
the classic "photo next to copy" layout instead of hand-building
|
|
93
|
+
section+grid+html.
|
|
94
|
+
- **`core/hero` and `core/cta` render their buttons server-side** via
|
|
95
|
+
`primary_label`/`primary_url` + `secondary_label`/`secondary_url`.
|
|
96
|
+
(The old `buttons` array relied on client hydration that never
|
|
97
|
+
existed — if you see `data-buttons` in stored content it renders
|
|
98
|
+
nothing; rebuild with the explicit fields.)
|
|
99
|
+
- **`core/image` gets responsive `<picture>` automatically at build
|
|
100
|
+
time** — the deploy pipeline's SEO transform converts CDN `<img>`
|
|
101
|
+
into `<picture>` with AVIF/WebP srcset variants. You do NOT need
|
|
102
|
+
`core/html` for responsive images; just point `src` at an uploaded
|
|
103
|
+
media URL (run `generate_image_variants` first) and optionally set
|
|
104
|
+
`radius`. Note: the in-portal preview shows the plain `<img>` — the
|
|
105
|
+
`<picture>` upgrade appears on the deployed site.
|
|
106
|
+
- **`core/icon` / `core/icon_box` icons do not render yet** — they
|
|
107
|
+
emit `data-icon` placeholders awaiting an icon pipeline. Prefer
|
|
108
|
+
numbered markers, emoji, or CSS shapes until that ships.
|
|
88
109
|
- **`core/section` is natively full-bleed on block pages** (since
|
|
89
110
|
template_capabilities_version 0.14.0): the section's background runs
|
|
90
111
|
edge-to-edge and meets the header with zero gap; content inside is
|
|
@@ -236,6 +257,28 @@ add_block page_id=home parent_id="blk_xyz" block={
|
|
|
236
257
|
}
|
|
237
258
|
```
|
|
238
259
|
|
|
260
|
+
Slot containers (`container: "slots"` — `core/columns`, `core/tabs`)
|
|
261
|
+
hold their children in per-slot lists, not in `children`. Two ways to
|
|
262
|
+
populate them (both require template_capabilities_version ≥ 0.15.2):
|
|
263
|
+
|
|
264
|
+
```
|
|
265
|
+
# Inline — pass the whole subtree in one call:
|
|
266
|
+
add_block page_id=home block={
|
|
267
|
+
type: 'core/columns', data: { ratio: '1-1' },
|
|
268
|
+
slots: [
|
|
269
|
+
[{ type: 'core/prose', data: { html: '<p>Left column</p>' } }],
|
|
270
|
+
[{ type: 'core/image', data: { src: '…' } }],
|
|
271
|
+
]
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
# Incrementally — slot_index picks the slot (0-based, defaults to 0):
|
|
275
|
+
add_block page_id=home block={ type: 'core/columns', data: {} }
|
|
276
|
+
# → { added_id: 'blk_cols' } — slots are auto-initialised to the type's arity
|
|
277
|
+
add_block page_id=home parent_id="blk_cols" slot_index=1 block={
|
|
278
|
+
type: 'core/prose', data: { html: '<p>Right column</p>' }
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
239
282
|
For an unfamiliar custom block, `read_block_type id="..."` gives the
|
|
240
283
|
full field list (types, defaults, required) so you don't ship invalid
|
|
241
284
|
`data`.
|
|
@@ -67,7 +67,7 @@ export const pageBlockTools = [
|
|
|
67
67
|
},
|
|
68
68
|
{
|
|
69
69
|
name: 'add_block',
|
|
70
|
-
description: 'Insert a block into a container (page, partial, or page template). With no parent_id, inserts at the top level of the container. Otherwise inserts as a child (or into
|
|
70
|
+
description: 'Insert a block into a container (page, partial, or page template). With no parent_id, inserts at the top level of the container. Otherwise inserts as a child (or into a slot for slot-containers). Position defaults to "end". Slot containers (core/columns, core/tabs — container: "slots"): slot_index picks the slot (0-based; defaults to 0) and works even if the parent instance has no slots array yet; a freshly added slot container gets its slots initialised to the type\'s arity. You can also pass the whole subtree inline via block.slots: [[...],[...]]. Pass `target: { kind: "partial", id: "header" }` to drop a block into the header on every page, etc. `page_id` is the legacy shorthand.',
|
|
71
71
|
inputSchema: {
|
|
72
72
|
target: targetSchema.optional(),
|
|
73
73
|
page_id: z.string().optional(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@typeroll/mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.2",
|
|
4
4
|
"description": "Model Context Protocol server for the Typeroll public API. Use with Claude Code or any MCP-compatible client to manage a Typeroll site.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
package/skills/tr-new-site.md
CHANGED
|
@@ -10,13 +10,17 @@ the portal UI or API) but has no design, no header/footer, and no pages.
|
|
|
10
10
|
The goal is to go from blank to a working 4-page site with correct brand
|
|
11
11
|
identity in a single session.
|
|
12
12
|
|
|
13
|
+
**Pages are built in block mode** — the platform default. Blocks give
|
|
14
|
+
structured, per-field editing, native full-bleed sections, responsive
|
|
15
|
+
breakpoints, and templates. HTML mode is the secondary path for
|
|
16
|
+
hand-crafted one-offs and migrated content (section at the end).
|
|
17
|
+
|
|
13
18
|
## Preconditions
|
|
14
19
|
|
|
15
20
|
- `@typeroll/mcp-server` configured with a valid `TYPEROLL_API_KEY`.
|
|
16
21
|
- The site exists (confirm with `get_site`).
|
|
17
22
|
- You have the customer brief: company name, industry, 2–3 key brand colors,
|
|
18
|
-
tone of voice
|
|
19
|
-
initial pages.
|
|
23
|
+
tone of voice, and a list of initial pages.
|
|
20
24
|
|
|
21
25
|
## Recipe
|
|
22
26
|
|
|
@@ -24,9 +28,11 @@ identity in a single session.
|
|
|
24
28
|
|
|
25
29
|
```
|
|
26
30
|
get_site
|
|
31
|
+
get_site_capabilities # template_capabilities_version — what this deployment supports
|
|
27
32
|
read_site_settings # see what (if anything) is already configured
|
|
28
33
|
list_pages # don't overwrite pages that already exist
|
|
29
34
|
list_partials # check if header/footer already have content
|
|
35
|
+
list_block_types # the per-site block palette — NEVER assume, always list
|
|
30
36
|
```
|
|
31
37
|
|
|
32
38
|
### 2. Brand + settings
|
|
@@ -47,203 +53,189 @@ One `update_site_settings` call with every field you know:
|
|
|
47
53
|
"text": "#1a1a2e",
|
|
48
54
|
"text_light": "#6b7280"
|
|
49
55
|
},
|
|
50
|
-
"fonts": {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"size_base": 16
|
|
54
|
-
},
|
|
55
|
-
"contact": {
|
|
56
|
-
"email": "hej@acme.se",
|
|
57
|
-
"phone": "+46 8 123 456",
|
|
58
|
-
"address": "Drottninggatan 1, 111 51 Stockholm"
|
|
59
|
-
},
|
|
60
|
-
"social": {
|
|
61
|
-
"instagram": "https://instagram.com/acme",
|
|
62
|
-
"linkedin": "https://linkedin.com/company/acme"
|
|
63
|
-
}
|
|
56
|
+
"fonts": { "heading": "Playfair Display", "body": "Inter", "size_base": 16 },
|
|
57
|
+
"contact": { "email": "hej@acme.se", "phone": "+46 8 123 456" },
|
|
58
|
+
"social": { "instagram": "https://instagram.com/acme" }
|
|
64
59
|
}
|
|
65
60
|
```
|
|
66
61
|
|
|
67
|
-
Read it back
|
|
68
|
-
|
|
62
|
+
Read it back with `read_site_settings`. Google Fonts names are
|
|
63
|
+
case-sensitive display names ("Plus Jakarta Sans", not "plus jakarta").
|
|
69
64
|
|
|
70
|
-
|
|
71
|
-
|
|
65
|
+
**Site icons are part of brand setup** — upload favicon (32–64px) and a
|
|
66
|
+
180×180 apple touch icon, set `favicon` + `apple_touch_icon`. No icon
|
|
67
|
+
assets? Derive a proposal (see `tr-brand`). Also set `settings.logo` to
|
|
68
|
+
the uploaded brand mark — it feeds OG/schema even if the header uses a
|
|
69
|
+
different lockup.
|
|
72
70
|
|
|
73
|
-
### 3. Header
|
|
71
|
+
### 3. Header + footer partials
|
|
74
72
|
|
|
75
|
-
|
|
73
|
+
Partials are usually simplest in HTML mode (one nav, a few links — no
|
|
74
|
+
per-field editing needed). Keep them lean; literal site name (no template
|
|
75
|
+
engine in partials):
|
|
76
76
|
|
|
77
77
|
```html
|
|
78
78
|
<header class="site-header">
|
|
79
79
|
<div class="header-inner">
|
|
80
|
-
<a class="header-logo" href="/"
|
|
80
|
+
<a class="header-logo" href="/"><img src="LOGO_MEDIA_URL" alt="Acme Studio" height="40" /></a>
|
|
81
81
|
<nav class="header-nav">
|
|
82
82
|
<a href="/om-oss">Om oss</a>
|
|
83
|
-
<a href="/tjanster">Tjänster</a>
|
|
84
83
|
<a href="/kontakt">Kontakt</a>
|
|
85
84
|
</nav>
|
|
86
85
|
</div>
|
|
87
86
|
</header>
|
|
88
87
|
<style>
|
|
89
|
-
.site-header{background:var(--color-
|
|
88
|
+
.site-header{background:var(--color-background);padding:1rem 2rem}
|
|
90
89
|
.header-inner{max-width:1080px;margin:0 auto;display:flex;align-items:center;justify-content:space-between}
|
|
91
|
-
.header-logo{color:#fff;text-decoration:none;font-family:var(--font-heading);font-size:1.25rem;font-weight:700}
|
|
92
90
|
.header-nav{display:flex;gap:2rem}
|
|
93
|
-
.header-nav a{color:
|
|
94
|
-
.header-nav a:hover{color:#fff}
|
|
95
|
-
</style>
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
Use `replace_partial partial_id="header" html_content="..."`.
|
|
99
|
-
|
|
100
|
-
### 4. Footer partial
|
|
101
|
-
|
|
102
|
-
```html
|
|
103
|
-
<footer class="site-footer">
|
|
104
|
-
<div class="footer-inner">
|
|
105
|
-
<p class="footer-brand">Acme Studio</p>
|
|
106
|
-
<p class="footer-tagline">Tagline here</p>
|
|
107
|
-
<div class="footer-links">
|
|
108
|
-
<a href="/om-oss">Om oss</a>
|
|
109
|
-
<a href="/kontakt">Kontakt</a>
|
|
110
|
-
</div>
|
|
111
|
-
<p class="footer-copy">© 2025 Acme Studio. Alla rättigheter förbehållna.</p>
|
|
112
|
-
</div>
|
|
113
|
-
</footer>
|
|
114
|
-
<style>
|
|
115
|
-
.site-footer{background:var(--color-secondary);color:rgba(255,255,255,0.7);padding:3rem 2rem;margin-top:4rem}
|
|
116
|
-
.footer-inner{max-width:1080px;margin:0 auto;text-align:center;display:grid;gap:1rem}
|
|
117
|
-
.footer-brand{font-family:var(--font-heading);font-size:1.5rem;color:#fff;font-weight:700}
|
|
118
|
-
.footer-links{display:flex;justify-content:center;gap:2rem}
|
|
119
|
-
.footer-links a{color:rgba(255,255,255,0.7);text-decoration:none}
|
|
120
|
-
.footer-links a:hover{color:#fff}
|
|
121
|
-
.footer-copy{font-size:0.8rem;opacity:0.5}
|
|
91
|
+
.header-nav a{color:var(--color-text);text-decoration:none}
|
|
122
92
|
</style>
|
|
123
93
|
```
|
|
124
94
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
```
|
|
130
|
-
create_page title="Start" slug="" content_mode="html"
|
|
131
|
-
html_content="<article class=\"home-page\">...full hero + intro HTML...</article>"
|
|
132
|
-
status="published"
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
`slug=""` creates the homepage under `page_id: "home"`. If it already exists, use `update_page page_id="home" patch={html_content: "..."}`.
|
|
136
|
-
|
|
137
|
-
**Always wrap the body in a page-scope `<article class="...-page">`.** The renderer's `.page-content` layout shell sets default max-width and padding; without a scope class, any selectors you write live in the same tree as the renderer defaults and you'll wonder why your `.hero h1` doesn't get the size you set. With `.home-page .hero h1` you're at specificity 0,2,1 and trivially win. Use `<article class="home-page">` for the homepage and `<article class="static-page">` (or page-specific names like `<article class="about-page">`) for inner pages.
|
|
138
|
-
|
|
139
|
-
A minimal homepage structure:
|
|
140
|
-
- Hero: **full-bleed** section (negative-margin escape) with `<h1>`, tagline, one CTA button
|
|
141
|
-
- Intro: 2–3 sentences about what the company does
|
|
142
|
-
- Services/features grid: 3 cards max
|
|
143
|
-
- CTA band: "Kontakta oss" or similar
|
|
144
|
-
|
|
145
|
-
Full-bleed hero recipe (matches the section conventions from `tr-brand`'s Step 4b):
|
|
146
|
-
|
|
147
|
-
```css
|
|
148
|
-
.home-page .home-hero {
|
|
149
|
-
position: relative;
|
|
150
|
-
margin-left: calc(50% - 50vw);
|
|
151
|
-
margin-right: calc(50% - 50vw);
|
|
152
|
-
margin-top: -32px; /* cancels .page-content top padding */
|
|
153
|
-
width: 100vw;
|
|
154
|
-
padding: 80px 0 64px;
|
|
155
|
-
background: var(--gradient-soft), #fff;
|
|
156
|
-
}
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
**Caveat:** never combine with `overflow-x: clip` on the page wrapper — the clip cancels the bleed.
|
|
160
|
-
|
|
161
|
-
### 6. Inner pages
|
|
162
|
-
|
|
163
|
-
Create each page (always with a page-scope class):
|
|
164
|
-
|
|
165
|
-
```
|
|
166
|
-
create_page title="Om oss" slug="om-oss" content_mode="html"
|
|
167
|
-
html_content="<article class=\"static-page\">...</article>"
|
|
168
|
-
status="published"
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
Standard set: Om oss, Tjänster, Kontakt. Add more if the brief says so.
|
|
172
|
-
|
|
173
|
-
### 6b. If the legacy site is still live, scrape canonical content
|
|
174
|
-
|
|
175
|
-
When a page (privacy policy, terms, about) exists on the still-live previous site, use `WebFetch` to pull the canonical text directly rather than rewriting from memory or relying on placeholder copy.
|
|
176
|
-
|
|
177
|
-
Pattern:
|
|
178
|
-
|
|
179
|
-
```
|
|
180
|
-
WebFetch url="https://www.olddomain.example/integritetspolicy"
|
|
181
|
-
prompt="Return full content as markdown, preserve Swedish characters,
|
|
182
|
-
preserve hierarchy, don't summarize"
|
|
183
|
-
```
|
|
95
|
+
`replace_partial partial_id="header" html_content="..."` — same pattern
|
|
96
|
+
for the footer. Design notes: **no border-bottom on the header if the
|
|
97
|
+
first page section should meet it seamlessly** — let background color
|
|
98
|
+
changes do the separating. Anchor links in nav (`/#section`) are fine.
|
|
184
99
|
|
|
185
|
-
|
|
100
|
+
### 4. Homepage — block tree
|
|
186
101
|
|
|
187
|
-
|
|
102
|
+
`create_page` with the whole tree in one call. Omit block `id`s — the
|
|
103
|
+
platform assigns them (`blk_…`); you read them back for later
|
|
104
|
+
`update_block` calls.
|
|
188
105
|
|
|
189
106
|
```
|
|
190
|
-
|
|
191
|
-
get_page_preview page_id="home" # get rendered HTML to spot-check
|
|
107
|
+
create_page title="Start" slug="" status="draft" content_mode="blocks" blocks=[...]
|
|
192
108
|
```
|
|
193
109
|
|
|
194
|
-
|
|
110
|
+
A proven landing-page skeleton (every top-level block is a `core/section`
|
|
111
|
+
— sections are **natively full-bleed**: the background runs edge-to-edge,
|
|
112
|
+
content is constrained by the section's own inner container via the
|
|
113
|
+
`width` field. NEVER use 100vw negative-margin hacks):
|
|
195
114
|
|
|
196
|
-
|
|
115
|
+
```json
|
|
116
|
+
[
|
|
117
|
+
{ "type": "core/section", "data": { "background": "#ffffff", "padding_y": "lg" }, "children": [
|
|
118
|
+
{ "type": "core/media_card", "data": {
|
|
119
|
+
"image": "MEDIA_URL", "image_alt": "…", "image_side": "right",
|
|
120
|
+
"heading": "Huvudrubriken", "heading_level": "h2",
|
|
121
|
+
"text": "<p>Ingress …</p>",
|
|
122
|
+
"button_label": "Kontakta oss", "button_url": "/#kontakt"
|
|
123
|
+
} }
|
|
124
|
+
] },
|
|
125
|
+
{ "type": "core/section", "data": { "padding_y": "lg" }, "children": [
|
|
126
|
+
{ "type": "core/heading", "data": { "text": "Så funkar det", "level": "h2", "align": "center" } },
|
|
127
|
+
{ "type": "core/grid", "data": { "cols": 3, "gap": "lg" }, "children": [
|
|
128
|
+
{ "type": "core/step_card", "data": { "number": "1", "title": "…", "text": "<p>…</p>" } },
|
|
129
|
+
{ "type": "core/step_card", "data": { "number": "2", "title": "…", "text": "<p>…</p>" } },
|
|
130
|
+
{ "type": "core/step_card", "data": { "number": "3", "title": "…", "text": "<p>…</p>" } }
|
|
131
|
+
] }
|
|
132
|
+
] },
|
|
133
|
+
{ "type": "core/cta", "data": {
|
|
134
|
+
"heading": "Redo att börja?",
|
|
135
|
+
"primary_label": "Kontakta oss", "primary_url": "/kontakt"
|
|
136
|
+
} }
|
|
137
|
+
]
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Block-palette guidance (verify against `list_block_types` — the source of
|
|
141
|
+
truth):
|
|
142
|
+
|
|
143
|
+
- **Hero:** `core/media_card` inside a white section (image beside copy),
|
|
144
|
+
or `core/hero` (eyebrow/heading/subheading + `primary_*`/`secondary_*`
|
|
145
|
+
buttons — rendered server-side; `layout: split-right` puts the image
|
|
146
|
+
beside the text). For a plain text hero: section + heading + prose +
|
|
147
|
+
button.
|
|
148
|
+
- **`core/heading`** decouples `level` (h1–h6, semantics) from `size`
|
|
149
|
+
(visual) — exactly one `level: h1` per page.
|
|
150
|
+
- **Images:** `core/image` with an uploaded media URL. The build pipeline
|
|
151
|
+
automatically emits responsive `<picture>` with AVIF/WebP variants
|
|
152
|
+
(run `generate_image_variants` after upload) — the in-portal preview
|
|
153
|
+
shows a plain `<img>`, the deployed site gets the upgrade. Use the
|
|
154
|
+
`radius` field for rounded corners.
|
|
155
|
+
- **Repeaters/listings:** `core/collection_list`, `gallery`,
|
|
156
|
+
`feature_grid` etc. — alias blocks over `core/repeater`. Use these for
|
|
157
|
+
collection-driven content instead of hand-writing listing markup.
|
|
158
|
+
- **Forms:** `create_form`, then a `core/html` block carrying the plain
|
|
159
|
+
`<form method="POST" action={submit_url}>` embed with the hidden
|
|
160
|
+
`_token` — see `tr-forms`.
|
|
161
|
+
- **`core/html`** is the escape hatch for the genuinely unique thing —
|
|
162
|
+
not a default. If you reach for it more than once or twice per page,
|
|
163
|
+
note why (that's block-library feedback).
|
|
164
|
+
|
|
165
|
+
Slot containers (`core/columns`, `core/tabs`): populate them either by
|
|
166
|
+
passing the whole tree inline (`block={ type: 'core/columns', slots:
|
|
167
|
+
[[…],[…]] }`) or incrementally with `add_block parent_id=<columns-id>
|
|
168
|
+
slot_index=0|1`. Requires template_capabilities_version ≥ 0.15.2 — on
|
|
169
|
+
older sites use `core/grid` (children flow into columns) instead.
|
|
170
|
+
|
|
171
|
+
Known limitations (honest list — don't fight them):
|
|
172
|
+
|
|
173
|
+
- **`core/icon` / `core/icon_box` icons don't render yet** (pending icon
|
|
174
|
+
pipeline). Use `step_card` numbers, emoji, or CSS markers.
|
|
175
|
+
|
|
176
|
+
Theming: block primitives render neutral. Brand color/typography comes
|
|
177
|
+
from settings (step 2). For page-specific polish (e.g. a colored card
|
|
178
|
+
treatment), a single `core/html` block with a small `<style>` scoped to
|
|
179
|
+
`[data-bid]`/section selectors is acceptable — keep it minimal and note
|
|
180
|
+
it in your log.
|
|
181
|
+
|
|
182
|
+
### 5. Inner pages
|
|
183
|
+
|
|
184
|
+
Same pattern: `create_page` with `content_mode: "blocks"` and a section
|
|
185
|
+
tree. Standard set: Om oss, Tjänster, Kontakt — or what the brief says.
|
|
186
|
+
Default new pages to `status: "draft"`; publish after review.
|
|
187
|
+
|
|
188
|
+
### 5b. If the legacy site is still live, scrape canonical content
|
|
189
|
+
|
|
190
|
+
For pages with canonical text (privacy policy, terms, about), `WebFetch`
|
|
191
|
+
the live page and carry the text verbatim — don't rewrite legal copy
|
|
192
|
+
from memory. Convert to prose blocks (or one `core/html` for complex
|
|
193
|
+
legacy markup).
|
|
194
|
+
|
|
195
|
+
### 6. Preview + iterate
|
|
196
|
+
|
|
197
|
+
```
|
|
198
|
+
get_preview_link # signed URL for browser review
|
|
199
|
+
get_page_preview page_id="home" # rendered HTML for structural checks
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Share the preview link. Iterate on feedback with `update_block` /
|
|
203
|
+
`add_block` / `move_block` — that's the point of block mode: surgical
|
|
204
|
+
edits, not full-page rewrites.
|
|
205
|
+
|
|
206
|
+
### 7. Deploy
|
|
197
207
|
|
|
198
208
|
When the user approves:
|
|
199
209
|
```
|
|
200
210
|
trigger_deploy
|
|
201
|
-
get_deploy_status job_id=<id>
|
|
211
|
+
get_deploy_status job_id=<id>
|
|
202
212
|
```
|
|
203
213
|
|
|
204
|
-
##
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
width + padding at normal specificity, so a scope class keeps your
|
|
218
|
-
rules cleanly above the defaults and the next agent can refactor a
|
|
219
|
-
single page without bleed-through.
|
|
220
|
-
- **Inherit `tr-brand`'s Step 4b section/layout defaults.** Full-bleed
|
|
221
|
-
sections via negative-margin escape, one signal per section boundary
|
|
222
|
-
(bg shift OR divider, not both), cards drop bg on `.section.alt`,
|
|
223
|
-
gradient-clipped headings use `line-height: 1.1+` to keep descenders
|
|
224
|
-
inside the box. See the brand skill for the full list — don't
|
|
225
|
-
re-derive.
|
|
226
|
-
- **Don't simplify data during import.** When porting content from an
|
|
227
|
-
existing site, preserve full fidelity at the data layer. Don't
|
|
228
|
-
ASCII-fold slugs into display labels (`affärsutveckling` ≠
|
|
229
|
-
`affarsutveckling`), don't truncate descriptions, don't drop
|
|
230
|
-
metadata fields you "won't use yet". Carry titles verbatim from
|
|
231
|
-
source; derive slug-from-title only for the URL, not the label.
|
|
232
|
-
- **Minimal JS.** The platform doesn't bundle JS. If you need interactions,
|
|
233
|
-
write a small `<script>` inside the partial or page; keep it under 50
|
|
234
|
-
lines. Avoid external CDN dependencies in page bodies.
|
|
214
|
+
## Secondary path: HTML mode
|
|
215
|
+
|
|
216
|
+
For migrated legacy pages or a hand-crafted one-off, `content_mode:
|
|
217
|
+
"html"` still works. Rules that apply there (and only there):
|
|
218
|
+
|
|
219
|
+
- Wrap the body in a page-scope `<article class="my-page">` and prefix
|
|
220
|
+
selectors with it — the `.page-content` shell sets width/padding at
|
|
221
|
+
normal specificity.
|
|
222
|
+
- HTML-mode bodies are container-constrained; full-bleed requires the
|
|
223
|
+
negative-margin escape (`margin-left: calc(50% - 50vw); width: 100vw`)
|
|
224
|
+
— never combine with `overflow-x: clip` on a wrapper.
|
|
225
|
+
- `set_page_mode` flips a page between modes;
|
|
226
|
+
`convert_page_to_blocks` does a heuristic HTML→blocks conversion.
|
|
235
227
|
|
|
236
228
|
## Pitfalls
|
|
237
229
|
|
|
238
|
-
- **Don't create pages that already exist
|
|
239
|
-
if the slug is taken
|
|
240
|
-
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
- **
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
230
|
+
- **Don't create pages that already exist** — `list_pages` first; use
|
|
231
|
+
`update_page` if the slug is taken.
|
|
232
|
+
- **`update_page` takes a `patch` object**, not flat fields.
|
|
233
|
+
- **Don't hardcode block field names from memory** — schemas are
|
|
234
|
+
per-site (`list_block_types`/`read_block_type`).
|
|
235
|
+
- **One `level: h1` per page** (core/heading) — SEO + screen readers.
|
|
236
|
+
- **Don't simplify data during import** — preserve Swedish characters in
|
|
237
|
+
labels (`affärsutveckling`, not the ASCII-folded slug), keep titles
|
|
238
|
+
verbatim; slugs are derived for URLs only.
|
|
239
|
+
- **Minimal JS.** Inline `<script>` in page content is stripped by the
|
|
240
|
+
sanitizer. Interactivity ships via block-type `script` (requires the
|
|
241
|
+
site's AI-scripts opt-in) or the human-managed `scripts_body_end`.
|