openyida 2026.9.24-beta.0 → 2026.9.30

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 (122) hide show
  1. package/README.md +2 -9
  2. package/bin/yida.js +1 -15
  3. package/lib/app/application-entry-urls.js +27 -1
  4. package/lib/app/create-page.js +5 -9
  5. package/lib/app/get-form-config.js +6 -0
  6. package/lib/app/publish.js +20 -1
  7. package/lib/auth/token-auth.js +22 -75
  8. package/lib/core/agent-capabilities.js +1 -0
  9. package/lib/core/command-manifest.js +32 -4
  10. package/lib/core/locales/en.js +2 -14
  11. package/lib/core/locales/zh.js +2 -14
  12. package/lib/design-plan/entry-navigation.js +10 -1
  13. package/lib/design-plan/init.js +2 -1
  14. package/lib/design-plan/materialize.js +6 -4
  15. package/package.json +1 -1
  16. package/yida-skills/skills/yida-app/SKILL.md +1 -1
  17. package/yida-skills/skills/yida-app/references/entry-navigation.md +2 -0
  18. package/yida-skills/skills/yida-app/workflow/step-6-main-page.md +1 -1
  19. package/yida-skills/skills/yida-app/workflow/step-7-page-code.md +1 -1
  20. package/yida-skills/skills/yida-app/workflow/step-8-publish-navigation.md +2 -2
  21. package/yida-skills/skills/yida-app/workflow/step-9-output-finish.md +3 -3
  22. package/yida-skills/skills/yida-canvas-custom-page/references/canvas-style-implementation-guide.md +5 -4
  23. package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +6 -6
  24. package/yida-skills/skills/yida-create-form-page/SKILL.md +3 -1
  25. package/yida-skills/skills/yida-create-form-page/references/field-definition-guide.md +16 -0
  26. package/yida-skills/skills/yida-create-form-page/references/form-field-properties.md +7 -5
  27. package/yida-skills/skills/yida-create-page/SKILL.md +1 -1
  28. package/yida-skills/skills/yida-dashboard/SKILL.md +1 -1
  29. package/yida-skills/skills/yida-design/SKILL.md +5 -3
  30. package/yida-skills/skills/yida-design/references/application-style-library.md +1 -1
  31. package/yida-skills/skills/yida-design/references/application-theme-consistency.md +4 -2
  32. package/yida-skills/skills/yida-design/references/native-form-styles.md +58 -0
  33. package/yida-skills/skills/yida-design/references/navigation-decision.md +2 -0
  34. package/yida-skills/skills/yida-design/references/page-quality-gates.md +6 -4
  35. package/yida-skills/skills/yida-design/references/theme/app-custom-theme-template.css +524 -186
  36. package/yida-skills/skills/yida-design/references/theme/theme-token-presets.md +3 -1
  37. package/yida-skills/skills/yida-design/references/visual-scaffold-recipes.md +13 -40
  38. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/SKILL.md +1 -1
  39. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/page-patterns.md +6 -4
  40. package/yida-skills/skills/yida-design/templates/design-themes/README.md +6 -3
  41. package/yida-skills/skills/yida-design/templates/design-themes/app-amber/app_theme.css +428 -189
  42. package/yida-skills/skills/yida-design/templates/design-themes/app-amber/design.md +12 -10
  43. package/yida-skills/skills/yida-design/templates/design-themes/app-editorial/app_theme.css +428 -189
  44. package/yida-skills/skills/yida-design/templates/design-themes/app-editorial/design.md +12 -10
  45. package/yida-skills/skills/yida-design/templates/design-themes/app-executive/app_theme.css +429 -190
  46. package/yida-skills/skills/yida-design/templates/design-themes/app-executive/design.md +13 -11
  47. package/yida-skills/skills/yida-design/templates/design-themes/app-finance/app_theme.css +427 -188
  48. package/yida-skills/skills/yida-design/templates/design-themes/app-finance/design.md +11 -9
  49. package/yida-skills/skills/yida-design/templates/design-themes/app-glass/app_theme.css +428 -189
  50. package/yida-skills/skills/yida-design/templates/design-themes/app-glass/design.md +12 -10
  51. package/yida-skills/skills/yida-design/templates/design-themes/app-graphite/app_theme.css +429 -190
  52. package/yida-skills/skills/yida-design/templates/design-themes/app-graphite/design.md +13 -11
  53. package/yida-skills/skills/yida-design/templates/design-themes/app-line/app_theme.css +427 -188
  54. package/yida-skills/skills/yida-design/templates/design-themes/app-line/design.md +11 -9
  55. package/yida-skills/skills/yida-design/templates/design-themes/app-neon/app_theme.css +430 -191
  56. package/yida-skills/skills/yida-design/templates/design-themes/app-neon/design.md +14 -12
  57. package/yida-skills/skills/yida-design/templates/design-themes/app-nordic/app_theme.css +427 -188
  58. package/yida-skills/skills/yida-design/templates/design-themes/app-nordic/design.md +11 -9
  59. package/yida-skills/skills/yida-design/templates/design-themes/app-paper/app_theme.css +428 -189
  60. package/yida-skills/skills/yida-design/templates/design-themes/app-paper/design.md +12 -10
  61. package/yida-skills/skills/yida-design/templates/design-themes/app-platinum/app_theme.css +427 -188
  62. package/yida-skills/skills/yida-design/templates/design-themes/app-platinum/design.md +11 -9
  63. package/yida-skills/skills/yida-design/templates/design-themes/app-plum/app_theme.css +428 -189
  64. package/yida-skills/skills/yida-design/templates/design-themes/app-plum/design.md +12 -10
  65. package/yida-skills/skills/yida-design/templates/design-themes/app-pop/app_theme.css +428 -189
  66. package/yida-skills/skills/yida-design/templates/design-themes/app-pop/design.md +12 -10
  67. package/yida-skills/skills/yida-design/templates/design-themes/app-sage/app_theme.css +428 -189
  68. package/yida-skills/skills/yida-design/templates/design-themes/app-sage/design.md +12 -10
  69. package/yida-skills/skills/yida-design/templates/design-themes/app-teal/app_theme.css +427 -188
  70. package/yida-skills/skills/yida-design/templates/design-themes/app-teal/design.md +11 -9
  71. package/yida-skills/skills/yida-design/templates/design-themes/app-terminal/app_theme.css +429 -190
  72. package/yida-skills/skills/yida-design/templates/design-themes/app-terminal/design.md +13 -11
  73. package/yida-skills/skills/yida-design/templates/design-themes/app-ticket/app_theme.css +428 -189
  74. package/yida-skills/skills/yida-design/templates/design-themes/app-ticket/design.md +12 -10
  75. package/yida-skills/skills/yida-design/templates/design-themes/app-wire/app_theme.css +427 -188
  76. package/yida-skills/skills/yida-design/templates/design-themes/app-wire/design.md +11 -9
  77. package/yida-skills/skills/yida-design/templates/design-themes/basic-tokens.json +1 -0
  78. package/yida-skills/skills/yida-design/templates/design-themes/dark-inset-hairline/app_theme.css +422 -183
  79. package/yida-skills/skills/yida-design/templates/design-themes/dark-inset-hairline/design.md +3 -1
  80. package/yida-skills/skills/yida-design/templates/design-themes/dark-rail-fine-lines/app_theme.css +422 -183
  81. package/yida-skills/skills/yida-design/templates/design-themes/dark-rail-fine-lines/design.md +4 -2
  82. package/yida-skills/skills/yida-design/templates/design-themes/dark-rail-signal-panels/app_theme.css +422 -183
  83. package/yida-skills/skills/yida-design/templates/design-themes/dark-rail-signal-panels/design.md +3 -1
  84. package/yida-skills/skills/yida-design/templates/design-themes/free-creative/app_theme.css +419 -180
  85. package/yida-skills/skills/yida-design/templates/design-themes/free-creative/design.md +3 -1
  86. package/yida-skills/skills/yida-design/templates/design-themes/graphite-bevel-grid/app_theme.css +422 -183
  87. package/yida-skills/skills/yida-design/templates/design-themes/graphite-bevel-grid/design.md +3 -1
  88. package/yida-skills/skills/yida-design/templates/design-themes/hairline-shared-bands/app_theme.css +422 -183
  89. package/yida-skills/skills/yida-design/templates/design-themes/hairline-shared-bands/design.md +3 -1
  90. package/yida-skills/skills/yida-design/templates/design-themes/hairline-soft-blocks/app_theme.css +422 -183
  91. package/yida-skills/skills/yida-design/templates/design-themes/hairline-soft-blocks/design.md +5 -3
  92. package/yida-skills/skills/yida-design/templates/design-themes/index.json +0 -13
  93. package/yida-skills/skills/yida-design/templates/design-themes/ink-glow-soft-panels/app_theme.css +421 -182
  94. package/yida-skills/skills/yida-design/templates/design-themes/ink-glow-soft-panels/design.md +2 -0
  95. package/yida-skills/skills/yida-design/templates/design-themes/inset-frame-pixel-rhythm/app_theme.css +421 -182
  96. package/yida-skills/skills/yida-design/templates/design-themes/inset-frame-pixel-rhythm/design.md +2 -0
  97. package/yida-skills/skills/yida-design/templates/design-themes/outlined-texture-duotone/app_theme.css +422 -183
  98. package/yida-skills/skills/yida-design/templates/design-themes/outlined-texture-duotone/design.md +3 -1
  99. package/yida-skills/skills/yida-design/templates/design-themes/soft-inset-surfaces/app_theme.css +421 -182
  100. package/yida-skills/skills/yida-design/templates/design-themes/soft-inset-surfaces/design.md +2 -0
  101. package/yida-skills/skills/yida-design/templates/design-themes/soft-outline-rhythm/app_theme.css +421 -182
  102. package/yida-skills/skills/yida-design/templates/design-themes/soft-outline-rhythm/design.md +2 -0
  103. package/yida-skills/skills/yida-design/templates/design-themes/soft-rail-bold-band/app_theme.css +422 -183
  104. package/yida-skills/skills/yida-design/templates/design-themes/soft-rail-bold-band/design.md +6 -4
  105. package/yida-skills/skills/yida-design/templates/design-themes/warm-canvas-contrast-panels/app_theme.css +422 -183
  106. package/yida-skills/skills/yida-design/templates/design-themes/warm-canvas-contrast-panels/design.md +5 -3
  107. package/yida-skills/skills/yida-design/templates/design-themes/warm-rail-muted-panels/app_theme.css +422 -183
  108. package/yida-skills/skills/yida-design/templates/design-themes/warm-rail-muted-panels/design.md +4 -2
  109. package/yida-skills/skills/yida-design/workflow/step-2-theme-system.md +3 -1
  110. package/yida-skills/skills/yida-design/workflow/step-4-wireframe-interaction.md +5 -5
  111. package/yida-skills/skills/yida-page-config/SKILL.md +2 -2
  112. package/yida-skills/skills/yida-prd/references/app/role-journey.md +1 -1
  113. package/yida-skills/skills/yida-prd/references/plan-content.md +1 -1
  114. package/yida-skills/skills/yida-prd/workflow/output-prd.md +5 -5
  115. package/yida-skills/skills/yida-prd/workflow/plan-business.md +2 -0
  116. package/yida-skills/skills/yida-prd/workflow/step-2-information-architecture.md +12 -3
  117. package/yida-skills/skills/yida-publish-page/SKILL.md +8 -1
  118. package/yida-skills/skills/yida-requirement-analysis/references/experience-groups.md +3 -3
  119. package/lib/auth/oauth-device.js +0 -311
  120. package/yida-skills/skills/yida-design/templates/design-themes/soft-spectral-panels/app_theme.css +0 -511
  121. package/yida-skills/skills/yida-design/templates/design-themes/soft-spectral-panels/design.md +0 -383
  122. package/yida-skills/skills/yida-design/templates/design-themes/soft-spectral-panels/form-layout.json +0 -13
