@happyvertical/smrt-content 0.43.10 → 0.44.1

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 (144) hide show
  1. package/agents/content-list.md +61 -5
  2. package/dist/chunks/{content-corrections-BxfA3tW1.js → content-corrections-B4mPd_th.js} +6 -3
  3. package/dist/chunks/content-corrections-B4mPd_th.js.map +1 -0
  4. package/dist/chunks/content-query-D1bLBzMT.js +4092 -0
  5. package/dist/chunks/{content-query-4w62u5_0.js.map → content-query-D1bLBzMT.js.map} +1 -1
  6. package/dist/chunks/{content-reviews-BWrYEQc6.js → content-reviews-lBoqJY7Y.js} +6 -3
  7. package/dist/chunks/content-reviews-lBoqJY7Y.js.map +1 -0
  8. package/dist/chunks/{content-versions-Di3vcTgb.js → content-versions-B0OUI1ut.js} +6 -3
  9. package/dist/chunks/content-versions-B0OUI1ut.js.map +1 -0
  10. package/dist/chunks/{contents-BV8RoP_R.js → contents-CRG2Snrb.js} +2 -2
  11. package/dist/chunks/{contents-BV8RoP_R.js.map → contents-CRG2Snrb.js.map} +1 -1
  12. package/dist/content-contribution.d.ts +1 -1
  13. package/dist/content-contribution.d.ts.map +1 -1
  14. package/dist/index.js +12 -9
  15. package/dist/index.js.map +1 -1
  16. package/dist/manifest.json +33 -6
  17. package/dist/server/content-list-actions.d.ts +10 -3
  18. package/dist/server/content-list-actions.d.ts.map +1 -1
  19. package/dist/server/content-list-lifecycle.d.ts +20 -0
  20. package/dist/server/content-list-lifecycle.d.ts.map +1 -0
  21. package/dist/server.d.ts +1 -0
  22. package/dist/server.d.ts.map +1 -1
  23. package/dist/server.js +189 -25
  24. package/dist/server.js.map +1 -1
  25. package/dist/smrt-knowledge.json +17 -5
  26. package/dist/svelte/components/ContentAgentChat.svelte +10 -0
  27. package/dist/svelte/components/ContentAgentChat.svelte.d.ts +10 -0
  28. package/dist/svelte/components/ContentAgentChat.svelte.d.ts.map +1 -1
  29. package/dist/svelte/components/ContentBodyEditor.svelte +9 -0
  30. package/dist/svelte/components/ContentBodyEditor.svelte.d.ts +9 -0
  31. package/dist/svelte/components/ContentBodyEditor.svelte.d.ts.map +1 -1
  32. package/dist/svelte/components/ContentBodyRenderer.svelte +3 -0
  33. package/dist/svelte/components/ContentBodyRenderer.svelte.d.ts +3 -0
  34. package/dist/svelte/components/ContentBodyRenderer.svelte.d.ts.map +1 -1
  35. package/dist/svelte/components/ContentClaimAuditTool.svelte +3 -0
  36. package/dist/svelte/components/ContentClaimAuditTool.svelte.d.ts +3 -0
  37. package/dist/svelte/components/ContentClaimAuditTool.svelte.d.ts.map +1 -1
  38. package/dist/svelte/components/ContentContributionForm.svelte +7 -0
  39. package/dist/svelte/components/ContentContributionForm.svelte.d.ts +7 -0
  40. package/dist/svelte/components/ContentContributionForm.svelte.d.ts.map +1 -1
  41. package/dist/svelte/components/ContentContributionInbox.svelte +8 -0
  42. package/dist/svelte/components/ContentContributionInbox.svelte.d.ts +8 -0
  43. package/dist/svelte/components/ContentContributionInbox.svelte.d.ts.map +1 -1
  44. package/dist/svelte/components/ContentContributionPortal.svelte +5 -0
  45. package/dist/svelte/components/ContentContributionPortal.svelte.d.ts +5 -0
  46. package/dist/svelte/components/ContentContributionPortal.svelte.d.ts.map +1 -1
  47. package/dist/svelte/components/ContentContributionTypeManager.svelte +3 -0
  48. package/dist/svelte/components/ContentContributionTypeManager.svelte.d.ts +3 -0
  49. package/dist/svelte/components/ContentContributionTypeManager.svelte.d.ts.map +1 -1
  50. package/dist/svelte/components/ContentContributorManager.svelte +3 -0
  51. package/dist/svelte/components/ContentContributorManager.svelte.d.ts +3 -0
  52. package/dist/svelte/components/ContentContributorManager.svelte.d.ts.map +1 -1
  53. package/dist/svelte/components/ContentCorrectionsTool.svelte +4 -0
  54. package/dist/svelte/components/ContentCorrectionsTool.svelte.d.ts +4 -0
  55. package/dist/svelte/components/ContentCorrectionsTool.svelte.d.ts.map +1 -1
  56. package/dist/svelte/components/ContentEditor.svelte +16 -0
  57. package/dist/svelte/components/ContentEditor.svelte.d.ts +16 -0
  58. package/dist/svelte/components/ContentEditor.svelte.d.ts.map +1 -1
  59. package/dist/svelte/components/ContentGovernanceAssignmentEditor.svelte +4 -0
  60. package/dist/svelte/components/ContentGovernanceAssignmentEditor.svelte.d.ts +4 -0
  61. package/dist/svelte/components/ContentGovernanceAssignmentEditor.svelte.d.ts.map +1 -1
  62. package/dist/svelte/components/ContentGovernanceManager.svelte +3 -0
  63. package/dist/svelte/components/ContentGovernanceManager.svelte.d.ts +3 -0
  64. package/dist/svelte/components/ContentGovernanceManager.svelte.d.ts.map +1 -1
  65. package/dist/svelte/components/ContentGovernancePanel.svelte +16 -0
  66. package/dist/svelte/components/ContentGovernancePanel.svelte.d.ts +16 -0
  67. package/dist/svelte/components/ContentGovernancePanel.svelte.d.ts.map +1 -1
  68. package/dist/svelte/components/ContentGovernancePolicyEditor.svelte +3 -0
  69. package/dist/svelte/components/ContentGovernancePolicyEditor.svelte.d.ts +3 -0
  70. package/dist/svelte/components/ContentGovernancePolicyEditor.svelte.d.ts.map +1 -1
  71. package/dist/svelte/components/ContentGovernanceProfileEditor.svelte +4 -0
  72. package/dist/svelte/components/ContentGovernanceProfileEditor.svelte.d.ts +4 -0
  73. package/dist/svelte/components/ContentGovernanceProfileEditor.svelte.d.ts.map +1 -1
  74. package/dist/svelte/components/ContentGovernanceTool.svelte +5 -0
  75. package/dist/svelte/components/ContentGovernanceTool.svelte.d.ts +5 -0
  76. package/dist/svelte/components/ContentGovernanceTool.svelte.d.ts.map +1 -1
  77. package/dist/svelte/components/ContentImageBrowser.svelte +10 -0
  78. package/dist/svelte/components/ContentImageBrowser.svelte.d.ts +10 -0
  79. package/dist/svelte/components/ContentImageBrowser.svelte.d.ts.map +1 -1
  80. package/dist/svelte/components/ContentImageChooser.svelte +4 -0
  81. package/dist/svelte/components/ContentImageChooser.svelte.d.ts +4 -0
  82. package/dist/svelte/components/ContentImageChooser.svelte.d.ts.map +1 -1
  83. package/dist/svelte/components/ContentList.svelte +201 -22
  84. package/dist/svelte/components/ContentList.svelte.d.ts +13 -0
  85. package/dist/svelte/components/ContentList.svelte.d.ts.map +1 -1
  86. package/dist/svelte/components/ContentListLifecyclePanel.svelte +341 -0
  87. package/dist/svelte/components/ContentListLifecyclePanel.svelte.d.ts +17 -0
  88. package/dist/svelte/components/ContentListLifecyclePanel.svelte.d.ts.map +1 -0
  89. package/dist/svelte/components/ContentMetadataFields.svelte +2 -0
  90. package/dist/svelte/components/ContentMetadataFields.svelte.d.ts +2 -0
  91. package/dist/svelte/components/ContentMetadataFields.svelte.d.ts.map +1 -1
  92. package/dist/svelte/components/ContentReferencesPanel.svelte +3 -0
  93. package/dist/svelte/components/ContentReferencesPanel.svelte.d.ts +3 -0
  94. package/dist/svelte/components/ContentReferencesPanel.svelte.d.ts.map +1 -1
  95. package/dist/svelte/components/ContentReviewStatusTray.svelte +5 -0
  96. package/dist/svelte/components/ContentReviewStatusTray.svelte.d.ts +5 -0
  97. package/dist/svelte/components/ContentReviewStatusTray.svelte.d.ts.map +1 -1
  98. package/dist/svelte/components/ContentStatusFields.svelte +2 -0
  99. package/dist/svelte/components/ContentStatusFields.svelte.d.ts +2 -0
  100. package/dist/svelte/components/ContentStatusFields.svelte.d.ts.map +1 -1
  101. package/dist/svelte/components/ContentTitleField.svelte +4 -0
  102. package/dist/svelte/components/ContentTitleField.svelte.d.ts +4 -0
  103. package/dist/svelte/components/ContentTitleField.svelte.d.ts.map +1 -1
  104. package/dist/svelte/components/ContentTransparencyReport.svelte +3 -0
  105. package/dist/svelte/components/ContentTransparencyReport.svelte.d.ts +3 -0
  106. package/dist/svelte/components/ContentTransparencyReport.svelte.d.ts.map +1 -1
  107. package/dist/svelte/components/ContentTransparencyTool.svelte +3 -0
  108. package/dist/svelte/components/ContentTransparencyTool.svelte.d.ts +3 -0
  109. package/dist/svelte/components/ContentTransparencyTool.svelte.d.ts.map +1 -1
  110. package/dist/svelte/components/ContentVersionsTool.svelte +3 -0
  111. package/dist/svelte/components/ContentVersionsTool.svelte.d.ts +3 -0
  112. package/dist/svelte/components/ContentVersionsTool.svelte.d.ts.map +1 -1
  113. package/dist/svelte/components/GovernedContentEditor.svelte +20 -0
  114. package/dist/svelte/components/GovernedContentEditor.svelte.d.ts +20 -0
  115. package/dist/svelte/components/GovernedContentEditor.svelte.d.ts.map +1 -1
  116. package/dist/svelte/content-list-lifecycle.d.ts +132 -0
  117. package/dist/svelte/content-list-lifecycle.d.ts.map +1 -0
  118. package/dist/svelte/content-list-lifecycle.js +439 -0
  119. package/dist/svelte/i18n.contribution.d.ts +27 -0
  120. package/dist/svelte/i18n.contribution.d.ts.map +1 -1
  121. package/dist/svelte/i18n.contribution.js +27 -0
  122. package/dist/svelte/index.d.ts +1 -0
  123. package/dist/svelte/index.d.ts.map +1 -1
  124. package/dist/svelte/index.js +1 -0
  125. package/dist/svelte/routes/ContentContributionsRoute.svelte +4 -0
  126. package/dist/svelte/routes/ContentContributionsRoute.svelte.d.ts +4 -0
  127. package/dist/svelte/routes/ContentContributionsRoute.svelte.d.ts.map +1 -1
  128. package/dist/svelte/routes/ContentFactsRoute.svelte +5 -0
  129. package/dist/svelte/routes/ContentFactsRoute.svelte.d.ts +5 -0
  130. package/dist/svelte/routes/ContentFactsRoute.svelte.d.ts.map +1 -1
  131. package/dist/svelte/routes/ContentGovernanceRoute.svelte +4 -0
  132. package/dist/svelte/routes/ContentGovernanceRoute.svelte.d.ts +4 -0
  133. package/dist/svelte/routes/ContentGovernanceRoute.svelte.d.ts.map +1 -1
  134. package/dist/svelte/routes/ContentWorkspaceRoute.svelte +5 -0
  135. package/dist/svelte/routes/ContentWorkspaceRoute.svelte.d.ts +5 -0
  136. package/dist/svelte/routes/ContentWorkspaceRoute.svelte.d.ts.map +1 -1
  137. package/dist/svelte/routes/PublishedArticleRoute.svelte +3 -0
  138. package/dist/svelte/routes/PublishedArticleRoute.svelte.d.ts +3 -0
  139. package/dist/svelte/routes/PublishedArticleRoute.svelte.d.ts.map +1 -1
  140. package/package.json +16 -15
  141. package/dist/chunks/content-corrections-BxfA3tW1.js.map +0 -1
  142. package/dist/chunks/content-query-4w62u5_0.js +0 -4092
  143. package/dist/chunks/content-reviews-BWrYEQc6.js.map +0 -1
  144. package/dist/chunks/content-versions-Di3vcTgb.js.map +0 -1
