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:
@@ -1,5 +1,5 @@
|
||||
import type { SkillStatusEntry, SkillStatusReport } from "../agents/skills-status.js";
|
||||
import { renderTable } from "../terminal/table.js";
|
||||
import { getTerminalTableWidth, renderTable } from "../terminal/table.js";
|
||||
import { theme } from "../terminal/theme.js";
|
||||
import { shortenHomePath } from "../utils.js";
|
||||
import { formatCliCommand } from "./command-format.js";
|
||||
@@ -38,8 +38,12 @@ function formatSkillStatus(skill: SkillStatusEntry): string {
|
||||
return theme.error("✗ missing");
|
||||
}
|
||||
|
||||
function normalizeSkillEmoji(emoji?: string): string {
|
||||
return (emoji ?? "📦").replaceAll("\uFE0E", "\uFE0F");
|
||||
}
|
||||
|
||||
function formatSkillName(skill: SkillStatusEntry): string {
|
||||
const emoji = skill.emoji ?? "📦";
|
||||
const emoji = normalizeSkillEmoji(skill.emoji);
|
||||
return `${emoji} ${theme.command(skill.name)}`;
|
||||
}
|
||||
|
||||
@@ -95,7 +99,7 @@ export function formatSkillsList(report: SkillStatusReport, opts: SkillsListOpti
|
||||
}
|
||||
|
||||
const eligible = skills.filter((s) => s.eligible);
|
||||
const tableWidth = Math.max(60, (process.stdout.columns ?? 120) - 1);
|
||||
const tableWidth = getTerminalTableWidth();
|
||||
const rows = skills.map((skill) => {
|
||||
const missing = formatSkillMissingSummary(skill);
|
||||
return {
|
||||
@@ -109,7 +113,7 @@ export function formatSkillsList(report: SkillStatusReport, opts: SkillsListOpti
|
||||
|
||||
const columns = [
|
||||
{ key: "Status", header: "Status", minWidth: 10 },
|
||||
{ key: "Skill", header: "Skill", minWidth: 18, flex: true },
|
||||
{ key: "Skill", header: "Skill", minWidth: 22 },
|
||||
{ key: "Description", header: "Description", minWidth: 24, flex: true },
|
||||
{ key: "Source", header: "Source", minWidth: 10 },
|
||||
];
|
||||
@@ -154,7 +158,7 @@ export function formatSkillInfo(
|
||||
}
|
||||
|
||||
const lines: string[] = [];
|
||||
const emoji = skill.emoji ?? "📦";
|
||||
const emoji = normalizeSkillEmoji(skill.emoji);
|
||||
const status = skill.eligible
|
||||
? theme.success("✓ Ready")
|
||||
: skill.disabled
|
||||
@@ -282,7 +286,7 @@ export function formatSkillsCheck(report: SkillStatusReport, opts: SkillsCheckOp
|
||||
lines.push("");
|
||||
lines.push(theme.heading("Ready to use:"));
|
||||
for (const skill of eligible) {
|
||||
const emoji = skill.emoji ?? "📦";
|
||||
const emoji = normalizeSkillEmoji(skill.emoji);
|
||||
lines.push(` ${emoji} ${skill.name}`);
|
||||
}
|
||||
}
|
||||
@@ -291,7 +295,7 @@ export function formatSkillsCheck(report: SkillStatusReport, opts: SkillsCheckOp
|
||||
lines.push("");
|
||||
lines.push(theme.heading("Missing requirements:"));
|
||||
for (const skill of missingReqs) {
|
||||
const emoji = skill.emoji ?? "📦";
|
||||
const emoji = normalizeSkillEmoji(skill.emoji);
|
||||
const missing = formatSkillMissingSummary(skill);
|
||||
lines.push(` ${emoji} ${skill.name} ${theme.muted(`(${missing})`)}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user