@typeroll/mcp-server 0.42.2 → 0.43.1

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.
@@ -1,182 +1,118 @@
1
1
  ---
2
2
  name: tr-header-footer
3
- description: Vetted, robust header and footer presets to drop into the header/footer partials. Use when building or restyling a site's site-wide header or footer start from a preset and restyle it instead of hand-rolling layout + overflow (the usual source of clipped logos and broken mobile menus).
3
+ description: Build accessible native header and footer partials with the site logo, semantic navigation, responsive disclosure, and optional native cookie consent.
4
4
  ---
5
5
 
6
- # Header & footer presets
7
-
8
- > **The buffer model (draft writes).** Every content write in this recipe
9
- > (pages, blocks, partials, collection items) lands in an unsaved per-doc
10
- > DRAFT deploys and plain previews only see SAVED content. For recipe-style
11
- > build work, pass `save: true` on write calls (the work is pre-approved by
12
- > the task itself), or run `commit_working_copy` per doc before any
13
- > `trigger_deploy`. Preview your drafts with `include_working_copy: true`.
14
-
15
-
16
- Headers and footers are the two partials every page shows, and hand-rolling them
17
- is where logos get clipped and mobile menus break. **Start from a preset below,
18
- fill the placeholders, restyle with the site's colours — don't build the layout
19
- from scratch.** Each preset is deliberately robust; the "why" notes call out the
20
- traps it avoids.
21
-
22
- ## How to use
23
-
24
- 1. `read_site_settings` — grab `logo`, `site_name`, `tagline`, `contact.email`,
25
- and the colour palette.
26
- 2. `read_partial partial_id="header"` (and `footer`) — see what's already there;
27
- don't blow away a working one without reason.
28
- 3. Pick a preset, replace every `{{PLACEHOLDER}}`, adjust colours to the palette
29
- (the presets already read `--color-*` / `--font-heading` with fallbacks).
30
- 4. `update_partial partial_id="header" patch={ html_content: "…" } version="…"`.
31
- 5. **Preview and self-review in context** (see `tr-redesign-branch` step 6):
32
- the logo must be FULLY VISIBLE (not clipped), legible against its background,
33
- and the mobile layout must work at 390px. Screenshot the header region in
34
- context never the logo element in isolation (that hides clipping).
35
-
36
- Placeholders: `{{SITE_NAME}}`, `{{LOGO_URL}}`, `{{TAGLINE}}`, `{{EMAIL}}`, `{{YEAR}}`.
37
-
38
- ---
39
-
40
- ## Header A — Centered logo (minimal; landing pages)
41
-
42
- ```html
43
- <header class="tr-hdr tr-hdr--center">
44
- <a class="tr-hdr-logo" href="/" aria-label="{{SITE_NAME}} — till startsidan">
45
- <img src="{{LOGO_URL}}" alt="{{SITE_NAME}}" />
46
- </a>
47
- </header>
48
- <style>
49
- .tr-hdr--center{background:var(--color-surface,#fff);display:flex;justify-content:center;padding:clamp(1rem,2.5vw,1.6rem) 1.5rem}
50
- .tr-hdr-logo{display:inline-block;line-height:0;transition:transform .15s ease}
51
- .tr-hdr-logo:hover{transform:translateY(-1px)}
52
- .tr-hdr-logo img{height:clamp(40px,6vw,58px);width:auto;display:block}
53
- </style>
6
+ # Native header and footer compositions
7
+
8
+ Use block-mode partials for new work. Core 0.1.8 / capability contract 0.43.0
9
+ ships `template/site_logo`, `core/navigation`, layout primitives, and a
10
+ revision-safe partial mode operation. HTML partials remain supported for
11
+ legacy markup, but generic navigation and responsive layout no longer require
12
+ tenant HTML, CSS, or JavaScript.
13
+
14
+ ## Preconditions
15
+
16
+ 1. Read `get_site_capabilities`, `read_site_settings`, `list_partials`, and
17
+ `list_block_types`.
18
+ 2. Require `supports_native_navigation`, `supports_partial_mode_switching`,
19
+ `supports_responsive_data_fields`, and
20
+ `supports_versioned_block_type_inheritance`.
21
+ 3. Read the existing header/footer before replacing anything. Work in a branch
22
+ for redesigns and preview at desktop, 390 px, keyboard-only, and 200% zoom.
23
+
24
+ ## Header tree
25
+
26
+ Stage this tree with `update_partial partial_id="header" patch={blocks:[...]}
27
+ save=true`, then call `set_partial_mode partial_id="header" to="blocks"` and
28
+ read it back. Localize both accessible labels and every link label.
29
+
30
+ ```json
31
+ [
32
+ {
33
+ "type": "core/section",
34
+ "data": { "width": "wide", "padding_y": "sm" },
35
+ "children": [{
36
+ "type": "core/container",
37
+ "data": {
38
+ "direction": { "mobile": "column", "tablet": "row" },
39
+ "align_main": "space-between",
40
+ "align_cross": "center",
41
+ "gap": "md",
42
+ "width": "wide",
43
+ "padding_y": "none",
44
+ "padding_x": "none"
45
+ },
46
+ "children": [
47
+ { "type": "template/site_logo", "data": { "height": "md", "link_to_home": true } },
48
+ {
49
+ "type": "core/navigation",
50
+ "data": {
51
+ "aria_label": "Main navigation",
52
+ "menu_label": "Menu",
53
+ "links": [
54
+ { "label": "Home", "href": "/" },
55
+ { "label": "About", "href": "/about/" },
56
+ { "label": "Contact", "href": "/contact/" }
57
+ ]
58
+ }
59
+ }
60
+ ]
61
+ }]
62
+ }
63
+ ]
54
64
  ```
55
65
 
56
- **Why it's robust:** no `overflow:hidden` anywhere near the logo (the #1 cause of a
57
- clipped wordmark); the logo sizes by `height` with `width:auto` so it never
58
- distorts and never gets cropped; symmetric padding so it can't collide with the
59
- section below. If you want a tinted header, set a solid `background` — don't add a
60
- glow that has to be clipped.
61
-
62
- ## Header B — Logo left + links right (no-JS responsive menu)
63
-
64
- ```html
65
- <header class="tr-hdr tr-hdr--nav">
66
- <div class="tr-hdr-inner">
67
- <a class="tr-hdr-logo" href="/" aria-label="{{SITE_NAME}} till startsidan">
68
- <img src="{{LOGO_URL}}" alt="{{SITE_NAME}}" />
69
- </a>
70
- <input type="checkbox" id="tr-nav-toggle" class="tr-nav-toggle" aria-hidden="true" />
71
- <label for="tr-nav-toggle" class="tr-nav-burger" aria-label="Meny"><span></span><span></span><span></span></label>
72
- <nav class="tr-hdr-nav" aria-label="Huvudmeny">
73
- <a href="/">Start</a>
74
- <a href="#">Sidan ett</a>
75
- <a href="#">Sidan två</a>
76
- <a class="tr-hdr-cta" href="#kontakt">Kontakta oss</a>
77
- </nav>
78
- </div>
79
- </header>
80
- <style>
81
- .tr-hdr--nav{background:var(--color-surface,#fff);border-bottom:1px solid rgba(0,0,0,.06)}
82
- .tr-hdr-inner{max-width:1160px;margin:0 auto;padding:.9rem 1.5rem;display:flex;align-items:center;justify-content:space-between;gap:1rem;flex-wrap:wrap}
83
- .tr-hdr-logo{line-height:0}
84
- .tr-hdr-logo img{height:clamp(36px,4.6vw,50px);width:auto;display:block}
85
- .tr-hdr-nav{display:flex;align-items:center;gap:clamp(1rem,2.4vw,2rem);font-family:var(--font-heading),sans-serif;font-weight:600}
86
- .tr-hdr-nav a{color:var(--color-text,#1a1a1a);text-decoration:none}
87
- .tr-hdr-nav a:hover{color:var(--color-primary,#1F4FB8)}
88
- .tr-hdr-cta{background:var(--color-primary,#1F4FB8);color:var(--color-primary-fg,#fff);padding:.6rem 1.2rem;border-radius:999px}
89
- .tr-hdr-cta:hover{filter:brightness(1.05);color:var(--color-primary-fg,#fff)}
90
- .tr-nav-toggle{display:none}
91
- .tr-nav-burger{display:none;flex-direction:column;gap:5px;cursor:pointer;padding:.4rem}
92
- .tr-nav-burger span{width:24px;height:2px;background:var(--color-text,#1a1a1a);border-radius:2px}
93
- @media(max-width:760px){
94
- .tr-nav-burger{display:flex}
95
- .tr-hdr-nav{flex-basis:100%;flex-direction:column;align-items:stretch;gap:.2rem;max-height:0;overflow:hidden;transition:max-height .25s ease}
96
- .tr-hdr-nav a{padding:.7rem .2rem}
97
- .tr-nav-toggle:checked ~ .tr-hdr-nav{max-height:60vh}
66
+ `core/navigation` emits a real `nav` and list in initial HTML, marks the
67
+ matching page with `aria-current="page"`, and progressively enhances to a
68
+ button-controlled mobile menu. Verify Tab and Shift-Tab order, Enter/Space,
69
+ Escape returning focus to the disclosure, visible focus, long translated
70
+ labels, and no horizontal overflow. Without JavaScript the links remain
71
+ visible and usable.
72
+
73
+ ## Footer tree and consent
74
+
75
+ Use the same tree for `footer`, normally with `padding_y: "lg"`, a small site
76
+ logo, localized footer navigation, and optional contact/prose blocks. The
77
+ footer navigation gets its own accessible label; it is not the main
78
+ navigation landmark.
79
+
80
+ Cookie consent is a site feature, not a footer widget. Configure it separately:
81
+
82
+ ```json
83
+ {
84
+ "cookie_consent": {
85
+ "enabled": true,
86
+ "text": "We use optional cookies. <a href=\"/privacy/\">Privacy policy</a>",
87
+ "privacy_policy_url": "/privacy/",
88
+ "reload_after_consent": false
89
+ }
98
90
  }
99
- </style>
100
- ```
101
-
102
- **Why it's robust:** the mobile menu is a pure-CSS checkbox toggle — no JS to break,
103
- no library. The `overflow:hidden` is ONLY on the collapsing nav list (never on the
104
- header or the logo), so the logo is always fully visible. Links use site colour
105
- variables so it matches the brand automatically. The header wraps (`flex-wrap`) so
106
- nothing overflows the viewport on narrow screens.
107
-
108
- ---
109
-
110
- ## Footer A — Centered minimal
111
-
112
- ```html
113
- <footer class="tr-ftr tr-ftr--center">
114
- <div class="tr-ftr-inner">
115
- <div class="tr-ftr-brand">{{SITE_NAME}}</div>
116
- <p class="tr-ftr-tag">{{TAGLINE}}</p>
117
- <p class="tr-ftr-contact"><a href="mailto:{{EMAIL}}">{{EMAIL}}</a></p>
118
- <p class="tr-ftr-copy">© {{YEAR}} {{SITE_NAME}}</p>
119
- </div>
120
- </footer>
121
- <style>
122
- .tr-ftr--center{background:var(--color-primary,#163C8C);color:rgba(255,255,255,.78)}
123
- .tr-ftr--center .tr-ftr-inner{max-width:1120px;margin:0 auto;padding:2.6rem 1.5rem;text-align:center;display:grid;gap:.45rem}
124
- .tr-ftr-brand{font-family:var(--font-heading),sans-serif;font-weight:800;font-size:1.35rem;color:#fff}
125
- .tr-ftr-tag{margin:0;font-size:1rem;color:rgba(255,255,255,.85)}
126
- .tr-ftr-contact{margin:.15rem 0 0}
127
- .tr-ftr-contact a{color:#fff;text-decoration:none;font-weight:600}
128
- .tr-ftr-contact a:hover{text-decoration:underline}
129
- .tr-ftr-copy{margin:.8rem 0 0;font-size:.85rem;color:rgba(255,255,255,.55)}
130
- </style>
131
- ```
132
-
133
- ## Footer B — Three columns (brand · links · contact)
134
-
135
- ```html
136
- <footer class="tr-ftr tr-ftr--cols">
137
- <div class="tr-ftr-grid">
138
- <div class="tr-ftr-col">
139
- <div class="tr-ftr-brand">{{SITE_NAME}}</div>
140
- <p class="tr-ftr-tag">{{TAGLINE}}</p>
141
- </div>
142
- <nav class="tr-ftr-col" aria-label="Sidfot">
143
- <a href="/">Start</a>
144
- <a href="#">Sidan ett</a>
145
- <a href="#">Sidan två</a>
146
- </nav>
147
- <div class="tr-ftr-col">
148
- <p class="tr-ftr-contact"><a href="mailto:{{EMAIL}}">{{EMAIL}}</a></p>
149
- </div>
150
- </div>
151
- <p class="tr-ftr-copy">© {{YEAR}} {{SITE_NAME}}</p>
152
- </footer>
153
- <style>
154
- .tr-ftr--cols{background:var(--color-primary,#163C8C);color:rgba(255,255,255,.78)}
155
- .tr-ftr--cols .tr-ftr-grid{max-width:1120px;margin:0 auto;padding:3rem 1.5rem 1.4rem;display:grid;grid-template-columns:1.4fr 1fr 1fr;gap:2rem}
156
- .tr-ftr--cols .tr-ftr-brand{font-family:var(--font-heading),sans-serif;font-weight:800;font-size:1.35rem;color:#fff;margin-bottom:.4rem}
157
- .tr-ftr--cols .tr-ftr-tag{margin:0;color:rgba(255,255,255,.8);max-width:34ch}
158
- .tr-ftr--cols .tr-ftr-col{display:grid;gap:.5rem;align-content:start}
159
- .tr-ftr--cols nav a{color:rgba(255,255,255,.85);text-decoration:none}
160
- .tr-ftr--cols nav a:hover{color:#fff;text-decoration:underline}
161
- .tr-ftr-contact a{color:#fff;text-decoration:none;font-weight:600}
162
- .tr-ftr--cols .tr-ftr-copy{max-width:1120px;margin:0 auto;padding:0 1.5rem 2.4rem;font-size:.85rem;color:rgba(255,255,255,.55)}
163
- @media(max-width:680px){.tr-ftr--cols .tr-ftr-grid{grid-template-columns:1fr;gap:1.4rem}}
164
- </style>
165
91
  ```
166
92
 
167
- **Why these footers are robust:** the columns collapse to one at 680px (no
168
- horizontal scroll); all colours come from `--color-*` with fallbacks; the contact
169
- is a real `mailto:` link; nothing relies on fixed heights. Swap `--color-primary`
170
- for a custom dark if the brand's primary is too light for white text.
171
-
172
- ---
173
-
174
- ## Restyling notes
175
-
176
- - The logo always comes from `read_site_settings → logo`. If it's `null`, set it
177
- first (upload + `update_site_settings`) — don't hard-code a path.
178
- - For a **shaped transition** from the header/footer into the page, don't build a
179
- wave band by hand that belongs to the adjacent `core/section` via its
180
- `divider_top` / `divider_bottom` (see `tr-redesign-branch`).
181
- - Keep the brand mark + a way home. Even a dramatic redesign keeps the logo
182
- linking to `/`.
93
+ Use `update_site_settings`, then read settings back. `scripts_necessary` and
94
+ `scripts_optional` execute in visitors' browsers and must be reviewed like
95
+ other API-key-authorized script surfaces. Verify accept/necessary/reject,
96
+ optional-script gating, and keyboard focus in preview and the actual build.
97
+ The signed hosted preview runs at an opaque origin. Consent choices therefore
98
+ use in-memory state for the current preview document; they are intentionally
99
+ not persisted as portal cookies across a reload or page navigation. Accepting
100
+ all must still activate optional scripts immediately, which is enough to test
101
+ the gate without weakening the preview sandbox.
102
+
103
+ ## Version and dependency rules
104
+
105
+ Child versions inherit installed, custom, and third-party block types from
106
+ their base chain. If preview reports a missing type, stop and inspect
107
+ `list_block_types` for that exact version; do not duplicate an Extension or
108
+ installation. A type missing after capability 0.43.0 is a dependency error to
109
+ report, not a reason to let the block silently disappear.
110
+
111
+ ## Final checks
112
+
113
+ - Header and footer landmarks exist exactly once around their content.
114
+ - Logo is fully visible, keeps its aspect ratio, has useful alt text, and links home.
115
+ - Current-page state and localized navigation labels are correct.
116
+ - Mobile disclosure is keyboard accessible and does not trap focus.
117
+ - No tenant CSS is required for spacing, focus, wrapping, or the narrow layout.
118
+ - Preview and a hosted static build match before promotion.
@@ -311,31 +311,36 @@ blocker. Redirect builds cover both source spellings and send internal targets
311
311
  to the site's configured trailing-slash style.
312
312
 
313
313
  Note what the check does NOT catch: it verifies that a URL *resolves*, not
314
- that the page at the other end is the right content. Spot-check the top
315
- `gsc_clicks` URLs by eye.
314
+ that the page at the other end is the right content. Review content and SEO
315
+ parity, then record the exact dataset and result with
316
+ `record_migration_seo_acceptance`.
316
317
 
317
318
  Iterate until `missing` and `broken_redirect` are both zero **on every site**.
318
319
  Then, per site:
319
320
 
320
- 1. `add_domain` / follow the DNS instructions the platform returns
321
- 2. Point DNS
322
- 3. `poll_domain` until verified `activate_domain`
321
+ 1. `add_domain` with the intended apex/www canonical; leave `auto_deploy` on
322
+ 2. Wait for that canonical/sitemap deploy to succeed, then point DNS
323
+ 3. `poll_domain` until verified there is no separate activation step
323
324
  4. Re-run `verify_migration_urls target_origin="https://example.de"` against
324
325
  the real domain, to confirm the cutover kept what the pre-check proved
325
326
  5. Submit the new sitemap in Search Console; keep the old property open for
326
327
  a few weeks and watch the 404 report
328
+ 6. Rerun the complete, unfiltered URL check and SEO review, then require
329
+ `get_migration_launch_report` to return `launch_ready: true`
327
330
 
328
331
  ## Definition of done (per site)
329
332
 
330
333
  - [ ] `get_migration_readiness source_url=<this market's old site>` → `ready: true`, warnings reviewed
331
334
  - [ ] `list_migration_urls status="unhandled"` → 0
332
- - [ ] `verify_migration_urls` → 0 `missing`, 0 `broken_redirect`
335
+ - [ ] Complete `verify_migration_urls` → 0 `missing`, 0 `broken_redirect`, 0 `error`, 0 untested variants
336
+ - [ ] `record_migration_seo_acceptance` is accepted for the latest deploy
337
+ - [ ] `get_migration_launch_report` → `launch_ready: true`
333
338
  - [ ] hreflang cluster written on both/all sides, absolute, final domains
334
339
  - [ ] `language` set on the site; `<html lang>` correct in the deployed HTML
335
340
  - [ ] Internal links rewritten (no lingering absolute links to the old domain)
336
341
  - [ ] Forms rebuilt, recipient address correct for THIS market, test submission sent and received
337
342
  - [ ] Media uploaded to this site's own library (no cross-site `cdn_url`), alt text in this market's language
338
- - [ ] Domain verified + activated; sitemap submitted
343
+ - [ ] Domain declared, canonical deploy succeeded, DNS verified; sitemap submitted
339
344
 
340
345
  ## Pitfalls specific to this job
341
346
 
@@ -26,6 +26,14 @@ agency) reviews each step in their terminal.
26
26
  get_migration_readiness source_url="https://oldsite.com"
27
27
  ```
28
28
 
29
+ When the migration includes reusable page/item layouts, include each proposed
30
+ block composition and its collection fields in this same call. A
31
+ `waiting_for_native_support` result means leave that template intact and do
32
+ not replace the gap with generic custom blocks, raw HTML, or corrective site
33
+ CSS. Independent content and SEO work may continue. Rerun the review after the
34
+ required Core version is deployed, then verify preview and a fresh hosted
35
+ build.
36
+
29
37
  Pass `source_url` — that adds the checks on the site you're migrating FROM.
30
38
  An old host that answers 403/429 to server-side requests is a **blocker**: the
31
39
  import would produce empty pages, or pages containing the host's block page,
@@ -110,26 +118,25 @@ c. Migrate referenced images:
110
118
  text (existing WP `alt` attribute or `aria-label`; fall back to
111
119
  filename only as a last resort).
112
120
 
113
- d. Reconstruct in the target's design. The cleaned HTML is rarely
114
- ready to ship typical fixes: replace WP `wp-block-*` classes
115
- with the target's CSS variables; turn Elementor sections into
116
- plain `<section>` with the target's spacing; fix headings so the
117
- page has exactly one `<h1>`. If you're confident, batch these
118
- through `bulk_replace_text` with `dry_run: true` first.
121
+ d. Reconstruct in the target's design with native blocks first. Read the
122
+ available block types and map headings, prose, images, buttons and layout
123
+ into their typed fields. Use HTML mode only for source-specific markup that
124
+ has no native representation and has passed the composition preflight.
119
125
 
120
126
  e. Write the page as a draft:
121
127
 
122
128
  ```
123
- create_page title="..." slug="<preserved-from-wp>"
124
- html_content="<reconstructed>"
129
+ create_page title="..." slug="<last-path-segment>"
130
+ path="/<preserved-wordpress/path/>"
131
+ blocks=[<native block tree>]
125
132
  status="draft" kind="article" author="..."
126
133
  seo_title="..." seo_description="..."
127
134
  ```
128
135
 
129
136
  **Preserve the source URL.** WP post URLs like
130
- `/2024/01/foo-bar/` go in as `slug: "2024/01/foo-bar"`. The
131
- slug supports slashes; encode the WP permalink structure verbatim
132
- when the customer wants existing links to keep working.
137
+ `/2024/01/foo-bar/` uses `slug: "foo-bar"` and
138
+ `path: "/2024/01/foo-bar/"`. Slug is one segment; `path` preserves the
139
+ complete nested URL.
133
140
 
134
141
  ### 4. Redirects
135
142
 
@@ -183,6 +190,8 @@ import_gsc_performance property="https://old.example.com/" months=6
183
190
  # Or paste a Search Console Pages CSV via csv="..." and source_origin.
184
191
  check_internal_links # database preflight before deploy
185
192
  verify_migration_urls # after trigger_deploy; compact exceptions by default
193
+ record_migration_seo_acceptance # after reviewing this exact deployment
194
+ get_migration_launch_report # final fail-closed launch gate
186
195
  ```
187
196
 
188
197
  The inventory merges slash-equivalent URLs into one work item but preserves
@@ -57,9 +57,9 @@ Common requirements that are easy to misclassify:
57
57
  | Responsive icon/card grid | `core/grid` + `core/icon_box`, or `core/feature_grid`; set responsive fields with `set_block_responsive` |
58
58
  | Custom cards backed by a collection | `core/repeater` / `core/collection_list` with a site-authored `item_compatible` block type as `item_block` |
59
59
  | Grouped collection listing | `core/repeater` with `group_by`; array-valued fields place an item in every matching group |
60
- | Breadcrumbs in a page template | `template/page_breadcrumbs` (verify that the relevant route supplies a breadcrumb trail) |
61
- | Generated heading index | `core/table_of_contents`; choose `h2`, `h2-h3`, or `h2-h4` |
62
- | Previous/next collection item links | `template/item_navigation` in the collection's block template; ordering follows `sort_field` / `sort_dir` |
60
+ | Breadcrumbs in a page template | `template/page_breadcrumbs`; page and item routes supply a server-rendered trail |
61
+ | Generated heading index | `core/table_of_contents`; choose heading levels and set `source_field` for collection content |
62
+ | Previous/next collection item links | `template/item_navigation`; defaults to collection order and can bind explicit neighbor fields |
63
63
  | Download CTA that disappears without a file | `template/show_if` around a context-bound `core/button`; a dedicated download block is only editor convenience |
64
64
  | Sticky/custom header and multi-column footer | Block-mode header/footer partials plus layout blocks, or one reusable custom block type |
65
65
  | Cookie notice | `settings.cookie_consent`, not a page block |
@@ -115,40 +115,22 @@ an iframe cannot be represented.
115
115
 
116
116
  ### 3. Header + footer partials
117
117
 
118
- **Start from a vetted preset — don't hand-roll the layout.** `read_skill
119
- tr-header-footer` has robust header + footer presets (centered logo, logo+nav
120
- with a no-JS mobile menu, centered + 3-column footers) that avoid the usual
121
- traps: clipped logos (no `overflow:hidden` near the logo), distorted logos
122
- (`height` + `width:auto`), and broken mobile menus. Fill the placeholders and
123
- restyle to the palette.
124
-
125
- Partials can use either mode. Use HTML for a compact hand-authored nav, or
126
- block mode when editors need per-field control or an installed Extension must
127
- be placed there. Partials receive the same `{{site.*}}` render context as page
128
- blocks, so literal brand data is optional rather than required:
129
-
130
- ```html
131
- <header class="site-header">
132
- <div class="header-inner">
133
- <a class="header-logo" href="/"><img src="LOGO_MEDIA_URL" alt="Acme Studio" height="40" /></a>
134
- <nav class="header-nav">
135
- <a href="/om-oss">Om oss</a>
136
- <a href="/kontakt">Kontakt</a>
137
- </nav>
138
- </div>
139
- </header>
140
- <style>
141
- .site-header{background:var(--color-background);padding:1rem 2rem}
142
- .header-inner{max-width:1080px;margin:0 auto;display:flex;align-items:center;justify-content:space-between}
143
- .header-nav{display:flex;gap:2rem}
144
- .header-nav a{color:var(--color-text);text-decoration:none}
145
- </style>
146
- ```
118
+ **Start from the native preset — don't hand-roll navigation.** Read
119
+ `tr-header-footer` and use its `template/site_logo` + `core/navigation`
120
+ composition. The server-rendered landmark, current-page state, no-JS links,
121
+ mobile disclosure, focus treatment, and responsive behavior are Core
122
+ contracts rather than tenant CSS/JavaScript.
123
+
124
+ Stage the complete tree with `update_partial partial_id="header"
125
+ patch={blocks:[...]} save=true`, then call `set_partial_mode partial_id="header"
126
+ to="blocks"`. Repeat for the footer and read both partials back. The inactive
127
+ HTML representation is retained for rollback; changing `content_mode` through
128
+ ordinary PATCH/PUT is rejected intentionally.
147
129
 
148
- `replace_partial partial_id="header" html_content="..."` same pattern
149
- for the footer. Design notes: **no border-bottom on the header if the
150
- first page section should meet it seamlessly** let background color
151
- changes do the separating. Anchor links in nav (`/#section`) are fine.
130
+ Use HTML mode only when preserving legacy authored markup that cannot yet be
131
+ represented natively. Partials receive the same `{{site.*}}` render context as
132
+ page blocks. Keep the first section and header backgrounds intentional; do not
133
+ add a decorative border merely to compensate for mismatched spacing.
152
134
 
153
135
  ### 4. Homepage — block tree
154
136
 
@@ -181,7 +163,7 @@ on a block *inside* the section instead):
181
163
  ] },
182
164
  { "type": "core/section", "data": { "padding_y": "lg" }, "children": [
183
165
  { "type": "core/heading", "data": { "text": "Så funkar det", "level": "h2", "align": "center" } },
184
- { "type": "core/grid", "data": { "cols": 3, "gap": "lg" }, "children": [
166
+ { "type": "core/grid", "data": { "cols": { "mobile": 1, "tablet": 2, "desktop": 3 }, "gap": "lg" }, "children": [
185
167
  { "type": "core/step_card", "data": { "number": "1", "title": "…", "text": "<p>…</p>" } },
186
168
  { "type": "core/step_card", "data": { "number": "2", "title": "…", "text": "<p>…</p>" } },
187
169
  { "type": "core/step_card", "data": { "number": "3", "title": "…", "text": "<p>…</p>" } }
@@ -23,6 +23,11 @@ Use `set_block_responsive` (or pass the object form directly in `add_block` /
23
23
  `update_block` data). `read_block_type <id>` tells you which fields are
24
24
  `responsive`.
25
25
 
26
+ The breakpoint object belongs on the responsive field inside `block.data`,
27
+ for example `data.cols`. Do not add a top-level `block.responsive` object: it
28
+ is not a rendered field, and page, partial and collection-template writes
29
+ reject it instead of silently storing an inert value.
30
+
26
31
  ```
27
32
  # 4 columns on desktop, 2 on tablet, 1 on mobile:
28
33
  set_block_responsive target={kind:page,id:home} block_id=<grid-id>
@@ -104,9 +109,7 @@ breakpoints needed.
104
109
  that didn't collapse) and fix THAT element's width / clip it with
105
110
  `overflow:hidden` on its own section. Verify with
106
111
  `document.documentElement.scrollWidth === clientWidth` at 360–390px.
107
- - **`core/grid` `stack_at` may not collapse on mobile** (a known platform bug):
108
- the block writes `style="--cols:N"` inline, and an inline custom property beats
109
- the media query that tries to set `--cols:1`, so the grid stays N-up and text
110
- wraps a letter per line. Workaround until fixed: in page-scoped CSS override the
111
- real property, e.g. `@media(max-width:640px){.my-section [data-block="grid"]{grid-template-columns:1fr!important}}`.
112
+ - `core/grid` `stack_at` and responsive `data.cols` both compile overrides
113
+ that beat the inline mobile baseline. Verify the computed column count at
114
+ the actual breakpoint; no page CSS workaround should be necessary.
112
115
  - Background design reference: `docs/responsive-blocks.md` in the platform repo.