Repository navigation
Releases
v0.1.7 — 冒烟抓到的两处变体/方案抢占
UI 冒烟第一次跑,就抓到两条真问题
新增的 UI 冒烟(tests/ui-smoke.mjs,Playwright 连真机 GUI)在第一轮就报红,两条都不是测试自己的问题:
① 变体记忆被自己的看门狗抢占。 theme/change 的镜像监听会把「当前方案」回写成变体记录。启动恢复时读到的变体(宣纸)与官方偏好(夜墨 = dark,且现在会落盘)不一致,镜像立刻把 localStorage 改成 song-grid-yemo —— 用户的选择被自己改掉。实测:写入 song-grid-xuanzhi 刷新后变成 song-grid-yemo、界面停在深色。
→ 修:删掉这处镜像回写。官方偏好现在自己能落盘(loopback 入口下),明暗方案不需要插件替它记;变体记忆只负责「宋律开着没」+ 选择器行的提示。
② 启动恢复跟官方显式偏好抢方案。 原来只要记住的是变体就 setTheme(变体方案),于是「服务端偏好 = dark + 本地记住宣纸」两边互相覆盖。
→ 修:只在官方偏好是「跟随系统」时才替它决定;用户显式选过 light/dark 就尊重它。背景与方案的对应关系据此成为不变量:dark → ink-night、light → celadon —— 冒烟按这个不变量断言,而不是按「选了宣纸就必须浅色」。
新增的测试
tests/ui-smoke.mjs(11 项):用 localStorage 驱动三档(夜墨 / 宣纸 / 关),不点界面(免掉语言差异)。断言:开关挂上、山水背景已注入且与当前方案一致、侧栏填充 alpha 达标(深 ≤0.65 / 浅 ≤0.70)、关掉时背景 none 且正文字体回宿主默认,以及**「刷新后变体没被写回」这条回归守卫**。
tests/contract.mjs 同步加两条静态守卫:不得 writeLocal(variant)、只在 preference === 'system' 时替方案。跑法:npm test(34 项)/ npm run test:ui(11 项)。
完整 diff:v0.1.6...v0.1.7
v0.1.6 — 跨浏览器验证通过;入口 hostname 决定 settings 是否落盘
结论:跨浏览器能用了,但入口 hostname 是决定性条件
0.1.5(Config + .volatile())让宿主的 settings 服务开始服务这个命名空间:
/api/settings/describe: 19 → 20 个命名空间
"song-grid-theme" autoGenerate:false applies:live value:{theme:"system"}
但选择仍然写不进去 —— 客户端报 status=unavailable。真因在上游 @deepseek-ai/dsh-client-ui-settings:
const persistence = ctx.remote.$host.isLoopback ? "host" : "memory";而 @deepseek-ai/dsh-client-connection 的 isLoopbackHostname() 只认 localhost / ::1 / 127.0.0.0-8 —— dsh.localhost 不算。于是整份 settings 降级成 memory:不只是本插件,官方主题偏好、语言、通知设置一样不落盘(这也解释了为什么"官方偏好刷新也会回退")。
实测(同一个代理,只换 hostname)
| 入口 | 结果 |
|---|---|
http://dsh.localhost:8080/ |
status=unavailable,写不进服务端 |
http://localhost:8080/ |
user:{theme:"songgrid"} revision:1,profile patch 落盘 - id: song-grid-theme + config: {theme: songgrid};清空 localStorage 刷新 → 主题从服务端恢复 |
所以:想让主题选择跨浏览器/跨机器记住,用 http://localhost:8080/ 打开;只想本浏览器记住,任何入口都行(插件自带 localStorage 镜像)。
本版改动
persist()不再吞掉失败:命名空间未就绪 / 宿主拒绝 / 抛错都console.warn—— 静默失败正是这个 bug 藏了两轮的原因。- README(en/zh)写明入口要求与降级行为。
完整 diff:v0.1.5...v0.1.6
v0.1.5 — Config 字段必须 .volatile()(0.1.4 的命名空间没被服务)
为什么 0.1.4 没生效:Config 字段必须标 .volatile()
0.1.4 已经把宿主面从运行时 ctx.settings.register("songgrid", …) 改成声明 Config(这是 0.2.0-rc.2 的正确机制:命名空间按宿主插件条目服务,id: ui-theme + export const Config 就是这么工作的)。但重启后 /api/settings/describe 里依然没有 song-grid-theme。
读完 dsh-settings 的枚举路径,差的是这一步:
describe() → volatileForm(schema)
· schema.meta.volatile → 收
· object → 只保留「子字段里能算出 volatile 的」那些
· 叶子字段没有 meta.volatile → 返回 undefined(算「普通配置」,不是活配置)
· 一个 volatile 子字段都没有 → 整个条目被过滤掉,命名空间不被服务
实测复刻这个判定:
| Config | volatileForm |
|---|---|
z.object({ theme: z.string().default("system") }) |
undefined ← 0.1.4 |
z.object({ theme: z.string().default("system").volatile() }) |
{ theme } ← 0.1.5 |
官方的 ui-theme 条目同样是 preference / fontSize 两个字段都标 .volatile() —— 照着它的形状写,还差这一笔。
这一版带来什么
命名空间 song-grid-theme 可被服务之后,主题选择会写进本 profile 的 cordis 配置:
- 换浏览器 / 换机器 / 清缓存:第一次打开就能恢复宋律(服务端值)
- 本浏览器仍以 localStorage 为准(切换立刻生效,不等网络),服务端只在本地没有记录时兜底
settings.configure({ auto: false })保证不会多出一个只有裸文本框的设置页
完整 diff:v0.1.4...v0.1.5
v0.1.4 — 跨浏览器持久化 + 浅色 tertiary 提亮 + 未选中零影响
选择改用宿主条目命名空间 —— 换浏览器/换机器也能恢复
0.2.0-rc.2 的 settings 命名空间是按宿主插件条目服务的:条目声明了 Config 才会出现在 /api/settings/describe 里(宿主自己的主题条目就是 web-app patch 里的 id: ui-theme + 包内 export const Config,命名空间名 = 条目 id)。
我们原来照 0.1.5 的写法在运行时 ctx.settings.register("songgrid", …) —— 那样登记不进去:命名空间永不被服务,客户端 configForms 的 snapshot 停在 pending,选择只能退化到浏览器 localStorage(换浏览器、清缓存就掉回原生外观)。
现在:
lib/index.js声明export const Config(字段theme)- 按官方同款
settings.configure({ auto: false })抑制自动生成的设置页(否则设置里会多一个只有裸文本框的表单) - 客户端取条目 id
song-grid-theme(= 本包cordis.patch.yml里的 id)
localStorage 仍是首选来源、服务端只在本地无记录时兜底,所以新浏览器/新机器第一次打开就能直接恢复宋律。生效需宿主重启一次。
宣纸 tertiary 提亮
#8B8478 (3.35:1) → #736E60 (4.67:1),caption 同步。宿主把消息正文、工具调用、时间戳、占位符都画在 --dsw-alias-label-tertiary 上,浅色下 3.35:1 对 13px 正文偏淡。
没选中宋律 = 零影响
排版层(正文宋体)与引用块的窑铜强调,也挂到 html[data-songgrid-theme="on"] 下。背景层自 0.1.2 起已经这样 —— 现在整个插件在未选中时对宿主 UI 的影响为零。
完整 diff:v0.1.3...v0.1.4
v0.1.3 — 夜墨文字亮度对齐宿主深色梯子
夜墨:三级文字对齐宿主深色梯子
问题:正文、工具调用、时间戳、占位符读起来发暗。宿主把这些文本都画在 --dsw-alias-label-tertiary 上(dsh-client-ui-chat 里 14 处),而夜墨把 secondary / tertiary 各压低了两档 —— tertiary #7A7468 落在 #10151A 上只有 4.0:1,13px 正文在这个对比度下明显发暗。
改法:对齐宿主深色自带的比例(--dsw-static-neutral-bluish-* 在深色下取 400/300/50 三档)。
| token | 改前 | 改后 |
|---|---|---|
label-primary |
#E8E3D6 · 14.4:1 |
不动 |
label-secondary |
#A8A193 · 6.6:1 |
#CFC8B8 · 11.4:1 |
label-tertiary |
#7A7468 · 4.0:1 |
#B0A897 · 7.8:1 |
label-caption |
#7A7468 |
#B0A897 |
label-primary-dimmed |
#A8A193 |
#CFC8B8 |
对照宿主深色自身的梯子:primary ≈17:1 / secondary 12.5:1 / tertiary 8.6:1 —— 改后落在同一量级,不是随手提亮。
宣纸(浅色)未改:浅色 tertiary 3.35:1 与宿主自身的浅色档(neutral-600 #81858c ≈ 3.5:1)一致,属于宿主既有约定。
完整 diff:v0.1.2...v0.1.3
v0.1.2 — 主题选择不再丢 + 夜墨「海上明月孤舟」加强
修复:主题选择不再丢 —— 「侧栏不透明、看不到背景」
现象:插件装着、山水背景在,但侧栏是不透明的原生色。因为选中状态活不过一次刷新(山水却无条件注入),于是留下「有背景、没配色、侧栏还是不透明」的半应用态。
根因:宿主 settings 命名空间在部分组合里不会被服务(profile 行被版本闸禁用时 host face 不加载、命名空间不注册),configForms 的 snapshot 永远停在 pending —— 选择既写不进去,也读不回来。
改法:
- 选择同时写
localStorage并在启动时以本地为准恢复,服务端值只在本地无记录时兜底;变体(宣纸/夜墨)也一起记住,否则刷新后明暗方案回落系统值。 - 背景层改为只在宋律调色板挂着时生效(
html[data-songgrid-theme="on"]):没启用就一点山水都不出现,不再留半应用的样子。
加强:夜墨「海上明月孤舟」
| 改前 | 改后 |
|---|---|
![]() |
![]() |
场景侧:月 r46→54 并加光晕/月华环 · 36 颗星子 · 三层山影对比整体提高、每条山脊加一道月照边光 · 月影水柱 0.6→0.78 · 水痕逐层提亮约 1.4× · 孤舟背后铺一层被月照到的水汽(原来 0.10 太淡,船体近黑压在近黑水面上等于隐形)· 船灯更亮并补三道暖色灯影。
覆层侧让路:深色对话区 0.90 → 0.80,侧栏 0.70 → 0.60。两边各让一步,夜色层次才真的落到眼睛里。
对比图只截了主内容区,避开侧栏里的会话内容。
完整 diff:v0.1.1...v0.1.2
v0.1.0
First public release of 宋律 · Songgrid — Song-dynasty aesthetics × Swiss International Style for the DeepSeek Harness web UI.
What's in it
- Two variants — 宣纸 Xuanzhi (light) and 夜墨 Yemo (dark), plus follow-system
- Two accents, two jobs — Ru celadon
#3A7284(4.91:1, passes WCAG AA) carries interactive states; kiln copper#9A6B50is reserved for blockquotes, never for controls - Structure layer orthogonal to colour — 4pt spacing scale, 2/4px radii, four line-height steps, five motion durations + three curves, focus ring
- 46 mask icons driven by
currentColor
Install
dsh plugin --profile web add dsh-theme-songgridVerified on DSH 0.1.5-rc.2. Not affiliated with DeepSeek.

