fix(terminal): stabilize skills table width across Terminal.app and iTerm (#42849)

* Terminal: measure grapheme display width

* Tests: cover grapheme terminal width

* Terminal: wrap table cells by grapheme width

* Tests: cover emoji table alignment

* Terminal: refine table wrapping and width handling

* Terminal: stop shrinking CLI tables by one column

* Skills: use Terminal-safe emoji in list output

* Changelog: note terminal skills table fixes

* Skills: normalize emoji presentation across outputs

* Terminal: consume unsupported escape bytes in tables
This commit is contained in:
Vincent Koc
2026-03-11 09:13:10 -04:00
committed by GitHub
parent 10e6e27451
commit 04e103d10e
32 changed files with 299 additions and 67 deletions

View File

@@ -1,7 +1,7 @@
import type { Command } from "commander";
import { formatTimeAgo } from "../../infra/format-time/format-relative.ts";
import { defaultRuntime } from "../../runtime.js";
import { renderTable } from "../../terminal/table.js";
import { getTerminalTableWidth, renderTable } from "../../terminal/table.js";
import { shortenHomeInString } from "../../utils.js";
import { parseDurationMs } from "../parse-duration.js";
import { getNodesTheme, runNodesCommand } from "./cli-utils.js";
@@ -112,7 +112,7 @@ export function registerNodesStatusCommands(nodes: Command) {
const obj: Record<string, unknown> =
typeof result === "object" && result !== null ? result : {};
const { ok, warn, muted } = getNodesTheme();
const tableWidth = Math.max(60, (process.stdout.columns ?? 120) - 1);
const tableWidth = getTerminalTableWidth();
const now = Date.now();
const nodes = parseNodeList(result);
const lastConnectedById =
@@ -256,7 +256,7 @@ export function registerNodesStatusCommands(nodes: Command) {
const status = `${paired ? ok("paired") : warn("unpaired")} · ${
connected ? ok("connected") : muted("disconnected")
}`;
const tableWidth = Math.max(60, (process.stdout.columns ?? 120) - 1);
const tableWidth = getTerminalTableWidth();
const rows = [
{ Field: "ID", Value: nodeId },
displayName ? { Field: "Name", Value: displayName } : null,
@@ -307,7 +307,7 @@ export function registerNodesStatusCommands(nodes: Command) {
const result = await callGatewayCli("node.pair.list", opts, {});
const { pending, paired } = parsePairingList(result);
const { heading, muted, warn } = getNodesTheme();
const tableWidth = Math.max(60, (process.stdout.columns ?? 120) - 1);
const tableWidth = getTerminalTableWidth();
const now = Date.now();
const hasFilters = connectedOnly || sinceMs !== undefined;
const pendingRows = hasFilters ? [] : pending;