@actuate-media/cms-admin 0.12.1 → 0.14.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.
Files changed (51) hide show
  1. package/dist/__tests__/components/comment-side-panel.test.js +93 -0
  2. package/dist/__tests__/components/comment-side-panel.test.js.map +1 -1
  3. package/dist/__tests__/components/mentionable-textarea.test.d.ts +2 -0
  4. package/dist/__tests__/components/mentionable-textarea.test.d.ts.map +1 -0
  5. package/dist/__tests__/components/mentionable-textarea.test.js +190 -0
  6. package/dist/__tests__/components/mentionable-textarea.test.js.map +1 -0
  7. package/dist/__tests__/components/notification-bell.test.js +146 -0
  8. package/dist/__tests__/components/notification-bell.test.js.map +1 -1
  9. package/dist/__tests__/lib/mention-picker.test.d.ts +2 -0
  10. package/dist/__tests__/lib/mention-picker.test.d.ts.map +1 -0
  11. package/dist/__tests__/lib/mention-picker.test.js +122 -0
  12. package/dist/__tests__/lib/mention-picker.test.js.map +1 -0
  13. package/dist/__tests__/lib/notifications-client.test.js +129 -1
  14. package/dist/__tests__/lib/notifications-client.test.js.map +1 -1
  15. package/dist/actuate-admin.css +1 -1
  16. package/dist/components/CommentSidePanel.d.ts +9 -1
  17. package/dist/components/CommentSidePanel.d.ts.map +1 -1
  18. package/dist/components/CommentSidePanel.js +8 -7
  19. package/dist/components/CommentSidePanel.js.map +1 -1
  20. package/dist/components/MentionableTextarea.d.ts +68 -0
  21. package/dist/components/MentionableTextarea.d.ts.map +1 -0
  22. package/dist/components/MentionableTextarea.js +181 -0
  23. package/dist/components/MentionableTextarea.js.map +1 -0
  24. package/dist/components/NotificationBell.d.ts +30 -9
  25. package/dist/components/NotificationBell.d.ts.map +1 -1
  26. package/dist/components/NotificationBell.js +57 -5
  27. package/dist/components/NotificationBell.js.map +1 -1
  28. package/dist/lib/api.d.ts +8 -0
  29. package/dist/lib/api.d.ts.map +1 -1
  30. package/dist/lib/api.js +10 -0
  31. package/dist/lib/api.js.map +1 -1
  32. package/dist/lib/mention-picker.d.ts +69 -0
  33. package/dist/lib/mention-picker.d.ts.map +1 -0
  34. package/dist/lib/mention-picker.js +138 -0
  35. package/dist/lib/mention-picker.js.map +1 -0
  36. package/dist/lib/notifications-client.d.ts +53 -0
  37. package/dist/lib/notifications-client.d.ts.map +1 -1
  38. package/dist/lib/notifications-client.js +89 -1
  39. package/dist/lib/notifications-client.js.map +1 -1
  40. package/package.json +2 -2
  41. package/src/__tests__/components/comment-side-panel.test.tsx +134 -0
  42. package/src/__tests__/components/mentionable-textarea.test.tsx +226 -0
  43. package/src/__tests__/components/notification-bell.test.tsx +167 -1
  44. package/src/__tests__/lib/mention-picker.test.ts +145 -0
  45. package/src/__tests__/lib/notifications-client.test.ts +145 -1
  46. package/src/components/CommentSidePanel.tsx +37 -14
  47. package/src/components/MentionableTextarea.tsx +328 -0
  48. package/src/components/NotificationBell.tsx +84 -12
  49. package/src/lib/api.ts +11 -0
  50. package/src/lib/mention-picker.ts +180 -0
  51. package/src/lib/notifications-client.ts +147 -1
