@company-semantics/contracts 58.0.0 → 58.2.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 +4 -4
- package/src/__tests__/resource-keys.test.ts +30 -0
- package/src/api/generated-spec-hash.ts +2 -2
- package/src/api/generated.ts +33 -1
- package/src/chat/README.md +15 -4
- package/src/chat/__tests__/proactive-kind.test.ts +51 -0
- package/src/chat/index.ts +9 -0
- package/src/chat/proactive-kind.ts +51 -0
- package/src/chat/schemas.ts +92 -1
- package/src/chat/types.ts +19 -1
- package/src/index.ts +110 -0
- package/src/message-parts/README.md +5 -0
- package/src/message-parts/__tests__/suggested-replies.test.ts +52 -0
- package/src/message-parts/__tests__/wire.test.ts +48 -0
- package/src/message-parts/index.ts +8 -0
- package/src/message-parts/suggested-replies.ts +48 -0
- package/src/message-parts/types.ts +7 -1
- package/src/message-parts/wire.ts +26 -0
- package/src/notifications/__tests__/__snapshots__/monospace-budget.test.ts.snap +1 -0
- package/src/notifications/__tests__/__snapshots__/registry.test.ts.snap +1 -0
- package/src/notifications/__tests__/__snapshots__/render-snapshot.test.ts.snap +207 -0
- package/src/notifications/__tests__/fixtures.ts +9 -0
- package/src/notifications/__tests__/org-invite.test.ts +75 -0
- package/src/notifications/__tests__/render-snapshot.test.ts +8 -0
- package/src/notifications/kinds/org-invite.ts +27 -12
- package/src/notifications/payloads.ts +7 -0
- package/src/org/README.md +38 -0
- package/src/org/__tests__/canonical-facts.test.ts +118 -0
- package/src/org/__tests__/structure-inference.test.ts +392 -0
- package/src/org/__tests__/structure-provenance.test.ts +187 -0
- package/src/org/canonical-facts.ts +94 -1
- package/src/org/index.ts +54 -0
- package/src/org/schemas.ts +23 -0
- package/src/org/structure-inference.ts +521 -0
- package/src/proactive/README.md +125 -0
- package/src/proactive/__tests__/README.md +56 -0
- package/src/proactive/__tests__/chat-templates.test.ts +167 -0
- package/src/proactive/__tests__/compile-fixtures.ts +110 -0
- package/src/proactive/__tests__/vocabulary.test.ts +279 -0
- package/src/proactive/classes.ts +125 -0
- package/src/proactive/composer.ts +104 -0
- package/src/proactive/facts.ts +87 -0
- package/src/proactive/index.ts +52 -0
- package/src/proactive/kinds.ts +127 -0
- package/src/proactive/plan.ts +79 -0
- package/src/proactive/registry.ts +71 -0
- package/src/proactive/surfaces.ts +59 -0
- package/src/proactive/templates/README.md +58 -0
- package/src/proactive/templates/index.ts +32 -0
- package/src/proactive/templates/morning-brief.ts +77 -0
- package/src/proactive/templates/org-became-shared.ts +54 -0
- package/src/resource-key-types.ts +9 -0
- package/src/resource-keys.ts +2 -0
- package/src/user-notifications/README.md +10 -0
- package/src/user-notifications/kinds.ts +28 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The IDS a surface needs — the vocabularies a proactive kind names INSTEAD of
|
|
3
|
+
* surface booleans (ADR-CONTRACTS-142).
|
|
4
|
+
*
|
|
5
|
+
* A kind never says `banner: true`; it says WHICH banner. This module holds
|
|
6
|
+
* the two id vocabularies the plan union in `./plan` draws on: the banner's
|
|
7
|
+
* `org_system_event_type` and the pushed-chat template. The durable inbox id
|
|
8
|
+
* is `UserNotificationKind` and lives in `../user-notifications`.
|
|
9
|
+
*
|
|
10
|
+
* INVARIANTS:
|
|
11
|
+
* - `ORG_SYSTEM_EVENT_TYPES` MIRRORS the backend `org_system_event_type`
|
|
12
|
+
* pgEnum exactly. Contracts cannot import the schema, so the mirror is the
|
|
13
|
+
* lock: a value here that the enum lacks is a banner nobody can write, and
|
|
14
|
+
* a value the enum has that is absent here is a banner no kind can name.
|
|
15
|
+
* Change both together.
|
|
16
|
+
* - A chat template earns membership by having a composer. The composer
|
|
17
|
+
* registry (a later module) is total over this array, so adding a template
|
|
18
|
+
* without prose is a compile error rather than an empty message.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
// =============================================================================
|
|
22
|
+
// OrgSystemEventType
|
|
23
|
+
// =============================================================================
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The banner types the org-level system-event table can record. Mirrors the
|
|
27
|
+
* backend `org_system_event_type` pgEnum value for value.
|
|
28
|
+
*
|
|
29
|
+
* `first_member_joined` is written AT MOST ONCE per org — a partial unique
|
|
30
|
+
* index makes the second write impossible — which is exactly why
|
|
31
|
+
* `org.became_shared` NAMES it rather than minting a third value. That kind
|
|
32
|
+
* fires at the personal-to-shared flip and nowhere else; a second value would
|
|
33
|
+
* put two banners on one transition.
|
|
34
|
+
*/
|
|
35
|
+
export const ORG_SYSTEM_EVENT_TYPES = [
|
|
36
|
+
/** Ownership of the org moved to a new owner. */
|
|
37
|
+
"ownership_transferred",
|
|
38
|
+
/** The first non-owner member joined — the org stopped being personal. */
|
|
39
|
+
"first_member_joined",
|
|
40
|
+
] as const;
|
|
41
|
+
export type OrgSystemEventType = (typeof ORG_SYSTEM_EVENT_TYPES)[number];
|
|
42
|
+
|
|
43
|
+
// =============================================================================
|
|
44
|
+
// ProactiveChatTemplate
|
|
45
|
+
// =============================================================================
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Which prose composes a kind's pushed chat. Template ids are camelCase, unlike
|
|
49
|
+
* the dotted kind strings, because a template is a NAME FOR PROSE and not a
|
|
50
|
+
* `{domain}.{type}` fact — one kind names one template, but the template's
|
|
51
|
+
* facts are its own and are not the kind's payload.
|
|
52
|
+
*/
|
|
53
|
+
export const PROACTIVE_CHAT_TEMPLATES = [
|
|
54
|
+
/** "Your org just became shared — here is what moved and why." */
|
|
55
|
+
"orgBecameShared",
|
|
56
|
+
/** "Good morning — here is where things stand for you today." */
|
|
57
|
+
"morningBrief",
|
|
58
|
+
] as const;
|
|
59
|
+
export type ProactiveChatTemplate = (typeof PROACTIVE_CHAT_TEMPLATES)[number];
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# proactive/templates/
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
One module per pushed-chat template (ADR-CONTRACTS-142). Each exports a single
|
|
6
|
+
`ProactiveChatComposer<T>` whose `compose(facts)` says what that pushed chat
|
|
7
|
+
says — its title, its prose and its chips — as content, never markup.
|
|
8
|
+
|
|
9
|
+
Copy lives here, in contracts, and not in the backend domain that writes the
|
|
10
|
+
chat. That is the precedent notify/'s README states for outbound notifications
|
|
11
|
+
(no templates/ directory in the sending domain; copy belongs to contracts'
|
|
12
|
+
`compose`), applied to the third surface.
|
|
13
|
+
|
|
14
|
+
`index.ts` is the registry: `PROACTIVE_CHAT_COMPOSERS`, total over
|
|
15
|
+
`PROACTIVE_CHAT_TEMPLATES` in both directions.
|
|
16
|
+
|
|
17
|
+
## Invariants
|
|
18
|
+
|
|
19
|
+
- `compose` is PURE and total: `(facts)` in, message out. No clock, no
|
|
20
|
+
environment, no I/O. The same facts produce a byte-identical message.
|
|
21
|
+
- `compose` emits CONTENT — never markup, styling, hrefs the app owns or a
|
|
22
|
+
channel name.
|
|
23
|
+
- **FROZEN AT WRITE TIME (INV-PROACTIVE-CONTENT).** The message is written once
|
|
24
|
+
into `chat_messages.content` and never re-renders, so a template may only
|
|
25
|
+
interpolate facts the recipient was entitled to at compose time. This is the
|
|
26
|
+
deliberate opposite of the durable-inbox rule, and both are correct: a
|
|
27
|
+
transcript that changed under the reader would be a lie about the
|
|
28
|
+
conversation.
|
|
29
|
+
- A template's `template` field equals its registry key. Enforced per entry by
|
|
30
|
+
the type and in practice by `../__tests__/chat-templates.test.ts`.
|
|
31
|
+
- At most `PROACTIVE_CHAT_MAX_REPLIES` chips. More is a menu, not a chip row.
|
|
32
|
+
- A file per template, named after it in kebab-case. Templates do not share a
|
|
33
|
+
module — the point is that a template is self-contained.
|
|
34
|
+
- `org-became-shared.ts` NAMES THE RELOCATION: the five sections that leave My
|
|
35
|
+
settings for Org settings. Unannounced, that reads as things going missing
|
|
36
|
+
(the app ADR with slug `nav-posture-opens-on-invite-send`).
|
|
37
|
+
- `morning-brief.ts` PERSONALISES THROUGH THE FACTS, never the occurrence. One
|
|
38
|
+
`brief.morning` occurrence per org per org-local day is shared by every
|
|
39
|
+
recipient; two people's briefs differ only because the `MorningBriefFacts`
|
|
40
|
+
they were handed differ. The composer does not know who it is composing for.
|
|
41
|
+
The org-local day arrives already rendered (`orgLocalDateLabel`) because
|
|
42
|
+
rendering it needs a locale and a time zone, and a pure composer reads
|
|
43
|
+
neither.
|
|
44
|
+
|
|
45
|
+
## Public API
|
|
46
|
+
|
|
47
|
+
| Export | Description |
|
|
48
|
+
| -------------------------- | --------------------------------------------------------------- |
|
|
49
|
+
| `PROACTIVE_CHAT_COMPOSERS` | The total template → composer registry |
|
|
50
|
+
| `orgBecameSharedComposer` | Prose for the personal-to-shared flip; names the relocation |
|
|
51
|
+
| `morningBriefComposer` | Prose for one recipient's morning brief; personal via its facts |
|
|
52
|
+
|
|
53
|
+
## Dependencies
|
|
54
|
+
|
|
55
|
+
Type-only imports of `ProactiveChatComposer` (`../composer`),
|
|
56
|
+
`ProactiveChatTemplate` (`../surfaces`) and the per-template facts
|
|
57
|
+
(`../facts`). No `zod`, no runtime imports beyond the sibling template
|
|
58
|
+
modules.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pushed-chat composer registry — every template and its prose
|
|
3
|
+
* (ADR-CONTRACTS-142).
|
|
4
|
+
*
|
|
5
|
+
* INVARIANTS:
|
|
6
|
+
* - TOTAL in both directions over `PROACTIVE_CHAT_TEMPLATES`. `as const
|
|
7
|
+
* satisfies Record<...>` means a template with no composer fails to compile,
|
|
8
|
+
* and a composer for a non-template fails too. A kind can therefore name a
|
|
9
|
+
* `chatTemplate` in `../registry` and be sure prose exists for it.
|
|
10
|
+
* - Each composer's `template` equals the key it sits under. The type says so
|
|
11
|
+
* per entry; the chat-templates test pins it in practice.
|
|
12
|
+
* - `as const` keeps each entry's literal shape, so
|
|
13
|
+
* `PROACTIVE_CHAT_COMPOSERS.orgBecameShared.compose` takes exactly
|
|
14
|
+
* `OrgBecameSharedFacts` rather than a union of every template's facts.
|
|
15
|
+
*
|
|
16
|
+
* To add a template:
|
|
17
|
+
* 1. Add it to `PROACTIVE_CHAT_TEMPLATES` in `../surfaces`
|
|
18
|
+
* 2. Add its facts to `ProactiveChatFacts` in `../facts`
|
|
19
|
+
* 3. Add a `<template>Composer` module beside this one, and register it here
|
|
20
|
+
*
|
|
21
|
+
* Step 1 alone will not type-check; that is the point.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import type { ProactiveChatComposer } from "../composer";
|
|
25
|
+
import type { ProactiveChatTemplate } from "../surfaces";
|
|
26
|
+
import { morningBriefComposer } from "./morning-brief";
|
|
27
|
+
import { orgBecameSharedComposer } from "./org-became-shared";
|
|
28
|
+
|
|
29
|
+
export const PROACTIVE_CHAT_COMPOSERS = {
|
|
30
|
+
orgBecameShared: orgBecameSharedComposer,
|
|
31
|
+
morningBrief: morningBriefComposer,
|
|
32
|
+
} as const satisfies Record<ProactiveChatTemplate, ProactiveChatComposer>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The morning-brief pushed chat — where things stand for ONE recipient on one
|
|
3
|
+
* org-local morning (ADR-CONTRACTS-142).
|
|
4
|
+
*
|
|
5
|
+
* The prose is PERSONAL and the occurrence is not: one `brief.morning`
|
|
6
|
+
* occurrence per org per day is shared by every recipient, and what makes two
|
|
7
|
+
* people's briefs differ is the `MorningBriefFacts` each was handed. Nothing
|
|
8
|
+
* in this module knows or cares which recipient it is composing for — that is
|
|
9
|
+
* exactly what keeps it pure.
|
|
10
|
+
*
|
|
11
|
+
* PURE. `(facts)` in, message out — no clock, no locale, no environment. The
|
|
12
|
+
* org-local day arrives already rendered (`orgLocalDateLabel`) for that reason.
|
|
13
|
+
*
|
|
14
|
+
* FROZEN AT WRITE TIME (INV-PROACTIVE-CONTENT). This prose is written once
|
|
15
|
+
* into the transcript and NEVER re-renders, so it may only interpolate facts
|
|
16
|
+
* the recipient was entitled to at compose time. Every fact it uses is the
|
|
17
|
+
* recipient's own — their name, their org's name, their counts — so there is
|
|
18
|
+
* nothing here a later revocation would have to take back.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type { ProactiveChatComposer } from "../composer";
|
|
22
|
+
import type { MorningBriefFacts } from "../facts";
|
|
23
|
+
|
|
24
|
+
/** "1 decision" / "3 decisions" — the one pluralisation the brief needs. */
|
|
25
|
+
function counted(n: number, singular: string, plural: string): string {
|
|
26
|
+
return `${n} ${n === 1 ? singular : plural}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The sentence about what is waiting on the recipient. Only the non-zero
|
|
31
|
+
* counts are mentioned; a brief that says "0 decisions" is reading a table
|
|
32
|
+
* aloud, not briefing anyone.
|
|
33
|
+
*/
|
|
34
|
+
function standingLine(facts: MorningBriefFacts): string {
|
|
35
|
+
const parts: string[] = [];
|
|
36
|
+
if (facts.pendingActionItemCount > 0) {
|
|
37
|
+
parts.push(
|
|
38
|
+
`${counted(facts.pendingActionItemCount, "decision", "decisions")} waiting on you`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
if (facts.unreadNotificationCount > 0) {
|
|
42
|
+
parts.push(
|
|
43
|
+
counted(
|
|
44
|
+
facts.unreadNotificationCount,
|
|
45
|
+
"unread notification",
|
|
46
|
+
"unread notifications",
|
|
47
|
+
),
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
if (parts.length === 0) {
|
|
51
|
+
return "Nothing is waiting on you, and your inbox is clear.";
|
|
52
|
+
}
|
|
53
|
+
return `You have ${parts.join(" and ")}.`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const morningBriefComposer: ProactiveChatComposer<"morningBrief"> = {
|
|
57
|
+
template: "morningBrief",
|
|
58
|
+
compose: (facts) => ({
|
|
59
|
+
title: `Your ${facts.orgLocalDateLabel} brief`,
|
|
60
|
+
text:
|
|
61
|
+
`Good morning, ${facts.recipientDisplayName}. Here is where ` +
|
|
62
|
+
`${facts.orgName} stands on ${facts.orgLocalDateLabel}.\n\n` +
|
|
63
|
+
standingLine(facts),
|
|
64
|
+
replies: [
|
|
65
|
+
{
|
|
66
|
+
id: "whats-changed",
|
|
67
|
+
label: "What changed since yesterday?",
|
|
68
|
+
prompt: "What changed in my workspace since yesterday?",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: "needs-me",
|
|
72
|
+
label: "What needs me today?",
|
|
73
|
+
prompt: "What needs a decision from me today?",
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
}),
|
|
77
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The org-became-shared pushed chat — the first invited person accepted, so
|
|
3
|
+
* the owner's personal workspace is now a shared one (ADR-CONTRACTS-142).
|
|
4
|
+
*
|
|
5
|
+
* The prose NAMES THE RELOCATION, and that is its job. The app ADR with slug
|
|
6
|
+
* `nav-posture-opens-on-invite-send` established that conversion is a
|
|
7
|
+
* relocation, not an addition: five sections leave My settings (Integrations,
|
|
8
|
+
* Invite team members, AI Usage, Budget, Billing) and reappear under Org
|
|
9
|
+
* settings. Unannounced, that reads as things going missing — and a banner
|
|
10
|
+
* can say "something moved" but not walk the owner through what and why.
|
|
11
|
+
* This chat is where that explanation lives, and the chips let the owner ask
|
|
12
|
+
* the question the banner cannot answer.
|
|
13
|
+
*
|
|
14
|
+
* FROZEN AT WRITE TIME (INV-PROACTIVE-CONTENT). Both interpolated facts are
|
|
15
|
+
* things the owner was entitled to at compose time: they named the org, and
|
|
16
|
+
* they invited the person who accepted. Nothing here can be revoked later in
|
|
17
|
+
* a way the transcript would have to take back.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { ProactiveChatComposer } from "../composer";
|
|
21
|
+
|
|
22
|
+
export const orgBecameSharedComposer: ProactiveChatComposer<"orgBecameShared"> =
|
|
23
|
+
{
|
|
24
|
+
template: "orgBecameShared",
|
|
25
|
+
compose: (facts) => ({
|
|
26
|
+
title: "Your workspace is now shared",
|
|
27
|
+
text:
|
|
28
|
+
`${facts.joinerDisplayName} accepted your invite, so ${facts.orgName} ` +
|
|
29
|
+
`moved from a personal workspace to a shared one.\n\n` +
|
|
30
|
+
`This is a relocation, not an addition: Integrations, Invite team ` +
|
|
31
|
+
`members, AI Usage, Budget and Billing have moved out of My settings ` +
|
|
32
|
+
`and into Org settings. Nothing was removed — the five sections now ` +
|
|
33
|
+
`belong to the workspace rather than to you.`,
|
|
34
|
+
replies: [
|
|
35
|
+
{
|
|
36
|
+
id: "plain-english",
|
|
37
|
+
label: "Explain in plain English",
|
|
38
|
+
prompt:
|
|
39
|
+
"Explain in plain English what changed when my workspace became shared.",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: "what-first",
|
|
43
|
+
label: "What should I set up first?",
|
|
44
|
+
prompt:
|
|
45
|
+
"Now that my workspace is shared, what should I set up first?",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: "who-can-see",
|
|
49
|
+
label: "Who can see what now?",
|
|
50
|
+
prompt: "Who can see what in my workspace now that it is shared?",
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
}),
|
|
54
|
+
};
|
|
@@ -150,6 +150,15 @@ export type ResourceKey =
|
|
|
150
150
|
// every badge in the shell, and the two have different invalidation triggers
|
|
151
151
|
// (a notification being READ changes the feed and not the bucket).
|
|
152
152
|
| { type: "feed"; orgId: string }
|
|
153
|
+
/**
|
|
154
|
+
* Org system events (the banner strip).
|
|
155
|
+
*
|
|
156
|
+
* Lives HERE and not in the app's `AppOnlyKey` union, whose doc bar is "the
|
|
157
|
+
* server can never need to name this key" — here the server is the publisher.
|
|
158
|
+
* Once the banner is an asynchronous projection, an unsubscribed hook means a
|
|
159
|
+
* banner that only appears on next mount or reload.
|
|
160
|
+
*/
|
|
161
|
+
| { type: "orgSystemEvents"; orgId: string }
|
|
153
162
|
// User-scoped
|
|
154
163
|
| { type: "dismissedBanners"; userId: string }
|
|
155
164
|
| { type: "userOrgs"; userId: string }
|
package/src/resource-keys.ts
CHANGED
|
@@ -147,6 +147,7 @@ const ORG_SCOPED_TYPES = [
|
|
|
147
147
|
"orgUnitOwners",
|
|
148
148
|
"actionItems",
|
|
149
149
|
"feed",
|
|
150
|
+
"orgSystemEvents",
|
|
150
151
|
] as const satisfies readonly OrgScopedType[];
|
|
151
152
|
|
|
152
153
|
const USER_SCOPED_TYPES = [
|
|
@@ -285,6 +286,7 @@ export function toQueryKey(key: ResourceKey): readonly string[] {
|
|
|
285
286
|
case "orgUnitOwners":
|
|
286
287
|
case "actionItems":
|
|
287
288
|
case "feed":
|
|
289
|
+
case "orgSystemEvents":
|
|
288
290
|
return [key.type, key.orgId] as const;
|
|
289
291
|
|
|
290
292
|
// System-scoped (ADR-CONTRACTS-052) — tenant-less super-admin resources
|
|
@@ -33,6 +33,16 @@ that renders them alongside action items at the top of `/me/work`.
|
|
|
33
33
|
- **The cursor encodes `(createdAt, id)`.** The id breaks ties so two rows
|
|
34
34
|
written in one transaction cannot straddle a page boundary and be served twice
|
|
35
35
|
or skipped.
|
|
36
|
+
- **A row can be BORN READ.** Every proactive kind writes a durable row here
|
|
37
|
+
as its paper trail, but only the class whose badge carrier is `inbox` writes
|
|
38
|
+
it unread; `proactive.org_became_shared` is an `announcement`, so the banner
|
|
39
|
+
holds the badge and the row arrives read, and `proactive.brief_morning` is a
|
|
40
|
+
`briefing`, so the pushed chat holds the badge and the row arrives read. That
|
|
41
|
+
rule is stated once, in `bornRead` (`../proactive/classes`), never re-derived
|
|
42
|
+
per call site (ADR-CONTRACTS-142). The kind strings are `proactive.*` — the
|
|
43
|
+
producing domain owns the renderer — and are deliberately not the
|
|
44
|
+
`ProactiveEventKind` strings `org.became_shared` / `brief.morning`, so
|
|
45
|
+
neither union can quietly become derived from the other.
|
|
36
46
|
|
|
37
47
|
## Public API
|
|
38
48
|
|
|
@@ -76,6 +76,34 @@ export const USER_NOTIFICATION_KINDS = [
|
|
|
76
76
|
"comment.mention",
|
|
77
77
|
/** A thread you took part in got a new comment. Never emitted for an edit. */
|
|
78
78
|
"comment.reply",
|
|
79
|
+
/**
|
|
80
|
+
* Your org stopped being personal — its first member joined
|
|
81
|
+
* (ADR-CONTRACTS-142). The durable row of the `org.became_shared` proactive
|
|
82
|
+
* kind.
|
|
83
|
+
*
|
|
84
|
+
* BORN READ: the banner holds the badge for an `announcement`, so this row
|
|
85
|
+
* is the paper trail and never the attention carrier (`bornRead` in
|
|
86
|
+
* `../proactive/classes` is the one place that rule is stated). The
|
|
87
|
+
* producing domain in the string is `proactive` — `src/proactive/` in the
|
|
88
|
+
* backend owns the renderer, matching the `companyMd.*` / `comment.*`
|
|
89
|
+
* convention. The string is deliberately DISTINCT from the proactive kind's
|
|
90
|
+
* `org.became_shared`: identical names across two unions is how one quietly
|
|
91
|
+
* becomes derived from the other, which ADR-CONT-108 forbids.
|
|
92
|
+
*/
|
|
93
|
+
"proactive.org_became_shared",
|
|
94
|
+
/**
|
|
95
|
+
* Your morning brief was written (ADR-CONTRACTS-142). The durable row of
|
|
96
|
+
* the `brief.morning` proactive kind.
|
|
97
|
+
*
|
|
98
|
+
* BORN READ: the pushed chat holds the badge for a `briefing`, so this row
|
|
99
|
+
* is the paper trail and never the attention carrier (`bornRead` in
|
|
100
|
+
* `../proactive/classes` is the one place that rule is stated). One row per
|
|
101
|
+
* recipient per org-local day, pointing at that recipient's OWN chat — the
|
|
102
|
+
* occurrence is shared, the row and the chat are not. The string is
|
|
103
|
+
* deliberately DISTINCT from the proactive kind's `brief.morning`, for the
|
|
104
|
+
* reason stated on the member above.
|
|
105
|
+
*/
|
|
106
|
+
"proactive.brief_morning",
|
|
79
107
|
] as const;
|
|
80
108
|
|
|
81
109
|
export type UserNotificationKind = (typeof USER_NOTIFICATION_KINDS)[number];
|