@samva/markup 0.6.0 → 0.7.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,47 +1,80 @@
1
- # Samva Markup consumer guide
2
-
3
- This package is plain TypeScript. It depends on no application runtime, so a template project
4
- installs the compiler and nothing that comes with one. The structured SML surface — `parseSml`,
5
- `compileSml`, `compileSource` — returns a better-result `Result` you branch on with `Result.isOk`
6
- / `Result.isErr`, carrying a `TaggedError` such as `SmlParseError` or `SmlCompileError` that you
7
- discriminate by `_tag`. Email compilation reports through `EmailDiagnostic` values instead. A
8
- declaration the compiler cannot accept at all throws a `TypeError`: a template id or fixture key
9
- that is not one plain path segment, a nonportable input schema, a project holding a reserved
10
- compiler path.
11
-
12
- Markup's own data models are types and interfaces, not schema declarations, so there is no schema
13
- library to adopt to consume them. Their unions discriminate on `type`: an `EmailNode` is `Text`,
14
- `Element`, `Fragment` or `Raw`, and an `SmlOp` names its edit the same way.
15
- Only the better-result error classes carry `_tag`. A template's JSON input contract is the one
16
- exception to the no-schema rule, because it has to be checked against real input: `inputSchema`
17
- accepts any Standard Schema through the `@standard-schema/spec` types and returns a portable JSON
18
- Schema with its validator, so which schema library an author reaches for stays theirs.
19
-
20
- Email entries default-export `defineEmail` definitions. SMS and WhatsApp use the structured
21
- channel runtime at `@samva/markup` and components at `@samva/markup/components`.
22
- Use documented package subpaths only. Template entry discovery is `.tsx` only.
23
- `presets/sources.gen.ts` is generated: run `bun run codegen:presets` from this package, never hand-edit
24
- it.
25
-
26
- Object email templates use `jsxImportSource: "@samva/markup/email"` and
27
- `defineEmail` from `@samva/markup/template`. Their render function receives actual validated
28
- JSON input and returns subject, optional preheader, body, and optional text. A template id and a
29
- fixture key are both written into file paths, so each must match `TEMPLATE_NAME_PATTERN` — one
30
- plain path segment — and `defineEmail` refuses anything else where the name is declared. A consumer
31
- that resolves a path from those names imports that pattern; it never restates it. The derived
32
- semantic tree lowers directly to HTML/text via `@samva/markup/email/render`; do not route object
33
- templates through recording proxies or textual SML. Authoring-source provenance belongs to semantic
34
- nodes.
35
-
36
- Ordinary HTML is typed. `@samva/markup/email/elements` is the one table behind both the JSX
37
- intrinsic prop types and the serializer's allowlist, so an unsupported element or attribute fails at
38
- typecheck and again at compile with a source location. Email primitives — `Email`, `Section`,
39
- `Columns`/`Column`, `Button`, `Spacer`, `Divider`, `Image`, `Link` — come from
40
- `@samva/markup/email/components`; they generate table markup and classic-Outlook fallbacks, and
41
- every wrapper they generate carries the authoring call site as its origin. Findings are
42
- `EmailDiagnostic` values from `@samva/markup/email/diagnostics`: errors throw `EmailCompileError`,
43
- warnings ride back on the render result. Nothing an author passes as a string becomes markup.
44
-
45
- `@samva/markup/compiler` is the bundler-agnostic compiler boundary. `@samva/vite` and
46
- `apps/agents/src/editor` are adapters over it — they own resolution, watching and transport, never
47
- compilation semantics — and the module imports no Vite, esbuild or Node builtins.
1
+ # @samva/markup agent guide
2
+
3
+ `@samva/markup` is the authoring language and compiler for Samva templates. Email templates are
4
+ TSX modules that default-export a `defineEmail` definition and render through a JSX runtime that
5
+ lowers to email-safe HTML and plain text. SMS and WhatsApp templates use the structured SML runtime.
6
+ The package is plain TypeScript with no application runtime; the local editor, project builds, and
7
+ exports live in `@samva/vite`.
8
+
9
+ ## Entry points
10
+
11
+ - `@samva/markup/template` — `defineEmail`, `TemplateDefinition`, `TemplateInput`, and
12
+ `TEMPLATE_NAME_PATTERN`.
13
+ - `@samva/markup/email` — the JSX runtime. Set `jsxImportSource: "@samva/markup/email"` for email
14
+ entries.
15
+ - `@samva/markup/email/components` — email primitives (`Email`, `Section`, `Columns`, `Button`,
16
+ and the rest of its exports). They generate table layout and classic-Outlook fallbacks.
17
+ - `@samva/markup/email/render` — `renderEmail(template, data)`, which validates input and returns
18
+ subject, preheader, HTML, text, and the derived tree.
19
+ - `@samva/markup/email/diagnostics` — `EmailDiagnostic` and `EmailCompileError`.
20
+ - `@samva/markup/input-schema` — `inputSchema`, which turns any Standard JSON Schema into the
21
+ portable JSON Schema and validator a template's input is checked against.
22
+ - `@samva/markup/compiler` — `compileEmailProject`, the bundler-agnostic compile step. It imports no
23
+ Vite, esbuild, or Node builtins, so it runs in any JavaScript host.
24
+ - `jsxImportSource: "@samva/markup"` and `@samva/markup/components` — the structured SMS and
25
+ WhatsApp runtime and components. `parseSml` (`@samva/markup/parser`) and `compileSml` /
26
+ `compileSource` (`@samva/markup/compile`) work on SML source.
27
+
28
+ The package's `generated/` directory ships the SML and email language references and agent
29
+ contracts; read those before authoring. Import only subpaths declared in `package.json` `exports`.
30
+
31
+ ## How you hurt yourself here
32
+
33
+ - **Passing a string where you want markup.** Nothing an author passes as a string becomes markup;
34
+ strings render as escaped text. There is no raw-HTML escape hatch: compatibility markup comes only
35
+ from the email primitives.
36
+ - **Using an element or attribute the serializer does not allow.** `@samva/markup/email/elements` is
37
+ the one table behind both the JSX intrinsic prop types and the serializer's allowlist, so an
38
+ unsupported element or attribute fails at typecheck and again at compile with a source location.
39
+ - **Naming a template or fixture like a path.** A template id and a fixture key are both written
40
+ into file paths, so each must match `TEMPLATE_NAME_PATTERN`, one plain path segment.
41
+ `defineEmail` throws a `TypeError` where the name is declared. Code that builds a path from these
42
+ names imports the pattern instead of restating it.
43
+ - **Reading the clock or randomness in a template.** Samva's hosted render has no ambient clock and
44
+ no entropy source: reading the current instant or a random value there throws unless the
45
+ definition declares the fixed value in its `runtime` (`TemplateRuntime`). A local render does not
46
+ enforce this, so a template that reads `Date.now()` can pass locally and fail once published. Take
47
+ times from validated input, or declare them.
48
+ - **Treating `renderEmail` as a sandbox.** It executes the template's TypeScript in the calling
49
+ process. Run untrusted template source in an isolated host.
50
+ - **Expecting the schema library to do the validation.** A template's input schema is converted to
51
+ a portable JSON Schema and validated by that document, not by the library that produced it. A
52
+ refinement that the converter cannot export is refused, and defaults are never inserted or values
53
+ coerced. The README lists the supported keywords.
54
+
55
+ ## Rules
56
+
57
+ - **Email entries default-export a `defineEmail` definition** from a `.tsx` file. The definition
58
+ owns a stable, project-unique `id`, an input schema, named fixtures, and a synchronous `render`
59
+ that receives the validated JSON input unchanged. Helper modules are ordinary imports, not
60
+ catalog entries.
61
+ - **Email templates have no textual SML form.** The derived semantic tree lowers directly to HTML
62
+ and text through `@samva/markup/email/render`; SML is the SMS and WhatsApp language.
63
+ - **Structured SML is a closed grammar.** `parseSml` and `compileSml` reject anything outside it
64
+ rather than passing it through.
65
+ - **Failures are typed.** The SML surface returns a better-result `Result`; branch with
66
+ `Result.isOk` / `Result.isErr` and discriminate the `TaggedError` (`SmlParseError`,
67
+ `SmlCompileError`) by `_tag`. Email compilation reports `EmailDiagnostic` values: errors throw
68
+ `EmailCompileError`, warnings ride back on the render result. A declaration the compiler cannot
69
+ accept at all, such as an invalid name or a nonportable input schema, throws a `TypeError`.
70
+ - **Data models are plain types.** Markup's data models are TypeScript types, not schema
71
+ declarations, so there is no schema library to adopt to consume them. Unions discriminate on
72
+ `type` (an `EmailNode`, an `SmlOp`); only error classes carry `_tag`. The input schema is the one
73
+ place a Standard Schema is accepted, and which library produces it is yours to choose.
74
+
75
+ ## Proving your setup
76
+
77
+ - `tsc --noEmit` over the template project catches unsupported elements, attributes, and props.
78
+ - `samva templates check` (from `@samva/cli`) compiles every entry and renders every fixture,
79
+ reporting findings with source locations.
80
+ - `renderEmail(template, fixture)` in a unit test proves one template's output without a build.
package/README.md CHANGED
@@ -101,7 +101,7 @@ a value at import. `new Date(value)` with an explicit argument always works, so
101
101
  takes its time from validated input or states it once as configuration. Locale (`en-US`) and time
