@pactor-app/skills 1.34.0 → 1.40.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.
Files changed (49) hide show
  1. package/package.json +1 -1
  2. package/pactor-dsl/SKILL.md +28 -1
  3. package/pactor-dsl/references/dsl-authoring.md +174 -36
  4. package/pactor-prd/SKILL.md +20 -21
  5. package/pactor-prd/references/acceptance-checklist.md +1 -1
  6. package/pactor-prd/references/component-mapping.md +1 -1
  7. package/pactor-prd/references/data-api.md +37 -0
  8. package/pactor-prd/references/incremental-wireframe-delta.md +8 -9
  9. package/pactor-prd/references/page-inventory-and-files.md +2 -2
  10. package/pactor-prd/references/page-templates.md +59 -12
  11. package/pactor-prd/references/project-structure.md +1 -1
  12. package/pactor-prd/template/README.md +4 -4
  13. package/pactor-prd/template/app/api/d/[...parts]/route.ts +80 -0
  14. package/pactor-prd/template/app/layout.tsx +1 -3
  15. package/pactor-prd/template/next.config.js +2 -2
  16. package/pactor-prd/template/pnpm-lock.yaml +97 -97
  17. package/pactor-prd/template/src/pactor/App.tsx +3 -2
  18. package/pactor-prd/template/src/pactor/actions.ts +5 -431
  19. package/pactor-prd/template/src/pactor/components/index.ts +1 -4
  20. package/pactor-prd/template/src/pactor/config.ts +12 -4
  21. package/pactor-prd/template/src/server/yaml/common/dashboard.yaml +1 -1
  22. package/pactor-prd/template/src/server/yaml/common/login.yaml +46 -75
  23. package/pactor-prd/template/src/server/yaml/models/data-list.yaml +4 -4
  24. package/pactor-prd/template/src/server/yaml/provider/provider-detail.yaml +2 -2
  25. package/pactor-prd/template/src/server/yaml/provider/provider-edit.yaml +1 -1
  26. package/pactor-prd/template/src/server/yaml/provider/provider-list.yaml +1 -1
  27. package/pactor-prd/template/src/server/yaml/route/route-detail.yaml +1 -1
  28. package/pactor-prd/template/src/server/yaml/route/route-edit.yaml +1 -1
  29. package/pactor-prd/template/src/server/yaml/route/route-list.yaml +1 -1
  30. package/pactor-prd/template/src/server/yaml/sitemap.yaml +2 -6
  31. package/pactor-prd/references/mock-data-routes.md +0 -66
  32. package/pactor-prd/template/app/api/data/[...parts]/route.ts +0 -160
  33. package/pactor-prd/template/app/login.css +0 -122
  34. package/pactor-prd/template/src/pactor/components/chip-filter.tsx +0 -89
  35. package/pactor-prd/template/src/pactor/components/chip-node.tsx +0 -46
  36. package/pactor-prd/template/src/pactor/mocks/audit-mock.ts +0 -403
  37. package/pactor-prd/template/src/pactor/mocks/call-logs-mock.ts +0 -875
  38. package/pactor-prd/template/src/pactor/mocks/client-debug-mock.ts +0 -188
  39. package/pactor-prd/template/src/pactor/mocks/client-templates-mock.ts +0 -497
  40. package/pactor-prd/template/src/pactor/mocks/clients-mock.ts +0 -483
  41. package/pactor-prd/template/src/pactor/mocks/costs-mock.ts +0 -406
  42. package/pactor-prd/template/src/pactor/mocks/dashboard-mock.ts +0 -192
  43. package/pactor-prd/template/src/pactor/mocks/data-list-mock.ts +0 -84
  44. package/pactor-prd/template/src/pactor/mocks/model-debug-mock.ts +0 -144
  45. package/pactor-prd/template/src/pactor/mocks/models-mock.ts +0 -799
  46. package/pactor-prd/template/src/pactor/mocks/ops-usage-mock.ts +0 -806
  47. package/pactor-prd/template/src/pactor/mocks/providers-mock.ts +0 -424
  48. package/pactor-prd/template/src/pactor/mocks/routes-mock.ts +0 -604
  49. package/pactor-prd/template/src/pactor/services.ts +0 -200
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pactor-app/skills",
3
- "version": "1.34.0",
3
+ "version": "1.40.0",
4
4
  "description": "Agent skills for rapidly building pactor Server-Driven UI applications",
