@actuate-media/cms-admin 0.12.0 → 0.13.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 (157) hide show
  1. package/dist/__tests__/components/comment-mark.test.d.ts +2 -0
  2. package/dist/__tests__/components/comment-mark.test.d.ts.map +1 -0
  3. package/dist/__tests__/components/comment-mark.test.js +89 -0
  4. package/dist/__tests__/components/comment-mark.test.js.map +1 -0
  5. package/dist/__tests__/components/comment-side-panel.test.d.ts +2 -0
  6. package/dist/__tests__/components/comment-side-panel.test.d.ts.map +1 -0
  7. package/dist/__tests__/components/comment-side-panel.test.js +291 -0
  8. package/dist/__tests__/components/comment-side-panel.test.js.map +1 -0
  9. package/dist/__tests__/components/notification-bell.test.d.ts +2 -0
  10. package/dist/__tests__/components/notification-bell.test.d.ts.map +1 -0
  11. package/dist/__tests__/components/notification-bell.test.js +346 -0
  12. package/dist/__tests__/components/notification-bell.test.js.map +1 -0
  13. package/dist/__tests__/components/offline-status.test.d.ts +2 -0
  14. package/dist/__tests__/components/offline-status.test.d.ts.map +1 -0
  15. package/dist/__tests__/components/offline-status.test.js +56 -0
  16. package/dist/__tests__/components/offline-status.test.js.map +1 -0
  17. package/dist/__tests__/components/presence-chips-helpers.test.d.ts +2 -0
  18. package/dist/__tests__/components/presence-chips-helpers.test.d.ts.map +1 -0
  19. package/dist/__tests__/components/presence-chips-helpers.test.js +35 -0
  20. package/dist/__tests__/components/presence-chips-helpers.test.js.map +1 -0
  21. package/dist/__tests__/lib/active-editor.test.d.ts +2 -0
  22. package/dist/__tests__/lib/active-editor.test.d.ts.map +1 -0
  23. package/dist/__tests__/lib/active-editor.test.js +57 -0
  24. package/dist/__tests__/lib/active-editor.test.js.map +1 -0
  25. package/dist/__tests__/lib/coauthor-client.test.d.ts +2 -0
  26. package/dist/__tests__/lib/coauthor-client.test.d.ts.map +1 -0
  27. package/dist/__tests__/lib/coauthor-client.test.js +173 -0
  28. package/dist/__tests__/lib/coauthor-client.test.js.map +1 -0
  29. package/dist/__tests__/lib/collaboration-provider.test.d.ts +2 -0
  30. package/dist/__tests__/lib/collaboration-provider.test.d.ts.map +1 -0
  31. package/dist/__tests__/lib/collaboration-provider.test.js +335 -0
  32. package/dist/__tests__/lib/collaboration-provider.test.js.map +1 -0
  33. package/dist/__tests__/lib/comment-anchor.test.d.ts +2 -0
  34. package/dist/__tests__/lib/comment-anchor.test.d.ts.map +1 -0
  35. package/dist/__tests__/lib/comment-anchor.test.js +173 -0
  36. package/dist/__tests__/lib/comment-anchor.test.js.map +1 -0
  37. package/dist/__tests__/lib/comments-client.test.d.ts +2 -0
  38. package/dist/__tests__/lib/comments-client.test.d.ts.map +1 -0
  39. package/dist/__tests__/lib/comments-client.test.js +202 -0
  40. package/dist/__tests__/lib/comments-client.test.js.map +1 -0
  41. package/dist/__tests__/lib/notifications-client.test.d.ts +2 -0
  42. package/dist/__tests__/lib/notifications-client.test.d.ts.map +1 -0
  43. package/dist/__tests__/lib/notifications-client.test.js +283 -0
  44. package/dist/__tests__/lib/notifications-client.test.js.map +1 -0
  45. package/dist/__tests__/lib/slash-command.test.d.ts +2 -0
  46. package/dist/__tests__/lib/slash-command.test.d.ts.map +1 -0
  47. package/dist/__tests__/lib/slash-command.test.js +123 -0
  48. package/dist/__tests__/lib/slash-command.test.js.map +1 -0
  49. package/dist/actuate-admin.css +1 -1
  50. package/dist/components/AICoauthorBubbleMenu.d.ts +18 -0
  51. package/dist/components/AICoauthorBubbleMenu.d.ts.map +1 -0
  52. package/dist/components/AICoauthorBubbleMenu.js +113 -0
  53. package/dist/components/AICoauthorBubbleMenu.js.map +1 -0
  54. package/dist/components/CoauthorResultPopover.d.ts +28 -0
  55. package/dist/components/CoauthorResultPopover.d.ts.map +1 -0
  56. package/dist/components/CoauthorResultPopover.js +42 -0
  57. package/dist/components/CoauthorResultPopover.js.map +1 -0
  58. package/dist/components/CommandPalette.d.ts +1 -1
  59. package/dist/components/CommandPalette.d.ts.map +1 -1
  60. package/dist/components/CommandPalette.js +138 -40
  61. package/dist/components/CommandPalette.js.map +1 -1
  62. package/dist/components/CommentMark.d.ts +54 -0
  63. package/dist/components/CommentMark.d.ts.map +1 -0
  64. package/dist/components/CommentMark.js +104 -0
  65. package/dist/components/CommentMark.js.map +1 -0
  66. package/dist/components/CommentSidePanel.d.ts +86 -0
  67. package/dist/components/CommentSidePanel.d.ts.map +1 -0
  68. package/dist/components/CommentSidePanel.js +165 -0
  69. package/dist/components/CommentSidePanel.js.map +1 -0
  70. package/dist/components/LivePreview.d.ts.map +1 -1
  71. package/dist/components/LivePreview.js +80 -16
  72. package/dist/components/LivePreview.js.map +1 -1
  73. package/dist/components/NotificationBell.d.ts +66 -0
  74. package/dist/components/NotificationBell.d.ts.map +1 -0
  75. package/dist/components/NotificationBell.js +142 -0
  76. package/dist/components/NotificationBell.js.map +1 -0
  77. package/dist/components/OfflineStatus.d.ts +39 -0
  78. package/dist/components/OfflineStatus.d.ts.map +1 -0
  79. package/dist/components/OfflineStatus.js +47 -0
  80. package/dist/components/OfflineStatus.js.map +1 -0
  81. package/dist/components/PresenceChips.d.ts +31 -0
  82. package/dist/components/PresenceChips.d.ts.map +1 -0
  83. package/dist/components/PresenceChips.js +58 -0
  84. package/dist/components/PresenceChips.js.map +1 -0
  85. package/dist/components/TipTapEditor.d.ts +61 -1
  86. package/dist/components/TipTapEditor.d.ts.map +1 -1
  87. package/dist/components/TipTapEditor.js +211 -8
  88. package/dist/components/TipTapEditor.js.map +1 -1
  89. package/dist/components/ui/Badge.d.ts +1 -1
  90. package/dist/index.d.ts +8 -0
  91. package/dist/index.d.ts.map +1 -1
  92. package/dist/index.js +4 -0
  93. package/dist/index.js.map +1 -1
  94. package/dist/lib/active-editor.d.ts +6 -0
  95. package/dist/lib/active-editor.d.ts.map +1 -0
  96. package/dist/lib/active-editor.js +35 -0
  97. package/dist/lib/active-editor.js.map +1 -0
  98. package/dist/lib/api.d.ts +8 -0
  99. package/dist/lib/api.d.ts.map +1 -1
  100. package/dist/lib/api.js +10 -0
  101. package/dist/lib/api.js.map +1 -1
  102. package/dist/lib/coauthor-client.d.ts +72 -0
  103. package/dist/lib/coauthor-client.d.ts.map +1 -0
  104. package/dist/lib/coauthor-client.js +98 -0
  105. package/dist/lib/coauthor-client.js.map +1 -0
  106. package/dist/lib/collaboration-provider.d.ts +142 -0
  107. package/dist/lib/collaboration-provider.d.ts.map +1 -0
  108. package/dist/lib/collaboration-provider.js +168 -0
  109. package/dist/lib/collaboration-provider.js.map +1 -0
  110. package/dist/lib/comment-anchor.d.ts +86 -0
  111. package/dist/lib/comment-anchor.d.ts.map +1 -0
  112. package/dist/lib/comment-anchor.js +139 -0
  113. package/dist/lib/comment-anchor.js.map +1 -0
  114. package/dist/lib/comments-client.d.ts +106 -0
  115. package/dist/lib/comments-client.d.ts.map +1 -0
  116. package/dist/lib/comments-client.js +138 -0
  117. package/dist/lib/comments-client.js.map +1 -0
  118. package/dist/lib/notifications-client.d.ts +121 -0
  119. package/dist/lib/notifications-client.d.ts.map +1 -0
  120. package/dist/lib/notifications-client.js +173 -0
  121. package/dist/lib/notifications-client.js.map +1 -0
  122. package/dist/lib/slash-command.d.ts +39 -0
  123. package/dist/lib/slash-command.d.ts.map +1 -0
  124. package/dist/lib/slash-command.js +50 -0
  125. package/dist/lib/slash-command.js.map +1 -0
  126. package/package.json +12 -3
  127. package/src/__tests__/components/comment-mark.test.ts +99 -0
  128. package/src/__tests__/components/comment-side-panel.test.tsx +343 -0
  129. package/src/__tests__/components/notification-bell.test.tsx +379 -0
  130. package/src/__tests__/components/offline-status.test.tsx +66 -0
  131. package/src/__tests__/components/presence-chips-helpers.test.ts +44 -0
  132. package/src/__tests__/lib/active-editor.test.ts +70 -0
  133. package/src/__tests__/lib/coauthor-client.test.ts +213 -0
  134. package/src/__tests__/lib/collaboration-provider.test.ts +425 -0
  135. package/src/__tests__/lib/comment-anchor.test.ts +188 -0
  136. package/src/__tests__/lib/comments-client.test.ts +239 -0
  137. package/src/__tests__/lib/notifications-client.test.ts +339 -0
  138. package/src/__tests__/lib/slash-command.test.ts +146 -0
  139. package/src/components/AICoauthorBubbleMenu.tsx +187 -0
  140. package/src/components/CoauthorResultPopover.tsx +193 -0
  141. package/src/components/CommandPalette.tsx +397 -209
  142. package/src/components/CommentMark.ts +138 -0
  143. package/src/components/CommentSidePanel.tsx +521 -0
  144. package/src/components/LivePreview.tsx +117 -11
  145. package/src/components/NotificationBell.tsx +314 -0
  146. package/src/components/OfflineStatus.tsx +90 -0
  147. package/src/components/PresenceChips.tsx +150 -0
  148. package/src/components/TipTapEditor.tsx +310 -12
  149. package/src/index.ts +20 -0
  150. package/src/lib/active-editor.ts +39 -0
  151. package/src/lib/api.ts +11 -0
  152. package/src/lib/coauthor-client.ts +160 -0
  153. package/src/lib/collaboration-provider.ts +306 -0
  154. package/src/lib/comment-anchor.ts +179 -0
  155. package/src/lib/comments-client.ts +208 -0
  156. package/src/lib/notifications-client.ts +292 -0
  157. package/src/lib/slash-command.ts +67 -0
