checkpoint: dsh-worktable v2 稳定版(PRD §10 全部定案并实现)

This commit is contained in:
Aisland
2026-08-16 23:34:22 +08:00
commit d58cbc583b
20 changed files with 2073 additions and 0 deletions
+232
View File
@@ -0,0 +1,232 @@
# dsh-worktable(工作台)PRD
> 版本:v0.2 草案 · 日期:2026-08-16 · 状态:v1 原型已上线;§10 待设计内容已定案并实现(v2,代码完成、待重启后 GUI 验收)
> 关联项目:dsh-travelatlas(第一个入驻项目)、上游参考 dsh-reminder(文件夹结构)
## 1. 项目定位
**dsh-worktable 是 DeepSeek Harness Web GUI 的一个「工作台」容器插件**:在左侧侧边栏的「工作区」
(官方会话/工作区浏览区)下方划出一条分隔线,其下开辟「工作台」区块,用于收纳**不同于 DSH 默认模式的
agent 级项目**(如旅行图鉴 TravelAtlas),让用户可以像管理应用抽屉一样管理自己安装的项目。
- 一句话:**侧边栏里的「应用抽屉」,agent 级项目的家。**
- 与官方关系:纯增量插件,不替换、不禁用任何官方插件(与 dsh-plugin-ya-workspace-sidebar 的替换路线相反)。
## 2. 背景与问题
- DSH 的侧边栏只有「工作区」(会话浏览)与「设置」两层,没有承载用户自装项目的位置;
- 现有项目(如 dsh-travelatlas)只能挤在 `sidebar.footer.action` 底部,各自为政、无统一入口与元信息;
- 用户希望有一个与「工作区」对等的「工作台」区域,统一收纳、搜索、整理自己的 agent 级项目。
## 3. 目标 / 非目标
### 目标(v1 原型,本窗口已完成)
- [x] 侧边栏底部(会话列表下方、设置行上方)渲染「工作台」区块:分隔线 + 标题 + 三按钮 + 项目列表;
- [x] 标题左侧 ≡ 拖动手柄:按住上下拖动整个区块,松手停靠(浮动位置持久化);
- [x] 三按钮照抄官方「工作区」头部:搜索(展开输入框过滤项目)、视图选项(分组/排序)、添加(占位符);
- [x] 项目注册协议:子座位 `sidebar.worktable.project`,任何插件注册即可入驻;
- [x] dsh-travelatlas 迁入成为第一个项目(含工作台缺席时的降级回退)。
### 目标(v2,§10 定案,本窗口已完成)
- [x] 视图选项简化:取消分组方式,只留排序(手动/最近),旧 groupBy 状态忽略;
- [x] 卡片规范 v2 渐进上报协议(owner props 扩展,全部可选,v1 卡片零改动兼容);
- [x] 「管理项目…」编辑模式:显示名改名 / 隐藏 / 手动排序(拖拽 + ↑↓),持久化;
- [x] 添加(+) 真实逻辑:接入指引面板 + 本地快捷方式条目(新标签打开);
- [x] 使用埋点:卡片点击上报,「最近」排序生效;
- [x] 完整 zh/en 词典接入 dsh-client-locale(NS worktable);
- [x] dsh-travelatlas 卡片升级协议 v2(报到/埋点/排序/隐藏/改名)。
### 非目标(明确不做)
- 不替换 ui-sidebar / ui-workspace / 官方任何组件;
- 不做「工作台自身的独立路由主页」——管理能力并入区块内编辑模式(见 §5.5);
- 不做项目市场/安装器(生态里已有 dsh-plugin-hub / dshfind;工作台仅提供外链)。
## 4. 用户故事
- 作为用户,我想在侧边栏一个固定的地方看到我装的 agent 级项目,而不是散落各处;
- 作为用户,我想拖动工作台区块到侧边栏里更顺手的高度,并且下次打开还记得;
- 作为用户,我想像搜索会话一样搜索我的项目;
- 作为插件作者,我想用几行代码让我的项目入驻工作台(拿到卡片 + 打开逻辑)。
## 5. 功能规格
### 5.1 区块结构(自上而下)
```
══════════ 分隔线 ══════════
[≡] 工作台 [🔍][视图选项][+]
┌───────────────────────────┐
│ 项目卡片(0..n,来自子座位) │
└───────────────────────────┘
```
- 标题文案:`工作台`(locale 键 `worktable.title`,en: `Worktable`);
- 底部悬浮面板避让:停靠态下检测侧边栏列内贴底的 fixed 面板(如 dsh-usage 余额 dock),
与区块重叠时以 margin-bottom 整体让位到面板上方,双方互不遮挡、各自可拖动;停靠期间 2s 轮询跟随面板移动。
- ≡ 手柄:`pointerdown` 捕获,垂直拖动 >6px 进入浮动模式(position:fixed 跟随指针,限制在侧边栏列宽内、
顶部不低于品牌行下沿、底部不超出设置行上沿);松手:与默认停靠位(footer 原位)距离 <32px 则回弹停靠,
否则保持浮动位置;持久化键 `dsh.worktable.view.v1`(字段 `query/searchOpen/orderBy/dock/floatTop`,旧版 groupBy 字段忽略);
双击 ≡ 复位到默认停靠;标题与 ≡ 均可作为拖拽手柄;浮动上限按区块实际高度计算(停靠位紧邻其下);
松手时按指针落点判定——越出有效落点区(底部/顶部/侧边余量 24/24/80px)即回归拖前位置;
- 悬浮窗几何与 sidebar 联动(只宽度/水平定位,高度由拖拽决定):向上遍历父链识别 sidebar
(className 含 SidebarRoot/sidebar,或 aside/nav,到 body 为止);ResizeObserver 实时跟随;
dockWidth = sidebar 宽 − paddingLeft − paddingRight − 40px(每边内缩 20px);
left = sidebar 左边缘视口坐标 + paddingLeft + 20px;找不到 sidebar 或宽度 ≤0 时降级
left 固定 14px、宽度不设内联(交 CSS min-width:176px / max-width:264px);
侧边栏折叠/展开保持原停靠位置——折叠态以「项目图标框」(收纳所有项目 emoji)显示在拖前高度,
展开即复原;图标框在折叠动画结束后(320/750ms 双次重测)按收敛后的折叠列几何水平居中;
仅窗口尺寸变化时回弹 footer 停靠。
### 5.2 三按钮(照抄官方工作区头部,逻辑作用于项目列表)
| 按钮 | 图标(primitives) | 行为 |
| --- | --- | --- |
| 搜索 | 🔍 | 点击展开输入行(Esc / 点 ✕ 收起);输入即过滤项目卡片与快捷方式(query 经座位 owner props 传给每个卡片,卡片自行判断是否隐藏) |
| 视图选项 | ☰ | 排序方式(手动/最近),选择即持久化到 `dsh.worktable.view.v1`;另含「管理项目…」入口(§5.5)。分组方式已按用户定案取消 |
| 添加 | + | 展开「添加项目」面板:接入指引(注册即入驻说明 + 插件市场外链)+ 本地快捷方式表单(§5.6) |
> 官方工作区头部三按钮 = 搜索 / 视图选项(ViewOptionsMenu) / 添加工作区(+),已逆向确认。
### 5.3 项目注册协议(子座位,v2 卡片规范)
工作台组件在注册 `sidebar.footer.action` 时声明子座位:
```ts
ctx.slots.register({
name: 'sidebar.footer.action',
id: 'dsh-worktable',
order: 20,
children: { 'sidebar.worktable.project': { kind: 'list', scope: 'root', owner: ProjectOwnerProps } },
}, WorktableSection)
```
卡片注册约定:`id` 为项目唯一 id(如 `travelatlas`),`order` 为默认排序(注册序)。
**owner props v2(渐进上报协议,全部可选)**:
| 字段 | 类型 | 含义与卡片行为 |
| --- | --- | --- |
| `query` | string | 当前搜索词;卡片自行判断是否返回 null |
| `wide` | boolean | 侧边栏是否展开 |
| `order` | string[] | 当前排序下的 id 序列;卡片用 `style={{ order: indexOf(自身id) + 1000 }}` 参与排序(+1000 偏移保证未上报的 v1 卡片在前) |
| `hidden` | string[] | 被隐藏的 id 集;包含自身 id 时返回 null |
| `nameOverrides` | Record<string,string> | 显示名覆盖表;卡片优先显示覆盖名(编辑模式改名) |
| `managing` | boolean | 编辑模式标记(卡片可据此减弱交互) |
| `reportMeta(meta)` | 回调 | mount 时上报 `{ id, name, icon }`,供管理条渲染;回调引用稳定 |
| `reportUsed(id)` | 回调 | 点击时上报使用时间戳(「最近」排序埋点) |
兼容性:未上报元信息的 v1 卡片零改动照常显示(按注册序排在最前),只是不参与排序/隐藏/改名。
机制依据:列表座位渲染器输出 `display:contents` 锚点且错误边界不产生 DOM 包裹,卡片根节点即
`.dsh-wt_projects` 的直接 flex 子项,CSS order 生效(已核实 web-react 渲染器实现)。
参考实现:dsh-travelatlas `src/client/index.tsx` 的 `WorktableCard`。
### 5.4 降级回退协议(对项目插件)
项目插件应实现:先 `ctx.slots.inject('sidebar.worktable.project', ...)` 注册工作台卡片;若工作台插件未安装
(座位永不出现),超时(~2.5s)后回退到 `sidebar.footer.action` 注册独立入口。参考 dsh-travelatlas
`src/client/index.tsx` 的实现。
### 5.5 管理项目(编辑模式)
- 入口:视图选项菜单「管理项目…」;「完成」退出(编辑状态不持久化)。
- 管理条逐项目列出(含未上报元信息的卡片,名称回退为注册 id):≡ 拖拽排序(HTML5 drag)+ ↑↓ 按钮 +
改名输入框 + 隐藏/显示切换(🙈/👁,隐藏后卡片区消失、管理条内可恢复)。
- 快捷方式条目在管理条中显示并可删除(✕);「恢复默认」清空排序/隐藏/改名覆盖(保留 lastUsed 与快捷方式)。
- 编辑模式下项目卡片区整体弱化(opacity + pointer-events:none)。
- 所有变更写入 `dsh.worktable.projects.v1`(见 §6)。
### 5.6 本地快捷方式(添加面板)
- 「+」展开添加面板:接入指引(项目=插件、注册即入驻,协议见 §5.3)+ 插件市场外链
(https://github.com/hikariming/dshfind,已核实可访问;dshfind.com 未验证、不链死链)。
- 快捷方式表单:名称 + 图标(emoji,可选,默认 🔗)+ 链接(校验 http/https);提交后立即出现在
项目卡片区下方,标「本地」角标;点击新标签打开(noopener)。
- 快捷方式参与搜索过滤;只存 localStorage,无任何网络请求。
### 5.7 国际化
- 词典:`01_content/src/client/locales.ts`,NS `worktable`,zh 为键集唯一来源,en 全量对齐。
- 接入方式(照 dsh-reminder):client inject `['slots','locale']`,apply 中 `ctx.locale.register(NS, { zh, en })`;
package.json `dsh.client.inject` 声明 `@deepseek-ai/dsh-client-locale`(peerDependencies 可选)。
- 宿主 locale 服务缺席时 t 回退 zh 词典,保证工作台独立可用。
## 6. 架构与技术方案
- 插件包:`01_content/`(dsh.plugin.json + cordis.patch.yml + build.mjs,参照 dsh-travelatlas / dsh-usage);
- 服务端:最小 cordis 插件,注册 `GET /api/worktable/health`(`inject: ['webServer']`),无业务路由;
- 客户端:单文件 CJS(`window.__ModuleLoader__.load` 握手),external react / @deepseek-ai/*;
- 注入座位:`sidebar.footer.action`(order 20,位于 dsh-usage 之后);
- 服务注入:`['slots','locale']`(locale 词典见 §5.7;宿主缺席时回退 zh 词典);
- 图标为 emoji 字符(🔍/☰/+/≡);
- 排序机制:owner props 下发 order 序列,卡片以 CSS order 参与排序(渲染器 display:contents 锚点已核实,见 §5.3);
- 子座位注册跟踪:apply 中 `ctx.slots.subscribe` + `entries()` 维护模块级 id 序列;
- 持久化:
- `dsh.worktable.view.v1`:query/searchOpen/orderBy/dock/floatTop(旧 groupBy 忽略);
- `dsh.worktable.projects.v1`:order/lastUsed/hidden/nameOverrides/shortcuts;
- 卡片上报的 meta 注册表仅存内存,不持久化;
- 样式:暗色优先,跟随 `--dsw-alias-*` 设计变量(与 dsh-usage / dsh-travelatlas 一致)。
## 7. 与 dsh-travelatlas 的关系
- travelatlas 是「第一个入驻项目」,不是工作台的一部分;
- travelatlas 客户端(2026-08-16 重写):图鉴视图 = 官方 `conversation.view` 会话头标签页(iframe 到
/travelatlas/site/),工作台卡片与降级入口点击时程序化切到该标签页;工作台缺席时回退 `sidebar.footer.action` 独立入口;
- 卡片协议 v2 已接入(2026-08-16 与并行重写冲突后被覆盖,已重新应用并构建);
- 项目图标 🌏(地球·亚洲)为 travelatlas 官方 emoji(2026-08-16 定案);工作台折叠态图标框
按各项目上报的自身 emoji 展示(协议 §5.3 reportMeta.icon)。
## 8. 隐私与安全
- 无个人数据采集;所有状态仅存 localStorage;
- 搜索仅在本机过滤项目名;
- 不读写任何工作区文件、不请求任何网络资源(除插件自身静态资源)。
## 9. 验收清单(v1)
- [ ] 侧边栏底部出现分隔线 + 「工作台」标题 + 三按钮 + 项目卡片(🌍 旅行 Atlas);
- [ ] ≡ 拖动区块上下移动,松手停靠,刷新后位置保持;双击 ≡ 复位;
- [ ] 搜索框展开/收起正常,输入能过滤项目卡片,Esc / ✕ 可关;
- [ ] 视图选项下拉可切换分组/排序并持久化;
- [ ] 添加(+) 点击显示「待定」占位提示;
- [ ] 卸载 dsh-worktable 后,dsh-travelatlas 自动回退为底部独立入口(不白屏);
- [ ] 与 dsh-usage、dsh-reminder、官方侧边栏折叠态共存无异常。
### 验收清单(v2,§10 定案,待重启后 GUI 验证)
- [ ] 视图选项菜单只含排序(手动/最近)+ 管理项目入口,选择持久化;
- [ ] 切「最近」后点击 travelatlas 卡片,卡片置顶;
- [ ] 管理项目:改名/隐藏/拖拽或 ↑↓ 排序生效,刷新保持;「恢复默认」清空排序/隐藏/改名;
- [ ] 隐藏后卡片从卡片区消失,管理条中可恢复显示;
- [ ] 「+」面板:接入指引 + 市场外链打开正常;快捷方式校验生效,添加后新标签打开、搜索可过滤、编辑模式可删除;
- [ ] 语言切 en 时工作台文案跟随(标题/菜单/面板);
- [ ] v1 兼容:未上报卡片仍显示且排在已上报卡片之前;
- [ ] travelatlas 降级回退不受影响(卸载工作台后回退底部入口)。
## 10. 定案记录(v2,本窗口与用户讨论后定案)
> 以下条目原为「待设计内容」,已于 2026-08-16 与用户逐项讨论定案并实现,规格并入 §5/§6。
| # | 议题 | 定案 |
| --- | --- | --- |
| 1 | 工作台自身内容 | 不做独立路由页;☰ 菜单「管理项目…」→ 区块内编辑模式(改名/隐藏/排序),见 §5.5 |
| 2 | 添加(+) 逻辑 | 接入指引面板 + 本地快捷方式条目,见 §5.6 |
| 3 | 卡片规范 v2 | 渐进上报协议(owner props 扩展,全部可选),见 §5.3 |
| 4 | 分类 | **取消分类**(用户定案:每项目占一行、自成工作台,无需分组);视图菜单只留排序 |
| 5 | 国际化 | zh/en 词典接入 dsh-client-locale,见 §5.7 |
已知边界:未上报元信息的 v1 卡片按注册序排在最前、不参与排序/隐藏/改名(协议兼容取舍);
市场外链用 GitHub 仓库 https://github.com/hikariming/dshfind(已核实可访问;dshfind.com 未验证、不链死链)。
## 11. 已实现 vs 待实现(接手分界线)
| 部分 | 状态 | 位置 |
| --- | --- | --- |
| 侧边栏区块 + 三按钮 + ≡ 拖动 | ✅ v1 已实现 | `01_content/src/client/` |
| 项目子座位协议 + travelatlas 入驻 | ✅ v1 已实现 | 同上 + dsh-travelatlas/src/client |
| 服务端健康路由 | ✅ 已实现 | `01_content/src/index.ts` |
| 视图菜单去分组、编辑模式、添加面板、快捷方式、埋点、i18n | ✅ 本窗口(v2)已实现并构建,待重启后 GUI 验收 | `01_content/src/client/`(§5.3–§5.7) |
| travelatlas 卡片协议 v2 | ✅ 本窗口已实现并构建(并行重写覆盖后已重新应用) | `dsh-travelatlas/src/client/index.tsx` |
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
@@ -0,0 +1,156 @@
# 2026-08-16 dsh-worktable v2:PRD §10 定案并实现
> 日期 / 主题 / 状态:2026-08-16 · 工作台 v2(管理/添加/卡片规范 v2/排序埋点/i18n) · 代码完成,待重启后 GUI 验收
## 任务目标
按 PRD §10 与用户逐项讨论定案后实现:①管理视图(编辑模式)②添加(+) 真实逻辑 ③卡片规范 v2
(渐进上报协议)④分类(取消)⑤i18n 接入 dsh-client-locale;并升级 dsh-travelatlas 卡片协议。
## 定案记录(与用户问答确认)
1. **管理功能**:☰ 菜单「管理项目…」→ 区块内编辑模式(改名/隐藏/拖拽+↑↓排序),全存 localStorage;不做独立路由页;不做真正插件启停(宿主职责)。
2. **添加(+)**:接入指引面板(注册即入驻说明 + 市场外链)+ 本地快捷方式条目(名称/图标/链接,点击新标签打开,标「本地」角标)。
3. **卡片规范 v2**:owner props 扩展(query/wide/order/hidden/nameOverrides/managing/reportMeta/reportUsed),全部可选;v1 卡片零改动兼容(按注册序排最前)。
4. **分类**:取消(用户定案:每项目占一行、自成工作台,无需分组);视图菜单只留「排序:手动/最近」;旧 groupBy 状态忽略。
5. **i18n**:NS worktable zh/en 词典,ctx.locale.register + package.json 声明 dsh-client-locale;宿主缺席回退 zh。
6. **市场外链**:https://github.com/hikariming/dshfind(已核实可访问;dshfind.com 未验证,不链死链)。
## 实际改动
- 工作台 `01_content/src/client/locales.ts`(新增):zh/en 词典(31 键)+ NS + 类型声明。
- 工作台 `01_content/src/client/index.tsx`(重构):
- 视图菜单去分组,只留排序(手动/最近)+「管理项目…」;
- 管理面板:改名/隐藏/拖拽(HTML5 drag)/↑↓ 排序/恢复默认;编辑态弱化卡片区;
- 添加面板:接入指引 + 市场外链 + 快捷方式表单(http/https 校验);
- 排序:effectiveOrder(手动=持久化序+注册序补齐;最近=lastUsed 降序+其余手动序);
- 协议回调 reportMeta/reportUsed(useCallback 稳定引用)+ 模块级注册 id 跟踪
(ctx.slots.subscribe + entries);
- 持久化 loadView/loadProjects 显式挑字段(旧 groupBy 忽略)。
- 工作台 `01_content/src/client/styles.ts`:新增菜单分隔/添加面板/管理面板/快捷方式/编辑弱化样式;移除旧 tip 规则。
- 工作台 `01_content/package.json`:dsh.client.inject + peerDependencies 增加 @deepseek-ai/dsh-client-locale。
- travelatlas `src/client/index.tsx`:WorktableCard 升级协议 v2(WORKTABLE_CARD_ID、reportMeta、
reportUsed、hidden/order/nameOverrides 消费,约 20 行);浮层/降级回退/站点未动。
- 构建:两插件 npm run build + node --check 全部通过。
- 健康检查:/api/worktable/health 200(旧进程)。
## 完成状态
代码实现完成;GUI 验收待重启后进行(bundle 层启动时组合)。
## 验证方式与结果
- 工作台、travelatlas 均 npm run build + node --check 通过;
- /api/worktable/health 200;
- 机制预核实(读宿主源码):列表座位渲染器 display:contents 锚点 + 错误边界无 DOM 包裹
→ 卡片根节点为 .dsh-wt_projects 直接 flex 子项,CSS order 排序可行;
- 待验证:重启后 GUI 验收清单(PRD §9 v2 项)。
## 尚未验证 / 待续事项
- 重启后的 GUI 验收(排序/编辑模式/添加面板/i18n/隐藏);
- 未上报 v1 卡片的兼容展示(当前 profile 无此类卡片,需造测试插件验证);
- 管理面板 HTML5 拖拽在侧边栏内的交互体验;
- 宿主无插件清单 API(/api/plugins 等已探测 404),「自动发现已装项目」本版不可行——PRD §10 已知边界。
## 后续待办
- 重启 dsh web(schedule-restart.ps1 已安排)后按 PRD §9 v2 清单验收;
- 观察 travelatlas 降级回退是否正常(卸载工作台场景)。
## 涉及文件清单
| 文件 | 操作 |
| --- | --- |
| dsh-worktable/01_content/src/client/locales.ts | 新增 |
| dsh-worktable/01_content/src/client/index.tsx | 重构 |
| dsh-worktable/01_content/src/client/styles.ts | 增删样式 |
| dsh-worktable/01_content/package.json | 声明 locale 依赖 |
| dsh-worktable/02_process/PRD.md | §3/§5/§6/§9/§10/§11 同步 |
| dsh-travelatlas/src/client/index.tsx | 卡片协议 v2(最小 diff) |
| 本文件 | 新增工作记录 |
## 补记(22:30 左右,重启验收阶段)
- 22:22 计划任务杀掉旧 dsh web 后,用户手动重启(22:22:59 起的新服务);工作台 v2 bundle
经字节级验证已在服务器生效,用户硬刷新后确认 GUI 正常(☰ 菜单/添加面板均为 v2)。
- **travelatlas 并行重写冲突**:22:10:44 其 src/client/index.tsx 被重写为 conversation.view
会话标签页架构(旧全屏浮层废弃),覆盖了本窗口早前应用在旧卡片上的 v2 协议改动。
- 处理:经用户确认后,把 v2 协议重新应用到新卡片(WorktableCard 约 20 行,不动 conversation.view
逻辑),重新构建并通过 node --check;验证服务器实时下发新 bundle(served len=9933、
含 WORKTABLE_CARD_ID/reportMeta),无需再次重启。
- **偶发排版错乱(已自愈)**:用户反馈界面排版乱、随后自行恢复。排查确认宿主资源未变、
travelatlas 样式为局部类、3080 仅一台服务器(npx 与 bin.js 为父子进程)。推断为页面刷新时
恰逢 travelatlas bundle 重建落盘(22:33:17)的写入竞争或重启后页面半载状态;无需代码修复。
教训:对外交付前应避免「用户可刷新的时间窗」内重打包插件。
## 补记(拖动与折叠体验修正)
- 需求(用户反馈):① 侧边栏折叠再展开、以及快速拖动越界松手时,不应重置到 footer,
而应回归上一次位置;② 折叠态不应显示「≡」,应显示一个框框收纳所有项目 emoji,
框框位置与展开前一致。
- 实现:① 移除 wide 变化时的 dock 重置(折叠/展开保持原停靠;仅窗口 resize 仍回弹 footer);
拖动新增越界判定(超出底部/顶部/侧边 24/24/80px 即「无有效落点」),松手回归拖前位置且不持久化;
② 折叠态渲染 .dsh-wt_railBox:项目 emoji(无报到卡片回退 📦)+ 快捷方式图标;
浮动态先以文档流实测折叠列几何(useLayoutEffect),再 fixed 定位到拖前高度;无项目时显示「≡」。
- 验证:构建 + node --check 通过;服务端实时下发(served len=38441,含 railBox/overshoot 标记),
无需重启,用户刷新后实测。
## 补记(与 dsh-usage 余额 dock 的共存修正)
- 问题(用户反馈):侧边栏新出现 dsh-usage 的余额悬浮面板(.u_dock:position:fixed、
bottom:72px、z-index:30,覆盖在侧边栏底部),遮挡了工作台停靠区与 ≡ 手柄,导致无法拖动。
- 实现:① 停靠态自动避让——扫描侧边栏列内贴底(距底 <300px、left<80px)的 fixed 面板,
与区块自然位置重叠时以 margin-bottom 把区块抬到面板上方(上限 340px,含多重面板取最大值);
停靠期间每 2s 轮询跟随面板自身移动;浮动态不避让(z-index 70 天然在上)。
② 标题文字也作为拖拽手柄(≡ 与标题均可拖、双击复位),加大抓取面积。
③ 检测为通用算法(不硬编码 .u_dock 类名),不依赖 dsh-usage 内部结构。
- 验证:构建 + node --check 通过;服务端实时下发(served len=40909,含 bottomInset/
measureBottomOverlay 标记),无需重启,用户刷新后实测。
## 补记(拖动无法落位的根因修复)
- 问题(用户反馈):能拖动,但松手必回原位。
- 根因:避让让位后停靠位置低于「固定 380px 估算的浮动上限 maxTop」,旧越界闩锁把起始位置
本身判定为越界 → 每次松手都触发「回归拖前位置」。
- 修复:① 浮动上限改为按区块实际高度计算(window.innerHeight - startRect.height - 12),
停靠位紧邻浮动范围下沿;② 取消拖动过程越界闩锁,改为「松手瞬间按指针位置判定落点」——
指针在有效落点区内则落位,越出(底/顶 24px、侧边 80px)才回归拖前位置。
- 验证:构建 + node --check 通过;服务端实时下发(served len=40969),无需重启。
## 补记(折叠态图标框水平偏移修复)
- 问题(用户反馈):侧边栏收起后 emoji 图标框偏右,不在窄栏中间。
- 根因:折叠瞬间在过渡帧上测得「展开态」的列宽/left,fixed 定位用旧几何 → 图标按旧宽度居中而偏位。
- 修复:改为折叠动画结束后(320ms + 750ms 双次重测取收敛值)再测量并定位;测量带收敛守卫
(差值 <1px 不重复 setState)。
- 验证:构建 + node --check 通过;服务端实时下发(served len=41341,含 measureRailRect),无需重启。
## 补记(悬浮窗宽度与 sidebar 联动)
- 需求(用户规格):悬浮窗只做宽度与水平定位的联动(高度仍由拖拽决定):
dockWidth = sidebar 宽 − paddingLeft − paddingRight − 40px(每边内缩 20px);
left = sidebar 左边缘视口坐标 + paddingLeft + 20px;
用 ResizeObserver 监听 sidebar(拖宽/折叠/窗口缩放实时跟随);
sidebar 识别 = 从挂载点向上遍历父链,className 含 SidebarRoot/sidebar 或标签 aside/nav,到 body 为止;
降级:找不到 sidebar 或宽度 ≤0 → left 固定 14px,宽度不设内联(CSS min-width:176px/max-width:264px)。
- 实现:FloatRect 精简为 { top }(left/width 全部由派生几何提供);新增 findSidebar 帮助函数、
floatGeo 状态与 measureFloatGeo(getBoundingClientRect + getComputedStyle 取 padding,取整);
useLayoutEffect 挂载首测 + ResizeObserver 订阅 + 卸载 disconnect,无轮询;
拖动仅更新 top,落点/回弹逻辑不变;.dsh-wt_float 增加 min/max 宽度兜底。
- 验证:构建 + node --check 通过;服务端实时下发(served len=42718,含 findSidebar/ResizeObserver)。
## 补记(折叠图标放大 + 项目 emoji 定案 🌏)
- 需求(用户反馈):① 折叠态图标框里的 emoji 太小 → 放大(12px → 17px,行高 16 → 22px);
② 旅行 Atlas 的 emoji 不是用户给的 → 经询问定案为 🌏(地球·亚洲,U+1F30F);
③ 后续所有项目按各自上报的 emoji 展示(协议 reportMeta.icon 已支持,无需改工作台)。
- 改动:travelatlas src/client/index.tsx 全部图标统一 🌏(卡片/报到/降级入口/分栏标题),
重建并通过检查;worktable styles .dsh-wt_railIcon 放大。
- 备注 1:23:21:58 dsh web 被重启过一次(非本窗口发起,疑并行窗口操作),打断了我的一步验证,
服务恢复后健康检查正常。
- 备注 2:travelatlas 客户端在本窗口期间被并行重写两次(会话标签页版 → 分栏模式版),
每次重写都会覆盖 v2 协议与 emoji;本窗口已两次重新应用。
- 备注 3(验证教训):esbuild 默认 ascii charset,非 ASCII 字符在 bundle 里以 \u{...} 转义
输出,且 pwsh Get-Content/IWR 按 ANSI 解码 UTF-8——校验 emoji 需用 read 工具读 UTF-8
或直接查转义序列(如 1F30F)。