@goodandready/dsh-kanban 0.2.33 → 0.2.35

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/lib/client.js CHANGED
@@ -1,11 +1,3 @@
1
- // Browser half of dsh-kanban: board screen and settings card.
2
- //
3
- // The identifier below must match `name` in package.json and `name:` in
4
- // cordis.patch.yml. A mismatch produces NO log error: the loader silently fails
5
- // to resolve the package, the server half works, and the UI never appears.
6
- //
7
- // This module is loaded by ModuleLoader and cannot import repository files;
8
- // pure helpers live here and are exported as `helpers` for node:vm testing.
9
1
  window.__ModuleLoader__.load({
10
2
  id: '@goodandready/dsh-kanban',
11
3
  factory: (require) => {
@@ -16,17 +8,11 @@ window.__ModuleLoader__.load({
16
8
  const ReactDOM = require('react-dom/client')
17
9
 
18
10
  const NS = 'dsh-kanban'
19
- // Plugins page row seat (DSH 0.1.6-alpha.2): key = '<package name>#<row id>'.
20
11
  const PKG = '@goodandready/dsh-kanban'
21
12
  const ROW_ID = 'dsh-kanban'
22
13
  const ROW_CONFIG_KEY = PKG + '#' + ROW_ID
23
14
  const COLUMN_ORDER = ['backlog', 'in-progress', 'review', 'deploy', 'cleanup', 'done']
24
15
 
25
-
26
- /**
27
- * Neighbours for card repositioning. The store operates on relative neighbours
28
- * rather than numeric indexes: `afterId` above, `beforeId` below.
29
- */
30
16
  function neighboursFor(columnTasks, dragId, index) {
31
17
  const rest = (columnTasks || []).filter((t) => t.id !== dragId)
32
18
  const clamped = Math.max(0, Math.min(index, rest.length))
@@ -40,13 +26,6 @@ window.__ModuleLoader__.load({
40
26
  return (tasks || []).filter((t) => t.column === column)
41
27
  }
42
28
 
43
- /**
44
- * Task ordering within a column.
45
- *
46
- * `manual` — server-provided order: manual order with active tasks on top.
47
- * `old`/`new` sort by creation date while preserving project grouping
48
- * and "active work first".
49
- */
50
29
  function sortTasks(tasks, order) {
51
30
  const list = tasks || []
52
31
  if (order !== 'old' && order !== 'new') return list
@@ -67,22 +46,6 @@ window.__ModuleLoader__.load({
67
46
  return out
68
47
  }
69
48
 
70
- /**
71
- * Session task batch progress: sequence index and total count.
72
- *
73
- * Orders matching the agent execution order: active task first,
74
- * followed by queue time.
75
- *
76
- * @returns {Object<string, {at: number, total: number}>} keyed by task id
77
- */
78
- /**
79
- * Apply a single batch action across all selected tasks.
80
- *
81
- * Failures on individual tasks do not abort remaining tasks;
82
- * completed count is reported to the user.
83
- *
84
- * @returns {Promise<{done: number, failed: number}>}
85
- */
86
49
  async function applyToEach(ids, act) {
87
50
  let done = 0
88
51
  let failed = 0
@@ -92,12 +55,6 @@ window.__ModuleLoader__.load({
92
55
  return { done, failed }
93
56
  }
94
57
 
95
- /**
96
- * Remembered launch settings in browser localStorage.
97
- *
98
- * Stored per user browser rather than plugin settings. Handles storage
99
- * errors gracefully if localStorage is disabled or restricted.
100
- */
101
58
  const RECALL_KEY = 'dsh-kanban:launch:'
102
59
 
103
60
  function recallLaunch(board) {
@@ -111,16 +68,9 @@ window.__ModuleLoader__.load({
111
68
  function rememberLaunch(board, choice) {
112
69
  try {
113
70
  window.localStorage.setItem(RECALL_KEY + (board || 'main'), JSON.stringify(choice))
114
- } catch { /* storage unavailable */ }
71
+ } catch { }
115
72
  }
116
73
 
117
- /**
118
- * Re-apply remembered preset only if still available in active runtime.
119
- *
120
- * Avoids applying obsolete profiles or permission ranks removed from the build.
121
- *
122
- * @param {Array<string>} known valid choices
123
- */
124
74
  function stillOffered(value, known, fallback) {
125
75
  return typeof value === 'string' && (known || []).indexOf(value) >= 0 ? value : fallback
126
76
  }
@@ -142,14 +92,6 @@ window.__ModuleLoader__.load({
142
92
  return out
143
93
  }
144
94
 
145
- /**
146
- * Group column tasks by assignee.
147
- *
148
- * Unassigned group ('Nobody') is placed first to highlight available work,
149
- * followed alphabetically by assignee username.
150
- *
151
- * @returns {Array<{who: string, tasks: object[]}>}
152
- */
153
95
  function groupByAssignee(tasks) {
154
96
  const by = new Map()
155
97
  for (const task of tasks || []) {
@@ -168,11 +110,6 @@ window.__ModuleLoader__.load({
168
110
  return task.repo + '#' + task.issueNumber
169
111
  }
170
112
 
171
- /**
172
- * Project repository and issue number rendered separately.
173
- *
174
- * Issue number links directly to the remote issue.
175
- */
176
113
  function taskRef(task, t) {
177
114
  if (!task || !task.repo) return null
178
115
  const number = typeof task.issueNumber === 'number' ? '#' + task.issueNumber : ''
@@ -202,11 +139,6 @@ window.__ModuleLoader__.load({
202
139
  return parts.join(' · ')
203
140
  }
204
141
 
205
- /**
206
- * Compact plan summary line for card badge: "3 of 7 · writing tests".
207
- *
208
- * Only progress numbers and current item description are shown on cards.
209
- */
210
142
  function planLine(task, t) {
211
143
  const progress = task && task.plan && task.plan.progress
212
144
  if (!progress || !progress.total) return ''
@@ -215,7 +147,6 @@ window.__ModuleLoader__.load({
215
147
  return progress.text ? counter + ' · ' + progress.text : counter
216
148
  }
217
149
 
218
- /** Status badge text. Waiting indicator is rendered separately. */
219
150
  function stateLabel(task, t) {
220
151
  const state = task && task.state
221
152
  if (state === 'running') return t('state.running')
@@ -223,26 +154,12 @@ window.__ModuleLoader__.load({
223
154
  return ''
224
155
  }
225
156
 
226
- /**
227
- * Determine whether column is collapsed.
228
- *
229
- * Empty columns collapse automatically to save horizontal space;
230
- * explicit user toggle overrides automatic behavior.
231
- *
232
- * @param {object} column column data from board response
233
- * @param {object} manual map of column id -> boolean user preference
234
- */
235
157
  function isCollapsed(column, manual) {
236
158
  const own = manual && manual[column.id]
237
159
  if (own === true || own === false) return own
238
160
  return (column.count || 0) === 0
239
161
  }
240
162
 
241
- /**
242
- * Relative elapsed time string: "5m", "3h", "3d".
243
- *
244
- * Computed client-side with full ISO timestamp displayed on hover tooltip.
245
- */
246
163
  function agoText(now, then, t) {
247
164
  if (typeof then !== 'number' || then <= 0) return ''
248
165
  const delta = Math.max(0, now - then)
@@ -254,7 +171,6 @@ window.__ModuleLoader__.load({
254
171
  return t('time.day', { n: Math.floor(hours / 24) })
255
172
  }
256
173
 
257
- /** Date formatted without time. Year is included only when not the current year. */
258
174
  function shortDate(then) {
259
175
  if (typeof then !== 'number' || then <= 0) return ''
260
176
  try {
@@ -266,19 +182,11 @@ window.__ModuleLoader__.load({
266
182
  } catch { return '' }
267
183
  }
268
184
 
269
- /** Exact timestamp for tooltip. Empty tooltip if timestamp is invalid. */
270
185
  function exactAt(then) {
271
186
  if (typeof then !== 'number' || then <= 0) return ''
272
187
  try { return new Date(then).toLocaleString() } catch { return '' }
273
188
  }
274
189
 
275
- /**
276
- * Sync telemetry status message.
277
- *
278
- * Alerts user when synchronization has halted or encountered errors.
279
- *
280
- * @returns {{tone: 'ok'|'bad'|'idle', text: string, title: string}}
281
- */
282
190
  function syncLine(sync, now, t) {
283
191
  const state = (sync && sync.state) || 'never'
284
192
  if (state === 'running') return { tone: 'idle', text: t('sync.running'), title: '' }
@@ -303,13 +211,6 @@ window.__ModuleLoader__.load({
303
211
  }
304
212
  }
305
213
 
306
- /**
307
- * Accessible text color over label background color.
308
- *
309
- * Computes perceived brightness and picks black or white accordingly.
310
- *
311
- * @param {string} hex 6-character hex code without '#'
312
- */
313
214
  function readableOn(hex) {
314
215
  const n = parseInt(String(hex || ''), 16)
315
216
  if (!isFinite(n)) return ''
@@ -319,11 +220,6 @@ window.__ModuleLoader__.load({
319
220
  return (r * 299 + g * 587 + b * 114) / 1000 > 140 ? '#111' : '#fff'
320
221
  }
321
222
 
322
- /**
323
- * Color accent stripe on left border of card for priority.
324
- *
325
- * Only applies to `priority/*` labels. Tasks without priority display no stripe.
326
- */
327
223
  function priorityColor(task) {
328
224
  const values = (task && task.facets && task.facets.priority) || []
329
225
  for (const value of values) {
@@ -333,18 +229,12 @@ window.__ModuleLoader__.load({
333
229
  return ''
334
230
  }
335
231
 
336
- /** Tag style helper. Neutral style when color is not defined. */
337
232
  function tagStyle(task, name) {
338
233
  const hex = task && task.labelColors ? task.labelColors[name] : undefined
339
234
  if (!hex) return undefined
340
235
  return { background: '#' + hex, color: readableOn(hex), borderColor: 'transparent' }
341
236
  }
342
237
 
343
- /**
344
- * Render parsed task body tree safely using React elements.
345
- *
346
- * Constructs DOM elements rather than using dangerouslySetInnerHTML to prevent XSS.
347
- */
348
238
  function renderSpans(spans, keyBase) {
349
239
  return (spans || []).map((span, i) => {
350
240
  const key = keyBase + '-' + i
@@ -385,14 +275,6 @@ window.__ModuleLoader__.load({
385
275
  })
386
276
  }
387
277
 
388
- /**
389
- * Label namespace display title.
390
- *
391
- * Falls back to raw namespace name when no translation key exists.
392
- */
393
- /**
394
- * Column title: custom override if configured, otherwise localized default.
395
- */
396
278
  function columnTitle(col, t) {
397
279
  const own = col && col.name
398
280
  return own !== undefined && own !== '' ? own : t('column.' + (col && col.id))
@@ -404,11 +286,6 @@ window.__ModuleLoader__.load({
404
286
  return text === key ? ns : text
405
287
  }
406
288
 
407
- /**
408
- * Board title.
409
- *
410
- * Default 'main' and 'simple' boards use localized titles; custom board names are preserved verbatim.
411
- */
412
289
  function boardTitle(board, t) {
413
290
  const key = 'boardName.' + (board && board.id)
414
291
  const text = t(key)
@@ -434,15 +311,8 @@ window.__ModuleLoader__.load({
434
311
  }
435
312
  }
436
313
 
437
- /** Repository facet filter key matching backend dimension. */
438
314
  const REPO_FACET = 'repo'
439
315
 
440
- /**
441
- * Check whether task matches active facet filters.
442
- *
443
- * AND across namespaces, OR within each namespace.
444
- * Uses pre-parsed task.facets from server response.
445
- */
446
316
  function matchesFilters(task, selected) {
447
317
  const keys = Object.keys(selected || {})
448
318
  for (const ns of keys) {
@@ -456,14 +326,12 @@ window.__ModuleLoader__.load({
456
326
  return true
457
327
  }
458
328
 
459
- /** True if any filter is active. */
460
329
  function anySelected(selected) {
461
330
  const keys = Object.keys(selected || {})
462
331
  for (const ns of keys) if ((selected[ns] || []).length > 0) return true
463
332
  return false
464
333
  }
465
334
 
466
- /** Toggle filter value, returning a new map for React immutability. */
467
335
  function toggleValue(selected, ns, value) {
468
336
  const current = (selected || {})[ns] || []
469
337
  const next = current.indexOf(value) >= 0
@@ -475,7 +343,6 @@ window.__ModuleLoader__.load({
475
343
  return out
476
344
  }
477
345
 
478
- /** Human-readable explanation for empty state or error. */
479
346
  function errorKey(error) {
480
347
  switch (error) {
481
348
  case 'gitea-absent': return 'error.giteaAbsent'
@@ -492,11 +359,6 @@ window.__ModuleLoader__.load({
492
359
  }
493
360
  }
494
361
 
495
- /**
496
- * Open session conversation in chat panel.
497
- *
498
- * @returns {boolean} true if session opened successfully
499
- */
500
362
  function openSession(ctx, sessionId) {
501
363
  if (!sessionId) return false
502
364
  const sessions = ctx && typeof ctx.get === 'function' ? ctx.get('sessions') : undefined
@@ -509,7 +371,6 @@ window.__ModuleLoader__.load({
509
371
  }
510
372
  }
511
373
 
512
- /** Repository option label with open issue count; marks archived repositories. */
513
374
  function repoOption(r, t) {
514
375
  const full = r.fullName || ((r.owner || '') + '/' + (r.repo || ''))
515
376
  const parts = [full]
@@ -518,16 +379,12 @@ window.__ModuleLoader__.load({
518
379
  return parts.join(' ')
519
380
  }
520
381
 
521
- /** Parse 'owner/repo' string into separate parts. */
522
382
  function splitFullName(fullName) {
523
383
  const parts = String(fullName || '').split('/')
524
384
  if (parts.length !== 2 || parts[0] === '' || parts[1] === '') return undefined
525
385
  return { owner: parts[0], repo: parts[1] }
526
386
  }
527
387
 
528
- /**
529
- * Filter cards by client-side text search query: title, repo, issue number.
530
- */
531
388
  function matchesQuery(task, query) {
532
389
  const needle = String(query || '').trim().toLowerCase()
533
390
  if (needle === '') return true
@@ -540,7 +397,6 @@ window.__ModuleLoader__.load({
540
397
  return hay.indexOf(needle) >= 0
541
398
  }
542
399
 
543
- /** Retrieve session ID for header chip. */
544
400
  function chipSessionId(props, session) {
545
401
  return String((props && props.sessionId) || (session && (session.sessionId || session.id)) || '').trim()
546
402
  }
@@ -554,7 +410,6 @@ window.__ModuleLoader__.load({
554
410
  chipSessionId, matchesQuery, splitFullName, repoOption, openSession, COLUMN_ORDER,
555
411
  }
556
412
 
557
-
558
413
  function createTimeoutSignal(ms) {
559
414
  const sig = typeof AbortSignal !== 'undefined' ? AbortSignal : (typeof globalThis !== 'undefined' ? globalThis.AbortSignal : undefined)
560
415
  if (sig && typeof sig.timeout === 'function') {
@@ -684,188 +539,12 @@ window.__ModuleLoader__.load({
684
539
  try {
685
540
  const s = (ctx?.get && ctx.get('lanSettings')) || ctx?.configForms
686
541
  s?.describe?.()?.load?.()
687
- } catch (_) { /* clipboard read best-effort */ }
542
+ } catch (_) { }
688
543
  }, 1000)
689
544
  return () => clearTimer(timer)
690
545
  }
691
546
 
692
-
693
- const css =
694
- '.dkb-section-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:10px;padding:14px 16px;display:flex;flex-direction:column;gap:8px;margin-bottom:12px}' +'.dkb-section-title{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary);display:flex;align-items:center;justify-content:space-between;gap:8px;padding-bottom:4px;border-bottom:1px solid var(--dsw-alias-border-l2)}' +'.dkb-section-badge{font-size:11px;font-weight:500;padding:2px 8px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary)}' +'.dkb-section-badge-ok{border-color:var(--dsw-alias-state-success-secondary,#34d399);color:var(--dsw-alias-state-success-primary,#059669);background:var(--dsw-alias-state-success-secondary,rgba(16,185,129,0.1))}' +
695
- '.dkb-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none}' +
696
- '.dkb-cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}' +
697
- '.dkb-page{padding:0}' +
698
- '.dkb-page .dkb-body{border-top:none;margin:0}' +
699
- '.dkb-head{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;display:flex;align-items:center;gap:12px;padding:14px 16px}' +
700
- '.dkb-headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}' +
701
- '.dkb-title{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}' +
702
- '.dkb-sub{color:var(--dsw-alias-label-secondary);font-size:13px}' +
703
- '.dkb-chevron{color:var(--dsw-alias-label-secondary);display:inline-flex;align-items:center;justify-content:center;flex:none;transition:transform .16s}' +
704
- '.dkb-chevronOpen{transform:rotate(180deg)}' +
705
- '.dkb-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}' +
706
- '.dkb-group{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;padding:14px 0 2px}' +
707
- '.dkb-field{display:flex;flex-direction:column;gap:6px;padding:10px 0}' +
708
- '.dkb-label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}' +
709
- '.dkb-hint{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:1.5}' +
710
- '.dkb-input{height:34px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;box-sizing:border-box;width:100%}' +
711
- '.dkb-area{min-height:96px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);border-radius:8px;padding:8px 12px;font-size:13px;box-sizing:border-box;width:100%;resize:vertical;font:inherit}' +
712
- '.dkb-input:disabled,.dkb-area:disabled{opacity:.55}' +
713
- '.dkb-foot{border-top:1px solid var(--dsw-alias-border-l2);display:flex;justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px}' +
714
- '.dkb-footSplit{justify-content:space-between}' +
715
- '.dkb-footLeft{display:flex;align-items:center;gap:8px}' +
716
- '.dkb-note{min-width:0;flex:1;margin:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-secondary)}' +
717
- '.dkb-failed{min-width:0;color:var(--dsw-alias-state-error-primary);flex:1;margin:0;font-size:12px;line-height:1.5}' +
718
- '.dkb-save{appearance:none;font:inherit;cursor:pointer;border:1px solid transparent;border-radius:8px;padding:5px 14px;font-size:13px;background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}' +
719
- '.dkb-discard{appearance:none;font:inherit;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:5px 14px;font-size:13px;color:var(--dsw-alias-label-secondary);background:transparent}' +
720
- '.dkb-discard:disabled,.dkb-save:disabled{opacity:.4;cursor:default}' +
721
- '.dkb-entry-clone{align-items:center;gap:8px;display:flex}' +
722
- '.dkb-entry-clone[data-active]{font-weight:600}' +
723
- '.dkb-entryIcon{flex:none;justify-content:center;align-items:center;display:inline-flex}' +
724
- '.dkb-entryLabel{text-overflow:ellipsis;overflow:hidden}' +
725
- '[data-dsh-frame][data-sidebar-collapsed] .dkb-entryLabel{display:none}' +
726
- '[data-pane=conversation],[class*=centerCol]{position:relative}' +
727
- '[data-dsh-kanban-view]{z-index:60;background:var(--dsw-alias-bg-base);display:none;position:absolute;inset:0}' +
728
- 'html[data-dsh-kanban-active]:not([data-dsh-ssh-active]) [data-dsh-kanban-view]{display:block}' +
729
- 'html[data-dsh-kanban-active]:not([data-dsh-ssh-active]) [data-pane=conversation]>:not([data-dsh-kanban-view]),' +
730
- 'html[data-dsh-kanban-active]:not([data-dsh-ssh-active]) [class*=centerCol]>:not([data-dsh-kanban-view]){display:none!important}' +
731
- '.dkb-screen{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 56px;display:flex}' +
732
- '.dkb-bar{flex:none;align-items:center;gap:8px;display:flex;flex-wrap:wrap}' +
733
- '.dkb-bar .dkb-input,.dkb-bar .dkb-search,.dkb-bar .dkb-save,.dkb-bar .dkb-discard,.dkb-bar .dkb-facetHead,.dkb-bar .dkb-back{height:30px;box-sizing:border-box;padding-top:0;padding-bottom:0;border-radius:8px;font-size:13px;line-height:28px;display:inline-flex;align-items:center}' +
734
- '.dkb-bar .dkb-facet{display:inline-flex}' +
735
- '.dkb-back{appearance:none;font:inherit;cursor:pointer;color:var(--dsw-alias-label-secondary);background:0 0;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;align-items:center;gap:6px;padding:4px 10px;font-size:13px;display:flex;flex:none}' +
736
- '.dkb-back:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}' +
737
- '.dkb-backIcon{align-items:center;display:flex}' +
738
- '.dkb-archive{flex:1;min-height:0;overflow-y:auto;display:flex;flex-direction:column;gap:8px}' +
739
- '.dkb-archiveList{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}' +
740
- '.dkb-archiveRow{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;align-items:center;gap:12px;padding:8px 12px;display:flex}' +
741
- '.dkb-start{display:contents}' +
742
- '.dkb-pack{color:var(--dsw-alias-label-secondary)}' +
743
- '.dkb-who{color:var(--dsw-alias-label-secondary)}' +
744
- '.dkb-sameSession{outline:1px solid var(--dsw-alias-label-secondary);outline-offset:1px}' +
745
- '.dkb-waitCount{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:8px;padding:0 10px;height:30px;cursor:pointer;color:var(--dsw-alias-label-primary)}' +
746
- '.dkb-archiveOpen{background:none;border:0;padding:0;text-align:left;cursor:pointer;font:inherit}' +
747
- '.dkb-archiveTitle{flex:1;font-size:13px;color:var(--dsw-alias-label-primary)}' +
748
- '.dkb-urgent{border-left-width:3px;border-left-style:solid;padding-left:9px}' +
749
- '.dkb-cardMenu{position:relative;align-self:flex-start;opacity:0;transition:opacity .12s}' +
750
- '.dkb-taskCard:hover .dkb-cardMenu,.dkb-taskCard:focus-within .dkb-cardMenu,.dkb-taskCard:focus-visible .dkb-cardMenu{opacity:1}' +
751
- '.dkb-groupHead{appearance:none;width:100%;font:inherit;color:var(--dsw-alias-label-secondary);text-align:left;cursor:pointer;background:0 0;border:0;border-top:1px solid var(--dsw-alias-border-l2);align-items:center;gap:6px;padding:8px 2px 4px;font-size:11px;font-weight:600;display:flex}' +
752
- '.dkb-groupHead:first-child{border-top:0;padding-top:2px}' +
753
- '.dkb-groupHead:hover{color:var(--dsw-alias-label-primary)}' +
754
- '.dkb-groupCount{margin-left:auto;color:var(--dsw-alias-label-tertiary);font-weight:400}' +
755
- '.dkb-shown{color:var(--dsw-alias-label-tertiary);white-space:nowrap;font-size:12px;flex:none}' +
756
- '.dkb-moveBtn{appearance:none;font:inherit;cursor:pointer;color:var(--dsw-alias-label-tertiary);background:0 0;border:0;padding:0;font-size:11px;text-decoration:underline dotted}' +
757
- '.dkb-moveBtn:hover{color:var(--dsw-alias-label-primary)}' +
758
- '.dkb-taskCard:focus-visible{outline:2px solid var(--dsw-alias-border-l3);outline-offset:2px}' +
759
- '.dkb-sync{color:var(--dsw-alias-label-tertiary);white-space:nowrap;font-size:12px;flex:none}' +
760
- '.dkb-syncBad{color:var(--dsw-alias-state-error-primary,#e5534b);cursor:help}' +
761
- '.dkb-facet{position:relative;flex:none}' +
762
- '.dkb-facetHead{appearance:none;font:inherit;cursor:pointer;color:var(--dsw-alias-label-secondary);background:0 0;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:4px 10px;font-size:13px;white-space:nowrap}' +
763
- '.dkb-facetHead:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}' +
764
- '.dkb-facetOn{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l3);background:var(--dsw-alias-interactive-bg-hover)}' +
765
- '.dkb-facetList{position:absolute;top:calc(100% + 4px);left:0;z-index:60;min-width:180px;max-height:280px;overflow-y:auto;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;box-shadow:var(--dsw-shadow-lv2);padding:6px;display:flex;flex-direction:column;gap:2px}' +
766
- '.dkb-facetRow{align-items:center;gap:8px;padding:4px 6px;border-radius:6px;font-size:13px;cursor:pointer;display:flex}' +
767
- '.dkb-facetRow:hover{background:var(--dsw-alias-interactive-bg-hover)}' +
768
- '.dkb-facetName{flex:1;color:var(--dsw-alias-label-primary)}' +
769
- '.dkb-barTitle{color:var(--dsw-alias-label-primary);white-space:nowrap;margin:0;font-size:16px;font-weight:700;margin-right:auto}' +
770
- '.dkb-cols{overscroll-behavior-inline:contain;scrollbar-color:var(--dsw-alias-border-l3) var(--dsw-alias-interactive-bg-hover);scrollbar-width:thin;flex:1;gap:12px;min-height:0;padding-bottom:6px;display:flex;overflow:auto hidden}' +
771
- '.dkb-cols::-webkit-scrollbar{height:10px}' +
772
- '.dkb-cols::-webkit-scrollbar-track{background:var(--dsw-alias-interactive-bg-hover);border-radius:999px}' +
773
- '.dkb-cols::-webkit-scrollbar-thumb{background:var(--dsw-alias-border-l3);background-clip:content-box;border:2px solid transparent;border-radius:999px}' +
774
- '.dkb-col{flex:1 1 220px;min-width:220px;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}' +
775
- '.dkb-colOver{border-color:var(--dsw-alias-border-l3)}' +
776
- '.dkb-colHead{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;flex:none;align-items:center;gap:6px;padding:10px 12px;display:flex}' +
777
- '.dkb-colHead:hover{background:var(--dsw-alias-interactive-bg-hover)}' +
778
- '.dkb-colShut{flex:0 0 auto;min-width:0;width:40px}' +
779
- '.dkb-colShut .dkb-colHead{writing-mode:vertical-rl;height:100%;width:auto;padding:12px 10px;justify-content:flex-start;gap:10px}' +
780
- '.dkb-colShut .dkb-colHead .dkb-dot{writing-mode:horizontal-tb}' +
781
- '.dkb-colShut .dkb-count{writing-mode:horizontal-tb}' +
782
- '.dkb-dot{border-radius:50%;flex:none;width:8px;height:8px}' +
783
- '.dkb-dot[data-col=backlog]{background:var(--dsw-alias-label-tertiary)}' +
784
- '.dkb-dot[data-col=in-progress]{background:var(--dsw-alias-state-warn-primary)}' +
785
- '.dkb-dot[data-col=review]{background:var(--dsw-alias-state-business-primary)}' +
786
- '.dkb-dot[data-col=deploy]{background:var(--dsw-alias-state-business-primary)}' +
787
- '.dkb-dot[data-col=cleanup]{background:var(--dsw-alias-label-tertiary)}' +
788
- '.dkb-dot[data-col=done]{background:var(--dsw-alias-state-success-primary)}' +
789
- '.dkb-colName{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;flex:1;margin:0;font-size:13px;font-weight:700;overflow:hidden}' +
790
- '.dkb-count{min-width:20px;text-align:center;color:var(--dsw-alias-bg-base);background:var(--dsw-alias-label-tertiary);border-radius:999px;flex:none;padding:1px 8px;font-size:12px;font-weight:600}' +
791
- '.dkb-count[data-col=in-progress]{background:var(--dsw-alias-state-warn-primary)}' +
792
- '.dkb-count[data-col=review]{background:var(--dsw-alias-state-business-primary)}' +
793
- '.dkb-count[data-col=deploy]{background:var(--dsw-alias-state-business-primary)}' +
794
- '.dkb-count[data-col=done]{background:var(--dsw-alias-state-success-primary)}' +
795
- '.dkb-countOver{background:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-label-primary-inverted,#fff)}' +
796
- '.dkb-overdue{border-color:var(--dsw-alias-state-error-primary)}' +
797
- '.dkb-dueOver{color:var(--dsw-alias-state-error-primary);font-weight:600}' +
798
- '.dkb-list{flex-direction:column;flex:1;gap:8px;min-height:0;padding:2px 8px 10px;display:flex;overflow-y:auto}' +
799
- '.dkb-empty{text-align:center;color:var(--dsw-alias-label-tertiary);padding:24px 8px;font-size:12px}' +
800
- '.dkb-taskCard{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}' +
801
- '.dkb-taskCard:hover{box-shadow:var(--dsw-shadow-lv2);border-color:var(--dsw-alias-border-l3);transform:translateY(-1px)}' +
802
- '.dkb-taskTitle{-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:13px;font-weight:600;line-height:1.35;display:-webkit-box;overflow:hidden}' +
803
- '.dkb-mdBody{color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.55;display:flex;flex-direction:column;gap:8px;max-height:46vh;overflow-y:auto}' +
804
- '.dkb-mdPara{margin:0;overflow-wrap:anywhere}' +
805
- '.dkb-mdHead{color:var(--dsw-alias-label-primary);font-weight:700;margin:6px 0 0;line-height:1.35}' +
806
- '.dkb-mdHead1{font-size:15px}' +
807
- '.dkb-mdHead2{font-size:14px}' +
808
- '.dkb-mdHead3{font-size:13px;color:var(--dsw-alias-label-secondary)}' +
809
- '.dkb-mdList{margin:0;padding-left:20px;display:flex;flex-direction:column;gap:3px}' +
810
- '.dkb-mdCode{margin:0;padding:8px 10px;border-radius:8px;background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);font-size:12px;overflow-x:auto;white-space:pre}' +
811
- '.dkb-mdQuote{margin:0;padding-left:10px;border-left:2px solid var(--dsw-alias-border-l3);color:var(--dsw-alias-label-secondary)}' +
812
- '.dkb-mdRule{border:0;border-top:1px solid var(--dsw-alias-border-l2);margin:2px 0;width:100%}' +
813
- '.dkb-code{background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);border-radius:4px;padding:0 4px;font-size:12px}' +
814
- '.dkb-issueLink{color:var(--dsw-alias-label-secondary)}' +
815
- '.dkb-taskHead{align-items:center;gap:6px;flex-wrap:wrap;display:flex}' +
816
- '.dkb-ref{align-items:baseline;gap:5px;min-width:0;display:inline-flex}' +
817
- '.dkb-refRepo{max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;padding:1px 7px;font-size:11px;font-weight:600}' +
818
- '.dkb-refNum{flex:none;color:var(--dsw-alias-label-tertiary);font-size:11px;font-family:var(--dsw-font-family-mono,ui-monospace,SFMono-Regular,Menlo,monospace);letter-spacing:.02em}' +
819
- '.dkb-refLink{text-decoration:none}' +
820
- '.dkb-refLink:hover{color:var(--dsw-alias-label-primary);text-decoration:underline}' +
821
- '.dkb-taskRef{color:var(--dsw-alias-label-tertiary);align-items:center;gap:8px;font-size:11px;display:flex}' +
822
- '.dkb-planLine{color:var(--dsw-alias-label-secondary);font-size:11px;line-height:1.35;overflow:hidden;-webkit-line-clamp:2;-webkit-box-orient:vertical;display:-webkit-box}' +
823
- '.dkb-state{border:1px solid var(--dsw-alias-border-l2);border-radius:6px;padding:0 5px;font-size:10px;line-height:16px}' +
824
- '.dkb-stateLive{border-color:var(--dsw-alias-state-warn-primary);color:var(--dsw-alias-state-warn-primary);font-weight:600}' +
825
- '.dkb-batchList{list-style:none;margin:0;padding:0;max-height:200px;overflow-y:auto;display:flex;flex-direction:column;gap:4px}' +
826
- '.dkb-batchRow{align-items:baseline;gap:8px;font-size:13px;color:var(--dsw-alias-label-primary);display:flex}' +
827
- '.dkb-pick{margin:0;flex:none;opacity:0;transition:opacity .12s}' +
828
- '.dkb-taskCard:hover .dkb-pick,.dkb-taskCard:focus-within .dkb-pick,.dkb-pickOn{opacity:1}' +
829
- '.dkb-toChat{appearance:none;font:inherit;cursor:pointer;color:var(--dsw-alias-label-secondary);background:0 0;border:0;padding:0;font-size:11px;text-decoration:underline dotted}' +
830
- '.dkb-toChat:hover{color:var(--dsw-alias-label-primary)}' +
831
- '.dkb-planList{display:flex;flex-direction:column;gap:4px;margin:0;padding:0;list-style:none}' +
832
- '.dkb-planItem{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.4;display:flex;gap:8px;align-items:baseline}' +
833
- '.dkb-planItem.dkb-planDone{color:var(--dsw-alias-label-tertiary);text-decoration:line-through}' +
834
- '.dkb-planItem.dkb-planActive{color:var(--dsw-alias-label-primary);font-weight:600;text-decoration:none}' +
835
- '.dkb-planMark{flex:none;width:14px;text-align:center}' +
836
- '.dkb-tags{display:flex;flex-wrap:wrap;gap:4px}' +
837
- '.dkb-waiting{color:var(--dsw-alias-state-warn-primary);font-weight:600}' +
838
- '.dkb-waitDot{border-radius:50%;flex:none;width:8px;height:8px;background:var(--dsw-alias-state-warn-primary);display:inline-block}' +
839
- '.dkb-tag{white-space:nowrap;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover);border:1px solid transparent;border-radius:999px;padding:1px 7px;font-size:11px;line-height:1.5;font-weight:500}' +
840
- '.dkb-modal{position:absolute;inset:0;background:var(--dsw-alias-bg-mask-1,rgba(0,0,0,.4));display:flex;align-items:center;justify-content:center;padding:24px 24px 84px;box-sizing:border-box;z-index:70}' +
841
- '.dkb-panel{width:min(720px,100%);max-height:100%;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;padding:16px;box-sizing:border-box;overflow-y:auto;display:flex;flex-direction:column;gap:12px}' +
842
- '@media (min-width: 960px){.dkb-panelWide{width:min(1100px,96%)}.dkb-drawerGrid{display:grid;grid-template-columns:300px 1fr;gap:20px;align-items:start}}' +
843
- '.dkb-drawerSidebar{display:flex;flex-direction:column;gap:12px;border-right:1px solid var(--dsw-alias-border-l2);padding-right:16px}' +
844
- '.dkb-drawerMain{display:flex;flex-direction:column;gap:12px;min-width:0}' +
845
- '.dkb-panelBody{white-space:pre-wrap;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:1.5;margin:0}' +
846
- '.dkb-log{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:4px}' +
847
- '.dkb-logRow{color:var(--dsw-alias-label-secondary);font-size:12px}' +
848
- '.dkb-dialog{position:absolute;inset:0;background:var(--dsw-alias-bg-mask-1,rgba(0,0,0,.4));display:flex;align-items:center;justify-content:center;z-index:80}' +
849
- '.dkb-dialogBox{width:min(560px,94%);max-height:86%;overflow-y:auto;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;padding:16px;display:flex;flex-direction:column;gap:10px}' +
850
- '.dkb-issue{appearance:none;font:inherit;text-align:left;cursor:pointer;background:0 0;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:8px 10px;color:var(--dsw-alias-label-primary);font-size:13px}' +
851
- '.dkb-issueUsed{opacity:.5}' +
852
- '.dkb-row{display:flex;gap:8px;align-items:center}' +
853
- '.dkb-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 240px;padding:6px 10px;font-size:13px}' +
854
- '.dkb-search::placeholder{color:var(--dsw-alias-label-tertiary)}' +
855
- '.dkb-slot{height:2px;border-radius:2px;background:transparent;flex:none}' +
856
- '.dkb-slotOn{background:var(--dsw-alias-state-business-primary)}' +
857
- '.dkb-danger{appearance:none;font:inherit;cursor:pointer;border:1px solid var(--dsw-alias-state-error-primary);border-radius:8px;padding:5px 14px;font-size:13px;color:var(--dsw-alias-state-error-primary);background:transparent}' +
858
- '.dkb-danger:hover{background:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-bg-base)}' +
859
- '.dkb-quickFilterBar{display:flex;gap:6px;align-items:center;flex-wrap:wrap;padding:4px 0}' +
860
- '.dkb-filterChip{appearance:none;font:inherit;cursor:pointer;background:var(--dsw-alias-interactive-bg-hover);border:1px solid var(--dsw-alias-border-l2);border-radius:999px;padding:3px 10px;font-size:12px;color:var(--dsw-alias-label-secondary);transition:all .15s}' +
861
- '.dkb-filterChip:hover{background:var(--dsw-alias-interactive-bg-active);color:var(--dsw-alias-label-primary)}' +
862
- '.dkb-filterChipActive{background:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-label-primary-inverted,#fff);border-color:transparent;font-weight:600}' +
863
- '.dkb-epicTag{background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 12%, transparent);color:var(--dsw-alias-state-business-primary,#0969da);font-weight:600}' +
864
- '.dkb-progressBar{height:4px;background:var(--dsw-alias-border-l2);border-radius:2px;overflow:hidden;margin:4px 0}' +
865
- '.dkb-progressFill{height:100%;background:var(--dsw-alias-state-success-primary,#16a34a);transition:width .2s}' +
866
- '.dkb-consolePre{background:var(--dsw-alias-bg-layer-1,#0d1117);color:var(--dsw-alias-state-business-primary,#58a6ff);border:1px solid var(--dsw-alias-border-l2);padding:12px;border-radius:8px;font-family:monospace;font-size:11px;max-height:260px;overflow-y:auto;white-space:pre-wrap}'
867
-
868
-
547
+ const css = '.dkb-section-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:10px;padding:14px 16px;display:flex;flex-direction:column;gap:8px;margin-bottom:12px}.dkb-section-title{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary);display:flex;align-items:center;justify-content:space-between;gap:8px;padding-bottom:4px;border-bottom:1px solid var(--dsw-alias-border-l2)}.dkb-section-badge{font-size:11px;font-weight:500;padding:2px 8px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary)}.dkb-section-badge-ok{border-color:var(--dsw-alias-state-success-secondary,#34d399);color:var(--dsw-alias-state-success-primary,#059669);background:var(--dsw-alias-state-success-secondary,rgba(16,185,129,0.1))}.dkb-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none}.dkb-cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.dkb-page{padding:0}.dkb-page .dkb-body{border-top:none;margin:0}.dkb-head{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;display:flex;align-items:center;gap:12px;padding:14px 16px}.dkb-headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.dkb-title{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.dkb-sub{color:var(--dsw-alias-label-secondary);font-size:13px}.dkb-chevron{color:var(--dsw-alias-label-secondary);display:inline-flex;align-items:center;justify-content:center;flex:none;transition:transform .16s}.dkb-chevronOpen{transform:rotate(180deg)}.dkb-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.dkb-group{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;padding:14px 0 2px}.dkb-field{display:flex;flex-direction:column;gap:6px;padding:10px 0}.dkb-label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.dkb-hint{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:1.5}.dkb-input{height:34px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;box-sizing:border-box;width:100%}.dkb-area{min-height:96px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);border-radius:8px;padding:8px 12px;font-size:13px;box-sizing:border-box;width:100%;resize:vertical;font:inherit}.dkb-input:disabled,.dkb-area:disabled{opacity:.55}.dkb-foot{border-top:1px solid var(--dsw-alias-border-l2);display:flex;justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px}.dkb-footSplit{justify-content:space-between}.dkb-footLeft{display:flex;align-items:center;gap:8px}.dkb-note{min-width:0;flex:1;margin:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-secondary)}.dkb-failed{min-width:0;color:var(--dsw-alias-state-error-primary);flex:1;margin:0;font-size:12px;line-height:1.5}.dkb-save{appearance:none;font:inherit;cursor:pointer;border:1px solid transparent;border-radius:8px;padding:5px 14px;font-size:13px;background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.dkb-discard{appearance:none;font:inherit;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:5px 14px;font-size:13px;color:var(--dsw-alias-label-secondary);background:transparent}.dkb-discard:disabled,.dkb-save:disabled{opacity:.4;cursor:default}.dkb-entry-clone{align-items:center;gap:8px;display:flex}.dkb-entry-clone[data-active]{font-weight:600}.dkb-entryIcon{flex:none;justify-content:center;align-items:center;display:inline-flex}.dkb-entryLabel{text-overflow:ellipsis;overflow:hidden}[data-dsh-frame][data-sidebar-collapsed] .dkb-entryLabel{display:none}[data-pane=conversation],[class*=centerCol]{position:relative}[data-dsh-kanban-view]{z-index:60;background:var(--dsw-alias-bg-base);display:none;position:absolute;inset:0}html[data-dsh-kanban-active]:not([data-dsh-ssh-active]) [data-dsh-kanban-view]{display:block}html[data-dsh-kanban-active]:not([data-dsh-ssh-active]) [data-pane=conversation]>:not([data-dsh-kanban-view]),html[data-dsh-kanban-active]:not([data-dsh-ssh-active]) [class*=centerCol]>:not([data-dsh-kanban-view]){display:none!important}.dkb-screen{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 56px;display:flex}.dkb-bar{flex:none;align-items:center;gap:8px;display:flex;flex-wrap:wrap}.dkb-bar .dkb-input,.dkb-bar .dkb-search,.dkb-bar .dkb-save,.dkb-bar .dkb-discard,.dkb-bar .dkb-facetHead,.dkb-bar .dkb-back{height:30px;box-sizing:border-box;padding-top:0;padding-bottom:0;border-radius:8px;font-size:13px;line-height:28px;display:inline-flex;align-items:center}.dkb-bar .dkb-facet{display:inline-flex}.dkb-back{appearance:none;font:inherit;cursor:pointer;color:var(--dsw-alias-label-secondary);background:0 0;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;align-items:center;gap:6px;padding:4px 10px;font-size:13px;display:flex;flex:none}.dkb-back:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}.dkb-backIcon{align-items:center;display:flex}.dkb-archive{flex:1;min-height:0;overflow-y:auto;display:flex;flex-direction:column;gap:8px}.dkb-archiveList{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}.dkb-archiveRow{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;align-items:center;gap:12px;padding:8px 12px;display:flex}.dkb-start{display:contents}.dkb-pack{color:var(--dsw-alias-label-secondary)}.dkb-who{color:var(--dsw-alias-label-secondary)}.dkb-sameSession{outline:1px solid var(--dsw-alias-label-secondary);outline-offset:1px}.dkb-waitCount{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:8px;padding:0 10px;height:30px;cursor:pointer;color:var(--dsw-alias-label-primary)}.dkb-archiveOpen{background:none;border:0;padding:0;text-align:left;cursor:pointer;font:inherit}.dkb-archiveTitle{flex:1;font-size:13px;color:var(--dsw-alias-label-primary)}.dkb-urgent{border-left-width:3px;border-left-style:solid;padding-left:9px}.dkb-cardMenu{position:relative;align-self:flex-start;opacity:0;transition:opacity .12s}.dkb-taskCard:hover .dkb-cardMenu,.dkb-taskCard:focus-within .dkb-cardMenu,.dkb-taskCard:focus-visible .dkb-cardMenu{opacity:1}.dkb-groupHead{appearance:none;width:100%;font:inherit;color:var(--dsw-alias-label-secondary);text-align:left;cursor:pointer;background:0 0;border:0;border-top:1px solid var(--dsw-alias-border-l2);align-items:center;gap:6px;padding:8px 2px 4px;font-size:11px;font-weight:600;display:flex}.dkb-groupHead:first-child{border-top:0;padding-top:2px}.dkb-groupHead:hover{color:var(--dsw-alias-label-primary)}.dkb-groupCount{margin-left:auto;color:var(--dsw-alias-label-tertiary);font-weight:400}.dkb-shown{color:var(--dsw-alias-label-tertiary);white-space:nowrap;font-size:12px;flex:none}.dkb-moveBtn{appearance:none;font:inherit;cursor:pointer;color:var(--dsw-alias-label-tertiary);background:0 0;border:0;padding:0;font-size:11px;text-decoration:underline dotted}.dkb-moveBtn:hover{color:var(--dsw-alias-label-primary)}.dkb-taskCard:focus-visible{outline:2px solid var(--dsw-alias-border-l3);outline-offset:2px}.dkb-sync{color:var(--dsw-alias-label-tertiary);white-space:nowrap;font-size:12px;flex:none}.dkb-syncBad{color:var(--dsw-alias-state-error-primary,#e5534b);cursor:help}.dkb-facet{position:relative;flex:none}.dkb-facetHead{appearance:none;font:inherit;cursor:pointer;color:var(--dsw-alias-label-secondary);background:0 0;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:4px 10px;font-size:13px;white-space:nowrap}.dkb-facetHead:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}.dkb-facetOn{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l3);background:var(--dsw-alias-interactive-bg-hover)}.dkb-facetList{position:absolute;top:calc(100% + 4px);left:0;z-index:60;min-width:180px;max-height:280px;overflow-y:auto;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;box-shadow:var(--dsw-shadow-lv2);padding:6px;display:flex;flex-direction:column;gap:2px}.dkb-facetRow{align-items:center;gap:8px;padding:4px 6px;border-radius:6px;font-size:13px;cursor:pointer;display:flex}.dkb-facetRow:hover{background:var(--dsw-alias-interactive-bg-hover)}.dkb-facetName{flex:1;color:var(--dsw-alias-label-primary)}.dkb-barTitle{color:var(--dsw-alias-label-primary);white-space:nowrap;margin:0;font-size:16px;font-weight:700;margin-right:auto}.dkb-cols{overscroll-behavior-inline:contain;scrollbar-color:var(--dsw-alias-border-l3) var(--dsw-alias-interactive-bg-hover);scrollbar-width:thin;flex:1;gap:12px;min-height:0;padding-bottom:6px;display:flex;overflow:auto hidden}.dkb-cols::-webkit-scrollbar{height:10px}.dkb-cols::-webkit-scrollbar-track{background:var(--dsw-alias-interactive-bg-hover);border-radius:999px}.dkb-cols::-webkit-scrollbar-thumb{background:var(--dsw-alias-border-l3);background-clip:content-box;border:2px solid transparent;border-radius:999px}.dkb-col{flex:1 1 220px;min-width:220px;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}.dkb-colOver{border-color:var(--dsw-alias-border-l3)}.dkb-colHead{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;flex:none;align-items:center;gap:6px;padding:10px 12px;display:flex}.dkb-colHead:hover{background:var(--dsw-alias-interactive-bg-hover)}.dkb-colShut{flex:0 0 auto;min-width:0;width:40px}.dkb-colShut .dkb-colHead{writing-mode:vertical-rl;height:100%;width:auto;padding:12px 10px;justify-content:flex-start;gap:10px}.dkb-colShut .dkb-colHead .dkb-dot{writing-mode:horizontal-tb}.dkb-colShut .dkb-count{writing-mode:horizontal-tb}.dkb-dot{border-radius:50%;flex:none;width:8px;height:8px}.dkb-dot[data-col=backlog]{background:var(--dsw-alias-label-tertiary)}.dkb-dot[data-col=in-progress]{background:var(--dsw-alias-state-warn-primary)}.dkb-dot[data-col=review]{background:var(--dsw-alias-state-business-primary)}.dkb-dot[data-col=deploy]{background:var(--dsw-alias-state-business-primary)}.dkb-dot[data-col=cleanup]{background:var(--dsw-alias-label-tertiary)}.dkb-dot[data-col=done]{background:var(--dsw-alias-state-success-primary)}.dkb-colName{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;flex:1;margin:0;font-size:13px;font-weight:700;overflow:hidden}.dkb-count{min-width:20px;text-align:center;color:var(--dsw-alias-bg-base);background:var(--dsw-alias-label-tertiary);border-radius:999px;flex:none;padding:1px 8px;font-size:12px;font-weight:600}.dkb-count[data-col=in-progress]{background:var(--dsw-alias-state-warn-primary)}.dkb-count[data-col=review]{background:var(--dsw-alias-state-business-primary)}.dkb-count[data-col=deploy]{background:var(--dsw-alias-state-business-primary)}.dkb-count[data-col=done]{background:var(--dsw-alias-state-success-primary)}.dkb-countOver{background:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-label-primary-inverted,#fff)}.dkb-overdue{border-color:var(--dsw-alias-state-error-primary)}.dkb-dueOver{color:var(--dsw-alias-state-error-primary);font-weight:600}.dkb-list{flex-direction:column;flex:1;gap:8px;min-height:0;padding:2px 8px 10px;display:flex;overflow-y:auto}.dkb-empty{text-align:center;color:var(--dsw-alias-label-tertiary);padding:24px 8px;font-size:12px}.dkb-taskCard{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}.dkb-taskCard:hover{box-shadow:var(--dsw-shadow-lv2);border-color:var(--dsw-alias-border-l3);transform:translateY(-1px)}.dkb-taskTitle{-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:13px;font-weight:600;line-height:1.35;display:-webkit-box;overflow:hidden}.dkb-mdBody{color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.55;display:flex;flex-direction:column;gap:8px;max-height:46vh;overflow-y:auto}.dkb-mdPara{margin:0;overflow-wrap:anywhere}.dkb-mdHead{color:var(--dsw-alias-label-primary);font-weight:700;margin:6px 0 0;line-height:1.35}.dkb-mdHead1{font-size:15px}.dkb-mdHead2{font-size:14px}.dkb-mdHead3{font-size:13px;color:var(--dsw-alias-label-secondary)}.dkb-mdList{margin:0;padding-left:20px;display:flex;flex-direction:column;gap:3px}.dkb-mdCode{margin:0;padding:8px 10px;border-radius:8px;background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);font-size:12px;overflow-x:auto;white-space:pre}.dkb-mdQuote{margin:0;padding-left:10px;border-left:2px solid var(--dsw-alias-border-l3);color:var(--dsw-alias-label-secondary)}.dkb-mdRule{border:0;border-top:1px solid var(--dsw-alias-border-l2);margin:2px 0;width:100%}.dkb-code{background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);border-radius:4px;padding:0 4px;font-size:12px}.dkb-issueLink{color:var(--dsw-alias-label-secondary)}.dkb-taskHead{align-items:center;gap:6px;flex-wrap:wrap;display:flex}.dkb-ref{align-items:baseline;gap:5px;min-width:0;display:inline-flex}.dkb-refRepo{max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;padding:1px 7px;font-size:11px;font-weight:600}.dkb-refNum{flex:none;color:var(--dsw-alias-label-tertiary);font-size:11px;font-family:var(--dsw-font-family-mono,ui-monospace,SFMono-Regular,Menlo,monospace);letter-spacing:.02em}.dkb-refLink{text-decoration:none}.dkb-refLink:hover{color:var(--dsw-alias-label-primary);text-decoration:underline}.dkb-taskRef{color:var(--dsw-alias-label-tertiary);align-items:center;gap:8px;font-size:11px;display:flex}.dkb-planLine{color:var(--dsw-alias-label-secondary);font-size:11px;line-height:1.35;overflow:hidden;-webkit-line-clamp:2;-webkit-box-orient:vertical;display:-webkit-box}.dkb-state{border:1px solid var(--dsw-alias-border-l2);border-radius:6px;padding:0 5px;font-size:10px;line-height:16px}.dkb-stateLive{border-color:var(--dsw-alias-state-warn-primary);color:var(--dsw-alias-state-warn-primary);font-weight:600}.dkb-batchList{list-style:none;margin:0;padding:0;max-height:200px;overflow-y:auto;display:flex;flex-direction:column;gap:4px}.dkb-batchRow{align-items:baseline;gap:8px;font-size:13px;color:var(--dsw-alias-label-primary);display:flex}.dkb-pick{margin:0;flex:none;opacity:0;transition:opacity .12s}.dkb-taskCard:hover .dkb-pick,.dkb-taskCard:focus-within .dkb-pick,.dkb-pickOn{opacity:1}.dkb-toChat{appearance:none;font:inherit;cursor:pointer;color:var(--dsw-alias-label-secondary);background:0 0;border:0;padding:0;font-size:11px;text-decoration:underline dotted}.dkb-toChat:hover{color:var(--dsw-alias-label-primary)}.dkb-planList{display:flex;flex-direction:column;gap:4px;margin:0;padding:0;list-style:none}.dkb-planItem{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.4;display:flex;gap:8px;align-items:baseline}.dkb-planItem.dkb-planDone{color:var(--dsw-alias-label-tertiary);text-decoration:line-through}.dkb-planItem.dkb-planActive{color:var(--dsw-alias-label-primary);font-weight:600;text-decoration:none}.dkb-planMark{flex:none;width:14px;text-align:center}.dkb-tags{display:flex;flex-wrap:wrap;gap:4px}.dkb-waiting{color:var(--dsw-alias-state-warn-primary);font-weight:600}.dkb-waitDot{border-radius:50%;flex:none;width:8px;height:8px;background:var(--dsw-alias-state-warn-primary);display:inline-block}.dkb-tag{white-space:nowrap;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover);border:1px solid transparent;border-radius:999px;padding:1px 7px;font-size:11px;line-height:1.5;font-weight:500}.dkb-modal{position:absolute;inset:0;background:var(--dsw-alias-bg-mask-1,rgba(0,0,0,.4));display:flex;align-items:center;justify-content:center;padding:24px 24px 84px;box-sizing:border-box;z-index:70}.dkb-panel{width:min(720px,100%);max-height:100%;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;padding:16px;box-sizing:border-box;overflow-y:auto;display:flex;flex-direction:column;gap:12px}@media (min-width:960px){.dkb-panelWide{width:min(1100px,96%)}.dkb-drawerGrid{display:grid;grid-template-columns:300px 1fr;gap:20px;align-items:start}}.dkb-drawerSidebar{display:flex;flex-direction:column;gap:12px;border-right:1px solid var(--dsw-alias-border-l2);padding-right:16px}.dkb-drawerMain{display:flex;flex-direction:column;gap:12px;min-width:0}.dkb-panelBody{white-space:pre-wrap;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:1.5;margin:0}.dkb-log{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:4px}.dkb-logRow{color:var(--dsw-alias-label-secondary);font-size:12px}.dkb-dialog{position:absolute;inset:0;background:var(--dsw-alias-bg-mask-1,rgba(0,0,0,.4));display:flex;align-items:center;justify-content:center;z-index:80}.dkb-dialogBox{width:min(560px,94%);max-height:86%;overflow-y:auto;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;padding:16px;display:flex;flex-direction:column;gap:10px}.dkb-issue{appearance:none;font:inherit;text-align:left;cursor:pointer;background:0 0;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:8px 10px;color:var(--dsw-alias-label-primary);font-size:13px}.dkb-issueUsed{opacity:.5}.dkb-row{display:flex;gap:8px;align-items:center}.dkb-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 240px;padding:6px 10px;font-size:13px}.dkb-search::placeholder{color:var(--dsw-alias-label-tertiary)}.dkb-slot{height:2px;border-radius:2px;background:transparent;flex:none}.dkb-slotOn{background:var(--dsw-alias-state-business-primary)}.dkb-danger{appearance:none;font:inherit;cursor:pointer;border:1px solid var(--dsw-alias-state-error-primary);border-radius:8px;padding:5px 14px;font-size:13px;color:var(--dsw-alias-state-error-primary);background:transparent}.dkb-danger:hover{background:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-bg-base)}.dkb-quickFilterBar{display:flex;gap:6px;align-items:center;flex-wrap:wrap;padding:4px 0}.dkb-filterChip{appearance:none;font:inherit;cursor:pointer;background:var(--dsw-alias-interactive-bg-hover);border:1px solid var(--dsw-alias-border-l2);border-radius:999px;padding:3px 10px;font-size:12px;color:var(--dsw-alias-label-secondary);transition:all .15s}.dkb-filterChip:hover{background:var(--dsw-alias-interactive-bg-active);color:var(--dsw-alias-label-primary)}.dkb-filterChipActive{background:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-label-primary-inverted,#fff);border-color:transparent;font-weight:600}.dkb-epicTag{background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 12%,transparent);color:var(--dsw-alias-state-business-primary,#0969da);font-weight:600}.dkb-progressBar{height:4px;background:var(--dsw-alias-border-l2);border-radius:2px;overflow:hidden;margin:4px 0}.dkb-progressFill{height:100%;background:var(--dsw-alias-state-success-primary,#16a34a);transition:width .2s}.dkb-consolePre{background:var(--dsw-alias-bg-layer-1,#0d1117);color:var(--dsw-alias-state-business-primary,#58a6ff);border:1px solid var(--dsw-alias-border-l2);padding:12px;border-radius:8px;font-family:monospace;font-size:11px;max-height:260px;overflow-y:auto;white-space:pre-wrap}'
869
548
 
870
549
  const en = {
871
550
  'updater.title': 'Plugin Updates',
@@ -1288,6 +967,39 @@ window.__ModuleLoader__.load({
1288
967
  'telemetry.output': 'Output',
1289
968
  'telemetry.cache': 'Cached',
1290
969
  'telemetry.cost': 'Estimated Cost',
970
+ 'group.remote': 'Remote execution (Server 111)',
971
+ 'field.remoteMode': 'Remote mode',
972
+ 'hint.remoteMode': 'Execute tasks, worktrees, and git commands on remote server 111.',
973
+ 'field.remoteHost': 'Remote host',
974
+ 'hint.remoteHost': 'Hostname or IP address of the remote server.',
975
+ 'field.remotePort': 'SSH port',
976
+ 'hint.remotePort': 'SSH port on remote server (default 22).',
977
+ 'field.remoteUser': 'SSH user',
978
+ 'hint.remoteUser': 'SSH login username on remote host.',
979
+ 'field.remoteAuthType': 'SSH auth type',
980
+ 'hint.remoteAuthType': 'SSH authentication method: key, agent, or password.',
981
+ 'field.remoteKeyPath': 'SSH private key',
982
+ 'hint.remoteKeyPath': 'Path to SSH private key file on local PC.',
983
+ 'field.remoteSudoUser': 'Sudo user',
984
+ 'hint.remoteSudoUser': 'Run remote commands under this Linux user via sudo -u.',
985
+ 'field.remoteRoot': 'Remote project root',
986
+ 'hint.remoteRoot': 'Remote projects directory (/mnt/external/Project/DEV).',
987
+ 'field.remoteServerUrl': 'DSH server URL',
988
+ 'hint.remoteServerUrl': 'Remote DSH URL (http://192.168.1.111:3080) for unified board state.',
989
+ 'field.remoteServerToken': 'Server auth token',
990
+ 'hint.remoteServerToken': 'Optional authentication token for remote DSH server.',
991
+ 'field.watchdogEnabled': 'Watchdog & circuit breaker',
992
+ 'hint.watchdogEnabled': 'Pause stalled tasks and prevent runaway token consumption.',
993
+ 'field.watchdogStallTimeoutMin': 'Watchdog stall timeout (min)',
994
+ 'hint.watchdogStallTimeoutMin': 'Minutes of inactivity before active task is marked stalled.',
995
+ 'field.watchdogMaxConsecutiveErrors': 'Watchdog max errors',
996
+ 'hint.watchdogMaxConsecutiveErrors': 'Consecutive tool execution errors before circuit breaker trips.',
997
+ 'watchdog.stalled': 'Stalled',
998
+ 'remote.testBtn': 'Test Connection',
999
+ 'remote.testing': 'Testing connection...',
1000
+ 'remote.ok': 'Connected to server 111',
1001
+ 'remote.failed': 'Connection failed',
1002
+ 'remote.badge': 'Remote: 111',
1291
1003
  'telemetry.limit': 'Budget Limit',
1292
1004
  }
1293
1005
 
@@ -1712,6 +1424,39 @@ window.__ModuleLoader__.load({
1712
1424
  'telemetry.output': '输出',
1713
1425
  'telemetry.cache': '缓存命中',
1714
1426
  'telemetry.cost': '预估成本',
1427
+ 'group.remote': '远程执行 (111 服务器)',
1428
+ 'field.remoteMode': '远程模式',
1429
+ 'hint.remoteMode': '在远程 111 服务器上执行任务、工作树和 git 命令。',
1430
+ 'field.remoteHost': '远程主机',
1431
+ 'hint.remoteHost': '远程服务器的主机名或 IP 地址。',
1432
+ 'field.remotePort': 'SSH 端口',
1433
+ 'hint.remotePort': '远程服务器的 SSH 端口(默认 22)。',
1434
+ 'field.remoteUser': 'SSH 用户',
1435
+ 'hint.remoteUser': '远程主机上的 SSH 登录用户名。',
1436
+ 'field.remoteAuthType': 'SSH 认证类型',
1437
+ 'hint.remoteAuthType': 'SSH 认证方式:key、agent 或 password。',
1438
+ 'field.remoteKeyPath': 'SSH 私钥路径',
1439
+ 'hint.remoteKeyPath': '本地电脑上的 SSH 私钥文件路径。',
1440
+ 'field.remoteSudoUser': 'Sudo 执行用户',
1441
+ 'hint.remoteSudoUser': '在远程通过 sudo -u 以此 Linux 用户身份运行命令。',
1442
+ 'field.remoteRoot': '远程项目根目录',
1443
+ 'hint.remoteRoot': '远程项目仓库目录 (/mnt/external/Project/DEV)。',
1444
+ 'field.remoteServerUrl': 'DSH 服务器地址',
1445
+ 'hint.remoteServerUrl': '远程 DSH 服务器地址 (http://192.168.1.111:3080),用于共享看板状态。',
1446
+ 'field.remoteServerToken': '服务器认证令牌',
1447
+ 'hint.remoteServerToken': '远程 DSH 服务器的可选认证令牌。',
1448
+ 'field.watchdogEnabled': '看门狗与熔断器',
1449
+ 'hint.watchdogEnabled': '暂停卡住的任务并防止额度失控消耗。',
1450
+ 'field.watchdogStallTimeoutMin': '看门狗超时时间 (分钟)',
1451
+ 'hint.watchdogStallTimeoutMin': '在任务被标记为停滞之前的非活跃分钟数。',
1452
+ 'field.watchdogMaxConsecutiveErrors': '看门狗最大连续错误数',
1453
+ 'hint.watchdogMaxConsecutiveErrors': '熔断器跳闸前的连续工具执行错误次数。',
1454
+ 'watchdog.stalled': '已停滞',
1455
+ 'remote.testBtn': '测试连接',
1456
+ 'remote.testing': '正在测试连接...',
1457
+ 'remote.ok': '已连接到 111 服务器',
1458
+ 'remote.failed': '连接失败',
1459
+ 'remote.badge': '远程: 111',
1715
1460
  'telemetry.limit': '预算限额',
1716
1461
  }
1717
1462
 
@@ -1725,7 +1470,6 @@ window.__ModuleLoader__.load({
1725
1470
  return text
1726
1471
  }
1727
1472
 
1728
-
1729
1473
  const FIELDS = [
1730
1474
  { field: 'gitProvider', kind: 'text', group: 'forge' },
1731
1475
  { field: 'giteaUrl', kind: 'text', group: 'gitea' },
@@ -1763,6 +1507,19 @@ window.__ModuleLoader__.load({
1763
1507
  { field: 'verificationTimeoutMs', kind: 'number', group: 'general' },
1764
1508
  { field: 'costMeterEnabled', kind: 'bool', group: 'sync' },
1765
1509
  { field: 'taskCostLimit', kind: 'number', group: 'limits' },
1510
+ { field: 'remoteMode', kind: 'bool', group: 'remote' },
1511
+ { field: 'remoteHost', kind: 'text', group: 'remote' },
1512
+ { field: 'remotePort', kind: 'number', group: 'remote' },
1513
+ { field: 'remoteUser', kind: 'text', group: 'remote' },
1514
+ { field: 'remoteAuthType', kind: 'text', group: 'remote' },
1515
+ { field: 'remoteKeyPath', kind: 'text', group: 'remote' },
1516
+ { field: 'remoteSudoUser', kind: 'text', group: 'remote' },
1517
+ { field: 'remoteRoot', kind: 'text', group: 'remote' },
1518
+ { field: 'remoteServerUrl', kind: 'text', group: 'remote' },
1519
+ { field: 'remoteServerToken', kind: 'text', group: 'remote' },
1520
+ { field: 'watchdogEnabled', kind: 'bool', group: 'general' },
1521
+ { field: 'watchdogStallTimeoutMin', kind: 'number', group: 'limits' },
1522
+ { field: 'watchdogMaxConsecutiveErrors', kind: 'number', group: 'limits' },
1766
1523
  ]
1767
1524
 
1768
1525
  function hintKey(spec) {
@@ -1770,6 +1527,7 @@ window.__ModuleLoader__.load({
1770
1527
  if (spec.group === 'gitea') return 'hint.' + spec.field
1771
1528
  if (spec.group === 'github') return 'hint.' + spec.field
1772
1529
  if (spec.group === 'forge') return 'hint.' + spec.field
1530
+ if (spec.group === 'remote') return 'hint.' + spec.field
1773
1531
  if (spec.group === 'labels') return 'hint.label'
1774
1532
  if (spec.group === 'limits') return 'hint.limit'
1775
1533
  return 'hint.' + spec.field
@@ -1807,6 +1565,63 @@ window.__ModuleLoader__.load({
1807
1565
  }, h(Icon))
1808
1566
  }
1809
1567
 
1568
+ function RemoteConnectionSection(props) {
1569
+ const t = props.t || fallbackT
1570
+ const [testing, setTesting] = React.useState(false)
1571
+ const [result, setResult] = React.useState(null)
1572
+
1573
+ const handleTest = React.useCallback(async () => {
1574
+ setTesting(true)
1575
+ setResult(null)
1576
+ try {
1577
+ const res = await fetch('/api/dsh-kanban/remote/test', {
1578
+ method: 'POST',
1579
+ headers: { 'Content-Type': 'application/json' },
1580
+ })
1581
+ const data = await res.json()
1582
+ setResult(data)
1583
+ } catch (e) {
1584
+ setResult({ ok: false, error: e?.message || String(e) })
1585
+ } finally {
1586
+ setTesting(false)
1587
+ }
1588
+ }, [])
1589
+
1590
+ return h('div', {
1591
+ className: 'dkb-section-card',
1592
+ key: 'remote-test-card',
1593
+ style: { borderLeft: '3px solid var(--dsw-alias-state-business-primary, #0969da)', background: 'var(--dsw-alias-bg-layer-1, #f6f8fa)', padding: '12px 14px', marginBottom: '12px' },
1594
+ },
1595
+ h('div', {
1596
+ style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: result ? '8px' : '0' },
1597
+ },
1598
+ h('span', { style: { fontWeight: '600', fontSize: '13px' } }, '🌐 ' + t('group.remote')),
1599
+ h('button', {
1600
+ type: 'button',
1601
+ className: 'dkb-save',
1602
+ style: { padding: '3px 10px', fontSize: '12px' },
1603
+ disabled: testing,
1604
+ onClick: handleTest,
1605
+ }, testing ? t('remote.testing') : t('remote.testBtn')),
1606
+ ),
1607
+ result ? h('div', {
1608
+ style: {
1609
+ fontSize: '12px',
1610
+ padding: '6px 8px',
1611
+ borderRadius: '4px',
1612
+ background: (result.ssh?.ok || result.server?.ok)
1613
+ ? 'var(--dsw-alias-state-success-secondary, #dafbe1)'
1614
+ : 'var(--dsw-alias-state-error-secondary, #ffebe9)',
1615
+ color: (result.ssh?.ok || result.server?.ok)
1616
+ ? 'var(--dsw-alias-state-success-primary, #1a7f37)'
1617
+ : 'var(--dsw-alias-state-error-primary, #cf222e)',
1618
+ },
1619
+ },
1620
+ h('div', null, (result.ssh?.ok ? '✓ SSH: OK (' + result.ssh.latencyMs + 'ms)' : '✗ SSH: ' + (result.ssh?.error || 'unreachable'))),
1621
+ h('div', null, (result.server?.ok ? '✓ DSH Server: OK (' + result.server.latencyMs + 'ms)' : '✗ DSH Server: ' + (result.server?.error || 'unreachable'))),
1622
+ ) : null,
1623
+ )
1624
+ }
1810
1625
 
1811
1626
  function PluginUpdaterSection(props) {
1812
1627
  const t = props.t || fallbackT
@@ -1899,8 +1714,6 @@ window.__ModuleLoader__.load({
1899
1714
  const ctx = props && props.ctx
1900
1715
  const t = (props && props.t) || fallbackT
1901
1716
  ensureCss()
1902
-
1903
- // Row seat (plugins.row.config): summary is a one-liner
1904
1717
  if (props && props.view === 'summary') {
1905
1718
  return h('span', { className: 'dkb-sub' }, t('settings.subtitle') || t('subtitle') || 'Kanban board for sessions and tasks')
1906
1719
  }
@@ -2085,6 +1898,7 @@ window.__ModuleLoader__.load({
2085
1898
  t('costMeter.notice'),
2086
1899
  ),
2087
1900
  ))
1901
+ rows.push(h(RemoteConnectionSection, { t, key: 'remote-test' }))
2088
1902
  rows.push(h(PluginUpdaterSection, { t, key: 'updater' }))
2089
1903
  rows.push(h('div', { className: 'dkb-foot', key: 'foot' },
2090
1904
  note,
@@ -2100,12 +1914,10 @@ window.__ModuleLoader__.load({
2100
1914
  return h('li', { className: 'dkb-card dkb-cardOpen' }, head, h('div', { className: 'dkb-body' }, body))
2101
1915
  }
2102
1916
 
2103
- /** Back button arrow icon: 14x14 stroke currentColor. */
2104
1917
  const BACK_ICON = '<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor"'
2105
1918
  + ' stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">'
2106
1919
  + '<path d="M10 3.5 5.5 8l4.5 4.5"/></svg>'
2107
1920
 
2108
-
2109
1921
  const TaskCard = React.memo(function TaskCard(props) {
2110
1922
  const { task, t, now } = props
2111
1923
  const ref = cardRef(task)
@@ -2124,6 +1936,7 @@ window.__ModuleLoader__.load({
2124
1936
  return h('div', {
2125
1937
  className: 'dkb-taskCard' + (urgent ? ' dkb-urgent' : '')
2126
1938
  + (overdue ? ' dkb-overdue' : '')
1939
+ + (task.stalled ? ' dkb-stalled' : '')
2127
1940
  + (props.sameSession ? ' dkb-sameSession' : ''),
2128
1941
  onMouseEnter: () => { if (props.onHover) props.onHover(task.sessionId || '') },
2129
1942
  onMouseLeave: () => { if (props.onHover) props.onHover('') },
@@ -2132,7 +1945,7 @@ window.__ModuleLoader__.load({
2132
1945
  tabIndex: 0,
2133
1946
  draggable: true,
2134
1947
  onDragStart: (e) => {
2135
- try { e.dataTransfer.setData('text/plain', task.id) } catch { /* dataTransfer unsupported */ }
1948
+ try { e.dataTransfer.setData('text/plain', task.id) } catch { }
2136
1949
  props.onDragStart(task)
2137
1950
  },
2138
1951
  onClick: () => props.onOpen(task),
@@ -2148,6 +1961,7 @@ window.__ModuleLoader__.load({
2148
1961
  }),
2149
1962
  waiting ? h('span', { className: 'dkb-waitDot' }) : null,
2150
1963
  waiting ? h('span', { className: 'dkb-waiting' }, t('board.waiting')) : null,
1964
+ task.stalled ? h('span', { className: 'dkb-tag', style: { background: 'var(--dsw-alias-state-warn-secondary, #fff8c5)', color: 'var(--dsw-alias-state-warn-primary, #9a6700)', borderColor: 'var(--dsw-alias-state-warn-secondary, #d4a72c)' }, title: task.stalledReason || 'Stalled' }, '⚠️ ' + (t('watchdog.stalled') || 'Stalled')) : null,
2151
1965
  taskRef(task, t),
2152
1966
  task.isEpic ? h('span', { className: 'dkb-tag dkb-epicTag', title: t('epic.badge') }, '👑 ' + t('epic.badge')) : null,
2153
1967
  task.parentId ? h('span', { className: 'dkb-tag', style: { opacity: 0.75 } }, '↳') : null,
@@ -2304,10 +2118,6 @@ window.__ModuleLoader__.load({
2304
2118
  )
2305
2119
  })
2306
2120
 
2307
- /**
2308
- * Execution options: agent preset and permission rank.
2309
- * Shared across single-task and batch launch modals.
2310
- */
2311
2121
  function LaunchFields(props) {
2312
2122
  const t = props.t
2313
2123
  const rows = []
@@ -2491,7 +2301,7 @@ window.__ModuleLoader__.load({
2491
2301
  es.addEventListener('task:change', scheduleReload)
2492
2302
  es.onerror = () => {
2493
2303
  if (es) {
2494
- try { es.close() } catch { /* eventsource close best-effort */ }
2304
+ try { es.close() } catch { }
2495
2305
  es = null
2496
2306
  }
2497
2307
  if (!reconnectTimer) {
@@ -2501,7 +2311,7 @@ window.__ModuleLoader__.load({
2501
2311
  }, 5000)
2502
2312
  }
2503
2313
  }
2504
- } catch { /* eventsource setup best-effort */ }
2314
+ } catch { }
2505
2315
  }
2506
2316
 
2507
2317
  connect()
@@ -2509,7 +2319,7 @@ window.__ModuleLoader__.load({
2509
2319
  return () => {
2510
2320
  if (reconnectTimer) clearTimeout(reconnectTimer)
2511
2321
  if (es) {
2512
- try { es.close() } catch { /* eventsource close best-effort */ }
2322
+ try { es.close() } catch { }
2513
2323
  es = null
2514
2324
  }
2515
2325
  }
@@ -2615,9 +2425,6 @@ window.__ModuleLoader__.load({
2615
2425
  }
2616
2426
  }, [load, t])
2617
2427
 
2618
- /**
2619
- * Confirm and execute card move transition.
2620
- */
2621
2428
  const askMove = React.useCallback((task, column, where) => {
2622
2429
  if (!task) return
2623
2430
  const place = where ?? neighboursFor(tasksOf((state && state.tasks) || [], column), task.id, Infinity)
@@ -2688,6 +2495,19 @@ window.__ModuleLoader__.load({
2688
2495
  h('span', null, t('board.back')),
2689
2496
  ),
2690
2497
  h('span', { className: 'dkb-barTitle' }, t('section.label')),
2498
+ state && state.remoteMode
2499
+ ? h('span', {
2500
+ className: 'dkb-tag',
2501
+ style: {
2502
+ background: 'var(--dsw-alias-state-business-tertiary, #ddf4ff)',
2503
+ color: 'var(--dsw-alias-state-business-primary, #0969da)',
2504
+ borderColor: 'var(--dsw-alias-state-business-tertiary, #54aeff)',
2505
+ fontWeight: '600',
2506
+ fontSize: '11px',
2507
+ },
2508
+ title: 'Operating in remote mode against server 111',
2509
+ }, '🌐 ' + t('remote.badge'))
2510
+ : null,
2691
2511
  boards.length > 1
2692
2512
  ? h('select', {
2693
2513
  className: 'dkb-input', style: { width: 'auto' }, value: board,
@@ -3055,6 +2875,11 @@ window.__ModuleLoader__.load({
3055
2875
  } catch (e) { setError(t(e.key || 'error.unknown')) }
3056
2876
  },
3057
2877
  onOpenChat: (x) => {
2878
+ if (state && state.remoteMode && x.sessionId) {
2879
+ const url = 'http://192.168.1.111:3080/#/chat?sessionId=' + encodeURIComponent(x.sessionId)
2880
+ if (typeof window !== 'undefined') window.open(url, '_blank')
2881
+ return
2882
+ }
3058
2883
  if (!openSession(props.ctx, x.sessionId)) {
3059
2884
  setError(t('error.sessionNotOpened', { id: x.sessionId }))
3060
2885
  return
@@ -3129,7 +2954,7 @@ window.__ModuleLoader__.load({
3129
2954
  method: 'POST',
3130
2955
  body: JSON.stringify({ reasoningEffort: val }),
3131
2956
  })
3132
- } catch { /* update reasoning effort best-effort */ }
2957
+ } catch { }
3133
2958
  },
3134
2959
  },
3135
2960
  h('option', { value: '' }, t('reasoning.default')),
@@ -4080,7 +3905,7 @@ window.__ModuleLoader__.load({
4080
3905
  }, t('panel.cleanupWorktree'))
4081
3906
  : null,
4082
3907
  ),
4083
- (openTask.worktree || openTask.rejectReason || (Array.isArray(openTask.runs) && openTask.runs.length > 0)) ? h('button', {
3908
+ (openTask.worktree || openTask.rejectReason || openTask.stalled || (Array.isArray(openTask.runs) && openTask.runs.length > 0)) ? h('button', {
4084
3909
  type: 'button',
4085
3910
  className: 'dkb-secondary',
4086
3911
  style: { marginRight: '8px', border: '1px solid var(--dsw-alias-border-l2)' },
@@ -4640,9 +4465,6 @@ window.__ModuleLoader__.load({
4640
4465
  )
4641
4466
  }
4642
4467
 
4643
- /**
4644
- * Header task chip in chat conversation. Only rendered when session is linked to a task.
4645
- */
4646
4468
  function TaskChip(props) {
4647
4469
  const t = props.t || fallbackT
4648
4470
  const [task, setTask] = React.useState(null)
@@ -4674,7 +4496,7 @@ window.__ModuleLoader__.load({
4674
4496
  })
4675
4497
  setTask(out.task)
4676
4498
  setOpen(false)
4677
- } catch { /* board handles error; keep header intact */ }
4499
+ } catch { }
4678
4500
  }, [task])
4679
4501
 
4680
4502
  if (!task) return null
@@ -4717,7 +4539,7 @@ window.__ModuleLoader__.load({
4717
4539
  setTask(out.task)
4718
4540
  setNote('')
4719
4541
  setOpen(false)
4720
- } catch { /* board handles error; keep header intact */ } finally {
4542
+ } catch { } finally {
4721
4543
  setSaving(false)
4722
4544
  }
4723
4545
  },
@@ -4726,8 +4548,6 @@ window.__ModuleLoader__.load({
4726
4548
  )
4727
4549
  }
4728
4550
 
4729
-
4730
-
4731
4551
  const PANEL = 'kanban'
4732
4552
  const ACTIVATE_EVENT = 'dsh-panel-activate'
4733
4553
  const ACTIVE_ATTR = 'data-dsh-kanban-active'
@@ -4740,8 +4560,6 @@ window.__ModuleLoader__.load({
4740
4560
  + ' stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">'
4741
4561
  + '<rect x="2" y="2.5" width="12" height="11" rx="1.5"/><path d="M2 6.5h12M6.5 6.5v7"/></svg>'
4742
4562
 
4743
- /** Board visibility state manager. */
4744
- /** @see helpers.createToggle */
4745
4563
  function createToggle() {
4746
4564
  let open = false
4747
4565
  let wanted = undefined
@@ -4754,12 +4572,10 @@ window.__ModuleLoader__.load({
4754
4572
  for (const fn of listeners) fn()
4755
4573
  },
4756
4574
  toggle() { this.set(!open) },
4757
- /** Open board focused on a specific task. */
4758
4575
  show(taskId, boardId) {
4759
4576
  wanted = { id: String(taskId || ''), board: String(boardId || 'main') }
4760
4577
  if (open) { for (const fn of listeners) fn() } else this.set(true)
4761
4578
  },
4762
- /** Consume requested focus task ID (cleared after first read). */
4763
4579
  takeWanted() {
4764
4580
  const asked = wanted
4765
4581
  wanted = undefined
@@ -4769,7 +4585,6 @@ window.__ModuleLoader__.load({
4769
4585
  }
4770
4586
  }
4771
4587
 
4772
- /** Find native 'New Session' sidebar button. */
4773
4588
  function nativeButton() {
4774
4589
  const nodes = document.querySelectorAll('button[class*="newSession"]')
4775
4590
  for (const node of nodes) {
@@ -4778,9 +4593,6 @@ window.__ModuleLoader__.load({
4778
4593
  return undefined
4779
4594
  }
4780
4595
 
4781
- /**
4782
- * Kanban entry button cloning native button styles to match host theme.
4783
- */
4784
4596
  function createEntry(target, toggle, label) {
4785
4597
  const entry = target.cloneNode(true)
4786
4598
  entry.className = String(target.className) + ' dkb-entry-clone'
@@ -4809,7 +4621,6 @@ window.__ModuleLoader__.load({
4809
4621
  return entry
4810
4622
  }
4811
4623
 
4812
- /** Place button at end of injected extension entries. */
4813
4624
  function placeEntry(target, entry) {
4814
4625
  let anchor = target
4815
4626
  let next = target.nextElementSibling
@@ -4822,7 +4633,6 @@ window.__ModuleLoader__.load({
4822
4633
  return true
4823
4634
  }
4824
4635
 
4825
- /** Sidebar entry button persistent across shell re-renders. */
4826
4636
  function mountSidebarEntry(toggle, label) {
4827
4637
  if (typeof document === 'undefined') return () => {}
4828
4638
 
@@ -4856,7 +4666,6 @@ window.__ModuleLoader__.load({
4856
4666
  }
4857
4667
  }
4858
4668
 
4859
- /** Board overlay container mounted over conversation column. */
4860
4669
  function mountBoard(ctx, toggle) {
4861
4670
  if (typeof document === 'undefined') return () => {}
4862
4671
  let root
@@ -4913,12 +4722,6 @@ window.__ModuleLoader__.load({
4913
4722
  }
4914
4723
  }
4915
4724
 
4916
-
4917
- /**
4918
- * Register component into first available UI slot.
4919
- *
4920
- * @returns {string|undefined} name of successfully resolved slot
4921
- */
4922
4725
  function registerFirst(ctx, candidates, component) {
4923
4726
  for (const entry of candidates) {
4924
4727
  try {
@@ -4933,7 +4736,7 @@ window.__ModuleLoader__.load({
4933
4736
  if (registeredInSlot || (res !== null && res !== false && res !== undefined)) {
4934
4737
  return entry.name
4935
4738
  }
4936
- } catch { /* slot missing in build — try next */ }
4739
+ } catch { }
4937
4740
  }
4938
4741
  return undefined
4939
4742
  }
@@ -4946,11 +4749,6 @@ window.__ModuleLoader__.load({
4946
4749
  refreshMirrorUntilVisible(ctx)
4947
4750
  const SettingsErrorBoundary = createErrorBoundary()
4948
4751
  const SafeSettingsCard = (props) => h(SettingsErrorBoundary, null, h(KanbanSettingsCard, props))
4949
- // List seat (plugins.item): the seat the Plugins page renders as the plugin's own
4950
- // page with its configuration. Registered separately from the fallback chain
4951
- // below — the seat is additive, not an either/or candidate. The label is a static
4952
- // string on purpose: it is resolved while the page renders, and a locale lookup
4953
- // there would take the whole client batch down with it.
4954
4752
  const trySlot = (name, register) => {
4955
4753
  try {
4956
4754
  if (typeof ctx.slots?.inject === 'function') ctx.slots.inject(name, register)
@@ -4959,9 +4757,6 @@ window.__ModuleLoader__.load({
4959
4757
  console.warn('[dsh-kanban] slot registration failed for ' + name + ':', e && e.message)
4960
4758
  }
4961
4759
  }
4962
-
4963
- // #274: Newest slot first, older slots as fallbacks:
4964
- // 1. plugins.row.config (Row seat on Plugins page, DSH 0.1.6-alpha.2)
4965
4760
  let cardSlot = undefined
4966
4761
  trySlot('plugins.row.config', () => {
4967
4762
  ctx.slots.register(
@@ -4975,8 +4770,6 @@ window.__ModuleLoader__.load({
4975
4770
  )
4976
4771
  if (!cardSlot) cardSlot = 'plugins.row.config'
4977
4772
  })
4978
-
4979
- // 2. plugins.item (Official group on Plugins page)
4980
4773
  trySlot('plugins.item', () => {
4981
4774
  ctx.slots.register(
4982
4775
  {
@@ -4990,8 +4783,6 @@ window.__ModuleLoader__.load({
4990
4783
  SafeSettingsCard,
4991
4784
  )
4992
4785
  })
4993
-
4994
- // 3. settings.plugin.item (legacy fallback for older cores)
4995
4786
  trySlot('settings.plugin.item', () => {
4996
4787
  ctx.slots.register(
4997
4788
  {