Skip to content

refactor(ui): align UI with shadcn and VueUse#1985

Merged
zhangmo8 merged 3 commits into
devfrom
refactor/shadcn-vueuse-alignment
Jul 16, 2026
Merged

refactor(ui): align UI with shadcn and VueUse#1985
zhangmo8 merged 3 commits into
devfrom
refactor/shadcn-vueuse-alignment

Conversation

@zhangmo8

@zhangmo8 zhangmo8 commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Prefer shadcn-vue feedback and form primitives (Spinner, Skeleton, Empty, Dialog, Select, Tooltip, RadioGroup) over hand-rolled loaders and controls across settings and main renderer surfaces.
  • Prefer VueUse for mechanical utilities (useDebounceFn, refDebounced, useEventListener, useResizeObserver, useClipboard) where the prior code was ad-hoc timers/listeners.
  • Document standing rule in AGENTS.md and architecture SDD under docs/architecture/shadcn-vueuse-alignment/.
  • Remove dead ScrollablePopover.vue.
  • Wave 4 sweep: remaining button/icon animate-spin loaders → shadcn Spinner.

Notable behavior notes

  • AcpDebugDialog now uses shadcn Dialog (fullscreen styling); smoke Esc/outside-close if you rely on sticky debug chrome.
  • MemoryListView search debounce uses VueUse 14 useDebounceFn with requestId stale-guard (no .cancel() API).
  • Model capability tooltips/i18n keys added for all locales.

Test plan

  • pnpm run format && pnpm run i18n && pnpm run lint && pnpm run typecheck
  • Smoke settings: Data / Remote / OAuth / MCP market / provider enable-all pending / shortcuts reset
  • Smoke chat: status bar ACP/model loading, voice input transcribing spinner, skills indicator loading
  • Smoke artifacts loading states and plugins catalog refresh
  • Confirm no remaining hand-rolled animate-spin loaders under src/renderer/**/*.vue (Spinner component internal spin is expected)

Architecture

See docs/architecture/shadcn-vueuse-alignment/spec.md and tasks.md (Waves 1–4).

Summary by CodeRabbit

  • UI Improvements
    • Unified loading, empty, and placeholder visuals with consistent Spinner/Skeleton/Empty components across settings, skills, plugins, chat, MCP, artifacts, and the sidebar, including updated dialogs, selectors, radio controls, tooltips, and capability badges.
  • Usability & Reliability
    • Improved debounced search behavior with stale-result protection, refined focus/context event handling, simplified resize updates, and added “artifact generating” status messaging.
  • Documentation
    • Added UI primitives guidelines and a phased shadcn-vue + VueUse alignment spec/plan/task list.
  • Maintenance
    • Updated UI-related dev dependencies.

Prefer shadcn Spinner/Skeleton/Empty/Dialog/Select and VueUse
helpers across settings and renderer loading surfaces.
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2cbd2052-03d0-4002-9283-b5ab081458d2

📥 Commits

Reviewing files that changed from the base of the PR and between cf10155 and d2cf829.

📒 Files selected for processing (25)
  • src/renderer/settings/components/MemoryListView.vue
  • src/renderer/src/components/artifacts/ArtifactThinking.vue
  • src/renderer/src/components/mcp-config/components/McpJsonViewer.vue
  • src/renderer/src/composables/useArtifactExport.ts
  • src/renderer/src/i18n/da-DK/artifacts.json
  • src/renderer/src/i18n/de-DE/artifacts.json
  • src/renderer/src/i18n/en-US/artifacts.json
  • src/renderer/src/i18n/es-ES/artifacts.json
  • src/renderer/src/i18n/fa-IR/artifacts.json
  • src/renderer/src/i18n/fr-FR/artifacts.json
  • src/renderer/src/i18n/he-IL/artifacts.json
  • src/renderer/src/i18n/id-ID/artifacts.json
  • src/renderer/src/i18n/it-IT/artifacts.json
  • src/renderer/src/i18n/ja-JP/artifacts.json
  • src/renderer/src/i18n/ko-KR/artifacts.json
  • src/renderer/src/i18n/ms-MY/artifacts.json
  • src/renderer/src/i18n/pl-PL/artifacts.json
  • src/renderer/src/i18n/pt-BR/artifacts.json
  • src/renderer/src/i18n/ru-RU/artifacts.json
  • src/renderer/src/i18n/tr-TR/artifacts.json
  • src/renderer/src/i18n/vi-VN/artifacts.json
  • src/renderer/src/i18n/zh-CN/artifacts.json
  • src/renderer/src/i18n/zh-HK/artifacts.json
  • src/renderer/src/i18n/zh-TW/artifacts.json
  • src/types/i18n.d.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/renderer/settings/components/MemoryListView.vue

📝 Walkthrough

Walkthrough

The 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 ScrollablePopover.vue, and documenting the phased alignment plan.

Changes

UI alignment

