Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@t3tools/desktop",
"version": "0.0.42",
"version": "0.0.99-gitcache.3",
"private": true,
"type": "module",
"main": "dist-electron/main.cjs",
Expand Down
2 changes: 1 addition & 1 deletion apps/server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "t3",
"version": "0.0.42",
"version": "0.0.99-gitcache.3",
"license": "MIT",
"repository": {
"type": "git",
Expand Down
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@t3tools/web",
"version": "0.0.42",
"version": "0.0.99-gitcache.3",
"private": true,
"type": "module",
"scripts": {
Expand Down
14 changes: 14 additions & 0 deletions apps/web/src/components/Sidebar.logic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
planPinnedReorder,
planSidebarThreadDrop,
sidebarMarkerId,
isBotThreadTitle,
sidebarListItemId,
sortPinnedThreadsForSidebar,
sortThreadsForSidebar,
Expand Down Expand Up @@ -353,6 +354,19 @@ describe("hasUnseenCompletion", () => {
});
});

describe("isBotThreadTitle", () => {
it("matches t3-dispatch titles", () => {
expect(isBotThreadTitle("bot-claude-code--fix sidebar")).toBe(true);
expect(isBotThreadTitle("bot-codex--task")).toBe(true);
});

it("ignores titles that only mention bots", () => {
expect(isBotThreadTitle("bot-claude-code fix")).toBe(false);
expect(isBotThreadTitle("Fix bot--prefix parsing")).toBe(false);
expect(isBotThreadTitle("robot-arm--calibration")).toBe(false);
});
});

