@samva/vite 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -1,92 +1,96 @@
1
1
  # @samva/vite agent guide
2
2
 
3
3
  `@samva/vite` is the local authoring toolchain for Samva templates written with `@samva/markup`: a
4
- Vite plugin that serves the visual editor, and programmatic entry points that build, render, and
5
- export a template project. It compiles nothing itself; every render goes through
6
- `@samva/markup/compiler`'s `compileEmailProject`, the same compile step a hosted Samva build runs, so
7
- a local render does not drift from a published one. Publishing and hosted synchronization are not
4
+ Vite plugin that serves the visual editor, a config-free dev server, and programmatic entry points
5
+ that build, render, and export a template project. A template is one TSX file in a static profile,
6
+ and nothing here runs it: every check, render and preview is `compileTemplate` from
7
+ `@samva/markup/compiler` followed by `renderIr`, the same two steps a hosted Samva build takes, so a
8
+ local render does not drift from a published one. Publishing and hosted synchronization are not
8
9
  here: they are ordinary Git plus `samva templates publish` from `@samva/cli` (see
9
10
  `samva templates --help`).
10
11
 
11
12
  ## Entry points
12
13
 
13
- - `@samva/vite` — `samvaEditor()`, the plugin you add to `vite.config.ts`. Options are typed by
14
- `SamvaEditorPluginOptions`; the README lists them.
15
- - `@samva/vite/build` — `buildTemplates()` and `createTemplateBuildSession()` for programmatic
16
- checks and builds. The `samva` CLI loads this module from your project, not from its own bundle.
17
- - `@samva/vite/project` — the folder-of-templates surface: `loadEmailCatalog()`,
18
- `renderEmailFixtures()`, `createEmailProjectSession()`, `exportEmailProject()`.
19
- - `@samva/vite/email` — `createEmailRenderer()` and `renderEmailProject()` for rendering one email
20
- project from source.
21
- - `@samva/vite/emit` — module-preview integration for structured SMS and WhatsApp entries.
14
+ - `@samva/vite` — `samvaEditor()`, the plugin you add to `vite.config.ts` when the defaults do not
15
+ cover your setup. Options are typed by `SamvaEditorPluginOptions`; the README lists them.
16
+ - `@samva/vite/dev` — `startDevServer({ root, dir?, port?, host?, open? })`: a Vite server with no
17
+ config file, `samvaEditor()`, and defaults. It is what `samva templates dev` starts.
18
+ - `@samva/vite/build` — `buildTemplates(options)` and `createTemplateBuildSession(options)` for
19
+ programmatic checks and builds. The `samva` CLI loads this module from your project, not from its
20
+ own bundle.
21
+ - `@samva/vite/render` — `renderTemplate()` and `renderChannel()`: one template and fixture to a
22
+ plain JSON result.
23
+ - `@samva/vite/project` — the folder-of-templates surface: `loadProject()`, `compileProject()`,
24
+ `createProjectSession()`, `exportProject()` and the render helpers over a compiled entry.
22
25
 
23
26
  Install `@samva/markup`, `@samva/vite`, and the `vite` peer as dev dependencies of the template
24
- project and keep them in its lockfile. The CLI's `templates check`, `templates build`, and the
25
- editor all resolve these packages from the project, so the lockfile is what pins the compiler
26
- version your previews, checks, and builds use.
27
+ project and keep them in its lockfile. The CLI's `templates check`, `templates build`,
28
+ `templates render` and the editor all resolve these packages from the project, so the lockfile is
29
+ what pins the compiler version your previews, checks, and builds use.
27
30
 
28
31
  ## How you hurt yourself here
29
32
 
