typebulb 0.14.4 → 0.14.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.
@@ -155,6 +155,17 @@ body {
155
155
  cluster spaces evenly regardless of which pills are present. */
156
156
  .token-wrap:empty, .sid-wrap:empty, .servers-wrap:empty { display: none; }
157
157
  .picker, .servers-pop {
158
+ --menu-h: 428px; /* one height for both statusbar menus — they sit side by side, so a
159
+ mismatched cap reads as a glitch; .picker grows to it, .servers-pop
160
+ is fixed at it (see its comment). Sized to show exactly 12 rows
161
+ (12 × --menu-row-h + 11 gaps + filter + padding) — recognition fades
162
+ past ~12 and search owns the tail. Plain px: desktop-only surface,
163
+ every desktop window fits it. 428, not 427: at 427 the two menus land
164
+ on different device-pixel roundings, a 1px sibling mismatch — the
165
+ lesser evil is 1px off the ideal height. */
166
+ --menu-row-h: 29px; /* one row height for both menus' lists — rows must match pitch exactly
167
+ or the side-by-side menus read as two designs. Whole px, eyeball-tuned;
168
+ the row paddings are kept below it so this floor is what governs. */
158
169
  position: absolute;
159
170
  bottom: calc(100% + .35rem);
160
171
  right: 0;
@@ -165,56 +176,86 @@ body {
165
176
  box-shadow: 0 8px 30px rgba(0, 0, 0, .45);
166
177
  }
167
178
 
179
+ /* ---- Shimmer — the one "working" cue. One gradient, one keyframes, composed via class, never
180
+ re-pasted (paste-drift is how each new shimmer kept landing at its own speed). `.shimmer` sweeps
181
+ the element's background; `.shimmer-text` clips the sweep to the glyphs alone. Knobs, set by the
182
+ using rule: --shimmer-base (the gradient's resting color — transparent for see-through chrome, an
183
+ opaque surface for pills that must cover content) and --shimmer-mix (accent strength). Pick the
184
+ cadence by the band's LINEAR speed, which is what reads consistent: travel is proportional to
185
+ element width (background-size 250%), and ~2rem/s is the calibrated pace — so narrow chrome
186
+ (a 1.7rem play button) takes the 2.2s default and anything text-width or wider takes
187
+ `.shimmer-slow` (7s). A fast sweep reads as urgency, not progress. */
188
+ /* The state longhands are !important: this class composes onto elements whose resting rules set a
189
+ `background:` shorthand at arbitrary specificity (button resets, .overlay-pill, :hover tints) —
190
+ any of which would silently kill the gradient (the bug that ate the play button's shimmer). A
191
+ working cue must win while it's on; resting background-color still shows beneath the sweep. */
192
+ .shimmer, .shimmer-text { background-size: 250% 100% !important; animation: bulb-shimmer var(--shimmer-speed, 2.2s) linear infinite !important; }
193
+ .shimmer {
194
+ background-image: linear-gradient(100deg, var(--shimmer-base, transparent) 30%, color-mix(in srgb, var(--accent) var(--shimmer-mix, 35%), var(--shimmer-base, transparent)) 50%, var(--shimmer-base, transparent) 70%) !important;
195
+ background-color: var(--shimmer-base, transparent);
196
+ }
197
+ .shimmer-text {
198
+ /* Shrink-to-fit, so the sweep paces the text's own length, not the container's — on a full-width
199
+ box the band spends most of its run off-glyph (same move as typebulb.com's shimmer.ts). */
200
+ display: inline-block;
201
+ background-image: linear-gradient(100deg, currentColor 35%, var(--accent) 50%, currentColor 65%) !important;
202
+ -webkit-background-clip: text;
203
+ background-clip: text;
204
+ -webkit-text-fill-color: transparent;
205
+ }
206
+ .shimmer-slow { --shimmer-speed: 7s; }
207
+ @keyframes bulb-shimmer { from { background-position: 250% 0; } to { background-position: -250% 0; } }
208
+ @media (prefers-reduced-motion: reduce) {
209
+ .shimmer { animation: none !important; background: color-mix(in srgb, var(--accent) 15%, var(--shimmer-base, transparent)) !important; }
210
+ .shimmer-text { animation: none !important; background: none !important; -webkit-text-fill-color: currentColor; }
211
+ }
212
+
168
213
  /* CC mid-turn: the token pill doubles as the working indicator — an accent shimmer
169
214
  over the chip, the count frozen until the turn flushes. Passive — driven by the
170
215
  live-chain leaf, not a live token stream (the JSONL is flushed in batches, so this
171
- can sit lit through a multi-minute buffering window). The sweep blends with
172
- --panel, not transparent like .launching's: statusbar pills must stay opaque over
173
- the message text the strip lets through. */
174
- .token.busy {
175
- border-color: var(--accent);
176
- background: linear-gradient(100deg, var(--panel) 30%, color-mix(in srgb, var(--accent) 25%, var(--panel)) 50%, var(--panel) 70%);
177
- background-size: 250% 100%;
178
- /* Much slower than .launching's sweep: that one paces a ~2s wait, this can sit lit for
179
- minutes — at that dwell a fast sweep reads as urgency, not progress. */
180
- animation: bulb-shimmer 7s linear infinite;
181
- }
182
- @media (prefers-reduced-motion: reduce) {
183
- .token.busy { animation: none; background: color-mix(in srgb, var(--accent) 15%, var(--panel)); }
184
- }
216
+ can sit lit through a multi-minute buffering window — hence shimmer-slow). The sweep
217
+ blends with --panel, not transparent like the launch buttons': statusbar pills must
218
+ stay opaque over the message text the strip lets through. */
219
+ .token.busy { border-color: var(--accent); --shimmer-base: var(--panel); --shimmer-mix: 25%; }
185
220
 
186
221
  /* Session list — shares the popover chrome (.picker above); these are
187
222
  just its own dimensions. */
188
223
  .picker {
189
224
  width: min(440px, calc(100vw - 2rem));
190
- max-height: 60vh;
225
+ max-height: var(--menu-h);
191
226
  overflow: hidden;
192
227
  padding: .35rem;
193
228
  display: flex;
194
229
  flex-direction: column;
195
230
  gap: .15rem;
196
231
  }
197
- /* The filter (.bulb-filter-control) pins at the top; the row list scrolls inside the capped box —
198
- same shape as the launcher's .servers-pop / .bulb-list. */
232
+ /* The filter (.bulb-filter-control) pins at the bottom the popover's anchored edge, so it never
233
+ jumps as the filtered list changes height; the row list scrolls inside the capped box, resting at
234
+ its end (newest-at-bottom) — same shape as the launcher's .servers-pop / .bulb-list. */
199
235
  .picker-list { display: flex; flex-direction: column; gap: .15rem; flex: 1; min-height: 0; overflow-y: auto; padding-right: 4px; }
200
236
  .picker-empty { padding: .8rem; color: var(--muted); font-size: .85rem; text-align: center; }
201
237
  .picker-row {
202
238
  display: flex;
203
- gap: .6rem;
204
- align-items: baseline;
205
- padding: .45rem .55rem;
239
+ flex-direction: column;
240
+ /* flex:none — the explicit min-height below replaces flexbox's implicit min-height:auto floor,
241
+ which would otherwise let an overflowing list squash rows below their content (snippet rows
242
+ rendered as overlapping text). The list scrolls; rows never compress. */
243
+ flex: none;
244
+ gap: .2rem;
245
+ /* min-height, not height: a full-text result row grows for its snippet line. */
246
+ padding: .35rem .55rem;
247
+ min-height: var(--menu-row-h);
248
+ box-sizing: border-box;
249
+ justify-content: center;
206
250
  border-radius: 6px;
207
251
  cursor: pointer;
208
- border: 1px solid transparent;
209
252
  font-size: .85rem;
210
253
  }
254
+ .picker-row-main { display: flex; gap: .6rem; align-items: baseline; }
211
255
  .picker:focus { outline: none; } /* the list owns its own highlight; no focus ring */
212
256
  .picker-row:hover { background: color-mix(in srgb, var(--accent) 10%, transparent); }
213
- /* Keyboard cursor (arrow keys / hover) — matches the bulb picker's .active row. */
214
- .picker-row.active {
215
- background: color-mix(in srgb, var(--accent) 18%, transparent);
216
- border-color: color-mix(in srgb, var(--accent) 40%, transparent);
217
- }
257
+ /* Keyboard cursor (arrow keys / hover) — same borderless tint as the launcher's .server-row.active. */
258
+ .picker-row.active { background: color-mix(in srgb, var(--accent) 16%, transparent); }
218
259
  /* The attached session — a leading accent dot in its own gutter slot, reserved (transparent) on
219
260
  every row so titles align whether or not it's shown, and filled only on the attached row so it
220
261
  reads as "you are here" even when the cursor has moved off. */
@@ -237,6 +278,20 @@ body {
237
278
  font-size: .72rem;
238
279
  min-width: 2.6rem; text-align: right;
239
280
  }
281
+ .picker-hits { color: var(--muted); font-size: .72rem; white-space: nowrap; }
282
+ /* Full-text result context: first matching turn, indented past the dot gutter into the title column. */
283
+ .picker-snippet {
284
+ color: var(--muted);
285
+ font-size: .78rem;
286
+ line-height: 1.4;
287
+ margin-left: calc(7px + .6rem);
288
+ overflow-wrap: anywhere;
289
+ }
290
+ .picker-mark {
291
+ background: color-mix(in srgb, var(--accent) 30%, transparent);
292
+ border-radius: 2px;
293
+ color: var(--fg);
294
+ }
240
295
 
241
296
  /* Running-breakouts list — shares the popover chrome (.servers-pop above); its own dimensions +
242
297
  per-row layout: the play/stop button + name read together on the left (▶/■ Title, name flex:1),
@@ -248,7 +303,7 @@ body {
248
303
  scrolls inside (see .bulb-list). */
249
304
  .servers-pop {
250
305
  width: min(560px, calc(100vw - 2rem));
251
- height: min(66vh, 560px);
306
+ height: var(--menu-h);
252
307
  overflow: hidden;
253
308
  padding: .35rem;
254
309
  display: flex;
@@ -271,7 +326,8 @@ body {
271
326
  the left, an asymmetry). Text gets its breathing room inside the highlight via end-item margins
272
327
  (.server-stop/.bulb-launch on the left, .server-port/.bulb-time on the right) rather than row
273
328
  padding, which would shift the subgrid tracks and break alignment. */
274
- padding: .15rem 0;
329
+ padding: .05rem 0;
330
+ min-height: var(--menu-row-h);
275
331
  border-radius: 6px;
276
332
  font-size: .85rem;
277
333
  }
@@ -294,7 +350,7 @@ a.server-port:hover { text-decoration: underline; }
294
350
  /* Launcher filter box — pinned above the list; type to narrow (matches name + path). The control
295
351
  wrapper pins to the top and anchors the clear (×) at the far right (position:relative + the
296
352
  input's right padding reserves its room). */
297
- .bulb-filter-control { position: relative; flex: none; margin-bottom: .35rem; }
353
+ .bulb-filter-control { position: relative; flex: none; margin-top: .35rem; }
298
354
  .bulb-filter {
299
355
  font: inherit;
300
356
  font-size: .82rem;
@@ -315,11 +371,29 @@ a.server-port:hover { text-decoration: underline; }
315
371
  color: var(--muted); font-size: 1.05rem; line-height: 1; padding: 0 .3rem; border-radius: 4px;
316
372
  }
317
373
  .bulb-filter-clear:hover { color: var(--accent); }
374
+ /* Filter-mode toggle (full-text search) — inset at the input's right edge; the clear × shifts
375
+ inward beside it. Only rendered when the host passes a trailing control (the session picker). */
376
+ .bulb-filter-control.has-trailing .bulb-filter { padding-right: 3.8rem; }
377
+ .bulb-filter-control.has-trailing .bulb-filter-clear { right: 2.5rem; }
378
+ .bulb-filter-mode {
379
+ /* Stretches flush to the input's top/right/bottom, inside its 1px border; the right corners
380
+ match the input's 6px radius so the latched fill reads as part of the box. */
381
+ position: absolute; right: 1px; top: 1px; bottom: 1px;
382
+ appearance: none; border: none; background: transparent; cursor: pointer;
383
+ font-size: .8rem; line-height: 1; padding: 0 .45rem;
384
+ border-radius: 0 5px 5px 0;
385
+ }
386
+ /* Same state cue as the prose pill's glyph: the emoji can't take currentColor, so it goes
387
+ grayscale at rest, color while hovered or on — filtered on the inner span, never the button. */
388
+ .bulb-filter-mode .glyph-img { filter: grayscale(1); opacity: .75; }
389
+ .bulb-filter-mode:hover .glyph-img, .bulb-filter-mode.on .glyph-img { filter: none; opacity: 1; }
390
+ /* Latched: a --panel fill — the popover's own surface, one step off the input's --bg. */
391
+ .bulb-filter-mode.on { background: var(--panel); }
318
392
  /* Scrolls within the fixed-height popover so the filter stays put and the box never grows. */
319
393
  /* The shared grid for the launcher rows: each .server-row is a subgrid spanning these tracks, so
320
394
  columns line up row-to-row. auto cols size to their widest content (tight, no slack); 1fr is the
321
395
  name. A small right padding keeps the selected-row highlight off the scrollbar. */
322
- .bulb-list { display: grid; grid-template-columns: auto 1fr auto auto auto; column-gap: .6rem; row-gap: .05rem; align-items: center; align-content: start; padding-right: 4px; flex: 1; min-height: 0; overflow-y: auto; }
396
+ .bulb-list { display: grid; grid-template-columns: auto 1fr auto auto auto; column-gap: .6rem; row-gap: .15rem; align-items: center; align-content: start; padding-right: 4px; flex: 1; min-height: 0; overflow-y: auto; }
323
397
  /* A stopped bulb's name isn't a link (no running URL), so it stays plain foreground. */
324
398
  .server-name.stopped { color: var(--fg); }
325
399
  .bulb-time { color: var(--muted); font-size: .72rem; font-variant-numeric: tabular-nums; text-align: right; margin-right: .4rem; }
@@ -331,7 +405,9 @@ a.server-port:hover { text-decoration: underline; }
331
405
  background: transparent; border: 1px solid var(--border); border-radius: 5px;
332
406
  height: 1.7rem; display: inline-flex; align-items: center; justify-content: center;
333
407
  }
