@samva/markup 0.9.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.
Files changed (92) hide show
  1. package/AGENTS.md +53 -41
  2. package/CHANGELOG.md +379 -0
  3. package/README.md +6 -6
  4. package/dist/assets-B0yseju_.d.ts +115 -0
  5. package/dist/brand-DTctcDmf.js +202 -0
  6. package/dist/brand-plugin-CHhcei-j.d.ts +32 -0
  7. package/dist/{theme-css.d.ts → brand.d.ts} +15 -4
  8. package/dist/brand.js +4 -0
  9. package/dist/compiler.d.ts +236 -18
  10. package/dist/compiler.js +1315 -13
  11. package/dist/{email/components.js → components-Bl6DT21n.js} +4 -4
  12. package/dist/diagnostic-model-DJtqbETd.js +38 -0
  13. package/dist/diagnostic-model-DxthUZBT.d.ts +31 -0
  14. package/dist/{email/diagnostics.js → diagnostics-CnKFefKz.js} +2 -2
  15. package/dist/diagnostics.d.ts +406 -27
  16. package/dist/diagnostics.js +402 -34
  17. package/dist/edit.d.ts +234 -0
  18. package/dist/{channel-edit.js → edit.js} +341 -3
  19. package/dist/email/jsx-dev-runtime.d.ts +1 -1
  20. package/dist/email/jsx-dev-runtime.js +1 -2
  21. package/dist/email/jsx-runtime.d.ts +1 -1
  22. package/dist/email/jsx-runtime.js +1 -85
  23. package/dist/{email/components.d.ts → email.d.ts} +2 -2
  24. package/dist/email.js +2 -0
  25. package/dist/html-DjPxwjLi.js +1637 -0
  26. package/dist/index.d.ts +1 -1
  27. package/dist/index.js +1 -1
  28. package/dist/{cascade-t6kxlgEI.js → ir-De6pTH51.js} +8 -4
  29. package/dist/jsx-runtime-CBlexoeR.js +98 -0
  30. package/dist/{elements-cqldpaJt.d.ts → jsx-runtime-ylIOnaSf.d.ts} +74 -74
  31. package/dist/{profile-BH-OtdDA.js → profile-PUfi3GPH.js} +166 -32
  32. package/dist/{email/render.js → render-D2ht57ZZ.js} +103 -8
  33. package/dist/render-DQKz4Aco.d.ts +548 -0
  34. package/dist/render.d.ts +35 -0
  35. package/dist/render.js +49 -0
  36. package/dist/sms.d.ts +1 -1
  37. package/dist/sms.js +1 -1
  38. package/dist/{template-_QE_rQND.d.ts → template-DMPwxMSq.d.ts} +2 -2
  39. package/dist/{template.js → template-zLUjcwfF.js} +2 -2
  40. package/dist/{theme-css-hFqcPSar.js → theme-css-CEUrDpqq.js} +3 -3
  41. package/dist/whatsapp.d.ts +1 -1
  42. package/dist/whatsapp.js +1 -1
  43. package/generated/contract.json +18 -12
  44. package/generated/reference.md +19 -12
  45. package/package.json +39 -122
  46. package/samva-brand.d.ts +4 -4
  47. package/skills/sml/SKILL.md +2 -2
  48. package/dist/assets-C5duwTya.d.ts +0 -40
  49. package/dist/brand-B-0bN_Ir.d.ts +0 -88
  50. package/dist/channel-edit.d.ts +0 -121
  51. package/dist/channel-specs-B6M_HlaV.js +0 -29
  52. package/dist/compatibility-DbISoFIq.js +0 -589
  53. package/dist/css-BVQKICKC.d.ts +0 -109
  54. package/dist/diagnostic-codes.d.ts +0 -403
  55. package/dist/diagnostic-codes.js +0 -399
  56. package/dist/email/assets.d.ts +0 -2
  57. package/dist/email/assets.js +0 -63
  58. package/dist/email/brand-footer.d.ts +0 -34
  59. package/dist/email/brand-footer.js +0 -63
  60. package/dist/email/brand.d.ts +0 -2
  61. package/dist/email/brand.js +0 -148
  62. package/dist/email/diagnostics.d.ts +0 -2
  63. package/dist/email/elements.d.ts +0 -2
  64. package/dist/email/elements.js +0 -299
  65. package/dist/email/html.d.ts +0 -2
  66. package/dist/email/html.js +0 -321
  67. package/dist/email/render.d.ts +0 -40
  68. package/dist/email/text.d.ts +0 -7
  69. package/dist/email/text.js +0 -100
  70. package/dist/html-BJepOwZ4.d.ts +0 -56
  71. package/dist/ir-aaqNE987.d.ts +0 -252
  72. package/dist/ir.d.ts +0 -2
  73. package/dist/ir.js +0 -7
  74. package/dist/preview-Ekdg1dXG.d.ts +0 -100
  75. package/dist/preview.d.ts +0 -2
  76. package/dist/preview.js +0 -160
  77. package/dist/render-ir.d.ts +0 -19
  78. package/dist/render-ir.js +0 -826
  79. package/dist/sms-segments.d.ts +0 -13
  80. package/dist/sms-segments.js +0 -46
  81. package/dist/source-edit.d.ts +0 -116
  82. package/dist/source-edit.js +0 -343
  83. package/dist/source-locations.d.ts +0 -2
  84. package/dist/source-locations.js +0 -16
  85. package/dist/static.d.ts +0 -57
  86. package/dist/static.js +0 -69
  87. package/dist/tailwind-CMYpZf1v.js +0 -614
  88. package/dist/template.d.ts +0 -2
  89. package/dist/theme-css.js +0 -2
  90. package/dist/theme-imports-lvBw3AD1.js +0 -109
  91. package/dist/transform-Ct2OMkFN.d.ts +0 -153
  92. package/dist/unsubscribe-link-CoesnQDU.js +0 -26
