@young1lin/dsh-ui-gitworkbench 0.1.3 → 0.1.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.
@@ -438,6 +438,13 @@
438
438
  .drawer > .tabs { z-index: 23; }
439
439
  .drawer > .compareBar { z-index: 22; }
440
440
  .drawer > .syncBar { z-index: 20; }
441
+ /* The roll-back confirmation, and the one deliberate exception to the
442
+ strictly-decreasing rule above: it is the LAST child and outranks every
443
+ other, because it is not a popover dropping through its siblings but a
444
+ modal that has to cover them. It also has to be declared here — the
445
+ `> *:not(.resizer)` rule out-specifies a bare `.confirmScrim`, which is
446
+ what pinned it to the bottom of the drawer as an in-flow flex item. */
447
+ .drawer > .confirmScrim { position: absolute; inset: 0; z-index: 40; }
441
448
  /* Maximized drops the inset and the rounding: at full bleed they would only
442
449
  waste edge pixels the diff wants. */
443
450
  .overlayMax { padding: 0; gap: 0; }
@@ -726,6 +733,402 @@
726
733
  }
727
734
  .commitPopTop { display: flex; align-items: center; gap: 8px; }
728
735
  .commitPopTop .commitWhen { margin-right: auto; }
736
+ /* Author / committer / exact date — the precise answers the row's summary
737
+ ("3 weeks ago") deliberately defers to the hover card. */
738
+ .commitPopMeta {
739
+ display: flex; flex-direction: column; gap: 2px;
740
+ color: var(--gs-fg-muted);
741
+ font-size: var(--gs-t-dense); line-height: 16px;
742
+ }
743
+ /* The filter box in the history pane head — it flexes to fill the head beside
744
+ the title (the funnel button is flex:none), so a wide pane gets a wide box
745
+ and a narrow pane still leaves room to type. */
746
+ .commitFilter {
747
+ flex: 1 1 auto; min-width: 120px;
748
+ height: var(--gs-h-control); box-sizing: border-box;
749
+ padding: 0 10px;
750
+ border: 1px solid var(--gs-border-soft); border-radius: var(--gs-r-control);
751
+ background: var(--gs-bg); color: var(--gs-fg);
752
+ font-family: inherit; font-size: var(--gs-t-dense);
753
+ outline: none;
754
+ }
755
+ .commitFilter:focus { border-color: var(--gs-accent); }
756
+ .commitFilter::placeholder { color: var(--gs-fg-faint); }
757
+ /* Active criteria under the head: one removable chip each, grammar tokens as
758
+ their own labels — the chip IS the query it would take to retype it. */
759
+ .filterChips {
760
+ display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
761
+ flex: none;
762
+ padding: 6px 10px;
763
+ border-bottom: 1px solid var(--gs-border-soft);
764
+ }
765
+ .filterChip {
766
+ display: inline-flex; align-items: center; gap: 2px;
767
+ max-width: 220px;
768
+ border: 1px solid var(--gs-border-soft); border-radius: var(--gs-r-control);
769
+ background: var(--gs-surface-2);
770
+ font-size: var(--gs-t-dense); line-height: 18px;
771
+ }
772
+ .filterChipLabel {
773
+ padding: 2px 4px 2px 8px;
774
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
775
+ color: var(--gs-fg-muted);
776
+ }
777
+ .filterChipRemove {
778
+ flex: none;
779
+ padding: 0 6px;
780
+ border: none; border-left: 1px solid var(--gs-border-soft);
781
+ background: transparent; color: var(--gs-fg-dim);
782
+ font-family: inherit; font-size: var(--gs-t-dense); line-height: 20px;
783
+ cursor: pointer;
784
+ }
785
+ .filterChipRemove:hover { color: var(--gs-danger, var(--gs-fg)); }
786
+ .filterClear {
787
+ border: none; background: transparent;
788
+ padding: 2px 4px;
789
+ color: var(--gs-fg-faint);
790
+ font-family: inherit; font-size: var(--gs-t-dense); line-height: 18px;
791
+ cursor: pointer;
792
+ }
793
+ .filterClear:hover { color: var(--gs-fg); }
794
+ /* The funnel dropdown — RefPicker's positioning pattern (relative anchor,
795
+ absolute panel, dismiss-on-outside). Grows leftward: it sits at the pane's
796
+ right edge. */
797
+ .funnel { position: relative; display: inline-flex; }
798
+ /* The button itself carries no geometry of its own — it joined the shared
799
+ control vocabulary above (`.btn, .refButton, …`) so it reads as a sibling of
800
+ every other button in the drawer, not a foreign element. */
801
+ .funnelButton.funnelButtonActive { color: var(--gs-accent); border-color: var(--gs-accent); }
802
+
803
+ /* The panel is a flex COLUMN with exactly ONE scrolling child. It used to
804
+ scroll itself AND cap its list at 240px: two nested scroll containers in a
805
+ 331px box that had ~700px of anchor space to spend, so a long roster got a
806
+ fat inner scrollbar instead of the height already available. The tab strip
807
+ and the footer are `flex: none`; the list takes what is left. */
808
+ .funnelPop {
809
+ position: fixed; z-index: 40;
810
+ display: flex; flex-direction: column;
811
+ width: 320px; max-width: calc(100vw - 24px);
812
+ box-sizing: border-box;
813
+ overflow: hidden;
814
+ border: 1px solid var(--gs-border); border-radius: var(--gs-r-surface);
815
+ background: var(--gs-panel);
816
+ box-shadow: 0 16px 40px var(--gs-shadow);
817
+ font-size: var(--gs-t-dense);
818
+ }
819
+ /* One section at a time — the panel's height is a property of its design,
820
+ not of how many authors the repository happens to have. The strip is a
821
+ segmented control (inset track, raised active chip) rather than one
822
+ outlined pill beside two bare words, which read as a button and two labels
823
+ instead of one three-way choice. */
824
+ .funnelTabs {
825
+ flex: none;
826
+ display: flex; gap: 2px;
827
+ margin: 8px 8px 0;
828
+ padding: 2px;
829
+ border: 1px solid var(--gs-border-soft); border-radius: var(--gs-r-pill);
830
+ background: var(--gs-bg);
831
+ }
832
+ .funnelTab {
833
+ flex: 1; min-width: 0;
834
+ height: 22px; padding: 0 8px;
835
+ border: none; border-radius: var(--gs-r-pill);
836
+ background: transparent;
837
+ color: var(--gs-fg-dim);
838
+ font-family: inherit; font-size: var(--gs-t-dense); line-height: 1;
839
+ white-space: nowrap;
840
+ cursor: pointer;
841
+ transition: background 120ms ease, color 120ms ease;
842
+ }
843
+ .funnelTab:hover { color: var(--gs-fg); }
844
+ .funnelTab.funnelTabActive {
845
+ color: var(--gs-fg);
846
+ background: var(--gs-raise);
847
+ box-shadow: inset 0 0 0 1px var(--gs-border);
848
+ }
849
+ .funnelTab:focus-visible { outline: 2px solid var(--gs-accent); outline-offset: 1px; }
850
+ /* A tab's own criteria count, so the two sections nobody is looking at still
851
+ say they hold something. */
852
+ .funnelTabCount { margin-left: 4px; color: var(--gs-accent); font-variant-numeric: tabular-nums; }
853
+
854
+ /* The body of the active section. Everything inside is `flex: none` except
855
+ the list, which is the one thing allowed to scroll. */
856
+ .funnelPane {
857
+ flex: 1 1 auto; min-height: 0;
858
+ display: flex; flex-direction: column; gap: 6px;
859
+ padding: 8px;
860
+ }
861
+ .funnelCaption {
862
+ flex: none;
863
+ color: var(--gs-fg-faint);
864
+ font-size: 11px; line-height: 14px;
865
+ letter-spacing: 0.04em; text-transform: uppercase;
866
+ }
867
+
868
+ /* Which bound a picked day lands in. Deliberately NOT the tab strip's shape:
869
+ a rect track with an accent-tinted chip under a caption, because two
870
+ identical pill rows stacked six pixels apart said nothing about meaning two
871
+ different things. */
872
+ .funnelBounds {
873
+ flex: none;
874
+ display: flex; gap: 2px;
875
+ padding: 2px;
876
+ border: 1px solid var(--gs-border-soft); border-radius: var(--gs-r-control);
877
+ background: var(--gs-bg);
878
+ }
879
+ .funnelBoundBtn {
880
+ flex: 1; min-width: 0;
881
+ height: 20px; padding: 0 6px;
882
+ border: none; border-radius: calc(var(--gs-r-control) - 3px);
883
+ background: transparent;
884
+ color: var(--gs-fg-dim);
885
+ font-family: inherit; font-size: var(--gs-t-dense); line-height: 1;
886
+ cursor: pointer;
887
+ transition: background 120ms ease, color 120ms ease;
888
+ }
889
+ .funnelBoundBtn:hover { color: var(--gs-fg); }
890
+ .funnelBoundBtn.funnelBoundBtnActive {
891
+ color: var(--gs-accent);
892
+ background: var(--gs-accent-bg);
893
+ box-shadow: inset 0 0 0 1px var(--gs-accent-border);
894
+ }
895
+
896
+ /* The calendar — modern-grid look in the drawer's own tokens. */
897
+ .cal { flex: none; display: flex; flex-direction: column; gap: 4px; }
898
+ .calHead { display: flex; align-items: center; gap: 4px; }
899
+ .calTitle {
900
+ flex: 1; text-align: center;
901
+ color: var(--gs-fg);
902
+ font-size: var(--gs-t-dense); line-height: 20px;
903
+ }
904
+ .calNav {
905
+ flex: none;
906
+ width: 22px; height: 20px; padding: 0;
907
+ border: none; border-radius: var(--gs-r-control);
908
+ background: transparent;
909
+ color: var(--gs-fg-dim);
910
+ font-family: inherit; font-size: var(--gs-t-ui);
911
+ cursor: pointer;
912
+ }
913
+ .calNav:hover { color: var(--gs-fg); background: var(--gs-raise); }
914
+ .calWeek, .calGrid {
915
+ display: grid;
916
+ grid-template-columns: repeat(7, 1fr);
917
+ gap: 2px;
918
+ }
919
+ .calWeek span {
920
+ text-align: center;
921
+ color: var(--gs-fg-faint);
922
+ font-size: 11px; line-height: 14px;
923
+ }
924
+ .calGrid button {
925
+ height: 26px;
926
+ border: none; border-radius: var(--gs-r-control);
927
+ background: transparent;
928
+ color: var(--gs-fg-muted);
929
+ font-family: inherit; font-size: var(--gs-t-dense);
930
+ font-variant-numeric: tabular-nums;
931
+ cursor: pointer;
932
+ }
933
+ .calGrid button:hover { background: var(--gs-raise); color: var(--gs-fg); }
934
+ .calGrid button.calOut { color: var(--gs-fg-fainter); }
935
+ .calGrid button.calToday { color: var(--gs-accent); font-weight: 600; }
936
+ /* Days strictly between the two bounds: the span the filter actually means,
937
+ which two lone marked endpoints never showed. */
938
+ .calGrid button.calIn { background: var(--gs-accent-bg); color: var(--gs-fg); }
939
+ .calGrid button.calMark {
940
+ background: var(--gs-accent);
941
+ color: var(--gs-on-accent);
942
+ font-weight: 600;
943
+ }
944
+
945
+ /* The two bound readouts, side by side under the grid. */
946
+ .funnelBoundRows { flex: none; display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
947
+ .funnelBoundRow {
948
+ display: flex; align-items: center; gap: 4px; min-width: 0;
949
+ box-sizing: border-box;
950
+ padding: 3px 6px;
951
+ border: 1px solid var(--gs-border-soft); border-radius: var(--gs-r-control);
952
+ background: var(--gs-bg);
953
+ font-size: 11px; line-height: 16px;
954
+ }
955
+ .funnelBoundKey { flex: none; color: var(--gs-fg-faint); }
956
+ .funnelBoundVal {
957
+ flex: 1; min-width: 0;
958
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
959
+ color: var(--gs-fg-faint);
960
+ font-variant-numeric: tabular-nums;
961
+ }
962
+ .funnelBoundValSet { color: var(--gs-fg); }
963
+ .funnelBoundClear {
964
+ flex: none;
965
+ padding: 0 2px;
966
+ border: none; background: transparent;
967
+ color: var(--gs-fg-dim);
968
+ font-family: inherit; font-size: var(--gs-t-dense); line-height: 1;
969
+ cursor: pointer;
970
+ }
971
+ .funnelBoundClear:hover { color: var(--gs-danger, var(--gs-fg)); }
972
+
973
+ .funnelSearch {
974
+ flex: none;
975
+ box-sizing: border-box;
976
+ height: 26px; padding: 0 8px;
977
+ border: 1px solid var(--gs-border-soft); border-radius: var(--gs-r-control);
978
+ background: var(--gs-bg); color: var(--gs-fg);
979
+ font-family: inherit; font-size: var(--gs-t-dense);
980
+ outline: none;
981
+ }
982
+ .funnelSearch::placeholder { color: var(--gs-fg-faint); }
983
+ .funnelSearch:focus { border-color: var(--gs-accent); box-shadow: 0 0 0 2px var(--gs-accent-bg); }
984
+ .funnelList {
985
+ flex: 1 1 auto; min-height: 0;
986
+ overflow-y: auto; overscroll-behavior: contain;
987
+ display: flex; flex-direction: column; gap: 1px;
988
+ margin: 0 -4px; padding: 0 4px;
989
+ scrollbar-width: thin; scrollbar-color: var(--gs-fg-fainter) transparent;
990
+ }
991
+ .funnelList::-webkit-scrollbar { width: 10px; }
992
+ .funnelList::-webkit-scrollbar-track { background: transparent; }
993
+ .funnelList::-webkit-scrollbar-thumb {
994
+ background: var(--gs-fg-fainter);
995
+ border: 3px solid transparent; border-radius: 5px;
996
+ background-clip: content-box;
997
+ }
998
+ /* Every row is the same four slots — chevron, tick, glyph, name — then the
999
+ count. Directories used to skip the glyph that files carried, so a folder
1000
+ name and the file names under it started at different x and the list had a
1001
+ ragged left edge. */
1002
+ .funnelRow {
1003
+ display: flex; align-items: center; gap: 6px;
1004
+ box-sizing: border-box;
1005
+ min-height: 24px; padding: 0 4px;
1006
+ border-radius: var(--gs-r-control);
1007
+ cursor: pointer;
1008
+ }
1009
+ .funnelRow:hover { background: var(--gs-raise); }
1010
+ .funnelRow:has(input[type='checkbox']:checked) .funnelName,
1011
+ .funnelRow:has(input[type='checkbox']:indeterminate) .funnelName { color: var(--gs-fg); }
1012
+ /* The tick IS the input — no wrapper span, no visually-hidden twin: the live
1013
+ probes select `[class*="funnelRow"] input[type=checkbox]` and the directory
1014
+ row's DIRECT child, so the markup has to stay flat. `appearance: none`
1015
+ hands us the box; the mark is a rotated border, so nothing ships as an
1016
+ asset and every state recolors with the theme. */
1017
+ .funnelRow input[type='checkbox'] {
1018
+ appearance: none; -webkit-appearance: none;
1019
+ flex: none; position: relative;
1020
+ box-sizing: border-box;
1021
+ width: 14px; height: 14px; margin: 0;
1022
+ border: 1px solid var(--gs-fg-fainter); border-radius: 3px;
1023
+ background: transparent;
1024
+ cursor: pointer;
1025
+ transition: background 120ms ease, border-color 120ms ease;
1026
+ }
1027
+ .funnelRow input[type='checkbox']:hover { border-color: var(--gs-fg-dim); }
1028
+ .funnelRow input[type='checkbox']:checked,
1029
+ .funnelRow input[type='checkbox']:indeterminate {
1030
+ background: var(--gs-accent); border-color: var(--gs-accent);
1031
+ }
1032
+ .funnelRow input[type='checkbox']:focus-visible { outline: 2px solid var(--gs-accent); outline-offset: 1px; }
1033
+ .funnelRow input[type='checkbox']:checked::after {
1034
+ content: ''; position: absolute;
1035
+ left: 4px; top: 1px; width: 3px; height: 7px;
1036
+ border: solid var(--gs-on-accent); border-width: 0 1.5px 1.5px 0;
1037
+ transform: rotate(42deg);
1038
+ }
1039
+ /* Partial — a folder some of whose subtree is ticked. */
1040
+ .funnelRow input[type='checkbox']:indeterminate::after {
1041
+ content: ''; position: absolute;
1042
+ left: 2px; top: 5px; width: 8px; height: 2px;
1043
+ border-radius: 1px;
1044
+ background: var(--gs-on-accent);
1045
+ }
1046
+ /* Tree chrome in the drawer's own visual language: a directory folds with the
1047
+ chevron the file tree uses, and both glyphs are inline SVG in the shape
1048
+ IntelliJ's New UI icon set uses — a 16px grid, 1px strokes, no fill,
1049
+ rounded joins, outlines rather than the filled silhouettes the old UI
1050
+ shipped. Drawn in `PathDirGlyph` / `PathFileGlyph` rather than imported:
1051
+ the bundle purity gate forbids an icon package, and the picker needs
1052
+ exactly these two. Indentation is the row's own padding ALONE; the nested
1053
+ rail this used to draw was `--gs-border-soft` over `--gs-panel` (the same
1054
+ value as `--gs-raise` in the GitHub palettes, so it vanished under hover)
1055
+ and its margin double-counted every level. */
1056
+ .pathNode { display: flex; flex-direction: column; }
1057
+ .pathChildren { display: flex; flex-direction: column; }
1058
+ /* One shared slot so a folder and a file start their names at the same x —
1059
+ New UI draws both on the same 16px grid, and the column only lines up if
1060
+ the box does too. `--gs-fg-dim` is the neutral the tree icons sit at there:
1061
+ present, but never louder than the filename beside them. */
1062
+ .pathFileGlyph, .pathDirGlyph {
1063
+ flex: none;
1064
+ width: 16px; height: 16px;
1065
+ color: var(--gs-fg-dim);
1066
+ }
1067
+ .funnelRow:hover .pathFileGlyph,
1068
+ .funnelRow:hover .pathDirGlyph { color: var(--gs-fg-muted); }
1069
+ .funnelName {
1070
+ flex: 1; min-width: 0;
1071
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
1072
+ color: var(--gs-fg-muted);
1073
+ }
1074
+ .funnelCount {
1075
+ flex: none;
1076
+ color: var(--gs-fg-faint);
1077
+ font-size: 11px;
1078
+ font-variant-numeric: tabular-nums;
1079
+ }
1080
+ .funnelMore { color: var(--gs-fg-faint); padding: 4px 6px; }
1081
+ .funnelChevron {
1082
+ flex: none;
1083
+ width: 14px; padding: 0;
1084
+ border: none; background: transparent;
1085
+ color: var(--gs-fg-faint);
1086
+ font-family: inherit; font-size: 9px; line-height: 16px;
1087
+ cursor: pointer;
1088
+ }
1089
+ .funnelChevron:hover:not(:disabled) { color: var(--gs-fg); }
1090
+ .funnelChevron:disabled { cursor: default; }
1091
+ .funnelPresets { flex: none; display: flex; gap: 4px; }
1092
+ /* `.funnelPreset` shipped with `flex: none` and nothing else, so it never
1093
+ joined the shared button vocabulary and the three date presets rendered as
1094
+ the browser's default grey buttons — the one patch of unthemed chrome in
1095
+ the drawer. It is in both selector lists now; this only sizes them to share
1096
+ one row. */
1097
+ .funnelPreset { flex: 1 1 0; min-width: 0; padding: 0 6px; }
1098
+ /* Qualified, like `.funnelButtonActive`: the shared vocabulary is declared
1099
+ several hundred lines BELOW this one, so a bare modifier would lose its
1100
+ background to that rule's `background: transparent`. */
1101
+ .funnelPreset.funnelPresetActive {
1102
+ color: var(--gs-accent);
1103
+ border-color: var(--gs-accent);
1104
+ background: var(--gs-accent-bg);
1105
+ }
1106
+
1107
+ /* The footer says what the panel is currently asking git for. Without it the
1108
+ only feedback lived on the chip row BEHIND the panel, so a reader had to
1109
+ close the thing to find out whether their ticks took. */
1110
+ .funnelFoot {
1111
+ flex: none;
1112
+ display: flex; align-items: center; justify-content: space-between; gap: 8px;
1113
+ padding: 6px 10px;
1114
+ border-top: 1px solid var(--gs-border-soft);
1115
+ background: var(--gs-bg);
1116
+ font-size: 11px; line-height: 16px;
1117
+ }
1118
+ .funnelFootCount { color: var(--gs-fg-faint); font-variant-numeric: tabular-nums; }
1119
+ .funnelFootCountOn { color: var(--gs-fg-muted); }
1120
+ .funnelFootClear {
1121
+ flex: none;
1122
+ padding: 2px 6px;
1123
+ border: none; border-radius: var(--gs-r-control);
1124
+ background: transparent;
1125
+ color: var(--gs-fg-dim);
1126
+ font-family: inherit; font-size: 11px; line-height: 16px;
1127
+ cursor: pointer;
1128
+ }
1129
+ .funnelFootClear:hover:not(:disabled) { color: var(--gs-fg); background: var(--gs-raise); }
1130
+ .funnelFootClear:disabled { color: var(--gs-fg-fainter); cursor: default; }
1131
+
729
1132
  .commitPopSubject {
730
1133
  color: var(--gs-fg);
731
1134
  font-size: var(--gs-t-ui); font-weight: 600; line-height: 18px;
@@ -954,6 +1357,13 @@
954
1357
  font-size: var(--gs-t-meta); line-height: 15px;
955
1358
  }
956
1359
  .commitWhen { flex: none; color: var(--gs-fg-faint); }
1360
+ /* Who wrote it — sits between hash and relative time, capped so a long name
1361
+ cannot evict the time from the row. */
1362
+ .commitAuthor {
1363
+ flex: none; max-width: 120px;
1364
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
1365
+ color: var(--gs-fg-dim);
1366
+ }
957
1367
 
958
1368
  .body { display: flex; flex: 1; min-height: 0; z-index: 1; }
959
1369
 
@@ -997,6 +1407,40 @@
997
1407
  white-space: nowrap;
998
1408
  color: var(--gs-fg-dim); font-size: var(--gs-t-dense);
999
1409
  }
