openxiangda 2.15.0 → 2.16.0

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.
@@ -0,0 +1,188 @@
1
+ # 声明速查:一次写对 openxiangda.config.ts {#cheatsheet}
2
+
3
+ 按"错误码 → 规则 → 正确片段"组织。这些规则全部来自真实返工:先扫一遍本页,再写声明,能省掉绝大多数首轮校验迭代。普通 CRUD 的完整可过检骨架见文末。
4
+
5
+ ## 模块与 CRUD 视图
6
+
7
+ | 规则 | 正确写法 |
8
+ | --- | --- |
9
+ | `crud[].model` 必须引用本模块已声明的模型 | `crud: [{ model: 'repair-requests', ... }]` |
10
+ | 视图 `list`/`form`/`detail` 的 `model` 可省略(继承视图模型);显式声明时必须与 `crud[].model` 一致 | `list: { fields: [...] }` 即可,不必写 `model` |
11
+ | 每个模型最多 20 个命名视图;命名视图需要稳定 `code` + `name` | `crud: [{ model: 'x', code: 'x-active', name: '进行中', ... }]` |
12
+ | 新建视图的 `form.fields` 必须覆盖全部无默认必填字段,或显式 `generated.create: false` | 检查器会列出缺失字段 |
13
+
14
+ ## 字段声明
15
+
16
+ | 规则 | 正确片段 |
17
+ | --- | --- |
18
+ | `option.*` 字段的值是 `{ label, value }` 快照(比较键是 `value`) | `options: [{ label: '教学设备', value: 'teaching' }]` |
19
+ | `number.integer` 可省略精度;只允许 `precision`(位数),不允许 `scale` | `{ code: 'qty', type: 'number.integer' }` |
20
+ | `number.decimal` 的 `precision` 必填、`scale` 0 到 precision | `{ type: 'number.decimal', precision: 12, scale: 2 }` |
21
+ | `audit.read` 可写 `true`(绑定本资源读能力)或能力数组 | `audit: { read: true }` |
22
+ | `resource-ref.*` 必须带 `source` 来源协议 | `{ type: 'resource-ref.single', source: { kind: 'resource', resourceCode: 'repair-requests', labelField: 'title', searchFields: ['title'], pageSize: 20, loadMode: 'search' } }` |
23
+ | `labelField` 必须指向目标资源的 `text.short` / `text.long` 字段 | 不要用流水号/选项字段当 label |
24
+ | 子表 `subtable` 的外键是子资源的 **uuid** 字段,排序字段是**可写 number.integer** | 子资源:`{ code: 'requestId', type: 'uuid', required: true }` + `{ code: 'sortOrder', type: 'number.integer', required: true }`;父表:`subtable: { resourceCode: 'repair-items', foreignKey: 'requestId', orderField: 'sortOrder', maxRows: 20 }` |
25
+ | 图片/附件的 `file` 限定数量与大小 | `file: { maxCount: 3, maxSizeMb: 10, accept: ['image/png', 'image/jpeg'] }` |
26
+
27
+ ## 权限声明
28
+
29
+ | 规则 | 正确片段 |
30
+ | --- | --- |
31
+ | 平台保留能力(如 `app:<app>:directory:read`)**不能**在 `capabilities` 里重复声明,直接在角色中引用即可 | `const directoryRead = \`app:\${APP_CODE}:directory:read\`` → `roles: [{ code: 'admin', capabilities: [directoryRead] }]` |
32
+ | 资源 CRUD 能力码用 `resourceCapabilityCodes(appCode, resourceCode)` 生成 | `const crud = resourceCapabilityCodes(APP_CODE, 'repair-requests')` → `capabilities: [crud.read, crud.create]` |
33
+ | `authenticatedUserRoleCode` 是平台登录用户的基线角色 | `authz: { authenticatedUserRoleCode: 'app-user', ... }` |
34
+
35
+ ## 后端操作(Nest)
36
+
37
+ | 规则 | 正确片段 |
38
+ | --- | --- |
39
+ | 写操作的 `ai.sideEffects` 至少一条具体副作用 | `ai: { name: '受理派单', ..., risk: 'write', sideEffects: ['更新报修单状态为处理中', '写入一条派工记录'] }` |
40
+ | GET 操作的 `ai.risk` 只能是 `read`;写操作不能是 `read` | `risk: 'read'` ↔ `method: 'GET'` |
41
+ | controller 路由必须绑定 `@OpenXiangdaOperation(appOperations.<code>)`,普通 CRUD 不写 controller | check 门禁会拒绝未绑定路由 |
42
+ | 事务守卫 `errorCode` 必须匹配 `^OPENXIANGDA_[A-Z0-9_]{1,96}$` | `errorCode: 'OPENXIANGDA_REPAIR_REQUEST_NOT_PENDING'` |
43
+
44
+ ## 事务写入快照字段
45
+
46
+ option / user / department / resource-ref 字段在事务和普通写入里都必须写快照对象,不能写裸字符串:
47
+
48
+ ```ts
49
+ import { optionSnapshot, userSnapshot, resourceSnapshot } from 'openxiangda/nest';
50
+
51
+ await this.data.transaction(idempotentTransaction(input.idempotencyKey, [
52
+ {
53
+ operation: 'update',
54
+ resourceCode: 'repair-requests',
55
+ id: input.requestId,
56
+ expectedRevision: revision,
57
+ data: {
58
+ status: optionSnapshot('处理中', 'processing'), // 不是 'processing'
59
+ assignedTechnician: userSnapshot(input.technicianId), // 不是裸 userId
60
+ },
61
+ },
62
+ {
63
+ operation: 'create',
64
+ resourceCode: 'repair-assignments',
65
+ data: {
66
+ requestId: resourceSnapshot('repair-requests', input.requestId, requestTitle),
67
+ technician: userSnapshot(input.technicianId),
68
+ },
69
+ },
70
+ ]));
71
+ ```
72
+
73
+ 读取时状态判断用 `record.data.status?.value === 'pending'`(存储值是快照对象)。
74
+
75
+ ## 幂等冲突复核模式
76
+
77
+ update 事务必须携带 `expectedRevision`;重试时 revision 已前进会让同一幂等键的内容指纹漂移,平台返回 409 `OPENXIANGDA_NATIVE_DATA_IDEMPOTENCY_CONFLICT`(而非 `replayed: true`)。标准处理:
78
+
79
+ ```ts
80
+ import { isIdempotencyConflict } from 'openxiangda/nest';
81
+
82
+ try {
83
+ result = await this.data.transaction(idempotentTransaction(key, operations, guards));
84
+ } catch (error) {
85
+ if (isIdempotencyConflict(error)) {
86
+ const current = await this.data.get('repair-requests', input.requestId);
87
+ if (/* 状态已离开 pending,说明本键的效果已生效 */) {
88
+ return { idempotencyKey: key, replayed: true, ... 当前状态 };
89
+ }
90
+ }
91
+ throw error;
92
+ }
93
+ ```
94
+
95
+ 不要用新生成的幂等键重试冲突——那会绕过幂等保护重复执行业务动作。
96
+
97
+ ## 两模型起步骨架(可直接改造)
98
+
99
+ ```ts
100
+ import {
101
+ adminNavigationGroup, adminResourcePage, defineAdminNavigation,
102
+ defineApplicationModule, defineOpenXiangdaApp, resourceCapabilityCodes,
103
+ } from 'openxiangda/config';
104
+
105
+ const APP_CODE = 'my-app';
106
+ const requestCrud = resourceCapabilityCodes(APP_CODE, 'requests');
107
+
108
+ const requests = {
109
+ code: 'requests', name: '申请单',
110
+ audit: { read: true },
111
+ fields: [
112
+ { code: 'title', type: 'text.short', label: '标题', required: true },
113
+ { code: 'category', type: 'option.single', label: '类别', required: true,
114
+ options: [{ label: '普通', value: 'normal' }, { label: '紧急', value: 'urgent' }] },
115
+ { code: 'status', type: 'option.single', label: '状态', required: true,
116
+ options: [{ label: '待受理', value: 'pending' }, { label: '已完成', value: 'done' }] },
117
+ { code: 'photo', type: 'image', label: '照片', file: { maxCount: 3, maxSizeMb: 10 } },
118
+ ],
119
+ };
120
+ const items = {
121
+ code: 'request-items', name: '明细',
122
+ fields: [
123
+ { code: 'requestId', type: 'uuid', required: true },
124
+ { code: 'sortOrder', type: 'number.integer', required: true },
125
+ { code: 'name', type: 'text.short', required: true },
126
+ { code: 'qty', type: 'number.integer' },
127
+ { code: 'price', type: 'number.decimal', precision: 12, scale: 2 },
128
+ ],
129
+ };
130
+
131
+ export default defineOpenXiangdaApp({
132
+ app: { code: APP_CODE, name: '我的应用' },
133
+ frontend: {
134
+ root: 'apps/web',
135
+ devicePolicy: { kind: 'viewport-family', mobileMaxWidthPx: 900, desktopMinWidthPx: 901 },
136
+ routes: [
137
+ { code: 'application-home', path: '/home', label: '应用首页', surface: 'user' },
138
+ { code: 'application-home-mobile', path: '/m/home', label: '移动首页', surface: 'user' },
139
+ ],
140
+ authentication: {
141
+ accountMode: 'existing-platform-users-only',
142
+ registration: { mode: 'reject' },
143
+ methods: [{ code: 'password', type: 'password', label: '账号密码登录', presentation: 'primary', required: true }],
144
+ surfaces: {
145
+ desktop: { routeCode: 'application-login', path: '/login', defaultRouteCode: 'application-home' },
146
+ mobile: { routeCode: 'application-login-mobile', path: '/m/login', defaultRouteCode: 'application-home-mobile' },
147
+ },
148
+ },
149
+ admin: {
150
+ navigation: defineAdminNavigation([
151
+ adminNavigationGroup('main', '业务管理', [adminResourcePage('requests', { label: '申请单' })], { icon: 'database', order: 100 }),
152
+ ]),
153
+ },
154
+ },
155
+ modules: [defineApplicationModule({
156
+ code: 'main',
157
+ models: [requests, items],
158
+ crud: [
159
+ {
160
+ model: 'requests',
161
+ list: {
162
+ fields: ['title', 'category', 'status'],
163
+ filterFields: ['category', 'status'],
164
+ searchableFields: ['title'],
165
+ defaultPageSize: 20,
166
+ defaultSort: { field: 'title', order: 'asc' },
167
+ },
168
+ mobile: { enabled: true },
169
+ },
170
+ ],
171
+ })],
172
+ authz: {
173
+ authenticatedUserRoleCode: 'app-user',
174
+ capabilities: [],
175
+ roles: [
176
+ { code: 'app-user', name: '应用用户', capabilities: [requestCrud.read, requestCrud.create] },
177
+ { code: 'admin', name: '管理员', capabilities: [requestCrud.read, requestCrud.create, requestCrud.update, requestCrud.delete] },
178
+ ],
179
+ scopeDimensions: [], scopeSources: [], dataPolicies: [], authorizationTransitions: [],
180
+ },
181
+ });
182
+ ```
183
+
184
+ `request-items` 不出现在 `crud` 里:子表行随 `requests` 表单的 `subtable` 字段写入(在 `requests.fields` 里补 `{ code: 'items', type: 'subtable', subtable: { resourceCode: 'request-items', foreignKey: 'requestId', orderField: 'sortOrder', maxRows: 20 } }`)。
185
+
186
+ ## 图片上传的像素上限
187
+
188
+ image/signature/富文本图片字段在上传计划(initiate)里返回 `maxPixels`;超过上限的图片会被标准组件自动压缩后重新发起上传。用 API 直传时自行按 `maxPixels` 预检。当前平台上限覆盖主流手机主摄(48/50/64MP);超出会得到带实际尺寸的 `OPENXIANGDA_NATIVE_DATA_IMAGE_PIXEL_LIMIT_EXCEEDED` 错误。
@@ -66,10 +66,10 @@ MCP 服务随项目根包一起安装,AI 客户端的 stdio 连接仍需配置
66
66
  以下命令的版本占位符由随包资料替换为该根包的精确版本。网站源码阅读者应先确认要使用的发行版本。
