diff --git a/.agents/docs/ink-divergences.md b/.agents/docs/ink-divergences.md index 2a5cbb1..cfb5d0f 100644 --- a/.agents/docs/ink-divergences.md +++ b/.agents/docs/ink-divergences.md @@ -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`, diff --git a/packages/runtime-tests/integration/components/borders.test.tsx b/packages/runtime-tests/integration/components/borders.test.tsx index a75ff47..2a9b213 100644 --- a/packages/runtime-tests/integration/components/borders.test.tsx +++ b/packages/runtime-tests/integration/components/borders.test.tsx @@ -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("↘"); diff --git a/packages/runtime-tests/integration/layout/grapheme-clip.test.tsx b/packages/runtime-tests/integration/layout/grapheme-clip.test.tsx index d61f0c8..38d1754 100644 --- a/packages/runtime-tests/integration/layout/grapheme-clip.test.tsx +++ b/packages/runtime-tests/integration/layout/grapheme-clip.test.tsx @@ -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)", () => { diff --git a/packages/runtime-tests/integration/layout/overflow.test.tsx b/packages/runtime-tests/integration/layout/overflow.test.tsx index 70b71e4..73058a9 100644 --- a/packages/runtime-tests/integration/layout/overflow.test.tsx +++ b/packages/runtime-tests/integration/layout/overflow.test.tsx @@ -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", () => { diff --git a/packages/runtime-tests/integration/public-api.test.ts b/packages/runtime-tests/integration/public-api.test.ts index 6143ee0..2435eff 100644 --- a/packages/runtime-tests/integration/public-api.test.ts +++ b/packages/runtime-tests/integration/public-api.test.ts @@ -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"); +}); diff --git a/packages/runtime/src/host/text-measure.ts b/packages/runtime/src/host/text-measure.ts index 53f428d..411a8e7 100644 --- a/packages/runtime/src/host/text-measure.ts +++ b/packages/runtime/src/host/text-measure.ts @@ -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. diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index c827906..48bcd40 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -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.