Skip to content
Merged
Show file tree
Hide file tree
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
45 changes: 11 additions & 34 deletions apps/web/src/j5/crew/crewRuntimeRequestsClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { EnvironmentId } from "@t3tools/contracts";
import type { CrewRuntimeRequestRespondRequest } from "@t3tools/contracts/j5";
import { useAtomValue } from "@effect/atom-react";
import * as Cause from "effect/Cause";
import { useEffect, useMemo } from "react";
import { useMemo } from "react";

import { appAtomRegistry } from "../../rpc/atomRegistry";
import {
Expand All @@ -12,7 +12,7 @@ import {
j5Environment,
refreshJ5Sources,
} from "../state";
import { startVisibleRefresh } from "../useVisibleRefresh";
import { createVisibleRefreshHook } from "../useVisibleRefresh";
import { mergeCrewRuntimeRequestSources } from "./crewRuntimeRequests.logic";

export const CREW_RUNTIME_REQUESTS_POLL_INTERVAL_MS = 7_500;
Expand All @@ -37,45 +37,22 @@ export const refreshCrewRuntimeRequests = (environmentId?: EnvironmentId) =>
...(environmentId === undefined ? {} : { environmentId }),
});

/** Re-reads only the environments with a thread waiting on an approval. */
/**
* Re-reads only the environments with a thread waiting on an approval; sends nothing while none is.
* An environment entering the plan needs no call here: its query mounts and fetches on its own.
*/
const refreshPolledEnvironments = () => {
for (const environmentId of appAtomRegistry.get(crewApprovalPollAtom).environmentIds)
void refreshJ5Sources(crewRuntimeRequestSourcesAtom, crewRuntimeRequestsQueryAtom, {
environmentId,
});
};

// One foreground poll shared by every consumer, running only while some approval is pending.
let consumers = 0;
let polledKey: string | null = null;
let stopPoll: (() => void) | undefined;

const syncPoll = (key: string) => {
if (polledKey === key) return;
polledKey = key;
stopPoll?.();
stopPoll = undefined;
if (key === "") return;
// A thread raised or cleared its flag: re-read now, then keep polling while any stays raised.
refreshPolledEnvironments();
stopPoll = startVisibleRefresh(refreshPolledEnvironments, CREW_RUNTIME_REQUESTS_POLL_INTERVAL_MS);
};

/** The bell and the Inbox page share this poll; nothing is read while no approval is pending. */
export function useCrewRuntimeRequestsRefresh() {
const { key } = useAtomValue(crewApprovalPollAtom);
useEffect(() => {
consumers += 1;
return () => {
consumers -= 1;
if (consumers > 0) return;
stopPoll?.();
stopPoll = undefined;
polledKey = null;
};
}, []);
useEffect(() => syncPoll(key), [key]);
}
/** The bell and the Inbox page share this poll. */
export const useCrewRuntimeRequestsRefresh = createVisibleRefreshHook(
refreshPolledEnvironments,
CREW_RUNTIME_REQUESTS_POLL_INTERVAL_MS,
);

export function useCrewRuntimeRequests() {
const sources = useAtomValue(crewRuntimeRequestSourcesAtom);
Expand Down
3 changes: 1 addition & 2 deletions apps/web/src/j5/useVisibleRefresh.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { useEffect, useRef } from "react";

/** Refreshes while the page is visible: on focus, on becoming visible, and every `intervalMs`. */
export function startVisibleRefresh(refresh: () => void, intervalMs: number | null) {
function startVisibleRefresh(refresh: () => void, intervalMs: number | null) {
let timer: ReturnType<typeof setInterval> | undefined;
let lastRefresh = Date.now();
const refreshVisible = () => {
Expand Down
Loading