dsh-taskboard 0.4.0 → 0.4.2

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
@@ -60,6 +60,16 @@ node scripts/screenshot.mjs # 重新生成 img/ 截图(需本机 Edge)
60
60
 
61
61
  ## 升级日志
62
62
 
63
+ ### 0.4.2
64
+
65
+ - **修复 DSH Desktop 上看板不出现(用户反馈:入口能点、后端正常,但中间看板无变化)**:当前 DSH Desktop 的 Web shell(`dsh-client-ui-layout`)已完全移除 `data-pane` 属性,中间列改用 CSS Module 哈希类名(`pI_x6G_centerCol`)——而看板挂载选择器写死 `[data-pane="conversation"]`,`querySelector` 永远落空,看板容器从未创建(侧栏入口正常,是因为它一直有 `[class*="sidebarCol"]` 兜底)。修复:挂载选择器与隐藏规则双兼容 `'[data-pane="conversation"], [class*="centerCol"]'`(新旧 shell 通吃,与侧栏入口同款兜底策略);`.dsh-atb-view` 以 `height:100%` 撑满列容器,不依赖列内部结构
66
+ - 回归测试:构造无 `data-pane`、哈希类名 `centerCol` 的 Desktop shell DOM——看板容器正确创建于列内、隐藏规则双选择器齐备
67
+
68
+ ### 0.4.1
69
+
70
+ - **修复侧栏入口点击竞态(用户反馈)**:部分 shell 的 DOM 里,任务看板侧栏入口被插在 class 含 `newSession` 的容器内部——点击入口时,看板的全局捕获监听器(「点侧栏会话/新会话让位」语义)先 `closeBoard`,入口自身的 `toggleBoard` 再翻回来,表现为**看板一开就闪关、或入口按钮关不掉**。修复:捕获监听器先按 `closest('[data-dsh-atb-entry]')` 整体豁免入口子树(比给选择器加 `:not()` 更稳——`:not()` 只排除元素自身匹配,挡不住入口嵌在带类祖先容器内的形态);真会话行/新会话按钮的让位语义不变。顺带加固 `newSessionButton()` 锚点扫描排除自身入口,杜绝自愈重插时锚点自引用
71
+ - 回归测试:构造「入口位于 newSession 容器内」的竞态形态 DOM——点击入口开/关 toggle 正常;点真新会话按钮仍让位
72
+
63
73
  ### 0.4.0
64
74
 
65
75
  **验收效率包——把「人验收」环节在看板内闭环:**
