fix: separate pi-tui-dependent layout utils to fix report generation (#1527)
Report generation in auto-loop uses native dynamic import() which bypasses jiti's alias resolution. The import chain metrics.js → mod.js → ui.js → @gsd/pi-tui failed because Node cannot resolve @gsd/pi-tui from ~/.gsd/agent/extensions/. Split ANSI-aware layout helpers (padRight, joinColumns, centerLine, fitColumns) into layout-utils.ts and keep format-utils.ts pure so report modules can import formatting functions without pulling in the @gsd/pi-tui dependency.
This commit is contained in:
parent
2fcbb40c09
commit
aa8d3ee059
6 changed files with 72 additions and 54 deletions
|
|
@ -2,7 +2,8 @@
|
|||
// Human-readable display of past auto-mode unit executions.
|
||||
|
||||
import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
||||
import { formatDuration, padRight, truncateWithEllipsis } from "../shared/format-utils.js";
|
||||
import { formatDuration, truncateWithEllipsis } from "../shared/format-utils.js";
|
||||
import { padRight } from "../shared/layout-utils.js";
|
||||
import {
|
||||
getLedger, getProjectTotals, formatCost, formatTokenCount,
|
||||
aggregateBySlice, aggregateByPhase, aggregateByModel, loadLedgerFromDisk,
|
||||
|
|
|
|||
|
|
@ -20,8 +20,10 @@ import { getAndClearSkills } from "./skill-telemetry.js";
|
|||
import { loadJsonFile, loadJsonFileOrNull, saveJsonFile } from "./json-persistence.js";
|
||||
import { parseUnitId } from "./unit-id.js";
|
||||
|
||||
// Re-export from shared — canonical implementation lives in format-utils.
|
||||
export { formatTokenCount } from "../shared/mod.js";
|
||||
// Re-export from shared — import directly from format-utils to avoid pulling
|
||||
// in the full barrel (mod.js → ui.js → @gsd/pi-tui) which breaks when loaded
|
||||
// outside jiti's alias resolution (e.g. dynamic import in auto-loop reports).
|
||||
export { formatTokenCount } from "../shared/format-utils.js";
|
||||
|
||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
/**
|
||||
* Shared formatting and layout utilities for TUI dashboard components.
|
||||
* Shared pure formatting utilities — no @gsd/pi-tui dependency.
|
||||
*
|
||||
* Consolidates helpers that were previously duplicated across
|
||||
* auto-dashboard.ts, dashboard-overlay.ts, and visualizer-views.ts.
|
||||
* ANSI-aware layout helpers (padRight, joinColumns, centerLine, fitColumns)
|
||||
* live in layout-utils.ts to avoid pulling @gsd/pi-tui into modules that
|
||||
* run outside jiti's alias resolution (e.g. HTML report generation via
|
||||
* dynamic import in auto-loop).
|
||||
*/
|
||||
|
||||
import { truncateToWidth, visibleWidth } from "@gsd/pi-tui";
|
||||
|
||||
// ─── Duration Formatting ──────────────────────────────────────────────────────
|
||||
|
||||
/** Format a millisecond duration as a compact human-readable string. */
|
||||
|
|
@ -31,45 +31,6 @@ export function formatTokenCount(count: number): string {
|
|||
return `${(count / 1_000_000).toFixed(2)}M`;
|
||||
}
|
||||
|
||||
// ─── Layout Helpers ───────────────────────────────────────────────────────────
|
||||
|
||||
/** Pad a string with trailing spaces to fill `width` (ANSI-aware). */
|
||||
export function padRight(content: string, width: number): string {
|
||||
const vis = visibleWidth(content);
|
||||
return content + " ".repeat(Math.max(0, width - vis));
|
||||
}
|
||||
|
||||
/** Build a line with left-aligned and right-aligned content. */
|
||||
export function joinColumns(left: string, right: string, width: number): string {
|
||||
const leftW = visibleWidth(left);
|
||||
const rightW = visibleWidth(right);
|
||||
if (leftW + rightW + 2 > width) {
|
||||
return truncateToWidth(`${left} ${right}`, width);
|
||||
}
|
||||
return left + " ".repeat(width - leftW - rightW) + right;
|
||||
}
|
||||
|
||||
/** Center content within `width` (ANSI-aware). */
|
||||
export function centerLine(content: string, width: number): string {
|
||||
const vis = visibleWidth(content);
|
||||
if (vis >= width) return truncateToWidth(content, width);
|
||||
const leftPad = Math.floor((width - vis) / 2);
|
||||
return " ".repeat(leftPad) + content;
|
||||
}
|
||||
|
||||
/** Join as many parts as fit within `width`, separated by `separator`. */
|
||||
export function fitColumns(parts: string[], width: number, separator = " "): string {
|
||||
const filtered = parts.filter(Boolean);
|
||||
if (filtered.length === 0) return "";
|
||||
let result = filtered[0];
|
||||
for (let i = 1; i < filtered.length; i++) {
|
||||
const candidate = `${result}${separator}${filtered[i]}`;
|
||||
if (visibleWidth(candidate) > width) break;
|
||||
result = candidate;
|
||||
}
|
||||
return truncateToWidth(result, width);
|
||||
}
|
||||
|
||||
// ─── Text Truncation ─────────────────────────────────────────────────────────
|
||||
|
||||
/** Truncate a string to `maxLength` characters, replacing the last character with an ellipsis if needed. */
|
||||
|
|
|
|||
49
src/resources/extensions/shared/layout-utils.ts
Normal file
49
src/resources/extensions/shared/layout-utils.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
* ANSI-aware TUI layout utilities that depend on @gsd/pi-tui.
|
||||
*
|
||||
* Separated from format-utils.ts so that modules needing only pure
|
||||
* formatting (e.g. HTML report generation) can import format-utils
|
||||
* without pulling in the @gsd/pi-tui dependency — which fails when
|
||||
* loaded outside jiti's alias resolution context.
|
||||
*/
|
||||
|
||||
import { truncateToWidth, visibleWidth } from "@gsd/pi-tui";
|
||||
|
||||
// ─── Layout Helpers ───────────────────────────────────────────────────────────
|
||||
|
||||
/** Pad a string with trailing spaces to fill `width` (ANSI-aware). */
|
||||
export function padRight(content: string, width: number): string {
|
||||
const vis = visibleWidth(content);
|
||||
return content + " ".repeat(Math.max(0, width - vis));
|
||||
}
|
||||
|
||||
/** Build a line with left-aligned and right-aligned content. */
|
||||
export function joinColumns(left: string, right: string, width: number): string {
|
||||
const leftW = visibleWidth(left);
|
||||
const rightW = visibleWidth(right);
|
||||
if (leftW + rightW + 2 > width) {
|
||||
return truncateToWidth(`${left} ${right}`, width);
|
||||
}
|
||||
return left + " ".repeat(width - leftW - rightW) + right;
|
||||
}
|
||||
|
||||
/** Center content within `width` (ANSI-aware). */
|
||||
export function centerLine(content: string, width: number): string {
|
||||
const vis = visibleWidth(content);
|
||||
if (vis >= width) return truncateToWidth(content, width);
|
||||
const leftPad = Math.floor((width - vis) / 2);
|
||||
return " ".repeat(leftPad) + content;
|
||||
}
|
||||
|
||||
/** Join as many parts as fit within `width`, separated by `separator`. */
|
||||
export function fitColumns(parts: string[], width: number, separator = " "): string {
|
||||
const filtered = parts.filter(Boolean);
|
||||
if (filtered.length === 0) return "";
|
||||
let result = filtered[0];
|
||||
for (let i = 1; i < filtered.length; i++) {
|
||||
const candidate = `${result}${separator}${filtered[i]}`;
|
||||
if (visibleWidth(candidate) > width) break;
|
||||
result = candidate;
|
||||
}
|
||||
return truncateToWidth(result, width);
|
||||
}
|
||||
|
|
@ -13,15 +13,18 @@ export {
|
|||
stripAnsi,
|
||||
formatTokenCount,
|
||||
formatDuration,
|
||||
padRight,
|
||||
joinColumns,
|
||||
centerLine,
|
||||
fitColumns,
|
||||
sparkline,
|
||||
normalizeStringArray,
|
||||
fileLink,
|
||||
} from "./format-utils.js";
|
||||
|
||||
export {
|
||||
padRight,
|
||||
joinColumns,
|
||||
centerLine,
|
||||
fitColumns,
|
||||
} from "./layout-utils.js";
|
||||
|
||||
export { shortcutDesc } from "./terminal.js";
|
||||
export { toPosixPath } from "./path-display.js";
|
||||
export { showInterviewRound } from "./interview-ui.js";
|
||||
|
|
|
|||
|
|
@ -2,13 +2,15 @@ import { describe, it } from "node:test";
|
|||
import assert from "node:assert/strict";
|
||||
import {
|
||||
formatDuration,
|
||||
sparkline,
|
||||
stripAnsi,
|
||||
} from "../format-utils.js";
|
||||
import {
|
||||
padRight,
|
||||
joinColumns,
|
||||
centerLine,
|
||||
fitColumns,
|
||||
sparkline,
|
||||
stripAnsi,
|
||||
} from "../format-utils.js";
|
||||
} from "../layout-utils.js";
|
||||
|
||||
describe("formatDuration", () => {
|
||||
it("formats seconds", () => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue