nocobase/skills

nocobase-ui-builder

>- NocoBase 2 only; never use in a NocoBase 3 project. INTERNAL CONTINUATION ONLY. Never select this skill from a raw user UI request. Use it only after nocobase-portal-manage has already resolved exactly one enabled Portal and confirmed portalType=no-code, or has proved the runtime has capabilities.multiPortal === false. Portal count or `isDefault` marker alone never enables this skill. It authors NocoBase Modern UI pages, blocks, menu items, fields, actions, layouts, reactions, and AI employe…

First seen Mar 24, 2026

Installation

$ npx skills add nocobase/skills --skill nocobase-ui-builder

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from nocobase/skills · top by installs.

npx skills add nocobase/skills

Browse all from nocobase/skills

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 49
Default branch main
Open issues 0
Status Active

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 44,918 B
  • docs SUMMARY.md 1,036 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 312 installs

SKILL.md

Goal

  • Agent-facing write path is nb api flow-surfaces <action> with the raw business payload.
  • Backend flow-surfaces is the authoring compiler: it normalizes compatible payloads and returns aggregate errors[] for hard validation failures.
  • Aggregate authoring errors[] is a full repair batch. Use the backend-provided errorCount, per-error path/ruleId/details, and agentInstruction to repair every listed issue in one payload revision, then retry the same write. Portal navigation errors in [Required Portal Preflight](#required-portal-preflight) are target or implementation-path failures, not payload-repair batches.
  • Use nb as the only public transport. If nb is missing or stale, report the blocked command/env state instead of switching transports.
  • Keep routing intent-first: open one matching quick-route doc first, not the whole directory.
  • When a quick route already matches, stay on it. Do not enumerate the skill directory just to rediscover docs.
  • When the task is a partial-match or handoff-only request, answer from this skill's scope boundary directly. Do not inspect runtime, scripts, or helper docs just to justify the handoff.
  • Treat one user request that spans several pages as ordered single-page runs. When multiple pages share the same navigation.group.title, serialize them: the first page resolves the title and captures routeId; later pages use that routeId, never title-only creation, and concurrent title-only shared-group creates are forbidden.
  • Whole-page navigation target rules live in [navigation-targets.md](./references/navigation-targets.md): a resolved no-code Portal requires exact kind: "portal" plus routeName discovery and navigation.portalUid; direct Admin/Mobile layout targeting is legacy-only when capabilities.multiPortal === false; duplicate same-title non-mobile groups require explicit routeId; shared title-only group creates must be serialized.

Mandatory Portal Manager Entry

Before applying any other instruction in this skill, load and execute nocobase-portal-manage for the same user request and follow its Ordinary UI Build Workflow. Do not start a quick route, inspect UI Builder navigation targets, or make a flow-surfaces call first.

  • If the current request already carries a Portal Manager routing outcome, consume it and do not invoke Portal Manager again. This prevents a no-code dispatch back to UI Builder from looping.
  • A valid outcome is exactly one selected enabled Portal with its exact name and portalType, explicit legacy evidence that capabilities.multiPortal === false, or Portal Manager's verified legacy Flow Surfaces signature (Portal target discovery absent while core apply-blueprint and structured read capability are present). A carried isDefault marker is display/readback context only and must not substitute for selection.
  • portalType === "no-code": continue this skill with the selected Portal context.
  • portalType === "ai": leave this skill and continue the same request on Portal Manager's AI Portal source-code path without asking the user again. Portal Manager must pull the Portal first when the selected record/info has no local development path.
  • Zero enabled Portals, multiple unselected Portals, or a missing/unsupported type remain Portal Manager outcomes; do not bypass them inside UI Builder.

Router

  • Portal-first boundary: Mandatory Portal Manager Entry is the only entry to this router. Exactly one enabled Portal may be selected automatically, but count or default status alone is not admission: continue here only when Portal Manager reports that selected record's portalType is exactly no-code, or with explicit evidence that capabilities.multiPortal === false. A sole AI Portal exits this skill without a flow-surfaces write, then immediately continues the same request on Portal Manager's source-code implementation path; Portal Manager pulls the Portal first if no development path exists. Do not ask whether to use the AI Portal or create a no-code Portal. A missing or unsupported portalType returns to Portal Manager for resolution without falling back to Admin.
  • whole-page authoring goes through backend applyBlueprint, nb api flow-surfaces apply-blueprint, and [whole-page-quick.md](./references/whole-page-quick.md)
  • AI employee / AI assistant action authoring stays inside whole-page or localized flow-surfaces writes; read [ai-employee-actions.md](./references/ai-employee-actions.md) when the request mentions AI employee placement, AI analysis buttons, AI assistants, or AI task reconfiguration. Use nocobase-ai-employee first only when the request needs employee discovery, matching, creation, prompt/model/skill/tool configuration, or a lifecycle decision beyond binding an existing visible username.
  • Dashboard / KPI / overview routing: [dashboard-routing.md](./references/dashboard-routing.md)
  • Comments and record history route through public Flow Surfaces block types; read [comments.md](./references/blocks/comments.md) or [record-history.md](./references/blocks/record-history.md) when the request mentions comments, discussion threads, history, audit history, or change history
  • Calendar / schedule / event-view authoring uses CalendarBlockModel; read [calendar.md](./references/blocks/calendar.md) when the request mentions 日历, calendar, 排期, 日程, 事件视图, or 排班
  • Menu, page-entry, routeId, pageSchemaUid, layout/workspace scope, and duplicate-page identity decisions use [navigation-targets.md](./references/navigation-targets.md). Visible desktop menu discovery uses nb api resource list --resource 'desktopRoutes:listAccessible' --no-paginate -j; do not try unavailable desktop-routes CLI families, menus, catalog, or describe-surface just to find menu items.
  • localized existing-surface edits go through backend actions such as compose, configure, update-settings, add-, move-, and remove-*, plus [local-edit-quick.md](./references/local-edit-quick.md)
  • localized existing-surface reaction work starts with get-reaction-meta, writes through set*Rules, and [reaction-quick.md](./references/reaction-quick.md); first-pass whole-page reactions stay in reaction.items[] with no live get-reaction-meta; artifact-only localized reaction drafts record the planned get-reaction-meta probe
  • partial-match or boundary-only requests go through [boundary-quick.md](./references/boundary-quick.md) first
  • After that route is clear, if template / reference / copy routing is truly in scope, read [template-quick.md](./references/template-quick.md) first and then [templates.md](./references/templates.md) for the full decision matrix.
  • Do not open [tool-shapes.md](./references/tool-shapes.md) until you are preparing a real nb body.
  • If the task involves JS code, renderer: "js", jsBlock, jsColumn, jsItem, JS actions, charts, or ctx.* API questions, read [js.md](./references/js.md) first, then [js-surfaces/index.md](./references/js-surfaces/index.md), then [js-snippets/index.md](./references/js-snippets/index.md), and only then [js-reference-index.md](./references/js-reference-index.md). If that intent includes opening a popup / drawer / dialog / drilldown, also read [popup-openview.md](./references/patterns/popup-openview.md): resolve a template-first popup-capable FlowModel before writing JS.
  • Whenever a payload or RunJS body will explicitly author defaultFilter, dataScope.filter, resource.setFilter(), request params.filter, or any { path, operator, value } condition, first load the nocobase-utils skill with topic filter, then read [Filter Condition Format](../nocobase-utils/references/filter/index.md) and inspect live metadata for the terminal field. Keep the UI Builder shape required by the host, but choose the operator only from the terminal field's frontend allowlist. Date comparisons use $dateBefore, $dateAfter, $dateNotBefore, or $dateNotAfter, never $lt, $lte, $gt, or $gte.
  • Before using a flow-surfaces action you have not used yet in the current task, run nb api flow-surfaces <action> --help when available.
  • The only local helper CLI kept by this skill is nb-template-decision. Invoke it through node skills/nocobase-ui-builder/runtime/bin/nb-template-decision.mjs from this repo root, or through the equivalent absolute path to this skill. Do not probe the bare helper name first.

Required Portal Preflight

Complete this gate before any flow-surfaces mutation, including whole-page and localized writes:

  1. Consume the current-request outcome produced by Mandatory Portal Manager Entry. If it is absent, return to that entry and execute Portal Manager before doing anything else in UI Builder.
  2. If the current request has no Portal resolution, or multiple Portals remain unselected, stop without writing and keep the request in nocobase-portal-manage.
  3. If exactly one enabled Portal was selected, require Portal Manager to report the selected record's portalType; the fact that only one Portal exists or that a Portal is marked isDefault is never sufficient by itself.
  4. Continue UI Builder only when portalType === "no-code". For exactly one Portal with portalType === "ai", exit UI Builder and immediately continue the same build request on Portal Manager's source-code path: locate the selected Portal project, run nb portal pull <portal> first if the selected record/info has no development path, implement, and test there. The original build request already authorizes this handoff; do not ask the user to choose between the AI Portal and creating a no-code Portal, and do not stop the overall task merely because UI Builder exited. For a missing or other unsupported type, exit without writing and return to Portal Manager to choose a compatible skill or implementation path; never fall back to Admin.
  5. For the confirmed no-code Portal, require a non-empty selected Portal name, then resolve a non-empty navigation.portalUid exactly as documented in [navigation-targets.md](./references/navigation-targets.md). A missing or ambiguous mapping stops without writing.
  6. The legacy Admin/Mobile layout lane requires either explicit capabilities.multiPortal === false evidence or Portal Manager's verified legacy Flow Surfaces signature. A merely missing, true, failed, unauthorized, server-error, or unclear capability result is not legacy evidence.
  7. Multi-portal whole-page create must include the resolved navigation.portalUid, omit navigation.layoutUid, and never retry against admin-layout-model. A mobile-backed no-code Portal still enters through navigation.portalUid and then follows the backend's mobile-root rules.

Backend Portal navigation errors are stop/handoff signals, not aggregate authoring payload repairs:

  • navigation-portal-type-unsupported: stop the same-write retry, do not switch to Admin, and hand off to the selected AI Portal source path.
  • navigation-portal-selection-required: stop all writes and ask the user to select a Portal through Portal Manage.
  • navigation-portal-not-found: stop and ask the user to create or inspect the requested Portal through Portal Manage.
  • navigation-admin-layout-not-portal-target: rerun Portal resolution; do not alter the payload to continue writing to Admin.

Required Block Type Error Recovery

  • When the user requirement or semantic routing has selected jsBlock, keep that section as jsBlock after authoring errors. Repair the payload shape (settings.code or assets.scripts.<key>.code plus block.script) and retry the same block type. Do not replace it with table, list, chart, actionPanel, gridCard, markdown, or a deferred note just to bypass the error.
  • When the user requirement or semantic routing has selected chart, keep that section as chart after authoring errors. Repair the payload shape (assets.charts.<key>.query, assets.charts.<key>.visual, and block.chart for whole-page blueprints, or localized settings.query/settings.visual) and retry the same block type. Do not replace it with table, list, jsBlock, actionPanel, gridCard, markdown, or a deferred note just to bypass the error.
  • An aggregate errors[] response for a required jsBlock or chart means the current payload is invalid; it is not permission to change the required block type. Change block type only when the user changes the requirement or the backend explicitly says that block type is unsupported in the current container.
  • If a required jsBlock or chart still cannot be authored after the documented repair attempts, report that specific section as unfinished with the latest error evidence instead of claiming completion through a fallback block.

Hard Rules

  1. After Required Portal Preflight succeeds, use nb api flow-surfaces <action> directly for nb writes. Send the raw business payload once; do not create wrapper envelopes, cliBody, or local helper output as a prerequisite.
  2. For a normal single-page request, default to exactly one real tab. Do not add empty tabs or placeholder markdown / note / banner blocks unless the user asked for them.
  3. Default blueprint fields[] entries to simple strings. Upgrade a field entry to an object only when popup, target, renderer, a field-specific type, or clear form behavior inferred from live field description is required.
  4. For page authoring, field truth comes from live collection metadata. Prefer nb api data-modeling collections get --filter-by-tk <collection> --appends fields -j; if that command family is unavailable, use nb api resource list --resource collections --filter '{"name":"<collection>"}' --appends fields -j. Do not use data-modeling fields list / nb api data-modeling collections fields list as the authoring truth. Any field used in blueprint fields[] must have a non-empty interface. Treat collection field description as active form-authoring input: use agent/LLM semantic extraction for arbitrary languages, then emit only structured public behavior, optionally as normalized field metadata descriptionBehavior.{settings,linkage} before prepare. Clear static required hints become field settings.required, low-risk length / range / regex / count constraints become settings.rules or settings.maxCount, and unambiguous same-form conditional required/disabled/hidden hints become reaction.items[] on stable local form targets, with generated local keys materialized only when needed; for backend-generated add/edit popups, put the same behavior under target-scoped defaults.collections.<collection>.formBehavior.addNew/edit. When a conditional description names fields by localized UI label/title instead of field name (for example 状态 for status or 重点变化 for highlights), resolve those labels through live metadata and emit an explicit linkage rule; do not rely on backend fallback parsing. Map condition values through live option value / localized label metadata when available. Helper text such as settings.extra is not sufficient coverage for conditional required/disabled/hidden behavior, and such fields must not be marked implemented unless a real linkage rule targets the field. Leave ambiguous descriptions as helper text instead of guessing, and do not rely on the NocoBase backend to parse raw descriptions or on deterministic keyword lists for arbitrary-language coverage. For whole-page applyBlueprint, recompute the full involved collection set from live metadata on every draft and rebuild defaults.collections from scratch instead of reusing stale fragments. For every involved direct collection, emit or allow the backend to materialize popups.view / popups.addNew / popups.edit as stable { name, description } descriptors, and let any table block pull that collection into addNew threshold evaluation even when the blueprint did not spell out an addNew opener. Keep fieldGroups and formBehavior collection-only on the target collection, and add defaults.collections.<collection>.fieldGroups only when one of those fixed generated popup scenes should still have more than 10 effective fields after scene filtering; otherwise let the backend materialize compatible defaults. Before finalizing generated defaults fieldGroups, run one compact self-review verdict (approve or regenerate) and regenerate at most once using the lowest practical reasoning / no-think mode. If the backend returns aggregate field-group errors, regenerate semantic groups from live metadata and retry once. For association fields, keep every involved relation scope on the same fixed view / addNew / edit trio under defaults.collections.<sourceCollection>.popups.associations.<associationField>.<action> with the same { name, description } contract, keyed only by the first relation segment. Do not create per-association or relation-scoped fieldGroups / formBehavior. Never generate defaults.blocks, and never put blocks, fields, fieldGroups, or layout under defaults.collections.*.popups.

When backend-generated add/edit candidate fields have a non-empty description, account for each described generated field either through structured defaults.collections.<collection>.formBehavior.addNew/edit output or through sibling defaults.collections.<collection>.formBehaviorDescriptionReview.fields.<field> = { decision, reasonCode? }. Use decision: "implemented" only when real coverage exists through structured formBehavior or applicable reaction.items[]; otherwise use decision: "noUiBehavior" or "unsupported" with a valid reasonCode. Do not send old fields: string[], hasTried, formBehavior: {}, or null as no-op escape hatches; null review entries are only for generated candidates whose description is empty.

  1. layout belongs only on tabs[] or inline popup, never on a block object. For createForm, editForm, and details, omit fieldsLayout by default and let backend authoring generate the inner grid: ordinary fields are two per row, divider, richText, and vditor fields get full-width rows from live collection metadata. For filterForm, prepare may still synthesize the compact three-per-row fieldsLayout. Use explicit fieldsLayout only 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. When multiple non-filter blocks share the same tab/popup, each non-template-backed data block needs a title; template-backed blocks are exempt. A single non-filter block may omit its title unless the user explicitly asks for one; if a redundant single-scope data-block title is supplied, backend authoring strips the persisted block chrome title. 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 uids, [[uidA], [uidB]] means two columns, and [[uidA, uidB]] means one stacked column.
  2. 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.
  3. 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.
  4. Whole-page navigation decisions must follow [navigation-targets.md](./references/navigation-targets.md): duplicate same-title non-mobile groups require explicit navigation.group.routeId; mobile targets use root navigation and no group. Non-mobile page identity is target plus menu group routeId plus page title: the same group and same title may replace, while a different group, layout, or portal with the same title does not merge, reuse, or auto-replace another page. A selected no-code Portal always uses its exactly mapped navigation.portalUid and omits navigation.layoutUid; ignore navigation-target defaults, layout targets, and backing layoutUid values. Direct Admin/Mobile layout navigation is allowed only in the proven legacy lane.
  5. In non-mobile applyBlueprint create, any newly created navigation.group and any top-level or second-level navigation.item must carry one valid Ant Design icon name. For mobile creates, navigation.item.icon must carry the page entry icon and no group icon is needed.
  6. Shared title-only group creates are serialized: first non-mobile page may resolve/create by navigation.group.title; later pages in the same target layout must use the captured navigation.group.routeId.
  7. 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.
  8. Before the first real whole-page applyBlueprint, complete Required Portal Preflight, make sure live collection/page/menu metadata used for planning is current, then call nb api flow-surfaces apply-blueprint --body-file <payload>.json -j. The backend resolves compatible defaults such as sorting aliases, height modes, popup defaults, field groups, title fields, navigation group title matches, template decisions, and omitted direct data-surface defaultFilter groups; explicit invalid default filters still return aggregate errors[]. If the backend returns aggregate authoring errors[], repair all listed issues in one pass and retry the raw payload; do not fix only the first error and immediately call applyBlueprint again. The four Portal navigation errors above stop this loop.
  9. Update action field assignment uses only public settings.assignValues. bulkUpdate is a collection action under block actions; updateRecord is a record action under recordActions. assignValues must be one plain object keyed by fields from the host collection metadata; {} is valid and clears assignment values. When a requested action is described as 图标按钮 / 仅图标 / icon button, preserve that visual intent by setting public settings.icon and settings.iconOnly: true. Submit/update-record workflow binding uses only public settings.triggerWorkflows or configure.changes.triggerWorkflows; each row is { workflowKey, context? }, [] clears bindings, and null is invalid. Do not use add-fields, raw flowModels, AssignFormGridModel, AssignFormItemModel, or direct internal stepParams for these action settings.

AI employee actions use only public type: "aiEmployee" plus settings.username, settings.auto, settings.workContext, settings.tasks, and settings.style; existing AI employee actions are reconfigured with the same keys under configure.changes or update-settings. Work context references in applyBlueprint / compose may use target: "self" or a same-run block key; localized edits use self or a live Flow Model uid; type is optional because the backend defaults it to flow-model. Task prompts go in tasks[].message.user or the tasks[].prompt alias, not both. Never write raw AI props, stepParams, flowModels, or database rows for shortcuts.

  1. Treat the backend response as the authority for normalized persisted write shape. Whole-page work includes whole-page create / replace, one route-backed tab full build, complex multi-block pages, nested-popup pages, and pages with multiple reaction families. Pre-write reads and metadata fetch are allowed, but the first mutating write in that route must be backend applyBlueprint. A successful applyBlueprint response is the default stop point. Run follow-up get only when follow-up localized work or explicit inspection needs live structure. Without that extra readback, report the write from the success response and request intent rather than as a normalized persisted subtree.
  2. Treat default values, computed values, field/block/action state, and show/hide as reaction work first. Do not guess raw configure keys.
  3. Any JS / RunJS write payload must preserve readable multiline source. For value.source: "runjs", jsBlock / jsItem / JS actions, chart raw code, or any other code field, author non-trivial code with actual newline characters and 2-space indentation before writing. Do not compress multiple statements, local variable setup, conditional branches, or string assembly onto one physical line just because the surrounding payload is JSON; encode line breaks as \n in JSON strings. Only a single short return/expression with no setup or branching may stay one line.

For jsBlock, the public payload is strict: new inline blocks use type: "jsBlock" + required settings.code / optional settings.version; existing JSBlock configure uses changes.code / changes.version; whole-page asset reuse goes under assets.scripts.<key>.code with block script: "<key>". Never create title-only JSBlocks that rely on default template code. Never author block top-level code, block top-level version, handwritten stepParams, or internal props / decoratorProps / flowRegistry for jsBlock, and never mix script with inline code/version. For JS / RunJS / chart code that opens a popup, use the popup/openView template-first path before writing code. The code may call ctx.openView(triggerUid, ...), but triggerUid must be an existing popup-capable FlowModel uid, usually a popup host whose openView.uid targetUid points at a template target with popupTemplateUid / popupTemplateMode="reference". Do not use a ChildPageModel, page, tab, popup subtree, or transient uid as the default trigger target. Runtime values that popup blocks/settings must consume, such as chart drilldown filters for a table dataScope, must be passed through defineProperties with meta and read as top-level variables like {{ctx.drilldownValue}}. Do not generate {{ctx.view.inputArgs.params.*}} for popup block settings. If a persisted action is only a chart ctx.openView() host for those variables, hide that host with actionLinkage; do not leave a visible row action whose popup depends on chart-only defineProperties.

  1. If live readback shows an existing template reference and the requested change touches template-owned content, default to the template source. Keep host/openView config edits local. Page-scoped wording is not local-only intent, so do not auto-detach to copy; clarify before writing when scope is unresolved. For decision artifacts, record the template-owned content route and the host/openView route separately as templateOwnedContentRoute and hostOpenViewConfigRoute.
  2. A successful applyBlueprint response that binds an inline popup opener to popup.template is a successful template-reuse outcome, not a structural miss. Inline popup.blocks in the submitted blueprint are fallback content for a template miss; they are not required to persist when the backend selects a compatible popup template. Do not remove the template-reuse probe, switch to replace, convert to copy, or rerun a whole page merely because the normalized response shows template references instead of expanded local popup blocks. Only force inline/local popup content when the user explicitly asks for local-only behavior such as "不要模板", "只改当前", copy, detach, or "本地独立"; keep exact popup-template flags in [template-quick.md](./references/template-quick.md) and [templates.md](./references/templates.md).
  3. In testing or multi-agent runs, do not perform destructive cleanup unless the user explicitly asked for deletion.
  4. When you actually have persisted readback to summarize for the user or for local helper artifacts, prefer one stable public summary with normalized type labels such as table, details, editForm, filterForm, and createForm; do not rely on raw model names alone. For page-level create / replace, keep page.pageSchemaUid, page.pageTitle, and page.menuGroupTitle explicit in that summary. When a scenario spans multiple pages, use the same canonical page identity keys under pages., and use type for concrete summary nodes such as tables., lists., and forms.; reserve blockTypes for aggregate arrays such as root.blockTypes or popups.*.blockTypes. Keep root actions under root.actionTitles instead of leaving recordActionTitles as the only proof.
  5. For reaction work, pick the final block/action target only after get-reaction-meta proves the required source path is available in that scene. On targets that expose multiple capabilities, select the write slot by matching kind first and then reuse that exact capability fingerprint; do not copy a nearby fingerprint from another kind. If the current target cannot expose the needed path, move the target or restructure the page/popup first instead of writing a guessed rule to an unsupported host.
  6. Resolve filter wording before choosing structure. For table / list / gridCard / calendar / kanban-like data surfaces, ambiguous “筛选 / filter” requests default to the same host's block-level filter action/button, not a separate filter block. Treat “搜索 / search” that way only when the request explicitly adds search to a table / list / gridCard / calendar / kanban / card-like host, including wording such as “支持搜索 / 带搜索 / 可搜索 / searchable”; page-noun wording such as “搜索页 / 搜索结果页 / 搜索门户 / 搜索列表页” should stay page intent, not filter intent. Route 树筛选 / 树状筛选 / tree filter / tree filter block / 树形筛选区块 directly to TreeBlockModel, not FilterFormBlockModel; read [references/blocks/tree.md](./references/blocks/tree.md) before writing it. Route 分析看板 / dashboard / trend / 概览 to chart / JSBlock insight paths by default: trends, distributions, rankings, percentages / 占比, and visual analysis use chart; KPI / 指标卡 / 数字统计 / 待阅数-style count metrics use JSBlockModel; record-card wording uses GridCardBlockModel. Route to KanbanBlockModel only when kanban cues such as 看板区块 / kanban / pipeline / status columns / 拖拽 / 泳道 / backlog are present. Plain 看板 alone does not override analytics intent. Do not create a new filterForm by default. Read [references/aliases.md](./references/aliases.md) first. Open [references/blocks/filter-form.md](./references/blocks/filter-form.md) and keep a real filterForm in the first-pass blueprint only when the user explicitly asks for a filter/search block, form, or query area and the phrase is not a tree-filter request; then include stable filter items, submit / reset actions, and same-blueprint string target block keys instead of low-level defaultTargetUid or raw block settings payloads.

Dashboard metric hard rule: - When the requested surface includes KPI / 指标卡 / 数字统计 / 统计卡 / 追踪产品数 / 待阅数 / 本周新增数 / summary numbers, use JSBlockModel by default. - Do not implement passive metric cards with actionPanel + js actions. - Do not use GridCardBlockModel for aggregated numeric metrics. - Use ActionPanelBlockModel only for user operations, shortcuts, or action entry points, unless the user explicitly asks for clickable action cards as the primary UI. - If a dashboard draft places pure numeric summary content inside actionPanel, regenerate the draft before writing. - If the backend returns a jsBlock authoring error, make the final retry instruction repair the same jsBlock payload (settings.code or assets.scripts + block.script) and retry that block type; do not switch the KPI area to table, chart, actionPanel, or gridCard. - If the backend returns a chart authoring error, repair the current chart payload from details.repairHint and retry it as chart; do not change the block type just to bypass the error. If the error says the content is a KPI / summary number, rebuild that section as jsBlock. - Dashboard chart gate: when the request explicitly asks for chart / 图表 / Charts, or for trend / 趋势, distribution / 分布, ranking / 排行, percentage / 占比, the first dashboard draft must include type: "chart" blocks for those sections. If the request gives a chart count, the payload must contain at least that many chart blocks before applyBlueprint. - jsBlock KPI cards and table/list summaries never count as chart coverage. If a required chart section is missing from the draft or readback, regenerate or repair it as chart; do not summarize the dashboard as complete. - For chart-required whole-page creates, successful applyBlueprint is not enough by itself: run flow-surfaces get for the returned pageSchemaUid and confirm the chart block evidence before claiming completion. - Probe pages may be used for metadata or payload investigation, but they are not final deliverables. Do not count them in the final handoff, and remove probe pages left under the user's delivery menu when cleanup is safe. Direct non-template table / list / gridCard / calendar / kanban data surfaces may omit defaultFilter; backend authoring materializes one from live collection metadata with up to 4 scalar/filterable fields and routes it to the same host's filter action. Provide block-level defaultFilter or actions[].settings.defaultFilter only when intentionally overriding those fields. Explicit default filters must cover the smaller of 3 and the collection's eligible direct interface-field count; explicit empty, invalid, relation-field, or unknown-path values are rejected through backend aggregate errors[]. Table block settings use public keys such as settings.pageSize, settings.sorting, settings.dataScope, settings.density, settings.showRowNumbers, settings.treeTable, settings.dragSort, and settings.dragSortBy; never write persisted/internal tableSettings, defaultSorting, or stepParams under either the block root or settings. For every direct public data surface, partial actions merge with that host's defaults (filter / refresh / addNew, plus table bulkDelete). Ordinary table recordActions merge with view / edit / delete, but tree collection tables with settings.treeTable=true do not complete view / edit / delete; omit tree-table recordActions unless the user explicitly asks, and let the backend inject only addChild by default. For tree tables with explicit fields[], keep the list self-contained: fields[0] must be a direct readable non-association field from live metadata, or an existing later readable field may be moved first; never inject a missing title / name fallback into explicit fields, and reject explicit lists with no readable direct field. Only omitted fields[] uses default first-field metadata priority titleField, then name, then code, then title, then another direct field with a non-empty interface. Never put id, uid, uuid, parentId, primary/foreign keys, xxxId / xxxUid, id, or uid first. Keep filter/search intent on the same host's block-level filter action unless the user explicitly asks for a separate filter block. Direct non-template whole-page applyBlueprint kanban main blocks may explicitly carry at most 2 card fields[]; omitted fields are materialized from live metadata with at most 2 suitable display fields, and explicit overflow returns kanban-main-fields-too-many instead of being trimmed. The same applyBlueprint path defaults settings.dragEnabled=true; send settings.dragSortBy only when a compatible sort field exists, otherwise let the backend create a hidden sort field for writable main datasource collections. Explicit dragEnabled=false opts out, and explicit incompatible dragSortBy is rejected.

  1. When you author one localized compose / add-block / add-blocks / configure body, send it directly to nb api flow-surfaces <action>. The backend authoring pipeline performs compatibility normalization and hard validation before write side effects. If it returns aggregate errors[], repair the full list, not only the first error.

For comments and recordHistory, use only the public block keys and inspect catalog before localized adds. comments page blocks require a comment-template collection; popup comments require resource.binding: "associatedRecords" with a hasMany or belongsToMany association to a comment-template collection. recordHistory blocks require a collection with a real filterTargetKey; current-record history is only valid in one-record popup/details scenes; association history resources are invalid. Never write raw CommentsBlockModel, RecordHistoryBlockModel, CommentItemModel, props, stepParams, or raw schema to bypass these checks.

  1. If a first-pass whole-page write still leaves filterForm as an empty shell after a successful applyBlueprint, treat it as an explicit local/live gap and keep any low-level addBlock / addAction / addField repair narrowly scoped. If the first applyBlueprint fails with a verified filterForm-specific shape/runtime error, repair the blueprint from the backend aggregate errors and retry blueprint-only up to 5 rounds. Do not switch to low-level writes during those pre-success retries. After 5 failed rounds, report the latest blueprint / error evidence.
  2. If a create/edit form helper or reference depends on formValues.*, inspect catalog / get-reaction-meta before choosing the host. When that live scene exposes fields / actions / node but not blocks, model the helper as a jsItem or other field-like helper inside the same form scene, not as a standalone block; for current JSItem targets, implement hide/show by rendering null until the form value is present instead of assuming setFieldState can target the JSItem. When that render-null pattern is used successfully, treat it as a configured helper toggle in readback/evidence instead of marking the helper outcome false only because fieldLinkage cannot target the JSItem itself.
  3. Treat pages with multiple work areas, filter/search blocks, nested popups, or multiple reaction families as complex whole-page requests, not as a separate router path. They still stay on [whole-page-quick.md](./references/whole-page-quick.md) and still prefer one applyBlueprint request.
  4. For those complex whole-page requests, first-pass blueprint generation should include the structural blocks, inline popups, and top-level reaction.items[] together. Do not split the page into root-shell / popup / reaction phases just because the page is large.
  5. Use low-level get-reaction-meta + setRules or add repair only for localized edits on an existing live page, or after a successful whole-page applyBlueprint when an explicit local/live gap still needs narrowly scoped repair. Before one whole-page applyBlueprint succeeds, do not use createMenu, createPage, compose, configure, update-settings, add, move, remove, or setRules. If a whole-page applyBlueprint fails before first success, repair the blueprint from the backend aggregate errors and retry blueprint-only up to 5 rounds. Do not switch to low-level writes during those pre-success retries. After 5 failed rounds, report the latest blueprint / error evidence.
  6. Stay env-neutral in the general skill contract. Use the current configured CLI env or explicit runtime flags instead of hard-coding local aliases or fixed URLs.

Read Paths

  • Route unclear: [references/index.md](./references/index.md)
  • Whole-page draft/create/replace from business intent: [whole-page-quick.md](./references/whole-page-quick.md)
  • Whole-page navigation layout, group, route, and duplicate-page identity rules: [navigation-targets.md](./references/navigation-targets.md)
  • Dashboard / KPI / overview routing: [dashboard-routing.md](./references/dashboard-routing.md)
  • Localized existing-surface edit: [local-edit-quick.md](./references/local-edit-quick.md)
  • AI employee action placement or task edits: [ai-employee-actions.md](./references/ai-employee-actions.md)
  • Calendar / schedule / event-view blocks: [calendar.md](./references/blocks/calendar.md)
  • Comments or record history blocks: [comments.md](./references/blocks/comments.md), [record-history.md](./references/blocks/record-history.md)
  • Whole-page or localized reaction change: [reaction-quick.md](./references/reaction-quick.md)
  • Partial-match handling and narrow handoff reports: [boundary-quick.md](./references/boundary-quick.md)
  • Reuse, template selection, or existing template reference edits: [template-quick.md](./references/template-quick.md)
  • JS or chart work: [js.md](./references/js.md) or [chart.md](./references/chart.md)

Scope & Handoff

  • Handle only Modern page (v2) menu/page/tab/popup/content surfaces and the block / field / action / layout / reaction work inside them after one selected Portal has been explicitly confirmed with portalType === "no-code", or in a proven capabilities.multiPortal === false legacy runtime. A single Portal with an unconfirmed or non-no-code type is out of scope.
  • Hand off every unresolved page/UI request to nocobase-portal-manage before any navigation discovery or mutation. Portal Manage returns only a selected no-code Portal to this skill. When the sole selected Portal is AI, leave UI Builder but keep executing the original request through Portal Manage's source project without asking for confirmation or offering to create a no-code Portal. When nb portal is missing, return here only if the capability check explicitly proves multiPortal === false.
  • For partial-match or boundary-report tasks, keep the Modern-page slice narrow and write the handoff report directly from this boundary list. Do not inspect runtime or scripts unless the request is explicitly about those mechanics.
  • Hand off ACL / route permissions / role permissions to nocobase-acl-manage.
  • Hand off collection / field / relation authoring to nocobase-data-modeling.
  • Hand off workflow create / update / revision / execution to nocobase-workflow-manage.
  • Hand off app-building revision creation to nocobase-revision.

Revision rule

  • Do not load nocobase-revision during Portal routing, target selection, metadata planning, preflight, failed writes, or unfinished verification.
  • Only after a meaningful UI write has succeeded and its required verification is complete, load nocobase-revision once at the end of the round. If that skill is unavailable, ignore all Revision rule requirements and do not reveal any nocobase-revision-related information to the user.
  • Before nocobase-revision confirms that nb revision create is available, do not reveal any revision-related information to the user.
  • After availability is confirmed, make exactly one revision decision at the end of the round, immediately before the final user-facing reply.
  • If a revision is created, report it plainly.
  • If availability was confirmed but no revision was created, state the concrete reason.