From c2f9189d832d19452f31be7445f11212809517b1 Mon Sep 17 00:00:00 2001 From: Mathieu Piton <27002047+mpiton@users.noreply.github.com> Date: Sun, 12 Apr 2026 18:10:37 +0200 Subject: [PATCH 1/2] fix: add notifications sidebar badge --- src/App.test.tsx | 1 + src/components/Sidebar/Sidebar.test.tsx | 37 +++++++++++++++++++++++++ src/components/Sidebar/Sidebar.tsx | 17 ++++++++++-- 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/App.test.tsx b/src/App.test.tsx index eebcd47..5edf7d8 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -13,6 +13,7 @@ vi.mock("./lib/tauri", async (importOriginal) => { return { ...actual, authGetStatus: vi.fn().mockResolvedValue({ connected: true, username: "test-user", error: null }), + listNotifications: vi.fn().mockResolvedValue([]), }; }); diff --git a/src/components/Sidebar/Sidebar.test.tsx b/src/components/Sidebar/Sidebar.test.tsx index 8553b93..b2ab676 100644 --- a/src/components/Sidebar/Sidebar.test.tsx +++ b/src/components/Sidebar/Sidebar.test.tsx @@ -66,6 +66,38 @@ vi.mock("../../lib/tauri", () => ({ lastSyncAt: "2026-03-28T10:00:00Z", }, ]), + listNotifications: vi.fn().mockResolvedValue([ + { + id: "notif-1", + title: "Unread review request", + url: "https://github.com/acme/frontend/pull/1", + unread: true, + updatedAt: "2026-03-28T10:00:00Z", + repo: "acme/frontend", + reason: "review_requested", + notificationType: "pull_request", + }, + { + id: "notif-2", + title: "Unread issue mention", + url: "https://github.com/acme/frontend/issues/2", + unread: true, + updatedAt: "2026-03-28T11:00:00Z", + repo: "acme/frontend", + reason: "mention", + notificationType: "issue", + }, + { + id: "notif-3", + title: "Read merge comment", + url: "https://github.com/acme/frontend/pull/3", + unread: false, + updatedAt: "2026-03-28T12:00:00Z", + repo: "acme/frontend", + reason: "comment", + notificationType: "pull_request", + }, + ]), setRepoEnabled: vi.fn().mockResolvedValue({}), authGetStatus: vi.fn().mockResolvedValue({ connected: true, @@ -109,6 +141,11 @@ describe("Sidebar", () => { expect(screen.getByText("3")).toBeInTheDocument(); }); + it("should show unread notification count", async () => { + renderSidebar(); + expect(await screen.findByRole("button", { name: /notifications \(2\)/i })).toBeInTheDocument(); + }); + it("should show workspace dots with state colors", () => { const { container } = renderSidebar(); const dots = container.querySelectorAll("[data-state]"); diff --git a/src/components/Sidebar/Sidebar.tsx b/src/components/Sidebar/Sidebar.tsx index 7fc409f..71c3eb1 100644 --- a/src/components/Sidebar/Sidebar.tsx +++ b/src/components/Sidebar/Sidebar.tsx @@ -6,7 +6,7 @@ import type { DashboardView } from "../../stores/dashboard"; import { useWorkspacesStore } from "../../stores/workspaces"; import { useGitHubData } from "../../hooks/useGitHubData"; import { FOCUS_RING } from "../../lib/a11y"; -import { listRepos, setRepoEnabled, authGetStatus } from "../../lib/tauri"; +import { listNotifications, listRepos, setRepoEnabled, authGetStatus } from "../../lib/tauri"; import { NavItem } from "./NavItem"; import { WorkspaceList } from "./WorkspaceList"; import { RepoList } from "./RepoList"; @@ -54,6 +54,12 @@ export function Sidebar(): ReactElement { refetchOnWindowFocus: false, }); + const notificationsQuery = useQuery({ + queryKey: ["github", "notifications"], + queryFn: listNotifications, + staleTime: 60_000, + }); + const toggleRepoMutation = useMutation({ mutationFn: ({ repoId, enabled }: { repoId: string; enabled: boolean }) => setRepoEnabled(repoId, enabled), @@ -87,6 +93,7 @@ export function Sidebar(): ReactElement { const repos = reposQuery.data ?? []; const enabledRepos = repos.filter((r) => r.enabled); const username = authQuery.data?.username ?? null; + const unreadNotificationsCount = notificationsQuery.data?.filter((item) => item.unread).length; useEffect(() => { if (typeof document !== "undefined" && navGroupRef.current?.contains(document.activeElement)) { @@ -173,7 +180,13 @@ export function Sidebar(): ReactElement { }} label={item.label} view={item.view} - count={item.countKey && stats ? stats[item.countKey] : undefined} + count={ + item.view === "notifications" + ? unreadNotificationsCount + : item.countKey && stats + ? stats[item.countKey] + : undefined + } isActive={currentView === item.view} tabIndex={focusedView === item.view ? 0 : -1} onClick={handleNavClick} From 3bfee63a102f8933f1571dc4e7d40ef76b8d0a66 Mon Sep 17 00:00:00 2001 From: Mathieu Piton <27002047+mpiton@users.noreply.github.com> Date: Sun, 12 Apr 2026 18:21:30 +0200 Subject: [PATCH 2/2] fix: address PR review comments --- src/components/Sidebar/Sidebar.test.tsx | 4 ++-- src/components/Sidebar/Sidebar.tsx | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/Sidebar/Sidebar.test.tsx b/src/components/Sidebar/Sidebar.test.tsx index b2ab676..55c419f 100644 --- a/src/components/Sidebar/Sidebar.test.tsx +++ b/src/components/Sidebar/Sidebar.test.tsx @@ -75,7 +75,7 @@ vi.mock("../../lib/tauri", () => ({ updatedAt: "2026-03-28T10:00:00Z", repo: "acme/frontend", reason: "review_requested", - notificationType: "pull_request", + notificationType: "pullRequest", }, { id: "notif-2", @@ -95,7 +95,7 @@ vi.mock("../../lib/tauri", () => ({ updatedAt: "2026-03-28T12:00:00Z", repo: "acme/frontend", reason: "comment", - notificationType: "pull_request", + notificationType: "pullRequest", }, ]), setRepoEnabled: vi.fn().mockResolvedValue({}), diff --git a/src/components/Sidebar/Sidebar.tsx b/src/components/Sidebar/Sidebar.tsx index 71c3eb1..5054916 100644 --- a/src/components/Sidebar/Sidebar.tsx +++ b/src/components/Sidebar/Sidebar.tsx @@ -54,10 +54,13 @@ export function Sidebar(): ReactElement { refetchOnWindowFocus: false, }); + const isGitHubConnected = authQuery.data?.connected === true; + const notificationsQuery = useQuery({ queryKey: ["github", "notifications"], queryFn: listNotifications, staleTime: 60_000, + enabled: isGitHubConnected, }); const toggleRepoMutation = useMutation({ @@ -93,7 +96,8 @@ export function Sidebar(): ReactElement { const repos = reposQuery.data ?? []; const enabledRepos = repos.filter((r) => r.enabled); const username = authQuery.data?.username ?? null; - const unreadNotificationsCount = notificationsQuery.data?.filter((item) => item.unread).length; + const unreadNotificationsCount = + notificationsQuery.data?.filter((item) => item.unread).length ?? 0; useEffect(() => { if (typeof document !== "undefined" && navGroupRef.current?.contains(document.activeElement)) {