From 51914292dc209f7471f62378fb7bb4010e128a58 Mon Sep 17 00:00:00 2001 From: xcs Date: Mon, 31 Aug 2026 16:41:14 +0800 Subject: [PATCH] =?UTF-8?q?feat(w05):=20=E6=95=B0=E6=8D=AE=E5=B7=A5?= =?UTF-8?q?=E5=8D=95=E7=AC=AC=E5=9B=9B=E8=BD=AE=E4=B9=9D=E8=BF=9E=E6=94=B9?= =?UTF-8?q?=20=E2=80=94=E2=80=94=20=E7=AC=94=E2=86=94=E8=BD=AF=E7=9B=98?= =?UTF-8?q?=E5=8D=A1=E8=A7=92=E7=BC=96=E8=BE=91=E3=80=81=E5=8F=AA=E9=80=89?= =?UTF-8?q?=E4=B8=8D=E5=BB=BA=E3=80=81=E6=B5=85=E8=93=9D=E9=A2=98=E5=A4=B4?= =?UTF-8?q?=E3=80=81=E9=95=9C=E5=83=8F=E4=BF=AE=E6=95=B0=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 2026-08-31 用户一次点齐九件,逐条落: ① 两行编辑闸退役 → 卡右上角笔(开当前这一面的编辑)↔ 软盘(保存)+ 叉(放弃); 按钮就是平台的 data-edit-open/save/cancel 触发器,快照/取消机制原样,按面显隐全在样式表。 ② 保存一瞬间下拉变数字 → ui-kit mirrorTwins:下拉镜像**选项文案**不镜 value,并把 selected 属性拨到人选的那一项(守卫 CSS 与取消回显都认它)。2 测。 ③ 参数输入框占位提示 → 渲染器 twinInputs 认 data-ph 落成 placeholder(平台小扩面,1 测); 值格「规格书上的值 · 查不到留空」,说明格「补充说明 · 选填」。 ④ 参数说明空着不再摆「建议」→ 什么都不摆(连兜底破折号一起收)。 ⑤ 区序对调:芯片档案在前,比对基准在后。 ⑥ **新建芯片类型整条路移除**(只能选已有类型):抽屉与两张编辑表(种子行/Critical-1 修法/thead 回归网)退役,S1 配方 ②③ 自投影删除(八笔→六笔),gateLedgers 收窄到 profile+parameter,writes 同步,gate_reason 与指导书改口「只选不建」;样例改名 「候选缺库·建档定型」;e2e 案例 0 改走选既有 type_1(type_id=90 那套绕坑法随路退役)。 退役面的七条钉测按仓例 it.skip 留墓志铭(钉的是已移除的路径,不是放松断言)。 ⑦ 题头回浅蓝渐变(原目标件那条 120°),深色夜景只留验收两件。 ⑧ 对话区执行卡不再摆「参考信息」板块(basis 数据照旧随卡走,详情/归档仍有)。 ⑨ 比对基准降噪:字段收成丸(名+硬软徽+单位),说明藏进悬浮气泡指上即看; 类型说明一行截断、悬浮展开。 测:ui-kit 33(mirror 2 新)+ 渲染器 61(ph 1 新)+ 包内 104(38 steps 重排)+ 2525 全仓 四门绿;2.10.0 装库 SQL 六探针全中(hd-light/软盘/data-ph/零表格/无 chip_type 编辑面/ gateLedgers 两张);:8787 已重启载入 data-ph 渲染器。 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet --- .../__tests__/activity.test.ts | 163 +++++++++--------- .../__tests__/steps.test.ts | 40 ++--- .../src/case-model.ts | 109 ++---------- .../src/manifest.ts | 4 +- .../src/views/report.ts | 161 ++++------------- .../src/views/style.ts | 48 +++++- .../interaction/__tests__/view-render.test.ts | 14 ++ .../internal/domain/view-render.ts | 4 +- .../desk-task/internal/chat-bubbles.tsx | 3 +- .../view-container-multihost.test.ts | 30 +++- packages/ui-kit/src/ViewContainer.tsx | 15 +- tools/e2e-a2.mjs | 57 +----- 12 files changed, 259 insertions(+), 389 deletions(-) diff --git a/activities/part-substitution-assessment/__tests__/activity.test.ts b/activities/part-substitution-assessment/__tests__/activity.test.ts index 9de3950..8d1ec51 100644 --- a/activities/part-substitution-assessment/__tests__/activity.test.ts +++ b/activities/part-substitution-assessment/__tests__/activity.test.ts @@ -450,7 +450,7 @@ describe('报告模版:编辑面与只读面各就各位', () => { it('每个可编辑格子的 data-col 都在对应台账对象的声明里(input-supplement 一屏四张表,四张台账对象的声明并起来看;difference-review 一张表)', () => { const cases: Array<[string, Array<[string, string]>]> = [ - ['input-supplement', [['parts', 'profile'], ['parts', 'chip_type'], ['parts', 'type_param'], ['parts', 'parameter']]], + ['input-supplement', [['parts', 'profile'], ['parts', 'parameter']]], /* 件② 2026-08-31 起多一张:收口四格可就地改,写向 substitution.relation */ ['difference-review', [['substitution', 'difference_item'], ['substitution', 'relation']]], ]; @@ -548,12 +548,12 @@ describe('件① 评估输入补全', () => { expect(steps.find((s) => s.item === 'S1_inputs')?.view).toBe('input-supplement'); }); - it('四块都在,行都绑本单草稿(不是整表);档案卡行源是 parts.profile 本身', () => { + it('三块都在,行都绑本单草稿(不是整表);档案卡行源是 parts.profile 本身(类型字典只读整表,不再有 mine: 镜像)', () => { expect(html).toContain('data-rows="mine:sub_eval.part"'); expect(html).toContain('data-rows="mine:parts.profile"'); expect(html).toContain('data-rows="mine:parts.parameter"'); - expect(html).toContain('data-rows="mine:parts.chip_type"'); - expect(html).toContain('data-rows="mine:parts.type_param"'); + expect(html).not.toContain('data-rows="mine:parts.chip_type"'); + expect(html).not.toContain('data-rows="mine:parts.type_param"'); }); /* @@ -597,16 +597,14 @@ describe('件① 评估输入补全', () => { /* 全只读:选类型在档案卡,建类型在抽屉 —— 基准卡上一个编辑面都不许有 */ expect(basis).not.toContain('data-col'); expect(basis).not.toContain('data-edit'); - /* 基准卡不再有表格:类型 + 字段全是瓷贴 */ + /* 基准卡不再有表格:类型 + 字段全是瓷贴;说明藏进悬浮气泡(降噪,2026-08-31) */ expect(basis).not.toContain(''); - const labEnd = html.indexOf('', labAt); - expect(labAt).toBeGreaterThanOrEqual(0); - expect(html.indexOf('')).toBeGreaterThan(labAt); - expect(html.indexOf('
')).toBeGreaterThan(labAt); - expect(labEnd).toBeGreaterThan(html.indexOf('
')); + expect(html).toContain('.ftile:hover .ft-desc'); + /* 新建类型抽屉已随「只选不建」退役(2026-08-31 用户定)—— 只剩说明折叠那一只 details */ + expect(html).not.toContain('
'); + expect(html).not.toContain('data-edit="parts.chip_type"'); + expect(html).not.toContain('data-edit="parts.type_param"'); }); /* @@ -631,34 +629,28 @@ describe('件① 评估输入补全', () => { expect(html).toContain('点击翻面'); }); - it('主画面无表格:说明折叠之前的正文一张
{ - const beforeDrawer = html.slice(0, html.indexOf('
')); - expect(beforeDrawer).not.toContain(' { + expect(html).not.toContain(' { + it('占位口径:正面必填红/建议橘;卡背值必填红,说明空着**什么都不摆**(2026-08-31 用户:「没写就什么都不要展示」)', () => { expect(html).toContain('.fcell.req .jc-vcell:empty::after{content:"必填"'); expect(html).toContain('.fcell.sug .jc-vcell:empty::after{content:"建议"'); expect(html).toContain('.ptile .pv .jc-vcell:empty::after{content:"必填"'); - expect(html).toContain('.ptile .sg .jc-vcell:empty::after{content:"建议"'); + expect(html).toContain('.ptile .sg .jc-vcell:empty{display:none}'); + expect(html).not.toContain('.ptile .sg .jc-vcell:empty::after'); }); - it('芯片类型是从台账现读的下拉 —— 写死等于「只能选出包那天就有的那几种」(块①的 chip_type_id 与块②b 的 type_id 都是)', () => { + it('芯片类型是从台账现读的下拉 —— 写死等于「只能选出包那天就有的那几种」', () => { expect(html).toContain('data-options="parts.chip_type"'); expect(html).toContain('data-option-value="type_id"'); expect(html).toContain('data-option-label="type_name"'); /* data-options 与 data-slot 不能共存(渲染时 data-slot 会被剥掉)—— 当下值由 data-col 定 */ expect(/data-options="[^"]*"[^>]*data-slot=/.test(html)).toBe(false); - /* 块①(sub_eval.part 行模版,只有一份行模版,被 data-rows 迭代)绑 chip_type_id 下拉 */ + /* 档案卡正面绑 chip_type_id 下拉 —— 只选不建(2026-08-31 用户定),type_id 的写入下拉已随抽屉退役 */ expect(html).toContain('data-col="chip_type_id" required data-options="parts.chip_type"'); - /* - * 块②b(type_param)绑 type_id 下拉的地方**该出现两次**——已有行的行模版(data-rows - * 迭代)一份、种子行(字面量静态行,不受行数所限)一份;只数子串存在会被「只剩其中一份」 - * 这种退化蒙混过去(已用变异验证逮到:只把动态行模版那份换回裸文本输入,这条子串断言 - * 仍然为真,因为种子行那份还在——按出现次数数才钉得住两处都在)。 - */ - const typeIdSelectCount = (html.match(/data-col="type_id" required data-options="parts\.chip_type"/g) ?? []).length; - expect(typeIdSelectCount).toBe(2); + expect(html).not.toContain('data-col="type_id"'); }); /* @@ -667,18 +659,14 @@ describe('件① 评估输入补全', () => { * (查证见 Task 7b 报告)。补一条**不受 `data-rows` 管辖的静态种子行**——两张表各一条, * 字面量手写 `data-col` 输入格,不经 `cell()`/`data-slot`,行数再少也在。 */ - it('**零行也加得出第一行**:两张表各带一条静态种子行(class="jc-row-static"),不靠 data-rows 生成', () => { - const staticRows = html.match(/
/g) ?? []; - expect(staticRows).toHaveLength(2); - /* 种子行里的输入格是手写的,不经 twinInputs —— 直接断言 data-col 存在即可 */ - expect(html).toContain(''); - expect(html).toContain(''); - expect(html).toContain(''); - expect(html).toContain(' { + expect(true).toBe(true); }); /* @@ -696,17 +684,14 @@ describe('件① 评估输入补全', () => { * 钉住修法:`type_id` 必须挂 `data-col`,且必须是禁用的 —— 人看得见「由系统生成」 * 这句占位,但怎么点都改不了它的值。 */ - it('**新建类型的种子行:type_id 挂 data-col、但禁用恒空**(2026-08-30 修 Critical 1 —— 漏挂会让 resolveRowKey 的"全表一致"把新建的类型误判成既有那一行,静默覆盖它)', () => { - expect(html).toContain('由系统生成'); - // 「由系统生成」只出现在 chip_type 那条种子行里 —— 精确抓出那一行 - const chipTypeStaticRow = /(?:(?!<\/tr>)[\s\S])*?由系统生成(?:(?!<\/tr>)[\s\S])*?<\/tr>/.exec(html)?.[0] ?? ''; - expect(chipTypeStaticRow).not.toBe(''); - /* 必须是一个真的 (collect() 只认 input/select/textarea[data-col]), - 不能是纯文本 —— 否则这一格永远不会出现在回传的 cells 里,barred 的保护无从谈起 */ - expect(chipTypeStaticRow).toMatch(/]*data-col="type_id"[^>]*disabled/); - /* disabled 意味着人怎么点都改不了它的值 —— 保存时这一格恒是空串,才谈得上「barred」 */ - expect(chipTypeStaticRow).toContain('data-col="type_name"'); - expect(chipTypeStaticRow).toContain('data-col="description"'); + /* + * SKIP(2026-08-31 用户定「把添加芯片类型的功能从这个活动中移除,只能选择已有类型」): + * 新建类型抽屉连同两张编辑表(静态种子行 / type_id 禁用 data-col 的 Critical-1 修法 / + * thead 后置回归网 / data-edit 双写)整体退役 —— 这些钉的是已移除的创建路径,不是被放松的 + * 断言。平台侧机制钉测(view-render-addrow-thead / view-table-edit)不随本活动退役。 + */ + it.skip('**新建类型的种子行:type_id 挂 data-col、但禁用恒空**(2026-08-30 修 Critical 1 —— 漏挂会让 resolveRowKey 的"全表一致"把新建的类型误判成既有那一行,静默覆盖它)', () => { + expect(true).toBe(true); }); it('**主键列必挂 `data-col`** —— 它不是编辑面,是行的钥匙(漏了会把人改的行错配到别行上)', () => { @@ -714,10 +699,28 @@ describe('件① 评估输入补全', () => { expect(html).toContain('data-col="param_name"'); }); - it('可改的三张表 + 档案卡各有一道编辑闸', () => { - for (const address of ['parts.profile', 'parts.chip_type', 'parts.type_param', 'parts.parameter']) { - expect(html).toContain(`data-edit="${address}"`); + /* + * 2026-08-31 用户定:两行编辑闸退役,换成卡右上角的笔↔软盘 —— 笔开当前这一面的编辑 + * (正面=主数据 / 背面=参数值),软盘保存,叉放弃。按钮就是平台认的 data-edit-open / + * data-edit-save / data-edit-cancel 触发器,快照与取消机制原样。 + */ + it('编辑收进卡角:笔(open)/软盘(save)/叉(cancel)正反两面各一套;地址只剩 profile 与 parameter', () => { + for (const address of ['parts.profile', 'parts.parameter']) { + expect(html).toContain(`data-edit-open="${address}"`); + expect(html).toContain(`data-edit-save="${address}"`); + expect(html).toContain(`data-edit-cancel="${address}"`); } + /* 老的闸行不再出现在正文里(样式表里的基座规则给 ② 用,不算) */ + expect(html.slice(html.indexOf(''))).not.toContain('jc-editgate'); + /* 笔↔软盘的按面显隐由样式表判 */ + expect(html).toContain('.dossiers.on .disk-f{display:grid}'); + expect(html).toContain(".dossier:has(.pback.on) .disk-b{display:grid}"); + /* 参数瓷贴的输入孪生件带占位提示(渲染器认 data-ph) */ + expect(html).toContain('data-ph="规格书上的值 · 查不到留空"'); + expect(html).toContain('data-ph="补充说明 · 选填"'); + /* 题头回浅蓝渐变(2026-08-31 用户:「标题背景还是改成之前的那种蓝色渐变」) */ + expect(html).toContain('class="card hd-light"'); + expect(html).toContain('linear-gradient(120deg,#f0f5ff 0%,#fff 55%,#eef4ff 100%)'); }); /* @@ -727,11 +730,14 @@ describe('件① 评估输入补全', () => { * 内层 `data-rows` 那个 `` 一处(给 `renderRows` 的 `editable` 检测看, * 它只认与 `data-rows` 同一个标签自带的属性,不看祖先)。 */ - it('新建类型 / 新建字段两张表的 data-edit 各出现两次(外层 table + 内层 data-rows tbody)', () => { - for (const address of ['parts.chip_type', 'parts.type_param']) { - const count = (html.match(new RegExp(`data-edit="${address}"`, 'g')) ?? []).length; - expect(count, address).toBe(2); - } + /* + * SKIP(2026-08-31 用户定「把添加芯片类型的功能从这个活动中移除,只能选择已有类型」): + * 新建类型抽屉连同两张编辑表(静态种子行 / type_id 禁用 data-col 的 Critical-1 修法 / + * thead 后置回归网 / data-edit 双写)整体退役 —— 这些钉的是已移除的创建路径,不是被放松的 + * 断言。平台侧机制钉测(view-render-addrow-thead / view-table-edit)不随本活动退役。 + */ + it.skip('新建类型 / 新建字段两张表的 data-edit 各出现两次(外层 table + 内层 data-rows tbody)', () => { + expect(true).toBe(true); }); /* @@ -758,20 +764,14 @@ describe('件① 评估输入补全', () => { * 谁若把 `` 挪回两个 `` 之前(哪怕只改一张表),这条测试立刻标红, * 提醒去核对 `view-render-addrow-thead.test.ts` 是否也需要同步。 */ - it('**回归安全网**:两张新表的 都排在两个 之后(不是最前面)—— 与 view-render-addrow-thead.test.ts 的假设锁在一起', () => { - for (const address of ['parts.chip_type', 'parts.type_param']) { - const tableStart = html.indexOf(`
`); - expect(tableStart, address).toBeGreaterThanOrEqual(0); - const tableEnd = html.indexOf('
', tableStart); - const tableHtml = html.slice(tableStart, tableEnd); - const theadAt = tableHtml.indexOf(''); - const tbodyPositions = [...tableHtml.matchAll(/ m.index ?? -1); - expect(theadAt, `${address}: 找不到 `).toBeGreaterThanOrEqual(0); - expect(tbodyPositions.length, `${address}: 应有两个 `).toBe(2); - // 必须排在两个 的开标签**之后**(源码序;视觉呈现由 CSS 的 - // table-header-group 保证仍在最前,与源码序无关) - for (const pos of tbodyPositions) expect(theadAt, address).toBeGreaterThan(pos); - } + /* + * SKIP(2026-08-31 用户定「把添加芯片类型的功能从这个活动中移除,只能选择已有类型」): + * 新建类型抽屉连同两张编辑表(静态种子行 / type_id 禁用 data-col 的 Critical-1 修法 / + * thead 后置回归网 / data-edit 双写)整体退役 —— 这些钉的是已移除的创建路径,不是被放松的 + * 断言。平台侧机制钉测(view-render-addrow-thead / view-table-edit)不随本活动退役。 + */ + it.skip('**回归安全网**:两张新表的 都排在两个 之后(不是最前面)—— 与 view-render-addrow-thead.test.ts 的假设锁在一起', () => { + expect(true).toBe(true); }); /* @@ -799,14 +799,14 @@ describe('件① 评估输入补全', () => { * 只读态与编辑态下「#」列逐行都是空白。钉死 `.jc-tbl-wrap` 现在也接了这五条规则, * 且这张件确实只用 `.jc-tbl-wrap`(没有表在用没打这条补丁就会数不出来的 `.tbl-wrap`)。 */ - it('抽屉里两张表的序号列真的会数:.jc-tbl-wrap 也接了 CSS 计数器(不再只认 .tbl-wrap)', () => { - expect(html).toMatch(/\.jc-tbl-wrap\s+table\s*,\s*\.tbl-wrap\s+table\s*\{\s*counter-reset:\s*rn;?\s*\}/); - expect(html).toMatch(/\.jc-tbl-wrap\s+table\s+td\.rn\s*,\s*\.tbl-wrap\s+table\s+td\.rn\s*\{\s*counter-increment:\s*rn;?\s*\}/); - expect(html).toMatch(/\.jc-tbl-wrap\s+table\s+td\.rn\s+\.jc-rn-num::before\s*,\s*\.tbl-wrap\s+table\s+td\.rn\s+\.jc-rn-num::before\s*\{\s*content:\s*counter\(rn\);?\s*\}/); - // 只剩抽屉里两张(2026-08-31 第三轮:基准卡瓷贴化、参数值上卡背 —— 主画面零表格) - expect(html).not.toContain('class="tbl-wrap"'); - const jcTblWrapCount = html.match(/class="jc-tbl-wrap"/g)?.length ?? 0; - expect(jcTblWrapCount).toBe(2); + /* + * SKIP(2026-08-31 用户定「把添加芯片类型的功能从这个活动中移除,只能选择已有类型」): + * 新建类型抽屉连同两张编辑表(静态种子行 / type_id 禁用 data-col 的 Critical-1 修法 / + * thead 后置回归网 / data-edit 双写)整体退役 —— 这些钉的是已移除的创建路径,不是被放松的 + * 断言。平台侧机制钉测(view-render-addrow-thead / view-table-edit)不随本活动退役。 + */ + it.skip('抽屉里两张表的序号列真的会数:.jc-tbl-wrap 也接了 CSS 计数器(不再只认 .tbl-wrap)', () => { + expect(true).toBe(true); }); }); @@ -1068,10 +1068,9 @@ describe.skip('徽标别名与值域相关的坑(2026-08-27 实测账,回归网) */ describe('可编辑件的主键与必填列(2026-08-27 复审:主键丢钥匙 / 必填填不了,两条 Critical 回归;Task 7b 补 parts.chip_type)', () => { /** input-supplement 一屏四张真能编辑的表与它们各自绑定的台账对象(Task 7b 加入 parts.chip_type) */ + /* 2026-08-31「只选不建」:chip_type / type_param 不再是可编辑面,从名单撤下 */ const EDITABLE: ReadonlyArray<{ view: string; domain: string; object: string }> = [ { view: 'input-supplement', domain: 'parts', object: 'profile' }, - { view: 'input-supplement', domain: 'parts', object: 'chip_type' }, - { view: 'input-supplement', domain: 'parts', object: 'type_param' }, { view: 'input-supplement', domain: 'parts', object: 'parameter' }, ]; diff --git a/activities/part-substitution-assessment/__tests__/steps.test.ts b/activities/part-substitution-assessment/__tests__/steps.test.ts index b463145..efe70da 100644 --- a/activities/part-substitution-assessment/__tests__/steps.test.ts +++ b/activities/part-substitution-assessment/__tests__/steps.test.ts @@ -146,10 +146,10 @@ describe('S1 配方', () => { * 变成空字符串)见 `apps/api/src/modules/loop-engine/__tests__/w05-gate-reason-order.test.ts` * ——这份文件只做结构断言,不跑引擎(活动包不许 import apps/*)。 */ - it('八笔,次序是:两颗料 → 类型字典自投 → 字段清单自投 → 主数据骨架 → 参数骨架 → 案卷 → 卡 → 交代', () => { - expect(kinds).toEqual(['project', 'project', 'project', 'project', 'project', 'project', 'card', 'say']); + it('六笔,次序是:两颗料 → 主数据骨架 → 参数骨架 → 案卷 → 卡 → 交代(类型字典/字段清单两笔自投影已随「只选不建」退役)', () => { + expect(kinds).toEqual(['project', 'project', 'project', 'project', 'card', 'say']); expect(acts.map((a) => (a.kind === 'project' ? a.into : undefined)).filter(Boolean)).toEqual([ - 'sub_eval.part', 'parts.chip_type', 'parts.type_param', 'parts.profile', 'parts.parameter', 'sub_eval.case', + 'sub_eval.part', 'parts.profile', 'parts.parameter', 'sub_eval.case', ]); }); @@ -165,21 +165,13 @@ describe('S1 配方', () => { * 跳过已落地的行(loop-engine/port.ts),prune 回来:没落地的陈旧选型照撤, * 已落地的原地留下 —— 案例 2 不再炸,用户那条路清得干净。 */ - it('②③ 两笔自投影都读 sub_eval.part 的 chip_type_id 去查字典/字段清单,原样抄回自己(带 prune,已落地由基座跳过)', () => { - const chipType = acts[1]; - expect(chipType?.kind === 'project' && chipType.into).toBe('parts.chip_type'); - expect(chipType?.kind === 'project' && chipType.prune).toBe(true); - expect(chipType?.kind === 'project' && chipType.from).toMatchObject({ - each: 'mine:sub_eval.part', - then: { each: 'parts.chip_type', where: { type_id: { outer: 'chip_type_id' } } }, - }); - const typeParam = acts[2]; - expect(typeParam?.kind === 'project' && typeParam.into).toBe('parts.type_param'); - expect(typeParam?.kind === 'project' && typeParam.prune).toBe(true); - expect(typeParam?.kind === 'project' && typeParam.from).toMatchObject({ - each: 'mine:sub_eval.part', - then: { each: 'parts.type_param', where: { type_id: { outer: 'chip_type_id' } } }, - }); + /* + * SKIP(2026-08-31 用户定「只选不建」):②③ 两笔自投影连同 gateLedgers 上那两张表 + * 一并退役 —— 类型只许从字典里选,选中的行本来就在正式表里,镜像与落地都失去了对象。 + * 这条钉的机制(prune + skipLanded)仍活在 auto-runner 测试里,不随本活动退役。 + */ + it.skip('②③ 两笔自投影都读 sub_eval.part 的 chip_type_id 去查字典/字段清单,原样抄回自己(带 prune,已落地由基座跳过)', () => { + expect(true).toBe(true); }); it('案号由 let 铸,只从本单开案时刻取材', () => { @@ -203,7 +195,7 @@ describe('S1 配方', () => { * 「加一行」),必须换成说实情、给真出路的话:没法比出差异,终止重开。 */ it('gate_reason 第一档:两参必填之后只剩"填成同一个号"这条残余路径,文案不再指向不存在的"补两格"', () => { - const kase = acts[5]; + const kase = acts[3]; const fields = kase?.kind === 'project' ? kase.fields : undefined; const gateReason = fields?.['gate_reason'] as { when?: string; then?: string } | undefined; expect(gateReason?.when).toBe('sub_eval_part_rows < 2'); @@ -213,7 +205,7 @@ describe('S1 配方', () => { }); it('参数骨架:两颗料都铺、值回填现值 —— 「在库但当年参数没录全」也要被拦住', () => { - const params = acts[4]; + const params = acts[2]; /* 'live':人加不了行的表,quiet 拍也撤 —— 改类型后旧参数行跟手消失(2026-08-31 用户点名「只会变多不会变少」) */ expect(params?.kind === 'project' && params.prune).toBe('live'); const from = params?.kind === 'project' ? params.from : undefined; @@ -224,7 +216,7 @@ describe('S1 配方', () => { }); it('**发卡的条件全是基座读数** —— 不许拿 `gate_reason != ""` 当条件', () => { - const card = acts[6]; + const card = acts[4]; const when = card?.kind === 'card' ? (card.when ?? '') : ''; /* * S-FEEL 里**未绑定的名字 `!= ""` 求值为 true**,而 `gate_reason` 为空串时那一列压根不写、 @@ -245,16 +237,16 @@ describe('S1 配方', () => { * (2026-08-30 真跑 e2e 撞见)。现在四张表都在闸上、也都会一起落地。 */ it('卡:数据工单、四张表都在闸上(含 parts.chip_type/parts.type_param,Ruling H35)、且有 basis(基座拒发 basis 为空的工单)', () => { - const card = acts[6]; + const card = acts[4]; expect(card?.kind === 'card' && card.as).toBe('execution'); expect(card?.kind === 'card' && card.variant).toBe('data'); - expect(card?.kind === 'card' && card.gateLedgers).toEqual(['parts.chip_type', 'parts.type_param', 'parts.profile', 'parts.parameter']); + expect(card?.kind === 'card' && card.gateLedgers).toEqual(['parts.profile', 'parts.parameter']); expect((card?.kind === 'card' ? card.basis ?? [] : []).length).toBeGreaterThan(0); expect((card?.kind === 'card' ? card.guide ?? [] : []).length).toBeGreaterThanOrEqual(3); }); it('发卡条件与 doneWhen 是同一件事的正反面', () => { - const when = acts[6]?.kind === 'card' ? (acts[6].when ?? '') : ''; + const when = acts[4]?.kind === 'card' ? (acts[4].when ?? '') : ''; for (const reading of [ 'sub_eval_part_exists_status_found', 'sub_eval_part_missing', 'sub_eval_part_distinct_chip_type_id', 'parts_profile_missing', 'parts_parameter_missing', diff --git a/activities/part-substitution-assessment/src/case-model.ts b/activities/part-substitution-assessment/src/case-model.ts index c76d0e9..658f38e 100644 --- a/activities/part-substitution-assessment/src/case-model.ts +++ b/activities/part-substitution-assessment/src/case-model.ts @@ -339,7 +339,7 @@ export const ASSESSMENT_STEPS = [ 与 auto.do 第 ⑥ 笔 `card` 的 `gateLedgers` 同源(parts.chip_type / parts.type_param / parts.profile / parts.parameter —— 四张表都在闸上,理由见那笔 `card` 定义处的注释)。 */ view: 'input-supplement', - writes: ['sub_eval.part', 'sub_eval.case', 'parts.chip_type', 'parts.type_param', 'parts.profile', 'parts.parameter'], + writes: ['sub_eval.part', 'sub_eval.case', 'parts.profile', 'parts.parameter'], reads: ['originalInternalPn', 'candidateInternalPn', 'case:openedAt', 'parts.profile', 'parts.parameter', 'parts.chip_type', 'parts.type_param'], tools: [], doneWhen: @@ -378,82 +378,12 @@ export const ASSESSMENT_STEPS = [ }, }, /* - * ② **选中的那一行类型字典,原样投一遍**(2026-08-30,Ruling H35 —— 真跑 e2e 撞见的 - * 平台侧连带问题:「数据工单确认即入库」按 `gateLedgers` 整表并入正式表,而 - * `parts.chip_type` 从 Task 7 起就没列在 `gateLedgers` 里 —— 人一旦真的新建一个类型, - * `parts.profile`/`parts.type_param` 引用着它一起落地,**字典行本身**却只留在这一单 - * 草稿里、从没落地过。下一个任务读到同一颗料,查这个 `chip_type_id` 时字典里空空如也, - * `refersTo` 当场拒——是一条悬空引用,不是「查无此值」那种输入错误)。 - * - * **这一笔不看「新建还是已有」,统统投一遍**(不选型的选也一样、新建的选也一样): - * 读的是 `sub_eval.part` 每一行当下的 `chip_type_id`(空串时下面这层 `each` 天然查 - * 不到、不产生任何行 —— 人还没选类型的那颗不会被提前铺出假行),按它去 - * `parts.chip_type` 里找到那一行、原样把 `type_name`/`description` 抄回自己身上。 - * 值没变,`draftWrite` 同 PK 覆盖静默跳过(幂等,不产生多余的写入痕迹)—— - * 但**这一单的草稿层从此真的有这一行**,`mine:parts.chip_type` 不再天然为空。 - * - * 这条把 `parts.chip_type` 重新放回下面 ⑥ 的 `gateLedgers` 铺平了路:闸不会再拿 - * 「零行」的旧理由误拦(那个前提不成立了),而选中的类型(新建的也好、已有的也好) - * 会随「数据工单确认入库」一起落进正式表 —— 不再留悬空引用。 + * ~~② 类型字典自投影 / ③ 字段清单自投影~~(2026-08-31 用户定「本活动只选类型、 + * 不新增类型」后退役):这两笔当年(Ruling H35)是为「人新建的类型/字段要随数据 + * 工单确认一起落地、且 mine: 不为空好让闸不误拦」而生 —— 类型只许从字典里选之后, + * 选中的类型本来就在正式表里,镜像与落地都失去了对象;`gateLedgers` 同批收窄到 + * `parts.profile` / `parts.parameter` 两张真被人编辑的表。历史见 git(2.9.0 及更早)。 */ - { - kind: 'project' as const, - into: 'parts.chip_type', - /* - * **带 `prune`,已落地的行由基座跳过**(2026-08-31 改回)。这一笔曾特意不带 - * `prune`(2026-08-30 e2e 案例 2:类型行随「数据工单确认即入库」落地后,人改了 - * 类型选择,`prune` 想撤已落地的旧行,`draftDrop` 硬拒、配方整单炸)—— 当时的 - * 代价是**没落地的**陈旧行也跟着不清:人在件上把候选从 LDO 改回 DC-DC,LDO 的 - * 字典行与字段行积在屏上,「下面还是显示两个类型」(2026-08-31 用户实测点名)。 - * 病根在撤行机制一刀切,不在 prune 本身 —— 现在 `draftDrop` 对配方这一路带 - * `skipLanded`(见 loop-engine/port.ts):已落地的行原地留下(它已是世界的一部分, - * 字典行本就该积累),**没落地的陈旧选型照撤** —— 案例 2 那条路不再炸, - * 用户点名的那条路清得干净。撤行只在非 quiet 拍跑(人正编辑时不动他的行,F3)。 - */ - prune: true, - from: { - each: 'mine:sub_eval.part', - then: { each: 'parts.chip_type', where: { type_id: { outer: 'chip_type_id' } } }, - }, - fields: { - type_id: { col: 'type_id' }, - type_name: { col: 'type_name' }, - description: { col: 'description' }, - }, - }, - /* - * ③ **选中类型的核心参数字段清单,同一套道理原样投一遍**(2026-08-30,与 ② 同一次 - * 发现、同一个技法):`parts.type_param` 只有人在件上「新建类型时加字段」才会写它 —— - * 挑一个已有类型时,这张表天然一行都没有,而它此刻仍在 ⑥ 的 `gateLedgers` 上, - * 「零行 = 没干完」的闸对这条路一样是误判(2026-08-30 真跑 e2e 五案例实测坐实: - * 「归到已有类型」的候选缺库 / 类型不一致 / 原PN缺库 / 两颗都缺库四例,全部卡死在 - * 同一条「芯片类型核心参数字段 这一单还一行都没有」)。 - * - * 修法与 ② 完全同构:按当下选中的 `chip_type_id` 把那个类型的核心参数字段清单 - * 整套原样抄回自己身上(值没变,幂等,不产生多余写入)——**新建的类型也走这条路**: - * 人在件上新增的字段行本就已经在 `mine:parts.type_param` 里,这一笔重新投一遍 - * 原样命中、照样幂等跳过,不会覆盖人刚填的内容。 - * - * **同样带 `prune`(已落地的由基座 `skipLanded` 跳过)**—— 与 ② 同一次改回、 - * 同一个理由,见 ② 头上的大注释:改选类型后,旧类型**没落地**的字段行照撤, - * 已随数据工单确认落地的原地留下,H35 案例 2 的死锁不再触发。 - */ - { - kind: 'project' as const, - into: 'parts.type_param', - prune: true, - from: { - each: 'mine:sub_eval.part', - then: { each: 'parts.type_param', where: { type_id: { outer: 'chip_type_id' } } }, - }, - fields: { - type_id: { col: 'type_id' }, - param_name: { col: 'param_name' }, - param_desc: { col: 'param_desc' }, - constraint_type: { col: 'constraint_type' }, - param_unit: { col: 'param_unit' }, - }, - }, /* * ④ 主数据骨架:**两颗料都铺、值回填现值**。 * 在库那颗回填出它此刻的样子(人不必重填);要建那颗全空等人填。 @@ -566,7 +496,7 @@ export const ASSESSMENT_STEPS = [ then: '有料号库里查不到 —— 在下面把它的主数据建起来。', else: { when: 'sub_eval_part_missing > 0', - then: '有料号还没归芯片类型 —— 在下面给它选一个;没有合适的就新建一个,并把它的核心参数字段一并定下来。', + then: '有料号还没归芯片类型 —— 在档案卡上给它选一个;字典里没有合适的,先请数据管理员补进类型字典(本活动只选不建)。', else: { when: 'sub_eval_part_distinct_chip_type_id > 1', then: '两颗料不是同一个芯片类型,逐项比对无从谈起 —— 换一颗料,或者把归错的那颗类型改对。', @@ -601,29 +531,18 @@ export const ASSESSMENT_STEPS = [ ], guide: [ '两颗料必须归到**同一个芯片类型**下 —— 要比的核心参数字段由类型定,类型不同就没有可比的字段。', - '类型下拉里没有合适的,就新建一个,并把它的**核心参数字段**一并定下来:每个字段要有名字、一句说明、以及硬约束还是软约束。', - '说明写「这个参数为什么要紧」——**它是后面判定差异时唯一的解释来源**,写成参数名的复述等于没写。', - '硬约束 = 不满足就不能用(烧芯片、超温域、装不上);软约束 = 影响表现但可权衡(功耗、噪声)。', + '类型**只从字典里选,本活动不新增类型**(2026-08-31 用户定)—— 字典里没有合适的,先请数据管理员补进字典,再回来选。', + '硬约束 = 不满足就不能用(烧芯片、超温域、装不上);软约束 = 影响表现但可权衡(功耗、噪声)—— 字段的硬软在字典上定死,件上「比对基准」卡可查。', '参数值填**原厂规格书上的值**,查不到就留空 —— 留空会被红带点名,但**编一个值比留空坏得多**。', ], standard: '红带消失、提交钮变绿:两颗料都在库、归了同一个类型、每个核心参数字段都有值。', /* - * **`parts.chip_type` 与 `parts.type_param` 都在闸上**(2026-08-30 改,Ruling H35)—— - * 从前 `parts.chip_type` 被永久排除在外,理由是「它没有编辑手段,`mine:` 天然为空, - * 列了就永久卡死」;那条理由**从 ②③ 两笔自我投影加上之后就不再成立**—— - * `mine:parts.chip_type`/`mine:parts.type_param` 现在恒不为空(只要 `sub_eval.part` - * 里已经选定了类型),闸不会再误判「一行都没有」。 - * - * **既然要落地就必须在名单里,这不是碰运气**:「数据工单确认即入库」把 `gateLedgers` - * 列出的表整批并入正式表;`parts.chip_type` 若不在这份名单里,人新建的类型字典行 - * 就只留在草稿层,而引用它的 `parts.profile`/`parts.type_param` 却随名单一起落地 —— - * 落地那一刻起,正式表里就有一行指着字典里查无此号的悬空引用(2026-08-30 真跑 - * e2e 撞见的平台侧连带发现:范围放行没有对 `refersTo` 做传递闭包,并一张表却不 - * 并它引用的表,必然留下悬空引用;那条更普遍的平台缺陷已记 `docs/debt.md`, - * 这里只在活动自己的声明层堵住这一处具体触发点)。四张表都在闸上、也都会一起 - * 落地,才是「选类型」这件事完整、闭环的样子。 + * **闸只盯人真编辑的两张表**(2026-08-31 随「只选不建」收窄):从前 `parts.chip_type`/ + * `parts.type_param` 也在名单上 —— 那是给「人新建的类型要随确认一起落地」铺的路 + * (Ruling H35,史见 git)。类型只许从字典里选之后,字典行本来就在正式表里, + * 落地无对象、mine: 恒为空 —— 留在名单上反而会被「零行 = 没干完」的闸误拦死。 */ - gateLedgers: ['parts.chip_type', 'parts.type_param', 'parts.profile', 'parts.parameter'], + gateLedgers: ['parts.profile', 'parts.parameter'], }, { kind: 'say' as const, text: '已核对两颗料的在库状态与参数齐备情况。' }, ], diff --git a/activities/part-substitution-assessment/src/manifest.ts b/activities/part-substitution-assessment/src/manifest.ts index b98fa94..62833ee 100644 --- a/activities/part-substitution-assessment/src/manifest.ts +++ b/activities/part-substitution-assessment/src/manifest.ts @@ -54,7 +54,7 @@ export const manifest: ActivityManifest = { activityId: ACTIVITY_ID, name: 'ACT-WORK-05 · 差异评估', /* 重写不是改 —— 同版重装是 no-op(静默丢失,踩过) */ - packageVersion: '2.9.0', + packageVersion: '2.10.0', category: '需求分析与方案匹配', sdkVersion: '0.1.0-m5', checksum: 'dev-checksum-part-substitution-assessment', @@ -568,7 +568,7 @@ export const manifest: ActivityManifest = { * LM2596S-3.3 刻意不在库。 */ samples: [ - { name: '候选缺库·新建类型', input: { originalInternalPn: 'LM2596S-5.0', candidateInternalPn: 'XL2596S-5.0' }, expect: { ports: ['assessment-released'] } }, + { name: '候选缺库·建档定型', input: { originalInternalPn: 'LM2596S-5.0', candidateInternalPn: 'XL2596S-5.0' }, expect: { ports: ['assessment-released'] } }, { name: '都在库·零卡直分析', input: { originalInternalPn: 'LM2596S-5.0', candidateInternalPn: 'LM2596S-ADJ' }, expect: { ports: ['assessment-released'] } }, { name: '类型不一致·硬拦', input: { originalInternalPn: 'LM2596S-5.0', candidateInternalPn: 'AMS1117-3.3' }, expect: { ports: ['assessment-released'] } }, { name: '原PN缺库', input: { originalInternalPn: 'LM2596S-3.3', candidateInternalPn: 'LM2596S-ADJ' }, expect: { ports: ['assessment-released'] } }, diff --git a/activities/part-substitution-assessment/src/views/report.ts b/activities/part-substitution-assessment/src/views/report.ts index c7fb846..495edd6 100644 --- a/activities/part-substitution-assessment/src/views/report.ts +++ b/activities/part-substitution-assessment/src/views/report.ts @@ -70,16 +70,12 @@ const editGate = (address: string, label: string): string => `
保存
`; const gapbar = (object: string, what: string): string => `
- 还差 行${what}没填 —— 。按「编辑」打开,带 * 的必填格填上,补齐了才确认得了。 + 还差 行${what}没填 —— 。打开编辑,带 * 的必填格填上,补齐了才确认得了。
`; -const addrow = (address: string, label: string, hint: string): string => `
- - ${hint} -
`; -/** 可改格:只读那一份留空,渲染器自动配孪生 ``(手写 input 会不填值,人一点编辑就是白框) */ -const cell = (col: string, cls = ''): string => ``; -/** 只读格:即便挂在可改的表里也不许被 `table.on` 藏起来 */ -const ro = (col: string, cls = ''): string => ``; +/** 可改格:只读那一份留空,渲染器自动配孪生 ``(手写 input 会不填值,人一点编辑就是白框)。 + * `ph` 落成孪生件的 placeholder(渲染器认 `data-ph`,2026-08-31)—— 短短一句说清这格填什么。 */ +const cell = (col: string, cls = '', ph = ''): string => + ``; const select = (col: string, options: ReadonlyArray, required = false): string => ``; /** @@ -111,9 +107,9 @@ const gateBar = (): string => `
* 收件人 / 关联任务两格由引擎注入;工单编号即本次评估案号 `eval_ref`(页脚)。 * `stage` 是每张件自己的 hero 场景(SVG 舞台),`accent` 决定这张件的强调色。 */ -const doc = (input: { accent: string; alias: string; tag: string; kicker: string; title: string; step: string; stage: string; body: string; footTag: string }): string => +const doc = (input: { accent: string; alias: string; tag: string; kicker: string; title: string; step: string; stage: string; body: string; footTag: string; headTone?: 'light' }): string => ` -
+
${input.kicker} @@ -130,6 +126,14 @@ const doc = (input: { accent: string; alias: string; tag: string; kicker: string
`; +/** 卡角编辑钮的三枚图标:笔(开编辑)/ 软盘(保存)/ 叉(放弃)—— 2026-08-31 用户定的笔↔软盘交互 */ +const PEN_SVG = + ''; +const DISK_SVG = + ''; +const X_SVG = + ''; + /* ==================== 局面:评估输入补全(input-supplement,S1 的数据工单) ==================== */ /* @@ -191,6 +195,14 @@ ${badgeCss('.rl', [ const DOSSIERS = `
点击翻面 · 核心参数点击翻回 · 主数据 + + + + + + + +
@@ -218,8 +230,8 @@ const DOSSIERS = `
核心参数值
${cell('param_name')} - ${cell('param_value', 'mono')} - ${cell('param_note')} + ${cell('param_value', 'mono', '规格书上的值 · 查不到留空')} + ${cell('param_note', '', '补充说明 · 选填')} ${cell('internal_pn', 'mono')}
@@ -254,102 +266,13 @@ const BASIS_CARD = `
`; -/** - * 块②a 新建芯片类型 —— `parts.chip_type`。类型下拉(块①)里没有合适的,人在这里现造一个。 - * `type_id` 是自增主键 —— **人不填它**,自增列不问人;已有行只读态摆 `ro()`。 - * - * **种子行里 `type_id` 必须挂一个真实的(禁用、恒空)`data-col`,不能干脆不给 —— - * 这是 2026-08-30 终审 Critical 1 的病灶与修法**:第一版把这一格写成纯文本 ``, - * 不带 `data-col` —— 保存时 `collect()` 只认 `input[data-col]/select[data-col]/ - * textarea[data-col]`,`type_id` 压根不出现在回传的 `cells` 里;而 `desk.ts` 的 - * `resolveRowKey` 用 `k in cells` 判「这一列人碰过没有」,漏挂在它眼里等同「这张表没有 - * 这一列」,专为「auto 列件上摆着却空着」设计的 `barred` 保护从未触发,退回「全表一致」: - * `parts.chip_type` 恰好一行时,人新建的类型被当场判成「就是那唯一一行」,一次保存就把 - * 种子字典行**静默改名** —— `autoKeys()` 续号器永远轮不到。修法:真的 - * ``(值恒空,只显示「由系统生成」占位)。 - * 回归钉在 `__tests__/activity.test.ts` 与 `apps/api/src/app/__tests__/view-table-edit.test.ts`。 - * - * **零行照样加得出第一行**(这一块存在的全部理由):`mine:parts.chip_type` 每单开局是 - * 零行,`ViewContainer.addRow` 靠克隆 `tr:last-of-type` 添新行,零行时无样可克隆 —— - * 多一个不受 `data-rows` 管辖的静态 ``,字面量摆一行输入格(不经 `cell()`), - * 它不是数据行,永远在;填了按保存,`writeEditedRows` 认得出「主键全空 + 别的列有值」 - * (`mintable`),转给 `autoKeys()` 续号。 - * - * `data-edit="parts.chip_type"` **两处都写** —— 外层 `` 一处(给 - * `ViewContainer.collect()` 与 `.on` 切换看,要同时收到已有行与种子行两处的输入,宿主 - * 必须是两者共同的祖先),内层 `data-rows` 那个 `` 一处(`renderRows` 判「这组行 - * 可编辑吗」只认与 `data-rows` 同一个标签自带的属性)。 - * - * **`` 排在两个 `` 之后 —— 评审逮到的 Critical 回归修法,不是随手换的顺序** - * (2026-08-30):`bodyOf()` 解析到外层 `
` 后,`addRow()` 在这个 host 里找 - * `tr:last-of-type` —— `:last-of-type` 按各自父元素判定,`` 里那个 `` 必然 - * 「最后」,若 `` 排前面会被文档序优先命中:克隆出来的是表头文字。CSS 的 - * `table-header-group` 排版规则保证 `` 视觉上永远在最前,不看 DOM 序 —— 呈现 - * 一字不变,而选择器认文档序。证据与变异验证见 - * `apps/api/src/modules/interaction/__tests__/view-render-addrow-thead.test.ts`。 +/* + * ~~块②a 新建芯片类型 / 块②b 核心参数字段清单~~(2026-08-31 用户定:「把添加芯片类型的 + * 功能从这个活动中移除,不能在这个活动中新增芯片类型,只能选择已有的芯片类型」)。 + * 两张编辑表(静态种子行、加删行、thead 后置、type_id 禁用 data-col 那整套 Critical-1 + * 修法)与「新建类型」抽屉一并退役 —— 类型字典只在治理侧维护,本活动只读它。 + * 机制史与钉测见 git(2.9.0 及更早)与 view-render-addrow-thead.test.ts(平台侧仍在)。 */ -const CHIP_TYPE_TABLE = `
- - - - - - - - - - - - - - - - - - -
${ro('type_id', 'mono')}${cell('type_name')}${cell('description')}
#编号类型名称类型说明
`; - -/** - * 块②b 芯片类型的核心参数字段清单 —— `parts.type_param`,复合主键 `type_id + param_name`。 - * 新建的类型在这里把核心参数字段一条条定下来(名字 / 说明 / 硬软约束 / 单位)。 - * - * `type_id` 是与块①同款的 `optionSelect`(选项从 `parts.chip_type` 现读,人挑的是 - * **类型名称**):块②a 新建的类型一旦保存,这份下拉立刻带上它 —— 不必谁记住那个号。 - * 静态种子行与块②a 同一套手法;这张表是唯一带「+加一行」/删行的一张,零行时那颗按钮 - * 同样够不到岸,种子行同样补。 - * - * **`` 排在两个 `` 之后,同 `CHIP_TYPE_TABLE` 那条大注释** —— 这张表更 - * 要紧:它的「加一行」在外层 `data-edit` 挂上之前本来是好的,是一次真回归。 - */ -const TYPE_PARAM_TABLE = `
- - - - - - - - - - - - - - - - - - - - - - - - -
- - ${optionSelect('type_id', 'parts.chip_type', 'type_id', 'type_name', true)} - ${cell('param_name')}${cell('param_desc')}${badge('ct', 'constraint_type', true)}${select('constraint_type', [['hard', '硬约束'], ['soft', '软约束']], true)}${cell('param_unit')}
${optionSelect('type_id', 'parts.chip_type', 'type_id', 'type_name', true)}
#所属类型参数名参数说明约束(硬/软)参考单位
`; export const INPUT_SUPPLEMENT = doc({ accent: ACCENT_DATA, @@ -359,6 +282,7 @@ export const INPUT_SUPPLEMENT = doc({ title: '差异评估的输入还差几格', step: '步骤 1/4 · 校验入参与补数据', stage: '', + headTone: 'light', footTag: '工单类型:数据', body: gateBar() + @@ -367,28 +291,15 @@ export const INPUT_SUPPLEMENT = doc({ '模板定位:本单是「数据工单」——目标台账 = 芯片主数据(P-08 主记录 + 标准化参数子表),核心参数字段清单挂在芯片类型字典上。', '触发原因:下面电路板上两张档案卡就是本次要比对的原 PN 与候选 PN —— 至少有一颗不在库、两颗归的类型不一致、或必填格还空着,顶部那条红带才会出现。', '已绑定:两颗料的内部料号已按启动参数铺上档案卡,不必你重填料号本身,只需核对 / 补齐其余各格;在库的料现值已回填,只是查漏补缺。', - '纪律:两颗料必须归到同一个芯片类型下才谈得上逐项比对;类型下拉里没有合适的就在下面的抽屉里新建一个,并把它的核心参数字段一并定下来;查不到的参数值留空(会被红带点名),不要编一个值顶替。', + '纪律:两颗料必须归到同一个芯片类型下才谈得上逐项比对;类型只从字典里选,本活动不新增类型 —— 字典里没有合适的,先请数据管理员补进字典;查不到的参数值留空(会被红带点名),不要编一个值顶替。', ]) + `` + - zone('比对基准', '芯片类型 + 它的核心参数字段 —— 两颗料归到哪个类型,就按这些字段逐项比;档案卡上改选,这张卡当场跟着换', ZIC_TABLE) + - BASIS_CARD + - zone('芯片档案', '一颗芯片一张卡:正面主数据、背面核心参数值 —— 点卡翻面看另一面', ZIC_TABLE) + - editGate('parts.profile', '正面主数据核对 / 补齐 —— 开编辑后卡停在正面') + - editGate('parts.parameter', '背面参数值核对 / 补齐 —— 开编辑后两张卡自动翻到背面') + + zone('芯片档案', '一颗芯片一张卡:正面主数据、背面核心参数值 —— 点卡翻面看另一面;右上角的笔 = 编辑当前这一面,软盘 = 保存', ZIC_TABLE) + gapbar('parts_profile', '必填') + gapbar('parts_parameter', '必填') + DOSSIERS + - gapbar('parts_chip_type', '必填') + - gapbar('parts_type_param', '必填') + - `
类型库里没有合适的?展开新建一个类型,并把它的核心参数字段定下来 —— 定完回到上面的档案卡挑它
` + - editGate('parts.chip_type', '新建一个类型:表尾那一行填类型名称与说明,保存后立刻出现在档案卡的下拉里') + - CHIP_TYPE_TABLE + - addrow('parts.chip_type', '+ 再建一个类型', '两颗料若都要新建、且类型不同,先各自保存,「加一行」能再建一个') + - `

这个类型逐条比对时要看哪些参数 —— 名字 / 说明 / 硬软约束(*为必填列)

` + - editGate('parts.type_param', '字段名字 / 说明 / 硬软约束,点「编辑」核对;表尾那一行 / 「加一行」补字段') + - TYPE_PARAM_TABLE + - addrow('parts.type_param', '+ 加一行(补一个核心参数字段)', '所属类型 / 参数名 / 参数说明 / 约束类型必须写;参考单位选填') + - `
` + + zone('比对基准', '芯片类型 + 它的核心参数字段 —— 两颗料归到哪个类型,就按这些字段逐项比;字段说明悬浮即看', ZIC_TABLE) + + BASIS_CARD + flow('提交 / 确认:本工单只承载内容;确认请在对话区的【执行卡】中进行。确认后活动自动从「等待数据」唤醒。'), }); diff --git a/activities/part-substitution-assessment/src/views/style.ts b/activities/part-substitution-assessment/src/views/style.ts index 670c701..bc34a52 100644 --- a/activities/part-substitution-assessment/src/views/style.ts +++ b/activities/part-substitution-assessment/src/views/style.ts @@ -110,6 +110,15 @@ i{font-style:normal} .seal-sum{flex:1 1 240px;min-width:200px;max-width:420px;margin:0;font-size:13px;line-height:1.85;color:var(--hero-dim)} .seal-sum:empty::after{content:"—"} +/* ── 浅色题头变体(件① 2026-08-31 用户:「标题背景还是改成之前的那种蓝色渐变」—— + 渐变取自原目标件 card-head 那一条,深色电路夜景只留给验收两件)── */ +.hd-light .card-head{background:linear-gradient(120deg,#f0f5ff 0%,#fff 55%,#eef4ff 100%);color:var(--ink);border-bottom:2px solid var(--ink)} +.hd-light .hero-sys{color:var(--ink-4)} +.hd-light .hero-title{color:var(--ink)} +.hd-light .hero-step{color:var(--ink-3);border-color:var(--line)} +.hd-light .hero-meta{color:var(--ink-3)} +.hd-light .hero-meta b{color:var(--ink)} + /* ================= 正文分区 ================= */ .card-bd{padding:20px 22px 8px} .sec{display:flex;align-items:baseline;gap:9px;margin:26px 0 10px;flex-wrap:wrap} @@ -324,6 +333,23 @@ table.on tr.jc-row-static td{background:var(--acc-wash);border-top:1px dashed va .dossiers:not(.on) .dossier:hover:has(.flip-tg:checked):not(:has(.pback.on)) .flip-in{transform:rotateY(168deg)} } +/* ── 卡角编辑钮:笔=编辑当前这一面,软盘=保存,叉=放弃(2026-08-31 用户定,顶掉两行编辑闸)── + 笔只在待处理态露面(html.editable,与从前的闸同一条纪律);软盘/叉只在对应面的编辑态露面。 */ +.fabs{position:absolute;top:10px;right:10px;z-index:5;display:flex;gap:6px} +.fab{display:none;width:32px;height:32px;border-radius:999px;border:1px solid var(--acc-line);background:#fff;color:var(--acc-deep);cursor:pointer;place-items:center;padding:0;box-shadow:0 4px 12px rgba(18,38,63,.14);transition:border-color .16s,color .16s,box-shadow .16s} +.fab:hover{border-color:var(--acc);box-shadow:0 6px 16px rgba(18,38,63,.22)} +.fab:active{transform:scale(.94)} +.fic{width:15px;height:15px;display:block;margin:auto} +.fab.pen{color:var(--acc-deep)} +.fab.disk{background:var(--acc);border-color:var(--acc);color:#fff} +.fab.xbtn{color:var(--ink-3)} +html.editable .dossiers:not(.on) .dossier:not(:has(.pback.on)):not(:has(.flip-tg:checked)) .pen-f{display:grid} +html.editable .dossiers:not(.on) .dossier:has(.flip-tg:checked):not(:has(.pback.on)) .pen-b{display:grid} +.dossiers.on .disk-f{display:grid} +.dossiers.on .x-f{display:grid} +.dossier:has(.pback.on) .disk-b{display:grid} +.dossier:has(.pback.on) .x-b{display:grid} + /* ── 卡背:参数瓷贴 ── */ .pb-hd{display:flex;align-items:baseline;gap:9px;flex-wrap:wrap} .pb-hd b{font-size:14px;font-weight:800;color:var(--ink)} @@ -337,7 +363,8 @@ table.on tr.jc-row-static td{background:var(--acc-wash);border-top:1px dashed va .pt-note .jc-vcell{font-size:10.5px;color:var(--ink-3)} /* 三级占位:值必填红 / 说明建议橘 */ .ptile .pv .jc-vcell:empty::after{content:"必填";font-size:10.5px;font-weight:800;color:var(--danger);background:var(--danger-soft);border-radius:999px;padding:0 9px} -.ptile .sg .jc-vcell:empty::after{content:"建议";font-size:10px;font-weight:800;color:var(--amber);background:var(--amber-soft);border-radius:999px;padding:0 8px} +/* 说明空着**什么都不摆**(2026-08-31 用户:「没写就什么都不要展示」)—— 连兜底破折号一起收 */ +.ptile .sg .jc-vcell:empty{display:none} /* 参数编辑态:值与说明换输入,参数名照旧当标签摆着;两枚主键孪生件藏死(钥匙不是编辑面)。 藏钥匙用包装类(.pt-name / .rowkey),不用「data-col 属性选择器」—— 那段选择器文本会原样 出现在共享样式表里,把「落账确认全只读」那类字面断言踩翻(2026-08-31 踩过,连注释也不许写)。 */ @@ -359,13 +386,19 @@ table.on tr.jc-row-static td{background:var(--acc-wash);border-top:1px dashed va .basis-hd .csvg{width:56px;flex:none} .basis-id{display:flex;flex-direction:column;gap:5px;min-width:0;flex:1 1 auto} .basis-in:has(.ftiles:empty) .ftiles{display:none} -.ftiles{display:grid;grid-template-columns:1fr 1fr;gap:8px} -.ftile{display:flex;flex-direction:column;gap:3px;border:1px solid var(--line-soft);border-radius:10px;background:#fff;padding:9px 11px;min-width:0} -.ft-hd{display:flex;align-items:baseline;gap:7px;flex-wrap:wrap} -.ft-hd b{font-size:12.5px;font-weight:800;color:var(--ink)} +/* 降噪(2026-08-31 用户:「乱糟糟…没必要显示全就不要显示完全,鼠标指上去后再显示」): + 字段收成一排丸(名 + 硬/软徽 + 单位),说明藏进悬浮气泡 —— 指上去才现身 */ +.ftiles{display:flex;flex-wrap:wrap;gap:8px} +.ftile{position:relative;display:flex;flex-direction:row;align-items:center;gap:7px;border:1px solid var(--line-soft);border-radius:999px;background:#fff;padding:6px 13px;min-width:0;transition:border-color .16s,box-shadow .16s} +.ftile:hover{border-color:var(--acc-line);box-shadow:0 4px 12px rgba(18,38,63,.1)} +.ft-hd{display:flex;align-items:baseline;gap:7px;white-space:nowrap} +.ft-hd b{font-size:12px;font-weight:800;color:var(--ink)} .ft-unit{font-style:normal;font-size:10px;color:var(--ink-4);font-family:var(--mono)} -.ft-desc{margin:0;font-size:11px;line-height:1.6;color:var(--ink-3)} -.ft-desc:empty{display:none} +.ft-desc{display:none;position:absolute;z-index:6;top:calc(100% + 7px);left:0;width:max-content;max-width:280px;margin:0;font-size:11px;line-height:1.65;color:var(--ink-2);background:#fff;border:1px solid var(--line);border-radius:9px;padding:8px 11px;box-shadow:0 10px 24px rgba(18,38,63,.16)} +.ftile:hover .ft-desc:not(:empty){display:block} +/* 类型说明:默认一行截断,悬浮题头才放开 */ +.basis-desc{display:block;max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} +.basis-hd:hover .basis-desc{white-space:normal} /* ================= 新建类型工作抽屉(details,零脚本)================= */ .lab{margin:0 0 6px;border:1.5px dashed var(--line);border-radius:12px;background:var(--soft)} @@ -377,7 +410,6 @@ table.on tr.jc-row-static td{background:var(--acc-wash);border-top:1px dashed va .lab-caret{margin-left:auto;flex:none;width:8px;height:8px;border-right:2px solid var(--ink-4);border-bottom:2px solid var(--ink-4);transform:rotate(45deg);transition:transform .16s} .lab[open] .lab-caret{transform:rotate(-135deg)} .lab-bd{padding:2px 14px 14px} -.lab-sub{margin:16px 0 8px;font-size:11.5px;color:var(--ink-3)} .lab-brief{margin:0 0 16px} .lab-brief .brief{margin:2px 14px 14px;border-left-width:3px} diff --git a/apps/api/src/modules/interaction/__tests__/view-render.test.ts b/apps/api/src/modules/interaction/__tests__/view-render.test.ts index a937a8c..f338772 100644 --- a/apps/api/src/modules/interaction/__tests__/view-render.test.ts +++ b/apps/api/src/modules/interaction/__tests__/view-render.test.ts @@ -542,3 +542,17 @@ describe('data-options:下拉的选项从台账现读', () => { expect(out).toContain(''); }); }); + +describe('孪生件占位提示:data-ph 落成 input 的 placeholder(2026-08-31,W05 参数瓷贴)', () => { + it('带 data-ph 的可改格,孪生 input 有 placeholder;不带的没有', () => { + const html = + '

' + + '' + + '' + + '

'; + const out = renderView(html, { 'mine:parts.parameter': [{ param_value: '', param_note: '' }] }); + expect(out).toContain('placeholder="规格书上的值,查不到留空"'); + const notePart = out.slice(out.indexOf('data-col="param_note"')); + expect(notePart.slice(0, notePart.indexOf('/>'))).not.toContain('placeholder='); + }); +}); diff --git a/apps/api/src/modules/interaction/internal/domain/view-render.ts b/apps/api/src/modules/interaction/internal/domain/view-render.ts index 4a0087e..08069a5 100644 --- a/apps/api/src/modules/interaction/internal/domain/view-render.ts +++ b/apps/api/src/modules/interaction/internal/domain/view-render.ts @@ -263,7 +263,9 @@ function twinInputs(inner: string, rowIndex: number): string { .split(/\s+/) .filter((c) => c === 'mono' || c === 'num') .join(' '); - const input = ``; + /* `data-ph`:模版给这一格的输入孪生件配一句占位提示(2026-08-31,W05 参数瓷贴要「短短一句说清这格填什么」) */ + const ph = /\bdata-ph\s*=\s*"([^"]*)"/.exec(attrs)?.[1]; + const input = ``; // 只读那一份把 `data-col` 交出去 —— 它的活已经干完(告诉这里是哪一列),留着只是噪音 return `${all.replace(/\s*\bdata-col\s*=\s*"[^"]*"/, '')}${input}`; }); diff --git a/apps/web/src/features/desk-task/internal/chat-bubbles.tsx b/apps/web/src/features/desk-task/internal/chat-bubbles.tsx index a7e1d43..6da1dee 100644 --- a/apps/web/src/features/desk-task/internal/chat-bubbles.tsx +++ b/apps/web/src/features/desk-task/internal/chat-bubbles.tsx @@ -1051,7 +1051,8 @@ function ExecForm({ order, onReplyOrder, onLocate, orderValues, onField, face =

{order.ask}

- {order.basis.length > 0 && } + {/* 参考信息不再上卡(2026-08-31 用户定:「派发的工单参考信息这个板块不要显示」)—— + basis 数据照旧随卡走(工单详情 / 归档件仍有),只是对话区这张卡不摆 */} {order.fields.map((f) => (