openxiangda-cli 2.0.0-alpha.150 → 2.0.0-alpha.153

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda-cli",
3
- "version": "2.0.0-alpha.150",
3
+ "version": "2.0.0-alpha.153",
4
4
  "description": "Thin application-level CLI for OpenXiangda 2.0.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -21,10 +21,10 @@
21
21
  ],
22
22
  "dependencies": {
23
23
  "@oclif/core": "4.13.3",
24
- "openxiangda-contracts": "2.0.0-alpha.77",
25
- "openxiangda-devkit-core": "2.0.0-alpha.97",
26
- "openxiangda-mcp": "2.0.0-alpha.97",
27
- "openxiangda-skill-kit": "2.0.0-alpha.119"
24
+ "openxiangda-contracts": "2.0.0-alpha.80",
25
+ "openxiangda-devkit-core": "2.0.0-alpha.100",
26
+ "openxiangda-mcp": "2.0.0-alpha.100",
27
+ "openxiangda-skill-kit": "2.0.0-alpha.122"
28
28
  },
29
29
  "devDependencies": {
30
30
  "tsx": "4.23.12",
@@ -42,6 +42,6 @@
42
42
  - `option.*`, `user.*`, `department.*` and `resource-ref.*` values never collapse to scalar IDs. Single values store one labeled object and multiple values store object arrays. `resource-ref.*` JSON is not a foreign key, trusted target snapshot or automatically refreshed copy; current target state is read by `resourceCode` plus `value`. A resource source `labelField` must be `text.short` or `text.long`; `serial-number` is allowed in source search, description and snapshot fields, but not as the label. `location` accepts only exact WGS84 coordinates captured by DingTalk or browser geolocation; it has no manual input or `manual` source. Roles that consume directory-backed fields explicitly include `app:<app-code>:directory:read`.
43
43
  - Derive role grants with `resourceCapabilityCodes(appCode, resourceCode)`. Declare current-user rows only with `currentUserDataPolicy(...)`; do not invent operators, values or alternate current-user spellings.
44
44
  - Do not add compatibility aliases, migration branches or silent fallbacks for an earlier 2.0 alpha contract. Replace an incorrect contract and regenerate the application.
45
- - Standard Workflow and Notification Hub are optional 2.0 modules. Enable them only through canonical `openxiangda.config.ts` declarations and generated clients. `standalone`/`hidden-handoff` use the compiler-owned process operation and Durable Business Process Command; never add a browser save callback or call Workflow prepare/start directly. Custom pages launch only through a verified Named Action using `OpenXiangdaBusinessProcessService`. PC and mobile render the same `ProcessCommandSurface` independently and recover only by `commandId`. To replace Workflow detail, declare both desktop and mobile `detailRouteCode` values whose routes contain exactly `:instanceId`. Add the current-user todo page only with `adminApplicationTodoCenterPage()`; never query Notification Hub management APIs from a user page.
45
+ - Standard Workflow and Notification Hub are optional 2.0 modules. Enable them only through canonical `openxiangda.config.ts` declarations and generated clients. `standalone`/`hidden-handoff` default to the compiler-owned process operation; action-owned submission must instead declare `launch.submission.kind: 'named-operation'` with sealed create/existing input and output bindings so the standard PC/mobile page calls the original Named Action and accepts an explicit no-Workflow result. Never add a browser save callback or call Workflow prepare/start directly. Custom pages launch only through a verified Named Action using `OpenXiangdaBusinessProcessService`. PC and mobile render the same `ProcessCommandSurface` independently and recover only by `commandId`. To replace Workflow detail, declare both desktop and mobile `detailRouteCode` values whose routes contain exactly `:instanceId`. Add the current-user todo page only with `frontend.user.applicationTodoCenter: true`; never query Notification Hub management APIs from a user page.
46
46
  - Run `pnpm openxiangda check --json` after contract changes and read `data.sealedArtifact`; check never seals, and an older `.openxiangda/build/app-package.json` is not the current check result. Use `pnpm openxiangda accept --plan <file>` only for optional real preproduction identity acceptance; it never blocks delivery. Deploy with `pnpm openxiangda deploy`, inspect with `pnpm openxiangda status` and `pnpm openxiangda logs`, and use the platform rollback command rather than mutating K3s directly.
47
47
  - AI-native clients start the workspace protocol through the same pinned binary: `pnpm exec openxiangda --mcp-stdio --cwd <workspace>`. Read `openxiangda://workspace/contracts` or call `contract_describe`; for first-time menu authoring, copy `data.adminNavigationAuthoring.suggestion.expression` once into `frontend.admin.navigation` with its listed `openxiangda/config` imports, then edit that application-owned declaration. Never treat the proposal as runtime discovery. When AppSpec is enabled, also read `openxiangda://workspace/appspec` or call `appspec_context`. Require `aiCatalog` and `aiCatalogDigest` to match the normal compiler output. This is a stdio transport mode, not another application-owned server. Never write an application MCP server, Catalog file, preview store or second authorization path.
@@ -23,8 +23,8 @@ pnpm openxiangda dev
23
23
  逐项绑定本地组件并传给 `OpenXiangdaApplication`。后者是唯一的
24
24
  BrowserRouter、RuntimeBoundary、Refine、generated routes 和后台 Shell
25
25
  所有者;用户页可以拥有独立布局,但不要再建 router/Shell。要使用通用应用待办中心,
26
- 在显式后台导航组中加入 `adminApplicationTodoCenterPage()`;平台生成
27
- `/admin/todos` 与 `/m/todos`,只读取当前用户的消息/待办投影并统一解析目标页面。
26
+ 声明 `frontend.user.applicationTodoCenter: true`;平台生成 `/todos` 与 `/m/todos`,
27
+ 只读取当前用户的消息/待办投影并统一解析目标页面。
28
28
 
29
29
  自定义后台页使用 `OpenXiangdaAdminPage` 作为页面根;Ant Design 组件自动继承平台
30
30
  `ConfigProvider`,图表等 TypeScript 渲染值读取 `theme.useToken()`,自定义 CSS
@@ -15,7 +15,7 @@
15
15
  "@nestjs/common": "11.1.29",
16
16
  "@nestjs/core": "11.1.29",
17
17
  "@nestjs/platform-fastify": "11.1.29",
18
- "openxiangda": "2.0.0-alpha.69",
18
+ "openxiangda": "2.0.0-alpha.72",
19
19
  "reflect-metadata": "0.2.2",
20
20
  "rxjs": "7.8.2"
21
21
  },