334
- .trust-toggle { font-size: .76rem; padding: 0 .5rem; color: var(--muted); justify-self: center; }
408
+ /* Shorter than the icon buttons: a bordered text pill at full row height reads as cramped, where
409
+ the round play/stop icons are meant to fill it. */
410
+ .trust-toggle { font-size: .76rem; height: 1.45rem; padding: 0 .5rem; color: var(--muted); justify-self: center; }
335
411
  .trust-toggle:hover, .trust-toggle.on { color: var(--accent); border-color: var(--accent); }
336
412
  .server-stop, .bulb-launch { width: 1.7rem; padding: 0; border-radius: 50%; margin-left: .4rem; }
337
413
  .btn-icon { display: block; }
@@ -344,18 +420,7 @@ a.server-port:hover { text-decoration: underline; }
344
420
  /* Launching: stays visible (hover-independent) and shimmers until the row flips to a stop button,
345
421
  so the ~2s wait reads as "working" rather than a dead click. Transient — driven by the in-memory
346
422
  launching set, never persisted. */
347
- .bulb-launch.launching {
348
- visibility: visible; border-color: var(--accent);
349
- background: linear-gradient(100deg, transparent 30%, color-mix(in srgb, var(--accent) 35%, transparent) 50%, transparent 70%);
350
- background-size: 250% 100%;
351
- /* One full pass per ~2s wait — slow enough to stay calm, fast enough that the band
352
- is guaranteed to cross while the user is actually watching. */
353
- animation: bulb-shimmer 2.2s linear infinite;
354
- }
355
- @keyframes bulb-shimmer { from { background-position: 250% 0; } to { background-position: -250% 0; } }
356
- @media (prefers-reduced-motion: reduce) {
357
- .bulb-launch.launching { animation: none; background: color-mix(in srgb, var(--accent) 18%, transparent); }
358
- }
423
+ .bulb-launch.launching { visibility: visible; border-color: var(--accent); }
359
424
  .server-stop { color: var(--muted); }