package/AGENTS.md CHANGED
@@ -10,63 +10,75 @@ the local editor, project builds, and exports live in `@samva/vite`.
10
10
 
11
11
  ## Entry points
12
12
 
13
+ Authoring entries are what a template file imports:
14
+
13
15
  - `@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`.
16
+ `SmlTemplate`, `TemplateInput`, and `TEMPLATE_ID_PATTERN` (lowercase kebab-case, the rule the CLI
17
+ and API slugs share). `email` is `{ subject, preheader?, body }`; each is a function of the
18
+ validated input.
19
+ - `@samva/markup/email` — email primitives (`Email`, `Section`, `Columns`, `Button`, and the rest of
20
+ its exports). They generate table layout and classic-Outlook fallbacks. Set
21
+ `jsxImportSource: "@samva/markup/email"`; the runtime is `@samva/markup/email/jsx-runtime`.
35
22
  - `@samva/markup/fmt` — `fmt.money`, `number`, `date`, `time`, `plural` and `list`: the only
36
23
  functions a body may call.
37
24
  - `@samva/markup/sms` and `@samva/markup/whatsapp` — the channel components (`Sms`; `WhatsApp` with
38
25
  `WhatsApp.Header`, `Body`, `Footer`, `Buttons`, `UrlButton`, `QuickReplyButton`, `PhoneButton`,
39
26
  `CopyCodeButton`). `whatsappTemplate` derives Meta's positional `{{n}}` text.
40
- - `@samva/markup/template` — `defineTemplate`, `SmlTemplate`, `TemplateInput`, and
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.
45
- - `@samva/markup/email` — the JSX runtime. Set `jsxImportSource: "@samva/markup/email"` for email
46
- entries.
47
- - `@samva/markup/email/components` — email primitives (`Email`, `Section`, `Columns`, `Button`,
48
- and the rest of its exports). They generate table layout and classic-Outlook fallbacks.
49
- - `@samva/markup/email/render` — `compileEmail(tree)` and `renderEmailContent`, which lower a
50
- derived email tree to HTML and text.
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.
54
27
  - `@samva/markup/input-schema` — `inputSchema`, which turns any Standard JSON Schema into the
55
28
  portable JSON Schema and validator a template's input is checked against.
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
29
  - `samva:brand` — `BrandLogo` (light logo, swapped for the dark one where the client honors dark
60
30
  mode) and `BrandFooter` (company, postal address, social links and the unsubscribe row the send
61
31
  path fills). The compiler binds them to the brand the build resolves; add
