@samva/markup 0.10.0 → 0.11.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.
@@ -1,24 +1,16 @@
1
1
  import { n as isObject } from "./guards-eV1qLbf2.js";
2
2
  import { n as inputSchema, r as jsonSchema } from "./input-schema-zjkgrXn2.js";
3
3
  import { n as TEMPLATE_NAME_PATTERN, t as TEMPLATE_ID_PATTERN } from "./template-zLUjcwfF.js";
4
- import { n as RESERVED_FOOTER_ATTRIBUTES, t as BRAND_FOOTER_MARKER } from "./unsubscribe-link-CoesnQDU.js";
5
- import { M as EMAIL_ELEMENTS, P as emailElement, a as Email, d as equalColumnWidth, i as Divider, l as Section, n as Column, o as Image, p as checkAttribute, r as Columns, s as Link, t as Button, u as Spacer, y as serializeStyle } from "./components-BZCqegS8.js";
4
+ import { D as samvaBrandPlugin, E as reservedBrandAttributes, O as BRAND_FOOTER_MARKER, S as emailElement, T as parseBrandSpecifier, b as EMAIL_ELEMENTS, s as serializeStyle, t as checkAttribute } from "./html-DjPxwjLi.js";
6
5
  import { FORMATTER_NAMES } from "./fmt.js";
7
6
  import { i as drainEmailDiagnostics, t as EmailNode } from "./jsx-runtime-CBlexoeR.js";
8
- import { n as applyStylesheet } from "./ir-CLhlklKV.js";
7
+ import { a as Email, d as equalColumnWidth, i as Divider, l as Section, n as Column, o as Image, r as Columns, s as Link, t as Button, u as Spacer } from "./components-Bl6DT21n.js";
8
+ import { n as applyStylesheet } from "./ir-De6pTH51.js";
9
9
  import { i as lineColumn, t as buildLineMap } from "./diagnostic-model-DJtqbETd.js";
10
- import { d as isAssetPath, i as brandComponents } from "./brand-C0MKTwmX.js";
10
+ import { l as isAssetPath, n as brandComponents } from "./brand-DTctcDmf.js";
11
11
  import { parse } from "css-tree";
12
12
  import { parse as parse$1 } from "@babel/parser";
13
13
  //#region src/email/theme-imports.ts
