From 4cd957976e83ee3d86008e402ac9dc6c211b72c7 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Sun, 16 Aug 2026 21:05:43 -0700 Subject: [PATCH] fix(native-chat): cover footnote definitions and correct the doc comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GFM adds a second definition construct. `[^note]: I meant this literally` was still rendering an empty bubble on a user row — the same total content loss, one construct over — so disable `gfmFootnoteDefinition` alongside `definition`. The comment claimed reference links survive. Measured, they do not: they resolve *against* the definitions this removes, so `[d]`, `[d][]` and `![alt][d]` now render literally. That is the right reading for prose, but the comment said the opposite. Also pin that the untouched-content cases render something, since comparing the two prop values to each other is trivially true if the plugin ever stops attaching. --- .../remark-disable-definitions.test.tsx | 10 ++++++++ .../sidebar/remark-disable-definitions.ts | 23 +++++++++++-------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/renderer/src/components/sidebar/remark-disable-definitions.test.tsx b/src/renderer/src/components/sidebar/remark-disable-definitions.test.tsx index 0cb0e25094f..659854876b8 100644 --- a/src/renderer/src/components/sidebar/remark-disable-definitions.test.tsx +++ b/src/renderer/src/components/sidebar/remark-disable-definitions.test.tsx @@ -22,6 +22,12 @@ describe('disableLinkDefinitions', () => { ['inside a bullet', '- [Image #1]: /tmp/a.png', '[Image #1]: /tmp/a.png'], ['inside an ordered item', '1. [Image #1]: /tmp/a.png', '[Image #1]: /tmp/a.png'], ['inside a blockquote', '> [Image #1]: /tmp/a.png', '[Image #1]: /tmp/a.png'], + // GFM adds a second definition construct that swallows prose the same way. + [ + 'as a footnote definition', + '[^note]: I meant this literally', + '[^note]: I meant this literally' + ], // A link label may span lines, so the swallow is not a single-line shape. [ 'with a wrapped label', @@ -49,6 +55,10 @@ describe('disableLinkDefinitions', () => { '~~~\ncode\n```\n[Image #1]: /tmp/a.png\n~~~', '| a | b |\n| - | - |\n| 1 | 2 |' ])('leaves content the pipeline already rendered untouched', (typed) => { + // Why the second assertion: comparing the two prop values to each other is + // trivially true if the plugin ever stops attaching, so pin that these + // render something before trusting that they render the same thing. + expect(rendered(typed, true)).not.toBe('') expect(rendered(typed, true)).toBe(rendered(typed, false)) }) diff --git a/src/renderer/src/components/sidebar/remark-disable-definitions.ts b/src/renderer/src/components/sidebar/remark-disable-definitions.ts index 67b1d46c9e0..b9fae1bf54d 100644 --- a/src/renderer/src/components/sidebar/remark-disable-definitions.ts +++ b/src/renderer/src/components/sidebar/remark-disable-definitions.ts @@ -1,19 +1,24 @@ import type { Processor } from 'unified' /** - * Stop CommonMark from reading `[label]: /some/target` as a link reference - * definition. + * Stop the parser from reading `[label]: /some/target` (and its GFM footnote + * sibling `[^label]: text`) as a definition. * - * Definitions render to nothing at all, so on content that is prose rather than - * authored Markdown — a chat turn the user typed — such a line silently deletes - * itself. Escaping the bracket in the source text only reaches lines at the top - * level; a definition inside a list item or blockquote, or one whose label wraps - * across lines, is still swallowed. Turning the construct off covers every - * container, and leaves inline links, reference *links*, and GFM intact. + * A definition renders to nothing at all, so on content that is prose rather + * than authored Markdown — a chat turn the user typed — such a line silently + * deletes itself. Escaping the bracket in the source text only reaches lines at + * the top level; a definition inside a list item or blockquote, or one whose + * label wraps across lines, is still swallowed. Turning the constructs off + * covers every container without rewriting a byte of what the user wrote. + * + * Inline links and the rest of GFM are untouched. Reference links necessarily + * render literally, because they resolve *against* the definitions this removes + * — which is the right reading for prose, where `[d]` is far more likely to be + * a bracket the user typed than a link they meant to define. */ export function remarkDisableDefinitions(this: Processor): undefined { const data = this.data() const extensions = (data.micromarkExtensions ??= []) - extensions.push({ disable: { null: ['definition'] } }) + extensions.push({ disable: { null: ['definition', 'gfmFootnoteDefinition'] } }) return undefined }