1410
+ /* The file filter, revealed under the tools rather than squeezed into them:
1411
+ the pane drags down to 170px, where the tick, the count and two icon buttons
1412
+ already own the row. It costs a row only while it is being used. */
1413
+ .treeFilter {
1414
+ position: relative;
1415
+ flex: none;
1416
+ display: flex; align-items: center;
1417
+ padding: 6px var(--gs-gutter-pane);
1418
+ border-bottom: 1px solid var(--gs-border-soft);
1419
+ }
1420
+ .treeFilterInput {
1421
+ flex: 1 1 auto; min-width: 0;
1422
+ height: var(--gs-h-compact); box-sizing: border-box;
1423
+ padding: 0 24px 0 8px;
1424
+ border: 1px solid var(--gs-border-soft); border-radius: var(--gs-r-control);
1425
+ background: var(--gs-bg); color: var(--gs-fg);
1426
+ font-family: inherit; font-size: var(--gs-t-dense);
1427
+ outline: none;
1428
+ }
1429
+ .treeFilterInput:focus { border-color: var(--gs-accent); }
1430
+ .treeFilterInput::placeholder { color: var(--gs-fg-faint); }
1431
+ /* Inside the field's right padding, so clearing does not move the field or
1432
+ change its width as the button comes and goes. */
1433
+ .treeFilterClear {
1434
+ position: absolute; right: calc(var(--gs-gutter-pane) + 6px);
1435
+ display: flex; align-items: center; justify-content: center;
1436
+ width: 16px; height: 16px;
1437
+ padding: 0;
1438
+ border: 0; border-radius: var(--gs-r-pill);
1439
+ background: transparent; color: var(--gs-fg-faint);
1440
+ font-size: var(--gs-t-ui); line-height: 1;
1441
+ cursor: pointer;
1442
+ }
1443
+ .treeFilterClear:hover { color: var(--gs-fg); }
1000
1444
  .tree {
1001
1445
  flex: 1; min-height: 0;
1002
1446
  margin: 0; padding: 6px 0 16px;
@@ -1212,7 +1656,7 @@
1212
1656
  }
