Skip to content

refactor: enhance transition effects and performance across message c… - #1542

Merged
zerob13 merged 1 commit into
devfrom
animate
Apr 27, 2026
Merged

refactor: enhance transition effects and performance across message c…#1542
zerob13 merged 1 commit into
devfrom
animate

Conversation

@zhangmo8

@zhangmo8 zhangmo8 commented Apr 27, 2026

Copy link
Copy Markdown
Collaborator

…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.

Summary by CodeRabbit

  • Accessibility

    • Added support for prefers-reduced-motion to honor user motion preferences globally
    • Enhanced keyboard navigation with improved ARIA labels
  • Style & Animation

    • Refined animation timing and easing across UI components for smoother interactions
    • Standardized motion tokens for consistent transitions in chat, sidebar, and panels
  • Bug Fixes & Improvements

    • Removed duplicate new-chat button UI
    • Enhanced copy-to-clipboard feedback timing
    • Performance optimizations for faster rendering and scrolling

…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.
@coderabbitai

coderabbitai Bot commented Apr 27, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

These 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

Cohort / File(s) Summary
App & Store Initialization
src/renderer/src/App.vue
Adds providerStore and modelStore initialization in onMounted; refactors error-display timer from Vue ref to plain let variable, removing debug logging.
Design System & Motion Tokens
src/renderer/src/assets/style.css
Centralizes motion and easing CSS variables (--dc-motion-*, --dc-ease-out-*); adds smooth transitions to html/body; enforces accessibility via prefers-reduced-motion: reduce override.
Sidebar Components
src/renderer/src/components/WindowSideBar.vue, src/renderer/src/components/WindowSideBarSessionItem.vue
WindowSideBar: Replaces Vue Transition with CSS-driven animations and aria-hidden/inert hiding; throttles scroll-trigger logic via requestAnimationFrame. WindowSideBarSessionItem: Reduces animation durations to 150/160ms, standardizes easing, removes backdrop-filter and hover box-shadow, fixes pin-button transform definitions.
Message Rendering & List Optimization
src/renderer/src/components/chat/MessageList.vue, src/renderer/src/components/message/MessageItemUser.vue
MessageList: Wraps each message in per-row container with content-visibility: auto for browser-level layout optimization. MessageItemUser: Refactors line counting scanner; throttles textarea auto-resize via requestAnimationFrame with cleanup on unmount.
Message Component Animations & Interactivity
src/renderer/src/components/message/MessageActionButtons.vue, src/renderer/src/components/message/MessageToolbar.vue, src/renderer/src/components/message/MessageBlockToolCall.vue
Switch from hardcoded Tailwind classes to CSS-variable-driven transition timing/easing; reduce vertical translate distances; add explicit duration/easing to button hover states; improve copy-to-clipboard feedback with per-action timeout tracking and cleanup.
Message Content & Artifacts
src/renderer/src/components/message/MessageBlockContent.vue, src/renderer/src/components/markdown/MarkdownRenderer.vue, src/renderer/src/components/message/MessageContent.vue
MessageBlockContent: Derives deterministic artifactSnapshot to gate artifact updates only on actual content changes. MarkdownRenderer: Memoizes sessionClient.getSearchResults promise with cache clearing on message-id change. MessageContent: Extracts icon map to shared constant to eliminate repeated construction.
Side Panel & Resizing
src/renderer/src/components/sidepanel/BrowserPanel.vue, src/renderer/src/components/sidepanel/ChatSidePanel.vue
BrowserPanel: Converts lastSyncedBounds from ref to plain let. ChatSidePanel: Separates panel width and visibility control; refactors resizing to throttle via requestAnimationFrame with centralized cleanup; updates transitions to use CSS variables and honors prefers-reduced-motion.
Chat State & Search
src/renderer/src/pages/ChatPage.vue
Optimizes displayMessages to single-pass construction with explicit types; defers and throttles chat-search highlight refresh via requestAnimationFrame with early-return validation after visibility check.
UI & Routing
src/renderer/src/views/ChatTabView.vue
Removes collapsed new-chat UI (template, imports, state, handlers, styling) and related store/i18n dependencies.
Test Updates
test/renderer/components/ChatTabView.test.ts, test/renderer/components/WindowSideBar.test.ts
ChatTabView: Adds startup bootstrap mocking; updates assertions from loadDefaultProjectPath to applyBootstrapDefaultProjectPath; changes collapsed-button tests to verify non-rendering. WindowSideBar: Stubs AgentAvatar; shifts group expand/collapse assertions from text visibility to aria-expanded attribute checks.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • zerob13

Poem

🐰 A thousand hops through motion tokens bright,
RAF frames now throttle reflow's flight,
Cache memos whisper, snapshots stay,
Accessibility shows the CSS way!
One button collapsed, but no tears shed—
Just cleaner flows where stores are led.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is truncated and vague, using generic terms like 'enhance transition effects and performance' without clearly specifying the main scope or deliverable. Complete the title and make it more specific about the primary changes, such as 'refactor: enhance transition effects and performance optimization across message components' or identify the single most important change.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch animate

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 | 🟠 Major

Catch path no longer initializes modelStore / ollamaStore.

