Stabilize chat read-state effect against snapshot reloads

useSnapshot now memoizes the actions object so dependants don't see a
fresh reference on every render. ChatPage's mark-read effect keys off
activeSpaceId/latestActiveMessageId primitives instead of the snapshot
objects that re-allocate on reload, which prevents the markRead → reload
→ rerender loop that was deadlocking the Electron click path under
Playwright.

Also constrains HomePage agenda time to same-day activity so the
'Today' column doesn't surface a meaningless wall-clock for older tasks.
This commit is contained in:
ZacharyZhang-NY committed 2026-05-21 20:32:25 -04:00
1 parent 1f78ae6f2e
commit 0eb1810246
3 files changed
+21 -9

No files matched your search

@@ -1,4 +1,4 @@
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import type { AppSnapshot } from "../../../shared/domain"; import type { AppSnapshot } from "../../../shared/domain";
import type { DesktopResult } from "../../../shared/result"; import type { DesktopResult } from "../../../shared/result";
@@ -35,5 +35,6 @@ export function useSnapshot(): {
}); });
}, [load]); }, [load]);
return { snapshot, actions: { run, reload: load } }; const actions = useMemo<SnapshotActions>(() => ({ run, reload: load }), [run, load]);
return { snapshot, actions };
} }
@@ -47,20 +47,20 @@ export function ChatPage({ snapshot, actions }: PageProps) {
: fallbackSpaceId; : fallbackSpaceId;
const activeSpace = spaces.find((space) => space.id === activeSpaceId) ?? null; const activeSpace = spaces.find((space) => space.id === activeSpaceId) ?? null;
const latestActiveMessageId = useMemo(() => { const latestActiveMessageId = useMemo(() => {
if (!activeSpace) return null; if (!activeSpaceId) return null;
const inSpace = messagesAll.filter((message) => message.spaceId === activeSpace.id); const inSpace = messagesAll.filter((message) => message.spaceId === activeSpaceId);
if (inSpace.length === 0) return null; if (inSpace.length === 0) return null;
return inSpace[inSpace.length - 1].id; return inSpace[inSpace.length - 1].id;
}, [messagesAll, activeSpace]); }, [messagesAll, activeSpaceId]);
useEffect(() => { useEffect(() => {
if (!activeSpace || !latestActiveMessageId) return; if (!activeSpaceId || !latestActiveMessageId) return;
void actions.run(() => window.agentCompany.markChatSpaceRead({ void actions.run(() => window.agentCompany.markChatSpaceRead({
orgId, orgId,
employeeId: employee.id, employeeId: employee.id,
spaceId: activeSpace.id, spaceId: activeSpaceId,
})); }));
}, [activeSpace, latestActiveMessageId, orgId, employee.id, actions]); }, [activeSpaceId, latestActiveMessageId, orgId, employee.id, actions]);
const messagesInSpace = useMemo( const messagesInSpace = useMemo(
() => (activeSpace ? messagesAll.filter((m) => m.spaceId === activeSpace.id) : []), () => (activeSpace ? messagesAll.filter((m) => m.spaceId === activeSpace.id) : []),
@@ -102,7 +102,7 @@ function TodayCard({
{rows.map((task) => { {rows.map((task) => {
const owner = agents.find((agent) => agent.id === task.assigneeAgentId); const owner = agents.find((agent) => agent.id === task.assigneeAgentId);
const ownerName = owner?.name ?? employee.displayName; const ownerName = owner?.name ?? employee.displayName;
const time = formatTime(task.updatedAt); const time = agendaTime(task, date);
const requiresApproval = approvals.some((approval) => approval.title === task.title); const requiresApproval = approvals.some((approval) => approval.title === task.title);
return ( return (
<div key={task.id} className="row" style={{ gap: 12 }}> <div key={task.id} className="row" style={{ gap: 12 }}>
@@ -279,6 +279,17 @@ function GoalCard({
); );
} }
function agendaTime(task: TaskRecord, reference: Date): string {
const updated = new Date(task.updatedAt);
if (Number.isNaN(updated.getTime())) return "—";
const sameDay =
updated.getFullYear() === reference.getFullYear() &&
updated.getMonth() === reference.getMonth() &&
updated.getDate() === reference.getDate();
if (!sameDay) return "—";
return formatTime(task.updatedAt);
}
function collectAgendaTasks(tasks: TaskRecord[], reference: Date): TaskRecord[] { function collectAgendaTasks(tasks: TaskRecord[], reference: Date): TaskRecord[] {
const startOfDay = new Date(reference.getFullYear(), reference.getMonth(), reference.getDate()).getTime(); const startOfDay = new Date(reference.getFullYear(), reference.getMonth(), reference.getDate()).getTime();
const endOfDay = startOfDay + 24 * 60 * 60 * 1000; const endOfDay = startOfDay + 24 * 60 * 60 * 1000;