openyida 2026.7.19 → 2026.7.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.
Files changed (139) hide show
  1. package/README.md +14 -0
  2. package/bin/yida.js +107 -18
  3. package/lib/app/create-app.js +12 -1
  4. package/lib/app/create-form/args.js +13 -0
  5. package/lib/app/create-form.js +154 -67
  6. package/lib/app/create-page.js +45 -9
  7. package/lib/app/get-schema.js +65 -14
  8. package/lib/app/import-app.js +92 -41
  9. package/lib/app/publish.js +289 -556
  10. package/lib/app/schema-field-resolution.js +370 -0
  11. package/lib/app/services/app-reader.js +85 -0
  12. package/lib/app/services/app-service.js +235 -0
  13. package/lib/app/services/canvas-page-compiler.js +91 -0
  14. package/lib/app/services/canvas-page-schema-builder.js +149 -0
  15. package/lib/app/services/field-bindings.js +68 -0
  16. package/lib/app/services/form-compiler.js +1922 -0
  17. package/lib/app/services/form-mode-service.js +182 -0
  18. package/lib/app/services/form-schema-patcher.js +333 -0
  19. package/lib/app/services/form-schema-reader.js +59 -0
  20. package/lib/app/services/form-service.js +379 -0
  21. package/lib/app/services/form-validation.js +562 -0
  22. package/lib/app/services/native-page-compiler.js +92 -0
  23. package/lib/app/services/native-page-schema-builder.js +428 -0
  24. package/lib/app/services/page-resource-service.js +185 -0
  25. package/lib/app/update-app.js +18 -6
  26. package/lib/auth/token-auth.js +34 -3
  27. package/lib/auth/token-store.js +22 -4
  28. package/lib/bridge/bridge.js +37 -5
  29. package/lib/core/agent-capabilities.js +5 -3
  30. package/lib/core/command-manifest.js +40 -0
  31. package/lib/core/doctor.js +15 -4
  32. package/lib/core/env-cmd.js +1 -1
  33. package/lib/core/env.js +7 -6
  34. package/lib/core/legacy-schema-guard.js +408 -0
  35. package/lib/core/locales/en.js +8 -46
  36. package/lib/core/locales/zh.js +8 -46
  37. package/lib/core/sample.js +1 -1
  38. package/lib/core/utils.js +334 -66
  39. package/lib/core/yida-client.js +104 -3
  40. package/lib/formula/evaluate.js +8 -66
  41. package/lib/formula/field-refs.js +83 -0
  42. package/lib/process/configure-process.js +44 -1992
  43. package/lib/process/create-process.js +54 -87
  44. package/lib/process/services/process-compiler.js +2056 -0
  45. package/lib/process/services/process-reader.js +232 -0
  46. package/lib/process/services/process-resource-service.js +571 -0
  47. package/lib/process/services/process-service.js +122 -0
  48. package/lib/process/services/process-stage-checkpoint.js +246 -0
  49. package/lib/report/append.js +7 -26
  50. package/lib/report/http.js +31 -5
  51. package/lib/report/index.js +10 -2
  52. package/lib/samples/yida-canvas-custom-page/business-list.canvas.jsx +63 -14
  53. package/lib/samples/yida-canvas-custom-page/dashboard-overview.canvas.jsx +176 -32
  54. package/lib/samples/yida-canvas-custom-page/data-management.canvas.jsx +228 -14
  55. package/lib/samples/yida-canvas-custom-page/split-pane-detail.canvas.jsx +204 -8
  56. package/lib/schema/adapters/app-adapter.js +210 -0
  57. package/lib/schema/adapters/form-adapter.js +1362 -0
  58. package/lib/schema/adapters/keys.js +19 -0
  59. package/lib/schema/adapters/page-adapter.js +566 -0
  60. package/lib/schema/adapters/process-adapter.js +519 -0
  61. package/lib/schema/applier.js +1878 -0
  62. package/lib/schema/apply-store.js +1083 -0
  63. package/lib/schema/command.js +645 -0
  64. package/lib/schema/dependency-graph.js +83 -0
  65. package/lib/schema/errors.js +481 -0
  66. package/lib/schema/hash.js +9 -0
  67. package/lib/schema/manifest-limits.js +177 -0
  68. package/lib/schema/manifest-loader.js +309 -0
  69. package/lib/schema/manifest-schema-v1.json +193 -0
  70. package/lib/schema/normalize-manifest.js +281 -0
  71. package/lib/schema/page-canvas-foundation.js +447 -0
  72. package/lib/schema/page-data-source-builder.js +561 -0
  73. package/lib/schema/page-foundation.js +556 -0
  74. package/lib/schema/page-source-loader.js +314 -0
  75. package/lib/schema/planner.js +691 -0
  76. package/lib/schema/remote-dispatch-boundary.js +39 -0
  77. package/lib/schema/remote-missing.js +21 -0
  78. package/lib/schema/remote-reader.js +189 -0
  79. package/lib/schema/resource-registry.js +157 -0
  80. package/lib/schema/server-revision.js +71 -0
  81. package/lib/schema/sort.js +28 -0
  82. package/lib/schema/state-store.js +589 -0
  83. package/package.json +2 -2
  84. package/scripts/postinstall.js +8 -12
  85. package/scripts/validate-package-size.js +1 -1
  86. package/yida-skills/SKILL.md +114 -29
  87. package/yida-skills/references/schema-as-code-phase1.md +99 -0
  88. package/yida-skills/references/setup-and-env.md +59 -59
  89. package/yida-skills/references/task-retrospective.md +6 -6
  90. package/yida-skills/skills/yida-app/SKILL.md +60 -14
  91. package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +18 -7
  92. package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +3 -3
  93. package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +7 -1
  94. package/yida-skills/skills/yida-create-app/SKILL.md +21 -3
  95. package/yida-skills/skills/yida-create-form-page/SKILL.md +19 -1
  96. package/yida-skills/skills/yida-create-page/SKILL.md +16 -3
  97. package/yida-skills/skills/yida-create-process/SKILL.md +100 -4
  98. package/yida-skills/skills/yida-custom-page/SKILL.md +19 -4
  99. package/yida-skills/skills/yida-dashboard/SKILL.md +1 -1
  100. package/yida-skills/skills/yida-dashboard/references/pitfalls.md +1 -1
  101. package/yida-skills/skills/yida-get-schema/SKILL.md +49 -5
  102. package/yida-skills/skills/yida-i18n/SKILL.md +1 -1
  103. package/yida-skills/skills/yida-integration/SKILL.md +7 -1
  104. package/yida-skills/skills/yida-login/SKILL.md +50 -99
  105. package/yida-skills/skills/yida-page-config/SKILL.md +7 -1
  106. package/yida-skills/skills/yida-page-uiux/references/app/blueprint.md +0 -1
  107. package/yida-skills/skills/yida-page-uiux/references/app/navigation-patterns.md +0 -1
  108. package/yida-skills/skills/yida-page-uiux/references/app/role-journey.md +0 -1
  109. package/yida-skills/skills/yida-page-uiux/references/dashboard/app-blueprint.md +0 -1
  110. package/yida-skills/skills/yida-page-uiux/references/dashboard/chart-contracts.md +0 -1
  111. package/yida-skills/skills/yida-page-uiux/references/dashboard/component-contracts.md +0 -1
  112. package/yida-skills/skills/yida-page-uiux/references/dashboard/layout-patterns.md +0 -1
  113. package/yida-skills/skills/yida-page-uiux/references/dashboard/theme-recipes.md +0 -1
  114. package/yida-skills/skills/yida-page-uiux/references/detail/object-narrative.md +0 -1
  115. package/yida-skills/skills/yida-page-uiux/references/detail/section-patterns.md +0 -1
  116. package/yida-skills/skills/yida-page-uiux/references/detail/timeline-and-related.md +0 -1
  117. package/yida-skills/skills/yida-page-uiux/references/landing/assets-workflow.md +0 -1
  118. package/yida-skills/skills/yida-page-uiux/references/landing/industry-playbooks.md +0 -1
  119. package/yida-skills/skills/yida-page-uiux/references/landing/research-levels.md +0 -1
  120. package/yida-skills/skills/yida-page-uiux/references/landing/section-patterns.md +0 -1
  121. package/yida-skills/skills/yida-page-uiux/references/list/drawer-detail.md +0 -1
  122. package/yida-skills/skills/yida-page-uiux/references/list/empty-loading-error.md +0 -1
  123. package/yida-skills/skills/yida-page-uiux/references/list/filter-patterns.md +0 -1
  124. package/yida-skills/skills/yida-page-uiux/references/list/table-patterns.md +0 -1
  125. package/yida-skills/skills/yida-page-uiux/references/scenes/screen.md +0 -1
  126. package/yida-skills/skills/yida-page-uiux/references/workbench/entry-patterns.md +0 -1
  127. package/yida-skills/skills/yida-page-uiux/references/workbench/portal-layouts.md +0 -1
  128. package/yida-skills/skills/yida-page-uiux/references/workbench/task-feed.md +0 -1
  129. package/yida-skills/skills/yida-page-uiux/workflow/output-decision-block.md +1 -1
  130. package/yida-skills/skills/yida-process-rule/SKILL.md +35 -1
  131. package/yida-skills/skills/yida-publish-page/SKILL.md +46 -4
  132. package/yida-skills/skills/yida-report/SKILL.md +7 -1
  133. package/yida-skills/skills-index.json +14 -46
  134. package/yida-skills/skills/large-file-write/SKILL.md +0 -93
  135. package/yida-skills/skills/large-file-write/references/write-patterns.md +0 -147
  136. package/yida-skills/skills/large-file-write/scripts/write.js +0 -157
  137. package/yida-skills/skills/sls-log-workbench/SKILL.md +0 -134
  138. package/yida-skills/skills/sls-log-workbench/references/workbench-reference.md +0 -130
  139. package/yida-skills/skills/sls-log-workbench/sls-query.js +0 -295
