🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

侧栏悬浮球 shuing/dsh-sidebar-launcher

DeepSeek Harness
shuing24 天前有提交已提供中文文档扫描:未发现敏感能力

拖动悬浮球快速打开和管理侧边栏页签

30/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/11 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add shuing/dsh-sidebar-launcher
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:仅索引
是什么
dsh 原生插件 · browser
装得上吗
本站尚未做安装检查
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 24 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
🟢 扫描:未发现敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描未发现敏感能力:最多只发网络请求、读取自身元数据或只读文件,未发现执行命令、文件写入或敏感信息读取。

扫描于 2026/11/3(1 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-client-locale@deepseek-ai/dsh-client-ui-primitives@deepseek-ai/dsh-client-ui-sidebar-right@deepseek-ai/dsh-client-ui-slots
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 拖动悬浮球打开侧边栏
  • 管理侧边栏页签
  • 右键清空侧边栏

通过悬浮球,管理你的侧边栏,鼠标左右键各自打开,右键悬浮球清空你的侧边栏

README

由 DeepSeek 最新模型翻译生成
dsh-sidebar-launcher English · 中文 一个可拖动的 36×36 悬浮方块,挂在 DSH Web 外壳的右侧边栏上。它位于 frame 的浮层(overlay layer)里,鼠标悬停或点击时弹出右列的页签菜单,并把最近激活的三个页签固定在菜单离方块最近的那一端。右键方块会弹出第二个菜单,包含六项侧边栏操作。 它是一个独立插件:不声明对任何其他侧边栏插件的依赖,只读取宿主的公开 API,通过普通的 bundle-patch 通道挂载。 它能在跨两代共四个宿主版本上不经修改地运行——现代 grid 渲染器(DSH 0.1.7-rc.2、 0.2.0-rc.2、0.2.1-alpha.2)与旧式 flex 渲染器(0.1.5-rc.2,Tavern 桌面版所用)——方式是探测实际服务暴露了什么能力,而不是比较版本号。详见跨宿主兼容。 截图 现代宿主上的悬停菜单。最近三项排在卡片离方块最近的那一端,与全量列表之间由一条分隔线隔开: 现代宿主上的悬停菜单,最近三项在离方块最近的一端 同一菜单在旧式宿主(0.1.5-rc.2,Tavern)上的样子,每个标签都来自宿主自己的页签类型: 旧式 0.1.5 Tavern 宿主上的悬停菜单 右键方块弹出的六行侧边栏操作卡片。
展开完整文档(README 剩余 1 节)
dsh-sidebar-launcher

English · 中文

一个可拖动的 36×36 悬浮方块,挂在 DSH Web 外壳的右侧边栏上。它位于 frame 的浮层(overlay layer)里,鼠标悬停或点击时弹出右列的页签菜单,并把最近激活的三个页签固定在菜单离方块最近的那一端。右键方块会弹出第二个菜单,包含六项侧边栏操作。

它是一个独立插件:不声明对任何其他侧边栏插件的依赖,只读取宿主的公开 API,通过普通的 bundle-patch 通道挂载。

它能在跨两代共四个宿主版本上不经修改地运行——现代 grid 渲染器(DSH 0.1.7-rc.2、 0.2.0-rc.2、0.2.1-alpha.2)与旧式 flex 渲染器(0.1.5-rc.2,Tavern 桌面版所用)——方式是探测实际服务暴露了什么能力,而不是比较版本号。详见跨宿主兼容。

截图

现代宿主上的悬停菜单。最近三项排在卡片离方块最近的那一端,与全量列表之间由一条分隔线隔开:

现代宿主上的悬停菜单,最近三项在离方块最近的一端

同一菜单在旧式宿主(0.1.5-rc.2,Tavern)上的样子,每个标签都来自宿主自己的页签类型:

旧式 0.1.5 Tavern 宿主上的悬停菜单

右键方块弹出的六行侧边栏操作卡片。图中"分栏"两项是置灰的,因为当前只开了一格停靠格:

六行侧边栏操作卡片

演示

一段 18.7 秒的实拍,环境是 0.2.0-rc.2 宿主:悬停方块打开卡片、选择页签,再点一行打开第二个页签并分出第二格。

如果你的 Markdown 渲染器不支持内联视频,文件本身在 docs/launcher-demo.mp4,静帧在 docs/launcher-demo-poster.png。

它做什么

- 悬浮方块。 36×36,直角(非圆角),首次运行时贴在右列自己的开关按钮下方;边栏尚未渲染时落在视口右下角。
- 可拖动。 仅主键。位移小于 4px 视为点击,4px 及以上视为拖动。位置跨刷新记忆,并始终钳制在视口内,保证整个方块可见。
- 悬停 / 点击菜单。 一个下拉卡片,列出宿主自己的页签指南里的全部条目——与右列 + 菜单相同的条目、相同的顺序。最近激活的三个页签固定排在离悬浮球最近的那一端:卡片向下开(球在上半屏)时排在卡片顶部,卡片向上开(球在下半屏)时排在卡片底部,与全量列表之间隔一条分隔线。
- 左键点某一行在左格打开该页签。右键点某一行在右格打开;还没有右格且宿主允许分栏时,先拆出一个右格,再把页签开进去。没有空间分栏时,宿主会静默落在当前格,与它自己的分栏按钮行为一致。右列整体收起时,左右键都会先把侧边栏打开(单栏)。这里的「左/右」是宿主自己的列,不是文档顺序——详见如何读取侧边栏的形状。
- 点击行不关闭菜单。 只有指针离开「球 + 卡片」区域才关闭;卡片开着期间行序冻结,连续点击两行不会跳行。
- 跟随主题与语言。 颜色只取自 --dsw-alias- 令牌,每个标签都经 locale 服务与页签类型自己的可翻译标题读取。

侧边栏操作菜单

右键方块会弹出第二张卡片,与悬停菜单相互独立。两者不会同时出现:打开一个会关闭另一个。

| 菜单项 | 行为 |
| 清空标签页(保留浮动) | 关闭全部停靠页签,随后收起侧边栏。浮动面板不动。 |
| 清空全部标签页(含浮动) | 关闭全部页签(停靠 + 浮动),随后收起侧边栏。 |
| 清空左分栏 | 关闭最左那个停靠格里的页签。只有一格时置灰。 |
| 清空右分栏 | 关闭最右那个停靠格里的页签。只有一格时置灰。 |
| 清除所有浮动面板 | 关闭全部浮动面板,停靠格不受影响。没有浮动时置灰。 |
| 重置悬浮球位置 | 把方块送回测量出的锚点,并清除已保存的位置。 |

分隔线把六项分成三组:前两项"清空并收起"、中间三项分栏/浮动、最后一项方块自身的重置。

几条需要知道的行为

- 唯一的停靠指南页永远不会被关闭。 当 guide 是唯一的停靠页签时,宿主会拒绝关闭它(canCloseTab 要求 !soleDockedTab),所以"清空标签页"总会剩下这一页。
插件自己复刻了同一条规则,而不是依赖宿主的守卫。浮动面板里的指南页不受保护——宿主的规则限定 host === "dock"——它会像其他浮动页签一样被关闭。
- 清空一格并不总会拆掉分栏。 dockkit 的 settle 过程会合并空 pane,但明确跳过 root pane,所以清空分栏结构中的 root 一格时,它会被清空而分栏结构保留。
- 关掉浮动面板的唯一页签,面板就消失了。 同一个操作会把该 float pane 从布局里摘除,因此不需要任何"关闭面板"的 API。
- 置灰状态在开菜单时从实时 DOM 与 API 计算,不缓存:停靠格少于两格时分栏两项置灰,没有浮动时浮动项置灰。

前置条件

- DSH Web,以下之一:0.1.5-rc.2、0.1.7-rc.2、0.2.0-rc.2、0.2.1-alpha.2。
- 右侧边栏(@deepseek-ai/dsh-client-ui-sidebar-right),dsh-web-app 默认会挂载它。
没有它插件保持不激活,方块直接不出现——sidebarRight/sidebarRightTabs 是等待就绪的,从不假设它们存在。

安装

在官方左侧栏添加

https://github.com/shuing/dsh-sidebar-launcher

下载到本地解压缩
dsh plugin --profile web add <文件地址>

目录结构

package.json dsh.bundle.patch + dsh.client {platform:"web", inject:[...]}
cordis.patch.yml 一条 insert:宿主行(空半边,存在是为了让 client 扫描发现它) tsdown.config.ts host ESM + 浏览器 module-loader 产物 tsconfig.json strict、no-emit 类型检查(也覆盖构建配置) tsconfig.build.json 只产出声明文件到 lib/types tsconfig.evidence.json 把 src/client 编译成普通 JS,供验证脚本使用 src/index.ts 宿主半边:刻意留空 src/client/ index.tsx apply():注册语言、等待服务、注册槽位、两个菜单的持有者与分发 launcher-ball.tsx 方块本身、它的拖动,以及悬停/点击/右键状态机 launcher-menu.tsx 页签下拉卡片,基于宿主的 Menu/MenuItemButton 原语 sidebar-action-menu.tsx 六行操作卡片及其纯函数行构造器 sidebar-actions.ts 当前开着什么、怎么关:停靠/浮动划分、左右格排序、指南页守卫、收起 menu-card.ts 两个菜单共用的卡片管线(定位 portal 卡片、避让方块) placement.ts 一行的左/右手势如何抵达各代宿主的分格 entries.ts guide() -> 菜单的两个分区 recent.ts 持久化的最近列表与页签条追踪器 anchor.ts 首次摆放与视口钳制 capabilities.ts 决定各宿主分支的能力探测 host-access.ts 读取两代宿主服务形状的唯一位置 context-types.ts 所消费宿主服务的结构化镜像 locales.ts 中/英文界面文案 launcher.css 只用令牌的样式,插件前缀的字面类名 docs/ 本 README 使用的截图与演示录像 .evidence/ 下文描述的验证脚本

跨宿主兼容

插件同时运行在四个宿主版本上,它们的客户端 API 形状不同。其中三个——0.1.7-rc.2、 0.2.0-rc.2、0.2.1-alpha.2——是现代形状(grid 渲染器、tabsIn、observable mounted、导出 MenuItemButton);0.1.5-rc.2(Tavern 桌面版)是旧式 flex 渲染器。每一个分支都是通过对活对象问一个 typeof 问题来选择的——浏览器 bundle 里读不到版本字符串,而且拿版本号当判据本身就是问错了问题。这些探测集中在 capabilities.ts:

| 探测 | 现代(0.1.7 / 0.2.0 / 0.2.1) | 旧式(0.1.5) |
| 导出 MenuItemButton | 有 → 组件行 | 无 → items 数据行 |
| 存在 sidebarRight.tabsIn | 有 | 无 → 读 mounted()?.layout.tabs |
| mounted 是 observable | 是 | 否 → 它是普通方法 |
| 存在 sidebarRight.split | 有 | 有 → 两代走同一套摆放 |
| 写出 data-dockkit-column | 有 → 左右由它给出 | 无 → 退回几何测量兜底 |

代价最大的几处:

- mounted 的差别在种类,不只是有无。 在 0.1.5 上它是会解构 this 的类方法;把引用摘下来裸调会抛异常。所有调用都显式带上接收者。
- 两代的 split() 都会给新格播种宿主的默认页,所以只调 split() + openTab() 会在用户要的页签旁边留一个多余页签。插件会认出「这次分栏新增的那个页地址页签」并通过 replaceTab 替换掉。
- 摆放不再按代分支。 上一轮在 0.1.7 用 preferNewPane、在 0.1.5 用 split()。
宿主 openContent 归约器对「目标格里已经开着的页面类内容」会把新格请求短路成普通的聚焦,所以这个选项在最需要它的那一行(右键已开项)上根本分不了栏。现在两代统一走
split() + openTab({ paneId });这也顺带退役了 preferNewPane 那个探测——
它是兼容层唯一的已知假设,没法用 typeof 探测、只能别名到 tabsIn,写在 capabilities.ts 里。

最近列表的来源

首选来源是宿主自己的页签条。dockkit 的 chip 带 role="tab" 与 aria-selected,并通过 data-dockkit-tab 标识自己,位于 [data-dockkit-strip-tabs] 容器内,所以监听 aria-selected 的 MutationObserver 就是宿主在报告用户刚激活了哪个页签。三个细节都很关键:

- 观察器只在变化时触发,所以挂载路径会先扫一次页签条;
- 切换会话时 seat 会被替换,所以 sidebarRight.mounted 变化会重新扫描并重绑(在 0.1.5 上没有这个信号,改用监听 seat 替换的 body 观察器覆盖同一过渡);
- tabId 通过 active() 解析成 kind,另一个格里的页签则回退到当前会话的完整记录列表。

如果在会话已挂载的情况下从未见过页签条,列表会降级为"从本启动器打开过的页签",并且菜单会明确显示一条提示说明降级。它绝不会仅因为右列恰好收起就降级,也绝不会隐藏降级。

只持久化 kind 字符串(dsh-sidebar-launcher:recent)。图标是组件、标题是 thunk,两者都在渲染时重新查表——这正是最近几行能跟随语言切换、以及跟随某个页签类型在插件之间迁移的原因。

方块自身的位置单独持久化(dsh-sidebar-launcher:position),"重置"除了移动方块也会清掉这个键——否则刷新会把保存的位置原样放回来。

如何读取侧边栏的形状

在这方面 sidebarRight 只提供 close(tabId),别的都没有:没有 closeAll、没有
closePane、也没有布局树。tabsIn(sessionId) 返回不带 pane 归属的扁平记录,所以
"左边那一格"根本无法从 API 读到。由此有两条规则:

- 浮动页签是集合差,绝不扫 DOM。 浮动面板的头部带 data-dockkit-float-grip / -title / -dock / -close,但没有 data-dockkit-tab,所以浮动页签的 id 无法从它自己的标记里恢复。它按 API 全集 − 停靠集 计算,这也正是诚实的定义:一个页签是浮动的,当且仅当它开在停靠格之外的地方。
- 停靠格的分组是唯一的 DOM 依赖。 它来自 [data-dockkit-pane],按宿主自己写的 data-dockkit-column 从左到右排序(该属性缺失时退回 getBoundingClientRect().left)。
按文档顺序排会是错的:现代 grid 渲染器按页签 id 字典序
(Object.values(tabs).sort((a, b) => a.id.localeCompare(b.id)))输出格子,之后才用
gridColumn: column 2 + 1 摆位,所以 DOM 顺序等于各格激活页签 id 的字典序,与左右毫无关系。旧式 flex 渲染器完全不写列号、本身已是视觉顺序,所以这条兜底是活路径,不是死代码。只有"清空某一分栏"这两项与左右键手势依赖它,其余不依赖。

容器属性也分代:现代宿主标记为 [data-sidebar-right-session],而 0.1.5 没有这个属性,面板是 [data-sidebar-right-panel]。两者都被接受。

样式

颜色只用宿主令牌。方块是唯一的实心琥珀表面(--dsw-alias-state-warn-primary,悬停与拖动用 --dsw-alias-state-warn-label);卡片取 --dsw-alias-bg-layer-3;悬停填充是 12% 的 color-mix 叠色,卡片描边是 20% 的细线。文字与图形取自 --dsw-alias-label-*。

两个菜单都刻意穿着同一套中性数值,取值落在两代宿主自身菜单度量之间而不是照抄任何一方——否则同一个插件在两个宿主上会长得明显不同。卡片宽度按最长一行自适应(width: max-content,范围 160–320px;较高的操作卡片用 196–264px),纯 CSS 实现,超长标签由宿主自己的 text-overflow: ellipsis 收尾。

两个后果如实说明:本插件的菜单不再与任一宿主上其他原生菜单完全一致;并且 0.1.7 的毛玻璃卡片材质被换成了不透明填充,换取在没有背景模糊的宿主上仍可读。

在 0.1.7 上,卡片的背景是由内层 MenuSurface 图层绘制的,而不是卡片本身;那一层被显式中和掉了,否则不透明填充会被合成在半透明模糊之下。

定位

卡片优先开在方块所在半边之外:球在中线以下就向上开,以上就向下开。这在通常情况下消除了重叠,但并不能保证不重叠——一张很高的卡片被宿主自己的视口边距钳制时,会被推回到方块上方。所以卡片打开期间有一个逐帧检查把它推开,写的是 transform 而绝不是 left/top,因为 0.1.7 的宿主每帧重算位置,会覆盖写进这两个属性的任何值。因此菜单开着时拖动方块,卡片会实时避让。

验证

pnpm build 产出 lib/index.js 与 lib/client.js。client 产物是一个
window.__ModuleLoader__.load({ id, factory }) 闭包,带 apply/inject——与宿主
自己的 client bundle 形状相同——它唯一的运行时 import 是模块表条目 react、 react/jsx-runtime 和 @deepseek-ai/dsh-client-ui-primitives。

.evidence/ 目录放着开发期间用的验证。cross-host.mjs 是核心那个:它把同一份启动器模块分别对着两代各自真实安装的 primitives bundle 加载,断言能力探测、菜单组装、左/右键分发、右键索引拒绝,以及非活动页签的 kind 能解析而不抛异常。它是钉住"一份代码、两代宿主"的回归。左右格排序另有两份证据:sidebar-actions-check.mjs 直接覆盖排序与兜底; next-round-column-order.mjs 把真实的 0.2.0 界面驱动到「文档顺序与列号顺序相反」的状态,断言左键落在视觉左格、右键落在视觉右格,并打印旧文档顺序代码会选中哪一格。

node .evidence/run-cross-host.mjs # 两代宿主(现代 + 旧式)
node .evidence/ball-interaction.mjs # 方块的手势状态机
node .evidence/sidebar-actions-check.mjs # 操作分区、指南页守卫、收起
node .evidence/menu-interaction.mjs # 页签下拉的接线
node .evidence/avoid-residue.mjs # 避让确实触发,且不留残留

三个必须说清的限制。菜单类脚本用了一个忠实于契约的替身替代宿主 primitives,所以它们证明的是 本插件自己的行与分发接线,而不是 primitives 的内部;primitives 自身的行为另有一套针对已安装包的验证。读 DOM 的检查是通过插件自己也在用的同一套属性契约去读宿主标记,所以它们确认的是"插件与它所依据的标记一致",而不是"宿主会一直保留这套标记"。而且这些脚本都不能替代 看真实页面:外观、真实指针手感与主题渲染都是在运行中的界面里确认的。

三处设计决定的说明

每一处都与原始需求的字面读法不同,理由都能在已安装的宿主代码里找到:

- 下拉卡片走 portal。 app frame 是 overflow: hidden,就地渲染的列表会被列的边缘裁掉。
portal 出去的卡片 z-index(1100)高于浮层的(20)。
- 悬停宽限在这里自己实现,而不是用 closeOnPointerLeave。 那个原语只把宽限接到它的触发包裹器上;列表一旦 portal 出去就没有东西取消它,指针一移到卡片上菜单就会关。持有的组件自己跑一个等效的 200ms 宽限,覆盖方块与卡片两者。
- 两个菜单用不同的卡片类名。 有几处效果只按类名找卡片——悬停宽限、避让的残留清理、 legacy 卡片打标——而且各自取第一个匹配。一张卡片一个类名会让它们全都猜错找到的是哪张。

许可

MIT
预览分享卡片长什么样 ↓shuing/dsh-sidebar-launcher
分享 · DeepSeek Harness Hub
shuing/dsh-sidebar-launcher

拖动悬浮球快速打开和管理侧边栏页签

这是你的仓库?认领条目

回到顶部