openxiangda 1.0.35 → 1.0.37

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 (57) hide show
  1. package/README.md +12 -0
  2. package/lib/cli.js +44 -4
  3. package/lib/workspace-bootstrap.js +238 -0
  4. package/openxiangda-skills/SKILL.md +43 -4
  5. package/openxiangda-skills/references/component-guide.md +10 -11
  6. package/openxiangda-skills/references/resource-manifest-cheatsheet.md +348 -0
  7. package/openxiangda-skills/references/style-system.md +14 -18
  8. package/openxiangda-skills/references/troubleshooting.md +13 -13
  9. package/openxiangda-skills/skills/openxiangda-app/SKILL.md +18 -2
  10. package/openxiangda-skills/skills/openxiangda-core/SKILL.md +37 -1
  11. package/openxiangda-skills/skills/openxiangda-form/SKILL.md +37 -2
  12. package/openxiangda-skills/skills/openxiangda-inspect/SKILL.md +26 -2
  13. package/openxiangda-skills/skills/openxiangda-page/SKILL.md +38 -4
  14. package/openxiangda-skills/skills/openxiangda-permission-settings/SKILL.md +19 -2
  15. package/openxiangda-skills/skills/openxiangda-workflow-automation/SKILL.md +26 -2
  16. package/package.json +1 -1
  17. package/packages/sdk/dist/runtime/index.cjs +67 -30
  18. package/packages/sdk/dist/runtime/index.cjs.map +1 -1
  19. package/packages/sdk/dist/runtime/index.mjs +67 -30
  20. package/packages/sdk/dist/runtime/index.mjs.map +1 -1
  21. package/packages/sdk/dist/styles/antd-theme.cjs +11 -3
  22. package/packages/sdk/dist/styles/antd-theme.cjs.map +1 -1
  23. package/packages/sdk/dist/styles/antd-theme.d.mts +2 -1
  24. package/packages/sdk/dist/styles/antd-theme.d.ts +2 -1
  25. package/packages/sdk/dist/styles/antd-theme.mjs +11 -3
  26. package/packages/sdk/dist/styles/antd-theme.mjs.map +1 -1
  27. package/packages/sdk/dist/styles/tailwind-preset.cjs +0 -1
  28. package/packages/sdk/dist/styles/tailwind-preset.cjs.map +1 -1
  29. package/packages/sdk/dist/styles/tailwind-preset.d.mts +0 -1
  30. package/packages/sdk/dist/styles/tailwind-preset.d.ts +0 -1
  31. package/packages/sdk/dist/styles/tailwind-preset.mjs +0 -1
  32. package/packages/sdk/dist/styles/tailwind-preset.mjs.map +1 -1
  33. package/packages/sdk/dist/styles/tokens.css +1 -0
  34. package/packages/sdk/src/build-source/scripts/build-forms.mjs +135 -50
  35. package/packages/sdk/src/build-source/scripts/build-pages.mjs +37 -10
  36. package/packages/sdk/src/build-source/scripts/register.mjs +2 -0
  37. package/packages/sdk/src/build-source/scripts/utils/load-config.mjs +3 -2
  38. package/packages/sdk/src/build-source/scripts/utils/register-payload.test.ts +2 -1
  39. package/packages/sdk/src/build-source/scripts/utils/tailwind-config.mjs +9 -7
  40. package/packages/sdk/src/build-source/scripts/utils/tailwind-config.test.ts +6 -4
  41. package/packages/sdk/src/build-source/src/cli.mjs +17 -0
  42. package/templates/sy-lowcode-app-workspace/.cursor/rules/openxiangda-form.mdc +20 -0
  43. package/templates/sy-lowcode-app-workspace/.cursor/rules/openxiangda-page.mdc +19 -0
  44. package/templates/sy-lowcode-app-workspace/.cursor/rules/openxiangda-resources.mdc +28 -0
  45. package/templates/sy-lowcode-app-workspace/.cursor/rules/openxiangda-workflow-automation.mdc +21 -0
  46. package/templates/sy-lowcode-app-workspace/.cursor/rules/openxiangda.mdc +47 -0
  47. package/templates/sy-lowcode-app-workspace/.qoder/rules/openxiangda-form.md +34 -0
  48. package/templates/sy-lowcode-app-workspace/.qoder/rules/openxiangda-page.md +37 -0
  49. package/templates/sy-lowcode-app-workspace/.qoder/rules/openxiangda-resources.md +46 -0
  50. package/templates/sy-lowcode-app-workspace/.qoder/rules/openxiangda-workflow-automation.md +46 -0
  51. package/templates/sy-lowcode-app-workspace/.qoder/rules/openxiangda.md +47 -0
  52. package/templates/sy-lowcode-app-workspace/AGENTS.md +92 -0
  53. package/templates/sy-lowcode-app-workspace/app-workspace.config.ts +3 -3
  54. package/templates/sy-lowcode-app-workspace/package.json +7 -0
  55. package/templates/sy-lowcode-app-workspace/postcss.config.cjs +0 -15
  56. package/templates/sy-lowcode-app-workspace/scripts/guard-publish.mjs +29 -0
  57. package/templates/sy-lowcode-app-workspace/src/main.tsx +1 -12
