@samva/vite 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md 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/static` 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/README.md CHANGED
@@ -1,31 +1,39 @@
1
1
  # @samva/vite
2
2
 
3
- The unified local editor for Samva email, SMS, and WhatsApp templates. One Vite
4
- plugin discovers `.tsx` files, evaluates them through the Samva JSX runtime,
5
- applies the project theme, and serves the full visual editor with HMR.
3
+ The local editor, dev server and build for Samva templates. A template is one TSX file that
4
+ `@samva/markup` compiles without running it; this package serves the visual editor over the
5
+ compiled templates, and checks, builds, renders and exports them, so what you preview is what a
6
+ publication renders.
6
7
 
7
- ## Install
8
+ ## Start
8
9
 
9
- Keep the authoring library and editor in the project, with Vite provided as the editor's peer:
10
+ `samva templates dev` bundles the dev server with sensible defaults, so most projects configure
11
+ nothing and need no `vite.config.ts`:
10
12
 
11
13
  ```sh
12
- npm install --save-dev @samva/markup @samva/vite vite
14
+ samva templates dev
13
15
  ```
14
16
 
15
- `samva templates init` creates a project from the canonical starter, which contains these
16
- dependencies and an equivalent `vite.config.ts`. Existing project scripts remain untouched.
17
+ `samva templates init` creates a project from the canonical starter. Templates live in `templates/`,
18
+ and `emails/` is also recognized. `@samva/vite` stays a public, configurable plugin for setups
19
+ those defaults cannot cover: templates in another folder, a monorepo, or a Vite server that also
20
+ runs your other plugins.
21
+
22
+ ```sh
23
+ npm install --save-dev @samva/markup @samva/vite vite
24
+ ```
17
25
 
18
26
  ```ts
19
27
  import { samvaEditor } from "@samva/vite";
20
28
  import { defineConfig } from "vite";
21
29
 
22
30
  export default defineConfig({
23
- plugins: [samvaEditor({ templatesDir: "emails" })],
31
+ plugins: [samvaEditor()],
24
32
  });
25
33
  ```
26
34
 
27
- Run `vite` and open `http://localhost:5173/`. The default assumes this dev server
28
- exists only for Samva authoring:
35
+ Run `vite` and open `http://localhost:5173/`. The default assumes this dev server exists only for
36
+ Samva authoring:
29
37
 
30
38
  - editor UI: `/`
31
39
  - editor API and event streams: `/api/*`
@@ -34,113 +42,153 @@ exists only for Samva authoring:
34
42
  To share a Vite server with another app, choose a route:
35
43
 
36
44
  ```ts
37
- samvaEditor({ templatesDir: "emails", route: "/__samva" });
45
+ samvaEditor({ route: "/__samva" });
38
46
  ```
39
47
 
40
- That mounts the UI at `/__samva/`, the API at `/__samva/api/*`, and the bundled
41
- assets at `/__samva/assets/*`. Requests outside the configured route pass through
42
- to the rest of the Vite middleware stack.
48
+ That mounts the UI at `/__samva/`, the API at `/__samva/api/*`, and the bundled assets at
49
+ `/__samva/assets/*`. Requests outside the configured route pass through to the rest of the Vite
50
+ middleware stack.
51
+
52
+ ## Options
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. |
60
+
61
+ For templates in another folder of a monorepo, set `projectRoot` to a folder that contains both the
62
+ templates and the files they import; a templates directory outside the project root is an error.
43
63
 
44
64
  ## Authoring files
45
65
 
46
- Email `.tsx` entries default-export `defineEmail({ id, schema, fixtures, render })` from
47
- `@samva/markup/template`. Use the `@samva/markup/email` JSX runtime and primitives from
48
- `@samva/markup/email/components`. Ordinary synchronous helpers, conditions and loops execute
49
- against actual validated JSON. Shared helper modules are excluded from the template catalog;
50
- ids are stable independently of paths and must be unique within the project.
66
+ A template default-exports `defineTemplate` from `@samva/markup` and is written in the static
67
+ profile: ordinary JSX, HTML and Tailwind that the compiler reads and never executes. The editor
68
+ previews every declared fixture, offers source-aware edits where the source intent is unambiguous,
69
+ and saves them to the file.
51
70
 
