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 +5 -0
- package/lib/client.js +10 -4
- package/package.json +1 -1
- package/src/client/board-mount.tsx +8 -0
- package/src/client/sidebar-entry.ts +6 -3
- package/src/shared/version.ts +1 -1
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.
|
|
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.
|
|
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). */
|
package/src/shared/version.ts
CHANGED