@samva/markup 0.7.0 → 0.9.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 +104 -37
- package/README.md +188 -52
- package/dist/assets-C5duwTya.d.ts +40 -0
- package/dist/brand-B-0bN_Ir.d.ts +88 -0
- package/dist/{render-aaJBnsXK.js → cascade-t6kxlgEI.js} +130 -160
- package/dist/channel-edit.d.ts +121 -0
- package/dist/channel-edit.js +609 -0
- package/dist/channel-specs-B6M_HlaV.js +29 -0
- package/dist/channel-specs-C2feJwuP.d.ts +13 -0
- package/dist/compatibility-DbISoFIq.js +589 -0
- package/dist/compiler.d.ts +19 -249
- package/dist/compiler.js +10 -1408
- package/dist/{css-COcFOT1y.d.ts → css-BVQKICKC.d.ts} +30 -4
- package/dist/diagnostic-codes.d.ts +403 -0
- package/dist/diagnostic-codes.js +399 -0
- package/dist/{diagnostics-BMYtBCQk.d.ts → diagnostics-B-Gym34R.d.ts} +8 -4
- package/dist/diagnostics.d.ts +31 -2
- package/dist/diagnostics.js +1 -19
- package/dist/{elements-BzjjnoCS.d.ts → elements-cqldpaJt.d.ts} +3 -3
- package/dist/email/assets.d.ts +2 -0
- package/dist/email/assets.js +63 -0
- package/dist/email/brand-footer.d.ts +34 -0
- package/dist/email/brand-footer.js +63 -0
- package/dist/email/brand.d.ts +2 -0
- package/dist/email/brand.js +148 -0
- package/dist/email/components.d.ts +21 -10
- package/dist/email/components.js +46 -11
- package/dist/email/diagnostics.d.ts +2 -2
- package/dist/email/diagnostics.js +2 -1
- package/dist/email/elements.d.ts +1 -1
- package/dist/email/html.d.ts +2 -42
- package/dist/email/html.js +56 -2
- package/dist/email/jsx-dev-runtime.d.ts +2 -2
- package/dist/email/jsx-runtime.d.ts +1 -1
- package/dist/email/render.d.ts +10 -13
- package/dist/email/render.js +163 -6
- package/dist/email/text.d.ts +1 -1
- package/dist/fmt.d.ts +32 -0
- package/dist/fmt.js +174 -0
- package/dist/html-BJepOwZ4.d.ts +56 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/ir-aaqNE987.d.ts +252 -0
- package/dist/ir.d.ts +2 -0
- package/dist/ir.js +7 -0
- package/dist/preview-Ekdg1dXG.d.ts +100 -0
- package/dist/preview.d.ts +2 -0
- package/dist/preview.js +160 -0
- package/dist/profile-BH-OtdDA.js +3670 -0
- package/dist/render-ir.d.ts +19 -0
- package/dist/render-ir.js +826 -0
- package/dist/sms-segments.d.ts +13 -2
- package/dist/sms.d.ts +16 -0
- package/dist/sms.js +12 -0
- package/dist/{email/source-edit.d.ts → source-edit.d.ts} +55 -6
- package/dist/source-edit.js +343 -0
- package/dist/source-locations-C8wCsiAy.d.ts +23 -0
- package/dist/source-locations.d.ts +2 -2
- package/dist/source-locations.js +3 -89
- package/dist/static.d.ts +57 -0
- package/dist/static.js +69 -0
- package/dist/tailwind-CMYpZf1v.js +614 -0
- package/dist/template-_QE_rQND.d.ts +65 -0
- package/dist/template.d.ts +2 -2
- package/dist/template.js +15 -10
- package/dist/theme-css-hFqcPSar.js +587 -0
- package/dist/theme-css.d.ts +57 -16
- package/dist/theme-css.js +2 -509
- package/dist/theme-imports-lvBw3AD1.js +109 -0
- package/dist/transform-Ct2OMkFN.d.ts +153 -0
- package/dist/unsubscribe-link-CoesnQDU.js +26 -0
- package/dist/whatsapp.d.ts +57 -0
- package/dist/whatsapp.js +22 -0
- package/generated/contract.json +1782 -0
- package/generated/reference.md +353 -0
- package/package.json +56 -154
- package/samva-brand.d.ts +14 -0
- 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-60eaYC7b.d.ts +0 -356
- package/dist/checks-DBweSIPN.js +0 -58954
- package/dist/checks.d.ts +0 -33
- package/dist/checks.js +0 -2
- package/dist/classes.d.ts +0 -27
- 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-BjYMqOoY.d.ts +0 -106
- package/dist/diagnostics-OcQKQYSY.d.ts +0 -49
- package/dist/email/selection.d.ts +0 -91
- package/dist/email/selection.js +0 -91
- package/dist/email/source-edit.js +0 -185
- package/dist/email/stylesheets.d.ts +0 -15
- package/dist/email/stylesheets.js +0 -45
- package/dist/emit.d.ts +0 -51
- package/dist/emit.js +0 -151
- package/dist/footer.d.ts +0 -66
- package/dist/footer.js +0 -93
- 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-2aiiYO7m.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/presets/category-mapping.d.ts +0 -13
- package/dist/presets/category-mapping.js +0 -49
- package/dist/presets/corpus.d.ts +0 -2
- package/dist/presets/corpus.js +0 -194
- package/dist/presets/public-gallery.d.ts +0 -266
- package/dist/presets/public-gallery.js +0 -916
- package/dist/presets/public-handoff.d.ts +0 -164
- package/dist/presets/public-handoff.js +0 -361
- package/dist/presets/public-preview-artifacts.d.ts +0 -43
- package/dist/presets/public-preview-artifacts.js +0 -104
- package/dist/presets/public-preview.d.ts +0 -61
- package/dist/presets/public-preview.js +0 -83
- package/dist/presets/showcase.d.ts +0 -2
- package/dist/presets/showcase.js +0 -627
- package/dist/presets/sources.d.ts +0 -38
- package/dist/presets/sources.js +0 -6083
- package/dist/presets.d.ts +0 -2
- package/dist/presets.js +0 -881
- package/dist/printer.d.ts +0 -6
- package/dist/printer.js +0 -122
- package/dist/registry.d.ts +0 -42
- package/dist/registry.js +0 -646
- package/dist/render-template.d.ts +0 -45
- package/dist/render-template.js +0 -76
- package/dist/showcase-DGZAWn4Z.d.ts +0 -456
- package/dist/sms-segments-y4FpPilJ.d.ts +0 -13
- package/dist/source-locations-DrCM82Ym.d.ts +0 -67
- package/dist/template-DWftqh4x.d.ts +0 -60
- package/dist/theme-6ApjjiBo.d.ts +0 -116
- package/dist/theme.d.ts +0 -2
- package/dist/theme.js +0 -118
- package/dist/tokens.d.ts +0 -6
- package/dist/tokens.js +0 -417
- 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/AGENTS.md
CHANGED
|
@@ -1,35 +1,100 @@
|
|
|
1
1
|
# @samva/markup agent guide
|
|
2
2
|
|
|
3
|
-
`@samva/markup` is the authoring language and compiler for Samva templates.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
`@samva/markup` is the authoring language and compiler for Samva templates. A template is one TSX
|
|
4
|
+
file in a static profile that default-exports `defineTemplate`: ordinary JSX, HTML and Tailwind that
|
|
5
|
+
a compiler reads without running. The compiler produces a versioned JSON IR, and a pure renderer
|
|
6
|
+
turns the IR and an input into subject, preheader, HTML and text, so nothing executes at send time.
|
|
7
|
+
A body that would compute over its input is a diagnostic, not a second mode: compute the value in
|
|
8
|
+
the caller and send it in the payload. The package is plain TypeScript with no application runtime;
|
|
9
|
+
the local editor, project builds, and exports live in `@samva/vite`.
|
|
8
10
|
|
|
9
11
|
## Entry points
|
|
10
12
|
|
|
11
|
-
- `@samva/markup
|
|
13
|
+
- `@samva/markup` — `defineTemplate({ id, schema, fixtures, locale?, email?, sms?, whatsapp? })`,
|
|
14
|
+
`TemplateInput`, and `TEMPLATE_ID_PATTERN` (lowercase kebab-case, the rule the CLI and API slugs
|
|
15
|
+
share). `email` is `{ subject, preheader?, body }`; each is a function of the validated input.
|
|
16
|
+
- `@samva/markup/static` — `compileTemplate`, which reads a project's files and returns the IR,
|
|
17
|
+
the fixtures, the assets and the diagnostics; `checkTemplateCompatibility` checks each fixture
|
|
18
|
+
against the client matrix and locates findings at source. It needs the optional peers Babel,
|
|
19
|
+
Tailwind, css-tree and caniemail.
|
|
20
|
+
`checkStaticProfile(source | files, entry?)` is the synchronous structural check: it runs the
|
|
21
|
+
compiler's lowering without Tailwind, a brand or the files an import names, and returns only
|
|
22
|
+
findings the source itself owns. `introducedProfileErrors(before, after)` is what a visual edit
|
|
23
|
+
is refused on.
|
|
24
|
+
- `@samva/markup/source-edit` — the code transforms behind visual edits (`inspectSourceElement`,
|
|
25
|
+
`literalReplacement`, `expressionReplacement`, `classNameEdit`, `structuralReplacements`,
|
|
26
|
+
`applySourceReplacements` with a `ProfileGuard`). Browser-safe: Babel parser only.
|
|
27
|
+
- `@samva/markup/channel-edit` — form reads and exact source edits for the `sms` and `whatsapp`
|
|
28
|
+
channel bodies (`readSmsChannel`, `editSmsChannel`, `readWhatsAppChannel`,
|
|
29
|
+
`editWhatsAppChannel`). Text between a body's expressions is editable; an expression,
|
|
30
|
+
conditional or element is a locked segment that no edit touches. Each edit is a list of
|
|
31
|
+
`SourceReplacement`s for `applySourceReplacements`. Browser-safe: Babel parser only.
|
|
32
|
+
- `@samva/markup/render-ir` — `renderIr(ir, input, { locale, timeZone })`, plus `renderIrSms` and
|
|
33
|
+
`renderIrWhatsApp`. Dependency-free and the send-time security boundary. `@samva/markup/ir` holds
|
|
34
|
+
the IR types and `SML_IR_VERSION`.
|
|
35
|
+
- `@samva/markup/fmt` — `fmt.money`, `number`, `date`, `time`, `plural` and `list`: the only
|
|
36
|
+
functions a body may call.
|
|
37
|
+
- `@samva/markup/sms` and `@samva/markup/whatsapp` — the channel components (`Sms`; `WhatsApp` with
|
|
38
|
+
`WhatsApp.Header`, `Body`, `Footer`, `Buttons`, `UrlButton`, `QuickReplyButton`, `PhoneButton`,
|
|
39
|
+
`CopyCodeButton`). `whatsappTemplate` derives Meta's positional `{{n}}` text.
|
|
40
|
+
- `@samva/markup/template` — `defineTemplate`, `SmlTemplate`, `TemplateInput`, and
|
|
12
41
|
`TEMPLATE_NAME_PATTERN`.
|
|
42
|
+
- `@samva/markup/preview` — `renderIrPreview(ir, input)`: the render an editor shows, with an
|
|
43
|
+
instance path (`INSTANCE_PATH_ATTRIBUTE`) stamped on every element and the `selections` that map
|
|
44
|
+
each one to the span of TSX that produced it.
|
|
13
45
|
- `@samva/markup/email` — the JSX runtime. Set `jsxImportSource: "@samva/markup/email"` for email
|
|
14
46
|
entries.
|
|
15
47
|
- `@samva/markup/email/components` — email primitives (`Email`, `Section`, `Columns`, `Button`,
|
|
16
48
|
and the rest of its exports). They generate table layout and classic-Outlook fallbacks.
|
|
17
|
-
- `@samva/markup/email/render` — `
|
|
18
|
-
|
|
49
|
+
- `@samva/markup/email/render` — `compileEmail(tree)` and `renderEmailContent`, which lower a
|
|
50
|
+
derived email tree to HTML and text.
|
|
19
51
|
- `@samva/markup/email/diagnostics` — `EmailDiagnostic` and `EmailCompileError`.
|
|
52
|
+
- `@samva/markup/email/assets` — `isAssetPath` and `ASSET_CONTENT_TYPES`: which imported files
|
|
53
|
+
become content-addressed assets, without loading the compiler.
|
|
20
54
|
- `@samva/markup/input-schema` — `inputSchema`, which turns any Standard JSON Schema into the
|
|
21
55
|
portable JSON Schema and validator a template's input is checked against.
|
|
22
|
-
- `@samva/markup/compiler` —
|
|
23
|
-
Vite, esbuild, or Node builtins, so it runs in any
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
`
|
|
56
|
+
- `@samva/markup/compiler` — the compiler's building blocks (`transformProject`, Tailwind, the
|
|
57
|
+
cascade, diagnostics). It imports no Vite, esbuild, or Node builtins, so it runs in any
|
|
58
|
+
JavaScript host.
|
|
59
|
+
- `samva:brand` — `BrandLogo` (light logo, swapped for the dark one where the client honors dark
|
|
60
|
+
mode) and `BrandFooter` (company, postal address, social links and the unsubscribe row the send
|
|
61
|
+
path fills). The compiler binds them to the brand the build resolves; add
|
|
62
|
+
`"types": ["@samva/markup/samva-brand"]` to the project's tsconfig for `tsc`. In `theme.css`,
|
|
63
|
+
`@import "samva:brand";` layers the brand theme; `samva:brand/<slug>` names one brand.
|
|
27
64
|
|
|
28
|
-
The package
|
|
29
|
-
|
|
65
|
+
The package ships what an agent needs to author without this repository:
|
|
66
|
+
|
|
67
|
+
- `@samva/markup/contract.json` (`generated/contract.json`) — components with their props, the
|
|
68
|
+
static-profile forms and the forms it refuses, formatters, HTML elements, channel specs and every
|
|
69
|
+
diagnostic code with its fix. `generated/reference.md` renders the same contract for reading.
|
|
70
|
+
`bun run codegen:contract` writes both from the modules that own each fact (component and `Fmt`
|
|
71
|
+
interfaces, `PROFILE_FORMS`, `DIAGNOSTIC_CODES`, the element table), and a test fails when they
|
|
72
|
+
drift.
|
|
73
|
+
- `skills/sml/SKILL.md` — the `sml` skill: the write, check, render, look, repair loop, the static
|
|
74
|
+
profile and the data rules, for an agent that edits a template project.
|
|
75
|
+
|
|
76
|
+
Read the contract before authoring. Import only subpaths declared in `package.json` `exports`.
|
|
30
77
|
|
|
31
78
|
## How you hurt yourself here
|
|
32
79
|
|
|
80
|
+
- **Computing in a body.** A body is read, not run. A call other than `fmt.*` and `.map`, a
|
|
81
|
+
statement (`const`, `if`, a loop), an import outside `@samva/markup` and the project, a hook, an
|
|
82
|
+
event handler and `dangerouslySetInnerHTML` are each a diagnostic with a code, a location and a
|
|
83
|
+
fix. Compute the value in the caller and send it in the payload. There is no escape hatch.
|
|
84
|
+
- **Chunking or counting in a body.** `items.reduce`, index arithmetic and `filter` helpers are
|
|
85
|
+
calls the profile refuses. Use `<Columns each={list} per={n}>{(item) => <Column … />}</Columns>`
|
|
86
|
+
for a grid and `.filter(condition)` before `.map` or `.length`; the condition is a `&&` / `?:`
|
|
87
|
+
condition over the item.
|
|
88
|
+
- **Building a class name at run time.** Tailwind reads literal classes. `className={`text-${size}`}`
|
|
89
|
+
is `dynamic-class`; choose between literal lists instead: `input.vip ? "bg-amber-100" : "bg-white"`.
|
|
90
|
+
- **Reading an optional field unguarded.** Binding `input.trackingUrl` outside
|
|
91
|
+
`input.trackingUrl && …` is `unguarded-optional`; a misspelled field is `unknown-field` with the
|
|
92
|
+
nearest match; a fixture that stopped matching the schema is `fixture-invalid`.
|
|
93
|
+
- **Reporting a finding without a registry entry.** `Findings.add` and the lowerer accept only
|
|
94
|
+
codes in `DIAGNOSTIC_CODES` (`src/diagnostic-codes.ts`), which holds each code's meaning and fix.
|
|
95
|
+
Add the entry with the code; the contract and the docs read it.
|
|
96
|
+
- **Putting the subject or preheader on `<Email>`.** They belong to the channel (`email.subject`,
|
|
97
|
+
`email.preheader`); `<Email>` has no `preheader` prop.
|
|
33
98
|
- **Passing a string where you want markup.** Nothing an author passes as a string becomes markup;
|
|
34
99
|
strings render as escaped text. There is no raw-HTML escape hatch: compatibility markup comes only
|
|
35
100
|
from the email primitives.
|
|
@@ -38,15 +103,15 @@ contracts; read those before authoring. Import only subpaths declared in `packag
|
|
|
38
103
|
unsupported element or attribute fails at typecheck and again at compile with a source location.
|
|
39
104
|
- **Naming a template or fixture like a path.** A template id and a fixture key are both written
|
|
40
105
|
into file paths, so each must match `TEMPLATE_NAME_PATTERN`, one plain path segment.
|
|
41
|
-
`
|
|
106
|
+
`defineTemplate` throws a `TypeError` where the name is declared. Code that builds a path from these
|
|
42
107
|
names imports the pattern instead of restating it.
|
|
43
|
-
- **
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
108
|
+
- **Declaring a webfont as if every client loads it.** A project font is an ordinary `@font-face`
|
|
109
|
+
in the project's CSS (usually `theme.css`) whose `src` is a `.woff2` file in the project, named
|
|
110
|
+
by a path relative to that stylesheet; the compiler serves it as a content-addressed asset and
|
|
111
|
+
hides the rule from classic Outlook. Gmail and Outlook never load it, so a `font-family` stack
|
|
112
|
+
that names a project font must end in a generic (`sans-serif`, `serif`, `monospace`) or the
|
|
113
|
+
render fails with `email-font-fallback`. Only WOFF2 is accepted; an `https:` `src` stays an
|
|
114
|
+
external reference.
|
|
50
115
|
- **Expecting the schema library to do the validation.** A template's input schema is converted to
|
|
51
116
|
a portable JSON Schema and validated by that document, not by the library that produced it. A
|
|
52
117
|
refinement that the converter cannot export is refused, and defaults are never inserted or values
|
|
@@ -54,27 +119,29 @@ contracts; read those before authoring. Import only subpaths declared in `packag
|
|
|
54
119
|
|
|
55
120
|
## Rules
|
|
56
121
|
|
|
57
|
-
- **Email entries default-export
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
rather than
|
|
65
|
-
- **Failures are typed.**
|
|
66
|
-
`
|
|
67
|
-
`SmlCompileError`) by `_tag`. Email compilation reports `EmailDiagnostic` values: errors throw
|
|
68
|
-
`EmailCompileError`, warnings ride back on the render result. A declaration the compiler cannot
|
|
122
|
+
- **Email entries default-export `defineTemplate`** from a `.tsx` file. The definition owns a
|
|
123
|
+
stable, project-unique kebab-case `id`, an input schema, at least one fixture, and one body per
|
|
124
|
+
channel. Helper modules are ordinary imports, not catalog entries; a partial is a function from
|
|
125
|
+
props (and `children`) to JSX that the compiler inlines. A fixture is data: literals, constants
|
|
126
|
+
and imported assets.
|
|
127
|
+
- **The IR is the artifact.** Rendering is a pure function of the IR and the input. The renderer
|
|
128
|
+
escapes per context, applies the serializer's URL and style rules to every bound value, and
|
|
129
|
+
rejects unsafe input rather than sanitizing it.
|
|
130
|
+
- **Failures are typed.** Compilation reports diagnostic values with a code, a location and a fix:
|
|
131
|
+
`compileTemplate` returns them beside the IR, and an error finding leaves no IR. A declaration the compiler cannot
|
|
69
132
|
accept at all, such as an invalid name or a nonportable input schema, throws a `TypeError`.
|
|
70
133
|
- **Data models are plain types.** Markup's data models are TypeScript types, not schema
|
|
71
134
|
declarations, so there is no schema library to adopt to consume them. Unions discriminate on
|
|
72
|
-
`type
|
|
135
|
+
`type`; only error classes carry `_tag`. The input schema is the one
|
|
73
136
|
place a Standard Schema is accepted, and which library produces it is yours to choose.
|
|
74
137
|
|
|
75
138
|
## Proving your setup
|
|
76
139
|
|
|
77
140
|
- `tsc --noEmit` over the template project catches unsupported elements, attributes, and props.
|
|
141
|
+
- `compileTemplate` reports every static-profile, binding and fixture finding with a source
|
|
142
|
+
location, and `renderIr(compiled.ir, fixture)` proves one fixture's output without executing the
|
|
143
|
+
template.
|
|
78
144
|
- `samva templates check` (from `@samva/cli`) compiles every entry and renders every fixture,
|
|
79
145
|
reporting findings with source locations.
|
|
80
|
-
- `
|
|
146
|
+
- `renderIrPreview(compiled.ir, fixture)` returns the same document with the source spans an editor
|
|
147
|
+
selects by.
|
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/components";
|
|
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/static` 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-ir`
|
|
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-ir";
|
|
106
|
+
import { compileTemplate } from "@samva/markup/static";
|
|
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/preview` 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
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { r as EmailDiagnostic } from "./diagnostics-B-Gym34R.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 };
|
|
40
|
+
//# sourceMappingURL=assets-C5duwTya.d.ts.map
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { m as EmailNode } from "./elements-cqldpaJt.js";
|
|
2
|
+
//#region src/internal/unsubscribe-link.d.ts
|
|
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
|
|
10
|
+
//#region src/email/brand.d.ts
|
|
11
|
+
interface EmailBrandAddress {
|
|
12
|
+
readonly street1: string;
|
|
13
|
+
readonly street2?: string | undefined;
|
|
14
|
+
readonly city: string;
|
|
15
|
+
readonly region: string;
|
|
16
|
+
readonly postalCode: string;
|
|
17
|
+
readonly country: string;
|
|
18
|
+
}
|
|
19
|
+
interface EmailBrandSocialLink {
|
|
20
|
+
readonly label: string;
|
|
21
|
+
readonly url: string;
|
|
22
|
+
}
|
|
23
|
+
interface EmailBrandAssets {
|
|
24
|
+
readonly logoUrl?: string | undefined;
|
|
25
|
+
readonly logoDarkUrl?: string | undefined;
|
|
26
|
+
readonly iconUrl?: string | undefined;
|
|
27
|
+
}
|
|
28
|
+
interface EmailBrandFooter {
|
|
29
|
+
readonly companyName?: string | undefined;
|
|
30
|
+
readonly address?: EmailBrandAddress | undefined;
|
|
31
|
+
readonly socialLinks?: readonly EmailBrandSocialLink[] | undefined;
|
|
32
|
+
}
|
|
33
|
+
interface EmailBrand {
|
|
34
|
+
/** The brand's slug; `samva:brand/<slug>` must name it. */
|
|
35
|
+
readonly slug: string;
|
|
36
|
+
/** Brand theme CSS in the closed grammar `parseBrandCss` accepts. */
|
|
37
|
+
readonly css: string;
|
|
38
|
+
readonly assets?: EmailBrandAssets | undefined;
|
|
39
|
+
readonly footer?: EmailBrandFooter | undefined;
|
|
40
|
+
}
|
|
41
|
+
/** Marks the block the send path recognizes as the template's own footer. */
|
|
42
|
+
declare const FOOTER_ATTRIBUTE = "data-samva-footer";
|
|
43
|
+
/** Marks the footer row that carries the unsubscribe link. */
|
|
44
|
+
declare const UNSUBSCRIBE_ROW_ATTRIBUTE = "data-samva-unsubscribe";
|
|
45
|
+
/** Every class the brand components use, compiled for any project that imports them. */
|
|
46
|
+
declare const BRAND_COMPONENT_CLASSES: readonly ["dark:hidden!", "hidden", "dark:block!", "text-xs", "text-gray-500", "text-center", "font-semibold", "underline"];
|
|
47
|
+
interface BrandLogoProps {
|
|
48
|
+
/** Rendered width in pixels; the height follows the image. */
|
|
49
|
+
readonly width?: number | undefined;
|
|
50
|
+
/** Defaults to the brand footer's company name. */
|
|
51
|
+
readonly alt?: string | undefined;
|
|
52
|
+
readonly align?: "left" | "center" | "right" | undefined;
|
|
53
|
+
readonly className?: string | undefined;
|
|
54
|
+
}
|
|
55
|
+
interface BrandFooterProps {
|
|
56
|
+
/** Text of the unsubscribe link. */
|
|
57
|
+
readonly unsubscribeLabel?: string | undefined;
|
|
58
|
+
readonly className?: string | undefined;
|
|
59
|
+
}
|
|
60
|
+
interface BrandComponents {
|
|
61
|
+
readonly BrandLogo: (props: BrandLogoProps) => EmailNode | null;
|
|
62
|
+
readonly BrandFooter: (props: BrandFooterProps) => EmailNode | null;
|
|
63
|
+
}
|
|
64
|
+
/** Bind `BrandLogo` and `BrandFooter` to one brand. */
|
|
65
|
+
declare const brandComponents: (brand: EmailBrand) => BrandComponents;
|
|
66
|
+
/** What a rendered message says about the footer it carries. */
|
|
67
|
+
interface RenderedFooterMarkers {
|
|
68
|
+
/** The message renders `BrandFooter` (or claims to, with the marker). */
|
|
69
|
+
readonly footer: boolean;
|
|
70
|
+
/** The message carries the unsubscribe placeholder link only `BrandFooter` can write. */
|
|
71
|
+
readonly unsubscribeLink: boolean;
|
|
72
|
+
}
|
|
73
|
+
/** Read the footer markers from rendered HTML. */
|
|
74
|
+
declare const renderedFooterMarkers: (html: string) => RenderedFooterMarkers;
|
|
75
|
+
/**
|
|
76
|
+
* Drop the unsubscribe row from a rendered message, for a send that resolved
|
|
77
|
+
* no unsubscribe group: there is no link to put there, so the row goes.
|
|
78
|
+
*/
|
|
79
|
+
declare const withoutUnsubscribeRow: (rendered: {
|
|
80
|
+
readonly html?: string | undefined;
|
|
81
|
+
readonly text?: string | undefined;
|
|
82
|
+
}) => {
|
|
83
|
+
readonly html: string | undefined;
|
|
84
|
+
readonly text: string | undefined;
|
|
85
|
+
};
|
|
86
|
+
//#endregion
|
|
87
|
+
export { EmailBrand as a, EmailBrandFooter as c, RenderedFooterMarkers as d, UNSUBSCRIBE_ROW_ATTRIBUTE as f, UNSUBSCRIBE_URL_PLACEHOLDER as g, withoutUnsubscribeRow as h, BrandLogoProps as i, EmailBrandSocialLink as l, renderedFooterMarkers as m, BrandComponents as n, EmailBrandAddress as o, brandComponents as p, BrandFooterProps as r, EmailBrandAssets as s, BRAND_COMPONENT_CLASSES as t, FOOTER_ATTRIBUTE as u };
|
|
88
|
+
//# sourceMappingURL=brand-B-0bN_Ir.d.ts.map
|