typebulb 0.59.7 → 0.60.1

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.
@@ -181,13 +181,13 @@ body {
181
181
  session list). Wrap = positioning context; the popover anchors bottom/right and
182
182
  grows up-and-left — the chips sit in the right-aligned cluster, so left:0 would
183
183
  run them off the viewport's right edge. */
184
- .token-wrap, .sid-wrap, .servers-wrap, .model-wrap, .gitdiff-wrap { position: relative; display: inline-block; }
184
+ .token-wrap, .sid-wrap, .servers-wrap, .model-wrap, .gitdiff-wrap, .children-wrap { position: relative; display: inline-block; }
185
185
  /* Each pill renders its wrap unconditionally, emitting an empty div when it has
186
186
  nothing to show (no tokens / no session / nothing running). An empty wrap is
187
187
  still a flex child, so .statusbar-actions' gap lands on both its sides —
188
188
  doubling the visible gap between its neighbours. Collapse the empties so the
189
189
  cluster spaces evenly regardless of which pills are present. */
190
- .token-wrap:empty, .sid-wrap:empty, .servers-wrap:empty, .model-wrap:empty, .gitdiff-wrap:empty { display: none; }
190
+ .token-wrap:empty, .sid-wrap:empty, .servers-wrap:empty, .model-wrap:empty, .gitdiff-wrap:empty, .children-wrap:empty { display: none; }
191
191
  .picker, .servers-pop {
192
192
  --menu-h: 428px; /* one height for both statusbar menus — they sit side by side, so a
193
193
  mismatched cap reads as a glitch; .picker grows to it, .servers-pop
@@ -644,9 +644,11 @@ body.tips-off [data-tip]::after { display: none; }
644
644
  .picker-row:hover { background: color-mix(in srgb, var(--accent) 10%, transparent); }
645
645
  /* Keyboard cursor (arrow keys / hover) — same borderless tint as the launcher's .server-row.active. */
646
646
  .picker-row.active { background: color-mix(in srgb, var(--accent) 16%, transparent); }
647
- /* The attached session — a leading accent dot in its own gutter slot, reserved (transparent) on
648
- every row so titles align whether or not it's shown, and filled only on the attached row so it
649
- reads as "you are here" even when the cursor has moved off. */
647
+ /* One dot in a reserved gutter slot, transparent on every row so titles align whether or not it
648
+ shows. Green = the harness reports this session mid-turn (the live-app axis, the launcher's
649
+ running-:port green); accent = "you are here", and it wins, because the row you are reading is
650
+ self-evidently working and a second mark at 7px would say nothing new. Deliberately NOT process
651
+ liveness, which is true of every window left open — see TB-Agent-Mirror-Ready.md. */
650
652
  .picker-dot {
651
653
  flex: none;
652
654
  align-self: center;
@@ -655,6 +657,8 @@ body.tips-off [data-tip]::after { display: none; }
655
657
  border-radius: 50%;
656
658
  background: transparent;
657
659
  }
660
+ .picker-dot.working { background: var(--fold-marker); }
661
+ /* Higher specificity than .working on purpose: attachment outranks the turn state. */
658
662
  .picker-row.current .picker-dot { background: var(--accent); }
659
663
  .picker-preview {
660
664
  flex: 1; min-width: 0;
@@ -670,6 +674,12 @@ body.tips-off [data-tip]::after { display: none; }
670
674
  (--fold-marker) when the session changed since the mirror opened and hasn't been viewed. */
671
675
  .picker-time.unseen { color: var(--fold-marker); }
672
676
  .picker-hits { color: var(--muted); font-size: .72rem; white-space: nowrap; }
677
+ /* A full-text hit inside a session's child transcript (TB-Agent-Children.md): indented under its
678
+ parent's row and muted, at the SAME size — the row's distinguishing content is the agent's
679
+ description, so shrinking it is the last thing that should happen to it. */
680
+ .picker-row.child { padding-left: 1.9rem; }
681
+ .picker-row.child .picker-preview { color: var(--muted); }
682
+ .picker-child-kind { flex: none; max-width: 7rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); opacity: .8; }
673
683
  /* Full-text result context: first matching turn, indented past the dot gutter into the title column. */
674
684
  .picker-snippet {
675
685
  color: var(--muted);
@@ -947,21 +957,20 @@ a.server-port:hover { text-decoration: underline; }
947
957
  }
948
958
  .bulb-filter::placeholder { color: var(--muted); }
949
959
  .bulb-filter:focus { outline: none; border-color: var(--accent); }
960
+ /* The inset controls (clear ×, then the mode toggle) flow in one cluster flush to the input's
961
+ top/right/bottom, inside its 1px border — so neither carries an offset for the other's width. */
962
+ .bulb-filter-inset { position: absolute; right: 1px; top: 1px; bottom: 1px; display: flex; align-items: stretch; }
950
963
  /* Clear (×) — only rendered when the filter is non-empty; wipes it and refocuses the box. */
951
964
  .bulb-filter-clear {
952
- position: absolute; right: .3rem; top: 50%; transform: translateY(-50%);
953
965
  appearance: none; border: none; background: transparent; cursor: pointer;
954
966
  color: var(--muted); font-size: 1.05rem; line-height: 1; padding: 0 .3rem; border-radius: 4px;
955
967
  }
956
968
  .bulb-filter-clear:hover { color: var(--accent); }
957
- /* Filter-mode toggle (full-text search) — inset at the input's right edge; the clear × shifts
958
- inward beside it. Both comboboxes render it (SearchComboboxPill's modeToggle). */
969
+ /* Filter-mode toggle (full-text search) — the cluster's right end. Both comboboxes render it
970
+ (ComboboxPill's modeToggle). */
959
971
  .bulb-filter-control.has-trailing .bulb-filter { padding-right: 3.8rem; }
960
- .bulb-filter-control.has-trailing .bulb-filter-clear { right: 2.5rem; }
961
972
  .bulb-filter-mode {
962
- /* Stretches flush to the input's top/right/bottom, inside its 1px border; the right corners
963
- match the input's 6px radius so the latched fill reads as part of the box. */
964
- position: absolute; right: 1px; top: 1px; bottom: 1px;
973
+ /* The right corners match the input's 6px radius so the latched fill reads as part of the box. */
965
974
  appearance: none; border: none; background: transparent; cursor: pointer;
966
975
  font-size: .8rem; line-height: 1; padding: 0 .45rem;
967
976
  border-radius: 0 5px 5px 0;
@@ -1164,10 +1173,13 @@ a.server-port:hover { text-decoration: underline; }
1164
1173
  .gitdiff-pill .icon { width: 8px; height: 12px; }
1165
1174
  .gitdiff-count { font-size: var(--pill-font); font-variant-numeric: tabular-nums; }
1166
1175
  .gitdiff-pop { width: min(480px, calc(100vw - 2rem)); }
1167
- .gitdiff-list { display: flex; flex-direction: column; gap: .15rem; flex: 1; min-height: 0; overflow-y: scroll; padding-right: 4px; }
1168
- .gitdiff-row { display: flex; align-items: center; gap: .55rem; flex: none; min-height: var(--menu-row-h); padding: .05rem .45rem; border-radius: 6px; font-size: .85rem; cursor: pointer; }
1169
- .gitdiff-row:hover { background: color-mix(in srgb, var(--accent) 10%, transparent); }
1170
- .gitdiff-row.active { background: color-mix(in srgb, var(--accent) 16%, transparent); }
1176
+ /* The single-line menu-row geometry both swap-pills use (the session picker's rows are a column —
1177
+ they grow for a search snippet — so they keep their own). Merged selectors, the convention the
1178
+ .*-wrap rules above already follow: a third pill adds its name here rather than a fourth copy. */
1179
+ .gitdiff-list, .children-list { display: flex; flex-direction: column; gap: .15rem; flex: 1; min-height: 0; overflow-y: scroll; padding-right: 4px; }
1180
+ .gitdiff-row, .children-row { display: flex; align-items: center; gap: .55rem; flex: none; min-height: var(--menu-row-h); padding: .05rem .45rem; border-radius: 6px; font-size: .85rem; cursor: pointer; }
1181
+ .gitdiff-row:hover, .children-row:hover { background: color-mix(in srgb, var(--accent) 10%, transparent); }
1182
+ .gitdiff-row.active, .children-row.active { background: color-mix(in srgb, var(--accent) 16%, transparent); }
1171
1183
  .gitdiff-row.viewing .gitdiff-path { color: var(--accent); }
1172
1184
  /* One status letter, VS Code's palette: modified/renamed amber, added/untracked green, deleted red. */
1173
1185
  .gitdiff-status { flex: none; width: 1rem; text-align: center; font: 600 .74rem ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; color: var(--muted); }
@@ -1189,13 +1201,53 @@ a.server-port:hover { text-decoration: underline; }
1189
1201
  .gitdiff-pill.viewing :is(.gitdiff-status, .gitdiff-doc-path, .gitdiff-counts span) { text-box: trim-both cap alphabetic; }
1190
1202
  .gitdiff-doc-path { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; direction: rtl; text-align: left; font-size: var(--pill-font); color: var(--accent); }
1191
1203
  .gitdiff-doc-path:hover { text-decoration: underline; }
1192
- .gitdiff-close { flex: none; display: inline-flex; padding: 0 .15rem; color: var(--muted); }
1193
- .gitdiff-close .icon { width: 10px; height: 10px; }
1194
- .gitdiff-close:hover { color: var(--accent); }
1204
+ /* The × on a pill holding the transcript's slot (ui.ts closeChip) — one rule, both swap-pills. */
1205
+ .pill-close { flex: none; display: inline-flex; padding: 0 .15rem; color: var(--muted); }
1206
+ .pill-close .icon { width: 10px; height: 10px; }
1207
+ .pill-close:hover { color: var(--accent); }
1195
1208
  /* Doc open ⇒ transcript-scoped pills hide: model switcher and session picker act on a conversation
1196
1209
  that is not on screen (the token pill stays — its shimmer says the live diff may still grow).
1197
1210
  Display only, never unmount — the model pill's superSelect must keep its mount. */
1198
- .statusbar-actions.doc-open :is(.model-wrap, .sid-wrap) { display: none; }
1211
+ .statusbar-actions.doc-open :is(.model-wrap, .sid-wrap, .children-wrap) { display: none; }
1212
+
1213
+ /* ── the agents pill (TB-Agent-Children.md): this session's child transcripts. The git-diff pill's
1214
+ shape throughout — presence is the signal (a session that spawned nothing has no pill), picking a
1215
+ row swaps the transcript for that child's conversation, and the pill then wears its identity so
1216
+ the child view carries no chrome of its own. ── */
1217
+ /* Only the VIEWING form is a glyph pill (it needs the fixed square's width freed); the chip is an
1218
+ ordinary word pill, `🤖${n}🤖`, so it takes .pill's centering recipe untouched. */
1219
+ .pill.glyph.children-pill { width: auto; padding: 0 .45rem; gap: .3rem; }
1220
+ /* The chip is a word pill, so it sits outside `.pill.glyph` twice over: its emoji keep their colour
1221
+ at rest (they read as the agents themselves, not as chrome), and they take none of the glyph
1222
+ padding. `.pill`'s .7rem is measured for letters; an emoji's advance already carries its own side
1223
+ bearings, so that padding lands outside whitespace the glyph brought with it and reads as a gap.
1224
+ The margin then goes on each glyph's INNER side only: the padding already owns the outer gap, so a
1225
+ symmetric one doubles up exactly where it shows. Chip only, since the viewing form is a flex row
1226
+ with a gap of its own. */
1227
+ .children-pill { padding: 0 .3rem; }
1228
+ .children-pill:not(.glyph) .glyph-img:first-child { margin-right: .12rem; }
1229
+ .children-pill:not(.glyph) .glyph-img:last-child { margin-left: .12rem; }
1230
+ .children-pop { width: min(520px, calc(100vw - 2rem)); }
1231
+ .children-row.viewing .children-label { color: var(--accent); }
1232
+ /* One dot carries the state: still working (the fold-marker green), finished (dim), killed by the
1233
+ user (warn). Green because running is the LIVE-APP axis, the same one the launcher's running
1234
+ :port and play glyph sit on; the blue accent is the source/name axis and would read as "selected"
1235
+ here, which the row already says by other means. */
1236
+ .children-dot { flex: none; align-self: center; width: 7px; height: 7px; border-radius: 50%; background: var(--muted); opacity: .4; }
1237
+ .children-dot.running { background: var(--fold-marker); opacity: 1; }
1238
+ .children-dot.stopped { background: var(--warn); opacity: .85; }
1239
+ /* The agent's type, capped so the common long one (general-purpose) can't crowd out the
1240
+ description — which is the only thing telling eleven siblings apart. Muted by colour, not size. */
1241
+ .children-kind { flex: none; max-width: 7rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); }
1242
+ .children-label { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
1243
+ .children-model { flex: none; color: var(--muted); opacity: .8; }
1244
+ .children-time { flex: none; color: var(--muted); font-size: .72rem; min-width: 2.6rem; text-align: right; }
1245
+ /* The viewing form: the open child's identity in place of the icon+count, with the × that returns.
1246
+ Same seating rule as the diff pill's — .pill.glyph opts out of the text-box trim, so each TEXT
1247
+ flex item takes it back or the words sit low against the icon. */
1248
+ .pill.glyph.children-pill.viewing { max-width: min(18rem, 35vw); gap: .45rem; padding: 0 .35rem 0 .5rem; }
1249
+ .children-pill.viewing .children-doc-label { text-box: trim-both cap alphabetic; }
1250
+ .children-doc-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--pill-font); color: var(--accent); }
1199
1251
 
