Adjust inbox tab memory and badge counts
This commit is contained in:
@@ -1,8 +1,31 @@
|
||||
// @vitest-environment node
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import type { Approval, DashboardSummary, HeartbeatRun, Issue, JoinRequest } from "@paperclipai/shared";
|
||||
import { computeInboxBadgeData, getUnreadTouchedIssues } from "./inbox";
|
||||
import {
|
||||
computeInboxBadgeData,
|
||||
getUnreadTouchedIssues,
|
||||
loadLastInboxTab,
|
||||
saveLastInboxTab,
|
||||
} from "./inbox";
|
||||
|
||||
const storage = new Map<string, string>();
|
||||
|
||||
Object.defineProperty(globalThis, "localStorage", {
|
||||
value: {
|
||||
getItem: (key: string) => storage.get(key) ?? null,
|
||||
setItem: (key: string, value: string) => {
|
||||
storage.set(key, value);
|
||||
},
|
||||
removeItem: (key: string) => {
|
||||
storage.delete(key);
|
||||
},
|
||||
clear: () => {
|
||||
storage.clear();
|
||||
},
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
function makeApproval(status: Approval["status"]): Approval {
|
||||
return {
|
||||
@@ -142,6 +165,10 @@ const dashboard: DashboardSummary = {
|
||||
};
|
||||
|
||||
describe("inbox helpers", () => {
|
||||
beforeEach(() => {
|
||||
storage.clear();
|
||||
});
|
||||
|
||||
it("counts the same inbox sources the badge uses", () => {
|
||||
const result = computeInboxBadgeData({
|
||||
approvals: [makeApproval("pending"), makeApproval("approved")],
|
||||
@@ -152,7 +179,7 @@ describe("inbox helpers", () => {
|
||||
makeRun("run-latest", "timed_out", "2026-03-11T01:00:00.000Z"),
|
||||
makeRun("run-other-agent", "failed", "2026-03-11T02:00:00.000Z", "agent-2"),
|
||||
],
|
||||
touchedIssues: [makeIssue("1", true), makeIssue("2", false)],
|
||||
unreadIssues: [makeIssue("1", true)],
|
||||
dismissed: new Set<string>(),
|
||||
});
|
||||
|
||||
@@ -172,7 +199,7 @@ describe("inbox helpers", () => {
|
||||
joinRequests: [],
|
||||
dashboard,
|
||||
heartbeatRuns: [makeRun("run-1", "failed", "2026-03-11T00:00:00.000Z")],
|
||||
touchedIssues: [],
|
||||
unreadIssues: [],
|
||||
dismissed: new Set<string>(["run:run-1", "alert:budget", "alert:agent-errors"]),
|
||||
});
|
||||
|
||||
@@ -192,4 +219,12 @@ describe("inbox helpers", () => {
|
||||
expect(getUnreadTouchedIssues(issues).map((issue) => issue.id)).toEqual(["1"]);
|
||||
expect(issues).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("defaults the remembered inbox tab to new and persists all", () => {
|
||||
localStorage.clear();
|
||||
expect(loadLastInboxTab()).toBe("new");
|
||||
|
||||
saveLastInboxTab("all");
|
||||
expect(loadLastInboxTab()).toBe("all");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,6 +10,8 @@ export const RECENT_ISSUES_LIMIT = 100;
|
||||
export const FAILED_RUN_STATUSES = new Set(["failed", "timed_out"]);
|
||||
export const ACTIONABLE_APPROVAL_STATUSES = new Set(["pending", "revision_requested"]);
|
||||
export const DISMISSED_KEY = "paperclip:inbox:dismissed";
|
||||
export const INBOX_LAST_TAB_KEY = "paperclip:inbox:last-tab";
|
||||
export type InboxTab = "new" | "all";
|
||||
|
||||
export interface InboxBadgeData {
|
||||
inbox: number;
|
||||
@@ -30,7 +32,28 @@ export function loadDismissedInboxItems(): Set<string> {
|
||||
}
|
||||
|
||||
export function saveDismissedInboxItems(ids: Set<string>) {
|
||||
localStorage.setItem(DISMISSED_KEY, JSON.stringify([...ids]));
|
||||
try {
|
||||
localStorage.setItem(DISMISSED_KEY, JSON.stringify([...ids]));
|
||||
} catch {
|
||||
// Ignore localStorage failures.
|
||||
}
|
||||
}
|
||||
|
||||
export function loadLastInboxTab(): InboxTab {
|
||||
try {
|
||||
const raw = localStorage.getItem(INBOX_LAST_TAB_KEY);
|
||||
return raw === "all" ? "all" : "new";
|
||||
} catch {
|
||||
return "new";
|
||||
}
|
||||
}
|
||||
|
||||
export function saveLastInboxTab(tab: InboxTab) {
|
||||
try {
|
||||
localStorage.setItem(INBOX_LAST_TAB_KEY, tab);
|
||||
} catch {
|
||||
// Ignore localStorage failures.
|
||||
}
|
||||
}
|
||||
|
||||
export function getLatestFailedRunsByAgent(runs: HeartbeatRun[]): HeartbeatRun[] {
|
||||
@@ -80,14 +103,14 @@ export function computeInboxBadgeData({
|
||||
joinRequests,
|
||||
dashboard,
|
||||
heartbeatRuns,
|
||||
touchedIssues,
|
||||
unreadIssues,
|
||||
dismissed,
|
||||
}: {
|
||||
approvals: Approval[];
|
||||
joinRequests: JoinRequest[];
|
||||
dashboard: DashboardSummary | undefined;
|
||||
heartbeatRuns: HeartbeatRun[];
|
||||
touchedIssues: Issue[];
|
||||
unreadIssues: Issue[];
|
||||
dismissed: Set<string>;
|
||||
}): InboxBadgeData {
|
||||
const actionableApprovals = approvals.filter((approval) =>
|
||||
@@ -96,7 +119,7 @@ export function computeInboxBadgeData({
|
||||
const failedRuns = getLatestFailedRunsByAgent(heartbeatRuns).filter(
|
||||
(run) => !dismissed.has(`run:${run.id}`),
|
||||
).length;
|
||||
const unreadTouchedIssues = getUnreadTouchedIssues(touchedIssues).length;
|
||||
const unreadTouchedIssues = unreadIssues.length;
|
||||
const agentErrorCount = dashboard?.agents.error ?? 0;
|
||||
const monthBudgetCents = dashboard?.costs.monthBudgetCents ?? 0;
|
||||
const monthUtilizationPercent = dashboard?.costs.monthUtilizationPercent ?? 0;
|
||||
|
||||
Reference in New Issue
Block a user