dsh-claude-style 0.13.2 → 0.14.0-dev.12

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 (93) hide show
  1. package/CHANGELOG.md +182 -0
  2. package/LICENSE +0 -5
  3. package/README.en.md +1 -1
  4. package/README.md +2 -2
  5. package/THIRD_PARTY_NOTICES.md +282 -6
  6. package/docs/FEATURES.en.md +13 -14
  7. package/docs/FEATURES.md +13 -14
  8. package/docs/SETTINGS.en.md +21 -12
  9. package/docs/SETTINGS.md +21 -12
  10. package/docs/STYLE.md +151 -12
  11. package/lib/assets/1952ecd55916.js.br +0 -0
  12. package/lib/assets/3b91bf51db72.js.br +0 -0
  13. package/lib/assets/4ec527e58330.js.br +0 -0
  14. package/lib/assets/7323c8c86470.js.br +0 -0
  15. package/lib/assets/755d09170f37.js.br +0 -0
  16. package/lib/assets/c02a70a798bc.js.br +0 -0
  17. package/lib/assets/d8df7a57fbe3.js.br +0 -0
  18. package/lib/assets/manifest.json +28 -28
  19. package/lib/client.js +11 -8
  20. package/lib/host/brand.js +136 -0
  21. package/lib/host/http.js +74 -0
  22. package/lib/host/peakrate.js +7 -0
  23. package/lib/host/routes.js +26 -88
  24. package/lib/host/search.js +8 -1
  25. package/lib/host/settings.js +5 -0
  26. package/lib/model-descriptions.json +195 -43
  27. package/package.json +12 -4
  28. package/docs/decisions/D11-private-routes.md +0 -26
  29. package/docs/decisions/D12-fail-fast.md +0 -36
  30. package/docs/decisions/D14-account-surface.md +0 -26
  31. package/docs/decisions/D15-hdsl-contract.md +0 -24
  32. package/docs/decisions/D16-popover-baseline.md +0 -23
  33. package/docs/decisions/D17-permission-presets.md +0 -26
  34. package/docs/decisions/D19-host-contracts.md +0 -27
  35. package/docs/decisions/D20-sliding-pill.md +0 -26
  36. package/docs/decisions/D21-archived-list.md +0 -24
  37. package/docs/decisions/D22-search-panel.md +0 -30
  38. package/docs/decisions/D24-mascot.md +0 -32
  39. package/docs/decisions/D25-ban-screen-titlebar.md +0 -24
  40. package/docs/decisions/D26-motion-preference.md +0 -23
  41. package/docs/decisions/D27-context-stats.md +0 -35
  42. package/docs/decisions/D28-desktop-band.md +0 -23
  43. package/docs/decisions/D29-feature-switches.md +0 -24
  44. package/docs/decisions/D3-host-selectors.md +0 -28
  45. package/docs/decisions/D30-palette-and-typeface.md +0 -25
  46. package/docs/decisions/D31-windows-titlebar.md +0 -24
  47. package/docs/decisions/D32-chat-interactions.md +0 -33
  48. package/docs/decisions/D33-stylesheet-ownership.md +0 -26
  49. package/docs/decisions/D34-turn-navigator.md +0 -29
  50. package/docs/decisions/D35-selection-scope.md +0 -23
  51. package/docs/decisions/D36-modules-and-bundling.md +0 -35
  52. package/docs/decisions/D38-assets.md +0 -28
  53. package/docs/decisions/D39-on-demand-loading.md +0 -33
  54. package/docs/decisions/D4-composer-gate.md +0 -22
  55. package/docs/decisions/D40-observation-bus.md +0 -36
  56. package/docs/decisions/D41-scroll-owner.md +0 -40
  57. package/docs/decisions/D42-feature-manifest.md +0 -28
  58. package/docs/decisions/D43-overlay-and-upstream.md +0 -27
  59. package/docs/decisions/D44-contract-module.md +0 -36
  60. package/docs/decisions/D45-testing.md +0 -30
  61. package/docs/decisions/D46-monorepo.md +0 -31
  62. package/docs/decisions/D47-build-output.md +0 -30
  63. package/docs/decisions/D48-documentation.md +0 -28
  64. package/docs/decisions/D49-visual-yield.md +0 -30
  65. package/docs/decisions/D5-model-copy.md +0 -29
  66. package/docs/decisions/D50-brand.md +0 -24
  67. package/docs/decisions/D51-css-toolchain.md +0 -34
  68. package/docs/decisions/D52-header-band.md +0 -31
  69. package/docs/decisions/D53-usage-merge.md +0 -33
  70. package/docs/decisions/D54-peakrate-meter.md +0 -33
  71. package/docs/decisions/D58-redraw-retired.md +0 -29
  72. package/docs/decisions/D60-settings-transport.md +0 -31
  73. package/docs/decisions/D61-wallpaper-under-one-mask.md +0 -28
  74. package/docs/decisions/D62-file-row-merge.md +0 -28
  75. package/docs/decisions/D63-turn-status-retired.md +0 -29
  76. package/docs/decisions/D7-one-package.md +0 -23
  77. package/docs/decisions/D9-style-performance.md +0 -25
  78. package/docs/decisions/README.md +0 -86
  79. package/lib/assets/2c01384360ac.js.map.br +0 -0
  80. package/lib/assets/31253ed42440.js.br +0 -0
  81. package/lib/assets/5063c46824b6.js.br +0 -0
  82. package/lib/assets/55a6154f87b5.js.map.br +0 -0
  83. package/lib/assets/5e05b29d5005.js.br +0 -0
  84. package/lib/assets/668d06ca781b.js.map.br +0 -0
  85. package/lib/assets/930546669455.js.map.br +0 -0
  86. package/lib/assets/968fcbfc3e22.js.br +0 -0
  87. package/lib/assets/abf0547f08a4.js.br +0 -0
  88. package/lib/assets/ac9b0e625837.js.map.br +0 -0
  89. package/lib/assets/ad2905345a69.js.br +0 -0
  90. package/lib/assets/ba68f60ecc71.js.map.br +0 -0
  91. package/lib/assets/c52e41b4842b.js.map.br +0 -0
  92. package/lib/assets/e1291f79b7c9.js.br +0 -0
  93. package/lib/client.js.map +0 -7
