dsh-sidebar-suite 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,13 +1,40 @@
1
1
  # 更新日志 / Changelog
2
2
 
3
- > 版本号以 npm registry **实际发布记录**为准(当前:`0.0.0-stage`、`0.1.3`、`0.2.4`、`0.2.5`、`0.2.6`、`0.2.7`、`0.2.8`、`0.2.9`、`0.3.0`、`0.4.0`、`0.4.1`)。
4
- > ⚠️ **`0.3.0` 已弃用 / deprecated**:它会让 DSH 卡在 `Loading plugins…` 且主窗口不显示(见 0.4.0 章节「修复启动死循环」)。请升级到 `0.4.1` 或更高版本。`0.3.0` hangs DSH on the loading screen; upgrade to 0.4.1 or later.
3
+ > 版本号以 npm registry **实际发布记录**为准(当前:`0.0.0-stage`、`0.1.3`、`0.2.4`、`0.2.5`、`0.2.6`、`0.2.7`、`0.2.8`、`0.2.9`、`0.3.0`、`0.4.0`、`0.4.1`、`0.5.0`)。
4
+ > ⚠️ **`0.3.0` 已弃用 / deprecated**:它会让 DSH 卡在 `Loading plugins…` 且主窗口不显示(见 0.4.0 章节「修复启动死循环」)。请升级到 `0.5.0` 或更高版本。`0.3.0` hangs DSH on the loading screen; upgrade to 0.5.0 or later.
5
5
  > `0.2.0`、`0.2.1`、`0.2.2`、`0.2.3` 是开发过程中的本地迭代号,**从未单独发布**;它们的功能一次性随 `0.2.4` 上线,所以在下面合并记录。
6
6
  >
7
7
  > Versions mirror the npm registry. `0.2.0`–`0.2.3` were local iteration numbers that were never published on their own; all of their features shipped together in `0.2.4`.
8
8
 
9
9
  ---
10
10
 
11
+ ## 0.5.0 — 砍掉折叠布局、加入官方默认,并修好控制栏位置(2026-11-12)
12
+
13
+ **这次改了客户端样式表与布局状态机(`lib/client.js`)、配置 schema(`lib/index.js`)**,所以**升级后要从托盘彻底退出 DSH 再重开一次**;刷新 / 热重载不算。This release changes the injected stylesheet and the layout state machine; quit DSH from the tray and reopen once after upgrading.
14
+
15
+ > ⚠️ **破坏性变更 / Breaking**:`mode: a`(折叠)已移除,`getLayout().mode` 现在只返回 `b` / `c` / `n`,`Config.mode` 的合法值集合也变了,所以版本号跳到 `0.5.0`。旧配置里的 `mode: a` **不会报错、也不会变成另一个布局**:客户端每次读取都把它映射到新的「官方默认」`n`;`lib/index.js` 的 schema 仍保留 `a` 作为只读别名,只为让老 profile 通过校验。The removed `a` value is normalized to `n` on read.
16
+
17
+ ### 布局精简 / Fewer, clearer layouts
18
+
19
+ - **砍掉第一种布局 `a` 折叠** — 它用两个独立开关控制两组,能产生 4 种状态:两组都收起 = 空白侧栏(死状态)、两组都展开 = 与 DSH 原生一模一样(「点了等于没点」),剩下「只留一组」两态又已被 `b` 标签页完全覆盖。它没有任何「有用且独有」的状态,却额外带来一套与互斥胶囊语义冲突的双开关、以及窄侧栏里只能靠单/双箭头区分的两个图标。前两轮反馈的「按钮跑到列表下方」「展开合起来回跳」也都出在这套折叠按钮上。Removed: its four toggle states held two dead states and two states already covered by layout `b`.
20
+ - **新增第三种布局 `n` 官方默认(放在胶囊最后)** — 两组都常驻,插件对布局**零干预**:既不写 CSS 隐藏规则,运行时也不隐藏任何一组。想看回 DSH 原本的侧栏,一个按钮就能退回去。Added as the last pill option: both groups stay exactly as the host drew them, with no CSS or runtime override.
21
+ - **官方默认下工具条不会消失** — 该布局没有任何动作按钮(竖线与右侧按钮自动隐藏),但**布局胶囊始终保留**。这对 `showQuickSwitch: false` 的用户尤其重要:循环按钮本来就不显示,若连胶囊一起藏掉就只剩「去详情页改配置」一条退路,等于「进去了出不来」。The pill stays visible so there is always a way back.
22
+ - **三种布局的新顺序**:`b` 标签页 → `c` 浮层 → `n` 官方默认。出厂默认仍是 `b`(插件本就是为了消除抢高度),`n` 作为随时可退回的兜底。
23
+ - **控件与字典一并清理** — 删掉 `fold-ext` / `fold-ws` 两个按钮、`fold` / `fold2` 两个图标、`data-dsbs-fold-*` 两个 html 标志、4 组中英字典键,以及 `foldExt` / `foldWs` 两个运行时状态;详情页示意图改成「两组并排常驻」。teardown 仍会清理旧版本可能残留的那两个 html 属性。
24
+ - **对三方插件的影响** — `getLayout()` 的 `mode` 取值集合变化(见上)。消费方本来就不该写 `if (layout.mode === 'a')`;`registerTab()` 拿到的容器与懒挂载行为不变,官方默认下插件页被激活时仍按既定规则接管整列(那是消费方的显式动作,不是布局覆盖)。
25
+ - **验证** — 新增 `tests/test_036.mjs`:`MODES` 顺序、无死状态、官方默认下两组都不隐藏、胶囊始终可见、动作按钮与竖线隐藏、`mode: a` → `n` 的迁移,以及「点胶囊能切回来」这条防困死断言。受影响的历史测试(`test_025` / `029` / `031` / `033`)同步更新,`npm test` 扩为 14 套。
26
+
27
+ ### 顺带修好的控制栏位置(原计划的 0.4.2)
28
+
29
+ - **问题**:布局工具条(三个布局图标 + 收起箭头)出现在**整个插件列表的下方**;点右侧「收起扩展 / 收起工作区」箭头后位置还会变,展开与合起时上下跳,交互很别扭。The layout toolbar sat below the whole panel list and jumped around when a group was folded.
30
+ - **更正上一次的判断**:本条下面曾经写过「宿主折叠时把面板重挂到更深一层,`root` 变了所以控制栏被搬走」——**这个解释是错的**。解包 DSH `app.asar` 里的 `SidebarRoot.tsx` / `SidebarRoot.module.css` 后确认,宿主 JSX 结构固定,折叠分组并不会重挂 `scrollArea` 的两块面板。The earlier "re-parenting" explanation was wrong; the host JSX is stable.
31
+ - **真正的根因**:rc.2 的 `div.scrollArea` 是 CSS Grid,**四行** `grid-template-rows: 8px max-content 2px max-content`,宿主把 `nav.panelList` 显式钉在 `grid-row: 2`、`div.regionArea` 钉在 `grid-row: 4`。插件注入的节点没有指定行,只能被自动排到**隐式的新行(第 5 行及以后)**,也就是永远排在两个宿主面板之后。`0.4.1` 只补了 `grid-column:1/-1`(修好了 4px 挤压),没解决行;上一次尝试再加 `order:-30/-20/-10` 也无效 —— **`order` 无法覆盖宿主显式指定的 `grid-row`**。The host pins its panels to explicit grid rows, so unpositioned children are auto-placed after them; `order` cannot override an explicit row.
32
+ - **修法**:把控制栏与页签条**移出 grid**,放进它的父级 `div.scrollRegion`(始终是 flex 列,React 不会重建这一层)。在那里 DOM 顺序就是视觉顺序,展开(grid)与收起成图标条(宿主把 `scrollArea` 切成 flex 列)两种状态都一样。`resolveSidebar()` 新增 `scrollArea` / `headerHost` 两个解析结果,仍只匹配语义后缀、不硬编码 CSS-module 哈希。第三方 tab 的页面仍是 grid item,保留 `grid-column:1/-1` 与 4px/6px 内边距;浮层(布局 c)的定位基准同步从 `scrollArea` 改为 `scrollRegion`,否则会盖住工具条。The bar and tab strip now live in the flex header host; only a plugin page stays in the grid.
33
+ - **验证(这次是真机,不是猜)**:用独立 `--user-data-dir` 起了第二个 DSH 实例开了调试端口,**没有关闭或干扰用户正在用的窗口**。CDP 实测:`gridDisplay=grid`、`gridTemplateRows=8px 448px 2px 0px`、`navGridRow=2`、`regionGridRow=4`;修复后 `barParent=wtg9ga_scrollRegion`、`barIsGridItem=false`、工具条底边 172px 与 grid 顶边 172px 严丝合缝、`scrollRegion` 子节点顺序 `[BAR, TABS, scrollArea, scrollFade]`、`scrollArea` 里只剩宿主自己的两个面板并**未被污染**。截图确认工具条在「新会话」下方、插件列表上方,独占一行且无重叠。再依次点 `fold` / `fold2`、两组都隐藏、恢复,共 5 个状态采样,工具条父容器与 top/bottom **完全不变**。
34
+ - **回归测试**:`tests/dom-stub.mjs` 新增 `gridHost` 选项复刻真实的两层树;`tests/test_035.mjs` 重写为 23 项,专测「工具条不是 grid item、永远在滚动视口之前」;`tests/test_034.mjs` 的样式断言同步改为「只有第三方 tab 页面仍带 grid 规则」。**把 `lib/client.js` 临时回退到已发布的 0.4.1 跑同一套测试,12 项断言失败**(其中工具条被塞进 `scrollArea`、位置在列表之后),换回修复版全过。`npm test` 13 套全绿、`npm run check` 通过。
35
+
36
+ ---
37
+
11
38
  ## 0.4.1 — 适配 DSH 0.2.2-rc.2 的网格侧栏 / Fit the rc.2 grid sidebar(2026-11-12)
12
39
 
13
40
  **这次改了客户端样式表(`lib/client.js`)**,所以**升级后要托盘退出 DSH 再重开一次**;刷新 / 热重载不算。This release changes the injected stylesheet; quit DSH from the tray and reopen once after upgrading.
package/DETAIL.md CHANGED
@@ -1,18 +1,20 @@
1
1
  # 侧栏布局大师 · Sidebar Suite
2
2
 
3
- > **中文**:**这是一款专门针对改进 DSH 左侧栏体验的插件。** 左侧栏里「扩展」和「工作区」永远在抢同一块高度;装了几个插件之后,找一个入口要滚半天;项目一多,扩展就被挤没。它只解决这一处,但要把它做到**既强实用、又好看**:三种布局一键切换、**默认布局就在本页最上方**、会话列表默认全部展开、临时会话默认在退出 DSH 时删除(每天定时清理默认关,可在本页开启);界面全程沿用宿主原生配色、圆角与间距,不另起一套皮肤。
3
+ > **中文**:**这是一款专门针对改进 DSH 左侧栏体验的插件。** 左侧栏里「扩展」和「工作区」永远在抢同一块高度;装了几个插件之后,找一个入口要滚半天;项目一多,扩展就被挤没。它只解决这一处,但要把它做到**既强实用、又好看**:标签页 / 浮层 / 官方默认三种布局一键切换、**默认布局就在本页最上方**、会话列表默认全部展开、临时会话默认在退出 DSH 时删除(每天定时清理默认关,可在本页开启);界面全程沿用宿主原生配色、圆角与间距,不另起一套皮肤。
4
4
  >
