openyida 2026.9.3 → 2026.9.4

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 (143) hide show
  1. package/README.md +9 -4
  2. package/bin/yida.js +6 -0
  3. package/lib/app/canvas-compile.js +2 -0
  4. package/lib/app/create-app.js +11 -115
  5. package/lib/app/create-form/batch.js +204 -0
  6. package/lib/app/create-form.js +1 -0
  7. package/lib/app/inline-css-guard.js +79 -0
  8. package/lib/app/page-linter.js +162 -0
  9. package/lib/app/services/canvas-page-schema-builder.js +18 -5
  10. package/lib/app/theme-from-design.js +88 -0
  11. package/lib/app/theme-presets.js +7 -0
  12. package/lib/app/update-app.js +103 -43
  13. package/lib/core/agent-capabilities.js +14 -0
  14. package/lib/core/command-manifest.js +87 -10
  15. package/lib/core/locales/en.js +19 -6
  16. package/lib/core/locales/zh.js +19 -6
  17. package/lib/core/query-data.js +31 -9
  18. package/lib/core/sample.js +80 -5
  19. package/lib/design-plan/design-plan.js +92 -0
  20. package/lib/design-plan/files.js +73 -0
  21. package/lib/design-plan/init.js +112 -0
  22. package/lib/design-plan/materialize.js +898 -0
  23. package/lib/design-plan/normalize.js +275 -0
  24. package/lib/design-plan/parallel.js +56 -0
  25. package/lib/design-plan/patch.js +174 -0
  26. package/lib/design-plan/preview.js +154 -0
  27. package/lib/design-plan/validate.js +133 -0
  28. package/lib/samples/openyida-scaffold/canvas-dialog.canvas.jsx +124 -0
  29. package/lib/samples/openyida-scaffold/canvas-form-drawer.canvas.jsx +265 -68
  30. package/lib/samples/openyida-scaffold/canvas-nav/data.jsx +60 -0
  31. package/lib/samples/openyida-scaffold/canvas-nav/dock.jsx +13 -0
  32. package/lib/samples/openyida-scaffold/canvas-nav/mixed.jsx +34 -0
  33. package/lib/samples/openyida-scaffold/canvas-nav/shared.jsx +43 -0
  34. package/lib/samples/openyida-scaffold/canvas-nav/side.jsx +19 -0
  35. package/lib/samples/openyida-scaffold/canvas-nav/sidebar.jsx +96 -0
  36. package/lib/samples/openyida-scaffold/canvas-nav/tabs.jsx +31 -0
  37. package/lib/samples/openyida-scaffold/canvas-nav/top.jsx +31 -0
  38. package/lib/samples/yida-canvas-table-form/table-form-batch-submit.canvas.jsx +2 -1
  39. package/lib/samples/yida-rechart/trend-combo.canvas.jsx +1 -1
  40. package/package.json +1 -1
  41. package/scripts/postinstall.js +5 -3
  42. package/yida-skills/SKILL.md +37 -6
  43. package/yida-skills/references/field-and-url-reference.md +11 -1
  44. package/yida-skills/skills/yida-app/SKILL.md +42 -7
  45. package/yida-skills/skills/yida-app/references/common-issues.md +1 -1
  46. package/yida-skills/skills/yida-app/workflow/incremental-preview.md +42 -0
  47. package/yida-skills/skills/yida-app/workflow/parallel-work.md +69 -0
  48. package/yida-skills/skills/yida-app/workflow/plan/step-1-understand.md +5 -0
  49. package/yida-skills/skills/yida-app/workflow/plan/step-2-confirm.md +7 -0
  50. package/yida-skills/skills/yida-app/workflow/plan/step-4-deliver.md +69 -0
  51. package/yida-skills/skills/yida-app/workflow/plan/workflow.md +45 -0
  52. package/yida-skills/skills/yida-app/workflow/step-1-resource-context.md +2 -0
  53. package/yida-skills/skills/yida-app/workflow/step-2-design.md +24 -26
  54. package/yida-skills/skills/yida-app/workflow/step-3-create-or-reuse-app.md +24 -7
  55. package/yida-skills/skills/yida-app/workflow/step-4-forms-processes.md +8 -2
  56. package/yida-skills/skills/yida-app/workflow/step-6-main-page.md +2 -0
  57. package/yida-skills/skills/yida-app/workflow/step-7-page-code.md +12 -4
  58. package/yida-skills/skills/yida-app/workflow/step-8-publish-navigation.md +20 -10
  59. package/yida-skills/skills/yida-app/workflow/step-9-output-finish.md +8 -4
  60. package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +9 -6
  61. package/yida-skills/skills/yida-canvas-custom-page/references/canvas-authoring-examples.md +1 -1
  62. package/yida-skills/skills/yida-canvas-custom-page/references/canvas-style-implementation-guide.md +55 -38
  63. package/yida-skills/skills/yida-canvas-custom-page/references/dialog-guide.md +35 -0
  64. package/yida-skills/skills/yida-canvas-custom-page/references/navigation-and-entry-guide.md +45 -93
  65. package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +6 -0
  66. package/yida-skills/skills/yida-canvas-table-form/SKILL.md +2 -2
  67. package/yida-skills/skills/yida-create-app/SKILL.md +33 -23
  68. package/yida-skills/skills/yida-create-form-page/SKILL.md +4 -0
  69. package/yida-skills/skills/yida-create-form-page/references/batch-forms.md +55 -0
  70. package/yida-skills/skills/yida-create-page/SKILL.md +2 -0
  71. package/yida-skills/skills/yida-custom-page/references/coding-guide.md +2 -0
  72. package/yida-skills/skills/yida-design/SKILL.md +31 -36
  73. package/yida-skills/skills/yida-design/references/ask-human-interaction-contract.md +187 -0
  74. package/yida-skills/skills/yida-design/references/design-mode.md +14 -0
  75. package/yida-skills/skills/yida-design/references/style-design-selection.md +3 -3
  76. package/yida-skills/skills/yida-design/references/theme/app-custom-theme-template.css +9 -1
  77. package/yida-skills/skills/yida-design/references/theme/theme-token-presets.md +43 -1
  78. package/yida-skills/skills/yida-design/references/visual-scaffold-recipes.md +1 -0
  79. package/yida-skills/skills/yida-design/sub_skill/page-design/SKILL.md +2 -2
  80. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/SKILL.md +26 -0
  81. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/agents/openai.yaml +4 -0
  82. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/README.md +214 -0
  83. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/build-plan-template.html +1181 -0
  84. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/business-flows.png +0 -0
  85. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/data-models.png +0 -0
  86. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/form-normal.svg +4 -0
  87. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/form-process.svg +6 -0
  88. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/graph-fit.svg +3 -0
  89. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/graph-relations.svg +7 -0
  90. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/graph-zoom-in.svg +4 -0
  91. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/graph-zoom-out.svg +4 -0
  92. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/overview.png +0 -0
  93. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/pages.png +0 -0
  94. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/visual-style.png +0 -0
  95. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/build-plan-compact-schema.md +138 -0
  96. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/build-plan-schema.md +456 -0
  97. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/page-patterns.md +204 -0
  98. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/visual-design.md +137 -0
  99. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/visual-theme-selection.md +168 -0
  100. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/scripts/render_build_plan.py +1172 -0
  101. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/scripts/validate_design_themes.py +190 -0
  102. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/README.md +98 -0
  103. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/airy-media-grid.md +339 -0
  104. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/airy-modular-clarity.md +348 -0
  105. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/airy-structured-clarity.md +344 -0
  106. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/ambient-halo-layered.md +345 -0
  107. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/dark-focus-layered.md +355 -0
  108. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/dark-luminous-modular.md +377 -0
  109. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/deep-stage-duotone.md +352 -0
  110. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/hairline-runway-clarity.md +389 -0
  111. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/high-contrast-modular.md +364 -0
  112. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/index.json +329 -0
  113. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/interlocked-vivid-modules.md +348 -0
  114. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/matrix-grid-focus.md +345 -0
  115. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/media-rail-inspector.md +379 -0
  116. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/mist-layered-signal.md +347 -0
  117. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/modular-rail-signal.md +352 -0
  118. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/mono-grid-signal.md +386 -0
  119. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/ribbon-ledger-lift.md +384 -0
  120. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/segmented-meter-clarity.md +388 -0
  121. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/split-rail-precision.md +341 -0
  122. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/status-framed-media-grid.md +361 -0
  123. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/page-patterns/index.json +63 -0
  124. package/yida-skills/skills/yida-design/workflow/output-design.md +39 -5
  125. package/yida-skills/skills/yida-design/workflow/output-prd.md +1 -1
  126. package/yida-skills/skills/yida-design/workflow/step-1-read-brief.md +3 -3
  127. package/yida-skills/skills/yida-design/workflow/step-2-theme-system.md +7 -11
  128. package/yida-skills/skills/yida-design/workflow/step-4-wireframe-interaction.md +6 -0
  129. package/yida-skills/skills/yida-design/workflow/step-5-visual-states.md +3 -3
  130. package/yida-skills/skills/yida-design/workflow/step-6-handoff.md +4 -4
  131. package/yida-skills/skills/yida-nav-group/SKILL.md +1 -0
  132. package/yida-skills/skills/yida-nav-shell/SKILL.md +21 -18
  133. package/yida-skills/skills/yida-nav-shell/references/nav-shell-patterns.md +212 -266
  134. package/yida-skills/skills/yida-prd/SKILL.md +21 -32
  135. package/yida-skills/skills/yida-prd/references/plan-content.md +169 -0
  136. package/yida-skills/skills/yida-prd/workflow/output-prd.md +36 -8
  137. package/yida-skills/skills/yida-prd/workflow/plan-business.md +12 -0
  138. package/yida-skills/skills/yida-prd/workflow/step-2-information-architecture.md +7 -3
  139. package/yida-skills/skills/yida-publish-page/SKILL.md +1 -1
  140. package/yida-skills/skills/yida-requirement-analysis/SKILL.md +17 -19
  141. package/yida-skills/skills/yida-requirement-analysis/workflow/prepare-brief.md +65 -0
  142. package/yida-skills/skills-index.json +11 -10
  143. package/yida-skills/skills/yida-prd/workflow/step-1-read-brief.md +0 -15
