app/tui/LadderView.tsx
zzmq.dev@gmail.com/zz / app / tui / LadderView.tsx
@ 568239fb
Download
Ladder Summary
- file The file defines a **LadderView** React component (exported) that renders a scrollable, indented list of “ladder” rungs for a single watched repository at a time, letting the user cycle through repositories with the **r** key. It flattens the hierarchical `ResolvedRung` tree into rows, computes the visible slice based on `maxRows` and `scrollOffset`, and displays repository info, pagination details, and each rung’s kind, path, and summary with themed colors. Internally it also exports a helper `flatten` (non‑exported) used to convert the tree into a flat array of `{depth, rung}` objects.
// The resolved Ladder tree for ONE watched repo at a time, flattened
// into an indented list -- lets you actually see whether
// ladder_write_rung calls are landing, not just infer it from the
// protocol trail. A Ladder is inherently per-repo (unlike the Lane list
// or the protocol feed, which read fine concatenated across repos), so
// this is a switcher, not a combined view: `r` cycles which watched
// repo's tree is shown, same convention LiveLog.tsx uses for its own
// repo filter -- no key collision since TabView only ever mounts the
// active tab's component. Scrolling (up/down, Page Up/Down, Home/End)
// is handled by the parent TabView, which passes down the resulting
// offset.
import React, { useMemo, useState } from "react";
import { Box, Text, useInput, useStdin } from "ink";
import type { ResolvedRung } from "../engine/ladderRender.js";
import type { LadderSnapshot } from "./useLadderTrees.js";
import { PINK, MUTED } from "./theme.js";
interface FlatRow {
depth: number;
rung: ResolvedRung;
}
function flatten(rung: ResolvedRung, depth: number, out: FlatRow[]): void {
out.push({ depth, rung });
for (const child of rung.children) flatten(child, depth + 1, out);
}
export function LadderView({ snapshots, maxRows, scrollOffset }: { snapshots: LadderSnapshot[]; maxRows: number; scrollOffset: number }) {
const repos = useMemo(() => Array.from(new Set(snapshots.map((s) => s.repo))), [snapshots]);
const [selected, setSelected] = useState<string | null>(null);
const { isRawModeSupported } = useStdin();
useInput(
(input) => {
if (input !== "r" || repos.length === 0) return;
const current = selected && repos.includes(selected) ? selected : repos[0];
const i = repos.indexOf(current);
setSelected(repos[(i + 1) % repos.length]);
},
{ isActive: isRawModeSupported },
);
const activeRepo = selected && repos.includes(selected) ? selected : repos[0];
const snapshot = snapshots.find((s) => s.repo === activeRepo);
const rows: FlatRow[] = [];
if (snapshot) flatten(snapshot.tree, 0, rows);
const windowSize = Math.max(1, maxRows - 1);
const maxOffset = Math.max(0, rows.length - windowSize);
const offset = Math.min(scrollOffset, maxOffset);
const shown = rows.slice(offset, offset + windowSize);
return (
<Box flexDirection="column">
<Text color={MUTED}>
repo=[<Text color={repos.length > 1 ? PINK : MUTED}>{activeRepo ?? "none"}</Text>]{repos.length > 1 ? "(r)" : ""} --{" "}
{rows.length === 0 ? "no Ladder content yet (nothing bootstrapped)" : `showing ${offset + 1}-${offset + shown.length} of ${rows.length} rungs`}
</Text>
{shown.map((row, i) => (
<Box key={i}>
<Text wrap="truncate-end">
{" ".repeat(row.depth)}
<Text color={PINK}>[{row.rung.kind}]</Text> <Text bold>{row.rung.path}</Text> <Text color={MUTED}>-- {row.rung.summary}</Text>
</Text>
</Box>
))}
</Box>
);
}