zz_

app/engine/ladderRender.ts

zzmq.dev@gmail.com/zz / app / engine / ladderRender.ts @ 7e2cc56a Download
Ladder Summary
  • file This module resolves a Ladder definition stored as Git‑notes into a single fully‑materialized tree (`ResolvedRung`), tracks how many rungs and notes were processed, and provides utilities to render that tree as plain text. It exports the resolution logic (`resolveLadderTree`), a convenience wrapper that reads from a local repository (`collectLadder`), a text‑renderer (`renderText`), a combined collect‑and‑render helper (`renderLadder`), and the `ResolvedRung` and `RenderStats` interfaces.
 // Materializes a Ladder into one resolved, nested tree -- reading from git
 // notes the same way the engine would, not from any cache, so this is a
 // view for reading, not a second source of truth. Every output format
 // (text, JSON, HTML) renders from the same resolved tree instead of each
 // re-walking git notes its own way.
 import { readAnchor, isRef, type EmbeddedRung } from "./ladderNotes.js";
 import { REPO_RUNG_PATH } from "./ladder.js";
 import type { RungKind, RungSource } from "./ladder.js";
 
 export interface ResolvedRung {
  path: string;
  kind: RungKind;
  summary: string;
  sources: RungSource[];
  touches?: string[];
  children: ResolvedRung[];
 }
 
 export interface RenderStats {
  rungs: number;
  notes: number;
 }
 
 export async function collectLadder(repoDir: string, stateDir: string): Promise<{ tree: ResolvedRung; stats: RenderStats }> {
  const stats: RenderStats = { rungs: 0, notes: 0 };
 
  function resolveOwnFields(rung: EmbeddedRung): Omit<ResolvedRung, "children"> {
  stats.rungs += 1;
  return { path: rung.path, kind: rung.kind, summary: rung.summary, sources: rung.sources, touches: rung.touches };
  }
 
  async function resolveNode(rung: EmbeddedRung): Promise<ResolvedRung> {
  const children: ResolvedRung[] = [];
  for (const child of rung.children) {
  if (isRef(child)) {
  const anchor = await readAnchor(repoDir, stateDir, child.ref);
  stats.notes += 1;
  children.push(anchor ? await resolveNode(anchor) : { path: child.ref, kind: "ask", summary: "[missing]", sources: [], children: [] });
  } else {
  children.push(await resolveNode(child));
  }
  }
  return { ...resolveOwnFields(rung), children };
  }
 
  const root = await readAnchor(repoDir, stateDir, REPO_RUNG_PATH);
  stats.notes += 1;
  const tree = root ? await resolveNode(root) : { path: REPO_RUNG_PATH, kind: "repo" as RungKind, summary: "[missing]", sources: [], children: [] };
  return { tree, stats };
 }
 
 export function renderText(tree: ResolvedRung, depth = 0): string {
  const indent = " ".repeat(depth);
  const lines = [`${indent}[${tree.kind}] ${tree.path}`, `${indent} ${tree.summary}`];
  if (tree.sources.length) {
  lines.push(`${indent} sources:`);
  for (const s of tree.sources) lines.push(`${indent} - ${s.kind} ${s.ref} (${s.at}) ${s.author}: ${s.title}`);
  }
  if (tree.touches?.length) lines.push(`${indent} touches: ${tree.touches.join(", ")}`);
  for (const child of tree.children) lines.push(renderText(child, depth + 1));
  return lines.join("\n");
 }
 
 /** Back-compat wrapper: collect + render text in one call, as the build script already uses. */
 export async function renderLadder(repoDir: string, stateDir: string): Promise<{ text: string; stats: RenderStats }> {
  const { tree, stats } = await collectLadder(repoDir, stateDir);
  return { text: renderText(tree), stats };
 }