102
102
  zone (UTC) are publication-wide and back every `Intl` constructor and `toLocale*` call.
103
103
 
104
- Portable validation uses `@cfworker/json-schema` 4.1.1 without runtime code generation. The
104
+ Portable validation uses `@cfworker/json-schema` without runtime code generation. The
105
105
  2020-12 meta-schema is checked at every supported schema position, and the explicit profile rejects
106
106
  keywords outside its contract before interpretation. The AJV dependency supplies static meta-schema
107
107
  JSON; its code generator is not invoked in a loaded template.
@@ -1,6 +1,6 @@
1
1
  import { t as Diagnostic } from "./diagnostics-OcQKQYSY.js";
2
2
  import { f as ElementNode } from "./ast-BrDtApwS.js";
3
- import { a as SMS_TAG_SPECS } from "./parser-CQcASEyy.js";
3
+ import { a as SMS_TAG_SPECS } from "./parser-t29cHLVL.js";
4
4
  import { t as SmsEncoding } from "./sms-segments-y4FpPilJ.js";
5
5
  //#region src/compile-sms.d.ts
6
6
  /** Documented worst-case rendered width of one `{{variable}}`. */
@@ -1,6 +1,6 @@
1
1
  import { r as SmlCompileError, t as Diagnostic } from "./diagnostics-OcQKQYSY.js";