package/README.md CHANGED
@@ -74,17 +74,10 @@ openyida login
74
74
 
75
75
  OpenYida login defaults to OAuth token mode. It opens the DingTalk OAuth authorization page, receives the local loopback callback, exchanges `code` / `authCode` with the Yida server, and stores `access_token` / `refresh_token` in the user auth store when available. If the user auth store is not writable, OpenYida explicitly falls back to the current project cache and reports that narrower persistence scope.
76
76
 
77
- In a sandbox, headless shell, or CI dashboard where the loopback callback cannot complete, use the device authorization flow. Open the displayed verification URL on any device and enter the user code. `--env-hint` optionally pins the intended environment profile at authorization time:
78
-
79
- ```bash
80
- openyida login --device
81
- openyida login --device --env-hint pre
82
- ```
83
-
84
77
  When the user names a target Yida entry URL, pass it to the login command so OpenYida can select the matching environment and auth profile. For example:
85
78
 
86
79
  ```bash
87
- openyida login https://<your-yida-entry-url>/
80
+ openyida login https://yida-group.alibaba-inc.com/
88
81
  openyida login --alibaba
89
82
  ```
90
83
 
@@ -390,7 +383,7 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
390
383
 
391
384
  | Command | Description |
392
385
  |---------|-------------|
393
- | `openyida login [target-url] [--env <name>\|--intl\|--overseas\|--global\|--yidaapps\|--alibaba] [--client-id <clientId>] [--endpoint <url>] [--no-browser] [--device] [--env-hint <name>]` | Login with OAuth token mode |
386
+ | `openyida login [target-url] [--env <name>\|--intl\|--overseas\|--global\|--yidaapps\|--alibaba] [--client-id <clientId>] [--endpoint <url>] [--no-browser]` | Login with OAuth token mode |
394
387
  | `openyida logout` | Logout / unbind current project auth |
395
388
  | `openyida auth <status\|login\|refresh\|logout\|profiles\|profile switch>` | Token login state and profile management |
396
389
  | `openyida org <list\|switch> [--json] [--corp-id <corpId>]` | Organization management (list / switch existing profiles first) |
