# 手工验收清单(真实 DSH profile) > **要在真实 DSH profile 上确认什么**:构建产物(`ui/client.js`)能加载 → 四个槽位能注册 → 宿主 props 形状与 glue 对齐 → 核心链路(组装 → 写入草稿 → 发送)行为正确。 > > **耗时**:安装启动 3 分钟 + 快线 6 分钟 + 发布门槛 4 分钟 + 观测 3 分钟。 > > **为什么不能省**:仓库内的自动化测试全部是纯逻辑断言与源码/产物文本断言,覆盖不到渲染层。`test/dsh-slots.test.js` 会读真实构建产物做包含性检查,但 slots 服务是模拟对象——它只能证明「产物能调用注册接口」,不能证明目标 DSH 版本喂进来的 props 形状与之一致。这一条只能靠真实 profile。 --- ## 0. 先填被测版本 | 项 | 值 | | --- | --- | | 被测对象 | 待发布的构建产物 `ui/client.js`(应等于当前源码构建结果) | | DSH 版本 | | | Node 版本 | | | 安装方式 | 本地目录 / GitHub 钉 commit / tarball | | 日期 | | > **确认产物与源码一致。** `dsh plugin add <目录>` 读取的是工作区文件;若验收中途改了源码,必须重跑 `npm run build` 并重新安装,否则测到的是旧产物。 --- ## 1. 安装与启动(约 3 分钟) 本仓库无运行时依赖,不需要 `npm install`。 > **环境前置:`dsh plugin` 会调用 PATH 上的 `pnpm`,其 store 布局主版本必须与目标 profile 记录的一致。** 否则命令会以 `ERR_PNPM_UNEXPECTED_STORE` 直接失败(失败是原子的,不会产生任何改动)。先校验: > > ```bash > grep storeDir ~/.dsh/profiles/web/node_modules/.modules.yaml # 例:…/store/v11 > pnpm --version && pnpm store path # 例:v10 → …/store/v10 ← 不一致 > ``` > > 末段 `v11` 对应 pnpm 11.x、`v10` 对应 10.x。不一致时改用**与记录一致的主版本**执行,例如隔离安装 `npm i pnpm@11` 后把其 bin 目录前置到 PATH。 ```bash npm run build dsh plugin --profile web add ``` 刷新浏览器,打开一个**已有会话**。 **预期**:会话视图区出现「科研工作台」;输入框左侧出现「资源」「工作流程」;右侧出现草稿增强入口。 不出现 → 停止,按 §4 的 L1/L2 定位。 --- ## 2. 快线(4 项,约 6 分钟) 任一项失败即停止并记录。 ### F1 槽位注册无重复 **操作**:查看视图标签区与输入框两侧,同时打开浏览器控制台。 **预期** - 「科研工作台」**恰好出现一次**;输入框左侧两个入口、右侧增强入口各一次; - 控制台无 `undefined is not an object` / `register` 相关报错。 **判定**:重复出现 = 残留旧版本安装或 disposer 未执行;完全不出现 = 槽位名与宿主不匹配。 ### F2 四分区可切换 **操作**:在「科研工作台」内依次点选四个二级分区。 **预期**:资源与工作流 / 方法工坊 / 研究资产库 / 研究证据图谱 均可切换,标题与内容对应,**默认落在「资源与工作流」**。 **判定**:标题错位、内容空白、切换报错 → L4 渲染层。 ### F3 写入不发送 + 只发一次 **操作** 1. 在「资源与工作流」选中 `审阅论文`; 2. 填写至少一个必填参数; 3. 点「写入输入框」; 4. 观察输入框; 5. 再点「发送到当前会话」。 **预期**:步骤 4 出现完整 Prompt 且**未自动发送**;步骤 5 **只产生一条**用户消息。 **判定** | 现象 | 定位 | | --- | --- | | 步骤 4 直接发出消息 | `setDraft` 被误接成 `submit` → L3,查 `dsh/standalone-glue.js` 的 props 取值 | | 步骤 5 出现两条消息 | `submit()` 被调用两次(绑定重复)→ L3 | | 输入框始终为空 | 草稿写入路径断开 → L5 状态/存储 | ### F4 切换会话不串台 **操作**:切换到另一个会话,再切回,重复 F3 的步骤 3–5。 **预期**:消息落在**当前**会话,不会写进上一个会话。 **判定**:串台 → `sessionId` 未随会话刷新,或 `inputActions` 被跨会话缓存 → L3。 --- ## 3. 发布门槛(2 项,约 4 分钟) §2 全绿后再做。 ### R1 卸载无残留 **操作**(三步,需要 **1 次重启**): ```bash dsh plugin --profile web remove dsh-research-kit # 1) 卸载 # 2) 重启 profile 并刷新页面 → 核对控件消失 dsh plugin --profile web add # 3) 重装 ``` **预期**: - **卸载后**:以下五处都不得再出现 `dsh-research-kit`——`package.json` 的 `dependencies`、`package.json` 的 `dsh.profile.bundles`、`pnpm-lock.yaml`、`node_modules/.modules.yaml`、`--dump-config` 合成树; - **重装后**:`package.json` 应逐字节回到卸载前,`bundles` 只 1 条,合成树只 1 层,重新打开会话时各控件**仍只出现一次**。 > **卸载在运行态不立即生效。** 插件树在 profile 启动时合成,重启前旧实例仍在服务(DSH 自身的卸载日志也会标注「待重启生效」)。因此「卸载无残留」必须**重启后**才能判定运行态。 ### R2 降级不崩 **操作**:构造一次宿主动作缺失(禁用对应能力,或在 devtools 中断开 `inputActions`)。 **预期**:相关按钮禁用并给出说明文案,视图不崩溃。 --- ## 4. 失败定位树 | 症状 | 层 | 首要检查点 | | --- | --- | --- | | 插件根本没被加载 | L1 加载 | `package.json` 的 `main` / `type: module`;`cordis.patch.yml` 的 id 与安装名是否一致 | | 加载了但没有任何槽位 | L2 注册 | `dsh/slot-registry.js` 的 slot 名是否与目标 DSH 版本一致 | | 槽位重复出现 | L2 注册 | 是否残留旧版本安装;`registerResearchSlots` 返回的 disposer 是否被调用 | | 槽位在但视图空白 / 报错 | L4 渲染 | 浏览器控制台首个 React 错误栈;`src/ui.js` 的组件契约 | | 写入草稿变成直接发送 | L3 契约 | `dsh/standalone-glue.js` 中 `inputActions` 的取值与调用点 | | 消息发两次 | L3 契约 | `submit` 的调用是否被重复绑定 | | 切换会话后串台 | L3 契约 | `sessionId` 的更新路径 | | 直查返回 503 / 增强提示「尚未建立模型路由」 | L5 路由 | `index.js` 三条路由是否注册;`ctx.sessions` 惰性兜底是否命中 | **修复位置原则** 1. 宿主 props 形状不符 → **只改 `dsh/standalone-glue.js`**,不要让 React 模块去读宿主私有数据; 2. `vendor/promptkit-embed.js` **不得改动**——其 SHA-256 被 `npm run check` 锁定,改动会导致校验失败; 3. 分区结构变更只改 `src/lib/console-sections.js`,并同步 `src/research-console.js` 的 `SECTION_VIEWS`,否则 `test/research-console.test.js` 会失败。 --- ## 5. 观测项(不阻断发布,建议记录) | # | 检查项 | 预期 | 结果 | | --- | --- | --- | --- | | O1 | 深色主题(`body[data-ds-dark-theme]`)下四分区可读 | 对比度足够,无错位底色 | | | O2 | 窄屏(< 880px)下分区导航与工作台为单列 | 不出现横向滚动 | | | O3 | 空结果状态 | 搜索无结果时显示空态而非空白 | | | O4 | 证据库在真实 profile 中可用 IndexedDB(无「刷新会丢失」提示) | 列表上方无降级提示 | | ### 证据库闭环(ROADMAP §4b 验收,4 项) | # | 检查项 | 预期 | 结果 | | --- | --- | --- | --- | | E1 | 保存一条证据后**刷新页面** | 条目仍在,状态为「未核验」 | | | E2 | 新建并切换项目,再刷新 | 列表只显示当前项目的条目;当前项目跨刷新不变 | | | E3 | 在同一项目重复保存同一 DOI / PMID | 停下并询问「覆盖已有 / 仍然另存一份」,**不静默入库** | | | E4 | 删除单条、清空当前项目 | 清空走二次确认;其他项目条目不受影响 | | > E1 依赖宿主提供可用的 IndexedDB。若证据库列表上方出现「证据暂存在内存中,刷新页面后会丢失」提示,说明当前 profile 未提供 IndexedDB,此时 E1 不成立——记为环境限制,不是回归。 ### 证据库写入 Prompt(ROADMAP §4c 验收,3 项) | # | 检查项 | 预期 | 结果 | | --- | --- | --- | --- | | W1 | 勾选 1–2 条证据后看面板;随后改检索词或核验状态筛选 | 出现引用块预览,首行为「…尚未经逐条核验…不得据此直接断言结论」,每条带稳定标识符与来源链接;按钮显示「写入 Prompt(N)」,N 与勾选条数一致。**改筛选后按钮计数与预览条数不变**——勾选跨筛选保留,不因被筛掉而缩水 | ✅ 2026-09-11 | | W2 | **不勾选任何条目** | 写入按钮为禁用态、点不动,**不产生任何注入**(「未选择不注入」的现场核对) | ✅ 2026-09-11 | | W3 | 勾选后点「写入 Prompt(N)」 | 输入框出现引用块全文且**未自动发送**;提示语报出的条数与实际注入条数一致 | ✅ 2026-09-11 | > W2 现场只能证明「点不动」;真正的不变式由 `planCitationWrite()` 的回归测试守护(未选择时 `action` 必为 `empty`、文本为空,且视图只在 `action === 'write'` 时调用 `setDraft`)。若在真实 profile 上发现按钮可点却无注入、或注入条数与提示不符,先跑 `npm test` 定位决策层,再查宿主 `inputActions` 契约。 > **验收记录(2026-09-11,真实 DSH Web profile,§4c 逐项)**:W1–W3 通过——勾选后「写入 Prompt(N)」可用、引用块预览出现且首行为核验边界;不勾选任何条目时按钮为禁用态、点不动(W2);点击写入后输入框出现引用块全文并**未自动发送**,提示条数与实际注入条数一致,本次为 1 条(W3)。W1 的现场观察覆盖「按钮可用」与「N 与勾选条数一致」;「改筛选后计数不变」与前者同源(选择集以 `entries` 为基准),由接线契约测试守护,现场调整筛选条件时顺带复核即可。 ### 研究证据图谱(ROADMAP §4d 验收,4 项) | # | 检查项 | 预期 | 结果 | | --- | --- | --- | --- | | G1 | 保存一条证据后打开分区④ | 出现独立证据节点,标注「来源库 · 稳定标识符 · 核验状态」;**不显示笔记、全文或检索词** | ✅ 2026-09-11 | | G2 | 观察证据节点与来源库的连线 | 来源库在目录中存在时,证据连到同库资源节点(本次:测试证据 → PubMed 资源) | ✅ 2026-09-11 | | G3 | 本会话查询过与证据同 URL / 同稳定标识符的来源时 | 证据另有一条连到该查询来源节点的线(`saved-copy`) | — 未触发(本次会话无同 URL / 标识符来源) | | G4 | 观察箭头与图谱说明 | 箭头按两端节点实际位置选锚点,逆向关系不再固定「左进右出」;图谱说明明确标注箭头含义 | ✅ 2026-09-11 | > G3 需要「先直查、再保存同一条来源」的操作顺序才会产生 `saved-copy` 边;只保存别处看到的证据时不会触发,属预期而非缺陷。G1–G2 的现场观察:测试证据与 PubMed 资源共 **2 个节点、1 条关系**。 > > G1 的「不显示笔记」不靠眼力判断:`test/evidence-graph.test.js` 断言图谱序列化结果里不得出现笔记正文;现场只是复核渲染没有把笔记画上节点。 ### 数据源能力标注(2 项) | # | 检查项 | 预期 | 结果 | | --- | --- | --- | --- | | D1 | 在工作台点开 PubMed、Crossref 等 11 个直查来源的详情 | 「当前状态」为绿色**「插件可直接查询」**,且出现可用的查询入口 | | | D2 | 点开一个非直查来源(如 Scopus、GTEx) | 状态为琥珀色「需要 MCP 或 Web 能力」,写明接入前提,**不伪造查询结果** | | > 这两项守护的是「目录标注与实现一致」这条双向契约。若 D1 出现琥珀色提示,说明 `catalog/databases.json` 的 `availability` 与 `dsh/database-query.js` 的适配器漂移——`npm run check` 本应拦住它,先跑一次即可定位。 --- ## 6. 证据记录(复制填写,建议附在发布说明里) | # | 检查项 | 结果 | 证据(截图 / 日志) | | --- | --- | --- | --- | | 0 | 被测版本与产物指纹(§0) | | | | F1 | 槽位注册无重复 | | | | F2 | 四分区可切换 | | | | F3 | 写入不发送 / 发送只一次 | | | | F4 | 切换会话不串台 | | | | R1 | 卸载无残留、重装不重复 | | | | R2 | 宿主动作缺失时降级不崩 | | | | O1–O4 | 深色 / 窄屏 / 空态 / 证据库存储可用 | | | | E1–E4 | 证据库闭环(刷新 / 项目 / 去重 / 删除) | | | | W1–W3 | 证据库写入 Prompt(引用块 / 未选择不注入 / 注入条数一致 / 跨筛选不缩水) | ✅ 2026-09-11 | | | G1–G4 | 证据图谱接入已保存证据(节点元数据 / 同库资源连线 / 来源匹配连线 / 箭头方向) | ✅ 2026-09-11(G3 未触发) | | **发布判定**:F1–F4 与 R1 全部通过方可发布;R2 与 O1–O3 失败须记录为已知限制。 --- ## 7. 已完成的验收与遗留 ### 已完成 真实 DSH Web profile 上已完成两轮完整走查:F1–F4、R1 与 O1–O3 全部通过,期间发现并修复 1 处分区渲染缺陷(容器内嵌套 `main.rk-page` 导致多余滚动、工具栏被推到标签栏背后;已补 `test/research-console.test.js` 的「分区嵌入契约」守护该缺陷类)。 2026-09-11 在真实 DSH Web profile 上补验证据库写入 Prompt(ROADMAP §4c)的 W1–W3,全部通过——**§4c 至此闭环**:勾选后「写入 Prompt(N)」可用、引用块预览可读;不勾选任何条目时按钮禁用、点不动;点击写入后输入框出现带来源链接与核验边界的引用块全文且未自动发送,提示条数与实际注入条数一致。该切片的自动化层(`planCitationWrite()` 的三态决策 + 视图接线契约)与现场层至此互为印证。 同日同一 profile 上验收证据图谱接入已保存证据(ROADMAP §4d)的 G1–G4:证据节点只带来源库 / 稳定标识符 / 核验状态(不含笔记),与同库资源节点连成 **2 个节点 / 1 条关系**,箭头按实际方向选锚点、图谱说明标注含义——**§4d 至此闭环**。(G3 的 `saved-copy` 边因本次会话无同 URL / 标识符来源而未触发,属预期。) > 该缺陷纯逻辑测试无法覆盖(渲染层),只有真实 profile 能暴露——这正是本清单存在的意义。 ### 遗留 1. **R2 缺渲染级自动化测试——根因是缺 harness**:`inputActions` 缺失时的降级守卫共五处(清单见 [ROADMAP §1](../ROADMAP.md)),目前只靠代码审查与 W2/W3、G 项现场核对。§4c 的写入降级已在决策层被 `planCitationWrite({ canWrite: false })` 覆盖,并有一条源码接线断言,但「按钮禁用态 + 提示文案」这一渲染层仍未自动化。**仓库当前没有可在 Node 中挂载 React 组件的渲染测试运行时**(已有的是纯逻辑断言与源码/产物文本断言),因此本项不是「补几个用例」,而是要先引入或搭建 harness(真实 React + DOM,能挂载分区组件并触发事件);在这一步落地前,各处降级的自动化覆盖只能停在决策层。真实 profile 的**可用**写入路径已复验(W1/W3),缺的是**无宿主动作**场景的自动化构造。 2. **工作台左列非 sticky**:右列详情可达约 1044px,左列列表仅约 415px 且随页面滚走;滚到详情底部出口按钮时列表已滑出视口。建议给左列加 `position: sticky` + 视口内高度约束; 3. **升级 DSH 后必须重跑本清单**:本次验收证明的只是当时那个 DSH build 的 props 形状。 > 原遗留第 4 条「§4c 尚未过真实 profile 验收」已于 2026-09-11 闭环(W1–W3 通过),见上文「已完成」。 > 与插件无关但会影响 `dsh plugin` 工作流的两个环境事实:pnpm store 布局主版本与 profile 记录不一致会让命令整体失败(见 §1 环境前置);`remove` 不会清理 `link:` 依赖在 `node_modules` 下的符号链接(因插件树由 `bundles` 驱动,残留链接不会被加载,影响为低)。