dsh-taskboard 0.4.0 → 0.4.1

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,11 @@ node scripts/screenshot.mjs # 重新生成 img/ 截图(需本机 Edge)
60
60
 
61
61
  ## 升级日志
62
62
 
63
+ ### 0.4.1
64
+
65
+ - **修复侧栏入口点击竞态(用户反馈)**:部分 shell 的 DOM 里,任务看板侧栏入口被插在 class 含 `newSession` 的容器内部——点击入口时,看板的全局捕获监听器(「点侧栏会话/新会话让位」语义)先 `closeBoard`,入口自身的 `toggleBoard` 再翻回来,表现为**看板一开就闪关、或入口按钮关不掉**。修复:捕获监听器先按 `closest('[data-dsh-atb-entry]')` 整体豁免入口子树(比给选择器加 `:not()` 更稳——`:not()` 只排除元素自身匹配,挡不住入口嵌在带类祖先容器内的形态);真会话行/新会话按钮的让位语义不变。顺带加固 `newSessionButton()` 锚点扫描排除自身入口,杜绝自愈重插时锚点自引用
66
+ - 回归测试:构造「入口位于 newSession 容器内」的竞态形态 DOM——点击入口开/关 toggle 正常;点真新会话按钮仍让位
67
+
63
68
  ### 0.4.0
64
69
 
65
70
  **验收效率包——把「人验收」环节在看板内闭环:**
package/lib/client.js CHANGED
@@ -1576,6 +1576,8 @@ window.__ModuleLoader__.load({
1576
1576
 
1577
1577
  //#endregion
1578
1578
  //#region src/client/sidebar-entry.ts
1579
+ /** Stable data attribute identifying this entry row. */
1580
+ const ENTRY_SELECTOR = "[data-dsh-atb-entry]";
1579
1581
  /** Inline icon (16px nav-icon look). */
1580
1582
  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
1583
  /**
@@ -1590,15 +1592,18 @@ window.__ModuleLoader__.load({
1590
1592
  /**
1591
1593
  * The New Session button: nested in the logo row on current shells, a direct
1592
1594
  * child BUTTON on the real shell (the family plugins' fallback), with
1593
- * aria-label/text fallbacks for other shells.
1595
+ * aria-label/text fallbacks for other shells. The direct-child scan skips
1596
+ * our own entry (0.4.1): on shells where the insertion anchor lands inside a
1597
+ * class-carrying container, a self-referential anchor would pin the entry
1598
+ * against that container's own geometry instead of the family block.
1594
1599
  */
1595
1600
  function newSessionButton(root) {
1596
1601
  const nested = root.querySelector("button[class*=\"newSession\"]");
1597
1602
  if (nested !== null) return nested;
1598
- for (const child of root.children) if (child instanceof HTMLButtonElement) return child;
1603
+ for (const child of root.children) if (child instanceof HTMLButtonElement && !child.matches("[data-dsh-atb-entry]")) return child;
1599
1604
  const byAria = root.querySelector("button[aria-label=\"新建会话\"], button[aria-label=\"New Session\"], button[aria-label*=\"新会话\"], button[aria-label*=\"new session\" i]");
1600
1605
  if (byAria !== null) return byAria;
1601
- return Array.from(root.querySelectorAll("button")).find((button) => /新会话|新建会话|new session/i.test(button.textContent ?? ""));
1606
+ return Array.from(root.querySelectorAll("button")).find((button) => !button.matches("[data-dsh-atb-entry]") && /新会话|新建会话|new session/i.test(button.textContent ?? ""));
1602
1607
  }
1603
1608
  /** Build the entry row (a detached button; insert once the shell is up). */
1604
1609
  function createEntry(controller) {
@@ -1806,7 +1811,7 @@ window.__ModuleLoader__.load({
1806
1811
  * @module dsh-taskboard/shared/version
1807
1812
  */
1808
1813
  /** The package version (must equal package.json "version"). */
1809
- const PLUGIN_VERSION = "0.4.0";
1814
+ const PLUGIN_VERSION = "0.4.1";
1810
1815
 
1811
1816
  //#endregion
1812
1817
  //#region src/client/board/TaskCard.tsx
@@ -4589,6 +4594,7 @@ window.__ModuleLoader__.load({
4589
4594
  if (!controller.getSnapshot().boardOpen) return;
4590
4595
  const target = event.target;
4591
4596
  if (target === null) return;
4597
+ if (target.closest("[data-dsh-atb-entry]") !== null) return;
4592
4598
  if (target.closest(SIDEBAR_ROW_SELECTOR) !== null) controller.closeBoard();
4593
4599
  };
4594
4600
  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.1",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {
@@ -10,6 +10,7 @@
10
10
  import { createRoot, type Root } from 'react-dom/client'
11
11
  import type { BoardController } from './controller.ts'
12
12
  import { TaskBoard } from './board/TaskBoard.tsx'
13
+ import { ENTRY_SELECTOR } from './sidebar-entry.ts'
13
14
 
14
15
  /** The injected board container. */
15
16
  export const BOARD_VIEW_SELECTOR = '[data-dsh-atb-view]'
@@ -72,6 +73,13 @@ export function mountBoard(controller: BoardController): () => void {
72
73
  if (!controller.getSnapshot().boardOpen) return
73
74
  const target = event.target as HTMLElement | null
74
75
  if (target === null) return
76
+ // 0.4.1 竞态修复:任务看板自己的侧栏入口先整体豁免——在部分 shell
77
+ // 的 DOM 里,插入点落在 class 含 newSession 的容器内部,此时入口点击
78
+ // 会被本捕获监听器先 closeBoard,再被入口自身的 toggleBoard 翻回来
79
+ // (症状:看板闪关或入口按钮关不掉)。豁免必须按「入口子树」判定
80
+ // (closest 而非元素自身匹配):入口可能嵌在带 newSession 类的祖先
81
+ // 容器里,仅对选择器加 :not() 排除元素自身挡不住那种嵌套形态。
82
+ if (target.closest(ENTRY_SELECTOR) !== null) return
75
83
  if (target.closest(SIDEBAR_ROW_SELECTOR) !== null) controller.closeBoard()
76
84
  }
77
85
  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). */
@@ -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.1'