@elevasis/ui 2.68.0 → 2.70.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 (85) hide show
  1. package/dist/api/index.js +3 -3
  2. package/dist/app/index.d.ts +577 -367
  3. package/dist/app/index.js +10 -19
  4. package/dist/auth/index.d.ts +5 -0
  5. package/dist/auth/index.js +5 -5
  6. package/dist/charts/index.js +5 -5
  7. package/dist/{chunk-JC5W55J3.js → chunk-26BTE3Z4.js} +1 -1
  8. package/dist/{chunk-ZNKZMK2E.js → chunk-5C62IRKS.js} +5 -5
  9. package/dist/{chunk-ZGCWTWBD.js → chunk-6TT3FQEZ.js} +16218 -15769
  10. package/dist/{chunk-QYI5BZKM.js → chunk-AGX2CY2O.js} +1 -1
  11. package/dist/{chunk-UFTM5SZZ.js → chunk-AKANHRQ5.js} +9 -3
  12. package/dist/{chunk-NDHMZT2G.js → chunk-C75XWBYZ.js} +1 -1
  13. package/dist/{chunk-2MTP3H3A.js → chunk-CCZJ4OWY.js} +1 -1
  14. package/dist/{chunk-75NOC536.js → chunk-DUEGHF7A.js} +4 -4
  15. package/dist/{chunk-SIVUCZXQ.js → chunk-GJQF3S2Q.js} +1 -1
  16. package/dist/{chunk-ZWS3TCQR.js → chunk-GZADST64.js} +21 -108
  17. package/dist/{chunk-TYKAKJGW.js → chunk-JA2XDMAE.js} +1 -1
  18. package/dist/{chunk-76IVMVI3.js → chunk-LWYLZUUV.js} +3 -3
  19. package/dist/{chunk-WPIASV3H.js → chunk-LY3IKYTZ.js} +2 -2
  20. package/dist/{chunk-HJVPVS57.js → chunk-MFCV4JMQ.js} +5 -5
  21. package/dist/{chunk-L3HFJFYF.js → chunk-NM4A63KJ.js} +30 -48
  22. package/dist/{chunk-OOACTYRA.js → chunk-QDMIFD6F.js} +1 -1
  23. package/dist/{chunk-GBXTRSAU.js → chunk-SMH6SQQE.js} +1 -1
  24. package/dist/{chunk-TGPJNKDU.js → chunk-TJMZUL6E.js} +4 -4
  25. package/dist/{chunk-5NRJ2BLQ.js → chunk-VDX6DL7Z.js} +1 -1
  26. package/dist/{chunk-FK3K2VK2.js → chunk-XKUEO4HG.js} +10 -7
  27. package/dist/components/index.d.ts +1614 -256
  28. package/dist/components/index.js +25 -25
  29. package/dist/components/navigation/index.js +6 -6
  30. package/dist/features/auth/index.d.ts +457 -163
  31. package/dist/features/auth/index.js +7 -7
  32. package/dist/features/clients/index.d.ts +39 -1
  33. package/dist/features/clients/index.js +6 -5
  34. package/dist/features/content/index.css +659 -0
  35. package/dist/features/content/index.d.ts +1978 -0
  36. package/dist/features/content/index.js +2312 -0
  37. package/dist/features/crm/index.d.ts +584 -369
  38. package/dist/features/crm/index.js +7 -7
  39. package/dist/features/dashboard/index.d.ts +0 -6
  40. package/dist/features/dashboard/index.js +7 -7
  41. package/dist/features/delivery/index.css +149 -149
  42. package/dist/features/delivery/index.d.ts +457 -163
  43. package/dist/features/delivery/index.js +6 -6
  44. package/dist/features/knowledge/index.js +5 -5
  45. package/dist/features/lead-gen/index.d.ts +20 -91
  46. package/dist/features/lead-gen/index.js +7 -7
  47. package/dist/features/monitoring/index.css +149 -149
  48. package/dist/features/monitoring/index.js +8 -8
  49. package/dist/features/monitoring/requests/index.js +7 -12
  50. package/dist/features/operations/index.d.ts +0 -84
  51. package/dist/features/operations/index.js +10 -10
  52. package/dist/features/settings/index.d.ts +457 -163
  53. package/dist/features/settings/index.js +7 -7
  54. package/dist/hooks/access/index.d.ts +5 -0
  55. package/dist/hooks/access/index.js +5 -5
  56. package/dist/hooks/delivery/index.d.ts +457 -163
  57. package/dist/hooks/delivery/index.js +2 -2
  58. package/dist/hooks/index.css +149 -149
  59. package/dist/hooks/index.d.ts +2128 -233
  60. package/dist/hooks/index.js +5 -5
  61. package/dist/hooks/operations/command-view/utils/transformCommandViewData.d.ts +0 -6
  62. package/dist/hooks/published.css +149 -149
  63. package/dist/hooks/published.d.ts +2128 -233
  64. package/dist/hooks/published.js +5 -5
  65. package/dist/initialization/index.d.ts +464 -164
  66. package/dist/knowledge/index.d.ts +4 -88
  67. package/dist/knowledge/index.js +3211 -3201
  68. package/dist/{knowledge-search-index-6EZNBNSR.js → knowledge-search-index-LP2WVSQX.js} +689 -695
  69. package/dist/layout/index.js +8 -8
  70. package/dist/organization/index.d.ts +7 -1
  71. package/dist/profile/index.d.ts +457 -163
  72. package/dist/provider/index.d.ts +577 -368
  73. package/dist/provider/index.js +5 -5
  74. package/dist/provider/published.d.ts +577 -368
  75. package/dist/provider/published.js +5 -5
  76. package/dist/supabase/index.d.ts +906 -339
  77. package/dist/test-utils/index.d.ts +10 -2
  78. package/dist/test-utils/index.js +39 -10
  79. package/dist/types/index.d.ts +464 -170
  80. package/dist/utils/index.d.ts +0 -6
  81. package/dist/utils/index.js +1 -1
  82. package/dist/vite/index.js +2 -2
  83. package/dist/vite-plugin-knowledge/index.js +1 -1
  84. package/package.json +9 -5
  85. package/src/features/README.md +28 -28
