The browser app had no tests and no way to run any: package.json shipped dev/build/preview/typecheck, and CI installed Node and pnpm but never ran anything with them. Its densest logic was module-private, so even with a runner none of it was reachable. Extracts four pure modules out of DOM-heavy ones — tree.ts (directory grouping), keys.ts (terminal escape encoding), paths.ts (completion arithmetic), html.ts (one escaper, replacing four copies). Extraction rather than `export` because attach.ts imports xterm's CSS at module level, which a node test cannot load. CatalogStore takes an injectable socket factory, scheduler and refetch, so the reducer and the backoff schedule run without a server. snapshot() now returns a copy: it returned the live object while events mutated it in place, so listeners could not diff and every event rebuilt the whole tree. 36 tests across 6 files, and a `web` CI job running typecheck, test and build. vitest is pinned to 2.x — 4.x requires vite >= 6 and this is vite 5. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
22 lines
752 B
TypeScript
22 lines
752 B
TypeScript
// Path splitting and completion arithmetic for the working-directory input.
|
|
|
|
export function splitPath(value: string): { dir: string; prefix: string } {
|
|
const slash = value.lastIndexOf("/");
|
|
if (slash === -1) return { dir: "", prefix: value };
|
|
return { dir: value.slice(0, slash + 1), prefix: value.slice(slash + 1) };
|
|
}
|
|
|
|
/** Longest common prefix of a list of strings (case-sensitive, preserving the
|
|
* actual on-disk casing). */
|
|
export function longestCommonPrefix(items: string[]): string {
|
|
if (items.length === 0) return "";
|
|
let lcp = items[0];
|
|
for (const s of items.slice(1)) {
|
|
let i = 0;
|
|
while (i < lcp.length && i < s.length && lcp[i] === s[i]) i++;
|
|
lcp = lcp.slice(0, i);
|
|
if (lcp === "") break;
|
|
}
|
|
return lcp;
|
|
}
|