refactor(ui): align UI with shadcn and VueUse#1985
Conversation
Prefer shadcn Spinner/Skeleton/Empty/Dialog/Select and VueUse helpers across settings and renderer loading surfaces.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (25)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThe PR aligns renderer UI surfaces with shadcn-vue and VueUse by replacing custom loading and empty states, migrating browser utilities, updating model capability tooltips and translations, removing ChangesUI alignment
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ 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 |
Translate settings.modelConfigItem.capability for non-English locales.
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (2)
src/renderer/settings/components/ProviderRateLimitConfig.vue (1)
234-251: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winPrefer
useTimeoutPollfor asynchronous network polling.While
useIntervalFnworks, it behaves likesetIntervaland does not wait for the asynchronousloadStatuscall to complete before scheduling the next tick. If the network is slow or degraded, and the request takes longer than 1000ms, this can lead to overlapping requests and resource exhaustion.Consider using VueUse's
useTimeoutPoll, which correctly waits for the promise to resolve before starting the next interval countdown.♻️ Proposed fix
Update the import at the top of the file:
import { useTimeoutPoll } from '`@vueuse/core`' // Remove: import { useIntervalFn } from '`@vueuse/core`'And update the polling block:
-const { pause: pauseStatusPolling, resume: resumeStatusPolling } = useIntervalFn( - () => { - void loadStatus() - }, +const { pause: pauseStatusPolling, resume: resumeStatusPolling } = useTimeoutPoll( + async () => { + await loadStatus() + }, 1000, { immediate: false } )🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/renderer/settings/components/ProviderRateLimitConfig.vue` around lines 234 - 251, Replace useIntervalFn with VueUse’s useTimeoutPoll in the status polling setup around startStatusPolling and stopStatusPolling. Configure the poll callback to await loadStatus before the next 1000ms delay, while preserving the existing immediate:false behavior and pause/resume control based on rateLimitEnabled.src/renderer/src/components/agent/AgentTransferDialog.vue (1)
57-84: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAvoid wrapping
RadioGroupItemin a<label>.
RadioGroupItemrenders a<button>element. Wrapping a<button>inside a<label>is invalid HTML5 and can cause screen readers to announce the element incorrectly or trigger duplicate click events.Consider making them siblings within a
divand using theforattribute on a<Label>element that corresponds to theidof theRadioGroupItem.♻️ Proposed refactor for accessibility
<RadioGroup v-if="mode === 'delete-agent'" v-model="action" class="flex flex-col gap-2"> - <label - class="flex cursor-pointer gap-3 rounded-lg border p-3 transition-colors hover:bg-muted/40" - > - <RadioGroupItem value="move" class="mt-1" /> - <span class="flex flex-col gap-1"> + <div class="flex items-start gap-3 rounded-lg border p-3 transition-colors hover:bg-muted/40 has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-ring"> + <RadioGroupItem id="action-move" value="move" class="mt-1" /> + <Label for="action-move" class="flex flex-col gap-1 cursor-pointer w-full"> <span class="text-sm font-medium"> {{ t('dialog.agentTransfer.moveBeforeDeleteTitle') }} </span> <span class="text-sm text-muted-foreground"> {{ t('dialog.agentTransfer.moveBeforeDeleteDescription') }} </span> - </span> - </label> - <label - class="flex cursor-pointer gap-3 rounded-lg border p-3 transition-colors hover:bg-muted/40" - > - <RadioGroupItem value="delete" class="mt-1" /> - <span class="flex flex-col gap-1"> + </Label> + </div> + <div class="flex items-start gap-3 rounded-lg border p-3 transition-colors hover:bg-muted/40 has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-ring"> + <RadioGroupItem id="action-delete" value="delete" class="mt-1" /> + <Label for="action-delete" class="flex flex-col gap-1 cursor-pointer w-full"> <span class="text-sm font-medium"> {{ t('dialog.agentTransfer.deleteSessionsTitle') }} </span> <span class="text-sm text-muted-foreground"> {{ t('dialog.agentTransfer.deleteSessionsDescription') }} </span> - </span> - </label> + </Label> + </div> </RadioGroup>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/renderer/src/components/agent/AgentTransferDialog.vue` around lines 57 - 84, Refactor the two option containers in the delete-agent RadioGroup so each RadioGroupItem button is not nested inside a label. Use sibling controls with a Label whose for attribute matches a unique RadioGroupItem id, while preserving the existing styling, translations, and action values.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/renderer/settings/components/MemoryListView.vue`:
- Around line 459-462: Update debouncedRunSearch to check whether its requestId
is still current before invoking runSearch. Return immediately for stale
requests so no API call occurs and the active searchError state is not modified;
preserve the existing runSearch behavior for current requests.
In `@src/renderer/src/components/artifacts/ArtifactThinking.vue`:
- Around line 8-12: In ArtifactThinking.vue, uncomment the useI18n import and
initialize it in the setup scope, then use the resulting localization helper for
the component’s user-facing text.
- Around line 2-6: Replace the hardcoded user-facing text in
ArtifactThinking.vue with a vue-i18n translation key, obtaining the translated
value through the component’s existing or standard i18n access pattern while
preserving the current loading display.
In `@src/renderer/src/i18n/da-DK/settings.json`:
- Around line 469-474: Localize all four capability labels—vision, functionCall,
reasoning, and search—in src/renderer/src/i18n/da-DK/settings.json lines 469-474
(Danish), src/renderer/src/i18n/de-DE/settings.json lines 796-801 (German),
src/renderer/src/i18n/es-ES/settings.json lines 796-801 (Spanish),
src/renderer/src/i18n/tr-TR/settings.json lines 796-801 (Turkish), and
src/renderer/src/i18n/vi-VN/settings.json lines 796-801 (Vietnamese), while
preserving the existing capability keys used by ModelConfigItem.vue.
In `@src/renderer/src/i18n/fa-IR/settings.json`:
- Around line 537-540: Translate the new vision, functionCall, reasoning, and
search labels in all affected locale files:
src/renderer/src/i18n/fa-IR/settings.json lines 537-540 into Persian,
src/renderer/src/i18n/fr-FR/settings.json lines 537-540 into French,
src/renderer/src/i18n/he-IL/settings.json lines 537-540 into Hebrew,
src/renderer/src/i18n/id-ID/settings.json lines 797-800 into Indonesian, and
src/renderer/src/i18n/it-IT/settings.json lines 797-800 into Italian, while
preserving the existing keys and JSON structure.
- Around line 536-541: Extend the DefineLocaleMessage declaration for
settings.modelConfigItem in src/types/i18n.d.ts to include the nested capability
keys vision, functionCall, reasoning, and search used by ModelConfigItem.vue.
Keep the existing chatFallbackWarning entry and ensure the declaration matches
the settings locale structure.
In `@src/renderer/src/i18n/ms-MY/settings.json`:
- Around line 797-800: Translate the capability tooltip values for vision,
functionCall, reasoning, and search in src/renderer/src/i18n/ms-MY/settings.json
lines 797-800 into Malay, src/renderer/src/i18n/pl-PL/settings.json lines
797-800 into Polish, src/renderer/src/i18n/pt-BR/settings.json lines 537-540
into Brazilian Portuguese, and src/renderer/src/i18n/ru-RU/settings.json lines
537-540 into Russian, preserving the existing keys and JSON structure.
---
Nitpick comments:
In `@src/renderer/settings/components/ProviderRateLimitConfig.vue`:
- Around line 234-251: Replace useIntervalFn with VueUse’s useTimeoutPoll in the
status polling setup around startStatusPolling and stopStatusPolling. Configure
the poll callback to await loadStatus before the next 1000ms delay, while
preserving the existing immediate:false behavior and pause/resume control based
on rateLimitEnabled.
In `@src/renderer/src/components/agent/AgentTransferDialog.vue`:
- Around line 57-84: Refactor the two option containers in the delete-agent
RadioGroup so each RadioGroupItem button is not nested inside a label. Use
sibling controls with a Label whose for attribute matches a unique
RadioGroupItem id, while preserving the existing styling, translations, and
action values.
🪄 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: 0c5aa9c0-ce23-4c34-97c3-a3229ed837e0
📒 Files selected for processing (100)
AGENTS.mddocs/architecture/shadcn-vueuse-alignment/plan.mddocs/architecture/shadcn-vueuse-alignment/spec.mddocs/architecture/shadcn-vueuse-alignment/tasks.mdpackage.jsonsrc/renderer/settings/App.vuesrc/renderer/settings/components/AboutUsSettings.vuesrc/renderer/settings/components/AcpDebugDialog.vuesrc/renderer/settings/components/AcpSettings.vuesrc/renderer/settings/components/BuiltinKnowledgeSettings.vuesrc/renderer/settings/components/CronJobsSettings.vuesrc/renderer/settings/components/DashboardSettings.vuesrc/renderer/settings/components/DataSettings.vuesrc/renderer/settings/components/EnvironmentsSettings.vuesrc/renderer/settings/components/GitHubCopilotOAuth.vuesrc/renderer/settings/components/GrokOAuth.vuesrc/renderer/settings/components/KnowledgeFile.vuesrc/renderer/settings/components/KnowledgeFileItem.vuesrc/renderer/settings/components/McpBuiltinMarket.vuesrc/renderer/settings/components/McpSettings.vuesrc/renderer/settings/components/MemoryEmptyState.vuesrc/renderer/settings/components/MemoryListView.vuesrc/renderer/settings/components/ModelProviderSettings.vuesrc/renderer/settings/components/ModelScopeMcpSync.vuesrc/renderer/settings/components/NotificationsHooksSettings.vuesrc/renderer/settings/components/OpenAICodexOAuth.vuesrc/renderer/settings/components/ProviderApiConfig.vuesrc/renderer/settings/components/ProviderConfigImportDialog.vuesrc/renderer/settings/components/ProviderModelList.vuesrc/renderer/settings/components/ProviderModelManager.vuesrc/renderer/settings/components/ProviderRateLimitConfig.vuesrc/renderer/settings/components/RemoteSettings.vuesrc/renderer/settings/components/ShortcutSettings.vuesrc/renderer/settings/components/skills/AdoptSkillDialog.vuesrc/renderer/settings/components/skills/InstallFromGitDialog.vuesrc/renderer/settings/components/skills/InstallSkillToAgentDialog.vuesrc/renderer/settings/components/skills/SkillAgentsTab.vuesrc/renderer/settings/components/skills/SkillDetailDialog.vuesrc/renderer/settings/components/skills/SkillFolderTree.vuesrc/renderer/settings/components/skills/SkillImportExportTab.vuesrc/renderer/settings/components/skills/SkillInstallDialog.vuesrc/renderer/settings/components/skills/SkillSyncDialog/ExportWizard.vuesrc/renderer/settings/components/skills/SkillSyncDialog/ImportWizard.vuesrc/renderer/settings/components/skills/SkillSyncDialog/ToolSelector.vuesrc/renderer/settings/components/skills/SkillsSettings.vuesrc/renderer/settings/components/skills/SyncStatusCard.vuesrc/renderer/settings/components/skills/SyncStatusSection.vuesrc/renderer/src/App.vuesrc/renderer/src/components/ScrollablePopover.vuesrc/renderer/src/components/WindowSideBar.vuesrc/renderer/src/components/agent/AgentTransferDialog.vuesrc/renderer/src/components/artifacts/ArtifactPreview.vuesrc/renderer/src/components/artifacts/ArtifactThinking.vuesrc/renderer/src/components/artifacts/SvgArtifact.vuesrc/renderer/src/components/artifacts/ToolCallPreview.vuesrc/renderer/src/components/chat-input/SkillsIndicator.vuesrc/renderer/src/components/chat/ChatInputToolbar.vuesrc/renderer/src/components/chat/ChatSessionSkeleton.vuesrc/renderer/src/components/chat/ChatStatusBar.vuesrc/renderer/src/components/mcp-config/components/McpJsonViewer.vuesrc/renderer/src/components/mcp-config/components/McpPromptPanel.vuesrc/renderer/src/components/mcp-config/components/McpResourceViewer.vuesrc/renderer/src/components/mcp-config/components/McpServerCard.vuesrc/renderer/src/components/mcp-config/components/McpServers.vuesrc/renderer/src/components/mcp-config/components/McpToolPanel.vuesrc/renderer/src/components/mcp/McpSamplingDialog.vuesrc/renderer/src/components/message/MessageBlockAudio.vuesrc/renderer/src/components/message/MessageBlockImage.vuesrc/renderer/src/components/message/MessageBlockVideo.vuesrc/renderer/src/components/message/MessageItemPlaceholder.vuesrc/renderer/src/components/message/MessageToolbar.vuesrc/renderer/src/components/popup/TranslatePopup.vuesrc/renderer/src/components/settings/ModelCheckDialog.vuesrc/renderer/src/components/settings/ModelConfigItem.vuesrc/renderer/src/components/sidepanel/viewer/WorkspaceCodePane.vuesrc/renderer/src/components/spotlight/SpotlightOverlay.vuesrc/renderer/src/composables/useArtifactExport.tssrc/renderer/src/i18n/da-DK/settings.jsonsrc/renderer/src/i18n/de-DE/settings.jsonsrc/renderer/src/i18n/en-US/settings.jsonsrc/renderer/src/i18n/es-ES/settings.jsonsrc/renderer/src/i18n/fa-IR/settings.jsonsrc/renderer/src/i18n/fr-FR/settings.jsonsrc/renderer/src/i18n/he-IL/settings.jsonsrc/renderer/src/i18n/id-ID/settings.jsonsrc/renderer/src/i18n/it-IT/settings.jsonsrc/renderer/src/i18n/ja-JP/settings.jsonsrc/renderer/src/i18n/ko-KR/settings.jsonsrc/renderer/src/i18n/ms-MY/settings.jsonsrc/renderer/src/i18n/pl-PL/settings.jsonsrc/renderer/src/i18n/pt-BR/settings.jsonsrc/renderer/src/i18n/ru-RU/settings.jsonsrc/renderer/src/i18n/tr-TR/settings.jsonsrc/renderer/src/i18n/vi-VN/settings.jsonsrc/renderer/src/i18n/zh-CN/settings.jsonsrc/renderer/src/i18n/zh-HK/settings.jsonsrc/renderer/src/i18n/zh-TW/settings.jsonsrc/renderer/src/pages/plugins/PluginsCatalogPage.vuesrc/renderer/src/stores/providerStore.tssrc/renderer/src/stores/ui/sidepanel.ts
💤 Files with no reviewable changes (1)
- src/renderer/src/components/ScrollablePopover.vue
zerob13
left a comment
There was a problem hiding this comment.
One blocking clipboard regression found.
zerob13
left a comment
There was a problem hiding this comment.
The clipboard migration has the same failure-mode regression in the MCP JSON viewer.
Restore writeText clipboard paths, guard memory search stale runs, and localize ArtifactThinking loading text.
Summary
Spinner,Skeleton,Empty,Dialog,Select,Tooltip,RadioGroup) over hand-rolled loaders and controls across settings and main renderer surfaces.useDebounceFn,refDebounced,useEventListener,useResizeObserver,useClipboard) where the prior code was ad-hoc timers/listeners.AGENTS.mdand architecture SDD underdocs/architecture/shadcn-vueuse-alignment/.ScrollablePopover.vue.animate-spinloaders → shadcnSpinner.Notable behavior notes
AcpDebugDialognow uses shadcnDialog(fullscreen styling); smoke Esc/outside-close if you rely on sticky debug chrome.MemoryListViewsearch debounce uses VueUse 14useDebounceFnwithrequestIdstale-guard (no.cancel()API).Test plan
pnpm run format && pnpm run i18n && pnpm run lint && pnpm run typecheckanimate-spinloaders undersrc/renderer/**/*.vue(Spinner component internal spin is expected)Architecture
See
docs/architecture/shadcn-vueuse-alignment/spec.mdandtasks.md(Waves 1–4).Summary by CodeRabbit