2
2
  import { f as ElementNode } from "./ast-BrDtApwS.js";
3
- import { l as WHATSAPP_TAG_SPECS } from "./parser-CQcASEyy.js";
3
+ import { l as WHATSAPP_TAG_SPECS } from "./parser-t29cHLVL.js";
4
4
  import { Result } from "better-result";
5
5
  //#region src/compile-whatsapp.d.ts
6
6
  type WhatsAppSpecs = typeof WHATSAPP_TAG_SPECS;
package/dist/compiler.js CHANGED
@@ -7,7 +7,7 @@ import { EmailCompileError, formatEmailDiagnostic, hasBlockingDiagnostic } from
7
7
  import { collectStylesheets, hasStylesheets, registerStylesheet, resetStylesheets } from "./email/stylesheets.js";
8
8
  import { deriveEmailText } from "./email/text.js";
9
9
  import { buildLineMap, lineColumn } from "./diagnostics.js";
10
- import { instrumentTsxSourceForPreview } from "./tsx-source-instrumentation.js";
10
+ import { TsxSourceInstrumentationError, instrumentTsxSourceForPreview } from "./tsx-source-instrumentation.js";
11
11
  import { parse } from "@babel/parser";
12
12
  import { caniemail, parseHtml, rawData } from "caniemail";
