@linxin666/dsh-client-ui-task-board 0.4.1 → 0.4.3
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.i18n.yaml +2 -2
- package/README.md +28 -3
- package/README.zh.md +28 -3
- package/lib/client.js +1142 -467
- package/lib/client.js.map +1 -1
- package/lib/index.js +2066 -1475
- package/lib/types/client/TaskBoardSettingsCard.d.ts +4 -0
- package/lib/types/client/board/LinkSubtaskModal.d.ts +11 -0
- package/lib/types/client/board/NewTaskModal.d.ts +7 -1
- package/lib/types/client/board/TaskCard.d.ts +11 -1
- package/lib/types/client/index.d.ts +15 -1
- package/lib/types/client/locales.d.ts +33 -0
- package/lib/types/client/native-panel.d.ts +62 -0
- package/lib/types/client/settings-entry-form.d.ts +49 -0
- package/lib/types/core/controller.d.ts +71 -3
- package/lib/types/core/subtask.d.ts +122 -0
- package/lib/types/core/tasks.d.ts +46 -3
- package/lib/types/core/use-cases/task-archive.d.ts +10 -13
- package/lib/types/core/use-cases/task-create.d.ts +13 -4
- package/lib/types/core/use-cases/task-delete.d.ts +7 -1
- package/lib/types/core/use-cases/task-parent.d.ts +21 -0
- package/lib/types/core/use-cases/task-update.d.ts +1 -1
- package/lib/types/host/agent-tools.d.ts +27 -0
- package/lib/types/host-ledger.d.ts +97 -4
- package/lib/types/host-runner.d.ts +39 -1
- package/lib/types/host-service.d.ts +98 -5
- package/lib/types/index.d.ts +93 -5
- package/lib/types/mount-once.d.ts +38 -7
- package/lib/types/protocol.d.ts +14 -0
- package/package.json +26 -21
- package/src/client/TaskBoardSettingsCard.tsx +48 -3
- package/src/client/board/LinkSubtaskModal.tsx +82 -0
- package/src/client/board/NewTaskModal.tsx +40 -6
- package/src/client/board/TaskBoard.tsx +88 -8
- package/src/client/board/TaskCard.tsx +52 -2
- package/src/client/board/TaskDetail.tsx +124 -1
- package/src/client/board.module.css +112 -131
- package/src/client/host-api.ts +14 -4
- package/src/client/index.ts +56 -18
- package/src/client/locales.ts +70 -2
- package/src/client/native-panel.tsx +124 -0
- package/src/client/settings-entry-form.ts +142 -0
- package/src/core/controller.ts +128 -10
- package/src/core/store.ts +15 -0
- package/src/core/subtask.ts +286 -0
- package/src/core/tasks.ts +50 -2
- package/src/core/use-cases/task-archive.ts +46 -12
- package/src/core/use-cases/task-create.ts +42 -7
- package/src/core/use-cases/task-delete.ts +11 -1
- package/src/core/use-cases/task-parent.ts +64 -0
- package/src/core/use-cases/task-update.ts +3 -1
- package/src/host/agent-tools.ts +643 -0
- package/src/host-ledger.ts +343 -45
- package/src/host-routes.ts +37 -1
- package/src/host-runner.ts +141 -12
- package/src/host-service.ts +265 -32
- package/src/index.ts +203 -9
- package/src/mount-once.ts +107 -13
- package/src/protocol.ts +26 -2
- package/lib/types/client/board-mount.d.ts +0 -23
- package/lib/types/client/body-mutations.d.ts +0 -45
- package/lib/types/client/panel-mount-core.d.ts +0 -56
- package/lib/types/client/sidebar-entry-core.d.ts +0 -55
- package/lib/types/client/sidebar-entry.d.ts +0 -29
- package/src/client/board-mount.tsx +0 -43
- package/src/client/body-mutations.ts +0 -133
- package/src/client/panel-mount-core.ts +0 -155
- package/src/client/sidebar-entry-core.ts +0 -237
- package/src/client/sidebar-entry.ts +0 -53
package/lib/client.js
CHANGED
|
@@ -6,7 +6,6 @@ window.__ModuleLoader__.load({
|
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
let react = require("react");
|
|
8
8
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
|
-
let react_dom_client = require("react-dom/client");
|
|
10
9
|
/**
|
|
11
10
|
* Repair a persisted tag list: keep the well-formed entries, trim, drop
|
|
12
11
|
* blanks and repeats, cap the count, and collapse a blank prompt line to
|
|
@@ -149,6 +148,7 @@ window.__ModuleLoader__.load({
|
|
|
149
148
|
title: input.title.trim(),
|
|
150
149
|
description: input.description.trim(),
|
|
151
150
|
prompt: input.prompt.trim(),
|
|
151
|
+
parentId: normalizeTargetId(input.parentId),
|
|
152
152
|
status: "todo",
|
|
153
153
|
createdAt: now,
|
|
154
154
|
updatedAt: now,
|
|
@@ -158,6 +158,7 @@ window.__ModuleLoader__.load({
|
|
|
158
158
|
permission: isTaskPermission(input.permission) ? input.permission : void 0,
|
|
159
159
|
model: normalizeTargetId(input.model),
|
|
160
160
|
reuseSession: input.reuseSession === true ? true : void 0,
|
|
161
|
+
teamRun: input.teamRun === true ? true : void 0,
|
|
161
162
|
...input.freeze === void 0 ? {} : { freeze: freezeOf(input.freeze, now) },
|
|
162
163
|
...input.handover === void 0 ? {} : { handover: {
|
|
163
164
|
...input.handover,
|
|
@@ -205,20 +206,173 @@ window.__ModuleLoader__.load({
|
|
|
205
206
|
if (execution.result === "cancelled") return "cancelled";
|
|
206
207
|
return "running";
|
|
207
208
|
}
|
|
209
|
+
/**
|
|
210
|
+
* Clamp a configured depth into the supported range. Anything unusable
|
|
211
|
+
* (absent, non-numeric, not finite) falls back to the default, so a bad
|
|
212
|
+
* configuration narrows the tree instead of lifting the guard.
|
|
213
|
+
*/
|
|
214
|
+
function normalizeSubtaskDepth(value) {
|
|
215
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return 1;
|
|
216
|
+
const depth = Math.trunc(value);
|
|
217
|
+
if (depth < 1) return 1;
|
|
218
|
+
if (depth > 3) return 3;
|
|
219
|
+
return depth;
|
|
220
|
+
}
|
|
221
|
+
/** Whether an unknown value is a settled execution outcome. */
|
|
222
|
+
function isExecutionOutcome(value) {
|
|
223
|
+
return value === "succeeded" || value === "failed" || value === "cancelled";
|
|
224
|
+
}
|
|
225
|
+
function indexOf(tasks) {
|
|
226
|
+
return new Map(tasks.map((task) => [task.id, task]));
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Nearest-first ancestor chain of a task. The walk is cycle-safe and bounded
|
|
230
|
+
* by the ledger size rather than by the deployment maximum: a chain stored
|
|
231
|
+
* under a larger limit survives that limit being lowered, and restore and
|
|
232
|
+
* inheritance must still see all of it.
|
|
233
|
+
*/
|
|
234
|
+
function ancestorChain(tasks, task) {
|
|
235
|
+
const index = indexOf(tasks);
|
|
236
|
+
const chain = [];
|
|
237
|
+
const seen = /* @__PURE__ */ new Set([task.id]);
|
|
238
|
+
let current = task.parentId === void 0 ? void 0 : index.get(task.parentId);
|
|
239
|
+
while (current !== void 0 && chain.length < tasks.length) {
|
|
240
|
+
if (seen.has(current.id)) break;
|
|
241
|
+
seen.add(current.id);
|
|
242
|
+
chain.push(current);
|
|
243
|
+
current = current.parentId === void 0 ? void 0 : index.get(current.parentId);
|
|
244
|
+
}
|
|
245
|
+
return chain;
|
|
246
|
+
}
|
|
247
|
+
/** Root-to-task depth: a root task is 0, its subtask 1, and so on. */
|
|
248
|
+
function taskDepth(tasks, id) {
|
|
249
|
+
const task = indexOf(tasks).get(id);
|
|
250
|
+
return task === void 0 ? 0 : ancestorChain(tasks, task).length;
|
|
251
|
+
}
|
|
252
|
+
/** Direct subtasks of a task, in ledger order. */
|
|
253
|
+
function directSubtasks(tasks, id) {
|
|
254
|
+
return tasks.filter((task) => task.parentId === id);
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Descendants of a task in breadth-first order, bounded by maxSubtaskDepth
|
|
258
|
+
* total depth. The visited set keeps a malformed ledger from looping.
|
|
259
|
+
*/
|
|
260
|
+
function descendantTasks(tasks, id, maxSubtaskDepth) {
|
|
261
|
+
const visited = /* @__PURE__ */ new Set([id]);
|
|
262
|
+
const found = [];
|
|
263
|
+
let frontier = [id];
|
|
264
|
+
for (let depth = 1; depth <= maxSubtaskDepth && frontier.length > 0; depth += 1) {
|
|
265
|
+
const next = [];
|
|
266
|
+
for (const parentId of frontier) for (const task of tasks) {
|
|
267
|
+
if (task.parentId !== parentId || visited.has(task.id)) continue;
|
|
268
|
+
visited.add(task.id);
|
|
269
|
+
found.push(task);
|
|
270
|
+
next.push(task.id);
|
|
271
|
+
}
|
|
272
|
+
frontier = next;
|
|
273
|
+
}
|
|
274
|
+
return found;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Height of a task's own subtree (0 for a leaf), bounded by the hard maximum.
|
|
278
|
+
* The visited set makes the walk terminate on a cyclic hand-edited ledger
|
|
279
|
+
* instead of recursing until the stack overflows.
|
|
280
|
+
*/
|
|
281
|
+
function subtreeHeight(tasks, id, visited = /* @__PURE__ */ new Set()) {
|
|
282
|
+
if (visited.has(id)) return 0;
|
|
283
|
+
const seen = /* @__PURE__ */ new Set([...visited, id]);
|
|
284
|
+
const children = directSubtasks(tasks, id);
|
|
285
|
+
if (children.length === 0) return 0;
|
|
286
|
+
let height = 0;
|
|
287
|
+
for (const child of children) height = Math.max(height, 1 + subtreeHeight(tasks, child.id, seen));
|
|
288
|
+
return Math.min(height, 3);
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Whether childId may be attached under parentId. A null parent detaches and
|
|
292
|
+
* is always allowed for a known task. Otherwise the link must name an
|
|
293
|
+
* on-board task that is neither the child itself nor one of its descendants,
|
|
294
|
+
* and the resulting depth (parent depth + 1 + the child own subtree height)
|
|
295
|
+
* must stay within maxSubtaskDepth.
|
|
296
|
+
*/
|
|
297
|
+
function checkParentLink(tasks, childId, parentId, maxSubtaskDepth) {
|
|
298
|
+
if (tasks.find((task) => task.id === childId) === void 0) return {
|
|
299
|
+
ok: false,
|
|
300
|
+
reason: "unknown-task"
|
|
301
|
+
};
|
|
302
|
+
if (parentId === null || parentId === "") return { ok: true };
|
|
303
|
+
if (parentId === childId) return {
|
|
304
|
+
ok: false,
|
|
305
|
+
reason: "self-parent"
|
|
306
|
+
};
|
|
307
|
+
const parent = tasks.find((task) => task.id === parentId);
|
|
308
|
+
if (parent === void 0) return {
|
|
309
|
+
ok: false,
|
|
310
|
+
reason: "unknown-parent"
|
|
311
|
+
};
|
|
312
|
+
if (parent.archivedAt !== void 0) return {
|
|
313
|
+
ok: false,
|
|
314
|
+
reason: "archived-parent"
|
|
315
|
+
};
|
|
316
|
+
if (descendantTasks(tasks, childId, 3).some((task) => task.id === parentId)) return {
|
|
317
|
+
ok: false,
|
|
318
|
+
reason: "cycle"
|
|
319
|
+
};
|
|
320
|
+
if (taskDepth(tasks, parentId) + 1 + subtreeHeight(tasks, childId) > maxSubtaskDepth) return {
|
|
321
|
+
ok: false,
|
|
322
|
+
reason: "depth-exceeded"
|
|
323
|
+
};
|
|
324
|
+
return { ok: true };
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* The permission a task would actually run under: its handover bundle's pin
|
|
328
|
+
* wins over the plain pin field, exactly as the runner resolves it.
|
|
329
|
+
*/
|
|
330
|
+
function effectiveTaskPermission(task) {
|
|
331
|
+
return task.handover?.permission ?? task.permission;
|
|
332
|
+
}
|
|
208
333
|
//#endregion
|
|
209
334
|
//#region src/core/use-cases/task-archive.ts
|
|
210
335
|
/**
|
|
211
|
-
* Archive
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
336
|
+
* Archive/restore task use case: move a task off the main board and back,
|
|
337
|
+
* whatever its status but running. The task keeps its status, execution
|
|
338
|
+
* history, and transcript references, while archiving disarms any schedule so
|
|
339
|
+
* it cannot create more execution records until the user restores and
|
|
340
|
+
* re-enables it.
|
|
341
|
+
*
|
|
342
|
+
* Subtask trees move as a unit: archiving takes the whole subtree (an on-board
|
|
343
|
+
* subtask under an archived parent would be unreachable from its parent) and
|
|
344
|
+
* refuses the whole group while any member is running. Restoring brings the
|
|
345
|
+
* task, its ancestors and its subtree back together.
|
|
346
|
+
*/
|
|
347
|
+
/** The archive/restore id set: the task, its subtree, and (for restore) its ancestors. */
|
|
348
|
+
function groupIds(tasks, id, maxSubtaskDepth, withAncestors) {
|
|
349
|
+
const target = tasks.find((task) => task.id === id);
|
|
350
|
+
const ids = /* @__PURE__ */ new Set([id]);
|
|
351
|
+
for (const task of descendantTasks(tasks, id, normalizeSubtaskDepth(maxSubtaskDepth))) ids.add(task.id);
|
|
352
|
+
if (withAncestors && target !== void 0) for (const ancestor of ancestorChain(tasks, target)) ids.add(ancestor.id);
|
|
353
|
+
return ids;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Archive one task and its subtasks: only a running member keeps the group on
|
|
357
|
+
* the board (its runner still owns its lifecycle until the execution settles);
|
|
358
|
+
* any other status can be archived. Archiving disarms schedules; an
|
|
359
|
+
* already-archived task is a no-op.
|
|
215
360
|
*/
|
|
216
|
-
function applyArchiveTask(tasks, id, now) {
|
|
361
|
+
function applyArchiveTask(tasks, id, now, maxSubtaskDepth = 1) {
|
|
362
|
+
const target = tasks.find((task) => task.id === id);
|
|
363
|
+
if (target === void 0 || target.archivedAt !== void 0) return {
|
|
364
|
+
tasks,
|
|
365
|
+
archived: false
|
|
366
|
+
};
|
|
367
|
+
const ids = groupIds(tasks, id, maxSubtaskDepth, false);
|
|
368
|
+
if (tasks.some((task) => ids.has(task.id) && !ARCHIVABLE_STATUSES.includes(task.status))) return {
|
|
369
|
+
tasks,
|
|
370
|
+
archived: false
|
|
371
|
+
};
|
|
217
372
|
let applied = false;
|
|
218
373
|
return {
|
|
219
374
|
tasks: tasks.map((task) => {
|
|
220
|
-
if (task.id
|
|
221
|
-
if (!ARCHIVABLE_STATUSES.includes(task.status)) return task;
|
|
375
|
+
if (!ids.has(task.id) || task.archivedAt !== void 0) return task;
|
|
222
376
|
applied = true;
|
|
223
377
|
const schedule = task.schedule === void 0 ? void 0 : {
|
|
224
378
|
...task.schedule,
|
|
@@ -235,12 +389,21 @@ window.__ModuleLoader__.load({
|
|
|
235
389
|
archived: applied
|
|
236
390
|
};
|
|
237
391
|
}
|
|
238
|
-
/**
|
|
239
|
-
|
|
392
|
+
/**
|
|
393
|
+
* Restore one task back onto the main board, together with its ancestors (an
|
|
394
|
+
* on-board subtask must not point at an archived parent) and its subtasks.
|
|
395
|
+
*/
|
|
396
|
+
function applyRestoreTask(tasks, id, now, maxSubtaskDepth = 1) {
|
|
397
|
+
const target = tasks.find((task) => task.id === id);
|
|
398
|
+
if (target === void 0 || target.archivedAt === void 0) return {
|
|
399
|
+
tasks,
|
|
400
|
+
archived: false
|
|
401
|
+
};
|
|
402
|
+
const ids = groupIds(tasks, id, maxSubtaskDepth, true);
|
|
240
403
|
let applied = false;
|
|
241
404
|
return {
|
|
242
405
|
tasks: tasks.map((task) => {
|
|
243
|
-
if (task.id
|
|
406
|
+
if (!ids.has(task.id) || task.archivedAt === void 0) return task;
|
|
244
407
|
applied = true;
|
|
245
408
|
const { archivedAt: _archived, ...rest } = task;
|
|
246
409
|
return {
|
|
@@ -424,23 +587,57 @@ window.__ModuleLoader__.load({
|
|
|
424
587
|
//#region src/core/use-cases/task-create.ts
|
|
425
588
|
/**
|
|
426
589
|
* Create-task use case: mint a new task from user input, rejecting a blank
|
|
427
|
-
* title
|
|
428
|
-
* orchestrates those), so it is
|
|
590
|
+
* title and any parent link the lineage gate refuses. Pure ledger transition
|
|
591
|
+
* (no persistence or notify - the controller orchestrates those), so it is
|
|
592
|
+
* unit-testable without any runtime face.
|
|
429
593
|
*/
|
|
430
594
|
/**
|
|
431
595
|
* Apply a create against the current ledger. Returns the new task and the
|
|
432
|
-
* appended ledger, or the unchanged ledger when the title is blank
|
|
596
|
+
* appended ledger, or the unchanged ledger when the title is blank or the
|
|
597
|
+
* requested parent link is refused.
|
|
598
|
+
*
|
|
599
|
+
* A subtask inherits the execution targets its parent already pins (workspace,
|
|
600
|
+
* agent preset, permission, model) unless the form supplied its own; the
|
|
601
|
+
* parent human permission confirmation travels with an inherited permission,
|
|
602
|
+
* so a subtask of an already-confirmed elevated card is runnable at once.
|
|
433
603
|
* @param tasks - current ledger.
|
|
434
|
-
* @param input - raw user input (title/description/prompt + optional schedule).
|
|
604
|
+
* @param input - raw user input (title/description/prompt + optional schedule/parent).
|
|
435
605
|
* @param now - clock instant (ms epoch).
|
|
436
606
|
* @param id - minted task id.
|
|
607
|
+
* @param maxSubtaskDepth - deployment subtask depth limit.
|
|
437
608
|
*/
|
|
438
|
-
function applyCreateTask(tasks, input, now, id) {
|
|
609
|
+
function applyCreateTask(tasks, input, now, id, maxSubtaskDepth = 1) {
|
|
439
610
|
if (input.title.trim() === "") return {
|
|
440
611
|
task: void 0,
|
|
441
|
-
tasks
|
|
612
|
+
tasks,
|
|
613
|
+
error: "title is required"
|
|
442
614
|
};
|
|
443
|
-
|
|
615
|
+
const limit = normalizeSubtaskDepth(maxSubtaskDepth);
|
|
616
|
+
let parent;
|
|
617
|
+
if (input.parentId !== void 0) {
|
|
618
|
+
parent = tasks.find((task) => task.id === input.parentId);
|
|
619
|
+
if (parent === void 0) return {
|
|
620
|
+
task: void 0,
|
|
621
|
+
tasks,
|
|
622
|
+
error: "parent task not found"
|
|
623
|
+
};
|
|
624
|
+
if (parent.archivedAt !== void 0) return {
|
|
625
|
+
task: void 0,
|
|
626
|
+
tasks,
|
|
627
|
+
error: "parent task is archived"
|
|
628
|
+
};
|
|
629
|
+
if (taskDepth(tasks, parent.id) + 1 > limit) return {
|
|
630
|
+
task: void 0,
|
|
631
|
+
tasks,
|
|
632
|
+
error: `subtask depth limit (${limit}) exceeded`
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
let task = createTask(parent === void 0 ? input : {
|
|
636
|
+
...input,
|
|
637
|
+
workspaceId: input.workspaceId ?? parent.workspaceId,
|
|
638
|
+
mode: input.mode ?? parent.mode,
|
|
639
|
+
model: input.model ?? parent.model
|
|
640
|
+
}, now, id);
|
|
444
641
|
const requested = input.schedule;
|
|
445
642
|
if (requested?.enabled === true && requested.cron.trim() !== "" && isValidCron(requested.cron)) {
|
|
446
643
|
const cron = requested.cron.trim();
|
|
@@ -465,9 +662,15 @@ window.__ModuleLoader__.load({
|
|
|
465
662
|
* @param id - the task to remove.
|
|
466
663
|
*/
|
|
467
664
|
function applyDeleteTask(tasks, selectedTaskId, id) {
|
|
665
|
+
if (tasks.some((task) => task.parentId === id)) return {
|
|
666
|
+
tasks,
|
|
667
|
+
selectionCleared: false,
|
|
668
|
+
blocked: true
|
|
669
|
+
};
|
|
468
670
|
return {
|
|
469
671
|
tasks: tasks.filter((task) => task.id !== id),
|
|
470
|
-
selectionCleared: selectedTaskId === id
|
|
672
|
+
selectionCleared: selectedTaskId === id,
|
|
673
|
+
blocked: false
|
|
471
674
|
};
|
|
472
675
|
}
|
|
473
676
|
//#endregion
|
|
@@ -531,6 +734,70 @@ window.__ModuleLoader__.load({
|
|
|
531
734
|
}, now) : task);
|
|
532
735
|
}
|
|
533
736
|
//#endregion
|
|
737
|
+
//#region src/core/use-cases/task-parent.ts
|
|
738
|
+
/**
|
|
739
|
+
* Parent-link use case: attach an existing on-board task under another as a
|
|
740
|
+
* subtask, or detach it back to the root. The lineage gate is the same one
|
|
741
|
+
* creation uses (unknown or archived parent, self, cycle, depth), so the two
|
|
742
|
+
* ways into the tree cannot disagree. Pure ledger transition.
|
|
743
|
+
*/
|
|
744
|
+
/** Host error text per refusal reason (the wire carries the same strings). */
|
|
745
|
+
const REJECTION_TEXT = {
|
|
746
|
+
"unknown-task": "task not found",
|
|
747
|
+
"unknown-parent": "parent task not found",
|
|
748
|
+
"archived-parent": "parent task is archived",
|
|
749
|
+
"self-parent": "a task cannot be its own parent",
|
|
750
|
+
"cycle": "a task cannot be attached under its own subtask",
|
|
751
|
+
"depth-exceeded": "subtask depth limit exceeded"
|
|
752
|
+
};
|
|
753
|
+
/**
|
|
754
|
+
* Attach or detach one task. A null (or blank) parent detaches, which is always
|
|
755
|
+
* allowed for a known on-board task; attaching validates the whole chain so a
|
|
756
|
+
* cyclic or too-deep ledger can never be stored.
|
|
757
|
+
* @param tasks - current ledger.
|
|
758
|
+
* @param id - the task being moved.
|
|
759
|
+
* @param parentId - the new parent, or null to detach.
|
|
760
|
+
* @param now - clock instant (ms epoch).
|
|
761
|
+
* @param maxSubtaskDepth - deployment subtask depth limit.
|
|
762
|
+
*/
|
|
763
|
+
function applySetParent(tasks, id, parentId, now, maxSubtaskDepth = 1) {
|
|
764
|
+
const task = tasks.find((candidate) => candidate.id === id);
|
|
765
|
+
if (task === void 0) return {
|
|
766
|
+
tasks,
|
|
767
|
+
applied: false,
|
|
768
|
+
error: REJECTION_TEXT["unknown-task"]
|
|
769
|
+
};
|
|
770
|
+
if (task.archivedAt !== void 0) return {
|
|
771
|
+
tasks,
|
|
772
|
+
applied: false,
|
|
773
|
+
error: "archived task is read-only"
|
|
774
|
+
};
|
|
775
|
+
if (task.status === "running" || task.executions.some((execution) => execution.endedAt === void 0)) return {
|
|
776
|
+
tasks,
|
|
777
|
+
applied: false,
|
|
778
|
+
error: "running task cannot be re-parented"
|
|
779
|
+
};
|
|
780
|
+
const check = checkParentLink(tasks, id, parentId, normalizeSubtaskDepth(maxSubtaskDepth));
|
|
781
|
+
if (!check.ok) return {
|
|
782
|
+
tasks,
|
|
783
|
+
applied: false,
|
|
784
|
+
error: REJECTION_TEXT[check.reason ?? "unknown-task"]
|
|
785
|
+
};
|
|
786
|
+
const nextParentId = parentId === null || parentId === "" ? void 0 : parentId;
|
|
787
|
+
if (nextParentId === task.parentId) return {
|
|
788
|
+
tasks: [...tasks],
|
|
789
|
+
applied: true
|
|
790
|
+
};
|
|
791
|
+
return {
|
|
792
|
+
tasks: tasks.map((candidate) => candidate.id === id ? {
|
|
793
|
+
...candidate,
|
|
794
|
+
parentId: nextParentId,
|
|
795
|
+
updatedAt: now
|
|
796
|
+
} : candidate),
|
|
797
|
+
applied: true
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
//#endregion
|
|
534
801
|
//#region src/core/use-cases/task-update.ts
|
|
535
802
|
/**
|
|
536
803
|
* Update-task use case: apply an editable-field patch (title/description/
|
|
@@ -599,6 +866,7 @@ window.__ModuleLoader__.load({
|
|
|
599
866
|
if ("tags" in patch) next.tags = tagsPatch == null ? void 0 : normalizeTags(tagsPatch);
|
|
600
867
|
if ("permission" in patch && patch.permission !== void 0 && patch.permission !== task.permission || "handover" in patch) next.permissionConfirmedAt = void 0;
|
|
601
868
|
if ("reuseSession" in patch) next.reuseSession = patch.reuseSession === true ? true : void 0;
|
|
869
|
+
if ("teamRun" in patch) next.teamRun = patch.teamRun === true ? true : void 0;
|
|
602
870
|
if (workspaceId !== void 0 || "workspaceId" in patch) next.workspaceId = workspaceId;
|
|
603
871
|
if (mode !== void 0 || "mode" in patch) next.mode = mode;
|
|
604
872
|
if (permission !== void 0 || "permission" in patch) next.permission = permission;
|
|
@@ -611,6 +879,8 @@ window.__ModuleLoader__.load({
|
|
|
611
879
|
function currentOf(sessions) {
|
|
612
880
|
return sessions?.current();
|
|
613
881
|
}
|
|
882
|
+
/** Stable id shared by the board's sidebar panel row and its main-slot page. */
|
|
883
|
+
const TASK_BOARD_PANEL_ID = "task-board";
|
|
614
884
|
/** The selected task (resolved from the ledger), or undefined. */
|
|
615
885
|
function selectedTaskOf(snapshot) {
|
|
616
886
|
if (snapshot.selectedTaskId === void 0) return void 0;
|
|
@@ -708,20 +978,55 @@ window.__ModuleLoader__.load({
|
|
|
708
978
|
async retryHostSync() {
|
|
709
979
|
return await this.initializeRemote();
|
|
710
980
|
}
|
|
981
|
+
/**
|
|
982
|
+
* Show the board. The layout owns which panel the center column renders, so
|
|
983
|
+
* the state flip and the panel selection travel together here; a composition
|
|
984
|
+
* with no layout face (tests, a shell-less host) still flips the state.
|
|
985
|
+
*
|
|
986
|
+
* The selection is requested AFTER the snapshot flips so a subscriber
|
|
987
|
+
* rendering against `boardOpen` never observes "open" while the shell still
|
|
988
|
+
* shows the conversation.
|
|
989
|
+
*/
|
|
711
990
|
openBoard() {
|
|
712
991
|
if (this.boardOpen) return;
|
|
713
992
|
this.boardOpen = true;
|
|
714
993
|
this.notify();
|
|
994
|
+
this.selectPanel(TASK_BOARD_PANEL_ID);
|
|
715
995
|
}
|
|
996
|
+
/**
|
|
997
|
+
* Return the center column to the conversation. The layout's own selection is
|
|
998
|
+
* the source of truth for what the column renders, so this asks for the
|
|
999
|
+
* conversation explicitly rather than only clearing local state.
|
|
1000
|
+
*/
|
|
716
1001
|
closeBoard() {
|
|
717
1002
|
this.boardOpen = false;
|
|
718
1003
|
this.notify();
|
|
1004
|
+
this.selectPanel(null);
|
|
719
1005
|
}
|
|
720
1006
|
toggleBoard() {
|
|
721
1007
|
if (this.boardOpen) this.closeBoard();
|
|
722
1008
|
else this.openBoard();
|
|
723
1009
|
}
|
|
724
1010
|
/**
|
|
1011
|
+
* Reflect a panel selection that came from OUTSIDE this controller (the user
|
|
1012
|
+
* clicked another sidebar row, or the layout dropped the panel id). Keeps
|
|
1013
|
+
* `boardOpen` aligned with what the column actually shows without asking the
|
|
1014
|
+
* layout to select anything back.
|
|
1015
|
+
* @param panelId - the layout's current panel id, or null for the conversation.
|
|
1016
|
+
*/
|
|
1017
|
+
syncPanelSelection(panelId) {
|
|
1018
|
+
const open = panelId === TASK_BOARD_PANEL_ID;
|
|
1019
|
+
if (open === this.boardOpen) return;
|
|
1020
|
+
this.boardOpen = open;
|
|
1021
|
+
this.notify();
|
|
1022
|
+
}
|
|
1023
|
+
/** Ask the layout to select a panel; a shell that serves no layout face is a no-op. */
|
|
1024
|
+
selectPanel(panelId) {
|
|
1025
|
+
try {
|
|
1026
|
+
this.deps.panel?.select(panelId);
|
|
1027
|
+
} catch {}
|
|
1028
|
+
}
|
|
1029
|
+
/**
|
|
725
1030
|
* Switch between the kanban columns and the archive view. Leaving the
|
|
726
1031
|
* archive view with an archived task still selected closes the selection —
|
|
727
1032
|
* the detail overlay must not linger over a task that is off-board.
|
|
@@ -746,7 +1051,7 @@ window.__ModuleLoader__.load({
|
|
|
746
1051
|
}
|
|
747
1052
|
createTask(input) {
|
|
748
1053
|
const id = this.uuid();
|
|
749
|
-
const { task, tasks } = applyCreateTask(this.tasks, input, this.now(), id);
|
|
1054
|
+
const { task, tasks } = applyCreateTask(this.tasks, input, this.now(), id, this.mirrorDepth());
|
|
750
1055
|
if (task === void 0) return void 0;
|
|
751
1056
|
this.tasks = [...tasks];
|
|
752
1057
|
this.persistAndNotify();
|
|
@@ -756,7 +1061,7 @@ window.__ModuleLoader__.load({
|
|
|
756
1061
|
async createTaskConfirmed(input) {
|
|
757
1062
|
if (this.deps.transport === void 0) return this.createTask(input);
|
|
758
1063
|
const id = this.uuid();
|
|
759
|
-
if (applyCreateTask(this.tasks, input, this.now(), id).task === void 0) return void 0;
|
|
1064
|
+
if (applyCreateTask(this.tasks, input, this.now(), id, this.mirrorDepth()).task === void 0) return void 0;
|
|
760
1065
|
return await this.commitRemote({
|
|
761
1066
|
kind: "create",
|
|
762
1067
|
id,
|
|
@@ -850,7 +1155,7 @@ window.__ModuleLoader__.load({
|
|
|
850
1155
|
* @returns true when applied.
|
|
851
1156
|
*/
|
|
852
1157
|
archiveTask(id) {
|
|
853
|
-
const { tasks, archived } = applyArchiveTask(this.tasks, id, this.now());
|
|
1158
|
+
const { tasks, archived } = applyArchiveTask(this.tasks, id, this.now(), this.mirrorDepth());
|
|
854
1159
|
if (!archived) return false;
|
|
855
1160
|
if (this.deps.transport !== void 0) {
|
|
856
1161
|
this.commitRemote({
|
|
@@ -863,9 +1168,30 @@ window.__ModuleLoader__.load({
|
|
|
863
1168
|
this.persistAndNotify();
|
|
864
1169
|
return true;
|
|
865
1170
|
}
|
|
1171
|
+
/**
|
|
1172
|
+
* Attach an existing task under a parent (or detach it with a null parent)
|
|
1173
|
+
* through the Host. The lineage gate — parent exists and is on-board, no
|
|
1174
|
+
* cycle, depth within the deployment limit — belongs to the Host; a refusal
|
|
1175
|
+
* surfaces through the transport error like every other rejected action.
|
|
1176
|
+
* @returns true when the link was accepted by the authority.
|
|
1177
|
+
*/
|
|
1178
|
+
async setParent(id, parentId) {
|
|
1179
|
+
if (this.deps.transport === void 0) {
|
|
1180
|
+
const result = applySetParent(this.tasks, id, parentId, this.now(), this.mirrorDepth());
|
|
1181
|
+
if (!result.applied) return false;
|
|
1182
|
+
this.tasks = [...result.tasks];
|
|
1183
|
+
this.persistAndNotify();
|
|
1184
|
+
return true;
|
|
1185
|
+
}
|
|
1186
|
+
return await this.commitRemote({
|
|
1187
|
+
kind: "set-parent",
|
|
1188
|
+
taskId: id,
|
|
1189
|
+
parentId
|
|
1190
|
+
}, id);
|
|
1191
|
+
}
|
|
866
1192
|
/** Restore an archived task back onto the board (same status column). */
|
|
867
1193
|
restoreTask(id) {
|
|
868
|
-
const { tasks, archived } = applyRestoreTask(this.tasks, id, this.now());
|
|
1194
|
+
const { tasks, archived } = applyRestoreTask(this.tasks, id, this.now(), this.mirrorDepth());
|
|
869
1195
|
if (!archived) return false;
|
|
870
1196
|
if (this.deps.transport !== void 0) {
|
|
871
1197
|
this.commitRemote({
|
|
@@ -980,8 +1306,8 @@ window.__ModuleLoader__.load({
|
|
|
980
1306
|
* (background navigation, the Host runner creating and selecting a fresh
|
|
981
1307
|
* execution session, settlement, other plugins), so closing on `current`
|
|
982
1308
|
* changes would evict the board without the user asking. The board closes
|
|
983
|
-
* only on explicit user navigation:
|
|
984
|
-
*
|
|
1309
|
+
* only on explicit user navigation: selecting another panel (the layout owns
|
|
1310
|
+
* selection, and `syncPanelSelection` follows it) or the board's own actions
|
|
985
1311
|
* (openSession / close). Keeping the hook preserves the subscription
|
|
986
1312
|
* contract for future listeners.
|
|
987
1313
|
*/
|
|
@@ -1057,6 +1383,7 @@ window.__ModuleLoader__.load({
|
|
|
1057
1383
|
onRemoteEvent(event) {
|
|
1058
1384
|
if (event !== void 0 && this.hostState !== void 0 && event.revision === this.hostState.revision && typeof event.scheduler === "object" && event.scheduler !== null && typeof event.power === "object" && event.power !== null) {
|
|
1059
1385
|
this.hostState = {
|
|
1386
|
+
...this.hostState,
|
|
1060
1387
|
revision: event.revision,
|
|
1061
1388
|
scheduler: event.scheduler,
|
|
1062
1389
|
power: event.power
|
|
@@ -1066,6 +1393,35 @@ window.__ModuleLoader__.load({
|
|
|
1066
1393
|
}
|
|
1067
1394
|
this.refreshRemote();
|
|
1068
1395
|
}
|
|
1396
|
+
/**
|
|
1397
|
+
* Deployment subtask depth limit the browser mirrors from the last Host
|
|
1398
|
+
* snapshot; the fallback is the deployment default for the legacy path (no
|
|
1399
|
+
* transport) and for the window before the first snapshot arrives.
|
|
1400
|
+
*/
|
|
1401
|
+
mirrorDepth() {
|
|
1402
|
+
return this.hostState?.maxSubtaskDepth ?? 1;
|
|
1403
|
+
}
|
|
1404
|
+
/**
|
|
1405
|
+
* Project a Host snapshot onto the browser's mirror. SSE frames and partial
|
|
1406
|
+
* snapshots carry only the volatile subset (revision/scheduler/power), so the
|
|
1407
|
+
* deployment constants the UI reads — the session-default permission the
|
|
1408
|
+
* confirmation banner compares against and the subtask depth limit — are
|
|
1409
|
+
* carried over from the last full snapshot instead of being dropped by a
|
|
1410
|
+
* heartbeat frame.
|
|
1411
|
+
*/
|
|
1412
|
+
mirrorOf(snapshot) {
|
|
1413
|
+
const sessionDefaultPermission = snapshot.sessionDefaultPermission ?? this.hostState?.sessionDefaultPermission;
|
|
1414
|
+
const maxSubtaskDepth = snapshot.maxSubtaskDepth ?? this.hostState?.maxSubtaskDepth;
|
|
1415
|
+
const teamRunAvailable = snapshot.teamRunAvailable ?? this.hostState?.teamRunAvailable;
|
|
1416
|
+
return {
|
|
1417
|
+
revision: snapshot.revision,
|
|
1418
|
+
scheduler: snapshot.scheduler,
|
|
1419
|
+
power: snapshot.power,
|
|
1420
|
+
...sessionDefaultPermission === void 0 ? {} : { sessionDefaultPermission },
|
|
1421
|
+
...maxSubtaskDepth === void 0 ? {} : { maxSubtaskDepth },
|
|
1422
|
+
...teamRunAvailable === void 0 ? {} : { teamRunAvailable }
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1069
1425
|
async refreshRemote(preserveError) {
|
|
1070
1426
|
const transport = this.deps.transport;
|
|
1071
1427
|
if (transport === void 0) return true;
|
|
@@ -1085,11 +1441,7 @@ window.__ModuleLoader__.load({
|
|
|
1085
1441
|
acceptRemote(snapshot) {
|
|
1086
1442
|
if (this.hostState?.scheduler.ledgerId === snapshot.scheduler.ledgerId && this.hostState !== void 0 && snapshot.revision < this.hostState.revision) return false;
|
|
1087
1443
|
this.tasks = [...snapshot.tasks];
|
|
1088
|
-
this.hostState =
|
|
1089
|
-
revision: snapshot.revision,
|
|
1090
|
-
scheduler: snapshot.scheduler,
|
|
1091
|
-
power: snapshot.power
|
|
1092
|
-
};
|
|
1444
|
+
this.hostState = this.mirrorOf(snapshot);
|
|
1093
1445
|
this.transportError = void 0;
|
|
1094
1446
|
if (this.selectedTaskId !== void 0 && !this.tasks.some((task) => task.id === this.selectedTaskId)) this.selectedTaskId = void 0;
|
|
1095
1447
|
if (!this.archiveView && this.selectedTaskId !== void 0 && this.tasks.find((task) => task.id === this.selectedTaskId)?.archivedAt !== void 0) this.selectedTaskId = void 0;
|
|
@@ -1387,6 +1739,7 @@ window.__ModuleLoader__.load({
|
|
|
1387
1739
|
if (typeof record.prompt !== "string") return false;
|
|
1388
1740
|
if (typeof record.createdAt !== "number") return false;
|
|
1389
1741
|
if (typeof record.updatedAt !== "number") return false;
|
|
1742
|
+
if (record.parentId !== void 0 && typeof record.parentId !== "string") return false;
|
|
1390
1743
|
if (record.workspaceId !== void 0 && typeof record.workspaceId !== "string") return false;
|
|
1391
1744
|
if (record.mode !== void 0 && typeof record.mode !== "string") return false;
|
|
1392
1745
|
if (record.permission !== void 0 && typeof record.permission !== "string") return false;
|
|
@@ -1496,6 +1849,13 @@ window.__ModuleLoader__.load({
|
|
|
1496
1849
|
status: normalizeStatus(row.status)
|
|
1497
1850
|
};
|
|
1498
1851
|
task.schedule = normalizeSchedule(row.schedule);
|
|
1852
|
+
task.parentId = normalizeTargetId(row.parentId);
|
|
1853
|
+
task.executions = task.executions.map((execution) => ({
|
|
1854
|
+
...execution,
|
|
1855
|
+
runGroupId: normalizeTargetId(execution.runGroupId),
|
|
1856
|
+
ownResult: isExecutionOutcome(execution.ownResult) ? execution.ownResult : void 0,
|
|
1857
|
+
ownError: typeof execution.ownError === "string" ? execution.ownError : void 0
|
|
1858
|
+
}));
|
|
1499
1859
|
task.workspaceId = normalizeTargetId(row.workspaceId);
|
|
1500
1860
|
task.mode = normalizeTargetId(row.mode);
|
|
1501
1861
|
task.archivedAt = typeof row.archivedAt === "number" && Number.isFinite(row.archivedAt) ? row.archivedAt : void 0;
|
|
@@ -1746,19 +2106,23 @@ window.__ModuleLoader__.load({
|
|
|
1746
2106
|
"exec.mode.brokenSuffix": "(不可用)",
|
|
1747
2107
|
"exec.mode.removed": "(已移除)",
|
|
1748
2108
|
"exec.permission.default": "会话默认",
|
|
2109
|
+
"exec.permission.inheritParent": "继承父任务({permission})",
|
|
1749
2110
|
"exec.permission.read-only": "只读",
|
|
1750
2111
|
"exec.permission.workspace-write": "工作区可写",
|
|
1751
2112
|
"exec.permission.danger-full-access": "完全访问",
|
|
1752
2113
|
"exec.model.default": "宿主默认(agent-default-model)",
|
|
1753
2114
|
"exec.model.unknown": "(未知模型/回退默认)",
|
|
1754
2115
|
"exec.reuseSession": "在同一对话继续",
|
|
2116
|
+
"exec.teamRun": "用 Agent Team 执行(Leader/teammate)",
|
|
2117
|
+
"exec.teamRunHint": "开启后:执行本任务只启动一个 Leader 会话,每个子任务由它派生为 teammate 并在该会话内并行工作(子任务自身的权限钉住不适用)。关闭则每个成员各开独立会话。",
|
|
2118
|
+
"exec.teamRunUnavailable": "当前部署未提供 Agent Teams 服务,无法启用。",
|
|
1755
2119
|
"exec.reuseSessionHint": "开启后,本任务的后续执行在上一次会话里继续(该会话空闲且仍存在时),不再每次新建对话;每次复用时都会重新应用上面钉住的权限与模型。",
|
|
1756
2120
|
"detail.executionSettings": "执行设置",
|
|
1757
2121
|
"exec.hint": "执行时生效:工作区决定执行会话落在哪个工作区;模式决定会话的 agent 预设;权限经 /permission 命令应用到会话。留空则使用运行时默认。",
|
|
1758
2122
|
"settings.title": "任务看板",
|
|
1759
2123
|
"settings.description": "控制 Host 任务看板、agent 播报与运行期间的系统空闲睡眠保护。",
|
|
1760
2124
|
"settings.enabled": "启用任务看板",
|
|
1761
|
-
"settings.enabledHint": "
|
|
2125
|
+
"settings.enabledHint": "关闭后隐藏侧边栏入口与看板视图,并注销 task_board_* agent 工具;开启时任何会话都可以通过这些工具读写看板、子任务与定时计划。",
|
|
1762
2126
|
"settings.announceToAgent": "向 agent 播报任务看板",
|
|
1763
2127
|
"settings.announceToAgentHint": "开启:每条 agent 系统提示都会包含本看板的说明;关闭:不播报,agent 仅在用户主动提及时了解看板。",
|
|
1764
2128
|
"settings.preventIdleSleep": "阻止系统空闲睡眠",
|
|
@@ -1781,7 +2145,36 @@ window.__ModuleLoader__.load({
|
|
|
1781
2145
|
"settings.discard": "放弃",
|
|
1782
2146
|
"settings.unsaved": "未保存",
|
|
1783
2147
|
"settings.saveFailed": "部署未接受这些值,已保留供你修改。",
|
|
1784
|
-
"settings.invalidNumber": "请输入数字,留空则使用默认值。"
|
|
2148
|
+
"settings.invalidNumber": "请输入数字,留空则使用默认值。",
|
|
2149
|
+
"settings.maxSubtaskDepth": "子任务深度上限",
|
|
2150
|
+
"settings.maxSubtaskDepthHint": "默认 1:一个任务只允许一层子任务,子任务不能再创建或关联子任务。最大 3。执行父任务会并发执行它的整棵子任务树,层级越深,一次执行开启的会话越多。",
|
|
2151
|
+
"settings.maxSubtaskDepthOption": "{depth} 层",
|
|
2152
|
+
"detail.parent": "父任务",
|
|
2153
|
+
"detail.parent.open": "打开父任务",
|
|
2154
|
+
"detail.subtasks": "子任务",
|
|
2155
|
+
"detail.subtasks.empty": "还没有子任务",
|
|
2156
|
+
"detail.subtasks.add": "添加子任务",
|
|
2157
|
+
"detail.subtasks.link": "关联现有任务",
|
|
2158
|
+
"detail.subtasks.detach": "解除关联",
|
|
2159
|
+
"detail.subtasks.runningLock": "子任务正在执行,结算后才能解除关联或改挂父任务",
|
|
2160
|
+
"detail.subtasks.runHint": "执行本任务会同时并发执行它的 {count} 个子任务;子任务失败不影响其它子任务继续执行。",
|
|
2161
|
+
"detail.subtasks.waiting": "等待子任务结算",
|
|
2162
|
+
"detail.subtasks.depthLimit": "当前子任务深度上限为 {depth} 层,子任务不能再创建或关联子任务。",
|
|
2163
|
+
"detail.subtasks.hostOnly": "连接 Host 后才能创建或关联子任务。",
|
|
2164
|
+
"new.subtaskTitle": "新建子任务",
|
|
2165
|
+
"new.subtaskInherit": "未单独选择的执行设置继承父任务(工作区、模式、权限、模型),可在此覆盖。",
|
|
2166
|
+
"link.subtask.title": "关联现有任务",
|
|
2167
|
+
"link.subtask.hint": "只能关联没有父任务的在看板任务;关联后它成为本任务的子任务。",
|
|
2168
|
+
"link.subtask.empty": "没有可关联的任务",
|
|
2169
|
+
"link.subtask.confirm": "关联",
|
|
2170
|
+
"card.subtask": "子任务",
|
|
2171
|
+
"card.subtasks": "{count} 子任务",
|
|
2172
|
+
"card.subtasksFailed": "{count} 失败",
|
|
2173
|
+
"card.subtasksRunning": "{count} 运行中",
|
|
2174
|
+
"card.subtasksBreakdown": "子任务 {total}:已完成 {done},运行中 {running},失败 {failed}",
|
|
2175
|
+
"board.hideSubtasks": "隐藏子任务",
|
|
2176
|
+
"board.showSubtasks": "显示子任务",
|
|
2177
|
+
"board.subtaskFilterHint": "看板默认只显示父任务;搜索或按标签筛选时会自动展开子任务。"
|
|
1785
2178
|
};
|
|
1786
2179
|
/** en dictionary, complete against the zh key set. */
|
|
1787
2180
|
const en = {
|
|
@@ -1935,19 +2328,23 @@ window.__ModuleLoader__.load({
|
|
|
1935
2328
|
"exec.mode.brokenSuffix": " (unavailable)",
|
|
1936
2329
|
"exec.mode.removed": " (removed)",
|
|
1937
2330
|
"exec.permission.default": "Session default",
|
|
2331
|
+
"exec.permission.inheritParent": "Inherit parent ({permission})",
|
|
1938
2332
|
"exec.permission.read-only": "Read-only",
|
|
1939
2333
|
"exec.permission.workspace-write": "Workspace Write",
|
|
1940
2334
|
"exec.permission.danger-full-access": "Full Access",
|
|
1941
2335
|
"exec.model.default": "Host default (agent-default-model)",
|
|
1942
2336
|
"exec.model.unknown": " (unknown / fallback to default)",
|
|
1943
2337
|
"exec.reuseSession": "Continue in the same conversation",
|
|
2338
|
+
"exec.teamRun": "Run with an Agent Team (Lead + teammates)",
|
|
2339
|
+
"exec.teamRunHint": "On: running this task starts one Lead session and spawns a teammate per subtask inside it, working in parallel (a subtask permission pin does not apply). Off: every member gets its own independent session.",
|
|
2340
|
+
"exec.teamRunUnavailable": "This deployment serves no Agent Teams service, so team runs cannot be enabled.",
|
|
1944
2341
|
"exec.reuseSessionHint": "When on, later runs continue in the previous session (when that session is idle and still exists) instead of starting a new conversation each time; the pinned permission and model above are re-applied on every reuse.",
|
|
1945
2342
|
"detail.executionSettings": "Execution Settings",
|
|
1946
2343
|
"exec.hint": "Applied when the task runs: the workspace decides where the execution session lands; the mode composes the session's agent preset; the permission is applied through the /permission command. Blank = runtime default.",
|
|
1947
2344
|
"settings.title": "Task Board",
|
|
1948
2345
|
"settings.description": "Configure the Host task board, agent announcement, and idle-system-sleep protection while work is pending.",
|
|
1949
2346
|
"settings.enabled": "Enable the task board",
|
|
1950
|
-
"settings.enabledHint": "When off, the sidebar entry and board view are hidden.",
|
|
2347
|
+
"settings.enabledHint": "When off, the sidebar entry and board view are hidden and the task_board_* agent tools are unregistered; while on, any session can read and drive the board, its subtasks and its schedules through those tools.",
|
|
1951
2348
|
"settings.announceToAgent": "Announce the task board to agents",
|
|
1952
2349
|
"settings.announceToAgentHint": "On: every agent system prompt includes a note about this board. Off: no announcement; agents learn about the board only when you mention it.",
|
|
1953
2350
|
"settings.preventIdleSleep": "Prevent idle system sleep",
|
|
@@ -1970,7 +2367,36 @@ window.__ModuleLoader__.load({
|
|
|
1970
2367
|
"settings.discard": "Discard",
|
|
1971
2368
|
"settings.unsaved": "Unsaved",
|
|
1972
2369
|
"settings.saveFailed": "The deployment did not accept these values; they were left for you to correct.",
|
|
1973
|
-
"settings.invalidNumber": "Enter a number, or leave blank to use the default."
|
|
2370
|
+
"settings.invalidNumber": "Enter a number, or leave blank to use the default.",
|
|
2371
|
+
"settings.maxSubtaskDepth": "Subtask depth limit",
|
|
2372
|
+
"settings.maxSubtaskDepthHint": "Default 1: a task may carry one level of subtasks, and a subtask cannot create or link further subtasks. Maximum 3. Running a task also runs its whole subtask tree concurrently, and every extra level opens more sessions per run.",
|
|
2373
|
+
"settings.maxSubtaskDepthOption": "{depth} levels",
|
|
2374
|
+
"detail.parent": "Parent task",
|
|
2375
|
+
"detail.parent.open": "Open parent task",
|
|
2376
|
+
"detail.subtasks": "Subtasks",
|
|
2377
|
+
"detail.subtasks.empty": "No subtasks yet",
|
|
2378
|
+
"detail.subtasks.add": "Add subtask",
|
|
2379
|
+
"detail.subtasks.link": "Link existing task",
|
|
2380
|
+
"detail.subtasks.detach": "Detach",
|
|
2381
|
+
"detail.subtasks.runningLock": "This subtask is running; it can be detached or re-linked once it settles",
|
|
2382
|
+
"detail.subtasks.runHint": "Running this task also runs its {count} subtasks concurrently; one subtask failing does not stop the others.",
|
|
2383
|
+
"detail.subtasks.waiting": "Waiting for subtasks",
|
|
2384
|
+
"detail.subtasks.depthLimit": "The subtask depth limit is {depth}; a subtask cannot create or link further subtasks.",
|
|
2385
|
+
"detail.subtasks.hostOnly": "Connect to the Host to create or link subtasks.",
|
|
2386
|
+
"new.subtaskTitle": "New Subtask",
|
|
2387
|
+
"new.subtaskInherit": "Execution settings left unset inherit the parent (workspace, mode, permission, model); override any of them here.",
|
|
2388
|
+
"link.subtask.title": "Link an existing task",
|
|
2389
|
+
"link.subtask.hint": "Only an on-board task without a parent can be linked; it then becomes a subtask of this task.",
|
|
2390
|
+
"link.subtask.empty": "No task can be linked",
|
|
2391
|
+
"link.subtask.confirm": "Link",
|
|
2392
|
+
"card.subtask": "subtask",
|
|
2393
|
+
"card.subtasks": "{count} subtasks",
|
|
2394
|
+
"card.subtasksFailed": "{count} failed",
|
|
2395
|
+
"card.subtasksRunning": "{count} running",
|
|
2396
|
+
"card.subtasksBreakdown": "Subtasks {total}: {done} done, {running} running, {failed} failed",
|
|
2397
|
+
"board.hideSubtasks": "Hide subtasks",
|
|
2398
|
+
"board.showSubtasks": "Show subtasks",
|
|
2399
|
+
"board.subtaskFilterHint": "The board shows parent tasks only; a text or label filter reveals the subtasks automatically."
|
|
1974
2400
|
};
|
|
1975
2401
|
/** Active dictionary, picked by the document language at call time. */
|
|
1976
2402
|
function dictionary() {
|
|
@@ -1997,7 +2423,7 @@ window.__ModuleLoader__.load({
|
|
|
1997
2423
|
}
|
|
1998
2424
|
//#endregion
|
|
1999
2425
|
//#region \0dsh-css:packages/dsh-task-board/src/client/board.module.css.mjs
|
|
2000
|
-
const css$1 = "[data-pane=conversation],[class*=centerCol]{position:relative}[data-dsh-taskboard-view]{z-index:60;background:var(--dsw-alias-bg-base);display:none;position:absolute;inset:0;container:_7D6uKa_task-board-view/inline-size}html[data-dsh-taskboard-active]:not([data-dsh-ssh-active]) [data-dsh-taskboard-view]{display:block}html[data-dsh-taskboard-active]:not([data-dsh-ssh-active]) [data-pane=conversation]>:not([data-dsh-taskboard-view]),html[data-dsh-taskboard-active]:not([data-dsh-ssh-active]) [class*=centerCol]>:not([data-dsh-taskboard-view]){display:none!important}._7D6uKa_entry{box-sizing:border-box;min-height:36px;color:var(--dsw-alias-label-primary);cursor:pointer;font:inherit;text-align:left;white-space:nowrap;background:0 0;border:none;border-radius:12px;align-items:center;gap:8px;margin:0 2px;padding:7px 8px;font-size:14px;line-height:22px;display:flex}._7D6uKa_entry:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}._7D6uKa_entry[data-active]{background:var(--dsw-alias-interactive-bg-active);color:var(--dsw-alias-label-primary);font-weight:600}._7D6uKa_entryIcon{flex:none;justify-content:center;align-items:center;width:16px;height:16px;display:inline-flex}._7D6uKa_entryIcon svg{width:16px;height:16px;display:block}._7D6uKa_entryLabel{text-overflow:ellipsis;overflow:hidden}[data-dsh-frame][data-sidebar-collapsed] ._7D6uKa_entry,[data-sidebar-collapsed] ._7D6uKa_entry{border-radius:12px;justify-content:center;width:36px;height:36px;margin:0 auto 12px;padding:0}[data-dsh-frame][data-sidebar-collapsed] ._7D6uKa_entryIcon,[data-sidebar-collapsed] ._7D6uKa_entryIcon,[data-dsh-frame][data-sidebar-collapsed] ._7D6uKa_entryIcon svg,[data-sidebar-collapsed] ._7D6uKa_entryIcon svg{width:18px;height:18px}[data-dsh-frame][data-sidebar-collapsed] ._7D6uKa_entryLabel,[data-sidebar-collapsed] ._7D6uKa_entryLabel{display:none}._7D6uKa_board{box-sizing:border-box;background:var(--dsw-alias-bg-base);min-width:0;height:100%;min-height:0;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family);flex-direction:column;gap:12px;padding:14px 16px 16px;display:flex}._7D6uKa_boardHeader{flex:none;align-items:center;gap:10px;display:flex}._7D6uKa_boardTitle{color:var(--dsw-alias-label-primary);white-space:nowrap;margin:0;font-size:16px;font-weight:700}._7D6uKa_backButton{align-items:center;gap:4px;display:inline-flex}._7D6uKa_search{min-width:120px;color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:none;flex:0 260px;padding:6px 10px;font-size:13px}._7D6uKa_search::placeholder{color:var(--dsw-alias-label-tertiary)}._7D6uKa_columns{overscroll-behavior-inline:contain;scrollbar-color:var(--dsw-alias-border-l3) var(--dsw-alias-interactive-bg-hover);scrollbar-width:thin;flex:1;grid-auto-columns:minmax(220px,1fr);grid-auto-flow:column;gap:12px;min-height:0;padding-bottom:6px;display:grid;overflow:auto hidden}._7D6uKa_columns::-webkit-scrollbar{height:10px}._7D6uKa_columns::-webkit-scrollbar-track{background:var(--dsw-alias-interactive-bg-hover);border-radius:999px}._7D6uKa_columns::-webkit-scrollbar-thumb{background:var(--dsw-alias-border-l3);background-clip:content-box;border:2px solid #0000;border-radius:999px}._7D6uKa_columns::-webkit-scrollbar-thumb:hover{background:var(--dsw-alias-border-l4);background-clip:content-box}._7D6uKa_column{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:12px;flex-direction:column;min-height:0;display:flex;overflow:hidden}._7D6uKa_columnHeader{flex:none;align-items:center;gap:6px;padding:10px 12px;display:flex}._7D6uKa_columnTitle{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;flex:1;margin:0;font-size:13px;font-weight:700;overflow:hidden}._7D6uKa_columnCount{min-width:0;color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-interactive-bg-hover);border-radius:999px;flex:none;padding:1px 8px;font-size:12px}._7D6uKa_statusDot{border-radius:50%;flex:none;width:8px;height:8px}._7D6uKa_statusDot[data-status=backlog]{background:var(--dsw-alias-label-tertiary)}._7D6uKa_statusDot[data-status=todo]{background:var(--dsw-alias-state-business-primary)}._7D6uKa_statusDot[data-status=running]{background:var(--dsw-alias-state-warn-primary)}._7D6uKa_statusDot[data-status=done]{background:var(--dsw-alias-state-success-primary)}._7D6uKa_statusDot[data-status=failed]{background:var(--dsw-alias-state-error-primary)}._7D6uKa_cards{flex-direction:column;flex:1;gap:8px;min-height:0;padding:2px 8px 10px;display:flex;overflow-y:auto}._7D6uKa_columnEmpty{text-align:center;color:var(--dsw-alias-label-tertiary);padding:24px 8px;font-size:12px}._7D6uKa_card{text-align:left;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;color:var(--dsw-alias-label-primary);border-radius:10px;flex-direction:column;gap:6px;padding:10px 12px;font-family:inherit;transition:box-shadow .12s,border-color .12s,transform .12s;display:flex}._7D6uKa_card:hover{box-shadow:var(--dsw-shadow-lv2);border-color:var(--dsw-alias-border-l3);transform:translateY(-1px)}._7D6uKa_card[data-status=running]{border-color:var(--dsw-alias-state-warn-primary)}._7D6uKa_cardTitle{-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:13px;font-weight:600;line-height:1.35;display:-webkit-box;overflow:hidden}._7D6uKa_cardExcerpt{color:var(--dsw-alias-label-secondary);-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:12px;line-height:1.4;display:-webkit-box;overflow:hidden}._7D6uKa_cardMeta{color:var(--dsw-alias-label-tertiary);align-items:center;gap:8px;font-size:11px;display:flex}._7D6uKa_cardTime{text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}._7D6uKa_cardSchedule{white-space:nowrap;min-width:0;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover);border-radius:999px;flex:none;padding:2px 6px;font-size:12px;line-height:1}._7D6uKa_cardRun{flex:none}._7D6uKa_cardRun[data-result=failed]{color:var(--dsw-alias-state-error-primary)}._7D6uKa_cardRun[data-result=succeeded]{color:var(--dsw-alias-state-success-primary)}._7D6uKa_cardSession{color:var(--dsw-alias-state-business-primary);flex:none}._7D6uKa_cardRunningLabel{color:var(--dsw-alias-state-warn-primary);font-size:11px}._7D6uKa_cardSpinner{border:2px solid var(--dsw-alias-state-warn-primary);border-top-color:#0000;border-radius:50%;flex:none;width:10px;height:10px;animation:.8s linear infinite _7D6uKa_dshTbSpin}@keyframes _7D6uKa_dshTbSpin{to{transform:rotate(360deg)}}._7D6uKa_primaryButton{color:var(--dsw-alias-label-primary-foreground);background:var(--dsw-alias-button-info-fill);cursor:pointer;white-space:nowrap;border:none;border-radius:8px;padding:6px 14px;font-size:13px;font-weight:600}._7D6uKa_primaryButton:hover:not(:disabled){background:var(--dsw-alias-button-info-hover)}._7D6uKa_primaryButton:disabled{opacity:.5;cursor:default}._7D6uKa_ghostButton{color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;white-space:nowrap;background:0 0;border-radius:8px;padding:5px 12px;font-size:12px}._7D6uKa_ghostButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}._7D6uKa_ghostButton:disabled{opacity:.45;cursor:default}._7D6uKa_dangerButton{color:#fff;background:var(--dsw-alias-state-error-primary);cursor:pointer;white-space:nowrap;border:none;border-radius:8px;padding:6px 14px;font-size:13px;font-weight:600}._7D6uKa_dangerButton:hover:not(:disabled){filter:brightness(1.08)}._7D6uKa_dangerButton:active:not(:disabled){filter:brightness(.94)}._7D6uKa_dangerButton:disabled{opacity:.5;cursor:default}._7D6uKa_iconButton{width:26px;height:26px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;justify-content:center;align-items:center;padding:0;font-size:13px;display:inline-flex}._7D6uKa_iconButton:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}._7D6uKa_linkButton{color:var(--dsw-alias-state-business-primary);cursor:pointer;white-space:nowrap;background:0 0;border:none;padding:0;font-size:12px}._7D6uKa_linkButton:hover{text-decoration:underline}._7D6uKa_modalBackdrop{z-index:1300;background:var(--dsw-alias-bg-mask-1);justify-content:center;align-items:center;display:flex;position:fixed;inset:0}._7D6uKa_modal{background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);width:min(520px,100vw - 48px);max-height:calc(100vh - 96px);box-shadow:var(--dsw-shadow-lv3);color:var(--dsw-alias-label-primary);border-radius:14px;flex-direction:column;gap:12px;padding:18px;display:flex;overflow-y:auto}._7D6uKa_modalTitle{margin:0;font-size:15px;font-weight:700}._7D6uKa_confirmMessage{color:var(--dsw-alias-label-secondary);white-space:pre-wrap;overflow-wrap:anywhere;margin:0;font-size:13px;line-height:1.5}._7D6uKa_modalFooter{justify-content:flex-end;gap:10px;margin-top:4px;display:flex}._7D6uKa_field{flex-direction:column;gap:5px;display:flex}._7D6uKa_fieldLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600}._7D6uKa_input{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);resize:vertical;border-radius:8px;outline:none;padding:7px 10px;font-family:inherit;font-size:13px}._7D6uKa_input:focus{border-color:var(--dsw-alias-state-business-primary)}._7D6uKa_select{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:none;max-width:100%;padding:7px 10px;font-family:inherit;font-size:13px}._7D6uKa_input::placeholder{color:var(--dsw-alias-label-tertiary)}._7D6uKa_formError{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px}._7D6uKa_detail{background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);width:min(640px,100vw - 48px);max-height:calc(100vh - 80px);box-shadow:var(--dsw-shadow-lv3);color:var(--dsw-alias-label-primary);border-radius:14px;flex-direction:column;display:flex;overflow:hidden}._7D6uKa_detailHeader{border-bottom:1px solid var(--dsw-alias-separator-primary);flex:none;align-items:center;gap:10px;padding:14px 18px;display:flex}._7D6uKa_detailTitle{overflow-wrap:anywhere;flex:1;margin:0;font-size:15px;font-weight:700}._7D6uKa_statusBadge{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:2px 10px;font-size:12px}._7D6uKa_statusBadge[data-status=running]{color:var(--dsw-alias-state-warn-primary);border-color:var(--dsw-alias-state-warn-primary)}._7D6uKa_statusBadge[data-status=done]{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary)}._7D6uKa_statusBadge[data-status=failed]{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}._7D6uKa_detailBody{flex-direction:column;flex:1;gap:16px;padding:14px 18px;display:flex;overflow-y:auto}._7D6uKa_detailSection{flex-direction:column;gap:6px;display:flex}._7D6uKa_detailSection h4{color:var(--dsw-alias-label-tertiary);text-transform:none;margin:0;font-size:12px;font-weight:700}._7D6uKa_detailText{color:var(--dsw-alias-label-primary);white-space:pre-wrap;overflow-wrap:anywhere;margin:0;font-size:13px;line-height:1.55}._7D6uKa_scheduleToggle{color:var(--dsw-alias-label-primary);cursor:pointer;user-select:none;align-items:center;gap:8px;font-size:13px;display:flex}._7D6uKa_scheduleToggle input{accent-color:var(--dsw-alias-state-business-primary)}._7D6uKa_scheduleRow{align-items:center;gap:8px;display:flex}._7D6uKa_scheduleInput{min-width:0;font-family:var(--dsw-font-markdown-code-block-small);flex:1;font-size:12.5px}._7D6uKa_scheduleInputInvalid,._7D6uKa_scheduleInputInvalid:focus{border-color:var(--dsw-alias-state-error-primary)}._7D6uKa_schedulePreset{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:none;flex:none;padding:7px 8px;font-size:12.5px}._7D6uKa_scheduleMeta{color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere;margin:0;font-size:12px}._7D6uKa_promptBlock{font-size:12.5px;line-height:1.5;font-family:var(--dsw-font-markdown-code-block-small);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-markdown-code-block);border:1px solid var(--dsw-alias-border-l1);white-space:pre-wrap;overflow-wrap:anywhere;border-radius:8px;max-height:240px;margin:0;padding:10px 12px;overflow-y:auto}._7D6uKa_executionList{flex-direction:column;gap:8px;margin:0;padding:0;list-style:none;display:flex}._7D6uKa_executionRow{border:1px solid var(--dsw-alias-border-l1);border-radius:8px;flex-wrap:wrap;align-items:center;gap:10px;padding:8px 10px;display:flex}._7D6uKa_executionBadge{color:var(--dsw-alias-state-warn-primary);background:var(--dsw-alias-state-warn-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:600}._7D6uKa_executionBadge[data-result=succeeded]{color:var(--dsw-alias-state-success-primary);background:0 0}._7D6uKa_executionBadge[data-result=failed]{color:var(--dsw-alias-state-error-primary);background:0 0}._7D6uKa_executionBadge[data-result=cancelled]{color:var(--dsw-alias-label-tertiary);background:0 0}._7D6uKa_executionTimes{color:var(--dsw-alias-label-secondary);font-size:12px}._7D6uKa_executionError{width:100%;color:var(--dsw-alias-state-error-primary);overflow-wrap:anywhere;font-size:12px}._7D6uKa_moveRow{flex-wrap:wrap;gap:8px;display:flex}._7D6uKa_detailFooter{border-top:1px solid var(--dsw-alias-separator-primary);flex:none;align-items:center;gap:10px;padding:12px 18px;display:flex}._7D6uKa_detailMeta{color:var(--dsw-alias-label-tertiary);margin-left:auto;font-size:11px}@container _7D6uKa_task-board-view (width<=768px){._7D6uKa_board{gap:10px;padding:10px}._7D6uKa_boardHeader{flex-wrap:wrap;align-items:center;gap:8px}._7D6uKa_backButton{flex:none;order:1}._7D6uKa_boardTitle{flex:auto;order:2}._7D6uKa_boardHeader>._7D6uKa_detailMeta{flex:1 0 100%;order:3;margin-left:0}._7D6uKa_search{flex:1 0 100%;order:4;min-width:0}._7D6uKa_boardHeader>button:not(._7D6uKa_backButton){flex:1 1 0;order:5;min-width:0}._7D6uKa_columns{scroll-snap-type:inline mandatory;scrollbar-width:none;-webkit-overflow-scrolling:touch;grid-auto-columns:86cqw;gap:10px;padding-inline:2px 14cqw;scroll-padding-inline:2px}._7D6uKa_columns::-webkit-scrollbar{display:none}._7D6uKa_column{scroll-snap-align:start;scroll-snap-stop:always}}@container _7D6uKa_task-board-view (width<=720px){._7D6uKa_boardHeader>._7D6uKa_detailMeta{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}}@container _7D6uKa_task-board-view (width<=600px){._7D6uKa_board{padding-inline:8px}}@media (width<=768px){[data-dsh-taskboard-view]{height:100dvh}._7D6uKa_entry,._7D6uKa_card,._7D6uKa_primaryButton,._7D6uKa_ghostButton,._7D6uKa_dangerButton,._7D6uKa_iconButton,._7D6uKa_linkButton,._7D6uKa_search,._7D6uKa_input,._7D6uKa_select,._7D6uKa_schedulePreset,._7D6uKa_scheduleToggle{min-height:44px}._7D6uKa_search,._7D6uKa_input,._7D6uKa_select,._7D6uKa_schedulePreset{box-sizing:border-box;font-size:16px}._7D6uKa_modalBackdrop{justify-content:stretch;align-items:stretch;width:100vw;height:100dvh}._7D6uKa_modal,._7D6uKa_detail{box-sizing:border-box;border:0;border-radius:0;width:100vw;height:100dvh;max-height:none}._7D6uKa_modal{padding-top:max(16px, env(safe-area-inset-top));padding-right:max(16px, env(safe-area-inset-right));padding-bottom:max(16px, env(safe-area-inset-bottom));padding-left:max(16px, env(safe-area-inset-left))}._7D6uKa_modalFooter{z-index:1;background:var(--dsw-alias-bg-base);flex-wrap:wrap;padding-top:8px;position:sticky;bottom:0}._7D6uKa_modalFooter>button{flex:120px}._7D6uKa_detailHeader{padding-top:max(12px, env(safe-area-inset-top));padding-right:max(14px, env(safe-area-inset-right));padding-left:max(14px, env(safe-area-inset-left));flex-wrap:wrap}._7D6uKa_detailTitle{min-width:0}._7D6uKa_detailBody{overscroll-behavior-y:contain;padding-right:max(14px, env(safe-area-inset-right));padding-left:max(14px, env(safe-area-inset-left))}._7D6uKa_detailFooter{padding-right:max(14px, env(safe-area-inset-right));padding-bottom:max(12px, env(safe-area-inset-bottom));padding-left:max(14px, env(safe-area-inset-left));flex-wrap:wrap}._7D6uKa_detailFooter>button{flex:96px}._7D6uKa_detailFooter>._7D6uKa_detailMeta{text-align:end;flex:1 0 100%;margin-left:0}._7D6uKa_scheduleRow{flex-direction:column;align-items:stretch}._7D6uKa_schedulePreset{width:100%}}._7D6uKa_entry:focus-visible,._7D6uKa_card:focus-visible,._7D6uKa_primaryButton:focus-visible,._7D6uKa_ghostButton:focus-visible,._7D6uKa_dangerButton:focus-visible,._7D6uKa_iconButton:focus-visible,._7D6uKa_linkButton:focus-visible,._7D6uKa_search:focus-visible,._7D6uKa_input:focus-visible,._7D6uKa_select:focus-visible,._7D6uKa_schedulePreset:focus-visible,._7D6uKa_scheduleToggle input:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}._7D6uKa_entry,._7D6uKa_primaryButton,._7D6uKa_ghostButton,._7D6uKa_dangerButton,._7D6uKa_iconButton,._7D6uKa_linkButton,._7D6uKa_search,._7D6uKa_input,._7D6uKa_select,._7D6uKa_schedulePreset,._7D6uKa_scheduleToggle input{transition:background-color .12s,color .12s,border-color .12s,outline-color .12s,box-shadow .12s,transform .12s}._7D6uKa_card:active{box-shadow:var(--dsw-shadow-lv1);transform:translateY(0)}._7D6uKa_entry:active,._7D6uKa_primaryButton:active:not(:disabled),._7D6uKa_ghostButton:active:not(:disabled),._7D6uKa_dangerButton:active:not(:disabled),._7D6uKa_iconButton:active:not(:disabled),._7D6uKa_linkButton:active:not(:disabled){transform:translateY(1px)}._7D6uKa_entry[data-active]:hover{background:var(--dsw-specific-sidebar-nav-item-active)}._7D6uKa_iconButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}._7D6uKa_linkButton:hover:not(:disabled){text-decoration:underline}._7D6uKa_iconButton:disabled,._7D6uKa_linkButton:disabled{opacity:.45;cursor:default}._7D6uKa_search:focus,._7D6uKa_select:focus,._7D6uKa_schedulePreset:focus{border-color:var(--dsw-alias-state-business-primary)}._7D6uKa_scheduleToggle input{margin:0}@media (prefers-reduced-motion:reduce){._7D6uKa_entry,._7D6uKa_card,._7D6uKa_primaryButton,._7D6uKa_ghostButton,._7D6uKa_dangerButton,._7D6uKa_iconButton,._7D6uKa_linkButton,._7D6uKa_search,._7D6uKa_input,._7D6uKa_select,._7D6uKa_schedulePreset,._7D6uKa_scheduleToggle input{transition:none}._7D6uKa_cardSpinner{animation:none}}._7D6uKa_cardTags{flex-wrap:wrap;gap:4px;display:flex}._7D6uKa_cardTag{border:1px solid var(--dsh-task-tag-border);background:var(--dsh-task-tag-fill);max-width:100%;color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;border-radius:999px;padding:0 7px;font-size:10px;line-height:16px;overflow:hidden}._7D6uKa_tagFilter{flex-wrap:wrap;align-items:center;gap:6px;margin:0 0 10px;display:flex}._7D6uKa_tagFilterLabel{color:var(--dsw-alias-label-tertiary);font-size:11px}._7D6uKa_tagChip{border:1px solid var(--dsh-task-tag-border);color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border-radius:999px;padding:1px 9px;font-family:inherit;font-size:11px;line-height:18px}._7D6uKa_tagChip[data-active=true]{background:var(--dsh-task-tag-fill);color:var(--dsw-alias-label-primary)}._7D6uKa_cardTag[data-tag-tone=\"0\"],._7D6uKa_tagChip[data-tag-tone=\"0\"]{--dsh-task-tag-fill:#4e93e82e;--dsh-task-tag-border:#4e93e866}._7D6uKa_cardTag[data-tag-tone=\"1\"],._7D6uKa_tagChip[data-tag-tone=\"1\"]{--dsh-task-tag-fill:#2ea36a2e;--dsh-task-tag-border:#2ea36a66}._7D6uKa_cardTag[data-tag-tone=\"2\"],._7D6uKa_tagChip[data-tag-tone=\"2\"]{--dsh-task-tag-fill:#d08a2a2e;--dsh-task-tag-border:#d08a2a66}._7D6uKa_cardTag[data-tag-tone=\"3\"],._7D6uKa_tagChip[data-tag-tone=\"3\"]{--dsh-task-tag-fill:#b456c82e;--dsh-task-tag-border:#b456c866}._7D6uKa_cardTag[data-tag-tone=\"4\"],._7D6uKa_tagChip[data-tag-tone=\"4\"]{--dsh-task-tag-fill:#cf5f7a2e;--dsh-task-tag-border:#cf5f7a66}._7D6uKa_cardTag[data-tag-tone=\"5\"],._7D6uKa_tagChip[data-tag-tone=\"5\"]{--dsh-task-tag-fill:#4a9fb52e;--dsh-task-tag-border:#4a9fb566}._7D6uKa_fieldHint{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.4}._7D6uKa_tagRow{align-items:center;gap:6px;display:flex}._7D6uKa_tagRow ._7D6uKa_input{flex:1 1 0;min-width:0}._7D6uKa_tagRow ._7D6uKa_ghostButton{flex:none}._7D6uKa_tagAddButton{align-self:flex-start}._7D6uKa_projectFilter{flex:none;align-items:center;gap:6px;display:flex}._7D6uKa_projectFilterLabel{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-size:12px}._7D6uKa_projectDialog{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;flex-direction:column;flex:none;gap:8px;margin-bottom:8px;padding:10px 12px;display:flex}._7D6uKa_projectDialogActions{justify-content:flex-end;gap:8px;display:flex}._7D6uKa_aiParse{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;flex-direction:column;gap:6px;padding:10px 12px;display:flex}._7D6uKa_aiParseRow{align-items:center;gap:8px;display:flex}._7D6uKa_aiParseRow ._7D6uKa_select{flex:1 1 0;min-width:0}._7D6uKa_aiParseRow ._7D6uKa_ghostButton,._7D6uKa_aiParseRow ._7D6uKa_primaryButton{flex:none}";
|
|
2426
|
+
const css$1 = "[data-dsh-taskboard-view]{box-sizing:border-box;background:var(--dsw-alias-bg-base);flex-direction:column;width:100%;min-width:0;height:100%;min-height:0;display:flex;container:_7D6uKa_task-board-view/inline-size}._7D6uKa_board{box-sizing:border-box;background:var(--dsw-alias-bg-base);min-width:0;height:100%;min-height:0;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family);flex-direction:column;gap:12px;padding:14px 16px 16px;display:flex}._7D6uKa_boardHeader{flex:none;align-items:center;gap:10px;display:flex}._7D6uKa_boardTitle{color:var(--dsw-alias-label-primary);white-space:nowrap;margin:0;font-size:16px;font-weight:700}._7D6uKa_backButton{align-items:center;gap:4px;display:inline-flex}._7D6uKa_search{min-width:120px;color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:none;flex:0 260px;padding:6px 10px;font-size:13px}._7D6uKa_search::placeholder{color:var(--dsw-alias-label-tertiary)}._7D6uKa_columns{overscroll-behavior-inline:contain;scrollbar-color:var(--dsw-alias-border-l3) var(--dsw-alias-interactive-bg-hover);scrollbar-width:thin;flex:1;grid-auto-columns:minmax(220px,1fr);grid-auto-flow:column;gap:12px;min-height:0;padding-bottom:6px;display:grid;overflow:auto hidden}._7D6uKa_columns::-webkit-scrollbar{height:10px}._7D6uKa_columns::-webkit-scrollbar-track{background:var(--dsw-alias-interactive-bg-hover);border-radius:999px}._7D6uKa_columns::-webkit-scrollbar-thumb{background:var(--dsw-alias-border-l3);background-clip:content-box;border:2px solid #0000;border-radius:999px}._7D6uKa_columns::-webkit-scrollbar-thumb:hover{background:var(--dsw-alias-border-l4);background-clip:content-box}._7D6uKa_column{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:12px;flex-direction:column;min-height:0;display:flex;overflow:hidden}._7D6uKa_columnHeader{flex:none;align-items:center;gap:6px;padding:10px 12px;display:flex}._7D6uKa_columnTitle{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;flex:1;margin:0;font-size:13px;font-weight:700;overflow:hidden}._7D6uKa_columnCount{min-width:0;color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-interactive-bg-hover);border-radius:999px;flex:none;padding:1px 8px;font-size:12px}._7D6uKa_statusDot{border-radius:50%;flex:none;width:8px;height:8px}._7D6uKa_statusDot[data-status=backlog]{background:var(--dsw-alias-label-tertiary)}._7D6uKa_statusDot[data-status=todo]{background:var(--dsw-alias-state-business-primary)}._7D6uKa_statusDot[data-status=running]{background:var(--dsw-alias-state-warn-primary)}._7D6uKa_statusDot[data-status=done]{background:var(--dsw-alias-state-success-primary)}._7D6uKa_statusDot[data-status=failed]{background:var(--dsw-alias-state-error-primary)}._7D6uKa_cards{flex-direction:column;flex:1;gap:8px;min-height:0;padding:2px 8px 10px;display:flex;overflow-y:auto}._7D6uKa_columnEmpty{text-align:center;color:var(--dsw-alias-label-tertiary);padding:24px 8px;font-size:12px}._7D6uKa_card{text-align:left;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;color:var(--dsw-alias-label-primary);border-radius:10px;flex-direction:column;gap:6px;padding:10px 12px;font-family:inherit;transition:box-shadow .12s,border-color .12s,transform .12s;display:flex}._7D6uKa_card:hover{box-shadow:var(--dsw-shadow-lv2);border-color:var(--dsw-alias-border-l3);transform:translateY(-1px)}._7D6uKa_card[data-status=running]{border-color:var(--dsw-alias-state-warn-primary)}._7D6uKa_cardTitle{-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:13px;font-weight:600;line-height:1.35;display:-webkit-box;overflow:hidden}._7D6uKa_cardExcerpt{color:var(--dsw-alias-label-secondary);-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:12px;line-height:1.4;display:-webkit-box;overflow:hidden}._7D6uKa_cardMeta{color:var(--dsw-alias-label-tertiary);align-items:center;gap:8px;font-size:11px;display:flex}._7D6uKa_cardTime{text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}._7D6uKa_cardSchedule{white-space:nowrap;min-width:0;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover);border-radius:999px;flex:none;padding:2px 6px;font-size:12px;line-height:1}._7D6uKa_cardRun{flex:none}._7D6uKa_cardRun[data-result=failed]{color:var(--dsw-alias-state-error-primary)}._7D6uKa_cardRun[data-result=succeeded]{color:var(--dsw-alias-state-success-primary)}._7D6uKa_cardSession{color:var(--dsw-alias-state-business-primary);flex:none}._7D6uKa_cardRunningLabel{color:var(--dsw-alias-state-warn-primary);font-size:11px}._7D6uKa_cardSpinner{border:2px solid var(--dsw-alias-state-warn-primary);border-top-color:#0000;border-radius:50%;flex:none;width:10px;height:10px;animation:.8s linear infinite _7D6uKa_dshTbSpin}@keyframes _7D6uKa_dshTbSpin{to{transform:rotate(360deg)}}._7D6uKa_primaryButton{color:var(--dsw-alias-label-primary-foreground);background:var(--dsw-alias-button-info-fill);cursor:pointer;white-space:nowrap;border:none;border-radius:8px;padding:6px 14px;font-size:13px;font-weight:600}._7D6uKa_primaryButton:hover:not(:disabled){background:var(--dsw-alias-button-info-hover)}._7D6uKa_primaryButton:disabled{opacity:.5;cursor:default}._7D6uKa_ghostButton{color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;white-space:nowrap;background:0 0;border-radius:8px;padding:5px 12px;font-size:12px}._7D6uKa_ghostButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}._7D6uKa_ghostButton:disabled{opacity:.45;cursor:default}._7D6uKa_dangerButton{color:#fff;background:var(--dsw-alias-state-error-primary);cursor:pointer;white-space:nowrap;border:none;border-radius:8px;padding:6px 14px;font-size:13px;font-weight:600}._7D6uKa_dangerButton:hover:not(:disabled){filter:brightness(1.08)}._7D6uKa_dangerButton:active:not(:disabled){filter:brightness(.94)}._7D6uKa_dangerButton:disabled{opacity:.5;cursor:default}._7D6uKa_iconButton{width:26px;height:26px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;justify-content:center;align-items:center;padding:0;font-size:13px;display:inline-flex}._7D6uKa_iconButton:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}._7D6uKa_linkButton{color:var(--dsw-alias-state-business-primary);cursor:pointer;white-space:nowrap;background:0 0;border:none;padding:0;font-size:12px}._7D6uKa_linkButton:hover{text-decoration:underline}._7D6uKa_modalBackdrop{z-index:1300;background:var(--dsw-alias-bg-mask-1);justify-content:center;align-items:center;display:flex;position:fixed;inset:0}._7D6uKa_modal{background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);width:min(520px,100vw - 48px);max-height:calc(100vh - 96px);box-shadow:var(--dsw-shadow-lv3);color:var(--dsw-alias-label-primary);border-radius:14px;flex-direction:column;gap:12px;padding:18px;display:flex;overflow-y:auto}._7D6uKa_modalTitle{margin:0;font-size:15px;font-weight:700}._7D6uKa_confirmMessage{color:var(--dsw-alias-label-secondary);white-space:pre-wrap;overflow-wrap:anywhere;margin:0;font-size:13px;line-height:1.5}._7D6uKa_modalFooter{justify-content:flex-end;gap:10px;margin-top:4px;display:flex}._7D6uKa_field{flex-direction:column;gap:5px;display:flex}._7D6uKa_fieldLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600}._7D6uKa_input{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);resize:vertical;border-radius:8px;outline:none;padding:7px 10px;font-family:inherit;font-size:13px}._7D6uKa_input:focus{border-color:var(--dsw-alias-state-business-primary)}._7D6uKa_select{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:none;max-width:100%;padding:7px 10px;font-family:inherit;font-size:13px}._7D6uKa_input::placeholder{color:var(--dsw-alias-label-tertiary)}._7D6uKa_formError{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px}._7D6uKa_detail{background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);width:min(640px,100vw - 48px);max-height:calc(100vh - 80px);box-shadow:var(--dsw-shadow-lv3);color:var(--dsw-alias-label-primary);border-radius:14px;flex-direction:column;display:flex;overflow:hidden}._7D6uKa_detailHeader{border-bottom:1px solid var(--dsw-alias-separator-primary);flex:none;align-items:center;gap:10px;padding:14px 18px;display:flex}._7D6uKa_detailTitle{overflow-wrap:anywhere;flex:1;margin:0;font-size:15px;font-weight:700}._7D6uKa_statusBadge{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:2px 10px;font-size:12px}._7D6uKa_statusBadge[data-status=running]{color:var(--dsw-alias-state-warn-primary);border-color:var(--dsw-alias-state-warn-primary)}._7D6uKa_statusBadge[data-status=done]{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary)}._7D6uKa_statusBadge[data-status=failed]{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}._7D6uKa_detailBody{flex-direction:column;flex:1;gap:16px;padding:14px 18px;display:flex;overflow-y:auto}._7D6uKa_detailSection{flex-direction:column;gap:6px;display:flex}._7D6uKa_detailSection h4{color:var(--dsw-alias-label-tertiary);text-transform:none;margin:0;font-size:12px;font-weight:700}._7D6uKa_detailText{color:var(--dsw-alias-label-primary);white-space:pre-wrap;overflow-wrap:anywhere;margin:0;font-size:13px;line-height:1.55}._7D6uKa_scheduleToggle{color:var(--dsw-alias-label-primary);cursor:pointer;user-select:none;align-items:center;gap:8px;font-size:13px;display:flex}._7D6uKa_scheduleToggle input{accent-color:var(--dsw-alias-state-business-primary)}._7D6uKa_scheduleRow{align-items:center;gap:8px;display:flex}._7D6uKa_scheduleInput{min-width:0;font-family:var(--dsw-font-markdown-code-block-small);flex:1;font-size:12.5px}._7D6uKa_scheduleInputInvalid,._7D6uKa_scheduleInputInvalid:focus{border-color:var(--dsw-alias-state-error-primary)}._7D6uKa_schedulePreset{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:none;flex:none;padding:7px 8px;font-size:12.5px}._7D6uKa_scheduleMeta{color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere;margin:0;font-size:12px}._7D6uKa_promptBlock{font-size:12.5px;line-height:1.5;font-family:var(--dsw-font-markdown-code-block-small);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-markdown-code-block);border:1px solid var(--dsw-alias-border-l1);white-space:pre-wrap;overflow-wrap:anywhere;border-radius:8px;max-height:240px;margin:0;padding:10px 12px;overflow-y:auto}._7D6uKa_executionList{flex-direction:column;gap:8px;margin:0;padding:0;list-style:none;display:flex}._7D6uKa_executionRow{border:1px solid var(--dsw-alias-border-l1);border-radius:8px;flex-wrap:wrap;align-items:center;gap:10px;padding:8px 10px;display:flex}._7D6uKa_executionBadge{color:var(--dsw-alias-state-warn-primary);background:var(--dsw-alias-state-warn-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:600}._7D6uKa_executionBadge[data-result=succeeded]{color:var(--dsw-alias-state-success-primary);background:0 0}._7D6uKa_executionBadge[data-result=failed]{color:var(--dsw-alias-state-error-primary);background:0 0}._7D6uKa_executionBadge[data-result=cancelled]{color:var(--dsw-alias-label-tertiary);background:0 0}._7D6uKa_executionTimes{color:var(--dsw-alias-label-secondary);font-size:12px}._7D6uKa_executionError{width:100%;color:var(--dsw-alias-state-error-primary);overflow-wrap:anywhere;font-size:12px}._7D6uKa_moveRow{flex-wrap:wrap;gap:8px;display:flex}._7D6uKa_detailFooter{border-top:1px solid var(--dsw-alias-separator-primary);flex:none;align-items:center;gap:10px;padding:12px 18px;display:flex}._7D6uKa_detailMeta{color:var(--dsw-alias-label-tertiary);margin-left:auto;font-size:11px}@container _7D6uKa_task-board-view (width<=768px){._7D6uKa_board{gap:10px;padding:10px}._7D6uKa_boardHeader{flex-wrap:wrap;align-items:center;gap:8px}._7D6uKa_backButton{flex:none;order:1}._7D6uKa_boardTitle{flex:auto;order:2}._7D6uKa_boardHeader>._7D6uKa_detailMeta{flex:1 0 100%;order:3;margin-left:0}._7D6uKa_search{flex:1 0 100%;order:4;min-width:0}._7D6uKa_boardHeader>button:not(._7D6uKa_backButton){flex:1 1 0;order:5;min-width:0}._7D6uKa_columns{scroll-snap-type:inline mandatory;scrollbar-width:none;-webkit-overflow-scrolling:touch;grid-auto-columns:86cqw;gap:10px;padding-inline:2px 14cqw;scroll-padding-inline:2px}._7D6uKa_columns::-webkit-scrollbar{display:none}._7D6uKa_column{scroll-snap-align:start;scroll-snap-stop:always}}@container _7D6uKa_task-board-view (width<=720px){._7D6uKa_boardHeader>._7D6uKa_detailMeta{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}}@container _7D6uKa_task-board-view (width<=600px){._7D6uKa_board{padding-inline:8px}}@media (width<=768px){[data-dsh-taskboard-view]{height:100dvh}._7D6uKa_card,._7D6uKa_primaryButton,._7D6uKa_ghostButton,._7D6uKa_dangerButton,._7D6uKa_iconButton,._7D6uKa_linkButton,._7D6uKa_search,._7D6uKa_input,._7D6uKa_select,._7D6uKa_schedulePreset,._7D6uKa_scheduleToggle{min-height:44px}._7D6uKa_search,._7D6uKa_input,._7D6uKa_select,._7D6uKa_schedulePreset{box-sizing:border-box;font-size:16px}._7D6uKa_modalBackdrop{justify-content:stretch;align-items:stretch;width:100vw;height:100dvh}._7D6uKa_modal,._7D6uKa_detail{box-sizing:border-box;border:0;border-radius:0;width:100vw;height:100dvh;max-height:none}._7D6uKa_modal{padding-top:max(16px, env(safe-area-inset-top));padding-right:max(16px, env(safe-area-inset-right));padding-bottom:max(16px, env(safe-area-inset-bottom));padding-left:max(16px, env(safe-area-inset-left))}._7D6uKa_modalFooter{z-index:1;background:var(--dsw-alias-bg-base);flex-wrap:wrap;padding-top:8px;position:sticky;bottom:0}._7D6uKa_modalFooter>button{flex:120px}._7D6uKa_detailHeader{padding-top:max(12px, env(safe-area-inset-top));padding-right:max(14px, env(safe-area-inset-right));padding-left:max(14px, env(safe-area-inset-left));flex-wrap:wrap}._7D6uKa_detailTitle{min-width:0}._7D6uKa_detailBody{overscroll-behavior-y:contain;padding-right:max(14px, env(safe-area-inset-right));padding-left:max(14px, env(safe-area-inset-left))}._7D6uKa_detailFooter{padding-right:max(14px, env(safe-area-inset-right));padding-bottom:max(12px, env(safe-area-inset-bottom));padding-left:max(14px, env(safe-area-inset-left));flex-wrap:wrap}._7D6uKa_detailFooter>button{flex:96px}._7D6uKa_detailFooter>._7D6uKa_detailMeta{text-align:end;flex:1 0 100%;margin-left:0}._7D6uKa_scheduleRow{flex-direction:column;align-items:stretch}._7D6uKa_schedulePreset{width:100%}}._7D6uKa_card:focus-visible,._7D6uKa_primaryButton:focus-visible,._7D6uKa_ghostButton:focus-visible,._7D6uKa_dangerButton:focus-visible,._7D6uKa_iconButton:focus-visible,._7D6uKa_linkButton:focus-visible,._7D6uKa_search:focus-visible,._7D6uKa_input:focus-visible,._7D6uKa_select:focus-visible,._7D6uKa_schedulePreset:focus-visible,._7D6uKa_scheduleToggle input:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}._7D6uKa_primaryButton,._7D6uKa_ghostButton,._7D6uKa_dangerButton,._7D6uKa_iconButton,._7D6uKa_linkButton,._7D6uKa_search,._7D6uKa_input,._7D6uKa_select,._7D6uKa_schedulePreset,._7D6uKa_scheduleToggle input{transition:background-color .12s,color .12s,border-color .12s,outline-color .12s,box-shadow .12s,transform .12s}._7D6uKa_card:active{box-shadow:var(--dsw-shadow-lv1);transform:translateY(0)}._7D6uKa_primaryButton:active:not(:disabled),._7D6uKa_ghostButton:active:not(:disabled),._7D6uKa_dangerButton:active:not(:disabled),._7D6uKa_iconButton:active:not(:disabled),._7D6uKa_linkButton:active:not(:disabled){transform:translateY(1px)}._7D6uKa_iconButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}._7D6uKa_linkButton:hover:not(:disabled){text-decoration:underline}._7D6uKa_iconButton:disabled,._7D6uKa_linkButton:disabled{opacity:.45;cursor:default}._7D6uKa_search:focus,._7D6uKa_select:focus,._7D6uKa_schedulePreset:focus{border-color:var(--dsw-alias-state-business-primary)}._7D6uKa_scheduleToggle input{margin:0}@media (prefers-reduced-motion:reduce){._7D6uKa_card,._7D6uKa_primaryButton,._7D6uKa_ghostButton,._7D6uKa_dangerButton,._7D6uKa_iconButton,._7D6uKa_linkButton,._7D6uKa_search,._7D6uKa_input,._7D6uKa_select,._7D6uKa_schedulePreset,._7D6uKa_scheduleToggle input{transition:none}._7D6uKa_cardSpinner{animation:none}}._7D6uKa_cardTags{flex-wrap:wrap;gap:4px;display:flex}._7D6uKa_cardTag{border:1px solid var(--dsh-task-tag-border);background:var(--dsh-task-tag-fill);max-width:100%;color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;border-radius:999px;padding:0 7px;font-size:10px;line-height:16px;overflow:hidden}._7D6uKa_tagFilter{flex-wrap:wrap;align-items:center;gap:6px;margin:0 0 10px;display:flex}._7D6uKa_tagFilterLabel{color:var(--dsw-alias-label-tertiary);font-size:11px}._7D6uKa_tagChip{border:1px solid var(--dsh-task-tag-border);color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border-radius:999px;padding:1px 9px;font-family:inherit;font-size:11px;line-height:18px}._7D6uKa_tagChip[data-active=true]{background:var(--dsh-task-tag-fill);color:var(--dsw-alias-label-primary)}._7D6uKa_cardTag[data-tag-tone=\"0\"],._7D6uKa_tagChip[data-tag-tone=\"0\"]{--dsh-task-tag-fill:#4e93e82e;--dsh-task-tag-border:#4e93e866}._7D6uKa_cardTag[data-tag-tone=\"1\"],._7D6uKa_tagChip[data-tag-tone=\"1\"]{--dsh-task-tag-fill:#2ea36a2e;--dsh-task-tag-border:#2ea36a66}._7D6uKa_cardTag[data-tag-tone=\"2\"],._7D6uKa_tagChip[data-tag-tone=\"2\"]{--dsh-task-tag-fill:#d08a2a2e;--dsh-task-tag-border:#d08a2a66}._7D6uKa_cardTag[data-tag-tone=\"3\"],._7D6uKa_tagChip[data-tag-tone=\"3\"]{--dsh-task-tag-fill:#b456c82e;--dsh-task-tag-border:#b456c866}._7D6uKa_cardTag[data-tag-tone=\"4\"],._7D6uKa_tagChip[data-tag-tone=\"4\"]{--dsh-task-tag-fill:#cf5f7a2e;--dsh-task-tag-border:#cf5f7a66}._7D6uKa_cardTag[data-tag-tone=\"5\"],._7D6uKa_tagChip[data-tag-tone=\"5\"]{--dsh-task-tag-fill:#4a9fb52e;--dsh-task-tag-border:#4a9fb566}._7D6uKa_fieldHint{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.4}._7D6uKa_tagRow{align-items:center;gap:6px;display:flex}._7D6uKa_tagRow ._7D6uKa_input{flex:1 1 0;min-width:0}._7D6uKa_tagRow ._7D6uKa_ghostButton{flex:none}._7D6uKa_tagAddButton{align-self:flex-start}._7D6uKa_projectFilter{flex:none;align-items:center;gap:6px;display:flex}._7D6uKa_projectFilterLabel{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-size:12px}._7D6uKa_projectDialog{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;flex-direction:column;flex:none;gap:8px;margin-bottom:8px;padding:10px 12px;display:flex}._7D6uKa_projectDialogActions{justify-content:flex-end;gap:8px;display:flex}._7D6uKa_aiParse{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;flex-direction:column;gap:6px;padding:10px 12px;display:flex}._7D6uKa_aiParseRow{align-items:center;gap:8px;display:flex}._7D6uKa_aiParseRow ._7D6uKa_select{flex:1 1 0;min-width:0}._7D6uKa_aiParseRow ._7D6uKa_ghostButton,._7D6uKa_aiParseRow ._7D6uKa_primaryButton{flex:none}._7D6uKa_cardSubtask{border:1px solid var(--dsw-alias-border-l2);max-width:100%;color:var(--dsw-alias-label-secondary);text-overflow:ellipsis;white-space:nowrap;background:0 0;border-radius:999px;padding:0 7px;font-size:10px;line-height:16px;overflow:hidden}._7D6uKa_cardSubtask[data-tone=failed]{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}._7D6uKa_cardSubtask[data-tone=running]{color:var(--dsw-alias-state-warn-primary);border-color:var(--dsw-alias-state-warn-primary)}._7D6uKa_cardSubtask[data-tone=done]{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary)}._7D6uKa_subtaskList{flex-direction:column;gap:6px;margin:0;padding:0;list-style:none;display:flex}._7D6uKa_subtaskRow{border:1px solid var(--dsw-alias-border-l1);border-radius:8px;flex-wrap:wrap;align-items:center;gap:10px;padding:6px 10px;display:flex}._7D6uKa_subtaskRow ._7D6uKa_linkButton:first-child{text-align:left;overflow-wrap:anywhere;flex:auto;min-width:0}._7D6uKa_subtaskAddRow{flex-wrap:wrap;gap:8px;display:flex}._7D6uKa_pickList{flex-direction:column;gap:6px;max-height:320px;margin:0;padding:0;list-style:none;display:flex;overflow-y:auto}._7D6uKa_pickRow{border:1px solid var(--dsw-alias-border-l1);border-radius:8px;align-items:center;gap:10px;padding:6px 10px;display:flex}._7D6uKa_pickTitle{min-width:0;color:var(--dsw-alias-label-primary);overflow-wrap:anywhere;flex:auto;font-size:13px}";
|
|
2001
2427
|
const tagId$1 = "@linxin666/dsh-client-ui-task-board/packages/dsh-task-board/src/client/board.module.css";
|
|
2002
2428
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
2003
2429
|
const tag = document.createElement("style");
|
|
@@ -2021,6 +2447,7 @@ window.__ModuleLoader__.load({
|
|
|
2021
2447
|
"cardSchedule": "_7D6uKa_cardSchedule",
|
|
2022
2448
|
"cardSession": "_7D6uKa_cardSession",
|
|
2023
2449
|
"cardSpinner": "_7D6uKa_cardSpinner",
|
|
2450
|
+
"cardSubtask": "_7D6uKa_cardSubtask",
|
|
2024
2451
|
"cardTag": "_7D6uKa_cardTag",
|
|
2025
2452
|
"cardTags": "_7D6uKa_cardTags",
|
|
2026
2453
|
"cardTime": "_7D6uKa_cardTime",
|
|
@@ -2043,9 +2470,6 @@ window.__ModuleLoader__.load({
|
|
|
2043
2470
|
"detailText": "_7D6uKa_detailText",
|
|
2044
2471
|
"detailTitle": "_7D6uKa_detailTitle",
|
|
2045
2472
|
"dshTbSpin": "_7D6uKa_dshTbSpin",
|
|
2046
|
-
"entry": "_7D6uKa_entry",
|
|
2047
|
-
"entryIcon": "_7D6uKa_entryIcon",
|
|
2048
|
-
"entryLabel": "_7D6uKa_entryLabel",
|
|
2049
2473
|
"executionBadge": "_7D6uKa_executionBadge",
|
|
2050
2474
|
"executionError": "_7D6uKa_executionError",
|
|
2051
2475
|
"executionList": "_7D6uKa_executionList",
|
|
@@ -2064,6 +2488,9 @@ window.__ModuleLoader__.load({
|
|
|
2064
2488
|
"modalFooter": "_7D6uKa_modalFooter",
|
|
2065
2489
|
"modalTitle": "_7D6uKa_modalTitle",
|
|
2066
2490
|
"moveRow": "_7D6uKa_moveRow",
|
|
2491
|
+
"pickList": "_7D6uKa_pickList",
|
|
2492
|
+
"pickRow": "_7D6uKa_pickRow",
|
|
2493
|
+
"pickTitle": "_7D6uKa_pickTitle",
|
|
2067
2494
|
"primaryButton": "_7D6uKa_primaryButton",
|
|
2068
2495
|
"projectDialog": "_7D6uKa_projectDialog",
|
|
2069
2496
|
"projectDialogActions": "_7D6uKa_projectDialogActions",
|
|
@@ -2080,6 +2507,9 @@ window.__ModuleLoader__.load({
|
|
|
2080
2507
|
"select": "_7D6uKa_select",
|
|
2081
2508
|
"statusBadge": "_7D6uKa_statusBadge",
|
|
2082
2509
|
"statusDot": "_7D6uKa_statusDot",
|
|
2510
|
+
"subtaskAddRow": "_7D6uKa_subtaskAddRow",
|
|
2511
|
+
"subtaskList": "_7D6uKa_subtaskList",
|
|
2512
|
+
"subtaskRow": "_7D6uKa_subtaskRow",
|
|
2083
2513
|
"tagAddButton": "_7D6uKa_tagAddButton",
|
|
2084
2514
|
"tagChip": "_7D6uKa_tagChip",
|
|
2085
2515
|
"tagFilter": "_7D6uKa_tagFilter",
|
|
@@ -2335,15 +2765,16 @@ window.__ModuleLoader__.load({
|
|
|
2335
2765
|
* Creates through the Host and closes only after the Host confirms it.
|
|
2336
2766
|
*/
|
|
2337
2767
|
/** New-task form overlay. */
|
|
2338
|
-
function NewTaskModal({ controller, onClose, initialTask, defaultWorkspaceId, onDuplicateSuccess }) {
|
|
2768
|
+
function NewTaskModal({ controller, onClose, initialTask, defaultWorkspaceId, onDuplicateSuccess, parentTask }) {
|
|
2339
2769
|
const isDuplicate = initialTask !== void 0;
|
|
2340
2770
|
const [title, setTitle] = (0, react.useState)(initialTask?.title ?? "");
|
|
2341
2771
|
const [description, setDescription] = (0, react.useState)(initialTask?.description ?? "");
|
|
2342
2772
|
const [prompt, setPrompt] = (0, react.useState)(initialTask?.prompt ?? "");
|
|
2343
|
-
const [workspaceId, setWorkspaceId] = (0, react.useState)(initialTask?.workspaceId ?? defaultWorkspaceId ?? "");
|
|
2344
|
-
const [mode, setMode] = (0, react.useState)(initialTask?.mode ?? "");
|
|
2773
|
+
const [workspaceId, setWorkspaceId] = (0, react.useState)(initialTask?.workspaceId ?? parentTask?.workspaceId ?? defaultWorkspaceId ?? "");
|
|
2774
|
+
const [mode, setMode] = (0, react.useState)(initialTask?.mode ?? parentTask?.mode ?? "");
|
|
2345
2775
|
const [permission, setPermission] = (0, react.useState)(initialTask?.permission ?? "");
|
|
2346
|
-
const [model, setModel] = (0, react.useState)(initialTask?.model ?? "");
|
|
2776
|
+
const [model, setModel] = (0, react.useState)(initialTask?.model ?? parentTask?.model ?? "");
|
|
2777
|
+
const inheritedPermission = parentTask === void 0 ? void 0 : effectiveTaskPermission(parentTask);
|
|
2347
2778
|
const [reuseSession, setReuseSession] = (0, react.useState)(initialTask?.reuseSession ?? false);
|
|
2348
2779
|
const [scheduleEnabled, setScheduleEnabled] = (0, react.useState)(initialTask?.schedule?.enabled ?? false);
|
|
2349
2780
|
const [scheduleCron, setScheduleCron] = (0, react.useState)(initialTask?.schedule?.cron ?? "");
|
|
@@ -2363,6 +2794,9 @@ window.__ModuleLoader__.load({
|
|
|
2363
2794
|
const [parseError, setParseError] = (0, react.useState)(void 0);
|
|
2364
2795
|
const parseAbort = (0, react.useRef)(void 0);
|
|
2365
2796
|
const parseModels = options.models ?? [];
|
|
2797
|
+
const workspaceKnown = workspaceId === "" || options.workspaces.some((item) => item.workspaceId === workspaceId);
|
|
2798
|
+
const modeKnown = mode === "" || options.presets.some((item) => item.id === mode);
|
|
2799
|
+
const modelKnown = model === "" || parseModels.some((item) => item.id === model);
|
|
2366
2800
|
(0, react.useEffect)(() => controller.subscribe(() => setOptions(controller.getSnapshot().executionOptions)), [controller]);
|
|
2367
2801
|
(0, react.useEffect)(() => {
|
|
2368
2802
|
if (parseModel === "" || parseModels.length === 0) return;
|
|
@@ -2434,6 +2868,7 @@ window.__ModuleLoader__.load({
|
|
|
2434
2868
|
title,
|
|
2435
2869
|
description,
|
|
2436
2870
|
prompt,
|
|
2871
|
+
...parentTask === void 0 ? {} : { parentId: parentTask.id },
|
|
2437
2872
|
freeze,
|
|
2438
2873
|
handover,
|
|
2439
2874
|
workspaceId: workspaceId === "" ? void 0 : workspaceId,
|
|
@@ -2461,7 +2896,7 @@ window.__ModuleLoader__.load({
|
|
|
2461
2896
|
};
|
|
2462
2897
|
/** Next-run preview for a valid armed cron (creation-time only). */
|
|
2463
2898
|
const scheduleNextRun = scheduleEnabled && scheduleCron.trim() !== "" && isValidCron(scheduleCron) ? nextRunAtMs(scheduleCron, Date.now()) : void 0;
|
|
2464
|
-
const modalTitle = isDuplicate ? t("new.duplicateTitle") : t("board.new");
|
|
2899
|
+
const modalTitle = parentTask !== void 0 ? t("new.subtaskTitle") : isDuplicate ? t("new.duplicateTitle") : t("board.new");
|
|
2465
2900
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ModalShell, {
|
|
2466
2901
|
ariaLabel: modalTitle,
|
|
2467
2902
|
title: modalTitle,
|
|
@@ -2542,6 +2977,17 @@ window.__ModuleLoader__.load({
|
|
|
2542
2977
|
})
|
|
2543
2978
|
]
|
|
2544
2979
|
}),
|
|
2980
|
+
parentTask !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
2981
|
+
className: board_module_css_default.detailText,
|
|
2982
|
+
"data-dsh-part": "subtask-inherit",
|
|
2983
|
+
children: [
|
|
2984
|
+
t("detail.parent"),
|
|
2985
|
+
": ",
|
|
2986
|
+
parentTask.title,
|
|
2987
|
+
" · ",
|
|
2988
|
+
t("new.subtaskInherit")
|
|
2989
|
+
]
|
|
2990
|
+
}),
|
|
2545
2991
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TaskContentFields, {
|
|
2546
2992
|
title,
|
|
2547
2993
|
description,
|
|
@@ -2606,13 +3052,20 @@ window.__ModuleLoader__.load({
|
|
|
2606
3052
|
onChange: (event) => {
|
|
2607
3053
|
setWorkspaceId(event.target.value);
|
|
2608
3054
|
},
|
|
2609
|
-
children: [
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
3055
|
+
children: [
|
|
3056
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
3057
|
+
value: "",
|
|
3058
|
+
children: t("exec.workspace.recent")
|
|
3059
|
+
}),
|
|
3060
|
+
!workspaceKnown && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
3061
|
+
value: workspaceId,
|
|
3062
|
+
children: [workspaceId, t("exec.mode.removed")]
|
|
3063
|
+
}),
|
|
3064
|
+
options.workspaces.map((workspace) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
3065
|
+
value: workspace.workspaceId,
|
|
3066
|
+
children: workspace.title
|
|
3067
|
+
}, workspace.workspaceId))
|
|
3068
|
+
]
|
|
2616
3069
|
})]
|
|
2617
3070
|
}),
|
|
2618
3071
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
@@ -2626,18 +3079,25 @@ window.__ModuleLoader__.load({
|
|
|
2626
3079
|
onChange: (event) => {
|
|
2627
3080
|
setMode(event.target.value);
|
|
2628
3081
|
},
|
|
2629
|
-
children: [
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
3082
|
+
children: [
|
|
3083
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
3084
|
+
value: "",
|
|
3085
|
+
children: t("exec.mode.default")
|
|
3086
|
+
}),
|
|
3087
|
+
!modeKnown && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
3088
|
+
value: mode,
|
|
3089
|
+
children: [mode, t("exec.mode.removed")]
|
|
3090
|
+
}),
|
|
3091
|
+
options.presets.map((preset) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
3092
|
+
value: preset.id,
|
|
3093
|
+
disabled: preset.broken !== void 0,
|
|
3094
|
+
children: [
|
|
3095
|
+
preset.name ?? preset.id,
|
|
3096
|
+
preset.isDefault ? t("exec.mode.defaultSuffix") : "",
|
|
3097
|
+
preset.broken !== void 0 ? t("exec.mode.brokenSuffix") : ""
|
|
3098
|
+
]
|
|
3099
|
+
}, preset.id))
|
|
3100
|
+
]
|
|
2641
3101
|
})]
|
|
2642
3102
|
}),
|
|
2643
3103
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
@@ -2653,7 +3113,7 @@ window.__ModuleLoader__.load({
|
|
|
2653
3113
|
},
|
|
2654
3114
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2655
3115
|
value: "",
|
|
2656
|
-
children: t("exec.permission.default")
|
|
3116
|
+
children: inheritedPermission === void 0 ? t("exec.permission.default") : t("exec.permission.inheritParent", { permission: t(`exec.permission.${inheritedPermission}`) })
|
|
2657
3117
|
}), TASK_PERMISSIONS.map((id) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2658
3118
|
value: id,
|
|
2659
3119
|
children: t(`exec.permission.${id}`)
|
|
@@ -2671,13 +3131,20 @@ window.__ModuleLoader__.load({
|
|
|
2671
3131
|
onChange: (event) => {
|
|
2672
3132
|
setModel(event.target.value);
|
|
2673
3133
|
},
|
|
2674
|
-
children: [
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
3134
|
+
children: [
|
|
3135
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
3136
|
+
value: "",
|
|
3137
|
+
children: t("exec.model.default")
|
|
3138
|
+
}),
|
|
3139
|
+
!modelKnown && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
3140
|
+
value: model,
|
|
3141
|
+
children: [model, t("exec.model.unknown")]
|
|
3142
|
+
}),
|
|
3143
|
+
options.models?.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
3144
|
+
value: item.id,
|
|
3145
|
+
children: item.name ?? item.id
|
|
3146
|
+
}, item.id))
|
|
3147
|
+
]
|
|
2681
3148
|
})]
|
|
2682
3149
|
}),
|
|
2683
3150
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
@@ -2811,7 +3278,7 @@ window.__ModuleLoader__.load({
|
|
|
2811
3278
|
if (timeZone !== void 0) return formatHostTimestamp(ms, timeZone);
|
|
2812
3279
|
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
|
2813
3280
|
}
|
|
2814
|
-
function TaskCardInner({ task, pending, timeZone, onClick }) {
|
|
3281
|
+
function TaskCardInner({ task, pending, timeZone, onClick, subtaskCount = 0, isSubtask = false, subtasksDone = 0, subtasksRunning = 0, subtasksFailed = 0 }) {
|
|
2815
3282
|
const latest = task.executions[task.executions.length - 1];
|
|
2816
3283
|
const runs = task.executions.length;
|
|
2817
3284
|
const archived = task.archivedAt !== void 0;
|
|
@@ -2852,6 +3319,27 @@ window.__ModuleLoader__.load({
|
|
|
2852
3319
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2853
3320
|
className: board_module_css_default.cardMeta,
|
|
2854
3321
|
children: [
|
|
3322
|
+
isSubtask && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3323
|
+
className: board_module_css_default.cardSubtask,
|
|
3324
|
+
"data-dsh-part": "subtask-badge",
|
|
3325
|
+
children: t("card.subtask")
|
|
3326
|
+
}),
|
|
3327
|
+
subtaskCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3328
|
+
className: board_module_css_default.cardSubtask,
|
|
3329
|
+
"data-dsh-part": "subtask-count",
|
|
3330
|
+
"data-tone": subtasksFailed > 0 ? "failed" : subtasksRunning > 0 ? "running" : subtasksDone === subtaskCount ? "done" : void 0,
|
|
3331
|
+
title: t("card.subtasksBreakdown", {
|
|
3332
|
+
total: String(subtaskCount),
|
|
3333
|
+
done: String(subtasksDone),
|
|
3334
|
+
running: String(subtasksRunning),
|
|
3335
|
+
failed: String(subtasksFailed)
|
|
3336
|
+
}),
|
|
3337
|
+
children: [
|
|
3338
|
+
t("card.subtasks", { count: String(subtaskCount) }),
|
|
3339
|
+
subtasksFailed > 0 ? " · " + t("card.subtasksFailed", { count: String(subtasksFailed) }) : "",
|
|
3340
|
+
subtasksFailed === 0 && subtasksRunning > 0 ? " · " + t("card.subtasksRunning", { count: String(subtasksRunning) }) : ""
|
|
3341
|
+
]
|
|
3342
|
+
}),
|
|
2855
3343
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2856
3344
|
className: board_module_css_default.cardTime,
|
|
2857
3345
|
children: [
|
|
@@ -2896,7 +3384,7 @@ window.__ModuleLoader__.load({
|
|
|
2896
3384
|
}),
|
|
2897
3385
|
!archived && latest !== void 0 && executionLabel(latest) === "running" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2898
3386
|
className: board_module_css_default.cardRunningLabel,
|
|
2899
|
-
children: [t("detail.result.running"), "…"]
|
|
3387
|
+
children: [latest.ownResult === void 0 ? t("detail.result.running") : t("detail.subtasks.waiting"), "…"]
|
|
2900
3388
|
})
|
|
2901
3389
|
]
|
|
2902
3390
|
});
|
|
@@ -3044,6 +3532,95 @@ window.__ModuleLoader__.load({
|
|
|
3044
3532
|
});
|
|
3045
3533
|
}
|
|
3046
3534
|
//#endregion
|
|
3535
|
+
//#region src/client/board/LinkSubtaskModal.tsx
|
|
3536
|
+
/**
|
|
3537
|
+
* Link-subtask modal: attach an existing on-board root task under the open
|
|
3538
|
+
* task. The candidate list mirrors the Host lineage gate (checkParentLink), so
|
|
3539
|
+
* a link the Host would refuse is not offered in the first place; the Host
|
|
3540
|
+
* still re-checks the action at submit time.
|
|
3541
|
+
*/
|
|
3542
|
+
/** Link-subtask overlay. */
|
|
3543
|
+
function LinkSubtaskModal({ controller, parent, onClose }) {
|
|
3544
|
+
const [pending, setPending] = (0, react.useState)(false);
|
|
3545
|
+
const [error, setError] = (0, react.useState)(void 0);
|
|
3546
|
+
const snapshot = controller.getSnapshot();
|
|
3547
|
+
const limit = snapshot.host?.maxSubtaskDepth ?? 1;
|
|
3548
|
+
const candidates = snapshot.tasks.filter((task) => task.archivedAt === void 0 && task.status !== "running" && task.parentId === void 0 && task.id !== parent.id && checkParentLink(snapshot.tasks, task.id, parent.id, limit).ok);
|
|
3549
|
+
const link = async (taskId) => {
|
|
3550
|
+
setPending(true);
|
|
3551
|
+
setError(void 0);
|
|
3552
|
+
if (await controller.setParent(taskId, parent.id)) {
|
|
3553
|
+
onClose();
|
|
3554
|
+
return;
|
|
3555
|
+
}
|
|
3556
|
+
setPending(false);
|
|
3557
|
+
setError(controller.getSnapshot().transportError ?? t("new.required"));
|
|
3558
|
+
};
|
|
3559
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3560
|
+
className: board_module_css_default.modalBackdrop,
|
|
3561
|
+
onMouseDown: (event) => {
|
|
3562
|
+
if (event.target === event.currentTarget) onClose();
|
|
3563
|
+
},
|
|
3564
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3565
|
+
className: board_module_css_default.modal,
|
|
3566
|
+
role: "dialog",
|
|
3567
|
+
"aria-label": t("link.subtask.title"),
|
|
3568
|
+
children: [
|
|
3569
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
3570
|
+
className: board_module_css_default.modalTitle,
|
|
3571
|
+
children: t("link.subtask.title")
|
|
3572
|
+
}),
|
|
3573
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3574
|
+
className: board_module_css_default.fieldHint,
|
|
3575
|
+
children: t("link.subtask.hint")
|
|
3576
|
+
}),
|
|
3577
|
+
candidates.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3578
|
+
className: board_module_css_default.detailText,
|
|
3579
|
+
children: t("link.subtask.empty")
|
|
3580
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
3581
|
+
className: board_module_css_default.pickList,
|
|
3582
|
+
children: candidates.map((task) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
3583
|
+
className: board_module_css_default.pickRow,
|
|
3584
|
+
children: [
|
|
3585
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3586
|
+
className: board_module_css_default.pickTitle,
|
|
3587
|
+
children: task.title
|
|
3588
|
+
}),
|
|
3589
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3590
|
+
className: board_module_css_default.statusBadge,
|
|
3591
|
+
"data-status": task.status,
|
|
3592
|
+
children: t(STATUS_KEY[task.status])
|
|
3593
|
+
}),
|
|
3594
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3595
|
+
type: "button",
|
|
3596
|
+
className: board_module_css_default.primaryButton,
|
|
3597
|
+
disabled: pending,
|
|
3598
|
+
onClick: () => {
|
|
3599
|
+
link(task.id);
|
|
3600
|
+
},
|
|
3601
|
+
children: t("link.subtask.confirm")
|
|
3602
|
+
})
|
|
3603
|
+
]
|
|
3604
|
+
}, task.id))
|
|
3605
|
+
}),
|
|
3606
|
+
error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3607
|
+
className: board_module_css_default.formError,
|
|
3608
|
+
children: error
|
|
3609
|
+
}),
|
|
3610
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("footer", {
|
|
3611
|
+
className: board_module_css_default.modalFooter,
|
|
3612
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3613
|
+
type: "button",
|
|
3614
|
+
className: board_module_css_default.ghostButton,
|
|
3615
|
+
onClick: onClose,
|
|
3616
|
+
children: t("new.cancel")
|
|
3617
|
+
})
|
|
3618
|
+
})
|
|
3619
|
+
]
|
|
3620
|
+
})
|
|
3621
|
+
});
|
|
3622
|
+
}
|
|
3623
|
+
//#endregion
|
|
3047
3624
|
//#region src/client/board/TaskDetail.tsx
|
|
3048
3625
|
/**
|
|
3049
3626
|
* Task detail: the full view of one task — content, prompt, execution
|
|
@@ -3102,7 +3679,13 @@ window.__ModuleLoader__.load({
|
|
|
3102
3679
|
/** The execution-target editor: workspace / mode / permission pickers. */
|
|
3103
3680
|
function ExecutionSettingsSection({ controller, task, pending }) {
|
|
3104
3681
|
const [options, setOptions] = (0, react.useState)(controller.getSnapshot().executionOptions);
|
|
3105
|
-
|
|
3682
|
+
const teamRunOffered = () => controller.getSnapshot().host?.teamRunAvailable === true;
|
|
3683
|
+
const [teamRunAvailable, setTeamRunAvailable] = (0, react.useState)(teamRunOffered());
|
|
3684
|
+
(0, react.useEffect)(() => controller.subscribe(() => {
|
|
3685
|
+
const snapshot = controller.getSnapshot();
|
|
3686
|
+
setOptions(snapshot.executionOptions);
|
|
3687
|
+
setTeamRunAvailable(snapshot.host?.teamRunAvailable === true);
|
|
3688
|
+
}), [controller]);
|
|
3106
3689
|
const workspaceId = task.workspaceId ?? "";
|
|
3107
3690
|
const mode = task.mode ?? "";
|
|
3108
3691
|
const permission = task.permission ?? "";
|
|
@@ -3242,6 +3825,21 @@ window.__ModuleLoader__.load({
|
|
|
3242
3825
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3243
3826
|
className: board_module_css_default.detailText,
|
|
3244
3827
|
children: t("exec.reuseSessionHint")
|
|
3828
|
+
}),
|
|
3829
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3830
|
+
className: board_module_css_default.scheduleToggle,
|
|
3831
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3832
|
+
type: "checkbox",
|
|
3833
|
+
checked: task.teamRun === true,
|
|
3834
|
+
disabled: pending || !teamRunAvailable,
|
|
3835
|
+
onChange: (event) => {
|
|
3836
|
+
controller.updateTask(task.id, { teamRun: event.target.checked });
|
|
3837
|
+
}
|
|
3838
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("exec.teamRun") })]
|
|
3839
|
+
}),
|
|
3840
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3841
|
+
className: board_module_css_default.detailText,
|
|
3842
|
+
children: teamRunAvailable ? t("exec.teamRunHint") : t("exec.teamRunUnavailable")
|
|
3245
3843
|
})
|
|
3246
3844
|
]
|
|
3247
3845
|
});
|
|
@@ -3370,6 +3968,125 @@ window.__ModuleLoader__.load({
|
|
|
3370
3968
|
]
|
|
3371
3969
|
});
|
|
3372
3970
|
}
|
|
3971
|
+
/**
|
|
3972
|
+
* The subtask block: the parent link, the direct subtasks with their status,
|
|
3973
|
+
* and the actions that grow or prune the tree. Every gate here mirrors the
|
|
3974
|
+
* Host lineage gate for affordance only; the Host re-checks the action.
|
|
3975
|
+
*/
|
|
3976
|
+
function SubtaskSection({ controller, task, pending, archived }) {
|
|
3977
|
+
const [snapshot, setSnapshot] = (0, react.useState)(controller.getSnapshot());
|
|
3978
|
+
(0, react.useEffect)(() => controller.subscribe(() => setSnapshot(controller.getSnapshot())), [controller]);
|
|
3979
|
+
const [showAdd, setShowAdd] = (0, react.useState)(false);
|
|
3980
|
+
const [showLink, setShowLink] = (0, react.useState)(false);
|
|
3981
|
+
const tasks = snapshot.tasks;
|
|
3982
|
+
const parent = task.parentId === void 0 ? void 0 : tasks.find((candidate) => candidate.id === task.parentId);
|
|
3983
|
+
const children = directSubtasks(tasks, task.id);
|
|
3984
|
+
const limit = snapshot.host?.maxSubtaskDepth ?? 1;
|
|
3985
|
+
const canAddChild = taskDepth(tasks, task.id) + 1 <= limit;
|
|
3986
|
+
const editable = !archived && !pending;
|
|
3987
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
3988
|
+
className: board_module_css_default.detailSection,
|
|
3989
|
+
"data-dsh-part": "subtasks",
|
|
3990
|
+
children: [
|
|
3991
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t("detail.subtasks") }),
|
|
3992
|
+
parent !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
3993
|
+
className: board_module_css_default.detailText,
|
|
3994
|
+
children: [
|
|
3995
|
+
t("detail.parent"),
|
|
3996
|
+
":",
|
|
3997
|
+
" ",
|
|
3998
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3999
|
+
type: "button",
|
|
4000
|
+
className: board_module_css_default.linkButton,
|
|
4001
|
+
title: t("detail.parent.open"),
|
|
4002
|
+
onClick: () => {
|
|
4003
|
+
controller.openTask(parent.id);
|
|
4004
|
+
},
|
|
4005
|
+
children: parent.title
|
|
4006
|
+
})
|
|
4007
|
+
]
|
|
4008
|
+
}),
|
|
4009
|
+
children.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4010
|
+
className: board_module_css_default.detailText,
|
|
4011
|
+
children: t("detail.subtasks.empty")
|
|
4012
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
4013
|
+
className: board_module_css_default.subtaskList,
|
|
4014
|
+
children: children.map((child) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
4015
|
+
className: board_module_css_default.subtaskRow,
|
|
4016
|
+
children: [
|
|
4017
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4018
|
+
type: "button",
|
|
4019
|
+
className: board_module_css_default.linkButton,
|
|
4020
|
+
onClick: () => {
|
|
4021
|
+
controller.openTask(child.id);
|
|
4022
|
+
},
|
|
4023
|
+
children: child.title
|
|
4024
|
+
}),
|
|
4025
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4026
|
+
className: board_module_css_default.statusBadge,
|
|
4027
|
+
"data-status": child.status,
|
|
4028
|
+
children: t(STATUS_KEY[child.status])
|
|
4029
|
+
}),
|
|
4030
|
+
!archived && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4031
|
+
type: "button",
|
|
4032
|
+
className: board_module_css_default.linkButton,
|
|
4033
|
+
disabled: pending || child.status === "running",
|
|
4034
|
+
title: child.status === "running" ? t("detail.subtasks.runningLock") : void 0,
|
|
4035
|
+
onClick: () => {
|
|
4036
|
+
controller.setParent(child.id, null);
|
|
4037
|
+
},
|
|
4038
|
+
children: t("detail.subtasks.detach")
|
|
4039
|
+
})
|
|
4040
|
+
]
|
|
4041
|
+
}, child.id))
|
|
4042
|
+
}),
|
|
4043
|
+
children.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4044
|
+
className: board_module_css_default.detailText,
|
|
4045
|
+
children: t("detail.subtasks.runHint", { count: String(children.length) })
|
|
4046
|
+
}),
|
|
4047
|
+
!archived && (canAddChild ? controller.isHostBacked() ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4048
|
+
className: board_module_css_default.subtaskAddRow,
|
|
4049
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4050
|
+
type: "button",
|
|
4051
|
+
className: board_module_css_default.ghostButton,
|
|
4052
|
+
disabled: !editable,
|
|
4053
|
+
onClick: () => {
|
|
4054
|
+
setShowAdd(true);
|
|
4055
|
+
},
|
|
4056
|
+
children: ["+ ", t("detail.subtasks.add")]
|
|
4057
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4058
|
+
type: "button",
|
|
4059
|
+
className: board_module_css_default.ghostButton,
|
|
4060
|
+
disabled: !editable,
|
|
4061
|
+
onClick: () => {
|
|
4062
|
+
setShowLink(true);
|
|
4063
|
+
},
|
|
4064
|
+
children: t("detail.subtasks.link")
|
|
4065
|
+
})]
|
|
4066
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4067
|
+
className: board_module_css_default.detailMeta,
|
|
4068
|
+
children: t("detail.subtasks.hostOnly")
|
|
4069
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4070
|
+
className: board_module_css_default.detailMeta,
|
|
4071
|
+
children: t("detail.subtasks.depthLimit", { depth: String(limit) })
|
|
4072
|
+
})),
|
|
4073
|
+
showAdd && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NewTaskModal, {
|
|
4074
|
+
controller,
|
|
4075
|
+
parentTask: task,
|
|
4076
|
+
onClose: () => {
|
|
4077
|
+
setShowAdd(false);
|
|
4078
|
+
}
|
|
4079
|
+
}),
|
|
4080
|
+
showLink && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LinkSubtaskModal, {
|
|
4081
|
+
controller,
|
|
4082
|
+
parent: task,
|
|
4083
|
+
onClose: () => {
|
|
4084
|
+
setShowLink(false);
|
|
4085
|
+
}
|
|
4086
|
+
})
|
|
4087
|
+
]
|
|
4088
|
+
});
|
|
4089
|
+
}
|
|
3373
4090
|
/** Task detail overlay. */
|
|
3374
4091
|
function TaskDetail({ controller, task }) {
|
|
3375
4092
|
const [confirmDelete, setConfirmDelete] = (0, react.useState)(false);
|
|
@@ -3393,6 +4110,7 @@ window.__ModuleLoader__.load({
|
|
|
3393
4110
|
const transportError = snapshot.transportError;
|
|
3394
4111
|
const timeZone = snapshot.host?.scheduler.timeZone;
|
|
3395
4112
|
const permissionPending = requiresPermissionConfirmation(current, snapshot.host?.sessionDefaultPermission);
|
|
4113
|
+
const subtaskChildren = directSubtasks(snapshot.tasks, current.id);
|
|
3396
4114
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3397
4115
|
className: board_module_css_default.modalBackdrop,
|
|
3398
4116
|
onMouseDown: (event) => {
|
|
@@ -3452,6 +4170,12 @@ window.__ModuleLoader__.load({
|
|
|
3452
4170
|
children: current.description !== "" ? current.description : "—"
|
|
3453
4171
|
})]
|
|
3454
4172
|
}),
|
|
4173
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SubtaskSection, {
|
|
4174
|
+
controller,
|
|
4175
|
+
task: current,
|
|
4176
|
+
pending,
|
|
4177
|
+
archived
|
|
4178
|
+
}),
|
|
3455
4179
|
current.tags !== void 0 && current.tags.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
3456
4180
|
className: board_module_css_default.detailSection,
|
|
3457
4181
|
"data-dsh-part": "tags",
|
|
@@ -3656,6 +4380,7 @@ window.__ModuleLoader__.load({
|
|
|
3656
4380
|
type: "button",
|
|
3657
4381
|
className: board_module_css_default.primaryButton,
|
|
3658
4382
|
disabled: running || pending,
|
|
4383
|
+
title: subtaskChildren.length > 0 ? t("detail.subtasks.runHint", { count: String(subtaskChildren.length) }) : void 0,
|
|
3659
4384
|
onClick: () => {
|
|
3660
4385
|
controller.rerunTask(current.id).then(() => {
|
|
3661
4386
|
if (controller.getSnapshot().transportError === void 0) controller.closeTask();
|
|
@@ -3772,14 +4497,19 @@ window.__ModuleLoader__.load({
|
|
|
3772
4497
|
* re-renders only when its own task changes — not when a sibling card status,
|
|
3773
4498
|
* the filter, or the selection moves.
|
|
3774
4499
|
*/
|
|
3775
|
-
const MemoTaskCard = (0, react.memo)(function MemoTaskCard({ task, pending, timeZone, onOpen }) {
|
|
4500
|
+
const MemoTaskCard = (0, react.memo)(function MemoTaskCard({ task, pending, timeZone, onOpen, subtaskCount, isSubtask, subtasksDone, subtasksRunning, subtasksFailed }) {
|
|
3776
4501
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TaskCard, {
|
|
3777
4502
|
task,
|
|
3778
4503
|
pending,
|
|
3779
4504
|
timeZone,
|
|
3780
4505
|
onClick: (0, react.useCallback)(() => {
|
|
3781
4506
|
onOpen(task.id);
|
|
3782
|
-
}, [task.id, onOpen])
|
|
4507
|
+
}, [task.id, onOpen]),
|
|
4508
|
+
subtaskCount,
|
|
4509
|
+
isSubtask,
|
|
4510
|
+
subtasksDone,
|
|
4511
|
+
subtasksRunning,
|
|
4512
|
+
subtasksFailed
|
|
3783
4513
|
});
|
|
3784
4514
|
});
|
|
3785
4515
|
/** Board component; subscribes to the controller snapshot. */
|
|
@@ -3788,6 +4518,7 @@ window.__ModuleLoader__.load({
|
|
|
3788
4518
|
(0, react.useEffect)(() => controller.subscribe(() => setSnapshot(controller.getSnapshot())), [controller]);
|
|
3789
4519
|
const [filter, setFilter] = (0, react.useState)("");
|
|
3790
4520
|
const [tagFilter, setTagFilter] = (0, react.useState)([]);
|
|
4521
|
+
const [hideSubtasks, setHideSubtasks] = (0, react.useState)(true);
|
|
3791
4522
|
const [showNew, setShowNew] = (0, react.useState)(false);
|
|
3792
4523
|
const [projectId, setProjectId] = (0, react.useState)("");
|
|
3793
4524
|
const [showNewProject, setShowNewProject] = (0, react.useState)(false);
|
|
@@ -3797,7 +4528,26 @@ window.__ModuleLoader__.load({
|
|
|
3797
4528
|
const selected = selectedTaskOf(snapshot);
|
|
3798
4529
|
const archiveView = snapshot.archiveView;
|
|
3799
4530
|
const knownTags = collectKnownTags(snapshot.tasks);
|
|
3800
|
-
const
|
|
4531
|
+
const onBoard = snapshot.tasks.filter((task) => archiveView ? task.archivedAt !== void 0 : task.archivedAt === void 0);
|
|
4532
|
+
const subtaskCounts = /* @__PURE__ */ new Map();
|
|
4533
|
+
const subtaskRollup = /* @__PURE__ */ new Map();
|
|
4534
|
+
for (const task of onBoard) {
|
|
4535
|
+
if (task.parentId === void 0) continue;
|
|
4536
|
+
subtaskCounts.set(task.parentId, (subtaskCounts.get(task.parentId) ?? 0) + 1);
|
|
4537
|
+
const rollup = subtaskRollup.get(task.parentId) ?? {
|
|
4538
|
+
done: 0,
|
|
4539
|
+
running: 0,
|
|
4540
|
+
failed: 0
|
|
4541
|
+
};
|
|
4542
|
+
if (task.status === "done") rollup.done += 1;
|
|
4543
|
+
else if (task.status === "running") rollup.running += 1;
|
|
4544
|
+
else if (task.status === "failed") rollup.failed += 1;
|
|
4545
|
+
subtaskRollup.set(task.parentId, rollup);
|
|
4546
|
+
}
|
|
4547
|
+
const hasSubtasks = subtaskCounts.size > 0;
|
|
4548
|
+
const searchActive = filter.trim() !== "" || tagFilter.length > 0;
|
|
4549
|
+
const hidingSubtasks = hideSubtasks && !searchActive;
|
|
4550
|
+
const visible = onBoard.filter((task) => (projectId === "" || task.workspaceId === projectId) && (!hidingSubtasks || task.parentId === void 0) && matchesFilter(task, filter) && matchesTagFilter(task, tagFilter));
|
|
3801
4551
|
const projects = snapshot.executionOptions.workspaces;
|
|
3802
4552
|
const canCreateProject = snapshot.canCreateWorkspace === true;
|
|
3803
4553
|
const submitNewProject = async () => {
|
|
@@ -3899,6 +4649,17 @@ window.__ModuleLoader__.load({
|
|
|
3899
4649
|
},
|
|
3900
4650
|
"aria-label": t("board.search")
|
|
3901
4651
|
}),
|
|
4652
|
+
hasSubtasks && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4653
|
+
type: "button",
|
|
4654
|
+
className: hidingSubtasks ? board_module_css_default.primaryButton : board_module_css_default.ghostButton,
|
|
4655
|
+
"data-dsh-part": "subtask-filter",
|
|
4656
|
+
"aria-pressed": hidingSubtasks,
|
|
4657
|
+
title: t("board.subtaskFilterHint"),
|
|
4658
|
+
onClick: () => {
|
|
4659
|
+
setHideSubtasks((value) => !value);
|
|
4660
|
+
},
|
|
4661
|
+
children: hidingSubtasks ? t("board.showSubtasks") : t("board.hideSubtasks")
|
|
4662
|
+
}),
|
|
3902
4663
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3903
4664
|
type: "button",
|
|
3904
4665
|
className: archiveView ? board_module_css_default.primaryButton : board_module_css_default.ghostButton,
|
|
@@ -4033,7 +4794,12 @@ window.__ModuleLoader__.load({
|
|
|
4033
4794
|
task,
|
|
4034
4795
|
pending: snapshot.pendingTaskIds.includes(task.id),
|
|
4035
4796
|
timeZone: snapshot.host?.scheduler.timeZone,
|
|
4036
|
-
onOpen: openTask
|
|
4797
|
+
onOpen: openTask,
|
|
4798
|
+
subtaskCount: subtaskCounts.get(task.id) ?? 0,
|
|
4799
|
+
isSubtask: task.parentId !== void 0,
|
|
4800
|
+
subtasksDone: subtaskRollup.get(task.id)?.done ?? 0,
|
|
4801
|
+
subtasksRunning: subtaskRollup.get(task.id)?.running ?? 0,
|
|
4802
|
+
subtasksFailed: subtaskRollup.get(task.id)?.failed ?? 0
|
|
4037
4803
|
}, task.id)), visible.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4038
4804
|
className: board_module_css_default.columnEmpty,
|
|
4039
4805
|
children: tagFilter.length > 0 ? t("board.tagEmpty") : t("archive.empty")
|
|
@@ -4080,7 +4846,12 @@ window.__ModuleLoader__.load({
|
|
|
4080
4846
|
task,
|
|
4081
4847
|
pending: snapshot.pendingTaskIds.includes(task.id),
|
|
4082
4848
|
timeZone: snapshot.host?.scheduler.timeZone,
|
|
4083
|
-
onOpen: openTask
|
|
4849
|
+
onOpen: openTask,
|
|
4850
|
+
subtaskCount: subtaskCounts.get(task.id) ?? 0,
|
|
4851
|
+
isSubtask: task.parentId !== void 0,
|
|
4852
|
+
subtasksDone: subtaskRollup.get(task.id)?.done ?? 0,
|
|
4853
|
+
subtasksRunning: subtaskRollup.get(task.id)?.running ?? 0,
|
|
4854
|
+
subtasksFailed: subtaskRollup.get(task.id)?.failed ?? 0
|
|
4084
4855
|
}, task.id)), tasks.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4085
4856
|
className: board_module_css_default.columnEmpty,
|
|
4086
4857
|
children: tagFilter.length > 0 ? t("board.tagEmpty") : t("board.empty")
|
|
@@ -4104,390 +4875,89 @@ window.__ModuleLoader__.load({
|
|
|
4104
4875
|
});
|
|
4105
4876
|
}
|
|
4106
4877
|
//#endregion
|
|
4107
|
-
//#region src/client/
|
|
4108
|
-
/**
|
|
4109
|
-
const
|
|
4110
|
-
const INVALIDATION_ONLY = Symbol.for("dsh-web.body-mutation-invalidation");
|
|
4111
|
-
function needsRecords(subscribers) {
|
|
4112
|
-
for (const listener of subscribers) if (!listener[INVALIDATION_ONLY]) return true;
|
|
4113
|
-
return false;
|
|
4114
|
-
}
|
|
4115
|
-
/**
|
|
4116
|
-
* Subscribe to a coalesced DOM re-check without retaining mutation records.
|
|
4117
|
-
* The marked wrapper also works with an older hub, which delivers records
|
|
4118
|
-
* that it simply ignores until a page reload picks up the updated hub.
|
|
4119
|
-
*/
|
|
4120
|
-
function subscribeBodyInvalidations(subscriber) {
|
|
4121
|
-
const listener = () => {
|
|
4122
|
-
subscriber();
|
|
4123
|
-
};
|
|
4124
|
-
listener[INVALIDATION_ONLY] = true;
|
|
4125
|
-
return subscribeBodyMutations(listener);
|
|
4126
|
-
}
|
|
4127
|
-
/**
|
|
4128
|
-
* Subscribe to body-level childList mutations.
|
|
4129
|
-
* @param subscriber - called at most once per animation frame with the records
|
|
4130
|
-
* collected since the previous flush; must be safe to run repeatedly.
|
|
4131
|
-
* @returns the disposer removing this subscriber (and the observer when it was
|
|
4132
|
-
* the last one).
|
|
4133
|
-
*/
|
|
4134
|
-
function subscribeBodyMutations(subscriber) {
|
|
4135
|
-
if (typeof globalThis === "undefined" || typeof document === "undefined") return () => {};
|
|
4136
|
-
if (typeof MutationObserver !== "function") return () => {};
|
|
4137
|
-
const registry = globalThis;
|
|
4138
|
-
let hub = registry[HUB_KEY];
|
|
4139
|
-
if (hub === void 0) {
|
|
4140
|
-
const subscribers = /* @__PURE__ */ new Set();
|
|
4141
|
-
const created = {
|
|
4142
|
-
observer: void 0,
|
|
4143
|
-
subscribers,
|
|
4144
|
-
pending: [],
|
|
4145
|
-
scheduled: false
|
|
4146
|
-
};
|
|
4147
|
-
const flush = () => {
|
|
4148
|
-
created.frame = void 0;
|
|
4149
|
-
created.scheduled = false;
|
|
4150
|
-
const batch = created.pending;
|
|
4151
|
-
created.pending = [];
|
|
4152
|
-
for (const listener of [...subscribers]) {
|
|
4153
|
-
if (!subscribers.has(listener)) continue;
|
|
4154
|
-
try {
|
|
4155
|
-
listener(batch);
|
|
4156
|
-
} catch {}
|
|
4157
|
-
}
|
|
4158
|
-
};
|
|
4159
|
-
const schedule = () => {
|
|
4160
|
-
if (created.scheduled) return;
|
|
4161
|
-
created.scheduled = true;
|
|
4162
|
-
if (typeof requestAnimationFrame === "function") created.frame = requestAnimationFrame(flush);
|
|
4163
|
-
else flush();
|
|
4164
|
-
};
|
|
4165
|
-
created.observer = new MutationObserver((records) => {
|
|
4166
|
-
if (needsRecords(subscribers)) for (const record of records) created.pending.push(record);
|
|
4167
|
-
schedule();
|
|
4168
|
-
});
|
|
4169
|
-
created.observer.observe(document.body ?? document.documentElement, {
|
|
4170
|
-
childList: true,
|
|
4171
|
-
subtree: true
|
|
4172
|
-
});
|
|
4173
|
-
registry[HUB_KEY] = created;
|
|
4174
|
-
hub = created;
|
|
4175
|
-
}
|
|
4176
|
-
const active = hub;
|
|
4177
|
-
active.subscribers.add(subscriber);
|
|
4178
|
-
let subscribed = true;
|
|
4179
|
-
return () => {
|
|
4180
|
-
if (!subscribed) return;
|
|
4181
|
-
subscribed = false;
|
|
4182
|
-
active.subscribers.delete(subscriber);
|
|
4183
|
-
if (!needsRecords(active.subscribers)) active.pending = [];
|
|
4184
|
-
if (active.subscribers.size === 0 && registry[HUB_KEY] === active) {
|
|
4185
|
-
active.observer.disconnect();
|
|
4186
|
-
if (active.frame !== void 0 && typeof cancelAnimationFrame === "function") cancelAnimationFrame(active.frame);
|
|
4187
|
-
active.frame = void 0;
|
|
4188
|
-
active.pending = [];
|
|
4189
|
-
active.scheduled = false;
|
|
4190
|
-
delete registry[HUB_KEY];
|
|
4191
|
-
}
|
|
4192
|
-
};
|
|
4193
|
-
}
|
|
4194
|
-
//#endregion
|
|
4195
|
-
//#region src/client/panel-mount-core.ts
|
|
4878
|
+
//#region src/client/native-panel.tsx
|
|
4879
|
+
/** Row order among the shell's global panel rows (Plugins is 0, Schedule 10). */
|
|
4880
|
+
const PANEL_ORDER = 20;
|
|
4196
4881
|
/**
|
|
4197
|
-
*
|
|
4882
|
+
* The sidebar row glyph the shell asks for at its own size and active state.
|
|
4883
|
+
* The shell owns the button, label, tooltip and rail geometry; this component
|
|
4884
|
+
* draws only the glyph, like every other panel row.
|
|
4198
4885
|
*
|
|
4199
|
-
* The `
|
|
4200
|
-
*
|
|
4201
|
-
*
|
|
4202
|
-
*
|
|
4203
|
-
*
|
|
4204
|
-
*
|
|
4205
|
-
* manages, and a stylesheet rule hides the conversation content while the
|
|
4206
|
-
* panel is active. Toggling is a data attribute on <html> — no React
|
|
4207
|
-
* involvement, so the conversation subtree underneath stays mounted and
|
|
4208
|
-
* stateful.
|
|
4209
|
-
*
|
|
4210
|
-
* Consuming plugins keep a thin wrapper that supplies the panel tree,
|
|
4211
|
-
* container attribute names, and stylesheet class; those names are pinned by
|
|
4212
|
-
* each package's CSS, skins, and the semantic-attributes contract. The
|
|
4213
|
-
* sidebar row toggling the panel shares its core the same way
|
|
4214
|
-
* (shared/client/sidebar-entry-core.ts, synced copy).
|
|
4215
|
-
*/
|
|
4216
|
-
const CONVERSATION_COLUMN_SELECTOR = "[data-pane=\"conversation\"], [class*=\"centerCol\"]";
|
|
4217
|
-
/** Cross-plugin activation event; detail is the activating panel name. */
|
|
4218
|
-
const ACTIVATE_EVENT = "dsh-panel-activate";
|
|
4219
|
-
const SIDEBAR_ROW_SELECTOR = "[class*=\"sessionRow\"], [class*=\"projectRow\"], [class*=\"searchResultRow\"], [class*=\"searchResultWorkspace\"], [class*=\"newSession\"]";
|
|
4220
|
-
/** Find the center column, or undefined while the frame is not mounted. */
|
|
4221
|
-
function conversationColumn() {
|
|
4222
|
-
return document.querySelector(CONVERSATION_COLUMN_SELECTOR) ?? void 0;
|
|
4223
|
-
}
|
|
4224
|
-
/**
|
|
4225
|
-
* Mount a family panel into the center column and bind its visibility to the
|
|
4226
|
-
* owning controller's open state.
|
|
4227
|
-
* @returns disposer unmounting the tree and restoring the column.
|
|
4886
|
+
* The glyph carries `data-dsh-panel-entry` because it is the only DOM the
|
|
4887
|
+
* panel's own code owns inside that shell-owned row: the L2 contract (skins)
|
|
4888
|
+
* resolves which row belongs to which plugin through it, since the shell
|
|
4889
|
+
* stamps no per-entry hook of its own (see contracts/semantic-attrs-v1.md).
|
|
4890
|
+
* @param props - the shell's icon share: square edge and selection state.
|
|
4891
|
+
* @returns the decorative board glyph.
|
|
4228
4892
|
*/
|
|
4229
|
-
function
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
container = document.createElement("div");
|
|
4249
|
-
container.dataset[options.viewDatasetKey] = "";
|
|
4250
|
-
container.dataset.dshPlugin = options.pluginName;
|
|
4251
|
-
container.className = options.viewClassName;
|
|
4252
|
-
column.appendChild(container);
|
|
4253
|
-
}
|
|
4254
|
-
if (root !== void 0 || !options.isOpen()) return;
|
|
4255
|
-
root = (0, react_dom_client.createRoot)(container);
|
|
4256
|
-
options.render(root);
|
|
4257
|
-
};
|
|
4258
|
-
const unsubscribeBody = subscribeBodyInvalidations(() => {
|
|
4259
|
-
ensure();
|
|
4893
|
+
function TaskBoardPanelIcon({ size }) {
|
|
4894
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
4895
|
+
"data-dsh-panel-entry": TASK_BOARD_PANEL_ID,
|
|
4896
|
+
viewBox: "0 0 16 16",
|
|
4897
|
+
width: size,
|
|
4898
|
+
height: size,
|
|
4899
|
+
fill: "none",
|
|
4900
|
+
stroke: "currentColor",
|
|
4901
|
+
strokeWidth: "1.3",
|
|
4902
|
+
strokeLinecap: "round",
|
|
4903
|
+
strokeLinejoin: "round",
|
|
4904
|
+
"aria-hidden": "true",
|
|
4905
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
4906
|
+
x: "2",
|
|
4907
|
+
y: "2.5",
|
|
4908
|
+
width: "12",
|
|
4909
|
+
height: "11",
|
|
4910
|
+
rx: "1.5"
|
|
4911
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M2 6.5h12M6.5 6.5v7" })]
|
|
4260
4912
|
});
|
|
4261
|
-
const applyActive = () => {
|
|
4262
|
-
if (options.isOpen()) {
|
|
4263
|
-
ensure();
|
|
4264
|
-
document.documentElement.removeAttribute(options.siblingActiveAttribute);
|
|
4265
|
-
document.documentElement.setAttribute(options.activeAttribute, "");
|
|
4266
|
-
document.dispatchEvent(new CustomEvent(ACTIVATE_EVENT, { detail: options.panelName }));
|
|
4267
|
-
} else document.documentElement.removeAttribute(options.activeAttribute);
|
|
4268
|
-
};
|
|
4269
|
-
const onOtherActivate = (event) => {
|
|
4270
|
-
if (event.detail === options.siblingPanelName && options.isOpen()) options.close();
|
|
4271
|
-
};
|
|
4272
|
-
const onClickSidebarRow = (event) => {
|
|
4273
|
-
if (!options.isOpen()) return;
|
|
4274
|
-
const target = event.target;
|
|
4275
|
-
if (target === null) return;
|
|
4276
|
-
if (target.closest(SIDEBAR_ROW_SELECTOR) !== null) options.close();
|
|
4277
|
-
};
|
|
4278
|
-
document.addEventListener("click", onClickSidebarRow, true);
|
|
4279
|
-
document.addEventListener(ACTIVATE_EVENT, onOtherActivate);
|
|
4280
|
-
const unsubscribe = options.subscribe(applyActive);
|
|
4281
|
-
applyActive();
|
|
4282
|
-
ensure();
|
|
4283
|
-
return () => {
|
|
4284
|
-
document.removeEventListener("click", onClickSidebarRow, true);
|
|
4285
|
-
document.removeEventListener(ACTIVATE_EVENT, onOtherActivate);
|
|
4286
|
-
unsubscribeBody();
|
|
4287
|
-
unsubscribe();
|
|
4288
|
-
unsubscribeLocale?.();
|
|
4289
|
-
document.documentElement.removeAttribute(options.activeAttribute);
|
|
4290
|
-
root?.unmount();
|
|
4291
|
-
root = void 0;
|
|
4292
|
-
container?.remove();
|
|
4293
|
-
container = void 0;
|
|
4294
|
-
};
|
|
4295
4913
|
}
|
|
4296
|
-
//#endregion
|
|
4297
|
-
//#region src/client/board-mount.tsx
|
|
4298
4914
|
/**
|
|
4299
|
-
*
|
|
4300
|
-
*
|
|
4301
|
-
*
|
|
4302
|
-
*
|
|
4303
|
-
*
|
|
4304
|
-
* @
|
|
4915
|
+
* The main-slot page. The layout mounts it only while the board is the selected
|
|
4916
|
+
* panel, so the conversation keeps the center column untouched the rest of the
|
|
4917
|
+
* time; the wrapper carries the pinned `data-dsh-taskboard-view` semantic
|
|
4918
|
+
* anchor (L2 contract, skins) and the container query context the board's
|
|
4919
|
+
* responsive rules read.
|
|
4920
|
+
* @param props - the framework main-slot share plus this entry's injected face.
|
|
4921
|
+
* @returns the board page.
|
|
4305
4922
|
*/
|
|
4306
|
-
function
|
|
4307
|
-
return
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
activeAttribute: "data-dsh-taskboard-active",
|
|
4313
|
-
siblingActiveAttribute: "data-dsh-ssh-active",
|
|
4314
|
-
panelName: "taskboard",
|
|
4315
|
-
siblingPanelName: "ssh",
|
|
4316
|
-
isOpen: () => controller.getSnapshot().boardOpen,
|
|
4317
|
-
close: () => controller.closeBoard(),
|
|
4318
|
-
subscribe: (listener) => controller.subscribe(listener),
|
|
4319
|
-
locale
|
|
4923
|
+
function TaskBoardPanel({ controller }) {
|
|
4924
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4925
|
+
className: board_module_css_default.panel,
|
|
4926
|
+
"data-dsh-taskboard-view": "",
|
|
4927
|
+
"data-dsh-plugin": "task-board",
|
|
4928
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TaskBoard, { controller })
|
|
4320
4929
|
});
|
|
4321
4930
|
}
|
|
4322
|
-
//#endregion
|
|
4323
|
-
//#region src/client/sidebar-entry-core.ts
|
|
4324
4931
|
/**
|
|
4325
|
-
*
|
|
4326
|
-
*
|
|
4327
|
-
* dsh's sidebar shell exposes no slot an external plugin can register into,
|
|
4328
|
-
* so the entry row is injected between the shell's New Session button and the
|
|
4329
|
-
* workspace browser. The injection self-heals: a MutationObserver watches the
|
|
4330
|
-
* sidebar root and re-inserts the row whenever a React re-render displaces it
|
|
4331
|
-
* (re-insertion happens in the same frame, before paint, so no flicker).
|
|
4332
|
-
*
|
|
4333
|
-
* The row is plain DOM (no React tree) so it can never disturb the shell's
|
|
4334
|
-
* reconciliation; the view it toggles is a separate root owned by the caller.
|
|
4932
|
+
* Register the board's sidebar row and center-column page.
|
|
4335
4933
|
*
|
|
4336
|
-
*
|
|
4337
|
-
*
|
|
4934
|
+
* Both seats are declared by shell plugins this package does not depend on at
|
|
4935
|
+
* runtime, so each registration is wrapped in `ctx.slots.inject`: the callback
|
|
4936
|
+
* runs only after the owning entry declares the seat, and a shell that never
|
|
4937
|
+
* declares it leaves the board simply absent instead of failing boot.
|
|
4938
|
+
* @param ctx - client root context (services: slots).
|
|
4939
|
+
* @param controller - the board controller the page and the row drive.
|
|
4940
|
+
* @returns disposer releasing both registrations.
|
|
4338
4941
|
*/
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
const
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
const entry = document.createElement("button");
|
|
4354
|
-
entry.type = "button";
|
|
4355
|
-
entry.setAttribute(options.rowAttribute, "");
|
|
4356
|
-
if (options.plugin !== void 0) {
|
|
4357
|
-
entry.setAttribute("data-dsh-plugin", options.plugin);
|
|
4358
|
-
entry.setAttribute("data-dsh-part", "sidebar-entry");
|
|
4359
|
-
}
|
|
4360
|
-
entry.className = options.css["entry"] ?? "";
|
|
4361
|
-
const labelSpan = document.createElement("span");
|
|
4362
|
-
labelSpan.className = options.css["entryLabel"] ?? "";
|
|
4363
|
-
const iconSpan = document.createElement("span");
|
|
4364
|
-
iconSpan.className = options.css["entryIcon"] ?? "";
|
|
4365
|
-
iconSpan.innerHTML = options.icon;
|
|
4366
|
-
entry.append(iconSpan, labelSpan);
|
|
4367
|
-
const applyLabel = () => {
|
|
4368
|
-
entry.setAttribute("aria-label", options.label());
|
|
4369
|
-
if (options.tooltip !== void 0) entry.setAttribute("title", options.tooltip());
|
|
4370
|
-
labelSpan.textContent = options.label();
|
|
4371
|
-
};
|
|
4372
|
-
applyLabel();
|
|
4373
|
-
entry.addEventListener("click", options.onToggle);
|
|
4374
|
-
return {
|
|
4375
|
-
entry,
|
|
4376
|
-
applyLabel
|
|
4377
|
-
};
|
|
4378
|
-
}
|
|
4379
|
-
/** Re-insert the entry after the New Session row (before the browser region). */
|
|
4380
|
-
function placeEntry(root, entry, options) {
|
|
4381
|
-
const button = newSessionButton(root);
|
|
4382
|
-
if (button === void 0) return false;
|
|
4383
|
-
if (entry.parentElement !== root) {
|
|
4384
|
-
const row = button.closest("[class*=\"logoRow\"]");
|
|
4385
|
-
const base = row !== null && row.parentElement === root ? row : button;
|
|
4386
|
-
const family = Array.from(root.children).filter((el) => el instanceof HTMLElement && el.matches(options.familySelectors.join(", ")));
|
|
4387
|
-
const anchor = options.position === "before" ? family.length > 0 ? family[0] : base.nextElementSibling : family.length > 0 ? family[family.length - 1].nextElementSibling : base.nextElementSibling;
|
|
4388
|
-
root.insertBefore(entry, anchor);
|
|
4389
|
-
}
|
|
4390
|
-
return true;
|
|
4391
|
-
}
|
|
4392
|
-
/**
|
|
4393
|
-
* Mount the sidebar entry, waiting for the shell to render and self-healing
|
|
4394
|
-
* on later React re-renders.
|
|
4395
|
-
* @param options - the row's attribute/icon/copy/action/ordering configuration.
|
|
4396
|
-
* @returns disposer removing the entry and its observers.
|
|
4397
|
-
*/
|
|
4398
|
-
function mountSidebarEntry$1(options) {
|
|
4399
|
-
if (typeof document !== "undefined" && document.querySelector(options.rowSelector) !== null) return () => {};
|
|
4400
|
-
const { entry, applyLabel } = createEntry(options);
|
|
4401
|
-
let root;
|
|
4402
|
-
let placed = false;
|
|
4403
|
-
let unsubscribeRefresh;
|
|
4404
|
-
if (options.refresh !== void 0) try {
|
|
4405
|
-
unsubscribeRefresh = options.refresh.subscribe(applyLabel);
|
|
4406
|
-
} catch {}
|
|
4407
|
-
const tryPlace = () => {
|
|
4408
|
-
if (root !== void 0 && !root.isConnected) {
|
|
4409
|
-
rootObserver.disconnect();
|
|
4410
|
-
root = void 0;
|
|
4411
|
-
placed = false;
|
|
4412
|
-
}
|
|
4413
|
-
if (placed) {
|
|
4414
|
-
if (document.body.contains(entry)) return;
|
|
4415
|
-
rootObserver.disconnect();
|
|
4416
|
-
root = void 0;
|
|
4417
|
-
placed = false;
|
|
4418
|
-
}
|
|
4419
|
-
root ??= sidebarRoot();
|
|
4420
|
-
if (root === void 0) return;
|
|
4421
|
-
placed = placeEntry(root, entry, options);
|
|
4422
|
-
if (placed) rootObserver.observe(root, {
|
|
4423
|
-
childList: true,
|
|
4424
|
-
subtree: true
|
|
4425
|
-
});
|
|
4426
|
-
};
|
|
4427
|
-
const unsubscribeBody = subscribeBodyInvalidations(() => {
|
|
4428
|
-
tryPlace();
|
|
4429
|
-
});
|
|
4430
|
-
const rootObserver = new MutationObserver(() => {
|
|
4431
|
-
if (root === void 0 || !root.isConnected) {
|
|
4432
|
-
placed = false;
|
|
4433
|
-
tryPlace();
|
|
4434
|
-
return;
|
|
4435
|
-
}
|
|
4436
|
-
if (!root.contains(entry)) placed = placeEntry(root, entry, options);
|
|
4437
|
-
});
|
|
4438
|
-
const unsubscribeActive = options.active === void 0 ? void 0 : (() => {
|
|
4439
|
-
const syncActive = () => {
|
|
4440
|
-
if (options.active.isOpen()) entry.dataset.active = "true";
|
|
4441
|
-
else delete entry.dataset.active;
|
|
4442
|
-
};
|
|
4443
|
-
const unsubscribe = options.active.subscribe(syncActive);
|
|
4444
|
-
syncActive();
|
|
4445
|
-
return unsubscribe;
|
|
4446
|
-
})();
|
|
4447
|
-
tryPlace();
|
|
4942
|
+
function registerTaskBoardPanel(ctx, controller) {
|
|
4943
|
+
const slots = ctx.slots;
|
|
4944
|
+
const disposers = [];
|
|
4945
|
+
disposers.push(slots.inject("sidebar.panellist", () => slots.register({
|
|
4946
|
+
name: "sidebar.panellist",
|
|
4947
|
+
id: TASK_BOARD_PANEL_ID,
|
|
4948
|
+
order: PANEL_ORDER,
|
|
4949
|
+
label: () => t("entry.label")
|
|
4950
|
+
}, TaskBoardPanelIcon)));
|
|
4951
|
+
disposers.push(slots.inject("main", () => slots.register({
|
|
4952
|
+
name: "main",
|
|
4953
|
+
key: TASK_BOARD_PANEL_ID,
|
|
4954
|
+
inject: () => ({ controller })
|
|
4955
|
+
}, TaskBoardPanel)));
|
|
4448
4956
|
return () => {
|
|
4449
|
-
|
|
4450
|
-
rootObserver.disconnect();
|
|
4451
|
-
unsubscribeRefresh?.();
|
|
4452
|
-
unsubscribeActive?.();
|
|
4453
|
-
entry.remove();
|
|
4957
|
+
for (const dispose of disposers.splice(0)) dispose();
|
|
4454
4958
|
};
|
|
4455
4959
|
}
|
|
4456
4960
|
//#endregion
|
|
4457
|
-
//#region src/client/sidebar-entry.ts
|
|
4458
|
-
/** Stable data attribute identifying the injected entry row. */
|
|
4459
|
-
const ENTRY_SELECTOR = "[data-dsh-taskboard-entry]";
|
|
4460
|
-
/** Inline icon normalized to the shell's 16px navigation glyph size. */
|
|
4461
|
-
const ICON = "<svg viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" 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>";
|
|
4462
|
-
/**
|
|
4463
|
-
* Mount the sidebar entry, waiting for the shell to render and self-healing
|
|
4464
|
-
* on later React re-renders.
|
|
4465
|
-
* @param controller - the board controller the entry toggles.
|
|
4466
|
-
* @param locale - locale-change source; when given, re-applies the label on
|
|
4467
|
-
* a Language switch (the plain-DOM row otherwise keeps the mount-time copy).
|
|
4468
|
-
* @returns disposer removing the entry and its observers.
|
|
4469
|
-
*/
|
|
4470
|
-
function mountSidebarEntry(controller, locale) {
|
|
4471
|
-
return mountSidebarEntry$1({
|
|
4472
|
-
rowAttribute: "data-dsh-taskboard-entry",
|
|
4473
|
-
rowSelector: ENTRY_SELECTOR,
|
|
4474
|
-
plugin: "task-board",
|
|
4475
|
-
icon: ICON,
|
|
4476
|
-
css: board_module_css_default,
|
|
4477
|
-
label: () => t("entry.label"),
|
|
4478
|
-
refresh: locale === void 0 ? void 0 : { subscribe: (listener) => locale.subscribe(listener) },
|
|
4479
|
-
onToggle: () => {
|
|
4480
|
-
controller.toggleBoard();
|
|
4481
|
-
},
|
|
4482
|
-
position: "before",
|
|
4483
|
-
familySelectors: ["[data-dsh-taskboard-entry]", "[data-dsh-ssh-entry]"],
|
|
4484
|
-
active: {
|
|
4485
|
-
subscribe: (listener) => controller.subscribe(listener),
|
|
4486
|
-
isOpen: () => controller.getSnapshot().boardOpen
|
|
4487
|
-
}
|
|
4488
|
-
});
|
|
4489
|
-
}
|
|
4490
|
-
//#endregion
|
|
4491
4961
|
//#region \0dsh-css:packages/dsh-task-board/src/client/settings-card.module.css.mjs
|
|
4492
4962
|
const css = ".Jh0q7G_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}.Jh0q7G_card:hover{border-color:var(--dsw-alias-label-dimmed)}.Jh0q7G_cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.Jh0q7G_header{appearance:none;box-sizing:border-box;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.Jh0q7G_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.Jh0q7G_headerStatic{box-sizing:border-box;border-radius:12px;align-items:center;gap:12px;width:100%;padding:14px 16px;display:flex}.Jh0q7G_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.Jh0q7G_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.Jh0q7G_description{color:var(--dsw-alias-label-secondary);font-size:13px;line-height:1.5}.Jh0q7G_pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.Jh0q7G_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.Jh0q7G_chevronOpen{transform:rotate(180deg)}.Jh0q7G_body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.Jh0q7G_readOnly{color:var(--dsw-alias-label-secondary);margin:12px 0 0;font-size:12px;line-height:1.5}.Jh0q7G_notExposed{color:var(--dsw-alias-state-warn-primary);margin:12px 0 0;font-size:12px;line-height:1.5}.Jh0q7G_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.Jh0q7G_failed{min-width:0;color:var(--dsw-alias-state-error-primary,#b42318);text-overflow:ellipsis;white-space:nowrap;flex:1;margin:0;font-size:12px;line-height:1.5;overflow:hidden}.Jh0q7G_discard,.Jh0q7G_save{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.Jh0q7G_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.Jh0q7G_discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.Jh0q7G_save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.Jh0q7G_discard:disabled,.Jh0q7G_save:disabled{opacity:.4;cursor:default}.Jh0q7G_discard:focus-visible,.Jh0q7G_save:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.Jh0q7G_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.Jh0q7G_field+.Jh0q7G_field{border-top:1px solid var(--dsw-alias-border-l2)}.Jh0q7G_head{align-items:center;gap:8px;display:flex}.Jh0q7G_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.Jh0q7G_badges{align-items:center;gap:8px;display:inline-flex}.Jh0q7G_badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.Jh0q7G_reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}.Jh0q7G_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.Jh0q7G_reset:disabled{cursor:default}.Jh0q7G_reset:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px;outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.Jh0q7G_input,.Jh0q7G_select{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.Jh0q7G_input:focus-visible,.Jh0q7G_select:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}.Jh0q7G_input:disabled,.Jh0q7G_select:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.Jh0q7G_inputInvalid{border:1px solid var(--dsw-alias-state-error-primary,#b42318);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.Jh0q7G_inputInvalid:focus-visible{outline:2px solid var(--dsw-alias-state-error-primary,#b42318);outline-offset:1px;border-color:var(--dsw-alias-state-error-primary,#b42318)}.Jh0q7G_selectWrap{position:relative}.Jh0q7G_selectButton{appearance:none;text-align:left;cursor:pointer;justify-content:space-between;align-items:center;gap:8px;width:100%;display:flex}.Jh0q7G_selectLabel{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.Jh0q7G_selectChevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.Jh0q7G_selectChevronOpen{transform:rotate(180deg)}.Jh0q7G_selectPopup{z-index:40;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);max-height:240px;box-shadow:0 8px 24px var(--dsw-alias-bg-mask-2);opacity:0;border-radius:8px;flex-direction:column;padding:4px;transition:opacity .1s,transform .1s;display:flex;position:absolute;top:calc(100% + 4px);left:0;right:0;overflow-y:auto;transform:translateY(-4px)}.Jh0q7G_selectPopupOpen{opacity:1;transform:none}.Jh0q7G_selectPopupClose{opacity:0;pointer-events:none;transform:translateY(-4px)}.Jh0q7G_selectOption{color:var(--dsw-alias-label-primary);cursor:pointer;white-space:nowrap;text-overflow:ellipsis;border-radius:6px;flex-shrink:0;padding:6px 10px;font-size:13px;line-height:1.5;overflow:hidden}.Jh0q7G_selectOption:hover,.Jh0q7G_selectOptionActive{background:var(--dsw-alias-interactive-bg-hover)}.Jh0q7G_selectOptionSelected{color:var(--dsw-alias-brand-primary);background:color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 10%, transparent);font-weight:500}.Jh0q7G_invalid{color:var(--dsw-alias-state-error-primary,#b42318);margin:0;font-size:12px;line-height:1.5}.Jh0q7G_hint{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:1.5}@media (prefers-reduced-motion:reduce){.Jh0q7G_card,.Jh0q7G_header,.Jh0q7G_chevron,.Jh0q7G_chevronOpen,.Jh0q7G_discard,.Jh0q7G_save,.Jh0q7G_selectChevron,.Jh0q7G_selectChevronOpen,.Jh0q7G_selectPopup{transition:none}}";
|
|
4493
4963
|
const tagId = "@linxin666/dsh-client-ui-task-board/packages/dsh-task-board/src/client/settings-card.module.css";
|
|
@@ -4896,6 +5366,49 @@ window.__ModuleLoader__.load({
|
|
|
4896
5366
|
]
|
|
4897
5367
|
});
|
|
4898
5368
|
}
|
|
5369
|
+
/** A staged enumerated field rendered as a select. */
|
|
5370
|
+
function ChoiceField(props) {
|
|
5371
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5372
|
+
className: settings_card_module_css_default.field,
|
|
5373
|
+
children: [
|
|
5374
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5375
|
+
className: settings_card_module_css_default.head,
|
|
5376
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
5377
|
+
className: settings_card_module_css_default.label,
|
|
5378
|
+
htmlFor: props.id,
|
|
5379
|
+
children: props.label
|
|
5380
|
+
}), props.overridden ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
5381
|
+
className: settings_card_module_css_default.badges,
|
|
5382
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5383
|
+
className: settings_card_module_css_default.badge,
|
|
5384
|
+
children: props.overriddenLabel
|
|
5385
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5386
|
+
type: "button",
|
|
5387
|
+
className: settings_card_module_css_default.reset,
|
|
5388
|
+
disabled: props.disabled,
|
|
5389
|
+
onClick: props.onReset,
|
|
5390
|
+
children: props.resetLabel
|
|
5391
|
+
})]
|
|
5392
|
+
}) : null]
|
|
5393
|
+
}),
|
|
5394
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectField, {
|
|
5395
|
+
id: props.id,
|
|
5396
|
+
options: [{
|
|
5397
|
+
value: "",
|
|
5398
|
+
label: props.inheritLabel
|
|
5399
|
+
}, ...props.choices],
|
|
5400
|
+
value: props.text,
|
|
5401
|
+
disabled: props.disabled,
|
|
5402
|
+
invalid: props.invalid,
|
|
5403
|
+
onEdit: props.onEdit
|
|
5404
|
+
}),
|
|
5405
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5406
|
+
className: props.invalid ? settings_card_module_css_default.invalid : settings_card_module_css_default.hint,
|
|
5407
|
+
children: props.invalid ? props.invalidLabel : props.hint
|
|
5408
|
+
})
|
|
5409
|
+
]
|
|
5410
|
+
});
|
|
5411
|
+
}
|
|
4899
5412
|
//#endregion
|
|
4900
5413
|
//#region \0dsh-store-engine
|
|
4901
5414
|
const platform = ["@deepseek-ai/dsh-client", "-store"].join("");
|
|
@@ -5181,6 +5694,28 @@ window.__ModuleLoader__.load({
|
|
|
5181
5694
|
};
|
|
5182
5695
|
//#endregion
|
|
5183
5696
|
//#region src/client/TaskBoardSettingsCard.tsx
|
|
5697
|
+
/** The depth choices the card offers, derived from the supported range. */
|
|
5698
|
+
const SUBTASK_DEPTH_CHOICES = Array.from({ length: 3 }, (_, index) => String(1 + index));
|
|
5699
|
+
/**
|
|
5700
|
+
* The depth field: a choice among the supported levels whose draft text is a
|
|
5701
|
+
* number, because the Host schema (`maxSubtaskDepth`) is numeric. A draft
|
|
5702
|
+
* outside the range blocks the save instead of staging a value the Host
|
|
5703
|
+
* refuses.
|
|
5704
|
+
*/
|
|
5705
|
+
function subtaskDepthField() {
|
|
5706
|
+
return {
|
|
5707
|
+
field: "maxSubtaskDepth",
|
|
5708
|
+
format: (value) => typeof value === "number" && Number.isInteger(value) ? String(value) : "",
|
|
5709
|
+
parse: (text) => {
|
|
5710
|
+
const trimmed = text.trim();
|
|
5711
|
+
if (trimmed === "") return { kind: "clear" };
|
|
5712
|
+
return SUBTASK_DEPTH_CHOICES.includes(trimmed) ? {
|
|
5713
|
+
kind: "set",
|
|
5714
|
+
value: Number(trimmed)
|
|
5715
|
+
} : void 0;
|
|
5716
|
+
}
|
|
5717
|
+
};
|
|
5718
|
+
}
|
|
5184
5719
|
/** Bridges the `task-board` settings form onto the card's staged form. */
|
|
5185
5720
|
var TaskBoardSettingsCardController = class {
|
|
5186
5721
|
form;
|
|
@@ -5190,7 +5725,8 @@ window.__ModuleLoader__.load({
|
|
|
5190
5725
|
this.form = new CardForm(scope, [
|
|
5191
5726
|
booleanField("enabled"),
|
|
5192
5727
|
booleanField("announceToAgent"),
|
|
5193
|
-
booleanField("preventIdleSleep")
|
|
5728
|
+
booleanField("preventIdleSleep"),
|
|
5729
|
+
subtaskDepthField()
|
|
5194
5730
|
]);
|
|
5195
5731
|
this.store = this.form.bind(() => this.projection());
|
|
5196
5732
|
}
|
|
@@ -5199,7 +5735,8 @@ window.__ModuleLoader__.load({
|
|
|
5199
5735
|
...this.form.shell(),
|
|
5200
5736
|
enabled: this.form.field("enabled"),
|
|
5201
5737
|
announceToAgent: this.form.field("announceToAgent"),
|
|
5202
|
-
preventIdleSleep: this.form.field("preventIdleSleep")
|
|
5738
|
+
preventIdleSleep: this.form.field("preventIdleSleep"),
|
|
5739
|
+
maxSubtaskDepth: this.form.field("maxSubtaskDepth")
|
|
5203
5740
|
};
|
|
5204
5741
|
}
|
|
5205
5742
|
/**
|
|
@@ -5232,7 +5769,7 @@ window.__ModuleLoader__.load({
|
|
|
5232
5769
|
const [power, setPower] = (0, react.useState)();
|
|
5233
5770
|
(0, react.useEffect)(() => {
|
|
5234
5771
|
let live = true;
|
|
5235
|
-
const events = new EventSource("
|
|
5772
|
+
const events = new EventSource("api/task-board/events");
|
|
5236
5773
|
events.onmessage = (message) => {
|
|
5237
5774
|
try {
|
|
5238
5775
|
const frame = JSON.parse(message.data);
|
|
@@ -5309,6 +5846,24 @@ window.__ModuleLoader__.load({
|
|
|
5309
5846
|
props.resetField("preventIdleSleep");
|
|
5310
5847
|
}
|
|
5311
5848
|
}),
|
|
5849
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChoiceField, {
|
|
5850
|
+
id: "settings-task-board-subtask-depth",
|
|
5851
|
+
label: t("settings.maxSubtaskDepth"),
|
|
5852
|
+
hint: t("settings.maxSubtaskDepthHint"),
|
|
5853
|
+
inheritLabel: t("settings.inherit"),
|
|
5854
|
+
choices: SUBTASK_DEPTH_CHOICES.map((value) => ({
|
|
5855
|
+
value,
|
|
5856
|
+
label: t("settings.maxSubtaskDepthOption", { depth: value })
|
|
5857
|
+
})),
|
|
5858
|
+
...fieldProps,
|
|
5859
|
+
...state.maxSubtaskDepth,
|
|
5860
|
+
onEdit: (text) => {
|
|
5861
|
+
props.edit("maxSubtaskDepth", text);
|
|
5862
|
+
},
|
|
5863
|
+
onReset: () => {
|
|
5864
|
+
props.resetField("maxSubtaskDepth");
|
|
5865
|
+
}
|
|
5866
|
+
}),
|
|
5312
5867
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("settings.powerStatus", {
|
|
5313
5868
|
platform: power?.platform ?? t("settings.powerUnknown"),
|
|
5314
5869
|
phase: power?.phase ?? t("settings.powerUnknown"),
|
|
@@ -5331,6 +5886,15 @@ window.__ModuleLoader__.load({
|
|
|
5331
5886
|
}
|
|
5332
5887
|
//#endregion
|
|
5333
5888
|
//#region src/client/host-api.ts
|
|
5889
|
+
/**
|
|
5890
|
+
* Client-side base for the board's routes: DOCUMENT-RELATIVE (issue #1707).
|
|
5891
|
+
*
|
|
5892
|
+
* The host registers `TASK_BOARD_API_PREFIX` root-absolute; the page is served
|
|
5893
|
+
* with `<base href="./">`, so the browser half must drop the leading slash or
|
|
5894
|
+
* a sub-path deployment escapes its entry directory. Derived from the shared
|
|
5895
|
+
* constant so host and client cannot drift.
|
|
5896
|
+
*/
|
|
5897
|
+
const CLIENT_API_PREFIX = TASK_BOARD_API_PREFIX.slice(1);
|
|
5334
5898
|
const IMPORT_MARKER = "dsh.taskBoard.v2.hostImported";
|
|
5335
5899
|
const SOURCE_KEY = "dsh.taskBoard.v2.sourceId";
|
|
5336
5900
|
const IMPORT_REQUEST_KEY = "dsh.taskBoard.v2.importRequestId";
|
|
@@ -5412,7 +5976,7 @@ window.__ModuleLoader__.load({
|
|
|
5412
5976
|
return initial;
|
|
5413
5977
|
}
|
|
5414
5978
|
async state() {
|
|
5415
|
-
return await this.request(`${
|
|
5979
|
+
return await this.request(`${CLIENT_API_PREFIX}/state`, { cache: "no-store" });
|
|
5416
5980
|
}
|
|
5417
5981
|
async action(action, initiator) {
|
|
5418
5982
|
return await this.post(uuid(), action, initiator);
|
|
@@ -5423,7 +5987,7 @@ window.__ModuleLoader__.load({
|
|
|
5423
5987
|
action,
|
|
5424
5988
|
...initiator === void 0 || initiator === "" ? {} : { initiator }
|
|
5425
5989
|
};
|
|
5426
|
-
return await this.request(`${
|
|
5990
|
+
return await this.request(`${CLIENT_API_PREFIX}/action`, {
|
|
5427
5991
|
method: "POST",
|
|
5428
5992
|
headers: { "content-type": "application/json" },
|
|
5429
5993
|
body: JSON.stringify(envelope)
|
|
@@ -5455,7 +6019,7 @@ window.__ModuleLoader__.load({
|
|
|
5455
6019
|
async parseDraft(request, signal) {
|
|
5456
6020
|
let response;
|
|
5457
6021
|
try {
|
|
5458
|
-
response = await fetch(`${
|
|
6022
|
+
response = await fetch(`${CLIENT_API_PREFIX}/parse`, {
|
|
5459
6023
|
method: "POST",
|
|
5460
6024
|
headers: { "content-type": "application/json" },
|
|
5461
6025
|
body: JSON.stringify(request),
|
|
@@ -5486,7 +6050,7 @@ window.__ModuleLoader__.load({
|
|
|
5486
6050
|
throw new HostApiError("rejected", t("new.aiParseFailed", { error: typeof record?.error === "string" && record.error !== "" ? record.error : String(response.status) }), response.status);
|
|
5487
6051
|
}
|
|
5488
6052
|
subscribe(listener) {
|
|
5489
|
-
const events = new EventSource(`${
|
|
6053
|
+
const events = new EventSource(`${CLIENT_API_PREFIX}/events`);
|
|
5490
6054
|
let lastStreamErrorNotify = 0;
|
|
5491
6055
|
events.onmessage = (message) => {
|
|
5492
6056
|
try {
|
|
@@ -5521,7 +6085,7 @@ window.__ModuleLoader__.load({
|
|
|
5521
6085
|
/** The building package's version, when the bundle carries it. */
|
|
5522
6086
|
function bakedVersion() {
|
|
5523
6087
|
try {
|
|
5524
|
-
return "0.4.
|
|
6088
|
+
return "0.4.3";
|
|
5525
6089
|
} catch {
|
|
5526
6090
|
return;
|
|
5527
6091
|
}
|
|
@@ -5709,6 +6273,96 @@ window.__ModuleLoader__.load({
|
|
|
5709
6273
|
reconcile();
|
|
5710
6274
|
}
|
|
5711
6275
|
//#endregion
|
|
6276
|
+
//#region src/client/settings-entry-form.ts
|
|
6277
|
+
/** The snapshot a form reports before the Host has answered with this entry. */
|
|
6278
|
+
function pendingSnapshot() {
|
|
6279
|
+
return {
|
|
6280
|
+
status: "loading",
|
|
6281
|
+
value: void 0,
|
|
6282
|
+
base: void 0,
|
|
6283
|
+
user: void 0,
|
|
6284
|
+
revision: void 0,
|
|
6285
|
+
writable: false,
|
|
6286
|
+
mode: "host"
|
|
6287
|
+
};
|
|
6288
|
+
}
|
|
6289
|
+
/**
|
|
6290
|
+
* Create a settings form bound to the profile entry id this Host serves, and
|
|
6291
|
+
* rebound whenever the shared describe mirror names a different one of this
|
|
6292
|
+
* package's rows.
|
|
6293
|
+
* @param options - the shared forms service and this package's candidate row ids.
|
|
6294
|
+
* @returns a form delegating to the currently resolved entry's own form.
|
|
6295
|
+
*/
|
|
6296
|
+
function createServedEntryForm(options) {
|
|
6297
|
+
const { forms, entryIds } = options;
|
|
6298
|
+
const fallbackId = entryIds[0];
|
|
6299
|
+
const namespaceId = entryIds[entryIds.length - 1];
|
|
6300
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
6301
|
+
let boundId;
|
|
6302
|
+
let bound;
|
|
6303
|
+
let offBound;
|
|
6304
|
+
let snapshot = pendingSnapshot();
|
|
6305
|
+
/** Republish: the delegated snapshot when one is bound, the pending one otherwise. */
|
|
6306
|
+
const publish = () => {
|
|
6307
|
+
if (bound !== void 0) snapshot = bound.getSnapshot();
|
|
6308
|
+
for (const listener of [...listeners]) listener();
|
|
6309
|
+
};
|
|
6310
|
+
/**
|
|
6311
|
+
* The entry id the mirror currently justifies. An unanswered or empty mirror
|
|
6312
|
+
* is not evidence of absence, so it keeps the first candidate; a mirror that
|
|
6313
|
+
* answers without any of this package's rows leaves the namespace itself.
|
|
6314
|
+
* @returns the entry id to bind.
|
|
6315
|
+
*/
|
|
6316
|
+
const resolve = () => {
|
|
6317
|
+
let served;
|
|
6318
|
+
try {
|
|
6319
|
+
served = forms.describe().getSnapshot().view?.namespaces.map((row) => row.ns);
|
|
6320
|
+
} catch {
|
|
6321
|
+
served = void 0;
|
|
6322
|
+
}
|
|
6323
|
+
if (served === void 0 || served.length === 0) return fallbackId;
|
|
6324
|
+
return entryIds.find((id) => served.includes(id)) ?? namespaceId;
|
|
6325
|
+
};
|
|
6326
|
+
/** Bind (or rebind) the resolved entry's form; a no-op while it is unchanged. */
|
|
6327
|
+
const bind = () => {
|
|
6328
|
+
const target = resolve();
|
|
6329
|
+
if (target === boundId) return;
|
|
6330
|
+
offBound?.();
|
|
6331
|
+
offBound = void 0;
|
|
6332
|
+
let form;
|
|
6333
|
+
try {
|
|
6334
|
+
form = forms.get(target);
|
|
6335
|
+
} catch {
|
|
6336
|
+
boundId = void 0;
|
|
6337
|
+
return;
|
|
6338
|
+
}
|
|
6339
|
+
boundId = target;
|
|
6340
|
+
bound = form;
|
|
6341
|
+
offBound = form.subscribe(() => {
|
|
6342
|
+
publish();
|
|
6343
|
+
});
|
|
6344
|
+
publish();
|
|
6345
|
+
};
|
|
6346
|
+
try {
|
|
6347
|
+
forms.describe().subscribe(() => {
|
|
6348
|
+
bind();
|
|
6349
|
+
});
|
|
6350
|
+
} catch {}
|
|
6351
|
+
bind();
|
|
6352
|
+
return {
|
|
6353
|
+
getSnapshot: () => snapshot,
|
|
6354
|
+
subscribe: (listener) => {
|
|
6355
|
+
listeners.add(listener);
|
|
6356
|
+
return () => {
|
|
6357
|
+
listeners.delete(listener);
|
|
6358
|
+
};
|
|
6359
|
+
},
|
|
6360
|
+
set: (field, value) => bound?.set(field, value) ?? Promise.resolve(false),
|
|
6361
|
+
unset: (field) => bound?.unset(field) ?? Promise.resolve(false),
|
|
6362
|
+
mutate: (ops, expectedRevision) => bound?.mutate(ops, expectedRevision) ?? Promise.resolve(false)
|
|
6363
|
+
};
|
|
6364
|
+
}
|
|
6365
|
+
//#endregion
|
|
5712
6366
|
//#region src/client/index.ts
|
|
5713
6367
|
/** Locale namespace this plugin owns. */
|
|
5714
6368
|
const NS = "task-board";
|
|
@@ -5749,7 +6403,8 @@ window.__ModuleLoader__.load({
|
|
|
5749
6403
|
"locale",
|
|
5750
6404
|
"remote",
|
|
5751
6405
|
"remote.session",
|
|
5752
|
-
"uiWorkspace"
|
|
6406
|
+
"uiWorkspace",
|
|
6407
|
+
"layout"
|
|
5753
6408
|
];
|
|
5754
6409
|
/**
|
|
5755
6410
|
* Mount the task board.
|
|
@@ -5794,6 +6449,9 @@ window.__ModuleLoader__.load({
|
|
|
5794
6449
|
const controller = new BoardController({
|
|
5795
6450
|
store: new LocalStorageTaskStore(),
|
|
5796
6451
|
transport: new HttpTaskBoardHostTransport(),
|
|
6452
|
+
panel: { select: (panelId) => {
|
|
6453
|
+
ctx.get("layout")?.selectPanel?.(panelId);
|
|
6454
|
+
} },
|
|
5797
6455
|
sessions: {
|
|
5798
6456
|
current: () => mainViewSessionId(sessions.list.getSnapshot().byId),
|
|
5799
6457
|
open: (id) => ctx.uiWorkspace.openSession(id),
|
|
@@ -5874,10 +6532,18 @@ window.__ModuleLoader__.load({
|
|
|
5874
6532
|
pushModelOptions();
|
|
5875
6533
|
}));
|
|
5876
6534
|
try {
|
|
5877
|
-
disposers.push(
|
|
5878
|
-
|
|
6535
|
+
disposers.push(registerTaskBoardPanel(ctx, controller));
|
|
6536
|
+
const layoutFace = ctx.get("layout");
|
|
6537
|
+
if (layoutFace?.panelInfo !== void 0) {
|
|
6538
|
+
const sync = () => {
|
|
6539
|
+
const active = layoutFace.panelInfo.getSnapshot().activePanelId;
|
|
6540
|
+
controller.syncPanelSelection(active === "task-board" ? TASK_BOARD_PANEL_ID : null);
|
|
6541
|
+
};
|
|
6542
|
+
sync();
|
|
6543
|
+
disposers.push(layoutFace.panelInfo.subscribe(sync));
|
|
6544
|
+
}
|
|
5879
6545
|
} catch (error) {
|
|
5880
|
-
console.error("[dsh-task-board]
|
|
6546
|
+
console.error("[dsh-task-board] panel registration failed:", error);
|
|
5881
6547
|
}
|
|
5882
6548
|
uiDisposer = () => {
|
|
5883
6549
|
for (const dispose of disposers.splice(0)) dispose();
|
|
@@ -5890,7 +6556,11 @@ window.__ModuleLoader__.load({
|
|
|
5890
6556
|
if (snapshot.status === "ready" ? snapshot.value?.enabled ?? true : snapshot.status === "unavailable") mountUi();
|
|
5891
6557
|
else uiDisposer?.();
|
|
5892
6558
|
};
|
|
5893
|
-
settingsForm.subscribe(syncEnabled);
|
|
6559
|
+
const unsubscribeSettings = settingsForm.subscribe(syncEnabled);
|
|
6560
|
+
ctx.effect(() => () => {
|
|
6561
|
+
unsubscribeSettings();
|
|
6562
|
+
uiDisposer?.();
|
|
6563
|
+
}, "task-board: DOM surfaces");
|
|
5894
6564
|
syncEnabled();
|
|
5895
6565
|
}
|
|
5896
6566
|
/**
|
|
@@ -5908,19 +6578,23 @@ window.__ModuleLoader__.load({
|
|
|
5908
6578
|
function bindSettingsForm(ctx) {
|
|
5909
6579
|
const binder = ctx.get("webUiSettings");
|
|
5910
6580
|
if (binder !== void 0 && typeof binder.bind === "function") return binder.bind({ namespace: TASK_BOARD_NS });
|
|
5911
|
-
return
|
|
6581
|
+
return createServedEntryForm({
|
|
6582
|
+
forms: ctx.configForms,
|
|
6583
|
+
entryIds: TASK_BOARD_ENTRY_IDS
|
|
6584
|
+
});
|
|
5912
6585
|
}
|
|
5913
6586
|
/**
|
|
5914
|
-
* The profile entry id this package's own row carries
|
|
6587
|
+
* The profile entry id this package's own row carries, for a page that serves
|
|
6588
|
+
* no family binder.
|
|
5915
6589
|
*
|
|
5916
6590
|
* The shared describe mirror is the only local evidence of which row id this
|
|
5917
|
-
* profile actually serves, but it answers asynchronously: at plugin
|
|
5918
|
-
*
|
|
5919
|
-
*
|
|
5920
|
-
*
|
|
5921
|
-
*
|
|
6591
|
+
* profile actually serves, but it answers asynchronously: at plugin activation
|
|
6592
|
+
* it usually holds nothing yet. An unanswered mirror therefore binds the
|
|
6593
|
+
* aggregate row id rather than guessing among the candidates, and the binding
|
|
6594
|
+
* is re-resolved once the mirror answers — see
|
|
6595
|
+
* {@link createServedEntryForm}, which owns that rebinding.
|
|
5922
6596
|
* @param forms - the shared configuration forms service.
|
|
5923
|
-
* @returns the entry id to bind.
|
|
6597
|
+
* @returns the entry id to bind before the mirror answers.
|
|
5924
6598
|
*/
|
|
5925
6599
|
function servedEntryId(forms) {
|
|
5926
6600
|
let served;
|
|
@@ -5936,6 +6610,7 @@ window.__ModuleLoader__.load({
|
|
|
5936
6610
|
exports.apply = apply;
|
|
5937
6611
|
exports.bindSettingsForm = bindSettingsForm;
|
|
5938
6612
|
exports.inject = inject;
|
|
6613
|
+
exports.servedEntryId = servedEntryId;
|
|
5939
6614
|
return module.exports;
|
|
5940
6615
|
}
|
|
5941
6616
|
});
|