@samva/markup 0.18.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +11 -4
- package/CHANGELOG.md +36 -0
- package/README.md +89 -5
- package/dist/assets-BSpgZOSR.js +61 -0
- package/dist/assets-k7_v3IOJ.d.ts +39 -0
- package/dist/{assets-CS5Xa_mr.d.ts → brand-Bxb6Sm7y.d.ts} +34 -41
- package/dist/brand.d.ts +2 -2
- package/dist/brand.js +3 -3
- package/dist/compatibility.d.ts +15 -5
- package/dist/compatibility.js +48 -5
- package/dist/compile-C1GWLQ5C.js +490 -0
- package/dist/compile-CIeuGmP-.d.ts +98 -0
- package/dist/{compiled-template-Bc4Dq9zy.d.ts → compiled-template-CPDHvFlt.d.ts} +157 -6
- package/dist/compiler.d.ts +4 -89
- package/dist/compiler.js +8 -486
- package/dist/components-DNBCz20_.js +3267 -0
- package/dist/{css-color-DLM12Osu.js → dark-scheme-BGDCCaUq.js} +12 -1
- package/dist/{diagnostic-codes-DZKb0Xcg.js → diagnostic-codes-BCwfUgnF.js} +33 -36
- package/dist/diagnostic-model-DJtqbETd.js +36 -0
- package/dist/{diagnostics-D4vSLQrO.d.ts → diagnostics-C3TSx6SU.d.ts} +22 -2
- package/dist/diagnostics.d.ts +33 -1
- package/dist/diagnostics.js +2 -1
- package/dist/edit.d.ts +15 -4
- package/dist/edit.js +67 -4
- package/dist/email/jsx-dev-runtime.d.ts +2 -2
- package/dist/email/jsx-runtime.d.ts +1 -1
- package/dist/email.d.ts +6 -2
- package/dist/email.js +6 -2
- package/dist/index.d.ts +1 -1
- package/dist/{jsx-runtime-DL8NVF1f.d.ts → jsx-runtime-HHfrVZEf.d.ts} +1 -2
- package/dist/markdown.d.ts +69 -0
- package/dist/markdown.js +95 -0
- package/dist/parse-Bex7QFqj.js +921 -0
- package/dist/{preview-rNoJBEIY.d.ts → preview-DRLWgmfg.d.ts} +3 -3
- package/dist/profile-DFk4gs7o.js +56 -0
- package/dist/{profile-JTvgCD6X.js → project-B6D64SZS.js} +497 -234
- package/dist/render.d.ts +8 -8
- package/dist/render.js +1 -2
- package/dist/{css-B_aVKrDo.js → selectors-CKGuc9MO.js} +158 -4
- package/dist/sms.d.ts +1 -1
- package/dist/{template-DmNcoH4Q.d.ts → template-4Dse7_OC.d.ts} +1 -1
- package/dist/{theme-css-P9F5S7Ic.js → theme-css-BujDuuEA.js} +1 -2
- package/dist/whatsapp.d.ts +1 -1
- package/generated/contract.json +41 -1
- package/generated/reference.md +97 -89
- package/package.json +15 -4
- package/dist/brand-CIiEqkU7.js +0 -193
- package/dist/brand-plugin-Dj9SMpfS.d.ts +0 -33
- package/dist/components-C5850-Qg.js +0 -409
- package/dist/dark-scheme-CbCwGDWH.js +0 -12
- package/dist/html-DJUK2YJF.js +0 -483
- package/dist/render-template-DwPDm3Hq.js +0 -1226
- package/dist/selectors-CYWCoxZr.js +0 -157
- package/dist/source-locations-C8wCsiAy.d.ts +0 -22
package/dist/render.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { C as emailElement, S as EmailTag, _ as EmailElementSpec, b as EmailLayout, g as EmailAttributeSpec, h as EMAIL_GLOBAL_ATTRIBUTES, m as EMAIL_ELEMENTS, v as EmailGlobalProps, w as isUrlAttribute, x as EmailStyle, y as EmailIntrinsicElements } from "./jsx-runtime-
|
|
3
|
-
import { A as
|
|
1
|
+
import { m as BrandPlugin } from "./brand-Bxb6Sm7y.js";
|
|
2
|
+
import { C as emailElement, S as EmailTag, _ as EmailElementSpec, b as EmailLayout, g as EmailAttributeSpec, h as EMAIL_GLOBAL_ATTRIBUTES, m as EMAIL_ELEMENTS, v as EmailGlobalProps, w as isUrlAttribute, x as EmailStyle, y as EmailIntrinsicElements } from "./jsx-runtime-HHfrVZEf.js";
|
|
3
|
+
import { A as RenderedWhatsAppButton, B as SemanticIntrinsic, C as PageHeadHints, D as RenderedPosition, E as RenderedPage, F as EmailPresentation, H as WebCssRule, I as EmailPresentationOverride, K as SemanticComponentName, L as SemanticBlock, M as SmsEncoding, N as SmsSegmentInfo, O as RenderedSms, P as smsSegmentInfo, R as SemanticComponent, S as CompiledWhatsAppButtonNode, T as RenderedMessage, U as WebPresentation, V as SemanticProp, _ as CompiledStyle, a as CompiledEach, b as CompiledWhatsApp, c as CompiledFormatter, d as CompiledPredicate, f as CompiledPreheader, g as CompiledSource, h as CompiledSms, i as CompiledComparison, j as Target, k as RenderedWhatsApp, l as CompiledIf, m as CompiledRawPart, n as Channel, o as CompiledElement, p as CompiledRaw, r as CompiledArithmetic, s as CompiledEmail, t as COMPILED_TEMPLATE_VERSION, u as CompiledNode, v as CompiledTemplate, w as RenderedEmail, x as CompiledWhatsAppButton, y as CompiledValue, z as SemanticInline } from "./compiled-template-CPDHvFlt.js";
|
|
4
4
|
//#region src/render-template.d.ts
|
|
5
5
|
interface RenderDiagnostic {
|
|
6
6
|
readonly code: string;
|
|
@@ -29,12 +29,12 @@ interface RenderSettings {
|
|
|
29
29
|
readonly brandPlugin?: BrandPlugin | undefined;
|
|
30
30
|
}
|
|
31
31
|
/**
|
|
32
|
-
* Render one
|
|
33
|
-
* `RenderError`: a template compiled by another version, a
|
|
32
|
+
* Render one target of a compiled template against one validated input. It throws only
|
|
33
|
+
* `RenderError`: a template compiled by another version, a target the template does not support,
|
|
34
34
|
* or input or markup the renderer refuses.
|
|
35
35
|
*/
|
|
36
|
-
declare const render: <C extends
|
|
37
|
-
readonly
|
|
36
|
+
declare const render: <C extends Target>(template: CompiledTemplate, input: unknown, options: RenderSettings & {
|
|
37
|
+
readonly target: C;
|
|
38
38
|
}) => RenderedMessage[C];
|
|
39
39
|
//#endregion
|
|
40
40
|
//#region src/email/html.d.ts
|
|
@@ -43,4 +43,4 @@ declare const escapeText: (value: string) => string;
|
|
|
43
43
|
/** Escape a value for a double-quoted attribute. */
|
|
44
44
|
declare const escapeAttribute: (value: string) => string;
|
|
45
45
|
//#endregion
|
|
46
|
-
export { COMPILED_TEMPLATE_VERSION, type Channel, type CompiledArithmetic, type CompiledComparison, type CompiledEach, type CompiledElement, type CompiledEmail, type CompiledFormatter, type CompiledIf, type CompiledNode, type CompiledPredicate, type CompiledPreheader, type CompiledRaw, type CompiledRawPart, type CompiledSms, type CompiledSource, type CompiledStyle, type CompiledTemplate, type CompiledValue, type CompiledWhatsApp, type CompiledWhatsAppButton, type CompiledWhatsAppButtonNode, EMAIL_ELEMENTS, EMAIL_GLOBAL_ATTRIBUTES, type EmailAttributeSpec, type EmailElementSpec, type EmailGlobalProps, type EmailIntrinsicElements, type EmailLayout, type EmailStyle, type EmailTag, type RenderDiagnostic, RenderError, type RenderedEmail, type RenderedMessage, type RenderedSms, type RenderedWhatsApp, type RenderedWhatsAppButton, type SmsEncoding, type SmsSegmentInfo, emailElement, escapeAttribute, escapeText, isUrlAttribute, render, smsSegmentInfo };
|
|
46
|
+
export { COMPILED_TEMPLATE_VERSION, type Channel, type CompiledArithmetic, type CompiledComparison, type CompiledEach, type CompiledElement, type CompiledEmail, type CompiledFormatter, type CompiledIf, type CompiledNode, type CompiledPredicate, type CompiledPreheader, type CompiledRaw, type CompiledRawPart, type CompiledSms, type CompiledSource, type CompiledStyle, type CompiledTemplate, type CompiledValue, type CompiledWhatsApp, type CompiledWhatsAppButton, type CompiledWhatsAppButtonNode, EMAIL_ELEMENTS, EMAIL_GLOBAL_ATTRIBUTES, type EmailAttributeSpec, type EmailElementSpec, type EmailGlobalProps, type EmailIntrinsicElements, type EmailLayout, type EmailPresentation, type EmailPresentationOverride, type EmailStyle, type EmailTag, type PageHeadHints, type RenderDiagnostic, RenderError, type RenderedEmail, type RenderedMessage, type RenderedPage, type RenderedPosition, type RenderedSms, type RenderedWhatsApp, type RenderedWhatsAppButton, type SemanticBlock, type SemanticComponent, type SemanticComponentName, type SemanticInline, type SemanticIntrinsic, type SemanticProp, type SmsEncoding, type SmsSegmentInfo, type Target, type WebCssRule, type WebPresentation, emailElement, escapeAttribute, escapeText, isUrlAttribute, render, smsSegmentInfo };
|
package/dist/render.js
CHANGED
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as EMAIL_ELEMENTS, c as isUrlAttribute, i as smsSegmentInfo, n as render, o as EMAIL_GLOBAL_ATTRIBUTES, p as COMPILED_TEMPLATE_VERSION, s as emailElement, t as RenderError } from "./render-template-DwPDm3Hq.js";
|
|
1
|
+
import { J as EMAIL_GLOBAL_ATTRIBUTES, O as RenderError, X as isUrlAttribute, Y as emailElement, g as escapeText, h as escapeAttribute, j as smsSegmentInfo, k as render, q as EMAIL_ELEMENTS, tt as COMPILED_TEMPLATE_VERSION } from "./components-DNBCz20_.js";
|
|
3
2
|
export { COMPILED_TEMPLATE_VERSION, EMAIL_ELEMENTS, EMAIL_GLOBAL_ATTRIBUTES, RenderError, emailElement, escapeAttribute, escapeText, isUrlAttribute, render, smsSegmentInfo };
|
|
@@ -1,7 +1,94 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as isDarkSchemeCondition } from "./dark-scheme-CbCwGDWH.js";
|
|
3
|
-
import { o as unquote } from "./selectors-CYWCoxZr.js";
|
|
1
|
+
import { a as downlevelColors, n as isDarkSchemeCondition } from "./dark-scheme-BGDCCaUq.js";
|
|
4
2
|
import { generate, ident, parse, walk } from "css-tree";
|
|
3
|
+
//#region src/email/font-stacks.ts
|
|
4
|
+
/** Generic families every email client understands as the last resort in a stack. */
|
|
5
|
+
const GENERIC_FAMILIES = /* @__PURE__ */ new Set([
|
|
6
|
+
"serif",
|
|
7
|
+
"sans-serif",
|
|
8
|
+
"monospace",
|
|
9
|
+
"cursive",
|
|
10
|
+
"fantasy"
|
|
11
|
+
]);
|
|
12
|
+
/** One family name as written, without its quotes and with runs of spaces collapsed. */
|
|
13
|
+
const unquote = (value) => {
|
|
14
|
+
const trimmed = value.trim();
|
|
15
|
+
const quote = trimmed[0];
|
|
16
|
+
return (quote === "\"" || quote === "'") && trimmed.endsWith(quote) && trimmed.length >= 2 ? trimmed.slice(1, -1) : trimmed.replace(/\s+/g, " ");
|
|
17
|
+
};
|
|
18
|
+
/** The families of a `font-family` value, unquoted, in stack order. */
|
|
19
|
+
const fontStackFamilies = (value) => {
|
|
20
|
+
const families = [];
|
|
21
|
+
let start = 0;
|
|
22
|
+
let quote;
|
|
23
|
+
for (let index = 0; index < value.length; index++) {
|
|
24
|
+
const character = value[index];
|
|
25
|
+
if (quote !== void 0) {
|
|
26
|
+
if (character === quote) quote = void 0;
|
|
27
|
+
} else if (character === "'" || character === "\"") quote = character;
|
|
28
|
+
else if (character === ",") {
|
|
29
|
+
families.push(unquote(value.slice(start, index)));
|
|
30
|
+
start = index + 1;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
families.push(unquote(value.slice(start)));
|
|
34
|
+
return families.filter((family) => family !== "");
|
|
35
|
+
};
|
|
36
|
+
const FONT_SIZE = /^(?:xx-small|x-small|small|medium|large|x-large|xx-large|xxx-large|larger|smaller|[+-]?(?:\d+\.?\d*|\.\d+)(?:[a-z]+|%)|(?:calc|clamp|min|max|var)\(.*\))$/i;
|
|
37
|
+
/** Whitespace-separated tokens at the top level, keeping parentheses and quotes whole. */
|
|
38
|
+
const shorthandTokens = (value) => {
|
|
39
|
+
const tokens = [];
|
|
40
|
+
let current = "";
|
|
41
|
+
let depth = 0;
|
|
42
|
+
let quote;
|
|
43
|
+
for (const character of value.trim()) {
|
|
44
|
+
if (quote !== void 0) {
|
|
45
|
+
if (character === quote) quote = void 0;
|
|
46
|
+
} else if (character === "'" || character === "\"") quote = character;
|
|
47
|
+
else if (character === "(") depth++;
|
|
48
|
+
else if (character === ")") depth--;
|
|
49
|
+
else if (/\s/.test(character) && depth === 0) {
|
|
50
|
+
if (current !== "") tokens.push(current);
|
|
51
|
+
current = "";
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
current += character;
|
|
55
|
+
}
|
|
56
|
+
if (current !== "") tokens.push(current);
|
|
57
|
+
return tokens;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* The family list of a `font` shorthand: everything after the size (and its
|
|
61
|
+
* optional line height), or `undefined` for a system-font keyword that names
|
|
62
|
+
* no family.
|
|
63
|
+
*/
|
|
64
|
+
const fontShorthandFamilies = (value) => {
|
|
65
|
+
const tokens = shorthandTokens(value);
|
|
66
|
+
const size = tokens.findIndex((token) => FONT_SIZE.test(token.replace(/\/.*$/, "")));
|
|
67
|
+
if (size < 0) return void 0;
|
|
68
|
+
let next = size + 1;
|
|
69
|
+
const sizeToken = tokens[size];
|
|
70
|
+
if (sizeToken.endsWith("/")) next += 1;
|
|
71
|
+
else if (!sizeToken.includes("/") && tokens[next]?.startsWith("/")) next += tokens[next] === "/" ? 2 : 1;
|
|
72
|
+
const family = tokens.slice(next).join(" ");
|
|
73
|
+
return family === "" ? void 0 : family;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* True when a stack names a declared project family without ending in a
|
|
77
|
+
* generic. `declared` holds lowercased family names. A quoted `"sans-serif"`
|
|
78
|
+
* names a font called sans-serif, not the generic, so it does not count.
|
|
79
|
+
*/
|
|
80
|
+
const lacksGenericFallback = (value, declared) => {
|
|
81
|
+
const stack = value.replace(/\s*!important\s*$/i, "");
|
|
82
|
+
if (!fontStackFamilies(stack).some((family) => declared.has(family.toLowerCase()))) return false;
|
|
83
|
+
const last = stack.slice(stack.lastIndexOf(",") + 1).trim();
|
|
84
|
+
return !GENERIC_FAMILIES.has(last.toLowerCase());
|
|
85
|
+
};
|
|
86
|
+
const fontFallbackDiagnostic = (value) => ({
|
|
87
|
+
code: "email-font-fallback",
|
|
88
|
+
severity: "error",
|
|
89
|
+
message: `font-family ${JSON.stringify(value)} uses a project font without a generic fallback. Clients that drop webfonts (Gmail, classic Outlook) render this stack as written, so end it with sans-serif, serif or monospace.`
|
|
90
|
+
});
|
|
91
|
+
//#endregion
|
|
5
92
|
//#region src/email/fonts.ts
|
|
6
93
|
const FONT_FILE = /\.woff2$/i;
|
|
7
94
|
const WOFF2_FORMAT = /^woff2(?:-variations)?$/i;
|
|
@@ -433,4 +520,71 @@ const parseStylesheet = (source, options = {}) => {
|
|
|
433
520
|
};
|
|
434
521
|
};
|
|
435
522
|
//#endregion
|
|
436
|
-
|
|
523
|
+
//#region src/email/selectors.ts
|
|
524
|
+
const classListOf = (element) => {
|
|
525
|
+
const value = element.props.class ?? element.props.className;
|
|
526
|
+
return typeof value === "string" ? value.split(/\s+/).filter((name) => name !== "") : [];
|
|
527
|
+
};
|
|
528
|
+
const attributeOf = (element, name) => {
|
|
529
|
+
const value = element.props[name];
|
|
530
|
+
if (value === void 0 || value === null || value === false) return void 0;
|
|
531
|
+
if (value === true) return "";
|
|
532
|
+
return typeof value === "string" || typeof value === "number" ? String(value) : void 0;
|
|
533
|
+
};
|
|
534
|
+
const matchesAttribute = (element, test) => {
|
|
535
|
+
const actual = test.name === "class" ? classListOf(element).join(" ") : attributeOf(element, test.name);
|
|
536
|
+
if (actual === void 0) return false;
|
|
537
|
+
if (test.operator === void 0 || test.value === void 0) return true;
|
|
538
|
+
switch (test.operator) {
|
|
539
|
+
case "=": return actual === test.value;
|
|
540
|
+
case "~=": return actual.split(/\s+/).includes(test.value);
|
|
541
|
+
case "^=": return actual.startsWith(test.value);
|
|
542
|
+
case "$=": return actual.endsWith(test.value);
|
|
543
|
+
case "*=": return actual.includes(test.value);
|
|
544
|
+
case "|=": return actual === test.value || actual.startsWith(`${test.value}-`);
|
|
545
|
+
default: return false;
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
const matchesCompound = (element, compound) => {
|
|
549
|
+
if (compound.opaque) return false;
|
|
550
|
+
if (compound.tag !== void 0 && compound.tag !== element.tag) return false;
|
|
551
|
+
if (compound.id !== void 0 && attributeOf(element, "id") !== compound.id) return false;
|
|
552
|
+
if (compound.classes.length > 0) {
|
|
553
|
+
const classes = classListOf(element);
|
|
554
|
+
if (!compound.classes.every((name) => classes.includes(name))) return false;
|
|
555
|
+
}
|
|
556
|
+
return compound.attributes.every((test) => matchesAttribute(element, test));
|
|
557
|
+
};
|
|
558
|
+
const matchesFrom = (selector, index, ancestry) => {
|
|
559
|
+
if (ancestry === void 0) return false;
|
|
560
|
+
const part = selector.parts[index];
|
|
561
|
+
if (part === void 0) return false;
|
|
562
|
+
if (!matchesCompound(ancestry.element, part.compound)) return false;
|
|
563
|
+
const left = selector.parts[index - 1];
|
|
564
|
+
if (left === void 0) return true;
|
|
565
|
+
switch (left.after ?? "descendant") {
|
|
566
|
+
case "child": return matchesFrom(selector, index - 1, ancestry.parent);
|
|
567
|
+
case "descendant":
|
|
568
|
+
for (let parent = ancestry.parent; parent !== void 0; parent = parent.parent) if (matchesFrom(selector, index - 1, parent)) return true;
|
|
569
|
+
return false;
|
|
570
|
+
case "adjacent": {
|
|
571
|
+
const sibling = ancestry.previous.at(-1);
|
|
572
|
+
return sibling === void 0 ? false : matchesFrom(selector, index - 1, {
|
|
573
|
+
element: sibling,
|
|
574
|
+
parent: ancestry.parent,
|
|
575
|
+
previous: ancestry.previous.slice(0, -1)
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
case "sibling":
|
|
579
|
+
for (let position = ancestry.previous.length - 1; position >= 0; position--) if (matchesFrom(selector, index - 1, {
|
|
580
|
+
element: ancestry.previous[position],
|
|
581
|
+
parent: ancestry.parent,
|
|
582
|
+
previous: ancestry.previous.slice(0, position)
|
|
583
|
+
})) return true;
|
|
584
|
+
return false;
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
/** Match a compiled selector against one element in its tree position. */
|
|
588
|
+
const matchesSelector = (selector, ancestry) => selector === void 0 || selector.parts.length === 0 ? false : matchesFrom(selector, selector.parts.length - 1, ancestry);
|
|
589
|
+
//#endregion
|
|
590
|
+
export { fontShorthandFamilies as a, fontFallbackDiagnostic as i, matchesSelector as n, lacksGenericFallback as o, parseStylesheet as r, classListOf as t };
|
package/dist/sms.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as EmailNode } from "./jsx-runtime-
|
|
1
|
+
import { i as EmailNode } from "./jsx-runtime-HHfrVZEf.js";
|
|
2
2
|
import { r as InputSchema } from "./input-schema-7dPFVvNE.js";
|
|
3
3
|
import { StandardJSONSchemaV1 } from "@standard-schema/spec";
|
|
4
4
|
//#region src/template.d.ts
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { r as isDarkSchemeQuery } from "./dark-scheme-CbCwGDWH.js";
|
|
1
|
+
import { o as parseColor, r as isDarkSchemeQuery } from "./dark-scheme-BGDCCaUq.js";
|
|
3
2
|
import { Result } from "better-result";
|
|
4
3
|
import { generate, parse, walk } from "css-tree";
|
|
5
4
|
//#region src/theme-css.ts
|
package/dist/whatsapp.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as EmailNode, t as EmailChild } from "./jsx-runtime-
|
|
1
|
+
import { i as EmailNode, t as EmailChild } from "./jsx-runtime-HHfrVZEf.js";
|
|
2
2
|
import { n as WhatsAppCategory, r as WhatsAppHeaderType } from "./channel-specs-C2feJwuP.js";
|
|
3
3
|
//#region src/whatsapp.d.ts
|
|
4
4
|
interface WhatsAppProps {
|
package/generated/contract.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"contract": 1,
|
|
3
|
-
"compiledTemplateVersion":
|
|
3
|
+
"compiledTemplateVersion": 2,
|
|
4
4
|
"template": {
|
|
5
5
|
"extension": ".tsx",
|
|
6
6
|
"directories": ["templates", "emails"],
|
|
@@ -1550,6 +1550,11 @@
|
|
|
1550
1550
|
"summary": "A prop that sizes layout at compile time is not a number literal.",
|
|
1551
1551
|
"fix": "Write the number literally, for example height={44}."
|
|
1552
1552
|
},
|
|
1553
|
+
{
|
|
1554
|
+
"code": "email-component-degradation",
|
|
1555
|
+
"summary": "A component loses content or behavior when lowered to email.",
|
|
1556
|
+
"fix": "Compare the email and web previews; provide an email-safe equivalent for content readers need."
|
|
1557
|
+
},
|
|
1553
1558
|
{
|
|
1554
1559
|
"code": "email-contrast",
|
|
1555
1560
|
"summary": "Text, or a filled button against its surroundings, falls below the contrast ratio in light or dark mode (below 1.5:1 a button is indistinguishable from its surface; WCAG 1.4.11 asks 3:1).",
|
|
@@ -1717,12 +1722,47 @@
|
|
|
1717
1722
|
"summary": "The plain-text body is not a string.",
|
|
1718
1723
|
"fix": "Return a string."
|
|
1719
1724
|
},
|
|
1725
|
+
{
|
|
1726
|
+
"code": "issue-layout",
|
|
1727
|
+
"summary": "A newsletter issue layout does not declare only an email channel.",
|
|
1728
|
+
"fix": "Use an email-only defineTemplate layout with one IssueContent slot."
|
|
1729
|
+
},
|
|
1730
|
+
{
|
|
1731
|
+
"code": "issue-slot",
|
|
1732
|
+
"summary": "An issue layout does not contain exactly one unconditional content slot.",
|
|
1733
|
+
"fix": "Import IssueContent from @samva/markup/email and place one <IssueContent /> outside loops and conditions."
|
|
1734
|
+
},
|
|
1720
1735
|
{
|
|
1721
1736
|
"code": "legacy-definition",
|
|
1722
1737
|
"summary": "The entry default-exports something other than defineTemplate.",
|
|
1723
1738
|
"fix": "Import { defineTemplate } from \"@samva/markup\" and default-export defineTemplate({ ... }).",
|
|
1724
1739
|
"upgrade": "Read the entry and its imports without executing them. For a default-exported defineEmail definition, import defineTemplate from \"@samva/markup\" and replace the definition call.\n\nPreserve the entry path, template id, schema, named fixtures, content, design, bindings, conditions, project partials, assets, theme and brand imports. Replace render(input) returning { subject, preheader?, body } with email: { subject: (input) => ..., preheader: (input) => ..., body: (input) => ... }, retaining its parameter bindings and each returned expression; omit preheader when absent.\n\nInline module constants only when they are literal strings, numbers or booleans and their bindings are unambiguous; never substitute a fixture value for an input binding. Keep each channel function a single static-profile expression, and keep subject and preheader off <Email>.\n\nHelper calls, `Math.*`, general arithmetic, render statements, spreads, computed properties and runtime clock or seed have no direct conversion. Rewrite with static-profile bindings, conditions, .map, .filter, project partials or `fmt.*` where equivalent; arithmetic is allowed only in formatter arguments and conditions. Otherwise compute the value in the sender, declare a payload field in the schema and every fixture, and explain the required sender change. Never guess time, randomness or computed data.\n\nExplicit text has no email-channel field: plain text is derived from the body. If deriving it changes intended content, or another construct cannot be preserved, explain the tradeoff and ask rather than silently discard it. Do not drop fixtures, weaken validation or hide diagnostics.\n\nConvert every affected entry in the project at its existing path, including emails/ entries. Compile each entry, render every declared fixture and inspect the affected output. Resolve errors before requesting one saved version for the whole project, approved by the user; do not publish."
|
|
1725
1740
|
},
|
|
1741
|
+
{
|
|
1742
|
+
"code": "markdown-expression",
|
|
1743
|
+
"summary": "An issue contains an expression or spread, which is never evaluated.",
|
|
1744
|
+
"fix": "Replace expressions with literal prose or quoted static component attributes."
|
|
1745
|
+
},
|
|
1746
|
+
{
|
|
1747
|
+
"code": "markdown-frontmatter",
|
|
1748
|
+
"summary": "Issue frontmatter is missing or does not match the typed contract.",
|
|
1749
|
+
"fix": "Provide title, slug and topic strings; use only the documented preheader, seo and gate fields."
|
|
1750
|
+
},
|
|
1751
|
+
{
|
|
1752
|
+
"code": "markdown-html",
|
|
1753
|
+
"summary": "An issue contains raw HTML rather than a supported SML component.",
|
|
1754
|
+
"fix": "Use Markdown syntax or a component from the closed SML component set."
|
|
1755
|
+
},
|
|
1756
|
+
{
|
|
1757
|
+
"code": "markdown-prop",
|
|
1758
|
+
"summary": "A Markdown component has an unknown, missing or invalid literal prop.",
|
|
1759
|
+
"fix": "Use the component's documented props with quoted static values or bare boolean flags."
|
|
1760
|
+
},
|
|
1761
|
+
{
|
|
1762
|
+
"code": "markdown-syntax",
|
|
1763
|
+
"summary": "An issue contains syntax outside SML Markdown.",
|
|
1764
|
+
"fix": "Write Markdown prose and supported SML component tags."
|
|
1765
|
+
},
|
|
1726
1766
|
{
|
|
1727
1767
|
"code": "missing-attribute",
|
|
1728
1768
|
"summary": "An element is missing a required attribute.",
|