From 8ded53c306aecb7bb6c69073920457d253310246 Mon Sep 17 00:00:00 2001 From: qer Date: Fri, 31 Jul 2026 17:22:27 +0800 Subject: [PATCH 1/4] fix(web): restore chat code block styling after the markstream upgrade markstream-vue 1.0.9 renders shiki code blocks through stream-diffs (code-editor-container) instead of
, and drives font size, line
height, and font family from monacoOptions applied as inline styles.
With only lineNumbers passed, blocks fell back to 12px/18px in the
inherited proportional UI font, and the pre/copy-button/content CSS
overrides in Markdown.vue no longer matched anything.

Pass fontSize/lineHeight/fontFamily/padding through codeBlockProps
monacoOptions (the only channel that reaches the shiki renderer),
retarget the dead overrides to code-block-shell-content and
code-action-btn, and hide the loading fallback's hardcoded line-number
gutter so the highlight upgrade no longer shifts layout.
---
 .changeset/web-code-block-font-fix.md         |  5 ++
 .../kimi-web/src/components/chat/Markdown.vue | 47 ++++++++++++++-----
 2 files changed, 41 insertions(+), 11 deletions(-)
 create mode 100644 .changeset/web-code-block-font-fix.md

diff --git a/.changeset/web-code-block-font-fix.md b/.changeset/web-code-block-font-fix.md
new file mode 100644
index 0000000000..630f644595
--- /dev/null
+++ b/.changeset/web-code-block-font-fix.md
@@ -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.
diff --git a/apps/kimi-web/src/components/chat/Markdown.vue b/apps/kimi-web/src/components/chat/Markdown.vue
index 25a9936c05..36035124bc 100644
--- a/apps/kimi-web/src/components/chat/Markdown.vue
+++ b/apps/kimi-web/src/components/chat/Markdown.vue
@@ -332,9 +332,17 @@ 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.
+//
+// fontSize / lineHeight / fontFamily / padding 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 — the values must be passed here. Left out, the block
+// falls back to 12px/18px in the inherited UI font (proportional sans, not
+// the mono the old `pre` CSS used to impose). Numbers mirror --text-sm (13px)
+// and its 1.65 line height; padding mirrors the old 12px vertical pre padding.
 const codeBlockProps = {
   showHeader: true,
   showCopyButton: true,
@@ -343,7 +351,13 @@ const codeBlockProps = {
   showCollapseButton: false,
   showFontSizeButtons: false,
   loading: false,
-  monacoOptions: { lineNumbers: false },
+  monacoOptions: {
+    lineNumbers: false,
+    fontSize: 13,
+    lineHeight: 21.45,
+    fontFamily: 'var(--font-mono)',
+    padding: { top: 12, bottom: 12 },
+  },
 };
 
 // Root cause for the "large session turns into code skeletons" failure:
@@ -629,9 +643,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;
@@ -641,24 +655,35 @@ 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);
 }