67
67
 
68
68
  ```bash
69
- pnpm dlx openxiangda@2.15.0 skill install --force
70
- pnpm dlx openxiangda@2.15.0 auth status --base-url <平台地址> --json
71
- pnpm dlx openxiangda@2.15.0 login --cwd my-app --base-url https://platform.example.com
72
- pnpm dlx openxiangda@2.15.0 create my-app --base-url https://platform.example.com
69
+ pnpm dlx openxiangda@2.16.0 skill install --force
70
+ pnpm dlx openxiangda@2.16.0 auth status --base-url <平台地址> --json
71
+ pnpm dlx openxiangda@2.16.0 login --cwd my-app --base-url https://platform.example.com
72
+ pnpm dlx openxiangda@2.16.0 create my-app --base-url https://platform.example.com
73
73
  cd my-app
74
74
  pnpm openxiangda context --json
75
75
  pnpm openxiangda dev
@@ -171,9 +171,9 @@ MCP 的 `docs_read` 可以读取本说明,当前没有独立的源码操作 MC
171
171
  无需本地工作区,使用本 Skill 随包精确版本或已安装的对应 CLI:
172
172
 
173
173
  ```bash
174
- pnpm dlx openxiangda@2.15.0 auth status --base-url <平台> --json
175
- pnpm dlx openxiangda@2.15.0 source resolve <仓库URL> --base-url <平台> --json
176
- pnpm dlx openxiangda@2.15.0 source clone <仓库URL> <新目录> --base-url <平台> --json
174
+ pnpm dlx openxiangda@2.16.0 auth status --base-url <平台> --json
175
+ pnpm dlx openxiangda@2.16.0 source resolve <仓库URL> --base-url <平台> --json
176
+ pnpm dlx openxiangda@2.16.0 source clone <仓库URL> <新目录> --base-url <平台> --json
177
177
  ```
178
178
 
179
179
  登录缺失或站点不匹配时,先按该平台执行 login。resolve 根据平台已经登记的绑定返回
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "schemaVersion": "openxiangda.documentation/v1",
3
- "version": "2.15.0",
3
+ "version": "2.16.0",
4
4
  "topics": [
5
5
  {
6
6
  "id": "getting-started",
7
7
  "title": "安装与开始开发",
8
8
  "file": "getting-started.md",
9
- "sha256": "8ce8ca74bff94aa582c4bce708a0d25529478c5062ed67ea7fb5227927d4fcd5"
9
+ "sha256": "17f4f79e99dd5880c368a3326c066c0b6420f62d9b61961bc3b23afc25a5b772"
10
10
  },
11
11
  {
12
12
  "id": "product-design",
@@ -44,6 +44,12 @@
44
44
  "file": "development.md",
45
45
  "sha256": "c172cf5a6a20340b4142a1443b653d3e312f8a4464f990e69055046033011366"
46
46
  },
47
+ {
48
+ "id": "declarations-cheatsheet",
49
+ "title": "声明速查:一次写对 config",
50
+ "file": "declarations-cheatsheet.md",
51
+ "sha256": "6938d50f86066a7ad01aff495fbfb3daf49ba56a0d19cdd35f1cba6257047e78"
52
+ },
47
53
  {
48
54
  "id": "application-foundation",
49
55
  "title": "业务模型与标准 CRUD",
@@ -96,7 +102,7 @@
96
102
  "id": "backend",
97
103
  "title": "按需后端与业务动作",
98
104
  "file": "backend.md",
99
- "sha256": "f934afc1b7db45dca107de0088db5573005fcc7601b12f15c0d049a5c82feaae"
105
+ "sha256": "75c8f9b83c37afe53b57d0283fa814a5153aa7d72a85efb2a1d9c4a9c1844e6a"
100
106
  },
101
107
  {
102
108
  "id": "administration",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda",
3
- "version": "2.15.0",
3
+ "version": "2.16.0",
4
4
  "description": "OpenXiangda 2.0 的统一命令、应用 SDK、MCP 与中文 AI 技能资料。",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -60,13 +60,13 @@
60
60
  "antd-mobile": "5.42.3",
61
61
  "dayjs": "1.11.18",
62
62
  "docx-preview": "0.3.7",
63
- "openxiangda-cli": "2.4.4",
64
- "openxiangda-contracts": "2.12.0",
65
- "openxiangda-devkit-core": "2.11.0",
63
+ "openxiangda-cli": "2.4.5",
64
+ "openxiangda-contracts": "2.13.0",
65
+ "openxiangda-devkit-core": "2.12.0",
66
66
  "openxiangda-legacy": "npm:openxiangda@1.0.269",
67
- "openxiangda-mcp": "2.0.19",
68
- "openxiangda-nest": "2.3.3",
69
- "openxiangda-skill-kit": "2.1.3",
67
+ "openxiangda-mcp": "2.0.20",
68
+ "openxiangda-nest": "2.4.0",
69
+ "openxiangda-skill-kit": "2.1.4",
70
70
  "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
71
71
  },
72
72
  "peerDependencies": {
@@ -132,43 +132,44 @@
132
132
  },
133
133
  "openxiangdaRelease": {
134
134
  "schemaVersion": "openxiangda.release-notes/v1",
135
- "version": "2.15.0",
135
+ "version": "2.16.0",
136
136
  "status": "reviewed",
137
- "title": "OpenXiangda 2.15.0:CRUD 复用 Data API 引导与 Nest 路由门禁",
138
- "summary": "为普通 CRUD 补齐浏览器端 Data API 正面路径文档与 Nest 启用决策表,并在 check/dev 拒绝未绑定已声明 operation 的应用 controller 路由,使数据增删改查默认回到平台统一接口。",
137
+ "title": "OpenXiangda 2.16.0:相机图片直传与声明一次写对",
138
+ "summary": "浏览器托管上传按 maxPixels 契约预检并自动降采样超限图片,声明校验放宽可推导默认值、报错附带可复制片段,新增声明速查主题与事务快照/幂等冲突助手。",
139
139
  "newFeatures": [
140
- "开发资料新增「自定义页面消费平台数据」:createNativeResourceClient 的列表/详情/写入/删除/上传用法、expectedRevision 乐观锁、transactNativeData 幂等事务与 batchAggregateNativeResources 服务端聚合。",
141
- "需求与开发流程新增「判定是否真的需要 Nest 后端」决策表,工作区 AGENTS 模板与按需后端资料同步指向;只有真实外部副作用或无法声明的跨资源不变量才启用 Nest。",
142
- "devkit check/dev 新增应用 controller operation 门禁:未以 `@OpenXiangdaOperation(appOperations.具名操作)` 绑定已声明 operation 的路由返回 `OPENXIANGDA_NEST_CONTROLLER_OPERATION_REQUIRED`,手写契约字面量返回 `OPENXIANGDA_NEST_OPERATION_CONTRACT_MUST_BE_DECLARED`。"
140
+ "资源与匿名托管上传通道读取上传计划发布的 maxPixels,超限图片本地降采样后重新发起上传;旧平台无契约时回退 40MP 保护阈值。",
141
+ "openxiangda/nest 新增 optionSnapshot、userSnapshot、departmentSnapshot、resourceSnapshot 快照值助手与 isIdempotencyConflict 冲突判定,事务写快照字段不再手工拼形状。",
142
+ "新增 declarations-cheatsheet 中文主题(入口 Skill 已路由):按错误码到规则到正确片段组织全部隐式声明规则,并附两模型起步骨架。"
143
143
  ],
144
144
  "fixes": [
145
- "修复交付流程简化后 CLI 黑盒仍期望已删除的 DELIVERY_GIT_COMMIT_REQUIRED 的问题;黑盒改为断言 AppSpec 章节门禁,verify:affected 恢复可用。"
145
+ "number.integer 允许可选 precision 位数并拒绝 scale 小数位;number.decimal 精度报错附带合法示例,resource-ref 缺 source、平台保留能力与 AI 写操作副作用报错的补救文本直接携带正确片段。",
146
+ "audit.read 支持 true 声明糖自动绑定资源读能力;CRUD 视图 list/form/detail 省略 model 时继承视图模型。",
147
+ "业务验收报告性能条目的报错逐字段定位,指出缺失字段名与最少实义字符要求。"
146
148
  ],
147
149
  "affectedUsers": [
148
- "所有 OpenXiangda 2.0 应用开发者和 AI 辅助开发会话;新指引让列表、表单、详情、删除优先复用平台 Data API 与事务守卫。",
149
- "维护含自写 Nest controller 的既有应用:升级后 check 会要求绑定已声明 operation 或移除该路由。"
150
+ "所有在表单中上传手机照片或长截图的 OpenXiangda 2.0 应用用户与开发者。",
151
+ "首次编写 openxiangda.config.ts 声明、在 Nest 事务中写 option/user/resource-ref 字段的开发者与 AI 工作流。"
150
152
  ],
151
153
  "upgradeSteps": [
152
- "将应用精确依赖升级到 openxiangda 2.15.0,刷新项目 Skill 与工作区指引后运行 check。",
153
- "对报 `OPENXIANGDA_NEST_CONTROLLER_OPERATION_REQUIRED` 的路由:普通 CRUD 改用 createNativeResourceClient 或标准 CRUD 页面;真实业务动作先在 openxiangda.config.ts 声明 operation(capability kind: 'backend'),再绑定 `@OpenXiangdaOperation(appOperations.具名操作)`。",
154
- " development#backend-decision 决策表复查既有后端使用面,完成 check、test、build 后按常规测试发布流程验证。"
154
+ "应用精确依赖升级到 openxiangda 2.16.0 并刷新项目 Skill;普通 CRUD 与既有声明无需修改。",
155
+ "新的图片字段上传自动获得像素预检;配合已修复 maxPixels 契约的平台(100MP 上限)效果最佳,旧平台自动回退压缩保护。",
156
+ "后端事务代码可选用新的快照值助手与 isIdempotencyConflict 复核模式,参见 backend 文档快照写入与幂等冲突两节。"
155
157
  ],
156
158
  "knownLimitations": [
157
- "门禁是静态判定:已声明 operation 但方法体仍只转发普通 CRUD 的 controller 不会被拒绝;该层由设计评审与后续 AppSpec 能力复用清单兜底。",
158
- "类级别 @OpenXiangdaOperation 绑定不被接受,需要逐路由方法绑定同一生成契约。",
159
- "文档与门禁不替代目标环境的真实角色、浏览器和性能验收。"
159
+ "前端压缩依赖浏览器 createImageBitmap;极旧的浏览器跳过预检,由服务端明确错误兜底。",
160
+ "操作通道(operation-managed)上传不自动改写业务文件内容,超限时返回带尺寸的 PIXEL_LIMIT_EXCEEDED,由调用方自行压缩。"
160
161
  ],
161
162
  "compatibility": {
162
163
  "node": ">=24",
163
164
  "workspaceGenerations": [
164
165
  "v2"
165
166
  ],
166
- "platform": "不要求平台服务端变更;已有应用升级后 check 行为变化见升级步骤。",
167
+ "platform": "maxPixels 契约与 100MP 处理上限需要配套平台版本;旧平台通过 40MP 回退阈值继续工作。V1 引擎保持独立。",
167
168
  "v1": "不改变 V1 运行时或应用。"
168
169
  },
169
170
  "issues": [],
170
- "sha256": "45fe1d3ad3b0a04d8ec4b3f657a201dee14823576fe8b48a369a92f7d0c9e029",
171
- "url": "https://github.com/1377385356/openxiangda/releases/tag/v2.15.0"
171
+ "sha256": "55687eb0915b5a1d2743f4b3c5aa54d7a3dbfad91b467cc9f80be10995aa83f5",
172
+ "url": "https://github.com/1377385356/openxiangda/releases/tag/v2.16.0"
172
173
  },
173
174
  "scripts": {
174
175
  "build": "node ../../scripts/prune-package-dist.mjs && tsc -p tsconfig.json && node scripts/copy-assets.mjs",
@@ -0,0 +1,41 @@
1
+ {
2
+ "schemaVersion": "openxiangda.release-notes/v1",
3
+ "version": "2.16.0",
4
+ "status": "reviewed",
5
+ "title": "OpenXiangda 2.16.0:相机图片直传与声明一次写对",
6
+ "summary": "浏览器托管上传按 maxPixels 契约预检并自动降采样超限图片,声明校验放宽可推导默认值、报错附带可复制片段,新增声明速查主题与事务快照/幂等冲突助手。",
7
+ "newFeatures": [
8
+ "资源与匿名托管上传通道读取上传计划发布的 maxPixels,超限图片本地降采样后重新发起上传;旧平台无契约时回退 40MP 保护阈值。",
9
+ "openxiangda/nest 新增 optionSnapshot、userSnapshot、departmentSnapshot、resourceSnapshot 快照值助手与 isIdempotencyConflict 冲突判定,事务写快照字段不再手工拼形状。",
10
+ "新增 declarations-cheatsheet 中文主题(入口 Skill 已路由):按错误码到规则到正确片段组织全部隐式声明规则,并附两模型起步骨架。"
11
+ ],
12
+ "fixes": [
13
+ "number.integer 允许可选 precision 位数并拒绝 scale 小数位;number.decimal 精度报错附带合法示例,resource-ref 缺 source、平台保留能力与 AI 写操作副作用报错的补救文本直接携带正确片段。",
14
+ "audit.read 支持 true 声明糖自动绑定资源读能力;CRUD 视图 list/form/detail 省略 model 时继承视图模型。",
15
+ "业务验收报告性能条目的报错逐字段定位,指出缺失字段名与最少实义字符要求。"
16
+ ],
17
+ "affectedUsers": [
18
+ "所有在表单中上传手机照片或长截图的 OpenXiangda 2.0 应用用户与开发者。",
19
+ "首次编写 openxiangda.config.ts 声明、在 Nest 事务中写 option/user/resource-ref 字段的开发者与 AI 工作流。"
20
+ ],
21
+ "upgradeSteps": [
22
+ "应用精确依赖升级到 openxiangda 2.16.0 并刷新项目 Skill;普通 CRUD 与既有声明无需修改。",
23
+ "新的图片字段上传自动获得像素预检;配合已修复 maxPixels 契约的平台(100MP 上限)效果最佳,旧平台自动回退压缩保护。",
24
+ "后端事务代码可选用新的快照值助手与 isIdempotencyConflict 复核模式,参见 backend 文档快照写入与幂等冲突两节。"
25
+ ],
26
+ "knownLimitations": [
27
+ "前端压缩依赖浏览器 createImageBitmap;极旧的浏览器跳过预检,由服务端明确错误兜底。",
28
+ "操作通道(operation-managed)上传不自动改写业务文件内容,超限时返回带尺寸的 PIXEL_LIMIT_EXCEEDED,由调用方自行压缩。"
29
+ ],
30
+ "compatibility": {
31
+ "node": ">=24",
32
+ "workspaceGenerations": [
33
+ "v2"
34
+ ],
35
+ "platform": "maxPixels 契约与 100MP 处理上限需要配套平台版本;旧平台通过 40MP 回退阈值继续工作。V1 引擎保持独立。",
36
+ "v1": "不改变 V1 运行时或应用。"
37
+ },
38
+ "issues": [],
39
+ "sha256": "55687eb0915b5a1d2743f4b3c5aa54d7a3dbfad91b467cc9f80be10995aa83f5",
40
+ "url": "https://github.com/1377385356/openxiangda/releases/tag/v2.16.0"
41
+ }
@@ -4,7 +4,7 @@
4
4
  {
5
5
  "name": "openxiangda-v2",
6
6
  "description": "使用 OpenXiangda 2.0 从模糊业务想法、已有资料或具体变更出发,通过对话发现模块、完成详细产品设计,由 AI 在工作区内调用 OpenDesign 原版 CLI/Skill/MCP 形成整体视觉与可运行原型,再开发、检查和交付应用。OpenDesign 客户端只作为可选预览器;维护 1.x 应用时使用对应的 1.x 技能。",
7
- "sha256": "37ddcd0b0d5bb157e76bac263609f910ed97e589ad6cbfebe15ca37c1874ecb0"
7
+ "sha256": "642c56742434479335e81cd6c285cd810baaa247b72f79e11536912d307609e0"
8
8
  }
9
9
  ]
10
10
  }
@@ -40,10 +40,10 @@ AI 接到新应用、页面或改版任务时,在同一个 OpenXiangda 工作
40
40
  未创建工作区时使用本 Skill 随根包发布的精确版本:
41
41
 
42
42
  ```bash
