openyida 2026.9.10 → 2026.9.11-2

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 (25) hide show
  1. package/README.md +4 -2
  2. package/bin/yida.js +1 -0
  3. package/lib/app/create-form/batch.js +12 -0
  4. package/lib/app/update-app.js +17 -2
  5. package/lib/core/command-manifest.js +11 -4
  6. package/lib/core/locales/en.js +6 -2
  7. package/lib/core/locales/zh.js +6 -2
  8. package/lib/process/services/process-actions.js +145 -0
  9. package/lib/process/services/process-compiler.js +4 -30
  10. package/lib/process/services/process-view-verifier.js +31 -0
  11. package/package.json +1 -1
  12. package/yida-skills/SKILL.md +1 -1
  13. package/yida-skills/skills/yida-app/workflow/step-2-design.md +1 -1
  14. package/yida-skills/skills/yida-app/workflow/step-3-create-or-reuse-app.md +21 -1
  15. package/yida-skills/skills/yida-create-app/SKILL.md +4 -4
  16. package/yida-skills/skills/yida-create-process/SKILL.md +2 -0
  17. package/yida-skills/skills/yida-design/references/ask-human-interaction-contract.md +4 -4
  18. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/build-plan-schema.md +1 -1
  19. package/yida-skills/skills/yida-design/workflow/step-4-wireframe-interaction.md +1 -1
  20. package/yida-skills/skills/yida-nav-shell/references/nav-shell-patterns.md +1 -1
  21. package/yida-skills/skills/yida-prd/workflow/output-prd.md +2 -2
  22. package/yida-skills/skills/yida-process-rule/SKILL.md +6 -1
  23. package/yida-skills/skills/yida-process-rule/references/approval-actions.md +48 -0
  24. package/yida-skills/skills/yida-requirement-analysis/SKILL.md +1 -1
  25. package/yida-skills/skills/yida-requirement-analysis/workflow/prepare-brief.md +22 -13
package/README.md CHANGED
@@ -210,6 +210,8 @@ openyida data create form APP_XXX FORM_XXX --expect-form-name 客户 --expect-fo
210
210
  openyida get-permission APP_XXX FORM_XXX
211
211
  ```
212
212
 
213
+ Append and forward permissions are configured in the process definition through `nodes[].actions.normalActions/appendActions`. Both `create-process` and `configure-process` compile these settings into matching designer and runtime properties. See [approval action configuration](yida-skills/skills/yida-process-rule/references/approval-actions.md) for the JSON example, defaults, and verification limits.
214
+
213
215
  `configure-process` 的流程 JSON 中,审批人可配置为发起人、指定成员、指定角色、部门主管或直属主管,例如:
214
216
 
215
217
  ```json
@@ -454,8 +456,8 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
454
456
 
455
457
  | Command | Description |
456
458
  |---------|-------------|
457
- | `openyida configure-process <appType> <formUuid> <definition> [processCode] [--replace]` | Configure and publish process rules |
458
- | `openyida create-process <appType> ... [--replace]` | Create process form (all-in-one) |
459
+ | `openyida configure-process <appType> <formUuid> <definition> [processCode] [--replace]` | Configure and publish process rules; Supports append/forward via JSON nodes[].actions.normalActions/appendActions |
460
+ | `openyida create-process <appType> ... [--replace]` | Create process form (all-in-one); Supports append/forward via JSON nodes[].actions.normalActions/appendActions |
459
461
  | `openyida ai-form-setting <get\|fields\|models\|enable\|disable\|save> <appType> ...` | Manage process form AI approval prompts |
460
462
  | `openyida process preview <appType> ...` | Preview process instance (visual flowchart) |
461
463
 
package/bin/yida.js CHANGED
@@ -496,6 +496,7 @@ const MANIFEST_HELP_PATHS = Object.freeze({
496
496
  data: ['data'],
497
497
  report: ['report'],
498
498
  'create-process': ['create-process'],
499
+ 'configure-process': ['configure-process'],
499
500
  'create-report': ['create-report'],
500
501
  'append-chart': ['append-chart'],
501
502
  'save-share-config': ['save-share-config'],
@@ -246,6 +246,9 @@ async function schedule(forms, concurrency, results, worker, save) {
246
246
  return !result || result.status === 'blocked' || (result.status === 'failed' && result.formUuid);
247
247
  }).map(form => form.key));
248
248
  const active = new Map();