@@ -0,0 +1,86 @@
1
+ import React from 'react';
2
+ import { type Comment, type CommentAnchor, type CommentOutcome } from '../lib/comments-client.js';
3
+ /**
4
+ * Side-panel view of the slice-4 comments API. Renders threaded comments
5
+ * for a document, lets the current user compose new threads and replies,
6
+ * and offers per-comment actions: edit, resolve / reopen, delete.
7
+ *
8
+ * The component is *pure I/O* against the comments-client module —
9
+ * anchor encoding/decoding, editor mark painting, and bubble-menu wiring
10
+ * are layered on top in `TipTapEditor.tsx` through the `comments` prop.
11
+ * Keeping the panel logic agnostic of TipTap means the unit tests can
12
+ * verify thread ordering / state transitions without spinning up an
13
+ * editor.
14
+ *
15
+ * Behaviour
16
+ * ─────────
17
+ * - Active comments (not resolved, not deleted) are shown by default.
18
+ * - A toggle reveals resolved threads in the same list, sorted by
19
+ * resolved-at descending so the most recently closed appears first.
20
+ * - Composer accepts free-form text. When `onComposeAnchor` returns a
21
+ * non-null anchor the new comment is bound to that range; otherwise
22
+ * it is a document-level comment.
23
+ * - The list refreshes after every successful mutation so optimistic
24
+ * state can't drift from the server.
25
+ *
26
+ * Errors fall through to the `onError` callback so the host shell can
27
+ * surface them with its own toast / banner system (we don't import
28
+ * sonner here to keep the component testable in plain JSDOM).
29
+ */
30
+ export interface CommentSidePanelProps {
31
+ documentId: string;
32
+ /** Current user id — used to gate edit / delete affordances. */
33
+ currentUserId: string;
34
+ /** Set to `true` to show admin-only affordances (delete others'). */
35
+ isAdmin?: boolean;
36
+ /**
37
+ * Called when the user clicks "Comment on selection". Should return
38
+ * the encoded anchor for the active editor selection, or `null` for a
39
+ * doc-level comment. Wiring lives in `TipTapEditor` so this component
40
+ * stays editor-agnostic.
41
+ */
42
+ onComposeAnchor?: () => CommentAnchor | null;
43
+ /**
44
+ * Called whenever the active comment changes (focus shift / open
45
+ * thread). The editor uses this to scroll the highlighted range into
46
+ * view and paint the `actuate-comment--active` modifier.
47
+ */
48
+ onActiveCommentChange?: (commentId: string | null) => void;
49
+ /** Error sink — bubble up server failures to a toast / banner. */
50
+ onError?: (message: string) => void;
51
+ /**
52
+ * Override the comments-client API. Tests inject a fake; real callers
53
+ * should leave this unset.
54
+ */
55
+ api?: CommentsApi;
56
+ className?: string;
57
+ }
58
+ /**
59
+ * Tiny adapter type that lets tests swap the comments-client module out.
60
+ * Mirrors the slice-4 client surface 1:1 — keeping it explicit prevents
61
+ * accidental drift when new endpoints are added later.
62
+ */
63
+ export interface CommentsApi {
64
+ list: (documentId: string, opts?: {
65
+ includeResolved?: boolean;
66
+ }) => Promise<CommentOutcome<Comment[]>>;
67
+ create: (input: {
68
+ documentId: string;
69
+ body: string;
70
+ anchor?: CommentAnchor | null;
71
+ parentId?: string | null;
72
+ }) => Promise<CommentOutcome<Comment>>;
73
+ update: (id: string, body: string) => Promise<CommentOutcome<Comment>>;
74
+ resolve: (id: string) => Promise<CommentOutcome<Comment>>;
75
+ reopen: (id: string) => Promise<CommentOutcome<Comment>>;
76
+ remove: (id: string) => Promise<CommentOutcome<Comment>>;
77
+ }
78
+ export declare function CommentSidePanel({ documentId, currentUserId, isAdmin, onComposeAnchor, onActiveCommentChange, onError, api, className, }: CommentSidePanelProps): React.JSX.Element;
79
+ /**
80
+ * Cheap "n seconds / minutes / hours / days ago" formatter. We avoid
81
+ * importing a full date-fns dependency just for this view; the panel is
82
+ * fine being approximate.
83
+ */
84
+ declare function formatRelative(iso: string): string;
85
+ export { formatRelative as _formatRelative };
86
+ //# sourceMappingURL=CommentSidePanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CommentSidePanel.d.ts","sourceRoot":"","sources":["../../src/components/CommentSidePanel.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA4D,MAAM,OAAO,CAAA;AAEhF,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,aAAa,EASlB,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAA;AAElC;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAA;IAClB,gEAAgE;IAChE,aAAa,EAAE,MAAM,CAAA;IACrB,qEAAqE;IACrE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,aAAa,GAAG,IAAI,CAAA;IAC5C;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IAC1D,kEAAkE;IAClE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IACnC;;;OAGG;IACH,GAAG,CAAC,EAAE,WAAW,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,CACJ,UAAU,EAAE,MAAM,EAClB,IAAI,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,KACjC,OAAO,CAAC,cAAc,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;IACvC,MAAM,EAAE,CAAC,KAAK,EAAE;QACd,UAAU,EAAE,MAAM,CAAA;QAClB,IAAI,EAAE,MAAM,CAAA;QACZ,MAAM,CAAC,EAAE,aAAa,GAAG,IAAI,CAAA;QAC7B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KACzB,KAAK,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAA;IACtC,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAA;IACtE,OAAO,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAA;IACzD,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAA;IACxD,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAA;CACzD;AAWD,wBAAgB,gBAAgB,CAAC,EAC/B,UAAU,EACV,aAAa,EACb,OAAe,EACf,eAAe,EACf,qBAAqB,EACrB,OAAO,EACP,GAAgB,EAChB,SAAS,GACV,EAAE,qBAAqB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CA2K3C;AAwND;;;;GAIG;AACH,iBAAS,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAW3C;AAGD,OAAO,EAAE,cAAc,IAAI,eAAe,EAAE,CAAA"}
@@ -0,0 +1,165 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
+ import { createComment as apiCreateComment, deleteComment as apiDeleteComment, groupCommentsIntoThreads, listComments as apiListComments, reopenComment as apiReopenComment, resolveComment as apiResolveComment, updateComment as apiUpdateComment, } from '../lib/comments-client.js';
5
+ const defaultApi = {
6
+ list: (documentId, opts) => apiListComments(documentId, opts),
7
+ create: (input) => apiCreateComment(input),
8
+ update: (id, body) => apiUpdateComment(id, body),
9
+ resolve: (id) => apiResolveComment(id),
10
+ reopen: (id) => apiReopenComment(id),
11
+ remove: (id) => apiDeleteComment(id),
12
+ };
13
+ export function CommentSidePanel({ documentId, currentUserId, isAdmin = false, onComposeAnchor, onActiveCommentChange, onError, api = defaultApi, className, }) {
14
+ const [comments, setComments] = useState([]);
15
+ const [includeResolved, setIncludeResolved] = useState(false);
16
+ const [loading, setLoading] = useState(true);
17
+ const [draft, setDraft] = useState('');
18
+ const [activeCommentId, setActiveCommentId] = useState(null);
19
+ const mountedRef = useRef(true);
20
+ useEffect(() => {
21
+ return () => {
22
+ mountedRef.current = false;
23
+ };
24
+ }, []);
25
+ const setActive = useCallback((id) => {
26
+ setActiveCommentId(id);
27
+ onActiveCommentChange?.(id);
28
+ }, [onActiveCommentChange]);
29
+ const refresh = useCallback(async () => {
30
+ setLoading(true);
31
+ const out = await api.list(documentId, { includeResolved });
32
+ if (!mountedRef.current)
33
+ return;
34
+ if (!out.ok) {
35
+ onError?.(out.error);
36
+ setComments([]);
37
+ }
38
+ else {
39
+ setComments(out.result);
40
+ }
41
+ setLoading(false);
42
+ }, [api, documentId, includeResolved, onError]);
43
+ useEffect(() => {
44
+ void refresh();
45
+ }, [refresh]);
46
+ const threads = useMemo(() => groupCommentsIntoThreads(comments), [comments]);
47
+ const handleCompose = useCallback(async () => {
48
+ const body = draft.trim();
49
+ if (!body)
50
+ return;
51
+ const anchor = onComposeAnchor?.() ?? null;
52
+ const out = await api.create({ documentId, body, anchor });
53
+ if (!out.ok) {
54
+ onError?.(out.error);
55
+ return;
56
+ }
57
+ setDraft('');
58
+ setActive(out.result.id);
59
+ await refresh();
60
+ }, [draft, onComposeAnchor, api, documentId, onError, setActive, refresh]);
61
+ const handleReply = useCallback(async (parentId, body) => {
62
+ const trimmed = body.trim();
63
+ if (!trimmed)
64
+ return;
65
+ const out = await api.create({ documentId, body: trimmed, parentId });
66
+ if (!out.ok) {
67
+ onError?.(out.error);
68
+ return;
69
+ }
70
+ await refresh();
71
+ }, [api, documentId, onError, refresh]);
72
+ const handleEdit = useCallback(async (id, body) => {
73
+ const out = await api.update(id, body);
74
+ if (!out.ok) {
75
+ onError?.(out.error);
76
+ return false;
77
+ }
78
+ await refresh();
79
+ return true;
80
+ }, [api, onError, refresh]);
81
+ const handleResolve = useCallback(async (id, alreadyResolved) => {
82
+ const out = alreadyResolved ? await api.reopen(id) : await api.resolve(id);
83
+ if (!out.ok) {
84
+ onError?.(out.error);
85
+ return;
86
+ }
87
+ await refresh();
88
+ }, [api, onError, refresh]);
89
+ const handleDelete = useCallback(async (id) => {
90
+ const out = await api.remove(id);
91
+ if (!out.ok) {
92
+ onError?.(out.error);
93
+ return;
94
+ }
95
+ if (activeCommentId === id)
96
+ setActive(null);
97
+ await refresh();
98
+ }, [api, onError, refresh, activeCommentId, setActive]);
99
+ const wrapperClass = `flex h-full flex-col gap-3 border-l border-gray-200 bg-white p-3 ${className ?? ''}`;
100
+ return (_jsxs("aside", { className: wrapperClass, "data-testid": "comment-side-panel", children: [_jsxs("header", { className: "flex items-center justify-between", children: [_jsx("h2", { className: "text-sm font-semibold text-gray-900", children: "Comments" }), _jsxs("label", { className: "flex cursor-pointer items-center gap-1 text-xs text-gray-600", children: [_jsx("input", { type: "checkbox", checked: includeResolved, onChange: (e) => setIncludeResolved(e.target.checked), "data-testid": "include-resolved-toggle" }), "Show resolved"] })] }), _jsxs("section", { className: "flex flex-col gap-1.5", children: [_jsx("textarea", { className: "w-full resize-none rounded border border-gray-300 p-2 text-sm focus:border-blue-500 focus:outline-none", rows: 3, placeholder: "Write a comment\u2026", value: draft, onChange: (e) => setDraft(e.target.value), "data-testid": "comment-composer" }), _jsx("div", { className: "flex justify-end", children: _jsx("button", { type: "button", className: "rounded bg-blue-600 px-3 py-1 text-xs font-medium text-white hover:bg-blue-500 disabled:opacity-50", disabled: !draft.trim(), onClick: handleCompose, "data-testid": "comment-submit", children: "Comment" }) })] }), _jsx("section", { className: "flex-1 overflow-y-auto", "aria-busy": loading, "data-testid": "comment-list", children: loading ? (_jsx("p", { className: "text-xs text-gray-500", children: "Loading\u2026" })) : threads.length === 0 ? (_jsx("p", { className: "text-xs text-gray-500", children: "No comments yet." })) : (_jsx("ul", { className: "flex flex-col gap-2", children: threads.map((thread) => (_jsx(CommentThreadView, { thread: thread, currentUserId: currentUserId, isAdmin: isAdmin, isActive: activeCommentId === thread.root.id, onFocus: () => setActive(thread.root.id), onReply: (body) => handleReply(thread.root.id, body), onEdit: (id, body) => handleEdit(id, body), onToggleResolve: (id, alreadyResolved) => handleResolve(id, alreadyResolved), onDelete: (id) => handleDelete(id) }, thread.root.id))) })) })] }));
101
+ }
102
+ function CommentThreadView({ thread, currentUserId, isAdmin, isActive, onFocus, onReply, onEdit, onToggleResolve, onDelete, }) {
103
+ const [replyDraft, setReplyDraft] = useState('');
104
+ const cardClass = `rounded border bg-gray-50 p-2 ${isActive ? 'border-yellow-400 ring-1 ring-yellow-300' : 'border-gray-200'}`;
105
+ return (_jsxs("li", { className: cardClass, onClick: onFocus, "data-testid": `comment-thread-${thread.root.id}`, "data-active": isActive ? 'true' : 'false', children: [_jsx(CommentRow, { comment: thread.root, currentUserId: currentUserId, isAdmin: isAdmin, onEdit: onEdit, onToggleResolve: () => onToggleResolve(thread.root.id, thread.root.isResolved), onDelete: () => onDelete(thread.root.id) }), thread.replies.length > 0 && (_jsx("ul", { className: "mt-2 flex flex-col gap-1 border-l-2 border-gray-200 pl-2", children: thread.replies.map((reply) => (_jsx("li", { children: _jsx(CommentRow, { comment: reply, currentUserId: currentUserId, isAdmin: isAdmin, onEdit: onEdit,
106
+ // Replies inherit the thread's resolve action; we only
107
+ // expose delete here.
108
+ onToggleResolve: null, onDelete: () => onDelete(reply.id) }) }, reply.id))) })), _jsxs("div", { className: "mt-2 flex gap-1", children: [_jsx("input", { className: "flex-1 rounded border border-gray-300 px-2 py-1 text-xs focus:border-blue-500 focus:outline-none", placeholder: "Reply\u2026", value: replyDraft, onChange: (e) => setReplyDraft(e.target.value), "data-testid": `reply-input-${thread.root.id}`, onClick: (e) => e.stopPropagation() }), _jsx("button", { type: "button", className: "rounded bg-gray-700 px-2 py-1 text-xs font-medium text-white hover:bg-gray-600 disabled:opacity-50", disabled: !replyDraft.trim(), onClick: (e) => {
109
+ e.stopPropagation();
110
+ onReply(replyDraft);
111
+ setReplyDraft('');
112
+ }, "data-testid": `reply-submit-${thread.root.id}`, children: "Reply" })] })] }));
113
+ }
114
+ function CommentRow({ comment, currentUserId, isAdmin, onEdit, onToggleResolve, onDelete, }) {
115
+ const [editing, setEditing] = useState(false);
116
+ const [draft, setDraft] = useState(comment.body);
117
+ useEffect(() => {
118
+ setDraft(comment.body);
119
+ }, [comment.body]);
120
+ const canMutate = isAdmin || comment.userId === currentUserId;
121
+ const meta = (_jsxs("div", { className: "text-xs text-gray-500", children: [comment.userId ?? 'deleted user', " \u00B7 ", formatRelative(comment.createdAt), comment.isResolved ? ' · resolved' : null] }));
122
+ return (_jsxs("div", { className: "flex flex-col gap-1", children: [meta, editing ? (_jsxs(_Fragment, { children: [_jsx("textarea", { className: "w-full resize-none rounded border border-gray-300 p-1 text-sm", rows: 3, value: draft, onChange: (e) => setDraft(e.target.value), "data-testid": `edit-textarea-${comment.id}`, onClick: (e) => e.stopPropagation() }), _jsxs("div", { className: "flex gap-1", children: [_jsx("button", { type: "button", className: "rounded bg-blue-600 px-2 py-1 text-xs font-medium text-white", onClick: async (e) => {
123
+ e.stopPropagation();
124
+ const ok = await onEdit(comment.id, draft);
125
+ if (ok)
126
+ setEditing(false);
127
+ }, "data-testid": `edit-save-${comment.id}`, children: "Save" }), _jsx("button", { type: "button", className: "rounded border border-gray-300 px-2 py-1 text-xs", onClick: (e) => {
128
+ e.stopPropagation();
129
+ setDraft(comment.body);
130
+ setEditing(false);
131
+ }, "data-testid": `edit-cancel-${comment.id}`, children: "Cancel" })] })] })) : (_jsx("p", { className: "text-sm whitespace-pre-wrap text-gray-900", "data-testid": `comment-body-${comment.id}`, children: comment.body })), !editing && (_jsxs("div", { className: "flex flex-wrap gap-1 text-xs", children: [canMutate && (_jsx("button", { type: "button", className: "text-gray-600 hover:text-gray-900", onClick: (e) => {
132
+ e.stopPropagation();
133
+ setEditing(true);
134
+ }, "data-testid": `edit-${comment.id}`, children: "Edit" })), onToggleResolve && (_jsx("button", { type: "button", className: "text-gray-600 hover:text-gray-900", onClick: (e) => {
135
+ e.stopPropagation();
136
+ onToggleResolve();
137
+ }, "data-testid": `resolve-${comment.id}`, children: comment.isResolved ? 'Reopen' : 'Resolve' })), canMutate && (_jsx("button", { type: "button", className: "text-red-600 hover:text-red-700", onClick: (e) => {
138
+ e.stopPropagation();
139
+ onDelete();
140
+ }, "data-testid": `delete-${comment.id}`, children: "Delete" }))] }))] }));
141
+ }
142
+ /**
143
+ * Cheap "n seconds / minutes / hours / days ago" formatter. We avoid
144
+ * importing a full date-fns dependency just for this view; the panel is
145
+ * fine being approximate.
146
+ */
147
+ function formatRelative(iso) {
148
+ const t = Date.parse(iso);
149
+ if (Number.isNaN(t))
150
+ return iso;
151
+ const deltaSec = Math.max(0, Math.round((Date.now() - t) / 1000));
152
+ if (deltaSec < 60)
153
+ return `${deltaSec}s ago`;
154
+ const m = Math.round(deltaSec / 60);
155
+ if (m < 60)
156
+ return `${m}m ago`;
157
+ const h = Math.round(m / 60);
158
+ if (h < 24)
159
+ return `${h}h ago`;
160
+ const d = Math.round(h / 24);
161
+ return `${d}d ago`;
162
+ }
163
+ // Re-export for tests that build their own CommentsApi.
164
+ export { formatRelative as _formatRelative };
165
+ //# sourceMappingURL=CommentSidePanel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CommentSidePanel.js","sourceRoot":"","sources":["../../src/components/CommentSidePanel.tsx"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"LivePreview.d.ts","sourceRoot":"","sources":["../../src/components/LivePreview.tsx"],"names":[],"mappings":"AAMA,UAAU,gBAAgB;IACxB,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAUD,wBAAgB,WAAW,CAAC,EAC1B,UAAU,EACV,UAAU,EACV,UAAU,EACV,MAAM,EACN,OAAO,GACR,EAAE,gBAAgB,2CAsIlB"}
1
+ {"version":3,"file":"LivePreview.d.ts","sourceRoot":"","sources":["../../src/components/LivePreview.tsx"],"names":[],"mappings":"AAeA,UAAU,gBAAgB;IACxB,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AA4BD,wBAAgB,WAAW,CAAC,EAC1B,UAAU,EACV,UAAU,EACV,UAAU,EACV,MAAM,EACN,OAAO,GACR,EAAE,gBAAgB,2CAqNlB"}
@@ -1,47 +1,105 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useState, useRef, useEffect, useCallback } from 'react';
4
- import { Monitor, Tablet, Smartphone, RefreshCw, ExternalLink, X } from 'lucide-react';
4
+ import { Monitor, Tablet, Smartphone, RefreshCw, ExternalLink, X, Wifi, WifiOff, } from 'lucide-react';
5
5
  import { cmsApi } from '../lib/api.js';
