# 已知问题与发布前检查清单 本文件记录 dsh-zcode-ui 当前**未解决**的测试失败、有意跳过的断言,以及发布前必须处理的事项。 维护原则:**宁可显式记录,不要静默留白**。任何「跑不绿但暂时不动」的测试都必须在此登记原因, 避免后续维护者把已知的存量漂移误当成新引入的回归。 最后更新:2026-10-13(当前基线:35 通过 / 3 跳过 / 0 失败) --- ## 1. 断言套件现状 运行 `node tests/run-assertions.mjs` 的基线结果: ``` 35 通过 / 3 跳过 / 0 失败(共 38 个脚本) 已跳过:codex-suite-installer.assert.mjs、codex-suite.assert.ts、release-pipeline.assert.mjs ``` 套件已改为**跑完全部再汇总**(原实现遇到第一个失败即退出),一次即可看清全部问题。 > 基线会随工作树变化:19:00 时还有 3 条失败(`conversation-visuals` / `dependency-manager` / > `settings-integration`,均为上游样式漂移),当天已全部重定基转绿,详见 §3。本块数字与 > `README` 中引用的口径一致。 --- ## 2. 有意跳过的断言(3 个) 这 3 个断言校验的是**上游 dsh-codex-ui monorepo** 的发布契约,而本仓库已 fork 为**单包仓库**, 被测对象本身不存在,因此永远不可能通过。处理方式是**保留文件与全部断言原文 + 顶部显式跳过**, 而不是删除——删除会让「测试不见了」和「问题解决了」难以区分。 | 文件 | 跳过原因 | 依赖的不存在路径 | | --- | --- | --- | | `tests/codex-suite.assert.ts` | 校验聚合包 `dsh-codex-suite` 的清单、patch、README 契约 | `../packages/dsh-codex-suite/` | | `tests/codex-suite-installer.assert.mjs` | 校验安装器的参数解析、bundles 归一化、Windows 转义 | `../packages/dsh-codex-suite-installer/installer.mjs` | | `tests/release-pipeline.assert.mjs` | 校验 GitHub Actions 发布流水线与发布脚本链路 | `.github/workflows/*.yml`、`scripts/prepare-suite-installer-release.mjs` | **恢复方式**:若将来重新引入 `packages/` 工作区与 `.github/workflows/`,删除各文件顶部的 `SKIP` 块即可(`codex-suite-installer.assert.mjs` 还需还原被注释的 import)。 **注意**:`codex-suite.assert.ts` 的跳过刻意写成 `const SKIP_REASON: string = ...` + `if` 判断, 而非直接 `process.exit(0)`。原因是后者返回 `never`,会让 tsc 把后续断言判为不可达代码、 把 `assert` 收窄为 `never`,从而报出一片 TS2339。修改时请勿简化回直接调用。 --- ## 3. 存量失败断言 → 已全部清零(2026-10-12) 原先只有 3 个文件报红,但**每个文件的第一条不匹配就抛错**,所以真正过期的远不止 3 条。 用 `scripts/collect-assert-mismatches.mjs`(把 `assert.match` 换成收集器、跑完列出**全部**不匹配)一测, 发现两份"视觉契约"文件整体钉着 **Codex 时代的视觉语言**:`conversation-visuals.assert.ts` 23 处、 `settings-integration.assert.ts` 11 处;再加上 `dependency-manager.assert.ts` 的 1 处包名,合计 **35 处**。 | 文件 | 过期项 | 性质与处理 | | --- | --- | --- | | `dependency-manager.assert.ts` | 1 | 夹具仍声明上游包名 `@michengai/dsh-codex-ui`,而 `src/dependencies.ts` 的 `{ id: 'ui' }` 认的是 `dsh-zcode-ui` → 改为 fork 包名 | | `conversation-visuals.assert.ts` | 23 | 配色/文字层级**迁移到主题层**(`zcode-theme.ts`,断言改为读该真值源);几何与动效常量随 ZCode 刻度更新(头部 34→40px、内衬 28→32px、间距 8→4px、动效由 500ms 弹簧改为 180ms ease-out(2026-10-13 进一步改为五档意图令牌,见 §3.1)、圆角 10→8px、行高 30→32px、刻度 left −16→−12px、图标列 20→16px);窄轨设置入口改为宿主 `wide:false` 渲染;展开按钮改用 `ZIconCollapse` | | `settings-integration.assert.ts` | 11 | 图标体系整体换 Lucide(文件夹/新建会话/定时/IM/继续分支);字号改为七档令牌;包名/注册 id 改为 `dsh-zcode-ui` / `zcode-ui-for-dsh`;导航内衬改为 px-2(8px) | **结论:35 通过 / 3 跳过 / 0 失败**(`node tests/run-assertions.mjs`);`scripts/gate.mjs` 的 `KNOWN_RED_ASSERT` 已清空,此后任何断言失败都会拦住提交。 > **为什么不在此文件里写裸短提交号**:短号在任何一次历史重写(例如 2026-10-13 的隐私清理)后都会失效, > 而 GitHub 在重写后仍会按旧号提供不可达对象——写在公开文档里等于把旧对象重新指出来。 > 因此本文件改用「提交说明 + 时间」引用,不再登记裸号。 ### 3.1 设计变更登记(不写成"必须如此"的断言,避免把变更伪装成契约) 重定基过程中发现以下几条**不是数值过期,而是 ZCode 有意改掉的设计决定**。它们没有按现状写成硬断言, 而是登记在此,供将来判断"是回归还是延续设计": | 位置 | 原 Codex 设计 | 现行 ZCode 设计 | 影响 | | --- | --- | --- | --- | | 侧栏导航层级 | 导航文字(`--dcu-sidebar-navigation`)比项目标题更淡,是独立层级 | ZCODE-DESIGN §2 只有 primary/secondary/tertiary/icon 四档,无"导航档";`--dcu-sidebar-navigation` 与 primary 同值,主菜单直接用 primary | 导航与标题不再有深浅差 | | 毛玻璃降级 | `@supports not (backdrop-filter)` 时恢复**实底**保证可读 | 玻璃层本身半透明,另由 `body[data-we-appwindow]` 关闭模糊;可读性依赖宿主背景 + 主题文字色 | 不支持毛玻璃时不再有独立实底兜底 | | 任务频道/定时页签 | 整行切换器(`border-bottom` 分隔),字号与主导航同级(14px),选中加粗(600) | ZCode **分段控件**:`--dcu-surface` 底 + 1px 边框 + 8px 圆角,字号 12px(sm),选中用底色而非字重 | 页签形态与层级语义都变了 | | 项目文件夹图标 | 区分「当前展开」(官方实心)/「其他展开」(线框)/「收起」(闭合) | 只区分展开(`ZIconFolderOpen`)/收起(`ZIconFolder`) | 当前项不再有独立图标样式 | #### 2026-10-13 可读性与一致性修复(相对 ZCode 的有意偏离) 界面评审(真机测量 + WCAG 2.1)后落地的一批修改。它们**偏离 ZCode 原值**,原因与判据记录如下: | 位置 | ZCode 原设计 | 本 fork 现行 | 原因与判据 | | --- | --- | --- | --- | | 侧栏 secondary / tertiary | neutral-800 以 60% / 40% 叠在**白底**推导(`#777777` / `#9f9f9f`) | `#5c5c5c`(5.87:1) / `#686868`(4.89:1);暗色 tertiary `#4f4f4f` → `#828282`(4.82:1) | 同一组值叠到侧栏 `#f0f0f0` 上实测只有 **3.93:1 / 2.32:1**,低于 WCAG AA 的 4.5:1;而暗色侧栏反而达标(5.10:1)——说明原值只对白底成立 | | 选中态底色 | accent 蓝(light `#ebf4ff` / dark `#001d3d`) | 中性 `#efefef` / `#3a3a3a` | 侧栏选中态是中性 `rgba(13,13,13,.05)`,同一套皮肤出现两种"选中"语言;统一到中性,蓝色只留给焦点环(`--dcu-focus`) | | 新会话建议卡标签 | 单行截断 | 允许两行(`line-clamp:2`) | 1440px 下每张卡约 171px,四个英文标签实测**全部断在半词上** | | 折叠轨内的计费卡 | —(第三方 `dsh-cost-meter` 自有样式) | 折叠态隐藏 | 56px 轨装不下它的文字,此前被裁成 `F41-peak` 这类乱码 | | 计费卡(展开态) | — | 数字 14px/500、说明 12px、去掉白底卡 | 白底卡片 + 加粗大数字比侧栏任何文字都重,压过导航层级 | | 项目标题 / 分区标签字重 | 「标签与标题 font-medium」 | **保持 400(不改)** | 代码里已有明确断言禁止项目标题加粗("不能显得过黑");且只加粗分区标签会造成"标签比标题重"的层级倒挂。此处保留对 ZCode 文档的有意偏离 | | 内部 REST 命名空间 | —(fork 自上游) | **保留 `/api/dsh-codex-ui/*` 不改名** | 该命名空间出现在 23 个文件(宿主注册端 + 客户端调用端 + 测试),是**纯内部契约**、用户完全不可见;改名收益仅是字面一致,风险是打断宿主↔客户端链路。用户可见的三处(安装 id `zcode-ui`、包名 `dsh-zcode-ui`、文档)均已正确 | | 三个面板页的宿主图标 | 全站 Lucide | **保留宿主图标**(`AboutSection` / `ScheduleBrowser` / `ConnectorsSection`) | 复核确认这三个文件**各自内部一致**(同页无混用),且只出现在独立面板页、不与侧栏同屏;换成 Lucide 等价图标属**视觉变更**,需设计确认与实拍比对,不属"统一来源"这类卫生改动。其余长尾已收归,现全仓仅 `zcode-icons.tsx` 直引 `lucide-react` | 新增门禁:`scripts/verify-a11y-and-overflow.mjs`(已接入 `gate.mjs` 与 `npm run test:layout`), 把上面三类问题变成客观判据——**对比度 ≥4.5:1(明暗两套)、建议卡标签不得截断、折叠轨不得横向溢出**。 反向测试已做:把侧栏 secondary 改回 `#777777` 会直接报 `3.93:1 低于 WCAG AA 的 4.5:1`。 #### 2026-10-13 设计制度补全(刻度 / 动效 / 契约 / 签名) 上面那一批修的是"看得见的问题"。这一批改的是**制度本身**——即"为什么这类问题此前只能靠人眼发现"。 全部判据由新增的 `tests/design-system.assert.ts` 守护(源码层,不依赖活宿主)。 | 位置 | 原状 | 现行 | 原因 | | --- | --- | --- | --- | | 字号刻度 | 七档只覆盖 10–18px;`20px` / `22px` / `34px` 三处页面级大字靠 §3.2「例外登记」存活 | 补 `title`(20px) / `headline`(22px) / `display`(34px) 三档,**值与原例外逐一对齐** | 例外会繁殖——一处「暂不归并」会变成后来人照抄的借口。收归后**零视觉变化**,只是把破例变成有据(ZCODE-DESIGN §3.1) | | `11px` ×4 | 夹在 `xs`(10px) 与 `sm`(12px) 之间的孤值 | **归一为 `sm`(12px)** | 补一档等于把孤值合法化,刻度会继续变碎;四处消费方均无宽度硬约束,+1px 不影响布局。**这是一次有意的小幅视觉变更** | | 字重 `650` ×1 | `ConnectorsSection` 的连接器标题 | 归一为 `600` | 与其余标题取同一档,原值属笔误(系统里不存在 650 这一档) | | 工作区分组标题字重 | `600` | 归一为 `400` | 与侧栏"标题/标签保持 400"的既有约定(已有断言守护)冲突,造成"分组标题比分区标签更重"的层级倒挂。分组层级改由披露箭头、缩进与明度表达(ZCODE-DESIGN §3.3) | | 页面级标题字重 `600` | 设置页 `h1`、设置分组 `h2`、关于页依赖名 | **保留 600**(登记为有意偏离) | ZCode 规定「标签与标题 font-medium」= 500,但 500 与正文 400 在页面级标题上区分度不足。**此偏离只适用于页面级标题**,侧栏内仍严格 400 | | 动效 | 全站统一 `180ms ease-out` 一条曲线 | 五档**意图令牌**:反馈 120 / 移动 180 / 进入 160 / 退出 120 / 按压 90 | 一条曲线是"一致"但不是"设计过"——出现、移动、消失同速读起来是"没做动效"。新曲线均无过冲,不违反 ZCode「避免弹簧感」;令牌化同时让 reduced-motion 变成一处归零(ZCODE-DESIGN §7.1) | | 外来内容覆盖 | 样式直接写 `.cm-*` 类名,对方改名即**静默失效** | 收敛为**契约锚点清单** + 运行时哨兵(`foreign-content-contract.ts`);覆盖未登记锚点会让门禁变红 | 覆盖范围必须可审查;属性通配会让范围随对方改版漂移。哨兵把"静默失效"变成"响亮失效"(ZCODE-DESIGN §10) | | 签名细节 | 无——视觉语言全部来自 ZCode | 两处登记:发送键的悬停/按压回应、空状态的口气 | 本插件此前没有"属于自己的表达"。签名只用位移与明度,不引入颜色(ZCODE-DESIGN §11) | #### 2026-10-13 第二批:外部评审意见的采纳与拒绝 一次独立评审(另一会话,快照 08:47–08:54)指出了若干问题。逐条核实后**采纳 5 条、拒绝或降级 3 条**, 两类都记在这里——拒绝的理由和采纳的同样重要,否则下一轮评审会把同一件事再提一遍。 **采纳**: | 位置 | 原状 | 现行 | 原因 | | --- | --- | --- | --- | | 主题层归属 | `ZCODE_THEME_STYLE` 的注入与 `syncZCodeTheme()` 都挂在 `CodexSidebar.tsx` 上 | 两者都移到 `index.ts` 的 `ctx.effect`(新增 `installThemeStyle()`) | 主题是**应用级**的:消费 `:root` 动效令牌的不只有侧栏,还有会话区的轮次导航与页签滑块、portal 到 body 的设置页。挂在侧栏上时,侧栏一不挂载,那几处会静默失去过渡且不报错 | | 门禁汇总 | 断言套件/vitest 只要「解析不到失败清单」就打印通过 | 新增「**无法判定**」分支并纳入阻断条件 | 进程非零退出但输出不含已识别的失败行时(崩溃、EPERM、OOM、汇总格式变更),原先会落到「全部通过」分支。实测过:沙箱禁止写 `node_modules` 触发 EPERM 时,汇总行照打「vitest:全部通过」 | | 上游哈希类名锚点 | 18 处散落在 6 个文件,上游改名后静默失效 | 全部登记进 `host-anchor-contract.ts`;其中 6 处(前置条件可确定)运行时自检,缺失即 `console.warn` | 与 §10 外来内容契约同一原则:**把静默失效变成响亮失效**。误报比不报更糟,所以只自检前置条件确定的那部分(见 ZCODE-DESIGN §12) | | README 定位 | 「**除了外观,什么都不动。** 这是一个纯粹的展示层插件」 | 改为「皮肤本身不改动别的东西」,并新增「**能力范围与权限**」章节(6 行能力表) | 本包继承上游的**依赖安装(跑 pnpm、可重写 `node_modules`)/ 会话移动 / 打开文件管理器 / 5 个 REST 端点**,自称纯展示层是过宽。仓库已公开,信任面必须如实披露——让读者自己从「皮肤」二字推断是不对的 | | README 令牌统计 | 18 结构性 / 192 唯一 | 21 / 195 | 本轮新增三档展示档令牌,文档未同步 | **拒绝或降级(附理由)**: | 评审意见 | 判断 | 理由 | | --- | --- | --- | | 「`zcode-skin.ts` 拒绝哈希类名,但其他文件仍在用 → 自相矛盾」 | **降级为措辞问题** | 原话的作用域是「本皮肤层」(该文件的上下文),而皮肤层确实一处都没用。集成层无法回避哈希类名。已在 `zcode-skin.ts` 补边界说明、并在 ZCODE-DESIGN §12 写清两层分工。**但其建议本身(装报警器)完全成立,已采纳** | | 「亮色下『拖动项目到此处置顶』的灰比 tertiary 更浅」 | **不成立** | 实机测得 `.dcu-wb-empty` 为 `rgb(104,104,104)` = `#686868`,正是 tertiary 本身,无额外 opacity。它比 secondary 浅是**设计分层**,不是缺陷。13px 抗锯齿在小字号下容易被看成更浅 | | 「`CodexWorkspaceBrowser.tsx` 有一个约 690 行的函数;`!important` 40 处」 | **承认,本轮不动** | 均为事实。但 690 行函数的重构风险远高于收益,`!important` 是皮肤层覆盖宿主所必需。登记为已知技术债,不做无谓改动 | **评审中已过期/已被修复的两条**(记录以免重复讨论): - 「断言套件与文档口径脱节(实测 34 通过 / 3 跳过 / 1 失败)」——那是**同一时间本会话正在编辑中的快照**:当时刚加入 `design-system.assert.ts`(脚本数 37→38)并改了空态文案,`workspace-groups-ui.assert.ts` 尚未同步。当日 09:15 前已修正,现为 35 通过 / 3 跳过 / 0 失败。 - 「布局套件正在红,根因是合成夹具没注入令牌层」——**同一根因、独立复现**,且本会话已在同一时段修掉,修法更彻底:抽出共享模块 `scripts/fixture-tokens.mjs` 从源码真值提取令牌块,四个脚本统一改用(见 §6.1)。评审还额外指出「12 个脚本里只有 2 个注入主题样式表」,该细节已并入 §6.1 的说明。 **动效断言从「锁数值」改为「锁意图」**:`conversation-visuals.assert.ts` 原先断言 `dcu-sidebar-expanded-in 180ms ease-out` 这类字面量,现改为断言"必须使用 `enter` 档令牌"。 数值将来调整不会误报,意图被改则立刻变红——这是令牌化的主要收益之一。 **`session-tree.assert.ts` 的两轮红灯都已转绿**:第一轮因「要求 Codex 的 10px 圆角 / 30px 高度 / 弹簧缓动」与 ZCode 改造冲突,已按设计意图更新断言(10px→8px、30px→32px 行高、弹簧→ease-out); 第二轮因悬停卡片去掉项目行与时长行(见 3b 第 2 条)而报红,已在 18:48 那次「断言套件重定基」提交中同步更新断言—— 从「必须带项目行的 Codex 文件夹图标」改为显式断言「不得再渲染 `dcu-wb-tip-row` / `dcu-wb-tip-time`」。 > 工具:`scripts/collect-assert-mismatches.mjs`(只读,不改断言)会把某份 `*.assert.ts` 里**全部** > 不匹配的「说明 ← 期望模式」列出来。凡是这类"第一条就抛错"的断言文件再次变红,先跑它再动手, > 否则只能一轮剥一条。 --- ## 3b. 界面问题归属(DSH 内实测反馈的 7 条) 以下来自 2026-10-12 DSH 会话里 agent 用真实浏览器的实测反馈,按「本插件是否可改」分类。 | # | 问题 | 归属 | 处理 | 证据(实机截图) | | --- | --- | --- | --- | --- | | 1 | 会话标题跑马灯把标题两端同时裁掉 | **本插件** | ✅ 已修:移除跑马灯,改为纯 ellipsis | `assets/screenshots/conversation-light.png`(标题以 `…` 收尾) | | 2 | hover 提示卡又大又重、信息重复 | **本插件** | ⚠️ 源码已改并于 18:48 提交,但**未重建 `lib/`**,实机仍是旧卡(见 3d) | 待重建后补图(19:00 实拍仍是「标题 + 20小时 + 测试1」) | | 3 | 费用信息出现 3 处(顶栏 chip / 底部状态栏 / 侧栏左下卡片) | **`dsh-cost-meter` + `dsh-context-pilot`** | ⛔ 不属本插件,见下方说明 | — | | 4 | 底部状态栏过挤、对比度低、末尾孤立 `5%` 语义不明 | **`dsh-context-pilot`** | ⛔ 不属本插件 | — | | 5 | 侧栏左下卡片与「记忆/任务/归档/设置」无分隔 | **本插件**(槽位在侧栏组件内) | ✅ 已修:加视觉分隔 | `assets/screenshots/conversation-light.png`(卡片下方可见分隔线) | | 6 | 顶栏右侧彩色方块按钮风格突兀 | 宿主内置 `open-in-app` 控件 | ⛔ 已查证:宿主彩色位图图标,不可接管,见下方说明 | —(该元素只在会话视图顶栏出现,本页证据图是空状态视图) | | 7 | 空状态四张卡偏高、内部留白约 50px、窄窗断行 | **本插件**(`NewConversationSuggestions.tsx`) | ✅ 已修:收为单行矮卡 | `assets/screenshots/conversation-light.png` | ### 关于 #3 和 #4:不属本插件 已 grep 确证**本插件 `src/` 内没有任何费用 / 速率 / token 计量代码**(零匹配)。三处 UI 的真实来源: | 位置 | 实际归属 | 注入插槽 | | --- | --- | --- | | 顶栏「费用明细」chip | `dsh-cost-meter` v1.8.19 | `header.actions` | | 侧栏左下「今日 / 余额」卡片 | `dsh-cost-meter` v1.8.19 | `sidebar.footer.action` | | 底部状态栏「2轮 21步 / tok/s / 缓存命中 / 本会话 ¥…」 | `dsh-context-pilot` | 状态栏 | 本插件是**皮肤层**(只做视觉适配,不改功能模块的注入逻辑),因此无法合并或删除这三处。 `dsh-cost-meter` 当前版本(1.8.19)**没有提供关闭某个入口的配置项**(已查其 package.json 与配置 schema,仅有 `schemaVersion`)。 **若要收敛,只能改那两个插件本身**,或在其各自的设置界面中调整。 ### 关于 #6:已查证到确切来源——宿主内置的「打开方式」控件 DSH 侧截图显示顶栏右侧有一个彩色方块按钮,原判断「像是插件自带的未适配样式」。现已查证: - 该元素是 ``,即宿主「用访达打开 / 更多打开方式」分段控件里的 **macOS 应用彩色位图图标**(不是 emoji,也不是插件注入)。 - 字符串 `open-in-app/icon` 命中 `DeepSeek Harness.app/Contents/Resources/app.asar` (偏移 21397551、51511145 等),**不在** `dsh-mcp-connector` / `dsh-notify-me` / `dsh-context-pilot` 内;其样式类名 `iq4beG_*` 也来自宿主自身的 CSS Module。 - 本插件**不注入 `header.actions`**(`src/client/conversation-header.ts` 的注释还专门说明 「官方 header.actions(标准模式 / Agent Team)」,即该插槽属宿主官方); 已注入 `header.actions` 的是 `dsh-cost-meter`;`dsh-mcp-connector` 注入的是 `header.split`。 结论:本插件无法接管它的样式。它是顶栏唯一的彩色元素,与全站 Lucide 线性单色图标不同源; 若要统一只能由宿主改。皮肤层原则上可以隐藏该图标只留文字,但那会改变宿主功能的可见性, 因此**本插件选择不隐藏**。 --- ## 3d. ⚠️ 构建滞后:3b 的 ✅ 只代表源码已改 宿主加载的是**构建产物 `lib/`**,不是 `src/`。改完源码必须重新构建(并按 README 重启宿主) 才会在实机生效,所以「源码已改」与「实机已生效」是两件事。 截至 2026-10-12 19:00 的实测对照: | 3b 项 | 源码 | `lib/`(构建于 18:43) | 实机(19:00 实拍) | | --- | --- | --- | --- | | #1 标题 ellipsis | ✅ | ✅ 已含 | ✅ 生效 | | #5 footer 分隔 | ✅ | ✅ 已含 | ✅ 生效 | | #7 空状态卡收矮 | ✅ | ✅ 已含 | ✅ 生效 | | #2 悬停卡去项目行/时长 | ✅ 18:48 提交 | ❌ **不含**(产物里仍有 `dcu-wb-tip-time`) | ❌ 仍是「标题 + 20小时 + 测试1」 | 原因:`src/client/session-tree.tsx` 的改动写在 **18:43 那次构建之后**,而 18:48 的提交只提交了源码、 没有重新构建。**下次构建后即可生效**,届时可补一张悬停卡片截图作为 3b #2 的证据。 --- ## 4. 发布前必做 - [x] **填写真实 GitHub 用户名** —— 已完成:`repository` / `bugs` / `homepage` 均指向 `github.com/jungenius-stack/dsh-zcode-ui`。该账号已通过 `api.github.com/users/jungenius-stack` 核实存在(id 322767483)。 历史备注:早期版本曾填 `BatchTTS`,但该账号在 GitHub 上**不存在**(API 返回 404), 它只是本机 git 的 local 身份(`BatchTTS `,邮箱为占位),不可用作发布账号。 - [x] **核对 npm 包名是否可用** —— 已完成:2026-10-12 查询 `registry.npmjs.org/dsh-zcode-ui` 返回 404,裸包名未被占用。 - [x] **确认 `publishConfig`** —— 已完成:当前为 `access: public` + 官方 registry (`https://registry.npmjs.org/`),与本条记录一致;若改为私有需另行调整。 - [x] **发布渠道定为 GitHub 直装(2026-10-13 决定)** —— 不发布到 npm,README 的安装方式改为 `dsh plugin --profile web add github:jungenius-stack/dsh-zcode-ui`。依据:`dsh plugin` 把参数原样转发给 `pnpm`,pnpm 原生支持 `github:/`;本包没有 `prepare` 脚本, 且 `lib/` 已随仓库提交,所以安装过程不触发编译。上面两条 npm 相关的核对结果保留作历史记录, `publishConfig` 也仍然保留,日后若要改发 npm 可直接沿用。 - [x] **公开前的隐私清理(2026-10-13)** —— 仓库由私有转为公开前完成: (a) README 顶部两张展示图用演示数据重截(原图会暴露本机真实项目名、会话标题与账户余额); (b) 删除 `debug/` 整目录(含 10 张同类截图、内含绝对路径的本地调试补丁、`lib/` 的派生副本), 并把 `debug/` 加入 `.gitignore`; (c) 删除未被任何文档引用的 `assets/screenshots/conversation.png` 与上游营销图 `assets/branding/dsh-banner.png`(后者宣传的是上游自己的 5 个配套插件,不属于本项目); (d) 清除 `docs/` 中残留的本机绝对路径。 - [x] **展示图重新拍摄(2026-10-13)** —— 原 `debug/shots*/` 10 张与 `assets/screenshots/conversation.png` 因含真实项目名与账户余额,已在隐私清理中删除并清除历史。现用**脱敏后重拍**的 5 张替代: `conversation-light/dark`(顶部横幅)、`settings-light`、`search-light`、`collapsed-light`, 内容为演示数据(acme-web / playground / ¥0.00 / ¥12.00)。拍摄脚本每次都会先把 项目名、会话标题、余额替换为演示值,并**逐张目检**后再入库——文本正则扫不出图里的信息。 - [x] **存量失败断言的处理** —— 已清零:本条原文针对"第 3 节的 3 个失败",这些断言已于 2026-10-12 全部重定基转绿(见 §3)。当前断言套件 35 通过 / 3 有意跳过(上游 monorepo 专属脚本,见 §2)/ 0 失败,发布前不存在"套件为红误导使用者"的问题。 --- ## 5. 环境与依赖遗留 - **`node_modules` 处于手工修补状态**:早期为绕过 `tsdown` 缺 `unrun` 的问题, 手工向 `node_modules` 塞入了 `unrun`、`synckit`、`@pkgr/core`,并为 `rolldown` 建了符号链接。 **下一次正规 `pnpm install` 会覆盖这些修补**,届时需确认 `unrun` 能被正常解析。 - **`packageManager` 与实际环境不一致**:`package.json` 声明 `pnpm@11.28.5`, 该版本要求 Node >= 22.13;而 ZCode 内置 Node 为 22.12,无法直接运行它。 本机可改用任意 Node 22.22.x(如 22.22.2-6,已验证可用),具体路径随各人环境而定。 - **本仓库无 CI**:`npm test` 包含 `pnpm run typecheck && vitest run && tsdown && node tests/run-assertions.mjs`, 其中 `pnpm` 在当前环境不可直接调用,需改用 `npm run` 对应脚本。 - **构建与测试必须用正确的 Node(2026-10-12 更正:此前"缺原生绑定"是误判)**: 原生绑定 `@rolldown/binding-darwin-arm64@1.2.4` 一直躺在 `.pnpm` 里、链接也正确; 真正原因是 **DSH 自带的 Node 24**(`~/.dsh/dsh-runtimes/.../node/bin/node`)在 macOS 上被代码签名拦住: `dlopen(...): code signature ... not valid for use in process: mapping process and mapped file (non-platform) have different Team IDs`。同一个 `.node` 在 `~/.workbuddy/binaries/node/versions/22.22.2-6` 下能正常加载。 **结论:跑 `vitest` / `tsdown` 时不要把 DSH 自带 Node 放到 PATH 前面**,用 22.22.2-6(满足 `engines`)或 PATH 上的 22.12。 用对 Node 后两者都正常:`vitest run` 47 个文件 / 311 个用例,`tsdown` 约 1.2 秒构建完成; `lib/` 已于 2026-10-12 19:52 成功重建。vitest 的真实失败基线见 §9。 - **真浏览器检查需要 Playwright 自带的 Chromium**:`./node_modules/.bin/playwright install chromium` (94 MB,装进 `~/Library/Caches/ms-playwright`)。本机已于 2026-10-12 安装 (`chromium_headless_shell-1243`)。缺它时 11 个布局脚本会以 `Executable doesn't exist` 失败—— 而**此前正是因为缺浏览器,这整套真机检查从未真正跑过**,把 5 个存量失败一起藏住了,见 §6。 - **本地门禁已就位**:`npm run gate`(= `node scripts/gate.mjs`,**四步:类型检查 → 断言套件 → vitest → 11 个真机布局脚本**; 含 vitest 后约 **70 秒**)。注意必须用能加载原生绑定的 Node(见上一条),否则 vitest 一步会以环境错误拦下。 pre-push 钩子在 `.githooks/pre-push`,启用方式 `git config core.hooksPath .githooks`(**本机已启用**, 2026-10-12 用临时裸仓库 `git push --dry-run` 实测过钩子确实被触发);`git push --no-verify` 可临时跳过。 已知红项集中登记在 `scripts/gate.mjs` 的三张表(`KNOWN_RED_ASSERT` / `KNOWN_RED_VITEST_FILES` / `KNOWN_RED_LAYOUT`),某项转绿时门禁会主动提示移除,避免登记表腐烂。 --- ## 6. 真浏览器布局套件:基线与逐条处理(2026-10-12) 在装上 Playwright Chromium 之前,`pnpm run test:layout` 里那 11 个脚本在本机**从未真正执行过**: 它们一启动就报 `Executable doesn't exist`,看起来只是"环境没配好",实际把 5 个存量失败一起藏住了。 装上浏览器后的首测基线是 **6 通过 / 5 失败**;逐条核查并修复后的现状是 **12 通过 / 0 失败**(`KNOWN_RED_LAYOUT` 已清空;2026-10-13 新增 `verify-a11y-and-overflow.mjs` 后由 11 项增至 12 项)。 | 脚本 | 首测失败原因 | 判定与处理 | | --- | --- | --- | | `verify-sidebar-titlebar.mjs` | 期望控件顶边 51px,实测 54px(`darwin/false/expanded`) | **口径过期**。核对后交通灯避让**本身没坏**:头部顶边仍正好等于宿主保留区 48px;变的是两项 ZCode 几何(头部 38→40px 使内边距 3→4px、控件 32→28px 使行内居中 +2px)。已改为断言真契约(头部顶边 === 保留区,控件落在保留区之下且不被推远),不再写死内部偏移;并用「把皮肤对保留区的消费改成 0px」注入故障**反向验证过它仍会红**(实测顶边 6 < 保留区 48) | | `verify-sidebar-transition.mjs` | 断言要求 `0.5s`,实测 `0.18s` | **口径过期**。P1-2 已把动效统一到 ZCode 的 150–200ms ease-out(`CodexSidebar.tsx`:`transition:grid-template-columns 180ms ease-out`)。断言更新为 0.18s,中间帧/收敛断言保留 | | `verify-workspace-drop-layout.mjs` | ① 空状态字体不统一 ② 截断标题必须滚动 | **两处都是过期口径**:① 合成页面未注入 `zcode-tokens.ts` 令牌 → 已按源码注入全部 `--dsw-*`(注入后 caption = 14−1 = 13px,原断言本身即成立);② 跑马灯已按实测反馈移除、改为纯 ellipsis → 断言改为双向:**不得再出现滚动动画** + **必须真的按 ellipsis 截断** | | `verify-composer-tools.mjs` | 字号 16px ≠ 13px | **夹具缺令牌**(菜单字号用 `var(--dsw-font-size-caption)`,解析失败回退继承值 16px)。三处合成页面补注入令牌后,原断言(含 18px 圆角、深浅底色)全部成立,未改断言 | | `verify-composer-style.mjs` | ① 圆角 25/20px ≠ 16px ② 发送键底色期望实色、实测透明 | ① **口径过期**,已按 ZCode 的 16px 更新 ② **是真问题**,已按方案 1 修复(令牌提升到根作用域),脚本转绿,见 §8 | **登记点**:`scripts/gate.mjs` 的 `KNOWN_RED_LAYOUT`(**现已清空**)。 门禁只拦截**新增**失败;某脚本转绿时会主动提示从登记表移除,避免登记表腐烂。 **尚未覆盖的场景**(真机检查与探测都没测):Windows / Linux、真实系统缩放(探测里用 `deviceScaleFactor` 近似)、全屏与分屏、多显示器。 --- ### 6.1 反复出现的坑:合成页面缺令牌(2026-10-13 根治) 布局脚本用 `page.setContent` 搭合成页面,只注入被测的样式表。样式表里的 `var(--dsw-*)` / `var(--dcu-*)` 在合成页面里**没有定义**——而 CSS 变量缺失不会报错, 只会静默回退(`font-size` 回退成继承值、`transition` 整条失效变 `0s`), 于是断言量到一个「看起来合理但错误」的数字,然后误报。 **同一个坑踩了两次,两次都是把字面量改成令牌之后发生的**: | 时间 | 触发改动 | 症状 | 当时的处理 | | --- | --- | --- | --- | | P1-1 | 工作区字号改用 `var(--dsw-font-size-*)` | 空状态被误报「字体不统一」 | 在 `verify-workspace-drop-layout.mjs` 里就地提取令牌并注入 | | 2026-10-13 | 动效改用 `var(--dcu-motion-*)`、hero 字号改用 `var(--dsw-font-size-display)` | `verify-sidebar-transition` 量到 `0s`、`verify-new-conversation` 量到 `16px` | 抽出共享模块 `scripts/fixture-tokens.mjs`,四个布局脚本统一改用 | **根治方式**:令牌块由 `scripts/fixture-tokens.mjs` 从**源码真值提取**(不手抄), `--dsw-*` 全量与 `--dcu-*` 全局令牌一起注入。新增令牌只要定义在 `zcode-tokens.ts` / `zcode-theme.ts` 里,所有 fixture 自动获得,不需要逐个脚本补。 > **教训**:把字面量改成令牌时,除了改样式表,还要问一句「谁在别处重新实现了这份样式」。 > 合成页面、测试夹具、文档里的示例都是"第二实现",它们不会跟着令牌一起升级。 ## 7. 上游 open issue 的继承风险(#40 探测记录) 上游 `MichengAI/dsh-codex-ui` 有两个未结 issue,本 fork 与它同源,风险共享: | issue | 症状 | 本 fork 实测(2026-10-12) | | --- | --- | --- | | [#40](https://github.com/MichengAI/dsh-codex-ui/issues/40) | 「侧栏会遮挡」「拖动不了」「显示不完全」 | **4 种窗口尺寸下均未复现**,详见下 | | [#41](https://github.com/MichengAI/dsh-codex-ui/issues/41) | 官方桌面版无法选择 Agent 预设 | 本机输入框底托条正常渲染「默认工作区 / ☁ 标准模式」,**未复现** | #40 的探测方法与结论(窗口 1280×800、1280×800@1.25、1024×700、900×600): - **遮挡**:`.dcu-wb-tree` 与 `.dcu-foot` 的盒子重叠为 **0px**(footer 是静态 flex 兄弟,不覆盖滚动区)。 首次探测曾把"行 box 落在 footer 之下的行"误判为遮挡,实为滚动容器正常裁剪—— 滚到底后最后一行**完整可见且可命中**,故该误判已撤回。 - **拖动**:HTML5 拖拽可用,拖拽指示 `dcu-wb-session-drop` 正常出现,1024×700 下顺序真的改变。 未能复现"拖不动"。 - **显示不完全**:`scrollHeight 436 > clientHeight 272` 属正常滚动;滚到底即可看到全部行。 **未验证**:非 macOS 平台、真实系统缩放、全屏/分屏,以及报障人提到的「回车当」(原文含义不明)。 --- ## 8. 已修复:发送键配色规则取不到令牌(令牌作用域问题) `verify-composer-style.mjs` 里那条「发送键底色」的断言,核查后确认**不是口径过期,而是皮肤里有一条失效声明**。 **现象与证据(2026-10-12 实测)** - 皮肤的规则是 `[data-conversation-scroll] [data-composer-card]>[data-input-scroll]+div button[class$=_primary]{width:28px;transform:none;background:var(--dcu-primary);color:var(--dcu-primary-foreground)}` (`src/client/CodexSidebar.tsx`)。 - 但 `--dcu-primary` / `--dcu-primary-foreground` 当时**只定义在 `zcode-theme.ts` 的 `ZCODE_THEME_STYLE` 中, 且作用域限定为 `.dcu-root` / `.dcu-settings-page` / `.dcu-home`**。 - 真实应用实测:`button[aria-label="发送消息"]` 的 `background-color` 为 **`rgba(0, 0, 0, 0)`**, 从按钮向上遍历 12 层祖先**没有任何元素定义 `--dcu-primary`** → 该 `var()` 解析失败。 规则里只有 `width:28px;transform:none` 真正生效(宽度实测确为 28px)。 **采用的修法(方案 1:补作用域)** 把这两个令牌从侧栏作用域**提升到根作用域**(`zcode-theme.ts`),并以同样的主题选择器给出暗色值: ```css :root{--dcu-primary:#000000;--dcu-primary-foreground:#ffffff} body[data-ds-dark-theme],.theme-zai-dark{--dcu-primary:#ffffff;--dcu-primary-foreground:#000000} ``` **影响面很小且已核实**:全仓库只有发送键那两处消费这两个令牌(`CodexSidebar.tsx` 的常态与 hover 规则), 所以提升作用域不会让其它 `--dcu-*` 消费者开始解析;`--dcu-surface` 等侧栏本地令牌**保持原作用域不动**。 **回归结果**:`verify-composer-style.mjs` 转绿(浅色 `#000` / 暗色 `#fff`,与 ZCode 主色一致); 该脚本的夹具原本只注入皮肤样式、不注入主题层,已同步补上 `ZCODE_THEME_STYLE`——否则夹具仍取不到令牌、 断言会继续测"空靶"。 > ⚠️ **产物已重建**(2026-10-12 19:52;此前"tsdown 失败"是 Node 选错导致,见 §5 的更正): > `lib/client.js` 现已包含本次修复。但**宿主仍需重启**才会加载新 bundle,重启前实机看到的仍是旧行为。 --- ## 9. vitest 基线 → 已全部转绿(2026-10-12) 此前因用错 Node(见 §5 的更正),`vitest run` 从未真正执行过。换对 Node 后首测: **47 个文件 / 311 个用例中有 5 个文件 / 20 个用例失败**。逐条定性后确认 **全部是测试侧问题,没有一个是产品缺陷**,共三类: | 类别 | 文件(用例数) | 根因 | 修法 | | --- | --- | --- | --- | | 硬编码上游身份 | `session-title-provider.spec.ts`(1)、`settings-registration.spec.ts`(1) | 断言写死 `michengai-codex-ui-session-title` / `@michengai/dsh-codex-ui/...`;fork 已改名为 `zcode-ui-session-title` / `dsh-zcode-ui/session-title` | 更新期望值(测试侧) | | 夹具 ctx 缺 `logger` | `client-runtime.integration.spec.ts`(7)、`official-sidebar-activate.spec.ts`(1) | 皮肤安装时会调用 `ctx.logger.info(...)`(`src/client/index.ts`),而两个夹具的 mock ctx 都没提供 `logger`;宿主真实 ctx 一定带它 | 夹具补 `logger: { info, warn, error }`(测试侧) | | macOS 路径符号链接 | `session-migration.spec.ts`(10) | 生产用 `realpath(target.path)` 规范化目标路径(`src/session-migration.ts:581`),而夹具的 `sessionPersistence.locate` mock 用**原始串**比较 `meta.cwd === targetPath`;macOS 的 `tmpdir()` 是 `/var/...`、realpath 为 `/private/var/...` → mock 返回旧工件 → 迁移守卫**按设计**拦下"同目录迁移" | 夹具临时根目录改用 `realpath(mkdtemp(...))`;**产品代码无需改动** | 修复后 **47 个文件 / 311 个用例全部通过**(`vitest run` exit 0);`scripts/gate.mjs` 的 `KNOWN_RED_VITEST_FILES` 已清空,此后任何 vitest 失败都会拦住提交。 > 教训:本仓库的宿主交互测试大量依赖 mock ctx 与**路径字符串比较**——同一份用例**在 macOS 上比在 > Linux CI 上更严格**(`/var` 符号链接、`ctx.logger` 是否存在)。新增这类夹具时:涉及路径比较先 `realpath`, > 涉及 ctx 必须提供 `logger`。 --- ## 10. compat 宿主 harness(`scripts/run-compat-host.mjs`)已可用,但上游脚本的 UI 期望尚未适配本 fork **2026-10-12:把这条 harness 从"跑不起来"修到"能真跑"**,共 6 处改动(全部在 `scripts/`,不涉及产品代码): | # | 位置 | 问题 | 修法 | | --- | --- | --- | --- | | 1 | `run-compat-host.mjs` | 安装目录仍找 `@michengai/dsh-codex-ui` | 改为裸包名 `dsh-zcode-ui` | | 2 | 同上 | patch 断言写死上游 id | 改为 `zcode-ui-session-title` / `dsh-zcode-ui/session-title` | | 3 | 同上 | 标题插件 id 断言写死上游名 | 改为 `zcode-ui-session-title` | | 4 | `verify-session-features-host.mjs` | i18n 命名空间用上游 `michengai.codexUi` | 改为本 fork 的 `zcode.ui` | | 5 | `run-compat-host.mjs` | 版本守卫用 `require.resolve('@deepseek-ai/dsh/package.json')`,而桌面版 App 的 CLI 把宿主打进 `app.asar`,解析不到 | 改成双路径:以 ` --version` 为权威来源(必检),能解析 `@deepseek-ai/*` 时再保留逐包校验;**只放过 `MODULE_NOT_FOUND`**,版本不符照旧抛出 | | 6 | 同上 | 假设 CLI 是 JS 入口,用 `node ` 调它;桌面版 CLI 是 **shell 垫片** → 语法错 | 按 shebang 判别:JS 入口用当前 node,垫片直接执行;`web` 子进程同样处理 | | 7 | 同上 | 隔离根目录用 `mkdtemp` 原串(macOS `/var/...`),宿主会规范化成 `/private/var/...`,而插件按会话 `cwd` 作键存输入历史 → 夹具种的历史取不到 | 根目录 `realpath` 化(与 `session-migration.spec.ts` 同一类修复) | **跑法**(约 1–2 分钟): ```bash export PATH="$HOME/.workbuddy/binaries/node/versions/22.22.2-6/bin:$PATH" npm pack --pack-destination /tmp DCU_DSH_BIN="/Applications/DeepSeek Harness.app/Contents/Resources/runtime/cli/bin/dsh" \ DCU_E2E_TARBALL=/tmp/dsh-zcode-ui-1.0.0.tgz DCU_E2E_VERSION=0.2.0-rc.2 \ node scripts/run-compat-host.mjs ``` 失败时 harness 会保留隔离 profile(路径打印在输出里,形如 `/private/var/folders/.../T/dcu-compat-host-xxxx`),可用来复现排查。`verify-compat-host.mjs` 里 种入输入历史用的 `michengai.codex-ui.input-history.v1` **是本 fork 保留的真实键名**(见 `src/client/input-history.ts`),不要"顺手改成 zcode"。 **当前卡点(需要决策,未擅自适配)**:修完上述 7 处后,harness 已经能装包、起宿主、跑第一个脚本,并且 过了输入历史调回(ArrowUp)、空草稿建议、兼容面板三项检查,最终停在 `verify-compat-host.mjs:66`: ```js await page.getByRole('button', { name: '任务', exact: true }).click() // 30s 超时 ``` 原因是**上游导航与本 fork 不同**:上游插件的侧栏首项是「任务」,而本 fork 的侧栏已改为 ZCode 结构 (新建任务 / 扩展管理 / 底部的记忆·任务·归档由宿主或其它插件提供)。因此这一条(以及后面的 `verify-settings-exit` / `verify-session-features-host`)需要按本 fork 的导航结构适配,**并逐条登记是 设计变更还是缺陷**——处理原则与 §3.1 相同,不要把"当前代码怎样"直接写成契约。 在适配完成前,这条 harness **不要**纳入 `scripts/gate.mjs`(它是一个独立的重型档,需要 tarball + 隔离宿主 + 约 1–2 分钟)。 ### 10.1 已定位的下一处卡点:全局面板打开后没有"返回会话"的入口 用常驻隔离宿主 + 探针(把 `verify-compat-host.mjs:66` 临时替换为打印**全部矩形可见**的控件,跑完已还原)确认: 打开 `sidebar.panellist` 的全局面板后(本例是兼容测试面板夹具),本 fork 的侧栏可见控件只有: `新建任务 / 收缩侧边栏 / 搜索会话 / 兼容测试面板 / 扩展管理(专家·技能·插件·连接器·定时任务·IM助理) / 置顶 / 项目 / 最近 / 设置` ——**没有任何"返回会话/关闭面板"的专用控件**;再点一次面板入口与按 Escape 都无效(面板仍在、输入框仍被替换掉)。 上游插件的 `任务` 入口正是承担这个返回职责,而 ZCode 侧栏结构里不存在该入口。两种解读,需要决策: - **设计变更**:ZCode 侧栏靠「点击会话行 / 新建任务」回到会话区(真实使用中有会话行,因此能回去;本隔离宿主里恰好没有会话行)。 → 那么 `verify-compat-host.mjs` 这一条应改为点击会话行,或改断言目标。 - **真 UX 缺口**:全局面板打开时缺少明确的返回入口(用户可能"被困"在面板里,只能靠会话行)。 → 那么应改**产品 UI**:面板打开时提供返回/关闭控件。 **在决定之前不要改这一条断言**。后续还有 `verify-settings-exit.mjs` / `verify-session-features-host.mjs` 两个脚本未跑到 (harness 遇首败即停)。另注:`verify-compat-host.mjs` 会往 `DCU_E2E_WORKSPACE` 写 `compat-reference.txt`, **重复跑同一工作区会 EEXIST** —— 迭代时先删掉该文件,或用全新工作区。 ### 10.2 已解决:全局面板缺返回入口(产品修复)+ 新暴露:建议菜单在 900px 宽下越出视口 **§10.1 的困境已按"真 UX 缺口"处理并修好(产品侧)**: - 侧栏新增显式「返回会话」入口,**只在全局面板打开时出现**,动作是 `selectPanel(null)`(打开面板的逆操作, 宿主会恢复此前主视图,会话与草稿都保留)。展开态是带文字的菜单项,紧凑轨是同名 aria-label 的图标按钮。 复用已有能力,没有新接管线(`session-navigation.ts` 的 `openConversation` 早就是这个语义)。 - 两处脚本适配(`verify-compat-host.mjs` 第 66 / 78 行):上游点的是「任务」导航项,本 fork 改点「返回会话」。 **这两处是"设计变更 + 产品补齐"后的等价操作,不是把断言改成迎合代码**——原缺口已用产品改动补齐。 - 回归:门禁四步全绿(断言 34 通过 / vitest 311 通过 / 布局 11 通过 / tsc),说明新增入口没碰坏任何既有契约; harness 端到端也已验证「返回后草稿仍在、会话 id 不变」。 **接着暴露的新问题(真实、非口径)**:脚本推进到 `verify-compat-host.mjs:114` 的 「建议菜单不得越出视口」断言失败: ``` dark / width=900 / 触发符 "/" outer=672 inner=662 left=-69.30 right=602.70 ``` 即建议菜单在 900px 宽下**左移出视口 69px**。两个要点: 1. `scripts/verify-composer-tools.mjs`(本 fork 自己的菜单脚本)只覆盖 **390 / 1280** 两个宽度, **900 恰好没覆盖** → 这是一处真实的测试覆盖缺口,被 compat harness(真实宿主)抓出来了。 2. 需要判断是**宿主演进**(0.2.0-rc.2 改了菜单锚点/尺寸约束,而 fork 的覆盖规则随之失效)还是 **fork 自身定位缺陷**;本 fork 的合成夹具自建 `[data-composer-card]`,看不到真实宿主下的锚点差异。 → 复现方式:按 §10 跑法起隔离宿主,把视口设 900、在输入框敲 `/`,量 `[data-trigger-menu]` 的 `getBoundingClientRect()` 与宿主锚点元素的相对关系。 **§10.2 追加证据(同日)——越界的成因已排除"我们的令牌",但还没定论**: 在失败点插探针(跑完已还原)量到(视口 900、dark、触发符 `/`): ``` card: class yhfFVG_card left=-53 right=610 w=663 max-width:672px anchor: class yhfFVG_overlayAnchor left=-53 right=610 w=663 position:absolute menu: left=-53 right=610 w=663 css left/right = 0 ← 菜单与锚点完全一致 tokens: --dsh-chat-content-width:640px --dsh-composer-card-max-width:calc(640px + 32px) ``` 随后核对宿主包(`@deepseek-ai/dsh-client-ui-conversation@0.2.0-rc.2`)发现: - **宿主自己也声明同一个公式**:`--dsh-composer-card-max-width:calc(var(--dsh-chat-content-width) + 32px)` (另有候选 `min(calc(100% - 16px), 952px)`);fork 在 `CodexSidebar.tsx:141` 的 `[data-conversation-scroll]` 上 重声明的是**与宿主一致的公式**,不是新引入的约束。 - 宿主的卡片规则是 `.…_card{box-sizing:border-box;width:100%;max-width:var(--dsh-composer-card-max-width)}` → 卡片宽度由**容器**决定,`max-width` 只会封顶,**不可能把卡片撑出容器**。 因此 `left=-53` 更可能是**整列偏左 / 文档发生了横向滚动**(−53 与"容器宽 663、被右移约 293px"等价, 293 与侧栏 240 量级相近),而**不是**本插件令牌造成的。菜单忠实跟随卡片(`left/right:0`)属于正确行为。 **下一步的决定性检查(尚未做)**:在同一个 900px、dark、`/` 的现场,同时记录 `window.scrollX` / `document.documentElement.scrollLeft`、会话列容器(卡片父级)的 rect、 以及 `[data-conversation-scroll]` 的 rect。若 `scrollX > 0`,说明是横向滚动导致(应查是谁把文档撑宽了: 很可能是被打开的某个面板或菜单),**不要**先改本插件的令牌公式。 **已顺带修掉的覆盖缺口**:`scripts/verify-composer-tools.mjs` 的宽度矩阵由 `[390, 1280]` 扩为 `[390, 900, 1024, 1280]`(3 处循环)并跑通;这也证实该越界**无法**在合成夹具里复现 (合成卡片不消费那个令牌),真机 harness 才是能抓到它的地方。 ### 10.3 compat harness 已端到端跑通(2026-10-12) 三个活宿主脚本全部通过、`pageErrors` 为空: - `verify-compat-host`:输入历史调回、空草稿建议、兼容面板与选中反馈、**返回会话且草稿保留**、紧凑轨导航与动态注销/重注册,以及建议菜单几何(三视口 × 两种触发符 × 明暗)。 - `verify-settings-exit`:返回、Escape、进入中退出、减少动态效果四条交互。 - `verify-session-features-host`:工作区树接管侧栏、隔离宿主可移动会话与目标项目、会话菜单移动入口、移动接口归位。 **过程中定性掉的一处"假缺陷"(重要,别再当 bug 修)**:`verify-compat-host` 曾在 `width=900 / dark / 触发符 "/"` 处报 「建议菜单越出视口」(`left=-52.9`)。逐层探针查明:页面自身没有滚动(`scrollX=0`、文档 900=900),而是 **宿主 app frame 被横滚了 264px** —— 宿主的 `rightbarCol` 宽度为 `0` 却带 **640px 隐藏内容** (来自 `compat-panel` 夹具面板),使 frame 的 `scrollWidth=1540 > 视口`;浏览器为保持焦点元素可见横滚了 frame, 于是所有 `getBoundingClientRect()` 一起左移,而菜单与卡片 `left/right` **完全一致**(插件行为正确)。 → 结论:**不是插件缺陷,也不要为此改插件的 `--dsh-composer-card-max-width` 公式**(宿主自己用的是同一公式, 且卡片是 `width:100%; max-width:该令牌`,只会封顶不会撑宽)。修法是在测量前把 frame 的横向滚动归零(已加注释说明成因)。 **跑通命令**(约 1–2 分钟;`npm run test:compat` 需要以下三个环境变量): ```bash export PATH="$HOME/.workbuddy/binaries/node/versions/22.22.2-6/bin:$PATH" npm pack --pack-destination /tmp DCU_DSH_BIN="/Applications/DeepSeek Harness.app/Contents/Resources/runtime/cli/bin/dsh" \ DCU_E2E_TARBALL=/tmp/dsh-zcode-ui-1.0.0.tgz DCU_E2E_VERSION=0.2.0-rc.2 \ node scripts/run-compat-host.mjs ``` **仍不纳入 `scripts/gate.mjs`**:它需要 tarball + 隔离宿主、耗时 1–2 分钟,属于独立的重型档(只在改动宿主交互时手动跑)。 **已知操作细节**:`verify-compat-host` 会往 `DCU_E2E_WORKSPACE` 写 `compat-reference.txt`, **同一个工作区重复跑会 `EEXIST`** → 复用工作区前先删该文件,或让 harness 每次新建隔离目录(默认行为即是)。 ### 3b 附注(2026-10-12 视觉复验发现):第 2 条此前改错了组件 §3b 第 2 条当时改的是 `session-tree.tsx` 的 `SessionHoverCard`,但**侧栏工作区树(即实测反馈里的那张卡)走的是 `workspace-hover-card.tsx` 的 `WorkspaceHoverCard`**;前者只服务定时任务 / IM 频道里的会话卡。所以侧栏那张卡 一直仍在显示「标题 + 时长 + 项目名」,**上一轮修复对它完全没生效**。 - 已修:`workspace-hover-card.tsx` 的**会话分支**只去掉时长与项目行(行内右侧已常驻时长、项目已由树形层级表达), 保留分支行;**工作区行**的 pin / 数量 / 路径 / 编辑入口一律不动。 - 已修断言:`settings-integration.assert.ts` 里那条「会话时间必须出现在悬停卡片右上角」——它把**旧设计写成了契约**, 正是它让"改错组件"没被发现。现改为反向断言(不得渲染时长 / 项目行,必须保留分支行)。 - 复验:隔离宿主(真机浏览器)实拍,悬停卡现在只显示完整标题;同图另见发送键为 ZCode 主色实心圆。 - 遗留 → **已处理(2026-10-12)**:`CodexWorkspaceBrowser.tsx` 的 `.dcu-wb-tip-time{...}` 死规则已删除 (渲染方自悬停卡修复那次提交起不存在,`session-tree` / `settings-integration` 两条断言反向守护)。 ## 11. 侧栏"意义不明的直线"(2026-10-12 实测反馈) **结论:本插件自己画了 2 根分隔线,已全部去掉;侧栏里剩下的横线属于计费插件,不是本插件画的。** 排查方法(可复用,无需重启宿主):用现签 `dsh-auth-*` cookie 的 headless 脚本连上实机, 在侧栏(`.dcu-root`)内统计"宽度 > 60px 且 border-top/bottom 可见"的元素,逐个打印类名与 y 坐标; 再用 `addStyleTag` 临时禁用怀疑的规则做 A/B 对照。 实测量到的 5 根横线: | y | 类名 | 归属 | 处置 | | --- | --- | --- | --- | | 160px | `dcu-workspaces` | 本插件(工作区块上沿) | **已删除** | | 689px | `dcu-foot` | 本插件(底栏上沿) | **已删除** | | 698px | `cm-footer-stack simple` | 计费插件 | 不动 | | 699px | `cm-simple-summary` | 计费插件(卡片边框) | 不动 | | 762px | `cm-peak-track` | 计费插件(进度条本体) | 不动 | 处置依据:`docs/ZCODE-DESIGN.md` 的「侧栏几何」只规定 `px-2/py-3/gap-1` **间距**,没有内部分隔线; 分区靠间距区分。同时删掉的还有 `.dcu-footer-actions>*+*` 那条"底栏逐项画线"(它会在底栏有多个真实盒子时 堆出一排线;实测在当前宿主下这些槽锚点是 `display:contents`,所以它没画出来,但属于同类过度装饰)。 反向断言已加在 `tests/conversation-visuals.assert.ts`:工作区块与底栏都不得再出现 `border-top`, 底栏入口之间也不得逐项画线——避免以后又被"顺手"加回来。 **没有动的**:计费插件的卡片边框与进度条(那是该插件自己的 UI 与数据可视化,不属于本皮肤该管的部分; 若确实想去掉卡片边框,应改那个插件或与本皮肤协商,不要在这里用 `!important` 覆盖别人的组件)。 --- ## 12. 2026-10-13 第二轮:设计制度收紧与自身回归修复 本轮目标是「把已有的偏离收归标度、把设计规则写成断言、把管不到的如实登记」,不动信息结构。 ### 12.1 已修复 | 项 | 内容 | 证据 | | --- | --- | --- | | 离标度圆角收归 | `18px / 10px / 9px / 7px / 3px / 1px / 99px` 全部改为 `var(--dsw-radius-*)`;新增 `--dsw-radius-2xl:16px`(白名单大圆角)与 `--dsw-radius-full:999px`(胶囊),并让 2xl 真正有消费方(搜索弹窗 / 主输入壳 / 轮次摘要浮层 3 处) | `npm run gate` 13/13;`grep -rn radius-2xl src/` 有 3 处消费 | | 死兜底清理 | 清掉 `var(--token,#fallback)` 中令牌已定义、兜底永不生效的 4 处(`--dcu-tip-bg` / `--dsw-alias-state-warn-*` / `--dsw-alias-scrollbar-hover-l1`) | 同上 | | 菜单暗色底纠正 | 主区工具菜单暗色底 `#292929`(离板灰)→ ZCode 菜单色 `#2b2b2b` | `ZCODE-DESIGN.md` §2 | | reduced-motion 停静止帧 | 时长归零管不到 `infinite` 动画;现把进度波浪停在 28%(保留「进行中」可读性)。旋转指示的归零留在组件层(同一份样式内同时定义 keyframes 与归零,更内聚) | 实机 `emulateMedia(reduced-motion:reduce)` 下 `animation-name:none`,元素仍在渲染(16×20) | | 主区嵌入面容器边界 | **试过并撤回**:写过 `:root:root [data-slot="main"] iframe{border-radius:…}`,实机像素比对证明它**没有任何可见效果**(见 §12.3 #8),属空操作,已删除 | `git diff` 中不再有该规则;否定结论记在 §12.3 #8 | | 圆角断言由「值白名单」改「棘轮」 | 原断言锁「值 ∈ 允许集」,把令牌改回 `999px` 仍绿(假断言)。现按 `文件:值 → 次数` 登记存量,未登记的字面量一律红 | 反向测试:把 `var(--dsw-radius-full)` 改回 `999px` → 立刻报「未登记的字面量」 | | 模板串截断守卫 | 新增第 13 节:对**抽取结果**校验前中部标记 **+ 尾标记**(只查前中部会漏掉尾部截断) | 反向测试:往主题层注释塞一对反引号 → 报「必须延伸到模板末尾」 | | 偶发断言修复 | `scripts/verify-composer-style.mjs` 用 `getBoundingClientRect()` 浮点值做严格相等(实测拿到 `28.000003814698`),且首帧可能读到过渡中间值 | 改为容差比较 + 首次测量前 150ms settle;连跑多次稳定 | ### 12.2 本轮自身引入并已修复的回归(记录在案) **误删 `--dsw-radius-panel`**:它**不是**自家死令牌,而是对**宿主**令牌的覆盖——宿主 `@deepseek-ai/dsh-client-ui-theme` 在 `:root` 定义 `--dsw-radius-panel:28px`,被 Modal(对话框)、`ui-settings-general`(设置面板)、`ui-message-feedback`、`ui-conversation`(`cardWorkspaceTrigger:after` 虚线投放轮廓)与 `dsh-web-frontend`(浮动面板 + `calc(var(--dsw-radius-panel) - 4px)` 的缩放角)消费。本插件用 `:root:root`(0,2,0)压过宿主 `:root`(0,1,0),删掉即上述表面回落到 **28px**。 - 实机复核:删除后 `getComputedStyle(document.documentElement).getPropertyValue('--dsw-radius-panel')` = `28px`;恢复为 `12px` 后 = `12px`。 - 现取值 `12px`(一级容器)而非宿主 28px:与卡片/控件同一语言,且相对上一版 10px 只 +2px。断言要求它**存在且 ∈ {12px,16px}**——把它删掉或改到标度外都会红。 - **教训**:判断「某令牌有没有消费方」时,`grep src/` 只覆盖自家代码;**宿主也消费同名令牌**。这类名字是共享命名空间,不是私有变量。 ### 12.3 登记为后续(带证据与建议,本轮不做) | # | 项 | 证据 | 建议 | | --- | --- | --- | --- | | 1 | 菜单底色仍写 `#fff` / `#2b2b2b!important`,未走宿主菜单真令牌 | 宿主 `--dsw-specific-menu` = `#f8f9faf0` / darwin 暗色 `#303136f0`,配 `backdrop-filter:var(--dsw-menu-backdrop-filter)`;本插件的 `!important` 抹掉了那层半透明与模糊 | 在 `ZCODE_LIGHT/ZCODE_DARK` 加 `--dsw-specific-menu`,删掉两条主题分支。**前置**:先修下面的 #2,否则夹具取不到正确的明暗值 | | 2 | `fixture-tokens.mjs` 把明暗两套令牌平铺进同一个 `:root{}`,重复键**暗色胜出** | 实测合成页 `--dsw-alias-border-l2` = `rgba(255,255,255,.1)`(应为亮色 `rgba(13,13,13,.1)`) | 改成 `fixtureTokenStyle(modality)` 只注入对应那一份;或在文件头把「重复键以暗色为准」写成显式契约并加断言 | | 3 | 宿主 `--dsw-radius-*` 命名空间被整体接管 | 宿主 `xs4/sm8/md12/lg16/xl20/panel28` vs 插件 `2/4/6/8/12/16`;宿主多处按名消费(`ui-jobs` / `ui-plugin-manager` / `ui-user-questions` badge 等),`lg` 期望 16px 实得 8px | 记在案即可(这是"令牌优先换肤"的固有代价);若要收敛,需改为只覆盖真正需要的角色,而不是整个标度 | | 4 | 主区 iframe 无同源约束 | `[data-slot="main"] iframe` 会命中未来任何注入 main 的 iframe(含外部站点) | 已知行为:今天只加圆角、影响面 1 个面板;如需收紧,加排除名单或同源判定 | | 5 | 圆角字面量存量 43 处(2/4/6/8px 共 36 + `0`×2 + `50%`×5) | 棘轮基线在 `tests/design-system.assert.ts` 第 9 节;`grep` 可复现 | 36 处是**零视觉变更**的可行迁移(令牌值=字面量),但会连带改 8 条锁字面量的断言(`session-tree` / `settings-integration` / `conversation-visuals`);建议单独一轮做 | | 6 | 分组标题 sticky 未交付 | 本机工作区**没有项目分组**,`.dcu-wb-collection-head` 数量为 0;且第一版把它打在该元素上属于**选错目标**——用户真正看到的分区标题是 `.dcu-wb-section-head`(置顶/项目/最近) | 运行时注入验证过:打在 `.dcu-wb-section-head` 上有效(滚动后 delta=0、宽度不变、无横向溢出、z-index 2、悬停仍不透明)。要做的话应连同 `collection-head` 一起,并补一条真浏览器断言(需要带分组的工作区或合成夹具) | | 7 | 平台覆盖仍只有 macOS 单窗口 | §6 已登记 | 不变 | 上述 #6 之前,本轮还做过一次**已撤回**的尝试,结论如下: | # | 项 | 证据 | 结论 | | --- | --- | --- | --- | | 8 | **跨文档第三方面板:皮肤层面不存在有效的容器手段** | ① 面板在 `