@agent-native/creative-context 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/README.md +51 -9
  2. package/agent-native.package.json +4 -0
  3. package/dist/actions/index.d.ts.map +1 -1
  4. package/dist/actions/index.js +8 -0
  5. package/dist/actions/index.js.map +1 -1
  6. package/dist/actions/list-context-memberships.d.ts +11 -0
  7. package/dist/actions/list-context-memberships.d.ts.map +1 -0
  8. package/dist/actions/list-context-memberships.js +47 -0
  9. package/dist/actions/list-context-memberships.js.map +1 -0
  10. package/dist/actions/list-creative-contexts.d.ts +12 -0
  11. package/dist/actions/list-creative-contexts.d.ts.map +1 -0
  12. package/dist/actions/list-creative-contexts.js +28 -0
  13. package/dist/actions/list-creative-contexts.js.map +1 -0
  14. package/dist/actions/manage-context-membership.d.ts +64 -0
  15. package/dist/actions/manage-context-membership.d.ts.map +1 -0
  16. package/dist/actions/manage-context-membership.js +70 -0
  17. package/dist/actions/manage-context-membership.js.map +1 -0
  18. package/dist/actions/manage-context-pack.d.ts +3 -0
  19. package/dist/actions/manage-context-pack.js +6 -0
  20. package/dist/actions/manage-context-pack.js.map +1 -1
  21. package/dist/actions/manage-context-source.js +4 -11
  22. package/dist/actions/manage-context-source.js.map +1 -1
  23. package/dist/actions/manage-creative-context.d.ts +28 -0
  24. package/dist/actions/manage-creative-context.d.ts.map +1 -0
  25. package/dist/actions/manage-creative-context.js +65 -0
  26. package/dist/actions/manage-creative-context.js.map +1 -0
  27. package/dist/actions/process-context-purge.d.ts +16 -1
  28. package/dist/actions/process-context-purge.js +2 -1
  29. package/dist/actions/process-context-purge.js.map +1 -1
  30. package/dist/actions/search-creative-context.d.ts +1 -0
  31. package/dist/actions/search-creative-context.js +2 -1
  32. package/dist/actions/search-creative-context.js.map +1 -1
  33. package/dist/client/CreativeContextChip.d.ts +7 -2
  34. package/dist/client/CreativeContextChip.d.ts.map +1 -1
  35. package/dist/client/CreativeContextChip.js +42 -7
  36. package/dist/client/CreativeContextChip.js.map +1 -1
  37. package/dist/client/CreativeContextPanel.d.ts.map +1 -1
  38. package/dist/client/CreativeContextPanel.js +406 -89
  39. package/dist/client/CreativeContextPanel.js.map +1 -1
  40. package/dist/client/CreativeContextShareTab.d.ts +56 -0
  41. package/dist/client/CreativeContextShareTab.d.ts.map +1 -0
  42. package/dist/client/CreativeContextShareTab.js +213 -0
  43. package/dist/client/CreativeContextShareTab.js.map +1 -0
  44. package/dist/client/actions.d.ts +210 -0
  45. package/dist/client/actions.d.ts.map +1 -1
  46. package/dist/client/actions.js +318 -0
  47. package/dist/client/actions.js.map +1 -1
  48. package/dist/client/application-state.d.ts +2 -0
  49. package/dist/client/application-state.d.ts.map +1 -1
  50. package/dist/client/application-state.js +25 -2
  51. package/dist/client/application-state.js.map +1 -1
  52. package/dist/client/index.d.ts +4 -3
  53. package/dist/client/index.d.ts.map +1 -1
  54. package/dist/client/index.js +4 -3
  55. package/dist/client/index.js.map +1 -1
  56. package/dist/client/messages.d.ts +1 -1
  57. package/dist/client/messages.d.ts.map +1 -1
  58. package/dist/client/messages.js +77 -0
  59. package/dist/client/messages.js.map +1 -1
  60. package/dist/connectors/types.d.ts +2 -2
  61. package/dist/connectors/types.d.ts.map +1 -1
  62. package/dist/schema/index.d.ts +1592 -0
  63. package/dist/schema/index.d.ts.map +1 -1
  64. package/dist/schema/index.js +95 -0
  65. package/dist/schema/index.js.map +1 -1
  66. package/dist/schema/migrations.d.ts.map +1 -1
  67. package/dist/schema/migrations.js +80 -0
  68. package/dist/schema/migrations.js.map +1 -1
  69. package/dist/server/generation-artifact-access.js +2 -0
  70. package/dist/server/generation-artifact-access.js.map +1 -1
  71. package/dist/server/generation-context.d.ts +12 -4
  72. package/dist/server/generation-context.d.ts.map +1 -1
  73. package/dist/server/generation-context.js +129 -7
  74. package/dist/server/generation-context.js.map +1 -1
  75. package/dist/server/index.d.ts +4 -0
  76. package/dist/server/index.d.ts.map +1 -1
  77. package/dist/server/index.js +16 -0
  78. package/dist/server/index.js.map +1 -1
  79. package/dist/server/isolated-a2a.d.ts +3 -0
  80. package/dist/server/isolated-a2a.d.ts.map +1 -1
  81. package/dist/server/isolated-a2a.js +3 -1
  82. package/dist/server/isolated-a2a.js.map +1 -1
  83. package/dist/server/media.d.ts +1 -1
  84. package/dist/server/media.d.ts.map +1 -1
  85. package/dist/server/media.js +24 -9
  86. package/dist/server/media.js.map +1 -1
  87. package/dist/server/native-resource-capture.d.ts +78 -0
  88. package/dist/server/native-resource-capture.d.ts.map +1 -0
  89. package/dist/server/native-resource-capture.js +94 -0
  90. package/dist/server/native-resource-capture.js.map +1 -0
  91. package/dist/server/public-serialization.js +1 -1
  92. package/dist/server/public-serialization.js.map +1 -1
  93. package/dist/server/retrieval.d.ts +1 -0
  94. package/dist/server/retrieval.d.ts.map +1 -1
  95. package/dist/server/retrieval.js +2 -0
  96. package/dist/server/retrieval.js.map +1 -1
  97. package/dist/server/safe-native-preview.d.ts +15 -0
  98. package/dist/server/safe-native-preview.d.ts.map +1 -0
  99. package/dist/server/safe-native-preview.js +149 -0
  100. package/dist/server/safe-native-preview.js.map +1 -0
  101. package/dist/store/brand.d.ts.map +1 -1
  102. package/dist/store/brand.js +2 -16
  103. package/dist/store/brand.js.map +1 -1
  104. package/dist/store/content.d.ts +1 -0
  105. package/dist/store/content.d.ts.map +1 -1
  106. package/dist/store/content.js +84 -20
  107. package/dist/store/content.js.map +1 -1
  108. package/dist/store/contexts.d.ts +216 -0
  109. package/dist/store/contexts.d.ts.map +1 -0
  110. package/dist/store/contexts.js +1341 -0
  111. package/dist/store/contexts.js.map +1 -0
  112. package/dist/store/index.d.ts +1 -0
  113. package/dist/store/index.d.ts.map +1 -1
  114. package/dist/store/index.js +1 -0
  115. package/dist/store/index.js.map +1 -1
  116. package/dist/store/packs.d.ts +3 -0
  117. package/dist/store/packs.d.ts.map +1 -1
  118. package/dist/store/packs.js +126 -9
  119. package/dist/store/packs.js.map +1 -1
  120. package/dist/store/purge.d.ts.map +1 -1
  121. package/dist/store/purge.js +103 -6
  122. package/dist/store/purge.js.map +1 -1
  123. package/dist/store/sources.d.ts.map +1 -1
  124. package/dist/store/sources.js +2 -15
  125. package/dist/store/sources.js.map +1 -1
  126. package/dist/store/suggestions.d.ts.map +1 -1
  127. package/dist/store/suggestions.js +3 -1
  128. package/dist/store/suggestions.js.map +1 -1
  129. package/dist/types.d.ts +77 -0
  130. package/dist/types.d.ts.map +1 -1
  131. package/dist/types.js +9 -1
  132. package/dist/types.js.map +1 -1
  133. package/docs/skills/creative-context/SKILL.md +13 -5
  134. package/package.json +2 -2
  135. package/src/actions/index.ts +8 -0
  136. package/src/actions/job-public-serialization.spec.ts +21 -0
  137. package/src/actions/list-context-memberships.ts +56 -0
  138. package/src/actions/list-creative-contexts.spec.ts +38 -0
  139. package/src/actions/list-creative-contexts.ts +32 -0
  140. package/src/actions/manage-context-membership.ts +80 -0
  141. package/src/actions/manage-context-pack.ts +6 -0
  142. package/src/actions/manage-context-source.ts +8 -11
  143. package/src/actions/manage-creative-context.ts +73 -0
  144. package/src/actions/process-context-purge.ts +4 -1
  145. package/src/actions/search-creative-context.ts +2 -1
  146. package/src/client/CreativeContextChip.spec.ts +59 -0
  147. package/src/client/CreativeContextChip.tsx +95 -14
  148. package/src/client/CreativeContextPanel.tsx +1947 -885
  149. package/src/client/CreativeContextShareTab.spec.ts +70 -0
  150. package/src/client/CreativeContextShareTab.tsx +657 -0
  151. package/src/client/actions.spec.ts +177 -0
  152. package/src/client/actions.ts +566 -0
  153. package/src/client/application-state.spec.ts +5 -0
  154. package/src/client/application-state.ts +30 -2
  155. package/src/client/index.ts +33 -0
  156. package/src/client/messages.ts +95 -0
  157. package/src/connectors/types.ts +6 -8
  158. package/src/eval/library-flow.acceptance.spec.tsx +1 -0
  159. package/src/schema/index.ts +116 -0
  160. package/src/schema/migrations.ts +80 -0
  161. package/src/server/generation-artifact-access.ts +1 -0
  162. package/src/server/generation-context-a2a.spec.ts +133 -0
  163. package/src/server/generation-context.ts +153 -7
  164. package/src/server/index.spec.ts +15 -0
  165. package/src/server/index.ts +16 -0
  166. package/src/server/isolated-a2a.ts +4 -1
  167. package/src/server/media.spec.ts +30 -2
  168. package/src/server/media.ts +31 -12
  169. package/src/server/native-resource-capture.spec.ts +121 -0
  170. package/src/server/native-resource-capture.ts +198 -0
  171. package/src/server/public-serialization.spec.ts +1 -0
  172. package/src/server/public-serialization.ts +1 -1
  173. package/src/server/retrieval.ts +3 -0
  174. package/src/server/safe-native-preview.spec.ts +29 -0
  175. package/src/server/safe-native-preview.ts +200 -0
  176. package/src/store/access.integration.spec.ts +1116 -0
  177. package/src/store/brand.ts +2 -21
  178. package/src/store/content.ts +217 -17
  179. package/src/store/contexts.ts +1826 -0
  180. package/src/store/index.ts +1 -0
  181. package/src/store/packs.ts +279 -24
  182. package/src/store/purge.ts +139 -6
  183. package/src/store/sources.ts +2 -18
  184. package/src/store/suggestions.ts +3 -1
  185. package/src/types.ts +95 -0
