@opencxh/domain 1.186.0 → 1.188.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.
@@ -0,0 +1,139 @@
1
+ import { LocaleBundle } from '../entities/analytics/dashboard';
2
+ /**
3
+ * Role group of an app that declares its own notification kinds.
4
+ *
5
+ * A constant and not a literal at both ends: `Bridge.providers.list` answers an unknown group
6
+ * with an empty list, so a typo would read as "no app sends notifications". Same reason
7
+ * `ONBOARDING_SOURCE_PROVIDER_GROUP` is a constant.
8
+ */
9
+ export declare const NOTIFICATION_SOURCE_PROVIDER_GROUP = "notification-source";
10
+ /**
11
+ * Text a reader renders without the sender knowing what it says: an i18n key from the declaring
12
+ * app's own bundle, plus values for `{name}` placeholders.
13
+ *
14
+ * Not a rendered string, because one notification is read on a laptop in Dutch and pushed to a
15
+ * phone in English. Not a bare key either: half of these lines carry data ("Vincent mentioned you")
16
+ * and a key per person does not exist.
17
+ *
18
+ * Sources write **bare** keys; the hub stamps the namespace (see `qualifyText` in platform-api).
19
+ */
20
+ export interface NotificationText {
21
+ key: string;
22
+ vars?: Record<string, string | number>;
23
+ }
24
+ /**
25
+ * One switchable kind of notification, declared by the app that sends it.
26
+ *
27
+ * Declared rather than listed centrally, because only the owning app knows that "you were
28
+ * mentioned" and "a task was assigned to you" are different questions to a reader. A hard-coded
29
+ * list breaks at app N+1 — the map it replaces is `DEFAULT_KIND_ENABLED` in comms' push dispatch.
30
+ */
31
+ export interface NotificationKindDef {
32
+ /** Bare in the describe (`"mentioned"`); the hub stamps it to `"communication:mentioned"`. */
33
+ id: string;
34
+ title: NotificationText;
35
+ description?: NotificationText;
36
+ /** What holds until the user touches a switch. Per transport, because they differ in practice. */
37
+ defaults: {
38
+ inApp: boolean;
39
+ push: boolean;
40
+ };
41
+ /** `true` = the user may not switch it off (an incoming call). Absent = they may. */
42
+ mandatory?: boolean;
43
+ /**
44
+ * Lucide icon name for the toast tile (`"at-sign"`, `"user-check"`). Declared with the kind
45
+ * because the icon is part of what the kind *is* — the reader tells a mention from an
46
+ * assignment by its shape before reading a word. Absent falls back to the toast type icon.
47
+ */
48
+ icon?: string;
49
+ /** Sort hint within an app. Ties break on id, so the settings screen stays stable. */
50
+ order?: number;
51
+ }
52
+ /**
53
+ * Payload of `GET /provider/notification/describe`.
54
+ *
55
+ * Bare, not wrapped in `ResponseFactory` — that is how the fan-out reads it, same as
56
+ * `analytics-source` and `onboarding-source`.
57
+ */
58
+ export interface NotificationSourceDescribe {
59
+ /** The declaring app (== manifest name == `req.source.app`). */
60
+ source: string;
61
+ kinds: NotificationKindDef[];
62
+ /** Texts for every key above, in every language this app ships. `flattenLocales()` helps. */
63
+ locales?: LocaleBundle;
64
+ }
65
+ /** Where clicking a notification takes you. App-relative, like an onboarding route link. */
66
+ export interface NotificationLink {
67
+ app: string;
68
+ path: string;
69
+ }
70
+ /**
71
+ * The person (or assistant) whose action caused this.
72
+ *
73
+ * Carried as a plain name and not a userId: a notification is read long after it was written, and
74
+ * a lookup per row means the panel cannot render until every actor resolves. Initials and the
75
+ * avatar colour are derived from the name by `Avatar` with `tone="identity"`, so the same person
76
+ * keeps one colour on every surface without this ever storing one.
77
+ */
78
+ export interface NotificationActor {
79
+ name: string;
80
+ avatarUrl?: string;
81
+ }
82
+ export interface Notification {
83
+ id: string;
84
+ organizationId: string;
85
+ userId: string;
86
+ /** The app that sent it. Derived from the caller, never from the body. */
87
+ source: string;
88
+ /** Namespaced: `"communication:mentioned"`. */
89
+ kind: string;
90
+ /** What happened, without the actor's name in it — the panel renders the name in front. */
91
+ title: NotificationText;
92
+ /** The quoted line: the comment text, the task name. */
93
+ body?: NotificationText;
94
+ /** Where it happened ("RE: EYLO / Leufgens"), shown with the timestamp on the third line. */
95
+ context?: NotificationText;
96
+ actor?: NotificationActor;
97
+ link?: NotificationLink;
98
+ /** Free-form; travels untouched into the push payload. */
99
+ data?: Record<string, unknown>;
100
+ createdAt: number;
101
+ readAt?: number;
102
+ }
103
+ /** Body of `POST notification/send`. The only thing that crosses an app boundary. */
104
+ export interface NotifyRequest {
105
+ to: {
106
+ userIds: string[];
107
+ } | {
108
+ userId: string;
109
+ };
110
+ /** Bare; stamped with the calling app's name server-side. */
111
+ kind: string;
112
+ title: NotificationText;
113
+ body?: NotificationText;
114
+ context?: NotificationText;
115
+ actor?: NotificationActor;
116
+ link?: NotificationLink;
117
+ data?: Record<string, unknown>;
118
+ /**
119
+ * Wake the devices without leaving a row behind. For a signal that is meaningless once it is
120
+ * over — an incoming call you already missed is not something to catch up on. Default `false`.
121
+ */
122
+ transient?: boolean;
123
+ /** Passed straight to push; a newer envelope with the same key replaces an undelivered one. */
124
+ collapseKey?: string;
125
+ }
126
+ export interface NotifyResult {
127
+ /** Rows written. `0` for a transient notification, and for a send nobody had switched on. */
128
+ created: number;
129
+ }
130
+ /** Result of the kinds fan-out, as the settings screen reads it. */
131
+ export interface NotificationCatalog {
132
+ kinds: NotificationKindDef[];
133
+ locales: LocaleBundle;
134
+ /** Sources that did not answer, so the screen can say who is missing instead of silently thinning. */
135
+ degradedApps: string[];
136
+ }
137
+ /** Per-device preference keys. Two transports, two keys — see `docs/` and the settings screen. */
138
+ export type NotificationPrefTransport = "inapp" | "push";
139
+ export declare const notificationPrefKey: (transport: NotificationPrefTransport, kind: string) => string;
@@ -16,7 +16,13 @@ export interface PushSubscription {
16
16
  createdAt: number;
17
17
  lastSeenAt: number;
18
18
  }
