设计说明(DESIGN)

August 19, 2026 · View on GitHub

本文记录 dsh-chat-width 的关键实现决策,供维护者与贡献者参考。

为什么覆盖 CSS 变量而不是改组件

dsh 的会话区布局由 --dsh-chat-content-width 控制(定义在会话根元素 .wSkVaW_root 上,默认 748px),文字列、输入框、底部工具栏的宽度都由它派生:

.wSkVaW_root { --dsh-chat-content-width: 748px; ... }
.flow { width: 100%; max-width: var(--dsh-chat-content-width); margin: 0 auto; }
.composer { max-width: calc(var(--dsh-chat-content-width) + 32px); }

因此只要覆盖这一个变量,整条会话链路的宽度同步变化,无需改动任何 dsh 内部组件,也无需重新构建前端。

选择器策略:类名哈希 + 属性兜底

  • .wSkVaW_root.wSkVaW_root:双类把特异性提到 (0,2,0),压过 dsh 自带定义,与样式注入顺序无关。
  • [data-phase][data-phase]:版本无关兜底。dsh 未来若改了类名哈希,会话根元素(带 data-phase 属性)仍会被命中;双属性同样保证 (0,2,0)。

两个选择器都只匹配会话根元素,不会误伤其它 UI。

拖拽把手的定位:CSS 变量驱动,零 JS 定位

把手绝对定位锚定在文字列右缘:

.dsh-cw-handle {
  right: calc((100% - var(--dsh-chat-content-width)) / 2 - 7px);
}

文字列居中(margin: 0 auto),右缘到容器右缘的距离恰为 `(100% - 宽度)/2$。宽度一变,把手位置自动跟上,不需要监听 \text{scroll}/\text{resize} 或逐帧计算。

跟手映射:为什么是 2 \times

文字列居中布局下,宽度变化 \text{dx} 时左右边缘各移动 \text{dx}/2。若宽度按指针位移 1 \times 变化,锚定在右缘的把手只走指针的一半距离,手感滞后。

因此拖拽时宽度按指针位移的 **2 \times ** 变化:

$``js setWidth(startWidth + 2 * (e.clientX - startX))


这样右缘(把手)与指针 1:1,列始终居中,松手无回弹。代价是宽度数值变化比指针快一倍——这是居中布局下"边缘跟手"的必然取舍。

## 生命周期

- 会话根元素在 boot 后才挂载(hero 阶段),插件用 `MutationObserver` 观察 `[data-phase]` 根元素的出现/切换,把把手挂到当前根元素上;换会话自动迁移。
- 所有宽度选择写入 localStorage(键 `dsh_chat_width`),刷新/换会话后仍生效。
- 插件卸载时移除样式与把手,不残留任何全局副作用(`__setChatWidth` 一并删除)。