1200
1252
  /* The diff doc — takes the transcript's slot in .chat (same flex geometry as .messages). Full-bleed
1201
1253
  like a code surface (no --content-max column: diff lines want the width); the body is the scroll
@@ -1303,6 +1355,27 @@ html[data-theme="dark"] .masthead-logo.dark { display: block; }
1303
1355
  border-radius: 12px;
1304
1356
  padding: .7rem .9rem;
1305
1357
  }
1358
+ /* A user bubble is a panel, so its hover copy pill clears the border instead of sitting on it.
1359
+ .5rem matches the controls' own `bottom`, so the pill stands equally clear on both axes.
1360
+ Inherited by the agent variant below, and by any later bubble that grows a surface. */
1361
+ .bubble.user { --pill-inset: .5rem; }
1362
+ /* A sub-agent's hand-back (TB-Agent-Children.md): a user turn the harness delivered on an agent's
1363
+ behalf, never one the user typed, so it wears its own outline instead of the plain send's. The
1364
+ accent is the source/name axis (the same one the agents pill's open-child label sits on); green
1365
+ belongs to the live axis and would read as "still running", which a report never is. */
1366
+ .bubble.user.agent {
1367
+ border-color: color-mix(in srgb, var(--accent) 40%, var(--border));
1368
+ background: color-mix(in srgb, var(--accent) 7%, transparent);
1369
+ }
1370
+ /* The frame's header: the agents pill's glyph, the agent's type, then its description, which is the
1371
+ link into its transcript. Muted by colour and NOT by size — the description is the one thing that
1372
+ tells eleven siblings apart, so it stays at the body's size (the picker's child-row rule). */
1373
+ .agent-head { display: flex; align-items: baseline; gap: .4rem; margin-bottom: .55rem; }
1374
+ .agent-head .glyph-img { flex: none; align-self: center; }
1375
+ .agent-kind { flex: none; color: var(--muted); }
1376
+ .agent-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1377
+ a.agent-name { color: var(--accent); cursor: pointer; }
1378
+ a.agent-name:hover { text-decoration: underline; }
1306
1379
  /* Consecutive tool-only bubbles sit tight via negative margin (flex `gap`
1307
1380
  can't be overridden per sibling pair). The adjacent-sibling selector leaves
1308
1381
  the first tools-only bubble's gap intact, preserving the turn boundary. */
