@spintax/core 0.1.3 → 0.1.4

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/CHANGELOG.md CHANGED
@@ -3,6 +3,29 @@
3
3
  All notable changes to `@spintax/core` are documented here. This project adheres to
4
4
  [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5
5
 
6
+ ## 0.1.4 — 2026-07-13
7
+
8
+ A post-process bug fix (hit in production) plus docs. No API change.
9
+
10
+ ### Fixed
11
+
12
+ - **`postProcess` no longer mangles `mailto:` / `tel:` URIs.** They carry no `//` authority,
13
+ so the URL shield missed them; the email shield then carved the address out from under the
14
+ prefix, and the "space after a colon" rule split the leftover into a malformed
15
+ `mailto: contact@example.com` href. They are now shielded as whole tokens (with the same
16
+ trailing-punctuation handling as URLs) before the email/domain passes. Mirrored into the PHP
17
+ engine and covered by 4 golden-corpus fixtures, so the post-process parity contract holds
18
+ in both engines. Reported in [#41](https://github.com/investblog/spintax-js/issues/41).
19
+
20
+ ### Changed
21
+
22
+ - Docs: a **Use Cases** section (cold email, notifications, chatbots, A/B copy, programmatic
23
+ SEO, spinning LLM-drafted templates locally) and the N-variants recipe — call `render` N
24
+ times with different seeds — with the caveat that distinct seeds are *independent draws, not
25
+ distinct results*, so a low-cardinality template will repeat. Batching stays a host concern.
26
+ - npm keywords broadened (`text-spinner`, `email-template`, `placeholders`, `variables`,
27
+ `conditionals`) to match how people actually search for this.
28
+
6
29
  ## 0.1.3 — 2026-07-08
7
30
 
8
31
  Precise diagnostic positions. Backward-compatible — `validate()` verdicts (pass/fail),
package/README.md CHANGED
@@ -43,6 +43,33 @@ validate('{a|b'); // → [{ severity: 'error', code: 'bracket.unclosed'
43
43
  extract('%title% {?promo?Sale}'); // → { refs: ['title', 'promo'], sets: [], includes: [] }
44
44
  ```
45
45
 
46
+ ## Use cases
47
+
48
+ Anywhere one message has to go out many times without reading like a form letter:
49
+
50
+ - **Cold email & outreach** — one template, a distinct body per recipient, personalized through `context`.
51
+ - **SMS / push / notifications** — deterministic output in a few kilobytes, no API call per send.
52
+ - **Chatbots & agents** — vary canned replies so a bot doesn't repeat itself verbatim.
53
+ - **A/B and multivariate testing** — enumerate copy variants in the template instead of in application code.
54
+ - **Programmatic SEO / content generation** — thousands of pages from one authored source.
55
+ - **LLM output at scale** — have a model draft the template once, then spin it locally, forever, for free.
56
+
57
+ The core renders a **single** string per call — batching is a host concern. To emit N variants, call
58
+ `render` N times with different seeds; a seeded call is reproducible, so any variant can be
59
+ regenerated later from its seed alone:
60
+
61
+ ```ts
62
+ const template = '{Hi|Hello|Hey} %name%, {quick|short|small} question about {pricing|billing}';
63
+ const variants = [1, 2, 3].map((seed) => render(template, { context: { name: 'Ada' }, seed }));
64
+ // → [ 'Hello Ada, quick question about billing',
65
+ // 'Hey Ada, quick question about pricing',
66
+ // 'Hey Ada, quick question about pricing' ] ← same as seed 2; see the caveat below
67
+ ```
68
+
69
+ Distinct seeds are **independent draws, not distinct results** — like any sampling they can repeat,
70
+ and the fewer combinations a template has, the more often they will. If you need N *unique* variants,
71
+ dedupe in the host and cap the retries: a template may simply not have N combinations to give.
72
+
46
73
  ## Spintax syntax
47
74
 
48
75
  | Construct | Example | Meaning |
package/dist/index.cjs CHANGED
@@ -906,6 +906,7 @@ var WS = " \\t\\r\\n\\f\\x0B";
906
906
  var S = `[${WS}]`;
907
907
  var DOMAIN_PART = "(?:(?:(?:xn--)?[\\p{L}\\p{N}]+(?:-[\\p{L}\\p{N}]+)*)\\.)+(?:xn--[a-z0-9\\-]{2,59}|[\\p{L}][\\p{L}\\p{N}-]{1,62})";
908
908
  var URL_RE = new RegExp(`(?:https?|ftp):\\/\\/[^${WS}<>"')\\]]+`, "giu");
909
+ var MAILTEL_RE = new RegExp(`(?:mailto|tel):[^${WS}<>"')\\]]+`, "giu");
909
910
  var EMAIL_RE = new RegExp(`[a-z0-9._%+\\-]+@${DOMAIN_PART}\\b`, "giu");
910
911
  var DOMAIN_RE = new RegExp(`\\b${DOMAIN_PART}\\b`, "giu");
911
912
  var DECIMAL_RE = /\b\d+\.\d+\b/gu;
@@ -940,6 +941,7 @@ function postProcess(input) {
940
941
  };
941
942
  let text = input;
942
943
  text = text.replace(URL_RE, (m) => storeWithTrailingPunct(m, "URL"));
944
+ text = text.replace(MAILTEL_RE, (m) => storeWithTrailingPunct(m, "URI"));
943
945
  text = text.replace(EMAIL_RE, (m) => store(m, "EMAIL"));
944
946
  text = text.replace(DOMAIN_RE, (m) => store(m, "DOM"));
945
947
  text = text.replace(DECIMAL_RE, (m) => store(m, "NUM"));