openclaw - 💡(How to fix) Fix [Bug]: renderTable misaligns borders when a wide CJK/emoji grapheme lands in a narrow (width-1) column [1 pull requests]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

renderTable (packages/terminal-core/src/table.ts) breaks its border-alignment invariant when a wide (width-2) CJK or emoji grapheme lands in a column whose content width is 1. wrapLine cannot split a single grapheme, and padCell only pads — it never truncates — so the content row renders one column wider than the border/separator lines and the right border is pushed out of alignment.

The repo already asserts this invariant for wide graphemes at a comfortable width (packages/terminal-core/src/table.test.ts — "keeps table borders aligned when cells contain wide emoji graphemes", which checks visibleWidth(line) === width for every line). The narrow-column boundary is simply uncovered.

This is reachable in real CLI output: renderTable backs many status/list tables (devices, plugins, skills, nodes, pairing, dns, etc.), whose cells can hold user/plugin-provided names containing CJK or emoji, and columns shrink on narrow terminals.

Root Cause

padCell (packages/terminal-core/src/table.ts) returns the cell text unchanged whenever visibleWidth(text) >= width, so an un-wrappable over-wide grapheme overflows the cell.

Fix Action

Fixed

Code Example

node --import tsx -e '
import { renderTable } from "./packages/terminal-core/src/table.ts";
import { visibleWidth } from "./packages/terminal-core/src/ansi.ts";
const out = renderTable({
  width: 10, border: "ascii",
  columns: [{ key: "A", header: "long header here" }, { key: "B", header: "", flex: true }],
  rows: [{ A: "data", B: "中" }],
});
const lines = out.trimEnd().split("\n");
console.log("line widths:", JSON.stringify(lines.map(visibleWidth)));
console.log(out);
'

---

line widths: [24,24,24,25,24]
+------------------+---+
| long header here |   |
+------------------+---+
| data             ||
+------------------+---+
RAW_BUFFERClick to expand / collapse

Bug type

Regression / correctness (terminal table rendering)

Summary

renderTable (packages/terminal-core/src/table.ts) breaks its border-alignment invariant when a wide (width-2) CJK or emoji grapheme lands in a column whose content width is 1. wrapLine cannot split a single grapheme, and padCell only pads — it never truncates — so the content row renders one column wider than the border/separator lines and the right border is pushed out of alignment.

The repo already asserts this invariant for wide graphemes at a comfortable width (packages/terminal-core/src/table.test.ts — "keeps table borders aligned when cells contain wide emoji graphemes", which checks visibleWidth(line) === width for every line). The narrow-column boundary is simply uncovered.

This is reachable in real CLI output: renderTable backs many status/list tables (devices, plugins, skills, nodes, pairing, dns, etc.), whose cells can hold user/plugin-provided names containing CJK or emoji, and columns shrink on narrow terminals.

Steps to reproduce

On upstream main (444562b3de), run:

node --import tsx -e '
import { renderTable } from "./packages/terminal-core/src/table.ts";
import { visibleWidth } from "./packages/terminal-core/src/ansi.ts";
const out = renderTable({
  width: 10, border: "ascii",
  columns: [{ key: "A", header: "long header here" }, { key: "B", header: "", flex: true }],
  rows: [{ A: "data", B: "中" }],
});
const lines = out.trimEnd().split("\n");
console.log("line widths:", JSON.stringify(lines.map(visibleWidth)));
console.log(out);
'

Current behavior (upstream main)

line widths: [24,24,24,25,24]
+------------------+---+
| long header here |   |
+------------------+---+
| data             | 中 |
+------------------+---+

The data row is 25 columns while every border line is 24 — the right border no longer lines up. A more minimal trigger: renderTable({ border: "ascii", padding: 0, columns: [{ key: "B", header: "B", minWidth: 1, maxWidth: 1 }], rows: [{ B: "中" }] }) yields a width-4 content row against width-3 borders.

Expected behavior

Every rendered line should have equal visible width (the invariant the existing wide-emoji test asserts). A wide grapheme that cannot fit a narrow cell should be clamped so the cell stays exactly width columns, rather than overflowing and misaligning the borders.

Root cause

padCell (packages/terminal-core/src/table.ts) returns the cell text unchanged whenever visibleWidth(text) >= width, so an un-wrappable over-wide grapheme overflows the cell.

Candidate fix shape

Add an ANSI-aware truncateToVisibleWidth(input, maxWidth) helper to packages/terminal-core/src/ansi.ts that drops whole grapheme clusters that would overflow (preserving ANSI sequences, including trailing resets, so styling does not bleed), and have padCell clamp over-wide content through it before padding. That keeps visibleWidth(line) === width for all lines.

Duplicate search

Searched open issues (table border alignment, table wide emoji column, renderTable CJK) and open/closed PRs (padCell, truncateToVisibleWidth, terminal-core table align, table border wide grapheme, renderTable alignment) — no existing issue or PR covers this. (PR #88209 fixes an unrelated token formatter; PR #55596 touches a different Markdown IR table renderer in src/markdown/ir.ts.)

Environment

OpenClaw main @ 444562b3de

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

Every rendered line should have equal visible width (the invariant the existing wide-emoji test asserts). A wide grapheme that cannot fit a narrow cell should be clamped so the cell stays exactly width columns, rather than overflowing and misaligning the borders.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - 💡(How to fix) Fix [Bug]: renderTable misaligns borders when a wide CJK/emoji grapheme lands in a narrow (width-1) column [1 pull requests]