@gperezmz/bb-plugin-thread-glance 0.2.4 → 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.
- package/README.md +1 -1
- package/app.tsx +3 -11
- package/dist/app.css +1 -1
- package/dist/app.js +31 -31
- package/dist/app.meta.json +1 -1
- package/dist/server.js +10 -10
- package/dist/server.js.map +3 -3
- package/dist/server.meta.json +1 -1
- package/features/thread-list/components/FoldRows.tsx +21 -30
- package/features/thread-list/components/GroupSection.tsx +7 -45
- package/features/thread-list/components/ListHeader.tsx +79 -0
- package/features/thread-list/components/ProviderBadge.tsx +0 -1
- package/features/thread-list/components/PullRequestProbes.tsx +27 -0
- package/features/thread-list/components/SettingsPanel.tsx +20 -34
- package/features/thread-list/components/ThreadDetails.tsx +3 -3
- package/features/thread-list/components/ThreadList.tsx +117 -83
- package/features/thread-list/components/ThreadRowView.tsx +203 -180
- package/features/thread-list/components/controller.ts +5 -10
- package/features/thread-list/components/glyphs.tsx +10 -16
- package/features/thread-list/data/useAutoExpand.ts +1 -1
- package/features/thread-list/data/useDefaultBranches.ts +35 -0
- package/features/thread-list/data/usePullRequestAnswers.ts +36 -0
- package/features/thread-list/data/useSystemFacts.ts +52 -0
- package/features/thread-list/icons.ts +1 -0
- package/features/thread-list/index.ts +0 -1
- package/features/thread-list/model/attention.ts +2 -66
- package/features/thread-list/model/branches.ts +27 -0
- package/features/thread-list/model/counters.ts +5 -5
- package/features/thread-list/model/details.ts +6 -6
- package/features/thread-list/model/expansion.ts +5 -5
- package/features/thread-list/model/groups.ts +2 -2
- package/features/thread-list/model/labels.ts +43 -18
- package/features/thread-list/model/menu.ts +1 -0
- package/features/thread-list/model/move.ts +1 -1
- package/features/thread-list/model/notes.ts +2 -28
- package/features/thread-list/model/settings.ts +1 -17
- package/features/thread-list/model/settled.ts +122 -0
- package/features/thread-list/model/share.ts +1 -7
- package/features/thread-list/model/sort.ts +5 -16
- package/features/thread-list/model/state.ts +17 -16
- package/features/thread-list/model/time.ts +7 -0
- package/features/thread-list/model/toggles.ts +35 -24
- package/features/thread-list/model/{families.ts → trees.ts} +40 -28
- package/features/thread-list/model/view.ts +202 -274
- package/features/thread-list/testing/browser.css +13 -0
- package/features/thread-list/testing/fixtures.ts +41 -12
- package/package.json +5 -1
- package/server/cli.ts +1 -1
- package/server/import.ts +36 -6
- package/server/preference-store.ts +2 -1
- package/server.ts +1 -1
- package/shared/preferences.ts +38 -27
- package/skills/thread-glance/SKILL.md +11 -12
- package/features/thread-list/data/useAttentionHold.ts +0 -19
|
@@ -29,31 +29,36 @@ import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover
|
|
|
29
29
|
import { cn } from "@/lib/utils";
|
|
30
30
|
import { useAutoExpand } from "../data/useAutoExpand";
|
|
31
31
|
import { useClientPreferences } from "../data/useClientPreferences";
|
|
32
|
-
import { useAttentionHold } from "../data/useAttentionHold";
|
|
33
32
|
import { useNow } from "../data/useNow";
|
|
34
33
|
import { usePreferences } from "../data/usePreferences";
|
|
35
34
|
import { useScheduled } from "../data/useScheduled";
|
|
36
35
|
import { useStamps } from "../data/useStamps";
|
|
37
36
|
import { resolveDrop, type DraggedThread, type DropAction, type DropTarget } from "../model/drag";
|
|
38
|
-
import { buildForest } from "../model/
|
|
37
|
+
import { buildForest } from "../model/trees";
|
|
39
38
|
import { moveGroup, ORDER_PREFERENCE } from "../model/groups";
|
|
40
39
|
import { MARK_ALL_CONFIRM_ABOVE, type RowMenuAction } from "../model/menu";
|
|
41
40
|
import { assignProviderMarks, providerMark } from "../model/provider-mark";
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
44
|
-
import {
|
|
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";
|
|
45
|
+
import { buildListView, countNeedYou, needYouActive, type GroupView, type ListView } from "../model/view";
|
|
45
46
|
import { shareView } from "../model/share";
|
|
46
47
|
import { ListLiveContext, type ListLive, type ModelInfo, type RowController } from "./controller";
|
|
47
48
|
import { ConfirmDialog, CustomizeDialog, DetailsDialog, MoveDialog, NewSectionDialog, type CustomizeItem } from "./Dialogs";
|
|
48
49
|
import { useNotes } from "../data/useNotes";
|
|
50
|
+
import { useDefaultBranches } from "../data/useDefaultBranches";
|
|
51
|
+
import { useSystemFacts } from "../data/useSystemFacts";
|
|
49
52
|
import { moveTargets } from "../model/move";
|
|
50
53
|
import { modelDisplayName } from "../model/details";
|
|
51
54
|
import { groupIdForRoot } from "../model/groups";
|
|
52
55
|
import { CounterStrip } from "./glyphs";
|
|
53
56
|
import { cancelPendingCards } from "./row-card";
|
|
54
|
-
import { GroupSection,
|
|
57
|
+
import { GroupSection, type DropStates, type GroupController } from "./GroupSection";
|
|
55
58
|
import type { ProviderDisplay } from "./ProviderBadge";
|
|
56
59
|
import { ThreadDetails } from "./ThreadDetails";
|
|
60
|
+
import { ListHeader } from "./ListHeader";
|
|
61
|
+
import type { ThreadTree } from "../model/trees";
|
|
57
62
|
|
|
58
63
|
const PLUGIN_ID = "thread-glance";
|
|
59
64
|
|
|
@@ -107,8 +112,10 @@ function ThreadListBody({
|
|
|
107
112
|
onRetry,
|
|
108
113
|
}: PluginThreadListProps & { attempt: number; onRetry(): void }) {
|
|
109
114
|
const { prefs, hydrated, update } = usePreferences();
|
|
110
|
-
const [client] = useClientPreferences();
|
|
111
|
-
|
|
115
|
+
const [client, updateClient] = useClientPreferences();
|
|
116
|
+
// The need-you filter is per window and starts off on every load.
|
|
117
|
+
const [needYouOn, setNeedYouOn] = useState(false);
|
|
118
|
+
const sidebar = useSidebarThreads({ experimental_lifecycles: prefs.showArchived ? ["active", "archived"] : ["active"] });
|
|
112
119
|
const actions = useThreadActions();
|
|
113
120
|
const sdk = useSdk();
|
|
114
121
|
const { providers } = useProviders();
|
|
@@ -133,8 +140,6 @@ function ThreadListBody({
|
|
|
133
140
|
const [confirm, setConfirm] = useState<Confirm | null>(null);
|
|
134
141
|
const [dropStates, setDropStates] = useState<DropStates>(() => new Map());
|
|
135
142
|
const [dropGroupId, setDropGroupId] = useState<string | null>(null);
|
|
136
|
-
const [defaultBranches, setDefaultBranches] = useState<ReadonlyMap<string, string | null>>(() => new Map());
|
|
137
|
-
const branchRequests = useRef(new Set<string>());
|
|
138
143
|
const models = useRef(new Map<string, Promise<ModelInfo | null>>());
|
|
139
144
|
|
|
140
145
|
const ready = sidebar.status === "ready";
|
|
@@ -159,7 +164,32 @@ function ThreadListBody({
|
|
|
159
164
|
[ready, threads, activeThreadId, stamps.finishedAt, stamps.seenAt, draftIds, scheduled, now, notes, prefs.childAttention],
|
|
160
165
|
);
|
|
161
166
|
const { targets, prune } = useAutoExpand(hydrated ? forest : null, activeThreadId);
|
|
162
|
-
|
|
167
|
+
// Projects with a thread on a branch: the rest need no default branch.
|
|
168
|
+
const branchedProjectIds = useMemo(
|
|
169
|
+
() => [...new Set(threads.flatMap((thread) => (thread.environment?.branchName ? [thread.projectId] : [])))],
|
|
170
|
+
[threads],
|
|
171
|
+
);
|
|
172
|
+
const defaultBranches = useDefaultBranches(branchedProjectIds);
|
|
173
|
+
const defaultBranchOf = useCallback(
|
|
174
|
+
(thread: PluginSidebarThread) => (defaultBranches.has(thread.projectId) ? (defaultBranches.get(thread.projectId) ?? null) : undefined),
|
|
175
|
+
[defaultBranches],
|
|
176
|
+
);
|
|
177
|
+
const [pullRequests, onPullRequest] = usePullRequestAnswers();
|
|
178
|
+
const system = useSystemFacts();
|
|
179
|
+
const pullRequestLookups = useMemo(
|
|
180
|
+
() => (forest === null ? [] : pullRequestLookupIds(forest.infos.values(), defaultBranchOf)),
|
|
181
|
+
[forest, defaultBranchOf],
|
|
182
|
+
);
|
|
183
|
+
const settle: SettleInputs = useMemo(
|
|
184
|
+
() => ({
|
|
185
|
+
now,
|
|
186
|
+
settleAfter: prefs.settleAfter,
|
|
187
|
+
startedAt: stamps.startedAt,
|
|
188
|
+
finishedAt: stamps.finishedAt,
|
|
189
|
+
pullRequestOf: (thread) => pullRequestFact(thread, defaultBranchOf(thread), pullRequests),
|
|
190
|
+
}),
|
|
191
|
+
[now, prefs.settleAfter, stamps.startedAt, stamps.finishedAt, defaultBranchOf, pullRequests],
|
|
192
|
+
);
|
|
163
193
|
// Rows and groups that did not change keep their objects, so their
|
|
164
194
|
// memoized components skip the render.
|
|
165
195
|
const previousView = useRef<ListView | null>(null);
|
|
@@ -174,14 +204,37 @@ function ThreadListBody({
|
|
|
174
204
|
sections: sidebar.sections,
|
|
175
205
|
prefs,
|
|
176
206
|
activeThreadId,
|
|
177
|
-
held,
|
|
178
207
|
targets,
|
|
208
|
+
settle,
|
|
209
|
+
defaultProviderId: system.defaultProviderId,
|
|
210
|
+
primaryHostId: system.primaryHostId,
|
|
211
|
+
comfortable: client.density === "comfortable",
|
|
212
|
+
defaultBranchOf,
|
|
213
|
+
needYouOnly: needYouActive(needYouOn, countNeedYou(forest)),
|
|
179
214
|
})),
|
|
180
|
-
[
|
|
215
|
+
[
|
|
216
|
+
forest,
|
|
217
|
+
threads,
|
|
218
|
+
sidebar.projects,
|
|
219
|
+
sidebar.sections,
|
|
220
|
+
prefs,
|
|
221
|
+
activeThreadId,
|
|
222
|
+
targets,
|
|
223
|
+
settle,
|
|
224
|
+
system.defaultProviderId,
|
|
225
|
+
system.primaryHostId,
|
|
226
|
+
client.density,
|
|
227
|
+
defaultBranchOf,
|
|
228
|
+
needYouOn,
|
|
229
|
+
],
|
|
181
230
|
);
|
|
182
231
|
useLayoutEffect(() => {
|
|
183
232
|
previousView.current = view;
|
|
184
233
|
}, [view]);
|
|
234
|
+
// Nothing left that needs you turns the filter off, so it does not narrow
|
|
235
|
+
// the list again unasked when something next does. Set during render, as
|
|
236
|
+
// React adjusts state from a changed input.
|
|
237
|
+
if (needYouOn && forest !== null && !needYouActive(needYouOn, countNeedYou(forest))) setNeedYouOn(false);
|
|
185
238
|
|
|
186
239
|
// Viewing a child stamps seenAt, on arrival and on leaving, so a child
|
|
187
240
|
// that finishes while you watch doesn't turn unread behind you.
|
|
@@ -197,22 +250,6 @@ function ThreadListBody({
|
|
|
197
250
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
198
251
|
}, [activeThreadId]);
|
|
199
252
|
|
|
200
|
-
// Each project's default branch, fetched once per session.
|
|
201
|
-
useEffect(() => {
|
|
202
|
-
if (!prefs.showPullRequests || forest === null) return;
|
|
203
|
-
for (const family of forest.families) {
|
|
204
|
-
const thread = family.root.thread;
|
|
205
|
-
const projectId = thread.projectId;
|
|
206
|
-
if (thread.environment?.branchName == null || thread.host === null) continue;
|
|
207
|
-
if (branchRequests.current.has(projectId)) continue;
|
|
208
|
-
branchRequests.current.add(projectId);
|
|
209
|
-
sdk.projects.branches({ projectId, hostId: thread.host.id, limit: "1" }).then(
|
|
210
|
-
(result) => setDefaultBranches((current) => new Map(current).set(projectId, result.defaultBranch)),
|
|
211
|
-
() => setDefaultBranches((current) => new Map(current).set(projectId, null)),
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
}, [prefs.showPullRequests, forest, sdk]);
|
|
215
|
-
|
|
216
253
|
const marks = useMemo(() => assignProviderMarks(providers), [providers]);
|
|
217
254
|
// One display per harness, built once: rows compare it by identity.
|
|
218
255
|
const providerDisplay = useMemo(() => {
|
|
@@ -257,10 +294,13 @@ function ThreadListBody({
|
|
|
257
294
|
case "copy-id":
|
|
258
295
|
void copyText(thread.id, "Thread ID copied");
|
|
259
296
|
return;
|
|
260
|
-
case "mark-read":
|
|
261
|
-
|
|
262
|
-
|
|
297
|
+
case "mark-read": {
|
|
298
|
+
const forest = latest.current.forest;
|
|
299
|
+
const plan = forest === null ? { read: [thread.id], seen: [] } : markReadPlanFor(thread.id, forest, context);
|
|
300
|
+
if (plan.seen.length > 0) markSeen(plan.seen);
|
|
301
|
+
for (const id of plan.read) actions.setRead(id, true).catch(fail("Couldn't mark read"));
|
|
263
302
|
return;
|
|
303
|
+
}
|
|
264
304
|
case "mark-unread":
|
|
265
305
|
clearSeen([thread.id]);
|
|
266
306
|
actions.setRead(thread.id, false).catch(fail("Couldn't mark unread"));
|
|
@@ -341,7 +381,6 @@ function ThreadListBody({
|
|
|
341
381
|
[sdk],
|
|
342
382
|
);
|
|
343
383
|
|
|
344
|
-
const multiHost = view?.multiHost ?? false;
|
|
345
384
|
const built = forest !== null && view !== null;
|
|
346
385
|
const rowController: RowController | null = useMemo(() => {
|
|
347
386
|
if (!built) return null;
|
|
@@ -349,11 +388,7 @@ function ThreadListBody({
|
|
|
349
388
|
compact: isCompactViewport,
|
|
350
389
|
comfortable: client.density === "comfortable",
|
|
351
390
|
setEditingId,
|
|
352
|
-
showPullRequests: prefs.showPullRequests,
|
|
353
391
|
harnessIcon: prefs.harnessIcon,
|
|
354
|
-
defaultBranchOf: (thread) =>
|
|
355
|
-
defaultBranches.has(thread.projectId) ? defaultBranches.get(thread.projectId) ?? null : undefined,
|
|
356
|
-
multiHost,
|
|
357
392
|
provider: providerDisplay,
|
|
358
393
|
sections: sidebar.sections,
|
|
359
394
|
mode: prefs.organizationMode,
|
|
@@ -363,11 +398,10 @@ function ThreadListBody({
|
|
|
363
398
|
applyToggle(toggleChip(row, prefs, forest!));
|
|
364
399
|
},
|
|
365
400
|
onToggleOlder: (row) => {
|
|
366
|
-
const { prefs, forest
|
|
367
|
-
|
|
368
|
-
applyToggle(toggleOlder(row, prefs, group, forest!));
|
|
401
|
+
const { prefs, forest } = latest.current;
|
|
402
|
+
applyToggle(toggleOlder(row, prefs, forest!));
|
|
369
403
|
},
|
|
370
|
-
|
|
404
|
+
onToggleSettled: (row) => applyToggle(toggleSettled(row, latest.current.prefs)),
|
|
371
405
|
onToggleEnvironment: (environmentId) => {
|
|
372
406
|
const { collapsedEnvironments } = latest.current.prefs;
|
|
373
407
|
update({
|
|
@@ -397,11 +431,8 @@ function ThreadListBody({
|
|
|
397
431
|
built,
|
|
398
432
|
isCompactViewport,
|
|
399
433
|
client.density,
|
|
400
|
-
prefs.showPullRequests,
|
|
401
434
|
prefs.harnessIcon,
|
|
402
435
|
prefs.organizationMode,
|
|
403
|
-
defaultBranches,
|
|
404
|
-
multiHost,
|
|
405
436
|
providerDisplay,
|
|
406
437
|
sidebar.sections,
|
|
407
438
|
onNavigate,
|
|
@@ -413,54 +444,58 @@ function ThreadListBody({
|
|
|
413
444
|
]);
|
|
414
445
|
|
|
415
446
|
const live: ListLive = useMemo(
|
|
416
|
-
() => ({ now, stamps, notes,
|
|
447
|
+
() => ({ now, stamps, notes, treeOf: (threadId) => forest?.treeOf.get(threadId) }),
|
|
417
448
|
[now, stamps, notes, forest],
|
|
418
449
|
);
|
|
419
450
|
|
|
420
451
|
const activeGroupId = useMemo(() => {
|
|
421
|
-
const
|
|
422
|
-
return
|
|
452
|
+
const tree = activeThreadId === null || forest === null ? undefined : forest.treeOf.get(activeThreadId);
|
|
453
|
+
return tree === undefined
|
|
423
454
|
? null
|
|
424
|
-
: groupIdForRoot(
|
|
455
|
+
: groupIdForRoot(tree.root.thread, { mode: prefs.organizationMode, projects: sidebar.projects });
|
|
425
456
|
}, [activeThreadId, forest, prefs.organizationMode, sidebar.projects]);
|
|
426
|
-
const showArchived = prefs.
|
|
457
|
+
const showArchived = prefs.showArchived;
|
|
427
458
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
const {
|
|
432
|
-
|
|
433
|
-
const families = group.rootIds.flatMap((id) => forest!.familyOf.get(id) ?? []);
|
|
434
|
-
const plan = markAllReadPlan(families, {
|
|
435
|
-
activeThreadId,
|
|
436
|
-
finishedAt: stamps.finishedAt,
|
|
437
|
-
seenAt: stamps.seenAt,
|
|
438
|
-
});
|
|
459
|
+
/** Marks every unread thread in the trees read, asking first above MARK_ALL_CONFIRM_ABOVE. `where` names them. */
|
|
460
|
+
const markTreesRead = useCallback(
|
|
461
|
+
(trees: readonly ThreadTree[], where: string) => {
|
|
462
|
+
const { activeThreadId, stamps } = latest.current;
|
|
463
|
+
const plan = markAllReadPlan(trees, { activeThreadId, finishedAt: stamps.finishedAt, seenAt: stamps.seenAt });
|
|
439
464
|
const run = () => {
|
|
440
465
|
if (plan.seen.length > 0) markSeen(plan.seen);
|
|
441
466
|
for (const id of plan.read) actions.setRead(id, true).catch(() => undefined);
|
|
442
467
|
};
|
|
443
468
|
if (plan.read.length === 0) {
|
|
444
|
-
toast(`Nothing unread in ${
|
|
469
|
+
toast(`Nothing unread in ${where}`);
|
|
445
470
|
} else if (plan.read.length > MARK_ALL_CONFIRM_ABOVE) {
|
|
446
471
|
setConfirm({
|
|
447
472
|
title: `Mark ${plan.read.length} threads read?`,
|
|
448
|
-
description: `Every unread thread in ${
|
|
473
|
+
description: `Every unread thread in ${where}, child threads included, will be marked read.`,
|
|
449
474
|
confirmLabel: "Mark all read",
|
|
450
475
|
run,
|
|
451
476
|
});
|
|
452
477
|
} else {
|
|
453
478
|
run();
|
|
454
479
|
}
|
|
455
|
-
}
|
|
480
|
+
},
|
|
481
|
+
[actions, markSeen],
|
|
482
|
+
);
|
|
483
|
+
const onMarkListRead = useCallback(() => markTreesRead(latest.current.forest?.trees ?? [], "the list"), [markTreesRead]);
|
|
484
|
+
const onToggleNeedYou = useCallback(() => setNeedYouOn((on) => !on), []);
|
|
485
|
+
|
|
486
|
+
const groupController: GroupController | null = useMemo(() => {
|
|
487
|
+
if (!built) return null;
|
|
488
|
+
// Folded roots count too: every tree bucketed in the group.
|
|
489
|
+
const markAll = (group: GroupView) =>
|
|
490
|
+
markTreesRead(
|
|
491
|
+
group.rootIds.flatMap((id) => latest.current.forest!.treeOf.get(id) ?? []),
|
|
492
|
+
group.descriptor.label,
|
|
493
|
+
);
|
|
456
494
|
return {
|
|
457
495
|
compact: isCompactViewport,
|
|
458
496
|
activeGroupId,
|
|
459
497
|
showArchived,
|
|
460
|
-
onToggleArchived: () =>
|
|
461
|
-
update({
|
|
462
|
-
threadLifecycles: latest.current.prefs.threadLifecycles.includes("archived") ? ["active"] : ["active", "archived"],
|
|
463
|
-
}),
|
|
498
|
+
onToggleArchived: () => update({ showArchived: !latest.current.prefs.showArchived }),
|
|
464
499
|
canCreateSections: prefs.organizationMode === "chronological",
|
|
465
500
|
onToggleCollapse: (group) => {
|
|
466
501
|
const { prefs, forest } = latest.current;
|
|
@@ -501,7 +536,7 @@ function ThreadListBody({
|
|
|
501
536
|
onMarkAllRead: markAll,
|
|
502
537
|
onNewSection: () => setNewSectionOpen(true),
|
|
503
538
|
};
|
|
504
|
-
}, [built, isCompactViewport, activeGroupId, showArchived, prefs.organizationMode, applyToggle, actions, onNavigate, update, sdk,
|
|
539
|
+
}, [built, isCompactViewport, activeGroupId, showArchived, prefs.organizationMode, applyToggle, actions, onNavigate, update, sdk, markTreesRead]);
|
|
505
540
|
|
|
506
541
|
const sensors = useSensors(
|
|
507
542
|
useSensor(MouseSensor, MOUSE_SENSOR),
|
|
@@ -523,8 +558,7 @@ function ThreadListBody({
|
|
|
523
558
|
}, []);
|
|
524
559
|
|
|
525
560
|
const dropContext = useMemo(() => {
|
|
526
|
-
|
|
527
|
-
const groupOfThread = new Map<string, string>(Object.entries(view?.attention?.homeGroupIds ?? {}));
|
|
561
|
+
const groupOfThread = new Map<string, string>();
|
|
528
562
|
for (const group of [...(view?.groups ?? []), ...(view?.more ?? [])]) {
|
|
529
563
|
for (const row of group.rows) if (row.type === "thread") groupOfThread.set(row.info.thread.id, group.descriptor.id);
|
|
530
564
|
}
|
|
@@ -678,23 +712,23 @@ function ThreadListBody({
|
|
|
678
712
|
return (
|
|
679
713
|
<ListLiveContext.Provider value={live}>
|
|
680
714
|
<div className="flex w-full min-w-0 flex-col px-1.5 pb-2">
|
|
715
|
+
<ListHeader
|
|
716
|
+
mode={prefs.organizationMode}
|
|
717
|
+
needYouCount={view.needYouCount}
|
|
718
|
+
needYouOnly={needYouOn}
|
|
719
|
+
onToggleNeedYou={onToggleNeedYou}
|
|
720
|
+
onMarkAllRead={onMarkListRead}
|
|
721
|
+
prefs={prefs}
|
|
722
|
+
client={client}
|
|
723
|
+
onPrefs={update}
|
|
724
|
+
onClient={updateClient}
|
|
725
|
+
/>
|
|
726
|
+
<PullRequestProbes threadIds={pullRequestLookups} onAnswer={onPullRequest} />
|
|
681
727
|
{threads.length === 0 ? (
|
|
682
728
|
// bb's own pinned New thread button covers the empty list.
|
|
683
729
|
<p className="px-3 py-4 text-sm text-muted-foreground">No threads yet.</p>
|
|
684
730
|
) : (
|
|
685
731
|
<DndContext sensors={sensors} collisionDetection={collision} onDragMove={onDragMove} onDragEnd={onDragEnd} onDragCancel={onDragCancel}>
|
|
686
|
-
{view.attention !== null ? (
|
|
687
|
-
<AttentionSection
|
|
688
|
-
view={view.attention}
|
|
689
|
-
rowController={rowController}
|
|
690
|
-
environmentProviders={environmentProviders}
|
|
691
|
-
dropStates={dropStates}
|
|
692
|
-
activeThreadId={activeThreadId}
|
|
693
|
-
editingId={editingId}
|
|
694
|
-
now={now}
|
|
695
|
-
stamps={stamps}
|
|
696
|
-
/>
|
|
697
|
-
) : null}
|
|
698
732
|
{view.groups.map((group) => (
|
|
699
733
|
<GroupSection
|
|
700
734
|
key={group.descriptor.id}
|
|
@@ -822,7 +856,7 @@ function ThreadListBody({
|
|
|
822
856
|
<ThreadDetails
|
|
823
857
|
info={details}
|
|
824
858
|
controller={rowController}
|
|
825
|
-
showPullRequest
|
|
859
|
+
showPullRequest
|
|
826
860
|
actions={{
|
|
827
861
|
open: () => {
|
|
828
862
|
setDetailsId(null);
|