@@ -11,9 +11,13 @@ import { M } from '../i18n.editor.js';
11
11
  const { t } = useI18n();
12
12
 
13
13
  export interface Props {
14
+ /** Content body text from which images are extracted. */
14
15
  body: string;
16
+ /** Format of the body content for correct image extraction. */
15
17
  format?: ContentBodyFormat | null;
18
+ /** Index of the currently selected image within the body. */
16
19
  selectedIndex?: number;
20
+ /** Invoked when the user selects an image by index. */
17
21
  onSelect?: (index: number) => void;
18
22
  }
19
23
 
@@ -1,8 +1,12 @@
1
1
  import { type ContentBodyFormat } from '../../body-format';
2
2
  export interface Props {
3
+ /** Content body text from which images are extracted. */
3
4
  body: string;
5
+ /** Format of the body content for correct image extraction. */
4
6
  format?: ContentBodyFormat | null;
7
+ /** Index of the currently selected image within the body. */
5
8
  selectedIndex?: number;
9
+ /** Invoked when the user selects an image by index. */
6
10
  onSelect?: (index: number) => void;
7
11
  }
8
12
  declare const ContentImageChooser: import("svelte").Component<Props, {}, "">;
@@ -1 +1 @@
1
- {"version":3,"file":"ContentImageChooser.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/ContentImageChooser.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,KAAK,iBAAiB,EAGvB,MAAM,mBAAmB,CAAC;AAI3B,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAClC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAoED,QAAA,MAAM,mBAAmB,2CAAwC,CAAC;AAClE,KAAK,mBAAmB,GAAG,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAClE,eAAe,mBAAmB,CAAC"}
1
+ {"version":3,"file":"ContentImageChooser.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/ContentImageChooser.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,KAAK,iBAAiB,EAGvB,MAAM,mBAAmB,CAAC;AAI3B,MAAM,WAAW,KAAK;IACpB,yDAAyD;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,+DAA+D;IAC/D,MAAM,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAClC,6DAA6D;IAC7D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,uDAAuD;IACvD,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAoED,QAAA,MAAM,mBAAmB,2CAAwC,CAAC;AAClE,KAAK,mBAAmB,GAAG,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAClE,eAAe,mBAAmB,CAAC"}
@@ -45,6 +45,11 @@ import { Button, Pagination } from '@happyvertical/smrt-ui/ui';
45
45
  import type { Snippet } from 'svelte';
