diff --git a/docs/issues/markdown-codeblock-scrollbar-jitter/plan.md b/docs/issues/markdown-codeblock-scrollbar-jitter/plan.md new file mode 100644 index 000000000..79844ae85 --- /dev/null +++ b/docs/issues/markdown-codeblock-scrollbar-jitter/plan.md @@ -0,0 +1,150 @@ +# Markdown Codeblock Scrollbar Jitter Plan + +## Current Layout + +```text +Before + +Chat viewport + - message-list-container + - stable vertical gutter already exists + | + +-- assistant message + | + +-- MarkdownRenderer .prose + | + +-- markstream-vue code block + | + +-- code-editor-container / code-pre-fallback / pre + - overflow: auto + - wrapping may be implicit + - internal scrollbar can appear/disappear while settling + - code content area shifts on Windows +``` + +```text +After + +Chat viewport + - unchanged + | + +-- assistant message + | + +-- MarkdownRenderer .prose + | + +-- markstream-vue code block + | + +-- stable internal scrollport + - scrollbar space is reserved or otherwise kept stable + - code wrapping is explicit for Monaco rendering + - fallback/enhanced renderer swaps do not move content +``` + +## Design + +Implement the first fix as a scoped renderer code block stabilization in the app-owned Markdown +renderer. + +Recommended target file: + +- `src/renderer/src/components/markdown/MarkdownRenderer.vue` + +Candidate targets: + +- `.markstream-vue [data-markstream-code-block='1']` +- `.markstream-vue [data-markstream-code-block='1'] .code-editor-container` +- `.markstream-vue [data-markstream-code-block='1'] .code-pre-fallback` +- `.markstream-vue pre[class^='language-']` +- `.markstream-vue pre[class*=' language-']` + +Preferred first increment: + +1. Pass `wordWrap: 'on'` explicitly to Markdown code block Monaco rendering so the app does not + rely on an implicit dependency default. +2. Add `scrollbar-gutter: stable;` to the inner code block scrollports that can create vertical + classic scrollbars. +3. Keep `overflow: auto` so scrollbars are still demand-driven. +4. Do not force `overflow-x: scroll`; default code blocks should wrap. +5. Validate the issue video scenario on Windows. +6. If a horizontal scrollbar still appears in default wrapped mode, identify which renderer path is + bypassing wrap before adding any scrollbar fallback. + +Avoid these first: + +- Do not add another outer `scrollbar-gutter` to `ChatPage.vue`; it already exists. +- Do not set global `overflow-y: scroll`. +- Do not force permanent horizontal scrollbars in default wrapped code blocks. +- Do not apply broad scrollbar rules to all `.prose` content. + +## Compatibility + +- The reported runtime is Electron/Chromium on Windows. Modern Chromium supports + `scrollbar-gutter`. +- macOS overlay scrollbars should not show meaningful visual changes because overlay scrollbars do + not consume layout gutter space. +- Linux classic scrollbar behavior should benefit from the same scoped stabilization. + +## Risk Areas + +- Horizontal scrollbar appearance in default wrapped mode would indicate a renderer path bypassing + wrapping; it must be verified on Windows with classic scrollbars. +- `markstream-vue` uses scoped compiled CSS, so app overrides need enough selector specificity. +- Code block fallback and enhanced editor layers overlap during streaming; a fix must avoid clipping + either layer. +- Permanent horizontal scrollbars would be noisy and conflict with the expected default wrapping + behavior. + +## Test Strategy + +Automated checks: + +- Add or update a renderer test around `MarkdownRenderer` if implementation introduces a stable app + class or data attribute that can be asserted in jsdom. +- Add a browser-level regression check when practical: + - mount a Markdown response with a streaming code block; + - append content until the code block crosses the overflow threshold; + - measure the code block rect and first code line rect before and after the scrollbar transition; + - fail if position changes by more than 1 px. + +Manual Windows validation: + +1. Run the app on Windows 11. +2. Open a chat and stream a response containing a long fenced code block with enough lines to make + the block overflow. +3. Watch the code block during streaming and completion. +4. Confirm normal long code wraps by default and does not show a permanent horizontal scrollbar. +5. Confirm the outer chat scrollbar, sticky input, and auto-follow behavior are unchanged. +6. Repeat once in dark theme. + +Quality gates after implementation: + +- `pnpm run format` +- `pnpm run i18n` +- `pnpm run lint` +- `pnpm run typecheck` +- Targeted renderer test, for example `pnpm test:renderer -- MarkdownRenderer` + +## Validation Fixture + +Use Markdown content shaped like the issue video: + +````markdown +### Comparison + +```java +String sql = "SELECT * FROM users WHERE id = ?"; +PreparedStatement ps = conn.prepareStatement(sql); +ps.setInt(1, 1); +ResultSet rs = ps.executeQuery(); +while (rs.next()) { + String name = rs.getString("name"); + int age = rs.getInt("age"); +} + +// Repeat enough long lines to exercise wrapping and code block overflow on Windows classic scrollbars. +String veryLongLine = "SELECT first_name, last_name, email, created_at FROM users WHERE account_status = 'active' ORDER BY created_at DESC"; +``` +```` + +Expected behavior: the long code line wraps by default. If a real scrollbar is needed for vertical +overflow, the code content and block geometry should not jump while the scrollbar state changes. diff --git a/docs/issues/markdown-codeblock-scrollbar-jitter/spec.md b/docs/issues/markdown-codeblock-scrollbar-jitter/spec.md new file mode 100644 index 000000000..bae6e90c0 --- /dev/null +++ b/docs/issues/markdown-codeblock-scrollbar-jitter/spec.md @@ -0,0 +1,98 @@ +# Markdown Codeblock Scrollbar Jitter + +## Source + +- GitHub issue: https://github.com/ThinkInAIXYZ/deepchat/issues/1763 +- Reported environment: Windows 11 Home Chinese, DeepChat v1.0.6-beta.5. +- Symptom: assistant Markdown rendering shows visible scrollbar jitter while generated content settles. +- Attached video: `20260612_174024.mp4`, linked from the issue body. +- Related comments: + - https://github.com/ThinkInAIXYZ/deepchat/issues/1763#issuecomment-4706327016 + - https://github.com/ThinkInAIXYZ/deepchat/issues/1763#issuecomment-4714279781 + +## Problem Determination + +The issue is most likely not caused by the outer chat viewport scrollbar. + +Evidence: + +- The issue video shows the visible jump inside a rendered Markdown code block. A horizontal + scrollbar is visible during settling, but default code block rendering is expected to wrap, so the + scrollbar should not be treated as the desired steady state. +- The outer chat container already reserves vertical scrollbar space in `ChatPage.vue` via + `.message-list-container { scrollbar-gutter: stable both-edges; }`. +- `MarkdownRenderer.vue` wraps `markstream-vue`'s `NodeRenderer` in a `.prose` container, but does + not add any scrollbar stability rule for inner Markdown scrollports. +- `markstream-vue@1.0.1-beta.4` renders code blocks through `CodeBlockNode`, including + `.code-block-container`, `.code-editor-container`, `.code-pre-fallback`, and + `pre[class^=language-]` nodes that use `overflow: auto`. +- `markstream-vue` defaults code block word wrapping to enabled when `monacoOptions.wordWrap` is not + set to `off`, and its fallback `.code-pre-fallback.is-wrap` CSS uses wrapping rules. +- On Windows classic scrollbars consume layout space. When an inner code block switches between + fallback and enhanced rendering, or crosses an overflow threshold during streaming, those + internal scrollbars can change the code block's available content area and cause visible jitter. + +## Comment Assessment + +The comment suggesting `scrollbar-gutter: stable` is directionally reasonable, but it must be +applied to the scrollable Markdown/code block nodes that actually jitter. Applying it only to the +outer chat page would likely be ineffective because the outer container already has a stable gutter. + +`overflow-y: scroll` is a weaker fit for this report: + +- The captured symptom is inside a Markdown code block, and the most visible scrollbar in the video + is horizontal. +- Forcing vertical scrollbars globally would add permanent scrollbar chrome to many non-problem + containers. +- Forcing a horizontal scrollbar would normalize an unreasonable default state; the first fix should + preserve automatic wrapping and only stabilize real scroll containers. + +MDN documents `scrollbar-gutter` as a way to reserve classic scrollbar space for scroll containers, +with `both-edges` mirroring the gutter on inline edges. This supports using it for vertical +scrollbar reflow, but horizontal scrollbar behavior still needs explicit Windows validation. + +## User Need + +As a Windows user reading a streaming assistant response, I need Markdown code blocks to remain +visually stable while content is rendered, so the response does not look like it is shaking or +reflowing around scrollbars. + +## Goals + +- Stabilize Markdown code block scrollports on Windows classic scrollbar environments. +- Preserve default automatic wrapping for Markdown code blocks. +- Keep the existing outer chat scroll behavior unchanged. +- Avoid introducing permanent horizontal scrollbars for normal wrapped code. +- Preserve code block rendering in chat messages, artifacts, and workspace Markdown preview. +- Keep the fix scoped to renderer styling unless validation proves the issue is in + `markstream-vue` behavior. + +## Acceptance Criteria + +- A streaming Markdown response containing a long code block does not visibly jitter when the code + block crosses an overflow threshold. +- The code block container width, block height, and first rendered code line position remain stable + within 1 px while fallback/enhanced code rendering settles. +- Long code lines wrap by default and do not gain a permanent horizontal scrollbar in normal chat + rendering. +- Normal Markdown paragraphs do not gain unwanted permanent scrollbars or extra spacing. +- Chat auto-follow, manual scroll-away, and session restore behavior remain unchanged. +- Markdown artifacts and workspace Markdown previews render without clipped code blocks. +- Light and dark themes keep the current code block visual style. + +## Non-goals + +- Redesign the Markdown renderer. +- Replace `markstream-vue`. +- Rewrite chat scrolling or virtualization. +- Add a user-facing setting. +- Hide scrollbars with `scrollbar-width: none`, because that weakens discoverability and + accessibility. + +## Constraints + +- Keep the implementation local to renderer Markdown/code block styling if possible. +- Do not patch files under `node_modules`. +- Prefer CSS overrides in app-owned files over dependency forks. +- Do not force permanent horizontal scrollbars as a default-mode fallback. +- No new runtime dependencies. diff --git a/docs/issues/markdown-codeblock-scrollbar-jitter/tasks.md b/docs/issues/markdown-codeblock-scrollbar-jitter/tasks.md new file mode 100644 index 000000000..74b90161d --- /dev/null +++ b/docs/issues/markdown-codeblock-scrollbar-jitter/tasks.md @@ -0,0 +1,34 @@ +# Markdown Codeblock Scrollbar Jitter Tasks + +## Investigation + +- [x] Read GitHub issue 1763 and current comments. +- [x] Download and inspect the attached issue video. +- [x] Confirm the visible jitter is inside a Markdown code block, not the outer chat viewport. +- [x] Check local chat container styling and confirm the outer viewport already uses + `scrollbar-gutter: stable both-edges`. +- [x] Check `MarkdownRenderer.vue` and `markstream-vue` code block output for inner + `overflow: auto` scrollports. +- [x] Assess the comment recommendation and document the scope correction. + +## Implementation + +- [x] Make Markdown code block Monaco word wrapping explicit. +- [x] Add scoped CSS stabilization for Markdown code block scrollports. +- [x] Keep normal Markdown paragraphs and non-scrollable blocks unaffected. +- [x] Avoid forcing permanent horizontal scrollbars in default wrapped code blocks. +- [ ] Validate whether `scrollbar-gutter: stable` is enough for the Windows issue video scenario. +- [x] Add or update a focused renderer/browser regression check where practical. + +## Verification + +- [ ] Run the original issue-style Markdown fixture on Windows 11. +- [ ] Verify chat message code blocks during streaming and after completion. +- [ ] Verify Markdown artifacts. +- [ ] Verify workspace Markdown preview. +- [ ] Verify light and dark themes. +- [x] Run `pnpm run format`. +- [x] Run `pnpm run i18n`. +- [x] Run `pnpm run lint`. +- [x] Run `pnpm run typecheck`. +- [x] Run targeted renderer tests for `MarkdownRenderer`. diff --git a/src/renderer/src/components/markdown/MarkdownRenderer.vue b/src/renderer/src/components/markdown/MarkdownRenderer.vue index 251dcf075..3a9185772 100644 --- a/src/renderer/src/components/markdown/MarkdownRenderer.vue +++ b/src/renderer/src/components/markdown/MarkdownRenderer.vue @@ -1,5 +1,7 @@