From 3a029aa6841ac61f64029b05175e9606df3245dd Mon Sep 17 00:00:00 2001 From: Yunfei He Date: Sun, 14 Jun 2026 13:20:13 +0800 Subject: [PATCH] docs(runtime): record TuiNode-via-TuiApp as accepted incidental exposure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review (Codex) flagged that `TuiApp extends Omit, "mount">` surfaces the internal `TuiNode` host-node type in the published .d.ts (it rides out on Vue's internal `App._container`). Decision: KEEP it / don't fix. Rationale: `_container` is a Vue-internal field no consumer touches, so the exposure is purely cosmetic (zero functional impact), and type-only surface isn't held to strict SemVer, so it imposes no real contract. Hiding it (`App` or a `Pick` allowlist) is ceremony for a cosmetic gain on a pre-1.0 lib. Documented at the TuiApp definition and in api-contract.md so it isn't re-flagged. No behavior/type change — just a conscious-decision record. Co-Authored-By: Claude Opus 4.8 (1M context) --- .agents/docs/api-contract.md | 11 +++++++++++ packages/runtime/src/render.ts | 12 ++++++++++++ 2 files changed, 23 insertions(+) diff --git a/.agents/docs/api-contract.md b/.agents/docs/api-contract.md index bd8537b..8bfbb89 100644 --- a/.agents/docs/api-contract.md +++ b/.agents/docs/api-contract.md @@ -43,3 +43,14 @@ Placement rule for any export: Packaging/build internals (the `exports` field shape, `.mjs` paths, `dist` layout) are likewise **not** part of the behavioral/type contract and are not aligned to Ink — see the alignment-scope note in [[ink-divergences]]. + +### Accepted incidental exposure: `TuiNode` via `TuiApp` + +`TuiNode` is an `/internal` type, but it is **incidentally reachable** through the public +`TuiApp`, which `extends Omit, "mount">` to inherit Vue's full app surface — Vue's +`App` carries the host type on its internal `_container` field. This is a **conscious +non-fix, not a contract**: `_container` is a Vue-internal field no consumer uses, so the exposure +is cosmetic (zero functional impact), and type-only surface isn't held to strict SemVer. Narrowing +it (`App` / a `Pick` allowlist) was considered and skipped as ceremony for a +cosmetic gain on a pre-1.0 library. Treat `TuiNode`-through-`TuiApp` as out-of-contract; don't +re-flag it. (Decision recorded after review surfaced it.) diff --git a/packages/runtime/src/render.ts b/packages/runtime/src/render.ts index 96e3f69..3b1d1c0 100644 --- a/packages/runtime/src/render.ts +++ b/packages/runtime/src/render.ts @@ -142,6 +142,18 @@ export interface MountOptions { kittyKeyboard?: KittyKeyboardOptions; } +// Extends `App` (the renderer's real app type) so `TuiApp` inherits Vue's full app +// surface — `use`/`component`/`provide`/`config`/… — for free. +// +// This DOES surface the internal `TuiNode` host-node type in the published `.d.ts`: Vue's +// `App` uses the generic only in `mount(rootContainer: HostElement)` (which we +// `Omit`+redefine) and the internal `_container: HostElement | null`, so `TuiNode` rides out +// on `_container`. That is KNOWN AND ACCEPTED — not a big deal: `_container` is a Vue-internal +// field consumers never touch, so the exposure is purely cosmetic (zero functional/usability +// impact), and a type-only surface isn't held to strict SemVer, so it imposes no real public +// contract. Hiding it (`App`, or a `Pick` allowlist) was considered and +// deliberately skipped: it's ceremony for a cosmetic gain on a pre-1.0 library. Please don't +// re-flag this. See .agents/docs/api-contract.md. export interface TuiApp extends Omit, "mount"> { mount(options?: MountOptions): ComponentPublicInstance; waitUntilExit(): Promise;