@@ -90,44 +90,7 @@ const applicationContributions = defineApplicationContributions(detailRoutes, {
90
90
  },
91
91
  });
92
92
 
93
- const adminPages = [
94
- {
95
- code: 'application:todo-center',
96
- kind: 'application-todo-center',
97
- path: '/admin/todos',
98
- label: '待办中心',
99
- navigationEligible: true,
100
- },
101
- {
102
- code: 'workflow:work-center',
103
- kind: 'workflow-work-center',
104
- path: '/admin/work-center',
105
- label: '我的审批',
106
- navigationEligible: true,
107
- },
108
- {
109
- code: 'workflow:purchase-approval:launch',
110
- kind: 'workflow-launch',
111
- path: '/admin/workflows/purchase-approval/new',
112
- label: '发起采购审批',
113
- navigationEligible: true,
114
- workflowCode: 'purchase-approval',
115
- },
116
- {
117
- code: 'workflow:task',
118
- kind: 'workflow-task',
119
- path: '/tasks/:taskId',
120
- label: '审批任务',
121
- navigationEligible: false,
122
- },
123
- {
124
- code: 'workflow:instance',
125
- kind: 'workflow-instance',
126
- path: '/workflows/:instanceId',
127
- label: '流程详情',
128
- navigationEligible: false,
129
- },
130
- ] as const satisfies AdminPagesInput;
93
+ const adminPages = [] as const satisfies AdminPagesInput;
131
94
 
