Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,59 @@

[简体中文](CHANGELOG.zh-CN.md)

## 0.7.0 - 2026-09-28

The architecture cleanup (originally PR #3) and the full-page settings work (originally PR #1) land together on top of
0.6.4. Nothing from 0.6.2–0.6.4 is dropped: the structure is the new one, and every assertion that used to live in the
old flat scripts was re-expressed in the new ones.

### Structure (the PR #3 refactor)

- **Client side is modular**: `src/client.template.js` is gone. `src/client/index.js` is the entry and pulls in
`settings.js`, `settings-card.js`, `override.js`, `stylesheet.js`, `theme-preview.js`, `host.js`,
`constants.js` and `model-picker/`. The bundler in `scripts/build.mjs` is zero-dependency and produces the same
classic `window.__ModuleLoader__.load(...)` script. A new feature is one module plus one line in the entry.
- **Tooling collapsed into three entries**: `npm run check` (host-free), `npm run verify` (fixtures),
`scripts/live/*` (real GUI). Shared code lives in `scripts/lib/`; fixtures are `scripts/specs/*.mjs`.
The old `check-repo.mjs` / `*-verify.mjs` / `live-gui-probe.mjs` / `theme-flash-probe.mjs` are gone; their
assertions were moved, not deleted.
- **No `:has()` in ancestor positions** and a narrower MutationObserver: style recalc while a reply streams in went
4264 → 267 ms (web) and 9518 → 442 ms (desktop shell).
- **The dark `html` background never applied**: the scoper turned `:root:has(body[data-ds-dark-theme])` into a
descendant selector that could not match. Compound selectors starting with `:root` now map to the root itself.

### Settings modal (the PR #1 work)

- **㉑ The settings dialog gets a full Codex pass**: a grouped sidebar with a "← Back to app" row and a search box that
filters host items, group headers, a page header in the content area, white cards with hairlines on every sub-page and
sticky save bars. Structure lives in `src/client/settings-modal.js`, looks in `skins/codex-ink/settings-modal.css`.
- **Host anchors first**: the panel is found by `[data-shortcut-modal="settings"]` (the host's own attribute) and only
falls back to the third-party skin-center adapter's `[data-dsh-surface="settings"]`. The visual layer hangs off the
plugin's own `[data-cx-sm-panel]` only, so a host rename moves one constant, not a stylesheet. When the settings
dialog is open but no anchor matches, the layer **warns** instead of failing silently.
- **Hiding is attribute-first**: the host rewrites a nav item's whole `className` when the selection moves away, so the
durable marker is `data-cx-sm-hidden`; the class is only for humans. The list observer runs with `subtree` — without
it the class rewrite produces zero mutation records (measured 0 vs 5).
- **Never takes over host nodes**: unknown items are only grouped visually, host nodes are never moved, cloned or
deleted. Reordering uses `style.order`; the known cost is that Tab order stays DOM order.

### Ported into the new structure

- The 0.6.2–0.6.4 model-picker work (seat-local `pending`, top-rung violet dot matrix, the 16×30 knob and the
Faster/Smarter row) lives in `src/client/model-picker/{component,view}.js`; the fixture's fake directory no longer
pretends the snapshot has a `pending` field, which is what the installed host actually looks like.
- The 0.6.3 measured-pixel values (sidebar `#f6f6f6`, centre-column ambient shadow 13px @7%, two-layer composer
shadow) are in, and the dark centre-column rule is kept separate: the light fit does not hold for dark, so a single
token cannot express both.
- `scripts/live/settings-modal.mjs` and `scripts/live/settings-sweep.mjs` are the live-GUI checks for the modal. The
sweep treats "found zero settings pages" as a hard failure — it used to report PASS while scanning nothing.
- 0.6.4's sidebar-colour verification comes across as the `sidebar-color` fixture spec (16 assertions: both themes on
one page, measured pixels, the hierarchy direction, plus a per-theme negative control).

### Verification

`npm run check` 67/67 · `node scripts/verify.mjs` 224/224 (9 specs).

## 0.6.4 - 2026-09-28

Both sidebars were rebased. Codex has **no sidebar color token**; the left panel is painted with a translucent
Expand Down
48 changes: 48 additions & 0 deletions CHANGELOG.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,54 @@

[English](CHANGELOG.md)

## 0.7.0 - 2026-09-28

架构整理(原 PR #3)与设置界面全页化(原 PR #1)一起落在 0.6.4 之上。0.6.2–0.6.4 的东西一样没丢:
结构用新的那一套,原来散在扁平脚本里的判据逐条搬进新脚本,不是删掉。

### 结构(原 PR #3 的重构)

- **客户端模块化**:`src/client.template.js` 删除。入口是 `src/client/index.js`,下面挂 `settings.js`、
`settings-card.js`、`override.js`、`stylesheet.js`、`theme-preview.js`、`host.js`、`constants.js`
与 `model-picker/`。`scripts/build.mjs` 里的打包器零依赖,产出的仍是宿主要求的经典脚本
`window.__ModuleLoader__.load(...)`。加一个功能 = 加一个模块 + 入口一行。
- **工具链收拢成三个入口**:`npm run check`(不依赖宿主)、`npm run verify`(夹具)、`scripts/live/*`(真 GUI)。
公共部分在 `scripts/lib/`,夹具在 `scripts/specs/*.mjs`。旧的 `check-repo.mjs` / `*-verify.mjs` /
`live-gui-probe.mjs` / `theme-flash-probe.mjs` 删除;它们的判据是**搬家**,不是删除。
- **祖先位置的 `:has()` 清零**,MutationObserver 收窄:流式输出时的样式重算 4264 → 267 ms(web)、
9518 → 442 ms(桌面壳)。
- **暗色 `html` 底色从未生效**:作用域化把 `:root:has(body[data-ds-dark-theme])` 变成了永远不匹配的后代选择器。
现在以 `:root` 开头的复合选择器映射到根自身。

### 设置模态框(原 PR #1 的工作)

- **㉑ 设置对话框全页 Codex 化**:分组侧栏(「← 返回应用」一行、按文字过滤宿主条目的搜索框)、分组标题、
内容区页头、每个子页面的白卡细边与贴底保存栏。结构在 `src/client/settings-modal.js`,外观在
`skins/codex-ink/settings-modal.css`。
- **宿主锚点优先**:面板先认宿主自己的 `[data-shortcut-modal="settings"]`,只有找不到才退到第三方
skin-center 适配器补打的 `[data-dsh-surface="settings"]`。视觉层只挂插件自有的 `[data-cx-sm-panel]` ——
宿主改锚点只动一个常量,不动样式表。设置界面确实开着却一个锚点都没匹配到时,这一层**发警告**,不静默失效。
- **隐藏态以属性为准**:宿主在选中态搬走时会把导航项的 `className` 整条重写,所以耐久标记是
`data-cx-sm-hidden`,类名只是给人看的。列表观察器必须开 `subtree` —— 不开时那次 class 重写一条 mutation
记录都不产生(实测 0 条 vs 5 条)。
- **绝不接管宿主节点**:未知项只做视觉分组,宿主节点不移动、不克隆、不删除。排序走 `style.order`;
已知代价是键盘 Tab 顺序仍是 DOM 顺序。

### 搬进新结构的部分

- 0.6.2–0.6.4 的模型选择器改动(席位级 `pending`、顶档紫色点阵、16×30 旋钮与「更快/更强」一行)落在
`src/client/model-picker/{component,view}.js`;夹具的假目录不再假装快照里有 `pending` 字段 ——
安装中的宿主本来就没有。
- 0.6.3 的实测像素值(侧栏 `#f6f6f6`、中列环境影 13px @7%、输入卡阴影两层)都在,且暗色中列规则**单独保留**:
亮色那支拟合对暗色不成立,一个令牌表达不了两档。
- `scripts/live/settings-modal.mjs` 与 `scripts/live/settings-sweep.mjs` 是模态框的真 GUI 验收。
扫掠脚本把「一个设置页都没扫到」当硬故障 —— 旧版在这种情况下报 PASS。
- 0.6.4 的侧栏底色验收搬成 `sidebar-color` 夹具 spec(16 项:亮暗同页、实测像素、层级方向,外加每套一条反例对照)。

### 验收

`npm run check` 67/67 · `node scripts/verify.mjs` 224/224(9 个 spec)。

## 0.6.4 - 2026-09-28

亮暗两套侧栏都换了基准。Codex **没有「侧栏色」令牌**;那一面是半透明遮罩,渲染出来的颜色取决于窗口背后是什么。
Expand Down
18 changes: 11 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ 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`);
the full 0.6.1 verification ran on the npm release `@deepseek-ai/dsh@0.1.7-rc.2` (fixtures against both its
the full 0.7.0 verification ran on the npm release `@deepseek-ai/dsh@0.1.7-rc.2` (fixtures against both its
`node_modules` and an `app.asar` packed from it, the live checks on a `dsh web` booted from it; see "Host and browser").
Fixtures read the shipped host CSS and render code directly, so a structural host change fails their assertions.

Expand All @@ -47,6 +47,7 @@ Fixtures read the shipped host CSS and render code directly, so a structural hos
| ②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 / Codex model picker / contrast |
| ㉑ | **Settings modal, full-page Codex pass**: the settings dialog gets a grouped sidebar (a "← Back to app" row, a search box that filters host items, group headers), a page header in the content area, white cards with hairlines on every sub-page and sticky save bars. Structure comes from `src/client/settings-modal.js` (host anchors only, host nodes never moved or cloned, hiding driven by a durable `data-*` attribute), looks from `skins/codex-ink/settings-modal.css`. Any step that does not match the host layout warns and skips — the layer degrades, it never throws |

## Screenshots

Expand Down Expand Up @@ -133,7 +134,7 @@ ones go through a `ctx.inject([...], cb)` child scope, as the model picker does.
| `src/client/override.js` | Override-layer pure functions (no DOM; unit-tested by `check.mjs`) |
| `src/client/model-picker/` | Model picker face B: `index.js` waits for the `modelDirectories` service, `component.js` is the seat takeover, popover and power rail, `view.js` holds the pure functions (unit-tested by `check.mjs`) |
| `src/client/constants.js` `host.js` | Shared names and small helpers for reading host services |
| `skins/codex-ink/` | Stylesheet sources (skin.css / patches.css / model-picker.css / sidebar-align.css / sidebar-surface.css / window-shadow.css / composer.css / settings.css) and the Skin v2 manifest |
| `skins/codex-ink/` | Stylesheet sources (skin.css / patches.css / model-picker.css / sidebar-align.css / sidebar-surface.css / window-shadow.css / composer.css / settings.css / settings-modal.css) and the Skin v2 manifest |
| `theme.css` `client.js` | Generated by `scripts/build.mjs` and committed (DSH loads `client.js`) |
| `scripts/build.mjs` | Scoping and bundling |
| `scripts/check.mjs` | Host-free repository checks; the CI entry point |
Expand All @@ -150,21 +151,24 @@ ones go through a `ctx.inject([...], cb)` child scope, as the model picker does.

| Command | Coverage | Requirement |
|---|---|---|
| `npm run check` | Syntax, JSON, manifest and `peerDependencies`, artifacts in sync with sources, the DSH client plugin contract of `client.js` (executed once in isolation), scoping, override-layer and power-rail pure functions, 36 WCAG pairs, color whitelist, encoding, docs pairing, machine-specific paths — 60 checks | none |
| `npm run verify` | All fixtures, 195 assertions (table below) | host packages + Chromium |
| `npm run check` | Syntax, JSON, manifest and `peerDependencies`, artifacts in sync with sources, the DSH client plugin contract of `client.js` (executed once in isolation), scoping, override-layer and power-rail pure functions, 36 WCAG pairs, color whitelist, encoding, docs pairing, machine-specific paths, the settings-modal contract (sources / scoping / host-first anchor / assembly) — 67 checks | none |
| `npm run verify` | All fixtures, 224 assertions (table below) | host packages + Chromium |
| `node scripts/live/gui.mjs --url <token URL>` | Real GUI: shadows and both dividers, plus the model seat — 14 assertions with face B on (takeover, geometry, a keyboard change written into the host store and reverted), 10 with it off (the face A pending window; `--latency` adds 800ms to that round trip by default, since locally it takes <60ms and cannot be sampled) | a running `dsh web` |
| `node scripts/live/settings.mjs --url <…>` | Real GUI: the card on the bundle page, its 9 rows, no override at defaults, switch and accent writes, the host seat coming back when the model picker is off, survival across a reload, no intermediate frame while switching theme; resets everything at the end — 30 assertions | same, with the plugin manager enabled |
| `node scripts/live/settings-modal.mjs --url <…>` | Real GUI: the ㉑ structure layer and its visual layer together — the grouped sidebar, the "← Back to app" row, the search filter, group headers, host node identity (nothing moved or cloned), and the attribute-first hiding that survives the host rewriting `className`. Needs a live `dsh web` with the plugin manager enabled; `--explore` dumps the structure without asserting | same |
| `node scripts/live/settings-sweep.mjs --url <…> --out <dir> --prefix c1` | Opens every settings page in light and dark, screenshots each and measures layout health. **Zero pages found is a hard failure** (non-zero exit): a sweep that scans nothing must never report PASS | same |
| `node scripts/live/parity.mjs snap --url <…> --out <dir>`<br>`node scripts/live/parity.mjs diff <before> <after>` | Stores every element's computed style across 14 UI states and compares them; exits 0 on zero differences. This is how a refactor proves the look did not change. `--ignore` skips given properties or newly added `--variables` | same |

Fixtures: `node scripts/verify.mjs [spec…]`; without a spec, all of them run.

| Spec | Sections | Coverage | Assertions |
|---|---|---|---|
| `composer` | composer-shadow · hero | ⑱ composer shadow aligned to Codex's `--elevation-composer` (per-layer geometry and alpha, dark inset, narrow-viewport 80→40px, rendered pixels); ⑬ ⑭ ⑰, the focus ring and the released header slots | 23 + 25 |
| `composer` | composer-shadow · hero | ⑱ composer shadow **fitted to measured pixels** (two layers — ring + near field; per-layer geometry and alpha, dark inset, narrow and wide viewports agreeing, rendered pixels); ⑬ ⑭ ⑰, the focus ring, the released header slots and the badge colour / radius read from the tokens themselves | 21 + 25 |
| `elevation` | elevation | ⑲ `--dsw-elevation-*` against Codex's source, plus a rendered menu panel | 19 |
| `model-picker` | host-menu · power-rail | ⑫ face A (the host menu) and the pending indicator; ⑳ face B: seat takeover and hand-back, geometry, no commit while dragging / one snapped commit on release, no snap-back and a spinner during a slow round trip, the four keys, focus ring, Escape, model change carrying its default effort, failure notice, reduced motion, dark, the switch | 20 + 47 |
| `model-picker` | host-menu · power-rail | ⑫ face A (the host menu) and the pending indicator; ⑳ face B: seat takeover and hand-back, geometry, no commit while dragging / one snapped commit on release, no snap-back and a spinner during a slow round trip, **the top-rung violet dot matrix** (5 rows, 8 tone buckets, hash-scattered phases, feathering, both reduced-motion switches), the four keys, focus ring, Escape, model change carrying its default effort, failure notice, reduced motion, dark, the switch. The fake directory is shaped like the **installed** host (no `pending` in the snapshot) | 20 + 62 |
| `rightbar` | rightbar | Shadow layer, right panel, both dividers | 42 |
| `sidebar` | align · surface | Sidebar column alignment; the sidebar scroll fade (Codex mask ramp), mechanism plus per-pixel alpha | 6 + 13 |
| `sidebar-color` | sidebar-color | The sidebar base against Codex's **measured** pixels, both themes on one page: light 246/233/255 and dark 15/31/17 (all neutral, R=G=B), the sidebar-to-content step in each, the hierarchy direction, plus a per-theme negative control | 16 |

Options: `--host <app.asar | node_modules>` picks the host, `--shots <dir>` the screenshot directory (default
`codex-ui-shots/` under the system temp directory), `--verbose` prints the readings. Fixtures read the shipped host CSS
Expand Down Expand Up @@ -348,7 +352,7 @@ thing from the sidebar. The composer card over it measures 35, matching Codex.
- The session row text column is 40px, 2px shorter than the workspace, new session and plugin rows, because the shipped
`Rows.module.css` gives `.sessionRow .title` its own margin. Left as is.
- `composer.css`, `patches.css`, `sidebar-align.css` and `sidebar-surface.css` use hash-class suffix anchors (`[class$=…]`,
`[class*=…]`) where the host exposes no `data-*` (0.6.1: composer 10 · patches 10 · sidebar-align 9 · sidebar-surface 2).
`[class*=…]`) where the host exposes no `data-*` (0.7.0 raw occurrences: composer 9 · patches 9 · sidebar-align 9 · sidebar-surface 2 · settings-modal 3).
The 9 in sidebar-align are one anchor, `_collapsed` (the collapsed sidebar), replacing an ancestor-position `:has()`.
`model-picker.css` has none.
- Selectors keep `:has()` out of ancestor positions: while the conversation streams nodes in, Chromium re-matches the
Expand Down
Loading
Loading