249
+ const recoveryAttempted = new Set(forms.filter(form =>
250
+ form.formUuid || (results[form.key]?.status === 'failed' && results[form.key]?.formUuid)
251
+ ).map(form => form.key));
249
252
  try {
250
253
  while (pending.size || active.size) {
251
254
  const pendingBefore = pending.size;
@@ -257,6 +260,8 @@ async function schedule(forms, concurrency, results, worker, save) {
257
260
  }
258
261
  if (active.size >= concurrency || deps.some(status => status !== 'success')) { continue; }
259
262
  pending.delete(form.key);
263
+ const hadKnownFormUuid = Boolean(form.formUuid || results[form.key]?.formUuid);
264
+ if (hadKnownFormUuid) { recoveryAttempted.add(form.key); }
260
265
  results[form.key] = { ...results[form.key], status: 'running' };
261
266
  save(); // Record intent before the request; an interrupted create is never retried automatically.
262
267
  const task = Promise.resolve().then(() => worker(form)).then(output => {
@@ -264,6 +269,13 @@ async function schedule(forms, concurrency, results, worker, save) {
264
269
  }, error => {
265
270
  results[form.key] = { ...results[form.key], status: 'failed', error: error.message,
266
271
  ...((error.output?.formUuid || error.output?.details?.formUuid) ? { formUuid: error.output.formUuid || error.output.details.formUuid } : {}) };
272
+ // A create may persist the form before a later schema/readback stage fails.
273
+ // Retry that known UUID once inside the same authoritative batch so its
274
+ // dependents remain pending and can run after conservative resume.
275
+ if (!hadKnownFormUuid && results[form.key].formUuid && !recoveryAttempted.has(form.key)) {
276
+ recoveryAttempted.add(form.key);
277
+ pending.add(form.key);
278
+ }
267
279
  }).then(() => { save(); }).finally(() => active.delete(form.key));
268
280
  active.set(form.key, task);
269
281
  }
@@ -221,6 +221,16 @@ function normalizeLayoutDirection(layoutDirection) {
221
221
  return normalized;
222
222
  }
223
223
 
224
+ /** 与 yc-utils 的应用配置归一化一致;不应用详情页 Shell 的 top_fold 覆盖。 */
225
+ function normalizeStoredLayoutDirection(layoutDirection, navType) {
226
+ if (SUPPORTED_LAYOUT_DIRECTIONS.includes(layoutDirection)) {return layoutDirection;}
227
+ if (layoutDirection === 'hoz') {return navType === 'top_side' ? 'l_shape' : 'top';}
228
+ if (layoutDirection === 'ver') {return 'side';}
229
+ if (navType === 'top_fold') {return 'top';}
230
+ if (navType === 'top_side') {return 'l_shape';}
231
+ return 'side';
232
+ }
233
+
224
234
  function pickAppField(currentApp, fieldName, configName) {
225
235
  if (currentApp[fieldName] !== undefined && currentApp[fieldName] !== null) {
226
236
  return currentApp[fieldName];
@@ -296,6 +306,8 @@ function buildUpdateAppPostData(params, currentApp = {}, authRef) {
296
306
  const currentCustomThemeStyle = pickAppField(currentApp, 'customThemeStyle', 'CUSTOM_THEME_STYLE');
297
307
  const addWaterMark = pickAppField(currentApp, 'addWaterMark', 'ADDWATERMARK');
298
308
  const sentryMode = pickAppField(currentApp, 'sentryMode', 'SENTRY_MODE');
309
+ const currentNavType = currentApp.navType || (currentApp.config && currentApp.config.NAVTYPE);
310
+ const currentLayoutDirection = currentApp.layoutDirection || (currentApp.config && currentApp.config.LAY_OUT_DIRECTION);
299
311
  if (params.colour === 'custom' && !params.themeColor && !currentThemeColor) {
300
312
  throw new Error(t('update_app.custom_theme_color_required'));
301
313
  }
@@ -319,9 +331,10 @@ function buildUpdateAppPostData(params, currentApp = {}, authRef) {
319
331
  mode: currentApp.mode || (currentApp.config && currentApp.config.APPMODE) || 'normal',
320
332
  type: currentApp.type || (currentApp.config && currentApp.config.APPTYPE) || 'single',
321
333
  navTheme: params.navTheme || currentApp.navTheme || (currentApp.config && currentApp.config.NAV_THEME) || 'light',
322
- navType: currentApp.navType || (currentApp.config && currentApp.config.NAVTYPE) || 'top_side',
323
334
  navLayout: currentApp.navLayout || (currentApp.config && currentApp.config.NAVLAYOUT) || 'auto',
324
- layoutDirection: params.layoutDirection || currentApp.layoutDirection || (currentApp.config && currentApp.config.LAY_OUT_DIRECTION) || 'side',
335
+ layoutDirection: params.layoutDirection
336
+ ? normalizeLayoutDirection(params.layoutDirection)
337
+ : normalizeStoredLayoutDirection(currentLayoutDirection, currentNavType),
325
338
  homepageLogo: currentApp.homepageLogo || (currentApp.config && currentApp.config.HOMEPAGELOGO) || '',
326
339
  logoSource: params.logoSource || currentApp.logoSource || (currentApp.config && currentApp.config.LOGO_SOURCE) || 'appIcon',
327
340
  logoLink: currentApp.logoLink || (currentApp.config && currentApp.config.LOGOLINK) || '',
@@ -346,6 +359,8 @@ function buildUpdateAppPostData(params, currentApp = {}, authRef) {
346
359
  pageFooter: currentApp.pageFooter || '',
347
360
  };
348
361
 
362
+ // ThemeNavSetting 保存现有 navType;新布局由 layoutDirection 控制,不反写或补造旧字段。
363
+ if (currentNavType) {postDataObj.navType = currentNavType;}
349
364
  if (addWaterMark !== undefined && addWaterMark !== null) {
350
365
  postDataObj.addWaterMark = addWaterMark;
351
366
  }
@@ -1292,8 +1292,15 @@ const COMMAND_GROUPS = [
1292
1292
  id: 'process',
1293
1293
  titleKey: 'help.group_process',
1294
1294
  commands: [
1295
- command('configure-process', ['configure-process'], 'configure-process <appType> <formUuid> <definition> [processCode] [--replace]', 'help.cmd_configure_process'),
1296
- command('create-process', ['create-process'], 'create-process <appType> ... [--replace]', 'help.cmd_create_process'),
1295
+ command('configure-process', ['configure-process'], 'configure-process <appType> <formUuid> <definition> [processCode] [--replace]', 'help.cmd_configure_process', {
1296
+ examples: ['openyida configure-process APP_XXX FORM_XXX .cache/openyida/process/process-with-actions.json'],
1297
+ }),
1298
+ command('create-process', ['create-process'], 'create-process <appType> ... [--replace]', 'help.cmd_create_process', {
1299
+ examples: [
1300
+ 'openyida create-process APP_XXX "Approval Form" .cache/openyida/process/fields.json .cache/openyida/process/process-with-actions.json',
1301
+ 'openyida create-process APP_XXX --formUuid FORM_XXX .cache/openyida/process/process-with-actions.json',
1302
+ ],
1303
+ }),
1297
1304
  command('ai-form-setting', ['ai-form-setting'], 'ai-form-setting <get|fields|models|enable|disable|save> <appType> ...', 'help.cmd_ai_form_setting', {
1298
1305
  output: 'json',
1299
1306
  aliases: ['ai-approve', 'aiFormSetting'],
@@ -1555,8 +1562,8 @@ function summarizeLocalizedCommands(commands) {
1555
1562
  plan_command_ids: ['design-plan.init', 'design-plan.preview', 'design-plan.materialize', 'design-plan.patch'],
1556
1563
  theme_command_ids: ['sample', 'create-app', 'update-app'],
1557
1564
  navigation_command_ids: { platform: ['update-app', 'nav-group'], custom: ['update-app', 'update-form-config', 'get-form-config'] },
1558
- navigation_policy: 'Before PRD planning, reuse explicit navigation choices. During first-time intake, ask only when unknown: platform-l-shape, platform-top, platform-side, custom, with neutral descriptions and no preselection. For custom navigation also settle side/top/mixed/dock. Store type/source/reason/variant in the brief. Custom navigation requires app and per-page navigation hiding with readback; layout and navigation tone are separate choices.',
1559
- design_mode_policy: 'Analyze requirements first. First-time builds include new apps and existing apps without business pages. Reuse detailed supplied plans; if the mode is unspecified, ask whether to prepare a PRD for confirmation (Plan, more detailed and slower) or build from the supplied requirements (Fast). Without detailed requirements, offer Fast and Plan neutrally. Confirm unresolved navigation, custom navigation layout, visual style and page scope before planning or resource creation. Existing business apps only clarify the current change. A named resource-only request with explicitScope.allowInferredResources=false skips unrelated navigation and visual questions. Standard Plan uses design-plan init parallelTasks, then executes the returned materialize.command exactly once with business-file and visual-file; preview and --from-preview are only for explicitly incremental large plans. Confirm the displayed revision before building, and do not materialize or patch again after confirmation.',
1565
+ navigation_policy: 'Before PRD planning, reuse explicit navigation choices. During first-time intake, ask only when ownership is unknown and offer exactly two neutral options with no preselection: "宜搭原生导航" (使用宜搭自带的导航菜单。) and "自定义导航" (在自定义页面里定制菜单的样式和操作方式,替代宜搭自带的导航菜单。). Native means platform navigation. The agent chooses layout from module count, hierarchy, switching frequency, content width and target device; do not ask layout or presentation-style questions. Prefer top for few shallow modules and wide content, side for many frequently accessed modules, L-shaped/mixed for business domains with child modules, and custom dock for lightweight mobile entries. Preserve user-specified layouts. Store resolved type/source/reason/variant in the brief, distinguishing user ownership from agent layout reasoning. Platform navigation uses update-app --layout top|side|l_shape --show-app-nav, which writes layoutDirection=top|side|l_shape and hideAppNav=n. Preserve stored navType unchanged; do not derive or invent it and do not add a --nav-type option. Without --layout, normalize stored legacy layoutDirection and navType using the yc-utils application rules (hoz plus top_side means l_shape; missing layout uses top_fold/top_side for top/l_shape). Shell page-level top_fold/none overrides do not belong in app settings. Custom side/top/mixed/dock is a page layout and requires app and per-page navigation hiding with readback. Verify persisted layoutDirection and hideAppNav; updatedFields and themeVerification do not prove navigation persistence. Navigation tone is separate from layout.',
1566
+ design_mode_policy: 'Analyze requirements first. First-time builds include new apps and existing apps without business pages. Reuse detailed supplied plans; if the mode is unspecified, ask whether to prepare a PRD for confirmation (Plan, more detailed and slower) or build from the supplied requirements (Fast). Without detailed requirements, offer Fast and Plan neutrally. Confirm unresolved navigation ownership, visual style and page scope before planning or resource creation; the agent determines navigation layout from the business context without a layout question. Existing business apps only clarify the current change. A named resource-only request with explicitScope.allowInferredResources=false skips unrelated navigation and visual questions. Standard Plan uses design-plan init parallelTasks, then executes the returned materialize.command exactly once with business-file and visual-file; preview and --from-preview are only for explicitly incremental large plans. Confirm the displayed revision before building, and do not materialize or patch again after confirmation.',
1560
1567
  product_design_policy: 'yida-requirement-analysis owns shared facts and first-time intake. yida-prd owns business planning; yida-design owns visual design. Reuse the same confirmed brief and supplied details. Prepare business and base visuals concurrently, then bind visuals to settled page tasks. yida-app merges and checks the artifacts before creating resources. Plan uses the compact authoring contract and selected theme context; the CLI reads full templates and renders all artifacts.',
1561
1568
  ui_guidance_policy: 'Page implementation consumes yida-prd prd.md for positioning, information architecture, page prototype, native form entry policy, material strategy, business-specific checks, resource creation order, page implementation delivery order, navigation order, and acceptance criteria; it consumes yida-design design.md for app custom theme CSS delivery, themeColor/navTheme, visual states, visualScaffold, surface material, rounded rules, density rules, components, and state styling. prd.md and design.md are the only design sources of truth. Page implementation may extract page-spec.json as a derived implementation handoff from prd.md + design.md; conflicts are resolved by sending business conflicts to yida-prd and visual conflicts to yida-design before regenerating the spec. Core normal forms default to 1-3 business sample records before page implementation, followed by query readback. An explicit opt-out, configuration dictionary, sensitive data, or lack of safely constructible values requires a recorded skip reason. Screenshots, public sharing, data-source deep binding, and fine navigation grouping are optional after explicit user request or PRD acceptance criteria.',
1562
1569
  default_nav_order_policy: 'For custom navigation, implement PRD navigation order in the custom shell and verify app/page navigation hiding; skip platform nav-group ordering. For platform navigation: after the primary page is successfully published, perform exactly one navigation order operation. If the PRD names a navigation order, publish without --auto-nav-order and then call openyida nav-group order <appType> <items...>. If the PRD only gives broad groups or is missing navigation order, use openyida publish ... --auto-nav-order and do not call nav-group order or auto-order afterward. Explicit and automatic ordering are mutually exclusive; never generate per-item move loops. The fallback priority is portal/home/workbench entry > business handling > data management > business analytics > system configuration.',
@@ -64,8 +64,8 @@ module.exports = {
64
64
  cmd_corp_manager: 'Manage platform admins and address book permissions',
65
65
  cmd_agent_center: 'Manage process and departure delegation',
66
66
  group_process: 'Process',
67
- cmd_configure_process: 'Configure and publish process rules',
68
- cmd_create_process: 'Create process form (all-in-one)',
67
+ cmd_configure_process: 'Configure and publish process rules; Supports append/forward via JSON nodes[].actions.normalActions/appendActions',
68
+ cmd_create_process: 'Create process form (all-in-one); Supports append/forward via JSON nodes[].actions.normalActions/appendActions',
69
69
  cmd_ai_form_setting: 'Manage process form AI approval prompts',
70
70
  cmd_process_preview: 'Preview process instance (visual flowchart)',
71
71
  group_share: 'Page Config & Sharing',
@@ -2070,3 +2070,7 @@ module.exports.connector_e2e = {
2070
2070
  test_contract_unverified: 'Connector testing did not prove the controlled fixture, marker, ownership, and auth runtime contract.',
2071
2071
  action_mutated: 'Connector testing mutated the persisted action definition.',
2072
2072
  };
2073
+
2074
+ Object.assign(module.exports.process_errors || (module.exports.process_errors = {}), {
2075
+ action_config_invalid: 'Invalid approval action configuration for node {0}: {1}',
2076
+ });
@@ -64,8 +64,8 @@ module.exports = {
64
64
  cmd_corp_manager: '管理平台管理员与通讯录权限',
65
65
  cmd_agent_center: '管理流程代理和离职代理',
66
66
  group_process: '流程',
67
- cmd_configure_process: '配置并发布流程规则',
68
- cmd_create_process: '创建流程表单(一体化)',
67
+ cmd_configure_process: '配置并发布流程规则; 支持加签/转交,配置位于 JSON nodes[].actions.normalActions/appendActions',
68
+ cmd_create_process: '创建流程表单(一体化); 支持加签/转交,配置位于 JSON nodes[].actions.normalActions/appendActions',
69
69
  cmd_ai_form_setting: '管理流程表单 AI 审批提示',
70
70
  cmd_process_preview: '预览流程实例(可视化流程图)',
71
71
  group_share: '页面配置 & 分享',
@@ -2024,3 +2024,7 @@ module.exports.connector_e2e = {
2024
2024
  test_contract_unverified: '连接器测试未能证明受控 fixture、标记、归属和鉴权运行时契约。',
2025
2025
  action_mutated: '连接器测试改变了持久化动作定义。',
2026
2026
  };
2027
+
2028
+ Object.assign(module.exports.process_errors || (module.exports.process_errors = {}), {
2029
+ action_config_invalid: '节点 {0} 的审批动作配置无效:{1}',
2030
+ });
@@ -0,0 +1,145 @@
1
+ 'use strict';
2
+
3
+ const { buildYidaI18n } = require('../../core/yida-i18n');
4
+ const { t } = require('../../core/i18n');
5
+
6
+ function buildActions() {
7
+ const actionDefs = [
8
+ { action: 'agree', zh: '同意', en: 'Agree', ja: '同意', hidden: false },
9
+ { action: 'disagree', zh: '拒绝', en: 'Disagree', ja: '拒否', hidden: false },
10
+ { action: 'save', zh: '保存', en: 'Save', ja: '保存', hidden: true },
11
+ { action: 'forward', zh: '转交', en: 'Forward', ja: '転送', hidden: true },
12
+ { action: 'append', zh: '加签', en: 'Append', ja: '承認者を追加', hidden: true },
13
+ { action: 'return', zh: '退回', en: 'Return', ja: '差し戻し', hidden: true },
14
+ ];
15
+
16
+ return actionDefs.map(function (def) {
17
+ return {
18
+ hidden: def.hidden,
19
+ name: buildYidaI18n(def.zh, { en_US: def.en, ja_JP: def.ja }),
20
+ action: def.action,
21
+ text: buildYidaI18n(def.zh, { en_US: def.en, ja_JP: def.ja }),
22
+ alias: buildYidaI18n(def.zh, { en_US: def.en, ja_JP: def.ja }),
23
+ };
24
+ });
25
+ }
26
+
27
+ function invalid(nodeName, field) {
28
+ const error = new TypeError(t('process_errors.action_config_invalid', nodeName, field));
29
+ error.code = 'PROCESS_COMPILE_ACTION_CONFIG_INVALID';
30
+ error.details = { nodeName, field };
31
+ throw error;
32
+ }
33
+
34
+ function isObject(value) {
35
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
36
+ }
37
+
38
+ function clone(value) {
39
+ return JSON.parse(JSON.stringify(value));
40
+ }
41
+
42
+ /** Merge partial button settings without changing unspecified permissions. */
43
+ function normalizeActions(value, field, nodeName, complete = false) {
44
+ const defaults = buildActions();
45
+ if (value === undefined) { return defaults; }
46
+ if (!Array.isArray(value)) { invalid(nodeName, field); }
47
+ const seen = new Set();
48
+ const overrides = value.map(action => {
49
+ if (!isObject(action) || typeof action.action !== 'string' || !action.action.trim()
50
+ || (!complete && !['agree', 'disagree', 'save', 'forward', 'append', 'return', 'recall'].includes(action.action))
51
+ || seen.has(action.action) || (action.hidden !== undefined && typeof action.hidden !== 'boolean')) {
52
+ invalid(nodeName, field);
53
+ }
54
+ seen.add(action.action);
55
+ return clone(action);
56
+ });
57
+ const merge = (base, override) => {
58
+ const result = { ...base, ...override };
59
+ if (override && override.name !== undefined && override.alias === undefined) { result.alias = clone(override.name); }
60
+ if (override && override.alias !== undefined && override.name === undefined) { result.name = clone(override.alias); }
61
+ return result;
62
+ };
63
+ // Raw processProps arrays are complete lists, not partial overrides. Do not add
64
+ // agree/disagree permissions to a legacy list that intentionally omitted them.
65
+ if (complete) {
66
+ // NewProcessAdapter treats an omitted hidden field in a raw action as visible.
67
+ return overrides.map(item => merge({ ...defaults.find(action => action.action === item.action), hidden: false }, item));
68
+ }
69
+ const result = defaults.map(action => merge(action, overrides.find(item => item.action === action.action)));
70
+ // Native versions may include the additional recall button.
71
+ overrides.filter(item => item.action === 'recall').forEach(item => result.push({ hidden: true, ...item }));
72
+ return result;
73
+ }
74
+
75
+ /**
76
+ * Compile one shared action contract into designer and engine representations.
77
+ * Native node.actions takes precedence; legacy processProps lists remain accepted.
78
+ * Missing append settings use the same defaults as ActionSetter; invalid explicit
79
+ * settings fail before publishing. No config retains historical hidden defaults.
80
+ * @param {object|undefined} config native normalActions / appendActions overrides
81
+ * @param {object} rawProps legacy approver process properties
82
+ * @param {string} nodeName diagnostic node name
83
+ * @returns {{viewActions: object, processProps: object}} independent JSON values
84
+ */
85
+ function buildNodeActions(config, rawProps = {}, nodeName = '') {
86
+ if (config !== undefined && (!isObject(config)
87
+ || Object.keys(config).some(key => !['normalActions', 'appendActions'].includes(key)))) {
88
+ invalid(nodeName, 'actions');
89
+ }
90
+ const input = config || {};
91
+ const normalActions = normalizeActions(input.normalActions === undefined ? rawProps.actions : input.normalActions,
92
+ 'actions.normalActions', nodeName, input.normalActions === undefined);
93
+ const appendActions = normalizeActions(input.appendActions === undefined ? rawProps.appendActions : input.appendActions,
94
+ 'actions.appendActions', nodeName, input.appendActions === undefined);
95
+ const append = normalActions.find(item => item.action === 'append') || { hidden: true };
96
+ const appendedAppend = appendActions.find(item => item.action === 'append') || { hidden: true };
97
+ const processProps = {};
98
+
99
+ // These engine fields are node-wide: the designer derives them from normalActions.
100
+ if (!appendedAppend.hidden && append.hidden) {
101
+ invalid(nodeName, 'actions.normalActions.append.hidden');
102
+ }
103
+ if (!append.hidden) {
104
+ if (append.appendPosition === undefined) {
105
+ append.appendPosition = rawProps.moldList === undefined ? ['BEFORE_APPEND'] : clone(rawProps.moldList);
106
+ }
107
+ if (append.appendResult === undefined) {
108
+ if (rawProps.isConsiderAppendedAction !== undefined && typeof rawProps.isConsiderAppendedAction !== 'boolean') {
109
+ invalid(nodeName, 'processProps.isConsiderAppendedAction');
110
+ }
111
+ append.appendResult = rawProps.isConsiderAppendedAction === undefined
112
+ ? 'valid' : (rawProps.isConsiderAppendedAction === true ? 'valid' : 'invalid');
113
+ }
114
+ if (!Array.isArray(append.appendPosition) || append.appendPosition.length === 0
115
+ || append.appendPosition.some(position => !['BEFORE_APPEND', 'AFTER_APPEND'].includes(position))
116
+ || new Set(append.appendPosition).size !== append.appendPosition.length) {
117
+ invalid(nodeName, 'actions.normalActions.append.appendPosition');
118
+ }
119
+ if (!['valid', 'invalid'].includes(append.appendResult)) {
120
+ invalid(nodeName, 'actions.normalActions.append.appendResult');
121
+ }
122
+ Object.assign(processProps, {
123
+ allowTaskAppend: true,
124
+ moldList: clone(append.appendPosition),
125
+ isConsiderAppendedAction: append.appendResult === 'valid',
126
+ isNeedEndTaskGroupChain: append.appendResult === 'valid',
127
+ });
128
+ } else if (['allowTaskAppend', 'moldList', 'isConsiderAppendedAction', 'isNeedEndTaskGroupChain']
129
+ .some(key => rawProps[key] !== undefined)) {
130
+ // Clear stale engine flags when the action is explicitly disabled.
131
+ Object.assign(processProps, {
132
+ allowTaskAppend: false, moldList: [], isConsiderAppendedAction: false, isNeedEndTaskGroupChain: false,
133
+ });
134
+ }
135
+ const viewActions = { normalActions, appendActions };
136
+ // Retain existing return settings in both representations.
137
+ ['realBackOriginator', 'triggerRule', 'backScope'].forEach(key => {
138
+ if (rawProps[key] !== undefined) { viewActions[key] = clone(rawProps[key]); }
139
+ });
140
+ processProps.actions = clone(normalActions);
141
+ processProps.appendActions = clone(appendActions);
142
+ return { viewActions, processProps };
143
+ }
144
+
145
+ module.exports = { buildNodeActions };
@@ -8,6 +8,7 @@
8
8
 
9
9
  const { buildYidaI18n } = require('../../core/yida-i18n');
10
10
  const { t } = require('../../core/i18n');
11
+ const { buildNodeActions } = require('./process-actions');
11
12
 
12
13
  const KEY_PATTERN = /^[A-Za-z][A-Za-z0-9_]*$/;
13
14
 
@@ -649,29 +650,6 @@ function buildFriendlyApproverConfig(node, approver) {
649
650
  return null;
650
651
  }
651
652
 
652
- // ── 构建审批/抄送动作列表 ────────────────────────────
653
-
654
- function buildActions() {
655
- const actionDefs = [
656
- { action: 'agree', zh: '同意', en: 'Agree', ja: '同意', hidden: false },
657
- { action: 'disagree', zh: '拒绝', en: 'Disagree', ja: '拒否', hidden: false },
658
- { action: 'save', zh: '保存', en: 'Save', ja: '保存', hidden: true },
659
- { action: 'forward', zh: '转交', en: 'Forward', ja: '転送', hidden: true },
660
- { action: 'append', zh: '加签', en: 'Append', ja: '承認者を追加', hidden: true },
661
- { action: 'return', zh: '退回', en: 'Return', ja: '差し戻し', hidden: true },
662
- ];
663
-
664
- return actionDefs.map(function (def) {
665
- return {
666
- hidden: def.hidden,
667
- name: i18n(def.zh, def.en, def.ja),
668
- action: def.action,
669
- text: i18n(def.zh, def.en, def.ja),
670
- alias: i18n(def.zh, def.en, def.ja),
671
- };
672
- });
673
- }
674
-
675
653
  // ── 构建条件规则(严格匹配宜搭真实格式)─────────────
676
654
 
677
655
  function buildConditionRules(rules, logic) {
@@ -1162,10 +1140,9 @@ function buildApprovalNode(node, nextNodeId, nameToIdMap, nodeType) {
1162
1140
  formConfig = { behaviorList: behaviorList };
1163
1141
  }
1164
1142
 
1143
+ const nodeActions = buildNodeActions(node.actions, approverConfig.processProps, displayName);
1165
1144
  const processNodeProps = {
1166
1145
  conditionalMode: 'conditional',
1167
- actions: buildActions(),
1168
- appendActions: buildActions(),
1169
1146
  openDigitalSign: false,
1170
1147
  noActionersType: 'stopProcess',
1171
1148
  routeRule: routeRule,
@@ -1173,7 +1150,7 @@ function buildApprovalNode(node, nextNodeId, nameToIdMap, nodeType) {
1173
1150
  if (approverConfig.approvals !== undefined) {
1174
1151
  processNodeProps.approvals = approverConfig.approvals;
1175
1152
  }
1176
- Object.assign(processNodeProps, approverConfig.processProps);
1153
+ Object.assign(processNodeProps, approverConfig.processProps, nodeActions.processProps);
1177
1154
  let multiApproverRules;
1178
1155
  if (approvalNodeType === 'multiApproval') {
1179
1156
  const multiMode = node.approvalMode
@@ -1220,10 +1197,7 @@ function buildApprovalNode(node, nextNodeId, nameToIdMap, nodeType) {
1220
1197
  nodeName: componentName,
1221
1198
  name: i18n(displayName, titleText[1], displayName),
1222
1199
  description: nodeDescription,
1223
- actions: {
1224
- normalActions: buildActions(),
1225
- appendActions: buildActions(),
1226
- },
1200
+ actions: nodeActions.viewActions,
1227
1201
  routeRule: routeRule,
1228
1202
  };
1229
1203
  if (multiApproverRules) {
@@ -125,6 +125,7 @@ function verifyPlatformView(response, expectedViewJson, expectedFormUuid) {
125
125
  actual: actualNodes,
126
126
  });
127
127
  }
128
+ verifyActionSettings(expectedViewJson.schema.children, actualView.schema.children, errors);
128
129
  return {
129
130
  verificationLevel: errors.length === 0 ? 'PLATFORM_VIEW_VERIFIED' : 'PUBLISHED_UNVERIFIED',
130
131
  valid: errors.length === 0,
@@ -133,6 +134,36 @@ function verifyPlatformView(response, expectedViewJson, expectedFormUuid) {
133
134
  };
134
135
  }
135
136
 
137
+ /** Compare permissions and append semantics, tolerating platform-added metadata. */
138
+ function verifyActionSettings(expectedNodes, actualNodes, errors, parentPath = '') {
139
+ (expectedNodes || []).forEach((node, index) => {
140
+ const actual = (actualNodes || [])[index];
141
+ const path = parentPath + '/' + index;
142
+ const expectedActions = node.props && node.props.actions;
143
+ const actualActions = actual && actual.props && actual.props.actions;
144
+ if (isPlainObject(expectedActions)) {
145
+ ['normalActions', 'appendActions'].forEach(group => {
146
+ if (!Array.isArray(expectedActions[group])) { return; }
147
+ expectedActions[group].forEach(action => {
148
+ const matches = actualActions && Array.isArray(actualActions[group])
149
+ ? actualActions[group].filter(item => item && item.action === action.action) : [];
150
+ const found = matches[0];
151
+ const keys = ['hidden'];
152
+ if (group === 'normalActions' && action.action === 'append' && !action.hidden) {
153
+ keys.push('appendPosition', 'appendResult');
154
+ }
155
+ if (matches.length !== 1 || keys.some(key => JSON.stringify(action[key]) !== JSON.stringify(found[key]))) {
156
+ errors.push({ code: 'PROCESS_PLATFORM_VIEW_ACTION_MISMATCH', path, group, action: action.action });
157
+ }
158
+ });
159
+ });
160
+ }
161
+ if (Array.isArray(node.children)) {
162
+ verifyActionSettings(node.children, actual && actual.children, errors, path);
163
+ }
164
+ });
165
+ }
166
+
136
167
  module.exports = {
137
168
  extractPlatformView,
138
169
  verifyPlatformView,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openyida",
3
- "version": "2026.9.10",
3
+ "version": "2026.9.11-2",
4
4
  "description": "OpenYida CLI - 宜搭低代码 AI 开发工具(安装即用,零配置)",
5
5
  "bin": {
6
6
  "openyida": "bin/yida.js",
@@ -128,7 +128,7 @@ description: >
128
128
  | 创建自定义展示页资源 | `yida-create-page`,之后交给 `yida-canvas-custom-page` 编写页面源码,再交给 `yida-publish-page` 发布 |
129
129
  | 开发表单字段结构 / 增删改字段 | 使用 `yida-create-form-page` 落地字段结构 |
130
130
  | 创建带审批的流程表单 | `yida-create-process` |
131
- | 修改已有流程节点/分支/字段权限 | `yida-process-rule` |
131
+ | 修改已有流程节点/分支/字段权限,配置加签/转交操作权限 | `yida-process-rule`;加签/转交先读其 `references/approval-actions.md` |
132
132
  | 查字段 ID / 保存 Schema 证据 | `yida-get-schema`;凡涉及 fieldId 的数据、流程、公式、页面代码先取证 |
133
133
  | 改表单数据记录 | `yida-data-management`,不是 `yida-create-form-page` |
134
134
  | 配字段默认值、计算、校验 | `yida-formula`;静态检查用 `yida-formula-evaluate` |
@@ -8,7 +8,7 @@
8
8
 
9
9
  ## 2.0 先分析并确认需求
10
10
 
11
- 调用 `yida-requirement-analysis`,按 [需求分析与首次搭建确认](../../yida-requirement-analysis/workflow/prepare-brief.md) 整理来源、复用资源及用户已有计划,在同一轮一次性确认尚未明确的 Fast / Plan、业务模块、页面与表单、导航归属与布局及风格。导航选择直接包含平台或自定义及具体布局,并按 [导航选项说明](../../yida-requirement-analysis/workflow/prepare-brief.md#导航选项说明) 备注平台原生布局与自定义页面实现的区别,“自定义导航”大类不标记“推荐”;已选自定义顶部时,呈现样式默认推荐浮导,用户明确的其他样式优先;后续阶段复用答案。首次搭建在必要回答写回、`intake.confirmed=true` 后继续。
11
+ 调用 `yida-requirement-analysis`,按 [需求分析与首次搭建确认](../../yida-requirement-analysis/workflow/prepare-brief.md) 整理来源、复用资源及用户已有计划,在同一轮一次性确认尚未明确的 Fast / Plan、业务模块、页面与表单、导航归属及风格。导航 `ask_human` 仅提供“宜搭原生导航”和“自定义导航”两个选项,原生导航就是平台导航,并按 [导航选项说明](../../yida-requirement-analysis/workflow/prepare-brief.md#导航选项说明) 备注实现方式,不标记“推荐”。顶部、侧边、L 型等布局在用户所选归属内根据场景确定,不询问布局或样式;用户已明确的要求优先,后续阶段复用归属选择和布局依据。首次搭建在必要回答写回、`intake.confirmed=true` 后继续。
12
12
 
13
13
  回答齐全后直接保存内部需求记录并进入 2.1;不把“生成需求简报”列为独立任务,不再扩写或展示简报请用户确认。已有确认记录且需求未变化时直接复用。记录粒度、保存和校验规则统一遵守上述需求分析流程。
14
14
 
@@ -40,7 +40,27 @@
40
40
 
41
41
  ## 应用导航配置
42
42
 
43
- 按 PRD 的 [导航类型](../../yida-prd/workflow/output-prd.md#导航类型与执行配置) 设置布局。已有应用切换平台导航时执行 `openyida update-app <appType> --layout <l_shape|top|side> --show-app-nav`;自定义导航执行 `openyida update-app <appType> --hide-app-nav`,并将逐页隐藏清单交给 Step 4 与 Step 6,在各页面创建后立即配置,Step 8 只做发布后核对。新建应用也在主题文件生成后的同一次 `update-app --theme-file` 中传入布局和 `--hide-app-nav` / `--show-app-nav`,不要提前单独更新导航或在创建时传入布局。用户已确认的应用导航隐藏随这次设置更新一起生效,不等待自定义页面创建、代码开发或发布。
43
+ 按 PRD 的 [导航类型](../../yida-prd/workflow/output-prd.md#agent-导航实施映射) 设置布局。已有应用切换平台导航时执行 `openyida update-app <appType> --layout <l_shape|top|side> --show-app-nav`;自定义导航执行 `openyida update-app <appType> --hide-app-nav`,并将逐页隐藏清单交给 Step 4 与 Step 6,在各页面创建后立即配置,Step 8 只做发布后核对。新建应用也在主题文件生成后的同一次 `update-app --theme-file` 中传入布局和 `--hide-app-nav` / `--show-app-nav`,不要提前单独更新导航或在创建时传入布局。用户已确认的应用导航隐藏随这次设置更新一起生效,不等待自定义页面创建、代码开发或发布。
44
+
45
+ ### 平台导航参数
46
+
47
+ 与 `yida-next` 的 ThemeNavSetting 一致:`updateApp.json` 接收的字段名是 **`layoutDirection`**。CLI 使用 `--layout` 传入,按下表逐项对应:
48
+
49
+ | PRD 导航方案 | CLI 参数 | 请求中的 `layoutDirection` | 请求中的 `hideAppNav` |
50
+ | --- | --- | --- | --- |
51
+ | 平台顶部导航 | `--layout top --show-app-nav` | `top` | `n` |
52
+ | 平台侧边导航 | `--layout side --show-app-nav` | `side` | `n` |
53
+ | 平台 L 型导航 | `--layout l_shape --show-app-nav` | `l_shape` | `n` |
54
+
55
+ 例如,仅将已有应用切换为平台顶部导航:`openyida update-app <appType> --layout top --show-app-nav`。联合更新主题时,将这两个参数合并到同一次 `update-app --theme-file` 调用。
56
+
57
+ Agent 必须显式传入场景选择对应的布局,不依赖 CLI 默认值。`navigationType=platform-top` 是计划内部标识,不能作为 `--layout` 的值;也不能用旧 Shell 的 `navType`、`hoz/ver/slide` 或 `top_fold/top_side/side_only` 代替上述参数。`navTheme` 只控制导航配色,不控制布局。自定义导航的 `variant=top/side/mixed/dock` 描述页面内菜单,不用于设置平台布局;自定义导航仍使用 `--hide-app-nav`。
58
+
59
+ `navType` 仍是兼容字段:与 `yida-next` 一致,CLI 查询应用后将已有值原样带回;缺失时不补造,也不根据新布局改写。没有新增 `--nav-type` 参数。未传 `--layout` 时,CLI 按 `yc-utils` 的应用配置规则保留原布局:现代 `side/top/l_shape` 优先;旧 `hoz + top_side` 为 L 型,其他 `hoz` 为顶部,`ver` 为侧边;布局缺失时用 `navType=top_fold/top_side` 分别恢复顶部/L 型,其余回退侧边。
60
+
61
+ Shell 渲染阶段才把顶部、侧边、L 型转换为 `top_fold`、`side_only`、`top_side`。详情/提交页的 `top_fold` 强制顶部、`none` 保持无导航属于页面运行态规则,不能反向写入应用配置。
62
+
63
+ 保存后核对应用详情中的 `layoutDirection` 与 `hideAppNav` 是否等于目标值,可通过应用设置页或 `/<appType>/query/app/getAppIncludingAecpInfo.json` 回读。CLI 输出的 `updatedFields` 是提交值,`themeVerification` 只验证主题资源,两者都不能单独证明导航配置已生效。发布后再刷新应用,检查实际布局;未完成回读时明确标记未验证。
44
64
 
45
65
  ## 产出
46
66
 
@@ -63,7 +63,7 @@ openyida update-app <appType> --theme-file <app-theme.css> --nav-theme light --l
63
63
  |---------|------|
64
64
  | `--colour` / `--theme` | 平台主题 key,例如 `podBlue`、`podGreen`、`podOrange`、`black`、`custom`;禁止填写 HEX、RGB 或自造名称 |
65
65
  | `--nav-theme` | `light` / `dark` / `white` / `gray` |
66
- | `--layout` | `side` / `top` / `l_shape`,按 PRD 设置 |
66
+ | `--layout` | 顶部传 `top`、侧边传 `side`、L 型传 `l_shape`;CLI 写入接口字段 `layoutDirection`,使用平台导航时同时传 `--show-app-nav` |
67
67
  | `--theme-file` | 上传主题 CSS,保存 `customThemeStyle` 资源及从 CSS 提取的 `themeColor` |
68
68
  | `--theme-color` | 仅更新应用主色;与主题文件同传时以 CSS 主色为准 |
69
69
  | `--logo-source` | `appIcon` / `customImage`;后者要求应用已有 `homepageLogo` |
@@ -77,7 +77,7 @@ openyida update-app <appType> --theme-file <app-theme.css> --nav-theme light --l
77
77
 
78
78
  创建命令只提交名称、描述、图标和必要的创建标记,不推断或提交应用主题与导航配置。创建完整应用、使用 PRD/design.md 或用户要求配置主题时,主题更新命令默认传入主题文件;只有用户明确只创建空壳或暂不配置主题时才跳过主题更新。搭建流程必须采用先 `create-app`、再 `update-app --theme-file` 的两个步骤。`create-app` 不接受 `--theme-file` 和 `--logo-source`,也不接受 `--colour`、`--theme`、`--nav-theme`、`--layout` 或旧位置参数里的主题与布局,不隐式执行应用设置更新。
79
79
 
80
- CLI 始终按“显式 `--icon` → 行业推断 → 随机系统图标”的顺序选择图标,只有未显式指定且未命中行业时才随机,与后续是否更新主题文件无关。主题文件生成后按 PRD 在 `update-app --layout` 中显式设置布局;普通应用未指定时由设计流程选择 `l_shape`。在 `update-app --theme-file` 步骤中校验 CSS 并将图标颜色统一为 `--color-brand1-6` 转换后的 HEX;导航配置沿用 PRD,普通浅色方案显式传 `--nav-theme light --logo-source appIcon`。
80
+ CLI 始终按“显式 `--icon` → 行业推断 → 随机系统图标”的顺序选择图标,只有未显式指定且未命中行业时才随机,与后续是否更新主题文件无关。主题文件生成后按 PRD 在 `update-app --layout` 中显式设置布局;用户未指定布局时按业务场景选择,不固定使用 `l_shape`。参数映射与保存后核对遵守 [平台导航参数](../yida-app/workflow/step-3-create-or-reuse-app.md#平台导航参数)。在 `update-app --theme-file` 步骤中校验 CSS 并将图标颜色统一为 `--color-brand1-6` 转换后的 HEX;导航配置沿用 PRD,普通浅色方案显式传 `--nav-theme light --logo-source appIcon`。
81
81
 
82
82
  **应用主题(colour)口径**:
83
83
 
@@ -93,8 +93,8 @@ openyida sample yida-design app-theme --output .cache/openyida/<项目名>/app-t
93
93
 
94
94
  ```bash
95
95
  openyida create-app --name "<应用名>" --desc "<描述>"
96
- # 从创建结果提取 appType 后执行
97
- openyida update-app <appType> --theme-file <app-theme.css> --nav-theme light --logo-source appIcon --layout l_shape
96
+ # 从创建结果提取 appType 后执行;以下为 PRD 选择平台顶部导航的示例
97
+ openyida update-app <appType> --theme-file <app-theme.css> --nav-theme light --logo-source appIcon --layout top --show-app-nav
98
98
  ```
99
99
 
100
100
  CLI 会先校验主题文件完整声明平台实际生成的 `--color-brand1-1/2/3/5/6/9/10`,并允许不存在 `--color-brand1-4/7/8`。`update-app --theme-file` 上传 CSS,再调用应用基础设置的 `updateApp` 接口联合保存从 `--color-brand1-6` 提取的 `themeColor`、`customThemeStyle`、`navTheme`、`logoSource` 和 `layoutDirection`。更新主题时,系统应用图标会同步保存为 `iconName%%主题色HEX`;外链或上传图片图标保持原值。
@@ -188,6 +188,7 @@ openyida create-process "APP_XXX" --formUuid "FORM-YYY" .cache/openyida/order/pr
188
188
  1. **🔐 字段权限**:当字段 ≥ 3 且审批节点 ≥ 2 时,每个节点只允许编辑相关字段
189
189
  2. **🔄 跳转规则**:存在回退/循环语义时,自动配置 `routeRules`
190
190
  3. **🔀 并行/办理/高级组件**:需要并行会审、办理节点或连接器/数据/消息等官方组件节点时,流程定义格式直接参考 `yida-process-rule`
191
+ 4. **加签/转交操作权限**:用户要求开启时,按 [操作权限配置](../yida-process-rule/references/approval-actions.md) 生成 `nodes[].actions.normalActions/appendActions`;未要求时保持默认关闭。`create-process` 与 `configure-process` 共用编译器,无需新增命令或手写运行字段。禁止仅修改 `hidden` 后提交原始运行 JSON。
191
192
 
192
193
  需要流程规则细节时,调用 `use_skill("yida-process-rule", "配置已有流程节点、分支和字段权限")`。
193
194
 
@@ -198,6 +199,7 @@ openyida create-process "APP_XXX" --formUuid "FORM-YYY" .cache/openyida/order/pr
198
199
  | 命令返回失败 | 检查 appType 和 formUuid 是否正确,确认登录态有效 |
199
200
  | processCode 获取失败 | 确认表单已成功转为流程表单类型,重新执行 |
200
201
  | 流程定义 JSON 格式错误 | 加载 `yida-process-rule` 子技能,按其中的 JSON 格式说明修正 |
202
+ | `PROCESS_COMPILE_ACTION_CONFIG_INVALID` | 本地编译已拦截无效操作权限,修正报错节点的加签位置、结果或按钮配置后再执行;不要通过删除校验或手动补运行 JSON 绕过 |
201
203
  | 返回 JSON 中无 processCode | 不要猜测 processCode,重新执行命令获取 |
202
204
  | 流程发布失败 | 检查流程定义中的 fieldId 是否为真实 ID(先 get-schema 获取) |
203
205
  | 登录态失效 | 执行 `openyida login` 重新登录后再试 |
@@ -60,7 +60,7 @@
60
60
  1. 进度消息使用业务结果和下一步动作。结构化问题紧随其后时,不额外发送内部分析结论;多个问题确需共同背景时,只在整组交互的引导中说明一次,不写入每个问题的 `title` 或 `prompt`。
61
61
  2. `title`、`prompt`、`label`、`description` 和普通对话只使用用户能直接理解的业务词。
62
62
  3. `interactionId`、`questionType`、`value`、`writeBackPath`、`reason`、状态字段和文件路径仅供内部执行,不进入用户可见内容。
63
- 4. `title` 只写确认主题,`prompt` 只写用户当前需要回答的问题;搭建模式、导航归属与布局保持中性,不标记“推荐”或“默认”,不预选、不按偏好排序,也不在引导或描述中推荐。已选自定义顶部导航时,其呈现样式默认采用浮导,同轮展示样式选项可写“浮导(推荐)”;用户明确的其他样式优先。其他问题存在合理默认值时可标记。只有某个选择会产生重要且难以撤销的影响时,才在对应选项的 `description` 中说明影响。
63
+ 4. `title` 只写确认主题,`prompt` 只写用户当前需要回答的问题;搭建模式、导航归属保持中性,不标记“推荐”或“默认”,不预选、不按偏好排序,也不在引导或描述中推荐。导航布局和呈现样式根据场景确定,不进入提问选项;用户明确的布局和样式优先。其他问题存在合理默认值时可标记。只有某个选择会产生重要且难以撤销的影响时,才在对应选项的 `description` 中说明影响。
64
64
  5. 用户已经提供的信息直接写入内部事实,不重复总结成“缺口分析”或再次追问。
65
65
  6. PRD 的业务说明、HTML、进度消息和交付总结使用功能、体验和验收结果描述配置。接口参数、配置键值、内部 ID 和 CLI 选项保留在 Agent 实施交接中;访问链接使用业务名称,链接目标保留必需参数。用户明确询问实现细节时,再解释对应技术内容。
66
66
  7. 规划写预期行为,进度写正在进行的动作,完成总结依据实际验证结果。导航方案写“采用自定义导航,支持各业务页面间切换”;完成配置并验证跳转后写“已启用自定义导航,可在各业务页面间切换”。
@@ -100,7 +100,7 @@ Fast / Plan 是面向用户的模式名称,可以展示。已有详细计划
100
100
  | ---------------- | ---------------------------------------------- | ---------------------------- | ----------------------------- |
101
101
  | 搭建方式 | `single_choice` | 按需求详细程度提供 PRD 是/否或 Fast/Plan | brief 的 `intake.designMode` |
102
102
  | 业务模块与页面范围 | `multi_choice` | 具体业务模块、对应页面、表单及用途,可合为一题 | brief 的 `coreFunctions/businessObjects/pageScenes/explicitScope` |
103
- | 导航归属与布局 | `single_choice` | 平台L型、平台顶部、平台侧边,以及自定义左侧、顶部浮导、顶部加左侧、底部悬浮菜单;一次选定归属和布局 | brief 的 `navigation`(自定义含 `variant`) |
103
+ | 导航归属 | `single_choice` | 仅“宜搭原生导航”和“自定义导航”两个选项;原生导航就是平台导航,布局根据场景确定 | brief 的 `navigation`(按场景补齐 `type`,自定义含 `variant`,`reason` 区分用户归属选择与布局依据) |
104
104
  | 应用设计风格 | `single_choice` | 面向业务的风格选项与自定义偏好 | brief 的 `visualSelection` |
105
105
  | 来源补齐 | `free_text` | 可读正文、链接或附件 | `meta.source` |
106
106
  | 最新搭建计划确认 | `confirm` | 当前版本、确认生成、继续调整 | `meta.planState` |
@@ -124,9 +124,9 @@ Fast / Plan 是面向用户的模式名称,可以展示。已有详细计划
124
124
 
125
125
  ## 首次搭建确认
126
126
 
127
- 先完成需求分析,再按 [首次搭建确认表](../../yida-requirement-analysis/workflow/prepare-brief.md#2-确认首次搭建的未决事项) 询问尚未明确的事项。该表统一维护详细计划复用、搭建方式、业务模块、导航归属与布局、风格和页面范围;已有业务应用的局部增改按本次疑问澄清。
127
+ 先完成需求分析,再按 [首次搭建确认表](../../yida-requirement-analysis/workflow/prepare-brief.md#2-确认首次搭建的未决事项) 询问尚未明确的事项。该表统一维护详细计划复用、搭建方式、业务模块、导航归属、风格和页面范围;已有业务应用的局部增改按本次疑问澄清。
128
128
 
129
- 首次搭建的全部未决事项在一次结构化提问调用中呈现并一次性作答,Fast / Plan、平台或自定义导航、导航布局、业务模块和页面范围不得分轮收集。导航选项直接包含归属和布局,不先选自定义导航再补问位置;每项必须按 [导航选项说明](../../yida-requirement-analysis/workflow/prepare-brief.md#导航选项说明) 附带布局及实现方式说明:平台L型、侧导、顶部均使用宜搭原生导航,自定义导航在自定义页面中实现、不使用宜搭原生导航。支持 `description` 时写入该字段,否则在选项后括号备注;“自定义导航”大类及顶部/侧边等布局不写“(推荐)”或“默认”,自定义顶部内部的样式默认推荐浮导,不增加确认轮次。工具限制问题数量时合并为复合问题,选项数量或多选能力不足时使用同组自由输入;没有可用结构化工具时,在一条消息中展示全部问题。具体合并方式遵守首次搭建确认表。用户明确的选择直接记录,全部必要回答写回后才开始后续规划。
129
+ 首次搭建的全部未决事项在一次结构化提问调用中呈现并一次性作答,Fast / Plan、导航归属、业务模块和页面范围不得分轮收集。导航仅提供“宜搭原生导航”和“自定义导航”两个选项,按 [导航选项说明](../../yida-requirement-analysis/workflow/prepare-brief.md#导航选项说明) 附带实现方式说明;宜搭原生导航就是平台导航。支持 `description` 时写入该字段,否则在选项后括号备注。顶部、侧边、L 型、浮导、底部菜单等布局或样式不进入 `ask_human`;Agent 在已选归属内根据场景确定,记录布局依据并交给后续规划,不补问位置。用户已明确的归属、布局或样式直接沿用。工具限制问题数量时合并为复合问题,多选能力不足时使用同组自由输入;没有可用结构化工具时,在一条消息中展示全部问题。具体合并方式遵守首次搭建确认表,全部必要回答写回后才开始后续规划。
130
130
 
131
131
  视觉映射统一写入 brief 的 `visualSelection`,由后续计划沿用 visualDirection、selectedTheme、colorStrategy、navigationStyle;未选候选保留在会话中。
132
132
 
@@ -142,7 +142,7 @@
142
142
 
143
143
  约束:
144
144
 
145
- - 计划生成前先确定应用范围与导航类型,再选择视觉方向。范围和导航不明时可合并询问,回答后生成遵守导航决策的视觉候选;每个问题必须有 `interactionId` 和 `writeBackPath`。
145
+ - 计划生成前先确定应用范围与导航类型,再选择视觉方向。范围和导航归属不明时可合并询问,导航选项仅为“宜搭原生导航”和“自定义导航”;原生导航即平台导航,顶部、侧边、L 型等布局由 Agent 按场景确定,不进入 `ask_human`。回答后生成遵守导航决策的视觉候选;每个问题必须有 `interactionId` 和 `writeBackPath`。
146
146
  - `questionType` 只取 `single_choice`、`multi_choice`、`free_text` 或最终计划使用的 `confirm`,不设置 AI 代填项中间确认类型。
147
147
  - 只有宽泛应用名称且缺少模块、场景、任务、流程和可读需求时才问应用范围。
148
148
  - 审批、角色权限、字段、首页、看板、页面和常规流程细节不进入 `ask_human`;用户已提供时承接,未提供时基于业务推断并在最终计划统一呈现。
@@ -13,7 +13,7 @@
13
13
 
14
14
  ## 自定义导航设计
15
15
 
16
- 沿用需求阶段确认的导航归属和布局,参考 [导航壳形态目录](../../yida-nav-shell/references/nav-shell-patterns.md) 设计位置、比例、留白与选中态,代码示例只按需参考。顶部默认浮导;侧导及顶部+侧边布局写清折叠/展开、恢复宽度、拖拽边界、内容区联动和移动端收起方式。已有满意的导航保留外观,只补缺失交互。
16
+ 沿用需求阶段用户选择的导航归属,以及根据场景确定或用户指定的布局;不再询问顶部、侧边等布局选项。参考 [导航壳形态目录](../../yida-nav-shell/references/nav-shell-patterns.md) 设计位置、比例、留白与选中态,代码示例只按需参考。顶部默认浮导;侧导及顶部+侧边布局写清折叠/展开、恢复宽度、拖拽边界、内容区联动和移动端收起方式。已有满意的导航保留外观,只补缺失交互。
17
17
 
18
18
  区分三种操作:本页视图切换、保留导航并更新主内容 iframe、当前标签跨页跳转。管理入口使用 workbench,办理入口使用 submission;不要把应用级办理导航设计为每次弹抽屉。页面内新增/详情按钮才采用下面的抽屉规则。设计结果写入当前 `design.md`;Plan 模式先更新计划源事实再物化。
19
19
 
@@ -14,7 +14,7 @@
14
14
  | 悬浮胶囊 / Dock | 沉浸展示、轻量门户,减少常驻导航占位 | 3–6 项 | 底部胶囊或可收起菜单 |
15
15
  | 标签页 | 同一模块的同级视图,不替代应用主导航 | 2–8 项 | 横向滚动 |
16
16
 
17
- 数量是布局参考,不是增删业务模块的依据。需求已确认导航归属和形态后直接落地;不为这些样式再发起一轮提问。
17
+ 数量是布局参考,不是增删业务模块的依据。用户选择自定义导航后,由 Agent 按场景确定形态,用户已指定的形态优先;不把形态拆成导航提问选项,也不为这些样式再发起一轮提问。
18
18
 
19
19
  ## 通用设计要点
20
20
 
@@ -24,11 +24,11 @@
24
24
 
25
25
  | 配置项 | 值 |
26
26
  | --- | --- |
27
- | 导航类型 | <平台L型导航 / 平台顶部导航 / 平台侧边导航 / 自定义导航;必须明确选择> |
27
+ | 导航类型 | <平台L型导航 / 平台顶部导航 / 平台侧边导航 / 自定义导航;按用户选择的归属和场景确定具体布局> |
28
28
  | 是否使用平台应用导航 | <前三种为是,自定义导航为否> |
29
29
  | 页面导航配置 | <自定义导航:列出本轮全部表单、流程表单、自定义页面及需配置的其他页面,统一隐藏平台页面导航;平台导航:保留页面设置,明确独立入口例外> |
30
30
 
31
- 导航方案说明页面入口和跨页切换方式;导航配色单独说明深色或浅色。
31
+ 用户仅选择宜搭原生导航(即平台导航)或自定义导航;上表记录可执行方案,平台顶部、侧边、L 型以及自定义布局由 Agent 根据场景确定,用户已指定的布局优先。导航方案区分用户选择的归属与布局判断依据,说明页面入口和跨页切换方式;导航配色单独说明深色或浅色。
32
32
 
33
33
  ## 3. 数据结构(业务语义,不含细节 ID)
34
34
 
@@ -34,7 +34,7 @@ description: 配置已有流程表单审批规则。
34
34
  - 配置前先用 `yida-get-schema` 获取所有字段 ID
35
35
  - 流程定义 JSON 必须用结构化文件写入工具创建到 `<projectRoot>/.cache/openyida/<项目名或任务名>/`,不要在仓库根目录、系统临时目录或 `.cache/` 顶层生成 `process-definition.json` 等临时文件
36
36
  - 必须以表单 binding 只读结果证明 `processCode` 属于目标 `formUuid`;`CONFIGURE_PROCESS_OWNERSHIP_UNVERIFIED` 时停止,不得换一个 processCode 猜测重试
37
- - 命令成功必须同时返回 `verificationLevel: "PLATFORM_VIEW_VERIFIED"` 和 `platformViewVerified: true`;这只证明平台可见 view 的节点、组件、名称、顺序和审批模式,不代表平台返回了可独立验证的 `processJson`
37
+ - 命令成功必须同时返回 `verificationLevel: "PLATFORM_VIEW_VERIFIED"` 和 `platformViewVerified: true`;这只证明平台可见 view 的节点、组件、名称、顺序、审批模式及按钮权限和加签参数,不代表平台返回了可独立验证的 `processJson`
38
38
 
39
39
  ## 适用场景
40
40
 
@@ -173,6 +173,7 @@ OpenYida 会自动兼容常见别名:
173
173
  | `approver` | String/Object | 是 | 审批人。`"originator"` 表示发起人;Object 支持 `user`、`role`、`deptLeader`、`directLeader`,也可传入宜搭流程设计器的原始审批人配置 |
174
174
  | `description` | String | 否 | 节点描述 |
175
175
  | `formConfig` | Object | 否 | 字段权限配置 |
176
+ | `actions` | Object | 否 | 操作权限,包含 `normalActions` 和 `appendActions`,见下文 |
176
177
  | `routeRules` | Array | 否 | 跳转规则 |
177
178
 
178
179
  ### 办理 / 填写节点(operator)
@@ -281,6 +282,10 @@ OpenYida 会自动兼容常见别名:
281
282
  | `In` | 属于 | SelectField, RadioField |
282
283
  | `NotIn` | 不属于 | SelectField, RadioField |
283
284
 
285
+ ### 加签和转交(actions)
286
+
287
+ CLI 支持通过 `node.actions.normalActions/appendActions` 同时生成按钮和完整加签参数。需要开启加签或转交时,先阅读 [操作权限配置](references/approval-actions.md);不要仅修改原始 JSON 的 `hidden`。未配置时保持历史默认权限。
288
+
284
289
  ### 字段权限配置(formConfig)
285
290
 
286
291
  ```json
@@ -0,0 +1,48 @@
1
+ # 加签和转交(actions)
2
+
3
+ 适用于 `approval`、`operator` 和 `multiApproval` 节点。加签和转交可以通过 CLI 配置;必须由编译器同时生成设计器配置和运行配置,不能只改 `hidden` 后直接提交原始流程 JSON。
4
+
5
+ ```json
6
+ {
7
+ "type": "approval",
8
+ "key": "purchase_approval",
9
+ "name": "采购审批",
10
+ "approver": "originator",
11
+ "actions": {
12
+ "normalActions": [
13
+ { "action": "forward", "hidden": false },
14
+ {
15
+ "action": "append",
16
+ "hidden": false,
17
+ "appendPosition": ["BEFORE_APPEND", "AFTER_APPEND"],
18
+ "appendResult": "valid"
19
+ }
20
+ ],
21
+ "appendActions": [
22
+ { "action": "forward", "hidden": false },
23
+ { "action": "append", "hidden": false }
24
+ ]
25
+ }
26
+ }
27
+ ```
28
+
29
+ - `normalActions` 是普通审批人的按钮;`appendActions` 是被加签人的按钮,独立配置。数组按 `action` 部分覆盖默认值,不配置时保持历史默认:同意、拒绝显示,保存、转交、加签、退回隐藏。
30
+ - 加签位置和结果是节点级规则,在 `normalActions` 的 `append` 动作上设置。`appendPosition` 必须为非空数组,支持 `BEFORE_APPEND`(前加签)、`AFTER_APPEND`(后加签);省略时默认前加签。`appendResult` 支持 `valid`(参与审批)、`invalid`(不参与审批);省略时默认 `valid`,与设计器开启加签时一致。
31
+ - 编译器同步生成运行配置 `allowTaskAppend`、`moldList`、`isConsiderAppendedAction`、`isNeedEndTaskGroupChain`。转交使用 `forward.hidden`,无需虚构额外的转交范围配置。
32
+ - 开启被加签人的再次加签时,也须开启普通审批人的加签,以确保存在节点级加签规则;无效配置会在发布前报错。
33
+ - 兼容已有 `approver.processProps.actions/appendActions` 完整数组,并同步到设计器;`node.actions` 对对应数组优先。旧运行字段 `moldList` 和布尔类型 `isConsiderAppendedAction` 可用于补齐省略的加签参数。不要同时提供互相矛盾的两套配置。
34
+ - 发布后的回读会核对按钮是否隐藏,以及开启加签时的位置和结果设置。`PLATFORM_VIEW_VERIFIED` 仍不等于真实审批人已完成加签/转交验收;需要在获授权的测试流程中分别验证普通审批人、被加签人和移动端。
35
+
36
+
37
+ ## CLI 入口
38
+
39
+ 将以上节点放入完整流程定义的 `nodes` 数组,由同一编译器处理:
40
+
41
+ ```bash
42
+ openyida configure-process APP_XXX FORM_XXX .cache/openyida/process/process-with-actions.json
43
+ openyida create-process APP_XXX --formUuid FORM_XXX .cache/openyida/process/process-with-actions.json
44
+ ```
45
+
46
+ 已有流程使用第一条;普通表单首次转流程可使用第二条。配置命令会替换整张流程图,必须保留完整节点与分支;发现已有版本时需按主技能的替换要求使用 `--replace`。本功能不提供 `--append`、`--forward` 等独立开关,也不用于执行某一待办实例的加签或转交操作。
47
+
48
+ 用 `openyida configure-process --help`、`openyida create-process --help` 查看入口;命令发现 JSON 也会返回加签/转交配置提示。发布后若返回 `PROCESS_PLATFORM_VIEW_ACTION_MISMATCH` 诊断,不得将已发布但未验证的流程当作成功,也不要直接重试写入。
@@ -32,7 +32,7 @@ description: 识别并读取需求来源,理解和澄清用户需求,输出
32
32
  | `pageScenes` | 已确认的页面与表单范围,数组;记录稳定 key、name、kind、purpose 及已有细项 |
33
33
  | `intake` | 首次搭建判断、来源详细程度、搭建方式和需求确认状态 |
34
34
  | `visualSelection` | 已确认风格及其主题、主色、导航明暗映射;Plan 初始化前必须含非空 `themeId` |
35
- | `navigation` | 应用导航决策:`type` 只能是 `platform-l-shape/platform-top/platform-side/custom`,并记录 `source/reason`;自定义导航增加 `variant: side/top/mixed/dock`;未决时 type 为 null,规划前补齐 |
35
+ | `navigation` | 用户仅选择宜搭原生导航(即平台导航)或自定义导航;Agent 按场景确定布局并在 `reason` 区分选择与推断。应用导航决策:`type` 只能是 `platform-l-shape/platform-top/platform-side/custom`,并记录 `source/reason`;自定义导航增加 `variant: side/top/mixed/dock`;未决时 type 为 null,规划前补齐 |
36
36
  | `resourceContext` | 已确认可复用的 app/page/form/process 业务上下文,不写猜测 ID |
37
37
  | `explicitScope` | 用户明确指定的页面、表单、流程、报表、导航项和本轮交付;明确窄范围时写对应数组及 `allowInferredResources:false`,没有时为 `null` |
38
38
  | `brandHints` / `colorHints` | 明确的品牌、参考页面、已有主题、偏好色与避用色 |
@@ -14,37 +14,46 @@
14
14
 
15
15
  ## 2. 确认首次搭建的未决事项
16
16
 
17
- 先分析再提问。用户已明确的信息直接采用;同一次搭建已回答的问题直接复用。首次提问必须在同一轮一次性收集所有尚未明确的搭建方式(Fast / Plan)、业务模块、页面与表单范围、导航归属(平台或自定义)、导航布局和设计风格。不要先问模块、导航和风格,收到回答后再另起一轮补问模式、导航位置或页面。
17
+ 先分析再提问。用户已明确的信息直接采用;同一次搭建已回答的问题直接复用。首次提问必须在同一轮一次性收集所有尚未明确的搭建方式(Fast / Plan)、业务模块、页面与表单范围、导航归属(宜搭原生导航或自定义导航)和设计风格。不要先问模块、导航和风格,收到回答后再另起一轮补问模式、导航位置或页面。
18
18
 
19
19
  先锁定用户已经明确的搭建方式:用户写明 `Plan`、先出 PRD/方案并确认后搭建时,立即把草稿的 `intake.designMode` 设为 `plan`;用户写明 `Fast` 或直接快速搭建时设为 `fast`。已锁定的搭建方式不再进入提问选项。后续 `ask_human` 只补齐其他未决事项,合并回答时必须保留已有 `intake.designMode`;不能因为回答里没有重复提到模式、结构化问题被合并、上下文压缩或重新生成 brief 而回退到 Fast。只有用户明确说“改用 Fast/Plan”时才能切换,并以最后一次明确选择为准。
20
20
 
21
- 将整组问题放在一次结构化提问调用中。宿主限制问题数量时,合并为“搭建方式”“导航归属与布局”“业务模块、页面与风格”等复合问题;选项数量或多选能力不足时,用允许自由输入的同组问题列出完整选择。不得因为工具数量限制而拆成多轮。只有回答遗漏、互相冲突或产生新的关键业务疑问时才针对性追问。
21
+ 将整组问题放在一次结构化提问调用中。宿主限制问题数量时,合并为“搭建方式”“导航归属”“业务模块、页面与风格”等复合问题;选项数量或多选能力不足时,用允许自由输入的同组问题列出完整选择。不得因为工具数量限制而拆成多轮。只有回答遗漏、互相冲突或产生新的关键业务疑问时才针对性追问。
22
22
 
23
23
  | 事项 | 何时询问 | 用户可见问题与选项 |
24
24
  | --- | --- | --- |
25
25
  | 搭建方式 | 用户未选择方式,且已提供详细计划 | “是否根据你提供的需求,整理一份应用 PRD 计划供你确认?” 是:计划更清晰详细,耗时更长;否:按现有需求快速搭建 |
26
26
  | 搭建方式 | 用户未选择方式,且需求细节不足 | “希望用哪种方式搭建?” Fast(快速搭建);Plan(先生成 PRD,确认后再搭建,耗时更长) |
27
- | 导航归属与布局 | 用户未明确平台或自定义导航及布局 | “应用导航使用平台导航还是自定义导航,采用哪种布局?” 一次列出平台L型导航、平台顶部导航、平台侧边导航、自定义左侧菜单、自定义顶部浮导、自定义顶部加左侧菜单、自定义底部悬浮菜单;每个选项必须附上下表的说明;自定义布局选项同时注明在自定义页面中实现,不使用宜搭原生导航。已明确归属时只列该归属下的布局,在同一轮收集 |
27
+ | 导航归属 | 用户未明确宜搭原生导航或自定义导航 | “你希望应用使用哪种导航菜单?” 仅提供“宜搭原生导航”和“自定义导航”两个选项,并附下表说明。宜搭原生导航就是平台导航;归属已明确时不再询问,顶部、侧边、L 型等布局根据场景选择,不进入 `ask_human` |
28
28
  | 业务模块 | 用户未明确业务范围 | “应用需要包含哪些业务模块?” 根据业务列出具体模块及用途,支持多选或自行补充;不要只用“标准4-5模块”代替具体模块清单 |
29
29
  | 应用设计风格 | 用户未提及 | 根据业务提供简短的配色与界面风格选项,并允许描述自己的偏好;已有品牌或参考图时沿用 |
30
30
  | 页面范围 | 用户未明确页面与表单 | “这次需要哪些页面和表单?” 同轮按候选业务模块列出页面和表单及用途,支持多选或自行补充,例如“工作台:汇总待办”“活动报名表:填写报名信息”;可与业务模块合成一题,不等模块回答后再问,也不要只用“标准4页”代替页面清单 |
31
31
 
32
32
  ### 导航选项说明
33
33
 
34
- 选项名称与说明一起呈现;支持 `description` 时写入该字段,否则在选项名称后用括号备注。平台侧导航与平台侧边导航指同一种布局。
34
+ 导航 `ask_human` 仅有以下两个选项,不把布局或呈现样式拆成选项,也不追加布局问题。选项名称与说明一起呈现;支持 `description` 时写入该字段,否则在选项名称后用括号备注。
35
35
 
36
36
  | 导航选项 | 必须附带的说明 |
37
37
  | --- | --- |
38
- | 平台L型导航 | 使用宜搭原生导航,顶部与左侧组合布局 |
39
- | 平台侧导航 | 使用宜搭原生导航,菜单位于左侧 |
40
- | 平台顶部导航 | 使用宜搭原生导航,菜单位于顶部 |
41
- | 自定义导航 | 在自定义页面中实现,不使用宜搭原生导航 |
38
+ | 宜搭原生导航 | 使用宜搭自带的导航菜单。 |
39
+ | 自定义导航 | 在自定义页面里定制菜单的样式和操作方式,替代宜搭自带的导航菜单。 |
42
40
 
43
- 自定义导航按本轮候选拆为左侧、顶部浮导、顶部加左侧、底部悬浮菜单时,每个选项都保留上述自定义说明,并补充所在位置;侧导及顶部+侧导注明支持折叠/展开和拖拽调宽。归属与布局仍在同一轮确定,不先选大类再另起一轮询问。
41
+ 需要解释布局时,统一使用:“菜单放在顶部、左侧,还是顶部加左侧,会根据应用场景安排,你不用再选。” 面向用户不使用“导航归属”“布局枚举”等内部术语。
44
42
 
45
- 模式、平台或自定义导航归属及顶部/侧边等布局选项平等说明,不推荐、不预选,不在“自定义导航”大类上添加“(推荐)”。自定义顶部导航的呈现样式默认推荐浮导:用户已选自定义顶部且未指定样式时直接采用;若同轮展示样式选择,使用“浮导(推荐)”“贴边通栏”。不为此追加一轮提问;用户已明确通栏等样式时沿用。详细计划问题的“是”对应 Plan,“否”对应 Fast;两者都复用已有需求。用户明确要求代为决定时,记录选择和依据。
43
+ 模式和导航归属选项平等说明,不推荐、不预选,不添加“(推荐)”或“默认”。详细计划问题的“是”对应 Plan,“否”对应 Fast;两者都复用已有需求。用户明确要求代为决定时,记录选择和依据。
46
44
 
47
- 展示自定义侧边导航或自定义顶部+侧边导航选项时,在说明中注明“支持折叠/展开和拖拽调宽”。选择后将这两项作为默认必需交互写入 PRD/design,不追加确认问题;UI 按设计实现,不要求套用示例外观。
45
+ ### 根据场景确定导航布局
46
+
47
+ 用户只选择导航归属。Agent 在该归属内,结合业务模块数量、层级、切换频率、内容宽度和目标设备确定布局;用户已明确布局或提供参考时优先沿用。用户只说“平台导航”视为已选“宜搭原生导航”;只指定顶部、侧边或 L 型但未明确归属时,保留布局要求,仅询问上述两个归属选项。
48
+
49
+ | 场景 | 宜搭原生导航(平台导航) | 自定义导航 |
50
+ | --- | --- | --- |
51
+ | 模块少、层级浅,表格或看板需要较宽内容区 | 顶部:`platform-top` | 顶部:`custom` + `variant: top` |
52
+ | 模块或分组多,需要频繁跨模块操作 | 侧边:`platform-side` | 左侧:`custom` + `variant: side` |
53
+ | 业务域与域内模块形成两级导航 | L 型:`platform-l-shape` | 顶部+侧边:`custom` + `variant: mixed` |
54
+ | 少量高频入口的移动端轻量门户或沉浸展示 | 按实际层级选顶部或侧边 | 可选底部悬浮菜单:`custom` + `variant: dock` |
55
+
56
+ 这些是布局判断依据,不是固定模板或用户问卷;不得为适配布局增删业务模块,也不能因布局偏好改变已选导航归属。自定义形态细化参考 [导航壳形态目录](../../yida-nav-shell/references/nav-shell-patterns.md)。自定义顶部未指定样式时默认浮导;侧边及顶部+侧边在 PRD/design 中写入折叠/展开和拖拽调宽,不另行提问。
48
57
 
49
58
  额外只补问影响搭建的关键疑问:给谁使用、主要解决什么问题、创建还是复用应用、数据可见范围、关键审批规则、必要的外部数据来源。能够从需求和资源上下文确定的内容直接记录;一般字段和布局细节交给后续规划。
50
59
 
@@ -63,7 +72,7 @@
63
72
  - 已有确认记录且需求未变化时直接复用;后续只补充已确定的视觉映射或更新用户变更涉及的字段,不因阶段切换重写全文或重新生成页面 key。
64
73
 
65
74
  - `intake` 记录 `firstBuild`、`sourceDetail`(`detailed/brief`)、`designMode`(`fast/plan`)、`confirmed`。未决事项处理完毕后才将 confirmed 设为 true。
66
- - `navigation` 记录 `type/source/reason`;自定义导航的 `variant` 为 `side/top/mixed/dock`。顶部浮导使用 `top`,默认浮导或用户指定的通栏样式写入 `reason`,PRD 与视觉设计共同沿用;`dock` 表示底部悬浮胶囊。导航明暗由视觉选择记录。
75
+ - `navigation` 记录 `type/source/reason`;归属由用户选择时 `source` 为 `user_selected`,`reason` 分别写清用户选择的归属、Agent 根据场景确定的布局及依据,不将推断布局记成用户指定。归属也由用户授权代选时标记 `ai_default`。保存 confirmed brief 前按上表补齐可执行的布局枚举,不把中文选项名或笼统的 `platform` 写入 `type`,不因缺少布局再次提问;自定义导航的 `variant` 为 `side/top/mixed/dock`。顶部浮导使用 `top`,默认浮导或用户指定的通栏样式写入 `reason`,PRD 与视觉设计共同沿用;`dock` 表示底部悬浮胶囊。导航明暗由视觉选择记录。
67
76
  - 业务模块答案写入 `coreFunctions/businessObjects/explicitScope`;与页面范围合问时,分别保存模块事实与 `pageScenes`,不要只保留模块或页面数量。
68
77
  - `targetUsers/businessGoals/coreFunctions/businessObjects/pageScenes` 一律保持数组类型;单个目标也写成单元素数组。Plan 的 `visualSelection.themeId` 在保存 confirmed brief 前补齐;导航 type 使用 `platform-l-shape/platform-top/platform-side/custom` 精确枚举。
69
78
  - 用户先用“应用/系统”描述背景、后续又明确“只完成/随后完成一个”具体资源并交付时,以具体资源作为本轮执行边界。`explicitScope` 写对应的 forms/processes/reports/pages/delivery 数组并设置 `allowInferredResources:false`;不把“应用”自动扩展为示例数据、工作台、自定义列表或其他未点名资源。
@@ -79,7 +88,7 @@ Plan 的视觉选择直接使用下面的对象结构,不另写 `styleDescript
79
88
  "themeId": "airy-structured-clarity",
80
89
  "visualDirection": {"label": "轻盈结构", "description": "清晰、简洁、适合持续业务操作", "source": "requirement"},
81
90
  "colorStrategy": {"primaryColor": "#1677FF", "primaryColorName": "专业蓝", "source": "requirement", "usage": "主操作与选中态", "surfaceTone": "brand-tinted"},
82
- "navigationStyle": {"structure": "side", "tone": "light", "source": "requirement", "selectionReason": "沿用平台侧边导航"}
91
+ "navigationStyle": {"structure": "side", "tone": "light", "source": "ai_default", "selectionReason": "用户选择宜搭原生导航;模块分组较多,采用平台侧边布局与浅色导航"}
83
92
  }
84
93
  ```
85
94