@company-semantics/contracts 52.0.0 → 53.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 +1 -1
- package/src/api/generated-spec-hash.ts +2 -2
- package/src/api/generated.ts +122 -69
- package/src/generated/openapi-routes.ts +3 -1
- package/src/identity/README.md +2 -2
- package/src/identity/__tests__/people-org-chart.test.ts +52 -17
- package/src/identity/__tests__/position-ref.test.ts +44 -0
- package/src/identity/index.ts +6 -2
- package/src/identity/people-org-chart.ts +26 -15
- package/src/identity/position-ref.ts +24 -0
- package/src/index.ts +21 -2
- package/src/notifications/__tests__/__snapshots__/monospace-budget.test.ts.snap +23 -0
- package/src/notifications/__tests__/__snapshots__/render-snapshot.test.ts.snap +309 -259
- package/src/notifications/__tests__/monospace-budget.test.ts +75 -0
- package/src/notifications/renderers/README.md +8 -4
- package/src/notifications/renderers/ascii/README.md +75 -0
- package/src/notifications/renderers/ascii/__tests__/README.md +39 -0
- package/src/notifications/renderers/ascii/__tests__/layout.test.ts +228 -0
- package/src/notifications/renderers/ascii/chat.ts +179 -0
- package/src/notifications/renderers/ascii/cta.ts +57 -0
- package/src/notifications/renderers/ascii/geometry.ts +112 -0
- package/src/notifications/renderers/ascii/index.ts +40 -0
- package/src/notifications/renderers/ascii/keyvalue.ts +34 -0
- package/src/notifications/renderers/ascii/rule.ts +53 -0
- package/src/notifications/renderers/ascii/runs.ts +84 -0
- package/src/notifications/renderers/ascii/signature.ts +41 -0
- package/src/notifications/renderers/ascii/wrap.ts +96 -0
- package/src/notifications/renderers/brand.ts +12 -0
- package/src/notifications/renderers/email/chat.ts +62 -146
- package/src/notifications/renderers/email/constants.ts +17 -2
- package/src/notifications/renderers/email/cta.ts +8 -13
- package/src/notifications/renderers/email/render.ts +29 -5
- package/src/notifications/renderers/layout.ts +31 -0
- package/src/notifications/renderers/slack/README.md +135 -79
- package/src/notifications/renderers/slack/__tests__/README.md +3 -2
- package/src/notifications/renderers/slack/__tests__/index.test.ts +233 -93
- package/src/notifications/renderers/slack/blocks.ts +149 -0
- package/src/notifications/renderers/slack/chat.ts +167 -0
- package/src/notifications/renderers/slack/cta.ts +69 -0
- package/src/notifications/renderers/slack/index.ts +136 -229
- package/src/notifications/renderers/slack/message.ts +23 -0
- package/src/org/README.md +10 -2
- package/src/org/__tests__/org-units.test.ts +1 -1
- package/src/org/__tests__/set-seat-manager.test.ts +177 -0
- package/src/org/index.ts +20 -2
- package/src/org/reconciliation.ts +162 -0
- package/src/org/schemas.ts +43 -17
- package/src/identity/org-chart-actor.ts +0 -24
|
@@ -12,66 +12,102 @@ record, a string and a different record, which is `../../renderer.ts`'s "each
|
|
|
12
12
|
renderer returns its natural type" held by real modules rather than asserted about
|
|
13
13
|
the type itself.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
| module | what it holds |
|
|
16
|
+
| ------------ | ---------------------------------------------------------------- |
|
|
17
|
+
| `index.ts` | `slackRenderer`, the element switch, the title |
|
|
18
|
+
| `message.ts` | `SlackMessage`, the channel's natural type |
|
|
19
|
+
| `blocks.ts` | Slack's surfaces, and `../ascii`'s runs bridged onto them |
|
|
20
|
+
| `cta.ts` | the button / the drawn box, and why a CTA is one or the other |
|
|
21
|
+
| `chat.ts` | `chatUnit` → a drawn conversation, cut where a button breaks out |
|
|
18
22
|
|
|
19
|
-
## The
|
|
23
|
+
## The voice is monospace, and it is REAL
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
from the wire format silently and nothing in this package would notice — the
|
|
26
|
-
compiler is the only thing that can, and only if the types are theirs.
|
|
25
|
+
The narrative content is drawn by `../ascii` — the same functions email's
|
|
26
|
+
`text/plain` surface uses, so a conversation cannot come out a column apart on the
|
|
27
|
+
two channels — and set in `rich_text_preformatted`, which Slack renders in a
|
|
28
|
+
monospace face itself.
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
That last clause is the whole point. The brand voice reaches a real workspace
|
|
31
|
+
here, not just the Ladle gallery. Nothing in the app applies a stylesheet to make
|
|
32
|
+
this look monospace; it IS monospace, because of the block type.
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
wire format and those are its names. That is deliberate, not a lapse in the
|
|
34
|
-
repo's camelCase convention.
|
|
34
|
+
## Where the voice stops, and why that is not a compromise
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
Controls, structure and images use Slack's own primitives:
|
|
37
37
|
|
|
38
|
-
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
- a linked `callToAction` is a native `actions` button
|
|
39
|
+
- `divider` is a `divider`
|
|
40
|
+
- `heroImage` is an `image`
|
|
41
|
+
|
|
42
|
+
Slack's button beats anything we could draw inside a code block — a large touch
|
|
43
|
+
target, an unambiguous affordance, keyboard and screen-reader semantics supplied
|
|
44
|
+
by the client, and forward compatibility with whatever Slack's button becomes.
|
|
45
|
+
Sharing a VOCABULARY across channels is the goal; sharing a control
|
|
46
|
+
REPRESENTATION is not. The drift worth preventing is semantic drift, not a
|
|
47
|
+
requirement that a button and a plaintext email match byte for byte.
|
|
48
|
+
|
|
49
|
+
## There is no author-set colour or font — do not go looking again
|
|
50
|
+
|
|
51
|
+
The entire Block Kit schema contains two colour-ish fields and neither helps:
|
|
52
|
+
|
|
53
|
+
| Candidate | What it actually is |
|
|
54
|
+
| ------------------ | ---------------------------------------------------------- |
|
|
55
|
+
| `Button.style` | `'primary' \| 'danger'` — and Slack's `primary` is GREEN |
|
|
56
|
+
| `RichTextColor` | renders a colour SWATCH from a hex, not coloured text |
|
|
57
|
+
| `AlertBlock.level` | `'default' \| 'info' \| 'warning' \| 'error' \| 'success'` |
|
|
58
|
+
| `CardBlock` | images, title, subtitle, body, actions — no colour |
|
|
59
|
+
| attachment `color` | legacy; the documentation page 404s |
|
|
60
|
+
|
|
61
|
+
No font or size field exists anywhere, on any block. A `header` block is
|
|
62
|
+
`plain_text` and Slack owns its typography completely — which is why the title is
|
|
63
|
+
NOT a header block here (see below). Any future attempt to brand this channel with
|
|
64
|
+
CSS belongs in a gallery, not in a renderer, and a gallery that does it is lying
|
|
65
|
+
about what ships.
|
|
66
|
+
|
|
67
|
+
## The title is body text, not a `header` block
|
|
68
|
+
|
|
69
|
+
`HeaderBlock` is `{ type, text }`. Slack picks the size, weight, face and colour,
|
|
70
|
+
so a header would be the one line of the notification that could never be set in
|
|
71
|
+
the brand's face. Putting the title in the monospace body with a rule under it
|
|
72
|
+
makes it monospace in a real workspace.
|
|
73
|
+
|
|
74
|
+
What that costs is Slack's large headline treatment. What it does not cost is the
|
|
75
|
+
push notification or the screen reader: those read `SlackMessage.text`, which is
|
|
76
|
+
`metadata.title` and never came from the header block.
|
|
45
77
|
|
|
46
78
|
## Element → block
|
|
47
79
|
|
|
48
|
-
| Element
|
|
49
|
-
|
|
|
50
|
-
| `metadata.title`
|
|
51
|
-
| `greeting`, `body`, `warning`
|
|
52
|
-
| `keyValueTable`
|
|
53
|
-
| `callToAction` **with** `href`
|
|
54
|
-
| `callToAction` **without** `href`
|
|
55
|
-
| `
|
|
56
|
-
| `
|
|
57
|
-
| `
|
|
58
|
-
| `heroImage`
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
80
|
+
| Element | Block |
|
|
81
|
+
| ---------------------------------------------------- | -------------------------------------------- |
|
|
82
|
+
| `metadata.title` | monospace lines + a full-width rule |
|
|
83
|
+
| `greeting`, `body`, `notice`, `warning`, `signature` | `rich_text` › `rich_text_preformatted` |
|
|
84
|
+
| `keyValueTable`, `metadata` | `Label: value` lines, in the same flow |
|
|
85
|
+
| `callToAction` **with** `href` | `actions` + `button` |
|
|
86
|
+
| `callToAction` **without** `href` | the drawn `>> LABEL <<` box, in the flow |
|
|
87
|
+
| `chatUnit` | a drawn conversation, cut for any linked CTA |
|
|
88
|
+
| `list` | `rich_text` › `rich_text_list` |
|
|
89
|
+
| `divider` | `divider` |
|
|
90
|
+
| `heroImage` | `image` |
|
|
91
|
+
|
|
92
|
+
Contiguous monospace COALESCES into one block. Slack draws every
|
|
93
|
+
`rich_text_preformatted` as its own bordered box, so one block per element stacks
|
|
94
|
+
a grey rectangle per element; email's `text/plain` body is one continuous surface
|
|
95
|
+
and so is this. The run breaks only at a native primitive.
|
|
96
|
+
|
|
97
|
+
## Nothing is declined any more
|
|
98
|
+
|
|
99
|
+
`supports` answers `true` for every element.
|
|
100
|
+
|
|
101
|
+
`chatUnit` was the one decline, on the grounds that Block Kit has no vocabulary
|
|
102
|
+
for turn-taking. That was sound about BLOCKS and wrong about the message: both
|
|
103
|
+
`auth.otp` and `org.invite` nest their payload inside a `chatUnit`, so declining
|
|
104
|
+
the element meant the one-time code and the accept button never reached Slack at
|
|
105
|
+
all. That is semantic loss, not a presentation limitation, and no amount of
|
|
106
|
+
correctness about turn-taking justifies posting a login notification with no login
|
|
107
|
+
code in it.
|
|
108
|
+
|
|
109
|
+
A monospace drawing depicts the conversation AND keeps the attribution the decline
|
|
110
|
+
was protecting, so there is nothing left to decline.
|
|
75
111
|
|
|
76
112
|
## Invariants
|
|
77
113
|
|
|
@@ -79,36 +115,56 @@ de-emphasised small print, which is exactly what `MetadataElement` exists to be.
|
|
|
79
115
|
environment, no I/O. The brand and the copyright year come from `context.brand`.
|
|
80
116
|
`signature` is the element that used to hide a `new Date()`; here there is
|
|
81
117
|
nowhere to hide.
|
|
82
|
-
- `supports
|
|
83
|
-
`
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
- `
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
is untruncated.
|
|
118
|
+
- **`assertNever`, not `supports`, is what protects the next element.** With
|
|
119
|
+
`supports` total-true it can no longer be the guard: an unmapped thirteenth
|
|
120
|
+
element would fall through and post a message with a hole in it — the `chatUnit`
|
|
121
|
+
bug under a new name, and harder to see, because there would be no `supports`
|
|
122
|
+
returning false to point at. `renderElement` therefore has NO `default` arm and
|
|
123
|
+
ends in `assertNever`, so adding a variant fails compilation until someone
|
|
124
|
+
chooses how Slack says it.
|
|
125
|
+
- **There is no mrkdwn surface here, and that is a safety property.** Escaping
|
|
126
|
+
used to be a per-call-site rule: `mrkdwn` parses `&`/`<`/`>` and had to be
|
|
127
|
+
escaped, `plain_text` and `rich_text` are literal and had to NOT be. Both
|
|
128
|
+
mistakes were possible and neither was a type error, because both surfaces are
|
|
129
|
+
`string`. Moving the body to `rich_text_preformatted` retired the last mrkdwn
|
|
130
|
+
emitter, so `escapeMrkdwn`, `mrkdwn`, `section` and `context` were DELETED
|
|
131
|
+
rather than kept in case — a helper that exists is one someone will reach for,
|
|
132
|
+
and reaching for that one is now always the bug.
|
|
133
|
+
- **No empty `rich_text_preformatted`, ever.** Slack draws one as a visible empty
|
|
134
|
+
rectangle, so a break-out at the head or tail of a chat unit must not leave a
|
|
135
|
+
zero-line box beside the button. `./chat.ts` merges segments and then filters.
|
|
136
|
+
- **Adjacent linked CTAs coalesce** into one `actions` block. Two buttons in one
|
|
137
|
+
block sit side by side the way a reader expects a choice; two blocks stack them
|
|
138
|
+
and read as two unrelated prompts. Slack caps a block at 25 elements.
|
|
139
|
+
- A `callToAction` with no `href` is drawn, never made a button. `href` absent
|
|
140
|
+
means the label IS the payload (an OTP code); a button with no destination is a
|
|
141
|
+
lie about what the notification is asking for.
|
|
142
|
+
- Slack's documented limits are enforced here, not left to the API: an `actions`
|
|
143
|
+
block splits at 25 elements. The header's 150-char cap left with the header
|
|
144
|
+
block.
|
|
110
145
|
- Nothing here reads `metadata.kind`. A renderer that special-cases a kind has
|
|
111
146
|
lost the model; the fix for a missing fact is upstream in `compose`.
|
|
112
147
|
- This channel is unreachable from `../../render.ts`'s `renderEmail` and touches
|
|
113
148
|
no parity fixture. It cannot change a sent email, and it must not acquire the
|
|
114
149
|
ability to.
|
|
150
|
+
|
|
151
|
+
## The blocks are Slack's, not ours
|
|
152
|
+
|
|
153
|
+
`SlackMessage.blocks` is `KnownBlock[]` from **`@slack/types`** — the published
|
|
154
|
+
type package behind `node-slack-sdk`, so it is the same union Slack's API
|
|
155
|
+
validates against. `index.ts` re-exports the block types it uses, so a consumer can
|
|
156
|
+
name them without taking its own dependency on `@slack/types`. Widen that list
|
|
157
|
+
rather than redeclaring a shape.
|
|
158
|
+
|
|
159
|
+
The field names are snake_case (`image_url`, `alt_text`) because Block Kit is a
|
|
160
|
+
wire format and those are its names. That is deliberate, not a lapse in the repo's
|
|
161
|
+
camelCase convention.
|
|
162
|
+
|
|
163
|
+
## What is still non-functional
|
|
164
|
+
|
|
165
|
+
- **It does not send.** No token, no channel id, no `chat.postMessage`. A renderer
|
|
166
|
+
renders; delivery and the decision to deliver are the backend's —
|
|
167
|
+
`SlackChannelDelivery` throws `ChannelNotImplementedError` by design.
|
|
168
|
+
- **The copy is invented**, not relocated from a shipped Slack app. The SHAPE is
|
|
169
|
+
legitimate Block Kit and `__tests__/index.test.ts` pins it; the WORDING is
|
|
170
|
+
expected to change when a kind is actually posted to Slack.
|
|
@@ -10,8 +10,9 @@ not pin the Block Kit wording. They pin the contract a placeholder still owes:
|
|
|
10
10
|
read from `RenderContext`, and a content carrying all twelve element types
|
|
11
11
|
rendering without throwing.
|
|
12
12
|
|
|
13
|
-
One exception: the mrkdwn
|
|
14
|
-
fidelity gap that a real Slack kind will close;
|
|
13
|
+
One exception: the ABSENCE of an mrkdwn surface is pinned exactly. Everything
|
|
14
|
+
else here is a fidelity gap that a real Slack kind will close; escaped user input
|
|
15
|
+
reaching a literal surface would be a
|
|
15
16
|
real defect shipped in a published package.
|
|
16
17
|
|
|
17
18
|
## Invariants
|