dsh-up-session 1.0.2 → 1.0.4

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,10 +1,26 @@
1
1
  # 更新日志
2
2
 
3
+ ## 1.0.4 - 2026-11-07
4
+
5
+ ### 新增
6
+
7
+ - 置顶区和最近区的会话行可打开宿主会话菜单:右键或键盘菜单键可置顶、重命名、分叉、复制 Session ID、在文件管理器中显示和归档。菜单项与工作区列表中的同一条会话一致。
8
+
9
+ ### 修复
10
+
11
+ - 将 DSH 最低兼容版本提高到 `0.2.2-alpha.3`,避免旧版 DSH 因缺少会话菜单接口而在安装插件后无法启动。
12
+
13
+ ## 1.0.3 - 2026-10-26
14
+
15
+ ### 变更
16
+
17
+ - 置顶区和最近区并入「工作区」列表顶部,成为工作区列表的前两个分组,随列表一起滚动;使用工作区搜索时暂时让位给搜索结果。
18
+
3
19
  ## 1.0.2 - 2026-10-26
4
20
 
5
21
  ### 修复
6
22
 
7
- - 调整兼容范围:>=DSH 0.2.1-rc.2
23
+ - DSH 兼容版本改为 `>=0.2.1-rc.2`。
8
24
 
9
25
  ## 1.0.1 - 2026-10-24
10
26
 
package/README.en.md CHANGED
@@ -6,16 +6,18 @@
6
6
 
7
7
  [简体中文](./README.md) | English
8
8
 
9
- A simple DeepSeek Harness sidebar plugin: it inserts two sections, "Pinned" and "Recent", above the "Workspaces" list, gathering pinned sessions from all workspaces into one place and collecting your latest sessions into another.
9
+ A simple DeepSeek Harness sidebar plugin: it inserts two groups, "Pinned" and "Recent", at the top of the "Workspaces" list, gathering pinned sessions from all workspaces into one place and collecting your latest sessions into another.
10
10
 
11
11
  <p align="center">
12
- <img src="./assets/screenshot.png" width="32%" alt="The sidebar showing the Pinned and Recent sections above the Workspaces list">
12
+ <img src="./assets/screenshot.png" width="32%" alt="The sidebar showing the Pinned and Recent groups at the top of the Workspaces list">
13
13
  <img src="./assets/settings.png" width="64%" alt="The plugin settings popup with toggles for Show Pinned, Show Recent, and Reveal the session in its workspace">
14
14
  </p>
15
15
 
16
16
  ## Installation and compatibility
17
17
 
18
- The DSH compatibility range is `>=0.2.1-rc.2`, allowing 0.2.1-rc.2, later prereleases of that version, and higher stable versions to install; functionality still needs to be verified against the DSH version in use.
18
+ **Upgrade to DSH 0.2.2-alpha.3 or later. This plugin uses the session menu API introduced in that version.**
19
+
20
+ Plugin version 1.0.4 requires DSH `>=0.2.2-alpha.3`. Installing it on an earlier DSH version may prevent DSH from starting.
19
21
 
20
22
  **Web**
21
23
 
@@ -29,19 +31,39 @@ Restart DSH after installation. If you use a custom profile, replace `web` with
29
31
 
30
32
  Open **Plugins** in the sidebar, click **Add plugin**, enter `dsh-up-session`, and click **Enable now** after installation.
31
33
 
34
+ ### Installing 1.0.3 on older DSH versions
35
+
36
+ If your DSH version is older than 0.2.2-alpha.3 and falls within plugin version 1.0.3's compatibility range (`>=0.2.1-rc.2`), install `dsh-up-session@1.0.3`.
37
+
38
+ **Web**
39
+
40
+ ```sh
41
+ dsh plugin --profile web add dsh-up-session@1.0.3
42
+ ```
43
+
44
+ Restart DSH after installation. If you use a custom profile, replace `web` with its name.
45
+
46
+ **Desktop**
47
+
48
+ Open **Plugins** in the sidebar, click **Add plugin**, enter `dsh-up-session@1.0.3`, and click **Enable now** after installation.
49
+
32
50
  ## Pinned section
33
51
 
34
52
  Gathers the pinned sessions of every workspace, newest activity first, and collapses like the host's own sections. Hovering a row reveals its archive and unpin buttons.
35
53
 