13
13
  import { generate, ident, parse as parse$1, walk } from "css-tree";
@@ -264,11 +264,10 @@ const EMAIL_CLIENT_MATRIX = [
264
264
  }
265
265
  ];
266
266
  const LABELS = new Map(EMAIL_CLIENT_MATRIX.map((target) => [target.id, target.label]));
267
- const data = rawData;
268
267
  const COMPATIBILITY_PROVENANCE = {
269
268
  rules: "caniemail@2.0.2",
270
- apiVersion: data.api_version,
271
- lastUpdate: data.last_update_date
269
+ apiVersion: rawData.api_version,
270
+ lastUpdate: rawData.last_update_date
272
271
  };
273
272
  /**
274
273
  * The constructs whose loss actually breaks a message: the recipient reads
@@ -298,7 +297,7 @@ const ESSENTIAL_CONSTRUCTS = /* @__PURE__ */ new Set([
298
297
  "mailto: links",
299
298
  "Local anchors"
300
299
  ]);
301
- const KNOWN_TITLES = new Set(data.data.map((feature) => feature.title));
300
+ const KNOWN_TITLES = new Set(rawData.data.map((feature) => feature.title));
302
301
  /** The innermost recorded node containing an offset, so a finding names its element. */
303
302
  const originsAt = (positions, offset) => {
304
303
  let best;
@@ -1246,6 +1245,28 @@ const stylesheetModule = (id, source, options) => {
1246
1245
  };
1247
1246
  };
