dsh-better-workspace 0.36.10 → 0.36.12

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/README.md CHANGED
@@ -129,7 +129,7 @@ DSH 的侧边栏浏览区是一个公开插槽 `sidebar.workspaces`(single),官
129
129
 
130
130
  - 会话**跨工作区**拖拽仍被拒绝(会话归属工作区);同一工作区内的分组归属已随拖拽生效(v0.18.0:拖到别组的行改写前缀,拖到自己的工作区行移出分组,右键也有「移出分组」)。
131
131
  - 搜索为本地标题过滤(两层扁平化后逐工作区匹配);宿主内容搜索(`session.search`)计划接回。
132
- - flat(平铺)视图未接管,层级树即视图。侧栏切到「单列表 / 按工作区」时,新会话页的选择器退化为**一层完整标题**的工作区列表(仍可选,不再分组)。
132
+ - flat(平铺)视图未接管,层级树即视图。侧栏切到「单列表」时,新会话页的选择器退化为**一层完整标题**的工作区列表(仍可选,不再分组);切到「按工作区」时(v0.36.11 起),只要「工作区分组」开着(磁盘+名称 / 仅名称),侧栏与新会话页选择器都显示**名称树挂顶层**的结构(名称组与磁盘容器平级,切换即时生效);「工作区分组」设为「磁盘目录」时保持官方的散开平铺。
133
133
  - 新会话页选择器里的分组开合是**选择器本地状态**(默认全展开):它不写侧栏的折叠状态,免得选工作区时被侧栏的折叠挡住目标。
134
134
 
135
135
  ## 开发
package/dsh.plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-external/dsh-better-workspace",
3
- "version": "0.36.10",
3
+ "version": "0.36.12",
4
4
  "main": "./src/index.js",
5
5
  "description": "侧边栏工作区树:按官方磁盘目录嵌套(官方 workspace-tree 语义),可选按名称中的 / 分组(默认关闭,设置里开启);新会话页的工作区选择器同树。Workspace tree for the DSH sidebar workspace list, nested by official disk folders with optional \"/\"-name grouping; the New Session workspace picker shows the same tree.",
6
6
  "engines": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-better-workspace",
3
- "version": "0.36.10",
3
+ "version": "0.36.12",
4
4
  "description": "侧边栏工作区树:按官方磁盘目录嵌套(官方 workspace-tree 语义),可选按名称中的 / 分组(默认关闭,设置里开启);新会话页的工作区选择器同树(同一份视图选项即时同步);添加工作区走官方目录流。Workspace tree for the DSH sidebar: disk-path nesting (official workspace-tree semantics), with optional \"/\"-name grouping (off by default), and the same tree in the New Session workspace picker; add-workspace rides the official directory flow.",
5
5
  "type": "module",