132
95
  const routeManifest = {
133
96
  schemaVersion: 'openxiangda.application-route-manifest/v3',
@@ -148,8 +111,8 @@ const routeManifest = {
148
111
  kind: 'application-todo-center',
149
112
  desktop: {
150
113
  routeCode: 'application.todo-center.desktop',
151
- path: '/admin/todos',
152
- surface: 'admin',
114
+ path: '/todos',
115
+ surface: 'user',
153
116
  pathParams: [],
154
117
  requiresAuthentication: true,
155
118
  },
@@ -166,8 +129,8 @@ const routeManifest = {
166
129
  kind: 'workflow-work-center',
167
130
  desktop: {
168
131
  routeCode: 'workflow.work.center.desktop',
169
- path: '/admin/work-center',
170
- surface: 'admin',
132
+ path: '/work-center',
133
+ surface: 'user',
171
134
  pathParams: [],
172
135
  requiresAuthentication: true,
173
136
  },
@@ -185,7 +148,7 @@ const routeManifest = {
185
148
  desktop: {
186
149
  routeCode: 'workflow.task.desktop',
187
150
  path: '/tasks/:taskId',
188
- surface: 'admin',
151
+ surface: 'user',
189
152
  pathParams: ['taskId'],
190
153
  requiresAuthentication: true,
191
154
  },
@@ -203,7 +166,7 @@ const routeManifest = {
203
166
  desktop: {
204
167
  routeCode: 'workflow.instance.desktop',
205
168
  path: '/workflows/:instanceId',
206
- surface: 'admin',
169
+ surface: 'user',
207
170
  pathParams: ['instanceId'],
208
171
  requiresAuthentication: true,
209
172
  },
@@ -221,9 +184,9 @@ const routeManifest = {
221
184
  workflowCode: 'purchase-approval',
222
185
  desktop: {
223
186
  routeCode: 'workflow.purchase-approval.launch.desktop',
224
- path: '/admin/workflows/purchase-approval/new',
225
- surface: 'admin',
226
- pathParams: [],
187
+ path: '/workflows/:workflowCode/start',
188
+ surface: 'user',
189
+ pathParams: ['workflowCode'],
227
190
  requiresAuthentication: true,
228
191
  },
229
192
  mobile: {
@@ -238,26 +201,10 @@ const routeManifest = {
238
201
  digest: '34cc08a02cd187a07d9e2d2f99c1080b5b86f3a4512f9e0f43ff4cb05b7195fa',
239
202
  } as const;
240
203
 
241
- const adminNavigation = [
242
- {
243
- code: 'workflow-center',
244
- label: '流程中心',
245
- icon: 'workflow',
246
- order: 0,
247
- items: [
248
- { pageCode: 'application:todo-center', label: '待办中心', order: 0 },
249
- { pageCode: 'workflow:work-center', label: '我的审批', order: 1 },
250
- {
251
- pageCode: 'workflow:purchase-approval:launch',
252
- label: '发起采购审批',
253
- order: 2,
254
- },
255
- ],
256
- },
257
- ] as const satisfies AdminNavigationInput;
204
+ const adminNavigation = [] as const satisfies AdminNavigationInput;
258
205
 
259
206
  const fixtureParams = new URLSearchParams(window.location.search);
260
- const initialPath = fixtureParams.get('initial') || '/admin/work-center';
207
+ const initialPath = fixtureParams.get('initial') || '/work-center';
261
208
  const runtimeBase = fixtureParams.get('base')?.trim();
262
209
  if (runtimeBase) {
263
210
  for (const [name, content] of [
@@ -62,7 +62,7 @@ function surface(completed = false, customDetail = false) {
62
62
  return {
63
63
  schemaVersion: 'openxiangda.workflow-surface/v2',
64
64
  protocolVersion: 'workflow_surface_v2',
65
- surfaceRevision: completed ? 'revision-2' : 'revision-1',
65
+ surfaceRevision: (completed ? 'b' : 'a').repeat(64),
66
66
  engineVersion: '2.0',
67
67
  commandToken: 'A'.repeat(43),
68
68
  commandTokenExpiresAt: '2099-08-29T09:05:00.000Z',
@@ -196,7 +196,7 @@ function surface(completed = false, customDetail = false) {
196
196
  },
197
197
  execute: {
198
198
  method: 'POST',
199
- href: '/workflow/approve',
199
+ href: `/openxiangda-api/v2/applications/${appCode}/workflow/tasks/${taskId}/commands/approve`,
200
200
  idempotencyRequired: true,
201
201
  },
202
202
  refresh: ['surface', 'timeline', 'work_center'],
@@ -226,7 +226,7 @@ function surface(completed = false, customDetail = false) {
226
226
  uiSchema: { confirmText: `确认${key}` },
227
227
  execute: {
228
228
  method: 'POST',
229
- href: `/workflow/${key}`,
229
+ href: `/openxiangda-api/v2/applications/${appCode}/workflow/tasks/${taskId}/commands/${key}`,
230
230
  idempotencyRequired: true,
231
231
  },
232
232
  refresh: ['surface', 'timeline', 'work_center'],
@@ -237,74 +237,180 @@ function surface(completed = false, customDetail = false) {
237
237
  }
238
238
 
239
239
  function timeline(completed = false) {
240
- return {
241
- engineVersion: '2.0',
242
- instanceId,
243
- flow: [
240
+ const flow = [
241
+ {
242
+ key: 'start',
243
+ nodeId: null,
244
+ kind: 'start',
245
+ title: '张三',
246
+ status: 'completed',
247
+ startedAt: '2026-08-25T01:00:00.000Z',
248
+ completedAt: '2026-08-25T01:00:00.000Z',
249
+ assignees: [],
250
+ operations: [],
251
+ },
252
+ {
253
+ key: 'manager',
254
+ nodeId: 'manager-review',
255
+ kind: 'approval',
256
+ title: '部门负责人',
257
+ status: completed ? 'completed' : 'active',
258
+ startedAt: '2026-08-25T01:01:00.000Z',
259
+ completedAt: completed ? '2026-08-25T01:12:00.000Z' : null,
260
+ assignees: [{ userId: 'acceptance-user', displayName: '李明' }],
261
+ operations: [{
262
+ id: 'operation-transfer',
263
+ operation: 'transfer',
264
+ operationLabel: '转交',
265
+ actorUserId: 'acceptance-user',
266
+ actorDisplayName: '李明',
267
+ actingForUserId: null,
268
+ reason: '由值班负责人继续处理',
269
+ detail: {},
270
+ severity: 'normal',
271
+ createdAt: '2026-08-25T01:08:00.000Z',
272
+ }],
273
+ },
274
+ {
275
+ key: 'finance',
276
+ nodeId: 'finance-review',
277
+ kind: 'approval',
278
+ title: '财务负责人',
279
+ status: completed ? 'completed' : 'waiting',
280
+ startedAt: null,
281
+ completedAt: null,
282
+ assignees: [{ userId: 'finance-user', displayName: '财务负责人' }],
283
+ operations: [],
284
+ },
285
+ ];
286
+ const items = [{
287
+ id: 'operation-transfer',
288
+ operation: 'transfer',
289
+ operationLabel: '转交',
290
+ actorUserId: 'acceptance-user',
291
+ actorDisplayName: '李明',
292
+ actingForUserId: null,
293
+ reason: '由值班负责人继续处理',
294
+ detail: {},
295
+ severity: 'normal',
296
+ createdAt: '2026-08-25T01:08:00.000Z',
297
+ }];
298
+ const display = {
299
+ entries: [
244
300
  {
245
301
  key: 'start',
302
+ kind: 'submission',
246
303
  nodeId: null,
247
- kind: 'start',
248
- title: '张三',
304
+ nodeKind: 'start',
305
+ title: '提交申请',
249
306
  status: 'completed',
250
- startedAt: '2026-08-25T01:00:00.000Z',
251
- completedAt: '2026-08-25T01:00:00.000Z',
252
- assignees: [],
307
+ enteredAt: '2026-08-25T01:00:00.000Z',
308
+ leftAt: '2026-08-25T01:00:00.000Z',
309
+ primaryDisplayTime: '2026-08-25T01:00:00.000Z',
310
+ people: [{
311
+ userId: 'applicant-user',
312
+ displayName: '张三',
313
+ avatarUrl: null,
314
+ departmentDisplayName: '理学院',
315
+ }],
253
316
  operations: [],
317
+ result: {},
318
+ terminalReason: null,
254
319
  },
255
320
  {
256
321
  key: 'manager',
322
+ kind: 'node',
257
323
  nodeId: 'manager-review',
258
- kind: 'approval',
324
+ nodeKind: 'approval',
259
325
  title: '部门负责人',
260
326
  status: completed ? 'completed' : 'active',
261
- startedAt: '2026-08-25T01:01:00.000Z',
262
- completedAt: completed ? '2026-08-25T01:12:00.000Z' : null,
263
- assignees: [{ userId: 'acceptance-user', displayName: '李明' }],
327
+ enteredAt: '2026-08-25T01:01:00.000Z',
328
+ leftAt: completed ? '2026-08-25T01:12:00.000Z' : null,
329
+ primaryDisplayTime: '2026-08-25T01:08:00.000Z',
330
+ people: [],
264
331
  operations: [{
265
332
  id: 'operation-transfer',
266
333
  operation: 'transfer',
267
334
  operationLabel: '转交',
268
- actorUserId: 'acceptance-user',
269
- actorDisplayName: '李明',
335
+ actor: {
336
+ userId: 'acceptance-user',
337
+ displayName: '李明',
338
+ avatarUrl: null,
339
+ departmentDisplayName: null,
340
+ },
270
341
  actingForUserId: null,
271
342
  reason: '由值班负责人继续处理',
272
- detail: {},
273
343
  severity: 'normal',
274
- createdAt: '2026-08-25T01:08:00.000Z',
344
+ occurredAt: '2026-08-25T01:08:00.000Z',
275
345
  }],
346
+ result: {},
347
+ terminalReason: null,
276
348
  },
277
349
  {
278
350
  key: 'finance',
351
+ kind: completed ? 'node' : 'planned_node',
279
352
  nodeId: 'finance-review',
280
- kind: 'approval',
353
+ nodeKind: 'approval',
281
354
  title: '财务负责人',
282
355
  status: completed ? 'completed' : 'waiting',
283
- startedAt: null,
284
- completedAt: null,
285
- assignees: [{ userId: 'finance-user', displayName: '财务负责人' }],
356
+ enteredAt: null,
357
+ leftAt: null,
358
+ primaryDisplayTime: null,
359
+ people: [{
360
+ userId: 'finance-user',
361
+ displayName: '财务负责人',
362
+ avatarUrl: null,
363
+ departmentDisplayName: null,
364
+ }],
286
365
  operations: [],
366
+ result: {},
367
+ terminalReason: null,
287
368
  },
288
369
  ],
289
- items: [{
290
- id: 'operation-transfer',
291
- operation: 'transfer',
292
- operationLabel: '转交',
293
- actorUserId: 'acceptance-user',
294
- actorDisplayName: '李明',
295
- actingForUserId: null,
296
- reason: '由值班负责人继续处理',
297
- detail: {},
298
- severity: 'normal',
299
- createdAt: '2026-08-25T01:08:00.000Z',
300
- }],
370
+ };
371
+ return {
372
+ engineVersion: '2.0',
373
+ instanceId,
374
+ instanceSequence: completed ? 4 : 3,
375
+ timelineRevision: (completed ? 'f' : 'e').repeat(64),
376
+ flow,
377
+ items,
378
+ display,
379
+ };
380
+ }
381
+
382
+ function detail(completed = false, customDetail = false) {
383
+ const currentSurface = surface(completed, customDetail);
384
+ const currentTimeline = timeline(completed);
385
+ return {
386
+ schemaVersion: 'openxiangda.workflow-detail-surface/v2',
387
+ protocolVersion: 'workflow_detail_surface_v2',
388
+ detailRevision: (completed ? '2' : '1').repeat(64),
389
+ instanceSequence: currentSurface.instanceSequence,
390
+ timelineRevision: currentTimeline.timelineRevision,
391
+ surface: currentSurface,
392
+ timeline: currentTimeline,
393
+ currentStatus: {
394
+ code: completed ? 'approved' : 'assigned',
395
+ label: completed ? '已同意' : '待审批',
396
+ tone: completed ? 'success' : 'warning',
397
+ },
398
+ nodes: currentTimeline.display.entries,
399
+ handlingRecords: currentTimeline.display.entries.flatMap(
400
+ entry => entry.operations
401
+ ),
402
+ operations: currentSurface.operations,
403
+ navigationContext: {
404
+ desktopReturnPath: '/admin/work-center',
405
+ mobileReturnPath: '/m/work-center',
406
+ },
301
407
  };
302
408
  }
303
409
 
304
410
  function launchSurface() {
305
411
  return {
306
- schemaVersion: 'openxiangda.workflow-launch-surface/v2',
307
- protocolVersion: 'workflow_launch_surface_v2',
412
+ schemaVersion: 'openxiangda.workflow-launch-surface/v3',
413
+ protocolVersion: 'workflow_launch_surface_v3',
308
414
  surfaceRevision: 'launch-revision-1',
309
415
  engineVersion: '2.0',
310
416
  appCode,
@@ -328,7 +434,7 @@ function launchSurface() {
328
434
  contractRevisionId: 'contract-revision-1',
329
435
  },
330
436
  paths: {
331
- desktop: '/admin/workflows/purchase-approval/new',
437
+ desktop: '/workflows/purchase-approval/start',
332
438
  mobile: '/m/workflows/purchase-approval/start',
333
439
  },
334
440
  commit: {
@@ -336,6 +442,15 @@ function launchSurface() {
336
442
  href: `/openxiangda-api/v2/applications/${appCode}/business-process/standard-commands`,
337
443
  idempotencyRequired: true,
338
444
  },
445
+ submission: {
446
+ kind: 'standard-process',
447
+ processOperationCode: 'openxiangda.workflow.purchase-approval.submit',
448
+ commit: {
449
+ method: 'POST',
450
+ href: `/openxiangda-api/v2/applications/${appCode}/business-process/standard-commands`,
451
+ idempotencyRequired: true,
452
+ },
453
+ },
339
454
  };
340
455
  }
341
456
 
@@ -610,6 +725,20 @@ async function mockWorkflow(
610
725
  ),
611
726
  });
612
727
  }
728
+ if (
729
+ path.endsWith(`/workflow/tasks/${taskId}/detail`) ||
730
+ path.endsWith(`/workflow/instances/${instanceId}/detail`)
731
+ ) {
732
+ expect(route.request().headers()['x-openxiangda-csrf-token']).toBe(
733
+ 'workflow-csrf-1'
734
+ );
735
+ return route.fulfill({
736
+ contentType: 'application/json',
737
+ body: JSON.stringify(
738
+ envelope(detail(completed, options.customDetail))
739
+ ),
740
+ });
741
+ }
613
742
  if (path.endsWith(`/workflow/instances/${instanceId}/surface`)) {
614
743
  return route.fulfill({
615
744
  contentType: 'application/json',
@@ -665,7 +794,7 @@ test('renders a paged desktop work center from the current-user role union', asy
665
794
  page,
666
795
  }) => {
667
796
  await mockWorkflow(page);
668
- await page.goto('/workflow-experience.e2e.html?initial=/admin/work-center');
797
+ await page.goto('/workflow-experience.e2e.html?initial=/work-center');
669
798
  await expect(page.getByRole('heading', { name: '审批工作台' })).toBeVisible();
670
799
  await expect(page.getByRole('link', { name: '采购申请审批' })).toBeVisible();
671
800
  await expect(page.getByText('PO-2026-0825')).toBeVisible();
@@ -675,7 +804,7 @@ test('renders the generic desktop and mobile application todo center without a s
675
804
  page,
676
805
  }) => {
677
806
  await mockWorkflow(page);
678
- await page.goto('/workflow-experience.e2e.html?initial=/admin/todos');
807
+ await page.goto('/workflow-experience.e2e.html?initial=/todos');
679
808
  await expect(page.getByRole('heading', { name: '待办中心' })).toBeVisible();
680
809
  await expect(page.locator('.oxa-todo-table')).toBeVisible();
681
810
  await expect(page.locator('.oxa-todo-preview-card')).toHaveCount(0);
@@ -733,7 +862,7 @@ test('routes desktop and mobile work-center entries to the declared custom detai
733
862
  page,
734
863
  }) => {
735
864
  await mockWorkflow(page, { customDetail: true });
736
- await page.goto('/workflow-experience.e2e.html?initial=/admin/work-center');
865
+ await page.goto('/workflow-experience.e2e.html?initial=/work-center');
737
866
  await page.getByRole('link', { name: '采购申请审批' }).click();
738
867
  await expect(page.getByTestId('custom-workflow-detail')).toContainText(
739
868
  `${instanceId} / ${taskId}`
@@ -765,16 +894,16 @@ test('redirects a direct standard task URL to the declared custom detail page',
765
894
  );
766
895
  });
767
896
 
768
- test('renders a standalone desktop detail and redirects the legacy admin URL', async ({
897
+ test('renders a standalone desktop detail on the canonical user URL', async ({
769
898
  page,
770
899
  }) => {
771
900
  await mockWorkflow(page);
772
901
  await page.goto(
773
- workflowFixtureUrl(`/admin/tasks/${taskId}?from=legacy#approval`),
902
+ workflowFixtureUrl(`/tasks/${taskId}?from=todo#approval`),
774
903
  );
775
904
  await expect(page.locator('.oxa-workflow-desktop-standalone-page')).toBeVisible();
776
905
  await expect(page).toHaveURL(
777
- new RegExp(`/tasks/${taskId}\\?from=legacy#approval$`),
906
+ new RegExp(`/tasks/${taskId}\\?from=todo#approval$`),
778
907
  );
779
908
  await expect(page.locator('.oxa-app-layout')).toHaveCount(0);
780
909
  await expect(page.locator('.oxa-topbar')).toHaveCount(0);
@@ -822,7 +951,7 @@ test('shows an explicit authorization error instead of an empty work center', as
822
951
  }) => {
823
952
  test.setTimeout(45_000);
824
953
  await mockWorkflow(page, { forbidden: true });
825
- await page.goto('/workflow-experience.e2e.html?initial=/admin/work-center', {
954
+ await page.goto('/workflow-experience.e2e.html?initial=/work-center', {
826
955
  waitUntil: 'domcontentloaded',
827
956
  });
828
957
  await expect(page.getByRole('alert')).toContainText('403: forbidden', {
@@ -837,7 +966,7 @@ test('negotiates direct standard routes after BrowserRouter listener setup', asy
837
966
  await mockWorkflow(page);
838
967
  await page.setViewportSize({ width: 360, height: 844 });
839
968
  await page.goto(
840
- workflowFixtureUrl('/admin/todos?view=pending#unread', {
969
+ workflowFixtureUrl('/todos?view=pending#unread', {
841
970
  state: 'deep-link',
842
971
  }),
843
972
  );
@@ -862,13 +991,13 @@ test('negotiates direct standard routes after BrowserRouter listener setup', asy
862
991
  await expect(page.locator('.oxa-todo-center-mobile')).toBeVisible();
863
992
  await page.setViewportSize({ width: 901, height: 844 });
864
993
  await expect(page.locator('.oxa-todo-center-desktop')).toBeVisible();
865
- await expect(page).toHaveURL(/\/admin\/todos\?view=pending#unread$/);
994
+ await expect(page).toHaveURL(/\/todos\?view=pending#unread$/);
866
995
  expect(await page.evaluate(() => window.history.length)).toBe(historyLength);
867
996
  expect(await page.evaluate(() => window.history.state?.usr)).toEqual({
868
997
  routeNegotiationState: 'deep-link',
869
998
  });
870
999
  await page.goBack();
871
- await expect(page).not.toHaveURL(/\/admin\/todos\?view=pending#unread$/);
1000
+ await expect(page).not.toHaveURL(/\/todos\?view=pending#unread$/);
872
1001
 
873
1002
  // Direct task and instance URLs carry their dynamic parameter and deep-link
874
1003
  // context to the independent mobile renderer at the narrow width.
@@ -898,7 +1027,7 @@ test('negotiates launch pairs in both directions and preserves the mount basenam
898
1027
  await page.setViewportSize({ width: 360, height: 844 });
899
1028
  await page.goto(
900
1029
  workflowFixtureUrl(
901
- `${base}/admin/workflows/purchase-approval/new?source=todo#launch`,
1030
+ `${base}/workflows/purchase-approval/start?source=todo#launch`,
902
1031
  { base },
903
1032
  ),
904
1033
  );
@@ -913,7 +1042,7 @@ test('negotiates launch pairs in both directions and preserves the mount basenam
913
1042
  await expect(page.locator('.oxa-workflow-submission-desktop')).toBeVisible();
914
1043
  await expect(page).toHaveURL(
915
1044
  new RegExp(
916
- `${base}/admin/workflows/purchase-approval/new\\?source=todo#launch$`,
1045
+ `${base}/workflows/purchase-approval/start\\?source=todo#launch$`,
917
1046
  ),
918
1047
  );
919
1048
  });
@@ -14,7 +14,7 @@
14
14
  "@ant-design/icons": "6.2.3",
15
15
  "@refinedev/core": "5.0.12",
16
16
  "antd": "6.4.3",
17
- "openxiangda": "2.0.0-alpha.69",
17
+ "openxiangda": "2.0.0-alpha.72",
18
18
  "react": "19.2.8",
19
19
  "react-dom": "19.2.8",
20
20
  "react-router-dom": "7.8.2"
@@ -16,6 +16,7 @@ import {
16
16
  authenticationSurfaces,
17
17
  anonymousPublicAccess,
18
18
  adminNavigation,
19
+ adminAccess,
19
20
  adminPages,
20
21
  resourceDefinitions,
21
22
  workflowDefinitions,
@@ -50,6 +51,7 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
50
51
  <OpenXiangdaApplication
51
52
  appCode={appCode}
52
53
  appName={appName}
54
+ adminAccess={adminAccess}
53
55
  adminNavigation={adminNavigation}
54
56
  adminPages={adminPages}
55
57
  routeManifest={routeManifest}
@@ -102,7 +102,7 @@ test('published E2E harnesses do not write evidence into the platform source rep
102
102
  assert.doesNotMatch(workflowSource, /docs\/architecture|evidenceDirectory/);
103
103
  });
104
104
 
105
- test('E2E experience fixtures explicitly own every tested admin route', () => {
105
+ test('E2E experience fixtures explicitly own every tested route', () => {
106
106
  const resourceFixture = readFileSync(
107
107
  new URL('../e2e/resource-experience-fixture.tsx', import.meta.url),
108
108
  'utf8'
@@ -121,7 +121,13 @@ test('E2E experience fixtures explicitly own every tested admin route', () => {
121
121
  assert.match(workflowFixture, /code: 'workflow:work-center'/);
122
122
  assert.match(workflowFixture, /code: 'workflow:task'/);
123
123
  assert.match(workflowFixture, /code: 'workflow:instance'/);
124
- assert.match(workflowFixture, /pageCode: 'workflow:work-center'/);
124
+ assert.match(workflowFixture, /path: '\/work-center'/);
125
+ assert.match(workflowFixture, /path: '\/tasks\/:taskId'/);
126
+ assert.match(workflowFixture, /path: '\/workflows\/:instanceId'/);
127
+ assert.doesNotMatch(
128
+ workflowFixture,
129
+ /path: '\/(?:m\/)?admin\/(?:todos|work-center|tasks|workflows)/
130
+ );
125
131
  });
126
132
 
127
133
  test('browser acceptance serializes the prewarmed Vite HTML entrypoints', () => {
@@ -23,7 +23,7 @@
23
23
  "build": "pnpm --recursive build"
24
24
  },
25
25
  "devDependencies": {
26
- "openxiangda": "2.0.0-alpha.69",
26
+ "openxiangda": "2.0.0-alpha.72",
27
27
  "typescript": "5.9.3"
28
28
  }
29
29
  }
@@ -113,6 +113,7 @@ export const platformAuthManifest = {
113
113
  }
114
114
  ]
115
115
  } as const;
116
+ export const adminAccess = undefined;
116
117
  export const routeManifest = {
117
118
  "schemaVersion": "openxiangda.application-route-manifest/v3",
118
119
  "appCode": "openxiangda-application",
@@ -165,6 +166,7 @@ export type ApplicationAuthenticationMethod = (typeof authenticationMethods)[num
165
166
  export type ApplicationAuthenticationSurface = (typeof authenticationSurfaces)[keyof typeof authenticationSurfaces];
166
167
  export type ApplicationAuthenticationSurfaceCode = ApplicationAuthenticationSurface["routeCode"];
167
168
  export type AnonymousPublicAccess = typeof anonymousPublicAccess;
169
+ export type AdminAccess = typeof adminAccess;
168
170
  export type RouteManifest = typeof routeManifest;
169
171
  export type AdminPage = (typeof adminPages)[number];
170
172
  export type AdminNavigationGroup = (typeof adminNavigation)[number];