1213
1657
  .syncLevel { color: var(--gs-fg-faint); }
1214
1658
  .syncSpacer { flex: 1 1 auto; }
1215
- .btn:disabled, .treeIcon:disabled, .miniBtn:disabled, .refButton:disabled {
1659
+ .btn:disabled, .treeIcon:disabled, .miniBtn:disabled, .refButton:disabled, .funnelButton:disabled {
1216
1660
  opacity: 0.45;
1217
1661
  cursor: default;
1218
1662
  }
@@ -1239,6 +1683,30 @@
1239
1683
  toggle trails it rather than displacing the tree's left edge. */
1240
1684
  .fileLi { display: flex; align-items: stretch; }
1241
1685
  .fileLi .file { flex: 1 1 auto; min-width: 0; }
1686
+ /* Roll back, in the same 22px slot the tick occupies on the other side — the
1687
+ row already has two gutters, and a third would eat the name.
1688
+ It holds its width at all times and only fades in on hover or keyboard
1689
+ focus: revealing it by `display` would re-flow the name column under the
1690
+ pointer, and a destructive control that shifts the row as it appears is one
1691
+ the pointer arrives at by accident. */
1692
+ .fileDiscard {
1693
+ flex: none;
1694
+ display: flex; align-items: center; justify-content: center;
1695
+ width: 22px;
1696
+ padding: 0;
1697
+ border: 0;
1698
+ background: transparent;
1699
+ color: var(--gs-fg-faint);
1700
+ opacity: 0;
1701
+ cursor: pointer;
1702
+ transition: opacity 120ms ease, color 120ms ease;
1703
+ }
1704
+ .fileLi:hover .fileDiscard, .fileDiscard:focus-visible { opacity: 1; }
1705
+ /* Red on hover, and only on hover. Standing red would make every row of a
1706
+ working tree look like a warning; arriving under the pointer is where the
1707
+ colour has something to say. */
1708
+ .fileDiscard:hover { color: var(--gs-del); }
1709
+ .fileDiscard:focus-visible { outline: 2px solid var(--gs-accent); outline-offset: -2px; border-radius: var(--gs-r-control); }
1242
1710
  /* Directory rows need the same two-slot row as files, but their subtree list
1243
1711
  has to stay a child of the <li> — hence a wrapper around just the row. */
1244
1712
  .treeRow { display: flex; align-items: stretch; }
@@ -1359,7 +1827,7 @@
1359
1827
  * its own padding, radius and font-size, and the same "small secondary button"
1360
1828
  * had drifted to four heights and three radii across one screen.
1361
1829
  */
1362
- .btn, .miniBtn, .treeIcon, .commitCopy, .scopeBtn, .refButton {
1830
+ .btn, .miniBtn, .treeIcon, .commitCopy, .scopeBtn, .refButton, .funnelButton, .funnelPreset {
1363
1831
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
1364
1832
  box-sizing: border-box;
1365
1833
  flex: none;
@@ -1377,13 +1845,14 @@
1377
1845
  transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
1378
1846
  }
1379
1847
  .btn:hover, .miniBtn:hover, .treeIcon:hover, .commitCopy:hover,
1380
- .scopeBtn:hover, .refButton:hover {
1848
+ .scopeBtn:hover, .refButton:hover, .funnelButton:hover, .funnelPreset:hover {
1381
1849
  background: var(--gs-raise);
1382
1850
  color: var(--gs-fg);
1383
1851
  border-color: var(--gs-fg-fainter);
1384
1852
  }
1385
1853
  .btn:focus-visible, .miniBtn:focus-visible, .treeIcon:focus-visible,
1386
- .commitCopy:focus-visible, .scopeBtn:focus-visible, .refButton:focus-visible {
1854
+ .commitCopy:focus-visible, .scopeBtn:focus-visible, .refButton:focus-visible,
1855
+ .funnelPreset:focus-visible {
1387
1856
  outline: 2px solid var(--gs-accent);
1388
1857
  outline-offset: 1px;
1389
1858
  }
@@ -1394,8 +1863,9 @@
1394
1863
  foreign. Controls that live INSIDE a panel or pane stay 8px rects. */
1395
1864
  .btn, .refButton { border-radius: var(--gs-r-pill); }
1396
1865
 
1397
- /* Pane tools give their vertical space back to the diff. */
1398
- .treeIcon, .commitCopy {
1866
+ /* Pane tools give their vertical space back to the diff; the date presets sit
1867
+ inside a 320px popover, so they take the compact height too. */
1868
+ .treeIcon, .commitCopy, .funnelPreset {
1399
1869
  height: var(--gs-h-compact);
1400
1870
  padding: var(--gs-pad-compact);
1401
1871
  font-size: var(--gs-t-dense);
@@ -1405,6 +1875,9 @@
1405
1875
  the pane drags down to, two more text buttons cannot share a row with the
1406
1876
  count — and these two are the pair a tree header always carries. */
1407
1877
  .treeIcon { width: var(--gs-h-compact); padding: 0; }
1878
+ /* Qualified, like `.funnelButtonActive`: this sits inside the shared button
1879
+ vocabulary, which sets `color` at the same specificity a few lines above. */
1880
+ .treeIcon.treeIconOn { color: var(--gs-accent); border-color: var(--gs-accent); }
1408
1881
  .treeIconGlyph { display: block; font-size: var(--gs-t-meta); line-height: 1; }
1409
1882
  .treeIconDown { transform: rotate(90deg); }
1410
1883
 
@@ -1510,3 +1983,47 @@
1510
1983
  font-size: var(--gs-t-meta); line-height: 15px;
1511
1984
  cursor: default;
1512
1985
  }
1986
+
1987
+ /* ---- the roll-back confirmation ----
1988
+ *
1989
+ * Scoped to the drawer rather than the viewport: `.drawer` is the positioned
1990
+ * ancestor, so the scrim dims the thing the question is about and leaves the
1991
+ * session behind it alone. It is also the only modal surface in this plugin —
1992
+ * everything else the drawer does can be done again.
1993
+ */
1994
+ .confirmScrim {
1995
+ /* `position`, `inset` and the stacking rank live with the drawer's other
1996
+ child ranks — see `.drawer > .confirmScrim`. */
1997
+ display: flex; align-items: center; justify-content: center;
1998
+ padding: 24px;
1999
+ background: var(--gs-backdrop);
2000
+ animation: gsFade 120ms ease-out;
2001
+ }
2002
+ .confirmBox {
2003
+ display: flex; flex-direction: column; gap: 10px;
2004
+ box-sizing: border-box;
2005
+ width: min(440px, 100%);
2006
+ padding: 18px;
2007
+ border: 1px solid var(--gs-border); border-radius: var(--gs-r-drawer);
2008
+ background: var(--gs-bg);
2009
+ box-shadow: 0 18px 48px var(--gs-shadow);
2010
+ }
2011
+ .confirmTitle { color: var(--gs-fg); font-size: var(--gs-t-ui); font-weight: 600; }
2012
+ /* The body carries a path, which is the one string here that can be long and
2013
+ has no sensible break points of its own. */
2014
+ .confirmBody {
2015
+ color: var(--gs-fg-dim);
2016
+ font-size: var(--gs-t-dense); line-height: 1.55;
2017
+ overflow-wrap: anywhere;
2018
+ }
2019
+ /* Cancel first in the DOM so it takes focus and the Tab order starts on the
2020
+ harmless answer; `row-reverse` puts the destructive one where the eye ends. */
2021
+ .confirmActions { display: flex; flex-direction: row-reverse; gap: 8px; margin-top: 2px; }
2022
+ /* Qualified rather than a bare modifier: written this way it out-specifies
2023
+ `.btn` wherever either rule sits, which is the failure the drawer already
2024
+ shipped once. */
2025
+ .btn.btnDanger {
2026
+ border-color: var(--gs-del); background: var(--gs-del); color: var(--gs-on-accent, #fff);
2027
+ font-weight: 600;
2028
+ }
2029
+ .btn.btnDanger:hover:not(:disabled) { border-color: var(--gs-del); background: var(--gs-del); color: var(--gs-on-accent, #fff); }