@fonlan/dsh-task-kanban 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/lib/client.js +371 -122
- package/lib/client.js.map +1 -1
- package/lib/index.js +214 -24
- package/lib/types/server/models.d.ts +16 -0
- package/lib/types/server/runner.d.ts +39 -1
- package/lib/types/server/settings.d.ts +35 -7
- package/lib/types/shared/card.d.ts +12 -1
- package/package.json +5 -1
- package/src/client/api.ts +19 -1
- package/src/client/board.css +107 -16
- package/src/client/board.tsx +65 -29
- package/src/client/index.tsx +11 -9
- package/src/client/locales.ts +24 -4
- package/src/client/sections.tsx +244 -48
- package/src/server/models.ts +40 -0
- package/src/server/rpc.ts +33 -4
- package/src/server/runner.ts +168 -20
- package/src/server/settings.ts +52 -7
- package/src/shared/card.ts +13 -1
package/lib/client.js
CHANGED
|
@@ -104,7 +104,12 @@ window.__ModuleLoader__.load({
|
|
|
104
104
|
remove: (cardId) => call("remove", { cardId }),
|
|
105
105
|
settingsGet: () => call("settings.get"),
|
|
106
106
|
settingsSet: (patch) => call("settings.set", patch),
|
|
107
|
-
models: () => call("models.list")
|
|
107
|
+
models: () => call("models.list"),
|
|
108
|
+
presets: () => call("presets.list"),
|
|
109
|
+
reasoningOptions: (provider, model) => call("reasoning.options", {
|
|
110
|
+
provider,
|
|
111
|
+
model
|
|
112
|
+
})
|
|
108
113
|
};
|
|
109
114
|
//#endregion
|
|
110
115
|
//#region src/client/kanban-state.ts
|
|
@@ -273,6 +278,9 @@ window.__ModuleLoader__.load({
|
|
|
273
278
|
plan: "实现方案",
|
|
274
279
|
summary: "摘要",
|
|
275
280
|
phases: "阶段",
|
|
281
|
+
phaseGoal: "目标",
|
|
282
|
+
phaseSessions: "会话",
|
|
283
|
+
phaseConclusion: "结论",
|
|
276
284
|
error: "错误",
|
|
277
285
|
requirementLabel: "需求文本",
|
|
278
286
|
requirementPlaceholder: "描述你要实现的需求……",
|
|
@@ -283,9 +291,16 @@ window.__ModuleLoader__.load({
|
|
|
283
291
|
modelPlaceholder: "(使用默认模型)",
|
|
284
292
|
noWorkspace: "没有可用工作区,请先在侧边栏添加",
|
|
285
293
|
refresh: "刷新",
|
|
286
|
-
settingsTitle: "
|
|
294
|
+
settingsTitle: "任务看板",
|
|
295
|
+
settingsDescription: "全局并行 worker 数与各会话类型的默认模型、推理强度、Agent 预设",
|
|
296
|
+
settingsExpand: "展开设置",
|
|
297
|
+
settingsCollapse: "收起设置",
|
|
287
298
|
maxParallelWorkers: "全局并行 worker 数",
|
|
288
|
-
|
|
299
|
+
settingsRefineGroup: "细化需求会话默认",
|
|
300
|
+
settingsPhaseGroup: "Phase 实现会话默认",
|
|
301
|
+
reasoningEffort: "推理强度",
|
|
302
|
+
agentPreset: "Agent 预设",
|
|
303
|
+
defaultOption: "(默认)",
|
|
289
304
|
saved: "已保存",
|
|
290
305
|
loadError: "加载任务失败",
|
|
291
306
|
noPlan: "该卡片还没有实现计划,请先点击\"添加并细化\"",
|
|
@@ -337,6 +352,9 @@ window.__ModuleLoader__.load({
|
|
|
337
352
|
plan: "Plan",
|
|
338
353
|
summary: "Summary",
|
|
339
354
|
phases: "Phases",
|
|
355
|
+
phaseGoal: "Goal",
|
|
356
|
+
phaseSessions: "Sessions",
|
|
357
|
+
phaseConclusion: "Conclusion",
|
|
340
358
|
error: "Error",
|
|
341
359
|
requirementLabel: "Requirement",
|
|
342
360
|
requirementPlaceholder: "Describe what you want to build…",
|
|
@@ -347,9 +365,16 @@ window.__ModuleLoader__.load({
|
|
|
347
365
|
modelPlaceholder: "(use default model)",
|
|
348
366
|
noWorkspace: "No workspaces available — add one in the sidebar first",
|
|
349
367
|
refresh: "Refresh",
|
|
350
|
-
settingsTitle: "Task Kanban
|
|
368
|
+
settingsTitle: "Task Kanban",
|
|
369
|
+
settingsDescription: "Global parallel workers and per-session-type defaults for model, reasoning effort, and agent preset",
|
|
370
|
+
settingsExpand: "Show settings",
|
|
371
|
+
settingsCollapse: "Hide settings",
|
|
351
372
|
maxParallelWorkers: "Global parallel workers",
|
|
352
|
-
|
|
373
|
+
settingsRefineGroup: "Refinement session defaults",
|
|
374
|
+
settingsPhaseGroup: "Phase implementation defaults",
|
|
375
|
+
reasoningEffort: "Reasoning effort",
|
|
376
|
+
agentPreset: "Agent preset",
|
|
377
|
+
defaultOption: "(default)",
|
|
353
378
|
saved: "Saved",
|
|
354
379
|
loadError: "Failed to load tasks",
|
|
355
380
|
noPlan: "This card has no plan yet — click \"Add & Refine\" first",
|
|
@@ -369,8 +394,8 @@ window.__ModuleLoader__.load({
|
|
|
369
394
|
};
|
|
370
395
|
//#endregion
|
|
371
396
|
//#region \0dsh-css:/Users/fonlan/Repos/dsh-task-kanban/src/client/board.css.mjs
|
|
372
|
-
const css = "/* @fonlan/dsh-task-kanban board styles (kb- prefixed, plain CSS).\n Surfaces use the DSH theme tokens (--dsw-alias-*) so they follow the\n shell's selected light/dark appearance; the var() fallbacks cover shells\n that do not define them. */\n.kb-board {\n height: 100%;\n display: flex;\n flex-direction: column;\n box-sizing: border-box;\n padding: 12px 16px;\n overflow: hidden;\n color: var(--dsw-alias-label-primary, var(--dsh-fg, #e6e6e6));\n background: var(--dsw-alias-bg-base, var(--dsh-bg, #141418));\n font: 14px/1.5 -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', 'Microsoft YaHei', sans-serif;\n}\n.kb-empty {\n align-items: center;\n justify-content: center;\n}\n.kb-empty-text { opacity: 0.6; }\n.kb-board-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding-bottom: 10px;\n border-bottom: 1px solid rgba(128, 128, 128, 0.25);\n flex: 0 0 auto;\n}\n.kb-board-actions { display: flex; gap: 8px; align-items: center; flex: none; }\n.kb-board-actions .kb-btn { flex: none; }\n.kb-ws-select {\n max-width: 320px;\n min-width: 0;\n flex: 0 1 auto;\n padding: 6px 10px;\n border-radius: 6px;\n border: 1px solid rgba(128, 128, 128, 0.35);\n background: rgba(128, 128, 128, 0.12);\n color: inherit;\n font-size: 14px;\n}\n.kb-btn {\n padding: 6px 14px;\n border-radius: 6px;\n border: 1px solid rgba(128, 128, 128, 0.35);\n background: rgba(128, 128, 128, 0.12);\n color: inherit;\n font-size: 13px;\n white-space: nowrap;\n cursor: pointer;\n}\n.kb-btn:hover { background: rgba(128, 128, 128, 0.22); }\n.kb-btn:disabled { opacity: 0.5; cursor: default; }\n.kb-btn-primary { background: #3b82f6; border-color: #3b82f6; color: #fff; }\n.kb-btn-primary:hover { background: #2f6fe0; }\n.kb-btn-danger { background: rgba(239, 68, 68, 0.15); border-color: rgba(239, 68, 68, 0.5); color: #f87171; }\n.kb-btn-small { padding: 2px 8px; font-size: 12px; }\n.kb-lanes {\n flex: 1 1 auto;\n display: flex;\n gap: 12px;\n overflow-x: auto;\n overflow-y: hidden;\n padding-top: 10px;\n min-height: 0;\n}\n.kb-lane {\n flex: 1 1 0;\n min-width: 220px;\n max-width: 320px;\n display: flex;\n flex-direction: column;\n background: rgba(128, 128, 128, 0.07);\n border-radius: 8px;\n border: 1px solid rgba(128, 128, 128, 0.12);\n overflow: hidden;\n}\n.kb-lane-drop { border-style: dashed; border-color: rgba(59, 130, 246, 0.45); }\n.kb-lane-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 10px;\n font-weight: 600;\n font-size: 13px;\n color: var(--dsw-alias-label-secondary, var(--dsh-fg-soft, #b8b8c0));\n border-bottom: 1px solid rgba(128, 128, 128, 0.12);\n flex: 0 0 auto;\n}\n.kb-lane-count {\n background: rgba(128, 128, 128, 0.18);\n border-radius: 10px;\n padding: 0 8px;\n font-size: 12px;\n}\n.kb-lane-body {\n flex: 1 1 auto;\n overflow-y: auto;\n padding: 8px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.kb-card {\n background: rgba(128, 128, 128, 0.1);\n border: 1px solid rgba(128, 128, 128, 0.2);\n border-radius: 8px;\n padding: 8px 10px;\n cursor: pointer;\n user-select: none;\n}\n.kb-card:hover { border-color: rgba(128, 128, 128, 0.45); }\n.kb-card[draggable='true'] { cursor: grab; }\n.kb-card-running { border-color: rgba(59, 130, 246, 0.6); }\n.kb-card-error { border-color: rgba(239, 68, 68, 0.6); }\n.kb-card-title { font-weight: 600; font-size: 13px; margin-bottom: 6px; word-break: break-all; }\n.kb-card-meta { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; font-size: 12px; }\n.kb-card-badge {\n background: rgba(128, 128, 128, 0.2);\n border-radius: 4px;\n padding: 1px 6px;\n}\n.kb-card-running .kb-card-badge { background: rgba(59, 130, 246, 0.3); }\n.kb-card-error .kb-card-badge { background: rgba(239, 68, 68, 0.3); }\n.kb-card-phase { opacity: 0.8; }\n.kb-card-skill {\n background: rgba(139, 92, 246, 0.22);\n border-radius: 4px;\n padding: 1px 6px;\n}\n.kb-card-model { opacity: 0.55; }\n.kb-card-error-text { margin-top: 6px; font-size: 12px; color: #f87171; word-break: break-all; }\n.kb-card-worktree { margin-top: 4px; font-size: 11px; opacity: 0.5; word-break: break-all; }\n.kb-toast {\n position: fixed;\n left: 50%;\n bottom: 24px;\n transform: translateX(-50%);\n background: var(--dsw-alias-bg-layer-2);\n border: 1px solid var(--dsw-alias-border-l2);\n color: var(--dsw-alias-label-primary);\n padding: 8px 16px;\n border-radius: 8px;\n font-size: 13px;\n z-index: 2147483000;\n max-width: 70vw;\n}\n.kb-modal-backdrop {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.5);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 2147482900;\n}\n.kb-modal {\n width: 460px;\n max-width: 90vw;\n max-height: 85vh;\n overflow-y: auto;\n background: var(--dsw-alias-bg-layer-1);\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 12px;\n padding: 18px;\n color: var(--dsw-alias-label-primary);\n}\n.kb-modal-wide { width: 640px; }\n/* Task-detail bottom panel: docks at the bottom of the board page, fills the\n full board width and exactly half of the board height (the lanes column\n above flexes to the remaining space). */\n.kb-detail-panel {\n flex: 0 0 50%;\n min-height: 0;\n display: flex;\n flex-direction: column;\n margin-top: 10px;\n background: var(--dsw-alias-bg-layer-2);\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 12px;\n overflow: hidden;\n color: var(--dsw-alias-label-primary);\n}\n.kb-detail-panel-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n padding: 10px 14px;\n border-bottom: 1px solid rgba(128, 128, 128, 0.2);\n flex: 0 0 auto;\n}\n.kb-detail-panel-head .kb-modal-title { margin-bottom: 0; }\n.kb-detail-panel-body {\n flex: 1 1 auto;\n min-height: 0;\n overflow-y: auto;\n padding: 14px;\n}\n.kb-detail-close {\n flex: none;\n width: 28px;\n height: 28px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: 1px solid transparent;\n border-radius: 6px;\n background: transparent;\n color: inherit;\n font-size: 15px;\n line-height: 1;\n opacity: 0.75;\n cursor: pointer;\n}\n.kb-detail-close:hover { background: rgba(128, 128, 128, 0.18); opacity: 1; }\n.kb-modal-title { font-size: 16px; font-weight: 700; margin-bottom: 14px; display: flex; align-items: center; gap: 10px; }\n.kb-field { display: block; margin-bottom: 12px; }\n.kb-field > span { display: block; font-size: 12px; opacity: 0.75; margin-bottom: 4px; }\n.kb-field-hint {\n display: block;\n font-size: 11px;\n opacity: 0.55;\n margin-top: 4px;\n}\n.kb-field-hint-skill {\n opacity: 0.9;\n color: #a78bfa;\n}\n.kb-skill-menu-wrap { position: relative; }\n.kb-skill-menu {\n position: absolute;\n top: 100%;\n left: 0;\n right: 0;\n z-index: 20;\n margin: 2px 0 0;\n padding: 4px;\n list-style: none;\n max-height: 180px;\n overflow-y: auto;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-overlay);\n color: var(--dsw-alias-label-primary);\n box-shadow: 0 6px 20px rgba(0, 0, 0, 0.35);\n}\n.kb-skill-menu-item {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 6px 8px;\n border-radius: 6px;\n cursor: pointer;\n font-size: 12px;\n color: var(--dsw-alias-label-primary);\n}\n.kb-skill-menu-item-active { background: var(--dsw-alias-interactive-bg-hover); }\n.kb-skill-menu-name { font-weight: 600; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }\n.kb-skill-menu-desc {\n color: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.kb-field-row { display: flex; align-items: center; gap: 10px; justify-content: space-between; }\n.kb-input {\n width: 100%;\n box-sizing: border-box;\n padding: 7px 10px;\n border-radius: 6px;\n border: 1px solid rgba(128, 128, 128, 0.35);\n background: rgba(128, 128, 128, 0.1);\n color: inherit;\n font-size: 13px;\n}\n.kb-input-number { width: 90px; }\n.kb-textarea { min-height: 110px; resize: vertical; font-family: inherit; }\n.kb-modal-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 14px; }\n.kb-detail-section { margin-bottom: 12px; }\n.kb-detail-label { font-size: 12px; opacity: 0.65; margin-bottom: 4px; }\n.kb-detail-skill {\n display: inline-block;\n font-size: 11px;\n background: rgba(139, 92, 246, 0.22);\n border-radius: 4px;\n padding: 1px 6px;\n margin-bottom: 4px;\n}\n.kb-detail-text { white-space: pre-wrap; word-break: break-all; font-size: 13px; }\n.kb-detail-error .kb-detail-text { color: #f87171; }\n.kb-detail-status {\n font-size: 12px;\n font-weight: 600;\n border-radius: 4px;\n padding: 1px 8px;\n background: rgba(128, 128, 128, 0.2);\n}\n.kb-status-running, .kb-status-merging { background: rgba(59, 130, 246, 0.3) !important; }\n.kb-status-error { background: rgba(239, 68, 68, 0.3) !important; color: #f87171; }\n.kb-status-merged { background: rgba(34, 197, 94, 0.3) !important; }\n.kb-plan-summary { font-size: 13px; margin-bottom: 10px; }\n.kb-plan-phases { margin: 0; padding-left: 20px; }\n.kb-plan-phase { margin-bottom: 10px; }\n.kb-plan-phase-current .kb-plan-phase-title { color: #60a5fa; }\n.kb-plan-phase-head { display: flex; gap: 8px; align-items: baseline; }\n.kb-plan-phase-title { font-weight: 600; font-size: 13px; }\n.kb-plan-phase-id { font-size: 11px; opacity: 0.5; }\n.kb-plan-phase-goal { font-size: 12px; opacity: 0.85; margin: 2px 0 6px; white-space: pre-wrap; }\n.kb-session-links { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }\n.kb-session-attempt { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }\n.kb-session-summary { font-size: 12px; opacity: 0.75; }\n.kb-settings { display: flex; flex-direction: column; gap: 12px; max-width: 420px; }\n.kb-settings-saved { font-size: 12px; color: #4ade80; }\n.kb-footer-action {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n width: 32px;\n height: 32px;\n border-radius: 6px;\n border: none;\n background: transparent;\n /* same foreground as the settings trigger (its trigger css uses this var) */\n color: var(--dsw-alias-label-primary, inherit);\n cursor: pointer;\n}\n.kb-footer-action:hover { background: rgba(128, 128, 128, 0.18); }\n.kb-footer-action-active { background: rgba(59, 130, 246, 0.25); }\n/* Wide mode borrows the settings trigger's metrics (34px tall, 14px label,\n 18px icon) so both controls read as siblings on the same line. */\n.kb-footer-action-wide {\n width: auto;\n height: 34px;\n padding: 0 8px;\n border-radius: 12px;\n font-size: 14px;\n}\n.kb-footer-action-wide .kb-footer-label {\n font-size: 14px;\n line-height: 22px;\n}\n/* Theme following is handled by the --dsw-alias-* tokens above: they change\n value with the shell's selected light/dark appearance, so no OS-level\n prefers-color-scheme override is needed (and one would fight the app\n theme when the two disagree). */\n/* Same-line placement WITHOUT moving any DOM: the sidebar foot area becomes\n a reversed flex row, so its LAST child (the settings area) renders on the\n LEFT and the FIRST child (footer actions = the kanban button) on the RIGHT\n — exactly \"settings, then kanban\". The settings trigger flexes to fill the\n remaining width; the button keeps its content width. */\n.kb-foot-row {\n display: flex;\n flex-direction: row-reverse;\n align-items: stretch;\n gap: 4px;\n}\n.kb-foot-row > div:first-child {\n flex: none;\n width: auto;\n min-width: 0;\n display: flex;\n align-items: center;\n}\n.kb-foot-row > div:last-child {\n flex: 1;\n min-width: 0;\n}\n/* Rail (collapsed sidebar): column-reverse stacks the settings circle on top\n and the kanban icon below it, both centered; the kanban button borrows the\n settings trigger's rail chrome (36px circle) so the two match. */\n.kb-foot-rail {\n flex-direction: column-reverse;\n align-items: center;\n}\n.kb-foot-rail .kb-footer-action {\n width: 36px;\n height: 36px;\n border-radius: 50%;\n margin: 8px 0 10px;\n}\n.kb-footer-label {\n max-width: 110px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n/* Narrow screens (mobile drawer / auto-collapse breakpoint): the drawer's\n foot area is ~256-280px but the footer actions row carries the kanban\n button PLUS mobile-nav's drawer actions (Files / session log), which\n together exceed the row and squeeze the settings trigger to zero width\n (row-reverse overflows left, clipping the button). Below the shell's\n auto-collapse breakpoint, stack the settings row above the actions and\n let the actions wrap so nothing is squeezed or clipped. */\n@media (max-width: 1023px) {\n .kb-foot-row {\n flex-direction: column-reverse;\n align-items: stretch;\n }\n .kb-foot-row > div:first-child {\n flex: none;\n width: 100%;\n display: flex;\n flex-wrap: wrap;\n justify-content: flex-start;\n gap: 4px;\n padding-top: 4px;\n }\n .kb-foot-row > div:last-child {\n flex: none;\n width: 100%;\n }\n /* The footer-actions entries are display:contents wrappers, so the kanban\n button itself is a flex item of the wrapping actions row; pull it to the\n first wrap position so it sits directly below the settings row (the\n mobile-nav drawer actions follow on their own wrapped line). */\n .kb-foot-row .kb-footer-action {\n order: -1;\n }\n}";
|
|
373
|
-
const tagId = "@fonlan/dsh-task-kanban/board.css";
|
|
397
|
+
const css = "/* @fonlan/dsh-task-kanban board styles (kb- prefixed, plain CSS).\n Surfaces use the DSH theme tokens (--dsw-alias-*) so they follow the\n shell's selected light/dark appearance; the var() fallbacks cover shells\n that do not define them. */\n.kb-board {\n height: 100%;\n display: flex;\n flex-direction: column;\n box-sizing: border-box;\n padding: 12px 16px;\n overflow: hidden;\n color: var(--dsw-alias-label-primary, var(--dsh-fg, #e6e6e6));\n background: var(--dsw-alias-bg-base, var(--dsh-bg, #141418));\n font: 14px/1.5 -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', 'Microsoft YaHei', sans-serif;\n}\n.kb-empty {\n align-items: center;\n justify-content: center;\n}\n.kb-empty-text { opacity: 0.6; }\n.kb-board-header {\n display: flex;\n align-items: center;\n gap: 12px;\n padding-bottom: 10px;\n border-bottom: 1px solid rgba(128, 128, 128, 0.25);\n flex: 0 0 auto;\n}\n/* Left-aligned group: workspace selector + new-task button sit adjacent. */\n.kb-board-title {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n flex: 0 1 auto;\n}\n.kb-board-title .kb-btn { flex: none; }\n.kb-ws-select {\n max-width: 320px;\n min-width: 0;\n flex: 0 1 auto;\n padding: 6px 10px;\n border-radius: 6px;\n border: 1px solid var(--dsw-alias-border-l2-darkmode-thin, var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35)));\n background: var(--dsw-specific-input-major, var(--dsw-alias-bg-layer-1, rgba(128, 128, 128, 0.12)));\n color: var(--dsw-alias-label-primary, inherit);\n font-size: 14px;\n}\n.kb-btn {\n padding: 6px 14px;\n border-radius: 6px;\n border: 1px solid rgba(128, 128, 128, 0.35);\n background: rgba(128, 128, 128, 0.12);\n color: inherit;\n font-size: 13px;\n white-space: nowrap;\n cursor: pointer;\n}\n.kb-btn:hover { background: rgba(128, 128, 128, 0.22); }\n.kb-btn:disabled { opacity: 0.5; cursor: default; }\n.kb-btn-primary { background: #3b82f6; border-color: #3b82f6; color: #fff; }\n.kb-btn-primary:hover { background: #2f6fe0; }\n.kb-btn-danger { background: rgba(239, 68, 68, 0.15); border-color: rgba(239, 68, 68, 0.5); color: #f87171; }\n.kb-btn-small { padding: 2px 8px; font-size: 12px; }\n.kb-lanes {\n flex: 1 1 auto;\n display: flex;\n gap: 12px;\n overflow-x: auto;\n overflow-y: hidden;\n padding-top: 10px;\n min-height: 0;\n}\n.kb-lane {\n flex: 1 1 0;\n min-width: 220px;\n max-width: 320px;\n display: flex;\n flex-direction: column;\n background: rgba(128, 128, 128, 0.07);\n border-radius: 8px;\n border: 1px solid rgba(128, 128, 128, 0.12);\n overflow: hidden;\n}\n.kb-lane-drop { border-style: dashed; border-color: rgba(59, 130, 246, 0.45); }\n.kb-lane-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 10px;\n font-weight: 600;\n font-size: 13px;\n color: var(--dsw-alias-label-secondary, var(--dsh-fg-soft, #b8b8c0));\n border-bottom: 1px solid rgba(128, 128, 128, 0.12);\n flex: 0 0 auto;\n}\n.kb-lane-count {\n background: rgba(128, 128, 128, 0.18);\n border-radius: 10px;\n padding: 0 8px;\n font-size: 12px;\n}\n.kb-lane-body {\n flex: 1 1 auto;\n overflow-y: auto;\n padding: 8px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.kb-card {\n background: rgba(128, 128, 128, 0.1);\n border: 1px solid rgba(128, 128, 128, 0.2);\n border-radius: 8px;\n padding: 8px 10px;\n cursor: pointer;\n user-select: none;\n}\n.kb-card:hover { border-color: rgba(128, 128, 128, 0.45); }\n.kb-card[draggable='true'] { cursor: grab; }\n.kb-card-running { border-color: rgba(59, 130, 246, 0.6); }\n.kb-card-error { border-color: rgba(239, 68, 68, 0.6); }\n.kb-card-title { font-weight: 600; font-size: 13px; margin-bottom: 6px; word-break: break-all; }\n.kb-card-meta { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; font-size: 12px; }\n.kb-card-badge {\n background: rgba(128, 128, 128, 0.2);\n border-radius: 4px;\n padding: 1px 6px;\n}\n.kb-card-running .kb-card-badge { background: rgba(59, 130, 246, 0.3); }\n.kb-card-error .kb-card-badge { background: rgba(239, 68, 68, 0.3); }\n.kb-card-phase { opacity: 0.8; }\n.kb-card-skill {\n background: rgba(139, 92, 246, 0.22);\n border-radius: 4px;\n padding: 1px 6px;\n}\n.kb-card-model { opacity: 0.55; }\n.kb-card-error-text { margin-top: 6px; font-size: 12px; color: #f87171; word-break: break-all; }\n.kb-card-worktree { margin-top: 4px; font-size: 11px; opacity: 0.5; word-break: break-all; }\n.kb-toast {\n position: fixed;\n left: 50%;\n bottom: 24px;\n transform: translateX(-50%);\n background: var(--dsw-alias-bg-layer-2);\n border: 1px solid var(--dsw-alias-border-l2);\n color: var(--dsw-alias-label-primary);\n padding: 8px 16px;\n border-radius: 8px;\n font-size: 13px;\n z-index: 2147483000;\n max-width: 70vw;\n}\n.kb-modal-backdrop {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.5);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 2147482900;\n}\n.kb-modal {\n width: 460px;\n max-width: 90vw;\n max-height: 85vh;\n overflow-y: auto;\n background: var(--dsw-alias-bg-layer-1);\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 12px;\n padding: 18px;\n color: var(--dsw-alias-label-primary);\n}\n.kb-modal-wide { width: 640px; }\n/* Task-detail bottom panel: docks at the bottom of the board page, fills the\n full board width and exactly half of the board height (the lanes column\n above flexes to the remaining space). */\n.kb-detail-panel {\n flex: 0 0 50%;\n min-height: 0;\n display: flex;\n flex-direction: column;\n margin-top: 10px;\n background: var(--dsw-alias-bg-layer-2);\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 12px;\n overflow: hidden;\n color: var(--dsw-alias-label-primary);\n}\n.kb-detail-panel-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n padding: 10px 14px;\n border-bottom: 1px solid rgba(128, 128, 128, 0.2);\n flex: 0 0 auto;\n}\n.kb-detail-panel-head .kb-modal-title { margin-bottom: 0; }\n.kb-detail-panel-body {\n flex: 1 1 auto;\n min-height: 0;\n overflow-y: auto;\n padding: 14px;\n}\n.kb-detail-close {\n flex: none;\n width: 28px;\n height: 28px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: 1px solid transparent;\n border-radius: 6px;\n background: transparent;\n color: inherit;\n font-size: 15px;\n line-height: 1;\n opacity: 0.75;\n cursor: pointer;\n}\n.kb-detail-close:hover { background: rgba(128, 128, 128, 0.18); opacity: 1; }\n.kb-modal-title { font-size: 16px; font-weight: 700; margin-bottom: 14px; display: flex; align-items: center; gap: 10px; }\n.kb-field { display: block; margin-bottom: 12px; }\n.kb-field > span { display: block; font-size: 12px; opacity: 0.75; margin-bottom: 4px; }\n.kb-field-hint {\n display: block;\n font-size: 11px;\n opacity: 0.55;\n margin-top: 4px;\n}\n.kb-field-hint-skill {\n opacity: 0.9;\n color: #a78bfa;\n}\n.kb-skill-menu-wrap { position: relative; }\n.kb-skill-menu {\n position: absolute;\n top: 100%;\n left: 0;\n right: 0;\n z-index: 20;\n margin: 2px 0 0;\n padding: 4px;\n list-style: none;\n max-height: 180px;\n overflow-y: auto;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-overlay);\n color: var(--dsw-alias-label-primary);\n box-shadow: 0 6px 20px rgba(0, 0, 0, 0.35);\n}\n.kb-skill-menu-item {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 6px 8px;\n border-radius: 6px;\n cursor: pointer;\n font-size: 12px;\n color: var(--dsw-alias-label-primary);\n}\n.kb-skill-menu-item-active { background: var(--dsw-alias-interactive-bg-hover); }\n.kb-skill-menu-name { font-weight: 600; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }\n.kb-skill-menu-desc {\n color: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.kb-field-row { display: flex; align-items: center; gap: 10px; justify-content: space-between; }\n/* Field labels in a row never wrap; selects get the remaining fixed width. */\n.kb-field-row > span { white-space: nowrap; flex-shrink: 0; margin-bottom: 0; }\n.kb-input {\n width: 100%;\n box-sizing: border-box;\n padding: 7px 10px;\n border-radius: 6px;\n /* Form controls follow the shell theme: DSH's input surface token plus a\n theme-tuned border; the fallbacks cover shells without these tokens. */\n border: 1px solid var(--dsw-alias-border-l2-darkmode-thin, var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35)));\n background: var(--dsw-specific-input-major, var(--dsw-alias-bg-layer-1, rgba(128, 128, 128, 0.1)));\n color: var(--dsw-alias-label-primary, inherit);\n font-size: 13px;\n}\n.kb-input-number { width: 90px; }\n/* All settings selects share one width so the column looks even. */\n.kb-settings select.kb-input { width: 200px; flex: 0 0 200px; }\n.kb-textarea { min-height: 110px; resize: vertical; font-family: inherit; }\n.kb-modal-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 14px; }\n.kb-detail-section { margin-bottom: 12px; }\n.kb-detail-label { font-size: 12px; opacity: 0.65; margin-bottom: 4px; }\n.kb-detail-skill {\n display: inline-block;\n font-size: 11px;\n background: rgba(139, 92, 246, 0.22);\n border-radius: 4px;\n padding: 1px 6px;\n margin-bottom: 4px;\n}\n.kb-detail-text { white-space: pre-wrap; word-break: break-all; font-size: 13px; }\n.kb-detail-error .kb-detail-text { color: #f87171; }\n.kb-detail-status {\n font-size: 12px;\n font-weight: 600;\n border-radius: 4px;\n padding: 1px 8px;\n background: rgba(128, 128, 128, 0.2);\n}\n.kb-status-running, .kb-status-merging { background: rgba(59, 130, 246, 0.3) !important; }\n.kb-status-error { background: rgba(239, 68, 68, 0.3) !important; color: #f87171; }\n.kb-status-merged { background: rgba(34, 197, 94, 0.3) !important; }\n.kb-plan-summary { font-size: 13px; margin-bottom: 10px; }\n/* Phase cards: each plan phase is a card with an ordered number (kept from the\n parent <ol>) and three labeled sections (goal / sessions / conclusion) split\n by section titles and thin divider lines. */\n.kb-plan-phases { margin: 0; padding-left: 24px; }\n.kb-plan-phase {\n margin-bottom: 10px;\n padding: 8px 10px;\n background: rgba(128, 128, 128, 0.08);\n border: 1px solid rgba(128, 128, 128, 0.2);\n border-radius: 8px;\n}\n.kb-plan-phase-head {\n display: flex;\n gap: 8px;\n align-items: baseline;\n padding-bottom: 6px;\n margin-bottom: 6px;\n border-bottom: 1px solid rgba(128, 128, 128, 0.16);\n}\n.kb-plan-phase-title { font-weight: 600; font-size: 13px; }\n.kb-plan-phase-id { font-size: 11px; opacity: 0.5; }\n.kb-plan-phase-body { display: flex; flex-direction: column; }\n/* Section isolation: label on top, thin divider between adjacent sections. */\n.kb-plan-phase-section { display: flex; flex-direction: column; }\n.kb-plan-phase-section + .kb-plan-phase-section {\n margin-top: 6px;\n padding-top: 6px;\n border-top: 1px solid rgba(128, 128, 128, 0.14);\n}\n.kb-plan-phase-section-title { font-size: 11px; opacity: 0.55; margin-bottom: 3px; }\n.kb-plan-phase-goal { font-size: 12px; opacity: 0.85; white-space: pre-wrap; word-break: break-all; }\n.kb-plan-phase-sessions { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }\n.kb-plan-phase-conclusion { font-size: 12px; opacity: 0.85; white-space: pre-wrap; word-break: break-all; }\n.kb-plan-phase-conclusion-item + .kb-plan-phase-conclusion-item { margin-top: 6px; }\n/* Running phase keeps the blue highlight; completed phases get success color. */\n.kb-plan-phase-current { border-color: rgba(59, 130, 246, 0.6); background: rgba(59, 130, 246, 0.08); }\n.kb-plan-phase-current .kb-plan-phase-title { color: #60a5fa; }\n.kb-plan-phase-completed { border-color: rgba(34, 197, 94, 0.45); background: rgba(34, 197, 94, 0.06); }\n.kb-plan-phase-completed .kb-plan-phase-title { color: #4ade80; }\n.kb-session-links { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }\n.kb-settings { display: flex; flex-direction: column; gap: 12px; max-width: 420px; }\n.kb-settings-saved { font-size: 12px; color: #4ade80; }\n.kb-settings-group { border-top: 1px solid rgba(128, 128, 128, 0.22); padding-top: 12px; }\n.kb-settings-group-title { font-size: 12px; font-weight: 600; opacity: 0.9; margin-bottom: 10px; }\n/* Settings card (设置 → 插件配置): mirrors the built-in plugin cards\n (dsh-client-ui-settings-plugins PluginCard) — same surface, border, head\n layout, and disclosure chevron, spelled with kb-settings-card prefixed\n classes. The prefix deliberately does NOT reuse the board's `.kb-card*`\n classes: the board card carries its own padding/cursor/user-select, and\n sharing the root class would leak those onto the settings card. */\n.kb-settings-card {\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-3);\n border-radius: 12px;\n list-style: none;\n transition: border-color .16s, background .16s;\n}\n.kb-settings-card:hover { border-color: var(--dsw-alias-label-dimmed); }\n.kb-settings-card[data-open] {\n background: var(--dsw-alias-bg-layer-2);\n border-color: var(--dsw-alias-label-dimmed);\n}\n.kb-settings-card-head {\n appearance: none;\n width: 100%;\n font: inherit;\n color: inherit;\n text-align: left;\n cursor: pointer;\n background: transparent;\n border: 0;\n border-radius: 12px;\n align-items: center;\n gap: 12px;\n padding: 14px 16px;\n display: flex;\n}\n.kb-settings-card-head:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: -2px; }\n.kb-settings-card-headText { flex-direction: column; flex: 1; gap: 4px; min-width: 0; display: flex; }\n.kb-settings-card-title { color: var(--dsw-alias-label-primary); font-size: 15px; font-weight: 600; line-height: 1.4; }\n.kb-settings-card-sub { color: var(--dsw-alias-label-tertiary); font-size: 13px; line-height: 1.5; }\n.kb-settings-card-chevron {\n color: var(--dsw-alias-label-tertiary);\n flex: none;\n transition: transform .16s;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.kb-settings-card-chevron[data-open] { transform: rotate(180deg); }\n.kb-settings-card-body { border-top: 1px solid var(--dsw-alias-border-l2); margin: 0 16px; padding: 12px 0 8px; }\n.kb-footer-action {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n width: 32px;\n height: 32px;\n border-radius: 6px;\n border: none;\n background: transparent;\n /* same foreground as the settings trigger (its trigger css uses this var) */\n color: var(--dsw-alias-label-primary, inherit);\n cursor: pointer;\n}\n.kb-footer-action:hover { background: rgba(128, 128, 128, 0.18); }\n.kb-footer-action-active { background: rgba(59, 130, 246, 0.25); }\n/* Wide mode borrows the settings trigger's metrics (34px tall, 14px label,\n 18px icon) so both controls read as siblings on the same line. */\n.kb-footer-action-wide {\n width: auto;\n height: 34px;\n padding: 0 8px;\n border-radius: 12px;\n font-size: 14px;\n}\n.kb-footer-action-wide .kb-footer-label {\n font-size: 14px;\n line-height: 22px;\n}\n/* Theme following is handled by the --dsw-alias-* tokens above: they change\n value with the shell's selected light/dark appearance, so no OS-level\n prefers-color-scheme override is needed (and one would fight the app\n theme when the two disagree). */\n/* Same-line placement WITHOUT moving any DOM: the sidebar foot area becomes\n a reversed flex row, so its LAST child (the settings area) renders on the\n LEFT and the FIRST child (footer actions = the kanban button) on the RIGHT\n — exactly \"settings, then kanban\". The settings trigger flexes to fill the\n remaining width; the button keeps its content width. */\n.kb-foot-row {\n display: flex;\n flex-direction: row-reverse;\n align-items: stretch;\n gap: 4px;\n}\n.kb-foot-row > div:first-child {\n flex: none;\n width: auto;\n min-width: 0;\n display: flex;\n align-items: center;\n}\n.kb-foot-row > div:last-child {\n flex: 1;\n min-width: 0;\n}\n/* Rail (collapsed sidebar): column-reverse stacks the settings circle on top\n and the kanban icon below it, both centered; the kanban button borrows the\n settings trigger's rail chrome (36px circle) so the two match. */\n.kb-foot-rail {\n flex-direction: column-reverse;\n align-items: center;\n}\n.kb-foot-rail .kb-footer-action {\n width: 36px;\n height: 36px;\n border-radius: 50%;\n margin: 8px 0 10px;\n}\n.kb-footer-label {\n max-width: 110px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n/* Narrow screens (mobile drawer / auto-collapse breakpoint): the drawer's\n foot area is ~256-280px but the footer actions row carries the kanban\n button PLUS mobile-nav's drawer actions (Files / session log), which\n together exceed the row and squeeze the settings trigger to zero width\n (row-reverse overflows left, clipping the button). Below the shell's\n auto-collapse breakpoint, stack the settings row above the actions and\n let the actions wrap so nothing is squeezed or clipped. */\n@media (max-width: 1023px) {\n .kb-foot-row {\n flex-direction: column-reverse;\n align-items: stretch;\n }\n .kb-foot-row > div:first-child {\n flex: none;\n width: 100%;\n display: flex;\n flex-wrap: wrap;\n justify-content: flex-start;\n gap: 4px;\n padding-top: 4px;\n }\n .kb-foot-row > div:last-child {\n flex: none;\n width: 100%;\n }\n /* The footer-actions entries are display:contents wrappers, so the kanban\n button itself is a flex item of the wrapping actions row; pull it to the\n first wrap position so it sits directly below the settings row (the\n mobile-nav drawer actions follow on their own wrapped line). */\n .kb-foot-row .kb-footer-action {\n order: -1;\n }\n}";
|
|
398
|
+
const tagId = "@fonlan/dsh-task-kanban/board.css#bcd6e906";
|
|
374
399
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
375
400
|
const tag = document.createElement("style");
|
|
376
401
|
tag.dataset.plugin = "@fonlan/dsh-task-kanban";
|
|
@@ -536,11 +561,11 @@ window.__ModuleLoader__.load({
|
|
|
536
561
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
537
562
|
className: "kb-board",
|
|
538
563
|
children: [
|
|
539
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
564
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
540
565
|
className: "kb-board-header",
|
|
541
|
-
children:
|
|
566
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
542
567
|
className: "kb-board-title",
|
|
543
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
568
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
544
569
|
className: "kb-ws-select",
|
|
545
570
|
value: selectedPath,
|
|
546
571
|
onChange: (e) => {
|
|
@@ -553,16 +578,13 @@ window.__ModuleLoader__.load({
|
|
|
553
578
|
value: w.path,
|
|
554
579
|
children: w.title
|
|
555
580
|
}, w.workspaceId))
|
|
556
|
-
})
|
|
557
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
558
|
-
className: "kb-board-actions",
|
|
559
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
581
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
560
582
|
type: "button",
|
|
561
583
|
className: "kb-btn kb-btn-primary",
|
|
562
584
|
onClick: () => setNewTaskOpen(true),
|
|
563
585
|
children: t("newTask")
|
|
564
|
-
})
|
|
565
|
-
})
|
|
586
|
+
})]
|
|
587
|
+
})
|
|
566
588
|
}),
|
|
567
589
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
568
590
|
className: "kb-lanes",
|
|
@@ -696,9 +718,6 @@ window.__ModuleLoader__.load({
|
|
|
696
718
|
}).catch(() => {
|
|
697
719
|
if (alive) setModels([]);
|
|
698
720
|
});
|
|
699
|
-
api.settingsGet().then((s) => {
|
|
700
|
-
if (alive && s.defaultModel !== "") setModel(s.defaultModel);
|
|
701
|
-
}).catch(() => void 0);
|
|
702
721
|
return () => {
|
|
703
722
|
alive = false;
|
|
704
723
|
};
|
|
@@ -965,30 +984,12 @@ window.__ModuleLoader__.load({
|
|
|
965
984
|
children: card.plan.summary
|
|
966
985
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ol", {
|
|
967
986
|
className: "kb-plan-phases",
|
|
968
|
-
children: card.plan.phases.map((phase, i) => /* @__PURE__ */ (0, react_jsx_runtime.
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
className: "kb-plan-phase-title",
|
|
975
|
-
children: phase.title
|
|
976
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
977
|
-
className: "kb-plan-phase-id",
|
|
978
|
-
children: phase.id
|
|
979
|
-
})]
|
|
980
|
-
}),
|
|
981
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
982
|
-
className: "kb-plan-phase-goal",
|
|
983
|
-
children: phase.goal
|
|
984
|
-
}),
|
|
985
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PhaseSessions, {
|
|
986
|
-
phaseIndex: i,
|
|
987
|
-
card,
|
|
988
|
-
t,
|
|
989
|
-
onOpenSession
|
|
990
|
-
})
|
|
991
|
-
]
|
|
987
|
+
children: card.plan.phases.map((phase, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PhaseCard, {
|
|
988
|
+
phase,
|
|
989
|
+
phaseIndex: i,
|
|
990
|
+
card,
|
|
991
|
+
t,
|
|
992
|
+
onOpenSession
|
|
992
993
|
}, phase.id))
|
|
993
994
|
})]
|
|
994
995
|
})]
|
|
@@ -1078,26 +1079,77 @@ window.__ModuleLoader__.load({
|
|
|
1078
1079
|
})]
|
|
1079
1080
|
});
|
|
1080
1081
|
}
|
|
1081
|
-
|
|
1082
|
+
/** One plan phase rendered as a card with goal / sessions / conclusion sections. */
|
|
1083
|
+
function PhaseCard({ phase, phaseIndex, card, t, onOpenSession }) {
|
|
1082
1084
|
const attempts = card.sessions.phases.filter((a) => a.phaseIndex === phaseIndex);
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
children:
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
]
|
|
1097
|
-
}), a.summary !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1098
|
-
className: "kb-session-summary",
|
|
1099
|
-
children: a.summary
|
|
1085
|
+
const summaries = attempts.filter((a) => a.summary !== void 0 && a.summary.trim() !== "").map((a) => a.summary);
|
|
1086
|
+
const isCurrent = phaseIndex === card.currentPhase && card.status === "running";
|
|
1087
|
+
const isCompleted = summaries.length > 0;
|
|
1088
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
1089
|
+
className: "kb-plan-phase" + (isCurrent ? " kb-plan-phase-current" : "") + (isCompleted ? " kb-plan-phase-completed" : ""),
|
|
1090
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1091
|
+
className: "kb-plan-phase-head",
|
|
1092
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1093
|
+
className: "kb-plan-phase-title",
|
|
1094
|
+
children: phase.title
|
|
1095
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1096
|
+
className: "kb-plan-phase-id",
|
|
1097
|
+
children: phase.id
|
|
1100
1098
|
})]
|
|
1099
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1100
|
+
className: "kb-plan-phase-body",
|
|
1101
|
+
children: [
|
|
1102
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1103
|
+
className: "kb-plan-phase-section",
|
|
1104
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1105
|
+
className: "kb-plan-phase-section-title",
|
|
1106
|
+
children: t("phaseGoal")
|
|
1107
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1108
|
+
className: "kb-plan-phase-goal",
|
|
1109
|
+
children: phase.goal
|
|
1110
|
+
})]
|
|
1111
|
+
}),
|
|
1112
|
+
attempts.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1113
|
+
className: "kb-plan-phase-section",
|
|
1114
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1115
|
+
className: "kb-plan-phase-section-title",
|
|
1116
|
+
children: t("phaseSessions")
|
|
1117
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PhaseSessions, {
|
|
1118
|
+
attempts,
|
|
1119
|
+
t,
|
|
1120
|
+
onOpenSession
|
|
1121
|
+
})]
|
|
1122
|
+
}),
|
|
1123
|
+
summaries.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1124
|
+
className: "kb-plan-phase-section",
|
|
1125
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1126
|
+
className: "kb-plan-phase-section-title",
|
|
1127
|
+
children: t("phaseConclusion")
|
|
1128
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1129
|
+
className: "kb-plan-phase-conclusion",
|
|
1130
|
+
children: summaries.map((s, idx) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1131
|
+
className: "kb-plan-phase-conclusion-item",
|
|
1132
|
+
children: s
|
|
1133
|
+
}, idx))
|
|
1134
|
+
})]
|
|
1135
|
+
})
|
|
1136
|
+
]
|
|
1137
|
+
})]
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
/** Open-session buttons for one phase's attempts (retries append more). */
|
|
1141
|
+
function PhaseSessions({ attempts, t, onOpenSession }) {
|
|
1142
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1143
|
+
className: "kb-plan-phase-sessions",
|
|
1144
|
+
children: attempts.map((a) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1145
|
+
type: "button",
|
|
1146
|
+
className: "kb-btn kb-btn-small",
|
|
1147
|
+
onClick: () => onOpenSession(a.sessionId),
|
|
1148
|
+
children: [
|
|
1149
|
+
t("openSession"),
|
|
1150
|
+
" · ",
|
|
1151
|
+
a.sessionId.slice(0, 13)
|
|
1152
|
+
]
|
|
1101
1153
|
}, a.sessionId))
|
|
1102
1154
|
});
|
|
1103
1155
|
}
|
|
@@ -1208,80 +1260,279 @@ window.__ModuleLoader__.load({
|
|
|
1208
1260
|
})]
|
|
1209
1261
|
});
|
|
1210
1262
|
}
|
|
1211
|
-
/**
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1263
|
+
/** Fallback shape while settings load (server always resolves the full set). */
|
|
1264
|
+
const EMPTY_SETTINGS = {
|
|
1265
|
+
maxParallelWorkers: 1,
|
|
1266
|
+
refinementModel: "",
|
|
1267
|
+
refinementProvider: "",
|
|
1268
|
+
refinementReasoningEffort: "",
|
|
1269
|
+
refinementPreset: "",
|
|
1270
|
+
phaseModel: "",
|
|
1271
|
+
phaseProvider: "",
|
|
1272
|
+
phaseReasoningEffort: "",
|
|
1273
|
+
phasePreset: ""
|
|
1274
|
+
};
|
|
1275
|
+
/** The setting field names of one session-type group. */
|
|
1276
|
+
const GROUP_FIELDS = {
|
|
1277
|
+
refinement: {
|
|
1278
|
+
model: "refinementModel",
|
|
1279
|
+
provider: "refinementProvider",
|
|
1280
|
+
effort: "refinementReasoningEffort",
|
|
1281
|
+
preset: "refinementPreset"
|
|
1282
|
+
},
|
|
1283
|
+
phase: {
|
|
1284
|
+
model: "phaseModel",
|
|
1285
|
+
provider: "phaseProvider",
|
|
1286
|
+
effort: "phaseReasoningEffort",
|
|
1287
|
+
preset: "phasePreset"
|
|
1288
|
+
}
|
|
1289
|
+
};
|
|
1290
|
+
/** Model / reasoning effort / agent preset defaults for one session type. */
|
|
1291
|
+
function SessionDefaultsGroup({ kind, settings, models, presets, efforts, tr, onCommit, loadEfforts }) {
|
|
1292
|
+
const fields = GROUP_FIELDS[kind];
|
|
1293
|
+
const model = settings[fields.model];
|
|
1294
|
+
const provider = settings[fields.provider];
|
|
1295
|
+
const effort = settings[fields.effort];
|
|
1296
|
+
const preset = settings[fields.preset];
|
|
1297
|
+
const effortKey = model !== "" && provider !== "" ? `${provider}/${model}` : "";
|
|
1298
|
+
const effortOptions = effortKey !== "" ? efforts[effortKey]?.efforts ?? [] : [];
|
|
1299
|
+
const handleModel = (id) => {
|
|
1300
|
+
const nextProvider = models.find((m) => m.id === id)?.provider ?? "";
|
|
1301
|
+
onCommit({
|
|
1302
|
+
[fields.model]: id,
|
|
1303
|
+
[fields.provider]: nextProvider
|
|
1304
|
+
});
|
|
1305
|
+
if (id !== "" && nextProvider !== "") loadEfforts(nextProvider, id);
|
|
1239
1306
|
};
|
|
1240
1307
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1241
|
-
className: "kb-settings",
|
|
1308
|
+
className: "kb-settings-group",
|
|
1242
1309
|
children: [
|
|
1243
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
1244
|
-
className: "kb-
|
|
1245
|
-
children:
|
|
1246
|
-
className: "kb-input kb-input-number",
|
|
1247
|
-
type: "number",
|
|
1248
|
-
min: 1,
|
|
1249
|
-
step: 1,
|
|
1250
|
-
value: settings.maxParallelWorkers,
|
|
1251
|
-
onChange: (e) => {
|
|
1252
|
-
const value = Math.max(1, Math.floor(Number(e.target.value) || 1));
|
|
1253
|
-
setSettings((s) => ({
|
|
1254
|
-
...s,
|
|
1255
|
-
maxParallelWorkers: value
|
|
1256
|
-
}));
|
|
1257
|
-
commit({ maxParallelWorkers: value });
|
|
1258
|
-
}
|
|
1259
|
-
})]
|
|
1310
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1311
|
+
className: "kb-settings-group-title",
|
|
1312
|
+
children: tr(kind === "refinement" ? "settingsRefineGroup" : "settingsPhaseGroup")
|
|
1260
1313
|
}),
|
|
1261
1314
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1262
1315
|
className: "kb-field kb-field-row",
|
|
1263
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: tr("
|
|
1316
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: tr("model") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1264
1317
|
className: "kb-input",
|
|
1265
|
-
value:
|
|
1266
|
-
onChange: (e) =>
|
|
1267
|
-
commit({ defaultModel: e.target.value });
|
|
1268
|
-
},
|
|
1318
|
+
value: model,
|
|
1319
|
+
onChange: (e) => handleModel(e.target.value),
|
|
1269
1320
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1270
1321
|
value: "",
|
|
1271
|
-
children: "
|
|
1322
|
+
children: tr("defaultOption")
|
|
1272
1323
|
}), models.map((m) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1273
1324
|
value: m.id,
|
|
1274
1325
|
children: m.name ?? m.id
|
|
1275
1326
|
}, m.provider + "/" + m.id))]
|
|
1276
1327
|
})]
|
|
1277
1328
|
}),
|
|
1278
|
-
|
|
1279
|
-
className: "kb-
|
|
1280
|
-
children: tr("
|
|
1329
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1330
|
+
className: "kb-field kb-field-row",
|
|
1331
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: tr("reasoningEffort") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1332
|
+
className: "kb-input",
|
|
1333
|
+
value: effort,
|
|
1334
|
+
disabled: model === "",
|
|
1335
|
+
onChange: (e) => void onCommit({ [fields.effort]: e.target.value }),
|
|
1336
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1337
|
+
value: "",
|
|
1338
|
+
children: tr("defaultOption")
|
|
1339
|
+
}), effortOptions.map((o) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1340
|
+
value: o.id,
|
|
1341
|
+
children: o.name
|
|
1342
|
+
}, o.id))]
|
|
1343
|
+
})]
|
|
1344
|
+
}),
|
|
1345
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1346
|
+
className: "kb-field kb-field-row",
|
|
1347
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: tr("agentPreset") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1348
|
+
className: "kb-input",
|
|
1349
|
+
value: preset,
|
|
1350
|
+
onChange: (e) => void onCommit({ [fields.preset]: e.target.value }),
|
|
1351
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1352
|
+
value: "",
|
|
1353
|
+
children: tr("defaultOption")
|
|
1354
|
+
}), presets.map((p) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1355
|
+
value: p.id,
|
|
1356
|
+
children: p.name ?? p.id
|
|
1357
|
+
}, p.id))]
|
|
1358
|
+
})]
|
|
1281
1359
|
})
|
|
1282
1360
|
]
|
|
1283
1361
|
});
|
|
1284
1362
|
}
|
|
1363
|
+
/** Plugin settings card (设置 → 插件配置): workers + default model + per-session-type defaults. */
|
|
1364
|
+
function KanbanSettingsCard({ t }) {
|
|
1365
|
+
const tr = t ?? ((key) => key);
|
|
1366
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
1367
|
+
const [settings, setSettings] = (0, react.useState)(EMPTY_SETTINGS);
|
|
1368
|
+
const [models, setModels] = (0, react.useState)([]);
|
|
1369
|
+
const [presets, setPresets] = (0, react.useState)([]);
|
|
1370
|
+
const [efforts, setEfforts] = (0, react.useState)({});
|
|
1371
|
+
const [saved, setSaved] = (0, react.useState)(false);
|
|
1372
|
+
const commit = (0, react.useCallback)(async (patch) => {
|
|
1373
|
+
try {
|
|
1374
|
+
const next = await api.settingsSet(patch);
|
|
1375
|
+
setSettings(next);
|
|
1376
|
+
setSaved(true);
|
|
1377
|
+
window.setTimeout(() => setSaved(false), 1500);
|
|
1378
|
+
} catch {}
|
|
1379
|
+
}, []);
|
|
1380
|
+
const settingsRef = (0, react.useRef)(settings);
|
|
1381
|
+
settingsRef.current = settings;
|
|
1382
|
+
const commitRef = (0, react.useRef)(commit);
|
|
1383
|
+
commitRef.current = commit;
|
|
1384
|
+
(0, react.useEffect)(() => {
|
|
1385
|
+
let alive = true;
|
|
1386
|
+
api.settingsGet().then((s) => {
|
|
1387
|
+
if (alive) setSettings(s);
|
|
1388
|
+
}).catch(() => void 0);
|
|
1389
|
+
api.models().then((list) => {
|
|
1390
|
+
if (alive) setModels(list);
|
|
1391
|
+
}).catch(() => void 0);
|
|
1392
|
+
api.presets().then((list) => {
|
|
1393
|
+
if (alive) setPresets(list);
|
|
1394
|
+
}).catch(() => void 0);
|
|
1395
|
+
return () => {
|
|
1396
|
+
alive = false;
|
|
1397
|
+
};
|
|
1398
|
+
}, []);
|
|
1399
|
+
/** Reasoning routes already requested (dedupe across mount + model changes). */
|
|
1400
|
+
const effortFetched = (0, react.useRef)(/* @__PURE__ */ new Set());
|
|
1401
|
+
/**
|
|
1402
|
+
* Fetch the reasoning efforts of one model route (once per route). A failed
|
|
1403
|
+
* fetch is marked `failed` so the stored effort is preserved (see the reset
|
|
1404
|
+
* effect below) instead of being wiped by a transient error.
|
|
1405
|
+
*/
|
|
1406
|
+
const loadEfforts = (0, react.useCallback)((provider, model) => {
|
|
1407
|
+
const key = `${provider}/${model}`;
|
|
1408
|
+
if (effortFetched.current.has(key)) return;
|
|
1409
|
+
effortFetched.current.add(key);
|
|
1410
|
+
api.reasoningOptions(provider, model).then((opts) => setEfforts((prev) => ({
|
|
1411
|
+
...prev,
|
|
1412
|
+
[key]: opts
|
|
1413
|
+
}))).catch(() => setEfforts((prev) => ({
|
|
1414
|
+
...prev,
|
|
1415
|
+
[key]: {
|
|
1416
|
+
efforts: [],
|
|
1417
|
+
failed: true
|
|
1418
|
+
}
|
|
1419
|
+
})));
|
|
1420
|
+
}, []);
|
|
1421
|
+
(0, react.useEffect)(() => {
|
|
1422
|
+
for (const kind of ["refinement", "phase"]) {
|
|
1423
|
+
const fields = GROUP_FIELDS[kind];
|
|
1424
|
+
const model = settings[fields.model];
|
|
1425
|
+
const provider = settings[fields.provider];
|
|
1426
|
+
if (model !== "" && provider !== "") loadEfforts(provider, model);
|
|
1427
|
+
}
|
|
1428
|
+
}, [
|
|
1429
|
+
settings.refinementModel,
|
|
1430
|
+
settings.refinementProvider,
|
|
1431
|
+
settings.phaseModel,
|
|
1432
|
+
settings.phaseProvider,
|
|
1433
|
+
loadEfforts
|
|
1434
|
+
]);
|
|
1435
|
+
(0, react.useEffect)(() => {
|
|
1436
|
+
const s = settingsRef.current;
|
|
1437
|
+
for (const kind of ["refinement", "phase"]) {
|
|
1438
|
+
const fields = GROUP_FIELDS[kind];
|
|
1439
|
+
const model = s[fields.model];
|
|
1440
|
+
const provider = s[fields.provider];
|
|
1441
|
+
const effort = s[fields.effort];
|
|
1442
|
+
if (effort === "" || model === "" || provider === "") continue;
|
|
1443
|
+
const opts = efforts[`${provider}/${model}`];
|
|
1444
|
+
if (opts === void 0 || opts.failed === true) continue;
|
|
1445
|
+
if (!opts.efforts.some((o) => o.id === effort)) commitRef.current({ [fields.effort]: "" });
|
|
1446
|
+
}
|
|
1447
|
+
}, [efforts]);
|
|
1448
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
1449
|
+
className: "kb-settings-card",
|
|
1450
|
+
"data-open": open ? "" : void 0,
|
|
1451
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1452
|
+
type: "button",
|
|
1453
|
+
className: "kb-settings-card-head",
|
|
1454
|
+
"aria-expanded": open,
|
|
1455
|
+
"aria-label": (open ? tr("settingsCollapse") : tr("settingsExpand")) + ":" + tr("settingsTitle"),
|
|
1456
|
+
onClick: () => setOpen(!open),
|
|
1457
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1458
|
+
className: "kb-settings-card-headText",
|
|
1459
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1460
|
+
className: "kb-settings-card-title",
|
|
1461
|
+
children: tr("settingsTitle")
|
|
1462
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1463
|
+
className: "kb-settings-card-sub",
|
|
1464
|
+
children: tr("settingsDescription")
|
|
1465
|
+
})]
|
|
1466
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1467
|
+
className: "kb-settings-card-chevron",
|
|
1468
|
+
"data-open": open ? "" : void 0,
|
|
1469
|
+
"aria-hidden": "true",
|
|
1470
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
1471
|
+
width: "14",
|
|
1472
|
+
height: "14",
|
|
1473
|
+
viewBox: "0 0 14 14",
|
|
1474
|
+
fill: "none",
|
|
1475
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
1476
|
+
d: "M3.5 5.25L7 8.75L10.5 5.25",
|
|
1477
|
+
stroke: "currentColor",
|
|
1478
|
+
strokeWidth: "1.5",
|
|
1479
|
+
strokeLinecap: "round",
|
|
1480
|
+
strokeLinejoin: "round"
|
|
1481
|
+
})
|
|
1482
|
+
})
|
|
1483
|
+
})]
|
|
1484
|
+
}), open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1485
|
+
className: "kb-settings-card-body",
|
|
1486
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1487
|
+
className: "kb-settings",
|
|
1488
|
+
children: [
|
|
1489
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1490
|
+
className: "kb-field kb-field-row",
|
|
1491
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: tr("maxParallelWorkers") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1492
|
+
className: "kb-input kb-input-number",
|
|
1493
|
+
type: "number",
|
|
1494
|
+
min: 1,
|
|
1495
|
+
step: 1,
|
|
1496
|
+
value: settings.maxParallelWorkers,
|
|
1497
|
+
onChange: (e) => {
|
|
1498
|
+
const value = Math.max(1, Math.floor(Number(e.target.value) || 1));
|
|
1499
|
+
setSettings((s) => ({
|
|
1500
|
+
...s,
|
|
1501
|
+
maxParallelWorkers: value
|
|
1502
|
+
}));
|
|
1503
|
+
commit({ maxParallelWorkers: value });
|
|
1504
|
+
}
|
|
1505
|
+
})]
|
|
1506
|
+
}),
|
|
1507
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionDefaultsGroup, {
|
|
1508
|
+
kind: "refinement",
|
|
1509
|
+
settings,
|
|
1510
|
+
models,
|
|
1511
|
+
presets,
|
|
1512
|
+
efforts,
|
|
1513
|
+
tr,
|
|
1514
|
+
onCommit: commit,
|
|
1515
|
+
loadEfforts
|
|
1516
|
+
}),
|
|
1517
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionDefaultsGroup, {
|
|
1518
|
+
kind: "phase",
|
|
1519
|
+
settings,
|
|
1520
|
+
models,
|
|
1521
|
+
presets,
|
|
1522
|
+
efforts,
|
|
1523
|
+
tr,
|
|
1524
|
+
onCommit: commit,
|
|
1525
|
+
loadEfforts
|
|
1526
|
+
}),
|
|
1527
|
+
saved && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1528
|
+
className: "kb-settings-saved",
|
|
1529
|
+
children: tr("saved")
|
|
1530
|
+
})
|
|
1531
|
+
]
|
|
1532
|
+
})
|
|
1533
|
+
})]
|
|
1534
|
+
});
|
|
1535
|
+
}
|
|
1285
1536
|
//#endregion
|
|
1286
1537
|
//#region src/client/index.tsx
|
|
1287
1538
|
/** Services required before mounting (provided by the client runtime). */
|
|
@@ -1311,13 +1562,11 @@ window.__ModuleLoader__.load({
|
|
|
1311
1562
|
label: () => t("kanban"),
|
|
1312
1563
|
locale: LOCALE_NS
|
|
1313
1564
|
}, KanbanFooterButton));
|
|
1314
|
-
ctx.slots.inject("settings.
|
|
1315
|
-
name: "settings.
|
|
1316
|
-
|
|
1317
|
-
order: 200,
|
|
1318
|
-
label: () => t("settingsTitle"),
|
|
1565
|
+
ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
|
|
1566
|
+
name: "settings.plugin.item",
|
|
1567
|
+
key: "task-kanban",
|
|
1319
1568
|
locale: LOCALE_NS
|
|
1320
|
-
},
|
|
1569
|
+
}, KanbanSettingsCard));
|
|
1321
1570
|
}
|
|
1322
1571
|
//#endregion
|
|
1323
1572
|
exports.apply = apply;
|