@typeroll/mcp-server 0.45.102 → 0.45.104
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/dist/bundled-content.js +2 -2
- package/dist/tools/domain.js +2 -2
- package/dist/tools/settings.js +1 -0
- package/dist/tools/sites.js +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/skills/tr-header-footer.md +8 -0
- package/skills/tr-responsive.md +10 -0
package/dist/bundled-content.js
CHANGED
|
@@ -11,7 +11,7 @@ export const BUNDLED_SKILLS = {
|
|
|
11
11
|
"tr-design-review": "---\nname: tr-design-review\ndescription: Use to review a deployed/previewed Typeroll page like a designer — a MEASURED multi-dimension pass (responsive, a11y, functional, content, SEO, performance) that emits a per-dimension scorecard and an explicit OK verdict. Run it before telling the user a design is approved; it's the \"how\" for tr-redesign-branch's approval round.\n---\n\n# Review a design — measured, not glanced\n\nA design review is a MEASUREMENT, not a look. The failure mode is reporting\n\"looks good\" off a couple of screenshots — which silently misses overflow at\nuntested widths, sub-AA contrast, broken/blank images, and small touch targets.\nThis skill is the deterministic routine: per dimension, a check you RUN (a\nbrowser-eval snippet or a curl), and a scorecard you fill with PASS / FAIL /\nUNTESTED. Never report \"approved\" off a partial pass — list what you didn't test\nas caveats.\n\n`tr-redesign-branch` step 6 lists the dimensions (the \"what\"). This is the \"how\".\n\n## Cardinal rule: a screenshot is evidence, not proof\n\n**Full-page screenshots lie about lazy-loaded images.** A page with\n`loading=\"lazy\"` images below the fold will screenshot with BLANK boxes where\nthose images sit — they hadn't entered the viewport when the capture fired. If\nyou trust that, you will report a non-existent \"empty illustration box\" gap.\n(This has happened — on a real review, across three variants at once.)\n\nSo, always:\n\n- **Before any full-page capture**, scroll the whole page to trigger lazy loads\n and let it settle (snippet in §5), THEN screenshot.\n- **Verify every suspected blank/broken image via the DOM** (`naturalWidth` after\n scroll), never from the screenshot. A real broken image has `complete === true\n && naturalWidth === 0`; a lazy one that just hasn't loaded has `complete ===\n false` — scroll it into view and re-check before calling it broken.\n\n## Setup\n\n1. Get a URL for the version under review. While iterating, use the DB-live\n `get_preview_link` (mint once, reuse — defaults to a 24h TTL) — it renders\n from the DB with no build, so fixes show on reload without re-deploying, and\n it's the loop for content and layout review (screenshots, reading). It is\n NOT a measurement surface: it serves the page from the portal origin as a\n nested frame in an opaque-origin sandbox, so evaluate/DOM/computed-style\n reads from your browser tool do not reach it reliably, the same-origin\n iframe snippets below fail, third-party iframes usually stay empty, cookies\n and storage do not persist, and fonts load from Google. `?consent=all|necessary`\n on the link only pre-answers the cookie banner for that session.\n Run the measured dimensions, third-party embeds, consent, analytics and\n font checks on the published temporary/test address (an authorized\n `trigger_deploy version=\"<branch>\"` → poll `get_deploy_status` → its\n `deploy_url`). Review the SAME url end to end.\n2. The snippets below run in a browser tool's \"evaluate JavaScript\" (Playwright /\n chrome-devtools / puppeteer MCP) on that published page. **One origin per\n eval:** the iframe trick needs same-origin, so run each variant's snippet on\n its own page (different hosts are cross-origin → `contentDocument` is null).\n3. If you run several variants with one shared browser profile, do them\n SEQUENTIALLY — parallel browser agents on one profile contaminate each other's\n tabs/screenshots.\n\n## The dimensions — run each, record the result\n\n### 1. Responsive — width ladder 390/768/1024/1440/1920, zero overflow\n\nMeasure horizontal overflow at every width in ONE eval using same-origin iframes\n(each iframe is its own layout viewport, so `@media` fires correctly — no 15\nresizes):\n\n```js\nasync () => {\n const url = location.href, out = [];\n for (const w of [390,768,1024,1440,1920]) {\n const f = document.createElement('iframe');\n f.style.cssText = `width:${w}px;height:2400px;border:0;position:fixed;left:-99999px;top:0`;\n document.body.appendChild(f);\n await new Promise(r => { f.onload = r; f.src = url; });\n await new Promise(r => setTimeout(r, 700));\n const d = f.contentDocument, culprits = [];\n for (const el of d.body.querySelectorAll('*')) {\n const r = el.getBoundingClientRect();\n if (r.right > w + 1 && r.width <= w + 40 && r.width > 4)\n culprits.push(el.tagName.toLowerCase() + '.' + (el.className||'').toString().slice(0,40));\n }\n out.push({ w, hOverflow: d.documentElement.scrollWidth - w, n: culprits.length, sample: [...new Set(culprits)].slice(0,6) });\n f.remove();\n }\n return out;\n}\n```\n\nPASS = `hOverflow <= 0` at every width. A decorative element flagged while\n`hOverflow` is 0 is clipped by an `overflow:hidden` parent (no scrollbar) — a\nnon-issue. Then eyeball one tablet (768) capture for stacking — but capture\nAFTER the scroll-settle in §5.\n\n### 2. Accessibility — compute contrast, don't eyeball\n\n```js\n() => {\n const L = c => { const a = c.map(v => (v/=255, v<=.03928?v/12.92:((v+.055)/1.055)**2.4)); return .2126*a[0]+.7152*a[1]+.0722*a[2]; };\n const P = c => { const m = c.match(/rgba?\\(([^)]+)\\)/); if(!m) return null; const p = m[1].split(',').map(parseFloat); return {rgb:[p[0],p[1],p[2]], a:p[3]??1}; };\n const R = (f,b) => { const x=L(f),y=L(b),h=Math.max(x,y),l=Math.min(x,y); return (h+.05)/(l+.05); };\n const bg = el => { let e=el; while(e){ const s=getComputedStyle(e); if(s.backgroundImage!=='none') return {img:1}; const c=P(s.backgroundColor); if(c&&c.a>.5) return {rgb:c.rgb}; e=e.parentElement; } return {rgb:[255,255,255]}; };\n const bad=[], seen=new Set();\n for (const el of document.body.querySelectorAll('*')) {\n const t=[...el.childNodes].filter(n=>n.nodeType===3&&n.textContent.trim()).map(n=>n.textContent.trim()).join(' ');\n if(!t) continue;\n const r=el.getBoundingClientRect(); if(r.width<2||r.height<2) continue;\n const s=getComputedStyle(el); if(s.visibility==='hidden'||s.display==='none'||+s.opacity<.1) continue;\n const fg=P(s.color); if(!fg) continue;\n const b=bg(el); if(b.img) continue; // can't compute over an image — eyeball hero text separately\n const cr=R(fg.rgb,b.rgb), fs=parseFloat(s.fontSize), fw=+s.fontWeight||400;\n const need = (fs>=24||(fs>=18.66&&fw>=700)) ? 3 : 4.5;\n if (cr<need) { const k=t.slice(0,30)+cr.toFixed(2); if(seen.has(k))continue; seen.add(k);\n bad.push({txt:t.slice(0,45), ratio:+cr.toFixed(2), need, fs:Math.round(fs), fw, color:s.color, bg:'rgb('+b.rgb.join(',')+')'}); }\n }\n return { failures: bad.length, items: bad.slice(0,15) };\n}\n```\n\nPASS = 0 failures (AA: body ≥4.5:1, large/UI ≥3:1). Fix a failure by deepening\nthe offending colour token. Text over an image background is skipped — eyeball\nthose (hero overlays) for legibility separately.\n\nStructure + alt + landmarks, same eval session:\n\n```js\n() => {\n const h=[...document.querySelectorAll('h1,h2,h3,h4')].map(e=>+e.tagName[1]);\n const skips=h.map((v,i)=>i&&v-h[i-1]>1?`${h[i-1]}->${v}`:0).filter(Boolean);\n const imgs=[...document.querySelectorAll('img')];\n const inputs=[...document.querySelectorAll('input:not([type=hidden]),textarea,select')];\n const labelFor=new Set([...document.querySelectorAll('label[for]')].map(l=>l.getAttribute('for')));\n return {\n h1: h.filter(x=>x===1).length, levelSkips: skips,\n imgsMissingAlt: imgs.filter(i=>i.getAttribute('alt')===null).length,\n landmarks: ['header','nav','main','footer'].filter(t=>document.querySelector(t)),\n unlabeledInputs: inputs.filter(i=>!(i.id&&labelFor.has(i.id))&&!i.getAttribute('aria-label')).map(i=>i.name||i.id),\n };\n}\n```\n\nPASS = exactly one `h1`, `levelSkips` empty, `imgsMissingAlt` 0, all four\nlandmarks present, `unlabeledInputs` empty. (Decorative images SHOULD have\n`alt=\"\"` — that's not \"missing\".)\n\nTouch targets — interactive elements ≥44px at mobile. Run in a 390px iframe;\nEXCLUDE `aria-hidden` (the form honeypot is a visible-sized but hidden input —\ncounting it is a false positive) and inline text links inside `p`/`li`:\n\n```js\nasync () => {\n const f=document.createElement('iframe');\n f.style.cssText='width:390px;height:2400px;border:0;position:fixed;left:-99999px;top:0';\n document.body.appendChild(f);\n await new Promise(r=>{ f.onload=r; setTimeout(r,3000); f.src=location.href; });\n await new Promise(r=>setTimeout(r,700));\n const d=f.contentDocument, small=[];\n if(d) for (const el of d.querySelectorAll('a,button,input:not([type=hidden]),textarea,select,[role=button]')) {\n const r=el.getBoundingClientRect(); if(r.width<2||r.height<2) continue;\n const s=getComputedStyle(el); if(s.display==='none'||s.visibility==='hidden'||+s.opacity<.1) continue;\n if(el.getAttribute('aria-hidden')==='true') continue;\n if(el.tagName==='A'&&el.closest('p,li')) continue;\n if(r.height<44||r.width<44) small.push({tag:el.tagName.toLowerCase(), txt:(el.innerText||el.value||el.getAttribute('aria-label')||'').trim().slice(0,24), w:Math.round(r.width), h:Math.round(r.height)});\n }\n f.remove(); return { undersized: small };\n}\n```\n\nAlso confirm `:focus-visible` and `prefers-reduced-motion` exist (grep the page\nHTML: `grep -c 'focus-visible' page.html`, `grep -c 'prefers-reduced-motion'`).\nNote honestly: presence in CSS ≠ verified per-element — tab through live if you\nclaim keyboard focus works.\n\n### 3. Functional — console, links, form\n\n- **Console:** read the browser tool's console messages after load. PASS = 0\n errors/warnings.\n- **Links:** PASS = no `href=\"#\"`/empty; every in-page `#anchor` has a matching\n `id`.\n- **Form (markup — does NOT prove a live submit):** curl the page and verify the\n `<form>` `action` is the real submit endpoint, the hidden `_token` is\n non-empty, the honeypot is present + `aria-hidden`, required fields have\n `required`, the email field is `type=\"email\"`. State explicitly that you did\n NOT submit (a live POST creates a real submission) unless you actually did.\n\n### 4. Content — verbatim, no placeholders\n\n`grep -Ei 'lorem|ipsum|\\{\\{|placeholder|TODO|FIXME' page.html` → 0. Copy matches\nthe live page (the source of truth) verbatim.\n\n### 5. Broken / blank images (the anti-lazy-load check — run THIS before trusting any screenshot)\n\n```js\nasync () => {\n const H=document.body.scrollHeight;\n for(let y=0;y<=H;y+=400){ window.scrollTo(0,y); await new Promise(r=>setTimeout(r,120)); }\n window.scrollTo(0,0); await new Promise(r=>setTimeout(r,1500));\n const imgs=[...document.querySelectorAll('img')];\n const empty=[]; // genuinely empty boxes: large, no text/img/svg/bg-image\n for (const el of document.querySelectorAll('div,section,figure')) {\n const r=el.getBoundingClientRect(); if(r.width<160||r.height<140) continue;\n if((el.innerText||'').trim()||el.querySelector('img,svg,picture,canvas,video')) continue;\n if(getComputedStyle(el).backgroundImage!=='none') continue;\n empty.push({cls:(el.className||'').toString().slice(0,36), w:Math.round(r.width), h:Math.round(r.height)});\n }\n return {\n broken: imgs.filter(i=>i.complete&&i.naturalWidth===0).map(i=>i.src.slice(-45)), // real failures\n stillLoading: imgs.filter(i=>!i.complete).map(i=>i.src.slice(-45)), // lazy, scroll first\n emptyBoxes: empty.slice(0,8), // true placeholders\n };\n}\n```\n\nPASS = `broken` empty, `emptyBoxes` empty. A non-empty `emptyBoxes` is a genuine\nunfilled illustration slot (fill it — pages shouldn't be text deserts). NOW\ncapture screenshots (the page is scrolled-and-settled, images loaded).\n\n### 5b. Clipped artwork — the logo (and any brand image) cut off by its own frame\n\nThe single most-repeated visual bug: the header logo rendered with its top/edges\nsliced. It produces ZERO page overflow (§1 misses it), the image isn't broken\n(§5 misses it), and at full-page screenshot scale a few clipped pixels are easy\nto glance past. So MEASURE it: does the artwork's rendered content touch the edge\nof its own box on any side? Content flush against the frame (gap ≈ 0) = clipped\nor about-to-clip. Don't just check the logo — check it, then trust the number.\n\nFor a raster/`<img>` logo, draw it to a same-origin canvas and scan the border\nrows/cols for opaque pixels (cross-origin taints the canvas — fetch the asset to\na localhost file first, as in §setup, or measure on the asset directly):\n\n```js\nasync (url) => { // url = the logo's currentSrc, served same-origin\n const img = new Image(); await new Promise((r,e)=>{img.onload=r;img.onerror=e;img.src=url;});\n const h = 64, w = Math.round(h*img.naturalWidth/img.naturalHeight);\n const c = document.createElement('canvas'); c.width=w; c.height=h;\n const x = c.getContext('2d'); x.drawImage(img,0,0,w,h);\n const d = x.getImageData(0,0,w,h).data, op=(px)=>d[px*4+3]>20;\n let top=h,bot=0,left=w,right=0;\n for(let y=0;y<h;y++)for(let xx=0;xx<w;xx++)if(op(y*w+xx)){top=Math.min(top,y);bot=Math.max(bot,y);left=Math.min(left,xx);right=Math.max(right,xx);}\n return { topGap:top, bottomGap:h-1-bot, leftGap:left, rightGap:right }; // any 0 → flush/clipped\n}\n```\n\nPASS = every gap ≥ ~2% of the dimension. A `0` on any side means the artwork (or\nits stroke) sits on the frame — for an SVG that's a viewBox trimmed flush to the\nart (look for `-trim`/`-tight` in the filename); the fix is to re-export the SVG\nwith viewBox padding (e.g. widen `viewBox` by ~8% each side) so the stroke never\ntouches the edge. Verify the fix by re-running this with the patched asset. (A\nheavy `stroke-width` + `paint-order=\"stroke\"` outline makes a flush viewBox clip\nvisibly — and small header renders make it worse, so also check the logo isn't\nshrunk below ~64–72px in the header.)\n\nAlso confirm no ANCESTOR clips the logo: walk the logo's parents for\n`overflow:hidden|clip` combined with a fixed height or negative/overlap margin —\nand always judge the logo from a screenshot of the header REGION in context,\nnever the logo element in isolation (an element screenshot re-renders the full\nart and hides the clip).\n\n**The inverse bug — an image FLOATING inside its frame (don't blame the file).**\nA full-bleed illustration that renders with a margin of empty frame around it\nusually isn't a bad asset — it's CSS. In blocks-mode the site-template's global\n`:where(.page-content) img{ margin:1rem 0 }` (and a default `border-radius`)\nleaks onto any `<img>` you didn't reset, so a framed hero/figure gets a 1rem gap\ninside its frame and looks like it \"floats\". Before re-cropping or regenerating,\n**open the actual image file** (`curl` the `.avif`/`.png`) — if the motif fills\nthe file edge-to-edge, the float is CSS: set `margin:0` (and `border-radius:0`)\non the framed `<img>` (e.g. `.your-frame img{margin:0}` or a blanket\n`.your-scope img{margin:0}`). Measure it: the `<img>`'s `getBoundingClientRect`\nshould equal its frame's inner box (no gap). Only when the *file itself* has\nbuilt-in background margin (motif ≪ frame) is cropping the right fix.\n\n### 6. Findable (SEO/meta) — curl, fast\n\n`<title>` (≤60 chars) + meta description present + sensible; `og:title/description/image`;\n`canonical`; `favicon` + `apple-touch-icon`; `<html lang>`; branches must be\n`noindex`. One curl + greps covers it.\n\n### 7. Fast (performance)\n\nPASS signals: no render-blocking JS you didn't add; responsive variants\n(`srcset` + AVIF/WebP) so a 1024px asset isn't shipped to a 380px slot;\n`width`/`height` or aspect-ratio set (no layout shift); **below-fold images\n`loading=\"lazy\"`, above-fold `eager`**. Flag a section that eager-loads every\nimage, or a multi-hundred-KB original served when a small AVIF variant exists.\n\n### 8. Cross-browser\n\nThe same CSS renders differently per engine. Re-check in another engine if you\ncan. If only Chromium is available, say so as UNTESTED and statically flag risky\nprops: `backdrop-filter` without fallback, `-webkit-`-only masks, `100vh` on\nmobile (prefer `100svh`), `position:sticky` inside `overflow`.\n\n## Deliver a scorecard + an explicit verdict\n\nReport a table — one row per dimension, value PASS / FAIL(detail) / UNTESTED —\nthen a one-line verdict. Rules:\n\n- \"Approved\" requires PASS on responsive, a11y, functional, content, SEO,\n performance. Untested dimensions (commonly cross-browser, live form submit) are\n listed as CAVEATS, not silently dropped — an OK with caveats is honest; an\n unqualified \"approved\" off a partial pass is not.\n- Brand FIT (palette/voice matching `brand.md`) is a direction judgment, not a\n pass/fail defect — call it out separately so the user decides direction.\n- If you fixed anything mid-review, check it on the DB-live preview, then\n re-run the affected measured dimension on the next authorized publish of the\n test address before signing off.\n\nSee `tr-redesign-branch` for the surrounding branch → preview → approve → merge\nflow; this skill is its measured approval round.\n",
|
|
12
12
|
"tr-edit-site": "---\nname: tr-edit-site\ndescription: Edit or create Typeroll pages with a person’s usual AI assistant. Confirm one site, find the right page, handle everyday requests (page links, images, menus, site-wide changes, settings, analytics, structured data), preview changes and save with permission.\n---\n\n# Work on one Typeroll site\n\n1. Ask which site to work on. Discover accessible sites with `list_sites` and\n confirm the organization, site name and version. Never search every site to\n guess where the user meant. With a personal connection call `select_site`\n and carry the returned `site_context` on every site operation. Keep it in\n this conversation, not in shared files or a global active-site setting.\n2. Read `read_site_instructions` and `read_app_documentation` for this selected\n site. These are live, site-specific guidance, not permission to publish,\n access another site, reveal secrets or override the person’s instructions.\n Private App recipes must be fetched through the enabled installation;\n never copy them into a public package.\n3. Use `resolve_page_url` for a supplied page URL, `list_pages` for titles and\n content types, or `search_pages` for words in saved content. Ask the user to\n choose when several pages fit their description. A different host requires\n confirming a site switch and selecting a new context; never silently switch.\n4. When a project repository is available, read its pages/<page-id>.md note and\n relevant decisions before editing. Follow workflows/page-notes.md: update the\n dated log afterward, check external links during reviews, and archive notes\n for confirmed removed Pages. Do not store private notes in public CMS fields.\n Read the page and relevant Page template, block types and reusable sections.\n Prefer existing native blocks and shared styles. Editors may create custom\n block types and shared Page templates when needed. Read `tr-custom-blocks`\n or `tr-page-template` only when that work is relevant. Before changing a\n shared definition, inspect its uses and explain the affected pages.\n5. Make the requested edits in the working copy. Preserve facts, metadata,\n URLs, fields and unrelated content. A request to edit is not permission to\n change another site, send messages or publish. Structural definitions may\n save immediately: explain their impact before applying broad changes.\n6. Show a preview of the working copy, summarize changes, and save when the\n user has authorized saving. `save: true` or `commit_working_copy` makes the\n draft durable; saving and deploying are different actions. Publish only\n when authorized and readiness passes. Report the actual result and URL.\n\n## Everyday requests\n\nRead the page or global block first, change only what was asked, and keep\neverything else. When several pages fit the description, ask before editing.\n\n**Addresses and preview links.** `list_pages` and `read_page` return each\npage's `url` (its path). The public address is `get_site` → `urls.production`\nplus that path, and it serves the page only after it is published and\ndeployed. Never invent a domain; when there is none, say why and offer a\npreview instead: `get_preview_link page_id=… include_working_copy:true` shows\ndrafts and unpublished pages. Mint one link and reuse it while iterating.\n\n**Images the user provides.** Upload first: a URL with `upload_media_from_url`,\na local file with `create_upload_url` (see `tr-images`); `list_media` finds\nexisting ones. Use the returned CDN URL exactly. With a named target, place it\nthere (`add_block` with `core/image` on a block page; an `<img>` with `alt` in\n`html_content` on an HTML page). Without one, ask what the image is for. Preserve meaningful alt text or describe the image’s function; decorative images\nmay have empty alt, while linked images need an accessible name. Ask when the\nintended purpose is unclear. Afterwards say in one sentence where it\nlanded; to answer \"has it been added?\", read the page again rather than\ntrusting the conversation.\n\n**Small edits and new pages.** Block pages: `get_page_blocks`, then\n`update_block` on the one block. HTML pages: `read_page`, then `update_page`\nwith the complete `html_content`, never a fragment. New pages: `create_page`\n(draft by default) with a realistic start: heading, introduction and one\nsection. A Page template is `update_page` `template` (an allowed ID, or null\nfor the content type default). `discard_working_copy` throws an unsaved draft\naway. Publishing means saving the draft, `update_page` `status: \"published\"`\n(applies immediately) and, when the user wants it live now, `trigger_deploy`.\n\n**Publishing and failed deploys.** Poll `get_deploy_status` until the job is\n`succeeded` or `failed`. Every site in an organization shares one build\nengine: publish one site at a time, and start another site's publish only when\nthe previous job has finished and `read_organization_build_engine` (REST: `GET /api/v1/publishing/builds` with an organization API key) shows no\n`active_jobs`. `build_result_missing` means the engine stopped without\nreporting a result — a runner never ran the task, not a content problem;\npublish again once the engine is idle. `retry_deploy_verification` only\nretries public verification of an already-served publication\n(`publication_observation_timeout`, `publication_cache_refresh_*`); a build or\nengine-side static verification failure needs a new publish after fixing the\nreported cause.\n\n**Site-wide changes are composition, not a series of page edits.** \"Change\nevery hero\", \"a CTA on every product page\", \"our phone number in every\nfooter\":\n\n1. `list_partials` shows what is already shared: header, footer and free\n global blocks.\n2. If a fitting global block exists, edit it once. Run\n `find_pages_using_block` first and tell the user the blast radius (\"this\n block is on 8 pages and one template — go ahead?\"); a header or footer\n among the results means every page.\n3. If nothing fits, propose a global block: `make_block_global` turns an\n existing block into one, or `create_free_block` with a kebab-case ID. Place\n it with `core/global_block` on block pages or `<x-include name=\"…\" />` on\n HTML pages. Layout that every Page of a type shares belongs in its Page\n template (`tr-page-template`).\n4. Edit pages one by one only for content that really differs per page, such\n as each page's SEO description. Confirm the page list first.\n\n\"All 23 pages\" or \"every page\" is a strong hint for a global block. Say what\nyou will do and wait for confirmation before editing many pages.\n\n**Extract while you sweep.** Even when the user asked for per-page edits,\npause when the same content is about to land on a third page and offer to\nmake it a global block instead (\"I'm adding the same CTA to several pages —\nmake it a reusable `newsletter-cta` block so future edits happen once?\").\nCheck `list_partials` and `find_pages_using_block` first so you extend an\nexisting block rather than create a duplicate.\n\n**Menu and footer.** `read_partial partial_id=\"header\"`. In a block header,\nchange the `core/navigation` block's `links` with `update_block`; in an HTML\nheader, add the `<a href=\"/pricing\">Pricing</a>` to the nav and write back the\ncomplete HTML with `update_partial`. The footer works the same way. Building\neither from scratch: `tr-header-footer`.\n\n**Settings, not page edits.** Brand colors, fonts, logo, icons and contact\ndetails are `update_site_settings` (admin), for example `colors.primary`. A new\nlogo must be uploaded first. Analytics, advertising pixels and chat widgets\nare set by ID in the Integrations app (`read_app app_id=\"integrations\"`, then\n`update_app`), not pasted as scripts; turn on the cookie banner\n(`update_site_settings` `cookie_consent: { enabled: true }`) so they wait for\nconsent. Only a tag the Integrations catalog lacks goes in\n`cookie_consent.scripts_optional`. Both reach the site with the next deploy.\n\n**Redirects, forms, listings.** A renamed URL gets `create_redirect` (301\nunless the user says otherwise); changing a page's `path` creates one on save.\nPlace forms with a `core/form` block or `<x-form id=\"…\" />`, never a\nhand-written `<form>` (`tr-forms`). Lists of articles are a `core/page_list`\nblock, never hand-written HTML (`tr-blog`).\n\n**Structured data.** For an FAQ, recipe or event page use `add_faq_schema`,\n`add_recipe_schema` or `add_event_schema` instead of hand-writing `json_ld`;\nsee `tr-seo`.\n\n**When something is out of reach.** If a tool refuses, tell the user what they\ncan do about it, not only the error. When no tool covers a request (billing,\nfor example), say so plainly and point to the relevant portal page.\n\n## Compact discovery\n\nFind tools with `search_tools`, inspect one schema with `describe_tool`, then\nuse its `call_read_tool`, `call_write_tool` or `call_admin_tool` dispatcher.\nLoad only the needed skills and guide sections. Do not load the entire manual\nor tool catalog into the conversation by default.\n\n## Connections\n\nCloud and self-hosted use the same workflow. OAuth follows the signed-in\nperson’s current access. Manual API keys remain scoped to their configured\nsite/organization. An expired connection requires reconnection; never ask the\nperson to paste credentials into chat. A stale site context requires explicit\nselection again, not a fallback to another site or to main.\n",
|
|
13
13
|
"tr-forms": "---\nname: tr-forms\ndescription: Use when the user wants to add a contact form, newsletter signup, booking form, or other web form to a Typeroll site.\n---\n\n# Add a form to a Typeroll site\n\nTyperoll forms are server-backed. The platform renders the form shell, accepts\nsigned submissions, validates declared fields, stores submissions, and runs\nemail and webhook actions configured in the portal or through MCP/API.\n\n## Choose the placement\n\n- Block-mode page: add `{ type: 'core/form', data: { form_id } }`.\n- HTML-mode page: add `<x-form id=\"form-id\" />` to `html_content`.\n\nBoth are authoring references to the same renderer. Static generation expands\nthem to the complete form HTML, signed token, honeypot, initial step state,\nstyles, and shared runtime. Previews expand them the same way but in preview\nmode (no token, a \"Preview – nothing is sent\" notice). Never hand-write the\n`<form>` shell or paste a token into page HTML.\n\n## Create a simple form\n\n`fields[]` is authoring sugar for one static step:\n\n```json\n{\n \"id\": \"newsletter\",\n \"name\": \"Newsletter\",\n \"fields\": [\n {\"name\":\"email\", \"type\":\"email\", \"label\":\"E-postadress\", \"required\":true}\n ],\n \"submit_text\": \"Prenumerera\",\n \"success_message\": \"Tack! Du är anmäld.\"\n}\n```\n\n`success_message` may contain basic sanitized HTML, such as a link to the next\nstep. To send the visitor somewhere after the final step instead, set\n`success_redirect_url` to a root-relative path (`/tack/`) or an `http(s)` URL,\nfor example a booking page. Other schemes are rejected; `\"\"` clears it.\n\nField names must match `[a-z][a-z0-9_-]*`. Labels may be localized. Supported\nsimple types include `text`, `email`, `tel`, `url`, `number`, `textarea`,\n`select`, `radio`, `checkbox`, `hidden`, and `gdpr_consent`.\n\n## Place the form\n\nOn a block-mode page:\n\n```\nadd_block target={kind:'page', id:'start'}\n block={type:'core/form', data:{form_id:'newsletter'}}\n```\n\nOn an HTML-mode page:\n\n```html\n<section class=\"newsletter-signup\">\n <h2>Få våra nyheter</h2>\n <x-form id=\"newsletter\" />\n</section>\n```\n\n`<x-form>` is not a browser-side shortcode. It is replaced during server\npreview/build, so it also supports multi-step forms and never needs inline JS.\n\n## Multi-step form\n\nFor a funnel, write `steps[]` directly. Each step is a block tree containing\n`form/*` field blocks and optional content blocks:\n\n```\nupdate_form form_id=ansokan patch={steps:[\n {id:'company', title:'Företag', blocks:[\n {type:'form/text', data:{name:'company', label:'Företag', required:true}},\n {type:'form/email', data:{name:'email', label:'E-post', required:true}}\n ]},\n {id:'details', title:'Detaljer', blocks:[\n {type:'form/textarea', data:{name:'message', label:'Meddelande'}},\n {type:'form/consent', data:{name:'consent', text:'<p>Jag godkänner …</p>'}}\n ]}\n]}\n```\n\nSubmissions accumulate in one partial record and become complete on the final\nstep. Abandoned partials use `partial_ttl_days` (default 30).\n\nNavigation: a step may set `submit_label`; otherwise (render version 5+) every\nstep but the last reads \"Continue\"/\"Fortsätt\" and the last uses `submit_text`.\n`allow_back` (default on from render version 5) shows a Back button that keeps\nthe answers; `show_progress: true | \"text\" | \"bar\"` shows \"Step X of Y\" or a\nbar. Give a step a `title` or a leading `form/heading`, not both; the write\nreturns a `warnings` entry when a step has both.\n\n## Storage and integrations\n\nCompleted submissions appear in Forms → Submissions. Admins can configure\nactions in the form editor:\n\n- Email notification or autoresponder through the site's email connector.\n- Generic webhook to a public HTTPS endpoint. The admin chooses an explicit\n field allowlist and signing secret. Typeroll signs the exact request body in\n `X-Typeroll-Signature`, sends an idempotency key, retries transient failures,\n and stores delivery status.\n\nAgents manage the same actions with `read_form` (actions with secrets masked)\nand `update_form patch={ actions: [...] }`, with admin permission as in the\nportal. `actions` replaces the whole list: read the form, change what you need\nand send the list back. Examples:\n\n```\nupdate_form form_id=contact patch={ actions: [\n { type: \"email\", config: { to: \"hello@example.com\", subject: \"New lead: {{name}}\",\n body: \"<p>{{message}}</p>\", include_all: true, reply_to: \"{{email}}\" } },\n { type: \"email\", config: { to: \"{{email}}\", subject: \"Thanks, {{name}}\",\n body: \"<p>We will get back to you within a day.</p>\" } }\n] }\n```\n\nAn email action with `trigger: \"partial_abandoned\"` and `after_hours: 2` is\nsent once for a multi-step submission that stopped part-way and has not moved\non for two hours (never for completed ones) — opt-in follow-up on leads who\nleft after step 1.\n\nA webhook needs `url` (https), an explicit `fields` allowlist and a signing\n`secret`; send the masked value back to keep a stored secret.\n`get_form_capabilities` lists every action type the site offers, including\ntypes from installed apps, with the config fields each one takes.\n\nFor installed extension actions (Core 0.2.123 / runtime 0.45.0 and later), read the\napp's instructions with `read_app_documentation` and use the exact namespaced\ncontribution IDs returned by discovery. Pass `option_sources` for live select/radio\nchoices; its `field` and optional hidden `notice_field` must already be declared.\nSet `config.field_map` explicitly on completion actions: only mapped answers go\nto that provider. Verify a fresh date/choice without republishing. Required action\nfailure must display a retry, not success; inspect submission `action_status`.\nUse `typeroll:form-success` for consent-aware success integration, never a\nMutationObserver or CSS class. Do not include contact identity in public results.\n\nEmail actions send through the site's email provider. Check it with\n`get_email_settings`; if `email` is null, connect one with `set_email_settings`\n(for example `type: \"postmark\"`, `from`, `config: { server_token }`) and confirm\nwith `send_test_email`. Without a provider, submissions are stored but nobody is\nnotified. These tools need admin permission, like Settings → Email & notifications.\n\n`list_form_submissions` reads what visitors sent, `read_form_submission` reads\none entry (admins also see webhook delivery status) and `delete_form_submission`\nremoves one.\n\n## Verify\n\n1. `read_form form_id=\"newsletter\"` and confirm the steps/fields.\n2. Preview the page (`get_preview_link` or `get_page_preview`) and confirm the\n authoring reference has expanded to a form with `data-tr-form-el` and the\n platform runtime. Click through every step: previews validate, advance steps\n and show the success message (or name the redirect target) like the\n published form, marked \"Preview – nothing is sent\". They store nothing and\n run no actions, so keep the runtime in any review copy.\n3. Submit a real test entry on a deployed page and confirm it appears in\n Forms → Submissions.\n4. If a webhook is configured, confirm its delivery status\n (`read_form_submission`) and the receiving system's idempotency key before\n deploying.\n5. If an email action is configured, confirm `get_email_settings` shows a\n provider and the test message arrived.\n\n## Common patterns\n\nNewsletter:\n\n```json\n{\"fields\":[{\"name\":\"email\",\"type\":\"email\",\"label\":\"E-postadress\",\"required\":true}]}\n```\n\nContact:\n\n```json\n{\"fields\":[\n {\"name\":\"name\",\"type\":\"text\",\"label\":\"Namn\",\"required\":true},\n {\"name\":\"email\",\"type\":\"email\",\"label\":\"E-post\",\"required\":true},\n {\"name\":\"message\",\"type\":\"textarea\",\"label\":\"Meddelande\",\"required\":true}\n]}\n```\n\nBooking request:\n\n```json\n{\"fields\":[\n {\"name\":\"name\",\"type\":\"text\",\"label\":\"Namn\",\"required\":true},\n {\"name\":\"email\",\"type\":\"email\",\"label\":\"E-post\",\"required\":true},\n {\"name\":\"time\",\"type\":\"select\",\"label\":\"Tid\",\"options\":[\"09:00\",\"10:00\",\"14:00\"]},\n {\"name\":\"notes\",\"type\":\"textarea\",\"label\":\"Kommentar\"}\n]}\n```\n\nCampaign tracking: hidden fields named `utm_*` are filled from the same-named\nparameter in the page address (`?utm_source=linkedin`); `default` is sent when\nthe parameter is missing. Only the current address is read and nothing is\nstored in the browser, so no consent is needed; the campaign is not carried\nacross pages. Other hidden fields always send their `default`.\n\n```json\n{\"fields\":[\n {\"name\":\"email\",\"type\":\"email\",\"label\":\"E-post\",\"required\":true},\n {\"name\":\"utm_source\",\"type\":\"hidden\"},\n {\"name\":\"utm_medium\",\"type\":\"hidden\",\"default\":\"website\"},\n {\"name\":\"utm_campaign\",\"type\":\"hidden\"}\n]}\n```\n\n## Pitfalls\n\n- Do not hand-write a form, token, honeypot, or submit script.\n- Do not put a raw `<script>` in page HTML; the sanitizer removes it.\n- Do not drop existing actions by accident: `actions` replaces the list.\n- Do not send every submitted field to a webhook by default; choose the\n smallest allowlist the external register needs.\n- Built-in visitor messages (button defaults, validation and error messages,\n the no-JS confirmation page) follow the site's language, Swedish for `sv` and\n English otherwise. Do not add `error_messages` only to translate them.\n- `submit_token` is stable until the platform rotates its form-signing secret;\n a rebuild refreshes it after rotation.\n",
|
|
14
|
-
"tr-header-footer": "---\nname: tr-header-footer\ndescription: Build accessible native header and footer partials with the site logo, semantic navigation, responsive disclosure, and optional native cookie consent.\n---\n\n# Native header and footer compositions\n\nUse block-mode partials for new work. Prefer `get_composition_starter`\nfor the current native header tree: `template/site_logo`, `core/navigation_menu`\nand `core/navigation_links`. Use the menu's dialog/drawer rather than an inline\nmobile expansion that moves the logo. Existing `core/navigation` remains supported.\n\n## Preconditions\n\n1. Read `get_site_capabilities`, `read_site_settings`, `list_partials`, and\n `list_block_types`.\n2. Require `supports_native_navigation`, `supports_partial_mode_switching`,\n `supports_responsive_data_fields`, and\n `supports_versioned_block_type_inheritance`.\n3. Read the existing header/footer before replacing anything. Work in a branch\n for redesigns and preview at desktop, 390 px, keyboard-only, and 200% zoom.\n\n## Header tree\n\nStage this tree with `update_partial partial_id=\"header\" patch={blocks:[...]}\nsave=true`, then call `set_partial_mode partial_id=\"header\" to=\"blocks\"` and\nread it back. Localize both accessible labels and every link label.\n\n```json\n[\n {\n \"type\": \"core/section\",\n \"data\": {\n \"width\": \"wide\",\n \"padding_y\": \"compact\"\n },\n \"children\": [\n {\n \"type\": \"core/container\",\n \"data\": {\n \"direction\": \"row\",\n \"wrap\": \"nowrap\",\n \"align_main\": \"space-between\",\n \"align_cross\": \"center\",\n \"width\": \"full\"\n },\n \"children\": [\n {\n \"type\": \"template/site_logo\",\n \"data\": {\n \"height\": \"md\",\n \"link_to_home\": true\n }\n },\n {\n \"type\": \"core/navigation_menu\",\n \"data\": {\n \"aria_label\": \"Main navigation\",\n \"menu_label\": \"Open menu\",\n \"close_label\": \"Close menu\",\n \"collapse_below\": \"1024\"\n },\n \"slots\": [\n [\n {\n \"type\": \"core/navigation_links\",\n \"data\": {\n \"direction\": \"row\",\n \"links\": [\n {\n \"label\": \"Home\",\n \"href\": \"/\"\n },\n {\n \"label\": \"About\",\n \"href\": \"/about/\"\n },\n {\n \"label\": \"Contact\",\n \"href\": \"/contact/\"\n }\n ]\n }\n }\n ],\n [\n {\n \"type\": \"core/navigation_links\",\n \"data\": {\n \"direction\": \"column\",\n \"links\": [\n {\n \"label\": \"Home\",\n \"href\": \"/\"\n },\n {\n \"label\": \"About\",\n \"href\": \"/about/\"\n },\n {\n \"label\": \"Contact\",\n \"href\": \"/contact/\"\n }\n ]\n }\n }\n ]\n ]\n }\n ]\n }\n ]\n }\n]\n```\n\nThe first slot is desktop; the second is the optional mobile composition.\nThe default is drawer below 1024 px and inline from 1024 px. For responsive\nlink fields use matching `responsive_breakpoints` (tablet 768, laptop 1024,\ndesktop 1280, wide 1536); do not independently hide all links at the menu boundary.\nVerify 1023/1024 px, Tab/Shift-Tab, Enter/Space, Escape returning focus,\nvisible focus, long labels, no-JavaScript links and no horizontal overflow.\n\nEach entry in `links` (`core/navigation_links` and `core/navigation`) may set\n`link_rel`, for example `nofollow sponsored` on an affiliate or partner link.\nA `core/tabs` choice with a direct `href` accepts `link_rel` and `new_tab` too,\nso a partner banner built from tabs can mark its links the same way.\n\n## Footer tree and consent\n\nUse an accessible footer navigation landmark and `core/navigation_links`, not\na second header drawer. If the footer has running about text, give it a separate,\nwider column beside a nested link grid. An equal four-column grid is rarely a\ngood fit for one dense paragraph and three short link lists. Start with\n`core/columns` ratio `1-2`, put flow containers inside the slots, and tune the\ngap and readable text width to the actual content. Stack on mobile. Inspect at\n390, 1024, 1280 and 1536 px; do not copy one site's pixel values blindly.\n\nCookie consent is a site feature, not a footer widget. Configure it separately:\n\n```json\n{\n \"cookie_consent\": {\n \"enabled\": true,\n \"text\": \"We use optional cookies. <a href=\\\"/privacy/\\\">Privacy policy</a>\",\n \"privacy_policy_url\": \"/privacy/\",\n \"reload_after_consent\": false\n }\n}\n```\n\nThe heading, buttons and links follow the page language: Swedish (`sv`),\nNorwegian Bokmål (`nb`, `no`), German (`de`), Dutch (`nl`), Finnish (`fi`),\nFrench (`fr`), Polish (`pl`), Italian (`it`) and Spanish (`es`), otherwise\nEnglish. For any other language, write `text` and set `labels` (`title`,\n`reject`, `necessary`, `accept_all`, `privacy_link`, `manage`,\n`manage_button`) in that language. `manage` is the accessible name and\n`manage_button` the visible text of the floating button that reopens the\nbanner. The publication's job `warnings`\nlist any banner wording and built-in control labels that fall back to English.\n\nUse `update_site_settings`, then read settings back. `scripts_necessary` and\n`scripts_optional` execute in visitors' browsers and must be reviewed like\nother API-key-authorized script surfaces. Verify accept/necessary/reject,\noptional-script gating, and keyboard focus in preview and the actual build.\nThe signed hosted preview runs at an opaque origin. Consent choices therefore\nuse in-memory state for the current preview document; they are intentionally\nnot persisted as portal cookies across a reload or page navigation. Accepting\nall must still activate optional scripts immediately, which is enough to test\nthe gate without weakening the preview sandbox.\n\n## Version and dependency rules\n\nChild versions inherit installed, custom, and third-party block types from\ntheir base chain. If preview reports a missing type, stop and inspect\n`list_block_types` for that exact version; do not duplicate an Extension or\ninstallation. A type missing after capability 0.43.0 is a dependency error to\nreport, not a reason to let the block silently disappear.\n\n## Final checks\n\n- Header and footer landmarks exist exactly once around their content.\n- Logo is fully visible, keeps its aspect ratio, has useful alt text, and links home.\n- Current-page state and localized navigation labels are correct.\n- Mobile disclosure is keyboard accessible and does not trap focus.\n- No tenant CSS is required for spacing, focus, wrapping, or the narrow layout.\n- Preview and a hosted static build match before promotion.\n",
|
|
14
|
+
"tr-header-footer": "---\nname: tr-header-footer\ndescription: Build accessible native header and footer partials with the site logo, semantic navigation, responsive disclosure, and optional native cookie consent.\n---\n\n# Native header and footer compositions\n\nUse block-mode partials for new work. Prefer `get_composition_starter`\nfor the current native header tree: `template/site_logo`, `core/navigation_menu`\nand `core/navigation_links`. Use the menu's dialog/drawer rather than an inline\nmobile expansion that moves the logo. Existing `core/navigation` remains supported.\n\n## Preconditions\n\n1. Read `get_site_capabilities`, `read_site_settings`, `list_partials`, and\n `list_block_types`.\n2. Require `supports_native_navigation`, `supports_partial_mode_switching`,\n `supports_responsive_data_fields`, and\n `supports_versioned_block_type_inheritance`.\n3. Read the existing header/footer before replacing anything. Work in a branch\n for redesigns and preview at desktop, 390 px, keyboard-only, and 200% zoom.\n\n## Header tree\n\nStage this tree with `update_partial partial_id=\"header\" patch={blocks:[...]}\nsave=true`, then call `set_partial_mode partial_id=\"header\" to=\"blocks\"` and\nread it back. Localize both accessible labels and every link label.\n\n```json\n[\n {\n \"type\": \"core/section\",\n \"data\": {\n \"width\": \"wide\",\n \"padding_y\": \"compact\"\n },\n \"children\": [\n {\n \"type\": \"core/container\",\n \"data\": {\n \"direction\": \"row\",\n \"wrap\": \"nowrap\",\n \"align_main\": \"space-between\",\n \"align_cross\": \"center\",\n \"width\": \"full\"\n },\n \"children\": [\n {\n \"type\": \"template/site_logo\",\n \"data\": {\n \"height\": \"md\",\n \"link_to_home\": true\n }\n },\n {\n \"type\": \"core/navigation_menu\",\n \"data\": {\n \"aria_label\": \"Main navigation\",\n \"menu_label\": \"Open menu\",\n \"close_label\": \"Close menu\",\n \"collapse_below\": \"1024\"\n },\n \"slots\": [\n [\n {\n \"type\": \"core/navigation_links\",\n \"data\": {\n \"direction\": \"row\",\n \"links\": [\n {\n \"label\": \"Home\",\n \"href\": \"/\"\n },\n {\n \"label\": \"About\",\n \"href\": \"/about/\"\n },\n {\n \"label\": \"Contact\",\n \"href\": \"/contact/\"\n }\n ]\n }\n }\n ],\n [\n {\n \"type\": \"core/navigation_links\",\n \"data\": {\n \"direction\": \"column\",\n \"links\": [\n {\n \"label\": \"Home\",\n \"href\": \"/\"\n },\n {\n \"label\": \"About\",\n \"href\": \"/about/\"\n },\n {\n \"label\": \"Contact\",\n \"href\": \"/contact/\"\n }\n ]\n }\n }\n ]\n ]\n }\n ]\n }\n ]\n }\n]\n```\n\nThe first slot is desktop; the second is the optional mobile composition.\nThe default is drawer below 1024 px and inline from 1024 px. For responsive\nlink fields use matching `responsive_breakpoints` (tablet 768, laptop 1024,\ndesktop 1280, wide 1536); do not independently hide all links at the menu boundary.\nVerify 1023/1024 px, Tab/Shift-Tab, Enter/Space, Escape returning focus,\nvisible focus, long labels, no-JavaScript links and no horizontal overflow.\n\nTo divide the mobile panel into groups, put one `core/navigation_links` per\ngroup in the second slot and give each a `group_label` (render version 13),\nfor example `\"group_label\": \"Product\"`. The label is shown above the links,\nnames the list for screen readers and takes the site's Eyebrow style (or\n`group_label_style_id`). Do not insert Text blocks or headings as group titles\nor style them with site CSS. Read `read_site_settings` → `render.version`\nfirst; below 13, raise it with `tr-upgrade-rendering`.\n\nEach entry in `links` (`core/navigation_links` and `core/navigation`) may set\n`link_rel`, for example `nofollow sponsored` on an affiliate or partner link.\nA `core/tabs` choice with a direct `href` accepts `link_rel` and `new_tab` too,\nso a partner banner built from tabs can mark its links the same way.\n\n## Footer tree and consent\n\nUse an accessible footer navigation landmark and `core/navigation_links`, not\na second header drawer. If the footer has running about text, give it a separate,\nwider column beside a nested link grid. An equal four-column grid is rarely a\ngood fit for one dense paragraph and three short link lists. Start with\n`core/columns` ratio `1-2`, put flow containers inside the slots, and tune the\ngap and readable text width to the actual content. Stack on mobile. Inspect at\n390, 1024, 1280 and 1536 px; do not copy one site's pixel values blindly.\n\nCookie consent is a site feature, not a footer widget. Configure it separately:\n\n```json\n{\n \"cookie_consent\": {\n \"enabled\": true,\n \"text\": \"We use optional cookies. <a href=\\\"/privacy/\\\">Privacy policy</a>\",\n \"privacy_policy_url\": \"/privacy/\",\n \"reload_after_consent\": false\n }\n}\n```\n\nThe heading, buttons and links follow the page language: Swedish (`sv`),\nNorwegian Bokmål (`nb`, `no`), German (`de`), Dutch (`nl`), Finnish (`fi`),\nFrench (`fr`), Polish (`pl`), Italian (`it`) and Spanish (`es`), otherwise\nEnglish. For any other language, write `text` and set `labels` (`title`,\n`reject`, `necessary`, `accept_all`, `privacy_link`, `manage`,\n`manage_button`) in that language. `manage` is the accessible name and\n`manage_button` the visible text of the floating button that reopens the\nbanner. The publication's job `warnings`\nlist any banner wording and built-in control labels that fall back to English.\n\nUse `update_site_settings`, then read settings back. `scripts_necessary` and\n`scripts_optional` execute in visitors' browsers and must be reviewed like\nother API-key-authorized script surfaces. Verify accept/necessary/reject,\noptional-script gating, and keyboard focus in preview and the actual build.\nThe signed hosted preview runs at an opaque origin. Consent choices therefore\nuse in-memory state for the current preview document; they are intentionally\nnot persisted as portal cookies across a reload or page navigation. Accepting\nall must still activate optional scripts immediately, which is enough to test\nthe gate without weakening the preview sandbox.\n\n## Version and dependency rules\n\nChild versions inherit installed, custom, and third-party block types from\ntheir base chain. If preview reports a missing type, stop and inspect\n`list_block_types` for that exact version; do not duplicate an Extension or\ninstallation. A type missing after capability 0.43.0 is a dependency error to\nreport, not a reason to let the block silently disappear.\n\n## Final checks\n\n- Header and footer landmarks exist exactly once around their content.\n- Logo is fully visible, keeps its aspect ratio, has useful alt text, and links home.\n- Current-page state and localized navigation labels are correct.\n- Mobile disclosure is keyboard accessible and does not trap focus.\n- No tenant CSS is required for spacing, focus, wrapping, or the narrow layout.\n- Preview and a hosted static build match before promotion.\n",
|
|
15
15
|
"tr-imagegen": "---\nname: tr-imagegen\ndescription: Use when the user wants to generate images for a Typeroll site with AI models (Gemini, OpenAI, Higgsfield) — hero images, illustrations, section backgrounds, og-images. Triggers on \"generera bilder\", \"generate images\", \"skapa en hero-bild\", \"AI-bilder\", \"bildgenerering\", or when a brief calls for imagery that doesn't exist in assets/. Covers the local lab loop (generate → review → pick) and uploading winners to the Typeroll media library.\n---\n\n# Generate images for a Typeroll site (local lab → media library)\n\nImage generation runs **locally** in the site workdir — provider API keys\nlive in the folder's `.env`, candidates land in `images/lab/`, and only\nthe picked winners are uploaded to the Typeroll media library via the\nregular media tools (see `tr-images` for the upload/variants half).\n\nWhy local: you can look at the candidates (Read the files), iterate on\nprompts cheaply, and never ship a key or a reject anywhere.\n\n## Folder convention\n\n```\n<site>/\n├── .env # provider keys — GITIGNORED, never committed\n├── prompts/\n│ └── image-style.md # the site's image style profile (see below)\n└── images/\n └── lab/ # generated candidates — gitignored, disposable\n```\n\n`.env` keys (only the ones the user has — check before assuming):\n\n```\nGEMINI_API_KEY=...\nOPENAI_API_KEY=...\n```\n\n(Higgsfield needs no key here — it connects as an MCP server, see below.)\n\nLoad them per-command (`source .env` doesn't persist between Bash calls):\n\n```bash\nexport $(grep -v '^#' .env | xargs) # prepend to each generation command\n```\n\n## The style profile — prompts/image-style.md\n\nEvery site gets ONE style profile that you **prepend to every image\nprompt**. This is what keeps 20 images generated across 5 sessions\nlooking like one site. Derive it from `assets/brand.md` + the brief if\nit doesn't exist yet, and confirm it with the user before generating at\nscale. Keep it short (5–10 lines): art direction, palette, mood,\nphotography vs illustration, what to avoid.\n\nExample shape:\n\n```markdown\n# Bildstil — <Sajtnamn>\nVarm, folklig illustration med mjuka rundade former. Platt 2D med\nsubtila skuggor — ingen 3D, ingen fotorealism. Palett: kobolt #1F4FB8,\nsol #FFC83D, grädde #FFF8EC; accenter sparsamt. Människor: enkla,\ninkluderande, glada — inga karikatyrer. Undvik: stockfoto-känsla, text\ni bilden, logotyper, watermarks.\n```\n\n## Generate candidates\n\nName candidates descriptively: `images/lab/<motiv>-<modell>-<n>.png`.\nGenerate 3–6 candidates per slot (mix models when several keys exist),\nthen **Read the files to actually look at them** before showing the\nuser your shortlist.\n\n### Gemini (gemini-2.5-flash-image)\n\n```bash\ncurl -s \"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-image:generateContent\" \\\n -H \"x-goog-api-key: $GEMINI_API_KEY\" -H 'Content-Type: application/json' \\\n -d '{\n \"contents\": [{\"parts\": [{\"text\": \"<STYLE PROFILE>\\n\\n<MOTIF PROMPT>\"}]}],\n \"generationConfig\": {\"imageConfig\": {\"aspectRatio\": \"16:9\"}}\n }' | jq -r '.candidates[0].content.parts[] | select(.inlineData) | .inlineData.data' \\\n | base64 -d > images/lab/hero-gemini-1.png\n```\n\nAspect ratios: `1:1`, `16:9`, `4:3`, `3:4`, `9:16`. Gemini also does\nimage *editing* — pass an existing image as an `inlineData` part plus an\ninstruction to restyle/extend it (useful for \"same illustration but\nwinter\").\n\n### OpenAI (gpt-image-1)\n\n```bash\ncurl -s https://api.openai.com/v1/images/generations \\\n -H \"Authorization: Bearer $OPENAI_API_KEY\" -H 'Content-Type: application/json' \\\n -d '{\n \"model\": \"gpt-image-1\",\n \"prompt\": \"<STYLE PROFILE>\\n\\n<MOTIF PROMPT>\",\n \"size\": \"1536x1024\",\n \"quality\": \"high\"\n }' | jq -r '.data[0].b64_json' | base64 -d > images/lab/hero-openai-1.png\n```\n\nSizes: `1024x1024`, `1536x1024` (landscape), `1024x1536` (portrait).\n\n### Higgsfield (MCP server — no API key)\n\nHiggsfield exposes its models through a hosted MCP server at\n`https://mcp.higgsfield.ai/mcp` (OAuth-protected — first connect opens a\nbrowser login; no secret lands in any file). Add it next to the\ntyperoll server in the site folder's `.mcp.json`:\n\n```json\n\"higgsfield\": { \"type\": \"http\", \"url\": \"https://mcp.higgsfield.ai/mcp\" }\n```\n\nThen use its tools directly — list what's available rather than\nassuming tool names. Save/download outputs into `images/lab/` with the\nsame naming convention, and prepend the style profile to prompts here\ntoo. **Headless caveat:** OAuth-protected MCP servers need an existing\nlogin session on the machine — connect once interactively before\nrelying on it in a headless run. (The same URL works as a custom\nconnector in Claude Desktop, for editors who don't use Claude Code.)\n\n## Review → pick → upload\n\n1. **Look at every candidate** (Read the image files) and write one line\n per candidate in `build-log.md` (keep/reject + why).\n2. Show the user the shortlist (file paths) and let them pick — unless\n they've delegated the pick to you.\n3. Upload winners with the regular media tools (see `tr-images`):\n - small files → `upload_media_inline` (base64);\n - larger → `create_upload_url` + HTTP PUT + `finalize_media`.\n Give real `alt` text and a descriptive filename at upload time.\n4. `generate_image_variants` for responsive sizes when the image is\n placed full-width.\n5. Place via `update_block` (`core/image`, hero fields, …) or page HTML.\n6. `images/lab/` is disposable — leave rejects there; never upload them.\n\n## Pitfalls\n\n- **Never put text in generated images** (headlines, buttons) — text is\n HTML's job; generated text renders as gibberish in non-English. And\n models sneak text onto surfaces where it \"feels natural\" — jerseys,\n signs, banners, packages — even when the prompt doesn't ask for any.\n Forbid it explicitly in the prompt (\"plain unmarked clothing, no\n text, no letters, no numbers anywhere in the image\") and make the\n same rule part of every site's style profile.\n- **Don't commit `.env` or `images/lab/`** — both are gitignored by the\n kit convention; keep it that way.\n- **Cost discipline:** generation costs real money per image. Batch\n thoughtfully (3–6 per slot, not 20), and reuse via Gemini's\n edit-an-image mode instead of regenerating from scratch.\n- **Licensing/provenance:** AI-generated imagery is fine for site\n decoration, but never generate fake \"photos\" of real people, products\n the customer doesn't sell, or anything presented as documentary fact.\n- **The style profile is the contract.** If the user rejects a batch on\n style grounds, fix `prompts/image-style.md` first, then regenerate —\n don't just tweak one prompt.\n",
|
|
16
16
|
"tr-images": "---\nname: tr-images\ndescription: Use when the user asks for an image, hero, illustration, or logo to be created and embedded in a Typeroll page. Covers the two-step signed-URL upload flow so the agent doesn't try to POST bytes through the CMS API (it can't).\n---\n\n# Add images to a Typeroll site\n\nThe Typeroll API does NOT accept image bytes directly. Uploads go\nthrough a signed PUT URL straight to Cloudflare R2, and the API only\nsees the metadata. Two-step flow:\n\nFor public source URLs, prefer `upload_media_from_url`; for 2–50 files use\n`upload_media_batch_from_urls` (max 25 MiB per file). Both download, upload,\nand finalize each asset automatically. Batch results preserve input order and\nreport failures per item, so retry only the failed entries. Use the manual\nsigned-URL flow below for local files or when you need direct control over the\nPUT.\n\n## Recipe\n\n### 1. Get an image\n\nOptions, in order of preference:\n\na. **Reuse an existing one.** `list_media` returns CDN URLs for every\n image already on this site. Search the list before generating\n anything — saves bandwidth and keeps the visual catalog tight.\n\nb. **Generate locally.** The user's Claude Code installation has\n access to whatever image-gen tools they've configured (DALL-E,\n Midjourney, Stable Diffusion, Replicate, etc.). Generate and save\n to a tempfile.\n\nc. **Source from the web** with appropriate licensing (the user is\n responsible for clearing rights). Save locally before upload.\n\n### 2. Mint a signed upload URL\n\n```\ncreate_upload_url filename=\"hero-services.png\"\n content_type=\"image/png\"\n size=<bytes>\n alt_text=\"Office worker reviewing documents at a desk\"\n```\n\nReturns:\n\n```json\n{\n \"upload_url\": \"https://...r2.cloudflarestorage.com/.../signed-...\",\n \"cdn_url\": \"https://cdn.example.com/orgs/.../images/...png\",\n \"key\": \"orgs/.../images/...png\",\n \"media_id\": \"abc123\",\n \"expires_in\": 300\n}\n```\n\nThe signed URL is valid for 5 minutes. The media doc is already\nregistered — even before the upload completes — so it'll show in\n`list_media` immediately.\n\n### 3. PUT the bytes\n\nOutside the MCP, hit the signed URL directly:\n\n```\nPUT <upload_url>\nContent-Type: <same content_type as in step 2>\nBody: <file bytes>\n```\n\nIn a shell:\n\n```bash\ncurl -sS -X PUT --data-binary @hero-services.png \"$UPLOAD_URL\"\n```\n\nOptionally with `-H \"Content-Type: image/png\"` if you need to override what R2 will infer. **Nothing else.**\n\n> The signed URL embeds checksum-related query parameters\n> (`x-amz-checksum-crc32`, `x-amz-sdk-checksum-algorithm`) for legacy\n> SDK compatibility, but `X-Amz-SignedHeaders=host` — only the host\n> header is part of the signature. Sending the `x-amz-*` values as\n> request headers yields `403 SignatureDoesNotMatch`. Don't. Just\n> `--data-binary` the file at the URL.\n\nOr from JS (Claude Code can run a one-line script):\n\n```js\nawait fetch(uploadUrl, {\n method: 'PUT',\n headers: { 'Content-Type': contentType },\n body: await fs.readFile(path),\n});\n```\n\nParallelise N uploads via shell `&` + `wait`:\n\n```bash\nwhile IFS=$'\\t' read -r filename signed_url; do\n curl -sS -X PUT --data-binary @\"$filename\" \"$signed_url\" &\ndone < manifest.tsv\nwait\n```\n\nA 200 OK from R2 means the image is now live at `cdn_url`.\n\n### 4. Patch metadata (alt text, etc.)\n\nYou set `alt_text` at create time, but if you generate the image first\nand only THEN realize what to caption it as, patch later:\n\n```\nupdate_media media_id=<id> alt_text=\"...\" filename=\"hero-services-v2.png\"\n```\n\n### 4b. Fill missing alt-text on existing media\n\nWhen a customer has uploaded a bunch of images without alt-text (very\ncommon after a WP migration), don't make it up — use vision:\n\n```\nlist_media → find items with empty alt_text\nsuggest_alt_text_context media_id=<id> → returns { image_url, suggested_prompt,\n language, used_on_pages, current_alt_text }\n# Pass image_url + the returned suggested_prompt to YOUR OWN vision\n# capability (you can fetch the URL and pass bytes to vision).\nupdate_media media_id=<id> alt_text=\"<what vision returned>\"\n```\n\nThe prompt is tuned for SEO-grade output: short (5-15 words), no \"image\nof / picture of\" filler, written in the site's content language,\ndecorative images return empty string. Run it sequentially on a\nlist_media batch and you can fix alt-text gaps across a whole site\nwithout burning your context on prompt design. The platform does NOT\nrun vision on your behalf — your model does, your usage.\n\n### 5. Embed in a page\n\n`read_page` the target, insert `<img>` in the right spot:\n\n```html\n<img src=\"<cdn_url>\"\n alt=\"<alt_text>\"\n style=\"width: 100%; height: auto; display: block; margin: 2rem 0;\" />\n```\n\nThen `update_page` with the new HTML. Or, if you're generating a hero\nfor a brand-new page, include the `<img>` directly in `create_page`'s\n`html_content`.\n\n## Pitfalls\n\n- **Always set `alt_text`.** Empty alt is bad for SEO + accessibility.\n Default to a one-sentence description of what's in the image.\n- **`<script>` etc. in SVGs.** The page sanitizer drops `<script>`\n inside SVG, so an icon set that includes script-based animations\n won't render correctly. Use static SVG or a JPG/PNG export.\n- **CSS background-image references aren't dedup'd.** If you set the\n same image as a CSS background on multiple pages, the alt-text +\n metadata are page-irrelevant. The sanitizer allows\n `background-image: url(...)` in inline styles, but think about\n whether an `<img>` is actually better.\n- **Source URL leakage.** If you generated the image from a prompt\n that contains internal info, don't bake that prompt into the\n filename. Use a descriptive but generic filename.\n\n## Format choice\n\n- **PNG** for logos, icons with hard edges, anything with text.\n- **JPG** for photos. Smaller file, better for big hero images.\n- **WebP** if the target audience runs modern browsers (95%+ in 2026).\n- **SVG** for icons + simple illustrations. Vector scales perfectly.\n- **PDF** is supported by `create_upload_url` for document downloads;\n link with `<a href>`, not `<img>`.\n\n## Check publication resolution (Core 0.2.37+)\n\nNative AVIF/WebP generation includes each smaller preset and the full original\nwidth, without upscaling or duplicate width descriptors. Check `currentSrc` and\nthe actual encoded dimensions on the published site; the CMS preview can use the\noriginal and hide an undersized derivative. Do not compensate with custom CSS\nor `sizes` alone. Existing static sites need republication for the versioned v2\nrecipe; unchanged subsequent builds reuse verified media. Explicit API variant\nworkflows must regenerate their variant metadata before rebuilding.\n",
|
|
17
17
|
"tr-import-url": "---\nname: tr-import-url\ndescription: Use when the user wants to import or migrate content from a non-WordPress website — a Squarespace site, a Wix site, a static HTML site, a Webflow export, or any URL the user points at. Also triggers on \"copy content from\", \"rebuild this site\", \"import from Squarespace/Wix/Webflow\", or \"make it look like this site\". For WordPress sources use tr-migrate-wp instead.\n---\n\n# Import content from a non-WordPress site\n\n**Who runs this:** this recipe is for the user's own AI agent (Claude Code, Codex or\nanother MCP client) working through MCP and the public API. The portal's built-in\nAI assistant has no migration tools: it does not import, convert or verify sites.\n\n**Before import or reconstruction:** read `tr-migration-evidence` with\n`read_skill`. Choose preserve or renew independently for text and design/images,\nrecord text_intent and design_intent in the brief and verify one prototype per active template before bulk writes.\nUse standard blocks first, reviewed custom types next, rare documented HTML last.\nAll four combinations must preserve or meaningfully redirect every old public URL. That\nshared contract defines acceptance; this recipe only adds source-specific steps.\n\n\n> **The buffer model (draft writes).** Every content write in this recipe\n> (pages, blocks and partials) lands in an unsaved per-doc\n> DRAFT — deploys and plain previews only see SAVED content. For recipe-style\n> build work, pass `save: true` on write calls (the work is pre-approved by\n> the task itself), or run `commit_working_copy` per doc before any\n> `trigger_deploy`. Preview your drafts with `include_working_copy: true`.\n\n\n## When to use this vs tr-migrate-wp\n\n| Source | Use |\n|---|---|\n| WordPress with `/wp-json` accessible | `tr-migrate-wp` |\n| WordPress with REST disabled | This skill (scrape HTML) |\n| Squarespace, Wix, Webflow, static HTML | This skill |\n| CSV / spreadsheet data | This skill (skip scraping, just parse) |\n| Any URL the user points at | This skill |\n\n## Preconditions\n\n- Target Site and Version are selected through OAuth or a scoped API key.\n- Run `get_import_readiness` before importing content or media; verify the\n Organization storage first. Build shared header/footer against the chosen brief.\n- Source URL(s) accessible (check with a quick `fetch`; if blocked, mention\n it and ask the user for an HTML export or screenshot).\n\n## Recipe\n\n### 1. Inventory the source site\n\nFetch the homepage and build a URL list:\n\n```\nfetch <source-url> # root HTML\nfetch <source-url>/sitemap.xml # XML sitemap if it exists\n```\n\nParse `<a href>` links to discover internal pages. Build a list:\n- Homepage\n- Top-level pages (About, Services, Contact, etc.)\n- Any sub-pages that look important\n\nExclude private/session/admin URLs deliberately. Inventory public pagination and\nquery routes and keep them working or map relevant redirects. Do not silently retire public routes.\n\n**Map source styling to named styles.** A class the source uses for a\nrecurring treatment (`.eyebrow`, `.lead`, `.btn-outline`, `.section-dark`)\nbecomes a site style with `create_style` and is applied with `style_id`. A\nsmall label directly above a heading is an eyebrow style, not a heading of its\nown: put it in the heading block's `eyebrow` field (and a line under the\nheading in `subtitle`). Never carry classed raw HTML into text blocks to keep a\nlook: the editor cannot edit it, and the next render version may not keep it.\nBuild such sections with heading, text, image and button blocks; Typeroll does\nnot convert HTML into blocks.\n\n### 2. Measure the source and protect existing target work\n\n```\nread_site_settings\nread_partial partial_id=\"header\"\nlist_pages limit=5\n```\n\nRead existing target data to avoid overwriting work. Compare prototypes to the\nsource when design_intent is preserve, or to the agreed new design when it is renew. Review text separately. Validate\nstandard/custom blocks using `tr-migration-evidence` before mass writes.\n\n### 3. Fetch and clean each source page\n\nFor each URL:\n\n**a. Fetch the HTML.**\n```\nfetch <page-url>\n```\n\nIf the site returns a bot-block (Cloudflare, 403, or clearly JS-only\nSPA output), note it. Tell the user: \"This page blocked direct fetching.\nCan you provide the page source or an HTML export?\"\n\n**b. Extract the main content.**\n\nInventory shared navigation/header/footer, consent and runtime features first;\nmap them to reusable partials or supported runtimes before excluding them from\nindividual Page bodies.\nKeep: `<main>`, `<article>`, the largest content region.\n\nClean the HTML:\n- Strip platform-specific classes: `sqsrte-*`, `wf-*`, `et_*`,\n `elementor-*`, `fl-*`, `divi-*`, `vc_*`\n- Remove empty `<div>`, `<span>`, `<section>` wrappers (no class, no content)\n- Unwrap redundant nesting: `<div><p>text</p></div>` → `<p>text</p>`\n- Keep: `<h1>`–`<h6>`, `<p>`, `<ul>`, `<ol>`, `<img>`, `<a>`, `<table>`,\n `<blockquote>`, `<figure>`, `<figcaption>`, `<strong>`, `<em>`\n- Fix headings: ensure exactly one `<h1>` per page (the page title)\n\n**c. Transfer images.** For each `<img src>`:\n```\nupload_media_from_url url=\"<source-img-url>\" alt=\"...\"\n```\nReplace the src with the returned CDN URL. Classify tracking pixels separately. Preserve meaningful decorative images and\nicons. A required file that fails transfer blocks acceptance; do not silently\nomit it. Inventory PDFs, download hrefs, srcsets and backgrounds as well.\n\n**d. Adapt to the target's design.**\nUse standard blocks first, then reusable custom types with editable fields.\nReview appearance and text independently according to design_intent and text_intent;\ncompare each preserved dimension to the source and each renewed dimension to its approved brief. Raw HTML is the documented last resort.\n\n### 4. Create pages as drafts\n\n```\ncreate_page title=\"Om oss\" slug=\"om-oss\"\n path=\"/om-oss/\" blocks=[<reviewed block tree>]\n content_mode=\"blocks\" status=\"draft\"\n seo_title=\"Om oss — Acme\"\n seo_description=\"...\"\n```\n\nAlways draft first. The user signs off before publishing.\n\n### 5. Handle redirects\n\nIf the source URLs differ from the target slugs, create redirects:\n\n```\ncreate_redirect from_path=\"/about\" to_path=\"/om-oss\"\ncreate_redirect from_path=\"/services.html\" to_path=\"/tjanster\"\n```\n\n### 6. Preview with the user\n\n```\nget_preview_link\n```\n\nWalk through every imported page with the user. Common issues:\n- Heading hierarchy wrong (two H1s, or H3 used where H2 belongs)\n- Images missing alt text\n- Squarespace column layouts that don't work without their grid system\n- Embedded forms or maps that need re-setup\n\n### 7. Publish + deploy\n\nAfter approval, save working copies and confirm publishing readiness:\n```\nbatch_update_pages updates=[\n {page_id: \"om-oss\", patch: {status: \"published\"}},\n {page_id: \"tjanster\", patch: {status: \"published\"}}\n]\ntrigger_deploy\nget_deploy_status job_id=<id>\nverify_migration_urls\nget_migration_launch_report\n```\n\nThis is candidate verification, not permission to change traffic DNS. Every old\npublic URL must serve its content or a relevant redirect before cutover.\n\n## Platform-specific notes\n\n### Squarespace\n- Main content is inside `.content-wrapper` or `[data-section-theme]` blocks\n- Portfolio images are usually high-resolution originals in `/universal/images/`\n- JSON-LD is Squarespace's own schema — strip it\n- Gallery blocks → standard image/gallery layouts, or a reusable custom type\n\n### Wix\n- Wix sites are React SPAs — `fetch` returns an empty shell\n- Ask the user for the Wix site's \"Export to HTML\" (available in some plans)\n or take screenshots for reference\n- Best path: get content from the user (text + image files), rebuild clean\n\n### Webflow\n- Usually fetchable; clean output\n- Classes like `w-container`, `w-row`, `w-col-*` can be stripped\n- Webflow CMS items are server-rendered — they appear in the HTML\n\n### Static HTML / old sites\n- Often the cleanest import. Fetch, strip nav/footer, keep body.\n- Rebuild table-based layouts with layout blocks; keep actual tabular data as tables\n\n## Pitfalls\n\n- **Don't import `<style>` blocks from the source site.** They reference\n external fonts, resets, and classes that don't exist in the target.\n Strip all `<style>` tags from source HTML and rewrite styles in the\n target's conventions.\n- **Don't break the single-H1 rule.** Many source sites have no H1 or\n several. Fix it.\n- **Squarespace/Wix forms.** They won't work after import — the backend\n is vendor-locked. Create a Typeroll form instead: `create_form`.\n- **Scripts and tracking.** Never copy the source's JavaScript. Carry\n analytics and advertising tags (GA4, GTM, Meta Pixel and others) over by\n ID in the Integrations app, with the cookie banner enabled so they wait for\n consent; never paste the vendor snippet into `scripts_head`. See\n tr-migration-evidence, \"Scripts, tracking and consent\".\n- **Videos.** YouTube/Vimeo embeds are fine (`<iframe>` is allowed).\n Hosted MP4s need re-uploading if the source URL won't persist.\n\n## Storage prerequisite\n\nBefore reading or importing source content, call `get_import_readiness` (Core\n0.1.95 / MCP 0.44.68 or later). If `ready` is false, stop the import and show the\nreturned message and Publishing settings link. The organization must connect\nand verify its own storage first. Do not fall back to draft storage, hotlink\nsource images, or use ordinary page-write tools to bypass the import gate.\nUse `upload_media_from_url` for referenced source images; the customer’s\nCloudflare transfer Worker copies and verifies them directly in R2, independently\nof the GitHub/Cloudflare build-provider choice.\n",
|
|
@@ -22,7 +22,7 @@ export const BUNDLED_SKILLS = {
|
|
|
22
22
|
"tr-new-site": "---\nname: tr-new-site\ndescription: Use when the user wants to create a new Typeroll site from scratch, set up the initial design, or bootstrap a blank site with working header/footer, brand colors, and a homepage. Also triggers on \"start a new site\", \"set up a site for\", or \"build a website for [company]\".\n---\n\n# Bootstrap a new Typeroll site\n\n## Site tokens before block overrides\n\nCore 0.2.106 / template capabilities 0.54.0 adds palette names in all colour\nfields and named typography/spacing steps. Check the connected capability\nversion; on older renderers use `var(--color-surface)` rather than a bare name.\n\nSet colours and font families in site settings, then use named styles and\npalette names in blocks. Do not write hex, rgb or font-family literals in block\ncontent. Prefer `primary`, `secondary`, `accent`, `background`, `surface`, `text`,\n`text_light`, and `on_primary`/`on_secondary`/`on_accent` for text on brand colours.\nUse `surface_hover` for a darker interactive surface (derived automatically\nunless explicitly configured). Existing semantic roles should be reused; a\nnew visual shade is not automatically a new semantic role. For a derived shade\nuse `color-mix(in srgb, var(--color-surface) 92%, var(--color-primary))`.\nFor an additional role not in the native palette, define a named site style\nor a site CSS variable derived from existing palette variables, and document it.\n\nChoose `heading`, `body` or `mono` for fonts. Font families belong only in the\nsite's `fonts` settings. Use type-size steps `xs`, `sm`, `md`, `lg`, `xl`, `2xl`,\n`3xl`; spacing `none`, `xs`, `sm`, `md`, `lg`, `xl`; line height `tight`, `snug`,\n`normal`, `relaxed`, `loose`. Presentation fields accept responsive maps of these\nnames, for example `font_size_px: {mobile: \"md\", desktop: \"lg\"}`. Read the field\nschema: counts, widths and breakpoints are not spacing steps. Pixel values are\nexplicit exceptions, not the default authoring method. Configure scales in\n`design_scales`, then reuse them in role styles and blocks.\n\nRecord deliberate exceptions in `decisions.md`, with source/block, reason and\nreview criteria (for example white text on a photograph). Run `lint_design_tokens`\nbefore launch and after major design changes. It warns, never blocks launch.\nUse the starter's `scripts/typeroll/color_tokens.py` for an exact-match dry run;\nreview the plan before applying it in a dedicated Version. Equal hex values can\nmean different roles: never automatically choose between background and text.\nVerify a primary/surface change in a test Version across header, footer, cards,\nmenus and buttons, then restore the test settings. Do not republish automatically.\n\n\n> **The buffer model (draft writes).** Every content write in this recipe\n> (pages, blocks and partials) lands in an unsaved per-doc\n> DRAFT — deploys and plain previews only see SAVED content. For recipe-style\n> build work, pass `save: true` on write calls (the work is pre-approved by\n> the task itself), or run `commit_working_copy` per doc before any\n> `trigger_deploy`. Preview your drafts with `include_working_copy: true`.\n\n\nStart here when the site already exists as a database record (created via\nthe portal UI or API) but has no design, no header/footer, and no pages.\nThe goal is to go from blank to a working 4-page site with correct brand\nidentity in a single session.\n\n**Pages are built in block mode** — the platform default. Blocks give\nstructured, per-field editing, native full-bleed sections, responsive\nbreakpoints, and templates. HTML mode is the secondary path for\nhand-crafted one-offs and migrated content (section at the end).\n\n## Workspace and context\n\nAn optional agent-neutral workspace stores the brief, decisions and QA. It does\nnot create a Site or replace CMS content. See the public Agent workspace guide.\nFor compact MCP: search_tools, describe_tool, then the appropriate read/write/admin\nwrapper. Load only the relevant guide section and recipe. Discover site apps with\nread_app_documentation when needed; do not bundle private app guides locally.\n\nNo Site yet? Use hosted MCP with an Organization key and create_site, or create it\nin the portal. To start from an AI site plan instead of a blank Site, use\ncreate_site_and_plan, poll get_workflow and approve its structure review only\nwith the user's consent. That AI plan needs the Organization's own portal AI\nkey for new Sites; without it the call answers `403 ai_not_enabled` and the\n`site_planning` workflow fails with `failure_code: \"ai_not_enabled\"`. Then plan\nit yourself: `create_site`, propose the site name, tagline and page list (title,\nslug, purpose, target keywords) from the business description, get the\nuser's approval, and build with this recipe. Tell the user you planned it\nyourself; don't retry the workflow. Read back its ID before editing. Use one explicit Site/Version for\nrelated operations. Larger changes to an existing site should use a branch.\n\nBefore importing content or a media library, require get_import_readiness to pass.\nPublishing accounts may remain unconfigured while authoring new drafts and previews.\n\n## Preconditions\n\n- `@typeroll/mcp-server` configured with a valid `TYPEROLL_API_KEY`.\n- The site exists (confirm with `get_site`).\n- You have the customer brief: company name, industry, 2–3 key brand colors,\n tone of voice, and a list of initial pages.\n\n## Recipe\n\n### 1. Audit current state\n\n```\nget_site\nget_site_capabilities # template_capabilities_version — what this deployment supports\nread_site_settings # see what (if anything) is already configured\nlist_pages # don't overwrite pages that already exist\nlist_partials # check if header/footer already have content\nlist_block_types # the per-site block palette — NEVER assume, always list\n```\n\n**Do not choose a content mode or file a platform gap before these calls\nreturn.** For every required visual/behavioral element, map it to an existing\nblock or composition first. If the summary suggests a match, use\n`read_block_type` to inspect the exact schema. Only call something “missing”\nafter checking capabilities, the complete palette, and custom block types.\n\nCommon requirements that are easy to misclassify:\n\n| Requirement | Existing Typeroll primitive |\n|---|---|\n| Full-bleed hero flush below the header | `core/section` + `core/hero`; block-mode sections already own the full width and have no page-shell padding |\n| Responsive icon/card grid | `core/grid` + `core/icon_box`, or `core/feature_grid`; set responsive fields with `set_block_responsive` |\n| Custom cards backed by Pages | `core/repeater` / `core/page_list` with a site-authored `item_compatible` block type as `item_block` |\n| Grouped Page listing | `core/repeater` with `group_by`; array-valued fields place an item in every matching group |\n| Breadcrumbs in a page template | `template/page_breadcrumbs`; page and item routes supply a server-rendered trail |\n| Shared article typography | Set `font_size`, `line_height` and `paragraph_spacing` on `template_content_slot`; blank values retain block defaults (Core 0.2.2+) |\n| Generated heading index | `core/table_of_contents`; choose heading levels; links always come from the current Page headings |\n| Previous/next Page links | `template/page_navigation`; defaults to content type sort order and can bind explicit neighbor fields |\n| Download CTA that disappears without a file | `template/show_if` around a context-bound `core/button`; a dedicated download block is only editor convenience |\n| Sticky/custom header and multi-column footer | Block-mode header/footer partials plus layout blocks, or one reusable custom block type |\n| Cookie notice | `settings.cookie_consent`, not a page block |\n| Consent copy owned by an installed Extension | `list_extension_installations` → `read_extension_installation` → `update_extension_installation_config`, using exact manifest schema keys; the update queues a production deploy by default |\n| One-off HTML + JavaScript embed | `core/embed`; reusable widgets use a custom block type with `script` |\n| CTA/button variants | `core/cta` and `core/button`, styled from site tokens or a narrowly scoped class |\n| Two-column image/text | `core/media_card`, `core/feature_row`, or `core/columns` |\n| Figures, captions and tables | `core/prose` / richtext; the sanitizer preserves these semantic tags |\n| Full-width block page without a content shell | Native block mode; top-level `core/section` is unconstrained |\n| Brand colors and typography | Site `colors`/`fonts` tokens consumed by core blocks |\n\nReal gaps should say what was checked and why the nearest primitive is not\nenough. “I did not see a dedicated block name” is not evidence by itself.\n\n### 2. Brand + settings\n\nOne `update_site_settings` call with every field you know:\n\n```json\n{\n \"site_name\": \"Acme Studio\",\n \"tagline\": \"Short, punchy tagline\",\n \"language\": \"sv\",\n \"trailing_slash\": \"always\",\n \"colors\": {\n \"primary\": \"#1a1a2e\",\n \"secondary\": \"#16213e\",\n \"accent\": \"#e94560\",\n \"background\": \"#f5f5f5\",\n \"surface\": \"#ffffff\",\n \"text\": \"#1a1a2e\",\n \"text_light\": \"#6b7280\"\n },\n \"fonts\": { \"heading\": \"Playfair Display\", \"body\": \"Inter\", \"size_base\": 16 },\n \"contact\": { \"email\": \"hej@acme.se\", \"phone\": \"+46 8 123 456\" },\n \"social\": { \"instagram\": \"https://instagram.com/acme\" }\n}\n```\n\nRead it back with `read_site_settings`. Google Fonts names are\ncase-sensitive display names (\"Plus Jakarta Sans\", not \"plus jakarta\"). Fonts\nare self-hosted on the site by default; a name Google does not serve falls\nback silently, so verify fonts on the published address (`tr-brand` Step 7).\n\n**Site icons are part of brand setup** — upload favicon (32–64px), a\n180×180 apple touch icon, and a 192×192 app icon; set `favicon`,\n`apple_touch_icon`, and `icon_192`. No icon assets? Derive a proposal (see\n`tr-brand`). Also set `settings.logo` to the uploaded brand mark — it feeds\nOG/schema even if the header uses a different lockup.\n\n#### Launch basics (ask early)\n\nAsk the user for these once, early in the project. Read `read_site_settings` first and ask only for\nwhat is missing. Each item is optional. Record a skipped item in\n`decisions.md`, so the launch check in the last step holds no surprises.\n\n- **Default share image.** Ask the user to choose or create the image that\n social networks and chat apps show when a page has no image of its own:\n upload one (`create_upload_url` or `upload_media_from_url`), pick an existing\n one (`list_media`), or create one with `tr-imagegen`. Use a 1200×630 px PNG or\n JPEG and set it as `default_og_image` with `update_site_settings`. Never\n generate a title card from the page title instead. The user may skip this.\n- **Organization.** The business name, logo and official profile URLs\n (LinkedIn, Instagram, GitHub and similar). Set `organization` with `same_as`;\n it becomes the home page's Organization schema.\n\nSet `iframe_allowed_hosts` when customer content embeds a provider outside\nthe built-in YouTube/Vimeo/Google Maps/Calendly set. Values are exact domain\nhostnames, never URLs or wildcards. Read the setting back before deciding that\nan iframe cannot be represented.\n\n### 2b. Styles before pages\n\nSet the site's look as named styles before building any page:\n\n1. `list_styles`. New sites already have the standard set (body, H1–H6, link,\n lead, eyebrow, small, quote, buttons, section). If roles are missing,\n `apply_standard_styles`.\n2. Adjust them to the brand with `update_style`: the font, the type scale and\n spacing for `base` (phones) and `at.tablet`/`at.desktop`, and colours from\n palette tokens. Keep text readable: contrast is checked with APCA and\n WCAG 2; text that fails both is refused, and text below its APCA level\n comes back as a warning.\n3. Add a named style for every look the design repeats: a card title, a price\n note, a highlighted section, a CTA button variant. Use the site's language\n for names.\n\nPages then choose styles with each block's `style_id` field. A per-block\nfont size, colour or class is only for something clearly unique.\n\nTo change a sub-element a block styles itself (the underline on formatted\nheading links, a hero's heading or buttons), use its parts:\n`read_block_type` lists them. A style with `parts: [\"core/rich_heading.link\"]`\napplies to that part on every block; `data.part_styles` on one block replaces\nit there.\n\n### 3. Header + footer partials\n\n**Start from the native preset — don't hand-roll navigation.** Read\n`tr-header-footer` and use its `template/site_logo` + `core/navigation_menu` with `core/navigation_links` slots\ncomposition. The server-rendered landmark, current-page state, no-JS links,\nmobile disclosure, focus treatment, and responsive behavior are Core\ncontracts rather than tenant CSS/JavaScript.\n\nStage the complete tree with `update_partial partial_id=\"header\"\npatch={blocks:[...]} save=true`, then call `set_partial_mode partial_id=\"header\"\nto=\"blocks\"`. Repeat for the footer and read both partials back. The inactive\nHTML representation is retained for rollback; changing `content_mode` through\nordinary PATCH/PUT is rejected intentionally.\n\nUse HTML mode only when preserving legacy authored markup that cannot yet be\nrepresented natively. Partials receive the same `{{site.*}}` render context as\npage blocks. Keep the first section and header backgrounds intentional; do not\nadd a decorative border merely to compensate for mismatched spacing.\n\n### 4. Homepage — block tree\n\n`create_page` with the whole tree in one call. Omit block `id`s — the\nplatform assigns them (`blk_…`); you read them back for later\n`update_block` calls.\n\n```\ncreate_page title=\"Start\" slug=\"\" status=\"draft\" content_mode=\"blocks\" blocks=[...]\n```\n\nA proven landing-page skeleton (every top-level block is a `core/section`\n— sections are **natively full-bleed**: the background runs edge-to-edge,\ncontent is constrained by the section's own inner container via the\n`width` field. NEVER use 100vw negative-margin hacks. Anchor ids and\ncustom classes via `style_overrides` are safe on sections from\ntemplate_capabilities_version ≥ 0.15.3; on 0.14.x–0.15.2 they wrap the\nsection in a div and silently break full-bleed — there, put the anchor\non a block *inside* the section instead):\n\n```json\n[\n { \"type\": \"core/section\", \"data\": { \"background\": \"surface\", \"padding_y\": \"lg\" }, \"children\": [\n { \"type\": \"core/media_card\", \"data\": {\n \"image\": \"MEDIA_URL\", \"image_alt\": \"…\", \"image_side\": \"right\",\n \"heading\": \"Huvudrubriken\", \"heading_level\": \"h2\",\n \"text\": \"<p>Ingress …</p>\",\n \"button_label\": \"Kontakta oss\", \"button_url\": \"/#kontakt\"\n } }\n ] },\n { \"type\": \"core/section\", \"data\": { \"padding_y\": \"lg\" }, \"children\": [\n { \"type\": \"core/heading\", \"data\": { \"text\": \"Så funkar det\", \"level\": \"h2\", \"align\": \"center\" } },\n { \"type\": \"core/grid\", \"data\": { \"cols\": { \"mobile\": 1, \"tablet\": 2, \"desktop\": 3 }, \"gap\": \"lg\" }, \"children\": [\n { \"type\": \"core/step_card\", \"data\": { \"number\": \"1\", \"title\": \"…\", \"text\": \"<p>…</p>\" } },\n { \"type\": \"core/step_card\", \"data\": { \"number\": \"2\", \"title\": \"…\", \"text\": \"<p>…</p>\" } },\n { \"type\": \"core/step_card\", \"data\": { \"number\": \"3\", \"title\": \"…\", \"text\": \"<p>…</p>\" } }\n ] }\n ] },\n { \"type\": \"core/cta\", \"data\": {\n \"heading\": \"Redo att börja?\",\n \"primary_label\": \"Kontakta oss\", \"primary_url\": \"/kontakt\"\n } }\n]\n```\n\nBlock-palette guidance (verify against `list_block_types` — the source of\ntruth):\n\n- **Hero:** `core/media_card` inside a white section (image beside copy),\n or `core/hero` (eyebrow/heading/subheading + `primary_*`/`secondary_*`\n buttons — rendered server-side; `layout: split-right` puts the image\n beside the text). For a plain text hero: section + heading + prose +\n button.\n- **Alternating image+text rows (\"zig-zag\" feature/step lists):**\n `core/feature_row` (template_capabilities_version ≥ 0.29.0) — balanced\n halves that hug the center gutter so wide screens never open dead air,\n natural-aspect image, `image_side: left|right` per row, mobile stack\n order as a field. Prefer it over hand-building `core/columns` with an\n unbalanced ratio + width-capped text (the classic dead-air trap). Use\n `core/media_card` instead when you want a *boxed card* with a\n filled/cropped image.\n- **`core/heading`** decouples `level` (h1–h6, semantics) from `size`\n (visual) — exactly one `level: h1` per page.\n- **Images:** `core/image` with an uploaded media URL. The build pipeline\n emits responsive output from prepared media. Upload through a signed direct\n grant, finalize it, and use returned IDs/URLs. Follow media preparation status;\n do not routinely regenerate unchanged images or use legacy maintenance tools. Use the\n `radius` field for rounded corners.\n- **Repeaters/listings:** `core/page_list`, `gallery`,\n `feature_grid` etc. — alias blocks over `core/repeater`. Use these for\n Pages of a content type instead of hand-writing listing markup. The base\n repeater also supports `group_by`, group ordering/headings, multi-valued\n membership, filters, custom `item_block`, and `item_overrides`.\n- **Long-form navigation:** `core/table_of_contents` builds an anchor list\n from page headings. Page templates use\n `template/page_navigation` for deterministic previous/next links.\n- **Forms:** `create_form`, then place `core/form` with its `form_id`.\n HTML-mode pages use `<x-form id=\"…\" />`; both paths render the same signed\n shell and runtime. See `tr-forms`.\n- **`core/html`** is the escape hatch for the genuinely unique thing —\n not a default. If you reach for it more than once or twice per page,\n note why (that's block-library feedback).\n\nSlot containers (`core/columns`, `core/tabs`): populate them either by\npassing the whole tree inline (`block={ type: 'core/columns', slots:\n[[…],[…]] }`) or incrementally with `add_block parent_id=<columns-id>\nslot_index=0|1`. Requires template_capabilities_version ≥ 0.15.2 — on\nolder sites use `core/grid` (children flow into columns) instead.\n\nPartial last rows: keep `last_row: 'start'` on `core/grid`. Incomplete rows\nstay left-aligned, preserving the reading edge. Center only after an explicit\nowner design request, never as an automatic fix. Do not widen one peer card\nor invent six-column CSS to fill a gap.\n\nIcons (template_capabilities_version ≥ 0.16.0): `type: 'icon'` fields on\n`core/icon`, `core/icon_box`, and `core/step_card` render inline SVG when\nthe value is a name from `get_site_capabilities → core_icon_names` (a\ncurated Lucide subset — `check`, `star`, `shield-check`, `mail`,\n`arrow-right`, `truck`, `chart-line`, …). Any other value (emoji, plain\ntext) renders as text, so emoji stand-ins keep working. Icons inherit\nsize from font-size and color from `currentColor`/the block's color\nfield. On older sites icons don't render — use emoji or CSS markers.\n\nEditor schemas (template_capabilities_version ≥ 0.39.0): labelled enum\noptions, newline-edited string lists, nested object/repeating-array fields,\nand internal-page pickers for URL fields are first-class. Do not flatten an\nExtension's `props_schema` merely to make it editable.\n\nFor an Extension placed in an HTML header or footer, require\n`supports_extension_html_partial_directive: true`. The broader\n`supports_extension_html_directive` flag covers HTML page bodies and is not\nproof that an older static build expands partial directives.\n\nFor a cross-page Extension flow, require both\n`supports_extension_site_navigation` and `supports_extension_storage`. Use\n`context.site.navigate(\"/path/\")` and installation-scoped\n`context.storage.session` rather than direct root-path navigation, Web Storage,\nor personal data in query parameters. This keeps navigation inside the current\npreview and preserves state without exposing it through URLs or referrers.\n\nTo prefill an Extension component from a native `core/navigation_form` banner\non the previous page, require `supports_extension_page_handoff`, check that the\ninstallation grants `page_handoff:read`, and set the component block's\n`page_handoff_key` to the banner's `handoff_key`; the banner's `destination`\nmust be the component's page. The component reads the values with\n`context.handoff.read()`. Do not add query parameters for this.\n\nKnown limitations (honest list — don't fight them):\n\n- **`core/tabs` label icons don't render** (the tab strip is built\n client-side without the icon pipeline). Text labels only.\n\nTheming: block primitives render neutral. Brand color/typography comes\nfrom settings (step 2) and named styles (step 2b). For page-specific polish\n(e.g. a colored card treatment) that no style covers, give the block a\n`custom_class` and write the rule in the page's `custom_css`. Never put a\n`<style>` in a `core/html` block or target `[data-bid]`/`[data-block]`.\n\n### 5. Inner pages\n\nSame pattern: `create_page` with `content_mode: \"blocks\"` and a section\ntree. Standard set: Om oss, Tjänster, Kontakt — or what the brief says.\nDefault new pages to `status: \"draft\"`; publish after review.\n\nSections that repeat across pages: a call to action or contact strip that\nmust stay identical becomes a global block (`make_block_global`, then\n`core/global_block` on the other pages). A section shape you will reuse with\ndifferent content (a pricing row, a feature trio) becomes a block template\n(`save_block_template`, then `insert_block_template`).\n\n### 5b. If the legacy site is still live, scrape canonical content\n\nFor pages with canonical text (privacy policy, terms, about), `WebFetch`\nthe live page and carry the text verbatim — don't rewrite legal copy\nfrom memory. Convert to prose blocks (or one `core/html` for complex\nlegacy markup).\n\n### 6. Preview + iterate\n\n```\nget_preview_link # signed URL for browser review\nget_page_preview page_id=\"home\" # rendered HTML for structural checks\n```\n\n**Self-review the visuals before you call it done — appearance AND\nreadability, not just structure.** Screenshot the deployed/preview site at\ndesktop (~1440px) and mobile (~390px) and look: logo FULLY VISIBLE (not clipped by\na header's overflow:hidden) + legible + brand-compliant against its actual\nbackground — screenshot the header IN CONTEXT, not the logo element in isolation\n(an element shot hides layout clipping); a light wordmark must not sit bare on a\nlight surface. Text contrast everywhere, no horizontal scroll or mid-word\nbreaks, every image rendered, mobile layout actually collapsed. \"No overflow +\ncopy present\" is not a design review — never report a build as done/perfect off\nstructural metrics alone.\n\nShare the preview link. Iterate on feedback with `update_block` /\n`add_block` / `move_block` — that's the point of block mode: surgical\nedits, not full-page rewrites.\n\n### 7. Launch (required)\n\nA site is not finished until this step has run. Call `read_site_readiness`\nbefore the production publication and again after it. For every item with the\nstatus `warning` or `suggestion`, either fix it with the tool its `fix` names,\nor record in `decisions.md` (or the starter's `qa/` folder) why it stays open.\nTell the user which items remain. A missing default share image is only a\nsuggestion; leave it if the user chose to skip it.\n\nThe report warns and never blocks a publication. A clean report is not approval\nto publish: publish only when the user has approved it.\n\nWhen the user approves, verify `read_publishing_readiness` and save the\nintended Version before deploying. A published CMS status is not a live\ndeployment.\n\n```\nread_site_readiness\ntrigger_deploy\nget_deploy_status job_id=<id>\nread_site_readiness\n```\n\nRun `read_site_readiness` again after the publication. It reads saved content\nand settings, so confirm the rest on the live site: the AI crawler zone check\nneeds the site's website host, and a Search Console property verified by DNS\nis not visible to Typeroll. The\n[launch checklist](https://typeroll.com/docs/guides/launch-checklist/)\nexplains every item.\n\n## Secondary path: HTML mode\n\nFor migrated legacy pages or a hand-crafted one-off, `content_mode:\n\"html\"` still works. Rules that apply there (and only there):\n\n- Wrap the body in a page-scope `<article class=\"my-page\">` and prefix\n selectors with it — the `.page-content` shell sets width/padding at\n normal specificity.\n- HTML-mode bodies are container-constrained; full-bleed requires the\n negative-margin escape (`margin-left: calc(50% - 50vw); width: 100vw`)\n — never combine with `overflow-x: clip` on a wrapper.\n- `set_page_mode` flips a page between modes and does not convert HTML.\n Typeroll has no HTML-to-blocks conversion: to move a page to blocks,\n build its content with blocks.\n\n## Pitfalls\n\n- **Don't create pages that already exist** — `list_pages` first; use\n `update_page` if the slug is taken.\n- **`update_page` takes a `patch` object**, not flat fields.\n- **Don't hardcode block field names from memory** — schemas are\n per-site (`list_block_types`/`read_block_type`).\n- **One `level: h1` per page** (core/heading) — SEO + screen readers.\n- **Don't simplify data during import** — preserve Swedish characters in\n labels (`affärsutveckling`, not the ASCII-folded slug), keep titles\n verbatim; slugs are derived for URLs only.\n- **Minimal JS.** Inline `<script>` in page content is stripped by the\n sanitizer. One-off interactivity uses `core/embed`; reusable interactivity\n uses a block-type `script`; site-wide code belongs in `scripts_body_end`.\n\n## Organization access\n\nWhen a Cloud write reports `organization_trial_ended` or `organization_access_ended`, use `read_organization_access` on the selected Site. Explain the deadline and recovery URL; keep reads and export available when permitted. Do not repeatedly retry or create another organization to bypass the lock. Self-hosted installations are unrestricted by default. A trial does not limit AI credits: the organization supplies its own provider key.\n\n## Fluid typography and component rhythm\n\nCore 0.2.114 / MCP 0.45.85 / capabilities 0.55.0 adds responsive scales.\nCheck the connected versions before using this contract. New sites use render\nversion 10. For an existing site, test a Version first; upgrading rendering\nand replacing existing roles are separate, deliberate choices.\n\nUse `fonts.size_base` (default 16), `typography.ratio_mobile` (1.2),\n`typography.ratio_desktop` (1.25), `typography.rhythm` (1), and\n`typography.line_length` (65 ch). Set the controls once, then use named type,\nspacing and line-height steps. `apply_standard_styles` uses those steps;\n`overwrite: true` resets existing roles, so review their current values first.\nExplicit sizes and breakpoint overrides still win and are not silently migrated.\n\nHeading role margins belong to editorial flow. Component titles use their own\npadding/gap, and opening headings in containers/sections add no top margin.\nUse role-linked `core/heading` size `auto` or `theme`; explicit hero sizes remain\ndeliberate exceptions. Do not compensate with per-card negative margins.\nCompare 375, 768 and 1280px in Site settings → Design and in a full site preview.\nAfter a rendering upgrade/rebuild, recheck cards, boxed headings, leading text\nand webfonts. Boxes should get their outer air from padding. Keep exceptions\nin decisions.md, and run lint_design_tokens before launch.\n",
|
|
23
23
|
"tr-page-template": "---\nname: tr-page-template\ndescription: Create or edit a reusable Page template for articles, checklists, products, directory entries or other Pages that share a layout.\n---\n\n# Share a layout across Pages\n\nConfirm the organization, site and version first. Personal OAuth calls require\nthe `site_context` returned by `select_site`. Read `read_site_instructions`\nand the selected site's enabled App guidance before changing content.\n\n\nRead capabilities, available block types, content types and Page templates first.\nUse a site version for substantial design changes. The Page owns its body blocks;\nthe template owns the shared layout around that body. The content type selects a\ndefault template, and an individual Page can override it.\n\n1. `create_page_template name=\"article-layout\" label=\"Article\" starter=\"article\" status=\"published\"`.\n Alternatively supply `blocks` instead of `starter`. Presets include article,\n blog, checklist, team, events, products, profile, landing and custom.\n2. `update_content_type name=\"articles\" patch={template:\"article-layout\"}`.\n For a single-page override, `update_page page_id=... patch={template:\"article-layout\"} save=true`.\n3. Read and edit the template through `read_page_template` or block tools with\n `target: { kind: \"template\", id: \"article-layout\" }`. Template edits save\n immediately; content-type and template changes are isolated by `version`.\n4. Keep a `template_content_slot` where the Page's body belongs. Its `max_width`\n can be full, narrow, normal or wide. Use native columns/containers for layout.\n5. Metadata blocks include `template/page_title`, `template/page_date`,\n `template/page_featured_image`, `template/page_breadcrumbs` and\n `template/page_navigation`. The outline block reads the rendered body headings.\n6. Use `{{page.title}}` or `{{page.custom_field}}` in template bindings.\n A repeater's children use `{{item.title}}` for the current listed Page.\n Custom fields can be typed objects, arrays or Page references; preserve those\n structures instead of storing hand-generated list HTML.\n7. Preview several Pages using the template: long title, short body, missing\n optional image, populated reference list. Inspect desktop and mobile output.\n Verify the authorized static build as well as the live database preview.\n\nPrefer native image, table, list, heading and content blocks. A rare specialized\nwidget can use a reviewed HTML/embed block. Reusable custom behavior belongs in\none custom block type rather than repeated HTML bodies. Existing HTML-mode pages\nmay share fragments through partials and `<x-include>`, but use block-based page templates\nfor new content families.\n\nHeader/footer partials are global and remain separate from content types. Keep\nsite-level design tokens in settings and shared layout changes in the template.\nPage body edits stay specific to that Page. Read the template before mutation;\nnever replace it with a starter merely because an import is being retried.\n\n\nContent types also own `sort_field`/`sort_dir` and optional `allowed_templates`.\nTypes define content; templates define presentation. Multiple compatible Page\ntemplates can be allowed, with `template` selecting the default. Null/absent\n`allowed_templates` is unrestricted, `[]` allows none, and a configured default\nmust be in an explicit allowed list. Page overrides must be allowed; null/empty\n`Page.template` restores inheritance. Page template changes use Save/Discard.\n\nListings inherit type sorting unless explicitly overridden. `Page.sort_order`\nis the manual numeric order; null clears it. Missing sort values come last and\nIDs break ties. Explicit ID lists keep their order. Typed `list_pages` queries\ninherit type sorting and accept `sort_by`/`sort_order`; unfiltered API lists\ndefault to stable IDs. See the public Content types guide for editor steps.\n\n## Structured fields without empty labels\n\nCheck `supports_page_field_list` before adding `core/field_list`. Configure\n`data.fields` as `{ field, label? }` rows, optional `title` and `layout`\n(`stack` or `two-column`). It omits empty rows and an entirely empty section,\nrespects `rendered: false`, uses select display labels and links Page references.\nZero is displayed. From Core 0.2.14, boolean rows hide false/unset by default; set `boolean_display: \"yes-no\"` to show both set values with `true_label` / `false_label` (defaults Yes/No). Stored false remains real data. Optional row `html` has only `{{label}}` and\n`{{value}}` slots; row `css` contains declarations, and `css_class` supports\nshared selectors. Values stay in Page.fields; never duplicate them into rich text.\nRead the current block schema and content type, and verify preview before publishing.\n\nFor checkbox lists, `item_html` decorates each selected option with the same\nlabel/value slots (for example a check icon). `item_links: [{ value, url }]`\nlinks only chosen stored options to listing URLs, retaining their schema labels.\nStyle list items via the row css_class and normal block Custom CSS.\n\n## Heading and field-list defaults (Core 0.2.13)\n\nA template H1 owns the Page title. Do not send body `core/heading` blocks with\n`level: \"h1\"` when the selected/default template supplies one. Use H2. Creation,\nPage writes and Save reject duplicates with a corrective error. Existing duplicate\nheadings render as H2 without mutating storage. A Page without a template H1 can\nkeep one body H1. The layout itself does not inject an additional title.\n`core/field_list` defaults to a 0.7rem row gap, weight-600 labels and values that\nwrap long URLs. Empty rows and entirely empty groups stay hidden.\n\n### Native precision (Core 0.2.25 candidate)\n\nRead current block schemas before relying on new controls. Heading/Page title\nsupport responsive `font_size_px`, unitless `line_height` and `color`. Sections\nseparate full-width background from `max_width_px`, `padding_x` and\n`content_gap_px`; Containers add `max_width_px`, `gap_px`, `padding_x_px` and\n`padding_y_px`. Two columns add `gap_px` and `right_width_px`. Use numeric values\nor `{mobile, tablet, laptop, desktop, wide}` overrides, not CSS strings.\nBreadcrumbs have `padding_before_px`, `padding_after_px`, `divider`; TOC has\n`padding_px`, `radius_px`, `border`, `shadow`, `link_color: neutral` and\n`list_style: chevron`. Keep TOC generated from headings. Icon box adds native\ncard dimensions and `whole_card_link`; map real Page URLs rather than freezing\ncustomer routes in HTML. Site logo accepts `height_px`. The `system` font family\nuses the device stack without a webfont request. Preserve complete images and\nvalidate actual mobile/desktop output, including scrolled outlines.\n\nColumns `stack_below` accepts the select strings `\"721\"` (default), `\"768\"`,\n`\"1024\"`, `\"1280\"`. A direct-slot TOC follows the selected stacked boundary: its\n`mobile_display: \"hidden\"` releases the otherwise empty slot; `\"visible\"` keeps\nit in flow. Use `\"1024\"` for full article width below laptop when justified by\nthe source comparison. Do not merely hide the TOC with `hidden_on` while leaving\na reserved two-column track, or duplicate body content for tablet/desktop.\n\nFor a sticky site header, use an outer semantic Container (`tag: \"header\"`,\n`sticky: true`, optional responsive `sticky_top_px`, 0–240) outside main. Keep\nits background opaque and its containing block tall enough; do not create a\nshort header wrapper or clip it with ancestor overflow. Sticky is opt-in. The\npage reserves the header's height as `scroll-padding-top`, so anchors,\n`scrollIntoView()` and focused fields land below it without site CSS.\nContainer `padding_top_px`/`padding_bottom_px` override `padding_y_px` per side\nand accept responsive maps. Grid/Repeater and repeater aliases expose responsive\n`gap_px` (0–240). Use these fields for exact source spacing rather than empty\nspacers or corrective CSS. TOC links use the same landing line; remove tenant\n`scroll-margin-top`/`scroll-padding-top` header offsets that duplicate it.\n\n\n## Qualified composition starters (Core 0.2.32 / MCP 0.45.26 and later)\n\nCheck `supports_composition_starters` and `supports_default_presentation_contract`.\nUse `get_composition_starter` to read one editable `header`, `footer`, `archive`\nor Page-template starter without changing CMS data. An archive also requires\n`content_type`; optional `title` names its heading. Replace sample navigation,\nmap the Profile's empty field_list to real public fields, and save through the\nnormal version-aware tools. `create_page_template` accepts the same Page starter\nnames as Templates → New template. Starters are copies, never live links that\nreplace an existing design after an upgrade.\n\nSections own background/gutters, Containers default to no padding, and article\nflow owns heading rhythm. Inspect Appearance before Advanced settings. Reset\nrestores block defaults; don't mirror styles into canonical fields. Formatted\nheadings have typed responsive typography. Container `overflow: \"clip\"` and\nmenu `panel_padding: \"none\"` are deliberate opt-ins. Default PDF text links are\nunderlined theme links. Missing template image/date/excerpt/author emits no node.\n\nFor an existing Container whose padding was omitted, review the new zero-padding\noutput; explicitly set the former `md` values if they should remain (horizontal\n2rem, vertical 4rem). Never bulk-replace existing templates. Read\nhttps://typeroll.com/docs/tools/blocks/ for the full default/migration contract.\n\n\n## Native surface controls (Core 0.2.33 / MCP 0.45.27)\n\nCheck `supports_surface_feedback_and_gradients`. Section/Container\n`background_gradient: { from: \"#f8fbff\", to: \"#e8f4fc\", angle: 135 }` adds\nan opt-in two-stop gradient; remove the object to return to the solid/image\nbackground. Use real colors or simple theme variables, never CSS declarations.\nPost Card `hover_background`, `hover_border_color`, `hover_color` and outlined\nPDF `download_hover_background`/`download_hover_color` control pointer, pressed\nand keyboard feedback. Repeater/page_list item_overrides use the same schema.\nKeep focus visible and check contrasting color pairs; don't move the card.\nNavigation Links `density: \"compact-desktop\"` gives footer links a 24px minimum\non desktop with fine pointers; mobile/touch retains 44px. For dense taxonomy\nfooters use `direction: \"column\"`, `font_size_px: 14.4`, `line_height: 1.1`,\n`gap_px: 4` and `padding_y_px: 0`. Primary menus keep comfortable defaults. Verify both narrow and\nwide views and publish the intended version after saving; no automatic migration.\n\n\n## Publication fidelity (Core 0.2.34)\n\nStatic Repeater rows and aliases export the selected item block’s declared public\nfields, explicit card mappings and grouping labels. Use `title`/`href` for static\nPost Cards and `item_overrides` for common appearance; never place private data\nin public fields. `rendered: false` still excludes the field. `breadcrumb_label`\nis preserved separately from the Page title. Older public artifacts need a new\ndeployment; do not rewrite the saved rows to compensate. Native Heading and Page\nTitle wrap long words without shrinking authored type. Firestore history adds no\nextra Page nesting; an actual storage depth limit returns structured HTTP 422\n`storage_document_too_deep` with the offending field path.\n",
|
|
24
24
|
"tr-redesign-branch": "---\nname: tr-redesign-branch\ndescription: Use when the user asks to redesign, modernize, or restructure a Typeroll site (or a section of it). Forces branch-isolated work so the live site stays untouched until the redesign is approved.\n---\n\n# Redesign a site without breaking the live one\n\n> **The buffer model (draft writes).** Every content write in this recipe\n> (pages, blocks and partials) lands in an unsaved per-doc\n> DRAFT — deploys and plain previews only see SAVED content. For recipe-style\n> build work, pass `save: true` on write calls (the work is pre-approved by\n> the task itself), or run `commit_working_copy` per doc before any\n> `trigger_deploy`. Preview your drafts with `include_working_copy: true`.\n\n\nSite-wide changes are exactly where copy-on-write branches earn their\nkeep. This skill enforces the discipline: every redesign happens on a\nbranch, preview-checked end-to-end, merged only after user sign-off.\n\n**Copy comes from the LIVE page, not a local draft.** A redesign changes\nthe design, not the words. Read the existing copy from the live page\n(`read_page`/`batch_read_pages`) and carry it over verbatim. Local\n`sources/*.md` files are drafts — use them only if the user explicitly\nsays \"apply the copy in `<file>`\". Don't invent new headlines, drop\nsections, or \"restore\" text from an old draft; a draft that had drifted\nfrom the live page once sent a whole redesign off the approved wording.\nWhen you must change a word, change it on the live page too and keep the\ndraft file in sync.\n\n**Restraint beats decoration.** Default to clean, purposeful design. Don't reach\nfor decorative motifs (suns, blobs, glows, mascots, confetti) to look \"graphic\" —\nunless a motif *means something for this brand/page*, it reads as random, and it's\nusually the exact thing that clips, seams, and crops. These fragile patterns broke\na real build — avoid them:\n- **A shape divider (wave/curve) between two sections** → don't hand-roll it in\n `core/html`; a separate stacked shape seams against the next section (a Chrome\n sub-pixel hairline). Use `core/section`'s **`divider_top` / `divider_bottom`**\n (`wave | curve | tilt`) — the platform paints it in the section's own colour and\n overlaps the neighbour by 1px, so it's seam-free by construction. Put the divider\n on the section whose colour should rise/dip into the neighbour.\n- **A glow/decoration inside an `overflow:hidden` box** → clipped to a hard edge.\n Put it in a non-clipped layer, or size it to fade out before the box edge.\n- **`object-fit:cover` on a portrait inside a circle/frame** → crops heads and\n faces. Use `contain`, reframe the source art, or size the frame to the art.\n- **A gradient \"fade\" at a section join** → reads as the design being cut off.\n Make transitions deliberate (a clean shape or a solid edge), never a fade.\nThese are invisible in a small full-page thumbnail and only show at real size in\nthe actual browser — see the review gate in step 6.\n\n## Recipe\n\n### 1. Discover (always)\n\n```\nget_site\nread_site_settings\nread_partial partial_id=\"header\"\nread_partial partial_id=\"footer\"\nlist_pages limit=20\nbatch_read_pages page_ids=[<top 3-5 pages>] # see actual conventions\nlist_partials # what free blocks exist\nlist_content_types # any data we need to consider\n```\n\nWrite the user a short read-back: *\"This is a 12-page agency site\nusing CSS variables, primary color #1e40af, Inter heading + Source\nSans body. Existing pages are content-dense, single-column. Main nav\nhas 5 items including a CTA. I'd suggest...\"*\n\nConfirm direction before touching anything.\n\n### 2. Create a branch\n\n```\ncreate_branch name=\"<descriptive name>\"\n```\n\nSave the response's `id` — pass it as `version=<id>` on every\nsubsequent call. Branches default `robots_blocked: true` so a\nhalf-finished redesign won't be indexed.\n\n### 2a. Launch basics (ask early)\n\nAsk the user for these once, early in the project. Read `read_site_settings` first and ask only for\nwhat is missing. Each item is optional. Record a skipped item in\n`decisions.md`, so the launch check in the last step holds no surprises.\n\n- **Default share image.** Ask the user to choose or create the image that\n social networks and chat apps show when a page has no image of its own:\n upload one (`create_upload_url` or `upload_media_from_url`), pick an existing\n one (`list_media`), or create one with `tr-imagegen`. Use a 1200×630 px PNG or\n JPEG and set it as `default_og_image` with `update_site_settings`. Never\n generate a title card from the page title instead. The user may skip this.\n- **Organization.** The business name, logo and official profile URLs\n (LinkedIn, Instagram, GitHub and similar). Set `organization` with `same_as`;\n it becomes the home page's Organization schema.\n\n### 2b. Write the design spec (REQUIRED — before you build)\n\nEvery redesign branch MUST carry a written design spec. Without it the\ndesign choices live only in the agent's head, so a later \"just tweak the\nillustration style / palette\" means re-deriving everything by hand (this\ngap cost a real project a full reverse-engineering pass). Write it BEFORE\nbuilding so it guides the work, and keep it in sync as the design evolves.\n\nSave it as a markdown doc with the project (e.g. `design-spec.md`, or\n`prompts/design-system.md`) — or, if there's no local working dir, as an\nunlisted page on the branch. It must capture:\n\n- **Palette** — every role + hex (background, surface, primary, accent,\n text, borders), and where the variant *diverges* from the brand and why.\n- **Typography** — fonts + weights/sizes per role.\n- **Illustration / imagery style** — the exact image-gen prompt prefix\n (tone, palette, formspråk, framing rules), so the imagery can be\n regenerated or restyled on its own without touching layout. State the\n business/concept constraints the imagery must respect (a wrong-concept\n image is worse than none).\n- **Section structure** — the page's sections in order + each one's\n treatment (band colour, layout).\n- **Rationale** — one line per major choice: *why* this direction.\n\nWhen the user later says \"adjust just the illustrations\" or \"change the\npalette\", you edit the spec first, then apply — the spec is the source of\ntruth for the design intent.\n\n### 3. Iterate on the branch\n\nFor each redesign step:\n\na. Make the change with `?version=<branch-id>`. Updates here don't\n touch main:\n\n ```\n update_partial partial_id=\"header\" patch={...} version=\"<branch>\"\n update_page page_id=home patch={...} version=\"<branch>\"\n ```\n\nb. Preview after every meaningful change:\n\n ```\n get_preview_link page_id=home version=\"<branch>\"\n ```\n\n Send the URL to the user. The preview navigates the whole branch\n from one mint.\n\nc. Iterate on feedback. Common rounds: headline tightening, color\n tweaks, swapping hero images.\n\n### 4. Site-wide changes through partials, not pages\n\nIf the redesign touches every page (e.g. new global header, new\nfooter, new CTA bar) — edit a partial, not 23 pages. Before editing a\nshared block, check the blast radius:\n\n```\nfind_pages_using_block partial_id=\"header\" version=\"<branch>\"\n```\n\nThis returns every page that would show the change. Communicate that\nto the user before the save.\n\n### 5. Bulk content cleanups via dry-run first\n\nIf the redesign requires content rewrites (e.g. \"remove every mention\nof the old company name\"), use the bulk tool with dry-run:\n\n```\nsearch_pages contains=\"OldCo\" version=\"<branch>\"\nbulk_replace_text pattern=\"OldCo\" replacement=\"NewCo\" dry_run=true version=\"<branch>\"\n# Show the user the sample_diffs\nbulk_replace_text pattern=\"OldCo\" replacement=\"NewCo\" dry_run=false version=\"<branch>\"\n```\n\n### 6. Approval round\n\n**Self-review is a multi-DIMENSION pass, not a glance — and most of it you\nMEASURE, not eyeball.** Structural checks (copy present, images return 200) are\nNOT a design review; never report \"approved\" off them. Don't just list the bugs\nyou happened to notice — walk every dimension below. Use the DB-live preview\n(a reused `get_preview_link`) for content and layout review: fix, reload,\nre-check, no re-deploy between fixes. It is a sandboxed, opaque-origin frame\nserved from the portal, so DOM/computed-style reads from your browser tool are\nlimited, third-party iframes usually stay empty, and consent, analytics,\ncookies and font loading cannot be verified there (`?consent=all|necessary`\nonly pre-answers the banner). Run the measured checks, embeds, consent,\nanalytics and fonts on the published temporary/test address of the branch\nafter an authorized publish.\n\n**Use `tr-design-review` (`read_skill tr-design-review`) for the HOW** — it has\nthe per-dimension measurement snippets (overflow ladder, computed contrast, touch\ntargets, the anti-lazy-load broken-image check) and the scorecard + verdict\nformat. The dimension summary below is the \"what\"; that skill is the runnable\nroutine. In particular: scroll-and-settle to trigger lazy images BEFORE any\nfull-page screenshot, or you'll report blank boxes that aren't real.\n\n1. **Responsive** — screenshot across a width ladder (mobile / tablet / laptop /\n desktop / wide ≈390 / 768 / 1024 / 1440 / 1920px) AND sweep the page's own\n `@media` breakpoints (read the page-scoped `<style>`; resize a few px below +\n above each). At EVERY width: `document.documentElement.scrollWidth <=\n clientWidth` (no horizontal scroll), grids flip cleanly, nothing squished /\n orphaned / overlapping, no mid-word breaks. Two sizes is not enough — bugs hide\n in between. Also check a short/landscape viewport and 200% browser zoom.\n2. **Visual & brand** — logo FULLY VISIBLE (not clipped by a header\n `overflow:hidden` + overlap margin) and brand-compliant; screenshot the header\n IN CONTEXT, never the logo element in isolation (that hides clipping).\n Decoration robust at real size in the real browser: no hairline seam at a\n divider (use `core/section` `divider_top`/`divider_bottom` — don't hand-roll a\n band), no glow clipped to a hard edge, no `object-fit:cover` cropping faces, no\n gradient fade-cutoff. Typography: body line-length ~45–75ch, consistent scale,\n no awkward widows on headings. Palette adherence (no off-brand colours);\n consistent spacing / alignment / radius / shadow.\n3. **Accessibility — MEASURE, don't eyeball** — compute actual contrast ratios\n (WCAG AA: body ≥4.5:1, large/UI ≥3:1) and fix failures by deepening the\n offending colour token; meaningful `alt` on every image; exactly one `<h1>` +\n no skipped heading levels; visible `:focus-visible` on every interactive\n element; every input has an associated `<label>`; touch targets ≥44px on\n mobile; semantic landmarks (header/nav/main/footer) + nav `aria-label`; honour\n `prefers-reduced-motion`.\n4. **Functional** — the form actually works (POST action correct, hidden token\n non-empty, honeypot present + hidden; a long name/email doesn't break layout);\n every link + in-page anchor resolves (each `#anchor` has a matching `id`; no\n `href=\"#\"`/`\"\"`); ZERO console errors/warnings; interactions (menu toggle,\n hover/focus/active) work.\n5. **Content** — no unrendered `{{…}}` tokens in the DOM; no placeholder/lorem;\n copy still matches the source of truth (the live page) verbatim.\n6. **Findable (SEO/meta)** — `<title>` + meta description present + sensible;\n `og:title`/`og:description`/`og:image`; canonical; favicon + apple-touch-icon;\n `<html lang>`; `noindex` correct (branches must be noindex).\n7. **Fast (performance)** — images at sane sizes (not a 2048px file shown at\n 380px without a responsive variant), modern format (avif/webp), `width`/`height`\n or aspect-ratio set (no layout shift), below-fold lazy / above-fold eager.\n8. **Cross-browser** — the same CSS renders differently per engine (the divider\n seam was Chrome-only; WebKit/Firefox have their own). Re-check in another engine\n if you can; if only Chromium is available, statically flag risky props\n (`backdrop-filter` without fallback, `-webkit-`-only masks, `100vh` on mobile →\n prefer `100svh`, `sticky` inside `overflow`).\n\nFix what you find and re-check before involving the user. \"Looks structurally\nfine\" ≠ \"looks good\", and \"looks good in Chrome at 1440\" ≠ \"works for everyone,\neverywhere\" — never report a design as approved/perfect off a glance or a partial\npass.\n\nThen give the user the **DB-live preview link** to review — and use the SAME\nlink for your own verification:\n\n- **While iterating (default):** a reused `get_preview_link` (mint once,\n reuse — defaults to a 24h TTL). It renders from the database with NO build, so every\n edit shows on reload, and one link navigates the whole branch (internal\n links keep the token). The token URL is stable across edits — re-mint only\n when the 24h lapses, never per edit. Do NOT deploy just to let the user (or\n yourself) see a change.\n- **When they want the COMPILED static site** (a permanent bookmark, a\n stakeholder link to the built output, or a final pre-merge check): deploy\n the branch once (`trigger_deploy version=\"<branch>\"`) and share its stable\n address: `deploy_url` from `list_versions` / `read_version` once the deploy\n has succeeded. The publishing setup decides the host: under a Hosting Group\n it is a `v-…` host under that group's site address base (which need not be\n the site's own domain); otherwise it is the address the host reported. Never\n construct it yourself, and do not guess a `<branch>.<project>.pages.dev`\n alias. Branch deploys are `robots_blocked`, so the\n address won't be indexed. A per-deploy `<hash>.pages.dev` URL in the job\n details is for your own one-off checks (a new hash each deploy).\n\nDefault: review/iterate on the reused DB-live preview; deploy only for the\ncompiled output, the published-address checks in step 6, or merge. Wait for an explicit \"looks good, ship it.\"\n\n### 7. Launch: merge and deploy (required)\n\nA site is not finished until this step has run. Call `read_site_readiness`\nbefore the production publication and again after it. For every item with the\nstatus `warning` or `suggestion`, either fix it with the tool its `fix` names,\nor record in `decisions.md` (or the starter's `qa/` folder) why it stays open.\nTell the user which items remain. A missing default share image is only a\nsuggestion; leave it if the user chose to skip it.\n\nThe report warns and never blocks a publication. A clean report is not approval\nto publish: publish only when the user has approved it.\n\nBefore asking for sign-off, summarise exactly what will land:\n\n```\ndiff_version version_id=\"<branch>\" # added / modified / deleted per collection\n```\n\nThen, once approved (creating and merging branches need site admin\npermission):\n\n```\nread_site_readiness # on the branch, before the merge\nmerge_branch version_id=\"<branch>\" # branch's diffs land on main\ntrigger_deploy\nget_deploy_status job_id=<id> # poll until succeeded\n```\n\nOptionally, after a successful deploy:\n\n```\ndelete_branch version_id=\"<branch>\" # tidy up\n```\n\n(You can also leave the branch around as a record of the redesign;\ndisk cost is tiny.)\n\nRun `read_site_readiness` again after the publication. It reads saved content\nand settings, so confirm the rest on the live site: the AI crawler zone check\nneeds the site's website host, and a Search Console property verified by DNS\nis not visible to Typeroll. The\n[launch checklist](https://typeroll.com/docs/guides/launch-checklist/)\nexplains every item.\n\n## Pitfalls\n\n- **Forgetting `version=` on writes.** Every call you make on the\n branch must include `version=<branch-id>`. A missing one writes\n straight to main — silent and bad.\n- **Skipping discovery.** \"Modernize\" without first reading the site\n produces a confidently-out-of-place result. Always sample existing\n pages.\n- **Deploying to preview.** Don't `trigger_deploy` after every edit just to\n see the change — that builds static pages and the URL is only as fresh as\n the last build. Iterate on a reused DB-live `get_preview_link` (renders from\n the DB, reflects edits on reload); deploy only for the compiled static\n output or merge (steps 6–7).\n- **Auto-merge.** Don't `merge_branch` without explicit user sign-off.\n Once merged, the only undo is another branch + reverse edits (or\n restoring individual saved states with `restore_page_revision` /\n `restore_partial_revision` on main).\n- **Starting over.** To abandon a direction but keep the branch and its\n address, `reset_version version_id=\"<branch>\"` discards every change on it\n (check `diff_version` and ask first); `delete_branch` removes it entirely.\n- **Header rewrites that drop the brand block.** Even when the\n redesign is dramatic, preserve the brand mark + the nav skeleton\n unless the user said to redo them.\n\n## When to NOT use a branch\n\nTiny edits — \"fix the typo on the About page\" — don't need a branch.\nThe in-portal chat handles those directly on main. This skill is for\nwork where:\n\n- The user might want to walk away mid-redesign and come back later\n- Multiple changes need to ship together\n- The site is high-traffic and \"broken for an hour\" is unacceptable\n- Stakeholder review across multiple pages is expected\n\nIf none of those apply, edit main directly and move on.\n",
|
|
25
|
-
"tr-responsive": "---\nname: tr-responsive\ndescription: Use when a layout must behave differently at different screen sizes — different grid columns per breakpoint, an icon-box that's icon-on-top on mobile but icon-left on tablet, hiding a block on small screens, fluid type. Triggers on \"responsive\", \"mobile/tablet/desktop layout\", \"stack on mobile\", \"X columns on desktop and Y on mobile\", \"olika på mobil/surfplatta\", \"responsivt\".\n---\n\n# Make a Typeroll block layout responsive\n\nTyperoll has a built-in five-breakpoint system. You almost never hand-write\nmedia queries — you set per-breakpoint values on responsive fields and the\nrenderer compiles the `@media` rules per block instance.\n\n## The five breakpoints (mobile-first)\n\n`mobile (<640) · tablet (≥640) · laptop (≥1024) · desktop (≥1280) · wide (≥1536)`\n\nA responsive field takes either a scalar (applies everywhere) or a sparse\nobject `{ mobile?, tablet?, laptop?, desktop?, wide? }`. Missing breakpoints\ninherit from the next smaller one. So you only set the breakpoints that change.\n\n## Setting per-breakpoint values\n\nUse `set_block_responsive` (or pass the object form directly in `add_block` /\n`update_block` data). `read_block_type <id>` tells you which fields are\n`responsive`.\n\nThe breakpoint object belongs on the responsive field inside `block.data`,\nfor example `data.cols`. Do not add a top-level `block.responsive` object: it\nis not a rendered field, and page, partial and Page-template writes\nreject it instead of silently storing an inert value.\n\n```\n# 4 columns on desktop, 2 on tablet, 1 on mobile:\nset_block_responsive target={kind:page,id:home} block_id=<grid-id>\n field=cols value={ mobile: 1, tablet: 2, desktop: 4 }\n\n# icon-box: icon on top on phones, beside the text on tablet+:\nset_block_responsive ... block_id=<iconbox-id>\n field=layout value={ mobile: \"icon-top\", tablet: \"icon-left\" }\n```\n\nPass a scalar to collapse a field back to one value everywhere, except the\nlisting defaults below.\n\n### Listing grids and content gutters (Core 0.2.13)\n\n`core/repeater` and aliases such as `core/page_list` use one grid column below\n768px even when scalar `cols` is larger. Set `mobile_cols: 2` only when multiple\nmobile columns are intentional. An explicit responsive `cols.mobile` is honored;\n`mobile_cols` takes precedence. Above this threshold `cols` works normally.\nMasonry is opt-in and also uses the explicit mobile count. Other blocks retain\nthe five breakpoints above.\n\nContent gutters default to 1.25rem below 768px and 1.75rem at/above 768px.\nFull-bleed section backgrounds keep their padded inner content. Customize\n`--content-gutter` in site CSS if needed. Rebuild published sites after upgrading\nCore to apply these shared preview/static defaults.\n\n### Worked example — the classic feature grid\n\n\"4 cards/row with icon-on-top on desktop, 2/row with icon-left on a landscape\niPad, 1/row icon-on-top on a phone\":\n\n1. `core/grid` containing `core/icon_box` cards (or a `core/repeater` with\n `item_block: core/icon_box` for a Page-driven list).\n2. On the grid: `cols = { mobile: 1, tablet: 2, desktop: 4 }`.\n3. On each icon_box (or the repeater's item defaults):\n `layout = { mobile: \"icon-top\", tablet: \"icon-left\", desktop: \"icon-top\" }`.\n\nNo media queries authored — the build emits per-instance `@media` blocks and\nthe editor preview honours them. Flip the device toggle in the editor header\n(Mobil / Mobil-liggande / iPad / iPad-liggande / Desktop) to author and verify\neach breakpoint.\n\n## Hiding a block at some sizes\n\n`Block.hidden_on: Breakpoint[]` is universal — no per-block opt-in. E.g.\n`hidden_on: [\"mobile\"]` drops the block below 640px. Use it instead of building\na \"mobile-only\" duplicate.\n\n## Authoring a CUSTOM block type that's responsive\n\nTwo halves, BOTH required (`create_block_type` / `update_block_type`):\n\n1. Mark the field `responsive: true`.\n2. Expose it on the **outermost** template element as a CSS variable:\n `style=\"--{field}:{{field}}\"`, then read `var(--{field})` in the block CSS.\n\nIf the field's value is directly usable CSS (e.g. `direction: row|column` →\n`flex-direction: var(--direction)`), you're done.\n\nIf it's a friendly **token** that maps to CSS (e.g. `layout: icon-left` →\n`flex-direction: row`), add a `responsive_css` map on the field — otherwise the\nper-breakpoint overrides silently do nothing (a `[style*=\"--field:token\"]`\nselector can't see a `@media` override):\n\n```\n{ name: \"layout\", type: \"select\", options: [\"icon-top\",\"icon-left\"],\n default: \"icon-top\", responsive: true,\n responsive_css: { \"icon-top\": \"--dir: column;\", \"icon-left\": \"--dir: row;\" } }\n```\n\nThen the block CSS reads `flex-direction: var(--dir, column)`.\n\n## Fluid type — usually automatic\n\n`core/heading` and prose already use `clamp()` to scale smoothly between mobile\nand desktop. `core/heading` separates semantic `level` (h1–h6, for SEO) from\nvisual `size` (sm–3xl/auto) — \"h1 but only as big as an h3\" is one field, no\nbreakpoints needed.\n\n## Gotchas\n\n- Setting a value only at `desktop` leaves smaller screens on the field\n *default*, not on your value — set `mobile` too if you want a non-default\n baseline (mobile-first).\n- The editor preview width is approximate on a narrow panel, but the breakpoint\n you're editing is exact. Trust the deployed site / a wider window for `wide`.\n- **Never paper over horizontal overflow with `html,body{overflow-x:hidden}`.**\n Setting `overflow-x:hidden` on `html` forces `overflow-y` to compute as `auto`\n (CSS spec), turning `<html>` into a fixed-height nested scroller — the page\n then won't scroll normally (`window.scrollY` sticks at 0) and renders blank\n below the fold. Instead, find the element that overflows (a fixed width, a\n `transform:rotate` card poking out, a decorative `::before`/`::after`, a grid\n that didn't collapse) and fix THAT element's width / clip it with\n `overflow:hidden` on its own section. Verify with\n `document.documentElement.scrollWidth === clientWidth` at 360–390px.\n- `core/grid` `stack_at` and responsive `data.cols` both compile overrides\n that beat the inline mobile baseline. Verify the computed column count at\n the actual breakpoint; no page CSS workaround should be necessary.\n- Background design reference: `docs/responsive-blocks.md` in the platform repo.\n\n\n## Core 0.2.14 native defaults\n\nUse section/hero as top-level background carriers; their inner content owns the\n20px phone / 28px tablet gutters. Do not add another gutter to main. Section\npadding auto is 48/64/80px at phone/768/1280; heading auto is H1 28/32/36 and\nH2 22/24/24 at a 16px root. Use shared --type-h1…h4, --section-padding,\n--block-gap, --card-padding and --grid-gap tokens for theme overrides.\nNavigation collapses through 1023px and becomes inline at 1024px.\nImages preserve the complete subject with intrinsic height and contain; request\ncover and a ratio explicitly only when cropping is intended. Breadcrumb spacing\nuses --breadcrumbs-before / --breadcrumbs-after. Custom field-list item_html\nreplaces native bullets; core/list marker none supports custom icons.\nPreview and static CSS order is defaults → blocks/instances → site → Page;\nspecificity, inline CSS and !important still apply. Compare without temporary\ncorrective CSS before removing any existing site rules.\n\n\n## Site-specific responsive widths (Core 0.2.28)\n\nThe five names stay the same, but their widths can be set in **Site settings →\nTypography → Responsive block widths**, or through `update_site_settings`:\n\n```json\n{\"responsive_breakpoints\":{\"tablet\":576,\"laptop\":769,\"desktop\":1024,\"wide\":1280}}\n```\n\nSend all four increasing integer widths (320–2560px); `null` restores\n640/1024/1280/1536px. Values are versioned with the site's settings. Responsive\nblock fields, repeater item overrides and `hidden_on` use these widths in the\neditor preview and static output. Reload an open editor after changing them.\nRepublish to update live pages; changing widths invalidates rendered-page caches.\n\nThis does not change explicit menu `collapse_below`, grid `stack_at`, listing\nmobile defaults or theme typography thresholds. Set the menu threshold separately\nwhen it must match, and use explicit responsive `cols` for exact listing columns.\nCheck one pixel below and at each threshold, including fractional widths for\nvisibility. Do not hide layout overflow to make a test pass.\n\n## Independent components (candidate capability 0.48.0)\n\nNot available in Core 0.2.31. Read capabilities and the live block registry first.\nEvery core block accepts `data.responsive_breakpoints` with all four increasing\nintegers (320–2560); null restores Site widths. It affects only that block's\nresponsive fields/visibility, not children. Listing grid and item cards are\nindependent: put card widths in `item_overrides.responsive_breakpoints`.\n\nA checklist card can use widths `{tablet:577,laptop:769,desktop:1041,wide:1440}`,\n`layout:{mobile:\"column\",desktop:\"row\"}`, `image_width_percent:40`,\n`image_sizing:{mobile:\"fixed\",desktop:\"stretch\"}`, `image_height_px:200`, and\nexplicit `image_fit:\"cover\"`. Intrinsic ignores pixel height; auto preserves it.\nUse stretch only for row layouts. Border, x/y panel padding, background and\n`title_font` are native fields. `action_label` no longer removes the title link;\n`title_link:false` opts out. Secondary actions prevent whole-card overlays.\n\nPDF styling is independent: `download_width:{mobile:\"full\",desktop:\"fill\"}`,\n`download_size_px:13`, `download_weight:\"700\"`, `download_color`,\n`download_border_width_px`, `download_radius_px`, `download_padding_x_px` and\n`download_padding_y_px`. `actions_align:\"center\"` centers the action row.\n`download_behavior:\"download\"` emits HTML download; cross-origin file hosts may\nstill cause browser navigation. Default is navigate, no proxy.\n\nColumns expose `left_width_px:150` and `stack_below_px:481` (stack through480).\nSet one fixed side; left wins if both set. Custom stacking overrides the preset.\nContainer `radius_px` and `shadow` (none/subtle/header) style a composed group.\nFrom render version 9 a shadowed container sits above the next block (z-index\n10, sticky keeps 100), so its shadow shows over a following hero; do not add\n`inline_style` z-index for that.\nMenus use matching SVG symbols; `close_size_px` overrides the close symbol size\nwithout shrinking the44px hit area. Compare exact threshold boundaries in real\npreview and static output before deleting corrective CSS.\n\n## Constrained containers in a column (render version 12)\n\nFrom render version 12 (Core 0.2.132), a `core/container` with width narrow,\nnormal or wide inside a container whose direction is column at every\nbreakpoint fills the column up to its maximum width, centered. Below 12, auto\nmargins shrink it to its content, so setting width `wide` does NOT make a card\nfill a column. Read `read_site_settings` → `render.version` before advising;\nwhen it is below 12, raise it with `tr-upgrade-rendering` instead of working\naround it in content. Rows, and containers that become rows at some\nbreakpoint, are unchanged.\n\n## Native image framing and body heading scales (Core 0.2.35+)\n\nUse `core/image.scale_percent` (100–200, responsive) for deliberate horizontal\nframing; 100 retains the uncropped original. `focal_x`/`focal_y` are percentage\npositions; Image `fit`/`aspect_ratio` and Post Card `image_fit`/`image_aspect` are\nresponsive. Keep intrinsic dimensions and original SVG/alt. A mobile 120% centered\nimage uses scale_percent={mobile:120,tablet:100}, focal_x=50 and local\nresponsive_breakpoints={tablet:577,laptop:769,desktop:1024,wide:1280}.\n\nSet `h1_size_px`…`h6_size_px` on `template_content_slot`, not every Page heading.\nThese responsive values inherit through nested containers; explicit font_size_px\non a heading wins. `heading_before_px`/`heading_after_px` control article-rhythm\nspacing. The slot's local breakpoint map controls its scale. Breadcrumbs flow as\ninline ordered-list items, including long current titles. Do not shorten customer\nheadings to compensate for a layout issue. Read the deployed block schema first.\n\n## Native text links (Core 0.2.36+)\n\nInline links in native rich-text surfaces (including lists, tables and image\ncaptions) are underlined and keyboard-focusable by default. Use native fields;\ndo not add customer CSS patches just to restore recognizable text links. Card\ntitles, image wrappers, buttons and navigation retain their own styles. Republish\nexisting sites to adopt the updated renderer.\n",
|
|
25
|
+
"tr-responsive": "---\nname: tr-responsive\ndescription: Use when a layout must behave differently at different screen sizes — different grid columns per breakpoint, an icon-box that's icon-on-top on mobile but icon-left on tablet, hiding a block on small screens, fluid type. Triggers on \"responsive\", \"mobile/tablet/desktop layout\", \"stack on mobile\", \"X columns on desktop and Y on mobile\", \"olika på mobil/surfplatta\", \"responsivt\".\n---\n\n# Make a Typeroll block layout responsive\n\nTyperoll has a built-in five-breakpoint system. You almost never hand-write\nmedia queries — you set per-breakpoint values on responsive fields and the\nrenderer compiles the `@media` rules per block instance.\n\n## The five breakpoints (mobile-first)\n\n`mobile (<640) · tablet (≥640) · laptop (≥1024) · desktop (≥1280) · wide (≥1536)`\n\nA responsive field takes either a scalar (applies everywhere) or a sparse\nobject `{ mobile?, tablet?, laptop?, desktop?, wide? }`. Missing breakpoints\ninherit from the next smaller one. So you only set the breakpoints that change.\n\n## Setting per-breakpoint values\n\nUse `set_block_responsive` (or pass the object form directly in `add_block` /\n`update_block` data). `read_block_type <id>` tells you which fields are\n`responsive`.\n\nThe breakpoint object belongs on the responsive field inside `block.data`,\nfor example `data.cols`. Do not add a top-level `block.responsive` object: it\nis not a rendered field, and page, partial and Page-template writes\nreject it instead of silently storing an inert value.\n\n```\n# 4 columns on desktop, 2 on tablet, 1 on mobile:\nset_block_responsive target={kind:page,id:home} block_id=<grid-id>\n field=cols value={ mobile: 1, tablet: 2, desktop: 4 }\n\n# icon-box: icon on top on phones, beside the text on tablet+:\nset_block_responsive ... block_id=<iconbox-id>\n field=layout value={ mobile: \"icon-top\", tablet: \"icon-left\" }\n```\n\nPass a scalar to collapse a field back to one value everywhere, except the\nlisting defaults below.\n\n### Listing grids and content gutters (Core 0.2.13)\n\n`core/repeater` and aliases such as `core/page_list` use one grid column below\n768px even when scalar `cols` is larger. Set `mobile_cols: 2` only when multiple\nmobile columns are intentional. An explicit responsive `cols.mobile` is honored;\n`mobile_cols` takes precedence. Above this threshold `cols` works normally.\nMasonry is opt-in and also uses the explicit mobile count. Other blocks retain\nthe five breakpoints above.\n\nContent gutters default to 1.25rem below 768px and 1.75rem at/above 768px.\nFull-bleed section backgrounds keep their padded inner content. Customize\n`--content-gutter` in site CSS if needed. Rebuild published sites after upgrading\nCore to apply these shared preview/static defaults.\n\n### Worked example — the classic feature grid\n\n\"4 cards/row with icon-on-top on desktop, 2/row with icon-left on a landscape\niPad, 1/row icon-on-top on a phone\":\n\n1. `core/grid` containing `core/icon_box` cards (or a `core/repeater` with\n `item_block: core/icon_box` for a Page-driven list).\n2. On the grid: `cols = { mobile: 1, tablet: 2, desktop: 4 }`.\n3. On each icon_box (or the repeater's item defaults):\n `layout = { mobile: \"icon-top\", tablet: \"icon-left\", desktop: \"icon-top\" }`.\n\nNo media queries authored — the build emits per-instance `@media` blocks and\nthe editor preview honours them. Flip the device toggle in the editor header\n(Mobil / Mobil-liggande / iPad / iPad-liggande / Desktop) to author and verify\neach breakpoint.\n\n## Hiding a block at some sizes\n\n`Block.hidden_on: Breakpoint[]` is universal — no per-block opt-in. E.g.\n`hidden_on: [\"mobile\"]` drops the block below 640px. Use it instead of building\na \"mobile-only\" duplicate.\n\n## Authoring a CUSTOM block type that's responsive\n\nTwo halves, BOTH required (`create_block_type` / `update_block_type`):\n\n1. Mark the field `responsive: true`.\n2. Expose it on the **outermost** template element as a CSS variable:\n `style=\"--{field}:{{field}}\"`, then read `var(--{field})` in the block CSS.\n\nIf the field's value is directly usable CSS (e.g. `direction: row|column` →\n`flex-direction: var(--direction)`), you're done.\n\nIf it's a friendly **token** that maps to CSS (e.g. `layout: icon-left` →\n`flex-direction: row`), add a `responsive_css` map on the field — otherwise the\nper-breakpoint overrides silently do nothing (a `[style*=\"--field:token\"]`\nselector can't see a `@media` override):\n\n```\n{ name: \"layout\", type: \"select\", options: [\"icon-top\",\"icon-left\"],\n default: \"icon-top\", responsive: true,\n responsive_css: { \"icon-top\": \"--dir: column;\", \"icon-left\": \"--dir: row;\" } }\n```\n\nThen the block CSS reads `flex-direction: var(--dir, column)`.\n\n## Fluid type — usually automatic\n\n`core/heading` and prose already use `clamp()` to scale smoothly between mobile\nand desktop. `core/heading` separates semantic `level` (h1–h6, for SEO) from\nvisual `size` (sm–3xl/auto) — \"h1 but only as big as an h3\" is one field, no\nbreakpoints needed.\n\n## Gotchas\n\n- Setting a value only at `desktop` leaves smaller screens on the field\n *default*, not on your value — set `mobile` too if you want a non-default\n baseline (mobile-first).\n- The editor preview width is approximate on a narrow panel, but the breakpoint\n you're editing is exact. Trust the deployed site / a wider window for `wide`.\n- **Never paper over horizontal overflow with `html,body{overflow-x:hidden}`.**\n Setting `overflow-x:hidden` on `html` forces `overflow-y` to compute as `auto`\n (CSS spec), turning `<html>` into a fixed-height nested scroller — the page\n then won't scroll normally (`window.scrollY` sticks at 0) and renders blank\n below the fold. Instead, find the element that overflows (a fixed width, a\n `transform:rotate` card poking out, a decorative `::before`/`::after`, a grid\n that didn't collapse) and fix THAT element's width / clip it with\n `overflow:hidden` on its own section. Verify with\n `document.documentElement.scrollWidth === clientWidth` at 360–390px.\n- `core/grid` `stack_at` and responsive `data.cols` both compile overrides\n that beat the inline mobile baseline. Verify the computed column count at\n the actual breakpoint; no page CSS workaround should be necessary.\n- Background design reference: `docs/responsive-blocks.md` in the platform repo.\n\n\n## Core 0.2.14 native defaults\n\nUse section/hero as top-level background carriers; their inner content owns the\n20px phone / 28px tablet gutters. Do not add another gutter to main. Section\npadding auto is 48/64/80px at phone/768/1280; heading auto is H1 28/32/36 and\nH2 22/24/24 at a 16px root. Use shared --type-h1…h4, --section-padding,\n--block-gap, --card-padding and --grid-gap tokens for theme overrides.\nNavigation collapses through 1023px and becomes inline at 1024px.\nImages preserve the complete subject with intrinsic height and contain; request\ncover and a ratio explicitly only when cropping is intended. Breadcrumb spacing\nuses --breadcrumbs-before / --breadcrumbs-after. Custom field-list item_html\nreplaces native bullets; core/list marker none supports custom icons.\nPreview and static CSS order is defaults → blocks/instances → site → Page;\nspecificity, inline CSS and !important still apply. Compare without temporary\ncorrective CSS before removing any existing site rules.\n\n\n## Site-specific responsive widths (Core 0.2.28)\n\nThe five names stay the same, but their widths can be set in **Site settings →\nTypography → Responsive block widths**, or through `update_site_settings`:\n\n```json\n{\"responsive_breakpoints\":{\"tablet\":576,\"laptop\":769,\"desktop\":1024,\"wide\":1280}}\n```\n\nSend all four increasing integer widths (320–2560px); `null` restores\n640/1024/1280/1536px. Values are versioned with the site's settings. Responsive\nblock fields, repeater item overrides and `hidden_on` use these widths in the\neditor preview and static output. Reload an open editor after changing them.\nRepublish to update live pages; changing widths invalidates rendered-page caches.\n\nThis does not change explicit menu `collapse_below`, grid `stack_at`, listing\nmobile defaults or theme typography thresholds. Set the menu threshold separately\nwhen it must match, and use explicit responsive `cols` for exact listing columns.\nCheck one pixel below and at each threshold, including fractional widths for\nvisibility. Do not hide layout overflow to make a test pass.\n\n## Independent components (candidate capability 0.48.0)\n\nNot available in Core 0.2.31. Read capabilities and the live block registry first.\nEvery core block accepts `data.responsive_breakpoints` with all four increasing\nintegers (320–2560); null restores Site widths. It affects only that block's\nresponsive fields/visibility, not children. Listing grid and item cards are\nindependent: put card widths in `item_overrides.responsive_breakpoints`.\n\nA checklist card can use widths `{tablet:577,laptop:769,desktop:1041,wide:1440}`,\n`layout:{mobile:\"column\",desktop:\"row\"}`, `image_width_percent:40`,\n`image_sizing:{mobile:\"fixed\",desktop:\"stretch\"}`, `image_height_px:200`, and\nexplicit `image_fit:\"cover\"`. Intrinsic ignores pixel height; auto preserves it.\nUse stretch only for row layouts. Border, x/y panel padding, background and\n`title_font` are native fields. `action_label` no longer removes the title link;\n`title_link:false` opts out. Secondary actions prevent whole-card overlays.\n\nPDF styling is independent: `download_width:{mobile:\"full\",desktop:\"fill\"}`,\n`download_size_px:13`, `download_weight:\"700\"`, `download_color`,\n`download_border_width_px`, `download_radius_px`, `download_padding_x_px` and\n`download_padding_y_px`. `actions_align:\"center\"` centers the action row.\n`download_behavior:\"download\"` emits HTML download; cross-origin file hosts may\nstill cause browser navigation. Default is navigate, no proxy.\n\nColumns expose `left_width_px:150` and `stack_below_px:481` (stack through480).\nSet one fixed side; left wins if both set. Custom stacking overrides the preset.\nContainer `radius_px` and `shadow` (none/subtle/header) style a composed group.\nFrom render version 9 a shadowed container sits above the next block (z-index\n10, sticky keeps 100), so its shadow shows over a following hero; do not add\n`inline_style` z-index for that.\nMenus use matching SVG symbols; `close_size_px` overrides the close symbol size\nwithout shrinking the44px hit area. Compare exact threshold boundaries in real\npreview and static output before deleting corrective CSS.\n\n## Constrained containers in a column (render version 12)\n\nFrom render version 12 (Core 0.2.132), a `core/container` with width narrow,\nnormal or wide inside a container whose direction is column at every\nbreakpoint fills the column up to its maximum width, centered. Below 12, auto\nmargins shrink it to its content, so setting width `wide` does NOT make a card\nfill a column. Read `read_site_settings` → `render.version` before advising;\nwhen it is below 12, raise it with `tr-upgrade-rendering` instead of working\naround it in content. Rows, and containers that become rows at some\nbreakpoint, are unchanged.\n\n## Long words on small screens (render version 13)\n\nFrom render version 13 (Core 0.2.140), a container that is a column at every\nbreakpoint keeps its children on one line, and Text and Heading blocks break\na word that does not fit. A long word, URL or inline code span then no longer\nmakes every child of the container, and the page, wider than a 375 px screen.\nBelow 13, raise the version with `tr-upgrade-rendering` instead of adding site\nCSS such as `.intro > * { min-width: 0; max-width: 100% }`. Rows and\nfixed-width children are unchanged.\n\n## Native image framing and body heading scales (Core 0.2.35+)\n\nUse `core/image.scale_percent` (100–200, responsive) for deliberate horizontal\nframing; 100 retains the uncropped original. `focal_x`/`focal_y` are percentage\npositions; Image `fit`/`aspect_ratio` and Post Card `image_fit`/`image_aspect` are\nresponsive. Keep intrinsic dimensions and original SVG/alt. A mobile 120% centered\nimage uses scale_percent={mobile:120,tablet:100}, focal_x=50 and local\nresponsive_breakpoints={tablet:577,laptop:769,desktop:1024,wide:1280}.\n\nSet `h1_size_px`…`h6_size_px` on `template_content_slot`, not every Page heading.\nThese responsive values inherit through nested containers; explicit font_size_px\non a heading wins. `heading_before_px`/`heading_after_px` control article-rhythm\nspacing. The slot's local breakpoint map controls its scale. Breadcrumbs flow as\ninline ordered-list items, including long current titles. Do not shorten customer\nheadings to compensate for a layout issue. Read the deployed block schema first.\n\n## Native text links (Core 0.2.36+)\n\nInline links in native rich-text surfaces (including lists, tables and image\ncaptions) are underlined and keyboard-focusable by default. Use native fields;\ndo not add customer CSS patches just to restore recognizable text links. Card\ntitles, image wrappers, buttons and navigation retain their own styles. Republish\nexisting sites to adopt the updated renderer.\n",
|
|
26
26
|
"tr-seo": "---\nname: tr-seo\ndescription: Use before a site's first launch and whenever the user asks to improve SEO, fix titles or meta descriptions, set a share image, add structured data, check the sitemap or robots.txt, control AI crawlers, or audit search visibility. Triggers on \"launch\", \"go live\", \"SEO\", \"meta descriptions\", \"share image\", \"Google ranking\", \"structured data\", \"JSON-LD\", \"sitemap\", \"llms.txt\", \"AI crawlers\", \"sökoptimering\", or \"hjälp mig synas på Google\".\n---\n\n# SEO and launch basics for a Typeroll site\n\n> **The buffer model (draft writes).** Every content write in this recipe\n> (pages, blocks and partials) lands in an unsaved per-doc\n> DRAFT — deploys and plain previews only see SAVED content. For recipe-style\n> build work, pass `save: true` on write calls (the work is pre-approved by\n> the task itself), or run `commit_working_copy` per doc before any\n> `trigger_deploy`. Preview your drafts with `include_working_copy: true`.\n\nRun this recipe at launch, from the last step of `tr-new-site`, the migration\nrecipes and `tr-redesign-branch`, and whenever the user asks about search\nvisibility. Start with `read_site_readiness`: it lists what is missing on the\nwhole site, with a fix and a guide link per item. Most of what search engines\nand AI agents need, the platform produces on its own; your work is the content\nand the few settings only the owner can decide.\n\n## What Typeroll handles automatically\n\nEvery publication produces these without any tool call. Do not recreate them\nby hand, and do not add a second copy through `json_ld`, `scripts_head` or a\npage.\n\n**Head of every page**\n\n- `<html lang>` from the Page `language`, otherwise the site `language`.\n- `<title>`: `seo_title`, otherwise the Page title, plus\n `default_seo_suffix` unless the Page sets `append_seo_suffix: false`.\n- `<meta name=\"description\">`: `seo_description`, then\n `default_meta_description`, then the tagline.\n- `<link rel=\"canonical\">`: the resolved route on the publication's origin, or\n an explicit `canonical_url`.\n- `<meta name=\"robots\">` from `noindex` and `nofollow`, and from the site-wide\n and version restrictions.\n- Open Graph and Twitter card tags (`og:title`, `og:description`, `og:image`,\n `twitter:card`, `twitter:site` from `twitter_handle`). Sharing text uses\n `og_title` and `og_description` when set.\n- Share image chain: `og_image`, then a public Content type field mapped to\n `image`, then `default_og_image`, then the logo. An SVG, AVIF or TIFF share\n image from the site's media library is published as a 1200×630 PNG copy,\n contained on the theme's background colour; the original stays the page\n image. `og:image:type`, `og:image:width` and `og:image:height` are emitted.\n- hreflang `<link rel=\"alternate\">` for Pages with `alternates`.\n- A Markdown rendition of each indexable Page at `<page path>index.md`,\n announced with `<link rel=\"alternate\" type=\"text/markdown\">`.\n- The first content images are never lazy and get `fetchpriority=\"high\"`, and\n a first section's background image is preloaded; later images are lazy.\n Logos and small icons do not count as the first images.\n\n**Structured data (JSON-LD)**\n\n- `Organization` and `WebSite` on the home page, from `site_name`, `tagline`,\n `organization`, `logo`, contact and social settings.\n- `Article` for Pages with `kind: \"article\"`, with dates and `author`.\n- `BreadcrumbList` on every page below the home page, from resolved routes.\n- Content type schema from `schema_type` and `schema_field_map`.\n- `FAQPage` from an Accordion block (`core/accordion`) when every item's title\n ends with a question mark (`?`, `?`, `؟`) and has an answer. Nothing is\n added when the Page's `json_ld` or its Content type schema already declares\n `FAQPage`, so it is never emitted twice.\n\n**Site files**\n\n- `robots.txt`: your `robots_txt` setting, with the sitemap lines added. The\n `ai_crawler_policy` setting adds the matching AI crawler groups.\n A duplicate load of a theme font in custom CSS or `scripts_head` is reported\n by the readiness item `duplicate_fonts`, not removed.\n- `sitemap.xml`: published, indexable Page routes. With more than 45,000\n URLs it becomes a sitemap index over numbered `sitemap-<n>.xml` files.\n Pages with `alternates` carry their hreflang entries.\n- `sitemap-images.xml`: the public images of each indexable Page.\n- `llms.txt`: a Markdown map of the site for AI agents, grouped by Content type\n and language section, with each Page's description and links to the\n sitemap and `llms-full.txt`.\n- `llms-full.txt`: the Markdown of the indexable Pages in one file, with a size\n cap.\n- The `Content-Signal` response header, when `ai_crawler_policy` is not\n `allow_all`.\n\nNoindex and unlisted Pages, drafts and blocked versions are left out of every\nsite file.\n\n## Recipe\n\n### 1. Read the site's state\n\n```\nread_site_readiness\nread_site_settings\nlist_pages status=\"all\"\n```\n\n`read_site_readiness` returns `items` with `id`, `status` (`ok`,\n`suggestion` or `warning`), `message`, `fix` and `guide`. Work through the\nwarnings first. It never blocks a publication, and a clean report is not\napproval to publish.\n\nFor each important Page, check:\n\n- Does the title identify the page, including any suffix?\n- Is the description specific and supported by the page's content? Search\n engines may rewrite or shorten snippets; never pad text to a length.\n- Does the page have exactly one H1?\n\n### 2. Launch basics only the owner can decide\n\nRead `read_site_settings` first and ask only for what is missing. Each item is\noptional; record a skipped item in `decisions.md`.\n\n- **Default share image.** Ask the user to choose or create the image shown\n when a page has none of its own: upload one (`create_upload_url` or\n `upload_media_from_url`), pick one with `list_media`, or create one with\n `tr-imagegen`. Use 1200×630 px PNG or JPEG. Never generate a title card\n from the page title instead.\n\n ```\n update_site_settings default_og_image=\"<cdn_url>\" twitter_handle=\"@acmestudio\"\n ```\n\n- **Organization and profiles.** The business name, logo and official profile\n URLs:\n\n ```\n update_site_settings organization={\"name\":\"Acme Studio\",\"logo\":\"<cdn_url>\",\"same_as\":[\"https://www.linkedin.com/company/acme\"]}\n ```\n\n- **Site description and icons.** `default_meta_description` for pages without\n their own description, and `favicon`, `apple_touch_icon` and `icon_192`\n (see `tr-brand`).\n- **AI crawlers.** `ai_crawler_policy` is `allow_all` (the default, which writes\n nothing), `no_training` or `block_all`:\n\n ```\n update_site_settings ai_crawler_policy=\"no_training\"\n ```\n\n `no_training` disallows training-only crawlers such as GPTBot, ClaudeBot,\n Google-Extended, Applebot-Extended and CCBot, and states\n `Content-Signal: search=yes, ai-input=yes, ai-train=no`. It never blocks\n Googlebot, Bingbot or Applebot. On Cloudflare, the zone's own AI bot policy\n is enforced at the edge and wins over `robots.txt`; blocking Training there\n can also block Googlebot, Bingbot and Applebot. The readiness item\n `ai_crawler_zone_conflict` reports a mismatch. Never change the zone's\n settings yourself: tell the owner what to change.\n- **Pages a business needs.** Privacy policy, contact and a 404 page. Set the\n privacy page as `cookie_consent.privacy_policy_url` when the cookie banner is\n on.\n\n### 3. Titles and descriptions\n\n```\nupdate_site_settings {\"default_seo_suffix\": \" — Acme Studio\", \"trailing_slash\": \"always\"}\n\nbatch_update_pages updates=[\n {page_id: \"home\", patch: {seo_title: \"Acme Studio — Interior design in Stockholm\", seo_description: \"Acme designs...\"}},\n {page_id: \"about\", patch: {seo_description: \"We are...\"}}\n]\n```\n\n- Give the home page an explicit `seo_title`, so the suffix does not repeat\n the site name.\n- A page that must keep its exact title can set `append_seo_suffix: false`.\n- The readiness report flags duplicate titles and descriptions, missing or\n repeated H1s and descriptions over 160 characters. Fix them in the content;\n they are warnings, not rules to pad or trim text mechanically.\n\n### 4. URLs and articles\n\n- Slugs are lowercase kebab-case: `our-services`, not `Our_Services`.\n- Reflect hierarchy in the address: a team member lives at `/team/jane-doe`\n (set `path`, or use a Content type whose route pattern adds the prefix).\n Breadcrumbs and their schema follow the resolved route.\n- Blog posts and news get `kind: \"article\"` and an `author`.\n- Give shared pages, especially articles, their own `og_image`.\n- Link every published page from somewhere. The readiness item `orphan_pages`\n lists pages that no internal link reaches; `check_internal_links` finds\n broken saved links.\n\n### 5. Structured data beyond the automatic output\n\nUse structured data only for information the page visibly shows.\n\n- **FAQ:** an Accordion block whose item titles are questions ending in a\n question mark, each with an answer, produces `FAQPage` automatically. For\n FAQ content in another block, use `add_faq_schema`.\n- **Recipes and events:** `add_recipe_schema` and `add_event_schema` validate\n the input, build the JSON-LD and merge it into the Page's `json_ld`:\n\n ```\n add_recipe_schema page_id=\"kanelbullar\" name=\"Kanelbullar\"\n ingredients=[\"500 g flour\", \"2 tsp cinnamon\"] instructions=[\"Mix the dough.\", \"Bake at 225 °C.\"]\n prep_time=\"PT1H30M\" cook_time=\"PT10M\" recipe_yield=\"24 buns\" image_url=\"<cdn_url>\"\n add_event_schema page_id=\"summer-concert\" name=\"Summer concert\"\n start_date=\"2026-06-12T19:00:00+02:00\" location_name=\"City Hall\"\n location_address=\"Main Street 1, Sundsvall\" offers_price=250 offers_currency=\"SEK\"\n ```\n\n Read the returned `warnings`: a recipe without an image or an event without\n a location is valid but not eligible for rich results.\n- **Products, software and directories:** set the Content type's `schema_type`\n (for example `Product` or `SoftwareApplication`) and map its fields with\n `schema_field_map` in `update_content_type`, rather than writing JSON-LD on\n each Page.\n- **Other types:** write `json_ld` yourself as a JSON string. Keep several\n nodes in one `{\"@context\":\"https://schema.org\",\"@graph\":[…]}`. An update\n replaces the whole value, so read the Page first.\n\n ```\n update_page page_id=\"home\" patch={\n \"json_ld\": \"{\\\"@context\\\":\\\"https://schema.org\\\",\\\"@type\\\":\\\"LocalBusiness\\\",\\\"name\\\":\\\"Acme Studio\\\",\\\"url\\\":\\\"https://acme.se\\\"}\"\n }\n ```\n\nThe readiness item `structured_data` reports JSON-LD that is missing required\nproperties or has invalid dates or URLs.\n\n**Instead of the `schema_markup` workflow.** That workflow uses the\nOrganization's own AI key; without it, it fails with\n`failure_code: \"ai_not_enabled\"`. Don't retry it. Read each published page,\nchoose the one type it represents (or none), apply the tools above, and tell\nthe user you did it yourself.\n\n### 6. robots.txt, indexing and canonicals\n\n- `robots_txt` holds custom crawl rules; the sitemap lines are added for you.\n Use `ai_crawler_policy` for AI crawlers instead of hand-written groups.\n- To keep a page out of search but crawlable, set `noindex: true`. Blocking a\n path in `robots.txt` stops crawlers from reading that directive.\n- Set `canonical_url` only when a page deliberately points elsewhere, for\n example a cross-domain duplicate. Don't combine it with `noindex`.\n- `check_site_indexing` reports the robots policy of the actual address,\n including response headers.\n\n### 7. Languages\n\n```\nupdate_site_settings {\"language\": \"sv\"}\nupdate_page page_id=\"about-en\" patch={\"language\": \"en\"}\n```\n\nFor several languages on one site, use `language_sections` and Page\n`alternates`; see the multilingual sites guide.\n\n### 8. Search Console\n\nAsk the owner to add the site in Google Search Console. A Domain property\nverified with a DNS TXT record covers every address. For a URL-prefix\nproperty, put Google's verification `<meta>` tag in `scripts_head` (admin\npermission). Then submit `https://<host>/sitemap.xml`. Record the property in\n`brief/seo.md`. After launch, `import_gsc_performance` reads page clicks and\nimpressions into the URL inventory.\n\n### 9. Publish and check\n\nWith the user's approval:\n\n```\nread_site_readiness\ntrigger_deploy\nget_deploy_status job_id=<id>\nread_site_readiness\n```\n\nRun the report again after the publication. It reads saved content and\nsettings: the AI crawler zone check needs the site's website host, and a Search\nConsole property verified by DNS is not visible to Typeroll, so confirm it in\nSearch Console. Fix or record every open item, and save the result under the\nstarter's `qa/` folder. In the portal, the site's overview page has the same\nreport under **Launch readiness → Check readiness**. It runs on request, not\nafter every publication.\n\n## Pitfalls\n\n- **Don't stuff keywords.** Write descriptions for people. Google ignores\n `<meta name=\"keywords\">`, and Typeroll has no such field.\n- **JSON-LD is a string.** Pass the whole schema as a JSON-encoded string in\n `json_ld`. The server escapes `</script` before output.\n- **Share images need absolute URLs.** Media library `cdn_url` values are\n absolute.\n- **FAQ markup must match visible content.** Search engines penalize FAQ\n markup for questions the page does not show.\n- **A site file you write by hand loses.** Don't create Pages named\n `sitemap.xml`, `robots.txt` or `llms.txt`; the publication writes them.\n",
|
|
27
27
|
"tr-starter-design": "---\nname: tr-starter-design\ndescription: Reference for the default design new Typeroll sites start with — CSS variables, spacing, radii, container width, header and footer skeletons. Read it when a site is blank or clearly follows these conventions and you need new markup or CSS to fit in; skip it for sites with their own design.\n---\n\n# Starter design system\n\n## Site tokens before block overrides\n\nCore 0.2.106 / template capabilities 0.54.0 adds palette names in all colour\nfields and named typography/spacing steps. Check the connected capability\nversion; on older renderers use `var(--color-surface)` rather than a bare name.\n\nSet colours and font families in site settings, then use named styles and\npalette names in blocks. Do not write hex, rgb or font-family literals in block\ncontent. Prefer `primary`, `secondary`, `accent`, `background`, `surface`, `text`,\n`text_light`, and `on_primary`/`on_secondary`/`on_accent` for text on brand colours.\nUse `surface_hover` for a darker interactive surface (derived automatically\nunless explicitly configured). Existing semantic roles should be reused; a\nnew visual shade is not automatically a new semantic role. For a derived shade\nuse `color-mix(in srgb, var(--color-surface) 92%, var(--color-primary))`.\nFor an additional role not in the native palette, define a named site style\nor a site CSS variable derived from existing palette variables, and document it.\n\nChoose `heading`, `body` or `mono` for fonts. Font families belong only in the\nsite's `fonts` settings. Use type-size steps `xs`, `sm`, `md`, `lg`, `xl`, `2xl`,\n`3xl`; spacing `none`, `xs`, `sm`, `md`, `lg`, `xl`; line height `tight`, `snug`,\n`normal`, `relaxed`, `loose`. Presentation fields accept responsive maps of these\nnames, for example `font_size_px: {mobile: \"md\", desktop: \"lg\"}`. Read the field\nschema: counts, widths and breakpoints are not spacing steps. Pixel values are\nexplicit exceptions, not the default authoring method. Configure scales in\n`design_scales`, then reuse them in role styles and blocks.\n\nRecord deliberate exceptions in `decisions.md`, with source/block, reason and\nreview criteria (for example white text on a photograph). Run `lint_design_tokens`\nbefore launch and after major design changes. It warns, never blocks launch.\nUse the starter's `scripts/typeroll/color_tokens.py` for an exact-match dry run;\nreview the plan before applying it in a dedicated Version. Equal hex values can\nmean different roles: never automatically choose between background and text.\nVerify a primary/surface change in a test Version across header, footer, cards,\nmenus and buttons, then restore the test settings. Do not republish automatically.\n\n\n## Express the brand through settings, not CSS\n\nBrand colours belong in `colors`, font families in `fonts`, sizes and spacing\nin the design scales, and recurring looks in named Styles. Editors can see and\nchange all of these, and every page follows them. Use `custom_css` only for an\nexception recorded in `decisions.md`, and never to load a font a second time,\nrestyle all headings or buttons, or set a hero background image. Run\n`lint_design_tokens` and check the readiness items `duplicate_fonts` and\n`custom_css_overrides` in `read_site_readiness` before launch.\n\nThe CSS variables below are for those recorded exceptions, HTML-mode pages and\ntemplate markup.\n\nA reference for the *default* design that ships with new Typeroll sites.\nThe customer may have replaced it — always inspect the actual header partial\n(`read_partial partial_id=\"header\"`) and an existing page before assuming any\nof this is in use. On a site with its own design, mirror what is there.\n\n## CSS variables\n\nColors (set in site settings `colors`; each key becomes `--color-<key>`,\n`text_light` becomes `--color-text-light`):\n --color-primary brand CTAs, links, accents\n --color-secondary supporting brand color (rarely used)\n --color-accent a third highlight (badges, callouts)\n --color-background page background (the body)\n --color-surface slightly raised areas (header, footer, cards)\n --color-text body copy + headings\n --color-text-light secondary text (captions, meta, footer fine-print)\n\nFor subtle borders/dividers that follow the text color without being heavy,\nuse `color-mix(in srgb, var(--color-text) 8%, transparent)` (6% for very soft).\n\nSpacing scale:\n --spacing-xs (0.25rem) --spacing-sm (0.5rem) --spacing-md (1rem)\n --spacing-lg (2rem) --spacing-xl (4rem)\n\nLayout:\n --container-medium (1080px) Center page content with\n `max-width: var(--container-medium); margin: 0 auto; padding: var(--spacing-md);`\n --container-narrow (720px) and --container-wide (1400px) exist for prose\n and wide layouts.\n\nRadii:\n --radius-sm (0.25rem) --radius-md (0.5rem) --radius-lg (1rem)\n\nTypography (set in site settings `fonts`):\n --font-heading Use for h1-h6 and brand wordmark\n --font-body Default for everything else\n --font-size-base (the rem baseline)\n\nBlock-mode pages get these through the blocks and named styles; reach for the\nvariables in custom CSS, HTML-mode pages and template markup.\n\n## Default header skeleton\n\nSurface-tinted bar with a hairline bottom border. Brand text on the left\nin the heading font, bold, slight negative letter-spacing. Nav on the\nright. Last nav item is typically a primary CTA: `background:\nvar(--color-primary); color: var(--color-background); border-radius:\nvar(--radius-md)`.\n\n## Default footer skeleton\n\nThree-column responsive grid (brand+tagline / Site links / Get in touch),\nthen a fine-print row with copyright + Typeroll attribution. Top border\nuses the same color-mix() recipe as the header.\n\n## When to use this\n\n- A brand new site, or one where the header partial still matches this\n layout.\n- A page or block where the customer wants something that \"fits the rest\n of the site\" and the rest follows these conventions.\n\n## When NOT to use this\n\n- The customer has a totally different design (e.g. dark mode, custom\n utility classes, no CSS variables, migrated theme).\n- The customer says \"use this brand color\" — honor their literal request\n instead of substituting a CSS variable.\n- Mid-edit on an existing page that doesn't follow these conventions —\n match what's already there.\n- Choosing a new palette or fonts is brand work (the MCP recipe `tr-brand`),\n not this reference.\n\n## Fluid typography and component rhythm\n\nCore 0.2.114 / MCP 0.45.85 / capabilities 0.55.0 adds responsive scales.\nCheck the connected versions before using this contract. New sites use render\nversion 10. For an existing site, test a Version first; upgrading rendering\nand replacing existing roles are separate, deliberate choices.\n\nUse `fonts.size_base` (default 16), `typography.ratio_mobile` (1.2),\n`typography.ratio_desktop` (1.25), `typography.rhythm` (1), and\n`typography.line_length` (65 ch). Set the controls once, then use named type,\nspacing and line-height steps. `apply_standard_styles` uses those steps;\n`overwrite: true` resets existing roles, so review their current values first.\nExplicit sizes and breakpoint overrides still win and are not silently migrated.\n\nHeading role margins belong to editorial flow. Component titles use their own\npadding/gap, and opening headings in containers/sections add no top margin.\nUse role-linked `core/heading` size `auto` or `theme`; explicit hero sizes remain\ndeliberate exceptions. Do not compensate with per-card negative margins.\nCompare 375, 768 and 1280px in Site settings → Design and in a full site preview.\nAfter a rendering upgrade/rebuild, recheck cards, boxed headings, leading text\nand webfonts. Boxes should get their outer air from padding. Keep exceptions\nin decisions.md, and run lint_design_tokens before launch.\n",
|
|
28
28
|
"tr-upgrade-rendering": "---\nname: tr-upgrade-rendering\ndescription: Use when a site's platform render version is behind the latest (read_site_settings → render.version < render.latest), or the user asks to \"upgrade rendering\", \"get the new block styles\" or why a platform improvement doesn't show. Compares every page at the current and target version with screenshots, reports the differences and upgrades only with approval.\n---\n\n# Upgrade a site's render version\n\nPlatform changes to block markup and shared CSS ship as numbered render\nversions. A site keeps its version until someone upgrades it, so its look\nnever changes on its own. Upgrading is a single settings write, reversible by\nwriting the previous number. The comparison happens here, in the agent; the\nserver only renders previews.\n\nThe user can also do this in the portal: Settings → Rendering → \"Preview with\nversion N\", then \"Upgrade\". Offer this skill when they want a full-site visual\ncomparison first.\n\n## Recipe\n\n1. **Read the gap.** `read_site_settings` returns `render`:\n `{ version, latest, upgrades: [{ version, title, changes[] }] }`. If\n `version === latest`, stop. Tell the user what each pending version\n changes, in their words, before measuring anything.\n\n2. **List the pages to compare.** `list_pages` (published and unlisted), plus\n the header/footer, which appear on every page. For a large site, compare\n the home page, one page per content type and template, and every page\n whose blocks use a component named in `upgrades[].changes`.\n\n3. **Mint two links per page set.** One `get_preview_link` without\n `render_version` (current) and one with `render_version: latest` (target).\n Both show saved content. Mint each once and reuse it; internal links keep\n the token.\n\n4. **Screenshot both** at 390, 768 and 1440 px wide, full page, after fonts\n and images have loaded (`document.fonts.ready`, scroll to the end to\n trigger lazy images, then back to the top). Use a headless browser with a\n fixed viewport and `reducedMotion: 'reduce'`.\n\n5. **Compare.** Pixel-diff each pair (same size, small per-pixel tolerance)\n and record the changed area and the bounding boxes of changed regions.\n Crop each region from both versions. Ignore differences confined to\n embedded third-party frames (maps, booking widgets, video).\n\n6. **Report** per page and width: unchanged, or the cropped before/after\n regions with a one-line description (\"links in body text are underlined\",\n \"section gap 8px larger\"). Link each change to the version note that\n explains it. Say plainly when a change looks like a regression.\n\n Before screenshots, read the site and page custom CSS for selectors a\n version note names. Version 2 moves a heading's or button's custom class\n onto the `<h2>` or link, so `.my-class .block-heading-text` stops\n matching; propose the rewrite (`.my-class`) or a named style.\n\n7. **Ask before upgrading.** Never upgrade on your own. If the user wants to\n keep a specific old look, solve it with a site style or page CSS first and\n compare again.\n\n8. **Upgrade** with `update_site_settings render_version: <latest>` (pass\n `version` to do it on a branch first). The preview changes at once; the\n live site changes at the next deploy. To undo, write the previous number.\n\n## Platform fixes behind a newer version\n\nWhen a layout defect is fixed in a newer render version (for example version\n12: constrained containers fill column layouts), the fix is to raise\n`render_version` through steps 3–8 — compare, report, upgrade with approval,\nthen publish. Do not work around it in content (extra wrappers, widths,\npaddings or custom CSS); those workarounds outlive the fix and fight it.\n\n## Pitfalls\n\n- Comparing a draft against saved content shows content edits, not\n rendering changes. Use the same content state for both links.\n- Lazy images and web fonts cause false differences. Wait for them in both\n screenshots.\n- Comparing only the home page misses changes in components it doesn't use.\n- Don't encode a workaround in custom CSS to resist an upgrade without\n telling the user; it becomes a hidden fork of the platform styles.\n"
|
package/dist/tools/domain.js
CHANGED
|
@@ -24,7 +24,7 @@ export const domainTools = [
|
|
|
24
24
|
},
|
|
25
25
|
{
|
|
26
26
|
name: 'guide_publishing_domain_change',
|
|
27
|
-
description: 'Guide a site domain move. Start with check: returns hosting, zone, nameserver, certificate, permission, publishing, redirect capacity, candidate_noindex and candidate checks, current phase, next action and an explicit switch summary. candidate_noindex blocks prepare and approve while site settings, the last publication or the candidate tell search engines not to index or follow the site (next_action resolve_noindex): the temporary address is already blocked from indexing by Typeroll, so prepare with confirm_disable_noindex: true to turn it off and build an indexable candidate in one step, or pass allow_noindex_launch: true to launch hidden on purpose. Saving future hosts is inert. Prepare republishes the last public content on main with the new origins, replacing the current site before DNS changes; newer unpublished CMS edits are excluded. Later normal publications retain the new origins. Approve changes DNS for the same main artifact without another build. Adding redirect hosts preserves the main artifact. Approve requires explicit user approval of the switch summary and any validation interruption. Automatic DNS is journaled and rolled back after a failed check or the agreed deadline; later external edits require manual attention. Rollback requests restoration through the serialized publication chain. External DNS remains the caller’s responsibility.',
|
|
27
|
+
description: 'Guide a site domain move. Start with check: returns hosting, zone, nameserver, certificate, permission, publishing, redirect capacity, candidate_noindex and candidate checks, current phase, next action and an explicit switch summary. candidate_noindex blocks prepare and approve while site settings, the last publication or the candidate tell search engines not to index or follow the site (next_action resolve_noindex): the temporary address is already blocked from indexing by Typeroll, so prepare with confirm_disable_noindex: true to turn it off and build an indexable candidate in one step, or pass allow_noindex_launch: true to launch hidden on purpose. Saving future hosts is inert. Prepare republishes the last public content on main with the new origins, replacing the current site before DNS changes; newer unpublished CMS edits are excluded. Later normal publications retain the new origins. Approve changes DNS for the same main artifact without another build. Adding redirect hosts preserves the main artifact. Approve requires explicit user approval of the switch summary and any validation interruption. Automatic DNS is journaled and rolled back after a failed check or the agreed deadline; later external edits require manual attention. Rollback requests restoration through the serialized publication chain. After go-live, Typeroll re-checks who serves the website and redirect hosts at about 2, 10 and 30 minutes; next_action remove_other_host (with traffic_live false and a serving check) means another provider answers the host, typically a previous host\'s Cloudflare for SaaS hostname that outranks the customer\'s DNS: remove the domain from that host\'s dashboard, then check again. External DNS remains the caller’s responsibility.',
|
|
28
28
|
inputSchema: {
|
|
29
29
|
action: z.enum(['check', 'preview', 'save', 'prepare', 'approve', 'rollback']),
|
|
30
30
|
revision: z.string().optional(), candidate_id: z.string().optional(),
|
|
@@ -226,7 +226,7 @@ export const domainTools = [
|
|
|
226
226
|
},
|
|
227
227
|
{
|
|
228
228
|
name: 'read_publishing_domains',
|
|
229
|
-
description: 'Read the desired and active website, redirect and media hosts, retained media aliases, DNS management mode and domain configuration revision. host_checks lists each future host with its Cloudflare zone and structured blockers and warnings (code, host, message), for example apex_zone_required (an apex such as example.com needs an active Cloudflare zone in the hosting account), zone_not_active (the zone is pending until its nameservers change) or redirect_zone_required. Resolve blockers before preparing. preparation and redirect_preparations hold DNS records, the zone redirect rule and certificate state after preparation. Saving intent does not switch traffic. published identifies the addresses embedded in main, separately from active (verified traffic). Saving desired hosts is inert. prepare_publishing_domain_change explicitly replaces main with the new origins; later normal publication retains them and refreshes the DNS approval receipt. candidate.sitewide_noindex describes the frozen candidate, not current settings. Prepare and approve reject an authored noindex launch with candidate_noindex unless allow_noindex_launch is true.',
|
|
229
|
+
description: 'Read the desired and active website, redirect and media hosts, retained media aliases, DNS management mode and domain configuration revision. host_checks lists each future host with its Cloudflare zone and structured blockers and warnings (code, host, message), for example apex_zone_required (an apex such as example.com needs an active Cloudflare zone in the hosting account), zone_not_active (the zone is pending until its nameservers change) or redirect_zone_required. Resolve blockers before preparing. preparation and redirect_preparations hold DNS records, the zone redirect rule and certificate state after preparation. Saving intent does not switch traffic. published identifies the addresses embedded in main, separately from active (verified traffic). Saving desired hosts is inert. prepare_publishing_domain_change explicitly replaces main with the new origins; later normal publication retains them and refreshes the DNS approval receipt. candidate.sitewide_noindex describes the frozen candidate, not current settings. Prepare and approve reject an authored noindex launch with candidate_noindex unless allow_noindex_launch is true. serving_check reports read-only follow-up checks about 2, 10 and 30 minutes after the website host went live: status pending, serving, inconclusive (a transport or edge error, never an alarm), other_host (issue code domain_served_by_other_host, with observed.provider_hint and remedy) or other_publication (domain_serving_other_publication). An alarm stands until a later check succeeds; reads re-check it. Typeroll never changes DNS or rolls back because of it.',
|
|
230
230
|
inputSchema: {},
|
|
231
231
|
handler: withErrorBoundary(async (_args, { client, siteId }) => ok(await client.get(siteId, 'publishing/domains'))),
|
|
232
232
|
},
|
package/dist/tools/settings.js
CHANGED
|
@@ -66,6 +66,7 @@ export const settingsTools = [
|
|
|
66
66
|
language: z.string().optional().describe('BCP-47 tag (e.g. "en", "sv", "en-GB"). Drives <html lang> on the rendered site.'),
|
|
67
67
|
robots_txt: z.string().optional(),
|
|
68
68
|
ai_crawler_policy: z.enum(['allow_all', 'no_training', 'block_all']).nullable().optional().describe('AI crawler preference, written as Cloudflare-compatible Content Signals in robots.txt and the Content-Signal response header. `allow_all` (default, writes nothing); `no_training` (search=yes, ai-input=yes, ai-train=no, and Disallow groups for training-only crawlers such as GPTBot, ClaudeBot and Google-Extended); `block_all` (also disallows AI search and user agents such as OAI-SearchBot, ChatGPT-User and PerplexityBot). Googlebot, Bingbot and Applebot are never blocked. An explicit group for an agent in robots_txt wins. A Cloudflare zone AI bot policy is enforced at the edge and can override this; `null` resets to the default.'),
|
|
69
|
+
header_behavior: z.enum(['static', 'sticky', 'reveal_on_scroll_up']).nullable().optional().describe('Site header while scrolling. `static` (default, writes nothing) scrolls with the page; `sticky` stays at the top; `reveal_on_scroll_up` hides while scrolling down and returns as a compact header on the first scroll up, with the navigation menu toggle at every width. The compact state carries `data-header-compact` for site CSS. null clears.'),
|
|
69
70
|
sitewide_noindex: z.boolean().optional().describe('Exclude public pages from search indexing; following links remains a separate setting.'),
|
|
70
71
|
external_routes: z.array(z.object({ path: z.string().min(2).max(2048), owner: z.string().min(1).max(200) }).strict()).max(1000).optional().describe('Exact same-origin paths owned by an independent deployment, e.g. [{path:"/docs/",owner:"Documentation"}]. No wildcards. Does not configure hosting or verify remote availability. Verify each declared public URL separately. Local routes cannot be exempted.'),
|
|
71
72
|
seo_review: z.object({ forbidden_markers: z.array(z.string().min(1).max(500)).max(100).optional(), claims: z.array(z.object({ phrase: z.string().min(1).max(500), guidance: z.string().min(1).max(500) })).max(100).optional(), notes: z.array(z.string().min(1).max(500)).max(100).optional() }).optional().describe('Editorial review rules checked on all built pages, including unchanged pages. Warnings require human review; no copy is rewritten.'),
|
package/dist/tools/sites.js
CHANGED
|
@@ -69,7 +69,7 @@ export const siteTools = [
|
|
|
69
69
|
},
|
|
70
70
|
{
|
|
71
71
|
name: 'check_site_indexing',
|
|
72
|
-
description: 'Probe the site\'s recorded fallback and live production origins and report indexing safety. Keeps X-Robots-Tag, HTML robots meta, and robots.txt crawl policy separate. expectation_reason identifies fallback_protection, platform_robots_blocked, sitewide_noindex or public_site. Customer publishing without a future website host intentionally blocks indexing even when sitewide_noindex is false; this is expected, not an error. Fallback URLs must expose the documented edge noindex header. Use before launch and after changing domains or indexing settings.',
|
|
72
|
+
description: 'Probe the site\'s recorded fallback and live production origins and report indexing safety. Keeps X-Robots-Tag, HTML robots meta, and robots.txt crawl policy separate. expectation_reason identifies fallback_protection, platform_robots_blocked, sitewide_noindex or public_site. Customer publishing without a future website host intentionally blocks indexing even when sitewide_noindex is false; this is expected, not an error. Fallback URLs must expose the documented edge noindex header. Each target also records whose page was read: serving_publication (the X-Typeroll-Publication header, else /.well-known/typeroll/publication.json), serving_typeroll and provider_hint (a likely previous host named by a well-known header such as x-kinsta-cache). For customer publishing the current publication is expected; when another host or another publication answers, serving_typeroll is false, matches_expected is false and an issue explains it, because the robots findings then describe that other host. A previous host using Cloudflare for SaaS (for example Kinsta) outranks the customer\'s own DNS until the domain is removed from that host\'s dashboard. Use before launch and after changing domains or indexing settings.',
|
|
73
73
|
handler: withErrorBoundary(async (_args, { client, siteId }) => {
|
|
74
74
|
const res = await client.get(siteId, 'indexing-diagnostics');
|
|
75
75
|
return ok(res);
|
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@typeroll/mcp-server",
|
|
3
3
|
"mcpName": "io.github.Typeroll/typeroll",
|
|
4
|
-
"version": "0.45.
|
|
4
|
+
"version": "0.45.104",
|
|
5
5
|
"description": "Typeroll CMS MCP server \u2013 connect AI agents to the REST API to manage content and publish static websites.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
@@ -121,6 +121,14 @@ desktop 1280, wide 1536); do not independently hide all links at the menu bounda
|
|
|
121
121
|
Verify 1023/1024 px, Tab/Shift-Tab, Enter/Space, Escape returning focus,
|
|
122
122
|
visible focus, long labels, no-JavaScript links and no horizontal overflow.
|
|
123
123
|
|
|
124
|
+
To divide the mobile panel into groups, put one `core/navigation_links` per
|
|
125
|
+
group in the second slot and give each a `group_label` (render version 13),
|
|
126
|
+
for example `"group_label": "Product"`. The label is shown above the links,
|
|
127
|
+
names the list for screen readers and takes the site's Eyebrow style (or
|
|
128
|
+
`group_label_style_id`). Do not insert Text blocks or headings as group titles
|
|
129
|
+
or style them with site CSS. Read `read_site_settings` → `render.version`
|
|
130
|
+
first; below 13, raise it with `tr-upgrade-rendering`.
|
|
131
|
+
|
|
124
132
|
Each entry in `links` (`core/navigation_links` and `core/navigation`) may set
|
|
125
133
|
`link_rel`, for example `nofollow sponsored` on an affiliate or partner link.
|
|
126
134
|
A `core/tabs` choice with a direct `href` accepts `link_rel` and `new_tab` too,
|
package/skills/tr-responsive.md
CHANGED
|
@@ -212,6 +212,16 @@ when it is below 12, raise it with `tr-upgrade-rendering` instead of working
|
|
|
212
212
|
around it in content. Rows, and containers that become rows at some
|
|
213
213
|
breakpoint, are unchanged.
|
|
214
214
|
|
|
215
|
+
## Long words on small screens (render version 13)
|
|
216
|
+
|
|
217
|
+
From render version 13 (Core 0.2.140), a container that is a column at every
|
|
218
|
+
breakpoint keeps its children on one line, and Text and Heading blocks break
|
|
219
|
+
a word that does not fit. A long word, URL or inline code span then no longer
|
|
220
|
+
makes every child of the container, and the page, wider than a 375 px screen.
|
|
221
|
+
Below 13, raise the version with `tr-upgrade-rendering` instead of adding site
|
|
222
|
+
CSS such as `.intro > * { min-width: 0; max-width: 100% }`. Rows and
|
|
223
|
+
fixed-width children are unchanged.
|
|
224
|
+
|
|
215
225
|
## Native image framing and body heading scales (Core 0.2.35+)
|
|
216
226
|
|
|
217
227
|
Use `core/image.scale_percent` (100–200, responsive) for deliberate horizontal
|