@young1lin/dsh-ui-gitworkbench 0.1.12 → 0.1.14

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.
@@ -0,0 +1,125 @@
1
+ /**
2
+ * How many matches the find panel found, and which one you are on.
3
+ *
4
+ * The panel ships without a count, which leaves the one question a reader
5
+ * actually has unanswered: a query that highlights nothing on screen might
6
+ * have no matches at all, or two hundred of them below the fold, and the panel
7
+ * looks identical either way.
8
+ *
9
+ * Counting is proportional to the DOCUMENT, and this drawer does not put work
10
+ * proportional to the document on the keystroke path. Two things keep that
11
+ * true, and both live here rather than in the wiring:
12
+ *
13
+ * - the walk stops at {@link MATCH_CAP}. A cap that turns a feature off would
14
+ * be no fix, so this one bounds the WORK and keeps the feature: past the cap
15
+ * the total reads `5000+`, which answers "is my query too broad" as well as
16
+ * an exact number would. It bounds memory the same way — a single-letter
17
+ * search over 20,000 lines of real TypeScript finds 71,515 matches, and an
18
+ * array of those is half a megabyte kept alive for a number nobody reads.
19
+ * - the offsets are KEPT, so moving between matches is a binary search rather
20
+ * than a second walk. Measured on this repo's own client sources: a full
21
+ * count costs 3-5ms at 300 lines, 9-11ms at 2,000, 12ms at 4,000, and 60ms
22
+ * at 20,000 (`SIDE_LINE_CAP`, the ceiling the pane will load). Recounting on
23
+ * every Enter would put that 60ms on the navigation path; recounting when
24
+ * the typing stops puts it nowhere the reader can feel it.
25
+ *
26
+ * The index belongs to one query over one document, and the caller throws it
27
+ * away when either changes — see `SearchCount` in `CodeEditor.tsx`.
28
+ *
29
+ * @module @young1lin/dsh-ui-gitworkbench/client/search-count
30
+ */
31
+
32
+ /**
33
+ * How many match positions are kept.
34
+ *
35
+ * 5,000 is past any count a reader distinguishes from "lots" and well inside
36
+ * what the pane can hold: at 8 bytes an offset it is 40KB, against the half a
37
+ * megabyte an uncapped single-letter search over the ceiling would take.
38
+ */
39
+ export const MATCH_CAP = 5000
40
+
41
+ /** Where every match starts, and whether the walk stopped early. */
42
+ export interface MatchIndex {
43
+ /** Ascending start offsets, at most {@link MATCH_CAP} of them. */
44
+ readonly offsets: readonly number[]
45
+ /** There were more matches than were kept; the total reads `N+`. */
46
+ readonly capped: boolean
47
+ }
48
+
49
+ /** No query, or a query with nothing to find. */
50
+ export const EMPTY_INDEX: MatchIndex = { offsets: [], capped: false }
51
+
52
+ /** One match start, however it is handed over. */
53
+ interface Match { readonly from: number }
54
+
55
+ /**
56
+ * Anything that yields matches in order.
57
+ *
58
+ * Both halves of the union are here for a reason: `SearchQuery.getCursor` is
59
+ * DECLARED as an iterator and is also iterable at runtime, while a test wants
60
+ * to hand over a plain array. Accepting either keeps the rule readable without
61
+ * a document, a view or a DOM behind it.
62
+ */
63
+ export type MatchWalk = Iterable<Match> | Iterator<Match>
64
+
65
+ /**
66
+ * Walk matches into an index, stopping at `cap`.
67
+ *
68
+ * The walk is driven by hand rather than with `for…of`, because the union
69
+ * above may arrive already unwrapped. A cursor reuses ONE object across
70
+ * iterations, which is safe here only because nothing but the number is kept.
71
+ */
72
+ export function indexMatches(matches: MatchWalk, cap: number = MATCH_CAP): MatchIndex {
73
+ const step: Iterator<Match> = Symbol.iterator in matches
74
+ ? matches[Symbol.iterator]()
75
+ : matches
76
+ const offsets: number[] = []
77
+ for (;;) {
78
+ if (offsets.length >= cap) {
79
+ // Ask once more: `capped` has to mean "there are more", not "there might
80
+ // have been", or a total that lands exactly on the cap reads as `5000+`.
81
+ return { offsets, capped: step.next().done !== true }
82
+ }
83
+ const next = step.next()
84
+ if (next.done === true) return { offsets, capped: false }
85
+ offsets.push(next.value.from)
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Which match the position `from` is on or before, 1-based; 0 when there are
91
+ * none.
92
+ *
93
+ * The first match at or after the caret, because that is the one the panel
94
+ * will land on — which makes the answer right in both of the states the reader
95
+ * sees it in. Straight after typing the caret has not moved and the count
96
+ * should already read `1/128`, not `0/128`; after Enter the selection sits
97
+ * exactly on a match and the lower bound IS that match. Past the last one the
98
+ * panel wraps, so the answer wraps with it.
99
+ */
100
+ export function ordinalAt(index: MatchIndex, from: number): number {
101
+ const { offsets } = index
102
+ if (offsets.length === 0) return 0
103
+ let low = 0
104
+ let high = offsets.length
105
+ while (low < high) {
106
+ const mid = (low + high) >> 1
107
+ if (offsets[mid]! < from) low = mid + 1
108
+ else high = mid
109
+ }
110
+ return low === offsets.length ? 1 : low + 1
111
+ }
112
+
113
+ /**
114
+ * The label: `3/128`, `3/5000+` when the walk stopped early, `0/0` for a query
115
+ * that finds nothing.
116
+ *
117
+ * No words, in a panel whose own labels are the library's English and are not
118
+ * translated either — a pair of numbers says it in every language the drawer
119
+ * ships (see `locales.ts` for the strings that do need both).
120
+ */
121
+ export function formatCount(index: MatchIndex, ordinal: number): string {
122
+ const total = index.offsets.length
123
+ if (total === 0) return '0/0'
124
+ return `${ordinal}/${total}${index.capped ? '+' : ''}`
125
+ }
@@ -107,6 +107,7 @@
107
107
  box-sizing: border-box; width: 100%;
108
108
  padding: 4px 12px 4px 8px;
109
109
  border: 0;
110
+ border-radius: var(--gs-r-control);
110
111
  background: transparent;
111
112
  color: var(--gs-fg-dim);
112
113
  font-family: inherit;
@@ -114,7 +115,7 @@
114
115
  text-align: left;
115
116
  cursor: pointer;
116
117
  }
117
- .treeDir:hover { background: var(--gs-panel); color: var(--gs-fg-muted); }
118
+ .treeDir:hover { background: var(--gs-raise); color: var(--gs-fg-muted); }
118
119
  .treeDirActive { color: var(--gs-fg-muted); }
119
120
  .chevron {
120
121
  flex: none;
@@ -132,7 +133,7 @@
132
133
  .treeDirCount {
133
134
  flex: none;
134
135
  padding: 0 6px;
135
- border-radius: 999px;
136
+ border-radius: var(--gs-r-pill);
136
137
  background: var(--gs-neutral-bg);
137
138
  color: var(--gs-fg-dim);
138
139
  font-size: var(--gs-t-meta); font-weight: 600; line-height: 16px;
@@ -144,6 +145,7 @@
144
145
  padding: 5px 14px;
145
146
  border: 0;
146
147
  border-left: 2px solid transparent;
148
+ border-radius: var(--gs-r-control);
147
149
  background: transparent;
148
150
  color: var(--gs-fg-muted);
149
151
  font-family: inherit;
@@ -151,18 +153,14 @@
151
153
  text-align: left;
152
154
  cursor: pointer;
153
155
  }
154
- .file:hover { background: var(--gs-panel); }
155
- .fileActive {
156
- background: var(--gs-panel);
157
- border-left-color: var(--gs-accent);
158
- color: var(--gs-fg);
159
- }
156
+ .file:hover { background: var(--gs-raise); }
157
+ /* .fileActive: see the shared selected-state rule in controls.css. */
160
158
 
161
159
  .fileStatus {
162
160
  flex: none;
163
161
  display: inline-flex; align-items: center; justify-content: center;
164
162
  width: 18px; height: 18px;
165
- border-radius: 5px;
163
+ border-radius: var(--gs-r-control);
166
164
  font-weight: 700; font-size: var(--gs-t-meta); line-height: 1;
167
165
  }
168
166
  .stAdded, .stUntracked { color: var(--gs-add); background: var(--gs-add-bg); }
@@ -174,7 +172,7 @@
174
172
  .fileBinary {
175
173
  flex: none;
176
174
  padding: 0 5px;
177
- border-radius: 4px;
175
+ border-radius: var(--gs-r-control);
178
176
  background: var(--gs-neutral-bg);
179
177
  color: var(--gs-fg-dim);
180
178
  font-size: 9px; font-weight: 700; letter-spacing: 0.06em;
@@ -281,6 +279,14 @@
281
279
 
282
280
  .wordAdd { background: var(--gs-add-word); border-radius: 2px; }
283
281
  .wordDel { background: var(--gs-del-word); border-radius: 2px; }
282
+ /* A carriage return, drawn. The diff's text carries the CR byte through
283
+ verbatim and a browser paints nothing for it, so a line whose only change
284
+ is its ending rendered as two identical-looking cells; the glyph (U+240D,
285
+ the picture of the control character — `cr-mark.ts`) makes the ending
286
+ visible wherever the diff paints one. Dim and slightly small so a whole
287
+ CRLF file reads as code with quiet markers, not as a wall of symbols, and
288
+ unselectable so copying a line copies code. */
289
+ .crMark { color: var(--gs-fg-faint); font-size: 0.9em; user-select: none; }
284
290
 
285
291
  /* Side-by-side diff pane. One grid holds EVERY row's four cells — a per-row
286
292
  grid would let the column boundary drift with each row's content, and a
@@ -459,6 +465,9 @@
459
465
  opacity: 0.32;
460
466
  transition: opacity 140ms ease;
461
467
  pointer-events: none;
468
+ /* Above the gutter, which CodeMirror stacks at 200. The bar overlays its
469
+ first two pixels, so at the default z-index an opaque gutter buries it. */
470
+ z-index: 201;
462
471
  }
463
472
  .cmHost[data-editable]:focus-within::before { opacity: 1; }
464
473
  /* The change nav takes the free space, so it and `.sideActions` after it sit
@@ -82,9 +82,20 @@
82
82
  because it has to win against the grouped rule above by source order. */
83
83
  .scopeBtn { flex: 1 1 0; }
84
84
 
85
- /* The single "this one is selected" idiom. Three different ones accent chip,
86
- bordered panel, left accent bar used to mean the same thing in one drawer. */
87
- .scopeBtnActive, .miniBtnPrimary, .segmentActive, .refRowActive, .paletteRowActive {
85
+ /* The single "this one is selected" idiom: accent text, in an accent tint,
86
+ inside an accent border. Everything that can be the chosen one is here a
87
+ tree leaf, a commit, a ref, a palette, a segment, a funnel tab, a date
88
+ bound, a preset — because the drawer had reached three spellings of one
89
+ meaning (this chip, a filled panel, a left accent bar) and showed two of
90
+ them at once whenever the Changes tree sat beside the history.
91
+
92
+ A modifier applied as `${css.base} ${css.mod}` carries the SAME specificity
93
+ as its base, so source order decides. Every base named here is declared
94
+ above this rule; `.fbRow.fbRowActive` is the one exception and is written
95
+ qualified, because `files.css` is imported after this file. */
96
+ .scopeBtnActive, .miniBtnPrimary, .segmentActive, .refRowActive, .paletteRowActive,
97
+ .fileActive, .commitActive, .funnelTabActive, .funnelBoundBtnActive, .funnelPresetActive,
98
+ .fbRow.fbRowActive {
88
99
  border-color: var(--gs-accent-border);
89
100
  background: var(--gs-accent-bg);
90
101
  color: var(--gs-accent);
@@ -125,10 +136,10 @@
125
136
  /* Publishing a branch that exists nowhere else is the one moment this bar has a
126
137
  primary action, so it gets the solid fill Commit uses — and only then. */
127
138
  .btnPrimary {
128
- border-color: var(--gs-accent); background: var(--gs-accent); color: var(--gs-on-accent, #fff);
139
+ border-color: var(--gs-accent); background: var(--gs-accent); color: var(--gs-on-accent);
129
140
  font-weight: 600;
130
141
  }
131
- .btnPrimary:hover:not(:disabled) { background: var(--gs-accent); color: var(--gs-on-accent, #fff); border-color: var(--gs-accent); }
142
+ .btnPrimary:hover:not(:disabled) { background: var(--gs-accent); color: var(--gs-on-accent); border-color: var(--gs-accent); }
132
143
 
133
144
  /* The count riding inside Pull / Push. Reads as part of the label rather than a
134
145
  badge stuck on it: same colour, tabular, just set apart by a hairline. */
@@ -165,7 +176,7 @@
165
176
  background: var(--gs-border);
166
177
  background-clip: padding-box;
167
178
  border: 3px solid transparent;
168
- border-radius: 999px;
179
+ border-radius: var(--gs-r-pill);
169
180
  }
170
181
  .drawer ::-webkit-scrollbar-thumb:hover { background: var(--gs-fg-faint); background-clip: padding-box; }
171
182
 
@@ -219,7 +230,7 @@
219
230
  `.btn` wherever either rule sits, which is the failure the drawer already
220
231
  shipped once. */
221
232
  .btn.btnDanger {
222
- border-color: var(--gs-del); background: var(--gs-del); color: var(--gs-on-accent, #fff);
233
+ border-color: var(--gs-del); background: var(--gs-del); color: var(--gs-on-accent);
223
234
  font-weight: 600;
224
235
  }
225
- .btn.btnDanger:hover:not(:disabled) { border-color: var(--gs-del); background: var(--gs-del); color: var(--gs-on-accent, #fff); }
236
+ .btn.btnDanger:hover:not(:disabled) { border-color: var(--gs-del); background: var(--gs-del); color: var(--gs-on-accent); }
@@ -21,7 +21,7 @@
21
21
  color: var(--gs-fg);
22
22
  background: var(--gs-surface);
23
23
  border: 1px solid var(--gs-border);
24
- border-radius: 4px;
24
+ border-radius: var(--gs-r-control);
25
25
  }
26
26
  .fbSearch:focus { outline: none; border-color: var(--gs-accent); }
27
27
  .fbNote { flex: none; font-size: var(--gs-t-meta); color: var(--gs-fg-dim); padding: 2px 4px; }
@@ -38,14 +38,16 @@
38
38
  text-align: left;
39
39
  background: none;
40
40
  border: 0;
41
- border-radius: 3px;
41
+ border-radius: var(--gs-r-control);
42
42
  cursor: pointer;
43
43
  white-space: nowrap;
44
44
  overflow: hidden;
45
45
  text-overflow: ellipsis;
46
46
  }
47
- .fbRow:hover { background: var(--gs-hover); }
48
- .fbRowActive { background: var(--gs-selected); color: var(--gs-fg); }
47
+ .fbRow:hover { background: var(--gs-raise); }
48
+ /* .fbRowActive: see the shared selected-state rule in controls.css. It is
49
+ written there qualified, because this file is imported after that one and a
50
+ bare modifier declared above its base loses the cascade. */
49
51
  .fbDirName { overflow: hidden; text-overflow: ellipsis; }
50
52
  .fbFileName { overflow: hidden; text-overflow: ellipsis; }
51
53
  .fbMain {
@@ -47,7 +47,7 @@
47
47
  font-family: inherit; font-size: var(--gs-t-dense); line-height: 20px;
48
48
  cursor: pointer;
49
49
  }
50
- .filterChipRemove:hover { color: var(--gs-danger, var(--gs-fg)); }
50
+ .filterChipRemove:hover { color: var(--gs-del); }
51
51
  .filterClear {
52
52
  border: none; background: transparent;
53
53
  padding: 2px 4px;
@@ -91,13 +91,17 @@
91
91
  display: flex; gap: 2px;
92
92
  margin: 8px 8px 0;
93
93
  padding: 2px;
94
- border: 1px solid var(--gs-border-soft); border-radius: var(--gs-r-pill);
94
+ border: 1px solid var(--gs-border-soft); border-radius: var(--gs-r-control);
95
95
  background: var(--gs-bg);
96
96
  }
97
97
  .funnelTab {
98
98
  flex: 1; min-width: 0;
99
- height: 22px; padding: 0 8px;
100
- border: none; border-radius: var(--gs-r-pill);
99
+ box-sizing: border-box;
100
+ height: var(--gs-h-compact); padding: 0 8px;
101
+ /* Transparent rather than none: the shared selected rule paints its
102
+ members through `border-color`, and a border that is not there takes
103
+ no colour. Inside `border-box`, so the strip does not grow by 2px. */
104
+ border: 1px solid transparent; border-radius: calc(var(--gs-r-control) - 3px);
101
105
  background: transparent;
102
106
  color: var(--gs-fg-dim);
103
107
  font-family: inherit; font-size: var(--gs-t-dense); line-height: 1;
@@ -106,11 +110,7 @@
106
110
  transition: background 120ms ease, color 120ms ease;
107
111
  }
108
112
  .funnelTab:hover { color: var(--gs-fg); }
109
- .funnelTab.funnelTabActive {
110
- color: var(--gs-fg);
111
- background: var(--gs-raise);
112
- box-shadow: inset 0 0 0 1px var(--gs-border);
113
- }
113
+ /* .funnelTabActive: see the shared selected-state rule in controls.css. */
114
114
  .funnelTab:focus-visible { outline: 2px solid var(--gs-accent); outline-offset: 1px; }
115
115
  /* A tab's own criteria count, so the two sections nobody is looking at still
116
116
  say they hold something. */
@@ -126,7 +126,7 @@
126
126
  .funnelCaption {
127
127
  flex: none;
128
128
  color: var(--gs-fg-faint);
129
- font-size: 11px; line-height: 14px;
129
+ font-size: var(--gs-t-meta); line-height: 14px;
130
130
  letter-spacing: 0.04em; text-transform: uppercase;
131
131
  }
132
132
 
@@ -143,8 +143,9 @@
143
143
  }
144
144
  .funnelBoundBtn {
145
145
  flex: 1; min-width: 0;
146
- height: 20px; padding: 0 6px;
147
- border: none; border-radius: calc(var(--gs-r-control) - 3px);
146
+ box-sizing: border-box;
147
+ height: var(--gs-h-compact); padding: 0 6px;
148
+ border: 1px solid transparent; border-radius: calc(var(--gs-r-control) - 3px);
148
149
  background: transparent;
149
150
  color: var(--gs-fg-dim);
150
151
  font-family: inherit; font-size: var(--gs-t-dense); line-height: 1;
@@ -152,11 +153,7 @@
152
153
  transition: background 120ms ease, color 120ms ease;
153
154
  }
154
155
  .funnelBoundBtn:hover { color: var(--gs-fg); }
155
- .funnelBoundBtn.funnelBoundBtnActive {
156
- color: var(--gs-accent);
157
- background: var(--gs-accent-bg);
158
- box-shadow: inset 0 0 0 1px var(--gs-accent-border);
159
- }
156
+ /* .funnelBoundBtnActive: see the shared selected-state rule in controls.css. */
160
157
 
161
158
  /* The calendar — modern-grid look in the drawer's own tokens. */
162
159
  .cal { flex: none; display: flex; flex-direction: column; gap: 4px; }
@@ -168,7 +165,7 @@
168
165
  }
169
166
  .calNav {
170
167
  flex: none;
171
- width: 22px; height: 20px; padding: 0;
168
+ width: var(--gs-h-compact); height: var(--gs-h-compact); padding: 0;
172
169
  border: none; border-radius: var(--gs-r-control);
173
170
  background: transparent;
174
171
  color: var(--gs-fg-dim);
@@ -184,10 +181,10 @@
184
181
  .calWeek span {
185
182
  text-align: center;
186
183
  color: var(--gs-fg-faint);
187
- font-size: 11px; line-height: 14px;
184
+ font-size: var(--gs-t-meta); line-height: 14px;
188
185
  }
189
186
  .calGrid button {
190
- height: 26px;
187
+ height: var(--gs-h-compact);
191
188
  border: none; border-radius: var(--gs-r-control);
192
189
  background: transparent;
193
190
  color: var(--gs-fg-muted);
@@ -215,7 +212,7 @@
215
212
  padding: 3px 6px;
216
213
  border: 1px solid var(--gs-border-soft); border-radius: var(--gs-r-control);
217
214
  background: var(--gs-bg);
218
- font-size: 11px; line-height: 16px;
215
+ font-size: var(--gs-t-meta); line-height: 16px;
219
216
  }
220
217
  .funnelBoundKey { flex: none; color: var(--gs-fg-faint); }
221
218
  .funnelBoundVal {
@@ -233,12 +230,12 @@
233
230
  font-family: inherit; font-size: var(--gs-t-dense); line-height: 1;
234
231
  cursor: pointer;
235
232
  }
236
- .funnelBoundClear:hover { color: var(--gs-danger, var(--gs-fg)); }
233
+ .funnelBoundClear:hover { color: var(--gs-del); }
237
234
 
238
235
  .funnelSearch {
239
236
  flex: none;
240
237
  box-sizing: border-box;
241
- height: 26px; padding: 0 8px;
238
+ height: var(--gs-h-compact); padding: 0 8px;
242
239
  border: 1px solid var(--gs-border-soft); border-radius: var(--gs-r-control);
243
240
  background: var(--gs-bg); color: var(--gs-fg);
244
241
  font-family: inherit; font-size: var(--gs-t-dense);
@@ -257,7 +254,7 @@
257
254
  .funnelList::-webkit-scrollbar-track { background: transparent; }
258
255
  .funnelList::-webkit-scrollbar-thumb {
259
256
  background: var(--gs-fg-fainter);
260
- border: 3px solid transparent; border-radius: 5px;
257
+ border: 3px solid transparent; border-radius: var(--gs-r-pill);
261
258
  background-clip: content-box;
262
259
  }
263
260
  /* Every row is the same four slots — chevron, tick, glyph, name — then the
@@ -339,7 +336,7 @@
339
336
  .funnelCount {
340
337
  flex: none;
341
338
  color: var(--gs-fg-faint);
342
- font-size: 11px;
339
+ font-size: var(--gs-t-meta);
343
340
  font-variant-numeric: tabular-nums;
344
341
  }
345
342
  .funnelMore { color: var(--gs-fg-faint); padding: 4px 6px; }
@@ -360,14 +357,7 @@
360
357
  the drawer. It is in both selector lists now; this only sizes them to share
361
358
  one row. */
362
359
  .funnelPreset { flex: 1 1 0; min-width: 0; padding: 0 6px; }
363
- /* Qualified, like `.funnelButtonActive`: the shared vocabulary is declared
364
- several hundred lines BELOW this one, so a bare modifier would lose its
365
- background to that rule's `background: transparent`. */
366
- .funnelPreset.funnelPresetActive {
367
- color: var(--gs-accent);
368
- border-color: var(--gs-accent);
369
- background: var(--gs-accent-bg);
370
- }
360
+ /* .funnelPresetActive: see the shared selected-state rule in controls.css. */
371
361
 
372
362
  /* The footer says what the panel is currently asking git for. Without it the
373
363
  only feedback lived on the chip row BEHIND the panel, so a reader had to
@@ -378,7 +368,7 @@
378
368
  padding: 6px 10px;
379
369
  border-top: 1px solid var(--gs-border-soft);
380
370
  background: var(--gs-bg);
381
- font-size: 11px; line-height: 16px;
371
+ font-size: var(--gs-t-meta); line-height: 16px;
382
372
  }
383
373
  .funnelFootCount { color: var(--gs-fg-faint); font-variant-numeric: tabular-nums; }
384
374
  .funnelFootCountOn { color: var(--gs-fg-muted); }
@@ -388,7 +378,7 @@
388
378
  border: none; border-radius: var(--gs-r-control);
389
379
  background: transparent;
390
380
  color: var(--gs-fg-dim);
391
- font-family: inherit; font-size: 11px; line-height: 16px;
381
+ font-family: inherit; font-size: var(--gs-t-meta); line-height: 16px;
392
382
  cursor: pointer;
393
383
  }
394
384
  .funnelFootClear:hover:not(:disabled) { color: var(--gs-fg); background: var(--gs-raise); }
@@ -208,7 +208,7 @@
208
208
  min-width: 0;
209
209
  }
210
210
  .commit:hover { background: var(--gs-raise); }
211
- .commitActive { border-color: var(--gs-accent-border); background: var(--gs-accent-bg); }
211
+ /* .commitActive: see the shared selected-state rule in controls.css. */
212
212
  /* Pushed to the right end of the row, and never squeezed: the subject is what
213
213
  gives way when the pane narrows, because it is the part with an ellipsis to
214
214
  give. */
@@ -41,8 +41,9 @@
41
41
 
42
42
  /* Radii. Four values, each with one job; nothing else may invent a fifth. */
43
43
  --gs-r-pill: 999px; /* drawer chrome: header actions, ref chips, triggers */
44
- --gs-r-control: 8px; /* controls inside a panel or pane; inputs; badges */
45
- --gs-r-surface: 12px; /* rows, popovers, panes */
44
+ --gs-r-control: 8px; /* controls inside a panel or pane; inputs; badges;
45
+ and the dense list rows a tree or a picker is made of */
46
+ --gs-r-surface: 12px; /* the tall card rows, popovers and panes they sit in */
46
47
  --gs-r-drawer: 14px; /* the drawer and its companion card */
47
48
 
48
49
  /* Type — dsh's --dsw-font-* ladder, which tops out at 11px for meta text. */
@@ -215,7 +216,7 @@
215
216
  display: inline-flex; align-items: center; gap: 6px;
216
217
  padding: 2px 10px;
217
218
  border: 1px solid var(--gs-accent-border);
218
- border-radius: 999px;
219
+ border-radius: var(--gs-r-pill);
219
220
  background: var(--gs-accent-bg);
220
221
  color: var(--gs-accent);
221
222
  font-weight: 600;
@@ -271,7 +272,7 @@
271
272
  .headerView {
272
273
  flex: none;
273
274
  padding: 1px 8px;
274
- border-radius: 999px;
275
+ border-radius: var(--gs-r-pill);
275
276
  background: var(--gs-neutral-bg);
276
277
  color: var(--gs-fg-muted);
277
278
  font-size: var(--gs-t-meta);
@@ -279,7 +280,7 @@
279
280
  }
280
281
  .headerDetached {
281
282
  padding: 1px 8px;
282
- border-radius: 999px;
283
+ border-radius: var(--gs-r-pill);
283
284
  background: var(--gs-warn-bg);
284
285
  color: var(--gs-warn);
285
286
  font-size: var(--gs-t-meta);