@samva/markup 0.8.0 → 0.10.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 +107 -46
- package/CHANGELOG.md +345 -0
- package/README.md +188 -52
- package/dist/{brand-DcoVkh_S.d.ts → assets-BIsyMQ92.d.ts} +41 -11
- package/dist/{email/brand.js → brand-C0MKTwmX.js} +64 -4
- package/dist/{theme-css.d.ts → brand.d.ts} +12 -3
- package/dist/brand.js +4 -0
- package/dist/channel-specs-C2feJwuP.d.ts +13 -0
- package/dist/compiler.d.ts +88 -115
- package/dist/compiler.js +750 -171
- package/dist/components-BZCqegS8.js +1864 -0
- package/dist/diagnostic-model-DJtqbETd.js +38 -0
- package/dist/{diagnostics-OcQKQYSY.d.ts → diagnostic-model-DxthUZBT.d.ts} +3 -21
- package/dist/{diagnostics-BMYtBCQk.d.ts → diagnostics-B-Gym34R.d.ts} +8 -4
- package/dist/{email/diagnostics.js → diagnostics-CnKFefKz.js} +4 -3
- package/dist/diagnostics.d.ts +410 -2
- package/dist/diagnostics.js +402 -52
- package/dist/edit.d.ts +234 -0
- package/dist/edit.js +947 -0
- package/dist/email/jsx-dev-runtime.d.ts +2 -2
- package/dist/email/jsx-dev-runtime.js +1 -2
- package/dist/email/jsx-runtime.d.ts +1 -1
- package/dist/email/jsx-runtime.js +1 -85
- package/dist/{email/components.d.ts → email.d.ts} +22 -11
- package/dist/email.js +2 -0
- package/dist/fmt.d.ts +32 -0
- package/dist/fmt.js +174 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/{render-vAv_4r9v.js → ir-CLhlklKV.js} +96 -279
- package/dist/jsx-runtime-CBlexoeR.js +98 -0
- package/dist/{elements-BzjjnoCS.d.ts → jsx-runtime-ylIOnaSf.d.ts} +76 -76
- package/dist/profile-pWGtWZXF.js +3803 -0
- package/dist/render-BPonDpCl.d.ts +539 -0
- package/dist/render-BQmfh94k.js +257 -0
- package/dist/render.d.ts +35 -0
- package/dist/render.js +49 -0
- package/dist/sms.d.ts +16 -0
- package/dist/sms.js +12 -0
- package/dist/source-locations-C8wCsiAy.d.ts +23 -0
- package/dist/template-DMPwxMSq.d.ts +65 -0
- package/dist/template-zLUjcwfF.js +42 -0
- package/dist/{theme-css-hFqcPSar.js → theme-css-JtRCm1fb.js} +2 -2
- package/dist/whatsapp.d.ts +57 -0
- package/dist/whatsapp.js +22 -0
- package/generated/contract.json +1788 -0
- package/generated/reference.md +360 -0
- package/package.json +46 -239
- package/samva-brand.d.ts +4 -4
- package/skills/sml/SKILL.md +123 -0
- package/dist/ast-BrDtApwS.d.ts +0 -107
- package/dist/ast.d.ts +0 -2
- package/dist/ast.js +0 -124
- package/dist/candidate.d.ts +0 -29
- package/dist/candidate.js +0 -88
- package/dist/catalog-CKuOooB8.d.ts +0 -354
- package/dist/checks-D73Xizbw.js +0 -59366
- package/dist/checks.d.ts +0 -33
- package/dist/checks.js +0 -2
- package/dist/classes.d.ts +0 -26
- package/dist/classes.js +0 -184
- package/dist/compile-sms.d.ts +0 -28
- package/dist/compile-sms.js +0 -44
- package/dist/compile-whatsapp.d.ts +0 -56
- package/dist/compile-whatsapp.js +0 -186
- package/dist/compile.d.ts +0 -18
- package/dist/compile.js +0 -29
- package/dist/components.d.ts +0 -289
- package/dist/components.js +0 -106
- package/dist/conditionals.d.ts +0 -10
- package/dist/conditionals.js +0 -74
- package/dist/corpus-CPPfgAHN.d.ts +0 -106
- package/dist/css-CS2jLID6.d.ts +0 -109
- package/dist/email/assets.d.ts +0 -40
- package/dist/email/assets.js +0 -63
- package/dist/email/brand-footer.d.ts +0 -34
- package/dist/email/brand-footer.js +0 -62
- package/dist/email/brand.d.ts +0 -2
- package/dist/email/components.js +0 -240
- package/dist/email/diagnostics.d.ts +0 -2
- package/dist/email/elements.d.ts +0 -2
- package/dist/email/elements.js +0 -299
- package/dist/email/html.d.ts +0 -2
- package/dist/email/html.js +0 -278
- package/dist/email/render.d.ts +0 -50
- package/dist/email/render.js +0 -3
- package/dist/email/selection.d.ts +0 -2
- package/dist/email/selection.js +0 -91
- package/dist/email/source-edit.d.ts +0 -67
- package/dist/email/source-edit.js +0 -185
- package/dist/email/stylesheets.d.ts +0 -15
- package/dist/email/stylesheets.js +0 -48
- package/dist/email/text.d.ts +0 -7
- package/dist/email/text.js +0 -100
- package/dist/emit.d.ts +0 -51
- package/dist/emit.js +0 -151
- package/dist/html-Bl9BAdqc.d.ts +0 -42
- package/dist/interpolate.d.ts +0 -24
- package/dist/interpolate.js +0 -43
- package/dist/jsx-dev-runtime.d.ts +0 -11
- package/dist/jsx-dev-runtime.js +0 -15
- package/dist/jsx-runtime-I-1nCtl2.d.ts +0 -57
- package/dist/jsx-runtime.d.ts +0 -2
- package/dist/jsx-runtime.js +0 -121
- package/dist/lexer.d.ts +0 -60
- package/dist/lexer.js +0 -281
- package/dist/model.d.ts +0 -24
- package/dist/model.js +0 -77
- package/dist/ops-BvwqHl7V.js +0 -630
- package/dist/ops.d.ts +0 -114
- package/dist/ops.js +0 -2
- package/dist/parser-t29cHLVL.d.ts +0 -396
- package/dist/parser.d.ts +0 -2
- package/dist/parser.js +0 -859
- package/dist/predicate-BI6uRRM-.js +0 -549
- package/dist/predicate-M-05Nd7R.d.ts +0 -56
- package/dist/predicate.d.ts +0 -2
- package/dist/predicate.js +0 -2
- package/dist/printer.d.ts +0 -6
- package/dist/printer.js +0 -122
- package/dist/registry-Dcp6QEK_.d.ts +0 -73
- package/dist/registry.d.ts +0 -2
- package/dist/registry.js +0 -646
- package/dist/render-template.d.ts +0 -45
- package/dist/render-template.js +0 -76
- package/dist/selection-CCArx9lU.d.ts +0 -91
- package/dist/showcase-egkusQBf.d.ts +0 -415
- package/dist/sms-segments-y4FpPilJ.d.ts +0 -13
- package/dist/sms-segments.d.ts +0 -2
- package/dist/sms-segments.js +0 -46
- package/dist/source-locations-DrCM82Ym.d.ts +0 -67
- package/dist/source-locations.d.ts +0 -2
- package/dist/source-locations.js +0 -102
- package/dist/starters/category-mapping.d.ts +0 -13
- package/dist/starters/category-mapping.js +0 -49
- package/dist/starters/corpus.d.ts +0 -2
- package/dist/starters/corpus.js +0 -194
- package/dist/starters/library.d.ts +0 -18
- package/dist/starters/library.js +0 -692
- package/dist/starters/public-gallery.d.ts +0 -266
- package/dist/starters/public-gallery.js +0 -916
- package/dist/starters/public-handoff.d.ts +0 -164
- package/dist/starters/public-handoff.js +0 -361
- package/dist/starters/public-preview-artifacts.d.ts +0 -43
- package/dist/starters/public-preview-artifacts.js +0 -104
- package/dist/starters/public-preview.d.ts +0 -61
- package/dist/starters/public-preview.js +0 -83
- package/dist/starters/showcase.d.ts +0 -2
- package/dist/starters/showcase.js +0 -335
- package/dist/starters/sources.d.ts +0 -45
- package/dist/starters/sources.js +0 -7762
- package/dist/starters/theme.d.ts +0 -45
- package/dist/starters/theme.js +0 -77
- package/dist/starters.d.ts +0 -2
- package/dist/starters.js +0 -874
- package/dist/tailwind-BYq7Lo6u.js +0 -727
- package/dist/template-DWftqh4x.d.ts +0 -60
- package/dist/template.d.ts +0 -2
- package/dist/template.js +0 -37
- package/dist/theme-css.js +0 -2
- package/dist/tsx-source-instrumentation.d.ts +0 -30
- package/dist/tsx-source-instrumentation.js +0 -115
- package/dist/tsx-tracer.d.ts +0 -112
- package/dist/tsx-tracer.js +0 -985
- package/dist/variables-J_L3qxRa.d.ts +0 -77
- package/dist/variables.d.ts +0 -2
- package/dist/variables.js +0 -141
- package/dist/version-BITr58kk.js +0 -96
- package/generated/sml-agent-contract.json +0 -709
- package/generated/sml-email-agent-contract.json +0 -615
- package/generated/sml-email-language-reference.md +0 -94
- package/generated/sml-language-reference.md +0 -195
package/README.md
CHANGED
|
@@ -6,59 +6,195 @@ directly for TSX templates or together with `@samva/vite` for the visual editor
|
|
|
6
6
|
## Install
|
|
7
7
|
|
|
8
8
|
```sh
|
|
9
|
-
npm install --save-dev @samva/markup
|
|
9
|
+
npm install --save-dev @samva/markup
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
Configure TypeScript with `jsx: "react-jsx"` and
|
|
13
|
-
|
|
14
|
-
(or use a Zod 4 schema). Email entries use `.tsx` and default-export a definition:
|
|
12
|
+
Configure TypeScript with `jsx: "react-jsx"` and `jsxImportSource: "@samva/markup/email"`. A
|
|
13
|
+
template is one `.tsx` file that default-exports `defineTemplate`:
|
|
15
14
|
|
|
16
15
|
```tsx
|
|
17
|
-
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
16
|
+
import { defineTemplate } from "@samva/markup";
|
|
17
|
+
import { Button, Email, Section } from "@samva/markup/email";
|
|
18
|
+
import { fmt } from "@samva/markup/fmt";
|
|
19
|
+
import { jsonSchema } from "@samva/markup/input-schema";
|
|
20
|
+
|
|
21
|
+
import { OrderRow } from "../partials/order-row";
|
|
22
|
+
|
|
23
|
+
export default defineTemplate({
|
|
24
|
+
id: "order-shipped",
|
|
25
|
+
schema: jsonSchema<{
|
|
26
|
+
name: string;
|
|
27
|
+
orderId: string;
|
|
28
|
+
currency: string;
|
|
29
|
+
total: number;
|
|
30
|
+
items: { title: string; qty: number; price: number }[];
|
|
31
|
+
trackingUrl?: string;
|
|
32
|
+
}>({
|
|
33
|
+
type: "object",
|
|
34
|
+
properties: {
|
|
35
|
+
name: { type: "string" },
|
|
36
|
+
orderId: { type: "string" },
|
|
37
|
+
currency: { type: "string" },
|
|
38
|
+
total: { type: "number" },
|
|
39
|
+
items: {
|
|
40
|
+
type: "array",
|
|
41
|
+
items: {
|
|
42
|
+
type: "object",
|
|
43
|
+
properties: {
|
|
44
|
+
title: { type: "string" },
|
|
45
|
+
qty: { type: "number" },
|
|
46
|
+
price: { type: "number" },
|
|
47
|
+
},
|
|
48
|
+
required: ["title", "qty", "price"],
|
|
49
|
+
additionalProperties: false,
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
trackingUrl: { type: "string" },
|
|
53
|
+
},
|
|
54
|
+
required: ["name", "orderId", "currency", "total", "items"],
|
|
55
|
+
additionalProperties: false,
|
|
56
|
+
}),
|
|
57
|
+
fixtures: {
|
|
58
|
+
default: {
|
|
59
|
+
name: "Ada",
|
|
60
|
+
orderId: "A-1042",
|
|
61
|
+
currency: "USD",
|
|
62
|
+
total: 84,
|
|
63
|
+
items: [{ title: "Linen shirt", qty: 2, price: 42 }],
|
|
64
|
+
trackingUrl: "https://track.example.com/A-1042",
|
|
65
|
+
},
|
|
66
|
+
"no-tracking": {
|
|
67
|
+
name: "Ada",
|
|
68
|
+
orderId: "A-1042",
|
|
69
|
+
currency: "USD",
|
|
70
|
+
total: 84,
|
|
71
|
+
items: [{ title: "Linen shirt", qty: 2, price: 42 }],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
email: {
|
|
75
|
+
subject: (input) => `Order ${input.orderId} is on its way`,
|
|
76
|
+
preheader: (input) => `${input.items.length} items, ${fmt.money(input.total, input.currency)}`,
|
|
77
|
+
body: (input) => (
|
|
32
78
|
<Email>
|
|
33
|
-
<Section>
|
|
34
|
-
<h1>
|
|
35
|
-
{input.items.
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
79
|
+
<Section className="px-8 py-6">
|
|
80
|
+
<h1 className="text-2xl font-semibold">Hi {input.name}, it shipped.</h1>
|
|
81
|
+
{input.items.map((item) => (
|
|
82
|
+
<OrderRow item={item} currency={input.currency} />
|
|
83
|
+
))}
|
|
84
|
+
<p className="mt-4 font-medium">Total {fmt.money(input.total, input.currency)}</p>
|
|
85
|
+
{input.trackingUrl && (
|
|
86
|
+
<Button href={input.trackingUrl} width={200} height={44}>
|
|
87
|
+
Track package
|
|
88
|
+
</Button>
|
|
41
89
|
)}
|
|
42
90
|
</Section>
|
|
43
91
|
</Email>
|
|
44
92
|
),
|
|
45
|
-
}
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
A template is read, not run. `@samva/markup/compiler` parses the file and the project files it
|
|
98
|
+
imports without evaluating them, checks every binding against the schema, resolves Tailwind and CSS
|
|
99
|
+
against the markup, and produces the template's IR: JSON, versioned (`"sml": 1`), carrying the
|
|
100
|
+
markup, the bindings, the conditions, the loops and the formatters. `@samva/markup/render`
|
|
101
|
+
renders that IR against an input. It is a pure function with no dependencies, so nothing executes
|
|
102
|
+
at send time.
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { renderIr } from "@samva/markup/render";
|
|
106
|
+
import { compileTemplate } from "@samva/markup/compiler";
|
|
107
|
+
|
|
108
|
+
const compiled = await compileTemplate({
|
|
109
|
+
files, // the project's files by path; binary files become content-addressed assets
|
|
110
|
+
entry: "templates/order-shipped.tsx",
|
|
111
|
+
assetBase: "https://assets.example.com",
|
|
112
|
+
tailwind: { css: files["theme.css"] },
|
|
113
|
+
});
|
|
114
|
+
if (compiled.ir === undefined) throw new Error(compiled.diagnostics.map(String).join("\n"));
|
|
115
|
+
|
|
116
|
+
const { subject, preheader, html, text } = renderIr(compiled.ir, compiled.fixtures.default, {
|
|
117
|
+
locale: "en-US",
|
|
118
|
+
timeZone: "UTC",
|
|
46
119
|
});
|
|
47
120
|
```
|
|
48
121
|
|
|
49
|
-
The
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
122
|
+
### The static profile
|
|
123
|
+
|
|
124
|
+
The profile is the subset of TSX the compiler reads without running it: the forms an agent already
|
|
125
|
+
writes for React. Anything else is a compile error that names the fix.
|
|
126
|
+
|
|
127
|
+
| Form | Example |
|
|
128
|
+
| ------------------- | ------------------------------------------------------------------------ |
|
|
129
|
+
| Text binding | `{input.name}` |
|
|
130
|
+
| Attribute binding | `href={input.trackingUrl}` |
|
|
131
|
+
| Template string | `` `Order ${input.orderId}` `` |
|
|
132
|
+
| Conditional | `{input.trackingUrl && <Button … />}` |
|
|
133
|
+
| Either/or | `{input.vip ? <Gold /> : <Standard />}` |
|
|
134
|
+
| Conditions | `===`, `!==`, `<`, `>`, `<=`, `>=`, `!`, `&&`, `\|\|`, `.length` |
|
|
135
|
+
| Loop | `{input.items.map((item, i) => <Row … />)}` |
|
|
136
|
+
| Filter | `input.steps.filter((step) => step.done).map(…)`, `.filter(…).length` |
|
|
137
|
+
| Grid | `<Columns each={input.deals} per={2}>{(deal) => <Column … />}</Columns>` |
|
|
138
|
+
| Formatter | `fmt.money(input.total, input.currency)` |
|
|
139
|
+
| Partial | `<OrderRow item={item} />`, with props and `children` |
|
|
140
|
+
| Conditional class | `className={input.vip ? "bg-amber-100" : "bg-white"}` |
|
|
141
|
+
| Destructured params | `body: ({ name, items }) => …` |
|
|
142
|
+
| Arithmetic | `item.price * item.qty`, inside a formatter argument or a condition |
|
|
143
|
+
|
|
144
|
+
A `.filter` callback takes the item and one condition from the same grammar as `&&` and `?:`; it
|
|
145
|
+
chains before `.map(...)` or `.length`. `<Columns each per>` lays a list out in rows of `per` cells
|
|
146
|
+
(one row without `per`): the child is a function from an item, and its index within the row, to a
|
|
147
|
+
`Column`. A `Column` that states no `width` takes an equal share of the row, whole percent rounded
|
|
148
|
+
down, and a last row with fewer items keeps that share, so it leaves empty space instead of
|
|
149
|
+
stretching. Both lower to a loop in the IR, so nothing is computed at send time.
|
|
150
|
+
|
|
151
|
+
Rejected, each with its own diagnostic: calls other than `fmt.*` and `.map` (`dynamic-expression`),
|
|
152
|
+
statements in a body (`statement-in-body`), imports other than `@samva/markup` and project files
|
|
153
|
+
(`non-project-import`), event handlers, hooks and `dangerouslySetInnerHTML`, class names built at
|
|
154
|
+
run time (`dynamic-class`), and recursion (`recursive-partial`). There is no escape hatch: logic the
|
|
155
|
+
profile cannot express is computed by the caller and sent in the payload.
|
|
156
|
+
|
|
157
|
+
Each diagnostic has a stable `code`, the exact `file:line:column`, a one-sentence `message` and a
|
|
158
|
+
`fix`. `unknown-field` names the field and suggests the nearest one, `unguarded-optional` marks an
|
|
159
|
+
optional field read outside a guard, and `fixture-invalid` names the fixture that no longer matches
|
|
160
|
+
the schema.
|
|
161
|
+
|
|
162
|
+
Only `@samva/markup` and files inside the project can be imported, so a project has no other
|
|
163
|
+
dependency to install. A fixture is data: a literal, or constants and imported assets assembled from
|
|
164
|
+
literals.
|
|
165
|
+
|
|
166
|
+
### Formatters
|
|
167
|
+
|
|
168
|
+
`@samva/markup/fmt` is the only set of functions a body may call. Each one is deterministic `Intl`
|
|
169
|
+
with the locale and time zone the render supplies; the template's `locale` is the default and an
|
|
170
|
+
explicit argument overrides it.
|
|
171
|
+
|
|
172
|
+
| Formatter | Example | Output |
|
|
173
|
+
| -------------------------------------- | -------------------------------------------------- | ------------- |
|
|
174
|
+
| `fmt.money(amount, currency, locale?)` | `fmt.money(84, "EUR", "de")` | 84,00 € |
|
|
175
|
+
| `fmt.number(n, options?, locale?)` | `fmt.number(1234.5, { maximumFractionDigits: 0 })` | 1,235 |
|
|
176
|
+
| `fmt.date(iso, style?, locale?)` | `fmt.date(input.shippedAt, "medium")` | Sep 29, 2026 |
|
|
177
|
+
| `fmt.time(iso, style?, locale?)` | `fmt.time(input.slot, "short")` | 3:30 PM |
|
|
178
|
+
| `fmt.plural(n, forms, locale?)` | `fmt.plural(n, { one: "item", other: "items" })` | items |
|
|
179
|
+
| `fmt.list(values, locale?)` | `fmt.list(["Ada", "Grace"])` | Ada and Grace |
|
|
180
|
+
|
|
181
|
+
### Channels
|
|
182
|
+
|
|
183
|
+
One template carries one schema and a body for each channel it supports. `@samva/markup/sms` and
|
|
184
|
+
`@samva/markup/whatsapp` export the channel components (`Sms`; `WhatsApp` with `WhatsApp.Header`,
|
|
185
|
+
`.Body`, `.Footer`, `.Buttons`, `.UrlButton`, `.QuickReplyButton`, `.PhoneButton` and
|
|
186
|
+
`.CopyCodeButton`). Their names are distinct per channel, so a wrong prop fails in the type checker.
|
|
187
|
+
WhatsApp bindings compile to Meta's positional `{{1}}` placeholders (`whatsappTemplate`), and a
|
|
188
|
+
conditional in a WhatsApp text part is refused because Meta templates cannot branch. The platform
|
|
189
|
+
gates these channels.
|
|
53
190
|
|
|
54
191
|
### Explicit template inputs
|
|
55
192
|
|
|
56
|
-
`@samva/markup
|
|
57
|
-
channel's authoring helper; `TemplateDefinition` and `TemplateInput` are channel-neutral.
|
|
193
|
+
`@samva/markup` exports `defineTemplate` and `TemplateInput`.
|
|
58
194
|
The schema must implement Standard **JSON Schema** v1; a validator-only Standard Schema is
|
|
59
195
|
insufficient. `Schema.toStandardJSONSchemaV1` (Effect) and Zod 4 schemas support caller-input
|
|
60
|
-
inference, including
|
|
61
|
-
|
|
196
|
+
inference, including named fixtures. `TemplateInput<typeof template>` extracts the caller type.
|
|
197
|
+
Rendering receives input unchanged.
|
|
62
198
|
|
|
63
199
|
The portable profile uses JSON Schema 2020-12. Supported validation keywords are `type`, `enum`,
|
|
64
200
|
`const`, `required`, numeric bounds and `multipleOf`, string lengths and `pattern`, object/array
|
|
@@ -84,22 +220,22 @@ The pinned Zod/Effect adapter rejects lazy/suspended schema nodes before executi
|
|
|
84
220
|
author recursive contracts directly using local JSON Schema references. Custom predicate metadata
|
|
85
221
|
is checked for these vendors, but a converter-supplied JSON Schema constraint remains its contract.
|
|
86
222
|
|
|
87
|
-
###
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
223
|
+
### Authoring with an agent
|
|
224
|
+
|
|
225
|
+
The package ships what an agent needs without any other source. `AGENTS.md` opens with the model:
|
|
226
|
+
a template is TSX in the static profile that compiles to IR, and nothing runs at send time.
|
|
227
|
+
`@samva/markup/contract.json` lists the components with their props, the profile's forms, the
|
|
228
|
+
formatters, the HTML elements and every diagnostic code with its fix; it is generated from the
|
|
229
|
+
compiler, so it matches the version you installed. `skills/sml/SKILL.md` is the `sml` skill: it
|
|
230
|
+
routes an agent through write, check, render, look and repair. Run `samva templates check --json`
|
|
231
|
+
for diagnostics an agent can apply.
|
|
232
|
+
|
|
233
|
+
### Previewing a render
|
|
234
|
+
|
|
235
|
+
`renderIrPreview(ir, input)` from `@samva/markup/render` renders a fixture the way `renderIr` does
|
|
236
|
+
and stamps `data-samva-instance` on every element. Its `selections` map each stamped element to the
|
|
237
|
+
line and column of the JSX that produced it, so an editor can select an element on the canvas and
|
|
238
|
+
edit the source at that span. A delivered message never carries the attribute.
|
|
103
239
|
|
|
104
240
|
Portable validation uses `@cfworker/json-schema` without runtime code generation. The
|
|
105
241
|
2020-12 meta-schema is checked at every supported schema position, and the explicit profile rejects
|
|
@@ -1,12 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* The per-recipient unsubscribe URL is not known at render: the send path
|
|
5
|
-
* substitutes each delivery's link for this placeholder, or removes the
|
|
6
|
-
* unsubscribe row when the send resolved no unsubscribe group.
|
|
7
|
-
*/
|
|
8
|
-
declare const UNSUBSCRIBE_URL_PLACEHOLDER = "%samva:unsubscribe-url%";
|
|
9
|
-
//#endregion
|
|
1
|
+
import { r as EmailDiagnostic } from "./diagnostics-B-Gym34R.js";
|
|
2
|
+
import { r as EmailNode } from "./jsx-runtime-ylIOnaSf.js";
|
|
10
3
|
//#region src/email/brand.d.ts
|
|
11
4
|
interface EmailBrandAddress {
|
|
12
5
|
readonly street1: string;
|
|
@@ -84,5 +77,42 @@ declare const withoutUnsubscribeRow: (rendered: {
|
|
|
84
77
|
readonly text: string | undefined;
|
|
85
78
|
};
|
|
86
79
|
//#endregion
|
|
87
|
-
|
|
88
|
-
|
|
80
|
+
//#region src/email/assets.d.ts
|
|
81
|
+
/** Files a template may import, and what they are served as. */
|
|
82
|
+
declare const ASSET_CONTENT_TYPES: Readonly<Record<string, string>>;
|
|
83
|
+
interface AssetEntry {
|
|
84
|
+
/** Project-relative path the template imported. */
|
|
85
|
+
readonly path: string;
|
|
86
|
+
/** SHA-256 of the exact bytes, lowercase hex. */
|
|
87
|
+
readonly digest: string;
|
|
88
|
+
readonly bytes: number;
|
|
89
|
+
readonly contentType: string;
|
|
90
|
+
/** `<digest><ext>` — the name the asset has under any base. */
|
|
91
|
+
readonly fileName: string;
|
|
92
|
+
}
|
|
93
|
+
interface AssetManifest {
|
|
94
|
+
/** Where these assets are served from in this build. */
|
|
95
|
+
readonly base: string;
|
|
96
|
+
readonly assets: readonly AssetEntry[];
|
|
97
|
+
}
|
|
98
|
+
/** True when the compiler turns this import into a content-addressed asset. */
|
|
99
|
+
declare const isAssetPath: (path: string) => boolean;
|
|
100
|
+
/** Address one file by its content. */
|
|
101
|
+
declare const assetEntry: (path: string, bytes: Uint8Array) => Promise<AssetEntry>;
|
|
102
|
+
/**
|
|
103
|
+
* The URL an asset has under a base. The base is a build input: publication
|
|
104
|
+
* passes its hosted origin, and a local export passes whatever it will serve
|
|
105
|
+
* the exported files from.
|
|
106
|
+
*/
|
|
107
|
+
declare const assetUrl: (entry: AssetEntry, base: string) => string;
|
|
108
|
+
/** Record, but never fetch, a URL the template points at. */
|
|
109
|
+
declare const isExternalAsset: (reference: string) => boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Reject an asset base that would produce URLs a client will not load. The
|
|
112
|
+
* relative forms are the ones a page on disk resolves against its own
|
|
113
|
+
* directory, which is what a local export writes.
|
|
114
|
+
*/
|
|
115
|
+
declare const checkAssetBase: (base: string) => readonly EmailDiagnostic[];
|
|
116
|
+
//#endregion
|
|
117
|
+
export { withoutUnsubscribeRow as C, renderedFooterMarkers as S, EmailBrandSocialLink as _, assetUrl as a, UNSUBSCRIBE_ROW_ATTRIBUTE as b, isExternalAsset as c, BrandFooterProps as d, BrandLogoProps as f, EmailBrandFooter as g, EmailBrandAssets as h, assetEntry as i, BRAND_COMPONENT_CLASSES as l, EmailBrandAddress as m, AssetEntry as n, checkAssetBase as o, EmailBrand as p, AssetManifest as r, isAssetPath as s, ASSET_CONTENT_TYPES as t, BrandComponents as u, FOOTER_ATTRIBUTE as v, brandComponents as x, RenderedFooterMarkers as y };
|
|
118
|
+
//# sourceMappingURL=assets-BIsyMQ92.d.ts.map
|
|
@@ -1,5 +1,65 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { i as UNSUBSCRIBE_URL_PLACEHOLDER, r as UNSUBSCRIBE_LINK, t as BRAND_FOOTER_MARKER } from "./unsubscribe-link-CoesnQDU.js";
|
|
2
|
+
import { a as jsx, n as Fragment } from "./jsx-runtime-CBlexoeR.js";
|
|
3
|
+
//#region src/email/assets.ts
|
|
4
|
+
/** Files a template may import, and what they are served as. */
|
|
5
|
+
const ASSET_CONTENT_TYPES = {
|
|
6
|
+
".png": "image/png",
|
|
7
|
+
".jpg": "image/jpeg",
|
|
8
|
+
".jpeg": "image/jpeg",
|
|
9
|
+
".gif": "image/gif",
|
|
10
|
+
".webp": "image/webp",
|
|
11
|
+
".avif": "image/avif",
|
|
12
|
+
".svg": "image/svg+xml",
|
|
13
|
+
".ico": "image/x-icon",
|
|
14
|
+
".woff": "font/woff",
|
|
15
|
+
".woff2": "font/woff2",
|
|
16
|
+
".ttf": "font/ttf",
|
|
17
|
+
".otf": "font/otf",
|
|
18
|
+
".pdf": "application/pdf",
|
|
19
|
+
".txt": "text/plain"
|
|
20
|
+
};
|
|
21
|
+
const extensionOf = (path) => {
|
|
22
|
+
const dot = path.lastIndexOf(".");
|
|
23
|
+
return dot < 0 ? "" : path.slice(dot).toLowerCase();
|
|
24
|
+
};
|
|
25
|
+
/** True when the compiler turns this import into a content-addressed asset. */
|
|
26
|
+
const isAssetPath = (path) => Object.hasOwn(ASSET_CONTENT_TYPES, extensionOf(path));
|
|
27
|
+
const hex = (buffer) => Array.from(new Uint8Array(buffer), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
28
|
+
/** Address one file by its content. */
|
|
29
|
+
const assetEntry = async (path, bytes) => {
|
|
30
|
+
const extension = extensionOf(path);
|
|
31
|
+
const view = new Uint8Array(bytes.byteLength);
|
|
32
|
+
view.set(bytes);
|
|
33
|
+
const digest = hex(await crypto.subtle.digest("SHA-256", view));
|
|
34
|
+
return {
|
|
35
|
+
path,
|
|
36
|
+
digest,
|
|
37
|
+
bytes: bytes.byteLength,
|
|
38
|
+
contentType: ASSET_CONTENT_TYPES[extension] ?? "application/octet-stream",
|
|
39
|
+
fileName: `${digest}${extension}`
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* The URL an asset has under a base. The base is a build input: publication
|
|
44
|
+
* passes its hosted origin, and a local export passes whatever it will serve
|
|
45
|
+
* the exported files from.
|
|
46
|
+
*/
|
|
47
|
+
const assetUrl = (entry, base) => `${base.replace(/\/+$/, "")}/${entry.fileName}`;
|
|
48
|
+
const EXTERNAL_IMAGE = /^https:\/\//i;
|
|
49
|
+
/** Record, but never fetch, a URL the template points at. */
|
|
50
|
+
const isExternalAsset = (reference) => EXTERNAL_IMAGE.test(reference);
|
|
51
|
+
/**
|
|
52
|
+
* Reject an asset base that would produce URLs a client will not load. The
|
|
53
|
+
* relative forms are the ones a page on disk resolves against its own
|
|
54
|
+
* directory, which is what a local export writes.
|
|
55
|
+
*/
|
|
56
|
+
const checkAssetBase = (base) => /^(https:\/\/|\.{1,2}\/|\/)/i.test(base) ? [] : [{
|
|
57
|
+
code: "invalid-asset-base",
|
|
58
|
+
severity: "error",
|
|
59
|
+
message: `Asset base ${JSON.stringify(base)} must be an https URL, an absolute path, or a relative path for a local export.`,
|
|
60
|
+
origins: []
|
|
61
|
+
}];
|
|
62
|
+
//#endregion
|
|
3
63
|
//#region src/email/brand.ts
|
|
4
64
|
/** Marks the block the send path recognizes as the template's own footer. */
|
|
5
65
|
const FOOTER_ATTRIBUTE = "data-samva-footer";
|
|
@@ -143,6 +203,6 @@ const withoutUnsubscribeRow = (rendered) => ({
|
|
|
143
203
|
text: rendered.text?.split("\n").filter((line) => !line.includes(UNSUBSCRIBE_URL_PLACEHOLDER)).join("\n")
|
|
144
204
|
});
|
|
145
205
|
//#endregion
|
|
146
|
-
export {
|
|
206
|
+
export { renderedFooterMarkers as a, assetEntry as c, isAssetPath as d, isExternalAsset as f, brandComponents as i, assetUrl as l, FOOTER_ATTRIBUTE as n, withoutUnsubscribeRow as o, UNSUBSCRIBE_ROW_ATTRIBUTE as r, ASSET_CONTENT_TYPES as s, BRAND_COMPONENT_CLASSES as t, checkAssetBase as u };
|
|
147
207
|
|
|
148
|
-
//# sourceMappingURL=brand.js.map
|
|
208
|
+
//# sourceMappingURL=brand-C0MKTwmX.js.map
|
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
import { t as
|
|
1
|
+
import { C as withoutUnsubscribeRow, S as renderedFooterMarkers, _ as EmailBrandSocialLink, a as assetUrl, b as UNSUBSCRIBE_ROW_ATTRIBUTE, c as isExternalAsset, d as BrandFooterProps, f as BrandLogoProps, g as EmailBrandFooter, h as EmailBrandAssets, i as assetEntry, l as BRAND_COMPONENT_CLASSES, m as EmailBrandAddress, n as AssetEntry, o as checkAssetBase, p as EmailBrand, r as AssetManifest, s as isAssetPath, t as ASSET_CONTENT_TYPES, u as BrandComponents, v as FOOTER_ATTRIBUTE, x as brandComponents, y as RenderedFooterMarkers } from "./assets-BIsyMQ92.js";
|
|
2
|
+
import { t as Diagnostic } from "./diagnostic-model-DxthUZBT.js";
|
|
2
3
|
import { Result } from "better-result";
|
|
4
|
+
//#region src/internal/unsubscribe-link.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* The per-recipient unsubscribe URL is not known at render: the send path
|
|
7
|
+
* substitutes each delivery's link for this placeholder, or removes the
|
|
8
|
+
* unsubscribe row when the send resolved no unsubscribe group.
|
|
9
|
+
*/
|
|
10
|
+
declare const UNSUBSCRIBE_URL_PLACEHOLDER = "%samva:unsubscribe-url%";
|
|
11
|
+
//#endregion
|
|
3
12
|
//#region src/theme-css.d.ts
|
|
4
13
|
/** One `@font-face` a brand declares. `src` is the declaration value as authored. */
|
|
5
14
|
interface BrandFontFace {
|
|
@@ -61,5 +70,5 @@ declare const brandThemeVariables: (brand: BrandCss) => Readonly<Record<string,
|
|
|
61
70
|
/** The stylesheet Tailwind compiles for `@import "samva:brand"`: `brandThemeVariables` and the font faces. */
|
|
62
71
|
declare const brandTailwindCss: (brand: BrandCss) => string;
|
|
63
72
|
//#endregion
|
|
64
|
-
export { BRAND_COLOR_VARIABLE, BRAND_FOREGROUND_VARIABLE, BrandCss, BrandFontFace, brandForegroundDefaults, brandTailwindCss, brandThemeVariables, contrastForeground, darkVariableName, parseBrandCss, printBrandCss };
|
|
65
|
-
//# sourceMappingURL=
|
|
73
|
+
export { ASSET_CONTENT_TYPES, AssetEntry, AssetManifest, BRAND_COLOR_VARIABLE, BRAND_COMPONENT_CLASSES, BRAND_FOREGROUND_VARIABLE, BrandComponents, BrandCss, BrandFontFace, BrandFooterProps, BrandLogoProps, EmailBrand, EmailBrandAddress, EmailBrandAssets, EmailBrandFooter, EmailBrandSocialLink, FOOTER_ATTRIBUTE, RenderedFooterMarkers, UNSUBSCRIBE_ROW_ATTRIBUTE, UNSUBSCRIBE_URL_PLACEHOLDER, assetEntry, assetUrl, brandComponents, brandForegroundDefaults, brandTailwindCss, brandThemeVariables, checkAssetBase, contrastForeground, darkVariableName, isAssetPath, isExternalAsset, parseBrandCss, printBrandCss, renderedFooterMarkers, withoutUnsubscribeRow };
|
|
74
|
+
//# sourceMappingURL=brand.d.ts.map
|
package/dist/brand.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { i as UNSUBSCRIBE_URL_PLACEHOLDER } from "./unsubscribe-link-CoesnQDU.js";
|
|
2
|
+
import { a as brandThemeVariables, c as parseBrandCss, i as brandTailwindCss, l as printBrandCss, n as BRAND_FOREGROUND_VARIABLE, o as contrastForeground, r as brandForegroundDefaults, s as darkVariableName, t as BRAND_COLOR_VARIABLE } from "./theme-css-JtRCm1fb.js";
|
|
3
|
+
import { a as renderedFooterMarkers, c as assetEntry, d as isAssetPath, f as isExternalAsset, i as brandComponents, l as assetUrl, n as FOOTER_ATTRIBUTE, o as withoutUnsubscribeRow, r as UNSUBSCRIBE_ROW_ATTRIBUTE, s as ASSET_CONTENT_TYPES, t as BRAND_COMPONENT_CLASSES, u as checkAssetBase } from "./brand-C0MKTwmX.js";
|
|
4
|
+
export { ASSET_CONTENT_TYPES, BRAND_COLOR_VARIABLE, BRAND_COMPONENT_CLASSES, BRAND_FOREGROUND_VARIABLE, FOOTER_ATTRIBUTE, UNSUBSCRIBE_ROW_ATTRIBUTE, UNSUBSCRIBE_URL_PLACEHOLDER, assetEntry, assetUrl, brandComponents, brandForegroundDefaults, brandTailwindCss, brandThemeVariables, checkAssetBase, contrastForeground, darkVariableName, isAssetPath, isExternalAsset, parseBrandCss, printBrandCss, renderedFooterMarkers, withoutUnsubscribeRow };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/channel-specs.d.ts
|
|
2
|
+
declare const SMS_CATEGORIES: readonly ["transactional", "promotional", "service_implicit"];
|
|
3
|
+
declare const WHATSAPP_CATEGORIES: readonly ["marketing", "utility", "authentication"];
|
|
4
|
+
declare const WHATSAPP_HEADER_TYPES: readonly ["text", "image", "video", "document"];
|
|
5
|
+
/** The SMS categories `<Sms category>` accepts. */
|
|
6
|
+
type SmsCategory = (typeof SMS_CATEGORIES)[number];
|
|
7
|
+
/** The Meta template categories `<WhatsApp category>` accepts. */
|
|
8
|
+
type WhatsAppCategory = (typeof WHATSAPP_CATEGORIES)[number];
|
|
9
|
+
/** The header kinds `<WhatsApp.Header type>` accepts. */
|
|
10
|
+
type WhatsAppHeaderType = (typeof WHATSAPP_HEADER_TYPES)[number];
|
|
11
|
+
//#endregion
|
|
12
|
+
export { WhatsAppCategory as n, WhatsAppHeaderType as r, SmsCategory as t };
|
|
13
|
+
//# sourceMappingURL=channel-specs-C2feJwuP.d.ts.map
|