54
+ Right-clicking a pinned or recent row, or focusing it and pressing the menu key or Shift+F10, opens the same session menu as a row in the workspace list: pin or unpin, rename, fork, copy the Session ID, reveal in the file manager, and archive. The items are the host's own contributions, so they stay in step with the same session further down the list. Rename is prefilled with the session's own title; an untitled session is not prefilled with the "Untitled" label.
55
+
56
+ Both Pinned and Recent sit at the top of the workspaces list as its first two groups and scroll together with the workspace groups below. While the workspace search is open, they step aside so the list can show the results.
57
+
36
58
  ## Recent section
37
59
 
38
- Sits between the pinned section and the workspaces, lists the sessions that are not pinned, newest activity first, and shows the latest five by default. It looks and collapses exactly like the pinned section. Once there are more than five sessions, a "Show more" button appears at the bottom of the list:
60
+ Sits between the pinned section and the workspace rows, lists the sessions that are not pinned, newest activity first, and shows the latest five by default. It looks and collapses exactly like the pinned section. Once there are more than five sessions, a "Show more" button appears at the bottom of the list:
39
61
 
40
62
  - each click reveals five more sessions,
41
63
  - once every session is on screen the button reads "Show less",
42
64
  - clicking "Show less" folds the list back to the latest five.
43
65
 
44
- Hovering a row reveals its archive and pin buttons. Pinned sessions appear only in the pinned section, so no conversation is listed twice.
66
+ Hovering a row reveals its archive and pin buttons. The context menu is the same as in the pinned section. Pinned sessions appear only in the pinned section, so no conversation is listed twice.
45
67
 
46
68
  ## Settings
47
69
 
package/README.md CHANGED
@@ -6,16 +6,18 @@
6
6
 
7
7
  简体中文 | [English](./README.en.md)
8
8
 
9
- 一个简单的 DeepSeek Harness 侧边栏插件:在「工作区」列表上方插入「置顶」和「最近」两个区,把分散在各个工作区里的置顶会话汇总到一处,也把最近的会话收拢到一处。
9
+ 一个简单的 DeepSeek Harness 侧边栏插件:在「工作区」列表顶部插入「置顶」和「最近」两个分组,把分散在各个工作区里的置顶会话汇总到一处,也把最近的会话收拢到一处。
10
10
 
11
11
  <p align="center">
12
- <img src="./assets/screenshot.png" width="32%" alt="侧边栏「工作区」列表上方依次显示置顶区与最近区">
12
+ <img src="./assets/screenshot.png" width="32%" alt="侧边栏「工作区」列表顶部依次显示置顶区与最近区">
13
13
  <img src="./assets/settings.png" width="64%" alt="插件设置弹窗,包含显示置顶、显示最近和展开工作区中的原会话三个开关">
14
14
  </p>
15
15
 
16
16
  ## 安装与兼容
17
17
 
18
- DSH 兼容版本声明为 `>=0.2.1-rc.2`,允许 0.2.1-rc.2、同版本号下更新的预发布版,以及更高的正式版安装;具体功能仍需与所用 DSH 版本验证。
18
+ **建议升级到 DSH 0.2.2-alpha.3 以上版本,本插件使用该版本提供的会话菜单接口。**
19
+
20
+ 插件 1.0.4 的 DSH 兼容版本为 `>=0.2.2-alpha.3`,在更早的 DSH 版本上安装可能导致 DSH 无法启动。
19
21
 
20
22
  **Web 端**
21
23
 
@@ -29,19 +31,39 @@ dsh plugin --profile web add dsh-up-session
29
31
 
30
32
  打开侧边栏「插件」,点击「添加插件」,输入 `dsh-up-session`,安装完成后点击「立即启用」。
31
33
 
34
+ ### 在旧版 DSH 上安装 1.0.3
35
+
36
+ 如果你的 DSH 版本早于 0.2.2-alpha.3,且符合插件 1.0.3 的兼容范围 `>=0.2.1-rc.2`,请指定版本安装 `dsh-up-session@1.0.3`。
37
+
38
+ **Web 端**
39
+
40
+ ```sh
41
+ dsh plugin --profile web add dsh-up-session@1.0.3
42
+ ```
43
+
44
+ 安装后重启 DSH。如果使用自定义 profile,将命令中的 `web` 替换为对应名称。
45
+
46
+ **桌面端**
47
+
48
+ 打开侧边栏「插件」,点击「添加插件」,输入 `dsh-up-session@1.0.3`,安装完成后点击「立即启用」。
49
+
32
50
  ## 置顶区
