@sunshaobei/dsh-session-manager 0.4.3-fork.1 → 0.7.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.
package/client.css CHANGED
@@ -41,7 +41,12 @@
41
41
  --dstrb-text-strong: #1a202c;
42
42
  --dstrb-text: #2d3748;
43
43
  --dstrb-text-secondary: #4a5568;
44
- --dstrb-text-muted: #718096;
44
+ /* #718096 measured 4.02:1 on the card surface and 3.83:1 on the subtle
45
+ surface (badges) — under the 4.5:1 text bar for the small labels that use
46
+ it. #5f6b7a clears it everywhere it lands: 5.43 on surface, 5.18 on
47
+ surface-subtle, 4.82 on surface-hover (the hovered badge), while staying
48
+ visibly weaker than --dstrb-text (11.99) and --dstrb-text-secondary (7.53). */
49
+ --dstrb-text-muted: #5f6b7a;
45
50
  --dstrb-text-faint: #a0aec0;
46
51
 
47
52
  /* Accents */
@@ -63,6 +68,12 @@
63
68
  --dstrb-border-light: #edf2f7;
64
69
  --dstrb-border-hairline: #f0f4f8;
65
70
 
71
+ /* Structural lines that must stay IDENTIFIABLE, not decorative: WCAG 1.4.11
72
+ wants >= 3:1 from the boundary that carries the hierarchy — the default
73
+ borders sit at ~1.1:1, so the nesting cue uses this token instead.
74
+ #64748b on the light card surface (#ffffff) = 4.76:1. */
75
+ --dstrb-structure: #64748b;
76
+
66
77
  /* Veils & overlays */
67
78
  --dstrb-veil: rgba(0, 0, 0, 0.06);
68
79
  --dstrb-overlay: rgba(0, 0, 0, 0.45);
@@ -122,6 +133,9 @@
122
133
  --dstrb-border-light: #333c4e;
123
134
  --dstrb-border-hairline: #2c3444;
124
135
 
136
+ /* Same >=3:1 rule on the dark card surface (#1f2530): #8a97a8 = 5.18:1. */
137
+ --dstrb-structure: #8a97a8;
138
+
125
139
  --dstrb-veil: rgba(255, 255, 255, 0.08);
126
140
  --dstrb-overlay: rgba(0, 0, 0, 0.65);
127
141
 