6
6
  "repository": {
package/src/client.js CHANGED
@@ -8273,7 +8273,15 @@ window.__ModuleLoader__.load({
8273
8273
  // the user report "拖拽中树会乱/落点漂移". Drop targets are now
8274
8274
  // answered by the stable render; nothing re-flows mid-drag.
8275
8275
  const tree = React.useMemo(() => {
8276
- const built = buildTree(items, workspaceGroupMode, wsFolders, folderOrderMap)
8276
+ // v0.36.11: "workspaces" (groupBy === 'workspace') turns OFF disk
8277
+ // nesting — official semantics — but the NAME tree stays on when the
8278
+ // user enabled one (workspaceGroupMode !== 'disk'). A plain buildTree
8279
+ // mode of 'slash' expresses exactly that: name groups at the root,
8280
+ // every workspace at the top, entry.sub always null.
8281
+ const treeMode = groupBy === 'workspace' && workspaceGroupMode !== 'disk'
8282
+ ? 'slash'
8283
+ : workspaceGroupMode
8284
+ const built = buildTree(items, treeMode, wsFolders, folderOrderMap)
8277
8285
  if (!compactChains) return built
8278
8286
  const folders = built.folders.map((f) => materializeChain(compressTree(f)))
8279
8287
  // v0.36.5: keep the MIXED root sequence across chain compression —
@@ -8285,14 +8293,21 @@ window.__ModuleLoader__.load({
8285
8293
  ? built.seq.map((item) => item.kind === 'folder' ? { kind: 'folder', node: byName.get(item.node.name) || item.node } : item)
8286
8294
  : built.seq
8287
8295
  return { ...built, folders, workspaces: built.workspaces, seq }
8288
- }, [items, compactChains, workspaceGroupMode, wsFolders, folderOrderMap])
8296
+ }, [items, compactChains, groupBy, workspaceGroupMode, wsFolders, folderOrderMap])
8289
8297
  // UNCOMPRESSED tree, for the new-group dialog only. compressTree merges a
8290
8298
  // single-child chain into one row that stands for BOTH a group and the
8291
8299
  // workspace under it — fine for drawing, useless for choosing a
8292
8300
  // container: one half must stay unselectable and the other must be
8293
8301
  // selectable, and a merged row cannot be both. (It also carried no
8294
8302
  // `name`, which is how a lone "E" rendered blank.)
8295
- const rawTree = React.useMemo(() => buildTree(items, workspaceGroupMode, wsFolders, folderOrderMap), [items, workspaceGroupMode, wsFolders, folderOrderMap])
8303
+ // v0.36.11: same groupBy-aware mode as the rendered tree — the
8304
+ // new-group dialog must mirror the hierarchy the user actually sees.
8305
+ const rawTree = React.useMemo(() => buildTree(
8306
+ items,
8307
+ groupBy === 'workspace' && workspaceGroupMode !== 'disk' ? 'slash' : workspaceGroupMode,
8308
+ wsFolders,
8309
+ folderOrderMap,
8310
+ ), [items, groupBy, workspaceGroupMode, wsFolders, folderOrderMap])
8296
8311
  // Disk-layer ancestry for drag semantics (same derivation as buildTree).
8297
8312
  const diskParentMap = React.useMemo(() => diskParentMapOf(items), [items])
8298
8313
 
@@ -8619,7 +8634,10 @@ window.__ModuleLoader__.load({
8619
8634
  // hierarchy to mirror, so they fall back to a flat workspace list.
8620
8635
  // Offering a nesting the sidebar is not showing would be a lie about
8621
8636
  // where the new group lands.
8637
+ // v0.36.11: "workspaces" + an enabled name layer shows a root-level
8638
+ // name tree — mirror it instead of claiming there is no hierarchy.
8622
8639
  const treeView = groupBy === 'workspace-tree'
8640
+ || (groupBy === 'workspace' && workspaceGroupMode !== 'disk')
8623
8641
  if (kind === 'workspace') {
8624
8642
  // "Top level" IS a real target here (a group at the root of the
8625
8643
  // workspace tree), so it is a node like any other.
@@ -10173,6 +10191,17 @@ window.__ModuleLoader__.load({
10173
10191
  }
10174
10192
  const wsHasCurrent = (w) => !!currentId && !!w
10175
10193
  && ((w.sessionIds || []).includes(currentId) || subHasCurrent(w.sub))
10194
+ // Name groups (slash view) have no disk path to prefix-match; the
10195
+ // folder glyph lights up whenever its SUBTREE holds the current
10196
+ // session — the official ProjectRow rule, applied tree-wide.
10197
+ const nodeHoldsCurrent = (level) => {
10198
+ if (!level || !currentId) return false
10199
+ for (const item of levelSeqOf(level)) {
10200
+ if (item.kind === 'ws') { if (wsHasCurrent(item.entry)) return true }
10201
+ else if (nodeHoldsCurrent(item.node)) return true
10202
+ }
10203
+ return false
10204
+ }
10176
10205
  const currentWsPath = currentId
10177
10206
  ? ((items || []).find((w) => (w.sessionIds || []).includes(currentId)) || {}).path
10178
10207
  : undefined
@@ -10208,7 +10237,7 @@ window.__ModuleLoader__.load({
10208
10237
  custStyle: rowStyleOf('folder:' + idPath),
10209
10238
  iconMode: (styleEntry('folder:' + idPath) || {}).icon || 'solid',
10210
10239
  pulse: expanded ? null : folderPulseOf(node),
10211
- active: pathHoldsCurrent(currentWsPath, node.path),
10240
+ active: pathHoldsCurrent(currentWsPath, node.path) || nodeHoldsCurrent(node),
10212
10241
  t,
10213
10242
  })]
10214
10243
  if (expanded) {
@@ -10263,14 +10292,28 @@ window.__ModuleLoader__.load({
10263
10292
  } else if (groupBy === 'flat') {
10264
10293
  bodyRows.push(...renderFlatRows())
10265
10294
  } else if (groupBy === 'workspace') {
10266
- // Official 'workspace' mode (v0.13): one level — every workspace at
10267
- // the root, no name groups, no disk nesting, no chain compression.
10268
- // Session rows keep the slash toggle; disk-nested children do not
10269
- // recurse here because raw items carry no .sub level.
10270
- // flatWorkspaceEntry, never the raw item: see its note — the raw one
10271
- // has no `leaf` and the row would render nameless.
10272
- for (const workspace of items || []) {
10273
- bodyRows.push(...renderWorkspaceEntry({ workspace: flatWorkspaceEntry(workspace) }, 0, wsPulseOf(workspace)))
10295
+ if (workspaceGroupMode === 'disk') {
10296
+ // Official 'workspace' mode (v0.13): one level — every workspace at
10297
+ // the root, no name groups, no disk nesting, no chain compression.
10298
+ // Session rows keep the slash toggle; disk-nested children do not
10299
+ // recurse here because raw items carry no .sub level.
10300
+ // flatWorkspaceEntry, never the raw item: see its note — the raw
10301
+ // one has no `leaf` and the row would render nameless.
10302
+ for (const workspace of items || []) {
10303
+ bodyRows.push(...renderWorkspaceEntry({ workspace: flatWorkspaceEntry(workspace) }, 0, wsPulseOf(workspace)))
10304
+ }
10305
+ } else {
10306
+ // v0.36.11: with a NAME layer enabled (disk-slash / slash), the
10307
+ // official scatter contradicts the user's mental model — the name
10308
+ // tree the sidebar has been rendering all along should stay a tree,
10309
+ // just lifted to the root (disk nesting OFF = 'slash' mode in the
10310
+ // tree memo above). Same render loop as the tree branch below:
10311
+ // mixed sequence, drag events, ungrouped block all apply.
10312
+ for (const item of levelSeqOf(tree)) {
10313
+ bodyRows.push(...(item.kind === 'ws'
10314
+ ? renderWorkspaceEntry({ workspace: item.entry }, 0, wsPulseOf(item.entry))
10315
+ : renderPlainFolder(item.node, 0)))
10316
+ }
10274
10317
  }
10275
10318
  bodyRows.push(...renderUngroupedBlock())
10276
10319
  } else {