@linxin666/dsh-client-ui-task-board 0.4.4 → 0.4.6

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 (120) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +59 -20
  3. package/README.zh.md +59 -20
  4. package/lib/client.js +6208 -624
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +4774 -493
  7. package/lib/types/client/PluginSettingsCard.d.ts +7 -0
  8. package/lib/types/client/TaskBoardSettingsCard.d.ts +31 -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 +4 -1
  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/status-key.d.ts +15 -0
  24. package/lib/types/client/board/task-markdown.d.ts +19 -0
  25. package/lib/types/client/index.d.ts +23 -5
  26. package/lib/types/client/locales.d.ts +168 -1
  27. package/lib/types/client/native-panel.d.ts +6 -20
  28. package/lib/types/client/plugin-card-seat.d.ts +5 -0
  29. package/lib/types/client/schedule-zone.d.ts +50 -0
  30. package/lib/types/client/seats.d.ts +18 -0
  31. package/lib/types/client/service.d.ts +32 -0
  32. package/lib/types/client/settings-form.d.ts +16 -0
  33. package/lib/types/core/controller.d.ts +46 -11
  34. package/lib/types/core/extension.d.ts +283 -0
  35. package/lib/types/core/handover.d.ts +13 -1
  36. package/lib/types/core/issue-form.d.ts +16 -0
  37. package/lib/types/core/poll-cadence.d.ts +31 -0
  38. package/lib/types/core/tasks.d.ts +228 -17
  39. package/lib/types/core/use-cases/task-schedule.d.ts +68 -10
  40. package/lib/types/core/use-cases/task-tag.d.ts +48 -0
  41. package/lib/types/core/use-cases/task-update.d.ts +5 -3
  42. package/lib/types/core/verification-budget.d.ts +52 -0
  43. package/lib/types/core/verification.d.ts +753 -0
  44. package/lib/types/core/workspace-target.d.ts +41 -0
  45. package/lib/types/host/agent-tools.d.ts +1 -1
  46. package/lib/types/host/extension-registry.d.ts +111 -0
  47. package/lib/types/host/llm-dispatch.d.ts +63 -0
  48. package/lib/types/host/verification-gate.d.ts +95 -0
  49. package/lib/types/host/verification-runner.d.ts +139 -0
  50. package/lib/types/host/workspace-evidence.d.ts +53 -0
  51. package/lib/types/host-ledger.d.ts +133 -14
  52. package/lib/types/host-runner.d.ts +55 -10
  53. package/lib/types/host-service.d.ts +205 -11
  54. package/lib/types/index.d.ts +134 -26
  55. package/lib/types/protocol.d.ts +92 -10
  56. package/lib/types/tool-surface.d.ts +71 -0
  57. package/package.json +28 -31
  58. package/src/client/PluginSettingsCard.tsx +9 -0
  59. package/src/client/TaskBoardSettingsCard.tsx +378 -66
  60. package/src/client/board/ClampedMarkdown.tsx +69 -0
  61. package/src/client/board/ConfirmDialog.tsx +7 -3
  62. package/src/client/board/EditTaskModal.tsx +5 -2
  63. package/src/client/board/LinkSubtaskModal.tsx +12 -7
  64. package/src/client/board/NewTaskModal.tsx +168 -21
  65. package/src/client/board/TagManagerModal.tsx +235 -0
  66. package/src/client/board/TaskBoard.tsx +259 -103
  67. package/src/client/board/TaskCard.tsx +214 -81
  68. package/src/client/board/TaskDetail.tsx +289 -43
  69. package/src/client/board/TaskForm.tsx +13 -3
  70. package/src/client/board/VerificationReport.tsx +322 -0
  71. package/src/client/board/card-view.ts +220 -0
  72. package/src/client/board/icons.tsx +89 -0
  73. package/src/client/board/overlay.tsx +189 -0
  74. package/src/client/board/status-key.ts +30 -0
  75. package/src/client/board/task-markdown.module.css +137 -0
  76. package/src/client/board/task-markdown.tsx +106 -0
  77. package/src/client/board-settings.module.css +66 -0
  78. package/src/client/board.module.css +488 -63
  79. package/src/client/index.ts +82 -51
  80. package/src/client/locales.ts +343 -6
  81. package/src/client/native-panel.tsx +18 -2
  82. package/src/client/plugin-card-seat.ts +7 -0
  83. package/src/client/schedule-zone.ts +100 -2
  84. package/src/client/seats.tsx +58 -0
  85. package/src/client/service.ts +94 -0
  86. package/src/client/settings-card.module.css +31 -5
  87. package/src/client/settings-form.ts +27 -1
  88. package/src/core/controller.ts +105 -10
  89. package/src/core/extension.ts +376 -0
  90. package/src/core/freeze-snapshot.ts +2 -2
  91. package/src/core/handover.ts +16 -1
  92. package/src/core/issue-form.ts +19 -0
  93. package/src/core/poll-cadence.ts +41 -0
  94. package/src/core/store.ts +80 -15
  95. package/src/core/tasks.ts +293 -45
  96. package/src/core/use-cases/task-create.ts +30 -9
  97. package/src/core/use-cases/task-parent.ts +3 -2
  98. package/src/core/use-cases/task-schedule.ts +190 -25
  99. package/src/core/use-cases/task-tag.ts +110 -0
  100. package/src/core/use-cases/task-update.ts +13 -7
  101. package/src/core/verification-budget.ts +115 -0
  102. package/src/core/verification.ts +1594 -0
  103. package/src/core/workspace-target.ts +59 -0
  104. package/src/host/agent-tools.ts +125 -38
  105. package/src/host/extension-registry.ts +509 -0
  106. package/src/host/llm-dispatch.ts +87 -0
  107. package/src/host/verification-gate.ts +487 -0
  108. package/src/host/verification-runner.ts +702 -0
  109. package/src/host/workspace-evidence.ts +176 -0
  110. package/src/host-ai.ts +15 -9
  111. package/src/host-ledger.ts +468 -71
  112. package/src/host-routes.ts +15 -2
  113. package/src/host-runner.ts +106 -24
  114. package/src/host-service.ts +546 -40
  115. package/src/index.ts +305 -21
  116. package/src/protocol.ts +226 -28
  117. package/src/tool-surface.ts +95 -0
  118. package/lib/invariant.js +0 -6
  119. package/lib/types/invariant.d.ts +0 -3
  120. package/src/invariant.ts +0 -4
