@company-semantics/contracts 35.0.0 → 36.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 +7 -2
- package/src/notifications/README.md +21 -19
- package/src/notifications/__tests__/README.md +42 -47
- package/src/notifications/__tests__/__snapshots__/README.md +15 -8
- package/src/notifications/__tests__/__snapshots__/registry.test.ts.snap +31 -0
- package/src/notifications/__tests__/__snapshots__/render-snapshot.test.ts.snap +2580 -215
- package/src/notifications/__tests__/context.test.ts +5 -5
- package/src/notifications/__tests__/definition.test.ts +2 -2
- package/src/notifications/__tests__/fixtures.ts +226 -0
- package/src/notifications/__tests__/kinds.test.ts +2 -3
- package/src/notifications/__tests__/registry.test.ts +27 -27
- package/src/notifications/__tests__/render-snapshot.test.ts +10 -6
- package/src/notifications/content.ts +13 -1
- package/src/notifications/index.ts +15 -9
- package/src/notifications/kinds/README.md +2 -2
- package/src/notifications/render.ts +6 -6
- package/src/notifications/renderers/email/README.md +132 -17
- package/src/notifications/renderers/email/__tests__/README.md +19 -16
- package/src/notifications/renderers/email/__tests__/amp.test.ts +217 -0
- package/src/notifications/renderers/email/__tests__/colors.test.ts +227 -0
- package/src/notifications/renderers/email/__tests__/render.test.ts +130 -36
- package/src/notifications/renderers/email/__tests__/styles.test.ts +111 -0
- package/src/notifications/renderers/email/chat.ts +44 -32
- package/src/notifications/renderers/email/colors.ts +342 -0
- package/src/notifications/renderers/email/constants.ts +27 -3
- package/src/notifications/renderers/email/cta.ts +60 -10
- package/src/notifications/renderers/email/index.ts +23 -8
- package/src/notifications/renderers/email/render.ts +31 -15
- package/src/notifications/renderers/email/shells.ts +115 -11
- package/src/notifications/renderers/email/styles.ts +361 -0
- package/src/notifications/renderers/slack/README.md +66 -25
- package/src/notifications/renderers/slack/__tests__/index.test.ts +249 -41
- package/src/notifications/renderers/slack/index.ts +192 -107
- package/src/notifications/text.ts +2 -2
- package/src/notifications/__tests__/output-parity.golden.ts +0 -363
- package/src/notifications/__tests__/output-parity.test.ts +0 -122
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@company-semantics/contracts",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "36.0.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -104,6 +104,7 @@
|
|
|
104
104
|
"prepublishOnly": "echo 'ERROR: Publishing is CI-only via tag push. Use pnpm release instead.' && exit 1",
|
|
105
105
|
"test": "vitest run",
|
|
106
106
|
"test:run": "vitest run",
|
|
107
|
+
"color": "tsx scripts/color.ts",
|
|
107
108
|
"generate:spec-hash": "tsx scripts/generate-spec-hash.ts",
|
|
108
109
|
"generate:spec-hash:check": "tsx scripts/generate-spec-hash.ts --check",
|
|
109
110
|
"generate:api": "pnpm generate:api-types && pnpm generate:spec-hash && pnpm generate:openapi-routes",
|
|
@@ -115,17 +116,21 @@
|
|
|
115
116
|
"repo-map": "tsx ../company-semantics-ci/scripts/generate-repo-map.ts --roots src --name company-semantics-contracts --write",
|
|
116
117
|
"repo-map:check": "tsx ../company-semantics-ci/scripts/generate-repo-map.ts --roots src --name company-semantics-contracts --check",
|
|
117
118
|
"readme-api": "tsx ../company-semantics-ci/scripts/generate-readme-api.ts --roots src --write --jsdoc",
|
|
118
|
-
"readme-api:check": "tsx ../company-semantics-ci/scripts/generate-readme-api.ts --roots src --check --jsdoc"
|
|
119
|
+
"readme-api:check": "tsx ../company-semantics-ci/scripts/generate-readme-api.ts --roots src --check --jsdoc",
|
|
120
|
+
"validate:amp": "tsx scripts/validate-amp.ts"
|
|
119
121
|
},
|
|
120
122
|
"packageManager": "pnpm@10.25.0",
|
|
121
123
|
"engines": {
|
|
122
124
|
"node": "22.x"
|
|
123
125
|
},
|
|
124
126
|
"dependencies": {
|
|
127
|
+
"@slack/types": "^3.0.0",
|
|
125
128
|
"zod": "^4.4.3"
|
|
126
129
|
},
|
|
127
130
|
"devDependencies": {
|
|
128
131
|
"@types/node": "^22.20.1",
|
|
132
|
+
"amphtml-validator": "^1.0.38",
|
|
133
|
+
"culori": "^4.0.2",
|
|
129
134
|
"husky": "^9.1.7",
|
|
130
135
|
"lint-staged": "^17.0.8",
|
|
131
136
|
"markdownlint-cli2": "^0.23.0",
|
|
@@ -9,15 +9,17 @@ into `Definition → Payload → compose(payload, context) → Content → Rende
|
|
|
9
9
|
This is the package's SOLE notification surface. `@company-semantics/contracts/email`
|
|
10
10
|
and every `Email*` symbol are gone, with no shim and no alias.
|
|
11
11
|
|
|
12
|
-
The domain holds the **
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
migration
|
|
12
|
+
The domain holds the **semantic model**: the channel-agnostic vocabulary an
|
|
13
|
+
element names, and the renderers that decide what it looks like per channel.
|
|
14
|
+
|
|
15
|
+
It used to hold an **output-parity baseline** too — a byte-exact capture of what
|
|
16
|
+
the email layer rendered at 32.0.0, landed deliberately _before_ the abstraction
|
|
17
|
+
it protected, so every step of the migration had a mechanical answer to "did any
|
|
18
|
+
real email change?". That baseline did its job: the answer was "none", and it
|
|
19
|
+
shipped in 35.0.0. ADR-CONTRACTS-087 retired it, because a proof that the
|
|
20
|
+
migration changed nothing cannot also serve as permission to change something,
|
|
21
|
+
and email design is not frozen. `__tests__/render-snapshot.test.ts` is the fence
|
|
22
|
+
now; the retired bytes are readable in git at `v35.0.0`.
|
|
21
23
|
|
|
22
24
|
- `content.ts` — `NotificationContent { metadata, sections }`, `Section`, and the
|
|
23
25
|
flat `NotificationElement` union (greeting, body, keyValueTable, callToAction,
|
|
@@ -91,16 +93,16 @@ deliberately (ADR-CONTRACTS-086):
|
|
|
91
93
|
|
|
92
94
|
## Invariants
|
|
93
95
|
|
|
94
|
-
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
- Every kind in `NOTIFICATION_DEFINITIONS` needs at least one
|
|
100
|
-
enforced by the
|
|
101
|
-
"every kind the entry point accepts" and "every
|
|
102
|
-
same claim.
|
|
103
|
-
- Rendering
|
|
96
|
+
- `__tests__/render-snapshot.test.ts` locks what this layer renders. A diff there
|
|
97
|
+
means a real sent email changed: review it, then regenerate. It is a regression
|
|
98
|
+
fence, not a specification — but since ADR-CONTRACTS-087 retired the parity
|
|
99
|
+
golden that used to outrank it, reading the diff is the ONLY control left. A
|
|
100
|
+
snapshot refreshed on sight proves nothing.
|
|
101
|
+
- Every kind in `NOTIFICATION_DEFINITIONS` needs at least one fixture in
|
|
102
|
+
`__tests__/fixtures.ts`, enforced by the tests. The registry is the source of
|
|
103
|
+
that list — it is total, so "every kind the entry point accepts" and "every
|
|
104
|
+
kind with a fixture" are the same claim.
|
|
105
|
+
- Rendering MUST pin the year to `FIXTURE_CLOCK`'s — by passing
|
|
104
106
|
`copyrightYear` in the `RenderContext`, NOT by freezing the clock. `signature`
|
|
105
107
|
used to read `new Date().getFullYear()` mid-render, which is why the fixtures
|
|
106
108
|
once needed fake timers; the year is a value now, and no test in this domain
|
|
@@ -2,53 +2,48 @@
|
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
-
The
|
|
6
|
-
(ADR-CONTRACTS-086), and the snapshot suite that documents the markup
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
- `render-snapshot.test.ts` + `__snapshots__/` —
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
The parity
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
`
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
5
|
+
The shared fixture corpus for the multi-channel notification render layer
|
|
6
|
+
(ADR-CONTRACTS-086), and the snapshot suite that documents the markup it renders
|
|
7
|
+
to.
|
|
8
|
+
|
|
9
|
+
- `fixtures.ts` — every implemented kind × representative payload, plus
|
|
10
|
+
`FIXTURE_CLOCK` and `fixtureKey`. Shared by every suite here and by
|
|
11
|
+
`../renderers/email/__tests__/render.test.ts`, so a kind added to the registry
|
|
12
|
+
is covered everywhere by being added once.
|
|
13
|
+
- `render-snapshot.test.ts` + `__snapshots__/` — those fixtures rendered through
|
|
14
|
+
the email channel, as vitest snapshots. This is the fence: **a diff here means
|
|
15
|
+
a real sent email changed.**
|
|
16
|
+
|
|
17
|
+
## The retired output-parity golden (ADR-CONTRACTS-087)
|
|
18
|
+
|
|
19
|
+
`fixtures.ts` was `output-parity.golden.ts`, and it used to carry
|
|
20
|
+
`OUTPUT_PARITY_GOLDEN` — the exact `{ subject, text, html }` the email layer
|
|
21
|
+
emitted at 32.0.0, before any of this existed — asserted byte-for-byte by
|
|
22
|
+
`output-parity.test.ts` at the entry point and by the email renderer's own suite
|
|
23
|
+
at the renderer. Those two tests were the "no output change" contract for the
|
|
24
|
+
generalisation: `src/email/` was deleted on that proof.
|
|
25
|
+
|
|
26
|
+
**The proof passed and shipped in 35.0.0, which discharged it.** It was a claim
|
|
27
|
+
about a migration, not a freeze on email design — and the moment email design is
|
|
28
|
+
deliberately exercised, byte-equality to a deleted layer fails by construction
|
|
29
|
+
and keeps failing.
|
|
30
|
+
|
|
31
|
+
It was **retired rather than regenerated**, and the distinction is the point:
|
|
32
|
+
rewriting those bytes to match a redesign would have made the file assert
|
|
33
|
+
something false about a layer that no longer exists to contradict it. The bytes
|
|
34
|
+
are readable in git at `v35.0.0`; nothing in the tree claims to be them.
|
|
27
35
|
|
|
28
36
|
## Invariants
|
|
29
37
|
|
|
30
|
-
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
- The golden and the fixtures MUST stay in exact correspondence — no missing and
|
|
43
|
-
no orphaned entries. The test enforces both directions.
|
|
44
|
-
- Every kind in `NOTIFICATION_DEFINITIONS` MUST have a fixture, enforced by both
|
|
45
|
-
suites.
|
|
46
|
-
- The golden's BYTES are frozen; its TYPES were re-anchored onto this domain when
|
|
47
|
-
the `Email*` originals were deleted. `OutputParityFixture.options` went with
|
|
48
|
-
them — it was declared as `RenderEmailOptions` and carried the old layer's
|
|
49
|
-
`includeRequestMetadata`, which `compose` replaced with "does the payload carry
|
|
50
|
-
request details?". It had been left in place as frozen evidence, but its type
|
|
51
|
-
no longer exists and no test ever read it; the one fixture that set it supplies
|
|
52
|
-
both request fields anyway, so the gate opens regardless. The byte equality is
|
|
53
|
-
what proves that — which is exactly why dropping the field was safe and
|
|
54
|
-
re-recording a byte would not be.
|
|
38
|
+
- A snapshot diff means **a real sent email changed**. Review it, then
|
|
39
|
+
`vitest -u` — never the other way round. Regenerating on sight is the whole
|
|
40
|
+
hazard; the review is the control, and it is now the only one.
|
|
41
|
+
- The year MUST be pinned via `FIXTURE_CLOCK` on the `RenderContext`, never by
|
|
42
|
+
freezing the clock. `signature` bakes it into every case, so an unpinned render
|
|
43
|
+
fails on the next New Year's Day for calendar reasons alone. Fake timers are
|
|
44
|
+
not needed anywhere here — that is the impurity ADR-CONTRACTS-086 retired, so
|
|
45
|
+
do not reintroduce them.
|
|
46
|
+
- The pinned year is **not** redacted or normalised. It is part of the output
|
|
47
|
+
surface these suites exist to protect.
|
|
48
|
+
- Every kind in `NOTIFICATION_DEFINITIONS` MUST have a fixture, enforced by
|
|
49
|
+
`./definition.test.ts` and `./registry.test.ts`.
|
|
@@ -13,6 +13,11 @@ stayed `{kind} · {name}`, so every snapshot key still resolves against the same
|
|
|
13
13
|
file. That is the claim the move is making — the keys matched, so the bytes
|
|
14
14
|
matched.
|
|
15
15
|
|
|
16
|
+
The `security.alert` cases have since moved: the `warning` banner is drawn in the
|
|
17
|
+
product's destructive colour (ADR-CONTRACTS-087). That is the first deliberate
|
|
18
|
+
change to this markup since the move, and the reason the parity golden that used
|
|
19
|
+
to guard it is gone.
|
|
20
|
+
|
|
16
21
|
This directory exists because vitest resolves snapshots adjacent to the test
|
|
17
22
|
file, so the `.snap` had to travel with it.
|
|
18
23
|
|
|
@@ -20,13 +25,15 @@ file, so the `.snap` had to travel with it.
|
|
|
20
25
|
|
|
21
26
|
- This is **production-parity email markup**. A diff here means a real sent email
|
|
22
27
|
changed. Review it, then `vitest -u` — never the other way round.
|
|
23
|
-
- These snapshots are **
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
- These snapshots are now the **only** automated record of that markup. Until
|
|
29
|
+
ADR-CONTRACTS-087 the migration's proof sat beside them in
|
|
30
|
+
`../output-parity.golden.ts`, a committed module `vitest -u` could not touch,
|
|
31
|
+
and it was the tie-breaker when the two disagreed. That proof was discharged in
|
|
32
|
+
35.0.0 and retired, because it asserted byte-equality with a deleted layer and
|
|
33
|
+
email design is allowed to change. Nothing now catches a snapshot regenerated
|
|
34
|
+
under refactor pressure except reading the diff — so read it.
|
|
35
|
+
- The copyright year is pinned to `FIXTURE_CLOCK`'s, never the wall clock. It is
|
|
36
|
+
baked into every case via `signature`, so an unpinned capture would fail on the
|
|
37
|
+
next New Year's Day for calendar reasons alone.
|
|
31
38
|
- The year is **not** redacted or normalised. It is part of the output surface
|
|
32
39
|
these snapshots exist to protect.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`NOTIFICATION_DEFINITIONS > titles every kind 1`] = `
|
|
4
|
+
{
|
|
5
|
+
"auth.otp · Default": "Your login code for Company Semantics",
|
|
6
|
+
"auth.otp · Short expiry": "Your login code for Company Semantics",
|
|
7
|
+
"auth.otp · With request metadata": "Your login code for Company Semantics",
|
|
8
|
+
"chat.shared · Default": "A chat has been shared with you",
|
|
9
|
+
"chat.shared · Long preview": "A chat has been shared with you",
|
|
10
|
+
"chat.shared · Long title": "A chat has been shared with you",
|
|
11
|
+
"chat.shared · No preview": "A chat has been shared with you",
|
|
12
|
+
"chat.shared · Short": "A chat has been shared with you",
|
|
13
|
+
"companyMd.access_request_approved · Editor": "Your access request was approved",
|
|
14
|
+
"companyMd.access_request_approved · Viewer": "Your access request was approved",
|
|
15
|
+
"companyMd.access_request_denied · No reason": "Your access request was reviewed",
|
|
16
|
+
"companyMd.access_request_denied · With reason": "Your access request was reviewed",
|
|
17
|
+
"companyMd.access_requested · No message": "Someone requested access to a document",
|
|
18
|
+
"companyMd.access_requested · With message": "Someone requested access to a document",
|
|
19
|
+
"org.invite · Admin": "You've been invited to join Acme Corp on Company Semantics",
|
|
20
|
+
"org.invite · Member": "You've been invited to join Acme Corp on Company Semantics",
|
|
21
|
+
"org.ownership_transfer · No note (admin)": "You've been invited to become a workspace owner",
|
|
22
|
+
"org.ownership_transfer · With note + from": "You've been invited to become a workspace owner",
|
|
23
|
+
"org.ownership_transfer_completed · Default": "Workspace ownership has been transferred",
|
|
24
|
+
"org.unit_owner_granted · Delegate with expiry": "You've been added as delegate owner to Platform Engineering in Acme Corp",
|
|
25
|
+
"org.unit_owner_granted · Unit owner + message": "You've been added as owner to Platform Engineering in Acme Corp",
|
|
26
|
+
"security.alert · Excessive OTP requests": "Security alert for your account",
|
|
27
|
+
"security.alert · Unusual login location": "Security alert for your account",
|
|
28
|
+
"share.granted · Document · editor · message": "Something has been shared with you",
|
|
29
|
+
"share.granted · Meeting · viewer · no title": "Something has been shared with you",
|
|
30
|
+
}
|
|
31
|
+
`;
|