@company-semantics/contracts 36.0.0 → 38.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": "@company-semantics/contracts",
3
- "version": "36.0.0",
3
+ "version": "38.0.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -50,11 +50,31 @@ The cost is that `EmailLine.html` is now an intermediate representation —
50
50
  `render.ts` no longer reads as the bytes it emits, and the snapshot is where the
51
51
  real markup is legible.
52
52
 
53
- **What AMP is for:** `:hover`. ADR-CONTRACTS-088 bought a hover state that Gmail
54
- does not honour, and Gmail is most opens. AMP is where that rule reaches people.
55
-
56
- **What AMP does not have:** dark. AMP4EMAIL disallows the `prefers-color-scheme`
57
- media feature outright. It costs nothing see Colour.
53
+ ### ⚠️ The AMP body is NEVER SENT
54
+
55
+ Two of the three ship. `amp` is rendered, previewed in the Ladle gallery, and
56
+ dropped — and **delivering it is a regression, not a feature**. The backend's
57
+ `EmailDelivery.test.ts` fails if it reaches the transport; that test is the
58
+ invariant, this section is its explanation.
59
+
60
+ **Why not sent.** Gmail iOS inverts the `text/html` part post-delivery and does NOT
61
+ touch a `text/x-amp-html` part. That inversion is the only dark mode a Gmail iOS
62
+ reader gets — no Gmail client honours `prefers-color-scheme` on any surface — so an
63
+ AMP part would switch it off and hand them a white email in a dark client. AMP buys
64
+ `:hover` in Gmail and would cost dark mode in Gmail. Measured, not argued:
65
+ `company-semantics-backend/docs/dark-mode-probe.md` (ADR-CONTRACTS-091, -092).
66
+
67
+ **Why it exists.** AMP is the only email format with a real animation model —
68
+ `amp-animation`, `amp-bind`, `amp-position-observer`, no arbitrary JS. Nothing else
69
+ can make the kaomoji companions blink once, three seconds after open. Keeping the
70
+ surface alive costs one inert field; rebuilding it later costs more. If Google ever
71
+ allows `prefers-color-scheme` here, or Gmail starts inverting AMP too, the trade
72
+ flips and this is ready.
73
+
74
+ **Light-only, permanently.** `prefers-color-scheme` in `<style amp-custom>` is a hard
75
+ validator error (explicit 14-feature allowlist, frozen since 2023, unchanged by
76
+ `data-css-strict`). The `media` ATTRIBUTE takes the same feature, PASSES the
77
+ validator — and Gmail's runtime ignores it. Both doors closed.
58
78
 
59
79
  `pnpm validate:amp` runs the real AMP validator. `__tests__/amp.test.ts` asserts
60
80
  the constraints we KNOW on every run, which is not the same claim: it passed once
@@ -161,12 +181,15 @@ expect to style it properly rather than trust the placeholder.
161
181
  simply rests. It is also the ONE rule the AMP surface shares, via
162
182
  `hoverStyle(false)`; AMP forbids `!important`, which is why that template takes a
163
183
  parameter and `DARK_STYLE` does not.
164
- - The AMP surface has NO dark scheme, and loses nothing by it. AMP4EMAIL disallows
165
- the `prefers-color-scheme` media feature (checked against the AMP validator, with
166
- and without `data-css-strict`), so it cannot. The audiences are disjoint: dark
167
- reaches Apple Mail, iOS Mail and Outlook for Mac; AMP reaches Gmail, Yahoo and
168
- Mail.ru the force-inverting clients that never received our dark colours anyway.
169
- AMP swaps a scheme its readers never got for a hover state they never got.
184
+ - The AMP surface has NO dark scheme, and that is why it is never SENT. AMP4EMAIL
185
+ disallows the `prefers-color-scheme` media feature (an explicit 14-feature
186
+ allowlist, unchanged by `data-css-strict`), so it cannot have one. ADR-CONTRACTS-089
187
+ called that free "AMP's readers force-invert and never got our dark colours
188
+ anyway". Half right: they never got them, because no Gmail client honours the media
189
+ feature on any surface. But Gmail web recolours nothing, Yahoo does not invert, and
190
+ Gmail iOS inverts the html part while EXEMPTING an AMP part — so an AMP part does
191
+ not lose a scheme its readers never had, it switches off the inversion that IS
192
+ their dark mode. Measured; see the AMP section above.
170
193
  - Role hooks ride the AMP surface unstyled (`cs-faint`, `cs-bubble`, `cs-cta`,
171
194
  `cs-link`), because their only rules live in `DARK_STYLE`. Their colours arrive
