@company-semantics/contracts 92.0.0 → 93.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 +9 -9
- package/src/action-items/README.md +12 -4
- package/src/action-items/schemas.ts +10 -0
- package/src/affordances/README.md +52 -1
- package/src/affordances/__tests__/README.md +10 -1
- package/src/affordances/__tests__/affordances.test.ts +13 -1
- package/src/affordances/__tests__/catalog-wave.test.ts +237 -0
- package/src/affordances/affordance.ts +56 -4
- package/src/affordances/args.ts +44 -1
- package/src/affordances/available.ts +59 -0
- package/src/affordances/coverage.ts +51 -0
- package/src/affordances/ids.ts +28 -0
- package/src/affordances/index.ts +17 -0
- package/src/api/README.md +1 -0
- package/src/api/generated-spec-hash.ts +2 -2
- package/src/api/generated.ts +397 -1
- package/src/api/primitives.ts +13 -0
- package/src/billing/README.md +1 -0
- package/src/billing/refusals.ts +12 -0
- package/src/message-parts/README.md +5 -0
- package/src/message-parts/affordance-offer.ts +44 -0
- package/src/message-parts/index.ts +7 -0
- package/src/message-parts/types.ts +7 -1
- package/src/message-parts/wire.ts +18 -0
- package/src/notifications/README.md +17 -3
- package/src/notifications/__tests__/README.md +4 -0
- package/src/notifications/__tests__/__snapshots__/render-snapshot.test.ts.snap +27 -25
- package/src/notifications/__tests__/fixtures.ts +7 -1
- package/src/notifications/__tests__/grounded-ctas.test.ts +103 -0
- package/src/notifications/__tests__/render-snapshot.test.ts +1 -1
- package/src/notifications/content.ts +9 -0
- package/src/notifications/kinds/billing-auto-recharge-failed.ts +4 -2
- package/src/notifications/kinds/billing-payment-past-due.ts +2 -0
- package/src/notifications/kinds/billing-suspended.ts +2 -0
- package/src/notifications/kinds/chat-shared.ts +10 -3
- package/src/notifications/kinds/company-md-access-approved.ts +13 -6
- package/src/notifications/kinds/share-granted.ts +1 -1
- package/src/notifications/kinds/unit-owner-granted.ts +9 -2
- package/src/notifications/payloads.ts +6 -0
- package/src/proactive/README.md +18 -2
- package/src/proactive/__tests__/README.md +11 -1
- package/src/proactive/__tests__/chat-templates.test.ts +7 -6
- package/src/proactive/__tests__/proactive-offers.test.ts +124 -0
- package/src/proactive/__tests__/vocabulary.test.ts +1 -1
- package/src/proactive/composer.ts +11 -0
- package/src/proactive/templates/README.md +11 -3
- package/src/proactive/templates/billing-auto-recharge-failed.ts +1 -0
- package/src/proactive/templates/billing-balance-exhausted.ts +2 -6
- package/src/proactive/templates/billing-payment-past-due.ts +1 -0
- package/src/proactive/templates/billing-subscription-canceled.ts +1 -5
- package/src/proactive/templates/billing-suspended.ts +1 -0
- package/src/proactive/templates/morning-brief.ts +1 -0
- package/src/proactive/templates/org-became-shared.ts +4 -3
- package/src/proactive/templates/source-sync-review-ready.ts +3 -8
- package/src/proactive/templates/workflow-needs-attention.ts +1 -0
- package/src/rate-limit/README.md +27 -1
- package/src/rate-limit/__tests__/README.md +23 -0
- package/src/rate-limit/__tests__/rate-limited-error.test.ts +53 -0
- package/src/rate-limit/index.ts +33 -0
- package/src/root-exports/agent-runtime.ts +14 -2
- package/src/root-exports/delivery.ts +15 -0
package/src/billing/refusals.ts
CHANGED
|
@@ -40,6 +40,11 @@
|
|
|
40
40
|
*/
|
|
41
41
|
import { z } from "zod";
|
|
42
42
|
|
|
43
|
+
import {
|
|
44
|
+
AffordanceSchema,
|
|
45
|
+
MAX_OFFERED_AFFORDANCES,
|
|
46
|
+
} from "../affordances/affordance";
|
|
47
|
+
|
|
43
48
|
import { type AiPauseReason } from "./vocabulary";
|
|
44
49
|
|
|
45
50
|
/* -------------------------------------------------------------------------- */
|
|
@@ -216,5 +221,12 @@ export const BillingRefusalPartSchema = z
|
|
|
216
221
|
orgId: z.string(),
|
|
217
222
|
/** Whether retrying the same turn could succeed once the cause clears. */
|
|
218
223
|
retryable: z.boolean(),
|
|
224
|
+
/**
|
|
225
|
+
* The catalog affordances that resolve the refusal for this viewer (control
|
|
226
|
+
* ADR affordance-system), e.g. billing.open. Present only when non-empty;
|
|
227
|
+
* the app renders them as controls beside the code's fact.
|
|
228
|
+
*/
|
|
229
|
+
// prettier-ignore
|
|
230
|
+
affordances: z.array(AffordanceSchema).min(1).max(MAX_OFFERED_AFFORDANCES).optional(),
|
|
219
231
|
})
|
|
220
232
|
.strict();
|
|
@@ -17,6 +17,7 @@ Canonical vocabulary for structured assistant message output. Defines the type s
|
|
|
17
17
|
- `SuggestedRepliesPart` is a surface but NOT a governed one: a chip fires an ordinary user turn (no `executionId`, no `submitEndpoint`), is stateless (clicking never consumes it) and advisory (the composer stays enabled) — so it is outside the at-most-one-governed-surface-per-turn rule
|
|
18
18
|
- `WorkflowProgressPart` is the second non-governed surface: a two-field ANCHOR (`workflowId`, `workflowType`) to a durable background workflow. It presents no decision, carries no result snapshot (minted at t=0; parts are never rewritten) and no `startedAt` (operational state stays off immutable content), and names no domain vocabulary — live state is hydrated from the generic workflow projection read
|
|
19
19
|
- `InputRequestPart` (`data-input-request` on the wire) is the GENERIC, GOVERNED anchor for a reviewed request: `{ requestType, requestId, stepCount, withheldCount }`, with every invariant of the structure-review anchor (anchor only — no cards, no endpoints, no staleness hashes; counts against the at-most-one-governed-surface-per-turn rule; emission is orchestration-driven). `isInputRequestDataPartShape` checks `requestType` as a non-empty string, NOT as a member of `INPUT_REQUEST_TYPES`: an unknown type renders nothing but never invalidates a persisted part
|
|
20
|
+
- `AffordanceOfferPart` (`data-affordance-offer` on the wire) is the fourth non-governed surface: the next steps a pushed chat OFFERS, frozen with it as `OfferedAffordance` instances (control ADR `affordance-system`). Ids and args only — never labels, hrefs or a projected id (`review.open`), at most `MAX_OFFERED_AFFORDANCES`; written once with its message and never re-derived; the app renders each as a real control, re-gated per viewer. Built by `WireSurfaceBuilder.affordanceOffer`; emission is orchestration-driven (the proactive chat writer), never a model choice
|
|
20
21
|
- `StructureReviewPart` (`data-structure-review`) is FROZEN, not retired: it stays in `SurfacePart` and parses forever because persisted transcripts carry it, and org-structure keeps emitting it
|
|
21
22
|
|
|
22
23
|
<!-- BEGIN GENERATED: readme-public-api — derived from code by `pnpm readme-api`. Do not edit. -->
|
|
@@ -24,6 +25,9 @@ Canonical vocabulary for structured assistant message output. Defines the type s
|
|
|
24
25
|
## Public API
|
|
25
26
|
|
|
26
27
|
- `AddPartResult` _(type)_ — Result of adding a part to the builder.
|
|
28
|
+
- `AffordanceOfferData` _(type)_ — Affordance offer data payload: the frozen, ordered offer.
|
|
29
|
+
- `AffordanceOfferDataPart` _(type)_ — Affordance offer data part (wire format, as persisted in `chat_messages.parts`).
|
|
30
|
+
- `AffordanceOfferPart` _(type)_ — Affordance offer message part (semantic type, as rendered).
|
|
27
31
|
- `AssistantMessagePart` _(type)_ — All assistant message part types.
|
|
28
32
|
- `CONFIRMATION_LABELS` — Human-readable confirmation dialog titles for each execution kind.
|
|
29
33
|
- `CandidateSource` _(type)_ — How the frontend sources candidate values for an editable field. - `inline`: the options are shipped with the…
|
|
@@ -116,6 +120,7 @@ Canonical vocabulary for structured assistant message output. Defines the type s
|
|
|
116
120
|
|
|
117
121
|
**Internal domains:**
|
|
118
122
|
|
|
123
|
+
- `affordances`
|
|
119
124
|
- `chat`
|
|
120
125
|
- `execution`
|
|
121
126
|
- `input-requests`
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Affordance Offer Part Types
|
|
3
|
+
*
|
|
4
|
+
* The next steps a message OFFERS, frozen with it (control ADR
|
|
5
|
+
* affordance-system). A pushed proactive chat carries its composer's
|
|
6
|
+
* `affordances` here so the app renders them as real controls — re-gated per
|
|
7
|
+
* viewer at render, live availability read separately, executed only by the
|
|
8
|
+
* server — instead of as chips that merely send a prompt.
|
|
9
|
+
*
|
|
10
|
+
* AFFORDANCE OFFER INVARIANTS:
|
|
11
|
+
* - OFFERED INSTANCES ONLY. Ids and their args, never labels or hrefs (the app
|
|
12
|
+
* owns both), never a projected id (`review.open`), at most
|
|
13
|
+
* MAX_OFFERED_AFFORDANCES.
|
|
14
|
+
* - WRITTEN ONCE, with the message it belongs to; never re-derived.
|
|
15
|
+
* - SHARE-SAFE. Ids and opaque target ids only.
|
|
16
|
+
* - NON-GOVERNED. It presents no decision and consumes no surface budget.
|
|
17
|
+
* - Emission is orchestration-driven (the proactive chat writer), never a model
|
|
18
|
+
* choice.
|
|
19
|
+
*
|
|
20
|
+
* Purely additive: an app that predates this part normalizes unknown part
|
|
21
|
+
* types to null and drops it.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import type { OfferedAffordance } from "../affordances/affordance";
|
|
25
|
+
|
|
26
|
+
/** Affordance offer data payload: the frozen, ordered offer. */
|
|
27
|
+
export interface AffordanceOfferData {
|
|
28
|
+
affordances: readonly OfferedAffordance[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Affordance offer message part (semantic type, as rendered). */
|
|
32
|
+
export interface AffordanceOfferPart {
|
|
33
|
+
type: "affordance-offer";
|
|
34
|
+
data: AffordanceOfferData;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Affordance offer data part (wire format, as persisted in
|
|
39
|
+
* `chat_messages.parts`). Uses AI SDK's data-{name} convention.
|
|
40
|
+
*/
|
|
41
|
+
export interface AffordanceOfferDataPart {
|
|
42
|
+
type: "data-affordance-offer";
|
|
43
|
+
data: AffordanceOfferData;
|
|
44
|
+
}
|
|
@@ -97,6 +97,13 @@ export type {
|
|
|
97
97
|
WorkflowOutcomeDataPart,
|
|
98
98
|
} from "./workflow-outcome";
|
|
99
99
|
|
|
100
|
+
// Affordance offer types (non-governed, the frozen offer on a pushed chat)
|
|
101
|
+
export type {
|
|
102
|
+
AffordanceOfferData,
|
|
103
|
+
AffordanceOfferPart,
|
|
104
|
+
AffordanceOfferDataPart,
|
|
105
|
+
} from "./affordance-offer";
|
|
106
|
+
|
|
100
107
|
// Execution result types
|
|
101
108
|
export type {
|
|
102
109
|
ExecutionArtifactStatus,
|
|
@@ -22,6 +22,7 @@ import type { InputRequestPart } from "./input-request";
|
|
|
22
22
|
import type { SuggestedRepliesPart } from "./suggested-replies";
|
|
23
23
|
import type { WorkflowProgressPart } from "./workflow-progress";
|
|
24
24
|
import type { WorkflowOutcomePart } from "./workflow-outcome";
|
|
25
|
+
import type { AffordanceOfferPart } from "./affordance-offer";
|
|
25
26
|
|
|
26
27
|
// =============================================================================
|
|
27
28
|
// Narrative Parts (Streamable)
|
|
@@ -130,6 +131,10 @@ export interface TablePart {
|
|
|
130
131
|
* workflow's execution epoch, carried on its settlement message. It presents no
|
|
131
132
|
* decision; a review disposition is delivered as its own InputRequestPart.
|
|
132
133
|
*
|
|
134
|
+
* AffordanceOfferPart is the fourth non-governed member: the next steps a pushed
|
|
135
|
+
* chat offers, frozen with it as affordance instances (control ADR
|
|
136
|
+
* affordance-system). It presents no decision and consumes no surface budget.
|
|
137
|
+
*
|
|
133
138
|
* InputRequestPart is GOVERNED, like StructureReviewPart: it anchors a reviewed
|
|
134
139
|
* request that asks the person to decide, so it counts against the
|
|
135
140
|
* at-most-one-governed-surface-per-turn budget. It is the generic anchor;
|
|
@@ -148,7 +153,8 @@ export type SurfacePart =
|
|
|
148
153
|
| InputRequestPart
|
|
149
154
|
| SuggestedRepliesPart
|
|
150
155
|
| WorkflowProgressPart
|
|
151
|
-
| WorkflowOutcomePart
|
|
156
|
+
| WorkflowOutcomePart
|
|
157
|
+
| AffordanceOfferPart;
|
|
152
158
|
|
|
153
159
|
/**
|
|
154
160
|
* All assistant message part types.
|
|
@@ -39,6 +39,10 @@ import type {
|
|
|
39
39
|
WorkflowOutcomeData,
|
|
40
40
|
WorkflowOutcomeDataPart,
|
|
41
41
|
} from "./workflow-outcome";
|
|
42
|
+
import type {
|
|
43
|
+
AffordanceOfferData,
|
|
44
|
+
AffordanceOfferDataPart,
|
|
45
|
+
} from "./affordance-offer";
|
|
42
46
|
import type {
|
|
43
47
|
ExecutionResultData,
|
|
44
48
|
ExecutionResultDataPart,
|
|
@@ -272,6 +276,20 @@ export const WireSurfaceBuilder = {
|
|
|
272
276
|
};
|
|
273
277
|
},
|
|
274
278
|
|
|
279
|
+
/**
|
|
280
|
+
* Build an affordance-offer data part: the frozen next steps a pushed chat
|
|
281
|
+
* offers (control ADR affordance-system). Non-governed; ids and args only.
|
|
282
|
+
*
|
|
283
|
+
* @param data - The offered affordance instances, in the producer's order
|
|
284
|
+
* @returns Wire-format affordance offer part
|
|
285
|
+
*/
|
|
286
|
+
affordanceOffer(data: AffordanceOfferData): AffordanceOfferDataPart {
|
|
287
|
+
return {
|
|
288
|
+
type: "data-affordance-offer",
|
|
289
|
+
data,
|
|
290
|
+
};
|
|
291
|
+
},
|
|
292
|
+
|
|
275
293
|
/**
|
|
276
294
|
* Build an execution result data part for streaming.
|
|
277
295
|
* Reports the outcome of an executed action.
|
|
@@ -131,9 +131,23 @@ deliberately (ADR-CONTRACTS-086):
|
|
|
131
131
|
`chat.mention`: a kind is composable prose for a caller with an explicit
|
|
132
132
|
trigger, and naming it on a plan would make the proactive pipeline send it
|
|
133
133
|
autonomously — notify/'s INV-1 to grant, not this union's.
|
|
134
|
-
- This domain depends on
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
- This domain depends on nothing outside itself except one **type-only** import:
|
|
135
|
+
`content.ts` reads `OfferedAffordance` from `../affordances/affordance` for
|
|
136
|
+
`CallToAction.affordance`. The `notifications → email` imports that existed
|
|
137
|
+
while both layers did are gone with the directory, and nothing may reintroduce
|
|
138
|
+
one.
|
|
139
|
+
- `CallToAction.affordance` names the catalog affordance a link performs
|
|
140
|
+
(control ADR affordance-system; contracts ADR
|
|
141
|
+
affordance-catalog-wave-and-grounded-ctas). It is present exactly when the
|
|
142
|
+
link does what a catalog id names — the three `billing.*` kinds
|
|
143
|
+
(`billing.open`), `chat.shared` (`chat.open` with the payload's `chatId`),
|
|
144
|
+
`companyMd.access_request_approved` (`companyMd.openDoc` with `docId`) and
|
|
145
|
+
`org.unit_owner_granted` (`team.open` with `unitId`) — and the label line
|
|
146
|
+
carries `// cta-grounded: <id>`. It is absent for an OTP code (no `href`) and
|
|
147
|
+
for a link no catalog id names (an invite acceptance, a comment). Renderers
|
|
148
|
+
ignore it and draw the link as before. Body prose around a call to action
|
|
149
|
+
states facts and never tells the reader what to do; the `cta-prose-ratchet`
|
|
150
|
+
guard and `__tests__/grounded-ctas.test.ts` hold that line.
|
|
137
151
|
- A kind IS its definition. Every `NotificationKind` has facts in `payloads.ts`
|
|
138
152
|
and a `compose` in `kinds/`, both compiler-enforced via `NotificationPayloads[K]`.
|
|
139
153
|
- Capability belongs to the **renderer** (`Renderer.supports`), never to an
|
|
@@ -13,6 +13,10 @@ to.
|
|
|
13
13
|
- `render-snapshot.test.ts` + `__snapshots__/` — those fixtures rendered through
|
|
14
14
|
the email channel, as vitest snapshots. This is the fence: **a diff here means
|
|
15
15
|
a real sent email changed.**
|
|
16
|
+
- `grounded-ctas.test.ts` — a linked call to action whose link a catalog id
|
|
17
|
+
names carries that affordance, targeting the fixture payload's own id; a code
|
|
18
|
+
to copy never carries one; and no grounded or share notification body tells
|
|
19
|
+
the reader what to do (control ADR affordance-system).
|
|
16
20
|
|
|
17
21
|
## The retired output-parity golden (ADR-CONTRACTS-087)
|
|
18
22
|
|
|
@@ -552,7 +552,7 @@ exports[`email render > billing.auto_recharge_failed · Default 1`] = `
|
|
|
552
552
|
<p class="csr-p-normal">Acme Corp tried to top up its AI balance automatically and the charge was not completed.</p>
|
|
553
553
|
<p class="csr-p-tight">Workspace: Acme Corp</p>
|
|
554
554
|
<p class="csr-p-normal">Status: Top-up failed</p>
|
|
555
|
-
<p class="csr-p-normal">AI work carries on until the remaining balance is spent, and then pauses. The next attempt is not immediate — the system waits
|
|
555
|
+
<p class="csr-p-normal">AI work carries on until the remaining balance is spent, and then pauses. The next attempt is not immediate — the system waits before charging the same card a second time — so updating the payment method is what resolves this.</p>
|
|
556
556
|
<table cellpadding="0" cellspacing="0" border="0" class="cs-cta cs-cta-hover csr-cta-box-normal">
|
|
557
557
|
<tr><td class="csr-cta-cell-linked">
|
|
558
558
|
<a href="https://app.companysemantics.ai/org/billing" class="cs-link csr-cta-anchor">>> <span class="csr-cta-label">OPEN BILLING</span> <<</a>
|
|
@@ -588,7 +588,7 @@ body { background-color: #1a1a1a !important; color: #e8e8e8 !important; }
|
|
|
588
588
|
<p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0 0 20px 0;">Acme Corp tried to top up its AI balance automatically and the charge was not completed.</p>
|
|
589
589
|
<p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0 0 4px 0;">Workspace: Acme Corp</p>
|
|
590
590
|
<p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0 0 20px 0;">Status: Top-up failed</p>
|
|
591
|
-
<p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0 0 20px 0;">AI work carries on until the remaining balance is spent, and then pauses. The next attempt is not immediate — the system waits
|
|
591
|
+
<p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0 0 20px 0;">AI work carries on until the remaining balance is spent, and then pauses. The next attempt is not immediate — the system waits before charging the same card a second time — so updating the payment method is what resolves this.</p>
|
|
592
592
|
<table cellpadding="0" cellspacing="0" border="0" class="cs-cta cs-cta-hover" style="display: inline-block; border: 1px solid #0047FF; border-radius: 2px; margin: 0 0 20px 0; max-width: 220px;">
|
|
593
593
|
<tr><td style="text-align: center; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px;">
|
|
594
594
|
<a href="https://app.companysemantics.ai/org/billing" class="cs-link" style="display: block; padding: 16px 24px; color: #0047FF; text-decoration: none;">>> <span style="text-decoration: underline;">OPEN BILLING</span> <<</a>
|
|
@@ -610,8 +610,8 @@ Status: Top-up failed
|
|
|
610
610
|
|
|
611
611
|
AI work carries on until the remaining balance
|
|
612
612
|
is spent, and then pauses. The next attempt is
|
|
613
|
-
not immediate — the system waits
|
|
614
|
-
|
|
613
|
+
not immediate — the system waits before charging
|
|
614
|
+
the same card a second time — so updating the
|
|
615
615
|
payment method is what resolves this.
|
|
616
616
|
|
|
617
617
|
*------------------------*
|
|
@@ -1590,7 +1590,7 @@ exports[`email render > chat.shared · Long title 1`] = `
|
|
|
1590
1590
|
<body class="csr-body">
|
|
1591
1591
|
<div class="csr-frame">
|
|
1592
1592
|
<p class="csr-p-normal">Hi there,</p>
|
|
1593
|
-
<p class="csr-p-normal">A chat was shared with you
|
|
1593
|
+
<p class="csr-p-normal">A chat was shared with you, and you are a participant.</p>
|
|
1594
1594
|
<hr class="cs-faint csr-chat-rule-top">
|
|
1595
1595
|
<table cellpadding="0" cellspacing="0" border="0" width="100%" class="csr-chat-row-24">
|
|
1596
1596
|
<tr>
|
|
@@ -1654,7 +1654,7 @@ body { background-color: #1a1a1a !important; color: #e8e8e8 !important; }
|
|
|
1654
1654
|
<body style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; color: #1a1a1a; background-color: #ffffff; margin: 0; padding: 0;">
|
|
1655
1655
|
<div style="max-width: 520px;">
|
|
1656
1656
|
<p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0 0 20px 0;">Hi there,</p>
|
|
1657
|
-
<p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0 0 20px 0;">A chat was shared with you
|
|
1657
|
+
<p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0 0 20px 0;">A chat was shared with you, and you are a participant.</p>
|
|
1658
1658
|
<hr class="cs-faint" style="border: none; border-top: 1px solid #bbb; margin: 12px 0 24px 0;">
|
|
1659
1659
|
<table cellpadding="0" cellspacing="0" border="0" width="100%" style="margin: 0 0 24px 0;">
|
|
1660
1660
|
<tr>
|
|
@@ -1699,8 +1699,8 @@ body { background-color: #1a1a1a !important; color: #e8e8e8 !important; }
|
|
|
1699
1699
|
"subject": "A chat has been shared with you",
|
|
1700
1700
|
"text": "Hi there,
|
|
1701
1701
|
|
|
1702
|
-
A chat was shared with you
|
|
1703
|
-
|
|
1702
|
+
A chat was shared with you, and you are a
|
|
1703
|
+
participant.
|
|
1704
1704
|
|
|
1705
1705
|
────────────────────────────────────────────────
|
|
1706
1706
|
|
|
@@ -2909,7 +2909,7 @@ exports[`email render > companyMd.access_request_approved · Editor 1`] = `
|
|
|
2909
2909
|
<tr>
|
|
2910
2910
|
<td class="csr-chat-avatar-left">[c_S]</td>
|
|
2911
2911
|
<td class="csr-chat-channel-left"><table cellpadding="0" cellspacing="0" border="0" class="csr-chat-bubble-wrap">
|
|
2912
|
-
<tr><td class="cs-bubble csr-bubble-assistant">
|
|
2912
|
+
<tr><td class="cs-bubble csr-bubble-assistant">The document is ready for you.</td></tr>
|
|
2913
2913
|
</table></td>
|
|
2914
2914
|
<td class="csr-chat-avatar-right-hidden">(•̀_ರ╮)</td>
|
|
2915
2915
|
</tr>
|
|
@@ -2962,7 +2962,7 @@ body { background-color: #1a1a1a !important; color: #e8e8e8 !important; }
|
|
|
2962
2962
|
<tr>
|
|
2963
2963
|
<td style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; padding-right: 8px; vertical-align: bottom; white-space: nowrap;">[c_S]</td>
|
|
2964
2964
|
<td style="width: 100%; text-align: left;"><table cellpadding="0" cellspacing="0" border="0" style="display: inline-block; max-width: 100%; vertical-align: bottom;">
|
|
2965
|
-
<tr><td class="cs-bubble" style="border-radius: 8px 8px 8px 0; padding: 10px 14px; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; color: #ffffff; background: #666;">
|
|
2965
|
+
<tr><td class="cs-bubble" style="border-radius: 8px 8px 8px 0; padding: 10px 14px; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; color: #ffffff; background: #666;">The document is ready for you.</td></tr>
|
|
2966
2966
|
</table></td>
|
|
2967
2967
|
<td style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; padding-left: 8px; vertical-align: bottom; visibility: hidden; white-space: nowrap;">(•̀_ರ╮)</td>
|
|
2968
2968
|
</tr>
|
|
@@ -2995,7 +2995,8 @@ Your access request was approved.
|
|
|
2995
2995
|
────────────────────────────────────────────────
|
|
2996
2996
|
|
|
2997
2997
|
┌──────────────────────────────┐
|
|
2998
|
-
|
|
2998
|
+
│ The document is ready for │
|
|
2999
|
+
[c_S] │ you. │
|
|
2999
3000
|
└──────────────────────────────┘
|
|
3000
3001
|
|
|
3001
3002
|
*----------------*
|
|
@@ -3257,7 +3258,7 @@ exports[`email render > companyMd.access_request_approved · Viewer 1`] = `
|
|
|
3257
3258
|
<tr>
|
|
3258
3259
|
<td class="csr-chat-avatar-left">[c_S]</td>
|
|
3259
3260
|
<td class="csr-chat-channel-left"><table cellpadding="0" cellspacing="0" border="0" class="csr-chat-bubble-wrap">
|
|
3260
|
-
<tr><td class="cs-bubble csr-bubble-assistant">
|
|
3261
|
+
<tr><td class="cs-bubble csr-bubble-assistant">The document is ready for you.</td></tr>
|
|
3261
3262
|
</table></td>
|
|
3262
3263
|
<td class="csr-chat-avatar-right-hidden">(•̀_ರ╮)</td>
|
|
3263
3264
|
</tr>
|
|
@@ -3310,7 +3311,7 @@ body { background-color: #1a1a1a !important; color: #e8e8e8 !important; }
|
|
|
3310
3311
|
<tr>
|
|
3311
3312
|
<td style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; padding-right: 8px; vertical-align: bottom; white-space: nowrap;">[c_S]</td>
|
|
3312
3313
|
<td style="width: 100%; text-align: left;"><table cellpadding="0" cellspacing="0" border="0" style="display: inline-block; max-width: 100%; vertical-align: bottom;">
|
|
3313
|
-
<tr><td class="cs-bubble" style="border-radius: 8px 8px 8px 0; padding: 10px 14px; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; color: #ffffff; background: #666;">
|
|
3314
|
+
<tr><td class="cs-bubble" style="border-radius: 8px 8px 8px 0; padding: 10px 14px; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; color: #ffffff; background: #666;">The document is ready for you.</td></tr>
|
|
3314
3315
|
</table></td>
|
|
3315
3316
|
<td style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; padding-left: 8px; vertical-align: bottom; visibility: hidden; white-space: nowrap;">(•̀_ರ╮)</td>
|
|
3316
3317
|
</tr>
|
|
@@ -3343,7 +3344,8 @@ Your access request was approved.
|
|
|
3343
3344
|
────────────────────────────────────────────────
|
|
3344
3345
|
|
|
3345
3346
|
┌──────────────────────────────┐
|
|
3346
|
-
|
|
3347
|
+
│ The document is ready for │
|
|
3348
|
+
[c_S] │ you. │
|
|
3347
3349
|
└──────────────────────────────┘
|
|
3348
3350
|
|
|
3349
3351
|
*----------------*
|
|
@@ -5059,7 +5061,7 @@ exports[`email render > org.unit_owner_granted · Delegate with expiry 1`] = `
|
|
|
5059
5061
|
<tr>
|
|
5060
5062
|
<td class="csr-chat-avatar-left">[c_S]</td>
|
|
5061
5063
|
<td class="csr-chat-channel-left"><table cellpadding="0" cellspacing="0" border="0" class="csr-chat-bubble-wrap">
|
|
5062
|
-
<tr><td class="cs-bubble csr-bubble-assistant">
|
|
5064
|
+
<tr><td class="cs-bubble csr-bubble-assistant">Platform Engineering is ready for you.</td></tr>
|
|
5063
5065
|
</table></td>
|
|
5064
5066
|
<td class="csr-chat-avatar-right-hidden">(•̀_ರ╮)</td>
|
|
5065
5067
|
</tr>
|
|
@@ -5113,7 +5115,7 @@ body { background-color: #1a1a1a !important; color: #e8e8e8 !important; }
|
|
|
5113
5115
|
<tr>
|
|
5114
5116
|
<td style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; padding-right: 8px; vertical-align: bottom; white-space: nowrap;">[c_S]</td>
|
|
5115
5117
|
<td style="width: 100%; text-align: left;"><table cellpadding="0" cellspacing="0" border="0" style="display: inline-block; max-width: 100%; vertical-align: bottom;">
|
|
5116
|
-
<tr><td class="cs-bubble" style="border-radius: 8px 8px 8px 0; padding: 10px 14px; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; color: #ffffff; background: #666;">
|
|
5118
|
+
<tr><td class="cs-bubble" style="border-radius: 8px 8px 8px 0; padding: 10px 14px; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; color: #ffffff; background: #666;">Platform Engineering is ready for you.</td></tr>
|
|
5117
5119
|
</table></td>
|
|
5118
5120
|
<td style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; padding-left: 8px; vertical-align: bottom; visibility: hidden; white-space: nowrap;">(•̀_ರ╮)</td>
|
|
5119
5121
|
</tr>
|
|
@@ -5150,8 +5152,8 @@ strategy, and execution against goals.
|
|
|
5150
5152
|
────────────────────────────────────────────────
|
|
5151
5153
|
|
|
5152
5154
|
┌──────────────────────────────┐
|
|
5153
|
-
│
|
|
5154
|
-
[c_S] │
|
|
5155
|
+
│ Platform Engineering is │
|
|
5156
|
+
[c_S] │ ready for you. │
|
|
5155
5157
|
└──────────────────────────────┘
|
|
5156
5158
|
|
|
5157
5159
|
*-----------------------*
|
|
@@ -5249,7 +5251,7 @@ exports[`email render > org.unit_owner_granted · Unit owner + message 1`] = `
|
|
|
5249
5251
|
<tr>
|
|
5250
5252
|
<td class="csr-chat-avatar-left">[c_S]</td>
|
|
5251
5253
|
<td class="csr-chat-channel-left"><table cellpadding="0" cellspacing="0" border="0" class="csr-chat-bubble-wrap">
|
|
5252
|
-
<tr><td class="cs-bubble csr-bubble-assistant">
|
|
5254
|
+
<tr><td class="cs-bubble csr-bubble-assistant">Platform Engineering is ready for you.</td></tr>
|
|
5253
5255
|
</table></td>
|
|
5254
5256
|
<td class="csr-chat-avatar-right-hidden">(•̀_ರ╮)</td>
|
|
5255
5257
|
</tr>
|
|
@@ -5316,7 +5318,7 @@ body { background-color: #1a1a1a !important; color: #e8e8e8 !important; }
|
|
|
5316
5318
|
<tr>
|
|
5317
5319
|
<td style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; padding-right: 8px; vertical-align: bottom; white-space: nowrap;">[c_S]</td>
|
|
5318
5320
|
<td style="width: 100%; text-align: left;"><table cellpadding="0" cellspacing="0" border="0" style="display: inline-block; max-width: 100%; vertical-align: bottom;">
|
|
5319
|
-
<tr><td class="cs-bubble" style="border-radius: 8px 8px 8px 0; padding: 10px 14px; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; color: #ffffff; background: #666;">
|
|
5321
|
+
<tr><td class="cs-bubble" style="border-radius: 8px 8px 8px 0; padding: 10px 14px; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; color: #ffffff; background: #666;">Platform Engineering is ready for you.</td></tr>
|
|
5320
5322
|
</table></td>
|
|
5321
5323
|
<td style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; padding-left: 8px; vertical-align: bottom; visibility: hidden; white-space: nowrap;">(•̀_ರ╮)</td>
|
|
5322
5324
|
</tr>
|
|
@@ -5358,8 +5360,8 @@ strategy, and execution against goals.
|
|
|
5358
5360
|
Jordan Lee
|
|
5359
5361
|
|
|
5360
5362
|
┌──────────────────────────────┐
|
|
5361
|
-
│
|
|
5362
|
-
[c_S] │
|
|
5363
|
+
│ Platform Engineering is │
|
|
5364
|
+
[c_S] │ ready for you. │
|
|
5363
5365
|
└──────────────────────────────┘
|
|
5364
5366
|
|
|
5365
5367
|
*-----------------------*
|
|
@@ -5853,7 +5855,7 @@ exports[`email render > share.granted · Meeting · viewer · no title 1`] = `
|
|
|
5853
5855
|
<tr>
|
|
5854
5856
|
<td class="csr-chat-avatar-left">[c_S]</td>
|
|
5855
5857
|
<td class="csr-chat-channel-left"><table cellpadding="0" cellspacing="0" border="0" class="csr-chat-bubble-wrap">
|
|
5856
|
-
<tr><td class="cs-bubble csr-bubble-assistant">
|
|
5858
|
+
<tr><td class="cs-bubble csr-bubble-assistant">It is ready for you.</td></tr>
|
|
5857
5859
|
</table></td>
|
|
5858
5860
|
<td class="csr-chat-avatar-right-hidden">(•̀_ರ╮)</td>
|
|
5859
5861
|
</tr>
|
|
@@ -5905,7 +5907,7 @@ body { background-color: #1a1a1a !important; color: #e8e8e8 !important; }
|
|
|
5905
5907
|
<tr>
|
|
5906
5908
|
<td style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; padding-right: 8px; vertical-align: bottom; white-space: nowrap;">[c_S]</td>
|
|
5907
5909
|
<td style="width: 100%; text-align: left;"><table cellpadding="0" cellspacing="0" border="0" style="display: inline-block; max-width: 100%; vertical-align: bottom;">
|
|
5908
|
-
<tr><td class="cs-bubble" style="border-radius: 8px 8px 8px 0; padding: 10px 14px; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; color: #ffffff; background: #666;">
|
|
5910
|
+
<tr><td class="cs-bubble" style="border-radius: 8px 8px 8px 0; padding: 10px 14px; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; color: #ffffff; background: #666;">It is ready for you.</td></tr>
|
|
5909
5911
|
</table></td>
|
|
5910
5912
|
<td style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; padding-left: 8px; vertical-align: bottom; visibility: hidden; white-space: nowrap;">(•̀_ರ╮)</td>
|
|
5911
5913
|
</tr>
|
|
@@ -5937,7 +5939,7 @@ A meeting was shared with you.
|
|
|
5937
5939
|
────────────────────────────────────────────────
|
|
5938
5940
|
|
|
5939
5941
|
┌──────────────────────────────┐
|
|
5940
|
-
[c_S] │
|
|
5942
|
+
[c_S] │ It is ready for you. │
|
|
5941
5943
|
└──────────────────────────────┘
|
|
5942
5944
|
|
|
5943
5945
|
*----------------*
|
|
@@ -102,6 +102,7 @@ add("org.unit_owner_granted", "Unit owner + message", {
|
|
|
102
102
|
unitName: "Platform Engineering",
|
|
103
103
|
roleLabel: "Unit owner",
|
|
104
104
|
ctaUrl: `${APP}/org/unit/42`,
|
|
105
|
+
unitId: "unit_42",
|
|
105
106
|
message: "Welcome aboard — glad to have you owning this team.",
|
|
106
107
|
});
|
|
107
108
|
add("org.unit_owner_granted", "Delegate with expiry", {
|
|
@@ -110,6 +111,7 @@ add("org.unit_owner_granted", "Delegate with expiry", {
|
|
|
110
111
|
unitName: "Platform Engineering",
|
|
111
112
|
roleLabel: "Delegate",
|
|
112
113
|
ctaUrl: `${APP}/org/unit/42`,
|
|
114
|
+
unitId: "unit_42",
|
|
113
115
|
expiresAt: "2026-06-13T00:00:00.000Z",
|
|
114
116
|
});
|
|
115
117
|
|
|
@@ -144,6 +146,7 @@ add("security.alert", "Unusual login location", {
|
|
|
144
146
|
|
|
145
147
|
const CHAT_BASE = {
|
|
146
148
|
sharedByName: "Alex Rivera",
|
|
149
|
+
chatId: "chat_7f3a",
|
|
147
150
|
access: "read",
|
|
148
151
|
} as const;
|
|
149
152
|
add("chat.shared", "Default", {
|
|
@@ -220,14 +223,16 @@ add("companyMd.access_request_approved", "Editor", {
|
|
|
220
223
|
docTitle: "Engineering Handbook",
|
|
221
224
|
accessLevel: "editor",
|
|
222
225
|
docUrl: `${APP}/doc/handbook`,
|
|
226
|
+
docId: "node_42",
|
|
223
227
|
});
|
|
224
|
-
// The approver's note replaces the "
|
|
228
|
+
// The approver's note replaces the "The document is ready for you." line rather than stacking
|
|
225
229
|
// on it — the branch this variant exists to pin.
|
|
226
230
|
add("companyMd.access_request_approved", "Editor with message", {
|
|
227
231
|
approverName: "Jordan Lee",
|
|
228
232
|
docTitle: "Engineering Handbook",
|
|
229
233
|
accessLevel: "editor",
|
|
230
234
|
docUrl: `${APP}/doc/handbook`,
|
|
235
|
+
docId: "node_42",
|
|
231
236
|
message: "Welcome aboard — start with the onboarding section.",
|
|
232
237
|
});
|
|
233
238
|
add("companyMd.access_request_approved", "Viewer", {
|
|
@@ -235,6 +240,7 @@ add("companyMd.access_request_approved", "Viewer", {
|
|
|
235
240
|
docTitle: "Engineering Handbook",
|
|
236
241
|
accessLevel: "viewer",
|
|
237
242
|
docUrl: `${APP}/doc/handbook`,
|
|
243
|
+
docId: "node_42",
|
|
238
244
|
});
|
|
239
245
|
|
|
240
246
|
add("companyMd.access_request_denied", "With reason", {
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
CallToAction,
|
|
5
|
+
NotificationContent,
|
|
6
|
+
NotificationElement,
|
|
7
|
+
} from "../content";
|
|
8
|
+
import { createRenderContext } from "../context";
|
|
9
|
+
import type { NotificationKind } from "../kinds";
|
|
10
|
+
import { NOTIFICATION_DEFINITIONS } from "../registry";
|
|
11
|
+
import { NOTIFICATION_FIXTURES } from "./fixtures";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A notification link that does what a catalog id names carries that
|
|
15
|
+
* affordance, and no notification prose tells the reader what to do
|
|
16
|
+
* (control ADR affordance-system; PRD-01137).
|
|
17
|
+
*/
|
|
18
|
+
const CALL_TO_ACTION =
|
|
19
|
+
/\byou can\b|\btry\b|\bagain\b|\bsettings\b|\breconnect\b|\badd funds\b|\bclick\b|\bgo to\b|\bopen\b/i;
|
|
20
|
+
const CONTEXT = createRenderContext({ brand: { copyrightYear: 2026 } });
|
|
21
|
+
|
|
22
|
+
function composeFixture(
|
|
23
|
+
kind: NotificationKind,
|
|
24
|
+
payload: unknown,
|
|
25
|
+
): NotificationContent {
|
|
26
|
+
const compose = NOTIFICATION_DEFINITIONS[kind].compose as (
|
|
27
|
+
p: unknown,
|
|
28
|
+
c: typeof CONTEXT,
|
|
29
|
+
) => NotificationContent;
|
|
30
|
+
return compose(payload, CONTEXT);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function elements(content: NotificationContent): NotificationElement[] {
|
|
34
|
+
const out: NotificationElement[] = [];
|
|
35
|
+
for (const section of content.sections) {
|
|
36
|
+
for (const element of section.elements) {
|
|
37
|
+
out.push(element);
|
|
38
|
+
if (element.type === "chatUnit")
|
|
39
|
+
out.push(...(element.items as unknown as NotificationElement[]));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const ctas = (content: NotificationContent) =>
|
|
46
|
+
elements(content).filter((e): e is CallToAction => e.type === "callToAction");
|
|
47
|
+
|
|
48
|
+
const GROUNDED: Partial<
|
|
49
|
+
Record<NotificationKind, (payload: Record<string, unknown>) => unknown>
|
|
50
|
+
> = {
|
|
51
|
+
"billing.auto_recharge_failed": () => ({ id: "billing.open" }),
|
|
52
|
+
"billing.payment_past_due": () => ({ id: "billing.open" }),
|
|
53
|
+
"billing.suspended": () => ({ id: "billing.open" }),
|
|
54
|
+
"chat.shared": (p) => ({ id: "chat.open", args: { chatId: p.chatId } }),
|
|
55
|
+
"companyMd.access_request_approved": (p) => ({
|
|
56
|
+
id: "companyMd.openDoc",
|
|
57
|
+
args: { docId: p.docId },
|
|
58
|
+
}),
|
|
59
|
+
"org.unit_owner_granted": (p) => ({
|
|
60
|
+
id: "team.open",
|
|
61
|
+
args: { unitId: p.unitId },
|
|
62
|
+
}),
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
describe("grounded notification calls to action", () => {
|
|
66
|
+
it("a linked call to action whose link a catalog id names carries that affordance, targeting the payload's own id", () => {
|
|
67
|
+
for (const fixture of NOTIFICATION_FIXTURES) {
|
|
68
|
+
const expected = GROUNDED[fixture.kind];
|
|
69
|
+
if (!expected) continue;
|
|
70
|
+
const linked = ctas(composeFixture(fixture.kind, fixture.payload)).filter(
|
|
71
|
+
(c) => c.href !== undefined,
|
|
72
|
+
);
|
|
73
|
+
expect(linked, fixture.name).toHaveLength(1);
|
|
74
|
+
expect(
|
|
75
|
+
linked[0]?.affordance,
|
|
76
|
+
`${fixture.kind} · ${fixture.name}`,
|
|
77
|
+
).toEqual(expected(fixture.payload as Record<string, unknown>));
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("a code to copy never carries an affordance", () => {
|
|
82
|
+
for (const fixture of NOTIFICATION_FIXTURES) {
|
|
83
|
+
for (const cta of ctas(composeFixture(fixture.kind, fixture.payload))) {
|
|
84
|
+
if (cta.href === undefined)
|
|
85
|
+
expect(cta.affordance, fixture.name).toBeUndefined();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("no grounded or share notification body tells the reader what to do", () => {
|
|
91
|
+
for (const fixture of NOTIFICATION_FIXTURES) {
|
|
92
|
+
if (!GROUNDED[fixture.kind] && fixture.kind !== "share.granted") continue;
|
|
93
|
+
for (const element of elements(
|
|
94
|
+
composeFixture(fixture.kind, fixture.payload),
|
|
95
|
+
)) {
|
|
96
|
+
if (element.type === "body")
|
|
97
|
+
expect(element.text, fixture.name).not.toMatch(CALL_TO_ACTION);
|
|
98
|
+
if (element.type === "message")
|
|
99
|
+
expect(element.text, fixture.name).not.toMatch(CALL_TO_ACTION);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
});
|
|
@@ -220,7 +220,7 @@ add("companyMd.access_request_approved", "Editor", {
|
|
|
220
220
|
accessLevel: "editor",
|
|
221
221
|
docUrl: `${APP}/doc/handbook`,
|
|
222
222
|
});
|
|
223
|
-
// The approver's note replaces the "
|
|
223
|
+
// The approver's note replaces the "The document is ready for you." line rather than stacking
|
|
224
224
|
// on it — the branch this variant exists to pin, across every channel.
|
|
225
225
|
add("companyMd.access_request_approved", "Editor with message", {
|
|
226
226
|
approverName: "Jordan Lee",
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
* security notice", not "render a red box".
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
+
import type { OfferedAffordance } from "../affordances/affordance";
|
|
27
|
+
|
|
26
28
|
// =============================================================================
|
|
27
29
|
// Shared value types
|
|
28
30
|
// =============================================================================
|
|
@@ -73,11 +75,18 @@ export interface KeyValueTable {
|
|
|
73
75
|
* The one action the notification is asking for. `href` absent means the label
|
|
74
76
|
* IS the payload (an OTP code), not a link — the renderer must not fabricate a
|
|
75
77
|
* destination for it.
|
|
78
|
+
*
|
|
79
|
+
* `affordance` names the catalog affordance the link performs (control ADR
|
|
80
|
+
* affordance-system): present exactly when the link does what a catalog id
|
|
81
|
+
* names, so the label is that affordance's label (`// cta-grounded: <id>` at
|
|
82
|
+
* the composer). Absent for an OTP code and for a link no catalog id names (an
|
|
83
|
+
* invite acceptance, a comment). Renderers draw the link as before.
|
|
76
84
|
*/
|
|
77
85
|
export interface CallToAction {
|
|
78
86
|
type: "callToAction";
|
|
79
87
|
label: string;
|
|
80
88
|
href?: string;
|
|
89
|
+
affordance?: OfferedAffordance;
|
|
81
90
|
}
|
|
82
91
|
|
|
83
92
|
/** A sequence of items. `ordered` when the sequence itself carries meaning. */
|
|
@@ -52,13 +52,15 @@ export const billingAutoRechargeFailedDefinition: NotificationDefinition<"billin
|
|
|
52
52
|
text:
|
|
53
53
|
"AI work carries on until the remaining balance is spent, " +
|
|
54
54
|
"and then pauses. The next attempt is not immediate — the " +
|
|
55
|
-
"system waits
|
|
56
|
-
"updating the payment method is what resolves this.",
|
|
55
|
+
"system waits before charging the same card a second time — " +
|
|
56
|
+
"so updating the payment method is what resolves this.",
|
|
57
57
|
},
|
|
58
58
|
{
|
|
59
59
|
type: "callToAction",
|
|
60
|
+
// cta-grounded: billing.open
|
|
60
61
|
label: "OPEN BILLING",
|
|
61
62
|
href: billingUrl,
|
|
63
|
+
affordance: { id: "billing.open" },
|
|
62
64
|
},
|
|
63
65
|
{
|
|
64
66
|
type: "notice",
|
|
@@ -59,8 +59,10 @@ export const billingPaymentPastDueDefinition: NotificationDefinition<"billing.pa
|
|
|
59
59
|
},
|
|
60
60
|
{
|
|
61
61
|
type: "callToAction",
|
|
62
|
+
// cta-grounded: billing.open
|
|
62
63
|
label: "OPEN BILLING",
|
|
63
64
|
href: billingUrl,
|
|
65
|
+
affordance: { id: "billing.open" },
|
|
64
66
|
},
|
|
65
67
|
{
|
|
66
68
|
type: "notice",
|