@@ -0,0 +1,68 @@
1
+ import React from 'react';
2
+ import { type MentionUser } from '../lib/mention-picker.js';
3
+ /**
4
+ * `<MentionableTextarea />` — drop-in replacement for `<textarea>` /
5
+ * `<input type="text">` that opens a Radix-styled mention picker
6
+ * whenever the user types `@`.
7
+ *
8
+ * Behaviour
9
+ * ─────────
10
+ * - The picker appears anchored to the textarea, below the input.
11
+ * We intentionally do NOT track the caret pixel position (that
12
+ * requires a textarea-mirror trick which doesn't add enough UX to
13
+ * justify the complexity); GitHub, Linear, and Asana all anchor
14
+ * their pickers this way on simple textareas.
15
+ * - Arrow Up / Arrow Down move the highlight. Enter / Tab commit the
16
+ * selection. Escape closes the picker without inserting anything.
17
+ * Mouse click on a row also commits.
18
+ * - The picker only fires server queries while a real mention is
19
+ * being typed; it short-circuits an empty `query` so the dropdown
20
+ * shows up immediately after `@` is typed (with an empty state)
21
+ * and only flips to "no matches" once the user has typed at least
22
+ * one character.
23
+ * - The textarea value is fully controlled by the parent — we never
24
+ * keep a separate copy. `onChange` is called with the next value
25
+ * on every keystroke AND every mention insertion.
26
+ *
27
+ * Dependency-injection seam
28
+ * ─────────────────────────
29
+ * The `search` prop accepts an override so tests can substitute a
30
+ * synchronous fake. Production callers leave it unset and the
31
+ * component uses the real `searchMentionUsers()` REST helper.
32
+ */
33
+ export interface MentionableTextareaProps {
34
+ value: string;
35
+ onChange: (next: string) => void;
36
+ /** Forwarded to the underlying textarea. */
37
+ placeholder?: string;
38
+ /** Forwarded to the underlying textarea. */
39
+ rows?: number;
40
+ className?: string;
41
+ /** Forwarded — useful for keyboard shortcuts the parent owns. */
42
+ onKeyDown?: (event: React.KeyboardEvent<HTMLTextAreaElement>) => void;
43
+ /** Forwarded — useful for click-to-stop-propagation in nested cards. */
44
+ onClick?: (event: React.MouseEvent<HTMLTextAreaElement>) => void;
45
+ /** Test hook — required when rendering with React state assertions. */
46
+ 'data-testid'?: string;
47
+ /**
48
+ * Override the user search. The default uses the REST helper.
49
+ * Tests inject an in-memory fake so dropdown contents are
50
+ * deterministic without HTTP mocking.
51
+ */
52
+ search?: (query: string) => Promise<MentionUser[]>;
53
+ /**
54
+ * Optional fixed id for the picker dropdown — pass when the parent
55
+ * wires up `aria-controls`. We generate one with `useId()` when
56
+ * unset.
57
+ */
58
+ pickerId?: string;
59
+ /**
60
+ * Format the displayed token. By default we show `@<name>` while
61
+ * the underlying value stores `@<userId>`. A future slice can swap
62
+ * to a rich-text editor where these diverge; the prop lets that
63
+ * land without changing the picker.
64
+ */
65
+ formatLabel?: (user: MentionUser) => string;
66
+ }
67
+ export declare function MentionableTextarea({ value, onChange, placeholder, rows, className, onKeyDown, onClick, 'data-testid': testId, search, pickerId: pickerIdProp, formatLabel, }: MentionableTextareaProps): React.JSX.Element;
68
+ //# sourceMappingURL=MentionableTextarea.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MentionableTextarea.d.ts","sourceRoot":"","sources":["../../src/components/MentionableTextarea.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA0D,MAAM,OAAO,CAAA;AAE9E,OAAO,EACL,KAAK,WAAW,EAIjB,MAAM,0BAA0B,CAAA;AAEjC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,4CAA4C;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,4CAA4C;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,iEAAiE;IACjE,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,aAAa,CAAC,mBAAmB,CAAC,KAAK,IAAI,CAAA;IACrE,wEAAwE;IACxE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,mBAAmB,CAAC,KAAK,IAAI,CAAA;IAChE,uEAAuE;IACvE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;OAIG;IACH,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,EAAE,CAAC,CAAA;IAClD;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,MAAM,CAAA;CAC5C;AAgBD,wBAAgB,mBAAmB,CAAC,EAClC,KAAK,EACL,QAAQ,EACR,WAAW,EACX,IAAQ,EACR,SAAS,EACT,SAAS,EACT,OAAO,EACP,aAAa,EAAE,MAAM,EACrB,MAAyE,EACzE,QAAQ,EAAE,YAAY,EACtB,WAAiC,GAClC,EAAE,wBAAwB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAiO9C"}
@@ -0,0 +1,181 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useCallback, useEffect, useId, useRef, useState } from 'react';
4
+ import { detectMentionContext, insertMentionToken, searchMentionUsers, } from '../lib/mention-picker.js';
5
+ const initialPicker = {
6
+ open: false,
7
+ query: '',
8
+ highlight: 0,
9
+ users: [],
10
+ };
11
+ export function MentionableTextarea({ value, onChange, placeholder, rows = 3, className, onKeyDown, onClick, 'data-testid': testId, search = (q) => searchMentionUsers(q).then((r) => (r.ok ? r.result : [])), pickerId: pickerIdProp, formatLabel = (u) => `@${u.name}`, }) {
12
+ const generatedId = useId();
13
+ const pickerId = pickerIdProp ?? `actuate-mention-picker-${generatedId}`;
14
+ const textareaRef = useRef(null);
15
+ const [picker, setPicker] = useState(initialPicker);
16
+ // `pickerRef` mirrors `picker` for the keydown handler so the
17
+ // arrow-key / enter listeners don't go stale between renders.
18
+ const pickerRef = useRef(picker);
19
+ useEffect(() => {
20
+ pickerRef.current = picker;
21
+ }, [picker]);
22
+ const requestSeq = useRef(0);
23
+ const mountedRef = useRef(true);
24
+ useEffect(() => {
25
+ return () => {
26
+ mountedRef.current = false;
27
+ };
28
+ }, []);
29
+ const closePicker = useCallback(() => {
30
+ setPicker((p) => (p.open ? initialPicker : p));
31
+ }, []);
32
+ // Recompute the active mention context whenever the value or
33
+ // selection changes. Centralising this in one effect keeps the
34
+ // open/close logic in a single place — every textarea event
35
+ // (input, click, keyup) just calls `recompute()`.
36
+ const recompute = useCallback(async (nextValue, caret) => {
37
+ const ctx = detectMentionContext(nextValue, caret);
38
+ if (!ctx) {
39
+ closePicker();
40
+ return;
41
+ }
42
+ const id = ++requestSeq.current;
43
+ // Open immediately so the user sees a dropdown the moment they
44
+ // type `@`. Clear the `users` array on every new query so the
45
+ // user can never press Enter / Tab and commit a stale row
46
+ // from the previous query while the new request is in flight
47
+ // (PR #159 Copilot finding — type `@a` then `@bo` quickly and
48
+ // hit Enter before the `@bo` results arrive: without this
49
+ // reset, the picker would commit a row from `@a`).
50
+ setPicker((p) => ({
51
+ open: true,
52
+ query: ctx.query,
53
+ highlight: 0,
54
+ users: p.query === ctx.query ? p.users : [],
55
+ }));
56
+ try {
57
+ const users = await search(ctx.query);
58
+ if (!mountedRef.current)
59
+ return;
60
+ // Drop stale responses — only commit if our request is still
61
+ // the latest one. Otherwise the user has typed more and a
62
+ // newer query is in flight.
63
+ if (id !== requestSeq.current)
64
+ return;
65
+ setPicker((p) =>
66
+ // If the picker was closed (Escape) between request and
67
+ // response, don't reopen it.
68
+ p.open ? { ...p, users, highlight: 0 } : p);
69
+ }
70
+ catch {
71
+ if (!mountedRef.current)
72
+ return;
73
+ if (id !== requestSeq.current)
74
+ return;
75
+ // Surface no rows on failure — the textarea must keep
76
+ // working when the user is offline. The poll loop / native
77
+ // submit path is unaffected.
78
+ setPicker((p) => (p.open ? { ...p, users: [] } : p));
79
+ }
80
+ }, [closePicker, search]);
81
+ const commitMention = useCallback((user) => {
82
+ const el = textareaRef.current;
83
+ if (!el)
84
+ return;
85
+ const caret = el.selectionStart ?? value.length;
86
+ const ctx = detectMentionContext(value, caret);
87
+ if (!ctx) {
88
+ closePicker();
89
+ return;
90
+ }
91
+ const next = insertMentionToken(value, ctx, user.id);
92
+ onChange(next.value);
93
+ closePicker();
94
+ // Move the caret past the inserted token AFTER React paints
95
+ // the new value, so the selection isn't clobbered by the
96
+ // controlled update.
97
+ requestAnimationFrame(() => {
98
+ const live = textareaRef.current;
99
+ if (!live)
100
+ return;
101
+ live.focus();
102
+ live.setSelectionRange(next.caret, next.caret);
103
+ });
104
+ }, [closePicker, onChange, value]);
105
+ const handleChange = (event) => {
106
+ const next = event.target.value;
107
+ const caret = event.target.selectionStart ?? next.length;
108
+ onChange(next);
109
+ void recompute(next, caret);
110
+ };
111
+ const handleKeyDown = (event) => {
112
+ const p = pickerRef.current;
113
+ if (p.open && p.users.length > 0) {
114
+ if (event.key === 'ArrowDown') {
115
+ event.preventDefault();
116
+ setPicker((s) => ({ ...s, highlight: (s.highlight + 1) % s.users.length }));
117
+ return;
118
+ }
119
+ if (event.key === 'ArrowUp') {
120
+ event.preventDefault();
121
+ setPicker((s) => ({
122
+ ...s,
123
+ highlight: (s.highlight - 1 + s.users.length) % s.users.length,
124
+ }));
125
+ return;
126
+ }
127
+ if (event.key === 'Enter' || event.key === 'Tab') {
128
+ event.preventDefault();
129
+ // Defensive: highlight could theoretically point past the
130
+ // end of `users` if state went stale between renders. Guard
131
+ // it so we never `undefined`-bang.
132
+ const target = p.users[p.highlight];
133
+ if (target)
134
+ commitMention(target);
135
+ return;
136
+ }
137
+ }
138
+ if (event.key === 'Escape' && p.open) {
139
+ event.preventDefault();
140
+ closePicker();
141
+ return;
142
+ }
143
+ onKeyDown?.(event);
144
+ };
145
+ const handleClick = (event) => {
146
+ onClick?.(event);
147
+ const el = textareaRef.current;
148
+ if (!el)
149
+ return;
150
+ void recompute(el.value, el.selectionStart ?? el.value.length);
151
+ };
152
+ // Closing on outside click — when the user clicks anywhere outside
153
+ // the textarea or the picker, the dropdown should disappear so it
154
+ // doesn't sit on top of unrelated UI.
155
+ useEffect(() => {
156
+ if (!picker.open)
157
+ return;
158
+ const onDocMouseDown = (ev) => {
159
+ const t = ev.target;
160
+ if (!t)
161
+ return;
162
+ const parent = textareaRef.current?.parentElement;
163
+ if (parent && parent.contains(t))
164
+ return;
165
+ closePicker();
166
+ };
167
+ document.addEventListener('mousedown', onDocMouseDown);
168
+ return () => document.removeEventListener('mousedown', onDocMouseDown);
169
+ }, [picker.open, closePicker]);
170
+ return (_jsxs("div", { className: "relative", children: [_jsx("textarea", { ref: textareaRef, value: value, onChange: handleChange, onKeyDown: handleKeyDown, onClick: handleClick, placeholder: placeholder, rows: rows, className: className, "aria-autocomplete": "list", "aria-controls": picker.open ? pickerId : undefined, "aria-expanded": picker.open, "data-testid": testId }), picker.open && (_jsx("div", { id: pickerId, role: "listbox", "aria-label": "Mention a teammate", className: "absolute top-full left-0 z-30 mt-1 max-h-56 w-64 overflow-y-auto rounded border border-gray-200 bg-white shadow-lg", "data-testid": testId ? `${testId}-picker` : 'mention-picker', children: picker.users.length === 0 ? (_jsx("p", { className: "px-3 py-2 text-xs text-gray-500", children: picker.query ? 'No matches' : 'Type a name…' })) : (_jsx("ul", { children: picker.users.map((u, idx) => (_jsx("li", { children: _jsxs("button", { type: "button", role: "option", "aria-selected": idx === picker.highlight, onMouseDown: (e) => {
171
+ // mousedown (not click) — prevents the
172
+ // textarea from losing focus before we
173
+ // commit, which would otherwise hide the
174
+ // selection and feel janky.
175
+ e.preventDefault();
176
+ commitMention(u);
177
+ }, onMouseEnter: () => setPicker((s) => ({ ...s, highlight: idx })), className: `block w-full px-3 py-1.5 text-left text-xs ${idx === picker.highlight
178
+ ? 'bg-blue-50 text-blue-900'
179
+ : 'text-gray-800 hover:bg-gray-50'}`, "data-testid": testId ? `${testId}-picker-row-${u.id}` : `mention-row-${u.id}`, children: [_jsx("div", { className: "font-medium", children: formatLabel(u) }), _jsx("div", { className: "text-[10px] text-gray-500", children: u.email })] }) }, u.id))) })) }))] }));
180
+ }
181
+ //# sourceMappingURL=MentionableTextarea.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MentionableTextarea.js","sourceRoot":"","sources":["../../src/components/MentionableTextarea.tsx"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { type Notification, type NotificationOutcome } from '../lib/notifications-client.js';
2
+ import { type Notification, type NotificationOutcome, type NotificationStreamHandle, type NotificationStreamOptions } from '../lib/notifications-client.js';
3
3
  /**
4
4
  * Top-bar notification bell + dropdown panel. Slice 6 of Phase 3
5
5
  * surfaces every collaboration event that affects the current user —
@@ -8,19 +8,25 @@ import { type Notification, type NotificationOutcome } from '../lib/notification
8
8
  *
9
9
  * Design choices:
10
10
  *
11
- * - **Polling, not SSE.** Phase 3 already ships an SSE stream for live
12
- * previews (Phase 2 / `/preview/stream`), but extending it to
13
- * per-user notifications adds a non-trivial fan-out path and a fresh
14
- * security surface (privacy-leak risk on misconfigured streams). The
15
- * bell polls every `pollIntervalMs` (default 30s) and on dropdown
16
- * open. A push-based extension is queued as a Phase 4 follow-up.
11
+ * - **SSE push, with poll fallback (Phase 7).** A persistent
12
+ * `EventSource` subscribes to `/api/cms/notifications/stream` for
13
+ * instant delivery; new notifications bump the badge immediately
14
+ * instead of after the next 30 s poll tick. The poll loop is kept
15
+ * alive at a back-pressure-friendly cadence (default 60 s twice
16
+ * the legacy 30 s value because SSE handles the common case) and
17
+ * acts as a backstop for:
18
+ * 1. multi-node deployments where the in-process bus only
19
+ * publishes to subscribers on the same node, and
20
+ * 2. proxies / browsers that silently drop the EventSource
21
+ * without firing `onerror`.
17
22
  * - **Optimistic read state.** When the user clicks a notification we
18
23
  * mark it read locally before the server confirms — so the bell
19
24
  * counter feels instant. Server failures roll the state back via
20
25
  * the `onError` toast.
21
26
  * - **API injection.** The dependency-injection seam matches the rest
22
27
  * of the admin (`CommentSidePanel.api`) so unit tests can substitute
23
- * in-memory fakes.
28
+ * in-memory fakes — including the EventSource subscriber, which
29
+ * isn't available in JSDOM by default.
24
30
  */