5
5
  "keywords": [
6
6
  "pactor",
@@ -181,7 +181,33 @@ Facts that are NOT guessable and must be followed:
181
181
 
182
182
  - Top level is `{ dsl, page, state?, dataSources?, actions?, components?, lifecycle?, body? }` — `page` is a wrapper object, `dsl: '1.0'` is mandatory.
183
183
  - `Table` reads data via an **expression** (`dataSource: ${data.customers}`), not a source name; column templates use the `cell` key (not `render`) with `${row.x}` scope; pagination is `pagination: { pageSize: 5 }`.
184
- - Form fields bind via `props.name`; the submit button uses `submit: true` (not `htmlType`); `Form onSubmit` receives all values as `${event.<field>}`.
184
+ - **Inside `Form`, every form control must be wrapped in a `Field`** (`Input`/`NumberInput`/`Select`/`Textarea`/`RadioGroup`/`Checkbox`/`Switch`/`Slider`/`DatePicker` …): label / required / description / error belong to `Field`, the control keeps only its own props (`name` / `placeholder` / `value` / `options` / `rules`). Search-bar buttons (查询/重置) also go inside a `Field` so they align with the inputs; **multiple buttons in one `Field` are placed side by side directly — adjacent buttons get spacing automatically (`[&>button+button]:ml-2`), no extra wrapper or margin needed** (use a `Flex` with `gap` inside the `Field` only when aligning mixed content, e.g. a right-aligned button row). Fields bind via `props.name`; the submit button uses `submit: true` (not `htmlType`); `Form onSubmit` receives all values as `${event.<field>}`.
185
+
186
+ ```yaml
187
+ - type: Form
188
+ props: { layout: inline }
189
+ children:
190
+ - type: Field
191
+ props: { label: 关键词 }
192
+ children:
193
+ - type: Input
194
+ props: { name: keyword, placeholder: 输入关键词 }
195
+ - type: Field
196
+ props: { label: 状态 }
197
+ children:
198
+ - type: Select
199
+ props:
200
+ name: status
201
+ options:
202
+ - { label: 启用, value: enabled }
203
+ - { label: 停用, value: disabled }
204
+ - type: Field # 查询/重置按钮同样放 Field 内,与输入框对齐
205
+ children:
206
+ - type: Button
207
+ props: { text: 查询, variant: primary, submit: true }
208
+ - type: Button
209
+ props: { text: 重置 }
210
+ ```
185
211
  - `navigate` takes `to`; `message` takes `text` + `messageType` (a `type` key in action `params` disables expression evaluation).
186
212
  - Query linkage = `onSubmit: [setState, reload]`; the data source's `params` referencing `${state.x}` pick up new values on reload.
187
213
 
@@ -357,6 +383,7 @@ pnpm pactor create my-app
357
383
  - **Do not** rely on frontend permission checks for security; enforce authorization on backend APIs.
358
384
  - **Do not** forget to import `@pactor-app/ui/styles.css` when using the default shadcn kit (`@pactor-app/chat/styles.css` additionally for chat surfaces).
359
385
  - **Do not** put a `type` key in action `params` (see the events workflow pitfall).
386
+ - **Do not** place bare form controls directly under `Form`; wrap every control (including search-bar buttons) in a `Field`.
360
387
  - **Do not** expect DslNode slots (sidebar / toolbar / inspector / …) to render outside a page runtime; in pure React hosts pass ReactNode instead.
361
388
 
362
389
  ## References (bundled — works from npm install)
@@ -178,7 +178,9 @@ Event payloads (`${event}`):
178
178
  | Button `onClick` | none |
179
179
  | Input / Select / Switch `onChange` | the new value (`${event}`) |
180
180
  | Tabs `onChange` | new active key |
181
- | Table `onChange` | `{ current, pageSize, total }` |
181
+ | Table `onChange` | `{ current, pageSize, total, field?, order? }` |
182
+ | Table `onSelectChange` | selected row keys array (`rowSelection: true`) |
183
+ | Table `onRowDrop` | `{ dragKey, dropKey, position: 'before' \| 'after' }` (`rowDraggable: true`) |
182
184
  | Form `onSubmit` | all values (`${event.name}`) |
183
185
  | Form `onValuesChange` | ONLY the changed field `{ <field>: newValue }` |
184
186
 
@@ -198,6 +200,7 @@ Action catalog:
198
200
  | `call` | `name` | run a top-level named action, inherits caller scope |
199
201
  | `delay` | `ms` | wait |
200
202
  | `form.setValues` | `values` (req), `form?` (default `default`) | write field values; does NOT trigger validation or onValuesChange |
203
+ | `form.submit` | `form?` (default `default`) | trigger the form's native submit — runs validation and `onSubmit`; for submit buttons outside the `<form>` DOM (e.g. Drawer `footer` slot) |
201
204
  | `overlay.open` / `overlay.close` | see below | open overlay and await close; close `payload` becomes open's result |
202
205
 
203
206
  `overlay.open` params: `{ overlay: { type: modal|drawer, title?, width?, content: <DslNode> | page: { pageId }, params? } }`. Overlay content evaluates in overlay scope: `${params.x}` = injected params, `${parent.x}` = parent page snapshot, `${state}`/`${data}` still live.
@@ -249,17 +252,24 @@ body:
249
252
  - action: reload
250
253
  params: { dataSource: customers }
251
254
  children:
252
- - type: Input
253
- props: { name: keyword, label: Keyword, placeholder: Name / owner }
254
- - type: Select
255
- props:
256
- name: status
257
- label: Status
258
- options:
259
- - { label: Enabled, value: active }
260
- - { label: Disabled, value: inactive }
261
- - type: Button
262
- props: { text: Search, variant: primary, submit: true } # submit: true, NOT htmlType
255
+ - type: Field # Form 内控件必须放 Field 内(label 归 Field)
256
+ props: { label: Keyword }
257
+ children:
258
+ - type: Input
259
+ props: { name: keyword, placeholder: Name / owner }
260
+ - type: Field
261
+ props: { label: Status }
262
+ children:
263
+ - type: Select
264
+ props:
265
+ name: status
266
+ options:
267
+ - { label: Enabled, value: active }
268
+ - { label: Disabled, value: inactive }
269
+ - type: Field # 查询按钮也放 Field 内,与输入框对齐
270
+ children:
271
+ - type: Button
272
+ props: { text: Search, variant: primary, submit: true } # submit: true, NOT htmlType
263
273
  - type: Card
264
274
  props: { title: Customer Data }
265
275
  children:
@@ -315,15 +325,19 @@ events:
315
325
  - action: message
316
326
  params: { messageType: error, text: Save failed, please retry }
317
327
  children:
318
- - type: Input
319
- props:
320
- name: name
321
- label: Name
322
- value: ${params.name} # field-level initial value (row data backfill)
323
- rules:
324
- - { required: true, message: Please enter a name }
325
- - type: Button
326
- props: { text: Save, variant: primary, submit: true }
328
+ - type: Field
329
+ props: { label: Name, required: true }
330
+ children:
331
+ - type: Input
332
+ props:
333
+ name: name
334
+ value: ${params.name} # field-level initial value (row data backfill)
335
+ rules:
336
+ - { required: true, message: Please enter a name }
337
+ - type: Field
338
+ children:
339
+ - type: Button
340
+ props: { text: Save, variant: primary, submit: true }
327
341
  - action: if # runs after the drawer closes; cancel (X / mask / Esc) skips
328
342
  params:
329
343
  condition: ${actions.editResult.success}
@@ -334,6 +348,60 @@ events:
334
348
  params: { messageType: success, text: Customer updated }
335
349
  ```
336
350
 
351
+ ### 8.2.1 Controlled overlay nodes (state + `open` expression)
352
+
353
+ Alternative to `overlay.open`: declare `Drawer` / `Dialog` / `AlertDialog` as ordinary DSL nodes with `open: ${state.x}` and drive them with `setState`. Use this when the overlay is a fixed part of the page tree (templates, statically known content). Key wiring:
354
+
355
+ ```yaml
356
+ state: { drawerOpen: false }
357
+ body:
358
+ type: Page
359
+ children:
360
+ - type: Button # open
361
+ props: { text: 新建, variant: primary }
362
+ events:
363
+ onClick:
364
+ - action: setState
365
+ params: { drawerOpen: true }
366
+ - type: Drawer
367
+ props:
368
+ title: 编辑
369
+ open: "${state.drawerOpen}"
370
+ style: { width: 480, maxWidth: 'none' }
371
+ okText: 保存 # 默认 footer [取消, 保存]
372
+ events:
373
+ onClose: # 关闭图标 / mask / Esc / 默认「取消」
374
+ - action: setState
375
+ params: { drawerOpen: false }
376
+ onOk: # 默认「保存」→ form.submit 桥接校验 + onSubmit
377
+ - action: form.submit
378
+ children:
379
+ - type: Form
380
+ events:
381
+ onSubmit:
382
+ - action: message
383
+ params: { messageType: success, text: 保存成功 }
384
+ - action: setState
385
+ params: { drawerOpen: false }
386
+ children:
387
+ - type: Field
388
+ props: { label: 名称, required: true }
389
+ children:
390
+ - type: Input
391
+ props:
392
+ name: name
393
+ rules:
394
+ - { required: true, message: 请输入名称 }
395
+ ```
396
+
397
+ Rules of thumb:
398
+
399
+ - `Drawer` 默认带右上角关闭图标(`closable: false` 关闭)与默认 footer([取消 → onClose, 确定 → onOk],右对齐、固定底部);关闭链路在未 wiring `onClose` 时也会默认自闭合,wiring `onClose` 回写 state 是推荐做法。
400
+ - 表单抽屉优先用 `okText` + `events.onOk: form.submit`(最简);需要非按钮组布局时才给 `footer` 传 DslNode 或 `false`。
401
+ - A `submit: true` button only submits the enclosing `<form>`; from the `footer` slot (outside the `<form>` DOM) use the `form.submit` action instead — it runs the same validation + `onSubmit` chain.
402
+ - `AlertDialog` in controlled mode never self-closes: wire BOTH `events.onConfirm` and `events.onCancel` to `setState x=false`.
403
+ - `${form.x}` is written to state only on successful submit; for per-step data in a wizard, submit at each step (or read Form values only after submit).
404
+
337
405
  ### 8.3 Server-side pagination
338
406
 
339
407
  Table's own `pagination` paginates the data already bound to it (client-side). For a server-paginated API, turn it off and pair the table with a standalone `Pagination` component driving state + reload:
@@ -372,6 +440,28 @@ dataSources:
372
440
 
373
441
  Table also supports `rowKey` (row key field, default `'id'`); Table `onChange` (`{ current, pageSize, total }`) exists for its built-in client-side pager.
374
442
 
443
+ ### 8.3.1 Table extras: tree data, row drag, sticky header
444
+
445
+ ```yaml
446
+ - type: Table
447
+ props:
448
+ dataSource: ${data.menus} # rows containing a `children` array render as a tree table automatically
449
+ childrenColumnName: children # default; set another field name, or false to disable tree mode
450
+ indentSize: 16 # px per tree level (default 16); expand toggles render in the first column
451
+ defaultExpandAllRows: true # start fully expanded (default: all collapsed, toggle via the row arrow)
452
+ rowDraggable: true # grip handle column; drag to reorder within the same parent level
453
+ scrollY: 400 # px; body scrolls vertically with the header stuck on top
454
+ events:
455
+ onRowDrop: # reorder already applied internally; use this to persist
456
+ - action: request
457
+ params:
458
+ url: /api/menus/reorder
459
+ method: POST
460
+ body: ${event} # { dragKey, dropKey, position }
461
+ ```
462
+
463
+ Tree tables sort recursively per level and paginate by top-level rows. Sortable columns (`sorter: true`), `rowSelection`, fixed columns (`fixed: right` on the action column) all compose with the above.
464
+
375
465
  ### 8.4 Confirm then act
376
466
 
377
467
  ```yaml
@@ -396,23 +486,67 @@ events:
396
486
  state:
397
487
  cityMap: { Zhejiang: [{ label: Hangzhou, value: Hangzhou }], Jiangsu: [{ label: Nanjing, value: Nanjing }] }
398
488
  cityOptions: []
399
- # fields:
400
- - type: Select
401
- props: { name: region, label: Province, options: [{ label: Zhejiang, value: Zhejiang }, { label: Jiangsu, value: Jiangsu }] }
402
- events:
403
- onChange:
404
- - action: form.setValues # clear the child field first
405
- params: { values: { city: '' } } # add `form: edit` when targeting a named form
406
- - action: setState # then update child options (computed member access)
407
- params: { cityOptions: ${state.cityMap[event]} }
408
- - type: Select
409
- props:
410
- name: city
411
- label: City
412
- options: ${state.cityOptions}
413
- disabled: ${state.cityOptions.length === 0}
489
+ # fields (each control wrapped in Field, as required inside Form):
490
+ - type: Field
491
+ props: { label: Province }
492
+ children:
493
+ - type: Select
494
+ props: { name: region, options: [{ label: Zhejiang, value: Zhejiang }, { label: Jiangsu, value: Jiangsu }] }
495
+ events:
496
+ onChange:
497
+ - action: form.setValues # clear the child field first
498
+ params: { values: { city: '' } } # add `form: edit` when targeting a named form
499
+ - action: setState # then update child options (computed member access)
500
+ params: { cityOptions: ${state.cityMap[event]} }
501
+ - type: Field
502
+ props: { label: City }
503
+ children:
504
+ - type: Select
505
+ props:
506
+ name: city
507
+ options: ${state.cityOptions}
508
+ disabled: ${state.cityOptions.length === 0}
414
509
  ```
415
510
 
511
+ ### 8.6 Card list page (CardList / EntityCard business components)
512
+
513
+ `CardList` renders a grid of `EntityCard` summary cards in one node — no `For` + nested `Flex`/`Text`/`Tag` scaffolding. Item fields mirror `EntityCard` props; extra business fields ride along and come back in event payloads.
514
+
515
+ ```yaml
516
+ state:
517
+ plugins:
518
+ - id: 1
519
+ icon: users # Icon whitelist name (or `logo` image URL)
520
+ title: 用户管理
521
+ subtitle: v0.1.0 # auxiliary line under the title
522
+ description: 管理插件安装状态与运行生命周期,支持启停与依赖检查。
523
+ tags: [{ text: 已安装, color: success }] # Tag colors: default/success/warning/error/info
524
+ actions: [{ text: 停用 }] # footer buttons, right-aligned, muted bar
525
+ body:
526
+ - type: CardList
527
+ props:
528
+ items: ${state.plugins}
529
+ columns: 3 # grid columns, default 3; 1 = single wide rows
530
+ gap: 16 # default 16
531
+ showCount: true # footer "共 N 个" count line
532
+ # createText: 新建插件 # dashed "create" placeholder card in the first cell
533
+ # hoverable: true # whole-card pointer + hover highlight
534
+ events:
535
+ onCreateClick: { action: message, params: { messageType: info, text: open create dialog } }
536
+ onItemClick: { action: message, params: { messageType: info, text: "进入详情:${event.item.title}" } }
537
+ onItemAction: # ${event} = { key, text, item, index }
538
+ - action: confirm
539
+ id: opConfirm
540
+ params: { title: "确认${event.text}「${event.item.title}」?" }
541
+ - action: if
542
+ params:
543
+ condition: ${actions.opConfirm}
544
+ then: [{ action: message, params: { messageType: success, text: done } }]
545
+ ```
546
+
547
+ - `EntityCard` standalone: `icon`/`logo`, `title`, `subtitle`, `tags[]`, `description` (two-line clamp), `meta` (right side of the middle row), `actions[]` (`{ text, key?, variant?, danger? }`), `footerBackground` (muted footer bar, default `true`), `hoverable`. Events `onClick` (whole card) and `onAction` (`{ key, text, index }`) — footer button clicks never trigger the card click.
548
+ - Empty `items` renders an `Empty` state (`emptyText` overrides the default).
549
+
416
550
  ## 9. Common mistakes (observed — do not do these)
417
551
 
418
552
  | Wrong | Right |
@@ -423,6 +557,10 @@ state:
423
557
  | Column template key `render` | `cell` |
424
558
  | `pagination: true` | `pagination: { pageSize: 10 }` |
425
559
  | `htmlType: submit` | `submit: true` |
560
+ | Bare `Input`/`Select`/`Button` directly under `Form` | Wrap every form control (incl. search-bar buttons) in `Field`; label / required / description / error belong to `Field` |
561
+ | Relying on `Field required: true` for validation | It is a visual `*` marker only — declare `rules: [{ required: true, message: ... }]` on the CONTROL (`Input`/`Select`/…); Form validates control rules on submit |
562
+ | `style` / `className` as node-level keys (sibling of `props:`) | They are component props — always nest them inside `props: { className: ..., style: ... }`; node-level keys are silently ignored |
563
+ | `children` nested inside `props:` | `children` is a NODE-level key (sibling of `props:`); nested under `props:` it is silently dropped |
426
564
  | `navigate` param `path` / `url` | `to` |
427
565
  | `message` param `type: success` | `messageType: success` — a `type` (or `action`) key inside action `params` makes the engine treat params as a nested DSL/action config and SKIP expression evaluation |
428
566
  | Reading full form values in `onValuesChange` | It only carries the changed field; use `onSubmit` or `${form.x}` |
@@ -1,13 +1,13 @@
1
1
  ---
2
2
  name: pactor-prd
3
3
  description: |
4
- Use when PRD or markdown wireframes drive Pactor YAML pages (skill id pactor-prd). Greenfield: copy template/ into a new repo. Incremental: in an existing Pactor project, add or update pages from new/changed wireframe files (DSL, sitemap, menus, mock API, styles). Triggers: pactor-prd, prd-to-app, 线框图增量, 已有项目加页面, PRD 更新页面.
5
- version: 1.3.0
4
+ Use when PRD or markdown wireframes drive Pactor YAML pages (skill id pactor-prd). Greenfield: copy template/ into a new repo. Incremental: in an existing Pactor project, add or update pages from new/changed wireframe files (DSL, sitemap, menus, data API, styles). Triggers: pactor-prd, prd-to-app, 线框图增量, 已有项目加页面, PRD 更新页面.
5
+ version: 1.5.0
6
6
  ---
7
7
 
8
8
  # PRD → YAML 页面应用生成
9
9
 
10
- 把「产品文档 + Markdown 线框图」落到 **YAML 页面 + sitemap/mock/样式**;支持 **两种模式**(先选型再执行):
10
+ 把「产品文档 + Markdown 线框图」落到 **YAML 页面 + sitemap/数据接口/样式**;支持 **两种模式**(先选型再执行):
11
11
 
12
12
  | 模式 | 何时用 | 脚手架 |
13
13
  | --- | --- | --- |
@@ -17,6 +17,7 @@ version: 1.3.0
17
17
  ## 输入要求
18
18
 
19
19
  - PRD/线框图:总览 README(模块 × 页面 × 优先级)+ 分页线框 markdown(ASCII + 区域 + 交互)
20
+ - **接口文档/说明(可选)**:API 路径、方法、参数、返回结构;存在时页面 dataSources 直接对接文档接口,不再走 `/api/d/...` 约定
20
21
  - **增量**:可只提供 **变更的线框图文件** 列表(或 PRD 目录 + 标明 diff 范围)
21
22
  - 原产品代码仓(可选):抽设计令牌、自定义 CSS、交互
22
23
  - 目标:空目录(新建工程)或已有工程根目录(增量)
@@ -25,20 +26,21 @@ version: 1.3.0
25
26
 
26
27
  ## 红线(必须遵守)
27
28
 
28
- 1. **dataSources 一律 `type: http`**,指向 `/api/data/...`,不写 `type: service`,便于后续替换真实接口
29
+ 1. **dataSources 一律 `type: http`,不写 `type: service`**;**PRD 含接口文档/说明时,url 取文档中的接口地址**(方法、路径、返回形状对齐,见「1. 输入解析」);无接口文档时指向 `/api/d/...`(转发层空数据回退,后续按 `references/data-api.md` 接真实接口)
29
30
  2. **布局与间距以样板页为准**:新建工程时以 `template/` 同类页为准;**增量时以目标工程内同模块/同类型页**为准。线框图只提取结构与功能,不逐像素还原 ASCII
30
- 3. **样式与原产品一致**:主题令牌(含深浅色)、自定义 CSS(如登录页)、换肤入口全部移植对齐,见 `references/theme-and-style.md`
31
+ 3. **样式与原产品一致**:主题令牌(含深浅色)、自定义 CSS、换肤入口全部移植对齐,见 `references/theme-and-style.md`
31
32
  4. **功能与原产品一致**:线框图描述的筛选/搜索/跳转/弹窗/提交等交互必须经 events/actions 闭环可用,禁止只画静态骨架;错误分支(校验失败提示等)也要实现
32
33
  5. **表单一律用 `Form` 组件**:凡是一组「字段 + 校验 + 提交」的表单——独立表单页、页面内嵌的配置面板(如树/列表右侧的编辑表单)、弹窗/抽屉表单、**搜索栏(关键词 + 查询/重置 的筛选区,用 `Form layout: inline`)**——都必须用 `Form` 包裹,字段经 `props.name` 绑定,校验用字段级 `rules`(required + message),提交用 `submit: true` 的 Button 触发 `onSubmit`;**禁止**散放 `Input`/`Select` + `setState` 攒草稿 + `if` 动作手写校验。上下结构的字段一律用 `Field` 包裹:label、必填星号(`required: true`)、行右提示(`labelHint`)由 `Field` 渲染,控件上只留 `name`/`rules` 等(`Form layout: inline` 的搜索栏除外,label 直接挂控件);禁止手写 `Text("*") + Label` 拼标签。回填联动(如点击列表/树节点填充表单)用 `form.setValues` 动作,不要绑 `${state.x}` 到字段 value;即点即生效的 ChipFilter 快筛行没有提交语义,不属于表单
33
34
  6. **列表页操作列规范**:操作列按钮样式统一 `Button variant: link`;「编辑」与「新建」指向同一个表单(同一路由页或同一弹窗),唯一区别是编辑以行数据预填(路由页 `initialValues: ${data.detail}`,弹窗/内嵌面板 `form.setValues`);**数据列表页(搜索 + 表格 + 新建/编辑)以 `template/src/server/yaml/models/data-list.yaml` 为标准骨架生成**(结构与要点见 `references/page-templates.md`「数据列表标准模型」)
34
35
  7. **文案以线框图为准**:标题、按钮、空态文案逐字取自线框图/PRD
35
- 8. DSL 语法细节(组件 props、表达式、动作)以 `pactor-dsl` skill 与 packages/ui 当前导出为准,本 skill 只约定流程与产出物
36
+ 8. **登录页用 `Login` 业务组件**:PRD 含登录页时,以模板 `yaml/common/login.yaml`(`type: Login`)为样板生成,禁止手写 Card+Form+自定义 CSS;**原型未明确布局方式时必须向用户提问**选择 `layout`(`split` 左右分栏 / `centered` 居中 / `brand-card` 品牌卡片 / `overlap` 卡片叠加 / `backdrop` 整页渐变背景,附效果简述,必要时含 `brandSide`/`brandRatio`/`authVariant` 修饰项),原型可判定布局则直接用、不提问;用户不答默认 `split` + `authVariant: card`;品牌区文案/logo/插画取自 PRD,提交闭环沿用 `onSubmit` → `request` + `message` + `app.reload` 模式
37
+ 9. DSL 语法细节(组件 props、表达式、动作)以 `pactor-dsl` skill 与 packages/ui 当前导出为准,本 skill 只约定流程与产出物
36
38
 
37
39
  ## 流程总览
38
40
 
39
41
  ```text
40
- 选型 → 1 解析 → 2 脚手架(仅新建工程)→ 3 页面 → 4 mock → 5 样式 → 6 验收
41
- 增量:2 改为「摸底」;1/3 只处理 delta;6 变更页全验 + 少量回归
42
+ 选型 → 1 解析 → 2 脚手架(仅新建工程)→ 3 页面 → 4 样式 → 5 验收
43
+ 增量:2 改为「摸底」;1/3 只处理 delta;5 变更页全验 + 少量回归
42
44
  ```
43
45
 
44
46
  详细增量步骤:**`references/incremental-wireframe-delta.md`**。
@@ -47,6 +49,7 @@ version: 1.3.0
47
49
 
48
50
  - 读 README / 变更线框图:模块树、页面 id(kebab-case)、path、菜单、P0/P1/P2
49
51
  - 逐页读线框:页面类型、区域、字段、交互
52
+ - **接口文档解析**:PRD 含接口文档/说明时,提取「资源 → 路径 / 方法 / 返回形状」接口清单,页面 dataSources 以该清单为准;无接口文档则按 `references/data-api.md` 命名约定写 `/api/d/...`
50
53
  - **新建工程**:输出全量 **页面清单表**(`references/page-inventory-and-files.md`)
51
54
  - **增量**:扫描工程现状清单 + 输出 **delta 表**(add / update / deprecate,`references/incremental-wireframe-delta.md`);P2 不建文件
52
55
 
@@ -57,7 +60,7 @@ version: 1.3.0
57
60
  - **整体复制 `template/`** 为新项目起点;不要用 `pnpm create pactor-app` 替代
58
61
  - 工程名:README 产品名 → kebab-case 英文名,创建前确认一次
59
62
  - `pnpm install` + dev 验证;核对 `references/project-structure.md`
60
- - 参考模块 provider/route 作样板;`yaml/models/` 是页面骨架目录(按模块目录结构存放,已注册 sitemap、菜单「页面骨架」分组可直接预览),生成时复制到目标模块目录改字段与文案;**全站生成完成后删除**参考模块、models/ 及对应 mock
63
+ - 参考模块 provider/route 作样板;`yaml/models/` 是页面骨架目录(按模块目录结构存放,已注册 sitemap、菜单「页面骨架」分组可直接预览),生成时复制到目标模块目录改字段与文案;**全站生成完成后删除**参考模块与 models/
61
64
 
62
65
  **增量**
63
66
 
@@ -71,17 +74,13 @@ version: 1.3.0
71
74
  - 校验见 `page-inventory-and-files.md`(全量)或 `incremental-wireframe-delta.md`(增量)
72
75
  - 按 `references/component-mapping.md` 映射组件;按 `references/page-templates.md` 选列表/详情/表单模板(**数据列表页直接复制 `yaml/models/data-list.yaml` 替换占位符**);表单一律 `Form` 包裹
73
76
  - 同步更新 `sitemap.yaml` 的 `pages` + `menus`(hidden 详情/编辑带 `activeMenu`)
74
- - 每页 dataSources 全部 `type: http`;交互 events + actions 闭环
77
+ - 每页 dataSources 全部 `type: http`:**有接口文档 → url 取文档接口地址**;无 → 按 `references/data-api.md` 命名约定写 `/api/d/...`(转发层空数据回退,无需 mock);交互 events + actions 闭环
75
78
 
76
- ### 4. mock 服务
77
-
78
- - 按 `references/mock-data-routes.md` 注册/更新路由表与 `src/pactor/mocks/`;**增量只动变更页涉及的接口**
79
-
80
- ### 5. 样式移植
79
+ ### 4. 样式移植
81
80
 
82
81
  - 按 `references/theme-and-style.md`;**增量**仅受影响页面/令牌(新建工程做全站对齐)
83
82
 
84
- ### 6. 功能与视觉验收(必经,不可跳过)
83
+ ### 5. 功能与视觉验收(必经,不可跳过)
85
84
 
86
85
  - `references/acceptance-checklist.md`:**新建工程**逐页全站验收;**增量**对 **所有变更页** 全项验收 + **至少 1~2 个未改页回归**(菜单可达、控制台无新增 error)
87
86
 
@@ -90,16 +89,16 @@ version: 1.3.0
90
89
  | 文件 | 何时加载 |
91
90
  | --- | --- |
92
91
  | `template/` | 阶段 2 **仅新建工程**:复制为新工程起点;增量勿用 |
93
- | `references/incremental-wireframe-delta.md` | **已有工程**:线框图 add/update/deprecate、改 sitemap/mock/样式 |
92
+ | `references/incremental-wireframe-delta.md` | **已有工程**:线框图 add/update/deprecate、改 sitemap/数据接口/样式 |
94
93
  | `references/project-structure.md` | 阶段 2:目录结构约定与 sitemap.yaml 骨架 |
95
94
  | `references/component-mapping.md` | 阶段 3:线框图区域 → pactor 组件映射 |
96
95
  | `references/page-templates.md` | 阶段 3:列表/详情/表单页 yaml 模板;数据列表页默认用「数据列表标准模型」(搜索 Form + 表格 + 抽屉新建/编辑) |
97
- | `references/mock-data-routes.md` | 阶段 4:mock 路由表模板与命名约定 |
98
- | `references/theme-and-style.md` | 阶段 5:主题令牌提取、皮肤、自定义 CSS、换肤 |
99
- | `references/acceptance-checklist.md` | 阶段 6:验收清单与结果输出格式 |
96
+ | `references/data-api.md` | 阶段 3:数据接口层约定(纯转发、路径即契约、空数据回退、路径命名) |
97
+ | `references/theme-and-style.md` | 阶段 4:主题令牌提取、皮肤、自定义 CSS、换肤 |
98
+ | `references/acceptance-checklist.md` | 阶段 5:验收清单与结果输出格式 |
100
99
  | `references/page-inventory-and-files.md` | 阶段 1–3:清单列 → 目录与文件批量创建、与 sitemap 对齐 |
101
100
 
102
101
  ## Sibling skills
103
102
 
104
- - **`pactor-dsl`** — 无线框图的单页/清单加页;**有线框图 + mock/sitemap 联动**仍用本 skill 增量模式
103
+ - **`pactor-dsl`** — 无线框图的单页/清单加页;**有线框图 + 数据接口/sitemap 联动**仍用本 skill 增量模式
105
104
  - **`pactor-project`** — `pnpm create pactor-app` 空壳(**不要**用其替代本 skill 的 template 复制)
@@ -45,5 +45,5 @@
45
45
 
46
46
  - **dev server 不会因 yaml 改动热重载**(yaml 在服务端模块加载时读取):改 `src/server/yaml/**` 后重启 dev server 再验
47
47
  - **端口冲突**:启动前 `lsof -nP -iTCP:<port> -sTCP:LISTEN`,已有 dev server 直接复用
48
- - **两段数据路径被当详情解析**:选项类接口用单段路径(`/api/data/project-members` 而非 `project/members`)
48
+ - **两段数据路径被当详情解析**:选项类接口用单段路径(`/api/d/project-members` 而非 `project/members`)
49
49
  - **toast 消失快误判未触发**:验证错误提示时在操作后立即截图/读 DOM
@@ -13,7 +13,7 @@
13
13
  | 下拉筛选(操作人等枚举) | `Select` | `options` 可绑 `${data.xxx}`(接口下发选项,单段路径) |
14
14
  | 日期范围筛选 | `DatePicker` | range 模式绑 state,onChange reload |
15
15
  | 数据表格 | `Table` | `columns` + `dataSource: ${data.x.items}`;操作列按钮统一 `Button variant: link`;「编辑」与「新建」共用同一表单,仅以预填数据区分(initialValues / form.setValues) |
16
- | 卡片网格(约 3-4 列) | `Grid` + `Card` | `columns: 3, gap: 16`;「添加卡」用 `Card` 虚线边框自定义样式 |
16
+ | 卡片网格(约 3-4 列) | `CardList`(业务组件) | 数据项直接携带 `icon`/`title`/`subtitle`/`tags`/`description`/`meta`/`actions` 字段;`columns`、`createText`(首格虚线新建卡)、`showCount`、事件 `onItemClick`/`onItemAction`/`onCreateClick`(`${event}` 含 item/index/key);单卡即 `EntityCard`,无需 `For`+`Card` 手写三层结构 |
17
17
  | 状态徽标(启用/停用/告警) | `Badge` / `StatusBadge` | 颜色语义化,状态文案取自线框图 |
18
18
  | 启停开关图标(⏻) | `StatusToggleIcon` | 卡片/行内启停,onChange 闭环动作 + reload |
19
19
  | 权限控制显隐(无权限隐藏新建卡等) | 表达式 `hidden` / `if` 动作 | 绑 `${data.session.permissions}` 等,按会话裁剪 |
@@ -0,0 +1,37 @@
1
+ # 数据接口层(接口文档优先 + 纯转发空数据约定)
2
+
3
+ **接口文档驱动(优先)**:PRD/产品文档含接口文档或接口说明时,页面 dataSources 的 url 直接取文档中的接口地址——方法、参数、返回形状均以文档为准,不再使用下述 `/api/d/...` 命名约定。同源相对路径直接写;跨域绝对地址写完整 URL(`type: http` 原生支持)。转发层与命名约定仅在**无接口文档**时兜底。
4
+
5
+ 页面 DSL 的 http 数据源统一入口:`app/api/d/[...parts]/route.ts`(**纯转发层,不含任何路径映射**)。**路径即契约**:DSL 中的 url(如 `/api/d/providers`)就是服务端接口路径;转发层按相同路径、方法、查询与请求体原样转发到 `$NEXT_PUBLIC_API_BASE_URL/api/d/<parts>`(携带会话 cookie,匿名 401)。
6
+
7
+ **没有服务层接口时返回空数据(不报错)**:上游不可达(连接失败)、接口不存在(404)或返回非 JSON(如命中前端兜底 HTML)时,GET 返回 `{}`、POST 返回 `{ ok: true }`,页面按空态渲染。接真实后端只需把 `NEXT_PUBLIC_API_BASE_URL`(next.config.js 的 `API_BASE_URLS`)换成真实地址,页面 yaml 与转发层零改动。
8
+
9
+ ## 命名约定
10
+
11
+ - **单段路径**:`/api/d/projects`、`/api/d/project-members`——列表/选项类一律单段 kebab-case
12
+ - **禁止** `/api/d/project/members` 这种两段写法:`[...parts]` 会把第二段当详情 id 解析(`project/detail?id=members`),踩过的坑
13
+ - 详情:`/api/d/<resource>/<code|id>`;子资源:`/api/d/<resource>/<code>/<sub>`;写操作:`/api/d/ops/<action>`(POST)
14
+ - 数据源 url 与页面 id 语义对应:`project-list` 页 → `/api/d/projects`
15
+
16
+ ## 转发层结构
17
+
18
+ ```text
19
+ 浏览器(同源 /api/d/*)
20
+ → app/api/d/[...parts]/route.ts 纯转发:会话鉴权(匿名 401)→
21
+ fetch $NEXT_PUBLIC_API_BASE_URL/api/d/<parts>
22
+ 不可达 / 404 / 非 JSON → 空数据(GET {} / POST { ok: true })
23
+ → 服务端接口 真实后端(与 DSL 同路径契约)
24
+ ```
25
+
26
+ 完整实现见模板工程 `app/api/d/[...parts]/route.ts`。
27
+
28
+ ## 对页面 yaml 的要求
29
+
30
+ - dataSources 一律 `type: http`;有接口文档时 url 取文档接口地址(方法/返回形状对齐),否则按命名约定写 `/api/d/...` 最终路径
31
+ - 列表接口返回形状约定 `{ items, total, filters? }`;详情接口对空 id 返回空对象而非 404(新建预填链路依赖)
32
+ - 选项类接口(如下拉的操作人列表)用单段路径,页面 `options: ${data.xxx}` 绑定
33
+ - 写操作走 `ops/` 前缀 POST,页面提交闭环(request → message → reload)不依赖返回体内容
34
+
35
+ ## 替换为真实接口
36
+
37
+ 把 `NEXT_PUBLIC_API_BASE_URL` 换成真实后端基准地址即可——页面 yaml 与转发层代码零改动。页面 dataSources 声明在无后端/真实后端两种状态下都无需改动,这就是必须 `type: http` 且路径即契约的原因。
@@ -8,7 +8,7 @@
8
8
 
9
9
  - `src/server/yaml/sitemap.yaml`(或等价 sitemap 源)
10
10
  - 页面 yaml 目录(**模式 B** `yaml/<模块>/<id>.yaml` 或 **模式 A** `yaml/pages/*.page.yaml` — 见 `pactor-dsl/references/page-catalog-and-layout.md`)
11
- - PRD 风格 mock:`app/api/data/[...parts]/route.ts` + `src/pactor/mocks/`(若工程仍用 `type: service`,增量页 **仍须 http**,并迁移或仅对新页遵守红线)
11
+ - 数据接口层:`app/api/d/[...parts]/route.ts` 纯转发(路径即契约、无服务层接口时返回空数据,见 `data-api.md`);**无 mock 层**(若工程仍用 `type: service`,增量页 **仍须 http**,并迁移或仅对新页遵守红线)
12
12
 
13
13
  不满足时:先 **`pactor-project`** 或 **新建工程 `pactor-prd` 模式** 搭壳,再回来做增量。
14
14
 
@@ -41,12 +41,12 @@
41
41
 
42
42
  | 变更 | 动作 |
43
43
  | --- | --- |
44
- | **add 页面** | `mkdir` 模块目录;新建 yaml;`sitemap.yaml` 增加 `pages` + `menus`;mock 路由表 + mocks 文件 |
44
+ | **add 页面** | `mkdir` 模块目录;新建 yaml;`sitemap.yaml` 增加 `pages` + `menus`;dataSources 直接声明最终接口路径(无需注册) |
45
45
  | **update 页面** | 改对应 yaml(结构/字段/交互/文案);若 path 或 id 变则同步 `sitemap.yaml`、检查 `router.push` / 菜单 `pageId` |
46
- | **update 仅文案/字段** | 只改 yaml;mock 字段变更时改 `mocks/*` 与路由返回形状 |
47
- | **add 数据源** | yaml 内 `dataSources` + `app/api/data/...` 注册 + mock handler |
48
- | **样式** | 仅当线框图要求新页面级样式时改 `globals.css` / `login.css` / 皮肤;见 `theme-and-style.md` |
49
- | **deprecate** | 删 yaml;从 `pages`/`menus` 移除;删 mock 路由项;全局搜 `pageId` / 路径引用 |
46
+ | **update 仅文案/字段** | 只改 yaml |
47
+ | **add 数据源** | yaml 内 `dataSources` 声明即可(路径即契约,转发层无需注册) |
48
+ | **样式** | 仅当线框图要求新页面级样式时改 `globals.css` / 皮肤;见 `theme-and-style.md` |
49
+ | **deprecate** | 删 yaml;从 `pages`/`menus` 移除;全局搜 `pageId` / 路径引用 |
50
50
 
51
51
  **禁止**:为增量在仓库根再复制一份 `template/` 覆盖工程。
52
52
 
@@ -56,7 +56,6 @@
56
56
  | --- | --- | --- |
57
57
  | 脚手架 | 复制 `template/` | **跳过**;仅摸底 |
58
58
  | 页面 | 全量清单 N 文件 | 仅 **delta** 行对应文件 |
59
- | mock | 全表扩展 | 只加/改涉及接口 |
60
59
  | 样式 | 全站移植 | 仅受影响页面/令牌 |
61
60
  | 验收 | 全站清单 | **变更页全量验** + **抽 1~2 条旧页回归**(见 acceptance-checklist) |
62
61
 
@@ -65,7 +64,7 @@
65
64
  ```text
66
65
  对每个 add:存在 yaml 且 sitemap.pages 含 id 且 menus 正确
67
66
  对每个 update:page.id 未变(除非走 rename 流程)且 validate 通过
68
- mock:变更页涉及的所有 http 路径在 Network 200
67
+ 数据接口:变更页涉及的所有 http 路径在 Network 200(无后端时为空数据 {},页面按空态渲染)
69
68
  ```
70
69
 
71
70
  `npx pactor validate` 仅覆盖改动的 yaml 路径。
@@ -73,4 +72,4 @@ mock:变更页涉及的所有 http 路径在 Network 200
73
72
  ## 5. 与 `pactor-dsl` 的分工
74
73
 
75
74
  - **单页小改**、无线框图、只改 DSL 语法:可用 **`pactor-dsl`**。
76
- - **线框图驱动**、含 mock/sitemap/多页联动:用 **本增量流程**(仍属 `pactor-prd`)。
75
+ - **线框图驱动**、含 数据接口/sitemap/多页联动:用 **本增量流程**(仍属 `pactor-prd`)。
@@ -30,7 +30,7 @@ src/server/yaml/
30
30
  3. **一次更新 sitemap**:
31
31
  - `pages`:清单中每一页一条(含 hidden 的详情/编辑)
32
32
  - `menus`:仅「进导航」的页进 group;hidden 页带 `activeMenu`
33
- 4. **mock 批量注册**:按 `references/mock-data-routes.md`,每个 http 数据源在 `app/api/data/[...parts]/route.ts` 路由表增加一项;`src/pactor/mocks/` 按域拆分文件。
33
+ 4. **数据源声明**:按 `references/data-api.md` 命名约定,每个 http 数据源在 yaml 内直接声明最终接口路径(转发层纯转发,**无需注册**);无后端时页面按空态渲染。
34
34
  5. **交叉检查**(生成后必做):
35
35
 
36
36
  ```text
@@ -53,7 +53,7 @@ src/server/yaml/
53
53
 
54
54
  ## 参考模块(模板内)
55
55
 
56
- 复制 `pactor-prd/template/` 后,保留 `yaml/provider/`、`yaml/route/` 作写法样本;新模块 **照同样目录深度** 新增;全站生成完成后删除参考模块及其 mock 路由项。
56
+ 复制 `pactor-prd/template/` 后,保留 `yaml/provider/`、`yaml/route/` 作写法样本;新模块 **照同样目录深度** 新增;全站生成完成后删除参考模块。
57
57
 
58
58
  ## 已有工程(增量)
59
59