52
71
  ```tsx
53
- /** @jsxImportSource @samva/markup/email */
54
- import { Schema } from "effect";
72
+ import { defineTemplate } from "@samva/markup";
55
73
  import { Email, Section } from "@samva/markup/email/components";
56
- import { defineEmail } from "@samva/markup/template";
74
+ import { jsonSchema } from "@samva/markup/input-schema";
57
75
 
58
- export default defineEmail({
76
+ export default defineTemplate({
59
77
  id: "welcome",
60
- schema: Schema.toStandardJSONSchemaV1(
61
- Schema.Struct({ firstName: Schema.String, items: Schema.Array(Schema.String) }),
62
- ),
63
- fixtures: { basic: { firstName: "Ada", items: ["Your first project"] } },
64
- render: (input) => ({
65
- subject: `Welcome, ${input.firstName}`,
66
- preheader: "Your account is ready",
67
- body: (
78
+ schema: jsonSchema<{ firstName: string }>({
79
+ type: "object",
80
+ required: ["firstName"],
81
+ properties: { firstName: { type: "string" } },
82
+ additionalProperties: false,
83
+ }),
84
+ fixtures: { basic: { firstName: "Ada" } },
85
+ email: {
86
+ subject: (input) => `Welcome, ${input.firstName}`,
87
+ preheader: () => "Your account is ready",
88
+ body: (input) => (
68
89
  <Email>
69
90
  <Section>
70
91
  <h1>Welcome, {input.firstName}</h1>
71
- {input.items.length > 0 && (
72
- <ul>
73
- {input.items.map((item) => (
74
- <li key={item}>{item}</li>
75
- ))}
76
- </ul>
77
- )}
78
92
  </Section>
79
93
  </Email>
80
94
  ),
81
- }),
95
+ },
82
96
  });
83
97
  ```
84
98
 
85
- The email editor previews declared fixtures and offers source-aware edits where the source intent
86
- is unambiguous. Source is canonical. Failed email entries remain visible with diagnostics and a
87
- null render. SMS and WhatsApp remain read-only structured previews; an invalid edit retains their
88
- last successful emission and labels it stale. Deleting an entry removes its preview.
89
-
90
- The browser canvas is a structural preview of Samva's modern and Outlook-safe
91
- compiler output. It does not emulate the exact Gmail, Outlook, Apple Mail, or
92
- Yahoo rendering engines. Use compatibility findings and inbox tests for
93
- client-specific proof.
94
-
95
- ## Options
99
+ Any other `.tsx` module in the templates directory (a partial, a component) is an ordinary module
100
+ that templates import; a module is a template only if its default export is `defineTemplate`.
101
+ Identity is the `id` the definition declares, unique within the project. A template that declares
102
+ `sms` or `whatsapp` beside `email` shows one editor document per channel over the same file.
96
103
 
97
- | Option | Type | Default | Description |
98
- | -------------- | ----------------- | ------------- | ---------------------------------------------------------------- |
99
- | `templatesDir` | `string` | `"emails"` | Recursive `.tsx` template directory. |
100
- | `route` | `string` | `"/"` | Absolute editor UI route; API and assets are mounted beneath it. |
101
- | `theme` | `string \| false` | `"theme.css"` | Project theme file, or `false` to disable discovery. |
104
+ The browser canvas is a structural preview of Samva's modern and Outlook-safe compiler output. It
105
+ does not emulate the exact Gmail, Outlook, Apple Mail, or Yahoo rendering engines. Use
106
+ compatibility findings and inbox tests for client-specific proof.
102
107
 
103
108
  ## Theme, check, and publish
104
109
 
105
110
  A root `theme.css` uses Tailwind v4 `@theme` syntax and may `@import` other project `.css` files