25
31
  export interface NotificationsApi {
26
32
  list: (opts: {
@@ -29,9 +35,24 @@ export interface NotificationsApi {
29
35
  unread: () => Promise<NotificationOutcome<number>>;
30
36
  markRead: (id: string) => Promise<NotificationOutcome<Notification>>;
31
37
  markAllRead: () => Promise<NotificationOutcome<number>>;
38
+ /**
39
+ * Open a per-user SSE subscription. Implementations MUST always
40
+ * return a handle — even in environments without `EventSource`
41
+ * (SSR, JSDOM without a polyfill), where a no-op `{ close() {} }`
42
+ * is the right answer. The bell's effect calls `handle.close()`
43
+ * unconditionally on unmount; a `null` return would crash.
44
+ * Omitting the member entirely is the supported way to opt out of
45
+ * SSE (the bell then runs poll-only).
46
+ */
47
+ subscribe?: (opts: NotificationStreamOptions) => NotificationStreamHandle;
32
48
  }
33
49
  export interface NotificationBellProps {
34
- /** Polling cadence in milliseconds. Default 30,000. Pass 0 to disable. */
50
+ /**
51
+ * Polling cadence in milliseconds for the fallback loop. Default
52
+ * 60,000 (twice the pre-SSE value — push handles the common case).
53
+ * Pass 0 to disable polling entirely (relies on SSE only — only
54
+ * safe on single-node deployments).
55
+ */
35
56
  pollIntervalMs?: number;
36
57
  /** Optional click hook — called with the notification when a row is clicked. */
37
58
  onSelect?: (notification: Notification) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"NotificationBell.d.ts","sourceRoot":"","sources":["../../src/components/NotificationBell.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAmD,MAAM,OAAO,CAAA;AAEvE,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,mBAAmB,EAMzB,MAAM,gCAAgC,CAAA;AAEvC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,CAAC,IAAI,EAAE;QAAE,WAAW,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,mBAAmB,CAAC,YAAY,EAAE,CAAC,CAAC,CAAA;IACvF,MAAM,EAAE,MAAM,OAAO,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC,CAAA;IAClD,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,mBAAmB,CAAC,YAAY,CAAC,CAAC,CAAA;IACpE,WAAW,EAAE,MAAM,OAAO,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC,CAAA;CACxD;AASD,MAAM,WAAW,qBAAqB;IACpC,0EAA0E;IAC1E,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,gFAAgF;IAChF,QAAQ,CAAC,EAAE,CAAC,YAAY,EAAE,YAAY,KAAK,IAAI,CAAA;IAC/C,+DAA+D;IAC/D,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IACnC,uCAAuC;IACvC,GAAG,CAAC,EAAE,gBAAgB,CAAA;IACtB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,cAAuB,EACvB,QAAQ,EACR,OAAO,EACP,GAAgB,EAChB,SAAS,GACV,EAAE,qBAAqB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CA0J3C"}
1
+ {"version":3,"file":"NotificationBell.d.ts","sourceRoot":"","sources":["../../src/components/NotificationBell.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAmD,MAAM,OAAO,CAAA;AAEvE,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,mBAAmB,EACxB,KAAK,wBAAwB,EAC7B,KAAK,yBAAyB,EAO/B,MAAM,gCAAgC,CAAA;AAEvC;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,CAAC,IAAI,EAAE;QAAE,WAAW,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,mBAAmB,CAAC,YAAY,EAAE,CAAC,CAAC,CAAA;IACvF,MAAM,EAAE,MAAM,OAAO,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC,CAAA;IAClD,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,mBAAmB,CAAC,YAAY,CAAC,CAAC,CAAA;IACpE,WAAW,EAAE,MAAM,OAAO,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC,CAAA;IACvD;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,yBAAyB,KAAK,wBAAwB,CAAA;CAC1E;AAUD,MAAM,WAAW,qBAAqB;IACpC;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,gFAAgF;IAChF,QAAQ,CAAC,EAAE,CAAC,YAAY,EAAE,YAAY,KAAK,IAAI,CAAA;IAC/C,+DAA+D;IAC/D,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IACnC,uCAAuC;IACvC,GAAG,CAAC,EAAE,gBAAgB,CAAA;IACtB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,cAAuB,EACvB,QAAQ,EACR,OAAO,EACP,GAAgB,EAChB,SAAS,GACV,EAAE,qBAAqB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAyM3C"}
@@ -1,19 +1,29 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useCallback, useEffect, useRef, useState } from 'react';
4
- import { listNotifications as defaultListNotifications, markAllNotificationsRead as defaultMarkAllRead, markNotificationRead as defaultMarkRead, notificationSummary, unreadCount as defaultUnreadCount, } from '../lib/notifications-client.js';
4
+ import { listNotifications as defaultListNotifications, markAllNotificationsRead as defaultMarkAllRead, markNotificationRead as defaultMarkRead, notificationSummary, subscribeNotificationStream as defaultSubscribeStream, unreadCount as defaultUnreadCount, } from '../lib/notifications-client.js';
5
5
  const defaultApi = {
6
6
  list: (opts) => defaultListNotifications(opts),
7
7
  unread: () => defaultUnreadCount(),
8
8
  markRead: (id) => defaultMarkRead(id),
9
9
  markAllRead: () => defaultMarkAllRead(),
10
+ subscribe: (opts) => defaultSubscribeStream(opts),
10
11
  };
11
- export function NotificationBell({ pollIntervalMs = 30_000, onSelect, onError, api = defaultApi, className, }) {
12
+ export function NotificationBell({ pollIntervalMs = 60_000, onSelect, onError, api = defaultApi, className, }) {
12
13
  const [open, setOpen] = useState(false);
13
14
  const [unread, setUnread] = useState(0);
14
15
  const [items, setItems] = useState([]);
15
16
  const [loading, setLoading] = useState(false);
16
17
  const mountedRef = useRef(true);
18
+ // Set of notification IDs we've already rendered. The SSE handler
19
+ // consults this synchronously so duplicate deliveries (REST refresh
20
+ // + SSE push, or two SSE retries) don't double-bump the badge.
21
+ // Kept in sync with `items` via an effect — the items state is the
22
+ // source of truth; this ref is a derived O(1) lookup index.
23
+ const seenIdsRef = useRef(new Set());
24
+ useEffect(() => {
25
+ seenIdsRef.current = new Set(items.map((n) => n.id));
26
+ }, [items]);
17
27
  useEffect(() => {
18
28
  return () => {
19
29
  mountedRef.current = false;
@@ -50,6 +60,37 @@ export function NotificationBell({ pollIntervalMs = 30_000, onSelect, onError, a
50
60
  const id = setInterval(() => void refreshUnread(), pollIntervalMs);
51
61
  return () => clearInterval(id);
52
62
  }, [refreshUnread, pollIntervalMs]);
63
+ // SSE push subscription. Bumps the badge + prepends the new row to
64
+ // the open dropdown the instant the server publishes. Falls back
65
+ // silently when the api/runtime doesn't support EventSource — the
66
+ // poll loop above keeps the bell correct in that case.
67
+ useEffect(() => {
68
+ if (!api.subscribe)
69
+ return;
70
+ const handle = api.subscribe({
71
+ onNotification: (n) => {
72
+ if (!mountedRef.current)
73
+ return;
74
+ // Defensive: SSE events can race with REST refreshes (or with
75
+ // their own retries). The seen-id ref is updated
76
+ // synchronously so a second delivery of the same row is a
77
+ // no-op for BOTH `items` and the unread badge — guarding only
78
+ // setItems would still let the counter drift.
79
+ if (seenIdsRef.current.has(n.id))
80
+ return;
81
+ seenIdsRef.current.add(n.id);
82
+ setItems((arr) => [n, ...arr]);
83
+ if (!n.readAt)
84
+ setUnread((v) => v + 1);
85
+ },
86
+ onError: () => {
87
+ // Don't surface every transient SSE disconnect as a UI error —
88
+ // the browser auto-reconnects, and the poll loop keeps state
89
+ // converging. We deliberately stay quiet here.
90
+ },
91
+ });
92
+ return () => handle.close();
93
+ }, [api]);
53
94
  useEffect(() => {
54
95
  if (!open)
55
96
  return;
@@ -71,14 +112,25 @@ export function NotificationBell({ pollIntervalMs = 30_000, onSelect, onError, a
71
112
  onSelect?.(n);
72
113
  }, [api, onError, onSelect]);
73
114
  const handleMarkAll = useCallback(async () => {
74
- const previous = items.slice();
115
+ // Snapshot ONLY the previously-unread IDs (and their original
116
+ // readAt values, which are null by definition). On rollback we
117
+ // restore those specific rows and leave anything that arrived
118
+ // via SSE while the request was in flight alone — otherwise a
119
+ // failed mark-all-read would drop fresh notifications.
120
+ const previouslyUnreadIds = items.filter((n) => n.readAt === null).map((n) => n.id);
121
+ if (previouslyUnreadIds.length === 0)
122
+ return;
123
+ const ids = new Set(previouslyUnreadIds);
75
124
  const now = new Date().toISOString();
76
- setItems(items.map((n) => ({ ...n, readAt: n.readAt ?? now })));
125
+ setItems((arr) => arr.map((n) => (ids.has(n.id) ? { ...n, readAt: n.readAt ?? now } : n)));
77
126
  setUnread(0);
78
127
  const out = await api.markAllRead();
79
128
  if (!out.ok) {
80
129
  onError?.(out.error);
81
- setItems(previous);
130
+ // Functional rollback: only revert the rows we optimistically
131
+ // marked. Anything mid-flight (SSE arrivals, locally-added) is
132
+ // untouched.
133
+ setItems((arr) => arr.map((n) => (ids.has(n.id) ? { ...n, readAt: null } : n)));
82
134
  await refreshUnread();
83
135
  }
84
136
  }, [api, items, onError, refreshUnread]);
@@ -1 +1 @@
1
- {"version":3,"file":"NotificationBell.js","sourceRoot":"","sources":["../../src/components/NotificationBell.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"NotificationBell.js","sourceRoot":"","sources":["../../src/components/NotificationBell.tsx"],"names":[],"mappings":"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"}
package/dist/lib/api.d.ts CHANGED
@@ -1,4 +1,12 @@
1
1
  export declare function setApiBase(path: string): void;
2
+ /**
3
+ * Current API base path. Exposed so non-`fetch` consumers (in
4
+ * particular `EventSource` for SSE channels) can resolve URLs against
5
+ * the same prefix configured via `setApiBase()`. Keep this read-only
6
+ * — mutation must always flow through `setApiBase()` so we never have
7
+ * two callers with diverging views of the base.
8
+ */
9
+ export declare function getApiBase(): string;
2
10
  export declare function ensureCsrfToken(): Promise<void>;
3
11
  export declare function cmsApi<T = unknown>(endpoint: string, options?: RequestInit): Promise<{
4
12
  data?: T;
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/lib/api.ts"],"names":[],"mappings":"AAIA,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,QAEtC;AAmBD,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAcrD;AAcD,wBAAsB,MAAM,CAAC,CAAC,GAAG,OAAO,EACtC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC;IAAE,IAAI,CAAC,EAAE,CAAC,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAsCvD"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/lib/api.ts"],"names":[],"mappings":"AAIA,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,QAEtC;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAmBD,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAcrD;AAcD,wBAAsB,MAAM,CAAC,CAAC,GAAG,OAAO,EACtC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC;IAAE,IAAI,CAAC,EAAE,CAAC,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAsCvD"}
package/dist/lib/api.js CHANGED
@@ -3,6 +3,16 @@ let basePath = DEFAULT_BASE;
3
3
  export function setApiBase(path) {
4
4
  basePath = path;
5
5
  }
6
+ /**
7
+ * Current API base path. Exposed so non-`fetch` consumers (in
8
+ * particular `EventSource` for SSE channels) can resolve URLs against
9
+ * the same prefix configured via `setApiBase()`. Keep this read-only
10
+ * — mutation must always flow through `setApiBase()` so we never have
11
+ * two callers with diverging views of the base.
12
+ */
13
+ export function getApiBase() {
14
+ return basePath;
15
+ }
6
16
  function getCsrfToken() {
7
17
  if (typeof document === 'undefined')
8
18
  return '';
@@ -1 +1 @@
1
- {"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/lib/api.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/lib/api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ export interface MentionUser {
2
+ id: string;
3
+ name: string;
4
+ email: string;
5
+ }
6
+ export type MentionSearchOutcome = {
7
+ ok: true;
8
+ result: MentionUser[];
9
+ } | {
10
+ ok: false;
11
+ error: string;
12
+ status: number;
13
+ };
14
+ export declare function searchMentionUsers(query: string): Promise<MentionSearchOutcome>;
15
+ /**
16
+ * Description of an active `@`-mention being typed in a textarea / input.
17
+ *
18
+ * - `start` and `end` are character offsets into the field value: the
19
+ * `@` itself sits at `start`, and `end` is the caret position
20
+ * (exclusive end of the query slice).
21
+ * - `query` is the substring between the `@` and the caret — exactly
22
+ * what we should pass to `searchMentionUsers()`.
23
+ *
24
+ * Callers replace `value.slice(start, end)` with the chosen token
25
+ * (`@<userId> `, including the trailing space) to commit a selection.
26
+ */
27
+ export interface MentionContext {
28
+ start: number;
29
+ end: number;
30
+ query: string;
31
+ }
32
+ /**
33
+ * Find an active mention at the caret. Mirrors the server-side
34
+ * `extractMentionedUserIds` regex
35
+ * (`/(^|\s)@[a-zA-Z0-9_-]{3,64}\b/g`) so the picker's intuition
36
+ * about what counts as a mention can never disagree with the
37
+ * server's notification routing.
38
+ *
39
+ * Returns `null` whenever:
40
+ * - the caret is at offset 0,
41
+ * - there is no `@` between the caret and the previous whitespace
42
+ * boundary,
43
+ * - the `@` is preceded by a non-whitespace, non-newline character
44
+ * (so an email address mid-word like `foo@bar` does NOT open
45
+ * the picker), or
46
+ * - the candidate query contains a character outside the mention
47
+ * charset (`[a-zA-Z0-9_-]`).
48
+ *
49
+ * The picker DOES open with a 0–2 character query — that's the
50
+ * "you've started typing a mention" UX. But it can NEVER commit
51
+ * such a token: `insertMentionToken` only fires for a `MentionUser`
52
+ * which always has an id of at least 3 chars, matching the server
53
+ * regex's min-length requirement.
54
+ */
55
+ export declare function detectMentionContext(value: string, caret: number): MentionContext | null;
56
+ /**
57
+ * Apply a mention selection to a text value. Returns the new value
58
+ * plus the caret position where the next keystroke should land.
59
+ *
60
+ * Inserts `@<userId> ` (trailing space) so the user can keep typing
61
+ * without manually moving the cursor past the token. If the caret
62
+ * already sits before a space, we don't insert a duplicate space.
63
+ */
64
+ export interface MentionInsertion {
65
+ value: string;
66
+ caret: number;
67
+ }
68
+ export declare function insertMentionToken(value: string, context: MentionContext, userId: string): MentionInsertion;
69
+ //# sourceMappingURL=mention-picker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mention-picker.d.ts","sourceRoot":"","sources":["../../src/lib/mention-picker.ts"],"names":[],"mappings":"AAuBA,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,MAAM,oBAAoB,GAC5B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,WAAW,EAAE,CAAA;CAAE,GACnC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAEhD,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAerF;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;CACd;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAiCxF;AAwBD;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CACd;AAED,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,cAAc,EACvB,MAAM,EAAE,MAAM,GACb,gBAAgB,CAclB"}