openyida 2026.9.24-beta.0 → 2026.9.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/README.md +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/materialize.js +6 -4
  14. package/package.json +1 -1
  15. package/yida-skills/skills/yida-app/references/entry-navigation.md +2 -0
  16. package/yida-skills/skills/yida-app/workflow/step-6-main-page.md +1 -1
  17. package/yida-skills/skills/yida-app/workflow/step-8-publish-navigation.md +2 -2
  18. package/yida-skills/skills/yida-app/workflow/step-9-output-finish.md +3 -3
  19. package/yida-skills/skills/yida-create-form-page/SKILL.md +3 -1
  20. package/yida-skills/skills/yida-create-form-page/references/field-definition-guide.md +16 -0
  21. package/yida-skills/skills/yida-create-form-page/references/form-field-properties.md +7 -5
  22. package/yida-skills/skills/yida-create-page/SKILL.md +1 -1
  23. package/yida-skills/skills/yida-design/SKILL.md +3 -1
  24. package/yida-skills/skills/yida-design/references/application-theme-consistency.md +4 -2
  25. package/yida-skills/skills/yida-design/references/native-form-styles.md +58 -0
  26. package/yida-skills/skills/yida-design/references/navigation-decision.md +2 -0
  27. package/yida-skills/skills/yida-design/references/theme/app-custom-theme-template.css +2 -0
  28. package/yida-skills/skills/yida-design/references/theme/theme-token-presets.md +3 -1
  29. package/yida-skills/skills/yida-design/templates/design-themes/README.md +4 -2
  30. package/yida-skills/skills/yida-design/templates/design-themes/app-amber/app_theme.css +2 -0
  31. package/yida-skills/skills/yida-design/templates/design-themes/app-amber/design.md +2 -0
  32. package/yida-skills/skills/yida-design/templates/design-themes/app-editorial/app_theme.css +2 -0
  33. package/yida-skills/skills/yida-design/templates/design-themes/app-editorial/design.md +2 -0
  34. package/yida-skills/skills/yida-design/templates/design-themes/app-executive/app_theme.css +3 -1
  35. package/yida-skills/skills/yida-design/templates/design-themes/app-executive/design.md +3 -1
  36. package/yida-skills/skills/yida-design/templates/design-themes/app-finance/app_theme.css +2 -0
  37. package/yida-skills/skills/yida-design/templates/design-themes/app-finance/design.md +2 -0
  38. package/yida-skills/skills/yida-design/templates/design-themes/app-glass/app_theme.css +2 -0
  39. package/yida-skills/skills/yida-design/templates/design-themes/app-glass/design.md +2 -0
  40. package/yida-skills/skills/yida-design/templates/design-themes/app-graphite/app_theme.css +3 -1
  41. package/yida-skills/skills/yida-design/templates/design-themes/app-graphite/design.md +3 -1
  42. package/yida-skills/skills/yida-design/templates/design-themes/app-line/app_theme.css +2 -0
  43. package/yida-skills/skills/yida-design/templates/design-themes/app-line/design.md +2 -0
  44. package/yida-skills/skills/yida-design/templates/design-themes/app-neon/app_theme.css +3 -1
  45. package/yida-skills/skills/yida-design/templates/design-themes/app-neon/design.md +3 -1
  46. package/yida-skills/skills/yida-design/templates/design-themes/app-nordic/app_theme.css +2 -0
  47. package/yida-skills/skills/yida-design/templates/design-themes/app-nordic/design.md +2 -0
  48. package/yida-skills/skills/yida-design/templates/design-themes/app-paper/app_theme.css +2 -0
  49. package/yida-skills/skills/yida-design/templates/design-themes/app-paper/design.md +2 -0
  50. package/yida-skills/skills/yida-design/templates/design-themes/app-platinum/app_theme.css +2 -0
  51. package/yida-skills/skills/yida-design/templates/design-themes/app-platinum/design.md +2 -0
  52. package/yida-skills/skills/yida-design/templates/design-themes/app-plum/app_theme.css +2 -0
  53. package/yida-skills/skills/yida-design/templates/design-themes/app-plum/design.md +2 -0
  54. package/yida-skills/skills/yida-design/templates/design-themes/app-pop/app_theme.css +2 -0
  55. package/yida-skills/skills/yida-design/templates/design-themes/app-pop/design.md +2 -0
  56. package/yida-skills/skills/yida-design/templates/design-themes/app-sage/app_theme.css +2 -0
  57. package/yida-skills/skills/yida-design/templates/design-themes/app-sage/design.md +2 -0
  58. package/yida-skills/skills/yida-design/templates/design-themes/app-teal/app_theme.css +2 -0
  59. package/yida-skills/skills/yida-design/templates/design-themes/app-teal/design.md +2 -0
  60. package/yida-skills/skills/yida-design/templates/design-themes/app-terminal/app_theme.css +3 -1
  61. package/yida-skills/skills/yida-design/templates/design-themes/app-terminal/design.md +3 -1
  62. package/yida-skills/skills/yida-design/templates/design-themes/app-ticket/app_theme.css +2 -0
  63. package/yida-skills/skills/yida-design/templates/design-themes/app-ticket/design.md +2 -0
  64. package/yida-skills/skills/yida-design/templates/design-themes/app-wire/app_theme.css +2 -0
  65. package/yida-skills/skills/yida-design/templates/design-themes/app-wire/design.md +2 -0
  66. package/yida-skills/skills/yida-design/templates/design-themes/basic-tokens.json +1 -0
  67. package/yida-skills/skills/yida-design/templates/design-themes/dark-inset-hairline/app_theme.css +3 -1
  68. package/yida-skills/skills/yida-design/templates/design-themes/dark-inset-hairline/design.md +3 -1
  69. package/yida-skills/skills/yida-design/templates/design-themes/dark-rail-fine-lines/app_theme.css +2 -0
  70. package/yida-skills/skills/yida-design/templates/design-themes/dark-rail-fine-lines/design.md +2 -0
  71. package/yida-skills/skills/yida-design/templates/design-themes/dark-rail-signal-panels/app_theme.css +3 -1
  72. package/yida-skills/skills/yida-design/templates/design-themes/dark-rail-signal-panels/design.md +3 -1
  73. package/yida-skills/skills/yida-design/templates/design-themes/free-creative/app_theme.css +2 -0
  74. package/yida-skills/skills/yida-design/templates/design-themes/free-creative/design.md +2 -0
  75. package/yida-skills/skills/yida-design/templates/design-themes/graphite-bevel-grid/app_theme.css +3 -1
  76. package/yida-skills/skills/yida-design/templates/design-themes/graphite-bevel-grid/design.md +3 -1
  77. package/yida-skills/skills/yida-design/templates/design-themes/hairline-shared-bands/app_theme.css +3 -1
  78. package/yida-skills/skills/yida-design/templates/design-themes/hairline-shared-bands/design.md +3 -1
  79. package/yida-skills/skills/yida-design/templates/design-themes/hairline-soft-blocks/app_theme.css +2 -0
  80. package/yida-skills/skills/yida-design/templates/design-themes/hairline-soft-blocks/design.md +2 -0
  81. package/yida-skills/skills/yida-design/templates/design-themes/ink-glow-soft-panels/app_theme.css +2 -0
  82. package/yida-skills/skills/yida-design/templates/design-themes/ink-glow-soft-panels/design.md +2 -0
  83. package/yida-skills/skills/yida-design/templates/design-themes/inset-frame-pixel-rhythm/app_theme.css +2 -0
  84. package/yida-skills/skills/yida-design/templates/design-themes/inset-frame-pixel-rhythm/design.md +2 -0
  85. package/yida-skills/skills/yida-design/templates/design-themes/outlined-texture-duotone/app_theme.css +3 -1
  86. package/yida-skills/skills/yida-design/templates/design-themes/outlined-texture-duotone/design.md +3 -1
  87. package/yida-skills/skills/yida-design/templates/design-themes/soft-inset-surfaces/app_theme.css +2 -0
  88. package/yida-skills/skills/yida-design/templates/design-themes/soft-inset-surfaces/design.md +2 -0
  89. package/yida-skills/skills/yida-design/templates/design-themes/soft-outline-rhythm/app_theme.css +2 -0
  90. package/yida-skills/skills/yida-design/templates/design-themes/soft-outline-rhythm/design.md +2 -0
  91. package/yida-skills/skills/yida-design/templates/design-themes/soft-rail-bold-band/app_theme.css +2 -0
  92. package/yida-skills/skills/yida-design/templates/design-themes/soft-rail-bold-band/design.md +2 -0
  93. package/yida-skills/skills/yida-design/templates/design-themes/soft-spectral-panels/app_theme.css +4 -2
  94. package/yida-skills/skills/yida-design/templates/design-themes/soft-spectral-panels/design.md +4 -2
  95. package/yida-skills/skills/yida-design/templates/design-themes/warm-canvas-contrast-panels/app_theme.css +2 -0
  96. package/yida-skills/skills/yida-design/templates/design-themes/warm-canvas-contrast-panels/design.md +2 -0
  97. package/yida-skills/skills/yida-design/templates/design-themes/warm-rail-muted-panels/app_theme.css +2 -0
  98. package/yida-skills/skills/yida-design/templates/design-themes/warm-rail-muted-panels/design.md +2 -0
  99. package/yida-skills/skills/yida-design/workflow/step-2-theme-system.md +3 -1
  100. package/yida-skills/skills/yida-page-config/SKILL.md +2 -2
  101. package/yida-skills/skills/yida-publish-page/SKILL.md +8 -1
  102. package/lib/auth/oauth-device.js +0 -311
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 };
@@ -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.24",
4
4
  "description": "OpenYida CLI - 宜搭低代码 AI 开发工具(安装即用,零配置)",
