@linxin666/dsh-client-ui-task-board 0.4.3 → 0.4.5

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.
Files changed (128) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +56 -18
  3. package/README.zh.md +56 -18
  4. package/lib/client.js +6242 -958
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +3475 -234
  7. package/lib/types/client/PluginSettingsCard.d.ts +7 -0
  8. package/lib/types/client/TaskBoardSettingsCard.d.ts +19 -3
  9. package/lib/types/client/board/ClampedMarkdown.d.ts +3 -0
  10. package/lib/types/client/board/ConfirmDialog.d.ts +4 -1
  11. package/lib/types/client/board/EditTaskModal.d.ts +5 -2
  12. package/lib/types/client/board/LinkSubtaskModal.d.ts +5 -2
  13. package/lib/types/client/board/NewTaskModal.d.ts +4 -1
  14. package/lib/types/client/board/TagManagerModal.d.ts +26 -0
  15. package/lib/types/client/board/TaskBoard.d.ts +10 -1
  16. package/lib/types/client/board/TaskCard.d.ts +14 -1
  17. package/lib/types/client/board/TaskDetail.d.ts +3 -1
  18. package/lib/types/client/board/TaskForm.d.ts +23 -2
  19. package/lib/types/client/board/VerificationReport.d.ts +23 -0
  20. package/lib/types/client/board/card-view.d.ts +113 -0
  21. package/lib/types/client/board/icons.d.ts +32 -0
  22. package/lib/types/client/board/overlay.d.ts +69 -0
  23. package/lib/types/client/board/preset-label.d.ts +22 -0
  24. package/lib/types/client/board/status-key.d.ts +15 -0
  25. package/lib/types/client/board/task-markdown.d.ts +19 -0
  26. package/lib/types/client/board-card-seat.d.ts +34 -0
  27. package/lib/types/client/host-api.d.ts +24 -1
  28. package/lib/types/client/index.d.ts +19 -0
  29. package/lib/types/client/locales.d.ts +141 -3
  30. package/lib/types/client/native-panel.d.ts +6 -20
  31. package/lib/types/client/schedule-zone.d.ts +47 -0
  32. package/lib/types/client/seats.d.ts +18 -0
  33. package/lib/types/client/service.d.ts +32 -0
  34. package/lib/types/client/settings-form.d.ts +16 -0
  35. package/lib/types/core/controller.d.ts +38 -1
  36. package/lib/types/core/degraded-reason.d.ts +46 -0
  37. package/lib/types/core/extension.d.ts +283 -0
  38. package/lib/types/core/handover.d.ts +13 -1
  39. package/lib/types/core/issue-form.d.ts +16 -0
  40. package/lib/types/core/package-name.d.ts +9 -0
  41. package/lib/types/core/schedule.d.ts +95 -20
  42. package/lib/types/core/subtask.d.ts +30 -11
  43. package/lib/types/core/tasks.d.ts +110 -5
  44. package/lib/types/core/use-cases/task-create.d.ts +2 -1
  45. package/lib/types/core/use-cases/task-schedule.d.ts +8 -4
  46. package/lib/types/core/use-cases/task-tag.d.ts +48 -0
  47. package/lib/types/core/use-cases/task-update.d.ts +5 -3
  48. package/lib/types/core/verification.d.ts +386 -0
  49. package/lib/types/core/workspace-target.d.ts +41 -0
  50. package/lib/types/host/agent-tools.d.ts +1 -1
  51. package/lib/types/host/extension-registry.d.ts +111 -0
  52. package/lib/types/host/llm-dispatch.d.ts +63 -0
  53. package/lib/types/host/verification-gate.d.ts +71 -0
  54. package/lib/types/host/verification-runner.d.ts +108 -0
  55. package/lib/types/host/workspace-evidence.d.ts +53 -0
  56. package/lib/types/host-ledger.d.ts +107 -11
  57. package/lib/types/host-runner.d.ts +99 -4
  58. package/lib/types/host-service.d.ts +114 -2
  59. package/lib/types/index.d.ts +80 -26
  60. package/lib/types/protocol.d.ts +53 -3
  61. package/lib/types/tool-surface.d.ts +71 -0
  62. package/package.json +27 -26
  63. package/src/client/PluginSettingsCard.tsx +9 -0
  64. package/src/client/TaskBoardSettingsCard.tsx +304 -65
  65. package/src/client/board/ClampedMarkdown.tsx +69 -0
  66. package/src/client/board/ConfirmDialog.tsx +7 -3
  67. package/src/client/board/EditTaskModal.tsx +5 -2
  68. package/src/client/board/LinkSubtaskModal.tsx +30 -15
  69. package/src/client/board/NewTaskModal.tsx +288 -151
  70. package/src/client/board/TagManagerModal.tsx +235 -0
  71. package/src/client/board/TaskBoard.tsx +259 -103
  72. package/src/client/board/TaskCard.tsx +238 -86
  73. package/src/client/board/TaskDetail.tsx +154 -59
  74. package/src/client/board/TaskForm.tsx +61 -6
  75. package/src/client/board/VerificationReport.tsx +203 -0
  76. package/src/client/board/card-view.ts +220 -0
  77. package/src/client/board/icons.tsx +89 -0
  78. package/src/client/board/overlay.tsx +189 -0
  79. package/src/client/board/preset-label.ts +44 -0
  80. package/src/client/board/status-key.ts +27 -0
  81. package/src/client/board/task-markdown.module.css +137 -0
  82. package/src/client/board/task-markdown.tsx +106 -0
  83. package/src/client/board-card-seat.ts +102 -0
  84. package/src/client/board-settings.module.css +66 -0
  85. package/src/client/board.module.css +602 -68
  86. package/src/client/host-api.ts +109 -6
  87. package/src/client/index.ts +48 -6
  88. package/src/client/locales.ts +290 -12
  89. package/src/client/native-panel.tsx +18 -2
  90. package/src/client/schedule-zone.ts +135 -0
  91. package/src/client/seats.tsx +58 -0
  92. package/src/client/service.ts +94 -0
  93. package/src/client/settings-card.module.css +31 -5
  94. package/src/client/settings-form.ts +27 -1
  95. package/src/core/controller.ts +101 -5
  96. package/src/core/degraded-reason.ts +81 -0
  97. package/src/core/extension.ts +376 -0
  98. package/src/core/freeze-snapshot.ts +2 -2
  99. package/src/core/handover.ts +16 -1
  100. package/src/core/issue-form.ts +19 -0
  101. package/src/core/package-name.ts +9 -0
  102. package/src/core/schedule.ts +211 -55
  103. package/src/core/store.ts +26 -1
  104. package/src/core/subtask.ts +77 -22
  105. package/src/core/tasks.ts +138 -10
  106. package/src/core/use-cases/task-create.ts +14 -2
  107. package/src/core/use-cases/task-parent.ts +3 -2
  108. package/src/core/use-cases/task-schedule.ts +17 -6
  109. package/src/core/use-cases/task-tag.ts +110 -0
  110. package/src/core/use-cases/task-update.ts +13 -4
  111. package/src/core/verification.ts +826 -0
  112. package/src/core/workspace-target.ts +59 -0
  113. package/src/host/agent-tools.ts +58 -24
  114. package/src/host/extension-registry.ts +509 -0
  115. package/src/host/llm-dispatch.ts +87 -0
  116. package/src/host/verification-gate.ts +331 -0
  117. package/src/host/verification-runner.ts +535 -0
  118. package/src/host/workspace-evidence.ts +176 -0
  119. package/src/host-ai.ts +15 -9
  120. package/src/host-ledger.ts +342 -51
  121. package/src/host-routes.ts +15 -2
  122. package/src/host-runner.ts +219 -20
  123. package/src/host-service.ts +346 -26
  124. package/src/index.ts +204 -16
  125. package/src/protocol.ts +122 -9
  126. package/src/tool-surface.ts +95 -0
  127. package/lib/types/host/run-guarded.d.ts +0 -39
  128. package/src/host/run-guarded.ts +0 -76
