@samva/vite 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.
package/AGENTS.md CHANGED
@@ -4,7 +4,7 @@
4
4
  Vite plugin that serves the visual editor, a config-free dev server, and programmatic entry points
5
5
  that build, render, and export a template project. A template is one TSX file in a static profile,
6
6
  and nothing here runs it: every check, render and preview is `compileTemplate` from
7
- `@samva/markup/static` followed by `renderIr`, the same two steps a hosted Samva build takes, so a
7
+ `@samva/markup/compiler` followed by `renderIr`, the same two steps a hosted Samva build takes, so a
8
8
  local render does not drift from a published one. Publishing and hosted synchronization are not
9
9
  here: they are ordinary Git plus `samva templates publish` from `@samva/cli` (see
10
10
  `samva templates --help`).
@@ -13,8 +13,8 @@ here: they are ordinary Git plus `samva templates publish` from `@samva/cli` (se
13
13
 
14
14
  - `@samva/vite` — `samvaEditor()`, the plugin you add to `vite.config.ts` when the defaults do not
15
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.
16
+ - `@samva/vite/dev` — `startDevServer(options)`: a Vite server with no config file,
17
+ `samvaEditor()`, and defaults. It is what `samva templates dev` starts.
18
18
  - `@samva/vite/build` — `buildTemplates(options)` and `createTemplateBuildSession(options)` for
19
19
  programmatic checks and builds. The `samva` CLI loads this module from your project, not from its
20
20
  own bundle.
@@ -61,13 +61,14 @@ what pins the compiler version your previews, checks, and builds use.
61
61
  saved elsewhere is not overwritten; reload the entry and retry.
62
62
  - **Committing or hand-editing `.samva/`.** `.samva/` holds files this package generates. It is
63
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`.
68
64
 
69
65
  ## Rules
70
66
 
67
+ - **Brand resolution belongs to the host.** Keep Samva authentication and storage in the CLI so
68
+ standalone authoring stays independent of a Samva account. Pass the host's resolver and brand
69
+ plugin through compilation, preview and rendering together; dropping the plugin changes footer
70
+ and unsubscribe behavior. Brand-resolution and editor-plugin tests enforce this boundary.
71
+
71
72
  - **Template entries are `.tsx`.** Discovery ignores every other extension, hidden folders and
72
73
  `node_modules`.
73
74
  - **Template ids and fixture keys match `TEMPLATE_NAME_PATTERN`** from `@samva/markup/template`,
@@ -92,5 +93,4 @@ what pins the compiler version your previews, checks, and builds use.
92
93
  - `samva templates dev`, or `vite` with `samvaEditor()` configured, serves the editor at the
93
94
  configured `route`; every template should appear with its fixtures, one document per channel.
94
95
  - `exportProject({ root, out })` writes browsable HTML and text for every fixture and a
95
- `catalog.json` with the asset mapping and findings, without Samva credentials unless the project
96
- imports `samva:brand` and has no cached copy.
96
+ `catalog.json` with the asset mapping and findings.
package/CHANGELOG.md ADDED
@@ -0,0 +1,380 @@
1
+ ## @samva/vite@0.11.0
2
+
3
+ ### The local editor draws Hugeicons and the editor's own control styling
4
+
5
+ The editor `samva templates dev` and the Vite plugin serve now draws its icons from the Hugeicons
6
+ free set (Stroke Rounded, MIT), whose licence files ship in `dist/editor/licenses`. Toggles that are
7
+ on and the source panel take the editor's own recessed styling in light and dark mode, matching the
8
+ Samva dashboard.
9
+
10
+ ### Breaking: `@samva/markup` reads brand and footer names from a brand plugin
11
+
12
+ Templates do not change: `samva:brand`, `BrandLogo`, `BrandFooter` and the unsubscribe row work as
13
+ before, because Samva's brand plugin is the default.
14
+
15
+ A host that compiles or renders templates can pass its own `BrandPlugin` as `brandPlugin` to
16
+ `compileTemplate`, `transformProject`, `projectBrandSpecifier`, `parseBrandSpecifier`,
17
+ `checkStaticProfile`, `renderIr`, `compileEmail`, `renderBrandFooter`, `renderBrandSample`,
18
+ `brandComponents`, `renderedFooterMarkers` and `withoutUnsubscribeRow`. The plugin names the brand
19
+ import, the attributes that mark the footer and its unsubscribe row, the unsubscribe link's URL
20
+ placeholder, and the attribute that marks that link as untracked.
21
+
22
+ `BrandFooter`'s unsubscribe link now carries `data-samva-no-track` instead of SES's
23
+ `ses:no-track`. A host that delivers mail through SES turns the marker into `ses:no-track` before it
24
+ sends.
25
+
26
+ `@samva/markup/brand` exports `samvaBrandPlugin` and the `BrandPlugin` type in place of
27
+ `FOOTER_ATTRIBUTE`, `UNSUBSCRIBE_ROW_ATTRIBUTE` and `UNSUBSCRIBE_URL_PLACEHOLDER`; read
28
+ `samvaBrandPlugin.footerAttribute`, `.unsubscribeRowAttribute` and `.unsubscribeUrlPlaceholder`.
29
+ `@samva/markup/compiler` no longer exports `BRAND_SPECIFIER`; read `samvaBrandPlugin.specifier`.
30
+
31
+ ### Breaking: supply brand resolution to the authoring toolchain
32
+
33
+ `@samva/vite` accepts `brandResolver` and `brandPlugin` on the editor plugin and its dev, project,
34
+ build, render and export APIs. A brand import without a resolver fails with `brand-unavailable`.
35
+ Standalone callers supply a resolver; the package does not read Samva credentials, invoke the
36
+ Samva CLI or load Samva brand snapshots. The `offline` option and Samva snapshot helpers move to
37
+ the CLI.
38
+
39
+ `samva templates dev`, `check`, `build`, `render`, `snapshot` and `export` supply the Samva resolver
40
+ and brand plugin, preserving live lookup, cached snapshots and brand diagnostics.
41
+
42
+ ## @samva/vite@0.10.0
43
+
44
+ ### Breaking: `@samva/markup` publishes 17 entries in two tiers
45
+
46
+ Template files import `@samva/markup`, `@samva/markup/email`, `@samva/markup/fmt`,
47
+ `@samva/markup/sms`, `@samva/markup/whatsapp` and `@samva/markup/input-schema`. Email components
48
+ move from `@samva/markup/email/components` to `@samva/markup/email`, the entry `jsxImportSource`
49
+ already names. A template that still imports the old path fails with `moved-import`, which names
50
+ the replacement.
51
+
52
+ Hosts import five tooling entries:
53
+
54
+ | Entry | Replaces |
55
+ | --------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
56
+ | `@samva/markup/compiler` | `./compiler`, `./static`, `./template` (`TEMPLATE_NAME_PATTERN`), `./email/brand-footer` |
57
+ | `@samva/markup/diagnostics` | `./diagnostics`, `./diagnostic-codes`, `./email/diagnostics` |
58
+ | `@samva/markup/render` | `./ir`, `./render-ir`, `./preview`, `./email/render`, `./email/html`, `./email/text`, `./email/elements`, `./sms-segments` |
59
+ | `@samva/markup/edit` | `./source-edit`, `./channel-edit`, `./source-locations` |
60
+ | `@samva/markup/brand` | `./email/brand`, `./email/assets`, `./theme-css` |
61
+
62
+ `@samva/markup/reference.md` replaces `@samva/markup/generated/*`. `tailwindcss`, `css-tree`,
63
+ `caniemail` and `@babel/parser` are dependencies instead of optional peers, so a project that
64
+ depends on `@samva/markup` alone can run `samva templates check`.
65
+
66
+ ## @samva/vite@0.9.0
67
+
68
+ ### Breaking: the editor, build and render run on the compiled template, not on evaluated code
69
+
70
+ `@samva/vite` compiles every template with the static compiler from `@samva/markup` and renders the
71
+ IR, so nothing in your project is executed to preview, check, build or export it. A local render is
72
+ the render a published template produces, and top-level code in a template module no longer runs.
73
+ Templates are `defineTemplate` files; a `defineEmail` entry is reported as an error.
74
+
75
+ - **Directories.** Templates live in `templates/` by default, and `emails/` is recognized beside it.
76
+ Set `templatesDir` on `samvaEditor()` (or `dir` on the entry points) to scan one directory
77
+ instead; `projectRoot` names the folder that holds `theme.css`, shared imports and `.samva/` when
78
+ it is not the Vite root. Document ids are project-relative paths, and a template that declares
79
+ SMS or WhatsApp gets one editor document per channel over the same file.
80
+ - **`.samva/`.** The brand record `samva:brand` resolves is fetched live and cached in
81
+ `.samva/brands/<slug>.json`, refreshed by every live fetch, and used with a warning when the API
82
+ is unreachable. Keep `.samva` in `.gitignore`.
83
+ - **`@samva/vite/dev`** adds `startDevServer({ root, dir?, port?, host?, open? })`, a Vite dev server
84
+ with no config file that serves the editor; `samva templates dev` runs it.
85
+ - **`@samva/vite/render`** adds `renderTemplate` and `renderChannel`, which compile a project and
86
+ render one template and fixture to plain JSON, for `samva templates render`.
87
+ - **`@samva/vite/build`** takes one options object: `buildTemplates({ root, dir, theme, assetBase })`
88
+ and `createTemplateBuildSession(options)`. Results carry `ok`, per-template IR, input schema,
89
+ every fixture rendered through every declared channel, and client-compatibility findings.
90
+ - **`@samva/vite/project`** replaces `loadEmailCatalog`, `renderEmailFixtures`,
91
+ `createEmailProjectSession` and `exportEmailProject` with `loadProject`, `compileProject`,
92
+ `createProjectSession` and `exportProject`. Exports also write `<fixture>.sms.txt` and
93
+ `<fixture>.whatsapp.json` for those channels.
94
+ - **Removed.** `@samva/vite/email`, `@samva/vite/emit`, the Vite config-file and alias caveats of
95
+ the old evaluator, and the `better-result` dependency.
96
+
97
+ ### Breaking: remove textual email SML APIs
98
+
99
+ `@samva/markup` removes textual email SML components and grammar types, plus the
100
+ `@samva/markup/candidate`, `/classes`, `/checks`, `/model`, `/ops`, `/registry`, and `/tsx-tracer`
101
+ subpaths. Email templates use TSX `defineEmail` modules and lower directly to subject, HTML, and text
102
+ through the email semantic tree. The structured SML language and compiler remain for SMS and
103
+ WhatsApp.
104
+
105
+ `@samva/vite` removes `darkClassCount` from the public `AuthoringValidationSummary` returned by its
106
+ emit API. Email validation no longer reports a count derived from textual email SML classes.
107
+
108
+ ### Breaking: templates are `defineTemplate` in a static profile
109
+
110
+ `@samva/markup` adds `defineTemplate({ id, schema, fixtures, locale?, email, sms?, whatsapp? })` at
111
+ its root. A template is one TSX file in a static profile that the new compiler reads without running:
112
+ text and attribute bindings, template strings, `&&`, `?:`, comparisons, `||`, `.length`, `.map`,
113
+ `fmt.*` calls, arithmetic inside formatter arguments and conditions, partials with props and
114
+ children, conditional class names, `.filter(condition)` before `.map` or `.length`, and
115
+ `<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
116
+ `file:line:column` location and a fix.
117
+
118
+ - `@samva/markup/static` compiles a project to a versioned JSON IR (`"sml": 1`) and checks every
119
+ binding against the input schema: `unknown-field` with a did-you-mean, `unguarded-optional`, and
120
+ `fixture-invalid` per fixture. Tailwind, project CSS, brand themes, fonts and assets resolve at
121
+ compile time, and `checkTemplateCompatibility` locates client findings at source.
122
+ - `@samva/markup/render-ir` renders the IR against an input as a pure, dependency-free function.
123
+ It escapes for text, attribute and URL contexts and rejects unsafe bound values.
124
+ - `@samva/markup/fmt` adds the deterministic formatters `money`, `number`, `date`, `time`, `plural`
125
+ and `list`, which take their locale and time zone from the render.
126
+ - `@samva/markup/sms` and `@samva/markup/whatsapp` add channel components with distinct names per
127
+ channel; WhatsApp bindings become Meta's positional `{{n}}` placeholders.
128
+ - `id` is lowercase kebab-case, the rule the CLI and API slugs share. `subject` and `preheader`
129
+ belong to the channel; the `Email` component no longer takes a `preheader` prop.
130
+
131
+ `defineEmail` remains for templates that compute over their input, and it stays outside the static
132
+ profile.
133
+
134
+ ## @samva/vite@0.8.0
135
+
136
+ ### Breaking: `@samva/markup/presets` is `@samva/markup/starters`
137
+
138
+ Every `@samva/markup/presets*` subpath is renamed to `@samva/markup/starters*`, and its exports
139
+ follow: `presetCatalog` is `starterCatalog`, `presetProject` is `starterProject`,
140
+ `presetEmailFixtures` is `starterEmailFixtures`, and every other `preset*` / `Preset*` name reads
141
+ `starter*` / `Starter*`. A public gallery entry's `presetId` is `starterId`, and the corpus
142
+ record types are `OwnedEditableStarter` and `BrandRecreationStarter`. `RECOMMENDED_THEME` and
143
+ `showcaseProjectPaths` are removed.
144
+
145
+ ### Starters on theme variables
146
+
147
+ Every starter styles each color and font through a theme variable, and keeps its look in its own
148
+ `starter.css`. `@samva/markup/starters/theme` names the variables every starter defines and a
149
+ brand sets to restyle one (`STARTER_THEME_VARIABLES`: `--color-brand`, `--color-surface`,
150
+ `--font-body` and the rest; `starterThemeVariableNames` adds each color's `-dark` pair) and writes
151
+ the `theme.css` a project starts with (`starterThemeCss({ brand })`), which imports `samva:brand`
152
+ only when `brand` is true:
153
+
154
+ ```css
155
+ @import "./starter.css";
156
+ @import "samva:brand";
157
+ @theme {
158
+ }
159
+ ```
160
+
161
+ `var()` now resolves in a `Button`'s classic Outlook shape and in a `bgcolor`, so
162
+ `<Button backgroundColor="var(--color-brand)">` paints the theme color everywhere. Every `@theme`
163
+ in project and brand CSS compiles as `static`, so a variable a project stylesheet reads reaches
164
+ the inliner even when no utility class reads it.
165
+
166
+ ### Theme imports and brand themes
167
+
168
+ A project `theme.css` layers with `@import`: another project stylesheet by relative path, then the
169
+ organization's brand, then the project's own `@theme` overrides.
170
+
171
+ ```css
172
+ @import "./starter.css";
173
+ @import "samva:brand";
174
+
175
+ @theme {
176
+ --color-accent: #0f766e;
177
+ }
178
+ ```
179
+
180
+ `samva:brand` names the organization's default brand and `samva:brand/<slug>` names one brand; the
181
+ build supplies it through `compileEmailProject`'s `brand` option. `parseBrandCss` and
182
+ `printBrandCss` (`@samva/markup/theme-css`) read and write the brand theme grammar: `@theme`
183
+ variables in the color, font, text, leading, tracking, radius, spacing, container and shadow
184
+ namespaces, `--color-*` variables in a `@media (prefers-color-scheme: dark)` block, and
185
+ `@font-face` rules with https WOFF2 sources. A dark-mode color compiles to its `--color-*-dark`
186
+ theme variable, read by `dark:` utilities.
187
+
188
+ A project compiles against one brand: importing two fails with `brand-import-conflict`, and a
189
+ `samva:brand` import the build has no brand for, or whose slug names another brand, fails with
190
+ `brand-unavailable`. A brand theme outside the grammar fails with its `brand-css-*` finding.
191
+ `@samva/markup/compiler` exports `parseBrandSpecifier`, `projectBrandSpecifier` and the
192
+ `EmailBrand` types.
193
+
194
+ Breaking: any other `@import` in the theme — including `@import "tailwindcss"` — now refuses the
195
+ compile with `css-import-unresolved` instead of resolving to nothing. An `@import` with layer,
196
+ supports or media conditions, one that imports itself, or a `.css` path outside the project is
197
+ refused the same way. A theme with no utility class in use still compiles its `@font-face` rules.
198
+
199
+ ### `BrandLogo` and `BrandFooter`
200
+
201
+ ```tsx
202
+ import { BrandFooter, BrandLogo } from "samva:brand";
203
+ ```
204
+
205
+ `BrandLogo` renders the brand's logo and swaps to its dark logo where the client honors dark mode.
206
+ It takes `width` (default 120), `alt`, `align` and `className`, and renders nothing when the brand
207
+ has no logo. `BrandFooter` takes `unsubscribeLabel` and `className`.
208
+ `BrandFooter` renders the company name, postal address, social links and an unsubscribe row; the
209
+ send path fills the unsubscribe link per delivery and never click-tracks it, and a template that
210
+ renders `BrandFooter` gets no appended footer. Add `"types": ["@samva/markup/samva-brand"]` to the
211
+ project's tsconfig so `tsc` knows the module.
212
+
213
+ ### Breaking: token-table exports removed
214
+
215
+ `@samva/markup/footer`, `@samva/markup/theme`, and `@samva/markup/tokens` are removed; a brand's
216
+ footer renders through `BrandFooter`. `parseThemeCss` and `printThemeCss` are replaced by
217
+ `parseBrandCss` and `printBrandCss` in `@samva/markup/theme-css`.
218
+
219
+ ### Local builds resolve `samva:brand`
220
+
221
+ `samvaEditor()`, `buildTemplates()` and `exportEmailProject()` compile a project that imports
222
+ `samva:brand` or `samva:brand/<slug>` against its organization's brand, resolved in this order:
223
+
224
+ 1. Live. With `SAMVA_API_KEY` in the environment or the project's `.env` (and `SAMVA_API_URL` for
225
+ another API), the brand is fetched from `/v1/brands`. Without one, the `samva` CLI on `PATH`
226
+ answers `samva brands get --json` with its `samva login` session; `@samva/vite` stores no
227
+ credential.
228
+ 2. Snapshot. `.samva/brands/<slug>.json`, written by `samva brands pull [slug]`, with a
229
+ `brand-offline` warning. The default brand reads the most recent snapshot pulled while it was
230
+ the default.
231
+ 3. Neither. The build fails with `brand-unavailable`, naming both fixes.
232
+
233
+ When the live brand's digest differs from the snapshot's, the build warns with
234
+ `brand-snapshot-stale`; run `samva brands pull` again. Warnings are build diagnostics and editor log
235
+ lines and never fail a build. The editor and `check --watch` reuse a resolution for up to 30
236
+ seconds. Keep `.samva` in `.gitignore`; a project that imports no brand needs no credentials.
237
+
238
+ `@samva/vite/project` exports `resolveProjectBrand`, `loadBrandEnv`, `BRAND_SNAPSHOT_DIR` and the
239
+ `BrandResolution`, `BrandSnapshot`, `BrandWarning` and `ResolvedBrand` types. The editor recompiles
240
+ the theme when any stylesheet it imports changes.
241
+
242
+ ### Breaking: `exportEmailProject` themes the export
243
+
244
+ `exportEmailProject` drops `tailwind` and takes `root` and `theme`. `dir` holds the templates and
245
+ `root` (default `dir`) is the project: its `theme.css` themes the export as in `buildTemplates()`,
246
+ `theme` names another file or `false` disables it, and only `.tsx` files under `dir` are entries.
247
+
248
+ ```ts
249
+ const receipt = await exportEmailProject({ dir: "emails", root: ".", out: "dist/email" });
250
+ ```
251
+
252
+ ### Project fonts in `@font-face`
253
+
254
+ A template project declares a webfont with ordinary CSS: an `@font-face` in `theme.css` (or any
255
+ imported stylesheet) whose `src` names a `.woff2` file in the project, plus a `--font-*` token in
256
+ `@theme`.
257
+
258
+ ```css
259
+ @font-face {
260
+ font-family: "Brand Sans";
261
+ src: url("./fonts/brand-sans.woff2") format("woff2");
262
+ font-weight: 400;
263
+ }
264
+ @theme {
265
+ --font-brand: "Brand Sans", Helvetica, Arial, sans-serif;
266
+ }
267
+ ```
268
+
269
+ The compiler serves the file as a content-addressed asset, so local previews, exports and
270
+ publications all load the same bytes, and it puts the rule in a `<style>` classic Outlook does not
271
+ read, so Outlook uses the rest of the stack instead of Times New Roman.
272
+
273
+ Breaking for projects that already declared fonts: a relative `src` that names no project file, a
274
+ source that is not WOFF2, and an `http:` or `data:` source are compile errors, and a `font-family`
275
+ stack that names a project font without ending in a generic family fails with
276
+ `email-font-fallback`. `Stylesheet` gains `fontFaces`, and `@font-face` no longer appears in
277
+ `headAtRules`.
278
+
279
+ ## @samva/vite@0.7.0
280
+
281
+ ### Breaking: WhatsApp `category="service"` removed
282
+
283
+ `<WhatsApp category>` accepts `marketing`, `utility`, or `authentication`, the categories Meta
284
+ approves templates under.
285
+
286
+ ## @samva/vite@0.6.0
287
+
288
+ ### Improve markup compilation safety
289
+
290
+ Invalid or unsafe markup is now rejected earlier, preventing malformed email output. Complex styles
291
+ and conditions also compile more reliably.
292
+
293
+ ### Carry the plain-text alternative on a public preview artifact
294
+
295
+ `buildPublicTemplatePreviewArtifact` returns `text` alongside `html`, `subject` and `preheader`, so
296
+ a surface that mounts a preset's published preview has the whole executed fixture rather than its
297
+ HTML half. It is the same value `renderEmail` derived when the artifact was built; nothing is
298
+ recomputed.
299
+
300
+ ## @samva/vite@0.5.0
301
+
302
+ ### Breaking: a template project no longer installs Effect
303
+
304
+ - **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.
305
+ - **`@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.
306
+ - **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.
307
+ - **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.
308
+ - **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.
309
+
310
+ ## @samva/vite@0.4.0
311
+
312
+ ### Faster, reliable saves in large projects
313
+
314
+ - **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.
315
+ - **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.
316
+
317
+ ### Breaking: every email template is a complete, editable project
318
+
319
+ - **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.
320
+ - **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.
321
+ - **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.
322
+ - **Stock photography removed:** The onboarding tour and setup checklist templates now use drawn artwork, so no template depends on third-party images.
323
+
324
+ ## @samva/vite@0.3.0
325
+
326
+ - **Reliable project styling:** Switching between projects or preview fixtures preserves each template's styles and avoids unexpected revision changes.
327
+ - **Better Outlook buttons:** `<Button>` sizes to its label without requiring a fixed width. Explicit widths remain supported.
328
+ - **New editable templates:** Product newsletter, order receipt, product launch promotion, and an attributed Vercel invitation, including styles, images, and sample data.
329
+ - **Richer template inputs:** Use lists and objects for repeating content such as newsletter stories and receipt items.
330
+ - **Cleaner plain-text emails:** Empty table cells no longer leave unnecessary separators.
331
+ - **For custom render integrations:** `bindStylesheets` from `@samva/markup/email/render` binds a renderer to one project's styles.
332
+
333
+ ## @samva/vite@0.2.0
334
+
335
+ ### Breaking: author email templates with typed input and executable TSX
336
+
337
+ Email entries now default-export `defineEmail({ id, schema, fixtures, render })` from
338
+ `@samva/markup/template`. Replace function-based email entries and `Variables<T>` placeholders
339
+ with a schema and a `render(input)` function. Set `jsxImportSource` to `@samva/markup/email`,
340
+ import email components from `@samva/markup/email/components`, and upgrade `@samva/markup` and
341
+ `@samva/vite` together to 0.2.0. Regenerate the project's dependency lockfile before publishing.
342
+
343
+ The input contract supports Standard JSON Schema, including Effect and Zod schema adapters.
344
+ `TemplateInput<typeof template>` extracts the inferred input type. Named fixtures supply preview
345
+ and validation inputs; rendering evaluates ordinary TSX, imported components, conditionals and
346
+ mapped arrays against actual input. Time and randomness must come from input or fixed `runtime`
347
+ configuration.
348
+
349
+ The shared local and hosted compiler resolves CSS, Tailwind and imported assets, validates fixture
350
+ inputs, and produces HTML, text and email-client diagnostics. Diagnostics identify known support
351
+ constraints; they do not certify rendering in every email client.
352
+
353
+ The Vite editor maps rendered instances to canonical TSX source. Literal edits preserve the
354
+ source expression; computed or structural changes use reviewed source edits. Fixture switching,
355
+ undo/redo, save/reopen and revision-conflict handling keep source edits tied to the preview.
356
+
357
+ Published email templates pin an executable, input contract, dependency graph and runtime
358
+ configuration. Existing email source must adopt the new contract and be republished; previously
359
+ published HTML snapshots are not executable publications.
360
+
361
+ ## @samva/vite@0.1.2
362
+
363
+ ### Reject oversized SML templates before compilation
364
+
365
+ Templates that exceed supported size, nesting, or node limits now return a validation error before
366
+ compilation.
367
+
368
+ ## @samva/vite@0.1.1
369
+
370
+ ### Release the 0.1.1 package family
371
+
372
+ `@samva/vite` publishes with the rest of the 0.1.1 family.
373
+
374
+ ## @samva/vite@0.1.0
375
+
376
+ ### Publish the Samva template authoring toolchain
377
+
378
+ `@samva/markup` and `@samva/vite` are published on npm. They provide typed TSX authoring, local
379
+ visual editing, and deterministic template builds. `samva templates push` uploads the compiled
380
+ templates.
package/README.md CHANGED
@@ -51,12 +51,16 @@ middleware stack.
51
51
 
52
52
  ## Options
53
53
 
54
- | Option | Type | Default | Description |
55
- | -------------- | ----------------- | -------------------------- | ----------------------------------------------------------------------------------------------------------------- |
56
- | `templatesDir` | `string` | `templates/` and `emails/` | The one directory to scan for `.tsx` templates, relative to the project root. Unset scans whichever exist. |
57
- | `projectRoot` | `string` | the Vite root | The folder holding `theme.css`, shared imports and `.samva/`, relative to the Vite root. Contains `templatesDir`. |
58
- | `route` | `string` | `"/"` | Absolute editor UI route; API and assets are mounted beneath it. |
59
- | `theme` | `string \| false` | `"theme.css"` | Project theme file relative to the project root, or `false` to compile without one. |
54
+ | Option | Type | Default | Description |
55
+ | ------------------- | ------------------- | -------------------------- | ----------------------------------------------------------------------------------------------------------------- |
56
+ | `templatesDir` | `string` | `templates/` and `emails/` | The one directory to scan for `.tsx` templates, relative to the project root. Unset scans whichever exist. |
57
+ | `projectRoot` | `string` | the Vite root | The folder holding `theme.css`, shared imports and `.samva/`, relative to the Vite root. Contains `templatesDir`. |
58
+ | `route` | `string` | `"/"` | Absolute editor UI route; API and assets are mounted beneath it. |
59
+ | `theme` | `string \| false` | `"theme.css"` | Project theme file relative to the project root, or `false` to compile without one. |
60
+ | `brandResolver` | `BrandResolver` | unset | Host-owned brand lookup; required when a project imports a brand. |
61
+ | `brandPlugin` | `BrandPlugin` | markup default | Brand import specifier, footer and unsubscribe behavior. |
62
+ | `authenticated` | `boolean` | `false` | Whether the host supplies authenticated editor capabilities. |
63
+ | `editorAffordances` | `EditorAffordances` | unset | Host-owned publishing instructions and authentication text. |
60
64
 
61
65
  For templates in another folder of a monorepo, set `projectRoot` to a folder that contains both the
62
66
  templates and the files they import; a templates directory outside the project root is an error.
@@ -70,7 +74,7 @@ and saves them to the file.
70
74
 
71
75
  ```tsx
72
76
  import { defineTemplate } from "@samva/markup";
73
- import { Email, Section } from "@samva/markup/email/components";
77
+ import { Email, Section } from "@samva/markup/email";
74
78
  import { jsonSchema } from "@samva/markup/input-schema";
75
79
 
76
80
  export default defineTemplate({
@@ -108,8 +112,8 @@ compatibility findings and inbox tests for client-specific proof.
108
112
  ## Theme, check, and publish
109
113
 
110
114
  A root `theme.css` uses Tailwind v4 `@theme` syntax and may `@import` other project `.css` files
111
- and `samva:brand`; any other import is a compile error. A change to the theme, a partial, or an
112
- imported stylesheet recompiles every template that reads it.
115
+ and a brand import recognized by the configured brand plugin; any other import is a compile error.
116
+ A change to the theme, a partial, or an imported stylesheet recompiles every template that reads it.
113
117
 
114
118
  `samva templates check` and `buildTemplates()` compile every template, render every declared
115
119
  fixture through each channel the template declares, and check the email output against the client
@@ -124,8 +128,8 @@ const result = await buildTemplates({ root: ".", assetBase: "https://assets.exam
124
128
  ```
125
129
 
126
130
  `buildTemplates(options)` and `createTemplateBuildSession(options)` take `root`, `dir`, `theme`,
127
- `assetBase` and `offline`. Imported email assets require an HTTPS `assetBase`, which names where
128
- their bytes are served in delivered messages. `createTemplateBuildSession` also reports file
131
+ `assetBase`, `brandResolver` and `brandPlugin`. Imported email assets require an HTTPS `assetBase`,
132
+ which names where their bytes are served in delivered messages. `createTemplateBuildSession` also reports file
129
133
  changes for `check --watch`.
130
134
 
131
135
  `samva templates build --out dist/templates.json` writes the CLI's local artifact. Check writes no
@@ -161,26 +165,36 @@ await server.close();
161
165
  `startDevServer` creates a Vite server with `configFile: false` and `samvaEditor()`, which is what
162
166
  `samva templates dev` does.
163
167
 
164
- ## The organization brand
168
+ ## Brand resolution
165
169
 
166
- A project that imports `samva:brand` (the default brand) or `samva:brand/<slug>` compiles against
167
- its organization's brand. The platform brand record is the source of truth; each publication pins
168
- the brand version it was built with. The editor, `buildTemplates()`, and `exportProject()` resolve
169
- it in this order:
170
+ `samvaEditor()`, `startDevServer()`, and the project, build, render and export APIs accept
171
+ `brandResolver` and `brandPlugin`. The resolver supplies an `EmailBrand`; the plugin defines the
172
+ brand import, footer and unsubscribe behavior. A project with no brand import runs without a
173
+ resolver or credentials. A brand import without a resolver reports `brand-unavailable`.
170
174
 
171
- 1. **Live.** With `SAMVA_API_KEY` in the environment or the project's `.env` (and `SAMVA_API_URL`
172
- for another API), the brand is fetched directly. Without one, the `samva` CLI on `PATH` is asked
173
- (`samva brands get --json`) and answers with your `samva login` session, which stays in the
174
- operating-system keyring the CLI owns. A live answer refreshes the cached copy.
175
- 2. **Cached copy.** `.samva/brands/<slug>.json`, written by every live fetch and by
176
- `samva brands pull [slug]`, with a `brand-offline` warning.
177
- 3. **Neither.** The build fails with `brand-unavailable`, naming both fixes.
175
+ For example, supply a brand from your own configuration:
178
176
 
179
- When the live brand's digest differs from the cached copy's, the build warns with
180
- `brand-snapshot-stale` and refreshes the copy. Warnings are build diagnostics and editor log lines;
181
- they never fail a build. The editor and `check --watch` reuse a resolution briefly, so a brand
182
- edited elsewhere arrives within seconds rather than on the next save. A project that imports no
183
- brand needs no credentials for local work.
177
+ ```ts
178
+ import { samvaEditor } from "@samva/vite";
179
+ import { samvaBrandPlugin } from "@samva/markup/brand";
180
+
181
+ samvaEditor({
182
+ brandPlugin: samvaBrandPlugin,
183
+ brandResolver: {
184
+ resolve: async () => ({
185
+ ok: true,
186
+ brand: { slug: "local", css: "@theme { --color-primary: #2563eb; }" },
187
+ source: "configuration",
188
+ warnings: [],
189
+ }),
190
+ },
191
+ });
192
+ ```
193
+
194
+ `samva templates dev/check/build/render/export` supplies the Samva resolver and brand plugin. These
195
+ commands resolve `samva:brand` from the organization's live brand or a cached
196
+ `.samva/brands/<slug>.json` snapshot. Configure Samva credentials and snapshot behavior through the
197
+ CLI; the standalone Vite plugin does not read them.
184
198
 
185
199
  ## The `.samva/` folder
186
200
 
@@ -216,8 +230,8 @@ const receipt = await exportProject({ root: ".", out: "dist/email" });
216
230
  ```
217
231
 
218
232
  `root` is the project: its `theme.css` themes the export, as in `buildTemplates()`; `dir` narrows
219
- the templates directory, `theme` names another file or `false` disables it, and `samva:brand`
220
- resolves as described above.
233
+ the templates directory, `theme` names another file or `false` disables it, and `brandResolver` / `brandPlugin`
234
+ configure brand resolution as described above.
221
235
 
222
236
  Export writes `templates/<id>/<fixture>.html` and `.txt`, plus `.sms.txt` and `.whatsapp.json` for
223
237
  those channels, content-addressed files under `assets/`, and `catalog.json` with the asset mapping
@@ -0,0 +1,52 @@
1
+ import { BrandPlugin, EmailBrand } from "@samva/markup/brand";
2
+ import { EmailDiagnostic } from "@samva/markup/diagnostics";
3
+ //#region src/layout.d.ts
4
+ /** Directories scanned for templates when no `dir` is configured, in scan order. */
5
+ declare const DEFAULT_TEMPLATE_DIRS: readonly ["templates", "emails"];
6
+ /** The folder for files this package generates, relative to the project root. Gitignore it. */
7
+ declare const SAMVA_DIR = ".samva";
8
+ /** A project's files by root-relative POSIX path: text as strings, imported assets as bytes. */
9
+ type ProjectFiles = Readonly<Record<string, string | Uint8Array>>;
10
+ /**
11
+ * The absolute directories a project's templates live in. A configured `dir`
12
+ * is the only one scanned; otherwise every default directory that exists. A
13
+ * directory outside the root cannot be compiled, because a project's files are
14
+ * addressed relative to its root, so it is refused rather than half-read.
15
+ */
16
+ declare const templateDirectories: (root: string, dir?: string | undefined) => Promise<readonly string[]>;
17
+ /** Every `.tsx` file beneath the given directories, as root-relative paths, sorted. */
18
+ declare const discoverEntries: (root: string, dirs: readonly string[]) => Promise<readonly string[]>;
19
+ /**
20
+ * Read the project into the map the compiler consumes: scripts, stylesheets
21
+ * and JSON as text, imported assets as bytes. Hidden folders (`.samva`, `.git`)
22
+ * and `node_modules` are not part of a project.
23
+ */
24
+ declare const readProjectFiles: (root: string) => Promise<ProjectFiles>;
25
+ //#endregion
26
+ //#region src/brand.d.ts
27
+ /** A host-resolved brand; a host may attach its content digest. */
28
+ interface ResolvedBrand extends EmailBrand {
29
+ readonly digest?: string | undefined;
30
+ }
31
+ /** A finding emitted by the host that resolves the brand. */
32
+ interface BrandWarning {
33
+ readonly code: string;
34
+ readonly message: string;
35
+ }
36
+ type BrandResolution = {
37
+ readonly ok: true;
38
+ /** `undefined` when the project imports no brand. */
39
+ readonly brand: ResolvedBrand | undefined;
40
+ readonly source: string;
41
+ readonly warnings: readonly BrandWarning[];
42
+ } | {
43
+ readonly ok: false;
44
+ readonly message: string;
45
+ };
46
+ /** The host owns brand storage, authentication and resolution caching. */
47
+ interface BrandResolver {
48
+ readonly resolve: (files: ProjectFiles, themePath: string | undefined) => Promise<BrandResolution>;
49
+ }
50
+ //#endregion
51
+ export { DEFAULT_TEMPLATE_DIRS as a, discoverEntries as c, ResolvedBrand as i, readProjectFiles as l, BrandResolver as n, ProjectFiles as o, BrandWarning as r, SAMVA_DIR as s, BrandResolution as t, templateDirectories as u };
52
+ //# sourceMappingURL=brand-BkXQMGgY.d.ts.map
package/dist/build.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { _ as TemplateChannel, a as ProjectOptions } from "./project-CiUuzKsC.js";
2
- import { EmailDiagnostic } from "@samva/markup/email/diagnostics";
3
- import { RenderedIrEmail, RenderedIrWhatsApp, TemplateIr } from "@samva/markup/ir";
1
+ import { ProjectOptions, TemplateChannel } from "./project.js";
2
+ import { RenderedIrEmail, RenderedIrWhatsApp, TemplateIr } from "@samva/markup/render";
3
+ import { EmailDiagnostic } from "@samva/markup/diagnostics";
4
4
  //#region src/build.d.ts
5
5
  /** The compiler a build ran, as a publication records it. */
6
6
  declare const COMPILER: string;
package/dist/build.js CHANGED
@@ -1,4 +1,4 @@
1
- import { d as emailCompatibility, f as finding, h as renderEmailFixtures, i as loadProject, m as renderChannelFixture, n as createProjectSession, p as hasErrors, s as isDeliverableBase } from "./project-Cp8qb3s0.js";
1
+ import { C as renderEmailFixtures, S as renderChannelFixture, b as finding, i as loadProject, n as createProjectSession, s as isDeliverableBase, x as hasErrors, y as emailCompatibility } from "./project-DwbOoPDk.js";
2
2
  import markupPackage from "@samva/markup/package.json" with { type: "json" };
3
3
  //#region src/build.ts
4
4
  /** The compiler a build ran, as a publication records it. */
package/dist/dev.d.ts CHANGED
@@ -1,5 +1,13 @@
1
+ import { n as BrandResolver } from "./brand-BkXQMGgY.js";
2
+ import { t as EditorAffordances } from "./editor-DfdlKEeu.js";
3
+ import { BrandPlugin } from "@samva/markup/brand";
1
4
  //#region src/dev.d.ts
2
5
  interface DevServerOptions {
6
+ readonly brandResolver?: BrandResolver | undefined;
7
+ readonly brandPlugin?: BrandPlugin | undefined;
8
+ /** Whether the host provides authenticated editor capabilities. */
9
+ readonly authenticated?: boolean | undefined;
10
+ readonly editorAffordances?: EditorAffordances | undefined;
3
11
  /** The project root. Default: the working directory. */
4
12
  readonly root?: string | undefined;
5
13
  /** The templates directory, relative to `root`. Default: `templates/` and `emails/`, whichever exist. */