dsh-sidebar-focus
左侧工作区的排序与展开,按「有没有在跑」来决定。五条规则,每条一个开关,位置在
插件页 → 点开 dsh-sidebar-focus 之后的详情页里(在描述与行列表之间)。
安装
dsh plugin --profile web add dsh-sidebar-focus
还没上 npm 时用 GitHub 源:
dsh plugin --profile web add github:wjingshan/dsh-sidebar-focus
装完刷新页面即可 —— 本插件是纯客户端(宿主半身是空的),不需要重启 dsh web。
它做什么
| 开关 | 默认 | 作用 |
|---|---|---|
| 置顶正在运行的工作区 | 开 | 有会话在跑、或正在等你的工作区排到列表最前。只影响显示,你手动拖出来的工作区顺序从不被改写 |
| 自动展开正在运行的工作区 | 开 | 有会话在跑、或正在等你的工作区自动展开。宿主本身只在「当前会话所在的工作区」上这么做,这条把它扩展到所有在跑的工作区 |
| 置顶正在进行的会话 | 开 | 运行中与等待你处理的会话排到所属工作区的最前 |
| 收起没有运行的工作区 | 开 | 没有会话在跑的工作区保持折叠 |
| 保留未读完成提醒 | 开 | 含「已完成未查看」的工作区不自动折叠,避免刚跑完的会话被收起来 |
交互语义:什么时候它不接管
自动折叠最烦人的失败模式是「你点开、它立刻折回去」。所以两侧都留了 意图豁免:
- 你亲手展开一个空闲工作区 → 它保持展开,插件不再碰它,直到这个工作区重新变忙再变闲;
- 你亲手折叠一个正在运行的工作区 → 它保持折叠,插件不替你展开,同样直到忙碌状态再次变化。
豁免只在本次页面会话内有效,刷新后回到规则本身。判断依据是 aria-expanded 上不是本插件
写出的翻转——所以「搜索结果跳转」「新建会话」这类宿主自己的展开,也被当成你的意图而保留。
另外两条不参与自动折叠的例外:
- 未分组(Ungrouped)永不折叠:那是散落会话的收容所,不是谁主动选的工作区;
- 当前选中会话所在的工作区保持展开:否则切换会话会落到一个被折起来的组上 (宿主的自动展开只对「没有存过状态」的组生效,而本插件会写状态)。
做法,以及为什么
- 排序不移动 DOM:列表列原本是
overflow-y:auto的块容器,order对它无效;插件先把它变成 flex 列([data-wsf-rank-tree]),再用order给忙碌工作区 / 活动会话排名。DOM 节点从不移动, 所以 React 的 reconciliation 与内置的 FLIP 行动画都不受影响。- 排序是纯展示:不写
Workspace.sessionIds,也不碰你手动排序的结果。
- 排序是纯展示:不写
- 折叠是一次状态写入:折叠的组根本不渲染会话行(宿主的
deriveGroups对折叠组返回空sessions),所以 DOM 层做不到,必须改展开状态本身。写入走宿主自己的视图 store:ctx.get('uiWorkspace').view.setGroupExpanded(key, expanded),键就是工作区 id。 - 「有没有在跑」不能只读行:只看 DOM 的判据在这里必然出错——组一折起来就没有行可读,于是
「里面有会话开始跑了」永远看不见,那个工作区会一直关着(用户报过:正在运行的工作区被折叠,打不开)。
忙碌判定因此改为读 Session 状态快照
useSessionStatus(running/pendingInteraction/completionUnread,与三条判据一一对应)加 Workspace 成员表useWorkspaces(items[].sessionIds说明哪些会话属于哪个工作区)。这两个事实与「有没有渲染成一行」无关, 折叠态照样成立,所以折叠中的工作区一旦开工就会被看见并展开。 两个 hook 由挂在shell.overlay上的零渲染桥接组件发布给 watcher(选它只因为那个席位永远挂载)。 行仍然照读——行级标记要用,快照未就绪时也要回退;两个来源取并集,任一见到在跑就不许关。 - 所有开关都是开关:关闭时对应的
data-wsf-*标记会被移除,CSS 规则随之失效,侧栏回到原生 布局与原生折叠行为——不做「半开」状态。
依赖的宿主契约(实测于本 profile 的内置 dsh-client-ui-workspace)
| 事实 | 来源 |
|---|---|
会话列表是 div[role="tree"] |
AnimatedRows.render |
工作区行是 div[data-row-key="workspace:<key>"][role="treeitem"][aria-expanded],其父元素即组的 section |
renderGroup / ProjectRowItem |
会话行是 div[data-row-key="session:<id>"][role="treeitem"][aria-selected],第一个子元素是该行自己的状态槽 |
SessionNodeItem |
状态点带 data-state ∈ {ongoing, warning, done, error, idle} |
ui-primitives 的 StateDot |
展开状态存在 dsh.workspace.view.v5,groupExpansion[key] 显式为 false 才折叠 |
createWorkspaceViewStore / deriveGroups |
接口是 ctx.get('uiWorkspace').view.setGroupExpanded(key, expanded) |
UiWorkspaceService 的 view 字段 |
| 折叠的组不渲染任何会话行,所以运行状态必须另有来源 | deriveGroups 的 GroupNode.sessions |
槽位标准钩子 useSessionStatus / useWorkspaces 提供状态快照与工作区成员 |
shell.overlay 的 standardProps |
WorkspaceSnapshot.items[].sessionIds 是「谁属于哪个工作区」 |
dsh-api-workspace-controller 的 WorkspaceView / WorkspaceSnapshot |
这些是构建产物的结构,不是文档化 API:宿主升级后如果结构变了,插件会安静地什么都不做
(data-wsf-* 标记无处可落),而不是弄坏侧栏。若 uiWorkspace.view.setGroupExpanded 不存在,
折叠会被整体关闭,并在该插件的详情页上显示告警、控制台打印原因——不会静默失败。
已知限制
- 不折叠未分组,理由见上;如果你要连它一起收,改
scanGroup里的key === ''判断。 - 自动折叠写的是宿主自己的视图偏好:被自动折起来的工作区,其
groupExpansion[key]会以false持久化。卸载本插件后它们会保持折叠——手动点开即可恢复,或在插件页里禁用这个包再刷新。 - 子工作区(树形分组)不参与置顶:子组在父组的
role="group"容器里,不是列表列的直接子元素,order在那里无效。会话级排序不受影响。 - 状态判定只读「运行中
ongoing」与「等待你warning」;error不参与。
验证状态
已验证:
node --check通过;manifest 解析通过;- 安装返回
application: applied,profile 的dependencies与dsh.profile.bundles都已登记; - 实时客户端注册已生效:
plugins.bundle.config席位上出现了dsh-sidebar-focus这个 key (cordis_inspect_query可见)。插件页只在存在该 key 的注册时才渲染那块配置 (owner 的判据是ledger.bundles.has(pkg.name)),所以这一条同时证明详情页里会出现这 5 个开关; - 原先在「设置 → 通用」的 5 行已确认从
settings.general.item移除,不会两处重复; - 状态桥接已确认挂载:
shell.overlay的实时占用者里有dsh-sidebar-focus.status-bridge, 而该席位的 standardProps 同时提供useSessionStatus与useWorkspaces。
未验证(本机 GUI 需要启动令牌,agent 浏览器无法进入;按插件规范的验证边界,未用截图或模拟 DOM 冒充):
- 视觉结果(置顶后的实际顺序、flex 化后的行高与间距、浅色/深色两套主题下的观感);
- 五条规则在真实运行/空闲会话上的实际效果,尤其是这一条:在一个已折叠的工作区里启动会话, 它应当自动展开。我能证明的只到「判据已换成不依赖行渲染的快照」;真实会话的启动我驱动不了;
- 与内置 FLIP 行动画、拖拽排序的共存表现。
自查方式:刷新 Web 页面 → 打开一个正在跑的工作区(应自动展开并置顶)→ 打开一个空闲工作区
(应保持折叠,你手动展开后应保持展开)→ 控制台不应出现 [dsh-sidebar-focus] 开头的报错。
No comments yet. Be the first to write one.