@@ -15,6 +15,7 @@ const THEN_CALLBACK_LINE_LIMIT = 50;
15
15
  const CALLBACK_SCAN_LINE_LIMIT = 80;
16
16
  const parser = Babel.packages.parser;
17
17
  const traverse = Babel.packages.traverse.default || Babel.packages.traverse;
18
+ const FORBIDDEN_SEARCH_FORM_DYNAMIC_ORDER_FIELDS = new Set(['gmtCreate']);
18
19
 
19
20
  const PARSER_OPTIONS = {
20
21
  sourceType: 'module',
@@ -404,6 +405,142 @@ function getNodeLine(node) {
404
405
  return node && node.loc && node.loc.start && node.loc.start.line ? node.loc.start.line : 1;
405
406
  }
406
407
 
408
+ function getStaticPropertyName(property) {
409
+ if (!property || (property.type !== 'ObjectProperty' && property.type !== 'ObjectMethod')) {
410
+ return '';
411
+ }
412
+ const key = property.key;
413
+ if (!key) {
414
+ return '';
415
+ }
416
+ if (key.type === 'Identifier' && !property.computed) {
417
+ return key.name;
418
+ }
419
+ if (key.type === 'StringLiteral') {
420
+ return key.value;
421
+ }
422
+ return '';
423
+ }
424
+
425
+ function getMemberPropertyName(member) {
426
+ if (!member || (member.type !== 'MemberExpression' && member.type !== 'OptionalMemberExpression') || !member.property) {
427
+ return '';
428
+ }
429
+ if (member.property.type === 'Identifier' && !member.computed) {
430
+ return member.property.name;
431
+ }
432
+ if (member.property.type === 'StringLiteral') {
433
+ return member.property.value;
434
+ }
435
+ return '';
436
+ }
437
+
438
+ function getStaticStringValue(node) {
439
+ if (!node) {
440
+ return null;
441
+ }
442
+ if (node.type === 'StringLiteral') {
443
+ return node.value;
444
+ }
445
+ if (node.type === 'TemplateLiteral' && node.expressions.length === 0) {
446
+ return node.quasis.map(part => part.value.cooked || '').join('');
447
+ }
448
+ return null;
449
+ }
450
+
451
+ function unwrapJsonStringify(node) {
452
+ if (!node || node.type !== 'CallExpression' || !node.callee || node.callee.type !== 'MemberExpression') {
453
+ return node;
454
+ }
455
+ const object = node.callee.object;
456
+ if (object && object.type === 'Identifier' && object.name === 'JSON' && getMemberPropertyName(node.callee) === 'stringify') {
457
+ return node.arguments && node.arguments[0] ? node.arguments[0] : node;
458
+ }
459
+ return node;
460
+ }
461
+
462
+ function resolveStaticBinding(node, pathRef) {
463
+ if (!node || node.type !== 'Identifier' || !pathRef || !pathRef.scope) {
464
+ return node;
465
+ }
466
+ const binding = pathRef.scope.getBinding(node.name);
467
+ if (!binding || !binding.constant || !binding.path || binding.path.node.type !== 'VariableDeclarator') {
468
+ return node;
469
+ }
470
+ return binding.path.node.init || node;
471
+ }
472
+
473
+ function findForbiddenDynamicOrderFields(node, pathRef) {
474
+ const resolved = resolveStaticBinding(node, pathRef);
475
+ const value = resolveStaticBinding(unwrapJsonStringify(resolved), pathRef);
476
+ if (value && value.type === 'ObjectExpression') {
477
+ return value.properties
478
+ .map(getStaticPropertyName)
479
+ .filter(field => FORBIDDEN_SEARCH_FORM_DYNAMIC_ORDER_FIELDS.has(field));
480
+ }
481
+
482
+ const staticValue = getStaticStringValue(value);
483
+ if (staticValue === null) {
484
+ return [];
485
+ }
486
+ try {
487
+ const parsed = JSON.parse(staticValue);
488
+ if (!parsed || Array.isArray(parsed) || typeof parsed !== 'object') {
489
+ return [];
490
+ }
491
+ return Object.keys(parsed).filter(field => FORBIDDEN_SEARCH_FORM_DYNAMIC_ORDER_FIELDS.has(field));
492
+ } catch {
493
+ return [];
494
+ }
495
+ }
496
+
497
+ function findDynamicOrderProperty(params) {
498
+ if (!params || params.type !== 'ObjectExpression') {
499
+ return null;
500
+ }
501
+ return params.properties.find(property => getStaticPropertyName(property) === 'dynamicOrder') || null;
502
+ }
503
+
504
+ function reportForbiddenDynamicOrderValue(pathRef, value, issueNode, errors, disableMap) {
505
+ const forbiddenFields = findForbiddenDynamicOrderFields(value, pathRef);
506
+ if (forbiddenFields.length === 0) {
507
+ return;
508
+ }
509
+ pushIssue(
510
+ errors,
511
+ getNodeLine(issueNode),
512
+ 'searchformdata-dynamic-order-metadata',
513
+ t('publish.lint_searchformdata_dynamic_order_metadata', forbiddenFields.join(', ')),
514
+ disableMap
515
+ );
516
+ }
517
+
518
+ function reportForbiddenDynamicOrder(pathRef, params, errors, disableMap) {
519
+ const resolvedParams = resolveStaticBinding(params, pathRef);
520
+ const dynamicOrderProperty = findDynamicOrderProperty(resolvedParams);
521
+ if (!dynamicOrderProperty) {
522
+ return;
523
+ }
524
+ reportForbiddenDynamicOrderValue(
525
+ pathRef,
526
+ dynamicOrderProperty.value,
527
+ dynamicOrderProperty,
528
+ errors,
529
+ disableMap
530
+ );
531
+ }
532
+
533
+ function isUrlSearchParamsInstance(node, pathRef) {
534
+ const resolved = resolveStaticBinding(node, pathRef);
535
+ return !!(
536
+ resolved &&
537
+ resolved.type === 'NewExpression' &&
538
+ resolved.callee &&
539
+ resolved.callee.type === 'Identifier' &&
540
+ resolved.callee.name === 'URLSearchParams'
541
+ );
542
+ }
543
+
407
544
  function getJsxElementName(nameNode) {
408
545
  if (!nameNode) {
409
546
  return '';
@@ -633,6 +770,24 @@ function collectAstLintIssues(sourceCode, errors, warnings, disableMap) {
633
770
  },
634
771
  CallExpression(pathRef) {
635
772
  const callee = pathRef.node.callee;
773
+ if (getMemberPropertyName(callee) === 'searchFormDatas') {
774
+ const params = pathRef.node.arguments && pathRef.node.arguments[0];
775
+ reportForbiddenDynamicOrder(pathRef, params, errors, disableMap);
776
+ }
777
+
778
+ const urlSearchParamsMethod = getMemberPropertyName(callee);
779
+ if (
780
+ (urlSearchParamsMethod === 'append' || urlSearchParamsMethod === 'set') &&
781
+ callee &&
782
+ callee.type === 'MemberExpression' &&
783
+ isUrlSearchParamsInstance(callee.object, pathRef)
784
+ ) {
785
+ const args = pathRef.node.arguments || [];
786
+ if (getStaticStringValue(args[0]) === 'dynamicOrder') {
787
+ reportForbiddenDynamicOrderValue(pathRef, args[1], pathRef.node, errors, disableMap);
788
+ }
789
+ }
790
+
636
791
  const isSetState = callee &&
637
792
  callee.type === 'MemberExpression' &&
638
793
  (callee.object.type === 'ThisExpression' ||
@@ -670,6 +825,13 @@ function collectAstLintIssues(sourceCode, errors, warnings, disableMap) {
670
825
  pushIssue(warnings, getNodeLine(pathRef.node), 'setState-non-timestamp', t('publish.lint_setstate_non_timestamp'), disableMap);
671
826
  }
672
827
  },
828
+ NewExpression(pathRef) {
829
+ const callee = pathRef.node.callee;
830
+ if (callee && callee.type === 'Identifier' && callee.name === 'URLSearchParams') {
831
+ const params = pathRef.node.arguments && pathRef.node.arguments[0];
832
+ reportForbiddenDynamicOrder(pathRef, params, errors, disableMap);
833
+ }
834
+ },
673
835
  FunctionDeclaration(pathRef) {
674
836
  const id = pathRef.node.id;
675
837
  if (!id || id.name !== 'renderJsx') {
@@ -216,7 +216,13 @@ function openyidaCreateRouterBridge(utils) {
216
216
  if (router && typeof router.push === 'function') {
217
217
  bridge.availableMethods.push('push');
218
218
  bridge.push = function() {
219
- return router.push.apply(router, arguments);
219
+ var args = Array.prototype.slice.call(arguments);
220
+ // 完整地址使用 URL 模式,避免平台再次拼接应用路由前缀。
221
+ if (args[3] === undefined && typeof args[0] === 'string' &&
222
+ /^(https?:[/][/]|[/][/]|[/]APP_[^/?#]+([/?#]|$))/i.test(args[0])) {
223
+ args[3] = true;
224
+ }
225
+ return router.push.apply(router, args);
220
226
  };
221
227
  } else {
222
228
  bridge.push = function() {
@@ -349,8 +355,8 @@ function buildCanvasPageSchemaObject(sourceCode, runtimeCode, importedModules, f
349
355
  componentName: 'Page',
350
356
  id: nextNodeId(),
351
357
  props: {
352
- contentBgColor: 'var(--pod-page-bg-color, var(--color-white, #fff))',
353
- pageStyle: { backgroundColor: 'var(--pod-page-bg-color, var(--color-white, #fff))' },
358
+ contentBgColor: 'var(--oyd-page-background, var(--pod-page-bg-color, var(--color-white, #fff)))',
359
+ pageStyle: { backgroundColor: 'var(--oyd-page-background, var(--pod-page-bg-color, var(--color-white, #fff)))' },
354
360
  contentMargin: '0',
355
361
  contentPadding: '0',
356
362
  showTitle: false,
@@ -358,10 +364,17 @@ function buildCanvasPageSchemaObject(sourceCode, runtimeCode, importedModules, f
358
364
  templateVersion: '1.0.0',
359
365
  contentMarginMobile: '0',
360
366
  className: 'page_' + nextSuffix(),
361
- contentBgColorMobile: 'var(--pod-page-bg-color, var(--color-white, #fff))',
367
+ contentBgColorMobile: 'var(--oyd-page-background, var(--pod-page-bg-color, var(--color-white, #fff)))',
362
368
  },
363
369
  condition: true,
364
- css: '.vc-page-yida-page{--yida-form-content-padding:0;--yida-form-content-margin:0;--yida-layout-padding:0}.vc-deep-container-entry.vc-rootcontent{padding:0!important;margin-top:0!important;margin-right:0!important;margin-bottom:0!important;margin-left:0!important}',
370
+ css: [
371
+ '.vc-page-yida-page{--yida-form-content-padding:0;--yida-form-content-margin:0;--yida-layout-padding:0}',
372
+ '.vc-deep-container-entry.vc-rootcontent{padding:0!important;margin-top:0!important;margin-right:0!important;margin-bottom:0!important;margin-left:0!important}',
373
+ // 隔离首个子元素的外边距,浮导 margin 不得把整个 Canvas 顶离页面顶部。
374
+ '.yida-code-canvas{display:flow-root}',
375
+ // Canvas 整页的纯容器至少撑满当前视口,避免嵌入时高度塌陷而裁掉内容。
376
+ '.vc-page-yida-pure-container:has(> .yida-code-canvas){min-height:100vh}',
377
+ ].join(''),
365
378
  methods: {
366
379
  __initMethods__: {
367
380
  type: 'js',
@@ -0,0 +1,88 @@
1
+ 'use strict';
2
+
3
+ const { CliError } = require('../core/cli-error');
4
+ const { REQUIRED_BRAND_SCALE_TOKENS, validateThemeCssContent, extractThemeColor } = require('./custom-theme');
5
+
6
+ // Both flat Fast tokens and grouped Plan tokens use one scalar declaration per line.
7
+ function readDesignTokens(markdown) {
8
+ const frontmatter = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(markdown);
9
+ if (!frontmatter) {
10
+ throw new CliError('design.md 缺少包含 token 的 frontmatter', { code: 'DESIGN_THEME_TOKENS_REQUIRED' });
11
+ }
12
+ const tokens = {};
13
+ for (const line of frontmatter[1].split(/\r?\n/)) {
14
+ const match = /^\s*["']?(--[\w-]+)["']?\s*:\s*(.+?)\s*$/.exec(line);
15
+ if (!match) {continue;}
16
+ const name = match[1];
17
+ // YAML comments may follow a quoted value; # inside a color or string is data.
18
+ const scalar = /^("(?:[^"\\]|\\.)*"|'(?:[^']|'')*'|.*?)\s*(?:\s+#.*)?$/.exec(match[2]);
19
+ const raw = scalar[1].trim();
20
+ let value = raw;
21
+ if (raw.startsWith('"')) {
22
+ try { value = JSON.parse(raw); } catch {
23
+ throw new CliError(`token ${name} 必须是单行字符串`, { code: 'DESIGN_THEME_TOKEN_INVALID' });
24
+ }
25
+ } else if (raw.startsWith("'")) {
26
+ if (!raw.endsWith("'")) {throw new CliError(`token ${name} 引号不完整`, { code: 'DESIGN_THEME_TOKEN_INVALID' });}
27
+ value = raw.slice(1, -1).replace(/''/g, "'");
28
+ }
29
+ if (typeof value !== 'string' || !value.trim() || /[;{}\r\n<>]|\/\*|\*\//.test(value) || /^[|>]/.test(value)) {
30
+ throw new CliError(`token ${name} 必须是已确定的单行 CSS 值`, { code: 'DESIGN_THEME_TOKEN_INVALID' });
31
+ }
32
+ if (name in tokens && tokens[name] !== value) {
33
+ throw new CliError(`token ${name} 存在冲突值`, { code: 'DESIGN_THEME_TOKEN_CONFLICT' });
34
+ }
35
+ if (/^--color-brand1-(4|7|8)$/.test(name)) {
36
+ throw new CliError(`平台不支持 ${name}`, { code: 'DESIGN_THEME_TOKEN_INVALID' });
37
+ }
38
+ tokens[name] = value;
39
+ }
40
+ const missing = REQUIRED_BRAND_SCALE_TOKENS.filter(name => !tokens[name]);
41
+ if (missing.length) {
42
+ throw new CliError(`design.md 缺少品牌 token:${missing.join(', ')}`, { code: 'DESIGN_THEME_TOKENS_REQUIRED' });
43
+ }
44
+ return tokens;
45
+ }
46
+
47
+ function resolveDesignTokens(markdown) {
48
+ const tokens = readDesignTokens(markdown);
49
+ for (const [alias, source] of Object.entries({
50
+ '--color-brand-1': '--color-brand1-10', '--color-brand-2': '--color-brand1-1',
51
+ '--color-brand-3': '--color-brand1-6', '--color-brand-4': '--color-brand1-9',
52
+ })) {
53
+ tokens[alias] = tokens[alias] || tokens[source];
54
+ }
55
+ tokens['--color-group'] = tokens['--color-group'] || [6, 1, 5, 2, 9, 3].map(n => tokens[`--color-brand1-${n}`]).join(', ');
56
+ return tokens;
57
+ }
58
+
59
+ function applyDesignTokens(template, markdown, previousMarkdown) {
60
+ const tokens = resolveDesignTokens(markdown);
61
+ const previous = previousMarkdown ? resolveDesignTokens(previousMarkdown) : null;
62
+ const changed = Object.fromEntries(Object.entries(tokens).filter(([name, value]) => !previous || previous[name] !== value));
63
+ if (!Object.keys(changed).length) {
64
+ validateThemeCssContent(template);
65
+ extractThemeColor(template);
66
+ return template;
67
+ }
68
+ const root = /^:root\s*\{([\s\S]*?)^\}/m.exec(template);
69
+ if (!root) {throw new Error('应用主题模板缺少 :root');}
70
+ const declarations = /^[ \t]*(--[\w-]+)\s*:\s*([^;]+);/gm;
71
+ const original = Object.fromEntries([...root[1].matchAll(declarations)].map(m => [m[1], m[2].trim()]));
72
+ const brandValues = new Map(REQUIRED_BRAND_SCALE_TOKENS.filter(name => original[name] && changed[name]).map(name => [original[name], tokens[name]]));
73
+ const escaped = [...brandValues.keys()].map(value => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
74
+ const brandPattern = new RegExp(escaped.join('|'), 'g');
75
+ const replaceBrand = value => brandValues.size ? value.replace(brandPattern, old => brandValues.get(old)) : value;
76
+ let body = root[1].replace(declarations, (line, name, value) => line.replace(value, () => changed[name] || replaceBrand(value)));
77
+ const extra = Object.entries(changed).filter(([name]) => !(name in original));
78
+ body += extra.map(([name, value]) => ` ${name}: ${value};\n`).join('');
79
+ // Keep scoped nav/light/dark selectors and their variable relationships intact.
80
+ const tail = template.slice(root.index + root[0].length).replace(declarations,
81
+ (line, name, value) => line.replace(value, () => replaceBrand(value)));
82
+ const css = `${template.slice(0, root.index)}:root {${body}}${tail}`;
83
+ validateThemeCssContent(css);
84
+ extractThemeColor(css);
85
+ return css;
86
+ }
87
+
88
+ module.exports = { readDesignTokens, applyDesignTokens };
@@ -126,7 +126,14 @@ function assertPresetThemeKey(themeKey) {
126
126
  );
127
127
  }
128
128
 
129
+ // colour 保存协议只接受平台主题 key;自定义色值另存 themeColor。
130
+ function assertAppThemeKey(themeKey) {
131
+ if (themeKey === 'custom') { return; }
132
+ assertPresetThemeKey(themeKey);
133
+ }
134
+
129
135
  module.exports = {
136
+ assertAppThemeKey,
130
137
  SUPPORTED_THEME_KEYS,
131
138
  APP_THEME_TOKEN_PRESETS,
132
139
  APP_THEME_TOKEN_PRESET_KEYS,
@@ -16,7 +16,7 @@ const { createAuthRef } = require('../core/yida-client');
16
16
  const { t } = require('../core/i18n');
17
17
  const { buildYidaI18n } = require('../core/yida-i18n');
18
18
  const { throwCommandError, throwUsage } = require('../core/command-errors');
19
- const { assertPresetThemeKey } = require('./theme-presets');
19
+ const { assertAppThemeKey } = require('./theme-presets');
20
20
  const {
21
21
  buildCustomThemeStyle,
22
22
  normalizeCssColorToHex,
@@ -284,12 +284,19 @@ function buildUpdateAppNamePostData(params, authRef) {
284
284
  }
285
285
 
286
286
  function buildUpdateAppPostData(params, currentApp = {}, authRef) {
287
+ assertAppThemeKey(params.colour);
288
+ if (params.colour && params.colour !== 'custom' && (params.themeFile || params.customThemeStyle || params.themeColor)) {
289
+ throw new Error(t('update_app.theme_preset_conflict'));
290
+ }
287
291
  const appName = params.name || pickText(currentApp.appName, params.appType);
288
292
  const description = params.desc || pickText(currentApp.description, appName);
289
293
  const icon = resolveAppIcon(params, currentApp);
290
294
 
291
295
  const currentThemeColor = pickAppField(currentApp, 'themeColor', 'THEME_COLOR');
292
296
  const currentCustomThemeStyle = pickAppField(currentApp, 'customThemeStyle', 'CUSTOM_THEME_STYLE');
297
+ if (params.colour === 'custom' && !params.themeColor && !currentThemeColor) {
298
+ throw new Error(t('update_app.custom_theme_color_required'));
299
+ }
293
300
  const postDataObj = {
294
301
  _csrf_token: authRef.csrfToken,
295
302
  _locale_time_zone_offset: '28800000',
@@ -305,7 +312,7 @@ function buildUpdateAppPostData(params, currentApp = {}, authRef) {
305
312
  iconUrl: icon,
306
313
  colour: params.customThemeStyle || params.themeColor
307
314
  ? 'custom'
308
- : (params.colour || currentApp.colour || (currentApp.config && currentApp.config.COLOUR) || 'deepBlue'),
315
+ : (params.colour || currentApp.colour || (currentApp.config && currentApp.config.COLOUR) || (currentThemeColor ? 'custom' : 'deepBlue')),
309
316
  // updateApp 的报错文案叫 appMode,但实际接受的请求字段是 mode。
310
317
  mode: currentApp.mode || (currentApp.config && currentApp.config.APPMODE) || 'normal',
311
318
  type: currentApp.type || (currentApp.config && currentApp.config.APPTYPE) || 'single',
@@ -339,18 +346,23 @@ function buildUpdateAppPostData(params, currentApp = {}, authRef) {
339
346
  sentryMode: currentApp.sentryMode || (currentApp.config && currentApp.config.SENTRY_MODE) || 'y',
340
347
  };
341
348
 
342
- if (params.hideAppNav !== null) {
349
+ if (params.hideAppNav !== null && params.hideAppNav !== undefined) {
343
350
  postDataObj.hideAppNav = params.hideAppNav;
351
+ } else {
352
+ const hideAppNav = pickAppField(currentApp, 'hideAppNav', 'HIDE_APP_NAV');
353
+ if (hideAppNav !== undefined && hideAppNav !== null) {
354
+ postDataObj.hideAppNav = hideAppNav;
355
+ }
344
356
  }
345
357
  if (params.themeColor) {
346
358
  postDataObj.themeColor = params.themeColor;
347
- } else if (params.colour) {
359
+ } else if (params.colour && params.colour !== 'custom') {
348
360
  postDataObj.themeColor = '';
349
361
  } else if (currentThemeColor !== undefined && currentThemeColor !== null) {
350
362
  postDataObj.themeColor = currentThemeColor;
351
363
  }
352
364
 
353
- if (params.colour && !params.customThemeStyle) {
365
+ if (params.colour && params.colour !== 'custom' && !params.customThemeStyle) {
354
366
  // 切回平台预置主题时必须清空旧 CSS,避免 CUSTOM_THEME_STYLE 继续覆盖 colour。
355
367
  postDataObj.customThemeStyle = '';
356
368
  } else {
@@ -382,34 +394,89 @@ async function fetchCurrentApp(appType, authRef) {
382
394
  return response.content || {};
383
395
  }
384
396
 
397
+ function getThemeSettings(app) {
398
+ const rawStyle = pickAppField(app, 'customThemeStyle', 'CUSTOM_THEME_STYLE');
399
+ let customThemeStyle = rawStyle || null;
400
+ if (typeof customThemeStyle === 'string') {
401
+ try { customThemeStyle = JSON.parse(customThemeStyle); } catch { /* Keep malformed data visible to verification. */ }
402
+ }
403
+ return {
404
+ colour: pickAppField(app, 'colour', 'COLOUR') || '',
405
+ themeColor: pickAppField(app, 'themeColor', 'THEME_COLOR') || '',
406
+ customThemeStyle,
407
+ };
408
+ }
409
+
410
+ function themeSettingsMatch(expected, actual) {
411
+ const normalizeColor = (value) => value ? normalizeCssColorToHex(value) || value : '';
412
+ if (expected.colour !== actual.colour || normalizeColor(expected.themeColor) !== normalizeColor(actual.themeColor)) {
413
+ return false;
414
+ }
415
+ const expectedStyle = expected.customThemeStyle;
416
+ const actualStyle = actual.customThemeStyle;
417
+ if (!expectedStyle || !actualStyle || typeof expectedStyle !== 'object' || typeof actualStyle !== 'object') {
418
+ return expectedStyle === actualStyle;
419
+ }
420
+ return ['enabled', 'iframePropagation'].every((key) => Boolean(expectedStyle[key]) === Boolean(actualStyle[key])) &&
421
+ ['cssUrl', 'cssFileName'].every((key) => (expectedStyle[key] || '') === (actualStyle[key] || ''));
422
+ }
423
+
424
+ /** updateApp 整包保存;与基础设置页一致,读取最新配置后合并,并核验主题资源。 */
425
+ async function saveAppSettings(params, authRef) {
426
+ const currentApp = await fetchCurrentApp(params.appType, authRef);
427
+ const payload = buildUpdateAppPostData(params, currentApp, authRef);
428
+ const response = await requestWithAutoLogin((auth) => httpPost(
429
+ auth.baseUrl,
430
+ `/${params.appType}/query/app/updateApp.json?_api=Form.updateApp&_mock=false&_stamp=${Date.now()}`,
431
+ querystring.stringify({ ...payload, _csrf_token: auth.csrfToken })
432
+ ), authRef);
433
+ if (!response || !response.success) { return response; }
434
+
435
+ const expected = getThemeSettings(payload);
436
+ if (!params.colour && !params.themeColor && !params.customThemeStyle && !expected.themeColor && !expected.customThemeStyle) {
437
+ return response;
438
+ }
439
+ let actual;
440
+ let readbackError;
441
+ // 只重试查询,避免重复上传资源或用旧快照再次覆盖设置。
442
+ for (let attempt = 0; attempt < 3; attempt++) {
443
+ if (attempt) { await new Promise((resolve) => setTimeout(resolve, attempt * 250)); }
444
+ try {
445
+ actual = getThemeSettings(await fetchCurrentApp(params.appType, authRef));
446
+ readbackError = undefined;
447
+ if (themeSettingsMatch(expected, actual)) {
448
+ return { ...response, themeVerification: { verified: true, ...actual } };
449
+ }
450
+ } catch (error) {
451
+ readbackError = error.message;
452
+ }
453
+ }
454
+ return {
455
+ success: false,
456
+ errorCode: 'APP_THEME_NOT_PERSISTED',
457
+ errorMsg: t('update_app.theme_not_persisted'),
458
+ themeVerification: { verified: false, expected, actual, readbackError },
459
+ };
460
+ }
461
+
385
462
  async function applyCustomThemeUpdate(appType, themeParams, authRef) {
386
463
  const params = {
387
464
  ...themeParams,
388
465
  appType,
389
- navTheme: themeParams.navTheme || 'light',
390
- logoSource: themeParams.logoSource || 'appIcon',
391
- layoutDirection: normalizeLayoutDirection(themeParams.layoutDirection || 'side'),
466
+ layoutDirection: normalizeLayoutDirection(themeParams.layoutDirection),
392
467
  };
393
468
  assertNavTheme(params.navTheme);
394
469
  assertLogoSource(params.logoSource);
395
470
 
396
471
  const themeFile = readThemeCssFile(params.themeFile);
397
472
  params.themeColor = themeFile.themeColor;
398
- const currentApp = await fetchCurrentApp(appType, authRef);
399
473
  const uploadResponse = await requestWithAutoLogin(
400
474
  (auth) => uploadCustomThemeFile(appType, params.themeFile, auth),
401
475
  authRef
402
476
  );
403
477
  params.customThemeStyle = buildCustomThemeStyle(uploadResponse);
404
478
 
405
- return requestWithAutoLogin((auth) => {
406
- const requestPath = `/${appType}/query/app/updateApp.json?_api=Form.updateApp&_mock=false&_stamp=${Date.now()}`;
407
- return httpPost(
408
- auth.baseUrl,
409
- requestPath,
410
- querystring.stringify(buildUpdateAppPostData(params, currentApp, auth))
411
- );
412
- }, authRef);
479
+ return saveAppSettings(params, authRef);
413
480
  }
414
481
 
415
482
  async function run(args) {
@@ -444,7 +511,10 @@ async function run(args) {
444
511
  printUsage();
445
512
  throwUsage(t('update_app.missing_update_field'), t('update_app.usage'));
446
513
  }
447
- assertPresetThemeKey(params.colour);
514
+ assertAppThemeKey(params.colour);
515
+ if (params.colour && params.colour !== 'custom' && (params.themeFile || params.themeColor)) {
516
+ throw new Error(t('update_app.theme_preset_conflict'));
517
+ }
448
518
  params.themeColor = normalizeThemeColor(params.themeColor);
449
519
  assertNavTheme(params.navTheme);
450
520
  assertLogoSource(params.logoSource);
@@ -482,29 +552,16 @@ async function run(args) {
482
552
  warn(t('update_app.layout_notice'));
483
553
  }
484
554
 
485
- const currentApp = shellUpdate ? await fetchCurrentApp(params.appType, authRef) : null;
486
- if (params.themeFile) {
487
- const uploadResponse = await requestWithAutoLogin(
488
- (auth) => uploadCustomThemeFile(params.appType, params.themeFile, auth),
489
- authRef
490
- );
491
- params.customThemeStyle = buildCustomThemeStyle(uploadResponse);
492
- }
493
- const requestPath = shellUpdate
494
- ? `/${params.appType}/query/app/updateApp.json?_api=Form.updateApp&_mock=false&_stamp=${Date.now()}`
495
- : `/query/app/updateAppName.json?_api=Form.updateAppName&_mock=false&_stamp=${Date.now()}`;
496
-
497
- const response = await requestWithAutoLogin((auth) => {
498
- const postDataObj = shellUpdate
499
- ? buildUpdateAppPostData(params, currentApp, auth)
500
- : buildUpdateAppNamePostData(params, auth);
501
- const postData = querystring.stringify(postDataObj);
502
- return httpPost(
503
- auth.baseUrl,
504
- requestPath,
505
- postData
506
- );
507
- }, authRef);
555
+ const response = params.themeFile ? await applyCustomThemeUpdate(params.appType, params, authRef)
556
+ : shellUpdate ? await saveAppSettings(params, authRef) : await requestWithAutoLogin((auth) => {
557
+ const postDataObj = buildUpdateAppNamePostData(params, auth);
558
+ const postData = querystring.stringify(postDataObj);
559
+ return httpPost(
560
+ auth.baseUrl,
561
+ `/query/app/updateAppName.json?_api=Form.updateAppName&_mock=false&_stamp=${Date.now()}`,
562
+ postData
563
+ );
564
+ }, authRef);
508
565
 
509
566
  // 输出结果
510
567
  if (response && response.success) {
@@ -515,6 +572,7 @@ async function run(args) {
515
572
  console.log(JSON.stringify({
516
573
  success: true,
517
574
  appType: params.appType,
575
+ themeVerification: response.themeVerification,
518
576
  updatedFields: {
519
577
  name: params.name || undefined,
520
578
  desc: params.desc || undefined,
@@ -531,8 +589,9 @@ async function run(args) {
531
589
  } else {
532
590
  const errorMsg = response ? response.errorMsg || t('common.unknown_error') : t('common.request_failed');
533
591
  chalkResult(false, t('update_app.failed', errorMsg));
534
- console.log(JSON.stringify({ success: false, error: errorMsg }));
535
- throwCommandError(errorMsg);
592
+ console.log(JSON.stringify({ success: false, appType: params.appType, error: errorMsg,
593
+ errorCode: response && response.errorCode, themeVerification: response && response.themeVerification }));
594
+ throwCommandError(errorMsg, { code: response && response.errorCode, details: response && response.themeVerification });
536
595
  }
537
596
  }
538
597
 
@@ -548,4 +607,5 @@ module.exports = {
548
607
  normalizeLayoutDirection,
549
608
  syncSystemIconColor,
550
609
  applyCustomThemeUpdate,
610
+ saveAppSettings,
551
611
  };
@@ -141,7 +141,16 @@ const BUILDER_CORE_COMMAND_IDS = Object.freeze([
141
141
  'get-permission',
142
142
  'save-permission',
143
143
  'create-app',
144
+ 'design-plan.init',
145
+ 'design-plan.materialize',
146
+ 'design-plan.preview',
147
+ 'design-plan.patch',
148
+ 'sample',
149
+ 'update-app',
150
+ 'update-form-config',
151
+ 'get-form-config',
144
152
  'create-form.create',
153
+ 'create-form.batch',
145
154
  'create-form.icons',
146
155
  'create-page',
147
156
  'publish',
@@ -452,6 +461,11 @@ function buildFullAppArtifactRoute(manifest) {
452
461
  orchestrator_skill_id: workflow.orchestrator_skill_id,
453
462
  requirement_analysis_skill_id: workflow.requirement_analysis_skill_id,
454
463
  requirement_brief_path: workflow.requirement_brief_path,
464
+ design_mode_policy: workflow.design_mode_policy,
465
+ navigation_policy: workflow.navigation_policy,
466
+ plan_command_ids: workflow.plan_command_ids,
467
+ theme_command_ids: workflow.theme_command_ids,
468
+ navigation_command_ids: workflow.navigation_command_ids,
455
469
  artifact_generation: workflow.artifact_generation,
456
470
  };
457
471
  }