Skip to content

fix: preserve text following colons in markdown rendering#1115

Open
fbricon wants to merge 1 commit intokortex-hub:mainfrom
fbricon:GH-1107
Open

fix: preserve text following colons in markdown rendering#1115
fbricon wants to merge 1 commit intokortex-hub:mainfrom
fbricon:GH-1107

Conversation

@fbricon
Copy link
Contributor

@fbricon fbricon commented Mar 17, 2026

Unrecognized micromark directives (e.g. :2b in "granite3.2:2b")
were silently stripped. Add a wildcard fallback handler that outputs the
raw text for any directive not handled by button, image, link, or warnings.

Before the change:
Screenshot 2026-03-17 at 14 15 36

After, the same message is rendered properly:
Screenshot 2026-03-17 at 14 16 23

Fixes #1107

@fbricon fbricon requested a review from a team as a code owner March 17, 2026 13:33
@fbricon fbricon requested review from MarsKubeX and benoitf and removed request for a team March 17, 2026 13:33
@codecov
Copy link

codecov bot commented Mar 17, 2026

Codecov Report

❌ Patch coverage is 66.66667% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...r/src/lib/markdown/micromark-fallback-directive.ts 66.66% 3 Missing ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitai
Copy link

coderabbitai bot commented Mar 18, 2026

📝 Walkthrough

Walkthrough

Adds a micromark fallback directive handler that preserves unrecognized directive-like text during markdown rendering, wires it into the Markdown renderer, and adds tests to assert the original raw text remains present.

Changes

Cohort / File(s) Summary
Markdown Renderer Configuration
packages/renderer/src/lib/markdown/Markdown.svelte
Adds import and registration of fallback in directive HTML extension config via wildcard key ('*': fallback) so unknown directives are handled by the fallback.
Directive Fallback Handler
packages/renderer/src/lib/markdown/micromark-fallback-directive.ts
New module exporting fallback(d: Directive): void that reconstructs and emits unrecognized directive syntax (e.g., :name[label]{attrs}) into micromark output using the compile context raw writer.
Directive Preservation Tests
packages/renderer/src/lib/markdown/Markdown.spec.ts
Adds describe('Unrecognized directives') tests asserting that colon-separated or unknown-directive-like strings (e.g., look at that:poof!, :unknown[some label]) remain in the rendered markdown-content.
Misc / Added File
Untitled
A new single-line file containing the text Markdown (no trailing newline).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning An 'Untitled' file with single line 'Markdown' appears unrelated to the directive fallback functionality; all other changes are scoped to fixing the reported issue. Remove the 'Untitled' file or clarify its purpose, as it appears to be an accidental commit unrelated to the markdown directive fix.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: adding fallback handling for unrecognized directives to preserve text following colons in markdown.
Description check ✅ Passed The description is directly related to the changeset, explaining the problem (unrecognized directives being stripped), the solution (wildcard fallback handler), and provides before/after visual proof.
Linked Issues check ✅ Passed The PR directly addresses issue #1107 by implementing a fallback handler that preserves unrecognized directive text, fixing the reported behavior where text after colons was being stripped.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@jeffmaury jeffmaury left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM but shouldn't we fix it on Podman Desktop side and backport it here ? @benoitf

@benoitf
Copy link
Contributor

benoitf commented Mar 18, 2026

for this one there is already some custom addition only on Kortex, I'm fine for now if it's not backported

Copy link
Contributor

@MarsKubeX MarsKubeX left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@benoitf benoitf enabled auto-merge (rebase) March 19, 2026 18:07
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
packages/renderer/src/lib/markdown/Markdown.spec.ts (1)

266-280: Consider adding a test for directives with attributes.

The tests cover the basic cases well. Consider adding a test for the attributes reconstruction path to ensure full coverage of the fallback handler:

test('Expect unrecognized directive with label and attributes to be preserved', async () => {
  await waitRender({ markdown: ':unknown[some label]{foo="bar"}' });
  const markdownContent = screen.getByRole('region', { name: 'markdown-content' });
  expect(markdownContent).toBeInTheDocument();
  expect(markdownContent.textContent).toContain(':unknown[some label]{foo="bar"}');
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/renderer/src/lib/markdown/Markdown.spec.ts` around lines 266 - 280,
Add a new test to the "Unrecognized directives" suite that exercises the
attributes reconstruction fallback: create a test titled like "Expect
unrecognized directive with label and attributes to be preserved" that calls
waitRender({ markdown: ':unknown[some label]{foo=\"bar\"}' }), then select the
rendered region via screen.getByRole('region', { name: 'markdown-content' }) and
assert the element exists and its textContent contains the exact string
':unknown[some label]{foo="bar"}' to cover the attributes path in the fallback
handler.
packages/renderer/src/lib/markdown/micromark-fallback-directive.ts (1)

19-20: Avoid blanket @ts-nocheck; prefer targeted fixes.

Using @ts-nocheck disables all type checking for the file. The this context typing issue can be resolved with a more targeted approach, similar to how other directive handlers in this codebase might handle it.

♻️ Suggested approach
-/* eslint-disable `@typescript-eslint/ban-ts-comment` */
-// `@ts-nocheck`
 import type { Directive } from 'micromark-extension-directive';
+import type { CompileContext } from 'micromark-util-types';
+
+// Type the function with explicit `this` context
+export function fallback(this: CompileContext, d: Directive): void {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/renderer/src/lib/markdown/micromark-fallback-directive.ts` around
lines 19 - 20, Remove the blanket file-level "@ts-nocheck" and instead fix the
specific typing issues: remove the "// `@ts-nocheck`" and add explicit "this"
typings on the micromark directive handler functions (e.g., declare handlers as
"function <handlerName>(this: unknown | HTMLElement | ParserState, ...)" or the
precise type used elsewhere in the repo) or add a single-line "//
`@ts-expect-error`" only where an unavoidable mismatch occurs; target symbols to
update are the directive handler functions in micromark-fallback-directive.ts
(the exported directive/handler definitions), replacing the global suppression
with localized typing/ts-comment fixes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/renderer/src/lib/markdown/micromark-fallback-directive.ts`:
- Around line 38-43: In the block inside micromark-fallback-directive.ts that
builds attrs (the code that iterates d.attributes, maps to `${key}="${value}"`
and calls this.raw), filter out attribute entries whose value is undefined,
null, or an empty string before mapping so you don't emit key="undefined" or
empty attributes; update the logic that constructs attrs (the
Object.entries(d.attributes).map(...) chain) to first .filter(([key, value]) =>
value != null && value !== '') and then join the remaining pairs into the
`{...}` string passed to this.raw.
- Around line 34-42: The directive renderer currently emits raw label and
attribute content using this.raw(...) which can allow unencoded user input;
update the handler that writes d.label and attribute values to HTML-encode them
(use this.encode(...) for label and for each attribute value) before calling
this.raw so labels and values are safely escaped, while preserving the
surrounding literal characters (':' prefix, '['/']' and '{'/' }'). Target the
code handling d.label and the mapping over d.attributes (and keep the existing
attribute key names as-is).

---

Nitpick comments:
In `@packages/renderer/src/lib/markdown/Markdown.spec.ts`:
- Around line 266-280: Add a new test to the "Unrecognized directives" suite
that exercises the attributes reconstruction fallback: create a test titled like
"Expect unrecognized directive with label and attributes to be preserved" that
calls waitRender({ markdown: ':unknown[some label]{foo=\"bar\"}' }), then select
the rendered region via screen.getByRole('region', { name: 'markdown-content' })
and assert the element exists and its textContent contains the exact string
':unknown[some label]{foo="bar"}' to cover the attributes path in the fallback
handler.

In `@packages/renderer/src/lib/markdown/micromark-fallback-directive.ts`:
- Around line 19-20: Remove the blanket file-level "@ts-nocheck" and instead fix
the specific typing issues: remove the "// `@ts-nocheck`" and add explicit "this"
typings on the micromark directive handler functions (e.g., declare handlers as
"function <handlerName>(this: unknown | HTMLElement | ParserState, ...)" or the
precise type used elsewhere in the repo) or add a single-line "//
`@ts-expect-error`" only where an unavoidable mismatch occurs; target symbols to
update are the directive handler functions in micromark-fallback-directive.ts
(the exported directive/handler definitions), replacing the global suppression
with localized typing/ts-comment fixes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2dc4bd11-f430-4812-acd2-b9ade8e7c371

📥 Commits

Reviewing files that changed from the base of the PR and between 7651253 and 27e2a50.

📒 Files selected for processing (3)
  • packages/renderer/src/lib/markdown/Markdown.spec.ts
  • packages/renderer/src/lib/markdown/Markdown.svelte
  • packages/renderer/src/lib/markdown/micromark-fallback-directive.ts

Comment on lines +34 to +42
this.raw(':' + (d.name ?? ''));
if (d.label) {
this.raw('[' + d.label + ']');
}
if (d.attributes && Object.keys(d.attributes).length > 0) {
const attrs = Object.entries(d.attributes)
.map(([key, value]) => `${key}="${value}"`)
.join(' ');
this.raw('{' + attrs + '}');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Consider HTML-encoding user content to prevent XSS.

Using this.raw() emits content without HTML encoding. While the DOMParser in Markdown.svelte provides some sanitization, it's safer to encode user-supplied content (label and attribute values) to prevent potential injection issues. Other directive handlers in this codebase use this.encode() for attribute values.

🛡️ Proposed defensive fix
 export function fallback(d: Directive): void {
   if (d.type === 'textDirective') {
     // Reconstruct the original text: :name[label]{attributes}
     this.raw(':' + (d.name ?? ''));
     if (d.label) {
-      this.raw('[' + d.label + ']');
+      this.raw('[' + this.encode(d.label) + ']');
     }
     if (d.attributes && Object.keys(d.attributes).length > 0) {
       const attrs = Object.entries(d.attributes)
-        .map(([key, value]) => `${key}="${value}"`)
+        .filter(([, value]) => value !== undefined && value !== '')
+        .map(([key, value]) => `${key}="${this.encode(value)}"`)
         .join(' ');
-      this.raw('{' + attrs + '}');
+      if (attrs) {
+        this.raw('{' + attrs + '}');
+      }
     }
   }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
this.raw(':' + (d.name ?? ''));
if (d.label) {
this.raw('[' + d.label + ']');
}
if (d.attributes && Object.keys(d.attributes).length > 0) {
const attrs = Object.entries(d.attributes)
.map(([key, value]) => `${key}="${value}"`)
.join(' ');
this.raw('{' + attrs + '}');
this.raw(':' + (d.name ?? ''));
if (d.label) {
this.raw('[' + this.encode(d.label) + ']');
}
if (d.attributes && Object.keys(d.attributes).length > 0) {
const attrs = Object.entries(d.attributes)
.filter(([, value]) => value !== undefined && value !== '')
.map(([key, value]) => `${key}="${this.encode(value)}"`)
.join(' ');
if (attrs) {
this.raw('{' + attrs + '}');
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/renderer/src/lib/markdown/micromark-fallback-directive.ts` around
lines 34 - 42, The directive renderer currently emits raw label and attribute
content using this.raw(...) which can allow unencoded user input; update the
handler that writes d.label and attribute values to HTML-encode them (use
this.encode(...) for label and for each attribute value) before calling this.raw
so labels and values are safely escaped, while preserving the surrounding
literal characters (':' prefix, '['/']' and '{'/' }'). Target the code handling
d.label and the mapping over d.attributes (and keep the existing attribute key
names as-is).

Comment on lines +38 to +43
if (d.attributes && Object.keys(d.attributes).length > 0) {
const attrs = Object.entries(d.attributes)
.map(([key, value]) => `${key}="${value}"`)
.join(' ');
this.raw('{' + attrs + '}');
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Handle empty or undefined attribute values.

When value is undefined or an empty string, the current code would produce malformed output like key="undefined" or emit empty attributes. Consider filtering these out.

🔧 Suggested fix
     if (d.attributes && Object.keys(d.attributes).length > 0) {
       const attrs = Object.entries(d.attributes)
+        .filter(([, value]) => value != null && value !== '')
         .map(([key, value]) => `${key}="${value}"`)
         .join(' ');
-      this.raw('{' + attrs + '}');
+      if (attrs) {
+        this.raw('{' + attrs + '}');
+      }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (d.attributes && Object.keys(d.attributes).length > 0) {
const attrs = Object.entries(d.attributes)
.map(([key, value]) => `${key}="${value}"`)
.join(' ');
this.raw('{' + attrs + '}');
}
if (d.attributes && Object.keys(d.attributes).length > 0) {
const attrs = Object.entries(d.attributes)
.filter(([, value]) => value != null && value !== '')
.map(([key, value]) => `${key}="${value}"`)
.join(' ');
if (attrs) {
this.raw('{' + attrs + '}');
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/renderer/src/lib/markdown/micromark-fallback-directive.ts` around
lines 38 - 43, In the block inside micromark-fallback-directive.ts that builds
attrs (the code that iterates d.attributes, maps to `${key}="${value}"` and
calls this.raw), filter out attribute entries whose value is undefined, null, or
an empty string before mapping so you don't emit key="undefined" or empty
attributes; update the logic that constructs attrs (the
Object.entries(d.attributes).map(...) chain) to first .filter(([key, value]) =>
value != null && value !== '') and then join the remaining pairs into the
`{...}` string passed to this.raw.

…#1107)

Unrecognized micromark directives (e.g. :poof in "look at that:poof!")
were silently stripped. Add a wildcard fallback handler that outputs the
raw text for any directive not handled by button, image, link, or warnings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Fred Bricon <fbricon@gmail.com>
auto-merge was automatically disabled March 19, 2026 18:27

Head branch was pushed to by a user without write access

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/renderer/src/lib/markdown/micromark-fallback-directive.ts (1)

19-21: Consider removing @ts-nocheck in favor of explicit this typing.

Blanket disabling TypeScript checking loses type safety for the entire file. TypeScript supports explicit this parameter typing which would preserve type checking while addressing the compile context typing:

import type { CompileContext } from 'micromark-util-types';

export function fallback(this: CompileContext, d: Directive): void {
  // ...
}

This keeps the rest of the file type-checked and documents the expected context explicitly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/renderer/src/lib/markdown/micromark-fallback-directive.ts` around
lines 19 - 21, Remove the file-wide "@ts-nocheck" and instead add an explicit
"this" parameter type to the fallback function to preserve typechecking; import
the CompileContext type from 'micromark-util-types' and declare the function
signature as fallback(this: CompileContext, d: Directive): void (or the
appropriate return type) so TypeScript can type the compile context while
keeping the Directive import in place.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/renderer/src/lib/markdown/micromark-fallback-directive.ts`:
- Around line 19-21: Remove the file-wide "@ts-nocheck" and instead add an
explicit "this" parameter type to the fallback function to preserve
typechecking; import the CompileContext type from 'micromark-util-types' and
declare the function signature as fallback(this: CompileContext, d: Directive):
void (or the appropriate return type) so TypeScript can type the compile context
while keeping the Directive import in place.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c80b05ce-428c-4080-8895-68f9234bce3f

📥 Commits

Reviewing files that changed from the base of the PR and between 27e2a50 and e6b5aea.

📒 Files selected for processing (4)
  • Untitled
  • packages/renderer/src/lib/markdown/Markdown.spec.ts
  • packages/renderer/src/lib/markdown/Markdown.svelte
  • packages/renderer/src/lib/markdown/micromark-fallback-directive.ts
✅ Files skipped from review due to trivial changes (2)
  • Untitled
  • packages/renderer/src/lib/markdown/Markdown.svelte
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/renderer/src/lib/markdown/Markdown.spec.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

User chat messages strip text immediately following a colon

4 participants