package/README.md CHANGED
@@ -79,8 +79,20 @@ Codex skills are installed separately from login/profile state. Run `openxiangda
79
79
 
80
80
  Use `openxiangda skill install --dest <skills-dir>` to target a different Codex skills directory. If a same-name skill exists but was not installed by OpenXiangda, the command refuses to overwrite it unless `--force` is provided. Restart Codex after installing skills.
81
81
 
82
+ For existing app workspaces created before AGENTS.md / `.qoder/rules/` / `.cursor/rules/` / `scripts/guard-publish.mjs` / `package.json` `_guard:publish` were added, run:
83
+
84
+ ```bash
85
+ openxiangda skill bootstrap # current dir, dry-run by default? no — write
86
+ openxiangda skill bootstrap --dry-run --json # preview
87
+ openxiangda skill bootstrap --force # overwrite drifted local copies
88
+ ```
89
+
90
+ It copies the AI-guidance bundle (AGENTS.md, Qoder/Cursor always-on + glob rules, the publish guard script) and patches `package.json` with the `_guard:publish` script plus `prepublish:all` / `prepublish:oss` / `preregister` / `preregister-bundle` / `prepublish:changed` / `preopenxiangda:publish` hooks so that direct `pnpm publish:all` / `pnpm publish:oss` / `pnpm register` calls fail-fast unless invoked through `openxiangda workspace publish ...`.
91
+
82
92
  Create a new publishable app workspace with `openxiangda workspace init <dir>`. The command writes a minimal `sy-lowcode-app-workspace` template with React, Ant Design, the single `openxiangda` package, and the required `publish:all` / `openxiangda:publish` scripts. Use `--install` to run `pnpm install` immediately, or run it manually after creation.
83
93
 
94
+ New OpenXiangda code pages and form custom pages publish with `cssIsolation: "none"` by default, so Tailwind utilities and normal Ant Design styles apply without a `.sy-app-workspace` prefix. Explicit `namespace` and `shadow` settings are kept only for legacy compatibility.
95
+
84
96
  Local workspace state is authoritative. If the current folder has no `.openxiangda/state.json` app binding, create a new app instead of searching the platform for a similar app name:
85
97
 
86
98
  ```bash