30
- - **Expecting your Vite config to apply to email rendering.** Every email render, in the editor,
31
- the CLI, and the programmatic entry points, runs in an evaluator this package roots at its own
32
- directory with no config file, so your `vite.config.ts` aliases and plugins never reach an email
33
- template. Bare imports resolve from the package root, which is what makes `@samva/markup` resolve
34
- under both hoisted and per-package installs. Import shared helpers by relative path; an alias that
35
- works elsewhere in your app fails here.
36
- - **Side effects at module scope.** Discovery imports every `.tsx` under the templates directory
37
- and asks whether its default export is a template definition, and a rebuild re-reads the whole
38
- project. Top-level code in every module, helpers included, runs whenever the project is
39
- evaluated; keep it free of I/O and mutable global state.
33
+ - **Expecting a template module to run.** The compiler reads the file and never executes it, so
34
+ top-level code, imports of constants, and helper calls do nothing. A value a template needs is
35
+ input data, a fixture literal, or an import the static profile allows (a partial, a stylesheet,
36
+ an asset). `samva templates check` reports what the profile refuses, with a location and a fix.
37
+ - **Expecting your Vite config to apply to templates.** Nothing about a template goes through
38
+ Vite's module graph, so your `vite.config.ts` aliases and plugins never reach one. Import
39
+ partials by relative path.
40
+ - **Putting templates where the project root cannot see them.** Files are addressed relative to
41
+ the project root, which holds `theme.css`, shared imports and `.samva/`. A templates directory
42
+ outside it is refused rather than half-read. Set `projectRoot` (plugin) or `root` (entry points)
43
+ to a folder that contains both.
44
+ - **Scanning the wrong directories.** With no `dir`, `templates/` and `emails/` are both scanned,
45
+ whichever exist. Setting `dir` scans only that one, so a project that keeps templates in both
46
+ and sets `dir` loses the other.
40
47
  - **Relying on a file path as template identity.** Identity is the `id` the definition declares.
41
48
  Two definitions declaring one id is a reported error, and moving a file does not change its id.
42
- Helper modules need no marker or exclusion list: a module is a template only if its default export
43
- is a definition.
44
- - **Exporting for delivery with the default asset base.** `exportEmailProject` renders with relative
49
+ Helper modules need no marker or exclusion list: a module is a template only if its default
50
+ export is `defineTemplate`. An editor document is a template's path plus its channel.
51
+ - **A broken file elsewhere in the project.** The compiler reads an entry and the files it imports.
52
+ A file it cannot parse is a finding that names the file, and it stops the templates that import
53
+ it; the others keep their render.
54
+ - **Exporting for delivery with the default asset base.** `exportProject` renders with relative
45
55
  URLs and writes each page at `templates/<id>/<fixture>.html` beside a content-addressed `assets/`
46
56
  directory, so its default base, `DEFAULT_EXPORT_ASSET_BASE`, only works when those files are
47
- browsed from disk. An inbox has no directory to resolve a relative URL against, so delivery refuses
48
- one. For an export you will send, pass the https origin that will serve the assets.
49
- - **Reusing a renderer without a stable namespace.** `createEmailRenderer` keeps one evaluator
50
- across renders, as a dev server does, and gives each project its own module-id namespace. Omit
51
- `namespace` and every `load` takes a fresh one, which isolates unrelated projects but grows the
52
- graph on every reload. A watch loop over one project passes the same `namespace` each time so its
53
- ids stay stable. Each `load` re-evaluates its own namespace's modules; `invalidate()` also drops
54
- the modules namespaces share. `close()` releases the evaluator.
55
- - **Expecting the email editor to overwrite concurrent edits.** The editor writes back to the
56
- entry's canonical `.tsx` source after checking that the file on disk is still the revision it
57
- read, so a change you saved elsewhere is not overwritten; reload the entry and retry. SMS and
58
- WhatsApp entries are read-only in the editor.
59
- - **Committing or hand-editing `.samva/brands`.** `samva:brand` resolves live first and falls
60
- back to the snapshot `samva brands pull` writes there, warning when it is older than the live
61
- brand. The API owns the brand, so the directory is gitignored and refreshed with `pull`; a
62
- project with neither credentials nor a snapshot fails with `brand-unavailable`.
57
+ browsed from disk. An inbox has no directory to resolve a relative URL against, so delivery
58
+ refuses one. For an export you will send, pass the https origin that will serve the assets.
59
+ - **Expecting the editor to overwrite concurrent edits.** The editor writes back to the entry's
60
+ `.tsx` source after checking that the file on disk is still the revision it read, so a change you
61
+ saved elsewhere is not overwritten; reload the entry and retry.
62
+ - **Committing or hand-editing `.samva/`.** `.samva/` holds files this package generates. It is
63
+ gitignored, like `.next/` and `.tanstack/`, and changes inside it never recompile templates.
64
+ `samva:brand` resolves live first, refreshes `.samva/brands/<slug>.json` from every live answer,
65
+ and falls back to that copy, warning when it is used or was older than the live brand. The API
66
+ owns the brand; a project with neither credentials nor a cached copy fails with
67
+ `brand-unavailable`.
63
68
 