106
- and `samva:brand`; any other import is a compile error. Changes to the theme or a stylesheet it
107
- imports recompile
108
- local previews while preserving the last valid theme when an edit is temporarily
109
- invalid.
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.
113
+
114
+ `samva templates check` and `buildTemplates()` compile every template, render every declared
115
+ fixture through each channel the template declares, and check the email output against the client
116
+ matrix. `buildTemplates()` returns `files`, one per template entry, with `ok`, the template's IR,
117
+ input schema, each fixture's rendered output, and structured diagnostics; helper modules have no
118
+ entry. A template requires at least one fixture.
119
+
120
+ ```ts
121
+ import { buildTemplates } from "@samva/vite/build";
122
+
123
+ const result = await buildTemplates({ root: ".", assetBase: "https://assets.example.com/p" });
124
+ ```
125
+
126
+ `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
129
+ changes for `check --watch`.
130
+
131
+ `samva templates build --out dist/templates.json` writes the CLI's local artifact. Check writes no
132
+ files. A successful fixture check does not prove every possible input branch.
133
+
134
+ ## Render one fixture
135
+
136
+ `samva templates render` runs this package's `render` entry from your project, so it uses the
137
+ compiler version your lockfile pins. The results are plain JSON.
138
+
139
+ ```ts
140
+ import { renderChannel, renderTemplate } from "@samva/vite/render";
141
+
142
+ const email = await renderTemplate({ root: ".", template: "welcome", fixture: "basic" });
143
+ // { ok: true, subject, preheader?, html, text, diagnostics } or { ok: false, diagnostics }
144
+
145
+ const sms = await renderChannel({ root: ".", template: "order", fixture: "first", channel: "sms" });
146
+ ```
147
+
148
+ `template` is a template id or the project-relative path of its entry. `locale` and `timeZone`
149
+ override the formatters' defaults.
110
150
 
111
- `samva templates check` and `buildTemplates()` execute every email fixture and validate its
112
- actual input. `buildTemplates()` returns `files`, each with a `preview.kind` discriminator:
113
- `email` contains input schema, compiled module graph, actual fixture subject/HTML/text and
114
- structured diagnostics; `structured` contains the SMS/WhatsApp preview. Helper modules have no
115
- file entry. An email requires at least one fixture.
151
+ ## Dev server without a config file
116
152
 
117
- `samva templates build --dir emails --out dist/templates.json` writes the CLI's version 2 local
118
- artifact. Check writes no files. `check --watch` observes changes; `--jsonl` emits one observation
119
- per rebuild and `--json` is one-shot. A successful fixture check does not prove every possible
120
- input branch.
153
+ ```ts
154
+ import { startDevServer } from "@samva/vite/dev";
155
+
156
+ const server = await startDevServer({ root: ".", port: 5173, open: true });
157
+ console.log(server.url);
158
+ await server.close();
159
+ ```
121
160
 
122
- Imported email assets require `assetBase` in `buildTemplates()` or `--asset-base` in CLI checks
123
- and builds, naming their actual HTTPS serving base.
161
+ `startDevServer` creates a Vite server with `configFile: false` and `samvaEditor()`, which is what
162
+ `samva templates dev` does.
124
163
 
125
164
  ## The organization brand
126
165
 
127
166
  A project that imports `samva:brand` (the default brand) or `samva:brand/<slug>` compiles against
128
- its organization's brand. The editor, `buildTemplates()`, and `exportEmailProject()` resolve it in
129
- this order:
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:
130
170
 
131
171
  1. **Live.** With `SAMVA_API_KEY` in the environment or the project's `.env` (and `SAMVA_API_URL`
132
172
  for another API), the brand is fetched directly. Without one, the `samva` CLI on `PATH` is asked
133
173
  (`samva brands get --json`) and answers with your `samva login` session, which stays in the
134
- operating-system keyring the CLI owns.
135
- 2. **Snapshot.** `.samva/brands/<slug>.json`, written by `samva brands pull [slug]`, with a
136
- `brand-offline` warning.
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.
137
177
  3. **Neither.** The build fails with `brand-unavailable`, naming both fixes.
138
178
 
139
- When the live brand's digest differs from the snapshot's, the build warns with
140
- `brand-snapshot-stale`; run `samva brands pull` again. Warnings are build diagnostics and editor
141
- log lines; they never fail a build. The editor and `check --watch` reuse a resolution briefly,
142
- so a brand edited elsewhere arrives within seconds rather than on the next save. Keep `.samva` in
143
- `.gitignore`. A project that imports no brand needs no credentials for local work.
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.
184
+
185
+ ## The `.samva/` folder
186
+
187
+ `.samva/` holds files this package generates in your project, the way `.next/` and `.tanstack/`
188
+ do. Keep it in `.gitignore`. It is not part of the project's source, so changes inside it never
189
+ recompile templates.
190
+
191
+ ## Publish
144
192
 
145
193
  For a hosted project, commit and push the checked TSX tree with ordinary Git, then publish one exact
146
194
  commit:
@@ -156,25 +204,23 @@ The CLI's repository-local credential helper obtains a short-lived Artifact cred
156
204
  Samva's credential protocol when Git needs one. It validates the repository host/path and does not
157
205
  store the token in Git config or the remote URL. `templates publish` requires a clean worktree and
158
206
  an exact commit reachable from the fetched `origin` default branch; it creates an immutable
159
- publication receipt and does not push or rewrite history.
160
-
161
- Hosted builds consume the committed Bun text lockfile version 2 or npm lockfile version 3.
162
- Keep dependencies and the supported lockfile in the project. Sending selects an exact immutable
207
+ publication receipt and does not push or rewrite history. Sending selects an exact immutable
163
208
  publication by default; later source edits do not change that publication.
164
209
 
165
210
  ## Local export
166
211
 
167
212
  ```ts