@@ -18,6 +18,10 @@ import type {
18
18
  } from "../types.js";
19
19
 
20
20
  export const CREATIVE_CONTEXT_ACTIONS = {
21
+ listContexts: "list-creative-contexts",
22
+ manageContext: "manage-creative-context",
23
+ listMemberships: "list-context-memberships",
24
+ manageMembership: "manage-context-membership",
21
25
  listSources: "list-context-sources",
22
26
  manageSource: "manage-context-source",
23
27
  previewImport: "preview-context-import",
@@ -41,6 +45,536 @@ export const CREATIVE_CONTEXT_ACTIONS = {
41
45
  manageLayoutTemplate: "manage-layout-template",
42
46
  } as const;
43
47
 
48
+ export type CreativeContextPolicy = "open" | "review" | "admins-only";
49
+ export type CreativeContextMembershipRank = "canonical" | "exemplar" | "normal";
50
+
51
+ export type CreativeContextSafePreview =
52
+ | {
53
+ type: "slides";
54
+ slideCount: number;
55
+ slides: Array<{ index: number; title: string; excerpt: string }>;
56
+ }
57
+ | { type: "slide"; index: number; title: string; excerpt: string }
58
+ | {
59
+ type: "design";
60
+ fileCount: number;
61
+ frames: Array<{ title: string; fileType: string; excerpt: string }>;
62
+ }
63
+ | {
64
+ type: "design-frame";
65
+ title: string;
66
+ fileType: string;
67
+ excerpt: string;
68
+ }
69
+ | {
70
+ type: "document";
71
+ headings: string[];
72
+ excerpt: string;
73
+ blocks: Array<{
74
+ kind: "heading" | "paragraph" | "bullet" | "quote" | "code";
75
+ text: string;
76
+ level?: number;
77
+ }>;
78
+ }
79
+ | {
80
+ type: "asset";
81
+ mediaType: "image" | "video";
82
+ width: number | null;
83
+ height: number | null;
84
+ durationSeconds: number | null;
85
+ }
86
+ | {
87
+ type: "dashboard";
88
+ data: "synthetic";
89
+ panels: Array<{ id: string; title: string; visualization: string }>;
90
+ };
91
+
92
+ export interface CreativeContextSummary {
93
+ id: string;
94
+ name: string;
95
+ description?: string | null;
96
+ kind: "default" | "specialty";
97
+ memberCount: number;
98
+ updatedAt?: string | null;
99
+ approvalPolicy: CreativeContextPolicy;
100
+ visibility: "private" | "org" | "public";
101
+ access: {
102
+ role: "viewer" | "editor" | "admin" | "owner";
103
+ canSubmit: boolean;
104
+ canReview: boolean;
105
+ canAdmin: boolean;
106
+ };
107
+ }
108
+
109
+ export interface CreativeContextPreviewItem {
110
+ id: string;
111
+ itemVersionId: string;
112
+ title: string;
113
+ kind: string;
114
+ status: string;
115
+ sourceModifiedAt: string | null;
116
+ preview: CreativeContextSafePreview | null;
117
+ media: Array<{
118
+ id: string;
119
+ kind: string;
120
+ mimeType: string | null;
121
+ url: string;
122
+ }>;
123
+ }
124
+
125
+ export interface CreativeContextMembership {
126
+ id: string;
127
+ contextId: string;
128
+ publishedItemId: string | null;
129
+ publishedItemVersionId: string | null;
130
+ pendingSubmissionId: string | null;
131
+ rank: CreativeContextMembershipRank;
132
+ purpose: string | null;
133
+ status: "active" | "removed";
134
+ updatedAt?: string | null;
135
+ nativeUpdateStatus?: {
136
+ state: "current" | "update-available" | "unknown";
137
+ } | null;
138
+ publishedItem?: CreativeContextPreviewItem | null;
139
+ pendingSubmission?: {
140
+ id: string;
141
+ status: string;
142
+ note: string | null;
143
+ submittedBy: string;
144
+ proposedItem: CreativeContextPreviewItem | null;
145
+ } | null;
146
+ }
147
+
148
+ export interface ListCreativeContextsParams {
149
+ limit?: number;
150
+ cursor?: string;
151
+ includeArchived?: boolean;
152
+ }
153
+
154
+ export interface ListCreativeContextsResult {
155
+ contexts: CreativeContextSummary[];
156
+ appId?: string;
157
+ appDefaultContextId?: string | null;
158
+ }
159
+
160
+ export type ManageCreativeContextParams =
161
+ | {
162
+ operation: "create";
163
+ name: string;
164
+ description?: string | null;
165
+ kind: "default" | "specialty";
166
+ brandProfileId?: string | null;
167
+ approvalPolicy?: CreativeContextPolicy;
168
+ }
169
+ | {
170
+ operation: "update";
171
+ contextId: string;
172
+ patch: {
173
+ name?: string;
174
+ description?: string | null;
175
+ brandProfileId?: string | null;
176
+ approvalPolicy?: CreativeContextPolicy;
177
+ };
178
+ }
179
+ | { operation: "archive"; contextId: string }
180
+ | { operation: "set-app-default"; contextId: string; appId: string };
181
+
182
+ export interface ManageCreativeContextResult {
183
+ context: CreativeContextSummary | null;
184
+ }
185
+
186
+ export interface ListContextMembershipsParams {
187
+ contextId: string;
188
+ status?: "active" | "removed";
189
+ limit?: number;
190
+ cursor?: string;
191
+ }
192
+
193
+ export interface ListContextMembershipsResult {
194
+ memberships: CreativeContextMembership[];
195
+ }
196
+
197
+ export type ManageContextMembershipParams =
198
+ | {
199
+ operation: "submit";
200
+ contextId: string;
201
+ itemId?: string;
202
+ itemVersionId?: string;
203
+ nativeResource?: {
204
+ appId: string;
205
+ resourceType: string;
206
+ resourceId: string;
207
+ expectedUpdatedAt?: string;
208
+ };
209
+ rank?: CreativeContextMembershipRank;
210
+ purpose?: string;
211
+ note?: string;
212
+ confirmBroaderPublication?: true;
213
+ }
214
+ | {
215
+ operation: "submit-latest";
216
+ contextId: string;
217
+ membershipId: string;
218
+ note?: string;
219
+ confirmBroaderPublication?: true;
220
+ }
221
+ | {
222
+ operation: "approve" | "request-changes" | "withdraw" | "remove";
223
+ contextId: string;
224
+ membershipId: string;
225
+ note?: string | null;
226
+ };
227
+
228
+ export interface ManageContextMembershipResult {
229
+ membership: CreativeContextMembership | null;
230
+ membershipId?: string;
231
+ submission?: { id: string; status: string };
232
+ withdrawn?: boolean;
233
+ approved?: boolean;
234
+ requestChanges?: boolean;
235
+ }
236
+
237
+ function record(value: unknown): Record<string, unknown> | null {
238
+ return value && typeof value === "object"
239
+ ? (value as Record<string, unknown>)
240
+ : null;
241
+ }
242
+
243
+ function previewString(value: unknown, limit: number) {
244
+ return typeof value === "string" ? value.slice(0, limit) : "";
245
+ }
246
+
247
+ function previewNumber(value: unknown) {
248
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
249
+ }
250
+
251
+ /**
252
+ * Accept only the compact structured preview contract. Native payloads and
253
+ * arbitrary item metadata deliberately never cross into the shared client.
254
+ */
255
+ export function parseCreativeContextSafePreview(
256
+ value: unknown,
257
+ ): CreativeContextSafePreview | null {
258
+ const preview = record(value);
259
+ if (!preview || typeof preview.type !== "string") return null;
260
+ if (preview.type === "slides") {
261
+ const slides = Array.isArray(preview.slides)
262
+ ? preview.slides.slice(0, 24).flatMap((entry, index) => {
263
+ const slide = record(entry);
264
+ if (!slide) return [];
265
+ return [
266
+ {
267
+ index:
268
+ typeof slide.index === "number" && slide.index > 0
269
+ ? Math.floor(slide.index)
270
+ : index + 1,
271
+ title: previewString(slide.title, 160) || `Slide ${index + 1}`,
272
+ excerpt: previewString(slide.excerpt, 320),
273
+ },
274
+ ];
275
+ })
276
+ : [];
277
+ return {
278
+ type: "slides",
279
+ slideCount:
280
+ typeof preview.slideCount === "number" && preview.slideCount >= 0
281
+ ? Math.floor(preview.slideCount)
282
+ : slides.length,
283
+ slides,
284
+ };
285
+ }
286
+ if (preview.type === "slide") {
287
+ return {
288
+ type: "slide",
289
+ index:
290
+ typeof preview.index === "number" && preview.index > 0
291
+ ? Math.floor(preview.index)
292
+ : 1,
293
+ title: previewString(preview.title, 160) || "Slide",
294
+ excerpt: previewString(preview.excerpt, 500),
295
+ };
296
+ }
297
+ if (preview.type === "design") {
298
+ const frames = Array.isArray(preview.frames)
299
+ ? preview.frames.slice(0, 24).flatMap((entry) => {
300
+ const frame = record(entry);
301
+ if (!frame) return [];
302
+ return [
303
+ {
304
+ title: previewString(frame.title, 160) || "Untitled frame",
305
+ fileType: previewString(frame.fileType, 80) || "design",
306
+ excerpt: previewString(frame.excerpt, 320),
307
+ },
308
+ ];
309
+ })
310
+ : [];
311
+ return {
312
+ type: "design",
313
+ fileCount:
314
+ typeof preview.fileCount === "number" && preview.fileCount >= 0
315
+ ? Math.floor(preview.fileCount)
316
+ : frames.length,
317
+ frames,
318
+ };
319
+ }
320
+ if (preview.type === "design-frame") {
321
+ return {
322
+ type: "design-frame",
323
+ title: previewString(preview.title, 160) || "Untitled frame",
324
+ fileType: previewString(preview.fileType, 80) || "design",
325
+ excerpt: previewString(preview.excerpt, 500),
326
+ };
327
+ }
328
+ if (preview.type === "document" || preview.type === "markdown") {
329
+ const headings = Array.isArray(preview.headings)
330
+ ? preview.headings
331
+ .slice(0, 8)
332
+ .map((heading) => previewString(heading, 160))
333
+ .filter(Boolean)
334
+ : [];
335
+ const blocks = Array.isArray(preview.blocks)
336
+ ? preview.blocks.slice(0, 40).flatMap((entry) => {
337
+ const block = record(entry);
338
+ if (!block) return [];
339
+ const kind: "heading" | "paragraph" | "bullet" | "quote" | "code" =
340
+ block.kind === "heading" ||
341
+ block.kind === "bullet" ||
342
+ block.kind === "quote" ||
343
+ block.kind === "code"
344
+ ? block.kind
345
+ : "paragraph";
346
+ const text = previewString(block.text, 600);
347
+ if (!text) return [];
348
+ const level =
349
+ kind === "heading" &&
350
+ typeof block.level === "number" &&
351
+ block.level >= 1 &&
352
+ block.level <= 6
353
+ ? Math.floor(block.level)
354
+ : undefined;
355
+ return [{ kind, text, ...(level ? { level } : {}) }];
356
+ })
357
+ : [];
358
+ return {
359
+ type: "document",
360
+ headings,
361
+ excerpt: previewString(preview.excerpt, 1_500),
362
+ blocks,
363
+ };
364
+ }
365
+ if (preview.type === "asset") {
366
+ return {
367
+ type: "asset",
368
+ mediaType: preview.mediaType === "video" ? "video" : "image",
369
+ width: previewNumber(preview.width),
370
+ height: previewNumber(preview.height),
371
+ durationSeconds: previewNumber(preview.durationSeconds),
372
+ };
373
+ }
374
+ if (preview.type === "dashboard") {
375
+ const panels = Array.isArray(preview.panels)
376
+ ? preview.panels.slice(0, 24).flatMap((entry, index) => {
377
+ const panel = record(entry);
378
+ if (!panel) return [];
379
+ return [
380
+ {
381
+ id: previewString(panel.id, 120) || String(index + 1),
382
+ title: previewString(panel.title, 160) || `Panel ${index + 1}`,
383
+ visualization: previewString(panel.visualization, 80) || "chart",
384
+ },
385
+ ];
386
+ })
387
+ : [];
388
+ return { type: "dashboard", data: "synthetic", panels };
389
+ }
390
+ return null;
391
+ }
392
+
393
+ function contextSummary(value: unknown): CreativeContextSummary | null {
394
+ const source = record(value);
395
+ if (
396
+ !source ||
397
+ typeof source.id !== "string" ||
398
+ typeof source.name !== "string"
399
+ ) {
400
+ return null;
401
+ }
402
+ const access = record(source.access);
403
+ const role =
404
+ access?.role === "owner" ||
405
+ access?.role === "admin" ||
406
+ access?.role === "editor"
407
+ ? access.role
408
+ : "viewer";
409
+ return {
410
+ id: source.id,
411
+ name: source.name,
412
+ description:
413
+ typeof source.description === "string" ? source.description : null,
414
+ kind: source.kind === "specialty" ? "specialty" : "default",
415
+ memberCount:
416
+ typeof source.memberCount === "number" ? source.memberCount : 0,
417
+ updatedAt: typeof source.updatedAt === "string" ? source.updatedAt : null,
418
+ approvalPolicy:
419
+ source.approvalPolicy === "review" ||
420
+ source.approvalPolicy === "admins-only"
421
+ ? source.approvalPolicy
422
+ : "open",
423
+ visibility:
424
+ source.visibility === "org" || source.visibility === "public"
425
+ ? source.visibility
426
+ : "private",
427
+ access: {
428
+ role,
429
+ canSubmit: access?.canSubmit === true,
430
+ canReview: access?.canReview === true,
431
+ canAdmin: access?.canAdmin === true,
432
+ },
433
+ };
434
+ }
435
+
436
+ export function parseCreativeContexts(
437
+ value: unknown,
438
+ ): CreativeContextSummary[] {
439
+ const source = Array.isArray(value)
440
+ ? value
441
+ : (record(value)?.contexts ?? record(value)?.items ?? []);
442
+ return Array.isArray(source)
443
+ ? source
444
+ .map(contextSummary)
445
+ .filter((item): item is CreativeContextSummary => Boolean(item))
446
+ : [];
447
+ }
448
+
449
+ function parseContextPreviewItem(
450
+ value: unknown,
451
+ ): CreativeContextPreviewItem | null {
452
+ const item = record(value);
453
+ if (
454
+ !item ||
455
+ typeof item.id !== "string" ||
456
+ typeof item.itemVersionId !== "string" ||
457
+ typeof item.title !== "string" ||
458
+ typeof item.kind !== "string"
459
+ )
460
+ return null;
461
+ const media = Array.isArray(item.media)
462
+ ? item.media.flatMap((value) => {
463
+ const medium = record(value);
464
+ return medium &&
465
+ typeof medium.id === "string" &&
466
+ typeof medium.kind === "string" &&
467
+ typeof medium.url === "string"
468
+ ? [
469
+ {
470
+ id: medium.id,
471
+ kind: medium.kind,
472
+ mimeType:
473
+ typeof medium.mimeType === "string" ? medium.mimeType : null,
474
+ url: medium.url,
475
+ },
476
+ ]
477
+ : [];
478
+ })
479
+ : [];
480
+ return {
481
+ id: item.id,
482
+ itemVersionId: item.itemVersionId,
483
+ title: item.title,
484
+ kind: item.kind,
485
+ status: typeof item.status === "string" ? item.status : "active",
486
+ sourceModifiedAt:
487
+ typeof item.sourceModifiedAt === "string" ? item.sourceModifiedAt : null,
488
+ preview: parseCreativeContextSafePreview(item.preview),
489
+ media,
490
+ };
491
+ }
492
+
493
+ export function parseContextMemberships(
494
+ value: unknown,
495
+ ): CreativeContextMembership[] {
496
+ const source = Array.isArray(value)
497
+ ? value
498
+ : (record(value)?.memberships ?? record(value)?.items ?? []);
499
+ if (!Array.isArray(source)) return [];
500
+ return source.flatMap((value) => {
501
+ const item = record(value);
502
+ if (
503
+ !item ||
504
+ typeof item.id !== "string" ||
505
+ typeof item.contextId !== "string"
506
+ ) {
507
+ return [];
508
+ }
509
+ return [
510
+ {
511
+ id: item.id,
512
+ contextId: item.contextId,
513
+ publishedItemId:
514
+ typeof item.publishedItemId === "string"
515
+ ? item.publishedItemId
516
+ : null,
517
+ publishedItemVersionId:
518
+ typeof item.publishedItemVersionId === "string"
519
+ ? item.publishedItemVersionId
520
+ : null,
521
+ pendingSubmissionId:
522
+ typeof item.pendingSubmissionId === "string"
523
+ ? item.pendingSubmissionId
524
+ : null,
525
+ rank:
526
+ item.rank === "canonical" || item.rank === "exemplar"
527
+ ? item.rank
528
+ : "normal",
529
+ purpose: typeof item.purpose === "string" ? item.purpose : null,
530
+ status: item.status === "removed" ? "removed" : "active",
531
+ updatedAt: typeof item.updatedAt === "string" ? item.updatedAt : null,
532
+ nativeUpdateStatus: (() => {
533
+ const status = record(item.nativeUpdateStatus)?.state;
534
+ return status === "current" ||
535
+ status === "update-available" ||
536
+ status === "unknown"
537
+ ? { state: status }
538
+ : null;
539
+ })(),
540
+ pendingSubmission: (() => {
541
+ const submission = record(item.pendingSubmission);
542
+ return submission &&
543
+ typeof submission.id === "string" &&
544
+ typeof submission.status === "string"
545
+ ? {
546
+ id: submission.id,
547
+ status: submission.status,
548
+ note:
549
+ typeof submission.note === "string" ? submission.note : null,
550
+ submittedBy:
551
+ typeof submission.submittedBy === "string"
552
+ ? submission.submittedBy
553
+ : "",
554
+ proposedItem: parseContextPreviewItem(submission.proposedItem),
555
+ }
556
+ : null;
557
+ })(),
558
+ publishedItem: parseContextPreviewItem(item.publishedItem),
559
+ },
560
+ ];
561
+ });
562
+ }
563
+
564
+ export function parseContextMembershipsForResource(
565
+ value: unknown,
566
+ resource: { appId: string; resourceType: string; resourceId: string },
567
+ ): CreativeContextMembership[] {
568
+ const source = record(value)?.memberships;
569
+ if (!Array.isArray(source)) return [];
570
+ const artifactKey = `${resource.appId}:${resource.resourceType}:${resource.resourceId}`;
571
+ return parseContextMemberships({
572
+ memberships: source.filter(
573
+ (value) => record(value)?.artifactKey === artifactKey,
574
+ ),
575
+ });
576
+ }
577
+
44
578
  export interface CanonicalLogoCandidate {
45
579
  mediaId: string;
46
580
  itemId: string;
@@ -80,6 +614,7 @@ export interface SearchCreativeContextParams {
80
614
  query: string;
81
615
  sourceIds?: string[];
82
616
  packId?: string;
617
+ contextId?: string;
83
618
  kinds?: string[];
84
619
  limit?: number;
85
620
  cursor?: string;
@@ -294,6 +829,37 @@ export function useCreativeContextSources(
294
829
  );
295
830
  }
296
831
 
832
+ export function useCreativeContexts(params: ListCreativeContextsParams = {}) {
833
+ return useActionQuery<ListCreativeContextsResult>(
834
+ CREATIVE_CONTEXT_ACTIONS.listContexts,
835
+ { limit: 50, ...params },
836
+ );
837
+ }
838
+
839
+ export function useManageCreativeContext() {
840
+ return useActionMutation<
841
+ ManageCreativeContextResult,
842
+ ManageCreativeContextParams
843
+ >(CREATIVE_CONTEXT_ACTIONS.manageContext);
844
+ }
845
+
846
+ export function useContextMemberships(
847
+ params: ListContextMembershipsParams | null,
848
+ ) {
849
+ return useActionQuery<ListContextMembershipsResult>(
850
+ CREATIVE_CONTEXT_ACTIONS.listMemberships,
851
+ params ? { limit: 50, ...params } : undefined,
852
+ { enabled: Boolean(params) },
853
+ );
854
+ }
855
+
856
+ export function useManageContextMembership() {
857
+ return useActionMutation<
858
+ ManageContextMembershipResult,
859
+ ManageContextMembershipParams
860
+ >(CREATIVE_CONTEXT_ACTIONS.manageMembership);
861
+ }
862
+
297
863
  export function useCreativeContextSearch() {
298
864
  return useActionMutation<
299
865
  SearchCreativeContextResult,
@@ -6,6 +6,7 @@ describe("normalizeCreativeContextState", () => {
6
6
  it("defaults missing state to automatic context", () => {
7
7
  expect(normalizeCreativeContextState(null)).toEqual({
8
8
  contextMode: "auto",
9
+ selectedContextId: null,
9
10
  currentPackId: null,
10
11
  pinnedPackId: null,
11
12
  });
@@ -15,11 +16,13 @@ describe("normalizeCreativeContextState", () => {
15
16
  expect(
16
17
  normalizeCreativeContextState({
17
18
  contextMode: "off",
19
+ selectedContextId: "selected-context",
18
20
  currentPackId: "current-pack",
19
21
  pinnedPackId: "pinned-pack",
20
22
  }),
21
23
  ).toEqual({
22
24
  contextMode: "off",
25
+ selectedContextId: null,
23
26
  currentPackId: null,
24
27
  pinnedPackId: null,
25
28
  });
@@ -29,11 +32,13 @@ describe("normalizeCreativeContextState", () => {
29
32
  expect(
30
33
  normalizeCreativeContextState({
31
34
  contextMode: "auto",
35
+ selectedContextId: " selected-context ",
32
36
  currentPackId: " current-pack ",
33
37
  pinnedPackId: "pinned-pack",
34
38
  }),
35
39
  ).toEqual({
36
40
  contextMode: "auto",
41
+ selectedContextId: "selected-context",
37
42
  currentPackId: "current-pack",
38
43
  pinnedPackId: "pinned-pack",
39
44
  });
@@ -12,12 +12,14 @@ export type CreativeContextMode = "auto" | "off";
12
12
 
13
13
  export interface CreativeContextApplicationState {
14
14
  contextMode: CreativeContextMode;
15
+ selectedContextId?: string | null;
15
16
  currentPackId?: string | null;
16
17
  pinnedPackId?: string | null;
17
18
  }
18
19
 
19
20
  export const DEFAULT_CREATIVE_CONTEXT_STATE: CreativeContextApplicationState = {
20
21
  contextMode: "auto",
22
+ selectedContextId: null,
21
23
  currentPackId: null,
22
24
  pinnedPackId: null,
23
25
  };
@@ -38,12 +40,14 @@ export function normalizeCreativeContextState(
38
40
  if (contextMode === "off") {
39
41
  return {
40
42
  contextMode,
43
+ selectedContextId: null,
41
44
  currentPackId: null,
42
45
  pinnedPackId: null,
43
46
  };
44
47
  }
45
48
  return {
46
49
  contextMode,
50
+ selectedContextId: normalizePackId(record.selectedContextId),
47
51
  currentPackId: normalizePackId(record.currentPackId),
48
52
  pinnedPackId: normalizePackId(record.pinnedPackId),
49
53
  };
@@ -76,8 +80,18 @@ export async function setCreativeContextMode(
76
80
  const state = current ?? (await readCreativeContextState());
77
81
  return setCreativeContextState(
78
82
  mode === "off"
79
- ? { contextMode: "off", currentPackId: null, pinnedPackId: null }
80
- : { ...state, contextMode: "auto" },
83
+ ? {
84
+ contextMode: "off",
85
+ selectedContextId: null,
86
+ currentPackId: null,
87
+ pinnedPackId: null,
88
+ }
89
+ : {
90
+ ...state,
91
+ contextMode: "auto",
92
+ selectedContextId: null,
93
+ pinnedPackId: null,
94
+ },
81
95
  );
82
96
  }
83
97
 
@@ -89,10 +103,24 @@ export async function setPinnedCreativeContextPack(
89
103
  return setCreativeContextState({
90
104
  ...state,
91
105
  contextMode: "auto",
106
+ selectedContextId: null,
92
107
  pinnedPackId: normalizePackId(packId),
93
108
  });
94
109
  }
95
110
 
111
+ export async function setSelectedCreativeContext(
112
+ contextId: string | null,
113
+ current?: CreativeContextApplicationState,
114
+ ): Promise<CreativeContextApplicationState> {
115
+ const state = current ?? (await readCreativeContextState());
116
+ return setCreativeContextState({
117
+ ...state,
118
+ contextMode: "auto",
119
+ selectedContextId: normalizePackId(contextId),
120
+ pinnedPackId: null,
121
+ });
122
+ }
123
+
96
124
  export function useCreativeContextState() {
97
125
  const appStateVersion = useChangeVersion("app-state");
98
126
  const [state, setState] = useState<CreativeContextApplicationState>(