DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

liuyun847 /

liuyun847/dsh-client-ui-session-rail

Verified

DSH 客户端插件:侧边栏收起时在左侧空白区竖排显示活跃会话横条 (DeepSeek Harness)

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@ab9718e2

description: "DSH 客户端插件:侧边栏收起时在左侧显示活跃会话横条(置顶行整体在前,其后阻塞→已完成未读→运行中);dsh web 落在 56px 图标轨道里,Windows 桌面端落在内容区左边缘的浮层里" kind: "plugin"

dsh-client-ui-session-rail

侧边栏收起时,在左侧竖排显示"活跃会话"横条。两种落脚形态(按 <html> 平台标记自动选):

轨道形态(dsh web 浏览器)          浮层形态(Windows 标题栏桌面)
┌──────────┐                      ┌──────────────────────────────┐
│   🐟     │  ← 品牌/收起按钮      │ 应用 编辑        ─ □ ✕      │ ← 40px 标题栏
│   +     │                      │ ▬▬  4. 要注意的代价          │
│   ✦      │                      │ ▬▬                           │
│   🔍     │                      │ ▬▬  正文…                    │
│          │                      │ ▬▬                           │
│   ▬▬     │ ← 已置顶(中性灰)    │ ▬▬  ← 24px 窄浮层压左留白   │
│   ▬▬     │ ← 阻塞(琥珀)        │ ▬▬                           │
│   ▬▬     │ ← 阻塞                │                              │
│   ▬▬     │ ← 已完成未读(绿)      │                              │
│   ▬▬     │ ← 运行中(蓝,呼吸)    │                              │
│          │                      │                              │
│   ⚙      │  ← 设置              │                              │
└──────────┘                      └──────────────────────────────┘
  • 顺序:置顶行整体在前(组内 阻塞 → 已完成未读 → 进行中 → 置顶空闲),之后才是 阻塞 → 已完成未读 → 运行中;同档按最近更新倒序。
  • 初始形态:与右侧对话索引同构的 20×2px 圆角横条,静止缩到 60%,悬停/聚焦放大到 100%。
  • 悬停:右侧弹出详情卡(会话标题 / 状态 · 多久以前 / 工作区)。
  • 点击:跳到该会话(uiWorkspace.openSession),侧边栏保持收起。
  • 空闲会话不进栏,唯一的例外是置顶会话:置顶不论状态都进栏,空闲时用中性灰横条 (静态,不呼吸)—— 所以栏里出现的每一根要么"有事在等",要么"是你钉住的那条"。

状态与配色