package/bin/yida.js CHANGED
@@ -193,7 +193,6 @@ function printLoginResult(result) {
193
193
 
194
194
  if (!result) {
195
195
  console.log(JSON.stringify({
196
- type: 'login_result',
197
196
  ok: false,
198
197
  status: 'login_failed',
199
198
  can_auto_use: false,
@@ -206,7 +205,6 @@ function printLoginResult(result) {
206
205
  ? !!result.ok
207
206
  : (result.can_auto_use !== false && !!result.access_token);
208
207
  const summary = {
209
- type: 'login_result',
210
208
  ok: tokenUsable,
211
209
  status: result.status || (tokenUsable ? 'ok' : 'token_not_issued'),
212
210
  auth_mode: 'token',
@@ -281,7 +279,6 @@ function getFirstPositionalArg(cliArgs, startIndex = 0) {
281
279
  '--login-url',
282
280
  '--profile',
283
281
  '--user-id',
284
- '--env-hint',
285
282
  ]);
286
283
  for (let index = startIndex; index < cliArgs.length; index++) {
287
284
  const arg = cliArgs[index];
@@ -389,7 +386,6 @@ function applyLoginEnvironmentFlags(cliArgs, options = {}) {
389
386
  '--client-id',
390
387
  '--profile',
391
388
  '--user-id',
392
- '--env-hint',
393
389
  ]);
394
390
  const targetUrlFlags = new Set([
395
391
  '--endpoint',
@@ -541,23 +537,13 @@ function printLoginHelp() {
541
537
  }
542
538
 
543
539
  function buildTokenLoginOptions(loginArgs) {
544
- const device = loginArgs.includes('--device');
545
540
  return {
546
541
  clientId: getArgValue(loginArgs, '--client-id'),
547
542
  corpId: getArgValue(loginArgs, '--corp-id'),
548
543
  userId: getArgValue(loginArgs, '--user-id'),
549
544
  authProfile: getArgValue(loginArgs, '--profile'),
550
545
  quiet: process.env.YIDA_QUIET === '1' || loginArgs.includes('--quiet'),
551
- noBrowser: device || loginArgs.includes('--no-browser'),
552
- device,
553
- envHint: getArgValue(loginArgs, '--env-hint'),
554
- // Machine-readable progress events for agent runtimes (sandbox browsers,
555
- // CI dashboards). Emits one JSON line per state transition.
556
- onDeviceState: (state) => {
557
- if (device) {
558
- process.stdout.write(JSON.stringify({ type: 'device_login_state', ...state }) + '\n');
559
- }
560
- },
546
+ noBrowser: loginArgs.includes('--no-browser'),
561
547
  };
562
548
  }
563
549
 
@@ -28,4 +28,30 @@ function buildApplicationEntryUrls({ appType, baseUrl, systemLink }) {
28
28
  return { appUrl: workbenchUrl, workbenchUrl, adminUrl: `${origin}/${appType}/admin${suffix}` };
29
29
  }
30
30
 
31
- module.exports = { buildApplicationEntryUrls };
31
+ function readRenderNav(config) {
32
+ const value = config && Object.prototype.hasOwnProperty.call(config, 'renderNav')
33
+ ? config.renderNav : config?.isRenderNav;
34
+ if (value === false || value === 'false') {return false;}
35
+ if (value === true || value === 'true') {return true;}
36
+ return null;
37
+ }
38
+
39
+ function buildDisplayPageEntryUrls({ appType, formUuid, baseUrl, config, formType = config?.formType }) {
40
+ if (formType !== 'display' || !/^FORM[-_][A-Za-z0-9_-]+$/.test(formUuid || '')) {return {};}
41
+ if (config?.formType && config.formType !== 'display') {return {};}
42
+ if ((config?.appType && config.appType !== appType) || (config?.formUuid && config.formUuid !== formUuid)) {return {};}
43
+ const appUrls = buildApplicationEntryUrls({ appType, baseUrl });
44
+ if (!appUrls.workbenchUrl) {return {};}
45
+ const origin = new URL(appUrls.workbenchUrl).origin;
46
+ const renderNav = readRenderNav(config);
47
+ const workbenchUrl = `${origin}/${appType}/workbench/${formUuid}`;
48
+ const standaloneUrl = renderNav === false ? `${origin}/${appType}/custom/${formUuid}` : null;
49
+ return {
50
+ url: standaloneUrl || workbenchUrl,
51
+ workbenchUrl,
52
+ standaloneUrl,
53
+ navigationVerification: { renderNav, verified: renderNav !== null },
54
+ };
55
+ }
56
+
57
+ module.exports = { buildApplicationEntryUrls, buildDisplayPageEntryUrls, readRenderNav };
@@ -20,6 +20,7 @@ const { parseOpenOption, withBrowserHandoff } = require('../core/browser-handoff
20
20
  const { throwCommandError } = require('../core/command-errors');
21
21
  const { buildApiPath } = require('./create-form/api-path');
22
22
  const { fetchFormSchemaInfo } = require('./get-form-config');
23
+ const { readRenderNav, buildDisplayPageEntryUrls } = require('./application-entry-urls');
23
24
 
24
25
  function parseArgs(args) {
25
26
  const openOption = parseOpenOption(args);
@@ -138,14 +139,6 @@ function buildPageInfoPostData(formUuid, pageName, isRenderNav, csrfToken, curre
138
139
  return querystring.stringify(payload);
139
140
  }
140
141
 
141
- function readRenderNav(config) {
142
- const value = config && Object.prototype.hasOwnProperty.call(config, 'renderNav')
143
- ? config.renderNav : config?.isRenderNav;
144
- if (value === false || value === 'false') {return false;}
145
- if (value === true || value === 'true') {return true;}
146
- return null;
147
- }
148
-
149
142
  async function configureNavigationVisibility(authRef, appType, pageId, pageName, isRenderNav) {
150
143
  const verification = { requestedRenderNav: isRenderNav, renderNav: null, verified: false, attempts: 0 };
151
144
  let currentConfig = null;
@@ -268,7 +261,10 @@ async function run(args) {
268
261
  navigationVerification = await configureNavigationVisibility(authRef, appType, pageId, pageName, false);
269
262
  if (navigationVerification.verified) {
270
263
  chalkSuccess(t('create_page.dashboard_config_ok'));
271
- pageUrl = `${authRef.baseUrl}/${appType}/custom/${pageId}?isRenderNav=false`;
264
+ pageUrl = buildDisplayPageEntryUrls({
265
+ appType, formUuid: pageId, baseUrl: authRef.baseUrl,
266
+ config: { formType: 'display', renderNav: navigationVerification.renderNav },
267
+ }).standaloneUrl;
272
268
  } else {
273
269
  dashboardConfigWarning = navigationVerification.error;
274
270
  warn(t('create_page.dashboard_config_failed', dashboardConfigWarning));
@@ -21,6 +21,7 @@ const {
21
21
  const { t } = require('../core/i18n');
22
22
  const { banner, step, label, info, success, error, result } = require('../core/chalk');
23
23
  const { throwCommandError } = require('../core/command-errors');
24
+ const { buildDisplayPageEntryUrls } = require('./application-entry-urls');
24
25
 
25
26
  // 常用配置字段白名单(人读摘要),--json 会输出完整对象
26
27
  const SUMMARY_FIELDS = [
@@ -121,6 +122,9 @@ async function run(args) {
121
122
  }
122
123
 
123
124
  const cfg = resp.content || resp.result || resp.data || {};
125
+ const entryUrls = buildDisplayPageEntryUrls({
126
+ appType: parsed.appType, formUuid: parsed.formUuid, baseUrl: authRef.baseUrl, config: cfg,
127
+ });
124
128
  success(t('get_form_config.read_ok'));
125
129
 
126
130
  if (parsed.json) {
@@ -129,6 +133,7 @@ async function run(args) {
129
133
  appType: parsed.appType,
130
134
  formUuid: parsed.formUuid,
131
135
  config: cfg,
136
+ ...entryUrls,
132
137
  }, null, 2));
133
138
  return;
134
139
  }
@@ -138,6 +143,7 @@ async function run(args) {
138
143
  appType: parsed.appType,
139
144
  formUuid: parsed.formUuid,
140
145
  title: extractTitle(cfg.title),
146
+ ...entryUrls,
141
147
  };
142
148
  SUMMARY_FIELDS.forEach((key) => {
143
149
  if (cfg[key] !== undefined) {
@@ -37,6 +37,8 @@ const { buildPageFile, shouldBuildPageSource } = require('./page-compat');
37
37
  const { warnLargePageSource } = require('./page-size-guard');
38
38
  const { assertPageTemplateCustomized } = require('./page-template-guard');
39
39
  const { fetchFormPageList } = require('./form-navigation');
40
+ const { fetchFormSchemaInfo } = require('./get-form-config');
41
+ const { buildDisplayPageEntryUrls } = require('./application-entry-urls');
40
42
  const { parseOpenOption, withBrowserHandoff } = require('../core/browser-handoff');
41
43
  const { assertNoEmojiInValue } = require('../core/no-emoji-guard');
42
44
  const { loadPageSource } = require('./services/page-source-loader');
@@ -834,7 +836,22 @@ async function runMain(argv) {
834
836
 
835
837
  baseUrl = authRef.baseUrl || baseUrl;
836
838
 
837
- const pageUrl = `${baseUrl}/${appType}/workbench/${savedFormUuid}`;
839
+ let pageConfig = null;
840
+ let navigationWarning = null;
841
+ try {
842
+ const configResponse = await fetchFormSchemaInfo(authRef, appType, savedFormUuid);
843
+ if (!configResponse || configResponse.success === false || configResponse.__needLogin || configResponse.__csrfExpired) {
844
+ throw new Error(configResponse?.errorMsg || t('common.request_failed'));
845
+ }
846
+ pageConfig = configResponse.content || configResponse.result || configResponse.data || {};
847
+ } catch (configError) {
848
+ navigationWarning = configError.message || t('common.request_failed');
849
+ }
850
+ const entryUrls = buildDisplayPageEntryUrls({
851
+ appType, formUuid: savedFormUuid, baseUrl: authRef.baseUrl || baseUrl,
852
+ config: pageConfig, formType: 'display',
853
+ });
854
+ const pageUrl = entryUrls.url || `${baseUrl}/${appType}/workbench/${savedFormUuid}`;
838
855
  const publishMode = isCanvas ? 'canvas' : 'native';
839
856
  let healthCheckResult = null;
840
857
  let navOrderResult = null;
@@ -911,6 +928,8 @@ async function runMain(argv) {
911
928
  formUuid: savedFormUuid,
912
929
  version,
913
930
  url: pageUrl,
931
+ ...entryUrls,
932
+ navigationWarning,
914
933
  publishMode,
915
934
  storageMode,
916
935
  bundleId,
@@ -83,27 +83,11 @@ function resolveDingtalkLoginOrigin(options = {}) {
83
83
  return DINGTALK_LOGIN_ORIGIN;
84
84
  }
85
85
 
86
- function requestJson(method, url, body, headers = {}, options = {}) {
86
+ function requestJson(method, url, body, headers = {}) {
87
87
  return new Promise((resolve, reject) => {
88
88
  const parsedUrl = new URL(url);
89
89
  const data = body === undefined || body === null ? null : Buffer.from(JSON.stringify(body));
90
90
  const transport = parsedUrl.protocol === 'https:' ? https : http;
91
- const socketTimeoutMs = positiveNumber(options.timeoutMs, 0);
92
- let timeoutId = null;
93
- let settled = false;
94
- const finish = (callback, value) => {
95
- if (settled) {
96
- return;
97
- }
98
- settled = true;
99
- if (timeoutId) {
100
- clearTimeout(timeoutId);
101
- }
102
- callback(value);
103
- };
104
- const resolveOnce = value => finish(resolve, value);
105
- const rejectOnce = error => finish(reject, error);
106
-
107
91
  const request = transport.request({
108
92
  method,
109
93
  protocol: parsedUrl.protocol,
@@ -121,7 +105,6 @@ function requestJson(method, url, body, headers = {}, options = {}) {
121
105
  }, (response) => {
122
106
  const chunks = [];
123
107
  response.on('data', (chunk) => chunks.push(chunk));
124
- response.on('error', rejectOnce);
125
108
  response.on('end', () => {
126
109
  const text = Buffer.concat(chunks).toString('utf8');
127
110
  let payload = null;
@@ -142,26 +125,14 @@ function requestJson(method, url, body, headers = {}, options = {}) {
142
125
  error.payload = payload;
143
126
  error.headers = response.headers;
144
127
  error.location = location;
145
- rejectOnce(error);
128
+ reject(error);
146
129
  return;
147
130
  }
148
- resolveOnce(payload || {});
131
+ resolve(payload || {});
149
132
  });
150
133
  });
151
134
 
152
- if (socketTimeoutMs > 0) {
153
- // Node's socket timeout is reset by network activity, so it cannot enforce
154
- // the caller's wall-clock budget. Use an independent timer that also
155
- // covers response bodies which trickle data without ever completing.
156
- timeoutId = setTimeout(() => {
157
- const error = new Error('request timed out');
158
- error.code = 'request_timeout';
159
- rejectOnce(error);
160
- request.destroy(error);
161
- }, socketTimeoutMs);
162
- }
163
-
164
- request.on('error', rejectOnce);
135
+ request.on('error', reject);
165
136
  if (data) {
166
137
  request.write(data);
167
138
  }
@@ -169,11 +140,6 @@ function requestJson(method, url, body, headers = {}, options = {}) {
169
140
  });
170
141
  }
171
142
 
172
- function positiveNumber(value, fallback) {
173
- const parsed = Number(value);
174
- return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
175
- }
176
-
177
143
  function sanitizeRawTokenResponse(response) {
178
144
  if (!response || typeof response !== 'object') {
179
145
  return response;
@@ -336,43 +302,25 @@ async function tokenLogin(options = {}) {
336
302
  const baseUrl = resolveTokenBaseUrl(options);
337
303
  const authBaseUrl = appendPath(baseUrl, DEFAULT_AUTH_PATH_PREFIX);
338
304
  const clientId = options.clientId || process.env.OPENYIDA_DINGTALK_CLIENT_ID || DINGTALK_OAUTH_CLIENT_ID;
339
- const scope = options.scope || process.env.OPENYIDA_DINGTALK_SCOPE || 'openid corpid';
340
-
341
- let response;
342
- if (options.device) {
343
- // Device Authorization Grant (RFC 8628): sandbox / headless login.
344
- const { runDeviceCodeFlow } = require('./oauth-device');
345
- response = await runDeviceCodeFlow({
346
- authBaseUrl,
347
- clientId,
348
- scope,
349
- envHint: options.envHint,
350
- quiet: options.quiet,
351
- onState: options.onDeviceState,
352
- timeoutMs: options.timeoutMs || env.OPENYIDA_OAUTH_TIMEOUT_MS,
353
- });
354
- requireOkResponse(response);
355
- } else {
356
- const callback = await runDingtalkLoopback({
357
- clientId,
358
- loginOrigin: resolveDingtalkLoginOrigin(options),
359
- scope,
360
- prompt: options.prompt,
361
- port: options.port,
362
- quiet: options.quiet,
363
- noBrowser: options.noBrowser,
364
- timeoutMs: options.timeoutMs,
365
- });
305
+ const callback = await runDingtalkLoopback({
306
+ clientId,
307
+ loginOrigin: resolveDingtalkLoginOrigin(options),
308
+ scope: options.scope || process.env.OPENYIDA_DINGTALK_SCOPE || 'openid corpid',
309
+ prompt: options.prompt,
310
+ port: options.port,
311
+ quiet: options.quiet,
312
+ noBrowser: options.noBrowser,
313
+ timeoutMs: options.timeoutMs,
314
+ });
366
315
 
367
- response = await requestJson('POST', appendPath(authBaseUrl, '/dingtalk/token'), {
368
- code: callback.code,
369
- authCode: callback.authCode,
370
- redirectUri: callback.redirectUri,
371
- state: callback.state,
372
- clientId,
373
- });
374
- requireOkResponse(response);
375
- }
316
+ const response = await requestJson('POST', appendPath(authBaseUrl, '/dingtalk/token'), {
317
+ code: callback.code,
318
+ authCode: callback.authCode,
319
+ redirectUri: callback.redirectUri,
320
+ state: callback.state,
321
+ clientId,
322
+ });
323
+ requireOkResponse(response);
376
324
 
377
325
  const normalized = normalizeTokenResponse(response, baseUrl, clientId);
378
326
  if (!normalized.access_token) {
@@ -676,5 +624,4 @@ module.exports = {
676
624
  resolveTokenBaseUrl,
677
625
  isRefreshAuthRequired,
678
626
  createTokenRefreshFailedError,
679
- getTokenPayload,
680
627
  };
@@ -499,6 +499,7 @@ function buildApplicationEntryPolicy(builderPath, env = process.env) {
499
499
  delivery_unit: 'single_application_entry_group',
500
500
  resource_delivery: 'summary_only',
501
501
  internal_artifact_delivery: 'never',
502
+ frontend_delivery: 'Infer standalone for a scoped service frontend, persist page navigation hiding and return the verified publish/get-form-config standaloneUrl. No extra flag or navigation URL parameter. Missing verification is incomplete delivery; never silently omit the frontend or replace it with workbench/admin.',
502
503
  entries: {
503
504
  workbench: 'when_workspace_in_scope',
504
505
  custom: 'when_entry_mode_standalone_and_is_render_nav_false_readback',
@@ -1035,7 +1035,7 @@ const COMMAND_GROUPS = [
1035
1035
  id: 'auth',
1036
1036
  titleKey: 'help.group_auth',
1037
1037
  commands: [
1038
- command('login', ['login'], 'login [target-url] [--env <name>|--intl|--overseas|--global|--yidaapps|--alibaba] [--client-id <clientId>] [--endpoint <url>] [--no-browser] [--device] [--env-hint <name>]', 'help.cmd_login', {
1038
+ command('login', ['login'], 'login [target-url] [--env <name>|--intl|--overseas|--global|--yidaapps|--alibaba] [--client-id <clientId>] [--endpoint <url>] [--no-browser]', 'help.cmd_login', {
1039
1039
  requiresLogin: false,
1040
1040
  output: 'json',
1041
1041
  }),
@@ -1311,7 +1311,10 @@ const COMMAND_GROUPS = [
1311
1311
  'openyida create-form create APP_XXX "访客登记" .cache/openyida/visitor/fields.json --layout section --theme comfortable --label-align left --icon name-card --locale zh_CN --no-open',
1312
1312
  'openyida commands build create-form.create --app-type APP_XXX --form-title "访客登记" --fields-json-file .cache/openyida/visitor/fields.json --layout section --theme comfortable --label-align left --locale zh_CN --no-open --json',
1313
1313
  ],
1314
- notes: ['Divider accepts the 23 visible platform styles (none is excluded) through field JSON dividerType, not a CLI flag. Omission falls back to bold-with-thin; invalid supplied styles fail. Choose per business page and keep same-level sections consistent; see summary.core_workflows.full_app_build.divider_style_contract.'],
1314
+ notes: [
1315
+ 'Divider accepts the 23 visible platform styles (none is excluded) through field JSON dividerType, not a CLI flag. Omission falls back to bold-with-thin; invalid supplied styles fail. Choose per business page and keep same-level sections consistent; see summary.core_workflows.full_app_build.divider_style_contract.',
1316
+ 'Divider colorType defaults to theme. If default bindings are unsuitable for the current theme, use custom with backgroundColor, secondaryColor and titleColor designed together; explicit user color values are not required. These are field JSON properties, not CLI flags. Dark-content and composite-title guidance is advisory, not a new validation or upload/save gate.',
1317
+ ],
1315
1318
  }),
1316
1319
  command('create-form.icons', ['create-form', 'icons'], 'create-form icons [--json]', 'help.cmd_list_form_icons', {
1317
1320
  requiresLogin: false,
@@ -1437,7 +1440,7 @@ const COMMAND_GROUPS = [
1437
1440
  { name: 'noOpen', type: 'boolean', source: 'option', builder_options: ['--no-open'] },
1438
1441
  { name: 'json', type: 'boolean', source: 'option', builder_options: ['--json'] },
1439
1442
  ],
1440
- notes: ['Canvas publishing runs the same theme and form-open-container structure checks as compile before login or remote writes. --force does not bypass Canvas compilation checks. --health-check verifies saved content; inspect the actual page for runtime behavior. Fixed brand overrides are preserved with structured warnings by default. --strict-theme blocks them; --allow-fixed-brand conflicts with --strict-theme and --fix-theme. --fix-theme requires the migrated controls to be inside a mounted CanvasThemeProvider and compiles before writing. Unproven dynamic render paths require manual migration. No stdin prompts.'],
1443
+ notes: ['Canvas publishing runs the same theme and form-open-container structure checks as compile before login or remote writes. --force does not bypass Canvas compilation checks. --health-check verifies saved content; inspect the actual page for runtime behavior. Fixed brand overrides are preserved with structured warnings by default. --strict-theme blocks them; --allow-fixed-brand conflicts with --strict-theme and --fix-theme. --fix-theme requires the migrated controls to be inside a mounted CanvasThemeProvider and compiles before writing. Unproven dynamic render paths require manual migration. No stdin prompts.', 'Publishing reads persisted page navigation by default without a new flag. With renderNav=false, url and standaloneUrl use the clean /custom/<formUuid> route, with no navigation query parameters; workbenchUrl remains available. Unknown or visible navigation returns standaloneUrl=null; inspect navigationVerification and navigationWarning. A navigation read failure does not undo publication or prove standalone readiness. Configure scoped service frontend pages persistently before publishing; get-form-config can recover the verified URL without republishing.'],
1441
1444
  }),
1442
1445
  command('update-form-config', ['update-form-config'], 'update-form-config <appType> <formUuid> <true|false|keep> "<title>" [--locale zh_CN|en_US|ja_JP]', 'help.cmd_update_form_config', {
1443
1446
  args: [
@@ -1456,7 +1459,7 @@ const COMMAND_GROUPS = [
1456
1459
  { name: 'formUuid', type: 'string', source: 'positional', position: 1, required: true },
1457
1460
  { name: 'json', type: 'boolean', source: 'option', builder_options: ['--json'] },
1458
1461
  ],
1459
- notes: ['Navigation readback uses renderNav first, falling back to isRenderNav only when renderNav is absent. true/false booleans and strings are recognized; missing or invalid values remain unknown. --json includes the raw configuration; the default summary preserves both navigation fields when present.'],
1462
+ notes: ['Navigation readback uses renderNav first, falling back to isRenderNav only when renderNav is absent. true/false booleans and strings are recognized; missing or invalid values remain unknown. --json includes the raw configuration; the default summary preserves both navigation fields when present. For a confirmed display page, both outputs include url, workbenchUrl, standaloneUrl and navigationVerification. Only persisted renderNav=false returns a clean custom standaloneUrl; visible or unknown navigation returns standaloneUrl=null. Route construction does not verify publication or viewer permissions.'],
1460
1463
  }),
1461
1464
  ],
1462
1465
  },
@@ -1829,6 +1832,29 @@ function summarizeLocalizedCommands(commands) {
1829
1832
  fallback: DEFAULT_DIVIDER_TYPE,
1830
1833
  selection: 'Choose by each page business task and visual weight; keep same-level sections consistent within a page. Prefer suitable variation across different business pages. If the scene is unclear or suitable candidates tie, shuffle compatible candidates and rotate across pages, preferring unused styles until exhausted. Record one choice per page in design.md and dividerType before compilation; retries reuse it. Preserve existing supported styles for local edits.',
1831
1834
  invalid_input: 'INVALID_DIVIDER_TYPE diagnostics; create rejects with CREATE_FORM_INVALID_DIVIDER_TYPE. Never silently replace a supplied unsupported style.',
1835
+ color_guidance: {
1836
+ reference: 'yida-skills/skills/yida-design/references/native-form-styles.md#分割线弱背景',
1837
+ mode: 'agent_design_guidance_only',
1838
+ default_mode: 'theme',
1839
+ custom_mode: 'custom',
1840
+ inputs: ['colorType', 'backgroundColor', 'secondaryColor', 'titleColor'],
1841
+ input_location: 'Field JSON for create/batch; changes for update; props in field-props operations for patch. Not CLI flags.',
1842
+ schema_paths: ['Divider.props.colorType', 'Divider.props.backgroundColor', 'Divider.props.secondaryColor', 'Divider.props.titleColor'],
1843
+ default_bindings: {
1844
+ primary: 'var(--color-brand1-6)',
1845
+ secondary: 'var(--color-brand1-2)',
1846
+ text: 'var(--color-text1-4, #171a1d)',
1847
+ },
1848
+ custom_when: 'The user requests colors, or default bindings do not suit the current theme, visual weight or readability. Design all three colors together; do not require user-specified values for each color. Reuse colors across peer sections.',
1849
+ dark_content: 'Distinguish dark content from dark navigation with light content. Treat brand1-2 as a weak brand surface, not permanently near-white. Root secondary overrides may lose to inline component bindings. For local repairs, prefer component colors when global changes would affect other consumers.',
1850
+ composite_titles: 'Consider outer decoration, the actual title surface and the title separately. In inner-ellipse-title the inner ellipse carries the text. Preserve existing supported shapes; do not dim the whole component to fix its background.',
1851
+ solid_title_surfaces: 'For dark-bar and light-left-title/light-center-title/light-house-title/light-hexagon-title, distinguish the primary-colored title block from the secondary strip. Prefer white or near-white on sufficiently dark primary surfaces; if a bright saturated fill weakens white text, deepen the component backgroundColor while preserving its hue. Do not force white onto pale surfaces. Set titleColor explicitly in custom mode instead of retaining its dark default; do not change global body text to fix a local title.',
1852
+ label_coordination: 'Design the divider title, first field label and input as a group. Share font family; use modest title weight/size emphasis, readable neutral labels, coordinated alignment and spacing. Preserve centered decorative headings without centering field labels. Keep the section gap larger than the title-to-first-field gap, and label-to-input spacing compact. Do not dim labels to create hierarchy or repeat excessive saturated decoration. The label role pod-form-label-color is separate from divider titleColor; record choices in existing design.md without new flags or blocking checks.',
1853
+ picker_icons: '--color-fill1-6 is a weak foreground for picker icons; choose a readable auxiliary foreground against the actual input surface, for example the current theme color-text1-3.',
1854
+ lifecycle: 'Record the choice in existing design.md. Revisit custom colors when changing themes; they do not automatically follow theme switching. Do not claim visual verification from a successful command or when preview is unavailable.',
1855
+ new_cli_flags: false,
1856
+ adds_validation_or_blocking: false,
1857
+ },
1832
1858
  },
1833
1859
  page_skill_policy: 'Use YidaCodeCanvas by default for new primary custom pages. Choose yida-custom-page only when the current target has been identified as existing .oyd.jsx/.oyb.jsx/renderJsx/platform Jsx component maintenance; do not switch to yida-custom-page merely because a page needs data, forms, uploads, this.utils.yida.*, or dataSourceMap-like capabilities.',
1834
1860
  form_entry_policy: {
@@ -1904,6 +1930,8 @@ function summarizeLocalizedCommands(commands) {
1904
1930
  custom: {
1905
1931
  include: 'when_entry_mode_standalone_and_is_render_nav_false_readback',
1906
1932
  url: '{base_url}/{appType}/custom/{formUuid}',
1933
+ default_policy: 'A scoped service frontend is a standalone display page even when entryMode was omitted. Plan derives it from role=service and sceneKey; Fast follows the same business intent. Persist page navigation hiding and read it back, without a new flag or navigation URL query. Preserve explicit user choices and backend navigation. Missing verification is incomplete frontend delivery, not permission to silently omit it.',
1934
+ url_source: 'publish.standaloneUrl or post-publish get-form-config.standaloneUrl; require navigationVerification.renderNav=false and verified=true',
1907
1935
  },
1908
1936
  admin: {
1909
1937
  include: 'default_for_complete_application',
@@ -382,8 +382,8 @@ Examples:
382
382
  save_permission_usage: 'Usage: openyida save-permission <appType> <formUuid> [--package-uuid <packageUuid>] [--data-permission <json>] [--action-permission <json>]',
383
383
  save_permission_example: "Example: openyida save-permission APP_XXX FORM-XXX --data-permission '{\"role\":\"DEFAULT\",\"dataRange\":\"SELF\"}'",
384
384
  exec_failed: '\n❌ Execution failed: {0}',
385
- login_usage: 'Usage: openyida login [entryUrl|--public|--alibaba|--intl] [--no-browser] [--device] [--env-hint <name>] [--check-only] [--json] [--client-id <clientId>]',
386
- login_example: 'Examples:\n openyida login # Automatically open the browser via OAuth loopback login\n openyida login --device # Use device authorization in a headless environment\n openyida login --device --env-hint pre # Pin device authorization to an environment profile\n openyida login --no-browser # Let the caller handle the authorization URL\n openyida login --check-only --json # Check token auth status only\n openyida login --intl # Login against the international environment\n OPENYIDA_NO_BROWSER=1 openyida login # Suppress auto-opening the browser with an environment variable\n openyida auth login # Login alias',
385
+ login_usage: 'Usage: openyida login [entryUrl|--public|--alibaba|--intl] [--no-browser] [--check-only] [--json] [--client-id <clientId>]',
386
+ login_example: 'Examples:\n openyida login # Automatically open the browser via OAuth loopback login\n openyida login --no-browser # Let the caller handle the authorization URL\n openyida login --check-only --json # Check token auth status only\n openyida login --intl # Login against the international environment\n OPENYIDA_NO_BROWSER=1 openyida login # Suppress auto-opening the browser with an environment variable\n openyida auth login # Login alias',
387
387
  login_unsupported_option: 'Removed legacy login option is no longer supported: {0}. Use token/OAuth login; env token mode reads only OPENYIDA_* tokens.',
388
388
  auth_usage: 'Usage: openyida auth <status|login|refresh|logout|profiles|profile switch>',
389
389
  auth_example: 'Examples:\n openyida auth status # View login status\n openyida auth profiles # List existing login profiles\n openyida auth profile switch <auth_profile> # Switch current project to an existing profile\n openyida auth login # Add a profile when the target does not exist\n openyida auth refresh # Refresh login session\n openyida auth logout # Unbind current project auth\n openyida auth logout --profile <auth_profile> # Delete a shared profile explicitly',
@@ -655,18 +655,6 @@ Examples:
655
655
  oauth_success_message: 'Sign-in is complete. Return to the previous window to continue.',
656
656
  oauth_closing: 'Closing this window automatically…',
657
657
  oauth_manual_close: 'This window could not close automatically. Close it manually, then return to the previous window to continue.',
658
- device_title: 'Device code login:',
659
- device_open: 'Open',
660
- device_enter_code: 'Enter code:',
661
- device_or_direct: 'Or open directly:',
662
- device_completed: 'Device authorization completed.',
663
- device_timeout: 'device authorization timed out; device code may still be pending',
664
- device_missing_params: 'device code flow requires authBaseUrl and clientId',
665
- device_poll_missing_params: 'device token polling requires authBaseUrl, clientId and deviceCode',
666
- device_invalid_code_response: 'auth service did not return a usable device code',
667
- device_code_expired: 'device code expired; run login --device again',
668
- device_access_denied: 'authorization was denied on the verification page',
669
- device_2xx_no_token: 'auth service returned 2xx without accessToken',
670
658
  oauth_failure_title: 'Sign-in not completed',
671
659
  oauth_invalid_state: 'The sign-in request could not be verified. Return to the terminal and try again.',
672
660
  oauth_missing_code: 'No authorization information was received. Return to the terminal and try again.',
@@ -382,8 +382,8 @@ openyida - 宜搭命令行工具
382
382
  save_permission_usage: '用法: openyida save-permission <appType> <formUuid> [--package-uuid <packageUuid>] [--data-permission <json>] [--action-permission <json>]',
383
383
  save_permission_example: "示例: openyida save-permission APP_XXX FORM-XXX --data-permission '{\"role\":\"DEFAULT\",\"dataRange\":\"SELF\"}'",
384
384
  exec_failed: '\n❌ 执行失败: {0}',
385
- login_usage: '用法: openyida login [entryUrl|--public|--alibaba|--intl] [--no-browser] [--device] [--env-hint <name>] [--check-only] [--json] [--client-id <clientId>]',
386
- login_example: '示例:\n openyida login # 通过 OAuth loopback 自动打开浏览器登录\n openyida login --device # 在无浏览器或沙箱环境使用设备授权登录\n openyida login --device --env-hint pre # 将设备授权限定到指定环境 profile\n openyida login --no-browser # 不自动打开浏览器,由调用方接管授权链接\n openyida login --check-only --json # 只检查 token 登录态\n openyida login --intl # 使用国际站环境登录\n OPENYIDA_NO_BROWSER=1 openyida login # 通过环境变量抑制自动打开浏览器\n openyida auth login # 登录入口别名',
385
+ login_usage: '用法: openyida login [entryUrl|--public|--alibaba|--intl] [--no-browser] [--check-only] [--json] [--client-id <clientId>]',
386
+ login_example: '示例:\n openyida login # 通过 OAuth loopback 自动打开浏览器登录\n openyida login --no-browser # 不自动打开浏览器,由调用方接管授权链接\n openyida login --check-only --json # 只检查 token 登录态\n openyida login --intl # 使用国际站环境登录\n OPENYIDA_NO_BROWSER=1 openyida login # 通过环境变量抑制自动打开浏览器\n openyida auth login # 登录入口别名',
387
387
  login_unsupported_option: '已删除的旧登录参数不再支持: {0}。请使用 token/OAuth 登录;env token 模式下只读取 OPENYIDA_* token。',
388
388
  first_run_title: ' 🤖 OpenYida - AI 问答模式已开启! ',
389
389
  first_run_welcome: ' {0}欢迎首次使用 OpenYida!{1} 以下是快速上手指南:',
@@ -626,18 +626,6 @@ openyida - 宜搭命令行工具
626
626
  oauth_success_message: '登录已完成,请返回刚才的窗口继续操作。',
627
627
  oauth_closing: '正在自动关闭此窗口…',
628
628
  oauth_manual_close: '此窗口未能自动关闭,请手动关闭后返回刚才的窗口继续操作。',
629
- device_title: '设备码登录:',
630
- device_open: '打开',
631
- device_enter_code: '输入验证码:',
632
- device_or_direct: '或直接打开:',
633
- device_completed: '设备授权完成。',
634
- device_timeout: '设备授权超时;设备码可能仍在等待验证',
635
- device_missing_params: '设备码流程需要 authBaseUrl 和 clientId',
636
- device_poll_missing_params: '设备令牌轮询需要 authBaseUrl、clientId 和 deviceCode',
637
- device_invalid_code_response: '认证服务未返回可用的设备码',
638
- device_code_expired: '设备码已过期;请重新执行 login --device',
639
- device_access_denied: '授权已在验证页面被拒绝',
640
- device_2xx_no_token: '认证服务返回 2xx 但未包含 accessToken',
641
629
  oauth_failure_title: '登录未完成',
642
630
  oauth_invalid_state: '登录验证信息无效,请返回终端重新发起登录。',
643
631
  oauth_missing_code: '未收到登录授权信息,请返回终端重新发起登录。',
@@ -16,6 +16,15 @@ const assert = (condition, message, details) => {
16
16
  };
17
17
 
18
18
  // Resource names are planning references. Runtime IDs and permissions are resolved after creation.
19
+ function resolvePageEntryMode(page, plan, { sceneKey, customNavigation }) {
20
+ const supplied = page.pageSpecHandoff || {};
21
+ const entries = plan.execution?.entryRecommendation?.entries;
22
+ const servicePage = Array.isArray(entries) && entries.some(entry =>
23
+ entry?.role === 'service' && filled(entry.sceneKey) && entry.sceneKey === sceneKey);
24
+ return supplied.entryMode || page.entryMode ||
25
+ (servicePage || customNavigation || supplied.navigation ? 'standalone' : 'platform-shell');
26
+ }
27
+
19
28
  function buildEntryNavigation(plan, resources, pages) {
20
29
  const recommendation = plan.execution?.entryRecommendation;
21
30
  if (recommendation === undefined) {return null;}
@@ -94,4 +103,4 @@ function buildEntryNavigation(plan, resources, pages) {
94
103
  return { recommendation: JSON.parse(JSON.stringify(recommendation)), platformOrder: [...new Set(platformOrder)] };
95
104
  }
96
105
 
97
- module.exports = { buildEntryNavigation, ENTRY_MODES, ENTRY_MODE_SUMMARIES };
106
+ module.exports = { buildEntryNavigation, resolvePageEntryMode, ENTRY_MODES, ENTRY_MODE_SUMMARIES };
@@ -233,7 +233,8 @@ function initialize(inputPath, options = {}) {
233
233
  '# 计划编写上下文', '', `需求事实:${input}`, `计划目录:${outputDir}`, '',
234
234
  `读取本上下文、编写契约和 business.json;按 authoring.pendingFields 补齐事实,保留 base 与已有需求。business facts 填写 overview、dataModels、businessFlows、pages 和可选 execution;视觉填写 visual.json。复核业务覆盖后设置片段 ready=true,再执行:${materializeCommand}。使用成功结果中的 HTML 路径展示“当前方案”,revision 仅放在内部确认字段,不写入用户可见标题、摘要或问题。视觉片段按每页实际任务补齐焦点、布局、主操作、响应式与验收;已完成的选择直接复用。`, '',
235
235
  '## 本轮编写重点', '设计功能范围、数据与规则、页面组织、关键交互和业务验收。页面 blocks 按优先顺序填写 {name,purpose};CLI 派生默认内容优先级与内容层次,保留明确填写的独立设计。通用状态和验收检查由 CLI 补齐,interactionStates 与 acceptanceCriteria 填写项目差异。', '',
236
- '首页按使用者的主要任务选择:集中办事可用门户式工作台(workbench),单项任务可直达原生业务页;只有已确认的指标分析或投屏监控任务才规划 dashboard/screen。工作台按需求组织待办、操作队列、业务列表和常用入口,辅助统计不改变页面主任务。不得从后台身份、系统名称或暗色风格自动增加驾驶舱。完整规则见 yida-requirement-analysis/references/experience-groups.md 的“首页按任务选择”。', '',
236
+ '首页按使用者的主要任务选择:集中办事可用门户式工作台(workbench),单项任务可直达原生业务页;只有已确认的指标分析或投屏监控任务才规划 dashboard/screen。工作台是围绕应用核心业务,把全局概况、业务管理、分析判断、异常处理和常用快捷入口组织在一起的综合工作空间;包含分析区块不自动改变整页主任务。不得从后台身份、系统名称或暗色风格自动增加驾驶舱。完整规则见 yida-requirement-analysis/references/experience-groups.md 的“首页按任务选择”。', '',
237
+ '工作台按角色与业务关系规划,参考 8-10 个有独立价值的区块;采用适合信息特点的表现形式,数据和洞察有依据,操作与快捷入口可用,不套固定列表组合。', '',
237
238
  '无论是否分前后台,每个业务入口都要确定默认页面、菜单及分组顺序、打开后的首屏任务。menu 数组记录展示顺序,defaultMenuKey 记录默认任务;按角色需求决定,不按资源创建先后排列。平台导航通过排序命令落实,自定义导航通过页面菜单落实,frontend-only 也必须完成入口排序与首页验收。', '',
238
239
  '入口方案沿用 execution.entryRecommendation:unified 表示不分前后台,service-management 表示分前后台,frontend-only 表示只有访问前台,backend-only 表示只有访问后台。向用户说明谁使用、看到什么、能做什么;只有前台且提交后需要处理时,在 reason 和业务流程中说明承接方式;逐入口补齐 role、menu 和 defaultMenuKey,前台关联独立页面 sceneKey。任何身份的入口含 local 菜单时,entry.sceneKey 必须关联承载页面,menu.resource 等于页面 name,viewKey 非空。管理端可直接落到原生业务视图,不强制创建首页。所有叶子菜单按资源、视图和操作记录 access 权限依赖,平台导航排序从管理菜单派生。详见 yida-app/references/entry-navigation.md。', '',
239
240
  theme.mode === 'creative'
@@ -9,7 +9,7 @@ const { normalizePlan, assertThemeProfile } = require('./normalize');
9
9
  const { readJson, writeFiles } = require('./files');
10
10
  const { prepareUpdates } = require('./artifact-updates');
11
11
  const { validateAuthoring } = require('./validate');
12
- const { buildEntryNavigation, ENTRY_MODE_SUMMARIES } = require('./entry-navigation');
12
+ const { buildEntryNavigation, resolvePageEntryMode, ENTRY_MODE_SUMMARIES } = require('./entry-navigation');
13
13
  const { buildPageNavigationPolicy } = require('./navigation-policy');
14
14
  const { buildAssetTasks } = require('../asset/asset-plan');
15
15
  const { mergeParts } = require('./parallel');
@@ -216,7 +216,7 @@ function pageHandoff(page, plan) {
216
216
  detail: 'detail-profile', landing: 'official-homepage', screen: 'data-screen',
217
217
  };
218
218
  const customNavigation = navigationConfig(plan).navigationType === 'custom';
219
- const entryMode = supplied.entryMode || page.entryMode || (customNavigation || supplied.navigation ? 'standalone' : 'platform-shell');
219
+ const entryMode = resolvePageEntryMode(page, plan, { sceneKey: page.sceneKey || scene, customNavigation });
220
220
  requireFact(!customNavigation || entryMode === 'standalone', `自定义导航的页面 ${page.name} 必须使用 standalone 入口`);
221
221
  if (supplied.navigation !== undefined) {
222
222
  const nav = supplied.navigation;
@@ -277,7 +277,9 @@ function pageNavigationRows(page, plan) {
277
277
  const handoff = page.pageSpecHandoff || {};
278
278
  const applicationCustom = navigationConfig(plan).navigationType === 'custom';
279
279
  const menu = handoff.navigation;
280
- const entryMode = handoff.entryMode || page.entryMode || (applicationCustom || menu ? 'standalone' : 'platform-shell');
280
+ const entryMode = resolvePageEntryMode(page, plan, {
281
+ sceneKey: page.sceneKey || sceneForPage(page), customNavigation: applicationCustom,
282
+ });
281
283
  const variants = { top: '顶部', side: '侧边', mixed: '顶部与侧边', dock: '底部' };
282
284
  return [
283
285
  ['访问方式', entryMode === 'standalone' ? '独立页面入口' : '应用工作区入口'],
@@ -613,7 +615,7 @@ function renderProjectApplications(plan, tokens, color, chapter) {
613
615
  '--color-brand1-6': '主色与主要品牌操作', '--color-brand1-1': '品牌悬停',
614
616
  '--color-brand1-9': '品牌按下', '--color-brand1-10': '品牌禁用',
615
617
  '--pod-page-bg-color': '页面画布', '--pod-card-bg-color': '内容容器',
616
- '--color-fill1-5': '独立浮层', '--color-fill1-10': 'Tooltip背景', '--color-text1-5': 'Tooltip前景',
618
+ '--color-fill1-5': '独立浮层', '--color-fill1-6': '弱图标与辅助操作', '--color-fill1-10': 'Tooltip背景', '--color-text1-5': 'Tooltip前景',
617
619
  };
618
620
  return [
619
621
  '## 5. 项目应用与调整规则', '',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openyida",
3
- "version": "2026.9.24-beta.0",
3
+ "version": "2026.9.30",
4
4
  "description": "OpenYida CLI - 宜搭低代码 AI 开发工具(安装即用,零配置)",
5
5
  "bin": {
6
6
  "openyida": "bin/yida.js",
@@ -108,7 +108,7 @@ Plan 规划阶段的工作流入口只读取 `workflow/step-1-resource-context.m
108
108
  - 需要收集或存储数据:先创建或复用核心普通表单,再生成页面;纯展示或静态内容可跳过表单创建。
109
109
  - 需要审批、申请、审核、工单流转:先创建或复用流程表单,再生成页面。
110
110
  - 需要标准统计:优先创建原生报表;明确高级图表或大屏时,再选择 `yida-rechart` / `yida-chart`。
111
- - 按页面主任务选技能:经营分析、指标判断或投屏监控页面加载 `yida-dashboard`;待办、操作队列、业务列表和门户式工作台由 `yida-canvas-custom-page` 实现,不能仅凭“工作台”或“看板”名称加载经营看板技能。页面读取任何表单业务数据时继续加载 `yida-canvas-data-binding`。
111
+ - 按页面主任务选技能:经营分析、指标判断或投屏监控页面加载 `yida-dashboard`;综合工作台由 `yida-canvas-custom-page` 实现,按 PRD 组织内容;其中的分析、洞察及快捷操作按实际需要实现,不能仅凭“工作台”或“看板”名称加载经营看板技能。页面读取任何表单业务数据时继续加载 `yida-canvas-data-binding`。
112
112
  - PRD 明确包含报表或集成自动化时,它们属于 Step 4 主流程资源,不得推迟到 final 后置建议;自动化动作必须按通知、数据新增/更新、审批完成、定时或手动触发分别建模,不得统一退化成新增通知。
113
113
 
114
114
  ## 页面数据契约