soma-kit
v0.1.11
Published
SOMA 第三条 UI 路线:写死模板与规则,DSL 显式传入,基础组件 + 数据组件
Readme
soma-kit
SOMA 的第三条 UI 路线(私有源码包,版本 0.1.0):模板与规则写死在 kit 内部,DSL 只显式传入数据结构、对象的样子与行为;
样式统一采用 Tailwind 类,不再书写原生 CSS。依赖方向保持单向:soma-design → soma-foundation → soma-core → soma-kit,不依赖 soma-ui、soma-lite。
组件只有两层:基础组件(处理单个值)与数据组件(处理单个对象或对象列表)。页面框、业务组件属于写死的模板外壳,不计入组件层。
核心设计原则
- 规整归对象引擎,异形归写死定制:
- 标准通用的表格、筛选、表单、详情、普通卡片流交给对象渲染机制统一生成。只读态默认一律渲染为纯文本
Text,保持克制干净,视觉重心由使用者显式传入的组件与样式决定; - 异形结构、特殊边框、复杂非标布局绝不强行扭曲通用对象语法,一律通过写死结构样式或写死专属声明翻译来实现。
- 标准通用的表格、筛选、表单、详情、普通卡片流交给对象渲染机制统一生成。只读态默认一律渲染为纯文本
- 标准业务组件只保通用,特例靠应用包装与组合:
- 内置标准业务组件(
StandardCrud、MasterSubWorkstation等)只负责跨行业通用的对象管理与协作流程,不掺杂特例配置参数; - 当通用 DSL 无法满足项目的特殊边框、特殊头尾或局部语法简写时,应用直接通过包装原语或组合原语快速沉淀自己的专属 DSL 翻译逻辑。
- 内置标准业务组件(
当前架构层级
- 第 0 层:包骨架与语法真源:
spec/kit-grammar.json(语法闭集、类型默认表、宽度拆分规则、语义色名表、诊断码)及其模式定义;src/theme/kit-theme.css是 Tailwind v4@theme主题头,把语义色名映射到--soma-*令牌;具体令牌取值由 foundation 主题流水线在装配期求解。 - 第 1 层:44 个基础组件(位于
src/base/,经createKitBaseComponents()取出组件字典交 core 注册;KIT_BASE_CLASS_CANDIDATES汇总全部类串供样式编译):- 核心通用基础件(18 个):
Text、Tag、Input、Textarea、Number、Select、Radio、Checkbox、Switch、Date、DateRange、Button、Link、TagInput、Upload、TimePicker、TreeSelect、Alert; - 交互与排版扩展(12 个):
Range、Rating、Otp、Avatar、AvatarGroup、Kbd、KitProgress(shape: ring | bar)、Countdown、Swap、Fab、Image、MenuButton;其中通用tooltip属性支持Button / Link / Switch / Swap / Kbd / Fab / MenuButton; - 高级选择与录入(8 个):
Cascader、Combobox、ColorPicker、Mentions、Popover(confirm: true即气泡确认框)、PriceText、QrCode、CommandPalette;并入既有件参数的形态:Radio variant: segmented、Checkbox variant: buttons、Number variant: stepper、Tag variant: dot(pulse)、MenuButton trigger: contextmenu; - 选项组竖排与纯图标(M 波 F1 / A12,页面
/ref/f1):Radio / Checkbox的orientation: horizontal | vertical(segmented / buttons 形态同样生效,竖排 segmented 用 ↑/↓ 移动);options 项键icon(短字符)与aria_label,icon且label为空即纯图标选项,可访问名取aria_label(缺省取value);Navbar下拉子项键header: true(只读标题行,页面/ref/f1)。 - 选择与反馈类参数(M 波 G1b,既有件的闭集参数,页面
/ref/g1b):Select的multiple / display: chips | text | count / group / clearable(NN 波 G1:Select 与 Combobox 合并为同一份自绘实现base/listbox-core.ts,Select 是不可输入的 select-only combobox,没有原生<select>,表单值在 hidden input,键盘含 typeahead,页面/ref/nn/select);Combobox的multiple / limit_tags / group与选项键desc / icon / disabled;Range的range({start, end}双滑块)/ marks / value_label / orientation: vertical(NN 波 G3:恒为自绘 SVG 滑块,没有原生 range,表单值在 hidden input,RTL 下反向,页面/ref/nn/range);命令式调用方的下拉统一用mountKitSelectField、整块重建的容器用createKitEmbedBag释放嵌入件(soma-kit/data);Upload的list: picture(照片墙)/ manual / upload_text(抛upload);Date / DateRange的disabled_dates(抛reject)与DateRange.presets(NN 波 G2:日历为自绘浮层,不用原生<input type=date>;新增week_start、Date.placeholder / clearable,页面/ref/nn/date);KitProgress的indeterminate / buffer / shape: dots;TimePicker的seconds / hour12;Fab的direction / back_top; - 输入与标记类参数(M 波 G1a,既有件的闭集参数):
Input的clearable / prefix / suffix / counter / password_toggle / variant: filled(suffix可传{ action, countdown }做发送验证码倒数,抛suffix_click);Textarea的auto_size / counter;Tag的removable / clickable / icon / variant: outline | solid;Button的variant: tonal / icon_only;MenuButton的split;角标badge / badge_max / badge_dot(Button / Link / MenuButton / Tag);Link的underline / external;Text的copyable;tooltip接到Tag / Avatar; - 动效展示件(5 个,
src/base/motion/):TextRotate、Hover3d、HoverGallery、Aura、Marquee,全部严格遵循motion-safe:触发,系统开启减少动效时保持静止; - 独立排版件(1 个):
KitDivider(支持横线、竖线、带文字与各色语气)。
- 核心通用基础件(18 个):
- 第 2 层:数据组件:
- 对象渲染机制(
src/object-renderer/):将KitObject转换为中间表示,经双发射器输出静态四件套片段(静态 HTML、_ui.yaml、_event.yaml)与候选类,或生成表单属性;支持单一对象排布(cols、matrix、rows、inline)与集合排布(table、cards、stack、treeTable); - 内置写死形状数据组件(45 个,登记表位于
src/data/static-registry.ts): 涵盖树与结构(Tree、Steps、Timeline、Gantt)、流程与审批(ApprovalFlow、Checklist、FlowEditor(内置节点配置面板:nodeTypes.<类型>.fields里写kind的字段出现在选中节点的右侧面板,改动抛change与node_change)、RuleBuilder)、指标与分析(StatGroup、Chart、TrendLine、AmountBreakdown)、协作与交互(CommentThread、RecordCard、Transfer、PermissionMatrix、FieldMapper、PermissionTree、AssignmentPicker、Kanban、RowEditor、AsyncResultPanel、RequestConsole、Questionnaire、TourGuide)、导航与外壳(Menu、Navbar、Breadcrumbs、Watermark、AnchorNav、Hero、Carousel、Dock、Diff、Mockup、Footer、PricingTable、ItemList、CodeView、Calendar、ImageViewer、MediaGrid、EmojiReactionBar、RichTextEditor、Pager、RecordPicker);对标补齐(M 波 G4):Tree加checkable / propagation / lazy / filter,RowEditor加number / money / date列、footer: sum、min_rows / max_rows,Chart加combo、ref_lines、legend、value_format,ItemList加条目group / leading / trailing,RecordCard加variant / clickable / media,RichTextEditor加source: markdown(只读),新增RecordPicker弹窗选记录(抛pick { value, values, item, items }供ref.fill回填); - 可写静态数据组件自动接线:当写死形状数据组件契约同时包含
emits.submit与props.object,且所在数据对象声明了behavior.write时,编译器自动注入object(若契约包含scope同时注入scope),并将<锚点>.submit默认接至ACTION | data.submit(显式书写view.on.submit时以显式为准); - 数据组件包装与组合原语:
wrapKitDataComponent与composeKitDataComponents,支持单组件样式固化、多组件拼装、属性转换与子事件转发; - 协作数据组件:持有草稿与局部校验的
KitObjectForm,以及承接表格行动作分流的KitTableActions。
- 对象渲染机制(
- 第 3 层:业务组件、模板外壳与编译器:
- 内置业务组件(15 个,位于
src/biz/;M 波 G5 新增ResultPage结果页 / 异常页、ImportWizard导入向导,配协作组件KitImportHub;StandardCrud浮层可声明editOverlay(只读浮层点「编辑」切到编辑浮层),RecordDetail的record.subsLayout: tabs把关联子对象收进分段切换页签):StandardCrud(单实体增删改查)、MasterSubWorkstation(主从协同工作台)、CategoryCrud(分类条目联动)、MultiObjectBoard(多对象组合看板)、SingleObjectForm(独立单对象表单)、ApprovalDesk(三对象审批台)、RecordDetail(单记录详情台)、StepForm(分步向导表单)、NotificationCenter(通知中心)、AuthPanel(账户权限面板)、SettingsCenter(设置中心)、CheckoutPage(结算页)、LandingPage(落地页); - 页面模板(
KIT_PAGE_TEMPLATES,自带product.config,不在内置闭集,应用经defineKitApp({ bizComponents })取用):SignInPage登录 / 注册 / 找回 / 二次验证页,包装SingleObjectForm,config取layout(center居中卡片 /split左品牌右表单)、brand、links、social;链接与第三方按钮只执行具名动作,不处理也不存储任何凭据;新增数据组件ResultPanel(success / error / info / warning / 403 / 404 / 500 / empty); - 无界面协作数据组件:
KitCrudBridge(焦点切片与增删改查协调)、KitLinkHub(选择切片与主子联动)、KitStepHub(步骤切片与向导数据汇总); - 专属配置通道
product.config与编译门禁:内置标准业务组件默认acceptConfig为假,页面向其传入product.config会被编译器直接拦截并报错KIT-BIZ-003;包装组件或自定义业务组件开启acceptConfig: true后方可合法承接专属配置; - 业务组件包装与组合原语:
wrapKitBizComponent(单业务组件包装、输入翻译与装饰外壳)与composeKitBizComponents(多业务组件前缀隔离、无冲突组合与异形外壳); - 页面框(
src/frames/):内置single-frame、blank-frame、top-main-frame、tabs-frame、split-frame、workspace-frame,页面框均支持page.breadcrumbs面包屑;带页头的框另支持page.actions / back / status / max_width / sticky(动作区窄屏收进下拉、返回对应信封nav.back);workspace-frame另有theme_panel(观看者主题抽屉)、fullscreen、user.avatar / accounts / guest、sidebar_footer与菜单divider,以及share_hash(标签与地址栏 hash 同步)、alerts(顶部告警条)、systems+navbar.end[].panel: systems(系统切换面板,数据组件SystemSwitcher); - 五步编译器(
src/compile/)与应用装配面(src/app/):严格进行契约校验、对象关系展开、对象渲染生成、样式合并与四件套产物构建。
- 内置业务组件(15 个,位于
质量体系与测试状态
soma-kit 拥有严密的质量保障网络,全层级代码、编译产物与运行时行为均已通过自动化验证:
- 单元测试与集成测试:全量测试套件通过(125 个测试文件、1,571 条测试用例全部通过,零失败);
- 端到端页面回归:包含 30 页真实业务示例(位于
../soma-kit-demo/pages/*.kit.yaml),在真实浏览器与模拟运行时环境下逐页全编译、无诊断、零冲突,页面 URL 唯一,产物四件套完全符合 core 标准规范。
四大包装与组合原语(soma-kit/app 公开导出)
为彻底解决“业务个性化需求侵蚀通用框架”的难题,soma-kit/app 对外公开导出四大包装与组合原语,均在编译期执行确定性展开:
1. wrapKitDataComponent(inner, options)
包装单个内置或已有数据组件。复用底层组件的模板与控制器,通过纯函数 adapt 将外部业务数据或简写参数翻译为底层属性,适合固化边框样式、限定业务字段或写死映射逻辑:
import { wrapKitDataComponent } from 'soma-kit/app';
import { createRecordCard } from 'soma-kit/data';
export const AppVipOrderCard = wrapKitDataComponent(createRecordCard(), {
name: 'AppVipOrderCard',
props: { highlight: { type: 'boolean', default: false } },
adapt(props) {
const data = (props.data ?? {}) as Record<string, unknown>;
return {
data: {
title: String(data.orderNo ?? ''),
status: data.vip ? '重点单' : '常规单',
tone: data.vip ? 'primary' : 'neutral',
},
class: props.highlight ? 'border-2 border-primary shadow-md' : 'border border-border',
};
},
});2. composeKitDataComponents(options)
将多个数据组件(或基础组件)拼装在同一个自定义网格或异形布局壳中,对外暴露为一个标准的写死形状数据组件(layer: 'data')。自动合成服务依赖,深度转发属性变更与子事件:
import { composeKitDataComponents } from 'soma-kit/app';
import { createStatGroup, createTimeline } from 'soma-kit/data';
export const AppOrderCockpit = composeKitDataComponents({
name: 'AppOrderCockpit',
emits: { metric_pick: { payload: { item: { type: 'object' } } } },
rootClass: '@container grid grid-cols-12 gap-4 rounded-xl border border-border p-4',
parts: {
metrics: {
component: createStatGroup,
slotClass: 'col-span-12 @md:col-span-5',
adaptProps: (p) => ({ data: p.data?.summary ?? {}, selectable: true }),
forwardEvents: { select: 'metric_pick' },
},
events: {
component: createTimeline,
slotClass: 'col-span-12 @md:col-span-7',
adaptProps: (p) => ({ data: p.data?.logs ?? [] }),
},
},
});3. wrapKitBizComponent(inner, options)
包装单个已有业务组件(如 StandardCrud),开启 product.config 配置通道。支持前置输入翻译 adaptInput 与后置外层外壳包裹 wrapHtml,保证标准组件零污染:
import { wrapKitBizComponent } from 'soma-kit/app';
import { StandardCrud } from 'soma-kit/biz';
export const AppBorderedCrud = wrapKitBizComponent(StandardCrud, {
kind: 'AppBorderedCrud',
extraAreas: ['shell'],
candidates: ['rounded-xl border-2 border-primary p-6 shadow-md'],
adaptInput(input, _ctx, diagnostics) {
if (input.config?.border !== 'glow') {
diagnostics.push({ code: 'KIT-BIZ-003', path: 'product.config.border', message: 'border 仅支持 glow' });
}
return input;
},
wrapHtml(innerHtml, { areaClass }) {
return `<div data-kit-wrapped="glow" class="${areaClass('shell', 'rounded-xl border-2 border-primary p-6 shadow-md')}">${innerHtml}</div>`;
},
});4. composeKitBizComponents(options)
将多个业务组件组合为复合业务产品。编译器为每个子部件分配独立的前缀 ${ctx.pageId}_${partKey},彻底隔离内部数据对象名、锚点与区域切片,杜绝 KIT-PAGE-002 冲突;各子部件生成的片段经 mergeFragments 统一合并后,由 renderShell 填入自定义外壳:
import { composeKitBizComponents } from 'soma-kit/app';
import { MultiObjectBoard, StandardCrud } from 'soma-kit/biz';
export const AppCommandDesk = composeKitBizComponents({
kind: 'AppCommandDesk',
areas: ['headerArea', 'bodyArea'],
parts: {
kpi: { component: MultiObjectBoard, roleMap: { panels: 'kpiPanels' } },
crud: { component: StandardCrud, roleMap: { entity: 'entity' }, forwardOverlays: true },
},
renderShell(partHtml, { rootClass, areaClass }) {
return `<div class="${rootClass}">`
+ `<header class="${areaClass('headerArea', 'p-4 bg-surface-sunken')}">${partHtml.kpi}</header>`
+ `<main class="${areaClass('bodyArea', 'p-6 bg-surface')}">${partHtml.crud}</main>`
+ `</div>`;
},
});工程目录规范与三要素(HTML · DSL 翻译 · Tailwind)存放准则
总则:有自己 DSL 的件一律一件一目录,目录里放自己的 DSL 翻译(shell / compile / index)与对核心对象引擎的引用(emitObject / fragmentFromEmit 等);业务组件(含包装件、组合件、领域包 desk)和页面框(翻译 frame.config)属此类。基础组件和数据组件只吃 props、没有编译期 DSL 翻译,一件一个 .ts,不按行数拆;以后某个数据组件长出编译期翻译,就升级为目录。
无论是 soma-kit 内部还是外部业务应用,HTML 结构、DSL 翻译、Tailwind 样式一律存放在职责分离的纯 TypeScript 模块中(全工程除唯一的主题头 kit-theme.css 外不建零散 .html / .css 文件,保证服务端与浏览器同构打包零磁盘文件读取隐患,且可脱离浏览器直接对纯函数跑毫秒级单测):
| 要素 | 存放位置与物理形态 | 核心纪律 |
|---|---|---|
| HTML 结构壳 | 业务组件一件一目录,放 shell.ts(纯函数拼装 HTML 串);包装/组合业务组件在目录内 index.ts 的 wrapHtml / renderShell;基础/数据组件一件一个 .ts,templateHtml 与控制器同在该文件 | 锚点 data-soma-id 以 pageId 为前缀、双下划线 __ 分段、零点号;浮层外层只挂 hidden 属性 |
| DSL 翻译逻辑 | 定制业务组件放 compile.ts;包装业务组件放 adaptInput(翻译 product.config,有翻译时可单放目录内 compile.ts);包装/组合数据组件的 adapt / adaptProps 与数据规整函数写在同一个 .ts 内 | 必须是无副作用纯函数;非法专属配置推入 KIT-BIZ-003 诊断码拦截 |
| Tailwind 样式 | 业务组件放 shell.ts 顶部的 *_CLASS 基准类与 *_AREAS 区域闭集(包装件放 candidates 选项,编译期自动并入当前页);基础/数据组件放自己 .ts 顶部的 *_CLASS,紧跟导出 *_CANDIDATES(不拆 classes.ts) | 外部覆写一律走 mergeClass(基准类, 覆写类);基础/数据组件控制器内的类串由应用 components/index.ts 汇总交入 defineKitApp({ appCandidates }) |
与 soma-core 完全对齐的外部应用目录与契约规范
应用工程根目录完全沿用 soma-core Host 标准结构(soma.app.ts / soma.server.ts / config/ / locales/ / pages/ / components/ / domain/ / api/service/);自定义组件直接采用 soma-core 原生 SomaComponentDefinition(App* 前缀、layer: basic | data、snake_case 事件名、data-soma-id 作用域锚点、services 注入白名单):
my-app/
├── soma.app.ts # 唯一装配入口(浏览器可进):defineKitApp 挂载组件、packs、actions、appCandidates
├── soma.server.ts # 可选服务端实现(经惰性 import 交入):身份鉴权、请求钩子、虚拟页
├── kit.app.yaml # 应用声明底座:theme 主题、baseComponents / staticObjectComponents 名单、types 覆盖
├── config/ # 可选:运行期共享配置(如 app.config.yaml)
├── locales/ # 可选:多语言字典(如 zh-CN.yaml)
├── pages/ # 页面声明层(与 soma-core / soma-lite 同名):存放 *.kit.yaml
├── domain/ # 前端领域纯逻辑与具名动作(如 domain/actions.ts,经 defineKitActions 登记)
├── components/ # 应用自定义与包装组件目录(与全仓 Host 约定同名;少于 4 个可平铺,多则按层分子目录)
│ ├── index.ts # 统一导出扩展组件,并汇总 base/ 与 data/(含被包内置件)的 *_CANDIDATES 供 appCandidates 使用
│ ├── base/ # 应用基础组件(App*,SomaComponentDefinition layer: basic)
│ ├── data/ # 应用数据组件(App*,SomaComponentDefinition layer: data;一件一个 App*.ts,包装/组合/自绘同此)
│ ├── biz/ # 应用业务组件外壳(App*,KitBizDefinition;一件一目录 <kebab>/{shell,compile,index}.ts,含包装件、组合件)
│ └── frames/ # 可选:应用自定义页面框(App*Frame)
└── api/
└── service/ # 入站业务服务(URL 段对应目录,_shared/ 存放内部私有共享代码)若需跨多个应用共享一套扩展组件,按
pack-starter与soma-kit/packs/*同构方式用defineKitPack封装为独立包,通过defineKitApp({ packs: [...] })接入。
编辑器补全与诊断定位
*.kit.yaml 是 YAML DSL,没有语言服务;用 JSON Schema 补全键与枚举、悬停看说明。schema 由语法真源 spec/kit-grammar.json 生成:
spec/generated/kit-page.schema.json:页面(page/frame/product、对象schema/behavior/view、字段引用键、collection键、动作键、各基础组件props;枚举取自真源闭集,description取自真源 note)。spec/generated/kit-app.schema.json:kit.app.yaml(键闭集对应src/app/app-config.ts)。- 重新生成:
npm run schema:build -w soma-kit;新鲜度门:npm run verify:fresh -w soma-kit(真源改了而生成物没更新即退出码 1)。
VS Code 用法:装 Red Hat 的 YAML 插件,在工作区或用户 settings.json 里映射(路径按你的位置改):
{
"yaml.schemas": {
"./soma-kit/spec/generated/kit-page.schema.json": "*.kit.yaml",
"./soma-kit/spec/generated/kit-app.schema.json": "kit.app.yaml"
}
}不想改设置时,在文件首行写相对路径的modeline,示例见 ../soma-kit-demo/:
# yaml-language-server: $schema=../../../../spec/generated/kit-page.schema.jsonschema 只做补全与提示,对象内部的字段级语义仍归编译器校验(未列全的位置放宽,不会误报)。
编译诊断(KitDiagnostic)除 code / path 外,应用装配编译页面时还带 file / line / column(1 起);KitAppAssemblyError 的消息按 文件:行:列 [码] 路径:说明 输出。行列由 src/compile/locate.ts 按路径扫描块式 YAML 得到(流式 { } 内部落到所在项的行);定位不到时只有路径。
公开子路径(package.json exports)
| 子路径 | 内容说明 |
|---|---|
| soma-kit | 包根入口:语法常量与顶层公共类型重导出 |
| soma-kit/app | 浏览器可进入的应用装配入口:defineKitApp、defineKitActions、defineKitPack、defineKitBizComponent;包装与组合原语 wrapKitBizComponent、composeKitBizComponents、wrapKitDataComponent、composeKitDataComponents、mapKeys;片段工具函数 emptyFragment、mergeFragments、fragmentFromEmit;嵌入挂载助手 mountKitComponent;通知端口 createKitNotifyRenderer;权限更新 refreshKitPermissionGates、setKitPermissionChecker、权限并集 addKitPermissionChecker;应用叠加 composeKitApps、mergeKitTables、createKitPermissionMiddleware;服务端分页适配 createPagedListAdapter、unwrapBizEnvelope、isBizFailure、withRowId、cursorPageWindow,以及相关类型导出 |
| soma-kit/app/server | 服务端装配面(通过 defineKitApp(...).server() 惰性载入,应用运行代码无需直接引入) |
| soma-kit/biz | 业务组件框架、13 个内置业务组件(StandardCrud、MasterSubWorkstation 等)、协作数据组件定义与类型 |
| soma-kit/frames | 页面框注册表 KIT_FRAMES 与页面框定义类型 KitFrameDefinition |
| soma-kit/workspace | 工作台视图与多标签状态机核心纯函数:mountKitWorkspace,支持独立嵌入或受控驱动 |
| soma-kit/base | 44 个基础组件的组件字典与样式候选列表(KIT_BASE_CLASS_CANDIDATES) |
| soma-kit/base/shared | 基础组件共享工具(str、bool、num、joinClass、尺寸规范等),支持应用自定义基础组件复用 |
| soma-kit/base/motion | 动效基础组件字典(TextRotate、Hover3d、HoverGallery、Aura、Marquee)与动效候选类 |
| soma-kit/compile | 页面编译器出口(compileKitPage 等),用于构建工具与测试流水线 |
| soma-kit/object-renderer | 对象渲染内核(emitObject、renderObject、mergeClass、createKitTableActions 与对象类型定义) |
| soma-kit/data | 写死形状数据组件工厂函数与静态注册表(KIT_STATIC_BUILTIN_SPECS、createKitStaticRegistry) |
| soma-kit/i18n | 内置文案表与取词(kitText、kitLocaleAt、kitDirAt、kitKey)、$t: 解析(resolveTextRefs、lookupKitText);应用自定义组件用它取页面语言与内置文案 |
| soma-kit/packs/commerce | 电商领域包 commercePack:CommerceAddressCard、CommerceOrderCard、CommerceCouponCard、CommercePurchaseOrderCard |
| soma-kit/packs/finance | 金融领域包 financePack:FinanceReconcileDesk、FinanceInvoiceSummaryCard、FinanceReconcileCaseCard |
| soma-kit/packs/ai | 人工智能平台领域包 aiPack:AiAgentRunCard、AiEvaluationCard、AiModelCard、AiGuardrailPolicyCard、AiModelRoutingCard、AiPromptTemplateCard |
| soma-kit/packs/ops | 运维发布领域包 opsPack:OpsChangeWindowCard、OpsDependencyStatusCard、OpsErrorBudgetCard、OpsIntegrationStatusCard、OpsReleaseReadinessCard、OpsRunbookExecutionCard、OpsExecutionTaskRunner |
| soma-kit/packs/security | 合规安全领域包 securityPack:SecurityAccessReviewCard、SecurityRiskFindingCard、SecurityFindingCard、SecurityVulnerabilityRemediationCard、SecurityControlEvidenceCard |
| soma-kit/theme/kit-theme.css | 唯一的 Tailwind v4 @theme 主题头文件 |
| soma-kit/spec/kit-grammar.json | 语法真源规范定义文件 |
表格密度、斑马纹、选中高亮与字段说明
collection.density: compact | standard | comfortable(缺省为 standard):只微调单元格上下内边距(py),不改动左右边距;--spacing随全局宿主密度自适应,单表密度不与全局倍率叠乘。collection.striped: true:斑马纹显示(默认关闭)。- 选中行高亮恒开:接通选择路径时,
KitTableActions自动给所在行打上data-kit-selected(表格采用无角色 div 架构,不使用 aria-selected;已选状态由首列选择框承担),行样式data-kit-selected:bg-primary-soft生效,样式优先级压过斑马纹与悬停效果。 - 行操作与步骤执行:同一表格的所有行操作合并至单个
KitTableActions实例,内部根据kit_action路由分流;confirm:确认与permission:权限直接标注在对应步骤上,由 core 按统一口径在前置阶段拦截,取消或无权限立即阻断执行。 fields[].help:表单字段辅助说明(置于输入控件下方、错误提示上方,字号颜色为text-xs text-fg-muted),仅在编辑态呈现,只读摘要与详情中不展示。- 诊断码
KIT-OBJ-009:负责拦截非法密度参数、非布尔斑马纹配置以及非法字段辅助文案,也负责 M 波 G2 的表格新键(见下节)。
表格列模型、工具栏、批量条、合计行与就地编辑(M 波 G2)
示例页 /ref/g2(../soma-kit-demo/pages/ref-g2.kit.yaml):一张 14 列订单宽表。
- 字段新键:
fixed: left | right(须连续地排在两端)、align: start | center | end、ellipsis: true(悬停 title 看全文)、min: 16|20|24|28|32|40|48|56|64|80(间距刻度)、editable: true、columnFilter: true | enum | text。写了fixed/min、或toolbar含columns,表格进滚动模式(定宽列、sticky 偏移是刻度累加;滚动后固定区边缘出现分隔线与阴影);与stickyHeader互斥。 - 集合新键:
toolbar: [reload, density, columns, export](列显隐与行高记在localStorage,键含页面路径与对象名)、batchActions(勾选后换成批量条,confirm里{n}换成个数,确认在条内就地问;需要select: multi与勾选态来源如 StandardCrud)、select: { mode, scope: page | all }、summary: { label, fields: { 字段: sum | avg | min | max | count } }(只算当前页)、edit_mode: cell | row(保存抛cell_edit,编成ACTION | data.submit;Enter 保存、Esc 取消,校验沿用字段rules)。 - 表头筛选值写进
view切片,作为请求参数cf_<字段>(枚举多值逗号连接);服务端按参数过滤。 - 运行时件:
KitTableTools(工具栏 / 批量条 / 列显隐 / 就地编辑 / 滚动边缘 / 省略 title)、KitTableSummary(合计行);固定列是编译期属性,运行时不改固定。treeTable 不支持这些新键(报KIT-OBJ-009并忽略)。 - 规格:
prd/强化规划/设计包/soma-kit 方言规格.md的「表格的附加能力」;闭集与限值:spec/kit-grammar.json#collectionKeys.tableExt;单测:tests/object-renderer/table-ext.test.ts。 - 多级表头与列宽拖拽(Q 波 G4):
collection.columnGroups: [{ title, fields }](单层,组内字段在列序里相邻,同一字段至多属一组;写法不对报KIT-TBL-001,混固定 / 非固定列报KIT-TBL-002,该组丢弃);collection.resizable: true(表级,固定列自动跳过)或字段级fields[].resizable(false退出,固定列写true报KIT-TBL-004)。两者都视同进入滚动模式;列宽只取tableExt.scroll.minSteps的 10 个预置档,拖动与键盘(←/→ 一档、Home / End、Enter / 双击复位)共用frames/resize-handle.ts,只换w-N类、不写 CSSOM,宽度不持久化;列设置「恢复默认」同时复位宽度;导出 CSV 表头写「组标题 / 列名」;固定组整体 sticky;可与virtual并用(B1:列类data-kit-col+w-N与表头对齐,列宽状态只在 KitTableTools 一份),treeTable / stickyHeader 报KIT-OBJ-009。示例页/ref/q/g4-groups;单测tests/object-renderer/table-groups.test.ts。 - 展开行子表(Q 波 G5):
collection.expand: { list, link }(与{ view: summary }二选一,并写 / 都不写报KIT-OBJ-005)。目前只在StandardCrud的 entity 列表里可用:子对象写在objects.overlays.<名>(kind: expand,只读、平铺 table / stack、必须有behavior.from),不画浮层。展开行主键经SCOPE | semantic | crud_expand交协调组件写focus.expandId,镜像成子对象from的link参数并编成require,没展开时缺参 core 不取数;一次只展开一行(手风琴)。滚动模式(固定列 /min/ 列设置 /columnGroups/resizable)、非 StandardCrud、子对象不合规一律KIT-TBL-003并忽略 expand;treeTable沿用KIT-OBJ-010,virtual报KIT-TBL-005(展开改变行高,与固定行高窗口冲突,保持拒收;需要行详情请用行操作开详情浮层或分页表格 + expand)。实现object-renderer/collection-expand.ts;示例页/ref/q/g5-expand;单测tests/object-renderer/table-expand-list.test.ts。 - 块级主题继承(Q 波 G5 修订):产品没写的维度一律继承页面。页面
dark/light+ 产品对象没写mode时补页面极性对应的昼夜词条;页面inverse+ 产品对象没写mode时产品块按反相极性求解(作用域类名带-inv后缀)。规则见方言规格 §3.4。
行操作折叠、条件行类、翻页方式与地址参数(M 波 F3)
示例页 /ref/f3、/ref/f3-more、/ref/f3-infinite、/ref/f3-shapes(../soma-kit-demo/pages/ref-f3*.kit.yaml)。
- 行操作:
when(行条件不成立按钮不显示)、more: true(固定进「更多」菜单);行内最多 3 个按钮,超出自动折叠。 view.list.rowClassWhen: [{ when, class }]:按行条件加类(仅 table,至多 4 项,类受 KIT-STYLE-001 约束)。collection.page:mode: pager | more | infinite、show_total、layout: simple | pages | full(pages= 上一页 + 页码序列(首尾页、当前页高亮、省略号折叠、方向键 /aria-current)+ 下一页;full再加页长选择与跳页;Pager组件同名 prop;示例页/ref/last-fn2)。- 必填星号是标签旁的红色独立子元素(
view.form.requiredMark: start | end,不并入标签文本)。 behavior.params(引用$page.params.<名>/$page.query.<名>)、behavior.syncUrl: true;page.params/page.query声明地址参数。- 联动子对象(
MasterSub/CategoryCrud/ApprovalDesk)没选中主行时不发请求(corefrom[].require)。 - 数据组件:
Steps的status: error与optional、Timeline.align、CodeView的stream / follow / max_lines / tree;接入面板(LAST E4):CodeView.variants / variant(页签切换多份文本,事件variant)、RecordCard的 facts 修饰mono / copy / tone(事件copy)与data.secret(一次性明文,复制并清除,事件secret_copied)。 - 规格:「4.9 行操作、翻页方式与地址参数」。
虚拟滚动(N 波 N3)
示例页 /ref/n3(1 万行表格)、/ref/n3-pick(5000 项 Select / Combobox)、/ref/n3-tree(6252 节点 Tree),数据由服务端生成(../soma-kit-demo/api/service/ref/n3/**)。
- 引擎走 core 虚拟滚动端口(
services.engines.virtual,缺省@tanstack/virtual-core由 core 动态加载,kit 不 import)。接线在src/base/virtual-list.ts;引擎不可用(未装配 / 加载失败)时回退为普通渲染,控制台只提示一次。 - 表格:
collection.virtual: { rowHeight: sm | md | lg, height?: sm | md | lg }。行高必须显式给档位名(不接受像素数字),没写rowHeight就不虚拟化;表体换成KitVirtualRows,保留表头吸顶、固定列、选择与批量条、合计行(粘底)、就地编辑、键盘(跨窗口)、aria-rowcount / aria-rowindex。与page.mode: infinite不并用(改按more处理并报KIT-OBJ-009),推荐page: false一次给全量。不支持expand与字段级action。 - 长列表组件:
Select(searchable 等 combobox 形态)、Combobox、Tree用virtual: true+row_height(Tree 另有height);带group分组时不虚拟化;ItemList条目变高,本波不做。 - 密度(K1):虚拟表的
collection.density与工具栏「行高」映射到行高档位,rowHeight是标准密度的档位,紧凑低一档、宽松高一档;切换后清行、换估算行高并重新测量窗口。 - 其它小修(K1):TourGuide 步骤卡贴着高亮目标定位(复用
base/popup贴边翻转,滚动 / 缩放跟随,无目标 / 贴不下落回底部居中);MediaGrid新增columns_by: { md?, xl? }分档列数;Kanban新增wip_mode: hard | soft(soft 抛wip_exceeded、照常移入);竖向Range高度随size(h-32 / h-40 / h-48)。 - 规格:「4.10 虚拟滚动」;闭集与像素估算:
spec/kit-grammar.json#collectionKeys.virtual;单测:tests/base/virtual-list.test.ts、tests/base/virtual-options.test.ts、tests/data/virtual-rows.test.ts、tests/object-renderer/virtual-table.test.ts。
右键菜单与 CSSOM 几何写入门(Q 波 G6)
contextMenu(view.list或collection,仅 table / cards / stack):contextMenu: ["@detail", "@ship", "@remove"],元素是behavior.actions的动作 id,至多 8 项(collectionKeys.contextMenu.maxItems)。鼠标右键 / 触屏长按 /Shift+F10·ContextMenu键打开,Esc 关闭并回焦该行;可编辑输入控件内部保留系统菜单;右键不改勾选。条件显隐when、permission、危险项红字、confirm尾段都沿用行动作。菜单项抛row_action(载荷与KitTableActions同形,带整行),页面_event与行操作共用同一套步骤。写法不对报KIT-MENU-001。- 实现:写死数据组件
KitContextMenu(src/data/context-menu.ts),菜单内容复用menu-core的wireMenu,按落点经浮层端口挂出(portal-core的showAtPoint,翻转 / 夹取复用popup.ts的resolvePopPlace)。MenuButton的trigger: contextmenu复用同一份showAtPoint;浮层端口缺失时降级为就地绝对定位(不写 CSSOM)。 - CSSOM 例外总表:运行期
.style.x =只许出现在spec/kit-grammar.json#cssomWrites登记的函数里(E1virtual-list.ts的setSpacerHeight写height;E2portal-core.ts的placeAtPoint写left/top)。npm run verify:cssom -w soma-kit判定,越权或登记项陈旧都红;已登记在scripts/test-all.mjs的静态门阶段kit-cssom,负样本见tests/spec/cssom-gate.test.ts。新增写入必须先改cssomWrites与《方言规格》§7.7。
声明式请求 / 下载动作与服务端分页适配(最后一轮 E1)
动作写 request 或 download,不必为「打一个接口」写具名动作(真源 kit-grammar.json#requestAction,规格 3.3.1):
actions:
- id: toggle
name: 启停
on: row
confirm: 确认切换?
request: { api: /api/x/toggle, body: { id: $row.id }, ok: { path: success, equals: true, message: message }, notify: 已更新, into: result }
- { id: export, name: 导出, on: toolbar, download: { api: /api/x/export, query: { kw: $filter.name }, filename: x.csv } }- 取值路径:
$row.*/$focus.*(浮层里取被点的行)/$filter.*/$page.page|size/$value/$selected.字段(批量,batchActions: ["@id"]);其余是字面量。 - request 经 core 写通道(合成只写对象 +
data.submit)发 POST / PUT / PATCH,结果落write-outcome-store;into是结果范围名,消费方用soma-kit/data的watchKitOutcome(services.data, 名字, cb)读{ status, message, data, seq }。失败弹提示,成功弹notify并重取(then: none不重取)。 - download 是浏览器原生下载(GET 链接 / POST 表单),不走 fetch。
- 页面里有这类动作时,编译器补页面级
KitActionHost(不需要应用登记任何东西)。AI 不能写这两个键。 - 附加浮层保存:
events.saved: { action: <request 动作 id>, then: saved },请求成功才关窗。 - 服务端薄壳:
soma-kit/app的createPagedListAdapter(页码协议 <-> 旧后台分页与信封)、unwrapBizEnvelope、cursorPageWindow(游标分页)。
编辑工作台:辅助栏 / 子表字段 / 级联 / 字段策略表(最后一轮 E3)
示例页 /ref/last/e3(ref-last-e3.kit.yaml),规格「4.6.1 / 4.6.2」,真源 kit-grammar.json#rowsField / formLinkage.optionsFrom / fieldPolicy:
- MasterSub 辅助栏:product.objects 多一个可选的
aside(单个对象,第三栏 4 / 5 / 3)。写behavior.from按选中行取数;或view.summary.staticComponent: AsyncResultPanel+props.outcome: <request 动作的 into>展示动作结果(不需要 from)。 - 表单子表字段:schema 里
type: text+edit: RowEditor,列与行为写在 form 视图字段项的props(columns / edit_mode: inline / fixed_rows / min_rows / max_rows / footer);值是对象数组,随表单提交,列校验与行数上下限在提交时逐行判(第 N 行:…)。fixed_rows用于「字段策略」这类只勾选不增删的表。 - 级联字段:多级依赖下拉用
options.from的params: { x: { field: 上一级 } }(既有);一条路径用edit: Cascader,options加lazy: <参数名>+levels: <层数>按层懒加载(或行里带children一次给整棵树,leafKey标叶子)。 - 字段策略表:
view.form.policy: { by: 状态字段, table: { 取值: { hide / readonly / required: [字段] } } },编译成showWhen / disabledWhen / requiredWhen,不新开运行时。 AsyncResultPanel.outcome/show_run:订阅请求动作的into结果(进行中 / 成功 / 失败),纯展示时show_prompt: false+show_run: false。- 单测:
tests/object-renderer/field-policy.test.ts、tests/data/rows-field.test.ts、tests/biz/master-sub/master-sub-aside.test.ts。
表单草稿 / 编辑详情 / master 筛选(最后一轮 W1)
$draft(request 取表单草稿):body里写$draft.字段,把编辑框里还没保存的值发给接口(「验证」「试运行」);$draft整体取全部草稿。草稿仍是KitObjectForm自己的,表单挂载时登记读口(soma-kit/data的registerKitFormDraft/readKitFormDraft/activeKitFormDraft);多个可见表单取「最近动过」的,没有可见表单读作{}。具名动作用kitActionExtras().draft(scope?)。真源kit-grammar.json#formDraft,规格 3.3.1。view.form.footerActions: [动作id]:编辑弹窗底栏的额外按钮(触发 request / download 动作,文案取动作name),是$draft的触发点;表单抛footer_action { id }。behavior.detail:编辑弹窗打开时按选中行先取详情,detail: { from: { api, params: { id: $row.id } }, unwrap? }。取数中只显示「处理中…」,成功后详情盖行作表单初值,失败照实报错并给重试、不渲染表单。写法不对KIT-DET-001。真源kit-grammar.json#formDetail,规格 4.6.1a。- master 的
filter视图:MasterSubWorkstation的master、CategoryCrud的category可写view.filter,提交 / 重置写 filter 切片并重取该列表(语义同 StandardCrud)。 - 示范页
/ref/last/w1(ref-last-w1.kit.yaml)。
展示与状态(最后一轮 GC)
示例页 /ref/last/gc,规格「4.6.4」,真源 kit-grammar.json#staticProps.show(数字在 kit-limits#dataLimits),诊断 KIT-SHOW-001 到 KIT-SHOW-005。
- CodeView
variants_from: <路径>:页签来自数据,每个页签单独复制(与variants二选一)。 - RecordCard
outcome+secret_path:一次性明文取自某个 request 动作(into)的成功响应,进行中 / 失败不显示,复制后组件不再持有明文。 - RowEditor
staged: true:集合本地草稿。增删改只在本页累积,常驻「未同步 N 项」条与行小标;「同步」抛submit(走 core 写通道data.submit,成功才清),「放弃变更」自绘确认后恢复服务端数据;不是乐观更新。 - 版本锁:
behavior.write.version: <字段>(core 的write.version)与请求 body 的$loaded.<对象>.<字段路径>;缺值不发请求,冲突如实报「已被他人修改」。 - FlowEditor
adopt: { from, path? }:画布建议来自请求结果,画成虚影,点「采纳」才合入草稿。
表单与字段(最后一轮 GB)
示例页 /ref/last/gb/steps(跨步取值)、/ref/last/gb/form(初值 / 隐藏 / 派生)、/ref/last/gb/flow(画布字段 / 子表远程选项),规格「4.6.5」,真源 kit-grammar.json#formLinkage.fieldValue / flowField(数字在 kit-limits#formLinkage.fieldValue),诊断 KIT-FORM-010 到 KIT-FORM-012、KIT-STEP-005。schema.<字段> 新增 default / hidden / derive 三个键(human-only,AI 写面不可写)。
default(字段初值):字面量,或$filter.<字段>/$row.<字段>/$steps.<步骤名>.<字段>。只在新建时填「空且用户没改过」的字段;编辑态不生效,取不到值不填。hidden: true(隐藏草稿字段):随草稿提交、表单不画。不是安全边界、不能绕过权限或校验:必须是可提交字段(w/create,r报错),create字段编辑态仍不提交,字典rules照常校验(不过时提示条写明字段名与原因),不被采纳建议回填;服务端必须自行校验。edit: FlowEditor(画布作表单字段):值{ nodes, edges, mappings? }随表单提交;必填时至少一个节点;FlowEditor 新增value / name / id / disabled四个 prop,change载荷多一个value。- RowEditor 列的远程选项:
columns[].options: { from, valueKey?, labelKey?, rowsPath?, params? }(kind: select);取数中 / 失败都如实显示,不假装有选项。 derive: { template }(派生展示字段):permission: r,闭集模板{字段名|过滤器},过滤器json / upper / lower / trim / count / or:兜底;没有表达式引擎(无运算、比较、函数调用、嵌套)。只在 form 视图显示,不进提交、不参与校验。- StepForm
$steps:default、options.params({ steps: '步骤名.字段' })、derive.template的{steps.步骤名.字段}、步骤request的body,都能取更早步骤已确认的值(闸门早已支持)。
动作与结果(最后一轮 GA)
示例页 /ref/last/ga1、/ref/last/ga2,规格「4.6.6」,真源 kit-grammar.json#formLinkage.requestExt(数字 kit-limits#formLinkage.requestExt,超时区间 core write.timeoutMs),诊断 KIT-ACT-003…007 / KIT-RES-001。全部 human-only,AI 不可写。
- 组件事件接动作(GA1):
view.on: { run: probe },probe是behavior.actions里的 request / download 动作 id;组件事件的载荷作$event取值根($event.method、$event.params)。 - 动作按状态禁用 / 隐藏(GA2):
actions[].disabledWhen / hiddenWhen,谓词叶子是outcome.<into>.status|message|data.路径或行 / 草稿字段(工具栏动作读outcome.*与页面读态total/selectedCount/selected.<字段>)。request.into可写{ name, key: $row.<主键> }让结果按行分槽,行内动作默认读本行那一份;RecordCard secret 复制后经services.data.clearOutcome清掉 core 存储里的明文。行操作、右键菜单、工具栏按钮、表单底栏动作都认;随写入结果自动重判。 - 请求结果常驻(GA3):
view.form.result: <into>(表单 / 弹窗内常驻,进行中 / 失败 / 成功都留在原地);非 form 视图写result编成只展示结果的AsyncResultPanel区域。 - 请求前输入(GA4):
request.prompt: { title?, fields: [{ name, type?, default?, required?, options? }] },先弹自绘输入框,输入值作$prompt.<name>;取消不发请求。 - 新建预填(GA5):
builtin: openCreate的动作写prefill: { 字段: $filter.x | 字面量 },同页可有多个预设不同的「新建」钮(StandardCrud;MasterSub / CategoryCrud 同样支持,H1a,联动型预填只许字面量)。 - 请求超时(GA6):
request.timeoutMs编进合成写对象的 corewrite.timeoutMs(1000..600000 毫秒,core 经 http 请求配置timeout下发)。
动作与刷新补充(H1)
示例页 /ref/last/h1a、/ref/last/h1,规格「4.6.6」H1 段,真源 kit-grammar.json#requestAction(防抖毫秒 kit-limits#formLinkage.requestExt.changeRequestDebounceMs),诊断 KIT-ACT-007…009。全部 human-only,AI 不可写。
- H1a 联动组件预设新建:MasterSub 主对象 / CategoryCrud 明细对象的工具栏可并列多个
builtin: openCreate+prefill: { type: http }的钮;只许字面量。 - H1c 后续链数组:
request.then: [{ reload: true | 数据对象名 | /接口路径, on?: success | failed | always }];on: failed即「失败也执行」(失败后刷新列表);写成数组后缺省刷新不再发生。 - H1b 跨页签重取:
reload: <数据对象名>(StandardCrud 主对象 =<页签 id>_entity)点名任意页签里的对象;reload: /api/x/options让页面里所有options.from等于它的字段重取候选(含其它页签的表单)。点名不存在的对象 / 路径编译报KIT-ACT-009。 - H1d 挂载自动发只读请求:
request: { read: true, on: mount },页面挂载时宿主自动发一次(进页就跑前置检查,下载钮的disabledWhen进页即正确);写类请求不得自动发(没声明read: true、非 POST、带prompt/confirm/permission/ 取$row$draft等挂载时没有的值,均报KIT-ACT-008)。read: true的请求then缺省为none。 - H1e 字段变化触发请求:
view.form.onChange: { table: loadMeta },用户改了该字段后防抖发loadMeta(须read: true),body 用$event.value/$draft.x;在途时只留最新一次,被顶替那次的结果丢弃(网络请求本身不取消,core 写通道没有中断接口)。
采纳建议回填草稿 / 向导阶段闸门(最后一轮 W2)
示例页 /ref/last/w2(采纳)与 /ref/last/w2/gate(闸门),规格「4.6.3 / 5.3.1」,真源 kit-grammar.json#formLinkage.adopt / formLinkage.stepGate,诊断 KIT-FORM-009 / KIT-STEP-004。
view.form.adopt: { from, path?, map?, label?, ask?, askLabel? }:某个 request 动作(into: 名字)返回一组建议值,用户在表单顶部的采纳条上点「采纳」才把它们写进当前草稿。只改草稿,不提交,保存仍由用户确认。被改的字段标签旁出现「已采纳」(只表示「当前值就是被采纳的建议值」,再改、重置、保存成功即消失)。from= request 的into;path= 建议对象在响应体里的点路径(接口带信封时写data.xxx);map={ 表单字段: 建议路径 },缺省按同名键回填;ask= 采纳条上「获取建议」钮要发的 request 动作 id(本对象behavior.actions里不写on,其into必须等于from,body 可取$draft.*)。不可回填的字段(只读、被条件隐藏或禁用)不进「N 项可采纳」,条上照实写「另有 M 项不可回填」。「审计网关」之类领域流程不进 kit,应用照常把请求发到自己的接口。steps.<名>.gate: { when, message, run?, runLabel? }(StepForm):进入下一步(末步是提交)的前置条件。when是闭集谓词(present/blank/ 算子,all/any一层),叶子可引用本步字段、outcome.<into>.status | message | data.<路径>、steps.<更早步骤>.<字段>;不满足时按钮禁用并常驻显示message(message必填,回车提交同样被拦);run指向本步不写on的 request 动作,被拦住时提示旁出现「去验证」钮。步骤的behavior.actions只许这种动作。注意业务信封success: false时outcome.<名>.status仍是saved,「验证通过」请写outcome.<名>.data.success eq true。- 登记的草稿读口新增实现
patch(values)(与采纳条共用回填逻辑)。两项均 human-only,AI 不可写。 - 单测:
tests/object-renderer/adopt-gate.test.ts、tests/object-renderer/adopt-render.test.ts、tests/data/object-form-adopt-gate.test.ts、tests/biz/step-form/step-form-gate.test.ts。
国际化与 RTL(N 波 N5)
示例页 /ref/n5/zh、/ref/n5/en(同一页面两种语言)、/ref/n5/rtl + /ref/n5/rtl-body(dir=rtl 阿拉伯语工作台)。
- 页面语言与方向:
page.locale(缺省应用defaultLocale)写进页面根<div data-soma-host … lang dir>;page.dir: ltr | rtl缺省按语言推导(真源spec/kit-grammar.json#i18n.rtlLangs)。两者都没声明时根不写 lang / dir。 - 内置文案:
src/i18n/kit-text.ts一张「键 → { zh-CN, en }」表(键soma.kit.*)。组件运行期kitText(键, 参数, 元素)读元素最近的[lang]祖先(不退回<html lang>,Next 宿主写死en);业务组件 / 页面框编译期kitText(键, 参数, ctx.locale)。没声明语言取 zh-CN,词表没有的语言(如 ja、ar)取 en;显式 prop(cancel_text/empty_text…)永远优先,这类 prop 缺省值改成空串,由内置文案补。 - 页面词条
$t:键:值位置的$t:键在 kit 编译期按页面语言展开(页面顶层locales: { 语言: { 键: 文案 } }→ 应用词条表KitAppContext.locales→ 内置词表;先试完整标签再试主标签,再换应用缺省语言,最后 en),core 只收到展开后的文案;找不到报KIT-LOCALE-001。应用locales/*.yaml的读取与交入由 Host 装配负责(见 N5 报告)。 - RTL:内置组件的物理方向类已换成逻辑类(
ms-/me-/ps-/pe-/start-/end-/text-start/text-end/border-s/border-e/rounded-s/rounded-e/rounded-es/rounded-ee),带符号位移配rtl:变体;浮层贴边翻转按方向换算(resolvePopPlace(…, rtl));手写方向键处理经kitKey(key, 元素)在 RTL 下互换左右键,core 接线的集合宿主读最近[dir]自动换向;侧栏 / 分栏拖柄的指针换算按方向。物理角命名的浮层(ContextMenu 的tl/tr/bl/br、Fab 的bottom-right)不翻转。 - 带参词条(B1 A):
{ $t: 键, args: { 占位名: 值 } },词条原文里的{占位名}按 args 替换(口径同 core 的interpolate,不另写一份)。静态值(字符串 / 数字 / 布尔,不许含花括号)编译期展开;@row.字段改写成行占位{字段}留在文案里,由运行期持有行的组件插值(目前只有删除确认,KitCrudBridge 按被删行插值,只进确认弹窗的纯文本)。字符串形$t:键不带参。删除确认缺省文案:对象有标题字段(title/name/label取第一个)时为「确定删除「{标题}」?」,没有则不带名字;确认文案里写的{name}指 name 字段,没有 name 字段时指标题字段;占位不是 schema 字段报KIT-ACT-001。AI 的confirm_text同样可写{name}。 - StepForm 放弃时删半成品(B1 A):
config.abandon: keep|delete,缺省 keep,只在saveMode: perStep下有意义。delete 时每步多一个「取消」钮,首步对象必须声明write.delete(占位恰是{stepKey},否则KIT-STEP-003);已保存过(有主键)才二次确认并删除,没保存过直接回初始;离开页面(站内链接)同样守卫;删除失败照实 toast,不拦离开;刷新 / 关闭标签只有浏览器自带提示,来不及异步删除。 - 应用语言与词条表:
defineKitApp({ defaultLocale })与应用根locales/<语言>.yaml(平铺键: 文案)由assembleKitServer读成KitAppContext.defaultLocale / locales;没有locales/目录就没有应用词条表。 - 规格:「4.11 国际化与 RTL」;单测:
tests/i18n/kit-text.test.ts、tests/i18n/resolve-text-refs.test.ts、tests/base/n5-rtl.test.ts。
声明组件嵌入了哪些内置组件(declareKitUses,N 波 N8)
样式按页裁剪后,应用组件在运行期命令式嵌入的内置组件(mountKitComponent 等)编译器看不到,需要声明:
import { declareKitUses } from 'soma-kit/app';
export const AppDeployPanel = declareKitUses(def, ['Button', 'Input', 'Alert'], DEPLOY_PANEL_CLASS_CANDIDATES.split(' '));declareKitUses(def, uses, own?):uses为嵌入的内置组件契约名(及其闭包类一并并入);own为本组件自己写死的类,页面用到它才并入,不再放全局appCandidates。返回同一个定义,可与wrapKitDataComponent/composeKitDataComponents叠加。- 未声明的
App*组件:样式退到全部基础件类兜底,并整进程按组件名提示一次KIT-STYLE-003(console.warn,不阻断)。 - 运行期开发态提示:挂载了未被任何声明覆盖的非常驻内置组件时 console 提示一次(生产不提示;进程里没有任何声明时不提示)。
- 领域包用
defineKitPack({ componentCandidates })按包内数据组件登记候选类,不再有整包candidates。 - 虚拟滚动的占位高度是 kit 里唯一的运行期几何写入(CSSOM
el.style.height,只在setSpacerHeight),边界见规格 §4.10;仓库内联样式相关门不判它,无需豁免。 - 规格:「4.12 嵌入声明与按页样式裁剪」;指南第 6.4 节;单测:
tests/compile/declare-uses.test.ts。
主题机制(kit.app.yaml 的 theme)
theme:
palette_scheme: soma-finance-steel # 配色方案(定义于 soma-design/spec/palette-library.json)
font_scheme: auto # 字体方案(font-library.json)
color: '#336699' # 可选:品牌主色号
mode: system # 模式:light / dark / system / soft;缺省为 system装配期(soma-kit/app/server)将 theme 传入 soma-foundation/compile 的主题求解流水线,统一计算出亮色与暗色两套 --soma-* 令牌值,并序列化为 :root 与暗色样式块,拼装在主题头前提交样式引擎。应用代码严禁手写固定色值。
暗色切换跟随系统设备偏好或通过根节点 soma-scheme-dark / soma-scheme-light 样式类即时切换,无须重新编译。取值越界报 KIT-APP-002,求解失败报 KIT-APP-003,直接阻断构建。
块级主题(page.theme / product.theme)
页面与产品可各写一个 theme,只改所在内容块的光学令牌(page.theme 只管内容区,不含页面框外壳;整站主题仍只在 kit.app.yaml):
page:
theme: dark # 简写 inverse / dark / light
product:
theme: { mode: 深邃暗夜, color: 科技海蓝 } # 或词条对象;只有 mode / color 两个键只收词条,不收 hex、factors、name、别的维度键;越界报 KIT-THEME-001(形状)/ 002(未知词)/ 003(越权或区域不可用)。令牌在编译期由 foundation 求解为 .soma-scope-<id> 变量块,作用域类由编译器写在产品根的包装上;产品块覆盖页面块(两者都是对象时只覆盖自己写的键)。校验入口 checkBlockTheme(soma-kit/src/theme/block-theme.ts),键表与回喂模板在 spec/kit-grammar.json#blockTheme。演示页 /ref/q/theme-product、/ref/q/theme-page。
装配项透传与通知
在 defineKitApp({ auth, eventMiddleware, renderPorts, services, identity, ... }) 中:
auth、eventMiddleware、renderPorts透传给前端浏览器引导程序;服务端项交由宿主装配;renderPorts默认提供{ notify: createKitNotifyRenderer() }(采用 Tailwind 语义色的轻量级提示弹条;确认操作唤起浏览器原生对话框);- 具备服务端实现的私有服务目录(以
_开头的目录或文件)只用于模块间共享,不向路由暴露。 startup:服务启动钩子,soma dev/soma start的服务进程启动时(Nextinstrumentation.ts的register)在装配完成后执行一次,抛错即拒绝启动;soma build不执行。soma.app.ts能进浏览器包,服务端实现惰性导入:startup: async () => (await import('./soma.server')).startup()。responseHeaders({ pathname, method, kind }):给页面(kind: 'page')、/api/*('api')、静态资源('asset')的响应统一加头,同名以此为准,可同步或异步,返回undefined表示不加;例如给非图片请求加Cache-Control: no-store。composeKitApps对startup顺序执行、对responseHeaders合并(叠加应用覆盖基座同名头)。详见docs/03-类与模块/soma-core类与模块设计.md。
领域包规范(Packs)
携带行业或特定业务名词的专用写死形状数据组件与业务组件放置在 soma-kit/packs/<领域>/ 目录下,由应用根据业务场景显式引入:
import { defineKitApp } from 'soma-kit/app';
import { commercePack } from 'soma-kit/packs/commerce';
import { financePack } from 'soma-kit/packs/finance';
export default defineKitApp({ packs: [commercePack, financePack] });- 领域包使用
defineKitPack({ name, staticComponents, bizComponents, dataComponents, candidates })统一构建; - 领域包组件必须在内置通用组件基础上包装一层实现,禁止脱离底层组件重写基础交互;
- 包内组件命名必须以大写首字母的包名作为前缀(如
CommerceAddressCard);命名冲突直接抛错KIT-PACK-002,禁止静默覆盖; - 各领域包覆盖范围:
commerce(电商):四类卡片封装自RecordCard,支持传入金额格式与选择动作;finance(金融):包含对账核销业务台FinanceReconcileDesk、发票摘要卡FinanceInvoiceSummaryCard等,金额数值严格由后端负责,组件不私自运算;ai(人工智能):封装RecordCard、StatGroup、Checklist等,用于展示智能体运行步骤、模型评测、策略路由与提示词模版;ops(运维发布):用于变更窗口、依赖状态、错误预算监控、发布就绪度与发布流程审批;security(合规安全):用于访问权限复核、安全漏洞处置卡与合规证据清单。
额外页面目录与默认主题
defineKitApp({ extraPagesDirs: ['../shell/pages'] }):支持指定额外页面扫描路径,与应用主路径页面共同编译;若存在跨目录的重复 URL,立即抛出KIT-PAGE-004诊断;defineKitApp({ defaultTheme: { paletteScheme, fontScheme, color, mode } }):提供应用级兜底主题,当配置文件未显式指定主题时生效。
服务目录与浏览器端边界
soma.app.ts引入soma-kit/app,仅包含可安全进入浏览器环境的代码;- 服务端专用逻辑与 Node 依赖通过动态导入按需加载,并由打包配置隔离,绝对不会泄露至前端发布产物。
AI 页目标(soma-kit/page-target)
细节见 docs/03-类与模块/soma-kit类与模块设计.md 与 docs/04-语法规范/Kit套件语法规范.md,摘要:
soma-kit 实现 AI 生成页的 kit 目标:createKitPageTarget(app) 返回 soma-core/page-target 的 PageTarget(format 为 kit,owner 为 soma-kit,读 design page-origin.json)。soma-ai-page 只经接口使用它,不 import soma-kit。仅服务端 / 测试环境可达,浏览器入口不得可达;目标目录不 import ai / zod / soma-ai* / next / react(AIP-G16)。
- 蓝图:模型只出意图蓝图(
kitIntentSchema()):title、subtitle?、locale?、页面框意图、页面模式意图、objects(字段name / label / type / key / required / options,接口api/write_api只能取授权接口,sample?)、roles、views(角色的槽位放哪些字段)、actions(内置动作)、tabs?。 - 意图词:页面模式与页面框用抽象意图词(页面模式
crud/master-detail/category/board/form/detail/steps/approval/settings/import/result,页面框plain/blank/top-nav/tabs),由 kit 目标按kit-grammar.json#aiIntentMap确定性映射到bizComponents/frameKinds闭集;映射目标 ⊆ 闭集,由真源自检(soma-kit/tests/spec/ai-writer.test.ts)。AI 不写组件名。 - 写面:
kit-grammar.json#aiWriter是写面键表(ai | compiler | human-only,表外键一律 human-only),生成投影KIT_AI_WRITER(src/generated/kit-grammar-runtime.ts),目标代码只读投影。样式类、视觉数字、显式组件、颜色、frame.config/product.config、事件与联动胶水、page.url等越权键拒收(KIT-AIT-002)。 - 开放面:块级
theme(蓝图根 =page.theme,top/tabs[i]= 产品块;只收词条,同一个checkBlockTheme,整站主题仍只在kit.app.yaml);字段展示语义词display(kit-grammar.json#aiDisplayMap,只作用于list/summary,类型不符KIT-AIT-005,component/props仍禁);内置动作openCreate/openEdit/reload/remove(remove仅crud行动作,对象另给授权的写类delete_api,编译器恒追加confirm与tone: danger,缺确认或删除通道KIT-ACT-001,人写页同守)。 - 管线:
accept(形状 + 闭集 + 写面 + 授权接口核对 + 确定性编译为page.kit.yaml,同一蓝图逐字节相同)→dryRun(compileKitPage+ 写面再检 + 标准载荷)→ 零 Domain 冒烟 →submit。drift为整页重编后按revise_scope对账。 - 提交面:
soma-kit/app/server的pages.submit / unbind / list(createKitServerPages):定址、pageGrants授权终裁、信封校验、编译、registerSomaPage(同 URL 覆盖,乐观并发expectGeneration);溯源provenance必填;persist只取runtime/memory,registry拒收(KIT-AIT-006)。 - 诊断码:
KIT-AIT-001…006(形状、越权、未知键或闭集外取值、越出授权接口、对象描述不足、提交面拒收;另KIT-ACT-001删除缺确认),登记在kit-grammar.json#diagnostics;编译层沿用KIT-PAGE-* / KIT-OBJ-* / KIT-FRAME-* / KIT-BIZ-* / KIT-TYPE-*。 - 判定落点:AIP-G06 / G13 / G15 / G16 / G17 / G18,见
docs/03-类与模块/soma-ai与ai-page类设计.md;测试在soma-kit/tests/page-target/与soma-kit/tests/app/server-pages.test.ts。
字段与派生补齐(最后一轮 H2)
示例页 /ref/last/h2/flow(H2a / H2b)、/ref/last/h2/adopt(H2c)、/ref/last/h2/console(H2d),规格「4.6.3 / 4.6.5 / 5.x RequestConsole」,真源 kit-grammar.json#formLinkage.{fieldValue,flowOptions,adopt} 与 #staticProps.show.requestConsole。
- 画布节点动态选项(H2a):画布字段
props.nodeTypes.<类型>.fields[]里kind: select的options写成对象{ from, params?, valueKey?, labelKey?, rowsPath?, meta? },由表单取数(画布不发请求)。params可取表单草稿{ field: 另一字段 },依赖值变了重取并作废上一次;meta: { 节点键: 行键 }把所选行的列并入节点 meta;加载中 / 失败如实占位。限值读kit-limits#formLinkage.flowOptions。错误KIT-FORM-012。 - 派生遍历行表(H2b):
derive.template里{#each 字段}…{/each}(至多两块、不嵌套),块内{row.键}/{@n};字段值不是数组渲染 0 行,仍然没有表达式引擎。例:'{#each deps}{@n}. {row.alias} -> {row.api}; {/each}'。错误KIT-FORM-011。 - 采纳回填隐藏字段(H2c):
adopt.map逐字点名的hidden字段可被回填(本来就随草稿提交、非只读),采纳条写明「含 N 个隐藏字段」;不点名的隐藏字段不会被隐式回填,别的组件经登记表patch回填也不放开。 - RequestConsole
targets_from(H2d):{ path?, value?, label?, outcome? },目标下拉取自组件data,或某个 request 动作(into)的成功响应体;加载中 / 失败 / 无行各有占位;与静态targets不能同写。错误KIT-SHOW-006。 - 单测:
tests/object-renderer/h2-fields.test.ts。
业务信封约定(envelope)
后端响应的「业务失败」口径只声明一次:kit.app.yaml 的 envelope: { failWhen, messagePath, dataPath }(页面 page.envelope 可覆写,human-only)。
failWhen 是 core 的闭集谓词({field, op, value} 与 all_of / any_of),命中即业务失败;messagePath 是失败文案位置;dataPath 是成功数据位置。
编译器把它编进 core 数据声明的 write.failWhen / messagePath(以及 from[]):HTTP 200 + 命中也记 failed,闸门可写 outcome.x.status eq saved。
详情预取(behavior.detail)、服务端分页适配(unwrapBizEnvelope / createPagedListAdapter)、具名动作 http(kitActionHttp / createKitActionHttp(envelope))读同一份;kit 与 core 都没有写死的 success / ret / data。
闸门引用的 outcome 来自读了 $draft 的请求时,被读字段之后一改,结果即过期,闸门重新拦住并提示「内容已修改,请重新验证」。
