@gperezmz/bb-plugin-thread-glance 0.2.1 → 0.2.2

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.
Files changed (34) hide show
  1. package/README.md +2 -2
  2. package/dist/app.css +1 -1
  3. package/dist/app.js +29 -29
  4. package/dist/app.meta.json +1 -1
  5. package/dist/server.js +1 -1
  6. package/dist/server.js.map +2 -2
  7. package/dist/server.meta.json +1 -1
  8. package/features/thread-list/components/FoldRows.tsx +12 -11
  9. package/features/thread-list/components/GroupSection.tsx +131 -102
  10. package/features/thread-list/components/ThreadDetails.tsx +2 -3
  11. package/features/thread-list/components/ThreadList.tsx +10 -9
  12. package/features/thread-list/components/ThreadRowView.tsx +22 -18
  13. package/features/thread-list/components/Toolbar.tsx +1 -1
  14. package/features/thread-list/components/controller.ts +2 -0
  15. package/features/thread-list/components/glyphs.tsx +19 -1
  16. package/features/thread-list/data/{useNeedsYouHold.ts → useAttentionHold.ts} +5 -5
  17. package/features/thread-list/model/{needs-you.ts → attention.ts} +24 -24
  18. package/features/thread-list/model/counters.ts +3 -3
  19. package/features/thread-list/model/details.ts +1 -1
  20. package/features/thread-list/model/expansion.ts +10 -10
  21. package/features/thread-list/model/families.ts +26 -22
  22. package/features/thread-list/model/layout.ts +0 -43
  23. package/features/thread-list/model/notes.ts +37 -4
  24. package/features/thread-list/model/settings.ts +1 -1
  25. package/features/thread-list/model/share.ts +3 -3
  26. package/features/thread-list/model/state.ts +1 -1
  27. package/features/thread-list/model/toggles.ts +7 -5
  28. package/features/thread-list/model/view.ts +68 -47
  29. package/features/thread-list/testing/fixtures.ts +3 -3
  30. package/package.json +1 -1
  31. package/server/cli.ts +1 -1
  32. package/shared/preferences.ts +2 -2
  33. package/skills/thread-glance/SKILL.md +4 -4
  34. package/features/thread-list/components/Rails.tsx +0 -27
@@ -20,10 +20,10 @@ import {
20
20
  } from "./groups";
21
21
  import { addCounters, countFamilies, EMPTY_COUNTERS, type Counters } from "./counters";
22
22
  import { comparePinned, effectiveSortField, makeComparator, type SortKey } from "./sort";
23
- import { inNeedsYou } from "./needs-you";
23
+ import { inAttention } from "./attention";
24
24
  import { mostUrgent, type Flag } from "./state";
25
25
  import type { Targets } from "./expansion";
26
- import { railsFor, type Rail } from "./layout";
26
+ import type { RowNote } from "./notes";
27
27
 
28
28
  /** How many of a group's newest quiet roots stay out of its older fold. */
29
29
  export const KEEP_QUIET = 5;
@@ -50,13 +50,18 @@ export interface ThreadRow {
50
50
  /** Title of the thread this one attaches to, for tooltips and labels. */
51
51
  parentTitle: string | null;
52
52
  chip: Chip | null;
53
- /** Guide rails by nesting level. */
54
- rails: (Rail | null)[];
55
- /** A hidden thread shown because it needs you or failed. */
53
+ /**
54
+ * The line under the title. In Needs attention, a row that itself needs
55
+ * attention says why; any other row says why it waits on you or failed.
56
+ */
57
+ note: RowNote | null;
58
+ /** The title, and the chip with it, step back: see `isDimmed`. */
59
+ dimmed: boolean;
60
+ /** A hidden thread shown because it needs attention or failed. */
56
61
  hiddenBadge: boolean;
57
62
  /** "In project X" when the thread is outside its family's group. */
58
63
  crossGroupLabel: string | null;
59
- /** A root in Needs you: its home group's name, drawn where the age goes. */
64
+ /** A root in Needs attention: its home group's name, drawn where the age goes. */
60
65
  homeGroupLabel: string | null;
61
66
  projectId: string;
62
67
  }
@@ -71,7 +76,6 @@ export interface OlderRow {
71
76
  count: number;
72
77
  expanded: boolean;
73
78
  depth: number;
74
- rails: (Rail | null)[];
75
79
  }
76
80
 
