feat(chat): add syntax highlighting for code blocks#1109
feat(chat): add syntax highlighting for code blocks#1109benoitf merged 3 commits intokortex-hub:mainfrom
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds syntax highlighting support to the markdown renderer by introducing highlight.js dependency, a new renderMarkdown function that handles anchor transformation, header ID generation, and code block highlighting, plus a GitHub-style CSS theme file. The chat component is updated to synchronize theme settings with the app's appearance. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes 🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. 📝 Coding Plan
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
packages/renderer/src/lib/markdown/syntax-highlighting.css (1)
65-71: Resolve duplicate.hljs-nametoken mapping.Line 66 maps
.hljs-nameto--hljs-keyword, then Line 124 remaps it to--hljs-tag. The first mapping is effectively dead and makes token intent harder to maintain.♻️ Proposed cleanup
.hljs-section, -.hljs-name, .hljs-selector-tag, .hljs-deletion, .hljs-subst { color: var(--hljs-keyword); }Also applies to: 123-126
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/renderer/src/lib/markdown/syntax-highlighting.css` around lines 65 - 71, The CSS maps .hljs-name twice (first in the selector group with .hljs-section, .hljs-selector-tag, .hljs-deletion, .hljs-subst to var(--hljs-keyword) and later to var(--hljs-tag)), leaving the first mapping dead and confusing; remove .hljs-name from the first selector group (or move it only into the later rule that sets var(--hljs-tag)) so .hljs-name has a single, clear mapping, and ensure both conflicting rule blocks (.hljs-section/.hljs-selector-tag/.hljs-deletion/.hljs-subst and the later .hljs-name/.hljs-tag group) reflect the intended semantic color variables.packages/renderer/src/lib/markdown/Markdown.svelte (1)
50-50: Consider importinghighlight.js/lib/coreand manually registering required languages to reduce renderer bundle size.Line 50 imports the full package, which registers all languages. According to highlight.js documentation, importing from
lib/coreand registering only needed languages results in a smaller bundle. However, this requires identifying which languages are used in your markdown and updating thehighlightElement()calls accordingly, or detecting language from code block class hints.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/renderer/src/lib/markdown/Markdown.svelte` at line 50, Replace the blanket import of highlight.js with the core build and explicitly register only the languages used by our markdown renderer: change the import in Markdown.svelte from the full package to 'highlight.js/lib/core' and import + register each needed language (e.g., javascript, typescript, bash, json, css, html) via hljs.registerLanguage(...). Keep using the existing highlightElement() call but ensure it detects the language from the code block class (e.g., "language-xyz") or falls back to auto-detection; update any code that calls highlightElement() to pass the detected language if required. This reduces bundle size while preserving the current highlighting behavior in Markdown.svelte.
🤖 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/Markdown.svelte`:
- Around line 82-97: The reactive markdown update block (the $: if (markdown) {
... } sequence that calls micromark, decode, DOMParser, highlights via
hljs.highlightElement, and assigns html) currently omits the link/heading
transforms that are applied later in onMount (the handlers that generate header
ids, rewrite podman-desktop:// links, and add jump/hash behavior), causing
inconsistencies after mount; extract the link/heading transform logic from
onMount into a shared function (e.g., applyTransforms or transformDoc) that
accepts the parsed/tempDoc or html and performs header id generation,
podman-desktop:// rewriting, and jump handling, then call that shared function
from both the reactive block (before assigning html) and from onMount so both
mount-time and reactive updates apply the same transforms.
---
Nitpick comments:
In `@packages/renderer/src/lib/markdown/Markdown.svelte`:
- Line 50: Replace the blanket import of highlight.js with the core build and
explicitly register only the languages used by our markdown renderer: change the
import in Markdown.svelte from the full package to 'highlight.js/lib/core' and
import + register each needed language (e.g., javascript, typescript, bash,
json, css, html) via hljs.registerLanguage(...). Keep using the existing
highlightElement() call but ensure it detects the language from the code block
class (e.g., "language-xyz") or falls back to auto-detection; update any code
that calls highlightElement() to pass the detected language if required. This
reduces bundle size while preserving the current highlighting behavior in
Markdown.svelte.
In `@packages/renderer/src/lib/markdown/syntax-highlighting.css`:
- Around line 65-71: The CSS maps .hljs-name twice (first in the selector group
with .hljs-section, .hljs-selector-tag, .hljs-deletion, .hljs-subst to
var(--hljs-keyword) and later to var(--hljs-tag)), leaving the first mapping
dead and confusing; remove .hljs-name from the first selector group (or move it
only into the later rule that sets var(--hljs-tag)) so .hljs-name has a single,
clear mapping, and ensure both conflicting rule blocks
(.hljs-section/.hljs-selector-tag/.hljs-deletion/.hljs-subst and the later
.hljs-name/.hljs-tag group) reflect the intended semantic color variables.
🪄 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: 1e1f569c-4f5c-419f-ae3a-b1fbd640aae4
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (3)
packages/renderer/package.jsonpackages/renderer/src/lib/markdown/Markdown.sveltepackages/renderer/src/lib/markdown/syntax-highlighting.css
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
|
|
68465d3 to
fc89405
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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/Markdown.svelte`:
- Around line 85-87: The reactive update currently only runs when markdown is
truthy, leaving previous html when markdown becomes an empty string; change the
reactive blocks that reference markdown and html (the "$: if (markdown) { html =
renderMarkdown(markdown); }" and the similar block around the 134-141 region) so
they always assign html — e.g., set html to renderMarkdown(markdown) when
markdown has content and to an empty string when it does not (use a conditional
assignment based on markdown in the reactive statement).
- Line 129: The inline arrow callback passed to
doc.querySelectorAll(...).forEach currently uses an implicit return (block =>
hljs.highlightElement(...)); change it to an explicit block-bodied arrow (block
=> { hljs.highlightElement(...); }) so the callback is clearly a side-effecting
statement; update the occurrence where doc.querySelectorAll('pre code').forEach
is called and ensure the callback body contains the highlight call wrapped in
braces.
🪄 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: 42834d68-28da-4c39-86cc-11d90588458d
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (5)
packages/renderer/package.jsonpackages/renderer/src/lib/chat/route/CustomChat.sveltepackages/renderer/src/lib/chat/route/main-chat-layout.sveltepackages/renderer/src/lib/markdown/Markdown.sveltepackages/renderer/src/lib/markdown/syntax-highlighting.css
🚧 Files skipped from review as they are similar to previous changes (2)
- packages/renderer/package.json
- packages/renderer/src/lib/markdown/syntax-highlighting.css
fc89405 to
d0ba131
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (2)
packages/renderer/src/lib/markdown/Markdown.svelte (2)
85-87:⚠️ Potential issue | 🟠 MajorReactive rendering still skips empty markdown and can leave stale content.
At Line 85, the truthy guard prevents clearing
htmlwhenmarkdownbecomes'', so old rendered output can remain visible.Proposed fix
-$: if (markdown) { - html = renderMarkdown(markdown); -} +$: { + html = renderMarkdown(markdown ?? ''); +} @@ onMount(() => { if (markdown) { text = markdown; } // Render markdown with all transformations applied - html = renderMarkdown(text); + html = renderMarkdown(markdown ? markdown : (text ?? '')); });Also applies to: 134-141
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/renderer/src/lib/markdown/Markdown.svelte` around lines 85 - 87, The reactive guard currently only runs renderMarkdown when markdown is truthy, so when markdown becomes an empty string it leaves stale html; change the reactive statements that set html (the one using markdown and the other similar block later around renderMarkdown) to always set html = markdown ? renderMarkdown(markdown) : '' (i.e., explicitly clear html when markdown is empty) and reference the renderMarkdown function and the markdown and html variables to locate and update both reactive blocks.
129-129:⚠️ Potential issue | 🟡 MinorBiome lint error remains on
forEachimplicit return callback.At Line 129, this still triggers
lint/suspicious/useIterableCallbackReturn; use a block body for side-effect-only callbacks.Proposed fix
- doc.querySelectorAll('pre code').forEach(block => hljs.highlightElement(block as HTMLElement)); + doc.querySelectorAll('pre code').forEach(block => { + hljs.highlightElement(block as HTMLElement); + });#!/bin/bash # Verify the pattern still exists in the current branch rg -n "querySelectorAll\\('pre code'\\)\\.forEach\\(block => hljs\\.highlightElement" packages/renderer/src/lib/markdown/Markdown.svelte🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/renderer/src/lib/markdown/Markdown.svelte` at line 129, The lint error is caused by the concise arrow callback used for side effects in the forEach call; change the callback for doc.querySelectorAll('pre code').forEach from a concise-body arrow to a block-body arrow (or a named function) so the callback uses a statement body that calls hljs.highlightElement(block as HTMLElement); update the callback at the forEach invocation in Markdown.svelte (referencing doc.querySelectorAll, forEach and hljs.highlightElement) to use { hljs.highlightElement(block as HTMLElement); } as the body.
🤖 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/Markdown.svelte`:
- Line 108: The code currently calls decode(rendered) before parsing, which can
re-enable escaped HTML and introduce XSS; update the DOM parsing in
Markdown.svelte to stop calling decode on the user-provided rendered string
(i.e., use rendered directly when creating doc via new
DOMParser().parseFromString) and ensure any HTML inserted with {`@html` html} is
sanitized (e.g., run through your sanitizer) or only used for trusted sources;
modify the usage around the DOMParser invocation and the html variable to remove
decode and add sanitization where appropriate.
- Around line 111-117: In the anchor-processing loop inside Markdown.svelte (the
doc.querySelectorAll('a').forEach callback), do not remove the href attribute —
keep the existing href so links remain focusable and keyboard-activatable;
instead set data-pd-jump-in-page to currentHref.substring(1) (and keep
link.classList.add('cursor-pointer')). Update the click/key activation handler
(the listener that performs smooth-scroll) to detect links with
data-pd-jump-in-page, call event.preventDefault() there, and perform the
smooth-scroll; this preserves native keyboard semantics while still enabling
in-page jump handling.
---
Duplicate comments:
In `@packages/renderer/src/lib/markdown/Markdown.svelte`:
- Around line 85-87: The reactive guard currently only runs renderMarkdown when
markdown is truthy, so when markdown becomes an empty string it leaves stale
html; change the reactive statements that set html (the one using markdown and
the other similar block later around renderMarkdown) to always set html =
markdown ? renderMarkdown(markdown) : '' (i.e., explicitly clear html when
markdown is empty) and reference the renderMarkdown function and the markdown
and html variables to locate and update both reactive blocks.
- Line 129: The lint error is caused by the concise arrow callback used for side
effects in the forEach call; change the callback for doc.querySelectorAll('pre
code').forEach from a concise-body arrow to a block-body arrow (or a named
function) so the callback uses a statement body that calls
hljs.highlightElement(block as HTMLElement); update the callback at the forEach
invocation in Markdown.svelte (referencing doc.querySelectorAll, forEach and
hljs.highlightElement) to use { hljs.highlightElement(block as HTMLElement); }
as the body.
🪄 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: 825f536b-bea0-4d6d-b553-00f1d91f3b74
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (5)
packages/renderer/package.jsonpackages/renderer/src/lib/chat/route/CustomChat.sveltepackages/renderer/src/lib/chat/route/main-chat-layout.sveltepackages/renderer/src/lib/markdown/Markdown.sveltepackages/renderer/src/lib/markdown/syntax-highlighting.css
🚧 Files skipped from review as they are similar to previous changes (3)
- packages/renderer/src/lib/chat/route/main-chat-layout.svelte
- packages/renderer/src/lib/chat/route/CustomChat.svelte
- packages/renderer/package.json
d0ba131 to
5411230
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
packages/renderer/src/lib/markdown/Markdown.svelte (1)
106-106:⚠️ Potential issue | 🔴 CriticalXSS risk:
decode()may re-enable escaped HTML.The
decode()function converts HTML entities back to raw characters before DOM parsing. If micromark escaped malicious input (e.g.,<script>),decode()would convert it back to<script>, which then gets injected via{@htmlhtml}.🔒 Proposed fix
- const doc = new DOMParser().parseFromString(decode(rendered), 'text/html'); + const doc = new DOMParser().parseFromString(rendered, 'text/html');🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/renderer/src/lib/markdown/Markdown.svelte` at line 106, The code calls decode(rendered) before new DOMParser().parseFromString(...) which can re-enable escaped HTML and allow XSS when the result is later injected via {`@html` html}; remove the decode(...) step and instead sanitize the HTML produced by micromark (the rendered variable) before parsing or injecting (e.g., run rendered through a trusted sanitizer such as DOMPurify.sanitize or your existing sanitizer) so DOMParser.parseFromString and the {`@html` html} binding only ever receive sanitized HTML; update the code paths around rendered, DOMParser.parseFromString and any use of {`@html` html} to use the sanitized output.
🧹 Nitpick comments (1)
packages/renderer/src/lib/markdown/syntax-highlighting.css (1)
5-44: Unused CSS custom properties create maintenance overhead.Several variables are defined but never used in the selectors:
--hljs-variable(lines 15, 36) —.hljs-variableuses--hljs-numberinstead (line 92)--hljs-attribute(lines 16, 37) —.hljs-attributeuses--hljs-stringinstead (line 79)--hljs-type(lines 14, 35) —.hljs-typeuses--hljs-numberinstead (line 87)If this grouping is intentional for visual consistency, consider removing the unused variables to avoid confusion. Otherwise, update the selectors to reference the intended variables.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/renderer/src/lib/markdown/syntax-highlighting.css` around lines 5 - 44, The CSS file defines custom properties (--hljs-variable, --hljs-attribute, --hljs-type) that are not actually used by the selectors; update either the selectors or the variables so names match: either remove the unused properties (--hljs-variable, --hljs-attribute, --hljs-type) to reduce clutter, or change the selectors (.hljs-variable, .hljs-attribute, .hljs-type) to reference the intended properties instead of using --hljs-number or --hljs-string; locate these names in syntax-highlighting.css and make the variables/selectors consistent across the :root/.dark blocks and the .hljs-* rules.
🤖 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/Markdown.svelte`:
- Around line 121-124: The header ID generation in the doc.querySelectorAll('h1,
h2, h3, h4, h5, h6') loop is too naive; replace the current id =
header.textContent?.toLowerCase().replace(/\s/g, '-') logic in the header
processing to a proper slugify routine that (1) trims whitespace, lowercases,
removes/normalizes diacritics, replaces any sequence of non-alphanumeric
characters with a single hyphen, collapses multiple hyphens, and strips
leading/trailing hyphens to produce valid HTML IDs, and (2) ensures uniqueness
by tracking generated slugs (e.g., a Map or object keyed by slug) and appending
a counter suffix like -1, -2 when duplicates occur before calling
header.setAttribute('id', id); this change should be applied where header is
handled inside that forEach.
---
Duplicate comments:
In `@packages/renderer/src/lib/markdown/Markdown.svelte`:
- Line 106: The code calls decode(rendered) before new
DOMParser().parseFromString(...) which can re-enable escaped HTML and allow XSS
when the result is later injected via {`@html` html}; remove the decode(...) step
and instead sanitize the HTML produced by micromark (the rendered variable)
before parsing or injecting (e.g., run rendered through a trusted sanitizer such
as DOMPurify.sanitize or your existing sanitizer) so DOMParser.parseFromString
and the {`@html` html} binding only ever receive sanitized HTML; update the code
paths around rendered, DOMParser.parseFromString and any use of {`@html` html} to
use the sanitized output.
---
Nitpick comments:
In `@packages/renderer/src/lib/markdown/syntax-highlighting.css`:
- Around line 5-44: The CSS file defines custom properties (--hljs-variable,
--hljs-attribute, --hljs-type) that are not actually used by the selectors;
update either the selectors or the variables so names match: either remove the
unused properties (--hljs-variable, --hljs-attribute, --hljs-type) to reduce
clutter, or change the selectors (.hljs-variable, .hljs-attribute, .hljs-type)
to reference the intended properties instead of using --hljs-number or
--hljs-string; locate these names in syntax-highlighting.css and make the
variables/selectors consistent across the :root/.dark blocks and the .hljs-*
rules.
🪄 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: be5f4b29-b5dd-44a8-b3e5-dde2a0d6d352
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (3)
packages/renderer/package.jsonpackages/renderer/src/lib/markdown/Markdown.sveltepackages/renderer/src/lib/markdown/syntax-highlighting.css
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/renderer/package.json
gastoner
left a comment
There was a problem hiding this comment.
I think that not every change is really related to the feature
Otherwise tried on Linux, is working fine
| const doc = parser.parseFromString(decode(html), 'text/html'); | ||
| const links = doc.querySelectorAll('a'); | ||
| links.forEach(link => { | ||
| const doc = new DOMParser().parseFromString(decode(rendered), 'text/html'); |
There was a problem hiding this comment.
Here we can really just replace the html -> rendered
There was a problem hiding this comment.
not sure what you mean here
5411230 to
a52a0bb
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/renderer/src/lib/markdown/Markdown.svelte (1)
134-140: Consider avoiding redundant rendering whenmarkdownprop is provided.When the
markdownprop is truthy,renderMarkdownis called twice on mount:
- Via the reactive binding at line 85
- Via onMount at line 140
This is functionally correct but performs redundant work.
💡 Optional optimization
onMount(() => { - if (markdown) { - text = markdown; + // Only render here if using slot content (markdown prop is empty) + // When markdown prop exists, the reactive binding already handles rendering + if (!markdown && text) { + html = renderMarkdown(text); } - - // Render markdown with all transformations applied - html = text ? renderMarkdown(text) : ''; // We create a click listener in order to execute any internal micromark commands🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/renderer/src/lib/markdown/Markdown.svelte` around lines 134 - 140, The onMount handler redundantly calls renderMarkdown when the reactive binding already rendered HTML from the markdown prop; update the onMount in Markdown.svelte so it only assigns text = markdown (or otherwise sets the prop) and does not call renderMarkdown again — i.e., remove the html = text ? renderMarkdown(text) : '' line from onMount (or guard it to run only if html is empty) and let the existing reactive block (which uses renderMarkdown) perform rendering for consistency with the markdown/text/reactive flow.
🤖 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/Markdown.svelte`:
- Around line 134-140: The onMount handler redundantly calls renderMarkdown when
the reactive binding already rendered HTML from the markdown prop; update the
onMount in Markdown.svelte so it only assigns text = markdown (or otherwise sets
the prop) and does not call renderMarkdown again — i.e., remove the html = text
? renderMarkdown(text) : '' line from onMount (or guard it to run only if html
is empty) and let the existing reactive block (which uses renderMarkdown)
perform rendering for consistency with the markdown/text/reactive flow.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: a57719cf-2b32-44f0-bb19-46cfe7007a12
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (4)
packages/renderer/package.jsonpackages/renderer/src/lib/chat/route/CustomChat.sveltepackages/renderer/src/lib/markdown/Markdown.sveltepackages/renderer/src/lib/markdown/syntax-highlighting.css
🚧 Files skipped from review as they are similar to previous changes (2)
- packages/renderer/src/lib/chat/route/CustomChat.svelte
- packages/renderer/src/lib/markdown/syntax-highlighting.css
26952a0 to
6412eb4
Compare
|
there is now a conflict as I merged another PR related to Markdown file probably #1115 will also conflict, I don't know which one you want to merge first ? |
The chat views use @sejohnson/svelte-themes ThemeProvider with attribute="class" to control dark mode via Tailwind's dark: variant. This is the only part of the app that uses ThemeProvider — the rest uses Appearance.svelte with the isDark store and --pd-* CSS variables. On restart with appearance set to "Dark", the ThemeProvider would read its own localStorage value and apply "light" before the app's config loaded, causing mixed light/dark colors in the chat UI. Fix: use forcedTheme prop bound to the app's isDark store so the ThemeProvider always syncs with the app's appearance setting. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> Signed-off-by: Fred Bricon <fbricon@gmail.com>
Add spacing between code blocks and following content to prevent text from appearing cramped against the code block. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> Signed-off-by: Fred Bricon <fbricon@gmail.com>
6412eb4 to
58089db
Compare
|
I fixed the conflict here. Once you merge it I'll move on to the other one |
Integrates highlight.js to provide syntax highlighting for code snippets in chat messages. Uses CSS variables for automatic light/dark theme switching. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> Signed-off-by: Fred Bricon <fbricon@gmail.com>
58089db to
5be7b52
Compare
Integrates highlight.js to provide syntax highlighting for code snippets in
chat messages. Uses CSS variables for automatic light/dark theme switching.
Light theme:

Dark Theme:

Fixes 2 related issues: