@typeroll/mcp-server 0.45.73 → 0.45.75
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/server.js +1 -0
- package/dist/tools/forms.js +12 -0
- package/dist/tools/pages.js +20 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/skills/tr-content-write.md +10 -0
- package/skills/tr-migration-evidence.md +9 -0
package/dist/bundled-content.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
export const BUNDLED_SKILLS = {
|
|
7
7
|
"tr-blog": "---\nname: tr-blog\ndescription: Set up a blog, news section or dated article series using Pages, a content type, shared templates and a dynamic listing.\n---\n\n# Build a blog with Pages\n\nEvery article is a Page. A Content type supplies its extra fields, URL pattern\nand default template. Do not create a separate item store or generate listing\nHTML by hand.\n\n1. Read `get_site_capabilities`, `list_block_types`, `list_content_types` and\n `list_page_templates`. For a substantial change, create a site version and\n pass its ID as `version` on every supported operation.\n2. Create an editable shared template:\n\n```json\n{ \"name\": \"article-layout\", \"label\": \"Article\", \"starter\": \"article\", \"status\": \"published\" }\n```\n\nSend it to `create_page_template`. Use block tools with\n`target: { kind: \"template\", id: \"article-layout\" }` to refine the layout.\n\n3. Call `create_content_type`:\n\n```json\n{\n \"name\": \"articles\", \"label_singular\": \"Article\", \"label_plural\": \"Articles\",\n \"route_template\": \"/articles/{slug}\", \"template\": \"article-layout\",\n \"sort_field\": \"date_published\", \"sort_dir\": \"desc\",\n \"fields\": [\n { \"name\": \"excerpt\", \"label\": \"Summary\", \"type\": \"textarea\" },\n { \"name\": \"featured_image\", \"label\": \"Cover image\", \"type\": \"image\" }\n ]\n}\n```\n\nTitle, slug, author, date, SEO, status and body are built-in Page values. Keep\nonly custom values in `fields`.\n\n4. Call `create_page` for each article with its `title`, `slug`,\n `content_type: \"articles\"`, `status`, `date_published`, `fields` and `blocks`.\n Use native heading, prose, image, list and table blocks. Store its returned\n Page ID; use it for every later operation.\n5. Create the archive as another Page with `core/page_list`, using\n `data: { content_type: \"articles\", sort_by: \"date_published\", sort_order: \"desc\", paginate: 10 }`.\n A homepage teaser can use `limit: 6` instead. Both resolve saved Pages at\n render time; adding an article needs no listing regeneration.\n6. Link to the archive from navigation. Preview representative articles and\n the archive on desktop and mobile with `get_preview_link page_id=...`.\n7. Save any working copies before publication. `update_page` and block edits\n need `save: true` or `commit_working_copy`; new Pages and template definitions\n are saved immediately. Deploy only within the user's authorized scope and\n verify the returned job and public URL.\n\nUse the organization's connected media storage before importing remote images.\nDo not hardcode a Typeroll media hostname. Use the returned media URL.\n\nA published Page with complete route tokens becomes one static detail page.\nDraft/review Pages stay private. Unlisted Pages have a public URL but are omitted\nfrom listings and search indexes. The selected version's robots policy applies.\n",
|
|
8
8
|
"tr-brand": "---\nname: tr-brand\ndescription: Use when the user asks to create a brand identity, design system, or visual style for a site. Triggers on \"create a brand\", \"design the look\", \"choose colors\", \"pick fonts\", \"make it look like [reference]\", or \"rebrand the site\". Produces a cohesive palette, typography scale, and CSS custom properties applied to an existing site.\n---\n\n# Design a brand identity 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\n\nThis skill turns a brief (or a reference URL/screenshot) into a complete\nvisual design system applied to the site's settings and partials.\n\n## Preconditions\n\n- Site exists and MCP is configured.\n- You have at least one of: industry, mood words, reference URL, existing\n logo colors, or competitor sites to contrast with.\n\n## Step 1 — Gather context\n\nAsk (or infer from the brief):\n\n1. **Industry + audience.** Law firm → formal, trust. Café → warm, approachable.\n Tech startup → clean, modern. Interior design → refined, editorial.\n2. **Mood words.** 3–5 adjectives the brand should feel: \"minimal, Nordic,\n calm\" or \"bold, energetic, playful\".\n3. **Reference.** A URL, a screenshot, or a competitor they like (and what\n they want to be different from it).\n4. **Must-keep.** Existing logo color? Legal industry color conventions?\n\nIf the user provided a URL, fetch it and note the dominant colors,\ntypeface categories, and layout density.\n\n## Step 2 — Build the palette\n\nA Typeroll site uses 7 color tokens:\n\n| Token | Role | Design rule |\n|---|---|---|\n| `primary` | Brand identity. CTA buttons, active nav, links. | High contrast on `background`. |\n| `secondary` | Header, footer, darker sections. | Darker or more neutral than primary. |\n| `accent` | Highlights, price tags, badges, hover states. | High-energy complement. |\n| `background` | Page background. | Near-white for light themes, near-black for dark. |\n| `surface` | Cards, input boxes, code blocks. | Slightly off from `background`. |\n| `text` | Body copy. | ≥4.5:1 contrast ratio on `background`. |\n| `text_light` | Secondary labels, captions, placeholders. | ≥3:1 on `background`. |\n\nText on `primary`, `secondary` and `accent` (buttons, badges, tabs,\ncall-to-action sections, the `on_*` style tokens) is black or white\nautomatically, chosen by APCA from render version 8. When the brand\nspecifies it, set `colors.on_primary`, `colors.on_secondary` or\n`colors.on_accent` (a hex colour; `null` returns to automatic). The response\nlists `on_colors` with each one's APCA Lc and WCAG 2 ratio: button text needs\nLc 60, body text Lc 75. Do not compensate with custom CSS.\n\n**Palette recipes by mood:**\n\n*Nordic / minimal:*\n```\nprimary: #1f2a30 secondary: #142027 accent: #c9b89a\nbackground: #faf8f4 surface: #f2ede5 text: #1f2a30 text_light: #7a7265\n```\n\n*Warm / artisan:*\n```\nprimary: #3d2b1f secondary: #2a1d14 accent: #c8860a\nbackground: #fdf6ee surface: #f7ede0 text: #1a1008 text_light: #8a7060\n```\n\n*Modern / tech:*\n```\nprimary: #2563eb secondary: #1e293b accent: #f59e0b\nbackground: #ffffff surface: #f8fafc text: #0f172a text_light: #64748b\n```\n\n*Editorial / dark:*\n```\nprimary: #e2c08d secondary: #0f0f0f accent: #e2c08d\nbackground: #0f0f0f surface: #1a1a1a text: #f5f5f0 text_light: #a0a090\n```\n\nCheck WCAG contrast ratios mentally: text on background must be ≥4.5:1.\nThe online tool `https://webaim.org/resources/contrastchecker/` is useful\nbut not accessible during a tool call — reason about perceived contrast\ninstead (light grey on white = bad; dark grey on white = fine).\n\n## Step 3 — Choose typefaces\n\nPick from high-quality Google Fonts pairings:\n\n| Heading | Body | Mood |\n|---|---|---|\n| Cormorant Garamond | Raleway | Luxury, editorial |\n| Playfair Display | Source Sans 3 | Classic, readable |\n| DM Serif Display | DM Sans | Contemporary, clean |\n| Fraunces | Mulish | Artisan, craft |\n| Syne | Inter | Bold, modern |\n| Plus Jakarta Sans | Plus Jakarta Sans | Clean, versatile |\n| Libre Baskerville | Libre Franklin | Traditional, trustworthy |\n\nSame font for heading and body is fine if it has enough weight variation\n(Inter at 700 + 400 works well).\n\n`size_base` should be 16 for most sites; 17–18 for text-heavy editorial\nsites; 15 for dense dashboards.\n\n## Step 4 — Apply to the site\n\nOne call sets everything:\n\n```\nupdate_site_settings {\n \"colors\": { ...all 7 tokens },\n \"fonts\": { \"heading\": \"...\", \"body\": \"...\", \"size_base\": 16 },\n \"custom_css\": \"/* optional: utility classes or @keyframes */\"\n}\n```\n\nRead back to confirm: `read_site_settings`.\n\n**Inside a redesign branch, scope the brand to the branch** — pass\n`version=\"<branch>\"` on `update_site_settings` (and `read_site_settings`).\nColors / fonts / logo / custom_css then live on the branch (copy-on-write,\nchain-fallback to main for anything you don't override) and don't touch the\nlive site until you `merge_branch`. This is the correct way to rebrand on a\nbranch — don't hack the palette into a `:root{}` override in the header\npartial just to keep it off live; that's no longer necessary.\n\n### Site icons — always propose them, never leave them empty\n\nEvery site gets a favicon + apple touch icon + 192px app icon as part of brand setup:\n\n1. **Brand assets exist** (favicon-*.png, app icon, symbol): upload the\n right sizes via `upload_media_inline` (favicon: 32–64px PNG or SVG;\n apple touch icon: 180×180 PNG; app icon: 192×192 PNG) and set all three in one call:\n `update_site_settings { \"favicon\": \"<url>\", \"apple_touch_icon\": \"<url>\", \"icon_192\": \"<url>\" }`.\n2. **No icon assets:** derive a proposal instead of skipping — crop the\n logo's symbol to a square and resize locally (`sips -z 180 180 in.png\n --out icon-180.png` on macOS, or ImageMagick), or generate a simple\n icon candidate with the imagegen lab (see `tr-imagegen`; respect the\n style profile, no text). Upload, set, and tell the user it's a\n proposal they can swap.\n\nA site shipping with the browser's default globe icon is a build gap —\ntreat icons like the logo: part of done.\n\n## Step 5 — Update partials to use the new palette\n\nPartials that hardcoded hex colors need updating. Fetch the header:\n\n```\nread_partial partial_id=\"header\"\n```\n\nIf it has hardcoded colors, replace them with CSS variable references\n(`var(--color-primary)`) and call `replace_partial`:\n\n```\nreplace_partial partial_id=\"header\" html_content=\"<updated HTML>\"\n```\n\nSame for footer.\n\n## Step 5b — Express the typography as styles\n\nColours and fonts are only half the brand. Put the type scale, weights,\nspacing and recurring treatments into the site's named styles (`list_styles`,\n`update_style`, `create_style`): body text and H1–H6 apply everywhere; the\neyebrow, lead, quote, buttons and section rhythm are chosen per block with\n`style_id`. Set phone values in `base` and grow them in `at.tablet` and\n`at.desktop`. Prefer this to custom CSS: editors can see and change styles,\nand every page follows.\n\n## Step 6 — Custom CSS for advanced tokens (optional)\n\nIf the brand needs things beyond the 7 base tokens — e.g. a gradient,\na special border radius, or a branded highlight color — add them via\n`custom_css`:\n\n```css\n:root {\n --brand-gradient: linear-gradient(135deg, var(--color-primary), var(--color-accent));\n --radius-brand: 2px; /* sharp corners for formal brands */\n --letter-spacing-display: -0.03em; /* tight tracking for display headings */\n}\n```\n\nThen reference `var(--brand-gradient)` etc. in page HTML and partials.\n\n## Step 4b — Section + layout design defaults\n\nThese are non-negotiable defaults the rest of the platform skills inherit (`tr-new-site`, `tr-page-template`). Apply them on every page that has visible sections — they're battle-tested across real customer migrations.\n\n### One signal per section boundary\n\nUse **either** a background-color shift **or** a horizontal divider line at a section transition — never both stacked. They serve the same purpose; stacking them looks busy.\n\n- Default: alternating `.section` / `.section.alt` with a bg shift is enough.\n- A standalone divider line (gradient/keyline) is reserved for the hero → body boundary, where the bg already shifts.\n\n### Sections are full-bleed; content is container-width\n\nThe section element ALWAYS spans the full viewport (its bg, border, decorative line). Content inside is constrained to a readable column.\n\n**Block-mode pages (the default):** this is native. Top-level\n`core/section` blocks are full-bleed out of the box — set `background`\non the section and it runs edge-to-edge, meeting the header with zero\ngap; the section's `width` field (narrow/normal/wide/full) constrains\nthe content column. **NEVER add 100vw negative-margin hacks on block\npages** — they double-bleed and break. Anchor ids / custom classes on\nsections are safe from template_capabilities_version ≥ 0.15.3 (older\nversions wrapped the section in a div and silently killed full-bleed —\nthere, put the anchor on a block inside the section).\n\n**HTML-mode pages (`html_content`) only:** the renderer wraps the body\nin `<main class=\"page-content\">` with `max-width: var(--container-medium)`\n— a section's bg-color rule alone gives a \"1080px-wide stripe in the\nmiddle of the page\", which is wrong. Every section that has a bg/border\nmust apply the negative-margin escape:\n\n```css\n.my-page .section {\n position: relative;\n margin-left: calc(50% - 50vw);\n margin-right: calc(50% - 50vw);\n width: 100vw;\n padding: 74px 0;\n}\n.my-page .section.alt { background: #fff }\n```\n\nThe first section (hero) also wants `margin-top: -32px` to cancel `.page-content`'s top padding. Do NOT wrap the page in `overflow-x: clip` — it cancels the bleed.\n\n### Cards sit directly on the section bg — never on a matching bg\n\nA card with a white bg inside a white-bg section creates a redundant \"white plate on white\" effect. Two enforcement rules:\n\n1. Cards on the default page bg (`var(--color-background)`) can use `background: #fff` + border. ✓\n2. Cards on `.section.alt` (which has `background: #fff`) must drop their own bg:\n - **Single-card section** (one card in a section): drop all chrome (bg, border, accent line). Just content; the section's bg is the only context.\n - **Grid cards** (multiple side-by-side): keep border for grid separation, drop bg. The card becomes a transparent container with a hairline outline.\n\n ```css\n .my-page .section.alt .my-expert,\n .my-page .section.alt .my-expert::before { background: transparent; border: 0; padding: 0; display: none }\n .my-page .section.alt .my-grid-card { background: transparent } /* grid cards keep border */\n ```\n\nMental model: bg shifts twice before you have a problem — body → section → card. Three shifts feels muddled.\n\n### Gradient-clipped headings need extra line-height for descenders\n\nWhen using `-webkit-background-clip: text` + `display: inline-block` to render a gradient-filled heading, the inline-block box is sized by `line-height`. With `line-height: 1` (a common \"tight\" value for display headings) the descenders of g/j/y/p get clipped.\n\n**Rule:** gradient-clipped headings use `line-height: 1.1` or higher, plus `padding-bottom: 0.05em` for belt-and-braces:\n\n```css\n.gradient-h1 {\n display: inline-block;\n background: var(--gradient-brand);\n -webkit-background-clip: text;\n background-clip: text;\n color: transparent;\n line-height: 1.12;\n padding-bottom: 0.05em;\n}\n```\n\n### Hero copy is not body copy\n\nWhen porting a page, identify the H1 + tagline pair and leave the body intro inside the body. Don't lift a body sentence up into the hero unless the source has it twice.\n\nRule: hero gets at most **H1 + one tagline**. Body intro stays in the body. Repeating the same sentence in both places looks accidental.\n\n### Footer architecture: navigate by domain, not by content type\n\nDefault footer columns should mirror the user's mental model of the BUSINESS, not the technical content shapes. Anti-pattern: separate \"Podcast / Articles / Events / Offers\" columns that just list content categories.\n\nBetter default:\n- **Områden / Domains** — subject domains, what the user wants help with\n- **Företaget / Company** — about / services / legal, meta-information about the org\n\nReserve a third column only when there's a genuinely different surface (locations, languages, partner pages). Don't pad the footer with content-type columns — navigation to those happens via top nav + topic pages.\n\n## Step 7 — Preview\n\n```\nget_preview_link\n```\n\nOpen in browser. Check:\n- Colors render as intended (not \"undefined\" or missing)\n- Fonts load (Google Fonts link is in `<head>`)\n- Nav text is readable against header background\n- Body text has sufficient contrast\n\n## Pitfalls\n\n- **Don't set colors without checking the header contrast.** If `primary`\n is light, white nav text becomes unreadable. Either darken `primary` or\n make the header use `secondary`.\n- **Custom_css is global.** Rules here apply to every page. Keep it to\n `:root {}` token additions and truly global utilities. Page-specific\n styles go in the page's HTML `<style>` block.\n- **Google Fonts load time.** Two different font families is fine; three\n adds measurable LCP impact. Stick to two families with variable-font\n versions when possible.\n- **Dark themes need dark surface too.** Setting `background: #0f0f0f`\n but leaving `surface: #f8fafc` (white) breaks every card/input. Always\n update all 7 tokens as a set.\n- **The renderer's `.page-content` layout shell (html-mode only).** The\n renderer wraps `html_content` in `<main class=\"page-content\">` with\n constrained `max-width` and default typography. The typography defaults\n now sit inside `:where()` so they have specificity 0 — a customer's\n class rules trivially win. The layout shell (width + padding) is still\n at normal specificity by design: it's what gives a brand-new page\n reasonable margins out of the box. If a section needs to escape the\n shell (full-bleed bg, full-width hero), apply the negative-margin\n pattern shown in Step 4b. Don't fight the shell with `overflow-x`\n hacks. Block-mode pages don't have the width problem — sections are\n natively full-bleed there.\n- **The shell's global `img` rule leaks into custom figures.** Both modes\n apply `:where(.page-content) img { margin: …; border-radius: … }`. A\n hand-built image card (rounded clipping wrapper around an `<img>`) gets\n phantom margins inside the wrapper — visible as white bands above and\n below the photo. Zero it explicitly in your figure CSS:\n `.my-figure img { margin: 0; border-radius: 0 }`.\n",
|
|
9
|
-
"tr-content-write": "---\nname: tr-content-write\ndescription: Use when the user asks to write, draft, or rewrite a page on a Typeroll site. Loads the site's design conventions before writing so the new content matches the existing voice and style.\n---\n\n# Write a page that fits the site\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\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\nThe default failure mode for an AI writing a page is \"good generic\nHTML in the wrong voice.\" This skill makes the discovery step\nnon-optional.\n\n## Recipe\n\n### 1. Always discover first\n\n```\nget_site # site name (use it in copy)\nread_site_settings # tagline, contact info, brand colors\nread_partial partial_id=\"header\" # what other pages exist in the nav\nlist_pages limit=5\nbatch_read_pages page_ids=[<2-3 representative pages>]\n```\n\nRead the actual HTML of an existing page. Note:\n- Heading structure (single `<h1>` per page? subtitle pattern?)\n- Whether the site uses CSS variables (`var(--color-primary)`) or\n hardcoded values\n- Tone (sober, playful, technical, marketing-y)\n- Length conventions (do existing pages run 200 words or 2000?)\n- Whether internal links use absolute or relative URLs\n\n`list_styles` too: use the site's named styles (`style_id` on heading, text,\nlist, button and section blocks) for eyebrows, leads, quotes and buttons. If a\ntreatment you need could recur and no style fits, create one rather than\nsetting one-off values; ask first when it changes the site's look.\n\nA small label above a heading is the heading block's `eyebrow`, and a line\nbelow it is its `subtitle` (render version 2), each with its own style field.\nNever write them as separate text blocks with classed HTML. In text blocks,\nstyle whole paragraphs with site text styles (`<p class=\"s-lead\">`). Content\nthat needs headings with parts, images or buttons belongs in their own blocks,\nnot in a text block's HTML.\n\n`list_block_templates` and `list_partials` too. Start a common section from a\nblock template (`insert_block_template`, then edit the copy). Use a global\nblock (`core/global_block`) for content that must stay identical on every\npage, such as a shared call to action; don't paste copies of it.\n\n### 2. Ask for the brief\n\nIf the user hasn't told you, ask:\n\n- **Topic + purpose**: what's the page for, who's it for?\n- **Key points**: must-include facts, calls to action\n- **Target length**: short landing vs. long-form\n- **Audience**: anything specific (existing customers, agencies,\n developers)\n- **Reference page**: is there an existing page to match in tone or\n structure?\n\n### 3. Draft\n\nWrite in semantic HTML, matching the site's conventions you observed\nin step 1:\n\n- Use `<section>`, `<article>`, `<h1>`/`<h2>`, `<p>`, `<ul>` — avoid\n div soup.\n- Match the existing site's class naming or CSS variable usage. Don't\n introduce a new design system mid-page.\n- Insert images via `<img src=\"https://cdn...\" alt=\"...\">` — use\n `list_media` to find existing images first; only generate new ones\n if necessary (see `tr-images` skill).\n- Default status: `draft`. Don't auto-publish unless the user said so.\n\n### 4. Create or update\n\n```\n# New page:\ncreate_page title=\"...\" slug=\"...\" html_content=\"<full body>\"\n status=\"draft\" kind=\"page\"\n seo_title=\"...\" seo_description=\"...\"\n\n# Or update an existing one:\nupdate_page page_id=<id> patch={ html_content: \"...\" }\n```\n\nFor an existing page, `read_page` first and preserve the existing\nstructure — replace one section at a time rather than rewriting the\nwhole body, unless the user explicitly asked for a full redo.\n\n### 5. Preview + iterate\n\n```\nget_preview_link page_id=<id>\n```\n\nShow the URL to the user. Iterate on feedback. Common rounds:\nshortening, adding a CTA, tweaking SEO description.\n\n### 6. Status change is the user's call\n\nDon't `update_page status:\"published\"` without an explicit \"looks\ngood, publish it\" from the user. Same for `trigger_deploy`.\n\n## Instead of the content workflows\n\n`start_workflow` types `content_generation` and `content_improvement` run the\nmodel with the Organization's own AI key. On a Site without that access they\nfail with `failure_code: \"ai_not_enabled\"`; don't retry them. Do the work with\nthis recipe and tell the user you did it yourself:\n\n- **Generate a page** (`content_generation { topic, tone?, audience? }`):\n steps 1–5 for that topic, created as a draft.\n- **Improve content** (`content_improvement { limit? }`): `list_pages\n status=\"published\"` and `batch_read_pages`, rank the pages by thin text,\n missing `seo_title`/`seo_description` and age (not updated for a year), and\n rewrite the weakest few (five by default) in the site's voice. Keep facts,\n URLs and structure; leave the rewrites as unsaved drafts and show previews\n before anything is saved or published.\n\n## SEO conventions worth knowing\n\n- **`kind: \"article\"`** for blog posts and news. Switches to\n `og:type=article` + emits Article JSON-LD. Set `author` too — empty\n author = no Person schema = no author rich-result eligibility.\n- **SEO title** target 50-60 chars. Past 60 Google truncates.\n- **Meta description** target 150-160 chars. Don't write fluff to\n fill it; Google rewrites descriptions when they go off-topic.\n- **OG image** per page matters for shareable content. For articles\n especially.\n\n## Pitfalls\n\n- Reading 0 pages and just inventing a design is the most common\n failure. Always sample at least one existing page first.\n- Skipping the brief and producing 1000 words of plausible filler when\n the user wanted a 200-word landing. Ask up front.\n- Auto-publishing. Don't.\n",
|
|
9
|
+
"tr-content-write": "---\nname: tr-content-write\ndescription: Use when the user asks to write, draft, or rewrite a page on a Typeroll site. Loads the site's design conventions before writing so the new content matches the existing voice and style.\n---\n\n# Write a page that fits the site\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\nResearch and migration seeding are not editor confirmations. On Core 0.2.95 /\nMCP 0.45.74+, create structured research Pages with `write_mode: \"import\"` and\nexplicit schema `writable_by: [\"import\", ...]`. For existing answers, discover\n`read_page_authority` and `import_page_fields`: read the revision, then change\nonly named leaves using stable keyed-item paths. This import saves immediately;\nthe ordinary draft-writing recipe below still applies to editorial writes.\nNever interpret absent provenance in `read_page` as an unprotected value, reset\nan owner's source, or retry a 409 using editor authority. Existing API-attributed\ndata requires separate review; it is not automatically downgraded.\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\nThe default failure mode for an AI writing a page is \"good generic\nHTML in the wrong voice.\" This skill makes the discovery step\nnon-optional.\n\n## Recipe\n\n### 1. Always discover first\n\n```\nget_site # site name (use it in copy)\nread_site_settings # tagline, contact info, brand colors\nread_partial partial_id=\"header\" # what other pages exist in the nav\nlist_pages limit=5\nbatch_read_pages page_ids=[<2-3 representative pages>]\n```\n\nRead the actual HTML of an existing page. Note:\n- Heading structure (single `<h1>` per page? subtitle pattern?)\n- Whether the site uses CSS variables (`var(--color-primary)`) or\n hardcoded values\n- Tone (sober, playful, technical, marketing-y)\n- Length conventions (do existing pages run 200 words or 2000?)\n- Whether internal links use absolute or relative URLs\n\n`list_styles` too: use the site's named styles (`style_id` on heading, text,\nlist, button and section blocks) for eyebrows, leads, quotes and buttons. If a\ntreatment you need could recur and no style fits, create one rather than\nsetting one-off values; ask first when it changes the site's look.\n\nA small label above a heading is the heading block's `eyebrow`, and a line\nbelow it is its `subtitle` (render version 2), each with its own style field.\nNever write them as separate text blocks with classed HTML. In text blocks,\nstyle whole paragraphs with site text styles (`<p class=\"s-lead\">`). Content\nthat needs headings with parts, images or buttons belongs in their own blocks,\nnot in a text block's HTML.\n\n`list_block_templates` and `list_partials` too. Start a common section from a\nblock template (`insert_block_template`, then edit the copy). Use a global\nblock (`core/global_block`) for content that must stay identical on every\npage, such as a shared call to action; don't paste copies of it.\n\n### 2. Ask for the brief\n\nIf the user hasn't told you, ask:\n\n- **Topic + purpose**: what's the page for, who's it for?\n- **Key points**: must-include facts, calls to action\n- **Target length**: short landing vs. long-form\n- **Audience**: anything specific (existing customers, agencies,\n developers)\n- **Reference page**: is there an existing page to match in tone or\n structure?\n\n### 3. Draft\n\nWrite in semantic HTML, matching the site's conventions you observed\nin step 1:\n\n- Use `<section>`, `<article>`, `<h1>`/`<h2>`, `<p>`, `<ul>` — avoid\n div soup.\n- Match the existing site's class naming or CSS variable usage. Don't\n introduce a new design system mid-page.\n- Insert images via `<img src=\"https://cdn...\" alt=\"...\">` — use\n `list_media` to find existing images first; only generate new ones\n if necessary (see `tr-images` skill).\n- Default status: `draft`. Don't auto-publish unless the user said so.\n\n### 4. Create or update\n\n```\n# New page:\ncreate_page title=\"...\" slug=\"...\" html_content=\"<full body>\"\n status=\"draft\" kind=\"page\"\n seo_title=\"...\" seo_description=\"...\"\n\n# Or update an existing one:\nupdate_page page_id=<id> patch={ html_content: \"...\" }\n```\n\nFor an existing page, `read_page` first and preserve the existing\nstructure — replace one section at a time rather than rewriting the\nwhole body, unless the user explicitly asked for a full redo.\n\n### 5. Preview + iterate\n\n```\nget_preview_link page_id=<id>\n```\n\nShow the URL to the user. Iterate on feedback. Common rounds:\nshortening, adding a CTA, tweaking SEO description.\n\n### 6. Status change is the user's call\n\nDon't `update_page status:\"published\"` without an explicit \"looks\ngood, publish it\" from the user. Same for `trigger_deploy`.\n\n## Instead of the content workflows\n\n`start_workflow` types `content_generation` and `content_improvement` run the\nmodel with the Organization's own AI key. On a Site without that access they\nfail with `failure_code: \"ai_not_enabled\"`; don't retry them. Do the work with\nthis recipe and tell the user you did it yourself:\n\n- **Generate a page** (`content_generation { topic, tone?, audience? }`):\n steps 1–5 for that topic, created as a draft.\n- **Improve content** (`content_improvement { limit? }`): `list_pages\n status=\"published\"` and `batch_read_pages`, rank the pages by thin text,\n missing `seo_title`/`seo_description` and age (not updated for a year), and\n rewrite the weakest few (five by default) in the site's voice. Keep facts,\n URLs and structure; leave the rewrites as unsaved drafts and show previews\n before anything is saved or published.\n\n## SEO conventions worth knowing\n\n- **`kind: \"article\"`** for blog posts and news. Switches to\n `og:type=article` + emits Article JSON-LD. Set `author` too — empty\n author = no Person schema = no author rich-result eligibility.\n- **SEO title** target 50-60 chars. Past 60 Google truncates.\n- **Meta description** target 150-160 chars. Don't write fluff to\n fill it; Google rewrites descriptions when they go off-topic.\n- **OG image** per page matters for shareable content. For articles\n especially.\n\n## Pitfalls\n\n- Reading 0 pages and just inventing a design is the most common\n failure. Always sample at least one existing page first.\n- Skipping the brief and producing 1000 words of plausible filler when\n the user wanted a 200-word landing. Ask up front.\n- Auto-publishing. Don't.\n",
|
|
10
10
|
"tr-custom-blocks": "---\nname: tr-custom-blocks\ndescription: Use when a Typeroll site needs a block the core library does not have (an icon list, cards, steps, a team strip), so the user wants a reusable block type with its own fields.\n---\n\n# Build a site block type\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\nA site block type is reusable: editors place it and fill in its fields, and\nevery instance updates when the type changes. Build one when the same section\nshape recurs, or when editors should fill in fields rather than arrange\nblocks. For a one-off section use plain blocks; for a section copied and then\nadapted per page use a block template; for content that must be identical\neverywhere use a global block.\n\nCreating, changing and deleting block types needs write permission. A changed\nblock type reaches the live site with the next deploy.\n\n## 1. Look before you build\n\n```\nlist_block_types # core and site types: maybe it exists\nlist_block_type_starters # icon_list, feature_cards, steps\nlist_styles # the site's named styles to use inside\n```\n\nPrefer a starter or a core block with the right settings over a new type.\n\n## 2. Compose it from existing blocks\n\nComposed is the default. Declare the editable fields in `schema` and build the\nmarkup from core blocks in `composition`:\n\n- An inner block field set to exactly `\"{{props.name}}\"` takes the block's\n field value. Bindings keep their type (lists, links, booleans).\n- For a repeating group, add a `core/repeater` with\n `items: \"{{props.items}}\"`, `source_type: \"static\"` and no `item_block`. Its\n children render once per item and read the item with `\"{{item.title}}\"`.\n- Make a whole item a link with a `core/container`\n `{ tag: \"a\", href: \"{{item.link.href}}\", new_tab: \"{{item.link.new_tab}}\" }`.\n Items without a link render as a plain container.\n- Use `core/text` for values from text fields (always escaped), `core/heading`\n for headings, `core/icon`, `core/image`, `core/button`.\n- Give inner blocks named styles (`style_id`) instead of CSS where you can.\n\nField types: `text`, `textarea`, `richtext`, `image`, `icon` (`{name}_svg`),\n`link` (`{ page_id }` or `{ url }` plus `new_tab`; resolves to `href`, `target`,\n`rel` and follows page renames), `select` with `options`, `boolean`, `number`,\n`array` (`fields`, `item_label`, `min_items`, `max_items`), `object`.\n\n## 3. Check and preview before saving\n\n```\nvalidate_block_type definition={…} # problems with paths, e.g. /schema/0/fields/1/name\npreview_block_type definition={…} # html + css with sample data, as published\n```\n\nFix every error. Read warnings: a binding inside other text\n(`\"Call {{props.phone}}\"`) is shown as typed; only whole-value bindings resolve.\n\n## 4. CSS\n\n`styles` apply only inside the block: selectors are prefixed automatically,\n`:scope` is the block element, and `html`, `body` and `:root` are refused. Use\n`var(--color-primary)` and the site's spacing, not fixed colours, and keep text\nat WCAG AA contrast.\n\n## 5. Create, place, deploy\n\n```\ncreate_block_type name=\"icon_list\" label=\"Icon list\" category=\"content\" schema=[…] composition=[…] styles=\"…\"\nadd_block page_id=<id> block={ type: \"icon_list\", data: { items: [ … ] } }\nget_preview_link page_id=<id>\n```\n\nShow the preview to the user. Deploy only when they approve.\n\n## Template block types (advanced)\n\nWhen the markup cannot be built from blocks, use `template` instead of\n`composition`: `{{field}}` (escaped), `{{{richtext_field}}}`, `{{#field}}…{{/field}}`,\n`{{^field}}…{{/field}}` (empty), `{{#each items}}…{{/each}}` with `{{@index}}`,\n`{{@number}}`, `{{@first}}`, `{{@last}}`, and `{{#link field class=\"x\"}}…{{/link}}`.\nA type has a composition or a template, never both.\n\nDeclare `parts` for the elements of the markup a site may want to restyle:\n`parts: [{ name: \"title\", label: \"Title\", selector: \".icon-list-title\", target: \"heading\" }]`.\nSelectors are tags, classes, attributes and structural pseudo-classes, never\n`:hover` or `::before`. Styles then reach the part site-wide\n(`parts: [\"icon_list.title\"]` on the style) or on one block\n(`data.part_styles: { title: \"card-title\" }`, a field added automatically).\n\n## Changing a type in use\n\n- Rename a field with `renames: { \"old\": \"new\" }` (`\"items.old\": \"new\"` inside a\n group): Typeroll moves the data everywhere the type is used.\n- Removing or retyping a field that holds data is refused with the list of\n usages unless you send `confirm_data_loss: true`. Ask the user first.\n- `find_pages_using_block_type` before deleting; a type in use cannot be\n deleted.\n\n## Pitfalls\n\n- Pasting HTML into a `core/html` block inside a composition: editors cannot\n edit it and it skips the site's styles. Compose from real blocks.\n- Binding a text field into `core/prose`: the value becomes HTML. Use `core/text`.\n- Nesting links: an icon or button with its own link inside a linked container.\n- Linked containers on a site below render version 4: every icon sits in a\n link of its own, so the icon list starter nests links, and an item without\n an address is still a link. The write warns; upgrade the site's rendering\n (`tr-upgrade-rendering`) first.\n- Writing `script` without the user's explicit request.\n",
|
|
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 the review-fix-recheck cycle. For a FINAL bit-for-bit check\n of the compiled output before merge, deploy once (`trigger_deploy\n version=\"<branch>\"` → poll `get_deploy_status` → use the immutable\n `deploy_url`, a Cloudflare Pages hash 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). **One origin per eval:** the iframe trick\n needs same-origin, so run each variant's snippet on its own page (different\n `*.pages.dev` hashes 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, just reload the DB-live preview and re-run\n the affected dimension before signing off — no re-deploy needed (deploy only\n for the final compiled-output check, if any).\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. 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**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",
|
|
@@ -18,7 +18,7 @@ export const BUNDLED_SKILLS = {
|
|
|
18
18
|
"tr-migrate-astro": "---\nname: tr-migrate-astro\ndescription: Migrate an existing Astro site, including Astro Content Collections, to native Typeroll Pages and Content types while preserving routes, design and media.\n---\n\n# Migrate an Astro 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 (platform change) or renew (new design/content),\nrecord the brief and verify one prototype per active template before bulk writes.\nUse standard blocks first, reviewed custom types next, rare documented HTML last.\nBoth paths must preserve or meaningfully redirect every old public URL. That\nshared contract defines acceptance; this recipe only adds source-specific steps.\n\n\nAstro Content Collections are source data. In Typeroll, each source record\nbecomes a Page, and each source schema becomes a Content type. Static Astro\npages use that same Page model. Shared Astro layouts become Page templates,\npartials or reusable block types; no separate item storage is created.\n\n## Inventory before writing\n\nRead the source repository's routing, content loaders, schema, frontmatter,\nlayouts, components, styling and public assets. New Astro projects can define\nloaders in `src/content.config.ts`; older projects may use\n`src/content/config.ts` and files under `src/content/`. Follow what the actual\nproject uses, including generated route logic and trailing-slash policy.\n\nDiscover the target with `get_site`, `get_site_capabilities`, `list_pages`,\n`list_content_types`, `list_page_templates` and `list_block_types`. Existing target\ncontent remains out of scope unless the user authorized replacing it. Create a\nsite version for the migration and pass it consistently on all versioned calls.\nCall `get_migration_readiness` before import; connect verified customer storage\nbefore transferring any source content or media.\n\nRecord a URL inventory from the existing site and source routing. Use explicit\nPage paths to preserve nested routes or frontmatter-based permalinks. Do not\nassume that a filename alone determines the production URL.\n\n## Map the source\n\n- Common frontmatter such as title, slug, author, publication date and SEO maps\n to top-level Page metadata.\n- Other schema fields become the Content type's custom fields and Page `fields`.\n Preserve typed objects and arrays; use `page_ref`/`page_ref_list` for relations.\n- Markdown becomes native Page body blocks. First render Markdown to HTML using\n the source project's parser, then review block conversion. MDX components\n require explicit equivalents; never drop them as if they were plain Markdown.\n- A shared layout becomes a reusable Page template with `template_content_slot`.\n Assign it as the type's default. Header/footer and reusable fragments can be\n partials. Use native blocks before choosing a rare reviewed HTML/embed exception.\n- Browser scripts, integrations and server-rendered endpoints need separate\n review. A static Page cannot inherit a private server runtime by copying its\n source text. Configure the supported Forms/Extension runtime explicitly.\n Do not copy client scripts; analytics and advertising tags move to the\n Integrations app behind the cookie banner (tr-migration-evidence, \"Scripts,\n tracking and consent\").\n\nCreate templates with `create_page_template`, types with `create_content_type`,\nthen one `create_page` per source record. Use returned Page IDs, not source IDs\nor slugs, for later updates and previews. For linked content, create the Pages\nfirst and resolve their reference fields in a second pass.\n\nUpload assets to connected storage and rewrite source-relative image references\nto returned media URLs. Use direct upload URLs for local bytes and the customer\ntransfer flow for remote media; do not relay large base64 payloads through the\nmodel. Preserve alt text, captions, dimensions, links and image variants.\n\n## Listing and verification\n\nAn archive is a Page with `core/page_list` filtered by `content_type`. Configure\nsorting and pagination from the block schema. It reads saved Pages at build\ntime; do not generate a second listing representation or call a regeneration tool.\n\nCheck representative long/short bodies, code, tables, images, captions, embedded\ncomponents and related links at desktop and mobile widths. Compare the source\nand Typeroll preview, then a fresh authorized static build. Verify every inventory\nURL and redirect, canonical/hreflang, sitemap inclusion, noindex state and assets.\nSave working copies before publication. Keep the source host and DNS in place\nuntil the candidate passes checks and the user authorizes its cutover.\n",
|
|
19
19
|
"tr-migrate-multisite": "---\nname: tr-migrate-multisite\ndescription: Use when migrating SEVERAL sites at once — a WordPress multisite network, a group of country/language sites on different top-level domains, or any batch of related sites moving to Typeroll together. Triggers on \"multisite\", \"network of sites\", \"10 sites\", \"our .se/.de/.co.uk sites\", \"language versions\", \"migrate all our sites\". For a single WordPress site use tr-migrate-wp; for a single non-WP source use tr-import-url.\n---\n\n# Migrate a multisite / multi-domain family to Typeroll\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 (platform change) or renew (new design/content),\nrecord the brief and verify one prototype per active template before bulk writes.\nUse standard blocks first, reviewed custom types next, rare documented HTML last.\nBoth paths 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## The first decision: one site or many?\n\n**One Typeroll site owns exactly one domain** (plus its apex/www sibling).\nSo:\n\n| The old family looks like | Build it as |\n|---|---|\n| `example.se`, `example.de`, `example.co.uk` — separate domains | **N Typeroll sites**, one per domain |\n| `example.com/se/`, `example.com/de/` — one domain, language folders | **One site**, using `path` on each page (`/se/om-oss`) |\n| A WP multisite on subdomains that the customer wants to consolidate onto one domain | **One site** + redirects from every old subdomain |\n\nConfirm this with the user before creating anything — it's the one decision\nthat is expensive to reverse (domains, deploys, and analytics all hang off\nit). The rest of this recipe assumes the common case: **N sites, one per\ndomain**, sharing a design.\n\nUse personal OAuth with the required Organization permissions or an org-scoped\nAPI key. Select each Site explicitly and use its returned context; never reuse\nanother Site's OAuth context. Keep Site and Version explicit on batch calls.\n\n## Phase 0 — Plan the batch (do this once, in writing)\n\nProduce a table and get the user to confirm it before touching the platform:\n\n| Old URL | Language | New Typeroll site | Domain | Notes |\n|---|---|---|---|---|\n\n**Check platform readiness before the plan is even agreed:**\n\n```\nget_migration_readiness site_id=<any existing site>\n```\n\nThen once per market, with that market's own source:\n\n```\nget_migration_readiness site_id=<de-site> source_url=\"https://example.de\"\n```\n\nCheck each target Site: storage is shared per Organization, but Hosting Groups,\npermissions and verification origins can differ. Verified customer storage is\nrequired before import; GitHub/build/domain readiness is checked separately for\npublication. Do not infer that every Site shares the same blockers.\n\nAsk explicitly:\n- Which intent applies per Site: preserve or renew? Which source or approved\n prototype is the **design reference**? Build that one properly first.\n- Are the sites **translations of each other** (same page structure) or\n independent? This decides whether hreflang clusters are mechanical or\n hand-mapped.\n- Any domains being **retired or merged**? Those need HTTP redirects served by the old hostname; DNS itself is not\n a redirect. Plan that hosting separately.\n\n## Phase 1 — Inventory EVERY old site, before building anything\n\nDo this for all sites up front. It's cheap, it's the only artefact that tells\nyou when you're done, and it stops you discovering an untouched 400-URL blog\nin week three.\n\nFor each source site, create the target site first (the inventory lives on\nit):\n\n```\ncreate_site name=\"Example DE\"\n```\n\nThen walk the source and post what you find:\n\n```\nfetch https://example.de/sitemap.xml # follow sitemap-index children\nfetch https://example.de/wp-json/wp/v2/pages?per_page=100 # walk X-WP-TotalPages\n```\n\n```\nadd_migration_urls site_id=<de-site> source_origin=\"https://example.de\" source=\"sitemap\" urls=[\n { url: \"https://example.de/ueber-uns\" },\n { url: \"https://example.de/kontakt\" },\n …\n]\n```\n\n**`source_origin` is not optional in a multisite job.** It rejects URLs from\nanother origin, which is the guard that stops domain B's `/kontakt` landing\nin domain A's inventory — where it would silently read as \"covered\" because\ndomain A happens to have a `/kontakt` too.\n\nAdd every source you have, each with its own label — they merge per URL:\n- `source=\"sitemap\"` — the sitemap(s)\n- `source=\"rest\"` — WP REST pages/posts/custom types\n- `source=\"gsc\"` — a Search Console export, **with `gsc_clicks`**. This is\n what makes the coverage report prioritise itself: the 12 URLs carrying all\n the traffic sort to the top.\n- `source=\"crawl\"` — anything you found by following internal links\n\nExclude only private/admin/session noise, with reasons. Public tags and archives\nneed working routes or relevant redirects, even when omitted from new navigation:\n\n```\nadd_migration_urls urls=[{ url: \"/wp-admin\", excluded: true, notes: \"Private source administration\" }, { url: \"/wp-login.php\", excluded: true, notes: \"Private source login; not public content\" }]\n```\n\nRead it back and note the starting number:\n\n```\nlist_migration_urls site_id=<de-site> status=\"unhandled\" limit=50\n```\n\n## Phase 2 — Build the reference site\n\nFollow `tr-migrate-wp` (or `tr-import-url`) for the ONE reference site:\ndesign, header/footer partials, page templates, block types. Get the user to\napprove it before replicating — every fix you make after this point costs\nN times as much.\n\n## Phase 3 — Replicate the design to the other sites\n\nReplicate only source-validated native recipes and brand settings. Confirm each\nmarket's template overrides and business workflows independently. Export/import\ncustom block types only for specifically reviewed source-specific exceptions,\nnot generic corrective blocks or copied tenant CSS.\n\nThen per site:\n- `read_site_settings` on the reference → `update_site_settings` on the\n target with the same colors/fonts (translate `site_name`, `tagline`,\n contact details — those are per-market, not shared).\n- Recreate header/footer partials with translated nav labels.\n- Page templates: rebuild with `add_block target={kind:'template', id:…}`.\n\nSet the language per site — it drives `<html lang>`, `og:locale` and\nalt-text generation:\n\n```\nupdate_site site_id=<de-site> language=\"de\"\n```\n\n## Phase 4 — Preserve each market's business functions\n\nUse the business-function inventory from `tr-migration-evidence`. Some markets\nuse native Forms, others an owned lead flow, affiliate destination or partner\niframe. Do not replace them all with a new contact form. Configure a Form only\nwhere it is the approved equivalent. Verify each market's recipient, destination,\nlanguage and attribution. Use authorized synthetic tests with a safe recipient;\nrecord delivery UNVERIFIED when it cannot be tested safely. Never create real\nleads or send mail to third parties as a routine migration test.\n\nScripts follow tr-migration-evidence, \"Scripts, tracking and consent\": copy no\nsource JavaScript, and carry each market's own analytics and advertising IDs\nover in that site's Integrations app with its cookie banner enabled. Never\nreuse one market's tracking IDs on another.\n\n## Phase 5 — Redirects for everything you didn't preserve\n\nWork the coverage report, not your memory:\n\n```\nlist_migration_urls site_id=<de-site> status=\"unhandled\"\n```\n\nFor each entry, one of three outcomes — no fourth option:\n\n- It moved → `create_redirect from_path=\"/alte-seite\" to_path=\"/neue-seite\"`\n- It was merged → redirect to the relevant replacement, not the homepage\n- It should exist and doesn't → go back and migrate it\n\nRe-read the list. `unhandled` reaching zero is the exit condition for this\nphase.\n\nPattern redirects require verified source-to-destination content mappings.\nA category/tag/year shape alone is not proof of equivalent destinations. Check\nexpanded artifact size and provider limits, actual slash/query variants and\nthat no rule hides a preserved Page. A 200 at the wrong content fails.\n\n## Phase 6 — Wire the hreflang cluster\n\nThis is the step that only exists because the family is multi-domain, and the\none most likely to be skipped. Each page declares its siblings on the other\ndomains; the renderer adds the page's own self-reference.\n\n```\nupdate_page site_id=<se-site> page_id=\"om-oss\" patch={ alternates: [\n { hreflang: \"de\", href: \"https://example.de/ueber-uns\" },\n { hreflang: \"en-GB\", href: \"https://example.co.uk/about-us\" },\n { hreflang: \"x-default\", href: \"https://example.com/about-us\" }\n]}\n```\n\nRules the search engines actually enforce:\n- **Reciprocal.** Every page in a cluster must list every other one. Write\n all N sides or the cluster is ignored. `batch_update_pages` is the sane way\n to do this once you have the mapping table.\n- **One `x-default`** per cluster, pointing at the language selector or the\n fallback market. Optional, but useful when the family doesn't cover a\n visitor's language.\n- **Absolute URLs on the final domain** — not the `*.typeroll` fallback\n subdomain. The cluster is what you want live after cutover, and a fallback\n URL in there is a leak you'll be cleaning up for months.\n- Invalid entries are **rejected at write time** with the reason. If a write\n fails, fix the tag/href — don't strip the field to make it pass.\n\nPages that have no equivalent on the other domains get no alternates at all.\nA cluster of one is meaningless markup.\n\n## Phase 7 — Verify BEFORE touching DNS\n\nDeploy each site (`trigger_deploy`), then check what it actually serves:\n\n```\nverify_migration_urls site_id=<de-site> source_origin=\"https://example.de\" check_source=true\n```\n\nThis requests every inventory URL against the site's fallback subdomain —\nthe real domain still points at the old host, which is exactly why the check\nis possible at all. Verdicts:\n\n| Verdict | Meaning | Action |\n|---|---|---|\n| `ok` | 200 at the same path | none |\n| `ok_redirect` | redirects to a 200 | none; flatten if `hops` > 1 |\n| `missing` | 404/410 | **the gap** — redirect it or migrate it |\n| `broken_redirect` | loop, or chain ending on an error | fix the rule |\n| `error` | 5xx / timeout | inconclusive, re-run |\n\n`check_source=true` also requests the OLD site, so a URL that already 404s\nupstream needs a reviewed retention/retirement decision, including traffic and\nbacklinks. Never automatically mark source 404s `excluded`.\n\nSlash-equivalent source URLs are one inventory decision but remain separate\nverification requests through `observed_paths`. Therefore `checked` can exceed\nthe normalized inventory size, and a missing slash variant remains a launch\nblocker. Redirect builds cover both source spellings and send internal targets\nto the site's configured trailing-slash style.\n\nNote what the check does NOT catch: it verifies that a URL *resolves*, not\nthat the page at the other end is the right content. Review content and SEO\nparity, then record the exact dataset and result with\n`record_migration_seo_acceptance`.\n\nIterate until `missing` and `broken_redirect` are both zero **on every site**.\nOnly after explicit cutover authorization, per site (`add_domain` prepares a\nnew canonical build and is not an inventory-only operation):\n\n1. `add_domain` with the intended apex/www canonical; leave `auto_deploy` on\n2. Wait for that canonical/sitemap deploy to succeed, then point DNS\n3. `poll_domain` until verified — there is no separate activation step\n4. Re-run `verify_migration_urls target_origin=\"https://example.de\"` against\n the real domain, to confirm the cutover kept what the pre-check proved\n5. Submit the new sitemap in Search Console; keep the old property open for\n a few weeks and watch the 404 report\n6. Rerun the complete, unfiltered URL check and SEO review, then require\n `get_migration_launch_report` to return `launch_ready: true`\n\n## Definition of done (per site)\n\n- [ ] `get_migration_readiness source_url=<this market's old site>` → `ready: true`, warnings reviewed\n- [ ] `list_migration_urls status=\"unhandled\"` → 0\n- [ ] Complete `verify_migration_urls` → 0 `missing`, 0 `broken_redirect`, 0 `error`, 0 untested variants\n- [ ] `record_migration_seo_acceptance` is accepted for the latest deploy\n- [ ] `get_migration_launch_report` → `launch_ready: true`\n- [ ] No source JavaScript copied; analytics/advertising via the Integrations app with the cookie banner on (`scripts.status: \"clean\"` in the launch report)\n- [ ] hreflang cluster written on both/all sides, absolute, final domains\n- [ ] `language` set on the site; `<html lang>` correct in the deployed HTML\n- [ ] Internal links rewritten (no lingering absolute links to the old domain)\n- [ ] Market-specific business functions mapped; authorized synthetic interaction/delivery verified or explicitly UNVERIFIED\n- [ ] Media uploaded to this site's own library (no cross-site `cdn_url`), alt text in this market's language\n- [ ] If cutover is authorized: domain declared, canonical deploy succeeded, DNS verified; sitemap submitted. Otherwise report pre-cutover complete separately.\n\n## Pitfalls specific to this job\n\n- **Don't share one inventory across domains.** Inventory entries key on\n path; two markets both have `/kontakt`. One site, one inventory.\n- **Don't build all N sites in parallel from scratch.** Build one, approve,\n replicate. Parallel building multiplies every design mistake by N.\n- **Don't skip the parity check because coverage says 100%.** Coverage is a\n claim about the data; parity is a measurement of the server. They disagree\n exactly when it matters — an unpublished target page, a typo'd path, a\n redirect chain.\n- **Don't point DNS site-by-site on a whim.** Cutting over one market at a\n time is fine and usually wise, but the hreflang cluster spans markets: a\n page pointing at a domain that still serves the old site is pointing at\n content that doesn't match. Either cut over close together, or write the\n cluster after the last market lands.\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",
|
|
20
20
|
"tr-migrate-wp": "---\nname: tr-migrate-wp\ndescription: Use when the user asks to migrate a WordPress site to Typeroll, mentions wp-json, or names a WP source URL. Walks the WP REST API, preserves cleaned HTML and shared references, transfers media, sets redirects, and leaves everything as drafts for human review. Typeroll does not convert HTML into blocks.\n---\n\n# Migrate from WordPress to Typeroll\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 (platform change) or renew (new design/content),\nrecord the brief and verify one prototype per active template before bulk writes.\nUse standard blocks first, reviewed custom types next, rare documented HTML last.\nBoth paths 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\nThe platform's migration workflow is the \"managed\" path for customers who\nwant one-click. It runs on the server and is also available to you:\n`start_workflow type=\"migration\" config={ wp_url }` on an existing site, or\n`create_site_and_migrate` (organization key) for a new one; poll `get_workflow`\nand approve its review gate only with the user's consent. This skill is the\n\"power-user\" path: you do it locally, mix data sources freely, and the user\nreviews each step in their terminal.\n\n## Preconditions\n\n**Run the readiness check FIRST — before touching any content:**\n\n```\nget_migration_readiness source_url=\"https://oldsite.com\"\n```\n\nWhen the migration includes reusable page/item layouts, include each proposed\nblock composition and its content type fields in this same call. A\n`waiting_for_native_support` result means inspect the actual blocker. Review\nintentional custom types and rare HTML exceptions using `tr-migration-evidence`;\nfix missing types/fields and report renderer defects rather than masking them. Independent content and SEO work may continue. Rerun the review after the\nrequired Core version is deployed, then verify preview and a fresh hosted\nbuild.\n\nPass `source_url` — that adds the checks on the site you're migrating FROM.\nAn old host that answers 403/429 to server-side requests is a **blocker**: the\nimport would produce empty pages, or pages containing the host's block page,\nwhich reads as real content and is worse. Whether `/wp-json` answers is a\nwarning, since scraping is a real fallback (it just loses ACF/custom fields).\n\nIf `ready` is false, STOP and report the blockers to the user. Do not start\nthe import \"and fix it after\": every blocker is one whose failure is invisible\nonce the work is done, so discovering it late means redoing the expensive part.\n\n- **Media storage** — without it, every `<img>` keeps its WordPress URL. The\n new site looks perfect and is still served images by the old host. It breaks\n the day the customer cancels that hosting, months later, all at once.\n- **Publishing** — with verified Organization storage, customer publishing can\n import and preview before GitHub, builds or website domains are connected.\n Publishing readiness is a separate gate before a hosted build/cutover. Legacy\n self-hosted configurations may report a missing hosting adapter; follow the\n returned blocker rather than assuming all installations have the same setup.\n\nWarnings are worth relaying but don't stop you: no verification origin (the\npre-cutover parity check can't run), forms without a\nnotification address, or a target site with no design to rebuild INTO.\n\nThen the ordinary preconditions:\n\n- MCP connected through personal OAuth or an appropriately scoped API key.\n Select and confirm the Site/Version before writes; do not request pasted secrets.\n- The source WP site has `/wp-json` reachable (Google for \"wordpress\n REST API disabled\" if not — common for hardened hosts).\n- The target exists. Establish the source design baseline first, then prove\n representative target templates using `tr-migration-evidence`. Preserve\n appearance in preserve mode; use the approved design/content brief in renew mode.\n- If the target already has content, you must NOT clobber it — always\n `list_pages` first and only write to slugs that don't already exist.\n Pass the slug explicitly: `create_page` then refuses an address that is\n already in use (409 `slug_in_use` naming the existing page) instead of\n numbering it.\n\n## Recipe\n\n### 1. Probe and inventory\n\n```\nfetch <wp-url>/wp-json # confirm REST is on\nfetch <wp-url>/wp-sitemap.xml or /sitemap.xml # URL inventory\n```\n\nBuild a list of every URL you intend to migrate. WP custom post types\nneed their REST endpoint (e.g. `/wp-json/wp/v2/news?per_page=100`),\nwalking `X-WP-TotalPages` to paginate.\n\n### 2. Measure the source and protect existing target work\n\n```\nget_site\nread_site_settings # colors, fonts, voice cues\nlist_partials # header / footer / shared\nread_partial partial_id=\"header\" # nav structure\nlist_pages limit=5\nbatch_read_pages page_ids=[<2-3 representative ids>] # see actual conventions\n```\n\nDon't skip this. Imposing a stranger's design on a customer's site is\nthe biggest avoidable mistake.\n\n### 3. Normalize shared data before importing page bodies\n\nRead taxonomy definitions and paginated terms from the helper's `/taxonomies`\nand `/terms/{taxonomy}` (Helper 0.3.2), or WordPress REST. Create a Content type\nfor each taxonomy and one Page per term. Preserve term IDs in a stable source\nmapping, archive URLs, parents and custom fields such as icons/emoji.\n\nAdd `page_ref_list` fields to article Content types. Store term Page IDs, never\ncopied category names, emojis, slugs or sort values on every article. Category\narchives can list articles using `core/repeater` with `source_type: backlinks`.\nUse an explicit primary category for breadcrumbs when available; do not guess\namong multiple categories. A table of contents is a derived template block,\nnot a `toc_html` field.\n\nCategory and tag Pages have editable block bodies. Scaffold the source term\ndescription plus a reverse-reference listing, leaving room for unique editorial\ncontent. Multiple tag/category references do not give a Page multiple parents:\npreserve its source page parent, or use an unambiguous primary category. Tags do\nnot set its parent. `parent` controls breadcrumb ancestry, not URL generation;\npreserve the source `path` even when it differs from the chosen hierarchy.\n\nThe managed importer preserves existing imported Pages on retry and refuses\nURL/identity conflicts or dangling term references. An older flattened import\nrequires a separately reviewed mapping repair; do not re-import over edits.\n\n**Styles.** Recurring source treatments (theme classes for eyebrows, leads,\nbutton variants, highlighted sections) become named site styles\n(`create_style`) applied with `style_id`, so editors can change them in one\nplace. Do not preserve them as classed raw HTML in text blocks.\n\n### 4. Import one page at a time, draft status\n\nFor each source URL:\n\na. Fetch from WP. Prefer the helper plugin's authenticated endpoint\n (`/wp-json/typeroll/v1/...`) if available — it bypasses\n `show_in_rest=false` and returns ACF + builder fields. Otherwise\n fall back to `/wp-json/wp/v2/<post-type>?slug=<slug>`.\n\nb. Clean the HTML. Strip Elementor / Gutenberg / Breakdance class\n soup. Drop empty `<div>` and `<span>` wrappers. Keep semantic tags,\n tables, iframes from known hosts (YouTube / Vimeo / Calendly).\n Remove every `<script>`: plugin and theme JavaScript is never copied.\n Analytics and advertising tags move to the Integrations app behind the\n cookie banner (tr-migration-evidence, \"Scripts, tracking and consent\").\n\nc. Migrate referenced images with `upload_media_from_url`. The customer's\n transfer Worker copies and verifies them directly in R2. Use the returned\n media reference only after verification. Stop and report a failed required\n transfer; do not silently hotlink the WordPress source.\n\nThe managed importer stores cleaned HTML. Typeroll does not convert HTML into blocks, and `set_page_mode` never rewrites HTML. Pages move to blocks only when someone rebuilds them with blocks.\n\nd. Rebuild into editable blocks according to the selected intent. Preserve exact\n wording in preserve mode; track approved changes in renew mode. Map headings,\n prose, images, buttons and layout to standard fields first, then create\n reviewed custom types where needed. Use raw HTML only for a documented,\n exceptional fragment. Preserve anchors and map every old URL in either mode.\n\ne. Write the page as a draft:\n\n ```\n create_page title=\"...\" slug=\"<last-path-segment>\"\n path=\"/<preserved-wordpress/path/>\"\n blocks=[<native block tree>]\n status=\"draft\" kind=\"article\" author=\"...\"\n seo_title=\"...\" seo_description=\"...\"\n ```\n\n **Preserve the source URL.** WP post URLs like\n `/2024/01/foo-bar/` uses `slug: \"foo-bar\"` and\n `path: \"/2024/01/foo-bar/\"`. Slug is one segment; `path` preserves the\n complete nested URL.\n\n### 5. Redirects\n\nAfter migration, every URL the agent didn't preserve verbatim needs a\nredirect:\n\n```\ncreate_redirect from_path=\"/old-services\" to_path=\"/services\"\n```\n\nWalk the inventory; for each URL: did it become a page with the same\npath? If yes, no redirect. If renamed, `create_redirect`. If\ncombined with another page, redirect to the relevant combined content. Do not\nmark a public page excluded just because the new design omits it. Public URL\nretirement requires a separately agreed exception; admin/session noise can be\nexcluded with a recorded reason.\n\nUse a pattern only after verifying every affected source-to-destination mapping.\nFor an explicitly approved prefix change, `/old-blog/:slug` → `/articles/:slug`\ncan preserve intent if each destination contains the corresponding article.\nDo not blanket-redirect category/tag/author/feed/year families to a home or blog\npage. Date paths need actual route mappings; stripping a year is not enough.\nReview source 404s rather than automatically excluding them.\n\nRules:\n\n- **Trailing `*` only.** A mid-path splat (`/blog/*/comments`) is dropped\n silently by Cloudflare — the platform refuses it at write time.\n- **`:splat`** carries the captured remainder; **`:name`** matches exactly one\n segment and can be replayed by name.\n- **A pattern that would hide a live page is refused**, naming the pages. That\n is the platform protecting you: redirects are applied before static files, so\n `/blogg/*` would make every real article under `/blogg/` unreachable. Narrow\n the prefix instead.\n- **Query-string URLs can't be matched.** WP's `/?p=123` has no path to key on;\n these require an explicit query-routing solution on the future host or a\n reviewed retirement decision. The old host cannot handle requests after its\n hostname moves.\n- Rules are emitted most-specific-first, so a narrow rule always beats a broad\n one — you can safely have `/blogg/recept/*` alongside `/blogg/*`.\n\nThen verify against reality before anything is cut over:\n\n```\nimport_sitemap url=\"https://old.example.com/sitemap.xml\"\n# Optional: direct Search Console query (platform service account must have property access)\nimport_gsc_performance property=\"https://old.example.com/\" months=6\n# Or paste a Search Console Pages CSV via csv=\"...\" and source_origin.\ncheck_internal_links # database preflight before deploy\nverify_migration_urls # after trigger_deploy; compact exceptions by default\nrecord_migration_seo_acceptance # after reviewing this exact deployment\nget_migration_launch_report # final fail-closed launch gate\n```\n\nThe inventory merges slash-equivalent URLs into one work item but preserves\ntheir source spellings in `observed_paths`. Verification requests every one of\nthose spellings; treat a failing variant as a real gap even when coverage is\notherwise complete. Fresh builds expand redirect sources to both slash forms.\n\nFor a site imported before Typeroll normalized WordPress plain-text fields,\naudit and repair the legacy records before visual review:\n\n```\nrepair_migration_plain_text # dry_run=true by default; returns exact field diffs\n```\n\nThis operation is deliberately limited to `title`, `seo_title`,\n`seo_description`, and schema-defined plain-text `excerpt` fields. It cannot\ntouch bodies, HTML, slugs, paths, or URLs. Show all returned diffs and\nconflicts to the user. Only after explicit approval, repeat the same scope and\nselectors with `dry_run=false save=true`. If `truncated=true`, narrow the\nselection or raise `diff_limit` and review the omitted diffs first. Never work\naround a `working_copy` conflict: that resource contains another edit which\nmust be resolved separately.\n\n### 6. Preview + review with the user\n\n```\nget_preview_link page_id=<id> # one URL the user can click\n```\n\nCompare the source and target at desktop and mobile widths. Review representative\narticles, an archive and the home page. Measure heading sizes, readable width,\nspacing, TOC placement and image/card proportions. Scroll below the fold: verify\nheadings after media, the TOC below a sticky header, anchor landing positions\nand related articles inside the intended content column. In Core 0.2.7+, use\n`rhythm: \"article\"` on the Page content slot and `appearance: \"card\"` on Post\nCards for these native presentation choices; no per-page corrective CSS. Check breadcrumbs and shared\ncategory references; test forms/Extensions. HTTP 200 and text presence do not\nprove visual fidelity. Document intentional improvements such as responsive video.\n\nRecord `fidelity: { desktop, mobile, shared_data, integrations, evidence }` with\n`record_migration_seo_acceptance`, based on real checks of the exact deployment.\nThe launch report requires this evidence in Core 0.2.5.\nNever mark an unavailable integration or uninspected screenshot as accepted.\n\n### 7. Ship\n\nWhen the user signs off:\n\n```\n# Bulk-publish drafts that look right\nbatch_update_pages updates=[{page_id, patch:{status:\"published\"}}, ...]\n\n# Deploy\ntrigger_deploy\nget_deploy_status job_id=<id> # poll\n```\n\n## Pitfalls\n\n- **Don't publish during migration.** Always import as `draft`. Even\n if the agent is confident, the customer needs the chance to spot-check.\n- **WP slugs sometimes drift.** A post saved with slug `foo-bar` may\n have been served at `/2024/01/foo-bar/` due to the permalink\n structure. The full URL is what users see in Google; preserve that,\n not the bare slug.\n- **Image bandwidth.** R2 upload is metered. Use `find_pages_matching`\n contains=\"<old-domain>\" on already-imported content to spot images\n that weren't transferred.\n- **Plugin JavaScript.** Never copy scripts from plugins, the theme or\n WordPress core into settings, pages, partials or block types, and never\n copy the cookie plugin. Carry analytics and advertising over by ID in the\n Integrations app with `cookie_consent.enabled: true`. The launch report\n fails on copied source scripts and on tracking outside consent.\n- **WP-specific JSON-LD** (Yoast, Rank Math) is usually wrong after a\n redesign because it references old URLs. Strip it; let Typeroll\n emit fresh Article/Page schemas via `kind: 'article'` + `author`.\n\n## When the source isn't WordPress\n\nThe same shape applies for any source — Squarespace export, custom\nCMS, scraped HTML, CSV. Replace step 1's \"WP REST\" probe with whatever\ndiscovery the source supports, and the rest of the recipe is unchanged.\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\n### Raw-source media order (Core 0.2.35+)\n\nPass raw body HTML into conversion before any parse/serialize round trip with\nanother parser. Core applies browser tree construction before lazy-media cleanup.\nAn image-only H2 becomes an image at that position, not a semantic heading.\nCompare ordered headings/text/media/CTAs, including malformed wrappers, lazy and\nnoscript images and related-content boundaries. Existing Pages are not rewritten;\nrepair confirmed placement errors against raw source without reimporting edits.\n\n\n## Navigation and empty-media variants\n\nInventory first-step input banners independently of final submission providers.\nPreserve their fields, required/minimum validation, service tabs and direct links,\nmobile collapsed/open states and navigation even when the final integration is\ndeferred. Core 0.2.40 provides native `core/navigation_form` and mixed `core/tabs`.\nRead their schema instead of rebuilding them in HTML or reintroducing a provider\nblock. Defaults are same-origin and tab-scoped; never place addresses in URLs or\nclaim cross-origin prefill. The receiving form must explicitly accept the fields.\n\nCompare listing rows with both real and missing images. Use shared post-card\nplaceholder settings for theme decoration, preserving real featured images and\nexplicit `show_image: false`. Compare actual footer link height plus gap, wrapping,\nfocus and column breakpoints; compact navigation density is an intentional option.\nVerify native preview and static output on the agreed viewport boundaries.\n",
|
|
21
|
-
"tr-migration-evidence": "---\nname: tr-migration-evidence\ndescription: Choose preserve or renew intent, map standard and custom blocks, preserve old URLs, and verify content, design and behavior before migration acceptance.\n---\n\n# Migration evidence contract\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\nUse this shared contract with WordPress, Astro, URL and multisite recipes.\n## Choose the migration intent before writes\n\nRecord `preserve` or `renew` in the workspace's `brief/migration.md` (or the\nproject's equivalent brief), with scope, source URLs and the user's decision.\nThese are planning labels, not fields accepted by the migration API. Ask when\nthe intent is unclear; do not infer redesign permission from “migrate”.\n\n| Intent | Source material | Acceptance reference |\n| --- | --- | --- |\n| Preserve: change publishing platform | Keep text, images, structure, visual identity and behavior as closely as practical. | The original site. Record deliberate deviations and their agreed benefit. |\n| Renew: improve design and content | Use source text, images, structure and graphic design as raw material. Agree what to retain, rewrite, combine or replace. | The agreed brief, content plan and approved prototypes, not visual similarity to the old site. |\n\nBoth paths preserve facts, ownership, required business behavior and historical\nURL continuity. Renewal does not authorize invented claims, removal of useful\ncontent or an unapproved domain switch. Do not use a partly migrated target as\nan accidental design reference. Protect existing target edits in either path.\n\n## Use editable blocks in both paths\n\n1. Inspect standard block schemas, composition starters, named styles and Page\n templates. Prefer these when they fit.\n2. When they do not fit, read `tr-custom-blocks` and create a reusable Site block\n type with meaningful editable fields. Prefer a composition of standard blocks;\n a field-driven template type is appropriate when composition cannot express it.\n Validate the type, preview it, save/read it back and test a representative use\n in static output. Do not store a whole page as an opaque HTML field.\n3. Raw HTML is a last resort for a small, exceptional fragment. Record the exact\n block/Page, why neither standard nor custom types fit, editing limitations and\n verification. Keep the surrounding page block-based whenever possible.\n\nA justified HTML exception is a unique art-directed hero: layered imagery,\nanimation and text placed precisely over a background at different breakpoints.\nPrefer a field-driven custom type when editors need to change its text, images\nor links repeatedly; a one-off hero may remain an isolated HTML block. Verify\nactual breakpoints, cropping, keyboard access, contrast and reduced motion. Keep\nessential copy and links usable without animation. Do not copy source-platform\nscripts; any custom script needs explicit scope and the supported runtime.\n\nOn Core 0.2.89 / MCP 0.45.69+, composition preflight accepts reviewed custom types in\n`reviewed_custom_block_types: [{type, reason}]` and rare HTML blocks in\n`reviewed_html_blocks: [{block_id, reason}]`. Inspect the deployed schema first on older installations. These are reviewer\nattestations, not proof of quality. They only acknowledge those specific choices; missing\nblock types/fields and unreviewed workarounds still block readiness. Do not call\nsomething business-specific just to bypass a check. A confirmed renderer bug\nstill needs a platform fix, not a disguised HTML/CSS workaround.\n\n## Import is not block reconstruction\n\nThe managed WordPress importer deterministically stores cleaned HTML and shared\nreferences, without AI rewriting. That is a source-preserving intermediate step.\nThe external agent rebuilds it into reviewed blocks/templates for either intent;\n`set_page_mode` does not perform conversion. Keep the complete source, map every\nin-scope content element, and do not silently truncate long pages. In preserve\nmode retain wording; in renew mode track approved rewrites and source facts.\nAn import finishing does not mean design, block reconstruction or launch is done.\n\n## Old URLs must continue to work\n\nInventory public URLs from sitemaps, CMS records, internal links, pagination and\navailable traffic/backlink exports, including important assets and anchors.\nRecord each old URL as preserved or redirected to a relevant working destination.\nRenewal may merge pages, but every old address still needs a meaningful mapping.\nNever blanket-redirect to the homepage or mark public content `excluded` merely\nto make coverage green. Private/admin/session noise can be excluded with a reason;\nretiring a public URL requires a separately agreed exception to this contract.\n\nUse `add_migration_urls` with `source_origin`, keep a per-URL mapping, and run\n`verify_migration_urls` against the authorized deployed candidate, then\n`get_migration_launch_report`. Check both slash forms, redirect chains/loops,\nquery routes and fragments with their actual routing mechanism. DNS is not an\nHTTP redirect: changed hostnames need a host that continues serving redirects.\nA missing route or a 200 at unrelated content fails continuity in either path.\nDo not switch traffic until these checks pass and cutover is authorized.\n\n## Gate 1: source baseline, before conversion\n\nRecord the chosen intent, authoritative source URL/date, scope, relevant Core version, active\nPage templates and overrides. Inventory home, category/archive, long/short\narticles, downloads, empty related lists and each distinct business surface.\nFor preserve, capture source and target geometry; for renew, compare the target\nwith the approved design while retaining source coverage evidence. Capture\nscreenshots and computed geometry at matching actual `innerWidth`\nvalues (320/390/768/1024/1280/1536 where relevant, plus both sides of actual\nbreakpoints). Record loaded font families/weights, color panels, logo/header,\nhero, gutters, article/sidebar widths, card anatomy/grid and footer. Inspect\nloaded images, menus, scrolled TOC, anchor landings and long links.\nInventory semantic CTA anchors/buttons, downloads and affiliate links before\ncopying prose: retain decoded labels, href paths, query parameters, fragments,\n`target` and meaningful `rel` tokens. A source CSS class may identify a CTA\nwhose appearance would disappear when copied into prose.\n\nBefore removing builder wrappers/classes, preserve the information they carry:\nanchors, lazy/srcset/background images, captions, tables, ordering, downloads,\nembeds and interactions. Content modernization is a separate decision.\n\n## Gate 2: one verified prototype per active recipe, before bulk writes\n\nCall `list_block_types` and `read_block_type` on the deployed runtime. Map each\nsource component to exact fields/bindings, defaults, minimum version and expected\ngeometry. A registry entry does not prove the capability works. Save and read\nback each prototype, then inspect its preview and an authorized static build.\n\nExample: a 48px heading uses `core/heading.font_size_px: 48` on Core 0.2.25+;\nread the saved block back and measure computed `fontSize` at 1280px. Writing\n`font_size_px` to `core/prose`, which does not declare it, is an unsupported\nmapping even if an API preserves that unknown key. A new presentation may use\na reviewed custom type as described above. Missing or ignored declared behavior\nis a platform defect: stop that recipe with `waiting_for_native_support` and do\nnot disguise the failure with tenant CSS.\n\nValidate shared Page references, item order, missing images and empty/unpublished\nreferences. Keep semantics separate from presentation: category emoji belongs\nto its shared Page field, not copied into every article or card excerpt.\nValidate actual field types, not just key names: number fields use JSON numbers\n(`16`, `1.6`, `1`), not strings (`\"16\"`, `\"1.6\"`, `\"1\"`); booleans use `true`/\n`false`. Check responsive map values too. Read-back retention is not enough:\nmeasure the resulting font size, line height and paragraph spacing. If an\nincorrect type is accepted but ignored, fix the prototype before bulk writes.\n\nMap standalone CTAs to `core/button` when its declared fields preserve the\nsource behavior, rather than leaving class-dependent anchors in prose.\nCore 0.2.25+ `new_tab: true` emits `target=\"_blank\"` and\n`rel=\"noopener noreferrer\"` in static HTML; false/unset keeps the same tab.\nVerify the rendered target/rel, preserved affiliate queries/fragments, keyboard\nactivation, actual tab opening and narrow-screen wrapping without requiring\nJavaScript. Do not silently discard source `sponsored`/`nofollow`, download\nbehavior or other semantics unsupported by the chosen block: record the gap\nand use a supported composition or stop for native support.\nDo not apply a template to hundreds of Pages until its prototype passes.\n\n## Distinguish custom presentation from platform defects\n\nA new layout or reusable visual component is a legitimate custom block, not\nnecessarily a Core defect. Use the block hierarchy above. When a declared Core\nfeature fails, do not cover it with site/page CSS, raw HTML or a replacement\nblock. Record and report it. Check these five causes first:\n\n1. **A platform default no setting reaches.** Compare computed text rendering\n and base styles on `body`, links and headings (`-webkit-font-smoothing`,\n `line-height`, `text-decoration`) with the source. If the difference comes\n from Typeroll's base CSS, check Site settings and the render versions first\n (`read_site_settings` → `render.upgrades`).\n2. **A replaced control ignores the block's settings.** A capability can swap a\n field for a provider element (address suggestions). Compare that control\n with a plain field in the same block: border, radius, height, background,\n text colour, font, placeholder, icon and colour scheme in light and dark mode.\n3. **A missing part.** The source has a part (icon, badge, second label) the\n block has no field for. Check `read_block_type` for a field before\n concluding it is missing.\n4. **A value that is stored and ignored.** Read every write response's\n `warnings` (`field_type_mismatch`, `unknown_style_override`): a wrong type or\n key is saved and never rendered. Measure the computed result, not the\n read-back.\n5. **Preview differs from the published page.** A setting that shows in preview\n but not in a fresh hosted build is a publication gap; report both URLs.\n\nReport each gap with: site ID, Organization, Core version and render version;\nsource and target URL; viewport (actual `innerWidth`), browser and DPR; the\nblock ID and type or the setting; source and target computed values; what is\nmissing, ignored or wrong; the proposed setting or fix; and acceptance\ncriteria a reviewer can measure. State that no workaround CSS is in place.\nMark the affected recipe `waiting_for_native_support`, continue independent\nwork, and verify against the chosen acceptance reference after the release.\n\n## Separate files, addresses and business behavior\n\nInventory both file bytes and every discovered historical asset URL, including\nPDF/download hrefs, image click targets, srcsets and backgrounds. Verify transfer\nand rendering separately from old-address preservation. Deduplicate by content\nidentity, not similar filenames. Preflight expanded redirect artifact bytes and\nprovider rule limits. A 200 at an unrelated destination is a failure. A source\n404 is not permission to discard a URL with traffic/backlinks: record a decision.\nQuery URLs need explicit routing support at the future host; keeping the old\nserver does not preserve them when the same hostname moves.\n\nClassify each CTA: contact form, owned lead workflow, affiliate link, partner\niframe/script or other runtime. Preserve market, language, destinations and\nattribution. Never clone recipients or partner IDs across markets. Test loading,\ninteraction and conversion separately; only use an authorized synthetic mode and\nsafe recipient. Unavailable delivery remains UNVERIFIED. No real leads or mail\nto third parties merely to fill a checklist.\n\n## Scripts, tracking and consent\n\nDo not copy JavaScript from the source site. WordPress plugins and themes\ninject their own runtime: jQuery, sliders, lightboxes, form and cookie\nplugins, page builders (Elementor, Breakdance, Divi), emoji and AJAX calls to\n`admin-ajax.php` or `/wp-json/`. It loads from, or posts to, a site that is\nabout to be switched off. Inventory every `<script>` on representative source\npages (head and body, inline and external, and which plugin or theme it\nbelongs to), then decide each one:\n\n| Source script | Decision |\n| --- | --- |\n| WordPress core, theme or plugin runtime | Never copy. Rebuild what it did with native blocks, Forms or settings, or report a platform gap (see \"Report platform gaps\"). |\n| Cookie plugin (CookieYes, Complianz, Cookiebot, Borlabs, …) | Never copy. Enable the site's cookie banner (`cookie_consent.enabled: true`) with Swedish or local text and the privacy policy URL. |\n| Analytics or advertising tag in the Integrations catalog (GA4, GTM, Meta Pixel, LinkedIn, TikTok, Hotjar, Clarity, Pinterest, Snap, Reddit, X, Microsoft Ads, HubSpot, Plausible, Fathom, Matomo) | Carry over the ID only: enable the Integrations app with the source's ID (`list_apps`, `update_app`). Enable the cookie banner; analytics and advertising integrations wait for the visitor's consent. Never paste the vendor snippet. |\n| Tracking tag the catalog does not cover | Only with the owner's approval, in `cookie_consent.scripts_optional` with the banner enabled. Never in `scripts_head`, `scripts_body_end`, `scripts_necessary`, pages or partials. |\n| Partner widget or embed the page needs to work | Prefer a native block or an iframe from an allowlisted host. Keep custom code minimal, record why, and do not load it from the source site. |\n\nAsk the owner to confirm the analytics IDs; never invent or reuse one from\nanother market. Before launch, `get_migration_launch_report` must show\n`scripts.status: \"clean\"`: it fails on copied source-platform scripts\n(`source_runtime_script`), tracking outside consent\n(`tracking_outside_consent`) and analytics or advertising integrations without\nthe banner (`tracking_without_consent_banner`). In the hosted preview, check\nthat no request goes to the source host or `/wp-content/`, no tracking request\nfires before consent, and the tags fire after \"accept all\".\n\n## Evidence record (repeat per recipe, viewport and state)\n\n- Source URL/date and target URL; Page/template IDs; exact publication ID.\n- Runtime/Core version and saved content/config revision.\n- Actual viewport `innerWidth` and height; scroll/menu/anchor state.\n- Source and target screenshot paths; measured expected and actual geometry.\n- Capability mapping: block type, field, read-back value and computed result.\n- Separate PASS / FAIL / UNVERIFIED for routing, content, visual, assets,\n keyboard/interaction and delivery; reviewer and timestamp.\n- Visible deviation, specific user benefit, accepted scope and rationale.\n\nFilled examples:\n\n| Observation | Technical result | Visual decision |\n| --- | --- | --- |\n| Source video overflows a 390px viewport; native video fits and shows the entire frame | Width 390px, original aspect preserved; captures reviewed | PASS: deliberate improvement in mobile usability |\n| Source has four cards and turquoise hero at 1280px; target has three plain cards and no color panel | HTTP 200, no overflow, all content is blocks | FAIL: brand/layout lost; those technical facts cannot attest visual fidelity |\n| Heading round-trip retains 48 but computed size stays 32 | Save succeeds | FAIL: find ignored field/cascade before bulk conversion |\n\nKeep imported, saved, locally checked, hosted-preview checked, published and\ntraffic-cut-over distinct. Template/theme changes invalidate affected acceptance;\nnever attach old evidence to a new publication. `record_migration_seo_acceptance`\nbooleans are reviewer attestations, not automated visual proof. Do not set them\nfrom HTTP status, native-block count or overflow alone. Report a completed\npre-cutover scope honestly when DNS or integrations remain out of scope.\n\n## Complete census and visual signoff\n\nThe initial route census must combine source sitemaps, internal links, archive\npagination, CMS inventory and known utility routes. Include homepage, archives,\n404/search behavior and routes outside the sitemap where present. In preserve mode, do not invent\nfeatures absent from the source. In renew mode, include additions in the agreed brief. Assign every route a recipe, template override,\ncomposition or documented exception. Inventory the whole shell: content outside\n`main`, header, footer, service navigation and archive heroes are not optional.\n\nDerive required evidence from that census. For preserve, pair every in-scope source/target route\nat mobile and desktop; for renew, compare every target route to its approved\nprototype and account for the source content/URL mapping; add each unique component, breakpoint and interaction\nstate. Inspect page-specific media, lists, tables, embeds, CTAs, anchors and related\ncontent. One representative article cannot attest every article's content.\n\nA coverage row records route, recipe, component, state, actual viewport dimensions,\nsource revision, target content/config/Core/publication revision, paired screenshot\nreferences, reviewer, review time and result. All rows start `UNVERIFIED`.\nKeep independent results for content, links, list cardinality, full membership,\nsequence, appearance, keyboard behavior and delivery. Screenshots must be inspected;\nimage diffs and contact sheets assist review and do not prove aesthetics.\n\nFor menus capture source and target closed, open and scrolled states, nested groups\nif present, short landscape, focus order, Escape, link activation and breakpoint\ntransitions. For TOCs capture top/middle/bottom and anchor landings. Include tabs,\naccordions and empty/loading/error/success states where applicable. Separate mobile\nand desktop menu compositions require separate evidence; shared links may be reused\nbut one variant's screenshots do not attest the other.\n\nReport denominators separately: routes/content, paired visual pages, recipes,\ncomponents and states. Missing an archive, open menu, footer or a unique embed leaves\ncoverage incomplete even if all captured screenshots pass. Shared block, theme,\npartial or template changes invalidate every dependent row; content changes\ninvalidate that route and dependent listings. An old revision cannot approve a new\none. Source availability failures remain `UNVERIFIED`, not silently excluded.\n\nVisual signoff is against the chosen intent: source fidelity for preserve,\napproved design/content for renew. It requires zero uncovered required surfaces\nand zero unexplained regressions against that reference. `FAIL` or `UNVERIFIED` blocks that signoff, even when a separately scoped\ntechnical publication has approval. An intentional deviation needs exact before/\nafter evidence, specific user benefit and accepted scope; “cleaner” is not a reason.\n\nPreserve-mode examples from a moving-services migration:\n\n| Surface | Passing evidence | Separate visual result |\n| --- | --- | --- |\n| Mobile menu | Keyboard focus and Escape work | FAIL: header moves, source service group is missing, open-menu composition differs |\n| Category archive | Every destination URL exists | FAIL: source sequence, card anatomy and columns differ |\n| Article body | All text imported | UNVERIFIED: unique embed and footer were never compared |\n\nDo not report “all design checked” from a viewport or recipe sample. Keep source\nlink-order comparisons and paired contact sheets with the report. The same contract\napplies to WordPress, URL, Astro and multisite migration; do not create separate,\nweaker checklists for individual import mechanisms.\n\n\n## Navigation and empty-media variants\n\nInventory first-step input banners independently of final submission providers.\nPreserve their fields, required/minimum validation, service tabs and direct links,\nmobile collapsed/open states and navigation even when the final integration is\ndeferred. Core 0.2.40 provides native `core/navigation_form` and mixed `core/tabs`.\nRead their schema instead of rebuilding them in HTML or reintroducing a provider\nblock. Defaults are same-origin and tab-scoped; never place addresses in URLs or\nclaim cross-origin prefill. The receiving form must explicitly accept the fields.\n\nCompare listing rows with both real and missing images. Use shared post-card\nplaceholder settings for theme decoration, preserving real featured images and\nexplicit `show_image: false`. Compare actual footer link height plus gap, wrapping,\nfocus and column breakpoints; compact navigation density is an intentional option.\nVerify native preview and static output on the agreed viewport boundaries.\n",
|
|
21
|
+
"tr-migration-evidence": "---\nname: tr-migration-evidence\ndescription: Choose preserve or renew intent, map standard and custom blocks, preserve old URLs, and verify content, design and behavior before migration acceptance.\n---\n\n# Migration evidence contract\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\nUse this shared contract with WordPress, Astro, URL and multisite recipes.\n## Choose the migration intent before writes\n\nRecord `preserve` or `renew` in the workspace's `brief/migration.md` (or the\nproject's equivalent brief), with scope, source URLs and the user's decision.\nThese are planning labels, not fields accepted by the migration API. Ask when\nthe intent is unclear; do not infer redesign permission from “migrate”.\n\n| Intent | Source material | Acceptance reference |\n| --- | --- | --- |\n| Preserve: change publishing platform | Keep text, images, structure, visual identity and behavior as closely as practical. | The original site. Record deliberate deviations and their agreed benefit. |\n| Renew: improve design and content | Use source text, images, structure and graphic design as raw material. Agree what to retain, rewrite, combine or replace. | The agreed brief, content plan and approved prototypes, not visual similarity to the old site. |\n\nBoth paths preserve facts, ownership, required business behavior and historical\nURL continuity. Renewal does not authorize invented claims, removal of useful\ncontent or an unapproved domain switch. Do not use a partly migrated target as\nan accidental design reference. Protect existing target edits in either path.\n\n## Use editable blocks in both paths\n\n1. Inspect standard block schemas, composition starters, named styles and Page\n templates. Prefer these when they fit.\n2. When they do not fit, read `tr-custom-blocks` and create a reusable Site block\n type with meaningful editable fields. Prefer a composition of standard blocks;\n a field-driven template type is appropriate when composition cannot express it.\n Validate the type, preview it, save/read it back and test a representative use\n in static output. Do not store a whole page as an opaque HTML field.\n3. Raw HTML is a last resort for a small, exceptional fragment. Record the exact\n block/Page, why neither standard nor custom types fit, editing limitations and\n verification. Keep the surrounding page block-based whenever possible.\n\nA justified HTML exception is a unique art-directed hero: layered imagery,\nanimation and text placed precisely over a background at different breakpoints.\nPrefer a field-driven custom type when editors need to change its text, images\nor links repeatedly; a one-off hero may remain an isolated HTML block. Verify\nactual breakpoints, cropping, keyboard access, contrast and reduced motion. Keep\nessential copy and links usable without animation. Do not copy source-platform\nscripts; any custom script needs explicit scope and the supported runtime.\n\nOn Core 0.2.89 / MCP 0.45.69+, composition preflight accepts reviewed custom types in\n`reviewed_custom_block_types: [{type, reason}]` and rare HTML blocks in\n`reviewed_html_blocks: [{block_id, reason}]`. Inspect the deployed schema first on older installations. These are reviewer\nattestations, not proof of quality. They only acknowledge those specific choices; missing\nblock types/fields and unreviewed workarounds still block readiness. Do not call\nsomething business-specific just to bypass a check. A confirmed renderer bug\nstill needs a platform fix, not a disguised HTML/CSS workaround.\n\n## Import is not block reconstruction\n\nThe managed WordPress importer deterministically stores cleaned HTML and shared\nreferences, without AI rewriting. That is a source-preserving intermediate step.\nThe external agent rebuilds it into reviewed blocks/templates for either intent;\n`set_page_mode` does not perform conversion. Keep the complete source, map every\nin-scope content element, and do not silently truncate long pages. In preserve\nmode retain wording; in renew mode track approved rewrites and source facts.\nAn import finishing does not mean design, block reconstruction or launch is done.\n\n## Old URLs must continue to work\n\nInventory public URLs from sitemaps, CMS records, internal links, pagination and\navailable traffic/backlink exports, including important assets and anchors.\nRecord each old URL as preserved or redirected to a relevant working destination.\nRenewal may merge pages, but every old address still needs a meaningful mapping.\nNever blanket-redirect to the homepage or mark public content `excluded` merely\nto make coverage green. Private/admin/session noise can be excluded with a reason;\nretiring a public URL requires a separately agreed exception to this contract.\n\nUse `add_migration_urls` with `source_origin`, keep a per-URL mapping, and run\n`verify_migration_urls` against the authorized deployed candidate, then\n`get_migration_launch_report`. Check both slash forms, redirect chains/loops,\nquery routes and fragments with their actual routing mechanism. DNS is not an\nHTTP redirect: changed hostnames need a host that continues serving redirects.\nA missing route or a 200 at unrelated content fails continuity in either path.\nDo not switch traffic until these checks pass and cutover is authorized.\n\n## Gate 1: source baseline, before conversion\n\nRecord the chosen intent, authoritative source URL/date, scope, relevant Core version, active\nPage templates and overrides. Inventory home, category/archive, long/short\narticles, downloads, empty related lists and each distinct business surface.\nFor preserve, capture source and target geometry; for renew, compare the target\nwith the approved design while retaining source coverage evidence. Capture\nscreenshots and computed geometry at matching actual `innerWidth`\nvalues (320/390/768/1024/1280/1536 where relevant, plus both sides of actual\nbreakpoints). Record loaded font families/weights, color panels, logo/header,\nhero, gutters, article/sidebar widths, card anatomy/grid and footer. Inspect\nloaded images, menus, scrolled TOC, anchor landings and long links.\nInventory semantic CTA anchors/buttons, downloads and affiliate links before\ncopying prose: retain decoded labels, href paths, query parameters, fragments,\n`target` and meaningful `rel` tokens. A source CSS class may identify a CTA\nwhose appearance would disappear when copied into prose.\n\nBefore removing builder wrappers/classes, preserve the information they carry:\nanchors, lazy/srcset/background images, captions, tables, ordering, downloads,\nembeds and interactions. Content modernization is a separate decision.\n\n## Gate 2: one verified prototype per active recipe, before bulk writes\n\nCall `list_block_types` and `read_block_type` on the deployed runtime. Map each\nsource component to exact fields/bindings, defaults, minimum version and expected\ngeometry. A registry entry does not prove the capability works. Save and read\nback each prototype, then inspect its preview and an authorized static build.\n\nExample: a 48px heading uses `core/heading.font_size_px: 48` on Core 0.2.25+;\nread the saved block back and measure computed `fontSize` at 1280px. Writing\n`font_size_px` to `core/prose`, which does not declare it, is an unsupported\nmapping even if an API preserves that unknown key. A new presentation may use\na reviewed custom type as described above. Missing or ignored declared behavior\nis a platform defect: stop that recipe with `waiting_for_native_support` and do\nnot disguise the failure with tenant CSS.\n\nValidate shared Page references, item order, missing images and empty/unpublished\nreferences. Keep semantics separate from presentation: category emoji belongs\nto its shared Page field, not copied into every article or card excerpt.\nValidate actual field types, not just key names: number fields use JSON numbers\n(`16`, `1.6`, `1`), not strings (`\"16\"`, `\"1.6\"`, `\"1\"`); booleans use `true`/\n`false`. Check responsive map values too. Read-back retention is not enough:\nmeasure the resulting font size, line height and paragraph spacing. If an\nincorrect type is accepted but ignored, fix the prototype before bulk writes.\n\nMap standalone CTAs to `core/button` when its declared fields preserve the\nsource behavior, rather than leaving class-dependent anchors in prose.\nCore 0.2.25+ `new_tab: true` emits `target=\"_blank\"` and\n`rel=\"noopener noreferrer\"` in static HTML; false/unset keeps the same tab.\nVerify the rendered target/rel, preserved affiliate queries/fragments, keyboard\nactivation, actual tab opening and narrow-screen wrapping without requiring\nJavaScript. Do not silently discard source `sponsored`/`nofollow`, download\nbehavior or other semantics unsupported by the chosen block: record the gap\nand use a supported composition or stop for native support.\nDo not apply a template to hundreds of Pages until its prototype passes.\n\n## Distinguish custom presentation from platform defects\n\nA new layout or reusable visual component is a legitimate custom block, not\nnecessarily a Core defect. Use the block hierarchy above. When a declared Core\nfeature fails, do not cover it with site/page CSS, raw HTML or a replacement\nblock. Record and report it. Check these five causes first:\n\n1. **A platform default no setting reaches.** Compare computed text rendering\n and base styles on `body`, links and headings (`-webkit-font-smoothing`,\n `line-height`, `text-decoration`) with the source. If the difference comes\n from Typeroll's base CSS, check Site settings and the render versions first\n (`read_site_settings` → `render.upgrades`).\n2. **A replaced control ignores the block's settings.** A capability can swap a\n field for a provider element (address suggestions). Compare that control\n with a plain field in the same block: border, radius, height, background,\n text colour, font, placeholder, icon and colour scheme in light and dark mode.\n3. **A missing part.** The source has a part (icon, badge, second label) the\n block has no field for. Check `read_block_type` for a field before\n concluding it is missing.\n4. **A value that is stored and ignored.** Read every write response's\n `warnings` (`field_type_mismatch`, `unknown_style_override`): a wrong type or\n key is saved and never rendered. Measure the computed result, not the\n read-back.\n5. **Preview differs from the published page.** A setting that shows in preview\n but not in a fresh hosted build is a publication gap; report both URLs.\n\nReport each gap with: site ID, Organization, Core version and render version;\nsource and target URL; viewport (actual `innerWidth`), browser and DPR; the\nblock ID and type or the setting; source and target computed values; what is\nmissing, ignored or wrong; the proposed setting or fix; and acceptance\ncriteria a reviewer can measure. State that no workaround CSS is in place.\nMark the affected recipe `waiting_for_native_support`, continue independent\nwork, and verify against the chosen acceptance reference after the release.\n\n## Separate files, addresses and business behavior\n\nInventory both file bytes and every discovered historical asset URL, including\nPDF/download hrefs, image click targets, srcsets and backgrounds. Verify transfer\nand rendering separately from old-address preservation. Deduplicate by content\nidentity, not similar filenames. Preflight expanded redirect artifact bytes and\nprovider rule limits. A 200 at an unrelated destination is a failure. A source\n404 is not permission to discard a URL with traffic/backlinks: record a decision.\nQuery URLs need explicit routing support at the future host; keeping the old\nserver does not preserve them when the same hostname moves.\n\nClassify each CTA: contact form, owned lead workflow, affiliate link, partner\niframe/script or other runtime. Preserve market, language, destinations and\nattribution. Never clone recipients or partner IDs across markets. Test loading,\ninteraction and conversion separately; only use an authorized synthetic mode and\nsafe recipient. Unavailable delivery remains UNVERIFIED. No real leads or mail\nto third parties merely to fill a checklist.\n\n## Scripts, tracking and consent\n\nDo not copy JavaScript from the source site. WordPress plugins and themes\ninject their own runtime: jQuery, sliders, lightboxes, form and cookie\nplugins, page builders (Elementor, Breakdance, Divi), emoji and AJAX calls to\n`admin-ajax.php` or `/wp-json/`. It loads from, or posts to, a site that is\nabout to be switched off. Inventory every `<script>` on representative source\npages (head and body, inline and external, and which plugin or theme it\nbelongs to), then decide each one:\n\n| Source script | Decision |\n| --- | --- |\n| WordPress core, theme or plugin runtime | Never copy. Rebuild what it did with native blocks, Forms or settings, or report a platform gap (see \"Report platform gaps\"). |\n| Cookie plugin (CookieYes, Complianz, Cookiebot, Borlabs, …) | Never copy. Enable the site's cookie banner (`cookie_consent.enabled: true`) with Swedish or local text and the privacy policy URL. |\n| Analytics or advertising tag in the Integrations catalog (GA4, GTM, Meta Pixel, LinkedIn, TikTok, Hotjar, Clarity, Pinterest, Snap, Reddit, X, Microsoft Ads, HubSpot, Plausible, Fathom, Matomo) | Carry over the ID only: enable the Integrations app with the source's ID (`list_apps`, `update_app`). Enable the cookie banner; analytics and advertising integrations wait for the visitor's consent. Never paste the vendor snippet. |\n| Tracking tag the catalog does not cover | Only with the owner's approval, in `cookie_consent.scripts_optional` with the banner enabled. Never in `scripts_head`, `scripts_body_end`, `scripts_necessary`, pages or partials. |\n| Partner widget or embed the page needs to work | Prefer a native block or an iframe from an allowlisted host. Keep custom code minimal, record why, and do not load it from the source site. |\n\nAsk the owner to confirm the analytics IDs; never invent or reuse one from\nanother market. Before launch, `get_migration_launch_report` must show\n`scripts.status: \"clean\"`: it fails on copied source-platform scripts\n(`source_runtime_script`), tracking outside consent\n(`tracking_outside_consent`) and analytics or advertising integrations without\nthe banner (`tracking_without_consent_banner`). In the hosted preview, check\nthat no request goes to the source host or `/wp-content/`, no tracking request\nfires before consent, and the tags fire after \"accept all\".\n\n## Evidence record (repeat per recipe, viewport and state)\n\n- Source URL/date and target URL; Page/template IDs; exact publication ID.\n- Runtime/Core version and saved content/config revision.\n- Actual viewport `innerWidth` and height; scroll/menu/anchor state.\n- Source and target screenshot paths; measured expected and actual geometry.\n- Capability mapping: block type, field, read-back value and computed result.\n- Separate PASS / FAIL / UNVERIFIED for routing, content, visual, assets,\n keyboard/interaction and delivery; reviewer and timestamp.\n- Visible deviation, specific user benefit, accepted scope and rationale.\n\nFilled examples:\n\n| Observation | Technical result | Visual decision |\n| --- | --- | --- |\n| Source video overflows a 390px viewport; native video fits and shows the entire frame | Width 390px, original aspect preserved; captures reviewed | PASS: deliberate improvement in mobile usability |\n| Source has four cards and turquoise hero at 1280px; target has three plain cards and no color panel | HTTP 200, no overflow, all content is blocks | FAIL: brand/layout lost; those technical facts cannot attest visual fidelity |\n| Heading round-trip retains 48 but computed size stays 32 | Save succeeds | FAIL: find ignored field/cascade before bulk conversion |\n\nKeep imported, saved, locally checked, hosted-preview checked, published and\ntraffic-cut-over distinct. Template/theme changes invalidate affected acceptance;\nnever attach old evidence to a new publication. `record_migration_seo_acceptance`\nbooleans are reviewer attestations, not automated visual proof. Do not set them\nfrom HTTP status, native-block count or overflow alone. Report a completed\npre-cutover scope honestly when DNS or integrations remain out of scope.\n\n## Complete census and visual signoff\n\nThe initial route census must combine source sitemaps, internal links, archive\npagination, CMS inventory and known utility routes. Include homepage, archives,\n404/search behavior and routes outside the sitemap where present. In preserve mode, do not invent\nfeatures absent from the source. In renew mode, include additions in the agreed brief. Assign every route a recipe, template override,\ncomposition or documented exception. Inventory the whole shell: content outside\n`main`, header, footer, service navigation and archive heroes are not optional.\n\nDerive required evidence from that census. For preserve, pair every in-scope source/target route\nat mobile and desktop; for renew, compare every target route to its approved\nprototype and account for the source content/URL mapping; add each unique component, breakpoint and interaction\nstate. Inspect page-specific media, lists, tables, embeds, CTAs, anchors and related\ncontent. One representative article cannot attest every article's content.\n\nA coverage row records route, recipe, component, state, actual viewport dimensions,\nsource revision, target content/config/Core/publication revision, paired screenshot\nreferences, reviewer, review time and result. All rows start `UNVERIFIED`.\nKeep independent results for content, links, list cardinality, full membership,\nsequence, appearance, keyboard behavior and delivery. Screenshots must be inspected;\nimage diffs and contact sheets assist review and do not prove aesthetics.\n\nFor menus capture source and target closed, open and scrolled states, nested groups\nif present, short landscape, focus order, Escape, link activation and breakpoint\ntransitions. For TOCs capture top/middle/bottom and anchor landings. Include tabs,\naccordions and empty/loading/error/success states where applicable. Separate mobile\nand desktop menu compositions require separate evidence; shared links may be reused\nbut one variant's screenshots do not attest the other.\n\nReport denominators separately: routes/content, paired visual pages, recipes,\ncomponents and states. Missing an archive, open menu, footer or a unique embed leaves\ncoverage incomplete even if all captured screenshots pass. Shared block, theme,\npartial or template changes invalidate every dependent row; content changes\ninvalidate that route and dependent listings. An old revision cannot approve a new\none. Source availability failures remain `UNVERIFIED`, not silently excluded.\n\nVisual signoff is against the chosen intent: source fidelity for preserve,\napproved design/content for renew. It requires zero uncovered required surfaces\nand zero unexplained regressions against that reference. `FAIL` or `UNVERIFIED` blocks that signoff, even when a separately scoped\ntechnical publication has approval. An intentional deviation needs exact before/\nafter evidence, specific user benefit and accepted scope; “cleaner” is not a reason.\n\nPreserve-mode examples from a moving-services migration:\n\n| Surface | Passing evidence | Separate visual result |\n| --- | --- | --- |\n| Mobile menu | Keyboard focus and Escape work | FAIL: header moves, source service group is missing, open-menu composition differs |\n| Category archive | Every destination URL exists | FAIL: source sequence, card anatomy and columns differ |\n| Article body | All text imported | UNVERIFIED: unique embed and footer were never compared |\n\nDo not report “all design checked” from a viewport or recipe sample. Keep source\nlink-order comparisons and paired contact sheets with the report. The same contract\napplies to WordPress, URL, Astro and multisite migration; do not create separate,\nweaker checklists for individual import mechanisms.\n\n\n## Navigation and empty-media variants\n\nStructured research imports must use explicit import authority, not ordinary\neditor API writes. Core 0.2.95/MCP 0.45.74 support `create_page(write_mode:\n\"import\")`, `read_page_authority` and atomic `import_page_fields`. Preserve\napproved answers and owner-cleared nulls. Add historical anchors to existing\nkeyed items as individual leaves; never send replacement arrays to add one\nfield. Use the same selected Site/version and the current authority revision.\nStop on 409 rather than overriding or resetting provenance. See the Pages tools\nguide for schema opt-in, version availability and path escaping.\n\nInventory first-step input banners independently of final submission providers.\nPreserve their fields, required/minimum validation, service tabs and direct links,\nmobile collapsed/open states and navigation even when the final integration is\ndeferred. Core 0.2.40 provides native `core/navigation_form` and mixed `core/tabs`.\nRead their schema instead of rebuilding them in HTML or reintroducing a provider\nblock. Defaults are same-origin and tab-scoped; never place addresses in URLs or\nclaim cross-origin prefill. The receiving form must explicitly accept the fields.\n\nCompare listing rows with both real and missing images. Use shared post-card\nplaceholder settings for theme decoration, preserving real featured images and\nexplicit `show_image: false`. Compare actual footer link height plus gap, wrapping,\nfocus and column breakpoints; compact navigation density is an intentional option.\nVerify native preview and static output on the agreed viewport boundaries.\n",
|
|
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> **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\").\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\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`\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\": \"#ffffff\", \"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 (template_capabilities_version ≥ 0.16.5): when N equal\ncards don't divide by the grid's column count (5 cards, 3 cols), set\n`last_row: 'center'` on the `core/grid` — the orphan row auto-centers.\nNEVER invent a \"wide\" variant of one peer card to fill the hole, and\ndon't hand-roll 6-column CSS tricks; both distort content to patch\nlayout. On older sites, pick a column count that divides N.\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. Deploy\n\nWhen the user approves, verify read_publishing_readiness and save the intended\nVersion before deploying. A published CMS status is not a live deployment.\n```\ntrigger_deploy\nget_deploy_status job_id=<id>\n```\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",
|
|
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### 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 on the DB-live preview\n(a reused `get_preview_link`; browser tool + DOM reads), fix what you find,\nreload, re-check. No re-deploy between fixes — the preview renders from the DB.\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 or merge. Wait for an explicit \"looks good, ship it.\"\n\n### 7. Merge + deploy\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```\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\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",
|
package/dist/server.js
CHANGED
|
@@ -105,6 +105,7 @@ function effectFor(name) {
|
|
|
105
105
|
|| name === 'set_email_settings'
|
|
106
106
|
|| name === 'delete_email_settings'
|
|
107
107
|
|| name === 'send_test_email'
|
|
108
|
+
|| name === 'retry_form_intake_notification'
|
|
108
109
|
|| name === 'set_incoming_email_forwarding')
|
|
109
110
|
return 'admin';
|
|
110
111
|
// Compute an answer and write nothing, like the preview_* tools.
|
package/dist/tools/forms.js
CHANGED
|
@@ -135,6 +135,18 @@ export const formTools = [
|
|
|
135
135
|
return ok(res);
|
|
136
136
|
}),
|
|
137
137
|
},
|
|
138
|
+
{
|
|
139
|
+
name: 'list_form_intake_responses',
|
|
140
|
+
description: 'Administrator-only private questionnaire responses linked to one Page. Includes partial answers and notification status; never use for public profile content. Installation access requires forms:intake:read and is restricted to its own form.',
|
|
141
|
+
inputSchema: { form_id: z.string(), page_id: z.string(), after_id: z.string().optional() },
|
|
142
|
+
handler: withErrorBoundary(async ({ form_id, page_id, after_id }, { client, siteId }) => ok(await client.get(siteId, `forms/${encodeURIComponent(form_id)}/intake-responses`, { page_id, after_id }))),
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: 'retry_form_intake_notification',
|
|
146
|
+
description: 'Explicitly retry one private questionnaire notification after a confirmed failure, or dispatch its pending outbox event. Never resends accepted, in-flight or uncertain delivery. Requires site administrator access; installation callers need forms:intake:read and email:send.',
|
|
147
|
+
inputSchema: { form_id: z.string(), page_id: z.string(), submission_id: z.string().regex(/^[a-f0-9]{64}$/), event: z.enum(['first_answer', 'complete']) },
|
|
148
|
+
handler: withErrorBoundary(async ({ form_id, page_id, submission_id, event }, { client, siteId }) => ok(await client.post(siteId, `forms/${encodeURIComponent(form_id)}/intake-responses`, { submission_id, event }, { page_id }))),
|
|
149
|
+
},
|
|
138
150
|
{
|
|
139
151
|
name: 'list_form_submissions',
|
|
140
152
|
description: 'List submissions received for a form, newest first, cursor-paginated (cap 200 per page). Admins also see each submission\'s webhook delivery status. Use this to help a customer triage inbound contact / lead submissions; read_form_submission reads one.',
|
package/dist/tools/pages.js
CHANGED
|
@@ -9,6 +9,25 @@ function v(version) {
|
|
|
9
9
|
return version ? { version } : undefined;
|
|
10
10
|
}
|
|
11
11
|
export const pageTools = [
|
|
12
|
+
{
|
|
13
|
+
name: 'read_page_authority',
|
|
14
|
+
description: 'Inspect saved custom-field authority before an import. Returns a revision and source for explicit schema leaf paths, without values or actor identities. Keyed items use programs/@stable-id/name; escape ~ as ~0 and / as ~1. Requires editor access. An absent source is explicitly unattributed, never inferred from ordinary read_page.',
|
|
15
|
+
inputSchema: { page_id: z.string(), paths: z.array(z.string().min(1).max(1000)).min(1).max(100), version: versionParam },
|
|
16
|
+
handler: withErrorBoundary(async (args, { client, siteId }) => ok(await client.get(siteId, `pages/${encodeURIComponent(args.page_id)}/authority`, { paths: JSON.stringify(args.paths), ...v(args.version) }))),
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: 'import_page_fields',
|
|
20
|
+
description: 'Save low-priority research/import changes to individual custom-field leaves. Requires the current revision from read_page_authority and explicit import in each field’s writable_by. Preserves unspecified fields and keyed-list items; never downgrades protected answers. Atomic 409 on conflicts, including an existing unsaved draft. No arbitrary provenance, item insertion/removal, or URL changes. This saves immediately; it does not publish the website.',
|
|
21
|
+
inputSchema: {
|
|
22
|
+
page_id: z.string(), base_revision: z.string().regex(/^[a-f0-9]{64}$/),
|
|
23
|
+
changes: z.array(z.object({ path: z.string().min(1).max(1000), value: z.json() }).strict()).min(1).max(100),
|
|
24
|
+
source_url: z.string().url().max(2000).optional(), import_run_id: z.string().min(1).max(200).optional(), version: versionParam,
|
|
25
|
+
},
|
|
26
|
+
handler: withErrorBoundary(async (args, { client, siteId }) => {
|
|
27
|
+
const { page_id, version, ...body } = args;
|
|
28
|
+
return ok(await client.post(siteId, `pages/${encodeURIComponent(page_id)}/import`, body, v(version)));
|
|
29
|
+
}),
|
|
30
|
+
},
|
|
12
31
|
{
|
|
13
32
|
name: 'list_pages',
|
|
14
33
|
description: 'List pages on the active site. Returns id, title, slug, status, and SEO summary (no html_content by default — use read_page or batch_read_pages for body content). Supports filtering by status and forward-cursor pagination. Pass full=true to include html_content + blocks in the response.',
|
|
@@ -89,6 +108,7 @@ export const pageTools = [
|
|
|
89
108
|
'Returns the created page (blocks for blocks-mode, html_content for html-mode) including ' +
|
|
90
109
|
'`url` (the resolved live URL).',
|
|
91
110
|
inputSchema: {
|
|
111
|
+
write_mode: z.enum(['editor', 'import']).optional().describe('Default editor. Use import for research/migration seeding so later owner answers outrank it. Fields must explicitly allow import; server assigns provenance. Does not reclassify existing pages.'),
|
|
92
112
|
title: z.string().min(1),
|
|
93
113
|
content_type: z.string().optional().describe('Content type ID; defaults to page.'),
|
|
94
114
|
fields: z.record(z.string(), z.unknown()).optional().describe('Custom field values defined by the content type.'),
|
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -9,6 +9,16 @@ Confirm the organization, site and version first. Personal OAuth calls require
|
|
|
9
9
|
the `site_context` returned by `select_site`. Read `read_site_instructions`
|
|
10
10
|
and the selected site's enabled App guidance before changing content.
|
|
11
11
|
|
|
12
|
+
Research and migration seeding are not editor confirmations. On Core 0.2.95 /
|
|
13
|
+
MCP 0.45.74+, create structured research Pages with `write_mode: "import"` and
|
|
14
|
+
explicit schema `writable_by: ["import", ...]`. For existing answers, discover
|
|
15
|
+
`read_page_authority` and `import_page_fields`: read the revision, then change
|
|
16
|
+
only named leaves using stable keyed-item paths. This import saves immediately;
|
|
17
|
+
the ordinary draft-writing recipe below still applies to editorial writes.
|
|
18
|
+
Never interpret absent provenance in `read_page` as an unprotected value, reset
|
|
19
|
+
an owner's source, or retry a 409 using editor authority. Existing API-attributed
|
|
20
|
+
data requires separate review; it is not automatically downgraded.
|
|
21
|
+
|
|
12
22
|
|
|
13
23
|
> **The buffer model (draft writes).** Every content write in this recipe
|
|
14
24
|
> (pages, blocks and partials) lands in an unsaved per-doc
|
|
@@ -303,6 +303,15 @@ weaker checklists for individual import mechanisms.
|
|
|
303
303
|
|
|
304
304
|
## Navigation and empty-media variants
|
|
305
305
|
|
|
306
|
+
Structured research imports must use explicit import authority, not ordinary
|
|
307
|
+
editor API writes. Core 0.2.95/MCP 0.45.74 support `create_page(write_mode:
|
|
308
|
+
"import")`, `read_page_authority` and atomic `import_page_fields`. Preserve
|
|
309
|
+
approved answers and owner-cleared nulls. Add historical anchors to existing
|
|
310
|
+
keyed items as individual leaves; never send replacement arrays to add one
|
|
311
|
+
field. Use the same selected Site/version and the current authority revision.
|
|
312
|
+
Stop on 409 rather than overriding or resetting provenance. See the Pages tools
|
|
313
|
+
guide for schema opt-in, version availability and path escaping.
|
|
314
|
+
|
|
306
315
|
Inventory first-step input banners independently of final submission providers.
|
|
307
316
|
Preserve their fields, required/minimum validation, service tabs and direct links,
|
|
308
317
|
mobile collapsed/open states and navigation even when the final integration is
|