openyida 2026.9.15 → 2026.9.16

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 (113) hide show
  1. package/README.md +8 -3
  2. package/lib/app/canvas-compile.js +2 -0
  3. package/lib/app/canvas-theme-guard.js +117 -0
  4. package/lib/app/create-form.js +4 -4
  5. package/lib/app/services/form-compiler.js +4 -4
  6. package/lib/app/theme-from-design.js +8 -2
  7. package/lib/app/theme-presets.js +1 -1
  8. package/lib/asset/ai-image.js +40 -6
  9. package/lib/asset/asset-cmd.js +8 -2
  10. package/lib/asset/asset-plan.js +46 -4
  11. package/lib/asset/asset-resolve.js +7 -3
  12. package/lib/core/agent-capabilities.js +10 -1
  13. package/lib/core/command-manifest.js +113 -16
  14. package/lib/core/locales/en.js +10 -2
  15. package/lib/core/locales/zh.js +10 -2
  16. package/lib/core/redact.js +15 -10
  17. package/lib/core/sample.js +6 -10
  18. package/lib/design-plan/design-plan.js +7 -2
  19. package/lib/design-plan/entry-navigation.js +83 -0
  20. package/lib/design-plan/init.js +73 -10
  21. package/lib/design-plan/materialize.js +103 -67
  22. package/lib/design-plan/normalize.js +23 -1
  23. package/lib/design-plan/patch.js +24 -4
  24. package/lib/design-plan/validate.js +30 -5
  25. package/lib/samples/openyida-scaffold/canvas-form-drawer.canvas.jsx +14 -85
  26. package/lib/samples/openyida-scaffold/canvas-nav/data.jsx +84 -17
  27. package/lib/samples/yida-canvas-table-form/table-form-batch-submit.canvas.jsx +9 -83
  28. package/lib/samples/yida-rechart/trend-combo.canvas.jsx +14 -87
  29. package/package.json +2 -1
  30. package/scripts/postinstall.js +3 -3
  31. package/yida-skills/SKILL.md +3 -3
  32. package/yida-skills/skills/yida-app/SKILL.md +9 -9
  33. package/yida-skills/skills/yida-app/references/entry-navigation.md +150 -0
  34. package/yida-skills/skills/yida-app/workflow/parallel-work.md +10 -1
  35. package/yida-skills/skills/yida-app/workflow/plan/step-1-understand.md +1 -1
  36. package/yida-skills/skills/yida-app/workflow/plan/step-2-confirm.md +1 -1
  37. package/yida-skills/skills/yida-app/workflow/plan/step-4-deliver.md +4 -4
  38. package/yida-skills/skills/yida-app/workflow/plan/workflow.md +6 -6
  39. package/yida-skills/skills/yida-app/workflow/step-1-resource-context.md +4 -2
  40. package/yida-skills/skills/yida-app/workflow/step-2-design.md +15 -5
  41. package/yida-skills/skills/yida-app/workflow/step-3-create-or-reuse-app.md +4 -2
  42. package/yida-skills/skills/yida-app/workflow/step-4-forms-processes.md +9 -1
  43. package/yida-skills/skills/yida-app/workflow/step-6-main-page.md +6 -4
  44. package/yida-skills/skills/yida-app/workflow/step-7-page-code.md +6 -4
  45. package/yida-skills/skills/yida-app/workflow/step-8-publish-navigation.md +13 -4
  46. package/yida-skills/skills/yida-app/workflow/step-9-output-finish.md +18 -12
  47. package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +32 -42
  48. package/yida-skills/skills/yida-canvas-custom-page/references/canvas-style-implementation-guide.md +5 -5
  49. package/yida-skills/skills/yida-canvas-custom-page/references/canvas-theme-provider.md +45 -50
  50. package/yida-skills/skills/yida-canvas-custom-page/references/navigation-and-entry-guide.md +2 -0
  51. package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +13 -12
  52. package/yida-skills/skills/yida-canvas-custom-page/scripts/build-canvas-theme.js +19 -2
  53. package/yida-skills/skills/yida-create-form-page/SKILL.md +23 -15
  54. package/yida-skills/skills/yida-design/SKILL.md +6 -2
  55. package/yida-skills/skills/yida-design/references/ask-human-interaction-contract.md +43 -25
  56. package/yida-skills/skills/yida-design/references/asset-workflow.md +4 -2
  57. package/yida-skills/skills/yida-design/references/design-mode.md +2 -2
  58. package/yida-skills/skills/yida-design/references/navigation-decision.md +42 -0
  59. package/yida-skills/skills/yida-design/references/theme/app-custom-theme-template.css +2 -2
  60. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/SKILL.md +4 -4
  61. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/build-plan-compact-schema.md +16 -11
  62. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/build-plan-schema.md +24 -150
  63. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/visual-design.md +6 -2
  64. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/visual-theme-selection.md +17 -13
  65. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/scripts/render_build_plan.py +25 -0
  66. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/scripts/validate_design_themes.py +29 -0
  67. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/README.md +13 -1
  68. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/airy-media-grid.md +86 -87
  69. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/airy-modular-clarity.md +84 -102
  70. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/airy-structured-clarity.md +73 -97
  71. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/ambient-halo-layered.md +91 -95
  72. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/basic-tokens.json +101 -0
  73. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/dark-focus-layered.md +82 -110
  74. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/dark-luminous-modular.md +86 -94
  75. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/deep-stage-duotone.md +91 -98
  76. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/hairline-runway-clarity.md +88 -99
  77. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/high-contrast-modular.md +84 -109
  78. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/interlocked-vivid-modules.md +83 -88
  79. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/matrix-grid-focus.md +83 -91
  80. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/media-rail-inspector.md +87 -85
  81. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/mist-layered-signal.md +88 -95
  82. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/modular-rail-signal.md +90 -101
  83. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/mono-grid-signal.md +87 -99
  84. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/ribbon-ledger-lift.md +85 -91
  85. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/segmented-meter-clarity.md +84 -92
  86. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/split-rail-precision.md +85 -87
  87. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/status-framed-media-grid.md +82 -85
  88. package/yida-skills/skills/yida-design/workflow/output-design.md +1 -1
  89. package/yida-skills/skills/yida-design/workflow/step-1-read-brief.md +1 -1
  90. package/yida-skills/skills/yida-image-assets/SKILL.md +48 -51
  91. package/yida-skills/skills/yida-image-assets/references/manifest-contract.md +50 -54
  92. package/yida-skills/skills/yida-image-assets/references/source-policy.md +14 -15
  93. package/yida-skills/skills/yida-nav-group/SKILL.md +4 -2
  94. package/yida-skills/skills/yida-nav-shell/SKILL.md +23 -6
  95. package/yida-skills/skills/yida-nav-shell/references/nav-shell-patterns.md +6 -6
  96. package/yida-skills/skills/yida-page-config/SKILL.md +3 -3
  97. package/yida-skills/skills/yida-prd/SKILL.md +2 -1
  98. package/yida-skills/skills/yida-prd/references/app/blueprint.md +1 -1
  99. package/yida-skills/skills/yida-prd/references/app/navigation-patterns.md +6 -3
  100. package/yida-skills/skills/yida-prd/references/plan-content.md +4 -0
  101. package/yida-skills/skills/yida-prd/workflow/output-prd.md +13 -7
  102. package/yida-skills/skills/yida-prd/workflow/plan-business.md +3 -3
  103. package/yida-skills/skills/yida-prd/workflow/step-2-information-architecture.md +7 -5
  104. package/yida-skills/skills/yida-publish-page/SKILL.md +1 -0
  105. package/yida-skills/skills/yida-requirement-analysis/SKILL.md +10 -39
  106. package/yida-skills/skills/yida-requirement-analysis/references/experience-groups.md +23 -0
  107. package/yida-skills/skills/yida-requirement-analysis/references/handoff.md +64 -0
  108. package/yida-skills/skills/yida-requirement-analysis/workflow/prepare-brief.md +32 -75
  109. package/yida-skills/skills-index.json +6 -6
  110. package/yida-skills/skills/yida-design/references/app/blueprint.md +0 -70
  111. package/yida-skills/skills/yida-design/references/app/navigation-patterns.md +0 -43
  112. package/yida-skills/skills/yida-design/references/app/role-journey.md +0 -30
  113. package/yida-skills/skills/yida-design/workflow/output-prd.md +0 -185