64
69
  ## Rules
65
70
 
66
- - **Template entries are `.tsx`.** Discovery ignores every other extension.
71
+ - **Template entries are `.tsx`.** Discovery ignores every other extension, hidden folders and
72
+ `node_modules`.
67
73
  - **Template ids and fixture keys match `TEMPLATE_NAME_PATTERN`** from `@samva/markup/template`,
68
74
  because both are written into export paths. Code that builds a path from them imports that
69
75
  pattern instead of restating it.
70
76
  - **Fixtures are validated against the template's own schema before they render.** A bad fixture is
71
77
  a finding on that template, not a runtime surprise at send time.
72
78
  - **One failure stays with its template.** A refused render reports against its fixture, and an
73
- email entry that does not build stays in the catalog with a null render and the findings that
74
- stopped it. SMS and WhatsApp entries that stop compiling keep their last successful preview,
75
- labeled stale, until the file compiles again or is deleted.
76
- - **Results are values.** `@samva/vite/build` and the preview surfaces return discriminated results
77
- with an `ok` field, and `@samva/vite/emit` passes through the compiler's better-result `Result`. A
78
- missing `vite` peer, or an entry whose default export is not a template definition where one is
79
- required, throws.
79
+ entry that does not compile stays in the catalog with a null render and the findings that
80
+ stopped it; an editor document whose file stops compiling keeps its place the same way.
81
+ - **Results are values.** `buildTemplates`, `renderTemplate` and `renderChannel` return
82
+ discriminated results with an `ok` field and never throw for a finding about your source. A
83
+ missing `vite` peer, or a templates directory outside the project root, throws.
80
84
  - **The asset base is a build input, not authored source.** The same content can be built for one
81
85
  origin and exported for another. The dev server is not an https origin, so editor previews
82
86
  reference assets through the editor's own route by path.
83
87
 
84
88
  ## Proving your setup
85
89
 
86
- - `samva templates check --dir <templates-dir>` executes every declared fixture and reports
90
+ - `samva templates check` compiles every template, renders every declared fixture, and reports
87
91
  findings; `--watch` keeps it running while you edit.
88
- - `vite` with `samvaEditor()` configured serves the editor at the configured `route`; every entry
89
- should appear with its fixtures.
90
- - `exportEmailProject({ dir, out })` writes browsable HTML and text for every fixture and a
92
+ - `samva templates dev`, or `vite` with `samvaEditor()` configured, serves the editor at the
93
+ configured `route`; every template should appear with its fixtures, one document per channel.
94
+ - `exportProject({ root, out })` writes browsable HTML and text for every fixture and a
91
95
  `catalog.json` with the asset mapping and findings, without Samva credentials unless the project
