@elevasis/ui 2.69.0 → 2.71.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 (78) hide show
  1. package/dist/app/index.d.ts +26 -0
  2. package/dist/app/index.js +7 -7
  3. package/dist/auth/index.css +149 -149
  4. package/dist/auth/index.js +3 -3
  5. package/dist/charts/index.css +208 -208
  6. package/dist/charts/index.js +3 -3
  7. package/dist/{chunk-6PZFOHJK.js → chunk-3BLYJT5L.js} +2 -2
  8. package/dist/{chunk-IT7GTJCB.js → chunk-AIOV5NM7.js} +1 -1
  9. package/dist/{chunk-MRVGQLWN.js → chunk-BM2WKRAQ.js} +1 -1
  10. package/dist/{chunk-XAZR3T6Y.js → chunk-CPBT6Z2G.js} +3 -3
  11. package/dist/{chunk-XQ3ILKYN.js → chunk-DCMFNSJ6.js} +1 -1
  12. package/dist/{chunk-NU3NBXHP.js → chunk-DH2MO5DX.js} +1 -1
  13. package/dist/{chunk-ZD6LM53O.js → chunk-H3SDGC5F.js} +2 -2
  14. package/dist/{chunk-YGCMMOY4.js → chunk-JTSNU6KA.js} +695 -73
  15. package/dist/{chunk-7WJEPTE7.js → chunk-LL5H7J4N.js} +1 -1
  16. package/dist/{chunk-FLECOYUS.js → chunk-R6NMRXNO.js} +1 -1
  17. package/dist/{chunk-MFLM7BVC.js → chunk-TB2PM2OU.js} +1 -1
  18. package/dist/{chunk-IRV6KJPV.js → chunk-TCQZ4AJI.js} +1 -1
  19. package/dist/{chunk-YNRYIXUT.js → chunk-TVFSVTRG.js} +6 -6
  20. package/dist/{chunk-WXHAHKKI.js → chunk-UZ7MSORW.js} +16291 -16029
  21. package/dist/{chunk-RW64UJWT.js → chunk-VHPHWTX7.js} +2 -2
  22. package/dist/{chunk-YCRINA2D.js → chunk-VRRIHA3S.js} +1 -1
  23. package/dist/{chunk-4JHLS234.js → chunk-ZFISGKYW.js} +1 -1
  24. package/dist/components/index.css +208 -208
  25. package/dist/components/index.d.ts +59 -3
  26. package/dist/components/index.js +24 -25
  27. package/dist/components/navigation/index.css +149 -149
  28. package/dist/components/navigation/index.js +4 -4
  29. package/dist/features/auth/index.css +149 -149
  30. package/dist/features/auth/index.d.ts +26 -0
  31. package/dist/features/auth/index.js +5 -5
  32. package/dist/features/clients/index.css +208 -208
  33. package/dist/features/clients/index.js +4 -4
  34. package/dist/features/content/index.d.ts +258 -51
  35. package/dist/features/content/index.js +595 -421
  36. package/dist/features/crm/index.d.ts +26 -0
  37. package/dist/features/crm/index.js +5 -5
  38. package/dist/features/dashboard/index.js +6 -7
  39. package/dist/features/delivery/index.css +149 -149
  40. package/dist/features/delivery/index.d.ts +26 -0
  41. package/dist/features/delivery/index.js +4 -4
  42. package/dist/features/knowledge/index.css +149 -149
  43. package/dist/features/knowledge/index.js +3 -3
  44. package/dist/features/lead-gen/index.js +5 -5
  45. package/dist/features/monitoring/index.css +149 -149
  46. package/dist/features/monitoring/index.d.ts +33 -3
  47. package/dist/features/monitoring/index.js +6 -6
  48. package/dist/features/monitoring/requests/index.js +4 -4
  49. package/dist/features/operations/index.js +9 -10
  50. package/dist/features/settings/index.d.ts +26 -0
  51. package/dist/features/settings/index.js +6 -6
  52. package/dist/hooks/access/index.css +149 -149
  53. package/dist/hooks/access/index.js +3 -3
  54. package/dist/hooks/delivery/index.d.ts +26 -0
  55. package/dist/hooks/index.css +149 -149
  56. package/dist/hooks/index.d.ts +296 -65
  57. package/dist/hooks/index.js +3 -3
  58. package/dist/hooks/published.css +149 -149
  59. package/dist/hooks/published.d.ts +296 -65
  60. package/dist/hooks/published.js +3 -3
  61. package/dist/initialization/index.d.ts +26 -0
  62. package/dist/knowledge/index.css +149 -149
  63. package/dist/knowledge/index.js +7 -7
  64. package/dist/layout/index.css +149 -149
  65. package/dist/layout/index.js +6 -6
  66. package/dist/profile/index.d.ts +26 -0
  67. package/dist/provider/index.d.ts +26 -0
  68. package/dist/provider/index.js +3 -3
  69. package/dist/provider/published.css +149 -149
  70. package/dist/provider/published.d.ts +26 -0
  71. package/dist/provider/published.js +3 -3
  72. package/dist/supabase/index.d.ts +50 -0
  73. package/dist/theme/index.js +3 -3
  74. package/dist/theme/presets/index.js +1 -1
  75. package/dist/types/index.d.ts +59 -3
  76. package/package.json +5 -5
  77. package/dist/chunk-ZPTEGGKU.js +0 -631
  78. /package/dist/{chunk-XQBURFKM.js → chunk-RBGVNPA6.js} +0 -0
@@ -1,13 +1,10 @@
1
- import { RunResourceButton } from '../../chunk-YGCMMOY4.js';
2
- import '../../chunk-3MEXPLWT.js';
3
- import '../../chunk-HENXLGVD.js';
4
1
  import { PageContainer } from '../../chunk-BZZCNLT6.js';
5
2
  import '../../chunk-TUMSNGTX.js';
6
3
  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';
4
+ import { CustomModal, useContentItems, useContentDistributions, useContentConfig, SubshellContentContainer, PageTitleCaption, CenteredErrorState, EmptyState, StatCard, getContentItemIdentity, usePaginationState, useContentItem, useContentItemAttempts, useUpdateContentItem, useReviewContentItem, getOpenContentReviewGates, useContentDistribution, useUpdateContentDistribution, useContentPipelineSummary, deriveContentBoard, useExecutions } from '../../chunk-UZ7MSORW.js';
5
+ export { CONTENT_ITEMS, ContentSidebar, ContentSidebarMiddle, DEFAULT_CONTENT_VOCABULARY, MyReviewQueuePanel, contentManifest, deriveContentBoard, getContentItemIdentity, resolveContentStepResource, resolveContentVocabulary, useContentConfig } from '../../chunk-UZ7MSORW.js';
6
+ import '../../chunk-R6NMRXNO.js';
7
+ import '../../chunk-RBGVNPA6.js';
11
8
  import '../../chunk-MKH2KOAO.js';
12
9
  import '../../chunk-VDX6DL7Z.js';
13
10
  import '../../chunk-L7BZZ4SI.js';
@@ -23,7 +20,7 @@ import '../../chunk-UD657ICS.js';
23
20
  import '../../chunk-DD3CCMCZ.js';
24
21
  import '../../chunk-RNP5R5I3.js';
25
22
  import '../../chunk-RT4KRGZT.js';
26
- import { getSystem } from '../../chunk-NM4A63KJ.js';
23
+ import { formatTimeAgo, formatRelativeTime } from '../../chunk-NM4A63KJ.js';
27
24
  import '../../chunk-Y6I3IC45.js';
28
25
  import '../../chunk-6SSQGWK7.js';
29
26
  import '../../chunk-BI5VDO2K.js';
@@ -32,10 +29,10 @@ import '../../chunk-TVTSASST.js';
32
29
  import '../../chunk-BRJ3QZ4E.js';
33
30
  import '../../chunk-Q7DJKLEN.js';
34
31
  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';
32
+ import { Stack, Group, Text, Badge, Button, Textarea, Box, Loader, Accordion, Tooltip, Card, Alert, List, Paper, Center, SimpleGrid, Table, TextInput, Select, Pagination, Title, Tabs, Anchor, Divider, Progress, Checkbox, TagsInput, Image, ActionIcon, UnstyledButton, ScrollArea } from '@mantine/core';
33
+ import { IconAlertTriangle, IconShieldCheck, IconShieldX, IconArticle, IconArrowRight, IconClock, IconSend, IconSearch, IconFileText, IconListDetails, IconAlertCircle, IconLayoutGrid, IconShare, IconPlayerPlay, IconExternalLink, IconMaximize, IconCopy, IconDownload, IconTrash, IconArrowLeft, IconArrowsExchange, IconCheck, IconX, IconChecklist, IconRoute, IconInfoCircle, IconClockHour4 } from '@tabler/icons-react';
37
34
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
38
- import { useState, useMemo, useEffect, useCallback } from 'react';
35
+ import { useState, useMemo, Fragment as Fragment$1, useCallback, useEffect } from 'react';
39
36
  import { useNavigate, useSearch } from '@tanstack/react-router';