6
6
  const VIEWPORT_WIDTHS = {
7
7
  desktop: '100%',
8
8
  tablet: '768px',
9
9
  mobile: '375px',
10
10
  };
11
+ const PREVIEW_STREAM_PATH = '/api/cms/preview/stream';
12
+ function postToIframe(iframe, src, data) {
13
+ if (!iframe?.contentWindow || !src)
14
+ return;
15
+ let targetOrigin;
16
+ try {
17
+ targetOrigin = new URL(src).origin;
18
+ }
19
+ catch {
20
+ targetOrigin = window.location.origin;
21
+ }
22
+ iframe.contentWindow.postMessage({ type: 'actuate-preview-update', data }, targetOrigin);
23
+ }
11
24
  export function LivePreview({ collection, documentId, previewUrl, values, onClose, }) {
12
25
  const iframeRef = useRef(null);
13
26
  const [viewport, setViewport] = useState('desktop');
14
27
  const [loading, setLoading] = useState(true);
15
28
  const [previewSrc, setPreviewSrc] = useState(null);
29
+ const [previewToken, setPreviewToken] = useState(null);
30
+ const [streamStatus, setStreamStatus] = useState('idle');
16
31
  useEffect(() => {
17
32
  if (!documentId || !previewUrl) {
18
33
  setPreviewSrc(null);
34
+ setPreviewToken(null);
19
35
  return;
20
36
  }
37
+ let cancelled = false;
21
38
  async function fetchToken() {
22
39
  const res = await cmsApi('/preview/token', {
23
40
  method: 'POST',
24
41
  body: JSON.stringify({ collection, documentId }),
25
42
  });
43
+ if (cancelled)
44
+ return;
26
45
  if (res.data?.token) {
27
46
  const sep = previewUrl.includes('?') ? '&' : '?';
47
+ setPreviewToken(res.data.token);
28
48
  setPreviewSrc(`${previewUrl}${sep}preview=true#token=${res.data.token}`);
29
49
  }
30
50
  }
31
51
  fetchToken();
52
+ return () => {
53
+ cancelled = true;
54
+ };
32
55
  }, [collection, documentId, previewUrl]);
33
56
  useEffect(() => {
34
- if (!iframeRef.current?.contentWindow || !previewSrc)
57
+ postToIframe(iframeRef.current, previewSrc, values);
58
+ }, [values, previewSrc]);
59
+ /**
60
+ * SSE pipe — when the server pushes a draft update for the same
61
+ * `{collection, documentId}`, forward the new `values` straight to the
62
+ * iframe via `postMessage`. The bus key matches the token's payload, so
63
+ * a second admin session updating the same draft (e.g. an AI agent)
64
+ * appears live in this preview without polling.
65
+ */
66
+ useEffect(() => {
67
+ if (typeof window === 'undefined' || typeof EventSource === 'undefined')
68
+ return;
69
+ if (!documentId || !previewToken) {
70
+ setStreamStatus('idle');
35
71
  return;
36
- let targetOrigin;
37
- try {
38
- targetOrigin = new URL(previewSrc).origin;
39
- }
40
- catch {
41
- targetOrigin = window.location.origin;
42
72
  }
43
- iframeRef.current.contentWindow.postMessage({ type: 'actuate-preview-update', data: values }, targetOrigin);
44
- }, [values, previewSrc]);
73
+ setStreamStatus('connecting');
74
+ const source = new EventSource(`${PREVIEW_STREAM_PATH}?token=${encodeURIComponent(previewToken)}`, { withCredentials: true });
75
+ const onReady = () => setStreamStatus('connected');
76
+ const onUpdate = (event) => {
77
+ try {
78
+ const payload = JSON.parse(event.data);
79
+ if (payload.values && typeof payload.values === 'object') {
80
+ postToIframe(iframeRef.current, previewSrc, payload.values);
81
+ }
82
+ }
83
+ catch {
84
+ // Ignore malformed events — the SSE wire format guarantees JSON,
85
+ // but a future schema bump shouldn't break this consumer.
86
+ }
87
+ };
88
+ const onError = () => setStreamStatus('offline');
89
+ const onOpen = () => setStreamStatus((s) => (s === 'offline' ? 'connecting' : s));
90
+ source.addEventListener('ready', onReady);
91
+ source.addEventListener('update', onUpdate);
92
+ source.addEventListener('error', onError);
93
+ source.addEventListener('open', onOpen);
94
+ return () => {
95
+ source.removeEventListener('ready', onReady);
96
+ source.removeEventListener('update', onUpdate);
97
+ source.removeEventListener('error', onError);
98
+ source.removeEventListener('open', onOpen);
99
+ source.close();
100
+ setStreamStatus('idle');
101
+ };
102
+ }, [documentId, previewToken, previewSrc]);
45
103
  const handleRefresh = useCallback(() => {
46
104
  if (iframeRef.current) {
47
105
  setLoading(true);
@@ -57,10 +115,16 @@ export function LivePreview({ collection, documentId, previewUrl, values, onClos
57
115
  ? 'Save the document first to enable preview'
58
116
  : 'Configure a preview URL in the collection settings to enable live preview' }), _jsx("button", { onClick: onClose, className: "mt-4 text-xs text-gray-400 hover:text-gray-600", children: "Close Preview" })] }));
