Skip to content

Commit 1fcf228

Browse files
authored
feat: improve ui builder skill (#47)
* fix: menu group mismatch * fix: incorrect columnCount * chore: improve skill * chore: improve skill * chore: improve skill * chore: improve skill
1 parent 6b994c4 commit 1fcf228

18 files changed

Lines changed: 967 additions & 170 deletions

skills/nocobase-ui-builder/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ description: >-
4747
5. `layout` belongs only on `tabs[]` or inline `popup`, never on a block object. For `createForm`, `editForm`, `details`, and `filterForm`, use `fieldsLayout` when the blueprint must control the inner field grid directly. Omit page/popup `layout` only when that tab/popup has at most one non-filter block; otherwise explicit layout is required. For low-level `set-layout`, do not reuse public `{ rows: [[{ key, span }]] }` syntax: runtime `rows` is `Record<string, string[][]>`, each cell array stacks live child `uid`s, `[[uidA], [uidB]]` means two columns, and `[[uidA, uidB]]` means one stacked column.
4848
6. For `createForm`, `editForm`, and `details`, once the block contains more than 10 real fields, use explicit `fieldGroups` instead of one flat `fields[]` list. Do not treat manual `divider` items as a substitute, and do not combine `fieldGroups` with `fieldsLayout`.
4949
7. If clicking a shown record or relation record should open details, prefer a field popup. Use a button or action column only when the request explicitly asks for one.
50-
8. If visible same-title destination menu groups already exist, never create another same-title group just to avoid ambiguity and never choose one locally. In that multi-match case, require explicit `navigation.group.routeId` before write. Only zero-match create and one-match title-only reuse may proceed without `routeId`.
50+
8. If visible same-title destination menu groups already exist, never create another same-title group just to avoid ambiguity and never choose one locally. In that multi-match case, require explicit `navigation.group.routeId` before write. Without `routeId`, only zero-match create and one-match title reuse may proceed; metadata on reused groups is ignored.
5151
9. In `applyBlueprint create`, any newly created `navigation.group` and any top-level or second-level `navigation.item` must carry one valid Ant Design icon name. When `navigation.item` is attached under an explicit existing `navigation.group.routeId`, keep an icon by default but do not assume the local preview can prove whether that live target is already third-level or deeper.
5252
10. `navigation.group.routeId` and desktop-route `id` are navigation locators only. When follow-up localized work or explicit inspection is needed after create/init or successful whole-page `applyBlueprint`, normalize to `pageSchemaUid` for page-level `flow-surfaces get`, and only use live `uid` values returned by `get` / `describe-surface` / create responses for `catalog`, `context`, `get-reaction-meta`, `compose`, `configure`, `add*`, or `remove*`. Never pass a desktop-route `id` as `target.uid`. For artifact-only locator handoffs, keep direct machine-readable fields `navigation.routeId`, `page.pageSchemaUid`, and `liveTargets[].uid`; when no live uid exists yet, use a non-empty placeholder string instead of `null`.
5353
11. Before the first real whole-page `applyBlueprint`, run the local prepare-write gate (`node skills/nocobase-ui-builder/runtime/bin/nb-page-preview.mjs --stdin-json --prepare-write` or `prepareApplyBlueprintRequest(...)`) and show one ASCII-first prewrite preview from the same draft blueprint. Treat that helper as a local shape/threshold gate: the CLI path normalizes any helper-envelope `collectionMetadata`, scans the blueprint, and auto-fetches only missing collection metadata with `nb api data-modeling collections get --filter-by-tk <collection> --appends fields -j`, falling back to `nb api resource list --resource collections --filter '{"name":"<collection>"}' --appends fields -j`; already supplied metadata wins and is not overwritten. `--no-auto-collection-metadata` keeps the old fail-closed `missing-collection-metadata` behavior for offline checks. `collectionMetadata` belongs only in the helper envelope/call options, never inside the blueprint root. It accepts omitted data-surface `filter` actions, but every direct non-template public `table` / `list` / `gridCard` / `calendar` / `kanban` block must still carry a non-empty block-level `defaultFilter`; if a `filter` action also provides `settings.defaultFilter`, prepare-write validates that explicit action payload too. Public `kanban` main blocks may keep `fields[]`, but must not carry `fieldGroups`, `fieldsLayout`, or `recordActions`; allowed main-block actions are `filter`, `addNew`, `popup`, `refresh`, and `js`. For block settings, numeric `height` means a fixed height and must be paired with `heightMode: "specifyValue"`; prepare-write auto-adds that mode when `settings.height` is present and `settings.heightMode` is omitted, including popup blocks. For sortable public blocks (`table`, `details`, `list`, `tree`, `kanban`, `gridCard`, `map`), legacy `settings.sort` is normalized to canonical `settings.sorting`; `calendar` is left unchanged by that alias path. With resolved `collectionMetadata`, it validates fixed defaults completeness for every involved scope: required `defaults.collections` entries, required popup `{ name, description }` entries for the fixed `view` / `addNew` / `edit` trio, relation field popup child `resource.binding`, and required `fieldGroups` when any fixed generated popup scene still has more than 10 effective fields. Automatic metadata only fills metadata gaps; it does not generate large-collection `fieldGroups`. For artifact-only drafts with no real write, draft `prewrite-preview.txt` directly from the same draft blueprint instead of attempting the local helper CLI.

skills/nocobase-ui-builder/references/blocks/grid-card.md

Lines changed: 27 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -22,46 +22,42 @@ description: Grid card 区块的最小稳定树、两层 actions slot 语义与
2222

2323
```json
2424
{
25-
"use": "GridCardBlockModel",
26-
"stepParams": {
27-
"resourceSettings": {
28-
"init": {
29-
"dataSourceKey": "main",
30-
"collectionName": "assets"
31-
}
32-
},
33-
"GridCardSettings": {
34-
"columnCount": {
35-
"xs": 1,
36-
"md": 2,
37-
"lg": 3,
38-
"xxl": 4
25+
"type": "gridCard",
26+
"title": "资产指标",
27+
"collection": "assets",
28+
"defaultFilter": {
29+
"logic": "$and",
30+
"items": [
31+
{
32+
"path": "name",
33+
"operator": "$includes",
34+
"value": ""
3935
},
40-
"rowCount": {
41-
"rowCount": 3
36+
{
37+
"path": "status",
38+
"operator": "$eq",
39+
"value": ""
4240
}
43-
}
41+
]
4442
},
45-
"subModels": {
46-
"item": {
47-
"use": "GridCardItemModel",
48-
"subModels": {
49-
"grid": {
50-
"use": "DetailsGridModel",
51-
"subModels": {
52-
"items": []
53-
}
54-
},
55-
"actions": []
56-
}
43+
"settings": {
44+
"columns": {
45+
"xs": 1,
46+
"sm": 1,
47+
"md": 2,
48+
"lg": 3,
49+
"xl": 3,
50+
"xxl": 4
5751
},
58-
"actions": []
59-
}
52+
"rowCount": 3
53+
},
54+
"fields": []
6055
}
6156
```
6257

6358
skill 至少要知道三件事:
6459

60+
- public authoring 使用 `settings.columns` 控制列数,可以传数字或包含 `xs` / `sm` / `md` / `lg` / `xl` / `xxl` 的响应式对象
6561
- `subModels.item.use` 必须是 `GridCardItemModel`
6662
- `subModels.item.subModels.grid.use` 必须是 `DetailsGridModel`
6763
- block 与 item 各有一层 `actions`,语义不同

skills/nocobase-ui-builder/references/chart-core.md

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ Other than that, the chart block should only expose four additional outer-block
4141

4242
The priority is to stabilize both "card displays" and "chart renders". Do not expose frontend-internal details such as `props / decoratorProps / stepParams` to the user.
4343

44-
Chart is also an example of the general public-settings pattern: when creating or reconfiguring, prefer public semantics such as `query / visual / events / title / displayTitle / height / heightMode`. Do not reverse internal `props / decoratorProps / stepParams` from readback into the next input template.
44+
Chart is also an example of the general public-settings pattern: when creating or reconfiguring, prefer public semantics such as `query / visual / events / title / height / heightMode`. Do not reverse internal `props / decoratorProps / stepParams` from readback into the next input template.
4545

4646
## Default Strategy
4747

@@ -59,9 +59,9 @@ Chart is also an example of the general public-settings pattern: when creating o
5959

6060
The most stable execution order for a chart block is not a one-shot blind write. It is:
6161

62-
1. `add-block(type="chart", settings={ title?, displayTitle?, height?, heightMode? })`
62+
1. `add-block(type="chart", settings={ title?, height?, heightMode? })`
6363
2. If you are configuring a builder query, read `context(path="collection")` first to pick fields
64-
3. Run `configure(changes={ query, title?, displayTitle?, height?, heightMode? })` first
64+
3. Run `configure(changes={ query, title?, height?, heightMode? })` first
6565
4. Then read `context(path="chart")`
6666
5. Based on `chart.queryOutputs / aliases / supportedMappings / supportedStyles / safeDefaults / riskyPatterns / unsupportedPatterns`, run `configure(changes={ visual, events? })`
6767
6. Use `nb api flow-surfaces get --uid <chart-uid>` for canonical readback
@@ -81,10 +81,9 @@ Only use `changes.configure` when you are explicitly preserving compatibility wi
8181

8282
## Outer Block Parameters (Minimum Exposed Set)
8383

84-
In addition to `query / visual / events / configure`, the chart block should expose only these four outer parameters to this skill:
84+
In addition to `query / visual / events / configure`, the chart block should expose only these three outer parameters to this skill:
8585

8686
- `title?: string`
87-
- `displayTitle?: boolean`
8887
- `height?: number`
8988
- `heightMode?: "defaultHeight" | "specifyValue" | "fullHeight"`
9089

@@ -95,7 +94,7 @@ Notes:
9594
- `specifyValue`
9695
- `fullHeight`
9796
- For compatibility with old skills / historical payloads, the server still accepts `fixed` and automatically normalizes it to `specifyValue`
98-
- `title` only accepts a non-empty string; `displayTitle` only accepts `true | false`
97+
- `title` only accepts a non-empty string
9998
- `height` only accepts numbers; it must be paired with `heightMode = "specifyValue"` for the frontend to use the fixed value
10099
- The local prepare-write and localized preflight helpers auto-add `heightMode = "specifyValue"` when `height` is present and `heightMode` is omitted
101100
- If `heightMode = "specifyValue"`, it is recommended to also pass `height`
@@ -106,6 +105,7 @@ Notes:
106105

107106
Invalid:
108107

108+
- passing `displayTitle`; current flowSurfaces chart configureOptions do not support it
109109
- documenting `heightMode = "fixed"` as the primary public syntax
110110
- passing arbitrary unknown strings into `heightMode`
111111

@@ -412,10 +412,9 @@ Rules:
412412

413413
Minimum required post-write readback:
414414

415-
- `tree.stepParams.cardSettings.titleDescription.title` when `displayTitle !== false` and `title` is non-empty
415+
- `tree.stepParams.cardSettings.titleDescription.title` when `title` is non-empty
416416
- `tree.stepParams.cardSettings.blockHeight.heightMode`
417417
- `tree.stepParams.cardSettings.blockHeight.height` when `heightMode = "specifyValue"`
418-
- if `displayTitle = false`, expect `tree.stepParams.cardSettings.titleDescription` to be absent
419418
- `cardSettings` is the primary criterion; if `tree.decoratorProps.*` exists it is only an auxiliary mirror, and `tree.props.*` is not the primary criterion
420419
- `tree.stepParams.chartSettings.configure.query`
421420
- `tree.stepParams.chartSettings.configure.chart.option`

skills/nocobase-ui-builder/references/helper-contracts.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Use this before the first real whole-page write.
1515
- treat the normalized write body as authoritative local write shape; expected helper-added or helper-normalized fields should be kept as-is instead of being locally undone
1616
- once this helper has run successfully, the first whole-page write must consume `result.cliBody` rather than reusing the original draft blueprint
1717
- this helper is local/read-only for page writes; it never performs the remote `apply-blueprint` write for you
18+
- by default, in `create` mode it also resolves `navigation.group.title` against live `desktopRoutes`: zero matches keep `title + icon` for new-group creation, one match rewrites the prepared `cliBody` to `navigation.group.routeId`, and multiple matches fail locally requiring explicit `routeId`
1819
- by default, the CLI path auto-resolves missing `collectionMetadata` entries before validation: it normalizes supplied metadata, scans data-bound blocks and popups, resolves association targets from known metadata for up to 5 rounds, fetches only missing collections with `nb api data-modeling collections get --filter-by-tk <collection> --appends fields -j`, and falls back to `nb api resource list --resource collections --filter '{"name":"<collection>"}' --appends fields -j`
1920
- caller-supplied `collectionMetadata` wins; fetched metadata only fills missing collection entries and is not emitted in `result.cliBody`
2021
- pass `--no-auto-collection-metadata` to keep fail-closed behavior; then any data-bound block (a block with `collection`, `resource`, `binding`, `dataSourceKey`, `associationPathName`, or `associationField`) with missing or empty metadata fails with `missing-collection-metadata`

skills/nocobase-ui-builder/references/normative-contract.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,7 @@ For `replace` runs:
221221
- skill-side authoring may omit layout only for scopes with at most one non-filter block; otherwise the draft must decide layout before write
222222
- in `create`, if an existing menu group is already known, prefer `navigation.group.routeId`; when only `navigation.group.title` is given, applyBlueprint reuses one unique same-title group, creates a new group if none exists, and rejects ambiguous multi-match titles
223223
- at the skill-authoring layer, if visible same-title menu groups already exist and title lookup would hit multiple groups, do **not** create another same-title group for disambiguation and do **not** choose one locally; require explicit `routeId` before write
224-
- `navigation.group.routeId` is exact targeting only and must not be mixed with `icon`, `tooltip`, or `hideInMenu`
225-
- same-title reuse is title-only; if an existing group's metadata must change, use low-level `updateMenu` instead of applyBlueprint create
224+
- `navigation.group.routeId` has highest priority and ignores `title`, `icon`, `tooltip`, and `hideInMenu`; title-based reuse also ignores `icon`, `tooltip`, and `hideInMenu` when an existing group is reused; if an existing group's metadata must change, use low-level `updateMenu` instead of applyBlueprint create
226225

227226
Use the resolved page `target` from the public response as the carry-forward locator. A successful `apply-blueprint` response is the default stop point. Run follow-up `get` only when follow-up localized work or explicit inspection needs live structure.
228227

skills/nocobase-ui-builder/references/page-blueprint.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -210,15 +210,16 @@ Example:
210210
### `navigation.group` semantics
211211

212212
- Prefer `navigation.group.routeId` when the destination menu group is already known.
213-
- `navigation.group.routeId` is exact targeting only; do not mix it with `icon`, `tooltip`, or `hideInMenu`.
214-
- `navigation.group.title` is for new-group creation or title-only unique same-title reuse.
213+
- `navigation.group.routeId` has highest priority; when it is present, `title`, `icon`, `tooltip`, and `hideInMenu` are ignored for the existing group.
214+
- `navigation.group.title` is for new-group creation or unique same-title reuse.
215215
- When `navigation.group.title` creates a new group, `navigation.group.icon` is required.
216216
- When `routeId` is omitted and `title` matches:
217217
- zero existing groups -> create a new group
218218
- one existing group -> reuse that group
219219
- multiple existing groups -> reject and require `routeId`
220-
- If same-title reuse hits an existing group, keep it title-only.
220+
- If same-title reuse hits an existing group, `icon`, `tooltip`, and `hideInMenu` are ignored.
221221
- If an existing group's metadata must change, do not rely on applyBlueprint create; use low-level `updateMenu` instead.
222+
- During real-write prepare, the local helper may rewrite one unique same-title existing group or one explicit `routeId` group to `navigation.group.routeId` in `cliBody`. Treat that prepared shape as authoritative.
222223

223224
### `navigation.item` semantics
224225

skills/nocobase-ui-builder/references/settings.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,11 +284,13 @@ When `add-block` creates a public `kanban`, keep collection binding in `resource
284284

285285
Common settings that are suitable for direct inline use:
286286

287-
- generic block: `title`, `displayTitle`, `height`, `heightMode`
287+
- generic card-like block: `title`, `displayTitle`, `height`, `heightMode`
288288
- `table`: `quickEdit`, `treeTable`, `defaultExpandAllRows`, `dragSort`, `dragSortBy`
289289
- `calendar`: `titleField`, `colorField`, `startField`, `endField`, `defaultView`, `quickCreateEvent`, `showLunar`, `weekStart`, `dataScope`, `linkageRules`, `quickCreatePopup`, `eventPopup`
290290
- form-like blocks: `labelWidth`, `labelWrap`, `layout`, `labelAlign`, `colon`
291291

292+
Do not copy `displayTitle` into block families whose runtime configureOptions do not expose it. Known unsupported cases include `chart` and `tree`; chart blocks accept `title`, `height`, `heightMode`, `query`, `visual`, and `events` instead.
293+
292294
Height settings:
293295

294296
- If you set a numeric `height`, pair it with `heightMode: "specifyValue"` so the frontend uses the value.

skills/nocobase-ui-builder/references/tool-shapes.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ nb request body:
375375
}
376376
```
377377

378-
When the target group is not already known, `navigation.group.title` is also valid; applyBlueprint will reuse a unique same-title group or create a new one when no match exists, but multi-match same-title cases must stop and require explicit `routeId` before write. Same-title reuse is title-only. `navigation.group.routeId` is exact targeting only and must not be mixed with `icon`, `tooltip`, or `hideInMenu`; if an existing group's metadata must change, use low-level `update-menu` instead.
378+
When the target group is not already known, `navigation.group.title` is also valid; applyBlueprint will reuse a unique same-title group or create a new one when no match exists, but multi-match same-title cases must stop and require explicit `routeId` before write. `navigation.group.routeId` has highest priority; if it is present, `title`, `icon`, `tooltip`, and `hideInMenu` are ignored. For title-based reuse, `icon`, `tooltip`, and `hideInMenu` are also ignored when an existing group is reused. If an existing group's metadata must change, use low-level `update-menu` instead.
379379

380380
When the requirement is "click the shown record / relation record to open details", prefer a field popup rather than inventing a new action button:
381381

skills/nocobase-ui-builder/references/whole-page-quick.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ Treat these as whole-page too: a whole page create / replace, one route-backed t
3030
- If a whole-page `applyBlueprint` fails before first success, repair the blueprint from the error, rerun `prepare-write` and preview, and retry blueprint-only up to 5 rounds. Do not continue with low-level writes during those pre-success retries. After 5 failed rounds, report the latest blueprint / preview / error evidence.
3131
5. For `create`, any newly created `navigation.group` and any top-level or second-level `navigation.item` must include one valid semantic Ant Design icon. When `navigation.item` is attached under one explicit existing `navigation.group.routeId`, keep an icon by default but do not assume the local preview can prove whether that live target is already third-level or deeper.
3232
6. If visible same-title menu groups already exist, do not pick one locally and do not create another same-title group just to disambiguate. Require explicit `navigation.group.routeId` before the write whenever title lookup would hit multiple groups.
33+
- The real-write prepare helper resolves `navigation.group.title` against live `desktopRoutes` when possible. If exactly one same-title group exists, use the prepared `cliBody` with `navigation.group.routeId`; if more than one exists, stop on the local error and ask for the routeId.
3334
7. When the page is being created now, keep structure, popup, and whole-page interaction logic in the same blueprint:
3435
- root blocks in `tabs[].blocks[]`
3536
- popup content inline under the owning field/action/record action

0 commit comments

Comments
 (0)