Layer / File(s) Summary
Alignment guidance and dependency foundation
AGENTS.md, docs/architecture/shadcn-vueuse-alignment/*, package.json
Adds alignment guidance, specification, rollout tasks, and dependency updates.
Renderer loading and interaction primitives
src/renderer/settings/**/*, src/renderer/src/**/*
Replaces animated icons and placeholders with shared Spinner, Skeleton, Empty, Tooltip, Badge, RadioGroup, and Select primitives.
VueUse and state integrations
src/renderer/settings/components/MemoryListView.vue, src/renderer/src/stores/*, src/renderer/src/components/sidepanel/*, src/renderer/src/App.vue
Migrates debounce, polling, event listeners, resize observation, and provider-order synchronization to VueUse-backed implementations.
Localized capability and artifact messages
src/renderer/src/i18n/*, src/types/i18n.d.ts
Adds capability labels, artifact-generation messages, and corresponding locale typing.
Removed obsolete component
src/renderer/src/components/ScrollablePopover.vue
Removes the obsolete scrollable popover wrapper.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested reviewers: zerob13

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: refactoring the UI to align with shadcn-vue primitives and VueUse utilities.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.
✨ 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 refactor/shadcn-vueuse-alignment

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.

Translate settings.modelConfigItem.capability for non-English locales.

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

🧹 Nitpick comments (2)
src/renderer/settings/components/ProviderRateLimitConfig.vue (1)

234-251: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Prefer useTimeoutPoll for asynchronous network polling.

While useIntervalFn works, it behaves like setInterval and does not wait for the asynchronous loadStatus call 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 value

Avoid wrapping RadioGroupItem in a <label>.

RadioGroupItem renders 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 div and using the for attribute on a <Label> element that corresponds to the id of the RadioGroupItem.

♻️ 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

📥 Commits

Reviewing files that changed from the base of the PR and between 106520e and dce9b94.

📒 Files selected for processing (100)
  • AGENTS.md
  • docs/architecture/shadcn-vueuse-alignment/plan.md
  • docs/architecture/shadcn-vueuse-alignment/spec.md
  • docs/architecture/shadcn-vueuse-alignment/tasks.md
  • package.json
  • src/renderer/settings/App.vue
  • src/renderer/settings/components/AboutUsSettings.vue
  • src/renderer/settings/components/AcpDebugDialog.vue
  • src/renderer/settings/components/AcpSettings.vue
  • src/renderer/settings/components/BuiltinKnowledgeSettings.vue
  • src/renderer/settings/components/CronJobsSettings.vue
  • src/renderer/settings/components/DashboardSettings.vue
  • src/renderer/settings/components/DataSettings.vue
  • src/renderer/settings/components/EnvironmentsSettings.vue
  • src/renderer/settings/components/GitHubCopilotOAuth.vue
  • src/renderer/settings/components/GrokOAuth.vue
  • src/renderer/settings/components/KnowledgeFile.vue
  • src/renderer/settings/components/KnowledgeFileItem.vue
  • src/renderer/settings/components/McpBuiltinMarket.vue
  • src/renderer/settings/components/McpSettings.vue
  • src/renderer/settings/components/MemoryEmptyState.vue
  • src/renderer/settings/components/MemoryListView.vue
  • src/renderer/settings/components/ModelProviderSettings.vue
  • src/renderer/settings/components/ModelScopeMcpSync.vue
  • src/renderer/settings/components/NotificationsHooksSettings.vue
  • src/renderer/settings/components/OpenAICodexOAuth.vue
  • src/renderer/settings/components/ProviderApiConfig.vue
  • src/renderer/settings/components/ProviderConfigImportDialog.vue
  • src/renderer/settings/components/ProviderModelList.vue
  • src/renderer/settings/components/ProviderModelManager.vue
  • src/renderer/settings/components/ProviderRateLimitConfig.vue
  • src/renderer/settings/components/RemoteSettings.vue
  • src/renderer/settings/components/ShortcutSettings.vue
  • src/renderer/settings/components/skills/AdoptSkillDialog.vue
  • src/renderer/settings/components/skills/InstallFromGitDialog.vue
  • src/renderer/settings/components/skills/InstallSkillToAgentDialog.vue
  • src/renderer/settings/components/skills/SkillAgentsTab.vue
  • src/renderer/settings/components/skills/SkillDetailDialog.vue
  • src/renderer/settings/components/skills/SkillFolderTree.vue
  • src/renderer/settings/components/skills/SkillImportExportTab.vue
  • src/renderer/settings/components/skills/SkillInstallDialog.vue
  • src/renderer/settings/components/skills/SkillSyncDialog/ExportWizard.vue
  • src/renderer/settings/components/skills/SkillSyncDialog/ImportWizard.vue
  • src/renderer/settings/components/skills/SkillSyncDialog/ToolSelector.vue
  • src/renderer/settings/components/skills/SkillsSettings.vue
  • src/renderer/settings/components/skills/SyncStatusCard.vue
  • src/renderer/settings/components/skills/SyncStatusSection.vue
  • src/renderer/src/App.vue
  • src/renderer/src/components/ScrollablePopover.vue
  • src/renderer/src/components/WindowSideBar.vue
  • src/renderer/src/components/agent/AgentTransferDialog.vue
  • src/renderer/src/components/artifacts/ArtifactPreview.vue
  • src/renderer/src/components/artifacts/ArtifactThinking.vue
  • src/renderer/src/components/artifacts/SvgArtifact.vue
  • src/renderer/src/components/artifacts/ToolCallPreview.vue
  • src/renderer/src/components/chat-input/SkillsIndicator.vue
  • src/renderer/src/components/chat/ChatInputToolbar.vue
  • src/renderer/src/components/chat/ChatSessionSkeleton.vue
  • src/renderer/src/components/chat/ChatStatusBar.vue
  • src/renderer/src/components/mcp-config/components/McpJsonViewer.vue
  • src/renderer/src/components/mcp-config/components/McpPromptPanel.vue
  • src/renderer/src/components/mcp-config/components/McpResourceViewer.vue
  • src/renderer/src/components/mcp-config/components/McpServerCard.vue
  • src/renderer/src/components/mcp-config/components/McpServers.vue
  • src/renderer/src/components/mcp-config/components/McpToolPanel.vue
  • src/renderer/src/components/mcp/McpSamplingDialog.vue
  • src/renderer/src/components/message/MessageBlockAudio.vue
  • src/renderer/src/components/message/MessageBlockImage.vue
  • src/renderer/src/components/message/MessageBlockVideo.vue
  • src/renderer/src/components/message/MessageItemPlaceholder.vue
  • src/renderer/src/components/message/MessageToolbar.vue
  • src/renderer/src/components/popup/TranslatePopup.vue
  • src/renderer/src/components/settings/ModelCheckDialog.vue
  • src/renderer/src/components/settings/ModelConfigItem.vue
  • src/renderer/src/components/sidepanel/viewer/WorkspaceCodePane.vue
  • src/renderer/src/components/spotlight/SpotlightOverlay.vue
  • src/renderer/src/composables/useArtifactExport.ts
  • src/renderer/src/i18n/da-DK/settings.json
  • src/renderer/src/i18n/de-DE/settings.json
  • src/renderer/src/i18n/en-US/settings.json
  • src/renderer/src/i18n/es-ES/settings.json
  • src/renderer/src/i18n/fa-IR/settings.json
  • src/renderer/src/i18n/fr-FR/settings.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/renderer/src/i18n/id-ID/settings.json
  • src/renderer/src/i18n/it-IT/settings.json
  • src/renderer/src/i18n/ja-JP/settings.json
  • src/renderer/src/i18n/ko-KR/settings.json
  • src/renderer/src/i18n/ms-MY/settings.json
  • src/renderer/src/i18n/pl-PL/settings.json
  • src/renderer/src/i18n/pt-BR/settings.json
  • src/renderer/src/i18n/ru-RU/settings.json
  • src/renderer/src/i18n/tr-TR/settings.json
  • src/renderer/src/i18n/vi-VN/settings.json
  • src/renderer/src/i18n/zh-CN/settings.json
  • src/renderer/src/i18n/zh-HK/settings.json
  • src/renderer/src/i18n/zh-TW/settings.json
  • src/renderer/src/pages/plugins/PluginsCatalogPage.vue
  • src/renderer/src/stores/providerStore.ts
  • src/renderer/src/stores/ui/sidepanel.ts
💤 Files with no reviewable changes (1)
  • src/renderer/src/components/ScrollablePopover.vue

Comment thread src/renderer/settings/components/MemoryListView.vue Outdated
Comment thread src/renderer/src/components/artifacts/ArtifactThinking.vue
Comment thread src/renderer/src/components/artifacts/ArtifactThinking.vue
Comment thread src/renderer/src/i18n/da-DK/settings.json
Comment thread src/renderer/src/i18n/fa-IR/settings.json
Comment thread src/renderer/src/i18n/fa-IR/settings.json Outdated
Comment thread src/renderer/src/i18n/ms-MY/settings.json Outdated

@zerob13 zerob13 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

One blocking clipboard regression found.

Comment thread src/renderer/src/composables/useArtifactExport.ts Outdated

@zerob13 zerob13 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The clipboard migration has the same failure-mode regression in the MCP JSON viewer.

Comment thread src/renderer/src/components/mcp-config/components/McpJsonViewer.vue Outdated
Restore writeText clipboard paths, guard memory search stale runs,
and localize ArtifactThinking loading text.
@zhangmo8
zhangmo8 merged commit b8eafcd into dev Jul 16, 2026
4 checks passed
@zhangmo8
zhangmo8 deleted the refactor/shadcn-vueuse-alignment branch July 16, 2026 09:20
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