Conversation
…omponents - Updated transition classes in MessageActionButtons.vue for smoother animations. - Refactored MessageBlockContent.vue to optimize artifact snapshot handling with computed properties. - Improved transition effects in MessageBlockToolCall.vue for better user experience. - Added a mention icon map in MessageContent.vue to streamline icon retrieval. - Enhanced MessageItemUser.vue with a new line counting function for better text handling. - Optimized MessageToolbar.vue for consistent transition effects on button interactions. - Refactored BrowserPanel.vue to simplify state management for synced bounds. - Improved ChatSidePanel.vue with better resizing and visibility handling. - Updated ChatPage.vue to enhance chat search highlight scheduling. - Cleaned up ChatTabView.vue by removing legacy collapsed new chat button functionality. - Enhanced tests in ChatTabView.test.ts and WindowSideBar.test.ts for improved coverage and accuracy.
📝 WalkthroughWalkthroughThese changes consolidate motion/easing CSS variables for consistent animations, optimize performance through memoization and request-animation-frame throttling, refactor animation approaches from Vue transitions to CSS-driven handling with accessibility support, improve component state management, and remove collapsed new-chat UI while adding provider/model store initialization. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/renderer/src/views/ChatTabView.vue (1)
101-104:⚠️ Potential issue | 🟠 MajorCatch path no longer initializes
modelStore/ollamaStore.The success path kicks off
modelStore.initialize()andollamaStore.initialize()viaPromise.allSettled(Lines 93–98), but the bootstrap-failure fallback only re-runsagentStore.fetchAgents()andprojectStore.loadDefaultProjectPath(). IfgetBootstrap()ever throws (transient IPC error, main-process race during cold start), users land in an interactive state with uninitialized model/ollama stores — which can leave model selection, Ollama integration, etc. silently broken until the user reloads.♻️ Proposed fix
} catch (error) { console.warn('[Startup][Renderer] ChatTabView critical hydration failed:', error) - await Promise.allSettled([agentStore.fetchAgents(), projectStore.loadDefaultProjectPath()]) + await Promise.allSettled([ + agentStore.fetchAgents(), + projectStore.loadDefaultProjectPath(), + modelStore.initialize(), + ollamaStore.initialize() + ]) await initializeRouteFromFallbackState() }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/renderer/src/views/ChatTabView.vue` around lines 101 - 104, The catch block after getBootstrap() currently only re-runs agentStore.fetchAgents() and projectStore.loadDefaultProjectPath(), leaving model/ollama stores uninitialized; update that fallback to also invoke and await modelStore.initialize() and ollamaStore.initialize() (e.g., include them in the Promise.allSettled alongside agentStore.fetchAgents() and projectStore.loadDefaultProjectPath()) before calling initializeRouteFromFallbackState() so model selection and Ollama integration are initialized on bootstrap failure.
🧹 Nitpick comments (8)
src/renderer/src/components/message/MessageBlockContent.vue (2)
36-37: Nit: merge the twovueimports.♻️ Proposed cleanup
-import { computed, ref } from 'vue' -import { nextTick, watch } from 'vue' +import { computed, nextTick, ref, watch } from 'vue'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/renderer/src/components/message/MessageBlockContent.vue` around lines 36 - 37, Nit: duplicate imports from 'vue' should be merged into one; replace the two separate import statements (the lines importing computed, ref and the line importing nextTick, watch) with a single import that includes all four symbols so the module only appears once (e.g., import { computed, ref, nextTick, watch } from 'vue') to clean up MessageBlockContent.vue.
58-80: Snapshot key may not differentiate adjacent artifacts in edge cases.Joining with literal separators (
::and\n__artifact__\n) means an artifact whosetitle/contenthappens to contain these tokens could produce a snapshot equal to that of a structurally different list, suppressing a legitimate update. The collision risk is low for normal markdown content, but consider a more robust hash, e.g.JSON.stringifyof a structured array, which avoids ambiguity and is similarly cheap:♻️ Proposed alternative
-const artifactSnapshot = computed(() => - processedContent.value - .filter(...) - .map((part) => { - const artifact = part.artifact - return [ - artifact.identifier, - artifact.title, - artifact.type, - artifact.language || '', - part.loading ? '1' : '0', - part.content - ].join('::') - }) - .join('\n__artifact__\n') -) +const artifactSnapshot = computed(() => + JSON.stringify( + processedContent.value + .filter((part): part is ProcessedPart & { type: 'artifact'; artifact: NonNullable<ProcessedPart['artifact']> } => + part.type === 'artifact' && Boolean(part.artifact) + ) + .map((part) => ({ + id: part.artifact.identifier, + title: part.artifact.title, + type: part.artifact.type, + language: part.artifact.language || '', + loading: part.loading, + content: part.content + })) + ) +)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/renderer/src/components/message/MessageBlockContent.vue` around lines 58 - 80, artifactSnapshot builds a string by joining artifact fields with literal separators which can collide if title/content include those tokens; replace the fragile join with a deterministic structured serialization (e.g., build an array of objects from processedContent.value with keys identifier, title, type, language, loading, content and return JSON.stringify(...) of that array) so artifactSnapshot (the computed) reliably differentiates adjacent artifacts; update the mapping inside the artifactSnapshot computed to produce the structured array instead of a joined string and then stringify it.src/renderer/src/components/message/MessageActionButtons.vue (1)
99-111: Style block still hardcodes0.3s easewhile the template now uses motion tokens.The
enter-active-class/leave-active-class(Lines 5, 8) were updated to use--dc-motion-default(220ms) and--dc-ease-out-express, but.message-action-leavingin the scoped style still definestransition: opacity 0.3s ease, transform 0.3s ease. Both apply during leave, so the visible animation duration/easing depends on CSS cascade and is inconsistent with the new design tokens.♻️ Align with design tokens
.message-action-leaving { position: absolute; width: var(--leave-w); height: var(--leave-h); left: var(--leave-l); top: var(--leave-t); pointer-events: none; - /* 控制离场的属性过渡(和 template 中的 leave-* class 一起工作) */ - transition: - opacity 0.3s ease, - transform 0.3s ease; + /* 控制离场的属性过渡(和 template 中的 leave-* class 一起工作) */ + transition: + opacity var(--dc-motion-default) var(--dc-ease-out-express), + transform var(--dc-motion-default) var(--dc-ease-out-express); }Also consider the same for
.message-actions-move(Line 96) for visual consistency across the group.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/renderer/src/components/message/MessageActionButtons.vue` around lines 99 - 111, The scoped CSS still hardcodes transition durations/easing in .message-action-leaving (transition: opacity 0.3s ease, transform 0.3s ease) which conflicts with the template's motion tokens; update .message-action-leaving to use the motion tokens used in the template (e.g., --dc-motion-default for duration and --dc-ease-out-express for easing) so the leave transition matches, and likewise change .message-actions-move to use the same tokens to ensure consistent duration/easing across move and leave animations.src/renderer/src/components/message/MessageToolbar.vue (1)
16-199: DRY: extract the repeated toolbar-button class string.The same lengthy Tailwind class string (
w-4 h-4 text-muted-foreground hover:text-primary hover:bg-transparent transition-colors duration-[var(--dc-motion-fast)] ease-[var(--dc-ease-out-soft)]) is duplicated across ~10<Button>elements. Future tweaks (e.g., changing the easing token) require touching every occurrence and risk drift.Consider extracting it once, e.g.:
♻️ Proposed refactor
<script setup lang="ts"> +const TOOLBAR_BUTTON_CLASS = + 'w-4 h-4 text-muted-foreground hover:text-primary hover:bg-transparent transition-colors duration-[var(--dc-motion-fast)] ease-[var(--dc-ease-out-soft)]'- class="w-4 h-4 text-muted-foreground hover:text-primary hover:bg-transparent transition-colors duration-[var(--dc-motion-fast)] ease-[var(--dc-ease-out-soft)]" + :class="TOOLBAR_BUTTON_CLASS"Or define a single CSS class in
<style scoped>using@applyand reuse it.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/renderer/src/components/message/MessageToolbar.vue` around lines 16 - 199, The toolbar button Tailwind string is duplicated across many <Button> elements in MessageToolbar.vue; extract it to a single reusable identifier and replace the repeated literal. Either add a scoped CSS utility (e.g., .toolbar-icon-button) in the component's <style scoped> using `@apply` with the long Tailwind string and swap each Button's class to that CSS class, or define a single constant/computed property (e.g., toolbarButtonClass) in the component script and bind it (:class="toolbarButtonClass") to all Button elements; update all occurrences (buttons using the string, e.g., the Button elements with `@click` handlers like handleCopy, handleCopyImageStart, emit('edit') etc.) to use the new reusable class.src/renderer/src/components/message/MessageBlockToolCall.vue (1)
633-664: RepositiononBeforeUnmountfor code clarity.The lifecycle hook is currently inserted between
getSubagentModeLabel(Lines 622–631) andgetSubagentStatusLabel(Lines 645–664), splitting two related helpers. Co-locating it with the other timer-related state declarations near Lines 562–565 (or grouping it with other top-level lifecycle calls) would make ownership ofparamsCopyResetTimer/responseCopyResetTimermore obvious to readers.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/renderer/src/components/message/MessageBlockToolCall.vue` around lines 633 - 664, Move the onBeforeUnmount lifecycle hook so it sits next to the timer state declarations (paramsCopyResetTimer and responseCopyResetTimer) or with other top-level lifecycle calls rather than between getSubagentModeLabel and getSubagentStatusLabel; locate the onBeforeUnmount block that clears paramsCopyResetTimer and responseCopyResetTimer and cut/paste it to directly follow the declarations of paramsCopyResetTimer/responseCopyResetTimer (or into a grouped lifecycle section) to keep ownership and related logic adjacent to getSubagentModeLabel and getSubagentStatusLabel.src/renderer/src/assets/style.css (1)
113-124: Define motion/easing tokens before the animation aliases that consume them.
--animation-accordion-down(Line 113) through--animation-pulse(Line 118) reference--dc-motion-*and--dc-ease-*which are declared a few lines later at 120–124. CSS variable resolution happens at use time so this works, but reordering improves readability and prevents confusion if someone refactors only the bottom block.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/renderer/src/assets/style.css` around lines 113 - 124, The animation alias custom properties (--animation-accordion-down, --animation-accordion-up, --animation-collapsible-down, --animation-collapsible-up, --animation-pulse) are declared before the motion/easing tokens they reference (--dc-motion-fast, --dc-motion-default, --dc-motion-slow, --dc-ease-out-express, --dc-ease-out-soft); reorder the declarations so the --dc-motion-* and --dc-ease-* variables appear before the --animation-* aliases to improve readability and avoid confusion during refactors (move the block defining --dc-motion-fast/default/slow and --dc-ease-out-express/soft above the block that defines the --animation-* variables).src/renderer/src/App.vue (1)
397-401: Drop the redundantproviderStore.ensureInitialized()call.
initAppStores()already invokesproviderStore.initialize()(seesrc/renderer/src/lib/storeInitializer.ts), andmodelStore.initialize()internally awaitsproviderStore.ensureInitialized()itself (seesrc/renderer/src/stores/modelStore.ts). BecauseuseProviderStore()was already called during setup, the singletoninitializationPromiseis created synchronously byinitAppStores()before this line runs, so the explicitensureInitialized()here just awaits the same promise.♻️ Proposed cleanup
void initAppStores() - void providerStore.ensureInitialized() void modelStore.initialize()If the intent is to surface the dependency at this call site, a clarifying comment would communicate it without the duplicate call.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/renderer/src/App.vue` around lines 397 - 401, The call to providerStore.ensureInitialized() is redundant because initAppStores() already triggers providerStore.initialize() and modelStore.initialize() awaits providerStore.ensureInitialized(); remove the providerStore.ensureInitialized() invocation from the sequence (leaving void initAppStores(), void modelStore.initialize(), void sessionStore.fetchSessions()) or, if you want to keep the dependency visible, replace that line with a short clarifying comment referencing initAppStores() and modelStore.initialize() to explain why provider initialization is guaranteed.src/renderer/src/components/sidepanel/ChatSidePanel.vue (1)
84-84: SyncPANEL_MOTION_MSwith the motion token, and short-circuit the hide delay underprefers-reduced-motion.Two related concerns with the JS hide-delay:
- Drift risk:
PANEL_MOTION_MS = 220(Line 84) duplicates the CSS--dc-motion-defaultvalue. If the token changes instyle.css, the JS schedule won't follow, so the layout collapse could outlast or under-run the surface fade.- Reduced-motion mismatch: The global rule in
style.cssforcestransition-duration: 1msunderprefers-reduced-motion, so the panel surface vanishes instantly — butsetTimeout(..., PANEL_MOTION_MS)(Line 191) still keepslayoutWidthnon-zero for ~220ms, leaving an empty gap before the column collapses.♻️ Suggested approach
-const PANEL_MOTION_MS = 220 +const PANEL_MOTION_MS_DEFAULT = 220 +const prefersReducedMotion = () => + typeof window !== 'undefined' && + window.matchMedia?.('(prefers-reduced-motion: reduce)').matches === true +const getPanelMotionMs = () => { + if (prefersReducedMotion()) return 0 + const raw = getComputedStyle(document.documentElement) + .getPropertyValue('--dc-motion-default') + .trim() + const ms = raw.endsWith('ms') ? parseFloat(raw) : raw.endsWith('s') ? parseFloat(raw) * 1000 : NaN + return Number.isFinite(ms) ? ms : PANEL_MOTION_MS_DEFAULT +} @@ - panelMotionTimer = window.setTimeout(() => { + const delay = getPanelMotionMs() + if (delay === 0) { + if (!shouldShow.value) { + layoutWidth.value = 0 + } + return + } + panelMotionTimer = window.setTimeout(() => { panelMotionTimer = null if (!shouldShow.value) { layoutWidth.value = 0 } - }, PANEL_MOTION_MS) + }, delay)Also applies to: 177-197, 237-241
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/renderer/src/components/sidepanel/ChatSidePanel.vue` at line 84, The hardcoded PANEL_MOTION_MS constant out-of-sync with the CSS motion token and ignores prefers-reduced-motion; change the hide-delay logic so PANEL_MOTION_MS is derived at runtime from the CSS --dc-motion-default value (via getComputedStyle on document.documentElement) and short-circuit to 0 when the user prefers reduced motion (matchMedia("(prefers-reduced-motion: reduce)").matches), then use this runtime value wherever PANEL_MOTION_MS is referenced (e.g., the setTimeout that clears layoutWidth and any other delays around panel hide/transition such as the code paths around layoutWidth and the hide scheduling).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/renderer/src/assets/style.css`:
- Line 852: Change the text-rendering property value to lowercase to satisfy
stylelint: locate the rule using the text-rendering property (text-rendering:
optimizeLegibility;) and replace the value with the lowercase keyword
(optimizelegibility) so it matches the project's value-keyword-case lint rule.
In `@src/renderer/src/components/chat/MessageList.vue`:
- Around line 177-180: The intrinsic-size of 180px in .message-list-row is too
small and causes getBoundingClientRect() to return the intrinsic box for
off-screen rows, breaking calculateMessageGroupRect() and scroll targeting in
setActiveChatSearchMatch(); increase the CSS estimate (e.g., change
contain-intrinsic-size to auto 300px or higher) and in
calculateMessageGroupRect() and setActiveChatSearchMatch() ensure you measure
real layout by temporarily disabling content-visibility on the target row (or
toggling a class) before calling getBoundingClientRect()/scrollIntoView(), then
restore the original content-visibility so captures and scrolling use true
rendered heights.
In `@src/renderer/src/components/markdown/MarkdownRenderer.vue`:
- Around line 87-90: The cached promise searchResultsPromise in getSearchResults
reuses a rejected promise forever; update getSearchResults to assign
searchResultsPromise =
sessionClient.getSearchResults(effectiveMessageId.value).catch(err => {
searchResultsPromise = undefined; throw err }) so the cache is cleared on
rejection and callers can retry, and update the reference handlers
(onClick/onMouseEnter) that currently call .then(...) to also handle errors (add
.catch(...) or switch to async/await with try/catch) to avoid swallowing
rejections and unhandled promise warnings.
---
Outside diff comments:
In `@src/renderer/src/views/ChatTabView.vue`:
- Around line 101-104: The catch block after getBootstrap() currently only
re-runs agentStore.fetchAgents() and projectStore.loadDefaultProjectPath(),
leaving model/ollama stores uninitialized; update that fallback to also invoke
and await modelStore.initialize() and ollamaStore.initialize() (e.g., include
them in the Promise.allSettled alongside agentStore.fetchAgents() and
projectStore.loadDefaultProjectPath()) before calling
initializeRouteFromFallbackState() so model selection and Ollama integration are
initialized on bootstrap failure.
---
Nitpick comments:
In `@src/renderer/src/App.vue`:
- Around line 397-401: The call to providerStore.ensureInitialized() is
redundant because initAppStores() already triggers providerStore.initialize()
and modelStore.initialize() awaits providerStore.ensureInitialized(); remove the
providerStore.ensureInitialized() invocation from the sequence (leaving void
initAppStores(), void modelStore.initialize(), void
sessionStore.fetchSessions()) or, if you want to keep the dependency visible,
replace that line with a short clarifying comment referencing initAppStores()
and modelStore.initialize() to explain why provider initialization is
guaranteed.
In `@src/renderer/src/assets/style.css`:
- Around line 113-124: The animation alias custom properties
(--animation-accordion-down, --animation-accordion-up,
--animation-collapsible-down, --animation-collapsible-up, --animation-pulse) are
declared before the motion/easing tokens they reference (--dc-motion-fast,
--dc-motion-default, --dc-motion-slow, --dc-ease-out-express,
--dc-ease-out-soft); reorder the declarations so the --dc-motion-* and
--dc-ease-* variables appear before the --animation-* aliases to improve
readability and avoid confusion during refactors (move the block defining
--dc-motion-fast/default/slow and --dc-ease-out-express/soft above the block
that defines the --animation-* variables).
In `@src/renderer/src/components/message/MessageActionButtons.vue`:
- Around line 99-111: The scoped CSS still hardcodes transition durations/easing
in .message-action-leaving (transition: opacity 0.3s ease, transform 0.3s ease)
which conflicts with the template's motion tokens; update
.message-action-leaving to use the motion tokens used in the template (e.g.,
--dc-motion-default for duration and --dc-ease-out-express for easing) so the
leave transition matches, and likewise change .message-actions-move to use the
same tokens to ensure consistent duration/easing across move and leave
animations.
In `@src/renderer/src/components/message/MessageBlockContent.vue`:
- Around line 36-37: Nit: duplicate imports from 'vue' should be merged into
one; replace the two separate import statements (the lines importing computed,
ref and the line importing nextTick, watch) with a single import that includes
all four symbols so the module only appears once (e.g., import { computed, ref,
nextTick, watch } from 'vue') to clean up MessageBlockContent.vue.
- Around line 58-80: artifactSnapshot builds a string by joining artifact fields
with literal separators which can collide if title/content include those tokens;
replace the fragile join with a deterministic structured serialization (e.g.,
build an array of objects from processedContent.value with keys identifier,
title, type, language, loading, content and return JSON.stringify(...) of that
array) so artifactSnapshot (the computed) reliably differentiates adjacent
artifacts; update the mapping inside the artifactSnapshot computed to produce
the structured array instead of a joined string and then stringify it.
In `@src/renderer/src/components/message/MessageBlockToolCall.vue`:
- Around line 633-664: Move the onBeforeUnmount lifecycle hook so it sits next
to the timer state declarations (paramsCopyResetTimer and
responseCopyResetTimer) or with other top-level lifecycle calls rather than
between getSubagentModeLabel and getSubagentStatusLabel; locate the
onBeforeUnmount block that clears paramsCopyResetTimer and
responseCopyResetTimer and cut/paste it to directly follow the declarations of
paramsCopyResetTimer/responseCopyResetTimer (or into a grouped lifecycle
section) to keep ownership and related logic adjacent to getSubagentModeLabel
and getSubagentStatusLabel.
In `@src/renderer/src/components/message/MessageToolbar.vue`:
- Around line 16-199: The toolbar button Tailwind string is duplicated across
many <Button> elements in MessageToolbar.vue; extract it to a single reusable
identifier and replace the repeated literal. Either add a scoped CSS utility
(e.g., .toolbar-icon-button) in the component's <style scoped> using `@apply` with
the long Tailwind string and swap each Button's class to that CSS class, or
define a single constant/computed property (e.g., toolbarButtonClass) in the
component script and bind it (:class="toolbarButtonClass") to all Button
elements; update all occurrences (buttons using the string, e.g., the Button
elements with `@click` handlers like handleCopy, handleCopyImageStart,
emit('edit') etc.) to use the new reusable class.
In `@src/renderer/src/components/sidepanel/ChatSidePanel.vue`:
- Line 84: The hardcoded PANEL_MOTION_MS constant out-of-sync with the CSS
motion token and ignores prefers-reduced-motion; change the hide-delay logic so
PANEL_MOTION_MS is derived at runtime from the CSS --dc-motion-default value
(via getComputedStyle on document.documentElement) and short-circuit to 0 when
the user prefers reduced motion (matchMedia("(prefers-reduced-motion:
reduce)").matches), then use this runtime value wherever PANEL_MOTION_MS is
referenced (e.g., the setTimeout that clears layoutWidth and any other delays
around panel hide/transition such as the code paths around layoutWidth and the
hide scheduling).
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: bbd3f77c-f309-44f7-8ec5-02c4d9708217
📒 Files selected for processing (18)
src/renderer/src/App.vuesrc/renderer/src/assets/style.csssrc/renderer/src/components/WindowSideBar.vuesrc/renderer/src/components/WindowSideBarSessionItem.vuesrc/renderer/src/components/chat/MessageList.vuesrc/renderer/src/components/markdown/MarkdownRenderer.vuesrc/renderer/src/components/message/MessageActionButtons.vuesrc/renderer/src/components/message/MessageBlockContent.vuesrc/renderer/src/components/message/MessageBlockToolCall.vuesrc/renderer/src/components/message/MessageContent.vuesrc/renderer/src/components/message/MessageItemUser.vuesrc/renderer/src/components/message/MessageToolbar.vuesrc/renderer/src/components/sidepanel/BrowserPanel.vuesrc/renderer/src/components/sidepanel/ChatSidePanel.vuesrc/renderer/src/pages/ChatPage.vuesrc/renderer/src/views/ChatTabView.vuetest/renderer/components/ChatTabView.test.tstest/renderer/components/WindowSideBar.test.ts
|
|
||
| html { | ||
| font-family: var(--dc-font-family); | ||
| text-rendering: optimizeLegibility; |
There was a problem hiding this comment.
Lowercase optimizeLegibility per stylelint value-keyword-case.
Static analysis flagged this. CSS keywords are case-insensitive at the engine level, but the project's stylelint config expects lowercase.
- text-rendering: optimizeLegibility;
+ text-rendering: optimizelegibility;📝 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.
| text-rendering: optimizeLegibility; | |
| text-rendering: optimizelegibility; |
🧰 Tools
🪛 Stylelint (17.9.0)
[error] 852-852: Expected "optimizeLegibility" to be "optimizelegibility" (value-keyword-case)
(value-keyword-case)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/renderer/src/assets/style.css` at line 852, Change the text-rendering
property value to lowercase to satisfy stylelint: locate the rule using the
text-rendering property (text-rendering: optimizeLegibility;) and replace the
value with the lowercase keyword (optimizelegibility) so it matches the
project's value-keyword-case lint rule.
| .message-list-row { | ||
| content-visibility: auto; | ||
| contain-intrinsic-size: auto 180px; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find capture/screenshot logic and chat-search highlight code to assess interaction with content-visibility.
fd -t f -e ts -e vue useMessageCapture
rg -nP -C3 "scrollIntoView|getBoundingClientRect" --type=ts --type=vue -g '!**/node_modules/**' \
| rg -i "search|highlight|capture|message"
rg -nP -C3 "html2canvas|toCanvas|toPng|domtoimage" --type=ts --type=vueRepository: ThinkInAIXYZ/deepchat
Length of output: 249
🏁 Script executed:
# Examine the capture logic
cat -n src/renderer/src/composables/message/useMessageCapture.ts | head -100Repository: ThinkInAIXYZ/deepchat
Length of output: 3897
🏁 Script executed:
# Look for chat-search highlight code
rg -n "highlight|search" src/renderer/src --type=ts -i | grep -i "search\|highlight"Repository: ThinkInAIXYZ/deepchat
Length of output: 15754
🏁 Script executed:
# Check the full MessageList.vue file context
wc -l src/renderer/src/components/chat/MessageList.vueRepository: ThinkInAIXYZ/deepchat
Length of output: 118
🏁 Script executed:
# Find MessageToolbar and examine emissions
fd -type f -name "MessageToolbar*" src/renderer/srcRepository: ThinkInAIXYZ/deepchat
Length of output: 236
🏁 Script executed:
# Continue reading useMessageCapture.ts
cat -n src/renderer/src/composables/message/useMessageCapture.ts | tail -100Repository: ThinkInAIXYZ/deepchat
Length of output: 3537
🏁 Script executed:
# Read full MessageList.vue to see current styles and content-visibility usage
cat -n src/renderer/src/components/chat/MessageList.vueRepository: ThinkInAIXYZ/deepchat
Length of output: 8366
🏁 Script executed:
# Find and examine usePageCapture
find src -name "usePageCapture*" -type fRepository: ThinkInAIXYZ/deepchat
Length of output: 167
🏁 Script executed:
# Check chatSearch for scrollIntoView usage
rg -n "scrollIntoView" src/renderer/src/lib/chatSearch.ts -A 3 -B 3Repository: ThinkInAIXYZ/deepchat
Length of output: 314
🏁 Script executed:
# Examine usePageCapture.ts to see how the actual capture is done
cat -n src/renderer/src/composables/usePageCapture.ts | head -150Repository: ThinkInAIXYZ/deepchat
Length of output: 4612
🏁 Script executed:
# Check if there are any workarounds for content-visibility in the capture flow
rg -n "content-visibility|contain-intrinsic" src/renderer/src --type=ts --type=vueRepository: ThinkInAIXYZ/deepchat
Length of output: 93
🏁 Script executed:
# Look for test files related to message capture
find test -name "*useMessageCapture*" -o -name "*capture*" | head -10Repository: ThinkInAIXYZ/deepchat
Length of output: 117
🏁 Script executed:
# Check ChatPage.vue for how chat-search is integrated
rg -n "applyChatSearchHighlights|setActiveChatSearchMatch" src/renderer/src --type=ts --type=vue -B 2 -A 2Repository: ThinkInAIXYZ/deepchat
Length of output: 93
🏁 Script executed:
# Continue reading usePageCapture.ts to see the actual capture mechanism
cat -n src/renderer/src/composables/usePageCapture.ts | tail -200Repository: ThinkInAIXYZ/deepchat
Length of output: 7269
🏁 Script executed:
# Check the test file for useMessageCapture to understand capture behavior
cat -n test/renderer/composables/useMessageCapture.test.tsRepository: ThinkInAIXYZ/deepchat
Length of output: 2982
🏁 Script executed:
# Find where chat-search highlights are applied - look in ChatPage
find src/renderer/src -name "ChatPage*"Repository: ThinkInAIXYZ/deepchat
Length of output: 101
🏁 Script executed:
# Search for applyChatSearchHighlights usage across the codebase
rg -n "applyChatSearchHighlights" src/renderer/src -A 5Repository: ThinkInAIXYZ/deepchat
Length of output: 1322
🏁 Script executed:
# Check if there's any existing handling for content-visibility issues
rg -n "content-visibility" src/renderer/src -B 2 -A 2Repository: ThinkInAIXYZ/deepchat
Length of output: 433
Verify capture-as-image accuracy and intrinsic-size tuning for content-visibility: auto.
The capture-as-image flow depends on calculateMessageGroupRect() calling getBoundingClientRect() on potentially off-screen message rows. With content-visibility: auto applied, off-screen elements may return the intrinsic-size box (180px height) rather than actual content dimensions. This breaks capture rectangle calculations, especially for:
- "Capture from top" mode: Iterative rect calculation across the full range relies on accurate element heights; mismatched heights compound across multiple captures.
- Tall assistant messages: Code blocks and artifacts can exceed 180px; capturing with the wrong height causes misaligned content in the final image.
- Search result navigation:
setActiveChatSearchMatch()callsscrollIntoView()on highlighted elements; if the element is in a skipped subtree, the scroll position target may be imprecise.
The intrinsic-size estimate of 180px works for average user messages but is too small for assistant responses with code or artifacts, causing visible scrollbar jumps when they enter the viewport. Consider a per-row or larger default estimate (e.g., auto 300px or dynamic tuning).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/renderer/src/components/chat/MessageList.vue` around lines 177 - 180, The
intrinsic-size of 180px in .message-list-row is too small and causes
getBoundingClientRect() to return the intrinsic box for off-screen rows,
breaking calculateMessageGroupRect() and scroll targeting in
setActiveChatSearchMatch(); increase the CSS estimate (e.g., change
contain-intrinsic-size to auto 300px or higher) and in
calculateMessageGroupRect() and setActiveChatSearchMatch() ensure you measure
real layout by temporarily disabling content-visibility on the target row (or
toggling a class) before calling getBoundingClientRect()/scrollIntoView(), then
restore the original content-visibility so captures and scrolling use true
rendered heights.
| const getSearchResults = () => { | ||
| searchResultsPromise ??= sessionClient.getSearchResults(effectiveMessageId.value) | ||
| return searchResultsPromise | ||
| } |
There was a problem hiding this comment.
Cached rejected promise prevents retries.
searchResultsPromise is set once and reused regardless of outcome. If the first sessionClient.getSearchResults call rejects (transient IPC/network error), every subsequent click/hover on a reference will reuse the same rejected promise, and the user will never see results until effectiveMessageId changes.
Consider clearing the cache on rejection so subsequent interactions can retry:
♻️ Proposed fix
const getSearchResults = () => {
- searchResultsPromise ??= sessionClient.getSearchResults(effectiveMessageId.value)
+ searchResultsPromise ??= sessionClient.getSearchResults(effectiveMessageId.value).catch((error) => {
+ searchResultsPromise = null
+ throw error
+ })
return searchResultsPromise
}Also note: the .then(...) callers in onClick/onMouseEnter (Lines 130, 139) currently swallow rejections silently — adding a .catch (or awaiting with try/catch) would prevent unhandled-promise warnings.
📝 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.
| const getSearchResults = () => { | |
| searchResultsPromise ??= sessionClient.getSearchResults(effectiveMessageId.value) | |
| return searchResultsPromise | |
| } | |
| const getSearchResults = () => { | |
| searchResultsPromise ??= sessionClient.getSearchResults(effectiveMessageId.value).catch((error) => { | |
| searchResultsPromise = null | |
| throw error | |
| }) | |
| return searchResultsPromise | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/renderer/src/components/markdown/MarkdownRenderer.vue` around lines 87 -
90, The cached promise searchResultsPromise in getSearchResults reuses a
rejected promise forever; update getSearchResults to assign searchResultsPromise
= sessionClient.getSearchResults(effectiveMessageId.value).catch(err => {
searchResultsPromise = undefined; throw err }) so the cache is cleared on
rejection and callers can retry, and update the reference handlers
(onClick/onMouseEnter) that currently call .then(...) to also handle errors (add
.catch(...) or switch to async/await with try/catch) to avoid swallowing
rejections and unhandled promise warnings.
…omponents (#1542) - Updated transition classes in MessageActionButtons.vue for smoother animations. - Refactored MessageBlockContent.vue to optimize artifact snapshot handling with computed properties. - Improved transition effects in MessageBlockToolCall.vue for better user experience. - Added a mention icon map in MessageContent.vue to streamline icon retrieval. - Enhanced MessageItemUser.vue with a new line counting function for better text handling. - Optimized MessageToolbar.vue for consistent transition effects on button interactions. - Refactored BrowserPanel.vue to simplify state management for synced bounds. - Improved ChatSidePanel.vue with better resizing and visibility handling. - Updated ChatPage.vue to enhance chat search highlight scheduling. - Cleaned up ChatTabView.vue by removing legacy collapsed new chat button functionality. - Enhanced tests in ChatTabView.test.ts and WindowSideBar.test.ts for improved coverage and accuracy.
* docs: add mac computer use spec * docs: sync provider tables * fix: rtk status (#1541) * fix(rtk): simplify health check * fix(knowledge): use config ipc * fix(models): persist db model status * fix: harden MCP env and knowledge delete * fix: clean knowledge presenter cleanup paths * refactor: enhance transition effects and performance across message components (#1542) - Updated transition classes in MessageActionButtons.vue for smoother animations. - Refactored MessageBlockContent.vue to optimize artifact snapshot handling with computed properties. - Improved transition effects in MessageBlockToolCall.vue for better user experience. - Added a mention icon map in MessageContent.vue to streamline icon retrieval. - Enhanced MessageItemUser.vue with a new line counting function for better text handling. - Optimized MessageToolbar.vue for consistent transition effects on button interactions. - Refactored BrowserPanel.vue to simplify state management for synced bounds. - Improved ChatSidePanel.vue with better resizing and visibility handling. - Updated ChatPage.vue to enhance chat search highlight scheduling. - Cleaned up ChatTabView.vue by removing legacy collapsed new chat button functionality. - Enhanced tests in ChatTabView.test.ts and WindowSideBar.test.ts for improved coverage and accuracy. * chore: update markstream-vue to 0.0.13 (#1544) * fix: preserve interleaved reasoning (#1543) * chore(release): prepare v1.0.4-beta.2 * fix(ipc): allow attachment date metadata (#1547) * chore(release): prepare v1.0.4-beta.3 * feat: add mac computer use helper * feat: enhance computer use guidance * chore: bump acp registry versions * fix: import mac signing identity for helper * build(cua): vendor CUA driver source * fix(cua): prefer element index click mode * fix(cua): route zoom clicks by coordinates * docs(cua): prefer visual fallback for sparse UI * feat: update vendored cua driver * fix(computer-use): improve error handling and testability * docs(cua): add runtime plugin spec * feat(plugin): add CUA runtime plugin * feat: migrate computer use to plugin * fix: surface plugin tools and permissions * fix(plugin): use MCP-only CUA flow * ci(plugin): release CUA dcplugin assets * fix(plugin): hide CUA on unsupported OS * feat(plugin): bundle official CUA plugin * fix: harden plugin startup and CUA telemetry * chore: update CUA driver vendor * fix(build): sign CUA plugin helper * fix: improve CUA window scoped vision fallback * fix(cua): align pixel clicks with upstream --------- Co-authored-by: yyhhyyyyyy <yyhhyyyyyy8@gmail.com> Co-authored-by: xiaomo <wegi866@gmail.com>
…omponents
Summary by CodeRabbit
Accessibility
prefers-reduced-motionto honor user motion preferences globallyStyle & Animation
Bug Fixes & Improvements