33
51
 
34
52
  汇总所有工作区里被置顶的会话,按最近更新时间排序,可展开收起。每行悬停时出现归档和取消置顶两个按钮。
35
53
 
54
+ 在置顶区或最近区的会话行上右键,或聚焦该行后按菜单键 / Shift+F10,会打开与工作区列表相同的会话菜单(置顶或取消置顶、重命名、分叉会话、复制 Session ID、在文件管理器中显示、归档)。菜单项由宿主注册,因此和下面工作区里的同一条会话保持一致。重命名对话框预填的是会话自己的标题,未命名会话不会被填成「未命名」。
55
+
56
+ 置顶区和最近区都位于「工作区」列表顶部,是该列表的前两个分组,会和下方的工作区分组一起滚动;使用「工作区」的搜索功能时,两个区暂时隐藏,把列表让给搜索结果。
57
+
36
58
  ## 最近区
37
59
 
38
- 位于置顶区与工作区之间,按最近更新时间排序显示尚未置顶的会话,默认显示最新的 5 条,样式与置顶区一致,同样可以展开收起。会话超过 5 条时,列表底部出现「展开更多」:
60
+ 位于置顶区与工作区列表之间,按最近更新时间排序显示尚未置顶的会话,默认显示最新的 5 条,样式与置顶区一致,同样可以展开收起。会话超过 5 条时,列表底部出现「展开更多」:
39
61
 
40
62
  - 每点一次「展开更多」再显示 5 条;
41
63
  - 全部会话都显示完后,按钮变为「收起」;
42
64
  - 点击「收起」把列表收回到最新的 5 条。
43
65
 
44
- 每行悬停时出现归档和置顶两个按钮。已置顶的会话只出现在置顶区,两区不会重复显示同一个会话。
66
+ 每行悬停时出现归档和置顶两个按钮。右键菜单与置顶区相同。已置顶的会话只出现在置顶区,两区不会重复显示同一个会话。
45
67
 
46
68
  ## 设置
47
69
 
