typebulb 0.55.1 → 0.55.3

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.
@@ -337,6 +337,81 @@ html:not([data-theme="dark"]) .turn-view > .shimmer-text {
337
337
  rides a whole buffering window). */
338
338
  .pill-busy { border-color: var(--accent); --shimmer-base: var(--panel); --shimmer-mix: 25%; }
339
339
 
340
+ /* ── Tooltip — the styled stand-in for native `title`, and only on FIXED EXPLANATORY copy: text
341
+ that teaches what a control does. Dynamic identifiers (a path, a model id, an mcp tool's full
342
+ name) keep the native attribute, which is better at them: they run long, they want to wrap, and
343
+ `title` is the reliable "show me the whole string" affordance a bounded box is worse at. One
344
+ attribute opts a control in, no wrapper component and no JS, and it shows on :focus-visible,
345
+ which native `title` never does for a keyboard user. `data-tip` is not an accessible name, so a
346
+ glyph-only control still needs its own ariaLabel.
347
+ Three non-obvious bits. :where() zeroes the position rule's specificity, so a control that is
348
+ ALREADY positioned (the filter's inset × and 🔬) keeps its own and is its own containing block
349
+ anyway. display:none at rest rather than opacity:0, because an absolutely positioned box still
350
+ counts toward a scroll container's scrollable overflow and .messages' stuck-to-bottom test is
351
+ measured in pixels. The open delay therefore rides an animation (fill-mode both holds the
352
+ from-state through the delay), since a display switch cannot transition. ── */
353
+ :where([data-tip]) { position: relative; }
354
+ [data-tip]::after {
355
+ content: attr(data-tip);
356
+ display: none;
357
+ position: absolute;
358
+ z-index: 40;
359
+ top: calc(100% + 7px);
360
+ left: 50%;
361
+ transform: translateX(-50%);
362
+ width: max-content;
363
+ max-width: min(22rem, 60vw);
364
+ padding: .3rem .55rem;
365
+ background: var(--panel);
366
+ color: var(--fg);
367
+ border: 1px solid var(--border);
368
+ border-radius: 8px;
369
+ box-shadow: 0 8px 30px rgba(0, 0, 0, .45);
370
+ /* Its own typography, never the host's (a stub label is italic, a pill is not) — but the same
371
+ SIZE as the chrome it explains, .8rem, which is both the statusbar's and the tab row's. A tip
372
+ set larger than the control it belongs to reads as shouting. Full --fg carries it instead. */
373
+ font-size: .8rem;
374
+ font-style: normal;
375
+ font-weight: 400;
376
+ line-height: 1.4;
377
+ /* Centred and balanced, because the box CANNOT hug wrapped text: `width: max-content` capped by
378
+ max-width means any tip too long for one line sits at exactly max-width, whatever it wraps to.
379
+ Left-aligned, the leftover on the last line then reads as dead space (the model pill's tip, the
380
+ only one long enough to wrap). Balancing evens the lines and centring splits what's left to both
381
+ sides, so a two-line tip reads as a block instead of a full line plus a stub. */
382
+ text-align: center;
383
+ text-wrap: balance;
384
+ /* pre-line, not normal: a tip built from two lines (the session pill's cwd over its id) keeps the
385
+ break native `title` gave it, and everything else still wraps at the max-width. */
386
+ white-space: pre-line;
387
+ pointer-events: none;
388
+ }
389
+ [data-tip]:hover::after,
390
+ [data-tip]:focus-visible::after { display: block; animation: tip-in .12s ease .4s both; }
391
+ @keyframes tip-in { from { opacity: 0; } to { opacity: 1; } }
392
+ /* One tip nested in another (the diff pill's × sits inside the pill body): hovering the inner one
393
+ hovers the outer too, so both boxes would open. Only the innermost shows, which is how native
394
+ `title` resolves it. */
395
+ [data-tip]:has([data-tip]:hover)::after { display: none; }
396
+ /* Pressed: tooltips are dismissed until the pointer reaches a different one (ui.ts
397
+ armTooltipDismiss). Out-specifies the :hover rule on its own, no !important needed. */
398
+ body.tips-off [data-tip]::after { display: none; }
399
+ /* Bottom-anchored surfaces open upward, like their own menus: under the statusbar and under the
400
+ composer's action row there is no room to drop into. */
401
+ :is(.statusbar, .composer) [data-tip]::after { top: auto; bottom: calc(100% + 7px); }
402
+ /* ...and grow LEFT from the control's right edge, which is the "end" alignment the statusbar menus
403
+ already take by default (.pop-center / .pop-start above are their opt-outs). Every one of these
404
+ clusters is right-aligned, so a centred box runs past the viewport — the bulbs pill's tip was
405
+ clipped — and inside a menu it is also the direction that stays within its overflow:hidden for a
406
+ control inset at the right edge. The transcript's own tips stay centred: they sit mid-column. */
407
+ :is(.statusbar, .composer, .picker, .servers-pop) [data-tip]::after { left: auto; right: 0; transform: none; }
408
+ /* ...except where the pill's WRAP already names an alignment for its menu. A tip and the menu it
409
+ belongs to must open the same way, so both read off the same tag (.pop-center / .pop-start above)
410
+ and neither can be set on its own. Direct child, like the menu rules: a control nested inside a
411
+ pill (the diff pill's ×) keeps the cluster's end alignment, which is right at a pill's far edge. */
412
+ .pop-center > [data-tip]::after { right: auto; left: 50%; transform: translateX(-50%); }
413
+ .pop-start > [data-tip]::after { right: auto; left: 0; transform: none; }
414
+
340
415
  /* ── The composer (TB-Agent-Composer.md): the prompt panel under the transcript. In flow (not
341
416
  overlaid like the statusbar) — it owns real height and must never cover content. ── */
342
417
  .composer { position: relative; flex: none; background: var(--bg); padding-inline: 1.75rem; }