1248
1247
  /**
1248
+ * Authored TSX the parser refused is a finding about the source, located where
1249
+ * the parser stopped, so every host reports it the way it reports any other
1250
+ * compile error.
1251
+ */
1252
+ const instrumentAuthoredTsx = (text, id) => {
1253
+ try {
1254
+ return instrumentTsxSourceForPreview(text, id, void 0, null);
1255
+ } catch (error) {
1256
+ if (!(error instanceof TsxSourceInstrumentationError)) throw error;
1257
+ throw new EmailCompileError({ diagnostics: [{
1258
+ code: "tsx-invalid-authored-source",
1259
+ severity: "error",
1260
+ message: error.message,
1261
+ origins: error.location === void 0 ? [] : [{
1262
+ fileName: id,
1263
+ lineNumber: error.location.line,
1264
+ columnNumber: error.location.column
1265
+ }]
1266
+ }] });
1267
+ }
1268
+ };
1269
+ /**
1249
1270
  * Transform one project file into a JavaScript module.
1250
1271
  *
1251
1272
  * `.tsx`/`.ts` keep their authored semantics and gain source origins. A
@@ -1270,7 +1291,7 @@ const transformModule = async (id, source, options) => {
1270
1291
  };
1271
1292
  const discovered = options.tailwind === false ? void 0 : discoverClassCandidates(text, id);
1272
1293
  return {
1273
- code: JSX.test(id) ? instrumentTsxSourceForPreview(text, id, void 0, null) : text,
1294
+ code: JSX.test(id) ? instrumentAuthoredTsx(text, id) : text,
1274
1295
  classes: discovered?.candidates,
1275
1296
  diagnostics: discovered?.diagnostics ?? []
1276
1297
  };
@@ -1,5 +1,5 @@
1
1
  import { f as ElementNode } from "./ast-BrDtApwS.js";
2
- import { i as EMAIL_TAG_SPECS, l as WHATSAPP_TAG_SPECS, o as TagSpec, t as AttributeSpec } from "./parser-CQcASEyy.js";
2
+ import { i as EMAIL_TAG_SPECS, l as WHATSAPP_TAG_SPECS, o as TagSpec, t as AttributeSpec } from "./parser-t29cHLVL.js";
3
3
  import { r as PredicateRef, s as VariableRef } from "./variables-J_L3qxRa.js";
4
4
  import { s as SmlChildren } from "./jsx-runtime-I-1nCtl2.js";
5
5
  //#region src/components.d.ts
@@ -244,7 +244,7 @@ declare const WhatsApp: (props: PropsOf<{
244
244
  readonly category: {
245
245
  readonly type: "string";
246
246
  readonly required: true;
247
- readonly values: readonly ["marketing", "utility", "authentication", "service"];
247
+ readonly values: readonly ["marketing", "utility", "authentication"];
248
248
  };
249
249
  };
250
250
  readonly content: "whatsapp";
package/dist/emit.js CHANGED
@@ -5,8 +5,8 @@ import { renderTemplate } from "./render-template.js";
5
5
  import { Result } from "better-result";
6
6
  //#region src/emit.ts
7
7
  /**
8
- * emitModule — the module-level emit boundary for the TSX authoring layer
9
- * (design brief §7b). The vite plugin does the importing: TSX runs at the
8
+ * emitModule — the module-level emit boundary for the TSX authoring layer.
9
+ * The vite plugin does the importing: TSX runs at the
10
10
  * USER's build time and this receives the already-evaluated module object (a
11
11
  * record of its exports). emitModule stays bundler-agnostic — it never touches
12
12
  * the filesystem, esbuild, or vite. It reads only `moduleExports.default`,
package/dist/footer.d.ts CHANGED
@@ -29,7 +29,7 @@ interface FooterContent {
29
29
  readonly customHtml?: string | undefined;
30
30
  }
31
31
  /** Which compliance-relevant footer fields the effective content is missing,
32
- * surfaced on the design read path for M6's dashboard nudge. */
32
+ * surfaced on the design read path for the dashboard's footer nudge. */
33
33
  interface FooterCompleteness {
34
34
  /** No physical mailing address is set anywhere in the cascade (CAN-SPAM). */
35
35
  readonly missingAddress: boolean;
@@ -42,8 +42,8 @@ interface RenderedFooter {
42
42
  readonly completeness: FooterCompleteness;
43
43
  }
44
44
  /**
45
- * Which compliance-relevant fields the footer content lacks — the signal M6's
46
- * dashboard nudge reads. Derivable from `design.footer` alone (no token table),
45
+ * Which compliance-relevant fields the footer content lacks — the signal the
46
+ * dashboard's footer nudge reads. Derivable from `design.footer` alone (no token table),
47
47
  * so the design read path can report it without composing the block. A
48
48
  * `customHtml` footer is treated as complete (the org owns its own content).
49
49
  */
package/dist/footer.js CHANGED
@@ -23,8 +23,8 @@ const addressLines = (address) => {
23
23
  ].filter((line) => typeof line === "string" && line.trim().length > 0);
24
24
  };
25
25
  /**
26
- * Which compliance-relevant fields the footer content lacks — the signal M6's
27
- * dashboard nudge reads. Derivable from `design.footer` alone (no token table),
26
+ * Which compliance-relevant fields the footer content lacks — the signal the
27
+ * dashboard's footer nudge reads. Derivable from `design.footer` alone (no token table),
28
28
  * so the design read path can report it without composing the block. A
29
29
  * `customHtml` footer is treated as complete (the org owns its own content).
30
30
  */
@@ -312,7 +312,7 @@ declare const WHATSAPP_TAG_SPECS: {
312
312
  readonly category: {
313
313
  readonly type: "string";
314
314
  readonly required: true;
315
- readonly values: readonly ["marketing", "utility", "authentication", "service"];
315
+ readonly values: readonly ["marketing", "utility", "authentication"];
316
316
  };
317
317
  };
318
318
  readonly content: "whatsapp";
@@ -393,4 +393,4 @@ declare const allowedChildren: (model: ContentModel) => ReadonlyArray<SmlTag>;
393
393
  declare const parseSml: (source: string) => Result<SmlDocument, SmlParseError>;
394
394
  //#endregion
395
395
  export { SMS_TAG_SPECS as a, UNIVERSAL_ATTRIBUTE_TAGS as c, parseSml as d, SML_PARSER_LIMITS as f, EMAIL_TAG_SPECS as i, WHATSAPP_TAG_SPECS as l, CHANNEL_TAG_SPECS as n, TagSpec as o, ContentModel as r, UNIVERSAL_ATTRIBUTES as s, AttributeSpec as t, allowedChildren as u };