19
- export type PushKind = "wake" | "incoming-call" | "message" | "presence" | "notification" | "custom";
19
+ /**
20
+ * The platform's own kinds, plus whatever a `notification-source` app declares
21
+ * (`"communication:mentioned"`). Open like {@link ExtensionContext}: the closed union kept
22
+ * autocomplete but made an app-declared kind unexpressible, and per-kind device preferences
23
+ * are exactly what apps must be able to declare.
24
+ */
25
+ export type PushKind = "wake" | "incoming-call" | "message" | "presence" | "notification" | "custom" | (string & {});
20
26
  export interface PushDisplay {
21
27
  title: string;
22
28
  body?: string;
@@ -113,6 +113,13 @@ export interface ToastConfig {
113
113
  * combine with `persistent: true` so it can't be auto- or hand-dismissed.
114
114
  */
115
115
  closable?: boolean;
116
+ /**
117
+ * Lucide icon name for the leading tile, overriding the one implied by `type`.
118
+ *
119
+ * A notification says what kind of attention it wants (a mention, an assignment) and `type` only
120
+ * says how loud it is, so the two cannot be the same field.
121
+ */
122
+ icon?: string;
116
123
  actions?: {
117
124
  label: string;
118
125
  onClick: () => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencxh/domain",
3
- "version": "1.186.0",
3
+ "version": "1.188.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",