@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.
- package/agents/content-list.md +61 -5
- 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-D1bLBzMT.js +4092 -0
- package/dist/chunks/{content-query-4w62u5_0.js.map → content-query-D1bLBzMT.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-Di3vcTgb.js → content-versions-B0OUI1ut.js} +6 -3
- package/dist/chunks/content-versions-B0OUI1ut.js.map +1 -0
- package/dist/chunks/{contents-BV8RoP_R.js → contents-CRG2Snrb.js} +2 -2
- package/dist/chunks/{contents-BV8RoP_R.js.map → contents-CRG2Snrb.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 +33 -6
- 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-lifecycle.d.ts +20 -0
- package/dist/server/content-list-lifecycle.d.ts.map +1 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +189 -25
- package/dist/server.js.map +1 -1
- package/dist/smrt-knowledge.json +17 -5
- package/dist/svelte/components/ContentAgentChat.svelte +10 -0
- package/dist/svelte/components/ContentAgentChat.svelte.d.ts +10 -0
- package/dist/svelte/components/ContentAgentChat.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ContentBodyEditor.svelte +9 -0
- package/dist/svelte/components/ContentBodyEditor.svelte.d.ts +9 -0
- package/dist/svelte/components/ContentBodyEditor.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ContentBodyRenderer.svelte +3 -0
- package/dist/svelte/components/ContentBodyRenderer.svelte.d.ts +3 -0
- package/dist/svelte/components/ContentBodyRenderer.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ContentClaimAuditTool.svelte +3 -0
- package/dist/svelte/components/ContentClaimAuditTool.svelte.d.ts +3 -0
- package/dist/svelte/components/ContentClaimAuditTool.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ContentContributionForm.svelte +7 -0
- package/dist/svelte/components/ContentContributionForm.svelte.d.ts +7 -0
- package/dist/svelte/components/ContentContributionForm.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ContentContributionInbox.svelte +8 -0
- package/dist/svelte/components/ContentContributionInbox.svelte.d.ts +8 -0
- package/dist/svelte/components/ContentContributionInbox.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ContentContributionPortal.svelte +5 -0
- package/dist/svelte/components/ContentContributionPortal.svelte.d.ts +5 -0
- package/dist/svelte/components/ContentContributionPortal.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ContentContributionTypeManager.svelte +3 -0
- package/dist/svelte/components/ContentContributionTypeManager.svelte.d.ts +3 -0
- package/dist/svelte/components/ContentContributionTypeManager.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ContentContributorManager.svelte +3 -0
- package/dist/svelte/components/ContentContributorManager.svelte.d.ts +3 -0
- package/dist/svelte/components/ContentContributorManager.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ContentCorrectionsTool.svelte +4 -0
- package/dist/svelte/components/ContentCorrectionsTool.svelte.d.ts +4 -0
- package/dist/svelte/components/ContentCorrectionsTool.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ContentEditor.svelte +16 -0
- package/dist/svelte/components/ContentEditor.svelte.d.ts +16 -0
- package/dist/svelte/components/ContentEditor.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ContentGovernanceAssignmentEditor.svelte +4 -0
- package/dist/svelte/components/ContentGovernanceAssignmentEditor.svelte.d.ts +4 -0
- package/dist/svelte/components/ContentGovernanceAssignmentEditor.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ContentGovernanceManager.svelte +3 -0
- package/dist/svelte/components/ContentGovernanceManager.svelte.d.ts +3 -0
- package/dist/svelte/components/ContentGovernanceManager.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ContentGovernancePanel.svelte +16 -0
- package/dist/svelte/components/ContentGovernancePanel.svelte.d.ts +16 -0
- package/dist/svelte/components/ContentGovernancePanel.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ContentGovernancePolicyEditor.svelte +3 -0
- package/dist/svelte/components/ContentGovernancePolicyEditor.svelte.d.ts +3 -0
- package/dist/svelte/components/ContentGovernancePolicyEditor.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ContentGovernanceProfileEditor.svelte +4 -0
- package/dist/svelte/components/ContentGovernanceProfileEditor.svelte.d.ts +4 -0
- package/dist/svelte/components/ContentGovernanceProfileEditor.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ContentGovernanceTool.svelte +5 -0
- package/dist/svelte/components/ContentGovernanceTool.svelte.d.ts +5 -0
- package/dist/svelte/components/ContentGovernanceTool.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ContentImageBrowser.svelte +10 -0
- package/dist/svelte/components/ContentImageBrowser.svelte.d.ts +10 -0
- package/dist/svelte/components/ContentImageBrowser.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ContentImageChooser.svelte +4 -0
- package/dist/svelte/components/ContentImageChooser.svelte.d.ts +4 -0
- package/dist/svelte/components/ContentImageChooser.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ContentList.svelte +201 -22
- package/dist/svelte/components/ContentList.svelte.d.ts +13 -0
- package/dist/svelte/components/ContentList.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ContentListLifecyclePanel.svelte +341 -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/components/ContentMetadataFields.svelte +2 -0
- package/dist/svelte/components/ContentMetadataFields.svelte.d.ts +2 -0
- package/dist/svelte/components/ContentMetadataFields.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ContentReferencesPanel.svelte +3 -0
- package/dist/svelte/components/ContentReferencesPanel.svelte.d.ts +3 -0
- package/dist/svelte/components/ContentReferencesPanel.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ContentReviewStatusTray.svelte +5 -0
- package/dist/svelte/components/ContentReviewStatusTray.svelte.d.ts +5 -0
- package/dist/svelte/components/ContentReviewStatusTray.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ContentStatusFields.svelte +2 -0
- package/dist/svelte/components/ContentStatusFields.svelte.d.ts +2 -0
- package/dist/svelte/components/ContentStatusFields.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ContentTitleField.svelte +4 -0
- package/dist/svelte/components/ContentTitleField.svelte.d.ts +4 -0
- package/dist/svelte/components/ContentTitleField.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ContentTransparencyReport.svelte +3 -0
- package/dist/svelte/components/ContentTransparencyReport.svelte.d.ts +3 -0
- package/dist/svelte/components/ContentTransparencyReport.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ContentTransparencyTool.svelte +3 -0
- package/dist/svelte/components/ContentTransparencyTool.svelte.d.ts +3 -0
- package/dist/svelte/components/ContentTransparencyTool.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ContentVersionsTool.svelte +3 -0
- package/dist/svelte/components/ContentVersionsTool.svelte.d.ts +3 -0
- package/dist/svelte/components/ContentVersionsTool.svelte.d.ts.map +1 -1
- package/dist/svelte/components/GovernedContentEditor.svelte +20 -0
- package/dist/svelte/components/GovernedContentEditor.svelte.d.ts +20 -0
- package/dist/svelte/components/GovernedContentEditor.svelte.d.ts.map +1 -1
- 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 +1 -0
- package/dist/svelte/index.d.ts.map +1 -1
- package/dist/svelte/index.js +1 -0
- package/dist/svelte/routes/ContentContributionsRoute.svelte +4 -0
- package/dist/svelte/routes/ContentContributionsRoute.svelte.d.ts +4 -0
- package/dist/svelte/routes/ContentContributionsRoute.svelte.d.ts.map +1 -1
- package/dist/svelte/routes/ContentFactsRoute.svelte +5 -0
- package/dist/svelte/routes/ContentFactsRoute.svelte.d.ts +5 -0
- package/dist/svelte/routes/ContentFactsRoute.svelte.d.ts.map +1 -1
- package/dist/svelte/routes/ContentGovernanceRoute.svelte +4 -0
- package/dist/svelte/routes/ContentGovernanceRoute.svelte.d.ts +4 -0
- package/dist/svelte/routes/ContentGovernanceRoute.svelte.d.ts.map +1 -1
- package/dist/svelte/routes/ContentWorkspaceRoute.svelte +5 -0
- package/dist/svelte/routes/ContentWorkspaceRoute.svelte.d.ts +5 -0
- package/dist/svelte/routes/ContentWorkspaceRoute.svelte.d.ts.map +1 -1
- package/dist/svelte/routes/PublishedArticleRoute.svelte +3 -0
- package/dist/svelte/routes/PublishedArticleRoute.svelte.d.ts +3 -0
- package/dist/svelte/routes/PublishedArticleRoute.svelte.d.ts.map +1 -1
- package/package.json +16 -15
- package/dist/chunks/content-corrections-BxfA3tW1.js.map +0 -1
- package/dist/chunks/content-query-4w62u5_0.js +0 -4092
- package/dist/chunks/content-reviews-BWrYEQc6.js.map +0 -1
- package/dist/chunks/content-versions-Di3vcTgb.js.map +0 -1
|
@@ -20,25 +20,45 @@ export type GovernedContentEditorSaveData = ContentData & {
|
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
export interface Props {
|
|
23
|
+
/** Base URL for content and governance API requests. */
|
|
23
24
|
apiBaseUrl?: string;
|
|
25
|
+
/** Existing content to edit; omit to create new content. */
|
|
24
26
|
content?: ContentData;
|
|
27
|
+
/** ID of the content being edited; defaults to 'new'. */
|
|
25
28
|
contentId?: string;
|
|
29
|
+
/** Default relationship type for new facts added to the content. */
|
|
26
30
|
defaultRelationship?: string;
|
|
31
|
+
/** Key for the publication review profile to enforce. */
|
|
27
32
|
reviewProfileKey?: string;
|
|
33
|
+
/** Label shown for custom review workflow submissions. */
|
|
28
34
|
customReviewLabel?: string;
|
|
35
|
+
/** Instructions shown for custom review workflow submissions. */
|
|
29
36
|
customReviewInstructions?: string;
|
|
37
|
+
/** Policy key applied to custom review submissions. */
|
|
30
38
|
customReviewPolicyKey?: string;
|
|
39
|
+
/** Whether to require publication readiness before saving. */
|
|
31
40
|
enforcePublishReadiness?: boolean;
|
|
41
|
+
/** Whether to disable the save button. */
|
|
32
42
|
saveDisabled?: boolean;
|
|
43
|
+
/** Message displayed when save is disabled or unavailable. */
|
|
33
44
|
saveNotice?: string | null;
|
|
45
|
+
/** Whether the agent chat assistant panel is available. */
|
|
34
46
|
agentChatEnabled?: boolean;
|
|
47
|
+
/** Message displayed when agent chat is unavailable. */
|
|
35
48
|
agentChatNotice?: string | null;
|
|
49
|
+
/** Whether to hide the save and cancel action buttons. */
|
|
36
50
|
hideActions?: boolean;
|
|
51
|
+
/** Whether to hide the agent chat panel. */
|
|
37
52
|
hideChat?: boolean;
|
|
53
|
+
/** Whether to display the fact catalog panel. */
|
|
38
54
|
showFactCatalog?: boolean;
|
|
55
|
+
/** Whether to display the governance status panel. */
|
|
39
56
|
showGovernancePanel?: boolean;
|
|
57
|
+
/** Invoked when the assistant context changes during editing. */
|
|
40
58
|
onAssistantContextChange?: ContentEditorAssistantContextChange;
|
|
59
|
+
/** Invoked when the user saves the content. */
|
|
41
60
|
onSave: (data: GovernedContentEditorSaveData) => void;
|
|
61
|
+
/** Invoked when the user cancels editing. */
|
|
42
62
|
onCancel: () => void;
|
|
43
63
|
}
|
|
44
64
|
|
|
@@ -5,25 +5,45 @@ export type GovernedContentEditorSaveData = ContentData & {
|
|
|
5
5
|
facts: FactData[];
|
|
6
6
|
};
|
|
7
7
|
export interface Props {
|
|
8
|
+
/** Base URL for content and governance API requests. */
|
|
8
9
|
apiBaseUrl?: string;
|
|
10
|
+
/** Existing content to edit; omit to create new content. */
|
|
9
11
|
content?: ContentData;
|
|
12
|
+
/** ID of the content being edited; defaults to 'new'. */
|
|
10
13
|
contentId?: string;
|
|
14
|
+
/** Default relationship type for new facts added to the content. */
|
|
11
15
|
defaultRelationship?: string;
|
|
16
|
+
/** Key for the publication review profile to enforce. */
|
|
12
17
|
reviewProfileKey?: string;
|
|
18
|
+
/** Label shown for custom review workflow submissions. */
|
|
13
19
|
customReviewLabel?: string;
|
|
20
|
+
/** Instructions shown for custom review workflow submissions. */
|
|
14
21
|
customReviewInstructions?: string;
|
|
22
|
+
/** Policy key applied to custom review submissions. */
|
|
15
23
|
customReviewPolicyKey?: string;
|
|
24
|
+
/** Whether to require publication readiness before saving. */
|
|
16
25
|
enforcePublishReadiness?: boolean;
|
|
26
|
+
/** Whether to disable the save button. */
|
|
17
27
|
saveDisabled?: boolean;
|
|
28
|
+
/** Message displayed when save is disabled or unavailable. */
|
|
18
29
|
saveNotice?: string | null;
|
|
30
|
+
/** Whether the agent chat assistant panel is available. */
|
|
19
31
|
agentChatEnabled?: boolean;
|
|
32
|
+
/** Message displayed when agent chat is unavailable. */
|
|
20
33
|
agentChatNotice?: string | null;
|
|
34
|
+
/** Whether to hide the save and cancel action buttons. */
|
|
21
35
|
hideActions?: boolean;
|
|
36
|
+
/** Whether to hide the agent chat panel. */
|
|
22
37
|
hideChat?: boolean;
|
|
38
|
+
/** Whether to display the fact catalog panel. */
|
|
23
39
|
showFactCatalog?: boolean;
|
|
40
|
+
/** Whether to display the governance status panel. */
|
|
24
41
|
showGovernancePanel?: boolean;
|
|
42
|
+
/** Invoked when the assistant context changes during editing. */
|
|
25
43
|
onAssistantContextChange?: ContentEditorAssistantContextChange;
|
|
44
|
+
/** Invoked when the user saves the content. */
|
|
26
45
|
onSave: (data: GovernedContentEditorSaveData) => void;
|
|
46
|
+
/** Invoked when the user cancels editing. */
|
|
27
47
|
onCancel: () => void;
|
|
28
48
|
}
|
|
29
49
|
declare const GovernedContentEditor: import("svelte").Component<Props, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GovernedContentEditor.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/GovernedContentEditor.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,mCAAmC,EAEpC,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EACV,WAAW,EAGX,QAAQ,EACT,MAAM,wBAAwB,CAAC;AAMhC,MAAM,MAAM,6BAA6B,GAAG,WAAW,GAAG;IACxD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB,CAAC;AAEF,MAAM,WAAW,KAAK;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,wBAAwB,CAAC,EAAE,mCAAmC,CAAC;IAC/D,MAAM,EAAE,CAAC,IAAI,EAAE,6BAA6B,KAAK,IAAI,CAAC;IACtD,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAwSD,QAAA,MAAM,qBAAqB;;gCAhOS,MAAM;MAgOyB,CAAC;AACpE,KAAK,qBAAqB,GAAG,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAC;AACtE,eAAe,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"GovernedContentEditor.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/GovernedContentEditor.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,mCAAmC,EAEpC,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EACV,WAAW,EAGX,QAAQ,EACT,MAAM,wBAAwB,CAAC;AAMhC,MAAM,MAAM,6BAA6B,GAAG,WAAW,GAAG;IACxD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB,CAAC;AAEF,MAAM,WAAW,KAAK;IACpB,wDAAwD;IACxD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,yDAAyD;IACzD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oEAAoE;IACpE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,yDAAyD;IACzD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,0DAA0D;IAC1D,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iEAAiE;IACjE,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,uDAAuD;IACvD,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,8DAA8D;IAC9D,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,0CAA0C;IAC1C,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,8DAA8D;IAC9D,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,2DAA2D;IAC3D,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,wDAAwD;IACxD,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,0DAA0D;IAC1D,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iDAAiD;IACjD,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,sDAAsD;IACtD,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,iEAAiE;IACjE,wBAAwB,CAAC,EAAE,mCAAmC,CAAC;IAC/D,+CAA+C;IAC/C,MAAM,EAAE,CAAC,IAAI,EAAE,6BAA6B,KAAK,IAAI,CAAC;IACtD,6CAA6C;IAC7C,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAwSD,QAAA,MAAM,qBAAqB;;gCAhOS,MAAM;MAgOyB,CAAC;AACpE,KAAK,qBAAqB,GAAG,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAC;AACtE,eAAe,qBAAqB,CAAC"}
|
|
@@ -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":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EACV,uBAAuB,EACvB,mBAAmB,EACnB,oBAAoB,EACpB,6BAA6B,EAC9B,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AAE3E,eAAO,MAAM,iCAAiC,2DAIpC,CAAC;AAEX,MAAM,MAAM,4BAA4B,GACtC,CAAC,OAAO,iCAAiC,CAAC,CAAC,MAAM,CAAC,CAAC;AAErD,MAAM,MAAM,wBAAwB,GAAG,OAAO,GAAG,QAAQ,GAAG,WAAW,CAAC;AAExE,MAAM,WAAW,0BAA0B;IACzC,oEAAoE;IACpE,KAAK,CAAC,EAAE,2BAA2B,CAAC;IACpC,6DAA6D;IAC7D,aAAa,EAAE,MAAM,CAAC;IACtB,kFAAkF;IAClF,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,CAAC,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,QAAQ,EAAE,4BAA4B,CAAC;IACvC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC;IAC3B,SAAS,EAAE,6BAA6B,CAAC;IACzC,OAAO,CAAC,EAAE,oBAAoB,CAAC;IAC/B,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,0BAA0B,CAAC;IACnC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,0BAA0B;IACzC,OAAO,CACL,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,uBAAuB,CAAC,CAAC;IACpC,KAAK,CAAC,OAAO,EAAE,2BAA2B,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;CAC/E;AAED,MAAM,WAAW,2BAA2B;IAC1C,MAAM,EAAE,0BAA0B,CAAC;IACnC,mEAAmE;IACnE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iDAAiD;IACjD,QAAQ,CAAC,EAAE,mBAAmB,CAAC;IAC/B,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,oCAAoC;IACnD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAChC,OAAO,CAAC,EAAE,WAAW,GAAG,CAAC,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC;IACnE,WAAW,CAAC,EAAE,kBAAkB,CAAC;CAClC;AAED,MAAM,MAAM,wCAAwC,GAChD,iBAAiB,GACjB,cAAc,GACd,YAAY,GACZ,gBAAgB,CAAC;AAErB,qBAAa,kCAAmC,SAAQ,KAAK;IAC3D,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,wCAAwC,CAAC;gBAGxD,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,MAAM,EACf,MAAM,GAAE,wCAAuD;CAOlE;AAED,MAAM,WAAW,2BAA2B;IAC1C,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,MAAM,EAAE,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,2BAA2B;IAC1C,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,2BAA2B,EAAE,CAAC;IACxC,oBAAoB,EAAE,MAAM,EAAE,CAAC;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mEAAmE;IACnE,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,oCAAoC,GAC5C,MAAM,GACN,YAAY,GACZ,OAAO,GACP,UAAU,GACV,WAAW,GACX,QAAQ,CAAC;AAEb,MAAM,WAAW,4BAA4B;IAC3C,MAAM,EAAE,oCAAoC,CAAC;IAC7C,QAAQ,CAAC,EAAE,4BAA4B,CAAC;IACxC,SAAS,CAAC,EAAE,6BAA6B,CAAC;IAC1C,OAAO,CAAC,EAAE,2BAA2B,CAAC;IACtC,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,eAAe,EAAE,OAAO,CAAC;IACzB,8EAA8E;IAC9E,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,gCAAgC;IAC/C,QAAQ,EAAE,4BAA4B,CAAC;IACvC,SAAS,EAAE,6BAA6B,CAAC;IACzC,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,2BAA2B,CAAC;IACpC,aAAa,CAAC,EAAE,wBAAwB,CAAC;IACzC,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,qCACf,SAAQ,2BAA2B;IACnC,eAAe,CAAC,EAAE,MAAM,MAAM,CAAC;IAC/B,oBAAoB,CAAC,EAAE,MAAM,MAAM,CAAC;CACrC;AAED,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,IAAI,4BAA4B,CAAC;IACzC,SAAS,CACP,QAAQ,EAAE,CAAC,QAAQ,EAAE,4BAA4B,KAAK,IAAI,GACzD,MAAM,IAAI,CAAC;IACd,OAAO,CACL,KAAK,EAAE,gCAAgC,GACtC,OAAO,CAAC,4BAA4B,CAAC,CAAC;IACzC;;;OAGG;IACH,KAAK,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,4BAA4B,CAAC,CAAC;IACrE,kEAAkE;IAClE,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,KAAK,IAAI,IAAI,CAAC;CACf;AAwDD,oEAAoE;AACpE,wBAAgB,mCAAmC,CACjD,OAAO,GAAE,oCAAyC,GACjD,0BAA0B,CA+F5B;AAED,gFAAgF;AAChF,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,uBAAuB,GAC9B,2BAA2B,CAmD7B;AAkBD,kFAAkF;AAClF,wBAAgB,oCAAoC,CAClD,OAAO,EAAE,qCAAqC,GAC7C,8BAA8B,CA6PhC;AAED,iFAAiF;AACjF,wBAAgB,sCAAsC,CACpD,cAAc,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,EAC5C,MAAM,EAAE,uBAAuB,GAC9B,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAOxB"}
|
|
@@ -0,0 +1,439 @@
|
|
|
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
|
+
export const CONTENT_LIST_LIFECYCLE_ACTION_IDS = [
|
|
14
|
+
'move-to-trash',
|
|
15
|
+
'restore',
|
|
16
|
+
'permanent-delete',
|
|
17
|
+
];
|
|
18
|
+
export class ContentListLifecycleTransportError extends Error {
|
|
19
|
+
status;
|
|
20
|
+
reason;
|
|
21
|
+
constructor(message, status, reason = 'http_error') {
|
|
22
|
+
super(message);
|
|
23
|
+
this.name = 'ContentListLifecycleTransportError';
|
|
24
|
+
this.status = status;
|
|
25
|
+
this.reason = reason;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const DEFAULT_IDENTITY = {
|
|
29
|
+
surfaceId: 'content-list',
|
|
30
|
+
kind: 'table',
|
|
31
|
+
};
|
|
32
|
+
const RENEWAL_REASONS = new Set([
|
|
33
|
+
'stale_preview',
|
|
34
|
+
'stale_revision',
|
|
35
|
+
'invalid_or_expired_confirmation',
|
|
36
|
+
'confirmation_mismatch',
|
|
37
|
+
'confirmation_replayed',
|
|
38
|
+
'count_drifted',
|
|
39
|
+
'matching_count_drifted',
|
|
40
|
+
'row_revision_drifted',
|
|
41
|
+
'stale_query_fingerprint',
|
|
42
|
+
]);
|
|
43
|
+
function isRecord(value) {
|
|
44
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
45
|
+
}
|
|
46
|
+
function identityMatches(value, expected) {
|
|
47
|
+
if (!isRecord(value))
|
|
48
|
+
return false;
|
|
49
|
+
const expectedSubject = expected.subject;
|
|
50
|
+
const subject = isRecord(value.subject) ? value.subject : undefined;
|
|
51
|
+
return (value.surfaceId === expected.surfaceId &&
|
|
52
|
+
value.kind === expected.kind &&
|
|
53
|
+
(expectedSubject
|
|
54
|
+
? subject?.type === expectedSubject.type &&
|
|
55
|
+
subject.id === expectedSubject.id
|
|
56
|
+
: value.subject === undefined));
|
|
57
|
+
}
|
|
58
|
+
function joinUrl(base, path) {
|
|
59
|
+
return `${base.replace(/\/+$/, '')}/${path.replace(/^\/+/, '')}`;
|
|
60
|
+
}
|
|
61
|
+
function messageOf(error) {
|
|
62
|
+
if (error instanceof Error && error.message.trim())
|
|
63
|
+
return error.message;
|
|
64
|
+
if (typeof error === 'string' && error.trim())
|
|
65
|
+
return error;
|
|
66
|
+
return 'The content lifecycle request failed.';
|
|
67
|
+
}
|
|
68
|
+
function boundedId(prefix) {
|
|
69
|
+
const random = globalThis.crypto?.randomUUID?.();
|
|
70
|
+
if (random)
|
|
71
|
+
return `${prefix}-${random}`;
|
|
72
|
+
return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
73
|
+
}
|
|
74
|
+
/** Authenticated fetch transport for the host's lifecycle route. */
|
|
75
|
+
export function createContentListLifecycleTransport(options = {}) {
|
|
76
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
77
|
+
if (typeof fetchImpl !== 'function') {
|
|
78
|
+
throw new TypeError('createContentListLifecycleTransport requires a fetch implementation');
|
|
79
|
+
}
|
|
80
|
+
const url = joinUrl(options.apiBaseUrl ?? '/api/v1', options.path ?? 'contents/lifecycle');
|
|
81
|
+
async function invoke(request) {
|
|
82
|
+
let headers;
|
|
83
|
+
try {
|
|
84
|
+
const provided = typeof options.headers === 'function'
|
|
85
|
+
? await options.headers()
|
|
86
|
+
: options.headers;
|
|
87
|
+
headers = new Headers(provided);
|
|
88
|
+
headers.set('content-type', 'application/json');
|
|
89
|
+
if (!headers.has('accept'))
|
|
90
|
+
headers.set('accept', 'application/json');
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
throw new ContentListLifecycleTransportError('The content lifecycle request could not be prepared.', undefined, 'network_failure');
|
|
94
|
+
}
|
|
95
|
+
let response;
|
|
96
|
+
try {
|
|
97
|
+
response = await fetchImpl(url, {
|
|
98
|
+
method: 'POST',
|
|
99
|
+
headers,
|
|
100
|
+
body: JSON.stringify(request),
|
|
101
|
+
...(options.credentials ? { credentials: options.credentials } : {}),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
throw new ContentListLifecycleTransportError('The content lifecycle request failed.', undefined, 'network_failure');
|
|
106
|
+
}
|
|
107
|
+
let payload;
|
|
108
|
+
try {
|
|
109
|
+
const text = await response.text();
|
|
110
|
+
payload = text ? JSON.parse(text) : undefined;
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
throw new ContentListLifecycleTransportError('The content lifecycle route returned invalid JSON.', response.status, 'invalid_json');
|
|
114
|
+
}
|
|
115
|
+
if (!response.ok) {
|
|
116
|
+
const error = isRecord(payload) && isRecord(payload.error)
|
|
117
|
+
? payload.error
|
|
118
|
+
: undefined;
|
|
119
|
+
throw new ContentListLifecycleTransportError(typeof error?.message === 'string'
|
|
120
|
+
? error.message
|
|
121
|
+
: `The content lifecycle request failed with HTTP ${response.status}.`, response.status, 'http_error');
|
|
122
|
+
}
|
|
123
|
+
const result = isRecord(payload) && Object.hasOwn(payload, 'result')
|
|
124
|
+
? payload.result
|
|
125
|
+
: payload;
|
|
126
|
+
if (!isRecord(result) ||
|
|
127
|
+
result.version !== 1 ||
|
|
128
|
+
typeof result.ok !== 'boolean' ||
|
|
129
|
+
result.requestId !== request.requestId ||
|
|
130
|
+
result.actionId !== request.actionId ||
|
|
131
|
+
result.phase !== request.phase ||
|
|
132
|
+
!identityMatches(result.identity, request.identity)) {
|
|
133
|
+
throw new ContentListLifecycleTransportError('The content lifecycle route returned an invalid result.', response.status, 'invalid_result');
|
|
134
|
+
}
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
return { preview: invoke, apply: invoke };
|
|
138
|
+
}
|
|
139
|
+
/** Parse the bounded, optional preview/apply details without trusting casts. */
|
|
140
|
+
export function readContentListLifecycleSummary(result) {
|
|
141
|
+
const details = isRecord(result.details) ? result.details : {};
|
|
142
|
+
const outcomes = Array.isArray(details.outcomes)
|
|
143
|
+
? details.outcomes.flatMap((entry) => {
|
|
144
|
+
if (!isRecord(entry))
|
|
145
|
+
return [];
|
|
146
|
+
if ((typeof entry.rowId !== 'string' &&
|
|
147
|
+
typeof entry.rowId !== 'number') ||
|
|
148
|
+
!['accepted', 'skipped', 'failed'].includes(String(entry.status)))
|
|
149
|
+
return [];
|
|
150
|
+
return [
|
|
151
|
+
{
|
|
152
|
+
rowId: entry.rowId,
|
|
153
|
+
status: entry.status,
|
|
154
|
+
...(typeof entry.reason === 'string'
|
|
155
|
+
? { reason: entry.reason }
|
|
156
|
+
: {}),
|
|
157
|
+
...(typeof entry.resourceType === 'string'
|
|
158
|
+
? { resourceType: entry.resourceType }
|
|
159
|
+
: {}),
|
|
160
|
+
...(typeof entry.resourceId === 'string'
|
|
161
|
+
? { resourceId: entry.resourceId }
|
|
162
|
+
: {}),
|
|
163
|
+
},
|
|
164
|
+
];
|
|
165
|
+
})
|
|
166
|
+
: [];
|
|
167
|
+
const number = (value) => Number.isSafeInteger(value) && Number(value) >= 0 ? Number(value) : 0;
|
|
168
|
+
const representativeLabels = Array.isArray(details.representativeLabels)
|
|
169
|
+
? details.representativeLabels.filter((label) => typeof label === 'string')
|
|
170
|
+
: [];
|
|
171
|
+
return {
|
|
172
|
+
resolvedCount: number(details.count),
|
|
173
|
+
accepted: number(details.accepted),
|
|
174
|
+
skipped: number(details.skipped),
|
|
175
|
+
failed: number(details.failed),
|
|
176
|
+
outcomes,
|
|
177
|
+
representativeLabels,
|
|
178
|
+
...(typeof details.expiresAt === 'number' &&
|
|
179
|
+
Number.isFinite(details.expiresAt)
|
|
180
|
+
? { expiresAt: details.expiresAt }
|
|
181
|
+
: {}),
|
|
182
|
+
auditReference: typeof details.auditReference === 'string'
|
|
183
|
+
? details.auditReference
|
|
184
|
+
: result.requestId,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function cloneSelection(selection) {
|
|
188
|
+
return selection.scope === 'explicit-ids'
|
|
189
|
+
? { scope: 'explicit-ids', rowIds: [...selection.rowIds] }
|
|
190
|
+
: { ...selection };
|
|
191
|
+
}
|
|
192
|
+
function requestPayload(input) {
|
|
193
|
+
return input.actionId === 'restore'
|
|
194
|
+
? { status: input.restoreStatus ?? 'draft' }
|
|
195
|
+
: undefined;
|
|
196
|
+
}
|
|
197
|
+
/** Framework-free state machine shared by UI and agent-driven lifecycle flows. */
|
|
198
|
+
export function createContentListLifecycleController(options) {
|
|
199
|
+
const listeners = new Set();
|
|
200
|
+
const identity = options.identity ?? DEFAULT_IDENTITY;
|
|
201
|
+
const createRequestId = options.createRequestId ?? (() => boundedId('lifecycle'));
|
|
202
|
+
const createIdempotencyKey = options.createIdempotencyKey ?? (() => boundedId('lifecycle-apply'));
|
|
203
|
+
let state = {
|
|
204
|
+
status: 'idle',
|
|
205
|
+
renewalRequired: false,
|
|
206
|
+
};
|
|
207
|
+
let generation = 0;
|
|
208
|
+
let previewViewKey;
|
|
209
|
+
let frozen;
|
|
210
|
+
let pendingApply;
|
|
211
|
+
const publish = (next) => {
|
|
212
|
+
state = next;
|
|
213
|
+
for (const listener of listeners)
|
|
214
|
+
listener({ ...state });
|
|
215
|
+
};
|
|
216
|
+
const reset = () => {
|
|
217
|
+
if (pendingApply)
|
|
218
|
+
return;
|
|
219
|
+
generation += 1;
|
|
220
|
+
previewViewKey = undefined;
|
|
221
|
+
frozen = undefined;
|
|
222
|
+
pendingApply = undefined;
|
|
223
|
+
publish({ status: 'idle', renewalRequired: false });
|
|
224
|
+
};
|
|
225
|
+
const preview = async (input) => {
|
|
226
|
+
if (pendingApply)
|
|
227
|
+
return state;
|
|
228
|
+
const currentGeneration = ++generation;
|
|
229
|
+
previewViewKey = input.viewKey;
|
|
230
|
+
frozen = undefined;
|
|
231
|
+
const selection = cloneSelection(input.selection);
|
|
232
|
+
if (input.expectedCount < 0 ||
|
|
233
|
+
!Number.isSafeInteger(input.expectedCount) ||
|
|
234
|
+
input.expectedCount > (options.maxSelectionSize ?? 200) ||
|
|
235
|
+
(selection.scope === 'all-matching' && !input.query) ||
|
|
236
|
+
(selection.scope === 'explicit-ids' && selection.rowIds.length === 0)) {
|
|
237
|
+
const failed = {
|
|
238
|
+
status: 'failed',
|
|
239
|
+
actionId: input.actionId,
|
|
240
|
+
selection,
|
|
241
|
+
error: 'The lifecycle selection is invalid.',
|
|
242
|
+
renewalRequired: true,
|
|
243
|
+
};
|
|
244
|
+
publish(failed);
|
|
245
|
+
return failed;
|
|
246
|
+
}
|
|
247
|
+
const payload = requestPayload(input);
|
|
248
|
+
const request = {
|
|
249
|
+
version: 1,
|
|
250
|
+
requestId: createRequestId(),
|
|
251
|
+
identity,
|
|
252
|
+
actionId: input.actionId,
|
|
253
|
+
phase: 'preview',
|
|
254
|
+
selection,
|
|
255
|
+
expectedRevision: options.revision ?? 0,
|
|
256
|
+
target: {
|
|
257
|
+
expectedCount: input.expectedCount,
|
|
258
|
+
...(input.query ? { query: input.query } : {}),
|
|
259
|
+
},
|
|
260
|
+
...(payload === undefined ? {} : { payload }),
|
|
261
|
+
};
|
|
262
|
+
publish({
|
|
263
|
+
status: 'previewing',
|
|
264
|
+
actionId: input.actionId,
|
|
265
|
+
selection,
|
|
266
|
+
renewalRequired: false,
|
|
267
|
+
});
|
|
268
|
+
try {
|
|
269
|
+
const result = await options.client.preview(request);
|
|
270
|
+
if (currentGeneration !== generation)
|
|
271
|
+
return state;
|
|
272
|
+
previewViewKey = undefined;
|
|
273
|
+
const summary = readContentListLifecycleSummary(result);
|
|
274
|
+
if (!result.ok || !result.confirmationToken) {
|
|
275
|
+
const reason = result.reason ?? 'preview_failed';
|
|
276
|
+
publish({
|
|
277
|
+
status: 'failed',
|
|
278
|
+
actionId: input.actionId,
|
|
279
|
+
selection,
|
|
280
|
+
result,
|
|
281
|
+
summary,
|
|
282
|
+
error: reason,
|
|
283
|
+
renewalRequired: RENEWAL_REASONS.has(reason),
|
|
284
|
+
});
|
|
285
|
+
return state;
|
|
286
|
+
}
|
|
287
|
+
frozen = { request, viewKey: input.viewKey, result };
|
|
288
|
+
publish({
|
|
289
|
+
status: 'ready',
|
|
290
|
+
actionId: input.actionId,
|
|
291
|
+
selection,
|
|
292
|
+
result,
|
|
293
|
+
summary,
|
|
294
|
+
renewalRequired: false,
|
|
295
|
+
});
|
|
296
|
+
return state;
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
if (currentGeneration !== generation)
|
|
300
|
+
return state;
|
|
301
|
+
previewViewKey = undefined;
|
|
302
|
+
publish({
|
|
303
|
+
status: 'failed',
|
|
304
|
+
actionId: input.actionId,
|
|
305
|
+
selection,
|
|
306
|
+
error: messageOf(error),
|
|
307
|
+
renewalRequired: true,
|
|
308
|
+
});
|
|
309
|
+
return state;
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
const apply = async (confirmedCount) => {
|
|
313
|
+
const previewed = frozen;
|
|
314
|
+
const summary = state.summary;
|
|
315
|
+
const expired = summary?.expiresAt !== undefined && summary.expiresAt <= Date.now();
|
|
316
|
+
const retry = state.status === 'failed' ? pendingApply : undefined;
|
|
317
|
+
if ((state.status !== 'ready' && !retry) ||
|
|
318
|
+
!previewed ||
|
|
319
|
+
!summary ||
|
|
320
|
+
confirmedCount !== summary.resolvedCount ||
|
|
321
|
+
(retry !== undefined && retry.confirmedCount !== confirmedCount) ||
|
|
322
|
+
expired) {
|
|
323
|
+
frozen = undefined;
|
|
324
|
+
publish({
|
|
325
|
+
...state,
|
|
326
|
+
status: 'failed',
|
|
327
|
+
error: expired
|
|
328
|
+
? 'The lifecycle preview expired and must be renewed.'
|
|
329
|
+
: 'The confirmed count no longer matches the lifecycle preview.',
|
|
330
|
+
renewalRequired: true,
|
|
331
|
+
});
|
|
332
|
+
return state;
|
|
333
|
+
}
|
|
334
|
+
const currentGeneration = ++generation;
|
|
335
|
+
const request = retry
|
|
336
|
+
? { ...retry.request, requestId: createRequestId() }
|
|
337
|
+
: {
|
|
338
|
+
...previewed.request,
|
|
339
|
+
requestId: createRequestId(),
|
|
340
|
+
phase: 'apply',
|
|
341
|
+
target: {
|
|
342
|
+
...previewed.request.target,
|
|
343
|
+
...(previewed.request.actionId === 'permanent-delete'
|
|
344
|
+
? { confirmedCount }
|
|
345
|
+
: {}),
|
|
346
|
+
},
|
|
347
|
+
confirmationToken: previewed.result.confirmationToken,
|
|
348
|
+
idempotencyKey: createIdempotencyKey(),
|
|
349
|
+
};
|
|
350
|
+
pendingApply = { request, confirmedCount };
|
|
351
|
+
publish({ ...state, status: 'applying', error: undefined });
|
|
352
|
+
try {
|
|
353
|
+
const result = await options.client.apply(request);
|
|
354
|
+
if (currentGeneration !== generation)
|
|
355
|
+
return state;
|
|
356
|
+
const nextSummary = readContentListLifecycleSummary(result);
|
|
357
|
+
if (!result.ok) {
|
|
358
|
+
const reason = result.reason ?? 'apply_failed';
|
|
359
|
+
if (reason === 'idempotency_in_progress') {
|
|
360
|
+
publish({
|
|
361
|
+
...state,
|
|
362
|
+
status: 'failed',
|
|
363
|
+
result,
|
|
364
|
+
error: reason,
|
|
365
|
+
renewalRequired: false,
|
|
366
|
+
replayRequired: true,
|
|
367
|
+
});
|
|
368
|
+
return state;
|
|
369
|
+
}
|
|
370
|
+
frozen = undefined;
|
|
371
|
+
pendingApply = undefined;
|
|
372
|
+
publish({
|
|
373
|
+
...state,
|
|
374
|
+
status: 'failed',
|
|
375
|
+
result,
|
|
376
|
+
summary: nextSummary,
|
|
377
|
+
error: reason,
|
|
378
|
+
renewalRequired: RENEWAL_REASONS.has(reason),
|
|
379
|
+
replayRequired: false,
|
|
380
|
+
});
|
|
381
|
+
return state;
|
|
382
|
+
}
|
|
383
|
+
frozen = undefined;
|
|
384
|
+
pendingApply = undefined;
|
|
385
|
+
publish({
|
|
386
|
+
...state,
|
|
387
|
+
status: 'succeeded',
|
|
388
|
+
result,
|
|
389
|
+
summary: nextSummary,
|
|
390
|
+
renewalRequired: false,
|
|
391
|
+
replayRequired: false,
|
|
392
|
+
});
|
|
393
|
+
return state;
|
|
394
|
+
}
|
|
395
|
+
catch (error) {
|
|
396
|
+
if (currentGeneration !== generation)
|
|
397
|
+
return state;
|
|
398
|
+
publish({
|
|
399
|
+
...state,
|
|
400
|
+
status: 'failed',
|
|
401
|
+
error: messageOf(error),
|
|
402
|
+
// The server may have committed before the response was lost. Retain
|
|
403
|
+
// the exact authority/idempotency envelope so retry can replay safely
|
|
404
|
+
// with a fresh transport correlation id.
|
|
405
|
+
renewalRequired: false,
|
|
406
|
+
replayRequired: true,
|
|
407
|
+
});
|
|
408
|
+
return state;
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
return {
|
|
412
|
+
snapshot: () => ({ ...state }),
|
|
413
|
+
subscribe(listener) {
|
|
414
|
+
listeners.add(listener);
|
|
415
|
+
listener({ ...state });
|
|
416
|
+
return () => listeners.delete(listener);
|
|
417
|
+
},
|
|
418
|
+
preview,
|
|
419
|
+
apply,
|
|
420
|
+
invalidate(viewKey) {
|
|
421
|
+
// Once apply starts, the server-authorized mutation owns completion.
|
|
422
|
+
// A changing view must not discard its refresh, audit, or reconciliation.
|
|
423
|
+
if (state.status === 'applying' || pendingApply)
|
|
424
|
+
return;
|
|
425
|
+
if ((previewViewKey !== undefined && previewViewKey !== viewKey) ||
|
|
426
|
+
(frozen !== undefined && frozen.viewKey !== viewKey)) {
|
|
427
|
+
reset();
|
|
428
|
+
}
|
|
429
|
+
},
|
|
430
|
+
reset,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
/** Keep unsuccessful rows selected so partial failures are visible/retryable. */
|
|
434
|
+
export function reconcileContentListLifecycleSelection(selectedRowIds, result) {
|
|
435
|
+
const accepted = new Set(readContentListLifecycleSummary(result)
|
|
436
|
+
.outcomes.filter((outcome) => outcome.status === 'accepted')
|
|
437
|
+
.map((outcome) => String(outcome.rowId)));
|
|
438
|
+
return selectedRowIds.filter((rowId) => !accepted.has(String(rowId)));
|
|
439
|
+
}
|