396
- //# sourceMappingURL=parser-CQcASEyy.d.ts.map
396
+ //# sourceMappingURL=parser-t29cHLVL.d.ts.map
package/dist/parser.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as SMS_TAG_SPECS, c as UNIVERSAL_ATTRIBUTE_TAGS, d as parseSml, f as SML_PARSER_LIMITS, i as EMAIL_TAG_SPECS, l as WHATSAPP_TAG_SPECS, n as CHANNEL_TAG_SPECS, o as TagSpec, r as ContentModel, s as UNIVERSAL_ATTRIBUTES, t as AttributeSpec, u as allowedChildren } from "./parser-CQcASEyy.js";
1
+ import { a as SMS_TAG_SPECS, c as UNIVERSAL_ATTRIBUTE_TAGS, d as parseSml, f as SML_PARSER_LIMITS, i as EMAIL_TAG_SPECS, l as WHATSAPP_TAG_SPECS, n as CHANNEL_TAG_SPECS, o as TagSpec, r as ContentModel, s as UNIVERSAL_ATTRIBUTES, t as AttributeSpec, u as allowedChildren } from "./parser-t29cHLVL.js";
2
2
  export { AttributeSpec, CHANNEL_TAG_SPECS, ContentModel, EMAIL_TAG_SPECS, SML_PARSER_LIMITS, SMS_TAG_SPECS, TagSpec, UNIVERSAL_ATTRIBUTES, UNIVERSAL_ATTRIBUTE_TAGS, WHATSAPP_TAG_SPECS, allowedChildren, parseSml };