+/* Loading/streaming fallback 
: 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-left: 1ch;
+}
 .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;
@@ -676,8 +701,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);
 }
 

From c97d715900bcc3f9788e00fdd2ef300941602e54 Mon Sep 17 00:00:00 2001
From: qer 
Date: Fri, 31 Jul 2026 18:37:14 +0800
Subject: [PATCH 2/4] fix(web): keep code block metrics in sync

---
 .../kimi-web/src/components/chat/Markdown.vue | 36 ++++++++++++-------
 1 file changed, 24 insertions(+), 12 deletions(-)

diff --git a/apps/kimi-web/src/components/chat/Markdown.vue b/apps/kimi-web/src/components/chat/Markdown.vue
index 36035124bc..3d8abe7958 100644
--- a/apps/kimi-web/src/components/chat/Markdown.vue
+++ b/apps/kimi-web/src/components/chat/Markdown.vue
@@ -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';
@@ -74,6 +75,7 @@ function disableInlineMath(md: MarkdownIt): MarkdownIt {
 }
 
 const { t } = useI18n();
+const { uiFontSize } = useAppearance();
 
 const resolveImage = inject<(src: string) => Promise>('resolveImage');
 const mdRef = ref(null);
@@ -336,14 +338,12 @@ const CODE_DARK_THEME = 'github-dark';
 // top-level codeBlockMonacoOptions to the 'monaco' renderer kind — the 'shiki'
 // kind's props object omits it, while codeBlockProps reach the same component.
 //
-// fontSize / lineHeight / fontFamily / padding 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 — the values must be passed here. Left out, the block
-// falls back to 12px/18px in the inherited UI font (proportional sans, not
-// the mono the old `pre` CSS used to impose). Numbers mirror --text-sm (13px)
-// and its 1.65 line height; padding mirrors the old 12px vertical pre padding.
-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,
@@ -353,12 +353,10 @@ const codeBlockProps = {
   loading: false,
   monacoOptions: {
     lineNumbers: false,
-    fontSize: 13,
-    lineHeight: 21.45,
+    fontSize: uiFontSize.value,
     fontFamily: 'var(--font-mono)',
-    padding: { top: 12, bottom: 12 },
   },
-};
+}));
 
 // Root cause for the "large session turns into code skeletons" failure:
 // markstream mounts every code block in the loaded transcript, then shiki has
@@ -672,6 +670,18 @@ function copyDiff(code: string, idx: number) {
 .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 
: 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
@@ -682,7 +692,9 @@ function copyDiff(code: string, idx: number) {
   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)) {

From effcf8365c36e08e93ca8adce89c415c7e29af05 Mon Sep 17 00:00:00 2001
From: qer 
Date: Fri, 31 Jul 2026 18:55:47 +0800
Subject: [PATCH 3/4] fix(web): pin chat code font to 13px and align the
 loading fallback
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Tying the code font size to the Appearance UI font size setting changed the
default rendering from the 13px design token to 14px and broke the 15/14/13
type scale; revert to the fixed token size. Keep the fallback ↔ settled
alignment: the restored monaco padding option feeds the fallback pre's
inline padding (12px, ignored by the shadow-root renderer), and a relative
1.65 line-height with !important beats the inline 1.5x default upstream
stamps on the fallback.
---
 .../kimi-web/src/components/chat/Markdown.vue | 35 ++++++++++---------
 1 file changed, 18 insertions(+), 17 deletions(-)

diff --git a/apps/kimi-web/src/components/chat/Markdown.vue b/apps/kimi-web/src/components/chat/Markdown.vue
index 3d8abe7958..267520c637 100644
--- a/apps/kimi-web/src/components/chat/Markdown.vue
+++ b/apps/kimi-web/src/components/chat/Markdown.vue
@@ -12,7 +12,6 @@ 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';
@@ -75,7 +74,6 @@ function disableInlineMath(md: MarkdownIt): MarkdownIt {
 }
 
 const { t } = useI18n();
-const { uiFontSize } = useAppearance();
 
 const resolveImage = inject<(src: string) => Promise>('resolveImage');
 const mdRef = ref(null);
@@ -340,10 +338,12 @@ const CODE_DARK_THEME = 'github-dark';
 //
 // 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(() => ({
+// container (`applyEditorStyles`), so CSS overrides cannot beat them — the
+// values must be passed here. fontSize mirrors --text-sm (13px). `padding`
+// is ignored by the settled renderer (it draws inside a shadow root; its
+// vertical padding comes from `--diffs-gap-block` below) but sets the loading
+// fallback's inline padding, keeping the fallback → settled swap stable.
+const codeBlockProps = {
   showHeader: true,
   showCopyButton: true,
   showExpandButton: false,
@@ -353,10 +353,11 @@ const codeBlockProps = computed(() => ({
   loading: false,
   monacoOptions: {
     lineNumbers: false,
-    fontSize: uiFontSize.value,
+    fontSize: 13,
     fontFamily: 'var(--font-mono)',
+    padding: { top: 12, bottom: 12 },
   },
-}));
+};
 
 // Root cause for the "large session turns into code skeletons" failure:
 // markstream mounts every code block in the loaded transcript, then shiki has
@@ -671,10 +672,10 @@ function copyDiff(code: string, idx: number) {
   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. */