40
37
  import { useDisclosure } from '@mantine/hooks';
41
38
 
@@ -75,7 +72,7 @@ function PayloadBody({
75
72
  value: body,
76
73
  onChange: (event) => onBodyChange(event.currentTarget.value),
77
74
  autosize: true,
78
- minRows: 6,
75
+ minRows: 3,
79
76
  disabled,
80
77
  styles: {
81
78
  input: {
@@ -303,6 +300,17 @@ function AlternatesPanel({
303
300
  ] }) })
304
301
  ] }) });
305
302
  }
303
+ var IRREVERSIBLE_WARNING = "This action is irreversible and visible outside the platform.";
304
+ function isIrreversible(action) {
305
+ return action?.consequence === "irreversible";
306
+ }
307
+ function resolveActionColor(action) {
308
+ if (!action) return void 0;
309
+ if (action.color) return action.color;
310
+ if (action.tone === "danger") return "var(--color-error)";
311
+ if (isIrreversible(action)) return "var(--color-warning)";
312
+ return void 0;
313
+ }
306
314
  function ReviewActionBar({ actions, onAction, pending = false }) {
307
315
  const [confirmAction, setConfirmAction] = useState(null);
308
316
  const [feedbackAction, setFeedbackAction] = useState(null);
@@ -311,7 +319,7 @@ function ReviewActionBar({ actions, onAction, pending = false }) {
311
319
  if (action.requiresFeedback) {
312
320
  setFeedback("");
313
321
  setFeedbackAction(action);
314
- } else if (action.requiresConfirmation) {
322
+ } else if (action.requiresConfirmation || isIrreversible(action)) {
315
323
  setConfirmAction(action);
316
324
  } else {
317
325
  onAction(action.id);
@@ -326,32 +334,36 @@ function ReviewActionBar({ actions, onAction, pending = false }) {
326
334
  return /* @__PURE__ */ jsxs(Fragment, { children: [
327
335
  /* @__PURE__ */ jsx(Group, { gap: "xs", children: actions.map((action) => {
328
336
  const Icon = action.icon;
329
- return /* @__PURE__ */ jsx(
337
+ const irreversible = isIrreversible(action);
338
+ const button = /* @__PURE__ */ jsx(
330
339
  Button,
331
340
  {
332
- variant: "light",
333
- color: action.color || (action.tone === "danger" ? "var(--color-error)" : void 0),
341
+ variant: irreversible ? "filled" : "light",
342
+ fw: irreversible ? 700 : void 0,
343
+ color: resolveActionColor(action),
334
344
  leftSection: Icon ? /* @__PURE__ */ jsx(Icon, { size: 16 }) : void 0,
335
345
  loading: pending,
336
- disabled: pending,
346
+ disabled: pending || action.disabled,
337
347
  onClick: () => openAction(action),
338
348
  size: "xs",
339
349
  radius: "xs",
340
350
  "data-testid": `content-review-action-${action.id}`,
341
351
  children: action.label
342
- },
343
- action.id
352
+ }
344
353
  );
354
+ return action.disabled && action.disabledReason ? /* @__PURE__ */ jsx(Tooltip, { label: action.disabledReason, withArrow: true, position: "top", children: /* @__PURE__ */ jsx("span", { children: button }) }, action.id) : /* @__PURE__ */ jsx("span", { children: button }, action.id);
345
355
  }) }),
346
356
  /* @__PURE__ */ jsx(CustomModal, { opened: !!confirmAction, onClose: () => setConfirmAction(null), size: "sm", children: /* @__PURE__ */ jsxs(Stack, { children: [
347
357
  /* @__PURE__ */ jsx(Text, { fw: 600, children: confirmAction?.label }),
358
+ isIrreversible(confirmAction) ? /* @__PURE__ */ jsx(Text, { size: "sm", fw: 600, c: "var(--color-warning)", children: IRREVERSIBLE_WARNING }) : null,
348
359
  /* @__PURE__ */ jsx(Text, { size: "sm", children: confirmAction?.confirmationMessage || `Are you sure you want to ${confirmAction?.label.toLowerCase()}?` }),
349
360
  /* @__PURE__ */ jsxs(Group, { justify: "flex-end", children: [
350
361
  /* @__PURE__ */ jsx(Button, { variant: "default", onClick: () => setConfirmAction(null), children: "Cancel" }),
351
362
  /* @__PURE__ */ jsx(
352
363
  Button,
353
364
  {
354
- color: confirmAction?.tone === "danger" ? "var(--color-error)" : void 0,
365
+ variant: isIrreversible(confirmAction) ? "filled" : void 0,
366
+ color: resolveActionColor(confirmAction),
355
367
  onClick: () => {
356
368
  if (confirmAction) onAction(confirmAction.id);
357
369
  setConfirmAction(null);
@@ -363,6 +375,7 @@ function ReviewActionBar({ actions, onAction, pending = false }) {
363
375
  ] }) }),
364
376
  /* @__PURE__ */ jsx(CustomModal, { opened: !!feedbackAction, onClose: () => setFeedbackAction(null), size: "sm", children: /* @__PURE__ */ jsxs(Stack, { children: [
365
377
  /* @__PURE__ */ jsx(Text, { fw: 600, children: feedbackAction?.label }),
378
+ isIrreversible(feedbackAction) ? /* @__PURE__ */ jsx(Text, { size: "sm", fw: 600, c: "var(--color-warning)", children: IRREVERSIBLE_WARNING }) : null,
366
379
  feedbackAction?.confirmationMessage ? /* @__PURE__ */ jsx(Text, { size: "sm", children: feedbackAction.confirmationMessage }) : null,
367
380
  /* @__PURE__ */ jsx(
368
381
  Textarea,
@@ -381,7 +394,8 @@ function ReviewActionBar({ actions, onAction, pending = false }) {
381
394
  /* @__PURE__ */ jsx(
382
395
  Button,
383
396
  {
384
- color: feedbackAction?.tone === "danger" ? "var(--color-error)" : void 0,
397
+ variant: isIrreversible(feedbackAction) ? "filled" : void 0,
398
+ color: resolveActionColor(feedbackAction),
385
399
  disabled: Boolean(feedbackAction?.feedbackRequired) && feedback.trim().length === 0,
386
400
  onClick: submitFeedback,
387
401
  "data-testid": "content-review-feedback-submit",
@@ -414,10 +428,10 @@ function ContentReviewCard({
414
428
  actionsPending = false
415
429
  }) {
416
430
  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 }),
431
+ title || processingSteps.length > 0 ? /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "flex-start", wrap: "nowrap", children: [
432
+ title ? /* @__PURE__ */ jsx(Text, { fw: 700, size: "lg", children: title }) : null,
419
433
  /* @__PURE__ */ jsx(ProcessingStateStrip, { steps: processingSteps })
420
- ] }),
434
+ ] }) : null,
421
435
  fidelity && !fidelity.matches && /* @__PURE__ */ jsx(
422
436
  Alert,
423
437
  {
@@ -521,52 +535,6 @@ function ContentReviewCard({
521
535
  /* @__PURE__ */ jsx(ReviewActionBar, { actions, onAction, pending: actionsPending })
522
536
  ] }) });
523
537
  }
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
538
 
571
539
  // src/lib/content/stage-colors.ts
572
540
  var FALLBACK_CONTENT_STAGE_COLORS = {
@@ -609,43 +577,100 @@ function ContentOverviewPage({ headerActions, footer } = {}) {
609
577
  );
610
578
  const isLoading = itemsQuery.isLoading;
611
579
  const error = itemsQuery.error;
580
+ const { vocabulary } = contentConfig;
612
581
  return /* @__PURE__ */ jsxs(SubshellContentContainer, { children: [
613
582
  /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
614
- /* @__PURE__ */ jsx(PageTitleCaption, { title: "Content Overview", caption: "Pipeline status, review load, and recent activity" }),
583
+ /* @__PURE__ */ jsx(
584
+ PageTitleCaption,
585
+ {
586
+ title: `${vocabulary.item.plural} Overview`,
587
+ caption: `${vocabulary.pipeline.singular} status, review load, and recent activity`
588
+ }
589
+ ),
615
590
  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: [
591
+ isLoading ? /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) }) : error ? /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(CenteredErrorState, { error, title: `Failed to load ${vocabulary.item.plural.toLowerCase()}` }) }) : items.length === 0 ? (
592
+ // Finding 11: zero items is the state every tenant starts in, and the only one
593
+ // with no path forward on screen. The one available action is to go produce
594
+ // something -- routed through the pipelines page, which itself lands a
595
+ // single-pipeline tenant straight inside its workspace (Proposal, "Design the
596
+ // first run").
597
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(
598
+ EmptyState,
599
+ {
600
+ icon: IconArticle,
601
+ title: `No ${vocabulary.item.plural.toLowerCase()} yet`,
602
+ description: `${vocabulary.pipeline.plural} produce ${vocabulary.item.plural.toLowerCase()} as they move from draft through review to publish. Pick a pipeline to produce the first one.`,
603
+ action: {
604
+ label: `Go to ${vocabulary.pipeline.plural.toLowerCase()}`,
605
+ onClick: () => navigate({ to: "/content/pipelines" }),
606
+ icon: /* @__PURE__ */ jsx(IconArrowRight, { size: 14 })
607
+ }
608
+ }
609
+ ) })
610
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
617
611
  /* @__PURE__ */ jsxs(SimpleGrid, { cols: { base: 1, sm: 3 }, children: [
618
- /* @__PURE__ */ jsx(StatCard, { variant: "hero", icon: IconArticle, value: totalItems, label: "Total Items" }),
612
+ /* @__PURE__ */ jsx(
613
+ StatCard,
614
+ {
615
+ variant: "hero",
616
+ icon: IconArticle,
617
+ value: totalItems,
618
+ label: `Total ${vocabulary.item.plural}`
619
+ }
620
+ ),
619
621
  /* @__PURE__ */ jsx(StatCard, { variant: "hero", icon: IconClock, value: pendingReviewCount, label: "Pending Review" }),
620
- /* @__PURE__ */ jsx(StatCard, { variant: "hero", icon: IconSend, value: totalDistributions, label: "Distributions" })
622
+ /* @__PURE__ */ jsx(
623
+ StatCard,
624
+ {
625
+ variant: "hero",
626
+ icon: IconSend,
627
+ value: totalDistributions,
628
+ label: vocabulary.distribution.plural
629
+ }
630
+ )
621
631
  ] }),
622
632
  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) => {
633
+ /* @__PURE__ */ jsx(
634
+ CardHeader,
635
+ {
636
+ title: "By Status",
637
+ subtitle: `Where each ${vocabulary.item.singular.toLowerCase()} currently stands`
638
+ }
639
+ ),
640
+ /* @__PURE__ */ jsx(Group, { gap: 4, wrap: "wrap", children: statusEntries.map((entry, index) => {
625
641
  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
- );
642
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
643
+ index > 0 && /* @__PURE__ */ jsx(IconArrowRight, { size: 14, color: "var(--color-text-dimmed)" }),
644
+ /* @__PURE__ */ jsxs(
645
+ Badge,
646
+ {
647
+ size: "lg",
648
+ variant: "light",
649
+ styles: { root: { color: colors.accent, backgroundColor: colors.background } },
650
+ children: [
651
+ entry.label,
652
+ ": ",
653
+ statusCounts.get(entry.key) ?? 0
654
+ ]
655
+ }
656
+ )
657
+ ] }, entry.key);
640
658
  }) })
641
659
  ] }) }),
642
660
  /* @__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" }) }),
661
+ /* @__PURE__ */ jsx(
662
+ CardHeader,
663
+ {
664
+ title: "Recently Updated",
665
+ subtitle: `Jump into a ${vocabulary.item.singular.toLowerCase()}'s review page`
666
+ }
667
+ ),
644
668
  /* @__PURE__ */ jsxs(Table, { children: [
645
669
  /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
646
- /* @__PURE__ */ jsx(Table.Th, { children: "Title" }),
670
+ /* @__PURE__ */ jsx(Table.Th, { children: vocabulary.item.singular }),
647
671
  /* @__PURE__ */ jsx(Table.Th, { children: "Status" }),
648
672
  /* @__PURE__ */ jsx(Table.Th, { children: "Pillar" }),
673
+ /* @__PURE__ */ jsx(Table.Th, { children: "Age" }),
649
674
  /* @__PURE__ */ jsx(Table.Th, { children: "Reviewed" })
650
675
  ] }) }),
651
676
  /* @__PURE__ */ jsx(Table.Tbody, { children: recentItems.map((item) => {
@@ -656,9 +681,10 @@ function ContentOverviewPage({ headerActions, footer } = {}) {
656
681
  style: { cursor: "pointer" },
657
682
  onClick: () => navigate({ to: "/content/items/$itemId", params: { itemId: item.id } }),
658
683
  children: [
659
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", fw: 600, children: item.title }) }),
684
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", fw: 600, children: getContentItemIdentity(item) }) }),
660
685
  /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "light", children: statusEntry?.label ?? item.status }) }),
661
686
  /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: item.pillar ?? "\u2014" }) }),
687
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: formatTimeAgo(item.updatedAt) }) }),
662
688
  /* @__PURE__ */ jsx(Table.Td, { children: item.reviewedAt ? /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "light", color: "var(--color-success)", children: "Reviewed" }) : /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "light", color: "var(--color-warning)", children: "Pending" }) })