package/README.md CHANGED
@@ -396,11 +396,12 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
396
396
  | `openyida app-list [--type managed\|created] [--page N] [--size N]` | Page through apps I manage or created |
397
397
  | `openyida corp-efficiency [overview\|details\|detail\|groups\|notify] [options] [--open\|--no-open]` | Query enterprise efficiency overview and detail reports |
398
398
  | `openyida create-app "<name>"\|--name <name> [options] [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]` | Create a Yida app |
399
+ | `openyida design-plan catalog [--json]` | List available planning themes and page patterns |
399
400
  | `openyida design-plan init <requirement-brief.json> [--theme-id <id>] [--output-dir <dir>] [--json]` | Initialize a plan draft from confirmed requirements |
400
401
  | `openyida design-plan preview <build-plan.json> --part-file <module.json> [--json]` | Update plan drafts by module |
401
402
  | `openyida design-plan materialize <build-plan.json> [--from-preview \| --business-file <json> --visual-file <json>] [--output-dir <dir>] [--check] [--json]` | Generate and validate design-plan artifacts from build-plan.json |
402
- | `openyida design-plan patch <build-plan.json> --set <path=value> [--set <path=value> ...] [--materialize] [--output-dir <dir>] [--json]` | Patch a design plan by field path and invalidate prior confirmation |
403
- | `openyida update-app <appType> [--name "..."] [--theme-file <css>] [--nav-theme light\|dark\|white\|gray] [--logo-source appIcon\|customImage] [--layout side\|top\|l_shape] [--hide-app-nav\|--show-app-nav]` | Update app info |
403
+ | `openyida design-plan patch <build-plan.json> --set <path=value> [--set <path=value> ...] [--materialize] [--output-dir <dir>] [--json]` | Patch a design plan; asset progress preserves existing approval |
404
+ | `openyida update-app <appType> [--name "..."] [--desc "..."] [--icon <name>] [--icon-color <color>] [--colour <key>] [--theme-color <color>] [--theme-file <css>] [--nav-theme light\|dark\|white\|gray] [--logo-source appIcon\|customImage] [--layout side\|top\|l_shape] [--hide-app-nav\|--show-app-nav]` | Update app info |
404
405
  | `openyida app-online <appType> [--to-ding-app-center] [--show-app-center]` | Enable a Yida app |