360
425
  .server-stop:hover { color: var(--err); border-color: var(--err); }
361
426
  /* Elevation prompt (VS-Code-Workspace-Trust style) — a modal over the whole view, since a
@@ -789,18 +854,10 @@ a.server-port:hover { text-decoration: underline; }
789
854
  .md .bulb-embed:hover .bulb-controls .overlay-pill,
790
855
  .md .bulb-controls .overlay-pill:focus-visible { opacity: 1; }
791
856
  .md .bulb-breakout:disabled { cursor: default; opacity: 1; }
792
- /* Breakout in flight: shimmer through the ~2s write+spawn wait so the click reads as "working"
793
- rather than dead — the same treatment (and shared keyframes) as the launcher's play button
794
- (.bulb-launch.launching). :disabled already keeps it visible if the pointer leaves. */
795
- .md .bulb-breakout.launching {
796
- color: var(--accent); border-color: var(--accent);
797
- background: linear-gradient(100deg, transparent 30%, color-mix(in srgb, var(--accent) 35%, transparent) 50%, transparent 70%);
798
- background-size: 250% 100%;
799
- animation: bulb-shimmer 2.2s linear infinite;
800
- }
801
- @media (prefers-reduced-motion: reduce) {
802
- .md .bulb-breakout.launching { animation: none; background: color-mix(in srgb, var(--accent) 18%, transparent); }
803
- }
857
+ /* Breakout in flight: shimmers through the ~2s write+spawn wait so the click reads as "working"
858
+ rather than dead — same treatment as the launcher's play button (.bulb-launch.launching).
859
+ :disabled already keeps it visible if the pointer leaves. */
860
+ .md .bulb-breakout.launching { color: var(--accent); border-color: var(--accent); }
804
861
  /* Copy pill "copied" flash — mirrors the message .copy.done accent cue, scoped to the
805
862
  embed control so it doesn't pick up the bubble-hover reveal. */
806
863
  .md .bulb-copy.done { color: var(--accent); border-color: var(--accent); }