@@ -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,6 +670,41 @@
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 {
@@ -449,6 +744,65 @@
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
+
452
806
  /* --- form sections (create/edit dialog) --------------------------------------
453
807
  * The task form groups its configuration into collapsible regions so the
454
808
  * dialog fits without a scrollbar: only the content region starts open, and
@@ -550,13 +904,18 @@
550
904
  color: var(--dsw-alias-label-secondary);
551
905
  }
552
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. */
553
910
  .input {
554
- padding: 7px 10px;
911
+ box-sizing: border-box;
912
+ min-height: var(--dsh-tb-control-h);
913
+ padding: 6px 10px;
555
914
  font-size: 13px;
556
915
  color: var(--dsw-alias-label-primary);
557
916
  background: var(--dsw-specific-input-major);
558
917
  border: 1px solid var(--dsw-alias-border-l2);
559
- border-radius: 8px;
918
+ border-radius: var(--dsh-tb-control-radius);
560
919
  outline: none;
561
920
  resize: vertical;
562
921
  font-family: inherit;
@@ -567,12 +926,14 @@
567
926
  }
568
927
 
569
928
  .select {
570
- padding: 7px 10px;
929
+ box-sizing: border-box;
930
+ height: var(--dsh-tb-control-h);
931
+ padding: 0 10px;
571
932
  font-size: 13px;
572
933
  color: var(--dsw-alias-label-primary);
573
934
  background: var(--dsw-specific-input-major);
574
935
  border: 1px solid var(--dsw-alias-border-l2);
575
- border-radius: 8px;
936
+ border-radius: var(--dsh-tb-control-radius);
576
937
  outline: none;
577
938
  font-family: inherit;
578
939
  max-width: 100%;
@@ -715,12 +1076,14 @@
715
1076
 
716
1077
  .schedulePreset {
717
1078
  flex: none;
718
- padding: 7px 8px;
1079
+ box-sizing: border-box;
1080
+ height: var(--dsh-tb-control-h);
1081
+ padding: 0 8px;
719
1082
  font-size: 12.5px;
720
1083
  color: var(--dsw-alias-label-primary);
721
1084
  background: var(--dsw-specific-input-major);
722
1085
  border: 1px solid var(--dsw-alias-border-l2);
723
- border-radius: 8px;
1086
+ border-radius: var(--dsh-tb-control-radius);
724
1087
  outline: none;
725
1088
  }
726
1089
 
@@ -762,6 +1125,38 @@
762
1125
  overflow-y: auto;
763
1126
  }
764
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
+
765
1160
  .executionList {
766
1161
  list-style: none;
767
1162
  margin: 0;
@@ -847,6 +1242,11 @@
847
1242
  padding: 10px;
848
1243
  }
849
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. */
850
1250
  .boardHeader {
851
1251
  align-items: center;
852
1252
  flex-wrap: wrap;
@@ -855,30 +1255,37 @@
855
1255
 
856
1256
  .backButton {
857
1257
  flex: 0 0 auto;
858
- order: 1;
859
1258
  }
860
1259
 
861
1260
  .boardTitle {
862
1261
  flex: 1 1 auto;
863
- order: 2;
864
1262
  }
865
1263
 
866
1264
  .boardHeader > .detailMeta {
867
1265
  flex: 1 0 100%;
868
1266
  margin-left: 0;
869
- order: 3;
870
1267
  }
871
1268
 
872
- .search {
1269
+ .boardTools {
873
1270
  flex: 1 0 100%;
1271
+ margin-left: 0;
1272
+ flex-wrap: wrap;
1273
+ justify-content: flex-start;
1274
+ }
1275
+
1276
+ .boardTools > .search {
1277
+ flex: 1 0 100%;
1278
+ min-width: 0;
1279
+ }
1280
+
1281
+ .boardTools > .projectFilter {
1282
+ flex: 1 1 auto;
874
1283
  min-width: 0;
875
- order: 4;
876
1284
  }
877
1285
 
878
- .boardHeader > button:not(.backButton) {
1286
+ .boardTools > button {
879
1287
  flex: 1 1 0;
880
1288
  min-width: 0;
881
- order: 5;
882
1289
  }
883
1290
 
884
1291
  .columns {
@@ -1036,7 +1443,6 @@
1036
1443
  * --------------------------------------------------------------------------- */
1037
1444
 
1038
1445
  /* --- one themed focus-visible ring (approx 2px, 2px offset) --- */
1039
- .card:focus-visible,
1040
1446
  .primaryButton:focus-visible,
1041
1447
  .ghostButton:focus-visible,
1042
1448
  .dangerButton:focus-visible,
@@ -1133,6 +1539,22 @@
1133
1539
  .cardSpinner {
1134
1540
  animation: none;
1135
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
+ }
1136
1558
  }
1137
1559
 
1138
1560
  /* --- task tags (issue #1521) ---------------------------------------------------
@@ -1168,7 +1590,10 @@
1168
1590
  flex-wrap: wrap;
1169
1591
  align-items: center;
1170
1592
  gap: 6px;
1171
- 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;
1172
1597
  }
1173
1598
 
1174
1599
  .tagFilterLabel {