@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.
- package/agents/content-list.md +93 -14
- package/dist/chunks/{content-corrections-BxfA3tW1.js → content-corrections-B4mPd_th.js} +6 -3
- package/dist/chunks/content-corrections-B4mPd_th.js.map +1 -0
- package/dist/chunks/{content-query-B4cHfrig.js → content-query-BV41W2Dq.js} +6 -6
- package/dist/chunks/{content-query-B4cHfrig.js.map → content-query-BV41W2Dq.js.map} +1 -1
- package/dist/chunks/{content-reviews-BWrYEQc6.js → content-reviews-lBoqJY7Y.js} +6 -3
- package/dist/chunks/content-reviews-lBoqJY7Y.js.map +1 -0
- package/dist/chunks/{content-versions-OEYQVrUf.js → content-versions-dZSBU8UF.js} +6 -3
- package/dist/chunks/content-versions-dZSBU8UF.js.map +1 -0
- package/dist/chunks/{contents-BrGxWH3s.js → contents-Ba7JHu2K.js} +2 -2
- package/dist/chunks/{contents-BrGxWH3s.js.map → contents-Ba7JHu2K.js.map} +1 -1
- package/dist/content-contribution.d.ts +1 -1
- package/dist/content-contribution.d.ts.map +1 -1
- package/dist/index.js +12 -9
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +2268 -4345
- package/dist/server/content-list-actions.d.ts +10 -3
- package/dist/server/content-list-actions.d.ts.map +1 -1
- package/dist/server/content-list-data-surface.d.ts +29 -0
- package/dist/server/content-list-data-surface.d.ts.map +1 -0
- package/dist/server/content-list-lifecycle.d.ts +20 -0
- package/dist/server/content-list-lifecycle.d.ts.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +247 -25
- package/dist/server.js.map +1 -1
- package/dist/smrt-knowledge.json +4291 -2510
- package/dist/svelte/components/ContentList.svelte +538 -40
- package/dist/svelte/components/ContentList.svelte.d.ts +6 -1
- package/dist/svelte/components/ContentList.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ContentListLifecyclePanel.svelte +387 -0
- package/dist/svelte/components/ContentListLifecyclePanel.svelte.d.ts +17 -0
- package/dist/svelte/components/ContentListLifecyclePanel.svelte.d.ts.map +1 -0
- package/dist/svelte/content-list-controller.d.ts +10 -0
- package/dist/svelte/content-list-controller.d.ts.map +1 -1
- package/dist/svelte/content-list-controller.js +111 -26
- package/dist/svelte/content-list-data-surface.d.ts +45 -0
- package/dist/svelte/content-list-data-surface.d.ts.map +1 -0
- package/dist/svelte/content-list-data-surface.js +196 -0
- package/dist/svelte/content-list-lifecycle.d.ts +132 -0
- package/dist/svelte/content-list-lifecycle.d.ts.map +1 -0
- package/dist/svelte/content-list-lifecycle.js +439 -0
- package/dist/svelte/i18n.contribution.d.ts +27 -0
- package/dist/svelte/i18n.contribution.d.ts.map +1 -1
- package/dist/svelte/i18n.contribution.js +27 -0
- package/dist/svelte/index.d.ts +2 -0
- package/dist/svelte/index.d.ts.map +1 -1
- package/dist/svelte/index.js +2 -0
- package/package.json +15 -15
- package/dist/chunks/content-corrections-BxfA3tW1.js.map +0 -1
- package/dist/chunks/content-reviews-BWrYEQc6.js.map +0 -1
- package/dist/chunks/content-versions-OEYQVrUf.js.map +0 -1
|
@@ -45,6 +45,16 @@ 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';
|
|
53
|
+
import {
|
|
54
|
+
registerContentListDataSurface,
|
|
55
|
+
type ContentListSurfaceContext,
|
|
56
|
+
type ContentListSurfaceHandle,
|
|
57
|
+
} from '../content-list-data-surface.js';
|
|
48
58
|
import {
|
|
49
59
|
applyContentListFilter,
|
|
50
60
|
buildContentListColumns,
|
|
@@ -123,10 +133,109 @@ import {
|
|
|
123
133
|
readContentListViewStateFromSearchParams,
|
|
124
134
|
} from '../content-list-url-state.js';
|
|
125
135
|
import { M } from '../i18n.contribution.js';
|
|
136
|
+
import ContentListLifecyclePanel from './ContentListLifecyclePanel.svelte';
|
|
126
137
|
import ImageThumbnail from './ImageThumbnail.svelte';
|
|
127
138
|
|
|
128
139
|
const { t } = useI18n();
|
|
129
140
|
|
|
141
|
+
const DEFAULT_CONTENT_LIST_SURFACE_IDENTITY: DataSurfaceIdentity = {
|
|
142
|
+
surfaceId: 'content-list',
|
|
143
|
+
kind: 'table',
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
function sameSurfaceIdentity(
|
|
147
|
+
left: DataSurfaceIdentity,
|
|
148
|
+
right: DataSurfaceIdentity,
|
|
149
|
+
): boolean {
|
|
150
|
+
return (
|
|
151
|
+
left.kind === right.kind &&
|
|
152
|
+
left.surfaceId === right.surfaceId &&
|
|
153
|
+
left.subject?.type === right.subject?.type &&
|
|
154
|
+
left.subject?.id === right.subject?.id
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function resolveSurfaceAuthority(
|
|
159
|
+
lifecycle: ContentListLifecycleBinding | undefined,
|
|
160
|
+
workflows: ContentListWorkflowBinding | undefined,
|
|
161
|
+
): {
|
|
162
|
+
identity?: DataSurfaceIdentity;
|
|
163
|
+
maxSelectionSize?: number;
|
|
164
|
+
lifecycle?: ContentListLifecycleBinding;
|
|
165
|
+
workflows?: ContentListWorkflowBinding;
|
|
166
|
+
} {
|
|
167
|
+
const lifecycleIdentity = lifecycle
|
|
168
|
+
? (lifecycle.identity ?? DEFAULT_CONTENT_LIST_SURFACE_IDENTITY)
|
|
169
|
+
: undefined;
|
|
170
|
+
const workflowIdentity = workflows
|
|
171
|
+
? (workflows.identity ?? DEFAULT_CONTENT_LIST_SURFACE_IDENTITY)
|
|
172
|
+
: undefined;
|
|
173
|
+
if (
|
|
174
|
+
lifecycleIdentity &&
|
|
175
|
+
workflowIdentity &&
|
|
176
|
+
!sameSurfaceIdentity(lifecycleIdentity, workflowIdentity)
|
|
177
|
+
) {
|
|
178
|
+
throw new Error(
|
|
179
|
+
'ContentList lifecycle and workflow bindings must use the same data surface identity',
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
const bindings = [lifecycle, workflows].filter(
|
|
183
|
+
(binding): binding is ContentListLifecycleBinding | ContentListWorkflowBinding =>
|
|
184
|
+
binding !== undefined,
|
|
185
|
+
);
|
|
186
|
+
const maxSelectionSize =
|
|
187
|
+
bindings.length > 0
|
|
188
|
+
? Math.min(...bindings.map((binding) => binding.maxSelectionSize ?? 200))
|
|
189
|
+
: undefined;
|
|
190
|
+
return {
|
|
191
|
+
...(lifecycleIdentity ?? workflowIdentity
|
|
192
|
+
? { identity: lifecycleIdentity ?? workflowIdentity }
|
|
193
|
+
: {}),
|
|
194
|
+
...(maxSelectionSize === undefined ? {} : { maxSelectionSize }),
|
|
195
|
+
...(lifecycle && lifecycleIdentity
|
|
196
|
+
? {
|
|
197
|
+
lifecycle: {
|
|
198
|
+
...lifecycle,
|
|
199
|
+
identity: lifecycleIdentity,
|
|
200
|
+
...(maxSelectionSize === undefined ? {} : { maxSelectionSize }),
|
|
201
|
+
},
|
|
202
|
+
}
|
|
203
|
+
: {}),
|
|
204
|
+
...(workflows && workflowIdentity
|
|
205
|
+
? {
|
|
206
|
+
workflows: {
|
|
207
|
+
...workflows,
|
|
208
|
+
identity: workflowIdentity,
|
|
209
|
+
...(maxSelectionSize === undefined ? {} : { maxSelectionSize }),
|
|
210
|
+
},
|
|
211
|
+
}
|
|
212
|
+
: {}),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function reconcileSurfaceDescriptor(
|
|
217
|
+
descriptor: import('@happyvertical/smrt-ui/data').DataSurfaceDescriptor,
|
|
218
|
+
authority: ReturnType<typeof resolveSurfaceAuthority>,
|
|
219
|
+
): import('@happyvertical/smrt-ui/data').DataSurfaceDescriptor {
|
|
220
|
+
if (
|
|
221
|
+
authority.identity &&
|
|
222
|
+
!sameSurfaceIdentity(descriptor.identity, authority.identity)
|
|
223
|
+
) {
|
|
224
|
+
throw new Error(
|
|
225
|
+
'ContentList custom data surface descriptor must use the lifecycle and workflow identity',
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
if (
|
|
229
|
+
authority.maxSelectionSize !== undefined &&
|
|
230
|
+
descriptor.limits.maxSelectionSize !== authority.maxSelectionSize
|
|
231
|
+
) {
|
|
232
|
+
throw new Error(
|
|
233
|
+
'ContentList custom data surface descriptor must use the lifecycle and workflow selection limit',
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
return descriptor;
|
|
237
|
+
}
|
|
238
|
+
|
|
130
239
|
const WORKFLOW_LABEL_MESSAGES: Record<
|
|
131
240
|
ContentListWorkflowId,
|
|
132
241
|
keyof typeof M
|
|
@@ -318,7 +427,7 @@ interface Props {
|
|
|
318
427
|
error?: string | null;
|
|
319
428
|
/** Retry affordance rendered with an error. */
|
|
320
429
|
onRetry?: () => void;
|
|
321
|
-
/** Opt-in agent addressability
|
|
430
|
+
/** Opt-in agent addressability shared by every list presentation. */
|
|
322
431
|
dataSurface?: ContentListDataSurface;
|
|
323
432
|
/**
|
|
324
433
|
* Opt-in server-backed rows (#2452). `bind()` is called exactly once, during
|
|
@@ -335,6 +444,10 @@ interface Props {
|
|
|
335
444
|
jobs?: ContentListJobBinding;
|
|
336
445
|
/** Opt-in, authenticated preview/apply client for bulk workflows (#2453). */
|
|
337
446
|
workflows?: ContentListWorkflowBinding;
|
|
447
|
+
/** Opt-in, server-authoritative trash/restore/permanent-delete lifecycle. */
|
|
448
|
+
lifecycle?: ContentListLifecycleBinding;
|
|
449
|
+
/** Locks the status query to deleted content and exposes trash actions. */
|
|
450
|
+
lifecycleMode?: 'active' | 'trash';
|
|
338
451
|
}
|
|
339
452
|
|
|
340
453
|
let {
|
|
@@ -356,8 +469,14 @@ let {
|
|
|
356
469
|
savedViews = undefined,
|
|
357
470
|
jobs = undefined,
|
|
358
471
|
workflows = undefined,
|
|
472
|
+
lifecycle = undefined,
|
|
473
|
+
lifecycleMode = 'active',
|
|
359
474
|
}: Props = $props();
|
|
360
475
|
|
|
476
|
+
const surfaceAuthority = $derived(
|
|
477
|
+
resolveSurfaceAuthority(lifecycle, workflows),
|
|
478
|
+
);
|
|
479
|
+
|
|
361
480
|
const initialQuery = untrack(() => query);
|
|
362
481
|
|
|
363
482
|
// One controller owns search, filters, sorting, paging, and selection for
|
|
@@ -425,6 +544,7 @@ const urlStateOptions: ContentListUrlStateOptions = {
|
|
|
425
544
|
|
|
426
545
|
const controller = createContentListController({
|
|
427
546
|
type: untrack(() => type),
|
|
547
|
+
...(untrack(() => lifecycleMode) === 'trash' ? { status: 'deleted' } : {}),
|
|
428
548
|
// Local mode keeps the historical unpaginated list.
|
|
429
549
|
...(serverPageSize === null ? {} : { pageSize: serverPageSize }),
|
|
430
550
|
});
|
|
@@ -454,6 +574,18 @@ let resultNotices = $state<ContentListQueryNotices>({
|
|
|
454
574
|
* its own total ever arrived.
|
|
455
575
|
*/
|
|
456
576
|
let settledSignature = $state<string | undefined>(undefined);
|
|
577
|
+
let settledLifecycleQuery = $state<ContentListDataQueryRequest | undefined>(
|
|
578
|
+
undefined,
|
|
579
|
+
);
|
|
580
|
+
let settledLifecycleFingerprint = $state<string | undefined>(undefined);
|
|
581
|
+
let settledSurfaceMetadata = $state<
|
|
582
|
+
| {
|
|
583
|
+
signature: string;
|
|
584
|
+
queryFingerprint: string | null;
|
|
585
|
+
totalRows: number | null;
|
|
586
|
+
}
|
|
587
|
+
| undefined
|
|
588
|
+
>(undefined);
|
|
457
589
|
|
|
458
590
|
function toSearchParams(
|
|
459
591
|
input: URLSearchParams | string | null | undefined,
|
|
@@ -480,22 +612,34 @@ if (initialUrlState?.params !== undefined && initialUrlState?.params !== null) {
|
|
|
480
612
|
// the same link just restored. Folding the lock into the restored patch keeps
|
|
481
613
|
// it to one `replaceState`, and leaves the effect's first run a no-op.
|
|
482
614
|
const initialLockedType = normalizeContentListTypeLock(untrack(() => type));
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
615
|
+
const initialTrashMode = untrack(() => lifecycleMode) === 'trash';
|
|
616
|
+
const lockedFilters = [...(reading.state.filters ?? [])].filter(
|
|
617
|
+
(filter) =>
|
|
618
|
+
(initialLockedType === null ||
|
|
619
|
+
filter.columnId !== CONTENT_LIST_TYPE_FILTER_ID) &&
|
|
620
|
+
(!initialTrashMode ||
|
|
621
|
+
filter.columnId !== CONTENT_LIST_STATUS_FILTER_ID),
|
|
622
|
+
);
|
|
623
|
+
if (initialLockedType !== null) {
|
|
624
|
+
lockedFilters.push({
|
|
625
|
+
columnId: CONTENT_LIST_TYPE_FILTER_ID,
|
|
626
|
+
operator: 'equals',
|
|
627
|
+
value: initialLockedType,
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
if (initialTrashMode) {
|
|
631
|
+
lockedFilters.push({
|
|
632
|
+
columnId: CONTENT_LIST_STATUS_FILTER_ID,
|
|
633
|
+
operator: 'equals',
|
|
634
|
+
value: 'deleted',
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
const patch = {
|
|
638
|
+
...reading.state,
|
|
639
|
+
...(initialLockedType !== null || initialTrashMode
|
|
640
|
+
? { filters: lockedFilters }
|
|
641
|
+
: {}),
|
|
642
|
+
};
|
|
499
643
|
applyContentListViewState(controller, patch, restoreOptions);
|
|
500
644
|
restoreDrops = reading.dropped;
|
|
501
645
|
}
|
|
@@ -515,6 +659,7 @@ const queryRequestOptions: ContentListQueryRequestOptions | undefined =
|
|
|
515
659
|
}
|
|
516
660
|
: undefined;
|
|
517
661
|
const serverBacked = queryBinding !== undefined;
|
|
662
|
+
let locallyRemovedLifecycleIds = $state<Set<string>>(new Set());
|
|
518
663
|
|
|
519
664
|
let snapshot = $state(controller.snapshot());
|
|
520
665
|
let viewMode: ContentListViewMode = $state(untrack(() => defaultViewMode));
|
|
@@ -532,6 +677,11 @@ const MAX_DEFERRED_JOB_COMPLETIONS = 50;
|
|
|
532
677
|
let completedJobs = $state<ContentListJob[]>([]);
|
|
533
678
|
let completedJobsOverflowed = $state(false);
|
|
534
679
|
let activeQueryKey = $state<string | undefined>(undefined);
|
|
680
|
+
let lifecycleRefreshPending = $state(false);
|
|
681
|
+
let surfaceRoot: HTMLDivElement | undefined;
|
|
682
|
+
let surfaceHighlighted = $state(false);
|
|
683
|
+
let surfaceHighlightTimeout: ReturnType<typeof setTimeout> | undefined;
|
|
684
|
+
let surfaceHandle: ContentListSurfaceHandle | undefined;
|
|
535
685
|
|
|
536
686
|
// Job state is subscribed once so hosts can use a framework-free controller.
|
|
537
687
|
// Only transitions observed after the initial snapshot are completion events;
|
|
@@ -697,8 +847,20 @@ const columnLabels = $derived({
|
|
|
697
847
|
|
|
698
848
|
const queryColumns = $derived(buildContentListColumns(columnLabels));
|
|
699
849
|
const sourceContents = $derived(
|
|
700
|
-
|
|
850
|
+
(queryBinding
|
|
851
|
+
? contentListQueryRowsToContents(queryBinding.rows)
|
|
852
|
+
: contents
|
|
853
|
+
).filter((content) => !locallyRemovedLifecycleIds.has(String(content.id))),
|
|
701
854
|
);
|
|
855
|
+
|
|
856
|
+
$effect(() => {
|
|
857
|
+
if (!queryBinding || refreshing || isLoading) return;
|
|
858
|
+
const removed = locallyRemovedLifecycleIds;
|
|
859
|
+
if (removed.size === 0) return;
|
|
860
|
+
const visible = new Set(queryBinding.rows.map((row) => String(row.id)));
|
|
861
|
+
const retained = new Set([...removed].filter((id) => visible.has(id)));
|
|
862
|
+
if (retained.size !== removed.size) locallyRemovedLifecycleIds = retained;
|
|
863
|
+
});
|
|
702
864
|
const rows = $derived(toContentListRows(sourceContents));
|
|
703
865
|
const visibleRowIds = $derived(
|
|
704
866
|
new Set(rows.filter((row) => row.identified).map((row) => String(row.id))),
|
|
@@ -843,21 +1005,58 @@ function refreshQuery(): void {
|
|
|
843
1005
|
void queryBinding
|
|
844
1006
|
.refresh()
|
|
845
1007
|
.then((result) => {
|
|
846
|
-
if (result !== undefined && activeQueryKey === key)
|
|
847
|
-
|
|
1008
|
+
if (result !== undefined && activeQueryKey === key) {
|
|
1009
|
+
if (executedWorkflowQuery) {
|
|
1010
|
+
settleWorkflowQueryResult(
|
|
1011
|
+
result,
|
|
1012
|
+
executedWorkflowQuery,
|
|
1013
|
+
querySignature,
|
|
1014
|
+
);
|
|
1015
|
+
} else {
|
|
1016
|
+
resultNotices = readContentListQueryNotices(result);
|
|
1017
|
+
}
|
|
1018
|
+
settleLifecycleResult(result, lifecycleQueryRequest, querySignature);
|
|
1019
|
+
}
|
|
848
1020
|
})
|
|
849
1021
|
.catch(() => undefined);
|
|
850
1022
|
}
|
|
851
1023
|
|
|
1024
|
+
function requestLifecycleRefresh(): void {
|
|
1025
|
+
if (!queryBinding?.refresh) return;
|
|
1026
|
+
if (refreshing || isLoading) {
|
|
1027
|
+
lifecycleRefreshPending = true;
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
lifecycleRefreshPending = false;
|
|
1031
|
+
refreshQuery();
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
$effect(() => {
|
|
1035
|
+
if (!lifecycleRefreshPending || refreshing || isLoading) return;
|
|
1036
|
+
lifecycleRefreshPending = false;
|
|
1037
|
+
refreshQuery();
|
|
1038
|
+
});
|
|
1039
|
+
|
|
852
1040
|
function settleWorkflowQueryResult(
|
|
853
1041
|
result: unknown,
|
|
854
1042
|
request: ContentListDataQueryRequest,
|
|
855
1043
|
signature: string,
|
|
856
1044
|
): void {
|
|
857
1045
|
settledSignature = signature;
|
|
1046
|
+
const envelope =
|
|
1047
|
+
result !== null && typeof result === 'object' && !Array.isArray(result)
|
|
1048
|
+
? (result as Record<string, unknown>)
|
|
1049
|
+
: undefined;
|
|
1050
|
+
settledSurfaceMetadata = {
|
|
1051
|
+
signature,
|
|
1052
|
+
queryFingerprint:
|
|
1053
|
+
typeof envelope?.queryFingerprint === 'string'
|
|
1054
|
+
? envelope.queryFingerprint
|
|
1055
|
+
: null,
|
|
1056
|
+
totalRows: surfaceMetadataTotalRows(envelope?.total),
|
|
1057
|
+
};
|
|
858
1058
|
resultNotices = readContentListQueryNotices(result);
|
|
859
|
-
if (
|
|
860
|
-
const envelope = result as Record<string, unknown>;
|
|
1059
|
+
if (envelope) {
|
|
861
1060
|
settledWorkflowQuery = request;
|
|
862
1061
|
settledWorkflowFingerprint =
|
|
863
1062
|
typeof envelope.queryFingerprint === 'string'
|
|
@@ -878,6 +1077,38 @@ function settleWorkflowQueryResult(
|
|
|
878
1077
|
settledWorkflowRevision = null;
|
|
879
1078
|
}
|
|
880
1079
|
|
|
1080
|
+
function surfaceMetadataTotalRows(total: unknown): number | null {
|
|
1081
|
+
if (!total || typeof total !== 'object' || Array.isArray(total)) return null;
|
|
1082
|
+
const record = total as Record<string, unknown>;
|
|
1083
|
+
return (record.kind === 'exact' || record.kind === 'estimated') &&
|
|
1084
|
+
typeof record.value === 'number' &&
|
|
1085
|
+
Number.isSafeInteger(record.value) &&
|
|
1086
|
+
record.value >= 0
|
|
1087
|
+
? record.value
|
|
1088
|
+
: null;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
function settleLifecycleResult(
|
|
1092
|
+
result: unknown,
|
|
1093
|
+
request: ContentListDataQueryRequest | undefined,
|
|
1094
|
+
signature: string,
|
|
1095
|
+
): void {
|
|
1096
|
+
if (
|
|
1097
|
+
!request ||
|
|
1098
|
+
result === null ||
|
|
1099
|
+
typeof result !== 'object' ||
|
|
1100
|
+
Array.isArray(result)
|
|
1101
|
+
) {
|
|
1102
|
+
settledLifecycleQuery = undefined;
|
|
1103
|
+
settledLifecycleFingerprint = undefined;
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
const fingerprint = (result as Record<string, unknown>).queryFingerprint;
|
|
1107
|
+
settledLifecycleQuery = request;
|
|
1108
|
+
settledLifecycleFingerprint =
|
|
1109
|
+
typeof fingerprint === 'string' ? fingerprint : undefined;
|
|
1110
|
+
settledSignature = signature;
|
|
1111
|
+
}
|
|
881
1112
|
/**
|
|
882
1113
|
* A retry re-reads the same query, so its answer replaces the rendered rows —
|
|
883
1114
|
* and therefore has to replace the completeness flags that describe them too.
|
|
@@ -903,6 +1134,7 @@ function retryQuery(): void {
|
|
|
903
1134
|
)
|
|
904
1135
|
return;
|
|
905
1136
|
settleWorkflowQueryResult(result, request, signature);
|
|
1137
|
+
settleLifecycleResult(result, lifecycleQueryRequest, signature);
|
|
906
1138
|
})
|
|
907
1139
|
.catch(() => undefined);
|
|
908
1140
|
}
|
|
@@ -930,6 +1162,7 @@ const querySignature = $derived(
|
|
|
930
1162
|
let executedSignature: string | undefined;
|
|
931
1163
|
let executedWorkflowQuery: ContentListDataQueryRequest | null = null;
|
|
932
1164
|
let publishedUrlSignature: string | undefined;
|
|
1165
|
+
let lifecycleQueryRequest: ContentListDataQueryRequest | undefined;
|
|
933
1166
|
|
|
934
1167
|
$effect(() => {
|
|
935
1168
|
const signature = querySignature;
|
|
@@ -976,6 +1209,7 @@ $effect(() => {
|
|
|
976
1209
|
// flags, which the binding itself does not expose.
|
|
977
1210
|
activeQueryKey = contentListQueryRequestKey(translated.request);
|
|
978
1211
|
executedWorkflowQuery = translated.request;
|
|
1212
|
+
lifecycleQueryRequest = translated.request;
|
|
979
1213
|
void queryBinding
|
|
980
1214
|
.execute(translated.request)
|
|
981
1215
|
.then((result) => {
|
|
@@ -985,6 +1219,7 @@ $effect(() => {
|
|
|
985
1219
|
// may be clamped against them and workflows may bind to the exact
|
|
986
1220
|
// settled request.
|
|
987
1221
|
settleWorkflowQueryResult(result, translated.request, signature);
|
|
1222
|
+
settleLifecycleResult(result, translated.request, signature);
|
|
988
1223
|
})
|
|
989
1224
|
.catch(() => undefined);
|
|
990
1225
|
}
|
|
@@ -1111,7 +1346,7 @@ const canSelectAllMatching = $derived(
|
|
|
1111
1346
|
settledWorkflowRevision &&
|
|
1112
1347
|
exactMatchingCount !== undefined &&
|
|
1113
1348
|
exactMatchingCount > 0 &&
|
|
1114
|
-
exactMatchingCount <= (
|
|
1349
|
+
exactMatchingCount <= (surfaceAuthority.maxSelectionSize ?? 200),
|
|
1115
1350
|
),
|
|
1116
1351
|
);
|
|
1117
1352
|
const workflowPayloadValid = $derived(
|
|
@@ -1124,6 +1359,23 @@ const workflowQueuedJob = $derived(
|
|
|
1124
1359
|
workflowQueuedJobs.find((job) => job.intent === workflowIntentSignature()) ??
|
|
1125
1360
|
workflowQueuedJobs[0],
|
|
1126
1361
|
);
|
|
1362
|
+
const lifecycleViewKey = $derived(
|
|
1363
|
+
JSON.stringify([
|
|
1364
|
+
lifecycleMode,
|
|
1365
|
+
querySignature,
|
|
1366
|
+
[...tableState.selectedRowIds].map(String).sort(),
|
|
1367
|
+
]),
|
|
1368
|
+
);
|
|
1369
|
+
const lifecycleQuery = $derived(
|
|
1370
|
+
serverBacked && settledSignature === querySignature
|
|
1371
|
+
? settledLifecycleQuery
|
|
1372
|
+
: undefined,
|
|
1373
|
+
);
|
|
1374
|
+
const lifecycleExactMatchingCount = $derived(
|
|
1375
|
+
serverBacked && settledSignature === querySignature
|
|
1376
|
+
? contentListQueryExactTotal(queryBinding?.total)
|
|
1377
|
+
: queryRows.length,
|
|
1378
|
+
);
|
|
1127
1379
|
|
|
1128
1380
|
/** Synthetic ids the adapter minted for rows that carry no durable identity. */
|
|
1129
1381
|
const unidentifiedRowKeys = $derived(
|
|
@@ -1153,6 +1405,23 @@ $effect(() => {
|
|
|
1153
1405
|
);
|
|
1154
1406
|
});
|
|
1155
1407
|
|
|
1408
|
+
// Trash is a view, not an editable select value. Keep its status predicate
|
|
1409
|
+
// locked against URL restores and agent-driven filter commands.
|
|
1410
|
+
$effect(() => {
|
|
1411
|
+
if (lifecycleMode !== 'trash') return;
|
|
1412
|
+
if (
|
|
1413
|
+
isContentListFilterExactly(
|
|
1414
|
+
tableState,
|
|
1415
|
+
CONTENT_LIST_STATUS_FILTER_ID,
|
|
1416
|
+
'deleted',
|
|
1417
|
+
)
|
|
1418
|
+
)
|
|
1419
|
+
return;
|
|
1420
|
+
untrack(() =>
|
|
1421
|
+
applyContentListFilter(controller, CONTENT_LIST_STATUS_FILTER_ID, 'deleted'),
|
|
1422
|
+
);
|
|
1423
|
+
});
|
|
1424
|
+
|
|
1156
1425
|
/**
|
|
1157
1426
|
* What each toolbar select may display, and whether it can display the live
|
|
1158
1427
|
* predicate at all.
|
|
@@ -1257,20 +1526,30 @@ const surfaceOptions = $derived(
|
|
|
1257
1526
|
? {
|
|
1258
1527
|
registry: dataSurface.registry,
|
|
1259
1528
|
descriptor:
|
|
1260
|
-
dataSurface.descriptor
|
|
1529
|
+
dataSurface.descriptor
|
|
1530
|
+
? reconcileSurfaceDescriptor(
|
|
1531
|
+
dataSurface.descriptor,
|
|
1532
|
+
surfaceAuthority,
|
|
1533
|
+
)
|
|
1534
|
+
:
|
|
1261
1535
|
(() => {
|
|
1262
1536
|
const descriptor = buildContentListSurfaceDescriptor({
|
|
1263
1537
|
columnLabels,
|
|
1538
|
+
serverBacked: initialQuery !== undefined,
|
|
1539
|
+
lifecycle: lifecycle !== undefined,
|
|
1540
|
+
lifecycleMode,
|
|
1264
1541
|
includeWorkflows: workflows !== undefined,
|
|
1542
|
+
refresh: queryBinding?.refresh !== undefined,
|
|
1543
|
+
retry: retryHandler !== undefined,
|
|
1265
1544
|
});
|
|
1266
1545
|
return {
|
|
1267
1546
|
...descriptor,
|
|
1268
|
-
identity:
|
|
1547
|
+
identity: surfaceAuthority.identity ?? descriptor.identity,
|
|
1269
1548
|
limits: {
|
|
1270
1549
|
...descriptor.limits,
|
|
1271
|
-
...(
|
|
1550
|
+
...(surfaceAuthority.maxSelectionSize === undefined
|
|
1272
1551
|
? {}
|
|
1273
|
-
: { maxSelectionSize:
|
|
1552
|
+
: { maxSelectionSize: surfaceAuthority.maxSelectionSize }),
|
|
1274
1553
|
},
|
|
1275
1554
|
};
|
|
1276
1555
|
})(),
|
|
@@ -1312,6 +1591,164 @@ const queryWarnings = $derived<ReadonlyArray<string>>(
|
|
|
1312
1591
|
boundResultNotices?.warnings ??
|
|
1313
1592
|
resultNotices.warnings,
|
|
1314
1593
|
);
|
|
1594
|
+
|
|
1595
|
+
function surfaceLastUpdated(value: unknown): string | null {
|
|
1596
|
+
if (value instanceof Date) return value.toISOString();
|
|
1597
|
+
if (typeof value === 'string') return value;
|
|
1598
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
1599
|
+
return new Date(value).toISOString();
|
|
1600
|
+
}
|
|
1601
|
+
return null;
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
const matchingSurfaceMetadata = $derived(
|
|
1605
|
+
serverBacked && settledSurfaceMetadata?.signature === querySignature
|
|
1606
|
+
? settledSurfaceMetadata
|
|
1607
|
+
: undefined,
|
|
1608
|
+
);
|
|
1609
|
+
|
|
1610
|
+
const surfaceContext = $derived<ContentListSurfaceContext>({
|
|
1611
|
+
viewMode,
|
|
1612
|
+
queryFingerprint: serverBacked
|
|
1613
|
+
? (matchingSurfaceMetadata?.queryFingerprint ?? null)
|
|
1614
|
+
: null,
|
|
1615
|
+
totalRows: serverBacked
|
|
1616
|
+
? (matchingSurfaceMetadata?.totalRows ?? null)
|
|
1617
|
+
: Number.isSafeInteger(totalRowCount)
|
|
1618
|
+
? totalRowCount
|
|
1619
|
+
: null,
|
|
1620
|
+
freshness: {
|
|
1621
|
+
stale,
|
|
1622
|
+
refreshing,
|
|
1623
|
+
offline,
|
|
1624
|
+
lastUpdated: surfaceLastUpdated(lastUpdated),
|
|
1625
|
+
truncated: queryTruncated,
|
|
1626
|
+
warnings: queryWarnings,
|
|
1627
|
+
},
|
|
1628
|
+
});
|
|
1629
|
+
|
|
1630
|
+
function focusSurface(): void {
|
|
1631
|
+
if (!surfaceRoot) return;
|
|
1632
|
+
surfaceRoot.tabIndex = -1;
|
|
1633
|
+
surfaceRoot.focus();
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
// ContentList owns the registration so grid, detailed, compact, empty, and
|
|
1637
|
+
// loading presentations all expose one stable identity and revision stream.
|
|
1638
|
+
// This ref is deliberately non-reactive: registration input changes must
|
|
1639
|
+
// advance a stable identity revision, but normal command acknowledgements must
|
|
1640
|
+
// not themselves cause a registration teardown/replay window.
|
|
1641
|
+
const surfaceRevision = { value: undefined as number | undefined };
|
|
1642
|
+
|
|
1643
|
+
function acceptsSurfaceTableCommand(
|
|
1644
|
+
command: import('@happyvertical/smrt-ui/data').DataTableCommand,
|
|
1645
|
+
): boolean {
|
|
1646
|
+
if (command.type === 'setPageSize' && serverPageSize !== null) {
|
|
1647
|
+
if (command.pageSize === null || command.pageSize > maxPageSize) return false;
|
|
1648
|
+
}
|
|
1649
|
+
if (command.type === 'setPage') {
|
|
1650
|
+
if (command.page > maxReachablePage) return false;
|
|
1651
|
+
if (
|
|
1652
|
+
tableState.pageSize !== null &&
|
|
1653
|
+
clampableRowCount !== undefined &&
|
|
1654
|
+
// A server binding retains the previous query's exact total until its
|
|
1655
|
+
// replacement settles. That count cannot bound a command for the new
|
|
1656
|
+
// query: it may be smaller, and would reject a valid page before the
|
|
1657
|
+
// new query gets a chance to report its own total.
|
|
1658
|
+
(!serverBacked || settledSignature === querySignature) &&
|
|
1659
|
+
command.page > Math.max(1, Math.ceil(clampableRowCount / tableState.pageSize))
|
|
1660
|
+
)
|
|
1661
|
+
return false;
|
|
1662
|
+
}
|
|
1663
|
+
if (command.type === 'setFilters') {
|
|
1664
|
+
if (
|
|
1665
|
+
lockedType !== null &&
|
|
1666
|
+
!isContentListFilterExactly(
|
|
1667
|
+
{ ...tableState, filters: command.filters },
|
|
1668
|
+
CONTENT_LIST_TYPE_FILTER_ID,
|
|
1669
|
+
lockedType,
|
|
1670
|
+
)
|
|
1671
|
+
)
|
|
1672
|
+
return false;
|
|
1673
|
+
if (
|
|
1674
|
+
lifecycleMode === 'trash' &&
|
|
1675
|
+
!isContentListFilterExactly(
|
|
1676
|
+
{ ...tableState, filters: command.filters },
|
|
1677
|
+
CONTENT_LIST_STATUS_FILTER_ID,
|
|
1678
|
+
'deleted',
|
|
1679
|
+
)
|
|
1680
|
+
)
|
|
1681
|
+
return false;
|
|
1682
|
+
}
|
|
1683
|
+
return command.type !== 'reset' || (lockedType === null && lifecycleMode !== 'trash');
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
$effect(() => {
|
|
1687
|
+
const surface = surfaceOptions;
|
|
1688
|
+
if (!surface) {
|
|
1689
|
+
surfaceHandle = undefined;
|
|
1690
|
+
return;
|
|
1691
|
+
}
|
|
1692
|
+
const handle = registerContentListDataSurface({
|
|
1693
|
+
...surface,
|
|
1694
|
+
controller,
|
|
1695
|
+
context: untrack(() => surfaceContext),
|
|
1696
|
+
initialRevision:
|
|
1697
|
+
surfaceRevision.value === undefined ? 0 : surfaceRevision.value + 1,
|
|
1698
|
+
onRevision: (revision) => {
|
|
1699
|
+
surfaceRevision.value = revision;
|
|
1700
|
+
},
|
|
1701
|
+
acceptsTableCommand: acceptsSurfaceTableCommand,
|
|
1702
|
+
setViewMode: (next) => {
|
|
1703
|
+
viewMode = next;
|
|
1704
|
+
},
|
|
1705
|
+
...(queryBinding?.refresh
|
|
1706
|
+
? {
|
|
1707
|
+
refresh: async () => {
|
|
1708
|
+
await refreshQuery();
|
|
1709
|
+
return true;
|
|
1710
|
+
},
|
|
1711
|
+
}
|
|
1712
|
+
: {}),
|
|
1713
|
+
...(retryHandler
|
|
1714
|
+
? {
|
|
1715
|
+
retry: async () => {
|
|
1716
|
+
await retryHandler();
|
|
1717
|
+
return true;
|
|
1718
|
+
},
|
|
1719
|
+
}
|
|
1720
|
+
: {}),
|
|
1721
|
+
focus: focusSurface,
|
|
1722
|
+
reveal: () => surfaceRoot?.scrollIntoView({ block: 'nearest' }),
|
|
1723
|
+
highlight: () => {
|
|
1724
|
+
if (surfaceHighlightTimeout !== undefined) {
|
|
1725
|
+
clearTimeout(surfaceHighlightTimeout);
|
|
1726
|
+
}
|
|
1727
|
+
surfaceHighlighted = true;
|
|
1728
|
+
const timeout = setTimeout(() => {
|
|
1729
|
+
if (surfaceHandle === handle) surfaceHighlighted = false;
|
|
1730
|
+
if (surfaceHighlightTimeout === timeout) {
|
|
1731
|
+
surfaceHighlightTimeout = undefined;
|
|
1732
|
+
}
|
|
1733
|
+
}, 1_000);
|
|
1734
|
+
surfaceHighlightTimeout = timeout;
|
|
1735
|
+
},
|
|
1736
|
+
});
|
|
1737
|
+
surfaceHandle = handle;
|
|
1738
|
+
return () => {
|
|
1739
|
+
if (surfaceHandle === handle) surfaceHandle = undefined;
|
|
1740
|
+
if (surfaceHighlightTimeout !== undefined) {
|
|
1741
|
+
clearTimeout(surfaceHighlightTimeout);
|
|
1742
|
+
surfaceHighlightTimeout = undefined;
|
|
1743
|
+
}
|
|
1744
|
+
surfaceHighlighted = false;
|
|
1745
|
+
handle.destroy();
|
|
1746
|
+
};
|
|
1747
|
+
});
|
|
1748
|
+
|
|
1749
|
+
$effect(() => {
|
|
1750
|
+
surfaceHandle?.update(surfaceContext);
|
|
1751
|
+
});
|
|
1315
1752
|
/** Identity of the current set of refusals, so a dismissal is not permanent. */
|
|
1316
1753
|
const dropNoticeKey = $derived(
|
|
1317
1754
|
dropNotices.length > 0 || queryTruncated || queryWarnings.length > 0
|
|
@@ -1606,6 +2043,16 @@ function workflowSelection(): ContentListWorkflowRequest['selection'] | null {
|
|
|
1606
2043
|
return { scope: 'explicit-ids', rowIds: effectiveRowIds };
|
|
1607
2044
|
}
|
|
1608
2045
|
|
|
2046
|
+
function workflowSelectionCount(
|
|
2047
|
+
selection: ContentListWorkflowRequest['selection'],
|
|
2048
|
+
): number {
|
|
2049
|
+
if (selection.scope === 'explicit-ids') return selection.rowIds.length;
|
|
2050
|
+
if (selection.scope === 'current-page') return selectablePageRowIds.length;
|
|
2051
|
+
return tableState.selection.scope === 'allMatching'
|
|
2052
|
+
? tableState.selection.expectedCount
|
|
2053
|
+
: 0;
|
|
2054
|
+
}
|
|
2055
|
+
|
|
1609
2056
|
function workflowJobTarget(
|
|
1610
2057
|
request: ContentListWorkflowRequest,
|
|
1611
2058
|
): ContentListJobTarget {
|
|
@@ -1646,6 +2093,8 @@ function createWorkflowRequest(
|
|
|
1646
2093
|
): ContentListWorkflowRequest | null {
|
|
1647
2094
|
const selection = workflowSelection();
|
|
1648
2095
|
if (!selection) return null;
|
|
2096
|
+
const selectionCount = workflowSelectionCount(selection);
|
|
2097
|
+
if (selectionCount > (surfaceAuthority.maxSelectionSize ?? 200)) return null;
|
|
1649
2098
|
const queryRequired = selection.scope !== 'explicit-ids';
|
|
1650
2099
|
if (
|
|
1651
2100
|
queryRequired &&
|
|
@@ -1658,7 +2107,8 @@ function createWorkflowRequest(
|
|
|
1658
2107
|
return {
|
|
1659
2108
|
version: 1,
|
|
1660
2109
|
requestId: globalThis.crypto?.randomUUID?.() ?? `content-workflow-${Date.now()}`,
|
|
1661
|
-
identity:
|
|
2110
|
+
identity:
|
|
2111
|
+
surfaceAuthority.identity ?? DEFAULT_CONTENT_LIST_SURFACE_IDENTITY,
|
|
1662
2112
|
actionId: selectedWorkflow,
|
|
1663
2113
|
phase,
|
|
1664
2114
|
selection,
|
|
@@ -1668,10 +2118,7 @@ function createWorkflowRequest(
|
|
|
1668
2118
|
...(queryRequired && settledWorkflowQuery
|
|
1669
2119
|
? { query: settledWorkflowQuery }
|
|
1670
2120
|
: {}),
|
|
1671
|
-
expectedCount:
|
|
1672
|
-
selection.scope === 'explicit-ids'
|
|
1673
|
-
? selection.rowIds.length
|
|
1674
|
-
: selectedCount,
|
|
2121
|
+
expectedCount: selectionCount,
|
|
1675
2122
|
},
|
|
1676
2123
|
};
|
|
1677
2124
|
}
|
|
@@ -2052,7 +2499,29 @@ function handleFilter(columnId: string, value: string) {
|
|
|
2052
2499
|
}
|
|
2053
2500
|
|
|
2054
2501
|
function rowActions(row: ContentListRow) {
|
|
2055
|
-
return contentListRowActions(row, {
|
|
2502
|
+
return contentListRowActions(row, {
|
|
2503
|
+
getViewHref,
|
|
2504
|
+
canEdit: lifecycleMode !== 'trash',
|
|
2505
|
+
canDelete: lifecycleMode !== 'trash' && lifecycle === undefined,
|
|
2506
|
+
});
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
function handleLifecycleComplete(
|
|
2510
|
+
result: import('@happyvertical/smrt-ui/data').DataSurfaceActionResult,
|
|
2511
|
+
) {
|
|
2512
|
+
const removed = new Set(locallyRemovedLifecycleIds);
|
|
2513
|
+
for (const outcome of readContentListLifecycleSummary(result).outcomes) {
|
|
2514
|
+
if (outcome.status === 'accepted') removed.add(String(outcome.rowId));
|
|
2515
|
+
}
|
|
2516
|
+
locallyRemovedLifecycleIds = removed;
|
|
2517
|
+
controller.dispatch({
|
|
2518
|
+
type: 'setSelectedRows',
|
|
2519
|
+
rowIds: reconcileContentListLifecycleSelection(
|
|
2520
|
+
tableState.selectedRowIds,
|
|
2521
|
+
result,
|
|
2522
|
+
),
|
|
2523
|
+
});
|
|
2524
|
+
requestLifecycleRefresh();
|
|
2056
2525
|
}
|
|
2057
2526
|
|
|
2058
2527
|
function viewHref(row: ContentListRow): string | null {
|
|
@@ -2127,7 +2596,11 @@ const tableColumns: DataTableColumn<ContentListRow>[] = $derived([
|
|
|
2127
2596
|
</script>
|
|
2128
2597
|
|
|
2129
2598
|
{#snippet tableEmptyState()}
|
|
2130
|
-
<p class="table-empty-state">
|
|
2599
|
+
<p class="table-empty-state">
|
|
2600
|
+
{lifecycleMode === 'trash'
|
|
2601
|
+
? t(M['content.content_list.trash_empty'])
|
|
2602
|
+
: t(M['content.content_list.empty'])}
|
|
2603
|
+
</p>
|
|
2131
2604
|
{/snippet}
|
|
2132
2605
|
|
|
2133
2606
|
{#snippet selectHeader()}
|
|
@@ -2226,7 +2699,11 @@ const tableColumns: DataTableColumn<ContentListRow>[] = $derived([
|
|
|
2226
2699
|
</div>
|
|
2227
2700
|
{/snippet}
|
|
2228
2701
|
|
|
2229
|
-
<div
|
|
2702
|
+
<div
|
|
2703
|
+
class="content-list-wrapper"
|
|
2704
|
+
class:data-surface-highlighted={surfaceHighlighted}
|
|
2705
|
+
bind:this={surfaceRoot}
|
|
2706
|
+
>
|
|
2230
2707
|
|
|
2231
2708
|
<div class="content-controls">
|
|
2232
2709
|
<div class="search-filters">
|
|
@@ -2267,6 +2744,7 @@ const tableColumns: DataTableColumn<ContentListRow>[] = $derived([
|
|
|
2267
2744
|
</Select>
|
|
2268
2745
|
{/if}
|
|
2269
2746
|
|
|
2747
|
+
{#if lifecycleMode !== 'trash'}
|
|
2270
2748
|
<Select
|
|
2271
2749
|
aria-label={t(M['content.content_list.filter_status'])}
|
|
2272
2750
|
value={selectedStatus}
|
|
@@ -2289,6 +2767,7 @@ const tableColumns: DataTableColumn<ContentListRow>[] = $derived([
|
|
|
2289
2767
|
</option>
|
|
2290
2768
|
{/if}
|
|
2291
2769
|
</Select>
|
|
2770
|
+
{/if}
|
|
2292
2771
|
|
|
2293
2772
|
{#if savedViews}
|
|
2294
2773
|
<div class="saved-views" role="group" aria-label={t(M['content.content_list.saved_views'])}>
|
|
@@ -2568,6 +3047,19 @@ const tableColumns: DataTableColumn<ContentListRow>[] = $derived([
|
|
|
2568
3047
|
</div>
|
|
2569
3048
|
{/if}
|
|
2570
3049
|
|
|
3050
|
+
{#if lifecycle}
|
|
3051
|
+
<ContentListLifecyclePanel
|
|
3052
|
+
binding={surfaceAuthority.lifecycle ?? lifecycle}
|
|
3053
|
+
mode={lifecycleMode}
|
|
3054
|
+
selectedRowIds={tableState.selectedRowIds}
|
|
3055
|
+
query={lifecycleQuery}
|
|
3056
|
+
queryFingerprint={settledLifecycleFingerprint}
|
|
3057
|
+
exactMatchingCount={lifecycleExactMatchingCount}
|
|
3058
|
+
viewKey={lifecycleViewKey}
|
|
3059
|
+
oncomplete={handleLifecycleComplete}
|
|
3060
|
+
/>
|
|
3061
|
+
{/if}
|
|
3062
|
+
|
|
2571
3063
|
{#if workflows && (selectedCount > 0 || workflowError || workflowResult)}
|
|
2572
3064
|
<section class="content-workflows" aria-label={t(M['content.content_list.bulk_workflows'])}>
|
|
2573
3065
|
<Select
|
|
@@ -2686,9 +3178,9 @@ const tableColumns: DataTableColumn<ContentListRow>[] = $derived([
|
|
|
2686
3178
|
|
|
2687
3179
|
{#if viewMode === 'compact'}
|
|
2688
3180
|
<!--
|
|
2689
|
-
The compact table stays mounted for empty and loading results
|
|
2690
|
-
|
|
2691
|
-
|
|
3181
|
+
The compact table stays mounted for empty and loading results so its
|
|
3182
|
+
table semantics and operator affordances remain stable across queries.
|
|
3183
|
+
ContentList itself owns the agent-addressable surface in every view.
|
|
2692
3184
|
-->
|
|
2693
3185
|
<div class="content-table-wrapper">
|
|
2694
3186
|
<DataTable
|
|
@@ -2701,7 +3193,6 @@ const tableColumns: DataTableColumn<ContentListRow>[] = $derived([
|
|
|
2701
3193
|
loading={isLoading}
|
|
2702
3194
|
caption={t(M['content.content_list.table_caption'])}
|
|
2703
3195
|
rowLabel={(row: ContentListRow) => row.title}
|
|
2704
|
-
dataSurface={surfaceOptions}
|
|
2705
3196
|
empty={tableEmptyState}
|
|
2706
3197
|
/>
|
|
2707
3198
|
</div>
|
|
@@ -2711,7 +3202,9 @@ const tableColumns: DataTableColumn<ContentListRow>[] = $derived([
|
|
|
2711
3202
|
</div>
|
|
2712
3203
|
{:else if pageRows.length === 0}
|
|
2713
3204
|
<div class="state-panel empty-state">
|
|
2714
|
-
{
|
|
3205
|
+
{lifecycleMode === 'trash'
|
|
3206
|
+
? t(M['content.content_list.trash_empty'])
|
|
3207
|
+
: t(M['content.content_list.empty'])}
|
|
2715
3208
|
</div>
|
|
2716
3209
|
{:else if viewMode === 'detailed'}
|
|
2717
3210
|
<div class="content-detailed">
|
|
@@ -2937,6 +3430,11 @@ const tableColumns: DataTableColumn<ContentListRow>[] = $derived([
|
|
|
2937
3430
|
width: 100%;
|
|
2938
3431
|
}
|
|
2939
3432
|
|
|
3433
|
+
.content-list-wrapper.data-surface-highlighted {
|
|
3434
|
+
outline: 2px solid var(--smrt-color-primary);
|
|
3435
|
+
outline-offset: 0.25rem;
|
|
3436
|
+
}
|
|
3437
|
+
|
|
2940
3438
|
.content-controls {
|
|
2941
3439
|
display: flex;
|
|
2942
3440
|
justify-content: space-between;
|