663
689
  ]
664
690
  },
@@ -676,10 +702,12 @@ var PAGE_SIZE = 20;
676
702
  function ContentItemsPage({ headerActions } = {}) {
677
703
  const navigate = useNavigate();
678
704
  const contentConfig = useContentConfig();
705
+ const rawSearch = useSearch({ strict: false });
706
+ const reviewFilterActive = rawSearch.review === "pending";
679
707
  const [searchQuery, setSearchQuery] = useState("");
680
708
  const [statusFilter, setStatusFilter] = useState("");
681
709
  const [pillarFilter, setPillarFilter] = useState("");
682
- const pagination = usePaginationState(PAGE_SIZE, [searchQuery, statusFilter, pillarFilter]);
710
+ const pagination = usePaginationState(PAGE_SIZE, [searchQuery, statusFilter, pillarFilter, reviewFilterActive]);
683
711
  const itemsQuery = useContentItems({
684
712
  search: searchQuery.trim() || void 0,
685
713
  status: statusFilter || void 0,
@@ -687,7 +715,8 @@ function ContentItemsPage({ headerActions } = {}) {
687
715
  limit: PAGE_SIZE,
688
716
  offset: pagination.offset
689
717
  });
690
- const items = itemsQuery.data?.data ?? [];
718
+ const fetchedItems = itemsQuery.data?.data ?? [];
719
+ const items = reviewFilterActive ? fetchedItems.filter((item) => item.reviewedAt === null) : fetchedItems;
691
720
  const total = itemsQuery.data?.total ?? 0;
692
721
  const statusOptions = [
693
722
  { value: "", label: "All statuses" },
@@ -698,7 +727,13 @@ function ContentItemsPage({ headerActions } = {}) {
698
727
  ...(contentConfig.stagesByCatalog.pillar ?? []).map((entry) => ({ value: entry.key, label: entry.label }))
699
728
  ];
700
729
  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" }),
730
+ /* @__PURE__ */ jsx(
731
+ PageTitleCaption,
732
+ {
733
+ title: contentConfig.vocabulary.item.plural,
734
+ caption: reviewFilterActive ? `${contentConfig.vocabulary.item.plural} awaiting review, across every pipeline` : `Every ${contentConfig.vocabulary.item.singular.toLowerCase()} across the content pipeline`
735
+ }
736
+ ),
702
737
  headerActions ? /* @__PURE__ */ jsx(Group, { gap: "xs", children: headerActions }) : null,
703
738
  /* @__PURE__ */ jsx(Paper, { children: /* @__PURE__ */ jsxs(Stack, { children: [
704
739
  /* @__PURE__ */ jsxs(FilterBar, { children: [
@@ -740,11 +775,11 @@ function ContentItemsPage({ headerActions } = {}) {
740
775
  EmptyState,
741
776
  {
742
777
  icon: IconFileText,
743
- title: searchQuery.trim() || statusFilter || pillarFilter ? "No items match your filters" : "No content items yet"
778
+ title: searchQuery.trim() || statusFilter || pillarFilter ? "No items match your filters" : reviewFilterActive ? "Nothing awaiting review" : `No ${contentConfig.vocabulary.item.plural.toLowerCase()} yet`
744
779
  }
745
780
  ) : /* @__PURE__ */ jsxs(Table, { children: [
746
781
  /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
747
- /* @__PURE__ */ jsx(Table.Th, { children: "Title" }),
782
+ /* @__PURE__ */ jsx(Table.Th, { children: contentConfig.vocabulary.item.singular }),
748
783
  /* @__PURE__ */ jsx(Table.Th, { children: "Status" }),
749
784
  /* @__PURE__ */ jsx(Table.Th, { children: "Pillar" }),
750
785
  /* @__PURE__ */ jsx(Table.Th, { children: "Reviewed" }),
@@ -760,7 +795,7 @@ function ContentItemsPage({ headerActions } = {}) {
760
795
  style: { cursor: "pointer" },
761
796
  onClick: () => navigate({ to: "/content/items/$itemId", params: { itemId: item.id } }),
762
797
  children: [
763
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, children: item.title }) }),
798
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, children: getContentItemIdentity(item) }) }),
764
799
  /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(
765
800
  Badge,
766
801
  {
@@ -774,7 +809,7 @@ function ContentItemsPage({ headerActions } = {}) {
774
809
  ) }),
775
810
  /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: pillarEntry?.label ?? item.pillar ?? "\u2014" }) }),
776
811
  /* @__PURE__ */ jsx(Table.Td, { children: item.reviewedAt ? /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "light", color: "var(--color-success)", children: "Reviewed" }) : /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "light", color: "var(--color-warning)", children: "Pending" }) }),
