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 +10 -0
- package/lib/client.js +25 -11
- package/package.json +1 -1
- package/src/client/board-mount.tsx +19 -6
- package/src/client/sidebar-entry.ts +6 -3
- package/src/client/styles.ts +4 -1
- package/src/shared/version.ts +1 -1
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
|
-
|
|
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.
|
|
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
|
|
4533
|
-
*
|
|
4534
|
-
*
|
|
4535
|
-
*
|
|
4536
|
-
*
|
|
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.
|
|
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
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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). */
|
package/src/client/styles.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
package/src/shared/version.ts
CHANGED