62
32
  `"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.
33
+ `@import "samva:brand";` layers the brand theme; `samva:brand/<slug>` names one brand. These are
34
+ Samva's brand plugin (`samvaBrandPlugin` from `@samva/markup/brand`), the default; a host that
35
+ delivers mail itself passes its own `BrandPlugin` as `brandPlugin` to `compileTemplate`,
36
+ `renderIr` and `renderBrandFooter` to rename the brand import, the footer markers, the
37
+ unsubscribe placeholder and the unsubscribe link's no-track marker.
38
+
39
+ Tooling entries are what a host (Vite, the CLI, the editor, the API, the template agent) imports:
40
+
41
+ - `@samva/markup/compiler` — `compileTemplate`, which reads a project's files and returns the IR,
42
+ the fixtures, the assets and the diagnostics; `checkTemplateCompatibility` checks each fixture
43
+ against the client matrix and locates findings at source. `checkStaticProfile(source | files,
44
+ entry?)` is the synchronous structural check: it runs the compiler's lowering without Tailwind, a
45
+ brand or the files an import names, and returns only findings the source itself owns.
46
+ `introducedProfileErrors(before, after)` is what a visual edit is refused on. It also holds the
47
+ compiler's building blocks (`transformProject`, Tailwind, the cascade, CSS), the brand footer and
48
+ sample renders (`renderBrandFooter`, `renderBrandSample`), and `TEMPLATE_NAME_PATTERN`. It imports no Vite, esbuild, or Node builtins, so it runs in any
49
+ JavaScript host.
50
+ - `@samva/markup/diagnostics` — source spans and line maps, the `DIAGNOSTIC_CODES` registry with
51
+ each code's fix and upgrade recipe, and `EmailDiagnostic`/`EmailCompileError`. It does not load
52
+ the compiler, so a host that only reports imports it.
53
+ - `@samva/markup/render` — `renderIr(ir, input, { locale, timeZone })`, `renderIrSms` and
54
+ `renderIrWhatsApp`; the IR types and `SML_IR_VERSION`; `renderIrPreview(ir, input)`, the render an
55
+ editor shows, with an instance path (`INSTANCE_PATH_ATTRIBUTE`) on every element and the
56
+ `selections` that map each one to its TSX span; `compileEmail`, the HTML serializer and text
57
+ derivation; the email element table; SMS segment counts. Its import closure excludes the
58
+ compiler's libraries (`tests/entry-closure.test.ts`): it is the send-time security boundary and
59
+ what the API bundles.
60
+ - `@samva/markup/edit` — the code transforms behind visual edits (`inspectSourceElement`,
61
+ `literalReplacement`, `expressionReplacement`, `classNameEdit`, `structuralReplacements`,
62
+ `applySourceReplacements` with a `ProfileGuard`); form reads and exact edits for the `sms` and
63
+ `whatsapp` channel bodies (`readSmsChannel`, `editSmsChannel`, `readWhatsAppChannel`,
64
+ `editWhatsAppChannel`), where text between expressions is editable and every expression,
65
+ conditional or element is a locked segment; and JSX source locations. Browser-safe: Babel parser
66
+ only.
67
+ - `@samva/markup/brand` — brand records and their CSS model (`parseBrandCss`, `printBrandCss`), the
68
+ footer markers a send reads (`renderedFooterMarkers`, `withoutUnsubscribeRow`), and project
69
+ assets (`isAssetPath`, `ASSET_CONTENT_TYPES`, `assetUrl`): which imported files become
70
+ content-addressed assets. The send path imports it, so its closure holds only the css-tree parser
71
+ `theme.css` needs; rendering a footer compiles Tailwind and lives in `./compiler`.
72
+
73
+ `tests/published-dependencies.test.ts` pins this list. The compiler's libraries (Babel, Tailwind,
74
+ css-tree, caniemail) are dependencies, because a template project depends on this package alone and
75
+ `samva templates check` runs its installed compiler.
64
76
 
65
77
  The package ships what an agent needs to author without this repository:
66
78
 
67
79
  - `@samva/markup/contract.json` (`generated/contract.json`) — components with their props, the
68
80
  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.
81
+ diagnostic code with its fix. `@samva/markup/reference.md` renders the same contract for reading.
70
82
  `bun run codegen:contract` writes both from the modules that own each fact (component and `Fmt`
71
83
  interfaces, `PROFILE_FORMS`, `DIAGNOSTIC_CODES`, the element table), and a test fails when they
72
84
  drift.
@@ -98,7 +110,7 @@ Read the contract before authoring. Import only subpaths declared in `package.js
98
110
  - **Passing a string where you want markup.** Nothing an author passes as a string becomes markup;
99
111
  strings render as escaped text. There is no raw-HTML escape hatch: compatibility markup comes only
100
112
  from the email primitives.
101
- - **Using an element or attribute the serializer does not allow.** `@samva/markup/email/elements` is
113
+ - **Using an element or attribute the serializer does not allow.** `@samva/markup/render` is
102
114
  the one table behind both the JSX intrinsic prop types and the serializer's allowlist, so an
103
115
  unsupported element or attribute fails at typecheck and again at compile with a source location.
104
116
  - **Naming a template or fixture like a path.** A template id and a fixture key are both written