档位 判定(与 ui-workspace 的行状态同口径) 颜色
已置顶(空闲) 在 pinnedSessionIds 里、不在 archivedSessionIds 里,且自身没有下面三档(无阻塞、没在跑、子代理没在跑、无未读完成) --dsw-alias-label-tertiary(中性灰、静态不呼吸;浅色 #81858c / 深色 #adb2b8)
阻塞 pendingInteraction.kind ∈ approval / plan-review / question --dsw-alias-state-warn-primary(与侧边栏"等待审批"点同色)
已完成未读 completionUnread === true(且自己没在跑、子代理也没在跑) --dsw-alias-state-success-primary(与"已完成"点同色)
运行中 自己 running === true,或名下还有 running 的直接子代理 --dsw-alias-state-business-primary(蓝色强调色)+ 1s 呼吸

优先级照抄侧边栏状态点:阻塞 > 自己运行 > 子代理运行 > 已完成未读 > 空闲。 只有子代理在跑的会话在侧边栏也是"进行中",所以这里同样进栏;悬停卡片会补一句 "N 个子智能体运行中"。子代理清单取自该会话的 subagentCatalog 投影,每个子代理的 running 先看状态快照、再看目录行(与 ui-workspace 的 runningChildCount 一致)。

置顶优先于档位:排序键是 (是否置顶, 档位, updatedAt 倒序, id 升序) —— 所有置顶行整体 排在所有非置顶行之前,置顶组内部再按 阻塞 → 已完成未读 → 进行中 → 置顶空闲。置顶口径照抄 ui-workspace 的 sessionNode():pinnedSessionIds 有它、archivedSessionIds 没有它; 两个集合同源于同一份 useWorkspaces 快照。置顶但本身已有状态的会话保持原档 (blocked / done / running),只是同样前置;只有"置顶且空闲"才落到 pinned 档。 排除规则不变 —— 空会话、子代理会话、归档会话仍不进栏,且归档优先于置顶。

实现要点

三个落点全部走座位契约,不读别的插件的 DOM / 样式 / 组件源码:

  1. sidebar.footer.action(ui-sidebar 声明的 root 级 list 座位)——它的 owner props 带 wide, 这是框架给插件看"侧边栏是否收起"的唯一正规通道。本插件在那里注册一个不渲染 DOM 的探针, 把 wide 发布到模块内的折叠态源。Windows 桌面端收起时该座位仍然挂载(ui-sidebar 只是给 footArea 加了 display:none,React 组件不卸载)⇒ 探针照常发布 wide === false,不用另找信号。
  2. shell.overlay(ui-layout 声明的 frame 级浮动层,在列容器之外、z-index 20)——横条栏渲染在这里: left:0 + 垂直居中。宽度走 CSS 变量 --sr-track:轨道形态 56px,浮层形态 (.sr-frame--overlay,判据是 <html data-windows-titlebar>)24px。 不用 position:fixed 挂在探针身上:祖先一旦有 transform/filter,fixed 就改成相对该祖先定位, 收起动画留下的 transform 会让横条错位。
  3. 数据来自标准 props:useSessions(会话目录)、useSessionStatus(running / pendingInteraction / completionUnread)、useWorkspaces(归档集 + 置顶集 + 工作区标题)。跳转回调与 t 走注册的 inject 面。

样式只吃主题令牌(--dsw-alias-* / --dsw-radius-lg / --dsw-elevation-panel / --dsw-font-*), 浅色深色自动跟随;prefers-reduced-motion 下关掉呼吸与过渡。可见文案走 locale 服务 (命名空间 sessionRail,zh/en 双词典)。

已知边界

  • macOS 桌面不出图:ui-layout 在 darwin 收起时把 sidebarCol 整个从 DOM 移除(不是压成 0), 既没有轨道也没有内容列左留白可压,本插件在那里返回 null。
  • Windows 桌面端走浮层形态:ui-layout 对 Windows 标题栏桌面把收起宽度设为 0 (collapsedWidth = darwin || hasAttribute('data-windows-titlebar') ? 0 : 56),整列压平。 本插件改成贴内容区左边缘的 24px 窄浮层 —— 实测 1280×720 最大化窗口下内容列左留白约 85px, 24px 只压住留白、不碰正文;窗口更窄时留白随之收窄,理论上到 24px 内边距才会开始压字。
  • 子代理只算直接子代理:清单来自该会话自己的 subagentCatalog 投影(侧边栏同源); 子代理会话自身仍不进栏(侧边栏也不列)。
  • 归档过滤与置顶都依赖工作区快照:useWorkspaces 缺席时退化为不过滤归档会话、也认不出置顶 (拿不到 pinnedSessionIds)⇒ 置顶空闲会话不进栏,行为与改造前完全一致(并留一条控制台告警)。 归档集与置顶集会一起缺席,不存在"认得出置顶却滤不掉归档"的半截状态。
  • 置顶行多了不特殊处理:多出来的置顶行与别的行一样进 .sr-scroller(最高 min(60vh,420px)),超出就滚动;落点、座位注册与轨道宽度都没变。
  • 横条栏是浮层,不吃鼠标事件的部分(横条之间的空隙)会穿透到下层界面。

安装与启用

本包是 profile 组合包(bundle):装进 profile 后,插件页「已安装」里的总开关写 dsh.profile.bundles,行级开关向 profile 的 cordis.patch.yml 写 disabled 覆盖。

# 安装(或重装以刷新链接)
# 用 plugin_manager 工具:action=install_bundle, target=<本包绝对路径>

客户端插件的加载边界:页面必须在插件装好之后打开(或刷新)。本机实测:宿主侧 dsh-client-hmr 的 /plugins/events 实时图同步在这个页面上没生效 —— 停用另一个客户端插件 (@michengai/dsh-btw)后宿主条目确实消失,而页面里的注册仍在。所以装完新插件要刷新一次页面 (F5);刷新后 sidebar.footer.action 座位里应出现 session-rail-probe、shell.overlay 里应出现 session-rail(可用 cordis_inspect_query 查 Slots 核对)。

更正(2026-10-06 实测):上面这条"装完新插件要刷新一次页面"不必再当硬要求 —— 当晚以 link: 装一个 新声明 dsh.client 的客户端插件后,不重启 dsh、也不刷新页面:宿主自己完成重组合并把新图推给已经连着 的页面(/plugins/events(SSE)首帧 graph 的 rev 变、entries 里出现该插件、按其 URL 取回 bundle 是 HTTP 200;宿主进程 pid 与启动时间全程未变)。正确口径:不必重启;宿主侧立刻生效;F5 只是零代价保险, 不是必需。但那次 @michengai/dsh-btw 的观察不被推翻、也不要用这条新结论覆盖它:它说的是页面 DOM 侧 那一环(已打开页面里的旧注册有没有随宿主条目一起消失),性质同本机热重载调研报告 §6 的 U2b —— 至今未在 开发者工具里验证过,可能只是当时版本的缺陷。两头都要确定性,就按一次 F5:零代价,顺带把这一环也钉死。

发布

已发布到 GitHub:liuyun847/dsh-client-ui-session-rail(public,2026-09-26)。

2026-10-01 把 Windows 桌面端浮层形态与详情卡生命周期修复同步上去:远端 main c4acc9a, 单测 37 → 40,推送后逐文件哈希与本地一致。

2026-11-06 发布 0.2.0:把 2026-11-05 的置顶会话不论状态都进栏(单测 40 → 51)推上远端 main。 版本号 0.1.0 → 0.2.0(新增用户可见行为,按语义化版本升次版本);package.json 的 description 同步写上"置顶会话不论状态都进栏、整体排在栏首"。

验收状态

2026-11-05 置顶会话不论状态都进栏(本轮新增)

  • 需求:置顶会话"空闲也要出现",且整体排在栏首;本轮实现 pinned 档 + 置顶排序键 (细节见「状态与配色」「实现要点」)。
  • 单测 51/51(node tests/derive.test.mjs,原 40 例一条未改),新增 11 例覆盖: 置顶空闲进栏且 state === "pinned"、置顶组整体前置(非置顶阻塞也排在置顶空闲之后)、 置顶运行中仍是 running 且组内压过置顶空闲、已归档的置顶会话不进栏、行对象带 pinned、 置顶集缺失(退化路径)不进栏、state.pinned 词典、previewMeta 追加"已置顶"、 CSS 中性令牌且无动画、渲染层置顶横条与无障碍名、BasicRail 不显示置顶空闲且不新增告警。
  • 数据侧取证(只读):.dsh/storages/workspace.json(用户主目录下)的 pinnedSessionIds 当前有两条,两条都不在 archivedSessionIds 里 ⇒ 目标会话空闲时应当以中性灰横条出现在栏首。
  • 本轮未做视觉验收:任务边界只要代码与单测,未刷新页面、未抓屏。页面刷新后 目标会话空闲时应看到栏首多一根中性灰横条(无障碍名 <标题> · 已置顶)。

2026-09-30 桌面端浮层形态(本轮新增)

  • 背景:本机 DSH 于 2026-09-30 从 dsh web 切到官方桌面端 0.2.0-rc.2,横条栏在桌面端完全不出图。 根因两条:ui-layout 对 Windows 标题栏桌面把 collapsedWidth 设成 0 (darwin || hasAttribute('data-windows-titlebar') ? 0 : 56),整列压平;而本插件原来的 railExists() 正是靠同一个 data-windows-titlebar 标记判"不出图"。
  • 改法:railExists() 只保留 darwin 的否决;新增 overlayMode()(判据仍是 data-windows-titlebar), 浮层形态下把轨道从 56px 收窄到 24px、贴内容区左边缘(见「实现要点」与「已知边界」)。
  • 实测(2026-09-30 18:0x,1920×1080 最大化窗口,cordis_inspect_query + Cua Driver 后台投递, 全程未抢前台):
    • Slots.listSubTree(root="shell.overlay") 的 occupants 里有 {id: "session-rail", order: 10, active: true}; root="sidebar.footer.action" 里有 session-rail-probe ⇒ 两半都注册上了。
    • 点标题栏左侧的收起按钮后,抓屏在窗口左边缘竖排出现两根横条:绿色(已完成未读)在上、 蓝色(运行中)在下,位于 x≈30–50、垂直居中 —— 与设计一致。
    • 同屏量到内容列左内边距 ≈196px(收起态),24px 的浮层完全落在留白里,不压正文。
    • 单测 40/40(node tests/derive.test.mjs)。
  • 本轮未实测:浮层形态下的悬停详情卡与点击跳转。原因是本机前台被别的窗口占着, Cua Driver 对 Chrome_WidgetWin_1 的后台 hover/drag 不可用,而抢前台会打断用户正在做的事 (本机当时有游戏在跑)。这两条代码路径本轮未改动,浏览器形态下此前已由用户实测通过。

2026-09-26 浏览器形态(原始验收)

已实测:

  • 宿主行 include:session-rail 生效;刷新后两个座位条目都注册上了。
  • 侧边栏收起时横条栏出现:UIA 树里 Group "活跃会话" + 三条 Button "<标题> · <状态>", 顺序 = 阻塞在前、运行中在后,无障碍名走的是词典中文(证明 locale 座位与词典注册都生效)。
  • 抓屏量像素(1280×720 逻辑坐标):阻塞条 rgb(245,158,11) = --dsw-static-amber-500; 运行中条 rgb(79,106,153) = 深色主题的蓝色强调色 #7aaaff 按呼吸动画的半透明叠加; 两条运行条处在不同呼吸相位(动画在跑);条宽 18px = 20 CSS × 静止 0.6(与右侧索引同构)。
  • 单测 37/37(node tests/derive.test.mjs)。

未能由我实测(工具面限制,不是已知缺陷):点击跳转。本机浏览器窗口是 Chrome_WidgetWin_1,Cua Driver 拒绝后台投递键鼠;move_cursor 移动真实指针后页面收不到 hover(条宽仍是静止态的 18px),而抢前台会打断用户正在做的事,故没有继续。

用户实测补充(2026-09-26):悬停详情卡能出,但移开鼠标后不消失 —— 已修 (横条补 onPointerLeave/onPointerCancel,原先只挂了 onBlur,鼠标划过而没点过时收不掉); 新增的渲染层用例会断言这两个处理函数存在,防止回归。

用户实测补充(2026-09-27):点绿色"已完成未读"横条跳转后,横条消失而详情卡留存, 划过别的横条才恢复 —— 已修,根因见「踩过的坑」的"卡片比横条活得久"一条。 回归用例走 tests/derive.test.mjs 的 mountRail(带 hook 槽位、能重复渲染的 harness)。

开发

lib/client.js          浏览器半侧(唯一有行为的文件):纯函数 + 组件 + apply
lib/index.js           宿主半侧:空 apply(只为让加载器发现本包)
cordis.patch.yml       profile 层 patch(一行 insert)
tests/derive.test.mjs  纯函数单测 + apply 冒烟 + 渲染层用例(node tests/derive.test.mjs,51 例)

测试里有两个 React 桩:reactStub 无状态,只够断言"处理函数挂上了没"; 需要跨渲染的用例(详情卡在横条消失后要收掉)用 mountRail —— 带 hook 槽位、 能重复渲染并在提交后冲刷 effect 的最小 harness(等价于真实 React 的 fiber 复用)。

改代码后:工作区这份是 link: 进 profile 的(实测 profile 里是 Junction), 改文件即时生效;但页面仍需刷新才会重新执行 bundle。

踩过的坑

  • 卡片比横条活得久:点绿色"已完成未读"横条跳转 ⇒ uiWorkspace.openSession 把该会话 变成主视图 ⇒ ui-session 的 reconcileStatus 当帧清掉 completionUnread ⇒ 横条从行里消失。 指针正压在那根横条上,而横条卸载时浏览器只对已脱离文档的节点发 pointerleave, React 的 onPointerLeave 收不到(焦点路径同理:被移除的按钮不再发 blur)⇒ preview 留在旧行上,详情卡挂着一根不存在的横条,直到划过别的横条才被换掉。 修法是让卡片只对"还在栏里的横条"负责:行里查不到 preview.row.id 就收卡片 (行还在、只是换档时不收,免得鼠标还停在横条上卡片却闪没)。
  • 详情卡只挂 onBlur 收不掉:卡片自己是 pointer-events:none,收不到任何鼠标事件; 鼠标划过横条(没点过、没聚焦)时不会触发 blur,卡片就一直挂着。横条必须自己处理 onPointerLeave(顺带 onPointerCancel,指针被系统抢走时也要收)。
  • --dsw-alias-brand-primary 不是蓝色强调色:这套设计里它是"品牌墨色" (--dsw-static-neutral-bluish-1000,浅色主题近黑),深色主题下画出来是浅灰横条。 实测抓屏量到 rgb(178,178,179) 才发现。蓝色强调色是 --dsw-alias-state-business-primary (= --dsw-static-deepseek-500 #4176e6)。单测里加了一条断言禁止再用 brand-primary 当强调色。
  • --dsw-alias-state-idle-primary 太淡,别拿它画"置顶空闲"横条:名字最贴("空闲"), 值也确实是界面主题里的真令牌,但浅色 = --dsw-static-neutral-300 #d4d4d4(对白底 1.48:1)、 深色 = --dsw-static-neutral-600 #545557(对 --dsw-alias-bg-base 2.44:1),画成 2px 细条 几乎看不见 —— 侧边栏对空闲行干脆不画状态点,原因就在这里。置顶空闲改用 --dsw-alias-label-tertiary(浅色 #81858c = 3.71:1、深色 #adb2b8 = 8.54:1,与三档状态色的 对比度量级相当),中性色相、无动画。
  • inject 必须声明全:动态客户端包的 apply 会等到 inject 里的服务全部就位才跑。 最初只写 ["slots"],apply 在 slots 一就绪就执行,那时 uiWorkspace / locale 还没被各自的 插件 provide,ctx.get("uiWorkspace") 返回 undefined ⇒ 整插件静默不注册(座位表里查不到条目, 页面无任何表现)。现在是 ["slots", "uiWorkspace", "locale"]。
  • locale: NS 与词典注册的顺序:ui-locale 的 apply 里 provide("locale") 之后同步 installLocale(),所以等到 locale 服务再执行就一定能拿到框架合成的 t 座位;服务缺席时组件 退回中文兜底,不硬崩。
  • 探针与横条栏的时序:探针的 effect 先于浮层的订阅 effect 跑,横条栏在订阅时补读一次当前值 (setValue(collapsedValue)),否则"一开始就是收起态"会漏掉第一次发布。
—/ 5

No ratings yet

Verified DSH bundle

Commit ab9718e21d3f

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout