给「任务活动开发 agent」沉淀的两份操作手册,基准是用户验收过的 A1(quote-intake@42, 三条形状不同的案例 released、被拒 0)。 活动开发规范.md 第三版,按四条轴重组: · 解耦机制 —— 活动与系统之间只有安装通道;三份声明=三个授权面; 边界纪律「别替下游产出」(随令牌流向下游的只有验收过的成果); · Harness 契约 —— 台账三写一撤与字段四标记(required/expect/tally/auto)、 基座读数全表与机检纪律(三次违反史逐条记账)、一路的门与闸 (起单宽进/提交闸两端/提交门/放行门哨兵/熔断)、拒回话术三分、五类卡、 phases/三色日志/一轮一句、放行落地; · Loop —— 局面组成、行动十四型全表、串纪律、人工激活与 start (「话说的范围要盖住门拦的范围」)、doneWhen 自动收步; · 活动怎么写 —— 清单六区(ledgers/editableAtRelease/判据措辞/内部一致性不上卡)、 建模十纪律(哨兵 ifPart、阶段全覆盖补进)、剧本 steps 六字段与 doneWhen 三坑、 guide 写作十条(一单一卡、卡骨架照抄、纯文本)、三常备案例与验收口径。 排障速查表并入这一轮全部新账(14 行新增)。 HTML报告生成规范.md 初版(用户点名单独成册,并强调报告与表单的关系): · 一份 HTML 三个身份:件(看)/表单(改)/归档件(存)—— 同一份,分开必分叉; · 报告与卡的关系立成硬规矩:逐行数据归件、整单级归卡;编辑在件、提交在卡; 同一个数管三处(件上红带/卡上闸/放行门 L1 同源);保存链路与三条实测教训; · 绑定语言全表、不算不判(按值上色/按条数分宽/按数开关)、编辑态四件套 (闸/孪生件/增删行/缺项红带)、PRD 平移法、浏览器走查清单、陷阱表。 CLAUDE.md 文档权威地图指向两份新版。五道门禁全绿(975 passed)。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JsTDjoHovERXyy6Wmw1ALp
14 KiB
HTML 报告生成规范(活动视图模版)
2026-08-02 初版,从《活动开发规范》拆出成册。基准:
activities/quote-intake@42的三张模版 (intake-overview/intake-classified/intake-release)—— 用户验收通过的那套。 渲染引擎:apps/api/src/modules/interaction/internal/domain/view-render.ts; 容器:packages/ui-kit/src/ViewContainer.tsx;保存链路:apps/api/src/app/routes/desk.ts。 本文与代码冲突时以代码为准。
1. 报告是什么:同一份 HTML 的三个身份
| 身份 | 在哪 | 状态 |
|---|---|---|
| 件(给人看) | 工作区 · 待处理 | 锁定态 —— 进屏先是一份能读的报告,不是一张待填的表 |
| 表单(给人改) | 工作区 · 待处理,按「编辑」后 | 可改格子现出输入件,能增删行 |
| 归档件(存档) | 工作区 · 工作成果 | 同一份 HTML 原样定稿,零输入框零外壳 |
为什么必须是同一份:渲成两三套就是存两三份,存多份就会分叉,分叉之后没人看得出 以哪份为准。看与改是两件事(整份件一进屏就是编辑态,人还没决定改什么, 眼前已经是 54 个亮着的输入框 —— 踩过),但看的与改的必须是同一份东西。
三条硬规矩:
- 一单一模版贯穿始终:同一个局面,在办与归档用同一张;局面变了换张 (A1 三张:受理需求 → 料号分类 → 受理记录),不在一张里堆条件分支。
- 剧本每步的
view定推哪张,写清单时定死,不归模型临场挑;放行那一步的view必须指向验收目标件 —— 最后摆上去的那张就是归档件。 - 数据变了基座重渲(repush):人在件上保存、在卡上回填,落草稿层后服务端拿 新数据重渲同一张 —— 由数据算出来的东西(类徽、分布条、去向)跟着一起变。 手改的全部意义就是纠正 AI 的错,改了数而画面不动,这个兜底功能就是假的(用户原话级教训)。
2. 报告与卡(表单)的关系 —— 这套里最容易做错的一章
三个区各归各位(2026-08-01 用户定,别再混):
| 区 | 放什么 | 不放什么 |
|---|---|---|
| 工作区 · 待处理 | 只放那份件(活动的 HTML 模版),整篇铺满工作区宽度、可就地改 | 题头 / 编号带 / 依据带 / 判据表 / 指导书 / 提交钮 —— 一样都不要 |
| 工作区 · 工作成果 | 同一份件,定稿归档 | 同上 |
| 对话区的卡 | 作业指导书(steps/standard)· 依据 · 判据 · 整单级表单字段 · 提交与裁决 · 落地结果 | 件本身(给一枚「执行工单/验收工单」四字钮跳过去就够) |
分工的原则:逐行数据归件,整单级归卡;编辑在件,提交在卡。
- 一张 9 行的清单,人要逐格核、逐格改 —— 那是件的事(报告即表单);
- 「结果说明」这种整单一格的字段、「先受理还是退回」这种裁决 —— 那是卡的事;
- 卡上那枚提交钮是唯一的交卷动作;件上的保存只落草稿层,工单不办结、 放行门的位置不动。
同一个数管三处(同源纪律):台账声明 required 产出的 域_对象_missing——
件上的缺项红带(gapBar) ←┐
卡上的提交闸(gate) ←┼── 同一个基座读数、同一套算法
放行门的 L1 机检 ←┘
三处各算各的,就会出现「件上说齐了、卡上说不能交、放行门又说行」—— 那种矛盾人是判不出对错的。所以模版与卡都不算这个数,只摆基座算好的。
保存链路(件 → 库 → 屏):
人按「保存」→ 前端按件自己说的话收值(data-edit 地址 + data-col 列名 + drops)
→ POST /tasks/:id/table {tables, drops}
→ 服务端按【活动清单的台账声明】过授权门,归型,只写真改了的行,
自增主键补号,draftDrop 撤显式点名的行
→ 留一条人痕(改了几行/新增几行/撤掉哪几行 —— 那条痕是下一拍模型的行动指南)
→ repush 重渲 → 闸、判据、类徽、分布条、去向全部跟着变
三条实测教训,每条都别再犯:
- 授权面是台账声明,不是「当下那张卡长什么样」 —— 从前保存借道工单
blocks[i]取列名,熔断卡(没有 blocks)开着时,人补的那一格一个字都送不出去, 而他要补的恰恰是把这一单卡住的必填格; - 删要说出来(载荷
drops点名主键),不靠「没回传就是删了」—— 那种口径会把 一次不完整的回传变成一次删表; - 只写真改了的行 —— 件上按一次保存整张表都回传,照单全写会留「修订 4 行」的痕, 而人只改了一格;日志是给人看的,那句话就成了假的。
卡侧还有一条老路(与件侧并存,别混):卡面 blocks 里绑了 ledger+fields 的表,
由基座铺行(发卡那一刻从草稿层铺,活动别自己抄行 —— 回复有长度上限,铺长了截断
整拍作废)、人提交时由基座写回(table:N 载荷)。验收卡的 editableAtRelease
成果表走的就是它。两条路的授权面是同一份台账声明。
3. 沙箱与安全
- 容器:
sandbox="allow-same-origin"的 iframe,永不给allow-scripts(两者同给才是破沙箱)。父文档读得到也改得动里面的 DOM:开编辑 = 加 class、 收值 = 读 input,全在父文档侧;iframe 里始终一行脚本都没有。 - 模版含
<script或外链,安装期即拒。样式全部自带(内联<style>)。 - ⚠️ 跨 realm 陷阱:父文档里
event.target instanceof Element判 iframe 元素 恒为 false(Element 是父文档的构造器)。症状:点「编辑」毫无反应而 class/CSS/ 选择器逐个查都对。认能力不认血统(有closest就当元素);监听挂载写成幂等的, 每次有机会就挂一遍(srcDoc 换文档的时机抓不准)。 - 内容更新就地换
body.innerHTML,不换 srcDoc —— srcDoc 一变整个文档重载, 高度掉回兜底再涨回来,人刚按下保存件就「闪黑」一下。
4. 模版语言参考(绑定全表)
渲染 = 纯取值绑定。基座三遍扫描:槽位填值 → 输入件填 value → 独立 class 投影。
| 属性 | 写在哪 | 语义 |
|---|---|---|
data-slot="键" |
任意元素 | 取值填为文本。数值整数原样、小数截 4 位;数组给长度 |
data-rows="mine:域.对象" |
行容器(tbody 等) | 按本单草稿行逐行复制内部结构;行内 data-slot 相对该行取值;序号交 CSS 计数器 |
data-blank |
与 data-slot 同元素 |
空值真空(不写「无」)—— 空格子的胶囊、占位交给 CSS :empty::after |
data-class-of="键" |
任意元素 | 把值消毒成 v-<小写值> 追加进 class(数值就是 v-0/v-1/…)。判断留在样式表 |
data-col="台账列名" |
可改格子(与 data-slot 同元素) |
渲染时多渲一个孪生 <input class="jc-cell">;写的是台账列名不是表头文字(表头改说法不该让写回错位) |
data-edit="域.对象" |
可改表的行容器 | 这组行可就地改;保存按它认地址 |
data-value |
<input> |
值进 value 属性(孪生件用,模版一般不手写) |
取数约定:mine:域.对象 = 本单草稿行(成果表只用它 —— 别单放行过的记录不混进来);
裸键 = 摊平字段 + 基座读数(域_对象_rows / _missing / _missing_keys /
_<列>_<值>…);case:openedAt / case:origin / case:owner / case:routeKey / routePort / routeTo / routeWhy = 引擎注入的案情与算出的去向。
语言纪律:报告上不摆系统内英文名 —— 状态名、表名、列名、口名一律人读名; 数值列与表头同侧左对齐(右对齐是给要竖着相加的数用的;一行一个料、彼此不相加的表, 右对齐只让数和自己的标题分家),等宽字体留着。
5. 模版不许算,但基座算过的可以摆
两件事别混:
- 报告自己算(把判据抄成静态文字、拿宽度骗人、JS 拼 class)—— 不行。 抄本与判据是两份东西,改了一处另一处就一直指着旧规则。
- 摆基座算好的那一次 —— 该做,而且要做足。三个惯用法:
- 按值上色:
data-class-of="type_tag"→.v-a/.v-b/.v-c三色徽; - 按条数分宽(分布条):
data-class-of="quote_intake_record_type_tag_a"→ 样式表备一把v-1…v-20的宽度梯子,flex按档取;零条那段自己消失; - 按数开关(缺项红带):
data-class-of="quote_intake_record_missing"→v-0那档display:none——「一格不缺就别出这条带」这个判断在样式表里, 模版仍只做取值。
- 按值上色:
- 去向那行:基座按契约各出口
when序求值后注入case:route*(与放行发口是 同一次判定),报告只摆;算不出来键就是空的,一行都不高亮,不冒充判过。 - 搬不过来的如实空着:编数据是撒谎,画空轨像组件坏了 —— 那一格不画, 把「为什么没有」写在卡脚。
6. 编辑态(报告即表单)
默认锁着,按「编辑」才开。 每张可改的表配三样(A1 report.ts 有现成实现):
-
闸(
editGate):[data-edit-for="域.对象"]+ 三钮data-edit-open / -cancel / -save。锁着只出「编辑」;开了换「取消/保存」; 闸只在这一屏可改时现身(根节点editable),归档件里一个字都没有。 -
孪生输入件:挂了
data-col的格子渲染时自动配一个<input>, 只读那份与输入那份由样式表按表上的on二选一 —— 同一份 HTML 两态复用。 编辑态下必填空格红框(:placeholder-shown)、整行浅红打底。 -
增删行:序号格编辑态换成 ×(一列两用);表尾一条「加一行」 (照末行克隆、值清空,主键不填不落库)。删是挂记号不当场抹掉 (
jc-row-gone划线摆着 —— 看得见、可后悔);「取消」把加的行撤掉、 把删记号摘掉、把值放回快照,三样一起才叫还原。为什么必须有增删:手改是兜 AI 的错的那一手,而错不止「这一格填错了」—— 「这一行根本不该在」(客户点名跳过的料被写进有效清单)与「漏了一行」是同一类错, 只给改格子就没有出口(实测那一单 13 次拒回、234k token 才熔断)。
-
缺项红带(
gapBar):表上方一条红带点名「还差 N 格必填,分别是哪一行哪一列」 (_missing+_missing_keys,与提交闸、放行门同源),并告诉人怎么办 (「按右上角编辑开表,红框那几格就是要填的」)。只在可改屏出、缺口为 0 自动收 (§5 的按数开关)。小胶囊淹在九行表里,人得逐行扫 —— 而他打开这一屏就是来填的。
7. 有目标件(设计稿)时:搬,不要照着做
A1 两份报告是 1:1 平移 PRD 目标件:样式表整段搬(prd-style.ts 的 PRD_CSS +
PRD_SPRITE,一个数值都没改),标记逐块搬,只把写死的数据换成绑定。
不用拼装件复述 —— 那套外壳有自己的版面语言,「照着做」出来的是第二种版面。
搬的时候四样替换(原型是带脚本的演示页,这里一行脚本跑不了):
| 原型怎么做 | 搬过来怎么做 |
|---|---|
| JS 造表、写死的行 | data-rows 行模版 + CSS 计数器序号 |
| JS 算的计数 | 台账汇总列或基座读数,data-slot 取 |
| JS 判空插胶囊 | data-blank 真空 + CSS :empty::after |
JS 拼 class / style="flex:N" |
data-class-of 投影 + 样式表分档(§5) |
验收只认并排截图比对 —— 「25 块到位」是结构断言,对版面零约束(栽过三轮)。
8. 版式纪律
- 铺满工作区:去掉外层宽度上限与横向留白、卸掉外框圆角边 —— 件就是这一屏的正文,
不是嵌在里面的另一个页面(
jc-stage { max-width:none })。 - 必填/选填胶囊只在在办态摆;归档件一律
dimempty(那时空格的意思不再是 「你还能填」而是「客户没给」,摆红胶囊是在催一件办完的事)。 - 别摆恒常说「没有」的横幅(「本轮不报 0 行:—」),别把同一个数摆两处 (人得停下来对一遍两处一不一样);已在别处说过的结论不再复述一遍。
- 标记里不写
**星号这类记号 —— 卡面与件都是纯文本/纯 HTML,记号不会变粗体。
9. 自测与浏览器走查
包内自测(结构层):模版无 <script 无外链;data-class-of 不以 data-slot 为前提
(纯色块曾整条静默失色);剧本每个 view 都在 viewTemplates 里;绑定键都是
声明列 / 基座读数 / case:*。
浏览器走查(版面与交互层,结构断言替代不了):
锁定态整篇可读 → 编辑 → 改一格 → 取消(还原) → 再改 + 删一行 + 加一行 → 保存
→ 件上新值现出、红带收起、类徽/分布条/去向重算 → 卡上提交闸开 → 提交 → 放行
→ 工作成果里同一份件零输入框零外壳 → 与目标件并排截图比对
10. 陷阱表
| 症状 | 根因 | 出路 |
|---|---|---|
| 点「编辑」毫无反应,一切选择器都对 | instanceof Element 跨 realm 恒 false;或监听挂在 about:blank 上 |
认能力不认血统;幂等重挂(§3) |
| 保存时件「闪黑」一下 | srcDoc 换文档整页重载 | 就地换 body.innerHTML(§3) |
| 人保存了,一格都没落库 | 保存借道当下那张卡的 blocks | 按 data-edit 地址收,授权=台账声明(§2) |
| 改了类标,分布条/去向不动 | 汇总数是自报的没人重算;或没 repush | 分类列标 tally 让基座数;保存后必 repush(§1) |
| 纯色块整条灰着像没算出来 | data-class-of 曾以 data-slot 为前提 |
已修;自测钉住 |
| 屏上多了一行删不掉 | 只给改格子没给删行 | §6-3 增删行 |
| 空格子显示「无」/上一行的值 | 没挂 data-blank;或加行时只清了 input 没清只读份 |
§4;addRow 两份都清 |
| 报告与卡说的判据不一致 | 两处各算各的 | 全部读基座读数,同源(§2) |
| 归档件上还有编辑闸/胶囊 | 闸没锁在 editable 根类下;胶囊没分态 |
§6-1、§8 |
| 版面「像又不像」目标件 | 用拼装件照着做而不是搬 | §7;截图比对 |