openyida 2026.7.30 → 2026.8.3

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 (102) hide show
  1. package/README.md +12 -9
  2. package/bin/yida.js +6 -1
  3. package/docs/capabilities.md +10 -5
  4. package/lib/ai/ai.js +2 -2
  5. package/lib/app/canvas-compile.js +5 -7
  6. package/lib/app/create-app.js +6 -6
  7. package/lib/app/create-form/args.js +18 -0
  8. package/lib/app/create-form.js +37 -16
  9. package/lib/app/create-page.js +32 -5
  10. package/lib/app/generate-page.js +123 -2
  11. package/lib/app/nav-group.js +117 -0
  12. package/lib/app/page-ir.js +64 -38
  13. package/lib/app/publish.js +25 -4
  14. package/lib/app/theme-presets.js +96 -1
  15. package/lib/bridge/bridge.js +28 -4
  16. package/lib/core/browser-handoff.js +1 -1
  17. package/lib/core/command-contract.js +558 -0
  18. package/lib/core/command-manifest.js +107 -9
  19. package/lib/core/doctor.js +2 -2
  20. package/lib/core/env-cmd.js +1 -1
  21. package/lib/core/env.js +1 -0
  22. package/lib/core/locales/en.js +20 -16
  23. package/lib/core/locales/zh.js +22 -18
  24. package/lib/core/utils.js +17 -1
  25. package/lib/flash-note/build-flash-note-prompt.js +1 -1
  26. package/lib/samples/yida-canvas-custom-page/business-list.canvas.jsx +71 -4
  27. package/lib/samples/yida-canvas-custom-page/dashboard-overview.canvas.jsx +71 -4
  28. package/lib/samples/yida-canvas-custom-page/data-management.canvas.jsx +81 -14
  29. package/lib/samples/yida-canvas-custom-page/data-screen.canvas.jsx +151 -68
  30. package/lib/samples/yida-canvas-custom-page/native-components-smoke.canvas.jsx +22 -9
  31. package/lib/samples/yida-canvas-custom-page/official-homepage.canvas.jsx +115 -115
  32. package/lib/samples/yida-canvas-custom-page/portal-native-components.canvas.jsx +18 -4
  33. package/lib/samples/yida-canvas-custom-page/portal-shell-home.canvas.jsx +31 -13
  34. package/lib/samples/yida-canvas-custom-page/split-pane-detail.canvas.jsx +71 -4
  35. package/lib/samples/yida-canvas-custom-page/workbench-home.canvas.jsx +47 -17
  36. package/lib/samples/yida-custom-page/workbench-home.oyd.jsx +1 -1
  37. package/package.json +1 -1
  38. package/scripts/postinstall.js +23 -4
  39. package/yida-skills/SKILL.md +35 -35
  40. package/yida-skills/references/development-rules.md +1 -1
  41. package/yida-skills/references/setup-and-env.md +4 -4
  42. package/yida-skills/references/task-retrospective.md +3 -3
  43. package/yida-skills/skills/yida-app/SKILL.md +52 -44
  44. package/yida-skills/skills/yida-app/references/app-build-contract.md +114 -0
  45. package/yida-skills/skills/yida-app/references/ui-guidance-by-mode.md +77 -0
  46. package/yida-skills/skills/yida-app-uiux/SKILL.md +4 -2
  47. package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +26 -23
  48. package/yida-skills/skills/yida-canvas-custom-page/references/canvas-authoring-examples.md +3 -3
  49. package/yida-skills/skills/yida-canvas-custom-page/references/canvas-design-system.md +14 -12
  50. package/yida-skills/skills/yida-canvas-custom-page/references/component-library-guide.md +1 -1
  51. package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +27 -9
  52. package/yida-skills/skills/yida-canvas-custom-page/references/dependencies-and-cdn.md +4 -4
  53. package/yida-skills/skills/yida-canvas-custom-page/references/employeefield-verification.md +12 -4
  54. package/yida-skills/skills/yida-canvas-custom-page/references/native-components-bridge.md +14 -12
  55. package/yida-skills/skills/yida-canvas-custom-page/references/navigation-and-entry-guide.md +100 -0
  56. package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +8 -4
  57. package/yida-skills/skills/yida-canvas-data-binding/SKILL.md +33 -13
  58. package/yida-skills/skills/yida-canvas-table-form/SKILL.md +1 -1
  59. package/yida-skills/skills/yida-chart/references/echarts-design-spec.md +3 -2
  60. package/yida-skills/skills/yida-create-app/SKILL.md +21 -35
  61. package/yida-skills/skills/yida-create-form-page/SKILL.md +7 -3
  62. package/yida-skills/skills/yida-create-form-page/references/form-field-properties.md +13 -4
  63. package/yida-skills/skills/yida-create-page/SKILL.md +6 -3
  64. package/yida-skills/skills/yida-custom-page/SKILL.md +3 -3
  65. package/yida-skills/skills/yida-custom-page/references/coding-guide.md +3 -3
  66. package/yida-skills/skills/yida-custom-page/references/component-jsx-guide.md +3 -3
  67. package/yida-skills/skills/yida-custom-page/references/design-system.md +5 -3
  68. package/yida-skills/skills/yida-dashboard/SKILL.md +5 -5
  69. package/yida-skills/skills/yida-dashboard/references/interaction-patterns.md +2 -2
  70. package/yida-skills/skills/yida-dashboard/references/pitfalls.md +3 -3
  71. package/yida-skills/skills/yida-dashboard/references/structure-and-layout.md +1 -1
  72. package/yida-skills/skills/yida-dashboard/references/theme-presets.md +11 -8
  73. package/yida-skills/skills/yida-form-detail/SKILL.md +33 -4
  74. package/yida-skills/skills/yida-login/SKILL.md +12 -10
  75. package/yida-skills/skills/yida-nav-group/SKILL.md +5 -1
  76. package/yida-skills/skills/yida-nav-shell/SKILL.md +4 -3
  77. package/yida-skills/skills/yida-nav-shell/references/nav-shell-patterns.md +3 -3
  78. package/yida-skills/skills/yida-page-config/SKILL.md +1 -1
  79. package/yida-skills/skills/yida-page-uiux/SKILL.md +14 -10
  80. package/yida-skills/skills/yida-page-uiux/references/app/blueprint.md +1 -1
  81. package/yida-skills/skills/yida-page-uiux/references/app/navigation-patterns.md +4 -2
  82. package/yida-skills/skills/yida-page-uiux/references/dashboard/theme-recipes.md +3 -2
  83. package/yida-skills/skills/yida-page-uiux/references/landing/realistic-brand-homepage.md +2 -2
  84. package/yida-skills/skills/yida-page-uiux/references/scenes/screen.md +3 -2
  85. package/yida-skills/skills/yida-page-uiux/references/scenes/workbench.md +3 -0
  86. package/yida-skills/skills/yida-page-uiux/references/visual-decision-engine.md +6 -6
  87. package/yida-skills/skills/yida-page-uiux/references/workbench/entry-patterns.md +2 -0
  88. package/yida-skills/skills/yida-page-uiux/workflow/output-decision-block.md +13 -2
  89. package/yida-skills/skills/yida-page-uiux/workflow/step-0-nav-shape.md +16 -12
  90. package/yida-skills/skills/yida-page-uiux/workflow/step-1-page-type.md +2 -2
  91. package/yida-skills/skills/yida-page-uiux/workflow/step-3-scene-routing.md +2 -2
  92. package/yida-skills/skills/yida-page-uiux/workflow/step-4-visual-decision.md +16 -4
  93. package/yida-skills/skills/yida-page-uiux/workflow/step-6-deai-selfcheck.md +1 -1
  94. package/yida-skills/skills/yida-ppt-slider/references/dark-tech-theme.md +2 -0
  95. package/yida-skills/skills/yida-publish-page/SKILL.md +3 -2
  96. package/yida-skills/skills/yida-report/SKILL.md +2 -2
  97. package/yida-skills/skills/yida-report/references/report-api-guide.md +3 -3
  98. package/yida-skills/skills/yida-skill-evaluator/SKILL.md +2 -2
  99. package/yida-skills/skills/yida-table-form/SKILL.md +1 -1
  100. package/yida-skills/skills/yida-theme/SKILL.md +36 -42
  101. package/yida-skills/skills/yida-theme/references/theme-token-presets.md +135 -0
  102. package/yida-skills/skills-index.json +13 -8