@@ -1480,17 +1553,20 @@ html[data-theme="dark"] .masthead-logo.dark { display: block; }
1480
1553
  .md .svg-embed { justify-content: center; }
1481
1554
  .md .svg-embed svg { max-width: 100%; height: auto; }
1482
1555
 
1483
- /* Markdown tables: chop in-column (the `.embed` rule below) and `safe center` — a table that fits its
1484
- box centres, and one that overflows falls back to left so it scrolls from its first column rather than
1485
- clipping. Centring holds in both modes: a small in-column table sits centred in the prose column, a
1486
- spread one centred in the lane. The scroll lives on the inner `.table-scroll`, not the `.embed`
1556
+ /* Markdown tables: chop in-column (the `.embed` rule below). An IN-COLUMN table starts where the prose
1557
+ starts — it is part of the paragraph flow, and a narrow one centred in the column reads as shifted
1558
+ right against the left-aligned text above and below it, which is the whole of that bug. Only a
1559
+ SPREAD table centres, and it has earned it: spreading means it is already wider than the column, so
1560
+ centring keeps it symmetric about the column axis exactly as the mermaid lane does. `safe` on both
1561
+ means one that overflows its box falls back to the start and scrolls from its first column rather
1562
+ than clipping. The scroll lives on the inner `.table-scroll`, not the `.embed`
1487
1563
  wrapper, so a horizontal scrollbar rides above the chop's bottom padding instead of flush against the
1488
1564
  next line (a scrollbar sits at its own container's bottom edge). It's the centred flex child, capped at
1489
1565
  the box so it shrinks-to-fit and scrolls only what overflows; `max-width:100%` + the overflow-induced
1490
1566
  0 min-width let it shrink below the table's natural width. `spread` widens the wrapper to the lane (the
1491
1567
  mermaid geometry) for a table fitTableEmbeds found too wide for the column. The opaque `.embed`
1492
1568
  padding/z-index still occludes the turn stripe the spread crosses — no box-shadow needed. */
