@gperezmz/bb-plugin-thread-glance 0.1.3 → 0.2.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.
- package/README.md +2 -2
- package/dist/app.css +1 -1
- package/dist/app.js +32 -32
- package/dist/app.meta.json +1 -1
- package/dist/server.js +8 -8
- package/dist/server.js.map +3 -3
- package/dist/server.meta.json +1 -1
- package/features/thread-list/components/FoldRows.tsx +14 -13
- package/features/thread-list/components/GroupSection.tsx +118 -69
- package/features/thread-list/components/Rails.tsx +2 -3
- package/features/thread-list/components/ThreadList.tsx +21 -16
- package/features/thread-list/components/ThreadRowView.tsx +12 -9
- package/features/thread-list/components/Toolbar.tsx +129 -187
- package/features/thread-list/components/controller.ts +1 -2
- package/features/thread-list/components/glyphs.tsx +1 -1
- package/features/thread-list/data/useClientPreferences.ts +1 -1
- package/features/thread-list/data/useNeedsYouHold.ts +19 -0
- package/features/thread-list/model/counters.ts +13 -19
- package/features/thread-list/model/details.ts +2 -2
- package/features/thread-list/model/expansion.ts +10 -10
- package/features/thread-list/model/families.ts +50 -48
- package/features/thread-list/model/labels.ts +2 -1
- package/features/thread-list/model/layout.ts +6 -11
- package/features/thread-list/model/needs-you.ts +133 -0
- package/features/thread-list/model/notes.ts +1 -1
- package/features/thread-list/model/settings.ts +54 -0
- package/features/thread-list/model/share.ts +7 -1
- package/features/thread-list/model/sort.ts +14 -6
- package/features/thread-list/model/state.ts +11 -11
- package/features/thread-list/model/time.ts +1 -1
- package/features/thread-list/model/toggles.ts +2 -7
- package/features/thread-list/model/view.ts +139 -143
- package/features/thread-list/testing/fixtures.ts +19 -10
- package/package.json +1 -1
- package/server/cli.ts +2 -2
- package/shared/preferences.ts +10 -23
- package/skills/thread-glance/SKILL.md +9 -10
- package/features/thread-list/model/attention.ts +0 -89
package/dist/server.meta.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// The fold rows (`N older` on a group, `N more child threads` on a family)
|
|
2
|
-
// and the environment folder row.
|
|
1
|
+
// The fold rows (`N older` on a group, `N more child threads` on a family),
|
|
2
|
+
// the "+N more" line in Needs you, and the environment folder row.
|
|
3
3
|
import { memo, useState } from "react";
|
|
4
4
|
import { experimental_Icon as Icon, experimental_ProviderIcon as ProviderIcon } from "@get-bb/plugin-sdk/app";
|
|
5
5
|
import type { PluginEnvironmentProvider } from "@get-bb/plugin-sdk/app";
|
|
@@ -14,7 +14,7 @@ import { ICONS } from "../icons";
|
|
|
14
14
|
import { RowRails } from "./Rails";
|
|
15
15
|
import { olderRowText } from "../model/labels";
|
|
16
16
|
import { rowIndent } from "../model/layout";
|
|
17
|
-
import
|
|
17
|
+
import type { EnvironmentRow, LeftOutRow, OlderRow } from "../model/view";
|
|
18
18
|
import type { RowController } from "./controller";
|
|
19
19
|
import { FlagGlyph } from "./glyphs";
|
|
20
20
|
import { RenameEditor } from "./RenameEditor";
|
|
@@ -35,9 +35,9 @@ export const OlderRowView = memo(function OlderRowView({ row, controller }: { ro
|
|
|
35
35
|
"relative flex w-full items-center rounded-md pr-2 text-left text-xs text-muted-foreground outline-none hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 focus-visible:ring-sidebar-ring",
|
|
36
36
|
inFamily ? "h-7 gap-1.5 max-md:pointer-coarse:h-9" : "h-6 gap-1",
|
|
37
37
|
)}
|
|
38
|
-
style={{ paddingLeft: rowIndent(row.depth
|
|
38
|
+
style={{ paddingLeft: rowIndent(row.depth) + (inFamily ? 0 : 2) }}
|
|
39
39
|
>
|
|
40
|
-
<RowRails rails={row.rails}
|
|
40
|
+
<RowRails rails={row.rails} />
|
|
41
41
|
{inFamily ? (
|
|
42
42
|
<span aria-hidden className="inline-flex size-4 shrink-0 items-center justify-center">
|
|
43
43
|
<Icon name={ICONS.more} className="size-3.5" />
|
|
@@ -50,16 +50,17 @@ export const OlderRowView = memo(function OlderRowView({ row, controller }: { ro
|
|
|
50
50
|
);
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
-
/**
|
|
54
|
-
export const
|
|
53
|
+
/** The "+N more" line under a family in Needs you: a count, not a control. */
|
|
54
|
+
export const LeftOutRowView = memo(function LeftOutRowView({ row }: { row: LeftOutRow }) {
|
|
55
|
+
const noun = row.count === 1 ? "child thread" : "child threads";
|
|
55
56
|
return (
|
|
56
57
|
<div
|
|
57
|
-
className="relative flex h-
|
|
58
|
-
style={{ paddingLeft: rowIndent(row.depth
|
|
58
|
+
className="relative flex h-6 w-full items-center gap-1.5 pr-2 text-xs text-muted-foreground"
|
|
59
|
+
style={{ paddingLeft: rowIndent(row.depth) }}
|
|
59
60
|
>
|
|
60
|
-
<RowRails rails={row.rails}
|
|
61
|
+
<RowRails rails={row.rails} />
|
|
61
62
|
<span aria-hidden className="size-4 shrink-0" />
|
|
62
|
-
<span
|
|
63
|
+
<span title={`${row.count} more ${noun}`}>+{row.count} more</span>
|
|
63
64
|
</div>
|
|
64
65
|
);
|
|
65
66
|
});
|
|
@@ -81,9 +82,9 @@ export const EnvironmentRowView = memo(function EnvironmentRowView({
|
|
|
81
82
|
return (
|
|
82
83
|
<div
|
|
83
84
|
className="group/row relative flex h-7 w-full items-center gap-1.5 rounded-md pr-1 text-sm text-muted-foreground hover:bg-sidebar-accent"
|
|
84
|
-
style={{ paddingLeft: rowIndent(row.depth
|
|
85
|
+
style={{ paddingLeft: rowIndent(row.depth) }}
|
|
85
86
|
>
|
|
86
|
-
<RowRails rails={row.rails}
|
|
87
|
+
<RowRails rails={row.rails} />
|
|
87
88
|
<button
|
|
88
89
|
type="button"
|
|
89
90
|
aria-expanded={!row.collapsed}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// A top-level group: its header with counters, and its rows, windowed
|
|
2
|
-
// in chunks.
|
|
2
|
+
// in chunks; and the Needs you section above every group.
|
|
3
3
|
import { memo, useEffect, useRef, useState } from "react";
|
|
4
4
|
import { useDraggable, useDroppable } from "@dnd-kit/core";
|
|
5
5
|
import { experimental_Icon as Icon } from "@get-bb/plugin-sdk/app";
|
|
@@ -14,10 +14,10 @@ import {
|
|
|
14
14
|
} from "@/components/ui/dropdown-menu";
|
|
15
15
|
import { cn } from "@/lib/utils";
|
|
16
16
|
import { ICONS } from "../icons";
|
|
17
|
-
import type
|
|
17
|
+
import { NEEDS_YOU_GROUP_ID, type GroupView, type NeedsYouView, type Row } from "../model/view";
|
|
18
18
|
import { chunk, windowedNavValue } from "../model/windowing";
|
|
19
19
|
import type { RowController } from "./controller";
|
|
20
|
-
import {
|
|
20
|
+
import { EnvironmentRowView, LeftOutRowView, OlderRowView } from "./FoldRows";
|
|
21
21
|
import { CounterStrip } from "./glyphs";
|
|
22
22
|
import { visibleCounters } from "../model/counters";
|
|
23
23
|
import { RenameEditor } from "./RenameEditor";
|
|
@@ -44,8 +44,7 @@ export interface GroupController {
|
|
|
44
44
|
export type DropStates = ReadonlyMap<string, "valid" | "blocked" | "unchanged" | "before" | "after">;
|
|
45
45
|
|
|
46
46
|
/** The sticky toolbar's height; headers stick below it. */
|
|
47
|
-
export const TOOLBAR_HEIGHT =
|
|
48
|
-
const HEADER_HEIGHT = 28;
|
|
47
|
+
export const TOOLBAR_HEIGHT = 28;
|
|
49
48
|
const ROW_HEIGHT = { compact: 28, comfortable: 44 };
|
|
50
49
|
|
|
51
50
|
function canRename(group: GroupView): boolean {
|
|
@@ -267,38 +266,107 @@ function WindowedChunk({
|
|
|
267
266
|
);
|
|
268
267
|
}
|
|
269
268
|
|
|
270
|
-
|
|
271
|
-
|
|
269
|
+
interface RowsProps {
|
|
270
|
+
rows: readonly Row[];
|
|
271
|
+
/** The group the rows are drawn in, for drag ids. */
|
|
272
|
+
groupId: string;
|
|
273
|
+
inPinned: boolean;
|
|
274
|
+
/** Mount every chunk, whatever is on screen (the More popover). */
|
|
275
|
+
forceMount: boolean;
|
|
276
|
+
rowController: RowController;
|
|
277
|
+
environmentProviders: readonly PluginEnvironmentProvider[];
|
|
278
|
+
dropStates: DropStates;
|
|
279
|
+
activeThreadId: string | null;
|
|
280
|
+
editingId: string | null;
|
|
281
|
+
now: number;
|
|
282
|
+
/** Rows get their own stamps as props, so a stamp renders one row. */
|
|
283
|
+
stamps: Stamps;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** A group's rows, windowed in chunks. */
|
|
287
|
+
function Rows({
|
|
288
|
+
rows: all,
|
|
289
|
+
groupId,
|
|
290
|
+
inPinned,
|
|
291
|
+
forceMount,
|
|
272
292
|
rowController,
|
|
273
|
-
groupController,
|
|
274
293
|
environmentProviders,
|
|
275
294
|
dropStates,
|
|
276
|
-
dropTargetGroupId,
|
|
277
295
|
activeThreadId,
|
|
278
296
|
editingId,
|
|
279
297
|
now,
|
|
280
298
|
stamps,
|
|
299
|
+
}: RowsProps) {
|
|
300
|
+
const rowHeight = rowController.comfortable ? ROW_HEIGHT.comfortable : ROW_HEIGHT.compact;
|
|
301
|
+
return (
|
|
302
|
+
<div data-sidebar="group-content" className="flex w-full flex-col text-sm">
|
|
303
|
+
{chunk(all).map((rows) => (
|
|
304
|
+
<WindowedChunk
|
|
305
|
+
key={rowKey(rows[0]!)}
|
|
306
|
+
rows={rows}
|
|
307
|
+
rowHeight={rowHeight}
|
|
308
|
+
forceMount={
|
|
309
|
+
forceMount ||
|
|
310
|
+
rows.some(
|
|
311
|
+
(row) =>
|
|
312
|
+
row.type === "thread" &&
|
|
313
|
+
(row.info.thread.id === activeThreadId || row.info.thread.id === editingId),
|
|
314
|
+
)
|
|
315
|
+
}
|
|
316
|
+
>
|
|
317
|
+
{rows.map((row) =>
|
|
318
|
+
row.type === "thread" ? (
|
|
319
|
+
<ThreadRowView
|
|
320
|
+
key={row.key}
|
|
321
|
+
row={row}
|
|
322
|
+
controller={rowController}
|
|
323
|
+
groupId={groupId}
|
|
324
|
+
inPinned={inPinned}
|
|
325
|
+
dropState={dropStates.get(row.info.thread.id) ?? null}
|
|
326
|
+
active={row.info.thread.id === activeThreadId}
|
|
327
|
+
editing={row.info.thread.id === editingId}
|
|
328
|
+
now={now}
|
|
329
|
+
startedAt={stamps.startedAt[row.info.thread.id]}
|
|
330
|
+
finishedAt={stamps.finishedAt[row.info.thread.id]}
|
|
331
|
+
pendingAt={stamps.pendingAt[row.info.thread.id]}
|
|
332
|
+
/>
|
|
333
|
+
) : row.type === "older" ? (
|
|
334
|
+
<OlderRowView key={row.key} row={row} controller={rowController} />
|
|
335
|
+
) : row.type === "left-out" ? (
|
|
336
|
+
<LeftOutRowView key={row.key} row={row} />
|
|
337
|
+
) : (
|
|
338
|
+
<EnvironmentRowView
|
|
339
|
+
key={row.key}
|
|
340
|
+
row={row}
|
|
341
|
+
controller={rowController}
|
|
342
|
+
environmentProviders={environmentProviders}
|
|
343
|
+
/>
|
|
344
|
+
),
|
|
345
|
+
)}
|
|
346
|
+
</WindowedChunk>
|
|
347
|
+
))}
|
|
348
|
+
</div>
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
type SectionProps = Omit<RowsProps, "rows" | "groupId" | "inPinned" | "forceMount">;
|
|
353
|
+
|
|
354
|
+
export const GroupSection = memo(function GroupSection({
|
|
355
|
+
group,
|
|
356
|
+
groupController,
|
|
357
|
+
dropTargetGroupId,
|
|
281
358
|
inOverflow = false,
|
|
282
|
-
|
|
359
|
+
...rest
|
|
360
|
+
}: SectionProps & {
|
|
283
361
|
group: GroupView;
|
|
284
|
-
rowController: RowController;
|
|
285
362
|
groupController: GroupController;
|
|
286
|
-
environmentProviders: readonly PluginEnvironmentProvider[];
|
|
287
|
-
dropStates: DropStates;
|
|
288
363
|
dropTargetGroupId: string | null;
|
|
289
|
-
activeThreadId: string | null;
|
|
290
|
-
editingId: string | null;
|
|
291
|
-
now: number;
|
|
292
|
-
/** Rows get their own stamps as props, so a stamp renders one row. */
|
|
293
|
-
stamps: Stamps;
|
|
294
364
|
inOverflow?: boolean;
|
|
295
365
|
}) {
|
|
296
366
|
const droppable = useDroppable({
|
|
297
367
|
id: `drop-group:${group.descriptor.id}`,
|
|
298
368
|
data: { kind: "group", groupId: group.descriptor.id },
|
|
299
369
|
});
|
|
300
|
-
const rowHeight = rowController.comfortable ? ROW_HEIGHT.comfortable : ROW_HEIGHT.compact;
|
|
301
|
-
const inPinned = group.descriptor.id === "pinned";
|
|
302
370
|
return (
|
|
303
371
|
<section
|
|
304
372
|
ref={droppable.setNodeRef}
|
|
@@ -314,57 +382,38 @@ export const GroupSection = memo(function GroupSection({
|
|
|
314
382
|
dropActive={dropTargetGroupId === group.descriptor.id}
|
|
315
383
|
/>
|
|
316
384
|
{group.collapsed ? null : group.rows.length === 0 ? (
|
|
317
|
-
|
|
385
|
+
// A group whose every family sits in Needs you draws only its header.
|
|
386
|
+
group.rootIds.length === 0 ? <p className="py-1 pl-8 text-xs text-muted-foreground">No threads</p> : null
|
|
318
387
|
) : (
|
|
319
|
-
<
|
|
320
|
-
{
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
inOverflow ||
|
|
327
|
-
rows.some(
|
|
328
|
-
(row) =>
|
|
329
|
-
row.type === "thread" &&
|
|
330
|
-
(row.info.thread.id === activeThreadId || row.info.thread.id === editingId),
|
|
331
|
-
)
|
|
332
|
-
}
|
|
333
|
-
>
|
|
334
|
-
{rows.map((row) =>
|
|
335
|
-
row.type === "thread" ? (
|
|
336
|
-
<ThreadRowView
|
|
337
|
-
key={row.key}
|
|
338
|
-
row={row}
|
|
339
|
-
controller={rowController}
|
|
340
|
-
groupId={group.descriptor.id}
|
|
341
|
-
inPinned={inPinned}
|
|
342
|
-
dropState={dropStates.get(row.info.thread.id) ?? null}
|
|
343
|
-
stickyTop={row.stickyLevel !== null ? TOOLBAR_HEIGHT + HEADER_HEIGHT + row.stickyLevel * rowHeight : null}
|
|
344
|
-
active={row.info.thread.id === activeThreadId}
|
|
345
|
-
editing={row.info.thread.id === editingId}
|
|
346
|
-
now={now}
|
|
347
|
-
startedAt={stamps.startedAt[row.info.thread.id]}
|
|
348
|
-
finishedAt={stamps.finishedAt[row.info.thread.id]}
|
|
349
|
-
pendingAt={stamps.pendingAt[row.info.thread.id]}
|
|
350
|
-
/>
|
|
351
|
-
) : row.type === "older" ? (
|
|
352
|
-
<OlderRowView key={row.key} row={row} controller={rowController} />
|
|
353
|
-
) : row.type === "empty" ? (
|
|
354
|
-
<EmptyRowView key={row.key} row={row} controller={rowController} />
|
|
355
|
-
) : (
|
|
356
|
-
<EnvironmentRowView
|
|
357
|
-
key={row.key}
|
|
358
|
-
row={row}
|
|
359
|
-
controller={rowController}
|
|
360
|
-
environmentProviders={environmentProviders}
|
|
361
|
-
/>
|
|
362
|
-
),
|
|
363
|
-
)}
|
|
364
|
-
</WindowedChunk>
|
|
365
|
-
))}
|
|
366
|
-
</div>
|
|
388
|
+
<Rows
|
|
389
|
+
{...rest}
|
|
390
|
+
rows={group.rows}
|
|
391
|
+
groupId={group.descriptor.id}
|
|
392
|
+
inPinned={group.descriptor.id === "pinned"}
|
|
393
|
+
forceMount={inOverflow}
|
|
394
|
+
/>
|
|
367
395
|
)}
|
|
368
396
|
</section>
|
|
369
397
|
);
|
|
370
398
|
});
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* The Needs you section: a header with its family count, then its rows. It
|
|
402
|
+
* has no collapse, menu or drag, and no drop target of its own.
|
|
403
|
+
*/
|
|
404
|
+
export const NeedsYouSection = memo(function NeedsYouSection({ view, ...rest }: SectionProps & { view: NeedsYouView }) {
|
|
405
|
+
return (
|
|
406
|
+
<section aria-label="Needs you" className="relative flex w-full min-w-0 flex-col">
|
|
407
|
+
<h2
|
|
408
|
+
style={{ top: TOOLBAR_HEIGHT }}
|
|
409
|
+
className="sticky z-20 flex h-7 items-center gap-1 bg-sidebar pl-2 pr-2 text-xs font-medium text-muted-foreground max-md:pointer-coarse:h-9"
|
|
410
|
+
>
|
|
411
|
+
<span className="min-w-0 flex-1 truncate">Needs you</span>
|
|
412
|
+
<span className="tabular-nums" aria-label={`${view.familyCount} ${view.familyCount === 1 ? "family" : "families"}`}>
|
|
413
|
+
{view.familyCount}
|
|
414
|
+
</span>
|
|
415
|
+
</h2>
|
|
416
|
+
<Rows {...rest} rows={view.rows} groupId={NEEDS_YOU_GROUP_ID} inPinned={false} forceMount={false} />
|
|
417
|
+
</section>
|
|
418
|
+
);
|
|
419
|
+
});
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// The guide rails of a nested row: one hairline per nesting level,
|
|
2
2
|
// drawn inside the row so they join up from row to row.
|
|
3
|
-
import type { Nesting } from "@/shared/preferences";
|
|
4
3
|
import { railLeft, type Rail } from "../model/layout";
|
|
5
4
|
|
|
6
5
|
// `start` begins just under the 16px status slot, which is centred in the row.
|
|
@@ -10,7 +9,7 @@ const SPAN: Record<Rail, { top: string; bottom: string }> = {
|
|
|
10
9
|
end: { top: "0px", bottom: "50%" },
|
|
11
10
|
};
|
|
12
11
|
|
|
13
|
-
export function RowRails({ rails
|
|
12
|
+
export function RowRails({ rails }: { rails: readonly (Rail | null)[] }) {
|
|
14
13
|
if (rails.every((rail) => rail === null)) return null;
|
|
15
14
|
return (
|
|
16
15
|
<span aria-hidden data-rails="" className="pointer-events-none absolute inset-0">
|
|
@@ -19,7 +18,7 @@ export function RowRails({ rails, nesting }: { rails: readonly (Rail | null)[];
|
|
|
19
18
|
<span
|
|
20
19
|
key={level}
|
|
21
20
|
className="absolute w-px bg-border"
|
|
22
|
-
style={{ left: railLeft(level
|
|
21
|
+
style={{ left: railLeft(level), ...SPAN[rail] }}
|
|
23
22
|
/>
|
|
24
23
|
),
|
|
25
24
|
)}
|
|
@@ -29,6 +29,7 @@ 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 { useNeedsYouHold } from "../data/useNeedsYouHold";
|
|
32
33
|
import { useNow } from "../data/useNow";
|
|
33
34
|
import { usePreferences } from "../data/usePreferences";
|
|
34
35
|
import { useScheduled } from "../data/useScheduled";
|
|
@@ -50,7 +51,7 @@ import { modelDisplayName } from "../model/details";
|
|
|
50
51
|
import { groupIdForRoot } from "../model/groups";
|
|
51
52
|
import { CounterStrip } from "./glyphs";
|
|
52
53
|
import { cancelPendingCards } from "./row-card";
|
|
53
|
-
import { GroupSection, type DropStates, type GroupController } from "./GroupSection";
|
|
54
|
+
import { GroupSection, NeedsYouSection, type DropStates, type GroupController } from "./GroupSection";
|
|
54
55
|
import type { ProviderDisplay } from "./ProviderBadge";
|
|
55
56
|
import { ThreadDetails } from "./ThreadDetails";
|
|
56
57
|
import { Toolbar } from "./Toolbar";
|
|
@@ -159,6 +160,7 @@ function ThreadListBody({
|
|
|
159
160
|
[ready, threads, activeThreadId, stamps.finishedAt, stamps.seenAt, draftIds, scheduled, now, notes, prefs.childAttention],
|
|
160
161
|
);
|
|
161
162
|
const { targets, prune } = useAutoExpand(hydrated ? forest : null, activeThreadId);
|
|
163
|
+
const heldRootId = useNeedsYouHold(forest, activeThreadId);
|
|
162
164
|
// Rows and groups that did not change keep their objects, so their
|
|
163
165
|
// memoized components skip the render.
|
|
164
166
|
const previousView = useRef<ListView | null>(null);
|
|
@@ -172,11 +174,11 @@ function ThreadListBody({
|
|
|
172
174
|
projects: sidebar.projects,
|
|
173
175
|
sections: sidebar.sections,
|
|
174
176
|
prefs,
|
|
175
|
-
filter: client.filter,
|
|
176
177
|
activeThreadId,
|
|
178
|
+
heldRootId,
|
|
177
179
|
targets,
|
|
178
180
|
})),
|
|
179
|
-
[forest, threads, sidebar.projects, sidebar.sections, prefs,
|
|
181
|
+
[forest, threads, sidebar.projects, sidebar.sections, prefs, activeThreadId, heldRootId, targets],
|
|
180
182
|
);
|
|
181
183
|
useLayoutEffect(() => {
|
|
182
184
|
previousView.current = view;
|
|
@@ -356,7 +358,6 @@ function ThreadListBody({
|
|
|
356
358
|
provider: providerDisplay,
|
|
357
359
|
sections: sidebar.sections,
|
|
358
360
|
mode: prefs.organizationMode,
|
|
359
|
-
nesting: prefs.nesting,
|
|
360
361
|
onNavigate,
|
|
361
362
|
onToggleChip: (row) => {
|
|
362
363
|
const { prefs, forest } = latest.current;
|
|
@@ -399,7 +400,6 @@ function ThreadListBody({
|
|
|
399
400
|
prefs.showPullRequests,
|
|
400
401
|
prefs.harnessIcon,
|
|
401
402
|
prefs.organizationMode,
|
|
402
|
-
prefs.nesting,
|
|
403
403
|
defaultBranches,
|
|
404
404
|
multiHost,
|
|
405
405
|
providerDisplay,
|
|
@@ -523,7 +523,8 @@ function ThreadListBody({
|
|
|
523
523
|
}, []);
|
|
524
524
|
|
|
525
525
|
const dropContext = useMemo(() => {
|
|
526
|
-
|
|
526
|
+
// A row in Needs you drops as it would in its home group.
|
|
527
|
+
const groupOfThread = new Map<string, string>(Object.entries(view?.needsYou?.homeGroupIds ?? {}));
|
|
527
528
|
for (const group of [...(view?.groups ?? []), ...(view?.more ?? [])]) {
|
|
528
529
|
for (const row of group.rows) if (row.type === "thread") groupOfThread.set(row.info.thread.id, group.descriptor.id);
|
|
529
530
|
}
|
|
@@ -677,19 +678,23 @@ function ThreadListBody({
|
|
|
677
678
|
return (
|
|
678
679
|
<ListLiveContext.Provider value={live}>
|
|
679
680
|
<div className="flex w-full min-w-0 flex-col px-1.5 pb-2">
|
|
680
|
-
<Toolbar prefs={prefs} client={client}
|
|
681
|
+
<Toolbar prefs={prefs} client={client} onPrefs={update} onClient={updateClient} />
|
|
681
682
|
{threads.length === 0 ? (
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
<Button size="sm" variant="outline" onClick={() => actions.openNewThread({ focusPrompt: true })}>
|
|
685
|
-
<Icon name="Plus" aria-hidden />
|
|
686
|
-
New thread
|
|
687
|
-
</Button>
|
|
688
|
-
</div>
|
|
683
|
+
// bb's own pinned New thread button covers the empty list.
|
|
684
|
+
<p className="px-3 py-4 text-sm text-muted-foreground">No threads yet.</p>
|
|
689
685
|
) : (
|
|
690
686
|
<DndContext sensors={sensors} collisionDetection={collision} onDragMove={onDragMove} onDragEnd={onDragEnd} onDragCancel={onDragCancel}>
|
|
691
|
-
{view.
|
|
692
|
-
<
|
|
687
|
+
{view.needsYou !== null ? (
|
|
688
|
+
<NeedsYouSection
|
|
689
|
+
view={view.needsYou}
|
|
690
|
+
rowController={rowController}
|
|
691
|
+
environmentProviders={environmentProviders}
|
|
692
|
+
dropStates={dropStates}
|
|
693
|
+
activeThreadId={activeThreadId}
|
|
694
|
+
editingId={editingId}
|
|
695
|
+
now={now}
|
|
696
|
+
stamps={stamps}
|
|
697
|
+
/>
|
|
693
698
|
) : null}
|
|
694
699
|
{view.groups.map((group) => (
|
|
695
700
|
<GroupSection
|
|
@@ -81,7 +81,6 @@ export interface ThreadRowViewProps {
|
|
|
81
81
|
inPinned: boolean;
|
|
82
82
|
/** Drop feedback from the list's drag state. */
|
|
83
83
|
dropState: "valid" | "blocked" | "unchanged" | "before" | "after" | null;
|
|
84
|
-
stickyTop: number | null;
|
|
85
84
|
/** The open thread is this one. */
|
|
86
85
|
active: boolean;
|
|
87
86
|
/** Its title is being renamed. */
|
|
@@ -100,7 +99,6 @@ export const ThreadRowView = memo(function ThreadRowView({
|
|
|
100
99
|
groupId,
|
|
101
100
|
inPinned,
|
|
102
101
|
dropState,
|
|
103
|
-
stickyTop,
|
|
104
102
|
active: isActive,
|
|
105
103
|
editing,
|
|
106
104
|
now,
|
|
@@ -165,7 +163,7 @@ export const ThreadRowView = memo(function ThreadRowView({
|
|
|
165
163
|
finishedAt: finishedAt === undefined ? {} : { [thread.id]: finishedAt },
|
|
166
164
|
},
|
|
167
165
|
now,
|
|
168
|
-
info.state.kind === "
|
|
166
|
+
info.state.kind === "waits-on-you" ? (pendingAt ?? null) : undefined,
|
|
169
167
|
);
|
|
170
168
|
const menuItems = rowMenuItems({
|
|
171
169
|
thread,
|
|
@@ -292,7 +290,7 @@ export const ThreadRowView = memo(function ThreadRowView({
|
|
|
292
290
|
: {};
|
|
293
291
|
|
|
294
292
|
const chip = row.chip;
|
|
295
|
-
const indent = rowIndent(row.depth
|
|
293
|
+
const indent = rowIndent(row.depth);
|
|
296
294
|
const title = titleTreatment(row.depth, { unread: info.unread, active: isActive });
|
|
297
295
|
const twoLines = controller.comfortable || info.note !== null;
|
|
298
296
|
// A parent keeps full colour so its children read as the quieter ones.
|
|
@@ -342,10 +340,8 @@ export const ThreadRowView = memo(function ThreadRowView({
|
|
|
342
340
|
dropState === "blocked" && "ring-1 ring-inset ring-destructive/60",
|
|
343
341
|
dropState === "before" && "shadow-[inset_0_2px_0_0_var(--sidebar-ring)]",
|
|
344
342
|
dropState === "after" && "shadow-[inset_0_-2px_0_0_var(--sidebar-ring)]",
|
|
345
|
-
stickyTop !== null && "sticky z-10 bg-sidebar",
|
|
346
|
-
stickyTop !== null && isActive && "bg-state-active",
|
|
347
343
|
)}
|
|
348
|
-
style={{ paddingLeft: indent
|
|
344
|
+
style={{ paddingLeft: indent }}
|
|
349
345
|
>
|
|
350
346
|
<a
|
|
351
347
|
ref={anchor}
|
|
@@ -363,7 +359,7 @@ export const ThreadRowView = memo(function ThreadRowView({
|
|
|
363
359
|
}}
|
|
364
360
|
className="absolute inset-0 rounded-md outline-none focus-visible:ring-2 focus-visible:ring-sidebar-ring"
|
|
365
361
|
/>
|
|
366
|
-
<RowRails rails={row.rails}
|
|
362
|
+
<RowRails rails={row.rails} />
|
|
367
363
|
<span className="pointer-events-none relative flex size-4 shrink-0 items-center justify-center">{stateSlot}</span>
|
|
368
364
|
{row.nested ? (
|
|
369
365
|
// Tight against the title, and over the row's gap, so it adds 8px.
|
|
@@ -490,7 +486,14 @@ export const ThreadRowView = memo(function ThreadRowView({
|
|
|
490
486
|
</kbd>
|
|
491
487
|
) : (
|
|
492
488
|
<>
|
|
493
|
-
{
|
|
489
|
+
{row.homeGroupLabel !== null ? (
|
|
490
|
+
<span
|
|
491
|
+
title={`In ${row.homeGroupLabel}`}
|
|
492
|
+
className={cn("pointer-events-none max-w-24 truncate text-[11px] text-muted-foreground transition-opacity", fadeClass)}
|
|
493
|
+
>
|
|
494
|
+
{row.homeGroupLabel}
|
|
495
|
+
</span>
|
|
496
|
+
) : time !== null ? (
|
|
494
497
|
<span
|
|
495
498
|
title={time.label}
|
|
496
499
|
aria-label={time.label}
|