package/lib/client.js CHANGED
@@ -945,7 +945,10 @@ window.__ModuleLoader__.load({
945
945
  .dsh-atb-search { width: 130px; }
946
946
  .dsh-atb-badge[data-kind="stale"] { background: rgba(217,130,43,.15); color: #d9822b; }
947
947
 
948
- html[data-dsh-atb-active] [data-pane="conversation"] > *:not([data-dsh-atb-view]) { display: none !important; }
948
+ /* 0.4.2: dual column matching — dev shell's data-pane pane OR the Desktop
949
+ * shell's CSS-Module hashed centerCol (see board-mount.tsx). */
950
+ html[data-dsh-atb-active] [data-pane="conversation"] > *:not([data-dsh-atb-view]),
951
+ html[data-dsh-atb-active] [class*="centerCol"] > *:not([data-dsh-atb-view]) { display: none !important; }
949
952
  .dsh-atb-view { display: none; }
950
953
  html[data-dsh-atb-active] .dsh-atb-view { display: flex; flex-direction: column; height: 100%; overflow: hidden; }
951
954
 
@@ -1576,6 +1579,8 @@ window.__ModuleLoader__.load({
1576
1579
 
1577
1580
  //#endregion
1578
1581
  //#region src/client/sidebar-entry.ts
1582
+ /** Stable data attribute identifying this entry row. */
1583
+ const ENTRY_SELECTOR = "[data-dsh-atb-entry]";
1579
1584
  /** Inline icon (16px nav-icon look). */
1580
1585
  const ICON = "<svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"2\" y=\"2.5\" width=\"12\" height=\"11\" rx=\"1.5\"/><path d=\"M2 6.5h12M6.5 6.5v7\"/></svg>";
1581
1586
  /**
@@ -1590,15 +1595,18 @@ window.__ModuleLoader__.load({
1590
1595
  /**
1591
1596
  * The New Session button: nested in the logo row on current shells, a direct
1592
1597
  * child BUTTON on the real shell (the family plugins' fallback), with
1593
- * aria-label/text fallbacks for other shells.
1598
+ * aria-label/text fallbacks for other shells. The direct-child scan skips
1599
+ * our own entry (0.4.1): on shells where the insertion anchor lands inside a
1600
+ * class-carrying container, a self-referential anchor would pin the entry
1601
+ * against that container's own geometry instead of the family block.
1594
1602
  */
1595
1603
  function newSessionButton(root) {
1596
1604
  const nested = root.querySelector("button[class*=\"newSession\"]");
1597
1605
  if (nested !== null) return nested;
1598
- for (const child of root.children) if (child instanceof HTMLButtonElement) return child;
1606
+ for (const child of root.children) if (child instanceof HTMLButtonElement && !child.matches("[data-dsh-atb-entry]")) return child;
1599
1607
  const byAria = root.querySelector("button[aria-label=\"新建会话\"], button[aria-label=\"New Session\"], button[aria-label*=\"新会话\"], button[aria-label*=\"new session\" i]");
1600
1608
  if (byAria !== null) return byAria;
1601
- return Array.from(root.querySelectorAll("button")).find((button) => /新会话|新建会话|new session/i.test(button.textContent ?? ""));
1609
+ return Array.from(root.querySelectorAll("button")).find((button) => !button.matches("[data-dsh-atb-entry]") && /新会话|新建会话|new session/i.test(button.textContent ?? ""));
1602
1610
  }
1603
1611
  /** Build the entry row (a detached button; insert once the shell is up). */
1604
1612
  function createEntry(controller) {
@@ -1806,7 +1814,7 @@ window.__ModuleLoader__.load({
1806
1814
  * @module dsh-taskboard/shared/version
1807
1815
  */
1808
1816
  /** The package version (must equal package.json "version"). */
1809
- const PLUGIN_VERSION = "0.4.0";
1817
+ const PLUGIN_VERSION = "0.4.2";
1810
1818
 
1811
1819
  //#endregion
1812
1820
  //#region src/client/board/TaskCard.tsx
@@ -4529,15 +4537,20 @@ window.__ModuleLoader__.load({
4529
4537
  //#endregion
4530
4538
  //#region src/client/board-mount.tsx
4531
4539
  /**
4532
- * Board view mounting: a container appended inside the `[data-pane=
4533
- * "conversation"]` grid item (a trailing child React never manages), with a
4534
- * stylesheet rule hiding the conversation content while the board is active.
4535
- * Toggling rides a data attribute on <html> — no React involvement in the
4536
- * shell.
4540
+ * Board view mounting: a container appended inside the center column (a
4541
+ * trailing child React never manages), with a stylesheet rule hiding the
4542
+ * conversation content while the board is active. Toggling rides a data
4543
+ * attribute on <html> — no React involvement in the shell.
4544
+ *
4545
+ * Column matching is DUAL (0.4.2): the dev shell marks the column with
4546
+ * `data-pane="conversation"`; the DSH Desktop shell (dsh-client-ui-layout)
4547
+ * dropped data-pane entirely and uses CSS-Module hashed class names
4548
+ * (`pI_x6G_centerCol`) — the class-substring fallback keeps both mounting,
4549
+ * exactly like sidebar-entry's `[class*="sidebarCol"]` fallback.
4537
4550
  *
4538
4551
  * @module dsh-taskboard/client/board-mount
4539
4552
  */
4540
- const CONVERSATION_COLUMN_SELECTOR = "[data-pane=\"conversation\"]";
4553
+ const CONVERSATION_COLUMN_SELECTOR = "[data-pane=\"conversation\"], [class*=\"centerCol\"]";
4541
4554
  const ACTIVE_ATTR = "data-dsh-atb-active";
4542
4555
  /** Sibling panels' activation attributes, evicted when this board opens. */
4543
4556
  const OTHER_ACTIVE_ATTRS = ["data-dsh-taskboard-active", "data-dsh-ssh-active"];
@@ -4589,6 +4602,7 @@ window.__ModuleLoader__.load({
4589
4602
  if (!controller.getSnapshot().boardOpen) return;
4590
4603
  const target = event.target;
4591
4604
  if (target === null) return;
4605
+ if (target.closest("[data-dsh-atb-entry]") !== null) return;
4592
4606
  if (target.closest(SIDEBAR_ROW_SELECTOR) !== null) controller.closeBoard();
4593
4607
  };
4594
4608
  document.addEventListener("click", onClickSidebarRow, true);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-taskboard",
3
3
  "description": "Agent-first task board for the DSH web GUI: host-authoritative task ledger with taskboard_* agent tools, project (= workspace) claim boundaries, per-task model execution in fresh sessions, optional per-task git-worktree isolation (dedicated task branches, commit evidence, one-click merge), host-side cron scheduling, and a live SSE kanban view. Mounts via the official dsh plugin system — no DSH source changes.",
4
- "version": "0.4.0",
4
+ "version": "0.4.2",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {
@@ -1,20 +1,26 @@
1
1
  /**
2
- * Board view mounting: a container appended inside the `[data-pane=
3
- * "conversation"]` grid item (a trailing child React never manages), with a
4
- * stylesheet rule hiding the conversation content while the board is active.
5
- * Toggling rides a data attribute on <html> — no React involvement in the
6
- * shell.
2
+ * Board view mounting: a container appended inside the center column (a
3
+ * trailing child React never manages), with a stylesheet rule hiding the
4
+ * conversation content while the board is active. Toggling rides a data
5
+ * attribute on <html> — no React involvement in the shell.
6
+ *
7
+ * Column matching is DUAL (0.4.2): the dev shell marks the column with
8
+ * `data-pane="conversation"`; the DSH Desktop shell (dsh-client-ui-layout)
9
+ * dropped data-pane entirely and uses CSS-Module hashed class names
10
+ * (`pI_x6G_centerCol`) — the class-substring fallback keeps both mounting,
11
+ * exactly like sidebar-entry's `[class*="sidebarCol"]` fallback.
7
12
  *
8
13
  * @module dsh-taskboard/client/board-mount
9
14
  */
10
15
  import { createRoot, type Root } from 'react-dom/client'
11
16
  import type { BoardController } from './controller.ts'
12
17
  import { TaskBoard } from './board/TaskBoard.tsx'
18
+ import { ENTRY_SELECTOR } from './sidebar-entry.ts'
13
19
 
14
20
  /** The injected board container. */
15
21
  export const BOARD_VIEW_SELECTOR = '[data-dsh-atb-view]'
16
22
 
17
- const CONVERSATION_COLUMN_SELECTOR = '[data-pane="conversation"]'
23
+ const CONVERSATION_COLUMN_SELECTOR = '[data-pane="conversation"], [class*="centerCol"]'
18
24
  const ACTIVE_ATTR = 'data-dsh-atb-active'
19
25
  /** Sibling panels' activation attributes, evicted when this board opens. */
20
26
  const OTHER_ACTIVE_ATTRS = ['data-dsh-taskboard-active', 'data-dsh-ssh-active']
@@ -72,6 +78,13 @@ export function mountBoard(controller: BoardController): () => void {
72
78
  if (!controller.getSnapshot().boardOpen) return
73
79
  const target = event.target as HTMLElement | null
74
80
  if (target === null) return
81
+ // 0.4.1 竞态修复:任务看板自己的侧栏入口先整体豁免——在部分 shell
82
+ // 的 DOM 里,插入点落在 class 含 newSession 的容器内部,此时入口点击
83
+ // 会被本捕获监听器先 closeBoard,再被入口自身的 toggleBoard 翻回来
84
+ // (症状:看板闪关或入口按钮关不掉)。豁免必须按「入口子树」判定
85
+ // (closest 而非元素自身匹配):入口可能嵌在带 newSession 类的祖先
86
+ // 容器里,仅对选择器加 :not() 排除元素自身挡不住那种嵌套形态。
87
+ if (target.closest(ENTRY_SELECTOR) !== null) return
75
88
  if (target.closest(SIDEBAR_ROW_SELECTOR) !== null) controller.closeBoard()
76
89
  }
77
90
  document.addEventListener('click', onClickSidebarRow, true)
@@ -36,20 +36,23 @@ function sidebarRoot(): HTMLElement | undefined {
36
36
  /**
37
37
  * The New Session button: nested in the logo row on current shells, a direct
38
38
  * child BUTTON on the real shell (the family plugins' fallback), with
39
- * aria-label/text fallbacks for other shells.
39
+ * aria-label/text fallbacks for other shells. The direct-child scan skips
40
+ * our own entry (0.4.1): on shells where the insertion anchor lands inside a
41
+ * class-carrying container, a self-referential anchor would pin the entry
42
+ * against that container's own geometry instead of the family block.
40
43
  */
41
44
  function newSessionButton(root: HTMLElement): HTMLButtonElement | undefined {
42
45
  const nested = root.querySelector<HTMLButtonElement>('button[class*="newSession"]')
43
46
  if (nested !== null) return nested
44
47
  for (const child of root.children) {
45
- if (child instanceof HTMLButtonElement) return child
48
+ if (child instanceof HTMLButtonElement && !child.matches(ENTRY_SELECTOR)) return child
46
49
  }
47
50
  const byAria = root.querySelector<HTMLButtonElement>(
48
51
  'button[aria-label="新建会话"], button[aria-label="New Session"], button[aria-label*="新会话"], button[aria-label*="new session" i]',
49
52
  )
50
53
  if (byAria !== null) return byAria
51
54
  const buttons = Array.from(root.querySelectorAll<HTMLButtonElement>('button'))
52
- return buttons.find(button => /新会话|新建会话|new session/i.test(button.textContent ?? ''))
55
+ return buttons.find(button => !button.matches(ENTRY_SELECTOR) && /新会话|新建会话|new session/i.test(button.textContent ?? ''))
53
56
  }
54
57
 
55
58
  /** Build the entry row (a detached button; insert once the shell is up). */
@@ -49,7 +49,10 @@ export const STYLES = `
49
49
  .dsh-atb-search { width: 130px; }
50
50
  .dsh-atb-badge[data-kind="stale"] { background: rgba(217,130,43,.15); color: #d9822b; }
51
51
 
52
- html[data-dsh-atb-active] [data-pane="conversation"] > *:not([data-dsh-atb-view]) { display: none !important; }
52
+ /* 0.4.2: dual column matching — dev shell's data-pane pane OR the Desktop
53
+ * shell's CSS-Module hashed centerCol (see board-mount.tsx). */
54
+ html[data-dsh-atb-active] [data-pane="conversation"] > *:not([data-dsh-atb-view]),
55
+ html[data-dsh-atb-active] [class*="centerCol"] > *:not([data-dsh-atb-view]) { display: none !important; }
53
56
  .dsh-atb-view { display: none; }
54
57
  html[data-dsh-atb-active] .dsh-atb-view { display: flex; flex-direction: column; height: 100%; overflow: hidden; }
55
58
 
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  /** The package version (must equal package.json "version"). */
9
- export const PLUGIN_VERSION = '0.4.0'
9
+ export const PLUGIN_VERSION = '0.4.2'