diff --git a/packages/tui/src/components/markdown.ts b/packages/tui/src/components/markdown.ts index 977c35054..bc0218d7e 100644 --- a/packages/tui/src/components/markdown.ts +++ b/packages/tui/src/components/markdown.ts @@ -295,10 +295,7 @@ export class Markdown implements Component { #cachedText?: string; #cachedWidth?: number; #cachedLines?: readonly string[]; - /** When true, skip the module-level LRU (lookup and insert) for this instance's - * renders. Set for in-flight streaming partials whose text changes every frame — - * caching those churns the LRU with near-duplicate full-message snapshots. */ - transientRenderCache = false; + #transientRenderCache = false; constructor( text: string, @@ -326,6 +323,16 @@ export class Markdown implements Component { this.#cachedWidth = undefined; this.#cachedLines = undefined; } + get transientRenderCache(): boolean { + return this.#transientRenderCache; + } + + set transientRenderCache(value: boolean) { + const next = value === true; + if (this.#transientRenderCache === next) return; + this.#transientRenderCache = next; + this.invalidate(); + } render(width: number): readonly string[] { // L1: per-instance cache — fastest path for repeated renders of the same diff --git a/packages/tui/test/markdown.test.ts b/packages/tui/test/markdown.test.ts index 9a5aff5d4..8776ec06f 100644 --- a/packages/tui/test/markdown.test.ts +++ b/packages/tui/test/markdown.test.ts @@ -1279,6 +1279,29 @@ describe("Module-level LRU render cache", () => { expect(plain).not.toContain("HIGHLIGHTED"); }); + it("re-renders code-block highlighting when a transient instance becomes stable", () => { + clearRenderCache(); + let highlightCallCount = 0; + const themeWithSpy = { + ...defaultMarkdownTheme, + highlightCode: (_code: string, _lang?: string): string[] => { + highlightCallCount++; + return ["HIGHLIGHTED"]; + }, + }; + + const markdown = new Markdown("```ts\nconst streamed = true;\n```", 0, 0, themeWithSpy); + markdown.transientRenderCache = true; + const plain = stripVTControlCharacters(markdown.render(80).join("\n")); + expect(highlightCallCount).toBe(0); + expect(plain).toContain("const streamed = true;"); + + markdown.transientRenderCache = false; + const highlighted = stripVTControlCharacters(markdown.render(80).join("\n")); + expect(highlightCallCount).toBe(1); + expect(highlighted).toContain("HIGHLIGHTED"); + }); + it("skips nested list code-block highlighting for transient streaming renders", () => { clearRenderCache(); let highlightCallCount = 0;