zz_

app/tui/useLadderTrees.ts

zzmq.dev@gmail.com/zz / app / tui / useLadderTrees.ts @ 5f98c7a3 Download
Ladder Summary
  • file The module provides a React hook `useLadderTrees` that periodically (default every 2 seconds) reads each repository’s resolved Ladder tree—either locally via `collectLadder` or remotely via `callRemoteTool`—and returns an array of `LadderSnapshot` objects (`{repo, tree}`) reflecting the latest state. It guards against overlapping fetches, skips repos that cannot be reached, and cleans up the interval when the component unmounts. Exported members: the `LadderSnapshot` interface and the `useLadderTrees` hook.
 // Polls each watched repo's resolved Ladder tree (engine/ladderRender.ts's
 // collectLadder -- the same resolver `zz ladder dump` uses) on an
 // interval, so the TUI can show whether Ladder writes are actually
 // landing, not just the Lane/Ask/Answer protocol trail.
 import { useEffect, useState } from "react";
 import { collectLadder, type ResolvedRung } from "../engine/ladderRender.js";
 import { callRemoteTool } from "../cli/lib/remoteTools.js";
 
 export interface LadderSnapshot {
  repo: string;
  tree: ResolvedRung;
 }
 
 export function useLadderTrees(repos: { repo: string; repoDir: string; stateDir: string }[], intervalMs = 2000, remote = false): LadderSnapshot[] {
  const [snapshots, setSnapshots] = useState<LadderSnapshot[]>([]);
  // See useLaneSnapshots.ts's identical comment -- content-keyed, not
  // identity-keyed, so a fresh array reference from the watch store on
  // every render doesn't restart polling unless the repo set actually changed.
  const reposKey = repos.map((r) => r.repo).join(",");
 
  useEffect(() => {
  let cancelled = false;
  // One round trip per repo now (ladder_tree, server-side resolved
  // and cached -- see cloudflareBackend.ts's readLadderTree), not
  // one per rung -- but still guarded against overlap, since a cold
  // cache on a large tree is still a real wait, and piling up
  // overlapping reads on top of that looks indistinguishable from
  // "never shows anything" rather than "takes a moment."
  let inFlight = false;
  const read = async () => {
  if (inFlight) return;
  inFlight = true;
  try {
  const next: LadderSnapshot[] = [];
  for (const { repo, repoDir, stateDir } of repos) {
  try {
  // allowAnonymous: see useLaneSnapshots.ts's identical comment.
  const tree = remote ? ((await callRemoteTool(repo, "ladder_tree", {}, true)) as ResolvedRung | null) : (await collectLadder(repoDir, stateDir)).tree;
  if (tree) next.push({ repo, tree });
  } catch {
  // repo not onboarded/bootstrapped/reachable yet -- skip rather than crash the view
  }
  }
  if (!cancelled) setSnapshots(next);
  } finally {
  inFlight = false;
  }
  };
  read();
  const id = setInterval(read, intervalMs);
  return () => {
  cancelled = true;
  clearInterval(id);
  };
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [intervalMs, remote, reposKey]);
 
  return snapshots;
 }