@@ -0,0 +1,2312 @@
1
+ import { PageContainer } from '../../chunk-BZZCNLT6.js';
2
+ import '../../chunk-TUMSNGTX.js';
3
+ import { FilterBar } from '../../chunk-PDHTXPSF.js';
4
+ import { CustomModal, useContentItems, useContentDistributions, useContentConfig, SubshellContentContainer, PageTitleCaption, CenteredErrorState, EmptyState, StatCard, getContentItemIdentity, usePaginationState, useContentItem, useContentItemAttempts, useUpdateContentItem, useReviewContentItem, useContentDistribution, useUpdateContentDistribution, useContentPipelineSummary, deriveContentBoard, useExecutions } from '../../chunk-6TT3FQEZ.js';
5
+ export { CONTENT_ITEMS, ContentSidebar, ContentSidebarMiddle, DEFAULT_CONTENT_VOCABULARY, MyReviewQueuePanel, contentManifest, deriveContentBoard, getContentItemIdentity, resolveContentStepResource, resolveContentVocabulary, useContentConfig } from '../../chunk-6TT3FQEZ.js';
6
+ import '../../chunk-R6NMRXNO.js';
7
+ import '../../chunk-RBGVNPA6.js';
8
+ import '../../chunk-MKH2KOAO.js';
9
+ import '../../chunk-VDX6DL7Z.js';
10
+ import '../../chunk-L7BZZ4SI.js';
11
+ import { showSuccessNotification } from '../../chunk-C75XWBYZ.js';
12
+ import '../../chunk-MISHPEJX.js';
13
+ import { CardHeader } from '../../chunk-62GVNH5U.js';
14
+ import '../../chunk-P45GQ3ZW.js';
15
+ import '../../chunk-M7WWRZ5Z.js';
16
+ import '../../chunk-3KMDHCAR.js';
17
+ import '../../chunk-2IFYDILW.js';
18
+ import '../../chunk-BLXJEIQS.js';
19
+ import '../../chunk-UD657ICS.js';
20
+ import '../../chunk-DD3CCMCZ.js';
21
+ import '../../chunk-RNP5R5I3.js';
22
+ import '../../chunk-RT4KRGZT.js';
23
+ import { formatTimeAgo, formatRelativeTime } from '../../chunk-NM4A63KJ.js';
24
+ import '../../chunk-Y6I3IC45.js';
25
+ import '../../chunk-6SSQGWK7.js';
26
+ import '../../chunk-BI5VDO2K.js';
27
+ import '../../chunk-MQZE7SUI.js';
28
+ import '../../chunk-TVTSASST.js';
29
+ import '../../chunk-BRJ3QZ4E.js';
30
+ import '../../chunk-Q7DJKLEN.js';
31
+ import '../../chunk-I2KLQ2HA.js';
32
+ import { Stack, Group, Text, Badge, Button, Textarea, Box, Loader, Accordion, Card, Alert, List, Paper, Center, SimpleGrid, Table, TextInput, Select, Pagination, Title, Tabs, Anchor, Divider, Progress, Checkbox, TagsInput, Image, Tooltip, ActionIcon, UnstyledButton, ScrollArea } from '@mantine/core';
33
+ import { IconAlertTriangle, IconShieldCheck, IconShieldX, IconArticle, IconArrowRight, IconClock, IconSend, IconSearch, IconFileText, IconListDetails, IconAlertCircle, IconLayoutGrid, IconShare, IconPlayerPlay, IconExternalLink, IconMaximize, IconCopy, IconDownload, IconTrash, IconArrowLeft, IconArrowsExchange, IconCheck, IconX, IconChecklist, IconRoute, IconInfoCircle, IconClockHour4 } from '@tabler/icons-react';
34
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
35
+ import { useState, useMemo, Fragment as Fragment$1, useCallback, useEffect } from 'react';
36
+ import { useNavigate, useSearch } from '@tanstack/react-router';
37
+ import { useDisclosure } from '@mantine/hooks';
38
+
39
+ function renderFieldValue(value, kind) {
40
+ if (value === void 0 || value === null || value === "") {
41
+ return /* @__PURE__ */ jsx(Text, { size: "sm", c: "var(--color-text-dimmed)", children: "\u2014" });
42
+ }
43
+ if (kind === "list" && Array.isArray(value)) {
44
+ return /* @__PURE__ */ jsx(Stack, { gap: 2, children: value.map((item, index) => /* @__PURE__ */ jsx(Text, { size: "sm", children: String(item) }, index)) });
45
+ }
46
+ return /* @__PURE__ */ jsx(Text, { size: "sm", lh: 1.5, style: { whiteSpace: kind === "longText" ? "pre-wrap" : void 0 }, children: String(value) });
47
+ }
48
+ function PayloadBody({
49
+ body,
50
+ onBodyChange,
51
+ originalBody,
52
+ onReset,
53
+ payload,
54
+ payloadFields,
55
+ disabled = false
56
+ }) {
57
+ const edited = body !== originalBody;
58
+ return /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
59
+ /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
60
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "center", children: [
61
+ /* @__PURE__ */ jsx(Text, { size: "xs", tt: "uppercase", c: "var(--color-text-dimmed)", fw: 600, style: { letterSpacing: "0.06em" }, children: "Body" }),
62
+ edited && /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
63
+ /* @__PURE__ */ jsx(Badge, { size: "xs", variant: "light", radius: "sm", children: "Edited" }),
64
+ /* @__PURE__ */ jsx(Button, { size: "compact-xs", variant: "subtle", color: "gray", onClick: onReset, disabled, children: "Reset" })
65
+ ] })
66
+ ] }),
67
+ /* @__PURE__ */ jsx(
68
+ Textarea,
69
+ {
70
+ "data-testid": "content-review-body",
71
+ "aria-label": "Body",
72
+ value: body,
73
+ onChange: (event) => onBodyChange(event.currentTarget.value),
74
+ autosize: true,
75
+ minRows: 3,
76
+ disabled,
77
+ styles: {
78
+ input: {
79
+ background: "var(--color-surface)",
80
+ fontSize: "var(--mantine-font-size-sm)",
81
+ lineHeight: 1.65,
82
+ padding: "var(--mantine-spacing-md)"
83
+ }
84
+ }
85
+ }
86
+ )
87
+ ] }),
88
+ payload && payloadFields && payloadFields.length > 0 && /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
89
+ /* @__PURE__ */ jsx(Text, { size: "xs", tt: "uppercase", c: "var(--color-text-dimmed)", fw: 600, style: { letterSpacing: "0.06em" }, children: "Original payload" }),
90
+ /* @__PURE__ */ jsx(
91
+ Box,
92
+ {
93
+ "data-testid": "content-review-payload",
94
+ p: "md",
95
+ style: {
96
+ background: "var(--color-surface)",
97
+ border: "1px solid var(--color-border)",
98
+ borderRadius: "var(--mantine-radius-sm)"
99
+ },
100
+ children: /* @__PURE__ */ jsx(Stack, { gap: "sm", children: payloadFields.map((field) => /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
101
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "var(--color-text-dimmed)", fw: 500, children: field.label }),
102
+ renderFieldValue(payload[field.key], field.kind)
103
+ ] }, field.key)) })
104
+ }
105
+ )
106
+ ] })
107
+ ] });
108
+ }
109
+ function formatElapsed(seconds) {
110
+ if (seconds < 60) return `${seconds}s`;
111
+ return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
112
+ }
113
+ var STATUS_COLOR = {
114
+ pending: void 0,
115
+ in_progress: void 0,
116
+ completed: "var(--color-success)",
117
+ failed: "var(--color-error)"
118
+ };
119
+ var STATUS_LABEL = {
120
+ pending: "Pending",
121
+ in_progress: "In progress",
122
+ completed: "Completed",
123
+ failed: "Failed"
124
+ };
125
+ function ProcessingStateStrip({ steps }) {
126
+ if (steps.length === 0) return null;
127
+ return /* @__PURE__ */ jsx(Group, { gap: "xs", "data-testid": "content-review-processing-strip", children: steps.map((step) => /* @__PURE__ */ jsx(
128
+ Badge,
129
+ {
130
+ size: "sm",
131
+ variant: "light",
132
+ color: STATUS_COLOR[step.status],
133
+ radius: "sm",
134
+ leftSection: step.status === "in_progress" ? /* @__PURE__ */ jsx(Loader, { size: 6 }) : void 0,
135
+ "data-testid": `content-review-step-${step.key}`,
136
+ children: /* @__PURE__ */ jsxs(Group, { gap: 4, wrap: "nowrap", children: [
137
+ /* @__PURE__ */ jsx(Text, { size: "xs", fw: 600, span: true, children: step.label }),
138
+ /* @__PURE__ */ jsxs(Text, { size: "xs", span: true, children: [
139
+ "\xB7 ",
140
+ STATUS_LABEL[step.status],
141
+ step.status === "in_progress" && step.elapsedSeconds !== void 0 ? ` (${formatElapsed(step.elapsedSeconds)})` : ""
142
+ ] })
143
+ ] })
144
+ },
145
+ step.key
146
+ )) });
147
+ }
148
+ function swapFirstLine(text, replacement) {
149
+ const lines = text.split("\n");
150
+ const at = lines.findIndex((line) => line.trim() !== "");
151
+ if (at === -1) return replacement;
152
+ lines[at] = replacement;
153
+ return lines.join("\n");
154
+ }
155
+ function swapLastLine(text, replacement) {
156
+ const lines = text.split("\n");
157
+ for (let at = lines.length - 1; at >= 0; at -= 1) {
158
+ if (lines[at].trim() !== "") {
159
+ lines[at] = replacement;
160
+ return lines.join("\n");
161
+ }
162
+ }
163
+ return replacement;
164
+ }
165
+ function firstLine(text) {
166
+ return text.split("\n").find((line) => line.trim() !== "")?.trim() ?? "";
167
+ }
168
+ function lastLine(text) {
169
+ return [...text.split("\n")].reverse().find((line) => line.trim() !== "")?.trim() ?? "";
170
+ }
171
+ function SwapOption({
172
+ line,
173
+ active,
174
+ onClick,
175
+ testId
176
+ }) {
177
+ return /* @__PURE__ */ jsx(UnstyledButton, { "data-testid": testId, onClick, style: { width: "100%" }, children: /* @__PURE__ */ jsxs(
178
+ Group,
179
+ {
180
+ gap: "sm",
181
+ wrap: "nowrap",
182
+ align: "flex-start",
183
+ p: "sm",
184
+ style: {
185
+ border: `1px solid ${active ? "var(--color-primary)" : "var(--color-border)"}`,
186
+ borderRadius: "var(--mantine-radius-sm)",
187
+ background: active ? "color-mix(in srgb, var(--color-primary) 8%, transparent)" : void 0
188
+ },
189
+ children: [
190
+ /* @__PURE__ */ jsx(
191
+ IconArrowsExchange,
192
+ {
193
+ size: 15,
194
+ style: { flexShrink: 0, marginTop: 2, color: active ? "var(--color-primary)" : void 0 }
195
+ }
196
+ ),
197
+ /* @__PURE__ */ jsx(Text, { size: "sm", lh: 1.5, children: line })
198
+ ]
199
+ }
200
+ ) });
201
+ }
202
+ function MoreOptionsButton({
203
+ label,
204
+ running,
205
+ elapsed,
206
+ onClick,
207
+ testId
208
+ }) {
209
+ if (running) {
210
+ return /* @__PURE__ */ jsx(Group, { gap: "sm", align: "center", wrap: "nowrap", "data-testid": `${testId}-running`, children: /* @__PURE__ */ jsxs(Text, { size: "sm", c: "var(--color-text-dimmed)", children: [
211
+ "Fetching more. ",
212
+ formatElapsed(elapsed),
213
+ " so far."
214
+ ] }) });
215
+ }
216
+ return /* @__PURE__ */ jsx(UnstyledButton, { onClick, "data-testid": testId, children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "var(--color-primary)", children: label }) });
217
+ }
218
+ function AlternatesPanel({
219
+ body,
220
+ onBodyChange,
221
+ hookOptions = [],
222
+ closeOptions = [],
223
+ onMoreHooks,
224
+ onMoreCloses,
225
+ running,
226
+ elapsed = 0
227
+ }) {
228
+ if (hookOptions.length === 0 && closeOptions.length === 0) return null;
229
+ const currentFirst = firstLine(body);
230
+ const currentLast = lastLine(body);
231
+ return /* @__PURE__ */ jsx(Accordion, { variant: "separated", radius: "sm", chevronPosition: "right", children: /* @__PURE__ */ jsxs(Accordion.Item, { value: "alternates", children: [
232
+ /* @__PURE__ */ jsx(Accordion.Control, { children: /* @__PURE__ */ jsx(Text, { size: "sm", fw: 600, children: "Other openings and closes" }) }),
233
+ /* @__PURE__ */ jsx(Accordion.Panel, { children: /* @__PURE__ */ jsxs(Stack, { gap: "lg", children: [
234
+ hookOptions.length > 0 && /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
235
+ /* @__PURE__ */ jsx(
236
+ Text,
237
+ {
238
+ size: "xs",
239
+ tt: "uppercase",
240
+ c: "var(--color-text-dimmed)",
241
+ fw: 600,
242
+ style: { letterSpacing: "0.06em" },
243
+ children: "Click one to become the first line"
244
+ }
245
+ ),
246
+ hookOptions.map((option) => /* @__PURE__ */ jsx(
247
+ SwapOption,
248
+ {
249
+ line: option.text,
250
+ active: currentFirst === option.text.trim(),
251
+ onClick: () => onBodyChange(swapFirstLine(body, option.text)),
252
+ testId: `content-review-hook-${option.id}`
253
+ },
254
+ option.id
255
+ )),
256
+ onMoreHooks && /* @__PURE__ */ jsx(
257
+ MoreOptionsButton,
258
+ {
259
+ label: "Show more openings",
260
+ running: running === "hooks",
261
+ elapsed,
262
+ onClick: onMoreHooks,
263
+ testId: "content-review-more-hooks"
264
+ }
265
+ )
266
+ ] }),
267
+ closeOptions.length > 0 && /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
268
+ /* @__PURE__ */ jsx(
269
+ Text,
270
+ {
271
+ size: "xs",
272
+ tt: "uppercase",
273
+ c: "var(--color-text-dimmed)",
274
+ fw: 600,
275
+ style: { letterSpacing: "0.06em" },
276
+ children: "Click one to become the last line"
277
+ }
278
+ ),
279
+ closeOptions.map((option) => /* @__PURE__ */ jsx(
280
+ SwapOption,
281
+ {
282
+ line: option.text,
283
+ active: currentLast === option.text.trim(),
284
+ onClick: () => onBodyChange(swapLastLine(body, option.text)),
285
+ testId: `content-review-close-${option.id}`
286
+ },
287
+ option.id
288
+ )),
289
+ onMoreCloses && /* @__PURE__ */ jsx(
290
+ MoreOptionsButton,
291
+ {
292
+ label: "Show more closes",
293
+ running: running === "closes",
294
+ elapsed,
295
+ onClick: onMoreCloses,
296
+ testId: "content-review-more-closes"
297
+ }
298
+ )
299
+ ] })
300
+ ] }) })
301
+ ] }) });
302
+ }
303
+ var IRREVERSIBLE_WARNING = "This action is irreversible and visible outside the platform.";
304
+ function isIrreversible(action) {
305
+ return action?.consequence === "irreversible";
306
+ }
307
+ function resolveActionColor(action) {
308
+ if (!action) return void 0;
309
+ if (action.color) return action.color;
310
+ if (action.tone === "danger") return "var(--color-error)";
311
+ if (isIrreversible(action)) return "var(--color-warning)";
312
+ return void 0;
313
+ }
314
+ function ReviewActionBar({ actions, onAction, pending = false }) {
315
+ const [confirmAction, setConfirmAction] = useState(null);
316
+ const [feedbackAction, setFeedbackAction] = useState(null);
317
+ const [feedback, setFeedback] = useState("");
318
+ function openAction(action) {
319
+ if (action.requiresFeedback) {
320
+ setFeedback("");
321
+ setFeedbackAction(action);
322
+ } else if (action.requiresConfirmation || isIrreversible(action)) {
323
+ setConfirmAction(action);
324
+ } else {
325
+ onAction(action.id);
326
+ }
327
+ }
328
+ function submitFeedback() {
329
+ if (!feedbackAction) return;
330
+ onAction(feedbackAction.id, feedback);
331
+ setFeedbackAction(null);
332
+ setFeedback("");
333
+ }
334
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
335
+ /* @__PURE__ */ jsx(Group, { gap: "xs", children: actions.map((action) => {
336
+ const Icon = action.icon;
337
+ const irreversible = isIrreversible(action);
338
+ return /* @__PURE__ */ jsx(
339
+ Button,
340
+ {
341
+ variant: irreversible ? "filled" : "light",
342
+ fw: irreversible ? 700 : void 0,
343
+ color: resolveActionColor(action),
344
+ leftSection: Icon ? /* @__PURE__ */ jsx(Icon, { size: 16 }) : void 0,
345
+ loading: pending,
346
+ disabled: pending,
347
+ onClick: () => openAction(action),
348
+ size: "xs",
349
+ radius: "xs",
350
+ "data-testid": `content-review-action-${action.id}`,
351
+ children: action.label
352
+ },
353
+ action.id
354
+ );
355
+ }) }),
356
+ /* @__PURE__ */ jsx(CustomModal, { opened: !!confirmAction, onClose: () => setConfirmAction(null), size: "sm", children: /* @__PURE__ */ jsxs(Stack, { children: [
357
+ /* @__PURE__ */ jsx(Text, { fw: 600, children: confirmAction?.label }),
358
+ isIrreversible(confirmAction) ? /* @__PURE__ */ jsx(Text, { size: "sm", fw: 600, c: "var(--color-warning)", children: IRREVERSIBLE_WARNING }) : null,
359
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: confirmAction?.confirmationMessage || `Are you sure you want to ${confirmAction?.label.toLowerCase()}?` }),
360
+ /* @__PURE__ */ jsxs(Group, { justify: "flex-end", children: [
361
+ /* @__PURE__ */ jsx(Button, { variant: "default", onClick: () => setConfirmAction(null), children: "Cancel" }),
362
+ /* @__PURE__ */ jsx(
363
+ Button,
364
+ {
365
+ variant: isIrreversible(confirmAction) ? "filled" : void 0,
366
+ color: resolveActionColor(confirmAction),
367
+ onClick: () => {
368
+ if (confirmAction) onAction(confirmAction.id);
369
+ setConfirmAction(null);
370
+ },
371
+ children: confirmAction?.label
372
+ }
373
+ )
374
+ ] })
375
+ ] }) }),
376
+ /* @__PURE__ */ jsx(CustomModal, { opened: !!feedbackAction, onClose: () => setFeedbackAction(null), size: "sm", children: /* @__PURE__ */ jsxs(Stack, { children: [
377
+ /* @__PURE__ */ jsx(Text, { fw: 600, children: feedbackAction?.label }),
378
+ isIrreversible(feedbackAction) ? /* @__PURE__ */ jsx(Text, { size: "sm", fw: 600, c: "var(--color-warning)", children: IRREVERSIBLE_WARNING }) : null,
379
+ feedbackAction?.confirmationMessage ? /* @__PURE__ */ jsx(Text, { size: "sm", children: feedbackAction.confirmationMessage }) : null,
380
+ /* @__PURE__ */ jsx(
381
+ Textarea,
382
+ {
383
+ "data-testid": "content-review-feedback-input",
384
+ label: feedbackAction?.feedbackLabel || "Feedback",
385
+ placeholder: feedbackAction?.feedbackPlaceholder,
386
+ value: feedback,
387
+ onChange: (event) => setFeedback(event.currentTarget.value),
388
+ autosize: true,
389
+ minRows: 3
390
+ }
391
+ ),
392
+ /* @__PURE__ */ jsxs(Group, { justify: "flex-end", children: [
393
+ /* @__PURE__ */ jsx(Button, { variant: "default", onClick: () => setFeedbackAction(null), children: "Cancel" }),
394
+ /* @__PURE__ */ jsx(
395
+ Button,
396
+ {
397
+ variant: isIrreversible(feedbackAction) ? "filled" : void 0,
398
+ color: resolveActionColor(feedbackAction),
399
+ disabled: Boolean(feedbackAction?.feedbackRequired) && feedback.trim().length === 0,
400
+ onClick: submitFeedback,
401
+ "data-testid": "content-review-feedback-submit",
402
+ children: feedbackAction?.label
403
+ }
404
+ )
405
+ ] })
406
+ ] }) })
407
+ ] });
408
+ }
409
+ function ContentReviewCard({
410
+ title,
411
+ body,
412
+ onBodyChange,
413
+ originalBody,
414
+ onReset,
415
+ payload,
416
+ payloadFields,
417
+ fidelity,
418
+ checkFirst,
419
+ hookOptions,
420
+ closeOptions,
421
+ onMoreHooks,
422
+ onMoreCloses,
423
+ alternatesRunning,
424
+ alternatesElapsed,
425
+ processingSteps = [],
426
+ actions,
427
+ onAction,
428
+ actionsPending = false
429
+ }) {
430
+ return /* @__PURE__ */ jsx(Card, { withBorder: true, radius: "md", padding: "xl", "data-testid": "content-review-card", children: /* @__PURE__ */ jsxs(Stack, { gap: "lg", children: [
431
+ title || processingSteps.length > 0 ? /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "flex-start", wrap: "nowrap", children: [
432
+ title ? /* @__PURE__ */ jsx(Text, { fw: 700, size: "lg", children: title }) : null,
433
+ /* @__PURE__ */ jsx(ProcessingStateStrip, { steps: processingSteps })
434
+ ] }) : null,
435
+ fidelity && !fidelity.matches && /* @__PURE__ */ jsx(
436
+ Alert,
437
+ {
438
+ icon: /* @__PURE__ */ jsx(IconAlertTriangle, { size: 16 }),
439
+ color: "var(--color-error)",
440
+ variant: "light",
441
+ title: "This one did not pass the fact check",
442
+ "data-testid": "content-review-fidelity-alert",
443
+ children: /* @__PURE__ */ jsx(Text, { size: "sm", children: "Open Fact check below to see what is off, or send this item back for another pass." })
444
+ }
445
+ ),
446
+ checkFirst && /* @__PURE__ */ jsx(
447
+ Alert,
448
+ {
449
+ icon: /* @__PURE__ */ jsx(IconAlertTriangle, { size: 16 }),
450
+ color: "var(--color-warning)",
451
+ variant: "light",
452
+ title: "One claim here is yours to verify",
453
+ "data-testid": "content-review-check-first",
454
+ children: /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
455
+ /* @__PURE__ */ jsx(Text, { size: "sm", fw: 600, children: checkFirst }),
456
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: "The producer could not check this one. Confirm it is true before this goes out." })
457
+ ] })
458
+ }
459
+ ),
460
+ /* @__PURE__ */ jsx(
461
+ PayloadBody,
462
+ {
463
+ body,
464
+ onBodyChange,
465
+ originalBody,
466
+ onReset,
467
+ payload,
468
+ payloadFields,
469
+ disabled: actionsPending
470
+ }
471
+ ),
472
+ /* @__PURE__ */ jsx(
473
+ AlternatesPanel,
474
+ {
475
+ body,
476
+ onBodyChange,
477
+ hookOptions,
478
+ closeOptions,
479
+ onMoreHooks,
480
+ onMoreCloses,
481
+ running: alternatesRunning,
482
+ elapsed: alternatesElapsed
483
+ }
484
+ ),
485
+ fidelity && /* @__PURE__ */ jsx(Accordion, { variant: "separated", radius: "sm", chevronPosition: "right", children: /* @__PURE__ */ jsxs(Accordion.Item, { value: "fidelity", children: [
486
+ /* @__PURE__ */ jsx(Accordion.Control, { children: /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
487
+ fidelity.matches ? /* @__PURE__ */ jsx(IconShieldCheck, { size: 16, color: "var(--color-success)" }) : /* @__PURE__ */ jsx(IconShieldX, { size: 16, color: "var(--color-error)" }),
488
+ /* @__PURE__ */ jsx(Text, { size: "sm", fw: 600, children: "Fact check" })
489
+ ] }) }),
490
+ /* @__PURE__ */ jsx(Accordion.Panel, { children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
491
+ fidelity.coreClaim && /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
492
+ /* @__PURE__ */ jsx(
493
+ Text,
494
+ {
495
+ size: "xs",
496
+ tt: "uppercase",
497
+ c: "var(--color-text-dimmed)",
498
+ fw: 600,
499
+ style: { letterSpacing: "0.06em" },
500
+ children: "What this item claims"
501
+ }
502
+ ),
503
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: fidelity.coreClaim })
504
+ ] }),
505
+ /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
506
+ /* @__PURE__ */ jsx(
507
+ Text,
508
+ {
509
+ size: "xs",
510
+ tt: "uppercase",
511
+ c: "var(--color-text-dimmed)",
512
+ fw: 600,
513
+ style: { letterSpacing: "0.06em" },
514
+ children: "Where the numbers came from"
515
+ }
516
+ ),
517
+ !fidelity.figuresUsed || fidelity.figuresUsed.length === 0 ? /* @__PURE__ */ jsx(Text, { size: "sm", c: "var(--color-text-dimmed)", children: "This item uses no numbers." }) : /* @__PURE__ */ jsx(List, { size: "sm", spacing: 4, children: fidelity.figuresUsed.map((figure) => /* @__PURE__ */ jsx(List.Item, { children: figure }, figure)) })
518
+ ] }),
519
+ fidelity.note && /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
520
+ /* @__PURE__ */ jsx(
521
+ Text,
522
+ {
523
+ size: "xs",
524
+ tt: "uppercase",
525
+ c: "var(--color-text-dimmed)",
526
+ fw: 600,
527
+ style: { letterSpacing: "0.06em" },
528
+ children: "Notes"
529
+ }
530
+ ),
531
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "var(--color-text-dimmed)", children: fidelity.note })
532
+ ] })
533
+ ] }) })
534
+ ] }) }),
535
+ /* @__PURE__ */ jsx(ReviewActionBar, { actions, onAction, pending: actionsPending })
536
+ ] }) });
537
+ }
538
+
539
+ // src/lib/content/stage-colors.ts
540
+ var FALLBACK_CONTENT_STAGE_COLORS = {
541
+ accent: "var(--color-text-dimmed)",
542
+ background: "color-mix(in srgb, var(--color-text-dimmed) 10%, transparent)",
543
+ border: "color-mix(in srgb, var(--color-border) 85%, var(--color-text-dimmed))",
544
+ text: "var(--color-text)"
545
+ };
546
+ function getContentStageColor(entry) {
547
+ if (!entry?.color) return FALLBACK_CONTENT_STAGE_COLORS;
548
+ return {
549
+ accent: entry.color,
550
+ background: `color-mix(in srgb, ${entry.color} 12%, transparent)`,
551
+ border: `color-mix(in srgb, var(--color-border) 70%, ${entry.color})`,
552
+ text: "var(--color-text)"
553
+ };
554
+ }
555
+ var OVERVIEW_ITEMS_LIMIT = 100;
556
+ var RECENT_ITEMS_LIMIT = 8;
557
+ function ContentOverviewPage({ headerActions, footer } = {}) {
558
+ const navigate = useNavigate();
559
+ const itemsQuery = useContentItems({ limit: OVERVIEW_ITEMS_LIMIT });
560
+ const distributionsQuery = useContentDistributions({ limit: OVERVIEW_ITEMS_LIMIT });
561
+ const contentConfig = useContentConfig();
562
+ const items = itemsQuery.data?.data ?? [];
563
+ const statusEntries = contentConfig.stagesByCatalog.status ?? [];
564
+ const statusCounts = useMemo(() => {
565
+ const counts = /* @__PURE__ */ new Map();
566
+ for (const item of items) {
567
+ counts.set(item.status, (counts.get(item.status) ?? 0) + 1);
568
+ }
569
+ return counts;
570
+ }, [items]);
571
+ const pendingReviewCount = items.filter((item) => item.reviewedAt === null).length;
572
+ const totalItems = itemsQuery.data?.total ?? items.length;
573
+ const totalDistributions = distributionsQuery.data?.data.length ?? 0;
574
+ const recentItems = useMemo(
575
+ () => [...items].sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()).slice(0, RECENT_ITEMS_LIMIT),
576
+ [items]
577
+ );
578
+ const isLoading = itemsQuery.isLoading;
579
+ const error = itemsQuery.error;
580
+ const { vocabulary } = contentConfig;
581
+ return /* @__PURE__ */ jsxs(SubshellContentContainer, { children: [
582
+ /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
583
+ /* @__PURE__ */ jsx(
584
+ PageTitleCaption,
585
+ {
586
+ title: `${vocabulary.item.plural} Overview`,
587
+ caption: `${vocabulary.pipeline.singular} status, review load, and recent activity`
588
+ }
589
+ ),
590
+ headerActions ? /* @__PURE__ */ jsx(Group, { gap: "xs", children: headerActions }) : null,
591
+ isLoading ? /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) }) : error ? /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(CenteredErrorState, { error, title: `Failed to load ${vocabulary.item.plural.toLowerCase()}` }) }) : items.length === 0 ? (
592
+ // Finding 11: zero items is the state every tenant starts in, and the only one
593
+ // with no path forward on screen. The one available action is to go produce
594
+ // something -- routed through the pipelines page, which itself lands a
595
+ // single-pipeline tenant straight inside its workspace (Proposal, "Design the
596
+ // first run").
597
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(
598
+ EmptyState,
599
+ {
600
+ icon: IconArticle,
601
+ title: `No ${vocabulary.item.plural.toLowerCase()} yet`,
602
+ description: `${vocabulary.pipeline.plural} produce ${vocabulary.item.plural.toLowerCase()} as they move from draft through review to publish. Pick a pipeline to produce the first one.`,
603
+ action: {
604
+ label: `Go to ${vocabulary.pipeline.plural.toLowerCase()}`,
605
+ onClick: () => navigate({ to: "/content/pipelines" }),
606
+ icon: /* @__PURE__ */ jsx(IconArrowRight, { size: 14 })
607
+ }
608
+ }
609
+ ) })
610
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
611
+ /* @__PURE__ */ jsxs(SimpleGrid, { cols: { base: 1, sm: 3 }, children: [
612
+ /* @__PURE__ */ jsx(
613
+ StatCard,
614
+ {
615
+ variant: "hero",
616
+ icon: IconArticle,
617
+ value: totalItems,
618
+ label: `Total ${vocabulary.item.plural}`
619
+ }
620
+ ),
621
+ /* @__PURE__ */ jsx(StatCard, { variant: "hero", icon: IconClock, value: pendingReviewCount, label: "Pending Review" }),
622
+ /* @__PURE__ */ jsx(
623
+ StatCard,
624
+ {
625
+ variant: "hero",
626
+ icon: IconSend,
627
+ value: totalDistributions,
628
+ label: vocabulary.distribution.plural
629
+ }
630
+ )
631
+ ] }),
632
+ statusEntries.length > 0 && /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
633
+ /* @__PURE__ */ jsx(
634
+ CardHeader,
635
+ {
636
+ title: "By Status",
637
+ subtitle: `Where each ${vocabulary.item.singular.toLowerCase()} currently stands`
638
+ }
639
+ ),
640
+ /* @__PURE__ */ jsx(Group, { gap: 4, wrap: "wrap", children: statusEntries.map((entry, index) => {
641
+ const colors = getContentStageColor(entry);
642
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
643
+ index > 0 && /* @__PURE__ */ jsx(IconArrowRight, { size: 14, color: "var(--color-text-dimmed)" }),
644
+ /* @__PURE__ */ jsxs(
645
+ Badge,
646
+ {
647
+ size: "lg",
648
+ variant: "light",
649
+ styles: { root: { color: colors.accent, backgroundColor: colors.background } },
650
+ children: [
651
+ entry.label,
652
+ ": ",
653
+ statusCounts.get(entry.key) ?? 0
654
+ ]
655
+ }
656
+ )
657
+ ] }, entry.key);
658
+ }) })
659
+ ] }) }),
660
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
661
+ /* @__PURE__ */ jsx(
662
+ CardHeader,
663
+ {
664
+ title: "Recently Updated",
665
+ subtitle: `Jump into a ${vocabulary.item.singular.toLowerCase()}'s review page`
666
+ }
667
+ ),
668
+ /* @__PURE__ */ jsxs(Table, { children: [
669
+ /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
670
+ /* @__PURE__ */ jsx(Table.Th, { children: vocabulary.item.singular }),
671
+ /* @__PURE__ */ jsx(Table.Th, { children: "Status" }),
672
+ /* @__PURE__ */ jsx(Table.Th, { children: "Pillar" }),
673
+ /* @__PURE__ */ jsx(Table.Th, { children: "Age" }),
674
+ /* @__PURE__ */ jsx(Table.Th, { children: "Reviewed" })
675
+ ] }) }),
676
+ /* @__PURE__ */ jsx(Table.Tbody, { children: recentItems.map((item) => {
677
+ const statusEntry = contentConfig.stageCatalog[item.status];
678
+ return /* @__PURE__ */ jsxs(
679
+ Table.Tr,
680
+ {
681
+ style: { cursor: "pointer" },
682
+ onClick: () => navigate({ to: "/content/items/$itemId", params: { itemId: item.id } }),
683
+ children: [
684
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", fw: 600, children: getContentItemIdentity(item) }) }),
685
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "light", children: statusEntry?.label ?? item.status }) }),
686
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: item.pillar ?? "\u2014" }) }),
687
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: formatTimeAgo(item.updatedAt) }) }),
688
+ /* @__PURE__ */ jsx(Table.Td, { children: item.reviewedAt ? /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "light", color: "var(--color-success)", children: "Reviewed" }) : /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "light", color: "var(--color-warning)", children: "Pending" }) })
689
+ ]
690
+ },
691
+ item.id
692
+ );
693
+ }) })
694
+ ] })
695
+ ] }) })
696
+ ] })
697
+ ] }) }),
698
+ footer ?? null
699
+ ] });
700
+ }
701
+ var PAGE_SIZE = 20;
702
+ function ContentItemsPage({ headerActions } = {}) {
703
+ const navigate = useNavigate();
704
+ const contentConfig = useContentConfig();
705
+ const rawSearch = useSearch({ strict: false });
706
+ const reviewFilterActive = rawSearch.review === "pending";
707
+ const [searchQuery, setSearchQuery] = useState("");
708
+ const [statusFilter, setStatusFilter] = useState("");
709
+ const [pillarFilter, setPillarFilter] = useState("");
710
+ const pagination = usePaginationState(PAGE_SIZE, [searchQuery, statusFilter, pillarFilter, reviewFilterActive]);
711
+ const itemsQuery = useContentItems({
712
+ search: searchQuery.trim() || void 0,
713
+ status: statusFilter || void 0,
714
+ pillar: pillarFilter || void 0,
715
+ limit: PAGE_SIZE,
716
+ offset: pagination.offset
717
+ });
718
+ const fetchedItems = itemsQuery.data?.data ?? [];
719
+ const items = reviewFilterActive ? fetchedItems.filter((item) => item.reviewedAt === null) : fetchedItems;
720
+ const total = itemsQuery.data?.total ?? 0;
721
+ const statusOptions = [
722
+ { value: "", label: "All statuses" },
723
+ ...(contentConfig.stagesByCatalog.status ?? []).map((entry) => ({ value: entry.key, label: entry.label }))
724
+ ];
725
+ const pillarOptions = [
726
+ { value: "", label: "All pillars" },
727
+ ...(contentConfig.stagesByCatalog.pillar ?? []).map((entry) => ({ value: entry.key, label: entry.label }))
728
+ ];
729
+ return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
730
+ /* @__PURE__ */ jsx(
731
+ PageTitleCaption,
732
+ {
733
+ title: contentConfig.vocabulary.item.plural,
734
+ caption: reviewFilterActive ? `${contentConfig.vocabulary.item.plural} awaiting review, across every pipeline` : `Every ${contentConfig.vocabulary.item.singular.toLowerCase()} across the content pipeline`
735
+ }
736
+ ),
737
+ headerActions ? /* @__PURE__ */ jsx(Group, { gap: "xs", children: headerActions }) : null,
738
+ /* @__PURE__ */ jsx(Paper, { children: /* @__PURE__ */ jsxs(Stack, { children: [
739
+ /* @__PURE__ */ jsxs(FilterBar, { children: [
740
+ /* @__PURE__ */ jsx(
741
+ TextInput,
742
+ {
743
+ placeholder: "Search by title...",
744
+ leftSection: /* @__PURE__ */ jsx(IconSearch, { size: 16 }),
745
+ value: searchQuery,
746
+ onChange: (event) => setSearchQuery(event.currentTarget.value)
747
+ }
748
+ ),
749
+ /* @__PURE__ */ jsx(
750
+ Select,
751
+ {
752
+ placeholder: "All statuses",
753
+ data: statusOptions,
754
+ value: statusFilter,
755
+ onChange: (value) => setStatusFilter(value ?? ""),
756
+ clearable: true,
757
+ size: "sm",
758
+ w: 180
759
+ }
760
+ ),
761
+ /* @__PURE__ */ jsx(
762
+ Select,
763
+ {
764
+ placeholder: "All pillars",
765
+ data: pillarOptions,
766
+ value: pillarFilter,
767
+ onChange: (value) => setPillarFilter(value ?? ""),
768
+ clearable: true,
769
+ size: "sm",
770
+ w: 180
771
+ }
772
+ )
773
+ ] }),
774
+ itemsQuery.isLoading ? /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) : itemsQuery.isError ? /* @__PURE__ */ jsx(CenteredErrorState, { error: itemsQuery.error, title: "Failed to load content items" }) : items.length === 0 ? /* @__PURE__ */ jsx(
775
+ EmptyState,
776
+ {
777
+ icon: IconFileText,
778
+ title: searchQuery.trim() || statusFilter || pillarFilter ? "No items match your filters" : reviewFilterActive ? "Nothing awaiting review" : `No ${contentConfig.vocabulary.item.plural.toLowerCase()} yet`
779
+ }
780
+ ) : /* @__PURE__ */ jsxs(Table, { children: [
781
+ /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
782
+ /* @__PURE__ */ jsx(Table.Th, { children: contentConfig.vocabulary.item.singular }),
783
+ /* @__PURE__ */ jsx(Table.Th, { children: "Status" }),
784
+ /* @__PURE__ */ jsx(Table.Th, { children: "Pillar" }),
785
+ /* @__PURE__ */ jsx(Table.Th, { children: "Reviewed" }),
786
+ /* @__PURE__ */ jsx(Table.Th, { children: "Updated" })
787
+ ] }) }),
788
+ /* @__PURE__ */ jsx(Table.Tbody, { children: items.map((item) => {
789
+ const statusEntry = contentConfig.stageCatalog[item.status];
790
+ const pillarEntry = item.pillar ? contentConfig.stageCatalog[item.pillar] : void 0;
791
+ const statusColors = getContentStageColor(statusEntry);
792
+ return /* @__PURE__ */ jsxs(
793
+ Table.Tr,
794
+ {
795
+ style: { cursor: "pointer" },
796
+ onClick: () => navigate({ to: "/content/items/$itemId", params: { itemId: item.id } }),
797
+ children: [
798
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, children: getContentItemIdentity(item) }) }),
799
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(
800
+ Badge,
801
+ {
802
+ size: "sm",
803
+ variant: "light",
804
+ styles: {
805
+ root: { color: statusColors.accent, backgroundColor: statusColors.background }
806
+ },
807
+ children: statusEntry?.label ?? item.status
808
+ }
809
+ ) }),
810
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: pillarEntry?.label ?? item.pillar ?? "\u2014" }) }),
811
+ /* @__PURE__ */ jsx(Table.Td, { children: item.reviewedAt ? /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "light", color: "var(--color-success)", children: "Reviewed" }) : /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "light", color: "var(--color-warning)", children: "Pending" }) }),
812
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: formatRelativeTime(item.updatedAt) }) })
813
+ ]
814
+ },
815
+ item.id
816
+ );
817
+ }) })
818
+ ] }),
819
+ total > PAGE_SIZE && /* @__PURE__ */ jsx(Group, { justify: "center", children: /* @__PURE__ */ jsx(
820
+ Pagination,
821
+ {
822
+ value: pagination.page,
823
+ onChange: pagination.setPage,
824
+ total: pagination.totalPages(total),
825
+ size: "sm"
826
+ }
827
+ ) })
828
+ ] }) })
829
+ ] }) }) });
830
+ }
831
+
832
+ // src/lib/content/processing-state.ts
833
+ function mapContentStepStatus(status) {
834
+ if (status === void 0) return "pending";
835
+ if (status === "error") return "failed";
836
+ return "completed";
837
+ }
838
+ function getContentProcessingSteps(processingState, pipelineSteps) {
839
+ return pipelineSteps.map((step) => ({
840
+ key: step.key,
841
+ label: step.label,
842
+ status: mapContentStepStatus(processingState?.[step.key]?.status)
843
+ }));
844
+ }
845
+
846
+ // src/lib/content/payload-text.ts
847
+ function toDisplayKind(type) {
848
+ if (type === "text") return "longText";
849
+ if (type === "string-array") return "list";
850
+ return "text";
851
+ }
852
+ function getPrimaryPayloadTextField(payloadFields) {
853
+ if (!payloadFields || payloadFields.length === 0) return void 0;
854
+ return payloadFields.find((field) => field.type === "text") ?? payloadFields.find((field) => field.type === "string");
855
+ }
856
+ function getPayloadText(payload, fieldKey) {
857
+ if (!fieldKey || !payload || typeof payload !== "object") return "";
858
+ const value = payload[fieldKey];
859
+ return typeof value === "string" ? value : "";
860
+ }
861
+ function getPipelinePayloadFields(pipelineSteps) {
862
+ const seen = /* @__PURE__ */ new Map();
863
+ for (const step of pipelineSteps) {
864
+ for (const field of step.payloadFields) {
865
+ if (!seen.has(field.key)) seen.set(field.key, field);
866
+ }
867
+ }
868
+ return [...seen.values()];
869
+ }
870
+ function toPayloadDisplayFields(fields, payload) {
871
+ if (!payload || typeof payload !== "object") return [];
872
+ const payloadRecord = payload;
873
+ return fields.filter((field) => field.key in payloadRecord).map((field) => ({ key: field.key, label: field.label, kind: toDisplayKind(field.type) }));
874
+ }
875
+ var REVIEW_QUEUE_FETCH_LIMIT = 100;
876
+ function buildReviewActions(isTerminalPipeline) {
877
+ return [
878
+ {
879
+ id: "approved",
880
+ label: "Approve",
881
+ icon: IconCheck,
882
+ // Decision 7, resolved 2026-08-12: approve collects an OPTIONAL note, which is what
883
+ // finally gives `content_items.reviewer_feedback` a writer -- the page has always
884
+ // rendered it and the API has always accepted it, but nothing in the UI could set it.
885
+ // `feedbackRequired` is deliberately omitted: a reviewer with nothing to add still
886
+ // approves in two clicks, and a blank note leaves the column NULL rather than ''.
887
+ requiresFeedback: true,
888
+ feedbackLabel: "Note (optional)",
889
+ feedbackPlaceholder: "Anything the next step or a later reader should know?",
890
+ confirmationMessage: isTerminalPipeline ? "Approve this item? There is no further pipeline step -- this is the last internal gate before it is published." : "Approve this item? It moves on to the next pipeline step.",
891
+ consequence: isTerminalPipeline ? "irreversible" : "standard"
892
+ },
893
+ {
894
+ id: "rejected",
895
+ label: "Reject",
896
+ icon: IconX,
897
+ tone: "danger",
898
+ requiresFeedback: true,
899
+ feedbackLabel: "Reason for rejection",
900
+ feedbackPlaceholder: "What needs to change before this can go out?",
901
+ feedbackRequired: true
902
+ }
903
+ ];
904
+ }
905
+ function DistributionMetricsCell({ metrics }) {
906
+ const entries = metrics && typeof metrics === "object" ? Object.entries(metrics) : [];
907
+ if (entries.length === 0) {
908
+ return /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "\u2014" });
909
+ }
910
+ const isFlat = entries.every(([, value]) => value === null || typeof value !== "object");
911
+ if (!isFlat) {
912
+ return /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", ff: "monospace", style: { overflowWrap: "anywhere" }, children: JSON.stringify(metrics) });
913
+ }
914
+ return /* @__PURE__ */ jsx(Stack, { gap: 2, children: entries.map(([key, value]) => /* @__PURE__ */ jsxs(Group, { gap: "xs", wrap: "nowrap", justify: "space-between", children: [
915
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", style: { overflowWrap: "anywhere" }, children: key }),
916
+ /* @__PURE__ */ jsx(Text, { size: "xs", ff: "monospace", style: { overflowWrap: "anywhere" }, children: String(value) })
917
+ ] }, key)) });
918
+ }
919
+ function ContentItemReviewPage({ itemId, headerActions }) {
920
+ const navigate = useNavigate();
921
+ const itemQuery = useContentItem(itemId);
922
+ const attemptsQuery = useContentItemAttempts(itemId);
923
+ const distributionsQuery = useContentDistributions({ contentItemId: itemId });
924
+ const contentConfig = useContentConfig();
925
+ const updateItem = useUpdateContentItem();
926
+ const reviewItem = useReviewContentItem();
927
+ const [viewingAttemptId, setViewingAttemptId] = useState(null);
928
+ const [body, setBody] = useState("");
929
+ const item = itemQuery.data;
930
+ const pipeline = item ? contentConfig.pipelines.find((candidate) => candidate.id === item.pipelineId) : void 0;
931
+ const pipelineSteps = useMemo(() => pipeline?.steps ?? [], [pipeline]);
932
+ const isTerminalPipeline = pipelineSteps.length <= 1;
933
+ const reviewActions = useMemo(() => buildReviewActions(isTerminalPipeline), [isTerminalPipeline]);
934
+ const pipelinePayloadFields = useMemo(() => getPipelinePayloadFields(pipelineSteps), [pipelineSteps]);
935
+ const primaryTextField = useMemo(() => getPrimaryPayloadTextField(pipelinePayloadFields), [pipelinePayloadFields]);
936
+ const originalBody = item ? getPayloadText(item.payload, primaryTextField?.key) : "";
937
+ const savedBody = item?.body ?? originalBody;
938
+ const displayPayloadFields = useMemo(
939
+ () => toPayloadDisplayFields(pipelinePayloadFields, item?.payload),
940
+ [pipelinePayloadFields, item?.payload]
941
+ );
942
+ const processingSteps = useMemo(
943
+ () => getContentProcessingSteps(item?.processingState, pipelineSteps),
944
+ [item?.processingState, pipelineSteps]
945
+ );
946
+ const visibleProcessingSteps = useMemo(
947
+ () => processingSteps.some((step) => step.status !== "pending") ? processingSteps : [],
948
+ [processingSteps]
949
+ );
950
+ const pipelineItemsQuery = useContentItems({
951
+ pipelineId: item?.pipelineId ?? void 0,
952
+ limit: REVIEW_QUEUE_FETCH_LIMIT
953
+ });
954
+ const reviewQueue = useMemo(() => {
955
+ if (!item?.pipelineId) return [];
956
+ return (pipelineItemsQuery.data?.data ?? []).filter((candidate) => candidate.reviewedAt === null).sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
957
+ }, [pipelineItemsQuery.data, item?.pipelineId]);
958
+ const queueIndex = item ? reviewQueue.findIndex((candidate) => candidate.id === item.id) : -1;
959
+ const queuePosition = queueIndex === -1 ? void 0 : queueIndex + 1;
960
+ const nextQueueItemId = queueIndex === -1 ? void 0 : reviewQueue[queueIndex + 1]?.id;
961
+ const goToNextInQueue = useCallback(() => {
962
+ if (nextQueueItemId) {
963
+ navigate({ to: "/content/items/$itemId", params: { itemId: nextQueueItemId } });
964
+ } else {
965
+ navigate({ to: "/content/items" });
966
+ }
967
+ }, [navigate, nextQueueItemId]);
968
+ useEffect(() => {
969
+ if (item) setBody(item.body ?? originalBody);
970
+ }, [item?.id]);
971
+ useEffect(() => {
972
+ function handleKeyDown(event) {
973
+ const target = event.target;
974
+ if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLElement && target.isContentEditable) {
975
+ return;
976
+ }
977
+ if (event.key === "a" || event.key === "A") {
978
+ event.preventDefault();
979
+ document.querySelector('[data-testid="content-review-action-approved"]')?.click();
980
+ } else if (event.key === "r" || event.key === "R") {
981
+ event.preventDefault();
982
+ document.querySelector('[data-testid="content-review-action-rejected"]')?.click();
983
+ } else if (event.key === "n" || event.key === "N" || event.key === "ArrowRight") {
984
+ event.preventDefault();
985
+ goToNextInQueue();
986
+ }
987
+ }
988
+ window.addEventListener("keydown", handleKeyDown);
989
+ return () => window.removeEventListener("keydown", handleKeyDown);
990
+ }, [goToNextInQueue]);
991
+ const backButton = /* @__PURE__ */ jsx(
992
+ Button,
993
+ {
994
+ variant: "light",
995
+ size: "xs",
996
+ leftSection: /* @__PURE__ */ jsx(IconArrowLeft, { size: 16 }),
997
+ onClick: () => navigate({ to: "/content/items" }),
998
+ children: "Items"
999
+ }
1000
+ );
1001
+ if (itemQuery.isLoading) {
1002
+ return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
1003
+ /* @__PURE__ */ jsx(
1004
+ PageTitleCaption,
1005
+ {
1006
+ title: "Item Review",
1007
+ caption: "Review, edit, and approve or reject a content item",
1008
+ rightSection: backButton
1009
+ }
1010
+ ),
1011
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) })
1012
+ ] }) }) });
1013
+ }
1014
+ if (itemQuery.isError || !item) {
1015
+ return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
1016
+ /* @__PURE__ */ jsx(
1017
+ PageTitleCaption,
1018
+ {
1019
+ title: "Item Review",
1020
+ caption: "Review, edit, and approve or reject a content item",
1021
+ rightSection: backButton
1022
+ }
1023
+ ),
1024
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(CenteredErrorState, { error: itemQuery.error, title: "Failed to load content item" }) })
1025
+ ] }) }) });
1026
+ }
1027
+ const isDirty = body !== (item.body ?? originalBody);
1028
+ const attempts = attemptsQuery.data?.data ?? [];
1029
+ const distributions = distributionsQuery.data?.data ?? [];
1030
+ const isSaving = updateItem.isPending || reviewItem.isPending;
1031
+ const viewingAttempt = attempts.find((attempt) => attempt.id === viewingAttemptId);
1032
+ const identity = getContentItemIdentity(item, pipelinePayloadFields);
1033
+ const persistBodyIfDirty = async () => {
1034
+ if (!isDirty) return;
1035
+ await updateItem.mutateAsync({ itemId: item.id, updates: { body } });
1036
+ };
1037
+ const handleSave = async () => {
1038
+ await persistBodyIfDirty();
1039
+ showSuccessNotification("Saved");
1040
+ };
1041
+ const handleAction = async (actionId, feedback) => {
1042
+ await persistBodyIfDirty();
1043
+ if (actionId === "approved") {
1044
+ const note = feedback?.trim();
1045
+ await reviewItem.mutateAsync({
1046
+ itemId: item.id,
1047
+ review: { decision: "approved", reviewerFeedback: note ? note : void 0 }
1048
+ });
1049
+ showSuccessNotification("Item approved");
1050
+ } else if (actionId === "rejected") {
1051
+ await reviewItem.mutateAsync({ itemId: item.id, review: { decision: "rejected", rejectReason: feedback ?? "" } });
1052
+ showSuccessNotification("Item rejected");
1053
+ }
1054
+ goToNextInQueue();
1055
+ };
1056
+ return /* @__PURE__ */ jsxs(SubshellContentContainer, { children: [
1057
+ /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
1058
+ /* @__PURE__ */ jsx(PageTitleCaption, { title: identity }),
1059
+ headerActions ? /* @__PURE__ */ jsx(Group, { gap: "xs", children: headerActions }) : null,
1060
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", gap: "xs", wrap: "nowrap", align: "center", children: [
1061
+ /* @__PURE__ */ jsxs(Group, { gap: "xs", wrap: "wrap", children: [
1062
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: pipeline?.label ?? item.pipelineId ?? "Unassigned" }),
1063
+ /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "light", children: contentConfig.stageCatalog[item.status]?.label ?? item.status }),
1064
+ item.pillar && /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "light", children: contentConfig.stageCatalog[item.pillar]?.label ?? item.pillar }),
1065
+ /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
1066
+ item.reviewedAt ? `Reviewed ${new Date(item.reviewedAt).toLocaleDateString()}` : "Not yet reviewed",
1067
+ queuePosition ? ` \xB7 ${queuePosition} of ${reviewQueue.length}` : ""
1068
+ ] })
1069
+ ] }),
1070
+ /* @__PURE__ */ jsxs(Group, { gap: "xs", wrap: "nowrap", children: [
1071
+ /* @__PURE__ */ jsx(
1072
+ Button,
1073
+ {
1074
+ size: "xs",
1075
+ variant: "light",
1076
+ onClick: handleSave,
1077
+ disabled: !isDirty || isSaving,
1078
+ loading: updateItem.isPending,
1079
+ children: "Save"
1080
+ }
1081
+ ),
1082
+ backButton
1083
+ ] })
1084
+ ] }),
1085
+ /* @__PURE__ */ jsx(
1086
+ ContentReviewCard,
1087
+ {
1088
+ reviewMode: "live",
1089
+ body,
1090
+ onBodyChange: setBody,
1091
+ originalBody: savedBody,
1092
+ onReset: () => setBody(savedBody),
1093
+ payload: item.payload,
1094
+ payloadFields: displayPayloadFields,
1095
+ processingSteps: visibleProcessingSteps,
1096
+ actions: reviewActions,
1097
+ onAction: handleAction,
1098
+ actionsPending: isSaving
1099
+ }
1100
+ ),
1101
+ item.reviewerFeedback && /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
1102
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", fw: 600, children: "Reviewer feedback" }),
1103
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: item.reviewerFeedback })
1104
+ ] }) }),
1105
+ item.rejectReason && /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
1106
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "var(--color-error)", fw: 600, children: "Reject reason" }),
1107
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: item.rejectReason })
1108
+ ] }) }),
1109
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
1110
+ /* @__PURE__ */ jsx(Title, { order: 4, children: "Attempt History" }),
1111
+ attempts.length === 0 ? /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "No recorded attempts." }) : /* @__PURE__ */ jsxs(Table, { children: [
1112
+ /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
1113
+ /* @__PURE__ */ jsx(Table.Th, { children: "Attempt" }),
1114
+ /* @__PURE__ */ jsx(Table.Th, { children: "Step" }),
1115
+ /* @__PURE__ */ jsx(Table.Th, { children: "Created" }),
1116
+ /* @__PURE__ */ jsx(Table.Th, { children: "Source Execution" }),
1117
+ /* @__PURE__ */ jsx(Table.Th, {})
1118
+ ] }) }),
1119
+ /* @__PURE__ */ jsx(Table.Tbody, { children: attempts.map((attempt) => /* @__PURE__ */ jsxs(Table.Tr, { children: [
1120
+ /* @__PURE__ */ jsxs(Table.Td, { children: [
1121
+ "#",
1122
+ attempt.attemptNumber
1123
+ ] }),
1124
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", children: attempt.stepKey ?? "\u2014" }) }),
1125
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: new Date(attempt.createdAt).toLocaleString() }) }),
1126
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", ff: "monospace", children: attempt.sourceExecutionId ?? "\u2014" }) }),
1127
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Button, { size: "compact-xs", variant: "subtle", onClick: () => setViewingAttemptId(attempt.id), children: "View payload" }) })
1128
+ ] }, attempt.id)) })
1129
+ ] }),
1130
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Attempt numbers count per item, not per step, so several attempts can share a step. Rows recorded before 2026-08-12 predate the step column and show no step." })
1131
+ ] }) }),
1132
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
1133
+ /* @__PURE__ */ jsx(Title, { order: 4, children: "Distributions" }),
1134
+ distributions.length === 0 ? /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "No distributions yet." }) : /* @__PURE__ */ jsxs(Table, { children: [
1135
+ /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
1136
+ /* @__PURE__ */ jsx(Table.Th, { children: "Platform" }),
1137
+ /* @__PURE__ */ jsx(Table.Th, { children: "Status" }),
1138
+ /* @__PURE__ */ jsx(Table.Th, { children: "Publish Method" }),
1139
+ /* @__PURE__ */ jsx(Table.Th, { children: "Platform Post" }),
1140
+ /* @__PURE__ */ jsx(Table.Th, { children: "Metrics" }),
1141
+ /* @__PURE__ */ jsx(Table.Th, { children: "Metrics Updated" })
1142
+ ] }) }),
1143
+ /* @__PURE__ */ jsx(Table.Tbody, { children: distributions.map((distribution) => /* @__PURE__ */ jsxs(Table.Tr, { children: [
1144
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", children: distribution.platform }) }),
1145
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "light", children: distribution.status }) }),
1146
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: distribution.publishMethod ?? "\u2014" }) }),
1147
+ /* @__PURE__ */ jsx(Table.Td, { children: distribution.platformUrl ? /* @__PURE__ */ jsx(
1148
+ Text,
1149
+ {
1150
+ size: "sm",
1151
+ component: "a",
1152
+ href: distribution.platformUrl,
1153
+ target: "_blank",
1154
+ rel: "noreferrer",
1155
+ c: "var(--color-primary)",
1156
+ children: distribution.platformPostId ?? distribution.platformUrl
1157
+ }
1158
+ ) : /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: distribution.platformPostId ?? "\u2014" }) }),
1159
+ /* @__PURE__ */ jsx(Table.Td, { style: { maxWidth: 220 }, children: /* @__PURE__ */ jsx(DistributionMetricsCell, { metrics: distribution.metrics }) }),
1160
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: distribution.metricsUpdatedAt ? new Date(distribution.metricsUpdatedAt).toLocaleString() : "\u2014" }) })
1161
+ ] }, distribution.id)) })
1162
+ ] })
1163
+ ] }) })
1164
+ ] }) }),
1165
+ /* @__PURE__ */ jsx(CustomModal, { opened: !!viewingAttempt, onClose: () => setViewingAttemptId(null), size: "lg", children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
1166
+ /* @__PURE__ */ jsxs(Title, { order: 4, children: [
1167
+ "Attempt #",
1168
+ viewingAttempt?.attemptNumber,
1169
+ " payload"
1170
+ ] }),
1171
+ /* @__PURE__ */ jsx(Text, { size: "xs", ff: "monospace", style: { whiteSpace: "pre-wrap", maxHeight: 400, overflowY: "auto" }, children: viewingAttempt ? JSON.stringify(viewingAttempt.payload, null, 2) : "" })
1172
+ ] }) })
1173
+ ] });
1174
+ }
1175
+ function formatLastRun(lastRunAt) {
1176
+ return lastRunAt ? new Date(lastRunAt).toLocaleString() : "Never";
1177
+ }
1178
+ function PipelineRow({
1179
+ pipeline,
1180
+ statusEntries
1181
+ }) {
1182
+ const navigate = useNavigate();
1183
+ const contentConfig = useContentConfig();
1184
+ const statusKeys = useMemo(() => statusEntries.map((entry) => entry.key), [statusEntries]);
1185
+ const stepResourceIds = useMemo(
1186
+ () => pipeline.steps.map((step) => contentConfig.resolveStepResource(step)?.resourceId).filter((resourceId) => !!resourceId),
1187
+ [pipeline.steps, contentConfig]
1188
+ );
1189
+ const summary = useContentPipelineSummary(pipeline.id, statusKeys, stepResourceIds);
1190
+ return /* @__PURE__ */ jsxs(
1191
+ Table.Tr,
1192
+ {
1193
+ style: { cursor: "pointer" },
1194
+ onClick: () => navigate({ to: "/content/pipelines/$pipelineId", params: { pipelineId: pipeline.id } }),
1195
+ children: [
1196
+ /* @__PURE__ */ jsxs(Table.Td, { children: [
1197
+ /* @__PURE__ */ jsx(Text, { fw: 500, children: pipeline.label }),
1198
+ pipeline.description ? /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: pipeline.description }) : null
1199
+ ] }),
1200
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: summary.isLoading ? /* @__PURE__ */ jsx(Loader, { size: "xs" }) : formatLastRun(summary.lastRunAt) }) })
1201
+ ]
1202
+ }
1203
+ );
1204
+ }
1205
+ function ContentPipelinesPage({ headerActions } = {}) {
1206
+ const contentConfig = useContentConfig();
1207
+ const { vocabulary } = contentConfig;
1208
+ const statusEntries = contentConfig.stagesByCatalog.status ?? [];
1209
+ return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
1210
+ /* @__PURE__ */ jsx(
1211
+ PageTitleCaption,
1212
+ {
1213
+ title: vocabulary.pipeline.plural,
1214
+ caption: `Every declared ${vocabulary.pipeline.singular.toLowerCase()} and its production state`
1215
+ }
1216
+ ),
1217
+ headerActions ? /* @__PURE__ */ jsx(Group, { gap: "xs", children: headerActions }) : null,
1218
+ /* @__PURE__ */ jsx(Paper, { children: contentConfig.pipelines.length === 0 ? /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(
1219
+ EmptyState,
1220
+ {
1221
+ icon: IconListDetails,
1222
+ title: `No ${vocabulary.pipeline.plural.toLowerCase()} declared`,
1223
+ description: `The content System's Organization Model declares no ${vocabulary.pipeline.singular.toLowerCase()} catalog entries yet.`
1224
+ }
1225
+ ) }) : /* @__PURE__ */ jsxs(Table, { children: [
1226
+ /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
1227
+ /* @__PURE__ */ jsx(Table.Th, { children: vocabulary.pipeline.singular }),
1228
+ /* @__PURE__ */ jsx(Table.Th, { children: "Last run" })
1229
+ ] }) }),
1230
+ /* @__PURE__ */ jsx(Table.Tbody, { children: contentConfig.pipelines.map((pipeline) => /* @__PURE__ */ jsx(PipelineRow, { pipeline, statusEntries }, pipeline.id)) })
1231
+ ] }) })
1232
+ ] }) }) });
1233
+ }
1234
+ var ACTOR_LEAD = {
1235
+ vendor: "An outside service",
1236
+ workflow: "An automated workflow",
1237
+ agent: "An AI agent"
1238
+ };
1239
+ var REVIEW_MODE_TAIL = {
1240
+ live: "and you review it live, in the moment",
1241
+ queued: "then it waits for your review",
1242
+ none: "and moves on without review"
1243
+ };
1244
+ function describeStep(step) {
1245
+ return `${ACTOR_LEAD[step.actor]} handles "${step.label}," ${REVIEW_MODE_TAIL[step.reviewMode]}.`;
1246
+ }
1247
+ function StepRow({ step, resourceId }) {
1248
+ return /* @__PURE__ */ jsxs(Group, { gap: "sm", align: "flex-start", wrap: "nowrap", children: [
1249
+ /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "light", style: { flexShrink: 0 }, children: step.order }),
1250
+ /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
1251
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: describeStep(step) }),
1252
+ step.description ? /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: step.description }) : null,
1253
+ resourceId ? /* @__PURE__ */ jsx(Text, { size: "xs", ff: "monospace", c: "dimmed", children: resourceId }) : null
1254
+ ] })
1255
+ ] });
1256
+ }
1257
+ function HowItWorks({ pipeline }) {
1258
+ const contentConfig = useContentConfig();
1259
+ return /* @__PURE__ */ jsx(Accordion, { variant: "separated", children: /* @__PURE__ */ jsxs(Accordion.Item, { value: "how-it-works", children: [
1260
+ /* @__PURE__ */ jsx(Accordion.Control, { icon: /* @__PURE__ */ jsx(IconInfoCircle, { size: 16 }), children: /* @__PURE__ */ jsxs(Text, { size: "sm", fw: 500, children: [
1261
+ "How this ",
1262
+ contentConfig.vocabulary.pipeline.singular.toLowerCase(),
1263
+ " works"
1264
+ ] }) }),
1265
+ /* @__PURE__ */ jsx(Accordion.Panel, { children: /* @__PURE__ */ jsx(Stack, { gap: "sm", children: pipeline.steps.map((step) => /* @__PURE__ */ jsx(StepRow, { step, resourceId: contentConfig.resolveStepResource(step)?.resourceId }, step.key)) }) })
1266
+ ] }) });
1267
+ }
1268
+ var BOARD_FETCH_LIMIT = 100;
1269
+ function formatAge(ms) {
1270
+ const seconds = Math.floor(ms / 1e3);
1271
+ if (seconds < 60) return `${seconds}s`;
1272
+ const minutes = Math.floor(seconds / 60);
1273
+ if (minutes < 60) return `${minutes}m`;
1274
+ const hours = Math.floor(minutes / 60);
1275
+ if (hours < 24) return `${hours}h`;
1276
+ return `${Math.floor(hours / 24)}d`;
1277
+ }
1278
+ function BoardItemCard({ card, onOpen }) {
1279
+ return /* @__PURE__ */ jsx(Card, { withBorder: true, padding: "xs", onClick: () => onOpen(card.item.id), style: { cursor: "pointer" }, children: /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
1280
+ /* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, lineClamp: 2, children: getContentItemIdentity(card.item) }),
1281
+ /* @__PURE__ */ jsxs(Group, { gap: 6, justify: "space-between", wrap: "nowrap", children: [
1282
+ /* @__PURE__ */ jsxs(Group, { gap: 4, wrap: "nowrap", children: [
1283
+ /* @__PURE__ */ jsx(IconClockHour4, { size: 12, style: { color: "var(--color-text-dimmed)" } }),
1284
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: formatAge(card.ageMs) })
1285
+ ] }),
1286
+ card.awaitingReview ? /* @__PURE__ */ jsx(Badge, { size: "xs", variant: "light", children: "Awaiting review" }) : null
1287
+ ] })
1288
+ ] }) });
1289
+ }
1290
+ function BoardColumn({ column, onOpen }) {
1291
+ return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "sm", style: { minWidth: 260, maxWidth: 260, flexShrink: 0 }, children: /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
1292
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", wrap: "nowrap", children: [
1293
+ /* @__PURE__ */ jsx(Text, { fw: 600, size: "sm", truncate: true, children: column.step.label }),
1294
+ /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "light", children: column.cards.length })
1295
+ ] }),
1296
+ column.cards.length === 0 ? /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Nothing here" }) : /* @__PURE__ */ jsx(Stack, { gap: "xs", children: column.cards.map((card) => /* @__PURE__ */ jsx(BoardItemCard, { card, onOpen }, card.item.id)) })
1297
+ ] }) });
1298
+ }
1299
+ function ColumnBoard({ board, onOpen }) {
1300
+ return /* @__PURE__ */ jsx(ScrollArea, { type: "auto", offsetScrollbars: true, children: /* @__PURE__ */ jsx(Group, { gap: "xs", align: "flex-start", wrap: "nowrap", children: board.columns.map((column) => /* @__PURE__ */ jsx(BoardColumn, { column, onOpen }, column.step.key)) }) });
1301
+ }
1302
+ function CollapsedBoard({
1303
+ board,
1304
+ itemNounPlural,
1305
+ onOpen
1306
+ }) {
1307
+ const column = board.columns[0];
1308
+ if (!column) {
1309
+ return /* @__PURE__ */ jsx(
1310
+ EmptyState,
1311
+ {
1312
+ icon: IconRoute,
1313
+ title: "No steps declared",
1314
+ description: "This pipeline has no steps yet, so nothing has anywhere to sit."
1315
+ }
1316
+ );
1317
+ }
1318
+ const waiting = column.cards.filter((card) => card.awaitingReview);
1319
+ const settled = column.cards.filter((card) => !card.awaitingReview);
1320
+ const hasGate = !!column.gate;
1321
+ return /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
1322
+ /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
1323
+ /* @__PURE__ */ jsx(Text, { size: "sm", fw: 600, children: hasGate ? "Waiting on review" : column.step.label }),
1324
+ waiting.length === 0 ? column.cards.length === 0 ? /* @__PURE__ */ jsx(
1325
+ EmptyState,
1326
+ {
1327
+ icon: IconChecklist,
1328
+ title: `No ${itemNounPlural.toLowerCase()} yet`,
1329
+ description: `Produce one and it lands at "${column.step.label}."`
1330
+ }
1331
+ ) : /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "Nothing is waiting on review right now." }) : /* @__PURE__ */ jsx(Stack, { gap: "xs", children: waiting.map((card) => /* @__PURE__ */ jsx(BoardItemCard, { card, onOpen }, card.item.id)) })
1332
+ ] }),
1333
+ settled.length > 0 ? /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
1334
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", tt: "uppercase", fw: 600, children: hasGate ? "Already reviewed" : "In progress" }),
1335
+ /* @__PURE__ */ jsx(Stack, { gap: "xs", children: settled.map((card) => /* @__PURE__ */ jsx(BoardItemCard, { card, onOpen }, card.item.id)) })
1336
+ ] }) : null
1337
+ ] });
1338
+ }
1339
+ function BoardOverflowSection({
1340
+ title,
1341
+ description,
1342
+ cards,
1343
+ onOpen
1344
+ }) {
1345
+ return /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
1346
+ /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
1347
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", tt: "uppercase", fw: 600, children: title }),
1348
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: description })
1349
+ ] }),
1350
+ /* @__PURE__ */ jsx(Group, { gap: "xs", align: "flex-start", wrap: "wrap", children: cards.map((card) => /* @__PURE__ */ jsx(Box, { style: { width: 260 }, children: /* @__PURE__ */ jsx(BoardItemCard, { card, onOpen }) }, card.item.id)) })
1351
+ ] });
1352
+ }
1353
+ function BoardTab({ pipeline }) {
1354
+ const navigate = useNavigate();
1355
+ const contentConfig = useContentConfig();
1356
+ const itemsQuery = useContentItems({ pipelineId: pipeline.id, limit: BOARD_FETCH_LIMIT });
1357
+ const items = useMemo(() => itemsQuery.data?.data ?? [], [itemsQuery.data]);
1358
+ const board = useMemo(() => deriveContentBoard(items, pipeline, Date.now()), [items, pipeline]);
1359
+ const openItem = (itemId) => navigate({ to: "/content/items/$itemId", params: { itemId } });
1360
+ if (itemsQuery.isLoading) {
1361
+ return /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) });
1362
+ }
1363
+ if (itemsQuery.isError) {
1364
+ return /* @__PURE__ */ jsx(
1365
+ CenteredErrorState,
1366
+ {
1367
+ error: itemsQuery.error,
1368
+ title: `Failed to load ${contentConfig.vocabulary.item.plural.toLowerCase()}`
1369
+ }
1370
+ );
1371
+ }
1372
+ if (items.length === 0) {
1373
+ return /* @__PURE__ */ jsx(
1374
+ EmptyState,
1375
+ {
1376
+ icon: IconChecklist,
1377
+ title: `No ${contentConfig.vocabulary.item.plural.toLowerCase()} in this pipeline yet`,
1378
+ description: "Produce something from the Produce tab and it will show up here."
1379
+ }
1380
+ );
1381
+ }
1382
+ return /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
1383
+ board.isCollapsed ? /* @__PURE__ */ jsx(CollapsedBoard, { board, itemNounPlural: contentConfig.vocabulary.item.plural, onOpen: openItem }) : /* @__PURE__ */ jsx(ColumnBoard, { board, onOpen: openItem }),
1384
+ board.done.length > 0 ? /* @__PURE__ */ jsx(
1385
+ BoardOverflowSection,
1386
+ {
1387
+ title: "Done",
1388
+ description: "Every declared step has succeeded for these.",
1389
+ cards: board.done,
1390
+ onOpen: openItem
1391
+ }
1392
+ ) : null,
1393
+ board.unplaced.length > 0 ? /* @__PURE__ */ jsx(
1394
+ BoardOverflowSection,
1395
+ {
1396
+ title: "Not on this pipeline's current steps",
1397
+ description: "Their recorded steps aren't in the step catalog anymore -- shown honestly rather than dropped into the first column.",
1398
+ cards: board.unplaced,
1399
+ onOpen: openItem
1400
+ }
1401
+ ) : null,
1402
+ /* @__PURE__ */ jsx(HowItWorks, { pipeline })
1403
+ ] });
1404
+ }
1405
+ var PAGE_SIZE2 = 20;
1406
+ var FETCH_LIMIT = 100;
1407
+ function summarizeMetrics(metrics) {
1408
+ if (!metrics || typeof metrics !== "object" || Array.isArray(metrics)) return "\u2014";
1409
+ const entries = Object.entries(metrics);
1410
+ if (entries.length === 0) return "\u2014";
1411
+ const shown = entries.slice(0, 2).map(([key, value]) => `${key} ${String(value)}`).join(" \xB7 ");
1412
+ return entries.length > 2 ? `${shown} +${entries.length - 2}` : shown;
1413
+ }
1414
+ function formatDateTime(value) {
1415
+ return value ? new Date(value).toLocaleDateString() : "\u2014";
1416
+ }
1417
+ function DistributionsTab({ pipeline }) {
1418
+ const navigate = useNavigate();
1419
+ const contentConfig = useContentConfig();
1420
+ const distributionLabel = contentConfig.vocabulary.distribution.plural.toLowerCase();
1421
+ const itemsQuery = useContentItems({ pipelineId: pipeline.id, limit: FETCH_LIMIT });
1422
+ const distributionsQuery = useContentDistributions({ limit: FETCH_LIMIT });
1423
+ const itemById = useMemo(() => {
1424
+ const map = /* @__PURE__ */ new Map();
1425
+ for (const item of itemsQuery.data?.data ?? []) map.set(item.id, item);
1426
+ return map;
1427
+ }, [itemsQuery.data]);
1428
+ const distributions = useMemo(
1429
+ () => (distributionsQuery.data?.data ?? []).filter((distribution) => itemById.has(distribution.contentItemId)),
1430
+ [distributionsQuery.data, itemById]
1431
+ );
1432
+ const pagination = usePaginationState(PAGE_SIZE2, [pipeline.id], distributions.length);
1433
+ const paginatedDistributions = distributions.slice(pagination.offset, pagination.offset + PAGE_SIZE2);
1434
+ const isLoading = itemsQuery.isLoading || distributionsQuery.isLoading;
1435
+ const error = itemsQuery.error ?? distributionsQuery.error;
1436
+ const resolveIdentity = (distribution) => {
1437
+ const item = itemById.get(distribution.contentItemId);
1438
+ return item ? getContentItemIdentity(item) : distribution.contentItemId;
1439
+ };
1440
+ if (isLoading) {
1441
+ return /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) });
1442
+ }
1443
+ if (itemsQuery.isError || distributionsQuery.isError) {
1444
+ return /* @__PURE__ */ jsx(CenteredErrorState, { error, title: `Failed to load ${distributionLabel}` });
1445
+ }
1446
+ if (distributions.length === 0) {
1447
+ return /* @__PURE__ */ jsx(
1448
+ EmptyState,
1449
+ {
1450
+ icon: IconShare,
1451
+ title: `No ${distributionLabel} yet`,
1452
+ description: `Nothing from this pipeline has been distributed anywhere yet.`
1453
+ }
1454
+ );
1455
+ }
1456
+ return /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
1457
+ /* @__PURE__ */ jsxs(Table, { children: [
1458
+ /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
1459
+ /* @__PURE__ */ jsx(Table.Th, { children: contentConfig.vocabulary.item.singular }),
1460
+ /* @__PURE__ */ jsx(Table.Th, { children: "Platform" }),
1461
+ /* @__PURE__ */ jsx(Table.Th, { style: { whiteSpace: "nowrap" }, children: "Publish method" }),
1462
+ /* @__PURE__ */ jsx(Table.Th, { style: { whiteSpace: "nowrap" }, children: "Platform post ID" }),
1463
+ /* @__PURE__ */ jsx(Table.Th, { children: "Metrics" }),
1464
+ /* @__PURE__ */ jsx(Table.Th, { style: { whiteSpace: "nowrap" }, children: "Metrics updated" }),
1465
+ /* @__PURE__ */ jsx(Table.Th, { children: "Published" }),
1466
+ /* @__PURE__ */ jsx(Table.Th, {})
1467
+ ] }) }),
1468
+ /* @__PURE__ */ jsx(Table.Tbody, { children: paginatedDistributions.map((distribution) => {
1469
+ const platformEntry = contentConfig.stageCatalog[distribution.platform];
1470
+ const platformColors = getContentStageColor(platformEntry);
1471
+ return /* @__PURE__ */ jsxs(
1472
+ Table.Tr,
1473
+ {
1474
+ style: { cursor: "pointer" },
1475
+ onClick: () => navigate({
1476
+ to: "/content/distributions/$distributionId",
1477
+ params: { distributionId: distribution.id }
1478
+ }),
1479
+ children: [
1480
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, lineClamp: 1, children: resolveIdentity(distribution) }) }),
1481
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(
1482
+ Badge,
1483
+ {
1484
+ size: "sm",
1485
+ variant: "light",
1486
+ styles: { root: { color: platformColors.accent, backgroundColor: platformColors.background } },
1487
+ children: platformEntry?.label ?? distribution.platform
1488
+ }
1489
+ ) }),
1490
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: distribution.publishMethod ?? "\u2014" }) }),
1491
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "xs", ff: "monospace", c: "dimmed", children: distribution.platformPostId ?? "\u2014" }) }),
1492
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: summarizeMetrics(distribution.metrics) }) }),
1493
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: formatDateTime(distribution.metricsUpdatedAt) }) }),
1494
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: formatDateTime(distribution.publishedAt) }) }),
1495
+ /* @__PURE__ */ jsx(Table.Td, { children: distribution.platformUrl ? /* @__PURE__ */ jsx(
1496
+ Anchor,
1497
+ {
1498
+ href: distribution.platformUrl,
1499
+ target: "_blank",
1500
+ rel: "noopener noreferrer",
1501
+ style: { display: "inline-flex" },
1502
+ onClick: (event) => event.stopPropagation(),
1503
+ children: /* @__PURE__ */ jsx(IconExternalLink, { size: 16 })
1504
+ }
1505
+ ) : null })
1506
+ ]
1507
+ },
1508
+ distribution.id
1509
+ );
1510
+ }) })
1511
+ ] }),
1512
+ distributions.length > PAGE_SIZE2 ? /* @__PURE__ */ jsx(Group, { justify: "center", children: /* @__PURE__ */ jsx(
1513
+ Pagination,
1514
+ {
1515
+ value: pagination.page,
1516
+ onChange: pagination.setPage,
1517
+ total: pagination.totalPages(distributions.length),
1518
+ size: "sm"
1519
+ }
1520
+ ) }) : null
1521
+ ] });
1522
+ }
1523
+ function formatDateTime2(value) {
1524
+ return value ? new Date(value).toLocaleString() : "\u2014";
1525
+ }
1526
+ function formatDuration(startTime, endTime) {
1527
+ if (!endTime) return "Running";
1528
+ const ms = endTime - startTime;
1529
+ if (ms < 1e3) return `${ms} ms`;
1530
+ const seconds = ms / 1e3;
1531
+ if (seconds < 60) return `${seconds.toFixed(1)}s`;
1532
+ return `${Math.floor(seconds / 60)}m ${Math.round(seconds % 60)}s`;
1533
+ }
1534
+ function getStatusColor(status) {
1535
+ switch (status) {
1536
+ case "completed":
1537
+ case "success":
1538
+ return "var(--color-success)";
1539
+ case "running":
1540
+ case "pending":
1541
+ return "var(--color-primary)";
1542
+ case "failed":
1543
+ case "error":
1544
+ return "var(--color-error)";
1545
+ default:
1546
+ return "var(--color-text-dimmed)";
1547
+ }
1548
+ }
1549
+ function StepRunsSection({ step, resourceId }) {
1550
+ const executionsQuery = useExecutions(resourceId ?? "", void 0, 20);
1551
+ const executions = executionsQuery.data?.executions ?? [];
1552
+ return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
1553
+ /* @__PURE__ */ jsx(CardHeader, { title: step.label, subtitle: resourceId ? resourceId : "No resource configured for this step" }),
1554
+ !resourceId ? /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "This step declares no resolvable resource, so it has no execution history to show." }) : executionsQuery.isLoading ? /* @__PURE__ */ jsx(Center, { p: "sm", children: /* @__PURE__ */ jsx(Loader, { size: "sm" }) }) : executions.length === 0 ? /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "No runs recorded for this step yet." }) : /* @__PURE__ */ jsxs(Table, { children: [
1555
+ /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
1556
+ /* @__PURE__ */ jsx(Table.Th, { children: "Execution" }),
1557
+ /* @__PURE__ */ jsx(Table.Th, { children: "Status" }),
1558
+ /* @__PURE__ */ jsx(Table.Th, { children: "Started" }),
1559
+ /* @__PURE__ */ jsx(Table.Th, { children: "Duration" })
1560
+ ] }) }),
1561
+ /* @__PURE__ */ jsx(Table.Tbody, { children: executions.map((execution) => /* @__PURE__ */ jsxs(Table.Tr, { children: [
1562
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "xs", ff: "monospace", c: "dimmed", children: execution.id }) }),
1563
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "light", styles: { root: { color: getStatusColor(execution.status) } }, children: execution.status }) }),
1564
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: formatDateTime2(execution.startTime) }) }),
1565
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: formatDuration(execution.startTime, execution.endTime) }) })
1566
+ ] }, execution.id)) })
1567
+ ] })
1568
+ ] }) });
1569
+ }
1570
+ function RunsTab({ pipeline }) {
1571
+ const contentConfig = useContentConfig();
1572
+ return /* @__PURE__ */ jsx(Stack, { gap: "md", children: pipeline.steps.map((step) => /* @__PURE__ */ jsx(StepRunsSection, { step, resourceId: contentConfig.resolveStepResource(step)?.resourceId }, step.key)) });
1573
+ }
1574
+ var PIPELINE_WORKSPACE_TABS = ["board", "distributions", "runs"];
1575
+ function isPipelineWorkspaceTab(value) {
1576
+ return typeof value === "string" && PIPELINE_WORKSPACE_TABS.includes(value);
1577
+ }
1578
+ function ContentPipelineWorkspacePage({
1579
+ pipelineId,
1580
+ headerActions
1581
+ }) {
1582
+ const navigate = useNavigate();
1583
+ const contentConfig = useContentConfig();
1584
+ const rawSearch = useSearch({ strict: false });
1585
+ const activeTab = isPipelineWorkspaceTab(rawSearch.tab) ? rawSearch.tab : "board";
1586
+ const handleTabChange = useCallback(
1587
+ (value) => {
1588
+ if (!isPipelineWorkspaceTab(value) || value === activeTab) return;
1589
+ void navigate({
1590
+ to: ".",
1591
+ search: ((current) => ({ ...current, tab: value })),
1592
+ replace: false
1593
+ });
1594
+ },
1595
+ [activeTab, navigate]
1596
+ );
1597
+ const pipeline = contentConfig.pipelines.find((candidate) => candidate.id === pipelineId);
1598
+ const vocabulary = contentConfig.vocabulary;
1599
+ const backButton = /* @__PURE__ */ jsx(
1600
+ Button,
1601
+ {
1602
+ variant: "light",
1603
+ size: "xs",
1604
+ leftSection: /* @__PURE__ */ jsx(IconArrowLeft, { size: 16 }),
1605
+ onClick: () => navigate({ to: "/content/pipelines" }),
1606
+ children: "Pipelines"
1607
+ }
1608
+ );
1609
+ if (!pipeline) {
1610
+ return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
1611
+ /* @__PURE__ */ jsx(
1612
+ PageTitleCaption,
1613
+ {
1614
+ title: "Pipeline Not Found",
1615
+ caption: "The requested content pipeline is unavailable.",
1616
+ rightSection: backButton
1617
+ }
1618
+ ),
1619
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(Center, { h: 240, children: /* @__PURE__ */ jsxs(Alert, { icon: /* @__PURE__ */ jsx(IconAlertCircle, { size: 16 }), color: "gray", variant: "light", children: [
1620
+ 'No pipeline named "',
1621
+ pipelineId,
1622
+ '" is declared on the content System.'
1623
+ ] }) }) })
1624
+ ] }) }) });
1625
+ }
1626
+ return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
1627
+ /* @__PURE__ */ jsx(
1628
+ PageTitleCaption,
1629
+ {
1630
+ title: pipeline.label,
1631
+ caption: pipeline.description ?? "Content pipeline configuration, production, and run history",
1632
+ rightSection: backButton
1633
+ }
1634
+ ),
1635
+ headerActions ? /* @__PURE__ */ jsx(Group, { gap: "xs", children: headerActions }) : null,
1636
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(Tabs, { value: activeTab, onChange: handleTabChange, keepMounted: false, children: [
1637
+ /* @__PURE__ */ jsxs(Tabs.List, { children: [
1638
+ /* @__PURE__ */ jsx(Tabs.Tab, { value: "board", leftSection: /* @__PURE__ */ jsx(IconLayoutGrid, { size: 14 }), children: vocabulary.board }),
1639
+ /* @__PURE__ */ jsx(Tabs.Tab, { value: "distributions", leftSection: /* @__PURE__ */ jsx(IconShare, { size: 14 }), children: vocabulary.distribution.plural }),
1640
+ /* @__PURE__ */ jsx(Tabs.Tab, { value: "runs", leftSection: /* @__PURE__ */ jsx(IconPlayerPlay, { size: 14 }), children: "Runs" })
1641
+ ] }),
1642
+ /* @__PURE__ */ jsx(Tabs.Panel, { value: "board", pt: "md", children: /* @__PURE__ */ jsx(BoardTab, { pipeline }) }),
1643
+ /* @__PURE__ */ jsx(Tabs.Panel, { value: "distributions", pt: "md", children: /* @__PURE__ */ jsx(DistributionsTab, { pipeline }) }),
1644
+ /* @__PURE__ */ jsx(Tabs.Panel, { value: "runs", pt: "md", children: /* @__PURE__ */ jsx(RunsTab, { pipeline }) })
1645
+ ] }) })
1646
+ ] }) }) });
1647
+ }
1648
+ var PAGE_SIZE3 = 20;
1649
+ var QUERY_LIMIT = 100;
1650
+ function formatDate(value) {
1651
+ return value ? new Date(value).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" }) : "\u2014";
1652
+ }
1653
+ function ContentDistributionsPage({ headerActions } = {}) {
1654
+ const navigate = useNavigate();
1655
+ const contentConfig = useContentConfig();
1656
+ const [platformFilter, setPlatformFilter] = useState("");
1657
+ const [statusFilter, setStatusFilter] = useState("");
1658
+ const [formatFilter, setFormatFilter] = useState("");
1659
+ const distributionsQuery = useContentDistributions({ limit: QUERY_LIMIT });
1660
+ const itemsQuery = useContentItems({ limit: 100 });
1661
+ const itemTitleById = useMemo(() => {
1662
+ const map = /* @__PURE__ */ new Map();
1663
+ for (const item of itemsQuery.data?.data ?? []) {
1664
+ map.set(item.id, item.title);
1665
+ }
1666
+ return map;
1667
+ }, [itemsQuery.data]);
1668
+ const distributions = distributionsQuery.data?.data ?? [];
1669
+ const formatOptions = useMemo(() => {
1670
+ const formats = new Set(distributions.map((distribution) => distribution.format));
1671
+ return [...formats].sort().map((format) => ({ value: format, label: format }));
1672
+ }, [distributions]);
1673
+ const statusOptions = useMemo(() => {
1674
+ const statuses = new Set(distributions.map((distribution) => distribution.status));
1675
+ return [...statuses].sort().map((status) => ({ value: status, label: status }));
1676
+ }, [distributions]);
1677
+ const filteredDistributions = useMemo(
1678
+ () => distributions.filter(
1679
+ (distribution) => (!platformFilter || distribution.platform === platformFilter) && (!formatFilter || distribution.format === formatFilter) && (!statusFilter || distribution.status === statusFilter)
1680
+ ),
1681
+ [distributions, platformFilter, formatFilter, statusFilter]
1682
+ );
1683
+ const platformOptions = [
1684
+ { value: "", label: "All platforms" },
1685
+ ...(contentConfig.stagesByCatalog.platform ?? []).map((entry) => ({ value: entry.key, label: entry.label }))
1686
+ ];
1687
+ const pagination = usePaginationState(
1688
+ PAGE_SIZE3,
1689
+ [platformFilter, statusFilter, formatFilter],
1690
+ filteredDistributions.length
1691
+ );
1692
+ const paginatedDistributions = filteredDistributions.slice(pagination.offset, pagination.offset + PAGE_SIZE3);
1693
+ const resolveTitle = (distribution) => itemTitleById.get(distribution.contentItemId) ?? distribution.contentItemId;
1694
+ return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
1695
+ /* @__PURE__ */ jsx(
1696
+ PageTitleCaption,
1697
+ {
1698
+ title: "Distributions",
1699
+ caption: "Per-platform content distribution tracking, including publish method and platform metrics"
1700
+ }
1701
+ ),
1702
+ headerActions ? /* @__PURE__ */ jsx(Group, { gap: "xs", children: headerActions }) : null,
1703
+ /* @__PURE__ */ jsx(Paper, { children: /* @__PURE__ */ jsxs(Stack, { children: [
1704
+ /* @__PURE__ */ jsxs(FilterBar, { children: [
1705
+ /* @__PURE__ */ jsx(
1706
+ Select,
1707
+ {
1708
+ placeholder: "All platforms",
1709
+ data: platformOptions,
1710
+ value: platformFilter,
1711
+ onChange: (value) => setPlatformFilter(value ?? ""),
1712
+ clearable: true,
1713
+ size: "sm",
1714
+ w: 180
1715
+ }
1716
+ ),
1717
+ /* @__PURE__ */ jsx(
1718
+ Select,
1719
+ {
1720
+ placeholder: "All formats",
1721
+ data: formatOptions,
1722
+ value: formatFilter,
1723
+ onChange: (value) => setFormatFilter(value ?? ""),
1724
+ clearable: true,
1725
+ size: "sm",
1726
+ w: 180
1727
+ }
1728
+ ),
1729
+ /* @__PURE__ */ jsx(
1730
+ Select,
1731
+ {
1732
+ placeholder: "All statuses",
1733
+ data: statusOptions,
1734
+ value: statusFilter,
1735
+ onChange: (value) => setStatusFilter(value ?? ""),
1736
+ clearable: true,
1737
+ size: "sm",
1738
+ w: 160
1739
+ }
1740
+ )
1741
+ ] }),
1742
+ distributionsQuery.isLoading ? /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) : distributionsQuery.isError ? /* @__PURE__ */ jsx(CenteredErrorState, { error: distributionsQuery.error, title: "Failed to load distributions" }) : paginatedDistributions.length === 0 ? /* @__PURE__ */ jsx(EmptyState, { icon: IconShare, title: "No distributions found" }) : /* @__PURE__ */ jsxs(Table, { children: [
1743
+ /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
1744
+ /* @__PURE__ */ jsx(Table.Th, { children: "Title" }),
1745
+ /* @__PURE__ */ jsx(Table.Th, { children: "Platform" }),
1746
+ /* @__PURE__ */ jsx(Table.Th, { children: "Format" }),
1747
+ /* @__PURE__ */ jsx(Table.Th, { children: "Status" }),
1748
+ /* @__PURE__ */ jsx(Table.Th, { children: "Publish Method" }),
1749
+ /* @__PURE__ */ jsx(Table.Th, { children: "Published" }),
1750
+ /* @__PURE__ */ jsx(Table.Th, {})
1751
+ ] }) }),
1752
+ /* @__PURE__ */ jsx(Table.Tbody, { children: paginatedDistributions.map((distribution) => {
1753
+ const platformEntry = contentConfig.stageCatalog[distribution.platform];
1754
+ const platformColors = getContentStageColor(platformEntry);
1755
+ return /* @__PURE__ */ jsxs(
1756
+ Table.Tr,
1757
+ {
1758
+ style: { cursor: "pointer" },
1759
+ onClick: () => navigate({
1760
+ to: "/content/distributions/$distributionId",
1761
+ params: { distributionId: distribution.id }
1762
+ }),
1763
+ children: [
1764
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { fw: 500, size: "sm", children: resolveTitle(distribution) }) }),
1765
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(
1766
+ Badge,
1767
+ {
1768
+ size: "sm",
1769
+ variant: "light",
1770
+ styles: {
1771
+ root: { color: platformColors.accent, backgroundColor: platformColors.background }
1772
+ },
1773
+ children: platformEntry?.label ?? distribution.platform
1774
+ }
1775
+ ) }),
1776
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", children: distribution.format }) }),
1777
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "light", children: distribution.status }) }),
1778
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: distribution.publishMethod ?? "\u2014" }) }),
1779
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: formatDate(distribution.publishedAt) }) }),
1780
+ /* @__PURE__ */ jsx(Table.Td, { children: distribution.platformUrl && /* @__PURE__ */ jsx(
1781
+ Anchor,
1782
+ {
1783
+ href: distribution.platformUrl,
1784
+ target: "_blank",
1785
+ rel: "noopener noreferrer",
1786
+ style: { display: "inline-flex" },
1787
+ onClick: (event) => event.stopPropagation(),
1788
+ children: /* @__PURE__ */ jsx(IconExternalLink, { size: 16 })
1789
+ }
1790
+ ) })
1791
+ ]
1792
+ },
1793
+ distribution.id
1794
+ );
1795
+ }) })
1796
+ ] }),
1797
+ filteredDistributions.length > PAGE_SIZE3 && /* @__PURE__ */ jsx(Group, { justify: "center", children: /* @__PURE__ */ jsx(
1798
+ Pagination,
1799
+ {
1800
+ value: pagination.page,
1801
+ onChange: pagination.setPage,
1802
+ total: pagination.totalPages(filteredDistributions.length),
1803
+ size: "sm"
1804
+ }
1805
+ ) })
1806
+ ] }) })
1807
+ ] }) }) });
1808
+ }
1809
+ function formatDateTime3(value) {
1810
+ return value ? new Date(value).toLocaleString() : "\u2014";
1811
+ }
1812
+ function toChecklist(value) {
1813
+ if (!Array.isArray(value)) return [];
1814
+ return value.filter(
1815
+ (item) => !!item && typeof item === "object" && typeof item.key === "string"
1816
+ );
1817
+ }
1818
+ function getChecklistProgress(checklist) {
1819
+ if (checklist.length === 0) return { done: 0, total: 0, pct: 0 };
1820
+ const done = checklist.filter((item) => item.done).length;
1821
+ return { done, total: checklist.length, pct: Math.round(done / checklist.length * 100) };
1822
+ }
1823
+ function MetricsDisplay({ metrics }) {
1824
+ if (!metrics || typeof metrics !== "object" || Object.keys(metrics).length === 0) {
1825
+ return /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "No metrics recorded yet." });
1826
+ }
1827
+ const entries = Object.entries(metrics);
1828
+ const isFlat = entries.every(([, value]) => value === null || typeof value !== "object");
1829
+ if (!isFlat) {
1830
+ return /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", ff: "monospace", style: { whiteSpace: "pre-wrap", overflowWrap: "anywhere" }, children: JSON.stringify(metrics, null, 2) });
1831
+ }
1832
+ return /* @__PURE__ */ jsx(SimpleGrid, { cols: { base: 1, sm: 2 }, spacing: "sm", verticalSpacing: "sm", children: entries.map(([key, value]) => /* @__PURE__ */ jsxs(Group, { children: [
1833
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", w: 140, children: key }),
1834
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: String(value) })
1835
+ ] }, key)) });
1836
+ }
1837
+ function ContentDistributionDetailPage({
1838
+ distributionId,
1839
+ headerActions
1840
+ }) {
1841
+ const navigate = useNavigate();
1842
+ const contentConfig = useContentConfig();
1843
+ const distributionQuery = useContentDistribution(distributionId);
1844
+ const dist = distributionQuery.data;
1845
+ const itemQuery = useContentItem(dist?.contentItemId);
1846
+ const updateDistribution = useUpdateContentDistribution();
1847
+ const [platformContent, setPlatformContent] = useState({});
1848
+ const [platformUrl, setPlatformUrl] = useState("");
1849
+ const [status, setStatus] = useState("");
1850
+ const [mediaOpened, { open: openMedia, close: closeMedia }] = useDisclosure(false);
1851
+ const [previewUrl, setPreviewUrl] = useState(null);
1852
+ useEffect(() => {
1853
+ setPlatformUrl(dist?.platformUrl ?? "");
1854
+ setStatus(dist?.status ?? "");
1855
+ setPlatformContent(dist?.platformContent ?? {});
1856
+ }, [dist?.id]);
1857
+ const backButton = /* @__PURE__ */ jsx(
1858
+ Button,
1859
+ {
1860
+ variant: "light",
1861
+ size: "xs",
1862
+ leftSection: /* @__PURE__ */ jsx(IconArrowLeft, { size: 16 }),
1863
+ onClick: () => navigate({ to: "/content/distributions" }),
1864
+ children: "Distributions"
1865
+ }
1866
+ );
1867
+ if (distributionQuery.isLoading) {
1868
+ return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
1869
+ /* @__PURE__ */ jsx(PageTitleCaption, { title: "Distribution", caption: "Loading distribution", rightSection: backButton }),
1870
+ /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) })
1871
+ ] }) }) });
1872
+ }
1873
+ if (distributionQuery.isError || !dist) {
1874
+ return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
1875
+ /* @__PURE__ */ jsx(PageTitleCaption, { title: "Distribution", caption: "Distribution not found", rightSection: backButton }),
1876
+ /* @__PURE__ */ jsx(Card, { withBorder: true, children: distributionQuery.isError ? /* @__PURE__ */ jsx(CenteredErrorState, { error: distributionQuery.error, title: "Failed to load distribution" }) : /* @__PURE__ */ jsx(EmptyState, { icon: IconFileText, title: "Distribution not found" }) })
1877
+ ] }) }) });
1878
+ }
1879
+ const contentItem = itemQuery.data;
1880
+ const contentTitle = contentItem?.title ?? dist.contentItemId;
1881
+ const checklist = toChecklist(dist.checklist);
1882
+ const checklistProgress = getChecklistProgress(checklist);
1883
+ const mediaUrls = dist.mediaUrls ?? [];
1884
+ const platformEntry = contentConfig.stageCatalog[dist.platform];
1885
+ const platformColors = getContentStageColor(platformEntry);
1886
+ const pillarEntry = contentItem?.pillar ? contentConfig.stageCatalog[contentItem.pillar] : void 0;
1887
+ const isPlatformContentDirty = JSON.stringify(platformContent) !== JSON.stringify(dist.platformContent ?? {});
1888
+ const patch = (updates) => {
1889
+ updateDistribution.mutate({ distributionId, updates });
1890
+ };
1891
+ const updatePlatformField = (field, value) => {
1892
+ setPlatformContent((prev) => ({ ...prev, [field]: value }));
1893
+ };
1894
+ const toggleChecklistItem = (key) => {
1895
+ const nextChecklist = checklist.map((item) => item.key === key ? { ...item, done: !item.done } : item);
1896
+ patch({ checklist: nextChecklist });
1897
+ };
1898
+ const deleteMedia = (url) => {
1899
+ patch({ mediaUrls: mediaUrls.filter((existing) => existing !== url) });
1900
+ };
1901
+ return /* @__PURE__ */ jsxs(SubshellContentContainer, { children: [
1902
+ /* @__PURE__ */ jsxs(PageContainer, { children: [
1903
+ /* @__PURE__ */ jsxs(Stack, { children: [
1904
+ /* @__PURE__ */ jsx(
1905
+ PageTitleCaption,
1906
+ {
1907
+ title: `${platformEntry?.label ?? dist.platform} \xB7 ${dist.format}`,
1908
+ caption: `Distribution for "${contentTitle}"`,
1909
+ rightSection: /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
1910
+ contentItem ? /* @__PURE__ */ jsx(
1911
+ Button,
1912
+ {
1913
+ variant: "light",
1914
+ size: "xs",
1915
+ onClick: () => navigate({ to: "/content/items/$itemId", params: { itemId: contentItem.id } }),
1916
+ children: contentTitle
1917
+ }
1918
+ ) : null,
1919
+ backButton
1920
+ ] })
1921
+ }
1922
+ ),
1923
+ headerActions ? /* @__PURE__ */ jsx(Group, { gap: "xs", children: headerActions }) : null,
1924
+ /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
1925
+ /* @__PURE__ */ jsx(
1926
+ Badge,
1927
+ {
1928
+ size: "sm",
1929
+ variant: "light",
1930
+ styles: { root: { color: platformColors.accent, backgroundColor: platformColors.background } },
1931
+ children: platformEntry?.label ?? dist.platform
1932
+ }
1933
+ ),
1934
+ /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "light", children: dist.status }),
1935
+ pillarEntry ? /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "outline", children: pillarEntry.label }) : null
1936
+ ] })
1937
+ ] }),
1938
+ /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
1939
+ /* @__PURE__ */ jsx(Title, { order: 3, children: "Details" }),
1940
+ /* @__PURE__ */ jsx(Divider, {}),
1941
+ /* @__PURE__ */ jsxs(SimpleGrid, { cols: { base: 1, sm: 2 }, spacing: "sm", verticalSpacing: "sm", children: [
1942
+ /* @__PURE__ */ jsxs(Group, { children: [
1943
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", w: 140, children: "Platform" }),
1944
+ /* @__PURE__ */ jsx(
1945
+ Badge,
1946
+ {
1947
+ size: "sm",
1948
+ variant: "light",
1949
+ styles: { root: { color: platformColors.accent, backgroundColor: platformColors.background } },
1950
+ children: platformEntry?.label ?? dist.platform
1951
+ }
1952
+ )
1953
+ ] }),
1954
+ /* @__PURE__ */ jsxs(Group, { children: [
1955
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", w: 140, children: "Format" }),
1956
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: dist.format })
1957
+ ] }),
1958
+ /* @__PURE__ */ jsxs(Group, { wrap: "nowrap", children: [
1959
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", w: 140, children: "Status" }),
1960
+ /* @__PURE__ */ jsx(
1961
+ TextInput,
1962
+ {
1963
+ size: "xs",
1964
+ value: status,
1965
+ onChange: (event) => setStatus(event.currentTarget.value),
1966
+ style: { flex: 1 },
1967
+ onBlur: () => {
1968
+ const value = status.trim();
1969
+ if (value && value !== dist.status) {
1970
+ const updates = { status: value };
1971
+ if (value === "published" && !dist.publishedAt) {
1972
+ updates.publishedAt = (/* @__PURE__ */ new Date()).toISOString();
1973
+ }
1974
+ patch(updates);
1975
+ }
1976
+ },
1977
+ onKeyDown: (event) => {
1978
+ if (event.key === "Enter") event.currentTarget.blur();
1979
+ }
1980
+ }
1981
+ )
1982
+ ] }),
1983
+ /* @__PURE__ */ jsxs(Group, { children: [
1984
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", w: 140, children: "Publish Method" }),
1985
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: dist.publishMethod ?? "\u2014" })
1986
+ ] }),
1987
+ /* @__PURE__ */ jsxs(Group, { children: [
1988
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", w: 140, children: "Platform Post ID" }),
1989
+ /* @__PURE__ */ jsx(Text, { size: "sm", ff: "monospace", children: dist.platformPostId ?? "\u2014" })
1990
+ ] }),
1991
+ /* @__PURE__ */ jsxs(Group, { children: [
1992
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", w: 140, children: "Published" }),
1993
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: formatDateTime3(dist.publishedAt) })
1994
+ ] }),
1995
+ /* @__PURE__ */ jsxs(Group, { children: [
1996
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", w: 140, children: "Created" }),
1997
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: formatDateTime3(dist.createdAt) })
1998
+ ] }),
1999
+ /* @__PURE__ */ jsxs(Group, { children: [
2000
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", w: 140, children: "Updated" }),
2001
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: formatDateTime3(dist.updatedAt) })
2002
+ ] }),
2003
+ /* @__PURE__ */ jsxs(Group, { wrap: "nowrap", children: [
2004
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", w: 140, children: "URL" }),
2005
+ /* @__PURE__ */ jsx(
2006
+ TextInput,
2007
+ {
2008
+ size: "xs",
2009
+ placeholder: "https://...",
2010
+ value: platformUrl,
2011
+ onChange: (event) => setPlatformUrl(event.currentTarget.value),
2012
+ style: { flex: 1 },
2013
+ onBlur: () => {
2014
+ const value = platformUrl.trim();
2015
+ if (value !== (dist.platformUrl ?? "")) {
2016
+ patch({ platformUrl: value || null });
2017
+ }
2018
+ },
2019
+ onKeyDown: (event) => {
2020
+ if (event.key === "Enter") event.currentTarget.blur();
2021
+ },
2022
+ rightSection: dist.platformUrl ? /* @__PURE__ */ jsx(Anchor, { href: dist.platformUrl, target: "_blank", rel: "noopener noreferrer", children: /* @__PURE__ */ jsx(IconExternalLink, { size: 14 }) }) : void 0
2023
+ }
2024
+ )
2025
+ ] })
2026
+ ] })
2027
+ ] }) }),
2028
+ /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
2029
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", children: [
2030
+ /* @__PURE__ */ jsx(Title, { order: 3, children: "Metrics" }),
2031
+ dist.metricsUpdatedAt ? /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
2032
+ "Updated ",
2033
+ formatDateTime3(dist.metricsUpdatedAt)
2034
+ ] }) : null
2035
+ ] }),
2036
+ /* @__PURE__ */ jsx(Divider, {}),
2037
+ /* @__PURE__ */ jsx(MetricsDisplay, { metrics: dist.metrics })
2038
+ ] }) }),
2039
+ checklist.length > 0 ? /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
2040
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", children: [
2041
+ /* @__PURE__ */ jsx(Title, { order: 3, children: "Checklist" }),
2042
+ /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
2043
+ /* @__PURE__ */ jsx(Progress, { value: checklistProgress.pct, size: "sm", w: 80 }),
2044
+ /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
2045
+ checklistProgress.done,
2046
+ "/",
2047
+ checklistProgress.total,
2048
+ " (",
2049
+ checklistProgress.pct,
2050
+ "%)"
2051
+ ] })
2052
+ ] })
2053
+ ] }),
2054
+ /* @__PURE__ */ jsx(Divider, {}),
2055
+ /* @__PURE__ */ jsx(SimpleGrid, { cols: { base: 1, sm: 2 }, spacing: "sm", verticalSpacing: "sm", children: checklist.map((item) => /* @__PURE__ */ jsx(
2056
+ Checkbox,
2057
+ {
2058
+ label: item.label,
2059
+ checked: item.done,
2060
+ onChange: () => toggleChecklistItem(item.key),
2061
+ disabled: updateDistribution.isPending,
2062
+ size: "sm"
2063
+ },
2064
+ item.key
2065
+ )) })
2066
+ ] }) }) : null,
2067
+ /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
2068
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", children: [
2069
+ /* @__PURE__ */ jsx(Title, { order: 3, children: "Platform Content" }),
2070
+ /* @__PURE__ */ jsx(
2071
+ Button,
2072
+ {
2073
+ size: "xs",
2074
+ disabled: !isPlatformContentDirty,
2075
+ loading: updateDistribution.isPending,
2076
+ onClick: () => patch({ platformContent }),
2077
+ children: "Save"
2078
+ }
2079
+ )
2080
+ ] }),
2081
+ /* @__PURE__ */ jsx(Divider, {}),
2082
+ /* @__PURE__ */ jsx(
2083
+ TextInput,
2084
+ {
2085
+ label: "Title",
2086
+ value: platformContent.title ?? "",
2087
+ onChange: (event) => updatePlatformField("title", event.currentTarget.value)
2088
+ }
2089
+ ),
2090
+ /* @__PURE__ */ jsx(
2091
+ Textarea,
2092
+ {
2093
+ label: "Description",
2094
+ autosize: true,
2095
+ minRows: 2,
2096
+ value: platformContent.description ?? "",
2097
+ onChange: (event) => updatePlatformField("description", event.currentTarget.value)
2098
+ }
2099
+ ),
2100
+ /* @__PURE__ */ jsx(
2101
+ TagsInput,
2102
+ {
2103
+ label: "Tags",
2104
+ value: platformContent.tags ?? [],
2105
+ onChange: (value) => updatePlatformField("tags", value)
2106
+ }
2107
+ ),
2108
+ dist.platform === "youtube" && /* @__PURE__ */ jsxs(Fragment, { children: [
2109
+ /* @__PURE__ */ jsx(
2110
+ TagsInput,
2111
+ {
2112
+ label: "Timestamps",
2113
+ placeholder: "0:00 Intro",
2114
+ value: platformContent.timestamps ?? [],
2115
+ onChange: (value) => updatePlatformField("timestamps", value)
2116
+ }
2117
+ ),
2118
+ /* @__PURE__ */ jsx(
2119
+ TextInput,
2120
+ {
2121
+ label: "End Screen Video ID",
2122
+ value: platformContent.endScreenVideoId ?? "",
2123
+ onChange: (event) => updatePlatformField("endScreenVideoId", event.currentTarget.value)
2124
+ }
2125
+ ),
2126
+ /* @__PURE__ */ jsx(
2127
+ Textarea,
2128
+ {
2129
+ label: "Pinned Comment",
2130
+ autosize: true,
2131
+ minRows: 2,
2132
+ value: platformContent.pinnedComment ?? "",
2133
+ onChange: (event) => updatePlatformField("pinnedComment", event.currentTarget.value)
2134
+ }
2135
+ )
2136
+ ] }),
2137
+ dist.platform === "linkedin" && /* @__PURE__ */ jsxs(Fragment, { children: [
2138
+ /* @__PURE__ */ jsx(
2139
+ Textarea,
2140
+ {
2141
+ label: "Hook",
2142
+ autosize: true,
2143
+ minRows: 2,
2144
+ value: platformContent.hook ?? "",
2145
+ onChange: (event) => updatePlatformField("hook", event.currentTarget.value)
2146
+ }
2147
+ ),
2148
+ /* @__PURE__ */ jsx(
2149
+ TagsInput,
2150
+ {
2151
+ label: "Hashtags",
2152
+ value: platformContent.hashtags ?? [],
2153
+ onChange: (value) => updatePlatformField("hashtags", value)
2154
+ }
2155
+ ),
2156
+ /* @__PURE__ */ jsx(
2157
+ Textarea,
2158
+ {
2159
+ label: "First Comment",
2160
+ autosize: true,
2161
+ minRows: 2,
2162
+ value: platformContent.firstComment ?? "",
2163
+ onChange: (event) => updatePlatformField("firstComment", event.currentTarget.value)
2164
+ }
2165
+ )
2166
+ ] }),
2167
+ dist.platform === "instagram" && /* @__PURE__ */ jsxs(Fragment, { children: [
2168
+ /* @__PURE__ */ jsx(
2169
+ Textarea,
2170
+ {
2171
+ label: "Alt Text",
2172
+ autosize: true,
2173
+ minRows: 2,
2174
+ value: platformContent.altText ?? "",
2175
+ onChange: (event) => updatePlatformField("altText", event.currentTarget.value)
2176
+ }
2177
+ ),
2178
+ /* @__PURE__ */ jsx(
2179
+ TagsInput,
2180
+ {
2181
+ label: "Hashtags",
2182
+ value: platformContent.hashtags ?? [],
2183
+ onChange: (value) => updatePlatformField("hashtags", value)
2184
+ }
2185
+ ),
2186
+ /* @__PURE__ */ jsx(
2187
+ TextInput,
2188
+ {
2189
+ label: "Story Link",
2190
+ value: platformContent.storyLink ?? "",
2191
+ onChange: (event) => updatePlatformField("storyLink", event.currentTarget.value)
2192
+ }
2193
+ )
2194
+ ] }),
2195
+ dist.platform === "x" && /* @__PURE__ */ jsxs(Fragment, { children: [
2196
+ /* @__PURE__ */ jsx(
2197
+ Textarea,
2198
+ {
2199
+ label: "Hook Tweet",
2200
+ autosize: true,
2201
+ minRows: 2,
2202
+ value: platformContent.hookTweet ?? "",
2203
+ onChange: (event) => updatePlatformField("hookTweet", event.currentTarget.value)
2204
+ }
2205
+ ),
2206
+ /* @__PURE__ */ jsx(
2207
+ TagsInput,
2208
+ {
2209
+ label: "Thread Tweets",
2210
+ value: platformContent.threadTweets ?? [],
2211
+ onChange: (value) => updatePlatformField("threadTweets", value)
2212
+ }
2213
+ ),
2214
+ /* @__PURE__ */ jsx(
2215
+ TextInput,
2216
+ {
2217
+ label: "Reply Thread",
2218
+ value: platformContent.replyThread ?? "",
2219
+ onChange: (event) => updatePlatformField("replyThread", event.currentTarget.value)
2220
+ }
2221
+ )
2222
+ ] })
2223
+ ] }) }),
2224
+ dist.adaptedBody ? /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
2225
+ /* @__PURE__ */ jsx(Title, { order: 3, children: "Adapted Content" }),
2226
+ /* @__PURE__ */ jsx(Divider, {}),
2227
+ /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsx(Text, { size: "sm", style: { whiteSpace: "pre-wrap" }, children: dist.adaptedBody }) })
2228
+ ] }) }) : null,
2229
+ contentItem ? /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
2230
+ /* @__PURE__ */ jsx(Title, { order: 3, children: "Source Content" }),
2231
+ /* @__PURE__ */ jsx(Divider, {}),
2232
+ /* @__PURE__ */ jsxs(Group, { children: [
2233
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", w: 140, children: "Title" }),
2234
+ /* @__PURE__ */ jsx(
2235
+ Anchor,
2236
+ {
2237
+ onClick: () => navigate({ to: "/content/items/$itemId", params: { itemId: contentItem.id } }),
2238
+ size: "sm",
2239
+ style: { cursor: "pointer" },
2240
+ children: contentItem.title
2241
+ }
2242
+ )
2243
+ ] }),
2244
+ /* @__PURE__ */ jsxs(Group, { children: [
2245
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", w: 140, children: "Pillar" }),
2246
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: pillarEntry?.label ?? contentItem.pillar ?? "\u2014" })
2247
+ ] }),
2248
+ /* @__PURE__ */ jsxs(Group, { children: [
2249
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", w: 140, children: "Status" }),
2250
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: contentConfig.stageCatalog[contentItem.status]?.label ?? contentItem.status })
2251
+ ] })
2252
+ ] }) }) : null,
2253
+ /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
2254
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", children: [
2255
+ /* @__PURE__ */ jsx(Title, { order: 3, children: "Media" }),
2256
+ mediaUrls.length > 0 ? /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
2257
+ mediaUrls.length,
2258
+ " image",
2259
+ mediaUrls.length !== 1 ? "s" : ""
2260
+ ] }) : null
2261
+ ] }),
2262
+ /* @__PURE__ */ jsx(Divider, {}),
2263
+ mediaUrls.length === 0 ? /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "No media yet." }) : /* @__PURE__ */ jsx(SimpleGrid, { cols: { base: 1, xs: 2, sm: 3 }, spacing: "sm", children: mediaUrls.map((url, index) => /* @__PURE__ */ jsxs(Card, { withBorder: true, radius: "md", style: { overflow: "hidden" }, children: [
2264
+ /* @__PURE__ */ jsx(
2265
+ Image,
2266
+ {
2267
+ src: url,
2268
+ alt: `Media variant ${index + 1}`,
2269
+ w: "100%",
2270
+ radius: "sm",
2271
+ style: { cursor: "pointer" },
2272
+ onClick: () => {
2273
+ setPreviewUrl(url);
2274
+ openMedia();
2275
+ }
2276
+ }
2277
+ ),
2278
+ /* @__PURE__ */ jsxs(Group, { gap: 4, p: 6, justify: "flex-end", children: [
2279
+ /* @__PURE__ */ jsx(Tooltip, { label: "View full size", children: /* @__PURE__ */ jsx(
2280
+ ActionIcon,
2281
+ {
2282
+ variant: "subtle",
2283
+ size: "sm",
2284
+ onClick: () => {
2285
+ setPreviewUrl(url);
2286
+ openMedia();
2287
+ },
2288
+ children: /* @__PURE__ */ jsx(IconMaximize, { size: 16 })
2289
+ }
2290
+ ) }),
2291
+ /* @__PURE__ */ jsx(Tooltip, { label: "Copy URL", children: /* @__PURE__ */ jsx(ActionIcon, { variant: "subtle", size: "sm", onClick: () => navigator.clipboard.writeText(url), children: /* @__PURE__ */ jsx(IconCopy, { size: 16 }) }) }),
2292
+ /* @__PURE__ */ jsx(Tooltip, { label: "Download", children: /* @__PURE__ */ jsx(ActionIcon, { variant: "subtle", size: "sm", component: "a", href: url, download: true, target: "_blank", children: /* @__PURE__ */ jsx(IconDownload, { size: 16 }) }) }),
2293
+ /* @__PURE__ */ jsx(Tooltip, { label: "Delete", children: /* @__PURE__ */ jsx(
2294
+ ActionIcon,
2295
+ {
2296
+ variant: "subtle",
2297
+ color: "var(--color-error)",
2298
+ size: "sm",
2299
+ loading: updateDistribution.isPending,
2300
+ onClick: () => deleteMedia(url),
2301
+ children: /* @__PURE__ */ jsx(IconTrash, { size: 16 })
2302
+ }
2303
+ ) })
2304
+ ] })
2305
+ ] }, url)) })
2306
+ ] }) })
2307
+ ] }),
2308
+ /* @__PURE__ */ jsx(CustomModal, { opened: mediaOpened, onClose: closeMedia, size: "xl", children: previewUrl ? /* @__PURE__ */ jsx(Image, { src: previewUrl, alt: "Media preview", fit: "contain" }) : null })
2309
+ ] });
2310
+ }
2311
+
2312
+ export { AlternatesPanel, ContentDistributionDetailPage, ContentDistributionsPage, ContentItemReviewPage, ContentItemsPage, ContentOverviewPage, ContentPipelineWorkspacePage, ContentPipelinesPage, ContentReviewCard, PayloadBody, ProcessingStateStrip, ReviewActionBar, firstLine, formatElapsed, lastLine, swapFirstLine, swapLastLine };