97 lines · 3.7 KB · TypeScript
RawDownload
import { useAtomValue } from "@effect/atom-react";import { useEffect } from "react";import { Atom } from "effect/unstable/reactivity"; import {  reconcilePendingThreadOrder,  type PendingThreadOrder,} from "../features/threads/threadOrder";import { getThreadListV2OrderedSection } from "../features/threads/threadListV2";import { appAtomRegistry } from "./atom-registry";import { environmentServerConfigsAtom } from "./server";import { environmentThreadShells } from "./threads";import { queuedThreadKeysAtom } from "./use-thread-outbox"; // Covers lifecycle commands before a cross-section move can acquire an order hold.export const threadArrangementOpenAtom = Atom.make(false).pipe(Atom.keepAlive); export const threadDropBusyAtom = Atom.make(false).pipe(Atom.keepAlive); export const pendingThreadOrderAtom = Atom.make<PendingThreadOrder | null>(null).pipe(  Atom.keepAlive,); export function usePendingThreadOrder(nowMinute: string, snoozeWakeTick: number) {  const pending = useAtomValue(pendingThreadOrderAtom);  // A timed wake can change section membership without a shell event. Use the  // lists' existing clocks to retire that hold and re-enable their move menus.  useEffect(() => {    getPendingThreadOrder();  }, [nowMinute, snoozeWakeTick]);  return pending;} let refreshPendingOrder: (() => void) | undefined; /** Shared by Home and the navigation sidebar, including their action guards. */export function getPendingThreadOrder(): PendingThreadOrder | null {  refreshPendingOrder?.();  return appAtomRegistry.get(pendingThreadOrderAtom);} export function beginPendingThreadOrder(pending: PendingThreadOrder) {  const unsubscribers: (() => void)[] = [];  const cancel = () => {    if (refreshPendingOrder !== refresh) return;    refreshPendingOrder = undefined;    for (const unsubscribe of unsubscribers) unsubscribe();    appAtomRegistry.set(pendingThreadOrderAtom, null);  };  const refresh = () => {    if (refreshPendingOrder !== refresh) return;    const current = appAtomRegistry.get(pendingThreadOrderAtom);    if (current === null) return;    const configs = appAtomRegistry.get(environmentServerConfigsAtom);    const ordered = getThreadListV2OrderedSection({      threads: appAtomRegistry.get(environmentThreadShells.threadShellsAtom),      section: current.section,      now: new Date().toISOString(),      queuedThreadKeys: appAtomRegistry.get(queuedThreadKeysAtom),      settlementEnvironmentIds: new Set(        [...configs].flatMap(([id, config]) =>          config.environment.capabilities.threadSettlement === true ? [id] : [],        ),      ),      snoozeEnvironmentIds: new Set(        [...configs].flatMap(([id, config]) =>          config.environment.capabilities.threadSnooze === true ? [id] : [],        ),      ),    });    const next = reconcilePendingThreadOrder(current, ordered);    if (next === null) cancel();    else if (next !== current) appAtomRegistry.set(pendingThreadOrderAtom, next);  };  refreshPendingOrder = refresh;  appAtomRegistry.set(pendingThreadOrderAtom, pending);  unsubscribers.push(    appAtomRegistry.subscribe(environmentThreadShells.threadShellsAtom, refresh),    appAtomRegistry.subscribe(environmentServerConfigsAtom, refresh),    appAtomRegistry.subscribe(queuedThreadKeysAtom, refresh),  );  return {    isPending: () => {      refresh();      return refreshPendingOrder === refresh;    },    complete: () => {      if (refreshPendingOrder !== refresh) return;      const current = appAtomRegistry.get(pendingThreadOrderAtom);      if (current !== null) {        appAtomRegistry.set(pendingThreadOrderAtom, { ...current, commandsComplete: true });        refresh();      }    },    cancel,  };}