168
- import { exportEmailProject } from "@samva/vite/project";
213
+ import { exportProject } from "@samva/vite/project";
169
214
 
170
- const receipt = await exportEmailProject({ dir: "emails", root: ".", out: "dist/email" });
215
+ const receipt = await exportProject({ root: ".", out: "dist/email" });
171
216
  ```
172
217
 
173
- `dir` holds the templates and `root` (default `dir`) is the project: its `theme.css` themes the
174
- export, as in `buildTemplates()`, `theme` names another file or `false` disables it, and
175
- `samva:brand` resolves as described above.
218
+ `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.
176
221
 
177
- Export writes `templates/<id>/<fixture>.html` and `.txt`, content-addressed files under `assets/`,
178
- and `catalog.json` with the asset mapping and findings. The default `../../assets` base resolves
179
- from each exported page. For delivery, supply the actual HTTPS `assetBase` and serve those bytes
180
- there; relative URLs are for local export. External HTTPS images stay external references.
222
+ Export writes `templates/<id>/<fixture>.html` and `.txt`, plus `.sms.txt` and `.whatsapp.json` for
223
+ those channels, content-addressed files under `assets/`, and `catalog.json` with the asset mapping
224
+ and findings. The default `../../assets` base resolves from each exported page. For delivery,
225
+ supply the actual HTTPS `assetBase` and serve those bytes there; relative URLs are for local
226
+ export. External HTTPS images stay external references.
package/dist/build.d.ts CHANGED
@@ -1,61 +1,66 @@
1
- import { ModulePreview } from "./emit.js";
2
- import { CompiledEmailProject } from "@samva/markup/compiler";
1
+ import { _ as TemplateChannel, a as ProjectOptions } from "./project-CiUuzKsC.js";
3
2
  import { EmailDiagnostic } from "@samva/markup/email/diagnostics";
3
+ import { RenderedIrEmail, RenderedIrWhatsApp, TemplateIr } from "@samva/markup/ir";
4
4
  //#region src/build.d.ts