43
- pnpm dlx openxiangda@2.15.0 auth status --cwd <应用目录> --base-url <平台地址> --json
44
- pnpm dlx openxiangda@2.15.0 login --cwd <应用目录> --base-url <平台地址>
45
- pnpm dlx openxiangda@2.15.0 create <应用目录> --base-url <同一平台地址>
46
- pnpm dlx openxiangda@2.15.0 skill install --force
43
+ pnpm dlx openxiangda@2.16.0 auth status --cwd <应用目录> --base-url <平台地址> --json
44
+ pnpm dlx openxiangda@2.16.0 login --cwd <应用目录> --base-url <平台地址>
45
+ pnpm dlx openxiangda@2.16.0 create <应用目录> --base-url <同一平台地址>
46
+ pnpm dlx openxiangda@2.16.0 skill install --force
47
47
  ```
48
48
 
49
49
  创建前把产品要求的目标平台明确带入命令,不从旧登录态推断站点。已有工作区从原绑定恢复,平台不一致时先解决登录与目标,不改 link 文件跨站创建。
@@ -63,6 +63,7 @@ pnpm dlx openxiangda@2.15.0 skill install --force
63
63
  | 模糊想法、模块发现、PRD、权限与架构设计 | [产品设计](references/product-design.md)、[交互模式](references/interaction-patterns.md) |
64
64
  | 界面设计、改版、原型和视觉修正 | 先读[设计工作流](references/design-workflow.md),使用 `openxiangda design open` 和 `design cli` 调用原版;[离线方法](references/opendesign-methods.md)与[设计 Craft](references/design-craft.md)仅作补充 |
65
65
  | 理解需求与选择能力 | [开发流程](references/development.md)、[架构](references/concepts.md) |
66
+ | 写 openxiangda.config.ts 声明、避免首轮校验返工 | [声明速查](references/declarations-cheatsheet.md);先扫规则表再动手 |
66
67
  | 模型、CRUD、字段与移动表单 | [业务模块](references/application-foundation.md)、[字段](references/field-components.md) |
67
68
  | 图片压缩、缩略图、附件和缓存 | [图片与附件读取](references/field-components.md#图片缩略图和附件读取):卡片优先缩略图,原图按需,使用平台权限与缓存规则 |
68
69
  | 页面、标准组件与扩展 | [前端](references/frontend.md) |
@@ -74,6 +74,41 @@ ISO 时间字符串;不接受空值、嵌套路径、引用或表达式。offs
74
74
  最多正负 366 天的整数,所有时间条件共享一个接受时刻。需要平台 Data API 1.1.0。
75
75
 
76
76
 
77
+
78
+ ### 事务内写入快照字段 {#snapshot-values}
79
+
80
+ option / user / department / resource-ref 字段在 Data API 与事务写入里保存 `{ label, value }` 显示快照,`value` 是比较键。写裸字符串会被字段校验拒绝(`OPENXIANGDA_NATIVE_DATA_OBJECT_REQUIRED`)。使用助手函数避免手写形状:
81
+
82
+ ```ts
83
+ import { optionSnapshot, userSnapshot, resourceSnapshot } from 'openxiangda/nest';
84
+
85
+ data: {
86
+ status: optionSnapshot('处理中', 'processing'),
87
+ assignedTechnician: userSnapshot(input.technicianId),
88
+ requestId: resourceSnapshot('repair-requests', input.requestId, title),
89
+ }
90
+ ```
91
+
92
+ 读取判断状态用 `record.data.status?.value === 'pending'`。
93
+
94
+ ### 幂等冲突复核 {#idempotency-recovery}
95
+
96
+ 同一 `idempotencyKey` 要求内容指纹一致。update 事务携带 `expectedRevision`,重试时 revision 已前进会触发 409 `OPENXIANGDA_NATIVE_DATA_IDEMPOTENCY_CONFLICT`。捕获后回读当前状态确认效果已生效,按幂等结果返回;不要换新键重试:
97
+
98
+ ```ts
99
+ import { isIdempotencyConflict } from 'openxiangda/nest';
100
+
101
+ try {
102
+ result = await this.data.transaction(idempotentTransaction(key, operations, guards));
103
+ } catch (error) {
104
+ if (isIdempotencyConflict(error) && alreadyApplied(await this.data.get(...))) {
105
+ return { idempotencyKey: key, replayed: true, ...currentState };
106
+ }
107
+ throw error;
108
+ }
109
+ ```
110
+
111
+ 事务守卫的 `errorCode` 必须匹配 `^OPENXIANGDA_[A-Z0-9_]{1,96}$`,例如 `OPENXIANGDA_REPAIR_REQUEST_NOT_PENDING`。
77
112
  ## 业务动作与普通查询 {#business-action}
78
113
 
79
114
  `OpenXiangdaDataApiService` 按当前用户的普通资源、行和字段权限执行。具名业务动作使用 `OpenXiangdaBusinessDataApiService`:入口先检查该动作 capability,平台在精确应用和环境内以受信任后端执行,并保留发起人与动作审计。业务动作不能接受任意模型/字段/用户 ID 后不做业务校验;应用负责该动作的输入约束和业务不变量。