From 7833676b8bb60b093cf9c4d4b8217fb2f089c69e Mon Sep 17 00:00:00 2001 From: Aleix Soler Date: Wed, 18 Mar 2026 16:20:01 +0100 Subject: [PATCH] fix: implement template deduplication and installation safety checks --- components/SettingsUI/TemplateSettings.tsx | 6 +++++ core/settingsService.ts | 27 ++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/components/SettingsUI/TemplateSettings.tsx b/components/SettingsUI/TemplateSettings.tsx index 7060507..033c79a 100644 --- a/components/SettingsUI/TemplateSettings.tsx +++ b/components/SettingsUI/TemplateSettings.tsx @@ -63,6 +63,12 @@ export const TemplateSettings: React.FC = ({ const handleInstallTemplate = useCallback( (template: StatusTemplate) => { + if (!isTemplateNameUnique(template.name, undefined)) { + alert( + `A template with the name "${template.name}" is already installed.`, + ); + return; + } const newId = generateTemplateId(template.name, settings.templates); const installedTemplate: StatusTemplate = { ...template, diff --git a/core/settingsService.ts b/core/settingsService.ts index 3d5a880..6d440ec 100644 --- a/core/settingsService.ts +++ b/core/settingsService.ts @@ -103,9 +103,36 @@ class SettingsService { private async loadSettings() { const loadedData = await this.plugin.loadData(); this.settings = this.mergeSettings(DEFAULT_PLUGIN_SETTINGS, loadedData); + this.deduplicateTemplates(); return this.settings; } + /** + * Removes duplicate templates with the same name, keeping the first occurrence. + */ + private deduplicateTemplates() { + if (!this.settings.templates) return; + + const seenNames = new Set(); + const uniqueTemplates = []; + let hasDuplicates = false; + + for (const template of this.settings.templates) { + const lowerName = template.name.toLowerCase().trim(); + if (!seenNames.has(lowerName)) { + seenNames.add(lowerName); + uniqueTemplates.push(template); + } else { + hasDuplicates = true; + } + } + + if (hasDuplicates) { + this.settings.templates = uniqueTemplates; + this.saveSettings().catch(console.error); + } + } + /** * Deep merges loaded settings with defaults to prevent data loss for nested objects like statusColors. */