59
117
  }
60
- return (_jsxs("div", { className: "flex h-full flex-col border-l border-gray-200 bg-white", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-200 bg-gray-50 px-3 py-2", children: [_jsx("div", { className: "flex items-center gap-1", children: [
61
- ['desktop', Monitor],
62
- ['tablet', Tablet],
63
- ['mobile', Smartphone],
64
- ].map(([vp, Icon]) => (_jsx("button", { onClick: () => setViewport(vp), className: `rounded p-1.5 ${viewport === vp ? 'bg-white text-blue-600 shadow-sm' : 'text-gray-400 hover:text-gray-600'}`, title: vp, children: _jsx(Icon, { className: "h-4 w-4" }) }, vp))) }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("button", { onClick: handleRefresh, className: "rounded p-1.5 text-gray-400 hover:text-gray-600", title: "Refresh", children: _jsx(RefreshCw, { className: "h-4 w-4" }) }), _jsx("button", { onClick: handleOpenExternal, className: "rounded p-1.5 text-gray-400 hover:text-gray-600", title: "Open in new tab", children: _jsx(ExternalLink, { className: "h-4 w-4" }) }), _jsx("button", { onClick: onClose, className: "rounded p-1.5 text-gray-400 hover:text-gray-600", title: "Close", children: _jsx(X, { className: "h-4 w-4" }) })] })] }), _jsx("div", { className: "flex flex-1 justify-center overflow-auto bg-gray-100 p-4", children: _jsxs("div", { style: { width: VIEWPORT_WIDTHS[viewport], maxWidth: '100%', transition: 'width 0.3s' }, className: "relative", children: [loading && (_jsx("div", { className: "absolute inset-0 z-10 flex items-center justify-center rounded-lg bg-white/80", children: _jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-b-2 border-blue-600" }) })), _jsx("iframe", { ref: iframeRef, src: previewSrc, className: "h-full w-full rounded-lg border border-gray-200 bg-white shadow-lg", style: { minHeight: '600px' }, onLoad: () => setLoading(false), title: "Page Preview" })] }) })] }));
118
+ return (_jsxs("div", { className: "flex h-full flex-col border-l border-gray-200 bg-white", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-200 bg-gray-50 px-3 py-2", children: [_jsxs("div", { className: "flex items-center gap-1", children: [[
119
+ ['desktop', Monitor],
120
+ ['tablet', Tablet],
121
+ ['mobile', Smartphone],
122
+ ].map(([vp, Icon]) => (_jsx("button", { onClick: () => setViewport(vp), className: `rounded p-1.5 ${viewport === vp ? 'bg-white text-blue-600 shadow-sm' : 'text-gray-400 hover:text-gray-600'}`, title: vp, children: _jsx(Icon, { className: "h-4 w-4" }) }, vp))), _jsx("span", { className: "ml-2 inline-flex items-center gap-1 rounded-full bg-white px-2 py-0.5 text-[10px] font-medium text-gray-600", title: streamStatus === 'connected'
123
+ ? 'Live preview is streaming updates from the server.'
124
+ : streamStatus === 'connecting'
125
+ ? 'Connecting to the live preview stream…'
126
+ : streamStatus === 'offline'
127
+ ? 'Live stream disconnected. Updates from this admin still work; cross-session updates will reconnect automatically.'
128
+ : 'Live preview stream idle.', children: streamStatus === 'connected' ? (_jsxs(_Fragment, { children: [_jsx(Wifi, { className: "h-3 w-3 text-green-600", "aria-hidden": true }), " Live"] })) : streamStatus === 'connecting' ? (_jsxs(_Fragment, { children: [_jsx(Wifi, { className: "h-3 w-3 animate-pulse text-blue-500", "aria-hidden": true }), " Connecting"] })) : streamStatus === 'offline' ? (_jsxs(_Fragment, { children: [_jsx(WifiOff, { className: "h-3 w-3 text-amber-500", "aria-hidden": true }), " Reconnecting"] })) : (_jsxs(_Fragment, { children: [_jsx(WifiOff, { className: "h-3 w-3 text-gray-400", "aria-hidden": true }), " Idle"] })) })] }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("button", { onClick: handleRefresh, className: "rounded p-1.5 text-gray-400 hover:text-gray-600", title: "Refresh", children: _jsx(RefreshCw, { className: "h-4 w-4" }) }), _jsx("button", { onClick: handleOpenExternal, className: "rounded p-1.5 text-gray-400 hover:text-gray-600", title: "Open in new tab", children: _jsx(ExternalLink, { className: "h-4 w-4" }) }), _jsx("button", { onClick: onClose, className: "rounded p-1.5 text-gray-400 hover:text-gray-600", title: "Close", children: _jsx(X, { className: "h-4 w-4" }) })] })] }), _jsx("div", { className: "flex flex-1 justify-center overflow-auto bg-gray-100 p-4", children: _jsxs("div", { style: { width: VIEWPORT_WIDTHS[viewport], maxWidth: '100%', transition: 'width 0.3s' }, className: "relative", children: [loading && (_jsx("div", { className: "absolute inset-0 z-10 flex items-center justify-center rounded-lg bg-white/80", children: _jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-b-2 border-blue-600" }) })), _jsx("iframe", { ref: iframeRef, src: previewSrc, className: "h-full w-full rounded-lg border border-gray-200 bg-white shadow-lg", style: { minHeight: '600px' }, onLoad: () => setLoading(false), title: "Page Preview" })] }) })] }));
65
129
  }
66
130
  //# sourceMappingURL=LivePreview.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"LivePreview.js","sourceRoot":"","sources":["../../src/components/LivePreview.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAChE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC,EAAE,MAAM,cAAc,CAAA;AACtF,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAYtC,MAAM,eAAe,GAA6B;IAChD,OAAO,EAAE,MAAM;IACf,MAAM,EAAE,OAAO;IACf,MAAM,EAAE,OAAO;CAChB,CAAA;AAED,MAAM,UAAU,WAAW,CAAC,EAC1B,UAAU,EACV,UAAU,EACV,UAAU,EACV,MAAM,EACN,OAAO,GACU;IACjB,MAAM,SAAS,GAAG,MAAM,CAAoB,IAAI,CAAC,CAAA;IACjD,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAW,SAAS,CAAC,CAAA;IAC7D,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAA;IAC5C,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAA;IAEjE,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,UAAU,IAAI,CAAC,UAAU,EAAE,CAAC;YAC/B,aAAa,CAAC,IAAI,CAAC,CAAA;YACnB,OAAM;QACR,CAAC;QAED,KAAK,UAAU,UAAU;YACvB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAoB,gBAAgB,EAAE;gBAC5D,MAAM,EAAE,MAAM;gBACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC;aACjD,CAAC,CAAA;YACF,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC;gBACpB,MAAM,GAAG,GAAG,UAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAA;gBACjD,aAAa,CAAC,GAAG,UAAU,GAAG,GAAG,sBAAsB,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAA;YAC1E,CAAC;QACH,CAAC;QAED,UAAU,EAAE,CAAA;IACd,CAAC,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,CAAA;IAExC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,aAAa,IAAI,CAAC,UAAU;YAAE,OAAM;QAC5D,IAAI,YAAoB,CAAA;QACxB,IAAI,CAAC;YACH,YAAY,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC,MAAM,CAAA;QAC3C,CAAC;QAAC,MAAM,CAAC;YACP,YAAY,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAA;QACvC,CAAC;QACD,SAAS,CAAC,OAAO,CAAC,aAAa,CAAC,WAAW,CACzC,EAAE,IAAI,EAAE,wBAAwB,EAAE,IAAI,EAAE,MAAM,EAAE,EAChD,YAAY,CACb,CAAA;IACH,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAA;IAExB,MAAM,aAAa,GAAG,WAAW,CAAC,GAAG,EAAE;QACrC,IAAI,SAAS,CAAC,OAAO,EAAE,CAAC;YACtB,UAAU,CAAC,IAAI,CAAC,CAAA;YAChB,SAAS,CAAC,OAAO,CAAC,GAAG,GAAG,SAAS,CAAC,OAAO,CAAC,GAAG,CAAA;QAC/C,CAAC;IACH,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,MAAM,kBAAkB,GAAG,WAAW,CAAC,GAAG,EAAE;QAC1C,IAAI,UAAU;YAAE,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAA;IACnD,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAA;IAEhB,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,CACL,eAAK,SAAS,EAAC,iHAAiH,aAC9H,YAAG,SAAS,EAAC,mCAAmC,YAC7C,CAAC,UAAU;wBACV,CAAC,CAAC,2CAA2C;wBAC7C,CAAC,CAAC,2EAA2E,GAC7E,EACJ,iBAAQ,OAAO,EAAE,OAAO,EAAE,SAAS,EAAC,gDAAgD,8BAE3E,IACL,CACP,CAAA;IACH,CAAC;IAED,OAAO,CACL,eAAK,SAAS,EAAC,wDAAwD,aACrE,eAAK,SAAS,EAAC,iFAAiF,aAC9F,cAAK,SAAS,EAAC,yBAAyB,YAEpC;4BACE,CAAC,SAAS,EAAE,OAAO,CAAC;4BACpB,CAAC,QAAQ,EAAE,MAAM,CAAC;4BAClB,CAAC,QAAQ,EAAE,UAAU,CAAC;yBAEzB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CACpB,iBAEE,OAAO,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,EAC9B,SAAS,EAAE,iBAAiB,QAAQ,KAAK,EAAE,CAAC,CAAC,CAAC,kCAAkC,CAAC,CAAC,CAAC,mCAAmC,EAAE,EACxH,KAAK,EAAE,EAAE,YAET,KAAC,IAAI,IAAC,SAAS,EAAC,SAAS,GAAG,IALvB,EAAE,CAMA,CACV,CAAC,GACE,EACN,eAAK,SAAS,EAAC,yBAAyB,aACtC,iBACE,OAAO,EAAE,aAAa,EACtB,SAAS,EAAC,iDAAiD,EAC3D,KAAK,EAAC,SAAS,YAEf,KAAC,SAAS,IAAC,SAAS,EAAC,SAAS,GAAG,GAC1B,EACT,iBACE,OAAO,EAAE,kBAAkB,EAC3B,SAAS,EAAC,iDAAiD,EAC3D,KAAK,EAAC,iBAAiB,YAEvB,KAAC,YAAY,IAAC,SAAS,EAAC,SAAS,GAAG,GAC7B,EACT,iBACE,OAAO,EAAE,OAAO,EAChB,SAAS,EAAC,iDAAiD,EAC3D,KAAK,EAAC,OAAO,YAEb,KAAC,CAAC,IAAC,SAAS,EAAC,SAAS,GAAG,GAClB,IACL,IACF,EAEN,cAAK,SAAS,EAAC,0DAA0D,YACvE,eACE,KAAK,EAAE,EAAE,KAAK,EAAE,eAAe,CAAC,QAAQ,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,YAAY,EAAE,EACvF,SAAS,EAAC,UAAU,aAEnB,OAAO,IAAI,CACV,cAAK,SAAS,EAAC,+EAA+E,YAC5F,cAAK,SAAS,EAAC,8DAA8D,GAAG,GAC5E,CACP,EACD,iBACE,GAAG,EAAE,SAAS,EACd,GAAG,EAAE,UAAU,EACf,SAAS,EAAC,oEAAoE,EAC9E,KAAK,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,EAC7B,MAAM,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,EAC/B,KAAK,EAAC,cAAc,GACpB,IACE,GACF,IACF,CACP,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"LivePreview.js","sourceRoot":"","sources":["../../src/components/LivePreview.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ import React from 'react';
2
+ import { type Notification, type NotificationOutcome, type NotificationStreamHandle, type NotificationStreamOptions } from '../lib/notifications-client.js';
3
+ /**
4
+ * Top-bar notification bell + dropdown panel. Slice 6 of Phase 3
5
+ * surfaces every collaboration event that affects the current user —
6
+ * comment replies, resolves, and mentions — without forcing the
7
+ * editor to round-trip through the comments side panel.
8
+ *
9
+ * Design choices:
10
+ *
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`.
22
+ * - **Optimistic read state.** When the user clicks a notification we
23
+ * mark it read locally before the server confirms — so the bell
24
+ * counter feels instant. Server failures roll the state back via
25
+ * the `onError` toast.
26
+ * - **API injection.** The dependency-injection seam matches the rest
27
+ * of the admin (`CommentSidePanel.api`) so unit tests can substitute
28
+ * in-memory fakes — including the EventSource subscriber, which
29
+ * isn't available in JSDOM by default.
30
+ */
31
+ export interface NotificationsApi {
32
+ list: (opts: {
33
+ includeRead?: boolean;
34
+ }) => Promise<NotificationOutcome<Notification[]>>;
35
+ unread: () => Promise<NotificationOutcome<number>>;
36
+ markRead: (id: string) => Promise<NotificationOutcome<Notification>>;
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;
48
+ }
49
+ export interface NotificationBellProps {
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
+ */
56
+ pollIntervalMs?: number;
57
+ /** Optional click hook — called with the notification when a row is clicked. */
58
+ onSelect?: (notification: Notification) => void;
59
+ /** Error sink — bubble server failures to a toast / banner. */
60
+ onError?: (message: string) => void;
61
+ /** Override the API client (tests). */
62
+ api?: NotificationsApi;
63
+ className?: string;
64
+ }
65
+ export declare function NotificationBell({ pollIntervalMs, onSelect, onError, api, className, }: NotificationBellProps): React.JSX.Element;
66
+ //# sourceMappingURL=NotificationBell.d.ts.map
@@ -0,0 +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,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"}