5
- /** One declared input rendered through executable email source. */
6
- interface EmailFixtureBuild {
5
+ /** The compiler a build ran, as a publication records it. */
6
+ declare const COMPILER: string;
7
+ /** One declared input rendered through every channel the template declares. */
8
+ interface FixtureBuild {
7
9
  readonly name: string;
8
10
  readonly input: unknown;
9
11
  readonly ok: boolean;
10
- readonly subject?: string | undefined;
11
- readonly preheader?: string | undefined;
12
- readonly html?: string | undefined;
13
- readonly text?: string | undefined;
12
+ readonly email?: Pick<RenderedIrEmail, "subject" | "preheader" | "html" | "text"> | undefined;
13
+ readonly sms?: string | undefined;
14
+ readonly whatsapp?: RenderedIrWhatsApp | undefined;
14
15
  readonly diagnostics: readonly EmailDiagnostic[];
15
16
  }
16
- /** Executable email artifacts retain their input contract, module graph and actual fixture output. */
17
- interface EmailBuildPreview {
18
- readonly kind: "email";
17
+ /** A template's IR, its input contract, and what each declared fixture rendered. */
18
+ interface TemplateBuild {
19
+ /** The id the definition declares. */
20
+ readonly template: string;
21
+ readonly channels: readonly TemplateChannel[];
19
22
  readonly ok: boolean;
20
- readonly compilerVersion: string;
23
+ /** Project-relative source of the entry. */
24
+ readonly source: string;
25
+ readonly inputSchema: Readonly<Record<string, unknown>>;
26
+ readonly ir: TemplateIr;
27
+ readonly fixtures: readonly FixtureBuild[];
28
+ /** Compile findings, brand findings and asset-base findings. */
21
29
  readonly diagnostics: readonly EmailDiagnostic[];
22
- readonly templates: readonly {
23
- readonly name: string;
24
- readonly channel: "email";
25
- readonly ok: boolean;
26
- readonly source: string;
27
- readonly inputSchema: Readonly<Record<string, unknown>>;
28
- readonly compiled: CompiledEmailProject;
29
- readonly fixtures: readonly EmailFixtureBuild[];
30
- }[];
30
+ /** Target-client findings across the email fixtures. */
31
+ readonly compatibility: readonly EmailDiagnostic[];
31
32
  }
32
- type TemplateBuildPreview = EmailBuildPreview | (ModulePreview & {
33
- readonly kind: "structured";
34
- });
35
- interface BuildTemplatesResult {
36
- /** Authored entry paths, sorted; helper modules have no entry here. */
37
- readonly files: ReadonlyArray<{
38
- readonly id: string;
39
- readonly kind: "tsx";
40
- readonly preview: TemplateBuildPreview;
41
- }>;
33
+ /** One template entry of the project. `template` is absent when the entry did not compile. */
34
+ interface BuildFile {
35
+ /** Project-relative path of the entry. */
36
+ readonly id: string;
37
+ readonly kind: "tsx";
38
+ readonly ok: boolean;
39
+ readonly template?: TemplateBuild | undefined;
40
+ readonly diagnostics: readonly EmailDiagnostic[];
42
41
  }
43
- interface BuildTemplatesOptions {
44
- /** Project root containing shared imports and theme.css; defaults to the parent of dir. */
45
- readonly root?: string | undefined;
46
- readonly theme?: string | false | undefined;
47
- /** Required for imported email assets. Must be their actual HTTPS serving base. */
48
- readonly assetBase?: string | undefined;
42
+ interface BuildTemplatesResult {
43
+ readonly compilerVersion: string;
44
+ /** Template entries, sorted by path; helper modules have no entry here. */
45
+ readonly files: readonly BuildFile[];
46
+ /** Findings about the project as a whole: the brand, a duplicate id. */
47
+ readonly diagnostics: readonly EmailDiagnostic[];
48
+ readonly ok: boolean;
49
49
  }
50
+ interface BuildTemplatesOptions extends ProjectOptions {}
50
51
  interface TemplateBuildSession {
51
52
  readonly build: () => Promise<BuildTemplatesResult>;
52
53
  readonly subscribe: (listener: (path: string) => void) => () => void;
53
54
  readonly close: () => Promise<void>;
54
55
  }
55
- /** Execute each email definition's fixtures; compile structured SMS/WhatsApp through their channel compiler. */
56
- declare const buildTemplates: (dir: string, options?: BuildTemplatesOptions) => Promise<BuildTemplatesResult>;
57
- /** A watcher and module evaluator over the project's installed authoring toolchain. */
58
- declare const createTemplateBuildSession: (dir: string, options?: BuildTemplatesOptions) => Promise<TemplateBuildSession>;
56
+ /**
57
+ * Compile every template in a project and render every fixture it declares
58
+ * through each of its channels, then check the email fixtures against the
59
+ * client matrix. Nothing is evaluated.
60
+ */
61
+ declare const buildTemplates: (options?: BuildTemplatesOptions) => Promise<BuildTemplatesResult>;
62
+ /** A watcher and compiler over a project, for `check --watch`. */
63
+ declare const createTemplateBuildSession: (options?: BuildTemplatesOptions) => TemplateBuildSession;
59
64
  //#endregion
60
- export { BuildTemplatesOptions, BuildTemplatesResult, EmailBuildPreview, EmailFixtureBuild, TemplateBuildPreview, TemplateBuildSession, buildTemplates, createTemplateBuildSession };
65
+ export { BuildFile, BuildTemplatesOptions, BuildTemplatesResult, COMPILER, FixtureBuild, TemplateBuild, TemplateBuildSession, buildTemplates, createTemplateBuildSession };
61
66
  //# sourceMappingURL=build.d.ts.map