92
- imports `samva:brand` and has no snapshot.
96
+ imports `samva:brand` and has no cached copy.
package/CHANGELOG.md ADDED
@@ -0,0 +1,339 @@
1
+ ## @samva/vite@0.10.0
2
+
3
+ ### Breaking: `@samva/markup` publishes 17 entries in two tiers
4
+
5
+ Template files import `@samva/markup`, `@samva/markup/email`, `@samva/markup/fmt`,
6
+ `@samva/markup/sms`, `@samva/markup/whatsapp` and `@samva/markup/input-schema`. Email components
7
+ move from `@samva/markup/email/components` to `@samva/markup/email`, the entry `jsxImportSource`
8
+ already names. A template that still imports the old path fails with `moved-import`, which names
9
+ the replacement.
10
+
11
+ Hosts import five tooling entries:
12
+
13
+ | Entry | Replaces |
14
+ | --------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
15
+ | `@samva/markup/compiler` | `./compiler`, `./static`, `./template` (`TEMPLATE_NAME_PATTERN`), `./email/brand-footer` |
16
+ | `@samva/markup/diagnostics` | `./diagnostics`, `./diagnostic-codes`, `./email/diagnostics` |
17
+ | `@samva/markup/render` | `./ir`, `./render-ir`, `./preview`, `./email/render`, `./email/html`, `./email/text`, `./email/elements`, `./sms-segments` |
18
+ | `@samva/markup/edit` | `./source-edit`, `./channel-edit`, `./source-locations` |
19
+ | `@samva/markup/brand` | `./email/brand`, `./email/assets`, `./theme-css` |
20
+
21
+ `@samva/markup/reference.md` replaces `@samva/markup/generated/*`. `tailwindcss`, `css-tree`,
22
+ `caniemail` and `@babel/parser` are dependencies instead of optional peers, so a project that
23
+ depends on `@samva/markup` alone can run `samva templates check`.
24
+
25
+ ## @samva/vite@0.9.0
26
+
27
+ ### Breaking: the editor, build and render run on the compiled template, not on evaluated code
28
+
29
+ `@samva/vite` compiles every template with the static compiler from `@samva/markup` and renders the
30
+ IR, so nothing in your project is executed to preview, check, build or export it. A local render is
31
+ the render a published template produces, and top-level code in a template module no longer runs.
32
+ Templates are `defineTemplate` files; a `defineEmail` entry is reported as an error.
33
+
34
+ - **Directories.** Templates live in `templates/` by default, and `emails/` is recognized beside it.
35
+ Set `templatesDir` on `samvaEditor()` (or `dir` on the entry points) to scan one directory
36
+ instead; `projectRoot` names the folder that holds `theme.css`, shared imports and `.samva/` when
37
+ it is not the Vite root. Document ids are project-relative paths, and a template that declares
38
+ SMS or WhatsApp gets one editor document per channel over the same file.
39
+ - **`.samva/`.** The brand record `samva:brand` resolves is fetched live and cached in
40
+ `.samva/brands/<slug>.json`, refreshed by every live fetch, and used with a warning when the API
41
+ is unreachable. Keep `.samva` in `.gitignore`.
42
+ - **`@samva/vite/dev`** adds `startDevServer({ root, dir?, port?, host?, open? })`, a Vite dev server
43
+ with no config file that serves the editor; `samva templates dev` runs it.
44
+ - **`@samva/vite/render`** adds `renderTemplate` and `renderChannel`, which compile a project and
45
+ render one template and fixture to plain JSON, for `samva templates render`.
46
+ - **`@samva/vite/build`** takes one options object: `buildTemplates({ root, dir, theme, assetBase })`
47
+ and `createTemplateBuildSession(options)`. Results carry `ok`, per-template IR, input schema,
48
+ every fixture rendered through every declared channel, and client-compatibility findings.
49
+ - **`@samva/vite/project`** replaces `loadEmailCatalog`, `renderEmailFixtures`,
50
+ `createEmailProjectSession` and `exportEmailProject` with `loadProject`, `compileProject`,
51
+ `createProjectSession` and `exportProject`. Exports also write `<fixture>.sms.txt` and
52
+ `<fixture>.whatsapp.json` for those channels.
53
+ - **Removed.** `@samva/vite/email`, `@samva/vite/emit`, the Vite config-file and alias caveats of
54
+ the old evaluator, and the `better-result` dependency.
55
+
56
+ ### Breaking: remove textual email SML APIs
57
+
58
+ `@samva/markup` removes textual email SML components and grammar types, plus the
59
+ `@samva/markup/candidate`, `/classes`, `/checks`, `/model`, `/ops`, `/registry`, and `/tsx-tracer`
60
+ subpaths. Email templates use TSX `defineEmail` modules and lower directly to subject, HTML, and text
61
+ through the email semantic tree. The structured SML language and compiler remain for SMS and
62
+ WhatsApp.
63
+
64
+ `@samva/vite` removes `darkClassCount` from the public `AuthoringValidationSummary` returned by its
65
+ emit API. Email validation no longer reports a count derived from textual email SML classes.
66
+
67
+ ### Breaking: templates are `defineTemplate` in a static profile
68
+
69
+ `@samva/markup` adds `defineTemplate({ id, schema, fixtures, locale?, email, sms?, whatsapp? })` at
70
+ its root. A template is one TSX file in a static profile that the new compiler reads without running:
71
+ text and attribute bindings, template strings, `&&`, `?:`, comparisons, `||`, `.length`, `.map`,
72
+ `fmt.*` calls, arithmetic inside formatter arguments and conditions, partials with props and
73
+ children, conditional class names, `.filter(condition)` before `.map` or `.length`, and
74
+ `<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
75
+ `file:line:column` location and a fix.
76
+
77
+ - `@samva/markup/static` compiles a project to a versioned JSON IR (`"sml": 1`) and checks every
78
+ binding against the input schema: `unknown-field` with a did-you-mean, `unguarded-optional`, and
79
+ `fixture-invalid` per fixture. Tailwind, project CSS, brand themes, fonts and assets resolve at
80
+ compile time, and `checkTemplateCompatibility` locates client findings at source.
81
+ - `@samva/markup/render-ir` renders the IR against an input as a pure, dependency-free function.
82
+ It escapes for text, attribute and URL contexts and rejects unsafe bound values.
83
+ - `@samva/markup/fmt` adds the deterministic formatters `money`, `number`, `date`, `time`, `plural`
84
+ and `list`, which take their locale and time zone from the render.
85
+ - `@samva/markup/sms` and `@samva/markup/whatsapp` add channel components with distinct names per
86
+ channel; WhatsApp bindings become Meta's positional `{{n}}` placeholders.
87
+ - `id` is lowercase kebab-case, the rule the CLI and API slugs share. `subject` and `preheader`
88
+ belong to the channel; the `Email` component no longer takes a `preheader` prop.
89
+
90
+ `defineEmail` remains for templates that compute over their input, and it stays outside the static
91
+ profile.
92
+
93
+ ## @samva/vite@0.8.0
94
+
95
+ ### Breaking: `@samva/markup/presets` is `@samva/markup/starters`
96
+
97
+ Every `@samva/markup/presets*` subpath is renamed to `@samva/markup/starters*`, and its exports
98
+ follow: `presetCatalog` is `starterCatalog`, `presetProject` is `starterProject`,
99
+ `presetEmailFixtures` is `starterEmailFixtures`, and every other `preset*` / `Preset*` name reads
100
+ `starter*` / `Starter*`. A public gallery entry's `presetId` is `starterId`, and the corpus
101
+ record types are `OwnedEditableStarter` and `BrandRecreationStarter`. `RECOMMENDED_THEME` and
102
+ `showcaseProjectPaths` are removed.
103
+
104
+ ### Starters on theme variables
105
+
106
+ Every starter styles each color and font through a theme variable, and keeps its look in its own
107
+ `starter.css`. `@samva/markup/starters/theme` names the variables every starter defines and a
108
+ brand sets to restyle one (`STARTER_THEME_VARIABLES`: `--color-brand`, `--color-surface`,
109
+ `--font-body` and the rest; `starterThemeVariableNames` adds each color's `-dark` pair) and writes
110
+ the `theme.css` a project starts with (`starterThemeCss({ brand })`), which imports `samva:brand`
111
+ only when `brand` is true:
112
+
113
+ ```css
114
+ @import "./starter.css";
115
+ @import "samva:brand";
116
+ @theme {
117
+ }
118
+ ```
119
+
120
+ `var()` now resolves in a `Button`'s classic Outlook shape and in a `bgcolor`, so
121
+ `<Button backgroundColor="var(--color-brand)">` paints the theme color everywhere. Every `@theme`
122
+ in project and brand CSS compiles as `static`, so a variable a project stylesheet reads reaches
123
+ the inliner even when no utility class reads it.
124
+
125
+ ### Theme imports and brand themes
126
+
127
+ A project `theme.css` layers with `@import`: another project stylesheet by relative path, then the
128
+ organization's brand, then the project's own `@theme` overrides.
129
+
130
+ ```css
131
+ @import "./starter.css";
132
+ @import "samva:brand";
133
+
134
+ @theme {
135
+ --color-accent: #0f766e;
136
+ }
137
+ ```
138
+
139
+ `samva:brand` names the organization's default brand and `samva:brand/<slug>` names one brand; the
140
+ build supplies it through `compileEmailProject`'s `brand` option. `parseBrandCss` and
141
+ `printBrandCss` (`@samva/markup/theme-css`) read and write the brand theme grammar: `@theme`
142
+ variables in the color, font, text, leading, tracking, radius, spacing, container and shadow
143
+ namespaces, `--color-*` variables in a `@media (prefers-color-scheme: dark)` block, and
144
+ `@font-face` rules with https WOFF2 sources. A dark-mode color compiles to its `--color-*-dark`
145
+ theme variable, read by `dark:` utilities.
146
+
147
+ A project compiles against one brand: importing two fails with `brand-import-conflict`, and a
148
+ `samva:brand` import the build has no brand for, or whose slug names another brand, fails with
149
+ `brand-unavailable`. A brand theme outside the grammar fails with its `brand-css-*` finding.
150
+ `@samva/markup/compiler` exports `parseBrandSpecifier`, `projectBrandSpecifier` and the
151
+ `EmailBrand` types.
152
+
153
+ Breaking: any other `@import` in the theme — including `@import "tailwindcss"` — now refuses the
154
+ compile with `css-import-unresolved` instead of resolving to nothing. An `@import` with layer,
155
+ supports or media conditions, one that imports itself, or a `.css` path outside the project is
156
+ refused the same way. A theme with no utility class in use still compiles its `@font-face` rules.
157
+
158
+ ### `BrandLogo` and `BrandFooter`
159
+
160
+ ```tsx
161
+ import { BrandFooter, BrandLogo } from "samva:brand";
162
+ ```
163
+
164
+ `BrandLogo` renders the brand's logo and swaps to its dark logo where the client honors dark mode.
165
+ It takes `width` (default 120), `alt`, `align` and `className`, and renders nothing when the brand
166
+ has no logo. `BrandFooter` takes `unsubscribeLabel` and `className`.
167
+ `BrandFooter` renders the company name, postal address, social links and an unsubscribe row; the
168
+ send path fills the unsubscribe link per delivery and never click-tracks it, and a template that
169
+ renders `BrandFooter` gets no appended footer. Add `"types": ["@samva/markup/samva-brand"]` to the
170
+ project's tsconfig so `tsc` knows the module.
171
+
172
+ ### Breaking: token-table exports removed
173
+
174
+ `@samva/markup/footer`, `@samva/markup/theme`, and `@samva/markup/tokens` are removed; a brand's
175
+ footer renders through `BrandFooter`. `parseThemeCss` and `printThemeCss` are replaced by
176
+ `parseBrandCss` and `printBrandCss` in `@samva/markup/theme-css`.
177
+
178
+ ### Local builds resolve `samva:brand`
179
+
180
+ `samvaEditor()`, `buildTemplates()` and `exportEmailProject()` compile a project that imports
181
+ `samva:brand` or `samva:brand/<slug>` against its organization's brand, resolved in this order:
182
+
183
+ 1. Live. With `SAMVA_API_KEY` in the environment or the project's `.env` (and `SAMVA_API_URL` for
184
+ another API), the brand is fetched from `/v1/brands`. Without one, the `samva` CLI on `PATH`
185
+ answers `samva brands get --json` with its `samva login` session; `@samva/vite` stores no
186
+ credential.
187
+ 2. Snapshot. `.samva/brands/<slug>.json`, written by `samva brands pull [slug]`, with a
188
+ `brand-offline` warning. The default brand reads the most recent snapshot pulled while it was
189
+ the default.
190
+ 3. Neither. The build fails with `brand-unavailable`, naming both fixes.
191
+
192
+ When the live brand's digest differs from the snapshot's, the build warns with
193
+ `brand-snapshot-stale`; run `samva brands pull` again. Warnings are build diagnostics and editor log
194
+ lines and never fail a build. The editor and `check --watch` reuse a resolution for up to 30
195
+ seconds. Keep `.samva` in `.gitignore`; a project that imports no brand needs no credentials.
196
+
197
+ `@samva/vite/project` exports `resolveProjectBrand`, `loadBrandEnv`, `BRAND_SNAPSHOT_DIR` and the
198
+ `BrandResolution`, `BrandSnapshot`, `BrandWarning` and `ResolvedBrand` types. The editor recompiles
199
+ the theme when any stylesheet it imports changes.
200
+
201
+ ### Breaking: `exportEmailProject` themes the export
202
+
203
+ `exportEmailProject` drops `tailwind` and takes `root` and `theme`. `dir` holds the templates and
204
+ `root` (default `dir`) is the project: its `theme.css` themes the export as in `buildTemplates()`,
205
+ `theme` names another file or `false` disables it, and only `.tsx` files under `dir` are entries.
206
+
207
+ ```ts
208
+ const receipt = await exportEmailProject({ dir: "emails", root: ".", out: "dist/email" });
209
+ ```
210
+
211
+ ### Project fonts in `@font-face`
212
+
213
+ A template project declares a webfont with ordinary CSS: an `@font-face` in `theme.css` (or any
214
+ imported stylesheet) whose `src` names a `.woff2` file in the project, plus a `--font-*` token in
215
+ `@theme`.
216
+
217
+ ```css
218
+ @font-face {
219
+ font-family: "Brand Sans";
220
+ src: url("./fonts/brand-sans.woff2") format("woff2");
221
+ font-weight: 400;
222
+ }
223
+ @theme {
224
+ --font-brand: "Brand Sans", Helvetica, Arial, sans-serif;
225
+ }
226
+ ```
227
+
228
+ The compiler serves the file as a content-addressed asset, so local previews, exports and
229
+ publications all load the same bytes, and it puts the rule in a `<style>` classic Outlook does not
230
+ read, so Outlook uses the rest of the stack instead of Times New Roman.
231
+
232
+ Breaking for projects that already declared fonts: a relative `src` that names no project file, a
233
+ source that is not WOFF2, and an `http:` or `data:` source are compile errors, and a `font-family`
234
+ stack that names a project font without ending in a generic family fails with
235
+ `email-font-fallback`. `Stylesheet` gains `fontFaces`, and `@font-face` no longer appears in
236
+ `headAtRules`.
237
+
238
+ ## @samva/vite@0.7.0
239
+
240
+ ### Breaking: WhatsApp `category="service"` removed
241
+
242
+ `<WhatsApp category>` accepts `marketing`, `utility`, or `authentication`, the categories Meta
243
+ approves templates under.
244
+
245
+ ## @samva/vite@0.6.0
246
+
247
+ ### Improve markup compilation safety
248
+
249
+ Invalid or unsafe markup is now rejected earlier, preventing malformed email output. Complex styles
250
+ and conditions also compile more reliably.
251
+
252
+ ### Carry the plain-text alternative on a public preview artifact
253
+
254
+ `buildPublicTemplatePreviewArtifact` returns `text` alongside `html`, `subject` and `preheader`, so
255
+ a surface that mounts a preset's published preview has the whole executed fixture rather than its
256
+ HTML half. It is the same value `renderEmail` derived when the artifact was built; nothing is
257
+ recomputed.
258
+
259
+ ## @samva/vite@0.5.0
260
+
261
+ ### Breaking: a template project no longer installs Effect
262
+
263
+ - **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.
264
+ - **`@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.
265
+ - **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.
266
+ - **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.
267
+ - **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.
268
+
269
+ ## @samva/vite@0.4.0
270
+
271
+ ### Faster, reliable saves in large projects
272
+
273
+ - **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.
274
+ - **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.
275
+
276
+ ### Breaking: every email template is a complete, editable project
277
+
278
+ - **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.
279
+ - **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.
280
+ - **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.
281
+ - **Stock photography removed:** The onboarding tour and setup checklist templates now use drawn artwork, so no template depends on third-party images.
282
+
283
+ ## @samva/vite@0.3.0
284
+
285
+ - **Reliable project styling:** Switching between projects or preview fixtures preserves each template's styles and avoids unexpected revision changes.
286
+ - **Better Outlook buttons:** `<Button>` sizes to its label without requiring a fixed width. Explicit widths remain supported.
287
+ - **New editable templates:** Product newsletter, order receipt, product launch promotion, and an attributed Vercel invitation, including styles, images, and sample data.
288
+ - **Richer template inputs:** Use lists and objects for repeating content such as newsletter stories and receipt items.
289
+ - **Cleaner plain-text emails:** Empty table cells no longer leave unnecessary separators.
290
+ - **For custom render integrations:** `bindStylesheets` from `@samva/markup/email/render` binds a renderer to one project's styles.
291
+
292
+ ## @samva/vite@0.2.0
293
+
294
+ ### Breaking: author email templates with typed input and executable TSX
295
+
296
+ Email entries now default-export `defineEmail({ id, schema, fixtures, render })` from
297
+ `@samva/markup/template`. Replace function-based email entries and `Variables<T>` placeholders
298
+ with a schema and a `render(input)` function. Set `jsxImportSource` to `@samva/markup/email`,
299
+ import email components from `@samva/markup/email/components`, and upgrade `@samva/markup` and
300
+ `@samva/vite` together to 0.2.0. Regenerate the project's dependency lockfile before publishing.
301
+
302
+ The input contract supports Standard JSON Schema, including Effect and Zod schema adapters.
303
+ `TemplateInput<typeof template>` extracts the inferred input type. Named fixtures supply preview
304
+ and validation inputs; rendering evaluates ordinary TSX, imported components, conditionals and
305
+ mapped arrays against actual input. Time and randomness must come from input or fixed `runtime`
306
+ configuration.
307
+
308
+ The shared local and hosted compiler resolves CSS, Tailwind and imported assets, validates fixture
309
+ inputs, and produces HTML, text and email-client diagnostics. Diagnostics identify known support
310
+ constraints; they do not certify rendering in every email client.
311
+
312
+ The Vite editor maps rendered instances to canonical TSX source. Literal edits preserve the
313
+ source expression; computed or structural changes use reviewed source edits. Fixture switching,
314
+ undo/redo, save/reopen and revision-conflict handling keep source edits tied to the preview.
315
+
316
+ Published email templates pin an executable, input contract, dependency graph and runtime
317
+ configuration. Existing email source must adopt the new contract and be republished; previously
318
+ published HTML snapshots are not executable publications.
319
+
320
+ ## @samva/vite@0.1.2
321
+
322
+ ### Reject oversized SML templates before compilation
323
+
324
+ Templates that exceed supported size, nesting, or node limits now return a validation error before
325
+ compilation.
326
+
327
+ ## @samva/vite@0.1.1
328
+
329
+ ### Release the 0.1.1 package family
330
+
331
+ `@samva/vite` publishes with the rest of the 0.1.1 family.
332
+
333
+ ## @samva/vite@0.1.0
334
+
335
+ ### Publish the Samva template authoring toolchain
336
+
337
+ `@samva/markup` and `@samva/vite` are published on npm. They provide typed TSX authoring, local
338
+ visual editing, and deterministic template builds. `samva templates push` uploads the compiled
339
+ templates.