Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/web-code-block-font-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---

web: Fix chat code blocks rendering in the proportional UI font at the wrong size after the markdown renderer upgrade, and align the loading fallback with the highlighted block so the upgrade no longer shifts layout.
63 changes: 50 additions & 13 deletions apps/kimi-web/src/components/chat/Markdown.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
clearMermaidWorker,
} from 'markstream-vue';
import type { MarkdownIt } from 'markstream-vue';
import { useAppearance } from '../../composables/client/useAppearance';
import { useIsDark } from '../../composables/useIsDark';
import type { FilePreviewRequest } from '../../types';
import { collectFilePathAliases, findFilePathLinks } from '../../lib/filePathLinks';
Expand Down Expand Up @@ -74,6 +75,7 @@ function disableInlineMath(md: MarkdownIt): MarkdownIt {
}

const { t } = useI18n();
const { uiFontSize } = useAppearance();

const resolveImage = inject<(src: string) => Promise<string>>('resolveImage');
const mdRef = ref<HTMLElement | null>(null);
Expand Down Expand Up @@ -332,19 +334,29 @@ const CODE_DARK_THEME = 'github-dark';
// Chat code blocks show no gutter: line numbers eat 3+ characters of reading
// width and every chat block starts at line 1 anyway. stream-diffs derives its
// gutter from `lineNumbers === false` (boolean, not monaco's 'off' string).
// This rides inside codeBlockProps because markstream 1.0.7 only forwards the
// This rides inside codeBlockProps because markstream only forwards the
// top-level codeBlockMonacoOptions to the 'monaco' renderer kind — the 'shiki'
// kind's props object omits it, while codeBlockProps reach the same component.
const codeBlockProps = {
//
// fontSize / fontFamily matter as much as the gutter: since 1.0.9 the shiki
// renderer (stream-diffs) applies these options as inline styles on the code
// container (`applyEditorStyles`), so CSS overrides cannot beat them. Keep the
// numeric font size tied to the shared Appearance setting; recomputing the
// props also lets markstream update already-mounted blocks when it changes.
const codeBlockProps = computed(() => ({
showHeader: true,
showCopyButton: true,
showExpandButton: false,
showPreviewButton: false,
showCollapseButton: false,
showFontSizeButtons: false,
loading: false,
monacoOptions: { lineNumbers: false },
};
monacoOptions: {
lineNumbers: false,
fontSize: uiFontSize.value,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Pass a matching lineHeight with fontSize

When the user changes the Appearance font size after a chat code block has already mounted, this reactive object now changes only monacoOptions.fontSize. In markstream-vue 1.0.9-beta.1, the prop watcher updates mounted stream-diffs editors with updateOptions({ fontSize }), and the fallback <pre> reads props.monacoOptions.lineHeight before using its own default, so the added CSS line-height: 1.65 is overridden by inline styles and the editor/fallback no longer keep the intended rhythm at non-default sizes. Please pass a numeric lineHeight derived from uiFontSize alongside fontSize so mounted and fallback blocks scale together. Fresh evidence: this commit made codeBlockProps reactive but still omits lineHeight.

Useful? React with 👍 / 👎.

fontFamily: 'var(--font-mono)',
},
}));

// Root cause for the "large session turns into code skeletons" failure:
// markstream mounts every code block in the loaded transcript, then shiki has
Expand Down Expand Up @@ -629,9 +641,9 @@ function copyDiff(code: string, idx: number) {
font-family: var(--font-ui);
}
/* Copy button — mirrors the §03 IconButton: muted glyph, sunken hover, soft
radius, and the shared focus ring. markstream renders its own button, so we
restyle it in place instead of swapping in the IconButton primitive. */
.md :deep(.code-block-header .copy-button),
radius, and the shared focus ring. markstream renders its own button (the
`.code-action-btn` class since 1.0.9 — the old `.copy-button` is gone), so
we restyle it in place instead of swapping in the IconButton primitive. */
.md :deep(.code-block-header .code-action-btn) {
color: var(--color-text-muted);
background: transparent;
Expand All @@ -641,24 +653,49 @@ function copyDiff(code: string, idx: number) {
transition: background var(--duration-base) var(--ease-out),
color var(--duration-base) var(--ease-out);
}
.md :deep(.code-block-header .copy-button:hover),
.md :deep(.code-block-header .code-action-btn:hover) {
background: var(--color-surface-sunken);
color: var(--color-text);
}
.md :deep(.code-block-header .copy-button:focus-visible),
.md :deep(.code-block-header .code-action-btn:focus-visible) {
outline: none;
box-shadow: var(--p-focus-ring);
}
.md :deep(.code-block-header .copy-button *),
.md :deep(.code-block-header .code-action-btn *) {
pointer-events: none;
}
.md :deep(.code-block-content),
/* The code body wrapper was renamed in 1.0.9: `.code-block-content` is gone,
the shiki (stream-diffs) block now mounts under `.code-block-shell-content`. */
.md :deep(.code-block-shell-content),
.md :deep(.markstream-pre) {
background: var(--color-surface-sunken);
}
/* Pierre renders the highlighted code inside a shadow root. Its native gap
variable inherits through that boundary; the old Monaco `padding` option
does not. Keep both layers on the same product spacing token. Line height
stays relative so it follows the reactive font size without relying on
markstream to re-apply a second numeric editor option. */
.md :deep(.code-editor-container) {
line-height: 1.65;
--diffs-gap-block: var(--space-3);
}
.md :deep(.code-editor-container diffs-container) {
--diffs-line-height: 1.65em;
}
/* Loading/streaming fallback <pre>: upstream hardcodes show-line-numbers on
it while the settled stream-diffs block honors lineNumbers:false — the
gutter (and its reserved padding) popping in and out on every load is a
visible flash. Hide the fallback gutter and pin its left inset to the
settled per-line padding (1ch) so the fallback → highlighted swap is
layout-stable. */
.md :deep(.code-pre-fallback > .markstream-pre__line-numbers) {
display: none;
}
.md :deep(.code-block-container .code-pre-fallback) {
padding-block: var(--space-3);
padding-left: 1ch;
line-height: 1.65;
}
.md :deep(.code-block-container pre:not(.code-pre-fallback):not(.markstream-pre--line-numbers)),
.md :deep(.markstream-pre:not(.code-pre-fallback):not(.markstream-pre--line-numbers)) {
margin: 0;
Expand All @@ -676,8 +713,8 @@ function copyDiff(code: string, idx: number) {
}
.md :deep(.markstream-pre),
.md :deep(.code-pre-fallback),
.md :deep(.code-block-content pre:not(.shiki)),
.md :deep(.code-block-content pre:not(.shiki) code) {
.md :deep(.code-block-shell-content pre:not(.shiki)),
.md :deep(.code-block-shell-content pre:not(.shiki) code) {
color: var(--color-text);
}

Expand Down
Loading