5
5
  > **English**: **This is a plugin dedicated to improving the DSH sidebar experience.** The panels group and the workspaces group fight for the same vertical space; once you install a few plugins, finding anything means scrolling; add more projects and the panels vanish entirely. It fixes only that one surface, but fixes it properly — **seriously practical and native-looking**: three layouts in one click, the **startup-default picker at the very top of this page**, an always-expanded session list, temp sessions deleted when DSH quits by default (daily cleanup off unless enabled), and a UI built entirely from the host's own colours, radii and spacing instead of a custom skin.
6
6
 
7
7
  ---
8
8
 
9
- ## 三种布局 / Three layouts
9
+ ## 布局 / Layouts
10
10
 
11
11
  | 模式 / Mode | 中文 | English |
12
12
  | --- | --- | --- |
13
- | `a` 折叠 / Fold | 两组各自收起,想留哪块留哪块。 | Each group folds independently — keep exactly what you need. |
14
13
  | `b` 标签页 / Toggle (**默认 / default**) | 两组共用一栏,用顶部按钮互斥切换,侧栏再长也不打架。 | Both groups share one space; the bar swaps between them. The sidebar stays short. |
15
14
  | `c` 浮层 / Overlay | 工作区常驻,扩展以浮层盖在上面,随时展开/收起。 | Workspaces stay put; panels open as a floating layer you can show or hide. |
15
+ | `n` 官方默认 / Default | 两组都常驻,插件既不写隐藏样式也不在运行时隐藏任何一组。 | Both groups stay exactly as DSH drew them; no layout override at all. |
16
+
17
+ > **0.5.0 砍掉了旧的 `a` 折叠布局。** 它用两个独立开关控制两组,4 种组合里有 2 种是无效状态:两组都收起 = 空白侧栏,两组都展开 = 与官方默认完全相同;剩下「只留一组」两态又已被 `b` 标签页覆盖。控件语义也与「互斥胶囊」冲突,窄侧栏里两个箭头只能靠单/双折线区分。旧配置里的 `mode: a` 现在自动按官方默认打开。Removed because it had no useful, unique state and added two dead states.
16
18
 
17
19
  每张布局卡片都画了示意图,选中的那张标着「当前默认 / Current」。点一下就通过官方配置层写入,**立刻生效、下次启动照旧**。
18
20
  Each card draws a small diagram of its layout, and the selected one is marked as the current default. A click writes through the official config layer — **effective immediately and on every future launch**.