1493
- .md .table-embed { justify-content: safe center; }
1569
+ .md .table-embed { justify-content: safe flex-start; }
1494
1570
  .md .table-embed .table-scroll { overflow-x: auto; max-width: 100%; }
1495
1571
  /* max-content keeps the table at its natural (unwrapped) width so it overflows `.table-scroll` and earns
1496
1572
  a scrollbar, rather than wrapping its cells to fit — the bar is the point, and it now rides inside the
@@ -1511,15 +1587,24 @@ html[data-theme="dark"] .masthead-logo.dark { display: block; }
1511
1587
  shattering text; a pathological no-space token (a long URL) then scrolls rather than breaking. */
1512
1588
  .md .table-embed table { margin: 0; width: max-content; max-width: var(--lane-w); }
1513
1589
  .md .table-embed th, .md .table-embed td { max-width: 40ch; word-break: normal; }
1590
+ /* The spread band's comfort goes on the RIGHT only, which looks asymmetric written down and is what
1591
+ reads as even on screen. `.messages` reserves a scrollbar gutter at BOTH edges (to hold the column
1592
+ on the window axis), but only the right one ever contains a scrollbar: the left gutter is dead
1593
+ space that already reads as margin, while on the right the lane's own 1rem inset is all there is
1594
+ between the table and the bar. Inset both sides equally and the left gap measures a gutter wider
1595
+ than the right; inset neither and the table sits flush against the bar, which is the bug. So the
1596
+ band adds back one inset on the right, and the table's cap comes down by the same 1rem or it would
1597
+ overflow the band's content box by exactly that at every width — the permanent scrollbar the
1598
+ zeroed inset was avoiding. */
1514
1599
  .md .table-embed.spread {
1600
+ justify-content: safe center;
1515
1601
  width: var(--lane-w);
1516
1602
  margin-left: var(--lane-ml);
1517
1603
  margin-right: auto;
1518
- /* Zero the `.embed` inset: spread makes the border box the lane, so a 1rem left padding would
1519
- leave the scroller 1rem narrower than the table's own lane cap — a capped table then overflows
1520
- by exactly that inset at every window width, which is the permanent 16px-of-travel scrollbar. */
1521
1604
  padding-left: 0;
1605
+ padding-right: 1rem;
1522
1606
  }