package/lib/cli.js CHANGED
@@ -20,6 +20,7 @@ const {
20
20
  const { requestJson } = require('./http');
21
21
  const { getSkillStatusReport, installSkills } = require('./skills');
22
22
  const { assertCanInitializeWorkspace, initWorkspace } = require('./workspace-init');
23
+ const { bootstrapWorkspace } = require('./workspace-bootstrap');
23
24
  const {
24
25
  fail,
25
26
  maskText,
@@ -116,6 +117,7 @@ Usage:
116
117
  openxiangda feedback preview|submit --summary <text> [--type bug] [--severity medium] [--profile name] [--yes]
117
118
  openxiangda skill install [--agent codex|claude|qoder|dual] [--dest <skills-dir>] [--force] [--dry-run] [--json]
118
119
  openxiangda skill status [--agent codex|claude|qoder|dual] [--dest <skills-dir>] [--json]
120
+ openxiangda skill bootstrap [<dir>] [--force] [--dry-run] [--json]
119
121
 
120
122
  OpenXiangda 使用普通用户登录 token,不需要 AK/SK。
121
123
  表单页、流程表单页和代码页的主链路是 sy-lowcode-app-workspace + openxiangda workspace publish。
@@ -1482,7 +1484,7 @@ async function page(args) {
1482
1484
  jsUrls: splitList(flags['js-urls']),
1483
1485
  framework: flags.framework || 'react',
1484
1486
  frameworkVersion: flags['framework-version'] || '',
1485
- cssIsolation: flags['css-isolation'] || 'namespace',
1487
+ cssIsolation: flags['css-isolation'] || 'none',
1486
1488
  format: flags.format || 'esm',
1487
1489
  },
1488
1490
  menu: flags.menu
@@ -2681,7 +2683,7 @@ async function commands(args) {
2681
2683
  'resource validate|plan|publish|pull',
2682
2684
  'inspect app|form|workflow|automation|permissions',
2683
2685
  'feedback preview|submit',
2684
- 'skill install|status',
2686
+ 'skill install|status|bootstrap',
2685
2687
  ],
2686
2688
  };
2687
2689
  if (flags.json) return writeJson(manifest);
@@ -2711,7 +2713,7 @@ function printWorkspaceInitReport(result) {
2711
2713
 
2712
2714
  async function skill(args) {
2713
2715
  const [subcommand, ...rest] = args;
2714
- const { flags } = parseArgs(rest);
2716
+ const { flags, positional } = parseArgs(rest);
2715
2717
  const options = {
2716
2718
  agent: flags.agent || 'codex',
2717
2719
  dest: flags.dest,
@@ -2733,7 +2735,18 @@ async function skill(args) {
2733
2735
  return;
2734
2736
  }
2735
2737
 
2736
- fail('用法: openxiangda skill install|status [--agent codex|claude|qoder|dual] [--dest <skills-dir>]');
2738
+ if (subcommand === 'bootstrap') {
2739
+ const result = bootstrapWorkspace({
2740
+ dir: positional[0],
2741
+ force: Boolean(flags.force),
2742
+ dryRun: Boolean(flags['dry-run']),
2743
+ });
2744
+ if (flags.json) return writeJson(result);
2745
+ printSkillBootstrapReport(result);
2746
+ return;
2747
+ }
2748
+
2749
+ fail('用法: openxiangda skill install|status [--agent codex|claude|qoder|dual] [--dest <skills-dir>]\n openxiangda skill bootstrap [<dir>] [--force] [--dry-run] [--json]');
2737
2750
  }
2738
2751
 
2739
2752
  function printSkillInstallReport(result) {
@@ -2792,6 +2805,33 @@ function printSkillStatusReport(result) {
2792
2805
  }
2793
2806
  }
2794
2807
 
2808
+ function printSkillBootstrapReport(result) {
2809
+ const lines = [
2810
+ `OpenXiangda skill bootstrap${result.dryRun ? ' (dry-run)' : ''}`,
2811
+ `Workspace: ${result.targetDir}`,
2812
+ ];
2813
+ for (const file of result.files) {
2814
+ const reason = file.reason ? ` — ${file.reason}` : '';
2815
+ lines.push(`- ${file.path}: ${file.action}${reason}`);
2816
+ }
2817
+ const pkg = result.packageJson;
2818
+ const pkgReason = pkg.reason ? ` — ${pkg.reason}` : '';
2819
+ lines.push(`- ${pkg.path}: ${pkg.action}${pkgReason}`);
2820
+ if (pkg.added && pkg.added.length > 0) {
2821
+ lines.push(` + scripts: ${pkg.added.join(', ')}`);
2822
+ }
2823
+ if (pkg.changed && pkg.changed.length > 0) {
2824
+ lines.push(` ~ scripts: ${pkg.changed.join(', ')}`);
2825
+ }
2826
+ lines.push(`Summary: ${result.summary}`);
2827
+ if (result.dryRun) {
2828
+ lines.push('dry-run completed; no files were changed');
2829
+ } else {
2830
+ lines.push('Done. 重启 Qoder / Cursor / Claude / Codex 以加载新的 always-on rules。');
2831
+ }
2832
+ print(lines.join('\n'));
2833
+ }
2834
+
2795
2835
  function getWorkspaceTarget(config, profileName, flags = {}) {
2796
2836
  const resolved = getProfile(config, profileName);
2797
2837
  const state = loadProjectState();
@@ -0,0 +1,238 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const ROOT_DIR = path.join(__dirname, '..');
5
+ const TEMPLATE_DIR = path.join(ROOT_DIR, 'templates', 'sy-lowcode-app-workspace');
6
+
7
+ // AI 引导/守卫"四件套 + glob rules"清单。
8
+ // 这些路径是相对于 workspace 根,文件源都来自 templates/sy-lowcode-app-workspace。
9
+ const BOOTSTRAP_FILES = [
10
+ 'AGENTS.md',
11
+ 'scripts/guard-publish.mjs',
12
+ '.qoder/rules/openxiangda.md',
13
+ '.qoder/rules/openxiangda-form.md',
14
+ '.qoder/rules/openxiangda-page.md',
15
+ '.qoder/rules/openxiangda-workflow-automation.md',
16
+ '.qoder/rules/openxiangda-resources.md',
17
+ '.cursor/rules/openxiangda.mdc',
18
+ '.cursor/rules/openxiangda-form.mdc',
19
+ '.cursor/rules/openxiangda-page.mdc',
20
+ '.cursor/rules/openxiangda-workflow-automation.mdc',
21
+ '.cursor/rules/openxiangda-resources.mdc',
22
+ ];
23
+
24
+ // package.json 必须存在的守卫脚本与 prefoo hook。
25
+ const PACKAGE_JSON_GUARD_SCRIPTS = {
26
+ '_guard:publish': 'node scripts/guard-publish.mjs',
27
+ 'prepublish:all': 'pnpm _guard:publish',
28
+ 'prepublish:oss': 'pnpm _guard:publish',
29
+ 'preregister': 'pnpm _guard:publish',
30
+ 'preregister-bundle': 'pnpm _guard:publish',
31
+ 'prepublish:changed': 'pnpm _guard:publish',
32
+ 'preopenxiangda:publish': 'pnpm _guard:publish',
33
+ };
34
+
35
+ function bootstrapWorkspace(options = {}) {
36
+ const targetDir = path.resolve(options.dir || process.cwd());
37
+ const force = Boolean(options.force);
38
+ const dryRun = Boolean(options.dryRun);
39
+
40
+ if (!fs.existsSync(TEMPLATE_DIR)) {
41
+ throw new Error(`workspace 模板不存在: ${TEMPLATE_DIR}`);
42
+ }
43
+ if (!fs.existsSync(targetDir) || !fs.statSync(targetDir).isDirectory()) {
44
+ throw new Error(`目标目录不存在或不是目录: ${targetDir}`);
45
+ }
46
+
47
+ const fileOps = BOOTSTRAP_FILES.map(rel => planFileOp(rel, targetDir, force));
48
+ const packageJsonOp = planPackageJsonOp(targetDir, force);
49
+
50
+ if (!dryRun) {
51
+ for (const op of fileOps) {
52
+ if (op.action === 'install' || op.action === 'overwrite') {
53
+ ensureDir(path.dirname(op.targetPath));
54
+ fs.copyFileSync(op.sourcePath, op.targetPath);
55
+ }
56
+ }
57
+ if (packageJsonOp.action === 'patch' || packageJsonOp.action === 'create-scripts') {
58
+ writePackageJson(packageJsonOp.targetPath, packageJsonOp.nextContent);
59
+ }
60
+ }
61
+
62
+ return {
63
+ targetDir,
64
+ dryRun,
65
+ force,
66
+ files: fileOps.map(op => ({
67
+ path: op.relativePath,
68
+ action: op.action,
69
+ reason: op.reason || null,
70
+ })),
71
+ packageJson: {
72
+ path: packageJsonOp.relativePath,
73
+ action: packageJsonOp.action,
74
+ added: packageJsonOp.added || [],
75
+ changed: packageJsonOp.changed || [],
76
+ reason: packageJsonOp.reason || null,
77
+ },
78
+ summary: buildSummary(fileOps, packageJsonOp),
79
+ };
80
+ }
81
+
82
+ function planFileOp(relativePath, targetDir, force) {
83
+ const sourcePath = path.join(TEMPLATE_DIR, relativePath);
84
+ const targetPath = path.join(targetDir, relativePath);
85
+ if (!fs.existsSync(sourcePath)) {
86
+ return {
87
+ relativePath,
88
+ sourcePath,
89
+ targetPath,
90
+ action: 'missing-source',
91
+ reason: '模板缺失,请升级 openxiangda',
92
+ };
93
+ }
94
+ const sourceContent = fs.readFileSync(sourcePath);
95
+ if (!fs.existsSync(targetPath)) {
96
+ return {
97
+ relativePath,
98
+ sourcePath,
99
+ targetPath,
100
+ action: 'install',
101
+ };
102
+ }
103
+ const targetContent = fs.readFileSync(targetPath);
104
+ if (sourceContent.equals(targetContent)) {
105
+ return {
106
+ relativePath,
107
+ sourcePath,
108
+ targetPath,
109
+ action: 'unchanged',
110
+ };
111
+ }
112
+ if (force) {
113
+ return {
114
+ relativePath,
115
+ sourcePath,
116
+ targetPath,
117
+ action: 'overwrite',
118
+ };
119
+ }
120
+ return {
121
+ relativePath,
122
+ sourcePath,
123
+ targetPath,
124
+ action: 'skip',
125
+ reason: '本地版本与模板不同;如需覆盖请传 --force',
126
+ };
127
+ }
128
+
129
+ function planPackageJsonOp(targetDir, force) {
130
+ const targetPath = path.join(targetDir, 'package.json');
131
+ const relativePath = 'package.json';
132
+ if (!fs.existsSync(targetPath)) {
133
+ return {
134
+ targetPath,
135
+ relativePath,
136
+ action: 'absent',
137
+ reason: 'package.json 不存在;不是 npm 工作区',
138
+ };
139
+ }
140
+ const raw = fs.readFileSync(targetPath, 'utf8');
141
+ let pkg;
142
+ try {
143
+ pkg = JSON.parse(raw);
144
+ } catch (error) {
145
+ return {
146
+ targetPath,
147
+ relativePath,
148
+ action: 'invalid-json',
149
+ reason: `package.json 解析失败: ${error.message}`,
150
+ };
151
+ }
152
+ const scripts = pkg.scripts ? { ...pkg.scripts } : null;
153
+ if (!scripts) {
154
+ const nextPkg = { ...pkg, scripts: { ...PACKAGE_JSON_GUARD_SCRIPTS } };
155
+ return {
156
+ targetPath,
157
+ relativePath,
158
+ action: 'create-scripts',
159
+ added: Object.keys(PACKAGE_JSON_GUARD_SCRIPTS),
160
+ changed: [],
161
+ nextContent: stringifyPackageJson(raw, nextPkg),
162
+ };
163
+ }
164
+ const added = [];
165
+ const changed = [];
166
+ for (const [name, value] of Object.entries(PACKAGE_JSON_GUARD_SCRIPTS)) {
167
+ if (!(name in scripts)) {
168
+ scripts[name] = value;
169
+ added.push(name);
170
+ } else if (scripts[name] !== value) {
171
+ if (force) {
172
+ scripts[name] = value;
173
+ changed.push(name);
174
+ } else {
175
+ changed.push(`${name} (skipped, value differs; pass --force to overwrite)`);
176
+ }
177
+ }
178
+ }
179
+ if (added.length === 0 && changed.length === 0) {
180
+ return {
181
+ targetPath,
182
+ relativePath,
183
+ action: 'unchanged',
184
+ added: [],
185
+ changed: [],
186
+ };
187
+ }
188
+ const nextPkg = { ...pkg, scripts };
189
+ return {
190
+ targetPath,
191
+ relativePath,
192
+ action: 'patch',
193
+ added,
194
+ changed,
195
+ nextContent: stringifyPackageJson(raw, nextPkg),
196
+ };
197
+ }
198
+
199
+ function stringifyPackageJson(originalRaw, nextPkg) {
200
+ const indent = detectIndent(originalRaw);
201
+ const trailingNewline = originalRaw.endsWith('\n') ? '\n' : '';
202
+ return `${JSON.stringify(nextPkg, null, indent)}${trailingNewline}`;
203
+ }
204
+
205
+ function detectIndent(raw) {
206
+ const match = raw.match(/^(\s+)\"/m);
207
+ if (!match) return 2;
208
+ if (match[1].includes('\t')) return '\t';
209
+ return match[1].length;
210
+ }
211
+
212
+ function ensureDir(dir) {
213
+ fs.mkdirSync(dir, { recursive: true });
214
+ }
215
+
216
+ function writePackageJson(targetPath, nextContent) {
217
+ fs.writeFileSync(targetPath, nextContent);
218
+ }
219
+
220
+ function buildSummary(fileOps, packageJsonOp) {
221
+ const counts = { install: 0, overwrite: 0, unchanged: 0, skip: 0 };
222
+ for (const op of fileOps) {
223
+ if (counts[op.action] !== undefined) counts[op.action] += 1;
224
+ }
225
+ const parts = [];
226
+ parts.push(`installed=${counts.install}`);
227
+ parts.push(`overwritten=${counts.overwrite}`);
228
+ parts.push(`unchanged=${counts.unchanged}`);
229
+ parts.push(`skipped=${counts.skip}`);
230
+ parts.push(`package.json=${packageJsonOp.action}`);
231
+ return parts.join(' ');
232
+ }
233
+
234
+ module.exports = {
235
+ BOOTSTRAP_FILES,
236
+ PACKAGE_JSON_GUARD_SCRIPTS,
237
+ bootstrapWorkspace,
238
+ };
@@ -1,13 +1,51 @@
1
1
  ---
2
2
  name: openxiangda
3
- description: Use OpenXiangda to help AI agents build and publish apps on a private low-code platform through the openxiangda CLI, ordinary user login tokens, platform profiles, workspace initialization, workspace binding, and app snapshots; use when the user mentions OpenXiangda, 湘搭, 私有化低代码平台, sy-lowcode-app-workspace, profile/domain login, or multi-platform publishing.
3
+ description: Use OpenXiangda for ANY work inside a sy-lowcode-app-workspace or any private low-code platform (OpenXiangda / 湘搭 / 私有化低代码) — publishing / 发布 / 上线 / deploying / 部署 / shipping / releasing / 上传 / pushing apps, pages, forms, workflows, automations; creating / scaffolding / 创建 / 搭建 / 新建 apps, code pages, form pages, workflow forms, JS_CODE nodes; editing / 修改 / 编辑 schemas, options, fields, layouts, menus; managing / 管理 roles, page permission groups, form permission groups, form settings, public access, data views, connectors, notifications; diagnosing / 排查 / 诊断 / 看快照 app snapshots, executions, logs, version drift, profile isolation, token / login / profile issues; running the openxiangda CLI or anything that touches `.openxiangda/state.json`, `~/.openxiangda/profiles.json`, `/openxiangda-api/v1`, `OPENXIANGDA_PROFILE / BASE_URL / ACCESS_TOKEN / APP_TYPE`, `app-workspace.config.ts`. Trigger when the user mentions OpenXiangda / 湘搭 / 私有化低代码 / 低代码平台 / sy-lowcode-app-workspace, or when the workspace contains `.openxiangda/state.json` or `app-workspace.config.ts`. Always prefer this skill over running `pnpm publish:all`, `pnpm publish:oss`, `pnpm register`, or `lowcode-workspace publish-*` directly.
4
4
  ---
5
5
 
6
6
  # OpenXiangda
7
7
 
8
- OpenXiangda is a lightweight bridge between an AI coding tool and a private low-code platform. It does not ask users for AK/SK. The user provides a platform domain, logs in as a normal platform user, and the CLI calls `/openxiangda-api/v1` with that user's token.
8
+ OpenXiangda is a lightweight bridge between an AI coding tool and a private low-code platform. No AK/SK. The user provides a platform domain, logs in as a normal platform user, and the CLI calls `/openxiangda-api/v1` with that user's token.
9
9
 
10
- For page generation, OpenXiangda must use `sy-lowcode-app-workspace`: form pages, workflow form pages, and custom code pages are implemented as workspace source files, built into bundles, uploaded to OSS, and registered through `openxiangda workspace publish`. Do not generate final user-facing pages by relying on the platform's legacy default schema page.
10
+ For any user-facing page (form pages, workflow form pages, custom code pages), OpenXiangda must use `sy-lowcode-app-workspace`: source files in `src/`, built into bundles, uploaded to OSS, and registered through `openxiangda workspace publish`. Do not rely on the platform's legacy default schema page.
11
+
12
+ ## TL;DR — Decision Card
13
+
14
+ **If the workspace has `.openxiangda/state.json` or `app-workspace.config.ts`, you are in an OpenXiangda app workspace. Read this card before any tool call.**
15
+
16
+ ### Routing — user intent → skill / command
17
+
18
+ | User says (zh / en) | Skill | First command |
19
+ |---|---|---|
20
+ | 发布 / 上线 / 部署 / publish / deploy / ship / release | `openxiangda-core` | `openxiangda workspace publish --profile <name> --changed --dry-run` |
21
+ | 只发布改动 / 增量 / 单页 / 单表 | `openxiangda-core` | `... --changed` / `--page <code>` / `--form <code>` / `--only pages/a,forms/b` |
22
+ | 创建应用 / 新建 app / scaffold / 初始化工作区 | `openxiangda-app` | `openxiangda workspace init <dir> --profile <name> --app-name "..."` |
23
+ | 绑定已有应用 / bind existing app | `openxiangda-app` | `openxiangda workspace bind --profile <name> --app-type APP_XXX` |
24
+ | 创建 / 修改表单字段 / 表单页 / schema | `openxiangda-form` | edit `src/forms/<code>/{schema.ts,page.tsx}` → `workspace publish --form <code>` |
25
+ | 创建 / 修改自定义代码页 / portal / dashboard | `openxiangda-page` | edit `src/pages/<code>/` → `workspace publish --page <code>` |
26
+ | 审批流程 / workflow / 流程节点 / JS_CODE | `openxiangda-workflow-automation` | `openxiangda workflow validate / create / publish` |
27
+ | 自动化 / 定时任务 / 提交触发 / cron | `openxiangda-workflow-automation` | `openxiangda automation validate / create / publish / enable` |
28
+ | 角色 / 权限组 / 字段权限 / 数据范围 / 公开访问 | `openxiangda-permission-settings` | `openxiangda permission ...` / `openxiangda settings ...` |
29
+ | 看应用结构 / 快照 / 对比 / 诊断 / 排查 / 报错 | `openxiangda-inspect` | `openxiangda app snapshot <APP_XXX> --profile <name> --json` |
30
+ | 登录 / 切换平台 / profile / token / whoami | `openxiangda-core` | `openxiangda env --profile <name>` / `openxiangda auth status` |
31
+ | 多表只读联表查询 / 报表数据源 | `openxiangda-form` (data view) | declare `src/resources/data-views/<code>.json` → `resource publish` |
32
+ | 调外部 / 第三方 API / 钉钉 / 自建系统 | `openxiangda-page` (connector) | declare `src/resources/connectors/<code>.json` → `sdk.connector.invoke` |
33
+
34
+ ### Hard rules — always
35
+
36
+ - ✅ Publish through `openxiangda workspace publish --profile <name>` (with `--changed` / `--page` / `--form` / `--only` for routine edits).
37
+ - ✅ User token lives in `~/.openxiangda/profiles.json`; project state in `.openxiangda/state.json` (IDs only).
38
+ - ✅ Each profile (dev / prod / ...) has its own `appType` and resource IDs; never copy a `formUuid` / `pageId` / `workflowId` / `automationId` across profiles.
39
+ - ✅ Run `openxiangda update check --json` at the start of substantial work; if `updateAvailable`, run `openxiangda update install` and `openxiangda skill install --force`.
40
+
41
+ ### Hard rules — never
42
+
43
+ - ❌ `pnpm publish:all` / `pnpm publish:oss` / `pnpm register` / `lowcode-workspace publish-*` directly — they are workspace internals and miss the profile token injection. Use `openxiangda workspace publish ...` instead.
44
+ - ❌ Asking the user for `AK` / `SK` / `appKey` / `appSecret`. The whole flow is normal-user token only.
45
+ - ❌ Searching the platform for a similar app name when `.openxiangda/state.json` has no binding — create a new app with `workspace init --app-name`.
46
+ - ❌ Using `openxiangda form create` / `form publish` / `page publish` as the normal page generation path. They are low-level repair commands.
47
+ - ❌ Running a full publish after editing one file. Default to `--changed --dry-run` → `--changed`, or targeted `--page` / `--form`.
48
+ - ❌ Storing tokens, AK, SK, or third-party API secrets in project files. Shared env (`APP_OSS_*`, feedback robot) goes to `~/.openxiangda/.env`.
11
49
 
12
50
  ## Platform Routing
13
51
 
@@ -128,6 +166,7 @@ Core CLI / state:
128
166
  - `references/openxiangda-api.md` — `/openxiangda-api/v1` request and response fields.
129
167
  - `references/workspace-state.md` — `.openxiangda/state.json` shape and profile isolation rules.
130
168
  - `references/connector-resources.md` — `src/resources` manifests, connector schema, and SDK connector calls.
169
+ - `references/resource-manifest-cheatsheet.md` — 复制即用的 connector / data-view / notification / workflow / automation / JS_CODE / role / permission group / settings / menu manifest 骨架与运行时调用示例。在创建任何 `src/resources/` 资源前先看这份。
131
170
  - `references/data-views.md` — `src/resources/data-views` materialized view resources, DSL, permissions, refresh, CLI commands, and runtime SDK usage.
132
171
  - `references/notifications.md` — `src/resources/notifications`, notification templates/type bindings, and `sdk.notification` / `ctx.notification`.
133
172
  - `references/best-practices.md` — initialized examples for modular pages, state lifecycles, role governance, permission isolation, high-performance queries, portal shells, workflow boundaries, and automation patterns.
@@ -153,7 +192,7 @@ Workflow / automation / permissions:
153
192
  Platform domain knowledge (read these first when generating forms or pages so the output matches the live platform behavior):
154
193
 
155
194
  - `references/platform-data-model.md` — how the platform persists field values (JSONB, `{label, value}` for option fields, attachment shape, etc.). Use this whenever you write, render, or diagnose form data.
156
- - `references/style-system.md` — three-layer style architecture, CSS namespace, and flexible Tailwind/CSS guidance. Use this before writing substantial page or form CSS.
195
+ - `references/style-system.md` — style isolation defaults, legacy namespace compatibility, and flexible Tailwind/CSS guidance. Use this before writing substantial page or form CSS.
157
196
  - `references/architecture-patterns.md` — CRUD data flow, `DataManagementList` pattern, recommended directory layout for `src/pages` and `src/forms`. Use this before scaffolding a new page or list view.
158
197
  - `references/best-practices.md` — read this before implementing app-level business patterns; it points to `examples/best-practices/` and explains when to use status fields instead of workflow.
159
198
  - `references/component-guide.md` — when to pick platform components vs. raw Ant Design vs. custom components, with the rules for option fields. Use this before introducing a new component.
@@ -102,19 +102,18 @@ export function CustomSelect({ options, className, ...rest }: CustomSelectProps)
102
102
  return (
103
103
  <Select
104
104
  className={clsx('w-full rounded-md', className)}
105
- popupClassName="sy-app-workspace"
106
- getPopupContainer={(trigger) => trigger.parentElement ?? document.body}
107
- options={options}
108
- {...rest}
105
+ getPopupContainer={(trigger) => trigger.parentElement ?? document.body}
106
+ options={options}
107
+ {...rest}
109
108
  />
110
109
  );
111
110
  }
112
111
  ```
113
112
 
114
- 要点:
115
- - 透传 `...rest`,保留 antd 全部 API。
116
- - `popupClassName="sy-app-workspace"` 确保弹层应用平台样式作用域。
117
- - `getPopupContainer` 解决弹层被裁切问题(详见第 7 节常见错误)。
113
+ 要点:
114
+ - 透传 `...rest`,保留 antd 全部 API。
115
+ - `getPopupContainer` 解决弹层被裁切问题(详见第 7 节常见错误)。
116
+ - 默认 `cssIsolation: "none"` 时不要额外给弹层加 `sy-app-workspace`;只有 legacy `namespace/shadow` 页面才需要给弹层或容器补 namespace class。
118
117
  - 使用 `w-full`、`rounded-md`、`text-slate-600`、`border-slate-200` 等 Tailwind 原生类作为默认基线;如果组件视觉需要精调,可以继续使用 Tailwind 任意值或局部 CSS。
119
118
 
120
119
  ### 4.2 错误:从头实现选择器
@@ -171,7 +170,7 @@ function BadSelect({ options }) {
171
170
  </ConfigProvider>
172
171
  ```
173
172
 
174
- 4. **谨慎**:直接覆盖组件内部 class(`.ant-select-selector { ... }`)属于私有 API,升级风险较高。确实需要时必须限定在页面根类或 `.sy-app-workspace` 下,并优先考虑 `ConfigProvider`、`className`、组件 props 或 CSS 变量是否能解决。
173
+ 4. **谨慎**:直接覆盖组件内部 class(`.ant-select-selector { ... }`)属于私有 API,升级风险较高。确实需要时必须限定在页面根类下;legacy 页面也可以兼容限定在 `.sy-app-workspace` 下。优先考虑 `ConfigProvider`、`className`、组件 props 或 CSS 变量是否能解决。
175
174
 
176
175
  ---
177
176
 
@@ -208,7 +207,7 @@ export function ActionButton(props) {
208
207
  | 用第三方富文本(TinyMCE 等) | 用 `EditorField` |
209
208
  | 自己写列表 + 分页 + 导出 | 用 `DataManagementList` |
210
209
  | 常规组件大量写 `style={{ color: '#333', padding: 16 }}` | 优先 Tailwind 原生工具类、任意值或局部 CSS;动态值和少量精调 inline style 可接受 |
211
- | Select / Date / Modal 弹层错位、被滚动容器裁切 | `getPopupContainer={(trigger) => trigger.parentElement}`,并在弹层 `popupClassName` 上加 `sy-app-workspace` |
210
+ | Select / Date / Modal 弹层错位、被滚动容器裁切 | 默认加 `getPopupContainer={(trigger) => trigger.parentElement}`;legacy `namespace/shadow` 页面才额外使用 `sy-app-workspace` |
212
211
  | 不分端,PC 组件直接放移动端 | 按端拆页 + 端内用对应 UI 库 |
213
212
  | 无作用域覆盖 `.ant-xxx` 内部 class | 优先用 `className`、CSS 变量或 `ConfigProvider` 主题;必要覆盖时限定到页面命名空间 |
214
213
  | 在自定义组件中不透传 `...rest` / `ref` | 透传 props 与 `forwardRef`,保留底层组件全部能力 |
@@ -220,5 +219,5 @@ export function ActionButton(props) {
220
219
  - [ ] 涉及人员 / 部门 / 文件 / 图片 / 富文本 / 签名 / 地图 / 列表 → 用了平台组件?
221
220
  - [ ] 自定义组件 → 基于 antd / antd-mobile 包装并透传 props?
222
221
  - [ ] 样式 → 默认用 Tailwind 原生类 / 任意值 / 局部 CSS;平台 token 只在主题兼容或平台组件需要时使用,且作用域收敛?
223
- - [ ] 弹层 → 设置了 `getPopupContainer` `sy-app-workspace` 弹层样式作用域?
222
+ - [ ] 弹层 → 默认使用正常容器;只有 legacy 隔离页面才额外设置 `sy-app-workspace` 样式作用域?
224
223
  - [ ] 多端 → PC 用 antd、Mobile 用 antd-mobile,业务逻辑共享?