777
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: new Date(item.updatedAt).toLocaleString() }) })
812
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: formatRelativeTime(item.updatedAt) }) })
778
813
  ]
779
814
  },
780
815
  item.id
@@ -797,15 +832,20 @@ function ContentItemsPage({ headerActions } = {}) {
797
832
  // src/lib/content/processing-state.ts
798
833
  function mapContentStepStatus(status) {
799
834
  if (status === void 0) return "pending";
835
+ if (status === "running") return "in_progress";
800
836
  if (status === "error") return "failed";
801
837
  return "completed";
802
838
  }
803
839
  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
- }));
840
+ return pipelineSteps.map((step) => {
841
+ const entry = processingState?.[step.key];
842
+ return {
843
+ key: step.key,
844
+ label: step.label,
845
+ status: mapContentStepStatus(entry?.status),
846
+ executionId: entry ? entry.executionId : void 0
847
+ };
848
+ });
809
849
  }
810
850
 
811
851
  // src/lib/content/payload-text.ts
@@ -837,38 +877,49 @@ function toPayloadDisplayFields(fields, payload) {
837
877
  const payloadRecord = payload;
838
878
  return fields.filter((field) => field.key in payloadRecord).map((field) => ({ key: field.key, label: field.label, kind: toDisplayKind(field.type) }));
839
879
  }
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
- ];
880
+ var REVIEW_QUEUE_FETCH_LIMIT = 100;
881
+ function buildReviewActions(isTerminalPipeline, gate, reason) {
882
+ const gateLabel = gate?.step.label;
883
+ const disabled = !gate;
884
+ return [
885
+ {
886
+ id: "approved",
887
+ label: "Approve",
888
+ icon: IconCheck,
889
+ // Decision 7, resolved 2026-08-12: approve collects an OPTIONAL note, which is what
890
+ // finally gives `content_items.reviewer_feedback` a writer -- the page has always
891
+ // rendered it and the API has always accepted it, but nothing in the UI could set it.
892
+ // `feedbackRequired` is deliberately omitted: a reviewer with nothing to add still
893
+ // approves in two clicks, and a blank note leaves the column NULL rather than ''.
894
+ requiresFeedback: true,
895
+ feedbackLabel: "Note (optional)",
896
+ feedbackPlaceholder: "Anything the next step or a later reader should know?",
897
+ confirmationMessage: gateLabel ? isTerminalPipeline ? `Approve "${gateLabel}"? There is no further pipeline step -- this is the last internal gate before it is published.` : `Approve "${gateLabel}"? It moves on to the next pipeline step.` : void 0,
898
+ consequence: isTerminalPipeline ? "irreversible" : "standard",
899
+ disabled,
900
+ disabledReason: reason
901
+ },
902
+ {
903
+ id: "rejected",
904
+ label: "Reject",
905
+ icon: IconX,
906
+ tone: "danger",
907
+ requiresFeedback: true,
908
+ feedbackLabel: "Reason for rejection",
909
+ feedbackPlaceholder: "What needs to change before this can go out?",
910
+ feedbackRequired: true,
911
+ confirmationMessage: gateLabel ? `Reject "${gateLabel}"? This sends it back for changes.` : void 0,
912
+ disabled,
913
+ disabledReason: reason
914
+ }
915
+ ];
916
+ }
866
917
  function DistributionMetricsCell({ metrics }) {
867
- if (!metrics || typeof metrics !== "object") {
918
+ const entries = metrics && typeof metrics === "object" ? Object.entries(metrics) : [];
919
+ if (entries.length === 0) {
868
920
  return /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "\u2014" });
869
921
  }
870
- const entries = Object.entries(metrics);
871
- const isFlat = entries.length > 0 && entries.every(([, value]) => value === null || typeof value !== "object");
922
+ const isFlat = entries.every(([, value]) => value === null || typeof value !== "object");
872
923
  if (!isFlat) {
873
924
  return /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", ff: "monospace", style: { overflowWrap: "anywhere" }, children: JSON.stringify(metrics) });
874
925
  }
