openyida 2026.9.14 → 2026.9.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -3
- package/lib/app/canvas-compile.js +2 -0
- package/lib/app/canvas-theme-guard.js +117 -0
- package/lib/app/create-form/batch.js +54 -6
- package/lib/app/create-form.js +93 -15
- package/lib/app/services/form-compiler.js +4 -4
- package/lib/app/theme-from-design.js +8 -2
- package/lib/app/theme-presets.js +1 -1
- package/lib/asset/ai-image.js +40 -6
- package/lib/asset/asset-cmd.js +8 -2
- package/lib/asset/asset-plan.js +46 -4
- package/lib/asset/asset-resolve.js +7 -3
- package/lib/core/agent-capabilities.js +16 -8
- package/lib/core/command-manifest.js +113 -16
- package/lib/core/locales/en.js +10 -2
- package/lib/core/locales/zh.js +10 -2
- package/lib/core/redact.js +15 -10
- package/lib/core/sample.js +6 -10
- package/lib/design-plan/design-plan.js +7 -2
- package/lib/design-plan/entry-navigation.js +83 -0
- package/lib/design-plan/init.js +73 -10
- package/lib/design-plan/materialize.js +103 -67
- package/lib/design-plan/normalize.js +23 -1
- package/lib/design-plan/patch.js +24 -4
- package/lib/design-plan/validate.js +30 -5
- package/lib/samples/openyida-scaffold/canvas-form-drawer.canvas.jsx +14 -85
- package/lib/samples/openyida-scaffold/canvas-nav/data.jsx +84 -17
- package/lib/samples/yida-canvas-table-form/table-form-batch-submit.canvas.jsx +9 -83
- package/lib/samples/yida-rechart/trend-combo.canvas.jsx +14 -87
- package/package.json +2 -1
- package/scripts/postinstall.js +3 -3
- package/yida-skills/SKILL.md +3 -3
- package/yida-skills/skills/yida-app/SKILL.md +18 -12
- package/yida-skills/skills/yida-app/references/entry-navigation.md +150 -0
- package/yida-skills/skills/yida-app/workflow/parallel-work.md +10 -1
- package/yida-skills/skills/yida-app/workflow/plan/step-1-understand.md +1 -1
- package/yida-skills/skills/yida-app/workflow/plan/step-2-confirm.md +1 -1
- package/yida-skills/skills/yida-app/workflow/plan/step-4-deliver.md +6 -6
- package/yida-skills/skills/yida-app/workflow/plan/workflow.md +10 -9
- package/yida-skills/skills/yida-app/workflow/step-1-resource-context.md +4 -2
- package/yida-skills/skills/yida-app/workflow/step-2-design.md +15 -5
- package/yida-skills/skills/yida-app/workflow/step-3-create-or-reuse-app.md +4 -2
- package/yida-skills/skills/yida-app/workflow/step-4-forms-processes.md +14 -3
- package/yida-skills/skills/yida-app/workflow/step-6-main-page.md +6 -4
- package/yida-skills/skills/yida-app/workflow/step-7-page-code.md +6 -4
- package/yida-skills/skills/yida-app/workflow/step-8-publish-navigation.md +13 -4
- package/yida-skills/skills/yida-app/workflow/step-9-output-finish.md +29 -19
- package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +32 -42
- package/yida-skills/skills/yida-canvas-custom-page/references/canvas-style-implementation-guide.md +5 -5
- package/yida-skills/skills/yida-canvas-custom-page/references/canvas-theme-provider.md +45 -50
- package/yida-skills/skills/yida-canvas-custom-page/references/navigation-and-entry-guide.md +2 -0
- package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +13 -12
- package/yida-skills/skills/yida-canvas-custom-page/scripts/build-canvas-theme.js +19 -2
- package/yida-skills/skills/yida-create-form-page/SKILL.md +36 -14
- package/yida-skills/skills/yida-create-form-page/references/batch-forms.md +4 -3
- package/yida-skills/skills/yida-design/SKILL.md +6 -2
- package/yida-skills/skills/yida-design/references/ask-human-interaction-contract.md +42 -55
- package/yida-skills/skills/yida-design/references/asset-workflow.md +4 -2
- package/yida-skills/skills/yida-design/references/design-mode.md +3 -3
- package/yida-skills/skills/yida-design/references/navigation-decision.md +42 -0
- package/yida-skills/skills/yida-design/references/theme/app-custom-theme-template.css +2 -2
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/SKILL.md +4 -4
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/build-plan-compact-schema.md +16 -11
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/build-plan-schema.md +24 -150
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/visual-design.md +6 -2
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/visual-theme-selection.md +17 -13
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/scripts/render_build_plan.py +25 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/scripts/validate_design_themes.py +29 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/README.md +13 -1
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/airy-media-grid.md +86 -87
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/airy-modular-clarity.md +84 -102
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/airy-structured-clarity.md +73 -97
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/ambient-halo-layered.md +91 -95
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/basic-tokens.json +101 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/dark-focus-layered.md +82 -110
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/dark-luminous-modular.md +86 -94
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/deep-stage-duotone.md +91 -98
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/hairline-runway-clarity.md +88 -99
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/high-contrast-modular.md +84 -109
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/interlocked-vivid-modules.md +83 -88
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/matrix-grid-focus.md +83 -91
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/media-rail-inspector.md +87 -85
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/mist-layered-signal.md +88 -95
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/modular-rail-signal.md +90 -101
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/mono-grid-signal.md +87 -99
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/ribbon-ledger-lift.md +85 -91
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/segmented-meter-clarity.md +84 -92
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/split-rail-precision.md +85 -87
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/status-framed-media-grid.md +82 -85
- package/yida-skills/skills/yida-design/workflow/output-design.md +1 -1
- package/yida-skills/skills/yida-design/workflow/step-1-read-brief.md +1 -1
- package/yida-skills/skills/yida-image-assets/SKILL.md +48 -51
- package/yida-skills/skills/yida-image-assets/references/manifest-contract.md +50 -54
- package/yida-skills/skills/yida-image-assets/references/source-policy.md +14 -15
- package/yida-skills/skills/yida-nav-group/SKILL.md +4 -2
- package/yida-skills/skills/yida-nav-shell/SKILL.md +23 -6
- package/yida-skills/skills/yida-nav-shell/references/nav-shell-patterns.md +6 -6
- package/yida-skills/skills/yida-page-config/SKILL.md +3 -3
- package/yida-skills/skills/yida-prd/SKILL.md +2 -1
- package/yida-skills/skills/yida-prd/references/app/blueprint.md +1 -1
- package/yida-skills/skills/yida-prd/references/app/navigation-patterns.md +6 -3
- package/yida-skills/skills/yida-prd/references/plan-content.md +4 -0
- package/yida-skills/skills/yida-prd/workflow/output-prd.md +13 -7
- package/yida-skills/skills/yida-prd/workflow/plan-business.md +3 -3
- package/yida-skills/skills/yida-prd/workflow/step-2-information-architecture.md +7 -5
- package/yida-skills/skills/yida-publish-page/SKILL.md +1 -0
- package/yida-skills/skills/yida-requirement-analysis/SKILL.md +10 -39
- package/yida-skills/skills/yida-requirement-analysis/references/experience-groups.md +23 -0
- package/yida-skills/skills/yida-requirement-analysis/references/handoff.md +64 -0
- package/yida-skills/skills/yida-requirement-analysis/workflow/prepare-brief.md +32 -73
- package/yida-skills/skills-index.json +6 -6
- package/yida-skills/skills/yida-design/references/app/blueprint.md +0 -70
- package/yida-skills/skills/yida-design/references/app/navigation-patterns.md +0 -43
- package/yida-skills/skills/yida-design/references/app/role-journey.md +0 -30
- package/yida-skills/skills/yida-design/workflow/output-prd.md +0 -185
package/README.md
CHANGED
|
@@ -396,11 +396,12 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
|
|
|
396
396
|
| `openyida app-list [--type managed\|created] [--page N] [--size N]` | Page through apps I manage or created |
|
|
397
397
|
| `openyida corp-efficiency [overview\|details\|detail\|groups\|notify] [options] [--open\|--no-open]` | Query enterprise efficiency overview and detail reports |
|
|
398
398
|
| `openyida create-app "<name>"\|--name <name> [options] [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]` | Create a Yida app |
|
|
399
|
+
| `openyida design-plan catalog [--json]` | List available planning themes and page patterns |
|
|
399
400
|
| `openyida design-plan init <requirement-brief.json> [--theme-id <id>] [--output-dir <dir>] [--json]` | Initialize a plan draft from confirmed requirements |
|
|
400
401
|
| `openyida design-plan preview <build-plan.json> --part-file <module.json> [--json]` | Update plan drafts by module |
|
|
401
402
|
| `openyida design-plan materialize <build-plan.json> [--from-preview \| --business-file <json> --visual-file <json>] [--output-dir <dir>] [--check] [--json]` | Generate and validate design-plan artifacts from build-plan.json |
|
|
402
|
-
| `openyida design-plan patch <build-plan.json> --set <path=value> [--set <path=value> ...] [--materialize] [--output-dir <dir>] [--json]` | Patch a design plan
|
|
403
|
-
| `openyida update-app <appType> [--name "..."] [--theme-file <css>] [--nav-theme light\|dark\|white\|gray] [--logo-source appIcon\|customImage] [--layout side\|top\|l_shape] [--hide-app-nav\|--show-app-nav]` | Update app info |
|
|
403
|
+
| `openyida design-plan patch <build-plan.json> --set <path=value> [--set <path=value> ...] [--materialize] [--output-dir <dir>] [--json]` | Patch a design plan; asset progress preserves existing approval |
|
|
404
|
+
| `openyida update-app <appType> [--name "..."] [--desc "..."] [--icon <name>] [--icon-color <color>] [--colour <key>] [--theme-color <color>] [--theme-file <css>] [--nav-theme light\|dark\|white\|gray] [--logo-source appIcon\|customImage] [--layout side\|top\|l_shape] [--hide-app-nav\|--show-app-nav]` | Update app info |
|
|
404
405
|
| `openyida app-online <appType> [--to-ding-app-center] [--show-app-center]` | Enable a Yida app |
|
|
405
406
|
| `openyida app-offline <appType> [--to-ding-app-center] [--show-app-center]` | Disable a Yida app |
|
|
406
407
|
| `openyida nav-group <list\|create\|rename\|delete\|move\|order\|auto-order\|hide\|show> <appType> ...` | Manage app sidebar navigation groups |
|
|
@@ -649,10 +650,14 @@ npx clawhub@latest install nicky1108/yida-app
|
|
|
649
650
|
```bash
|
|
650
651
|
git clone https://github.com/openyida/openyida.git
|
|
651
652
|
cd openyida
|
|
652
|
-
|
|
653
|
+
nvm install
|
|
654
|
+
nvm use
|
|
655
|
+
npm ci --ignore-scripts
|
|
653
656
|
npm run check:ci
|
|
654
657
|
```
|
|
655
658
|
|
|
659
|
+
Use the Node.js version in `.nvmrc` to match CI validation. If you do not use nvm, install that version directly.
|
|
660
|
+
|
|
656
661
|
Useful checks:
|
|
657
662
|
|
|
658
663
|
| Command | Purpose |
|
|
@@ -50,6 +50,7 @@ const {
|
|
|
50
50
|
} = require('./form-detail-link-guard');
|
|
51
51
|
const { t } = require('../core/i18n');
|
|
52
52
|
const { assertInlineCssStructure } = require('./inline-css-guard');
|
|
53
|
+
const { assertCanvasThemeStructure } = require('./canvas-theme-guard');
|
|
53
54
|
|
|
54
55
|
/**
|
|
55
56
|
* 依赖 → window 别名白名单,用于把 import 改写到运行时全局对象。
|
|
@@ -967,6 +968,7 @@ function compileCanvasLocal(source, options = {}) {
|
|
|
967
968
|
assertNoBareDependencyGlobals(source, options);
|
|
968
969
|
assertNoManualDependencyGlobals(source, options);
|
|
969
970
|
assertInlineCssStructure(source, options);
|
|
971
|
+
assertCanvasThemeStructure(source, options);
|
|
970
972
|
|
|
971
973
|
const importedModules = extractImportedModules(source);
|
|
972
974
|
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Babel = require('@babel/standalone');
|
|
4
|
+
const { CliError } = require('../core/cli-error');
|
|
5
|
+
const { t } = require('../core/i18n');
|
|
6
|
+
|
|
7
|
+
// Check the maintained theme contract without executing business code.
|
|
8
|
+
// Dynamic component factories and runtime branches still need browser verification.
|
|
9
|
+
function assertCanvasThemeStructure(source, options = {}) {
|
|
10
|
+
if (!/CanvasThemeProvider|useCanvasThemeContext|CanvasThemeContext/.test(source)) { return; }
|
|
11
|
+
let ast;
|
|
12
|
+
try {
|
|
13
|
+
ast = Babel.packages.parser.parse(source, { sourceType: 'module', plugins: ['jsx', 'typescript'] });
|
|
14
|
+
} catch { return; } // Let the compiler report syntax errors.
|
|
15
|
+
const traverse = Babel.packages.traverse.default || Babel.packages.traverse;
|
|
16
|
+
const functions = new Map();
|
|
17
|
+
let program;
|
|
18
|
+
let defaultExport;
|
|
19
|
+
const fail = (kind, node) => {
|
|
20
|
+
const line = node?.loc?.start.line || 1;
|
|
21
|
+
throw new CliError(t(`publish.canvas_theme_${kind}`, line), {
|
|
22
|
+
code: 'OPENYIDA_CANVAS_THEME_PROVIDER_INVALID',
|
|
23
|
+
details: { stage: 'canvas_compile', sourcePath: options.sourcePath || '', line, issueType: kind },
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
traverse(ast, {
|
|
28
|
+
Program(p) { program = p; },
|
|
29
|
+
ExportDefaultDeclaration(p) { defaultExport = p.get('declaration'); },
|
|
30
|
+
Function(p) {
|
|
31
|
+
const name = p.node.id?.name || (p.parentPath.isVariableDeclarator() ? p.parentPath.node.id.name : '');
|
|
32
|
+
functions.set(p.node, { name, calls: [], renders: [], hooks: [] });
|
|
33
|
+
},
|
|
34
|
+
VariableDeclarator(p) {
|
|
35
|
+
if (p.node.id.name === 'CanvasThemeContext' && p.getFunctionParent()) {
|
|
36
|
+
fail('context_scope', p.node);
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
function resolveFunction(p, seen = new Set()) {
|
|
42
|
+
if (!p?.node || seen.has(p.node)) { return null; }
|
|
43
|
+
seen.add(p.node);
|
|
44
|
+
if (p.isFunction()) { return p.node; }
|
|
45
|
+
if (p.isVariableDeclarator()) { return resolveFunction(p.get('init'), seen); }
|
|
46
|
+
if (p.isIdentifier() || p.isJSXIdentifier()) {
|
|
47
|
+
return resolveFunction(p.scope.getBinding(p.node.name)?.path, seen);
|
|
48
|
+
}
|
|
49
|
+
// React.memo/forwardRef retain the wrapped component's theme requirements.
|
|
50
|
+
if (p.isCallExpression()) {
|
|
51
|
+
const callee = p.node.callee;
|
|
52
|
+
const name = callee.type === 'Identifier' ? callee.name : callee.property?.name;
|
|
53
|
+
if (['memo', 'forwardRef'].includes(name)) { return resolveFunction(p.get('arguments.0'), seen); }
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
traverse(ast, {
|
|
59
|
+
CallExpression(p) {
|
|
60
|
+
const owner = functions.get(p.getFunctionParent()?.node);
|
|
61
|
+
const target = resolveFunction(p.get('callee'));
|
|
62
|
+
const targetName = functions.get(target)?.name || p.node.callee.name;
|
|
63
|
+
if (targetName === 'useCanvasThemeContext') {
|
|
64
|
+
if (!owner) { fail('root_hook', p.node); }
|
|
65
|
+
owner.hooks.push(p.node);
|
|
66
|
+
}
|
|
67
|
+
if (!owner) { return; }
|
|
68
|
+
if (target) { owner.calls.push(target); }
|
|
69
|
+
const callee = p.node.callee;
|
|
70
|
+
if (callee.type === 'MemberExpression' && callee.object.name === 'React' && callee.property.name === 'createElement') {
|
|
71
|
+
const component = resolveFunction(p.get('arguments.0'));
|
|
72
|
+
if (component) { owner.renders.push(component); }
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
JSXOpeningElement(p) {
|
|
76
|
+
const owner = functions.get(p.getFunctionParent()?.node);
|
|
77
|
+
const component = resolveFunction(p.get('name'));
|
|
78
|
+
if (owner && component) { owner.renders.push(component); }
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const entry = resolveFunction(program.scope.getBinding('YidaComp')?.path) || resolveFunction(defaultExport);
|
|
83
|
+
if (!entry) { return; }
|
|
84
|
+
|
|
85
|
+
// Calling a hook before returning a Provider cannot read that Provider.
|
|
86
|
+
const directSeen = new Set();
|
|
87
|
+
function checkEntryCalls(node) {
|
|
88
|
+
if (directSeen.has(node)) { return; }
|
|
89
|
+
directSeen.add(node);
|
|
90
|
+
const info = functions.get(node);
|
|
91
|
+
if (info.hooks.length) { fail('root_hook', info.hooks[0]); }
|
|
92
|
+
info.calls.forEach(checkEntryCalls);
|
|
93
|
+
}
|
|
94
|
+
checkEntryCalls(entry);
|
|
95
|
+
|
|
96
|
+
const seen = new Set();
|
|
97
|
+
let providerMounted = false;
|
|
98
|
+
let firstHook;
|
|
99
|
+
function visit(node) {
|
|
100
|
+
if (seen.has(node)) { return; }
|
|
101
|
+
seen.add(node);
|
|
102
|
+
const info = functions.get(node);
|
|
103
|
+
firstHook = firstHook || info.hooks[0];
|
|
104
|
+
info.renders.forEach(child => {
|
|
105
|
+
if (functions.get(child).name === 'CanvasThemeProvider') { providerMounted = true; }
|
|
106
|
+
visit(child);
|
|
107
|
+
});
|
|
108
|
+
info.calls.forEach(visit);
|
|
109
|
+
}
|
|
110
|
+
visit(entry);
|
|
111
|
+
const hasProvider = [...functions.values()].some(info => info.name === 'CanvasThemeProvider');
|
|
112
|
+
if (!providerMounted && (hasProvider || firstHook)) {
|
|
113
|
+
fail('provider_missing', firstHook || entry);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = { assertCanvasThemeStructure };
|
|
@@ -225,6 +225,25 @@ function readbackMismatch(formUuid, form, schema) {
|
|
|
225
225
|
});
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
+
function buildPartialFailureRecovery(results) {
|
|
229
|
+
const items = Object.values(results || {});
|
|
230
|
+
const hasUnknownWrite = items.some(item =>
|
|
231
|
+
item && (item.status === 'running' || (item.status === 'failed' && !item.formUuid))
|
|
232
|
+
);
|
|
233
|
+
const recoveryAction = hasUnknownWrite
|
|
234
|
+
? 'inspect_unknown_write_then_reconcile'
|
|
235
|
+
: 'rerun_unchanged_plan';
|
|
236
|
+
return { recoveryAction, nextAction: recoveryAction };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function buildDeliveryUrls(baseUrl, appType, formUuid) {
|
|
240
|
+
const normalizedBaseUrl = String(baseUrl || '').replace(/\/+$/, '');
|
|
241
|
+
if (!normalizedBaseUrl || !appType || !formUuid) { return {}; }
|
|
242
|
+
const appUrl = `${normalizedBaseUrl}/${appType}/workbench`;
|
|
243
|
+
const formUrl = `${appUrl}/${formUuid}`;
|
|
244
|
+
return { url: formUrl, formUrl, appUrl };
|
|
245
|
+
}
|
|
246
|
+
|
|
228
247
|
function execute(args, { execFile: execFileImpl = execFile } = {}) {
|
|
229
248
|
return new Promise((resolve, reject) => {
|
|
230
249
|
execFileImpl(process.execPath, [path.resolve(__dirname, '../../../bin/yida.js'), ...args, '--quiet'], {
|
|
@@ -317,13 +336,15 @@ async function run(args, dependencies = {}) {
|
|
|
317
336
|
try {
|
|
318
337
|
const state = fs.existsSync(stateFile) ? JSON.parse(fs.readFileSync(stateFile, 'utf8')) : { fingerprint, appType: options.appType, results: {} };
|
|
319
338
|
if (state.fingerprint !== fingerprint) { invalid('state belongs to a different plan; reconcile existing resources before preparing a new batch'); }
|
|
320
|
-
await call(['login', '--check-only', '--json']);
|
|
339
|
+
const loginStatus = await call(['login', '--check-only', '--json']);
|
|
340
|
+
const baseUrl = loginStatus?.base_url || loginStatus?.baseUrl || '';
|
|
321
341
|
// Read back completed resources before their IDs can be used by dependent forms.
|
|
322
342
|
for (const form of forms.filter(item => state.results[item.key]?.status === 'success')) {
|
|
323
343
|
const item = state.results[form.key];
|
|
324
344
|
const schema = await call(['get-schema', options.appType, item.formUuid, '--field-map-json']);
|
|
325
345
|
if (schema.formUuid !== item.formUuid || !Array.isArray(schema.fields)) { invalid(`schema: ${form.key}`); }
|
|
326
346
|
item.fields = schema.fields;
|
|
347
|
+
Object.assign(item, buildDeliveryUrls(baseUrl, options.appType, item.formUuid));
|
|
327
348
|
}
|
|
328
349
|
const resolve = (key, field) => {
|
|
329
350
|
const item = state.results[key];
|
|
@@ -336,6 +357,7 @@ async function run(args, dependencies = {}) {
|
|
|
336
357
|
await schedule(forms, options.concurrency, state.results, async form => {
|
|
337
358
|
let formUuid = form.formUuid || state.results[form.key]?.formUuid;
|
|
338
359
|
let shouldResume = Boolean(!form.formUuid && formUuid);
|
|
360
|
+
let delivery = buildDeliveryUrls(baseUrl, options.appType, formUuid);
|
|
339
361
|
const resolvedFields = mapReferences(form.fields, resolve);
|
|
340
362
|
if (!formUuid) {
|
|
341
363
|
const argv = ['create-form', 'create', options.appType, form.title, JSON.stringify(resolvedFields), '--no-open'];
|
|
@@ -344,6 +366,12 @@ async function run(args, dependencies = {}) {
|
|
|
344
366
|
const created = await call(argv);
|
|
345
367
|
if (typeof created.formUuid !== 'string' || !created.formUuid.startsWith('FORM')) { invalid(`create result: ${form.key}`); }
|
|
346
368
|
formUuid = created.formUuid;
|
|
369
|
+
delivery = {
|
|
370
|
+
...buildDeliveryUrls(baseUrl, options.appType, formUuid),
|
|
371
|
+
...(created.url ? { url: created.url } : {}),
|
|
372
|
+
...(created.formUrl ? { formUrl: created.formUrl } : {}),
|
|
373
|
+
...(created.appUrl ? { appUrl: created.appUrl } : {}),
|
|
374
|
+
};
|
|
347
375
|
} catch (error) {
|
|
348
376
|
const createdFormUuid = error.output?.formUuid || error.output?.details?.formUuid;
|
|
349
377
|
if (typeof createdFormUuid !== 'string' || !createdFormUuid.startsWith('FORM')) {
|
|
@@ -357,25 +385,43 @@ async function run(args, dependencies = {}) {
|
|
|
357
385
|
save(state);
|
|
358
386
|
let resumed = false;
|
|
359
387
|
if (shouldResume) {
|
|
360
|
-
await call(['create-form', 'resume', options.appType, formUuid, JSON.stringify(resolvedFields), '--json']);
|
|
388
|
+
const resumeOutput = await call(['create-form', 'resume', options.appType, formUuid, JSON.stringify(resolvedFields), '--json']);
|
|
389
|
+
delivery = {
|
|
390
|
+
...buildDeliveryUrls(baseUrl, options.appType, formUuid),
|
|
391
|
+
...(resumeOutput.url ? { url: resumeOutput.url } : {}),
|
|
392
|
+
...(resumeOutput.formUrl ? { formUrl: resumeOutput.formUrl } : {}),
|
|
393
|
+
...(resumeOutput.appUrl ? { appUrl: resumeOutput.appUrl } : {}),
|
|
394
|
+
};
|
|
361
395
|
resumed = true;
|
|
362
396
|
}
|
|
363
397
|
let schema = await call(['get-schema', options.appType, formUuid, '--field-map-json']);
|
|
364
398
|
if (!readbackMatchesExpectedFields(schema, resolvedFields) && !resumed && !form.formUuid) {
|
|
365
|
-
await call(['create-form', 'resume', options.appType, formUuid, JSON.stringify(resolvedFields), '--json']);
|
|
399
|
+
const resumeOutput = await call(['create-form', 'resume', options.appType, formUuid, JSON.stringify(resolvedFields), '--json']);
|
|
400
|
+
delivery = {
|
|
401
|
+
...buildDeliveryUrls(baseUrl, options.appType, formUuid),
|
|
402
|
+
...(resumeOutput.url ? { url: resumeOutput.url } : {}),
|
|
403
|
+
...(resumeOutput.formUrl ? { formUrl: resumeOutput.formUrl } : {}),
|
|
404
|
+
...(resumeOutput.appUrl ? { appUrl: resumeOutput.appUrl } : {}),
|
|
405
|
+
};
|
|
366
406
|
resumed = true;
|
|
367
407
|
schema = await call(['get-schema', options.appType, formUuid, '--field-map-json']);
|
|
368
408
|
}
|
|
369
409
|
if (schema.formUuid !== formUuid || !readbackMatchesExpectedFields(schema, resolvedFields)) {
|
|
370
410
|
throw readbackMismatch(formUuid, { ...form, fields: resolvedFields }, schema);
|
|
371
411
|
}
|
|
372
|
-
return { formUuid, fields: schema.fields };
|
|
412
|
+
return { formUuid, fields: schema.fields, ...delivery };
|
|
373
413
|
}, () => save(state));
|
|
374
414
|
const success = Object.values(state.results).every(item => item.status === 'success');
|
|
375
|
-
const output = {
|
|
415
|
+
const output = {
|
|
416
|
+
success,
|
|
417
|
+
groups,
|
|
418
|
+
stateFile,
|
|
419
|
+
results: state.results,
|
|
420
|
+
...((baseUrl && options.appType) ? { appUrl: `${String(baseUrl).replace(/\/+$/, '')}/${options.appType}/workbench` } : {}),
|
|
421
|
+
};
|
|
376
422
|
if (!success) {
|
|
377
423
|
output.errorCode = 'FORM_BATCH_PARTIAL_FAILURE';
|
|
378
|
-
output
|
|
424
|
+
Object.assign(output, buildPartialFailureRecovery(state.results));
|
|
379
425
|
}
|
|
380
426
|
console.log(JSON.stringify(output));
|
|
381
427
|
if (!output.success) { process.exitCode = 1; }
|
|
@@ -397,5 +443,7 @@ module.exports = {
|
|
|
397
443
|
execute,
|
|
398
444
|
expectedReadbackFields,
|
|
399
445
|
readbackMatchesExpectedFields,
|
|
446
|
+
buildPartialFailureRecovery,
|
|
447
|
+
buildDeliveryUrls,
|
|
400
448
|
validateStaticDefinition,
|
|
401
449
|
};
|
package/lib/app/create-form.js
CHANGED
|
@@ -1719,7 +1719,7 @@ function buildFormSchema(formTitle, fields, formUuid, corpId, appType, layout, t
|
|
|
1719
1719
|
id: nextNodeId(),
|
|
1720
1720
|
props: {
|
|
1721
1721
|
contentBgColor: 'white',
|
|
1722
|
-
pageStyle: { backgroundColor: '
|
|
1722
|
+
pageStyle: { backgroundColor: 'transparent' },
|
|
1723
1723
|
contentMargin: '20',
|
|
1724
1724
|
contentPadding: '20',
|
|
1725
1725
|
showTitle: false,
|
|
@@ -1742,7 +1742,7 @@ function buildFormSchema(formTitle, fields, formUuid, corpId, appType, layout, t
|
|
|
1742
1742
|
labelWeightMobile: 'normal',
|
|
1743
1743
|
},
|
|
1744
1744
|
condition: true,
|
|
1745
|
-
css: 'body{background-color
|
|
1745
|
+
css: 'body{background-color:transparent}',
|
|
1746
1746
|
methods: {
|
|
1747
1747
|
__initMethods__: {
|
|
1748
1748
|
type: 'js',
|
|
@@ -1941,7 +1941,7 @@ function buildEmptyFormSchema() {
|
|
|
1941
1941
|
id: nextNodeId(),
|
|
1942
1942
|
props: {
|
|
1943
1943
|
contentBgColor: 'white',
|
|
1944
|
-
pageStyle: { backgroundColor: '
|
|
1944
|
+
pageStyle: { backgroundColor: 'transparent' },
|
|
1945
1945
|
contentMargin: '20',
|
|
1946
1946
|
contentPadding: '20',
|
|
1947
1947
|
showTitle: false,
|
|
@@ -1952,7 +1952,7 @@ function buildEmptyFormSchema() {
|
|
|
1952
1952
|
contentBgColorMobile: 'white',
|
|
1953
1953
|
},
|
|
1954
1954
|
condition: true,
|
|
1955
|
-
css: 'body{background-color
|
|
1955
|
+
css: 'body{background-color:transparent}',
|
|
1956
1956
|
methods: {
|
|
1957
1957
|
__initMethods__: {
|
|
1958
1958
|
type: 'js',
|
|
@@ -4858,7 +4858,10 @@ async function saveFormSchema(authRef, appType, formUuid, schema, version, stepO
|
|
|
4858
4858
|
formUuid,
|
|
4859
4859
|
result: sanitizeFailureResult(saveResult),
|
|
4860
4860
|
});
|
|
4861
|
-
if (failureContext) {
|
|
4861
|
+
if (failureContext && failureContext.deferFailureOutput) {
|
|
4862
|
+
// Resume may safely resolve an HTTP 5xx by exact readback. Defer the
|
|
4863
|
+
// failure payload so a recovered command emits one authoritative result.
|
|
4864
|
+
} else if (failureContext) {
|
|
4862
4865
|
emitCreateFormPostCreateFailure(Object.assign({}, failureContext, {
|
|
4863
4866
|
stage: 'saveFormSchema',
|
|
4864
4867
|
error: saveError,
|
|
@@ -4879,6 +4882,13 @@ async function saveFormSchema(authRef, appType, formUuid, schema, version, stepO
|
|
|
4879
4882
|
|
|
4880
4883
|
// ── create 模式主流程 ─────────────────────────────────
|
|
4881
4884
|
|
|
4885
|
+
function buildFormDeliveryUrls(baseUrl, appType, formUuid) {
|
|
4886
|
+
return {
|
|
4887
|
+
appUrl: baseUrl + '/' + appType + '/workbench',
|
|
4888
|
+
formUrl: baseUrl + '/' + appType + '/workbench/' + formUuid,
|
|
4889
|
+
};
|
|
4890
|
+
}
|
|
4891
|
+
|
|
4882
4892
|
async function mainValidateFields(parsedArgs) {
|
|
4883
4893
|
const { fieldsJsonOrFile } = parsedArgs;
|
|
4884
4894
|
assertNoEmojiInDefinitionFileName(fieldsJsonOrFile);
|
|
@@ -5034,13 +5044,13 @@ async function mainCreate(parsedArgs, authRef) {
|
|
|
5034
5044
|
}
|
|
5035
5045
|
|
|
5036
5046
|
// 输出结果
|
|
5037
|
-
const formUrl = authRef.baseUrl
|
|
5047
|
+
const { appUrl, formUrl } = buildFormDeliveryUrls(authRef.baseUrl, appType, formUuid);
|
|
5038
5048
|
result(true, t('create_form.create_success'), [
|
|
5039
5049
|
['Form UUID', formUuid],
|
|
5040
5050
|
['URL', formUrl],
|
|
5041
5051
|
]);
|
|
5042
5052
|
console.log(JSON.stringify(withBrowserHandoff(
|
|
5043
|
-
{ success: true, formUuid, formTitle, appType, fieldCount, icon: formIcon, iconSource: iconResolution.source, url: formUrl },
|
|
5053
|
+
{ success: true, formUuid, formTitle, appType, fieldCount, icon: formIcon, iconSource: iconResolution.source, url: formUrl, formUrl, appUrl },
|
|
5044
5054
|
formUrl,
|
|
5045
5055
|
{ stage: 'create_form_success', title: formTitle },
|
|
5046
5056
|
parsedArgs.browserOpenMode
|
|
@@ -5127,6 +5137,7 @@ async function createFormForLegacyProcessQuiet(context, input) {
|
|
|
5127
5137
|
}
|
|
5128
5138
|
const saveResult = await saveFormSchema(authRef, appType, formUuid, schema, serverRevision, 4);
|
|
5129
5139
|
const navIconResult = await updateCreatedFormNavigationIcon(authRef, appType, formUuid, formIcon);
|
|
5140
|
+
const { appUrl, formUrl } = buildFormDeliveryUrls(authRef.baseUrl, appType, formUuid);
|
|
5130
5141
|
return {
|
|
5131
5142
|
success: true,
|
|
5132
5143
|
appType,
|
|
@@ -5138,7 +5149,9 @@ async function createFormForLegacyProcessQuiet(context, input) {
|
|
|
5138
5149
|
navIconResult,
|
|
5139
5150
|
icon: formIcon,
|
|
5140
5151
|
iconSource: iconResolution.source,
|
|
5141
|
-
url:
|
|
5152
|
+
url: formUrl,
|
|
5153
|
+
formUrl,
|
|
5154
|
+
appUrl,
|
|
5142
5155
|
};
|
|
5143
5156
|
}
|
|
5144
5157
|
|
|
@@ -5319,7 +5332,7 @@ async function mainAddOption(parsedArgs, authRef) {
|
|
|
5319
5332
|
// Step 4: 保存 Schema
|
|
5320
5333
|
await saveFormSchema(authRef, appType, formUuid, schema, version, 4);
|
|
5321
5334
|
|
|
5322
|
-
const formUrl = authRef.baseUrl
|
|
5335
|
+
const { appUrl, formUrl } = buildFormDeliveryUrls(authRef.baseUrl, appType, formUuid);
|
|
5323
5336
|
result(true, '选项追加成功', [
|
|
5324
5337
|
['Form UUID', formUuid],
|
|
5325
5338
|
['Field', fieldLabel],
|
|
@@ -5339,6 +5352,8 @@ async function mainAddOption(parsedArgs, authRef) {
|
|
|
5339
5352
|
skipped: skippedOptions,
|
|
5340
5353
|
totalOptions: existingDataSource.length,
|
|
5341
5354
|
url: formUrl,
|
|
5355
|
+
formUrl,
|
|
5356
|
+
appUrl,
|
|
5342
5357
|
}));
|
|
5343
5358
|
}
|
|
5344
5359
|
|
|
@@ -5438,7 +5453,7 @@ async function mainBindDataSource(parsedArgs, authRef) {
|
|
|
5438
5453
|
|
|
5439
5454
|
await saveFormSchema(authRef, appType, formUuid, schema, version, 4);
|
|
5440
5455
|
|
|
5441
|
-
const formUrl = authRef.baseUrl
|
|
5456
|
+
const { appUrl, formUrl } = buildFormDeliveryUrls(authRef.baseUrl, appType, formUuid);
|
|
5442
5457
|
result(true, '字段数据源保存成功', [
|
|
5443
5458
|
['Form UUID', formUuid],
|
|
5444
5459
|
['Field', fieldLabel],
|
|
@@ -5459,6 +5474,8 @@ async function mainBindDataSource(parsedArgs, authRef) {
|
|
|
5459
5474
|
options: normalized.options.length,
|
|
5460
5475
|
filterLocal: targetComponent.props.filterLocal,
|
|
5461
5476
|
pageUrl: formUrl,
|
|
5477
|
+
formUrl,
|
|
5478
|
+
appUrl,
|
|
5462
5479
|
},
|
|
5463
5480
|
formUrl,
|
|
5464
5481
|
{ stage: 'bind_datasource_success', title: formUuid },
|
|
@@ -6109,6 +6126,59 @@ async function readResumeSchema(authRef, appType, formUuid) {
|
|
|
6109
6126
|
return { schemaResult, schema, version: extractSchemaServerRevision(schemaResult) };
|
|
6110
6127
|
}
|
|
6111
6128
|
|
|
6129
|
+
function isRetryableResumeSaveServerFailure(errorObject) {
|
|
6130
|
+
const status = Number(errorObject?.details?.result?.__httpStatus);
|
|
6131
|
+
return errorObject?.code === 'CREATE_FORM_SAVE_SCHEMA_FAILED' &&
|
|
6132
|
+
Number.isInteger(status) && status >= 500 && status <= 599;
|
|
6133
|
+
}
|
|
6134
|
+
|
|
6135
|
+
async function saveResumeSchemaWithSafeRetry(authRef, appType, formUuid, schema, version, fields) {
|
|
6136
|
+
const save = (value, revision) => saveFormSchema(
|
|
6137
|
+
authRef, appType, formUuid, value, revision, 4, { deferFailureOutput: true }
|
|
6138
|
+
);
|
|
6139
|
+
try {
|
|
6140
|
+
await save(schema, version);
|
|
6141
|
+
return '';
|
|
6142
|
+
} catch (originalError) {
|
|
6143
|
+
if (!isRetryableResumeSaveServerFailure(originalError)) { throw originalError; }
|
|
6144
|
+
|
|
6145
|
+
let readback;
|
|
6146
|
+
try { readback = await readResumeSchema(authRef, appType, formUuid); } catch (_) { throw originalError; }
|
|
6147
|
+
const root = readback.schema.pages[0].componentsTree && readback.schema.pages[0].componentsTree[0];
|
|
6148
|
+
const container = root ? findFormContainer(root) : null;
|
|
6149
|
+
if (!container) { throw originalError; }
|
|
6150
|
+
const evidence = collectResumeFieldEvidence(container.children);
|
|
6151
|
+
const verified = buildResumeChanges(
|
|
6152
|
+
evidence,
|
|
6153
|
+
collectResumeDesiredFields(fields, { verifyNestedFields: true })
|
|
6154
|
+
);
|
|
6155
|
+
if (verified.conflicts.length > 0) { throw originalError; }
|
|
6156
|
+
if (verified.missing.length === 0) { return 'save_schema_recovered_by_readback'; }
|
|
6157
|
+
const retryPlan = buildResumeChanges(evidence, collectResumeDesiredFields(fields));
|
|
6158
|
+
if (retryPlan.conflicts.length > 0 || retryPlan.missing.length === 0) { throw originalError; }
|
|
6159
|
+
const applied = applyChangesToSchema(
|
|
6160
|
+
readback.schema,
|
|
6161
|
+
retryPlan.missing.map(function (field) { return { action: 'add', field }; }),
|
|
6162
|
+
{ verbose: true }
|
|
6163
|
+
);
|
|
6164
|
+
if ((applied.diagnostics || []).length > 0) { throw originalError; }
|
|
6165
|
+
const updatedContainer = findFormContainer(readback.schema.pages[0].componentsTree[0]);
|
|
6166
|
+
fillSerialNumberFormulas(updatedContainer.children, resolveCorpId(authRef.authData), appType, formUuid);
|
|
6167
|
+
|
|
6168
|
+
try {
|
|
6169
|
+
await save(readback.schema, readback.version);
|
|
6170
|
+
} catch (retryError) {
|
|
6171
|
+
originalError.details = Object.assign({}, originalError.details, {
|
|
6172
|
+
safeRetryAttempted: true,
|
|
6173
|
+
retryErrorCode: retryError?.code || 'CREATE_FORM_SAVE_SCHEMA_FAILED',
|
|
6174
|
+
retryResult: sanitizeFailureResult(retryError?.details?.result),
|
|
6175
|
+
});
|
|
6176
|
+
throw originalError;
|
|
6177
|
+
}
|
|
6178
|
+
return 'save_schema_retried';
|
|
6179
|
+
}
|
|
6180
|
+
}
|
|
6181
|
+
|
|
6112
6182
|
async function mainResume(parsedArgs, authRef) {
|
|
6113
6183
|
const { appType, formUuid, fieldsJsonOrFile } = parsedArgs;
|
|
6114
6184
|
assertNoEmojiInDefinitionFileName(fieldsJsonOrFile);
|
|
@@ -6139,8 +6209,11 @@ async function mainResume(parsedArgs, authRef) {
|
|
|
6139
6209
|
if (resumeValidationRules.length > 0) {
|
|
6140
6210
|
applySmartValidations(firstRead.schema, resumeValidationRules);
|
|
6141
6211
|
}
|
|
6142
|
-
|
|
6212
|
+
const saveRecoveryStage = await saveResumeSchemaWithSafeRetry(
|
|
6213
|
+
authRef, appType, formUuid, firstRead.schema, firstRead.version, fields
|
|
6214
|
+
);
|
|
6143
6215
|
completedStages.push('rebuild_blank_schema', 'save_schema');
|
|
6216
|
+
if (saveRecoveryStage) { completedStages.push(saveRecoveryStage); }
|
|
6144
6217
|
recoveredBlankShell = true;
|
|
6145
6218
|
plan = {
|
|
6146
6219
|
existing: [],
|
|
@@ -6189,8 +6262,11 @@ async function mainResume(parsedArgs, authRef) {
|
|
|
6189
6262
|
const corpId = resolveCorpId(authRef.authData);
|
|
6190
6263
|
const updatedContainer = findFormContainer(firstRead.schema.pages[0].componentsTree[0]);
|
|
6191
6264
|
fillSerialNumberFormulas(updatedContainer.children, corpId, appType, formUuid);
|
|
6192
|
-
|
|
6265
|
+
const saveRecoveryStage = await saveResumeSchemaWithSafeRetry(
|
|
6266
|
+
authRef, appType, formUuid, firstRead.schema, firstRead.version, fields
|
|
6267
|
+
);
|
|
6193
6268
|
completedStages.push('add_missing_fields', 'save_schema');
|
|
6269
|
+
if (saveRecoveryStage) { completedStages.push(saveRecoveryStage); }
|
|
6194
6270
|
}
|
|
6195
6271
|
|
|
6196
6272
|
const finalRead = await readResumeSchema(authRef, appType, formUuid);
|
|
@@ -6211,7 +6287,7 @@ async function mainResume(parsedArgs, authRef) {
|
|
|
6211
6287
|
});
|
|
6212
6288
|
}
|
|
6213
6289
|
completedStages.push('verify_final_schema');
|
|
6214
|
-
const formUrl = authRef.baseUrl
|
|
6290
|
+
const { appUrl, formUrl } = buildFormDeliveryUrls(authRef.baseUrl, appType, formUuid);
|
|
6215
6291
|
const output = {
|
|
6216
6292
|
success: true,
|
|
6217
6293
|
appType,
|
|
@@ -6223,6 +6299,8 @@ async function mainResume(parsedArgs, authRef) {
|
|
|
6223
6299
|
finalFieldCount: collectResumeFieldEvidence(finalContainer.children).length,
|
|
6224
6300
|
recoveredBlankShell,
|
|
6225
6301
|
url: formUrl,
|
|
6302
|
+
formUrl,
|
|
6303
|
+
appUrl,
|
|
6226
6304
|
};
|
|
6227
6305
|
console.log(JSON.stringify(withBrowserHandoff(
|
|
6228
6306
|
output,
|
|
@@ -6384,14 +6462,14 @@ async function mainUpdate(parsedArgs, authRef) {
|
|
|
6384
6462
|
await saveFormSchema(authRef, appType, formUuid, schema, version, 6);
|
|
6385
6463
|
|
|
6386
6464
|
// 输出结果
|
|
6387
|
-
const formUrl = authRef.baseUrl
|
|
6465
|
+
const { appUrl, formUrl } = buildFormDeliveryUrls(authRef.baseUrl, appType, formUuid);
|
|
6388
6466
|
result(true, t('create_form.update_success'), [
|
|
6389
6467
|
['Form UUID', formUuid],
|
|
6390
6468
|
['URL', formUrl],
|
|
6391
6469
|
['Changes', String(appliedChanges.length)],
|
|
6392
6470
|
]);
|
|
6393
6471
|
console.log(JSON.stringify(withBrowserHandoff(
|
|
6394
|
-
{ success: true, formUuid, appType, changesApplied: appliedChanges.length, changes: appliedChanges, url: formUrl },
|
|
6472
|
+
{ success: true, formUuid, appType, changesApplied: appliedChanges.length, changes: appliedChanges, url: formUrl, formUrl, appUrl },
|
|
6395
6473
|
formUrl,
|
|
6396
6474
|
{ stage: 'update_form_success', title: formUuid },
|
|
6397
6475
|
parsedArgs.browserOpenMode
|
|
@@ -1615,7 +1615,7 @@ function buildFormSchema(formTitle, fields, formUuid, corpId, appType, layout, t
|
|
|
1615
1615
|
id: nextNodeId(),
|
|
1616
1616
|
props: {
|
|
1617
1617
|
contentBgColor: 'white',
|
|
1618
|
-
pageStyle: { backgroundColor: '
|
|
1618
|
+
pageStyle: { backgroundColor: 'transparent' },
|
|
1619
1619
|
contentMargin: '20',
|
|
1620
1620
|
contentPadding: '20',
|
|
1621
1621
|
showTitle: false,
|
|
@@ -1638,7 +1638,7 @@ function buildFormSchema(formTitle, fields, formUuid, corpId, appType, layout, t
|
|
|
1638
1638
|
labelWeightMobile: 'normal',
|
|
1639
1639
|
},
|
|
1640
1640
|
condition: true,
|
|
1641
|
-
css: 'body{background-color
|
|
1641
|
+
css: 'body{background-color:transparent}',
|
|
1642
1642
|
methods: {
|
|
1643
1643
|
__initMethods__: {
|
|
1644
1644
|
type: 'js',
|
|
@@ -1833,7 +1833,7 @@ function buildEmptyFormSchema() {
|
|
|
1833
1833
|
id: nextNodeId(),
|
|
1834
1834
|
props: {
|
|
1835
1835
|
contentBgColor: 'white',
|
|
1836
|
-
pageStyle: { backgroundColor: '
|
|
1836
|
+
pageStyle: { backgroundColor: 'transparent' },
|
|
1837
1837
|
contentMargin: '20',
|
|
1838
1838
|
contentPadding: '20',
|
|
1839
1839
|
showTitle: false,
|
|
@@ -1844,7 +1844,7 @@ function buildEmptyFormSchema() {
|
|
|
1844
1844
|
contentBgColorMobile: 'white',
|
|
1845
1845
|
},
|
|
1846
1846
|
condition: true,
|
|
1847
|
-
css: 'body{background-color
|
|
1847
|
+
css: 'body{background-color:transparent}',
|
|
1848
1848
|
methods: {
|
|
1849
1849
|
__initMethods__: {
|
|
1850
1850
|
type: 'js',
|
|
@@ -76,9 +76,15 @@ function applyDesignTokens(template, markdown, previousMarkdown) {
|
|
|
76
76
|
let body = root[1].replace(declarations, (line, name, value) => line.replace(value, () => changed[name] || replaceBrand(value)));
|
|
77
77
|
const extra = Object.entries(changed).filter(([name]) => !(name in original));
|
|
78
78
|
body += extra.map(([name, value]) => ` ${name}: ${value};\n`).join('');
|
|
79
|
-
|
|
80
|
-
const tail = template.slice(root.index + root[0].length).replace(declarations,
|
|
79
|
+
let tail = template.slice(root.index + root[0].length).replace(declarations,
|
|
81
80
|
(line, name, value) => line.replace(value, () => replaceBrand(value)));
|
|
81
|
+
// Platform navigation modes bind independently; the root shell remains the custom-page default.
|
|
82
|
+
if (changed['--pod-shell-theme-bg-color']) {
|
|
83
|
+
tail = tail.replace(/(\.pod-premium\.nav-(light|dark)\s*\{)([^}]*)(\})/g,
|
|
84
|
+
(block, selector, tone, body, close) => selector + body.replace(declarations,
|
|
85
|
+
(line, name, value) => ['--pod-shell-theme-bg-color', '--pod-page-header-bg-color'].includes(name)
|
|
86
|
+
? line.replace(value, `var(--color-brand1-${tone === 'dark' ? 5 : 3})`) : line) + close);
|
|
87
|
+
}
|
|
82
88
|
const css = `${template.slice(0, root.index)}:root {${body}}${tail}`;
|
|
83
89
|
validateThemeCssContent(css);
|
|
84
90
|
extractThemeColor(css);
|
package/lib/app/theme-presets.js
CHANGED
|
@@ -122,7 +122,7 @@ function assertPresetThemeKey(themeKey) {
|
|
|
122
122
|
throw new Error(
|
|
123
123
|
`Unsupported theme: ${themeKey}. Use one of: ${formatPresetThemeKeys()}. ` +
|
|
124
124
|
'For custom colors or application theme token profiles that are not platform --theme keys, ' +
|
|
125
|
-
'upload an application theme CSS file with update-app --theme-file
|
|
125
|
+
'upload an application theme CSS file with update-app --theme-file.'
|
|
126
126
|
);
|
|
127
127
|
}
|
|
128
128
|
|
package/lib/asset/ai-image.js
CHANGED
|
@@ -73,18 +73,52 @@ function getFreeStockLibraries() {
|
|
|
73
73
|
function getMaterialSourcingGuidance() {
|
|
74
74
|
return {
|
|
75
75
|
steps: [
|
|
76
|
-
'1.
|
|
77
|
-
'2.
|
|
78
|
-
'3.
|
|
79
|
-
'4.
|
|
80
|
-
'5.
|
|
76
|
+
'1. Plan 确认后、Fast 设计就绪后启动素材任务,同时推进应用、表单和无图页面;先复用用户图片和已完成位置。',
|
|
77
|
+
'2. 同一页面的图片位置也并发搜索,最多同时四个;每个位置选一张来源完整的图片并查看。',
|
|
78
|
+
'3. 第二轮只补第一轮失败的必需位置,换一张图片或使用设计允许的生成图。',
|
|
79
|
+
'4. 两轮结束后,可选位置采用设计允许的无图布局,必需位置保留缺口;已就绪页面直接继续。',
|
|
80
|
+
'5. 每页使用 asset resolve --page-id 并发下载上传,写入独立清单并交接该页。',
|
|
81
81
|
],
|
|
82
|
+
schedulingPolicy: {
|
|
83
|
+
owner: 'host_agent',
|
|
84
|
+
searchConcurrency: 4,
|
|
85
|
+
concurrencyScope: 'collection_run',
|
|
86
|
+
searchUnit: 'slot',
|
|
87
|
+
resumePolicy: 'reuse_final_slots_and_remaining_round_budget',
|
|
88
|
+
runAlongside: ['app_creation', 'form_creation', 'pages_without_images'],
|
|
89
|
+
waitAt: 'own_page_image_binding_and_acceptance',
|
|
90
|
+
resultWriter: 'one_per_page',
|
|
91
|
+
},
|
|
92
|
+
collectionPolicy: {
|
|
93
|
+
maxRoundsPerPage: 2,
|
|
94
|
+
imagesPerSlot: 1,
|
|
95
|
+
candidatesPerSlotPerRound: 1,
|
|
96
|
+
secondRound: 'failed_required_slots_only',
|
|
97
|
+
concurrency: 'independent_pages_and_assets',
|
|
98
|
+
roundOwner: 'host_agent',
|
|
99
|
+
pageSelector: '--page-id',
|
|
100
|
+
},
|
|
101
|
+
completionPolicy: {
|
|
102
|
+
resultSource: 'asset-manifests/<pageId>.json',
|
|
103
|
+
planApproval: 'reuse_existing_approval',
|
|
104
|
+
progressFields: ['visualStyle.forUser.assetStrategy.materialStatus', 'visualStyle.forUser.assetStrategy.missingAssets'],
|
|
105
|
+
nextStep: 'continue_ready_pages',
|
|
106
|
+
},
|
|
107
|
+
failurePolicy: {
|
|
108
|
+
attemptsPerCandidate: 1,
|
|
109
|
+
sourceUnavailable: 'switch_source_for_remaining_slots',
|
|
110
|
+
candidateFailed: 'replace_input',
|
|
111
|
+
exhausted: 'planned_optional_layout_or_required_gap',
|
|
112
|
+
retryCondition: 'within_two_rounds_or_explicit_request',
|
|
113
|
+
},
|
|
82
114
|
libraries: getFreeStockLibraries(),
|
|
83
115
|
rules: [
|
|
84
116
|
'不编造图片 URL;按 manifest 的页面状态继续,只使用当前页 materialStatus=final 的图片。',
|
|
85
117
|
'默认下载并上传宜搭图片附件;超过 20 MiB 保留原链接;不额外探测 URL,下载失败跳过并记录缺口。',
|
|
86
118
|
'联网搜图仅使用 Unsplash / Pexels。',
|
|
87
|
-
'
|
|
119
|
+
'401、403、429、反爬挑战或超时后,将该来源标记为本次采集不可用,其他页面和后续轮次使用其他来源。',
|
|
120
|
+
'每页最多两轮;每个位置每轮选一张,同一候选尝试一次,成功即结束。第二轮只补失败的必需位置;换来源、换工具或恢复上传仍计入本轮预算。',
|
|
121
|
+
'来源信息获取失败时换用信息齐全的候选;真实房源、商品和人员保持真实素材要求。',
|
|
88
122
|
],
|
|
89
123
|
};
|
|
90
124
|
}
|