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:
@@ -40,12 +40,6 @@ deliberate. Divergences fall into a few kinds:
|
||||
- **Why:** intentionally minimal, single-purpose composables. `waitUntilRenderFlush` is
|
||||
deliberately **not** exposed.
|
||||
|
||||
### Exported text-measurement helpers
|
||||
|
||||
- **Ink:** does not export its internal `measure-text` module.
|
||||
- **vue-tui:** exports `measureText` / `measureTextNatural` from the public index.
|
||||
- **Why:** a deliberately public utility surface for consumers who need to size text.
|
||||
|
||||
### No named type / prop re-exports
|
||||
|
||||
- **Ink:** re-exports `BoxProps`, `TextProps`, `StaticProps`, `TransformProps`,
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
|
||||
@@ -124,18 +124,6 @@ export function wrapText(text: string, width: number, mode: WrapMode = "wrap"):
|
||||
return cliTruncate(text, width, { position }).split("\n");
|
||||
}
|
||||
|
||||
export function measureText(
|
||||
text: string,
|
||||
width: number,
|
||||
mode: WrapMode = "wrap",
|
||||
): { width: number; height: number } {
|
||||
const wrapped = wrapText(text, width, mode);
|
||||
return {
|
||||
width: wrapped.reduce((max, line) => Math.max(max, stringWidth(line)), 0),
|
||||
height: wrapped.length,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Natural (unwrapped) dimensions of `text`, mode-independent. Mirrors Ink's
|
||||
* measure-text.js: width = widest line, height = number of \n-separated lines.
|
||||
|
||||
@@ -38,4 +38,6 @@ export {
|
||||
type KittyKeyboardOptions,
|
||||
type KittyFlagName,
|
||||
} from "./io/kitty-keyboard.ts";
|
||||
export { measureText, measureTextNatural } from "./host/text-measure.ts";
|
||||
// `measureText` / `measureTextNatural` are deliberately NOT re-exported: Ink keeps
|
||||
// its `measure-text` module internal, and so do we. They remain internal helpers
|
||||
// (yoga.ts uses `measureTextNatural`). See .agents/docs/ink-divergences.md.
|
||||
|
||||
Reference in New Issue
Block a user