5
5
  "bin": {
6
6
  "openyida": "bin/yida.js",
@@ -123,6 +123,8 @@ Fast(直接搭建)与 Plan(先确认方案)使用同一份入口规划
123
123
 
124
124
  ## 规划字段速查(Fast / Plan)
125
125
 
126
+ 前台用途已经确定时,自定义承载页面默认采用独立入口。`role=service` 与页面 `sceneKey` 对应后,Plan 自动推导遗漏的 `entryMode=standalone`,Fast 执行同样规则;不靠页面名称猜测,不新增 CLI 参数。显式的导航要求优先,冲突需核对。取得页面 ID 后持久化隐藏页面导航并回读,发布后交付 `publish` 或 `get-form-config` 返回的 `standaloneUrl`,不追加导航 URL 参数。前台入口属于交付范围时不能因字段漏写或只拿到工作台地址而静默省略;未确认成功须明确列为未完成。
127
+
126
128
  需求文件中的 `entryRecommendation` 在 Plan 初始化后保存在 `execution.entryRecommendation`;Fast 在 PRD 中使用同一结构。旧计划没有此字段时仍可使用;首次完整生成时应明确入口规划。保留已有 `source/reason/taskRefs` 等需求依据。
127
129
 
128
130
  | 字段 | 含义和要求 |
@@ -16,7 +16,7 @@
16
16
  3. 创建页面前核对 PRD/resource context 与 auth snapshot 的 `corpId`。
17
17
  4. `corpId` 不一致 → 先确认重新登录到目标组织,或确认在当前组织继续。
18
18
  5. 创建 display 页面后记录真实主页面 `formUuid`。
19
- 6. PRD 的应用工作区采用自定义导航,或该页已确定 `entryMode=standalone` / 页面隐藏导航时,取得真实 `formUuid` 后立即执行 `openyida update-form-config <appType> <formUuid> false "<页面标题>"` 并 `get-form-config --json` 回读 `renderNav=false`;已有页面按同样规则处理,`create-page --hide-nav` 已配置成功时直接回读即可。该任务与 Step 7 的源码开发并行,不等待代码或发布完成。
19
+ 6. 已确定面向前台用户的自定义页面(如 `entryRecommendation.entries[].role=service`),默认作为独立页面交付,即使遗漏 `entryMode` 也按业务用途补齐为 `standalone`,不要求用户再选择开关。PRD 的应用工作区采用自定义导航,或该页已确定 `entryMode=standalone` / 页面隐藏导航时,取得真实 `formUuid` 后立即执行 `openyida update-form-config <appType> <formUuid> false "<页面标题>"` 并 `get-form-config --json` 回读 `renderNav=false`;已有页面按同样规则处理,配置已正确则不重复写入。该任务与 Step 7 的源码开发并行,不等待代码或发布完成。使用持久化配置,不在自定义页 URL 中追加 `isRenderNav=false`。用户明确要求保留平台导航、普通后台内容页和无关页面局部修改沿用其已有边界。
20
20
  7. 将页面导航配置结果交给 Step 8 做发布后核对。其余自定义页面同样在各自创建/复用成功后配置,不只处理主页面。
21
21
 
22
22
  同一 sceneKey 的前台业务视图共用一个自定义页面;将全部视图交给 Step 7 实现。