@@ -76,8 +78,8 @@ The report always carries: plugin version (read from `package.json`, so it canno
76
78
 
77
79
  ## 控制栏在哪 / Where the controls are
78
80
 
79
- 侧栏顶部是一条分段控件:`[折叠 | 标签页 | 浮层] │ 动作按钮`。前三个互斥,装在同一枚胶囊里,选中的那个白底高亮;竖线右边是当前布局专用的动作按钮(展开/折叠扩展、显示工作区、换标签页、浮出面板)。
80
- The toolbar is one segmented control: `[fold | tabs | overlay] │ actions`. The first three are mutually exclusive inside a single pill with the active one highlighted; right of the divider sit the buttons that act on the chosen layout.
81
+ 侧栏顶部是一条分段控件:`[标签页 | 浮层 | 官方默认] │ 动作按钮`。三个互斥,装在同一枚胶囊里,选中的那个白底高亮;竖线右边是当前布局专用的动作按钮(换标签页、浮出面板)。**官方默认没有任何动作按钮**(竖线一并隐藏),但胶囊保留 —— 这是 `showQuickSwitch: false` 时唯一的退出口。
82
+ The toolbar is one segmented control: `[toggle | overlay | default] │ actions`. The three are mutually exclusive inside a single pill with the active one highlighted; right of the divider sit the buttons that act on the chosen layout. Default has no actions, but the pill always stays visible so the user can switch back.
81
83
 
82
84
  `换布局 / Cycle` 默认不出现(`showQuickSwitch`),打开后和其它动作按钮共用同一套图标。`Cycle` is off by default and shares the icon set when enabled.
83
85
 
@@ -115,7 +117,7 @@ export function apply(ctx) {
115
117
  三种布局的差异 / Under the three layouts:
116
118
 
117
119
  - B 标签页:`[扩展][工作区][你的页…]` 并排,都是页。
118
- - A 折叠、C 浮层:**只有第三方页**进条;内置两组仍由原按钮控制,因为这两种布局不是页签模型。
120
+ - C 浮层、N 官方默认:**只有第三方页**进条;内置两组仍由宿主自己显示,因为这两种布局不是页签模型。官方默认下插件页被激活时仍会接管整列(这是消费方的显式动作,不是布局覆盖)。
119
121
  - 没有任何第三方注册:页签条本身带 `hidden`,不占高度、不参与布局,看到的和 0.2.x 一模一样(只是多一个隐藏的空容器)。With no consumer the strip carries `hidden`: zero height, no layout impact — visually identical to 0.2.x.
120
122
 
121
123
  选中态存 `localStorage["dsh-sidebar-suite:active-tab"]`,不写插件配置;再点当前页签退回原来的组。
@@ -137,9 +139,9 @@ For local development, point the same command at this folder or a packed `.tgz`.
137
139
  ## 已知限制 / Known limits
138
140
 
139
141
  - `[data-slot="sidebar.workspaces"]` 与 `panelList` 是宿主的半稳定约定;若宿主改版,插件会退化成「有控制栏但无效果」而不是抛错。**0.2.2-rc.2 实测**:根节点 `wtg9ga_scrollArea`、扩展面板 `nav.wtg9ga_panelList`、工作区容器 `wtg9ga_regionArea` —— CSS-module 哈希前缀 `wtg9ga_` 会在 DSH 重新构建时变,插件只匹配 `panelList` 这个语义后缀与上面那个 `data-slot`,从不硬编码完整类名;`tests/test_034.mjs` 会验证换哈希、纯哈希类名、以及缺少 workspaces 插槽时的行为。These host markup contracts are semi-stable; a host change degrades gracefully instead of throwing. On 0.2.2-rc.2 the real classes are `wtg9ga_scrollArea` / `nav.wtg9ga_panelList` / `wtg9ga_regionArea`; only the semantic suffix and the slot are matched, never the generated prefix.
140
- - **侧栏根节点可能是 flex,也可能是 grid(0.4.1)** — 0.2.2-rc.2 把 `wtg9ga_scrollArea` 从纵向 flex 改成三列 CSS Grid(`4px 264px 6px`),宿主的面板被显式钉在第 2 列。插件注入的控制栏 / 页签条 / 插件面板不声明 `grid-column`,就会默认落进只有 4px 宽的第 1 列,按钮溢出后盖住「工作区」标题。适配方式是三者统一 `grid-column:1/-1` 独占整行,再用 `padding-left:4px` / `padding-right:6px` 把装饰列还原成内边距、与宿主「新会话」左对齐 —— **不改宿主节点、不依赖生成类名、不做宿主版本嗅探**;`grid-column` 在旧 flex 根上是惰性属性,所以同一份样式两代宿主都能用。The host root may be a flex column (rc.1) or a three-column grid (rc.2); one spanning rule covers both without version sniffing, and the property is inert on older roots.
142
+ - **侧栏根节点可能是 flex,也可能是 grid(0.4.1 / 0.5.0)** — 0.2.2-rc.2 把 `wtg9ga_scrollArea` 从纵向 flex 改成三列 CSS Grid(`4px 264px 6px`),而且是**四行** `8px / max-content / 2px / max-content`:宿主把 `nav.panelList` 钉在 `grid-row:2`、`wtg9ga_regionArea` 钉在 `grid-row:4`。0.4.1 让注入节点 `grid-column:1/-1` 修好了 4px 挤压与标题重叠;但没有指定行的节点仍被自动排进隐式行、落在两个宿主面板**之后**,`order` 也压不过显式的 `grid-row`。所以控制栏与页签条改为放进 grid 的父级 `wtg9ga_scrollRegion`(稳定的 flex 列,React 不重建),DOM 顺序即视觉顺序;只有第三方 tab 页面仍留在 grid 里,保留跨列规则与 4px / 6px 内边距。**不改宿主节点、不依赖生成类名、不做版本嗅探**;旧 flex 根上这套定位同样成立。The host root may be flex (rc.1) or a grid with explicitly pinned rows (rc.2); the header lives in the flex wrapper above that grid, and only a plugin page still spans its columns.
141
143
  - 浮层定位依赖 `:has()`,需要 Chromium 内核(DSH 桌面端即是)。Overlay positioning uses `:has()`, so it needs a Chromium-based client.
142
- - 标签页模式会整块隐藏工作区区域,里面的其它入口也一并隐藏 —— 这是「一组一栏」的刻意设计。Toggle mode hides the whole workspaces region, including anything else the host renders inside it.
144
+ - 标签页模式会整块隐藏工作区区域,里面的其它入口也一并隐藏 —— 这是「一组一栏」的刻意设计;官方默认则两组都不隐藏。Toggle mode hides the whole workspaces region; the official default hides neither group.
143
145
 
144
146
  ## 临时会话 / Temporary session(0.2.0)
145
147
 
package/README.md CHANGED
@@ -5,16 +5,16 @@
5
5
  > 两个数字不是一回事:左边是 **npm 近 30 天安装量**;右边是 **GitHub Release 附件的下载总数** —— GitHub 只统计这个,`git clone` 和 npm 安装都不计入。附件是 2026-11-10 才补齐的,所以右边从 0 开始,每有一次真实下载才 +1。
6
6
  > The left badge counts npm installs (last 30 days); the right one counts downloads of **attached release files only** — clones and npm installs are not included. Assets were first attached on 2026-11-10, so that number starts at zero.
7
7
 
8
- > 让左侧栏从「抢地盘」变成「听你的」。三种布局、一键切换、零改动宿主结构,默认布局在插件详情页顶部直接改。
8
+ > 让左侧栏从「抢地盘」变成「听你的」。标签页 / 浮层 / 官方默认三种布局、一键切换、零改动宿主结构,默认布局在插件详情页顶部直接改。
9
9
  > Tame the DSH sidebar: three layouts, one click, zero DOM rewrites — set your startup default right at the top of the plugin page.
10
10
 
11
- **中文**:DSH 左侧栏把「扩展面板」和「工作区」堆在同一列,互相抢高度。本项目提供三种布局解决这个问题,并带来一个中英双语、带图形化设置的详情页。
11
+ **中文**:DSH 左侧栏把「扩展面板」和「工作区」堆在同一列,互相抢高度。本项目提供两种改写方案(标签页 / 浮层)与一个随时可退回的「官方默认」,并带来一个中英双语、带图形化设置的详情页。
12
12
  **English**: The panels group and the workspaces group compete for the same vertical space. This bundle offers three layouts for them and ships a bilingual detail page with a graphical settings picker.
13
13
 
14
14
  详情页长文档见 [DETAIL.md](DETAIL.md)(中英双语 / bilingual, 含收款码 / includes the payment QR)。
15
15
  The full detail document is [DETAIL.md](DETAIL.md).
16
16
 
17
- **一步步照着用**:[使用说明.md](使用说明.md) —— 安装、三种布局、临时会话、配置文件逐行说明、常见故障与反馈,中英双语随包附带(0.2.5 起)。
17
+ **一步步照着用**:[使用说明.md](使用说明.md) —— 安装、布局说明、临时会话、配置文件逐行说明、常见故障与反馈,中英双语随包附带(0.2.5 起)。
18
18
  **Step-by-step guide**: [使用说明.md](使用说明.md) — install, layouts, temp sessions, every config line, troubleshooting and feedback; shipped inside the npm package since 0.2.5.
19
19
 
20
20
  ## 这是一款什么插件 / What this plugin is
@@ -24,7 +24,7 @@ The full detail document is [DETAIL.md](DETAIL.md).
24
24
 
25
25
  | 设计取向 / Design goal | 落在哪 / What it means |
26
26
  | --- | --- |
27
- | **强实用性 / Seriously practical** | 三种布局一键切换、默认布局在详情页顶部直接改、会话列表默认全展开(不用再点「展开其余」)、「临时」开真会话且**默认在退出 DSH 时删除**(`DELETE_ON_EXIT=on`;**每天定时清理默认关**,需要时在详情页开启并设时间)、**临时会话可右键「保留为正式会话」或「立即删除」**(0.4.0)、详情页三种反馈入口 |
27
+ | **强实用性 / Seriously practical** | 三种布局(含官方默认)一键切换、默认布局在详情页顶部直接改、会话列表默认全展开(不用再点「展开其余」)、「临时」开真会话且**默认在退出 DSH 时删除**(`DELETE_ON_EXIT=on`;**每天定时清理默认关**,需要时在详情页开启并设时间)、**临时会话可右键「保留为正式会话」或「立即删除」**(0.4.0)、详情页三种反馈入口 |
28
28
  | **美观性 / Looks native** | 颜色与层级全取宿主设计变量、深浅主题自动跟随;细字号控制栏、迷你布局示意图、「临时」用 CSS 标签不破坏行高;不重构宿主 DOM、不弹窗、不另起一套皮肤 |
29
29
 
30
30
  完整的「强实用性 / 美观性」逐条说明见 [DETAIL.md](DETAIL.md) 的「这是一款什么插件」一节。
@@ -57,13 +57,15 @@ The point-by-point list of both design goals is in the "What this plugin is" sec
57
57
 
58
58
  > Report a bug, request a feature, or just say something feels off. Use **Issues** for bugs, **Discussions** for questions, **marketplace comments** for the channel the plugin market actually defines, or the **Feedback** box on the plugin detail page, which packs your version, OS, layout and last error into copyable text.
59
59
 
60
- ## 三种布局 / Three layouts
60
+ ## 布局 / Layouts
61
61
 
62
62
  | 模式 / Mode | 中文 | English |
63
63
  | --- | --- | --- |
64
- | `a` 折叠 / Fold | 两组各自收起。 | Each group folds on its own. |
65
64
  | `b` 标签页 / Toggle(默认 / default) | 两组共用一栏,标签互斥切换。 | The groups share one space; the bar switches between them. |
66
65
  | `c` 浮层 / Overlay | 工作区常驻,扩展以浮层展开。 | Workspaces stay put; panels open as an overlay. |
66
+ | `n` 官方默认 / Default | 两组都常驻,插件对布局零干预。 | Both groups stay visible — the official sidebar, with no override at all. |
67
+
68
+ > **0.5.0 移除了旧的 `a` 折叠布局**:两个独立开关能造出「空白侧栏」和「与 DSH 原生一模一样」两种无效状态,其余两态又已被 `b` 完全覆盖。旧配置里的 `mode: a` 会自动按 `n` 打开,无需手动改;`getLayout().mode` 现在只返回 `b` / `c` / `n`。The folded layout was removed in 0.5.0; old `a` configs now open in Default.
67
69
 
68
70
  ## 默认模式 / Default mode
69
71
 
@@ -98,8 +100,8 @@ Scan with WeChat Pay — **purely voluntary, no rewards promised**. The author a
98
100
 
99
101
  ## 控制栏 / Where the controls are
100
102
 
101
- 侧栏顶部是一条**分段控件**:三个互斥布局(折叠 / 标签页 / 浮层)装在同一枚灰色胶囊里,选中的那个是白底高亮;右侧隔一道竖线是当前布局专用的动作按钮(展开/折叠扩展、显示工作区、换标签页、浮出面板)。0.2.9 起全部换成矢量图标,窄侧栏只留图标(悬停有文字提示),侧栏宽于 300px 时动作按钮把文字长回来。
102
- The toolbar is one segmented control: the three mutually exclusive layouts share a single pill with the active one highlighted, and the buttons that act on the chosen layout sit on the far side of a divider. Since 0.2.9 the icons are real vectors — icon-only in a narrow column (with tooltips), and the action buttons grow their labels back once the column passes 300 px.
103
+ 侧栏顶部是一条**分段控件**:三个互斥布局(标签页 / 浮层 / 官方默认)装在同一枚灰色胶囊里,选中的那个是白底高亮;右侧隔一道竖线是当前布局专用的动作按钮(换标签页、浮出面板)。0.2.9 起全部换成矢量图标,窄侧栏只留图标(悬停有文字提示),侧栏宽于 300px 时动作按钮把文字长回来。**官方默认下没有动作按钮,但胶囊始终保留** —— 否则把 `showQuickSwitch` 关掉的用户就切不回来了。
104
+ The toolbar is one segmented control: the three mutually exclusive layouts share a single pill with the active one highlighted, and the buttons that act on the chosen layout sit on the far side of a divider. Since 0.2.9 the icons are real vectors — icon-only in a narrow column (with tooltips), and the action buttons grow their labels back once the column passes 300 px. The official default has no action buttons, but the pill always stays visible so there is a way back.
103
105
 
104
106
  `换布局 / Cycle` 默认不出现,可用 `showQuickSwitch: true` 打开;它和其余动作按钮一起走同一套图标。
105
107
  `Cycle` stays hidden unless `showQuickSwitch: true`; it uses the same icon set as the other action buttons.
@@ -139,10 +141,10 @@ export function apply(ctx) {
139
141
  | 布局 | 表现 |
140
142
  |---|---|
141
143
  | B 标签页 | 和控制栏下面那排「扩展 / 工作区」并排 |
142
- | A 折叠、C 浮层 | 只有插件页签进条;内置两组仍由原按钮控制(这两种布局本来就不是"页签"模型) |
144
+ | C 浮层、N 官方默认 | 只有插件页签进条;内置两组仍由宿主自己显示(这两种布局不是"页签"模型) |
143
145
  | 没人注册 | 这排页签**完全不出现**,界面和 0.2.x 一模一样 |
144
146
 
145
- 选中哪一页记在 `localStorage["dsh-sidebar-suite:active-tab"]`;**再点一次当前页签就退回原来的组**(布局 A / C 下这是唯一的退出口)。
147
+ 选中哪一页记在 `localStorage["dsh-sidebar-suite:active-tab"]`;**再点一次当前页签就退回原来的组**(布局 C / 官方默认下这是唯一的退出口)。
146
148
 
147
149
  服务其余方法:`unregisterTab(id)`、`getTabs()`、`activateTab(id)`、`getActiveTab()`、`subscribe(fn)`(`fn(tabs, reason, activeId)`)、`getLayout()`。
148
150
 
@@ -174,7 +176,7 @@ For local development, point the same command at this folder or a packed `.tgz`
174
176
  Every release ships a `.zip` source archive and an npm `.tgz`; point the install command at the downloaded `.tgz` for an offline install. GitHub counts downloads of these attached files.
175
177
 
176
178
  **已在 DSH 0.2.2-rc.2(desktop profile)真机验证 / Tested on DSH 0.2.2-rc.2 (desktop profile).**
177
- 2026-11-12 用 CDP 连上真机复核:服务发布、`[data-slot="sidebar.workspaces"]` 与 `nav.wtg9ga_panelList` 解析、控制栏 / 页签条 /「临时」按钮注入、第三方 tab 注册与懒挂载、Host 配置路由返回 200、root 子节点顺序稳定 —— **DOM 契约未变**。但 rc.2 把侧栏根节点从纵向 flex 换成了 CSS Grid(列宽 `4px 264px 6px`),只验节点是否注入、不验几何就会漏掉布局重叠:**0.4.1 已让注入的控制栏 / 页签条 / 插件面板 `grid-column:1/-1` 独占整行**。`0.2.2-rc.1` 也兼容(该属性在旧 flex 根上是惰性的),但已是旧版本。Verified against a live 0.2.2-rc.2 renderer: the markup contract is unchanged, but the host switched its sidebar root to CSS Grid, which 0.4.1 now spans correctly. The property is inert on the older flex root, so 0.2.2-rc.1 remains compatible but is superseded.
179
+ 2026-11-12 用 CDP 连上真机复核:服务发布、`[data-slot="sidebar.workspaces"]` 与 `nav.wtg9ga_panelList` 解析、控制栏 / 页签条 /「临时」按钮注入、第三方 tab 注册与懒挂载、Host 配置路由返回 200、root 子节点顺序稳定 —— **DOM 契约未变**。但 rc.2 把侧栏根节点从纵向 flex 换成了 CSS Grid(三列 `4px 264px 6px`、四行,且宿主面板被钉在 `grid-row:2` 与 `grid-row:4`),只验节点是否注入、不验几何就会漏掉布局问题:**0.4.1** 让注入节点 `grid-column:1/-1` 独占整行(修好按钮压住「工作区」标题);**未发布的 0.4.2** 进一步把控制栏 / 页签条移出 grid、放进父级 `scrollRegion` flex 列,否则它们会被自动排到隐式行、显示在整列列表下方。`0.2.2-rc.1` 也兼容(旧 flex 根上这套定位同样成立),但已是旧版本。Verified against a live 0.2.2-rc.2 renderer: the markup contract is unchanged, but the host pins its panels to explicit grid rows, so injected header boxes must live in the flex wrapper above the grid.
178
180
  本包刻意不声明 `engines.dsh`:当前 DSH 是预发布版,semver 默认规则会把常规版本范围误判为不兼容。
179
181
  This bundle deliberately omits `engines.dsh`: DSH is currently a prerelease, and semver's default rules would reject any ordinary range.
180
182
 
@@ -188,7 +190,7 @@ The client half is not hot-reloaded — fully quit and reopen DSH after installi
188
190
 
189
191
  - `[data-slot="sidebar.workspaces"]` 与 `panelList` 是半稳定宿主约定,变动时插件降级而不抛错。These host contracts are semi-stable; changes degrade gracefully.
190
192
  - 浮层定位依赖 `:has()`,需要 Chromium 内核。Overlay positioning needs `:has()` (Chromium-based client).
191
- - 标签页模式整块隐藏工作区区域,区域内其它入口也一并隐藏,这是刻意的「一组一栏」设计。Toggle hides the whole workspaces region, which is intentional.
193
+ - 标签页模式整块隐藏工作区区域,区域内其它入口也一并隐藏,这是刻意的「一组一栏」设计。官方默认则相反:两组都不隐藏。Toggle hides the whole workspaces region, which is intentional; the official default hides neither group.
192
194
 
193
195
  ## 会话列表默认全部展开 / Always-expanded session list(0.2.4)
194
196
 
package/lib/client.js CHANGED
@@ -5,9 +5,14 @@
5
5
  // (`[data-slot="sidebar.workspaces"]` inside its own region). They compete for
6
6
  // the same vertical space, so this plugin offers three layouts for them:
7
7
  //
8
- // a both groups fold independently
9
8
  // b the groups share one space and the control bar switches between them
10
9
  // c the workspaces stay put and the panels open as an overlay
10
+ // n official default: both groups stay visible, no override at all
11
+ //
12
+ // 0.5.0 removed the old `a` (independent folding). Its four toggle states
13
+ // included an empty sidebar and a state identical to the official default,
14
+ // while the two useful states were already covered by `b`. Profiles that still
15
+ // hold `mode: a` are normalized to `n` on read, never written back.
11
16
  //
12
17
  // All layout state lives in attributes on <html>, and the host's markup is
13
18
  // never restructured or moved: this plugin only ADDS one control bar as a
@@ -21,7 +26,7 @@ window.__ModuleLoader__.load({
21
26
  var ENTRY = 'dsh-sidebar-suite'
22
27
  // Reported to tab consumers so they can feature-detect. Kept in lockstep
23
28
  // with package.json by tests/test_030.mjs.
24
- var VERSION = '0.4.1'
29
+ var VERSION = '0.5.0'
25
30
  var NS = 'sidebar-suite'
26
31
  var LS_KEY = 'dsh-sidebar-suite:state'
27
32
  // Where the third-party tab choice lives. Kept out of LS_KEY on purpose:
@@ -61,27 +66,24 @@ window.__ModuleLoader__.load({
61
66
  // same <html> attributes, so the first live run owns the sidebar.
62
67
  var OWNER_KEY = '__dshSidebarSuiteOwner'
63
68
 
64
- var DEFAULTS = { mode: 'b', showQuickSwitch: true, tab: 'ext', foldExt: false, foldWs: false }
65
- var MODES = ['a', 'b', 'c']
69
+ var DEFAULTS = { mode: 'b', showQuickSwitch: true, tab: 'ext' }
70
+ // 0.5.0: the folded layout `a` is gone. Its id survives only as a read
71
+ // alias so old profiles migrate to a visually equivalent layout.
72
+ var MODES = ['b', 'c', 'n']
73
+ var LEGACY_MODES = { a: 'n' }
66
74
 
67
75
  var DICTS = {
68
76
  en: {
69
77
  'bar.label': 'Sidebar layout',
70
78
  'bar.local': 'Settings are stored on this device only (the official config layer is unavailable).',
71
- 'mode.a': 'Fold',
72
- 'mode.a.hint': 'Each group folds on its own.',
73
79
  'mode.b': 'Toggle',
74
80
  'mode.b.hint': 'The two groups share one space; choose which one is shown.',
75
81
  'mode.c': 'Overlay',
76
82
  'mode.c.hint': 'Workspaces stay visible; panels open as an overlay.',
83
+ 'mode.n': 'Default',
84
+ 'mode.n.hint': 'Both groups stay visible — the official sidebar, with no layout override.',
77
85
  'quick.label': 'Cycle',
78
86
  'quick.hint': 'Cycle the sidebar layout.',
79
- 'fold.ext.collapse': 'Hide panels',
80
- 'fold.ext.expand': 'Show panels',
81
- 'fold.ext.hint': 'Hide or show the panels group.',
82
- 'fold.ws.collapse': 'Hide workspaces',
83
- 'fold.ws.expand': 'Show workspaces',
84
- 'fold.ws.hint': 'Hide or show the workspaces group.',
85
87
  'tab.to.ext': 'Show panels',
86
88
  'tab.to.ext.hint': 'Switch the shared space to the panels group.',
87
89
  'tab.to.ws': 'Show workspaces',
@@ -94,7 +96,7 @@ window.__ModuleLoader__.load({
94
96
  'pop.close': 'Close panels',
95
97
  'pop.hint': 'Open or close the panels overlay.',
96
98
  'settings.title': 'Default layout on startup',
97
- 'settings.help': 'Pick the layout DSH starts with. This writes the plugin config; switching from the sidebar bar is session-only.',
99
+ 'settings.help': 'Pick the layout DSH starts with. This writes the plugin config; switching from the sidebar bar is session-only. The Fold layout was removed in 0.5.0; profiles still set to a now open in Default.',
98
100
  'settings.unavailable': 'The config service is unavailable right now, so the default cannot be changed here.',
99
101
  'settings.current': 'Current default',
100
102
  'temp.label': 'Temp',
@@ -131,20 +133,14 @@ window.__ModuleLoader__.load({
131
133
  zh: {
132
134
  'bar.label': '侧栏布局',
133
135
  'bar.local': '设置仅保存在本机(官方配置层不可用)。',
134
- 'mode.a': '折叠',
135
- 'mode.a.hint': '两组各自收起。',
136
136
  'mode.b': '标签页',
137
137
  'mode.b.hint': '两组共用一栏,用标签页切换显示。',
138
138
  'mode.c': '浮层',
139
139
  'mode.c.hint': '工作区常驻,扩展以浮层展开。',
140
+ 'mode.n': '官方默认',
141
+ 'mode.n.hint': '两组都常驻,插件不改动侧栏布局。',
140
142
  'quick.label': '换布局',
141
143
  'quick.hint': '循环切换侧栏布局。',
142
- 'fold.ext.collapse': '收起扩展',
143
- 'fold.ext.expand': '展开扩展',
144
- 'fold.ext.hint': '收起或展开扩展区。',
145
- 'fold.ws.collapse': '收起工作区',
146
- 'fold.ws.expand': '展开工作区',
147
- 'fold.ws.hint': '收起或展开工作区。',
148
144
  'tab.to.ext': '显示扩展',
149
145
  'tab.to.ext.hint': '把共用的一栏切换到扩展区。',
150
146
  'tab.to.ws': '显示工作区',
@@ -157,7 +153,7 @@ window.__ModuleLoader__.load({
157
153
  'pop.close': '收起扩展',
158
154
  'pop.hint': '打开或收起扩展浮层。',
159
155
  'settings.title': '启动时的默认布局',
160
- 'settings.help': '选择 DSH 启动时使用的布局。这里会写入插件配置;在侧栏上临时切换不会改动它。',
156
+ 'settings.help': '选择 DSH 启动时使用的布局。这里会写入插件配置;在侧栏上临时切换不会改动它。0.5.0 起旧的「折叠」布局已移除,配置里仍写 a 的会自动切到「官方默认」。',
161
157
  'settings.unavailable': '配置服务暂时不可用,无法在这里修改默认布局。',
162
158
  'settings.current': '当前默认',
163
159
  'temp.label': '临时',
@@ -206,7 +202,7 @@ window.__ModuleLoader__.load({
206
202
  'policy.help': '决定临时会话什么时候被清掉。默认:退出 DSH 时删除=开,每天定时清理=关;两个开关互不影响,改完立即生效,不用重启。 / Choose when temporary sessions are removed. Defaults: delete on quit = on, daily cleanup = off; the two switches are independent and apply immediately — no restart.',
207
203
  'settings.intro': '一款专门针对改进 DSH 左侧栏体验的插件 —— 强实用性 + 美观性:沿用宿主原生观感,不另起一套皮肤。 / A plugin dedicated to the DSH sidebar experience — seriously practical and good-looking, built from native host design tokens.',
208
204
  'settings.title': '启动时的默认布局 · Default layout on startup',
209
- 'settings.help': '选择 DSH 启动时使用的布局,这里会写入插件配置;在侧栏上临时切换只影响本次会话。 / Pick the layout DSH starts with — this writes the plugin config, while switching from the sidebar bar stays session-only.',
205
+ 'settings.help': '选择 DSH 启动时使用的布局,这里会写入插件配置;在侧栏上临时切换只影响本次会话。0.5.0 起旧的「折叠」布局已移除,配置文件里仍写着 a 的会自动切到「官方默认」。 / Pick the layout DSH starts with — this writes the plugin config, while switching from the sidebar bar stays session-only. The Fold layout was removed in 0.5.0; profiles still set to a now open in Default.',
210
206
  'settings.unavailable': '配置服务暂时不可用,无法在这里修改默认布局。 / The config service is unavailable right now, so the default cannot be changed here.',
211
207
  'settings.current': '当前默认 / Current',
212
208
  'settings.support': '喜欢这个插件?请作者喝杯咖啡 · Enjoy it? Buy me a coffee',
@@ -265,12 +261,12 @@ window.__ModuleLoader__.load({
265
261
  '[data-dsbs-bar] button[data-dsbs-btn="quick"]{margin-left:auto;color:var(--dsw-alias-label-tertiary,inherit);padding:2px 6px}',
266
262
  'html[data-dsbs-narrow="true"] [data-dsbs-btn="quick"]{display:none}',
267
263
  'html[data-dsbs-rail="true"] [data-dsbs-bar]{display:none}',
268
- // a — each group folds on its own
269
- 'html[data-dsbs-mode="a"][data-dsbs-fold-ext="true"] nav[data-dsbs-ext]{display:none}',
270
- 'html[data-dsbs-mode="a"][data-dsbs-fold-ws="true"] div:has(> [data-slot="sidebar.workspaces"]){display:none}',
271
264
  // b — the two groups are mutually exclusive
272
265
  'html[data-dsbs-mode="b"][data-dsbs-tab="ext"] div:has(> [data-slot="sidebar.workspaces"]){display:none}',
273
266
  'html[data-dsbs-mode="b"][data-dsbs-tab="ws"] nav[data-dsbs-ext]{display:none}',
267
+ // n — official default: deliberately NO rule. The point of this layout
268
+ // is that neither group is hidden; do not add a display declaration for
269
+ // data-dsbs-mode="n" here or in the runtime state machine below.
274
270
  // c — panels become an overlay above the workspaces
275
271
  'html[data-dsbs-mode="c"]:not([data-dsbs-pop="true"]) nav[data-dsbs-ext]{display:none}',
276
272
  'html[data-dsbs-mode="c"][data-dsbs-pop="true"] nav[data-dsbs-ext]{display:flex;flex-direction:column;gap:2px;position:absolute;z-index:60;left:4px;right:4px;top:var(--dsbs-pop-top,64px);max-height:60vh;overflow:auto;padding:6px;border-radius:10px;background:var(--dsw-alias-bg-overlay,var(--dsw-alias-bg-base,#fff));border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.3));box-shadow:0 12px 32px rgba(0,0,0,.24)}',
@@ -373,16 +369,12 @@ window.__ModuleLoader__.load({
373
369
  '[data-dsbs-seg] button[aria-pressed="false"]{opacity:.70}',
374
370
  '[data-dsbs-seg] button[aria-pressed="false"]:hover{opacity:.9;background:color-mix(in srgb,currentColor 9%,transparent)}',
375
371
  '[data-dsbs-bar] button[data-dsbs-glyph]::before{content:"";position:absolute;inset:0;margin:auto;width:16px;height:16px;background:currentColor;opacity:.88;-webkit-mask-image:var(--dsbs-icon);-webkit-mask-size:16px 16px;-webkit-mask-position:center;-webkit-mask-repeat:no-repeat;mask-image:var(--dsbs-icon);mask-size:16px 16px;mask-position:center;mask-repeat:no-repeat}',
376
- // a — one column of stacked rows
377
- '[data-dsbs-bar] [data-dsbs-glyph="a"]{--dsbs-icon:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 16 16%22 fill=%22none%22 stroke=%22%23000%22 stroke-width=%221.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22%3E%3Crect x=%222.5%22 y=%222.5%22 width=%2211%22 height=%2211%22 rx=%222.5%22/%3E%3Cpath d=%22M5.5 6.5h5M5.5 9.5h3%22/%3E%3C/svg%3E")}',
372
+ // n — official default: both stacked groups stay visible
373
+ '[data-dsbs-bar] [data-dsbs-glyph="n"]{--dsbs-icon:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 16 16%22 fill=%22none%22 stroke=%22%23000%22 stroke-width=%221.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22%3E%3Crect x=%222.5%22 y=%222.5%22 width=%2211%22 height=%2211%22 rx=%222.5%22/%3E%3Cpath d=%22M5.5 6.5h5M5.5 9.5h3%22/%3E%3C/svg%3E")}',
378
374
  // b — one column split down the middle
379
375
  '[data-dsbs-bar] [data-dsbs-glyph="b"]{--dsbs-icon:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 16 16%22 fill=%22none%22 stroke=%22%23000%22 stroke-width=%221.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22%3E%3Crect x=%222.5%22 y=%222.5%22 width=%2211%22 height=%2211%22 rx=%222.5%22/%3E%3Cpath d=%22M8 3v10%22/%3E%3C/svg%3E")}',
380
376
  // c — a panel floating above the column
381
377
  '[data-dsbs-bar] [data-dsbs-glyph="c"]{--dsbs-icon:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 16 16%22 fill=%22none%22 stroke=%22%23000%22 stroke-width=%221.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22%3E%3Crect x=%222.5%22 y=%226.5%22 width=%229%22 height=%227%22 rx=%221.8%22/%3E%3Cpath d=%22M7.2 6.5V4.4a1.6 1.6 0 011.6-1.6h3.1a1.6 1.6 0 011.6 1.6v3.9%22/%3E%3C/svg%3E")}',
382
- // fold — collapse one list (single chevron)
383
- '[data-dsbs-bar] [data-dsbs-glyph="fold"]{--dsbs-icon:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 16 16%22 fill=%22none%22 stroke=%22%23000%22 stroke-width=%221.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22%3E%3Cpath d=%22M3 3.5h10M8 6v6M5.6 9.6L8 12l2.4-2.4%22/%3E%3C/svg%3E")}',
384
- // fold all — two chevrons, so the two fold buttons never look alike
385
- '[data-dsbs-bar] [data-dsbs-glyph="fold2"]{--dsbs-icon:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 16 16%22 fill=%22none%22 stroke=%22%23000%22 stroke-width=%221.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22%3E%3Cpath d=%22M3 3h10M5.6 6.4L8 8.8l2.4-2.4M5.6 10.2L8 12.6l2.4-2.4%22/%3E%3C/svg%3E")}',
386
378
  // swap — trade the two tabs
387
379
  '[data-dsbs-bar] [data-dsbs-glyph="swap"]{--dsbs-icon:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 16 16%22 fill=%22none%22 stroke=%22%23000%22 stroke-width=%221.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22%3E%3Cpath d=%22M2.5 5.5h9.5M9.5 3.2l2.5 2.3-2.5 2.3%22/%3E%3Cpath d=%22M13.5 10.5H4M6.5 8.2l-2.5 2.3 2.5 2.3%22/%3E%3C/svg%3E")}',
388
380
  // pop — detach the panel
@@ -434,17 +426,24 @@ window.__ModuleLoader__.load({
434
426
  'html[data-dsbs-rail="true"] [data-dsbs-tabs] button{max-width:40px;padding:4px 5px}',
435
427
 
436
428
  // ---- grid-based sidebar roots (DSH 0.2.2-rc.2) --------------------
437
- // rc.2 turns the sidebar scroll area into a CSS grid with explicit
438
- // columns (sampled on a real renderer: "4px 264px 6px") and pins the
439
- // host's panels nav and region to column 2. Any child we inject with
440
- // the default `grid-column:auto` lands in that 4px first column: the
441
- // control bar becomes 4px wide while its buttons still need ~99px, so
442
- // they overflow on top of the host's "工作区" heading. Spanning every
443
- // column gives each injected header its own full-width row. On older
444
- // flex-column roots `grid-column` is inert, so both host layouts work.
445
- // The 4px/6px padding gives those two decorative grid columns back, so
446
- // the header lines up with the host's New Session button and groups.
447
- '[data-dsbs-bar],[data-dsbs-tabs],[data-dsbs-tabpanel]{grid-column:1/-1;min-width:0;max-width:100%;box-sizing:border-box;padding-left:4px;padding-right:6px}',
429
+ // rc.2 makes div.scrollArea a CSS grid with three explicit columns
430
+ // (4px | 1fr | 6px) AND four explicit rows: nav.panelList is pinned to
431
+ // grid-row 2, div.regionArea to grid-row 4. 0.4.1 only spanned the
432
+ // columns, which fixed the 4px squeeze but still left every injected
433
+ // child auto-placed in a new implicit row AFTER both host panels -- the
434
+ // control bar appeared below the whole list. `order` cannot override an
435
+ // explicit grid-row, so the bar and tab strip now live OUTSIDE the grid
436
+ // (in the flex column div.scrollRegion). Only a third-party tab page is
437
+ // still a grid item; it keeps the full-width span and the 4px/6px
438
+ // padding that gives the two decorative columns back.
439
+ '[data-dsbs-tabpanel]{grid-column:1/-1;min-width:0;max-width:100%;box-sizing:border-box;padding-left:4px;padding-right:6px}',
440
+ '[data-dsbs-bar],[data-dsbs-tabs]{min-width:0;max-width:100%;box-sizing:border-box;flex:none}',
441
+ // In the flex header host DOM order already is visual order. These are a
442
+ // belt-and-braces guard for a future host that grids that wrapper too:
443
+ // the injected header still precedes the scrolling viewport.
444
+ '[data-dsbs-bar]{order:-30}',
445
+ '[data-dsbs-tabs]{order:-20}',
446
+ '[data-dsbs-tabpanel]{order:-10}',
448
447
  ].join('\n')
449
448
 
450
449
  function assign(target) {
@@ -507,7 +506,47 @@ window.__ModuleLoader__.load({
507
506
  }
508
507
  }
509
508
  }
510
- return { root: root, nav: nav, regionArea: regionArea, workspaces: workspaces }
509
+
510
+ // DSH 0.2.2-rc.2 renders (see the host's SidebarRoot.tsx):
511
+ // div.scrollRegion flex column, never rebuilt by React
512
+ // > div.scrollArea CSS grid, 4 explicit rows
513
+ // > nav.panelList pinned to grid-row 2
514
+ // > div.regionArea pinned to grid-row 4
515
+ // A child injected into scrollArea without its own grid-row is
516
+ // auto-placed into a NEW implicit row AFTER both host panels, so the
517
+ // control bar shows up under the whole list. `order` cannot beat an
518
+ // explicit grid-row -- that is why the 0.4.2 `order` attempt failed.
519
+ // The reliable place for our header is scrollRegion, the flex column
520
+ // that owns scrollArea: DOM order is visual order there in BOTH the
521
+ // grid (expanded) and flex-column (rail) states, and nothing re-parents
522
+ // that wrapper when a group is folded. Semantic class suffixes only;
523
+ // the generated CSS-module hash prefix must never be hard-coded.
524
+ var scrollArea = null
525
+ var scan = root
526
+ while (scan && scan !== document.body && scan !== document.documentElement) {
527
+ if (scan.tagName === 'DIV' && /scrollArea/.test(String(scan.className || ''))) {
528
+ scrollArea = scan
529
+ break
530
+ }
531
+ scan = scan.parentElement
532
+ }
533
+ // Keep `root` pinned to the outer scrolling container even when a fold
534
+ // puts the nav one level deeper; popover geometry and the observer use it.
535
+ if (scrollArea && scrollArea !== root && scrollArea.contains(workspaces)
536
+ && (!nav || scrollArea.contains(nav))) {
537
+ root = scrollArea
538
+ }
539
+ var headerHost = null
540
+ if (scrollArea && scrollArea.parentElement
541
+ && /scrollRegion/.test(String(scrollArea.parentElement.className || ''))) {
542
+ headerHost = scrollArea.parentElement
543
+ }
544
+ // Pre-rc.2 hosts have no grid. With no headerHost the old flex root is
545
+ // still correct and the `order` rules keep the header in front there.
546
+ return {
547
+ root: root, scrollArea: scrollArea, headerHost: headerHost,
548
+ nav: nav, regionArea: regionArea, workspaces: workspaces
549
+ }
511
550
  }
512
551
 
513
552
  function readLocal() {
@@ -529,14 +568,21 @@ window.__ModuleLoader__.load({
529
568
  }
530
569
  }
531
570
 
571
+ // 0.5.0: `a` (independent folding) was removed. A profile that still has
572
+ // `mode: a` must not silently land on a different-looking layout, so every
573
+ // read maps it to the official default. The alias is never written back,
574
+ // never rendered as a button and never returned by getLayout().
575
+ function normalizeMode(raw) {
576
+ var mode = LEGACY_MODES[raw] || raw
577
+ return MODES.indexOf(mode) >= 0 ? mode : DEFAULTS.mode
578
+ }
579
+
532
580
  function adoptValues(raw) {
533
581
  var out = assign({}, DEFAULTS)
534
582
  if (!raw || typeof raw !== 'object') return out
535
- if (MODES.indexOf(raw.mode) >= 0) out.mode = raw.mode
583
+ out.mode = normalizeMode(raw.mode)
536
584
  if (typeof raw.showQuickSwitch === 'boolean') out.showQuickSwitch = raw.showQuickSwitch
537
585
  if (raw.tab === 'ext' || raw.tab === 'ws') out.tab = raw.tab
538
- if (typeof raw.foldExt === 'boolean') out.foldExt = raw.foldExt
539
- if (typeof raw.foldWs === 'boolean') out.foldWs = raw.foldWs
540
586
  return out
541
587
  }
542
588
 
@@ -703,6 +749,8 @@ window.__ModuleLoader__.load({
703
749
  // instead of sniffing classes off <html>.
704
750
  getLayout: function () {
705
751
  return {
752
+ // 0.5.0: 'b' (toggle), 'c' (overlay), 'n' (official default).
753
+ // 'a' no longer exists; old configs are normalized on read.
706
754
  mode: config.values.mode,
707
755
  tab: config.values.tab,
708
756
  activeTab: runtime.activeTab,
@@ -1545,10 +1593,13 @@ window.__ModuleLoader__.load({
1545
1593
  tempChatGuard = setInterval(syncChatMode, 1500)
1546
1594
  }
1547
1595
 
1548
- // 0.2.9: the three layouts are one segmented pill (they are mutually
1549
- // exclusive), and the actions that belong to the chosen layout sit after
1550
- // a divider. Glyphs are CSS pseudo elements, so makeButton() keeps owning
1551
- // the plain text node that relabel() rewrites.
1596
+ // The layouts are one segmented pill (mutually exclusive); the actions
1597
+ // belonging to the chosen layout sit after a divider. 0.5.0 removed the
1598
+ // two independent fold toggles, which could produce an empty sidebar.
1599
+ // The official default has no action at all, but the pill STAYS visible:
1600
+ // with showQuickSwitch off it is the only way back out of that layout.
1601
+ // Glyphs are CSS pseudo elements, so makeButton() keeps owning the plain
1602
+ // text node that relabel() rewrites.
1552
1603
  function buildBar() {
1553
1604
  var bar = document.createElement('div')
1554
1605
  bar.setAttribute('data-dsbs-bar', '')
@@ -1565,7 +1616,7 @@ window.__ModuleLoader__.load({
1565
1616
  var modeButton = makeButton('mode-' + mode, 'mode.' + mode, 'mode.' + mode + '.hint', (function (value) {
1566
1617
  return function () { setMode(value) }
1567
1618
  })(mode))
1568
- modeButton.setAttribute('data-dsbs-glyph', mode === 'a' ? 'a' : mode === 'b' ? 'b' : 'c')
1619
+ modeButton.setAttribute('data-dsbs-glyph', mode)
1569
1620
  seg.appendChild(modeButton)
1570
1621
  }
1571
1622
  bar.appendChild(seg)
@@ -1573,8 +1624,6 @@ window.__ModuleLoader__.load({
1573
1624
  var separator = makeSeparator()
1574
1625
  bar.appendChild(separator)
1575
1626
 
1576
- var foldExt = makeButton('fold-ext', 'fold.ext.collapse', 'fold.ext.hint', function () { setField('foldExt', !config.values.foldExt) })
1577
- var foldWs = makeButton('fold-ws', 'fold.ws.collapse', 'fold.ws.hint', function () { setField('foldWs', !config.values.foldWs) })
1578
1627
  var tabSwap = makeButton('tab-swap', 'tab.to.ws', 'tab.to.ws.hint', function () {
1579
1628
  setField('tab', config.values.tab === 'ext' ? 'ws' : 'ext')
1580
1629
  })
@@ -1587,21 +1636,14 @@ window.__ModuleLoader__.load({
1587
1636
  var index = MODES.indexOf(config.values.mode)
1588
1637
  setMode(MODES[(index + 1) % MODES.length])
1589
1638
  })
1590
- foldExt.setAttribute('data-dsbs-glyph', 'fold')
1591
- // Two fold buttons sit side by side in layout a, so they must not
1592
- // share a glyph — a single chevron vs. a double one is the only cue
1593
- // available once the labels collapse to icons.
1594
- foldWs.setAttribute('data-dsbs-glyph', 'fold2')
1595
1639
  tabSwap.setAttribute('data-dsbs-glyph', 'swap')
1596
1640
  pop.setAttribute('data-dsbs-glyph', 'pop')
1597
1641
  quick.setAttribute('data-dsbs-glyph', 'quick')
1598
- bar.appendChild(foldExt)
1599
- bar.appendChild(foldWs)
1600
1642
  bar.appendChild(tabSwap)
1601
1643
  bar.appendChild(pop)
1602
1644
  bar.appendChild(quick)
1603
1645
 
1604
- runtime.parts = { separator: separator, context: [foldExt, foldWs, tabSwap, pop] }
1646
+ runtime.parts = { separator: separator, context: [tabSwap, pop] }
1605
1647
  return bar
1606
1648
  }
1607
1649
 
@@ -1627,8 +1669,15 @@ window.__ModuleLoader__.load({
1627
1669
  }
1628
1670
 
1629
1671
  function ensureBar(fx) {
1672
+ // rc.2's scrollArea is a grid with explicitly pinned host rows, so the
1673
+ // bar lives one level up in scrollRegion (flex column). Targeting the
1674
+ // scrolling viewport as the anchor means "above the list" in every
1675
+ // fold state without ever becoming a grid item.
1676
+ var host = fx.headerHost || fx.root
1677
+ var anchor = (fx.scrollArea && fx.scrollArea.parentNode === host)
1678
+ ? fx.scrollArea : host.firstChild
1630
1679
  var bar = runtime.bar
1631
- if (bar && bar.parentNode !== fx.root) {
1680
+ if (bar && bar.parentNode !== host) {
1632
1681
  try {
1633
1682
  if (bar.parentNode) bar.parentNode.removeChild(bar)
1634
1683
  } catch (error) {
@@ -1640,12 +1689,13 @@ window.__ModuleLoader__.load({
1640
1689
  bar = buildBar()
1641
1690
  runtime.bar = bar
1642
1691
  }
1643
- var anchor = fx.nav || fx.regionArea
1644
- if (anchor && anchor.parentNode === fx.root) {
1645
- if (bar.parentNode !== fx.root || !isBefore(bar, anchor)) fx.root.insertBefore(bar, anchor)
1646
- } else if (bar.parentNode !== fx.root) {
1647
- fx.root.appendChild(bar)
1648
- }
1692
+ // Compare ORDER, never adjacency: the tab strip sits between the bar
1693
+ // and the anchor in the steady state. Demanding `nextSibling ===
1694
+ // anchor` moved a node on every paint and hung DSH in 0.3.0.
1695
+ if (anchor === bar) return bar
1696
+ if (bar.parentNode === host && anchor && anchor.parentNode === host
1697
+ && isBefore(bar, anchor)) return bar
1698
+ host.insertBefore(bar, anchor)
1649
1699
  return bar
1650
1700
  }
1651
1701
 
@@ -1907,8 +1957,17 @@ window.__ModuleLoader__.load({
1907
1957
  }
1908
1958
 
1909
1959
  function ensureTabStrip(fx) {
1960
+ // The strip shares the control bar's flex header host, staying out of
1961
+ // the grid for exactly the same reason.
1962
+ var host = fx.headerHost || fx.root
1963
+ var bar = runtime.bar
1964
+ // rc.2: all injected header boxes sit before the scrolling viewport.
1965
+ // Older flex roots have no scrollArea, so follow the bar directly.
1966
+ var anchor = (fx.scrollArea && fx.scrollArea.parentNode === host)
1967
+ ? fx.scrollArea
1968
+ : ((bar && bar.parentNode === host) ? bar.nextSibling : null)
1910
1969
  var strip = runtime.tabStrip
1911
- if (strip && strip.parentNode !== fx.root) {
1970
+ if (strip && strip.parentNode !== host) {
1912
1971
  try {
1913
1972
  if (strip.parentNode) strip.parentNode.removeChild(strip)
1914
1973
  } catch (error) {
@@ -1923,17 +1982,14 @@ window.__ModuleLoader__.load({
1923
1982
  strip.setAttribute('aria-label', t('tabs.label'))
1924
1983
  runtime.tabStrip = strip
1925
1984
  }
1926
- // Sits under the control bar so the two read as one header. A plugin
1927
- // panel may sit after it, so "immediately after the bar" is too strict
1928
- // a condition for the steady state: demanding an exact previousSibling
1929
- // moves the strip on every paint and spins the observer loop.
1930
- if (strip.parentNode === fx.root && (!runtime.bar || isBefore(runtime.bar, strip))) return strip
1931
- var anchor = runtime.bar && runtime.bar.parentNode === fx.root
1932
- ? runtime.bar.nextSibling
1933
- : (fx.nav || fx.regionArea)
1934
- if (anchor === strip) return strip
1935
- if (anchor && anchor.parentNode === fx.root) fx.root.insertBefore(strip, anchor)
1936
- else fx.root.appendChild(strip)
1985
+ // Steady state: in this host, after the bar, before the scrolling
1986
+ // viewport. Order checks only -- exact adjacency spins the observer.
1987
+ var inPlace = strip.parentNode === host
1988
+ && (!bar || bar.parentNode !== host || isBefore(bar, strip))
1989
+ && (!anchor || anchor.parentNode !== host || isBefore(strip, anchor))
1990
+ if (inPlace || anchor === strip) return strip
1991
+ if (anchor && anchor.parentNode === host) host.insertBefore(strip, anchor)
1992
+ else host.appendChild(strip)
1937
1993
  return strip
1938
1994
  }
1939
1995
 
@@ -2033,9 +2089,19 @@ window.__ModuleLoader__.load({
2033
2089
 
2034
2090
  function placeTabPanel(panel, fx) {
2035
2091
  if (panel.parentNode === fx.root) return
2036
- var anchor = fx.nav || fx.regionArea
2037
- if (anchor && anchor.parentNode === fx.root) fx.root.insertBefore(panel, anchor)
2038
- else fx.root.appendChild(panel)
2092
+ var anchor = (fx.nav && fx.nav.parentNode === fx.root) ? fx.nav
2093
+ : ((fx.regionArea && fx.regionArea.parentNode === fx.root) ? fx.regionArea : null)
2094
+ if (anchor) {
2095
+ fx.root.insertBefore(panel, anchor)
2096
+ return
2097
+ }
2098
+ // With the root pinned to the outer scroll area the host panels are no
2099
+ // longer direct children, so the old `appendChild` fallback put the
2100
+ // plugin page after everything. Keep it directly under the header.
2101
+ var after = (runtime.tabStrip && runtime.tabStrip.parentNode === fx.root)
2102
+ ? runtime.tabStrip.nextSibling
2103
+ : ((runtime.bar && runtime.bar.parentNode === fx.root) ? runtime.bar.nextSibling : null)
2104
+ fx.root.insertBefore(panel, after)
2039
2105
  }
2040
2106
 
2041
2107
  function syncTabPanels(fx, activeId) {
@@ -2157,9 +2223,13 @@ window.__ModuleLoader__.load({
2157
2223
  // the heartbeat stays cheap.
2158
2224
  if (config.values.mode !== 'c' || !runtime.popOpen) return
2159
2225
  try {
2160
- var rootRect = fx.root.getBoundingClientRect()
2226
+ // The overlay is position:absolute relative to scrollRegion (the
2227
+ // flex header host), not to scrollArea. With the bar now above the
2228
+ // grid, measuring against the grid would yield a negative offset
2229
+ // and clamp the popover on top of the control bar.
2230
+ var anchorRect = (fx.headerHost || fx.root).getBoundingClientRect()
2161
2231
  var barRect = runtime.bar.getBoundingClientRect()
2162
- var top = Math.round(barRect.bottom - rootRect.top + 4)
2232
+ var top = Math.round(barRect.bottom - anchorRect.top + 4)
2163
2233
  if (!isFinite(top) || top < 0) top = 0
2164
2234
  fx.root.style.setProperty('--dsbs-pop-top', top + 'px')
2165
2235
  } catch (error) {
@@ -2173,8 +2243,6 @@ window.__ModuleLoader__.load({
2173
2243
  html.setAttribute('data-dsbs-mode', state.mode)
2174
2244
  html.setAttribute('data-dsbs-tab', state.tab)
2175
2245
  html.setAttribute('data-dsbs-quick', state.showQuickSwitch ? 'on' : 'off')
2176
- setFlag(html, 'data-dsbs-fold-ext', state.foldExt === true)
2177
- setFlag(html, 'data-dsbs-fold-ws', state.foldWs === true)
2178
2246
  setFlag(html, 'data-dsbs-pop', runtime.popOpen && state.mode === 'c')
2179
2247
  setFlag(html, 'data-dsbs-rail', runtime.rail)
2180
2248
 
@@ -2202,21 +2270,14 @@ window.__ModuleLoader__.load({
2202
2270
  // With no panels to show, the buttons that act on that group are
2203
2271
  // pointless, and hiding the workspaces would leave an empty column.
2204
2272
  var hasExt = !!fx.nav
2205
- var showFold = state.mode === 'a'
2206
2273
  var showTabs = state.mode === 'b'
2207
2274
  var showPop = state.mode === 'c'
2208
- setVisible('fold-ext', showFold && hasExt)
2209
- setVisible('fold-ws', showFold)
2210
2275
  setVisible('tab-swap', showTabs && hasExt)
2211
2276
  setVisible('pop', showPop && hasExt)
2212
2277
  setVisible('quick', state.showQuickSwitch === true)
2213
2278
 
2214
- // The label states what the click will do, so "收起扩展" and "展开扩展"
2279
+ // The label states what the click will do, so "展开扩展" and "收起扩展"
2215
2280
  // never coexist with a stale state.
2216
- var foldExtButton = barButton('fold-ext')
2217
- if (foldExtButton) foldExtButton.setAttribute('data-dsbs-label', state.foldExt ? 'fold.ext.expand' : 'fold.ext.collapse')
2218
- var foldWsButton = barButton('fold-ws')
2219
- if (foldWsButton) foldWsButton.setAttribute('data-dsbs-label', state.foldWs ? 'fold.ws.expand' : 'fold.ws.collapse')
2220
2281
  var popButton = barButton('pop')
2221
2282
  if (popButton) popButton.setAttribute('data-dsbs-label', runtime.popOpen && showPop ? 'pop.close' : 'pop.open')
2222
2283
  var tabSwapButton = barButton('tab-swap')
@@ -2244,8 +2305,6 @@ window.__ModuleLoader__.load({
2244
2305
  var modeButton = barButton('mode-' + MODES[i])
2245
2306
  if (modeButton) modeButton.setAttribute('aria-pressed', state.mode === MODES[i] ? 'true' : 'false')
2246
2307
  }
2247
- if (foldExtButton) foldExtButton.setAttribute('aria-expanded', state.foldExt ? 'false' : 'true')
2248
- if (foldWsButton) foldWsButton.setAttribute('aria-expanded', state.foldWs ? 'false' : 'true')
2249
2308
  if (popButton) {
2250
2309
  popButton.setAttribute('aria-expanded', runtime.popOpen && showPop ? 'true' : 'false')
2251
2310
  }
@@ -2277,16 +2336,20 @@ window.__ModuleLoader__.load({
2277
2336
  if (pluginTab) {
2278
2337
  extHidden = true
2279
2338
  wsHidden = true
2280
- } else if (state.mode === 'a') {
2281
- extHidden = state.foldExt === true
2282
- wsHidden = state.foldWs === true
2283
2339
  } else if (state.mode === 'b') {
2284
2340
  extHidden = state.tab !== 'ext'
2285
2341
  // No panels: keep the workspaces visible rather than the empty half.
2286
2342
  wsHidden = hasExt && state.tab === 'ext'
2287
- } else {
2343
+ } else if (state.mode === 'c') {
2288
2344
  extHidden = !runtime.popOpen
2289
2345
  wsHidden = false
2346
+ } else {
2347
+ // n — official default. No CSS rule hides either group, and no
2348
+ // runtime state may hide one either: both stay exactly as DSH drew
2349
+ // them. A registered plugin tab still takes the column (that is an
2350
+ // explicit consumer action, not a layout override).
2351
+ extHidden = false
2352
+ wsHidden = false
2290
2353
  }
2291
2354
  setGroupHidden(fx.nav, 'ext', extHidden)
2292
2355
  setGroupHidden(fx.regionArea, 'ws', wsHidden)
@@ -2370,8 +2433,6 @@ window.__ModuleLoader__.load({
2370
2433
  runtime.railSaved = {
2371
2434
  mode: config.values.mode,
2372
2435
  tab: config.values.tab,
2373
- foldExt: config.values.foldExt,
2374
- foldWs: config.values.foldWs,
2375
2436
  }
2376
2437
  } else if (runtime.railSaved) {
2377
2438
  // Leaving the rail restores what the user had before the column was
@@ -2560,7 +2621,7 @@ window.__ModuleLoader__.load({
2560
2621
  try {
2561
2622
  var snapshot = controller && controller.getSnapshot && controller.getSnapshot()
2562
2623
  var value = snapshot && snapshot.value
2563
- return MODES.indexOf(value && value.mode) >= 0 ? value.mode : DEFAULTS.mode
2624
+ return normalizeMode(value && value.mode)
2564
2625
  } catch (error) {
2565
2626
  return DEFAULTS.mode
2566
2627
  }
@@ -2625,12 +2686,13 @@ window.__ModuleLoader__.load({
2625
2686
  }, extra || {}) }, first || null, second || null)
2626
2687
  }
2627
2688
  function mini(mode) {
2628
- if (mode === 'a') {
2629
- return frame(null, box({ width: '100%', height: '7px' }), box({ width: '100%', height: '7px' }))
2630
- }
2631
2689
  if (mode === 'b') {
2632
2690
  return frame(null, box({ width: '60%', height: '4px' }), box({ width: '100%', height: '11px' }))
2633
2691
  }
2692
+ if (mode === 'n') {
2693
+ // Official default: two full-width rows, nothing hidden or overlaid.
2694
+ return frame(null, box({ width: '100%', height: '7px' }), box({ width: '100%', height: '7px' }))
2695
+ }
2634
2696
  return frame(null,
2635
2697
  box({ width: '100%', height: '100%', opacity: '0.35' }),
2636
2698
  box({
@@ -3555,6 +3617,8 @@ window.__ModuleLoader__.load({
3555
3617
  var attributes = [
3556
3618
  'data-dsbs-mode',
3557
3619
  'data-dsbs-tab',
3620
+ // Legacy flags from <= 0.4.1. Clear them if an older client run
3621
+ // left them behind; 0.5.0 never sets these itself.
3558
3622
  'data-dsbs-fold-ext',
3559
3623
  'data-dsbs-fold-ws',
3560
3624
  'data-dsbs-pop',
package/lib/index.js CHANGED
@@ -27,6 +27,8 @@ const DEFAULTS = {
27
27
  mode: 'b',
28
28
  showQuickSwitch: true,
29
29
  tab: 'ext',
30
+ // Legacy keys from <= 0.4.1. The Client half no longer reads them, but they
31
+ // stay in the schema so profiles written before 0.5.0 still validate.
30
32
  foldExt: false,
31
33
  foldWs: false,
32
34
  }
@@ -135,14 +137,17 @@ export const Config = schema === null
135
137
  ? undefined
136
138
  : schema.object({
137
139
  mode: schema
138
- .union(['a', 'b', 'c'])
140
+ // 0.5.0: `a` is a removed legacy value. It stays in the union only so
141
+ // profiles written before 0.5.0 still validate; the Client half maps it
142
+ // to `n` on every read and never writes it back.
143
+ .union(['b', 'c', 'n', 'a'])
139
144
  .default(DEFAULTS.mode)
140
- .description('Sidebar layout: a folds each group, b switches the two groups with tabs, c keeps the workspaces and opens the panels as an overlay.')
145
+ .description('Sidebar layout: b switches the two groups with tabs, c keeps the workspaces and opens the panels as an overlay, n is the official default with no overrides. `a` is a removed legacy alias mapped to n.')
141
146
  .volatile(),
142
147
  showQuickSwitch: schema
143
148
  .boolean()
144
149
  .default(DEFAULTS.showQuickSwitch)
145
- .description('Show the button that cycles through the three layouts in the sidebar control bar.')
150
+ .description('Show the button that cycles through the sidebar layouts in the control bar.')
146
151
  .volatile(),
147
152
  tab: schema
148
153
  .union(['ext', 'ws'])
@@ -152,12 +157,12 @@ export const Config = schema === null
152
157
  foldExt: schema
153
158
  .boolean()
154
159
  .default(DEFAULTS.foldExt)
155
- .description('Folded layout: whether the panels group starts folded.')
160
+ .description('Legacy, unused since 0.5.0. Kept so older profiles still validate.')
156
161
  .volatile(),
157
162
  foldWs: schema
158
163
  .boolean()
159
164
  .default(DEFAULTS.foldWs)
160
- .description('Folded layout: whether the workspaces group starts folded.')
165
+ .description('Legacy, unused since 0.5.0. Kept so older profiles still validate.')
161
166
  .volatile(),
162
167
  })
163
168
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-sidebar-suite",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "license": "MIT",
5
5
  "author": "Yonan",
6
6
  "repository": {
@@ -14,7 +14,7 @@
14
14
  "publishConfig": {
15
15
  "access": "public"
16
16
  },
17
- "description": "一款专门针对改进 DSH 左侧栏体验的插件:三种布局一键切换、默认布局在详情页顶部直接设、会话列表默认全展开、「临时会话」开原生会话且默认在退出 DSH 时删除(DELETE_ON_EXIT 默认开;每天定时清理默认关,可在详情页开启并自设时间)、详情页三种反馈入口、临时会话可右键「保留为正式会话」或「立即删除」、全部 9 项配置都有图形开关且改文件免重启 —— 强实用性;界面沿用宿主原生配色、圆角与间距,只加属性与一条控制栏、不重构宿主结构 —— 克制美观。|A plugin dedicated to the DSH sidebar experience: one-click layouts, always-expanded sessions, native temp sessions deleted when DSH quits by default (DELETE_ON_EXIT on; daily cleanup off by default and opt-in) in-page settings/feedback, a right-click menu to keep or delete one temp session, a control for every config key and hot-reloaded config edits — seriously practical; native colours, radii and spacing with zero host-DOM rewrites — restrained and good-looking.",
17
+ "description": "一款专门针对改进 DSH 左侧栏体验的插件:标签页 / 浮层 / 官方默认三种布局一键切换、默认布局在详情页顶部直接设、会话列表默认全展开、「临时会话」开原生会话且默认在退出 DSH 时删除(DELETE_ON_EXIT 默认开;每天定时清理默认关,可在详情页开启并自设时间)、详情页三种反馈入口、临时会话可右键「保留为正式会话」或「立即删除」、全部 9 项配置都有图形开关且改文件免重启 —— 强实用性;界面沿用宿主原生配色、圆角与间距,只加属性与一条控制栏、不重构宿主结构 —— 克制美观。|A plugin dedicated to the DSH sidebar experience: one-click layouts (toggle, overlay, official default), always-expanded sessions, native temp sessions deleted when DSH quits by default (DELETE_ON_EXIT on; daily cleanup off by default and opt-in) in-page settings/feedback, a right-click menu to keep or delete one temp session, a control for every config key and hot-reloaded config edits — seriously practical; native colours, radii and spacing with zero host-DOM rewrites — restrained and good-looking.",
18
18
  "keywords": [
19
19
  "dsh-plugin",
20
20
  "dsh",
@@ -27,7 +27,7 @@
27
27
  ],
28
28
  "type": "module",
29
29
  "scripts": {
30
- "test": "node tests/test_020.mjs && node tests/test_022.mjs && node tests/test_023.mjs && node tests/test_024.mjs && node tests/test_025.mjs && node tests/test_028.mjs && node tests/test_029.mjs && node tests/test_030.mjs && node tests/test_031.mjs && node tests/test_032.mjs && node tests/test_033.mjs && node tests/test_034.mjs",
30
+ "test": "node tests/test_020.mjs && node tests/test_022.mjs && node tests/test_023.mjs && node tests/test_024.mjs && node tests/test_025.mjs && node tests/test_028.mjs && node tests/test_029.mjs && node tests/test_030.mjs && node tests/test_031.mjs && node tests/test_032.mjs && node tests/test_033.mjs && node tests/test_034.mjs && node tests/test_035.mjs && node tests/test_036.mjs",
31
31
  "check": "node --check lib/index.js && node --check lib/client.js && node --check lib/temp-session-host.mjs && npm pack --dry-run"
32
32
  },
33
33
  "main": "lib/index.js",
@@ -1,8 +1,10 @@
1
1
  # dsh-sidebar-suite 使用说明
2
2
 
3
- **适用插件版本:v0.4.1**(最后更新 2026-11-12)
3
+ **适用插件版本:v0.5.0**(最后更新 2026-11-12)
4
4
 
5
- **已验证的 DSH:0.2.2-rc.2(当前最新)**;`0.2.2-rc.1` 也兼容,但已是旧版本。DSH 从 rc.2 起把侧栏根节点换成了 CSS Grid,**v0.4.1 已做好样式适配**;如果你在 rc.2 上还用着 v0.4.0,会看到布局按钮压住「工作区」标题 —— 升级到 v0.4.1 并从托盘彻底退出 DSH 再打开一次即可。
5
+ **已验证的 DSH:0.2.2-rc.2(当前最新)**;`0.2.2-rc.1` 也兼容,但已是旧版本。DSH 从 rc.2 起把侧栏根节点换成了 CSS Grid,**v0.5.0 已完整适配**,布局工具条也固定在「新会话」下方、不再跑到列表底部。
6
+
7
+ > ⚠️ **v0.5.0 移除了旧的「折叠」布局**:胶囊里现在是 `标签页 / 浮层 / 官方默认` 三项。配置文件里若还写着 `mode: a`,**不用手动改** —— 插件会自动当作「官方默认」打开;下次在详情页点一下布局卡片就会写成新值。`0.3.0` 已弃用别装;`0.4.1` 及更早版本仍可能看到布局按钮压住标题或跑到列表下方,升级到 v0.5.0 后**从托盘彻底退出 DSH 再打开一次**即可。
6
8
 
7
9
  **这是一款专门针对改进 DSH 左侧栏体验的插件** —— 只管左侧栏,不碰会话内容、不换模型;设计上既要「强实用性」(少点几下、少滚几屏),也要「美观性」(沿用 DSH 原生观感,不另起一套皮肤)。
8
10
 
@@ -59,9 +61,11 @@
59
61
 
60
62
  | 模式 | 名字 | 长什么样 | 适合 |
61
63
  | --- | --- | --- | --- |
62
- | `a` | 折叠 | 两组各自可以收起/展开 | 只用一个,另一组收起来 |
63
64
  | `b` | 标签页(**出厂默认**) | 两组共用一栏,点标签互斥切换 | 侧栏窄、想省地方 |
64
65
  | `c` | 浮层 | 工作区常驻,扩展面板浮在上面 | 经常两个都要看 |
66
+ | `n` | 官方默认 | 两组都常驻,插件不改布局 | 想看回 DSH 原本的侧栏 |
67
+
68
+ > `a`(折叠)从 v0.5.0 起已移除:两个独立开关能造出「空白栏」和「跟原生一模一样」两种没用的状态,其余两态又和标签页重复。旧配置里的 `a` 会自动按官方默认打开。
65
69
 
66
70
  ### 3.2 临时切一下(只影响这次)
67
71
 
@@ -69,14 +73,14 @@
69
73
 
70
74
  ```
71
75
  ┌─────────────────┐
72
- │ ▤ ▥ ▢ │ │ ⇄ 显示扩展
76
+ │ ▥ ▢ ▤ │ │ ⇄ 换标签页
73
77
  └─────────────────┘
74
- 折叠 标签页 浮层 动作按钮
78
+ 标签页 浮层 官方默认 动作按钮
75
79
  ```
76
80
 
77
- - 点胶囊里的三个图标 = 切换布局(`▤` 折叠 / `▥` 标签页 / `▢` 浮层)。**只对本次会话有效**,下次启动回到默认值。
81
+ - 点胶囊里的三个图标 = 切换布局(`▥` 标签页 / `▢` 浮层 / `▤` 官方默认)。**只对本次会话有效**,下次启动回到默认值。
78
82
  - 每个按钮鼠标悬停都会显示文字说明,不用记图标。
79
- - 竖线右边的按钮 = 在当前布局下切换「看哪一组」;侧栏拖宽超过 300px 后,这些按钮会把文字显示出来。
83
+ - 竖线右边的按钮 = 在当前布局下切换「看哪一组」;侧栏拖宽超过 300px 后,这些按钮会把文字显示出来。**选「官方默认」时右侧没有动作按钮,胶囊也照样在**,随时能点回来。
80
84
  - 侧栏很窄(< 300px)时只显示图标;再窄(< 100px,折成图标条)时连图标都会精简。
81
85
 
82
86
  > 故意不做记忆:临时切换不写盘、不写 localStorage,避免你在窄屏随手一点把默认布局改坏了。
@@ -92,30 +96,30 @@
92
96
  **路线 B(改文件)**
93
97
 
94
98
  - 文件:`C:\Users\12691\.dsh\profiles\desktop\cordis.patch.yml`
95
- - 第 **721** 行,原文:
99
+ - 用记事本打开,找到 `dsh-sidebar-suite` 那一块的 `config:`,里面有这一行(本机在第 **715** 行;行号会随 profile 内容变化,以 `mode:` 这几个字为准):
96
100
 
97
101
  ```yaml
98
102
  mode: b
99
103
  ```
100
104
 
101
- - 改成 `mode: a` 或 `mode: c`
105
+ - 改成 `mode: c`(浮层)或 `mode: n`(官方默认);`b` 是标签页。
102
106
  - 保存 → 彻底退出 DSH → 重开(这里改的是 `cordis.patch.yml`,不是插件设置卡;改插件设置不用重启)
103
107
 
104
- 顺手看下第 **717–725** 行,这一块现在长这样(**只改第 721 行**,其它别动):
108
+ 这一块现在长这样(**只改 `mode:` 那一行**,其它别动):
105
109
 
106
110
  ```yaml
107
- 717 - id: dsh-sidebar-suite
108
- 718 disabled: false
109
- 719 name: dsh-sidebar-suite
110
- 720 config:
111
- 721 mode: b # ← 只改这一行:a = 折叠, b = 标签页, c = 浮层
112
- 722 showQuickSwitch: false
113
- 723 tab: ws
114
- 724 foldExt: true
115
- 725 foldWs: true
111
+ - id: dsh-sidebar-suite
112
+ disabled: false
113
+ name: dsh-sidebar-suite
114
+ config:
115
+ mode: b # ← 只改这一行:b = 标签页, c = 浮层, n = 官方默认
116
+ showQuickSwitch: false
117
+ tab: ws
118
+ foldExt: true # 旧版遗留值,v0.5.0 起不再读取,留着不影响
119
+ foldWs: false # 同上
116
120
  ```
117
121
 
118
- (`showQuickSwitch: false` 就是「换布局」那个按钮不显示;想让它出现就改成 `true`。)
122
+ (`showQuickSwitch: false` 就是「换布局」那个按钮不显示;想让它出现就改成 `true`。哪怕它是关的,布局胶囊也一直在,随时能切回来。)
119
123
 
120
124
  ---
121
125
 
@@ -135,8 +139,8 @@
135
139
  | 你选的布局 | 页签条长什么样 |
136
140
  |---|---|
137
141
  | 标签页(默认) | `扩展 / 工作区 / 插件页` 三种并排 |
138
- | 折叠 | 只有插件页进条;扩展和工作区还是用原来那两个折叠按钮 |
139
142
  | 浮层 | 只有插件页进条;扩展还是用「浮出面板」按钮 |
143
+ | 官方默认 | 只有插件页进条;两组都常驻,没有布局动作按钮 |
140
144
 
141
145
  **一个都没装的话,这排页签根本不会出现** —— 界面和 0.2.x 一模一样,不会平白多一行东西。
142
146
 
@@ -458,7 +462,7 @@ C:\Users\12691\.dsh\backups\20261110-cookie-pileup\ ← 含 Cookies.bak 和
458
462
  | **想看用户反馈到哪了** | [本仓库 Issues](https://github.com/YONAN1269/dsh-sidebar-suite/issues) / [github.com/notifications](https://github.com/notifications);市场评论见 9.1 |
459
463
  | **在插件市场里留言** | 详情页「反馈」框的「**插件市场评论区**」按钮 |
460
464
  | **会话不想再点「展开其余」** | 默认已经全部展开;想恢复折叠见 5.5 |
461
- | 换布局(这次) | 侧栏顶部点 `折叠 / 标签页 / 浮层` |
465
+ | 换布局(这次) | 侧栏顶部点 `标签页 / 浮层 / 官方默认` |
462
466
  | 换布局(以后每次) | 插件 → dsh-sidebar-suite → 详情页最上面三张卡片 |
463
467
  | **设临时会话什么时候自动删** | 详情页「**临时会话自动删除**」那块的两个开关(见 5.1) |
464
468
  | **让它每天凌晨 3 点清一次** | 同上:开「每天定时清理」,时间选 `03:00` |