The success path kicks off modelStore.initialize() and ollamaStore.initialize() via Promise.allSettled (Lines 93–98), but the bootstrap-failure fallback only re-runs agentStore.fetchAgents() and projectStore.loadDefaultProjectPath(). If getBootstrap() 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 two vue imports.

♻️ 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 whose title/content happens 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.stringify of 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 hardcodes 0.3s ease while 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-leaving in the scoped style still defines transition: 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 @apply and 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: Reposition onBeforeUnmount for code clarity.

The lifecycle hook is currently inserted between getSubagentModeLabel (Lines 622–631) and getSubagentStatusLabel (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 of paramsCopyResetTimer / responseCopyResetTimer more 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 redundant providerStore.ensureInitialized() call.

initAppStores() already invokes providerStore.initialize() (see src/renderer/src/lib/storeInitializer.ts), and modelStore.initialize() internally awaits providerStore.ensureInitialized() itself (see src/renderer/src/stores/modelStore.ts). Because useProviderStore() was already called during setup, the singleton initializationPromise is created synchronously by initAppStores() before this line runs, so the explicit ensureInitialized() 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: Sync PANEL_MOTION_MS with the motion token, and short-circuit the hide delay under prefers-reduced-motion.

Two related concerns with the JS hide-delay:

  1. Drift risk: PANEL_MOTION_MS = 220 (Line 84) duplicates the CSS --dc-motion-default value. If the token changes in style.css, the JS schedule won't follow, so the layout collapse could outlast or under-run the surface fade.
  2. Reduced-motion mismatch: The global rule in style.css forces transition-duration: 1ms under prefers-reduced-motion, so the panel surface vanishes instantly — but setTimeout(..., PANEL_MOTION_MS) (Line 191) still keeps layoutWidth non-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

📥 Commits

Reviewing files that changed from the base of the PR and between a1af3a7 and fc7c99e.

📒 Files selected for processing (18)
  • src/renderer/src/App.vue
  • src/renderer/src/assets/style.css
  • src/renderer/src/components/WindowSideBar.vue
  • src/renderer/src/components/WindowSideBarSessionItem.vue
  • src/renderer/src/components/chat/MessageList.vue
  • src/renderer/src/components/markdown/MarkdownRenderer.vue
  • src/renderer/src/components/message/MessageActionButtons.vue
  • src/renderer/src/components/message/MessageBlockContent.vue
  • src/renderer/src/components/message/MessageBlockToolCall.vue
  • src/renderer/src/components/message/MessageContent.vue
  • src/renderer/src/components/message/MessageItemUser.vue
  • src/renderer/src/components/message/MessageToolbar.vue
  • src/renderer/src/components/sidepanel/BrowserPanel.vue
  • src/renderer/src/components/sidepanel/ChatSidePanel.vue
  • src/renderer/src/pages/ChatPage.vue
  • src/renderer/src/views/ChatTabView.vue
  • test/renderer/components/ChatTabView.test.ts
  • test/renderer/components/WindowSideBar.test.ts


html {
font-family: var(--dc-font-family);
text-rendering: optimizeLegibility;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
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.

Comment on lines +177 to +180
.message-list-row {
content-visibility: auto;
contain-intrinsic-size: auto 180px;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 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=vue

Repository: ThinkInAIXYZ/deepchat

Length of output: 249


🏁 Script executed:

# Examine the capture logic
cat -n src/renderer/src/composables/message/useMessageCapture.ts | head -100

Repository: 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.vue

Repository: ThinkInAIXYZ/deepchat

Length of output: 118


🏁 Script executed:

# Find MessageToolbar and examine emissions
fd -type f -name "MessageToolbar*" src/renderer/src

Repository: ThinkInAIXYZ/deepchat

Length of output: 236


🏁 Script executed:

# Continue reading useMessageCapture.ts
cat -n src/renderer/src/composables/message/useMessageCapture.ts | tail -100

Repository: 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.vue

Repository: ThinkInAIXYZ/deepchat

Length of output: 8366


🏁 Script executed:

# Find and examine usePageCapture
find src -name "usePageCapture*" -type f

Repository: 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 3

Repository: 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 -150

Repository: 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=vue

Repository: ThinkInAIXYZ/deepchat

Length of output: 93


🏁 Script executed:

# Look for test files related to message capture
find test -name "*useMessageCapture*" -o -name "*capture*" | head -10

Repository: 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 2

Repository: 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 -200

Repository: 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.ts

Repository: 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 5

Repository: 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 2

Repository: 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:

  1. "Capture from top" mode: Iterative rect calculation across the full range relies on accurate element heights; mismatched heights compound across multiple captures.
  2. Tall assistant messages: Code blocks and artifacts can exceed 180px; capturing with the wrong height causes misaligned content in the final image.
  3. Search result navigation: setActiveChatSearchMatch() calls scrollIntoView() 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.

Comment on lines +87 to +90
const getSearchResults = () => {
searchResultsPromise ??= sessionClient.getSearchResults(effectiveMessageId.value)
return searchResultsPromise
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
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.

@zerob13
zerob13 merged commit 07375ac into dev Apr 27, 2026
3 checks passed
zerob13 pushed a commit that referenced this pull request Apr 27, 2026
…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.
@zhangmo8
zhangmo8 deleted the animate branch April 30, 2026 05:54
zerob13 added a commit that referenced this pull request May 1, 2026
* 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants