dsh-trae-skin 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/LICENSE +21 -0
  3. package/README.en.md +349 -0
  4. package/README.md +346 -2
  5. package/THIRD_PARTY_NOTICES.md +99 -0
  6. package/build.mjs +195 -0
  7. package/cordis.patch.yml +33 -0
  8. package/lib/client.js +2017 -0
  9. package/lib/index.js +1158 -0
  10. package/lib/index.js.map +7 -0
  11. package/lib/preview.js +1214 -0
  12. package/locale/en.json +64 -0
  13. package/locale/zh.json +64 -0
  14. package/package.json +76 -4
  15. package/preview/index.html +331 -0
  16. package/scripts/install-profile.mjs +251 -0
  17. package/src/client/accent-preview.ts +34 -0
  18. package/src/client/activity.ts +129 -0
  19. package/src/client/components/Panel.tsx +279 -0
  20. package/src/client/components/controls.tsx +91 -0
  21. package/src/client/copy.ts +174 -0
  22. package/src/client/index.ts +147 -0
  23. package/src/client/palette.ts +136 -0
  24. package/src/client/published.ts +43 -0
  25. package/src/client/skin.ts +158 -0
  26. package/src/client/strip.ts +90 -0
  27. package/src/client/styles/page.ts +414 -0
  28. package/src/client/styles/panel.ts +256 -0
  29. package/src/constants/config.ts +58 -0
  30. package/src/constants/dom.ts +63 -0
  31. package/src/constants/palette.ts +482 -0
  32. package/src/constants/plugin.ts +93 -0
  33. package/src/enums/activity.ts +13 -0
  34. package/src/enums/scheme.ts +23 -0
  35. package/src/enums/settings.ts +30 -0
  36. package/src/enums/shell.ts +32 -0
  37. package/src/host/index.ts +75 -0
  38. package/src/host/publish.ts +50 -0
  39. package/src/host/schema.ts +89 -0
  40. package/src/index.ts +14 -0
  41. package/src/preview/main.ts +127 -0
  42. package/src/types/color.ts +43 -0
  43. package/src/types/config.ts +50 -0
  44. package/src/types/host.ts +19 -0
  45. package/src/types/palette.ts +55 -0
  46. package/src/types/panel.ts +86 -0
  47. package/src/types/settings.ts +81 -0
  48. package/src/utils/accent.ts +144 -0
  49. package/src/utils/color.ts +264 -0
  50. package/src/utils/config.ts +88 -0
  51. package/src/utils/section.ts +176 -0
  52. package/src/utils/value.ts +83 -0
  53. package/src/utils/volatile.ts +19 -0
  54. package/test/accent.test.mjs +173 -0
  55. package/test/activity.test.mjs +215 -0
  56. package/test/color.test.mjs +180 -0
  57. package/test/config.test.mjs +287 -0
  58. package/test/contrast.test.mjs +168 -0
  59. package/test/copy.test.mjs +165 -0
  60. package/test/fake-dom.mjs +351 -0
  61. package/test/host.test.mjs +285 -0
  62. package/test/input.mjs +38 -0
  63. package/test/load-module.mjs +57 -0
  64. package/test/palette.test.mjs +271 -0
  65. package/test/skin.test.mjs +298 -0
  66. package/test/strip.test.mjs +148 -0
  67. package/test/styles.test.mjs +462 -0
  68. package/test/value.test.mjs +104 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,45 @@