@@ -181,6 +195,9 @@ body[data-ds-dark-theme] {
181
195
  --dstrb-border-light: #333c4e;
182
196
  --dstrb-border-hairline: #2c3444;
183
197
 
198
+ /* Same >=3:1 rule on the dark card surface (#1f2530): #8a97a8 = 5.18:1. */
199
+ --dstrb-structure: #8a97a8;
200
+
184
201
  --dstrb-veil: rgba(255, 255, 255, 0.08);
185
202
  --dstrb-overlay: rgba(0, 0, 0, 0.65);
186
203
 
@@ -220,7 +237,12 @@ body:not([data-ds-dark-theme]) {
220
237
  --dstrb-text-strong: #1a202c;
221
238
  --dstrb-text: #2d3748;
222
239
  --dstrb-text-secondary: #4a5568;
223
- --dstrb-text-muted: #718096;
240
+ /* #718096 measured 4.02:1 on the card surface and 3.83:1 on the subtle
241
+ surface (badges) — under the 4.5:1 text bar for the small labels that use
242
+ it. #5f6b7a clears it everywhere it lands: 5.43 on surface, 5.18 on
243
+ surface-subtle, 4.82 on surface-hover (the hovered badge), while staying
244
+ visibly weaker than --dstrb-text (11.99) and --dstrb-text-secondary (7.53). */
245
+ --dstrb-text-muted: #5f6b7a;
224
246
  --dstrb-text-faint: #a0aec0;
225
247
 
226
248
  /* Accents */
@@ -242,6 +264,12 @@ body:not([data-ds-dark-theme]) {
242
264
  --dstrb-border-light: #edf2f7;
243
265
  --dstrb-border-hairline: #f0f4f8;
244
266
 
267
+ /* Structural lines that must stay IDENTIFIABLE, not decorative: WCAG 1.4.11
268
+ wants >= 3:1 from the boundary that carries the hierarchy — the default
269
+ borders sit at ~1.1:1, so the nesting cue uses this token instead.
270
+ #64748b on the light card surface (#ffffff) = 4.76:1. */
271
+ --dstrb-structure: #64748b;
272
+
245
273
  /* Veils & overlays */
246
274
  --dstrb-veil: rgba(0, 0, 0, 0.06);
247
275
  --dstrb-overlay: rgba(0, 0, 0, 0.45);
@@ -394,7 +422,7 @@ body:not([data-ds-dark-theme]) .dsh-trash-container { color-scheme: light; }
394
422
  }
395
423
 
396
424
  .dsh-trash-search-input::placeholder {
397
- color: var(--dstrb-text-faint);
425
+ color: var(--dstrb-text-muted);
398
426
  }
399
427
 
400
428
  .dsh-trash-search-input:focus {
@@ -407,14 +435,26 @@ body:not([data-ds-dark-theme]) .dsh-trash-container { color-scheme: light; }
407
435
  /* Filter toolbar */
408
436
  /* ------------------------------------------------------------------ */
409
437
 
438
+ /* Flex-wrap rather than `1fr 1fr auto`: a `1fr` grid track refuses to shrink
439
+ below its longest option, so at a 320px settings column the toolbar pushed
440
+ itself 6px past the panel. The controls now wrap instead of spilling. */
410
441
  .dsh-trash-toolbar {
411
- display: grid;
412
- grid-template-columns: 1fr 1fr auto;
442
+ display: flex;
443
+ flex-wrap: wrap;
413
444
  gap: 12px;
414
445
  align-items: center;
415
446
  margin-bottom: 20px;
416
447
  }
417
448
 
449
+ .dsh-trash-toolbar > .dsh-trash-select {
450
+ flex: 1 1 120px;
451
+ min-width: 0;
452
+ }
453
+
454
+ .dsh-trash-toolbar > .dsh-trash-btn-danger-outline {
455
+ flex: 0 0 auto;
456
+ }
457
+
418
458
  .dsh-trash-select {
419
459
  width: 100%;
420
460
  height: 36px;
@@ -463,7 +503,7 @@ body:not([data-ds-dark-theme]) .dsh-trash-container { color-scheme: light; }
463
503
  .dsh-trash-state {
464
504
  padding: 40px 0;
465
505
  text-align: center;
466
- color: var(--dstrb-text-faint);
506
+ color: var(--dstrb-text-muted);
467
507
  font-size: 14px;
468
508
  }
469
509
 
@@ -512,7 +552,7 @@ body:not([data-ds-dark-theme]) .dsh-trash-container { color-scheme: light; }
512
552
 
513
553
  .dsh-trash-group-path {
514
554
  font-size: 11px;
515
- color: var(--dstrb-text-faint);
555
+ color: var(--dstrb-text-muted);
516
556
  font-family: var(--dstrb-mono, var(--ds-font-family-code, var(--dstrb-mono-fallback)));
517
557
  margin-left: 4px;
518
558
  overflow: hidden;
@@ -530,7 +570,7 @@ body:not([data-ds-dark-theme]) .dsh-trash-container { color-scheme: light; }
530
570
 
531
571
  .dsh-trash-group-count {
532
572
  font-size: 12px;
533
- color: var(--dstrb-text-faint);
573
+ color: var(--dstrb-text-muted);
534
574
  }
535
575
 
536
576
  .dsh-trash-more-wrap {
@@ -540,7 +580,7 @@ body:not([data-ds-dark-theme]) .dsh-trash-container { color-scheme: light; }
540
580
  .dsh-trash-more-btn {
541
581
  background: none;
542
582
  border: none;
543
- color: var(--dstrb-text-faint);
583
+ color: var(--dstrb-text-muted);
544
584
  font-size: 14px;
545
585
  cursor: pointer;
546
586
  padding: 2px 6px;
@@ -596,16 +636,24 @@ body:not([data-ds-dark-theme]) .dsh-trash-container { color-scheme: light; }
596
636
  border: 1px solid var(--dstrb-border-light);
597
637
  overflow: hidden;
598
638
  box-shadow: var(--dstrb-shadow-card);
639
+ /* Query container for the narrow-panel rules below: the panel sits inside
640
+ the settings column, so its own width — not the viewport — decides what
641
+ the meta row can still show. */
642
+ container-type: inline-size;
599
643
  }
600
644
 
645
+ /* ONE geometry at every width. A per-width padding/gap (plus a label that
646
+ reappears once the container grows) makes the title NARROWER on a wider
647
+ panel — a 400px list showed a 97px title while a 310px one showed 178px.
648
+ With the row's fixed costs constant, the title grows 1:1 with the panel. */
601
649
  .dsh-trash-card {
602
650
  display: flex;
603
651
  justify-content: space-between;
604
652
  align-items: center;
605
- padding: 14px 20px;
653
+ padding: 12px 10px;
606
654
  border-bottom: 1px solid var(--dstrb-border-hairline);
607
655
  transition: background-color 0.15s ease;
608
- gap: 16px;
656
+ gap: 4px;
609
657
  }
610
658
 
611
659
  .dsh-trash-card--checked {
@@ -623,6 +671,13 @@ body:not([data-ds-dark-theme]) .dsh-trash-container { color-scheme: light; }
623
671
  flex-shrink: 0;
624
672
  }
625
673
 
674
+ /* The row gap already separates the card checkbox; an extra margin would come
675
+ straight out of the title's width. Declared AFTER the shared rule: same
676
+ specificity, so source order decides. */
677
+ .dsh-trash-checkbox--card {
678
+ margin-right: 0;
679
+ }
680
+
626
681
  .dsh-trash-card-main {
627
682
  display: flex;
628
683
  flex-direction: column;
@@ -653,6 +708,12 @@ body:not([data-ds-dark-theme]) .dsh-trash-container { color-scheme: light; }
653
708
 
654
709
  .dsh-trash-card-meta-item {
655
710
  white-space: nowrap;
711
+ /* An item wider than the wrapping line must yield, not spill: without this a
712
+ squeezed card clips the timestamp by a couple of pixels inside its own
713
+ column (scrollWidth > clientWidth). */
714
+ min-width: 0;
715
+ overflow: hidden;
716
+ text-overflow: ellipsis;
656
717
  }
657
718
 
658
719
  .dsh-trash-badge {
@@ -667,24 +728,86 @@ body:not([data-ds-dark-theme]) .dsh-trash-container { color-scheme: light; }
667
728
  align-items: center;
668
729
  }
669
730
 
731
+ /* Sub-session badge: same box as the size badge, but it discloses the inline
732
+ list, so it is a real button rather than a label. No font-size of its own —
733
+ it inherits the badge's, which keeps both badges the same size. */
734
+ .dsh-trash-badge--children {
735
+ font-family: inherit;
736
+ cursor: pointer;
737
+ transition: background-color 0.12s ease, border-color 0.12s ease, color 0.12s ease;
738
+ }
739
+
740
+ .dsh-trash-badge--children:hover {
741
+ background-color: var(--dstrb-surface-hover);
742
+ border-color: var(--dstrb-text-faint);
743
+ }
744
+
745
+ /* The open state is carried by the accent border and icon; the COUNT is 11px
746
+ text, and --dstrb-accent on the accent veil measures 3.22:1 — under the
747
+ 4.5:1 text bar this count already had to meet. */
748
+ .dsh-trash-badge--children[aria-expanded="true"] {
749
+ background-color: var(--dstrb-accent-veil);
750
+ border-color: var(--dstrb-accent);
751
+ color: var(--dstrb-text-strong);
752
+ }
753
+
754
+ .dsh-trash-badge--children[aria-expanded="true"] .dsh-trash-icon-badge {
755
+ color: var(--dstrb-accent);
756
+ }
757
+
758
+ .dsh-trash-badge--children:focus-visible {
759
+ outline: 2px solid var(--dstrb-accent);
760
+ outline-offset: 1px;
761
+ }
762
+
763
+ /* A reserved slot with tabular figures: the count is backfilled asynchronously,
764
+ so its digits must not resize the badge and shift the row under it. */
765
+ .dsh-trash-badge-num {
766
+ min-width: 1.6em;
767
+ text-align: center;
768
+ font-variant-numeric: tabular-nums;
769
+ }
770
+
670
771
  .dsh-trash-card-actions {
671
772
  display: flex;
672
773
  align-items: center;
673
- gap: 8px;
774
+ /* Icon-only buttons need less air than labelled pills; the width they give up
775
+ belongs to the title. */
776
+ gap: 4px;
674
777
  flex-shrink: 0;
675
778
  }
676
779
 
780
+ /* The action LABELS are hidden at every width, not just on a narrow panel.
781
+ Re-showing them once the container grows is what made a wider panel show a
782
+ narrower title: a label costs ~123px, so the title would drop by that much
783
+ the moment they come back. The icons keep a full accessible name
784
+ (aria-label + title), which is the state P0-4 asks for on a narrow panel.
785
+ To trade this back, show .dsh-trash-btn-label under an @container rule and
786
+ accept that the title then steps DOWN at that width. */
787
+ .dsh-trash-btn-label {
788
+ display: none;
789
+ }
790
+
791
+ .dsh-trash-btn-ghost .dsh-trash-icon-sm,
792
+ .dsh-trash-btn-danger-text .dsh-trash-icon-sm {
793
+ margin-right: 0;
794
+ }
795
+
677
796
  .dsh-trash-btn-ghost {
678
797
  background-color: var(--dstrb-surface-subtle);
679
798
  color: var(--dstrb-text-secondary);
680
799
  border: 1px solid var(--dstrb-border);
681
- padding: 3px 9px;
800
+ padding: 4px 5px;
682
801
  border-radius: 12px;
683
802
  font-size: 11px;
684
803
  font-weight: 500;
685
804
  font-family: inherit;
686
805
  display: inline-flex;
687
806
  align-items: center;
807
+ /* WCAG 2.5.8: an icon-only action keeps a >=24x24 target. */
808
+ min-width: 24px;
809
+ min-height: 24px;
810
+ justify-content: center;
688
811
  cursor: pointer;
689
812
  transition: all 0.12s ease;
690
813
  white-space: nowrap;
@@ -699,13 +822,17 @@ body:not([data-ds-dark-theme]) .dsh-trash-container { color-scheme: light; }
699
822
  background-color: transparent;
700
823
  color: var(--dstrb-danger);
701
824
  border: none;
702
- padding: 3px 6px;
825
+ padding: 4px 5px;
703
826
  border-radius: 12px;
704
827
  font-size: 11px;
705
828
  font-weight: 500;
706
829
  font-family: inherit;
707
830
  display: inline-flex;
708
831
  align-items: center;
832
+ /* WCAG 2.5.8: an icon-only action keeps a >=24x24 target. */
833
+ min-width: 24px;
834
+ min-height: 24px;
835
+ justify-content: center;
709
836
  cursor: pointer;
710
837
  transition: all 0.12s ease;
711
838
  white-space: nowrap;
@@ -716,35 +843,277 @@ body:not([data-ds-dark-theme]) .dsh-trash-container { color-scheme: light; }
716
843
  }
717
844
 
718
845
  /* ------------------------------------------------------------------ */
719
- /* Hover tooltip (position left/top set inline by JS) */
846
+ /* Sub-session rows — full-width inline list under a card */
720
847
  /* ------------------------------------------------------------------ */
721
848
 
849
+ /* Sits between two cards of the list, so it owns the hairline separator the
850
+ card would otherwise paint. The nesting cue is the RAIL, not a background
851
+ tint: a tint cannot carry a boundary (a subtle fill is ~1.05:1 against the
852
+ card by construction), so the hierarchy hangs on a line that meets the
853
+ >=3:1 non-text contrast bar in both themes — see --dstrb-structure.
854
+ 24px = the card's 10px padding + the 2px rail + the 12px indent. */
855
+ .dsh-trash-children {
856
+ position: relative;
857
+ padding: 6px 10px 10px 24px;
858
+ background-color: transparent;
859
+ border-bottom: 1px solid var(--dstrb-border-hairline);
860
+ }
861
+
862
+ .dsh-trash-children::before {
863
+ content: '';
864
+ position: absolute;
865
+ left: 10px;
866
+ top: 6px;
867
+ bottom: 10px;
868
+ width: 2px;
869
+ border-radius: 1px;
870
+ background-color: var(--dstrb-structure);
871
+ }
872
+
873
+ .dsh-trash-child {
874
+ display: flex;
875
+ align-items: center;
876
+ gap: 6px;
877
+ height: 28px;
878
+ font-size: 0.92em;
879
+ color: var(--dstrb-text-secondary);
880
+ }
881
+
882
+ .dsh-trash-child--live {
883
+ /* No row-level opacity: fading the whole row drags the TITLE down with it
884
+ (2.81:1, below the 4.5:1 text bar). The running state is carried by the
885
+ pill plus a danger tint, and only the periphery is dimmed — below. */
886
+ background-color: var(--dstrb-danger-veil);
887
+ border-radius: 6px;
888
+ cursor: not-allowed;
889
+ }
890
+
891
+ .dsh-trash-child--live .dsh-trash-child-check,
892
+ .dsh-trash-child--live .dsh-trash-child-meta,
893
+ .dsh-trash-child--live .dsh-trash-child-delete {
894
+ opacity: 0.55;
895
+ }
896
+
897
+ .dsh-trash-child-check {
898
+ /* Smaller and muted next to the card's own checkbox: this one scopes a
899
+ cascade delete, that one picks sessions for a batch action. */
900
+ width: 13px;
901
+ height: 13px;
902
+ margin-right: 0;
903
+ accent-color: var(--dstrb-text-faint);
904
+ }
905
+
906
+ /* The title is the preview control (a real button), so it needs button
907
+ resets; it also has to outrank the meta line for the row's width. */
908
+ .dsh-trash-child-title {
909
+ flex: 1 1 auto;
910
+ min-width: 0;
911
+ overflow: hidden;
912
+ text-overflow: ellipsis;
913
+ white-space: nowrap;
914
+ padding: 0;
915
+ border: none;
916
+ background: none;
917
+ color: inherit;
918
+ font: inherit;
919
+ text-align: left;
920
+ cursor: pointer;
921
+ }
922
+
923
+ .dsh-trash-child-title:hover {
924
+ color: var(--dstrb-text);
925
+ text-decoration: underline;
926
+ }
927
+
928
+ .dsh-trash-child-title:focus-visible {
929
+ outline: 2px solid var(--dstrb-accent);
930
+ outline-offset: 1px;
931
+ border-radius: 3px;
932
+ }
933
+
934
+ .dsh-trash-child-meta {
935
+ /* Shrinkable, so the title keeps its room: this is the first thing to give
936
+ way when the panel narrows. */
937
+ flex: 0 1 auto;
938
+ min-width: 0;
939
+ overflow: hidden;
940
+ text-overflow: ellipsis;
941
+ white-space: nowrap;
942
+ color: var(--dstrb-text-muted);
943
+ font-variant-numeric: tabular-nums;
944
+ }
945
+
946
+ .dsh-trash-child-live {
947
+ flex: none;
948
+ padding: 0 5px;
949
+ border-radius: 4px;
950
+ border: 1px solid var(--dstrb-danger-border);
951
+ background-color: var(--dstrb-surface);
952
+ color: var(--dstrb-danger);
953
+ line-height: 1.6;
954
+ white-space: nowrap;
955
+ }
956
+
957
+ /* A 24x24 icon button, always visible: a hover-only affordance does not exist
958
+ on touch, and WCAG 2.5.8 asks for a >=24px target. */
959
+ .dsh-trash-child-delete {
960
+ flex: none;
961
+ display: inline-flex;
962
+ align-items: center;
963
+ justify-content: center;
964
+ width: 24px;
965
+ height: 24px;
966
+ padding: 0;
967
+ border: none;
968
+ border-radius: 6px;
969
+ background: none;
970
+ color: var(--dstrb-danger);
971
+ cursor: pointer;
972
+ transition: background-color 0.12s ease;
973
+ }
974
+
975
+ .dsh-trash-child-delete:hover {
976
+ background-color: var(--dstrb-danger-veil);
977
+ }
978
+
979
+ .dsh-trash-child-delete:focus-visible {
980
+ outline: 2px solid var(--dstrb-accent);
981
+ outline-offset: 1px;
982
+ }
983
+
984
+ .dsh-trash-child-delete:disabled {
985
+ cursor: not-allowed;
986
+ }
987
+
988
+ .dsh-trash-child-delete-icon {
989
+ width: 14px;
990
+ height: 14px;
991
+ margin-right: 0;
992
+ }
993
+
994
+ /* Two placeholder rows: the grouping is fetched after the list paints, so an
995
+ expanded card holds its height instead of jumping when the rows arrive. */
996
+ .dsh-trash-child--skeleton {
997
+ border-radius: 6px;
998
+ background-color: var(--dstrb-veil);
999
+ margin: 3px 0;
1000
+ }
1001
+
1002
+ .dsh-trash-children-more {
1003
+ display: block;
1004
+ width: 100%;
1005
+ padding: 4px 0 0;
1006
+ border: none;
1007
+ background: none;
1008
+ color: var(--dstrb-accent);
1009
+ font-family: inherit;
1010
+ font-size: inherit;
1011
+ text-align: left;
1012
+ cursor: pointer;
1013
+ }
1014
+
1015
+ .dsh-trash-children-empty {
1016
+ padding: 4px 0;
1017
+ color: var(--dstrb-text-muted);
1018
+ }
1019
+
1020
+ /* ── Narrow panel ───────────────────────────────────────────────────
1021
+ The container is the card list, so these fire on the panel's own width.
1022
+ Priority is fixed: the turn count goes first, the timestamp second, then the
1023
+ action LABELS (the icons stay — they keep their aria-label/title), and the
1024
+ sub-session age last. The size badge and the sub-session badge are never
1025
+ hidden: they are counts the row exists to show. */
1026
+ @container (max-width: 360px) {
1027
+ .dsh-trash-card-meta-item--turns {
1028
+ display: none;
1029
+ }
1030
+
1031
+ /* Title first: the age gives way instead of squeezing the name. The row's
1032
+ size and age stay in the TITLE BUTTON's accessible name, which is never
1033
+ hidden — `display: none` here would drop them out of the accessibility
1034
+ tree entirely, and a tooltip is not readable by keyboard or screen
1035
+ reader. */
1036
+ .dsh-trash-child-meta {
1037
+ display: none;
1038
+ }
1039
+ }
1040
+
1041
+ @container (max-width: 320px) {
1042
+ .dsh-trash-card-meta-item--time {
1043
+ display: none;
1044
+ }
1045
+ }
1046
+
1047
+ /* Kept sub-sessions of a main session that is already gone: no restore is
1048
+ offered for them, so the row reads as a bin shell, not as a card. */
1049
+ .dsh-trash-card--orphan {
1050
+ background-color: var(--dstrb-surface-subtle);
1051
+ }
1052
+
1053
+ .dsh-trash-tag-kept {
1054
+ padding: 0 5px;
1055
+ border-radius: 4px;
1056
+ border: 1px solid var(--dstrb-border);
1057
+ background-color: var(--dstrb-surface);
1058
+ color: var(--dstrb-text-muted);
1059
+ white-space: nowrap;
1060
+ }
1061
+
1062
+ /* ------------------------------------------------------------------ */
1063
+ /* Hover info card (left/top set inline by JS, from the hovered TITLE) */
1064
+ /* ------------------------------------------------------------------ */
1065
+ /*
1066
+ * Deliberately NOT the host tooltip language. `.dsh-trash-tooltip-host` is a
1067
+ * short hint ON a button and follows the app's own tooltip tokens
1068
+ * (--dsw-alias-tooltip-*), which are a DARK bubble in both themes — the host's
1069
+ * choice for one-line hints. This card is a small PANEL of the recycle bin that
1070
+ * the user reads and selects text from, so it is painted with the plugin's own
1071
+ * theme surface: white in the light theme, #1f2530 in the dark one, like every
1072
+ * other surface of this panel. The boundary uses the structure token
1073
+ * (4.76:1 light / 5.18:1 dark), not a hairline.
1074
+ */
722
1075
  .dsh-trash-tooltip {
723
1076
  position: fixed;
724
- transform: translateY(-100%);
725
- background-color: var(--dstrb-tooltip-bg);
726
- color: var(--dstrb-tooltip-text);
727
- padding: 10px 14px;
728
- border-radius: 8px;
729
- box-shadow: var(--dstrb-shadow-tooltip);
730
- pointer-events: none;
731
1077
  z-index: 20000;
732
- max-width: 380px;
733
1078
  display: flex;
734
1079
  flex-direction: column;
735
- gap: 4px;
1080
+ gap: 6px;
1081
+ max-width: min(380px, 92vw);
1082
+ padding: 10px 12px;
1083
+ border-radius: 8px;
1084
+ background: var(--dstrb-surface);
1085
+ color: var(--dstrb-text-strong);
1086
+ /* The boundary is a real boundary, so it uses the structure token (>=3:1)
1087
+ rather than a hairline that only reads as a smudge; the elevation shadow
1088
+ underneath carries the same message. */
1089
+ border: 1px solid var(--dstrb-structure);
1090
+ box-shadow: var(--dstrb-shadow-tooltip);
1091
+ /* Readable and selectable: the card is content, not a decoration. */
1092
+ pointer-events: auto;
1093
+ user-select: text;
1094
+ -webkit-user-select: text;
1095
+ }
1096
+
1097
+ /* Placement is decided in JS from the measured box; the transform only flips
1098
+ the card to the row's other side. */
1099
+ .dsh-trash-tooltip--above {
1100
+ transform: translateY(-100%);
1101
+ }
1102
+
1103
+ .dsh-trash-tooltip--below {
1104
+ transform: none;
736
1105
  }
737
1106
 
738
1107
  .dsh-trash-tooltip-title {
739
- font-size: 13px;
1108
+ font-size: 1em;
740
1109
  font-weight: 600;
741
- color: var(--dstrb-tooltip-text);
1110
+ color: inherit;
742
1111
  word-break: break-all;
743
1112
  }
744
1113
 
745
1114
  .dsh-trash-tooltip-sub {
746
- font-size: 11px;
747
- color: var(--dstrb-tooltip-muted);
1115
+ font-size: 0.86em;
1116
+ color: var(--dstrb-text-muted);
748
1117
  font-family: var(--dstrb-mono, var(--ds-font-family-code, var(--dstrb-mono-fallback)));
749
1118
  word-break: break-all;
750
1119
  }
@@ -756,7 +1125,7 @@ body:not([data-ds-dark-theme]) .dsh-trash-container { color-scheme: light; }
756
1125
  .dsh-trash-footer {
757
1126
  margin-top: 24px;
758
1127
  font-size: 12px;
759
- color: var(--dstrb-text-faint);
1128
+ color: var(--dstrb-text-muted);
760
1129
  text-align: right;
761
1130
  }
762
1131
 
@@ -810,7 +1179,7 @@ body:not([data-ds-dark-theme]) .dsh-trash-container { color-scheme: light; }
810
1179
  background: none;
811
1180
  border: none;
812
1181
  font-size: 18px;
813
- color: var(--dstrb-text-faint);
1182
+ color: var(--dstrb-text-muted);
814
1183
  cursor: pointer;
815
1184
  padding: 4px 8px;
816
1185
  }
@@ -832,7 +1201,7 @@ body:not([data-ds-dark-theme]) .dsh-trash-container { color-scheme: light; }
832
1201
  .dsh-trash-preview-state {
833
1202
  text-align: center;
834
1203
  padding: 30px;
835
- color: var(--dstrb-text-faint);
1204
+ color: var(--dstrb-text-muted);
836
1205
  }
837
1206
 
838
1207
  .dsh-trash-modal {
@@ -858,6 +1227,26 @@ body:not([data-ds-dark-theme]) .dsh-trash-container { color-scheme: light; }
858
1227
  line-height: 1.5;
859
1228
  }
860
1229
 
1230
+ /* The cascade dialog names every sub-session it will erase; past a handful the
1231
+ list scrolls inside the dialog rather than growing it. */
1232
+ .dsh-trash-modal-list {
1233
+ margin: -8px 0 20px 0;
1234
+ padding: 6px 10px;
1235
+ max-height: 40vh;
1236
+ overflow-y: auto;
1237
+ border: 1px solid var(--dstrb-border-light);
1238
+ border-radius: 8px;
1239
+ background-color: var(--dstrb-surface-subtle);
1240
+ }
1241
+
1242
+ .dsh-trash-modal-list-item {
1243
+ color: var(--dstrb-text-secondary);
1244
+ line-height: 1.8;
1245
+ overflow: hidden;
1246
+ text-overflow: ellipsis;
1247
+ white-space: nowrap;
1248
+ }
1249
+
861
1250
  .dsh-trash-modal-actions {
862
1251
  display: flex;
863
1252
  justify-content: flex-end;
@@ -917,7 +1306,7 @@ body:not([data-ds-dark-theme]) .dsh-trash-container { color-scheme: light; }
917
1306
  }
918
1307
 
919
1308
  .dsh-trash-message-time {
920
- color: var(--dstrb-text-faint);
1309
+ color: var(--dstrb-text-muted);
921
1310
  }
922
1311
 
923
1312
  .dsh-trash-bubble-user {
@@ -1060,94 +1449,101 @@ body:not([data-ds-dark-theme]) .dsh-trash-container { color-scheme: light; }
1060
1449
  }
1061
1450
 
1062
1451
  /* ------------------------------------------------------------------ */
1063
- /* Toast layer (bottom-left; show/hide animated via inline styles) */
1452
+ /* Toast layer — host-matched surface */
1064
1453
  /* ------------------------------------------------------------------ */
1454
+ /*
1455
+ * Ported from @deepseek-ai/dsh-client-ui-primitives Toast.module.css. This
1456
+ * hand-written bundle cannot import that package: primitives declares no
1457
+ * `dsh.client`, so Vite inlines it into each consumer and it is neither a
1458
+ * platform seed word nor a registered module-row factory — `require()` would
1459
+ * miss the module table. Porting the spec is the only way to match it.
1460
+ *
1461
+ * Two things changed beyond styling:
1462
+ * - Placement is TOP-CENTRE, not a bottom-right card.
1463
+ * - The surface is dark in BOTH themes. The old --dstrb-toast-* pair flipped
1464
+ * to a LIGHT panel in dark mode, i.e. the exact opposite of the host.
1465
+ * The host tokens lead with the plugin's own as fallbacks so older cohorts,
1466
+ * which publish no --dsw-* palette, still render a readable toast.
1467
+ */
1065
1468
 
1066
1469
  .dsh-trash-toast-container {
1067
1470
  position: fixed;
1068
- bottom: 24px;
1069
- right: 24px;
1070
- z-index: 10000;
1471
+ top: 40px;
1472
+ left: 50%;
1473
+ transform: translateX(-50%);
1474
+ /* Above the 1000 the image lightbox backdrop uses, as the host does. */
1475
+ z-index: 1100;
1071
1476
  display: flex;
1072
1477
  flex-direction: column;
1478
+ align-items: center;
1073
1479
  gap: 8px;
1480
+ /* Announcements never intercept clicks; only the action text does. */
1074
1481
  pointer-events: none;
1075
1482
  }
1076
1483
 
1077
1484
  .dsh-trash-toast {
1078
- background-color: var(--dstrb-toast-bg);
1079
- color: var(--dstrb-toast-text);
1080
- padding: 10px 18px;
1081
- border-radius: 6px;
1082
- box-shadow: var(--dstrb-shadow-toast);
1083
1485
  display: flex;
1084
1486
  align-items: center;
1085
- gap: 12px;
1086
- font-size: 13px;
1087
- /* font-family comes from the "Font routing" block (it is portaled to
1088
- <body>, outside .dsh-trash-container, so it is routed there too). */
1089
- pointer-events: auto;
1090
- transition: all 0.2s ease;
1091
- opacity: 0;
1092
- transform: translateY(10px);
1093
- }
1094
-
1095
- .dsh-trash-toast-undo {
1096
- background-color: var(--dstrb-toast-btn-bg);
1097
- color: var(--dstrb-toast-btn-text);
1098
- border: none;
1099
- padding: 4px 10px;
1100
- border-radius: 4px;
1101
- font-size: 12px;
1102
- font-family: inherit;
1103
- cursor: pointer;
1104
- margin-left: 4px;
1487
+ gap: 10px;
1488
+ width: max-content;
1489
+ max-width: min(640px, calc(100vw - 48px));
1490
+ padding: 12px 16px;
1491
+ border-radius: var(--dsw-radius-lg, 12px);
1492
+ background: var(--dsw-alias-toast-bg, var(--dstrb-toast-bg));
1493
+ color: var(--dsw-alias-toast-label, var(--dstrb-toast-text));
1494
+ font-size: 14px;
1495
+ line-height: 22px;
1496
+ box-shadow: var(--dsw-shadow-lv3, var(--dstrb-shadow-toast));
1497
+ pointer-events: none;
1498
+ /* One value drives both the fade delay and the unmount timer below. */
1499
+ animation:
1500
+ dsh-trash-toast-in 160ms ease-out,
1501
+ dsh-trash-toast-fade 1000ms ease var(--dsh-toast-hold, 3000ms) forwards;
1105
1502
  }
1106
1503
 
1107
- /* ------------------------------------------------------------------ */
1108
- /* Sidebar per-row delete button (moved out of client.js) */
1109
- /* ------------------------------------------------------------------ */
1504
+ @keyframes dsh-trash-toast-in {
1505
+ from {
1506
+ opacity: 0;
1507
+ transform: translateY(-6px);
1508
+ }
1110
1509
 
1111
- .dsh-trash-row-btn {
1112
- background: none;
1113
- border: none;
1114
- padding: 2px;
1115
- width: 20px;
1116
- height: 20px;
1117
- flex: none;
1118
- display: inline-flex;
1119
- align-items: center;
1120
- justify-content: center;
1121
- cursor: pointer;
1122
- border-radius: 4px;
1123
- opacity: 0;
1124
- transition: opacity 0.12s ease, color 0.12s ease, background 0.12s ease;
1125
- color: var(--dsw-alias-label-tertiary, var(--dstrb-text-faint));
1126
- margin-right: 4px;
1510
+ to {
1511
+ opacity: 1;
1512
+ transform: translateY(0);
1513
+ }
1127
1514
  }
1128
1515
 
1129
- /* 占位按钮:等宽隐形,保持无法解析 ID 的会话行与其它行布局一致(防止"往左靠") */
1130
- .dsh-trash-row-btn--spacer {
1131
- pointer-events: none;
1132
- cursor: default;
1133
- opacity: 0 !important;
1516
+ @keyframes dsh-trash-toast-fade {
1517
+ to {
1518
+ opacity: 0;
1519
+ visibility: hidden;
1520
+ }
1134
1521
  }
1135
1522
 
1136
- .dsh-trash-row:hover .dsh-trash-row-btn:not(.dsh-trash-row-btn--spacer),
1137
- [role="treeitem"]:hover .dsh-trash-row-btn:not(.dsh-trash-row-btn--spacer),
1138
- .dsh-session-item:hover .dsh-trash-row-btn:not(.dsh-trash-row-btn--spacer),
1139
- a[href*="/session/"]:hover .dsh-trash-row-btn:not(.dsh-trash-row-btn--spacer) {
1140
- opacity: 0.85;
1523
+ /* Reduced motion drops the slide-in; the delayed fade still ends the banner
1524
+ before the timed unmount. */
1525
+ @media (prefers-reduced-motion: reduce) {
1526
+ .dsh-trash-toast {
1527
+ animation: dsh-trash-toast-fade 1000ms ease var(--dsh-toast-hold, 3000ms) forwards;
1528
+ }
1141
1529
  }
1142
1530
 
1143
- .dsh-trash-row-btn:not(.dsh-trash-row-btn--spacer):hover {
1144
- opacity: 1;
1145
- color: var(--dstrb-danger);
1146
- background: var(--dstrb-veil);
1531
+ .dsh-trash-toast-undo {
1532
+ display: inline;
1533
+ padding: 0;
1534
+ border: 0;
1535
+ background: none;
1536
+ /* deepseek-400 reads as the action against the always-dark surface. */
1537
+ color: var(--dsw-static-deepseek-400, var(--dstrb-toast-btn-text));
1538
+ font: inherit;
1539
+ cursor: pointer;
1540
+ margin-inline: 3px;
1541
+ /* The banner stays click-through; only this takes the pointer. */
1542
+ pointer-events: auto;
1147
1543
  }
1148
1544
 
1149
- .dsh-trash-row-btn svg {
1150
- display: block;
1545
+ .dsh-trash-toast-undo:hover {
1546
+ opacity: 0.8;
1151
1547
  }
1152
1548
 
1153
1549
  /* ------------------------------------------------------------------ */
@@ -1166,7 +1562,7 @@ a[href*="/session/"]:hover .dsh-trash-row-btn:not(.dsh-trash-row-btn--spacer) {
1166
1562
  border-radius: 7px;
1167
1563
  line-height: 1;
1168
1564
  cursor: pointer;
1169
- color: var(--dsw-alias-label-tertiary, var(--dstrb-text-faint));
1565
+ color: var(--dsw-alias-label-tertiary, var(--dstrb-text-muted));
1170
1566
  opacity: 0.85;
1171
1567
  transition: color 0.12s ease, background 0.12s ease, opacity 0.12s ease;
1172
1568
  }
@@ -1176,3 +1572,42 @@ a[href*="/session/"]:hover .dsh-trash-row-btn:not(.dsh-trash-row-btn--spacer) {
1176
1572
  background: var(--dstrb-veil);
1177
1573
  opacity: 1;
1178
1574
  }
1575
+
1576
+ /* One-line label tooltip for the header action, replacing the native `title`
1577
+ attribute: an OS-drawn bubble next to the host's themed tooltips read as
1578
+ "unstyled plugin", and it cannot follow the palette or the motion setting.
1579
+ Ported from @deepseek-ai/dsh-client-ui-primitives Tooltip.module.css for the
1580
+ same reason the toast is (the package is inlined by Vite, so it cannot be
1581
+ required from a hand-written bundle).
1582
+ `translateX(-100%)` is the host's `side: bottom` + `align: end` transform,
1583
+ so `left` carries the anchor's RIGHT edge. */
1584
+ .dsh-trash-tooltip-host {
1585
+ position: fixed;
1586
+ z-index: 100;
1587
+ width: max-content;
1588
+ max-width: 50vw;
1589
+ padding: 3px 7px;
1590
+ border-radius: var(--dsw-radius-sm, 4px);
1591
+ background: var(--dsw-alias-tooltip-bg, var(--dstrb-tooltip-bg));
1592
+ color: var(--dsw-static-neutral-bluish-00, var(--dstrb-tooltip-text));
1593
+ font-size: 13px;
1594
+ line-height: 20px;
1595
+ white-space: pre-line;
1596
+ /* Unbreakable tokens (paths, URLs) must not push past max-width. */
1597
+ overflow-wrap: break-word;
1598
+ pointer-events: none;
1599
+ transform: translateX(-100%);
1600
+ animation: dsh-trash-tooltip-in 150ms var(--ds-ease-in-out, ease-in-out);
1601
+ }
1602
+
1603
+ @keyframes dsh-trash-tooltip-in {
1604
+ from {
1605
+ opacity: 0;
1606
+ }
1607
+ }
1608
+
1609
+ @media (prefers-reduced-motion: reduce) {
1610
+ .dsh-trash-tooltip-host {
1611
+ animation: none;
1612
+ }
1613
+ }