diff --git a/ui/src/components/IssueFiltersPopover.test.tsx b/ui/src/components/IssueFiltersPopover.test.tsx
index d09c48c9406..74a40304e4f 100644
--- a/ui/src/components/IssueFiltersPopover.test.tsx
+++ b/ui/src/components/IssueFiltersPopover.test.tsx
@@ -79,5 +79,6 @@ describe("IssueFiltersPopover", () => {
element.className.includes("md:grid-cols-3"),
);
expect(layoutGrid?.className).toContain("grid-cols-1");
+ expect(popoverContent?.textContent).toContain("Live runs only");
});
});
diff --git a/ui/src/components/IssueFiltersPopover.tsx b/ui/src/components/IssueFiltersPopover.tsx
index 2702ea0c7e5..19a844ad2c3 100644
--- a/ui/src/components/IssueFiltersPopover.tsx
+++ b/ui/src/components/IssueFiltersPopover.tsx
@@ -344,9 +344,16 @@ export function IssueFiltersPopover({
) : null}
- {enableRoutineVisibilityFilter ? (
-
-
Visibility
+
+ Visibility
+
+ {enableRoutineVisibilityFilter ? (
-
- ) : null}
+ ) : null}
+
diff --git a/ui/src/components/IssuesList.tsx b/ui/src/components/IssuesList.tsx
index 7edbee6df94..01d2479ea49 100644
--- a/ui/src/components/IssuesList.tsx
+++ b/ui/src/components/IssuesList.tsx
@@ -23,6 +23,7 @@ import {
issuePriorityOrder,
normalizeIssueFilterState,
resolveIssueFilterWorkspaceId,
+ shouldIncludeIssueFilterWorkspaceOption,
issueStatusOrder,
type IssueFilterState,
} from "../lib/issue-filters";
@@ -483,6 +484,10 @@ export function IssuesList({
}
return map;
}, [projects]);
+ const defaultProjectWorkspaceIds = useMemo(
+ () => new Set(defaultProjectWorkspaceIdByProjectId.values()),
+ [defaultProjectWorkspaceIdByProjectId],
+ );
const executionWorkspaceById = useMemo(() => {
const map = new Map ({
+ executionWorkspaceById,
+ defaultProjectWorkspaceIdByProjectId,
+ }), [defaultProjectWorkspaceIdByProjectId, executionWorkspaceById]);
const workspaceNameMap = useMemo(() => {
const map = new Map();
for (const [workspaceId, workspace] of projectWorkspaceById) {
+ if (!shouldIncludeIssueFilterWorkspaceOption({ id: workspaceId }, defaultProjectWorkspaceIds)) continue;
map.set(workspaceId, workspace.name);
}
for (const [workspaceId, workspace] of executionWorkspaceById) {
+ if (!shouldIncludeIssueFilterWorkspaceOption({
+ id: workspaceId,
+ mode: workspace.mode,
+ projectWorkspaceId: workspace.projectWorkspaceId,
+ }, defaultProjectWorkspaceIds)) continue;
map.set(workspaceId, workspace.name);
}
return map;
- }, [executionWorkspaceById, projectWorkspaceById]);
+ }, [defaultProjectWorkspaceIds, executionWorkspaceById, projectWorkspaceById]);
const workspaceOptions = useMemo(() => {
const options = new Map();
@@ -635,9 +650,27 @@ export function IssuesList({
const searchScopedIssues = normalizedIssueSearch.length > 0 && searchWithinLoadedIssues
? sourceIssues.filter((issue) => issueMatchesLocalSearch(issue, normalizedIssueSearch))
: sourceIssues;
- const filteredByControls = applyIssueFilters(searchScopedIssues, viewState, currentUserId, enableRoutineVisibilityFilter);
+ const filteredByControls = applyIssueFilters(
+ searchScopedIssues,
+ viewState,
+ currentUserId,
+ enableRoutineVisibilityFilter,
+ liveIssueIds,
+ issueFilterWorkspaceContext,
+ );
return sortIssues(filteredByControls, viewState);
- }, [boardIssues, issues, searchedIssues, searchWithinLoadedIssues, viewState, normalizedIssueSearch, currentUserId, enableRoutineVisibilityFilter]);
+ }, [
+ boardIssues,
+ issues,
+ searchedIssues,
+ searchWithinLoadedIssues,
+ viewState,
+ normalizedIssueSearch,
+ currentUserId,
+ enableRoutineVisibilityFilter,
+ liveIssueIds,
+ issueFilterWorkspaceContext,
+ ]);
const { data: labels } = useQuery({
queryKey: queryKeys.issues.labels(selectedCompanyId!),
@@ -664,7 +697,10 @@ export function IssuesList({
.map((p) => ({ key: p, label: issueFilterLabel(p), items: groups[p]! }));
}
if (viewState.groupBy === "workspace") {
- const groups = groupBy(filtered, (issue) => resolveIssueFilterWorkspaceId(issue) ?? "__no_workspace");
+ const groups = groupBy(
+ filtered,
+ (issue) => resolveIssueFilterWorkspaceId(issue, issueFilterWorkspaceContext) ?? "__no_workspace",
+ );
return Object.keys(groups)
.sort((a, b) => {
// Groups with items first, "no workspace" last
@@ -708,7 +744,17 @@ export function IssuesList({
: (agentName(key) ?? key.slice(0, 8)),
items: groups[key]!,
}));
- }, [filtered, viewState.groupBy, agents, agentName, currentUserId, workspaceNameMap, issueTitleMap, companyUserLabelMap]);
+ }, [
+ filtered,
+ issueFilterWorkspaceContext,
+ viewState.groupBy,
+ agents,
+ agentName,
+ currentUserId,
+ workspaceNameMap,
+ issueTitleMap,
+ companyUserLabelMap,
+ ]);
useEffect(() => {
if (viewState.viewMode !== "list") return;
@@ -1125,7 +1171,7 @@ export function IssuesList({
columns={visibleTrailingIssueColumns}
projectName={issueProject?.name ?? null}
projectColor={issueProject?.color ?? null}
- workspaceId={resolveIssueFilterWorkspaceId(issue)}
+ workspaceId={resolveIssueFilterWorkspaceId(issue, issueFilterWorkspaceContext)}
workspaceName={resolveIssueWorkspaceName(issue, {
executionWorkspaceById,
projectWorkspaceById,
diff --git a/ui/src/lib/inbox.test.ts b/ui/src/lib/inbox.test.ts
index 6fef7bfbb9a..cae820df6cc 100644
--- a/ui/src/lib/inbox.test.ts
+++ b/ui/src/lib/inbox.test.ts
@@ -841,6 +841,7 @@ describe("inbox helpers", () => {
labels: [],
projects: [],
workspaces: [],
+ liveOnly: false,
hideRoutineExecutions: true,
},
}).map((issue) => issue.id),
@@ -860,6 +861,7 @@ describe("inbox helpers", () => {
labels: [],
projects: [],
workspaces: [],
+ liveOnly: false,
hideRoutineExecutions: true,
},
}),
@@ -879,6 +881,7 @@ describe("inbox helpers", () => {
labels: [],
projects: [],
workspaces: [],
+ liveOnly: false,
hideRoutineExecutions: true,
},
}),
@@ -944,6 +947,7 @@ describe("inbox helpers", () => {
labels: ["label-1"],
projects: ["project-1"],
workspaces: ["workspace-1"],
+ liveOnly: true,
hideRoutineExecutions: false,
},
});
@@ -958,6 +962,7 @@ describe("inbox helpers", () => {
labels: [],
projects: [],
workspaces: [],
+ liveOnly: false,
hideRoutineExecutions: true,
},
});
@@ -973,6 +978,7 @@ describe("inbox helpers", () => {
labels: ["label-1"],
projects: ["project-1"],
workspaces: ["workspace-1"],
+ liveOnly: true,
hideRoutineExecutions: false,
},
});
@@ -987,6 +993,7 @@ describe("inbox helpers", () => {
labels: [],
projects: [],
workspaces: [],
+ liveOnly: false,
hideRoutineExecutions: true,
},
});
@@ -1004,6 +1011,7 @@ describe("inbox helpers", () => {
labels: null,
projects: ["project-1"],
workspaces: ["workspace-1", false],
+ liveOnly: "yes",
hideRoutineExecutions: "yes",
},
}));
@@ -1019,6 +1027,7 @@ describe("inbox helpers", () => {
labels: [],
projects: ["project-1"],
workspaces: ["workspace-1"],
+ liveOnly: false,
hideRoutineExecutions: false,
},
});
diff --git a/ui/src/lib/inbox.ts b/ui/src/lib/inbox.ts
index 355b14d63de..37e0d90f7be 100644
--- a/ui/src/lib/inbox.ts
+++ b/ui/src/lib/inbox.ts
@@ -445,6 +445,7 @@ export function getInboxSearchSupplementIssues({
issueFilters,
currentUserId,
enableRoutineVisibilityFilter = false,
+ liveIssueIds,
}: {
query: string;
filteredWorkItems: InboxWorkItem[];
@@ -453,6 +454,7 @@ export function getInboxSearchSupplementIssues({
issueFilters: IssueFilterState;
currentUserId?: string | null;
enableRoutineVisibilityFilter?: boolean;
+ liveIssueIds?: ReadonlySet;
}): Issue[] {
const normalizedQuery = query.trim();
if (!normalizedQuery) return [];
@@ -462,7 +464,7 @@ export function getInboxSearchSupplementIssues({
.map((item) => item.issue.id),
...archivedSearchIssues.map((issue) => issue.id),
]);
- return applyIssueFilters(remoteIssues, issueFilters, currentUserId, enableRoutineVisibilityFilter)
+ return applyIssueFilters(remoteIssues, issueFilters, currentUserId, enableRoutineVisibilityFilter, liveIssueIds)
.filter((issue) => !visibleIssueIds.has(issue.id));
}
diff --git a/ui/src/lib/issue-filters.test.ts b/ui/src/lib/issue-filters.test.ts
index 523a2222d6e..504182b7655 100644
--- a/ui/src/lib/issue-filters.test.ts
+++ b/ui/src/lib/issue-filters.test.ts
@@ -2,7 +2,13 @@
import { describe, expect, it } from "vitest";
import type { Issue } from "@paperclipai/shared";
-import { applyIssueFilters, countActiveIssueFilters, defaultIssueFilterState } from "./issue-filters";
+import {
+ applyIssueFilters,
+ countActiveIssueFilters,
+ defaultIssueFilterState,
+ resolveIssueFilterWorkspaceId,
+ shouldIncludeIssueFilterWorkspaceOption,
+} from "./issue-filters";
function makeIssue(overrides: Partial = {}): Issue {
return {
@@ -66,4 +72,100 @@ describe("issue filters", () => {
creators: ["user:user-1"],
})).toBe(1);
});
+
+ it("filters issues to live issue ids when live-only is enabled", () => {
+ const issues = [
+ makeIssue({ id: "live-issue" }),
+ makeIssue({ id: "idle-issue" }),
+ ];
+
+ const filtered = applyIssueFilters(
+ issues,
+ { ...defaultIssueFilterState, liveOnly: true },
+ null,
+ false,
+ new Set(["live-issue"]),
+ );
+
+ expect(filtered.map((issue) => issue.id)).toEqual(["live-issue"]);
+ });
+
+ it("counts the live-only filter as an active filter group", () => {
+ expect(countActiveIssueFilters({
+ ...defaultIssueFilterState,
+ liveOnly: true,
+ })).toBe(1);
+ });
+
+ it("does not treat default project workspaces as workspace filter matches", () => {
+ const issue = makeIssue({
+ id: "default-workspace-issue",
+ projectId: "project-1",
+ projectWorkspaceId: "workspace-default",
+ });
+ const workspaceContext = {
+ defaultProjectWorkspaceIdByProjectId: new Map([["project-1", "workspace-default"]]),
+ };
+
+ expect(resolveIssueFilterWorkspaceId(issue, workspaceContext)).toBeNull();
+ expect(applyIssueFilters(
+ [issue],
+ { ...defaultIssueFilterState, workspaces: ["workspace-default"] },
+ null,
+ false,
+ undefined,
+ workspaceContext,
+ )).toEqual([]);
+ });
+
+ it("does not treat shared default execution workspaces as workspace filter matches", () => {
+ const issue = makeIssue({
+ id: "shared-default-issue",
+ projectId: "project-1",
+ projectWorkspaceId: "workspace-default",
+ executionWorkspaceId: "execution-shared-default",
+ });
+ const workspaceContext = {
+ executionWorkspaceById: new Map([[
+ "execution-shared-default",
+ { mode: "shared_workspace", projectWorkspaceId: "workspace-default" },
+ ]]),
+ defaultProjectWorkspaceIdByProjectId: new Map([["project-1", "workspace-default"]]),
+ };
+
+ expect(resolveIssueFilterWorkspaceId(issue, workspaceContext)).toBeNull();
+ expect(shouldIncludeIssueFilterWorkspaceOption(
+ { id: "execution-shared-default", mode: "shared_workspace", projectWorkspaceId: "workspace-default" },
+ new Set(["workspace-default"]),
+ )).toBe(false);
+ });
+
+ it("keeps non-default project and isolated execution workspaces filterable", () => {
+ const featureIssue = makeIssue({
+ id: "feature-issue",
+ projectId: "project-1",
+ projectWorkspaceId: "workspace-feature",
+ });
+ const executionIssue = makeIssue({
+ id: "execution-issue",
+ projectId: "project-1",
+ projectWorkspaceId: "workspace-default",
+ executionWorkspaceId: "execution-isolated",
+ });
+ const workspaceContext = {
+ executionWorkspaceById: new Map([[
+ "execution-isolated",
+ { mode: "isolated_workspace", projectWorkspaceId: "workspace-default" },
+ ]]),
+ defaultProjectWorkspaceIdByProjectId: new Map([["project-1", "workspace-default"]]),
+ };
+
+ expect(resolveIssueFilterWorkspaceId(featureIssue, workspaceContext)).toBe("workspace-feature");
+ expect(resolveIssueFilterWorkspaceId(executionIssue, workspaceContext)).toBe("execution-isolated");
+ expect(shouldIncludeIssueFilterWorkspaceOption({ id: "workspace-feature" }, new Set(["workspace-default"]))).toBe(true);
+ expect(shouldIncludeIssueFilterWorkspaceOption(
+ { id: "execution-isolated", mode: "isolated_workspace", projectWorkspaceId: "workspace-default" },
+ new Set(["workspace-default"]),
+ )).toBe(true);
+ });
});
diff --git a/ui/src/lib/issue-filters.ts b/ui/src/lib/issue-filters.ts
index 4a64b839f3f..63ce019378d 100644
--- a/ui/src/lib/issue-filters.ts
+++ b/ui/src/lib/issue-filters.ts
@@ -1,5 +1,15 @@
import type { Issue } from "@paperclipai/shared";
+export type IssueFilterWorkspaceLookup = {
+ mode?: string | null;
+ projectWorkspaceId?: string | null;
+};
+
+export type IssueFilterWorkspaceContext = {
+ executionWorkspaceById?: ReadonlyMap;
+ defaultProjectWorkspaceIdByProjectId?: ReadonlyMap;
+};
+
export type IssueFilterState = {
statuses: string[];
priorities: string[];
@@ -8,6 +18,7 @@ export type IssueFilterState = {
labels: string[];
projects: string[];
workspaces: string[];
+ liveOnly?: boolean;
hideRoutineExecutions: boolean;
};
@@ -19,6 +30,7 @@ export const defaultIssueFilterState: IssueFilterState = {
labels: [],
projects: [],
workspaces: [],
+ liveOnly: false,
hideRoutineExecutions: false,
};
@@ -59,6 +71,7 @@ export function normalizeIssueFilterState(value: unknown): IssueFilterState {
labels: normalizeIssueFilterValueArray(candidate.labels),
projects: normalizeIssueFilterValueArray(candidate.projects),
workspaces: normalizeIssueFilterValueArray(candidate.workspaces),
+ liveOnly: candidate.liveOnly === true,
hideRoutineExecutions: candidate.hideRoutineExecutions === true,
};
}
@@ -68,9 +81,41 @@ export function toggleIssueFilterValue(values: string[], value: string): string[
}
export function resolveIssueFilterWorkspaceId(
- issue: Pick,
+ issue: Pick,
+ context: IssueFilterWorkspaceContext = {},
): string | null {
- return issue.executionWorkspaceId ?? issue.projectWorkspaceId ?? null;
+ const defaultProjectWorkspaceId = issue.projectId
+ ? context.defaultProjectWorkspaceIdByProjectId?.get(issue.projectId) ?? null
+ : null;
+
+ if (issue.executionWorkspaceId) {
+ const executionWorkspace = context.executionWorkspaceById?.get(issue.executionWorkspaceId) ?? null;
+ const linkedProjectWorkspaceId =
+ executionWorkspace?.projectWorkspaceId ?? issue.projectWorkspaceId ?? null;
+ const isDefaultSharedExecutionWorkspace =
+ executionWorkspace?.mode === "shared_workspace"
+ && linkedProjectWorkspaceId != null
+ && linkedProjectWorkspaceId === defaultProjectWorkspaceId;
+ if (isDefaultSharedExecutionWorkspace) return null;
+ return issue.executionWorkspaceId;
+ }
+
+ if (issue.projectWorkspaceId) {
+ if (issue.projectWorkspaceId === defaultProjectWorkspaceId) return null;
+ return issue.projectWorkspaceId;
+ }
+
+ return null;
+}
+
+export function shouldIncludeIssueFilterWorkspaceOption(
+ workspace: { id: string; mode?: string | null; projectWorkspaceId?: string | null },
+ defaultProjectWorkspaceIds: ReadonlySet,
+): boolean {
+ if (defaultProjectWorkspaceIds.has(workspace.id)) return false;
+ return !(workspace.mode === "shared_workspace"
+ && workspace.projectWorkspaceId != null
+ && defaultProjectWorkspaceIds.has(workspace.projectWorkspaceId));
}
export function applyIssueFilters(
@@ -78,8 +123,13 @@ export function applyIssueFilters(
state: IssueFilterState,
currentUserId?: string | null,
enableRoutineVisibilityFilter = false,
+ liveIssueIds?: ReadonlySet,
+ workspaceContext: IssueFilterWorkspaceContext = {},
): Issue[] {
let result = issues;
+ if (state.liveOnly) {
+ result = result.filter((issue) => liveIssueIds?.has(issue.id) === true);
+ }
if (enableRoutineVisibilityFilter && state.hideRoutineExecutions) {
result = result.filter((issue) => issue.originKind !== "routine_execution");
}
@@ -112,7 +162,7 @@ export function applyIssueFilters(
}
if (state.workspaces.length > 0) {
result = result.filter((issue) => {
- const workspaceId = resolveIssueFilterWorkspaceId(issue);
+ const workspaceId = resolveIssueFilterWorkspaceId(issue, workspaceContext);
return workspaceId != null && state.workspaces.includes(workspaceId);
});
}
@@ -131,6 +181,7 @@ export function countActiveIssueFilters(
if (state.labels.length > 0) count += 1;
if (state.projects.length > 0) count += 1;
if (state.workspaces.length > 0) count += 1;
+ if (state.liveOnly) count += 1;
if (enableRoutineVisibilityFilter && state.hideRoutineExecutions) count += 1;
return count;
}
diff --git a/ui/src/lib/liveIssueIds.test.ts b/ui/src/lib/liveIssueIds.test.ts
index b4db99910e3..c94a3ff3ad0 100644
--- a/ui/src/lib/liveIssueIds.test.ts
+++ b/ui/src/lib/liveIssueIds.test.ts
@@ -57,6 +57,19 @@ describe("collectLiveIssueIds", () => {
adapterType: "codex_local",
issueId: "issue-2",
},
+ {
+ id: "run-5",
+ status: "succeeded",
+ invocationSource: "scheduler",
+ triggerDetail: null,
+ startedAt: "2026-04-20T10:04:00.000Z",
+ finishedAt: "2026-04-20T10:05:00.000Z",
+ createdAt: "2026-04-20T10:04:00.000Z",
+ agentId: "agent-5",
+ agentName: "Done",
+ adapterType: "codex_local",
+ issueId: "completed-issue",
+ },
];
expect([...collectLiveIssueIds(liveRuns)]).toEqual(["issue-1", "issue-2"]);
diff --git a/ui/src/lib/liveIssueIds.ts b/ui/src/lib/liveIssueIds.ts
index 40865fe95ae..5a98875096a 100644
--- a/ui/src/lib/liveIssueIds.ts
+++ b/ui/src/lib/liveIssueIds.ts
@@ -1,9 +1,13 @@
import type { LiveRunForIssue } from "../api/heartbeats";
+function isLiveRunStatus(status: string): boolean {
+ return status === "queued" || status === "running";
+}
+
export function collectLiveIssueIds(liveRuns: readonly LiveRunForIssue[] | null | undefined): Set {
const ids = new Set();
for (const run of liveRuns ?? []) {
- if (run.issueId) ids.add(run.issueId);
+ if (run.issueId && isLiveRunStatus(run.status)) ids.add(run.issueId);
}
return ids;
}
diff --git a/ui/src/pages/ExecutionWorkspaceDetail.tsx b/ui/src/pages/ExecutionWorkspaceDetail.tsx
index 0e5aedaf8ce..78dd1700049 100644
--- a/ui/src/pages/ExecutionWorkspaceDetail.tsx
+++ b/ui/src/pages/ExecutionWorkspaceDetail.tsx
@@ -25,6 +25,7 @@ import {
} from "../components/WorkspaceRuntimeControls";
import { useBreadcrumbs } from "../context/BreadcrumbContext";
import { useCompany } from "../context/CompanyContext";
+import { collectLiveIssueIds } from "../lib/liveIssueIds";
import { queryKeys } from "../lib/queryKeys";
import { cn, formatDateTime, issueUrl, projectRouteRef, projectWorkspaceUrl } from "../lib/utils";
@@ -271,13 +272,7 @@ function ExecutionWorkspaceIssuesList({
refetchInterval: 5000,
});
- const liveIssueIds = useMemo(() => {
- const ids = new Set();
- for (const run of liveRuns ?? []) {
- if (run.issueId) ids.add(run.issueId);
- }
- return ids;
- }, [liveRuns]);
+ const liveIssueIds = useMemo(() => collectLiveIssueIds(liveRuns), [liveRuns]);
const updateIssue = useMutation({
mutationFn: ({ id, data }: { id: string; data: Record }) => issuesApi.update(id, data),
diff --git a/ui/src/pages/Inbox.tsx b/ui/src/pages/Inbox.tsx
index a2d1b54142b..aa7707a03d1 100644
--- a/ui/src/pages/Inbox.tsx
+++ b/ui/src/pages/Inbox.tsx
@@ -23,6 +23,7 @@ import {
countActiveIssueFilters,
type IssueFilterState,
} from "../lib/issue-filters";
+import { collectLiveIssueIds } from "../lib/liveIssueIds";
import { formatAssigneeUserLabel } from "../lib/assignees";
import { buildCompanyUserLabelMap, buildCompanyUserProfileMap } from "../lib/company-members";
import {
@@ -826,6 +827,7 @@ export function Inbox() {
enabled: !!selectedCompanyId,
refetchInterval: 5000,
});
+ const liveIssueIds = useMemo(() => collectLiveIssueIds(liveRuns), [liveRuns]);
const { data: companyMembers } = useQuery({
queryKey: queryKeys.access.companyUserDirectory(selectedCompanyId!),
queryFn: () => accessApi.listUserDirectory(selectedCompanyId!),
@@ -845,12 +847,12 @@ export function Inbox() {
const mineIssues = useMemo(() => getRecentTouchedIssues(mineIssuesRaw), [mineIssuesRaw]);
const touchedIssues = useMemo(() => getRecentTouchedIssues(touchedIssuesRaw), [touchedIssuesRaw]);
const visibleMineIssues = useMemo(
- () => applyIssueFilters(mineIssues, issueFilters, currentUserId, true),
- [mineIssues, issueFilters, currentUserId],
+ () => applyIssueFilters(mineIssues, issueFilters, currentUserId, true, liveIssueIds),
+ [mineIssues, issueFilters, currentUserId, liveIssueIds],
);
const visibleTouchedIssues = useMemo(
- () => applyIssueFilters(touchedIssues, issueFilters, currentUserId, true),
- [touchedIssues, issueFilters, currentUserId],
+ () => applyIssueFilters(touchedIssues, issueFilters, currentUserId, true, liveIssueIds),
+ [touchedIssues, issueFilters, currentUserId, liveIssueIds],
);
const unreadTouchedIssues = useMemo(
() => visibleTouchedIssues.filter((issue) => issue.isUnreadForMe),
@@ -1004,14 +1006,6 @@ export function Inbox() {
),
[heartbeatRuns, dismissedAtByKey],
);
- const liveIssueIds = useMemo(() => {
- const ids = new Set();
- for (const run of liveRuns ?? []) {
- if (run.issueId) ids.add(run.issueId);
- }
- return ids;
- }, [liveRuns]);
-
const approvalsToRender = useMemo(() => {
let filtered = getApprovalsForTab(approvals ?? [], tab, allApprovalFilter, currentUserId);
if (tab === "mine") {
@@ -1159,12 +1153,14 @@ export function Inbox() {
issueFilters,
currentUserId,
enableRoutineVisibilityFilter: true,
+ liveIssueIds,
}),
[
archivedSearchIssues,
currentUserId,
filteredWorkItems,
issueFilters,
+ liveIssueIds,
normalizedSearchQuery,
remoteIssueSearchResults,
],
diff --git a/ui/src/pages/ProjectDetail.tsx b/ui/src/pages/ProjectDetail.tsx
index 5fc126310a5..e49f066839a 100644
--- a/ui/src/pages/ProjectDetail.tsx
+++ b/ui/src/pages/ProjectDetail.tsx
@@ -24,6 +24,7 @@ import { PageSkeleton } from "../components/PageSkeleton";
import { PageTabBar } from "../components/PageTabBar";
import { ProjectWorkspacesContent } from "../components/ProjectWorkspacesContent";
import { buildProjectWorkspaceSummaries } from "../lib/project-workspaces-tab";
+import { collectLiveIssueIds } from "../lib/liveIssueIds";
import { projectRouteRef } from "../lib/utils";
import { Button } from "@/components/ui/button";
import { Tabs } from "@/components/ui/tabs";
@@ -175,13 +176,7 @@ function ProjectIssuesList({ projectId, companyId }: { projectId: string; compan
enabled: !!companyId,
});
- const liveIssueIds = useMemo(() => {
- const ids = new Set();
- for (const run of liveRuns ?? []) {
- if (run.issueId) ids.add(run.issueId);
- }
- return ids;
- }, [liveRuns]);
+ const liveIssueIds = useMemo(() => collectLiveIssueIds(liveRuns), [liveRuns]);
const { data: issues, isLoading, error } = useQuery({
queryKey: queryKeys.issues.listByProject(companyId, projectId),
diff --git a/ui/src/pages/Routines.tsx b/ui/src/pages/Routines.tsx
index 79d6b707ae2..3c00462fbca 100644
--- a/ui/src/pages/Routines.tsx
+++ b/ui/src/pages/Routines.tsx
@@ -13,6 +13,7 @@ import { useToastActions } from "../context/ToastContext";
import { queryKeys } from "../lib/queryKeys";
import { groupBy } from "../lib/groupBy";
import { createIssueDetailLocationState } from "../lib/issueDetailBreadcrumb";
+import { collectLiveIssueIds } from "../lib/liveIssueIds";
import { getRecentAssigneeIds, sortAgentsByRecency, trackRecentAssignee } from "../lib/recent-assignees";
import { getRecentProjectIds, trackRecentProject } from "../lib/recent-projects";
import { ToggleSwitch } from "@/components/ui/toggle-switch";
@@ -492,13 +493,7 @@ export function Routines() {
() => new Map((projects ?? []).map((project) => [project.id, project])),
[projects],
);
- const liveIssueIds = useMemo(() => {
- const ids = new Set();
- for (const run of liveRuns ?? []) {
- if (run.issueId) ids.add(run.issueId);
- }
- return ids;
- }, [liveRuns]);
+ const liveIssueIds = useMemo(() => collectLiveIssueIds(liveRuns), [liveRuns]);
const routineGroups = useMemo(
() => buildRoutineGroups(routines ?? [], routineViewState.groupBy, projectById, agentById),
[agentById, projectById, routineViewState.groupBy, routines],