fix: dock app badge shows incorrect number

Closes #122

We now only show number based on open streams
This commit is contained in:
talksik
2026-04-09 12:31:14 -07:00
parent d024751135
commit 6fe5af8d8d
4 changed files with 47 additions and 13 deletions
+4
View File
@@ -18,6 +18,7 @@ import { particlePath } from "@/lib/particle-path";
import { useParticle } from "@/hooks/use-particle"; import { useParticle } from "@/hooks/use-particle";
import type { Particle } from "@/api/types"; import type { Particle } from "@/api/types";
import { PropsWithChildren, useCallback } from "react"; import { PropsWithChildren, useCallback } from "react";
import { useDockBadge } from "@/hooks/use-dock-badge";
import { toast } from "sonner"; import { toast } from "sonner";
function getParticleDisplayName(particle: Particle): string { function getParticleDisplayName(particle: Particle): string {
@@ -170,6 +171,9 @@ function TopBar() {
} }
export default function Layout({ children }: PropsWithChildren) { export default function Layout({ children }: PropsWithChildren) {
const { networkId } = useParams();
useDockBadge(networkId);
return ( return (
<div className="flex h-screen flex-col"> <div className="flex h-screen flex-col">
<TopBar /> <TopBar />
-4
View File
@@ -8,8 +8,6 @@ import { ComposeOverlay } from "./compose/compose-overlay";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { useStreamParticles } from "@/hooks/use-stream-particles"; import { useStreamParticles } from "@/hooks/use-stream-particles";
import { useStreamKeyboardNav } from "@/hooks/use-stream-keyboard-nav"; import { useStreamKeyboardNav } from "@/hooks/use-stream-keyboard-nav";
import { useDockBadge } from "@/hooks/use-dock-badge";
import { useAuthStore } from "@/stores/auth-store";
/** /**
* Route-level component for /:networkId (index). * Route-level component for /:networkId (index).
@@ -21,8 +19,6 @@ export default function NetworkRoot() {
const path = particlePath(networkId!, []); const path = particlePath(networkId!, []);
const { streams, isLoading } = useStreamParticles(path); const { streams, isLoading } = useStreamParticles(path);
const userId = useAuthStore((s) => s.user?.id);
useDockBadge(streams, userId);
const [composeActive, setComposeActive] = useState(false); const [composeActive, setComposeActive] = useState(false);
const [statusTab, setStatusTab] = useState<"open" | "closed">("open"); const [statusTab, setStatusTab] = useState<"open" | "closed">("open");
+36 -8
View File
@@ -1,24 +1,52 @@
import { useEffect, useMemo } from "react"; import { useEffect, useMemo } from "react";
import { where } from "firebase/firestore";
import { useLiveParticleChildren } from "@/hooks/use-particle";
import { useAuthStore } from "@/stores/auth-store";
import { particlePath } from "@/lib/particle-path";
import type { Particle, StreamProperties } from "@/api/types"; import type { Particle, StreamProperties } from "@/api/types";
type StreamParticle = Particle & { type: "stream"; properties: StreamProperties }; type StreamParticle = Particle & { type: "stream"; properties: StreamProperties };
const openStatusFilter = where("status", "==", "open");
/** /**
* Syncs the macOS dock badge with the count of unseen streams. * Self-contained hook that syncs the macOS dock badge with the count of
* Both last_child_created_at and playback_markers store the child particle's * unseen open streams the current user is involved in.
* created_at, so they're directly comparable. *
* Sets up its own Firestore listener so it works independently of
* whatever stream list is rendered on screen.
*/ */
export function useDockBadge(streams: StreamParticle[], userId: string | undefined) { export function useDockBadge(networkId: string | undefined) {
const user = useAuthStore((s) => s.user);
const userId = user?.id;
const visibilityScopes = useMemo(() => {
const scopes: string[] = [];
if (userId) scopes.push(`human:${userId}`);
if (networkId) scopes.push(`network:${networkId}`);
return scopes;
}, [userId, networkId]);
const path = networkId ? particlePath(networkId, []) : undefined;
const { children } = useLiveParticleChildren(path, {
orderByField: "last_child_created_at",
orderDirection: "desc",
visibilityScopes,
whereFilter: openStatusFilter,
});
const unseenCount = useMemo(() => { const unseenCount = useMemo(() => {
if (!userId) return 0; if (!userId) return 0;
return streams.filter((s) => { return children.filter((c): c is StreamParticle => {
const lastActivity = s.last_child_created_at?.getTime(); if (c.type !== "stream") return false;
const lastActivity = c.last_child_created_at?.getTime();
if (!lastActivity) return false; if (!lastActivity) return false;
const marker = s.playback_markers?.[userId]?.getTime(); const marker = c.playback_markers?.[userId]?.getTime();
if (marker === undefined) return false; if (marker === undefined) return false;
return lastActivity > marker; return lastActivity > marker;
}).length; }).length;
}, [streams, userId]); }, [children, userId]);
useEffect(() => { useEffect(() => {
window.electronApp.setDockBadge(unseenCount); window.electronApp.setDockBadge(unseenCount);
+7 -1
View File
@@ -66,7 +66,7 @@ interface UseLiveParticleChildrenParams {
} }
export function useLiveParticleChildren( export function useLiveParticleChildren(
path: ParticlePath, path: ParticlePath | undefined,
{ {
orderByField = "created_at", orderByField = "created_at",
orderDirection = "desc", orderDirection = "desc",
@@ -81,6 +81,12 @@ export function useLiveParticleChildren(
const [error, setError] = useState<Error | null>(null); const [error, setError] = useState<Error | null>(null);
useEffect(() => { useEffect(() => {
if (!path) {
setChildren([]);
setIsLoading(false);
return;
}
setIsLoading(true); setIsLoading(true);
setError(null); setError(null);
setChildren([]); setChildren([]);