Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions apps/web/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4751,25 +4751,35 @@ export default function ChatView(props: ChatViewProps) {
const supportsSnooze = serverConfig?.environment.capabilities.threadSnooze === true;
const supportsPinning = serverConfig?.environment.capabilities.threadPinning === true;
const activeThreadPinned = supportsPinning && activeThreadShell?.pinnedAt != null;
// The compiler caches render results on their inputs, so a clock read in render
// stays frozen until the shell changes. The clock is state: reset with the shell,
// and moved by the wake timer.
const [snoozeClockState, setSnoozeClockState] = useState(() => ({
shell: activeThreadShell,
now: new Date().toISOString(),
}));
if (snoozeClockState.shell !== activeThreadShell) {
setSnoozeClockState({ shell: activeThreadShell, now: new Date().toISOString() });
}
const snoozeClock = snoozeClockState.now;
const activeThreadSnoozed =
activeThreadShell !== null &&
supportsSnooze &&
effectiveSnoozed(activeThreadShell, { now: new Date().toISOString() });
const [snoozeWakeTick, bumpSnoozeWakeTick] = useState(0);
effectiveSnoozed(activeThreadShell, { now: snoozeClock });
useEffect(() => {
if (!activeThreadSnoozed) return;
const wakeAtMs = Date.parse(activeThreadShell?.snoozedUntil ?? "");
if (!Number.isFinite(wakeAtMs)) return;
const id = window.setTimeout(
() => bumpSnoozeWakeTick((tick) => tick + 1),
() => setSnoozeClockState((clock) => ({ ...clock, now: new Date().toISOString() })),
Math.min(Math.max(0, wakeAtMs - Date.now()) + 50, 2_147_483_647),
);
return () => window.clearTimeout(id);
// oxlint-disable-next-line react/exhaustive-effect-dependencies -- A tick that lands before the wake time (timer clamping, clock skew) re-arms the timer.
}, [activeThreadShell?.snoozedUntil, activeThreadSnoozed, snoozeWakeTick]);
// oxlint-disable-next-line react/exhaustive-effect-dependencies -- A clock that lands before the wake time (timer clamping, clock skew) re-arms the timer.
}, [activeThreadShell?.snoozedUntil, activeThreadSnoozed, snoozeClock]);
const activeThreadWokeAt =
activeThreadShell !== null && supportsSnooze
? threadWokeAt(activeThreadShell, { now: new Date().toISOString() })
? threadWokeAt(activeThreadShell, { now: snoozeClock })
: null;
const acknowledgeThreadWoke = useAcknowledgeThreadWoke();
const acknowledgeActiveThreadWoke = () => {
Expand Down
Loading