1607
+ .md .table-embed.spread table { max-width: calc(var(--lane-w) - 1rem); }
1523
1608
 
1524
1609
  /* ````bulb```` inline bulbs: a sandboxed nested app. createBulbFrame owns the iframe (auto-height,
1525
1610
  borderless). No frame of our own — like the mermaid/svg embeds, it sits in the flow rather
@@ -1595,7 +1680,12 @@ html[data-theme="dark"] .masthead-logo.dark { display: block; }
1595
1680
  .overlay-pill {
1596
1681
  position: absolute;
1597
1682
  bottom: .5rem;
1598
- right: 0; /* flush with the content column's right edge (prose / statusbar pills) */
1683
+ /* Flush with the content column's right edge (prose / statusbar pills) when the pill sits on the
1684
+ page's own background. A PANEL sets --pill-inset instead, so the pill clears its edge rather
1685
+ than landing on the border: `right: 0` resolves against the containing block's padding box, so
1686
+ inside a bordered box it means "touching the border". Only this axis needed it — `bottom` has
1687
+ always stood the pill off, which is why the defect read as a right-edge one. */
1688
+ right: var(--pill-inset, 0px);
1599
1689
  font: inherit;
1600
1690
  font-size: var(--pill-font);
1601
1691
  padding: 0 .45rem;