@@ -890,9 +941,26 @@ function ContentItemReviewPage({ itemId, headerActions }) {
890
941
  const item = itemQuery.data;
891
942
  const pipeline = item ? contentConfig.pipelines.find((candidate) => candidate.id === item.pipelineId) : void 0;
892
943
  const pipelineSteps = useMemo(() => pipeline?.steps ?? [], [pipeline]);
944
+ const isTerminalPipeline = pipelineSteps.length <= 1;
945
+ const openGates = useMemo(
946
+ () => getOpenContentReviewGates(item?.processingState ?? {}, pipelineSteps),
947
+ [item?.processingState, pipelineSteps]
948
+ );
949
+ const earliestOpenGate = openGates[0];
950
+ const reviewDisabledReason = useMemo(() => {
951
+ if (earliestOpenGate) return void 0;
952
+ if (!item?.pipelineId) return "This item has no pipeline, so there is no review gate to clear.";
953
+ if (!pipeline) return `Unknown pipeline "${item.pipelineId}" -- there is no review gate to clear.`;
954
+ return "No open review gate on this item. Every queued step has already been reviewed, or the current step is still running.";
955
+ }, [earliestOpenGate, item?.pipelineId, pipeline]);
956
+ const reviewActions = useMemo(
957
+ () => buildReviewActions(isTerminalPipeline, earliestOpenGate, reviewDisabledReason),
958
+ [isTerminalPipeline, earliestOpenGate, reviewDisabledReason]
959
+ );
893
960
  const pipelinePayloadFields = useMemo(() => getPipelinePayloadFields(pipelineSteps), [pipelineSteps]);
894
961
  const primaryTextField = useMemo(() => getPrimaryPayloadTextField(pipelinePayloadFields), [pipelinePayloadFields]);
895
962
  const originalBody = item ? getPayloadText(item.payload, primaryTextField?.key) : "";
963
+ const savedBody = item?.body ?? originalBody;
896
964
  const displayPayloadFields = useMemo(
897
965
  () => toPayloadDisplayFields(pipelinePayloadFields, item?.payload),
898
966
  [pipelinePayloadFields, item?.payload]
@@ -901,9 +969,51 @@ function ContentItemReviewPage({ itemId, headerActions }) {
901
969
  () => getContentProcessingSteps(item?.processingState, pipelineSteps),
902
970
  [item?.processingState, pipelineSteps]
903
971
  );
972
+ const visibleProcessingSteps = useMemo(
973
+ () => processingSteps.some((step) => step.status !== "pending") ? processingSteps : [],
974
+ [processingSteps]
975
+ );
976
+ const pipelineItemsQuery = useContentItems({
977
+ pipelineId: item?.pipelineId ?? void 0,
978
+ limit: REVIEW_QUEUE_FETCH_LIMIT
979
+ });
980
+ const reviewQueue = useMemo(() => {
981
+ if (!item?.pipelineId) return [];
982
+ return (pipelineItemsQuery.data?.data ?? []).filter((candidate) => candidate.reviewedAt === null).sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
983
+ }, [pipelineItemsQuery.data, item?.pipelineId]);
984
+ const queueIndex = item ? reviewQueue.findIndex((candidate) => candidate.id === item.id) : -1;
985
+ const queuePosition = queueIndex === -1 ? void 0 : queueIndex + 1;
986
+ const nextQueueItemId = queueIndex === -1 ? void 0 : reviewQueue[queueIndex + 1]?.id;
987
+ const goToNextInQueue = useCallback(() => {
988
+ if (nextQueueItemId) {
989
+ navigate({ to: "/content/items/$itemId", params: { itemId: nextQueueItemId } });
990
+ } else {
991
+ navigate({ to: "/content/items" });
992
+ }
993
+ }, [navigate, nextQueueItemId]);
904
994
  useEffect(() => {
905
995
  if (item) setBody(item.body ?? originalBody);
906
996
  }, [item?.id]);
997
+ useEffect(() => {
998
+ function handleKeyDown(event) {
999
+ const target = event.target;
1000
+ if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLElement && target.isContentEditable) {
1001
+ return;
1002
+ }
1003
+ if (event.key === "a" || event.key === "A") {
1004
+ event.preventDefault();
1005
+ document.querySelector('[data-testid="content-review-action-approved"]')?.click();
1006
+ } else if (event.key === "r" || event.key === "R") {
1007
+ event.preventDefault();
1008
+ document.querySelector('[data-testid="content-review-action-rejected"]')?.click();
1009
+ } else if (event.key === "n" || event.key === "N" || event.key === "ArrowRight") {
1010
+ event.preventDefault();
1011
+ goToNextInQueue();
1012
+ }
1013
+ }
1014
+ window.addEventListener("keydown", handleKeyDown);
1015
+ return () => window.removeEventListener("keydown", handleKeyDown);
1016
+ }, [goToNextInQueue]);
907
1017
  const backButton = /* @__PURE__ */ jsx(
908
1018
  Button,
909
1019
  {
@@ -945,6 +1055,7 @@ function ContentItemReviewPage({ itemId, headerActions }) {
945
1055
  const distributions = distributionsQuery.data?.data ?? [];
946
1056
  const isSaving = updateItem.isPending || reviewItem.isPending;
947
1057
  const viewingAttempt = attempts.find((attempt) => attempt.id === viewingAttemptId);
1058
+ const identity = getContentItemIdentity(item, pipelinePayloadFields);
948
1059
  const persistBodyIfDirty = async () => {
949
1060
  if (!isDirty) return;
950
1061
  await updateItem.mutateAsync({ itemId: item.id, updates: { body } });
@@ -954,64 +1065,77 @@ function ContentItemReviewPage({ itemId, headerActions }) {
954
1065
  showSuccessNotification("Saved");
955
1066
  };
956
1067
  const handleAction = async (actionId, feedback) => {
1068
+ if (!earliestOpenGate) return;
1069
+ const stepKey = earliestOpenGate.stepKey;
957
1070
  await persistBodyIfDirty();
958
1071
  if (actionId === "approved") {
959
1072
  const note = feedback?.trim();
960
1073
  await reviewItem.mutateAsync({
961
1074
  itemId: item.id,
962
- review: { decision: "approved", reviewerFeedback: note ? note : void 0 }
1075
+ review: { decision: "approved", stepKey, reviewerFeedback: note ? note : void 0 }
963
1076
  });
964
1077
  showSuccessNotification("Item approved");
965
1078
  } else if (actionId === "rejected") {
966
- await reviewItem.mutateAsync({ itemId: item.id, review: { decision: "rejected", rejectReason: feedback ?? "" } });
1079
+ await reviewItem.mutateAsync({
1080
+ itemId: item.id,
1081
+ review: { decision: "rejected", stepKey, rejectReason: feedback ?? "" }
1082
+ });
967
1083
  showSuccessNotification("Item rejected");
968
1084
  }
1085
+ goToNextInQueue();
969
1086
  };
970
1087
  return /* @__PURE__ */ jsxs(SubshellContentContainer, { children: [
971
1088
  /* @__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
- ),
1089
+ /* @__PURE__ */ jsx(PageTitleCaption, { title: identity }),
980
1090
  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: [
1091
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", gap: "xs", wrap: "nowrap", align: "center", children: [
1092
+ /* @__PURE__ */ jsxs(Group, { gap: "xs", wrap: "wrap", children: [
1093
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: pipeline?.label ?? item.pipelineId ?? "Unassigned" }),
983
1094
  /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "light", children: contentConfig.stageCatalog[item.status]?.label ?? item.status }),
984
1095
  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" })
1096
+ /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
1097
+ item.reviewedAt ? `Reviewed ${new Date(item.reviewedAt).toLocaleDateString()}` : "Not yet reviewed",
1098
+ queuePosition ? ` \xB7 ${queuePosition} of ${reviewQueue.length}` : ""
1099
+ ] })
989
1100
  ] }),
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
- )
1101
+ /* @__PURE__ */ jsxs(Group, { gap: "xs", wrap: "nowrap", children: [
1102
+ /* @__PURE__ */ jsx(
1103
+ Button,
1104
+ {
1105
+ size: "xs",
1106
+ variant: "light",
1107
+ onClick: handleSave,
1108
+ disabled: !isDirty || isSaving,
1109
+ loading: updateItem.isPending,
1110
+ children: "Save"
1111
+ }
1112
+ ),
1113
+ backButton
1114
+ ] })
1001
1115
  ] }),
1116
+ reviewDisabledReason && /* @__PURE__ */ jsx(
1117
+ Alert,
1118
+ {
1119
+ icon: /* @__PURE__ */ jsx(IconAlertTriangle, { size: 16 }),
1120
+ color: "var(--color-warning)",
1121
+ variant: "light",
1122
+ title: "Nothing to review right now",
1123
+ "data-testid": "content-review-no-open-gate",
1124
+ children: /* @__PURE__ */ jsx(Text, { size: "sm", children: reviewDisabledReason })
1125
+ }
1126
+ ),
1002
1127
  /* @__PURE__ */ jsx(
1003
1128
  ContentReviewCard,
1004
1129
  {
1005
- title: item.title,
1006
1130
  reviewMode: "live",
1007
1131
  body,
1008
1132
  onBodyChange: setBody,
1009
- originalBody,
1010
- onReset: () => setBody(originalBody),
1133
+ originalBody: savedBody,
1134
+ onReset: () => setBody(savedBody),
1011
1135
  payload: item.payload,
1012
1136
  payloadFields: displayPayloadFields,
1013
- processingSteps,
1014
- actions: REVIEW_ACTIONS,
1137
+ processingSteps: visibleProcessingSteps,
1138
+ actions: reviewActions,
1015
1139
  onAction: handleAction,
1016
1140
  actionsPending: isSaving
1017
1141
  }
@@ -1105,8 +1229,6 @@ function PipelineRow({
1105
1229
  [pipeline.steps, contentConfig]
1106
1230
  );
1107
1231
  const summary = useContentPipelineSummary(pipeline.id, statusKeys, stepResourceIds);
1108
- const artifactsQuery = useContentArtifacts({ pipelineId: pipeline.id });
1109
- const activeArtifacts = artifactsQuery.artifacts.filter((artifact) => artifact.isActive);
1110
1232
  return /* @__PURE__ */ jsxs(
1111
1233
  Table.Tr,
1112
1234
  {
@@ -1117,28 +1239,6 @@ function PipelineRow({
1117
1239
  /* @__PURE__ */ jsx(Text, { fw: 500, children: pipeline.label }),
1118
1240
  pipeline.description ? /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: pipeline.description }) : null
1119
1241
  ] }),
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
1242
  /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: summary.isLoading ? /* @__PURE__ */ jsx(Loader, { size: "xs" }) : formatLastRun(summary.lastRunAt) }) })
1143
1243
  ]
1144
1244
  }
@@ -1146,158 +1246,323 @@ function PipelineRow({
1146
1246
  }
1147
1247
  function ContentPipelinesPage({ headerActions } = {}) {
1148
1248
  const contentConfig = useContentConfig();
1249
+ const { vocabulary } = contentConfig;
1149
1250
  const statusEntries = contentConfig.stagesByCatalog.status ?? [];
1150
1251
  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" }),
1252
+ /* @__PURE__ */ jsx(
1253
+ PageTitleCaption,
1254
+ {
1255
+ title: vocabulary.pipeline.plural,
1256
+ caption: `Every declared ${vocabulary.pipeline.singular.toLowerCase()} and its production state`
1257
+ }
1258
+ ),
1152
1259
  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(
1260
+ /* @__PURE__ */ jsx(Paper, { children: contentConfig.pipelines.length === 0 ? /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(
1154
1261
  EmptyState,
1155
1262
  {
1156
1263
  icon: IconListDetails,
1157
- title: "No pipelines declared",
1158
- description: "The content System's Organization Model declares no content-pipeline catalog entries yet."
1264
+ title: `No ${vocabulary.pipeline.plural.toLowerCase()} declared`,
1265
+ description: `The content System's Organization Model declares no ${vocabulary.pipeline.singular.toLowerCase()} catalog entries yet.`
1159
1266
  }
1160
1267
  ) }) : /* @__PURE__ */ jsxs(Table, { children: [
1161
1268
  /* @__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" }),
1269
+ /* @__PURE__ */ jsx(Table.Th, { children: vocabulary.pipeline.singular }),
1165
1270
  /* @__PURE__ */ jsx(Table.Th, { children: "Last run" })
1166
1271
  ] }) }),
1167
1272
  /* @__PURE__ */ jsx(Table.Tbody, { children: contentConfig.pipelines.map((pipeline) => /* @__PURE__ */ jsx(PipelineRow, { pipeline, statusEntries }, pipeline.id)) })
1168
- ] }) }) })
1273
+ ] }) })
1169
1274
  ] }) }) });