package/client.js CHANGED
@@ -7,8 +7,8 @@ window.__ModuleLoader__.load({
7
7
 
8
8
  /** Dictionary namespace owned by this plugin. */
9
9
  const NS = 'upSession';
10
- /** Services required to read pin state, render, and pin, unpin, or archive. */
11
- const inject = ['slots', 'locale', 'uiWorkspace'];
10
+ /** Services required to read pin state, render, pin, unpin, archive, and open the host session menu. */
11
+ const inject = ['slots', 'locale', 'uiWorkspace', 'contextMenus'];
12
12
 
13
13
  /** Simplified Chinese dictionary and key source of truth. */
14
14
  const zh = {
@@ -150,7 +150,7 @@ window.__ModuleLoader__.load({
150
150
  // twice, so the section copies the real geometry instead of predicting it.
151
151
  '.dshUS-mount{box-sizing:border-box;flex:none;display:flex;flex-direction:column;gap:4px;',
152
152
  'padding-left:4px;margin:0 0 4px -4px}',
153
- '.dshUS-mount[data-rail="true"]{display:none}',
153
+ '.dshUS-mount[data-rail="true"],.dshUS-mount[data-searching="true"]{display:none}',
154
154
  '.dshUS-root{box-sizing:border-box;display:flex;flex-direction:column;gap:2px}',
155
155
  // The header copies the host's "Workspaces" section header: same box, same
156
156
  // 14px/20px label, same tertiary label colour. The whole row is the toggle,
@@ -204,7 +204,7 @@ window.__ModuleLoader__.load({
204
204
  'border:0;text-align:left;width:100%}',
205
205
  '.dshUS-row:hover{background:var(--dsw-alias-interactive-bg-hover)}',
206
206
  // Matches the sidebar's own selected row (same surface, same label weight).
207
- '.dshUS-current{background:var(--dsw-alias-interactive-bg-hover);',
207
+ '.dshUS-current,.dshUS-menuOpen{background:var(--dsw-alias-interactive-bg-hover);',
208
208
  'color:var(--dsw-alias-label-primary)}',
209
209
  '.dshUS-row:focus-visible{outline:var(--dsw-focus-ring-width,2px) solid ',
210
210
  'var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary,#4d6bfe));outline-offset:-2px}',
@@ -290,7 +290,8 @@ window.__ModuleLoader__.load({
290
290
  'color:var(--dsw-alias-label-caption,currentColor)}',
291
291
  '.dshUS-row:hover .dshUS-actions,',
292
292
  '.dshUS-row:focus-visible .dshUS-actions,',
293
- '.dshUS-row:focus-within .dshUS-actions{display:inline-flex}',
293
+ '.dshUS-row:focus-within .dshUS-actions,',
294
+ '.dshUS-menuOpen .dshUS-actions{display:inline-flex}',
294
295
  '.dshUS-action:hover{color:var(--dsw-alias-label-primary)}',
295
296
  '.dshUS-action:focus-visible{outline:var(--dsw-focus-ring-width,2px) solid ',
296
297
  'var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary,#4d6bfe));outline-offset:1px}',
@@ -303,7 +304,9 @@ window.__ModuleLoader__.load({
303
304
  '.dshUS-row:focus-within .dshUS-time,',
304
305
  '.dshUS-row:hover .dshUS-pinMark,',
305
306
  '.dshUS-row:focus-visible .dshUS-pinMark,',
306
- '.dshUS-row:focus-within .dshUS-pinMark{display:none}',
307
+ '.dshUS-row:focus-within .dshUS-pinMark,',
308
+ '.dshUS-menuOpen .dshUS-time,',
309
+ '.dshUS-menuOpen .dshUS-pinMark{display:none}',
307
310
  // A touch surface never hovers, so the actions stay visible there and the
308
311
  // time and marker stay hidden with them.
309
312
  '@media (hover:none){.dshUS-actions{display:inline-flex}',
@@ -544,6 +547,8 @@ window.__ModuleLoader__.load({
544
547
  const NO_STATUSES = new Map();
545
548
  /** Stand-in for a host without the status hook, so the sections still render. */
546
549
  const useNoSessionStatus = (selector) => selector(NO_STATUSES);
550
+ /** Used when the host has no context-menu target store; the row still opens the menu. */
551
+ const useNoContextMenuTarget = () => undefined;
547
552
 
548
553
  /**
549
554
  * The pending-interaction kind the sidebar renders, or undefined for a kind
@@ -917,6 +922,8 @@ window.__ModuleLoader__.load({
917
922
  return {
918
923
  id: summary.id,
919
924
  title: summary.displayTitle || untitled,
925
+ // The host menu's rename draft is the raw title, not the untitled label.
926
+ menuTitle: summary.blank ? '' : summary.title?.trim() ?? '',
920
927
  workspace: owner?.title ?? '',
921
928
  // A known owner names its Workspace; once the list has loaded, a Session
922
929
  // no Workspace accounts for sits in the "Ungrouped" bucket (`''`).
@@ -1009,8 +1016,8 @@ window.__ModuleLoader__.load({
1009
1016
  /**
1010
1017
  * The root of the plugin's sidebar surface: the pinned section, the recent
1011
1018
  * section, and the one archive confirmation both sections can raise. It is
1012
- * rendered through a portal into the host node the plugin keeps just above
1013
- * the workspace slot's anchor.
1019
+ * rendered through a portal into the host node the plugin keeps at the top
1020
+ * of the workspace list, inside the "Workspaces" section.
1014
1021
  * @param props - composed slot props plus the injected host and actions.
1015
1022
  * @returns the portal, or null when there is nowhere to portal into.
1016
1023
  */
@@ -1028,6 +1035,8 @@ window.__ModuleLoader__.load({
1028
1035
  useSessionStatus,
1029
1036
  usePanelInfo,
1030
1037
  settingsEvents,
1038
+ openMenu,
1039
+ useContextMenuTarget,
1031
1040
  } = props;
1032
1041
  const pinnedSessionIds = useWorkspaces((state) => state.pinnedSessionIds);
1033
1042
  const archivedSessionIds = useWorkspaces((state) => state.archivedSessionIds);
@@ -1149,10 +1158,18 @@ window.__ModuleLoader__.load({
1149
1158
  t,
1150
1159
  });
1151
1160
 
1161
+ // The host menu's captured target, so the row that opened it stays marked
1162
+ // after the pointer leaves. Undefined until a session menu is open.
1163
+ const readMenuTarget = useContextMenuTarget ?? useNoContextMenuTarget;
1164
+ const menuSessionId = readMenuTarget(
1165
+ (target) => (target?.kind === 'session' ? target.value.sessionId : undefined),
1166
+ );
1152
1167
  const shared = {
1153
1168
  currentId,
1154
1169
  onOpen: openRow,
1155
1170
  onArchive: raiseArchive,
1171
+ onOpenMenu: openMenu,
1172
+ menuSessionId,
1156
1173
  onOpenSettings: () => setSettingsOpen(true),
1157
1174
  settingsOpen,
1158
1175
  t,
@@ -1240,29 +1257,62 @@ window.__ModuleLoader__.load({
1240
1257
  * keyboard focus. A pinned row keeps the host's own pin marker where the
1241
1258
  * time would end, so a pinned row looks pinned at rest exactly as the
1242
1259
  * sidebar's rows do.
1243
- * @param props - the row, the current Session id, the open handler, the trailing buttons, the pin marker, and the translator.
1260
+ * @param props - the row, the current Session id, the open handler, the host menu opener, the trailing buttons, the pin marker, and the translator.
1244
1261
  * @returns the row element.
1245
1262
  */
1246
- function SessionRow({ row, currentId, onOpen, actions, pinned, t }) {
1263
+ function SessionRow({ row, currentId, onOpen, onOpenMenu, menuOpen, actions, pinned, t }) {
1247
1264
  const now = Date.now();
1248
1265
  // A live status is the row's own leading cell, exactly as the sidebar
1249
1266
  // draws it; an idle row draws none, so quiet rows keep the section's left
1250
1267
  // edge instead of the sidebar's deeper indent.
1251
1268
  const statusLabel = row.status === undefined ? undefined : statusText(row.status, t);
1269
+ // Same capture the host's session rows use: right-click, the Context Menu
1270
+ // key, or Shift+F10 open the shared session menu at the pointer or the row.
1271
+ const showMenu = (point, event) => {
1272
+ if (typeof onOpenMenu !== 'function') return;
1273
+ event.preventDefault();
1274
+ event.stopPropagation();
1275
+ if (event.currentTarget instanceof HTMLElement) event.currentTarget.focus({ preventScroll: true });
1276
+ onOpenMenu(row.id, row.menuTitle ?? '', point);
1277
+ };
1278
+ const className = ['dshUS-row', row.id === currentId ? 'dshUS-current' : '', menuOpen ? 'dshUS-menuOpen' : '']
1279
+ .filter(Boolean)
1280
+ .join(' ');
1252
1281
  return h(
1253
1282
  'div',
1254
1283
  {
1255
- className: row.id === currentId ? 'dshUS-row dshUS-current' : 'dshUS-row',
1284
+ className,
1256
1285
  role: 'button',
1257
1286
  tabIndex: 0,
1258
1287
  'aria-current': row.id === currentId ? 'true' : undefined,
1288
+ 'aria-haspopup': 'menu',
1259
1289
  title: row.workspace === '' ? row.title : `${row.title} · ${row.workspace}`,
1260
1290
  'aria-label':
1261
1291
  statusLabel === undefined
1262
1292
  ? t('openNamed', { title: row.title })
1263
1293
  : t('openNamedStatus', { title: row.title, status: statusLabel }),
1264
1294
  onClick: () => onOpen(row),
1295
+ onContextMenu: (event) => {
1296
+ const keyboard = event.clientX === 0 && event.clientY === 0;
1297
+ const rect = keyboard ? event.currentTarget.getBoundingClientRect() : undefined;
1298
+ showMenu(
1299
+ { x: rect?.left ?? event.clientX, y: rect?.bottom ?? event.clientY },
1300
+ event,
1301
+ );
1302
+ },
1265
1303
  onKeyDown: (event) => {
1304
+ if (
1305
+ !event.repeat &&
1306
+ !event.nativeEvent.isComposing &&
1307
+ !event.ctrlKey &&
1308
+ !event.altKey &&
1309
+ !event.metaKey &&
1310
+ (event.key === 'ContextMenu' || (event.key === 'F10' && event.shiftKey))
1311
+ ) {
1312
+ const rect = event.currentTarget.getBoundingClientRect();
1313
+ showMenu({ x: rect.left, y: rect.bottom }, event);
1314
+ return;
1315
+ }
1266
1316
  if (event.key !== 'Enter' && event.key !== ' ') return;
1267
1317
  event.preventDefault();
1268
1318
  onOpen(row);
@@ -1337,6 +1387,8 @@ window.__ModuleLoader__.load({
1337
1387
  onOpen,
1338
1388
  onArchive,
1339
1389
  onUnpin,
1390
+ onOpenMenu,
1391
+ menuSessionId,
1340
1392
  onOpenSettings,
1341
1393
  settingsOpen,
1342
1394
  t,
@@ -1368,6 +1420,8 @@ window.__ModuleLoader__.load({
1368
1420
  row,
1369
1421
  currentId,
1370
1422
  onOpen,
1423
+ onOpenMenu,
1424
+ menuOpen: menuSessionId === row.id,
1371
1425
  t,
1372
1426
  // Every row here is pinned, so every row wears the marker.
1373
1427
  pinned: true,
@@ -1399,6 +1453,8 @@ window.__ModuleLoader__.load({
1399
1453
  onOpen,
1400
1454
  onArchive,
1401
1455
  onPin,
1456
+ onOpenMenu,
1457
+ menuSessionId,
1402
1458
  onOpenSettings,
1403
1459
  settingsOpen,
1404
1460
  t,
@@ -1447,6 +1503,8 @@ window.__ModuleLoader__.load({
1447
1503
  row,
1448
1504
  currentId,
1449
1505
  onOpen,
1506
+ onOpenMenu,
1507
+ menuOpen: menuSessionId === row.id,
1450
1508
  t,
1451
1509
  // No row here is pinned, so no row wears the marker and the
1452
1510
  // time is the row's trailing cell, flush with its edge.
@@ -1838,11 +1896,32 @@ window.__ModuleLoader__.load({
1838
1896
  }
1839
1897
 
1840
1898
  /**
1841
- * Keep the host node immediately before the workspace slot's anchor. The
1842
- * workspaces seat lives in the sidebar's column flex container, so a sibling
1843
- * inserted there takes its own space and pushes the browser down instead of
1844
- * covering it. Both sections share that node and stack inside it. Hiding in
1845
- * the collapsed rail mirrors the shell's own rule.
1899
+ * The scroll box holding the workspace list. In the grouped and flat views
1900
+ * the `role="tree"` box scrolls itself; while searching, the results tree
1901
+ * sits one level inside that same box, so the nearest scrolling ancestor is
1902
+ * the seat either way. The sections live at the top of this box, inside the
1903
+ * "Workspaces" section, and scroll away with the workspace rows instead of
1904
+ * taking a fixed slice of the sidebar above them.
1905
+ * @param anchor - the Workspaces slot anchor.
1906
+ * @returns the scroll box, or null while the sidebar renders no list.
1907
+ */
1908
+ function workspaceScrollBox(anchor) {
1909
+ const tree = anchor.querySelector('[role="tree"]');
1910
+ if (!(tree instanceof Element)) return null;
1911
+ for (let node = tree; node !== null && node !== anchor; node = node.parentElement) {
1912
+ const { overflowY } = getComputedStyle(node);
1913
+ if (overflowY === 'auto' || overflowY === 'scroll') return node;
1914
+ }
1915
+ return null;
1916
+ }
1917
+
1918
+ /**
1919
+ * Keep the host node at the top of the workspace list. Both sections share
1920
+ * that node and stack inside it, so they read as the first two groups of
1921
+ * the "Workspaces" section rather than as sections of their own. Hiding in
1922
+ * the collapsed rail mirrors the shell's own rule, and the list cannot hold
1923
+ * the sections while the user is searching its rows, so they step aside for
1924
+ * the results.
1846
1925
  * @param host - the container the portal renders into.
1847
1926
  * @returns the disposer that detaches the node and its observers.
1848
1927
  */
@@ -1852,12 +1931,25 @@ window.__ModuleLoader__.load({
1852
1931
 
1853
1932
  const place = () => {
1854
1933
  const anchor = document.querySelector('[data-slot="sidebar.workspaces"]');
1855
- if (!(anchor instanceof Element)) return;
1934
+ if (!(anchor instanceof Element)) {
1935
+ host.remove();
1936
+ return;
1937
+ }
1856
1938
  const parent = anchor.parentElement;
1857
1939
  if (parent === null) return;
1858
- if (host.parentElement !== parent || host.nextElementSibling !== anchor) {
1859
- parent.insertBefore(host, anchor);
1940
+ const seat = workspaceScrollBox(anchor);
1941
+ if (seat === null) {
1942
+ host.remove();
1943
+ return;
1944
+ }
1945
+ if (host.parentElement !== seat || seat.firstElementChild !== host) {
1946
+ seat.insertBefore(host, seat.firstChild);
1860
1947
  }
1948
+ // Search results replace the list's rows; the sections would otherwise
1949
+ // sit above those results as unrelated pinned and recent rows.
1950
+ const search = anchor.querySelector('input[type="text"]');
1951
+ host.dataset.searching =
1952
+ search instanceof HTMLInputElement && search.value.trim() !== '' ? 'true' : 'false';
1861
1953
  if (resize === undefined) {
1862
1954
  // The seat spans the sidebar's inner width, which is narrower than the
1863
1955
  // column by its inline padding; the rail seat is far narrower still.
@@ -1891,7 +1983,7 @@ window.__ModuleLoader__.load({
1891
1983
  };
1892
1984
  }
1893
1985
 
1894
- /** Contribute the pinned and recent sections above the workspace list. */
1986
+ /** Contribute the pinned and recent sections inside the workspace list. */
1895
1987
  function apply(ctx) {
1896
1988
  ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'up-session: dictionaries');
1897
1989
  const t = ctx.locale.bind(NS);
@@ -1954,6 +2046,18 @@ window.__ModuleLoader__.load({
1954
2046
  }
1955
2047
  },
1956
2048
  stopAndArchive: (sessionId) => ctx.uiWorkspace.archiveSession(sessionId, { stopActivity: true }),
2049
+ // The sidebar's own session menu (pin, rename, fork, copy id, Finder, archive).
2050
+ // Opening it here reuses every contribution registered for target "session".
2051
+ openMenu: (sessionId, displayTitle, point) => {
2052
+ ctx.contextMenus.open(
2053
+ {
2054
+ kind: 'session',
2055
+ value: { sessionId, displayTitle },
2056
+ },
2057
+ point,
2058
+ );
2059
+ },
2060
+ hooks: { contextMenuTarget: ctx.contextMenus.active },
1957
2061
  });
1958
2062
 
1959
2063
  ctx.slots.inject('shell.overlay', () =>
package/index.js CHANGED
@@ -2,7 +2,8 @@
2
2
  * Host half of the dsh-up-session bundle.
3
3
  *
4
4
  * The plugin is a pure sidebar decoration: it reads pin state through the
5
- * Client's workspace controller and renders above the workspace list. There is
6
- * no Host-side behavior, so this module only satisfies the bundle's Host row.
5
+ * Client's workspace controller and renders at the top of the workspace list.
6
+ * There is no Host-side behavior, so this module only satisfies the bundle's
7
+ * Host row.
7
8
  */
8
9
  export function apply() {}
package/locale/en.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "meta": {
3
3
  "title": "dsh-up-session",
4
- "description": "Adds pinned and recent sections above the workspace list in the sidebar, gathering pinned and recent sessions, with a settings popup in each section header."
4
+ "description": "Adds pinned and recent groups at the top of the workspace list in the sidebar, gathering pinned and recent sessions. Rows open the same context menu as the workspace list, and each group header has a settings popup."
5
5
  }
6
6
  }
package/locale/zh.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "meta": {
3
3
  "title": "dsh-up-session",
4
- "description": "在侧边栏工作区上方增加置顶区和最近区,汇总各工作区的置顶与最近会话,并在标题栏提供设置入口。"
4
+ "description": "在侧边栏工作区列表顶部增加置顶区和最近区,汇总各工作区的置顶与最近会话;会话行可打开与工作区相同的右键菜单,标题栏提供设置入口。"
5
5
  }
6
6
  }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "dsh-up-session",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "type": "module",
5
- "description": "Adds pinned and recent session sections above the workspace list in the DSH sidebar, with a small settings popup.",
5
+ "description": "Adds pinned and recent session groups at the top of the workspace list in the DSH sidebar. Rows open the host session menu, and each group header has a settings popup.",
6
6
  "license": "MIT",
7
7
  "author": "kyrsieger",
8
8
  "repository": {
@@ -42,7 +42,7 @@
42
42
  "CHANGELOG.md"
43
43
  ],
44
44
  "engines": {
45
- "dsh": ">=0.2.1-rc.2"
45
+ "dsh": ">=0.2.2-alpha.3"
46
46
  },
47
47
  "dsh": {
48
48
  "bundle": {