@@ -1614,6 +1704,10 @@ html[data-theme="dark"] .masthead-logo.dark { display: block; }
1614
1704
  the chop/breakout; the code-block wrapper needs it for the absolute pill. The pill is positioned
1615
1705
  (absolute), so it paints over the static fence content regardless of DOM order. */
1616
1706
  .md .copyable { position: relative; }
1707
+ /* A code fence IS a panel (the <pre> carries the border and background, the wrapper is the
1708
+ positioning context), so its copy pill takes the inset. The svg and mermaid embeds are
1709
+ transparent bands on the page background and keep the flush edge. */
1710
+ .md .code-block { --pill-inset: .5rem; }
1617
1711
  .md .copyable:hover .copy-src,
1618
1712
  .md .copy-src:focus-visible { opacity: 1; }
1619
1713
  .md .copy-src.done { color: var(--accent); border-color: var(--accent); }
@@ -1974,7 +2068,7 @@ html[data-theme="dark"] .masthead-logo.dark { display: block; }
1974
2068
  .bubble-controls {
1975
2069
  position: absolute;
1976
2070
  bottom: .5rem;
1977
- right: 0; /* flush with the content column's right edge (prose / statusbar pills) */
2071
+ right: var(--pill-inset, 0px);
1978
2072
  display: flex;
1979
2073
  gap: .4rem;
1980
2074
  }