405
406
  | `openyida app-offline <appType> [--to-ding-app-center] [--show-app-center]` | Disable a Yida app |
406
407
  | `openyida nav-group <list\|create\|rename\|delete\|move\|order\|auto-order\|hide\|show> <appType> ...` | Manage app sidebar navigation groups |
@@ -649,10 +650,14 @@ npx clawhub@latest install nicky1108/yida-app
649
650
  ```bash
650
651
  git clone https://github.com/openyida/openyida.git
651
652
  cd openyida
652
- npm install
653
+ nvm install
654
+ nvm use
655
+ npm ci --ignore-scripts
653
656
  npm run check:ci
654
657
  ```
655
658
 
659
+ Use the Node.js version in `.nvmrc` to match CI validation. If you do not use nvm, install that version directly.
660
+
656
661
  Useful checks:
657
662
 
658
663
  | Command | Purpose |
@@ -50,6 +50,7 @@ const {
50
50
  } = require('./form-detail-link-guard');
51
51
  const { t } = require('../core/i18n');
52
52
  const { assertInlineCssStructure } = require('./inline-css-guard');
53
+ const { assertCanvasThemeStructure } = require('./canvas-theme-guard');
53
54
 
54
55
  /**
55
56
  * 依赖 → window 别名白名单,用于把 import 改写到运行时全局对象。
@@ -967,6 +968,7 @@ function compileCanvasLocal(source, options = {}) {
967
968
  assertNoBareDependencyGlobals(source, options);
968
969
  assertNoManualDependencyGlobals(source, options);
969
970
  assertInlineCssStructure(source, options);
971
+ assertCanvasThemeStructure(source, options);
970
972
 
971
973
  const importedModules = extractImportedModules(source);
972
974
 
@@ -0,0 +1,117 @@
1
+ 'use strict';
2
+
3
+ const Babel = require('@babel/standalone');
4
+ const { CliError } = require('../core/cli-error');
5
+ const { t } = require('../core/i18n');
6
+
7
+ // Check the maintained theme contract without executing business code.
8
+ // Dynamic component factories and runtime branches still need browser verification.
9
+ function assertCanvasThemeStructure(source, options = {}) {
10
+ if (!/CanvasThemeProvider|useCanvasThemeContext|CanvasThemeContext/.test(source)) { return; }
11
+ let ast;
12
+ try {
13
+ ast = Babel.packages.parser.parse(source, { sourceType: 'module', plugins: ['jsx', 'typescript'] });
14
+ } catch { return; } // Let the compiler report syntax errors.
15
+ const traverse = Babel.packages.traverse.default || Babel.packages.traverse;
16
+ const functions = new Map();
17
+ let program;
18
+ let defaultExport;
19
+ const fail = (kind, node) => {
20
+ const line = node?.loc?.start.line || 1;
21
+ throw new CliError(t(`publish.canvas_theme_${kind}`, line), {
22
+ code: 'OPENYIDA_CANVAS_THEME_PROVIDER_INVALID',
23
+ details: { stage: 'canvas_compile', sourcePath: options.sourcePath || '', line, issueType: kind },
24
+ });
25
+ };
26
+
27
+ traverse(ast, {
28
+ Program(p) { program = p; },
29
+ ExportDefaultDeclaration(p) { defaultExport = p.get('declaration'); },
30
+ Function(p) {
31
+ const name = p.node.id?.name || (p.parentPath.isVariableDeclarator() ? p.parentPath.node.id.name : '');
32
+ functions.set(p.node, { name, calls: [], renders: [], hooks: [] });
33
+ },
34
+ VariableDeclarator(p) {
35
+ if (p.node.id.name === 'CanvasThemeContext' && p.getFunctionParent()) {
36
+ fail('context_scope', p.node);
37
+ }
38
+ },
39
+ });
40
+
41
+ function resolveFunction(p, seen = new Set()) {
42
+ if (!p?.node || seen.has(p.node)) { return null; }
43
+ seen.add(p.node);
44
+ if (p.isFunction()) { return p.node; }
45
+ if (p.isVariableDeclarator()) { return resolveFunction(p.get('init'), seen); }
46
+ if (p.isIdentifier() || p.isJSXIdentifier()) {
47
+ return resolveFunction(p.scope.getBinding(p.node.name)?.path, seen);
48
+ }
49
+ // React.memo/forwardRef retain the wrapped component's theme requirements.
50
+ if (p.isCallExpression()) {
51
+ const callee = p.node.callee;
52
+ const name = callee.type === 'Identifier' ? callee.name : callee.property?.name;
53
+ if (['memo', 'forwardRef'].includes(name)) { return resolveFunction(p.get('arguments.0'), seen); }
54
+ }
55
+ return null;
56
+ }
57
+
58
+ traverse(ast, {
59
+ CallExpression(p) {
60
+ const owner = functions.get(p.getFunctionParent()?.node);
61
+ const target = resolveFunction(p.get('callee'));
62
+ const targetName = functions.get(target)?.name || p.node.callee.name;
63
+ if (targetName === 'useCanvasThemeContext') {
64
+ if (!owner) { fail('root_hook', p.node); }
65
+ owner.hooks.push(p.node);
66
+ }
67
+ if (!owner) { return; }
68
+ if (target) { owner.calls.push(target); }
69
+ const callee = p.node.callee;
70
+ if (callee.type === 'MemberExpression' && callee.object.name === 'React' && callee.property.name === 'createElement') {
71
+ const component = resolveFunction(p.get('arguments.0'));
72
+ if (component) { owner.renders.push(component); }
73
+ }
74
+ },
75
+ JSXOpeningElement(p) {
76
+ const owner = functions.get(p.getFunctionParent()?.node);
77
+ const component = resolveFunction(p.get('name'));
78
+ if (owner && component) { owner.renders.push(component); }
79
+ },
80
+ });
81
+
82
+ const entry = resolveFunction(program.scope.getBinding('YidaComp')?.path) || resolveFunction(defaultExport);
83
+ if (!entry) { return; }
84
+
85
+ // Calling a hook before returning a Provider cannot read that Provider.
86
+ const directSeen = new Set();
87
+ function checkEntryCalls(node) {
88
+ if (directSeen.has(node)) { return; }
89
+ directSeen.add(node);
90
+ const info = functions.get(node);
91
+ if (info.hooks.length) { fail('root_hook', info.hooks[0]); }
92
+ info.calls.forEach(checkEntryCalls);
93
+ }
94
+ checkEntryCalls(entry);
95
+
96
+ const seen = new Set();
97
+ let providerMounted = false;
98
+ let firstHook;
99
+ function visit(node) {
100
+ if (seen.has(node)) { return; }
101
+ seen.add(node);
102
+ const info = functions.get(node);
103
+ firstHook = firstHook || info.hooks[0];
104
+ info.renders.forEach(child => {
105
+ if (functions.get(child).name === 'CanvasThemeProvider') { providerMounted = true; }
106
+ visit(child);
107
+ });
108
+ info.calls.forEach(visit);
109
+ }
110
+ visit(entry);
111
+ const hasProvider = [...functions.values()].some(info => info.name === 'CanvasThemeProvider');
112
+ if (!providerMounted && (hasProvider || firstHook)) {
113
+ fail('provider_missing', firstHook || entry);
114
+ }
115
+ }
116
+
117
+ module.exports = { assertCanvasThemeStructure };
@@ -1719,7 +1719,7 @@ function buildFormSchema(formTitle, fields, formUuid, corpId, appType, layout, t
1719
1719
  id: nextNodeId(),
1720
1720
  props: {
1721
1721
  contentBgColor: 'white',
1722
- pageStyle: { backgroundColor: '#f2f3f5' },
1722
+ pageStyle: { backgroundColor: 'transparent' },
1723
1723
  contentMargin: '20',
1724
1724
  contentPadding: '20',
1725
1725
  showTitle: false,
@@ -1742,7 +1742,7 @@ function buildFormSchema(formTitle, fields, formUuid, corpId, appType, layout, t
1742
1742
  labelWeightMobile: 'normal',
1743
1743
  },
1744
1744
  condition: true,
1745
- css: 'body{background-color:#f2f3f5}',
1745
+ css: 'body{background-color:transparent}',
1746
1746
  methods: {
1747
1747
  __initMethods__: {
1748
1748
  type: 'js',
@@ -1941,7 +1941,7 @@ function buildEmptyFormSchema() {
1941
1941
  id: nextNodeId(),
1942
1942
  props: {
1943
1943
  contentBgColor: 'white',
1944
- pageStyle: { backgroundColor: '#f2f3f5' },
1944
+ pageStyle: { backgroundColor: 'transparent' },
1945
1945
  contentMargin: '20',
1946
1946
  contentPadding: '20',
1947
1947
  showTitle: false,
@@ -1952,7 +1952,7 @@ function buildEmptyFormSchema() {
1952
1952
  contentBgColorMobile: 'white',
1953
1953
  },
1954
1954
  condition: true,
1955
- css: 'body{background-color:#f2f3f5}',
1955
+ css: 'body{background-color:transparent}',
1956
1956
  methods: {
1957
1957
  __initMethods__: {
1958
1958
  type: 'js',
@@ -1615,7 +1615,7 @@ function buildFormSchema(formTitle, fields, formUuid, corpId, appType, layout, t
1615
1615
  id: nextNodeId(),
1616
1616
  props: {
1617
1617
  contentBgColor: 'white',
1618
- pageStyle: { backgroundColor: '#f2f3f5' },
1618
+ pageStyle: { backgroundColor: 'transparent' },
1619
1619
  contentMargin: '20',
1620
1620
  contentPadding: '20',
1621
1621
  showTitle: false,
@@ -1638,7 +1638,7 @@ function buildFormSchema(formTitle, fields, formUuid, corpId, appType, layout, t
1638
1638
  labelWeightMobile: 'normal',
1639
1639
  },
1640
1640
  condition: true,
1641
- css: 'body{background-color:#f2f3f5}',
1641
+ css: 'body{background-color:transparent}',
1642
1642
  methods: {
1643
1643
  __initMethods__: {
1644
1644
  type: 'js',
@@ -1833,7 +1833,7 @@ function buildEmptyFormSchema() {
1833
1833
  id: nextNodeId(),
1834
1834
  props: {
1835
1835
  contentBgColor: 'white',
1836
- pageStyle: { backgroundColor: '#f2f3f5' },
1836
+ pageStyle: { backgroundColor: 'transparent' },
1837
1837
  contentMargin: '20',
1838
1838
  contentPadding: '20',
1839
1839
  showTitle: false,
@@ -1844,7 +1844,7 @@ function buildEmptyFormSchema() {
1844
1844
  contentBgColorMobile: 'white',
1845
1845
  },
1846
1846
  condition: true,
1847
- css: 'body{background-color:#f2f3f5}',
1847
+ css: 'body{background-color:transparent}',
1848
1848
  methods: {
1849
1849
  __initMethods__: {
1850
1850
  type: 'js',
@@ -76,9 +76,15 @@ function applyDesignTokens(template, markdown, previousMarkdown) {
76
76
  let body = root[1].replace(declarations, (line, name, value) => line.replace(value, () => changed[name] || replaceBrand(value)));
77
77
  const extra = Object.entries(changed).filter(([name]) => !(name in original));
78
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,
79
+ let tail = template.slice(root.index + root[0].length).replace(declarations,
81
80
  (line, name, value) => line.replace(value, () => replaceBrand(value)));
81
+ // Platform navigation modes bind independently; the root shell remains the custom-page default.
82
+ if (changed['--pod-shell-theme-bg-color']) {
83
+ tail = tail.replace(/(\.pod-premium\.nav-(light|dark)\s*\{)([^}]*)(\})/g,
84
+ (block, selector, tone, body, close) => selector + body.replace(declarations,
85
+ (line, name, value) => ['--pod-shell-theme-bg-color', '--pod-page-header-bg-color'].includes(name)
86
+ ? line.replace(value, `var(--color-brand1-${tone === 'dark' ? 5 : 3})`) : line) + close);
87
+ }
82
88
  const css = `${template.slice(0, root.index)}:root {${body}}${tail}`;
83
89
  validateThemeCssContent(css);
84
90
  extractThemeColor(css);
@@ -122,7 +122,7 @@ function assertPresetThemeKey(themeKey) {
122
122
  throw new Error(
123
123
  `Unsupported theme: ${themeKey}. Use one of: ${formatPresetThemeKeys()}. ` +
124
124
  'For custom colors or application theme token profiles that are not platform --theme keys, ' +
125
- 'upload an application theme CSS file with update-app --theme-file and pass --theme-color instead.'
125
+ 'upload an application theme CSS file with update-app --theme-file.'
126
126
  );
127
127
  }
128
128
 
@@ -73,18 +73,52 @@ function getFreeStockLibraries() {
73
73
  function getMaterialSourcingGuidance() {
74
74
  return {
75
75
  steps: [
76
- '1. 先用用户提供的素材。',
77
- '2. 再从 Unsplash / Pexels 搜图。',
78
- '3. 仍有缺口时使用宿主生图。',
79
- '4. 最后使用中性占位并保持 draft。',
80
- '5. 所有候选图通过 asset resolve 写入 manifest。',
76
+ '1. Plan 确认后、Fast 设计就绪后启动素材任务,同时推进应用、表单和无图页面;先复用用户图片和已完成位置。',
77
+ '2. 同一页面的图片位置也并发搜索,最多同时四个;每个位置选一张来源完整的图片并查看。',
78
+ '3. 第二轮只补第一轮失败的必需位置,换一张图片或使用设计允许的生成图。',
79
+ '4. 两轮结束后,可选位置采用设计允许的无图布局,必需位置保留缺口;已就绪页面直接继续。',
80
+ '5. 每页使用 asset resolve --page-id 并发下载上传,写入独立清单并交接该页。',
81
81
  ],
82
+ schedulingPolicy: {
83
+ owner: 'host_agent',
84
+ searchConcurrency: 4,
85
+ concurrencyScope: 'collection_run',
86
+ searchUnit: 'slot',
87
+ resumePolicy: 'reuse_final_slots_and_remaining_round_budget',
88
+ runAlongside: ['app_creation', 'form_creation', 'pages_without_images'],
89
+ waitAt: 'own_page_image_binding_and_acceptance',
90
+ resultWriter: 'one_per_page',
91
+ },
92
+ collectionPolicy: {
93
+ maxRoundsPerPage: 2,
94
+ imagesPerSlot: 1,
95
+ candidatesPerSlotPerRound: 1,
96
+ secondRound: 'failed_required_slots_only',
97
+ concurrency: 'independent_pages_and_assets',
98
+ roundOwner: 'host_agent',
99
+ pageSelector: '--page-id',
100
+ },
101
+ completionPolicy: {
102
+ resultSource: 'asset-manifests/<pageId>.json',
103
+ planApproval: 'reuse_existing_approval',
104
+ progressFields: ['visualStyle.forUser.assetStrategy.materialStatus', 'visualStyle.forUser.assetStrategy.missingAssets'],
105
+ nextStep: 'continue_ready_pages',
106
+ },
107
+ failurePolicy: {
108
+ attemptsPerCandidate: 1,
109
+ sourceUnavailable: 'switch_source_for_remaining_slots',
110
+ candidateFailed: 'replace_input',
111
+ exhausted: 'planned_optional_layout_or_required_gap',
112
+ retryCondition: 'within_two_rounds_or_explicit_request',
113
+ },
82
114
  libraries: getFreeStockLibraries(),
83
115
  rules: [
84
116
  '不编造图片 URL;按 manifest 的页面状态继续,只使用当前页 materialStatus=final 的图片。',
85
117
  '默认下载并上传宜搭图片附件;超过 20 MiB 保留原链接;不额外探测 URL,下载失败跳过并记录缺口。',
86
118
  '联网搜图仅使用 Unsplash / Pexels。',
87
- '来源不明时使用中性占位或宿主生成图。',
119
+ '401、403、429、反爬挑战或超时后,将该来源标记为本次采集不可用,其他页面和后续轮次使用其他来源。',
120
+ '每页最多两轮;每个位置每轮选一张,同一候选尝试一次,成功即结束。第二轮只补失败的必需位置;换来源、换工具或恢复上传仍计入本轮预算。',
121
+ '来源信息获取失败时换用信息齐全的候选;真实房源、商品和人员保持真实素材要求。',
88
122
  ],
89
123
  };
90
124
  }
@@ -8,13 +8,13 @@ const { resolveAssets, ASSET_SOURCES } = require('./asset-resolve');
8
8
  const { detectImageGenerator, getMaterialSourcingGuidance } = require('./ai-image');
9
9
  const { throwCommandError, throwStatus, throwUsage } = require('../core/command-errors');
10
10
 
11
- const RESOLVE_USAGE = '用法: openyida asset resolve (--input <manifest-draft.json> | --slot <slotId>=<路径或URL> ...) [--manifest <asset-manifest.json>] [--design <design.md>] [--source search|user|generated] [--app-type <appType>] [--upload-assets] [--offline] [--json]';
11
+ const RESOLVE_USAGE = '用法: openyida asset resolve (--input <manifest-draft.json> | --slot <slotId>=<路径或URL> ...) [--manifest <asset-manifest.json>] [--design <design.md>] [--page-id <pageId>] [--source search|user|generated] [--app-type <appType>] [--upload-assets] [--offline] [--json]';
12
12
 
13
13
  function printHelp() {
14
14
  console.log(`
15
15
  用法:
16
16
  openyida asset status [--offline] [--json]
17
- openyida asset resolve (--input <manifest-draft.json> | --slot <slotId>=<路径或URL> ...) [--manifest <asset-manifest.json>] [--design <design.md>] [--source search|user|generated] [--app-type <appType>] [--upload-assets] [--offline] [--json]
17
+ openyida asset resolve (--input <manifest-draft.json> | --slot <slotId>=<路径或URL> ...) [--manifest <asset-manifest.json>] [--design <design.md>] [--page-id <pageId>] [--source search|user|generated] [--app-type <appType>] [--upload-assets] [--offline] [--json]
18
18
  openyida asset sources [--json]
19
19
 
20
20
  子命令:
@@ -23,6 +23,7 @@ function printHelp() {
23
23
 
24
24
  图片不超过 20 MiB 时上传;超过限制的外链保留原地址。
25
25
  --app-type 指定目标应用,省略时读取项目 config.json。
26
+ --page-id 只处理该页槽位;按页并发时,每页使用独立的 --manifest 文件。
26
27
  --upload-assets 仅兼容旧命令,默认已上传;--offline 不联网、不上传。
27
28
  sources 输出 Unsplash/Pexels 使用规则
28
29
  `);
@@ -85,6 +86,10 @@ function parseArgs(args) {
85
86
  parsed.usedOptions.push('--design');
86
87
  parsed.design = args[++i] || '';
87
88
  if (!parsed.design || parsed.design.startsWith('--')) {parsed.errors.push('ASSET_DESIGN_REQUIRED');}
89
+ } else if (arg === '--page-id') {
90
+ parsed.usedOptions.push('--page-id');
91
+ parsed.pageId = args[++i] || '';
92
+ if (!parsed.pageId.trim() || parsed.pageId.startsWith('--')) {parsed.errors.push('ASSET_PAGE_ID_REQUIRED');}
88
93
  } else if (arg === '--manifest') {
89
94
  parsed.usedOptions.push('--manifest');
90
95
  parsed.manifest = args[++i] || '';
@@ -158,6 +163,7 @@ async function runResolve(options) {
158
163
 
159
164
  const result = await resolveAssets(assets, {
160
165
  assetStrategy,
166
+ pageId: options.pageId,
161
167
  online: !options.offline,
162
168
  appType: options.appType,
163
169
  assetSource: options.source,
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const fs = require('fs');
4
+ const path = require('path');
4
5
  const { CliError } = require('../core/cli-error');
5
6
 
6
7
  function invalid(code, details = {}) {
@@ -18,7 +19,7 @@ function nonempty(value) {
18
19
  function minimumSize(asset) {
19
20
  const match = typeof asset.minSize === 'string' && asset.minSize.match(/^(\d+)x(\d+)$/i);
20
21
  if (asset.minSize !== undefined && !match) {
21
- invalid('ASSET_INVALID_SIZE', { slotId: asset.slotId, minSize: asset.minSize });
22
+ invalid('ASSET_INVALID_SIZE', { slotId: asset.slotId, field: 'minSize', minSize: asset.minSize, expected: 'widthxheight string, e.g. 1600x900' });
22
23
  }
23
24
  const values = [asset.minWidth ?? 0, asset.minHeight ?? 0];
24
25
  const declared = match ? [Number(match[1]), Number(match[2])] : [0, 0];
@@ -42,7 +43,7 @@ function readDesignAssetStrategy(file) {
42
43
  }
43
44
 
44
45
  // The design owns page membership, required slots and minimum dimensions.
45
- function prepareAssetPlan(assets, assetStrategy) {
46
+ function prepareAssetPlan(assets, assetStrategy, pageId) {
46
47
  if (!Array.isArray(assets)) {invalid('ASSET_INPUT_INVALID', { field: 'assets' });}
47
48
  const seen = new Set();
48
49
  const entries = assets.map(asset => {
@@ -53,10 +54,15 @@ function prepareAssetPlan(assets, assetStrategy) {
53
54
  minimumSize(asset);
54
55
  return { ...asset, slotId };
55
56
  });
56
- if (assetStrategy === undefined) {return { entries, pages: [], assetStrategy: undefined };}
57
+ if (assetStrategy === undefined) {
58
+ if (pageId !== undefined) {invalid('ASSET_PAGE_REQUIRES_DESIGN', { pageId });}
59
+ return { entries, pages: [], assetStrategy: undefined };
60
+ }
57
61
  if (!object(assetStrategy) || !Array.isArray(assetStrategy.pages)) {
58
62
  invalid('ASSET_DESIGN_INVALID', { field: 'assetStrategy.pages' });
59
63
  }
64
+ assetStrategy = { ...assetStrategy, pages: assetStrategy.pages.map(page =>
65
+ object(page) && page.imageNeed === 'none' && page.slots === undefined ? { ...page, slots: [] } : page) };
60
66
  const pageIds = new Set();
61
67
  const slots = new Map();
62
68
  const pages = assetStrategy.pages.map(page => {
@@ -99,7 +105,43 @@ function prepareAssetPlan(assets, assetStrategy) {
99
105
  minHeight: Math.max(designSize.minHeight, inputSize.minHeight),
100
106
  };
101
107
  });
108
+ if (pageId !== undefined) {
109
+ if (!nonempty(pageId) || !pageIds.has(pageId.trim())) {invalid('ASSET_PAGE_NOT_FOUND', { pageId });}
110
+ const selectedId = pageId.trim();
111
+ return {
112
+ entries: planned.filter(entry => entry.pageId === selectedId),
113
+ pages: pages.filter(page => page.pageId === selectedId),
114
+ assetStrategy: { ...assetStrategy, pages: assetStrategy.pages.filter(page => page.pageId.trim() === selectedId) },
115
+ };
116
+ }
102
117
  return { entries: planned, pages, assetStrategy };
103
118
  }
104
119
 
105
- module.exports = { minimumSize, prepareAssetPlan, readDesignAssetStrategy };
120
+ // The host executes search tasks; the CLI provides concrete inputs and independent output paths.
121
+ function buildAssetTasks(assetStrategy, outputDir) {
122
+ const plan = prepareAssetPlan([], assetStrategy);
123
+ const guidance = require('./ai-image').getMaterialSourcingGuidance();
124
+ return plan.pages.filter(page => page.imageNeed !== 'none' && page.slotIds.length).map(page => {
125
+ const basename = path.join(outputDir, 'asset-manifests', encodeURIComponent(page.pageId));
126
+ const draft = `${basename}.draft.json`;
127
+ const manifest = `${basename}.json`;
128
+ return {
129
+ pageId: page.pageId, skillId: 'yida-image-assets', startWhen: 'plan_confirmed',
130
+ ...guidance.schedulingPolicy,
131
+ collectionPolicy: guidance.collectionPolicy,
132
+ failurePolicy: guidance.failurePolicy,
133
+ draft, manifest,
134
+ searches: plan.entries.filter(slot => slot.pageId === page.pageId).map(slot => ({
135
+ slotId: slot.slotId, usage: slot.usage, ...minimumSize(slot),
136
+ required: slot.required, generationAllowed: slot.generationAllowed === true, dependsOn: [],
137
+ })),
138
+ resolve: {
139
+ startWhen: 'page_draft_and_app_type_ready', appTypeRequired: true,
140
+ argv: ['asset', 'resolve', '--input', draft, '--manifest', manifest,
141
+ '--design', path.join(outputDir, 'design.md'), '--page-id', page.pageId, '--app-type', '<appType>', '--json'],
142
+ },
143
+ };
144
+ });
145
+ }
146
+
147
+ module.exports = { minimumSize, prepareAssetPlan, readDesignAssetStrategy, buildAssetTasks };
@@ -10,6 +10,7 @@ const { minimumSize, prepareAssetPlan } = require('./asset-plan');
10
10
  const { readImageMetadata, parseImageMetadata } = require('./image-metadata');
11
11
  const { MAX_IMAGE_BYTES, uploadAttachment } = require('./attachment-upload');
12
12
  const { getAssetStatus } = require('./asset-status');
13
+ const { t } = require('../core/i18n');
13
14
 
14
15
  const ALLOWED_STOCK_IMAGE_PROVIDERS = Object.freeze({
15
16
  unsplash: Object.freeze(['images.unsplash.com']),
@@ -258,7 +259,9 @@ async function resolveOne(candidate, ctx = {}) {
258
259
  }
259
260
  }
260
261
 
261
- return { resolved: false, url: raw, source: 'unknown', reason: 'NOT_FOUND' };
262
+ return { resolved: false, url: raw, source: 'unknown', reason: 'NOT_FOUND',
263
+ message: t('asset.localFileUnavailable', path.resolve(raw)),
264
+ details: { input: raw, resolvedPath: path.resolve(raw), baseDir: process.cwd(), pathBase: 'cwd' } };
262
265
  }
263
266
 
264
267
  function normalizeSource(source, fallback) {
@@ -284,7 +287,7 @@ function metadataGaps(asset) {
284
287
  }
285
288
 
286
289
  async function resolveAssets(assets = [], options = {}) {
287
- const plan = prepareAssetPlan(assets, options.assetStrategy);
290
+ const plan = prepareAssetPlan(assets, options.assetStrategy, options.pageId);
288
291
  const entries = plan.entries;
289
292
  const online = options.online !== false;
290
293
  const status = options.status || getAssetStatus({ online, env: options.env, runtime: options.runtime });
@@ -363,7 +366,8 @@ async function resolveAssets(assets = [], options = {}) {
363
366
  slotId: slotId || `slot-${index + 1}`,
364
367
  pageId: output.pageId,
365
368
  code: result.reason,
366
- message: `图片未落地:${result.reason}`,
369
+ message: result.message || `图片未落地:${result.reason}`,
370
+ ...(result.details ? { details: result.details } : {}),
367
371
  });
368
372
  gaps.push(...output.gaps.map(gap => ({ ...gap })));
369
373
  return;
@@ -146,6 +146,7 @@ const BUILDER_CORE_COMMAND_IDS = Object.freeze([
146
146
  'get-permission',
147
147
  'save-permission',
148
148
  'create-app',
149
+ 'design-plan.catalog',
149
150
  'design-plan.init',
150
151
  'design-plan.materialize',
151
152
  'design-plan.preview',
@@ -176,6 +177,7 @@ function commandBriefs(manifest, commandIds) {
176
177
  examples: entry.examples || [],
177
178
  args: entry.args || [],
178
179
  canonical: entry.canonical || null,
180
+ notes: entry.notes || [],
179
181
  }));
180
182
  }
181
183
 
@@ -466,9 +468,15 @@ function buildFullAppArtifactRoute(manifest) {
466
468
  orchestrator_skill_id: workflow.orchestrator_skill_id,
467
469
  requirement_analysis_skill_id: workflow.requirement_analysis_skill_id,
468
470
  requirement_brief_path: workflow.requirement_brief_path,
471
+ user_visible_expression_policy: workflow.user_visible_expression_policy,
469
472
  optional_asset_branch: workflow.optional_asset_branch,
470
473
  design_mode_policy: workflow.design_mode_policy,
471
474
  navigation_policy: workflow.navigation_policy,
475
+ entry_navigation_contract: workflow.entry_navigation_contract,
476
+ application_entry_policy: workflow.application_entry_policy,
477
+ default_nav_order_policy: workflow.default_nav_order_policy,
478
+ final_link_policy: workflow.final_link_policy,
479
+ product_design_policy: workflow.product_design_policy,
472
480
  plan_command_ids: workflow.plan_command_ids,
473
481
  theme_command_ids: workflow.theme_command_ids,
474
482
  navigation_command_ids: workflow.navigation_command_ids,
@@ -489,7 +497,7 @@ function buildApplicationEntryPolicy(builderPath, env = process.env) {
489
497
  resource_delivery: 'summary_only',
490
498
  internal_artifact_delivery: 'never',
491
499
  entries: {
492
- workbench: 'always',
500
+ workbench: 'when_workspace_in_scope',
493
501
  custom: 'when_entry_mode_standalone_and_is_render_nav_false_readback',
494
502
  admin: injectedAuthUsable ? 'omit' : 'include',
495
503
  },
@@ -539,6 +547,7 @@ function buildCommandManifestDigest(manifest) {
539
547
  permission_effect: entry.permission && entry.permission.effect,
540
548
  args: entry.args || [],
541
549
  canonical: entry.canonical || null,
550
+ notes: entry.notes || [],
542
551
  })),
543
552
  };
544
553