fix(runtime): keep text-measure helpers internal, matching Ink (#68)

Ink keeps its `measure-text` module internal and never re-exports it. vue-tui
exported `measureText` / `measureTextNatural` from the public index under the
mistaken belief — stated verbatim in commit 0e7d775's own message — that doing
so "matched Ink's public API". It does not; Ink keeps that module internal. A
later design doc then rationalized the leak post-hoc as an intentional
divergence. It was neither intentional nor a divergence — it was a mistake.

Align with Ink:
- Drop both from the public index. `measureTextNatural` stays as an internal
  helper (yoga.ts uses it). `measureText` had zero production callers (yoga uses
  `wrapText` + `measureTextNatural`, never `measureText`) and is removed.
- Integration tests that used `measureText(stripAnsi(x), 9999).width` as a
  line-width helper now use `stringWidth(stripAnsi(x))` directly.
- public-api.test.ts gains a regression test asserting neither is exported.
- Remove the now-obsolete entry from .agents/docs/ink-divergences.md.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yunfei He
2026-05-30 18:53:34 +08:00
committed by GitHub
parent 936f2ee1ee
commit 688da13872
7 changed files with 21 additions and 26 deletions
@@ -1,8 +1,9 @@
import { defineComponent, shallowRef, nextTick } from "vue";
import { test } from "vite-plus/test";
import { render } from "@vue-tui/testing";
import { Box, Text, measureText } from "@vue-tui/runtime";
import { Box, Text } from "@vue-tui/runtime";
import stripAnsi from "strip-ansi";
import stringWidth from "string-width";
// single node — full width box
test("single node - full width box", async ({ expect }) => {
@@ -977,7 +978,7 @@ test("arrow border on narrow box does not overflow", async ({ expect }) => {
);
const frame = lastFrame()!;
for (const line of frame.split("\n")) {
expect(measureText(stripAnsi(line), 9999).width).toBeLessThanOrEqual(3);
expect(stringWidth(stripAnsi(line))).toBeLessThanOrEqual(3);
}
const stripped = stripAnsi(frame);
expect(stripped.split("\n")[0]).toContain("↘");
@@ -1,11 +1,12 @@
import { defineComponent } from "vue";
import { describe, expect, test } from "vite-plus/test";
import { render } from "@vue-tui/testing";
import { Box, Text, measureText } from "@vue-tui/runtime";
import { Box, Text } from "@vue-tui/runtime";
import stripAnsi from "strip-ansi";
import stringWidth from "string-width";
function lineWidth(text: string): number {
return measureText(stripAnsi(text), 9999).width;
return stringWidth(stripAnsi(text));
}
describe("grapheme-aware clipping (issue #21)", () => {
@@ -1,8 +1,9 @@
import { defineComponent } from "vue";
import { describe, expect, test } from "vite-plus/test";
import { render } from "@vue-tui/testing";
import { Box, Text, Transform, measureText } from "@vue-tui/runtime";
import { Box, Text, Transform } from "@vue-tui/runtime";
import stripAnsi from "strip-ansi";
import stringWidth from "string-width";
/** Build a round-border box string like boxen(text, { borderStyle: "round" }) */
function box(text: string): string {
@@ -586,7 +587,7 @@ test("out of bounds writes do not crash", async () => {
// --- absolute overlay wide glyph clipping (issue #10) ---
function lineWidth(text: string): number {
return measureText(stripAnsi(text), 9999).width;
return stringWidth(stripAnsi(text));
}
describe("absolute overlay wide glyph clipping", () => {
@@ -27,7 +27,6 @@ test("public API exposes documented members", () => {
"useAnimation",
"useBoxMetrics",
"measureElement",
"measureText",
"usePaste",
// Rendering
"renderToString",
@@ -43,3 +42,12 @@ test("public API exposes documented members", () => {
test("useWindowSize is an alias for useTerminalSize", () => {
expect(api.useWindowSize).toBe(api.useTerminalSize);
});
// Ink keeps its `measure-text` module internal and does not re-export it. vue-tui
// once exported `measureText`/`measureTextNatural` under the (incorrect) belief it
// "matched Ink's public API" — it does not. These stay internal; this guards the
// alignment against re-introduction. See .agents/docs/ink-divergences.md.
test("does not expose internal text-measurement helpers (Ink keeps them internal)", () => {
expect(api).not.toHaveProperty("measureText");
expect(api).not.toHaveProperty("measureTextNatural");
});