77
81
  export interface EnvironmentRow {
@@ -86,10 +90,9 @@ export interface EnvironmentRow {
86
90
  collapsed: boolean;
87
91
  flag: Flag | null;
88
92
  depth: number;
89
- rails: (Rail | null)[];
90
93
  }
91
94
 
92
- /** Under a family in Needs you: how many of its child threads it leaves out ("+N more"). Not a control. */
95
+ /** Under a closed family in Needs attention: how many of its child threads it leaves out ("+N more"). Opens the family. */
93
96
  export interface LeftOutRow {
94
97
  type: "left-out";
95
98
  key: string;
@@ -97,7 +100,6 @@ export interface LeftOutRow {
97
100
  scopeId: string;
98
101
  count: number;
99
102
  depth: number;
100
- rails: (Rail | null)[];
101
103
  }
102
104
 
103
105
  export type Row = ThreadRow | OlderRow | EnvironmentRow | LeftOutRow;
@@ -111,15 +113,15 @@ export interface GroupView {
111
113
  collapsed: boolean;
112
114
  hidden: boolean;
113
115
  rows: Row[];
114
- /** Every family root bucketed in the group, those in Needs you and behind folds included. */
116
+ /** Every family root bucketed in the group, those in Needs attention and behind folds included. */
115
117
  rootIds: string[];
116
118
  }
117
119
 
118
- /** The id Needs you's rows are drawn under; no group has it. */
119
- export const NEEDS_YOU_GROUP_ID = "needs-you";
120
+ /** The id Needs attention's rows are drawn under; no group has it. */
121
+ export const ATTENTION_GROUP_ID = "attention";
120
122
 
121
- /** The Needs you section: every family with a thread that needs you, or held there while one of its threads is open. */
122
- export interface NeedsYouView {
123
+ /** The Needs attention section: every family with a thread that needs attention, or held there while one of its threads is open. */
124
+ export interface AttentionView {
123
125
  /** How many families the section holds, for its header. */
124
126
  familyCount: number;
125
127
  rows: Row[];
@@ -129,7 +131,7 @@ export interface NeedsYouView {
129
131
 
130
132
  export interface ListView {
131
133
  /** Null when no family is in the section. */
132
- needsYou: NeedsYouView | null;
134
+ attention: AttentionView | null;
133
135
  groups: GroupView[];
134
136
  /** Hidden groups, shown in the More popover. */
135
137
  more: GroupView[];
@@ -147,7 +149,7 @@ export interface ViewInputs {
147
149
  sections: readonly PluginSidebarSection[];
148
150
  prefs: Preferences;
149
151
  activeThreadId: string | null;
150
- /** The root of the family Needs you keeps while one of its threads is open. */
152
+ /** The root of the family Needs attention keeps while one of its threads is open. */
151
153
  heldRootId: string | null;
152
154
  targets: Targets;
153
155
  }
@@ -164,6 +166,8 @@ interface Context extends ViewInputs {
164
166
  activePath: ReadonlySet<string>;
165
167
  projectNames: ReadonlyMap<string, string>;
166
168
  sectionNames: ReadonlyMap<string, string>;
169
+ /** The rows are drawn in Needs attention. */
170
+ drawnInAttention: boolean;
167
171
  }
168
172
 
169
173
  function titleOf(context: Context, id: string | null): string | null {
@@ -190,12 +194,23 @@ function crossGroupLabel(context: Context, info: ThreadInfo, root: ThreadInfo):
190
194
  }
191
195
  }
192
196
 
197
+ /**
198
+ * Whether a row's title steps back: a quiet thread does, at any depth, so
199
+ * brightness shows state and depth is left to the indent and smaller text.
200
+ * A root with children stays bright until its whole family is quiet.
201
+ */
202
+ function isDimmed(context: Context, info: ThreadInfo, chip: Chip | null): boolean {
203
+ if (info.parentId === null && chip !== null) return context.forest.familyOf.get(info.thread.id)?.quiet ?? info.quiet;
204
+ return info.quiet;
205
+ }
206
+
193
207
  function threadRow(
194
208
  context: Context,
195
209
  info: ThreadInfo,
196
210
  root: ThreadInfo,
197
211
  options: { depth: number; nested: boolean; chip: Chip | null },
198
212
  ): ThreadRow {
213
+ const needsAttention = !info.thread.isArchived && info.attentionFlags.size > 0;
199
214
  return {
200
215
  type: "thread",
201
216
  key: `thread:${info.thread.id}`,
@@ -204,7 +219,8 @@ function threadRow(
204
219
  nested: options.nested,
205
220
  parentTitle: titleOf(context, info.parentId),
206
221
  chip: options.chip,
207
- rails: [],
222
+ note: context.drawnInAttention && needsAttention ? info.attentionNote : info.note,
223
+ dimmed: isDimmed(context, info, options.chip),
208
224
  hiddenBadge: info.thread.isHidden,
209
225
  crossGroupLabel: crossGroupLabel(context, info, root),
210
226
  homeGroupLabel: null,
@@ -212,11 +228,11 @@ function threadRow(
212
228
  };
213
229
  }
214
230
 
215
- /** The children of `parent` that get a row: hidden ones only when they need you or failed. */
231
+ /** The children of `parent` that get a row: hidden ones only when they need attention or failed. */
216
232
  function eligibleChildren(context: Context, parentId: string): ThreadInfo[] {
217
233
  return (context.forest.children.get(parentId) ?? [])
218
234
  .map((id) => context.forest.infos.get(id)!)
219
- .filter((info) => !info.thread.isHidden || info.needsYou.size > 0);
235
+ .filter((info) => !info.thread.isHidden || info.attentionFlags.size > 0);
220
236
  }
221
237
 
222
238
  function subtreeOf(context: Context, id: string): Subtree {
@@ -248,7 +264,6 @@ function foldedChildren(
248
264
  count,
249
265
  expanded,
250
266
  depth,
251
- rails: [],
252
267
  }
253
268
  : null;
254
269
 
@@ -330,7 +345,6 @@ function clusterEnvironments(context: Context, units: Unit[], depth: number): Ro
330
345
  collapsed,
331
346
  flag: collapsed ? mostUrgent(flags) : null,
332
347
  depth,
333
- rails: [],
334
348
  });
335
349
  if (!collapsed) {
336
350
  for (const member of members) {
@@ -357,7 +371,7 @@ function childProviders(parent: ThreadInfo, descendants: readonly ThreadInfo[]):
357
371
  function chipOf(context: Context, info: ThreadInfo, expanded: boolean): Chip | null {
358
372
  const subtree = subtreeOf(context, info.thread.id);
359
373
  const hasChildren =
360
- subtree.visibleCount > 0 || subtree.descendants.some((descendant) => descendant.thread.isHidden && descendant.needsYou.size > 0);
374
+ subtree.visibleCount > 0 || subtree.descendants.some((descendant) => descendant.thread.isHidden && descendant.attentionFlags.size > 0);
361
375
  if (!hasChildren) return null;
362
376
  return {
363
377
  count: subtree.visibleCount,
@@ -369,7 +383,7 @@ function chipOf(context: Context, info: ThreadInfo, expanded: boolean): Chip | n
369
383
 
370
384
  /** What a thread and everything under it carry, for an environment folder's glyph. */
371
385
  function rollupFlags(context: Context, info: ThreadInfo): Set<Flag> {
372
- const flags = new Set<Flag>([...info.needsYou, ...subtreeOf(context, info.thread.id).flags]);
386
+ const flags = new Set<Flag>([...info.attentionFlags, ...subtreeOf(context, info.thread.id).flags]);
373
387
  if (info.flags.has("working")) flags.add("working");
374
388
  return flags;
375
389
  }
@@ -408,7 +422,7 @@ function familyUnit(context: Context, family: Family): Unit {
408
422
 
409
423
  /** Waiting on you, then failed, then offline, then unread, then the rest. */
410
424
  export function urgency(family: Family): number {
411
- const flags = family.needsYouFlags;
425
+ const flags = family.attentionFlags;
412
426
  if (flags.has("waits-on-you")) return 0;
413
427
  if (flags.has("unread-failed") || flags.has("queue-failed")) return 1;
414
428
  if (flags.has("offline")) return 2;
@@ -417,20 +431,30 @@ export function urgency(family: Family): number {
417
431
  }
418
432
 
419
433
  /**
420
- * One family's rows in Needs you: the path from the root down to each thread
421
- * that needs you or is open, then one "+N more" line for the child threads
422
- * left out. No row draws a chip, since the section never opens one.
434
+ * One family's rows in Needs attention. It arrives with the path from the root
435
+ * down to each thread that needs attention or is open. Closed, the path is all
436
+ * it draws, with one "+N more" line for the child threads left out; opened
437
+ * with the root's chip, it draws as in its home group and keeps the path.
423
438
  */
424
- function needsYouFamilyRows(context: Context, family: Family, homeGroupLabel: string): Row[] {
439
+ function attentionFamilyRows(groupContext: Context, family: Family, homeGroupLabel: string): Row[] {
440
+ const context: Context = { ...groupContext, drawnInAttention: true };
425
441
  const root = family.root;
426
442
  const onPath = new Set<string>([root.thread.id]);
427
443
  for (const info of family.descendants) {
428
- const needsYou = !info.thread.isArchived && info.needsYou.size > 0;
429
- if (!needsYou && !info.isActive) continue;
444
+ const attention = !info.thread.isArchived && info.attentionFlags.size > 0;
445
+ if (!attention && !info.isActive) continue;
430
446
  onPath.add(info.thread.id);
431
447
  for (const ancestor of ancestorsOf(info.thread.id, context.forest.infos, root.thread.id)) onPath.add(ancestor);
432
448
  }
433
- const rows: Row[] = [{ ...threadRow(context, root, root, { depth: 0, nested: false, chip: null }), homeGroupLabel }];
449
+ const expanded = context.expandedChildren.has(root.thread.id);
450
+ if (expanded) {
451
+ // The path is kept the way a reveal target's is.
452
+ const kept: Context = { ...context, revealIds: new Set([...context.revealIds, ...onPath]) };
453
+ const [rootRow, ...rest] = foldedFamilyRows(kept, family);
454
+ return [{ ...(rootRow as ThreadRow), homeGroupLabel }, ...rest];
455
+ }
456
+ const chip = chipOf(context, root, false);
457
+ const rows: Row[] = [{ ...threadRow(context, root, root, { depth: 0, nested: false, chip }), homeGroupLabel }];
434
458
  const walk = (parentId: string, depth: number) => {
435
459
  for (const id of context.forest.children.get(parentId) ?? []) {
436
460
  if (!onPath.has(id)) continue;
@@ -440,15 +464,15 @@ function needsYouFamilyRows(context: Context, family: Family, homeGroupLabel: st
440
464
  };
441
465
  walk(root.thread.id, 1);
442
466
  const left = family.descendants.filter((info) => !info.thread.isHidden && !onPath.has(info.thread.id)).length;
443
- if (left > 0) rows.push({ type: "left-out", key: `left-out:${root.thread.id}`, scopeId: root.thread.id, count: left, depth: 1, rails: [] });
467
+ if (left > 0) rows.push({ type: "left-out", key: `left-out:${root.thread.id}`, scopeId: root.thread.id, count: left, depth: 1 });
444
468
  return rows;
445
469
  }
446
470
 
447
- function buildNeedsYou(
471
+ function buildAttention(
448
472
  context: Context,
449
473
  families: readonly Family[],
450
474
  homeGroupOf: (family: Family) => GroupDescriptor,
451
- ): NeedsYouView | null {
475
+ ): AttentionView | null {
452
476
  if (families.length === 0) return null;
453
477
  // Most urgent first, then the chosen field in its natural direction: the
454
478
  // sort direction orders the groups only.
@@ -468,21 +492,20 @@ function buildNeedsYou(
468
492
  const homeGroupIds: Record<string, string> = {};
469
493
  const rows = sorted.flatMap((family) => {
470
494
  const home = homeGroupOf(family);
471
- const familyRows = needsYouFamilyRows(context, family, home.label);
495
+ const familyRows = attentionFamilyRows(context, family, home.label);
472
496
  for (const row of familyRows) if (row.type === "thread") homeGroupIds[row.info.thread.id] = home.id;
473
497
  return familyRows;
474
498
  });
475
- const rails = railsFor(rows.map((row) => row.depth));
476
499
  return {
477
500
  familyCount: sorted.length,
478
- rows: rows.map((row, index) => ({ ...row, rails: rails[index]! })),
501
+ rows,
479
502
  homeGroupIds,
480
503
  };
481
504
  }
482
505
 
483
506
  /**
484
507
  * One group. `families` is every family bucketed in it, for its counters and
485
- * `rootIds`; `drawn` leaves out those in Needs you.
508
+ * `rootIds`; `drawn` leaves out those in Needs attention.
486
509
  */
487
510
  function buildGroup(
488
511
  context: Context,
@@ -544,15 +567,12 @@ function buildGroup(
544
567
  count: foldedFamilies.length,
545
568
  expanded: opened,
546
569
  depth: 0,
547
- rails: [],
548
570
  };
549
571
  }
550
572
  }
551
573
 
552
574
  const units = visible.map((family) => familyUnit(context, family));
553
- const clustered = clusterEnvironments(context, units, 0);
554
- const rails = railsFor(clustered.map((row) => row.depth));
555
- const rows = clustered.map((row, index) => ({ ...row, rails: rails[index]! }));
575
+ const rows = clusterEnvironments(context, units, 0);
556
576
  if (older !== null) rows.push(older);
557
577
  return {
558
578
  descriptor,
@@ -594,6 +614,7 @@ export function buildListView(inputs: ViewInputs): ListView {
594
614
  activePath,
595
615
  projectNames: new Map(inputs.projects.map((project) => [project.id, project.name])),
596
616
  sectionNames: new Map(inputs.sections.map((section) => [section.id, section.name])),
617
+ drawnInAttention: false,
597
618
  };
598
619
 
599
620
  const byGroup = new Map<string, Family[]>();
@@ -609,7 +630,7 @@ export function buildListView(inputs: ViewInputs): ListView {
609
630
  list.push(family);
610
631
  byGroup.set(id, list);
611
632
  homeOf.set(family, id);
612
- if (inNeedsYou(family, inputs.heldRootId, openRootId)) section.push(family);
633
+ if (inAttention(family, inputs.heldRootId, openRootId)) section.push(family);
613
634
  }
614
635
  const inSection = new Set(section);
615
636
 
@@ -637,7 +658,7 @@ export function buildListView(inputs: ViewInputs): ListView {
637
658
  const families = byGroup.get(id) ?? [];
638
659
  const drawn = families.filter((family) => !inSection.has(family));
639
660
  // Pinned and the loose bucket appear only when they hold threads outside
640
- // Needs you, except the loose bucket in custom-section mode, as in bb.
661
+ // Needs attention, except the loose bucket in custom-section mode, as in bb.
641
662
  if (drawn.length === 0) {
642
663
  if (id === PINNED_GROUP_ID) continue;
643
664
  if (id === THREADS_GROUP_ID && prefs.organizationMode !== "chronological") continue;
@@ -655,8 +676,8 @@ export function buildListView(inputs: ViewInputs): ListView {
655
676
  const hosts = new Set<string>();
656
677
  for (const thread of inputs.threads) if (thread.host !== null) hosts.add(thread.host.id);
657
678
  // Every root's group id has a descriptor: entity groups include unknown sections and every host.
658
- const needsYou = buildNeedsYou(context, section, (family) => descriptors.get(homeOf.get(family)!)!);
659
- return { needsYou, groups, more, moreCounters, order, multiHost: hosts.size > 1 };
679
+ const attention = buildAttention(context, section, (family) => descriptors.get(homeOf.get(family)!)!);
680
+ return { attention, groups, more, moreCounters, order, multiHost: hosts.size > 1 };
660
681
  }
661
682
 
662
683
  /** Every thread row in visual order, for keyboard and windowing. */
@@ -165,7 +165,7 @@ export function rowIds(view: ListView, groupId: string): string[] {
165
165
  return idsOf(group.rows);
166
166
  }
167
167
 
168
- /** Thread ids in Needs you, in order; "+N more" lines as `+N`. Empty when the section is absent. */
169
- export function needsYouIds(view: ListView): string[] {
170
- return idsOf(view.needsYou?.rows ?? []);
168
+ /** Thread ids in Needs attention, in order; "+N more" lines as `+N`. Empty when the section is absent. */
169
+ export function attentionIds(view: ListView): string[] {
170
+ return idsOf(view.attention?.rows ?? []);
171
171
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gperezmz/bb-plugin-thread-glance",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "bb": ">=0.43",
package/server/cli.ts CHANGED
@@ -38,7 +38,7 @@ export function createCli(store: PreferenceStore): PluginCliRegistration {
38
38
  name: "thread-glance",
39
39
  summary: "Inspect and change the Thread Glance sidebar's layout preferences",
40
40
  description:
41
- "Organization, sort, group order, hidden and collapsed groups, child-thread folding and which child threads Needs you counts, for the Thread Glance sidebar. Values are JSON; a bare word is read as a string.",
41
+ "Organization, sort, group order, hidden and collapsed groups, child-thread folding and which child threads Needs attention counts, for the Thread Glance sidebar. Values are JSON; a bare word is read as a string.",
42
42
  commands: {
43
43
  "prefs list": cliCommand({
44
44
  summary: "List every preference and its current value",
@@ -131,7 +131,7 @@ export const PREFERENCES = {
131
131
  childAttention: define(
132
132
  childAttentionSchema,
133
133
  "blocked" as ChildAttention,
134
- "Needs you counts every child: blocked counts a child thread that waits on you, is offline or has an orphaned failure; everything also counts every failed or finished-unread child. The same children stay out of a family's older fold, as running ones do.",
134
+ "Needs attention counts every child: blocked counts a child thread that waits on you, is offline or has an orphaned failure; everything also counts every failed or finished-unread child. The same children stay out of a family's older fold, as running ones do.",
135
135
  ),
136
136
  harnessIcon: define(
137
137
  harnessIconSchema,
@@ -212,7 +212,7 @@ export function mapBbPreferences(raw: unknown): Partial<Preferences> {
212
212
  return mapped as Partial<Preferences>;
213
213
  }
214
214
 
215
- /** Per-client preferences, kept in localStorage only. A saved `filter` from before Needs you is dropped. */
215
+ /** Per-client preferences, kept in localStorage only. A saved `filter`, from the old All / Needs attention control, is dropped. */
216
216
  export const clientPreferencesSchema = z.object({
217
217
  density: z.enum(["compact", "comfortable"]).catch("compact"),
218
218
  });
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: thread-glance
3
- description: "Reads and changes the Thread Glance sidebar's layout preferences with `bb thread-glance prefs`: grouping, sort, group order, hidden and collapsed groups, child-thread folding, which child threads Needs you counts, harness icons. Use when asked to change how the Thread Glance sidebar lists, groups, sorts, hides, collapses or folds threads or draws their harness icon."
3
+ description: "Reads and changes the Thread Glance sidebar's layout preferences with `bb thread-glance prefs`: grouping, sort, group order, hidden and collapsed groups, child-thread folding, which child threads Needs attention counts, harness icons. Use when asked to change how the Thread Glance sidebar lists, groups, sorts, hides, collapses or folds threads or draws their harness icon."
4
4
  ---
5
5
 
6
6
  # Thread Glance preferences
@@ -57,12 +57,12 @@ and `bb environment list`; a thread id is a `thr_…` id.
57
57
  | `expandedOlder` | Group ids and parent thread ids whose "N older" or "N more child threads" row is open | `[]` |
58
58
  | `expandedChildren` | Parent thread ids whose chip is open | `[]` |
59
59
  | `showPullRequests` | `true` shows a pull request badge on rows | `true` |
60
- | `childAttention` | Needs you counts every child. Which child threads need you (the Needs you section, counters, auto-reveal) and stay out of a family's "N more child threads" fold alongside running ones: `blocked` counts a child that waits on you, is offline, or has an orphaned failure (its parent thread idle since); `everything` also counts every failed or unread child | `blocked` |
60
+ | `childAttention` | Needs attention counts every child. Which child threads need attention (the Needs attention section, counters, auto-reveal) and stay out of a family's "N more child threads" fold alongside running ones: `blocked` counts a child that waits on you, is offline, or has an orphaned failure (its parent thread idle since); `everything` also counts every failed or unread child | `blocked` |
61
61
  | `harnessIcon` | How rows draw the harness logo: `muted` (monochrome), `colour` (the provider's tint) or `hidden` | `muted` |
62
62
 
63
63
  Row density is kept per browser, and the CLI cannot read or change it. No
64
- preference filters the list: families that need you always move into the
65
- Needs you section.
64
+ preference filters the list: families that need attention always move into the
65
+ Needs attention section.
66
66
 
67
67
  Sections themselves and the section a thread is in are bb core state: use
68
68
  `bb thread section` and `bb thread update`.
@@ -1,27 +0,0 @@
1
- // The guide rails of a nested row: one hairline per nesting level,
2
- // drawn inside the row so they join up from row to row.
3
- import { railLeft, type Rail } from "../model/layout";
4
-
5
- // `start` begins just under the 16px status slot, which is centred in the row.
6
- const SPAN: Record<Rail, { top: string; bottom: string }> = {
7
- start: { top: "calc(50% + 10px)", bottom: "0px" },
8
- full: { top: "0px", bottom: "0px" },
9
- end: { top: "0px", bottom: "50%" },
10
- };
11
-
12
- export function RowRails({ rails }: { rails: readonly (Rail | null)[] }) {
13
- if (rails.every((rail) => rail === null)) return null;
14
- return (
15
- <span aria-hidden data-rails="" className="pointer-events-none absolute inset-0">
16
- {rails.map((rail, level) =>
17
- rail === null ? null : (
18
- <span
19
- key={level}
20
- className="absolute w-px bg-border"
21
- style={{ left: railLeft(level), ...SPAN[rail] }}
22
- />
23
- ),
24
- )}
25
- </span>
26
- );
27
- }