@gperezmz/bb-plugin-thread-glance 0.4.0 → 0.4.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.
@@ -3,7 +3,7 @@
3
3
  "sdkVersion": "0.5.9",
4
4
  "artifactFormatVersion": 1,
5
5
  "pluginId": "thread-glance",
6
- "pluginVersion": "0.4.0",
6
+ "pluginVersion": "0.4.1",
7
7
  "builtWith": {
8
8
  "bbVersion": "0.43.4",
9
9
  "pluginSdkVersion": "0.5.9"
@@ -39,9 +39,7 @@ import { moveGroup, ORDER_PREFERENCE } from "../model/groups";
39
39
  import { MARK_ALL_CONFIRM_ABOVE, type RowMenuAction } from "../model/menu";
40
40
  import { assignProviderMarks, providerMark } from "../model/provider-mark";
41
41
  import { markAllReadPlan, markReadPlanFor, toggleChip, toggleGroup, toggleOlder, toggleSettled, type ToggleOutcome } from "../model/toggles";
42
- import { pullRequestFact, pullRequestLookupIds, type SettleInputs } from "../model/settled";
43
- import { usePullRequestAnswers } from "../data/usePullRequestAnswers";
44
- import { PullRequestProbes } from "./PullRequestProbes";
42
+ import type { SettleInputs } from "../model/settled";
45
43
  import { buildListView, countNeedYou, needYouActive, type GroupView, type ListView } from "../model/view";
46
44
  import { shareView } from "../model/share";
47
45
  import { ListLiveContext, type ListLive, type ModelInfo, type RowController } from "./controller";
@@ -174,21 +172,15 @@ function ThreadListBody({
174
172
  (thread: PluginSidebarThread) => (defaultBranches.has(thread.projectId) ? (defaultBranches.get(thread.projectId) ?? null) : undefined),
175
173
  [defaultBranches],
176
174
  );
177
- const [pullRequests, onPullRequest] = usePullRequestAnswers();
178
175
  const system = useSystemFacts();
179
- const pullRequestLookups = useMemo(
180
- () => (forest === null ? [] : pullRequestLookupIds(forest.infos.values(), defaultBranchOf)),
181
- [forest, defaultBranchOf],
182
- );
183
176
  const settle: SettleInputs = useMemo(
184
177
  () => ({
185
178
  now,
186
179
  settleAfter: prefs.settleAfter,
187
180
  startedAt: stamps.startedAt,
188
181
  finishedAt: stamps.finishedAt,
189
- pullRequestOf: (thread) => pullRequestFact(thread, defaultBranchOf(thread), pullRequests),
190
182
  }),
191
- [now, prefs.settleAfter, stamps.startedAt, stamps.finishedAt, defaultBranchOf, pullRequests],
183
+ [now, prefs.settleAfter, stamps.startedAt, stamps.finishedAt],
192
184
  );
193
185
  // Rows and groups that did not change keep their objects, so their
194
186
  // memoized components skip the render.
@@ -723,7 +715,6 @@ function ThreadListBody({
723
715
  onPrefs={update}
724
716
  onClient={updateClient}
725
717
  />
726
- <PullRequestProbes threadIds={pullRequestLookups} onAnswer={onPullRequest} />
727
718
  {threads.length === 0 ? (
728
719
  // bb's own pinned New thread button covers the empty list.
729
720
  <p className="px-3 py-4 text-sm text-muted-foreground">No threads yet.</p>
@@ -15,46 +15,12 @@ export const SETTLE_AFTER_MS: Readonly<Record<Exclude<SettleAfter, "never">, num
15
15
  "1w": 7 * 24 * HOUR,
16
16
  };
17
17
 
18
- /** A pull request's state, as bb's lookup reports it. */
19
- export type PullRequestState = "open" | "draft" | "merged" | "closed";
20
-
21
- /**
22
- * What settling knows of a thread's pull request: its state, null when the
23
- * thread has none, or `unknown` while a lookup it needs has not answered.
24
- */
25
- export type PullRequestFact = PullRequestState | null | "unknown";
26
-
27
- /**
28
- * Whether a thread's branch can carry a pull request worth looking up: it is
29
- * on a branch other than its project's default. A default branch the lookup
30
- * could not find (null) is no reason to skip one; one still being looked up
31
- * (undefined) is, until it answers.
32
- */
33
- export function needsPullRequestLookup(branch: string | null, defaultBranch: string | null | undefined): boolean {
34
- return branch !== null && defaultBranch !== undefined && branch !== defaultBranch;
35
- }
36
-
37
- /** A thread's pull request fact, from its project's default branch and the lookups that answered. */
38
- export function pullRequestFact(
39
- thread: Pick<PluginSidebarThread, "id" | "environment">,
40
- defaultBranch: string | null | undefined,
41
- answers: ReadonlyMap<string, PullRequestState | null>,
42
- ): PullRequestFact {
43
- const branch = thread.environment?.branchName ?? null;
44
- if (branch === null) return null;
45
- if (defaultBranch === undefined) return "unknown";
46
- if (!needsPullRequestLookup(branch, defaultBranch)) return null;
47
- const answer = answers.get(thread.id);
48
- return answer === undefined ? "unknown" : answer;
49
- }
50
-
51
18
  export interface SettleInputs {
52
19
  now: number;
53
20
  settleAfter: SettleAfter;
54
21
  /** Server stamps: when each thread last started and finished a turn. */
55
22
  startedAt: Readonly<Record<string, number>>;
56
23
  finishedAt: Readonly<Record<string, number>>;
57
- pullRequestOf(thread: PluginSidebarThread): PullRequestFact;
58
24
  }
59
25
 
60
26
  /**
@@ -77,46 +43,18 @@ export function lastActivityAt(
77
43
 
78
44
  /**
79
45
  * A settled thread: quiet as if no thread were open, not needing attention,
80
- * not pinned, with no open pull request, and either its pull request merged
81
- * or closed or its own last activity is older than the Settle after period.
82
- * Hidden and archived threads take the same test.
46
+ * not pinned, and its own last activity is older than the Settle after
47
+ * period. Hidden and archived threads take the same test.
83
48
  */
84
49
  export function isSettledThread(info: ThreadInfo, inputs: SettleInputs): boolean {
85
50
  const thread = info.thread;
86
- if (!mightSettle(info)) return false;
87
- const pullRequest = inputs.pullRequestOf(thread);
88
- if (pullRequest === "unknown" || pullRequest === "open" || pullRequest === "draft") return false;
89
- if (pullRequest === "merged" || pullRequest === "closed") return true;
51
+ if (thread.pinnedAt !== null || thread.isPinned) return false;
52
+ if (!isQuietThread(info.state, info.unread, false) || info.attentionFlags.size > 0) return false;
90
53
  if (inputs.settleAfter === "never") return false;
91
54
  return inputs.now - lastActivityAt(thread, inputs) >= SETTLE_AFTER_MS[inputs.settleAfter];
92
55
  }
93
56
 
94
- /** A row's thread that settles once its pull request and activity allow: quiet as if none were open, needing no attention, not pinned. */
95
- function mightSettle(info: ThreadInfo): boolean {
96
- const thread = info.thread;
97
- if (thread.pinnedAt !== null || thread.isPinned) return false;
98
- return isQuietThread(info.state, info.unread, false) && info.attentionFlags.size === 0;
99
- }
100
-
101
57
  /** A tree settles as one unit: when every thread in it is settled. */
102
58
  export function isSettledTree(tree: Pick<ThreadTree, "root" | "descendants">, inputs: SettleInputs): boolean {
103
59
  return isSettledThread(tree.root, inputs) && tree.descendants.every((info) => isSettledThread(info, inputs));
104
60
  }
105
-
106
- /**
107
- * The threads whose pull request settling has to look up: every thread that
108
- * would settle but for its pull request, on a branch other than its
109
- * project's default.
110
- */
111
- export function pullRequestLookupIds(
112
- infos: Iterable<ThreadInfo>,
113
- defaultBranchOf: (thread: PluginSidebarThread) => string | null | undefined,
114
- ): string[] {
115
- const ids: string[] = [];
116
- for (const info of infos) {
117
- const thread = info.thread;
118
- if (!mightSettle(info)) continue;
119
- if (needsPullRequestLookup(thread.environment?.branchName ?? null, defaultBranchOf(thread))) ids.push(thread.id);
120
- }
121
- return ids;
122
- }
@@ -8,7 +8,7 @@ import { defaultPreferences, type Preferences } from "@/shared/preferences";
8
8
  import { buildForest, type Forest } from "../model/trees";
9
9
  import { buildListView, type ListView, type Row } from "../model/view";
10
10
  import type { Targets } from "../model/expansion";
11
- import { pullRequestFact, type PullRequestState, type SettleInputs } from "../model/settled";
11
+ import type { SettleInputs } from "../model/settled";
12
12
 
13
13
  export const T0 = 1_780_000_000_000;
14
14
 
@@ -125,8 +125,6 @@ export interface Scenario {
125
125
  needYouOnly?: boolean;
126
126
  /** bb's default harness; "claude-code", the fixtures' own, when absent. */
127
127
  defaultProviderId?: string | null;
128
- /** Pull request lookups that answered, by thread id. */
129
- pullRequests?: Record<string, PullRequestState | null>;
130
128
  }
131
129
 
132
130
  export function forestOf(scenario: Scenario): Forest {
@@ -165,15 +163,11 @@ export function viewOf(scenario: Scenario): ListView {
165
163
 
166
164
  /** The settle inputs a scenario stands for. */
167
165
  export function settleOf(scenario: Scenario): SettleInputs {
168
- const answers = new Map(Object.entries(scenario.pullRequests ?? {}));
169
- const defaultBranchOf = (projectId: string) =>
170
- scenario.defaultBranches === undefined ? "main" : scenario.defaultBranches[projectId];
171
166
  return {
172
167
  now: scenario.now ?? T0 + 60_000,
173
168
  settleAfter: scenario.prefs?.settleAfter ?? defaultPreferences().settleAfter,
174
169
  startedAt: scenario.startedAt ?? {},
175
170
  finishedAt: scenario.finishedAt ?? {},
176
- pullRequestOf: (thread) => pullRequestFact(thread, defaultBranchOf(thread.projectId), answers),
177
171
  };
178
172
  }
179
173
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gperezmz/bb-plugin-thread-glance",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "bb": ">=0.43",
@@ -52,7 +52,7 @@ and `bb environment list`; a thread id is a `thr_…` id.
52
52
  | `collapsedThreadSections` | Custom section keys, `section:<id>` | `[]` |
53
53
  | `collapsedMachines` | Machine ids | `[]` |
54
54
  | `collapsedEnvironments` | Environment ids of collapsed folder rows | `[]` |
55
- | `settleAfter` | Settle after: how long a quiet thread goes without activity before its tree can settle into the group's "Settled (N)" fold: `12h`, `1d`, `3d`, `1w` or `never` (then only a merged or closed pull request settles it) | `1d` |
55
+ | `settleAfter` | Settle after: how long a quiet thread goes without activity before its tree can settle into the group's "Settled (N)" fold: `12h`, `1d`, `3d`, `1w` or `never` (then nothing settles) | `1d` |
56
56
  | `openSettledFolds` | Group ids whose settled fold is open | `[]` |
57
57
  | `expandedOlder` | Parent thread ids whose "N more child threads" row is open, once that tree's children chip is open | `[]` |
58
58
  | `expandedChildren` | Parent thread ids whose chip is open | `[]` |
@@ -1,27 +0,0 @@
1
- // Looks up the pull request of every thread settling has to know about and
2
- // reports each answer. Draws nothing: the host shares one lookup per
3
- // environment with the badges that ask for the same threads.
4
- import { memo, useEffect } from "react";
5
- import { experimental_useSidebarThreadPullRequest as usePullRequest } from "@get-bb/plugin-sdk/app";
6
- import type { PullRequestState } from "../model/settled";
7
-
8
- type Answer = (threadId: string, state: PullRequestState | null) => void;
9
-
10
- const Probe = memo(function Probe({ threadId, onAnswer }: { threadId: string; onAnswer: Answer }) {
11
- const { isLoading, pullRequest } = usePullRequest(threadId);
12
- const state = isLoading ? undefined : (pullRequest?.state ?? null);
13
- useEffect(() => {
14
- if (state !== undefined) onAnswer(threadId, state);
15
- }, [threadId, state, onAnswer]);
16
- return null;
17
- });
18
-
19
- export function PullRequestProbes({ threadIds, onAnswer }: { threadIds: readonly string[]; onAnswer: Answer }) {
20
- return (
21
- <>
22
- {threadIds.map((threadId) => (
23
- <Probe key={threadId} threadId={threadId} onAnswer={onAnswer} />
24
- ))}
25
- </>
26
- );
27
- }
@@ -1,36 +0,0 @@
1
- // The pull request lookups that answered, by thread id. Answers arriving in
2
- // one burst (a list's worth of lookups on load) land in one state update.
3
- //
4
- // Debt: this copies server state the host's per-row pull request hook owns,
5
- // because settling reads every thread's pull request at once and the SDK
6
- // offers no query over many threads. It clears when the SDK does.
7
- import { useCallback, useRef, useState } from "react";
8
- import type { PullRequestState } from "../model/settled";
9
-
10
- export type PullRequestAnswers = ReadonlyMap<string, PullRequestState | null>;
11
-
12
- export function usePullRequestAnswers(): [PullRequestAnswers, (threadId: string, state: PullRequestState | null) => void] {
13
- const [answers, setAnswers] = useState<PullRequestAnswers>(() => new Map());
14
- const pending = useRef(new Map<string, PullRequestState | null>());
15
- const scheduled = useRef(false);
16
- const onAnswer = useCallback((threadId: string, state: PullRequestState | null) => {
17
- pending.current.set(threadId, state);
18
- if (scheduled.current) return;
19
- scheduled.current = true;
20
- setTimeout(() => {
21
- scheduled.current = false;
22
- const batch = pending.current;
23
- pending.current = new Map();
24
- setAnswers((current) => {
25
- let next: Map<string, PullRequestState | null> | null = null;
26
- for (const [id, value] of batch) {
27
- if (current.has(id) && current.get(id) === value) continue;
28
- next ??= new Map(current);
29
- next.set(id, value);
30
- }
31
- return next ?? current;
32
- });
33
- }, 0);
34
- }, []);
35
- return [answers, onAnswer];
36
- }