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