@manohub/app-kit 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,63 @@
1
+ # 样式纪律与反例
2
+
3
+ 应用侧 CSS 的边界:**只写布局**。视觉一律由骨架层组件与令牌承担。
4
+ 契约原文见 `node_modules/@manohub/app-kit/CONTRACT.md` §6(样式纪律)与 §5(反例库)。
5
+
6
+ ---
7
+
8
+ ## 一、允许的属性(白名单)
9
+
10
+ `display` / `flex*` / `grid*` / `gap` / `width` / `height` / `min-*` / `max-*` / `padding` / `margin` /
11
+ `overflow` / `position` / `inset` / `align-*` / `justify-*` / `z-index` / `transform` / `transition` /
12
+ `cursor` / `white-space` / `text-align`
13
+
14
+ 尺寸取值用令牌 px:`--ui-space-*` / `--ui-radius-*`(4 / 8 / full)/ `--ui-line-*` / `--ui-weight-*` /
15
+ `--ui-shadow-*` 等;字号只取 `--ui-font-*`(13 / 14 / 16 / 18 / 20),不自由取值。
16
+
17
+ ## 二、禁止项
18
+
19
+ | 禁止 | 为什么 | 正确做法 |
20
+ |---|---|---|
21
+ | `color` / `background*` / `border*` / `border-radius` / `box-shadow` / `font-*` / `fill` / `stroke` | 视觉属性被护栏拦;视觉归组件 | 用语义 prop(`tone` / `shape` / `size`)或换件 |
22
+ | `!important` | 会压过桥接层,产生不可控覆盖 | 走组件的 prop 维度;底层内部 px 只能改包内 `farris-bridge.css` |
23
+ | 自写 `rem` 尺寸 | 全套件统一 px、不跟随根字号缩放(契约 §9) | 用令牌 px |
24
+ | 裸元素选择器(`div {}` / `input {}`) | 影响面不可控 | 用已登记的类名前缀 |
25
+ | 未登记的选择器前缀(含底层内部类、其它模板遗留类) | 前缀未登记是红线 | 在 `appkit-guardrails.config.json` 登记自己的前缀;外部类一律删 |
26
+ | `.ak-*` 选择器 | 那是骨架层内部类 | 用组件 prop 或 `class` 载荷 |
27
+ | 应用侧自定义 CSS 令牌 | 令牌归骨架层 | 用 `--ui-*` |
28
+ | 自绘视觉(`.x-selected` 手写选中类、自绘 hover、自绘关闭按钮) | 视觉被收走后只剩布局 ⇒ 状态不可见(真实踩过) | `AppRadioCard` / `AppButton` / `AppNotice` 等件自带 |
29
+ | `height: 100vh` | 注入宿主容器后溢出 | `height: 100%` |
30
+
31
+ 唯一例外:文件头带 `@figma-fidelity: <节点>` 且登记在豁免注册表的设计还原文件(放行视觉属性 / 裸元素 /
32
+ 前缀三类,但 `!important` 与自写 rem **仍然禁止**,字面量只减不增)。
33
+
34
+ ## 三、常见违规的替代写法
35
+
36
+ | 违规写法 | 替代 |
37
+ |---|---|
38
+ | 区块间距 `margin-bottom` | `AppSection`(间距在组件里);组内行距同理 |
39
+ | 自绘「标签 + 值」两个 span(带不同颜色) | `AppForm.Item text={…}`(label 自动弱化) |
40
+ | 手写卡片选中类 | `AppRadioCard` |
41
+ | 单元格里塞原生 `<input>` + 居中类 | `AppTable` + `AppInput` / `AppSelect` + `column.align` |
42
+ | CSS Grid 自绘表格(列宽模板、溢出、空态留白) | `AppTable`(列宽权重、序号列、空态内建) |
43
+ | 自绘提示条 + 原生关闭按钮 | `AppNotice`;行内可点文字用 `AppButton shape="link"` |
44
+ | 自绘行内操作(手型光标、hover、禁用态) | `AppButton shape="link"` |
45
+ | 给面板加描边 / 圆角 / 阴影 / 底色 | 卡片外观用 `AppTable framed` 等内容件;`AppPanel` 无框是硬契约 |
46
+ | 给 Split 栏的首层子元素加 `border-right/left` | 删掉,分隔线归 Split |
47
+ | 给 `AppPanel` / `AppPanel.Body` 挂「只写 padding」的类 | 用组件的 `padding` 维度(`{ y, x }` 或数字) |
48
+ | 应用侧改控件高度(`height: 36px !important`) | 不要改;确有诉求走契约 §7 建件流程 |
49
+ | 自绘 `display:flex; gap` 排版 | `AppLayout.Row` / `AppLayout.Column` |
50
+
51
+ ## 四、命名与登记
52
+
53
+ - 应用自己在护栏配置里登记合法类名前缀(如 `vm-`);**不要**为了绕过护栏去登记底层内部类前缀。
54
+ - 出现「前缀未登记」违规时,正确做法是判断这个类该不该存在:属于自己的 → 登记;属于外部/遗留 → 删。
55
+
56
+ ## 五、自检
57
+
58
+ ```bash
59
+ node node_modules/@manohub/app-kit/lint/style-audit.mjs # 只查样式
60
+ node node_modules/@manohub/app-kit/lint/run-all.mjs --changed # 三条护栏查改动文件
61
+ ```
62
+
63
+ 每条违规都带 `file:line` + `correction`(唯一改法)+ `doc`(契约章节)—— 照 `correction` 改,不要另想一套。
@@ -0,0 +1,99 @@
1
+ ---
2
+ name: app-kit-migrate
3
+ version: 1.0.0
4
+ description: 把仍在直连底层组件库的存量子应用改造为使用 @manohub/app-kit 骨架层:接入护栏产违规基线、划定范围、逐文件替换、基线归零、验收与登记。触发条件:用户要求迁移/改造/接入子应用、要求处理护栏违规或降低违规基线、或代码里出现直连 farris / 底层风格 prop 需要清理时使用。
5
+ ---
6
+
7
+ # app-kit-migrate:存量应用改造
8
+
9
+ 目标:让一个存量应用**在护栏下合规**,且页面表现与改造前等价。
10
+ 按六个阶段推进,不跳阶段;每个阶段都有可验证的产出。
11
+
12
+ ## 阶段 0 · 摸清现状(改造前必做)
13
+
14
+ 1. 确认是否已接入:`package.json` 里有 `@manohub/app-kit`,且应用包根有 `appkit-guardrails.config.json`。
15
+ 未接入 → 先按 `../app-kit/references/adoption.md` 完成接入(安装、样式三行、入口、护栏配置),再回来。
16
+ 2. 读应用自己的 `AGENTS.md` 与结构分层(`views/`、`components/`、样式文件分布),确认业务边界。
17
+ 3. 记录改造前基线:**类型检查、构建产物大小**(后续验收要与它对比)。
18
+
19
+ ## 阶段 1 · 接入护栏并产出违规基线
20
+
21
+ 以「只报告、不拦断」的方式取得一份**可比的数字**:
22
+
23
+ ```bash
24
+ node node_modules/@manohub/app-kit/lint/run-all.mjs
25
+ ```
26
+
27
+ - 逐条规则记录违规数(style / component / structure 各自分规则),写入应用自己的基线文档
28
+ (放在应用包根,供后续「只减不增」比对)。
29
+ - 同步在 `appkit-guardrails.config.json` 里登记:应用目录、类名前缀、豁免注册表、`contractDoc`;
30
+ 存量未清完前保持挂起状态(`pending: true`),并登记豁免。
31
+ - 基线文档结构参见 `references/migration-playbook.md`「基线登记」。
32
+
33
+ ## 阶段 2 · 划定范围与顺序
34
+
35
+ - 按**文件**而不是按规则推进:一个文件内的所有违规一次改完,避免反复进同一文件。
36
+ - 推荐顺序:入口与全局(`main.ts`、`style.css`、`app.css`)→ 页面骨架(路由页的 `AppShell`)→ 组件(弹窗、表格、表单)→ 演示/边缘页。
37
+ - 每个文件开改前先查 `references/migration-map.md`,按表替换;表里没有的项,先查
38
+ `node_modules/@manohub/app-kit/CONTRACT.md` 对应章节,再动手。
39
+
40
+ ## 阶段 3 · 逐文件替换
41
+
42
+ 单文件循环(每个文件走一遍,不要攒着一起跑):
43
+
44
+ ```bash
45
+ node node_modules/@manohub/app-kit/lint/run-all.mjs --changed # 本文件违规清零
46
+ pnpm exec vue-tsc --noEmit # 未引入类型错误
47
+ ```
48
+
49
+ - **改动过的文件必须归零**(不留豁免);未改动的存量文件可以继续挂起。
50
+ - 护栏给出的 `correction` 是唯一改法,`doc` 锚点指回契约章节;不要自创等价写法。
51
+ - 样式违规的处理原则:能删就删(迁移后不需要的覆盖段),需要保留的改为布局属性或语义件的 prop。
52
+ 视觉属性一律交给骨架层组件与令牌。
53
+
54
+ ## 阶段 4 · 基线归零
55
+
56
+ 全部文件改完后,跑全量三条护栏,确保:
57
+
58
+ | 检查 | 通过标准 |
59
+ |---|---|
60
+ | 护栏 | 三条全绿;违规数不高于基线,动过的文件为 0 |
61
+ | 类型 | `pnpm exec vue-tsc --noEmit` 0 错 |
62
+ | 构建 | `pnpm build` 成功;产物大小与阶段 0 记录值量级一致(差异要能解释) |
63
+
64
+ 若某规则无法在当前骨架层能力下归零,**先查 `CONTRACT.md` §7 缺件处置流程**,
65
+ 不要私自留下违规或放宽护栏。
66
+
67
+ ## 阶段 5 · 验收(缺一不可)
68
+
69
+ 1. 浏览器逐个过页面与弹窗:渲染、交互、样式、弹层落点。
70
+ 2. **特殊入口必须单独验**:以 `index.html?xxx=1` 直开的入口(iframe 嵌入、选择模式)要在
71
+ `createSubApp` 的 `rootPathAliases` 里登记,否则首屏守卫会清空 query(见 `CONTRACT.md` §9.2)。
72
+ 3. 已知缺口要如实判断是否属于本次范围,不要误判为「漏改」:
73
+ - 结构护栏只扫 `.ts` / `.tsx`,不扫 `.vue`;未纳入骨架的 `.vue` 页面不会报 `structure/no-shell`。
74
+ - 骨架层尚未提供的件(缺件清单见 `CONTRACT.md` §7 与包内 `AGENTS.md`「待建件」)只能用现有件近似,不得自绘。
75
+ 4. 结论记录到应用文档:改了什么、验收证据、遗留项。
76
+
77
+ ## 阶段 6 · 收尾登记
78
+
79
+ - 基线文档更新为「迁移后:0」并写明日期;豁免登记表清空(或保留设计还原类豁免并注明节点)。
80
+ - `appkit-guardrails.config.json` 的挂起状态置为不挂起(纳入门禁)。
81
+ - 建议启用提交前钩子(包内提供样本),见 `CONTRACT.md` §8.1。
82
+ - 在应用 `AGENTS.md` 里补充「本应用已接入骨架层」与本次改造的关键结论。
83
+
84
+ ## 失败处理
85
+
86
+ | 现象 | 处理 |
87
+ |---|---|
88
+ | 护栏改动文件仍报同一处违规 | 读该违规的 `correction.summary` / `.example` 照改;仍不解则查 `doc` 锚点章节 |
89
+ | 组件疑似缺失,找不到对应件 | 走 `CONTRACT.md` §7 缺件处置流程;不要在应用侧自绘 |
90
+ | 替换后视觉与改造前不一致 | 先用等价骨架件复现原语义;确属骨架层缺陷 → 记录为待修项,不在应用侧写覆盖样式 |
91
+ | 类型出现「两种同名类型不兼容」 | 检查是否装了两份 vue / 依赖重复;按 `../app-kit/references/adoption.md` 的单例收敛处理 |
92
+ | 弹层落点偏移 | 应用侧只写 `--ibp-popup-shift-*` 变量,位移规则由骨架层桥接负责,不要写 `.popover` 类覆盖 |
93
+
94
+ ## 参考
95
+
96
+ - `references/migration-map.md`:替换映射表(旧写法 → 骨架层写法 → 注意事项),按类目查
97
+ - `references/migration-playbook.md`:阶段细则、基线登记格式、归零口径、验收清单
98
+ - `../app-kit/references/adoption.md`:接入 SOP
99
+ - `node_modules/@manohub/app-kit/CONTRACT.md`:规范唯一事实源
@@ -0,0 +1,139 @@
1
+ # 替换映射表:底层组件库直连 / 自绘 → 骨架层
2
+
3
+ 来源:一个已完成的子应用全量迁移(值映射管理,4 个页面 + 4 个弹窗 + 演示页,护栏违规 1353 → 0)。
4
+ 每条均取自真实改造痕迹,可直接查用。
5
+
6
+ **怎么用**:改造某个文件前,按类目在本文里查对应写法;本文没有的项,先查
7
+ `node_modules/@manohub/app-kit/CONTRACT.md` 对应章节,再动手。本文只做「旧 → 新」对照,
8
+ 规范口径以契约为准。
9
+
10
+ > 术语:**骨架层** = `@manohub/app-kit`;**底层组件库** = farris(被骨架层收敛,应用侧不得直连)。
11
+
12
+ ---
13
+
14
+ ## 一、页面骨架:自绘 → `AppShell` / `AppPanel`
15
+
16
+ | 旧写法 | 新写法 | 注意事项 |
17
+ |---|---|---|
18
+ | 自绘根容器(`display:flex; flex-direction:column` + `height:100vh` + padding) | `AppShell` + `AppShell.Body`(`mode="plain"\|"scroll"\|"table"`) | **禁 `100vh`**:注入宿主容器后会溢出;高度一律 `100%` |
19
+ | 自绘页头(标题 + 关闭 + 操作按钮) | `AppShell.Header title=… toolbar={…}` | 页头规格(高度、间距、按钮形态)归组件;选择模式这类极简页可以不给页头,只留 Body |
20
+ | 自绘面板头 / 工具条 / 搜索位 | `AppPanel toolbar={…} actions={…}` 或 `AppPanel.Header` | `toolbar` 是区域筛选(≤3 字段)、`actions` 是区域操作,**两位不可互换**;弹窗内可以不给 title |
21
+ | 自绘表格外框 / 卡片(描边 + 圆角 + 表头) | `AppTable framed` | 卡片外观由内容件承担;`AppPanel` 本身无框 |
22
+ | 自绘双栏 + 手写 1px 竖直分隔线 | `AppShell.Split sidebar={{ width: 208, content: () => … }}` | 中间分隔线由 Split 统一给;**业务侧不得再写 `border-left/right`**;`width` 是内容宽 |
23
+ | 自绘区域容器 + 自管滚动(`flex:1; min-height:0; overflow:auto`) | `AppPanel`(`Header` / `Body` / `Footer` 三件,只有 Body 滚) | 自己摆这层**少写一条 `min-height:0`** 就会把弹窗顶高 |
24
+ | 自绘「只写内距」的容器类 | `AppPanel` / `AppPanel.Body` 的 `padding` 维度 | 内距走组件 prop,不要用应用侧类 |
25
+ | 分页条自绘页脚 / 挂在页面级 Footer | 表格在面板内 → `AppPanel.Footer` 内放 `AppPagination`;表格直接挂 Shell 下才用 `AppShell.Footer` | **分页跟「承载表格的容器」走**;加载失败或无数据时不渲染分页 |
26
+ | 纯标题区块(自绘标题 + 间距) | `AppSection`(组间距由组件给) | `AppPanel` 只给「区域」用;拿它当「带标题的卡片」会带进 48px 区域头与滚动契约 |
27
+
28
+ ---
29
+
30
+ ## 二、组件替换:底层件 → `App*` 件
31
+
32
+ | 旧组件 | 新组件 | 注意事项 |
33
+ |---|---|---|
34
+ | `FButton` | `AppButton`(`tone` / `shape` / `size` / `loading`) | 底层 `type` / `customClass` / `iconClass` 由包内翻译;**`tone` 缺省 + `solid` 会渲染成主色** —— 按钮组只留一颗主按钮且在首位,其余显式 `tone="secondary"`,图标按钮 `shape="ghost"` |
35
+ | `FInputGroup` | `AppInput` | 包内固定实时回传(底层默认失焦),应用侧拿到的就是当前值 |
36
+ | `FComboList` | `AppSelect`(`options` + `modelValue`) | 应用侧只给 `{ label, value }`;底层的值/文本字段与枚举类型由包内翻译 |
37
+ | `FDataGrid` | `AppTable` | 三态(loading / empty / error)内建,不要再自绘遮罩与空态 |
38
+ | `FSearchBox` | `AppSearchBox` | 包内补齐「清空」与「回车」两条事件 |
39
+ | `FPagination` | `AppPagination` | **0 基**(底层 1 基由包内换算);总条数信息自带,不要自绘「共 N 条」 |
40
+ | `FMessageBoxService`(静态回调式) | `messageBox.confirm()` / `.show()`(Promise 化) | 文案传**纯文本**(`\n` 换行,包内转义);HTML 只用于静态长文;右上角 ✕ / Esc **只关窗、不落定 Promise** |
41
+ | `FNotifyService` / `inject(令牌)` | `notify.success/warning/error/info`(单例) | 应用侧不再注入底层服务令牌;错误提示可设更长自动关闭时间 |
42
+ | `FModal` | `AppDialog`(`open` + `onUpdate:open`) | 内容渲染进应用容器(否则令牌与样式全丢);`fitContent` 缺省 true,要「定高 + 内部滚动」必须显式关掉并给 `height` |
43
+ | `FStep` | `AppSteps`(`items` + `modelValue`,0 基) | 受控语义由包内承担 |
44
+ | `FDynamicForm` / `FDynamicFormGroup` | `AppForm` + `AppForm.Item`(**自建件**) | 底层是元数据驱动体系、校验为浮层,与契约冲突;错误与说明走文档流的 `error` / `hint` |
45
+ | `FCheckBox` | `AppCheckbox`(**自建件**) | `modelValue` / `onChange` / `indeterminate` |
46
+ | `FTreeView` | `AppTree`(**自建件**,受控展开) | 数据刷新不丢展开态;**不要**在应用侧改缩进或写死像素 |
47
+ | (无) | `AppBadge`(语义 `tone` 承担配色) | 原本自绘色标签的,改用 `tone`,应用侧不写颜色 |
48
+ | (无) | `AppNotice`(说明条 / 警告) | 底层无对应件;原自绘提示条与行内链接改为 `AppNotice` + `AppButton shape="link"` |
49
+ | (无) | `AppLayout.Row` / `.Column`、`AppSection`、`AppRadioCard`、`AppQueryState`、`AppTabs` | 行内横排、区块标题、卡片单选、三态块、页签:自绘结构退役 |
50
+
51
+ ---
52
+
53
+ ## 三、prop 替换:底层风格 → 契约 prop
54
+
55
+ | 旧 prop | 新 prop | 注意事项 |
56
+ |---|---|---|
57
+ | `valueField` / `textField` / `enumValueType` | `AppSelect` 的 `options = [{ label, value }]` | 底层漏设值/文本字段会**静默丢选中值**;翻译在包内,应用侧写不出来 |
58
+ | `idField` | `AppTable` 的 `rowKey` | — |
59
+ | `columnTemplate` | `columns[].render(row, index)` | `render` 收到的是**业务对象本身**(可直接回写 `row.字段`),不加包装 |
60
+ | `headerFormatter`(双行表头) | `columns[].title: () => VNode` | 传函数即自定义表头节点 |
61
+ | `rowOption.height` | `rowHeight` | 底层 `rowOption` 收敛为组件 prop |
62
+ | `rowOption.customRowStyle` | `rowClass(row)` | 只透传 class,不开放行内 style(如「重复行标红」用它) |
63
+ | `columnOption={{ fitColumns, resizeColumn, fitMode }}` | 无 prop(包内固定) | 已内化;列宽用 `columns[].width`(数字=权重,百分比字符串=百分比) |
64
+ | `showCheckbox` / `showSelectAll` / `enableSelectRow` / `keepSelectingOnPaging` / `selectionValues` | `selection={{ mode, selected, onSelectedChange, selectable, onSelectAll }}`;行高亮用 `rowHighlightKey` | 选择列由包内自绘;`rowHighlightKey` 只染行、不加勾选列 |
65
+ | `searchPlaceHolder` | `AppSearchBox` 的 `placeholder` | — |
66
+ | `customClass` / `iconClass` | `class`(唯一透传项之一)/ `tone` + `shape` | 除 `class` / `style` 外其它透传属性一律丢弃 |
67
+ | 行事件参数顺序不一(`(index,row)` 与 `(row,index)` 混用) | `onRowClick(row)` / `onRowDoubleClick(row)` | 只回传业务行,顺序固定 |
68
+ | `enableXxx` / `showXxx` 形态布尔前缀 | 语义化的 `tone` / `shape` / `selection` 等 | 契约禁止布尔前缀与嵌套底层配置对象 |
69
+
70
+ > `selection={{…}}` 是**合法契约**(骨架层自己的结构化入参),不是底层配置对象 —— 判定看「这个键属于谁」。
71
+
72
+ ---
73
+
74
+ ## 四、内部类覆写:压底层内部选择器 → 三种去向
75
+
76
+ | 旧做法(应用 CSS 内) | 新做法 | 注意事项 |
77
+ |---|---|---|
78
+ | `.fv-grid` / `.fv-datagrid` / `.fv-grid-cell` 的宽度与溢出覆写 | **删除**,交骨架层 `AppTable` 内部几何 | 应用侧出现内部选择器 + `!important` 会同时撞两条红线;若实测仍被裁剪 → 修骨架层桥接白名单,不要写回应用 |
79
+ | 压 `.fv-grid-header*` 高度 + `!important`(双行表头) | `AppTable` 的 `headerHeight` prop | 底层表头高度是内联写死的,只能由桥接层压过;不传该 prop 的应用零影响 |
80
+ | `.popover { translate: … }`(门户弹层位移) | **移交桥接层**;应用侧只保留写变量的工具(`--ibp-popup-shift-x/y`) | 弹层挂到 body、不受应用容器作用域约束;应用侧写 `.popover` 必撞选择器前缀红线 |
81
+ | 底层 tooltip / 自适应弹层的位置补丁 | 桥接层统一处理(`position: fixed` + `translate: none`) | 与「变量平移」是**互斥两套修法**,叠用会双倍偏移 |
82
+ | 压 `.fv-grid-cell` 行高等全局覆盖 | 随旧缩放覆盖段**整段删除** | 对应「不跟随根字号缩放」的既定取舍(契约已知偏差) |
83
+
84
+ ---
85
+
86
+ ## 五、样式改写:视觉属性 / rem / `!important` / 裸元素 / 未登记前缀
87
+
88
+ 迁移后的口径:应用侧 CSS **只写布局属性**(`display` / `flex` / `grid` / `gap` / `width` / `height` /
89
+ `padding` / `margin` / `overflow` / `position` 等),尺寸取令牌 px。视觉一律由骨架层组件承担。
90
+
91
+ 参考基线(某应用的迁移前后):视觉属性 717 → 0、自写 rem 353 → 0、未登记前缀 120 → 0、
92
+ `!important` 36 → 0、裸元素选择器 9 → 0、应用侧自定义令牌 2 → 0。
93
+
94
+ | 旧 CSS(自绘 / 违规) | 新写法 | 注意事项 |
95
+ |---|---|---|
96
+ | 区块间距 `margin-bottom` | `AppSection`(间距在组件里) | 应用侧不写间距 |
97
+ | 自绘「标签 + 值」两个 span(带颜色) | `AppForm.Item text={…}`(只读文本行) | 应用侧写不了颜色,自绘只会「标签与值同色」 |
98
+ | 手写卡片单选的选中类(`.x-selected`) | `AppRadioCard` | 视觉被收走后只剩布局 ⇒ 选中态不可见,不要试图自己补色 |
99
+ | 表格单元格内的原生控件 + 居中/必填类 | `AppTable` + `AppInput` / `AppSelect` / `AppCheckbox` + `column.align` | 原生控件在应用侧拿不到边框底色,且撞结构护栏 |
100
+ | CSS Grid 自绘表格(列宽模板 + 溢出 + 空态留白) | `AppTable`(列宽权重、`rowNumber` 序号列、空态内建) | 换件后网格相关的 `min-width:0` 等一并删除 |
101
+ | 自绘提示条 + 原生关闭按钮 | `AppNotice` + `AppButton shape="link"` | 禁原生控件承载外观 |
102
+ | 自绘行内操作(hover、手型光标、禁用态) | `AppButton shape="link"` | 字形/颜色/hover/禁用态归组件 |
103
+ | 自绘等宽字形(`font-family`) | **删除**,等骨架层出编码类组件 | `font-family` 属视觉属性,被护栏拦下;应用侧不自己写字体 |
104
+ | 未登记选择器前缀(底层内部类、模板遗留类) | 删除;应用合法前缀只有自己在护栏配置里登记的那些 | 未登记前缀是 `style/prefix` 红线 |
105
+ | Vite / 模板遗留(默认深色块、其它 UI 库变量、`prefers-color-scheme` 媒体查询) | 删除(由骨架层 reset 承担) | — |
106
+
107
+ ---
108
+
109
+ ## 六、入口与全局
110
+
111
+ | 旧写法 | 新写法 | 注意事项 |
112
+ |---|---|---|
113
+ | 手写 `createApp` / `mount` / `window.mount` / 微前端协议 / pinia / 路由守卫 / 宿主语言监听 | `createSubApp({ rootComponent, routes, extraPlugins, rootPathAliases, onReady })` | 工厂内部已完成容器包裹、pinia、路由、守卫、协议、非微前端自动挂载;应用侧**不得重复书写**。vue-i18n 走 `extraPlugins`(骨架层内置的 i18n 选项是另一套语义,不要用) |
114
+ | 手写样式导入顺序 | 固定三行:`reset.css` → `styles.css` → `./app.css` | 顺序即契约,不增不减 |
115
+ | 应用侧各自声明底层组件库 / 图标包依赖 | 只声明骨架层包;其余由骨架层的依赖带入 | 应用侧不得安装或 `import` 底层组件库 |
116
+ | 自建 lint 脚本多条 | 只挂 `lint` / `lint:changed` 两条 | 三条护栏由统一入口并发跑 |
117
+ | 旧缩放覆盖段 + 弹层平移段(应用 CSS 内) | 整段删除;弹层位移由桥接层消费应用侧写入的变量 | 未写变量的应用为零偏移,无副作用 |
118
+ | 弹窗尺寸按宿主窗口(`window.innerWidth/Height`)计算 | 按**子应用容器**实际尺寸计算 | 门户容器远小于宿主窗口,用窗口尺寸会把弹窗撑出容器被裁 |
119
+
120
+ ---
121
+
122
+ ## 七、迁移中暴露的坑与结论
123
+
124
+ 1. **单例收敛必须保留**:依赖去重配置与安装方式无关,删掉会出现两份实例(`provide/inject` 失效、服务拿不到上下文)。
125
+ 2. **弹层包含块不是文档原点**:门户容器内的弹层需要原点补偿;补偿用 `translate`(独立于 `transform`,不会覆盖底层对齐箭头的行内 transform)。
126
+ 3. **弹窗渲染宿主**:弹层默认挂到 `body` 会丢令牌与应用样式;骨架层改为挂进应用容器,且要精确匹配本应用容器(门户里可能同时存在多个应用)。
127
+ 4. **`fitContent` 缺省为 true**:不显式关掉并给高度,`height` 会被忽略、弹窗随内容顶出视口;定高弹窗的内部滚动由 `AppPanel.Body` 承担。
128
+ 5. **表格高度链**:底层网格是**填充型**,高度只能来自外部;`height:100%` 落在自动高度父级会塌成 0(行也被裁掉)。优先用 `AppShell.Body mode="table"` 或 `AppPanel.Body`;滚动区里的表格要给确定高度(常量要与表格的行高/表头高 prop 同源)。
129
+ 6. **`AppPanel.Body` 不要套 `height:auto` 的 div**:会让整个 Body 滚动、表头跟着滚走。
130
+ 7. **表头高度**:只能走 `headerHeight` prop(底层内联写死),不要在应用里压内部类。
131
+ 8. **树展开态**:自建树是受控的,数据刷新不丢展开态;不要用「数据变了就重挂」的写法绕过。
132
+ 9. **布尔/字符串 prop 判定顺序**:出现过「成功态空串被判成 true,永远显示加载失败」的真实事故 —— 传空字符串表示无错误时要注意类型顺序。
133
+ 10. **行对象身份**:`render` / `rowClass` / 行事件收到的是传入的**那一个业务对象**,可直接回写字段。
134
+ 11. **选中契约**:传受控值则上报增量、不传则非受控上报全量;翻页时非受控选择自然清空。
135
+ 12. **分页归属**:跟承载表格的容器走;无数据或加载失败时不渲染分页(否则「共 0 条」是假数)。
136
+ 13. **分页是 0 基**:从 1 基数据接入时要换算一次。
137
+ 14. **按钮语义色**:`tone` 缺省即主色,按钮组里只有一颗主按钮。
138
+ 15. **消息框文案**:传纯文本(内部转义),不要自己拼 HTML;关闭按钮不落定 Promise,需要「确定」语义时用确认框。
139
+ 16. **缺件不要自绘近似件**:本次迁移中 `AppForm` / `AppCheckbox` / `AppTree` / `AppNotice` 都经历「先自绘 → 移交骨架层自建」的过程;应用侧自绘的同类结构最终都要拆掉。
@@ -0,0 +1,167 @@
1
+ # 迁移剧本:阶段细则、基线登记与验收
2
+
3
+ 配合 `SKILL.md` 的六阶段使用。本文给的是**可照抄的格式与口径**,替换映射见 `migration-map.md`。
4
+
5
+ ---
6
+
7
+ ## 一、阶段 0 记录表(改造前留痕)
8
+
9
+ ```markdown
10
+ ## 改造前基线(YYYY-MM-DD)
11
+
12
+ - 应用:<应用名>(包目录:apps/<x>)
13
+ - 依赖形态:<骨架层版本 / 是否 link 联调>
14
+ - 类型检查:pnpm exec vue-tsc --noEmit → <0 错 / N 错>
15
+ - 生产构建:pnpm build → <成功/失败>,产物 <dist/index.js 大小>
16
+ - 护栏:<三条是否可跑、是否已挂起>
17
+ - 页面清单:<路由页 / 弹窗 / 演示页,各多少>
18
+ ```
19
+
20
+ 产物大小与错误数必须记下来:阶段 4 要用它判断「迁移有没有改变行为」。
21
+
22
+ ---
23
+
24
+ ## 二、阶段 1 基线登记格式
25
+
26
+ 在应用自己的文档里维护(建议单独一份,如 `docs/<pkg>-guardrails.md`),供后续「只减不增」比对:
27
+
28
+ ```markdown
29
+ ## 一、违规基线(YYYY-MM-DD,迁移前)
30
+
31
+ | 护栏 | 规则 | 迁移前 | 现状 |
32
+ |---|---|---|---|
33
+ | style | `style/visual` 视觉属性 | 717 | 717 |
34
+ | style | `style/rem` 自写 rem 尺寸 | 353 | 353 |
35
+ | style | `style/prefix` 选择器前缀未登记 | 120 | 120 |
36
+ | style | `style/important` `!important` | 36 | 36 |
37
+ | style | `style/bare-element` 裸元素选择器 | 9 | 9 |
38
+ | style | `style/token` 应用侧定义令牌 | 2 | 2 |
39
+ | api | `api/legacy-prop` 底层风格 prop | 97 | 97 |
40
+ | api | `api/farris-import` 直连底层组件库 | 7 | 7 |
41
+ | structure | `structure/native-control` 原生控件带 class | 5 | 5 |
42
+ | structure | `structure/no-shell` 路由页未用骨架 | 3 | 3 |
43
+ | structure | `structure/self-header` 自绘页头/面板头/表格框 | 2 | 2 |
44
+ | structure | `structure/self-border` 自绘竖直边框(warn) | 2 | 2 |
45
+ | | **合计** | **1353** | **1353** |
46
+ ```
47
+
48
+ 规则名以护栏输出的 `rule` 字段为准(不同版本可能增减),不要照抄本文的数量。
49
+
50
+ 同时在 `appkit-guardrails.config.json` 里登记:
51
+
52
+ ```jsonc
53
+ {
54
+ "$schema": "./node_modules/@manohub/app-kit/lint/guardrails.config.schema.json",
55
+ "apps": [{ "dir": ".", "name": "<app>", "prefixes": ["<你的类名前缀>"], "pending": true }],
56
+ "srcGlobs": ["src/**/*.ts", "src/**/*.tsx"],
57
+ "styleGlobs": ["src/**/*.css"],
58
+ "contractDoc": "node_modules/@manohub/app-kit/CONTRACT.md",
59
+ "exemptionRegistry": "docs/<pkg>-guardrails.md"
60
+ }
61
+ ```
62
+
63
+ - `pending: true` = 只报告不拦断(存量未清完时保持);全部归零后置 `false` 纳入门禁。
64
+ - `prefixes` 只登记本应用自己的合法类名前缀,**不要**为了绕过护栏去登记底层内部类前缀。
65
+
66
+ ---
67
+
68
+ ## 三、阶段 2 范围与顺序
69
+
70
+ 按**文件**推进(一个文件的全部违规一次改完),推荐顺序:
71
+
72
+ 1. **入口与全局**:`main.ts`(入口编排)、`style.css`(样式三行)、`app.css`(删旧缩放/平移/模板遗留段)、`package.json`(依赖与脚本)。
73
+ 2. **页面骨架**:路由页改用 `AppShell`,这一步会一次性消掉大部分 `structure/*` 违规。
74
+ 3. **组件**:弹窗 → 表格 → 表单 → 其它。
75
+ 4. **边缘页**:演示页、错误页(401/404)、首页。
76
+
77
+ 顺序原则:先做「改动面大、能顺带消掉一片违规」的骨架层改造,再做零散替换。
78
+
79
+ ---
80
+
81
+ ## 四、阶段 3 单文件循环
82
+
83
+ ```bash
84
+ node node_modules/@manohub/app-kit/lint/run-all.mjs --changed # 直到本文件 0 违规
85
+ pnpm exec vue-tsc --noEmit # 直到 0 错
86
+ ```
87
+
88
+ - 护栏每条违规自带 `file:line` + `correction.summary` / `.example` + `doc`(契约章节锚点):
89
+ **照 `correction` 改,不要另想一套**。
90
+ - 需要单独排查某条规则时才直接跑单条脚本:
91
+ `node node_modules/@manohub/app-kit/lint/style-audit.mjs`(component / structure 同理)。
92
+ - 样式类违规的处理次序:能删就删(迁移后不再需要的覆盖段)→ 需要保留的改成布局属性 →
93
+ 视觉语义改用组件的 `tone` / `shape` / `size`。
94
+
95
+ ---
96
+
97
+ ## 五、阶段 4 归零口径
98
+
99
+ | 口径 | 说明 |
100
+ |---|---|
101
+ | 存量文件 | 「只减不增」:允许高于 0(挂起中),但不得比基线更差 |
102
+ | **改动过的文件** | **必须归零**,不留豁免 |
103
+ | 新增文件 | 必须归零 |
104
+ | 无法归零的规则 | 先按契约缺件处置流程判断是「缺件」还是「用法错」,不要私自放宽护栏 |
105
+ | 设计还原需要 | 用 `@figma-fidelity: <节点>` 文件头标记 + 在豁免登记表登记,护栏对该文件的视觉/裸元素/前缀三类放行(`!important` 与自写 rem **仍然禁止**) |
106
+
107
+ 豁免登记格式:
108
+
109
+ ```markdown
110
+ ## 三、设计还原豁免登记
111
+
112
+ | 文件 | 设计稿节点 | 视觉字面量基线 | 登记日期 |
113
+ |---|---|---|---|
114
+ | src/views/xxx/index.tsx | 12345:67890 | 12 | 2026-09-18 |
115
+ ```
116
+
117
+ ---
118
+
119
+ ## 六、阶段 5 验收清单
120
+
121
+ | 项 | 命令 / 做法 | 通过标准 |
122
+ |---|---|---|
123
+ | 护栏 | `node node_modules/@manohub/app-kit/lint/run-all.mjs` | 三条全绿;改动文件为 0;总数不高于基线 |
124
+ | 类型 | `pnpm exec vue-tsc --noEmit` | 0 错 |
125
+ | 构建 | `pnpm build` | 成功;产物大小与阶段 0 量级一致(差异需能解释) |
126
+ | 页面 | 浏览器逐个打开路由页与弹窗 | 渲染、交互、样式、弹层落点与改造前等价 |
127
+ | **特殊入口** | 以 `index.html?xxx=1` 直开的入口(iframe 嵌入 / 选择模式) | 在 `createSubApp` 的 `rootPathAliases` 登记,且实测 query 未被首屏守卫生成清空 |
128
+ | 宿主集成 | 在真实宿主(门户)里打开一次 | 弹层落点、容器尺寸、语言切换正常 |
129
+
130
+ 特殊入口的判定依据:`createSubApp` 默认首帧把非根路径重置到 `/` **并清空 query**
131
+ (契约已知偏差)。凡是靠 query 传参进入的页面都必须登记别名,否则该入口会静默失效。
132
+
133
+ ---
134
+
135
+ ## 七、阶段 6 收尾
136
+
137
+ ```markdown
138
+ ## 迁移完成(YYYY-MM-DD)
139
+
140
+ | 规则 | 迁移前 | 迁移后 |
141
+ |---|---|---|
142
+ | … | 1353 | 0 |
143
+ ```
144
+
145
+ - `appkit-guardrails.config.json`:`pending` 置 `false`。
146
+ - 基线文档补「迁移完成清单」:逐文件写清改了什么(替换了哪些件、删了哪些自绘结构、移交了哪些能力)。
147
+ - 豁免登记表清空(设计还原类可保留并注明节点)。
148
+ - 建议启用提交前钩子:
149
+
150
+ ```bash
151
+ cp node_modules/@manohub/app-kit/lint/pre-commit.sample .git/hooks/pre-commit
152
+ chmod +x .git/hooks/pre-commit
153
+ # monorepo 中应用不是仓库根时:
154
+ export APPKIT_APP_DIR=apps/<x>
155
+ ```
156
+
157
+ - 应用 `AGENTS.md` 补充「已接入骨架层」与本次改造结论。
158
+
159
+ ---
160
+
161
+ ## 八、已知缺口(判断「是否漏改」时看这里)
162
+
163
+ | 缺口 | 影响 | 处理 |
164
+ |---|---|---|
165
+ | 结构护栏只扫 `.ts` / `.tsx`,不扫 `.vue` | 未纳入骨架的 `.vue` 页面不会报 `structure/no-shell` | 属已知范围,不要以为漏了;要覆盖需先有页面级豁免能力 |
166
+ | 骨架层尚未提供的件 | 某些位置只能用现有件近似 | 走契约缺件处置流程,不在应用侧自绘;缺件清单见契约 §7 与包内 `AGENTS.md` 的「待建件」 |
167
+ | 底层组件库版本跃迁带来的视觉差异 | 个别间距/圆角可能与改造前有细微不同 | 先确认是否骨架层规范使然;确属缺陷则记为骨架层待修项,不在应用侧写覆盖样式 |