package/dist/parser.js CHANGED
@@ -20,8 +20,7 @@ const SMS_CATEGORIES = [
20
20
  const WHATSAPP_CATEGORIES = [
21
21
  "marketing",
22
22
  "utility",
23
- "authentication",
24
- "service"
23
+ "authentication"
25
24
  ];
26
25
  const WHATSAPP_HEADER_TYPES = [
27
26
  "text",
@@ -1,7 +1,16 @@
1
1
  //#region src/tsx-source-instrumentation.d.ts
2
2
  declare const TsxSourceInstrumentationError_base: import("better-result").TaggedErrorClass<"TsxSourceInstrumentationError">;
3
+ /**
4
+ * The authored TSX could not be instrumented. `location` is the 1-based line
5
+ * and column the refusal points at, when the parser or the offending attribute
6
+ * names one.
7
+ */
3
8
  declare class TsxSourceInstrumentationError extends TsxSourceInstrumentationError_base<{
4
9
  readonly message: string;
10
+ readonly location: {
11
+ readonly line: number;
12
+ readonly column: number;
13
+ } | undefined;
5
14
  readonly cause?: unknown;
6
15
  }> {}
7
16
  /**
@@ -4,6 +4,11 @@ import { buildLineMap, lineColumn } from "./diagnostics.js";
4
4
  import { TaggedError } from "better-result";
5
5
  import { parse } from "@babel/parser";
6
6
  //#region src/tsx-source-instrumentation.ts
7
+ /**
8
+ * The authored TSX could not be instrumented. `location` is the 1-based line
9
+ * and column the refusal points at, when the parser or the offending attribute
10
+ * names one.
11
+ */
7
12
  var TsxSourceInstrumentationError = class extends TaggedError("TsxSourceInstrumentationError") {};
8
13
  const isAstNode = (value) => isObject(value) && typeof value.type === "string" && typeof value.start === "number" && typeof value.end === "number";
9
14
  const walkAst = (value, visit) => {
@@ -23,6 +28,15 @@ const jsxIdentifier = (value) => {
23
28
  return typeof value.name === "string" ? value.name : void 0;
24
29
  };
25
30
  const parseErrorMessage = (error) => isObject(error) && typeof error.message === "string" ? error.message : "The authored TSX could not be parsed for preview instrumentation.";
31
+ /** Babel reports a 1-based line and a 0-based column. */
32
+ const parseErrorLocation = (error) => {
33
+ if (!isObject(error) || !isObject(error.loc)) return void 0;
34
+ const { line, column } = error.loc;
35
+ return typeof line === "number" && typeof column === "number" ? {
36
+ line,
37
+ column: column + 1
38
+ } : void 0;
39
+ };
26
40
  const marker = (fileName, source, offset, token) => {
27
41
  const position = lineColumn(buildLineMap(source), offset);
28
42
  return ` ${JSX_SOURCE_LOCATION_PROP}={${JSON.stringify({
@@ -53,7 +67,8 @@ const instrumentTsxSourceForPreview = (source, fileName, token, jsxImportSource
53
67
  } catch (error) {
54
68
  throw new TsxSourceInstrumentationError({
55
69
  cause: error,
56
- message: parseErrorMessage(error)
70
+ message: parseErrorMessage(error),
71
+ location: parseErrorLocation(error)
57
72
  });
58
73
  }
59
74
  const edits = [];
@@ -78,7 +93,10 @@ const instrumentTsxSourceForPreview = (source, fileName, token, jsxImportSource
78
93
  const attributes = openingElement.attributes;
79
94
  if (Array.isArray(attributes)) for (const attribute of attributes) {
80
95
  if (!isAstNode(attribute) || attribute.type !== "JSXAttribute") continue;
81
- if (jsxIdentifier(attribute.name) === "__samva_source_location") throw new TsxSourceInstrumentationError({ message: `The reserved JSX prop ${JSX_SOURCE_LOCATION_PROP} cannot be authored directly.` });
96
+ if (jsxIdentifier(attribute.name) === "__samva_source_location") throw new TsxSourceInstrumentationError({
97
+ message: `The reserved JSX prop ${JSX_SOURCE_LOCATION_PROP} cannot be authored directly.`,
98
+ location: lineColumn(buildLineMap(source), attribute.start)
99
+ });
82
100
  }
83
101
  const closingLength = source.slice(openingElement.end - 2, openingElement.end) === "/>" ? 2 : 1;
84
102
  edits.push({
@@ -138,11 +138,11 @@ const headingText = (node) => {
138
138
  };
139
139
  const parseAuthored = (source) => {
140
140
  try {
141
- const parsed = parse(source, {
141
+ const { program } = parse(source, {
142
142
  plugins: ["typescript", "jsx"],
143
143
  sourceType: "module"
144
144
  });
145
- return Result.ok(parsed.program);
145
+ return isAstNode(program) ? Result.ok(program) : Result.err(diagnostic("tsx-invalid-authored-source", "The authored TSX parsed without a location."));
146
146
  } catch (error) {
147
147
  return Result.err(diagnostic("tsx-invalid-authored-source", error instanceof Error ? error.message : "The authored TSX could not be parsed."));
148
148
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@samva/markup",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Type-safe authoring and compilation primitives for Samva templates",
5
5
  "keywords": [
6
6
  "email",
@@ -279,16 +279,16 @@
279
279
  "devDependencies": {
280
280
  "@babel/parser": "^8.0.4",
281
281
  "@css-inline/css-inline-wasm": "0.15.0",
282
- "@effect/vitest": "4.0.0-rc.112",
282
+ "@effect/vitest": "4.0.0-rc.117",
283
283
  "@types/css-tree": "3.2.0",
284
284
  "caniemail": "2.0.2",
285
285
  "css-tree": "3.2.1",
286
- "effect": "4.0.0-rc.112",
286
+ "effect": "4.0.0-rc.117",
287
287
  "tailwindcss": "^4.3.3",
288
288
  "tsdown": "^0.22.13",
289
289
  "typescript": "^7.0.2",
290
290
  "vitest": "^4.1.11",
291
- "zod": "4.5.2"
291
+ "zod": "4.6.5"
292
292
  },
293
293
  "peerDependencies": {
294
294
  "@babel/parser": "^8.0.4",