describe("shouldRecedeSidebarThread", () => {
it.each(["working", "monitoring"] as const)(
"recedes an inactive %s thread even when it is unread and woke",
Expand Down
7 changes: 7 additions & 0 deletions apps/web/src/components/Sidebar.logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,13 @@ export type SidebarThreadStatus =
| "failed"
| "ready";

// Threads created by t3-dispatch carry a `bot-<caller>--` title prefix.
const BOT_THREAD_TITLE_PATTERN = /^bot-[a-z0-9]+(?:-[a-z0-9]+)*--/;

export function isBotThreadTitle(title: string): boolean {
return BOT_THREAD_TITLE_PATTERN.test(title);
}

export function shouldRecedeSidebarThread(input: {
status: SidebarThreadStatus;
isUnread: boolean;
Expand Down
33 changes: 31 additions & 2 deletions apps/web/src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ import {
shouldNavigateAfterThreadPark,
shouldRecedeSidebarThread,
resolveWorkingStartedAt,
isBotThreadTitle,
sidebarListItemId,
sidebarMarkerId,
sortLogicalProjectsForSidebar,
Expand Down Expand Up @@ -254,6 +255,7 @@ const SETTLED_TAIL_PAGE_COUNT = 25;
// Fresh keys deliberately reset both shelves to collapsed for existing users.
const SETTLED_SHELF_EXPANDED_KEY = "t3code:sidebar:settled-expanded";
const SNOOZED_SHELF_EXPANDED_KEY = "t3code:sidebar:snoozed-expanded";
const HIDE_BOT_THREADS_KEY = "t3code:sidebar:hide-bot-threads";

function compactSidebarTimeLabel(label: string): string {
if (label === "just now") return "now";
Expand Down Expand Up @@ -2520,6 +2522,18 @@ export default function Sidebar() {
override holds until all of them appear in canonical state. */
readonly assignedKeys: ReadonlyMap<string, string>;
} | null>(null);
const [hideBotThreads, setHideBotThreads] = useLocalStorage(
HIDE_BOT_THREADS_KEY,
false,
Schema.Boolean,
);
const toggleHideBotThreads = useCallback(
() => setHideBotThreads((value) => !value),
[setHideBotThreads],
);
// Only the hide filter reads the route, so navigation does not re-partition
// the list while bot threads are shown.
const botExemptThreadKey = hideBotThreads ? routeThreadKey : null;
const {
pinnedThreads,
draggableThreadKeys,
Expand All @@ -2539,7 +2553,11 @@ export default function Sidebar() {
(thread) =>
thread.archivedAt === null &&
(scopedProjectKeys === null ||
scopedProjectKeys.has(`${thread.environmentId}:${thread.projectId}`)),
scopedProjectKeys.has(`${thread.environmentId}:${thread.projectId}`)) &&
// The open thread keeps its row even when bot threads are hidden.
(!hideBotThreads ||
!isBotThreadTitle(thread.title) ||
scopedThreadKey(scopeThreadRef(thread.environmentId, thread.id)) === botExemptThreadKey),
);
const pinned: EnvironmentThreadShell[] = [];
const active: EnvironmentThreadShell[] = [];
Expand Down Expand Up @@ -2625,7 +2643,16 @@ export default function Sidebar() {
settledThreads: sortSettledThreadsForSidebar(settled),
snoozeNow: preciseNow,
};
}, [nowMinute, optimisticDrop, scopedProjectKeys, serverConfigs, snoozeWakeTick, threads]);
}, [
botExemptThreadKey,
hideBotThreads,
nowMinute,
optimisticDrop,
scopedProjectKeys,
serverConfigs,
snoozeWakeTick,
threads,
]);

const threadSearchInputRef = useRef<HTMLInputElement>(null);
const [threadSearchQuery, setThreadSearchQuery] = useState("");
Expand Down Expand Up @@ -4414,6 +4441,8 @@ export default function Sidebar() {
<SidebarThreadHeader
searchFieldRef={headerSearchRef}
hasProjects={projectGroups.length > 0}
hideBotThreads={hideBotThreads}
onToggleHideBotThreads={toggleHideBotThreads}
projectScope={
<Combobox
items={projectScopeItems}
Expand Down
22 changes: 21 additions & 1 deletion apps/web/src/components/sidebar/SidebarThreadHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,14 @@
* of the sidebar's scope logic. `searchFieldRef` lands on the search field so
* the picker's popup can anchor to that width rather than to its 28px trigger.
*/
import { FolderPlusIcon, SearchIcon, SquarePenIcon, XIcon } from "lucide-react";
import {
BotIcon,
BotOffIcon,
FolderPlusIcon,
SearchIcon,
SquarePenIcon,
XIcon,
} from "lucide-react";
import {
type ComponentProps,
type KeyboardEvent as ReactKeyboardEvent,
Expand All @@ -31,6 +38,9 @@ export interface SidebarThreadHeaderProps {
hasProjects: boolean;
/** The project scope combobox, rendered as the first icon of the group. */
projectScope: ReactNode;
/** Hides threads titled `bot-<caller>--...` (created by t3-dispatch). */
hideBotThreads: boolean;
onToggleHideBotThreads: () => void;
onNewProject: () => void;
/** Receives the click so Shift+click can skip the project picker. */
onNewThread: (event: ReactMouseEvent) => void;
Expand All @@ -53,6 +63,8 @@ export function SidebarThreadHeader({
searchFieldRef,
hasProjects,
projectScope,
hideBotThreads,
onToggleHideBotThreads,
onNewProject,
onNewThread,
newThreadDisabled,
Expand Down Expand Up @@ -126,6 +138,14 @@ export function SidebarThreadHeader({
<div className="flex shrink-0 items-center">
{hasProjects ? (
<>
<SidebarHeaderIconButton
label="Hide bot threads"
tooltip={hideBotThreads ? "Show bot threads" : "Hide bot threads"}
aria-pressed={hideBotThreads}
onClick={onToggleHideBotThreads}
>
{hideBotThreads ? <BotOffIcon /> : <BotIcon />}
</SidebarHeaderIconButton>
{projectScope}
<SidebarHeaderIconButton label="New project" onClick={onNewProject}>
<FolderPlusIcon />
Expand Down
2 changes: 1 addition & 1 deletion packages/contracts/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@t3tools/contracts",
"version": "0.0.42",
"version": "0.0.99-gitcache.3",
"private": true,
"files": [
"dist"
Expand Down
Loading