@ultimat3/mail 5.0.1 → 7.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/mail",
3
- "version": "5.0.1",
3
+ "version": "7.0.0",
4
4
  "description": "Transactional email as data: one template renders HTML and text, sent through a job.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -31,10 +31,10 @@
31
31
  "test": "bun test"
32
32
  },
33
33
  "dependencies": {
34
- "@ultimat3/core": "5.0.1",
35
- "@ultimat3/i18n": "5.0.1",
36
- "@ultimat3/jobs": "5.0.1",
37
- "@ultimat3/schema": "5.0.1",
38
- "@ultimat3/time": "5.0.1"
34
+ "@ultimat3/core": "7.0.0",
35
+ "@ultimat3/i18n": "7.0.0",
36
+ "@ultimat3/jobs": "7.0.0",
37
+ "@ultimat3/schema": "7.0.0",
38
+ "@ultimat3/time": "7.0.0"
39
39
  }
40
40
  }
package/src/catalog.ts CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  type Locale,
10
10
  loadCatalog,
11
11
  type NestedCatalog,
12
- registerCatalog,
12
+ registerBaseCatalog,
13
13
  } from '@ultimat3/i18n';
14
14
 
15
15
  export const MAIL_CATALOG_SOURCE: NestedCatalog = {
@@ -85,24 +85,18 @@ export const MAIL_CATALOG: Catalog = loadCatalog(MAIL_CATALOG_SOURCE);
85
85
  /** The one locale these strings are written in. */
86
86
  export const MAIL_CATALOG_LOCALE: Locale = DEFAULT_LOCALE;
87
87
 
88
- /**
89
- * Register the mail templates' own strings under the ONE locale they are written in.
90
- *
91
- * **No locale parameter, and that is the fix**: this catalog is English, so `registerMailCatalog('es')`
92
- * seated English subjects and headings under `es` where `isMiss` then read FALSE — a fallback locale
93
- * chain wearing registration as a disguise, the same defect `registerFrameworkCatalog` carried until
94
- * it lost its own parameter. A locale argument is now a compile error rather than a silent one.
95
- *
96
- * **Call it once, at boot.** This is NOT idempotent, and the comment here claimed it was:
97
- * `registerCatalog` merges the existing entry first and its argument second, so a second call after
98
- * an app has overridden `mail.*` keys puts the English strings back on top of exactly the keys that
99
- * app cared enough to translate. Guarding on "has this locale a catalog" the shape
100
- * `registerFrameworkCatalog` usescannot work here, because the framework catalog has already
101
- * claimed this locale by the time mail registers; a guard by CONTENT needs an i18n primitive that
102
- * does not exist, and remembering the call in a module flag goes stale the moment
103
- * `resetCatalogs()` runs (`packages/testing/src/registry-snapshot.ts`), which would silently stop
104
- * registering altogether. Stating the limit beats inventing a guard that fails quietly.
105
- */
106
- export function registerMailCatalog(): void {
107
- registerCatalog(MAIL_CATALOG_LOCALE, MAIL_CATALOG);
108
- }
88
+ // At module scope, and that is the whole point: importing `@ultimat3/mail` is what installs these
89
+ // strings, so no app and no boot can forget them. `registerMailCatalog()` was the alternative and
90
+ // it had **zero** production callers — only this package's own tests — so every `mail.*` string in
91
+ // every running Ultimate app rendered as a loud miss, which is issue #249 one package over.
92
+ //
93
+ // Under an app's catalog, never over it: `registerBaseCatalog` merges the base FIRST and whatever
94
+ // `registerCatalog` seated second, so an app that translated `mail.footer.unsubscribe` keeps it
95
+ // through every later install. That is the "guard by CONTENT" this file said needed an i18n
96
+ // primitive that did not exist it exists now, and it is idempotent by construction rather than
97
+ // by a module flag `resetCatalogs()` would go stale against.
98
+ //
99
+ // Under the ONE locale these strings are written in. Seating English under `es` made `isMiss` read
100
+ // FALSE for a subject nobody had translated a fallback locale chain wearing registration as a
101
+ // disguise. A non-`en` app translates the `mail.*` keys it renders into its own catalog.
102
+ registerBaseCatalog(MAIL_CATALOG_LOCALE, MAIL_CATALOG);
package/src/index.ts CHANGED
@@ -7,12 +7,7 @@ export { t } from '@ultimat3/schema';
7
7
  export type { CalloutTone, MailBlock, MailTemplate, TemplateArgs } from './blocks';
8
8
  export { blocks } from './blocks';
9
9
 
10
- export {
11
- MAIL_CATALOG,
12
- MAIL_CATALOG_LOCALE,
13
- MAIL_CATALOG_SOURCE,
14
- registerMailCatalog,
15
- } from './catalog';
10
+ export { MAIL_CATALOG, MAIL_CATALOG_LOCALE, MAIL_CATALOG_SOURCE } from './catalog';
16
11
  export type {
17
12
  MailDriver,
18
13
  MailMessage,
package/src/layout.ts CHANGED
@@ -21,21 +21,20 @@ export type MailToken =
21
21
  | 'calloutDangerBg'
22
22
  | 'calloutDangerText';
23
23
 
24
- export const MAIL_TOKENS: Readonly<Record<MailToken, Readonly<Record<ColorScheme, string>>>> =
25
- Object.freeze({
26
- pageBg: { light: '#f4f5f7', dark: '#0b0d10' },
27
- surfaceBg: { light: '#ffffff', dark: '#14181d' },
28
- textPrimary: { light: '#16191d', dark: '#e7eaee' },
29
- textMuted: { light: '#5c6470', dark: '#9aa4b1' },
30
- borderSubtle: { light: '#e2e6ea', dark: '#262c33' },
31
- accentBg: { light: '#2f6df6', dark: '#4f86ff' },
32
- accentText: { light: '#ffffff', dark: '#0b0d10' },
33
- linkText: { light: '#2f6df6', dark: '#7aa7ff' },
34
- calloutInfoBg: { light: '#eef3ff', dark: '#141c2c' },
35
- calloutInfoText: { light: '#1f3f8f', dark: '#b9cdfb' },
36
- calloutDangerBg: { light: '#fdeceb', dark: '#2a1416' },
37
- calloutDangerText: { light: '#8c2118', dark: '#f5b3ad' },
38
- });
24
+ export const MAIL_TOKENS = Object.freeze<Record<MailToken, Readonly<Record<ColorScheme, string>>>>({
25
+ pageBg: { light: '#f4f5f7', dark: '#0b0d10' },
26
+ surfaceBg: { light: '#ffffff', dark: '#14181d' },
27
+ textPrimary: { light: '#16191d', dark: '#e7eaee' },
28
+ textMuted: { light: '#5c6470', dark: '#9aa4b1' },
29
+ borderSubtle: { light: '#e2e6ea', dark: '#262c33' },
30
+ accentBg: { light: '#2f6df6', dark: '#4f86ff' },
31
+ accentText: { light: '#ffffff', dark: '#0b0d10' },
32
+ linkText: { light: '#2f6df6', dark: '#7aa7ff' },
33
+ calloutInfoBg: { light: '#eef3ff', dark: '#141c2c' },
34
+ calloutInfoText: { light: '#1f3f8f', dark: '#b9cdfb' },
35
+ calloutDangerBg: { light: '#fdeceb', dark: '#2a1416' },
36
+ calloutDangerText: { light: '#8c2118', dark: '#f5b3ad' },
37
+ });
39
38
 
40
39
  /** Resolve a token to a hex value. The only function in the package that returns a colour. */
41
40
  export function token(name: MailToken, scheme: ColorScheme = 'light'): string {
package/src/render.ts CHANGED
@@ -5,6 +5,13 @@
5
5
 
6
6
  import { directionOf, type TranslateVars, type Translator, translatorFor } from '@ultimat3/i18n';
7
7
  import type { CalloutTone, MailBlock, MailTemplate } from './blocks';
8
+ // The renderer depends on the strings it renders. This is the only module that resolves a `mail.*`
9
+ // key, and `catalog.ts` installs them at ITS module scope — so importing it here is what makes
10
+ // "a rendered mail has words in it" structural rather than something a caller has to arrange.
11
+ // Without this edge, only the package ENTRY reached the catalog: every deep import of `render.ts`
12
+ // produced `⟦mail.welcome.subject⟧`, and the tests that would have noticed were the ones calling
13
+ // `registerMailCatalog()` themselves — the exact shape of issue #249.
14
+ import './catalog';
8
15
  import { layoutUnknown, textMissing } from './errors';
9
16
  import { escapeHtml, safeUrl, styleAttr } from './html';
10
17
  import { layoutFor, registeredLayouts, token } from './layout';