@@ -0,0 +1,447 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('crypto');
4
+ const { isDeepStrictEqual } = require('util');
5
+ const { schemaError } = require('./errors');
6
+ const { hashStable } = require('./state-store');
7
+ const {
8
+ CANVAS_PAGE_PROFILE,
9
+ createCanvasCompiledHash,
10
+ isCompiledCanvasPage,
11
+ } = require('../app/services/canvas-page-compiler');
12
+
13
+ const DISPLAY_FORM_TYPE = 'display';
14
+ const COMPONENT_ORDER_KEYS = new Set(['children', 'componentsMap', 'componentsTree']);
15
+ const SHELL_NEUTRAL_TIME_KEYS = new Set([
16
+ 'createtime',
17
+ 'createdat',
18
+ 'gmtcreate',
19
+ 'gmtmodified',
20
+ 'modifiedtime',
21
+ 'timestamp',
22
+ 'updatedat',
23
+ ]);
24
+
25
+ function projectCanvasPageSchema(input = {}) {
26
+ const observedIdentity = normalizeObservedIdentity(input);
27
+ if (observedIdentity.formType !== DISPLAY_FORM_TYPE) {
28
+ foundationError(
29
+ 'SCHEMA_PAGE_FOUNDATION_PROFILE_UNSUPPORTED',
30
+ 'Observed page is not a display resource.'
31
+ );
32
+ }
33
+ const schema = normalizeSchema(input.schema);
34
+ const structure = inspectCanvasStructure(schema);
35
+ const canvasProps = inspectCanvasProps(structure.canvasComponent);
36
+ const managed = {
37
+ compiledHash: createCanvasCompiledHash(canvasProps.runtimeCode, canvasProps.importedModules),
38
+ formType: observedIdentity.formType,
39
+ profile: CANVAS_PAGE_PROFILE,
40
+ sourceHash: hashStableText(canvasProps.code),
41
+ title: observedIdentity.title,
42
+ };
43
+ return {
44
+ classification: 'SCHEMA_SAVED',
45
+ managed,
46
+ managedHash: hashStable(managed),
47
+ remoteSchemaHash: hashRemoteSchema(schema),
48
+ structure: {
49
+ canvasComponentCount: structure.canvasComponents.length,
50
+ pageComponentCount: structure.pageComponents.length,
51
+ },
52
+ };
53
+ }
54
+
55
+ function buildCanvasPageSchemaPatch(input = {}) {
56
+ const observedIdentity = normalizeObservedIdentity(input);
57
+ const desiredIdentity = normalizeDesiredIdentity(input);
58
+ assertIdentityMatches(observedIdentity, desiredIdentity);
59
+ const compiledPage = requireCompiledPage(input.compiledPage);
60
+ const schema = normalizeSchema(input.remoteSchema);
61
+ const beforeProjection = projectCanvasPageSchema({
62
+ observedFormType: observedIdentity.formType,
63
+ observedTitle: observedIdentity.title,
64
+ schema,
65
+ });
66
+ if (
67
+ input.expectedRemoteSchemaHash !== undefined &&
68
+ input.expectedRemoteSchemaHash !== beforeProjection.remoteSchemaHash
69
+ ) {
70
+ foundationError('SCHEMA_PAGE_FOUNDATION_CONFLICT', 'Remote page Schema changed before patching.');
71
+ }
72
+
73
+ const desiredManaged = buildDesiredManaged(desiredIdentity.title, compiledPage);
74
+ if (isDeepStrictEqual(beforeProjection.managed, desiredManaged)) {
75
+ return {
76
+ baselineRemoteSchemaHash: beforeProjection.remoteSchemaHash,
77
+ managed: desiredManaged,
78
+ managedHash: hashStable(desiredManaged),
79
+ preparedRemoteSchemaHash: beforeProjection.remoteSchemaHash,
80
+ schema,
81
+ unmanagedHash: hashCanvasUnmanagedSchema(schema),
82
+ };
83
+ }
84
+
85
+ const beforeUnmanagedHash = hashCanvasUnmanagedSchema(schema);
86
+ const patched = clonePlain(schema);
87
+ const structure = inspectCanvasStructure(patched);
88
+ const canvasProps = requireCanvasProps(structure.canvasComponent);
89
+ canvasProps.code = compiledPage.source;
90
+ canvasProps.runtimeCode = compiledPage.runtimeCode;
91
+ canvasProps.importedModules = compiledPage.importedModules;
92
+
93
+ const afterUnmanagedHash = hashCanvasUnmanagedSchema(patched);
94
+ if (afterUnmanagedHash !== beforeUnmanagedHash) {
95
+ foundationError('SCHEMA_PAGE_FOUNDATION_SCHEMA_INVALID', 'Canvas page patch changed unmanaged Schema content.');
96
+ }
97
+ const afterProjection = projectCanvasPageSchema({
98
+ observedFormType: observedIdentity.formType,
99
+ observedTitle: observedIdentity.title,
100
+ schema: patched,
101
+ });
102
+ if (!isDeepStrictEqual(afterProjection.managed, desiredManaged)) {
103
+ foundationError('SCHEMA_PAGE_FOUNDATION_SCHEMA_INVALID', 'Canvas page patch did not produce the desired managed content.');
104
+ }
105
+
106
+ return {
107
+ baselineRemoteSchemaHash: beforeProjection.remoteSchemaHash,
108
+ managed: desiredManaged,
109
+ managedHash: afterProjection.managedHash,
110
+ preparedRemoteSchemaHash: afterProjection.remoteSchemaHash,
111
+ schema: patched,
112
+ unmanagedHash: beforeUnmanagedHash,
113
+ };
114
+ }
115
+
116
+ function buildDesiredManaged(title, compiledPage) {
117
+ return {
118
+ compiledHash: compiledPage.compiledHash,
119
+ formType: DISPLAY_FORM_TYPE,
120
+ profile: CANVAS_PAGE_PROFILE,
121
+ sourceHash: compiledPage.sourceHash,
122
+ title,
123
+ };
124
+ }
125
+
126
+ function normalizeObservedIdentity(input) {
127
+ const title = normalizeTitle(
128
+ input.observedTitle,
129
+ 'SCHEMA_PAGE_FOUNDATION_OBSERVED_IDENTITY_INVALID',
130
+ 'Observed page title is invalid.'
131
+ );
132
+ const formType = normalizeFormType(
133
+ input.observedFormType,
134
+ 'SCHEMA_PAGE_FOUNDATION_OBSERVED_IDENTITY_INVALID',
135
+ 'Observed page form type is invalid.'
136
+ );
137
+ return { formType, title };
138
+ }
139
+
140
+ function normalizeDesiredIdentity(input) {
141
+ const title = normalizeTitle(
142
+ input.desiredTitle,
143
+ 'SCHEMA_PAGE_FOUNDATION_SCHEMA_INVALID',
144
+ 'Desired page title is invalid.'
145
+ );
146
+ const formType = input.desiredFormType === undefined
147
+ ? DISPLAY_FORM_TYPE
148
+ : normalizeFormType(
149
+ input.desiredFormType,
150
+ 'SCHEMA_PAGE_FOUNDATION_SCHEMA_INVALID',
151
+ 'Desired page form type is invalid.'
152
+ );
153
+ if (formType !== DISPLAY_FORM_TYPE) {
154
+ foundationError(
155
+ 'SCHEMA_PAGE_FOUNDATION_PROFILE_UNSUPPORTED',
156
+ 'Desired page must use the display resource type.'
157
+ );
158
+ }
159
+ return { formType, title };
160
+ }
161
+
162
+ function normalizeTitle(value, code, message) {
163
+ const title = typeof value === 'string' ? value.trim() : '';
164
+ if (!title || title.length > 256 || containsUnsafeText(title)) {
165
+ foundationError(code, message);
166
+ }
167
+ return title;
168
+ }
169
+
170
+ function normalizeFormType(value, code, message) {
171
+ const formType = typeof value === 'string' ? value.trim().toLowerCase() : '';
172
+ if (!formType || formType.length > 64 || containsUnsafeText(formType)) {
173
+ foundationError(code, message);
174
+ }
175
+ return formType;
176
+ }
177
+
178
+ function assertIdentityMatches(observedIdentity, desiredIdentity) {
179
+ if (
180
+ observedIdentity.title !== desiredIdentity.title ||
181
+ observedIdentity.formType !== desiredIdentity.formType
182
+ ) {
183
+ foundationError(
184
+ 'SCHEMA_PAGE_FOUNDATION_CONFLICT',
185
+ 'Observed page identity does not match desired identity.'
186
+ );
187
+ }
188
+ }
189
+
190
+ function normalizeSchema(value) {
191
+ let candidate = value;
192
+ if (
193
+ candidate &&
194
+ typeof candidate === 'object' &&
195
+ !Array.isArray(candidate) &&
196
+ candidate.success === true &&
197
+ Object.prototype.hasOwnProperty.call(candidate, 'content')
198
+ ) {
199
+ candidate = candidate.content;
200
+ }
201
+ if (typeof candidate === 'string') {
202
+ if (!candidate.trim()) {
203
+ foundationError('SCHEMA_PAGE_FOUNDATION_SCHEMA_INVALID', 'Canvas page Schema content is empty.');
204
+ }
205
+ try {
206
+ candidate = JSON.parse(candidate);
207
+ } catch (error) {
208
+ foundationError('SCHEMA_PAGE_FOUNDATION_SCHEMA_INVALID', 'Canvas page Schema content is invalid.');
209
+ }
210
+ }
211
+ assertPlainObject(candidate);
212
+ return clonePlain(candidate);
213
+ }
214
+
215
+ function inspectCanvasStructure(schema) {
216
+ if (
217
+ schema.schemaType !== 'superform' ||
218
+ typeof schema.schemaVersion !== 'string' ||
219
+ !schema.schemaVersion.startsWith('5') ||
220
+ !Array.isArray(schema.pages) ||
221
+ schema.pages.length !== 1
222
+ ) {
223
+ foundationError('SCHEMA_PAGE_FOUNDATION_SCHEMA_INVALID', 'Canvas page Schema root is invalid.');
224
+ }
225
+ const page = schema.pages[0];
226
+ assertPlainObject(page);
227
+ if (!Array.isArray(page.componentsTree)) {
228
+ foundationError('SCHEMA_PAGE_FOUNDATION_SCHEMA_INVALID', 'Canvas page component tree is invalid.');
229
+ }
230
+ const components = collectComponents(page.componentsTree);
231
+ if (components.some(component => component.componentName === 'Jsx')) {
232
+ foundationError('SCHEMA_PAGE_FOUNDATION_PROFILE_UNSUPPORTED', 'Native page Schema is unsupported for canvas/default.');
233
+ }
234
+ const pageComponents = components.filter(component => component.componentName === 'Page');
235
+ const canvasComponents = components.filter(component => component.componentName === 'YidaCodeCanvas');
236
+ if (pageComponents.length !== 1 || canvasComponents.length !== 1) {
237
+ foundationError('SCHEMA_PAGE_FOUNDATION_SCHEMA_INVALID', 'Canvas page component structure is ambiguous.');
238
+ }
239
+ return {
240
+ canvasComponent: canvasComponents[0],
241
+ canvasComponents,
242
+ pageComponents,
243
+ };
244
+ }
245
+
246
+ function inspectCanvasProps(canvasComponent) {
247
+ const props = requireCanvasProps(canvasComponent);
248
+ const code = props.code;
249
+ const runtimeCode = props.runtimeCode;
250
+ const importedModules = props.importedModules;
251
+ if (
252
+ typeof code !== 'string' ||
253
+ !code ||
254
+ typeof runtimeCode !== 'string' ||
255
+ !runtimeCode ||
256
+ typeof importedModules !== 'string' ||
257
+ !isJsonArrayString(importedModules)
258
+ ) {
259
+ foundationError('SCHEMA_PAGE_FOUNDATION_SCHEMA_INVALID', 'Canvas page managed props are incomplete.');
260
+ }
261
+ return { code, importedModules, runtimeCode };
262
+ }
263
+
264
+ function requireCanvasProps(canvasComponent) {
265
+ assertPlainObject(canvasComponent);
266
+ if (canvasComponent.props === undefined) {
267
+ canvasComponent.props = {};
268
+ }
269
+ assertPlainObject(canvasComponent.props);
270
+ return canvasComponent.props;
271
+ }
272
+
273
+ function collectComponents(value, result = []) {
274
+ if (!value || typeof value !== 'object') {
275
+ return result;
276
+ }
277
+ if (Array.isArray(value)) {
278
+ for (const item of value) {
279
+ collectComponents(item, result);
280
+ }
281
+ return result;
282
+ }
283
+ if (typeof value.componentName === 'string') {
284
+ result.push(value);
285
+ }
286
+ for (const key of Object.keys(value).sort()) {
287
+ collectComponents(value[key], result);
288
+ }
289
+ return result;
290
+ }
291
+
292
+ function hashCanvasUnmanagedSchema(schema) {
293
+ const unmanaged = clonePlain(schema);
294
+ stripManagedCanvasProps(unmanaged);
295
+ return hashStable(normalizeShellFingerprintValue(unmanaged));
296
+ }
297
+
298
+ function stripManagedCanvasProps(schema) {
299
+ let structure;
300
+ try {
301
+ structure = inspectCanvasStructure(schema);
302
+ } catch (error) {
303
+ return;
304
+ }
305
+ const props = requireCanvasProps(structure.canvasComponent);
306
+ delete props.code;
307
+ delete props.runtimeCode;
308
+ delete props.importedModules;
309
+ }
310
+
311
+ function normalizeShellFingerprintValue(value, pathSegments = [], inheritedComponentName = '') {
312
+ if (Array.isArray(value)) {
313
+ const normalized = value.map((item, index) => normalizeShellFingerprintValue(
314
+ item,
315
+ pathSegments.concat(String(index)),
316
+ inheritedComponentName
317
+ ));
318
+ const collectionName = pathSegments[pathSegments.length - 1];
319
+ if (
320
+ COMPONENT_ORDER_KEYS.has(collectionName) &&
321
+ normalized.every(item => (
322
+ item &&
323
+ typeof item === 'object' &&
324
+ !Array.isArray(item) &&
325
+ typeof item.componentName === 'string'
326
+ ))
327
+ ) {
328
+ normalized.sort(compareCanonicalValues);
329
+ }
330
+ return normalized;
331
+ }
332
+ if (!value || typeof value !== 'object') {
333
+ return value;
334
+ }
335
+
336
+ const ownComponentName = typeof value.componentName === 'string'
337
+ ? value.componentName
338
+ : '';
339
+ const componentName = ownComponentName || inheritedComponentName;
340
+ const result = {};
341
+ for (const key of Object.keys(value).sort()) {
342
+ if (isShellNeutralKey(value, key, pathSegments, ownComponentName, componentName)) {
343
+ continue;
344
+ }
345
+ result[key] = normalizeShellFingerprintValue(
346
+ value[key],
347
+ pathSegments.concat(key),
348
+ componentName
349
+ );
350
+ }
351
+ return result;
352
+ }
353
+
354
+ function isShellNeutralKey(value, key, pathSegments, ownComponentName, componentName) {
355
+ if (SHELL_NEUTRAL_TIME_KEYS.has(key.toLowerCase())) {
356
+ return true;
357
+ }
358
+ if (key === 'id' && ownComponentName) {
359
+ return true;
360
+ }
361
+ if (
362
+ key === 'id' &&
363
+ pathSegments.length === 2 &&
364
+ pathSegments[0] === 'pages' &&
365
+ /^\d+$/.test(pathSegments[1])
366
+ ) {
367
+ return true;
368
+ }
369
+ return (
370
+ key === 'className' &&
371
+ componentName === 'Page' &&
372
+ typeof value[key] === 'string' &&
373
+ /^page_[a-z0-9]+$/i.test(value[key])
374
+ );
375
+ }
376
+
377
+ function compareCanonicalValues(left, right) {
378
+ const leftValue = JSON.stringify(left);
379
+ const rightValue = JSON.stringify(right);
380
+ if (leftValue < rightValue) {
381
+ return -1;
382
+ }
383
+ if (leftValue > rightValue) {
384
+ return 1;
385
+ }
386
+ return 0;
387
+ }
388
+
389
+ function hashRemoteSchema(schema) {
390
+ const normalized = clonePlain(schema);
391
+ delete normalized.gmtModified;
392
+ return hashStable(normalized);
393
+ }
394
+
395
+ function requireCompiledPage(value) {
396
+ if (!isCompiledCanvasPage(value)) {
397
+ foundationError('SCHEMA_PAGE_SOURCE_INVALID', 'Page patch requires trusted compiled Canvas source.');
398
+ }
399
+ return value;
400
+ }
401
+
402
+ function hashStableText(value) {
403
+ return 'sha256:' + crypto.createHash('sha256').update(value).digest('hex');
404
+ }
405
+
406
+ function isJsonArrayString(value) {
407
+ try {
408
+ return Array.isArray(JSON.parse(value));
409
+ } catch {
410
+ return false;
411
+ }
412
+ }
413
+
414
+ function assertPlainObject(value) {
415
+ const prototype = value && typeof value === 'object' ? Object.getPrototypeOf(value) : undefined;
416
+ if (
417
+ !value ||
418
+ typeof value !== 'object' ||
419
+ Array.isArray(value) ||
420
+ (prototype !== Object.prototype && prototype !== null)
421
+ ) {
422
+ foundationError('SCHEMA_PAGE_FOUNDATION_SCHEMA_INVALID', 'Canvas page Schema value must be a plain object.');
423
+ }
424
+ }
425
+
426
+ function containsUnsafeText(value) {
427
+ for (let index = 0; index < value.length; index++) {
428
+ const code = value.charCodeAt(index);
429
+ if ((code < 32 && code !== 9 && code !== 10 && code !== 13) || code === 127) {
430
+ return true;
431
+ }
432
+ }
433
+ return false;
434
+ }
435
+
436
+ function clonePlain(value) {
437
+ return JSON.parse(JSON.stringify(value));
438
+ }
439
+
440
+ function foundationError(code, message) {
441
+ throw schemaError(code, message);
442
+ }
443
+
444
+ module.exports = {
445
+ buildCanvasPageSchemaPatch,
446
+ projectCanvasPageSchema,
447
+ };