foundry-design-protocol 0.2.0-beta.2 → 0.2.0-beta.21
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/README.md +164 -17
- package/dist/index.d.ts +4688 -104
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +738 -33
- package/dist/index.js.map +1 -1
- package/dist/verification-value.d.ts +3 -0
- package/dist/verification-value.d.ts.map +1 -0
- package/dist/verification-value.js +250 -0
- package/dist/verification-value.js.map +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
export
|
|
2
|
+
export { verificationValueMatches } from './verification-value.js';
|
|
3
|
+
export const PROTOCOL_VERSION = '1.3.0';
|
|
4
|
+
export const PREVIOUS_PROTOCOL_VERSION = '1.2.0';
|
|
3
5
|
export const LEGACY_PROTOCOL_VERSION = '1.0.0';
|
|
4
6
|
export const APPLY_RUN_PROTOCOL_VERSION = '1.1.0';
|
|
7
|
+
const readableProtocolVersionSchema = z.enum([
|
|
8
|
+
PROTOCOL_VERSION,
|
|
9
|
+
PREVIOUS_PROTOCOL_VERSION,
|
|
10
|
+
APPLY_RUN_PROTOCOL_VERSION,
|
|
11
|
+
LEGACY_PROTOCOL_VERSION,
|
|
12
|
+
]);
|
|
5
13
|
export const platformSchema = z.enum(['web', 'swiftui', 'react-native']);
|
|
6
14
|
export const changeCategorySchema = z.enum([
|
|
7
15
|
'layout',
|
|
@@ -18,7 +26,7 @@ export const changeCategorySchema = z.enum([
|
|
|
18
26
|
'state',
|
|
19
27
|
'motion',
|
|
20
28
|
]);
|
|
21
|
-
export const scopeSchema = z.enum(['instance', 'component']);
|
|
29
|
+
export const scopeSchema = z.enum(['instance', 'variant', 'component']);
|
|
22
30
|
export const confidenceSchema = z.enum(['measured', 'instrumented', 'inferred', 'unresolved']);
|
|
23
31
|
export const geometrySchema = z.object({
|
|
24
32
|
x: z.number().finite(),
|
|
@@ -39,6 +47,7 @@ export const sessionContextSchema = z.object({
|
|
|
39
47
|
designGraphRevision: z.string().optional(),
|
|
40
48
|
platform: platformSchema,
|
|
41
49
|
targetUrl: z.string().url().optional(),
|
|
50
|
+
previewOrigin: z.string().url().optional(),
|
|
42
51
|
targetName: z.string().optional(),
|
|
43
52
|
device: z.string().optional(),
|
|
44
53
|
viewport: z.object({ width: z.number().positive(), height: z.number().positive() }).optional(),
|
|
@@ -46,6 +55,27 @@ export const sessionContextSchema = z.object({
|
|
|
46
55
|
breakpoint: z.string().default('current'),
|
|
47
56
|
state: z.string().default('current'),
|
|
48
57
|
});
|
|
58
|
+
export const changeContextSchema = z.object({
|
|
59
|
+
breakpoint: z.string().default('current'),
|
|
60
|
+
theme: z.string().default('current'),
|
|
61
|
+
state: z.string().default('current'),
|
|
62
|
+
});
|
|
63
|
+
const contextSetValuesSchema = z
|
|
64
|
+
.array(z.string().min(1))
|
|
65
|
+
.min(1)
|
|
66
|
+
.transform((values) => [...new Set(values)]);
|
|
67
|
+
export const contextSetSchema = z.object({
|
|
68
|
+
breakpoints: contextSetValuesSchema,
|
|
69
|
+
themes: contextSetValuesSchema,
|
|
70
|
+
states: contextSetValuesSchema,
|
|
71
|
+
});
|
|
72
|
+
function singletonContextSet(context) {
|
|
73
|
+
return {
|
|
74
|
+
breakpoints: [context.breakpoint],
|
|
75
|
+
themes: [context.theme],
|
|
76
|
+
states: [context.state],
|
|
77
|
+
};
|
|
78
|
+
}
|
|
49
79
|
export const targetRefSchema = z.object({
|
|
50
80
|
id: z.string().min(1),
|
|
51
81
|
platform: platformSchema,
|
|
@@ -107,16 +137,53 @@ export const designTokenSchema = z.object({
|
|
|
107
137
|
'other',
|
|
108
138
|
]),
|
|
109
139
|
cssVariable: z.string().optional(),
|
|
140
|
+
aliasOfTokenId: z.string().optional(),
|
|
141
|
+
aliasOfTokenName: z.string().optional(),
|
|
142
|
+
resolvedValue: z.string().optional(),
|
|
143
|
+
aliasChain: z.array(z.string()).optional(),
|
|
144
|
+
aliasStatus: z.enum(['direct', 'resolved', 'broken', 'circular']).optional(),
|
|
110
145
|
source: sourceRefSchema.optional(),
|
|
111
146
|
confidence: confidenceSchema.default('inferred'),
|
|
112
147
|
evidence: z.array(z.string()).default([]),
|
|
113
148
|
});
|
|
149
|
+
export const tokenPromotionCandidateSchema = z.object({
|
|
150
|
+
id: z.string().min(1),
|
|
151
|
+
value: z.string().min(1),
|
|
152
|
+
category: designTokenSchema.shape.category,
|
|
153
|
+
property: z.string().min(1),
|
|
154
|
+
occurrenceCount: z.number().int().positive(),
|
|
155
|
+
sources: z.array(sourceRefSchema).min(1),
|
|
156
|
+
componentIds: z.array(z.string()).default([]),
|
|
157
|
+
recommendation: z.enum(['use-existing', 'create-token']),
|
|
158
|
+
relation: z.enum(['exact', 'near', 'new']),
|
|
159
|
+
suggestedTokenId: z.string().optional(),
|
|
160
|
+
suggestedTokenName: z.string().min(1),
|
|
161
|
+
suggestedValue: z.string().min(1),
|
|
162
|
+
aliasChain: z.array(z.string()).default([]),
|
|
163
|
+
canStage: z.boolean().default(true),
|
|
164
|
+
blockers: z.array(z.string()).default([]),
|
|
165
|
+
evidence: z.array(z.string()).default([]),
|
|
166
|
+
});
|
|
114
167
|
export const componentVariantSchema = z.object({
|
|
115
168
|
id: z.string().min(1),
|
|
116
169
|
label: z.string().min(1),
|
|
117
170
|
property: z.string().min(1),
|
|
118
171
|
value: changeValueSchema,
|
|
172
|
+
props: z.record(z.string(), changeValueSchema).default({}),
|
|
119
173
|
source: sourceRefSchema.optional(),
|
|
174
|
+
adapter: z.enum(['storybook', 'cva', 'typescript', 'configured']).optional(),
|
|
175
|
+
sourceProperty: z.string().optional(),
|
|
176
|
+
});
|
|
177
|
+
export const componentVariantAxisSchema = z.object({
|
|
178
|
+
id: z.string().min(1),
|
|
179
|
+
label: z.string().min(1),
|
|
180
|
+
property: z.string().min(1),
|
|
181
|
+
values: z.array(changeValueSchema).default([]),
|
|
182
|
+
adapter: z.enum(['storybook', 'cva', 'typescript', 'configured']),
|
|
183
|
+
source: sourceRefSchema,
|
|
184
|
+
sourceProperty: z.string().min(1),
|
|
185
|
+
canCreate: z.boolean().default(false),
|
|
186
|
+
evidence: z.array(z.string()).default([]),
|
|
120
187
|
});
|
|
121
188
|
export const componentDefinitionSchema = z.object({
|
|
122
189
|
id: z.string().min(1),
|
|
@@ -125,6 +192,7 @@ export const componentDefinitionSchema = z.object({
|
|
|
125
192
|
selector: z.string().optional(),
|
|
126
193
|
instances: z.number().int().nonnegative().default(0),
|
|
127
194
|
variants: z.array(componentVariantSchema).default([]),
|
|
195
|
+
variantAxes: z.array(componentVariantAxisSchema).default([]),
|
|
128
196
|
evidence: z.array(z.string()).default([]),
|
|
129
197
|
});
|
|
130
198
|
export const stateDefinitionSchema = z.object({
|
|
@@ -147,6 +215,17 @@ export const breakpointDefinitionSchema = z.object({
|
|
|
147
215
|
mediaQuery: z.string().optional(),
|
|
148
216
|
source: sourceRefSchema.optional(),
|
|
149
217
|
});
|
|
218
|
+
export const containerQueryDefinitionSchema = z.object({
|
|
219
|
+
id: z.string().min(1),
|
|
220
|
+
label: z.string().min(1),
|
|
221
|
+
name: z.string().optional(),
|
|
222
|
+
condition: z.string().min(1),
|
|
223
|
+
axis: z.enum(['inline-size', 'block-size', 'size']).default('inline-size'),
|
|
224
|
+
minWidth: z.number().nonnegative().optional(),
|
|
225
|
+
maxWidth: z.number().nonnegative().optional(),
|
|
226
|
+
source: sourceRefSchema,
|
|
227
|
+
evidence: z.array(z.string()).default([]),
|
|
228
|
+
});
|
|
150
229
|
export const themeDefinitionSchema = z.object({
|
|
151
230
|
id: z.string().min(1),
|
|
152
231
|
label: z.string().min(1),
|
|
@@ -154,6 +233,8 @@ export const themeDefinitionSchema = z.object({
|
|
|
154
233
|
attribute: z.string().optional(),
|
|
155
234
|
value: z.string().optional(),
|
|
156
235
|
source: sourceRefSchema.optional(),
|
|
236
|
+
confidence: confidenceSchema.default('instrumented'),
|
|
237
|
+
evidence: z.array(z.string()).default([]),
|
|
157
238
|
});
|
|
158
239
|
export const motionPresetSchema = z.object({
|
|
159
240
|
id: z.string().min(1),
|
|
@@ -161,20 +242,58 @@ export const motionPresetSchema = z.object({
|
|
|
161
242
|
duration: z.number().nonnegative().optional(),
|
|
162
243
|
easing: z.string().optional(),
|
|
163
244
|
delay: z.number().nonnegative().optional(),
|
|
245
|
+
adapter: z.enum(['css', 'motion', 'gsap', 'react-spring']).optional(),
|
|
246
|
+
sourceProperty: z.string().optional(),
|
|
247
|
+
configuration: z.record(z.string(), z.unknown()).default({}),
|
|
164
248
|
source: sourceRefSchema.optional(),
|
|
249
|
+
evidence: z.array(z.string()).default([]),
|
|
165
250
|
});
|
|
166
|
-
export const
|
|
167
|
-
|
|
251
|
+
export const designTokenUsageSchema = z.object({
|
|
252
|
+
id: z.string().min(1),
|
|
253
|
+
tokenId: z.string().min(1),
|
|
254
|
+
tokenName: z.string().min(1),
|
|
255
|
+
value: z.string().min(1),
|
|
256
|
+
category: designTokenSchema.shape.category,
|
|
257
|
+
kind: z.enum(['reference', 'literal', 'alias']),
|
|
258
|
+
property: z.string().optional(),
|
|
259
|
+
componentId: z.string().optional(),
|
|
260
|
+
source: sourceRefSchema,
|
|
261
|
+
evidence: z.array(z.string()).default([]),
|
|
262
|
+
});
|
|
263
|
+
export const designSystemFindingSchema = z.object({
|
|
264
|
+
id: z.string().min(1),
|
|
265
|
+
kind: z.enum(['near-duplicate', 'literal-drift', 'unused-token', 'component-drift']),
|
|
266
|
+
severity: z.enum(['info', 'warning']).default('info'),
|
|
267
|
+
title: z.string().min(1),
|
|
268
|
+
detail: z.string().min(1),
|
|
269
|
+
category: designTokenSchema.shape.category.optional(),
|
|
270
|
+
tokenIds: z.array(z.string()).default([]),
|
|
271
|
+
usageIds: z.array(z.string()).default([]),
|
|
272
|
+
componentIds: z.array(z.string()).default([]),
|
|
273
|
+
suggestedTokenId: z.string().optional(),
|
|
274
|
+
source: sourceRefSchema.optional(),
|
|
275
|
+
evidence: z.array(z.string()).default([]),
|
|
276
|
+
});
|
|
277
|
+
const projectDesignGraphInputSchema = z.object({
|
|
278
|
+
protocolVersion: z.union([z.literal(PROTOCOL_VERSION), z.literal(PREVIOUS_PROTOCOL_VERSION)]),
|
|
168
279
|
projectRoot: z.string().min(1),
|
|
169
280
|
revision: z.string().optional(),
|
|
170
281
|
tokens: z.array(designTokenSchema).default([]),
|
|
171
282
|
components: z.array(componentDefinitionSchema).default([]),
|
|
172
283
|
breakpoints: z.array(breakpointDefinitionSchema).default([]),
|
|
284
|
+
containerQueries: z.array(containerQueryDefinitionSchema).optional(),
|
|
173
285
|
themes: z.array(themeDefinitionSchema).default([]),
|
|
174
286
|
states: z.array(stateDefinitionSchema).default([]),
|
|
175
287
|
motionPresets: z.array(motionPresetSchema).default([]),
|
|
288
|
+
tokenUsages: z.array(designTokenUsageSchema).default([]),
|
|
289
|
+
designSystemFindings: z.array(designSystemFindingSchema).default([]),
|
|
290
|
+
tokenPromotions: z.array(tokenPromotionCandidateSchema).optional(),
|
|
176
291
|
indexedAt: z.string().datetime(),
|
|
177
292
|
});
|
|
293
|
+
export const projectDesignGraphSchema = projectDesignGraphInputSchema.transform((graph) => ({
|
|
294
|
+
...graph,
|
|
295
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
296
|
+
}));
|
|
178
297
|
export const sourceMappingCandidateSchema = z.object({
|
|
179
298
|
id: z.string().min(1),
|
|
180
299
|
label: z.string().min(1),
|
|
@@ -188,6 +307,8 @@ export const sourceMappingCandidateSchema = z.object({
|
|
|
188
307
|
'content',
|
|
189
308
|
'motion',
|
|
190
309
|
'state',
|
|
310
|
+
'component-variant',
|
|
311
|
+
'token-refactor',
|
|
191
312
|
]),
|
|
192
313
|
property: z.string().min(1),
|
|
193
314
|
targetId: z.string().optional(),
|
|
@@ -198,9 +319,21 @@ export const sourceMappingCandidateSchema = z.object({
|
|
|
198
319
|
evidence: z.array(z.string()).default([]),
|
|
199
320
|
blastRadius: z.number().int().nonnegative().default(1),
|
|
200
321
|
});
|
|
201
|
-
export const designOperationSchema = z
|
|
322
|
+
export const designOperationSchema = z
|
|
323
|
+
.object({
|
|
202
324
|
id: z.string().min(1),
|
|
203
|
-
kind: z.enum([
|
|
325
|
+
kind: z.enum([
|
|
326
|
+
'resize',
|
|
327
|
+
'spacing',
|
|
328
|
+
'align',
|
|
329
|
+
'distribute',
|
|
330
|
+
'style',
|
|
331
|
+
'content',
|
|
332
|
+
'motion',
|
|
333
|
+
'state',
|
|
334
|
+
'component-variant',
|
|
335
|
+
'token-refactor',
|
|
336
|
+
]),
|
|
204
337
|
label: z.string().min(1),
|
|
205
338
|
targetIds: z.array(z.string().min(1)).min(1),
|
|
206
339
|
changeIds: z.array(z.string().min(1)).default([]),
|
|
@@ -210,8 +343,35 @@ export const designOperationSchema = z.object({
|
|
|
210
343
|
status: z.enum(['preview', 'resolved', 'unresolved']).default('preview'),
|
|
211
344
|
createdAt: z.string().datetime(),
|
|
212
345
|
updatedAt: z.string().datetime(),
|
|
346
|
+
})
|
|
347
|
+
.superRefine((operation, context) => {
|
|
348
|
+
const candidateIds = operation.mappingCandidates.map((candidate) => candidate.id);
|
|
349
|
+
if (new Set(candidateIds).size !== candidateIds.length) {
|
|
350
|
+
context.addIssue({
|
|
351
|
+
code: 'custom',
|
|
352
|
+
path: ['mappingCandidates'],
|
|
353
|
+
message: 'Source mapping candidate ids must be unique',
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
if (operation.selectedMappingId &&
|
|
357
|
+
!operation.mappingCandidates.some((candidate) => candidate.id === operation.selectedMappingId)) {
|
|
358
|
+
context.addIssue({
|
|
359
|
+
code: 'custom',
|
|
360
|
+
path: ['selectedMappingId'],
|
|
361
|
+
message: 'Selected source mapping must name an existing candidate',
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
if (operation.status === 'resolved' &&
|
|
365
|
+
(operation.mappingCandidates.length === 0 ||
|
|
366
|
+
(operation.mappingCandidates.length > 1 && !operation.selectedMappingId))) {
|
|
367
|
+
context.addIssue({
|
|
368
|
+
code: 'custom',
|
|
369
|
+
path: ['selectedMappingId'],
|
|
370
|
+
message: 'Resolved operations require a selected source mapping',
|
|
371
|
+
});
|
|
372
|
+
}
|
|
213
373
|
});
|
|
214
|
-
|
|
374
|
+
const designChangeInputSchema = z.object({
|
|
215
375
|
id: z.string().min(1),
|
|
216
376
|
target: targetRefSchema,
|
|
217
377
|
category: changeCategorySchema,
|
|
@@ -225,19 +385,51 @@ export const designChangeSchema = z.object({
|
|
|
225
385
|
mappingCandidates: z.array(sourceMappingCandidateSchema).default([]),
|
|
226
386
|
selectedMappingId: z.string().optional(),
|
|
227
387
|
scope: scopeSchema.default('instance'),
|
|
228
|
-
context:
|
|
229
|
-
|
|
230
|
-
theme: z.string().default('current'),
|
|
231
|
-
state: z.string().default('current'),
|
|
232
|
-
}),
|
|
388
|
+
context: changeContextSchema,
|
|
389
|
+
contextSet: contextSetSchema.optional(),
|
|
233
390
|
confidence: confidenceSchema,
|
|
234
391
|
evidence: z.array(z.string()).default([]),
|
|
235
392
|
createdAt: z.string().datetime(),
|
|
236
393
|
updatedAt: z.string().datetime(),
|
|
237
394
|
status: z.enum(['draft', 'approved', 'applied', 'rejected', 'unresolved']).default('draft'),
|
|
238
395
|
});
|
|
239
|
-
export const
|
|
240
|
-
|
|
396
|
+
export const designChangeSchema = designChangeInputSchema
|
|
397
|
+
.transform((change) => ({
|
|
398
|
+
...change,
|
|
399
|
+
contextSet: change.contextSet ?? singletonContextSet(change.context),
|
|
400
|
+
}))
|
|
401
|
+
.superRefine((change, context) => {
|
|
402
|
+
const candidateIds = change.mappingCandidates.map((candidate) => candidate.id);
|
|
403
|
+
if (new Set(candidateIds).size !== candidateIds.length) {
|
|
404
|
+
context.addIssue({
|
|
405
|
+
code: 'custom',
|
|
406
|
+
path: ['mappingCandidates'],
|
|
407
|
+
message: 'Source mapping candidate ids must be unique',
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
if (change.selectedMappingId &&
|
|
411
|
+
!change.mappingCandidates.some((candidate) => candidate.id === change.selectedMappingId)) {
|
|
412
|
+
context.addIssue({
|
|
413
|
+
code: 'custom',
|
|
414
|
+
path: ['selectedMappingId'],
|
|
415
|
+
message: 'Selected source mapping must name an existing candidate',
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
const missingAxes = [
|
|
419
|
+
change.contextSet.breakpoints.includes(change.context.breakpoint) ? undefined : 'breakpoint',
|
|
420
|
+
change.contextSet.themes.includes(change.context.theme) ? undefined : 'theme',
|
|
421
|
+
change.contextSet.states.includes(change.context.state) ? undefined : 'state',
|
|
422
|
+
].filter((axis) => Boolean(axis));
|
|
423
|
+
if (missingAxes.length) {
|
|
424
|
+
context.addIssue({
|
|
425
|
+
code: 'custom',
|
|
426
|
+
path: ['contextSet'],
|
|
427
|
+
message: `Context set must contain the exact capture ${missingAxes.join(', ')}`,
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
const changeSetInputSchema = z.object({
|
|
432
|
+
protocolVersion: readableProtocolVersionSchema,
|
|
241
433
|
sessionId: z.string().min(1),
|
|
242
434
|
context: sessionContextSchema,
|
|
243
435
|
changes: z.array(designChangeSchema),
|
|
@@ -253,14 +445,138 @@ export const changeSetSchema = z.object({
|
|
|
253
445
|
createdAt: z.string().datetime(),
|
|
254
446
|
updatedAt: z.string().datetime(),
|
|
255
447
|
});
|
|
448
|
+
function refineChangeCollection(value, context) {
|
|
449
|
+
const changeIds = value.changes.map((change) => change.id);
|
|
450
|
+
if (new Set(changeIds).size !== changeIds.length) {
|
|
451
|
+
context.addIssue({
|
|
452
|
+
code: 'custom',
|
|
453
|
+
path: ['changes'],
|
|
454
|
+
message: 'Design change ids must be unique within a change collection',
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
const operationIds = value.operations.map((operation) => operation.id);
|
|
458
|
+
if (new Set(operationIds).size !== operationIds.length) {
|
|
459
|
+
context.addIssue({
|
|
460
|
+
code: 'custom',
|
|
461
|
+
path: ['operations'],
|
|
462
|
+
message: 'Design operation ids must be unique within a change collection',
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
const changes = new Map(value.changes.map((change) => [change.id, change]));
|
|
466
|
+
const operations = new Map(value.operations.map((operation) => [operation.id, operation]));
|
|
467
|
+
for (const [index, operation] of value.operations.entries()) {
|
|
468
|
+
if (new Set(operation.changeIds).size !== operation.changeIds.length) {
|
|
469
|
+
context.addIssue({
|
|
470
|
+
code: 'custom',
|
|
471
|
+
path: ['operations', index, 'changeIds'],
|
|
472
|
+
message: 'Operation change ids must be unique',
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
if (new Set(operation.targetIds).size !== operation.targetIds.length) {
|
|
476
|
+
context.addIssue({
|
|
477
|
+
code: 'custom',
|
|
478
|
+
path: ['operations', index, 'targetIds'],
|
|
479
|
+
message: 'Operation target ids must be unique',
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
for (const referencedId of operation.changeIds) {
|
|
483
|
+
const change = changes.get(referencedId);
|
|
484
|
+
if (!change || change.operationId !== operation.id) {
|
|
485
|
+
context.addIssue({
|
|
486
|
+
code: 'custom',
|
|
487
|
+
path: ['operations', index, 'changeIds'],
|
|
488
|
+
message: `Operation ${operation.id} references an inconsistent change ${referencedId}`,
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
for (const [index, change] of value.changes.entries()) {
|
|
494
|
+
if (!change.operationId)
|
|
495
|
+
continue;
|
|
496
|
+
const operation = operations.get(change.operationId);
|
|
497
|
+
if (!operation || !operation.changeIds.includes(change.id)) {
|
|
498
|
+
context.addIssue({
|
|
499
|
+
code: 'custom',
|
|
500
|
+
path: ['changes', index, 'operationId'],
|
|
501
|
+
message: `Change ${change.id} references an inconsistent operation ${change.operationId}`,
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
export const changeSetSchema = changeSetInputSchema
|
|
507
|
+
.transform((changeSet) => ({
|
|
508
|
+
...changeSet,
|
|
509
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
510
|
+
}))
|
|
511
|
+
.superRefine((changeSet, context) => {
|
|
512
|
+
refineChangeCollection(changeSet, context);
|
|
513
|
+
});
|
|
514
|
+
export const designBranchSchema = z
|
|
515
|
+
.object({
|
|
516
|
+
id: z.string().min(1),
|
|
517
|
+
name: z.string().min(1).max(80),
|
|
518
|
+
status: z.enum(['exploring', 'chosen', 'rejected', 'archived']).default('exploring'),
|
|
519
|
+
originBranchId: z.string().optional(),
|
|
520
|
+
changes: z.array(designChangeSchema).default([]),
|
|
521
|
+
operations: z.array(designOperationSchema).default([]),
|
|
522
|
+
rejectionReason: z.string().max(280).optional(),
|
|
523
|
+
createdAt: z.string().datetime(),
|
|
524
|
+
updatedAt: z.string().datetime(),
|
|
525
|
+
})
|
|
526
|
+
.superRefine((branch, context) => {
|
|
527
|
+
refineChangeCollection(branch, context);
|
|
528
|
+
});
|
|
529
|
+
export const designBranchRecordSourceSchema = z.object({
|
|
530
|
+
targetId: z.string().min(1),
|
|
531
|
+
targetLabel: z.string().min(1),
|
|
532
|
+
property: z.string().min(1),
|
|
533
|
+
componentPath: z.array(z.string()).default([]),
|
|
534
|
+
source: sourceRefSchema.optional(),
|
|
535
|
+
});
|
|
536
|
+
export const designBranchRecordSchema = z
|
|
537
|
+
.object({
|
|
538
|
+
version: z.literal(1),
|
|
539
|
+
id: z.string().min(1),
|
|
540
|
+
branchId: z.string().min(1),
|
|
541
|
+
name: z.string().min(1).max(80),
|
|
542
|
+
outcome: z.enum(['chosen', 'rejected']),
|
|
543
|
+
rationale: z.string().max(280).optional(),
|
|
544
|
+
context: sessionContextSchema,
|
|
545
|
+
changes: z.array(designChangeSchema).default([]),
|
|
546
|
+
operations: z.array(designOperationSchema).default([]),
|
|
547
|
+
sourceRelationships: z.array(designBranchRecordSourceSchema).default([]),
|
|
548
|
+
compatibility: z.object({
|
|
549
|
+
status: z.enum(['current', 'stale', 'missing']),
|
|
550
|
+
matchedSources: z.number().int().nonnegative(),
|
|
551
|
+
totalSources: z.number().int().nonnegative(),
|
|
552
|
+
warnings: z.array(z.string()).default([]),
|
|
553
|
+
checkedAt: z.string().datetime(),
|
|
554
|
+
}),
|
|
555
|
+
importedAt: z.string().datetime().optional(),
|
|
556
|
+
createdAt: z.string().datetime(),
|
|
557
|
+
updatedAt: z.string().datetime(),
|
|
558
|
+
})
|
|
559
|
+
.superRefine((record, context) => {
|
|
560
|
+
refineChangeCollection(record, context);
|
|
561
|
+
});
|
|
562
|
+
export const designBranchRecordBundleSchema = z.object({
|
|
563
|
+
format: z.literal('foundry.design-branch-records'),
|
|
564
|
+
version: z.literal(1),
|
|
565
|
+
exportedAt: z.string().datetime(),
|
|
566
|
+
records: z.array(designBranchRecordSchema).max(100),
|
|
567
|
+
});
|
|
256
568
|
export const verificationResultSchema = z.object({
|
|
257
569
|
changeId: z.string().min(1),
|
|
258
570
|
property: z.string().min(1),
|
|
259
571
|
requested: changeValueSchema,
|
|
260
572
|
rendered: changeValueSchema,
|
|
573
|
+
context: changeContextSchema.optional(),
|
|
574
|
+
applyRunId: z.string().min(1).optional(),
|
|
575
|
+
claimAttemptId: z.string().min(1).optional(),
|
|
261
576
|
passed: z.boolean(),
|
|
262
577
|
reason: z.string().optional(),
|
|
263
578
|
geometry: geometrySchema.optional(),
|
|
579
|
+
evidence: z.array(z.string().min(1)).default([]),
|
|
264
580
|
screenshotPath: z.string().optional(),
|
|
265
581
|
verifiedAt: z.string().datetime(),
|
|
266
582
|
});
|
|
@@ -285,11 +601,44 @@ export const validationResultSchema = z.object({
|
|
|
285
601
|
name: z.string().min(1),
|
|
286
602
|
passed: z.boolean(),
|
|
287
603
|
summary: z.string().optional(),
|
|
604
|
+
applyRunId: z.string().min(1).optional(),
|
|
605
|
+
claimAttemptId: z.string().min(1).optional(),
|
|
606
|
+
validatedRevision: z.string().min(1).optional(),
|
|
607
|
+
validatedAt: z.string().datetime().optional(),
|
|
288
608
|
});
|
|
289
|
-
export const
|
|
609
|
+
export const sourceLineAnchorSchema = z
|
|
610
|
+
.object({
|
|
611
|
+
line: z.number().int().positive(),
|
|
612
|
+
endLine: z.number().int().positive().optional(),
|
|
613
|
+
symbol: z.string().min(1).optional(),
|
|
614
|
+
sha256: z.string().regex(/^[a-f0-9]{64}$/),
|
|
615
|
+
})
|
|
616
|
+
.superRefine((anchor, context) => {
|
|
617
|
+
if (anchor.endLine !== undefined && anchor.endLine < anchor.line) {
|
|
618
|
+
context.addIssue({
|
|
619
|
+
code: 'custom',
|
|
620
|
+
path: ['endLine'],
|
|
621
|
+
message: 'Source anchor endLine must not precede line',
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
});
|
|
625
|
+
export const sourceFileSnapshotSchema = z.object({
|
|
626
|
+
path: z.string().min(1),
|
|
627
|
+
exists: z.boolean(),
|
|
628
|
+
sha256: z
|
|
629
|
+
.string()
|
|
630
|
+
.regex(/^[a-f0-9]{64}$/)
|
|
631
|
+
.nullable(),
|
|
632
|
+
lineAnchors: z.array(sourceLineAnchorSchema).optional(),
|
|
633
|
+
});
|
|
634
|
+
export const applyRunSchema = z
|
|
635
|
+
.object({
|
|
290
636
|
id: z.string().min(1),
|
|
291
637
|
sessionId: z.string().min(1),
|
|
292
638
|
changeIds: z.array(z.string().min(1)).min(1),
|
|
639
|
+
reviewedChangeSet: changeSetSchema.optional(),
|
|
640
|
+
/** Enables only the pre-1.3 acknowledgement shape for a run migrated from that protocol. */
|
|
641
|
+
legacyApplyCompatibility: z.boolean().optional(),
|
|
293
642
|
revision: z.string().optional(),
|
|
294
643
|
designGraphRevision: z.string().optional(),
|
|
295
644
|
state: applyRunStateSchema,
|
|
@@ -304,13 +653,297 @@ export const applyRunSchema = z.object({
|
|
|
304
653
|
changedFiles: z.array(z.string()).default([]),
|
|
305
654
|
validationResults: z.array(validationResultSchema).default([]),
|
|
306
655
|
verificationResults: z.array(verificationResultSchema).default([]),
|
|
656
|
+
applyResultAcknowledgedAt: z.string().datetime().optional(),
|
|
657
|
+
applyResultClaimAttemptId: z.string().min(1).optional(),
|
|
658
|
+
sourceProofScope: z.enum(['git', 'mapped-files']).optional(),
|
|
659
|
+
sourceHeadRevision: z.string().min(1).optional(),
|
|
660
|
+
baselineSourceFiles: z.array(sourceFileSnapshotSchema).default([]),
|
|
661
|
+
appliedRevision: z.string().min(1).optional(),
|
|
662
|
+
appliedChangedFiles: z.array(z.string().min(1)).default([]),
|
|
663
|
+
appliedSourceFiles: z.array(sourceFileSnapshotSchema).default([]),
|
|
307
664
|
attempts: z.number().int().positive().default(1),
|
|
665
|
+
claimAttemptId: z.string().min(1).optional(),
|
|
666
|
+
claimCapabilityHash: z
|
|
667
|
+
.string()
|
|
668
|
+
.regex(/^[a-f0-9]{64}$/)
|
|
669
|
+
.optional(),
|
|
670
|
+
claimExpiresAt: z.string().datetime().optional(),
|
|
671
|
+
claimHeartbeatAt: z.string().datetime().optional(),
|
|
672
|
+
requeueCount: z.number().int().nonnegative().default(0),
|
|
673
|
+
interruptedState: z.enum(['applying', 'rebuilding', 'verifying']).optional(),
|
|
674
|
+
resumedAt: z.string().datetime().optional(),
|
|
308
675
|
retryOf: z.string().optional(),
|
|
309
676
|
error: z.string().optional(),
|
|
310
677
|
requestedAt: z.string().datetime(),
|
|
311
678
|
claimedAt: z.string().datetime().optional(),
|
|
312
679
|
completedAt: z.string().datetime().optional(),
|
|
313
680
|
updatedAt: z.string().datetime(),
|
|
681
|
+
})
|
|
682
|
+
.superRefine((run, context) => {
|
|
683
|
+
if (Boolean(run.applyResultAcknowledgedAt) !== Boolean(run.applyResultClaimAttemptId)) {
|
|
684
|
+
context.addIssue({
|
|
685
|
+
code: 'custom',
|
|
686
|
+
path: ['applyResultAcknowledgedAt'],
|
|
687
|
+
message: 'Apply result acknowledgement time and claim attempt must be stored together',
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
if (run.applyResultAcknowledgedAt &&
|
|
691
|
+
(!run.appliedRevision ||
|
|
692
|
+
run.appliedChangedFiles.length === 0 ||
|
|
693
|
+
run.appliedSourceFiles.length === 0)) {
|
|
694
|
+
context.addIssue({
|
|
695
|
+
code: 'custom',
|
|
696
|
+
path: ['appliedRevision'],
|
|
697
|
+
message: 'Acknowledged Apply results require a proven revision and changed source files',
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
if (!run.reviewedChangeSet)
|
|
701
|
+
return;
|
|
702
|
+
if (run.reviewedChangeSet.sessionId !== run.sessionId) {
|
|
703
|
+
context.addIssue({
|
|
704
|
+
code: 'custom',
|
|
705
|
+
path: ['reviewedChangeSet', 'sessionId'],
|
|
706
|
+
message: 'Reviewed change set must belong to the apply run session',
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
const runIds = [...new Set(run.changeIds)].sort();
|
|
710
|
+
const reviewedIds = [
|
|
711
|
+
...new Set(run.reviewedChangeSet.changes.map((change) => change.id)),
|
|
712
|
+
].sort();
|
|
713
|
+
if (runIds.length !== run.changeIds.length ||
|
|
714
|
+
reviewedIds.length !== run.reviewedChangeSet.changes.length ||
|
|
715
|
+
runIds.length !== reviewedIds.length ||
|
|
716
|
+
runIds.some((id, index) => id !== reviewedIds[index])) {
|
|
717
|
+
context.addIssue({
|
|
718
|
+
code: 'custom',
|
|
719
|
+
path: ['reviewedChangeSet', 'changes'],
|
|
720
|
+
message: 'Reviewed change set must contain each apply run change exactly once',
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
for (const [index, change] of run.reviewedChangeSet.changes.entries()) {
|
|
724
|
+
if (change.status !== 'approved') {
|
|
725
|
+
context.addIssue({
|
|
726
|
+
code: 'custom',
|
|
727
|
+
path: ['reviewedChangeSet', 'changes', index, 'status'],
|
|
728
|
+
message: 'Frozen reviewed changes must remain approved',
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
});
|
|
733
|
+
export const deliveryRecordStatusSchema = z.enum([
|
|
734
|
+
'draft',
|
|
735
|
+
'ready',
|
|
736
|
+
'implementing',
|
|
737
|
+
'verified',
|
|
738
|
+
'superseded',
|
|
739
|
+
]);
|
|
740
|
+
export const deliveryNarrativeSourceSchema = z.enum(['deterministic', 'agent', 'authored']);
|
|
741
|
+
export const deliveryAcceptanceCriterionSchema = z.object({
|
|
742
|
+
id: z.string().min(1),
|
|
743
|
+
label: z.string().min(1),
|
|
744
|
+
status: z.enum(['pending', 'passed', 'failed']).default('pending'),
|
|
745
|
+
evidence: z.array(z.string()).default([]),
|
|
746
|
+
});
|
|
747
|
+
export const deliveryRecordSchema = z.object({
|
|
748
|
+
version: z.literal(1),
|
|
749
|
+
id: z.string().min(1),
|
|
750
|
+
sessionId: z.string().min(1),
|
|
751
|
+
applyRunId: z.string().min(1),
|
|
752
|
+
title: z.string().min(1).max(120),
|
|
753
|
+
summary: z.string().max(2000).default(''),
|
|
754
|
+
intent: z.string().max(4000).default(''),
|
|
755
|
+
narrativeSource: deliveryNarrativeSourceSchema.default('deterministic'),
|
|
756
|
+
status: deliveryRecordStatusSchema,
|
|
757
|
+
changeIds: z.array(z.string().min(1)).min(1),
|
|
758
|
+
operationIds: z.array(z.string().min(1)).default([]),
|
|
759
|
+
affectedFiles: z.array(z.string().min(1)).default([]),
|
|
760
|
+
affectedComponents: z.array(z.string().min(1)).default([]),
|
|
761
|
+
affectedTokens: z.array(z.string().min(1)).default([]),
|
|
762
|
+
contexts: z
|
|
763
|
+
.array(z.object({
|
|
764
|
+
breakpoint: z.string(),
|
|
765
|
+
theme: z.string(),
|
|
766
|
+
state: z.string(),
|
|
767
|
+
}))
|
|
768
|
+
.default([]),
|
|
769
|
+
risks: z.array(z.string()).default([]),
|
|
770
|
+
questions: z.array(z.string()).default([]),
|
|
771
|
+
acceptanceCriteria: z.array(deliveryAcceptanceCriterionSchema).default([]),
|
|
772
|
+
blockers: z.array(z.string()).default([]),
|
|
773
|
+
validationResults: z.array(validationResultSchema).default([]),
|
|
774
|
+
verificationResults: z.array(verificationResultSchema).default([]),
|
|
775
|
+
evidence: z
|
|
776
|
+
.array(z.object({
|
|
777
|
+
label: z.string().min(1),
|
|
778
|
+
path: z.string().min(1),
|
|
779
|
+
createdAt: z.string().datetime(),
|
|
780
|
+
}))
|
|
781
|
+
.default([]),
|
|
782
|
+
revision: z.string().optional(),
|
|
783
|
+
baselineRevision: z.string().optional(),
|
|
784
|
+
appliedRevision: z.string().optional(),
|
|
785
|
+
designGraphRevision: z.string().optional(),
|
|
786
|
+
createdAt: z.string().datetime(),
|
|
787
|
+
updatedAt: z.string().datetime(),
|
|
788
|
+
verifiedAt: z.string().datetime().optional(),
|
|
789
|
+
supersededAt: z.string().datetime().optional(),
|
|
790
|
+
});
|
|
791
|
+
export const documentationPageSchema = z.object({
|
|
792
|
+
version: z.literal(1),
|
|
793
|
+
id: z.string().min(1),
|
|
794
|
+
kind: z.enum(['component', 'system', 'feature', 'screen']),
|
|
795
|
+
slug: z.string().min(1),
|
|
796
|
+
title: z.string().min(1),
|
|
797
|
+
summary: z.string().default(''),
|
|
798
|
+
body: z.string().default(''),
|
|
799
|
+
narrativeSource: deliveryNarrativeSourceSchema.default('deterministic'),
|
|
800
|
+
freshness: z.enum(['current', 'stale', 'conflicted']).default('current'),
|
|
801
|
+
sourceFiles: z.array(z.string()).default([]),
|
|
802
|
+
componentIds: z.array(z.string()).default([]),
|
|
803
|
+
deliveryRecordIds: z.array(z.string()).default([]),
|
|
804
|
+
revision: z.string().optional(),
|
|
805
|
+
designGraphRevision: z.string().optional(),
|
|
806
|
+
contentHash: z.string().min(1),
|
|
807
|
+
exportedHash: z.string().optional(),
|
|
808
|
+
exportedPath: z.string().optional(),
|
|
809
|
+
createdAt: z.string().datetime(),
|
|
810
|
+
updatedAt: z.string().datetime(),
|
|
811
|
+
});
|
|
812
|
+
export const designHistoryEntrySchema = z.object({
|
|
813
|
+
version: z.literal(1),
|
|
814
|
+
id: z.string().min(1),
|
|
815
|
+
deliveryRecordId: z.string().min(1),
|
|
816
|
+
applyRunId: z.string().min(1),
|
|
817
|
+
title: z.string().min(1),
|
|
818
|
+
summary: z.string().default(''),
|
|
819
|
+
changeIds: z.array(z.string().min(1)).min(1),
|
|
820
|
+
affectedFiles: z.array(z.string()).default([]),
|
|
821
|
+
contexts: z.array(z.object({ breakpoint: z.string(), theme: z.string(), state: z.string() })),
|
|
822
|
+
validationResults: z.array(validationResultSchema).default([]),
|
|
823
|
+
verificationResults: z.array(verificationResultSchema).default([]),
|
|
824
|
+
revision: z.string().optional(),
|
|
825
|
+
baselineRevision: z.string().optional(),
|
|
826
|
+
appliedRevision: z.string().optional(),
|
|
827
|
+
createdAt: z.string().datetime(),
|
|
828
|
+
});
|
|
829
|
+
export const deliveryMilestoneSchema = z.object({
|
|
830
|
+
version: z.literal(1),
|
|
831
|
+
id: z.string().min(1),
|
|
832
|
+
name: z.string().min(1).max(120),
|
|
833
|
+
summary: z.string().max(2000).default(''),
|
|
834
|
+
entryIds: z.array(z.string().min(1)).default([]),
|
|
835
|
+
status: z.enum(['draft', 'published']).default('draft'),
|
|
836
|
+
createdAt: z.string().datetime(),
|
|
837
|
+
updatedAt: z.string().datetime(),
|
|
838
|
+
publishedAt: z.string().datetime().optional(),
|
|
839
|
+
});
|
|
840
|
+
export const visualAgentContextTargetSchema = z.object({
|
|
841
|
+
id: z.string().min(1),
|
|
842
|
+
selector: z.string().min(1),
|
|
843
|
+
label: z.string().min(1),
|
|
844
|
+
kind: z.string().min(1),
|
|
845
|
+
component: z.string().nullable().optional(),
|
|
846
|
+
source: z.string().optional(),
|
|
847
|
+
confidence: confidenceSchema,
|
|
848
|
+
geometry: geometrySchema,
|
|
849
|
+
measurements: z.record(z.string(), changeValueSchema).default({}),
|
|
850
|
+
});
|
|
851
|
+
export const visualAgentCommentSchema = z.object({
|
|
852
|
+
id: z.string().min(1),
|
|
853
|
+
body: z.string().min(1).max(2000),
|
|
854
|
+
targetId: z.string().optional(),
|
|
855
|
+
regionId: z.string().optional(),
|
|
856
|
+
createdAt: z.string().datetime(),
|
|
857
|
+
});
|
|
858
|
+
export const visualAgentContextSchema = z.object({
|
|
859
|
+
targets: z.array(visualAgentContextTargetSchema).default([]),
|
|
860
|
+
region: z
|
|
861
|
+
.object({
|
|
862
|
+
id: z.string().min(1),
|
|
863
|
+
x: z.number().finite(),
|
|
864
|
+
y: z.number().finite(),
|
|
865
|
+
width: z.number().nonnegative().finite(),
|
|
866
|
+
height: z.number().nonnegative().finite(),
|
|
867
|
+
label: z.string().default('Canvas region'),
|
|
868
|
+
})
|
|
869
|
+
.optional(),
|
|
870
|
+
comments: z.array(visualAgentCommentSchema).default([]),
|
|
871
|
+
viewport: z.object({ width: z.number().positive(), height: z.number().positive() }),
|
|
872
|
+
breakpoint: z.string().default('current'),
|
|
873
|
+
theme: z.string().default('current'),
|
|
874
|
+
state: z.string().default('current'),
|
|
875
|
+
tokens: z.array(designTokenSchema).default([]),
|
|
876
|
+
designGraphRevision: z.string().optional(),
|
|
877
|
+
});
|
|
878
|
+
export const visualAgentProposalSchema = z.object({
|
|
879
|
+
id: z.string().min(1),
|
|
880
|
+
name: z.string().min(1).max(80),
|
|
881
|
+
summary: z.string().min(1).max(2000),
|
|
882
|
+
reasoning: z.array(z.string().min(1)).default([]),
|
|
883
|
+
exactValues: z.array(z.string().min(1)).default([]),
|
|
884
|
+
sourceLocations: z.array(z.string().min(1)).default([]),
|
|
885
|
+
responsiveImpact: z.string().min(1).max(2000),
|
|
886
|
+
verificationPlan: z.array(z.string().min(1)).default([]),
|
|
887
|
+
changes: z.array(designChangeSchema).default([]),
|
|
888
|
+
branchId: z.string().optional(),
|
|
889
|
+
status: z.enum(['proposed', 'previewing', 'promoted', 'rejected']).default('proposed'),
|
|
890
|
+
createdAt: z.string().datetime(),
|
|
891
|
+
updatedAt: z.string().datetime(),
|
|
892
|
+
});
|
|
893
|
+
export const visualAgentMessageSchema = z.object({
|
|
894
|
+
id: z.string().min(1),
|
|
895
|
+
role: z.enum(['user', 'agent', 'system']),
|
|
896
|
+
body: z.string().min(1),
|
|
897
|
+
createdAt: z.string().datetime(),
|
|
898
|
+
});
|
|
899
|
+
export const visualAgentRequestSchema = z.object({
|
|
900
|
+
id: z.string().min(1),
|
|
901
|
+
sessionId: z.string().min(1),
|
|
902
|
+
title: z.string().min(1).max(120),
|
|
903
|
+
prompt: z.string().min(1).max(8000),
|
|
904
|
+
status: z.enum(['queued', 'thinking', 'ready', 'needs_attention', 'closed']),
|
|
905
|
+
context: visualAgentContextSchema,
|
|
906
|
+
messages: z.array(visualAgentMessageSchema).default([]),
|
|
907
|
+
proposals: z.array(visualAgentProposalSchema).default([]),
|
|
908
|
+
agent: z
|
|
909
|
+
.object({
|
|
910
|
+
name: z.string().min(1),
|
|
911
|
+
version: z.string().optional(),
|
|
912
|
+
taskId: z.string().optional(),
|
|
913
|
+
})
|
|
914
|
+
.optional(),
|
|
915
|
+
claimAttemptId: z.string().optional(),
|
|
916
|
+
claimCapabilityHash: z
|
|
917
|
+
.string()
|
|
918
|
+
.regex(/^[a-f0-9]{64}$/)
|
|
919
|
+
.optional(),
|
|
920
|
+
claimExpiresAt: z.string().datetime().optional(),
|
|
921
|
+
error: z.string().optional(),
|
|
922
|
+
createdAt: z.string().datetime(),
|
|
923
|
+
updatedAt: z.string().datetime(),
|
|
924
|
+
});
|
|
925
|
+
export const stressConditionIdSchema = z.enum([
|
|
926
|
+
'long-content',
|
|
927
|
+
'empty-content',
|
|
928
|
+
'large-numbers',
|
|
929
|
+
'missing-images',
|
|
930
|
+
'loading-state',
|
|
931
|
+
'error-state',
|
|
932
|
+
'offline-state',
|
|
933
|
+
'text-200',
|
|
934
|
+
'browser-zoom-200',
|
|
935
|
+
'high-contrast',
|
|
936
|
+
'monochrome',
|
|
937
|
+
'reduced-motion',
|
|
938
|
+
'keyboard-only',
|
|
939
|
+
]);
|
|
940
|
+
export const stressTestSessionSchema = z.object({
|
|
941
|
+
conditions: z.array(stressConditionIdSchema).default([]),
|
|
942
|
+
scope: z.enum(['selection', 'canvas']).default('selection'),
|
|
943
|
+
targetId: z.string().optional(),
|
|
944
|
+
viewport: z.object({ width: z.number().positive(), height: z.number().positive() }),
|
|
945
|
+
temporary: z.literal(true),
|
|
946
|
+
appliedAt: z.string().datetime(),
|
|
314
947
|
});
|
|
315
948
|
export const surfaceSnapshotSchema = z.object({
|
|
316
949
|
platform: z.enum(['swiftui', 'react-native']),
|
|
@@ -321,6 +954,71 @@ export const surfaceSnapshotSchema = z.object({
|
|
|
321
954
|
controlsByTarget: z.record(z.string(), z.array(controlDescriptorSchema)).default({}),
|
|
322
955
|
updatedAt: z.string().datetime(),
|
|
323
956
|
});
|
|
957
|
+
export const previewContextSchema = z.object({
|
|
958
|
+
version: z.literal(1),
|
|
959
|
+
requestRevision: z.number().int().nonnegative(),
|
|
960
|
+
viewport: z.object({
|
|
961
|
+
id: z.string().min(1),
|
|
962
|
+
width: z.number().positive().optional(),
|
|
963
|
+
height: z.number().positive().optional(),
|
|
964
|
+
}),
|
|
965
|
+
theme: z.string().min(1),
|
|
966
|
+
state: z.string().min(1),
|
|
967
|
+
motionPreference: z.enum(['system', 'reduce', 'no-preference']),
|
|
968
|
+
selectedTarget: z
|
|
969
|
+
.object({
|
|
970
|
+
id: z.string().min(1),
|
|
971
|
+
selector: z.string().min(1),
|
|
972
|
+
})
|
|
973
|
+
.optional(),
|
|
974
|
+
});
|
|
975
|
+
export const previewAxisResultSchema = z.object({
|
|
976
|
+
status: z.enum(['applied', 'current', 'unsupported']),
|
|
977
|
+
method: z.string().min(1),
|
|
978
|
+
evidence: z.array(z.string()).default([]),
|
|
979
|
+
failureReason: z.string().min(1).optional(),
|
|
980
|
+
});
|
|
981
|
+
export const previewContextResultSchema = z.object({
|
|
982
|
+
version: z.literal(1),
|
|
983
|
+
requestRevision: z.number().int().nonnegative(),
|
|
984
|
+
applied: z.boolean(),
|
|
985
|
+
reloadQuery: z.record(z.string(), z.string()).optional(),
|
|
986
|
+
failureReason: z.string().min(1).optional(),
|
|
987
|
+
context: previewContextSchema,
|
|
988
|
+
axes: z.object({
|
|
989
|
+
viewport: previewAxisResultSchema,
|
|
990
|
+
theme: previewAxisResultSchema,
|
|
991
|
+
state: previewAxisResultSchema,
|
|
992
|
+
motion: previewAxisResultSchema,
|
|
993
|
+
}),
|
|
994
|
+
});
|
|
995
|
+
export const previewCapabilitiesSchema = z.object({
|
|
996
|
+
version: z.literal(1),
|
|
997
|
+
viewports: z.array(z.object({
|
|
998
|
+
id: z.string().min(1),
|
|
999
|
+
width: z.number().positive().optional(),
|
|
1000
|
+
height: z.number().positive().optional(),
|
|
1001
|
+
})),
|
|
1002
|
+
themes: z.array(z.object({
|
|
1003
|
+
id: z.string().min(1),
|
|
1004
|
+
selector: z.string().min(1).optional(),
|
|
1005
|
+
attribute: z.string().min(1).optional(),
|
|
1006
|
+
value: z.string().optional(),
|
|
1007
|
+
})),
|
|
1008
|
+
states: z.array(z.object({
|
|
1009
|
+
id: z.string().min(1),
|
|
1010
|
+
methods: z
|
|
1011
|
+
.array(z.enum(['query', 'variant', 'pseudo', 'native', 'reduced-motion']))
|
|
1012
|
+
.default([]),
|
|
1013
|
+
})),
|
|
1014
|
+
motionPreferences: z.array(z.enum(['system', 'reduce', 'no-preference'])).default(['system']),
|
|
1015
|
+
});
|
|
1016
|
+
export const workspaceSnapshotSchema = z.object({
|
|
1017
|
+
version: z.literal(1),
|
|
1018
|
+
capabilities: previewCapabilitiesSchema,
|
|
1019
|
+
currentPreviewContext: previewContextSchema.nullable().default(null),
|
|
1020
|
+
lastPreviewApplication: previewContextResultSchema.nullable().default(null),
|
|
1021
|
+
});
|
|
324
1022
|
export const previewCommandSchema = z.object({
|
|
325
1023
|
id: z.string().min(1),
|
|
326
1024
|
targetId: z.string().min(1),
|
|
@@ -330,37 +1028,43 @@ export const previewCommandSchema = z.object({
|
|
|
330
1028
|
createdAt: z.string().datetime(),
|
|
331
1029
|
});
|
|
332
1030
|
export function changeKey(change) {
|
|
333
|
-
const
|
|
334
|
-
return [
|
|
1031
|
+
const contextSet = change.contextSet;
|
|
1032
|
+
return JSON.stringify([
|
|
335
1033
|
change.target.id,
|
|
336
1034
|
change.property,
|
|
337
1035
|
change.scope,
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
[...change.stateIds].sort()
|
|
342
|
-
]
|
|
1036
|
+
[...contextSet.breakpoints].sort(),
|
|
1037
|
+
[...contextSet.themes].sort(),
|
|
1038
|
+
[...contextSet.states].sort(),
|
|
1039
|
+
[...change.stateIds].sort(),
|
|
1040
|
+
]);
|
|
343
1041
|
}
|
|
344
1042
|
export function coalesceChanges(changes) {
|
|
345
|
-
const
|
|
346
|
-
const
|
|
1043
|
+
const result = [];
|
|
1044
|
+
const editableIndexByKey = new Map();
|
|
347
1045
|
for (const change of changes) {
|
|
348
1046
|
const parsed = designChangeSchema.parse(change);
|
|
349
1047
|
const key = changeKey(parsed);
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
byKey.set(key, parsed);
|
|
1048
|
+
if (!['draft', 'unresolved'].includes(parsed.status)) {
|
|
1049
|
+
result.push(parsed);
|
|
1050
|
+
editableIndexByKey.delete(key);
|
|
354
1051
|
continue;
|
|
355
1052
|
}
|
|
356
|
-
|
|
1053
|
+
const existingIndex = editableIndexByKey.get(key);
|
|
1054
|
+
if (existingIndex === undefined) {
|
|
1055
|
+
editableIndexByKey.set(key, result.length);
|
|
1056
|
+
result.push(parsed);
|
|
1057
|
+
continue;
|
|
1058
|
+
}
|
|
1059
|
+
const existing = result[existingIndex];
|
|
1060
|
+
result[existingIndex] = {
|
|
357
1061
|
...parsed,
|
|
358
1062
|
id: existing.id,
|
|
359
1063
|
before: existing.before,
|
|
360
1064
|
createdAt: existing.createdAt,
|
|
361
|
-
}
|
|
1065
|
+
};
|
|
362
1066
|
}
|
|
363
|
-
return
|
|
1067
|
+
return result;
|
|
364
1068
|
}
|
|
365
1069
|
function renderValue(value, unit) {
|
|
366
1070
|
const rendered = typeof value === 'string' ? value : JSON.stringify(value);
|
|
@@ -385,12 +1089,13 @@ export function renderChangePrompt(changeSet) {
|
|
|
385
1089
|
return lines.join('\n');
|
|
386
1090
|
}
|
|
387
1091
|
for (const [index, change] of approved.entries()) {
|
|
388
|
-
lines.push(`${index + 1}. ${change.target.label} — ${change.property}`, ` - Change: ${renderValue(change.before, change.unit)} → ${renderValue(change.after, change.unit)}`, ` - Scope: ${change.scope}; breakpoint=${change.context.breakpoint}; theme=${change.context.theme}; state=${change.context.state}`, ` - Target: ${change.target.source ? `${change.target.source.file}${change.target.source.line ? `:${change.target.source.line}` : ''}` : JSON.stringify(change.target.locator)}`, ` - Evidence: ${change.confidence}; ${[...new Set([...change.target.evidence, ...change.evidence])].join('; ') || 'none recorded'}`);
|
|
1092
|
+
lines.push(`${index + 1}. ${change.target.label} — ${change.property}`, ` - Change: ${renderValue(change.before, change.unit)} → ${renderValue(change.after, change.unit)}`, ` - Scope: ${change.scope}; breakpoint=${change.context.breakpoint}; theme=${change.context.theme}; state=${change.context.state}`, ` - Affected contexts: breakpoints=${change.contextSet.breakpoints.join(', ')}; themes=${change.contextSet.themes.join(', ')}; states=${change.contextSet.states.join(', ')}`, ` - Target: ${change.target.source ? `${change.target.source.file}${change.target.source.line ? `:${change.target.source.line}` : ''}` : JSON.stringify(change.target.locator)}`, ` - Evidence: ${change.confidence}; ${[...new Set([...change.target.evidence, ...change.evidence])].join('; ') || 'none recorded'}`);
|
|
389
1093
|
if (change.token)
|
|
390
1094
|
lines.push(` - Token: ${change.token}`);
|
|
391
1095
|
if (change.stateIds.length)
|
|
392
1096
|
lines.push(` - State set: ${change.stateIds.join(', ')}`);
|
|
393
|
-
const mapping = change.mappingCandidates.find((candidate) => candidate.id === change.selectedMappingId)
|
|
1097
|
+
const mapping = change.mappingCandidates.find((candidate) => candidate.id === change.selectedMappingId) ??
|
|
1098
|
+
(change.mappingCandidates.length === 1 ? change.mappingCandidates[0] : undefined);
|
|
394
1099
|
if (mapping) {
|
|
395
1100
|
lines.push(` - Source intent: ${mapping.label}; property=${mapping.property}; confidence=${mapping.confidence}; blast-radius=${mapping.blastRadius}`);
|
|
396
1101
|
}
|