@typeroll/mcp-server 0.45.83 → 0.45.85
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 +9 -8
- package/dist/tools/deploy.js +1 -1
- package/dist/tools/extensions.js +1 -1
- package/dist/tools/forms.js +2 -2
- package/dist/tools/media.js +6 -6
- package/dist/tools/settings.js +5 -2
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/skills/tr-brand.md +23 -0
- package/skills/tr-migrate-wp.md +23 -0
- package/skills/tr-new-site.md +23 -0
- package/skills/tr-starter-design.md +23 -0
- package/workspace-templates/qa/checklist.md +6 -0
- package/workspace-templates/scripts/README.md +9 -0
- package/workspace-templates/scripts/typeroll/check_heading_spacing.cjs +49 -0
- package/workspace-templates/workflows/design.md +23 -0
- package/workspace-templates/workflows/migration.md +23 -0
package/dist/tools/deploy.js
CHANGED
|
@@ -41,7 +41,7 @@ export const deployTools = [
|
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
43
|
name: 'get_deploy_status',
|
|
44
|
-
description: "Status of a single deploy job. Optional top-level build_progress reports the exact task status, attempt, step and completed/total files; not an overall percentage or ETA. Returns { job, queued_for_seconds, queue_timeout_seconds }. job.status is one of queued | running | succeeded | failed. On a failed build, job.error names the failed step and the cause the step printed, job.failure.code is the reported code (e.g. media_build_process_exit_1) and job.failure.diagnostic holds { cause, lines } (the last 40 lines that step printed, credentials removed). On the Cloudflare build engine job.failure.provider_log also holds { step, build_id, summary, lines } from the provider's build log (last 60 lines). Read them before retrying; retrying does not fix a cause that is in the content or the build engine. Polling cadence: ~5s for queued/running. job.render_report, when present, reports actual HTML mode (full/partial), rendered, reused, total and removed route counts plus a fallback reason. From Core 0.2.17, custom blocks and consumed image metadata are tracked per route; older caches require one full build. It does not measure media or provider startup savings. job.coordinator, when present, reports the last completed CMS phase and duration_ms for that attempt; external build waiting is excluded. Keep observing the same job across checkpoints; do not trigger duplicate deploys. A job stuck in 'queued' for longer than queue_timeout_seconds (default 300s) auto-flips to failed with phase='queue_timeout' — so a single poll past that timestamp returns the terminal state and you can stop polling.",
|
|
44
|
+
description: "Status of a single deploy job. Optional top-level build_progress reports the exact task status, attempt, step and completed/total files; not an overall percentage or ETA. Returns { job, queued_for_seconds, queue_timeout_seconds }. job.status is one of queued | running | succeeded | failed. On a failed build, job.error names the failed step and the cause the step printed, job.failure.code is the reported code (e.g. media_build_process_exit_1) and job.failure.diagnostic holds { cause, lines } (the last 40 lines that step printed, credentials removed). On the Cloudflare build engine job.failure.provider_log also holds { step, build_id, summary, lines } from the provider's build log (last 60 lines). Read them before retrying; retrying does not fix a cause that is in the content or the build engine. job.warnings, when present, lists non-blocking findings of a deploy that still ran, for example a media file left out of the publication because it could not be published safely (an SVG that could not be sanitized); report them to the user. Polling cadence: ~5s for queued/running. job.render_report, when present, reports actual HTML mode (full/partial), rendered, reused, total and removed route counts plus a fallback reason. From Core 0.2.17, custom blocks and consumed image metadata are tracked per route; older caches require one full build. It does not measure media or provider startup savings. job.coordinator, when present, reports the last completed CMS phase and duration_ms for that attempt; external build waiting is excluded. Keep observing the same job across checkpoints; do not trigger duplicate deploys. A job stuck in 'queued' for longer than queue_timeout_seconds (default 300s) auto-flips to failed with phase='queue_timeout' — so a single poll past that timestamp returns the terminal state and you can stop polling.",
|
|
45
45
|
inputSchema: { job_id: z.string(), include_provider: z.boolean().optional().describe('Also read the exact Git commit and Cloudflare build stages for a customer Git publication. Does not return build secrets.') },
|
|
46
46
|
handler: withErrorBoundary(async (args, { client, siteId }) => {
|
|
47
47
|
const res = await client.get(siteId, `deploys/${encodeURIComponent(args.job_id)}`);
|
package/dist/tools/extensions.js
CHANGED
|
@@ -142,7 +142,7 @@ export const developerExtensionTools = [
|
|
|
142
142
|
},
|
|
143
143
|
{
|
|
144
144
|
name: 'read_developer_extension', noSite: true,
|
|
145
|
-
description: 'Read one of your organization\'s Extensions with all saved releases and their status (draft, review, published, deprecated, revoked). Requires an organization API key.',
|
|
145
|
+
description: 'Read one of your organization\'s Extensions with all saved releases and their status (draft, review, published, deprecated, revoked). extension.developer_org_id is the value of the developer_org_id claim in every token Typeroll issues for its installations: provider backends must require it in addition to aud. id_reservation is reserved, unreserved (a registration that predates reservations; any update claims it) or held_by_other (another developer organization holds this id). Requires an organization API key.',
|
|
146
146
|
inputSchema: { extension_id: z.string().min(1) },
|
|
147
147
|
handler: withErrorBoundary(async (args, { client }) => ok(await client.developer('GET', ext(args.extension_id)))),
|
|
148
148
|
},
|
package/dist/tools/forms.js
CHANGED
|
@@ -149,7 +149,7 @@ export const formTools = [
|
|
|
149
149
|
},
|
|
150
150
|
{
|
|
151
151
|
name: 'list_form_submissions',
|
|
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.',
|
|
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. Visitor IP addresses are not stored or returned; completed submissions past the site\'s form_submission_retention_days (read_site_settings) are deleted and never listed. Use this to help a customer triage inbound contact / lead submissions; read_form_submission reads one.',
|
|
153
153
|
inputSchema: {
|
|
154
154
|
form_id: z.string(),
|
|
155
155
|
limit: z.number().int().min(1).max(200).optional(),
|
|
@@ -163,7 +163,7 @@ export const formTools = [
|
|
|
163
163
|
},
|
|
164
164
|
{
|
|
165
165
|
name: 'read_form_submission',
|
|
166
|
-
description: 'Read one submission of a form by id (from list_form_submissions): the submitted values, status (partial or complete), step and timestamps. Admins also see its webhook delivery status, as in the portal.',
|
|
166
|
+
description: 'Read one submission of a form by id (from list_form_submissions): the submitted values, status (partial or complete), step and timestamps, with expires_at when the submission is due for deletion. Admins also see its webhook delivery status, as in the portal.',
|
|
167
167
|
inputSchema: {
|
|
168
168
|
form_id: z.string(),
|
|
169
169
|
submission_id: z.string(),
|
package/dist/tools/media.js
CHANGED
|
@@ -67,8 +67,8 @@ export const mediaTools = [
|
|
|
67
67
|
name: 'create_upload_url',
|
|
68
68
|
description: 'Mint a 5-min signed PUT URL for direct-to-R2 upload. After the upload completes, the CDN url is what you reference in <img src="…">. REQUIRED follow-up: call `finalize_media` after the PUT succeeds — without it the original ships with no Cache-Control (Lighthouse will flag it) and no responsive variants get generated. The response carries `finalize_url` as a reminder. This signed-PUT path is the INTEGRITY-SAFE way to upload bytes from disk or a generated asset — a direct `curl --data-binary @file` sends the bytes straight to R2 without transcribing them through the model, so it can\'t be corrupted the way a large `upload_media_inline` base64 blob can (ESPECIALLY use this, not inline, for SVG/logos). For a public source URL, `upload_media_from_url` is the one-call equivalent; reserve `upload_media_inline` for tiny payloads only.',
|
|
69
69
|
inputSchema: {
|
|
70
|
-
filename: z.string().min(1),
|
|
71
|
-
content_type: z.string().min(1).describe('e.g. "image/png", "image/jpeg", "application/pdf"'),
|
|
70
|
+
filename: z.string().min(1).describe('Should end in an extension of content_type (e.g. logo.png with image/png). When the extension is missing or belongs to another type, the type\'s extension is appended (logo → logo.png, photo.html → photo.html.png); the response returns the stored filename. Unsupported types answer 415.'),
|
|
71
|
+
content_type: z.string().min(1).describe('e.g. "image/png", "image/jpeg", "application/pdf". SVGs are sanitized at finalize; an SVG that cannot be sanitized is refused.'),
|
|
72
72
|
size: z.number().int().nonnegative().optional(),
|
|
73
73
|
alt_text: z.string().optional(),
|
|
74
74
|
},
|
|
@@ -82,7 +82,7 @@ export const mediaTools = [
|
|
|
82
82
|
description: 'Fetch an image (or PDF) from any public URL and push it to the site\'s media library in one call. The MCP server downloads the source from verified public HTTP(S) destinations; private addresses and unsafe redirects are refused. Integrity-safe: the bytes are streamed, never transcribed as base64 through the model, so (unlike a large `upload_media_inline` payload) they can\'t be silently corrupted in transit. Returns { media_id, cdn_url, filename }.',
|
|
83
83
|
inputSchema: {
|
|
84
84
|
source_url: z.string().url().describe('Public URL to download the image from.'),
|
|
85
|
-
filename: z.string().optional().describe('Override the filename used on R2. Defaults to the last path segment of source_url.'),
|
|
85
|
+
filename: z.string().optional().describe('Override the filename used on R2. Defaults to the last path segment of source_url. When its extension does not match the content type, the type\'s extension is appended; the response returns the stored filename.'),
|
|
86
86
|
content_type: z.string().optional().describe('Override the inferred content type (e.g. when source_url has no extension).'),
|
|
87
87
|
alt_text: z.string().optional(),
|
|
88
88
|
},
|
|
@@ -131,7 +131,7 @@ export const mediaTools = [
|
|
|
131
131
|
name: 'upload_media_inline',
|
|
132
132
|
description: 'Upload an image (or PDF) from base64-encoded bytes. ⚠️ Use ONLY for tiny payloads (a few KB). The base64 string crosses the model/tool boundary as text, where a larger blob can be SILENTLY CORRUPTED in transit — a few mutated chars yield a broken-but-valid-looking file that uploads with no error and the right size, and only fails when rendered (this has bitten a ~12KB logo SVG: half the wordmark vanished). For anything non-trivial, and ALWAYS for SVG/logos or assets you generated, prefer create_upload_url + a direct curl --data-binary PUT of the file bytes (bytes never transcribed → verified byte-identical), or upload_media_from_url (bytes fetched from a URL). After uploading any generated asset, VERIFY it (render it, or byte-diff against the source) before referencing it. Returns { media_id, cdn_url, filename }.',
|
|
133
133
|
inputSchema: {
|
|
134
|
-
filename: z.string().min(1),
|
|
134
|
+
filename: z.string().min(1).describe('When its extension does not match content_type, the type\'s extension is appended; the response returns the stored filename.'),
|
|
135
135
|
content_type: z.string().min(1).describe('e.g. "image/png", "image/jpeg", "application/pdf"'),
|
|
136
136
|
data_base64: z.string().min(1).describe('The file bytes, base64-encoded (without a data: URL prefix).'),
|
|
137
137
|
alt_text: z.string().optional(),
|
|
@@ -172,7 +172,7 @@ export const mediaTools = [
|
|
|
172
172
|
return ok({
|
|
173
173
|
media_id: mint.media_id,
|
|
174
174
|
cdn_url: mint.cdn_url,
|
|
175
|
-
filename: args.filename,
|
|
175
|
+
filename: mint.filename ?? args.filename,
|
|
176
176
|
content_type: args.content_type,
|
|
177
177
|
size_bytes: buf.byteLength,
|
|
178
178
|
finalize: finalizeResult,
|
|
@@ -182,7 +182,7 @@ export const mediaTools = [
|
|
|
182
182
|
},
|
|
183
183
|
{
|
|
184
184
|
name: 'update_media',
|
|
185
|
-
description: 'Patch a media item\'s alt text, filename or public path. A public path change is applied at the next publication.',
|
|
185
|
+
description: 'Patch a media item\'s alt text, filename or public path. A rename must be correct: a new filename or public path must end in an extension matching the file\'s type, and nothing is appended (400 media_extension_mismatch otherwise). A public path change is applied at the next publication.',
|
|
186
186
|
inputSchema: {
|
|
187
187
|
media_id: z.string(),
|
|
188
188
|
alt_text: z.string().optional(),
|
package/dist/tools/settings.js
CHANGED
|
@@ -28,7 +28,7 @@ export const settingsTools = [
|
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
30
|
name: 'read_site_settings',
|
|
31
|
-
description: "Read every site setting: name, tagline, logo, favicon/app icons, colors, fonts, contact info, social links, URL trailing-slash policy, iframe host allowlist, default SEO suffix/description, default_og_image, twitter_handle, organization (JSON-LD), language, robots_txt, image_sizes_default, cookie_consent, plus the scriptable surfaces scripts_head, scripts_body_end, and custom_css, `urls` (including `urls.staging`, the site's staging_url) and `render` (the site's platform render version, the latest version and what upgrading would change). Pass `version` to read a branch's settings (with copy-on-write chain-fallback to main for fields the branch hasn't overridden).",
|
|
31
|
+
description: "Read every site setting: name, tagline, logo, favicon/app icons, colors, fonts, contact info, social links, URL trailing-slash policy, iframe host allowlist, default SEO suffix/description, default_og_image, twitter_handle, organization (JSON-LD), language, robots_txt, image_sizes_default, cookie_consent, plus the scriptable surfaces scripts_head, scripts_body_end, and custom_css, form_submission_retention_days (days completed form submissions are kept before deletion; null = kept until deleted; site-wide, not per version), `urls` (including `urls.staging`, the site's staging_url) and `render` (the site's platform render version, the latest version and what upgrading would change). Pass `version` to read a branch's settings (with copy-on-write chain-fallback to main for fields the branch hasn't overridden).",
|
|
32
32
|
inputSchema: {
|
|
33
33
|
version: versionParam,
|
|
34
34
|
},
|
|
@@ -47,6 +47,7 @@ export const settingsTools = [
|
|
|
47
47
|
'scripts_head / scripts_body_end / custom_css and cookie_consent scripts ARE writable here — useful for a global stylesheet or analytics tag across all pages. ' +
|
|
48
48
|
'Social sharing and structured data: default_og_image, twitter_handle, organization ({name, logo, same_as[]}, replaced as a whole; null clears). ' +
|
|
49
49
|
'staging_url is stored on the Site (not per version), so it applies whichever `version` you pass. ' +
|
|
50
|
+
'form_submission_retention_days is also site-wide and deletes data: set it only when the user asks for a retention period. ' +
|
|
50
51
|
'Pass `version` to scope the write to a branch (copy-on-write) instead of main — the right way to brand/recolor a site inside a redesign branch (colors, fonts, logo, custom_css) without touching the live settings. Omit it to write main.',
|
|
51
52
|
inputSchema: {
|
|
52
53
|
version: versionParam,
|
|
@@ -81,8 +82,10 @@ export const settingsTools = [
|
|
|
81
82
|
same_as: z.array(z.string()).max(50).optional().describe('Profile URLs (LinkedIn, X, Facebook, …) emitted as sameAs.'),
|
|
82
83
|
}).strict().nullable().optional().describe('Organization schema.org JSON-LD emitted on every page. Replaces the stored value as a whole; null (or all-empty) clears it.'),
|
|
83
84
|
staging_url: z.string().nullable().optional().describe('URL of a staging environment for this site, reported as urls.staging. Stored on the Site, not per version. Trailing slashes are dropped; empty string or null clears.'),
|
|
85
|
+
form_submission_retention_days: z.number().int().min(30).max(3650).nullable().optional().describe('Opt-in deletion of completed form submissions this many days (30–3650) after they were completed, on every form of the site, including existing submissions: those already older are deleted immediately and cannot be restored. null (the default) keeps submissions until someone deletes them. Site administrators only (never an Extension credential); stored on the Site, not per version. The response reports form_submission_retention { updated, deleted }. Partial submissions keep their form\'s partial_ttl_days.'),
|
|
84
86
|
render_version: z.number().int().min(1).optional().describe('Platform render version. Platform output changes ship as new versions so existing sites never change look on their own. Upgrade only after previewing (get_preview_link render_version) and with the user\'s approval; the tr-upgrade-rendering skill compares every page. Lowering it returns to an earlier version.'),
|
|
85
|
-
|
|
87
|
+
typography: z.object({ ratio_mobile: z.number().min(1.1).max(1.5).optional(), ratio_desktop: z.number().min(1.1).max(1.5).optional(), rhythm: z.number().min(0.5).max(2).optional(), line_length: z.number().min(40).max(90).optional() }).strict().optional().describe('Fluid design controls (render version 10+). Base size is fonts.size_base. Replaces typography as a whole; omitted controls use defaults.'),
|
|
88
|
+
design_scales: z.record(z.string(), z.record(z.string(), z.union([z.string(), z.object({ mobile: z.string().optional(), tablet: z.string().optional(), laptop: z.string().optional(), desktop: z.string().optional(), wide: z.string().optional() }).strict()]))).optional().describe('Site token scales (replaced as a whole): type_size xs/sm/md/lg/xl/2xl/3xl; space none/xs/sm/md/lg/xl in px/rem/em or clamp(min, length + vw, max), or responsive maps; leading tight/snug/normal/relaxed/loose as unitless 1–2. Omitted steps use platform defaults.'),
|
|
86
89
|
colors: z.record(z.string(), z.string().nullable()).optional().describe('Palette: primary, secondary, accent, background, surface, surface_hover, text, text_light (shallow-merged). Optional on_primary, on_secondary, on_accent set the text colour on that theme colour (buttons, badges, tabs, call-to-action sections, styles using the on_* tokens) as a hex colour such as "#ffffff"; null returns to the automatic black or white (chosen by APCA from render version 8, by the WCAG 2 ratio before). A chosen on_* colour applies on every render version. The response lists `on_colors` with each one\'s APCA Lc and WCAG 2 ratio; a choice below APCA Lc 60 (button text, 16px/600) is kept and returned as a warning. Prefer the automatic choice unless the brand specifies a colour.'),
|
|
87
90
|
fonts: z.record(z.string(), z.unknown()).optional().describe('heading, body, mono (Google Fonts family or "system"), size_base (px) and smoothing: "auto" (browser default) or "antialiased" (thinner text on macOS, for light text on dark backgrounds); null clears smoothing. Unset smoothing is auto from render version 6.'),
|
|
88
91
|
contact: z
|
package/dist/version.js
CHANGED
package/package.json
CHANGED
package/skills/tr-brand.md
CHANGED
|
@@ -367,3 +367,26 @@ Open in browser. Check:
|
|
|
367
367
|
phantom margins inside the wrapper — visible as white bands above and
|
|
368
368
|
below the photo. Zero it explicitly in your figure CSS:
|
|
369
369
|
`.my-figure img { margin: 0; border-radius: 0 }`.
|
|
370
|
+
|
|
371
|
+
## Fluid typography and component rhythm
|
|
372
|
+
|
|
373
|
+
Core 0.2.114 / MCP 0.45.85 / capabilities 0.55.0 adds responsive scales.
|
|
374
|
+
Check the connected versions before using this contract. New sites use render
|
|
375
|
+
version 10. For an existing site, test a Version first; upgrading rendering
|
|
376
|
+
and replacing existing roles are separate, deliberate choices.
|
|
377
|
+
|
|
378
|
+
Use `fonts.size_base` (default 16), `typography.ratio_mobile` (1.2),
|
|
379
|
+
`typography.ratio_desktop` (1.25), `typography.rhythm` (1), and
|
|
380
|
+
`typography.line_length` (65 ch). Set the controls once, then use named type,
|
|
381
|
+
spacing and line-height steps. `apply_standard_styles` uses those steps;
|
|
382
|
+
`overwrite: true` resets existing roles, so review their current values first.
|
|
383
|
+
Explicit sizes and breakpoint overrides still win and are not silently migrated.
|
|
384
|
+
|
|
385
|
+
Heading role margins belong to editorial flow. Component titles use their own
|
|
386
|
+
padding/gap, and opening headings in containers/sections add no top margin.
|
|
387
|
+
Use role-linked `core/heading` size `auto` or `theme`; explicit hero sizes remain
|
|
388
|
+
deliberate exceptions. Do not compensate with per-card negative margins.
|
|
389
|
+
Compare 375, 768 and 1280px in Site settings → Design and in a full site preview.
|
|
390
|
+
After a rendering upgrade/rebuild, recheck cards, boxed headings, leading text
|
|
391
|
+
and webfonts. Boxes should get their outer air from padding. Keep exceptions
|
|
392
|
+
in decisions.md, and run lint_design_tokens before launch.
|
package/skills/tr-migrate-wp.md
CHANGED
|
@@ -429,3 +429,26 @@ DNS, explicitly prepare the last published snapshot for the future hosts with
|
|
|
429
429
|
traffic separately. If newer content is published while the candidate waits,
|
|
430
430
|
prepare again before cutover; do not overwrite it with an outdated snapshot. Respect preserve/renew choices for owner-supplied facts;
|
|
431
431
|
"timeless" wording must not invent recurring events or erase preserved copy.
|
|
432
|
+
|
|
433
|
+
## Fluid typography and component rhythm
|
|
434
|
+
|
|
435
|
+
Core 0.2.114 / MCP 0.45.85 / capabilities 0.55.0 adds responsive scales.
|
|
436
|
+
Check the connected versions before using this contract. New sites use render
|
|
437
|
+
version 10. For an existing site, test a Version first; upgrading rendering
|
|
438
|
+
and replacing existing roles are separate, deliberate choices.
|
|
439
|
+
|
|
440
|
+
Use `fonts.size_base` (default 16), `typography.ratio_mobile` (1.2),
|
|
441
|
+
`typography.ratio_desktop` (1.25), `typography.rhythm` (1), and
|
|
442
|
+
`typography.line_length` (65 ch). Set the controls once, then use named type,
|
|
443
|
+
spacing and line-height steps. `apply_standard_styles` uses those steps;
|
|
444
|
+
`overwrite: true` resets existing roles, so review their current values first.
|
|
445
|
+
Explicit sizes and breakpoint overrides still win and are not silently migrated.
|
|
446
|
+
|
|
447
|
+
Heading role margins belong to editorial flow. Component titles use their own
|
|
448
|
+
padding/gap, and opening headings in containers/sections add no top margin.
|
|
449
|
+
Use role-linked `core/heading` size `auto` or `theme`; explicit hero sizes remain
|
|
450
|
+
deliberate exceptions. Do not compensate with per-card negative margins.
|
|
451
|
+
Compare 375, 768 and 1280px in Site settings → Design and in a full site preview.
|
|
452
|
+
After a rendering upgrade/rebuild, recheck cards, boxed headings, leading text
|
|
453
|
+
and webfonts. Boxes should get their outer air from padding. Keep exceptions
|
|
454
|
+
in decisions.md, and run lint_design_tokens before launch.
|
package/skills/tr-new-site.md
CHANGED
|
@@ -437,3 +437,26 @@ For migrated legacy pages or a hand-crafted one-off, `content_mode:
|
|
|
437
437
|
## Organization access
|
|
438
438
|
|
|
439
439
|
When a Cloud write reports `organization_trial_ended` or `organization_access_ended`, use `read_organization_access` on the selected Site. Explain the deadline and recovery URL; keep reads and export available when permitted. Do not repeatedly retry or create another organization to bypass the lock. Self-hosted installations are unrestricted by default. A trial does not limit AI credits: the organization supplies its own provider key.
|
|
440
|
+
|
|
441
|
+
## Fluid typography and component rhythm
|
|
442
|
+
|
|
443
|
+
Core 0.2.114 / MCP 0.45.85 / capabilities 0.55.0 adds responsive scales.
|
|
444
|
+
Check the connected versions before using this contract. New sites use render
|
|
445
|
+
version 10. For an existing site, test a Version first; upgrading rendering
|
|
446
|
+
and replacing existing roles are separate, deliberate choices.
|
|
447
|
+
|
|
448
|
+
Use `fonts.size_base` (default 16), `typography.ratio_mobile` (1.2),
|
|
449
|
+
`typography.ratio_desktop` (1.25), `typography.rhythm` (1), and
|
|
450
|
+
`typography.line_length` (65 ch). Set the controls once, then use named type,
|
|
451
|
+
spacing and line-height steps. `apply_standard_styles` uses those steps;
|
|
452
|
+
`overwrite: true` resets existing roles, so review their current values first.
|
|
453
|
+
Explicit sizes and breakpoint overrides still win and are not silently migrated.
|
|
454
|
+
|
|
455
|
+
Heading role margins belong to editorial flow. Component titles use their own
|
|
456
|
+
padding/gap, and opening headings in containers/sections add no top margin.
|
|
457
|
+
Use role-linked `core/heading` size `auto` or `theme`; explicit hero sizes remain
|
|
458
|
+
deliberate exceptions. Do not compensate with per-card negative margins.
|
|
459
|
+
Compare 375, 768 and 1280px in Site settings → Design and in a full site preview.
|
|
460
|
+
After a rendering upgrade/rebuild, recheck cards, boxed headings, leading text
|
|
461
|
+
and webfonts. Boxes should get their outer air from padding. Keep exceptions
|
|
462
|
+
in decisions.md, and run lint_design_tokens before launch.
|
|
@@ -113,3 +113,26 @@ uses the same color-mix() recipe as the header.
|
|
|
113
113
|
match what's already there.
|
|
114
114
|
- Choosing a new palette or fonts is brand work (the MCP recipe `tr-brand`),
|
|
115
115
|
not this reference.
|
|
116
|
+
|
|
117
|
+
## Fluid typography and component rhythm
|
|
118
|
+
|
|
119
|
+
Core 0.2.114 / MCP 0.45.85 / capabilities 0.55.0 adds responsive scales.
|
|
120
|
+
Check the connected versions before using this contract. New sites use render
|
|
121
|
+
version 10. For an existing site, test a Version first; upgrading rendering
|
|
122
|
+
and replacing existing roles are separate, deliberate choices.
|
|
123
|
+
|
|
124
|
+
Use `fonts.size_base` (default 16), `typography.ratio_mobile` (1.2),
|
|
125
|
+
`typography.ratio_desktop` (1.25), `typography.rhythm` (1), and
|
|
126
|
+
`typography.line_length` (65 ch). Set the controls once, then use named type,
|
|
127
|
+
spacing and line-height steps. `apply_standard_styles` uses those steps;
|
|
128
|
+
`overwrite: true` resets existing roles, so review their current values first.
|
|
129
|
+
Explicit sizes and breakpoint overrides still win and are not silently migrated.
|
|
130
|
+
|
|
131
|
+
Heading role margins belong to editorial flow. Component titles use their own
|
|
132
|
+
padding/gap, and opening headings in containers/sections add no top margin.
|
|
133
|
+
Use role-linked `core/heading` size `auto` or `theme`; explicit hero sizes remain
|
|
134
|
+
deliberate exceptions. Do not compensate with per-card negative margins.
|
|
135
|
+
Compare 375, 768 and 1280px in Site settings → Design and in a full site preview.
|
|
136
|
+
After a rendering upgrade/rebuild, recheck cards, boxed headings, leading text
|
|
137
|
+
and webfonts. Boxes should get their outer air from padding. Keep exceptions
|
|
138
|
+
in decisions.md, and run lint_design_tokens before launch.
|
|
@@ -57,3 +57,9 @@ Follow workflows/page-notes.md and pages/README.md. Create notes during migratio
|
|
|
57
57
|
read before edits, update afterward, check external links at each editorial review,
|
|
58
58
|
and archive notes for confirmed removed Pages under pages/archived/. Refreshing
|
|
59
59
|
generated facts never counts as a completed review. Keep notes out of public output.
|
|
60
|
+
|
|
61
|
+
- After a Core rendering upgrade, compare typography and spacing at 375, 768
|
|
62
|
+
and 1280px. Opening headings in cards, boxes and sections must not add the
|
|
63
|
+
editorial heading's top margin. Check actual fonts as well as the Site settings
|
|
64
|
+
scale preview. Run `node scripts/typeroll/check_heading_spacing.cjs <origin>`
|
|
65
|
+
and review deliberate exceptions in decisions.md.
|
|
@@ -117,3 +117,12 @@ See workflows/source-media.md for archive verification and original preservation
|
|
|
117
117
|
Omit --dry-run to write local files; --migration-inventory adds recorded redirect
|
|
118
118
|
sources. It never writes to the CMS, marks a Page reviewed or deletes notes. Read
|
|
119
119
|
pages/README.md and workflows/page-notes.md before first use.
|
|
120
|
+
|
|
121
|
+
### Heading rhythm after a rebuild
|
|
122
|
+
|
|
123
|
+
`node scripts/typeroll/check_heading_spacing.cjs https://example.com [paths.json]`
|
|
124
|
+
checks opening component headings and card spacing at 375, 768 and 1280px.
|
|
125
|
+
`paths.json` is an array of same-origin page paths; otherwise it reads a flat
|
|
126
|
+
sitemap. Requires Playwright and its Chromium browser. Findings need visual
|
|
127
|
+
review: deliberate asymmetric cards belong in decisions.md. It is a read-only
|
|
128
|
+
QA helper, not a publication gate or a CSS repair tool.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Read-only component rhythm QA. Deliberate exceptions require visual review.
|
|
2
|
+
const { chromium } = require('playwright');
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
async function main() {
|
|
5
|
+
const origin = new URL(process.argv[2]);
|
|
6
|
+
if (!['http:', 'https:'].includes(origin.protocol) || origin.username || origin.password) throw Error('Use an http(s) origin without credentials.');
|
|
7
|
+
const browser = await chromium.launch();
|
|
8
|
+
try {
|
|
9
|
+
const page = await browser.newPage();
|
|
10
|
+
let paths;
|
|
11
|
+
if (process.argv[3]) paths = JSON.parse(fs.readFileSync(process.argv[3], 'utf8'));
|
|
12
|
+
else {
|
|
13
|
+
const response = await page.request.get(new URL('/sitemap.xml', origin).href);
|
|
14
|
+
if (!response.ok()) throw Error(`Sitemap returned ${response.status()}`);
|
|
15
|
+
const xml = await response.text();
|
|
16
|
+
if (xml.includes('<sitemapindex')) throw Error('Provide paths.json for a sitemap index.');
|
|
17
|
+
paths = [...xml.matchAll(/<loc>([^<]+)<\/loc>/g)].map(m => m[1].replace(/&/g,'&'));
|
|
18
|
+
}
|
|
19
|
+
if (!Array.isArray(paths) || !paths.length || paths.some(p => typeof p !== 'string' || new URL(p,origin).origin !== origin.origin)) throw Error('Provide a nonempty array of same-origin paths.');
|
|
20
|
+
let count = 0;
|
|
21
|
+
for (const width of [375,768,1280]) {
|
|
22
|
+
await page.setViewportSize({width,height:900});
|
|
23
|
+
for (const path of [...new Set(paths)]) {
|
|
24
|
+
await page.goto(new URL(path,origin).href,{waitUntil:'domcontentloaded'});
|
|
25
|
+
await page.evaluate(()=>document.fonts.ready);
|
|
26
|
+
const findings=await page.evaluate(()=>{
|
|
27
|
+
const found=[];
|
|
28
|
+
const roots=document.querySelectorAll('[data-block="container"], .block-section-inner, [data-block="post_card"] [class*="postcard-body"], [data-block="icon_box"]');
|
|
29
|
+
for(const root of roots) {
|
|
30
|
+
const visible=[...root.children].filter(e=>e.getBoundingClientRect().height>0);
|
|
31
|
+
const first=visible[0];
|
|
32
|
+
const heading=first?.matches('h1,h2,h3,h4,h5,h6')?first:first?.matches('[data-block="heading"],hgroup')?first.querySelector('h1,h2,h3,h4,h5,h6'):null;
|
|
33
|
+
if(heading && parseFloat(getComputedStyle(heading).marginBlockStart)>1) found.push({kind:'opening_heading_margin',block:root.closest('[data-block]')?.getAttribute('data-block'),text:heading.textContent.slice(0,80),px:parseFloat(getComputedStyle(heading).marginBlockStart)});
|
|
34
|
+
if(root.closest('[data-block="post_card"]') && visible.length) {
|
|
35
|
+
const rect=root.getBoundingClientRect(),top=visible[0].getBoundingClientRect().top-rect.top,bottom=rect.bottom-visible.at(-1).getBoundingClientRect().bottom;
|
|
36
|
+
if(top>bottom+4) found.push({kind:'asymmetric_card',top:Math.round(top),bottom:Math.round(bottom)});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return found;
|
|
40
|
+
});
|
|
41
|
+
count+=findings.length;
|
|
42
|
+
if(findings.length) console.log(JSON.stringify({path:new URL(path,origin).pathname,width,findings}));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
console.log(JSON.stringify({pages:paths.length,widths:[375,768,1280],findings:count}));
|
|
46
|
+
process.exitCode=count?1:0;
|
|
47
|
+
} finally {await browser.close();}
|
|
48
|
+
}
|
|
49
|
+
main().catch(error=>{console.error(error.message);process.exitCode=2;});
|
|
@@ -63,3 +63,26 @@ review the plan before applying it in a dedicated Version. Equal hex values can
|
|
|
63
63
|
mean different roles: never automatically choose between background and text.
|
|
64
64
|
Verify a primary/surface change in a test Version across header, footer, cards,
|
|
65
65
|
menus and buttons, then restore the test settings. Do not republish automatically.
|
|
66
|
+
|
|
67
|
+
## Fluid typography and component rhythm
|
|
68
|
+
|
|
69
|
+
Core 0.2.114 / MCP 0.45.85 / capabilities 0.55.0 adds responsive scales.
|
|
70
|
+
Check the connected versions before using this contract. New sites use render
|
|
71
|
+
version 10. For an existing site, test a Version first; upgrading rendering
|
|
72
|
+
and replacing existing roles are separate, deliberate choices.
|
|
73
|
+
|
|
74
|
+
Use `fonts.size_base` (default 16), `typography.ratio_mobile` (1.2),
|
|
75
|
+
`typography.ratio_desktop` (1.25), `typography.rhythm` (1), and
|
|
76
|
+
`typography.line_length` (65 ch). Set the controls once, then use named type,
|
|
77
|
+
spacing and line-height steps. `apply_standard_styles` uses those steps;
|
|
78
|
+
`overwrite: true` resets existing roles, so review their current values first.
|
|
79
|
+
Explicit sizes and breakpoint overrides still win and are not silently migrated.
|
|
80
|
+
|
|
81
|
+
Heading role margins belong to editorial flow. Component titles use their own
|
|
82
|
+
padding/gap, and opening headings in containers/sections add no top margin.
|
|
83
|
+
Use role-linked `core/heading` size `auto` or `theme`; explicit hero sizes remain
|
|
84
|
+
deliberate exceptions. Do not compensate with per-card negative margins.
|
|
85
|
+
Compare 375, 768 and 1280px in Site settings → Design and in a full site preview.
|
|
86
|
+
After a rendering upgrade/rebuild, recheck cards, boxed headings, leading text
|
|
87
|
+
and webfonts. Boxes should get their outer air from padding. Keep exceptions
|
|
88
|
+
in decisions.md, and run lint_design_tokens before launch.
|
|
@@ -134,3 +134,26 @@ Follow workflows/page-notes.md and pages/README.md. Create notes during migratio
|
|
|
134
134
|
read before edits, update afterward, check external links at each editorial review,
|
|
135
135
|
and archive notes for confirmed removed Pages under pages/archived/. Refreshing
|
|
136
136
|
generated facts never counts as a completed review. Keep notes out of public output.
|
|
137
|
+
|
|
138
|
+
## Fluid typography and component rhythm
|
|
139
|
+
|
|
140
|
+
Core 0.2.114 / MCP 0.45.85 / capabilities 0.55.0 adds responsive scales.
|
|
141
|
+
Check the connected versions before using this contract. New sites use render
|
|
142
|
+
version 10. For an existing site, test a Version first; upgrading rendering
|
|
143
|
+
and replacing existing roles are separate, deliberate choices.
|
|
144
|
+
|
|
145
|
+
Use `fonts.size_base` (default 16), `typography.ratio_mobile` (1.2),
|
|
146
|
+
`typography.ratio_desktop` (1.25), `typography.rhythm` (1), and
|
|
147
|
+
`typography.line_length` (65 ch). Set the controls once, then use named type,
|
|
148
|
+
spacing and line-height steps. `apply_standard_styles` uses those steps;
|
|
149
|
+
`overwrite: true` resets existing roles, so review their current values first.
|
|
150
|
+
Explicit sizes and breakpoint overrides still win and are not silently migrated.
|
|
151
|
+
|
|
152
|
+
Heading role margins belong to editorial flow. Component titles use their own
|
|
153
|
+
padding/gap, and opening headings in containers/sections add no top margin.
|
|
154
|
+
Use role-linked `core/heading` size `auto` or `theme`; explicit hero sizes remain
|
|
155
|
+
deliberate exceptions. Do not compensate with per-card negative margins.
|
|
156
|
+
Compare 375, 768 and 1280px in Site settings → Design and in a full site preview.
|
|
157
|
+
After a rendering upgrade/rebuild, recheck cards, boxed headings, leading text
|
|
158
|
+
and webfonts. Boxes should get their outer air from padding. Keep exceptions
|
|
159
|
+
in decisions.md, and run lint_design_tokens before launch.
|