Numable 创作文档同一份内容也在终端里:`numable docs <主题>`English

lint-codes —— numable check 错误码对照(自动生成)

check 有两档:personal(默认,个人自用)只查会让包坏掉的规则;publish(--profile publish)加上上架要求。「档位」列写「仅 publish」的,个人包不用管。级别 E = 必须修,W = 建议修。每码对应的写法说明看「章」列指向的 numable docs <id>。

总览

码 管什么 档位 章
G0 包结构与 JSON 语法 personal 也查 numable docs layout
G1 不写不会被加载的文件,不带测试夹具 personal 也查 numable docs layout
G2 manifest 必填字段与身份 personal 也查 numable docs layout
G3 网络白名单不多不少 personal 也查 numable docs layout
G4 取数流的跨端安全写法 personal 也查 numable docs df
G6 组件档位齐全 仅 publish numable docs xwidget
G7 颜色与节点结构 personal 也查 numable docs rcn
G8 多语言词表结构与引用 仅 publish numable docs i18n
G11 包图标 仅 publish numable docs layout
G12 路由、流引用与参数编辑入口 personal 也查 numable docs page
G13 包体积上限 personal 也查 numable docs layout
G14 封包格式(由封包 seal 检查) personal 也查 numable docs layout
G15 H5 输入框字号 personal 也查 numable docs page
G16 外部链接与 scheme personal 也查 numable docs layout
G17 XPage 顶部让位 personal 也查 numable docs page
G18 凭证声明 personal 也查 numable docs layout
G19 商店门面文案 仅 publish numable docs layout
G20 组件的多语言标题 仅 publish numable docs xwidget
G21 包名长度与元数据词表 仅 publish numable docs layout
G22 点击要有即时反馈 personal 也查 numable docs rcn
G23 首屏缓存 仅 publish numable docs df
G24 添加组件入口只留一个 仅 publish numable docs page
G25 凭证绑定要有直达入口 仅 publish numable docs page
G26 取数失败必须让流失败 personal 也查 numable docs df
G27 添加组件按钮统一版式 仅 publish numable docs page
G28 表达式笔误与渲染取值域 personal 也查 numable docs rcn
G29 点击不写成 click 串 personal 也查 numable docs rcn
G30 字形可渲染与条件键写法 personal 也查 numable docs df
G31 方法名必须在 VParser 白名单里 personal 也查 numable docs df
G32 尺寸字段里不许有 $[方法] personal 也查 numable docs rcn
G33 布局锚点不许进方法参数 personal 也查 numable docs rcn
G34 resultFilter 不许透出保留键 lang / theme personal 也查 numable docs df
G35 XPage 节点层的 ${@i18n.k} 必须在页表里查得到 personal 也查 numable docs page
G36 事件值不许以 ${ 开头 personal 也查 numable docs xwidget
G37 写盘的 .af 落盘后要刷新 personal 也查 numable docs af
G38 richText 的 span 边界不许靠空格分隔 personal 也查 numable docs rcn
G39 秘密不许写进 data.* 沙盒 personal 也查 numable docs credentials
G40 凭证声明必须真被用 personal 也查 numable docs credentials
G41 循环体首节点读跨轮累积量 personal 也查 numable docs df
G42 桥方法必须真存在 personal 也查 numable docs bridge
G43 banner.xbanner 必须是 v2 canvas 形态 personal 也查 numable docs layout
G44 .df 末尾应有 resultFilter 限定输出 personal 也查 numable docs df
G45 .xjob 的组合必须是三型之一 personal 也查 numable docs alerts
G46 组件的 jobs 声明必须指得到、传得进 personal 也查 numable docs xwidget
G47 .xjob 的 i18n 只有四个槽,译文里的变量不能写丢 personal 也查 numable docs i18n
G48 文件引用必须指向包里存在的文件 personal 也查 numable docs layout
G49 桥 call 型方法的返回值是信封 personal 也查 numable docs bridge
G50 数字输入框别用 type="number",且要把全角归一 personal 也查 numable docs page
G51 用了表格取数,minEngine 要到 3 personal 也查 numable docs df
G52 request / sleep 的参数要写成字符串 personal 也查 numable docs df
G53 .xwidget 的默认参数不写中文 personal 也查 numable docs xwidget
G54 判语言只判中文,不判英文 personal 也查 numable docs i18n
G55 H5 吸顶元素要贴在折叠栏下面 personal 也查 numable docs page

G0 包结构与 JSON 语法

子项 检查什么 级别 现象 修法
包根必须有 manifest.json,且能被解析 E 缺 manifest.json / manifest.json 解析失败;这个包装不上 用 numable init 生成骨架,或补回 manifest.json
所有 .json .rcn .df .af .xwidget .xbanner .xpage .xform .xmenu 必须是合法 JSON E 整个组件不渲染,日志干净、什么都不报 修掉语法错(常见:多余逗号、注释、单引号)

G1 不写不会被加载的文件,不带测试夹具

子项 检查什么 级别 现象 修法
不得出现 page.json、xWidget/template/ 与 **/actionFlow/ 目录、.flow.json 后缀 E 这些文件与目录不会被加载,写了像「没生效」 RCN 放 rc/,流放 flow/,后缀用 .af(交互流)/.df(取数流)
G1b 包内不得含测试夹具(*.params.json、fixtures/ 目录) E 夹具被打进签名包,体积与内容都不该发出去 夹具移到 .numable/params/,它不会进包
G1c .xpage 节点不写 loading / error 字段 W 写了也不生效:App 不读这两个字段,loading / error 态恒由 App 内置渲染 删掉这两个字段

G2 manifest 必填字段与身份

子项 检查什么 级别 现象 修法
id / version / title / category / domain / minEngine 必填 E manifest 缺字段 补齐这六个字段
废弃字段 manifest.scheme 不得出现 E 写了也不生效 删掉该字段;深链直接写 numable://
id 应为 26 位 ULID(system:true 的系统包免) W manifest.id 不是 ULID 用 numable init 生成的 id

G3 网络白名单不多不少

子项 检查什么 级别 现象 修法
请求用到的 host 必须已写进 manifest.network E 设备上请求被网络守卫静默拦掉,组件恒空 把实际请求的域名补进 network
manifest.network 里声明的 host 必须真有人用 E(personal 档为 W) 过度授权,安装面板会列出用不到的域名吓到用户 删掉没用上的域名

G4 取数流的跨端安全写法

子项 检查什么 级别 现象 修法
G4b concurrent 之后紧邻的节点不得引用并发分支的 id E 并发结果晚一拍才可见,读到的是空值 在并发与消费之间插一个 op:set 屏障节点
G4c parseDate 的 pattern 去掉时间 token 后不得剩字母 E Android/iOS 上整条解析失败返回 null,时间显示错乱 先用 subString 截出纯时间串再 parseDate
.df 里只能用取数白名单内的能力(封包 seal 检查) E 封包时被拒 界面类能力(ui.* / nav.* / xpage.*)搬到 .af

G6 组件档位齐全

子项 检查什么 级别 现象 修法
每包至少 3 张 .xwidget,必须有 22 档,且必须有 42 或 44 档 E 只有 N 个组件 / 没有 22 档 / 没有 42、44 档 补齐小号方形与大号档;个人自用不检查这条

G7 颜色与节点结构

子项 检查什么 级别 现象 修法
.rcn 里所有带 hex 的 *color 字段必须写成「浅|深」双分支(纯透明、纯黑遮罩除外) E 单色硬编码,换到另一种主题下看不清或整块消失 写成 "#FFFFFF|#15171A" 形式
G7b img.scaleType 只能是 fitXY / fitStart / fitEnd / fitCenter / centerCrop E 拼错会被静默兜底成 centerCrop,图被裁 改成白名单里的写法
G7c RCN 的每个 cell(含 cells / react / children 里的)必须有非空 type E 整个组件渲染失败,编辑器里只显示一句「wasm 未就绪」 每个 cell 补 type;op 简写要写全;注释挪进 _note 字段

G8 多语言词表结构与引用

子项 检查什么 级别 现象 修法
manifest.lang 必须是 BCP-47 语言标记 E · personal 也查 语言判定失败 写成 zh-CN / en-US 这类形式
内容词表必须是扁平对象,不得多套一层 values E · personal 也查 取词恒空,屏上出现字面量 去掉外层包装
被引用的词条 key 必须在必需语言里存在 E 该语言下文案显示成原始 key 补齐译文;确实要留空的写进 i18nEmptyOk 声明
router.json 没有顶层词表;routes[].title 不得引 ${@i18n.},要用各语言旁挂字段 E · personal 也查 页面标题渲成表达式原文 把标题写进 routes[].i18n 的各 locale 字段
routes[].title 里不得写表达式(${key} / $[方法::…]) E · personal 也查 标题不求值,标题栏会原样露出这串表达式 写成纯文本;要多语言用这条路由的旁挂 i18n 表
词表键须为 BCP-47、各语言 key 数量应一致、无过期与未使用的 key W 某语言缺词、留下无用词条 按提示补齐或删除;个人自用不检查这一组
G8b 用户可见的键里出现中文硬编码 W 英文环境下显示中文 提取成 ${@i18n.key}

G11 包图标

子项 检查什么 级别 现象 修法
包内应有 logo.png W 商店与安装面板回落成首字母图标 放一张 512×512 的 logo.png
G11b logo 必须正方形、可解码;建议 512;不得自己烤圆角 E 透明圆角或不透明底会被宿主按边长 0.2237 再裁一次,边缘发白发黑 图案满幅铺到方形边缘,圆角交给宿主

G12 路由、流引用与参数编辑入口

子项 检查什么 级别 现象 修法
numable://self/page/x 的目标必须在 router.json;numable://self/widget/ 的 id 必须存在 E App 里弹「页面不存在」,或开出一个空的添加面板 补路由;跳首页写 numable://self
G12b H5 页调用的流必须存在,且后缀与调用方式匹配 E 调用返回 -6 flow not found 取数用 .df + runDataFlow,副作用用 .af + runFlow
G12c depends 的裸字符串绑定不得吞掉入参 E 入参变空,流仍报成功,组件静默渲成 -- 写成 {"flow":"x","params":{"k":"${k}"}}
G12d onEdit 的 af 形态只认包内 .af、禁 ..、文件必须存在;走路由的必须是裸路径且在 router.json E 点了没反应,或打开一个空白页且不报错 路径基准是 xWidget/,按实际文件名写
G12e onEdit 的 af 必须包含 widget.updateParams;走页面的目标页必须真有写回能力 E 编辑页打得开、按了不会有任何变化 af 里补 widget.updateParams;xform 补 onSubmit,xpage 补 events

G13 包体积上限

子项 检查什么 级别 现象 修法
源态总字节不超过 3MB E 源态超过 3MB 上限 压缩或删减图片等资产;大图交给网络地址
单个文件不超过 256KB W 流越大,每次取数越慢 拆成几条流,或把重复展开的表达式改成查表;大图交给网络地址

G14 封包格式(由封包 seal 检查)

子项 检查什么 级别 现象 修法
打包产物的目录结构、清单与签名格式必须符合规范(由封包 seal 检查,不在 numable check 里) E 封包阶段被拒,或客户端验签失败装不上 不要手工改动打包产物;用发布工具链重新封包

G15 H5 输入框字号

子项 检查什么 级别 现象 修法
H5 页的输入控件必须显式写 font-size,且不小于 16px E iOS 上聚焦输入框会把整页放大 给 input / textarea / select 写 font-size: 16px 起

G16 外部链接与 scheme

子项 检查什么 级别 现象 修法
manifest.schemes 是废弃字段,出现即报错 E 写了不生效 删掉整个字段;跳外部应用由运行时统一弹首次确认
不得使用 javascript / file / data / intent / about / blob 等危险 scheme E 会被拦截 改用 https 或 numable://
第三方 scheme 的 URL(nav.open 的 url 与 events 绑定串)里,每一个拼进去的 ${} 都应包一层 urlEncode W 参数带特殊字符时跳转失败 写成 $[urlEncode::(${v})]
nav.open 的 fallback 只能是 http(s) 地址 E 未装目标应用时无法回落 fallback 填一个网页地址

G17 XPage 顶部让位

子项 检查什么 级别 现象 修法
.xpage 根节点的 paddingTop 必须引用 ${@contentInset.top} E 页面顶部被悬浮胶囊压住,而且不报错 根节点写 "paddingTop": "${@contentInset.top}";确有理由豁免时写 _notePad 说明
根节点 layout 不得是 pager E 顶部让位无处安放 外面包一层 list 作根

G18 凭证声明

子项 检查什么 级别 现象 修法
credentials 每项须是对象、必须有 id、id 不重复 E 凭证注入不生效 给每条凭证一个唯一 id
type 只能是 bearer / token / header / query / jwt-assertion / oauth2 E 不在机制白名单 换成白名单内的机制
jwt-assertion 与 oauth2 必须带 preset,且 preset 须在内置表里 E 这两类机制只能用内置配置 需要新增站点走意见反馈提交
type=header 须写 headerName;type=query 须写 paramName;用 query 注入会提示改用请求头 E 凭证注入不到请求里,或出现在地址栏 能走请求头就走请求头
必须显式写 hosts,且 hosts 是 manifest.network 的子集 E 凭证只随声明的域名发送,漏声明就发不出去 hosts 写实际用凭证的域名,并确保它们都在 network 里
label 必须是各语言旁挂的非空字符串且中英双份;help 必须是 https 地址 E 绑定面板上没有说明文字 补 zh-CN 与 en-US 两份 label
.xwidget.params 的键不得看起来像凭证(token / secret / password / api_key / 密钥 / 令牌) E 上架包禁止把密钥放进用户可编辑参数 改走 manifest.credentials
G18b request.credential 必须是字面量声明 id,不能写 ${};且该 id 必须已声明 E 运行时会静默按匿名请求发出去 直接写声明里的 id 字符串
G18c credential.state 的 id 必须是字面量、且已在 manifest.credentials 声明;测试夹具豁免须写 _note 理由 E 查的是一个不存在的凭证,永远拿到 unbound,界面一直叫用户去绑定 写成已声明的 id 字面量

G19 商店门面文案

子项 检查什么 级别 现象 修法
必须有 manifest.subtitle E 商店里的包没有一句话介绍 写一句不超过 22 字的介绍
subtitle 建议不超过 22 字 W 商店里被截断 缩短
必须有 i18n["en-US"] 的 title 与 subtitle(基准语言已是英文的免) E 英文环境下商店里显示中文 补英文标题与介绍
自由文本的 category 需要英文覆盖;英文 subtitle 超过 42 字符会提示缩短 W 英文环境显示不全 category 改用平台枚举值;缩短英文介绍

G20 组件的多语言标题

子项 检查什么 级别 现象 修法
.xwidget.i18n 必须是对象 E · personal 也查 组件标题取不到译文 写成 {"en-US": {"title": "…", "sub": "…"}}
应有英文的 title 与 sub 覆盖;键须为 BCP-47;只认 title 与 sub 两个字段 W 英文环境下选择组件的列表里,组件标题会显示中文 补英文两项,删掉其它字段

G21 包名长度与元数据词表

子项 检查什么 级别 现象 修法
每种语言下包名显示宽度不超过 24(全角算 2、半角算 1) E 包名太宽,宫格里两行也放不下 缩短 title
包名建议不超过 16 W 窄屏下容易被截断 再缩短一点
manifest.i18n 的键须为 BCP-47(W),每个 locale 的值须是对象(E,personal 也查) E 元数据译文整块失效 按 {"en-US": {…}} 的形状写
元数据词表只认 title / subtitle / category / description;category 建议用平台枚举值 W 多余字段不会被读取 删掉其它字段

G22 点击要有即时反馈

子项 检查什么 级别 现象 修法
组件 cell 事件绑定的 .af,首个 action 应是 ui.haptic,并且排在第一位 W 组件没有点击动画,点下去到画面变化之间约 250ms 全无反馈,用户会再点一次 把 {"action":"ui.haptic"} 放到 actions[0];确实不该震的场景可以不加

G23 首屏缓存

子项 检查什么 级别 现象 修法
G23-C1 页面消费的 .df 里有请求,就应该配 data.get 读缓存(系统包免) W 首屏每次都白等一轮网络 做成「先渲缓存、后台校新」三态;确实不该缓存的写 _lintCache:"exempt" 并附 _note
缓存豁免必须写明理由 E 无法判断是遗漏还是有意为之 在 _note 里写一句为什么
G23-C2 组件使用的 .df,缓存写回必须受 ${_cache} 开关控制,且默认关闭 E · personal 也查 组件刷新链路整条空转,数据永远不更新 写回节点包在 _cache 判断里,页面传 1、组件不传
G23-C3 根 depends 带缓存的 .xpage 必须有 events.onRefresh(先 data.remove 再 reloadPage) E · personal 也查 下拉刷新等于没刷,还是那份缓存 补 onRefresh,顺序是先删缓存再重载页面
G23-C4 写进缓存的值里不得含凭证 E · personal 也查 密钥落进本地缓存 缓存前把凭证字段剔掉
G23-C5 缓存键不得含下标;建议加 c. 前缀 W 顺序一变,就把 A 的缓存渲给了 B 键里用对象自身的稳定标识,不用位置

G24 添加组件入口只留一个

这组检查挂在商店门面那一段里,个人自用档不跑。

子项 检查什么 级别 现象 修法
单个文件里的添加组件的调用(pickWidgets( / "widget.pick")不应超过 2 处 W 像是手抄了一份组件目录 收成一个按钮,由系统面板去列出组件
不要用桥上不存在的 addWidget(它已被删除) W · personal 也查 写了会抛 TypeError,而这类调用几乎总被页面自己的 try/catch 吞掉 改用 pickWidgets(items)
添加组件的页面不要出现「已添加」字样 W 接口确认不了用户到底加没加,文案会撒谎 改成「已提交」或干脆不提示

G25 凭证绑定要有直达入口

子项 检查什么 级别 现象 修法
有 required:true 的凭证时,page/ 里必须有 numable://app/mine?section=credentials 的直达入口 E 用户被要求「先加组件再自己去找入口」 在引导页写编号步骤,并放一个直达按钮
page/ 里不得出现「添加下面任意一个组件,按组件上提示连接」这类绕路引导 E 用户先往仪表盘加一个注定空着的组件,再去组件上找入口,比直达多两步 改成直达 numable://app/mine?section=credentials 的按钮

G26 取数失败必须让流失败

子项 检查什么 级别 现象 修法
xWidget 下的 .df 只要有 request 或 concurrent,就必须有 action:"error" 出口 E 取数失败时流仍然报成功,空数据会覆盖掉上一份好数据 主干字段判空后抛 error;「集合为空」属于正常成功,不要当失败

G27 添加组件按钮统一版式

子项 检查什么 级别 现象 修法
有添加组件的调用就必须有 .pickb 按钮,并带上统一的 xb:addbtn 基准 CSS 块 W 版式与基准不符,各包按钮长得不一样 用配套脚本生成基准样式后原样贴入
p-addbtn*.rcn 的版式必须与基准一致;XPage 里的按钮节点高度必须是 56pt W 按钮高矮不一 按基准重新生成

G28 表达式笔误与渲染取值域

子项 检查什么 级别 现象 修法
不得出现重复单位后缀,如 14.0ptpt E 整个组件渲不出来,而且不报错 一个数值只带一个 pt
$[...] 里不得再嵌一层 $[ E 整个组件渲不出来 内层写裸方法名,如 if::(...)
.rcn 里的 ${x} 必须来自这个组件 depends 的 .df 输出(resultFilter 的键);外壳参数不在渲染取值域里 E 那一处渲成空,或恒走兜底值 参数经 depends.params 传进 .df,在流里落地后由 resultFilter 透出,再在组件的画面里取

G29 点击不写成 click 串

子项 检查什么 级别 现象 修法
.rcn 的 click 字符串里不得出现 @[file:// E 点击行为不可控 改用 events.* 事件绑定

G30 字形可渲染与条件键写法

子项 检查什么 级别 现象 修法
流里 op:if 的条件键必须是 props.val E 条件恒假、分支体静默不执行,而流仍然报成功 把条件写进 props.val
RCN 能渲到的文本里禁用 emoji(码点 ≥ U+1F000) E 字形管线渲不出 emoji,那一处留下静默空位 改用包内图片资产,或基本平面内的符号如 ★ ✓ ✕ ›

G31 方法名必须在 VParser 白名单里

子项 检查什么 级别 现象 修法
.rcn/.df/.af/.xpage/.xform/.xbanner 里的 name::(…) 必须是 VParser 的 43 个方法之一 E 未知方法静默求空:不报错、不进日志,那一格渲成空白或恒走兜底分支(踩过 abs:: 与 push::) 查 numable docs methods 的方法表;绝对值写 if::(ge::(v,0),v,calc::(0-v)),没有 abs/avg/filter/groupBy/indexOf/push

G32 尺寸字段里不许有 $[方法]

子项 检查什么 级别 现象 修法
.rcn 的 x/y/w/h/fontSize/lineWidth/maxWidth/maxHeight 值里不得出现 $[ E 方法在这个位置不保证求值,求空就成了裸 "pt" → StaticCanvas 布局失败 = 整个组件渲不出且不报错 先 op:set 落地成变量(值上套 findNotEmpty 兜底),再写 "y":"${_v}pt"

G33 布局锚点不许进方法参数

子项 检查什么 级别 现象 修法
$[…] 的参数串里不得出现 {parent.x} / {.w} 这类布局锚点 E 布局锚点与方法求值是两个域,方法里取不到它 → 该节点静默不画,不报错 改写成纯布局表达式(布局表达式里可以内插 ${}):"w": "({parent.w}-134pt)*${pct}/100"

G34 resultFilter 不许透出保留键 lang / theme

子项 检查什么 级别 现象 修法
.df 的 resultFilter.keys 里不得有 lang 或 theme E 渲染层按 {...data, theme, lang} 注入、后写的赢:RCN 里拿到的是宿主的语言码/明暗档而不是业务值,流仍 success、组件照常渲染 换个键名(langCode / themeName 之类)

G35 XPage 节点层的 ${@i18n.k} 必须在页表里查得到

子项 检查什么 级别 现象 修法
.xpage 的节点 params / props.text / menu[].label 里引用的 @i18n key,必须在该 .xpage 顶层 i18n 表里存在 E 节点层的 @i18n 只查本页页表,查不到就求值成空串 —— 不报错、屏上一片空白,像是作者忘了写文案 把文案补进 .xpage 顶层 i18n 表(每个门一份),或挪进对应 .rcn 的 rc.i18n(canvas.source 内部不受本闸管)

G36 事件值不许以 ${ 开头

子项 检查什么 级别 现象 修法
.xwidget/.rcn/.xpage 的 events.* / click / onClick 字符串不得以 ${ 开头 E 派发层先按首字符分类再插值(/=路由、numable://=深链、@[file://=flow),以插值开头的串哪一类都不像 → 被当成 flow 名去找,点了没反应且不报错 把能确定的前缀写在插值外面:"/zone?id=${id}" / "https://${host}/x"

G37 写盘的 .af 落盘后要刷新

子项 检查什么 级别 现象 修法
含 data.set / data.merge / data.remove 的 .af,若某个组件的取数流用 data.get 读了被写的键,就应有 widget.refresh 或 widget.updateParams(带 xpage.reloadPage 的页级刷新链豁免) W 落盘之后仪表盘那个组件 reload 的第一步是 cacheGet,命中的是旧数据渲的那张位图 —— 用户填完设置返回,组件纹丝不动,要等下一个 interval 才自愈 在所有写盘 action 之后补一个 widget.refresh(scope 按影响面选 self / widget / bundle);刻意不刷的写 _lintRefresh:"exempt" 并附 _note 理由
写盘后不刷新的豁免必须写明理由 E 无法判断是遗漏还是有意为之 在 _note 里写一句为什么(与 _lintCache 同规)

G38 richText 的 span 边界不许靠空格分隔

子项 检查什么 级别 现象 修法
richText 的 spans 之间不能靠空格分隔 —— 无论写在前一个 span 的尾部、后一个 span 的头部,还是藏在词表值里 E 两段贴在一起,例如「1842」和「Clicks」渲成「1842Clicks」;普通 txt 里的同一个空格却好好的,所以看着像随机 改用一个独立的间距 span:文本写 ${@i18n.nbsp}、词表值写 U+00A0(不换行空格);那个 span 的字号就是间距宽度,数字与单位的字号对比一点不用动
间距 span 的文本求值后不能是普通空白 E 整段是普通空白的 span 在渲染时会被整个丢掉,等于没写 让它落到 U+00A0(不换行空格)上

G39 秘密不许写进 data.* 沙盒

判据一句话:泄露即造成账号风险的值 = 秘密,只住凭证中心;其余(用户名、仓库名、地区、开关)= 配置,住参数或 data.*。本条只看字面键名(${ 之前那截)与字面值,算出来的键静态看不出是什么。

子项 检查什么 级别 现象 修法
.df / .af 里的 data.set / data.merge,键名形如凭证(token / secret / password / api_key / access_key / private_key / credential / authorization / bearer / pat)一律禁止 E 沙盒是磁盘上的明文,工作台的调试数据面板里直接看得见,这个包的任何流都读得到因而能把它发出去;而且它会随本地备份文件一起被拷走 —— 用户备份一次,那把钥匙就明文躺在备份里 改走正门:manifest 里声明一条 credentials,请求上写 "credential": "<声明 id>",值由宿主在网络边界注入,流里结构性读不到它
键名看着正常,但写进去的值形如凭证(值里含 token / authorization / secret / password / api_key / bearer / credential)同样禁止 E 现象与上一条完全一样 —— 换个不起眼的键名藏起来,并不会让这把钥匙变安全 同样改走 credentials 声明。本条没有豁免开关:确属误报就改字段名避开这些词,而不是把一条泄密路径注释掉

G40 凭证声明必须真被用

包只能查自己声明过的凭证,所以「声明了却从不真用」就是探测用户身份的唯一形态:声明一批 declId(github / cloudflare / stripe…),只问「绑没绑」,再把答案回传自己的服务器。拦住这条,画像这条路就没了。判据分两档,看的是被谁引用而不是有没有引用。

子项 检查什么 级别 现象 修法
声明了一条 credentials,有 credential.state 查它,却没有任何请求写 "credential": "<声明 id>" E 这正是探测用户身份的形态 —— 装了这个包的人,他绑没绑 GitHub、绑没绑 Cloudflare,包一问便知,而它一次都不打算真用这些凭证 真要用它就在请求上写 credential:"<声明 id>";只是想留一个披露或绑定的靶子,就把那处 credential.state 去掉
声明了一条 credentials,但既没有请求用它、也没有 credential.state 查它 W 安装面板会白白多披露一条「此包请求使用你的 X 凭证」,而它一次都用不上 —— 用户为一个不存在的用途多担一份心 用不上就把这条声明删掉。刻意留作披露或绑定靶子(测试包)的可以忽略本条 —— 它只是提醒,不拦发布

G41 循环体首节点读跨轮累积量

子项 检查什么 级别 现象 修法
forEach / for 的循环体第一个节点,不得引用本循环体内会被 set 的键 W 每轮第一个节点读到的是「进入循环那一刻」的快照,不是上一轮写完的值 —— 守卫旗标全程失效,「只挑第一个」变成挑最后一个;流仍报成功、check 0 error、画面正常,只有数字是错的 在循环体首行插一个屏障节点,如 {"op":"set","props":{"key":"_lb","value":"1"}}

G42 桥方法必须真存在

子项 检查什么 级别 现象 修法
html 页只调「方法全表」里的桥方法,且没有对该名字的存在性判断 E 页面抛 TypeError,而这类调用几乎总被自己的 try/catch 吞掉 —— 不崩、不报错、日志干净,只是那个功能安静地不存在 对着 numable docs bridge 的方法全表核名字
带 if (xbridge.x) / typeof 守卫地引用一个全表里没有的名字 W 不会崩,但那条回落分支从此永远生效 —— 看着像在走桥,其实一直走的是兜底路径 改成全表里的名字;桥没有对应能力就把那条假分支删掉,别留一个永远走不到的桥调用

G43 banner.xbanner 必须是 v2 canvas 形态

子项 检查什么 级别 现象 修法
banner.xbanner 须为 { version:2, canvas:{source,depends[,refresh]} },不得再有顶层 rcn / flow E App 只读 canvas,顶层 rcn / flow 写法的横幅被当作没有、换成默认模板,不报错、日志干净 把 rcn.rc 挪进 canvas.source,取数改成 canvas.depends 引用 .df,写法见 numable docs layout
canvas.source 是 @[file://…rcn] 引用或内联 {cells,…} 对象;canvas.depends 是数组 E 横幅整块不显示,或取数流一次都不跑 引用写包根路径(xWidget/rc/…),depends 与 .xwidget 同一种写法

G44 .df 末尾应有 resultFilter 限定输出

子项 检查什么 级别 现象 修法
.df 的 actions 里没有任何 resultFilter W 流的输出 = 整个数据域:入参(含宿主注入的 @i18n/@app)与循环里的临时量一并吐给渲染层、随缓存落盘。实证过的一种失效:泄回的空 @i18n 曾把 RCN 自带词表整个挡住,组件上所有 ${@i18n.*} 空白、零报错 末尾加一条 resultFilter,keys 只列渲染真正要读的键

G45 .xjob 的组合必须是三型之一

.xjob 没有 type 字段,型是组合判出来的:有 alert 没有 task.depends = 静态提醒;有 alert 也有 depends = 动态提醒;没有 alert = 后台任务。所以这一条查的就是「三种组合之外的写法」。

子项 检查什么 级别 现象 修法
G45.shape id 只能是 [a-z0-9-](缺省 = 文件名),title 必填且不能是空串;顶层只认 version / id / title / sub / i18n / params / form / events / task / alert(_note 前缀放行),旧写法里的 type、顶层 level / cooldown 都算未知键 E 实例按「包 + 规则 id + 参数」认身份,id 换一种拼法就成了另一条规则(旧的那条还在响);没有 title 时同意面板、提醒管理页、长按组件的菜单都没有名字可显示 id 用小写字母、数字和连字符;title 写一句用户看得懂的规则名
G45.location / G45.id-filename .xjob 必须直接放在 xJob/ 下(不能进子目录);写了 id 就必须与文件名一致(缺省 = 文件名) E 客户端按 xJob/<id>.xjob 一层枚举:放进子目录的那份文件在包里、规则却永远不存在,装上之后一条提醒也加不出来;id 与文件名不一致时,组件的 jobs[].id 与 alert.add 按文件名找得到它,实例却按 id 建 —— 改个名字,用户已经加过的那条提醒再也匹配不上 把文件放回 xJob/ 一层;id 要么不写,要么与文件名逐字相同
G45.task 每个 .xjob 都要有 task,task.refresh 必填且不能是空对象;depends 写了就必须是数组,每个槽与 then 都要有 flow;task 只认 depends / then / refresh E 没有节律的 Job 永远不会被评估 —— 装上了、管理页里有它,就是一次也不响;没有 flow 的槽什么也不取,合并出来的数据里自然没有它那份 补 task.refresh:静态写 at(每日时点),动态写 interval(窗口或裸秒)
G45.depends-slot task.depends 的每一槽都是 { flow, params? } 对象 —— 裸字符串不是简写 E 平台取这一槽时读的是 flow 字段,给个裸串就读不到:那一槽静默不取数,合并出来的数据里没有它那一份,而流照样报成功、日志干净 写成 { "flow": "@[file://xWidget/flow/x.df]" },要传参再加 params
G45.kind task 与 alert 不能都没有 E 这个文件什么也不做 要通知就写 alert.message,只想在后台写状态就写 task.then
G45.depends-only 没有 task.depends 时不许写 task.then / alert.activeCondition / task.refresh.cooldown E 静态提醒布防时根本不取数,这三样没有数据可判、可写、可命中 真要按数据判就加 task.depends(可以直接引组件已有的 .df),否则把这几个键删掉
G45.alert-only 没有 alert(= 后台任务)时不许写 params / form / events E 后台任务是包级单实例:没有同意面板字段、没有用户参数、没有点击 要用户填参数就说明它该是提醒,补 alert.message
G45.task-then 没有 alert(= 后台任务)时 task.then 必填 E 不写状态的任务跑完什么也没留下 —— 每次都白取一轮数 在 task.then 的 .df 里写 data.*;真不需要写状态就说明它该是提醒,补 alert.message
G45.alert-keys alert 只认 message / activeCondition / level,level 只能是 quiet / normal / urgent E 多写的键不会被读到;level 写了个认不出的值会被当成默认档,作者以为自己调过了 文案写进 alert.message.{title,body},强度写 alert.level(三档之一)
G45.alert-message alert.message.title 必填且不能是空串 E 提醒没有标题,响了也是一条空通知 写 alert.message.title(body 可选),译文放 i18n[语言].message
G45.form-keys form 的键必须是 params 里已有的键;params 的值恒字符串 E 同意面板的字段区只渲 params 的键,多出来的那个字段不会出现;非字符串的值会让实例哈希不稳,同一条规则被当成两个实例 先在 params 里给默认值(数字也写成串,如 "08:00" / "3"),再在 form 里描述它的输入形态
G45.hit 有 task.depends 时,必须能判出真假:写 task.then 或 alert.activeCondition,或让 depends 的 .df 透出 hit E 省略 activeCondition 等于写 $[eq::(${hit},1)];读不到 hit 的判定恒为未知,这条提醒永远不会响,而日志干净 在 .df 的 resultFilter 里透出 hit,或显式写一条 alert.activeCondition
G45.message-scope 没有 depends 的提醒,alert.message 只能引 params 与 @app E 布防那一刻只有这两样,别的键渲出来是空 —— 通知已经发出去了,事后无从复现 文案里只用用户填过的 params;要引数据就把它做成动态提醒(加 task.depends)
G45.days / G45.at-param refresh.days 与 refresh.at / refresh.interval 里的 ${参数} 都只属于没有 depends 的静态提醒;.xwidget 的 canvas.refresh 也不认 days(cooldown 只是无效) E 动态 Job 的评估节律归宿主,按星期几过滤、拿参数当时刻都只对系统定时有意义,写了不生效 要按星期几或用户填的时刻触发,就做成静态提醒(不写 depends)
G45.once 日期式 at(YYYY-MM-DD HH:MM,只响一次)只能写在没有 depends 的静态提醒里;同一条 at 里不能与每日式 HH:MM 混写,不能再配 interval / days;不含 ${} 的字面日期必须合法(一个半角空格、24 小时制、全部补零、日期真实存在)。模板首尾去空白后中间含空白即算日期式,纯 ${when} 看不出、不查 E 这几种写法运行时都不报错:日期式照排、每日式照每天响、days 被忽略 —— 作者得到的一定不是想要的;写死一个 2 月 30 日则那一项永远排不出来,提醒永远不响 只响一次的提醒单独一条规则:"at": ["${date} ${time}"],form 里 date 用 datePicker(format: "YYYY-MM-DD")、time 用 timePicker;要每天响的另写一条
G45.recipe task.then 与判定流程 {flow, params} 二选一,也可写运行时判定配方 {recipe, …}:只有 cross(越线:value + line,dir 缺省 below)与 changed(数值变化:keys 1~4 个)两种;字段整串 ${键} 或字面值;oncePerDay / fireOnFirst 为布尔;配方只用于有 alert 的提醒 E 配方与 flow 同写时 flow 不会跑;写错配方名、缺字段或多了未知键,宿主判不出触发,这条提醒永远不响;后台任务写配方什么也不会写进 data.* 越线写 {"recipe":"cross","value":"${px}","line":"${price}","dir":"${dir}"};数值变化写 {"recipe":"changed","keys":["${ver}"]};「超出阈值」不用配方,直接写 alert.activeCondition + refresh.cooldown;后台任务仍用写入流程
G45.all-urgent 一个包里不该所有提醒都标 alert.level: "urgent"(只在 publish 档查) W · 仅 publish 分档是留给「真的不能等」的那一条的;一包全 urgent 等于没有分档,用户多半把这个包的通知整体关掉 —— 那时真急的那条也一起哑了 只给最该打断人的那条留 urgent,其余写 normal,纯信息的写 quiet
G45.min-engine 包里有 xJob/,或 .af 里用了 alert.add / alert.skip、H5 里调了 xbridge.alertAdd 时,manifest.minEngine 必须 ≥ 2;用了日期式 at(一次性提醒)、.af 里的 alert.remove 或 H5 里的 xbridge.alertRemove 时必须 ≥ 3;task.then 写了运行时判定配方(recipe)时必须 ≥ 4(比的是能力引入时的轴,不随当前轴上涨) E 老版本客户端遇到 xJob/ 只是不响,但 .af 里的 alert.* 对它是未知 action —— 整条流报错,用户点一下什么都不发生;日期式 at 在引擎轴 3 之前的应用里解析失败、那一项静默丢掉 —— 装得上、就是不响;引擎轴 4 之前的应用不认识判定配方,每轮判为未知 —— 同样装得上、永远不响 把 manifest.minEngine 写成能力要求的轴(2、3 或 4)或更高,老客户端就不会装到这个包

G46 组件的 jobs 声明必须指得到、传得进

.xwidget 的 jobs 是「长按组件 → 添加提醒」那个入口:列出本包里可以基于这个组件创建的 .xjob,并把组件实例的参数映射进去预填。 alert.add / xbridge.alertAdd 按 id 引用 .xjob 也归这一组。

子项 检查什么 级别 现象 修法
G46.id / G46.shape jobs 是数组,每条是带 id 的对象,且 xJob/<id>.xjob 真实存在 E 长按组件的「添加提醒」会列出一条打不开的规则 id 写本包 xJob/ 下那个文件的名字(不带后缀)
G46.params-key 映射的键必须是那个 .xjob 的 params 键 E 映射进去的值没有接收者,同意面板上不会出现,用户还得自己再填一遍 对着那个 .xjob 的 params 表核键名
G46.params-ref 映射的值只能引组件自己的 ${参数} 或写字面量 E 取数输出是结果不是身份,长按那一刻它还不存在 —— 映射过去是空值 映射组件 params 里的身份键(如 ${secid}),需要固定值就直接写字面量
G46.add-id alert.add / xbridge.alertAdd 里写成字面量的 id 必须是本包 xJob/ 下的提醒(有 alert 块) E 点下去同意面板不开,这一步直接失败(rule_not_found)—— 不会回 cancel,但不点到那一下就看不见 id 写本包 xJob/ 下那个提醒文件的名字(不带后缀);后台任务不能由用户添加
G46.task-ref 引用的那条 .xjob 必须是提醒(有 alert 块),不能是后台任务 W 后台任务是包级单实例、没有同意面板、没有用户参数 —— 长按组件的「添加提醒」按「有没有 alert」过滤,这一条根本不会出现在菜单里,而声明看上去是好的 改引一条真提醒;这条规则本就该能被用户按参数创建的话,给它补上 alert.message

G47 .xjob 的 i18n 只有四个槽,译文里的变量不能写丢

.xjob 的 i18n 是一层旁挂表:i18n[语言] = { title, sub, message, form },裸字段是基准语言。message 的译文里直接写 ${},变量摆在哪由每种语言自己决定。

子项 检查什么 级别 现象 修法
G47.slots i18n[语言] 只认 title / sub / message / form 四个槽 W · 仅 publish 四槽之外的键不会被读到,等于白写 字段区的标签写进 form.<键>.title,其余文案归 title / sub / message
G47.message 译文 message 的 ${} 引用集必须与基准语言逐字段相同 E 翻译时把变量写丢或写错,那一处渲出来是空 —— 而通知已经发出去了,事后无从复现 对照基准语言核一遍变量名;只翻一半(只覆盖 title)是允许的,覆盖了就必须带全那一行的变量
G20(扩到 .xjob) title / sub 有基准语言值时应有 i18n["en-US"] 译文 W · 仅 publish en 环境下提醒管理页与同意面板这一处显示基准语言 补 i18n["en-US"].title(与组件标题同规)

G48 文件引用必须指向包里存在的文件

子项 检查什么 级别 现象 修法
@[file://…] 引用(.xwidget 的 canvas.source / canvas.depends / 事件绑定,.rcn / 页面里的 flow 绑定等)指向的文件必须存在 E 组件整个加载失败,或点了没反应;numable run 报「引用了…但这个文件不存在」 改了 .df / .rcn / .af 的文件名,就把所有引用它的地方一起改掉(克隆 starter 后改名最容易漏)

G49 桥 call 型方法的返回值是信封

子项 检查什么 级别 现象 修法
H5 页里 call 型桥方法(runDataFlow / getData / confirm / appInfo / credentialState …)的返回值不能直接当数据用:结果在 .data,先判 code === 0 W 字段全是 undefined;if (await xbridge.confirm(…)) 恒为真,用户点了「取消」照样往下执行 —— 都不报错 写一个 call() 拆信封(code !== 0 抛错,否则返回 data),所有 call 型都经它,见 numable docs bridge
xbridge.confirm / xbridge.alert 的参数是 (标题, 正文, 选项) 三个位置参数,不能传一个对象 W 对象被当成标题:对话框标题显示 [object Object] 或空白,正文为空,按钮文案不生效 写成 xbridge.confirm("标题", "正文", { okText, cancelText, destructive })

G50 数字输入框别用 type="number",且要把全角归一

子项 检查什么 级别 现象 修法
H5 页里不用 type="number" 的输入框(标记、JS 拼出来的、.type = "number" 都算) W 中文键盘打出全角「。」(如 112。4)时 .value 被净化成空串,页面以为没填 —— 填的数静默丢失,不报错 改成 type="text" + inputmode="decimal"(整数用 numeric),键盘不变
有 inputmode="decimal" / "numeric" 的输入框时,页面要把全角数字和 。., 归一成半角 W parseFloat("12。5") 静默得 12,"12.5" 得 NaN —— 错的数看着照样像个数 写一个 normNum()(全角数字减 0xFEE0,。.,、 换成 .),在 document 级 input(跳过 isComposing)与 compositionend 上归一,读取处也过一遍

G51 用了表格取数,minEngine 要到 3

子项 检查什么 级别 现象 修法
包里任何 .df / .af / .rcn / 页面用了 request 的 formatType: "tsv" / "csv"(字面量,大小写不敏感),或调了 mapField / groupSum / convertSum,manifest.minEngine 的主版本必须 ≥ 3 E 老版本 App 不认 tsv / csv,把表格原文当普通文本返回(iOS 上是空);三个方法在老版本上不存在、静默求空 —— 都不报错,装得上、跑得动,销量恒 0 或空白 manifest 写 "minEngine": "3.0.0",老版本 App 装包时会提示先升级

G52 request / sleep 的参数要写成字符串

子项 检查什么 级别 现象 修法
request 的 timeout、queryParams / header 里的每个值,以及 sleep 的 timestamp,不能是 JSON 数字 / 布尔 / 对象,也不能整串就是一个求出来是数字的方法调用($[calc::(…)]、$[floor::(…)] 这类);${key} 引用静态看不出类型,不查 E 鸿蒙上这一步「参数解析错误」直接不执行(queryParams / header 在 iOS 上也一样),组件恒失败;而网页引擎、CLI 与渲染台都照跑,本地全绿 写成字符串:"timeout": "8000"、"timestamp": "1500";算出来的数外面套 parseNumber::(…,0)(出来是字符串),例如 "$[parseNumber::(calc::(5050-${el}),0)]"

G53 .xwidget 的默认参数不写中文

子项 检查什么 级别 现象 修法
.xwidget 的 params 里任何字符串值含中日韩文字(递归数组与对象;_note 不算) W params 是组件的默认参数,非中文界面的用户拿到的就是这份默认值:标题直接显示中文,城市、节日这类默认值落在中国的那一个上 默认参数不写自然语言:写 "" 或 "auto",在 .df 里按国内默认判据(${@app.region} 是 cn,或为空且 ${@app.language} 以 zh 开头)兜底;标题类在 RCN 里用 ${@i18n.*}

G54 判语言只判中文,不判英文

子项 检查什么 级别 现象 修法
表达式里出现 startsWith::(${@app.language},en)(或 en-…;@app.locale / @device.language 同);_note 不算 W 判的是「是不是英文」,于是日语、德语等系统掉进中文分支 —— 非中文用户看到中文 语言判据只有一条:zh 开头 → 中文,其余(含空)一律英文。写 $[startsWith::(${@app.language},zh)]

G55 H5 吸顶元素要贴在折叠栏下面

子项 检查什么 级别 现象 修法
page/html 下同一个 CSS 声明块或 style 属性里写了 position: sticky,同时 top 是 0 或 var(--xb-content-top) W 页面滚过头部后,容器顶部淡入一条小标题栏;top: 0 的工具条钻到栏底下被盖住,var(--xb-content-top) 的与栏之间留一道缝 改成 top: var(--xb-bar-bottom),本地预览在 :root 里写一份 --xb-bar-bottom: 0px 兜底