@@ -1,33 +0,0 @@
1
- # D39. 按需加载:首屏用不到的功能拆成单独的块
2
-
3
- - **状态**:已实施
4
- - **分组**:构建与源码
5
- - **关联**:D12、D36、D38、D42、D43、D49
6
-
7
- ## 决定
8
-
9
- - 清单写 `load: 'deferred'` 的功能离开主产物:它自己的模块打成一个块,由宿主半边的资产路由按内容哈希提供(D38),页面被接管后立即请求,块到达后安装。块到达之前它的句柄不在 `ui` 上,读它的功能照常把它当作缺席。现在延后的有设置页、内容搜索、吉祥物、发送动效、对话导航、上下文弹层与封号彩蛋。
10
- - 主产物携带的模块一律不复制进块:块对这些模块的导入保持为导入,运行时经 `virtual:dsh-claude-style/chunk-modules` 解析到主产物自己的实例,宿主包按加载器的 `require` 原样交出;两个块都会携带的模块并入主产物(`scripts/chunks.mjs`)。构建期生成的模块(`virtual:dsh-claude-style/generated`,D36)与源码模块同样处理:共享表按它们的 `virtual:` 路径给出主产物里的那一份,块不再带走第二份树摇结果。构建拒绝主产物与块同时携带一个模块,源码模块与生成模块都算在内,也拒绝块读取构建编号。
11
- - 块与它的 source map 各自按自身内容的哈希命名(`chunkFiles`),由资产路由的一条路径送出(D38):只改注释、不动代码时,map 换了名字,浏览器不会拿旧的那份。
12
- - 块是经典脚本:运行时把工厂挂到自己的 script 元素上(`document.currentScript.__dshChunk`),`core/chunks.ts` 取下工厂、以共享表为 `require` 调用、拿到 `install`,脚本元素随即移除;同一代里同一个块只请求一次。
13
- - 块装不上(加载失败、没有工厂、运行时抛错)等同于功能安装失败:报告一次并退役(D12)。块到达时这一代已经结束、页面已让给皮肤(设置页除外,D49)、功能已被关掉或已退役,就不安装。
14
- - 样式表留在主产物:层叠顺序由全部样式表的统一序号决定(D42),块到达之前这些规则匹配不到元素。
15
- - 只延后首屏可以缺席的功能:首屏就要改写宿主界面的(账号区、首页、输入区、模型与权限控件)留在主产物,否则页面先露出宿主原样再被改写。
16
- - 构建的写入顺序是有意的:新资产先写、清单随后、清单里没有的文件最后删,`lib/client.js` 写在最后。页面跑着的客户端包一变就换代,而客户端热重载只认主产物:把主产物放到最后写,换代的那一代要的块已经在磁盘上、也已经在清单里,不会在新块落盘之前按旧清单请求块。
17
- - 调试档 `DSH_CLAUDE_STYLE_DEBUG=1` 让清单继续带着上一代仍然存在的条目并保留它们的文件:宿主在启动时读到旧主产物的页面请求的是那一代的块名,名字还在清单里它才送得出去。发布构建不带这个标记,清单就是这一次构建自己的资产;持续集成里这个标记一律不生效。
18
-
19
- ## 理由
20
-
21
- - 主产物在页面启动时整份求值;延后的功能在启动之后各自作为短任务求值与安装,启动阶段的主线程阻塞随之减少。本地临时宿主实测(两种产物各载入 20 次取中位数):主产物 789 KB → 690 KB,前 3 秒的主线程长任务 713 ms → 648 ms,皮肤生效时刻 298 ms → 280 ms。
22
- - 宿主加载器没有插件内的按需入口,也不接受外部 issue 与 PR(D43);资产路由已经存在(D38),块走同一条路由与同一份清单,不需要新的宿主能力。
23
-
24
- ## 代价
25
-
26
- - 延后的功能比主产物晚到一次请求(本地实测约 5 ms);块到达之前打开设置页、按下搜索或点账号行没有反应。
27
- - 宿主在启动时缓存客户端包:没有客户端热重载的宿主上,插件更新后页面拿到的仍是旧主产物,只有重启宿主才能换到新的;链接安装的开发机可以在重建时带上 `DSH_CLAUDE_STYLE_DEBUG=1`,让上一代的块继续可以送达。
28
- - 构建要计算模块归属:主产物的模块集合与块的模块集合互相影响,反复计算到没有模块被携带两次为止。
29
-
30
- ## 重审条件
31
-
32
- - DSH 加载器提供插件内的异步分块或资产 URL 时,块改走加载器的机制,删除共享表与经典脚本这层包装。
33
- - 某个延后功能需要在首帧改写宿主界面时,把它移回主产物。
@@ -1,22 +0,0 @@
1
- # D4. 输入框样式的构建期门控
2
-
3
- - **状态**:已实施
4
- - **分组**:构建与源码
5
- - **关联**:D51
6
-
7
- ## 决定
8
-
9
- - 输入框相关的样式规则写在 `/* @composer-gate */` 标记之下;构建在语法树上把门控属性(`COMPOSER_ATTR`)盖到标记以下每一条规则的作用域那一段上(D51),缺少标记或标记以下没有规则即构建失败。
10
- - 门控属性由输入框功能按「输入框重绘」偏好与当前页面写在 `<body>` 上;偏好关闭或功能退役时,这些规则整体失效。
11
-
12
- ## 理由
13
-
14
- - 输入框是性能与正确性最敏感的区域,它的规则必须能随一个开关整体撤下。
15
-
16
- ## 代价
17
-
18
- - 写输入框样式要放在标记之下;构建维护门控逻辑。
19
-
20
- ## 重审条件
21
-
22
- - 无,这是安全网。
@@ -1,36 +0,0 @@
1
- # D40. 观察总线与帧管线
2
-
3
- - **状态**:已实施
4
- - **分组**:运行时
5
- - **关联**:取代 D6;D12、D32、D41、D42、D45
6
-
7
- ## 决定
8
-
9
- - 页面上只有一个 MutationObserver(`packages/client/src/core/bus.ts` 的 `subscribeMutations`)。订阅写明目标与 MutationObserver 自己的选项(子节点、属性及其名单、字符数据、是否含子树),总线把同一目标上各订阅的选项合并后挂上,每批记录再按每个订阅自己的选项分回去:一个订阅收到的正好是它自己的选项选中的记录,回调按订阅建立的先后执行。订阅增删时总线重挂,旧挂载已收集的记录照样交给订阅者;没有订阅时整个观察者摘下,所以 `<head>` 只在有人订阅时被观察。
10
- - 尺寸由 `observeSize` 统一登记:一个订阅可以同时看几个元素,每批尺寸变化只回调一次、只带它自己的元素。普通订阅共用一个早建的 ResizeObserver;`afterHost` 的订阅共用另一个,每加入一个订阅就重建一次。浏览器按建立先后调用 ResizeObserver,宿主在挂上消息列时新建自己的,所以在新消息列出现之后订阅的 `afterHost` 回调排在宿主之后(D32 的跟随在宿主写到最底的同一帧里把位置收回)。
11
- - 帧管线(`packages/client/src/core/frame.ts` 的 `requestFrame`):皮肤的每个帧回调都经它,整页只有一个 `requestAnimationFrame`。每帧先依次运行所有任务的读阶段,再依次运行写阶段;写阶段也可以读,读到的是本帧在它之前写下的结果。帧内请求的任务属于下一帧,帧内取消的任务不再运行剩下的阶段。刷新、聊天区跟随、插入符、开合、淡入、气泡动效、滚动弹簧、对话导航与各处「等一帧」都在写阶段运行。
12
- - 订阅、尺寸登记与帧任务出错时用 `reportError` 报告,其余照常(D12)。
13
- - 必须继续满足的行为:
14
- - 带 `data-dsh-claude-quiet` 的皮肤记账节点:刷新的订阅设 `skipQuiet`,容器内部的改动与增删带此标记的节点不算页面变化。
15
- - 指针移动只经 `onActivity` 告诉功能「读者在」,只记时刻,不触发刷新。
16
- - 皮肤代发给宿主菜单的 Escape 带 `__dshHostMenuEscape`,全局键盘路径见到就跳过。
17
- - 系统「减少动态效果」中途翻转、或另一个聊天插件装卸,都重解动画档位并重跑偏好监听(`refreshMotionAttribute()`、`notifyEnvironmentChange()`),功能不各自听媒体查询。
18
- - 文本选区、主题翻转、分段控件尺寸、皮肤中心的归属标记这些监听各是总线上自己的订阅,不触发刷新。
19
- - 弹层开着期间的指针与按键在打开时挂、关闭或卸载时摘。
20
- - 每个订阅只在值变化时写 DOM(同值写入也会让元素样式失效)。
21
- - 订阅随功能的安装与卸载成对增删;总线与帧管线的分发顺序、选项路由、早晚两个尺寸观察者的先后由单元测试(D45)在真实浏览器里核对。
22
-
23
- ## 理由
24
-
25
- - 各功能自带观察者时,先后顺序只能碰运气:D32 的跟随与宿主互相争抢滚动位置,就是两个 ResizeObserver 谁先建立的问题。
26
- - 一个注册表让顺序成为可声明、可测试的东西;读写分阶段让多个功能在同一帧里不互相触发强制布局。
27
-
28
- ## 代价
29
-
30
- - 总线成为所有功能的公共依赖,它出错影响面大,需要最完整的单元测试。
31
- - 选项合并后,浏览器替每个订阅做的筛选改由总线在脚本里做:同一批记录要按订阅逐条判定。
32
- - 几个功能的帧回调既读又写,目前都放在写阶段;拆成读写两半要逐个确认它读的是不是自己或别人本帧刚写的结果。
33
-
34
- ## 重审条件
35
-
36
- - 宿主提供统一的页面变化订阅或渲染阶段钩子时,总线改为接入宿主的。
@@ -1,40 +0,0 @@
1
- # D41. 聊天区的滚动位置只有一个写入者
2
-
3
- - **状态**:已实施
4
- - **分组**:运行时
5
- - **关联**:D32、D34、D40、D43、D45
6
-
7
- ## 决定
8
-
9
- - 聊天区的滚动位置只有 `packages/client/src/shared/scroll-owner.ts` 可以写:会话滚动条与过程组的滚动主体都由它写,每个滚动容器各自仲裁。滑行用 `packages/client/src/shared/scroll-ease.ts` 的弹簧,构建检查除滚动主人之外没有模块引用这条弹簧。
10
- - 请求的来源与优先级,从高到低:
11
- - `jump`:对话导航滑到读者选中的一轮;
12
- - `fold`:过程组被皮肤自动展开后把位置放回原处,卷帘门落定后把跟随交还宿主;
13
- - `composer`:输入卡片变高时把停在底部的读者留在底部;
14
- - `follow`:增强跟随在结构变化时把跟随交还宿主;
15
- - `stream`:流式输出期间撤回宿主写到最底的那一下并滑过去,与 `follow` 同级;
16
- - `process`:封顶过程组主体的追赶。
17
- - 读者自己的滚轮是浏览器的:滚动主人不接管这个事件,只把滚轮、触摸、指针按下、滚动键与页内查找记成「读者动过」,跟随与流式滑行的请求因此被拒绝。指针按下落在滚动条自己的条带上才算(会话滚动条与过程组主体用同一个判据),落在内容上是读者在打开什么。滚轮的减速曲线、触控板的惯性、边界处的表现都由平台自己画。
18
- - 仲裁规则:
19
- - 容器上正在滑行的来源优先级更高时,请求被拒绝;同级或更高的请求照常执行,直接写入还会结束正在滑行的低优先级来源。
20
- - 读者接管一个容器后,`follow`、`stream`、`process` 的请求被拒绝,正在进行的滑行在下一帧结束;读者回到这个容器底部的阈值以内(会话 25px,过程组主体 4px),接管结束。结束由滚动事件当场判定,内容在读者回到底部之后才增长也不会留下接管。对话导航跳到某一轮时同样取一次接管(`holdReader`):跳转是读者自己选定的阅读位置,跟随在这一期间让开。接管期间容器带着 `data-dsh-claude-reader-hold`,等待行的固定读这个标记(`packages/client/src/features/chat-wait/chat-wait.css`),只在跟随自己移动位置时成立,读者往回翻看时该行留在文档位置。
21
- - 读者自己发出的消息(或宿主替它挂上的回显)到达后的 1.2 秒内,`stream` 的请求被拒绝、滑行结束,宿主把新消息滚进视野的那一下照旧一步到位。这段时间属于一个会话:会话一换当场作废,因为打开会话本身就会挂上这一轮自己的消息行。
22
- - 减少动态效果时,滑行请求直接写目标位置。
23
- - 读者的滚动意图在 `packages/client/src/shared/reader-intent.ts` 判定。功能问「读者在某一时刻之后动过没有」用 `readerMovedSince`。
24
- - 与宿主的交接只在这里发生:撤回宿主写到最底的那一下(`takeBackHostPin`)、在宿主的跟随关闭时滑到底或按它自己的「回到底部」按钮把跟随重新点亮(`handBackFollow`)、流式滑行期间把这个按钮藏起来(`holdFollowButton`)。
25
- - 意图、滚动与消息到达的监听只在有功能加入(`joinScrollOwner`)期间挂着,最后一个功能离开时摘下。
26
- - 仲裁、接管与释放、消息到达的让位、减少动态效果下的直接写入由单元测试(D45)在真实浏览器里核对。
27
-
28
- ## 理由
29
-
30
- - 多个功能写同一个位置,跨功能的争抢只能逐个打补丁;一个写入者把争抢变成结构上不会发生的事。
31
- - 插件只写自己要画的那几段路(跳转、跟随、过程组追赶),读者的滚轮路径不在其中:接管它就要连平台自己的减速曲线、触控板惯性与边界回弹一起换掉,而这几样在各平台上的表现本来就不一样。
32
-
33
- ## 代价
34
-
35
- - 每个写滚动位置的功能都要先想清楚自己的来源与优先级;新增一个来源要同时改优先级表与单元测试。
36
- - 读者滚动与插件画出来的滚动是两种运动:插件自己滑的那几段有它的速度上限与曲线,读者的滚轮是浏览器自己的减速。接管判定依赖位置(25px / 4px 阈值),长距离的插件滑行滚到两端时被夹在 0 与末尾之间。
37
-
38
- ## 重审条件
39
-
40
- - 宿主开放可替换的滚动跟随策略(D43)时,跟随部分改为注册进宿主,这里只保留皮肤自己的跳转与过渡。
@@ -1,28 +0,0 @@
1
- # D42. 功能清单
2
-
3
- - **状态**:已实施
4
- - **分组**:运行时
5
- - **关联**:取代 D13;承接 D29;D12、D39、D40、D41、D44、D48
6
-
7
- ## 决定
8
-
9
- - 每个功能在主模块旁放一份 `<主模块>.manifest.ts`,只放数据:`id`、可选的 `handle`、安装顺序 `order`、偏好键或不设开关的理由(D29)、让位的上游插件 `yieldsTo`(D32)、样式表及其在整张样式表里的序号、拥有的设置页开关行、覆盖它的冒烟用例、中英文的功能描述;依赖的宿主契约(D44)是 `contracts` 字段:该功能读过哪些契约条目;要读的其他功能句柄是 `reads` 字段;`load: 'deferred'` 让这个功能离开主产物、由单独的块在页面被接管后送达(D39)。主模块导出 `install(ctx, ui)`。
10
- - 功能之间的依赖由类型检查把关:`core/scheduler.ts` 的 `Handles` 列出每个句柄名及其类型;功能模块把 `ui` 写成 `FeatureUi<typeof manifest>`(清单以类型导入,不进产物),只看得见自己的句柄和 `reads` 列出的那些,没声明的读取在类型检查时报错;完整的 `Ui` 只给入口与调度器用,lint 拒绝功能模块引入它。
11
- - 向观察总线(D40)与滚动主人(D41)的登记留在功能里,不进清单:两者都要在安装时定下目标元素、选项与回调,而清单只放数据;把它们写成清单字段等于把同一件事记两份,正是这份清单要消掉的东西。总线上「谁订了什么」要看时按订阅建立处查。
12
- - 构建在 Node 里读取全部清单(`scripts/shared/read-manifests.cjs`),生成虚拟模块 `virtual:dsh-claude-style/features`:按 `order` 排好的运行时字段加各自的 `install`,延后的功能带的是它的块的地址(D39)。清单本身不进产物。
13
- - 从清单汇总、不再各写一份的:安装与刷新顺序、样式表的拼接顺序(与主题样式表共用一套序号)、设置页的布尔开关行、冒烟测试的功能覆盖表、对上游的让位(入口按 `yieldsTo` 安装与卸下)、README 的功能介绍(D48)。声明 `load: 'deferred'` 的功能,覆盖表里补上加载通道自己的用例(`chunk-fault`、`chunk-late`,D39):它们考的是块怎么到达,不是某个功能自己的行为,所以不必由清单逐个列,`--feature` 也仍然选得到。
14
- - 功能句柄可实现的钩子:`sync`、`owns` + `close('outside')`、`onPointerDown`、`close('escape')`、`close('composer')`、`onInput`、`onFocusIn`、`reposition('viewport' | 'composer')`、`onCopyChange`、`onKey`、`onActivity`;没实现的跳过。`onKey` 收到读者的每一次按键,接手的功能自己调用 `preventDefault()`;功能退役后这个键交还宿主。
15
- - 退役按 `id` 或句柄匹配:只匹配到句柄时只停止 `sync`、不拆安装(设置页的导航)。
16
- - 功能内部的拆分就是普通的模块拆分,状态留在模块自己的闭包里,跨功能不伸手进别的模块的状态。
17
-
18
- ## 理由
19
-
20
- - 开关、测试覆盖、让位、契约依赖都是「关于这个功能」的事实,放在功能自己的清单里,加一个功能只加一个目录,漏写由类型检查与构建发现。
21
-
22
- ## 代价
23
-
24
- - 清单的形状要稳定;样式表序号把层叠顺序写成了数字,插入新样式表要选一个空位。
25
-
26
- ## 重审条件
27
-
28
- - 钩子继续增多(例如出现第二类键盘事件或第二种观察来源)时,按事件类别分组;总线上出现两处以上需要「谁订了什么」才能判定的行为时,订阅的声明再进清单。
@@ -1,27 +0,0 @@
1
- # D43. 覆盖层定位与等待扩展点
2
-
3
- - **状态**:已实施
4
- - **分组**:宿主边界
5
- - **关联**:取代 D2;D3、D19、D39、D41、D44
6
- - **现状**:一切效果靠浏览器端的 CSS 覆盖与 DOM 覆盖实现,不修改 DSH 引擎、apiproxy 与官方 UI 包;宿主目前不接受外部 issue 与 PR,扩展点只能等宿主自己开
7
-
8
- ## 决定
9
-
10
- - 插件是盖在宿主上的一层:宿主没有正式扩展点的地方,效果以 CSS 覆盖与 DOM 覆盖实现,并把这些覆盖依赖的假设写进契约模块(D44)。
11
- - 扩展点不由本仓库推动:宿主不接受外部 issue 与 pull request,等宿主自己开放。等待期间的做法:
12
- - 缺扩展点的几处覆盖(跟随策略、提交生命周期、吉祥物插槽、插件资产与动态分块、官方 UI 的稳定 `data-*`)各写一份文档留在 `.debug/upstream/`,写清现在靠什么细节撑着、坏了会怎样、想要的入口是什么;草稿是内部记录,不对外提交,也不进版本管理。
13
- - 宿主放出对应能力时,该处覆盖改为接入宿主,并从契约模块里删掉被取代的假设(D44)。
14
- - 宿主半边只做浏览器做不到的事:提供模型文案、字体、吉祥物帧图与其他资源、系统用户名、HDSL 契约、用量汇总、会话内容搜索、删除会话这些路由(D11、D22),以及设置表单用的 `Config`(D10)。
15
-
16
- ## 理由
17
-
18
- - 覆盖层的正确性取决于宿主没有承诺过的内部细节;今天能做到的效果,宿主改一次版就可能失效,最有效的稳定手段是宿主自己承诺,而这件事不由本仓库决定。
19
- - 把每处覆盖的代价写清楚,宿主开放能力时可以立刻对照着改,不必重新推导一遍。
20
-
21
- ## 代价
22
-
23
- - 覆盖与真接入的差别只能靠契约测试(D44)与端到端通道(D45)兜住;宿主升级要跑一遍才知道哪条假设失效了。
24
-
25
- ## 重审条件
26
-
27
- - 宿主开始接受外部 issue 与 PR,或开放完整的主题 API 时,恢复「向上游提交扩展点」这条路。
@@ -1,36 +0,0 @@
1
- # D44. 宿主契约模块与契约测试
2
-
3
- - **状态**:已实施
4
- - **分组**:宿主边界
5
- - **关联**:承接 D3、D19 与 D32 的聊天区契约表;D42、D43、D45
6
- - **现状**:契约模块是 `packages/contracts/src/`:`dom.ts` 是皮肤依赖的宿主选择器与属性的唯一定义处(66 条),`table.ts` 是每条 id、它是什么、谁在读、核对过的宿主构建与核对方式,`timing.ts` 是宿主与皮肤之间的时序假设(每条写明依赖的顺序、坏了会怎样、由哪个检查持有),`services.ts` 是宿主服务与值的形状(服务、会话、聊天快照与时间线、轮次与步骤、投影、目录、清单、账号与流,只声明皮肤真正读到的成员),`usage.ts` 是插件两半自己交换的用量与搜索载荷。`core/host.ts` 的访问器按这些形状返回,各功能按名读取的值(`ctx.get(name)` 与面板的 `service(name)`)仍由调用方指名形状,全仓库剩下的 `HostValue` 只有这三处定义。功能清单用 `contracts` 字段登记自己读过哪些条目,构建检查三者一致。端到端通道的 `contract` 场景走一遍空页、提交、流式、落定、菜单与暗色,逐条核对表里的条目(D45)
7
-
8
- ## 决定
9
-
10
- - 皮肤依赖的宿主选择器、属性、服务形状与时序假设集中成一个有类型的契约模块,每条注明在哪个宿主版本上验证过、哪些功能依赖它(功能清单里列出,D42)。
11
- - 契约测试用锁定版本的真实宿主客户端包挂载真实组件来跑,逐条核对契约模块里的假设,取代手搭的替代页面。
12
- - 宿主升级时先跑契约测试,失效的假设逐条列出,再改对应功能。
13
- - 时序假设同样写成契约并测试,例如「宿主在挂上消息列时新建 ResizeObserver、在其中把位置写到最底」(D32)。
14
-
15
- ## 实施
16
-
17
- - `packages/contracts/src/dom.ts` 只放字面:皮肤读的每个宿主选择器与属性在这里定义一次。
18
- - `packages/contracts/src/table.ts` 是同一份契约的表:`id`、`value`(引用上面的字面)、`probe`(在哪个页面状态、按哪种方式核对)、`use`(它是什么、谁在读),表的头里写着这些条目核对过的宿主构建。表是构建与测试数据,不进产物;浏览器半边只带字面本身。
19
- - `packages/contracts/src/timing.ts` 是时序那一半:`assumption`(宿主的工作顺序)、`use`(皮肤拿它做什么、坏了会怎样)、`checks`(`scenario:<通道场景>` 或 `test:<测试文件>`)。构建核对场景名在通道里存在、测试文件存在,通道启动时反过来核对它的场景名与表里一致。
20
- - `packages/contracts/src/services.ts` 是服务那一半:宿主服务(`sessions`、`uiSession`、`uiConversation`、`locale`)与它们的值(会话、聊天快照、轮次、步骤、投影面)各一个接口,只声明皮肤读到的成员,每个成员注明读来做什么;宿主不挂载的服务由 `ctx.get` 返回 `undefined`,因此类型上可能缺席,读取处照旧按「可能没有」写(D12)。`core/host.ts` 的访问器按这些接口返回,各功能内部沿用的 `HostValue` 是尚未收进来的那些值。
21
- - 功能清单的 `contracts` 字段登记该功能读过的条目 id;构建检查:`dom.ts` 的每个字面在表里有条目、表里的 id 不重复、清单里的 id 都存在、每条要么被至少一个清单认领、要么标着 `owner: 'core'`(只由插件的核心与共享模块读的少数几条)。
22
- - 迁移分步:聊天区、外壳、核心与各功能的选择器都已收进模块,时序假设已成表,服务与值的形状已有接口,各功能的读取也已逐处换成具名形状;按名查找服务的三处保留 `HostValue`,形状由调用方指名。
23
-
24
- ## 理由
25
-
26
- - 替代页面只能复现作者已经知道的结构,复现不了真实宿主的时序;聊天区跟随与宿主的争抢在替代页面上一直是绿的。
27
- - 契约集中之后,宿主升级的影响面一眼可见。
28
-
29
- ## 代价
30
-
31
- - 契约测试要能装上并挂载真实宿主包,环境比替代页面重;锁定的宿主版本要随支持范围更新。
32
- - 加一条宿主选择器要同时改模块、表与清单三处,构建会盯着,但这三处是手工维护的。
33
-
34
- ## 重审条件
35
-
36
- - 宿主为插件提供官方的契约或测试工具包时改用宿主的。
@@ -1,30 +0,0 @@
1
- # D45. 三层测试与持续集成
2
-
3
- - **状态**:已实施
4
- - **分组**:分发与流程
5
- - **关联**:D40、D44、D46、D47
6
-
7
- ## 决定
8
-
9
- - 单元测试(Vitest,浏览器模式跑在本机 Chrome 上)覆盖纯逻辑:弹簧曲线、气泡形状采样、折行分段、吉祥物状态机、偏好解析、观察总线的分发顺序(D40)、滚动主人的仲裁与接管(D41)。毫秒级,改一行就能跑。
10
- - 契约测试见 D44:宿主选择器与入口逐条对着真实页面核对。
11
- - 端到端测试(Playwright)跑在装配好的宿主上:
12
- - `packages/testing/dsh-web.cjs` 起一个临时实例:`$DSH_HOME` 在 `.debug/` 下,本仓库以链接装进它的 web 配置,启动后交出带令牌的地址;通道自己的补丁走 `--patch` 覆盖层,profile 的用户层留给宿主;`resetState` 清掉会话与侧栏缓存,断言页面的场景因此从空会话列表开始。实例的标准输入是一条只由启动进程持有的管道,`packages/testing/lifeline.cjs` 经 `NODE_OPTIONS` 预加载进宿主,读到管道关闭就结束宿主:启动进程无论正常退出、抛出未捕获的错误、被 Ctrl+C 或 `taskkill /F` 终止,实例都随它结束,不会留在后台。
13
- - `packages/testing/mock-llm.cjs` 是脚本化的模型服务,按 Messages 协议回答宿主路由,脚本按请求顺序交付;只有模型是脚本化的,agent 循环、会话、客户端各层与渲染都是发行版宿主。
14
- - 端到端通道按职责分成几份:`packages/testing/e2e.cjs` 是运行器,为每个场景起一套实例、发出提示词、在轮次进行中逐帧采样、失败时写下现场;`packages/testing/scenarios.cjs` 是场景表,自带读取逻辑的场景各成一个模块挂进表里;`packages/testing/lane.cjs` 放运行器与场景共用的页面标记与断言形状;`packages/testing/contract-probe.cjs` 放契约表的逐条核对。场景可以给宿主的命令行与浏览器各加参数(`hostArgs`、`browserArgs`)。
15
- - 每个场景各起一套实例,断言各自的东西:`conversation` 读者的消息成行、脚本回答的 Markdown 与思考过程归组;`narrow` 以 390×844 的手机窗口打开,断言输入框下沿那一行放得下权限档位、模型、推理等级与上下文圆环(权限收起下三角、两个选择器按窄屏上限截断、圆环不显示数字、四者都在卡片内且圆环不与旁边重叠);`statsPosition` 用补丁层把「对话统计信息位置」存成「独立成行」,断言这个值经宿主的设置命名空间到达页面、宿主的统计行与计量环在卡片下面排成自己的一行、皮肤自己的数字块没有建起来(`packages/testing/stats-position.cjs`:存档值与宿主设置表单这一段路,替代的宿主页面证明不了);`statsOverlay` 在「收进上下文」那一档下把计量环所在的 dock 与输入行两个盒子逐边对起来,把卡片在那一行下方留的空隙分别改成 0 与 16px 断言 dock 跟着那一行走、不停在写死的 4px 上,再把触发按钮在停放节点里左移 14px 断言行尾留出的宽度按两个盒子的并集算(`packages/testing/stats-overlay.cjs`;两个场景由 `stats-position.cjs` 一次导出);`sidebarRail` 收起侧栏,断言新会话那一格是宿主的 36 像素图标按钮、宿主留给它自己图标的座位不再占这一行的宽度、圆片由按钮自己居中并落在收起按钮与各面板图标同一条中线上,再把座位放回这一行量一次它占住的宽度,让这条中线不是巧合(`packages/testing/sidebar-rail.cjs`);`tool` 工具调用单独成行并带调用编号、结果之后的回答渲染出来;`fileMerge` 让脚本化的模型在一个轮次里改同一个文件四次、另一个文件写一次,断言这个文件在页面上只留一行可见、行尾数字是这四次改动的累计值并随每次改动滚动、其余三行整行离开页面且不占高度、展开代表行读到全部改动,另一个文件的单行不被卷进来(`packages/testing/file-merge.cjs`,D62);`send` 发送起飞期间替身离开页面、真行不可见、读者的字每一帧都看得见、替身带走了输入卡片的字体;`scroll` 逐帧记录滚动位置与内容高度,断言没有一帧在内容没有变短时倒退、没有一帧在内容没有增长时把尾部留在视野之外;`readerScroll` 在轮次进行中自己滚一次滚轮,断言状态行的固定只在跟随自己移动位置时钉住、读者往回翻看时该行随内容走而不浮在内容上,它自己逐帧采样(`packages/testing/reader-scroll.cjs`);`processSummary` 让脚本化的模型先沉默过 10 秒再开口(`packages/testing/mock-llm.cjs` 的 `holdMs`),断言运行中那一行换成皮肤的等待行、计时用宿主的单位、过 10 秒挂出「暂未响应」、模型一开口就撤下,并在轮次结束后按每个过程组标题上的计数核对它收住的东西(`packages/testing/process-summary.cjs`);`reasoningStream` 让脚本化的模型分几步推理半分钟,推理进行中每 250 毫秒读一次窗口,断言窗口跟着文本长高、长到宿主给过程组正文的上限就停住并开始按两行一步往上走,长满之前文本铺满窗口、没有遮罩也没有位移,走到的位置不超过内容末尾、最新一行始终留在窗口里,推理结束后窗口撤下、整段文本原样站着(`packages/testing/reasoning-stream.cjs`);`importance` 走过首页、对话、草稿、流式输出、各个弹层与菜单、附件、暗色与品牌和配色的切换以及每条 `:hover` 规则的悬停,逐条判定皮肤的 `!important` 是否需要,一条多余即失败;设置 `DSH_IMPORTANCE_REFERENCE` 时每个状态还与一张旧样式表逐元素比对(D51);`remoteSettings` 让宿主多信任一个名字 `remote.test`、浏览器把它解析到本机,同一实例分别以 loopback 地址与这个名字打开,在本机页面改主题风格,断言远程页面不刷新就跟上、刷新后读到的仍是宿主的值、它的设置页开头有说明行且控件全部禁用、本机设置页照常可写,结束前把主题风格写回原值,因为设置文档在各场景共用的临时目录里(`packages/testing/remote-settings.cjs`,D60);`shots` 亮暗两套截图。
16
- - 截图沿用写盘前的隐私替换与泄漏扫描(`scripts/shared/privacy.cjs`,与 `scripts/shoot.cjs` 共用),两套明暗各写一张到本次运行的输出目录供人查看;仓库不存比对基线,画面是否变样由用户看过再定。
17
- - 持续集成(`.github/workflows/ci.yml`):构建检查、单元测试与完整冒烟在 Linux 上跑;完整冒烟另在 Windows 上跑一遍,读者的桌面端在 Windows,宿主半边在那里读会话目录、盘符路径与文件时间;端到端的行为场景与截图捕获在 Linux 上跑,宿主版本固定;`contract` 场景另对宿主最新的预发布版(npm 的 `alpha` 标签)跑一遍,宿主改动了皮肤读的契约时先在这里报出,只作预警,不挡合并。每个任务都有时限;工作流用到的 Actions 由 Dependabot 每周提更新(`.github/dependabot.yml`)。
18
- - `.debug/` 里有价值的探针已升级为有维护的工具:逐帧滚动记录并入 `scroll` 场景,发送过程录制并入 `send` 场景的逐帧采样,替身采样并入同一份采样;其余草稿在会话结束时删除。
19
-
20
- ## 理由
21
-
22
- - 冒烟跑在替代宿主页面上,发现得了结构与标记的回归,发现不了真实宿主的时序问题;只靠人工运行的探针又把这类问题留给了下一次手工排查。
23
-
24
- ## 代价
25
-
26
- - 持续集成要装 DSH、pnpm 与 Chrome,端到端部分耗时以分钟计;截图基线随视觉改动刷新。
27
-
28
- ## 重审条件
29
-
30
- - 宿主提供官方的插件测试环境时改用宿主的。
@@ -1,31 +0,0 @@
1
- # D46. monorepo 布局,对外仍是一个插件
2
-
3
- - **状态**:已实施
4
- - **分组**:构建与源码
5
- - **关联**:取代 D18 与 D8;D7、D10、D36、D38、D45、D48
6
- - **现状**:npm workspaces 建起来,五个包各就各位:`packages/client`(浏览器半边:宿主访问、偏好、模型文案、i18n、调度器、观察总线与帧流水线、共用部件、全局外观、各功能目录,单元测试就在旁边,构建打成 `lib/client.js`)、`packages/host`(宿主半边的十二个模块,TypeScript,构建先跑它自己的类型检查,再逐模块把 `@dsh-claude-style/contracts` 的值内联后转译进 `lib/host/`——contracts 不进 npm 包,产物里不留它的 specifier;根包的 `main` 与 `exports` 指向那里)、`packages/contracts`(两半共用的契约:选择器与属性、条目表、时序假设、服务与值的形状、两半交换的载荷、偏好默认值与路由名,按包名引用)、`packages/assets`(每张图片与资源生成器)、`packages/testing`(临时宿主、脚本化模型服务、端到端通道)。构建、清单读取、lint、单元测试与两半的类型检查都按包内路径工作;lint 的停止线覆盖 `packages/` 与 `scripts/` 的全部脚本
7
-
8
- ## 决定
9
-
10
- - 用 npm workspaces 组织:
11
- - `packages/client`:浏览器半边;内部仍按功能分目录,一个功能的脚本与样式放在同一目录、主文件以功能命名,多个功能共用的部件单独成层,不属于任何功能的全局外观单独成层。已搬。
12
- - `packages/host`:宿主半边,同样改用 TypeScript。已搬并转为 TypeScript,类型检查在构建里。
13
- - `packages/contracts`:两半共用的常量、路由名、偏好键与默认值(D10)、宿主契约(D44)。已搬。
14
- - `packages/assets`:资源与清单生成(D38)。已搬。
15
- - `packages/testing`:模拟模型服务、轨迹记录工具(D45)。已搬。
16
- - 搬动分批:一个包一次,每次搬完都过一遍构建、单元测试、相关冒烟与端到端通道;包的路径引用由脚本改,改完读整份差异。
17
- - 对外只发布一个插件(D7)。
18
- - 包内的停止线(文件行数、第 3 份重复)与类名规则由 lint 执行;lint 覆盖之前写在 AGENTS.md 里(D48)。
19
-
20
- ## 理由
21
-
22
- - 两半各写一份常量与偏好声明、构建逐键核对,是因为没有共用的地方;有了 `contracts` 包,这份核对就不需要了。
23
- - 资源、测试工具与两半代码的依赖关系不同,分包后各自的构建与测试边界清楚。
24
-
25
- ## 代价
26
-
27
- - 工作区配置与包之间的引用多一层;全仓库路径一次性变动,搬动与改逻辑分开提交。
28
-
29
- ## 重审条件
30
-
31
- - 出现第二个真实使用者需要单独依赖其中某个包时,把它独立发布。
@@ -1,30 +0,0 @@
1
- # D47. 构建产物不进主分支,发布分支提供 git 安装
2
-
3
- - **状态**:部分实施
4
- - **分组**:构建与源码
5
- - **关联**:D36、D45
6
- - **现状**:`lib/` 已加入 `.gitignore` 并从版本管理里移出;`scripts/dist-branch.mjs` 自己跑一遍构建、把 npm 会发布的同一份文件写成一个孤立的 `dist` 提交,并把版本标签移到该提交上(两条安装路径同一份产物),`.github/workflows/release.yml` 在标签推送后核对标签与版本、跑门、推送该分支、经可信发布上 npm 并建 GitHub Release;整条发布路径已在临时克隆里走过一遍(汇编变更记录、改版本、打标签、写分支、核对产物与标签指向),首次推送与 README 的 GitHub 安装行改指 `#dist` 待这次发版进行
7
-
8
- ## 决定
9
-
10
- - `lib/`(以及 D46 之后各包的构建输出)在 `master` 上加入 `.gitignore`,不再提交。
11
- - 持续集成负责构建;发版时由持续集成构建、打包并发布到 npm,发布的内容与通过测试的构建是同一份。
12
- - 同一次发版,持续集成把与 npm 包内容相同的文件提交到 `dist` 分支,并把版本标签移到那个提交上(发布工作流由标签推送触发,标签先落在发版提交上):按标签安装与按 npm 安装拿到的是同一份已构建的包。`dist` 分支只由持续集成写入。
13
- - 写 `dist` 的那一步自己先构建:`lib/` 不在版本管理里,干净检出没有它,只执行打包会写出一个 `main` 指向缺失文件的包。
14
- - 发布工作流先核对标签与 `package.json` 的版本一致,不一致即失败。重跑时从上次停下的地方接着走:npm 上已有这个版本就不再发布,标签已在 `dist` 的头上就不再写分支。预发布版以 `next` 标签发布,不占 `latest`。
15
- - 上 npm 走可信发布(trusted publishing):工作流用 OIDC 换一次性的发布凭据并附 provenance,仓库不存 npm 令牌。npm 上该包的可信发布者登记本仓库与 `release.yml`。
16
- - 发布后以版本的 CHANGELOG 段为正文建或更新 GitHub Release(`scripts/changelog.mjs --notes <版本>`)。
17
-
18
- ## 理由
19
-
20
- - 被跟踪却只在发版时更新的产物既不是源码,也不能保证是最新的,平时只会以「改过的文件」出现在工作区里。
21
- - DSH 用 pnpm 安装 git 上的插件,带构建脚本的包要读者先批准构建、再下载开发依赖;装一个已经构建好的分支,读者不需要这两步。
22
-
23
- ## 代价
24
-
25
- - 无法再从 `master` 的某个提交直接拿到可用的产物,必须构建,或从 npm、`dist` 分支安装。
26
- - 发版流程多一步推送分支,持续集成需要仓库的写权限与 OIDC 令牌权限。
27
-
28
- ## 重审条件
29
-
30
- - DSH 的插件管理器能在安装 git 上的插件时自动执行构建,或插件市场改为只从 npm 安装时,删除 `dist` 分支。
@@ -1,28 +0,0 @@
1
- # D48. 文档流程
2
-
3
- - **状态**:已实施
4
- - **分组**:分发与流程
5
- - **关联**:D42
6
- - **现状**:决策一文件一条,索引由 `scripts/decisions-index.mjs` 从各文件的标题、状态、分组与取代关系生成,持续集成核对它与文件一致;每个改动在 `docs/changes/` 下放一个结构化的小文件,`scripts/changelog.mjs` 把它汇编成 CHANGELOG 的 `[Unreleased]` 一节,发版时汇编成版本一节并清空该目录,持续集成核对这一节与文件一致;`scripts/lint.mjs` 管三条不需要构建的规则(停止线、Markdown 链接、决策编号引用);功能一览由 `scripts/features.mjs` 从各功能清单的 `description` 字段汇编进 `docs/FEATURES.md` 与 `docs/FEATURES.en.md`,持续集成核对;README 与其余公开文档仍手写;停止线已在 lint 里,注释规范仍只在 AGENTS.md;作用域、`:has()` 位置与令牌门已由构建检查(D51)
7
-
8
- ## 决定
9
-
10
- - 决策一文件一条,文件名 `D<编号>-<英文短名>.md`,模板固定为:标题;状态(已实施 / 待实施 / 部分实施);分组(索引里的分组名);关联(取代了哪些编号也写在这里);可选的现状(待实施与部分实施写明);决定、理由、代价、重审条件四节。编号稳定,代码注释按编号引用;被推翻的编号作废、不复用,新决策追加编号。事故经过与演变过程写在提交说明里,不进决策文件。
11
- - 篇幅:四节都以要点列出,每条讲一件事;「理由」「代价」「重审条件」各不超过三条。「决定」里出现与标题无关的主题时,说明这条决策该拆成两条。
12
- - 索引由脚本从各决策文件的标题、状态、分组与取代关系生成,持续集成核对它与文件一致;索引里的一行由文件自己给出,没有第二处手写。
13
- - 变更记录:每个改动在 `docs/changes/` 下放一个 `<短名>.json`,中英文写在一起,带日期、分组、组内次序与「是否影响外部契约」的标记;`scripts/changelog.mjs` 把它汇编成 CHANGELOG 的 `[Unreleased]` 一节,发版时用 `--release` 汇编成版本一节并把文件清空。CHANGELOG 里这两类小节都不手改,持续集成核对小节与文件一致。
14
- - README:只留预览、安装、字体、皮肤中心交接与许可等手写部分;功能一览与设置说明放在 `docs/FEATURES.md`、`docs/FEATURES.en.md`、`docs/SETTINGS.md` 与 `docs/SETTINGS.en.md`,README 用链接指向它们。功能一览从功能清单(D42)的描述字段生成中英文两条目,夹在标记之间由 `scripts/features.mjs` 汇编。
15
- - AGENTS.md 只留工作流与红线,决策里已有的规则只写编号;能由工具强制的规则写进 lint(`scripts/lint.mjs`)之后,从 AGENTS.md 里删掉。lint 管的是不需要构建就能判定的几条:停止线、Markdown 里的相对链接、注释与文档引用的决策编号;已经由构建检查(D44、D51)或冒烟用例盯住的规则不再进 lint。
16
-
17
- ## 理由
18
-
19
- - 一个长文件里的长段落,读的人要花很多力气才找到要的那一条;每条一个文件、固定模板,改动与引用都更准确。
20
- - 中英文与 CHANGELOG 手工同步,容易漏、也容易和并行的会话冲突。
21
-
22
- ## 代价
23
-
24
- - 生成的文件不能手改;脚本的格式要和现有 CHANGELOG 的格式完全一致。
25
-
26
- ## 重审条件
27
-
28
- - 无。
@@ -1,30 +0,0 @@
1
- # D49. 让路:皮肤占用页面时收掉整套视觉,留下设置页
2
-
3
- - **状态**:已实施
4
- - **分组**:功能
5
- - **关联**:D12、D29、D30、D32、D33、D39、D40
6
-
7
- ## 决定
8
-
9
- - `html[data-dsh-skin]` 回答「这一页现在归谁」:皮肤中心在皮肤作画时盖上它(值是皮肤 id),它在即让路。`packages/client/src/shared/visual-owner.ts` 读它,`apply()` 开头同步读一次,之后无论启动时是否让路,都由观察总线上的一个订阅(D40)跟随真实翻转;判定每次重算,不跨翻转缓存。
10
- - 壁纸插件不是属主:壁纸画在外壳背后,本主题与它共存,配色选「跟随宿主」时弹层带上它的玻璃效果(D30);配色留 Claude 时外壳画布不画、整窗铺一层画布色遮罩(D61)。
11
- - 让路时:保留 `setHostContext`、`adoptSettingsForm` 与 `ctx.inject(['configForms'])`、`loadModelCopy` / `loadUsername` / `loadHdsl`、`adoptPrefs`,只安装 `settings` 一个功能;不盖 `data-dsh-claude-style`、不挂样式表、不跑调度器。让路期间 `adoptPrefs` 只更新偏好值、不往 body 写属性:表单随时可能应答,设置页的块也可能在让路之后才到(D39)。模块作用域的 `parkForeignSheets()` 照常运行(兄弟包的样式表不能等本皮肤)。
12
- - 抢回页面时(`own()`):盖上 `data-dsh-claude-style` 与 `data-dsh-claude-style-handoff`、重新 `adoptPrefs`、挂样式表、装齐功能、装调度器;已在装的功能不重复安装。交还页面时(`release(keep)`):跑完每个功能的 teardown、撤掉本包在 body 上的全部属性与样式表,`keep` 名单里的功能留下。
13
- - 宿主运行时收到 teardown 仍是唯一的一次性拆卸器:`teardown()` 先停归属观察,再 `release()`,再停对话插件观察与偏好绑定。
14
- - `data-dsh-claude-style-handoff` 是能力标记:它在 `body` 上与存活标记同时出现,表示这一版能让路。皮肤中心据此才把本主题当作可选皮肤列出来;没有它的旧版本不会被选用。
15
-
16
- ## 理由
17
-
18
- - 皮肤中心把它注入服务端 HTML,所以第一帧就有答案;而 D32 已经用同样的「信号 + 观察者」形状处理过另一个插件,重复发明一套形状不如沿用。
19
- - 让路的粒度是整页而不是单个功能:只关掉功能表会漏掉样式表、无开关的功能、`adoptPrefs` 打在 body 上的属性,以及独立安装的调度器与座位键(D12 的隔离不等于整页交还)。
20
- - 收掉视觉不是卸载:读者的设置页与偏好必须继续在,别人的皮肤不该顺手删掉本包的东西。
21
-
22
- ## 代价
23
-
24
- - 让路期间整页惰性:设置页以宿主样式渲染,页面上的其他本包行为全部停摆。
25
- - 翻转会带来一次可见的重新挂载;判定在订阅回调里重算,不缓存。
26
-
27
- ## 重审条件
28
-
29
- - 宿主提供主题层接口时改接宿主的(D30 已带着这一条)。
30
- - 皮肤中心提供可注入的主题服务时,判定改走服务,属性只留作第一帧的先验。
@@ -1,29 +0,0 @@
1
- # D5. 模型文案是数据,不进产物
2
-
3
- - **状态**:已实施
4
- - **分组**:构建与源码
5
-
6
- ## 决定
7
-
8
- - `packages/client/data/model-descriptions.json` 构建时按 `packages/client/data/model-descriptions.schema.json` 校验后复制到 `lib/`,浏览器第一次绘制模型选择器时经宿主路由读取。
9
- - 查找顺序:家族规则 → 档位规则 → 目录自带文本。
10
- - 文档里没有按模型 id 登记的条目:模型 id 到句子的映射只有一张有序的模式规则表,别名与拼写变体由规则本身容忍(`[._-]?`、`(?!\d)`)。
11
- - 文案属于产品线,不属于某个版本:同一产品线按名字模式映射到同一句,版本迭代与退场都不改。
12
- - 需要写版本数字或最高级的文案,模式锚定到那个版本(`glm[._-]?5[._-]?3`),同一条产品线在它下面另有一条通用规则兜底。
13
- - 不写自造的档位前缀(「旗舰档:」),不重复行里已有的模型名。
14
- - 家族规则有顺序且必须限定范围(`flash` 规则只作用于 deepseek)。
15
- - 同一文档的 `brands` 把模型绑定到厂商组合标;没有规则认领的模型不画组合标。
16
- - 扩充文案表不需要改 JS。
17
-
18
- ## 理由
19
-
20
- - 模型目录变化快,更新文案不应要求改代码发版;写给某个版本的描述最终会挂到别的版本上。
21
- - 按模型 id 逐条登记时,目录改一个拼写、加一个日期后缀或换一个售卖方都要补一行;一张按名字模式认领的表把同一代新增的档位直接落到合适的规则上。
22
-
23
- ## 代价
24
-
25
- - 第一次绘制选择器多一次异步读取;文案规则有学习成本。
26
-
27
- ## 重审条件
28
-
29
- - 宿主的模型目录接口直接提供本地化文案时改用宿主的。
@@ -1,24 +0,0 @@
1
- # D50. 品牌两档:配色切换只靠令牌
2
-
3
- - **状态**:已实施
4
- - **分组**:功能
5
- - **关联**:D24、D30、D51
6
-
7
- ## 决定
8
-
9
- - 品牌偏好有两档:Claude(默认)与 DeepSeek(值为 `deepseek`;早先存下的 `off` 读作 DeepSeek,`anthropic` 读作 Claude)。
10
- - DeepSeek 一档保留宿主自己的品牌区并画成 DeepSeek 蓝;配色为 Claude 时亮暗两套都换成蓝色系。
11
- - 皮肤自己画的 Claude 标(账号行的兜底头像)在 DeepSeek 一档换成宿主的鲸鱼标(`FISH_LOGO_PATH`)。
12
- - 配色切换只靠令牌:DeepSeek 的亮暗两块(多一个品牌属性,优先级更高)整组重写宿主的别名令牌与皮肤的私有令牌,各功能样式表一律读令牌。
13
-
14
- ## 理由
15
-
16
- - 品牌只换色板与标志,功能的行为不变;令牌集中重写,功能样式表不必知道当前是哪个品牌。
17
-
18
- ## 代价
19
-
20
- - 新的私有颜色令牌要同时写进 DeepSeek 的亮暗两块。
21
-
22
- ## 重审条件
23
-
24
- - 新品牌带来行为分歧(不同的 DOM 覆盖、不同的输入框结构)时,品牌不再只是一组令牌,改为独立的主题包。
@@ -1,34 +0,0 @@
1
- # D51. CSS 工具链:语法树上的检查与一份令牌数据
2
-
3
- - **状态**:已实施
4
- - **分组**:构建与源码
5
- - **关联**:取代 D37;D4、D9、D30、D36、D50
6
-
7
- ## 决定
8
-
9
- - 层叠顺序由样式表的排位声明:主题样式表在 `scripts/build.mjs` 的 `THEME_SHEETS`,功能样式表在各自清单里(D42),两张样式表同一排位即构建失败。规则之间的先后由排位与选择器权重决定。不使用层叠层。
10
- - `!important` 只留给真实页面上需要它的地方:压过宿主的 `!important`、内联样式或另一条皮肤规则。端到端通道的 `importance` 场景(`packages/testing/importance.cjs`,D45)在临时宿主走过的页面状态里逐条去掉它、比较匹配元素的计算值,计算值不变的判为多余,场景即失败;没出现过的界面上的那些保留,等审计能走到时再判。规则处在条件不成立的 `@media` / `@supports` 里时,去掉重要性本来就改不动计算值,审计按页面当刻的 `matchMedia` / `CSS.supports` 判条件,条件不成立就不审这条规则。清掉一批多余的重要性时,用 `DSH_IMPORTANCE_REFERENCE=<压缩前的样式表>` 把两份样式表放在同一页上逐元素比对,作为「画出的样子不变」的证据。
11
- - 样式表是普通 CSS,没有构建期占位:属性名与属性值写成字面值,品牌标志的地址读令牌样式表生成的 `--dsh-claude-image-<名字>`(地址由资源清单决定,D38)。构建把每张样式表交给 PostCSS 解析,选择器用 postcss-selector-parser 解析,规则在语法树上检查(`scripts/css.mjs`):
12
- - 作用域:每个选择器都含 `body[data-dsh-claude-style]`,且不在 `:not()` 里;画 `<html>` 的规则经 `html:has(body[data-dsh-claude-style]…)` 进入作用域。
13
- - `:has()` 只在所在选择器的最后一段,`:is()`、`:not()` 里的每一层都一样(D9)。
14
- - 令牌门:写 `--dsw-` 颜色令牌的规则每个选择器都在作用域那一段挂配色门,写 `--dsw-font-*` 的挂字体门;Claude 一档定义的每个私有令牌在宿主一档都有别名(D30)。
15
- - 输入框门控:带门控的样式表在 `/* @composer-gate */` 注释以下的每条规则,门控属性盖到作用域那一段上;缺少标记或标记以下没有规则即构建失败(D4)。
16
- - 皮肤属性:选择器读到的每个 `data-dsh-*` 属性,名字都要出现在浏览器半边的 TypeScript 里(常量的值或字面值);按角色名拼出的吉祥物站位标记登记在 `scripts/css.mjs`。代码里改了名、或样式表拼错,构建即失败。
17
- - 以上检查读源样式表;拼好的整张样式表随后经 esbuild 的 CSS 压缩(`scripts/shared/minify-css.cjs`)进产物,注释与排版只留在源文件里,它们约占整张样式表的四成。esbuild 对样式表报出警告即构建失败。`importance` 场景按规则的文本认出第三方规则,读 `third-party.css` 时走同一个压缩,两边的写法才对得上。
18
- - 设计令牌写在 `packages/client/src/theme/tokens.json`,结构由 `packages/client/src/theme/tokens.schema.json` 声明、构建用 Ajv 校验:字体令牌的 Claude 与宿主两档、推理强度顶档的亮暗两色、颜色令牌在 Claude 品牌亮暗两套里的值、DeepSeek 品牌与之不同的值、宿主一档的别名,以及令牌的用途与取值缘由。构建由它生成令牌样式表(排位最前),并把 `docs/STYLE.md` 里标记之间的令牌表重写一遍;两处都不手改。
19
-
20
- ## 理由
21
-
22
- - 宿主的样式几乎全部不在层叠层里,而层里的规则不论权重都输给层外的规则;皮肤的规则一旦进层,就压不过宿主任何一条。
23
- - 原来的检查按行与字符扫描选择器,换行、注释与嵌套括号都可能让它漏判;语法树上的检查与成熟的解析库没有这类问题。占位只把一部分属性名与常量连起来,其余字面值没有任何核对;写成普通 CSS 再按名字核对,编辑器与检查工具都读得懂,所有属性一视同仁。
24
- - 令牌值、宿主别名与文档里的表各写一遍,改一处就要找另外两处;一份数据生成全部,三者不会走样。
25
-
26
- ## 代价
27
-
28
- - 多了 PostCSS 与 postcss-selector-parser 两个开发依赖。
29
- - 令牌的说明只能写进 JSON 的 `note` 与 `use` 字段,生成的样式表不带注释。
30
- - 页面上的样式表没有注释与排版,压缩还会合并相邻的同值规则、把四边合成 `inset`;在开发者工具里对照源文件要按选择器去找。
31
-
32
- ## 重审条件
33
-
34
- - 宿主把自己的样式放进层叠层,或提供主题层级与令牌分层机制时,改用层叠层并接入宿主的层级。
@@ -1,31 +0,0 @@
1
- # D52. 会话标题与顶栏控件抬进桌面端标题栏,放不下就留在原行
2
-
3
- - **状态**:已实施
4
- - **分组**:功能
5
- - **关联**:D3、D9、D28、D31、D42;承接 D31
6
-
7
- ## 决定
8
-
9
- - Windows 桌面端(`html[data-windows-titlebar]`,且 `desktopBand()` 给出横带)下,会话标题与标题行里的控件可以离开标题行,抬进窗口顶端那一行:标题靠左、控件的两簇(`headerUtilities` 与 `headerCorner`)靠右排在窗口按钮之前。抬升的是宿主自己的元素,因此在原处打标记后用 `position: fixed` 摆放,不移动父子关系;留下的标题行自行收拢,预设选择器等随内容左移(D9:结构判断写成属性,样式只读属性)。
10
- - 两侧分别判定:控件簇靠右,先量它;标题再量自己左侧到「标签条左边 / 控件簇左边」之间的余量。余量不足的一侧不写标记、留在原行 —— 样式表的基础规则就是回退,不需要恢复动作。标题在横带里的宽度上限写进 `--dsh-header-band-max`,超出部分由宿主自己的 crumb 省略号截断。
11
- - 两簇各自判定:右侧栏打开时 `headerCorner` 是空位(那一栏自己的控件接管了它),有簇可抬就抬,缺的那一簇不留空隙,另一簇也不因此失去位置。
12
- - 标题离开后,标题行只剩控件:它按标题自己的盒子向上挪 `ROW_TUCK`(行内文字与标题文字之间约 10px),用 `position: relative` 的偏移而不是外边距——外边距会改变行在宿主网格里的高度,偏移不动头部高度,也不给 fixed 子元素造新的包含块。行本身声明 `-webkit-app-region: no-drag`,它的上沿此时落在桌面壳的拖动区里。
13
- - 宿主用挂在标题行上的容器查询(行宽 540px 以下时隐藏芯片文字)保护标题与控件共享的那一行;标题进入横带后那一行只剩控件、宽度够用,因此在这个模式下把行内被藏起的标签重新显示,控件不会凭空消失。
14
- - `<body>` 上的 `data-dsh-header-band` 是模式标记,取值为 `title`、`actions` 或两者;四个元素标记(`data-dsh-header-title`、`data-dsh-header-row`、`data-dsh-header-actions`、`data-dsh-header-corner`)与标签条上的 `data-dsh-header-band-tabs` 让样式表不写宿主的类名。
15
- - 横带里的落位全部来自测量:窗口按钮的预留宽度与横带高度取自 `desktopBand()`(D28),行的左端取自标题行本身,标签条的宽度取自它自己的盒子(view-tabs 把它按窗口居中摆放,本功能随后再移动它,因此余量判定读宽度、不读移动后的位置),桌面壳自己那一格(`[data-windows-menu]`)占住的左端让出来;收起侧栏是列宽动画、没有 DOM 变化,因此订阅标题行的尺寸,变化时在同一帧重排。
16
- - 标签条本身由 view-tabs 摆放:它在横带里取两者的中线——左侧是标题的右沿(标题留在原行时是桌面壳的左端),右侧是控件簇的左沿(控件留在原行时是窗口按钮的预留边)——写进 `--dsh-header-band-tabs-left`,样式表把 `left` 从窗口中心改读这个值,标签条自己的 `translate(-50%, -50%)` 保持居中。
17
- - 横带是桌面壳的拖动区,抬上去的每一块都声明 `-webkit-app-region: no-drag`。macOS 不动:那边的外壳不给内容留上下内边距,标题行本来就画在红绿灯那一条里。
18
-
19
- ## 理由
20
-
21
- - Claude Code 桌面端把窗口最上面一行留给标题与全局控件,会话级的内容压在下面;桌面端顶栏已经透明(D31),把标题抬上去才有那一行的样子。
22
- - 量着放、放不下就留在原行:窗口宽度、侧栏开合、有没有运行中的后台任务都会改变这一行里有多少东西,固定偏移在某一种窗口下必然压到邻居(同一课 view-tabs 已经上过)。
23
-
24
- ## 代价
25
-
26
- - 标题与控件簇因此在两种位置上出现,截图与视觉评审要覆盖桌面端窄窗与非 Windows 页面这两处。
27
- - 控件簇的悬停卡与菜单按触发元素的矩形定位;抬升后矩形就是新位置的,宿主按打开时的测量摆放,不受影响,但这一条依赖宿主在打开时测量而不是缓存。
28
-
29
- ## 重审条件
30
-
31
- - 宿主给标题行或顶栏提供插槽(把会话标题与操作区交给插件摆放)时改用那个,删掉这里的标记与测量。
@@ -1,33 +0,0 @@
1
- # D53. 用量两个来源按日期合并
2
-
3
- - **状态**:已实施
4
- - **分组**:运行时
5
- - **关联**:D11、D12
6
- - **现状**:本条改写「账本只在它覆盖最新会话活动时被采用」的旧规则。此前只要账本的最新一天早于最新一条会话活动(成本计量插件停写就会出现),整份账本被丢弃,面板改用本地折算,账本里那些日志已清掉的日子随之从面板上消失。
7
-
8
- ## 决定
9
-
10
- - 成本计量插件的账本与本地折算(`usage.ts`、`usage-fold.ts`)是同一批日子的两本账,按日期合并,绝不按金额相加:账本认识的一天就是账本的一天,只有账本最新一天之后的日期来自折算。
11
- - `totals` 按合并后的日图重算,`sessions` 取两边的并集(账本每天的会话 id,加上最后写入落在未覆盖日期的会话日志 id),`activeDays` 是合并后的天数。
12
- - 路由先把账本自己算出的结果交出去(`publish`),折算完成后换成合并结果,所以账本再旧也能立刻显示它覆盖的全部历史。
13
- - `source` 在折算补进过日期时写 `cost-meter+local`,完全没有账本或账本补不上时写 `local`;两个值都不改变面板的取数路径。
14
- - 折到账本已覆盖日期的会话照旧留在折算缓存里,读它们的账本日不与之相加。
15
- - 账本不存在、解析不了或版本不认时,整条路径与从前一致:只用本地折算(D12)。
16
- - 一个会话的日志经宿主的观察接口读(`sessionQuery.observeSession`,见 `session-events.ts`):它只按 id 观察这一份存档,不先列整份存档,所以读 N 个会话是 N 次观察、零次列档;宿主只发布 `readSession` 时退回按 id 读,那一版每读一个会话会先列一次整份存档。
17
-
18
- ## 理由
19
-
20
- - 两本账说的是同一批日子:相加等于同一批结算被计两次,丢弃等于拿日志还在的一部分去顶替全部。
21
- - 账本比会话日志活得久(日志清掉的日子仍留在账本里),合并后总量对「全部」与两个窗口都自洽。
22
- - 折算只补缺口的那一天,账本停写不再让历史一起消失。
23
-
24
- ## 代价
25
-
26
- - 折算仍要遍历全部会话日志,成本由缓存摊平(只有变更过的日志重读);一趟里每个变更过的日志各观察一次,观察按存档修订号复用已还原的会话。
27
- - 一个会话的最后写入落在未覆盖日期、而它也在账本覆盖的日子里结算过时,会话数会重复计一次;账本的会话 id 与会话目录名不同格式,无法相减。
28
- - 「高峰时段」只由折算补上的那几天决定:账本没有小时维度,它覆盖的日子在被折到之前没有直方图,窗口的峰值可能因此偏低。
29
-
30
- ## 重审条件
31
-
32
- - 宿主发布跨会话的用量总量(不必再读别的插件的文件)时,改为只读宿主。
33
- - 成本计量插件不再维护账本格式时,删掉这条路径。