diff --git a/CHANGELOG.md b/CHANGELOG.md index 872bf1d..e0e5b63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,108 @@ [简体中文](CHANGELOG.zh-CN.md) +## 0.6.0 - 2026-09-28 + +Face B of the model picker (the Codex reasoning power rail) lands, together with a full-repository review against the +replication spec and fixes for what it confirmed. + +### ⑳ Model picker face B: our own component takes over the model seat + +- **Seat takeover in the DOM, never parasitic on the host menu** (0.5.0's CSS re-layout was slow and bare, and was + reverted). Our trigger is appended to `[data-slot="conversation.input.model"]` and the popover to `document.body`; + while our trigger is in the seat, **one** direct-child `:has()` in `model-picker.css` sets the host's child to + `display:none` (it stays in the React tree). No marker attribute: React replacing the host child would drop it and the + host control would flash back; `:has()` only asks "are we seated", and removing our trigger restores the host at once. +- **Data and commits go through the host only**: `ctx.inject(['modelDirectories'], …)` waits for the service (the same + hook the host uses for its own seat; a missing service never blocks the skin), subscribes to `directoryFor(session).store` + and commits with `select({ provider, model, reasoningEffort })`. The session id comes from the seat's + `data-conversation-session` ancestor (the right-bar side chat has its own seat), falling back to `uiSession`. Seats the + host did not render (subagent sessions) are left alone. +- **Power rail verbatim from the Codex source**: track 24px / radius 12 / 10% foreground / `inset .5px` stroke; ticks + 4px with a 16px hit area, passed ticks 30% white; thumb a 28px white disc / `.5px` strong stroke / `0 0 2px` shadow; + filled part in the accent, ending at the thumb centre; motion `.3s cubic-bezier(.23,1,.32,1)`, thumb 0s on the first + frame and .3s after 16ms. Popover 254px wide (`spacing × 63.5`), enters in `.32s … 30ms` from `scale(.98)`; placed like + the host `place()` (right-aligned, 8px above, 12px viewport margin). +- **Interaction**: real drag (grab on press → free slide with **no commit** → one commit snapped to the nearest level on + release), `touch-action: none`; ←/→/Home/End; keyboard focus draws the 2px ring on the thumb; Escape closes and returns + focus (also when a mouse-open left focus on the trigger); choosing a model commits that model's default effort and + closes on success. Level count and names come from `reasoning.efforts`. +- **During the round trip** (the host marks the directory `selecting` for the whole selectModel round trip): status is + not part of the list signature, so the card is neither redrawn nor cleared; the rail and trigger show the pending level + optimistically with a spinner beside it (on a model change, at the end of that row). On failure the popover shows the + host's own message (with the session-in-use case spelled out) and the rail returns to the effective level. +- The trigger's effort label follows Codex `_ModelPickerTriggerEffortText`: all names stacked in one cell with a blurred + cross-fade, so the width never jumps. +- Copy is borrowed from the host `model` namespace first (word-for-word with the native menu, built-in model descriptions + localized too); our own table is only the fallback. +- New settings row "Codex model picker" (`modelPicker`, **on by default**). Off removes every node of ours and the host + menu (⑫ face A) returns at once; while the settings document has not arrived the component does not take over, so it + never flashes on and off. **`Config` therefore has 12 fields** (the replication spec lists 11 plus the theme preference). +- **Not built**: the three advanced states of the Codex rail (highlight, Fast-mode tick fly-out, the purple/blue gradient + beyond the maximum) — DSH has neither Fast mode nor that state; the purple Max label on the trigger is skipped too, + being outside the color whitelist. +- Note: 606faea once committed a 0.6.0 under the same name, but only the generated `client.js` — the component and style + sources, the build change, the checks and the probe never reached the repository — and it was reverted. This release is + a complete redo with sources, build and verification committed together. + +### Review fixes + +- **Two sources for the composer shadow**: `patches.css` ⑧ still carried `[data-composer-card] { box-shadow: + var(--dsw-elevation-panel) }`, a second source next to `composer.css` ⑭'s three Codex layers (specificity decided the + winner). The host only renders this card inside the conversation scroller; removed. +- **`--dsw-elevation-soft` is no longer invented**: 0.5.10's `0 8px 28px` at 10% ink (55% black in dark) had neither a + measurement nor a source anchor, yet it landed on the host `SegmentedControl` thumb — every segmented control in + settings wore it. The host's own value is back in charge. +- `--dsw-elevation-stroke` is now `0 0 0 .5px var(--dsw-elevation-stroke-color)` (the spec's wording; computed value unchanged). +- **Font stacks** are declared verbatim once in `skin.css` (checked word-for-word against the host 0.1.7-rc.2 + base_css_default), so the look no longer drifts with the host version. +- **Focus ring wired to the host token**: `--dsw-focus-ring-color: var(--dsw-codex-focus)`. The host's ten box-shadow ring + rules used to fall back to ink and are now Codex blue; the skin's own rings read the token too, which makes them honour + the host's "no ring for pointer input" contract (under `html[data-input-modality=pointer]` the host sets it to + transparent in place). +- New `--dsw-codex-border` (10% / 12%) and `--dsw-codex-border-strong` (15% / 20%) — Codex's `--color-border` and + `--color-border-strong`. +- **Wrong dark default on the settings card**: `SKIN_DEFAULTS.dark.surface` was still `#181818` although the dark window + background has been `#111111` since 0.5.6, so the background swatch showed a "follow skin" value that was not the skin's. + Fixed; `check-repo` now reconciles every field against skin.css and fails on drift. +- `.cx-error` read a token that does not exist (`--dsw-alias-state-error`), so "the save did not take effect" was plain + text colour; it now reads `error-primary`. +- **Font stack validation hardened**: `isFontStack` also rejects backslashes (`u\72l(` tokenizes as `url(`), comment + delimiters (a comment opened inside the value swallowed the whole dark override block), control characters and + unbalanced quotes. `check-repo` asserts the "dark block survives" outcome on the generated CSS. +- **The installer wrote invalid YAML on a fresh profile**: dsh rc.2 creates `cordis.patch.yml` as a flow-style `[]`, and + appending `- insert:` after it made `dsh web` die at boot with `failed to parse overlay`. An empty `[]` is now removed + first; a non-empty flow list is refused with a hint. +- **A local path shipped in the artifacts**: a clone path in a `composer.css` comment was copied verbatim into `theme.css` + and `client.js`, while the repository check only scanned JS and missed the JSON-escaped double backslash. The check now + covers stylesheets and docs in both spellings; local paths in `docs/` became placeholders. +- Stale comments: card radius 10px → 12px, "links are ink" → accent, 100ms → 150ms transitions, composer 14px → 16px, + ⑫ row radius 8 → 13, `install-plugin.mjs` → `src/build.mjs` in the layer headers, "five stylesheets" in `build.mjs`, + and the skin README's file table and "links are ink" rule. + +### Verification + +- **New** `scripts/power-rail-verify.mjs` (47 assertions, Chromium only): the real component and the real theme.css + against a fake directory written to the host contract, with `select()` slowed to 600ms on purpose — the real host + answers locally in under 60ms, too fast to observe pending behaviour in the live GUI. It found and fixed two defects: + built-in model descriptions rendered the dictionary key when no host dictionary was present, and Escape did nothing + while a mouse-open left focus on the trigger. +- **New** `scripts/pack-host-asar.mjs`: without a desktop shell, packs npm-installed host packages (the same set the shell + ships) into an `app.asar` the fixtures can read. +- `hero-verify.mjs`: "badge radius stays the official 6px" hard-coded rc.1's literal; rc.2 changed it to + `var(--dsw-radius-xs)` (the host gives 4px), so it always failed on rc.2. The expected value is now read from the host source. +- `live-gui-probe.mjs`: a fresh profile opens with two onboarding dialogs and the probe's clicks landed on their backdrop + — that is where the three "measured none" right-panel failures came from; the dialogs are dismissed first now. The model + seat is measured as face B (7 assertions: takeover, geometry, a keyboard change written into the host store and reverted); + with face B off it measures the face A pending window, with `--latency` (800ms by default) added to that round trip, + otherwise the window closes before the first sample. The probe puts the effort back afterwards. +- `settings-page-verify.mjs`: 9 rows; the dark background assertion still expected the pre-0.5.6 `#181818` and now expects + `#111111`; five new assertions for the switch (off, reload, no trigger of ours in the seat, host control visible, reset). +- Full run (npm `@deepseek-ai/dsh@0.1.7-rc.2` + an asar packed from it + Edge/Chromium 153; live GUI on a `dsh web` of the + same version): `check-repo` 23/23 · `audit` 36/36 · `elevation` 19/19 · `composer-shadow` 23/23 · `model-picker` 20 · + `power-rail` 47/47 · `rightbar` 42 · `hero` 25 · `sidebar-align` 6/6 · `sidebar-surface` 13/13 · live `live-gui-probe` + face B 14/14, face A 10/10 · `settings-page-verify` 29/29 · `theme-flash-probe` 9 windows, 0 intermediate frames (click → colour median 23ms). + ## 0.5.10 - 2026-09-28 A third Codex capture let the composer's ring be pinned down without depending on the device pixel ratio — and it diff --git a/CHANGELOG.zh-CN.md b/CHANGELOG.zh-CN.md index 49474e8..4d7739f 100644 --- a/CHANGELOG.zh-CN.md +++ b/CHANGELOG.zh-CN.md @@ -2,6 +2,80 @@ [English](CHANGELOG.md) +## 0.6.0 - 2026-09-28 + +模型选择器的 B 面(Codex 推理等级功率轨)落地;外加一轮对照复刻规格的全仓审查,修掉其中查实的问题。 + +### ⑳ 模型选择器 B 面:自建组件顶替模型位 + +- **做法是「DOM 顶替席位」,不寄生宿主菜单**(0.5.0 那条 CSS 重排的路卡顿、简陋,已 revert)。 + 自己的触发器追加进 `[data-slot="conversation.input.model"]`,弹层挂 `document.body`;席位里有我们的触发器时, + `model-picker.css` 用**一条**直接子代 `:has()` 把宿主那一格 `display:none`(仍在 React 树里)。不打标记属性 —— + React 换掉宿主子节点时标记会丢、宿主控件闪回;`:has()` 只看「我们在不在席」,摘掉触发器宿主立刻复原。 +- **数据与提交只走宿主**:`ctx.inject(['modelDirectories'], …)` 等服务(与宿主自己挂模型位同一个口子,服务缺席不阻塞皮肤), + `directoryFor(会话).store` 订阅,`select({ provider, model, reasoningEffort })` 提交。会话 id 取席位祖先的 + `data-conversation-session`(右栏侧边聊天有自己的席位),取不到再退到 `uiSession`。宿主没渲染的席位(子代理会话)不接管。 +- **功率轨按 Codex 源码逐字**:轨 24px / 圆角 12 / 前景 10% / `inset .5px` 描边;档位点 4px、热区 16px,走过的 30% 白; + 拇指 28px 白片 / `.5px` 强描边 / `0 0 2px` 微影;已选段强调色、止于拇指中线;主曲线 `.3s cubic-bezier(.23,1,.32,1)`, + 拇指首帧 0s、16ms 后抬到 .3s。弹层宽 254px(`spacing × 63.5`),入场 `.32s … 30ms` 自 `scale(.98)`; + 定位照宿主 `place()`(右沿对齐、上方 8px、视口留 12px)。 +- **交互**:真拖拽(按下抓取 → 自由滑动、**不提交** → 松手对齐最近档提交一次),`touch-action: none`;←/→/Home/End; + 键盘焦点把 2px 环画在拇指上;Escape 关闭并还焦点(鼠标打开后焦点在触发器上时也能关);换模型连带该模型的默认档提交,成功后关弹层。 + 档位数与名字全部来自 `reasoning.efforts`。 +- **往返期间**(宿主在整个 selectModel 往返里把目录标成 selecting):列表签名里没有 status,卡片不重画、不清空; + 轨与触发器按 pending 那一档乐观显示,档位名旁转圈(换模型时是那一行行尾转圈)。失败时弹层顶上给宿主同款提示 + (会话被占用单独一句),轨退回生效档。 +- 触发器档位文字照 Codex `_ModelPickerTriggerEffortText`:各档名叠在同一格、模糊交叉淡入,宽度不跳。 +- 文案先借宿主 `model` 命名空间(与原生菜单逐字一致,内置模型的说明也跟着本地化),宿主字典缺席才用自带表。 +- 设置卡新增一行「Codex 模型选择器」(`modelPicker`,**默认开**)。关掉即撤走全部自建节点,宿主原生菜单(⑫ A 面)立刻复原; + 设置文档还没到时不先接管,免得开了又撤闪一下。**Config 因此是 12 个字段**(复刻规格写的是 11 个 + 主题偏好)。 +- **没做**:Codex 功率轨的三个进阶态(高亮、Fast 圆点飞出、超出最大档的紫蓝渐变)—— DSH 没有 Fast 模式也没有那一态; + 触发器上 Max 档的紫色不做,它在彩色白名单之外。 +- 注:606faea 曾以同名 0.6.0 提交过一次,但只提交了生成物 `client.js`,组件与样式源文件、构建改动、体检与探针都没进仓库, + 随即 revert。本版是完整重做,源码、构建与验收一起入库。 + +### 审查修正 + +- **输入卡阴影双真源**:`patches.css` ⑧ 还写着 `[data-composer-card] { box-shadow: var(--dsw-elevation-panel) }`, + 与 `composer.css` ⑭ 的 Codex 三层阴影是同一元素的两个真源(谁生效只看权重)。宿主只在会话滚动区里渲染这张卡,已删。 +- **`--dsw-elevation-soft` 不再自造**:0.5.10 的 `0 8px 28px 10% 墨`(暗色 55% 黑)既无读数也无锚点,却落在宿主 + `SegmentedControl` 的选中片上(设置页每个分段控件都背着它)。交还宿主自己的值。 +- `--dsw-elevation-stroke` 改为 `0 0 0 .5px var(--dsw-elevation-stroke-color)`(规格原文;计算值不变)。 +- **字体栈**在 `skin.css` 逐字声明一次(与宿主 0.1.7-rc.2 的 base_css_default 逐字比对一致),外观不随宿主版本漂。 +- **焦点环接到宿主令牌上**:声明 `--dsw-focus-ring-color: var(--dsw-codex-focus)`。宿主 10 条用 box-shadow 画环的组件规则 + 此前落回墨色回落值,现在是 Codex 蓝;皮肤自己的环也改读它,于是继承宿主「指针操作不出环」的约定 + (`html[data-input-modality=pointer]` 时宿主把它就地置成 transparent)。 +- 新增 `--dsw-codex-border`(10% / 12%)与 `--dsw-codex-border-strong`(15% / 20%),即 Codex 的 `--color-border` / `--color-border-strong`。 +- **设置卡深色背景的默认值错了**:`SKIN_DEFAULTS.dark.surface` 停在 `#181818`,而 0.5.6 起深色窗口背景是 `#111111` + —— 背景色块显示的「跟随皮肤」值与实际不符。已改,`check-repo` 新增逐字段对账 skin.css,漂移即 FAIL。 +- `.cx-error` 读的是不存在的 `--dsw-alias-state-error`,「保存没生效」的提示一直是普通文字色;改读 `error-primary`。 +- **字体栈校验加固**:`isFontStack` 另挡反斜杠(`u\72l(` 在分词器眼里就是 `url(`)、注释起止(值里开一个注释会把深色覆盖整块吞掉)、 + 控制字符与不成对的引号。`check-repo` 对「吞掉深色块」直接断言产物。 +- **安装器在新 profile 上写坏 YAML**:dsh rc.2 新建 profile 的 `cordis.patch.yml` 是流式空列表 `[]`,安装器直接在后面接 + `- insert:`,`dsh web` 启动即抛 `failed to parse overlay`。现在空 `[]` 先摘掉,非空流式列表拒绝并提示。 +- **产物里带着本机路径**:`composer.css` 注释里的本地克隆路径被原样拼进 `theme.css` 与 `client.js` 发出去,而体检只扫 JS、 + 且认不出 JSON 转义后的双反斜杠。体检扩到样式与文档并认两种写法;`docs/` 里的本机路径换成占位符。 +- 过期注释:卡片圆角 10px → 12px、「链接 = 墨」→ 强调色、过渡 100ms → 150ms、输入区 14px → 16px、⑫ 行圆角 8 → 13、 + 各层头部的 `install-plugin.mjs` → `src/build.mjs`、`build.mjs` 的「五份样式」、皮肤 README 的文件表与「链接是墨色」。 + +### 验收 + +- **新增** `scripts/power-rail-verify.mjs`(47 项,只要 Chromium):真实组件 + 真实 theme.css + 按宿主契约写的假目录, + `select()` 往返故意放慢到 600ms —— 本机真宿主往返不到 60ms,pending 行为在真 GUI 上来不及量。它量出并修掉了两个问题: + 宿主字典缺席时内置模型说明画出了字典键名;鼠标打开后焦点在触发器上时 Escape 关不掉。 +- **新增** `scripts/pack-host-asar.mjs`:没有桌面壳时,把 npm 装的宿主包拼成夹具能读的 `app.asar`(与桌面壳同一批包)。 +- `hero-verify.mjs`:「徽标圆角保持官方 6px」写死的是 rc.1 的字面量;rc.2 改成了 `var(--dsw-radius-xs)`(宿主给 4px), + 在 rc.2 上必然 FAIL。期望值改为从宿主源码现取。 +- `live-gui-probe.mjs`:新 profile 一进来是两层引导弹层,探针的点击全落在遮罩上 —— 右栏三条断言「实测 none」就是这么来的, + 现在先关弹层。模型位按 B 面量 7 条(顶替、几何、键盘改档写进宿主 store 并改回);B 面关着时量 A 面 pending, + `--latency`(默认 800ms)给那一次往返加时延,否则窗口在第一次采样前就结束了。探针改完档位会改回去。 +- `settings-page-verify.mjs`:9 行;深色底色断言仍是 0.5.6 之前的 `#181818`,改为 `#111111`;新增开关 5 条 + (关掉后刷新、席位里没有自建触发器、宿主那一格可见,收工复位)。 +- 全套(npm `@deepseek-ai/dsh@0.1.7-rc.2` + 由它打包的 asar + Edge/Chromium 153;真 GUI 为同版本 `dsh web`): + `check-repo` 23/23 · `audit` 36/36 · `elevation` 19/19 · `composer-shadow` 23/23 · `model-picker` 20 · `power-rail` 47/47 · + `rightbar` 42 · `hero` 25 · `sidebar-align` 6/6 · `sidebar-surface` 13/13 · 真 GUI `live-gui-probe` B 面 14/14、A 面 10/10 · + `settings-page-verify` 29/29 · `theme-flash-probe` 9 段 0 异常帧(点击→变色中位 23ms)。 + ## 0.5.10 - 2026-09-28 第三张 Codex 截图让输入卡环的取值**不再依赖设备像素比**就能定下来 —— 也顺带查出 0.5.8 的 12% 偏重。 diff --git a/README.md b/README.md index b9f2551..e3316ab 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ # codex-ui - **Codex appearance for DSH Web: window edges, sidebar divider, model menu, composer, theme colors** + **Codex appearance for DSH Web: window edges, sidebar divider, model picker and reasoning power rail, composer, theme colors** [简体中文](README.zh-CN.md) · [Changelog](CHANGELOG.md) · [MIT](LICENSE) @@ -26,14 +26,16 @@ images are in the sections below and in `assets/reference/`. ## Host compatibility -Developed against DSH `0.1.7-rc.1` (npm global install) and `0.1.7-rc.2` (Windows desktop shell `app.asar`). +Developed against DSH `0.1.7-rc.1` (npm global install) and `0.1.7-rc.2` (Windows desktop shell `app.asar`); +the full 0.6.0 verification ran on the npm release `@deepseek-ai/dsh@0.1.7-rc.2` (a real `dsh web` instance plus an asar packed from it, see "Host paths"). The verification scripts read shipped CSS straight out of `app.asar`; a host upgrade that changes structure fails their assertions. ## Features | Id | Content | |---|---| -| ⑫ | Model picker: native trigger, opaque white menu (18px radius), 28px rows with 13px radius (concentric: 18 − 5px inset), permanent check column, pending spinner | +| ⑳ | **Model picker, face B** (on by default): a component of our own takes over the composer model seat — model list (grouped by provider, with descriptions) plus the Codex reasoning **power rail** (24px track / 4px ticks / 28px white thumb, real drag, ←/→/Home/End, levels from the data); during the round trip it shows the new level optimistically with a spinner and never clears the list; the trigger cross-fades the effort label with blur. Can be switched off in settings | +| ⑫ | Model picker, face A (the host menu when ⑳ is off): opaque white menu (18px radius), 28px rows with 13px radius (concentric: 18 − 5px inset), permanent check column, pending spinner | | ⑬ | Composer header turned blank, panel buttons keep the official icons | | ⑬c | Both header slots **released**: entries registered there render again (subagent descendant count / job roster / preset badge / open-in-app); the view tabs stay hidden. The entries are conditionally rendered, so the top bar is unchanged in the common case | | ⑭ | Composer card: radius (card 20/25, top strip = the card's own radius), shadow, geometry, tool row, hero layout | @@ -43,7 +45,7 @@ The verification scripts read shipped CSS straight out of `app.asar`; a host upg | ②c | Conversation window edge: 0.5px hairline plus a 24px ambient shadow | | ②d | Right panel: hairline only on its left edge, shadow bleeds upward only; the dockkit 1px border is removed | | ②e | Right divider handle: center-darkest gradient on hover | -| ⑱ | Settings card on the plugin manager's codex-ui bundle page: theme / accent / background / foreground / UI font / code font / translucent sidebar / contrast | +| ⑱ | Settings card on the plugin manager's codex-ui bundle page: theme / accent / background / foreground / UI font / code font / translucent sidebar / Codex model picker / contrast | ## Screenshots @@ -55,7 +57,11 @@ Composer bottom row: no box by default, a chip on hover.  -Model menu while a reasoning effort is being written. +Model picker face B (captured on a real `dsh web` instance): model list plus reasoning power rail, light and dark. + +