1170
1275
  }
1171
- function OverviewTab({ pipeline, statusEntries }) {
1276
+ var ACTOR_LEAD = {
1277
+ vendor: "An outside service",
1278
+ workflow: "An automated workflow",
1279
+ agent: "An AI agent"
1280
+ };
1281
+ var REVIEW_MODE_TAIL = {
1282
+ live: "and you review it live, in the moment",
1283
+ queued: "then it waits for your review",
1284
+ none: "and moves on without review"
1285
+ };
1286
+ function describeStep(step) {
1287
+ return `${ACTOR_LEAD[step.actor]} handles "${step.label}," ${REVIEW_MODE_TAIL[step.reviewMode]}.`;
1288
+ }
1289
+ function StepRow({ step, resourceId }) {
1290
+ return /* @__PURE__ */ jsxs(Group, { gap: "sm", align: "flex-start", wrap: "nowrap", children: [
1291
+ /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "light", style: { flexShrink: 0 }, children: step.order }),
1292
+ /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
1293
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: describeStep(step) }),
1294
+ step.description ? /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: step.description }) : null,
1295
+ resourceId ? /* @__PURE__ */ jsx(Text, { size: "xs", ff: "monospace", c: "dimmed", children: resourceId }) : null
1296
+ ] })
1297
+ ] });
1298
+ }
1299
+ function HowItWorks({ pipeline }) {
1172
1300
  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
- }) })
1301
+ return /* @__PURE__ */ jsx(Accordion, { variant: "separated", children: /* @__PURE__ */ jsxs(Accordion.Item, { value: "how-it-works", children: [
1302
+ /* @__PURE__ */ jsx(Accordion.Control, { icon: /* @__PURE__ */ jsx(IconInfoCircle, { size: 16 }), children: /* @__PURE__ */ jsxs(Text, { size: "sm", fw: 500, children: [
1303
+ "How this ",
1304
+ contentConfig.vocabulary.pipeline.singular.toLowerCase(),
1305
+ " works"
1199
1306
  ] }) }),
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 })
1307
+ /* @__PURE__ */ jsx(Accordion.Panel, { children: /* @__PURE__ */ jsx(Stack, { gap: "sm", children: pipeline.steps.map((step) => /* @__PURE__ */ jsx(StepRow, { step, resourceId: contentConfig.resolveStepResource(step)?.resourceId }, step.key)) }) })
1308
+ ] }) });
1309
+ }
1310
+ var BOARD_FETCH_LIMIT = 100;
1311
+ function formatAge(ms) {
1312
+ const seconds = Math.floor(ms / 1e3);
1313
+ if (seconds < 60) return `${seconds}s`;
1314
+ const minutes = Math.floor(seconds / 60);
1315
+ if (minutes < 60) return `${minutes}m`;
1316
+ const hours = Math.floor(minutes / 60);
1317
+ if (hours < 24) return `${hours}h`;
1318
+ return `${Math.floor(hours / 24)}d`;
1319
+ }
1320
+ function BoardItemCard({ card, onOpen }) {
1321
+ return /* @__PURE__ */ jsx(Card, { withBorder: true, padding: "xs", onClick: () => onOpen(card.item.id), style: { cursor: "pointer" }, children: /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
1322
+ /* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, lineClamp: 2, children: getContentItemIdentity(card.item) }),
1323
+ /* @__PURE__ */ jsxs(Group, { gap: 6, justify: "space-between", wrap: "nowrap", children: [
1324
+ /* @__PURE__ */ jsxs(Group, { gap: 4, wrap: "nowrap", children: [
1325
+ /* @__PURE__ */ jsx(IconClockHour4, { size: 12, style: { color: "var(--color-text-dimmed)" } }),
1326
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: formatAge(card.ageMs) })
1327
+ ] }),
1328
+ card.awaitingReview ? /* @__PURE__ */ jsx(Badge, { size: "xs", variant: "light", children: "Awaiting review" }) : null
1329
+ ] })
1330
+ ] }) });
1331
+ }
1332
+ function BoardColumn({ column, onOpen }) {
1333
+ return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "sm", style: { minWidth: 260, maxWidth: 260, flexShrink: 0 }, children: /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
1334
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", wrap: "nowrap", children: [
1335
+ /* @__PURE__ */ jsx(Text, { fw: 600, size: "sm", truncate: true, children: column.step.label }),
1336
+ /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "light", children: column.cards.length })
1337
+ ] }),
1338
+ column.cards.length === 0 ? /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Nothing here" }) : /* @__PURE__ */ jsx(Stack, { gap: "xs", children: column.cards.map((card) => /* @__PURE__ */ jsx(BoardItemCard, { card, onOpen }, card.item.id)) })
1339
+ ] }) });
1340
+ }
1341
+ function ColumnBoard({ board, onOpen }) {
1342
+ return /* @__PURE__ */ jsx(ScrollArea, { type: "auto", offsetScrollbars: true, children: /* @__PURE__ */ jsx(Group, { gap: "xs", align: "flex-start", wrap: "nowrap", children: board.columns.map((column) => /* @__PURE__ */ jsx(BoardColumn, { column, onOpen }, column.step.key)) }) });
1343
+ }
1344
+ function CollapsedBoard({
1345
+ board,
1346
+ itemNounPlural,
1347
+ onOpen
1348
+ }) {
1349
+ const column = board.columns[0];
1350
+ if (!column) {
1351
+ return /* @__PURE__ */ jsx(
1352
+ EmptyState,
1353
+ {
1354
+ icon: IconRoute,
1355
+ title: "No steps declared",
1356
+ description: "This pipeline has no steps yet, so nothing has anywhere to sit."
1357
+ }
1358
+ );
1359
+ }
1360
+ const waiting = column.cards.filter((card) => card.awaitingReview);
1361
+ const settled = column.cards.filter((card) => !card.awaitingReview);
1362
+ const hasGate = !!column.gate;
1363
+ return /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
1364
+ /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
1365
+ /* @__PURE__ */ jsx(Text, { size: "sm", fw: 600, children: hasGate ? "Waiting on review" : column.step.label }),
1366
+ waiting.length === 0 ? column.cards.length === 0 ? /* @__PURE__ */ jsx(
1367
+ EmptyState,
1368
+ {
1369
+ icon: IconChecklist,
1370
+ title: `No ${itemNounPlural.toLowerCase()} yet`,
1371
+ description: `Produce one and it lands at "${column.step.label}."`
1372
+ }
1373
+ ) : /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "Nothing is waiting on review right now." }) : /* @__PURE__ */ jsx(Stack, { gap: "xs", children: waiting.map((card) => /* @__PURE__ */ jsx(BoardItemCard, { card, onOpen }, card.item.id)) })
1374
+ ] }),
1375
+ settled.length > 0 ? /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
1376
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", tt: "uppercase", fw: 600, children: hasGate ? "Already reviewed" : "In progress" }),
1377
+ /* @__PURE__ */ jsx(Stack, { gap: "xs", children: settled.map((card) => /* @__PURE__ */ jsx(BoardItemCard, { card, onOpen }, card.item.id)) })
1230
1378
  ] }) : null
1231
1379
  ] });
1232
1380
  }
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
- ) });
1381
+ function BoardOverflowSection({
1382
+ title,
1383
+ description,
1384
+ cards,
1385
+ onOpen
1386
+ }) {
1387
+ return /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
1388
+ /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
1389
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", tt: "uppercase", fw: 600, children: title }),
1390
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: description })
1391
+ ] }),
1392
+ /* @__PURE__ */ jsx(Group, { gap: "xs", align: "flex-start", wrap: "wrap", children: cards.map((card) => /* @__PURE__ */ jsx(Box, { style: { width: 260 }, children: /* @__PURE__ */ jsx(BoardItemCard, { card, onOpen }) }, card.item.id)) })
1393
+ ] });
1242
1394
  }