@@ -30,7 +30,21 @@
30
30
 
31
31
  /* --- board frame -------------------------------------------------------------- */
32
32
 
33
+ /* Board geometry and motion tokens. Every single-line control on the board —
34
+ the header's buttons, its pickers, its search box, the form inputs — takes
35
+ its height, radius and spacing from this one place, so a control never
36
+ drifts off the header's shared track as its padding is tuned. Declared on
37
+ the board root, which also contains every overlay the board mounts. */
33
38
  .board {
39
+ --dsh-tb-control-h: 30px;
40
+ --dsh-tb-control-radius: 8px;
41
+ --dsh-tb-control-gap: 8px;
42
+ --dsh-tb-icon-size: 30px;
43
+ --dsh-tb-hit-size: 44px;
44
+ --dsh-tb-motion-enter: 160ms;
45
+ --dsh-tb-motion-close: 140ms;
46
+ --dsh-tb-ease: cubic-bezier(0.2, 0, 0, 1);
47
+
34
48
  display: flex;
35
49
  flex-direction: column;
36
50
  box-sizing: border-box;
@@ -51,6 +65,20 @@
51
65
  flex: none;
52
66
  }
53
67
 
68
+ /* The right-hand toolbar. Filters, view toggles and the create action ride one
69
+ row on the shared control height, pushed against the trailing edge by their
70
+ own container so the identity block (title + host meta) and the toolbar read
71
+ as two groups instead of one undifferentiated strip. */
72
+ .boardTools {
73
+ display: flex;
74
+ align-items: center;
75
+ gap: var(--dsh-tb-control-gap);
76
+ flex: 1 1 auto;
77
+ min-width: 0;
78
+ justify-content: flex-end;
79
+ margin-left: auto;
80
+ }
81
+
54
82
  .boardTitle {
55
83
  margin: 0;
56
84
  font-size: 16px;
@@ -68,12 +96,14 @@
68
96
  .search {
69
97
  flex: 0 1 260px;
70
98
  min-width: 120px;
71
- padding: 6px 10px;
99
+ box-sizing: border-box;
100
+ height: var(--dsh-tb-control-h);
101
+ padding: 0 10px;
72
102
  font-size: 13px;
73
103
  color: var(--dsw-alias-label-primary);
74
104
  background: var(--dsw-specific-input-major);
75
105
  border: 1px solid var(--dsw-alias-border-l2);
76
- border-radius: 8px;
106
+ border-radius: var(--dsh-tb-control-radius);
77
107
  outline: none;
78
108
  }
79
109
 
@@ -190,11 +220,17 @@
190
220
 
191
221
  /* --- cards -------------------------------------------------------------------- */
192
222
 
223
+ /* The card is an article; the stretched .cardOpen button is its keyboard and
224
+ accessibility target. Content sits above the button (z-index 1) so the
225
+ badges keep their own tooltips, and the article's click handler opens the
226
+ detail for any click that is not a quick action. */
193
227
  .card {
228
+ position: relative;
194
229
  display: flex;
230
+ flex: none;
195
231
  flex-direction: column;
196
232
  gap: 6px;
197
- padding: 10px 12px;
233
+ padding: 10px 12px 10px 14px;
198
234
  text-align: left;
199
235
  background: var(--dsw-alias-bg-base);
200
236
  border: 1px solid var(--dsw-alias-border-l2);
@@ -202,20 +238,87 @@
202
238
  cursor: pointer;
203
239
  color: var(--dsw-alias-label-primary);
204
240
  font-family: inherit;
241
+ overflow: hidden;
205
242
  transition: box-shadow 120ms ease, border-color 120ms ease, transform 120ms ease;
206
243
  }
207
244
 
245
+ .card > :not(.cardOpen) {
246
+ position: relative;
247
+ z-index: 1;
248
+ }
249
+
250
+ /* State stripe: the latest execution's outcome, readable while scanning a
251
+ column without reading the run count. No tone (never ran, archived) paints
252
+ nothing. */
253
+ .card::before {
254
+ content: '';
255
+ position: absolute;
256
+ inset: 0 auto 0 0;
257
+ width: 3px;
258
+ background: transparent;
259
+ pointer-events: none;
260
+ }
261
+
262
+ .card[data-tone='running']::before { background: var(--dsw-alias-state-warn-primary); }
263
+ .card[data-tone='succeeded']::before { background: var(--dsw-alias-state-success-primary); }
264
+ .card[data-tone='failed']::before { background: var(--dsw-alias-state-error-primary); }
265
+ .card[data-tone='cancelled']::before { background: var(--dsw-alias-label-tertiary); }
266
+
208
267
  .card:hover {
209
268
  box-shadow: var(--dsw-shadow-lv2);
210
269
  border-color: var(--dsw-alias-border-l3);
211
270
  transform: translateY(-1px);
212
271
  }
213
272
 
273
+ /* Dense columns scroll fast; a lift per hovered row reads as jitter. */
274
+ .card[data-compact]:hover {
275
+ box-shadow: none;
276
+ transform: none;
277
+ }
278
+
214
279
  .card[data-status='running'] {
215
280
  border-color: var(--dsw-alias-state-warn-primary);
216
281
  }
217
282
 
283
+ .cardOpen {
284
+ position: absolute;
285
+ inset: 0;
286
+ z-index: 0;
287
+ margin: 0;
288
+ padding: 0;
289
+ border: 0;
290
+ border-radius: inherit;
291
+ background: transparent;
292
+ cursor: pointer;
293
+ }
294
+
295
+ .cardOpen:focus-visible {
296
+ outline: 2px solid var(--dsw-alias-state-business-primary);
297
+ outline-offset: -2px;
298
+ }
299
+
300
+ .cardTitleRow {
301
+ display: flex;
302
+ align-items: flex-start;
303
+ gap: 6px;
304
+ min-width: 0;
305
+ }
306
+
307
+ .cardKind {
308
+ flex: none;
309
+ margin-top: 1px;
310
+ padding: 0 6px;
311
+ border-radius: 4px;
312
+ font-size: 10px;
313
+ font-weight: 600;
314
+ line-height: 16px;
315
+ white-space: nowrap;
316
+ color: var(--dsw-alias-label-secondary);
317
+ background: var(--dsw-alias-interactive-bg-hover);
318
+ }
319
+
218
320
  .cardTitle {
321
+ min-width: 0;
219
322
  font-size: 13px;
220
323
  font-weight: 600;
221
324
  line-height: 1.35;
@@ -225,6 +328,31 @@
225
328
  -webkit-box-orient: vertical;
226
329
  }
227
330
 
331
+ .card[data-compact] {
332
+ gap: 2px;
333
+ padding-top: 6px;
334
+ padding-bottom: 6px;
335
+ }
336
+
337
+ .card[data-compact] .cardTitle {
338
+ font-size: 12px;
339
+ font-weight: 500;
340
+ -webkit-line-clamp: 1;
341
+ }
342
+
343
+ /* Provider decorations join the meta row as inline chips, never a full-width
344
+ row of their own; an empty decoration takes no space. */
345
+ .cardDecoration {
346
+ display: inline-flex;
347
+ align-items: center;
348
+ gap: 4px;
349
+ flex: none;
350
+ }
351
+
352
+ .cardDecoration:empty {
353
+ display: none;
354
+ }
355
+
228
356
  .cardExcerpt {
229
357
  font-size: 12px;
230
358
  line-height: 1.4;
@@ -235,22 +363,31 @@
235
363
  -webkit-box-orient: vertical;
236
364
  }
237
365
 
366
+ /* The meta line holds values that all have to stay readable — the update
367
+ stamp, the label/schedule/roll-up chips and the run count — so it wraps
368
+ instead of squeezing the stamp down to an unreadable stub on a narrow
369
+ column, and the stamp itself keeps its full width. */
238
370
  .cardMeta {
239
371
  display: flex;
240
372
  align-items: center;
241
- gap: 8px;
373
+ flex-wrap: wrap;
374
+ gap: 4px 8px;
242
375
  font-size: 11px;
243
376
  color: var(--dsw-alias-label-tertiary);
244
377
  }
245
378
 
246
379
  .cardTime {
247
- flex: 1;
380
+ flex: 1 1 auto;
381
+ min-width: max-content;
248
382
  overflow: hidden;
249
383
  text-overflow: ellipsis;
250
384
  white-space: nowrap;
251
385
  }
252
386
 
253
387
  .cardSchedule {
388
+ display: inline-flex;
389
+ align-items: center;
390
+ gap: 4px;
254
391
  flex: none;
255
392
  min-width: 0;
256
393
  font-size: 12px;
@@ -266,6 +403,11 @@
266
403
  flex: none;
267
404
  }
268
405
 
406
+ .cardRun[data-declared] {
407
+ text-decoration: underline dotted;
408
+ text-underline-offset: 2px;
409
+ }
410
+
269
411
  .cardRun[data-result='failed'] {
270
412
  color: var(--dsw-alias-state-error-primary);
271
413
  }
@@ -275,10 +417,88 @@
275
417
  }
