@samva/markup 0.16.0 → 0.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +42 -40
- package/CHANGELOG.md +72 -0
- package/README.md +10 -9
- package/dist/{brand-jFV6YtSV.d.ts → assets-CS5Xa_mr.d.ts} +41 -3
- package/dist/{brand-Ci05p37c.js → brand-CIiEqkU7.js} +64 -3
- package/dist/{brand-plugin-CHhcei-j.d.ts → brand-plugin-Dj9SMpfS.d.ts} +3 -1
- package/dist/brand.d.ts +2 -3
- package/dist/brand.js +3 -4
- package/dist/compatibility.d.ts +76 -0
- package/dist/compatibility.js +807 -0
- package/dist/compiled-template-Bc4Dq9zy.d.ts +268 -0
- package/dist/compiler.d.ts +59 -193
- package/dist/compiler.js +473 -775
- package/dist/{components-3XHjYD3i.js → components-C5850-Qg.js} +37 -29
- package/dist/css-B_aVKrDo.js +436 -0
- package/dist/css-color-DLM12Osu.js +315 -0
- package/dist/{diagnostic-codes-8XDLR9ef.js → diagnostic-codes-DZKb0Xcg.js} +40 -1
- package/dist/{diagnostics-CUYlQ_6x.d.ts → diagnostics-D4vSLQrO.d.ts} +1 -10
- package/dist/diagnostics.d.ts +6 -2
- package/dist/diagnostics.js +3 -4
- package/dist/edit.d.ts +10 -9
- package/dist/edit.js +12 -11
- package/dist/email/jsx-dev-runtime.d.ts +1 -1
- package/dist/email/jsx-dev-runtime.js +1 -1
- package/dist/email/jsx-runtime.d.ts +2 -2
- package/dist/email/jsx-runtime.js +2 -2
- package/dist/email.d.ts +1 -1
- package/dist/email.js +1 -1
- package/dist/html-DJUK2YJF.js +483 -0
- package/dist/index.d.ts +1 -1
- package/dist/{jsx-runtime-DC7xG0aA.d.ts → jsx-runtime-DL8NVF1f.d.ts} +12 -6
- package/dist/{jsx-runtime-CBlexoeR.js → jsx-runtime-qL19u3Fy.js} +17 -10
- package/dist/preview-rNoJBEIY.d.ts +104 -0
- package/dist/{profile-BsY4dkan.js → profile-JTvgCD6X.js} +714 -29
- package/dist/{html-BvRFepeO.js → render-template-DwPDm3Hq.js} +155 -572
- package/dist/render.d.ts +40 -28
- package/dist/render.js +3 -47
- package/dist/selectors-CYWCoxZr.js +157 -0
- package/dist/sms.d.ts +1 -1
- package/dist/sms.js +1 -1
- package/dist/{template-B-UDXK8a.d.ts → template-DmNcoH4Q.d.ts} +2 -9
- package/dist/{theme-css-s9QwD5Ko.js → theme-css-P9F5S7Ic.js} +3 -316
- package/dist/whatsapp.d.ts +1 -1
- package/dist/whatsapp.js +1 -1
- package/generated/contract.json +7 -2
- package/generated/reference.md +3 -2
- package/package.json +5 -5
- package/skills/sml/SKILL.md +2 -2
- package/dist/assets-BSpgZOSR.js +0 -61
- package/dist/assets-BSuNT9gr.d.ts +0 -39
- package/dist/brand-footer-ClRtWnIc.d.ts +0 -40
- package/dist/brand-footer-DXVGsKE5.js +0 -746
- package/dist/brand-sample.d.ts +0 -4
- package/dist/brand-sample.js +0 -2
- package/dist/cascade-DoLWZ66g.js +0 -701
- package/dist/diagnostic-model-DJtqbETd.js +0 -36
- package/dist/ir-KJLFW08u.js +0 -5
- package/dist/render-CVnXZMyK.js +0 -258
- package/dist/render-CuZej25A.d.ts +0 -564
- package/dist/theme-imports-u_P0ygvQ.js +0 -102
package/dist/whatsapp.js
CHANGED
package/generated/contract.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"contract": 1,
|
|
3
|
-
"
|
|
3
|
+
"compiledTemplateVersion": 1,
|
|
4
4
|
"template": {
|
|
5
5
|
"extension": ".tsx",
|
|
6
6
|
"directories": ["templates", "emails"],
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"emailBody": "{ subject, preheader?, body }, each a function of the validated input",
|
|
15
15
|
"schema": "Any Standard JSON Schema converter, or a literal jsonSchema<Input>({ ... }); portable JSON Schema 2020-12. It validates every send and drives the binding checker. No coercion and no inserted defaults.",
|
|
16
16
|
"fixtures": "Named payloads that must validate. At least one. Each is a preview and a publish check.",
|
|
17
|
-
"execution": "Nothing runs at send time. A template compiles to
|
|
17
|
+
"execution": "Nothing runs at send time. A template compiles to a compiled template and the send path renders it against the input. Locale and time zone are send options."
|
|
18
18
|
},
|
|
19
19
|
"profile": {
|
|
20
20
|
"forms": [
|
|
@@ -1556,6 +1556,11 @@
|
|
|
1556
1556
|
"fix": "Pair background and text tokens (bg-primary with text-primary-foreground), give the theme's dark-mode @theme a value for the token, or choose colors that reach the required ratio.",
|
|
1557
1557
|
"upgrade": "Move the project's theme onto the theme vocabulary with one dark-mode block. In starter.css and theme.css, rename each color token to the vocabulary name for its role (reference.md, Theme): a page color to --color-background, the main text to --color-foreground, a surface to --color-card with --color-card-foreground, the brand or action color to --color-primary with --color-primary-foreground, subdued text to --color-muted-foreground, a highlight to --color-accent with --color-accent-foreground, rules to --color-border, body and heading fonts to --font-sans and --font-heading, and a corner radius to --radius. Keep every value.\n\nMove each --color-<name>-dark value into @media (prefers-color-scheme: dark) { @theme { --color-<name>: <value>; } } under the renamed token, and delete the -dark tokens. In every template and partial, rename the utilities to match (bg-surface to bg-card, text-muted to text-muted-foreground, and so on) and delete each dark:…-dark class whose token now has a dark value; keep a dark: class only where it picks a different token in dark mode.\n\nThen fix what email-contrast still reports at its location: pair each fill with its -foreground token, or give the dark block a value that reaches the ratio. Do not weaken the design to pass: keep the brand's colors and adjust the paired token.\n\nCompile every entry and render every fixture in light and dark mode; confirm the light render is unchanged apart from fixes. Resolve errors before requesting one saved version for the whole project, approved by the user; do not publish."
|
|
1558
1558
|
},
|
|
1559
|
+
{
|
|
1560
|
+
"code": "email-external-asset",
|
|
1561
|
+
"summary": "An image points at another site's URL; a hotlinked image can disappear, be blocked, track recipients or slow the message.",
|
|
1562
|
+
"fix": "Add the image to the project's files and import it (import logo from './logo.png'), then use the import as the src."
|
|
1563
|
+
},
|
|
1559
1564
|
{
|
|
1560
1565
|
"code": "email-font-fallback",
|
|
1561
1566
|
"summary": "A font stack names a project font without a generic fallback.",
|
package/generated/reference.md
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
A template is one TSX file that default-exports `defineTemplate({ id, schema, fixtures, email })`
|
|
4
4
|
from `@samva/markup`, in `templates/` (`emails/` is also recognized). Set
|
|
5
5
|
`jsxImportSource: "@samva/markup/email"`. `email` is `{ subject, preheader?, body }`; each is a
|
|
6
|
-
function of the validated input that the compiler reads without running. The
|
|
7
|
-
every preview and send renders (
|
|
6
|
+
function of the validated input that the compiler reads without running. The compiled template it
|
|
7
|
+
produces is what every preview and send renders (compiled template version 1).
|
|
8
8
|
|
|
9
9
|
## Static profile
|
|
10
10
|
|
|
@@ -305,6 +305,7 @@ Global attributes: `id`, `title`, `lang`, `dir`, `role`.
|
|
|
305
305
|
| `dynamic-expression` | An expression outside the static profile: a call other than fmt.* and .map, a computed value, or a spread. | Compute the value where the message is sent and bind it from the payload. |
|
|
306
306
|
| `dynamic-numeric-prop` | A prop that sizes layout at compile time is not a number literal. | Write the number literally, for example height={44}. |
|
|
307
307
|
| `email-contrast` | 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). | Pair background and text tokens (bg-primary with text-primary-foreground), give the theme's dark-mode @theme a value for the token, or choose colors that reach the required ratio. |
|
|
308
|
+
| `email-external-asset` | An image points at another site's URL; a hotlinked image can disappear, be blocked, track recipients or slow the message. | Add the image to the project's files and import it (import logo from './logo.png'), then use the import as the src. |
|
|
308
309
|
| `email-font-fallback` | A font stack names a project font without a generic fallback. | End the stack with sans-serif, serif or monospace. |
|
|
309
310
|
| `email-font-family` | @font-face declares no font-family. | Add font-family. |
|
|
310
311
|
| `email-font-format` | @font-face uses a format other than WOFF2. | Convert the file to .woff2. |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@samva/markup",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.1",
|
|
4
4
|
"description": "Type-safe authoring and compilation primitives for Samva templates",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"email",
|
|
@@ -73,6 +73,10 @@
|
|
|
73
73
|
"types": "./dist/compiler.d.ts",
|
|
74
74
|
"import": "./dist/compiler.js"
|
|
75
75
|
},
|
|
76
|
+
"./compatibility": {
|
|
77
|
+
"types": "./dist/compatibility.d.ts",
|
|
78
|
+
"import": "./dist/compatibility.js"
|
|
79
|
+
},
|
|
76
80
|
"./diagnostics": {
|
|
77
81
|
"types": "./dist/diagnostics.d.ts",
|
|
78
82
|
"import": "./dist/diagnostics.js"
|
|
@@ -89,10 +93,6 @@
|
|
|
89
93
|
"types": "./dist/brand.d.ts",
|
|
90
94
|
"import": "./dist/brand.js"
|
|
91
95
|
},
|
|
92
|
-
"./brand-sample": {
|
|
93
|
-
"types": "./dist/brand-sample.d.ts",
|
|
94
|
-
"import": "./dist/brand-sample.js"
|
|
95
|
-
},
|
|
96
96
|
"./samva-brand": {
|
|
97
97
|
"types": "./samva-brand.d.ts"
|
|
98
98
|
},
|
package/skills/sml/SKILL.md
CHANGED
|
@@ -7,7 +7,7 @@ description: Author, check, render and repair Samva email templates written as s
|
|
|
7
7
|
|
|
8
8
|
A template is one `.tsx` file that default-exports `defineTemplate` from `@samva/markup`. It is
|
|
9
9
|
ordinary JSX, HTML and Tailwind written in a static profile: the compiler reads it without running
|
|
10
|
-
it and produces a JSON
|
|
10
|
+
it and produces a compiled template: JSON. Every preview and every send renders that template against the input, so
|
|
11
11
|
nothing you write executes at send time.
|
|
12
12
|
|
|
13
13
|
## The loop
|
|
@@ -23,7 +23,7 @@ samva templates dev # local editor with every fixture re
|
|
|
23
23
|
|
|
24
24
|
Add `--json` for diagnostics as data: each has a `code`, a location, a severity and a `fix`. Apply
|
|
25
25
|
the fix the diagnostic names rather than rewriting around it. Without the CLI, call
|
|
26
|
-
`compileTemplate` from `@samva/markup/compiler` and `
|
|
26
|
+
`compileTemplate` from `@samva/markup/compiler` and `render` from `@samva/markup/render`.
|
|
27
27
|
|
|
28
28
|
## Upgrades
|
|
29
29
|
|
package/dist/assets-BSpgZOSR.js
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
//#region src/email/assets.ts
|
|
2
|
-
/** Files a template may import, and what they are served as. */
|
|
3
|
-
const ASSET_CONTENT_TYPES = {
|
|
4
|
-
".png": "image/png",
|
|
5
|
-
".jpg": "image/jpeg",
|
|
6
|
-
".jpeg": "image/jpeg",
|
|
7
|
-
".gif": "image/gif",
|
|
8
|
-
".webp": "image/webp",
|
|
9
|
-
".avif": "image/avif",
|
|
10
|
-
".svg": "image/svg+xml",
|
|
11
|
-
".ico": "image/x-icon",
|
|
12
|
-
".woff": "font/woff",
|
|
13
|
-
".woff2": "font/woff2",
|
|
14
|
-
".ttf": "font/ttf",
|
|
15
|
-
".otf": "font/otf",
|
|
16
|
-
".pdf": "application/pdf",
|
|
17
|
-
".txt": "text/plain"
|
|
18
|
-
};
|
|
19
|
-
const extensionOf = (path) => {
|
|
20
|
-
const dot = path.lastIndexOf(".");
|
|
21
|
-
return dot < 0 ? "" : path.slice(dot).toLowerCase();
|
|
22
|
-
};
|
|
23
|
-
/** True when the compiler turns this import into a content-addressed asset. */
|
|
24
|
-
const isAssetPath = (path) => Object.hasOwn(ASSET_CONTENT_TYPES, extensionOf(path));
|
|
25
|
-
const hex = (buffer) => Array.from(new Uint8Array(buffer), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
26
|
-
/** Address one file by its content. */
|
|
27
|
-
const assetEntry = async (path, bytes) => {
|
|
28
|
-
const extension = extensionOf(path);
|
|
29
|
-
const view = new Uint8Array(bytes.byteLength);
|
|
30
|
-
view.set(bytes);
|
|
31
|
-
const digest = hex(await crypto.subtle.digest("SHA-256", view));
|
|
32
|
-
return {
|
|
33
|
-
path,
|
|
34
|
-
digest,
|
|
35
|
-
bytes: bytes.byteLength,
|
|
36
|
-
contentType: ASSET_CONTENT_TYPES[extension] ?? "application/octet-stream",
|
|
37
|
-
fileName: `${digest}${extension}`
|
|
38
|
-
};
|
|
39
|
-
};
|
|
40
|
-
/**
|
|
41
|
-
* The URL an asset has under a base. The base is a build input: publication
|
|
42
|
-
* passes its hosted origin, and a local export passes whatever it will serve
|
|
43
|
-
* the exported files from.
|
|
44
|
-
*/
|
|
45
|
-
const assetUrl = (entry, base) => `${base.replace(/\/+$/, "")}/${entry.fileName}`;
|
|
46
|
-
const EXTERNAL_IMAGE = /^https:\/\//i;
|
|
47
|
-
/** Record, but never fetch, a URL the template points at. */
|
|
48
|
-
const isExternalAsset = (reference) => EXTERNAL_IMAGE.test(reference);
|
|
49
|
-
/**
|
|
50
|
-
* Reject an asset base that would produce URLs a client will not load. The
|
|
51
|
-
* relative forms are the ones a page on disk resolves against its own
|
|
52
|
-
* directory, which is what a local export writes.
|
|
53
|
-
*/
|
|
54
|
-
const checkAssetBase = (base) => /^(https:\/\/|\.{1,2}\/|\/)/i.test(base) ? [] : [{
|
|
55
|
-
code: "invalid-asset-base",
|
|
56
|
-
severity: "error",
|
|
57
|
-
message: `Asset base ${JSON.stringify(base)} must be an https URL, an absolute path, or a relative path for a local export.`,
|
|
58
|
-
origins: []
|
|
59
|
-
}];
|
|
60
|
-
//#endregion
|
|
61
|
-
export { isAssetPath as a, checkAssetBase as i, assetEntry as n, isExternalAsset as o, assetUrl as r, ASSET_CONTENT_TYPES as t };
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { r as EmailDiagnostic } from "./diagnostics-CUYlQ_6x.js";
|
|
2
|
-
//#region src/email/assets.d.ts
|
|
3
|
-
/** Files a template may import, and what they are served as. */
|
|
4
|
-
declare const ASSET_CONTENT_TYPES: Readonly<Record<string, string>>;
|
|
5
|
-
interface AssetEntry {
|
|
6
|
-
/** Project-relative path the template imported. */
|
|
7
|
-
readonly path: string;
|
|
8
|
-
/** SHA-256 of the exact bytes, lowercase hex. */
|
|
9
|
-
readonly digest: string;
|
|
10
|
-
readonly bytes: number;
|
|
11
|
-
readonly contentType: string;
|
|
12
|
-
/** `<digest><ext>` — the name the asset has under any base. */
|
|
13
|
-
readonly fileName: string;
|
|
14
|
-
}
|
|
15
|
-
interface AssetManifest {
|
|
16
|
-
/** Where these assets are served from in this build. */
|
|
17
|
-
readonly base: string;
|
|
18
|
-
readonly assets: readonly AssetEntry[];
|
|
19
|
-
}
|
|
20
|
-
/** True when the compiler turns this import into a content-addressed asset. */
|
|
21
|
-
declare const isAssetPath: (path: string) => boolean;
|
|
22
|
-
/** Address one file by its content. */
|
|
23
|
-
declare const assetEntry: (path: string, bytes: Uint8Array) => Promise<AssetEntry>;
|
|
24
|
-
/**
|
|
25
|
-
* The URL an asset has under a base. The base is a build input: publication
|
|
26
|
-
* passes its hosted origin, and a local export passes whatever it will serve
|
|
27
|
-
* the exported files from.
|
|
28
|
-
*/
|
|
29
|
-
declare const assetUrl: (entry: AssetEntry, base: string) => string;
|
|
30
|
-
/** Record, but never fetch, a URL the template points at. */
|
|
31
|
-
declare const isExternalAsset: (reference: string) => boolean;
|
|
32
|
-
/**
|
|
33
|
-
* Reject an asset base that would produce URLs a client will not load. The
|
|
34
|
-
* relative forms are the ones a page on disk resolves against its own
|
|
35
|
-
* directory, which is what a local export writes.
|
|
36
|
-
*/
|
|
37
|
-
declare const checkAssetBase: (base: string) => readonly EmailDiagnostic[];
|
|
38
|
-
//#endregion
|
|
39
|
-
export { assetUrl as a, isExternalAsset as c, assetEntry as i, AssetEntry as n, checkAssetBase as o, AssetManifest as r, isAssetPath as s, ASSET_CONTENT_TYPES as t };
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { n as EmailContent } from "./template-B-UDXK8a.js";
|
|
2
|
-
import { t as BrandPlugin } from "./brand-plugin-CHhcei-j.js";
|
|
3
|
-
import { a as EmailBrand, n as BrandComponents } from "./brand-jFV6YtSV.js";
|
|
4
|
-
import { r as RenderedEmail } from "./render-CuZej25A.js";
|
|
5
|
-
//#region src/email/brand-footer.d.ts
|
|
6
|
-
/** A footer block for the send path to append, and its plain-text tail. */
|
|
7
|
-
interface RenderedBrandFooter {
|
|
8
|
-
readonly html: string;
|
|
9
|
-
readonly text: string;
|
|
10
|
-
/**
|
|
11
|
-
* The rules the footer needs in the message's `<head>`: its dark-mode colors and the
|
|
12
|
-
* `dark:` swap of the brand logo. Empty when it needs none.
|
|
13
|
-
*/
|
|
14
|
-
readonly headCss: string;
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Render `BrandFooter` against a brand on its own, for the send path to
|
|
18
|
-
* append to a message whose template renders no footer of its own. It
|
|
19
|
-
* compiles the same component, styled by the same brand theme, a template
|
|
20
|
-
* would get from its brand import. Without `unsubscribe` the unsubscribe row is
|
|
21
|
-
* left out.
|
|
22
|
-
*/
|
|
23
|
-
declare const renderBrandFooter: (brand: EmailBrand, options: {
|
|
24
|
-
readonly unsubscribe: boolean;
|
|
25
|
-
readonly brandPlugin?: BrandPlugin | undefined;
|
|
26
|
-
}) => Promise<RenderedBrandFooter>;
|
|
27
|
-
/**
|
|
28
|
-
* Render content against a brand as a project whose `theme.css` imports only
|
|
29
|
-
* the brand renders it: Tailwind compiles the brand theme for
|
|
30
|
-
* the classes the content names, `BrandLogo` and `BrandFooter` are bound to the
|
|
31
|
-
* brand, and the render inlines the result. A brand preview renders through
|
|
32
|
-
* this, so it shows what a template importing the brand compiles to.
|
|
33
|
-
*/
|
|
34
|
-
declare const renderBrandSample: (brand: EmailBrand, sample: {
|
|
35
|
-
/** Every class the content names; nothing is discovered from source here. */
|
|
36
|
-
readonly classes: readonly string[];
|
|
37
|
-
readonly render: (components: BrandComponents) => EmailContent;
|
|
38
|
-
}, brandPlugin?: BrandPlugin) => Promise<RenderedEmail>;
|
|
39
|
-
//#endregion
|
|
40
|
-
export { renderBrandFooter as n, renderBrandSample as r, RenderedBrandFooter as t };
|