1243
- function DefaultProduceTab({ pipeline }) {
1395
+ function BoardTab({ pipeline }) {
1396
+ const navigate = useNavigate();
1244
1397
  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 ?? [];
1398
+ const itemsQuery = useContentItems({ pipelineId: pipeline.id, limit: BOARD_FETCH_LIMIT });
1399
+ const items = useMemo(() => itemsQuery.data?.data ?? [], [itemsQuery.data]);
1400
+ const board = useMemo(() => deriveContentBoard(items, pipeline, Date.now()), [items, pipeline]);
1401
+ const openItem = (itemId) => navigate({ to: "/content/items/$itemId", params: { itemId } });
1402
+ if (itemsQuery.isLoading) {
1403
+ return /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) });
1404
+ }
1405
+ if (itemsQuery.isError) {
1406
+ return /* @__PURE__ */ jsx(
1407
+ CenteredErrorState,
1408
+ {
1409
+ error: itemsQuery.error,
1410
+ title: `Failed to load ${contentConfig.vocabulary.item.plural.toLowerCase()}`
1411
+ }
1412
+ );
1413
+ }
1414
+ if (items.length === 0) {
1415
+ return /* @__PURE__ */ jsx(
1416
+ EmptyState,
1417
+ {
1418
+ icon: IconChecklist,
1419
+ title: `No ${contentConfig.vocabulary.item.plural.toLowerCase()} in this pipeline yet`,
1420
+ description: "Produce something from the Produce tab and it will show up here."
1421
+ }
1422
+ );
1423
+ }
1249
1424
  return /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
1250
- /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsx(
1251
- Select,
1425
+ board.isCollapsed ? /* @__PURE__ */ jsx(CollapsedBoard, { board, itemNounPlural: contentConfig.vocabulary.item.plural, onOpen: openItem }) : /* @__PURE__ */ jsx(ColumnBoard, { board, onOpen: openItem }),
1426
+ board.done.length > 0 ? /* @__PURE__ */ jsx(
1427
+ BoardOverflowSection,
1252
1428
  {
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
1429
+ title: "Done",
1430
+ description: "Every declared step has succeeded for these.",
1431
+ cards: board.done,
1432
+ onOpen: openItem
1262
1433
  }
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
- })
1434
+ ) : null,
1435
+ board.unplaced.length > 0 ? /* @__PURE__ */ jsx(
1436
+ BoardOverflowSection,
1437
+ {
1438
+ title: "Not on this pipeline's current steps",
1439
+ description: "Their recorded steps aren't in the step catalog anymore -- shown honestly rather than dropped into the first column.",
1440
+ cards: board.unplaced,
1441
+ onOpen: openItem
1442
+ }
1443
+ ) : null,
1444
+ /* @__PURE__ */ jsx(HowItWorks, { pipeline })
1294
1445
  ] });
1295
1446
  }
1296
- function ProduceTab({ pipeline, render }) {
1297
- if (render) return /* @__PURE__ */ jsx(Fragment, { children: render({ pipeline }) });
1298
- return /* @__PURE__ */ jsx(DefaultProduceTab, { pipeline });
1447
+ var PAGE_SIZE2 = 20;
1448
+ var FETCH_LIMIT = 100;
1449
+ function summarizeMetrics(metrics) {
1450
+ if (!metrics || typeof metrics !== "object" || Array.isArray(metrics)) return "\u2014";
1451
+ const entries = Object.entries(metrics);
1452
+ if (entries.length === 0) return "\u2014";
1453
+ const shown = entries.slice(0, 2).map(([key, value]) => `${key} ${String(value)}`).join(" \xB7 ");
1454
+ return entries.length > 2 ? `${shown} +${entries.length - 2}` : shown;
1299
1455
  }
1300
1456
  function formatDateTime(value) {
1457
+ return value ? new Date(value).toLocaleDateString() : "\u2014";
1458
+ }
1459
+ function DistributionsTab({ pipeline }) {
1460
+ const navigate = useNavigate();
1461
+ const contentConfig = useContentConfig();
1462
+ const distributionLabel = contentConfig.vocabulary.distribution.plural.toLowerCase();
1463
+ const itemsQuery = useContentItems({ pipelineId: pipeline.id, limit: FETCH_LIMIT });
1464
+ const distributionsQuery = useContentDistributions({ limit: FETCH_LIMIT });
1465
+ const itemById = useMemo(() => {
1466
+ const map = /* @__PURE__ */ new Map();
1467
+ for (const item of itemsQuery.data?.data ?? []) map.set(item.id, item);
1468
+ return map;
1469
+ }, [itemsQuery.data]);
1470
+ const distributions = useMemo(
1471
+ () => (distributionsQuery.data?.data ?? []).filter((distribution) => itemById.has(distribution.contentItemId)),
1472
+ [distributionsQuery.data, itemById]
1473
+ );
1474
+ const pagination = usePaginationState(PAGE_SIZE2, [pipeline.id], distributions.length);
1475
+ const paginatedDistributions = distributions.slice(pagination.offset, pagination.offset + PAGE_SIZE2);
1476
+ const isLoading = itemsQuery.isLoading || distributionsQuery.isLoading;
1477
+ const error = itemsQuery.error ?? distributionsQuery.error;
1478
+ const resolveIdentity = (distribution) => {
1479
+ const item = itemById.get(distribution.contentItemId);
1480
+ return item ? getContentItemIdentity(item) : distribution.contentItemId;
1481
+ };
1482
+ if (isLoading) {
1483
+ return /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) });
1484
+ }
1485
+ if (itemsQuery.isError || distributionsQuery.isError) {
1486
+ return /* @__PURE__ */ jsx(CenteredErrorState, { error, title: `Failed to load ${distributionLabel}` });
1487
+ }
1488
+ if (distributions.length === 0) {
1489
+ return /* @__PURE__ */ jsx(
1490
+ EmptyState,
1491
+ {
1492
+ icon: IconShare,
1493
+ title: `No ${distributionLabel} yet`,
1494
+ description: `Nothing from this pipeline has been distributed anywhere yet.`
1495
+ }
1496
+ );
1497
+ }
1498
+ return /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
1499
+ /* @__PURE__ */ jsxs(Table, { children: [
1500
+ /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
1501
+ /* @__PURE__ */ jsx(Table.Th, { children: contentConfig.vocabulary.item.singular }),
1502
+ /* @__PURE__ */ jsx(Table.Th, { children: "Platform" }),
1503
+ /* @__PURE__ */ jsx(Table.Th, { style: { whiteSpace: "nowrap" }, children: "Publish method" }),
1504
+ /* @__PURE__ */ jsx(Table.Th, { style: { whiteSpace: "nowrap" }, children: "Platform post ID" }),
1505
+ /* @__PURE__ */ jsx(Table.Th, { children: "Metrics" }),
1506
+ /* @__PURE__ */ jsx(Table.Th, { style: { whiteSpace: "nowrap" }, children: "Metrics updated" }),
1507
+ /* @__PURE__ */ jsx(Table.Th, { children: "Published" }),
1508
+ /* @__PURE__ */ jsx(Table.Th, {})
1509
+ ] }) }),
1510
+ /* @__PURE__ */ jsx(Table.Tbody, { children: paginatedDistributions.map((distribution) => {
1511
+ const platformEntry = contentConfig.stageCatalog[distribution.platform];
1512
+ const platformColors = getContentStageColor(platformEntry);
1513
+ return /* @__PURE__ */ jsxs(
1514
+ Table.Tr,
1515
+ {
1516
+ style: { cursor: "pointer" },
1517
+ onClick: () => navigate({
1518
+ to: "/content/distributions/$distributionId",
1519
+ params: { distributionId: distribution.id }
1520
+ }),
1521
+ children: [
1522
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, lineClamp: 1, children: resolveIdentity(distribution) }) }),
1523
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(
1524
+ Badge,
1525
+ {
1526
+ size: "sm",
1527
+ variant: "light",
1528
+ styles: { root: { color: platformColors.accent, backgroundColor: platformColors.background } },
1529
+ children: platformEntry?.label ?? distribution.platform
1530
+ }
1531
+ ) }),
1532
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: distribution.publishMethod ?? "\u2014" }) }),
1533
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "xs", ff: "monospace", c: "dimmed", children: distribution.platformPostId ?? "\u2014" }) }),
1534
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: summarizeMetrics(distribution.metrics) }) }),
1535
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: formatDateTime(distribution.metricsUpdatedAt) }) }),
1536
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: formatDateTime(distribution.publishedAt) }) }),
1537
+ /* @__PURE__ */ jsx(Table.Td, { children: distribution.platformUrl ? /* @__PURE__ */ jsx(
1538
+ Anchor,
1539
+ {
1540
+ href: distribution.platformUrl,
1541
+ target: "_blank",
1542
+ rel: "noopener noreferrer",
1543
+ style: { display: "inline-flex" },
1544
+ onClick: (event) => event.stopPropagation(),
1545
+ children: /* @__PURE__ */ jsx(IconExternalLink, { size: 16 })
1546
+ }
1547
+ ) : null })
1548
+ ]
1549
+ },
1550
+ distribution.id
1551
+ );
1552
+ }) })
1553
+ ] }),
1554
+ distributions.length > PAGE_SIZE2 ? /* @__PURE__ */ jsx(Group, { justify: "center", children: /* @__PURE__ */ jsx(
1555
+ Pagination,
1556
+ {
1557
+ value: pagination.page,
1558
+ onChange: pagination.setPage,
1559
+ total: pagination.totalPages(distributions.length),
1560
+ size: "sm"
1561
+ }
1562
+ ) }) : null
1563
+ ] });
1564
+ }
1565
+ function formatDateTime2(value) {
1301
1566
  return value ? new Date(value).toLocaleString() : "\u2014";
1302
1567
  }
