@fonlan/dsh-task-kanban 0.2.5 → 0.2.7
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 +7 -5
- package/lib/client.js +207 -283
- package/lib/client.js.map +1 -1
- package/lib/index.js +46 -31
- package/lib/types/index.d.ts +3 -3
- package/lib/types/server/settings.d.ts +6 -26
- package/package.json +25 -25
- package/src/client/board.css +21 -147
- package/src/client/board.tsx +5 -2
- package/src/client/icons.tsx +74 -0
- package/src/client/index.tsx +38 -18
- package/src/client/kanban-state.ts +49 -83
- package/src/client/locales.ts +2 -6
- package/src/client/sections.tsx +57 -203
- package/src/index.ts +7 -5
- package/src/server/settings.ts +62 -37
package/lib/client.js
CHANGED
|
@@ -4,9 +4,32 @@ window.__ModuleLoader__.load({
|
|
|
4
4
|
var module = { exports: {} };
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
//#region \0rolldown/runtime.js
|
|
8
|
+
var __create = Object.create;
|
|
9
|
+
var __defProp = Object.defineProperty;
|
|
10
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
11
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
12
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
13
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
+
var __copyProps = (to, from, except, desc) => {
|
|
15
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
16
|
+
key = keys[i];
|
|
17
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
18
|
+
get: ((k) => from[k]).bind(null, key),
|
|
19
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
return to;
|
|
23
|
+
};
|
|
24
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
25
|
+
value: mod,
|
|
26
|
+
enumerable: true
|
|
27
|
+
}) : target, mod));
|
|
28
|
+
//#endregion
|
|
7
29
|
let react = require("react");
|
|
8
30
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
31
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
32
|
+
_deepseek_ai_dsh_client_ui_primitives = __toESM(_deepseek_ai_dsh_client_ui_primitives, 1);
|
|
10
33
|
//#region src/shared/lanes.ts
|
|
11
34
|
/** Which lane a card renders in, based on status + error stage. */
|
|
12
35
|
function laneOf(card) {
|
|
@@ -122,33 +145,25 @@ window.__ModuleLoader__.load({
|
|
|
122
145
|
//#endregion
|
|
123
146
|
//#region src/client/kanban-state.ts
|
|
124
147
|
/**
|
|
125
|
-
*
|
|
148
|
+
* Panel id shared by this plugin's main-panel key (`main`) and its sidebar
|
|
149
|
+
* entry (`sidebar.panellist`): the shell selects the panel by this id, so the
|
|
150
|
+
* two registrations must agree.
|
|
151
|
+
*/
|
|
152
|
+
const KANBAN_PANEL_ID = "task-kanban";
|
|
153
|
+
/**
|
|
154
|
+
* HMR-safe owner context, keyed on the global symbol registry.
|
|
126
155
|
*
|
|
127
156
|
* The client-hmr hot swap re-evaluates this module (a fresh `lib/client.js`
|
|
128
|
-
* bundle) WITHOUT reloading the page
|
|
129
|
-
* duplicated per evaluation
|
|
130
|
-
*
|
|
131
|
-
* OLD copy's `boardOpen`/`boardDisposer` — while a board opened after the swap
|
|
132
|
-
* writes the NEW copy's state cell. The wrapper would then no-op and clicking
|
|
133
|
-
* a sidebar session would never leave the board.
|
|
134
|
-
*
|
|
135
|
-
* Keeping every field on one `globalThis`-backed record (via `Symbol.for`, so
|
|
136
|
-
* all bundle copies resolve the same key) makes every copy read/write the same
|
|
137
|
-
* state cell, so exit works regardless of which module copy performed the
|
|
138
|
-
* enter.
|
|
157
|
+
* bundle) WITHOUT reloading the page; a module-level cell would then be
|
|
158
|
+
* duplicated per evaluation and an old copy's helper would read a stale owner.
|
|
159
|
+
* `Symbol.for` makes every bundle copy share one cell.
|
|
139
160
|
*/
|
|
140
161
|
const STATE_KEY = Symbol.for("@fonlan/dsh-task-kanban/state");
|
|
141
162
|
function getState() {
|
|
142
163
|
const g = globalThis;
|
|
143
164
|
let state = g[STATE_KEY];
|
|
144
165
|
if (state === void 0) {
|
|
145
|
-
state = {
|
|
146
|
-
client: null,
|
|
147
|
-
boardRoot: null,
|
|
148
|
-
boardDisposer: null,
|
|
149
|
-
boardOpen: false,
|
|
150
|
-
listeners: /* @__PURE__ */ new Set()
|
|
151
|
-
};
|
|
166
|
+
state = { client: null };
|
|
152
167
|
g[STATE_KEY] = state;
|
|
153
168
|
}
|
|
154
169
|
return state;
|
|
@@ -159,73 +174,38 @@ window.__ModuleLoader__.load({
|
|
|
159
174
|
function getClient() {
|
|
160
175
|
return getState().client;
|
|
161
176
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
function notify() {
|
|
166
|
-
for (const listener of getState().listeners) listener();
|
|
167
|
-
}
|
|
168
|
-
function isBoardOpen() {
|
|
169
|
-
return getState().boardOpen;
|
|
170
|
-
}
|
|
171
|
-
function subscribe(cb) {
|
|
172
|
-
const listeners = getState().listeners;
|
|
173
|
-
listeners.add(cb);
|
|
174
|
-
return () => {
|
|
175
|
-
listeners.delete(cb);
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
function useBoardOpen() {
|
|
179
|
-
return (0, react.useSyncExternalStore)(subscribe, isBoardOpen);
|
|
180
|
-
}
|
|
181
|
-
function enterBoard() {
|
|
182
|
-
const state = getState();
|
|
183
|
-
const ctx = state.client;
|
|
184
|
-
const root = state.boardRoot;
|
|
185
|
-
if (ctx === null || root === null || state.boardDisposer !== null) return;
|
|
177
|
+
/** The shell's panel face, when this runtime provides it. */
|
|
178
|
+
function layoutOf(ctx) {
|
|
179
|
+
if (ctx === null) return null;
|
|
186
180
|
try {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}, root));
|
|
192
|
-
state.boardOpen = true;
|
|
193
|
-
notify();
|
|
194
|
-
} catch (error) {
|
|
195
|
-
console.error("[@fonlan/dsh-task-kanban] cannot open the board:", error);
|
|
181
|
+
const layout = ctx.layout;
|
|
182
|
+
return typeof layout?.selectPanel === "function" ? layout : null;
|
|
183
|
+
} catch {
|
|
184
|
+
return null;
|
|
196
185
|
}
|
|
197
186
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
if (state.boardOpen) {
|
|
205
|
-
state.boardOpen = false;
|
|
206
|
-
notify();
|
|
187
|
+
/** Hand the center column back to the Conversation. */
|
|
188
|
+
function leaveKanbanPanel(ctx = getClient()) {
|
|
189
|
+
try {
|
|
190
|
+
layoutOf(ctx)?.selectPanel(null);
|
|
191
|
+
} catch (error) {
|
|
192
|
+
console.error("[@fonlan/dsh-task-kanban] cannot leave the kanban panel:", error);
|
|
207
193
|
}
|
|
208
194
|
}
|
|
209
|
-
function toggleBoard() {
|
|
210
|
-
if (isBoardOpen()) exitBoard();
|
|
211
|
-
else enterBoard();
|
|
212
|
-
}
|
|
213
195
|
/**
|
|
214
|
-
* Route session navigation through the
|
|
215
|
-
* board is
|
|
216
|
-
*
|
|
196
|
+
* Route session navigation through the panel selection: opening any session
|
|
197
|
+
* while the board is the active panel first returns the center column to the
|
|
198
|
+
* Conversation, so the session view renders.
|
|
217
199
|
*
|
|
218
200
|
* Every sidebar path that selects a session funnels through `sessions.open`
|
|
219
201
|
* (session rows, search results, fork results, New Session) or
|
|
220
202
|
* `sessions.openSubagent` (catalog children) — including re-clicking the
|
|
221
203
|
* already-current session, which never changes `list.current` and therefore
|
|
222
|
-
* cannot be caught by a list-store subscription. Wrapping the two entry
|
|
223
|
-
*
|
|
204
|
+
* cannot be caught by a list-store subscription. Wrapping the two entry points
|
|
205
|
+
* covers all of them in one place.
|
|
224
206
|
*
|
|
225
207
|
* The wrapper is idempotent: a `kbBound` marker on the wrapper prevents a
|
|
226
|
-
* second bind (plugin re-apply / HMR) from stacking another layer.
|
|
227
|
-
* wrapper's exit goes through the shared state store, a wrapper left over from
|
|
228
|
-
* a previous (HMR-replaced) module copy still closes the board correctly.
|
|
208
|
+
* second bind (plugin re-apply / HMR) from stacking another layer.
|
|
229
209
|
*/
|
|
230
210
|
function bindSessionNavigation(ctx) {
|
|
231
211
|
const sessions = ctx.sessions;
|
|
@@ -233,7 +213,7 @@ window.__ModuleLoader__.load({
|
|
|
233
213
|
const open = sessions.open;
|
|
234
214
|
if (typeof open === "function" && open.kbBound !== true) {
|
|
235
215
|
const bound = ((id) => {
|
|
236
|
-
|
|
216
|
+
leaveKanbanPanel(ctx);
|
|
237
217
|
return open.call(sessions, id);
|
|
238
218
|
});
|
|
239
219
|
bound.kbBound = true;
|
|
@@ -242,7 +222,7 @@ window.__ModuleLoader__.load({
|
|
|
242
222
|
const openSubagent = sessions.openSubagent;
|
|
243
223
|
if (typeof openSubagent === "function" && openSubagent.kbBound !== true) {
|
|
244
224
|
const bound = ((address) => {
|
|
245
|
-
|
|
225
|
+
leaveKanbanPanel(ctx);
|
|
246
226
|
return openSubagent.call(sessions, address);
|
|
247
227
|
});
|
|
248
228
|
bound.kbBound = true;
|
|
@@ -301,9 +281,7 @@ window.__ModuleLoader__.load({
|
|
|
301
281
|
noWorkspace: "没有可用工作区,请先在侧边栏添加",
|
|
302
282
|
refresh: "刷新",
|
|
303
283
|
settingsTitle: "任务看板",
|
|
304
|
-
|
|
305
|
-
settingsExpand: "展开设置",
|
|
306
|
-
settingsCollapse: "收起设置",
|
|
284
|
+
sectionSub: "全局并行 worker 数与各会话类型的默认模型、推理强度、Agent 预设",
|
|
307
285
|
maxParallelWorkers: "全局并行 worker 数",
|
|
308
286
|
settingsRefineGroup: "细化需求会话默认",
|
|
309
287
|
settingsPhaseGroup: "Phase 实现会话默认",
|
|
@@ -376,9 +354,7 @@ window.__ModuleLoader__.load({
|
|
|
376
354
|
noWorkspace: "No workspaces available — add one in the sidebar first",
|
|
377
355
|
refresh: "Refresh",
|
|
378
356
|
settingsTitle: "Task Kanban",
|
|
379
|
-
|
|
380
|
-
settingsExpand: "Show settings",
|
|
381
|
-
settingsCollapse: "Hide settings",
|
|
357
|
+
sectionSub: "Global parallel workers and per-session-type defaults for model, reasoning effort, and agent preset",
|
|
382
358
|
maxParallelWorkers: "Global parallel workers",
|
|
383
359
|
settingsRefineGroup: "Refinement session defaults",
|
|
384
360
|
settingsPhaseGroup: "Phase implementation defaults",
|
|
@@ -404,8 +380,8 @@ window.__ModuleLoader__.load({
|
|
|
404
380
|
};
|
|
405
381
|
//#endregion
|
|
406
382
|
//#region \0dsh-css:/home/runner/work/dsh-task-kanban/dsh-task-kanban/src/client/board.css.mjs
|
|
407
|
-
const css = "/* @fonlan/dsh-task-kanban board styles (kb- prefixed, plain CSS).\n Surfaces use the DSH theme tokens (--dsw-alias-*) so they follow the\n shell's selected light/dark appearance; the var() fallbacks cover shells\n that do not define them. */\n.kb-board {\n height: 100%;\n display: flex;\n flex-direction: column;\n box-sizing: border-box;\n padding: 12px 16px;\n overflow: hidden;\n color: var(--dsw-alias-label-primary, var(--dsh-fg, #e6e6e6));\n background: var(--dsw-alias-bg-base, var(--dsh-bg, #141418));\n font: 14px/1.5 -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', 'Microsoft YaHei', sans-serif;\n}\n.kb-empty {\n align-items: center;\n justify-content: center;\n}\n.kb-empty-text { opacity: 0.6; }\n.kb-board-header {\n display: flex;\n align-items: center;\n gap: 12px;\n padding-bottom: 10px;\n border-bottom: 1px solid rgba(128, 128, 128, 0.25);\n flex: 0 0 auto;\n}\n/* Left-aligned group: workspace selector + new-task button sit adjacent. */\n.kb-board-title {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n flex: 0 1 auto;\n}\n.kb-board-title .kb-btn { flex: none; }\n.kb-ws-select {\n max-width: 320px;\n min-width: 0;\n flex: 0 1 auto;\n padding: 6px 10px;\n border-radius: 6px;\n border: 1px solid var(--dsw-alias-border-l2-darkmode-thin, var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35)));\n background: var(--dsw-specific-input-major, var(--dsw-alias-bg-layer-1, rgba(128, 128, 128, 0.12)));\n color: var(--dsw-alias-label-primary, inherit);\n font-size: 14px;\n}\n.kb-btn {\n padding: 6px 14px;\n border-radius: 6px;\n border: 1px solid rgba(128, 128, 128, 0.35);\n background: rgba(128, 128, 128, 0.12);\n color: inherit;\n font-size: 13px;\n white-space: nowrap;\n cursor: pointer;\n}\n.kb-btn:hover { background: rgba(128, 128, 128, 0.22); }\n.kb-btn:disabled { opacity: 0.5; cursor: default; }\n.kb-btn-primary { background: #3b82f6; border-color: #3b82f6; color: #fff; }\n.kb-btn-primary:hover { background: #2f6fe0; }\n.kb-btn-danger { background: rgba(239, 68, 68, 0.15); border-color: rgba(239, 68, 68, 0.5); color: #f87171; }\n.kb-btn-small { padding: 2px 8px; font-size: 12px; }\n.kb-lanes {\n flex: 1 1 auto;\n display: flex;\n gap: 12px;\n overflow-x: auto;\n overflow-y: hidden;\n padding-top: 10px;\n min-height: 0;\n}\n.kb-lane {\n flex: 1 1 0;\n min-width: 220px;\n max-width: 320px;\n display: flex;\n flex-direction: column;\n background: rgba(128, 128, 128, 0.07);\n border-radius: 8px;\n border: 1px solid rgba(128, 128, 128, 0.12);\n overflow: hidden;\n}\n.kb-lane-drop { border-style: dashed; border-color: rgba(59, 130, 246, 0.45); }\n.kb-lane-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 10px;\n font-weight: 600;\n font-size: 13px;\n color: var(--dsw-alias-label-secondary, var(--dsh-fg-soft, #b8b8c0));\n border-bottom: 1px solid rgba(128, 128, 128, 0.12);\n flex: 0 0 auto;\n}\n.kb-lane-count {\n background: rgba(128, 128, 128, 0.18);\n border-radius: 10px;\n padding: 0 8px;\n font-size: 12px;\n}\n.kb-lane-body {\n flex: 1 1 auto;\n overflow-y: auto;\n padding: 8px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.kb-card {\n background: rgba(128, 128, 128, 0.1);\n border: 1px solid rgba(128, 128, 128, 0.2);\n border-radius: 8px;\n padding: 8px 10px;\n cursor: pointer;\n user-select: none;\n}\n.kb-card:hover { border-color: rgba(128, 128, 128, 0.45); }\n.kb-card[draggable='true'] { cursor: grab; }\n.kb-card-running { border-color: rgba(59, 130, 246, 0.6); }\n.kb-card-error { border-color: rgba(239, 68, 68, 0.6); }\n.kb-card-title { font-weight: 600; font-size: 13px; margin-bottom: 6px; word-break: break-all; }\n.kb-card-meta { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; font-size: 12px; }\n.kb-card-badge {\n background: rgba(128, 128, 128, 0.2);\n border-radius: 4px;\n padding: 1px 6px;\n}\n.kb-card-running .kb-card-badge { background: rgba(59, 130, 246, 0.3); }\n.kb-card-error .kb-card-badge { background: rgba(239, 68, 68, 0.3); }\n.kb-card-phase { opacity: 0.8; }\n.kb-card-skill {\n background: rgba(139, 92, 246, 0.22);\n border-radius: 4px;\n padding: 1px 6px;\n}\n.kb-card-model { opacity: 0.55; }\n.kb-card-error-text { margin-top: 6px; font-size: 12px; color: #f87171; word-break: break-all; }\n.kb-card-worktree { margin-top: 4px; font-size: 11px; opacity: 0.5; word-break: break-all; }\n.kb-toast {\n position: fixed;\n left: 50%;\n bottom: 24px;\n transform: translateX(-50%);\n background: var(--dsw-alias-bg-layer-2);\n border: 1px solid var(--dsw-alias-border-l2);\n color: var(--dsw-alias-label-primary);\n padding: 8px 16px;\n border-radius: 8px;\n font-size: 13px;\n z-index: 2147483000;\n max-width: 70vw;\n}\n.kb-modal-backdrop {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.5);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 2147482900;\n}\n.kb-modal {\n width: 460px;\n max-width: 90vw;\n max-height: 85vh;\n overflow-y: auto;\n background: var(--dsw-alias-bg-layer-1);\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 12px;\n padding: 18px;\n color: var(--dsw-alias-label-primary);\n}\n.kb-modal-wide { width: 640px; }\n/* Task-detail bottom panel: docks at the bottom of the board page, fills the\n full board width and exactly half of the board height (the lanes column\n above flexes to the remaining space). */\n.kb-detail-panel {\n flex: 0 0 50%;\n min-height: 0;\n display: flex;\n flex-direction: column;\n margin-top: 10px;\n background: var(--dsw-alias-bg-layer-2);\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 12px;\n overflow: hidden;\n color: var(--dsw-alias-label-primary);\n}\n.kb-detail-panel-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n padding: 10px 14px;\n border-bottom: 1px solid rgba(128, 128, 128, 0.2);\n flex: 0 0 auto;\n}\n.kb-detail-panel-head .kb-modal-title { margin-bottom: 0; }\n.kb-detail-panel-body {\n flex: 1 1 auto;\n min-height: 0;\n overflow-y: auto;\n padding: 14px;\n}\n.kb-detail-close {\n flex: none;\n width: 28px;\n height: 28px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: 1px solid transparent;\n border-radius: 6px;\n background: transparent;\n color: inherit;\n font-size: 15px;\n line-height: 1;\n opacity: 0.75;\n cursor: pointer;\n}\n.kb-detail-close:hover { background: rgba(128, 128, 128, 0.18); opacity: 1; }\n.kb-modal-title { font-size: 16px; font-weight: 700; margin-bottom: 14px; display: flex; align-items: center; gap: 10px; }\n.kb-field { display: block; margin-bottom: 12px; }\n.kb-field > span { display: block; font-size: 12px; opacity: 0.75; margin-bottom: 4px; }\n.kb-field-hint {\n display: block;\n font-size: 11px;\n opacity: 0.55;\n margin-top: 4px;\n}\n.kb-field-hint-skill {\n opacity: 0.9;\n color: #a78bfa;\n}\n.kb-skill-menu-wrap { position: relative; }\n.kb-skill-menu {\n position: absolute;\n top: 100%;\n left: 0;\n right: 0;\n z-index: 20;\n margin: 2px 0 0;\n padding: 4px;\n list-style: none;\n max-height: 180px;\n overflow-y: auto;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-overlay);\n color: var(--dsw-alias-label-primary);\n box-shadow: 0 6px 20px rgba(0, 0, 0, 0.35);\n}\n.kb-skill-menu-item {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 6px 8px;\n border-radius: 6px;\n cursor: pointer;\n font-size: 12px;\n color: var(--dsw-alias-label-primary);\n}\n.kb-skill-menu-item-active { background: var(--dsw-alias-interactive-bg-hover); }\n.kb-skill-menu-name { font-weight: 600; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }\n.kb-skill-menu-desc {\n color: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.kb-field-row { display: flex; align-items: center; gap: 10px; justify-content: space-between; }\n/* Field labels in a row never wrap; selects get the remaining fixed width. */\n.kb-field-row > span { white-space: nowrap; flex-shrink: 0; margin-bottom: 0; }\n.kb-input {\n width: 100%;\n box-sizing: border-box;\n padding: 7px 10px;\n border-radius: 6px;\n /* Form controls follow the shell theme: DSH's input surface token plus a\n theme-tuned border; the fallbacks cover shells without these tokens. */\n border: 1px solid var(--dsw-alias-border-l2-darkmode-thin, var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35)));\n background: var(--dsw-specific-input-major, var(--dsw-alias-bg-layer-1, rgba(128, 128, 128, 0.1)));\n color: var(--dsw-alias-label-primary, inherit);\n font-size: 13px;\n}\n.kb-input-number { width: 90px; }\n/* All settings selects share one width so the column looks even. */\n.kb-settings select.kb-input { width: 200px; flex: 0 0 200px; }\n.kb-textarea { min-height: 110px; resize: vertical; font-family: inherit; }\n.kb-modal-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 14px; }\n.kb-detail-section { margin-bottom: 12px; }\n.kb-detail-label { font-size: 12px; opacity: 0.65; margin-bottom: 4px; }\n.kb-detail-skill {\n display: inline-block;\n font-size: 11px;\n background: rgba(139, 92, 246, 0.22);\n border-radius: 4px;\n padding: 1px 6px;\n margin-bottom: 4px;\n}\n.kb-detail-text { white-space: pre-wrap; word-break: break-all; font-size: 13px; }\n.kb-detail-error .kb-detail-text { color: #f87171; }\n.kb-detail-status {\n font-size: 12px;\n font-weight: 600;\n border-radius: 4px;\n padding: 1px 8px;\n background: rgba(128, 128, 128, 0.2);\n}\n.kb-status-running, .kb-status-merging { background: rgba(59, 130, 246, 0.3) !important; }\n.kb-status-error { background: rgba(239, 68, 68, 0.3) !important; color: #f87171; }\n.kb-status-merged { background: rgba(34, 197, 94, 0.3) !important; }\n.kb-plan-summary { font-size: 13px; margin-bottom: 10px; }\n/* Phase cards: each plan phase is a card with an ordered number (kept from the\n parent <ol>) and three labeled sections (goal / sessions / conclusion) split\n by section titles and thin divider lines. */\n.kb-plan-phases { margin: 0; padding-left: 24px; }\n.kb-plan-phase {\n margin-bottom: 10px;\n padding: 8px 10px;\n background: rgba(128, 128, 128, 0.08);\n border: 1px solid rgba(128, 128, 128, 0.2);\n border-radius: 8px;\n}\n.kb-plan-phase-head {\n display: flex;\n gap: 8px;\n align-items: baseline;\n padding-bottom: 6px;\n margin-bottom: 6px;\n border-bottom: 1px solid rgba(128, 128, 128, 0.16);\n}\n.kb-plan-phase-title { font-weight: 600; font-size: 13px; }\n.kb-plan-phase-id { font-size: 11px; opacity: 0.5; }\n.kb-plan-phase-body { display: flex; flex-direction: column; }\n/* Section isolation: label on top, thin divider between adjacent sections. */\n.kb-plan-phase-section { display: flex; flex-direction: column; }\n.kb-plan-phase-section + .kb-plan-phase-section {\n margin-top: 6px;\n padding-top: 6px;\n border-top: 1px solid rgba(128, 128, 128, 0.14);\n}\n.kb-plan-phase-section-title { font-size: 11px; opacity: 0.55; margin-bottom: 3px; }\n.kb-plan-phase-goal { font-size: 12px; opacity: 0.85; white-space: pre-wrap; word-break: break-all; }\n.kb-plan-phase-sessions { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }\n.kb-plan-phase-conclusion { font-size: 12px; opacity: 0.85; white-space: pre-wrap; word-break: break-all; }\n.kb-plan-phase-conclusion-item + .kb-plan-phase-conclusion-item { margin-top: 6px; }\n/* Running phase keeps the blue highlight; completed phases get success color. */\n.kb-plan-phase-current { border-color: rgba(59, 130, 246, 0.6); background: rgba(59, 130, 246, 0.08); }\n.kb-plan-phase-current .kb-plan-phase-title { color: #60a5fa; }\n.kb-plan-phase-completed { border-color: rgba(34, 197, 94, 0.45); background: rgba(34, 197, 94, 0.06); }\n.kb-plan-phase-completed .kb-plan-phase-title { color: #4ade80; }\n.kb-session-links { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }\n.kb-settings { display: flex; flex-direction: column; gap: 12px; max-width: 420px; }\n.kb-settings-saved { font-size: 12px; color: #4ade80; }\n.kb-settings-group { border-top: 1px solid rgba(128, 128, 128, 0.22); padding-top: 12px; }\n.kb-settings-group-title { font-size: 12px; font-weight: 600; opacity: 0.9; margin-bottom: 10px; }\n/* Settings card (设置 → 插件配置): mirrors the built-in plugin cards\n (dsh-client-ui-settings-plugins PluginCard) — same surface, border, head\n layout, and disclosure chevron, spelled with kb-settings-card prefixed\n classes. The prefix deliberately does NOT reuse the board's `.kb-card*`\n classes: the board card carries its own padding/cursor/user-select, and\n sharing the root class would leak those onto the settings card. */\n.kb-settings-card {\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-3);\n border-radius: 12px;\n list-style: none;\n transition: border-color .16s, background .16s;\n}\n.kb-settings-card:hover { border-color: var(--dsw-alias-label-dimmed); }\n.kb-settings-card[data-open] {\n background: var(--dsw-alias-bg-layer-2);\n border-color: var(--dsw-alias-label-dimmed);\n}\n.kb-settings-card-head {\n appearance: none;\n width: 100%;\n font: inherit;\n color: inherit;\n text-align: left;\n cursor: pointer;\n background: transparent;\n border: 0;\n border-radius: 12px;\n align-items: center;\n gap: 12px;\n padding: 14px 16px;\n display: flex;\n}\n.kb-settings-card-head:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: -2px; }\n.kb-settings-card-headText { flex-direction: column; flex: 1; gap: 4px; min-width: 0; display: flex; }\n.kb-settings-card-title { color: var(--dsw-alias-label-primary); font-size: 15px; font-weight: 600; line-height: 1.4; }\n.kb-settings-card-sub { color: var(--dsw-alias-label-tertiary); font-size: 13px; line-height: 1.5; }\n.kb-settings-card-chevron {\n color: var(--dsw-alias-label-tertiary);\n flex: none;\n transition: transform .16s;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.kb-settings-card-chevron[data-open] { transform: rotate(180deg); }\n.kb-settings-card-body { border-top: 1px solid var(--dsw-alias-border-l2); margin: 0 16px; padding: 12px 0 8px; }\n.kb-footer-action {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n width: 32px;\n height: 32px;\n border-radius: 6px;\n border: none;\n background: transparent;\n /* same foreground as the settings trigger (its trigger css uses this var) */\n color: var(--dsw-alias-label-primary, inherit);\n cursor: pointer;\n}\n.kb-footer-action:hover { background: rgba(128, 128, 128, 0.18); }\n.kb-footer-action-active { background: rgba(59, 130, 246, 0.25); }\n/* Wide mode borrows the settings trigger's metrics (34px tall, 14px label,\n 18px icon) so both controls read as siblings on the same line. */\n.kb-footer-action-wide {\n width: auto;\n height: 34px;\n padding: 0 8px;\n border-radius: 12px;\n font-size: 14px;\n}\n.kb-footer-action-wide .kb-footer-label {\n font-size: 14px;\n line-height: 22px;\n}\n/* Theme following is handled by the --dsw-alias-* tokens above: they change\n value with the shell's selected light/dark appearance, so no OS-level\n prefers-color-scheme override is needed (and one would fight the app\n theme when the two disagree). */\n/* Same-line placement WITHOUT moving any DOM: the sidebar foot area becomes\n a reversed flex row, so its LAST child (the settings area) renders on the\n LEFT and the FIRST child (footer actions = the kanban button) on the RIGHT\n — exactly \"settings, then kanban\". The settings trigger flexes to fill the\n remaining width; the button keeps its content width. */\n.kb-foot-row {\n display: flex;\n flex-direction: row-reverse;\n align-items: stretch;\n gap: 4px;\n}\n.kb-foot-row > div:first-child {\n flex: none;\n width: auto;\n min-width: 0;\n display: flex;\n align-items: center;\n}\n.kb-foot-row > div:last-child {\n flex: 1;\n min-width: 0;\n}\n/* Rail (collapsed sidebar): column-reverse stacks the settings circle on top\n and the kanban icon below it, both centered; the kanban button borrows the\n settings trigger's rail chrome (36px circle) so the two match. */\n.kb-foot-rail {\n flex-direction: column-reverse;\n align-items: center;\n}\n.kb-foot-rail .kb-footer-action {\n width: 36px;\n height: 36px;\n border-radius: 50%;\n margin: 8px 0 10px;\n}\n.kb-footer-label {\n max-width: 110px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n/* Narrow screens (mobile drawer / auto-collapse breakpoint): the drawer's\n foot area is ~256-280px but the footer actions row carries the kanban\n button PLUS mobile-nav's drawer actions (Files / session log), which\n together exceed the row and squeeze the settings trigger to zero width\n (row-reverse overflows left, clipping the button). Below the shell's\n auto-collapse breakpoint, stack the settings row above the actions and\n let the actions wrap so nothing is squeezed or clipped. */\n@media (max-width: 1023px) {\n .kb-foot-row {\n flex-direction: column-reverse;\n align-items: stretch;\n }\n .kb-foot-row > div:first-child {\n flex: none;\n width: 100%;\n display: flex;\n flex-wrap: wrap;\n justify-content: flex-start;\n gap: 4px;\n padding-top: 4px;\n }\n .kb-foot-row > div:last-child {\n flex: none;\n width: 100%;\n }\n /* The footer-actions entries are display:contents wrappers, so the kanban\n button itself is a flex item of the wrapping actions row; pull it to the\n first wrap position so it sits directly below the settings row (the\n mobile-nav drawer actions follow on their own wrapped line). */\n .kb-foot-row .kb-footer-action {\n order: -1;\n }\n}";
|
|
408
|
-
const tagId = "@fonlan/dsh-task-kanban/board.css#
|
|
383
|
+
const css = "/* @fonlan/dsh-task-kanban board styles (kb- prefixed, plain CSS).\n Surfaces use the DSH theme tokens (--dsw-alias-*) so they follow the\n shell's selected light/dark appearance; the var() fallbacks cover shells\n that do not define them. */\n.kb-board {\n height: 100%;\n display: flex;\n flex-direction: column;\n box-sizing: border-box;\n padding: 12px 16px;\n overflow: hidden;\n color: var(--dsw-alias-label-primary, var(--dsh-fg, #e6e6e6));\n background: var(--dsw-alias-bg-base, var(--dsh-bg, #141418));\n font: 14px/1.5 -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', 'Microsoft YaHei', sans-serif;\n}\n.kb-empty {\n align-items: center;\n justify-content: center;\n}\n.kb-empty-text { opacity: 0.6; }\n.kb-board-header {\n display: flex;\n align-items: center;\n gap: 12px;\n padding-bottom: 10px;\n border-bottom: 1px solid rgba(128, 128, 128, 0.25);\n flex: 0 0 auto;\n}\n/* Left-aligned group: workspace selector + new-task button sit adjacent. */\n.kb-board-title {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n flex: 0 1 auto;\n}\n.kb-board-title .kb-btn { flex: none; }\n.kb-ws-select {\n max-width: 320px;\n min-width: 0;\n flex: 0 1 auto;\n padding: 6px 10px;\n border-radius: 6px;\n border: 1px solid var(--dsw-alias-border-l2-darkmode-thin, var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35)));\n background: var(--dsw-specific-input-major, var(--dsw-alias-bg-layer-1, rgba(128, 128, 128, 0.12)));\n color: var(--dsw-alias-label-primary, inherit);\n font-size: 14px;\n}\n.kb-btn {\n padding: 6px 14px;\n border-radius: 6px;\n border: 1px solid rgba(128, 128, 128, 0.35);\n background: rgba(128, 128, 128, 0.12);\n color: inherit;\n font-size: 13px;\n white-space: nowrap;\n cursor: pointer;\n}\n.kb-btn:hover { background: rgba(128, 128, 128, 0.22); }\n.kb-btn:disabled { opacity: 0.5; cursor: default; }\n.kb-btn-primary { background: #3b82f6; border-color: #3b82f6; color: #fff; }\n.kb-btn-primary:hover { background: #2f6fe0; }\n.kb-btn-danger { background: rgba(239, 68, 68, 0.15); border-color: rgba(239, 68, 68, 0.5); color: #f87171; }\n.kb-btn-small { padding: 2px 8px; font-size: 12px; }\n.kb-lanes {\n flex: 1 1 auto;\n display: flex;\n gap: 12px;\n overflow-x: auto;\n overflow-y: hidden;\n padding-top: 10px;\n min-height: 0;\n}\n.kb-lane {\n flex: 1 1 0;\n min-width: 220px;\n max-width: 320px;\n display: flex;\n flex-direction: column;\n background: rgba(128, 128, 128, 0.07);\n border-radius: 8px;\n border: 1px solid rgba(128, 128, 128, 0.12);\n overflow: hidden;\n}\n.kb-lane-drop { border-style: dashed; border-color: rgba(59, 130, 246, 0.45); }\n.kb-lane-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 10px;\n font-weight: 600;\n font-size: 13px;\n color: var(--dsw-alias-label-secondary, var(--dsh-fg-soft, #b8b8c0));\n border-bottom: 1px solid rgba(128, 128, 128, 0.12);\n flex: 0 0 auto;\n}\n.kb-lane-count {\n background: rgba(128, 128, 128, 0.18);\n border-radius: 10px;\n padding: 0 8px;\n font-size: 12px;\n}\n.kb-lane-body {\n flex: 1 1 auto;\n overflow-y: auto;\n padding: 8px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.kb-card {\n background: rgba(128, 128, 128, 0.1);\n border: 1px solid rgba(128, 128, 128, 0.2);\n border-radius: 8px;\n padding: 8px 10px;\n cursor: pointer;\n user-select: none;\n}\n.kb-card:hover { border-color: rgba(128, 128, 128, 0.45); }\n.kb-card[draggable='true'] { cursor: grab; }\n.kb-card-running { border-color: rgba(59, 130, 246, 0.6); }\n.kb-card-error { border-color: rgba(239, 68, 68, 0.6); }\n.kb-card-title { font-weight: 600; font-size: 13px; margin-bottom: 6px; word-break: break-all; }\n.kb-card-meta { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; font-size: 12px; }\n.kb-card-badge {\n background: rgba(128, 128, 128, 0.2);\n border-radius: 4px;\n padding: 1px 6px;\n}\n.kb-card-running .kb-card-badge { background: rgba(59, 130, 246, 0.3); }\n.kb-card-error .kb-card-badge { background: rgba(239, 68, 68, 0.3); }\n.kb-card-phase { opacity: 0.8; }\n.kb-card-skill {\n background: rgba(139, 92, 246, 0.22);\n border-radius: 4px;\n padding: 1px 6px;\n}\n.kb-card-model { opacity: 0.55; }\n.kb-card-error-text { margin-top: 6px; font-size: 12px; color: #f87171; word-break: break-all; }\n.kb-card-worktree { margin-top: 4px; font-size: 11px; opacity: 0.5; word-break: break-all; }\n.kb-toast {\n position: fixed;\n left: 50%;\n bottom: 24px;\n transform: translateX(-50%);\n background: var(--dsw-alias-bg-layer-2);\n border: 1px solid var(--dsw-alias-border-l2);\n color: var(--dsw-alias-label-primary);\n padding: 8px 16px;\n border-radius: 8px;\n font-size: 13px;\n z-index: 2147483000;\n max-width: 70vw;\n}\n.kb-modal-backdrop {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.5);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 2147482900;\n}\n.kb-modal {\n width: 460px;\n max-width: 90vw;\n max-height: 85vh;\n overflow-y: auto;\n background: var(--dsw-alias-bg-layer-1);\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 12px;\n padding: 18px;\n color: var(--dsw-alias-label-primary);\n}\n.kb-modal-wide { width: 640px; }\n/* Task-detail bottom panel: docks at the bottom of the board page, fills the\n full board width and exactly half of the board height (the lanes column\n above flexes to the remaining space). */\n.kb-detail-panel {\n flex: 0 0 50%;\n min-height: 0;\n display: flex;\n flex-direction: column;\n margin-top: 10px;\n background: var(--dsw-alias-bg-layer-2);\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 12px;\n overflow: hidden;\n color: var(--dsw-alias-label-primary);\n}\n.kb-detail-panel-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n padding: 10px 14px;\n border-bottom: 1px solid rgba(128, 128, 128, 0.2);\n flex: 0 0 auto;\n}\n.kb-detail-panel-head .kb-modal-title { margin-bottom: 0; }\n.kb-detail-panel-body {\n flex: 1 1 auto;\n min-height: 0;\n overflow-y: auto;\n padding: 14px;\n}\n.kb-detail-close {\n flex: none;\n width: 28px;\n height: 28px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: 1px solid transparent;\n border-radius: 6px;\n background: transparent;\n color: inherit;\n font-size: 15px;\n line-height: 1;\n opacity: 0.75;\n cursor: pointer;\n}\n.kb-detail-close:hover { background: rgba(128, 128, 128, 0.18); opacity: 1; }\n.kb-modal-title { font-size: 16px; font-weight: 700; margin-bottom: 14px; display: flex; align-items: center; gap: 10px; }\n.kb-field { display: block; margin-bottom: 12px; }\n.kb-field > span { display: block; font-size: 12px; opacity: 0.75; margin-bottom: 4px; }\n.kb-field-hint {\n display: block;\n font-size: 11px;\n opacity: 0.55;\n margin-top: 4px;\n}\n.kb-field-hint-skill {\n opacity: 0.9;\n color: #a78bfa;\n}\n.kb-skill-menu-wrap { position: relative; }\n.kb-skill-menu {\n position: absolute;\n top: 100%;\n left: 0;\n right: 0;\n z-index: 20;\n margin: 2px 0 0;\n padding: 4px;\n list-style: none;\n max-height: 180px;\n overflow-y: auto;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-overlay);\n color: var(--dsw-alias-label-primary);\n box-shadow: 0 6px 20px rgba(0, 0, 0, 0.35);\n}\n.kb-skill-menu-item {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 6px 8px;\n border-radius: 6px;\n cursor: pointer;\n font-size: 12px;\n color: var(--dsw-alias-label-primary);\n}\n.kb-skill-menu-item-active { background: var(--dsw-alias-interactive-bg-hover); }\n.kb-skill-menu-name { font-weight: 600; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }\n.kb-skill-menu-desc {\n color: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.kb-field-row { display: flex; align-items: center; gap: 10px; justify-content: space-between; }\n/* Field labels in a row never wrap; selects get the remaining fixed width. */\n.kb-field-row > span { white-space: nowrap; flex-shrink: 0; margin-bottom: 0; }\n.kb-input {\n width: 100%;\n box-sizing: border-box;\n padding: 7px 10px;\n border-radius: 6px;\n /* Form controls follow the shell theme: DSH's input surface token plus a\n theme-tuned border; the fallbacks cover shells without these tokens. */\n border: 1px solid var(--dsw-alias-border-l2-darkmode-thin, var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35)));\n background: var(--dsw-specific-input-major, var(--dsw-alias-bg-layer-1, rgba(128, 128, 128, 0.1)));\n color: var(--dsw-alias-label-primary, inherit);\n font-size: 13px;\n}\n.kb-input-number { width: 90px; }\n/* All settings selects share one width so the column looks even. */\n.kb-settings select.kb-input { width: 200px; flex: 0 0 200px; }\n.kb-textarea { min-height: 110px; resize: vertical; font-family: inherit; }\n.kb-modal-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 14px; }\n.kb-detail-section { margin-bottom: 12px; }\n.kb-detail-label { font-size: 12px; opacity: 0.65; margin-bottom: 4px; }\n.kb-detail-skill {\n display: inline-block;\n font-size: 11px;\n background: rgba(139, 92, 246, 0.22);\n border-radius: 4px;\n padding: 1px 6px;\n margin-bottom: 4px;\n}\n.kb-detail-text { white-space: pre-wrap; word-break: break-all; font-size: 13px; }\n.kb-detail-error .kb-detail-text { color: #f87171; }\n.kb-detail-status {\n font-size: 12px;\n font-weight: 600;\n border-radius: 4px;\n padding: 1px 8px;\n background: rgba(128, 128, 128, 0.2);\n}\n.kb-status-running, .kb-status-merging { background: rgba(59, 130, 246, 0.3) !important; }\n.kb-status-error { background: rgba(239, 68, 68, 0.3) !important; color: #f87171; }\n.kb-status-merged { background: rgba(34, 197, 94, 0.3) !important; }\n.kb-plan-summary { font-size: 13px; margin-bottom: 10px; }\n/* Phase cards: each plan phase is a card with an ordered number (kept from the\n parent <ol>) and three labeled sections (goal / sessions / conclusion) split\n by section titles and thin divider lines. */\n.kb-plan-phases { margin: 0; padding-left: 24px; }\n.kb-plan-phase {\n margin-bottom: 10px;\n padding: 8px 10px;\n background: rgba(128, 128, 128, 0.08);\n border: 1px solid rgba(128, 128, 128, 0.2);\n border-radius: 8px;\n}\n.kb-plan-phase-head {\n display: flex;\n gap: 8px;\n align-items: baseline;\n padding-bottom: 6px;\n margin-bottom: 6px;\n border-bottom: 1px solid rgba(128, 128, 128, 0.16);\n}\n.kb-plan-phase-title { font-weight: 600; font-size: 13px; }\n.kb-plan-phase-id { font-size: 11px; opacity: 0.5; }\n.kb-plan-phase-body { display: flex; flex-direction: column; }\n/* Section isolation: label on top, thin divider between adjacent sections. */\n.kb-plan-phase-section { display: flex; flex-direction: column; }\n.kb-plan-phase-section + .kb-plan-phase-section {\n margin-top: 6px;\n padding-top: 6px;\n border-top: 1px solid rgba(128, 128, 128, 0.14);\n}\n.kb-plan-phase-section-title { font-size: 11px; opacity: 0.55; margin-bottom: 3px; }\n.kb-plan-phase-goal { font-size: 12px; opacity: 0.85; white-space: pre-wrap; word-break: break-all; }\n.kb-plan-phase-sessions { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }\n.kb-plan-phase-conclusion { font-size: 12px; opacity: 0.85; white-space: pre-wrap; word-break: break-all; }\n.kb-plan-phase-conclusion-item + .kb-plan-phase-conclusion-item { margin-top: 6px; }\n/* Running phase keeps the blue highlight; completed phases get success color. */\n.kb-plan-phase-current { border-color: rgba(59, 130, 246, 0.6); background: rgba(59, 130, 246, 0.08); }\n.kb-plan-phase-current .kb-plan-phase-title { color: #60a5fa; }\n.kb-plan-phase-completed { border-color: rgba(34, 197, 94, 0.45); background: rgba(34, 197, 94, 0.06); }\n.kb-plan-phase-completed .kb-plan-phase-title { color: #4ade80; }\n.kb-session-links { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }\n.kb-settings { display: flex; flex-direction: column; gap: 12px; max-width: 420px; }\n.kb-settings-saved { font-size: 12px; color: #4ade80; }\n.kb-settings-group { border-top: 1px solid rgba(128, 128, 128, 0.22); padding-top: 12px; }\n.kb-settings-group-title { font-size: 12px; font-weight: 600; opacity: 0.9; margin-bottom: 10px; }\n/* Settings page (设置 -> 侧栏「任务看板」): the page owns one entry in the\n settings sidebar and renders into the panel's content column, so it draws\n its own header (title plus a one-line summary) above the always-visible\n body. The body keeps the board's kb-settings control classes. */\n.kb-page {\n display: flex;\n flex-direction: column;\n gap: 16px;\n max-width: 720px;\n}\n.kb-page-head {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n.kb-page-title {\n margin: 0;\n color: var(--dsw-alias-label-primary);\n font-size: 15px;\n font-weight: 600;\n line-height: 1.4;\n}\n.kb-page-sub {\n margin: 0;\n color: var(--dsw-alias-label-tertiary);\n font-size: 13px;\n line-height: 1.5;\n}\n";
|
|
384
|
+
const tagId = "@fonlan/dsh-task-kanban/board.css#3d30e9c5";
|
|
409
385
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
410
386
|
const tag = document.createElement("style");
|
|
411
387
|
tag.dataset.plugin = "@fonlan/dsh-task-kanban";
|
|
@@ -554,7 +530,7 @@ window.__ModuleLoader__.load({
|
|
|
554
530
|
]);
|
|
555
531
|
const openSession = (0, react.useCallback)((sessionId) => {
|
|
556
532
|
const ctx = getClient();
|
|
557
|
-
|
|
533
|
+
leaveKanbanPanel(ctx);
|
|
558
534
|
if (ctx === null) return;
|
|
559
535
|
try {
|
|
560
536
|
ctx.sessions.open(sessionId);
|
|
@@ -1175,112 +1151,81 @@ window.__ModuleLoader__.load({
|
|
|
1175
1151
|
});
|
|
1176
1152
|
}
|
|
1177
1153
|
//#endregion
|
|
1178
|
-
//#region src/client/
|
|
1179
|
-
/**
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
const ref = (0, react.useRef)(null);
|
|
1186
|
-
const label = t !== void 0 ? t(open ? "closeBoard" : "openBoard") : "Task Kanban";
|
|
1187
|
-
(0, react.useEffect)(() => {
|
|
1188
|
-
const btn = ref.current;
|
|
1189
|
-
if (btn === null) return;
|
|
1190
|
-
let foot = null;
|
|
1191
|
-
let disposed = false;
|
|
1192
|
-
const findFoot = () => {
|
|
1193
|
-
let el = btn.parentElement;
|
|
1194
|
-
while (el !== null && el !== document.body && el.parentElement !== null) {
|
|
1195
|
-
const last = el.lastElementChild;
|
|
1196
|
-
const hasTrigger = last !== null && (last.querySelector?.("button[aria-haspopup=\"dialog\"]") !== null || last.querySelector?.("button[aria-haspopup=\"menu\"]") !== null);
|
|
1197
|
-
const isLastChild = el.parentElement.lastElementChild === el;
|
|
1198
|
-
if (hasTrigger && isLastChild) return el;
|
|
1199
|
-
el = el.parentElement;
|
|
1200
|
-
}
|
|
1201
|
-
return null;
|
|
1202
|
-
};
|
|
1203
|
-
const tryApply = () => {
|
|
1204
|
-
if (foot === null) try {
|
|
1205
|
-
foot = findFoot();
|
|
1206
|
-
} catch {
|
|
1207
|
-
foot = null;
|
|
1208
|
-
}
|
|
1209
|
-
if (foot === null) return false;
|
|
1210
|
-
try {
|
|
1211
|
-
foot.classList.add("kb-foot-row");
|
|
1212
|
-
foot.classList.toggle("kb-foot-rail", wide !== true);
|
|
1213
|
-
} catch {}
|
|
1214
|
-
return true;
|
|
1215
|
-
};
|
|
1216
|
-
let applied = false;
|
|
1154
|
+
//#region src/client/icons.tsx
|
|
1155
|
+
/** The runtime export table, read by capability instead of by one hard-coded name. */
|
|
1156
|
+
const table = _deepseek_ai_dsh_client_ui_primitives;
|
|
1157
|
+
/** First name the runtime actually provides (a missing export is `undefined`). */
|
|
1158
|
+
function firstDefined(...names) {
|
|
1159
|
+
for (const name of names) {
|
|
1160
|
+
let candidate;
|
|
1217
1161
|
try {
|
|
1218
|
-
|
|
1219
|
-
} catch
|
|
1220
|
-
|
|
1221
|
-
}
|
|
1222
|
-
if (!applied) {
|
|
1223
|
-
const observer = new MutationObserver(() => {
|
|
1224
|
-
if (disposed) return;
|
|
1225
|
-
if (tryApply()) observer.disconnect();
|
|
1226
|
-
});
|
|
1227
|
-
observer.observe(document.body, {
|
|
1228
|
-
childList: true,
|
|
1229
|
-
subtree: true
|
|
1230
|
-
});
|
|
1231
|
-
const timer = window.setInterval(() => {
|
|
1232
|
-
if (disposed) {
|
|
1233
|
-
window.clearInterval(timer);
|
|
1234
|
-
return;
|
|
1235
|
-
}
|
|
1236
|
-
if (tryApply()) {
|
|
1237
|
-
observer.disconnect();
|
|
1238
|
-
window.clearInterval(timer);
|
|
1239
|
-
}
|
|
1240
|
-
}, 1e3);
|
|
1241
|
-
window.setTimeout(() => {
|
|
1242
|
-
observer.disconnect();
|
|
1243
|
-
window.clearInterval(timer);
|
|
1244
|
-
if (foot === null) {
|
|
1245
|
-
const chain = [];
|
|
1246
|
-
let el = btn.parentElement;
|
|
1247
|
-
while (el !== null && chain.length < 8) {
|
|
1248
|
-
chain.push(el.tagName + "." + String(el.className).slice(0, 60));
|
|
1249
|
-
el = el.parentElement;
|
|
1250
|
-
}
|
|
1251
|
-
console.warn("[@fonlan/dsh-task-kanban] could not locate the sidebar foot area:", chain);
|
|
1252
|
-
}
|
|
1253
|
-
}, 15e3);
|
|
1254
|
-
return () => {
|
|
1255
|
-
disposed = true;
|
|
1256
|
-
observer.disconnect();
|
|
1257
|
-
window.clearInterval(timer);
|
|
1258
|
-
try {
|
|
1259
|
-
foot?.classList.remove("kb-foot-row");
|
|
1260
|
-
foot?.classList.remove("kb-foot-rail");
|
|
1261
|
-
} catch {}
|
|
1262
|
-
};
|
|
1162
|
+
candidate = table[name];
|
|
1163
|
+
} catch {
|
|
1164
|
+
continue;
|
|
1263
1165
|
}
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1166
|
+
if (typeof candidate === "function") return candidate;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
/**
|
|
1170
|
+
* Self-contained 16×16 checklist glyph (the 0.1.7 artwork) used when the shell
|
|
1171
|
+
* primitives expose none of the names above: the footer entry must render
|
|
1172
|
+
* something even on a runtime whose icon exports we do not know.
|
|
1173
|
+
*/
|
|
1174
|
+
function ChecklistFallback({ size = 14, className }) {
|
|
1175
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
1176
|
+
width: size,
|
|
1177
|
+
height: size,
|
|
1178
|
+
className,
|
|
1179
|
+
viewBox: "0 0 16 16",
|
|
1180
|
+
fill: "none",
|
|
1181
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1182
|
+
"aria-hidden": "true",
|
|
1183
|
+
strokeWidth: 1,
|
|
1184
|
+
children: [
|
|
1185
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
1186
|
+
d: "M3.75 6.25C4.7165 6.25 5.5 5.4665 5.5 4.5C5.5 3.5335 4.7165 2.75 3.75 2.75C2.7835 2.75 2 3.5335 2 4.5C2 5.4665 2.7835 6.25 3.75 6.25Z",
|
|
1187
|
+
stroke: "currentColor"
|
|
1188
|
+
}),
|
|
1189
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
1190
|
+
d: "M7.5 4.5H13.5",
|
|
1191
|
+
stroke: "currentColor"
|
|
1192
|
+
}),
|
|
1193
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
1194
|
+
d: "M3.75 13.25C4.7165 13.25 5.5 12.4665 5.5 11.5C5.5 10.5335 4.7165 9.75 3.75 9.75C2.7835 9.75 2 10.5335 2 11.5C2 12.4665 2.7835 13.25 3.75 13.25Z",
|
|
1195
|
+
stroke: "currentColor"
|
|
1196
|
+
}),
|
|
1197
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
1198
|
+
d: "M7.5 11.5H13.5",
|
|
1199
|
+
stroke: "currentColor"
|
|
1200
|
+
})
|
|
1201
|
+
]
|
|
1282
1202
|
});
|
|
1283
1203
|
}
|
|
1204
|
+
/**
|
|
1205
|
+
* The checklist icon used by the sidebar panel row entry.
|
|
1206
|
+
*
|
|
1207
|
+
* DSH 0.1.7 renamed every size-suffixed shell icon (`IconChecklistOutline14`)
|
|
1208
|
+
* into stroke-weight variants (`…Regular` 1px / `…Medium` 1.3px) inside
|
|
1209
|
+
* `@deepseek-ai/dsh-client-ui-primitives`. A stale named import resolves to
|
|
1210
|
+
* `undefined` and React then throws "Element type is invalid" while rendering
|
|
1211
|
+
* the slot entry — the entry is dropped and the button silently disappears
|
|
1212
|
+
* (exactly what upgrading to 0.1.7 did to this plugin). Resolve the name at
|
|
1213
|
+
* runtime, newest first, so the next rename degrades to another shipped
|
|
1214
|
+
* variant instead of taking the button down with it.
|
|
1215
|
+
*/
|
|
1216
|
+
const ChecklistIcon = firstDefined("IconChecklistOutlineRegular", "IconChecklistOutlineMedium", "IconChecklistOutline", "IconChecklistOutline14") ?? ChecklistFallback;
|
|
1217
|
+
//#endregion
|
|
1218
|
+
//#region src/client/sections.tsx
|
|
1219
|
+
/**
|
|
1220
|
+
* Sidebar panel-row glyph for the board (`sidebar.panellist`).
|
|
1221
|
+
*
|
|
1222
|
+
* The shell owns the row: it renders the button, its label, tooltip and active
|
|
1223
|
+
* state, and calls `layout.selectPanel(id)` on click. The entry contributes
|
|
1224
|
+
* only the glyph, so this component must stay purely presentational.
|
|
1225
|
+
*/
|
|
1226
|
+
function KanbanPanelIcon({ size }) {
|
|
1227
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChecklistIcon, { size });
|
|
1228
|
+
}
|
|
1284
1229
|
/** Fallback shape while settings load (server always resolves the full set). */
|
|
1285
1230
|
const EMPTY_SETTINGS = {
|
|
1286
1231
|
maxParallelWorkers: 1,
|
|
@@ -1381,10 +1326,9 @@ window.__ModuleLoader__.load({
|
|
|
1381
1326
|
]
|
|
1382
1327
|
});
|
|
1383
1328
|
}
|
|
1384
|
-
/** Plugin settings
|
|
1385
|
-
function
|
|
1329
|
+
/** Plugin settings page (设置 -> 侧栏「任务看板」): workers + default model + per-session-type defaults. */
|
|
1330
|
+
function KanbanSettingsSection({ t }) {
|
|
1386
1331
|
const tr = t ?? ((key) => key);
|
|
1387
|
-
const [open, setOpen] = (0, react.useState)(false);
|
|
1388
1332
|
const [settings, setSettings] = (0, react.useState)(EMPTY_SETTINGS);
|
|
1389
1333
|
const [models, setModels] = (0, react.useState)([]);
|
|
1390
1334
|
const [presets, setPresets] = (0, react.useState)([]);
|
|
@@ -1466,96 +1410,70 @@ window.__ModuleLoader__.load({
|
|
|
1466
1410
|
if (!opts.efforts.some((o) => o.id === effort)) commitRef.current({ [fields.effort]: "" });
|
|
1467
1411
|
}
|
|
1468
1412
|
}, [efforts]);
|
|
1469
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("
|
|
1470
|
-
className: "kb-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
className: "kb-settings-card-headText",
|
|
1480
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1481
|
-
className: "kb-settings-card-title",
|
|
1482
|
-
children: tr("settingsTitle")
|
|
1483
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1484
|
-
className: "kb-settings-card-sub",
|
|
1485
|
-
children: tr("settingsDescription")
|
|
1486
|
-
})]
|
|
1487
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1488
|
-
className: "kb-settings-card-chevron",
|
|
1489
|
-
"data-open": open ? "" : void 0,
|
|
1490
|
-
"aria-hidden": "true",
|
|
1491
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
1492
|
-
width: "14",
|
|
1493
|
-
height: "14",
|
|
1494
|
-
viewBox: "0 0 14 14",
|
|
1495
|
-
fill: "none",
|
|
1496
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
1497
|
-
d: "M3.5 5.25L7 8.75L10.5 5.25",
|
|
1498
|
-
stroke: "currentColor",
|
|
1499
|
-
strokeWidth: "1.5",
|
|
1500
|
-
strokeLinecap: "round",
|
|
1501
|
-
strokeLinejoin: "round"
|
|
1502
|
-
})
|
|
1503
|
-
})
|
|
1413
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1414
|
+
className: "kb-page",
|
|
1415
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
1416
|
+
className: "kb-page-head",
|
|
1417
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1418
|
+
className: "kb-page-title",
|
|
1419
|
+
children: tr("settingsTitle")
|
|
1420
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1421
|
+
className: "kb-page-sub",
|
|
1422
|
+
children: tr("sectionSub")
|
|
1504
1423
|
})]
|
|
1505
|
-
}),
|
|
1506
|
-
className: "kb-settings
|
|
1507
|
-
children:
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
1511
|
-
className: "kb-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
value
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
})
|
|
1552
|
-
]
|
|
1553
|
-
})
|
|
1424
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1425
|
+
className: "kb-settings",
|
|
1426
|
+
children: [
|
|
1427
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1428
|
+
className: "kb-field kb-field-row",
|
|
1429
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: tr("maxParallelWorkers") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1430
|
+
className: "kb-input kb-input-number",
|
|
1431
|
+
type: "number",
|
|
1432
|
+
min: 1,
|
|
1433
|
+
step: 1,
|
|
1434
|
+
value: settings.maxParallelWorkers,
|
|
1435
|
+
onChange: (e) => {
|
|
1436
|
+
const value = Math.max(1, Math.floor(Number(e.target.value) || 1));
|
|
1437
|
+
setSettings((s) => ({
|
|
1438
|
+
...s,
|
|
1439
|
+
maxParallelWorkers: value
|
|
1440
|
+
}));
|
|
1441
|
+
commit({ maxParallelWorkers: value });
|
|
1442
|
+
}
|
|
1443
|
+
})]
|
|
1444
|
+
}),
|
|
1445
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionDefaultsGroup, {
|
|
1446
|
+
kind: "refinement",
|
|
1447
|
+
settings,
|
|
1448
|
+
models,
|
|
1449
|
+
presets,
|
|
1450
|
+
efforts,
|
|
1451
|
+
tr,
|
|
1452
|
+
onCommit: commit,
|
|
1453
|
+
loadEfforts
|
|
1454
|
+
}),
|
|
1455
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionDefaultsGroup, {
|
|
1456
|
+
kind: "phase",
|
|
1457
|
+
settings,
|
|
1458
|
+
models,
|
|
1459
|
+
presets,
|
|
1460
|
+
efforts,
|
|
1461
|
+
tr,
|
|
1462
|
+
onCommit: commit,
|
|
1463
|
+
loadEfforts
|
|
1464
|
+
}),
|
|
1465
|
+
saved && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1466
|
+
className: "kb-settings-saved",
|
|
1467
|
+
children: tr("saved")
|
|
1468
|
+
})
|
|
1469
|
+
]
|
|
1554
1470
|
})]
|
|
1555
1471
|
});
|
|
1556
1472
|
}
|
|
1557
1473
|
//#endregion
|
|
1558
1474
|
//#region src/client/index.tsx
|
|
1475
|
+
/** The settings sidebar entry this plugin's page owns (must stay stable). */
|
|
1476
|
+
const SETTINGS_SECTION_ID = "task-kanban";
|
|
1559
1477
|
/** Services required before mounting (provided by the client runtime). */
|
|
1560
1478
|
const inject = [
|
|
1561
1479
|
"slots",
|
|
@@ -1566,7 +1484,6 @@ window.__ModuleLoader__.load({
|
|
|
1566
1484
|
/** Client plugin body. */
|
|
1567
1485
|
function apply(ctx) {
|
|
1568
1486
|
setClient(ctx);
|
|
1569
|
-
setBoardRoot(BoardRoot);
|
|
1570
1487
|
bindSessionNavigation(ctx);
|
|
1571
1488
|
const t = ctx.locale.bind(LOCALE_NS);
|
|
1572
1489
|
ctx.effect(() => {
|
|
@@ -1576,18 +1493,25 @@ window.__ModuleLoader__.load({
|
|
|
1576
1493
|
});
|
|
1577
1494
|
return () => off();
|
|
1578
1495
|
}, "task-kanban: dictionaries");
|
|
1579
|
-
ctx.slots.inject("
|
|
1580
|
-
name: "
|
|
1581
|
-
|
|
1582
|
-
|
|
1496
|
+
ctx.slots.inject("main", () => ctx.slots.register({
|
|
1497
|
+
name: "main",
|
|
1498
|
+
key: KANBAN_PANEL_ID,
|
|
1499
|
+
locale: LOCALE_NS
|
|
1500
|
+
}, BoardRoot));
|
|
1501
|
+
ctx.slots.inject("sidebar.panellist", () => ctx.slots.register({
|
|
1502
|
+
name: "sidebar.panellist",
|
|
1503
|
+
id: KANBAN_PANEL_ID,
|
|
1504
|
+
order: 20,
|
|
1583
1505
|
label: () => t("kanban"),
|
|
1584
1506
|
locale: LOCALE_NS
|
|
1585
|
-
},
|
|
1586
|
-
ctx.slots.inject("settings.
|
|
1587
|
-
name: "settings.
|
|
1588
|
-
|
|
1507
|
+
}, KanbanPanelIcon));
|
|
1508
|
+
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
1509
|
+
name: "settings.section",
|
|
1510
|
+
id: SETTINGS_SECTION_ID,
|
|
1511
|
+
order: 200,
|
|
1512
|
+
label: () => t("settingsTitle"),
|
|
1589
1513
|
locale: LOCALE_NS
|
|
1590
|
-
},
|
|
1514
|
+
}, KanbanSettingsSection));
|
|
1591
1515
|
}
|
|
1592
1516
|
//#endregion
|
|
1593
1517
|
exports.apply = apply;
|