SKILL.md
Full skill instructions
Goal
- Agent-facing write path is
nb api flow-surfaces <action>with the raw business payload. - Backend
flow-surfacesis the authoring compiler: it normalizes compatible payloads and returns aggregateerrors[]for hard validation failures. - Aggregate
errors[]is a full repair batch. Use the backend-providederrorCount, per-errorpath/ruleId/details, andagentInstructionto repair every listed issue in one payload revision, then retry the same write. - Use
nbas the only public transport. Ifnbis 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.
- Whole-page navigation target rules live in navigation-targets.md: default desktop/admin
admin-layout-model; mobile intent usesnavigation.layoutUid: "mobile-layout-model",navigation.item, and nonavigation.group; duplicate same-title non-mobile groups require explicitrouteId; shared title-only group creates must be serialized.
Router
- whole-page authoring goes through backend
applyBlueprint,nb api flow-surfaces apply-blueprint, and whole-page-quick.md - AI employee / AI assistant action authoring stays inside whole-page or localized
flow-surfaceswrites; read ai-employee-actions.md when the request mentions AI employee placement, AI analysis buttons, AI assistants, or AI task reconfiguration. Usenocobase-ai-employeefirst 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
- Comments and record history route through public Flow Surfaces block types; read comments.md or 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 when the request mentions 日历, calendar, 排期, 日程, 事件视图, or 排班 - Menu, page-entry,
routeId,pageSchemaUid, layout scope, and duplicate-page identity decisions use navigation-targets.md. Do not try unavailable desktop-routes CLI families,menus,catalog, ordescribe-surfacejust to find menu items. - localized existing-surface edits go through backend actions such as
compose,configure,update-settings,add-*,move-*, andremove-*, plus local-edit-quick.md - localized existing-surface reaction work starts with
get-reaction-meta, writes throughset*Rules, and reaction-quick.md; first-pass whole-page reactions stay inreaction.items[]with no liveget-reaction-meta; artifact-only localized reaction drafts record the plannedget-reaction-metaprobe - partial-match or boundary-only requests go through boundary-quick.md first
- After that route is clear, if template / reference /
copyrouting is truly in scope, read template-quick.md first and then templates.md for the full decision matrix. - Do not open 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, orctx.*API questions, read js.md first, then js-surfaces/index.md, then js-snippets/index.md, and only then js-reference-index.md. If that intent includes opening a popup / drawer / dialog / drilldown, also read popup-openview.md: resolve a template-first popup-capable FlowModel before writing JS. - Before using a
flow-surfacesaction you have not used yet in the current task, runnb api flow-surfaces <action> --helpwhen available. - The only local helper CLI kept by this skill is
nb-template-decision. Invoke it throughnode skills/nocobase-ui-builder/runtime/bin/nb-template-decision.mjsfrom this repo root, or through the equivalent absolute path to this skill. Do not probe the bare helper name first.
Required Block Type Error Recovery
- When the user requirement or semantic routing has selected
jsBlock, keep that section asjsBlockafter authoring errors. Repair the payload shape (settings.codeorassets.scripts.<key>.codeplusblock.script) and retry the same block type. Do not replace it withtable,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 aschartafter authoring errors. Repair the payload shape (assets.charts.<key>.query,assets.charts.<key>.visual, andblock.chartfor whole-page blueprints, or localizedsettings.query/settings.visual) and retry the same block type. Do not replace it withtable,list,jsBlock,actionPanel,gridCard,markdown, or a deferred note just to bypass the error. - An aggregate
errors[]response for a requiredjsBlockorchartmeans 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
jsBlockorchartstill 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
- For nb writes, use
nb api flow-surfaces <action>directly. Send the raw business payload once; do not create wrapper envelopes,cliBody, or local helper output as a prerequisite. - 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. - Default blueprint
fields[]entries to simple strings. Upgrade a field entry to an object only whenpopup,target,renderer, a field-specifictype, or clear form behavior inferred from live fielddescriptionis required. - 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, usenb api resource list --resource collections --filter '{"name":"<collection>"}' --appends fields -j. Do not usedata-modeling fields list/nb api data-modeling collections fields listas the authoring truth. Any field used in blueprintfields[]must have a non-emptyinterface. Treat collection fielddescriptionas active form-authoring input: use agent/LLM semantic extraction for arbitrary languages, then emit only structured public behavior, optionally as normalized field metadatadescriptionBehavior.{settings,linkage}before prepare. Clear static required hints become fieldsettings.required, low-risk length / range / regex / count constraints becomesettings.rulesorsettings.maxCount, and unambiguous same-form conditional required/disabled/hidden hints becomereaction.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-scopeddefaults.collections.<collection>.formBehavior.addNew/edit. When a conditional description names fields by localized UI label/title instead of fieldname(for example状态forstatusor重点变化forhighlights), resolve those labels through live metadata and emit an explicit linkage rule; do not rely on backend fallback parsing. Map condition values through live optionvalue/ localizedlabelmetadata when available. Helper text such assettings.extrais not sufficient coverage for conditional required/disabled/hidden behavior, and such fields must not be markedimplementedunless 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-pageapplyBlueprint, recompute the full involved collection set from live metadata on every draft and rebuilddefaults.collectionsfrom scratch instead of reusing stale fragments. For every involved direct collection, emit or allow the backend to materializepopups.view/popups.addNew/popups.editas stable{ name, description }descriptors, and let anytableblock pull that collection intoaddNewthreshold evaluation even when the blueprint did not spell out anaddNewopener. KeepfieldGroupsandformBehaviorcollection-only on the target collection, and adddefaults.collections.<collection>.fieldGroupsonly 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 (approveorregenerate) 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 fixedview/addNew/edittrio underdefaults.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-scopedfieldGroups/formBehavior. Never generatedefaults.blocks, and never putblocks,fields,fieldGroups, or layout underdefaults.collections.*.popups. When backend-generated add/edit candidate fields have a non-emptydescription, account for each described generated field either through structureddefaults.collections.<collection>.formBehavior.addNew/editoutput or through siblingdefaults.collections.<collection>.formBehaviorDescriptionReview.fields.<field> = { decision, reasonCode? }. Usedecision: "implemented"only when real coverage exists through structuredformBehavioror applicablereaction.items[]; otherwise usedecision: "noUiBehavior"or"unsupported"with a validreasonCode. Do not send oldfields: string[],hasTried,formBehavior: {}, ornullas no-op escape hatches;nullreview entries are only for generated candidates whosedescriptionis empty. layoutbelongs only ontabs[]or inlinepopup, never on a block object. ForcreateForm,editForm, anddetails, omitfieldsLayoutby default and let backend authoring generate the inner grid: ordinary fields are two per row,divider,richText, andvditorfields get full-width rows from live collection metadata. ForfilterForm, prepare may still synthesize the compact three-per-rowfieldsLayout. Use explicitfieldsLayoutonly when the blueprint must control the inner field grid directly. Omit page/popuplayoutonly 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-levelset-layout, do not reuse public{ rows: [[{ key, span }]] }syntax: runtimerowsisRecord<string, string[][]>, each cell array stacks live childuids,[[uidA], [uidB]]means two columns, and[[uidA, uidB]]means one stacked column.- For
createForm,editForm, anddetails, once the block contains more than 10 real fields, use explicitfieldGroupsinstead of one flatfields[]list. Do not treat manualdivideritems as a substitute, and do not combinefieldGroupswithfieldsLayout. - 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.
- Whole-page
navigationdecisions must follow navigation-targets.md: duplicate same-title non-mobile groups require explicitnavigation.group.routeId; mobile pages usenavigation.layoutUid: "mobile-layout-model"and no group; same layout + same group/root + samepage.titlemay replace, while different group or layout never merges, reuses, or auto-replaces another page. - In non-mobile
applyBlueprint create, any newly creatednavigation.groupand any top-level or second-levelnavigation.itemmust carry one valid Ant Design icon name. For mobile creates,navigation.item.iconmust carry the page entry icon and no group icon is needed. - 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 capturednavigation.group.routeId. navigation.group.routeIdand desktop-routeidare navigation locators only. When follow-up localized work or explicit inspection is needed after create/init or successful whole-pageapplyBlueprint, normalize topageSchemaUidfor page-levelflow-surfaces get, and only use liveuidvalues returned byget/describe-surface/ create responses forcatalog,context,get-reaction-meta,compose,configure,add*, orremove*. Never pass a desktop-routeidastarget.uid. For artifact-only locator handoffs, keep direct machine-readable fieldsnavigation.routeId,page.pageSchemaUid, andliveTargets[].uid; when no live uid exists yet, use a non-empty placeholder string instead ofnull.- Before the first real whole-page
applyBlueprint, make sure live collection/page/menu metadata used for planning is current, then callnb 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-surfacedefaultFiltergroups; explicit invalid default filters still return aggregateerrors[]. If the backend returns aggregateerrors[], repair all listed issues in one pass and retry the raw payload; do not fix only the first error and immediately callapplyBlueprintagain. - Update action field assignment uses only public
settings.assignValues.bulkUpdateis a collection action under blockactions;updateRecordis a record action underrecordActions.assignValuesmust 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 publicsettings.iconandsettings.iconOnly: true. Submit/update-record workflow binding uses only publicsettings.triggerWorkflowsorconfigure.changes.triggerWorkflows; each row is{ workflowKey, context? },[]clears bindings, andnullis invalid. Do not useadd-fields, rawflowModels,AssignFormGridModel,AssignFormItemModel, or direct internalstepParamsfor these action settings. AI employee actions use only publictype: "aiEmployee"plussettings.username,settings.auto,settings.workContext,settings.tasks, andsettings.style; existing AI employee actions are reconfigured with the same keys underconfigure.changesorupdate-settings. Work context references inapplyBlueprint/composemay usetarget: "self"or a same-run block key; localized edits useselfor a live Flow Modeluid;typeis optional because the backend defaults it toflow-model. Task prompts go intasks[].message.useror thetasks[].promptalias, not both. Never write raw AIprops,stepParams,flowModels, or database rows for shortcuts. - 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 successfulapplyBlueprintresponse is the default stop point. Run follow-upgetonly 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. - Treat default values, computed values, field/block/action state, and show/hide as reaction work first. Do not guess raw configure keys.
- Any JS / RunJS write payload must preserve readable multiline source. For
value.source: "runjs",jsBlock/jsItem/ JS actions, chart raw code, or any othercodefield, 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\nin JSON strings. Only a single short return/expression with no setup or branching may stay one line. ForjsBlock, the public payload is strict: new inline blocks usetype: "jsBlock"+ requiredsettings.code/ optionalsettings.version; existing JSBlockconfigureuseschanges.code/changes.version; whole-page asset reuse goes underassets.scripts.<key>.codewith blockscript: "<key>". Never create title-only JSBlocks that rely on default template code. Never author block top-levelcode, block top-levelversion, handwrittenstepParams, or internalprops/decoratorProps/flowRegistryforjsBlock, and never mixscriptwith 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 callctx.openView(triggerUid, ...), buttriggerUidmust be an existing popup-capable FlowModel uid, usually a popup host whoseopenView.uidtargetUid points at a template target withpopupTemplateUid/popupTemplateMode="reference". Do not use aChildPageModel, 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 tabledataScope, must be passed throughdefinePropertieswithmetaand 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 chartctx.openView()host for those variables, hide that host withactionLinkage; do not leave a visible row action whose popup depends on chart-onlydefineProperties. - 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 astemplateOwnedContentRouteandhostOpenViewConfigRoute. - A successful
applyBlueprintresponse that binds an inline popup opener topopup.templateis a successful template-reuse outcome, not a structural miss. Inlinepopup.blocksin 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 toreplace, convert tocopy, 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 and templates.md. - In testing or multi-agent runs, do not perform destructive cleanup unless the user explicitly asked for deletion.
- 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, andcreateForm; do not rely on raw model names alone. For page-level create / replace, keeppage.pageSchemaUid,page.pageTitle, andpage.menuGroupTitleexplicit in that summary. When a scenario spans multiple pages, use the same canonical page identity keys underpages.*, and usetypefor concrete summary nodes such astables.*,lists.*, andforms.*; reserveblockTypesfor aggregate arrays such asroot.blockTypesorpopups.*.blockTypes. Keep root actions underroot.actionTitlesinstead of leavingrecordActionTitlesas the only proof. - For reaction work, pick the final block/action target only after
get-reaction-metaproves the required source path is available in that scene. On targets that expose multiple capabilities, select the write slot by matchingkindfirst and then reuse that exact capability fingerprint; do not copy a nearby fingerprint from anotherkind. 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. - 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
filteraction/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 toTreeBlockModel, notFilterFormBlockModel; read 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 useJSBlockModel; record-card wording usesGridCardBlockModel. Route toKanbanBlockModelonly when kanban cues such as看板区块 / kanban / pipeline / status columns / 拖拽 / 泳道 / backlogare present. Plain看板alone does not override analytics intent. Do not create a newfilterFormby default. Read references/aliases.md first. Open references/blocks/filter-form.md and keep a realfilterFormin 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/resetactions, and same-blueprint stringtargetblock keys instead of low-leveldefaultTargetUidor raw block settings payloads. Dashboard metric hard rule:
- When the requested surface includes KPI / 指标卡 / 数字统计 / 统计卡 / 追踪产品数 / 待阅数 / 本周新增数 / summary numbers, use
JSBlockModelby default. - Do not implement passive metric cards with
actionPanel+jsactions. - Do not use
GridCardBlockModelfor aggregated numeric metrics. - Use
ActionPanelBlockModelonly 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
jsBlockauthoring error, make the final retry instruction repair the samejsBlockpayload (settings.codeorassets.scripts + block.script) and retry that block type; do not switch the KPI area totable,chart,actionPanel, orgridCard. - If the backend returns a chart authoring error, repair the current chart payload from
details.repairHintand retry it aschart; 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 asjsBlock. - 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 beforeapplyBlueprint. jsBlockKPI 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 aschart; do not summarize the dashboard as complete.- For chart-required whole-page creates, successful
applyBlueprintis not enough by itself: runflow-surfaces getfor the returnedpageSchemaUidand 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'sfilteraction. Provide block-leveldefaultFilteroractions[].settings.defaultFilteronly 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 aggregateerrors[]. Table block settings use public keys such assettings.pageSize,settings.sorting,settings.dataScope,settings.density,settings.showRowNumbers,settings.treeTable,settings.dragSort, andsettings.dragSortBy; never write persisted/internaltableSettings,defaultSorting, orstepParamsunder either the block root orsettings. For every direct public data surface, partialactionsmerge with that host's defaults (filter/refresh/addNew, plus tablebulkDelete). Ordinary tablerecordActionsmerge withview/edit/delete, but tree collection tables withsettings.treeTable=truedo not completeview/edit/delete; omit tree-tablerecordActionsunless the user explicitly asks, and let the backend inject onlyaddChildby default. For tree tables with explicitfields[], 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 missingtitle/namefallback into explicit fields, and reject explicit lists with no readable direct field. Only omittedfields[]uses default first-field metadata prioritytitleField, thenname, thencode, thentitle, then another direct field with a non-emptyinterface. Never putid,uid,uuid,parentId, primary/foreign keys,xxxId/xxxUid,_id, or_uidfirst. Keep filter/search intent on the same host's block-levelfilteraction unless the user explicitly asks for a separate filter block. Direct non-template whole-pageapplyBlueprintkanban main blocks may explicitly carry at most 2 cardfields[]; omitted fields are materialized from live metadata with at most 2 suitable display fields, and explicit overflow returnskanban-main-fields-too-manyinstead of being trimmed. The same applyBlueprint path defaultssettings.dragEnabled=true; sendsettings.dragSortByonly when a compatible sort field exists, otherwise let the backend create a hidden sort field for writable main datasource collections. ExplicitdragEnabled=falseopts out, and explicit incompatibledragSortByis rejected.
- When you author one localized
compose/add-block/add-blocks/configurebody, send it directly tonb api flow-surfaces <action>. The backend authoring pipeline performs compatibility normalization and hard validation before write side effects. If it returns aggregateerrors[], repair the full list, not only the first error. ForcommentsandrecordHistory, use only the public block keys and inspectcatalogbefore localized adds.commentspage blocks require a comment-template collection; popup comments requireresource.binding: "associatedRecords"with ahasManyorbelongsToManyassociation to a comment-template collection.recordHistoryblocks require a collection with a realfilterTargetKey; current-record history is only valid in one-record popup/details scenes; association history resources are invalid. Never write rawCommentsBlockModel,RecordHistoryBlockModel,CommentItemModel,props,stepParams, or raw schema to bypass these checks. - If a first-pass whole-page write still leaves
filterFormas an empty shell after a successfulapplyBlueprint, treat it as an explicit local/live gap and keep any low-leveladdBlock/addAction/addFieldrepair narrowly scoped. If the firstapplyBlueprintfails with a verifiedfilterForm-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. - If a create/edit form helper or reference depends on
formValues.*, inspect catalog /get-reaction-metabefore choosing the host. When that live scene exposesfields/actions/nodebut notblocks, model the helper as ajsItemor other field-like helper inside the same form scene, not as a standalone block; for current JSItem targets, implement hide/show by renderingnulluntil the form value is present instead of assumingsetFieldStatecan 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 becausefieldLinkagecannot target the JSItem itself. - 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 and still prefer one
applyBlueprintrequest. - 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. - Use low-level
get-reaction-meta+set*Rulesoradd*repair only for localized edits on an existing live page, or after a successful whole-pageapplyBlueprintwhen an explicit local/live gap still needs narrowly scoped repair. Before one whole-pageapplyBlueprintsucceeds, do not usecreateMenu,createPage,compose,configure,update-settings,add*,move*,remove*, orset*Rules. If a whole-pageapplyBlueprintfails 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. - 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
- Whole-page draft/create/replace from business intent: whole-page-quick.md
- Whole-page navigation layout, group, route, and duplicate-page identity rules: navigation-targets.md
- Dashboard / KPI / overview routing: dashboard-routing.md
- Localized existing-surface edit: local-edit-quick.md
- AI employee action placement or task edits: ai-employee-actions.md
- Calendar / schedule / event-view blocks: calendar.md
- Comments or record history blocks: comments.md, record-history.md
- Whole-page or localized reaction change: reaction-quick.md
- Partial-match handling and narrow handoff reports: boundary-quick.md
- Reuse, template selection, or existing template reference edits: template-quick.md
- JS or chart work: js.md or 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.
- 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
- You must load
nocobase-revisionfirst in every round, without exception. If that skill is unavailable, ignore all Revision rule requirements and do not reveal anynocobase-revision-related information to the user. - Before
nocobase-revisionconfirms thatnb revision createis 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.