1303
1568
  function formatDuration(startTime, endTime) {
@@ -1338,7 +1603,7 @@ function StepRunsSection({ step, resourceId }) {
1338
1603
  /* @__PURE__ */ jsx(Table.Tbody, { children: executions.map((execution) => /* @__PURE__ */ jsxs(Table.Tr, { children: [
1339
1604
  /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "xs", ff: "monospace", c: "dimmed", children: execution.id }) }),
1340
1605
  /* @__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) }) }),
1606
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: formatDateTime2(execution.startTime) }) }),
1342
1607
  /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: formatDuration(execution.startTime, execution.endTime) }) })
1343
1608
  ] }, execution.id)) })
1344
1609
  ] })
@@ -1348,19 +1613,18 @@ function RunsTab({ pipeline }) {
1348
1613
  const contentConfig = useContentConfig();
1349
1614
  return /* @__PURE__ */ jsx(Stack, { gap: "md", children: pipeline.steps.map((step) => /* @__PURE__ */ jsx(StepRunsSection, { step, resourceId: contentConfig.resolveStepResource(step)?.resourceId }, step.key)) });
1350
1615
  }
1351
- var PIPELINE_WORKSPACE_TABS = ["overview", "sources", "produce", "runs"];
1616
+ var PIPELINE_WORKSPACE_TABS = ["board", "distributions", "runs"];
1352
1617
  function isPipelineWorkspaceTab(value) {
1353
1618
  return typeof value === "string" && PIPELINE_WORKSPACE_TABS.includes(value);
1354
1619
  }
1355
1620
  function ContentPipelineWorkspacePage({
1356
1621
  pipelineId,
1357
- headerActions,
1358
- renderProduce
1622
+ headerActions
1359
1623
  }) {
1360
1624
  const navigate = useNavigate();
1361
1625
  const contentConfig = useContentConfig();
1362
1626
  const rawSearch = useSearch({ strict: false });
1363
- const activeTab = isPipelineWorkspaceTab(rawSearch.tab) ? rawSearch.tab : "overview";
1627
+ const activeTab = isPipelineWorkspaceTab(rawSearch.tab) ? rawSearch.tab : "board";
1364
1628
  const handleTabChange = useCallback(
1365
1629
  (value) => {
1366
1630
  if (!isPipelineWorkspaceTab(value) || value === activeTab) return;
@@ -1373,7 +1637,7 @@ function ContentPipelineWorkspacePage({
1373
1637
  [activeTab, navigate]
1374
1638
  );
1375
1639
  const pipeline = contentConfig.pipelines.find((candidate) => candidate.id === pipelineId);
1376
- const statusEntries = contentConfig.stagesByCatalog.status ?? [];
1640
+ const vocabulary = contentConfig.vocabulary;
1377
1641
  const backButton = /* @__PURE__ */ jsx(
1378
1642
  Button,
1379
1643
  {
@@ -1411,109 +1675,19 @@ function ContentPipelineWorkspacePage({
1411
1675
  }
1412
1676
  ),
1413
1677
  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: [
1678
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(Tabs, { value: activeTab, onChange: handleTabChange, keepMounted: false, children: [
1419
1679
  /* @__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" }),
1680
+ /* @__PURE__ */ jsx(Tabs.Tab, { value: "board", leftSection: /* @__PURE__ */ jsx(IconLayoutGrid, { size: 14 }), children: vocabulary.board }),
1681
+ /* @__PURE__ */ jsx(Tabs.Tab, { value: "distributions", leftSection: /* @__PURE__ */ jsx(IconShare, { size: 14 }), children: vocabulary.distribution.plural }),
1423
1682
  /* @__PURE__ */ jsx(Tabs.Tab, { value: "runs", leftSection: /* @__PURE__ */ jsx(IconPlayerPlay, { size: 14 }), children: "Runs" })
1424
1683
  ] }),
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 }) })
1684
+ /* @__PURE__ */ jsx(Tabs.Panel, { value: "board", pt: "md", children: /* @__PURE__ */ jsx(BoardTab, { pipeline }) }),
1685
+ /* @__PURE__ */ jsx(Tabs.Panel, { value: "distributions", pt: "md", children: /* @__PURE__ */ jsx(DistributionsTab, { pipeline }) }),
1686
+ /* @__PURE__ */ jsx(Tabs.Panel, { value: "runs", pt: "md", children: /* @__PURE__ */ jsx(RunsTab, { pipeline }) })
1429
1687
  ] }) })
1430
1688
  ] }) }) });
1431
1689
  }
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;
1690
+ var PAGE_SIZE3 = 20;
1517
1691
  var QUERY_LIMIT = 100;
1518
1692
  function formatDate(value) {
1519
1693
  return value ? new Date(value).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" }) : "\u2014";
@@ -1553,11 +1727,11 @@ function ContentDistributionsPage({ headerActions } = {}) {
1553
1727
  ...(contentConfig.stagesByCatalog.platform ?? []).map((entry) => ({ value: entry.key, label: entry.label }))
1554
1728
  ];
1555
1729
  const pagination = usePaginationState(
1556
- PAGE_SIZE2,
1730
+ PAGE_SIZE3,
1557
1731
  [platformFilter, statusFilter, formatFilter],
1558
1732
  filteredDistributions.length
1559
1733
  );
1560
- const paginatedDistributions = filteredDistributions.slice(pagination.offset, pagination.offset + PAGE_SIZE2);
1734
+ const paginatedDistributions = filteredDistributions.slice(pagination.offset, pagination.offset + PAGE_SIZE3);
1561
1735
  const resolveTitle = (distribution) => itemTitleById.get(distribution.contentItemId) ?? distribution.contentItemId;
1562
1736
  return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
1563
1737
  /* @__PURE__ */ jsx(
@@ -1662,7 +1836,7 @@ function ContentDistributionsPage({ headerActions } = {}) {
1662
1836
  );
1663
1837
  }) })
1664
1838
  ] }),
1665
- filteredDistributions.length > PAGE_SIZE2 && /* @__PURE__ */ jsx(Group, { justify: "center", children: /* @__PURE__ */ jsx(
1839
+ filteredDistributions.length > PAGE_SIZE3 && /* @__PURE__ */ jsx(Group, { justify: "center", children: /* @__PURE__ */ jsx(
1666
1840
  Pagination,
1667
1841
  {
1668
1842
  value: pagination.page,
@@ -2177,4 +2351,4 @@ function ContentDistributionDetailPage({
2177
2351
  ] });
2178
2352
  }
2179
2353
 
2180
- export { AlternatesPanel, ContentDistributionDetailPage, ContentDistributionsPage, ContentItemReviewPage, ContentItemsPage, ContentOverviewPage, ContentPipelineWorkspacePage, ContentPipelinesPage, ContentReviewCard, ContentSettingsPage, PayloadBody, ProcessingStateStrip, ReviewActionBar, firstLine, formatElapsed, lastLine, resolveContentStepResource, swapFirstLine, swapLastLine, useContentConfig };
2354
+ export { AlternatesPanel, ContentDistributionDetailPage, ContentDistributionsPage, ContentItemReviewPage, ContentItemsPage, ContentOverviewPage, ContentPipelineWorkspacePage, ContentPipelinesPage, ContentReviewCard, PayloadBody, ProcessingStateStrip, ReviewActionBar, firstLine, formatElapsed, lastLine, swapFirstLine, swapLastLine };