@@ -101,7 +101,80 @@ const TEMPLATES = {
101
101
  },
102
102
  };
103
103
 
104
+ const SAMPLE_RESIDUE_TOKENS = {
105
+ 'official-homepage': [
106
+ 'Luma Roast',
107
+ 'Luma',
108
+ 'coffee',
109
+ 'Coffee',
110
+ '豆单',
111
+ '烘焙',
112
+ '冷萃',
113
+ '手冲',
114
+ '吧台',
115
+ '杯测',
116
+ '生豆',
117
+ '门店',
118
+ '订阅',
119
+ 'Shanghai',
120
+ 'Seasonal roast',
121
+ 'Visit Luma',
122
+ 'oy-coffee',
123
+ 'COFFEE_',
124
+ ],
125
+ 'product-homepage': [
126
+ '演示预约正在同步到客户表单',
127
+ '线索表单、产品演示和客户回访',
128
+ ],
129
+ 'workbench-home': [
130
+ '清理高优先级待办',
131
+ '核对客户回访结果',
132
+ '同步异常与负责人',
133
+ ],
134
+ 'business-list': [
135
+ 'SO-240716',
136
+ 'PRJ-240716',
137
+ 'CL-240717',
138
+ ],
139
+ 'detail-profile': [
140
+ '采购负责人李青',
141
+ '苏州仓库存',
142
+ ],
143
+ 'portal-shell-home': [
144
+ 'Red Portal Shell',
145
+ ],
146
+ };
147
+
148
+ function normalizeSearchText(value) {
149
+ return String(value || '').toLowerCase();
150
+ }
151
+
152
+ function collectTextDeep(value, output) {
153
+ if (value === undefined || value === null) {
154
+ return output;
155
+ }
156
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
157
+ output.push(String(value));
158
+ return output;
159
+ }
160
+ if (Array.isArray(value)) {
161
+ value.forEach((item) => collectTextDeep(item, output));
162
+ return output;
163
+ }
164
+ if (typeof value === 'object') {
165
+ Object.keys(value).forEach((key) => collectTextDeep(value[key], output));
166
+ }
167
+ return output;
168
+ }
169
+
104
170
  function collectSpecText(spec) {
171
+ if (!spec || typeof spec !== 'object') {
172
+ return '';
173
+ }
174
+ return collectTextDeep(spec, []).join(' ');
175
+ }
176
+
177
+ function collectRoutingText(spec) {
105
178
  if (!spec || typeof spec !== 'object') {
106
179
  return '';
107
180
  }
@@ -130,6 +203,43 @@ function collectSpecText(spec) {
130
203
  .join(' ');
131
204
  }
132
205
 
206
+ function stripFallbackSource(source) {
207
+ return String(source || '')
208
+ .replace(/'data:image\/[^']*'/g, "''")
209
+ .replace(/"data:image\/[^"]*"/g, '""')
210
+ .replace(/const\s+[A-Z0-9_]*IMAGE\s*=\s*['"][\s\S]*?['"];\n/g, '')
211
+ .replace(/applyPageFallbacks\(\{[\s\S]*?\n\}\);\n/g, '')
212
+ .replace(/const\s+DEFAULT_[A-Z0-9_]+\s*=\s*[\s\S]*?;\n/g, '');
213
+ }
214
+
215
+ function findSampleResidues(source, templateName, spec) {
216
+ const tokens = SAMPLE_RESIDUE_TOKENS[templateName] || [];
217
+ if (!tokens.length) {
218
+ return [];
219
+ }
220
+ const searchableSource = normalizeSearchText(stripFallbackSource(source));
221
+ const specText = normalizeSearchText(collectSpecText(spec || {}));
222
+ const residues = tokens.filter((token) => {
223
+ const normalizedToken = normalizeSearchText(token);
224
+ return searchableSource.includes(normalizedToken) && !specText.includes(normalizedToken);
225
+ });
226
+ return Array.from(new Set(residues));
227
+ }
228
+
229
+ function assertNoSampleResidueInDomainReadyOutput(source, templateName, spec, ir) {
230
+ const fidelity = ir && ir.domainFidelity;
231
+ if (!fidelity || fidelity.status !== 'domain-ready') {
232
+ return;
233
+ }
234
+ const residues = findSampleResidues(source, templateName, spec);
235
+ if (!residues.length) {
236
+ return;
237
+ }
238
+ fidelity.status = 'draft-needs-domain-spec';
239
+ fidelity.sampleFallbacks = Array.from(new Set([...(fidelity.sampleFallbacks || []), ...residues.map((item) => `source:${item}`)]));
240
+ throwCommandError(`生成页仍残留 sample 内容,已停止写入/发布:${residues.join(', ')}。请把这些文案、行业词、配图说明或样式命名替换为当前业务自己的内容。`);
241
+ }
242
+
133
243
  function inferTemplateName(options, spec) {
134
244
  if (options.template) {
135
245
  return options.template;
@@ -138,7 +248,7 @@ function inferTemplateName(options, spec) {
138
248
  return spec.template;
139
249
  }
140
250
 
141
- const text = collectSpecText(spec);
251
+ const text = collectRoutingText(spec);
142
252
  if (/数据大屏|大屏|实时监控|预警系统|态势|指挥舱|监测预警|screen/i.test(text)) {
143
253
  return 'data-screen';
144
254
  }
@@ -484,7 +594,7 @@ function reportNavigationConfig(ir) {
484
594
  if (!blueprint || blueprint.renderNav !== false) {
485
595
  return;
486
596
  }
487
- hint('检测到页面内应用导航或沉浸式页面,发布后应设置 isRenderNav=false:openyida update-form-config <appType> <formUuid> false "<页面标题>"');
597
+ hint('检测到显式隐藏平台导航,发布后应设置 isRenderNav=false:openyida update-form-config <appType> <formUuid> false "<页面标题>"');
488
598
  }
489
599
 
490
600
  function ensureOutputDir(outputPath) {
@@ -604,6 +714,7 @@ async function run(args) {
604
714
  artifact: outputPath,
605
715
  code: 'OPENYIDA_GENERATED_PAGE_EMOJI_FORBIDDEN',
606
716
  });
717
+ assertNoSampleResidueInDomainReadyOutput(outputSource, templateName, spec, ir);
607
718
 
608
719
  ensureOutputDir(outputPath);
609
720
  fs.writeFileSync(outputPath, outputSource, 'utf-8');
@@ -636,6 +747,10 @@ async function run(args) {
636
747
  ['Archetype', ir.archetype],
637
748
  ['Visual profile', ir.visualProfile && ir.visualProfile.name],
638
749
  ['Theme profile', ir.themeProfile && ir.themeProfile.name],
750
+ ['Theme source', ir.themeProfile && ir.themeProfile.themeColorSource],
751
+ ['Theme color', ir.themeProfile && ir.themeProfile.themeColor],
752
+ ['Nav theme', ir.themeProfile && (ir.themeProfile.navTheme || 'light')],
753
+ ['Theme color mode', ir.themeProfile && ir.themeProfile.colorMode],
639
754
  ['Theme scope', ir.themeScope],
640
755
  ['Yida nav', ir.appBlueprint && ir.appBlueprint.renderNav === false ? 'hide (isRenderNav=false)' : 'render'],
641
756
  ['Imported modules', importedModules.join(', ')],
@@ -676,6 +791,10 @@ async function run(args) {
676
791
  ['Archetype', ir.archetype],
677
792
  ['Visual profile', ir.visualProfile && ir.visualProfile.name],
678
793
  ['Theme profile', ir.themeProfile && ir.themeProfile.name],
794
+ ['Theme source', ir.themeProfile && ir.themeProfile.themeColorSource],
795
+ ['Theme color', ir.themeProfile && ir.themeProfile.themeColor],
796
+ ['Nav theme', ir.themeProfile && (ir.themeProfile.navTheme || 'light')],
797
+ ['Theme color mode', ir.themeProfile && ir.themeProfile.colorMode],
679
798
  ['Theme scope', ir.themeScope],
680
799
  ['Yida nav', ir.appBlueprint && ir.appBlueprint.renderNav === false ? 'hide (isRenderNav=false)' : 'render'],
681
800
  ['Blocks', ir.blocks.map((block) => block.type).join(', ')],
@@ -695,4 +814,6 @@ module.exports = {
695
814
  normalizeOutputPathForProjectCwd,
696
815
  inferMode,
697
816
  inferTemplateName,
817
+ findSampleResidues,
818
+ assertNoSampleResidueInDomainReadyOutput,
698
819
  };
@@ -347,6 +347,81 @@ function reorderRootNodes(list, values) {
347
347
  };
348
348
  }
349
349
 
350
+ function normalizeComparableText(value) {
351
+ return String(value || '').trim().toLowerCase();
352
+ }
353
+
354
+ function getNavigationPriority(node) {
355
+ const name = normalizeComparableText(getNodeName(node));
356
+ const formType = normalizeComparableText(node.formType);
357
+ const navType = normalizeComparableText(node.navType);
358
+
359
+ if (navType === 'system') {
360
+ return 99;
361
+ }
362
+
363
+ if (/门户|首页|主页|工作台|入口|总览|概览|home|homepage|portal|workspace|overview/.test(name)) {
364
+ return 0;
365
+ }
366
+
367
+ if (formType === 'display') {
368
+ return 1;
369
+ }
370
+
371
+ if (formType === 'process') {
372
+ return 2;
373
+ }
374
+
375
+ if (['receipt', 'form'].includes(formType)) {
376
+ return 3;
377
+ }
378
+
379
+ if (navType === 'nav') {
380
+ return 4;
381
+ }
382
+
383
+ return 5;
384
+ }
385
+
386
+ function compareNavigationNodes(a, b) {
387
+ const priorityDiff = getNavigationPriority(a) - getNavigationPriority(b);
388
+ if (priorityDiff !== 0) {
389
+ return priorityDiff;
390
+ }
391
+
392
+ const orderA = Number.isFinite(Number(a.listOrder)) ? Number(a.listOrder) : Number.MAX_SAFE_INTEGER;
393
+ const orderB = Number.isFinite(Number(b.listOrder)) ? Number(b.listOrder) : Number.MAX_SAFE_INTEGER;
394
+ if (orderA !== orderB) {
395
+ return orderA - orderB;
396
+ }
397
+
398
+ return String(a.id || a.navUuid || '').localeCompare(String(b.id || b.navUuid || ''));
399
+ }
400
+
401
+ function autoOrderRootNodes(list) {
402
+ const { roots } = buildMutableForest(list);
403
+ const systemRoots = roots.filter((node) => node.navType === 'SYSTEM');
404
+ const sortableRoots = roots
405
+ .filter((node) => node.navType !== 'SYSTEM')
406
+ .slice()
407
+ .sort(compareNavigationNodes);
408
+
409
+ const orderedRoots = systemRoots.concat(sortableRoots);
410
+
411
+ return {
412
+ roots: orderedRoots,
413
+ ids: flattenTreeIds(orderedRoots),
414
+ orderedNavUuids: sortableRoots.map((node) => node.navUuid),
415
+ orderedNodes: sortableRoots.map((node) => ({
416
+ navUuid: node.navUuid,
417
+ formUuid: node.formUuid || null,
418
+ name: getNodeName(node),
419
+ formType: node.formType || '',
420
+ priority: getNavigationPriority(node),
421
+ })),
422
+ };
423
+ }
424
+
350
425
  function countChildren(list, navUuid) {
351
426
  return (list || []).filter((node) => node.parentNavUuid === navUuid).length;
352
427
  }
@@ -595,6 +670,41 @@ async function orderRootNodes(appType, values, authRef) {
595
670
  };
596
671
  }
597
672
 
673
+ async function autoOrderNavigation(appType, authRef) {
674
+ const list = await fetchNavigationList(appType, authRef);
675
+ const ordered = autoOrderRootNodes(list);
676
+ if (!ordered.orderedNavUuids.length) {
677
+ return {
678
+ success: true,
679
+ appType,
680
+ action: 'auto-order',
681
+ targetParentNavUuid: ROOT_NAV_UUID,
682
+ orderedNavUuids: [],
683
+ orderedNodes: [],
684
+ skipped: true,
685
+ };
686
+ }
687
+
688
+ const firstNode = resolveNode(list, ordered.orderedNavUuids[0], { excludeSystem: true });
689
+ await postNavAction(appType, 'updateOrderNew', {
690
+ currentId: firstNode.id,
691
+ parentNavUuid: ROOT_NAV_UUID,
692
+ navType: firstNode.navType,
693
+ ids: ordered.ids.join(','),
694
+ }, authRef);
695
+
696
+ const updatedList = await fetchNavigationList(appType, authRef);
697
+ const updatedOrdered = autoOrderRootNodes(updatedList);
698
+ return {
699
+ success: true,
700
+ appType,
701
+ action: 'auto-order',
702
+ targetParentNavUuid: ROOT_NAV_UUID,
703
+ orderedNavUuids: updatedOrdered.orderedNavUuids,
704
+ orderedNodes: updatedOrdered.orderedNodes,
705
+ };
706
+ }
707
+
598
708
  async function setVisibility(appType, value, hidden, authRef) {
599
709
  if (!value) {
600
710
  throw new Error('Usage: openyida nav-group <hide|show> <appType> <navUuid|formUuid|name>');
@@ -630,6 +740,7 @@ Usage:
630
740
  openyida nav-group delete <appType> <groupNavUuid|groupName> [--force]
631
741
  openyida nav-group move <appType> <navUuid|formUuid|name> --to <groupNavUuid|groupName|root> [--before <node>] [--after <node>]
632
742
  openyida nav-group order <appType> <navUuid|formUuid|name> [more items...]
743
+ openyida nav-group auto-order <appType>
633
744
  openyida nav-group hide <appType> <navUuid|formUuid|name>
634
745
  openyida nav-group show <appType> <navUuid|formUuid|name>
635
746
  `);
@@ -664,6 +775,8 @@ async function run(args) {
664
775
  result = await moveNode(appType, positional[1], flags, authRef);
665
776
  } else if (subCommand === 'order' || subCommand === 'reorder') {
666
777
  result = await orderRootNodes(appType, positional.slice(1), authRef);
778
+ } else if (subCommand === 'auto-order' || subCommand === 'autoorder' || subCommand === 'sort') {
779
+ result = await autoOrderNavigation(appType, authRef);
667
780
  } else if (subCommand === 'hide') {
668
781
  result = await setVisibility(appType, positional[1], true, authRef);
669
782
  } else if (subCommand === 'show') {
@@ -677,10 +790,14 @@ async function run(args) {
677
790
 
678
791
  module.exports = {
679
792
  ROOT_NAV_UUID,
793
+ autoOrderNavigation,
794
+ autoOrderRootNodes,
680
795
  buildI18nTitle,
681
796
  buildNavigationTree,
797
+ compareNavigationNodes,
682
798
  flattenTreeIds,
683
799
  flattenTreeNodes,
800
+ getNavigationPriority,
684
801
  moveNodeInTree,
685
802
  normalizeNavNode,
686
803
  orderRootNodes,
@@ -1,5 +1,11 @@
1
1
  'use strict';
2
2
 
3
+ const {
4
+ APP_THEME_TOKEN_PRESETS,
5
+ APP_THEME_TOKEN_PRESET_KEYS,
6
+ isPresetThemeKey,
7
+ } = require('./theme-presets');
8
+
3
9
  const IR_VERSION = '1.0';
4
10
 
5
11
  const PRODUCT_HOMEPAGE_TEMPLATE = 'product-homepage';
@@ -16,6 +22,7 @@ const TODO_MVC_TEMPLATE = 'todo-mvc';
16
22
  const PAGE_SCENES = ['workbench', 'dashboard', 'list', 'detail', 'landing', 'screen'];
17
23
  const THEME_SCOPES = ['page', 'app'];
18
24
  const RESEARCH_LEVELS = ['none', 'light', 'enhanced', 'deep'];
25
+ const DEFAULT_THEME_PROFILE_KEY = 'podBlue';
19
26
 
20
27
  const CANVAS_CONTROL_RESET_CSS = `
21
28
  :where(.oy-page, .oy-workbench-home, .oy-dashboard-overview, .oy-business-list, .oy-data-management, .oy-detail-profile, .oy-split-pane-detail, .oy-portal-shell-home, .oy-official, .oy-screen, .oy-todo-os, .oy-native-portal, .oy-smoke-page) {
@@ -128,30 +135,19 @@ const DEFAULT_VISUAL_PROFILES = {
128
135
  },
129
136
  screen: {
130
137
  name: 'yida-app-theme',
131
- tone: 'immersive-command',
138
+ tone: 'light-command',
132
139
  density: 'screen-dense',
133
- neutral: 'dark-blue',
140
+ neutral: 'light-blue-gray',
134
141
  corner: 'layered',
135
- accent: 'glow-brand',
142
+ accent: 'brand-signal',
136
143
  typography: 'tabular-numeric',
137
144
  motif: ['command-map', 'glow-kpi', 'radar-panel', 'rank-feed'],
138
145
  },
139
146
  };
140
147
 
148
+ // Application theme token profiles work as the default page-level theme suggestions.
149
+ // Only platform-supported theme keys are mirrored into appThemeKey.
141
150
  const DEFAULT_THEME_PROFILES = {
142
- 'yida-pod-business': {
143
- name: 'yida-app-theme',
144
- followRuntimeTheme: true,
145
- themeColorSource: 'runtime-css-vars',
146
- themeColor: '#6B7CAB',
147
- themeColorDeep: '#435480',
148
- themeColorSoft: '#F3F5FB',
149
- themeColorTint: 'rgba(107, 124, 171, 0.2)',
150
- navTheme: 'light',
151
- mode: 'color_color',
152
- colorMode: 'gradient',
153
- mobileNavStyle: 'top',
154
- },
155
151
  'yida-app-theme': {
156
152
  name: 'yida-app-theme',
157
153
  followRuntimeTheme: true,
@@ -167,18 +163,36 @@ const DEFAULT_THEME_PROFILES = {
167
163
  },
168
164
  };
169
165
 
170
- const VISUAL_NEUTRAL_ALIASES = {
171
- 'pod-blue-gray': 'yida-blue-gray',
172
- };
166
+ function registerTokenThemeProfiles(presetKeys, tokenPresets, options = {}) {
167
+ presetKeys.forEach((presetName) => {
168
+ const tokens = tokenPresets[presetName];
169
+ DEFAULT_THEME_PROFILES[presetName] = {
170
+ name: presetName,
171
+ appThemeKey: options.appThemeKeyFor ? options.appThemeKeyFor(presetName) : (options.appThemeKey === false ? '' : presetName),
172
+ followRuntimeTheme: false,
173
+ themeColorSource: options.themeColorSource || 'theme-token-preset',
174
+ themeColor: tokens['--color-brand1-6'],
175
+ themeColorDeep: tokens['--color-brand1-9'],
176
+ themeColorSoft: tokens['--color-brand1-2'],
177
+ themeColorTint: tokens['--color-brand1-3'],
178
+ navTheme: 'light',
179
+ mode: 'color_color',
180
+ colorMode: 'gradient',
181
+ mobileNavStyle: 'top',
182
+ };
183
+ });
184
+ }
173
185
 
174
- const VISUAL_CORNER_ALIASES = {
175
- 'pod-layered': 'layered',
176
- };
186
+ registerTokenThemeProfiles(APP_THEME_TOKEN_PRESET_KEYS, APP_THEME_TOKEN_PRESETS, {
187
+ themeColorSource: 'application-theme',
188
+ appThemeKeyFor: (presetName) => (isPresetThemeKey(presetName) ? presetName : ''),
189
+ });
177
190
 
178
- const VISUAL_MOTIF_ALIASES = {
179
- 'pod-shell': 'app-shell',
180
- 'pod-panel': 'app-panel',
181
- };
191
+ const VISUAL_NEUTRAL_ALIASES = {};
192
+
193
+ const VISUAL_CORNER_ALIASES = {};
194
+
195
+ const VISUAL_MOTIF_ALIASES = {};
182
196
 
183
197
  const DEFAULT_PRODUCT_HOMEPAGE = {
184
198
  meta: {
@@ -707,7 +721,7 @@ function getThemeProfileInput(spec) {
707
721
  }
708
722
 
709
723
  function normalizeThemeProfile(rawProfile) {
710
- const defaults = DEFAULT_THEME_PROFILES['yida-app-theme'];
724
+ const defaults = DEFAULT_THEME_PROFILES[DEFAULT_THEME_PROFILE_KEY];
711
725
  const profile = typeof rawProfile === 'string'
712
726
  ? { name: rawProfile }
713
727
  : isPlainObject(rawProfile)
@@ -716,7 +730,8 @@ function normalizeThemeProfile(rawProfile) {
716
730
 
717
731
  const name = valueOrDefault(profile.name || profile.profile, defaults.name);
718
732
  const preset = DEFAULT_THEME_PROFILES[name] || defaults;
719
- const normalizedName = DEFAULT_THEME_PROFILES[name] ? preset.name : name;
733
+ const isKnownProfile = Boolean(DEFAULT_THEME_PROFILES[name]);
734
+ const normalizedName = isKnownProfile ? preset.name : name;
720
735
  const hasExplicit = (...keys) => keys.some((key) => (
721
736
  Object.prototype.hasOwnProperty.call(profile, key)
722
737
  && profile[key] !== undefined
@@ -732,10 +747,15 @@ function normalizeThemeProfile(rawProfile) {
732
747
  ? Boolean(preset.followRuntimeTheme && !hasExplicitColorFamily)
733
748
  : profile.followRuntimeTheme !== false;
734
749
 
735
- return {
750
+ const normalized = {
736
751
  name: normalizedName,
737
752
  followRuntimeTheme,
738
- themeColorSource: followRuntimeTheme ? 'runtime-css-vars' : 'profile',
753
+ themeColorSource: followRuntimeTheme
754
+ ? 'runtime-css-vars'
755
+ : valueOrDefault(
756
+ profile.themeColorSource || profile.colorSource,
757
+ hasExplicitColorFamily ? 'profile' : (preset.themeColorSource || 'profile')
758
+ ),
739
759
  themeColor: valueOrDefault(profile.themeColor || profile.color || profile.brandColor, preset.themeColor),
740
760
  themeColorDeep: valueOrDefault(profile.themeColorDeep || profile.brandDeep, preset.themeColorDeep),
741
761
  themeColorSoft: valueOrDefault(profile.themeColorSoft || profile.brandSoft, preset.themeColorSoft),
@@ -745,6 +765,13 @@ function normalizeThemeProfile(rawProfile) {
745
765
  colorMode: valueOrDefault(profile.colorMode, preset.colorMode),
746
766
  mobileNavStyle: valueOrDefault(profile.mobileNavStyle, preset.mobileNavStyle),
747
767
  };
768
+
769
+ const appThemeKey = valueOrDefault(profile.appThemeKey || profile.themeKey, isKnownProfile ? preset.appThemeKey : '');
770
+ if (appThemeKey) {
771
+ normalized.appThemeKey = appThemeKey;
772
+ }
773
+
774
+ return normalized;
748
775
  }
749
776
 
750
777
  function getThemeScopeInput(spec) {
@@ -920,10 +947,10 @@ function inferPageLevelNavigation(templateName, source) {
920
947
  if (explicit !== null) {
921
948
  return explicit;
922
949
  }
923
- return ['portal-shell-home', 'workbench-home'].includes(templateName);
950
+ return false;
924
951
  }
925
952
 
926
- function inferHideAppNav(source, scene, templateName, hasPageNavigation) {
953
+ function inferHideAppNav(source) {
927
954
  const explicitHide = normalizeOptionalBoolean(
928
955
  firstDefined(
929
956
  source.hideAppNav,
@@ -948,10 +975,7 @@ function inferHideAppNav(source, scene, templateName, hasPageNavigation) {
948
975
  return !explicitRender;
949
976
  }
950
977
 
951
- if (scene === 'screen' || templateName === 'data-screen') {
952
- return true;
953
- }
954
- return hasPageNavigation;
978
+ return false;
955
979
  }
956
980
 
957
981
  function normalizeAppBlueprint(spec, scene, templateName, meta) {
@@ -966,7 +990,7 @@ function normalizeAppBlueprint(spec, scene, templateName, meta) {
966
990
  const shell = valueOrDefault(source.shell || source.shellType || spec.shell || spec.shellType, scene === 'screen' ? 'no_shell' : 'single_page');
967
991
  const navigation = normalizeStringList(source.navigation || source.navGroups || spec.navigation || spec.navGroups);
968
992
  const hasPageNavigation = inferPageLevelNavigation(templateName, source);
969
- const hideAppNav = inferHideAppNav(source, scene, templateName, hasPageNavigation);
993
+ const hideAppNav = inferHideAppNav(source);
970
994
  const renderNav = !hideAppNav;
971
995
  const defaultPage = {
972
996
  name: meta && (meta.brandName || meta.title) ? (meta.brandName || meta.title) : templateName,
@@ -988,7 +1012,7 @@ function normalizeAppBlueprint(spec, scene, templateName, meta) {
988
1012
  navConfig: {
989
1013
  isRenderNav: renderNav,
990
1014
  reason: hideAppNav
991
- ? 'page-level navigation or immersive page should own the viewport'
1015
+ ? 'explicit hide navigation request'
992
1016
  : 'use Yida application navigation',
993
1017
  },
994
1018
  };
@@ -1159,6 +1183,8 @@ function normalizeMediaAssets(spec, defaults) {
1159
1183
  return {
1160
1184
  heroImage: valueOrDefault(source.heroImage || source.hero || spec.heroImage || spec.hero_image, fallback.heroImage || ''),
1161
1185
  heroImageAlt: valueOrDefault(source.heroImageAlt || source.heroAlt || spec.heroImageAlt || spec.hero_image_alt, fallback.heroImageAlt || ''),
1186
+ storyImage: valueOrDefault(source.storyImage || source.story || spec.storyImage || spec.story_image, fallback.storyImage || ''),
1187
+ storyImageAlt: valueOrDefault(source.storyImageAlt || source.storyAlt || spec.storyImageAlt || spec.story_image_alt, fallback.storyImageAlt || ''),
1162
1188
  productImages: productImages.length ? productImages : clone(fallback.productImages || []),
1163
1189
  materialStrategy: valueOrDefault(source.materialStrategy || spec.materialStrategy, fallback.materialStrategy || 'visual-anchor-required'),
1164
1190
  // 素材完成度:final(可交付最终版) / draft(仅草稿) / none(无素材) / unknown(尚未预检)
@@ -97,8 +97,9 @@ function parseArgs(argv = process.argv.slice(2)) {
97
97
  const compat = args.includes('--compat') || args.includes('--modern');
98
98
  const force = args.includes('--force');
99
99
  const canvas = args.includes('--canvas');
100
+ const autoNavOrder = args.includes('--auto-nav-order');
100
101
  const help = args.includes('--help') || args.includes('-h');
101
- const filteredArgs = args.filter(arg => arg !== '--skip-lint' && arg !== '--health-check' && arg !== '--check' && arg !== '--compat' && arg !== '--modern' && arg !== '--force' && arg !== '--canvas' && arg !== '--json' && arg !== '--help' && arg !== '-h');
102
+ const filteredArgs = args.filter(arg => arg !== '--skip-lint' && arg !== '--health-check' && arg !== '--check' && arg !== '--compat' && arg !== '--modern' && arg !== '--force' && arg !== '--canvas' && arg !== '--auto-nav-order' && arg !== '--json' && arg !== '--help' && arg !== '-h');
102
103
 
103
104
  if (help) {
104
105
  usage(t('publish.usage'), t('publish.example'));
@@ -119,6 +120,7 @@ function parseArgs(argv = process.argv.slice(2)) {
119
120
  compat,
120
121
  force,
121
122
  canvas,
123
+ autoNavOrder,
122
124
  browserOpenMode: openOption.mode,
123
125
  };
124
126
  }
@@ -735,7 +737,7 @@ async function runMain(argv) {
735
737
  if (options.help) {
736
738
  return options;
737
739
  }
738
- const { appType, formUuid, sourceFile, skipLint, healthCheck, compat, force, canvas: canvasFlag, browserOpenMode } = options;
740
+ const { appType, formUuid, sourceFile, skipLint, healthCheck, compat, force, canvas: canvasFlag, autoNavOrder, browserOpenMode } = options;
739
741
 
740
742
  let sourcePath = path.resolve(sourceFile);
741
743
  if (!fs.existsSync(sourcePath)) {
@@ -876,6 +878,8 @@ async function runMain(argv) {
876
878
 
877
879
  const pageUrl = `${baseUrl}/${appType}/workbench/${savedFormUuid}`;
878
880
  let healthCheckResult = null;
881
+ let navOrderResult = null;
882
+ let navOrderWarning = null;
879
883
  if (healthCheck) {
880
884
  step(5, t('publish.step_health_check'));
881
885
  try {
@@ -894,6 +898,23 @@ async function runMain(argv) {
894
898
  }
895
899
  }
896
900
 
901
+ if (autoNavOrder) {
902
+ step(healthCheck ? 6 : 5, t('publish.step_nav_order'));
903
+ try {
904
+ const { autoOrderNavigation } = require('./nav-group');
905
+ navOrderResult = await autoOrderNavigation(appType, authRef);
906
+ const orderedNames = (navOrderResult.orderedNodes || [])
907
+ .slice(0, 6)
908
+ .map((node) => node.name || node.formUuid || node.navUuid)
909
+ .filter(Boolean)
910
+ .join(' > ');
911
+ success(t('publish.nav_order_ok', orderedNames || '-'));
912
+ } catch (navOrderError) {
913
+ navOrderWarning = navOrderError && navOrderError.message ? navOrderError.message : t('common.unknown_error');
914
+ warn(t('publish.nav_order_failed', navOrderWarning));
915
+ }
916
+ }
917
+
897
918
  if (configResponse && configResponse.success) {
898
919
  result(true, t('publish.success'), [
899
920
  ['Form UUID', savedFormUuid],
@@ -901,7 +922,7 @@ async function runMain(argv) {
901
922
  ['URL', pageUrl],
902
923
  ]);
903
924
  console.log(JSON.stringify(withBrowserHandoff(
904
- { success: true, appType, formUuid: savedFormUuid, version, url: pageUrl, healthCheck: healthCheckResult },
925
+ { success: true, appType, formUuid: savedFormUuid, version, url: pageUrl, healthCheck: healthCheckResult, navOrder: navOrderResult, navOrderWarning },
905
926
  pageUrl,
906
927
  { stage: 'publish_page_success', title: savedFormUuid },
907
928
  browserOpenMode
@@ -915,7 +936,7 @@ async function runMain(argv) {
915
936
  ]);
916
937
  hint(t('publish.schema_ok_config_failed'));
917
938
  console.log(JSON.stringify(withBrowserHandoff(
918
- { success: true, appType, formUuid: savedFormUuid, version, url: pageUrl, configWarning: configErrorMsg, healthCheck: healthCheckResult },
939
+ { success: true, appType, formUuid: savedFormUuid, version, url: pageUrl, configWarning: configErrorMsg, healthCheck: healthCheckResult, navOrder: navOrderResult, navOrderWarning },
919
940
  pageUrl,
920
941
  { stage: 'publish_page_success', title: savedFormUuid },
921
942
  browserOpenMode