276
418
 
277
419
  .cardSession {
420
+ display: inline-flex;
421
+ align-items: center;
278
422
  flex: none;
279
423
  color: var(--dsw-alias-state-business-primary);
280
424
  }
281
425
 
426
+ /* A quick action on a card: a compact icon button above the open control. */
427
+ .cardAction {
428
+ justify-content: center;
429
+ width: 22px;
430
+ height: 22px;
431
+ margin: -4px -4px -4px 0;
432
+ padding: 0;
433
+ border: 0;
434
+ border-radius: 6px;
435
+ background: transparent;
436
+ cursor: pointer;
437
+ font-family: inherit;
438
+ }
439
+
440
+ .cardAction:hover {
441
+ background: var(--dsw-alias-interactive-bg-hover);
442
+ }
443
+
444
+ .cardAction:focus-visible {
445
+ outline: 2px solid var(--dsw-alias-state-business-primary);
446
+ outline-offset: 1px;
447
+ }
448
+
449
+ /* Recency groups inside a settled column. */
450
+ .cardGroup {
451
+ display: flex;
452
+ flex-direction: column;
453
+ gap: 6px;
454
+ }
455
+
456
+ .cardGroupHeader {
457
+ display: flex;
458
+ align-items: center;
459
+ gap: 6px;
460
+ padding: 4px 2px 0;
461
+ font-size: 11px;
462
+ font-weight: 600;
463
+ color: var(--dsw-alias-label-tertiary);
464
+ }
465
+
466
+ .cardGroupCount {
467
+ font-weight: 400;
468
+ }
469
+
470
+ .cardGroupHeader .linkButton {
471
+ margin-left: auto;
472
+ font-size: 11px;
473
+ }
474
+
475
+ /* Per-column density switch in the column header. */
476
+ .densityToggle {
477
+ display: inline-flex;
478
+ align-items: center;
479
+ justify-content: center;
480
+ flex: none;
481
+ width: 22px;
482
+ height: 22px;
483
+ padding: 0;
484
+ border: 0;
485
+ border-radius: 6px;
486
+ background: transparent;
487
+ color: var(--dsw-alias-label-tertiary);
488
+ cursor: pointer;
489
+ }
490
+
491
+ .densityToggle:hover,
492
+ .densityToggle[data-active='true'] {
493
+ color: var(--dsw-alias-label-primary);
494
+ background: var(--dsw-alias-interactive-bg-hover);
495
+ }
496
+
497
+ .densityToggle:focus-visible {
498
+ outline: 2px solid var(--dsw-alias-state-business-primary);
499
+ outline-offset: 1px;
500
+ }
501
+
282
502
  .cardRunningLabel {
283
503
  font-size: 11px;
284
504
  color: var(--dsw-alias-state-warn-primary);
@@ -300,57 +520,74 @@
300
520
 
301
521
  /* --- buttons ------------------------------------------------------------------ */
302
522
 
303
- .primaryButton {
304
- padding: 6px 14px;
305
- font-size: 13px;
306
- font-weight: 600;
307
- color: var(--dsw-alias-label-primary-foreground);
308
- background: var(--dsw-alias-button-info-fill);
309
- border: none;
310
- border-radius: 8px;
523
+ /* One geometry, three roles. Every filled, outlined and icon control on the
524
+ board takes its height, radius and inner alignment from the board tokens, so
525
+ the header's buttons, the detail footer's actions and every modal footer sit
526
+ on one track, and a state change (selected, disabled, pending) can never
527
+ resize the control. Filled actions use the matched primary-action set
528
+ (button-primary-fill / button-primary-hover / label-primary-foreground) and
529
+ never a cross-family pair. */
530
+ .primaryButton,
531
+ .ghostButton,
532
+ .dangerButton,
533
+ .iconButton {
534
+ display: inline-flex;
535
+ align-items: center;
536
+ justify-content: center;
537
+ gap: 6px;
538
+ box-sizing: border-box;
539
+ height: var(--dsh-tb-control-h);
540
+ border-radius: var(--dsh-tb-control-radius);
311
541
  cursor: pointer;
312
542
  white-space: nowrap;
543
+ font-family: inherit;
313
544
  }
314
545
 
315
- .primaryButton:hover:not(:disabled) {
316
- background: var(--dsw-alias-button-info-hover);
546
+ .primaryButton,
547
+ .dangerButton {
548
+ padding: 0 14px;
549
+ font-size: 13px;
550
+ font-weight: 600;
317
551
  }
318
552
 
319
- .primaryButton:disabled {
320
- opacity: 0.5;
321
- cursor: default;
553
+ .primaryButton {
554
+ color: var(--dsw-alias-label-primary-foreground);
555
+ background: var(--dsw-alias-button-primary-fill);
556
+ border: 1px solid transparent;
557
+ }
558
+
559
+ .dangerButton {
560
+ color: var(--dsw-alias-label-primary-foreground);
561
+ background: var(--dsw-alias-state-error-primary);
562
+ border: 1px solid transparent;
322
563
  }
323
564
 
324
565
  .ghostButton {
325
- padding: 5px 12px;
326
- font-size: 12px;
566
+ padding: 0 12px;
567
+ font-size: 13px;
327
568
  color: var(--dsw-alias-label-primary);
328
569
  background: transparent;
329
570
  border: 1px solid var(--dsw-alias-border-l2);
330
- border-radius: 8px;
331
- cursor: pointer;
332
- white-space: nowrap;
333
571
  }
334
572
 
335
- .ghostButton:hover:not(:disabled) {
336
- background: var(--dsw-alias-interactive-bg-hover);
573
+ /* Selected view toggle. The outlined control keeps its box and only changes
574
+ role colour, so pressing a toggle cannot move the toolbar around it. */
575
+ .ghostButton[data-active='true'] {
576
+ color: var(--dsw-alias-label-primary-foreground);
577
+ background: var(--dsw-alias-button-primary-fill);
578
+ border-color: transparent;
337
579
  }
338
580
 
339
- .ghostButton:disabled {
340
- opacity: 0.45;
341
- cursor: default;
581
+ .primaryButton:hover:not(:disabled) {
582
+ background: var(--dsw-alias-button-primary-hover);
342
583
  }
343
584
 
344
- .dangerButton {
345
- padding: 6px 14px;
346
- font-size: 13px;
347
- font-weight: 600;
348
- color: #fff;
349
- background: var(--dsw-alias-state-error-primary);
350
- border: none;
351
- border-radius: 8px;
352
- cursor: pointer;
353
- white-space: nowrap;
585
+ .ghostButton:hover:not(:disabled) {
586
+ background: var(--dsw-alias-interactive-bg-hover);
587
+ }
588
+
589
+ .ghostButton[data-active='true']:hover:not(:disabled) {
590
+ background: var(--dsw-alias-button-primary-hover);
354
591
  }
355
592
 
356
593
  .dangerButton:hover:not(:disabled) {
@@ -361,31 +598,48 @@
361
598
  filter: brightness(0.94);
362
599
  }
363
600
 
601
+ .primaryButton:disabled,
364
602
  .dangerButton:disabled {
365
603
  opacity: 0.5;
366
604
  cursor: default;
367
605
  }
368
606
 
607
+ .ghostButton:disabled {
608
+ opacity: 0.45;
609
+ cursor: default;
610
+ }
611
+
612
+ /* Icon-only control: a 30px box on the control track whose pointer target is
613
+ expanded to the 44px minimum without changing what the eye sees. */
369
614
  .iconButton {
370
- display: inline-flex;
371
- align-items: center;
372
- justify-content: center;
373
- width: 26px;
374
- height: 26px;
615
+ position: relative;
616
+ width: var(--dsh-tb-icon-size);
375
617
  padding: 0;
376
618
  background: transparent;
377
- border: none;
378
- border-radius: 6px;
619
+ border: 1px solid transparent;
379
620
  color: var(--dsw-alias-label-secondary);
380
- cursor: pointer;
381
- font-size: 13px;
382
621
  }
383
622
 
384
- .iconButton:hover {
623
+ .iconButton::after {
624
+ content: '';
625
+ position: absolute;
626
+ left: 50%;
627
+ top: 50%;
628
+ width: var(--dsh-tb-hit-size);
629
+ height: var(--dsh-tb-hit-size);
630
+ transform: translate(-50%, -50%);
631
+ }
632
+
633
+ .iconButton:hover:not(:disabled) {
385
634
  background: var(--dsw-alias-interactive-bg-hover);
386
635
  color: var(--dsw-alias-label-primary);
387
636
  }
388
637
 
638
+ .iconButton:disabled {
639
+ opacity: 0.45;
640
+ cursor: default;
641
+ }
642
+
389
643
  .linkButton {
390
644
  padding: 0;
391
645
  font-size: 12px;
@@ -402,6 +656,12 @@
402
656
 
403
657
  /* --- modals ------------------------------------------------------------------- */
404
658
 
659
+ /* Enter and exit are one motion pair: the backdrop fades while the surface
660
+ rises from exactly the origin it returns to (8px down, 98% scale, centred).
661
+ `usePresence` keeps the node mounted through the exit leg and paints
662
+ `data-state="closing"`, so the reverse leg always runs instead of the node
663
+ vanishing; both legs keep the surface's dimensions, so nothing behind the
664
+ overlay reflows. */
405
665
  .modalBackdrop {
406
666
  position: fixed;
407
667
  inset: 0;
@@ -410,15 +670,50 @@
410
670
  align-items: center;
411
671
  justify-content: center;
412
672
  background: var(--dsw-alias-bg-mask-1);
673
+ animation: dshTbBackdropIn var(--dsh-tb-motion-enter) var(--dsh-tb-ease) both;
674
+ }
675
+
676
+ .modalBackdrop[data-state='closing'] {
677
+ animation: dshTbBackdropOut var(--dsh-tb-motion-close) var(--dsh-tb-ease) both;
678
+ }
679
+
680
+ .modal,
681
+ .detail {
682
+ animation: dshTbSurfaceIn var(--dsh-tb-motion-enter) var(--dsh-tb-ease) both;
683
+ }
684
+
685
+ .modalBackdrop[data-state='closing'] .modal,
686
+ .modalBackdrop[data-state='closing'] .detail {
687
+ animation: dshTbSurfaceOut var(--dsh-tb-motion-close) var(--dsh-tb-ease) both;
688
+ }
689
+
690
+ @keyframes dshTbBackdropIn {
691
+ from { opacity: 0; }
692
+ to { opacity: 1; }
693
+ }
694
+
695
+ @keyframes dshTbBackdropOut {
696
+ from { opacity: 1; }
697
+ to { opacity: 0; }
698
+ }
699
+
700
+ @keyframes dshTbSurfaceIn {
701
+ from { opacity: 0; transform: translateY(8px) scale(0.98); }
702
+ to { opacity: 1; transform: none; }
703
+ }
704
+
705
+ @keyframes dshTbSurfaceOut {
706
+ from { opacity: 1; transform: none; }
707
+ to { opacity: 0; transform: translateY(8px) scale(0.98); }
413
708
  }
414
709
 
415
710
  .modal {
416
711
  display: flex;
417
712
  flex-direction: column;
418
- gap: 12px;
713
+ gap: 10px;
419
714
  width: min(520px, calc(100vw - 48px));
420
715
  max-height: calc(100vh - 96px);
421
- overflow-y: auto;
716
+ overflow: hidden;
422
717
  padding: 18px;
423
718
  background: var(--dsw-alias-bg-base);
424
719
  border: 1px solid var(--dsw-alias-border-l2);
@@ -449,6 +744,154 @@
449
744
  margin-top: 4px;
450
745
  }
451
746
 
747
+ /* Label manager (label cleanup): a titled overlay with one row per label in
748
+ use, each carrying its usage count and the two ledger-wide transitions. */
749
+ .modalHeader {
750
+ display: flex;
751
+ align-items: center;
752
+ gap: 10px;
753
+ }
754
+
755
+ .modalHeader > .modalTitle {
756
+ flex: 1;
757
+ min-width: 0;
758
+ }
759
+
760
+ .tagManageList {
761
+ list-style: none;
762
+ margin: 0;
763
+ padding: 0;
764
+ display: flex;
765
+ flex-direction: column;
766
+ gap: 6px;
767
+ }
768
+
769
+ .tagManageRow {
770
+ display: flex;
771
+ align-items: center;
772
+ flex-wrap: wrap;
773
+ gap: 8px;
774
+ padding: 6px 8px;
775
+ border: 1px solid var(--dsw-alias-border-l1);
776
+ border-radius: var(--dsh-tb-control-radius);
777
+ }
778
+
779
+ .tagManageName {
780
+ flex: 1 1 auto;
781
+ min-width: 0;
782
+ overflow: hidden;
783
+ text-overflow: ellipsis;
784
+ white-space: nowrap;
785
+ font-size: 13px;
786
+ color: var(--dsw-alias-label-primary);
787
+ }
788
+
789
+ .tagManageCount {
790
+ flex: none;
791
+ font-size: 11px;
792
+ color: var(--dsw-alias-label-tertiary);
793
+ }
794
+
795
+ .tagManageInput {
796
+ flex: 1 1 auto;
797
+ min-width: 0;
798
+ }
799
+
800
+ .tagManageMerge {
801
+ flex: 1 0 100%;
802
+ font-size: 11px;
803
+ color: var(--dsw-alias-label-tertiary);
804
+ }
805
+
806
+ /* --- form sections (create/edit dialog) --------------------------------------
807
+ * The task form groups its configuration into collapsible regions so the
808
+ * dialog fits without a scrollbar: only the content region starts open, and
809
+ * every collapsed header carries a one-line summary of what it holds. The
810
+ * body keeps an overflow fallback for tiny viewports. The chevron is drawn
811
+ * with borders (no glyph), so no font or emoji can change it.
812
+ * ----------------------------------------------------------------------------- */
813
+
814
+ .modalBody {
815
+ display: flex;
816
+ flex-direction: column;
817
+ gap: 8px;
818
+ min-height: 0;
819
+ overflow-y: auto;
820
+ }
821
+
822
+ /* Every direct child keeps its natural height: a column flex item shrinks by
823
+ default, and a shrunk region would clip its own fields behind its
824
+ `overflow: hidden` instead of letting the body scroll. */
825
+ .modalBody > * {
826
+ flex: none;
827
+ }
828
+
829
+ .formSection {
830
+ display: flex;
831
+ flex-direction: column;
832
+ border: 1px solid var(--dsw-alias-separator-primary);
833
+ border-radius: 10px;
834
+ overflow: hidden;
835
+ }
836
+
837
+ .formSectionHeader {
838
+ display: flex;
839
+ align-items: center;
840
+ gap: 8px;
841
+ width: 100%;
842
+ padding: 7px 10px;
843
+ font-family: inherit;
844
+ font-size: 12.5px;
845
+ font-weight: 600;
846
+ color: var(--dsw-alias-label-primary);
847
+ text-align: left;
848
+ background: transparent;
849
+ border: 0;
850
+ cursor: pointer;
851
+ }
852
+
853
+ .formSectionHeader:hover {
854
+ background: var(--dsw-alias-bg-mask-1);
855
+ }
856
+
857
+ .formSectionChevron {
858
+ flex: none;
859
+ width: 0;
860
+ height: 0;
861
+ margin-left: 1px;
862
+ border-top: 4px solid transparent;
863
+ border-bottom: 4px solid transparent;
864
+ border-left: 5px solid var(--dsw-alias-label-tertiary);
865
+ transition: transform 120ms ease;
866
+ }
867
+
868
+ .formSection[data-open='true'] .formSectionChevron {
869
+ transform: rotate(90deg);
870
+ }
871
+
872
+ .formSectionTitle {
873
+ flex: none;
874
+ }
875
+
876
+ .formSectionSummary {
877
+ flex: 1 1 auto;
878
+ min-width: 0;
879
+ overflow: hidden;
880
+ font-weight: 400;
881
+ color: var(--dsw-alias-label-tertiary);
882
+ text-align: right;
883
+ text-overflow: ellipsis;
884
+ white-space: nowrap;
885
+ }
886
+
887
+ .formSectionBody {
888
+ display: flex;
889
+ flex-direction: column;
890
+ gap: 8px;
891
+ padding: 8px 10px 10px;
892
+ border-top: 1px solid var(--dsw-alias-separator-primary);
893
+ }
894
+
452
895
  .field {
453
896
  display: flex;
454
897
  flex-direction: column;
@@ -461,13 +904,18 @@
461
904
  color: var(--dsw-alias-label-secondary);
462
905
  }
463
906
 
907
+ /* Single-line fields (and the multi-line ones, which only grow past the
908
+ minimum) share the control height, the control radius and the control
909
+ border, so an input never sits taller than the button beside it. */
464
910
  .input {
465
- padding: 7px 10px;
911
+ box-sizing: border-box;
912
+ min-height: var(--dsh-tb-control-h);
913
+ padding: 6px 10px;
466
914
  font-size: 13px;
467
915
  color: var(--dsw-alias-label-primary);
468
916
  background: var(--dsw-specific-input-major);
469
917
  border: 1px solid var(--dsw-alias-border-l2);
470
- border-radius: 8px;
918
+ border-radius: var(--dsh-tb-control-radius);
471
919
  outline: none;
472
920
  resize: vertical;
473
921
  font-family: inherit;
@@ -478,12 +926,14 @@
478
926
  }
479
927
 
480
928
  .select {
481
- padding: 7px 10px;
929
+ box-sizing: border-box;
930
+ height: var(--dsh-tb-control-h);
931
+ padding: 0 10px;
482
932
  font-size: 13px;
483
933
  color: var(--dsw-alias-label-primary);
484
934
  background: var(--dsw-specific-input-major);
485
935
  border: 1px solid var(--dsw-alias-border-l2);
486
- border-radius: 8px;
936
+ border-radius: var(--dsh-tb-control-radius);
487
937
  outline: none;
488
938
  font-family: inherit;
489
939
  max-width: 100%;
@@ -626,15 +1076,32 @@
626
1076
 
627
1077
  .schedulePreset {
628
1078
  flex: none;
629
- padding: 7px 8px;
1079
+ box-sizing: border-box;
1080
+ height: var(--dsh-tb-control-h);
1081
+ padding: 0 8px;
630
1082
  font-size: 12.5px;
631
1083
  color: var(--dsw-alias-label-primary);
632
1084
  background: var(--dsw-specific-input-major);
633
1085
  border: 1px solid var(--dsw-alias-border-l2);
634
- border-radius: 8px;
1086
+ border-radius: var(--dsh-tb-control-radius);
635
1087
  outline: none;
636
1088
  }
637
1089
 
1090
+ /* Zone row: the label and the picker share the cron row's geometry so the two
1091
+ controls read as one editor. */
1092
+ .scheduleZone {
1093
+ display: flex;
1094
+ align-items: center;
1095
+ gap: 8px;
1096
+ font-size: 12.5px;
1097
+ color: var(--dsw-alias-label-secondary);
1098
+ }
1099
+
1100
+ .scheduleZone select {
1101
+ flex: 1;
1102
+ min-width: 0;
1103
+ }
1104
+
638
1105
  .scheduleMeta {
639
1106
  margin: 0;
640
1107
  font-size: 12px;
@@ -658,6 +1125,38 @@
658
1125
  overflow-y: auto;
659
1126
  }
660
1127
 
1128
+ /* --- clamped long content (detail description / prompt) ---------------------
1129
+ * A long markdown body renders behind a fixed clamp with a fade into the
1130
+ * detail surface; the expand toggle exists only when the content really
1131
+ * overflows the clamp (measured at runtime), so short texts are untouched.
1132
+ * The clamp height is read back by ClampedMarkdown through getComputedStyle —
1133
+ * keep the pixel value here only.
1134
+ * --------------------------------------------------------------------------- */
1135
+
1136
+ .clampBody {
1137
+ position: relative;
1138
+ }
1139
+
1140
+ .clampBody[data-clamped='true'] {
1141
+ max-height: 160px;
1142
+ overflow: hidden;
1143
+ }
1144
+
1145
+ .clampBody[data-clamped='true']::after {
1146
+ content: '';
1147
+ position: absolute;
1148
+ right: 0;
1149
+ bottom: 0;
1150
+ left: 0;
1151
+ height: 44px;
1152
+ pointer-events: none;
1153
+ background: linear-gradient(to bottom, transparent, var(--dsw-alias-bg-base));
1154
+ }
1155
+
1156
+ .clampToggle {
1157
+ align-self: flex-start;
1158
+ }
1159
+
661
1160
  .executionList {
662
1161
  list-style: none;
663
1162
  margin: 0;
@@ -743,6 +1242,11 @@
743
1242
  padding: 10px;
744
1243
  }
745
1244
 
1245
+ /* The header stacks in source order: identity (back control + title), then the
1246
+ host stamp on its own line, then the toolbar, which wraps inside itself —
1247
+ the search takes a full line, the picker and the actions share the next. No
1248
+ order overrides: the toolbar is one group now, and reordering its children
1249
+ from outside the group is what would break this layout. */
746
1250
  .boardHeader {
747
1251
  align-items: center;
748
1252
  flex-wrap: wrap;
@@ -751,30 +1255,37 @@
751
1255
 
752
1256
  .backButton {
753
1257
  flex: 0 0 auto;
754
- order: 1;
755
1258
  }
756
1259
 
757
1260
  .boardTitle {
758
1261
  flex: 1 1 auto;
759
- order: 2;
760
1262
  }
761
1263
 
762
1264
  .boardHeader > .detailMeta {
763
1265
  flex: 1 0 100%;
764
1266
  margin-left: 0;
765
- order: 3;
766
1267
  }
767
1268
 
768
- .search {
1269
+ .boardTools {
1270
+ flex: 1 0 100%;
1271
+ margin-left: 0;
1272
+ flex-wrap: wrap;
1273
+ justify-content: flex-start;
1274
+ }
1275
+
1276
+ .boardTools > .search {
769
1277
  flex: 1 0 100%;
770
1278
  min-width: 0;
771
- order: 4;
772
1279
  }
773
1280
 
774
- .boardHeader > button:not(.backButton) {
1281
+ .boardTools > .projectFilter {
1282
+ flex: 1 1 auto;
1283
+ min-width: 0;
1284
+ }
1285
+
1286
+ .boardTools > button {
775
1287
  flex: 1 1 0;
776
1288
  min-width: 0;
777
- order: 5;
778
1289
  }
779
1290
 
780
1291
  .columns {
@@ -932,7 +1443,6 @@
932
1443
  * --------------------------------------------------------------------------- */
933
1444
 
934
1445
  /* --- one themed focus-visible ring (approx 2px, 2px offset) --- */
935
- .card:focus-visible,
936
1446
  .primaryButton:focus-visible,
937
1447
  .ghostButton:focus-visible,
938
1448
  .dangerButton:focus-visible,
@@ -942,7 +1452,8 @@
942
1452
  .input:focus-visible,
943
1453
  .select:focus-visible,
944
1454
  .schedulePreset:focus-visible,
945
- .scheduleToggle input:focus-visible {
1455
+ .scheduleToggle input:focus-visible,
1456
+ .formSectionHeader:focus-visible {
946
1457
  outline: 2px solid var(--dsw-alias-state-business-primary);
947
1458
  outline-offset: 2px;
948
1459
  }
@@ -957,7 +1468,9 @@
957
1468
  .input,
958
1469
  .select,
959
1470
  .schedulePreset,
960
- .scheduleToggle input {
1471
+ .scheduleToggle input,
1472
+ .formSectionHeader,
1473
+ .formSectionChevron {
961
1474
  transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease, outline-color 120ms ease, box-shadow 120ms ease, transform 120ms ease;
962
1475
  }
963
1476
 
@@ -1017,13 +1530,31 @@
1017
1530
  .input,
1018
1531
  .select,
1019
1532
  .schedulePreset,
1020
- .scheduleToggle input {
1533
+ .scheduleToggle input,
1534
+ .formSectionHeader,
1535
+ .formSectionChevron {
1021
1536
  transition: none;
1022
1537
  }
1023
1538
 
1024
1539
  .cardSpinner {
1025
1540
  animation: none;
1026
1541
  }
1542
+
1543
+ /* Overlay lifecycle without spatial travel: the backdrop and the surface keep
1544
+ a short reversible fade, so entering and leaving stay perceptible and the
1545
+ surface still spends the same time on screen before `usePresence`
1546
+ unmounts it. */
1547
+ .modalBackdrop,
1548
+ .modal,
1549
+ .detail {
1550
+ animation: dshTbBackdropIn var(--dsh-tb-motion-close) linear both;
1551
+ }
1552
+
1553
+ .modalBackdrop[data-state='closing'],
1554
+ .modalBackdrop[data-state='closing'] .modal,
1555
+ .modalBackdrop[data-state='closing'] .detail {
1556
+ animation: dshTbBackdropOut var(--dsh-tb-motion-close) linear both;
1557
+ }
1027
1558
  }
1028
1559
 
1029
1560
  /* --- task tags (issue #1521) ---------------------------------------------------
@@ -1059,7 +1590,10 @@
1059
1590
  flex-wrap: wrap;
1060
1591
  align-items: center;
1061
1592
  gap: 6px;
1062
- margin: 0 0 10px;
1593
+ /* The board's own row rhythm spaces this row from the header and the
1594
+ columns; a local margin would double it. */
1595
+ margin: 0;
1596
+ flex: none;
1063
1597
  }
1064
1598
 
1065
1599
  .tagFilterLabel {