@gperezmz/bb-plugin-thread-glance 0.3.0 → 0.4.0

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.
@@ -3,7 +3,7 @@
3
3
  "sdkVersion": "0.5.9",
4
4
  "artifactFormatVersion": 1,
5
5
  "pluginId": "thread-glance",
6
- "pluginVersion": "0.3.0",
6
+ "pluginVersion": "0.4.0",
7
7
  "builtWith": {
8
8
  "bbVersion": "0.43.4",
9
9
  "pluginSdkVersion": "0.5.9"
@@ -19,12 +19,12 @@ import { chipLabel, rowAriaLabel } from "../model/labels";
19
19
  import { rowIndent } from "../model/layout";
20
20
  import { rowMenuItems } from "../model/menu";
21
21
  import { noteText } from "../model/notes";
22
- import { pluginStatusWins } from "../model/state";
23
- import { trailingTime } from "../model/time";
22
+ import { chipTone, pluginStatusWins } from "../model/state";
23
+ import { TRAILING_SLOT_SIZERS, trailingTime } from "../model/time";
24
24
  import type { ThreadRow } from "../model/view";
25
25
  import type { DraggedThread } from "../model/drag";
26
26
  import type { RowController } from "./controller";
27
- import { ChildDot, GlyphIcon, NoteLine, PluginStatusGlyph } from "./glyphs";
27
+ import { ChipStateGlyph, GlyphIcon, NoteLine, PluginStatusGlyph, TONE_CLASS } from "./glyphs";
28
28
  import { ProviderBadge } from "./ProviderBadge";
29
29
  import { PullRequestBadge } from "./PullRequestBadge";
30
30
  import { RenameEditor } from "./RenameEditor";
@@ -299,8 +299,12 @@ export const ThreadRowView = memo(function ThreadRowView({
299
299
  const twoLines = note !== null || row.branchLine !== null;
300
300
  const dimmed = row.dimmed && !editing;
301
301
  const menuShowing = menuOpen || contextOpen;
302
- // Desktop: the actions cross-fade over the harness and age, as bb's
303
- // trailing slot does. Compact: "…" always shows beside them.
302
+ // A pressed shortcut's pill takes the machine's place, as it takes the time's.
303
+ const showMachine = row.machine !== null && shortcut === null;
304
+ const badgesShown = row.harness || showMachine;
305
+ const actionsShown = !compact && shortcut === null;
306
+ // Desktop: on hover the harness and machine fade out for Mark read and
307
+ // Archive, and the time for "…". Compact: nothing fades.
304
308
  const fadeClass = compact
305
309
  ? ""
306
310
  : menuShowing
@@ -364,7 +368,6 @@ export const ThreadRowView = memo(function ThreadRowView({
364
368
  />
365
369
  <span className="pointer-events-none relative flex size-4 shrink-0 items-center justify-center">
366
370
  {stateSlot}
367
- {row.childDot !== null ? <ChildDot flag={row.childDot} /> : null}
368
371
  </span>
369
372
  {row.nested ? (
370
373
  // Tight against the title, and over the row's gap, so it adds 8px.
@@ -440,141 +443,169 @@ export const ThreadRowView = memo(function ThreadRowView({
440
443
  <PullRequestBadge threadId={thread.id} />
441
444
  </span>
442
445
  ) : null}
443
- {chip !== null && !editing ? (
444
- <button
445
- type="button"
446
- aria-expanded={chip.expanded}
447
- aria-label={chipLabel(thread.displayTitle, chip.count, chip.expanded)}
448
- title={chipLabel(thread.displayTitle, chip.count, chip.expanded)}
449
- onClick={(event) => {
450
- event.preventDefault();
451
- event.stopPropagation();
452
- controller.onToggleChip(row);
453
- }}
454
- onPointerDown={(event) => event.stopPropagation()}
455
- onKeyDown={(event) => event.stopPropagation()}
456
- className={cn(
457
- "pointer-events-auto relative z-10 inline-flex h-5 shrink-0 items-center gap-0.5 rounded-md px-0.5 text-[11px] leading-none tabular-nums text-muted-foreground outline-none hover:text-foreground focus-visible:ring-2 focus-visible:ring-sidebar-ring",
458
- dimmed && QUIET_TEXT,
459
- )}
460
- >
461
- {chip.count}
462
- <Icon
463
- name={ICONS.expand}
464
- aria-hidden
465
- className={cn("size-3 transition-transform duration-150", chip.expanded && "rotate-90")}
466
- />
467
- </button>
468
- ) : null}
469
- {!editing && row.harness ? (
470
- <span
471
- className={cn(
472
- "pointer-events-none relative inline-flex shrink-0 transition-opacity",
473
- // The hover actions take this slot too, as bb's trailing slot does.
474
- shortcut === null && fadeClass,
475
- )}
476
- >
477
- <ProviderBadge display={provider} mode={controller.harnessIcon} />
478
- </span>
479
- ) : null}
480
- {!editing ? (
481
- <span className="relative flex h-6 min-w-8 shrink-0 items-center justify-end gap-1">
482
- {shortcut !== null ? (
483
- <kbd aria-hidden className="pointer-events-none rounded border border-border px-1 font-sans text-[10px] leading-4 text-muted-foreground">
484
- {shortcut.label}
485
- </kbd>
486
- ) : (
487
- <>
488
- {row.machine !== null ? (
446
+ {!editing && (badgesShown || actionsShown) ? (
447
+ // One cell: the harness and machine at rest, Mark read and Archive
448
+ // on hover. The actions take no width at rest, so no title is
449
+ // shorter for them; on hover the title gives up only what they
450
+ // need beyond the badges they replace.
451
+ <span className="relative -ml-1.5 grid shrink-0 items-center">
452
+ {badgesShown ? (
453
+ <span className={cn("flex items-center gap-1.5 pl-1.5 [grid-area:1/1] transition-opacity", shortcut === null && fadeClass)}>
454
+ {row.harness ? (
455
+ <span className="pointer-events-none relative inline-flex shrink-0">
456
+ <ProviderBadge display={provider} mode={controller.harnessIcon} />
457
+ </span>
458
+ ) : null}
459
+ {showMachine ? (
489
460
  <span
490
461
  title={`On ${row.machine}`}
491
462
  aria-label={`On ${row.machine}`}
492
- className={cn("pointer-events-none max-w-20 truncate text-[11px] text-muted-foreground transition-opacity", fadeClass)}
463
+ className="pointer-events-none max-w-20 truncate text-[11px] text-muted-foreground"
493
464
  >
494
465
  {row.machine}
495
466
  </span>
496
467
  ) : null}
497
- {time !== null ? (
498
- <span
499
- title={time.label}
500
- aria-label={time.label}
501
- className={cn(
502
- "pointer-events-none text-[11px] tabular-nums transition-opacity",
503
- // A running timer reads as work, an age as history.
504
- time.kind === "timer" ? "font-medium text-[var(--timeline-accent)]" : "text-muted-foreground",
505
- fadeClass,
506
- )}
468
+ </span>
469
+ ) : null}
470
+ {actionsShown ? (
471
+ <span
472
+ className={cn(
473
+ "items-center justify-self-end gap-0.5 pl-1.5 [grid-area:1/1]",
474
+ menuShowing ? "flex" : "hidden group-hover/row:flex group-focus-within/row:flex",
475
+ )}
476
+ >
477
+ {row.treeUnread ? (
478
+ <button
479
+ type="button"
480
+ aria-label="Mark read"
481
+ title="Mark read"
482
+ className={ROW_ICON_BUTTON}
483
+ onPointerDown={(event) => event.stopPropagation()}
484
+ onClick={(event) => {
485
+ event.preventDefault();
486
+ event.stopPropagation();
487
+ controller.onMenuAction("mark-read", thread);
488
+ }}
507
489
  >
508
- {time.text}
509
- </span>
490
+ <Icon name={ICONS.markRead} aria-hidden className="size-4" />
491
+ </button>
510
492
  ) : null}
511
- <span
512
- className={cn(
513
- "flex items-center gap-0.5 transition-opacity",
514
- compact
515
- ? "relative"
516
- : menuShowing
517
- ? "absolute right-0 opacity-100"
518
- : "absolute right-0 opacity-0 group-hover/row:opacity-100 group-focus-within/row:opacity-100",
519
- )}
493
+ <button
494
+ type="button"
495
+ aria-label={archived ? "Unarchive thread" : "Archive thread"}
496
+ title={archived ? "Unarchive" : "Archive"}
497
+ className={ROW_ICON_BUTTON}
498
+ onPointerDown={(event) => event.stopPropagation()}
499
+ onClick={(event) => {
500
+ event.preventDefault();
501
+ event.stopPropagation();
502
+ controller.onMenuAction(archived ? "unarchive" : "archive", thread);
503
+ }}
520
504
  >
521
- {!compact && row.treeUnread ? (
522
- <button
523
- type="button"
524
- aria-label="Mark read"
525
- title="Mark read"
526
- className={ROW_ICON_BUTTON}
527
- onPointerDown={(event) => event.stopPropagation()}
528
- onClick={(event) => {
529
- event.preventDefault();
530
- event.stopPropagation();
531
- controller.onMenuAction("mark-read", thread);
532
- }}
533
- >
534
- <Icon name={ICONS.markRead} aria-hidden className="size-4" />
535
- </button>
536
- ) : null}
537
- {!compact ? (
538
- <button
539
- type="button"
540
- aria-label={archived ? "Unarchive thread" : "Archive thread"}
541
- title={archived ? "Unarchive" : "Archive"}
542
- className={ROW_ICON_BUTTON}
543
- onPointerDown={(event) => event.stopPropagation()}
544
- onClick={(event) => {
545
- event.preventDefault();
546
- event.stopPropagation();
547
- controller.onMenuAction(archived ? "unarchive" : "archive", thread);
548
- }}
505
+ <Icon name={archived ? ICONS.unarchive : ICONS.archive} aria-hidden className="size-4" />
506
+ </button>
507
+ </span>
508
+ ) : null}
509
+ </span>
510
+ ) : null}
511
+ {!editing ? (
512
+ // The chip sits 4px before the trailing slot, whose width never
513
+ // changes, so hover never moves it.
514
+ <span className="relative flex shrink-0 items-center gap-1">
515
+ {chip !== null ? (
516
+ <button
517
+ type="button"
518
+ aria-expanded={chip.expanded}
519
+ aria-label={chipLabel(thread.displayTitle, chip)}
520
+ title={chipLabel(thread.displayTitle, chip)}
521
+ onClick={(event) => {
522
+ event.preventDefault();
523
+ event.stopPropagation();
524
+ controller.onToggleChip(row);
525
+ }}
526
+ onPointerDown={(event) => event.stopPropagation()}
527
+ onKeyDown={(event) => event.stopPropagation()}
528
+ className={cn(
529
+ "pointer-events-auto relative z-10 inline-flex h-5 shrink-0 items-center gap-0.5 rounded-md px-0.5 text-[11px] leading-none tabular-nums outline-none focus-visible:ring-2 focus-visible:ring-sidebar-ring",
530
+ chip.flag === null
531
+ ? cn("text-muted-foreground hover:text-foreground", dimmed && QUIET_TEXT)
532
+ : TONE_CLASS[chipTone(chip.flag)],
533
+ )}
534
+ >
535
+ {chip.flag !== null ? <ChipStateGlyph flag={chip.flag} /> : null}
536
+ {chip.count > 0 ? chip.count : null}
537
+ <Icon
538
+ name={ICONS.expand}
539
+ aria-hidden
540
+ className={cn("size-3 transition-transform duration-150", chip.expanded && "rotate-90")}
541
+ />
542
+ </button>
543
+ ) : null}
544
+ {/* The trailing slot: one grid cell, as wide as "…" or the widest
545
+ time, whichever is wider, holding the time at rest and "…" on hover. */}
546
+ <span data-trailing-slot="" className="relative grid h-6 shrink-0 items-center">
547
+ <span aria-hidden className="invisible w-6 [grid-area:1/1]" />
548
+ {TRAILING_SLOT_SIZERS.map((text) => (
549
+ <span key={text} aria-hidden className="invisible text-[11px] font-medium tabular-nums [grid-area:1/1]">
550
+ {text}
551
+ </span>
552
+ ))}
553
+ {shortcut !== null ? (
554
+ <kbd aria-hidden className="pointer-events-none justify-self-end rounded border border-border px-1 font-sans text-[10px] leading-4 text-muted-foreground [grid-area:1/1]">
555
+ {shortcut.label}
556
+ </kbd>
557
+ ) : (
558
+ <>
559
+ {time !== null ? (
560
+ <span
561
+ title={time.label}
562
+ aria-label={time.label}
563
+ className={cn(
564
+ "pointer-events-none justify-self-end text-[11px] tabular-nums transition-opacity [grid-area:1/1]",
565
+ // A running timer reads as work, an age as history.
566
+ time.kind === "timer" ? "font-medium text-[var(--timeline-accent)]" : "text-muted-foreground",
567
+ fadeClass,
568
+ )}
549
569
  >
550
- <Icon name={archived ? ICONS.unarchive : ICONS.archive} aria-hidden className="size-4" />
551
- </button>
570
+ {time.text}
571
+ </span>
552
572
  ) : null}
553
- <DropdownMenu open={menuOpen} onOpenChange={setMenuOpen}>
554
- <DropdownMenuTrigger asChild>
555
- <button
556
- type="button"
557
- aria-label="Thread actions"
558
- // Phones open the menu by long-press, as bb's lists do;
559
- // the button stays for keyboards and screen readers.
560
- className={compact ? "sr-only" : ROW_ICON_BUTTON}
561
- onPointerDown={(event) => event.stopPropagation()}
562
- onClick={(event) => event.stopPropagation()}
563
- >
564
- <Icon name={ICONS.more} aria-hidden className="size-4" />
565
- </button>
566
- </DropdownMenuTrigger>
567
- <RowDropdownMenuContent
568
- items={menuItems}
569
- sections={controller.sections}
570
- currentSectionId={thread.sectionId}
571
- onAction={onMenuAction}
572
- onCloseAutoFocus={onMenuCloseAutoFocus}
573
- />
574
- </DropdownMenu>
575
- </span>
576
- </>
577
- )}
573
+ <span
574
+ className={cn(
575
+ "flex items-center justify-self-end transition-opacity [grid-area:1/1]",
576
+ compact
577
+ ? "relative"
578
+ : menuShowing
579
+ ? "opacity-100"
580
+ : "opacity-0 group-hover/row:opacity-100 group-focus-within/row:opacity-100",
581
+ )}
582
+ >
583
+ <DropdownMenu open={menuOpen} onOpenChange={setMenuOpen}>
584
+ <DropdownMenuTrigger asChild>
585
+ <button
586
+ type="button"
587
+ aria-label="Thread actions"
588
+ // Phones open the menu by long-press, as bb's lists do;
589
+ // the button stays for keyboards and screen readers.
590
+ className={compact ? "sr-only" : ROW_ICON_BUTTON}
591
+ onPointerDown={(event) => event.stopPropagation()}
592
+ onClick={(event) => event.stopPropagation()}
593
+ >
594
+ <Icon name={ICONS.more} aria-hidden className="size-4" />
595
+ </button>
596
+ </DropdownMenuTrigger>
597
+ <RowDropdownMenuContent
598
+ items={menuItems}
599
+ sections={controller.sections}
600
+ currentSectionId={thread.sectionId}
601
+ onAction={onMenuAction}
602
+ onCloseAutoFocus={onMenuCloseAutoFocus}
603
+ />
604
+ </DropdownMenu>
605
+ </span>
606
+ </>
607
+ )}
608
+ </span>
578
609
  </span>
579
610
  ) : null}
580
611
  </div>
@@ -1,11 +1,12 @@
1
- // State glyphs, the child dot and header counters. They draw what the model
2
- // decided; tone and animation come from host token classes only.
1
+ // State glyphs, the children chip's state glyph and header counters. They
2
+ // draw what the model decided; tone and animation come from host token
3
+ // classes only.
3
4
  import { experimental_Icon as Icon } from "@get-bb/plugin-sdk/app";
4
5
  import type { PluginSidebarThreadRowStatus } from "@get-bb/plugin-sdk/app";
5
6
  import { cn } from "@/lib/utils";
6
7
  import type { Counters } from "../model/counters";
7
8
  import type { RowNote } from "../model/notes";
8
- import { dotTone, FLAG_GLYPHS, type DotTone, type Flag, type Glyph, type Tone } from "../model/state";
9
+ import { FLAG_GLYPHS, type Flag, type Glyph, type Tone } from "../model/state";
9
10
 
10
11
  export const TONE_CLASS: Record<Tone, string> = {
11
12
  attention: "text-attention",
@@ -18,32 +19,6 @@ export const TONE_CLASS: Record<Tone, string> = {
18
19
  none: "",
19
20
  };
20
21
 
21
- // The child dot's colours: the tone of the state it stands for, and the
22
- // unread dot's accent for unread.
23
- const DOT_TONE_CLASS: Record<DotTone, string> = {
24
- attention: "bg-attention",
25
- destructive: "bg-destructive",
26
- working: "bg-[var(--timeline-accent)]",
27
- unread: "bg-[var(--timeline-accent)]",
28
- };
29
-
30
- /**
31
- * The child dot on a parent's status glyph, at its lower right corner. A
32
- * ring of the sidebar's colour keeps it apart from the glyph under it.
33
- */
34
- export function ChildDot({ flag }: { flag: Flag }) {
35
- return (
36
- <span
37
- aria-hidden
38
- data-child-dot={flag}
39
- className={cn(
40
- "pointer-events-none absolute -right-0.5 -bottom-0.5 size-[7px] rounded-full ring-[1.5px] ring-sidebar",
41
- DOT_TONE_CLASS[dotTone(flag)],
42
- )}
43
- />
44
- );
45
- }
46
-
47
22
  const SHINE = "animate-shine-icon motion-reduce:animate-none";
48
23
  const SPIN = "animate-spin motion-reduce:animate-none";
49
24
 
@@ -139,6 +114,12 @@ export function FlagGlyph({ flag, className }: { flag: Flag; className?: string
139
114
  return <GlyphIcon glyph={FLAG_GLYPHS[flag]} className={className} />;
140
115
  }
141
116
 
117
+ /** The state glyph that leads a children chip, smaller than the Status column's; the chip's label names it. */
118
+ export function ChipStateGlyph({ flag }: { flag: Flag }) {
119
+ if (FLAG_GLYPHS[flag].icon === "dot") return <UnreadDot className="size-[5px]" />;
120
+ return <FlagGlyph flag={flag} className="size-3" />;
121
+ }
122
+
142
123
  const COUNTER_ITEMS: readonly {
143
124
  key: keyof Counters;
144
125
  flag: Flag;
@@ -1,6 +1,6 @@
1
1
  // Accessible names: everything a tooltip says is in the aria-label.
2
- import type { OlderRow, SettledRow, ThreadRow } from "./view";
3
- import { FLAG_GLYPHS, type Flag } from "./state";
2
+ import type { Chip, OlderRow, SettledRow, ThreadRow } from "./view";
3
+ import type { Flag } from "./state";
4
4
  import { formatDateTime } from "./details";
5
5
  import type { OrganizationMode } from "@/shared/preferences";
6
6
 
@@ -20,7 +20,6 @@ export function rowAriaLabel(
20
20
  ): string {
21
21
  const parts = [stateText(row, options.pluginLabel), options.providerName];
22
22
  if (row.parentTitle !== null && row.depth > 0) parts.push(`child of ${row.parentTitle}`);
23
- if (row.childDot !== null) parts.push(`child threads: ${FLAG_GLYPHS[row.childDot].label}`);
24
23
  if (row.crossGroupLabel !== null) parts.push(row.crossGroupLabel.toLowerCase());
25
24
  if (row.hiddenBadge) parts.push("hidden thread");
26
25
  if (row.info.unread && row.info.state.kind !== "unread") parts.push("unread");
@@ -28,9 +27,24 @@ export function rowAriaLabel(
28
27
  return `Open ${row.info.thread.displayTitle} — ${parts.join("; ")}`;
29
28
  }
30
29
 
31
- export function chipLabel(title: string, count: number, expanded: boolean): string {
32
- const noun = count === 1 ? "child thread" : "child threads";
33
- return `${expanded ? "Collapse" : "Show"} ${count} ${noun} of ${title}`;
30
+ /** What a children chip's state says, uncounted: the state can come from any depth, the count only from direct children. */
31
+ const CHIP_STATE_TEXT: Readonly<Record<Flag, string>> = {
32
+ "waits-on-you": "waiting on you below",
33
+ "unread-failed": "failed below",
34
+ "queue-failed": "queued message failed below",
35
+ offline: "machine offline below",
36
+ working: "working below",
37
+ unread: "unread below",
38
+ };
39
+
40
+ /**
41
+ * "Show 2 child threads of Release, working below"; with no visible child,
42
+ * "Show hidden child threads of Release, waiting on you below".
43
+ */
44
+ export function chipLabel(title: string, chip: Chip): string {
45
+ const children = chip.count === 0 ? "hidden child threads" : `${chip.count} ${chip.count === 1 ? "child thread" : "child threads"}`;
46
+ const state = chip.flag === null ? "" : `, ${CHIP_STATE_TEXT[chip.flag]}`;
47
+ return `${chip.expanded ? "Collapse" : "Show"} ${children} of ${title}${state}`;
34
48
  }
35
49
 
36
50
  /** The text and accessible name of an open tree's fold row. */
@@ -271,17 +271,17 @@ export function hiddenThreadFlags(flags: ReadonlySet<Flag>): Set<Flag> {
271
271
  return kept;
272
272
  }
273
273
 
274
- /** Flags a hidden thread adds to its parent's child dot: what asks for you or failed, and offline. */
275
- const HIDDEN_DOT_FLAGS: ReadonlySet<Flag> = new Set<Flag>(["waits-on-you", "unread-failed", "queue-failed", "offline"]);
274
+ /** Flags a hidden thread adds to the children chip above it: what asks for you or failed, and offline. */
275
+ const HIDDEN_CHIP_FLAGS: ReadonlySet<Flag> = new Set<Flag>(["waits-on-you", "unread-failed", "queue-failed", "offline"]);
276
276
 
277
277
  /**
278
- * The flags a thread adds to the child dot of the threads above it. A
278
+ * The flags a thread adds to the children chip of the threads above it. A
279
279
  * hidden thread adds only waits-on-you, failed, queued message failed and
280
280
  * offline; which trees need attention reads `hiddenThreadFlags` instead,
281
281
  * so this leaves it unchanged.
282
282
  */
283
- export function dotFlagsOf(flags: ReadonlySet<Flag>, isHidden: boolean): Set<Flag> {
284
- return isHidden ? new Set([...flags].filter((flag) => HIDDEN_DOT_FLAGS.has(flag))) : new Set(flags);
283
+ export function chipFlagsOf(flags: ReadonlySet<Flag>, isHidden: boolean): Set<Flag> {
284
+ return isHidden ? new Set([...flags].filter((flag) => HIDDEN_CHIP_FLAGS.has(flag))) : new Set(flags);
285
285
  }
286
286
 
287
287
  /** The most urgent flag, or null. */
@@ -289,22 +289,10 @@ export function mostUrgent(flags: ReadonlySet<Flag>): Flag | null {
289
289
  return FLAG_ORDER.find((flag) => flags.has(flag)) ?? null;
290
290
  }
291
291
 
292
- /** The colour of a child dot, by the most urgent state among the descendants. */
293
- export type DotTone = "attention" | "destructive" | "working" | "unread";
294
-
295
- export function dotTone(flag: Flag): DotTone {
296
- switch (flag) {
297
- case "waits-on-you":
298
- case "offline":
299
- return "attention";
300
- case "unread-failed":
301
- case "queue-failed":
302
- return "destructive";
303
- case "working":
304
- return "working";
305
- case "unread":
306
- return "unread";
307
- }
292
+ /** The tone a children chip takes for a state: its glyph's, and the unread dot's accent for unread. */
293
+ export function chipTone(flag: Flag): Tone {
294
+ const tone = FLAG_GLYPHS[flag].tone;
295
+ return tone === "none" ? "working" : tone;
308
296
  }
309
297
 
310
298
  export const FLAG_GLYPHS: Readonly<Record<Flag, Glyph & { label: string }>> = {
@@ -105,3 +105,10 @@ export function stateSince(
105
105
  if (at === undefined) return null;
106
106
  return formatDuration(now - at);
107
107
  }
108
+
109
+ /**
110
+ * The widest texts `trailingTime` gives up to 99 weeks, one per unit. The
111
+ * trailing slot is as wide as the widest of them, so times line up down the
112
+ * list; an older row widens its own.
113
+ */
114
+ export const TRAILING_SLOT_SIZERS = ["now", "<1m", "59m", "23h", "6d", "99w"] as const;
@@ -3,7 +3,7 @@
3
3
  import type { PluginSidebarThread } from "@get-bb/plugin-sdk/app";
4
4
  import {
5
5
  computeState,
6
- dotFlagsOf,
6
+ chipFlagsOf,
7
7
  hiddenThreadFlags,
8
8
  isQuietThread,
9
9
  isUnread,
@@ -29,8 +29,8 @@ export interface ThreadInfo {
29
29
  unread: boolean;
30
30
  /** Own flags; for hidden threads only waits-on-you and unread-failed. */
31
31
  flags: ReadonlySet<Flag>;
32
- /** What it adds to the child dot of the threads above it (see `dotFlagsOf`). */
33
- dotFlags: ReadonlySet<Flag>;
32
+ /** What it adds to the children chip of the threads above it (see `chipFlagsOf`). */
33
+ chipFlags: ReadonlySet<Flag>;
34
34
  /**
35
35
  * The flags of this thread that make it need attention, for collapsed
36
36
  * groups, the counters, the need-you filter and auto-reveal: a child counts
@@ -55,14 +55,14 @@ export interface ThreadInfo {
55
55
  note: RowNote | null;
56
56
  }
57
57
 
58
- /** What a thread's descendants add up to, for its chip, its child dot and folding. */
58
+ /** What a thread's descendants add up to, for its chip and folding. */
59
59
  export interface Subtree {
60
60
  /** Descendants, depth-first in creation order, hidden ones included. */
61
61
  descendants: ThreadInfo[];
62
62
  /** Union of the descendants' Needs attention flags and working, archived ones left out. */
63
63
  flags: ReadonlySet<Flag>;
64
- /** Union of the descendants' `dotFlags`, archived ones left out: the child dot. */
65
- dotFlags: ReadonlySet<Flag>;
64
+ /** Union of the descendants' `chipFlags`, archived ones left out: the chip's state. */
65
+ chipFlags: ReadonlySet<Flag>;
66
66
  /** Visible descendants at any depth. */
67
67
  visibleCount: number;
68
68
  /** Visible direct children: the chip's number. */
@@ -167,7 +167,7 @@ export function buildForest(inputs: ForestInputs): Forest {
167
167
  state,
168
168
  unread,
169
169
  flags: thread.isHidden ? hiddenThreadFlags(flags) : flags,
170
- dotFlags: dotFlagsOf(flags, thread.isHidden),
170
+ chipFlags: chipFlagsOf(flags, thread.isHidden),
171
171
  attentionFlags: new Set<Flag>(),
172
172
  quiet: isQuietThread(state, unread, isActive),
173
173
  quietIgnoringOpen: false,
@@ -216,7 +216,7 @@ export function buildForest(inputs: ForestInputs): Forest {
216
216
  if (cached !== undefined) return cached;
217
217
  const descendants: ThreadInfo[] = [];
218
218
  const flags = new Set<Flag>();
219
- const dotFlags = new Set<Flag>();
219
+ const chipFlags = new Set<Flag>();
220
220
  let visibleCount = 0;
221
221
  let childCount = 0;
222
222
  let quietIgnoringOpen = true;
@@ -229,10 +229,10 @@ export function buildForest(inputs: ForestInputs): Forest {
229
229
  if (!child.thread.isArchived) {
230
230
  for (const flag of child.attentionFlags) flags.add(flag);
231
231
  if (child.flags.has("working")) flags.add("working");
232
- for (const flag of child.dotFlags) dotFlags.add(flag);
232
+ for (const flag of child.chipFlags) chipFlags.add(flag);
233
233
  }
234
234
  for (const flag of below.flags) flags.add(flag);
235
- for (const flag of below.dotFlags) dotFlags.add(flag);
235
+ for (const flag of below.chipFlags) chipFlags.add(flag);
236
236
  if (child.thread.isHidden ? child.attentionFlags.size > 0 : !child.quietIgnoringOpen) quietIgnoringOpen = false;
237
237
  if (!child.thread.isHidden) {
238
238
  visibleCount += 1;
@@ -242,7 +242,7 @@ export function buildForest(inputs: ForestInputs): Forest {
242
242
  if (!below.quietIgnoringOpen) quietIgnoringOpen = false;
243
243
  }
244
244
  path.delete(id);
245
- const subtree: Subtree = { descendants, flags, dotFlags, visibleCount, childCount, quietIgnoringOpen };
245
+ const subtree: Subtree = { descendants, flags, chipFlags, visibleCount, childCount, quietIgnoringOpen };
246
246
  subtrees.set(id, subtree);
247
247
  return subtree;
248
248
  };