46
46
  import { untrack } from 'svelte';
47
47
  import type { ContentData } from '../../mock-smrt-client.js';
48
+ import type { ContentListLifecycleBinding } from '../content-list-lifecycle.js';
49
+ import {
50
+ readContentListLifecycleSummary,
51
+ reconcileContentListLifecycleSelection,
52
+ } from '../content-list-lifecycle.js';
48
53
  import {
49
54
  applyContentListFilter,
50
55
  buildContentListColumns,
@@ -123,6 +128,7 @@ import {
123
128
  readContentListViewStateFromSearchParams,
124
129
  } from '../content-list-url-state.js';
125
130
  import { M } from '../i18n.contribution.js';
131
+ import ContentListLifecyclePanel from './ContentListLifecyclePanel.svelte';
126
132
  import ImageThumbnail from './ImageThumbnail.svelte';
127
133
 
128
134
  const { t } = useI18n();
@@ -290,6 +296,7 @@ function workflowJobStatusLabel(status: string): string {
290
296
  type ContentListDropNotice = ContentListStateDrop | ContentListQueryDrop;
291
297
 
292
298
  interface Props {
299
+ /** Base URL for content API requests. */
293
300
  apiBaseUrl?: string;
294
301
  /**
295
302
  * Client-side rows. Ignored when `query` is supplied — the server then owns
@@ -297,12 +304,19 @@ interface Props {
297
304
  * source of truth.
298
305
  */
299
306
  contents?: ContentData[];
307
+ /** Filter content by this type; no filtering applied when omitted. */
300
308
  type?: string;
309
+ /** Initial view layout mode for the content list. */
301
310
  defaultViewMode?: ContentListViewMode;
311
+ /** Invoked when the user requests to edit a content item. */
302
312
  onEdit: (content: ContentData) => void;
313
+ /** Invoked when the user requests to delete a content item. */
303
314
  onDelete: (content: ContentData) => void;
315
+ /** Invoked when the user requests to create new content. */
304
316
  onAdd: () => void;
317
+ /** Optional UI controls rendered above the content list. */
305
318
  controls?: Snippet;
319
+ /** Returns a URL to view the published content; null hides the view link. */
306
320
  getViewHref?: (content: ContentData) => string | null;
307
321
  /** Announced uniformly by every presentation; #2455 extends it. */
308
322
  loading?: boolean;
@@ -327,6 +341,10 @@ interface Props {
327
341
  jobs?: ContentListJobBinding;
328
342
  /** Opt-in, authenticated preview/apply client for bulk workflows (#2453). */
329
343
  workflows?: ContentListWorkflowBinding;
344
+ /** Opt-in, server-authoritative trash/restore/permanent-delete lifecycle. */
345
+ lifecycle?: ContentListLifecycleBinding;
346
+ /** Locks the status query to deleted content and exposes trash actions. */
347
+ lifecycleMode?: 'active' | 'trash';
330
348
  }
331
349
 
332
350
  let {
@@ -348,6 +366,8 @@ let {
348
366
  savedViews = undefined,
349
367
  jobs = undefined,
350
368
  workflows = undefined,
369
+ lifecycle = undefined,
370
+ lifecycleMode = 'active',
351
371
  }: Props = $props();
352
372
 
353
373
  const initialQuery = untrack(() => query);
@@ -417,6 +437,7 @@ const urlStateOptions: ContentListUrlStateOptions = {
417
437
 
418
438
  const controller = createContentListController({
419
439
  type: untrack(() => type),
440
+ ...(untrack(() => lifecycleMode) === 'trash' ? { status: 'deleted' } : {}),
420
441
  // Local mode keeps the historical unpaginated list.
421
442
  ...(serverPageSize === null ? {} : { pageSize: serverPageSize }),
422
443
  });
@@ -446,6 +467,10 @@ let resultNotices = $state<ContentListQueryNotices>({
446
467
  * its own total ever arrived.
447
468
  */
448
469
  let settledSignature = $state<string | undefined>(undefined);
470
+ let settledLifecycleQuery = $state<ContentListDataQueryRequest | undefined>(
471
+ undefined,
472
+ );
473
+ let settledLifecycleFingerprint = $state<string | undefined>(undefined);
449
474
 
450
475
  function toSearchParams(
451
476
  input: URLSearchParams | string | null | undefined,
@@ -472,22 +497,34 @@ if (initialUrlState?.params !== undefined && initialUrlState?.params !== null) {
472
497
  // the same link just restored. Folding the lock into the restored patch keeps
473
498
  // it to one `replaceState`, and leaves the effect's first run a no-op.
474
499
  const initialLockedType = normalizeContentListTypeLock(untrack(() => type));
475
- const patch =
476
- initialLockedType === null
477
- ? reading.state
478
- : {
479
- ...reading.state,
480
- filters: [
481
- ...(reading.state.filters ?? []).filter(
482
- (filter) => filter.columnId !== CONTENT_LIST_TYPE_FILTER_ID,
483
- ),
484
- {
485
- columnId: CONTENT_LIST_TYPE_FILTER_ID,
486
- operator: 'equals' as const,
487
- value: initialLockedType,
488
- },
489
- ],
490
- };
500
+ const initialTrashMode = untrack(() => lifecycleMode) === 'trash';
501
+ const lockedFilters = [...(reading.state.filters ?? [])].filter(
502
+ (filter) =>
503
+ (initialLockedType === null ||
504
+ filter.columnId !== CONTENT_LIST_TYPE_FILTER_ID) &&
505
+ (!initialTrashMode ||
506
+ filter.columnId !== CONTENT_LIST_STATUS_FILTER_ID),
507
+ );
508
+ if (initialLockedType !== null) {
509
+ lockedFilters.push({
510
+ columnId: CONTENT_LIST_TYPE_FILTER_ID,
511
+ operator: 'equals',
512
+ value: initialLockedType,
513
+ });
514
+ }
515
+ if (initialTrashMode) {
516
+ lockedFilters.push({
517
+ columnId: CONTENT_LIST_STATUS_FILTER_ID,
518
+ operator: 'equals',
519
+ value: 'deleted',
520
+ });
521
+ }
522
+ const patch = {
523
+ ...reading.state,
524
+ ...(initialLockedType !== null || initialTrashMode
525
+ ? { filters: lockedFilters }
526
+ : {}),
527
+ };
491
528
  applyContentListViewState(controller, patch, restoreOptions);
492
529
  restoreDrops = reading.dropped;
493
530
  }
@@ -507,6 +544,7 @@ const queryRequestOptions: ContentListQueryRequestOptions | undefined =
507
544
  }
508
545
  : undefined;
509
546
  const serverBacked = queryBinding !== undefined;
547
+ let locallyRemovedLifecycleIds = $state<Set<string>>(new Set());
510
548
 
511
549
  let snapshot = $state(controller.snapshot());
512
550
  let viewMode: ContentListViewMode = $state(untrack(() => defaultViewMode));
@@ -524,6 +562,7 @@ const MAX_DEFERRED_JOB_COMPLETIONS = 50;
524
562
  let completedJobs = $state<ContentListJob[]>([]);
525
563
  let completedJobsOverflowed = $state(false);
526
564
  let activeQueryKey = $state<string | undefined>(undefined);
565
+ let lifecycleRefreshPending = $state(false);
527
566
 
528
567
  // Job state is subscribed once so hosts can use a framework-free controller.
529
568
  // Only transitions observed after the initial snapshot are completion events;
@@ -689,8 +728,20 @@ const columnLabels = $derived({
689
728
 
690
729
  const queryColumns = $derived(buildContentListColumns(columnLabels));
691
730
  const sourceContents = $derived(
692
- queryBinding ? contentListQueryRowsToContents(queryBinding.rows) : contents,
731
+ (queryBinding
732
+ ? contentListQueryRowsToContents(queryBinding.rows)
733
+ : contents
734
+ ).filter((content) => !locallyRemovedLifecycleIds.has(String(content.id))),
693
735
  );
736
+
737
+ $effect(() => {
738
+ if (!queryBinding || refreshing || isLoading) return;
739
+ const removed = locallyRemovedLifecycleIds;
740
+ if (removed.size === 0) return;
741
+ const visible = new Set(queryBinding.rows.map((row) => String(row.id)));
742
+ const retained = new Set([...removed].filter((id) => visible.has(id)));
743
+ if (retained.size !== removed.size) locallyRemovedLifecycleIds = retained;
744
+ });
694
745
  const rows = $derived(toContentListRows(sourceContents));
695
746
  const visibleRowIds = $derived(
696
747
  new Set(rows.filter((row) => row.identified).map((row) => String(row.id))),
@@ -835,12 +886,38 @@ function refreshQuery(): void {
835
886
  void queryBinding
836
887
  .refresh()
837
888
  .then((result) => {
838
- if (result !== undefined && activeQueryKey === key)
839
- resultNotices = readContentListQueryNotices(result);
889
+ if (result !== undefined && activeQueryKey === key) {
890
+ if (executedWorkflowQuery) {
891
+ settleWorkflowQueryResult(
892
+ result,
893
+ executedWorkflowQuery,
894
+ querySignature,
895
+ );
896
+ } else {
897
+ resultNotices = readContentListQueryNotices(result);
898
+ }
899
+ settleLifecycleResult(result, lifecycleQueryRequest, querySignature);
900
+ }
840
901
  })
841
902
  .catch(() => undefined);
842
903
  }
843
904
 
905
+ function requestLifecycleRefresh(): void {
906
+ if (!queryBinding?.refresh) return;
907
+ if (refreshing || isLoading) {
908
+ lifecycleRefreshPending = true;
909
+ return;
910
+ }
911
+ lifecycleRefreshPending = false;
912
+ refreshQuery();
913
+ }
914
+
915
+ $effect(() => {
916
+ if (!lifecycleRefreshPending || refreshing || isLoading) return;
917
+ lifecycleRefreshPending = false;
918
+ refreshQuery();
919
+ });
920
+
844
921
  function settleWorkflowQueryResult(
845
922
  result: unknown,
846
923
  request: ContentListDataQueryRequest,
@@ -870,6 +947,27 @@ function settleWorkflowQueryResult(
870
947
  settledWorkflowRevision = null;
871
948
  }
872
949
 
950
+ function settleLifecycleResult(
951
+ result: unknown,
952
+ request: ContentListDataQueryRequest | undefined,
953
+ signature: string,
954
+ ): void {
955
+ if (
956
+ !request ||
957
+ result === null ||
958
+ typeof result !== 'object' ||
959
+ Array.isArray(result)
960
+ ) {
961
+ settledLifecycleQuery = undefined;
962
+ settledLifecycleFingerprint = undefined;
963
+ return;
964
+ }
965
+ const fingerprint = (result as Record<string, unknown>).queryFingerprint;
966
+ settledLifecycleQuery = request;
967
+ settledLifecycleFingerprint =
968
+ typeof fingerprint === 'string' ? fingerprint : undefined;
969
+ settledSignature = signature;
970
+ }
873
971
  /**
874
972
  * A retry re-reads the same query, so its answer replaces the rendered rows —
875
973
  * and therefore has to replace the completeness flags that describe them too.
@@ -895,6 +993,7 @@ function retryQuery(): void {
895
993
  )
896
994
  return;
897
995
  settleWorkflowQueryResult(result, request, signature);
996
+ settleLifecycleResult(result, lifecycleQueryRequest, signature);
898
997
  })
899
998
  .catch(() => undefined);
900
999
  }
@@ -922,6 +1021,7 @@ const querySignature = $derived(
922
1021
  let executedSignature: string | undefined;
923
1022
  let executedWorkflowQuery: ContentListDataQueryRequest | null = null;
924
1023
  let publishedUrlSignature: string | undefined;
1024
+ let lifecycleQueryRequest: ContentListDataQueryRequest | undefined;
925
1025
 
926
1026
  $effect(() => {
927
1027
  const signature = querySignature;
@@ -968,6 +1068,7 @@ $effect(() => {
968
1068
  // flags, which the binding itself does not expose.
969
1069
  activeQueryKey = contentListQueryRequestKey(translated.request);
970
1070
  executedWorkflowQuery = translated.request;
1071
+ lifecycleQueryRequest = translated.request;
971
1072
  void queryBinding
972
1073
  .execute(translated.request)
973
1074
  .then((result) => {
@@ -977,6 +1078,7 @@ $effect(() => {
977
1078
  // may be clamped against them and workflows may bind to the exact
978
1079
  // settled request.
979
1080
  settleWorkflowQueryResult(result, translated.request, signature);
1081
+ settleLifecycleResult(result, translated.request, signature);
980
1082
  })
981
1083
  .catch(() => undefined);
982
1084
  }
@@ -1116,6 +1218,23 @@ const workflowQueuedJob = $derived(
1116
1218
  workflowQueuedJobs.find((job) => job.intent === workflowIntentSignature()) ??
1117
1219
  workflowQueuedJobs[0],
1118
1220
  );
1221
+ const lifecycleViewKey = $derived(
1222
+ JSON.stringify([
1223
+ lifecycleMode,
1224
+ querySignature,
1225
+ [...tableState.selectedRowIds].map(String).sort(),
1226
+ ]),
1227
+ );
1228
+ const lifecycleQuery = $derived(
1229
+ serverBacked && settledSignature === querySignature
1230
+ ? settledLifecycleQuery
1231
+ : undefined,
1232
+ );
1233
+ const lifecycleExactMatchingCount = $derived(
1234
+ serverBacked && settledSignature === querySignature
1235
+ ? contentListQueryExactTotal(queryBinding?.total)
1236
+ : queryRows.length,
1237
+ );
1119
1238
 
1120
1239
  /** Synthetic ids the adapter minted for rows that carry no durable identity. */
1121
1240
  const unidentifiedRowKeys = $derived(
@@ -1145,6 +1264,23 @@ $effect(() => {
1145
1264
  );
1146
1265
  });
1147
1266
 
1267
+ // Trash is a view, not an editable select value. Keep its status predicate
1268
+ // locked against URL restores and agent-driven filter commands.
1269
+ $effect(() => {
1270
+ if (lifecycleMode !== 'trash') return;
1271
+ if (
1272
+ isContentListFilterExactly(
1273
+ tableState,
1274
+ CONTENT_LIST_STATUS_FILTER_ID,
1275
+ 'deleted',
1276
+ )
1277
+ )
1278
+ return;
1279
+ untrack(() =>
1280
+ applyContentListFilter(controller, CONTENT_LIST_STATUS_FILTER_ID, 'deleted'),
1281
+ );
1282
+ });
1283
+
1148
1284
  /**
1149
1285
  * What each toolbar select may display, and whether it can display the live
1150
1286
  * predicate at all.
@@ -2044,7 +2180,29 @@ function handleFilter(columnId: string, value: string) {
2044
2180
  }
2045
2181
 
2046
2182
  function rowActions(row: ContentListRow) {
2047
- return contentListRowActions(row, { getViewHref });
2183
+ return contentListRowActions(row, {
2184
+ getViewHref,
2185
+ canEdit: lifecycleMode !== 'trash',
2186
+ canDelete: lifecycleMode !== 'trash' && lifecycle === undefined,
2187
+ });
2188
+ }
2189
+
2190
+ function handleLifecycleComplete(
2191
+ result: import('@happyvertical/smrt-ui/data').DataSurfaceActionResult,
2192
+ ) {
2193
+ const removed = new Set(locallyRemovedLifecycleIds);
2194
+ for (const outcome of readContentListLifecycleSummary(result).outcomes) {
2195
+ if (outcome.status === 'accepted') removed.add(String(outcome.rowId));
2196
+ }
2197
+ locallyRemovedLifecycleIds = removed;
2198
+ controller.dispatch({
2199
+ type: 'setSelectedRows',
2200
+ rowIds: reconcileContentListLifecycleSelection(
2201
+ tableState.selectedRowIds,
2202
+ result,
2203
+ ),
2204
+ });
2205
+ requestLifecycleRefresh();
2048
2206
  }
2049
2207
 
2050
2208
  function viewHref(row: ContentListRow): string | null {
@@ -2119,7 +2277,11 @@ const tableColumns: DataTableColumn<ContentListRow>[] = $derived([
2119
2277
  </script>
2120
2278
 
2121
2279
  {#snippet tableEmptyState()}
2122
- <p class="table-empty-state">{t(M['content.content_list.empty'])}</p>
2280
+ <p class="table-empty-state">
2281
+ {lifecycleMode === 'trash'
2282
+ ? t(M['content.content_list.trash_empty'])
2283
+ : t(M['content.content_list.empty'])}
2284
+ </p>
2123
2285
  {/snippet}
2124
2286
 
2125
2287
  {#snippet selectHeader()}
@@ -2259,6 +2421,7 @@ const tableColumns: DataTableColumn<ContentListRow>[] = $derived([
2259
2421
  </Select>
2260
2422
  {/if}
2261
2423
 
2424
+ {#if lifecycleMode !== 'trash'}
2262
2425
  <Select
2263
2426
  aria-label={t(M['content.content_list.filter_status'])}
2264
2427
  value={selectedStatus}
@@ -2281,6 +2444,7 @@ const tableColumns: DataTableColumn<ContentListRow>[] = $derived([
2281
2444
  </option>
2282
2445
  {/if}
2283
2446
  </Select>
2447
+ {/if}
2284
2448
 
2285
2449
  {#if savedViews}
2286
2450
  <div class="saved-views" role="group" aria-label={t(M['content.content_list.saved_views'])}>
@@ -2560,6 +2724,19 @@ const tableColumns: DataTableColumn<ContentListRow>[] = $derived([
2560
2724
  </div>
2561
2725
  {/if}
2562
2726
 
2727
+ {#if lifecycle}
2728
+ <ContentListLifecyclePanel
2729
+ binding={lifecycle}
2730
+ mode={lifecycleMode}
2731
+ selectedRowIds={tableState.selectedRowIds}
2732
+ query={lifecycleQuery}
2733
+ queryFingerprint={settledLifecycleFingerprint}
2734
+ exactMatchingCount={lifecycleExactMatchingCount}
2735
+ viewKey={lifecycleViewKey}
2736
+ oncomplete={handleLifecycleComplete}
2737
+ />
2738
+ {/if}
2739
+
2563
2740
  {#if workflows && (selectedCount > 0 || workflowError || workflowResult)}
2564
2741
  <section class="content-workflows" aria-label={t(M['content.content_list.bulk_workflows'])}>
2565
2742
  <Select
@@ -2703,7 +2880,9 @@ const tableColumns: DataTableColumn<ContentListRow>[] = $derived([
2703
2880
  </div>
2704
2881
  {:else if pageRows.length === 0}
2705
2882
  <div class="state-panel empty-state">
2706
- {t(M['content.content_list.empty'])}
2883
+ {lifecycleMode === 'trash'
2884
+ ? t(M['content.content_list.trash_empty'])
2885
+ : t(M['content.content_list.empty'])}
2707
2886
  </div>
2708
2887
  {:else if viewMode === 'detailed'}
2709
2888
  <div class="content-detailed">
@@ -25,12 +25,14 @@ export interface ContentListUrlStateBinding {
25
25
  }
26
26
  import type { Snippet } from 'svelte';
27
27
  import type { ContentData } from '../../mock-smrt-client.js';
28
+ import type { ContentListLifecycleBinding } from '../content-list-lifecycle.js';
28
29
  import { type ContentListDataSurface, type ContentListViewMode } from '../content-list-controller.js';
29
30
  import { type ContentListQuerySource } from '../content-list-query.js';
30
31
  import { type ContentListJobBinding } from '../content-list-runtime.js';
31
32
  import { type ContentListSavedViewStore } from '../content-list-saved-views.js';
32
33
  import { type ContentListWorkflowBinding } from '../content-list-workflows.js';
33
34
  interface Props {
35
+ /** Base URL for content API requests. */
34
36
  apiBaseUrl?: string;
35
37
  /**
36
38
  * Client-side rows. Ignored when `query` is supplied — the server then owns
@@ -38,12 +40,19 @@ interface Props {
38
40
  * source of truth.
39
41
  */
40
42
  contents?: ContentData[];
43
+ /** Filter content by this type; no filtering applied when omitted. */
41
44
  type?: string;
45
+ /** Initial view layout mode for the content list. */
42
46
  defaultViewMode?: ContentListViewMode;
47
+ /** Invoked when the user requests to edit a content item. */
43
48
  onEdit: (content: ContentData) => void;
49
+ /** Invoked when the user requests to delete a content item. */
44
50
  onDelete: (content: ContentData) => void;
51
+ /** Invoked when the user requests to create new content. */
45
52
  onAdd: () => void;
53
+ /** Optional UI controls rendered above the content list. */
46
54
  controls?: Snippet;
55
+ /** Returns a URL to view the published content; null hides the view link. */
47
56
  getViewHref?: (content: ContentData) => string | null;
48
57
  /** Announced uniformly by every presentation; #2455 extends it. */
49
58
  loading?: boolean;
@@ -68,6 +77,10 @@ interface Props {
68
77
  jobs?: ContentListJobBinding;
69
78
  /** Opt-in, authenticated preview/apply client for bulk workflows (#2453). */
70
79
  workflows?: ContentListWorkflowBinding;
80
+ /** Opt-in, server-authoritative trash/restore/permanent-delete lifecycle. */
81
+ lifecycle?: ContentListLifecycleBinding;
82
+ /** Locks the status query to deleted content and exposes trash actions. */
83
+ lifecycleMode?: 'active' | 'trash';
71
84
  }
72
85
  declare const ContentList: import("svelte").Component<Props, {}, "">;
73
86
  type ContentList = ReturnType<typeof ContentList>;
@@ -1 +1 @@
1
- {"version":3,"file":"ContentList.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/ContentList.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,IAAI,6BAA6B,EAAE,MAAM,6BAA6B,CAAC;AACvG,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAE/E;;;;;;;GAOG;AACH,MAAM,WAAW,0BAA0B;IACzC;;;OAGG;IACH,MAAM,CAAC,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CAAC;IACzC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CACT,MAAM,EAAE,eAAe,EACvB,KAAK,EAAE,6BAA6B,KACjC,IAAI,CAAC;IACV,uEAAuE;IACvE,OAAO,CAAC,EAAE,0BAA0B,CAAC;CACtC;AAcD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EASL,KAAK,sBAAsB,EAE3B,KAAK,mBAAmB,EAiBzB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAOL,KAAK,sBAAsB,EAU5B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAGL,KAAK,qBAAqB,EAG3B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAEL,KAAK,yBAAyB,EAG/B,MAAM,gCAAgC,CAAC;AACxC,OAAO,EACL,KAAK,0BAA0B,EAOhC,MAAM,8BAA8B,CAAC;AAgBtC,UAAU,KAAK;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,mBAAmB,CAAC;IACtC,MAAM,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IACvC,QAAQ,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IACzC,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,MAAM,GAAG,IAAI,CAAC;IACtD,mEAAmE;IACnE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,+CAA+C;IAC/C,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,4EAA4E;IAC5E,WAAW,CAAC,EAAE,sBAAsB,CAAC;IACrC;;;;;OAKG;IACH,KAAK,CAAC,EAAE,sBAAsB,CAAC;IAC/B,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,0BAA0B,CAAC;IACtC,oFAAoF;IACpF,UAAU,CAAC,EAAE,yBAAyB,CAAC;IACvC,6EAA6E;IAC7E,IAAI,CAAC,EAAE,qBAAqB,CAAC;IAC7B,6EAA6E;IAC7E,SAAS,CAAC,EAAE,0BAA0B,CAAC;CACxC;AAq9ED,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"ContentList.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/ContentList.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,IAAI,6BAA6B,EAAE,MAAM,6BAA6B,CAAC;AACvG,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAE/E;;;;;;;GAOG;AACH,MAAM,WAAW,0BAA0B;IACzC;;;OAGG;IACH,MAAM,CAAC,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CAAC;IACzC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CACT,MAAM,EAAE,eAAe,EACvB,KAAK,EAAE,6BAA6B,KACjC,IAAI,CAAC;IACV,uEAAuE;IACvE,OAAO,CAAC,EAAE,0BAA0B,CAAC;CACtC;AAcD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,8BAA8B,CAAC;AAKhF,OAAO,EASL,KAAK,sBAAsB,EAE3B,KAAK,mBAAmB,EAiBzB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAOL,KAAK,sBAAsB,EAU5B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAGL,KAAK,qBAAqB,EAG3B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAEL,KAAK,yBAAyB,EAG/B,MAAM,gCAAgC,CAAC;AACxC,OAAO,EACL,KAAK,0BAA0B,EAOhC,MAAM,8BAA8B,CAAC;AAiBtC,UAAU,KAAK;IACb,yCAAyC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC;IACzB,sEAAsE;IACtE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,eAAe,CAAC,EAAE,mBAAmB,CAAC;IACtC,6DAA6D;IAC7D,MAAM,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IACvC,+DAA+D;IAC/D,QAAQ,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IACzC,4DAA4D;IAC5D,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,MAAM,GAAG,IAAI,CAAC;IACtD,mEAAmE;IACnE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,+CAA+C;IAC/C,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,4EAA4E;IAC5E,WAAW,CAAC,EAAE,sBAAsB,CAAC;IACrC;;;;;OAKG;IACH,KAAK,CAAC,EAAE,sBAAsB,CAAC;IAC/B,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,0BAA0B,CAAC;IACtC,oFAAoF;IACpF,UAAU,CAAC,EAAE,yBAAyB,CAAC;IACvC,6EAA6E;IAC7E,IAAI,CAAC,EAAE,qBAAqB,CAAC;IAC7B,6EAA6E;IAC7E,SAAS,CAAC,EAAE,0BAA0B,CAAC;IACvC,6EAA6E;IAC7E,SAAS,CAAC,EAAE,2BAA2B,CAAC;IACxC,2EAA2E;IAC3E,aAAa,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;CACpC;AAgnFD,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}