@happyvertical/smrt-content 0.44.0 → 0.45.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 (52) hide show
  1. package/agents/content-list.md +93 -14
  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-B4cHfrig.js → content-query-BV41W2Dq.js} +6 -6
  5. package/dist/chunks/{content-query-B4cHfrig.js.map → content-query-BV41W2Dq.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-OEYQVrUf.js → content-versions-dZSBU8UF.js} +6 -3
  9. package/dist/chunks/content-versions-dZSBU8UF.js.map +1 -0
  10. package/dist/chunks/{contents-BrGxWH3s.js → contents-Ba7JHu2K.js} +2 -2
  11. package/dist/chunks/{contents-BrGxWH3s.js.map → contents-Ba7JHu2K.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 +2268 -4345
  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-data-surface.d.ts +29 -0
  20. package/dist/server/content-list-data-surface.d.ts.map +1 -0
  21. package/dist/server/content-list-lifecycle.d.ts +20 -0
  22. package/dist/server/content-list-lifecycle.d.ts.map +1 -0
  23. package/dist/server.d.ts +2 -0
  24. package/dist/server.d.ts.map +1 -1
  25. package/dist/server.js +247 -25
  26. package/dist/server.js.map +1 -1
  27. package/dist/smrt-knowledge.json +4291 -2510
  28. package/dist/svelte/components/ContentList.svelte +538 -40
  29. package/dist/svelte/components/ContentList.svelte.d.ts +6 -1
  30. package/dist/svelte/components/ContentList.svelte.d.ts.map +1 -1
  31. package/dist/svelte/components/ContentListLifecyclePanel.svelte +387 -0
  32. package/dist/svelte/components/ContentListLifecyclePanel.svelte.d.ts +17 -0
  33. package/dist/svelte/components/ContentListLifecyclePanel.svelte.d.ts.map +1 -0
  34. package/dist/svelte/content-list-controller.d.ts +10 -0
  35. package/dist/svelte/content-list-controller.d.ts.map +1 -1
  36. package/dist/svelte/content-list-controller.js +111 -26
  37. package/dist/svelte/content-list-data-surface.d.ts +45 -0
  38. package/dist/svelte/content-list-data-surface.d.ts.map +1 -0
  39. package/dist/svelte/content-list-data-surface.js +196 -0
  40. package/dist/svelte/content-list-lifecycle.d.ts +132 -0
  41. package/dist/svelte/content-list-lifecycle.d.ts.map +1 -0
  42. package/dist/svelte/content-list-lifecycle.js +439 -0
  43. package/dist/svelte/i18n.contribution.d.ts +27 -0
  44. package/dist/svelte/i18n.contribution.d.ts.map +1 -1
  45. package/dist/svelte/i18n.contribution.js +27 -0
  46. package/dist/svelte/index.d.ts +2 -0
  47. package/dist/svelte/index.d.ts.map +1 -1
  48. package/dist/svelte/index.js +2 -0
  49. package/package.json +15 -15
  50. package/dist/chunks/content-corrections-BxfA3tW1.js.map +0 -1
  51. package/dist/chunks/content-reviews-BWrYEQc6.js.map +0 -1
  52. package/dist/chunks/content-versions-OEYQVrUf.js.map +0 -1
@@ -101,8 +101,25 @@ const DEFAULT_SURFACE_LIMITS = {
101
101
  maxQueryBytes: 50_000,
102
102
  maxSelectionSize: 200,
103
103
  };
104
+ /** Operators shared by the local evaluator and server query translator. */
105
+ const CONTENT_LIST_FILTER_OPERATORS = [
106
+ 'equals',
107
+ 'notEquals',
108
+ 'contains',
109
+ 'startsWith',
110
+ 'endsWith',
111
+ 'in',
112
+ 'notIn',
113
+ 'gt',
114
+ 'gte',
115
+ 'lt',
116
+ 'lte',
117
+ 'isNull',
118
+ 'isNotNull',
119
+ ];
104
120
  /** Local table commands a mounted content list accepts from a data surface. */
105
121
  const CONTENT_LIST_CONTROLS = [
122
+ { id: 'set-view', label: 'Change list presentation' },
106
123
  { id: 'set-search', label: 'Search contents' },
107
124
  { id: 'set-filters', label: 'Filter contents' },
108
125
  { id: 'set-sorting', label: 'Sort contents' },
@@ -112,6 +129,8 @@ const CONTENT_LIST_CONTROLS = [
112
129
  { id: 'set-selected-rows', label: 'Replace the row selection' },
113
130
  { id: 'toggle-row-selection', label: 'Toggle one row selection' },
114
131
  { id: 'reset', label: 'Reset the list view' },
132
+ { id: 'refresh', label: 'Refresh contents' },
133
+ { id: 'retry', label: 'Retry the content query' },
115
134
  { id: 'focus', label: 'Focus the list' },
116
135
  { id: 'reveal', label: 'Scroll the list into view' },
117
136
  { id: 'highlight', label: 'Highlight the list' },
@@ -828,14 +847,11 @@ export function contentListRowActions(row, options = {}) {
828
847
  actions.push('delete');
829
848
  return actions;
830
849
  }
831
- function surfaceColumn(id, labels, order, column) {
832
- const capabilities = [
833
- 'read',
834
- 'filter',
835
- 'sort',
836
- 'project',
837
- ];
838
- if (column.searchable !== false)
850
+ function surfaceColumn(id, labels, order, column, queryable = true) {
851
+ const capabilities = ['read'];
852
+ if (queryable)
853
+ capabilities.push('filter', 'sort', 'project');
854
+ if (queryable && column.searchable !== false)
839
855
  capabilities.push('search');
840
856
  const fieldName = CONTENT_LIST_COLUMN_FIELD_NAMES[id];
841
857
  return {
@@ -846,6 +862,21 @@ function surfaceColumn(id, labels, order, column) {
846
862
  visibility: 'basic',
847
863
  order,
848
864
  role: column.role === 'status' ? 'status' : 'data',
865
+ readable: true,
866
+ ...(queryable
867
+ ? {
868
+ operators: {
869
+ ...(column.searchable !== false ? { search: ['contains'] } : {}),
870
+ filter: [...CONTENT_LIST_FILTER_OPERATORS],
871
+ sort: ['asc', 'desc'],
872
+ },
873
+ ...(column.searchable !== false
874
+ ? { searchOperators: ['contains'] }
875
+ : {}),
876
+ filterOperators: [...CONTENT_LIST_FILTER_OPERATORS],
877
+ sortOperators: ['asc', 'desc'],
878
+ }
879
+ : {}),
849
880
  };
850
881
  }
851
882
  /**
@@ -861,7 +892,7 @@ export function buildContentListSurfaceDescriptor(options = {}) {
861
892
  if (!column) {
862
893
  throw new Error(`Missing content list column definition: ${id}`);
863
894
  }
864
- return surfaceColumn(id, columnLabels, index, column);
895
+ return surfaceColumn(id, columnLabels, index, column, !(options.serverBacked && id === 'site'));
865
896
  });
866
897
  // The row-key column must be declared even though the table renders identity
867
898
  // through Svelte keys rather than a visible column.
@@ -871,11 +902,15 @@ export function buildContentListSurfaceDescriptor(options = {}) {
871
902
  capabilities: ['read', 'project'],
872
903
  fieldName: CONTENT_LIST_ROW_KEY,
873
904
  role: 'row-key',
905
+ readable: true,
874
906
  };
875
- const columnIds = visibleColumns.map((column) => column.id);
907
+ const columnIds = visibleColumns
908
+ .filter((column) => column.capabilities.includes('project'))
909
+ .map((column) => column.id);
876
910
  const searchableColumnIds = visibleColumns
877
911
  .filter((column) => column.capabilities.includes('search'))
878
912
  .map((column) => column.id);
913
+ const lifecycleMode = options.lifecycleMode ?? 'active';
879
914
  const actions = [
880
915
  {
881
916
  id: 'view',
@@ -883,20 +918,65 @@ export function buildContentListSurfaceDescriptor(options = {}) {
883
918
  selectionScopes: ['explicit-ids'],
884
919
  columnIds: ['title'],
885
920
  },
886
- {
887
- id: 'edit',
888
- label: actionLabels.edit ?? DEFAULT_ACTION_LABELS.edit,
889
- selectionScopes: ['explicit-ids'],
890
- },
891
- {
892
- id: 'delete',
893
- label: actionLabels.delete ?? DEFAULT_ACTION_LABELS.delete,
894
- sensitivity: 'sensitive',
895
- selectionScopes: ['explicit-ids', 'current-page'],
896
- requiresConfirmation: true,
897
- },
921
+ ...(lifecycleMode === 'active'
922
+ ? [
923
+ {
924
+ id: 'edit',
925
+ label: actionLabels.edit ?? DEFAULT_ACTION_LABELS.edit,
926
+ selectionScopes: ['explicit-ids'],
927
+ },
928
+ options.lifecycle
929
+ ? {
930
+ id: 'move-to-trash',
931
+ label: 'Move to trash',
932
+ sensitivity: 'sensitive',
933
+ selectionScopes: [
934
+ 'explicit-ids',
935
+ 'current-page',
936
+ 'all-matching',
937
+ ],
938
+ requiresConfirmation: true,
939
+ }
940
+ : {
941
+ id: 'delete',
942
+ label: actionLabels.delete ?? DEFAULT_ACTION_LABELS.delete,
943
+ sensitivity: 'sensitive',
944
+ selectionScopes: [
945
+ 'explicit-ids',
946
+ 'current-page',
947
+ ],
948
+ requiresConfirmation: true,
949
+ },
950
+ ]
951
+ : options.lifecycle
952
+ ? [
953
+ {
954
+ id: 'restore',
955
+ label: 'Restore',
956
+ sensitivity: 'sensitive',
957
+ selectionScopes: [
958
+ 'explicit-ids',
959
+ 'current-page',
960
+ 'all-matching',
961
+ ],
962
+ requiresConfirmation: true,
963
+ },
964
+ {
965
+ id: 'permanent-delete',
966
+ label: 'Delete permanently',
967
+ sensitivity: 'sensitive',
968
+ selectionScopes: [
969
+ 'explicit-ids',
970
+ 'current-page',
971
+ 'all-matching',
972
+ ],
973
+ requiresConfirmation: true,
974
+ },
975
+ ]
976
+ : []),
898
977
  ...(options.includeWorkflows
899
- ? CONTENT_LIST_WORKFLOW_OPTIONS.map((workflow) => ({
978
+ ? CONTENT_LIST_WORKFLOW_OPTIONS.filter((workflow) => !(options.lifecycle && workflow.id === 'move-to-trash') &&
979
+ !(lifecycleMode === 'trash' && workflow.id === 'restore')).map((workflow) => ({
900
980
  id: workflow.id,
901
981
  label: workflow.label,
902
982
  sensitivity: workflow.sensitivity,
@@ -925,10 +1005,15 @@ export function buildContentListSurfaceDescriptor(options = {}) {
925
1005
  modes: ['rows', 'count'],
926
1006
  projectableColumnIds: [CONTENT_LIST_ROW_KEY, ...columnIds],
927
1007
  searchableColumnIds,
928
- filterableColumnIds: columnIds,
929
- sortableColumnIds: columnIds,
1008
+ filterableColumnIds: visibleColumns
1009
+ .filter((column) => column.capabilities.includes('filter'))
1010
+ .map((column) => column.id),
1011
+ sortableColumnIds: visibleColumns
1012
+ .filter((column) => column.capabilities.includes('sort'))
1013
+ .map((column) => column.id),
930
1014
  },
931
- controls: CONTENT_LIST_CONTROLS.map((control) => ({ ...control })),
1015
+ controls: CONTENT_LIST_CONTROLS.filter((control) => (control.id !== 'refresh' || options.refresh === true) &&
1016
+ (control.id !== 'retry' || options.retry === true)).map((control) => ({ ...control })),
932
1017
  actions,
933
1018
  limits: { ...DEFAULT_SURFACE_LIMITS, ...options.limits },
934
1019
  };
@@ -0,0 +1,45 @@
1
+ /** ContentList-owned mounted data surface, shared by every presentation. */
2
+ import { type DataSurfaceDescriptor, type DataSurfaceRegistry, type DataTableCommand, type DataTableController } from '@happyvertical/smrt-ui/data';
3
+ import type { ContentListViewMode } from './content-list-controller.js';
4
+ export interface ContentListSurfaceFreshness {
5
+ stale: boolean;
6
+ refreshing: boolean;
7
+ offline: boolean;
8
+ lastUpdated: string | null;
9
+ truncated: boolean;
10
+ warnings: readonly string[];
11
+ }
12
+ export interface ContentListSurfaceContext {
13
+ viewMode: ContentListViewMode;
14
+ queryFingerprint: string | null;
15
+ totalRows: number | null;
16
+ freshness: ContentListSurfaceFreshness;
17
+ }
18
+ export interface ContentListSurfaceRegistrationOptions {
19
+ registry: DataSurfaceRegistry;
20
+ descriptor: DataSurfaceDescriptor;
21
+ controller: DataTableController;
22
+ context: ContentListSurfaceContext;
23
+ /** Carries the mounted identity's monotonic revision across re-registration. */
24
+ initialRevision?: number;
25
+ onRevision?: (revision: number) => void;
26
+ /**
27
+ * ContentList-owned constraints that must hold before a visible command is
28
+ * acknowledged. Returning false denies the command rather than publishing a
29
+ * transient state which a later component effect would correct.
30
+ */
31
+ acceptsTableCommand?: (command: DataTableCommand) => boolean;
32
+ setViewMode(viewMode: ContentListViewMode): void;
33
+ refresh?: () => boolean | Promise<boolean>;
34
+ retry?: () => boolean | Promise<boolean>;
35
+ focus?: () => void;
36
+ reveal?: () => void;
37
+ highlight?: () => void;
38
+ }
39
+ export interface ContentListSurfaceHandle {
40
+ update(context: ContentListSurfaceContext): void;
41
+ destroy(): void;
42
+ }
43
+ /** Register one stable ContentList identity independently of its view mode. */
44
+ export declare function registerContentListDataSurface(options: ContentListSurfaceRegistrationOptions): ContentListSurfaceHandle;
45
+ //# sourceMappingURL=content-list-data-surface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-list-data-surface.d.ts","sourceRoot":"","sources":["../../src/svelte/content-list-data-surface.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAE5E,OAAO,EACL,KAAK,qBAAqB,EAG1B,KAAK,mBAAmB,EAGxB,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EAIzB,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAExE,MAAM,WAAW,2BAA2B;IAC1C,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,2BAA2B,CAAC;CACxC;AAED,MAAM,WAAW,qCAAqC;IACpD,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,UAAU,EAAE,qBAAqB,CAAC;IAClC,UAAU,EAAE,mBAAmB,CAAC;IAChC,OAAO,EAAE,yBAAyB,CAAC;IACnC,gFAAgF;IAChF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,OAAO,CAAC;IAC7D,WAAW,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACjD,OAAO,CAAC,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3C,KAAK,CAAC,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACzC,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,CAAC,OAAO,EAAE,yBAAyB,GAAG,IAAI,CAAC;IACjD,OAAO,IAAI,IAAI,CAAC;CACjB;AA8HD,+EAA+E;AAC/E,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,qCAAqC,GAC7C,wBAAwB,CAyG1B"}
@@ -0,0 +1,196 @@
1
+ /** ContentList-owned mounted data surface, shared by every presentation. */
2
+ import { dataTableCommandFromDataSurfaceCommand, dataTableRowIdKey, } from '@happyvertical/smrt-ui/data';
3
+ const VIEWS = new Set(['grid', 'detailed', 'compact']);
4
+ const revisionsByRegistry = new WeakMap();
5
+ function identityKey(descriptor) {
6
+ const { identity } = descriptor;
7
+ return JSON.stringify([
8
+ identity.kind,
9
+ identity.surfaceId,
10
+ identity.subject?.type,
11
+ identity.subject?.id,
12
+ ]);
13
+ }
14
+ function selectionReference(selection) {
15
+ if (selection.scope === 'page')
16
+ return { scope: 'current-page' };
17
+ if (selection.scope === 'allMatching') {
18
+ return {
19
+ scope: 'all-matching',
20
+ queryFingerprint: selection.queryFingerprint,
21
+ };
22
+ }
23
+ return selection.rowIds.length > 0
24
+ ? { scope: 'explicit-ids', rowIds: selection.rowIds }
25
+ : null;
26
+ }
27
+ function contextState(context) {
28
+ return {
29
+ viewMode: context.viewMode,
30
+ queryFingerprint: context.queryFingerprint,
31
+ totalRows: context.totalRows,
32
+ freshness: {
33
+ stale: context.freshness.stale,
34
+ refreshing: context.freshness.refreshing,
35
+ offline: context.freshness.offline,
36
+ lastUpdated: context.freshness.lastUpdated,
37
+ truncated: context.freshness.truncated,
38
+ warnings: [...context.freshness.warnings],
39
+ },
40
+ };
41
+ }
42
+ function payloadObject(value) {
43
+ return value && !Array.isArray(value) && typeof value === 'object'
44
+ ? value
45
+ : undefined;
46
+ }
47
+ function allowsFilterOperator(column, operator) {
48
+ if (!column)
49
+ return false;
50
+ const canonical = column.operators?.filter;
51
+ const alias = column.filterOperators;
52
+ if (canonical && alias)
53
+ return canonical.includes(operator) && alias.includes(operator);
54
+ return canonical?.includes(operator) ?? alias?.includes(operator) ?? false;
55
+ }
56
+ function commandAllowed(command, descriptor, controller) {
57
+ const readable = new Set(descriptor.columns
58
+ .filter((column) => column.capabilities.includes('read'))
59
+ .map((column) => column.id));
60
+ const filterable = new Set(descriptor.query.filterableColumnIds ?? []);
61
+ const sortable = new Set(descriptor.query.sortableColumnIds ?? []);
62
+ switch (command.type) {
63
+ case 'setFilters':
64
+ return command.filters.every((filter) => {
65
+ const column = descriptor.columns.find((candidate) => candidate.id === filter.columnId);
66
+ return (filterable.has(filter.columnId) &&
67
+ allowsFilterOperator(column, filter.operator));
68
+ });
69
+ case 'setSorting':
70
+ return command.sorting.every((sort) => sortable.has(sort.columnId));
71
+ case 'toggleSorting':
72
+ return sortable.has(command.columnId);
73
+ case 'setSelectedRows':
74
+ return command.rowIds.length <= descriptor.limits.maxSelectionSize;
75
+ case 'toggleRowSelection': {
76
+ const selection = controller.snapshot().state.selection;
77
+ if (selection.scope === 'allMatching')
78
+ return false;
79
+ const selected = selection.rowIds.some((rowId) => dataTableRowIdKey(rowId) === dataTableRowIdKey(command.rowId));
80
+ return (selected || selection.rowIds.length < descriptor.limits.maxSelectionSize);
81
+ }
82
+ case 'setPageSize':
83
+ return (command.pageSize === null ||
84
+ (Number.isSafeInteger(command.pageSize) && command.pageSize > 0));
85
+ case 'setColumnOrder':
86
+ return command.columnIds.every((columnId) => readable.has(columnId));
87
+ case 'setColumnVisibility':
88
+ return command.columns.every((column) => readable.has(column.columnId));
89
+ default:
90
+ return true;
91
+ }
92
+ }
93
+ /** Register one stable ContentList identity independently of its view mode. */
94
+ export function registerContentListDataSurface(options) {
95
+ let revisions = revisionsByRegistry.get(options.registry);
96
+ if (!revisions) {
97
+ revisions = new Map();
98
+ revisionsByRegistry.set(options.registry, revisions);
99
+ }
100
+ const key = identityKey(options.descriptor);
101
+ const previousRevision = revisions.get(key);
102
+ let revision = Math.max(options.initialRevision ?? 0, previousRevision === undefined ? 0 : previousRevision + 1);
103
+ revisions.set(key, revision);
104
+ const advanceRevision = () => {
105
+ revision += 1;
106
+ revisions.set(key, revision);
107
+ options.onRevision?.(revision);
108
+ };
109
+ let context = options.context;
110
+ let contextSignature = JSON.stringify(contextState(context));
111
+ const updateContext = (next) => {
112
+ const signature = JSON.stringify(contextState(next));
113
+ if (signature !== contextSignature) {
114
+ advanceRevision();
115
+ contextSignature = signature;
116
+ }
117
+ context = next;
118
+ };
119
+ const unsubscribe = options.controller.subscribe((transition) => {
120
+ if (transition.changed)
121
+ advanceRevision();
122
+ });
123
+ const unregister = options.registry.register({
124
+ descriptor: options.descriptor,
125
+ getSnapshot: () => {
126
+ const table = options.controller.snapshot();
127
+ return {
128
+ revision,
129
+ state: {
130
+ ...contextState(context),
131
+ table: table,
132
+ },
133
+ selection: selectionReference(table.state.selection),
134
+ };
135
+ },
136
+ execute: async (command) => {
137
+ const tableCommand = dataTableCommandFromDataSurfaceCommand(command);
138
+ if (tableCommand) {
139
+ if (!commandAllowed(tableCommand, options.descriptor, options.controller) ||
140
+ options.acceptsTableCommand?.(tableCommand) === false)
141
+ return { ok: false };
142
+ options.controller.dispatch(tableCommand);
143
+ return;
144
+ }
145
+ switch (command.controlId) {
146
+ case 'set-view': {
147
+ const view = payloadObject(command.payload)?.view;
148
+ if (typeof view !== 'string' ||
149
+ !VIEWS.has(view))
150
+ return { ok: false };
151
+ const viewMode = view;
152
+ updateContext({ ...context, viewMode });
153
+ options.setViewMode(viewMode);
154
+ return;
155
+ }
156
+ case 'refresh':
157
+ if (!options.refresh)
158
+ return { ok: false };
159
+ if ((await options.refresh()) === false)
160
+ return { ok: false };
161
+ return;
162
+ case 'retry':
163
+ if (!options.retry)
164
+ return { ok: false };
165
+ if ((await options.retry()) === false)
166
+ return { ok: false };
167
+ return;
168
+ case 'focus':
169
+ if (!options.focus)
170
+ return { ok: false };
171
+ options.focus();
172
+ return;
173
+ case 'reveal':
174
+ if (!options.reveal)
175
+ return { ok: false };
176
+ options.reveal();
177
+ return;
178
+ case 'highlight':
179
+ if (!options.highlight)
180
+ return { ok: false };
181
+ options.highlight();
182
+ return;
183
+ default:
184
+ return { ok: false };
185
+ }
186
+ },
187
+ });
188
+ options.onRevision?.(revision);
189
+ return {
190
+ update: updateContext,
191
+ destroy() {
192
+ unsubscribe();
193
+ unregister();
194
+ },
195
+ };
196
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Browser-safe trash, restore, and permanent-delete orchestration for
3
+ * ContentList (#2454).
4
+ *
5
+ * Lifecycle authority stays on the server. The browser supplies a selection
6
+ * hint and expected count, then renders the server's resolved preview. Apply
7
+ * must reuse that preview's opaque confirmation token and authority-bound
8
+ * selection while using a fresh request id for each transport attempt; a
9
+ * changed query, count, permission, row revision, or eligibility is therefore
10
+ * rejected by the principal-bound server adapter rather than being guessed
11
+ * from stale list rows.
12
+ */
13
+ import type { DataSurfaceActionResult, DataSurfaceIdentity, DataSurfaceJsonValue, DataSurfaceSelectionReference } from '@happyvertical/smrt-ui/data';
14
+ import type { ContentListDataQueryRequest } from './content-list-query.js';
15
+ export declare const CONTENT_LIST_LIFECYCLE_ACTION_IDS: readonly ["move-to-trash", "restore", "permanent-delete"];
16
+ export type ContentListLifecycleActionId = (typeof CONTENT_LIST_LIFECYCLE_ACTION_IDS)[number];
17
+ export type ContentListRestoreStatus = 'draft' | 'review' | 'published';
18
+ export interface ContentListLifecycleTarget {
19
+ /** Canonical server query. Required for all-matching selections. */
20
+ query?: ContentListDataQueryRequest;
21
+ /** Count visible when the operator requested the preview. */
22
+ expectedCount: number;
23
+ /** Operator-entered server preview count, sent only on permanent-delete apply. */
24
+ confirmedCount?: number;
25
+ }
26
+ export interface ContentListLifecycleRequest {
27
+ version: 1;
28
+ requestId: string;
29
+ identity: DataSurfaceIdentity;
30
+ actionId: ContentListLifecycleActionId;
31
+ phase: 'preview' | 'apply';
32
+ selection: DataSurfaceSelectionReference;
33
+ payload?: DataSurfaceJsonValue;
34
+ expectedRevision: number;
35
+ target: ContentListLifecycleTarget;
36
+ confirmationToken?: string;
37
+ idempotencyKey?: string;
38
+ }
39
+ export interface ContentListLifecycleClient {
40
+ preview(request: ContentListLifecycleRequest): Promise<DataSurfaceActionResult>;
41
+ apply(request: ContentListLifecycleRequest): Promise<DataSurfaceActionResult>;
42
+ }
43
+ export interface ContentListLifecycleBinding {
44
+ client: ContentListLifecycleClient;
45
+ /** Server-owned surface revision. Never derived from list rows. */
46
+ revision?: number;
47
+ /** Override the default ContentList identity. */
48
+ identity?: DataSurfaceIdentity;
49
+ /** Server action-selection cap. Defaults to 200 and fails closed in the UI. */
50
+ maxSelectionSize?: number;
51
+ }
52
+ export interface ContentListLifecycleTransportOptions {
53
+ apiBaseUrl?: string;
54
+ path?: string;
55
+ fetch?: typeof globalThis.fetch;
56
+ headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
57
+ credentials?: RequestCredentials;
58
+ }
59
+ export type ContentListLifecycleTransportErrorReason = 'network_failure' | 'invalid_json' | 'http_error' | 'invalid_result';
60
+ export declare class ContentListLifecycleTransportError extends Error {
61
+ readonly status?: number;
62
+ readonly reason: ContentListLifecycleTransportErrorReason;
63
+ constructor(message: string, status?: number, reason?: ContentListLifecycleTransportErrorReason);
64
+ }
65
+ export interface ContentListLifecycleOutcome {
66
+ rowId: string | number;
67
+ status: 'accepted' | 'skipped' | 'failed';
68
+ reason?: string;
69
+ /** Canonical runtime subtype, for example a Document or Mirror. */
70
+ resourceType?: string;
71
+ /** Canonical resource id used by the server mutation. */
72
+ resourceId?: string;
73
+ }
74
+ export interface ContentListLifecycleSummary {
75
+ resolvedCount: number;
76
+ accepted: number;
77
+ skipped: number;
78
+ failed: number;
79
+ outcomes: ContentListLifecycleOutcome[];
80
+ representativeLabels: string[];
81
+ expiresAt?: number;
82
+ /** Correlates the visible result with principal audit metadata. */
83
+ auditReference: string;
84
+ }
85
+ export type ContentListLifecycleControllerStatus = 'idle' | 'previewing' | 'ready' | 'applying' | 'succeeded' | 'failed';
86
+ export interface ContentListLifecycleSnapshot {
87
+ status: ContentListLifecycleControllerStatus;
88
+ actionId?: ContentListLifecycleActionId;
89
+ selection?: DataSurfaceSelectionReference;
90
+ summary?: ContentListLifecycleSummary;
91
+ result?: DataSurfaceActionResult;
92
+ error?: string;
93
+ /** True when the only safe recovery is another server preview. */
94
+ renewalRequired: boolean;
95
+ /** True while an ambiguous apply must replay its exact authority envelope. */
96
+ replayRequired?: boolean;
97
+ }
98
+ export interface ContentListLifecyclePreviewInput {
99
+ actionId: ContentListLifecycleActionId;
100
+ selection: DataSurfaceSelectionReference;
101
+ expectedCount: number;
102
+ query?: ContentListDataQueryRequest;
103
+ restoreStatus?: ContentListRestoreStatus;
104
+ /** Invalidates the preview when the visible query/selection changes. */
105
+ viewKey: string;
106
+ }
107
+ export interface ContentListLifecycleControllerOptions extends ContentListLifecycleBinding {
108
+ createRequestId?: () => string;
109
+ createIdempotencyKey?: () => string;
110
+ }
111
+ export interface ContentListLifecycleController {
112
+ snapshot(): ContentListLifecycleSnapshot;
113
+ subscribe(listener: (snapshot: ContentListLifecycleSnapshot) => void): () => void;
114
+ preview(input: ContentListLifecyclePreviewInput): Promise<ContentListLifecycleSnapshot>;
115
+ /**
116
+ * Apply only after the operator confirms the server-resolved count. The
117
+ * number is intentionally passed again rather than inferred by the UI.
118
+ */
119
+ apply(confirmedCount: number): Promise<ContentListLifecycleSnapshot>;
120
+ /** Drop a preview after any visible query or selection change. */
121
+ invalidate(viewKey: string): void;
122
+ reset(): void;
123
+ }
124
+ /** Authenticated fetch transport for the host's lifecycle route. */
125
+ export declare function createContentListLifecycleTransport(options?: ContentListLifecycleTransportOptions): ContentListLifecycleClient;
126
+ /** Parse the bounded, optional preview/apply details without trusting casts. */
127
+ export declare function readContentListLifecycleSummary(result: DataSurfaceActionResult): ContentListLifecycleSummary;
128
+ /** Framework-free state machine shared by UI and agent-driven lifecycle flows. */
129
+ export declare function createContentListLifecycleController(options: ContentListLifecycleControllerOptions): ContentListLifecycleController;
130
+ /** Keep unsuccessful rows selected so partial failures are visible/retryable. */
131
+ export declare function reconcileContentListLifecycleSelection(selectedRowIds: readonly (string | number)[], result: DataSurfaceActionResult): Array<string | number>;
132
+ //# sourceMappingURL=content-list-lifecycle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-list-lifecycle.d.ts","sourceRoot":"","sources":["../../src/svelte/content-list-lifecycle.ts"],"names":[],"mappings":"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"}