+   variable inherits through that boundary; the Monaco `padding` option above
+   does not, but it sets the loading fallback's inline padding — both layers
+   end up at 12px. Line height stays relative (1.65) so every rendering path
+   shares the token ratio without pinning a px value into inline styles. */
 .md :deep(.code-editor-container) {
   line-height: 1.65;
   --diffs-gap-block: var(--space-3);
@@ -685,16 +686,16 @@ function copyDiff(code: string, idx: number) {
 /* Loading/streaming fallback 
: 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. */
+   visible flash. Hide the fallback gutter, pin its left inset to the settled
+   per-line padding (1ch), and force the shared 1.65 line height over the
+   inline 1.5×-font-size default upstream stamps on the pre, 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;
+  line-height: 1.65 !important;
 }
 .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)) {

From b85b1d78529f99485d911a15249f9c0ded34bc11 Mon Sep 17 00:00:00 2001
From: qer 
Date: Fri, 31 Jul 2026 19:03:07 +0800
Subject: [PATCH 4/4] chore(web): pin markstream-vue and its renderer stack to
 exact versions
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The markstream family ships breaking rendering changes in patch releases
(1.0.8 swapped the code-block engine and renamed its DOM classes), so a
floating caret range hands merge control to upstream. Pin markstream-vue,
stream-diffs, stream-markdown and stream-monaco exactly — upgrades become
deliberate actions with a visual check, same posture as @chenglou/pretext
in the same file.
---
 apps/kimi-web/package.json | 8 ++++----
 pnpm-lock.yaml             | 8 ++++----
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/apps/kimi-web/package.json b/apps/kimi-web/package.json
index 87d0a2041c..7e0d79ed06 100644
--- a/apps/kimi-web/package.json
+++ b/apps/kimi-web/package.json
@@ -18,13 +18,13 @@
     "@xterm/addon-fit": "^0.11.0",
     "@xterm/xterm": "^6.0.0",
     "katex": "^0.17.0",
-    "markstream-vue": "^1.0.9-beta.1",
+    "markstream-vue": "1.0.9-beta.1",
     "mermaid": "^11.15.0",
     "monaco-editor": "^0.55.1",
     "shiki": "^4.3.0",
-    "stream-diffs": "^0.0.2",
-    "stream-markdown": "^0.0.16",
-    "stream-monaco": "^0.0.49",
+    "stream-diffs": "0.0.2",
+    "stream-markdown": "0.0.16",
+    "stream-monaco": "0.0.49",
     "vue": "^3.5.35",
     "vue-i18n": "^11.4.5"
   },
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 980988e3bf..72d8deb208 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -218,7 +218,7 @@ importers:
         specifier: ^0.17.0
         version: 0.17.0
       markstream-vue:
-        specifier: ^1.0.9-beta.1
+        specifier: 1.0.9-beta.1
         version: 1.0.9-beta.1(katex@0.17.0)(mermaid@11.15.0)(stream-diffs@0.0.2(@shikijs/themes@4.3.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vue@3.5.35(typescript@6.0.2)))(stream-markdown@0.0.16(react@19.2.5)(shiki@4.3.0)(vue@3.5.35(typescript@6.0.2)))(stream-monaco@0.0.49(monaco-editor@0.55.1))(vue-i18n@11.4.5(vue@3.5.35(typescript@6.0.2)))(vue@3.5.35(typescript@6.0.2))
       mermaid:
         specifier: ^11.15.0
@@ -230,13 +230,13 @@ importers:
         specifier: ^4.3.0
         version: 4.3.0
       stream-diffs:
-        specifier: ^0.0.2
+        specifier: 0.0.2
         version: 0.0.2(@shikijs/themes@4.3.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vue@3.5.35(typescript@6.0.2))
       stream-markdown:
-        specifier: ^0.0.16
+        specifier: 0.0.16
         version: 0.0.16(react@19.2.5)(shiki@4.3.0)(vue@3.5.35(typescript@6.0.2))
       stream-monaco:
-        specifier: ^0.0.49
+        specifier: 0.0.49
         version: 0.0.49(monaco-editor@0.55.1)
       vue:
         specifier: ^3.5.35