172
195
  via each recipe's `roleDecl`, so this is dead bytes rather than a bug — the price
@@ -183,7 +206,11 @@ expect to style it properly rather than trust the placeholder.
183
206
  promise: made alone it stops Apple Mail protecting our colours while leaving
184
207
  light text on a dark background — worse than declaring light-only. This channel
185
208
  WAS light-only by construction until ADR-CONTRACTS-088; it is not any more.
186
- - Dark reaches Apple Mail, iOS Mail and Outlook for Mac. Gmail and Outlook
187
- force-invert and are unreachable the dark path is additive, and a client that
188
- strips `<style>` keeps the inline light colours, which is the status quo rather
189
- than a degradation.
209
+ - Dark reaches Apple Mail, iOS Mail and Outlook for Mac, and nobody else. Gmail
210
+ honours none of it on any surface, so a Gmail reader's dark mode is whatever their
211
+ client does to our light colours unaided: Gmail iOS inverts the html part, and
212
+ **Gmail web does nothing at all** — a light email in a dark client. That gap is
213
+ real and open, and the lever is inversion-indifferent authoring in `colors.ts`
214
+ (mid-tones, no pure `#ffffff`/`#000000` in large fields, solid-fill CTAs), not
215
+ another surface. The dark path is additive: a client that strips `<style>` keeps
216
+ the inline light colours, which is the status quo rather than a degradation.
@@ -174,11 +174,19 @@ describe("the AMP surface", () => {
174
174
 
175
175
  it("carries no dark scheme, because AMP disallows the media feature", () => {
176
176
  // NOT an omission to fix later: `prefers-color-scheme` is a disallowed media
177
- // feature in amp-custom (verified against the AMP validator, with and without
178
- // `data-css-strict`), so this surface cannot have dark in any form. It costs
179
- // nothing the dark block's clients (Apple Mail, iOS Mail, Outlook for Mac)
180
- // and this surface's clients (Gmail, Yahoo, Mail.ru) are disjoint sets, and
181
- // the latter force-invert rather than read a stylesheet.
177
+ // feature in amp-custom (an explicit 14-feature allowlist in the validator's
178
+ // protoascii, `issues_as_error: true`, unchanged by `data-css-strict`, frozen
179
+ // since 2023), so this surface cannot have dark in any form.
180
+ //
181
+ // Nor can it via the back door: the `media` ATTRIBUTE on an AMP element takes
182
+ // the same feature and PASSES the validator — and Gmail's runtime ignores it,
183
+ // rendering a dark-gated and a light-gated block at once. Measured; see
184
+ // `company-semantics-backend/docs/dark-mode-probe.md`. Both doors are closed.
185
+ //
186
+ // The surface being light-only is exactly why it is never SENT: an AMP part
187
+ // switches off Gmail iOS's inversion, which is the only dark mode those readers
188
+ // get (ADR-CONTRACTS-091/-092). It is rendered for a future that needs AMP's
189
+ // animation model, not for delivery.
182
190
  //
183
191
  // This test exists so that "add dark mode to AMP" fails here with the reason,
184
192
  // rather than in `pnpm validate:amp` with a CSS syntax error.
@@ -270,12 +270,16 @@ const dark = PALETTES.dark;
270
270
  * the LIGHT hover; the dark block follows it in the shell and overrides it, so
271
271
  * source order is what resolves them (same specificity).
272
272
  *
273
- * Whether any of it happens is the client's call, and this is the ONE role whose
274
- * reach the AMP surface changes. Apple Mail honours `:hover`, new Outlook for Mac
275
- * and Yahoo partially, Gmail and classic Outlook not at all but Gmail is an AMP
276
- * client, and there this rule simply works. On the html surface there is no
277
- * fallback to write: without it the button rests, which is how it looks today.
278
- * Purely additive, like the dark path. See `./styles.ts` for the surface split.
273
+ * Whether any of it happens is the client's call. Apple Mail honours `:hover`, new
274
+ * Outlook for Mac and Yahoo partially, Gmail and classic Outlook not at all. There
275
+ * is no fallback to write: without it the button rests, which is how it looks
276
+ * today. Purely additive, like the dark path.
277
+ *
278
+ * This is the ONE role whose reach the AMP surface would change — Gmail honours
279
+ * `:hover` there — and it is why that surface exists. It is NOT why the surface is
280
+ * unsent: shipping it would cost Gmail iOS its dark mode, which is worth more than
281
+ * a hover state (ADR-CONTRACTS-091/-092). The `important` parameter is what lets
282
+ * one rule serve both shells; see `./shells.ts`.
279
283
  *
280
284
  * `.cs-cta-hover` is present ONLY on a CTA that actually has an `href` — see
281
285
  * `./cta.ts`. An OTP code wears `.cs-cta` for its edge and never this, because a
@@ -301,15 +305,26 @@ export const BASE_STYLE = hoverStyle(true);
301
305
  * colour is inline, inline beats a stylesheet on specificity, and without
302
306
  * `!important` every rule here silently loses and dark quietly does nothing.
303
307
  *
304
- * **This block is the html surface's alone, and gives up nothing by being so.**
305
- * AMP4EMAIL disallows the `prefers-color-scheme` media feature outright — verified
306
- * against the AMP validator, with and without `data-css-strict` — so the AMP
307
- * surface cannot carry these rules in any form, and does not try
308
- * (ADR-CONTRACTS-089). It costs nothing, because the two audiences are disjoint:
309
- * this block is read by Apple Mail, iOS Mail and Outlook for Mac (see
310
- * `./shells.ts`), and AMP is read by Gmail, Yahoo and Mail.ru — clients that
311
- * force-invert and never honoured it. Unlike `hoverStyle`, there was no version of
312
- * this worth parameterizing.
308
+ * **This block is the html surface's alone, and that is why the AMP surface is
309
+ * never sent.** AMP4EMAIL disallows the `prefers-color-scheme` media feature
310
+ * outright a hard validator error, checked with and without `data-css-strict` —
311
+ * so the AMP surface cannot carry these rules in any form (ADR-CONTRACTS-089).
312
+ * Unlike `hoverStyle`, there was no version of this worth parameterizing.
313
+ *
314
+ * ADR-CONTRACTS-089 called that free, on the grounds that AMP's readers force-invert
315
+ * and never received these colours anyway. **The first half is true and the second is
316
+ * false.** Nobody in Gmail or Yahoo honours `prefers-color-scheme`, so this block
317
+ * genuinely never reached them — but Gmail web recolours nothing, Yahoo does not
318
+ * invert, and Gmail iOS inverts the html part while EXEMPTING an AMP part. So an AMP
319
+ * part does not lose a scheme its readers never got; it switches off the client-side
320
+ * inversion that IS their dark mode. Measured in
321
+ * `company-semantics-backend/docs/dark-mode-probe.md`; the surface stays unsent
322
+ * (ADR-CONTRACTS-091/-092).
323
+ *
324
+ * **Which leaves a real gap this block cannot close.** Gmail web recolours nothing
325
+ * and honours nothing, so a light email stays light in a dark client. The lever there
326
+ * is inversion-indifferent authoring in this file — mid-tones, no pure `#ffffff` /
327
+ * `#000000` in large fields, solid-fill CTAs — not another surface.
313
328
  *
314
329
  * `.cs-faint` states both `color` and `border-top-color` because one role lands
315
330
  * on two properties — the `/* EOM *\/` span takes the first and ignores the
@@ -33,9 +33,11 @@ import { ampShell, type EmailLine, htmlShell, textShell } from "./shells";
33
33
  * Three bodies, because an email IS a `multipart/alternative`: one message, whose
34
34
  * parts a client chooses between. This is NOT three channels — a recipient gets one
35
35
  * email — which is why AMP lives here rather than behind a second `Renderer`
36
- * (ADR-CONTRACTS-089). Assembling the parts is delivery's job, and delivery is not
37
- * contracts': today the backend sends `text` and `html` over SES's simple send and
38
- * `amp` goes nowhere.
36
+ * (ADR-CONTRACTS-089).
37
+ *
38
+ * **Two of them ship.** Assembling the parts is delivery's job, and delivery is not
39
+ * contracts' — but the `amp` body is not merely un-plumbed, it is deliberately unsent
40
+ * and must stay that way (ADR-CONTRACTS-092). See the field.
39
41
  */
40
42
  export interface RenderedEmail {
41
43
  /** Subject line — the notification's `metadata.title`. */
@@ -45,8 +47,16 @@ export interface RenderedEmail {
45
47
  /** HTML body (`text/html`) — every kind is multi-output. */
46
48
  html: string;
47
49
  /**
48
- * AMP4EMAIL body (`text/x-amp-html`) — read by Gmail, Yahoo and Mail.ru, and
49
- * the only surface where the CTA's `:hover` reaches most recipients.
50
+ * AMP4EMAIL body (`text/x-amp-html`).
51
+ *
52
+ * ⚠️ **Rendered, and deliberately NEVER SENT** (ADR-CONTRACTS-092). Delivering it
53
+ * is a regression: Gmail iOS inverts the html part and does NOT touch an AMP
54
+ * part, so an AMP part switches off the only dark mode a Gmail iOS reader gets.
55
+ * `EmailDelivery.test.ts` in the backend fails if this reaches the transport.
56
+ *
57
+ * It is kept alive for AMP's animation model (`amp-animation`, `amp-bind`),
58
+ * which no other email format has, and which is the one thing that could make it
59
+ * worth sending. See `./shells.ts`'s `ampShell`.
50
60
  */
51
61
  amp: string;
52
62
  }
@@ -49,10 +49,17 @@ export interface EmailLine {
49
49
  * text colour sitting on top of it. The two ship together or not at all.
50
50
  *
51
51
  * Only Apple Mail, iOS Mail and Outlook for Mac read any of this. Clients that
52
- * force-invert (Outlook.com, Windows Outlook, Gmail Android) ignore both metas
53
- * and rewrite the inline styles post-delivery — nothing here can stop that, which
54
- * is why the body still states an explicit background rather than trusting a
55
- * default.
52
+ * recolour instead (Outlook.com, Windows Outlook, and measured
53
+ * **Gmail iOS**) ignore both metas and rewrite the inline styles post-delivery.
54
+ * Nothing here can stop that, which is why the body still states an explicit
55
+ * background rather than trusting a default.
56
+ *
57
+ * Do not read that as a degradation to fix: Gmail iOS's inversion of THIS part is
58
+ * the only dark mode a Gmail iOS reader gets, and preserving it is why `ampShell`'s
59
+ * output is never sent (ADR-CONTRACTS-092). Gmail web, by contrast, recolours
60
+ * nothing at all — a light email stays light in a dark client, and no surface fixes
61
+ * that. Gmail Android is untested: unknown, not negative.
62
+ * `company-semantics-backend/docs/dark-mode-probe.md`.
56
63
  *
57
64
  * The `<!--[if mso]>` block is the mirror image: it is read ONLY by the Outlook
58
65
  * Word engine, and undoes the one layout that engine cannot render — a linked
@@ -86,12 +93,38 @@ ${inner}
86
93
  }
87
94
 
88
95
  /**
89
- * Wrap rendered lines in the AMP4EMAIL shell (ADR-CONTRACTS-089).
96
+ * Wrap rendered lines in the AMP4EMAIL shell (ADR-CONTRACTS-089, -091, -092).
97
+ *
98
+ * ⚠️ **THIS BODY IS NEVER SENT, AND MUST NOT BE.** It is rendered, previewed in the
99
+ * Ladle gallery, and dropped. Delivering it is a REGRESSION, not a feature — see
100
+ * below. `company-semantics-backend`'s `EmailDelivery.test.ts` fails if it ever
101
+ * reaches the transport, and that test is the invariant; this comment is only its
102
+ * explanation.
103
+ *
104
+ * **Why it is not sent.** Gmail iOS inverts the `text/html` part post-delivery and
105
+ * does **NOT** touch a `text/x-amp-html` part. That inversion is the ONLY dark mode
106
+ * a Gmail iOS reader gets — no Gmail client honours `prefers-color-scheme` on any
107
+ * surface — so shipping an AMP part would switch it off and hand them a white email
108
+ * in a dark client. This surface buys `:hover` in Gmail and would cost dark mode in
109
+ * Gmail. That trade was measured and refused (ADR-CONTRACTS-091), and the measurement
110
+ * is `company-semantics-backend/docs/dark-mode-probe.md`. ADR-CONTRACTS-089 asserted
111
+ * the opposite — "AMP clients force-invert, so they never got our dark colours
112
+ * anyway" — and that is FALSE: Gmail web recolours nothing, Yahoo does not invert,
113
+ * and Gmail iOS exempts AMP.
90
114
  *
91
- * The third MIME part of the same email `text/x-amp-html`, beside `text/html`
92
- * and `text/plain`. Gmail, Yahoo and Mail.ru read it; everyone else reads the
93
- * html part and never knows this existed. That is the point: `./colors.ts`'s
94
- * `hoverStyle` is absent in Gmail on the html surface, and here it simply works.
115
+ * **Why it exists at all, then.** AMP is the only email format with a real animation
116
+ * model `amp-animation`, `amp-bind`, `amp-position-observer` and no arbitrary JS
117
+ * needed. Nothing else can make the kaomoji companions blink once, three seconds
118
+ * after open. That is a future worth keeping the surface alive for, and rebuilding it
119
+ * later costs more than carrying it inert now (ADR-CONTRACTS-092). If Google ever
120
+ * allows `prefers-color-scheme` here, or Gmail starts inverting AMP too, the trade
121
+ * flips and this is ready.
122
+ *
123
+ * **Light-only, permanently.** AMP4EMAIL disallows the `prefers-color-scheme` media
124
+ * feature — a hard validator error, checked with and without `data-css-strict` — so
125
+ * `DARK_STYLE` cannot appear here in any form. The `media` ATTRIBUTE on an AMP
126
+ * element takes the same feature and PASSES the validator; Gmail's runtime ignores
127
+ * it. Both doors are closed. That is not a gap to close; it is the format.
95
128
  *
96
129
  * Everything AMP forbids is absent BY CONSTRUCTION rather than by stripping:
97
130
  *
@@ -103,16 +136,6 @@ ${inner}
103
136
  * in a line, so nothing has to remove it. Outlook is not an AMP client, and
104
137
  * `display: block` needs no correction in the three that are.
105
138
  *
106
- * **No dark scheme, and that is not an omission.** AMP4EMAIL disallows the
107
- * `prefers-color-scheme` media feature — verified against the AMP validator, with
108
- * and without `data-css-strict` — so `DARK_STYLE` cannot appear here in any form.
109
- * It costs nothing: that block is read by Apple Mail, iOS Mail and Outlook for Mac,
110
- * and this surface is read by Gmail, Yahoo and Mail.ru. The two sets are disjoint,
111
- * and the AMP clients force-invert rather than honour a stylesheet — so they were
112
- * never receiving our dark colours on the html surface either. What this surface
113
- * adds is `:hover`, which those clients DO honour and which the html surface cannot
114
- * reach them with. It trades nothing away.
115
- *
116
139
  * The boilerplate hides the body until the runtime unhides it — which is why an
117
140
  * AMP document that cannot run scripts shows nothing at all, rather than showing
118
141
  * an unstyled version of itself.