1
+ # Changelog
2
+
3
+ 本插件的所有值得记下的改动。格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),
4
+ 版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
5
+
6
+ ## [0.2.0] — 2026-10-06
7
+
8
+ 首个按 `dsh-trae-skin` 功能清单(L1 / L2)重写的版本。包名、loader 行 id、设置命名空间统一成
9
+ `dsh-trae-skin`,宿主全局名 `__DSH_TRAE_SKIN__`,皮肤挂在 body 的 `data-dsh-trae*` 系列属性上。
10
+
11
+ ### 新增
12
+
13
+ - **整页换肤**:深色(参照站自己的近黑页 `#0a0b0d` + 薄荷绿)/ 浅色(白纸 + 深绿)/ 跟随产品三档配色,
14
+ 只经产品有文档的 `--dsw-*` alias token 换色。默认深色,改设置不刷新页面。
15
+ 状态色、toast、tooltip、diff 底色与语法高亮留给产品按 `data-ds-dark-theme` 自己切。
16
+ - **强调色派生**:一个 `#rrggbb` 输入框派生出填充色、hover、12% 信息底色、当文字用的可读色与两套渐变;
17
+ 当文字那一档自动到 WCAG AA,派生值先量化成 `#rrggbb` 再复验。
18
+ 自定义强调色按参照站两套原版量出来的 HSL 节奏派生渐变。
19
+ - **顶部状态带**:32px 固定条,指示灯 → 渐变字标(四段渐变+16% 底衬)→ 给读屏的状态文字。
20
+ 指示灯空闲是圈、输入框获得焦点或鼠标移入时填实;只在产品报告推理时呼吸,读不到 `data-streaming`
21
+ 信号时改成持续呼吸。整条可以关掉,也可以只留内容。
22
+ - **细节修饰**:品牌绿主按钮、2 / 4 / 8px 圆角、1px 三档发丝线、0.5px 内高光+柔和投影、
23
+ 三套字体(不带字体文件)、两个开关状态、深浅两套版本徽标、滚动条 / 焦点环 / 链接色。
24
+ - **插件卡片设置面板**:七项(配色、强调色、字标文字、顶部状态带、条带只留内容、指示灯呼吸、呼吸灯样式),
25
+ 「呼吸灯」自成一块(光晕颜色 / 扩散大小 / 周期 / 强度),一颗「恢复默认」,保存才生效,写进 profile 配置,
26
+ 逐项错误提示与不可写 / 旧宿主半边提示。
27
+ - **宿主半边**:把设置随首页注入表推到页面上、做成设置命名空间、把本行挡在自动设置页之外。
28
+ - **构建与验证**:一条命令构建两个半边+自测预览页(另带 `--watch`)、265 项单测(含反向断言与对比度实测下限)、
29
+ 安装脚本(逐份备份、可卸载)、自测预览页(真实模块挂模拟外壳,不用引擎)。
30
+
31
+ ### 明确不做
32
+
33
+ - 主显示区背后的方格与鼠标跟随、它带来的那层深度纱层,以及模式色调三档。
34
+ - 径向渐变 / 辉光、`backdrop-filter`、混合模式、噪点 —— 四条在参照站全站出现 0 次,测试逐条断言不许出现。
35
+
36
+ ### 文案与卡片排版(发布前调整)
37
+
38
+ - 卡片读数区定名为**「派生色值」**,各标签收短为一个词(填充 / hover / 12% 底色 / 浅色下当文字 / 深色下当文字 / 浅色渐变 / 深色渐变),说明文字收成一句;单测对标签长度、说明长度与句数设了下限约束。
39
+ - 卡片排版向同系列的 `dsh-mimo-skin` 卡片对齐:同一个说明框、84px 标签列、读数行、开关行、脚注与 6 / 8px 圆角,两个设置页在感受上成一个系列(代码仍各自独立)。
40
+
41
+ ### 已知取舍
42
+
43
+ - 发消息圆钮的字形是产品写死的白色字面量,本版本用「输入卡钩子 + 控件可达性契约」把它一起换成近黑字。
44
+ - 条带会被自绘的不透明整窗覆盖层盖住,皮肤不强穿。
45
+ - 不带粗体等宽;「跟随产品」模式拆卸时留着产品最后一次的 `data-ds-dark-theme`。
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 RonnyJung2021
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.en.md ADDED
@@ -0,0 +1,349 @@
1
+ <div align="center">
2
+
3
+ # dsh-trae-skin
4
+
5
+ A **skin-only plugin** for the DSH Web GUI — near-black paper, a mint accent, a gradient wordmark band across the top, 2px corners and 0.5px inner-highlight shadows.
6
+
7
+ [中文](README.md) / English
8
+
9
+ [Install](#install) · [First run](#first-run) · [Configuration](#configuration) · [Layout](#layout) · [Development](#development)
10
+
11
+ <p>
12
+ <a href="https://www.npmjs.com/package/dsh-trae-skin"><img src="https://img.shields.io/badge/version-0.2.0-blue" alt="version"></a>
13
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="license"></a>
14
+ <img src="https://img.shields.io/badge/DSH-%3E%3D0.1.5--rc.2-8b5cf6" alt="DSH">
15
+ <img src="https://img.shields.io/badge/tests-270%20passing-brightgreen" alt="tests">
16
+ <img src="https://img.shields.io/badge/node-%5E22.19%20%7C%7C%20%3E%3D24-339933" alt="node">
17
+ </p>
18
+
19
+ </div>
20
+
21
+ ***
22
+
23
+ <!-- Images use the repository's absolute address: npm cannot resolve a relative path when it
24
+ renders this README, and docs/… would break into image frames there. -->
25
+ <p align="center">
26
+ <img src="https://raw.githubusercontent.com/RonnyJung2021/dsh-trae-skin/main/docs/preview-before.png" alt="Before: the product's own shell" width="49%">
27
+ <img src="https://raw.githubusercontent.com/RonnyJung2021/dsh-trae-skin/main/docs/preview-dark.png" alt="After: with the skin on (dark)" width="49%">
28
+ </p>
29
+
30
+ Same engine, same page: **left** is the product's own shell, **right** is the same page with the skin on. Both are screenshots of a real engine in a real browser, not mock-ups.
31
+
32
+ ## What it is
33
+
34
+ It turns the DSH Web GUI into the look of [`trae.cn`](https://www.trae.cn/). It adds **no features and changes nothing but the look**: sidebar, message column, composer card, menus and code blocks change together, in both a light and a dark shell. It is for anyone who wants DSH in that near-black-paper, mint-accent, dark-first design.
35
+
36
+ ### What it costs
37
+
38
+ | Item | Value |
39
+ | --- | --- |
40
+ | Payload | `lib/index.js` 45.7 KB / `lib/client.js` 76.7 KB |
41
+ | Product tokens remapped | 93 (92 `--dsw-*` aliases plus `--ds-font-family-code`) |
42
+ | The skin's own variables | 42 `--dsh-trae-*` |
43
+ | Bundled assets / runtime deps | **0** (no font files, no images; the engine supplies `@deepseek-ai/cordis`, and MIT packages such as `schemastery` are inlined into `lib/index.js` at build time) |
44
+ | Tests | 270 cases in 12 files, about 0.2 s |
45
+
46
+ ## What it does
47
+
48
+ ### Whole-page reskin
49
+
50
+ Recoloured only through the product's documented `--dsw-*` aliases; the page structure and the generated CSS-module class names are untouched.
51
+
52
+ | Part | Dark | Light | Taken from the reference |
53
+ | --- | --- | --- | --- |
54
+ | Page ground | `#0a0b0d` | `#ffffff` | `--bg-bg-base-default` / the light block's `base` |
55
+ | Raised surfaces (menus, code blocks) | `#121314` | `#fafafa` | `--bg-bg-base-secondary` / `card` |
56
+ | Body text | `#f5f9fe` | `#1a1a1a` | `--text-text-default` |
57
+ | Secondary / tertiary text | `#a6aab5` / `#787d87` | `#5b6066` / `#6f757b` | `--text-text-secondary` / `tertiary` |
58
+ | Three hairline steps | white at 6% / 12% / 18% | `#4d6b991f` / `2e` / `5c` | `--border-border-neutral-l1…l3` |
59
+ | Four ink washes | `#edeff20a` … `2e` | `#5663770f` … `33` | `--bg-bg-overlay-l1…l4` |
60
+
61
+ ### Three shells: dark, light, or follow the product
62
+
63
+ **Dark is the default**: the reference is a dark-first product (`color-scheme:dark`, with no light mode), so following the product would show a light-system user a design it never shipped. Under the follow setting the skin repaints whenever the product switches, and the light/dark switch stays in the user's hands. Every change **repaints on the spot**, with no page reload.
64
+
65
+ <p align="center">
66
+ <img src="https://raw.githubusercontent.com/RonnyJung2021/dsh-trae-skin/main/docs/preview-light.png" alt="The light shell" width="100%">
67
+ </p>
68
+
69
+ **The state colours are deliberately left alone**: warn / success / error, the toast and tooltip surfaces, the coloured diff backgrounds and syntax highlighting all stay with the product, which already switches them itself on `body[data-ds-dark-theme]`.
70
+
71
+ ### One field in, a whole safe palette out
72
+
73
+ You fill in one `#rrggbb` (the reference's own green, `#32f08c`, by default) and the skin derives the fill, a darker hover step, a 12% wash, a text-safe step, and two gradient ramps from it.
74
+
75
+ - The text-safe step lands on **WCAG AA 4.5:1** automatically, measured against the paper it is actually read on (white for the light shell, near-black for the dark one). Every derived value is quantised to `#rrggbb` and then checked again — rounding alone can pull 4.50 down to 4.48.
76
+ - Measured with the default accent: `#1c864e` as text in the light shell (4.60:1) and `#32f08c` in the dark one (13.11:1).
77
+ - The gradients follow: the default accent uses the reference's own four stops (`#3ee1a3 #32f08c #60f2bd #a0fde7` in the dark shell, a separate set in the light one); any other colour derives its ramps along the **same rhythm** (an HSL offset measured off the two originals).
78
+ - **The card prints the measured numbers**: the “Derived colours” row shows every derived value in both shells together with its measured contrast ratio — figures, not promises.
79
+
80
+ <!-- TODO: screenshot — a close-up of the “Derived colours” read-out row. -->
81
+
82
+ ### The top status band
83
+
84
+ A fixed 32px band, reading left to right: **the light → the gradient wordmark → the state in words for screen readers**.
85
+
86
+ - **The wordmark** is small monospace type on a four-stop gradient, over a 16% plate of the same gradient (the reference's gradient-label treatment). Its text is editable, `DEEPSEEK HARNESS` by default, 1–24 characters; an over-long value is **refused, not truncated**.
87
+ - **The light** is a ring when idle, and fills in when the composer takes focus or the pointer enters it.
88
+ - **It only breathes while reasoning**: the pulse runs while the product reports reasoning or an answer in progress; when there is **no readable `data-streaming` signal it breathes continuously** (a still dot reads as “not working”). The signal comes from the product's own attribute, the observer watches that one attribute only, and the reading **changes twice per turn** rather than following the token stream.
89
+ - **The whole band can be turned off** (leaving only the colours and the type), or **kept as content only** (no fill of its own, no foot rule).
90
+ - **Accessibility**: the band declares `role="status"`, and the state words (idle / working) are for screen readers only.
91
+ - The band is **read-only**: nothing on it is clickable, and it declares the product's `data-window-drag`, so the top of a desktop window can still be dragged.
92
+
93
+ <!-- TODO: screenshot — a close-up of the band, idle ring and breathing state. -->
94
+
95
+ ### Detail work
96
+
97
+ | Item | Treatment |
98
+ | --- | --- |
99
+ | Primary button | The reference's call to action: `#32f08c` fill with near-black type |
100
+ | Corners | Only 2 / 4 / 8px (`2px` does most of the work); no 6 / 10 / 16px in-betweens |
101
+ | Rules | 1px throughout, at the reference's three alpha steps |
102
+ | Shadows | A 0.5px inner highlight with a soft drop; the light shell gets corrected weights |
103
+ | Type | A platform UI stack (Inter as the first fallback), a display face for headings, monospace for the wordmark and code (JetBrains Mono first); **no font files ship** |
104
+ | Switches | Both states come from the skin's own scale: on is the accent, off is a hairline |
105
+ | Version badge | Sidebar badges become “type on a dark surface”: near-black on white in the light shell, inverted in the dark one, never dark-on-dark |
106
+ | Scrollbars / focus ring / links | Follow the skin; the focus ring is a 1px neutral stroke with a 2px offset, **never the brand colour** (the brand is reserved for state) |
107
+ | Deliberately absent | Radial gradients / glows, `backdrop-filter`, blend modes, noise — all four appear 0 times across the reference site, and tests assert each one stays out |
108
+
109
+ ### The plugin card
110
+
111
+ Sidebar **Plugins → dsh-trae-skin**; the card below it is the plugin's **own settings page**. The ten appearance fields sit in two places: five in the card body (**Shell, Accent, Wordmark, Top band, Content only**), and five in the **Breathing light** group (**Light breathes, Glow colour, Glow spread, Breath period, Glow strength**).
112
+
113
+ - **The breathing light is a block of its own**: the glow colour (follow the accent, or name one), the spread, the period and the strength all live together there rather than being wedged in among the band's fields.
114
+ - One **“Restore defaults”** writes every appearance field back.
115
+ - **Nothing changes until Save is pressed**: edits are not written back while you type, and Save checks every field first — an invalid value is refused, with the reason given under that field.
116
+ - **Changes repaint on the spot**, and **they stick**: the values are written into the profile's config, so they survive a new port, an engine restart, and switching between the desktop window and the panel.
117
+ - The card states its condition: it says so when the form is not writable, and when it detects the older host half running in the engine it tells you plainly that the **engine** is what needs restarting, not the page.
118
+ - With the band off, the pulse and breathing-light blocks are **disabled on the card** rather than quietly recorded for later.
119
+
120
+ <p align="center">
121
+ <img src="https://raw.githubusercontent.com/RonnyJung2021/dsh-trae-skin/main/docs/preview-card.png" alt="The plugin's settings card" width="100%">
122
+ </p>
123
+
124
+ The card's layout language is deliberately shared with the other skin in this series (`dsh-mimo-skin`): the same note box, the same 84px label column, the same read-out rows and footnote. The two plugins install independently and neither depends on the other, but the two settings pages read as one series.
125
+
126
+ ## Install
127
+
128
+ ### Requirements
129
+
130
+ | Item | Requirement |
131
+ | --- | --- |
132
+ | DSH | `dsh-v0.1.5-rc.2` and later, backwards compatible (the same floor as the series' `dsh-mimo-skin`) |
133
+ | Node.js | `^22.19.0 \|\| >=24.0.0` (needed only if you build it locally) |
134
+ | System | Whatever the DSH Web GUI runs on: macOS, Windows or Linux |
135
+
136
+ ### Option 1: the command line
137
+
138
+ ```bash
139
+ # Straight from GitHub: the repo carries the built lib/, so the installer builds nothing
140
+ dsh plugin --profile web add https://github.com/RonnyJung2021/dsh-trae-skin
141
+
142
+ # Or by package name, once it is published to npm
143
+ dsh plugin --profile web add dsh-trae-skin
144
+ ```
145
+
146
+ > **A GitHub install relies on the `lib/` in the repo; do not turn it back into “build after install”.**
147
+ > pnpm refuses to run a git dependency's build scripts (`ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED`) unless
148
+ > the machine lists the package under `allowBuilds` in the profile's `pnpm-workspace.yaml`,
149
+ > and that key has to be copied character for character from what pnpm printed. So the payload is
150
+ > committed and the build moved to `prepack`: a git install has nothing to build and nothing to allow.
151
+
152
+ ### Option 2: the Plugins page
153
+
154
+ Sidebar **Plugins** → **Add plugin** → enter `dsh-trae-skin` (or the GitHub URL above) → **Install** →
155
+ **Enable now** when it finishes.
156
+
157
+ ### Installing from the working directory, while developing
158
+
159
+ ```bash
160
+ node scripts/install-profile.mjs --home /path/to/home # install
161
+ node scripts/install-profile.mjs --home /path/to/home --dry-run # print what it would change
162
+ node scripts/install-profile.mjs --home /path/to/home --uninstall # remove
163
+ ```
164
+
165
+ `--home` defaults to `$DSH_HOME` or `~/.dsh`, and `--profile` to `web`. That profile has to have run the engine once (`profiles/web/package.json` exists) before anything can be installed into it.
166
+
167
+ It writes the three things the Plugins page itself writes for a local package: a `link:` dependency plus the package name in `dsh.profile.bundles` in the profile's `package.json`, the `node_modules/<name>` symlink, and the importer entry in `pnpm-lock.yaml`. Along the way it takes out an older `file://`-style `insert` row (which would otherwise load the plugin twice), and backs up every file it edits as `*.bak-dsh-trae-skin`. Running it again is idempotent.
168
+
169
+ Installing as a bundle is what puts the row in play; the `dsh-trae-skin` row on the Plugins page is its on/off switch and its uninstall entry.
170
+
171
+ > **The host half loads once, when the engine process starts.** After installing, after editing `cordis.patch.yml`, or after upgrading the plugin, the thing to restart is the **engine**. The browser half is hot: a page reload picks it up. The card says so plainly when it finds the older host half still running in the engine.
172
+
173
+ ### First run
174
+
175
+ 1. Install and **enable** it by either route above.
176
+ 2. Open the GUI: sidebar **Plugins → Installed → dsh-trae-skin**. The card below it is the plugin's **own settings page**.
177
+ 3. Change an appearance field and press **Save**; the page repaints at once.
178
+
179
+ ### Uninstall
180
+
181
+ ```bash
182
+ # Installed from GitHub or npm
183
+ dsh plugin --profile web remove dsh-trae-skin
184
+
185
+ # Installed from the working directory as a link:
186
+ node scripts/install-profile.mjs --home /path/to/home --uninstall
187
+ ```
188
+
189
+ Or press **Uninstall** on this package's row on the Plugins page. The stylesheet, the status band, the breathing light and the palette written into the body all go with it, the page returns to the product's own shell, and **nothing is left behind**.
190
+
191
+ ## Configuration
192
+
193
+ Ten appearance fields, all optional. Omit one and its default applies; an invalid value falls back field by field — a broken skin should never hold up the GUI's startup.
194
+
195
+ | Field | Type | Default | Meaning |
196
+ | --- | --- | --- | --- |
197
+ | `scheme` | `dark` \| `light` \| `auto` | `dark` | The shell; `auto` follows the product's own light/dark setting |
198
+ | `accent` | `#rgb` / `#rrggbb` | `#32f08c` | The accent (used as a fill); the text-safe step, the 12% wash and both ramps derive from it |
199
+ | `wordmark` | string 1–24 | `DEEPSEEK HARNESS` | The gradient line on the band; an over-long value is **refused**, not truncated |
200
+ | `strip` | boolean | `true` | Whether to paint the top status band |
201
+ | `stripBare` | boolean | `false` | Whether the band keeps only its content (no fill, no foot rule) |
202
+ | `pulse` | boolean | `true` | Whether the light breathes while the product reports reasoning |
203
+ | `pulseColor` | `follow` / `#rrggbb` | `follow` | The glow colour; `follow` derives it from the accent |
204
+ | `pulseScale` | number 1.2–2.4 | `1.6` | The spread, as a multiple of the light's radius |
205
+ | `pulsePeriodMs` | number 800–4000 | `1600` | One breath, in milliseconds |
206
+ | `pulseOpacity` | number 0.1–0.6 | `0.35` | The glow's strength |
207
+ | `enabled` | boolean | `true` | Whether the skin renders at all (deployment, not on the card) |
208
+ | `globalName` | string | `__DSH_TRAE_SKIN__` | The global the host writes into the page (deployment, not on the card) |
209
+
210
+ Written into the profile's patch (`profiles/web/cordis.patch.yml`):
211
+
212
+ ```yaml
213
+ - insert:
214
+ - id: dsh-trae-skin
215
+ name: dsh-trae-skin
216
+ config:
217
+ scheme: dark # dark | light | auto
218
+ accent: '#32f08c' # any #rrggbb
219
+ wordmark: DEEPSEEK HARNESS # 1–24 characters
220
+ strip: true
221
+ stripBare: false
222
+ pulse: true
223
+ pulseColor: follow # follow | '#rrggbb'
224
+ pulseScale: 1.6
225
+ pulsePeriodMs: 1600
226
+ pulseOpacity: 0.35
227
+ enabled: true
228
+ ```
229
+
230
+ What the card writes on Save is the plain `- id: dsh-trae-skin` override with a `config:` block (not an `insert`) — the form the Plugins page writes itself.
231
+
232
+ ## Layout
233
+
234
+ ```
235
+ dsh-trae-skin/
236
+ ├── src/
237
+ │ ├── constants/ identity, the card's field list, DOM names, the palette and token map
238
+ │ ├── enums/ closed sets: shell, scheme, activity reading, settings form
239
+ │ ├── types/ shapes both halves share
240
+ │ ├── utils/ colour arithmetic, accent derivation, config narrowing, card sections
241
+ │ ├── host/ the host half: publishing, the settings namespace, the schema
242
+ │ ├── client/ the browser half: stylesheet, applier, status band, signal reading, card
243
+ │ └── preview/ the self-test preview's entry (real modules, simulated shell)
244
+ ├── locale/ the zh / en copy
245
+ ├── lib/ build output (index.js host, client.js browser, preview.js preview), committed
246
+ ├── preview/ the self-test preview page (double-click index.html)
247
+ ├── test/ unit tests
248
+ ├── docs/ screenshots
249
+ └── scripts/ install into / remove from one profile
250
+ ```
251
+
252
+ ### The two halves
253
+
254
+ | Half | What it does | Why it is split this way |
255
+ | --- | --- | --- |
256
+ | Host (`lib/index.js`, thin) | Publishes the settings into the page through the index injection table (so the first frame has the full set), turns the appearance fields into a settings namespace (which is how the card's edits reach the profile), and keeps this row off the generated settings pages | A loader row's `config` never reaches the page (the boot graph carries only id/inject/external), and the card's writes can only land in the profile through the settings service |
257
+ | Browser (`lib/client.js`) | Installs the stylesheet, writes the palette, mounts the status band and the breathing light, and repaints on the card's changes | A skin has no timeline and no session state; all it needs is settings |
258
+
259
+ The host half **registers no routes, needs no web server and ships no bundled assets**. The browser half **falls back to its own defaults when it cannot read the host's values** (a missing or unreadable injection table costs the page nothing).
260
+
261
+ On the product side it touches only documented hooks: `data-composer-card`, `data-menu-material`, the `role` / `aria-checked` a control publishes itself, `data-ds-dark-theme`, `data-streaming`, and `body` / `#root`. It **depends on no generated CSS-module class name**, and tests pin each of those down.
262
+
263
+ ### Compatibility
264
+
265
+ - Built for DSH alone, with no cross-host support. The floor is `dsh-v0.1.5-rc.2`, backwards compatible from there — the same floor as the series' `dsh-mimo-skin`, since both halves call the same engine interfaces (the `webserver/index-inject` table, `settings.configure`, and the client module table).
266
+ - Known limits are in [Trade-offs](#trade-offs) below.
267
+
268
+ ### Development
269
+
270
+ ```bash
271
+ npm install # build-time dependencies (esbuild + schemastery)
272
+ npm run build # one command builds both halves and the preview
273
+ npm run watch # the same, with --watch
274
+ npm test # unit tests
275
+ ```
276
+
277
+ | Command | Artifact | Notes |
278
+ | --- | --- | --- |
279
+ | `npm run build` | `lib/index.js` | the host half, ESM, with `@deepseek-ai/cordis` kept external |
280
+ | | `lib/client.js` | the browser half, bundled for the shell's module-table dialect (`window.__ModuleLoader__`) |
281
+ | | `lib/preview.js` | the self-test preview, an IIFE; double-click `preview/index.html` and it runs |
282
+
283
+ **`lib/` is committed, so a change under `src/` should carry the rebuilt `lib/` with it** — publishing rebuilds through `prepack`, which keeps the committed copy in step with `src/`.
284
+
285
+ After a source change: the browser half is **hot**, while the host half loads once when the engine process starts — replacing the host half means restarting the engine, not reloading the page.
286
+
287
+ #### The self-test preview page
288
+
289
+ `preview/index.html` mounts the **real modules** on a simulated shell: the same stylesheet, the same applier, the same status band, the same signal reading. It needs **no engine, no profile and no sign-in** to show the palette, switch shells, change the accent, simulate a reasoning turn and check the contrast ratios. It is not a copy of the product — it is a page carrying the product's token names and a few documented hooks.
290
+
291
+ ```bash
292
+ npm run build && open preview/index.html
293
+ ```
294
+
295
+ #### Tests
296
+
297
+ **270 cases across 12 files, all green** (`npm test`). A good part of them are **negative assertions**: no generated class names; no radial gradients, background blur, blend modes or noise; the dark blocks must come after the light ones; the state colours and the toast / tooltip / diff tokens must not be taken over; `--dsw-alias-label-primary-inverted` must not be rewritten; neither switch state may reuse the product's own colour; corners may only be 2 / 4 / 8; the primary button must go through the accent; and a badge must never become dark-on-dark. Others are measured assertions on the contrast floors: the accent's text-safe step (including the re-check after quantisation), the text and grey ladders of both shells, and every stop of both gradient ramps.
298
+
299
+ ## FAQ
300
+
301
+ **Installed it, but nothing changed.**
302
+ First check that the `dsh-trae-skin` row is switched on in the Plugins page, then make sure the page has been reloaded. The host half loads only once, when the engine process starts, so if you have just replaced the host half, the DSH engine itself is what needs restarting.
303
+
304
+ **Why is the accent not the reference's `#32f08c` when it is used as text?**
305
+ `#32f08c` is only 1.82:1 on white paper, below the AA 4.5:1 floor. The skin keeps the contrast, so that step is computed (with the default accent, `#1c864e` in the light shell at 4.60:1; the dark shell still uses `#32f08c` at 13.11:1). Copying the original value exactly would mean accepting links below AA in the light shell.
306
+
307
+ **The status band is covered by something.**
308
+ The product's own opaque full-window overlays sit at a `z-index` of 100 or more, and the band is at `z-index: 4`: the skin does not force its way through. When something covers the band, it stays covered.
309
+
310
+ **The type does not look like the reference.**
311
+ The skin **ships no font files**; it only declares font stacks, so the actual glyphs come from the fonts installed on the machine, and it falls back from there.
312
+
313
+ **How do I upgrade?**
314
+ The plugin does not update itself. On the GitHub route, remove the row and install the new version (`dsh plugin --profile web remove dsh-trae-skin`, then `add` again). On the local `link:` route, update the code in the working directory, run `npm run build` to rebuild `lib/`, and run `node scripts/install-profile.mjs` again (it is idempotent). Either way, **restart the engine** afterwards to replace the host half.
315
+
316
+ ## Trade-offs
317
+
318
+ **Which two layers the reference gives.** The dark shell comes from `trae.cn`'s own site layer (`:root`, with no theme attribute — the page a visitor sees). The light shell comes from that design system's light block (`.solo-theme[data-theme=light]`): the site has no light mode, so the light shell here is assembled from its own light tokens.
319
+
320
+ **Three light-shell values had their lightness moved along the reference's own hues, because its light block does not clear the floor on white paper** (this is not a second palette):
321
+
322
+ | Role | Reference light | Here | Why |
323
+ | --- | --- | --- | --- |
324
+ | Tertiary text | `#8a9096` | `#6f757b` | 3.23:1 → 4.66:1, so caption text passes AA |
325
+ | Brand fill | `#0fdc78` | `#0a9c52` | 1.82:1 → 3.57:1, without which a button is not even legible as a shape on white paper |
326
+ | Gradient stops | `#0ac27b #0ecd70 #06c179 #63c5b1` | `#078856 #09874a #048755 #408073` | 2.07–2.36:1 → 4.50–4.61:1; the wordmark is rendered as text (`background-clip: text`) |
327
+
328
+ **Trade-offs taken:**
329
+
330
+ - **The send button's glyph.** The product draws that arrow with a **hard-coded white literal** (its own comment explains why: white type over the blue info fill). The skin replaces the fill with the accent, so the glyph has to move with it — done through two things only: the composer card this plugin is documented to hook, and the control's own accessibility contract (icon-only, opens no popup, its only child is the glyph). No generated class names. If the product's markup changes, this rule **misses and falls back to the product's own white** rather than breaking.
331
+ - **The band is covered by the product's opaque full-window overlays** (the band is `z-index: 4`, those overlays 100 and up): the skin does not force its way through.
332
+ - **No bold monospace.** The reference's 400 / 500 / 600 all point at one Regular file, so it has no bold monospace, and this skin does not fake one.
333
+ - **In follow mode, uninstalling leaves the product's last `data-ds-dark-theme` behind**: that attribute belongs to the product's theme runtime.
334
+ - **With the band off, the card also disables “Content only”**: a switch about a band that does not exist should say so, not remember a value for later.
335
+ - **The card's corners are 6 / 8px, not the page's 2 / 4 / 8px**: the page (the layer the skin replaces) keeps the reference's scale, while the card is a form inside the product's page and lines up with the series' other card.
336
+
337
+ **Deliberately not done**: the grid and pointer-follow behind the main display area, the depth veil it brings, and the three mode tints; plus radial gradients / glows, `backdrop-filter`, blend modes and noise (those four appear 0 times across the reference site, and tests assert each one stays out).
338
+
339
+ ## Contributing
340
+
341
+ - For an issue, include your DSH version, your system and the steps to reproduce; a screenshot helps for anything visual.
342
+ - For a pull request, keep the tests green (`npm test`) and commit the rebuilt `lib/` alongside any change under `src/`.
343
+ - One change does one thing; no unrelated refactoring.
344
+
345
+ ## License
346
+
347
+ MIT, see [LICENSE](LICENSE). The third-party packages the host half inlines (`@deepseek-ai/schemastery`, `@deepseek-ai/cosmokit`, `@standard-schema/spec`, all MIT) are credited in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md) and appended to the artifact by `build.mjs`.
348
+
349
+ Visual reference: [`trae.cn`](https://www.trae.cn/). Host platform: [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness). Sibling plugin: [`dsh-mimo-skin`](https://github.com/RonnyJung2021/dsh-mimo-skin) (the two install independently and neither depends on the other). Changes are logged in [CHANGELOG.md](CHANGELOG.md).