docs: 交接件 —— input-supplement 换版面的设计与前提(为上下文压缩而写)

用户已批准的 bounded 改动:把 S1 数据工单那一屏的视图层换成
docs/ss02-act-05-new/ACT-WORK-05-数据工单-PN主数据创建.html 的版面,绑定与功能不动。

用户已定三件事(压缩后不必再问):两条并列轨道 · 新增类型面板每行扩四格且去掉独立字段表 ·
不动平台每步骤卡一道闸。

文档里落了压缩后会用到的全部事实:目标件结构、现有件四个片段的行号与绑定、
P08_CSS 的位置、ViewContainer 的 editable/bodyOf/addRow 机制、gateLedgers 四张表、
必须留着的终审 C1 修复(那个 disabled 的 data-col=type_id)、
以及一条要先验证再往下写的风险(步骤③ 两组瓷贴共用一个宿主时 cellsOf/rowOf 能否正确归行 ——
bodyOf 只找得到一个宿主,认错会把两颗料的参数值混成一行)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
This commit is contained in:
xcs
2026-08-30 19:32:15 +08:00
co-authored by Claude Opus 5
parent 45cec82984
commit 4076621e04
@@ -0,0 +1,232 @@
# `input-supplement` 换版面 —— 照新目标件重做数据工单那一屏
> **状态**:设计已定、**已获用户批准**(2026-08-30)。这份文档是**为上下文压缩而写的交接件** ——
> 压缩之后照它直接开工,不必重新 brainstorm、不必再问用户。
>
> **分类**:bounded(要改的流程仓里已经有;换版面、功能不变)。按 brainstorming 技能的 bounded 路径,
> **不写 spec、不走 writing-plans**;这份文档只是上下文的替身,不是流程意义上的实施计划。
---
## 一句话
把 `input-supplement`(S1 派的那张数据工单)的**视图层**换成
`docs/ss02-act-05-new/ACT-WORK-05-数据工单-PN主数据创建.html` 的版面,**绑定面与功能一个不动**。
用户原话:「功能应该是一样的」「注意页面样式要和这个模版html几乎一样」。
---
## 用户已定的三件事(不要再问)
| # | 问题 | 用户的选择 |
|---|---|---|
| 1 | 目标件是单颗 PN 三步向导,而 W05 天生两颗料 | **每颗料一条轨道(两条并列)** |
| 2 | 目标件的新增类型面板只收参数名,而 W05 的硬/软约束必填 | **面板里每行扩成四格**(参数名/说明/硬软约束/单位),**独立的「核心参数字段」表去掉** |
| 3 | 目标件是一道总闸切整页,平台的闸按台账地址走 | **不动平台:每个步骤卡自带一道闸** |
**第 1 条我提过风险**(类型要在两条轨道各选一次、而它们必须相同,容易选出不一致再被判据拦下),
用户仍选了它 —— 按他的决定走,但**加一道无脚本护栏**(见下)。
**第 2 条的连带代价**(要在实现时留意):独立字段表去掉之后,**已有类型的字段清单在件上看不到也改不了**。
现有件那张表的第二个用途是「在库但当年参数没录全 → 给已有类型补一个字段」。
**缓解**:把新增类型那个 `<details>` 面板对**已有类型**也开放(标题写「类型库里没有?或要给这个类型补一个字段」),
同一个面板兼两用,版面不变而能力不丢。**这一条是我在设计里补的,用户没有单独批,但它属于「功能一样」的承诺范围。**
---
## 现场事实(已逐条核实,不必重查)
### 目标件
`docs/ss02-act-05-new/ACT-WORK-05-数据工单-PN主数据创建.html` —— 444 行 / 26KB。
同目录另有 `SS02_ACT-WORK-05_差异评估_E04-I01架构版_V1.2.html` 等,**不在本次范围**。
旧目标件在 `docs/SS02-ACT-WORK-05-html/ACT-WORK-05-数据工单-01-芯片主数据创建.html`(现有件是照它平移的)。
目标件结构:
- `.card-head`(`.badge`「数据」· `.kick`/`h1`/`.sub` · `.side` 里 tag/step/deadline)
- 【触发原因】`.zone` + `.note`(流程 / AI 预填情况)
- **`.jc-editgate` 带 `.gateprog` 进度条**(`.track`/`.fill`/`.pct` = `0 / 3 项`)
- **渲染态 `.viewarea`**:`.idcard` 两格(PN 名称 / PN 类型,大号 mono 值)+ 核心参数表 `.tbl-wrap`
- **编辑态 `.editarea`**:`.steps` 步骤轨道(`.stepbox` / `.sdot` / `.scard` / `.fld`)
- ① PN 名称 ② PN 类型(select 首行 `+新增类型`)+ `.newtype` 面板 ③ 核心参数值 `.pvgrid` 瓷贴(2 列)
- `.flow` 底部说明 + `.card-foot`
- 尾部 `<script>` 驱动:类型库 `PN_TYPES` → 选类型即时加载参数框、新增类型面板、进度条
**目标件的闸结构与平台逐字一样**(`.jc-editgate` / `.eg-open` / `.eg-cancel` / `.eg-save` / `.on`)——
它本来就是照平台做的,只有「一道 vs 四道」这一处对不上。
### 现有件
`activities/part-substitution-assessment/src/views/report.ts`(953 行):
| 片段 | 行 | 绑定 |
|---|---|---|
| `PART_TABLE` | :298 | `<tbody data-rows="mine:sub_eval.part" data-edit="parts.profile">` |
| `CHIP_TYPE_TABLE` | :398 | `<table data-edit="parts.chip_type">` + `<tbody data-rows="mine:parts.chip_type" data-edit="parts.chip_type">`(**两处都写**,见下)+ `addrow` |
| `TYPE_PARAM_TABLE` | :447 | `<table data-edit="parts.type_param">` + `<tbody data-rows="mine:parts.type_param" …>` + `addrow` |
| `PARAMETER_TABLE` | :496 | `<tbody data-rows="mine:parts.parameter" data-edit="parts.parameter">` |
| `INPUT_SUPPLEMENT` | :515 | `doc({ css: P08_CSS, alias: BIND_P08, badge:'数据', … })` |
四道 `editGate` + 两处 `addrow`(`parts.chip_type` / `parts.type_param`)。
只读态查类型人读名的写法(已验证可用):
`<span data-rows="parts.chip_type" data-by="type_id=chip_type_id"><i data-slot="type_name" data-blank></i></span>`
### 样式
`activities/part-substitution-assessment/src/views/target-style.ts`:
`P08_CSS`(:34,数据工单)· `ACC1_CSS`(:144,结论审核)· `ACC2_CSS`(:241,落账确认)。
**本次只换 `P08_CSS`**,另两段不动。
### 平台机制(会影响怎么写)
- `ViewContainer.tsx:548` —— `documentElement.classList.toggle('editable', editable)`;
**`editable` 只让闸现身**,开不开由人按闸,开了给那张表的宿主挂 `.on`(`table.on td>.jc-vcell{display:none}`)。
- **`bodyOf` 是单个 `querySelector`**(`ViewContainer.tsx:164-167`)——**一个地址只找得到一个宿主**。
这是终审 C1 与 Task 7b 的教训来源,**本次版面成立与否就卡在这一条**(见风险)。
- `addRow` 靠克隆 `tr:last-of-type`,**零行时返回 false**(Task 7b 的旧账,已由种子行绕开)。
- 件是**零脚本沙箱**;交互靠 CSS(`:has` / `<details>` / `:focus-within`)与外层容器。
### 当前版本与闸的名单
- `packageVersion: '2.1.9'`(`manifest.ts:57`)→ 本次改完升 **2.2.0**。
- S1 卡的 `gateLedgers`(`case-model.ts:592`):
`['parts.chip_type', 'parts.type_param', 'parts.profile', 'parts.parameter']` —— **四张,不要动**。
- 现有自测:`__tests__/activity.test.ts` 共 70 条 `it(`。
---
## 设计
### 版面
```
卡头(数据 badge · kick/h1/sub · 右侧 tag/step/deadline)
【触发原因】zone + note(流程 / AI 预填情况)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
原 PN 轨道 候选 PN 轨道
① 名称·在库·品牌·生命周期 ① 同左 ← 一道闸(parts.profile)
② 芯片类型 [下拉] ② 芯片类型 [下拉]
▸ 类型库里没有?在这里新建 / 补字段 ← 一道闸(chip_type + type_param)
类型名 + 逐行四格(参数名/说明/硬软约束/单位)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
③ 核心参数值:左右两组瓷贴,按料分 ← 一道闸(parts.parameter)
.flow 说明 + 页脚
```
**两条并列轨道怎么出来的**:`<div class="tracks" data-rows="mine:sub_eval.part" data-edit="parts.profile">`
里放 `<section class="track">…</section>`,`.tracks{display:grid;grid-template-columns:1fr 1fr}`。
`data-rows` 逐项复制内部结构,CSS 把它们排成两栏 —— 这是 A2 已验证的写法
(「`<article data-rows>` 包 `<section class="cand">`」逐颗料一张卡)。
**行容器的坑**:行展开按**第一个同名闭合标签**截行内容 —— 行模版里不许再出现同名标签。
用 `<section>` / `<tbody>` 这类行内不会重复的标签。
### 无脚本翻译
| 目标件(靠 `<script>`) | 平台等价物 |
|---|---|
| 选类型 → 即时加载参数输入框 | **保存即铺格**(配方按类型的字段清单铺出参数行,已实现且走查验过) |
| `+新增类型` 选中才显示面板 | **`<details>` 常驻折叠条**(A2 方法论条同款,零脚本) |
| `.stepbox.done` 点亮 | `data-class-of` 投影 + 样式表判断(**模版不许算、不许判**) |
| 进度条 `n / m 项` | 分母**必须是真数**:由配方算进 `sub_eval.case` 两列(如 `fill_done`/`fill_total`);<br>**算不出就只摆「还差 N 项」+ 二态条,不许编分母**(界面不撒谎) |
| `body.editing` 整页切 | 平台的 `editable` + 每卡 `.on` —— 用户选了「每步骤卡一道闸」 |
### 加的一道护栏(方案 1 的风险缓解)
两条轨道各选一次类型、而它们必须相同(`distinct = 1` 那条判据)。
基座本来就出 `sub_eval_part_distinct_chip_type_id` —— 把它投出来,
**两处选得不一样时件上直接出红带点名**,不必等提交才被判据拦。
用 `data-class-of` 投影 + 样式表出显隐(`v-1` 那档 `display:none`),**模版不算不判**。
---
## ⚠️ 要先验证再往下写的一条(这版面成立的前提)
步骤③ 的两组瓷贴要共用**一个** `data-edit="parts.parameter"` 宿主
——因为 **`bodyOf` 只找得到一个宿主**(终审 C1 / Task 7b 的教训:同一地址挂两处,
`querySelector` 只命中第一个,后面那组的值**一格都收不上去,而且不报错**)。
宿主内部靠**嵌套 `data-rows` + `data-by` 父键过滤**按料分栏。
**必须先钉住的是收值归行**:`cellsOf` 按宿主内所有 `[data-col]` 取格子,`rowOf` 按行容器归行。
嵌套之后 `rowOf` 认的是内层瓷贴组还是外层?**认错就会把两颗料的参数值混成一行**。
**做法**:先写一条测试跑真实的 `cellsOf`/`rowOf`(或经 `resolveRowKey` 走一遍),
**证明两颗料的瓷贴各归各行**。
- 成立 → 按上面的版面写;
- **不成立 → 退回「③ 单独一块、内部分两栏」的形状**(轨道只到 ②),并**告诉用户这一处与目标件的差别**。
顺带留意 `data-by` 的已知缺陷(终审 I3,已记 debt 未修):
**行内没有该键时会落回外层同名字段** —— `internal_pn` 在多张表上撞名,分栏过滤要用真有值的键。
---
## 动的文件
| 文件 | 改什么 |
|---|---|
| `src/views/target-style.ts` | `P08_CSS` 整段换成从新目标件**机械抽取**的样式(数值不改;`ACC1_CSS`/`ACC2_CSS` 不动) |
| `src/views/report.ts` | 重写 `INPUT_SUPPLEMENT` 与它的片段(`PART_TABLE`/`CHIP_TYPE_TABLE`/`TYPE_PARAM_TABLE`/`PARAMETER_TABLE`) |
| `src/manifest.ts` | `packageVersion` → `2.2.0` |
| `__tests__/activity.test.ts` | 断言跟着新版面走(**不许放松**) |
**不动**:平台(`ViewContainer` / `view-render` / `desk.ts`)· 配方(`case-model.ts`)· `gateLedgers` · 另两张件。
---
## 必须留着的东西(动了就是回归)
1. **`<input class="jc-cell mono" data-col="type_id" placeholder="由系统生成" disabled>`**(`report.ts:407` 一带)
—— 这是**终审 C1 的修复**:没有它,人新建的类型会被「全表一致」认成既有行、**把既有字典行改名并落进共享主数据**。
新版面里这一格换了位置也**必须带着走**,并保留「由系统生成」那句占位
(2026-08-02 立的规矩:自增列不问人,但**不能一声不吭地拿掉**)。
2. **主键列必挂 `data-col`** —— 它不是编辑面,是**行的钥匙**;不挂,服务端按「全表一致」猜,会把两行对到同一行上互相覆盖。
3. `CHIP_TYPE_TABLE` 那个「`data-edit` 外层 `<table>` 与内层 `<tbody>` 两处都写」的写法有原文注释解释原因
(`bodyOf` 要同时收到「已有行」与「加一行」出来的行)—— 换版面时**读那段注释再决定怎么摆**。
4. `gateLedgers` 四张表与件上的可编辑面**同源** —— 闸盯的表,件上必须给得出填的手段
(Task 7 那颗「按不下去的确认钮」就是这么来的)。
---
## 验收
**现有断言继续绿,一条都不许放松**:可改面只有哪几列 · 三份件用到的 class 必须都能在自己那段 CSS 里找到定义 ·
零脚本(`not.toContain('<script')`)· 主键挂 `data-col` · 编辑闸外壳那六个属性 · `gateLedgers` 与件同源。
**新增**:
- 两条轨道各出一颗料(`mine:sub_eval.part` 两行 → 两个 `.track`);
- **步骤③ 瓷贴按料归行正确**(上面那条风险的钉子);
- 类型不一致时红带现身(`distinct > 1` → `data-class-of` 投影命中)。
**每条新断言都要做变异验证**(拿掉修复/改坏一处 → 测试必须真红;复原 → 绿),两段输出贴进报告。
本轮已连吃三次「测试测的是副本」「变异测试本身是空的」「夹具替被测系统干了活」的亏。
**门禁**:`pnpm gates` **以退出码验,不许接管道**(`cmd | tail` 的退出码是 `tail` 的)。
**最后必须做的两步**:
1. `pnpm activities:install` 装包,**用 SQL 核库里那一行**(版本号 / 视图 HTML)—— 装完看库,不看脚本的话;
2. **浏览器走一遍**(起单 → 数据工单 → 新建类型 → 保存即铺格 → 填参数 → 提交)——
**版面对不对只有真看一眼才算数**;脚本 e2e 逮不到「一个图标 946 像素高」这类事。
跑试跑要用 AI 服务账号或用户授权的账号,跑完 `pnpm --filter @app/api reset-runs` 清运行数据。
---
## 环境备忘(压缩后会用到)
- 开发后端 `:8787`、前端 `:5173`;**稳定版 `:8790` 目前是停着的**(用户授权,大改期间不动它)。
- 起后端要先 `set -a; . .stable/env; set +a`,否则「缺 KEYCLOAK_URL…」起不来。
- **长跑的 dev 进程可能在跑几小时前的代码** —— 本轮踩过三次;`tsup --watch` 监视进程本身也可能已死,
`touch` 无效,要**完整重启** `pnpm dev:api`;杀进程要杀到 `--onSuccess` 拉起的那个 node(否则 `EADDRINUSE`)。
- 查库:`docker exec jcai-postgres psql -U jcai -d jcai_wos -c "…"`(`.stable/env` 里的 `DATABASE_URL` 指向 5432,
而 docker 映的是 54329,直接进容器最省事)。
- 浏览器登录走 Keycloak 用户名密码;**密码那一格我不能替用户输**,要他自己登。
---
## 上下文:这条分支此刻的状态
`feat/w05-rewrite`,HEAD `45cec82`,**工作树干净,`pnpm gates` 退出码 0(2476 测)**。
ACT-WORK-05 重写(Task 1–12 + 7b + 全分支终审 + 修复波 + 定向复审)**已全部完成**,
**未合并、未推送** —— 合并方式等用户定(选项已摆给他:本地合进 `mvp-foundation` / 推送开 PR / 先放着)。
`feat/deterministic-steps`(第一份计划)**已并在这条分支里**,合它即合两者。
逐任务账本与全部裁定:`.superpowers/sdd/2026-08-29-act-work-05-rewrite/progress.md`(用户尚未决定删留)。
**本次换版面是这之后的新活,与上面那条分支的收尾是两件事。**