package/CHANGELOG.md ADDED
@@ -0,0 +1,379 @@
1
+ ## @samva/markup@0.11.0
2
+
3
+ ### Breaking: `@samva/markup` reads brand and footer names from a brand plugin
4
+
5
+ Templates do not change: `samva:brand`, `BrandLogo`, `BrandFooter` and the unsubscribe row work as
6
+ before, because Samva's brand plugin is the default.
7
+
8
+ A host that compiles or renders templates can pass its own `BrandPlugin` as `brandPlugin` to
9
+ `compileTemplate`, `transformProject`, `projectBrandSpecifier`, `parseBrandSpecifier`,
10
+ `checkStaticProfile`, `renderIr`, `compileEmail`, `renderBrandFooter`, `renderBrandSample`,
11
+ `brandComponents`, `renderedFooterMarkers` and `withoutUnsubscribeRow`. The plugin names the brand
12
+ import, the attributes that mark the footer and its unsubscribe row, the unsubscribe link's URL
13
+ placeholder, and the attribute that marks that link as untracked.
14
+
15
+ `BrandFooter`'s unsubscribe link now carries `data-samva-no-track` instead of SES's
16
+ `ses:no-track`. A host that delivers mail through SES turns the marker into `ses:no-track` before it
17
+ sends.
18
+
19
+ `@samva/markup/brand` exports `samvaBrandPlugin` and the `BrandPlugin` type in place of
20
+ `FOOTER_ATTRIBUTE`, `UNSUBSCRIBE_ROW_ATTRIBUTE` and `UNSUBSCRIBE_URL_PLACEHOLDER`; read
21
+ `samvaBrandPlugin.footerAttribute`, `.unsubscribeRowAttribute` and `.unsubscribeUrlPlaceholder`.
22
+ `@samva/markup/compiler` no longer exports `BRAND_SPECIFIER`; read `samvaBrandPlugin.specifier`.
23
+
24
+ ### Breaking: supply brand resolution to the authoring toolchain
25
+
26
+ `@samva/vite` accepts `brandResolver` and `brandPlugin` on the editor plugin and its dev, project,
27
+ build, render and export APIs. A brand import without a resolver fails with `brand-unavailable`.
28
+ Standalone callers supply a resolver; the package does not read Samva credentials, invoke the
29
+ Samva CLI or load Samva brand snapshots. The `offline` option and Samva snapshot helpers move to
30
+ the CLI.
31
+
32
+ `samva templates dev`, `check`, `build`, `render`, `snapshot` and `export` supply the Samva resolver
33
+ and brand plugin, preserving live lookup, cached snapshots and brand diagnostics.
34
+
35
+ ## @samva/markup@0.10.0
36
+
37
+ ### Breaking: `@samva/markup` publishes 17 entries in two tiers
38
+
39
+ Template files import `@samva/markup`, `@samva/markup/email`, `@samva/markup/fmt`,
40
+ `@samva/markup/sms`, `@samva/markup/whatsapp` and `@samva/markup/input-schema`. Email components
41
+ move from `@samva/markup/email/components` to `@samva/markup/email`, the entry `jsxImportSource`
42
+ already names. A template that still imports the old path fails with `moved-import`, which names
43
+ the replacement.
44
+
45
+ Hosts import five tooling entries:
46
+
47
+ | Entry | Replaces |
48
+ | --------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
49
+ | `@samva/markup/compiler` | `./compiler`, `./static`, `./template` (`TEMPLATE_NAME_PATTERN`), `./email/brand-footer` |
50
+ | `@samva/markup/diagnostics` | `./diagnostics`, `./diagnostic-codes`, `./email/diagnostics` |
51
+ | `@samva/markup/render` | `./ir`, `./render-ir`, `./preview`, `./email/render`, `./email/html`, `./email/text`, `./email/elements`, `./sms-segments` |
52
+ | `@samva/markup/edit` | `./source-edit`, `./channel-edit`, `./source-locations` |
53
+ | `@samva/markup/brand` | `./email/brand`, `./email/assets`, `./theme-css` |
54
+
55
+ `@samva/markup/reference.md` replaces `@samva/markup/generated/*`. `tailwindcss`, `css-tree`,
56
+ `caniemail` and `@babel/parser` are dependencies instead of optional peers, so a project that
57
+ depends on `@samva/markup` alone can run `samva templates check`.
58
+
59
+ ## @samva/markup@0.9.0
60
+
61
+ ### Breaking: remove textual email SML APIs
62
+
63
+ `@samva/markup` removes textual email SML components and grammar types, plus the
64
+ `@samva/markup/candidate`, `/classes`, `/checks`, `/model`, `/ops`, `/registry`, and `/tsx-tracer`
65
+ subpaths. Email templates use TSX `defineEmail` modules and lower directly to subject, HTML, and text
66
+ through the email semantic tree. The structured SML language and compiler remain for SMS and
67
+ WhatsApp.
68
+
69
+ `@samva/vite` removes `darkClassCount` from the public `AuthoringValidationSummary` returned by its
70
+ emit API. Email validation no longer reports a count derived from textual email SML classes.
71
+
72
+ ### Breaking: the executing template runtime and the textual SML grammar are removed
73
+
74
+ A template is `defineTemplate` in the static profile, compiled to IR; nothing in `@samva/markup`
75
+ runs a template any more.
76
+
77
+ - **Removed.** `defineEmail`, `isTemplateDefinition`, `TemplateDefinition`, `TemplateRuntime`,
78
+ `renderEmail` and `bindStylesheets`; `compileEmailProject` and the generated entry, reset and
79
+ stylesheet-registry modules (`@samva/markup/email/stylesheets`); the TSX source instrumentation and
80
+ runtime tracing (`@samva/markup/tsx-source-instrumentation`, `@samva/markup/email/selection`); and
81
+ the textual structured SML grammar with its JSX runtime and components
82
+ (`@samva/markup/{ast,components,compile,compile-sms,compile-whatsapp,conditionals,emit,
83
+ interpolate,jsx-runtime,jsx-dev-runtime,lexer,parser,predicate,printer,render-template,variables}`).
84
+ SMS and WhatsApp bodies are the `sms` and `whatsapp` fields of `defineTemplate`, written with
85
+ `Sms` from `@samva/markup/sms` and `WhatsApp` from `@samva/markup/whatsapp`.
86
+ - **`@samva/markup/preview`** adds `renderIrPreview`, which renders a fixture from the IR with an
87
+ instance path on every element and the selection each path resolves to, taken from the source
88
+ spans the compiler recorded. The selection helpers that lived in `email/selection` move here.
89
+ - **`@samva/markup/source-edit`** (moved from `email/source-edit`) is the set of code transforms behind
90
+ visual edits: text, attributes, `className`, and inserting, duplicating, moving and deleting
91
+ elements. `checkStaticProfile` in `@samva/markup/static` refuses an edit that would leave the
92
+ static profile. `@samva/markup/channel-edit` does the same for `sms` and `whatsapp` bodies.
93
+ - **`TemplateInput`** reads the caller input from a template's `schema`.
94
+
95
+ ### Breaking: templates are `defineTemplate` in a static profile
96
+
97
+ `@samva/markup` adds `defineTemplate({ id, schema, fixtures, locale?, email, sms?, whatsapp? })` at
98
+ its root. A template is one TSX file in a static profile that the new compiler reads without running:
99
+ text and attribute bindings, template strings, `&&`, `?:`, comparisons, `||`, `.length`, `.map`,
100
+ `fmt.*` calls, arithmetic inside formatter arguments and conditions, partials with props and
101
+ children, conditional class names, `.filter(condition)` before `.map` or `.length`, and
102
+ `<Columns each per>` for grids (a `Column` without a `width` takes an equal share of its row). Everything else is a compile error with a stable code, a
103
+ `file:line:column` location and a fix.
104
+
105
+ - `@samva/markup/static` compiles a project to a versioned JSON IR (`"sml": 1`) and checks every
106
+ binding against the input schema: `unknown-field` with a did-you-mean, `unguarded-optional`, and
107
+ `fixture-invalid` per fixture. Tailwind, project CSS, brand themes, fonts and assets resolve at
108
+ compile time, and `checkTemplateCompatibility` locates client findings at source.
109
+ - `@samva/markup/render-ir` renders the IR against an input as a pure, dependency-free function.
110
+ It escapes for text, attribute and URL contexts and rejects unsafe bound values.
111
+ - `@samva/markup/fmt` adds the deterministic formatters `money`, `number`, `date`, `time`, `plural`
112
+ and `list`, which take their locale and time zone from the render.
113
+ - `@samva/markup/sms` and `@samva/markup/whatsapp` add channel components with distinct names per
114
+ channel; WhatsApp bindings become Meta's positional `{{n}}` placeholders.
115
+ - `id` is lowercase kebab-case, the rule the CLI and API slugs share. `subject` and `preheader`
116
+ belong to the channel; the `Email` component no longer takes a `preheader` prop.
117
+
118
+ `defineEmail` remains for templates that compute over their input, and it stays outside the static
119
+ profile.
120
+
121
+ ### Agents get a contract, a skill and a `templates/` root
122
+
123
+ - **`@samva/markup`.** Ships `@samva/markup/contract.json` and `generated/reference.md`, generated
124
+ from the compiler: component props, the static-profile forms and the forms it refuses, formatters,
125
+ HTML elements and every diagnostic code with its fix. The `sml` skill ships in the package under
126
+ `skills/sml`. Every compiler finding now comes from one registry of codes
127
+ (`DIAGNOSTIC_CODES`). The generated `sml-*-contract.json` and `sml-*-reference.md` files are
128
+ replaced by these two.
129
+ - **Entry root.** New hosted projects, the starter and the gallery use `templates/` as the entry
130
+ root. `emails/` is still accepted for entry paths in the API, the SDK types and the CLI
131
+ (`samva templates add`, `move`, `publish`).
132
+
133
+ ## @samva/markup@0.8.0
134
+
135
+ ### Breaking: `@samva/markup/presets` is `@samva/markup/starters`
136
+
137
+ Every `@samva/markup/presets*` subpath is renamed to `@samva/markup/starters*`, and its exports
138
+ follow: `presetCatalog` is `starterCatalog`, `presetProject` is `starterProject`,
139
+ `presetEmailFixtures` is `starterEmailFixtures`, and every other `preset*` / `Preset*` name reads
140
+ `starter*` / `Starter*`. A public gallery entry's `presetId` is `starterId`, and the corpus
141
+ record types are `OwnedEditableStarter` and `BrandRecreationStarter`. `RECOMMENDED_THEME` and
142
+ `showcaseProjectPaths` are removed.
143
+
144
+ ### Starters on theme variables
145
+
146
+ Every starter styles each color and font through a theme variable, and keeps its look in its own
147
+ `starter.css`. `@samva/markup/starters/theme` names the variables every starter defines and a
148
+ brand sets to restyle one (`STARTER_THEME_VARIABLES`: `--color-brand`, `--color-surface`,
149
+ `--font-body` and the rest; `starterThemeVariableNames` adds each color's `-dark` pair) and writes
150
+ the `theme.css` a project starts with (`starterThemeCss({ brand })`), which imports `samva:brand`
151
+ only when `brand` is true:
152
+
153
+ ```css
154
+ @import "./starter.css";
155
+ @import "samva:brand";
156
+ @theme {
157
+ }
158
+ ```
159
+
160
+ `var()` now resolves in a `Button`'s classic Outlook shape and in a `bgcolor`, so
161
+ `<Button backgroundColor="var(--color-brand)">` paints the theme color everywhere. Every `@theme`
162
+ in project and brand CSS compiles as `static`, so a variable a project stylesheet reads reaches
163
+ the inliner even when no utility class reads it.
164
+
165
+ ### Theme imports and brand themes
166
+
167
+ A project `theme.css` layers with `@import`: another project stylesheet by relative path, then the
168
+ organization's brand, then the project's own `@theme` overrides.
169
+
170
+ ```css
171
+ @import "./starter.css";
172
+ @import "samva:brand";
173
+
174
+ @theme {
175
+ --color-accent: #0f766e;
176
+ }
177
+ ```
178
+
179
+ `samva:brand` names the organization's default brand and `samva:brand/<slug>` names one brand; the
180
+ build supplies it through `compileEmailProject`'s `brand` option. `parseBrandCss` and
181
+ `printBrandCss` (`@samva/markup/theme-css`) read and write the brand theme grammar: `@theme`
182
+ variables in the color, font, text, leading, tracking, radius, spacing, container and shadow
183
+ namespaces, `--color-*` variables in a `@media (prefers-color-scheme: dark)` block, and
184
+ `@font-face` rules with https WOFF2 sources. A dark-mode color compiles to its `--color-*-dark`
185
+ theme variable, read by `dark:` utilities.
186
+
187
+ A project compiles against one brand: importing two fails with `brand-import-conflict`, and a
188
+ `samva:brand` import the build has no brand for, or whose slug names another brand, fails with
189
+ `brand-unavailable`. A brand theme outside the grammar fails with its `brand-css-*` finding.
190
+ `@samva/markup/compiler` exports `parseBrandSpecifier`, `projectBrandSpecifier` and the
191
+ `EmailBrand` types.
192
+
193
+ Breaking: any other `@import` in the theme — including `@import "tailwindcss"` — now refuses the
194
+ compile with `css-import-unresolved` instead of resolving to nothing. An `@import` with layer,
195
+ supports or media conditions, one that imports itself, or a `.css` path outside the project is
196
+ refused the same way. A theme with no utility class in use still compiles its `@font-face` rules.
197
+
198
+ ### `BrandLogo` and `BrandFooter`
199
+
200
+ ```tsx
201
+ import { BrandFooter, BrandLogo } from "samva:brand";
202
+ ```
203
+
204
+ `BrandLogo` renders the brand's logo and swaps to its dark logo where the client honors dark mode.
205
+ It takes `width` (default 120), `alt`, `align` and `className`, and renders nothing when the brand
206
+ has no logo. `BrandFooter` takes `unsubscribeLabel` and `className`.
207
+ `BrandFooter` renders the company name, postal address, social links and an unsubscribe row; the
208
+ send path fills the unsubscribe link per delivery and never click-tracks it, and a template that
209
+ renders `BrandFooter` gets no appended footer. Add `"types": ["@samva/markup/samva-brand"]` to the
210
+ project's tsconfig so `tsc` knows the module.
211
+
212
+ ### Breaking: token-table exports removed
213
+
214
+ `@samva/markup/footer`, `@samva/markup/theme`, and `@samva/markup/tokens` are removed; a brand's
215
+ footer renders through `BrandFooter`. `parseThemeCss` and `printThemeCss` are replaced by
216
+ `parseBrandCss` and `printBrandCss` in `@samva/markup/theme-css`.
217
+
218
+ ### Local builds resolve `samva:brand`
219
+
220
+ `samvaEditor()`, `buildTemplates()` and `exportEmailProject()` compile a project that imports
221
+ `samva:brand` or `samva:brand/<slug>` against its organization's brand, resolved in this order:
222
+
223
+ 1. Live. With `SAMVA_API_KEY` in the environment or the project's `.env` (and `SAMVA_API_URL` for
224
+ another API), the brand is fetched from `/v1/brands`. Without one, the `samva` CLI on `PATH`
225
+ answers `samva brands get --json` with its `samva login` session; `@samva/vite` stores no
226
+ credential.
227
+ 2. Snapshot. `.samva/brands/<slug>.json`, written by `samva brands pull [slug]`, with a
228
+ `brand-offline` warning. The default brand reads the most recent snapshot pulled while it was
229
+ the default.
230
+ 3. Neither. The build fails with `brand-unavailable`, naming both fixes.
231
+
232
+ When the live brand's digest differs from the snapshot's, the build warns with
233
+ `brand-snapshot-stale`; run `samva brands pull` again. Warnings are build diagnostics and editor log
234
+ lines and never fail a build. The editor and `check --watch` reuse a resolution for up to 30
235
+ seconds. Keep `.samva` in `.gitignore`; a project that imports no brand needs no credentials.
236
+
237
+ `@samva/vite/project` exports `resolveProjectBrand`, `loadBrandEnv`, `BRAND_SNAPSHOT_DIR` and the
238
+ `BrandResolution`, `BrandSnapshot`, `BrandWarning` and `ResolvedBrand` types. The editor recompiles
239
+ the theme when any stylesheet it imports changes.
240
+
241
+ ### Breaking: `exportEmailProject` themes the export
242
+
243
+ `exportEmailProject` drops `tailwind` and takes `root` and `theme`. `dir` holds the templates and
244
+ `root` (default `dir`) is the project: its `theme.css` themes the export as in `buildTemplates()`,
245
+ `theme` names another file or `false` disables it, and only `.tsx` files under `dir` are entries.
246
+
247
+ ```ts
248
+ const receipt = await exportEmailProject({ dir: "emails", root: ".", out: "dist/email" });
249
+ ```
250
+
251
+ ### Project fonts in `@font-face`
252
+
253
+ A template project declares a webfont with ordinary CSS: an `@font-face` in `theme.css` (or any
254
+ imported stylesheet) whose `src` names a `.woff2` file in the project, plus a `--font-*` token in
255
+ `@theme`.
256
+
257
+ ```css
258
+ @font-face {
259
+ font-family: "Brand Sans";
260
+ src: url("./fonts/brand-sans.woff2") format("woff2");
261
+ font-weight: 400;
262
+ }
263
+ @theme {
264
+ --font-brand: "Brand Sans", Helvetica, Arial, sans-serif;
265
+ }
266
+ ```
267
+
268
+ The compiler serves the file as a content-addressed asset, so local previews, exports and
269
+ publications all load the same bytes, and it puts the rule in a `<style>` classic Outlook does not
270
+ read, so Outlook uses the rest of the stack instead of Times New Roman.
271
+
272
+ Breaking for projects that already declared fonts: a relative `src` that names no project file, a
273
+ source that is not WOFF2, and an `http:` or `data:` source are compile errors, and a `font-family`
274
+ stack that names a project font without ending in a generic family fails with
275
+ `email-font-fallback`. `Stylesheet` gains `fontFaces`, and `@font-face` no longer appears in
276
+ `headAtRules`.
277
+
278
+ ## @samva/markup@0.7.0
279
+
280
+ ### Breaking: WhatsApp `category="service"` removed
281
+
282
+ `<WhatsApp category>` accepts `marketing`, `utility`, or `authentication`, the categories Meta
283
+ approves templates under.
284
+
285
+ ## @samva/markup@0.6.0
286
+
287
+ ### Improve markup compilation safety
288
+
289
+ Invalid or unsafe markup is now rejected earlier, preventing malformed email output. Complex styles
290
+ and conditions also compile more reliably.
291
+
292
+ ### Carry the plain-text alternative on a public preview artifact
293
+
294
+ `buildPublicTemplatePreviewArtifact` returns `text` alongside `html`, `subject` and `preheader`, so
295
+ a surface that mounts a preset's published preview has the whole executed fixture rather than its
296
+ HTML half. It is the same value `renderEmail` derived when the artifact was built; nothing is
297
+ recomputed.
298
+
299
+ ## @samva/markup@0.5.0
300
+
301
+ ### Breaking: a template project no longer installs Effect
302
+
303
+ - **A much smaller install:** `@samva/markup` has no Effect dependency and no `ajv` dependency, so a template project's lockfile and `node_modules` drop both graphs. The JSON Schema 2020-12 dialect the input validator checks against ships inside the package. Writing templates is unchanged: JSX, `defineEmail`, components, fixtures, and the input schema all work exactly as before, and you can still declare template input with an Effect, Zod, or plain JSON schema.
304
+ - **`@samva/vite` drops Effect too:** The Vite plugin carries no Effect dependency either, so `samva email templates build`, the editor dev server, and a template project that installs both packages resolve neither graph.
305
+ - **Breaking: compile and render results:** Everything the compiler returns is now a [better-result](https://better-result.dev) `Result`. Read a success with `Result.isOk(result)` and `result.value`, a failure with `Result.isError(result)` and `result.error`. Errors keep their names and tags — `SmlParseError`, `SmlCompileError`, `SmlOpError`, `EmailCompileError` — so code that matches on `_tag` or catches `EmailCompileError` still works.
306
+ - **Breaking: data unions discriminate on `type`:** `EmailNode` and the editor-op, corpus and gallery unions name their variant with `type` instead of `_tag` — `node.type === "Element"`, `op.type === "setProp"`. Compiler errors are unaffected: `SmlParseError`, `SmlCompileError`, `SmlOpError` and `EmailCompileError` keep `_tag`, which is their error library's own field.
307
+ - **Breaking: schema values are gone:** The package declares its data models as TypeScript types only. Runtime schema values such as `EmailDiagnostic`, `EmailElementSelection`, `SmlOpSchema`, and `SmlDocumentSchema` no longer exist; the types of the same name do, and the shapes are identical. If you decoded one of these at a boundary, declare the schema in your own project against the exported type.
308
+
309
+ ## @samva/markup@0.4.0
310
+
311
+ ### Faster, reliable saves in large projects
312
+
313
+ - **Saves in large projects are quick again:** Saving a template no longer re-evaluates every template in the project, so previews come back in about the time one template takes to build.
314
+ - **No false "Document changed elsewhere" banner:** Your own edits and fixture switches are recognized as yours, so the editor no longer locks the source panel and undo after a save. Edits made outside the editor are still detected.
315
+
316
+ ### Breaking: every email template is a complete, editable project
317
+
318
+ - **28 templates rebuilt:** The 28 remaining single-file presets are rebuilt as showcase projects with their own stylesheet, drawn artwork, and `default` and `long-content` sample data. Marketing, digest, lifecycle, commerce, and notification templates are covered, authored for five fictional senders: Northwick, Kestrel, Northloop, Fernhollow, and Meridian. Template ids and their gallery pages are unchanged.
319
+ - **Breaking: simpler template inputs:** Repeating content such as stories, deals, agenda items, checklist steps, and order lines is a list instead of numbered fields. Images are bundled with the project instead of passed as URLs, and headline and body copy is edited in the template source rather than supplied as input. Optional sections such as sponsors, gift cards, and reminders appear only when you send them. Dates, days remaining, and step numbers come from your input, so scheduled sends never depend on the clock. If you render one of these templates with your own data, move to the new input shape; each template's sample data shows it.
320
+ - **Breaking: four presets retired:** `receipt`, `newsletter`, `promo`, and `order-delivered` are removed from the catalog, the public gallery, and the `PresetId` types. Use `order-receipt`, `product-newsletter`, and `product-launch-promo` instead.
321
+ - **Stock photography removed:** The onboarding tour and setup checklist templates now use drawn artwork, so no template depends on third-party images.
322
+
323
+ ## @samva/markup@0.3.0
324
+
325
+ - **Reliable project styling:** Switching between projects or preview fixtures preserves each template's styles and avoids unexpected revision changes.
326
+ - **Better Outlook buttons:** `<Button>` sizes to its label without requiring a fixed width. Explicit widths remain supported.
327
+ - **New editable templates:** Product newsletter, order receipt, product launch promotion, and an attributed Vercel invitation, including styles, images, and sample data.
328
+ - **Richer template inputs:** Use lists and objects for repeating content such as newsletter stories and receipt items.
329
+ - **Cleaner plain-text emails:** Empty table cells no longer leave unnecessary separators.
330
+ - **For custom render integrations:** `bindStylesheets` from `@samva/markup/email/render` binds a renderer to one project's styles.
331
+
332
+ ## @samva/markup@0.2.0
333
+
334
+ ### Breaking: author email templates with typed input and executable TSX
335
+
336
+ Email entries now default-export `defineEmail({ id, schema, fixtures, render })` from
337
+ `@samva/markup/template`. Replace function-based email entries and `Variables<T>` placeholders
338
+ with a schema and a `render(input)` function. Set `jsxImportSource` to `@samva/markup/email`,
339
+ import email components from `@samva/markup/email/components`, and upgrade `@samva/markup` and
340
+ `@samva/vite` together to 0.2.0. Regenerate the project's dependency lockfile before publishing.
341
+
342
+ The input contract supports Standard JSON Schema, including Effect and Zod schema adapters.
343
+ `TemplateInput<typeof template>` extracts the inferred input type. Named fixtures supply preview
344
+ and validation inputs; rendering evaluates ordinary TSX, imported components, conditionals and
345
+ mapped arrays against actual input. Time and randomness must come from input or fixed `runtime`
346
+ configuration.
347
+
348
+ The shared local and hosted compiler resolves CSS, Tailwind and imported assets, validates fixture
349
+ inputs, and produces HTML, text and email-client diagnostics. Diagnostics identify known support
350
+ constraints; they do not certify rendering in every email client.
351
+
352
+ The Vite editor maps rendered instances to canonical TSX source. Literal edits preserve the
353
+ source expression; computed or structural changes use reviewed source edits. Fixture switching,
354
+ undo/redo, save/reopen and revision-conflict handling keep source edits tied to the preview.
355
+
356
+ Published email templates pin an executable, input contract, dependency graph and runtime
357
+ configuration. Existing email source must adopt the new contract and be republished; previously
358
+ published HTML snapshots are not executable publications.
359
+
360
+ ## @samva/markup@0.1.2
361
+
362
+ ### Reject oversized SML templates before compilation
363
+
364
+ Templates that exceed supported size, nesting, or node limits now return a validation error before
365
+ compilation.
366
+
367
+ ## @samva/markup@0.1.1
368
+
369
+ ### Release the 0.1.1 package family
370
+
371
+ `@samva/markup` publishes with the rest of the 0.1.1 family.
372
+
373
+ ## @samva/markup@0.1.0
374
+
375
+ ### Publish the Samva template authoring toolchain
376
+
377
+ `@samva/markup` and `@samva/vite` are published on npm. They provide typed TSX authoring, local
378
+ visual editing, and deterministic template builds. `samva templates push` uploads the compiled
379
+ templates.
package/README.md CHANGED
@@ -14,7 +14,7 @@ template is one `.tsx` file that default-exports `defineTemplate`:
14
14
 
15
15
  ```tsx
16
16
  import { defineTemplate } from "@samva/markup";
17
- import { Button, Email, Section } from "@samva/markup/email/components";
17
+ import { Button, Email, Section } from "@samva/markup/email";
18
18
  import { fmt } from "@samva/markup/fmt";
19
19
  import { jsonSchema } from "@samva/markup/input-schema";
20
20
 
@@ -94,16 +94,16 @@ export default defineTemplate({
94
94
  });
95
95
  ```
96
96
 
97
- A template is read, not run. `@samva/markup/static` parses the file and the project files it
97
+ A template is read, not run. `@samva/markup/compiler` parses the file and the project files it
98
98
  imports without evaluating them, checks every binding against the schema, resolves Tailwind and CSS
99
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`
100
+ markup, the bindings, the conditions, the loops and the formatters. `@samva/markup/render`
101
101
  renders that IR against an input. It is a pure function with no dependencies, so nothing executes
102
102
  at send time.
103
103
 
104
104
  ```ts
105
- import { renderIr } from "@samva/markup/render-ir";
106
- import { compileTemplate } from "@samva/markup/static";
105
+ import { renderIr } from "@samva/markup/render";
106
+ import { compileTemplate } from "@samva/markup/compiler";
107
107
 
108
108
  const compiled = await compileTemplate({
109
109
  files, // the project's files by path; binary files become content-addressed assets
@@ -232,7 +232,7 @@ for diagnostics an agent can apply.
232
232
 
233
233
  ### Previewing a render
234
234
 
235
- `renderIrPreview(ir, input)` from `@samva/markup/preview` renders a fixture the way `renderIr` does
235
+ `renderIrPreview(ir, input)` from `@samva/markup/render` renders a fixture the way `renderIr` does
236
236
  and stamps `data-samva-instance` on every element. Its `selections` map each stamped element to the
237
237
  line and column of the JSX that produced it, so an editor can select an element on the canvas and
238
238
  edit the source at that span. A delivered message never carries the attribute.