14
- /** The CSS and TSX specifier a template uses to reach its organization's brand. */
15
- const BRAND_SPECIFIER = "samva:brand";
16
- const BRAND_IMPORT = /^samva:brand(?:\/([a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?))?$/;
17
- /** The brand a `samva:brand` specifier names: `undefined` slug means the default brand. */
18
- const parseBrandSpecifier = (specifier) => {
19
- const match = BRAND_IMPORT.exec(specifier);
20
- return match === null ? void 0 : { slug: match[1] };
21
- };
22
14
  /** The directory part of a project path, with no trailing slash (`""` at the root). */
23
15
  const projectDirectory = (path) => path.includes("/") ? path.slice(0, path.lastIndexOf("/")) : "";
24
16
  /** A relative or root-absolute stylesheet reference, resolved from a project directory. */
@@ -45,10 +37,12 @@ const importTarget = (node) => {
45
37
  };
46
38
  /**
47
39
  * Walk the theme's `@import` graph. Project `.css` files resolve from the
48
- * importing file's directory; `samva:brand` and `samva:brand/<slug>` are
49
- * recorded for the brand the build supplies. Anything else is an error.
40
+ * importing file's directory; the plugin's brand specifier, bare or with a
41
+ * slug, is recorded for the brand the build supplies. Anything else is an
42
+ * error.
50
43
  */
51
- const resolveThemeImports = (themePath, themeCss, files) => {
44
+ const resolveThemeImports = (themePath, themeCss, files, brandPlugin) => {
45
+ const brand = JSON.stringify(brandPlugin.specifier);
52
46
  const stylesheets = /* @__PURE__ */ new Map([[themePath, themeCss]]);
53
47
  const brandSpecifiers = /* @__PURE__ */ new Set();
54
48
  const diagnostics = [];
@@ -76,19 +70,19 @@ const resolveThemeImports = (themePath, themeCss, files) => {
76
70
  });
77
71
  const { target, extra } = importTarget(node);
78
72
  if (target === void 0) {
79
- refuse(`${path}: @import needs a quoted stylesheet path or "samva:brand".`);
73
+ refuse(`${path}: @import needs a quoted stylesheet path or ${brand}.`);
80
74
  continue;
81
75
  }
82
76
  if (extra) {
83
77
  refuse(`${path}: @import ${JSON.stringify(target)} takes no layer, supports or media conditions; the import order is the layering.`);
84
78
  continue;
85
79
  }
86
- if (parseBrandSpecifier(target) !== void 0) {
80
+ if (parseBrandSpecifier(target, brandPlugin) !== void 0) {
87
81
  brandSpecifiers.add(target);
88
82
  continue;
89
83
  }
90
84
  if (!/^\.{0,2}\//.test(target) || !/\.css$/i.test(target.replace(/[?#].*$/, ""))) {
91
- refuse(`${path}: @import ${JSON.stringify(target)} is not a project stylesheet or "samva:brand". The compiler supplies Tailwind itself; import project .css files by relative path.`);
85
+ refuse(`${path}: @import ${JSON.stringify(target)} is not a project stylesheet or ${brand}. The compiler supplies Tailwind itself; import project .css files by relative path.`);
92
86
  continue;
93
87
  }
94
88
  const resolved = resolveFromDirectory(projectDirectory(path), target.replace(/[?#].*$/, ""));
@@ -1093,7 +1087,7 @@ const columnsEach = (call, bindings) => {
1093
1087
  /** Run a compiler primitive at compile time and lower what it returns. */
1094
1088
  const runBuiltinComponent = (call) => {
1095
1089
  const { lowerer, ctx, node, binding } = call;
1096
- const specifier = binding.kind === "markup" ? binding.specifier : "samva:brand";
1090
+ const specifier = binding.specifier;
1097
1091
  if (binding.kind === "markup" && (specifier === "@samva/markup/sms" || specifier === "@samva/markup/whatsapp")) {
1098
1092
  const lowered = call.channel?.(call);
1099
1093
  if (lowered !== void 0) return lowered;
@@ -1104,17 +1098,17 @@ const runBuiltinComponent = (call) => {
1104
1098
  const isEmail = binding.kind === "markup" && specifier === "@samva/markup/email";
1105
1099
  const isBrand = binding.kind === "brand";
1106
1100
  if (!(isEmail || isBrand)) {
1107
- lowerer.error(ctx, node, "unknown-component", `<${call.name}> is not a component the profile can read.`, "Import components from @samva/markup/email, samva:brand, or the project.");
1101
+ lowerer.error(ctx, node, "unknown-component", `<${call.name}> is not a component the profile can read.`, `Import components from @samva/markup/email, ${call.brandPlugin.specifier}, or the project.`);
1108
1102
  return [];
1109
1103
  }
1110
1104
  let fn;
1111
1105
  if (isEmail) fn = COMPONENTS[exported];
1112
1106
  else {
1113
1107
  if (call.brand === void 0) {
1114
- lowerer.error(ctx, node, "brand-unavailable", `<${call.name}> comes from samva:brand, and this build has no brand to resolve it.`, "Compile with a brand, or remove the brand components.");
1108
+ lowerer.error(ctx, node, "brand-unavailable", `<${call.name}> comes from ${binding.specifier}, and this build has no brand to resolve it.`, "Compile with a brand, or remove the brand components.");
1115
1109
  return [];
1116
1110
  }
1117
- const brand = brandComponents(call.brand);
1111
+ const brand = brandComponents(call.brand, call.brandPlugin);
1118
1112
  fn = exported === "BrandLogo" ? brand.BrandLogo : exported === "BrandFooter" ? brand.BrandFooter : void 0;
1119
1113
  }
1120
1114
  if (fn === void 0 || !Object.hasOwn(COMPONENTS, exported) && isEmail) {
@@ -1406,8 +1400,10 @@ var Lowerer = class {
1406
1400
  options;
1407
1401
  sources = [];
1408
1402
  uniqueId = 0;
1403
+ reservedAttributes;
1409
1404
  constructor(options) {
1410
1405
  this.options = options;
1406
+ this.reservedAttributes = reservedBrandAttributes(options.brandPlugin);
1411
1407
  }
1412
1408
  get findings() {
1413
1409
  return this.options.findings;
@@ -2129,8 +2125,8 @@ var Lowerer = class {
2129
2125
  this.error(ctx, attribute, isHtml ? "dangerous-html" : name === "ref" ? "unsupported-attribute" : "event-handler", isHtml ? "`dangerouslySetInnerHTML` injects unescaped markup." : name === "ref" ? "A `ref` needs a running component." : `\`${name}\` is an event handler; email clients strip scripting and Samva refuses it.`, isHtml ? "Write the markup as elements." : "Remove it.");
2130
2126
  continue;
2131
2127
  }
2132
- if (RESERVED_FOOTER_ATTRIBUTES.has(name)) {
2133
- this.error(ctx, attribute, "reserved-attribute", `\`${name}\` is written only by BrandFooter from samva:brand.`, "Render <BrandFooter /> instead of marking an element as the footer.");
2128
+ if (this.reservedAttributes.has(name)) {
2129
+ this.error(ctx, attribute, "reserved-attribute", `\`${name}\` is written only by BrandFooter from ${this.options.brandPlugin.specifier}.`, "Render <BrandFooter /> instead of marking an element as the footer.");
2134
2130
  continue;
2135
2131
  }
2136
2132
  if (name === "className" || name === "class") {
@@ -2154,7 +2150,7 @@ var Lowerer = class {
2154
2150
  }
2155
2151
  if (value === void 0) continue;
2156
2152
  if (value === false || value === null) continue;
2157
- if (value === "%samva:unsubscribe-url%") {
2153
+ if (value === this.options.brandPlugin.unsubscribeUrlPlaceholder) {
2158
2154
  this.error(ctx, attribute, "reserved-url", "That URL is the unsubscribe placeholder only BrandFooter can write.", "Render <BrandFooter /> instead.");
2159
2155
  continue;
2160
2156
  }
@@ -2301,6 +2297,7 @@ var Lowerer = class {
2301
2297
  name,
2302
2298
  binding: imported,
2303
2299
  brand: this.options.brand,
2300
+ brandPlugin: this.options.brandPlugin,
2304
2301
  channel: this.options.channel
2305
2302
  });
2306
2303
  let fn;
@@ -3342,7 +3339,7 @@ const lowerProject = (options, project, findings, inputs) => {
3342
3339
  staticContext,
3343
3340
  assetUrl: urlFor,
3344
3341
  brand: options.brand,
3345
- brand: options.brand,
3342
+ brandPlugin: options.brandPlugin ?? samvaBrandPlugin,
3346
3343
  channel: channelLowering
3347
3344
  });
3348
3345
  const rootContext = (target) => ({
@@ -3546,7 +3543,7 @@ const resolveSpecifier = (from, specifier, files) => {
3546
3543
  const isScriptPath = (path) => /\.[cm]?[jt]sx?$/.test(path);
3547
3544
  const declaredNames = (pattern) => pattern.type === "Identifier" ? [text(pattern, "name") ?? ""] : [];
3548
3545
  /** Load the entry and everything it imports from the project. */
3549
- const loadProject = (files, entryPath, findings, loadOptions = {}) => {
3546
+ const loadProject = (files, entryPath, findings, loadOptions) => {
3550
3547
  const modules = /* @__PURE__ */ new Map();
3551
3548
  const unresolved = /* @__PURE__ */ new Set();
3552
3549
  const loading = /* @__PURE__ */ new Set();
@@ -3631,7 +3628,7 @@ const loadProject = (files, entryPath, findings, loadOptions = {}) => {
3631
3628
  }
3632
3629
  break;
3633
3630
  }
3634
- if (/^samva:brand(\/|$)/.test(specifier)) {
3631
+ if (parseBrandSpecifier(specifier, loadOptions.brandPlugin) !== void 0) {
3635
3632
  for (const item of specifiers) if (text(item, "importKind") !== "type") imports.set(local(item), {
3636
3633
  kind: "brand",
3637
3634
  specifier,
@@ -3764,13 +3761,17 @@ const asFiles = (files, entry) => typeof files === "string" ? { [entry]: files }
3764
3761
  * and the entry's path. Imports of files that are absent are trusted to exist, and the brand,
3765
3762
  * Tailwind and theme are not resolved, so the result holds only findings the source itself owns.
3766
3763
  */
3767
- const checkStaticProfile = (files, entry = DEFAULT_ENTRY) => {
3764
+ const checkStaticProfile = (files, entry = DEFAULT_ENTRY, brandPlugin = samvaBrandPlugin) => {
3768
3765
  const findings = new Findings();
3769
- const project = loadProject(asFiles(files, entry), entry, findings, { lenient: true });
3766
+ const project = loadProject(asFiles(files, entry), entry, findings, {
3767
+ brandPlugin,
3768
+ lenient: true
3769
+ });
3770
3770
  if (project === void 0) return findings.items;
3771
3771
  lowerProject({
3772
3772
  entry,
3773
- brand: void 0
3773
+ brand: void 0,
3774
+ brandPlugin
3774
3775
  }, project, findings, {
3775
3776
  assets: [],
3776
3777
  stylesheets: /* @__PURE__ */ new Map(),
@@ -3798,6 +3799,6 @@ const introducedProfileErrors = (before, after) => {
3798
3799
  return added;
3799
3800
  };
3800
3801
  //#endregion
3801
- export { lowerProject as a, WHATSAPP_HEADER_TYPES as c, parseBrandSpecifier as d, projectDirectory as f, completed as i, Findings as l, resolveThemeImports as m, introducedProfileErrors as n, SMS_CATEGORIES as o, resolveFromDirectory as p, loadProject as r, WHATSAPP_CATEGORIES as s, checkStaticProfile as t, BRAND_SPECIFIER as u };
3802
+ export { lowerProject as a, WHATSAPP_HEADER_TYPES as c, resolveFromDirectory as d, resolveThemeImports as f, completed as i, Findings as l, introducedProfileErrors as n, SMS_CATEGORIES as o, loadProject as r, WHATSAPP_CATEGORIES as s, checkStaticProfile as t, projectDirectory as u };
3802
3803
 
3803
- //# sourceMappingURL=profile-pWGtWZXF.js.map
3804
+ //# sourceMappingURL=profile-PUfi3GPH.js.map
@@ -1,6 +1,7 @@
1
- import { P as emailElement, f as preheaderNode, v as serializeEmailHtml } from "./components-BZCqegS8.js";
1
+ import { S as emailElement, o as serializeEmailHtml } from "./html-DjPxwjLi.js";
2
2
  import { i as drainEmailDiagnostics, t as EmailNode } from "./jsx-runtime-CBlexoeR.js";
3
- import { n as applyStylesheet } from "./ir-CLhlklKV.js";
3
+ import { f as preheaderNode } from "./components-Bl6DT21n.js";
4
+ import { n as applyStylesheet } from "./ir-De6pTH51.js";
4
5
  import { r as hasBlockingDiagnostic, t as EmailCompileError } from "./diagnostics-CnKFefKz.js";
5
6
  //#region src/email/text.ts
6
7
  const PARAGRAPH = "\n\n";
@@ -254,4 +255,4 @@ const renderEmailContent = (stylesheet, render, options = {}) => {
254
255
  //#endregion
255
256
  export { deriveEmailText as i, compileEmail as n, renderEmailContent as r, EMAIL_DOCTYPE as t };
256
257
 
257
- //# sourceMappingURL=render-BQmfh94k.js.map
258
+ //# sourceMappingURL=render-D2ht57ZZ.js.map
@@ -1,3 +1,4 @@
1
+ import { t as BrandPlugin } from "./brand-plugin-CHhcei-j.js";
1
2
  import { r as JsxSourceLocation } from "./source-locations-C8wCsiAy.js";
2
3
  import { r as EmailDiagnostic } from "./diagnostics-B-Gym34R.js";
3
4
  import { r as EmailNode } from "./jsx-runtime-ylIOnaSf.js";
@@ -207,6 +208,12 @@ interface RenderOptions {
207
208
  readonly timeZone?: string;
208
209
  /** Also return where each element landed in the HTML, for mapping findings back to source. */
209
210
  readonly positions?: boolean;
211
+ /**
212
+ * The brand plugin the template compiled against: its unsubscribe placeholder gets the
213
+ * no-track marker, and only the elements `BrandFooter` wrote may carry its footer markers.
214
+ * `samvaBrandPlugin` when omitted.
215
+ */
216
+ readonly brandPlugin?: BrandPlugin;
210
217
  }
211
218
  /** One rendered element's offset range in the HTML, and the source it was lowered from. */
212
219
  interface RenderedPosition {
@@ -486,6 +493,8 @@ interface SerializeEmailOptions {
486
493
  * A delivered message never carries it.
487
494
  */
488
495
  readonly instancePaths?: boolean | undefined;
496
+ /** The brand plugin whose footer markers and unsubscribe link `BrandFooter` writes. */
497
+ readonly brandPlugin?: BrandPlugin | undefined;
489
498
  }
490
499
  /** Lower the derived tree to email HTML, reporting every unsupported construct with its origin. */
491
500
  declare const serializeEmailHtml: (node: EmailNode, options?: SerializeEmailOptions) => EmailHtmlOutput;
@@ -536,4 +545,4 @@ interface RenderedEmail {
536
545
  declare const renderEmailContent: (stylesheet: Stylesheet | undefined, render: () => EmailContent, options?: SerializeEmailOptions) => RenderedEmail;
537
546
  //#endregion
538
547
  export { IrWhatsApp as $, emailAuthoringTarget as A, IrElement as B, Compound as C, EmittedPosition as D, EmailElementSelection as E, emailSelections as F, IrPredicate as G, IrFormatter as H, renderIrPreview as I, IrRawPart as J, IrPreheader as K, IrArithmetic as L, emailSelectionAncestry as M, emailSelectionIndex as N, INSTANCE_PATH_ATTRIBUTE as O, emailSelectionSiblings as P, IrValue as Q, IrComparison as R, CompiledSelector as S, matchesSelector as T, IrIf as U, IrEmail as V, IrNode as W, IrSource as X, IrSms as Y, IrStyle as Z, Specificity as _, renderEmailContent as a, RenderedPosition as at, parseStylesheet as b, checkAttribute as c, TemplateIr as ct, escapeText as d, IrWhatsAppButton as et, kebab as f, CssRule as g, CssDeclaration as h, compileEmail as i, RenderedIrWhatsApp as it, emailDefinitionInstances as j, IrPreview as k, declarationValue as l, serializeStyle as m, EmailCompileOutput as n, RenderOptions as nt, EmailHtmlOutput as o, RenderedWhatsAppButton as ot, serializeEmailHtml as p, IrRaw as q, RenderedEmail as r, RenderedIrEmail as rt, SerializeEmailOptions as s, SML_IR_VERSION as st, EMAIL_DOCTYPE as t, IrWhatsAppButtonNode as tt, escapeAttribute as u, Stylesheet as v, classListOf as w, Ancestry as x, compileSelector as y, IrEach as z };
539
- //# sourceMappingURL=render-BPonDpCl.d.ts.map
548
+ //# sourceMappingURL=render-DQKz4Aco.d.ts.map
package/dist/render.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { C as isUrlAttribute, S as emailElement, _ as EmailGlobalProps, b as EmailStyle, g as EmailElementSpec, h as EmailAttributeSpec, m as EMAIL_GLOBAL_ATTRIBUTES, p as EMAIL_ELEMENTS, r as EmailNode, v as EmailIntrinsicElements, x as EmailTag, y as EmailLayout } from "./jsx-runtime-ylIOnaSf.js";
2
- import { $ as IrWhatsApp, A as emailAuthoringTarget, B as IrElement, D as EmittedPosition, E as EmailElementSelection, F as emailSelections, G as IrPredicate, H as IrFormatter, I as renderIrPreview, J as IrRawPart, K as IrPreheader, L as IrArithmetic, M as emailSelectionAncestry, N as emailSelectionIndex, O as INSTANCE_PATH_ATTRIBUTE, P as emailSelectionSiblings, Q as IrValue, R as IrComparison, U as IrIf, V as IrEmail, W as IrNode, X as IrSource, Y as IrSms, Z as IrStyle, a as renderEmailContent, at as RenderedPosition, c as checkAttribute, ct as TemplateIr, d as escapeText, et as IrWhatsAppButton, f as kebab, i as compileEmail, it as RenderedIrWhatsApp, j as emailDefinitionInstances, k as IrPreview, l as declarationValue, m as serializeStyle, n as EmailCompileOutput, nt as RenderOptions, o as EmailHtmlOutput, ot as RenderedWhatsAppButton, p as serializeEmailHtml, q as IrRaw, r as RenderedEmail, rt as RenderedIrEmail, s as SerializeEmailOptions, st as SML_IR_VERSION, t as EMAIL_DOCTYPE, tt as IrWhatsAppButtonNode, u as escapeAttribute, z as IrEach } from "./render-BPonDpCl.js";
2
+ import { $ as IrWhatsApp, A as emailAuthoringTarget, B as IrElement, D as EmittedPosition, E as EmailElementSelection, F as emailSelections, G as IrPredicate, H as IrFormatter, I as renderIrPreview, J as IrRawPart, K as IrPreheader, L as IrArithmetic, M as emailSelectionAncestry, N as emailSelectionIndex, O as INSTANCE_PATH_ATTRIBUTE, P as emailSelectionSiblings, Q as IrValue, R as IrComparison, U as IrIf, V as IrEmail, W as IrNode, X as IrSource, Y as IrSms, Z as IrStyle, a as renderEmailContent, at as RenderedPosition, c as checkAttribute, ct as TemplateIr, d as escapeText, et as IrWhatsAppButton, f as kebab, i as compileEmail, it as RenderedIrWhatsApp, j as emailDefinitionInstances, k as IrPreview, l as declarationValue, m as serializeStyle, n as EmailCompileOutput, nt as RenderOptions, o as EmailHtmlOutput, ot as RenderedWhatsAppButton, p as serializeEmailHtml, q as IrRaw, r as RenderedEmail, rt as RenderedIrEmail, s as SerializeEmailOptions, st as SML_IR_VERSION, t as EMAIL_DOCTYPE, tt as IrWhatsAppButtonNode, u as escapeAttribute, z as IrEach } from "./render-DQKz4Aco.js";
3
3
  //#region src/render-ir.d.ts
4
4
  interface IrRenderDiagnostic {
5
5
  readonly code: string;
package/dist/render.js CHANGED
@@ -1,6 +1,6 @@
1
- import { A as renderIrSms, C as emailSelectionAncestry, D as renderIrPreview, E as emailSelections, F as isUrlAttribute, M as EMAIL_ELEMENTS, N as EMAIL_GLOBAL_ATTRIBUTES, O as IrRenderError, P as emailElement, S as emailDefinitionInstances, T as emailSelectionSiblings, _ as kebab, b as INSTANCE_PATH_ATTRIBUTE, g as escapeText, h as escapeAttribute, j as renderIrWhatsApp, k as renderIr, m as declarationValue, p as checkAttribute, v as serializeEmailHtml, w as emailSelectionIndex, x as emailAuthoringTarget, y as serializeStyle } from "./components-BZCqegS8.js";
2
- import { t as SML_IR_VERSION } from "./ir-CLhlklKV.js";
3
- import { i as deriveEmailText, n as compileEmail, r as renderEmailContent, t as EMAIL_DOCTYPE } from "./render-BQmfh94k.js";
1
+ import { C as isUrlAttribute, S as emailElement, _ as renderIr, a as kebab, b as EMAIL_ELEMENTS, c as INSTANCE_PATH_ATTRIBUTE, d as emailSelectionAncestry, f as emailSelectionIndex, g as IrRenderError, h as renderIrPreview, i as escapeText, l as emailAuthoringTarget, m as emailSelections, n as declarationValue, o as serializeEmailHtml, p as emailSelectionSiblings, r as escapeAttribute, s as serializeStyle, t as checkAttribute, u as emailDefinitionInstances, v as renderIrSms, x as EMAIL_GLOBAL_ATTRIBUTES, y as renderIrWhatsApp } from "./html-DjPxwjLi.js";
2
+ import { t as SML_IR_VERSION } from "./ir-De6pTH51.js";
3
+ import { i as deriveEmailText, n as compileEmail, r as renderEmailContent, t as EMAIL_DOCTYPE } from "./render-D2ht57ZZ.js";
4
4
  //#region src/sms-segments.ts
5
5
  const GSM7_EXTENDED = /* @__PURE__ */ new Set([
6
6
  "[",
@@ -575,7 +575,7 @@ const brandThemeVariables = (brand) => {
575
575
  ...defaults.dark === void 0 ? {} : { [darkVariableName(BRAND_FOREGROUND_VARIABLE)]: defaults.dark }
576
576
  };
577
577
  };
578
- /** The stylesheet Tailwind compiles for `@import "samva:brand"`: `brandThemeVariables` and the font faces. */
578
+ /** The stylesheet Tailwind compiles for the theme's brand import: `brandThemeVariables` and the font faces. */
579
579
  const brandTailwindCss = (brand) => printBrandCss({
580
580
  fontFaces: brand.fontFaces,
581
581
  variables: brandThemeVariables(brand),
@@ -584,4 +584,4 @@ const brandTailwindCss = (brand) => printBrandCss({
584
584
  //#endregion
585
585
  export { brandThemeVariables as a, parseBrandCss as c, formatColor as d, parseColor as f, brandTailwindCss as i, printBrandCss as l, BRAND_FOREGROUND_VARIABLE as n, contrastForeground as o, brandForegroundDefaults as r, darkVariableName as s, BRAND_COLOR_VARIABLE as t, downlevelColors as u };
586
586
 
587
- //# sourceMappingURL=theme-css-JtRCm1fb.js.map
587
+ //# sourceMappingURL=theme-css-CEUrDpqq.js.map
@@ -1304,7 +1304,7 @@
1304
1304
  },
1305
1305
  {
1306
1306
  "code": "brand-unavailable",
1307
- "summary": "The build has no brand to resolve a samva:brand import.",
1307
+ "summary": "The build has no brand to resolve a brand import.",
1308
1308
  "fix": "Create or select the brand the import names."
1309
1309
  },
1310
1310
  {
@@ -1598,7 +1598,7 @@
1598
1598
  {
1599
1599
  "code": "reserved-attribute",
1600
1600
  "summary": "An attribute is written only by a brand component.",
1601
- "fix": "Render BrandFooter from samva:brand instead of marking an element as the footer."
1601
+ "fix": "Render BrandFooter from the brand import instead of marking an element as the footer."
1602
1602
  },
1603
1603
  {
1604
1604
  "code": "reserved-character",
@@ -1655,7 +1655,7 @@
1655
1655
  {
1656
1656
  "code": "unknown-component",
1657
1657
  "summary": "A component the profile cannot read.",
1658
- "fix": "Import components from @samva/markup/email, samva:brand, or the project."
1658
+ "fix": "Import components from @samva/markup/email, the brand import, or the project."
1659
1659
  },
1660
1660
  {
1661
1661
  "code": "unknown-field",
@@ -249,7 +249,7 @@ Global attributes: `id`, `title`, `lang`, `dir`, `role`.
249
249
  | Code | Meaning | Fix |
250
250
  | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
251
251
  | `brand-import-conflict` | The project imports two different brands. | Import one brand specifier everywhere. |
252
- | `brand-unavailable` | The build has no brand to resolve a samva:brand import. | Create or select the brand the import names. |
252
+ | `brand-unavailable` | The build has no brand to resolve a brand import. | Create or select the brand the import names. |
253
253
  | `button-dynamic-label` | A Button label is read at render time, so classic Outlook cannot size it. | Give the button a width: <Button width={200}>. |
254
254
  | `button-outlook-fallback` | Classic Outlook needs a height and a plain string label to draw the button. | Give <Button> a height and a string label. |
255
255
  | `caniemail/unknown-coverage` | A feature has no client-support data. | Verify it in the target clients. |
@@ -306,7 +306,7 @@ Global attributes: `id`, `title`, `lang`, `dir`, `role`.
306
306
  | `not-a-function` | A channel field is not a function of the input. | Write (input) => ... in place or declare the function in the project. |
307
307
  | `preheader-without-shell` | A preheader is set but the body has no <Email> shell to hold it. | Wrap the body in <Email>. |
308
308
  | `recursive-partial` | A partial renders itself; partials are inlined at compile time. | Flatten the recursion, or repeat the structure with .map. |
309
- | `reserved-attribute` | An attribute is written only by a brand component. | Render BrandFooter from samva:brand instead of marking an element as the footer. |
309
+ | `reserved-attribute` | An attribute is written only by a brand component. | Render BrandFooter from the brand import instead of marking an element as the footer. |
310
310
  | `reserved-character` | Text contains a private-use character the compiler reserves. | Remove the characters U+E000 to U+E003. |
311
311
  | `reserved-url` | A URL is the unsubscribe placeholder only BrandFooter can write. | Render <BrandFooter /> instead. |
312
312
  | `schema-not-static` | The schema cannot be read without running code. | Write the schema as a literal jsonSchema<Input>({ type: 'object', ... }). |
@@ -315,7 +315,7 @@ Global attributes: `id`, `title`, `lang`, `dir`, `role`.
315
315
  | `tailwind-dynamic-class` | A class list is assembled at run time. | Write the classes literally. |
316
316
  | `too-many-class-variants` | A class list chooses between more literal lists than the compiler resolves. | Split the choice into fewer conditions, or move part of it to a partial. |
317
317
  | `unguarded-optional` | An optional field is read outside a guard, so a send without it would render a blank. | Guard it with {input.field && ...}, or make the field required. |
318
- | `unknown-component` | A component the profile cannot read. | Import components from @samva/markup/email, samva:brand, or the project. |
318
+ | `unknown-component` | A component the profile cannot read. | Import components from @samva/markup/email, the brand import, or the project. |
319
319
  | `unknown-field` | A binding names a field the schema does not declare. | Add the field to the schema, or bind a field it declares (the message suggests the nearest). |
320
320
  | `unknown-formatter` | fmt.<name> is not a formatter. | Use fmt.money, number, date, time, plural or list. |
321
321
  | `unmatched-class` | A class produced no styles. | Use a class the theme or Tailwind defines, or write the style inline. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@samva/markup",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Type-safe authoring and compilation primitives for Samva templates",
5
5
  "keywords": [
6
6
  "email",
@@ -121,12 +121,12 @@
121
121
  },
122
122
  "devDependencies": {
123
123
  "@css-inline/css-inline-wasm": "0.15.0",
124
- "@effect/vitest": "4.0.0-rc.117",
124
+ "@effect/vitest": "4.0.0",
125
125
  "@types/css-tree": "3.2.0",
126
- "effect": "4.0.0-rc.117",
126
+ "effect": "4.0.0",
127
127
  "tsdown": "^0.22.13",
128
128
  "typescript": "^7.0.2",
129
- "vitest": "^4.1.11",
129
+ "vitest": "^5.0.3",
130
130
  "zod": "4.6.5"
131
131
  }
132
132
  }
@@ -1,26 +0,0 @@
1
- //#region src/internal/unsubscribe-link.ts
2
- /**
3
- * Marks the one link the serializer lets carry the unsubscribe placeholder as
4
- * its href. Only `BrandFooter` sets it; nothing public exports it, so no
5
- * author-written link can claim the exemption.
6
- */
7
- const UNSUBSCRIBE_LINK = Symbol("samva.unsubscribe-link");
8
- /**
9
- * The per-recipient unsubscribe URL is not known at render: the send path
10
- * substitutes each delivery's link for this placeholder, or removes the
11
- * unsubscribe row when the send resolved no unsubscribe group.
12
- */
13
- const UNSUBSCRIBE_URL_PLACEHOLDER = "%samva:unsubscribe-url%";
14
- /**
15
- * Marks the elements `BrandFooter` writes the send path's footer markers on.
16
- * The serializer emits `data-samva-footer` and `data-samva-unsubscribe` only
17
- * for an element carrying it, so an author cannot claim a footer the send
18
- * path would then not append.
19
- */
20
- const BRAND_FOOTER_MARKER = Symbol("samva.brand-footer-marker");
21
- /** The attributes only `BrandFooter` writes. */
22
- const RESERVED_FOOTER_ATTRIBUTES = /* @__PURE__ */ new Set(["data-samva-footer", "data-samva-unsubscribe"]);
23
- //#endregion
24
- export { UNSUBSCRIBE_URL_PLACEHOLDER as i, RESERVED_FOOTER_ATTRIBUTES as n, UNSUBSCRIBE_LINK as r, BRAND_FOOTER_MARKER as t };
25
-
26
- //# sourceMappingURL=unsubscribe-link-CoesnQDU.js.map