typebulb 0.14.5 → 0.14.7

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