From 039103eff56a7ee93d7af568a11190aa15104215 Mon Sep 17 00:00:00 2001 From: tanzz Date: Fri, 13 Mar 2026 21:53:09 +0800 Subject: [PATCH 1/7] =?UTF-8?q?docs:=20=E5=B0=86=20AGENTS.md=20=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=E4=B8=BA=E4=B8=AD=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 完整翻译所有章节为简体中文 - 添加测试命令详细说明 - 补充 TypeScript 严格模式配置 feat: 添加 PowerShell 脚本工具 - cleanup.ps1: 清理脚本 - task.ps1: 任务管理脚本 --- script/task.ps1 | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/script/task.ps1 b/script/task.ps1 index 29f96d6..5f80162 100644 --- a/script/task.ps1 +++ b/script/task.ps1 @@ -1,13 +1,13 @@ -param( - [string]$Task -) - -$path = ".ai/worktrees/$Task" -$branch = "task/$Task" - -git worktree add $path -b $branch - -Write-Host "Worktree created:" -Write-Host $path - -wt -w 0 new-tab -d $path \ No newline at end of file +param( + [string]$Task +) + +$path = ".ai/worktrees/$Task" +$branch = "task/$Task" + +git worktree add $path -b $branch + +Write-Host "Worktree created:" +Write-Host $path + +wt -w 0 new-tab -d $path From 72fa2dd759dab1f96d5c0aaf14b76609256c5663 Mon Sep 17 00:00:00 2001 From: tanzz Date: Sat, 14 Mar 2026 04:40:40 +0800 Subject: [PATCH 2/7] feat(i18n): add internationalization support with i18next Add i18n support using i18next library with English and Chinese translations Implement language switcher component and settings integration Add theme management system with light/dark/system modes Update all UI text to use translation keys Create translation files for en-US and zh-CN Add settings store for persisting user preferences --- package.json | 4 +- pnpm-lock.yaml | 38 ++++ src/renderer/components/LanguageSwitcher.ts | 220 ++++++++++++++++++++ src/renderer/components/ThemeSwitcher.ts | 127 +++++++++++ src/renderer/i18n/en-US.json | 190 +++++++++++++++++ src/renderer/i18n/index.ts | 67 ++++++ src/renderer/i18n/zh-CN.json | 190 +++++++++++++++++ src/renderer/index.html | 143 ++++++++----- src/renderer/main.ts | 33 ++- src/renderer/pages/backupPage.ts | 46 ++-- src/renderer/pages/historyPage.ts | 44 ++-- src/renderer/pages/mainPage.ts | 107 +++++----- src/renderer/pages/settingsPage.ts | 145 +++++++++---- src/renderer/styles/themes.css | 122 +++++++++++ src/renderer/utils/settingsStore.ts | 88 ++++++++ src/renderer/utils/themeManager.ts | 115 ++++++++++ 16 files changed, 1483 insertions(+), 196 deletions(-) create mode 100644 src/renderer/components/LanguageSwitcher.ts create mode 100644 src/renderer/components/ThemeSwitcher.ts create mode 100644 src/renderer/i18n/en-US.json create mode 100644 src/renderer/i18n/index.ts create mode 100644 src/renderer/i18n/zh-CN.json create mode 100644 src/renderer/styles/themes.css create mode 100644 src/renderer/utils/settingsStore.ts create mode 100644 src/renderer/utils/themeManager.ts diff --git a/package.json b/package.json index ba3e015..9e5aea6 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,9 @@ }, "dependencies": { "better-sqlite3": "^11.0.0", - "electron-log": "^5.2.0" + "electron-log": "^5.2.0", + "i18next": "^25.8.18", + "i18next-http-backend": "^3.0.2" }, "devDependencies": { "@electron-forge/cli": "^7.6.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index de9b41b..d3c9547 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,12 @@ importers: electron-log: specifier: ^5.2.0 version: 5.4.3 + i18next: + specifier: ^25.8.18 + version: 25.8.18(typescript@5.9.3) + i18next-http-backend: + specifier: ^3.0.2 + version: 3.0.2(encoding@0.1.13) devDependencies: '@electron-forge/cli': specifier: ^7.6.0 @@ -1288,6 +1294,9 @@ packages: cross-dirname@0.1.0: resolution: {integrity: sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==} + cross-fetch@4.0.0: + resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==} + cross-spawn@6.0.6: resolution: {integrity: sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==} engines: {node: '>=4.8'} @@ -1806,6 +1815,17 @@ packages: humanize-ms@1.2.1: resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + i18next-http-backend@3.0.2: + resolution: {integrity: sha512-PdlvPnvIp4E1sYi46Ik4tBYh/v/NbYfFFgTjkwFl0is8A18s7/bx9aXqsrOax9WUbeNS6mD2oix7Z0yGGf6m5g==} + + i18next@25.8.18: + resolution: {integrity: sha512-lzY5X83BiL5AP77+9DydbrqkQHFN9hUzWGjqjLpPcp5ZOzuu1aSoKaU3xbBLSjWx9dAzW431y+d+aogxOZaKRA==} + peerDependencies: + typescript: ^5 + peerDependenciesMeta: + typescript: + optional: true + iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -4647,6 +4667,12 @@ snapshots: cross-dirname@0.1.0: {} + cross-fetch@4.0.0(encoding@0.1.13): + dependencies: + node-fetch: 2.7.0(encoding@0.1.13) + transitivePeerDependencies: + - encoding + cross-spawn@6.0.6: dependencies: nice-try: 1.0.5 @@ -5288,6 +5314,18 @@ snapshots: dependencies: ms: 2.1.3 + i18next-http-backend@3.0.2(encoding@0.1.13): + dependencies: + cross-fetch: 4.0.0(encoding@0.1.13) + transitivePeerDependencies: + - encoding + + i18next@25.8.18(typescript@5.9.3): + dependencies: + '@babel/runtime': 7.28.6 + optionalDependencies: + typescript: 5.9.3 + iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 diff --git a/src/renderer/components/LanguageSwitcher.ts b/src/renderer/components/LanguageSwitcher.ts new file mode 100644 index 0000000..15d8fe5 --- /dev/null +++ b/src/renderer/components/LanguageSwitcher.ts @@ -0,0 +1,220 @@ +import type { SupportedLanguage } from '../i18n'; +import { SUPPORTED_LANGUAGES, changeLanguage, getCurrentLanguage, onLanguageChanged, t } from '../i18n'; +import { getSettingsStore } from '../utils/settingsStore'; + +export interface LanguageSwitcherOptions { + onChange?: (lang: SupportedLanguage) => void; +} + +export function createLanguageSwitcher(container: HTMLElement, options: LanguageSwitcherOptions = {}): HTMLSelectElement { + const select = document.createElement('select'); + select.className = 'native-select'; + select.style.minWidth = '140px'; + + SUPPORTED_LANGUAGES.forEach(lang => { + const option = document.createElement('option'); + option.value = lang.code; + option.textContent = `${lang.flag} ${lang.name}`; + select.appendChild(option); + }); + + // Set current value + select.value = getCurrentLanguage(); + + // Listen for changes + select.addEventListener('change', () => { + const lang = select.value as SupportedLanguage; + changeLanguage(lang).then(() => { + getSettingsStore().setSetting('language', lang); + options.onChange?.(lang); + }); + }); + + // Listen for external changes + onLanguageChanged((lang) => { + select.value = lang; + }); + + container.appendChild(select); + return select; +} + +export function createLanguageSelectorButton(container: HTMLElement, options: LanguageSwitcherOptions = {}): HTMLElement { + const wrapper = document.createElement('div'); + wrapper.className = 'language-selector'; + wrapper.style.display = 'flex'; + wrapper.style.gap = '8px'; + wrapper.style.alignItems = 'center'; + + const buttons = new Map(); + + SUPPORTED_LANGUAGES.forEach(lang => { + const btn = document.createElement('button'); + btn.className = 'lang-btn'; + btn.title = lang.name; + btn.innerHTML = `${lang.flag} ${lang.name}`; + btn.style.cssText = ` + height: 32px; + padding: 0 12px; + border-radius: var(--radius-sm); + border: 1px solid var(--border); + background: var(--surface); + cursor: pointer; + font-size: 13px; + display: flex; + align-items: center; + gap: 6px; + transition: all 0.2s; + color: var(--text); + `; + + btn.addEventListener('click', () => { + changeLanguage(lang.code).then(() => { + getSettingsStore().setSetting('language', lang.code); + options.onChange?.(lang.code); + }); + }); + + btn.addEventListener('mouseenter', () => { + if (getCurrentLanguage() !== lang.code) { + btn.style.background = 'var(--surface2)'; + } + }); + + btn.addEventListener('mouseleave', () => { + if (getCurrentLanguage() !== lang.code) { + btn.style.background = 'var(--surface)'; + } + }); + + buttons.set(lang.code, btn); + wrapper.appendChild(btn); + }); + + // Update button states + const updateButtons = (currentLang: SupportedLanguage) => { + buttons.forEach((btn, lang) => { + if (lang === currentLang) { + btn.style.background = 'var(--accent)'; + btn.style.borderColor = 'var(--accent)'; + btn.style.color = '#fff'; + } else { + btn.style.background = 'var(--surface)'; + btn.style.borderColor = 'var(--border)'; + btn.style.color = 'var(--text)'; + } + }); + }; + + // Initial state + updateButtons(getCurrentLanguage()); + + // Listen for changes + onLanguageChanged((lang) => { + updateButtons(lang); + }); + + container.appendChild(wrapper); + return wrapper; +} + +export function createLanguageDropdown(container: HTMLElement, options: LanguageSwitcherOptions = {}): HTMLElement { + const wrapper = document.createElement('div'); + wrapper.className = 'lang-dropdown-wrapper'; + wrapper.style.position = 'relative'; + wrapper.style.display = 'inline-block'; + + const currentLang = SUPPORTED_LANGUAGES.find(l => l.code === getCurrentLanguage()) || SUPPORTED_LANGUAGES[0]; + + const btn = document.createElement('button'); + btn.className = 'lang-dropdown-btn'; + btn.innerHTML = `${currentLang.flag} ${currentLang.name} `; + btn.style.cssText = ` + height: 32px; + padding: 0 12px; + border-radius: var(--radius-sm); + border: 1px solid var(--border); + background: var(--surface); + cursor: pointer; + font-size: 13px; + display: flex; + align-items: center; + gap: 6px; + transition: all 0.2s; + color: var(--text); + `; + + const dropdown = document.createElement('div'); + dropdown.className = 'lang-dropdown-menu'; + dropdown.style.cssText = ` + position: absolute; + top: 100%; + right: 0; + margin-top: 4px; + min-width: 140px; + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + box-shadow: var(--shadow-md); + z-index: 1000; + display: none; + overflow: hidden; + `; + + SUPPORTED_LANGUAGES.forEach(lang => { + const item = document.createElement('div'); + item.className = 'lang-dropdown-item'; + item.innerHTML = `${lang.flag} ${lang.name}`; + item.style.cssText = ` + padding: 8px 12px; + cursor: pointer; + font-size: 13px; + display: flex; + align-items: center; + gap: 6px; + transition: background 0.15s; + color: var(--text); + `; + + item.addEventListener('mouseenter', () => { + item.style.background = 'var(--surface2)'; + }); + + item.addEventListener('mouseleave', () => { + item.style.background = 'transparent'; + }); + + item.addEventListener('click', () => { + changeLanguage(lang.code).then(() => { + getSettingsStore().setSetting('language', lang.code); + btn.innerHTML = `${lang.flag} ${lang.name} `; + dropdown.style.display = 'none'; + options.onChange?.(lang.code); + }); + }); + + dropdown.appendChild(item); + }); + + btn.addEventListener('click', (e) => { + e.stopPropagation(); + const isVisible = dropdown.style.display === 'block'; + dropdown.style.display = isVisible ? 'none' : 'block'; + }); + + document.addEventListener('click', () => { + dropdown.style.display = 'none'; + }); + + onLanguageChanged((langCode) => { + const lang = SUPPORTED_LANGUAGES.find(l => l.code === langCode); + if (lang) { + btn.innerHTML = `${lang.flag} ${lang.name} `; + } + }); + + wrapper.appendChild(btn); + wrapper.appendChild(dropdown); + container.appendChild(wrapper); + return wrapper; +} diff --git a/src/renderer/components/ThemeSwitcher.ts b/src/renderer/components/ThemeSwitcher.ts new file mode 100644 index 0000000..2e7cb4c --- /dev/null +++ b/src/renderer/components/ThemeSwitcher.ts @@ -0,0 +1,127 @@ +import type { ThemeMode } from '../utils/themeManager'; +import { getThemeManager } from '../utils/themeManager'; +import { t } from '../i18n'; + +export interface ThemeSwitcherOptions { + onChange?: (mode: ThemeMode) => void; +} + +export function createThemeSwitcher(container: HTMLElement, options: ThemeSwitcherOptions = {}): HTMLSelectElement { + const select = document.createElement('select'); + select.className = 'native-select'; + select.style.minWidth = '140px'; + + const modes: { value: ThemeMode; label: string }[] = [ + { value: 'system', label: t('settings.appearance.themeSystem') }, + { value: 'light', label: t('settings.appearance.themeLight') }, + { value: 'dark', label: t('settings.appearance.themeDark') }, + ]; + + modes.forEach(mode => { + const option = document.createElement('option'); + option.value = mode.value; + option.textContent = mode.label; + select.appendChild(option); + }); + + // Set current value + const themeManager = getThemeManager(); + select.value = themeManager.getTheme(); + + // Listen for changes + select.addEventListener('change', () => { + const mode = select.value as ThemeMode; + themeManager.setTheme(mode); + options.onChange?.(mode); + }); + + // Listen for external changes + themeManager.onThemeChange((mode) => { + select.value = mode; + }); + + container.appendChild(select); + return select; +} + +export function createThemeSelectorButton(container: HTMLElement, options: ThemeSwitcherOptions = {}): HTMLElement { + const wrapper = document.createElement('div'); + wrapper.className = 'theme-selector'; + wrapper.style.display = 'flex'; + wrapper.style.gap = '8px'; + wrapper.style.alignItems = 'center'; + + const themeManager = getThemeManager(); + + const modes: { value: ThemeMode; icon: string; label: string }[] = [ + { value: 'system', icon: '💻', label: t('settings.appearance.themeSystem') }, + { value: 'light', icon: '☀️', label: t('settings.appearance.themeLight') }, + { value: 'dark', icon: '🌙', label: t('settings.appearance.themeDark') }, + ]; + + const buttons = new Map(); + + modes.forEach(mode => { + const btn = document.createElement('button'); + btn.className = 'theme-btn'; + btn.title = mode.label; + btn.innerHTML = mode.icon; + btn.style.cssText = ` + width: 32px; + height: 32px; + border-radius: var(--radius-sm); + border: 1px solid var(--border); + background: var(--surface); + cursor: pointer; + font-size: 16px; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s; + `; + + btn.addEventListener('click', () => { + themeManager.setTheme(mode.value); + options.onChange?.(mode.value); + }); + + btn.addEventListener('mouseenter', () => { + if (themeManager.getTheme() !== mode.value) { + btn.style.background = 'var(--surface2)'; + } + }); + + btn.addEventListener('mouseleave', () => { + if (themeManager.getTheme() !== mode.value) { + btn.style.background = 'var(--surface)'; + } + }); + + buttons.set(mode.value, btn); + wrapper.appendChild(btn); + }); + + // Update button states + const updateButtons = (currentMode: ThemeMode) => { + buttons.forEach((btn, mode) => { + if (mode === currentMode) { + btn.style.background = 'var(--accent)'; + btn.style.borderColor = 'var(--accent)'; + } else { + btn.style.background = 'var(--surface)'; + btn.style.borderColor = 'var(--border)'; + } + }); + }; + + // Initial state + updateButtons(themeManager.getTheme()); + + // Listen for changes + themeManager.onThemeChange((mode) => { + updateButtons(mode); + }); + + container.appendChild(wrapper); + return wrapper; +} diff --git a/src/renderer/i18n/en-US.json b/src/renderer/i18n/en-US.json new file mode 100644 index 0000000..87f2f99 --- /dev/null +++ b/src/renderer/i18n/en-US.json @@ -0,0 +1,190 @@ +{ + "translation": { + "app": { + "name": "ContextMaster", + "title": "Windows Context Menu Manager", + "description": "Windows right-click menu management tool" + }, + "nav": { + "menuScenes": "Menu Scenes", + "management": "Management", + "desktop": "Desktop Right-Click", + "file": "File Right-Click", + "folder": "Folder Right-Click", + "drive": "Drive Right-Click", + "directoryBackground": "Directory Background", + "recycleBin": "Recycle Bin Right-Click", + "history": "History", + "backup": "Backup Management", + "settings": "Settings" + }, + "main": { + "search": "Global Search...", + "all": "All", + "enabled": "Enabled", + "disabled": "Disabled", + "items": "items", + "addItem": "Add Item", + "loading": "Loading...", + "loadFailed": "Load failed", + "noItems": "No items", + "selectItem": "Select Item", + "selectItemDesc": "Select an item from the left to view details", + "noItemSelected": "No item selected", + "operationFailed": "Operation failed", + "actionDone": "", + "callFailed": "Call failed", + "deleteDev": "Delete feature in development", + "searchResults": "Search Results", + "matchesFor": "matches for", + "noMatchFor": "No matches found for" + }, + "item": { + "name": "Name", + "status": "Status", + "type": "Type", + "enabled": "Enabled", + "disabled": "Disabled", + "custom": "Custom", + "system": "System", + "shellExt": "Shell Extension", + "source": "Source Program", + "sourceUnknown": "Unknown Source", + "command": "Command", + "scene": "Scene", + "registryPath": "Registry Path", + "copyPath": "Copy Path", + "openInRegedit": "Open in Registry Editor", + "locateInRegedit": "Locate in Regedit", + "locateFailed": "Locate failed", + "comObject": "COM Object", + "commandSubkey": "Command Subkey Path" + }, + "detail": { + "enableDisable": "Enable/Disable", + "edit": "Edit", + "delete": "Delete", + "disabledNote": "LegacyDisable key has been written to disable this item", + "disabledNoteShellExt": "Registry key has been prefixed with '-' to disable (Shell Extension mechanism)" + }, + "history": { + "title": "Operation History", + "recent": "Recent Operations", + "clearAll": "Clear All", + "undo": "Undo", + "noRecords": "No operation records", + "undoFailed": "Undo failed", + "undoSuccess": "Undo successful", + "confirmClear": "Clear all operation records?", + "today": "Today", + "yesterday": "Yesterday", + "operation": { + "enable": "Enable", + "disable": "Disable", + "create": "Create", + "delete": "Delete", + "update": "Update", + "backup": "Backup", + "restore": "Restore" + } + }, + "backup": { + "title": "Backup Management", + "import": "Import Backup", + "create": "Create Backup", + "auto": "Auto", + "manual": "Manual", + "warning": "Restoring backup will overwrite current config. It is recommended to create a snapshot first.", + "items": "items", + "fileSize": "file size", + "restore": "Restore", + "delete": "Delete", + "export": "Export", + "loadFailed": "Failed to load backups", + "noBackups": "No backups", + "enterNote": "Enter backup note:", + "manualBackup": "Manual Backup", + "createFailed": "Failed to create backup", + "backupCreated": "Backup created", + "previewFailed": "Failed to preview differences", + "willRestore": "Will restore backup", + "itemsWillChange": "{{count}} items will change state", + "autoSnapshot": "System will automatically create a snapshot of current state", + "confirmContinue": "Continue?", + "noDiff": "Backup", + "noNeedRestore": "is identical to current state, no need to restore", + "restoreFailed": "Restore failed", + "restored": "Backup restored", + "exportFailed": "Export failed", + "noFileSelected": "No file selected", + "importFailed": "Import failed", + "imported": "Backup imported", + "confirmDelete": "Delete this backup? This action cannot be undone.", + "deleteFailed": "Delete failed" + }, + "settings": { + "title": "Settings", + "appearance": { + "title": "Appearance", + "theme": "Theme", + "themeSystem": "Follow System", + "themeLight": "Light", + "themeDark": "Dark", + "language": "Language" + }, + "permission": { + "title": "Permission", + "admin": "Administrator Permission", + "adminGranted": "Administrator permission granted", + "adminNotGranted": "Not running as administrator", + "restartAsAdmin": "Restart as Administrator" + }, + "backup": { + "title": "Backup & Security", + "autoBackup": "Auto Backup", + "autoBackupDesc": "Automatically create config snapshot before batch operations", + "confirmDanger": "Confirm Dangerous Operations", + "confirmDangerDesc": "Show confirmation dialog before deleting items" + }, + "logs": { + "title": "Logs", + "logFiles": "Log Files", + "logDesc": "Logs are kept for the last 7 days, stored in the logs folder under app data directory", + "openLogDir": "Open Log Folder" + }, + "about": { + "title": "About", + "version": "ContextMaster v1.0.0", + "description": "Windows context menu management tool, built with Electron + TypeScript", + "developer": "Developer", + "license": "License", + "mit": "MIT License · Open source and free, contributions welcome", + "source": "Source Code Repository" + } + }, + "statusBar": { + "adminMode": "Administrator Mode", + "standardMode": "Standard Mode (Auto-elevate on operations)", + "currentScene": "Current Scene: ", + "enabled": "Enabled", + "disabled": "Disabled" + }, + "common": { + "confirm": "Confirm", + "cancel": "Cancel", + "save": "Save", + "close": "Close", + "search": "Search", + "loading": "Loading...", + "error": "Error", + "success": "Success", + "warning": "Warning" + }, + "window": { + "minimize": "Minimize", + "maximize": "Maximize", + "restore": "Restore", + "close": "Close" + } + } +} diff --git a/src/renderer/i18n/index.ts b/src/renderer/i18n/index.ts new file mode 100644 index 0000000..e22c621 --- /dev/null +++ b/src/renderer/i18n/index.ts @@ -0,0 +1,67 @@ +import i18next, { type TFunction } from 'i18next'; +import zhCN from './zh-CN.json'; +import enUS from './en-US.json'; + +export const SUPPORTED_LANGUAGES = [ + { code: 'zh-CN', name: '中文', flag: '🇨🇳' }, + { code: 'en-US', name: 'English', flag: '🇺🇸' }, +] as const; + +export type SupportedLanguage = typeof SUPPORTED_LANGUAGES[number]['code']; + +const resources = { + 'zh-CN': zhCN, + 'en-US': enUS, +}; + +export function initI18n(lng: SupportedLanguage = 'zh-CN'): Promise> { + return i18next.init({ + lng, + fallbackLng: 'zh-CN', + resources, + interpolation: { + escapeValue: false, + }, + }); +} + +export function t(key: string, options?: Record): string { + return i18next.t(key, options); +} + +export function changeLanguage(lng: SupportedLanguage): Promise> { + return i18next.changeLanguage(lng); +} + +export function getCurrentLanguage(): SupportedLanguage { + return (i18next.language as SupportedLanguage) || 'zh-CN'; +} + +export function onLanguageChanged(callback: (lng: SupportedLanguage) => void): () => void { + const handler = (lng: string) => callback(lng as SupportedLanguage); + i18next.on('languageChanged', handler); + return () => i18next.off('languageChanged', handler); +} + +export function updatePageTranslations(): void { + document.querySelectorAll('[data-i18n]').forEach((el) => { + const key = el.getAttribute('data-i18n'); + if (key) { + el.textContent = t(key); + } + }); + document.querySelectorAll('[data-i18n-placeholder]').forEach((el) => { + const key = el.getAttribute('data-i18n-placeholder'); + if (key && el instanceof HTMLInputElement) { + el.placeholder = t(key); + } + }); + document.querySelectorAll('[data-i18n-title]').forEach((el) => { + const key = el.getAttribute('data-i18n-title'); + if (key && el instanceof HTMLElement) { + el.title = t(key); + } + }); +} + +export { i18next }; diff --git a/src/renderer/i18n/zh-CN.json b/src/renderer/i18n/zh-CN.json new file mode 100644 index 0000000..7999041 --- /dev/null +++ b/src/renderer/i18n/zh-CN.json @@ -0,0 +1,190 @@ +{ + "translation": { + "app": { + "name": "ContextMaster", + "title": "Windows 右键菜单管理工具", + "description": "Windows 右键菜单管理工具" + }, + "nav": { + "menuScenes": "菜单场景", + "management": "管理", + "desktop": "桌面右键", + "file": "文件右键", + "folder": "文件夹右键", + "drive": "驱动器右键", + "directoryBackground": "目录背景", + "recycleBin": "回收站右键", + "history": "操作记录", + "backup": "备份管理", + "settings": "设置" + }, + "main": { + "search": "全局搜索...", + "all": "全部", + "enabled": "已启用", + "disabled": "已禁用", + "items": "个条目", + "addItem": "新增条目", + "loading": "加载中...", + "loadFailed": "加载失败", + "noItems": "暂无条目", + "selectItem": "选择条目", + "selectItemDesc": "在左侧选择一个条目查看详情", + "noItemSelected": "未选中任何条目", + "operationFailed": "操作失败", + "actionDone": "已", + "callFailed": "调用失败", + "deleteDev": "删除功能开发中", + "searchResults": "搜索结果", + "matchesFor": "个匹配", + "noMatchFor": "未找到匹配" + }, + "item": { + "name": "名称", + "status": "状态", + "type": "类型", + "enabled": "已启用", + "disabled": "已禁用", + "custom": "自定义", + "system": "系统", + "shellExt": "Shell 扩展", + "source": "来源程序", + "sourceUnknown": "未知来源", + "command": "执行命令", + "scene": "场景", + "registryPath": "注册表路径", + "copyPath": "复制路径", + "openInRegedit": "在注册表编辑器中打开", + "locateInRegedit": "在 Regedit 中定位", + "locateFailed": "定位失败", + "comObject": "COM 对象", + "commandSubkey": "命令子键路径" + }, + "detail": { + "enableDisable": "启用/禁用", + "edit": "编辑", + "delete": "删除", + "disabledNote": "已在注册表项下写入 LegacyDisable 键值使其禁用", + "disabledNoteShellExt": "已将注册表键名添加 - 前缀使其禁用(Shell 扩展机制)" + }, + "history": { + "title": "操作记录", + "recent": "最近操作", + "clearAll": "清除全部", + "undo": "撤销", + "noRecords": "暂无操作记录", + "undoFailed": "撤销失败", + "undoSuccess": "撤销成功", + "confirmClear": "确定清除所有操作记录?", + "today": "今天", + "yesterday": "昨天", + "operation": { + "enable": "启用", + "disable": "禁用", + "create": "新增", + "delete": "删除", + "update": "修改", + "backup": "备份", + "restore": "恢复" + } + }, + "backup": { + "title": "备份管理", + "import": "导入备份", + "create": "新建备份", + "auto": "自动", + "manual": "手动", + "warning": "恢复备份会覆盖当前配置,建议先手动创建快照", + "items": "条目", + "fileSize": "文件大小", + "restore": "恢复", + "delete": "删除", + "export": "导出", + "loadFailed": "加载备份失败", + "noBackups": "暂无备份", + "enterNote": "请输入备份备注:", + "manualBackup": "手动备份", + "createFailed": "创建备份失败", + "backupCreated": "备份已创建", + "previewFailed": "预览差异失败", + "willRestore": "将恢复备份", + "itemsWillChange": "共有 {{count}} 个条目状态将变更", + "autoSnapshot": "系统将自动创建当前状态的快照", + "confirmContinue": "确定继续?", + "noDiff": "备份", + "noNeedRestore": "与当前状态无差异,无需恢复", + "restoreFailed": "恢复失败", + "restored": "已恢复备份", + "exportFailed": "导出失败", + "noFileSelected": "未选择文件", + "importFailed": "导入失败", + "imported": "备份已导入", + "confirmDelete": "确定删除这个备份?此操作不可撤销。", + "deleteFailed": "删除失败" + }, + "settings": { + "title": "设置", + "appearance": { + "title": "外观", + "theme": "主题", + "themeSystem": "跟随系统", + "themeLight": "浅色", + "themeDark": "深色", + "language": "语言" + }, + "permission": { + "title": "权限", + "admin": "管理员权限", + "adminGranted": "已获取管理员权限", + "adminNotGranted": "未以管理员身份运行", + "restartAsAdmin": "以管理员重启" + }, + "backup": { + "title": "备份与安全", + "autoBackup": "自动备份", + "autoBackupDesc": "批量操作前自动创建配置快照", + "confirmDanger": "高危操作二次确认", + "confirmDangerDesc": "删除条目等操作前弹出确认对话框" + }, + "logs": { + "title": "日志", + "logFiles": "日志文件", + "logDesc": "运行日志保留最近 7 天,存储于应用数据目录下的 logs 文件夹", + "openLogDir": "打开日志文件夹" + }, + "about": { + "title": "关于", + "version": "ContextMaster v1.0.0", + "description": "Windows 右键菜单管理工具,基于 Electron + TypeScript 构建", + "developer": "开发者", + "license": "开源协议", + "mit": "MIT License · 开源免费,欢迎贡献", + "source": "源码仓库" + } + }, + "statusBar": { + "adminMode": "管理员模式", + "standardMode": "标准模式(操作时自动提权)", + "currentScene": "当前场景:", + "enabled": "已启用", + "disabled": "禁用" + }, + "common": { + "confirm": "确定", + "cancel": "取消", + "save": "保存", + "close": "关闭", + "search": "搜索", + "loading": "加载中...", + "error": "错误", + "success": "成功", + "warning": "警告" + }, + "window": { + "minimize": "最小化", + "maximize": "最大化", + "restore": "还原", + "close": "关闭" + } + } +} diff --git a/src/renderer/index.html b/src/renderer/index.html index 6e4079d..6353a0d 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -203,13 +203,13 @@ ContextMaster
- - -
@@ -221,53 +221,53 @@ @@ -281,13 +281,13 @@ 桌面右键 — 个条目
- - - + + +
@@ -301,23 +301,23 @@
-
选择条目
-
在左侧选择一个条目查看详情
+
选择条目
+
在左侧选择一个条目查看详情
-
未选中任何条目
+
未选中任何条目
@@ -327,15 +327,15 @@
- 操作记录 最近操作 + 操作记录 最近操作
- 全部 - 启用 - 禁用 - 删除 + 全部 + 启用 + 禁用 + 删除
- +
@@ -347,21 +347,21 @@
- 备份管理 + 备份管理
- 恢复备份会覆盖当前配置,建议先手动创建快照 + 恢复备份会覆盖当前配置,建议先手动创建快照
加载中…
@@ -372,29 +372,60 @@
- 设置 + 设置
+
-
权限
+
外观
-
管理员权限
+
主题
+
选择应用主题外观
+
+
+ +
+
+
+
+
语言
+
选择界面语言
+
+
+ +
+
+
+ + +
+
权限
+
+
+
管理员权限
检查中…
- +
-
备份与安全
+
备份与安全
-
自动备份
-
批量操作前自动创建配置快照
+
自动备份
+
批量操作前自动创建配置快照
@@ -402,8 +433,8 @@
-
高危操作二次确认
-
删除条目等操作前弹出确认对话框
+
高危操作二次确认
+
删除条目等操作前弹出确认对话框
@@ -412,41 +443,41 @@
-
日志
+
日志
-
日志文件
-
运行日志保留最近 7 天,存储于应用数据目录下的 logs 文件夹
+
日志文件
+
运行日志保留最近 7 天,存储于应用数据目录下的 logs 文件夹
- +
-
关于
+
关于
-
ContextMaster v1.0.0
-
Windows 右键菜单管理工具,基于 Electron + TypeScript 构建
+
ContextMaster v1.0.0
+
Windows 右键菜单管理工具,基于 Electron + TypeScript 构建
-
开发者
+
开发者
tanzz
-
开源协议
-
MIT License · 开源免费,欢迎贡献
+
开源协议
+
MIT License · 开源免费,欢迎贡献
-
源码仓库
+
源码仓库
@@ -480,7 +511,7 @@
操作已完成 - +
diff --git a/src/renderer/main.ts b/src/renderer/main.ts index b051602..b9d2179 100644 --- a/src/renderer/main.ts +++ b/src/renderer/main.ts @@ -1,11 +1,17 @@ import './api/bridge'; +import './styles/themes.css'; import { MenuScene } from '../shared/enums'; import type { MenuItemEntry } from '../shared/types'; -import { loadScene, SCENE_NAMES, preloadBadgeCounts, renderGlobalResults, restoreSceneTitle } from './pages/mainPage'; -import { loadHistory, renderHistory, filterHistory, clearAllHistory } from './pages/historyPage'; -import { loadBackups, renderBackup, createBackup, importBackup } from './pages/backupPage'; +import { loadScene, preloadBadgeCounts, renderGlobalResults, restoreSceneTitle } from './pages/mainPage'; +import { loadHistory, filterHistory, clearAllHistory } from './pages/historyPage'; +import { loadBackups, createBackup, importBackup } from './pages/backupPage'; import { initSettings, requestAdminRestart, toggleSwitch, openLogDir } from './pages/settingsPage'; +// i18n 和主题 +import { initI18n, t, updatePageTranslations } from './i18n'; +import { initTheme, getThemeManager } from './utils/themeManager'; +import { getSettingsStore } from './utils/settingsStore'; + // ── 全局搜索 ── let allScenesCache: Map | null = null; let globalSearchTimer: ReturnType | null = null; @@ -109,19 +115,17 @@ async function updateMaximizeBtn(): Promise { const btn = document.getElementById('winMaximize'); if (!btn) return; const isMax = await window.api.isMaximized(); - btn.title = isMax ? '还原' : '最大化'; + btn.title = isMax ? t('window.restore') : t('window.maximize'); } -// ── 管理员状态检查 ── async function checkAdminStatus(): Promise { const result = await window.api.isAdmin(); const isAdmin = result.success && result.data; - // 状态栏 const sbDot = document.getElementById('sbDot') as HTMLElement | null; const sbAdmin = document.getElementById('sbAdmin'); if (sbDot) sbDot.style.background = isAdmin ? '#70D070' : 'rgba(255,255,255,0.4)'; - if (sbAdmin) sbAdmin.textContent = isAdmin ? '管理员模式' : '标准模式(操作时自动提权)'; + if (sbAdmin) sbAdmin.textContent = isAdmin ? t('statusBar.adminMode') : t('statusBar.standardMode'); if (sbAdmin) { (sbAdmin as HTMLElement).style.cursor = ''; (sbAdmin as HTMLElement).onclick = null; @@ -149,6 +153,21 @@ Object.assign(window, { // ── 初始化 ── document.addEventListener('DOMContentLoaded', async () => { + // 初始化 i18n 和主题(从设置中读取) + const settingsStore = getSettingsStore(); + const settings = settingsStore.getSettings(); + + // 初始化主题 + initTheme(); + const themeManager = getThemeManager(); + themeManager.setTheme(settings.theme); + + // 初始化 i18n + await initI18n(settings.language); + + // 应用页面翻译 + updatePageTranslations(); + // 管理员检查 await checkAdminStatus(); diff --git a/src/renderer/pages/backupPage.ts b/src/renderer/pages/backupPage.ts index 4861af4..f47356d 100644 --- a/src/renderer/pages/backupPage.ts +++ b/src/renderer/pages/backupPage.ts @@ -1,13 +1,14 @@ import '../api/bridge'; import type { BackupSnapshot } from '../../shared/types'; import { BackupType } from '../../shared/enums'; +import { t } from '../i18n'; let backups: BackupSnapshot[] = []; export async function loadBackups(): Promise { const result = await window.api.getBackups(); if (!result.success) { - alert(`加载备份失败: ${result.error}`); + alert(`${t('backup.loadFailed')}: ${result.error}`); return; } backups = result.data; @@ -21,7 +22,7 @@ export function renderBackup(): void { if (!backups.length) { grid.innerHTML = `
-
暂无备份
+
${t('backup.noBackups')}
`; return; } @@ -43,18 +44,18 @@ export function renderBackup(): void {
${dateStr}
SHA256: ${b.sha256Checksum.substring(0, 12)}…
- ${isAuto ? '自动' : '手动'} + ${isAuto ? t('backup.auto') : t('backup.manual')}
-
${items}条目数
-
${sizeKb}KB文件大小
+
${items}${t('backup.items')}
+
${sizeKb}KB${t('backup.fileSize')}
+ onclick="window._backupPage.restoreBackup(${b.id})">${t('backup.restore')} - +
${timeStr}
- ${canUndo ? `` : ''} + ${canUndo ? `` : ''}
`; }).join(''); } @@ -81,16 +85,16 @@ export function renderHistory(filter?: string): void { export async function undoRecord(id: number): Promise { const result = await window.api.undoOperation(id); if (!result.success) { - alert(`撤销失败: ${result.error}`); + alert(`${t('history.undoFailed')}: ${result.error}`); return; } await loadHistory(); (window as Window & { showUndo?: (msg: string) => void }) - .showUndo?.('撤销成功'); + .showUndo?.(t('history.undoSuccess')); } export async function clearAllHistory(): Promise { - if (!confirm('确定清除所有操作记录?')) return; + if (!confirm(t('history.confirmClear'))) return; await window.api.clearHistory(); allRecords = []; renderHistory(); @@ -109,8 +113,8 @@ function formatTime(iso: string): string { const diffMs = now.getTime() - d.getTime(); const diffDays = Math.floor(diffMs / 86400000); const timeStr = d.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }); - if (diffDays === 0) return `今天 ${timeStr}`; - if (diffDays === 1) return `昨天 ${timeStr}`; + if (diffDays === 0) return `${t('history.today')} ${timeStr}`; + if (diffDays === 1) return `${t('history.yesterday')} ${timeStr}`; return d.toLocaleDateString('zh-CN') + ' ' + timeStr; } catch { return iso; diff --git a/src/renderer/pages/mainPage.ts b/src/renderer/pages/mainPage.ts index 71c4c38..63e836a 100644 --- a/src/renderer/pages/mainPage.ts +++ b/src/renderer/pages/mainPage.ts @@ -1,8 +1,8 @@ import '../api/bridge'; import { MenuScene, MenuItemType } from '../../shared/enums'; import type { MenuItemEntry, ToggleItemParams } from '../../shared/types'; +import { t } from '../i18n'; -// ── 场景映射 ── export const SCENE_REG_ROOTS: Record = { [MenuScene.Desktop]: 'HKEY_CLASSES_ROOT\\DesktopBackground\\Shell', [MenuScene.File]: 'HKEY_CLASSES_ROOT\\*\\shell', @@ -12,14 +12,17 @@ export const SCENE_REG_ROOTS: Record = { [MenuScene.RecycleBin]: 'HKEY_CLASSES_ROOT\\CLSID\\{645FF040-5081-101B-9F08-00AA002F954E}\\shell', }; -export const SCENE_NAMES: Record = { - [MenuScene.Desktop]: '桌面右键', - [MenuScene.File]: '文件右键', - [MenuScene.Folder]: '文件夹右键', - [MenuScene.Drive]: '驱动器右键', - [MenuScene.DirectoryBackground]:'目录背景', - [MenuScene.RecycleBin]: '回收站右键', -}; +export function getSceneName(scene: MenuScene): string { + const sceneKeys: Record = { + [MenuScene.Desktop]: 'nav.desktop', + [MenuScene.File]: 'nav.file', + [MenuScene.Folder]: 'nav.folder', + [MenuScene.Drive]: 'nav.drive', + [MenuScene.DirectoryBackground]:'nav.directoryBackground', + [MenuScene.RecycleBin]: 'nav.recycleBin', + }; + return t(sceneKeys[scene]); +} // ── 状态 ── let currentItems: MenuItemEntry[] = []; @@ -33,7 +36,7 @@ export async function loadScene(scene: MenuScene): Promise { loadingScene = true; const listEl = document.getElementById('itemList'); - if (listEl) listEl.innerHTML = `
加载中…
`; + if (listEl) listEl.innerHTML = `
${t('main.loading')}
`; selectedItemId = null; resetDetailPanel(); @@ -42,7 +45,7 @@ export async function loadScene(scene: MenuScene): Promise { loadingScene = false; if (!result.success) { - showError(`加载失败: ${result.error}`); + showError(`${t('main.loadFailed')}: ${result.error}`); return; } @@ -73,7 +76,7 @@ export function renderItems(): void { if (!items.length) { listEl.innerHTML = `
-
暂无条目
+
${t('main.noItems')}
`; return; } @@ -85,16 +88,16 @@ function renderItemCard(item: MenuItemEntry, showScene = false): string { const isSelected = item.id === selectedItemId; const typeTag = item.type === MenuItemType.Custom - ? '自定义' + ? `${t('item.custom')}` : item.type === MenuItemType.ShellExt - ? 'Shell 扩展' - : '系统'; + ? `${t('item.shellExt')}` + : `${t('item.system')}`; const stateTag = item.isEnabled - ? '已启用' - : '已禁用'; + ? `${t('item.enabled')}` + : `${t('item.disabled')}`; const sourceText = showScene - ? `${SCENE_NAMES[item.menuScene]}${item.source ? ' · ' + item.source : ''}` - : (item.source || '未知来源'); + ? `${getSceneName(item.menuScene)}${item.source ? ' · ' + item.source : ''}` + : (item.source || t('item.sourceUnknown')); return `
{ const result = await window.api.toggleItem(params); if (!result.success) { - showOperationError(`操作失败: ${result.error}`); + showOperationError(`${t('main.operationFailed')}: ${result.error}`); return; } @@ -158,9 +161,9 @@ export async function toggleItem(id: number): Promise { item.registryKey = result.data.newRegistryKey; } renderItems(); - const action = item.isEnabled ? '启用' : '禁用'; + const action = item.isEnabled ? t('history.operation.enable') : t('history.operation.disable'); (window as Window & { showUndo?: (msg: string, itemId: number) => void }) - .showUndo?.(`已${action}「${item.name}」`, id); + .showUndo?.(`${t('main.actionDone')}${action}「${item.name}」`, id); updateStatusBarFromCurrent(); if (selectedItemId === id) showDetail(id); @@ -187,12 +190,11 @@ export function showDetail(id: number): void { const regCmdPath = isShellExt ? `(COM DLL,CLSID: ${item.command})` : `${regItemPath}\\command`; - // 在 HTML onclick 属性里,反斜杠会被 JS 当转义前缀消耗,必须双写 const regItemPathAttr = regItemPath.replace(/\\/g, '\\\\').replace(/'/g, "\\'"); const disabledNoteContent = isShellExt - ? `已将注册表键名添加 - 前缀使其禁用(Shell 扩展机制)` - : `已在注册表项下写入 LegacyDisable 键值使其禁用`; + ? t('detail.disabledNoteShellExt') + : t('detail.disabledNote'); const legacyNote = item.isEnabled ? '' : `
@@ -205,45 +207,45 @@ export function showDetail(id: number): void { const actionsEl = document.getElementById('detailActions'); if (nameEl) nameEl.textContent = item.name; - if (subEl) subEl.textContent = item.source || '未知来源'; + if (subEl) subEl.textContent = item.source || t('item.sourceUnknown'); if (bodyEl) bodyEl.innerHTML = `
-
名称
+
${t('item.name')}
${escapeHtml(item.name)}
-
状态
+
${t('item.status')}
- ${item.isEnabled ? '已启用' : '已禁用'} + ${item.isEnabled ? t('item.enabled') : t('item.disabled')}
-
类型
+
${t('item.type')}
- ${item.type === MenuItemType.Custom ? '自定义' : item.type === MenuItemType.ShellExt ? 'Shell 扩展' : '系统'} + ${item.type === MenuItemType.Custom ? t('item.custom') : item.type === MenuItemType.ShellExt ? t('item.shellExt') : t('item.system')}
-
来源程序
+
${t('item.source')}
${escapeHtml(item.source || '—')}
-
执行命令
+
${t('item.command')}
${escapeHtml(item.command || '—')}
-
场景
-
${SCENE_NAMES[item.menuScene]}
+
${t('item.scene')}
+
${getSceneName(item.menuScene)}
-
注册表路径
+
${t('item.registryPath')}
${escapeHtml(regItemPath)}
@@ -252,16 +254,16 @@ export function showDetail(id: number): void {
-
${isShellExt ? 'COM 对象' : '命令子键路径'}
+
${isShellExt ? t('item.comObject') : t('item.commandSubkey')}
${escapeHtml(regCmdPath)}
-
在注册表编辑器中打开
-
+
`; @@ -285,7 +287,7 @@ export function setFilter(mode: 'all' | 'enabled' | 'disabled', btn: HTMLElement // ── 状态栏 ── function updateStatusBar(scene: MenuScene): void { const sbScene = document.getElementById('sbScene'); - if (sbScene) sbScene.textContent = `当前场景:${SCENE_NAMES[scene]}`; + if (sbScene) sbScene.textContent = `${t('statusBar.currentScene')}${getSceneName(scene)}`; updateStatusBarFromCurrent(); } @@ -293,13 +295,13 @@ function updateStatusBarFromCurrent(): void { const enabled = currentItems.filter((i) => i.isEnabled).length; const disabled = currentItems.length - enabled; const sbCount = document.getElementById('sbCount'); - if (sbCount) sbCount.textContent = `已启用 ${enabled} / 禁用 ${disabled}`; + if (sbCount) sbCount.textContent = `${t('statusBar.enabled')} ${enabled} / ${t('statusBar.disabled')} ${disabled}`; } function updateSceneHeader(scene: MenuScene): void { const titleEl = document.getElementById('sceneTitle'); if (titleEl) { - titleEl.innerHTML = `${SCENE_NAMES[scene]} ${currentItems.length} 个条目`; + titleEl.innerHTML = `${getSceneName(scene)} ${currentItems.length} ${t('main.items')}`; } const badgeEl = document.getElementById(`badge-${scene}`); if (badgeEl) badgeEl.textContent = String(currentItems.length); @@ -310,11 +312,11 @@ function resetDetailPanel(): void { const subEl = document.getElementById('detailSub'); const bodyEl = document.getElementById('detailBody'); const actionsEl = document.getElementById('detailActions'); - if (nameEl) nameEl.textContent = '选择条目'; - if (subEl) subEl.textContent = '在左侧选择一个条目查看详情'; + if (nameEl) nameEl.textContent = t('main.selectItem'); + if (subEl) subEl.textContent = t('main.selectItemDesc'); if (bodyEl) bodyEl.innerHTML = `
-
未选中任何条目
+
${t('main.noItemSelected')}
`; if (actionsEl) actionsEl.style.display = 'none'; } @@ -370,17 +372,15 @@ export function deleteSelected(): void { if (selectedItemId == null) return; const item = currentItems.find((i) => i.id === selectedItemId); if (!item) return; - alert(`删除功能开发中\n\n条目:${item.name}`); + alert(`${t('main.deleteDev')}\n\n${t('item.name')}: ${item.name}`); } -// ── 全局搜索结果渲染 ── export function renderGlobalResults(items: MenuItemEntry[], query: string): void { - // 切换到 main 页(确保可见) document.querySelectorAll('.page').forEach((p) => p.classList.remove('active')); document.getElementById('page-main')?.classList.add('active'); const titleEl = document.getElementById('sceneTitle'); - if (titleEl) titleEl.innerHTML = `搜索结果 ${items.length} 个匹配「${escapeHtml(query)}」`; + if (titleEl) titleEl.innerHTML = `${t('main.searchResults')} ${items.length} ${t('main.matchesFor')}「${escapeHtml(query)}」`; selectedItemId = null; resetDetailPanel(); @@ -391,7 +391,7 @@ export function renderGlobalResults(items: MenuItemEntry[], query: string): void if (!items.length) { listEl.innerHTML = `
-
未找到匹配「${escapeHtml(query)}」的条目
+
${t('main.noMatchFor')}「${escapeHtml(query)}」
`; return; } @@ -399,11 +399,10 @@ export function renderGlobalResults(items: MenuItemEntry[], query: string): void listEl.innerHTML = items.map((item) => renderItemCard(item, true)).join(''); } -// ── 恢复场景视图(全局搜索退出时调用)── export function restoreSceneTitle(scene: MenuScene): void { const titleEl = document.getElementById('sceneTitle'); if (titleEl) { - titleEl.innerHTML = `${SCENE_NAMES[scene]} ${currentItems.length} 个条目`; + titleEl.innerHTML = `${getSceneName(scene)} ${currentItems.length} ${t('main.items')}`; } renderItems(); resetDetailPanel(); diff --git a/src/renderer/pages/settingsPage.ts b/src/renderer/pages/settingsPage.ts index 4c77fbf..57baee3 100644 --- a/src/renderer/pages/settingsPage.ts +++ b/src/renderer/pages/settingsPage.ts @@ -1,35 +1,110 @@ -import '../api/bridge'; - -export async function initSettings(): Promise { - // 显示管理员状态 - const adminResult = await window.api.isAdmin(); - const adminStatus = document.getElementById('adminStatus'); - if (adminStatus) { - if (adminResult.success && adminResult.data) { - adminStatus.textContent = '已获取管理员权限'; - adminStatus.style.color = 'var(--success)'; - } else { - adminStatus.textContent = '未以管理员身份运行'; - adminStatus.style.color = 'var(--danger)'; - } - } -} - -export async function requestAdminRestart(): Promise { - if (!confirm('确定要以管理员身份重启应用吗?')) return; - await window.api.restartAsAdmin(); -} - -export function toggleSwitch(btn: HTMLElement): void { - btn.classList.toggle('on'); - btn.classList.toggle('off'); -} - -export async function openLogDir(): Promise { - const result = await window.api.openLogDir(); - if (!result.success) alert(`打开日志目录失败: ${result.error}`); -} - -const settingsPageApi = { requestAdminRestart, toggleSwitch, openLogDir }; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -(window as any)._settingsPage = settingsPageApi; +import '../api/bridge'; +import { t, changeLanguage, getCurrentLanguage, type SupportedLanguage } from '../i18n'; +import { getThemeManager, type ThemeMode } from '../utils/themeManager'; +import { getSettingsStore } from '../utils/settingsStore'; + +// 是否已经初始化的标记 +let isSettingsInitialized = false; + +// 初始化设置页面 +export async function initSettings(): Promise { + // 显示管理员状态 + await updateAdminStatus(); + + // 初始化外观设置(每次都初始化,确保事件监听器正确绑定) + initAppearanceSettings(); +} + +// 更新管理员状态 +async function updateAdminStatus(): Promise { + const adminResult = await window.api.isAdmin(); + const adminStatus = document.getElementById('adminStatus'); + if (adminStatus) { + if (adminResult.success && adminResult.data) { + adminStatus.textContent = '已获取管理员权限'; + adminStatus.style.color = 'var(--success)'; + } else { + adminStatus.textContent = '未以管理员身份运行'; + adminStatus.style.color = 'var(--danger)'; + } + } +} + +// 初始化外观设置(主题和语言) +function initAppearanceSettings(): void { + // 主题设置 + const themeSelect = document.getElementById('themeSelect') as HTMLSelectElement | null; + if (themeSelect) { + const themeManager = getThemeManager(); + + // 设置当前值 + themeSelect.value = themeManager.getTheme(); + + // 使用 onclick 而不是 addEventListener 来避免重复绑定 + themeSelect.onchange = () => { + const mode = themeSelect.value as ThemeMode; + console.log('主题切换到:', mode); + themeManager.setTheme(mode); + getSettingsStore().setSetting('theme', mode); + + // 立即应用主题,不等待外部通知 + document.documentElement.setAttribute('data-theme', mode === 'dark' ? 'dark' : 'light'); + }; + } + + // 语言设置 + const languageSelect = document.getElementById('languageSelect') as HTMLSelectElement | null; + if (languageSelect) { + // 设置当前值 + languageSelect.value = getCurrentLanguage(); + + // 使用 onclick 而不是 addEventListener 来避免重复绑定 + languageSelect.onchange = () => { + const lang = languageSelect.value as SupportedLanguage; + console.log('语言切换到:', lang); + + // 保存语言设置 + getSettingsStore().setSetting('language', lang); + + // 切换语言并刷新页面 + changeLanguage(lang).then(() => { + console.log('语言切换成功,准备刷新页面'); + // 使用延迟确保设置已保存 + setTimeout(() => { + window.location.reload(); + }, 100); + }).catch((error) => { + console.error('语言切换失败:', error); + alert('语言切换失败,请重试'); + }); + }; + } +} + +// 以管理员身份重启 +export async function requestAdminRestart(): Promise { + if (!confirm('确定要以管理员身份重启应用吗?')) return; + await window.api.restartAsAdmin(); +} + +// 切换开关状态 +export function toggleSwitch(btn: HTMLElement): void { + btn.classList.toggle('on'); + btn.classList.toggle('off'); +} + +// 打开日志目录 +export async function openLogDir(): Promise { + const result = await window.api.openLogDir(); + if (!result.success) alert(`打开日志目录失败: ${result.error}`); +} + +// 导出 API 供 HTML 调用 +const settingsPageApi = { + requestAdminRestart, + toggleSwitch, + openLogDir +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +(window as any)._settingsPage = settingsPageApi; diff --git a/src/renderer/styles/themes.css b/src/renderer/styles/themes.css new file mode 100644 index 0000000..a24860d --- /dev/null +++ b/src/renderer/styles/themes.css @@ -0,0 +1,122 @@ +/* Theme CSS Variables */ + +/* Light Theme (Default) */ +:root { + /* Primary Colors */ + --accent: #0067C0; + --accent-light: #0078D4; + --accent-hover: #005A9E; + --accent-bg: #EFF6FC; + + /* Status Colors */ + --success: #0F7B0F; + --success-bg: #DFF6DD; + --danger: #C42B1C; + --danger-bg: #FDE7E9; + --warning: #9D5D00; + --warning-bg: #FFF4CE; + + /* Background Colors */ + --bg: #F3F3F3; + --surface: #FFFFFF; + --surface2: #F9F9F9; + + /* Border & Text */ + --border: #E0E0E0; + --border2: #EBEBEB; + --text: #1A1A1A; + --text2: #616161; + --text3: #8A8A8A; + + /* Layout */ + --nav-w: 220px; + --radius: 8px; + --radius-sm: 4px; + + /* Shadows */ + --shadow: 0 2px 8px rgba(0,0,0,0.08); + --shadow-md: 0 4px 16px rgba(0,0,0,0.12); +} + +/* Dark Theme */ +[data-theme="dark"] { + /* Primary Colors */ + --accent: #4CA3F5; + --accent-light: #5CB3FF; + --accent-hover: #3A93E5; + --accent-bg: #1A3A5C; + + /* Status Colors */ + --success: #5CB85C; + --success-bg: #1E3A1E; + --danger: #E74C3C; + --danger-bg: #3A1A1A; + --warning: #F0AD4E; + --warning-bg: #3A3010; + + /* Background Colors */ + --bg: #1E1E1E; + --surface: #2D2D2D; + --surface2: #3A3A3A; + + /* Border & Text */ + --border: #404040; + --border2: #505050; + --text: #E0E0E0; + --text2: #B0B0B0; + --text3: #808080; + + /* Shadows - darker for dark theme */ + --shadow: 0 2px 8px rgba(0,0,0,0.3); + --shadow-md: 0 4px 16px rgba(0,0,0,0.4); +} + +/* Additional Dark Theme Overrides */ +[data-theme="dark"] .titlebar { + background: rgba(30, 30, 30, 0.85); +} + +[data-theme="dark"] .nav { + background: rgba(45, 45, 45, 0.8); +} + +[data-theme="dark"] .toolbar { + background: rgba(30, 30, 30, 0.9); +} + +[data-theme="dark"] .item-card { + background: var(--surface); +} + +[data-theme="dark"] .detail-panel { + background: var(--surface); +} + +[data-theme="dark"] .settings-section { + background: var(--surface); +} + +/* Scrollbar dark theme */ +[data-theme="dark"] ::-webkit-scrollbar-thumb { + background: rgba(255,255,255,0.2); +} + +/* Ensure proper contrast for all text */ +[data-theme="dark"] .empty-state svg { + fill: var(--border); +} + +/* Button hover states for dark theme */ +[data-theme="dark"] .btn-secondary:hover { + background: var(--surface2); +} + +[data-theme="dark"] .icon-btn:hover { + background: rgba(255,255,255,0.1); +} + +/* Selection color */ +[data-theme="dark"] ::selection { + background: var(--accent); + color: white; +} diff --git a/src/renderer/utils/settingsStore.ts b/src/renderer/utils/settingsStore.ts new file mode 100644 index 0000000..cc9bd06 --- /dev/null +++ b/src/renderer/utils/settingsStore.ts @@ -0,0 +1,88 @@ +import type { SupportedLanguage } from '../i18n'; + +export interface AppSettings { + theme: 'system' | 'light' | 'dark'; + language: SupportedLanguage; +} + +const SETTINGS_KEY = 'contextmaster_settings'; + +const DEFAULT_SETTINGS: AppSettings = { + theme: 'system', + language: 'zh-CN', +}; + +class SettingsStore { + private settings: AppSettings; + private listeners: Set<(settings: AppSettings) => void> = new Set(); + + constructor() { + this.settings = this.loadSettings(); + } + + private loadSettings(): AppSettings { + try { + const stored = localStorage.getItem(SETTINGS_KEY); + if (stored) { + const parsed = JSON.parse(stored); + return { + ...DEFAULT_SETTINGS, + ...parsed, + }; + } + } catch { + // localStorage not available + } + return { ...DEFAULT_SETTINGS }; + } + + private saveSettings(): void { + try { + localStorage.setItem(SETTINGS_KEY, JSON.stringify(this.settings)); + } catch { + // localStorage not available + } + } + + private notifyListeners(): void { + const settings = { ...this.settings }; + this.listeners.forEach((listener) => { + listener(settings); + }); + } + + public getSettings(): AppSettings { + return { ...this.settings }; + } + + public setSetting(key: K, value: AppSettings[K]): void { + if (this.settings[key] === value) return; + this.settings[key] = value; + this.saveSettings(); + this.notifyListeners(); + } + + public onSettingsChange(callback: (settings: AppSettings) => void): () => void { + this.listeners.add(callback); + return () => { + this.listeners.delete(callback); + }; + } + + public reset(): void { + this.settings = { ...DEFAULT_SETTINGS }; + this.saveSettings(); + this.notifyListeners(); + } +} + +let settingsStoreInstance: SettingsStore | null = null; + +export function getSettingsStore(): SettingsStore { + if (!settingsStoreInstance) { + settingsStoreInstance = new SettingsStore(); + } + return settingsStoreInstance; +} + +export { SettingsStore, DEFAULT_SETTINGS }; diff --git a/src/renderer/utils/themeManager.ts b/src/renderer/utils/themeManager.ts new file mode 100644 index 0000000..c145540 --- /dev/null +++ b/src/renderer/utils/themeManager.ts @@ -0,0 +1,115 @@ +// 主题管理器 +// 支持 system/light/dark 三种模式 + +export type ThemeMode = 'system' | 'light' | 'dark'; + +const THEME_KEY = 'contextmaster-theme'; + +class ThemeManager { + private currentMode: ThemeMode = 'system'; + private mediaQuery: MediaQueryList | null = null; + private listeners: Array<(mode: ThemeMode, isDark: boolean) => void> = []; + + constructor() { + this.loadTheme(); + this.setupMediaQuery(); + this.applyTheme(); + } + + private loadTheme(): void { + try { + const saved = localStorage.getItem(THEME_KEY); + if (saved === 'light' || saved === 'dark' || saved === 'system') { + this.currentMode = saved; + } + } catch { + // localStorage 不可用 + } + } + + private saveTheme(): void { + try { + localStorage.setItem(THEME_KEY, this.currentMode); + } catch { + // localStorage 不可用 + } + } + + private setupMediaQuery(): void { + if (typeof window !== 'undefined' && window.matchMedia) { + this.mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); + this.mediaQuery.addEventListener('change', () => { + if (this.currentMode === 'system') { + this.applyTheme(); + } + }); + } + } + + private applyTheme(): void { + const isDark = this.isDark(); + + if (isDark) { + document.documentElement.setAttribute('data-theme', 'dark'); + } else { + document.documentElement.setAttribute('data-theme', 'light'); + } + + // 通知所有监听器 + this.listeners.forEach(listener => { + listener(this.currentMode, isDark); + }); + } + + public setTheme(mode: ThemeMode): void { + if (this.currentMode === mode) return; + + this.currentMode = mode; + this.saveTheme(); + this.applyTheme(); + } + + public getTheme(): ThemeMode { + return this.currentMode; + } + + public isDark(): boolean { + if (this.currentMode === 'system') { + return this.mediaQuery?.matches ?? false; + } + return this.currentMode === 'dark'; + } + + public onChange(callback: (mode: ThemeMode, isDark: boolean) => void): () => void { + this.listeners.push(callback); + + // 立即调用一次 + callback(this.currentMode, this.isDark()); + + return () => { + const index = this.listeners.indexOf(callback); + if (index > -1) { + this.listeners.splice(index, 1); + } + }; + } + + // 别名方法,用于兼容 + public onThemeChange = this.onChange.bind(this); +} + +// 全局实例 +let themeManager: ThemeManager | null = null; + +export function getThemeManager(): ThemeManager { + if (!themeManager) { + themeManager = new ThemeManager(); + } + return themeManager; +} + +export function initTheme(): void { + getThemeManager(); +} + +export { ThemeManager }; From e4bc2eb86469a151466d14308344f15eb8a51cc5 Mon Sep 17 00:00:00 2001 From: tanzz Date: Sat, 14 Mar 2026 04:47:44 +0800 Subject: [PATCH 3/7] feat(i18n): implement refresh callback system for language changes Add refresh callback registration and triggering mechanism to automatically update UI components when language changes. This eliminates the need for page reloads and ensures all relevant components refresh their content. Modified pages (backup, history, main, settings) to register their refresh functions. The i18n module now triggers all registered callbacks after language change completes. --- src/renderer/i18n/index.ts | 17 ++++++++++++++++- src/renderer/main.ts | 10 +++++++++- src/renderer/pages/backupPage.ts | 8 +++++++- src/renderer/pages/historyPage.ts | 8 +++++++- src/renderer/pages/mainPage.ts | 21 +++++++++++++++++---- src/renderer/pages/settingsPage.ts | 24 ++---------------------- 6 files changed, 58 insertions(+), 30 deletions(-) diff --git a/src/renderer/i18n/index.ts b/src/renderer/i18n/index.ts index e22c621..a446a6f 100644 --- a/src/renderer/i18n/index.ts +++ b/src/renderer/i18n/index.ts @@ -14,6 +14,19 @@ const resources = { 'en-US': enUS, }; +type RefreshCallback = () => void; +const refreshCallbacks: Set = new Set(); + +export function registerRefreshCallback(callback: RefreshCallback): () => void { + refreshCallbacks.add(callback); + return () => refreshCallbacks.delete(callback); +} + +function triggerRefresh(): void { + updatePageTranslations(); + refreshCallbacks.forEach((cb) => cb()); +} + export function initI18n(lng: SupportedLanguage = 'zh-CN'): Promise> { return i18next.init({ lng, @@ -30,7 +43,9 @@ export function t(key: string, options?: Record): string { } export function changeLanguage(lng: SupportedLanguage): Promise> { - return i18next.changeLanguage(lng); + const result = i18next.changeLanguage(lng); + result.then(() => triggerRefresh()); + return result; } export function getCurrentLanguage(): SupportedLanguage { diff --git a/src/renderer/main.ts b/src/renderer/main.ts index b9d2179..7429682 100644 --- a/src/renderer/main.ts +++ b/src/renderer/main.ts @@ -8,7 +8,7 @@ import { loadBackups, createBackup, importBackup } from './pages/backupPage'; import { initSettings, requestAdminRestart, toggleSwitch, openLogDir } from './pages/settingsPage'; // i18n 和主题 -import { initI18n, t, updatePageTranslations } from './i18n'; +import { initI18n, t, updatePageTranslations, registerRefreshCallback } from './i18n'; import { initTheme, getThemeManager } from './utils/themeManager'; import { getSettingsStore } from './utils/settingsStore'; @@ -132,6 +132,11 @@ async function checkAdminStatus(): Promise { } } +function refreshMainContent(): void { + updateMaximizeBtn(); + checkAdminStatus(); +} + // ── 暴露给 HTML inline onclick ── Object.assign(window, { showUndo, @@ -167,6 +172,9 @@ document.addEventListener('DOMContentLoaded', async () => { // 应用页面翻译 updatePageTranslations(); + + // 注册语言切换刷新回调 + registerRefreshCallback(refreshMainContent); // 管理员检查 await checkAdminStatus(); diff --git a/src/renderer/pages/backupPage.ts b/src/renderer/pages/backupPage.ts index f47356d..3a4dc00 100644 --- a/src/renderer/pages/backupPage.ts +++ b/src/renderer/pages/backupPage.ts @@ -1,10 +1,16 @@ import '../api/bridge'; import type { BackupSnapshot } from '../../shared/types'; import { BackupType } from '../../shared/enums'; -import { t } from '../i18n'; +import { t, registerRefreshCallback } from '../i18n'; let backups: BackupSnapshot[] = []; +export function refreshBackupContent(): void { + renderBackup(); +} + +registerRefreshCallback(refreshBackupContent); + export async function loadBackups(): Promise { const result = await window.api.getBackups(); if (!result.success) { diff --git a/src/renderer/pages/historyPage.ts b/src/renderer/pages/historyPage.ts index 99b93d1..d2bd84d 100644 --- a/src/renderer/pages/historyPage.ts +++ b/src/renderer/pages/historyPage.ts @@ -1,7 +1,7 @@ import '../api/bridge'; import type { OperationRecord } from '../../shared/types'; import { OperationType } from '../../shared/enums'; -import { t } from '../i18n'; +import { t, registerRefreshCallback } from '../i18n'; function getOpLabel(type: OperationType): string { const opKeys: Record = { @@ -29,6 +29,12 @@ const OP_CSS_CLASS: Record = { let allRecords: OperationRecord[] = []; let filterType: string = 'all'; +export function refreshHistoryContent(): void { + renderHistory(); +} + +registerRefreshCallback(refreshHistoryContent); + export async function loadHistory(): Promise { const listEl = document.getElementById('historyList'); if (listEl) listEl.innerHTML = `
${t('main.loading')}
`; diff --git a/src/renderer/pages/mainPage.ts b/src/renderer/pages/mainPage.ts index 63e836a..b349523 100644 --- a/src/renderer/pages/mainPage.ts +++ b/src/renderer/pages/mainPage.ts @@ -1,7 +1,7 @@ import '../api/bridge'; import { MenuScene, MenuItemType } from '../../shared/enums'; import type { MenuItemEntry, ToggleItemParams } from '../../shared/types'; -import { t } from '../i18n'; +import { t, registerRefreshCallback } from '../i18n'; export const SCENE_REG_ROOTS: Record = { [MenuScene.Desktop]: 'HKEY_CLASSES_ROOT\\DesktopBackground\\Shell', @@ -24,16 +24,29 @@ export function getSceneName(scene: MenuScene): string { return t(sceneKeys[scene]); } -// ── 状态 ── let currentItems: MenuItemEntry[] = []; let selectedItemId: number | null = null; let filterMode: 'all' | 'enabled' | 'disabled' = 'all'; let loadingScene = false; +let currentScene: MenuScene = MenuScene.Desktop; + +export function refreshCurrentContent(): void { + renderItems(); + updateSceneHeader(currentScene); + updateStatusBar(currentScene); + if (selectedItemId !== null) { + showDetail(selectedItemId); + } else { + resetDetailPanel(); + } +} + +registerRefreshCallback(refreshCurrentContent); -// ── 加载场景数据 ── export async function loadScene(scene: MenuScene): Promise { if (loadingScene) return; loadingScene = true; + currentScene = scene; const listEl = document.getElementById('itemList'); if (listEl) listEl.innerHTML = `
${t('main.loading')}
`; @@ -186,7 +199,7 @@ export function showDetail(id: number): void { return `HKEY_CLASSES_ROOT\\${parent}${actual}`; } return `${SCENE_REG_ROOTS[item.menuScene]}\\${item.registryKey.split('\\').pop()}`; - })(); + })(); const regCmdPath = isShellExt ? `(COM DLL,CLSID: ${item.command})` : `${regItemPath}\\command`; diff --git a/src/renderer/pages/settingsPage.ts b/src/renderer/pages/settingsPage.ts index 57baee3..5e6ab78 100644 --- a/src/renderer/pages/settingsPage.ts +++ b/src/renderer/pages/settingsPage.ts @@ -1,21 +1,15 @@ import '../api/bridge'; -import { t, changeLanguage, getCurrentLanguage, type SupportedLanguage } from '../i18n'; +import { changeLanguage, getCurrentLanguage, type SupportedLanguage } from '../i18n'; import { getThemeManager, type ThemeMode } from '../utils/themeManager'; import { getSettingsStore } from '../utils/settingsStore'; -// 是否已经初始化的标记 let isSettingsInitialized = false; -// 初始化设置页面 export async function initSettings(): Promise { - // 显示管理员状态 await updateAdminStatus(); - - // 初始化外观设置(每次都初始化,确保事件监听器正确绑定) initAppearanceSettings(); } -// 更新管理员状态 async function updateAdminStatus(): Promise { const adminResult = await window.api.isAdmin(); const adminStatus = document.getElementById('adminStatus'); @@ -30,49 +24,35 @@ async function updateAdminStatus(): Promise { } } -// 初始化外观设置(主题和语言) function initAppearanceSettings(): void { - // 主题设置 const themeSelect = document.getElementById('themeSelect') as HTMLSelectElement | null; if (themeSelect) { const themeManager = getThemeManager(); - // 设置当前值 themeSelect.value = themeManager.getTheme(); - // 使用 onclick 而不是 addEventListener 来避免重复绑定 themeSelect.onchange = () => { const mode = themeSelect.value as ThemeMode; console.log('主题切换到:', mode); themeManager.setTheme(mode); getSettingsStore().setSetting('theme', mode); - // 立即应用主题,不等待外部通知 document.documentElement.setAttribute('data-theme', mode === 'dark' ? 'dark' : 'light'); }; } - // 语言设置 const languageSelect = document.getElementById('languageSelect') as HTMLSelectElement | null; if (languageSelect) { - // 设置当前值 languageSelect.value = getCurrentLanguage(); - // 使用 onclick 而不是 addEventListener 来避免重复绑定 languageSelect.onchange = () => { const lang = languageSelect.value as SupportedLanguage; console.log('语言切换到:', lang); - // 保存语言设置 getSettingsStore().setSetting('language', lang); - // 切换语言并刷新页面 changeLanguage(lang).then(() => { - console.log('语言切换成功,准备刷新页面'); - // 使用延迟确保设置已保存 - setTimeout(() => { - window.location.reload(); - }, 100); + console.log('语言切换成功'); }).catch((error) => { console.error('语言切换失败:', error); alert('语言切换失败,请重试'); From 60bb96995f3d80538a36cdc76ad7e38b208c85b2 Mon Sep 17 00:00:00 2001 From: tanzz Date: Sat, 14 Mar 2026 05:03:44 +0800 Subject: [PATCH 4/7] feat(cache): add caching mechanism for menu items with invalidation refactor(ipc): wrap all handlers with logging and error handling perf(menu): optimize badge preloading with Promise.all style(logging): add detailed logging throughout the application --- src/main/ipc/backup.ts | 30 ++++--- src/main/ipc/history.ts | 19 +++-- src/main/ipc/registry.ts | 15 +++- src/main/ipc/system.ts | 86 +++++++++++++------- src/main/services/BackupService.ts | 14 +++- src/main/services/MenuManagerService.ts | 47 ++++++++++- src/main/services/OperationHistoryService.ts | 13 +-- src/renderer/main.ts | 7 +- src/renderer/pages/mainPage.ts | 23 +++--- 9 files changed, 180 insertions(+), 74 deletions(-) diff --git a/src/main/ipc/backup.ts b/src/main/ipc/backup.ts index 9cadf5b..6863ee9 100644 --- a/src/main/ipc/backup.ts +++ b/src/main/ipc/backup.ts @@ -3,30 +3,37 @@ import { IPC } from '../../shared/ipc-channels'; import { BackupType } from '../../shared/enums'; import { BackupService } from '../services/BackupService'; import { wrapHandler } from '../utils/ipcWrapper'; +import log from '../utils/logger'; export function registerBackupHandlers(backup: BackupService): void { ipcMain.handle( IPC.BACKUP_GET_ALL, - wrapHandler(() => backup.getAllBackups()) + wrapHandler(() => { + log.debug('[Backup] Getting all backups'); + return backup.getAllBackups(); + }) ); ipcMain.handle( IPC.BACKUP_CREATE, - wrapHandler((_event: unknown, name: string) => - backup.createBackup(name, BackupType.Manual) - ) + wrapHandler((_event: unknown, name: string) => { + log.info(`[Backup] Creating backup: ${name}`); + return backup.createBackup(name, BackupType.Manual); + }) ); ipcMain.handle( IPC.BACKUP_RESTORE, - wrapHandler((_event: unknown, snapshotId: number) => - backup.restoreBackup(snapshotId) - ) + wrapHandler((_event: unknown, snapshotId: number) => { + log.info(`[Backup] Restoring backup: snapshotId=${snapshotId}`); + return backup.restoreBackup(snapshotId); + }) ); ipcMain.handle( IPC.BACKUP_DELETE, wrapHandler((_event: unknown, id: number) => { + log.warn(`[Backup] Deleting backup: id=${id}`); backup.deleteBackup(id); return true; }) @@ -35,6 +42,7 @@ export function registerBackupHandlers(backup: BackupService): void { ipcMain.handle( IPC.BACKUP_EXPORT, wrapHandler(async (event: Electron.IpcMainInvokeEvent, snapshotId: number) => { + log.info(`[Backup] Exporting backup: snapshotId=${snapshotId}`); const win = BrowserWindow.fromWebContents(event.sender)!; await backup.exportBackup(snapshotId, win); return true; @@ -44,6 +52,7 @@ export function registerBackupHandlers(backup: BackupService): void { ipcMain.handle( IPC.BACKUP_IMPORT, wrapHandler(async (event: Electron.IpcMainInvokeEvent) => { + log.info('[Backup] Importing backup'); const win = BrowserWindow.fromWebContents(event.sender)!; return backup.importBackup(win); }) @@ -51,8 +60,9 @@ export function registerBackupHandlers(backup: BackupService): void { ipcMain.handle( IPC.BACKUP_PREVIEW_DIFF, - wrapHandler((_event: unknown, snapshotId: number) => - backup.previewRestoreDiff(snapshotId) - ) + wrapHandler((_event: unknown, snapshotId: number) => { + log.debug(`[Backup] Previewing restore diff: snapshotId=${snapshotId}`); + return backup.previewRestoreDiff(snapshotId); + }) ); } diff --git a/src/main/ipc/history.ts b/src/main/ipc/history.ts index ce04c70..e1885ed 100644 --- a/src/main/ipc/history.ts +++ b/src/main/ipc/history.ts @@ -3,6 +3,7 @@ import { IPC } from '../../shared/ipc-channels'; import { OperationHistoryService } from '../services/OperationHistoryService'; import { MenuManagerService } from '../services/MenuManagerService'; import { wrapHandler } from '../utils/ipcWrapper'; +import log from '../utils/logger'; export function registerHistoryHandlers( history: OperationHistoryService, @@ -10,18 +11,26 @@ export function registerHistoryHandlers( ): void { ipcMain.handle( IPC.HISTORY_GET_ALL, - wrapHandler(() => history.getAllRecords()) + wrapHandler(() => { + log.debug('[History] Getting all records'); + return history.getAllRecords(); + }) ); ipcMain.handle( IPC.HISTORY_UNDO, - wrapHandler((_event: unknown, recordId: number) => - history.undoOperation(recordId, menuManager) - ) + wrapHandler((_event: unknown, recordId: number) => { + log.info(`[History] Undo operation requested: recordId=${recordId}`); + return history.undoOperation(recordId, menuManager); + }) ); ipcMain.handle( IPC.HISTORY_CLEAR, - wrapHandler(() => { history.clearAll(); return true; }) + wrapHandler(() => { + log.warn('[History] Clear all records requested'); + history.clearAll(); + return true; + }) ); } diff --git a/src/main/ipc/registry.ts b/src/main/ipc/registry.ts index aa207e4..0f655cb 100644 --- a/src/main/ipc/registry.ts +++ b/src/main/ipc/registry.ts @@ -4,18 +4,21 @@ import { MenuScene, MenuItemType } from '../../shared/enums'; import { ToggleItemParams, BatchToggleParams } from '../../shared/types'; import { MenuManagerService } from '../services/MenuManagerService'; import { wrapHandler } from '../utils/ipcWrapper'; +import log from '../utils/logger'; export function registerRegistryHandlers(menuManager: MenuManagerService): void { ipcMain.handle( IPC.REGISTRY_GET_ITEMS, - wrapHandler((_event: unknown, scene: MenuScene) => - menuManager.getMenuItems(scene) - ) + wrapHandler((_event: unknown, scene: MenuScene) => { + log.debug(`[Registry] Getting items for scene: ${scene}`); + return menuManager.getMenuItems(scene); + }) ); ipcMain.handle( IPC.REGISTRY_TOGGLE, wrapHandler(async (_event: unknown, params: ToggleItemParams) => { + log.info(`[Registry] Toggle item: ${params.name} (${params.isEnabled ? 'enabled' : 'disabled'} -> ${params.isEnabled ? 'disabled' : 'enabled'})`); const item = { id: -1, name: params.name, @@ -28,6 +31,8 @@ export function registerRegistryHandlers(menuManager: MenuManagerService): void type: params.type ?? MenuItemType.System, }; const result = await menuManager.toggleItem(item); + menuManager.invalidateCache(params.menuScene); + log.info(`[Registry] Toggle completed: ${params.name} -> ${!params.isEnabled}`); return { newState: !params.isEnabled, newRegistryKey: result.newRegistryKey }; }) ); @@ -35,11 +40,15 @@ export function registerRegistryHandlers(menuManager: MenuManagerService): void ipcMain.handle( IPC.REGISTRY_BATCH, wrapHandler(async (_event: unknown, params: BatchToggleParams) => { + log.info(`[Registry] Batch ${params.enable ? 'enable' : 'disable'}: ${params.items.length} items`); + const start = Date.now(); if (params.enable) { await menuManager.batchEnable(params.items); } else { await menuManager.batchDisable(params.items); } + const elapsed = Date.now() - start; + log.info(`[Registry] Batch operation completed in ${elapsed}ms`); return true; }) ); diff --git a/src/main/ipc/system.ts b/src/main/ipc/system.ts index 8f81bcd..45175c0 100644 --- a/src/main/ipc/system.ts +++ b/src/main/ipc/system.ts @@ -9,19 +9,28 @@ import log, { getLogDir } from '../utils/logger'; const execFileAsync = promisify(execFile); export function registerSystemHandlers(): void { - ipcMain.handle(IPC.SYS_IS_ADMIN, wrapHandler(() => isAdmin())); + ipcMain.handle( + IPC.SYS_IS_ADMIN, + wrapHandler(() => { + log.debug('[System] Checking admin status'); + return isAdmin(); + }) + ); - ipcMain.handle(IPC.SYS_RESTART_AS_ADMIN, wrapHandler(() => { - restartAsAdmin(); - return true; - })); + ipcMain.handle( + IPC.SYS_RESTART_AS_ADMIN, + wrapHandler(() => { + log.info('[System] Restarting as admin'); + restartAsAdmin(); + return true; + }) + ); ipcMain.handle( IPC.SYS_OPEN_REGEDIT, wrapHandler(async (_event: unknown, fullRegPath: string) => { log.info('[Regedit] 收到路径:', fullRegPath); - // regedit 根节点名称随系统 UI 语言本地化,用 app.getLocale() 直接检测 const locale = app.getLocale(); const isChinese = locale.startsWith('zh'); const computerPrefix = isChinese ? '计算机' : 'Computer'; @@ -30,7 +39,6 @@ export function registerSystemHandlers(): void { const normalizedPath = `${computerPrefix}\\${fullRegPath}`; log.info('[Regedit] 规范化路径:', normalizedPath); - // 用 reg.exe 写入 LastKey try { const { stdout, stderr } = await execFileAsync('reg.exe', [ 'add', @@ -43,7 +51,6 @@ export function registerSystemHandlers(): void { throw e; } - // 关闭已有 regedit(单实例,必须重启才会读取新的 LastKey) try { const { stdout } = await execFileAsync('taskkill.exe', ['/F', '/IM', 'regedit.exe']); log.info('[Regedit] taskkill 成功:', stdout.trim()); @@ -52,8 +59,6 @@ export function registerSystemHandlers(): void { log.info('[Regedit] taskkill 失败(regedit 未运行,正常)'); } - // 通过 cmd /c start 启动 regedit - // 直接 execFile('regedit.exe') 在管理员上下文会报 EACCES,需借道 cmd log.info('[Regedit] 启动 regedit.exe (via cmd)'); const child = spawn('cmd.exe', ['/c', 'start', '', 'regedit.exe'], { detached: true, stdio: 'ignore' }); child.on('error', (err) => log.error('[Regedit] regedit.exe 启动失败:', err)); @@ -78,6 +83,7 @@ export function registerSystemHandlers(): void { ipcMain.handle( IPC.SYS_COPY_CLIPBOARD, wrapHandler((_event: unknown, text: string) => { + log.debug(`[System] Copying to clipboard: ${text.substring(0, 50)}...`); clipboard.writeText(text); return true; }) @@ -85,27 +91,45 @@ export function registerSystemHandlers(): void { ipcMain.handle( IPC.SYS_OPEN_EXTERNAL, - wrapHandler((_event: unknown, url: string) => shell.openExternal(url)) + wrapHandler((_event: unknown, url: string) => { + log.info(`[System] Opening external URL: ${url}`); + return shell.openExternal(url); + }) + ); + + ipcMain.handle( + IPC.WIN_MINIMIZE, + wrapHandler(() => { + const win = BrowserWindow.getFocusedWindow(); + win?.minimize(); + }) ); - // 窗口控制 - ipcMain.handle(IPC.WIN_MINIMIZE, (_event) => { - const win = BrowserWindow.getFocusedWindow(); - win?.minimize(); - }); - - ipcMain.handle(IPC.WIN_MAXIMIZE, (_event) => { - const win = BrowserWindow.getFocusedWindow(); - if (!win) return; - if (win.isMaximized()) { win.unmaximize(); } else { win.maximize(); } - }); - - ipcMain.handle(IPC.WIN_CLOSE, (_event) => { - const win = BrowserWindow.getFocusedWindow(); - win?.close(); - }); - - ipcMain.handle(IPC.WIN_IS_MAXIMIZED, (_event) => { - return BrowserWindow.getFocusedWindow()?.isMaximized() ?? false; - }); + ipcMain.handle( + IPC.WIN_MAXIMIZE, + wrapHandler(() => { + const win = BrowserWindow.getFocusedWindow(); + if (!win) return; + if (win.isMaximized()) { + win.unmaximize(); + } else { + win.maximize(); + } + }) + ); + + ipcMain.handle( + IPC.WIN_CLOSE, + wrapHandler(() => { + const win = BrowserWindow.getFocusedWindow(); + win?.close(); + }) + ); + + ipcMain.handle( + IPC.WIN_IS_MAXIMIZED, + wrapHandler(() => { + return BrowserWindow.getFocusedWindow()?.isMaximized() ?? false; + }) + ); } diff --git a/src/main/services/BackupService.ts b/src/main/services/BackupService.ts index 9672fdd..9e1425b 100644 --- a/src/main/services/BackupService.ts +++ b/src/main/services/BackupService.ts @@ -24,6 +24,7 @@ export class BackupService { ) {} async createBackup(name: string, type = BackupType.Manual): Promise { + const start = Date.now(); const allItems: MenuItemEntry[] = []; for (const scene of Object.values(MenuScene)) { const items = await this.menuManager.getMenuItems(scene); @@ -42,11 +43,13 @@ export class BackupService { }); this.history.recordOperation(OperationType.Backup, name, '', '', checksum); - log.info(`Backup created: ${name} (${allItems.length} items)`); + const elapsed = Date.now() - start; + log.info(`[Backup] Backup created: ${name} (${allItems.length} items) in ${elapsed}ms`); return snapshot; } async restoreBackup(snapshotId: number): Promise { + const start = Date.now(); const snapshot = this.repo.findById(snapshotId); if (!snapshot) throw new Error('找不到备份快照'); @@ -57,7 +60,6 @@ export class BackupService { throw new Error('备份校验失败,文件可能已被篡改'); } - // 还原前先自动创建备份 await this.createBackup( `AutoBackup_BeforeRestore_${new Date().toISOString().replace(/[:.]/g, '-')}`, BackupType.Auto @@ -87,18 +89,23 @@ export class BackupService { if (toDisable.length) await this.menuManager.batchDisable(toDisable); this.history.recordOperation(OperationType.Restore, snapshot.name, '', '', snapshotId.toString()); - log.info(`Restore completed from backup: ${snapshot.name}`); + const elapsed = Date.now() - start; + log.info(`[Backup] Restore completed from backup: ${snapshot.name} in ${elapsed}ms`); } async deleteBackup(id: number): Promise { + const snapshot = this.repo.findById(id); this.repo.delete(id); + log.warn(`[Backup] Deleted backup: id=${id}, name=${snapshot?.name ?? 'unknown'}`); } getAllBackups(): BackupSnapshot[] { + log.debug('[Backup] Getting all backups'); return this.repo.findAll(); } async previewRestoreDiff(snapshotId: number): Promise { + log.debug(`[Backup] Previewing restore diff: snapshotId=${snapshotId}`); const snapshot = this.repo.findById(snapshotId); if (!snapshot) throw new Error('找不到备份快照'); @@ -115,6 +122,7 @@ export class BackupService { diff.push({ current, backup: backupItem }); } } + log.debug(`[Backup] Preview diff result: ${diff.length} items changed`); return diff; } diff --git a/src/main/services/MenuManagerService.ts b/src/main/services/MenuManagerService.ts index 0a510cd..43d664b 100644 --- a/src/main/services/MenuManagerService.ts +++ b/src/main/services/MenuManagerService.ts @@ -4,14 +4,51 @@ import { RegistryService } from './RegistryService'; import { OperationHistoryService } from './OperationHistoryService'; import log from '../utils/logger'; +interface CacheEntry { + items: MenuItemEntry[]; + timestamp: number; +} + +const CACHE_TTL = 5 * 60 * 1000; + export class MenuManagerService { + private cache = new Map(); + constructor( private readonly registry: RegistryService, private readonly history: OperationHistoryService ) {} - async getMenuItems(scene: MenuScene): Promise { - return this.registry.getMenuItems(scene); + async getMenuItems(scene: MenuScene, forceRefresh = false): Promise { + if (!forceRefresh) { + const cached = this.cache.get(scene); + if (cached && Date.now() - cached.timestamp < CACHE_TTL) { + log.debug(`[MenuManager] Cache hit for scene: ${scene}`); + return cached.items; + } + } + + log.debug(`[MenuManager] Loading items for scene: ${scene} (forceRefresh: ${forceRefresh})`); + const start = Date.now(); + const items = await this.registry.getMenuItems(scene); + const elapsed = Date.now() - start; + + if (elapsed > 100) { + log.info(`[MenuManager] Loaded ${items.length} items for ${scene} in ${elapsed}ms`); + } + + this.cache.set(scene, { items, timestamp: Date.now() }); + return items; + } + + invalidateCache(scene?: MenuScene): void { + if (scene) { + this.cache.delete(scene); + log.debug(`[MenuManager] Cache invalidated for scene: ${scene}`); + } else { + this.cache.clear(); + log.debug('[MenuManager] All cache invalidated'); + } } async enableItem(item: MenuItemEntry): Promise<{ newRegistryKey?: string }> { @@ -26,7 +63,7 @@ export class MenuManagerService { 'false', 'true' ); - log.info(`Enabled: ${item.name}`); + log.info(`[MenuManager] Enabled: ${item.name}`); return result; } @@ -42,7 +79,7 @@ export class MenuManagerService { 'true', 'false' ); - log.info(`Disabled: ${item.name}`); + log.info(`[MenuManager] Disabled: ${item.name}`); return result; } @@ -64,6 +101,7 @@ export class MenuManagerService { await this.enableItem(item); } this.registry.commitTransaction(); + this.cache.clear(); } catch (e) { await this.registry.rollback(); throw new Error(`批量启用失败,已回滚: ${(e as Error).message}`); @@ -80,6 +118,7 @@ export class MenuManagerService { await this.disableItem(item); } this.registry.commitTransaction(); + this.cache.clear(); } catch (e) { await this.registry.rollback(); throw new Error(`批量禁用失败,已回滚: ${(e as Error).message}`); diff --git a/src/main/services/OperationHistoryService.ts b/src/main/services/OperationHistoryService.ts index 811d6f9..f5e93ce 100644 --- a/src/main/services/OperationHistoryService.ts +++ b/src/main/services/OperationHistoryService.ts @@ -2,6 +2,7 @@ import { OperationType, MenuScene, MenuItemType } from '../../shared/enums'; import { OperationRecord, MenuItemEntry } from '../../shared/types'; import { OperationRecordRepo } from '../data/repositories/OperationRecordRepo'; import { MenuManagerService } from './MenuManagerService'; +import log from '../utils/logger'; export class OperationHistoryService { constructor(private readonly repo: OperationRecordRepo) {} @@ -24,17 +25,15 @@ export class OperationHistoryService { } getAllRecords(): OperationRecord[] { + log.debug('[History] Getting all records'); return this.repo.findAll(); } clearAll(): void { + log.warn('[History] Clearing all operation records'); this.repo.deleteAll(); } - /** - * 单条撤销:根据操作类型执行反向操作 - * 仅支持 Enable / Disable - */ async undoOperation( recordId: number, menuManager: MenuManagerService @@ -50,6 +49,8 @@ export class OperationHistoryService { } const wasEnabled = record.operationType === OperationType.Enable; + log.info(`[History] Undo operation: recordId=${recordId}, type=${record.operationType}, target=${record.targetEntryName}, reverting to ${wasEnabled ? 'disabled' : 'enabled'}`); + const tempItem: MenuItemEntry = { id: -1, name: record.targetEntryName, @@ -67,10 +68,12 @@ export class OperationHistoryService { } else { await menuManager.enableItem(tempItem); } + + menuManager.invalidateCache(tempItem.menuScene); + log.info(`[History] Undo completed: ${record.targetEntryName} -> ${wasEnabled ? 'disabled' : 'enabled'}`); } } -/** 与 C# DetermineSceneFromRegistryKey 逻辑一致 */ function determineSceneFromRegistryKey(registryKey: string): MenuScene { if (registryKey.includes('DesktopBackground')) return MenuScene.Desktop; if (registryKey.includes('*\\')) return MenuScene.File; diff --git a/src/renderer/main.ts b/src/renderer/main.ts index 7429682..304b34a 100644 --- a/src/renderer/main.ts +++ b/src/renderer/main.ts @@ -85,8 +85,6 @@ let currentPage: PageId = 'main'; let currentScene: MenuScene = MenuScene.Desktop; async function switchPage(page: PageId, navEl?: HTMLElement, scene?: MenuScene): Promise { - // 切换场景时清空搜索状态 - allScenesCache = null; const searchEl = document.getElementById('globalSearch') as HTMLInputElement | null; if (searchEl) searchEl.value = ''; @@ -137,6 +135,10 @@ function refreshMainContent(): void { checkAdminStatus(); } +function invalidateAllScenesCache(): void { + allScenesCache = null; +} + // ── 暴露给 HTML inline onclick ── Object.assign(window, { showUndo, @@ -144,6 +146,7 @@ Object.assign(window, { doUndo, switchPage, updateMaximizeBtn, + invalidateAllScenesCache, // History filterHistory: (mode: string, btn: HTMLElement) => filterHistory(mode, btn), clearHistory: clearAllHistory, diff --git a/src/renderer/pages/mainPage.ts b/src/renderer/pages/mainPage.ts index b349523..5f03b48 100644 --- a/src/renderer/pages/mainPage.ts +++ b/src/renderer/pages/mainPage.ts @@ -175,8 +175,9 @@ export async function toggleItem(id: number): Promise { } renderItems(); const action = item.isEnabled ? t('history.operation.enable') : t('history.operation.disable'); - (window as Window & { showUndo?: (msg: string, itemId: number) => void }) + (window as Window & { showUndo?: (msg: string, itemId: number) => void; invalidateAllScenesCache?: () => void }) .showUndo?.(`${t('main.actionDone')}${action}「${item.name}」`, id); + (window as Window & { invalidateAllScenesCache?: () => void }).invalidateAllScenesCache?.(); updateStatusBarFromCurrent(); if (selectedItemId === id) showDetail(id); @@ -424,17 +425,17 @@ export function restoreSceneTitle(scene: MenuScene): void { // ── 预加载其余场景的 badge 数量 ── export async function preloadBadgeCounts(skipScene: MenuScene): Promise { const allScenes = Object.values(MenuScene) as MenuScene[]; - for (const scene of allScenes) { - if (scene === skipScene) continue; - const result = await window.api.getMenuItems(scene); - if (result.success) { + const scenesToLoad = allScenes.filter((s) => s !== skipScene); + + await Promise.all( + scenesToLoad.map(async (scene) => { + const result = await window.api.getMenuItems(scene); const badgeEl = document.getElementById(`badge-${scene}`); - if (badgeEl) badgeEl.textContent = String(result.data.length); - } else { - const badgeEl = document.getElementById(`badge-${scene}`); - if (badgeEl) badgeEl.textContent = '?'; - } - } + if (badgeEl) { + badgeEl.textContent = result.success ? String(result.data.length) : '?'; + } + }) + ); } // 挂载到 window 供 HTML inline onclick 调用 From 3495f5a797e12ed44a56db4371bb3008356be293 Mon Sep 17 00:00:00 2001 From: tanzz Date: Sat, 14 Mar 2026 05:09:29 +0800 Subject: [PATCH 5/7] feat(i18n): add descriptions for theme and language settings Add description text for theme and language settings in both zh-CN and en-US locales. Move hardcoded strings to translation files and implement refresh callback for admin status text. --- src/renderer/i18n/en-US.json | 4 +++- src/renderer/i18n/zh-CN.json | 4 +++- src/renderer/index.html | 4 ++-- src/renderer/pages/settingsPage.ts | 12 +++++------- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/renderer/i18n/en-US.json b/src/renderer/i18n/en-US.json index 87f2f99..fac379b 100644 --- a/src/renderer/i18n/en-US.json +++ b/src/renderer/i18n/en-US.json @@ -127,10 +127,12 @@ "appearance": { "title": "Appearance", "theme": "Theme", + "themeDesc": "Select application theme appearance", "themeSystem": "Follow System", "themeLight": "Light", "themeDark": "Dark", - "language": "Language" + "language": "Language", + "languageDesc": "Select interface language" }, "permission": { "title": "Permission", diff --git a/src/renderer/i18n/zh-CN.json b/src/renderer/i18n/zh-CN.json index 7999041..976538a 100644 --- a/src/renderer/i18n/zh-CN.json +++ b/src/renderer/i18n/zh-CN.json @@ -127,10 +127,12 @@ "appearance": { "title": "外观", "theme": "主题", + "themeDesc": "选择应用主题外观", "themeSystem": "跟随系统", "themeLight": "浅色", "themeDark": "深色", - "language": "语言" + "language": "语言", + "languageDesc": "选择界面语言" }, "permission": { "title": "权限", diff --git a/src/renderer/index.html b/src/renderer/index.html index 6353a0d..9b2bd4f 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -382,7 +382,7 @@
主题
-
选择应用主题外观
+
选择应用主题外观
diff --git a/src/renderer/pages/settingsPage.ts b/src/renderer/pages/settingsPage.ts index 5e6ab78..5de202f 100644 --- a/src/renderer/pages/settingsPage.ts +++ b/src/renderer/pages/settingsPage.ts @@ -1,5 +1,5 @@ import '../api/bridge'; -import { changeLanguage, getCurrentLanguage, type SupportedLanguage } from '../i18n'; +import { t, changeLanguage, getCurrentLanguage, registerRefreshCallback, type SupportedLanguage } from '../i18n'; import { getThemeManager, type ThemeMode } from '../utils/themeManager'; import { getSettingsStore } from '../utils/settingsStore'; @@ -15,15 +15,17 @@ async function updateAdminStatus(): Promise { const adminStatus = document.getElementById('adminStatus'); if (adminStatus) { if (adminResult.success && adminResult.data) { - adminStatus.textContent = '已获取管理员权限'; + adminStatus.textContent = t('settings.permission.adminGranted'); adminStatus.style.color = 'var(--success)'; } else { - adminStatus.textContent = '未以管理员身份运行'; + adminStatus.textContent = t('settings.permission.adminNotGranted'); adminStatus.style.color = 'var(--danger)'; } } } +registerRefreshCallback(updateAdminStatus); + function initAppearanceSettings(): void { const themeSelect = document.getElementById('themeSelect') as HTMLSelectElement | null; if (themeSelect) { @@ -61,25 +63,21 @@ function initAppearanceSettings(): void { } } -// 以管理员身份重启 export async function requestAdminRestart(): Promise { if (!confirm('确定要以管理员身份重启应用吗?')) return; await window.api.restartAsAdmin(); } -// 切换开关状态 export function toggleSwitch(btn: HTMLElement): void { btn.classList.toggle('on'); btn.classList.toggle('off'); } -// 打开日志目录 export async function openLogDir(): Promise { const result = await window.api.openLogDir(); if (!result.success) alert(`打开日志目录失败: ${result.error}`); } -// 导出 API 供 HTML 调用 const settingsPageApi = { requestAdminRestart, toggleSwitch, From 6e00fdaadc967f5ed68c04fab5a6d64e8bfaca5f Mon Sep 17 00:00:00 2001 From: tanzz Date: Sat, 14 Mar 2026 05:19:02 +0800 Subject: [PATCH 6/7] feat(logging): add file logging capability for renderer process - Implement new IPC channel for logging to file from renderer - Add debug utility with log level support (info, warn, error) - Replace console calls with new debug utility in settings page - Update tsconfig for ESM module system compatibility --- src/main/ipc/system.ts | 17 +++++++++++++++++ src/preload/index.ts | 3 +++ src/renderer/api/bridge.ts | 1 + src/renderer/pages/settingsPage.ts | 9 +++++---- src/renderer/utils/debug.ts | 20 ++++++++++++++++++++ src/shared/ipc-channels.ts | 1 + tsconfig.json | 7 ++++--- 7 files changed, 51 insertions(+), 7 deletions(-) create mode 100644 src/renderer/utils/debug.ts diff --git a/src/main/ipc/system.ts b/src/main/ipc/system.ts index 45175c0..4b596a4 100644 --- a/src/main/ipc/system.ts +++ b/src/main/ipc/system.ts @@ -97,6 +97,23 @@ export function registerSystemHandlers(): void { }) ); + ipcMain.handle( + IPC.SYS_LOG_TO_FILE, + wrapHandler((_event: unknown, level: 'info' | 'warn' | 'error', message: string) => { + const prefix = '[Renderer]'; + switch (level) { + case 'error': + log.error(prefix, message); + break; + case 'warn': + log.warn(prefix, message); + break; + default: + log.info(prefix, message); + } + }) + ); + ipcMain.handle( IPC.WIN_MINIMIZE, wrapHandler(() => { diff --git a/src/preload/index.ts b/src/preload/index.ts index 1f09661..e263d4f 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -78,6 +78,9 @@ const api = { openExternal: (url: string) => invoke(IPC.SYS_OPEN_EXTERNAL, url), + logToFile: (level: 'info' | 'warn' | 'error', message: string) => + ipcRenderer.invoke(IPC.SYS_LOG_TO_FILE, level, message), + // ── Window ── minimizeWindow: () => ipcRenderer.invoke(IPC.WIN_MINIMIZE), maximizeWindow: () => ipcRenderer.invoke(IPC.WIN_MAXIMIZE), diff --git a/src/renderer/api/bridge.ts b/src/renderer/api/bridge.ts index 28ba9c0..d9407aa 100644 --- a/src/renderer/api/bridge.ts +++ b/src/renderer/api/bridge.ts @@ -34,6 +34,7 @@ export interface WindowApi { openRegedit(fullRegPath: string): Promise>; copyToClipboard(text: string): Promise>; openLogDir(): Promise>; + logToFile(level: 'info' | 'warn' | 'error', message: string): Promise; minimizeWindow(): Promise; maximizeWindow(): Promise; diff --git a/src/renderer/pages/settingsPage.ts b/src/renderer/pages/settingsPage.ts index 5de202f..a16603f 100644 --- a/src/renderer/pages/settingsPage.ts +++ b/src/renderer/pages/settingsPage.ts @@ -2,6 +2,7 @@ import '../api/bridge'; import { t, changeLanguage, getCurrentLanguage, registerRefreshCallback, type SupportedLanguage } from '../i18n'; import { getThemeManager, type ThemeMode } from '../utils/themeManager'; import { getSettingsStore } from '../utils/settingsStore'; +import { debug } from '../utils/debug'; let isSettingsInitialized = false; @@ -35,7 +36,7 @@ function initAppearanceSettings(): void { themeSelect.onchange = () => { const mode = themeSelect.value as ThemeMode; - console.log('主题切换到:', mode); + debug.log('主题切换到:', mode); themeManager.setTheme(mode); getSettingsStore().setSetting('theme', mode); @@ -49,14 +50,14 @@ function initAppearanceSettings(): void { languageSelect.onchange = () => { const lang = languageSelect.value as SupportedLanguage; - console.log('语言切换到:', lang); + debug.log('语言切换到:', lang); getSettingsStore().setSetting('language', lang); changeLanguage(lang).then(() => { - console.log('语言切换成功'); + debug.log('语言切换成功'); }).catch((error) => { - console.error('语言切换失败:', error); + debug.error('语言切换失败:', error); alert('语言切换失败,请重试'); }); }; diff --git a/src/renderer/utils/debug.ts b/src/renderer/utils/debug.ts new file mode 100644 index 0000000..aa6ad7f --- /dev/null +++ b/src/renderer/utils/debug.ts @@ -0,0 +1,20 @@ +const isDebug = import.meta.env.DEV || import.meta.env.VITE_DEBUG === 'true'; + +export const debug = { + log: (...args: unknown[]): void => { + if (isDebug) console.log(...args); + window.api.logToFile('info', args.map((a) => String(a)).join(' ')); + }, + warn: (...args: unknown[]): void => { + if (isDebug) console.warn(...args); + window.api.logToFile('warn', args.map((a) => String(a)).join(' ')); + }, + error: (...args: unknown[]): void => { + if (isDebug) console.error(...args); + window.api.logToFile('error', args.map((a) => String(a)).join(' ')); + }, + info: (...args: unknown[]): void => { + if (isDebug) console.info(...args); + window.api.logToFile('info', args.map((a) => String(a)).join(' ')); + }, +}; diff --git a/src/shared/ipc-channels.ts b/src/shared/ipc-channels.ts index 5e2c6c1..ff686a6 100644 --- a/src/shared/ipc-channels.ts +++ b/src/shared/ipc-channels.ts @@ -26,6 +26,7 @@ export const IPC = { SYS_OPEN_LOG_DIR: 'sys:openLogDir', SYS_COPY_CLIPBOARD: 'sys:copyClipboard', SYS_OPEN_EXTERNAL: 'sys:openExternal', + SYS_LOG_TO_FILE: 'sys:logToFile', WIN_MINIMIZE: 'win:minimize', WIN_MAXIMIZE: 'win:maximize', WIN_CLOSE: 'win:close', diff --git a/tsconfig.json b/tsconfig.json index 86ac21f..586b8e0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,8 +1,8 @@ { "compilerOptions": { "target": "ES2022", - "module": "CommonJS", - "moduleResolution": "node", + "module": "ESNext", + "moduleResolution": "bundler", "strict": true, "esModuleInterop": true, "skipLibCheck": true, @@ -17,7 +17,8 @@ }, "resolveJsonModule": true, "declaration": false, - "sourceMap": true + "sourceMap": true, + "types": ["vite/client"] }, "include": ["src/**/*"], "exclude": ["node_modules", ".vite", "dist", "tests"] From 14f986b2cf1ed841c5a0c5ea46b87a47d87d7b3b Mon Sep 17 00:00:00 2001 From: tanzz Date: Sat, 14 Mar 2026 05:29:29 +0800 Subject: [PATCH 7/7] chore: remove coverage files and update gitignore - Delete coverage directory and its contents - Add coverage directory to gitignore - Update test files with debug logger and invalidateCache mock --- .gitignore | 3 + coverage/base.css | 224 --- coverage/block-navigation.js | 87 -- coverage/coverage-final.json | 13 - coverage/favicon.png | Bin 445 -> 0 bytes coverage/index.html | 176 --- .../repositories/BackupSnapshotRepo.ts.html | 268 ---- .../repositories/OperationRecordRepo.ts.html | 277 ---- coverage/main/data/repositories/index.html | 131 -- coverage/main/ipc/index.html | 116 -- coverage/main/ipc/registry.ts.html | 223 --- coverage/main/services/BackupService.ts.html | 571 -------- .../main/services/MenuManagerService.ts.html | 349 ----- .../main/services/PowerShellBridge.ts.html | 1204 ----------------- .../main/services/RegistryService.ts.html | 628 --------- coverage/main/services/index.html | 161 --- coverage/main/utils/AdminHelper.ts.html | 244 ---- coverage/main/utils/index.html | 146 -- coverage/main/utils/ipcWrapper.ts.html | 154 --- coverage/main/utils/logger.ts.html | 136 -- coverage/prettify.css | 1 - coverage/prettify.js | 2 - coverage/shared/enums.ts.html | 172 --- coverage/shared/index.html | 131 -- coverage/shared/ipc-channels.ts.html | 184 --- coverage/sort-arrow-sprite.png | Bin 138 -> 0 bytes coverage/sorter.js | 210 --- tests/unit/main/ipc/registry.test.ts | 1 + .../main/services/MenuManagerService.test.ts | 1 + 29 files changed, 5 insertions(+), 5808 deletions(-) delete mode 100644 coverage/base.css delete mode 100644 coverage/block-navigation.js delete mode 100644 coverage/coverage-final.json delete mode 100644 coverage/favicon.png delete mode 100644 coverage/index.html delete mode 100644 coverage/main/data/repositories/BackupSnapshotRepo.ts.html delete mode 100644 coverage/main/data/repositories/OperationRecordRepo.ts.html delete mode 100644 coverage/main/data/repositories/index.html delete mode 100644 coverage/main/ipc/index.html delete mode 100644 coverage/main/ipc/registry.ts.html delete mode 100644 coverage/main/services/BackupService.ts.html delete mode 100644 coverage/main/services/MenuManagerService.ts.html delete mode 100644 coverage/main/services/PowerShellBridge.ts.html delete mode 100644 coverage/main/services/RegistryService.ts.html delete mode 100644 coverage/main/services/index.html delete mode 100644 coverage/main/utils/AdminHelper.ts.html delete mode 100644 coverage/main/utils/index.html delete mode 100644 coverage/main/utils/ipcWrapper.ts.html delete mode 100644 coverage/main/utils/logger.ts.html delete mode 100644 coverage/prettify.css delete mode 100644 coverage/prettify.js delete mode 100644 coverage/shared/enums.ts.html delete mode 100644 coverage/shared/index.html delete mode 100644 coverage/shared/ipc-channels.ts.html delete mode 100644 coverage/sort-arrow-sprite.png delete mode 100644 coverage/sorter.js diff --git a/.gitignore b/.gitignore index 349daa2..3aadeca 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ node_modules/ .vite/ out/ dist/ +coverage/ # 数据库 *.db @@ -36,3 +37,5 @@ Desktop.ini # 临时文件 *.tmp *.temp + +.trae/ \ No newline at end of file diff --git a/coverage/base.css b/coverage/base.css deleted file mode 100644 index f418035..0000000 --- a/coverage/base.css +++ /dev/null @@ -1,224 +0,0 @@ -body, html { - margin:0; padding: 0; - height: 100%; -} -body { - font-family: Helvetica Neue, Helvetica, Arial; - font-size: 14px; - color:#333; -} -.small { font-size: 12px; } -*, *:after, *:before { - -webkit-box-sizing:border-box; - -moz-box-sizing:border-box; - box-sizing:border-box; - } -h1 { font-size: 20px; margin: 0;} -h2 { font-size: 14px; } -pre { - font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; - margin: 0; - padding: 0; - -moz-tab-size: 2; - -o-tab-size: 2; - tab-size: 2; -} -a { color:#0074D9; text-decoration:none; } -a:hover { text-decoration:underline; } -.strong { font-weight: bold; } -.space-top1 { padding: 10px 0 0 0; } -.pad2y { padding: 20px 0; } -.pad1y { padding: 10px 0; } -.pad2x { padding: 0 20px; } -.pad2 { padding: 20px; } -.pad1 { padding: 10px; } -.space-left2 { padding-left:55px; } -.space-right2 { padding-right:20px; } -.center { text-align:center; } -.clearfix { display:block; } -.clearfix:after { - content:''; - display:block; - height:0; - clear:both; - visibility:hidden; - } -.fl { float: left; } -@media only screen and (max-width:640px) { - .col3 { width:100%; max-width:100%; } - .hide-mobile { display:none!important; } -} - -.quiet { - color: #7f7f7f; - color: rgba(0,0,0,0.5); -} -.quiet a { opacity: 0.7; } - -.fraction { - font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; - font-size: 10px; - color: #555; - background: #E8E8E8; - padding: 4px 5px; - border-radius: 3px; - vertical-align: middle; -} - -div.path a:link, div.path a:visited { color: #333; } -table.coverage { - border-collapse: collapse; - margin: 10px 0 0 0; - padding: 0; -} - -table.coverage td { - margin: 0; - padding: 0; - vertical-align: top; -} -table.coverage td.line-count { - text-align: right; - padding: 0 5px 0 20px; -} -table.coverage td.line-coverage { - text-align: right; - padding-right: 10px; - min-width:20px; -} - -table.coverage td span.cline-any { - display: inline-block; - padding: 0 5px; - width: 100%; -} -.missing-if-branch { - display: inline-block; - margin-right: 5px; - border-radius: 3px; - position: relative; - padding: 0 4px; - background: #333; - color: yellow; -} - -.skip-if-branch { - display: none; - margin-right: 10px; - position: relative; - padding: 0 4px; - background: #ccc; - color: white; -} -.missing-if-branch .typ, .skip-if-branch .typ { - color: inherit !important; -} -.coverage-summary { - border-collapse: collapse; - width: 100%; -} -.coverage-summary tr { border-bottom: 1px solid #bbb; } -.keyline-all { border: 1px solid #ddd; } -.coverage-summary td, .coverage-summary th { padding: 10px; } -.coverage-summary tbody { border: 1px solid #bbb; } -.coverage-summary td { border-right: 1px solid #bbb; } -.coverage-summary td:last-child { border-right: none; } -.coverage-summary th { - text-align: left; - font-weight: normal; - white-space: nowrap; -} -.coverage-summary th.file { border-right: none !important; } -.coverage-summary th.pct { } -.coverage-summary th.pic, -.coverage-summary th.abs, -.coverage-summary td.pct, -.coverage-summary td.abs { text-align: right; } -.coverage-summary td.file { white-space: nowrap; } -.coverage-summary td.pic { min-width: 120px !important; } -.coverage-summary tfoot td { } - -.coverage-summary .sorter { - height: 10px; - width: 7px; - display: inline-block; - margin-left: 0.5em; - background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; -} -.coverage-summary .sorted .sorter { - background-position: 0 -20px; -} -.coverage-summary .sorted-desc .sorter { - background-position: 0 -10px; -} -.status-line { height: 10px; } -/* yellow */ -.cbranch-no { background: yellow !important; color: #111; } -/* dark red */ -.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } -.low .chart { border:1px solid #C21F39 } -.highlighted, -.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ - background: #C21F39 !important; -} -/* medium red */ -.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } -/* light red */ -.low, .cline-no { background:#FCE1E5 } -/* light green */ -.high, .cline-yes { background:rgb(230,245,208) } -/* medium green */ -.cstat-yes { background:rgb(161,215,106) } -/* dark green */ -.status-line.high, .high .cover-fill { background:rgb(77,146,33) } -.high .chart { border:1px solid rgb(77,146,33) } -/* dark yellow (gold) */ -.status-line.medium, .medium .cover-fill { background: #f9cd0b; } -.medium .chart { border:1px solid #f9cd0b; } -/* light yellow */ -.medium { background: #fff4c2; } - -.cstat-skip { background: #ddd; color: #111; } -.fstat-skip { background: #ddd; color: #111 !important; } -.cbranch-skip { background: #ddd !important; color: #111; } - -span.cline-neutral { background: #eaeaea; } - -.coverage-summary td.empty { - opacity: .5; - padding-top: 4px; - padding-bottom: 4px; - line-height: 1; - color: #888; -} - -.cover-fill, .cover-empty { - display:inline-block; - height: 12px; -} -.chart { - line-height: 0; -} -.cover-empty { - background: white; -} -.cover-full { - border-right: none !important; -} -pre.prettyprint { - border: none !important; - padding: 0 !important; - margin: 0 !important; -} -.com { color: #999 !important; } -.ignore-none { color: #999; font-weight: normal; } - -.wrapper { - min-height: 100%; - height: auto !important; - height: 100%; - margin: 0 auto -48px; -} -.footer, .push { - height: 48px; -} diff --git a/coverage/block-navigation.js b/coverage/block-navigation.js deleted file mode 100644 index 530d1ed..0000000 --- a/coverage/block-navigation.js +++ /dev/null @@ -1,87 +0,0 @@ -/* eslint-disable */ -var jumpToCode = (function init() { - // Classes of code we would like to highlight in the file view - var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; - - // Elements to highlight in the file listing view - var fileListingElements = ['td.pct.low']; - - // We don't want to select elements that are direct descendants of another match - var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` - - // Selector that finds elements on the page to which we can jump - var selector = - fileListingElements.join(', ') + - ', ' + - notSelector + - missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` - - // The NodeList of matching elements - var missingCoverageElements = document.querySelectorAll(selector); - - var currentIndex; - - function toggleClass(index) { - missingCoverageElements - .item(currentIndex) - .classList.remove('highlighted'); - missingCoverageElements.item(index).classList.add('highlighted'); - } - - function makeCurrent(index) { - toggleClass(index); - currentIndex = index; - missingCoverageElements.item(index).scrollIntoView({ - behavior: 'smooth', - block: 'center', - inline: 'center' - }); - } - - function goToPrevious() { - var nextIndex = 0; - if (typeof currentIndex !== 'number' || currentIndex === 0) { - nextIndex = missingCoverageElements.length - 1; - } else if (missingCoverageElements.length > 1) { - nextIndex = currentIndex - 1; - } - - makeCurrent(nextIndex); - } - - function goToNext() { - var nextIndex = 0; - - if ( - typeof currentIndex === 'number' && - currentIndex < missingCoverageElements.length - 1 - ) { - nextIndex = currentIndex + 1; - } - - makeCurrent(nextIndex); - } - - return function jump(event) { - if ( - document.getElementById('fileSearch') === document.activeElement && - document.activeElement != null - ) { - // if we're currently focused on the search input, we don't want to navigate - return; - } - - switch (event.which) { - case 78: // n - case 74: // j - goToNext(); - break; - case 66: // b - case 75: // k - case 80: // p - goToPrevious(); - break; - } - }; -})(); -window.addEventListener('keydown', jumpToCode); diff --git a/coverage/coverage-final.json b/coverage/coverage-final.json deleted file mode 100644 index 88275ed..0000000 --- a/coverage/coverage-final.json +++ /dev/null @@ -1,13 +0,0 @@ -{"E:\\workspaces\\windows-apps\\ContextMaster\\.claude\\worktrees\\elastic-swirles\\src\\main\\data\\repositories\\BackupSnapshotRepo.ts": {"path":"E:\\workspaces\\windows-apps\\ContextMaster\\.claude\\worktrees\\elastic-swirles\\src\\main\\data\\repositories\\BackupSnapshotRepo.ts","statementMap":{"0":{"start":{"line":15,"column":31},"end":{"line":15,"column":54}},"1":{"start":{"line":18,"column":17},"end":{"line":22,"column":null}},"2":{"start":{"line":23,"column":19},"end":{"line":28,"column":null}},"3":{"start":{"line":30,"column":4},"end":{"line":30,"column":null}},"4":{"start":{"line":34,"column":17},"end":{"line":36,"column":12}},"5":{"start":{"line":37,"column":4},"end":{"line":37,"column":null}},"6":{"start":{"line":41,"column":16},"end":{"line":43,"column":14}},"7":{"start":{"line":44,"column":4},"end":{"line":44,"column":null}},"8":{"start":{"line":48,"column":4},"end":{"line":48,"column":null}},"9":{"start":{"line":52,"column":4},"end":{"line":59,"column":null}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":15,"column":2},"end":{"line":15,"column":14}},"loc":{"start":{"line":15,"column":54},"end":{"line":15,"column":54}},"line":15},"1":{"name":"(anonymous_1)","decl":{"start":{"line":17,"column":2},"end":{"line":17,"column":9}},"loc":{"start":{"line":17,"column":63},"end":{"line":30,"column":null}},"line":17},"2":{"name":"(anonymous_2)","decl":{"start":{"line":33,"column":2},"end":{"line":33,"column":30}},"loc":{"start":{"line":33,"column":30},"end":{"line":37,"column":null}},"line":33},"3":{"name":"(anonymous_3)","decl":{"start":{"line":40,"column":2},"end":{"line":40,"column":11}},"loc":{"start":{"line":40,"column":46},"end":{"line":44,"column":null}},"line":40},"4":{"name":"(anonymous_4)","decl":{"start":{"line":47,"column":2},"end":{"line":47,"column":9}},"loc":{"start":{"line":47,"column":27},"end":{"line":48,"column":null}},"line":47},"5":{"name":"(anonymous_5)","decl":{"start":{"line":51,"column":2},"end":{"line":51,"column":10}},"loc":{"start":{"line":51,"column":46},"end":{"line":59,"column":null}},"line":51}},"branchMap":{"0":{"loc":{"start":{"line":44,"column":11},"end":{"line":44,"column":null}},"type":"cond-expr","locations":[{"start":{"line":44,"column":17},"end":{"line":44,"column":34}},{"start":{"line":44,"column":37},"end":{"line":44,"column":null}}],"line":44}},"s":{"0":6,"1":1,"2":1,"3":1,"4":2,"5":2,"6":2,"7":2,"8":1,"9":3},"f":{"0":6,"1":1,"2":2,"3":2,"4":1,"5":3},"b":{"0":[1,1]},"meta":{"lastBranch":1,"lastFunction":6,"lastStatement":10,"seen":{"f:15:2:15:14":0,"s:15:31:15:54":0,"f:17:2:17:9":1,"s:18:17:22:Infinity":1,"s:23:19:28:Infinity":2,"s:30:4:30:Infinity":3,"f:33:2:33:30":2,"s:34:17:36:12":4,"s:37:4:37:Infinity":5,"f:40:2:40:11":3,"s:41:16:43:14":6,"s:44:4:44:Infinity":7,"b:44:17:44:34:44:37:44:Infinity":0,"f:47:2:47:9":4,"s:48:4:48:Infinity":8,"f:51:2:51:10":5,"s:52:4:59:Infinity":9}}} -,"E:\\workspaces\\windows-apps\\ContextMaster\\.claude\\worktrees\\elastic-swirles\\src\\main\\data\\repositories\\OperationRecordRepo.ts": {"path":"E:\\workspaces\\windows-apps\\ContextMaster\\.claude\\worktrees\\elastic-swirles\\src\\main\\data\\repositories\\OperationRecordRepo.ts","statementMap":{"0":{"start":{"line":16,"column":31},"end":{"line":16,"column":54}},"1":{"start":{"line":19,"column":17},"end":{"line":23,"column":null}},"2":{"start":{"line":24,"column":19},"end":{"line":30,"column":null}},"3":{"start":{"line":32,"column":4},"end":{"line":32,"column":null}},"4":{"start":{"line":36,"column":17},"end":{"line":38,"column":12}},"5":{"start":{"line":39,"column":4},"end":{"line":39,"column":null}},"6":{"start":{"line":43,"column":16},"end":{"line":45,"column":14}},"7":{"start":{"line":46,"column":4},"end":{"line":46,"column":null}},"8":{"start":{"line":50,"column":4},"end":{"line":50,"column":null}},"9":{"start":{"line":54,"column":4},"end":{"line":62,"column":null}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":16,"column":2},"end":{"line":16,"column":14}},"loc":{"start":{"line":16,"column":54},"end":{"line":16,"column":54}},"line":16},"1":{"name":"(anonymous_1)","decl":{"start":{"line":18,"column":2},"end":{"line":18,"column":9}},"loc":{"start":{"line":18,"column":63},"end":{"line":32,"column":null}},"line":18},"2":{"name":"(anonymous_2)","decl":{"start":{"line":35,"column":2},"end":{"line":35,"column":31}},"loc":{"start":{"line":35,"column":31},"end":{"line":39,"column":null}},"line":35},"3":{"name":"(anonymous_3)","decl":{"start":{"line":42,"column":2},"end":{"line":42,"column":11}},"loc":{"start":{"line":42,"column":47},"end":{"line":46,"column":null}},"line":42},"4":{"name":"(anonymous_4)","decl":{"start":{"line":49,"column":2},"end":{"line":49,"column":20}},"loc":{"start":{"line":49,"column":20},"end":{"line":50,"column":null}},"line":49},"5":{"name":"(anonymous_5)","decl":{"start":{"line":53,"column":2},"end":{"line":53,"column":10}},"loc":{"start":{"line":53,"column":47},"end":{"line":62,"column":null}},"line":53}},"branchMap":{"0":{"loc":{"start":{"line":29,"column":6},"end":{"line":29,"column":null}},"type":"binary-expr","locations":[{"start":{"line":29,"column":6},"end":{"line":29,"column":25}},{"start":{"line":29,"column":25},"end":{"line":29,"column":null}}],"line":29},"1":{"loc":{"start":{"line":30,"column":6},"end":{"line":30,"column":null}},"type":"binary-expr","locations":[{"start":{"line":30,"column":6},"end":{"line":30,"column":25}},{"start":{"line":30,"column":25},"end":{"line":30,"column":null}}],"line":30},"2":{"loc":{"start":{"line":46,"column":11},"end":{"line":46,"column":null}},"type":"cond-expr","locations":[{"start":{"line":46,"column":17},"end":{"line":46,"column":34}},{"start":{"line":46,"column":37},"end":{"line":46,"column":null}}],"line":46}},"s":{"0":6,"1":2,"2":2,"3":2,"4":1,"5":1,"6":2,"7":2,"8":1,"9":3},"f":{"0":6,"1":2,"2":1,"3":2,"4":1,"5":3},"b":{"0":[2,1],"1":[2,1],"2":[1,1]},"meta":{"lastBranch":3,"lastFunction":6,"lastStatement":10,"seen":{"f:16:2:16:14":0,"s:16:31:16:54":0,"f:18:2:18:9":1,"s:19:17:23:Infinity":1,"s:24:19:30:Infinity":2,"b:29:6:29:25:29:25:29:Infinity":0,"b:30:6:30:25:30:25:30:Infinity":1,"s:32:4:32:Infinity":3,"f:35:2:35:31":2,"s:36:17:38:12":4,"s:39:4:39:Infinity":5,"f:42:2:42:11":3,"s:43:16:45:14":6,"s:46:4:46:Infinity":7,"b:46:17:46:34:46:37:46:Infinity":2,"f:49:2:49:20":4,"s:50:4:50:Infinity":8,"f:53:2:53:10":5,"s:54:4:62:Infinity":9}}} -,"E:\\workspaces\\windows-apps\\ContextMaster\\.claude\\worktrees\\elastic-swirles\\src\\main\\ipc\\registry.ts": {"path":"E:\\workspaces\\windows-apps\\ContextMaster\\.claude\\worktrees\\elastic-swirles\\src\\main\\ipc\\registry.ts","statementMap":{"0":{"start":{"line":9,"column":2},"end":{"line":14,"column":null}},"1":{"start":{"line":12,"column":6},"end":{"line":12,"column":37}},"2":{"start":{"line":16,"column":2},"end":{"line":33,"column":null}},"3":{"start":{"line":19,"column":19},"end":{"line":29,"column":null}},"4":{"start":{"line":30,"column":21},"end":{"line":30,"column":55}},"5":{"start":{"line":31,"column":6},"end":{"line":31,"column":null}},"6":{"start":{"line":35,"column":2},"end":{"line":45,"column":null}},"7":{"start":{"line":38,"column":6},"end":{"line":41,"column":null}},"8":{"start":{"line":39,"column":8},"end":{"line":39,"column":null}},"9":{"start":{"line":41,"column":8},"end":{"line":41,"column":null}},"10":{"start":{"line":43,"column":6},"end":{"line":43,"column":null}}},"fnMap":{"0":{"name":"registerRegistryHandlers","decl":{"start":{"line":8,"column":16},"end":{"line":8,"column":41}},"loc":{"start":{"line":8,"column":80},"end":{"line":45,"column":null}},"line":8},"1":{"name":"(anonymous_1)","decl":{"start":{"line":11,"column":4},"end":{"line":11,"column":17}},"loc":{"start":{"line":12,"column":6},"end":{"line":12,"column":37}},"line":12},"2":{"name":"(anonymous_2)","decl":{"start":{"line":18,"column":16},"end":{"line":18,"column":23}},"loc":{"start":{"line":18,"column":69},"end":{"line":32,"column":null}},"line":18},"3":{"name":"(anonymous_3)","decl":{"start":{"line":37,"column":16},"end":{"line":37,"column":23}},"loc":{"start":{"line":37,"column":70},"end":{"line":44,"column":null}},"line":37}},"branchMap":{"0":{"loc":{"start":{"line":28,"column":14},"end":{"line":28,"column":null}},"type":"binary-expr","locations":[{"start":{"line":28,"column":14},"end":{"line":28,"column":29}},{"start":{"line":28,"column":29},"end":{"line":28,"column":null}}],"line":28},"1":{"loc":{"start":{"line":38,"column":6},"end":{"line":41,"column":null}},"type":"if","locations":[{"start":{"line":38,"column":6},"end":{"line":41,"column":null}},{"start":{"line":40,"column":13},"end":{"line":41,"column":null}}],"line":38}},"s":{"0":7,"1":1,"2":7,"3":1,"4":1,"5":1,"6":7,"7":2,"8":1,"9":1,"10":2},"f":{"0":7,"1":1,"2":1,"3":2},"b":{"0":[1,0],"1":[1,1]},"meta":{"lastBranch":2,"lastFunction":4,"lastStatement":11,"seen":{"f:8:16:8:41":0,"s:9:2:14:Infinity":0,"f:11:4:11:17":1,"s:12:6:12:37":1,"s:16:2:33:Infinity":2,"f:18:16:18:23":2,"s:19:19:29:Infinity":3,"b:28:14:28:29:28:29:28:Infinity":0,"s:30:21:30:55":4,"s:31:6:31:Infinity":5,"s:35:2:45:Infinity":6,"f:37:16:37:23":3,"b:38:6:41:Infinity:40:13:41:Infinity":1,"s:38:6:41:Infinity":7,"s:39:8:39:Infinity":8,"s:41:8:41:Infinity":9,"s:43:6:43:Infinity":10}}} -,"E:\\workspaces\\windows-apps\\ContextMaster\\.claude\\worktrees\\elastic-swirles\\src\\main\\services\\BackupService.ts": {"path":"E:\\workspaces\\windows-apps\\ContextMaster\\.claude\\worktrees\\elastic-swirles\\src\\main\\services\\BackupService.ts","statementMap":{"0":{"start":{"line":13,"column":14},"end":{"line":13,"column":35}},"1":{"start":{"line":14,"column":2},"end":{"line":14,"column":null}},"2":{"start":{"line":14,"column":18},"end":{"line":14,"column":null}},"3":{"start":{"line":15,"column":2},"end":{"line":15,"column":null}},"4":{"start":{"line":20,"column":21},"end":{"line":20,"column":null}},"5":{"start":{"line":21,"column":21},"end":{"line":21,"column":null}},"6":{"start":{"line":22,"column":21},"end":{"line":22,"column":null}},"7":{"start":{"line":26,"column":38},"end":{"line":26,"column":40}},"8":{"start":{"line":27,"column":4},"end":{"line":29,"column":null}},"9":{"start":{"line":28,"column":20},"end":{"line":28,"column":62}},"10":{"start":{"line":29,"column":6},"end":{"line":29,"column":null}},"11":{"start":{"line":32,"column":21},"end":{"line":32,"column":45}},"12":{"start":{"line":33,"column":10},"end":{"line":33,"column":72}},"13":{"start":{"line":35,"column":21},"end":{"line":41,"column":6}},"14":{"start":{"line":43,"column":4},"end":{"line":43,"column":null}},"15":{"start":{"line":44,"column":4},"end":{"line":44,"column":null}},"16":{"start":{"line":45,"column":4},"end":{"line":45,"column":null}},"17":{"start":{"line":49,"column":21},"end":{"line":49,"column":51}},"18":{"start":{"line":50,"column":4},"end":{"line":50,"column":null}},"19":{"start":{"line":50,"column":19},"end":{"line":50,"column":null}},"20":{"start":{"line":52,"column":10},"end":{"line":54,"column":20}},"21":{"start":{"line":55,"column":4},"end":{"line":56,"column":null}},"22":{"start":{"line":56,"column":6},"end":{"line":56,"column":null}},"23":{"start":{"line":60,"column":4},"end":{"line":63,"column":null}},"24":{"start":{"line":65,"column":44},"end":{"line":65,"column":78}},"25":{"start":{"line":66,"column":4},"end":{"line":66,"column":null}},"26":{"start":{"line":66,"column":32},"end":{"line":66,"column":null}},"27":{"start":{"line":68,"column":45},"end":{"line":68,"column":47}},"28":{"start":{"line":69,"column":4},"end":{"line":70,"column":null}},"29":{"start":{"line":70,"column":6},"end":{"line":70,"column":null}},"30":{"start":{"line":73,"column":38},"end":{"line":73,"column":40}},"31":{"start":{"line":74,"column":39},"end":{"line":74,"column":41}},"32":{"start":{"line":76,"column":4},"end":{"line":81,"column":null}},"33":{"start":{"line":77,"column":22},"end":{"line":77,"column":119}},"34":{"start":{"line":77,"column":50},"end":{"line":77,"column":118}},"35":{"start":{"line":78,"column":6},"end":{"line":81,"column":null}},"36":{"start":{"line":79,"column":8},"end":{"line":79,"column":null}},"37":{"start":{"line":80,"column":8},"end":{"line":81,"column":null}},"38":{"start":{"line":80,"column":34},"end":{"line":80,"column":null}},"39":{"start":{"line":81,"column":13},"end":{"line":81,"column":null}},"40":{"start":{"line":85,"column":4},"end":{"line":85,"column":null}},"41":{"start":{"line":85,"column":25},"end":{"line":85,"column":null}},"42":{"start":{"line":86,"column":4},"end":{"line":86,"column":null}},"43":{"start":{"line":86,"column":26},"end":{"line":86,"column":null}},"44":{"start":{"line":88,"column":4},"end":{"line":88,"column":null}},"45":{"start":{"line":89,"column":4},"end":{"line":89,"column":null}},"46":{"start":{"line":93,"column":4},"end":{"line":93,"column":null}},"47":{"start":{"line":97,"column":4},"end":{"line":97,"column":null}},"48":{"start":{"line":101,"column":21},"end":{"line":101,"column":51}},"49":{"start":{"line":102,"column":4},"end":{"line":102,"column":null}},"50":{"start":{"line":102,"column":19},"end":{"line":102,"column":null}},"51":{"start":{"line":104,"column":41},"end":{"line":104,"column":75}},"52":{"start":{"line":105,"column":42},"end":{"line":105,"column":44}},"53":{"start":{"line":106,"column":4},"end":{"line":107,"column":null}},"54":{"start":{"line":107,"column":6},"end":{"line":107,"column":null}},"55":{"start":{"line":110,"column":36},"end":{"line":110,"column":38}},"56":{"start":{"line":111,"column":4},"end":{"line":114,"column":null}},"57":{"start":{"line":112,"column":22},"end":{"line":112,"column":116}},"58":{"start":{"line":112,"column":47},"end":{"line":112,"column":115}},"59":{"start":{"line":113,"column":6},"end":{"line":114,"column":null}},"60":{"start":{"line":114,"column":8},"end":{"line":114,"column":null}},"61":{"start":{"line":117,"column":4},"end":{"line":117,"column":null}},"62":{"start":{"line":121,"column":21},"end":{"line":121,"column":51}},"63":{"start":{"line":122,"column":4},"end":{"line":122,"column":null}},"64":{"start":{"line":122,"column":19},"end":{"line":122,"column":null}},"65":{"start":{"line":124,"column":35},"end":{"line":128,"column":6}},"66":{"start":{"line":130,"column":4},"end":{"line":130,"column":null}},"67":{"start":{"line":130,"column":31},"end":{"line":130,"column":null}},"68":{"start":{"line":131,"column":4},"end":{"line":131,"column":null}},"69":{"start":{"line":132,"column":4},"end":{"line":132,"column":null}},"70":{"start":{"line":136,"column":36},"end":{"line":143,"column":6}},"71":{"start":{"line":145,"column":4},"end":{"line":145,"column":null}},"72":{"start":{"line":145,"column":39},"end":{"line":145,"column":null}},"73":{"start":{"line":147,"column":21},"end":{"line":147,"column":null}},"74":{"start":{"line":148,"column":21},"end":{"line":148,"column":57}},"75":{"start":{"line":149,"column":10},"end":{"line":149,"column":72}},"76":{"start":{"line":151,"column":21},"end":{"line":157,"column":6}},"77":{"start":{"line":159,"column":4},"end":{"line":159,"column":null}},"78":{"start":{"line":160,"column":4},"end":{"line":160,"column":null}}},"fnMap":{"0":{"name":"normalizeKey","decl":{"start":{"line":12,"column":9},"end":{"line":12,"column":22}},"loc":{"start":{"line":12,"column":43},"end":{"line":15,"column":null}},"line":12},"1":{"name":"(anonymous_1)","decl":{"start":{"line":19,"column":2},"end":{"line":19,"column":null}},"loc":{"start":{"line":23,"column":4},"end":{"line":22,"column":null}},"line":23},"2":{"name":"(anonymous_2)","decl":{"start":{"line":25,"column":8},"end":{"line":25,"column":21}},"loc":{"start":{"line":25,"column":86},"end":{"line":45,"column":null}},"line":25},"3":{"name":"(anonymous_3)","decl":{"start":{"line":48,"column":8},"end":{"line":48,"column":22}},"loc":{"start":{"line":48,"column":57},"end":{"line":89,"column":null}},"line":48},"4":{"name":"(anonymous_4)","decl":{"start":{"line":77,"column":38},"end":{"line":77,"column":44}},"loc":{"start":{"line":77,"column":50},"end":{"line":77,"column":118}},"line":77},"5":{"name":"(anonymous_5)","decl":{"start":{"line":92,"column":8},"end":{"line":92,"column":21}},"loc":{"start":{"line":92,"column":48},"end":{"line":93,"column":null}},"line":92},"6":{"name":"(anonymous_6)","decl":{"start":{"line":96,"column":2},"end":{"line":96,"column":36}},"loc":{"start":{"line":96,"column":36},"end":{"line":97,"column":null}},"line":96},"7":{"name":"(anonymous_7)","decl":{"start":{"line":100,"column":8},"end":{"line":100,"column":27}},"loc":{"start":{"line":100,"column":75},"end":{"line":117,"column":null}},"line":100},"8":{"name":"(anonymous_8)","decl":{"start":{"line":112,"column":35},"end":{"line":112,"column":41}},"loc":{"start":{"line":112,"column":47},"end":{"line":112,"column":115}},"line":112},"9":{"name":"(anonymous_9)","decl":{"start":{"line":120,"column":8},"end":{"line":120,"column":21}},"loc":{"start":{"line":120,"column":76},"end":{"line":132,"column":null}},"line":120},"10":{"name":"(anonymous_10)","decl":{"start":{"line":135,"column":8},"end":{"line":135,"column":21}},"loc":{"start":{"line":135,"column":66},"end":{"line":160,"column":null}},"line":135}},"branchMap":{"0":{"loc":{"start":{"line":14,"column":2},"end":{"line":14,"column":null}},"type":"if","locations":[{"start":{"line":14,"column":2},"end":{"line":14,"column":null}},{"start":{},"end":{}}],"line":14},"1":{"loc":{"start":{"line":25,"column":35},"end":{"line":25,"column":86}},"type":"default-arg","locations":[{"start":{"line":25,"column":42},"end":{"line":25,"column":86}}],"line":25},"2":{"loc":{"start":{"line":50,"column":4},"end":{"line":50,"column":null}},"type":"if","locations":[{"start":{"line":50,"column":4},"end":{"line":50,"column":null}},{"start":{},"end":{}}],"line":50},"3":{"loc":{"start":{"line":55,"column":4},"end":{"line":56,"column":null}},"type":"if","locations":[{"start":{"line":55,"column":4},"end":{"line":56,"column":null}},{"start":{},"end":{}}],"line":55},"4":{"loc":{"start":{"line":66,"column":4},"end":{"line":66,"column":null}},"type":"if","locations":[{"start":{"line":66,"column":4},"end":{"line":66,"column":null}},{"start":{},"end":{}}],"line":66},"5":{"loc":{"start":{"line":78,"column":6},"end":{"line":81,"column":null}},"type":"if","locations":[{"start":{"line":78,"column":6},"end":{"line":81,"column":null}},{"start":{},"end":{}}],"line":78},"6":{"loc":{"start":{"line":78,"column":10},"end":{"line":78,"column":65}},"type":"binary-expr","locations":[{"start":{"line":78,"column":10},"end":{"line":78,"column":21}},{"start":{"line":78,"column":21},"end":{"line":78,"column":65}}],"line":78},"7":{"loc":{"start":{"line":80,"column":8},"end":{"line":81,"column":null}},"type":"if","locations":[{"start":{"line":80,"column":8},"end":{"line":81,"column":null}},{"start":{"line":81,"column":13},"end":{"line":81,"column":null}}],"line":80},"8":{"loc":{"start":{"line":85,"column":4},"end":{"line":85,"column":null}},"type":"if","locations":[{"start":{"line":85,"column":4},"end":{"line":85,"column":null}},{"start":{},"end":{}}],"line":85},"9":{"loc":{"start":{"line":86,"column":4},"end":{"line":86,"column":null}},"type":"if","locations":[{"start":{"line":86,"column":4},"end":{"line":86,"column":null}},{"start":{},"end":{}}],"line":86},"10":{"loc":{"start":{"line":102,"column":4},"end":{"line":102,"column":null}},"type":"if","locations":[{"start":{"line":102,"column":4},"end":{"line":102,"column":null}},{"start":{},"end":{}}],"line":102},"11":{"loc":{"start":{"line":113,"column":6},"end":{"line":114,"column":null}},"type":"if","locations":[{"start":{"line":113,"column":6},"end":{"line":114,"column":null}},{"start":{},"end":{}}],"line":113},"12":{"loc":{"start":{"line":113,"column":10},"end":{"line":113,"column":65}},"type":"binary-expr","locations":[{"start":{"line":113,"column":10},"end":{"line":113,"column":21}},{"start":{"line":113,"column":21},"end":{"line":113,"column":65}}],"line":113},"13":{"loc":{"start":{"line":122,"column":4},"end":{"line":122,"column":null}},"type":"if","locations":[{"start":{"line":122,"column":4},"end":{"line":122,"column":null}},{"start":{},"end":{}}],"line":122},"14":{"loc":{"start":{"line":130,"column":4},"end":{"line":130,"column":null}},"type":"if","locations":[{"start":{"line":130,"column":4},"end":{"line":130,"column":null}},{"start":{},"end":{}}],"line":130},"15":{"loc":{"start":{"line":130,"column":8},"end":{"line":130,"column":31}},"type":"binary-expr","locations":[{"start":{"line":130,"column":8},"end":{"line":130,"column":20}},{"start":{"line":130,"column":20},"end":{"line":130,"column":31}}],"line":130},"16":{"loc":{"start":{"line":145,"column":4},"end":{"line":145,"column":null}},"type":"if","locations":[{"start":{"line":145,"column":4},"end":{"line":145,"column":null}},{"start":{},"end":{}}],"line":145},"17":{"loc":{"start":{"line":145,"column":8},"end":{"line":145,"column":39}},"type":"binary-expr","locations":[{"start":{"line":145,"column":8},"end":{"line":145,"column":20}},{"start":{"line":145,"column":20},"end":{"line":145,"column":39}}],"line":145}},"s":{"0":0,"1":0,"2":0,"3":0,"4":4,"5":4,"6":4,"7":2,"8":2,"9":12,"10":12,"11":2,"12":2,"13":2,"14":2,"15":2,"16":2,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":0,"31":0,"32":0,"33":0,"34":0,"35":0,"36":0,"37":0,"38":0,"39":0,"40":0,"41":0,"42":0,"43":0,"44":0,"45":0,"46":1,"47":1,"48":0,"49":0,"50":0,"51":0,"52":0,"53":0,"54":0,"55":0,"56":0,"57":0,"58":0,"59":0,"60":0,"61":0,"62":0,"63":0,"64":0,"65":0,"66":0,"67":0,"68":0,"69":0,"70":0,"71":0,"72":0,"73":0,"74":0,"75":0,"76":0,"77":0,"78":0},"f":{"0":0,"1":4,"2":2,"3":0,"4":0,"5":1,"6":1,"7":0,"8":0,"9":0,"10":0},"b":{"0":[0,0],"1":[2],"2":[0,0],"3":[0,0],"4":[0,0],"5":[0,0],"6":[0,0],"7":[0,0],"8":[0,0],"9":[0,0],"10":[0,0],"11":[0,0],"12":[0,0],"13":[0,0],"14":[0,0],"15":[0,0],"16":[0,0],"17":[0,0]},"meta":{"lastBranch":18,"lastFunction":11,"lastStatement":79,"seen":{"f:12:9:12:22":0,"s:13:14:13:35":0,"b:14:2:14:Infinity:undefined:undefined:undefined:undefined":0,"s:14:2:14:Infinity":1,"s:14:18:14:Infinity":2,"s:15:2:15:Infinity":3,"f:19:2:19:Infinity":1,"s:20:21:20:Infinity":4,"s:21:21:21:Infinity":5,"s:22:21:22:Infinity":6,"f:25:8:25:21":2,"b:25:42:25:86":1,"s:26:38:26:40":7,"s:27:4:29:Infinity":8,"s:28:20:28:62":9,"s:29:6:29:Infinity":10,"s:32:21:32:45":11,"s:33:10:33:72":12,"s:35:21:41:6":13,"s:43:4:43:Infinity":14,"s:44:4:44:Infinity":15,"s:45:4:45:Infinity":16,"f:48:8:48:22":3,"s:49:21:49:51":17,"b:50:4:50:Infinity:undefined:undefined:undefined:undefined":2,"s:50:4:50:Infinity":18,"s:50:19:50:Infinity":19,"s:52:10:54:20":20,"b:55:4:56:Infinity:undefined:undefined:undefined:undefined":3,"s:55:4:56:Infinity":21,"s:56:6:56:Infinity":22,"s:60:4:63:Infinity":23,"s:65:44:65:78":24,"b:66:4:66:Infinity:undefined:undefined:undefined:undefined":4,"s:66:4:66:Infinity":25,"s:66:32:66:Infinity":26,"s:68:45:68:47":27,"s:69:4:70:Infinity":28,"s:70:6:70:Infinity":29,"s:73:38:73:40":30,"s:74:39:74:41":31,"s:76:4:81:Infinity":32,"s:77:22:77:119":33,"f:77:38:77:44":4,"s:77:50:77:118":34,"b:78:6:81:Infinity:undefined:undefined:undefined:undefined":5,"s:78:6:81:Infinity":35,"b:78:10:78:21:78:21:78:65":6,"s:79:8:79:Infinity":36,"b:80:8:81:Infinity:81:13:81:Infinity":7,"s:80:8:81:Infinity":37,"s:80:34:80:Infinity":38,"s:81:13:81:Infinity":39,"b:85:4:85:Infinity:undefined:undefined:undefined:undefined":8,"s:85:4:85:Infinity":40,"s:85:25:85:Infinity":41,"b:86:4:86:Infinity:undefined:undefined:undefined:undefined":9,"s:86:4:86:Infinity":42,"s:86:26:86:Infinity":43,"s:88:4:88:Infinity":44,"s:89:4:89:Infinity":45,"f:92:8:92:21":5,"s:93:4:93:Infinity":46,"f:96:2:96:36":6,"s:97:4:97:Infinity":47,"f:100:8:100:27":7,"s:101:21:101:51":48,"b:102:4:102:Infinity:undefined:undefined:undefined:undefined":10,"s:102:4:102:Infinity":49,"s:102:19:102:Infinity":50,"s:104:41:104:75":51,"s:105:42:105:44":52,"s:106:4:107:Infinity":53,"s:107:6:107:Infinity":54,"s:110:36:110:38":55,"s:111:4:114:Infinity":56,"s:112:22:112:116":57,"f:112:35:112:41":8,"s:112:47:112:115":58,"b:113:6:114:Infinity:undefined:undefined:undefined:undefined":11,"s:113:6:114:Infinity":59,"b:113:10:113:21:113:21:113:65":12,"s:114:8:114:Infinity":60,"s:117:4:117:Infinity":61,"f:120:8:120:21":9,"s:121:21:121:51":62,"b:122:4:122:Infinity:undefined:undefined:undefined:undefined":13,"s:122:4:122:Infinity":63,"s:122:19:122:Infinity":64,"s:124:35:128:6":65,"b:130:4:130:Infinity:undefined:undefined:undefined:undefined":14,"s:130:4:130:Infinity":66,"b:130:8:130:20:130:20:130:31":15,"s:130:31:130:Infinity":67,"s:131:4:131:Infinity":68,"s:132:4:132:Infinity":69,"f:135:8:135:21":10,"s:136:36:143:6":70,"b:145:4:145:Infinity:undefined:undefined:undefined:undefined":16,"s:145:4:145:Infinity":71,"b:145:8:145:20:145:20:145:39":17,"s:145:39:145:Infinity":72,"s:147:21:147:Infinity":73,"s:148:21:148:57":74,"s:149:10:149:72":75,"s:151:21:157:6":76,"s:159:4:159:Infinity":77,"s:160:4:160:Infinity":78}}} -,"E:\\workspaces\\windows-apps\\ContextMaster\\.claude\\worktrees\\elastic-swirles\\src\\main\\services\\MenuManagerService.ts": {"path":"E:\\workspaces\\windows-apps\\ContextMaster\\.claude\\worktrees\\elastic-swirles\\src\\main\\services\\MenuManagerService.ts","statementMap":{"0":{"start":{"line":9,"column":21},"end":{"line":9,"column":null}},"1":{"start":{"line":10,"column":21},"end":{"line":10,"column":null}},"2":{"start":{"line":14,"column":4},"end":{"line":14,"column":null}},"3":{"start":{"line":18,"column":4},"end":{"line":18,"column":null}},"4":{"start":{"line":18,"column":24},"end":{"line":18,"column":null}},"5":{"start":{"line":19,"column":19},"end":{"line":19,"column":77}},"6":{"start":{"line":20,"column":4},"end":{"line":20,"column":null}},"7":{"start":{"line":20,"column":31},"end":{"line":20,"column":null}},"8":{"start":{"line":21,"column":4},"end":{"line":21,"column":null}},"9":{"start":{"line":22,"column":4},"end":{"line":28,"column":null}},"10":{"start":{"line":29,"column":4},"end":{"line":29,"column":null}},"11":{"start":{"line":30,"column":4},"end":{"line":30,"column":null}},"12":{"start":{"line":34,"column":4},"end":{"line":34,"column":null}},"13":{"start":{"line":34,"column":25},"end":{"line":34,"column":null}},"14":{"start":{"line":35,"column":19},"end":{"line":35,"column":78}},"15":{"start":{"line":36,"column":4},"end":{"line":36,"column":null}},"16":{"start":{"line":36,"column":31},"end":{"line":36,"column":null}},"17":{"start":{"line":37,"column":4},"end":{"line":37,"column":null}},"18":{"start":{"line":38,"column":4},"end":{"line":44,"column":null}},"19":{"start":{"line":45,"column":4},"end":{"line":45,"column":null}},"20":{"start":{"line":46,"column":4},"end":{"line":46,"column":null}},"21":{"start":{"line":50,"column":4},"end":{"line":53,"column":null}},"22":{"start":{"line":51,"column":6},"end":{"line":51,"column":null}},"23":{"start":{"line":53,"column":6},"end":{"line":53,"column":null}},"24":{"start":{"line":58,"column":20},"end":{"line":58,"column":53}},"25":{"start":{"line":58,"column":40},"end":{"line":58,"column":53}},"26":{"start":{"line":59,"column":4},"end":{"line":59,"column":null}},"27":{"start":{"line":59,"column":25},"end":{"line":59,"column":null}},"28":{"start":{"line":61,"column":4},"end":{"line":61,"column":null}},"29":{"start":{"line":62,"column":4},"end":{"line":69,"column":null}},"30":{"start":{"line":63,"column":6},"end":{"line":64,"column":null}},"31":{"start":{"line":64,"column":8},"end":{"line":64,"column":null}},"32":{"start":{"line":66,"column":6},"end":{"line":66,"column":null}},"33":{"start":{"line":68,"column":6},"end":{"line":68,"column":null}},"34":{"start":{"line":69,"column":6},"end":{"line":69,"column":null}},"35":{"start":{"line":74,"column":20},"end":{"line":74,"column":52}},"36":{"start":{"line":74,"column":40},"end":{"line":74,"column":52}},"37":{"start":{"line":75,"column":4},"end":{"line":75,"column":null}},"38":{"start":{"line":75,"column":25},"end":{"line":75,"column":null}},"39":{"start":{"line":77,"column":4},"end":{"line":77,"column":null}},"40":{"start":{"line":78,"column":4},"end":{"line":85,"column":null}},"41":{"start":{"line":79,"column":6},"end":{"line":80,"column":null}},"42":{"start":{"line":80,"column":8},"end":{"line":80,"column":null}},"43":{"start":{"line":82,"column":6},"end":{"line":82,"column":null}},"44":{"start":{"line":84,"column":6},"end":{"line":84,"column":null}},"45":{"start":{"line":85,"column":6},"end":{"line":85,"column":null}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":8,"column":2},"end":{"line":8,"column":null}},"loc":{"start":{"line":11,"column":4},"end":{"line":10,"column":null}},"line":11},"1":{"name":"(anonymous_1)","decl":{"start":{"line":13,"column":8},"end":{"line":13,"column":21}},"loc":{"start":{"line":13,"column":65},"end":{"line":14,"column":null}},"line":13},"2":{"name":"(anonymous_2)","decl":{"start":{"line":17,"column":8},"end":{"line":17,"column":19}},"loc":{"start":{"line":17,"column":78},"end":{"line":30,"column":null}},"line":17},"3":{"name":"(anonymous_3)","decl":{"start":{"line":33,"column":8},"end":{"line":33,"column":20}},"loc":{"start":{"line":33,"column":79},"end":{"line":46,"column":null}},"line":33},"4":{"name":"(anonymous_4)","decl":{"start":{"line":49,"column":8},"end":{"line":49,"column":19}},"loc":{"start":{"line":49,"column":78},"end":{"line":53,"column":null}},"line":49},"5":{"name":"(anonymous_5)","decl":{"start":{"line":57,"column":8},"end":{"line":57,"column":20}},"loc":{"start":{"line":57,"column":59},"end":{"line":69,"column":null}},"line":57},"6":{"name":"(anonymous_6)","decl":{"start":{"line":58,"column":26},"end":{"line":58,"column":34}},"loc":{"start":{"line":58,"column":40},"end":{"line":58,"column":53}},"line":58},"7":{"name":"(anonymous_7)","decl":{"start":{"line":73,"column":8},"end":{"line":73,"column":21}},"loc":{"start":{"line":73,"column":60},"end":{"line":85,"column":null}},"line":73},"8":{"name":"(anonymous_8)","decl":{"start":{"line":74,"column":26},"end":{"line":74,"column":34}},"loc":{"start":{"line":74,"column":40},"end":{"line":74,"column":52}},"line":74}},"branchMap":{"0":{"loc":{"start":{"line":18,"column":4},"end":{"line":18,"column":null}},"type":"if","locations":[{"start":{"line":18,"column":4},"end":{"line":18,"column":null}},{"start":{},"end":{}}],"line":18},"1":{"loc":{"start":{"line":20,"column":4},"end":{"line":20,"column":null}},"type":"if","locations":[{"start":{"line":20,"column":4},"end":{"line":20,"column":null}},{"start":{},"end":{}}],"line":20},"2":{"loc":{"start":{"line":34,"column":4},"end":{"line":34,"column":null}},"type":"if","locations":[{"start":{"line":34,"column":4},"end":{"line":34,"column":null}},{"start":{},"end":{}}],"line":34},"3":{"loc":{"start":{"line":36,"column":4},"end":{"line":36,"column":null}},"type":"if","locations":[{"start":{"line":36,"column":4},"end":{"line":36,"column":null}},{"start":{},"end":{}}],"line":36},"4":{"loc":{"start":{"line":50,"column":4},"end":{"line":53,"column":null}},"type":"if","locations":[{"start":{"line":50,"column":4},"end":{"line":53,"column":null}},{"start":{"line":52,"column":11},"end":{"line":53,"column":null}}],"line":50},"5":{"loc":{"start":{"line":59,"column":4},"end":{"line":59,"column":null}},"type":"if","locations":[{"start":{"line":59,"column":4},"end":{"line":59,"column":null}},{"start":{},"end":{}}],"line":59},"6":{"loc":{"start":{"line":75,"column":4},"end":{"line":75,"column":null}},"type":"if","locations":[{"start":{"line":75,"column":4},"end":{"line":75,"column":null}},{"start":{},"end":{}}],"line":75}},"s":{"0":13,"1":13,"2":1,"3":6,"4":1,"5":5,"6":4,"7":4,"8":4,"9":4,"10":4,"11":4,"12":6,"13":1,"14":5,"15":4,"16":4,"17":4,"18":4,"19":4,"20":4,"21":2,"22":1,"23":1,"24":3,"25":4,"26":3,"27":1,"28":2,"29":2,"30":2,"31":3,"32":1,"33":1,"34":1,"35":3,"36":4,"37":3,"38":1,"39":2,"40":2,"41":2,"42":3,"43":1,"44":1,"45":1},"f":{"0":13,"1":1,"2":6,"3":6,"4":2,"5":3,"6":4,"7":3,"8":4},"b":{"0":[1,5],"1":[0,4],"2":[1,5],"3":[0,4],"4":[1,1],"5":[1,2],"6":[1,2]},"meta":{"lastBranch":7,"lastFunction":9,"lastStatement":46,"seen":{"f:8:2:8:Infinity":0,"s:9:21:9:Infinity":0,"s:10:21:10:Infinity":1,"f:13:8:13:21":1,"s:14:4:14:Infinity":2,"f:17:8:17:19":2,"b:18:4:18:Infinity:undefined:undefined:undefined:undefined":0,"s:18:4:18:Infinity":3,"s:18:24:18:Infinity":4,"s:19:19:19:77":5,"b:20:4:20:Infinity:undefined:undefined:undefined:undefined":1,"s:20:4:20:Infinity":6,"s:20:31:20:Infinity":7,"s:21:4:21:Infinity":8,"s:22:4:28:Infinity":9,"s:29:4:29:Infinity":10,"s:30:4:30:Infinity":11,"f:33:8:33:20":3,"b:34:4:34:Infinity:undefined:undefined:undefined:undefined":2,"s:34:4:34:Infinity":12,"s:34:25:34:Infinity":13,"s:35:19:35:78":14,"b:36:4:36:Infinity:undefined:undefined:undefined:undefined":3,"s:36:4:36:Infinity":15,"s:36:31:36:Infinity":16,"s:37:4:37:Infinity":17,"s:38:4:44:Infinity":18,"s:45:4:45:Infinity":19,"s:46:4:46:Infinity":20,"f:49:8:49:19":4,"b:50:4:53:Infinity:52:11:53:Infinity":4,"s:50:4:53:Infinity":21,"s:51:6:51:Infinity":22,"s:53:6:53:Infinity":23,"f:57:8:57:20":5,"s:58:20:58:53":24,"f:58:26:58:34":6,"s:58:40:58:53":25,"b:59:4:59:Infinity:undefined:undefined:undefined:undefined":5,"s:59:4:59:Infinity":26,"s:59:25:59:Infinity":27,"s:61:4:61:Infinity":28,"s:62:4:69:Infinity":29,"s:63:6:64:Infinity":30,"s:64:8:64:Infinity":31,"s:66:6:66:Infinity":32,"s:68:6:68:Infinity":33,"s:69:6:69:Infinity":34,"f:73:8:73:21":7,"s:74:20:74:52":35,"f:74:26:74:34":8,"s:74:40:74:52":36,"b:75:4:75:Infinity:undefined:undefined:undefined:undefined":6,"s:75:4:75:Infinity":37,"s:75:25:75:Infinity":38,"s:77:4:77:Infinity":39,"s:78:4:85:Infinity":40,"s:79:6:80:Infinity":41,"s:80:8:80:Infinity":42,"s:82:6:82:Infinity":43,"s:84:6:84:Infinity":44,"s:85:6:85:Infinity":45}}} -,"E:\\workspaces\\windows-apps\\ContextMaster\\.claude\\worktrees\\elastic-swirles\\src\\main\\services\\PowerShellBridge.ts": {"path":"E:\\workspaces\\windows-apps\\ContextMaster\\.claude\\worktrees\\elastic-swirles\\src\\main\\services\\PowerShellBridge.ts","statementMap":{"0":{"start":{"line":10,"column":6},"end":{"line":10,"column":41}},"1":{"start":{"line":12,"column":19},"end":{"line":12,"column":null}},"2":{"start":{"line":13,"column":15},"end":{"line":13,"column":null}},"3":{"start":{"line":20,"column":4},"end":{"line":20,"column":null}},"4":{"start":{"line":21,"column":31},"end":{"line":24,"column":null}},"5":{"start":{"line":27,"column":4},"end":{"line":28,"column":null}},"6":{"start":{"line":28,"column":6},"end":{"line":28,"column":null}},"7":{"start":{"line":31,"column":20},"end":{"line":31,"column":33}},"8":{"start":{"line":32,"column":4},"end":{"line":32,"column":null}},"9":{"start":{"line":32,"column":18},"end":{"line":32,"column":null}},"10":{"start":{"line":34,"column":4},"end":{"line":38,"column":null}},"11":{"start":{"line":35,"column":6},"end":{"line":35,"column":null}},"12":{"start":{"line":37,"column":6},"end":{"line":37,"column":null}},"13":{"start":{"line":38,"column":6},"end":{"line":38,"column":null}},"14":{"start":{"line":47,"column":4},"end":{"line":48,"column":null}},"15":{"start":{"line":48,"column":6},"end":{"line":48,"column":null}},"16":{"start":{"line":51,"column":16},"end":{"line":51,"column":35}},"17":{"start":{"line":52,"column":22},"end":{"line":52,"column":64}},"18":{"start":{"line":53,"column":23},"end":{"line":53,"column":67}},"19":{"start":{"line":56,"column":25},"end":{"line":56,"column":55}},"20":{"start":{"line":57,"column":27},"end":{"line":71,"column":8}},"21":{"start":{"line":73,"column":4},"end":{"line":73,"column":null}},"22":{"start":{"line":76,"column":25},"end":{"line":76,"column":null}},"23":{"start":{"line":78,"column":4},"end":{"line":78,"column":null}},"24":{"start":{"line":79,"column":4},"end":{"line":86,"column":null}},"25":{"start":{"line":80,"column":6},"end":{"line":84,"column":null}},"26":{"start":{"line":86,"column":6},"end":{"line":86,"column":null}},"27":{"start":{"line":86,"column":12},"end":{"line":86,"column":45}},"28":{"start":{"line":89,"column":4},"end":{"line":90,"column":null}},"29":{"start":{"line":90,"column":6},"end":{"line":90,"column":null}},"30":{"start":{"line":94,"column":4},"end":{"line":98,"column":null}},"31":{"start":{"line":95,"column":6},"end":{"line":95,"column":null}},"32":{"start":{"line":96,"column":6},"end":{"line":96,"column":null}},"33":{"start":{"line":98,"column":6},"end":{"line":98,"column":null}},"34":{"start":{"line":102,"column":4},"end":{"line":105,"column":null}},"35":{"start":{"line":103,"column":6},"end":{"line":103,"column":null}},"36":{"start":{"line":105,"column":6},"end":{"line":105,"column":null}},"37":{"start":{"line":108,"column":4},"end":{"line":109,"column":null}},"38":{"start":{"line":109,"column":6},"end":{"line":109,"column":null}},"39":{"start":{"line":112,"column":4},"end":{"line":112,"column":null}},"40":{"start":{"line":121,"column":4},"end":{"line":152,"column":null}},"41":{"start":{"line":161,"column":19},"end":{"line":161,"column":null}},"42":{"start":{"line":162,"column":4},"end":{"line":185,"column":null}},"43":{"start":{"line":163,"column":6},"end":{"line":174,"column":null}},"44":{"start":{"line":176,"column":6},"end":{"line":185,"column":null}},"45":{"start":{"line":199,"column":4},"end":{"line":342,"column":null}},"46":{"start":{"line":351,"column":22},"end":{"line":351,"column":55}},"47":{"start":{"line":352,"column":26},"end":{"line":352,"column":65}},"48":{"start":{"line":353,"column":20},"end":{"line":353,"column":60}},"49":{"start":{"line":354,"column":22},"end":{"line":354,"column":48}},"50":{"start":{"line":355,"column":25},"end":{"line":355,"column":null}},"51":{"start":{"line":357,"column":29},"end":{"line":357,"column":null}},"52":{"start":{"line":358,"column":25},"end":{"line":358,"column":null}},"53":{"start":{"line":359,"column":4},"end":{"line":371,"column":null}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":19,"column":8},"end":{"line":19,"column":19}},"loc":{"start":{"line":19,"column":47},"end":{"line":38,"column":null}},"line":19},"1":{"name":"(anonymous_1)","decl":{"start":{"line":46,"column":8},"end":{"line":46,"column":27}},"loc":{"start":{"line":46,"column":55},"end":{"line":112,"column":null}},"line":46},"2":{"name":"(anonymous_2)","decl":{"start":{"line":119,"column":2},"end":{"line":119,"column":22}},"loc":{"start":{"line":119,"column":51},"end":{"line":152,"column":null}},"line":119},"3":{"name":"(anonymous_3)","decl":{"start":{"line":160,"column":2},"end":{"line":160,"column":24}},"loc":{"start":{"line":160,"column":74},"end":{"line":185,"column":null}},"line":160},"4":{"name":"(anonymous_4)","decl":{"start":{"line":198,"column":2},"end":{"line":198,"column":30}},"loc":{"start":{"line":198,"column":62},"end":{"line":342,"column":null}},"line":198},"5":{"name":"(anonymous_5)","decl":{"start":{"line":350,"column":2},"end":{"line":350,"column":28}},"loc":{"start":{"line":350,"column":78},"end":{"line":371,"column":null}},"line":350}},"branchMap":{"0":{"loc":{"start":{"line":13,"column":15},"end":{"line":13,"column":null}},"type":"cond-expr","locations":[{"start":{"line":13,"column":43},"end":{"line":13,"column":56}},{"start":{"line":13,"column":56},"end":{"line":13,"column":null}}],"line":13},"1":{"loc":{"start":{"line":27,"column":4},"end":{"line":28,"column":null}},"type":"if","locations":[{"start":{"line":27,"column":4},"end":{"line":28,"column":null}},{"start":{},"end":{}}],"line":27},"2":{"loc":{"start":{"line":32,"column":4},"end":{"line":32,"column":null}},"type":"if","locations":[{"start":{"line":32,"column":4},"end":{"line":32,"column":null}},{"start":{},"end":{}}],"line":32},"3":{"loc":{"start":{"line":47,"column":4},"end":{"line":48,"column":null}},"type":"if","locations":[{"start":{"line":47,"column":4},"end":{"line":48,"column":null}},{"start":{},"end":{}}],"line":47},"4":{"loc":{"start":{"line":89,"column":4},"end":{"line":90,"column":null}},"type":"if","locations":[{"start":{"line":89,"column":4},"end":{"line":90,"column":null}},{"start":{},"end":{}}],"line":89},"5":{"loc":{"start":{"line":108,"column":4},"end":{"line":109,"column":null}},"type":"if","locations":[{"start":{"line":108,"column":4},"end":{"line":109,"column":null}},{"start":{},"end":{}}],"line":108},"6":{"loc":{"start":{"line":108,"column":8},"end":{"line":108,"column":69}},"type":"binary-expr","locations":[{"start":{"line":108,"column":8},"end":{"line":108,"column":18}},{"start":{"line":108,"column":18},"end":{"line":108,"column":48}},{"start":{"line":108,"column":48},"end":{"line":108,"column":69}}],"line":108},"7":{"loc":{"start":{"line":162,"column":4},"end":{"line":185,"column":null}},"type":"if","locations":[{"start":{"line":162,"column":4},"end":{"line":185,"column":null}},{"start":{"line":175,"column":11},"end":{"line":185,"column":null}}],"line":162},"8":{"loc":{"start":{"line":357,"column":29},"end":{"line":357,"column":null}},"type":"cond-expr","locations":[{"start":{"line":357,"column":38},"end":{"line":357,"column":56}},{"start":{"line":357,"column":56},"end":{"line":357,"column":null}}],"line":357},"9":{"loc":{"start":{"line":358,"column":25},"end":{"line":358,"column":null}},"type":"cond-expr","locations":[{"start":{"line":358,"column":34},"end":{"line":358,"column":46}},{"start":{"line":358,"column":46},"end":{"line":358,"column":null}}],"line":358}},"s":{"0":1,"1":1,"2":1,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":0,"31":0,"32":0,"33":0,"34":0,"35":0,"36":0,"37":0,"38":0,"39":0,"40":1,"41":2,"42":2,"43":1,"44":1,"45":0,"46":0,"47":0,"48":0,"49":0,"50":0,"51":0,"52":0,"53":0},"f":{"0":0,"1":0,"2":1,"3":2,"4":0,"5":0},"b":{"0":[0,1],"1":[0,0],"2":[0,0],"3":[0,0],"4":[0,0],"5":[0,0],"6":[0,0,0],"7":[1,1],"8":[0,0],"9":[0,0]},"meta":{"lastBranch":10,"lastFunction":6,"lastStatement":54,"seen":{"s:10:6:10:41":0,"s:12:19:12:Infinity":1,"s:13:15:13:Infinity":2,"b:13:43:13:56:13:56:13:Infinity":0,"f:19:8:19:19":0,"s:20:4:20:Infinity":3,"s:21:31:24:Infinity":4,"b:27:4:28:Infinity:undefined:undefined:undefined:undefined":1,"s:27:4:28:Infinity":5,"s:28:6:28:Infinity":6,"s:31:20:31:33":7,"b:32:4:32:Infinity:undefined:undefined:undefined:undefined":2,"s:32:4:32:Infinity":8,"s:32:18:32:Infinity":9,"s:34:4:38:Infinity":10,"s:35:6:35:Infinity":11,"s:37:6:37:Infinity":12,"s:38:6:38:Infinity":13,"f:46:8:46:27":1,"b:47:4:48:Infinity:undefined:undefined:undefined:undefined":3,"s:47:4:48:Infinity":14,"s:48:6:48:Infinity":15,"s:51:16:51:35":16,"s:52:22:52:64":17,"s:53:23:53:67":18,"s:56:25:56:55":19,"s:57:27:71:8":20,"s:73:4:73:Infinity":21,"s:76:25:76:Infinity":22,"s:78:4:78:Infinity":23,"s:79:4:86:Infinity":24,"s:80:6:84:Infinity":25,"s:86:6:86:Infinity":26,"s:86:12:86:45":27,"b:89:4:90:Infinity:undefined:undefined:undefined:undefined":4,"s:89:4:90:Infinity":28,"s:90:6:90:Infinity":29,"s:94:4:98:Infinity":30,"s:95:6:95:Infinity":31,"s:96:6:96:Infinity":32,"s:98:6:98:Infinity":33,"s:102:4:105:Infinity":34,"s:103:6:103:Infinity":35,"s:105:6:105:Infinity":36,"b:108:4:109:Infinity:undefined:undefined:undefined:undefined":5,"s:108:4:109:Infinity":37,"b:108:8:108:18:108:18:108:48:108:48:108:69":6,"s:109:6:109:Infinity":38,"s:112:4:112:Infinity":39,"f:119:2:119:22":2,"s:121:4:152:Infinity":40,"f:160:2:160:24":3,"s:161:19:161:Infinity":41,"b:162:4:185:Infinity:175:11:185:Infinity":7,"s:162:4:185:Infinity":42,"s:163:6:174:Infinity":43,"s:176:6:185:Infinity":44,"f:198:2:198:30":4,"s:199:4:342:Infinity":45,"f:350:2:350:28":5,"s:351:22:351:55":46,"s:352:26:352:65":47,"s:353:20:353:60":48,"s:354:22:354:48":49,"s:355:25:355:Infinity":50,"s:357:29:357:Infinity":51,"b:357:38:357:56:357:56:357:Infinity":8,"s:358:25:358:Infinity":52,"b:358:34:358:46:358:46:358:Infinity":9,"s:359:4:371:Infinity":53}}} -,"E:\\workspaces\\windows-apps\\ContextMaster\\.claude\\worktrees\\elastic-swirles\\src\\main\\services\\RegistryService.ts": {"path":"E:\\workspaces\\windows-apps\\ContextMaster\\.claude\\worktrees\\elastic-swirles\\src\\main\\services\\RegistryService.ts","statementMap":{"0":{"start":{"line":7,"column":56},"end":{"line":14,"column":null}},"1":{"start":{"line":17,"column":55},"end":{"line":24,"column":null}},"2":{"start":{"line":27,"column":20},"end":{"line":27,"column":null}},"3":{"start":{"line":43,"column":25},"end":{"line":43,"column":51}},"4":{"start":{"line":44,"column":26},"end":{"line":44,"column":null}},"5":{"start":{"line":45,"column":19},"end":{"line":45,"column":null}},"6":{"start":{"line":48,"column":4},"end":{"line":48,"column":null}},"7":{"start":{"line":55,"column":21},"end":{"line":55,"column":null}},"8":{"start":{"line":56,"column":24},"end":{"line":56,"column":null}},"9":{"start":{"line":57,"column":4},"end":{"line":86,"column":null}},"10":{"start":{"line":59,"column":21},"end":{"line":59,"column":58}},"11":{"start":{"line":60,"column":18},"end":{"line":60,"column":64}},"12":{"start":{"line":61,"column":20},"end":{"line":61,"column":64}},"13":{"start":{"line":64,"column":42},"end":{"line":64,"column":44}},"14":{"start":{"line":65,"column":6},"end":{"line":70,"column":null}},"15":{"start":{"line":66,"column":30},"end":{"line":66,"column":78}},"16":{"start":{"line":67,"column":27},"end":{"line":67,"column":80}},"17":{"start":{"line":68,"column":8},"end":{"line":68,"column":null}},"18":{"start":{"line":70,"column":8},"end":{"line":70,"column":null}},"19":{"start":{"line":73,"column":6},"end":{"line":83,"column":null}},"20":{"start":{"line":73,"column":53},"end":{"line":83,"column":9}},"21":{"start":{"line":85,"column":6},"end":{"line":85,"column":null}},"22":{"start":{"line":86,"column":6},"end":{"line":86,"column":null}},"23":{"start":{"line":96,"column":4},"end":{"line":112,"column":null}},"24":{"start":{"line":97,"column":6},"end":{"line":104,"column":null}},"25":{"start":{"line":98,"column":23},"end":{"line":98,"column":78}},"26":{"start":{"line":99,"column":8},"end":{"line":99,"column":null}},"27":{"start":{"line":100,"column":8},"end":{"line":100,"column":null}},"28":{"start":{"line":102,"column":23},"end":{"line":102,"column":74}},"29":{"start":{"line":103,"column":8},"end":{"line":103,"column":null}},"30":{"start":{"line":104,"column":8},"end":{"line":104,"column":null}},"31":{"start":{"line":107,"column":6},"end":{"line":108,"column":null}},"32":{"start":{"line":108,"column":8},"end":{"line":108,"column":null}},"33":{"start":{"line":110,"column":6},"end":{"line":112,"column":null}},"34":{"start":{"line":120,"column":4},"end":{"line":120,"column":null}},"35":{"start":{"line":121,"column":4},"end":{"line":122,"column":null}},"36":{"start":{"line":122,"column":6},"end":{"line":122,"column":null}},"37":{"start":{"line":124,"column":4},"end":{"line":124,"column":null}},"38":{"start":{"line":131,"column":4},"end":{"line":131,"column":null}},"39":{"start":{"line":131,"column":29},"end":{"line":131,"column":null}},"40":{"start":{"line":132,"column":4},"end":{"line":132,"column":null}},"41":{"start":{"line":133,"column":4},"end":{"line":139,"column":null}},"42":{"start":{"line":134,"column":6},"end":{"line":135,"column":null}},"43":{"start":{"line":135,"column":8},"end":{"line":135,"column":null}},"44":{"start":{"line":138,"column":6},"end":{"line":138,"column":null}},"45":{"start":{"line":139,"column":6},"end":{"line":139,"column":null}},"46":{"start":{"line":147,"column":4},"end":{"line":147,"column":null}},"47":{"start":{"line":148,"column":4},"end":{"line":148,"column":null}},"48":{"start":{"line":155,"column":4},"end":{"line":155,"column":null}},"49":{"start":{"line":159,"column":4},"end":{"line":164,"column":null}},"50":{"start":{"line":160,"column":21},"end":{"line":160,"column":76}},"51":{"start":{"line":161,"column":6},"end":{"line":161,"column":null}},"52":{"start":{"line":163,"column":21},"end":{"line":163,"column":72}},"53":{"start":{"line":164,"column":6},"end":{"line":164,"column":null}},"54":{"start":{"line":170,"column":4},"end":{"line":170,"column":null}},"55":{"start":{"line":174,"column":4},"end":{"line":174,"column":null}},"56":{"start":{"line":178,"column":4},"end":{"line":178,"column":null}},"57":{"start":{"line":178,"column":33},"end":{"line":178,"column":null}},"58":{"start":{"line":179,"column":4},"end":{"line":179,"column":null}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":47,"column":2},"end":{"line":47,"column":14}},"loc":{"start":{"line":47,"column":36},"end":{"line":48,"column":null}},"line":47},"1":{"name":"(anonymous_1)","decl":{"start":{"line":54,"column":8},"end":{"line":54,"column":21}},"loc":{"start":{"line":54,"column":65},"end":{"line":86,"column":null}},"line":54},"2":{"name":"(anonymous_2)","decl":{"start":{"line":73,"column":41},"end":{"line":73,"column":46}},"loc":{"start":{"line":73,"column":53},"end":{"line":83,"column":9}},"line":73},"3":{"name":"(anonymous_3)","decl":{"start":{"line":95,"column":8},"end":{"line":95,"column":23}},"loc":{"start":{"line":95,"column":100},"end":{"line":112,"column":null}},"line":95},"4":{"name":"(anonymous_4)","decl":{"start":{"line":119,"column":2},"end":{"line":119,"column":22}},"loc":{"start":{"line":119,"column":87},"end":{"line":124,"column":null}},"line":119},"5":{"name":"(anonymous_5)","decl":{"start":{"line":130,"column":8},"end":{"line":130,"column":34}},"loc":{"start":{"line":130,"column":34},"end":{"line":139,"column":null}},"line":130},"6":{"name":"(anonymous_6)","decl":{"start":{"line":146,"column":2},"end":{"line":146,"column":28}},"loc":{"start":{"line":146,"column":28},"end":{"line":148,"column":null}},"line":146},"7":{"name":"(anonymous_7)","decl":{"start":{"line":154,"column":2},"end":{"line":154,"column":22}},"loc":{"start":{"line":154,"column":48},"end":{"line":155,"column":null}},"line":154},"8":{"name":"(anonymous_8)","decl":{"start":{"line":158,"column":16},"end":{"line":158,"column":39}},"loc":{"start":{"line":158,"column":93},"end":{"line":164,"column":null}},"line":158},"9":{"name":"(anonymous_9)","decl":{"start":{"line":169,"column":2},"end":{"line":169,"column":10}},"loc":{"start":{"line":169,"column":54},"end":{"line":170,"column":null}},"line":169},"10":{"name":"(anonymous_10)","decl":{"start":{"line":173,"column":0},"end":{"line":173,"column":8}},"loc":{"start":{"line":173,"column":48},"end":{"line":174,"column":null}},"line":173},"11":{"name":"(anonymous_11)","decl":{"start":{"line":177,"column":2},"end":{"line":177,"column":10}},"loc":{"start":{"line":177,"column":57},"end":{"line":179,"column":null}},"line":177}},"branchMap":{"0":{"loc":{"start":{"line":61,"column":20},"end":{"line":61,"column":64}},"type":"cond-expr","locations":[{"start":{"line":61,"column":41},"end":{"line":61,"column":48}},{"start":{"line":61,"column":48},"end":{"line":61,"column":64}}],"line":61},"1":{"loc":{"start":{"line":61,"column":48},"end":{"line":61,"column":64}},"type":"cond-expr","locations":[{"start":{"line":61,"column":54},"end":{"line":61,"column":59}},{"start":{"line":61,"column":62},"end":{"line":61,"column":64}}],"line":61},"2":{"loc":{"start":{"line":68,"column":23},"end":{"line":68,"column":95}},"type":"cond-expr","locations":[{"start":{"line":68,"column":51},"end":{"line":68,"column":65}},{"start":{"line":68,"column":65},"end":{"line":68,"column":95}}],"line":68},"3":{"loc":{"start":{"line":68,"column":65},"end":{"line":68,"column":95}},"type":"cond-expr","locations":[{"start":{"line":68,"column":78},"end":{"line":68,"column":90}},{"start":{"line":68,"column":93},"end":{"line":68,"column":95}}],"line":68},"4":{"loc":{"start":{"line":79,"column":16},"end":{"line":79,"column":58}},"type":"binary-expr","locations":[{"start":{"line":79,"column":16},"end":{"line":79,"column":28}},{"start":{"line":79,"column":28},"end":{"line":79,"column":58}}],"line":79},"5":{"loc":{"start":{"line":97,"column":6},"end":{"line":104,"column":null}},"type":"if","locations":[{"start":{"line":97,"column":6},"end":{"line":104,"column":null}},{"start":{"line":101,"column":13},"end":{"line":104,"column":null}}],"line":97},"6":{"loc":{"start":{"line":107,"column":6},"end":{"line":108,"column":null}},"type":"if","locations":[{"start":{"line":107,"column":6},"end":{"line":108,"column":null}},{"start":{},"end":{}}],"line":107},"7":{"loc":{"start":{"line":131,"column":4},"end":{"line":131,"column":null}},"type":"if","locations":[{"start":{"line":131,"column":4},"end":{"line":131,"column":null}},{"start":{},"end":{}}],"line":131},"8":{"loc":{"start":{"line":159,"column":4},"end":{"line":164,"column":null}},"type":"if","locations":[{"start":{"line":159,"column":4},"end":{"line":164,"column":null}},{"start":{"line":162,"column":11},"end":{"line":164,"column":null}}],"line":159},"9":{"loc":{"start":{"line":170,"column":11},"end":{"line":170,"column":89}},"type":"binary-expr","locations":[{"start":{"line":170,"column":11},"end":{"line":170,"column":42}},{"start":{"line":170,"column":46},"end":{"line":170,"column":89}}],"line":170},"10":{"loc":{"start":{"line":174,"column":11},"end":{"line":174,"column":null}},"type":"binary-expr","locations":[{"start":{"line":174,"column":11},"end":{"line":174,"column":25}},{"start":{"line":174,"column":25},"end":{"line":174,"column":null}}],"line":174},"11":{"loc":{"start":{"line":178,"column":4},"end":{"line":178,"column":null}},"type":"if","locations":[{"start":{"line":178,"column":4},"end":{"line":178,"column":null}},{"start":{},"end":{}}],"line":178}},"s":{"0":1,"1":1,"2":1,"3":4,"4":4,"5":4,"6":4,"7":2,"8":2,"9":2,"10":2,"11":2,"12":2,"13":2,"14":2,"15":2,"16":2,"17":2,"18":0,"19":2,"20":1,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":0,"31":0,"32":0,"33":0,"34":2,"35":2,"36":3,"37":2,"38":0,"39":0,"40":0,"41":0,"42":0,"43":0,"44":0,"45":0,"46":1,"47":1,"48":0,"49":0,"50":0,"51":0,"52":0,"53":0,"54":0,"55":0,"56":1,"57":0,"58":1},"f":{"0":4,"1":2,"2":1,"3":0,"4":2,"5":0,"6":1,"7":0,"8":0,"9":0,"10":0,"11":1},"b":{"0":[2,0],"1":[0,0],"2":[2,0],"3":[0,0],"4":[1,0],"5":[0,0],"6":[0,0],"7":[0,0],"8":[0,0],"9":[0,0],"10":[0,0],"11":[0,1]},"meta":{"lastBranch":12,"lastFunction":12,"lastStatement":59,"seen":{"s:7:56:14:Infinity":0,"s:17:55:24:Infinity":1,"s:27:20:27:Infinity":2,"s:43:25:43:51":3,"s:44:26:44:Infinity":4,"s:45:19:45:Infinity":5,"f:47:2:47:14":0,"s:48:4:48:Infinity":6,"f:54:8:54:21":1,"s:55:21:55:Infinity":7,"s:56:24:56:Infinity":8,"s:57:4:86:Infinity":9,"s:59:21:59:58":10,"s:60:18:60:64":11,"s:61:20:61:64":12,"b:61:41:61:48:61:48:61:64":0,"b:61:54:61:59:61:62:61:64":1,"s:64:42:64:44":13,"s:65:6:70:Infinity":14,"s:66:30:66:78":15,"s:67:27:67:80":16,"s:68:8:68:Infinity":17,"b:68:51:68:65:68:65:68:95":2,"b:68:78:68:90:68:93:68:95":3,"s:70:8:70:Infinity":18,"s:73:6:83:Infinity":19,"f:73:41:73:46":2,"s:73:53:83:9":20,"b:79:16:79:28:79:28:79:58":4,"s:85:6:85:Infinity":21,"s:86:6:86:Infinity":22,"f:95:8:95:23":3,"s:96:4:112:Infinity":23,"b:97:6:104:Infinity:101:13:104:Infinity":5,"s:97:6:104:Infinity":24,"s:98:23:98:78":25,"s:99:8:99:Infinity":26,"s:100:8:100:Infinity":27,"s:102:23:102:74":28,"s:103:8:103:Infinity":29,"s:104:8:104:Infinity":30,"b:107:6:108:Infinity:undefined:undefined:undefined:undefined":6,"s:107:6:108:Infinity":31,"s:108:8:108:Infinity":32,"s:110:6:112:Infinity":33,"f:119:2:119:22":4,"s:120:4:120:Infinity":34,"s:121:4:122:Infinity":35,"s:122:6:122:Infinity":36,"s:124:4:124:Infinity":37,"f:130:8:130:34":5,"b:131:4:131:Infinity:undefined:undefined:undefined:undefined":7,"s:131:4:131:Infinity":38,"s:131:29:131:Infinity":39,"s:132:4:132:Infinity":40,"s:133:4:139:Infinity":41,"s:134:6:135:Infinity":42,"s:135:8:135:Infinity":43,"s:138:6:138:Infinity":44,"s:139:6:139:Infinity":45,"f:146:2:146:28":6,"s:147:4:147:Infinity":46,"s:148:4:148:Infinity":47,"f:154:2:154:22":7,"s:155:4:155:Infinity":48,"f:158:16:158:39":8,"b:159:4:164:Infinity:162:11:164:Infinity":8,"s:159:4:164:Infinity":49,"s:160:21:160:76":50,"s:161:6:161:Infinity":51,"s:163:21:163:72":52,"s:164:6:164:Infinity":53,"f:169:2:169:10":9,"s:170:4:170:Infinity":54,"b:170:11:170:42:170:46:170:89":9,"f:173:0:173:8":10,"s:174:4:174:Infinity":55,"b:174:11:174:25:174:25:174:Infinity":10,"f:177:2:177:10":11,"b:178:4:178:Infinity:undefined:undefined:undefined:undefined":11,"s:178:4:178:Infinity":56,"s:178:33:178:Infinity":57,"s:179:4:179:Infinity":58}}} -,"E:\\workspaces\\windows-apps\\ContextMaster\\.claude\\worktrees\\elastic-swirles\\src\\main\\utils\\AdminHelper.ts": {"path":"E:\\workspaces\\windows-apps\\ContextMaster\\.claude\\worktrees\\elastic-swirles\\src\\main\\utils\\AdminHelper.ts","statementMap":{"0":{"start":{"line":6,"column":34},"end":{"line":6,"column":null}},"1":{"start":{"line":14,"column":2},"end":{"line":14,"column":null}},"2":{"start":{"line":14,"column":36},"end":{"line":14,"column":null}},"3":{"start":{"line":15,"column":2},"end":{"line":15,"column":null}},"4":{"start":{"line":15,"column":28},"end":{"line":15,"column":null}},"5":{"start":{"line":16,"column":2},"end":{"line":27,"column":null}},"6":{"start":{"line":17,"column":10},"end":{"line":24,"column":23}},"7":{"start":{"line":25,"column":4},"end":{"line":25,"column":null}},"8":{"start":{"line":27,"column":4},"end":{"line":27,"column":null}},"9":{"start":{"line":29,"column":2},"end":{"line":29,"column":null}},"10":{"start":{"line":37,"column":18},"end":{"line":37,"column":36}},"11":{"start":{"line":38,"column":2},"end":{"line":38,"column":null}},"12":{"start":{"line":40,"column":17},"end":{"line":40,"column":null}},"13":{"start":{"line":41,"column":2},"end":{"line":49,"column":null}},"14":{"start":{"line":45,"column":6},"end":{"line":46,"column":null}},"15":{"start":{"line":46,"column":8},"end":{"line":46,"column":null}},"16":{"start":{"line":52,"column":2},"end":{"line":52,"column":null}},"17":{"start":{"line":52,"column":19},"end":{"line":52,"column":29}}},"fnMap":{"0":{"name":"isAdmin","decl":{"start":{"line":13,"column":16},"end":{"line":13,"column":35}},"loc":{"start":{"line":13,"column":35},"end":{"line":29,"column":null}},"line":13},"1":{"name":"restartAsAdmin","decl":{"start":{"line":36,"column":16},"end":{"line":36,"column":39}},"loc":{"start":{"line":36,"column":39},"end":{"line":52,"column":null}},"line":36},"2":{"name":"(anonymous_2)","decl":{"start":{"line":43,"column":57},"end":{"line":43,"column":null}},"loc":{"start":{"line":44,"column":13},"end":{"line":49,"column":null}},"line":44},"3":{"name":"(anonymous_3)","decl":{"start":{"line":52,"column":2},"end":{"line":52,"column":19}},"loc":{"start":{"line":52,"column":19},"end":{"line":52,"column":29}},"line":52}},"branchMap":{"0":{"loc":{"start":{"line":14,"column":2},"end":{"line":14,"column":null}},"type":"if","locations":[{"start":{"line":14,"column":2},"end":{"line":14,"column":null}},{"start":{},"end":{}}],"line":14},"1":{"loc":{"start":{"line":15,"column":2},"end":{"line":15,"column":null}},"type":"if","locations":[{"start":{"line":15,"column":2},"end":{"line":15,"column":null}},{"start":{},"end":{}}],"line":15},"2":{"loc":{"start":{"line":45,"column":6},"end":{"line":46,"column":null}},"type":"if","locations":[{"start":{"line":45,"column":6},"end":{"line":46,"column":null}},{"start":{},"end":{}}],"line":45}},"s":{"0":9,"1":8,"2":1,"3":7,"4":3,"5":4,"6":4,"7":4,"8":1,"9":4,"10":3,"11":3,"12":3,"13":3,"14":2,"15":1,"16":3,"17":1},"f":{"0":8,"1":3,"2":2,"3":1},"b":{"0":[1,7],"1":[3,4],"2":[1,1]},"meta":{"lastBranch":3,"lastFunction":4,"lastStatement":18,"seen":{"s:6:34:6:Infinity":0,"f:13:16:13:35":0,"b:14:2:14:Infinity:undefined:undefined:undefined:undefined":0,"s:14:2:14:Infinity":1,"s:14:36:14:Infinity":2,"b:15:2:15:Infinity:undefined:undefined:undefined:undefined":1,"s:15:2:15:Infinity":3,"s:15:28:15:Infinity":4,"s:16:2:27:Infinity":5,"s:17:10:24:23":6,"s:25:4:25:Infinity":7,"s:27:4:27:Infinity":8,"s:29:2:29:Infinity":9,"f:36:16:36:39":1,"s:37:18:37:36":10,"s:38:2:38:Infinity":11,"s:40:17:40:Infinity":12,"s:41:2:49:Infinity":13,"f:43:57:43:Infinity":2,"b:45:6:46:Infinity:undefined:undefined:undefined:undefined":2,"s:45:6:46:Infinity":14,"s:46:8:46:Infinity":15,"s:52:2:52:Infinity":16,"f:52:2:52:19":3,"s:52:19:52:29":17}}} -,"E:\\workspaces\\windows-apps\\ContextMaster\\.claude\\worktrees\\elastic-swirles\\src\\main\\utils\\ipcWrapper.ts": {"path":"E:\\workspaces\\windows-apps\\ContextMaster\\.claude\\worktrees\\elastic-swirles\\src\\main\\utils\\ipcWrapper.ts","statementMap":{"0":{"start":{"line":13,"column":2},"end":{"line":20,"column":null}},"1":{"start":{"line":14,"column":4},"end":{"line":20,"column":null}},"2":{"start":{"line":15,"column":19},"end":{"line":15,"column":36}},"3":{"start":{"line":16,"column":6},"end":{"line":16,"column":null}},"4":{"start":{"line":18,"column":18},"end":{"line":18,"column":60}},"5":{"start":{"line":19,"column":6},"end":{"line":19,"column":null}},"6":{"start":{"line":20,"column":6},"end":{"line":20,"column":null}}},"fnMap":{"0":{"name":"wrapHandler","decl":{"start":{"line":9,"column":16},"end":{"line":9,"column":null}},"loc":{"start":{"line":11,"column":45},"end":{"line":20,"column":null}},"line":11},"1":{"name":"(anonymous_1)","decl":{"start":{"line":13,"column":9},"end":{"line":13,"column":16}},"loc":{"start":{"line":13,"column":58},"end":{"line":20,"column":null}},"line":13}},"branchMap":{"0":{"loc":{"start":{"line":18,"column":18},"end":{"line":18,"column":60}},"type":"cond-expr","locations":[{"start":{"line":18,"column":39},"end":{"line":18,"column":51}},{"start":{"line":18,"column":51},"end":{"line":18,"column":60}}],"line":18}},"s":{"0":8,"1":8,"2":8,"3":1,"4":7,"5":7,"6":7},"f":{"0":8,"1":8},"b":{"0":[2,5]},"meta":{"lastBranch":1,"lastFunction":2,"lastStatement":7,"seen":{"f:9:16:9:Infinity":0,"s:13:2:20:Infinity":0,"f:13:9:13:16":1,"s:14:4:20:Infinity":1,"s:15:19:15:36":2,"s:16:6:16:Infinity":3,"s:18:18:18:60":4,"b:18:39:18:51:18:51:18:60":0,"s:19:6:19:Infinity":5,"s:20:6:20:Infinity":6}}} -,"E:\\workspaces\\windows-apps\\ContextMaster\\.claude\\worktrees\\elastic-swirles\\src\\main\\utils\\logger.ts": {"path":"E:\\workspaces\\windows-apps\\ContextMaster\\.claude\\worktrees\\elastic-swirles\\src\\main\\utils\\logger.ts","statementMap":{"0":{"start":{"line":6,"column":2},"end":{"line":7,"column":null}},"1":{"start":{"line":7,"column":4},"end":{"line":7,"column":58}},"2":{"start":{"line":8,"column":2},"end":{"line":8,"column":null}},"3":{"start":{"line":9,"column":2},"end":{"line":9,"column":null}},"4":{"start":{"line":10,"column":2},"end":{"line":10,"column":null}},"5":{"start":{"line":14,"column":2},"end":{"line":14,"column":null}}},"fnMap":{"0":{"name":"initLogger","decl":{"start":{"line":5,"column":16},"end":{"line":5,"column":35}},"loc":{"start":{"line":5,"column":35},"end":{"line":10,"column":null}},"line":5},"1":{"name":"(anonymous_1)","decl":{"start":{"line":6,"column":22},"end":{"line":6,"column":null}},"loc":{"start":{"line":7,"column":4},"end":{"line":7,"column":58}},"line":7},"2":{"name":"getLogDir","decl":{"start":{"line":13,"column":16},"end":{"line":13,"column":36}},"loc":{"start":{"line":13,"column":36},"end":{"line":14,"column":null}},"line":13}},"branchMap":{},"s":{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0},"f":{"0":0,"1":0,"2":0},"b":{},"meta":{"lastBranch":0,"lastFunction":3,"lastStatement":6,"seen":{"f:5:16:5:35":0,"s:6:2:7:Infinity":0,"f:6:22:6:Infinity":1,"s:7:4:7:58":1,"s:8:2:8:Infinity":2,"s:9:2:9:Infinity":3,"s:10:2:10:Infinity":4,"f:13:16:13:36":2,"s:14:2:14:Infinity":5}}} -,"E:\\workspaces\\windows-apps\\ContextMaster\\.claude\\worktrees\\elastic-swirles\\src\\shared\\enums.ts": {"path":"E:\\workspaces\\windows-apps\\ContextMaster\\.claude\\worktrees\\elastic-swirles\\src\\shared\\enums.ts","statementMap":{"0":{"start":{"line":1,"column":7},"end":{"line":8,"column":null}},"1":{"start":{"line":2,"column":2},"end":{"line":2,"column":null}},"2":{"start":{"line":3,"column":2},"end":{"line":3,"column":null}},"3":{"start":{"line":4,"column":2},"end":{"line":4,"column":null}},"4":{"start":{"line":5,"column":2},"end":{"line":5,"column":null}},"5":{"start":{"line":6,"column":2},"end":{"line":6,"column":null}},"6":{"start":{"line":7,"column":2},"end":{"line":7,"column":null}},"7":{"start":{"line":10,"column":7},"end":{"line":14,"column":null}},"8":{"start":{"line":11,"column":2},"end":{"line":11,"column":null}},"9":{"start":{"line":12,"column":2},"end":{"line":12,"column":null}},"10":{"start":{"line":13,"column":2},"end":{"line":13,"column":null}},"11":{"start":{"line":16,"column":7},"end":{"line":24,"column":null}},"12":{"start":{"line":17,"column":2},"end":{"line":17,"column":null}},"13":{"start":{"line":18,"column":2},"end":{"line":18,"column":null}},"14":{"start":{"line":19,"column":2},"end":{"line":19,"column":null}},"15":{"start":{"line":20,"column":2},"end":{"line":20,"column":null}},"16":{"start":{"line":21,"column":2},"end":{"line":21,"column":null}},"17":{"start":{"line":22,"column":2},"end":{"line":22,"column":null}},"18":{"start":{"line":23,"column":2},"end":{"line":23,"column":null}},"19":{"start":{"line":26,"column":7},"end":{"line":29,"column":null}},"20":{"start":{"line":27,"column":2},"end":{"line":27,"column":null}},"21":{"start":{"line":28,"column":2},"end":{"line":28,"column":null}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":1,"column":7},"end":{"line":1,"column":12}},"loc":{"start":{"line":1,"column":7},"end":{"line":8,"column":null}},"line":1},"1":{"name":"(anonymous_1)","decl":{"start":{"line":10,"column":7},"end":{"line":10,"column":12}},"loc":{"start":{"line":10,"column":7},"end":{"line":14,"column":null}},"line":10},"2":{"name":"(anonymous_2)","decl":{"start":{"line":16,"column":7},"end":{"line":16,"column":12}},"loc":{"start":{"line":16,"column":7},"end":{"line":24,"column":null}},"line":16},"3":{"name":"(anonymous_3)","decl":{"start":{"line":26,"column":7},"end":{"line":26,"column":12}},"loc":{"start":{"line":26,"column":7},"end":{"line":29,"column":null}},"line":26}},"branchMap":{},"s":{"0":6,"1":6,"2":6,"3":6,"4":6,"5":6,"6":6,"7":6,"8":6,"9":6,"10":6,"11":6,"12":6,"13":6,"14":6,"15":6,"16":6,"17":6,"18":6,"19":6,"20":6,"21":6},"f":{"0":6,"1":6,"2":6,"3":6},"b":{},"meta":{"lastBranch":0,"lastFunction":4,"lastStatement":22,"seen":{"s:1:7:8:Infinity":0,"f:1:7:1:12":0,"s:2:2:2:Infinity":1,"s:3:2:3:Infinity":2,"s:4:2:4:Infinity":3,"s:5:2:5:Infinity":4,"s:6:2:6:Infinity":5,"s:7:2:7:Infinity":6,"s:10:7:14:Infinity":7,"f:10:7:10:12":1,"s:11:2:11:Infinity":8,"s:12:2:12:Infinity":9,"s:13:2:13:Infinity":10,"s:16:7:24:Infinity":11,"f:16:7:16:12":2,"s:17:2:17:Infinity":12,"s:18:2:18:Infinity":13,"s:19:2:19:Infinity":14,"s:20:2:20:Infinity":15,"s:21:2:21:Infinity":16,"s:22:2:22:Infinity":17,"s:23:2:23:Infinity":18,"s:26:7:29:Infinity":19,"f:26:7:26:12":3,"s:27:2:27:Infinity":20,"s:28:2:28:Infinity":21}}} -,"E:\\workspaces\\windows-apps\\ContextMaster\\.claude\\worktrees\\elastic-swirles\\src\\shared\\ipc-channels.ts": {"path":"E:\\workspaces\\windows-apps\\ContextMaster\\.claude\\worktrees\\elastic-swirles\\src\\shared\\ipc-channels.ts","statementMap":{"0":{"start":{"line":2,"column":19},"end":{"line":33,"column":null}}},"fnMap":{},"branchMap":{},"s":{"0":1},"f":{},"b":{},"meta":{"lastBranch":0,"lastFunction":0,"lastStatement":1,"seen":{"s:2:19:33:Infinity":0}}} -} diff --git a/coverage/favicon.png b/coverage/favicon.png deleted file mode 100644 index c1525b811a167671e9de1fa78aab9f5c0b61cef7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 445 zcmV;u0Yd(XP))rP{nL}Ln%S7`m{0DjX9TLF* zFCb$4Oi7vyLOydb!7n&^ItCzb-%BoB`=x@N2jll2Nj`kauio%aw_@fe&*}LqlFT43 z8doAAe))z_%=P%v^@JHp3Hjhj^6*Kr_h|g_Gr?ZAa&y>wxHE99Gk>A)2MplWz2xdG zy8VD2J|Uf#EAw*bo5O*PO_}X2Tob{%bUoO2G~T`@%S6qPyc}VkhV}UifBuRk>%5v( z)x7B{I~z*k<7dv#5tC+m{km(D087J4O%+<<;K|qwefb6@GSX45wCK}Sn*> - - - - Code coverage report for All files - - - - - - - - - -
-
-

All files

-
- -
- 54.48% - Statements - 176/323 -
- - -
- 34.21% - Branches - 39/114 -
- - -
- 70.14% - Functions - 47/67 -
- - -
- 55.59% - Lines - 164/295 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
main/data/repositories -
-
100%20/20100%8/8100%12/12100%20/20
main/ipc -
-
100%11/1175%3/4100%4/4100%11/11
main/services -
-
40.75%97/23821.27%20/9455.26%21/3841.31%88/213
main/utils -
-
80.64%25/31100%8/866.66%6/978.57%22/28
shared -
-
100%23/23100%0/0100%4/4100%23/23
-
-
-
-
- - - - - - - \ No newline at end of file diff --git a/coverage/main/data/repositories/BackupSnapshotRepo.ts.html b/coverage/main/data/repositories/BackupSnapshotRepo.ts.html deleted file mode 100644 index c6b3bf1..0000000 --- a/coverage/main/data/repositories/BackupSnapshotRepo.ts.html +++ /dev/null @@ -1,268 +0,0 @@ - - - - - - Code coverage report for main/data/repositories/BackupSnapshotRepo.ts - - - - - - - - - -
-
-

All files / main/data/repositories BackupSnapshotRepo.ts

-
- -
- 100% - Statements - 10/10 -
- - -
- 100% - Branches - 2/2 -
- - -
- 100% - Functions - 6/6 -
- - -
- 100% - Lines - 10/10 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62  -  -  -  -  -  -  -  -  -  -  -  -  -  -6x -  -  -1x -  -  -  -  -1x -  -  -  -  -  -  -1x -  -  -  -2x -  -  -2x -  -  -  -2x -  -  -2x -  -  -  -1x -  -  -  -3x -  -  -  -  -  -  -  -  -  - 
import type Database from 'better-sqlite3';
-import { BackupSnapshot } from '../../../shared/types';
-import { BackupType } from '../../../shared/enums';
- 
-interface DbRow {
-  id: number;
-  name: string;
-  creation_time: string;
-  type: string;
-  menu_items_json: string;
-  sha256_checksum: string;
-}
- 
-export class BackupSnapshotRepo {
-  constructor(private readonly db: Database.Database) {}
- 
-  insert(snapshot: Omit<BackupSnapshot, 'id'>): BackupSnapshot {
-    const stmt = this.db.prepare(`
-      INSERT INTO backup_snapshots
-        (name, creation_time, type, menu_items_json, sha256_checksum)
-      VALUES (?, ?, ?, ?, ?)
-    `);
-    const result = stmt.run(
-      snapshot.name,
-      snapshot.creationTime,
-      snapshot.type,
-      snapshot.menuItemsJson,
-      snapshot.sha256Checksum
-    );
-    return { ...snapshot, id: result.lastInsertRowid as number };
-  }
- 
-  findAll(): BackupSnapshot[] {
-    const rows = this.db
-      .prepare('SELECT * FROM backup_snapshots ORDER BY creation_time DESC')
-      .all() as DbRow[];
-    return rows.map(this.toModel);
-  }
- 
-  findById(id: number): BackupSnapshot | null {
-    const row = this.db
-      .prepare('SELECT * FROM backup_snapshots WHERE id = ?')
-      .get(id) as DbRow | undefined;
-    return row ? this.toModel(row) : null;
-  }
- 
-  delete(id: number): void {
-    this.db.prepare('DELETE FROM backup_snapshots WHERE id = ?').run(id);
-  }
- 
-  private toModel(row: DbRow): BackupSnapshot {
-    return {
-      id: row.id,
-      name: row.name,
-      creationTime: row.creation_time,
-      type: row.type as BackupType,
-      menuItemsJson: row.menu_items_json,
-      sha256Checksum: row.sha256_checksum,
-    };
-  }
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/main/data/repositories/OperationRecordRepo.ts.html b/coverage/main/data/repositories/OperationRecordRepo.ts.html deleted file mode 100644 index c397fe5..0000000 --- a/coverage/main/data/repositories/OperationRecordRepo.ts.html +++ /dev/null @@ -1,277 +0,0 @@ - - - - - - Code coverage report for main/data/repositories/OperationRecordRepo.ts - - - - - - - - - -
-
-

All files / main/data/repositories OperationRecordRepo.ts

-
- -
- 100% - Statements - 10/10 -
- - -
- 100% - Branches - 6/6 -
- - -
- 100% - Functions - 6/6 -
- - -
- 100% - Lines - 10/10 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -6x -  -  -2x -  -  -  -  -2x -  -  -  -  -  -  -  -2x -  -  -  -1x -  -  -1x -  -  -  -2x -  -  -2x -  -  -  -1x -  -  -  -3x -  -  -  -  -  -  -  -  -  -  - 
import type Database from 'better-sqlite3';
-import { OperationRecord } from '../../../shared/types';
-import { OperationType } from '../../../shared/enums';
- 
-interface DbRow {
-  id: number;
-  timestamp: string;
-  operation_type: string;
-  target_name: string;
-  registry_path: string;
-  old_value: string | null;
-  new_value: string | null;
-}
- 
-export class OperationRecordRepo {
-  constructor(private readonly db: Database.Database) {}
- 
-  insert(record: Omit<OperationRecord, 'id'>): OperationRecord {
-    const stmt = this.db.prepare(`
-      INSERT INTO operation_records
-        (timestamp, operation_type, target_name, registry_path, old_value, new_value)
-      VALUES (?, ?, ?, ?, ?, ?)
-    `);
-    const result = stmt.run(
-      record.timestamp,
-      record.operationType,
-      record.targetEntryName,
-      record.registryPath,
-      record.oldValue ?? null,
-      record.newValue ?? null
-    );
-    return { ...record, id: result.lastInsertRowid as number };
-  }
- 
-  findAll(): OperationRecord[] {
-    const rows = this.db
-      .prepare('SELECT * FROM operation_records ORDER BY timestamp DESC')
-      .all() as DbRow[];
-    return rows.map(this.toModel);
-  }
- 
-  findById(id: number): OperationRecord | null {
-    const row = this.db
-      .prepare('SELECT * FROM operation_records WHERE id = ?')
-      .get(id) as DbRow | undefined;
-    return row ? this.toModel(row) : null;
-  }
- 
-  deleteAll(): void {
-    this.db.prepare('DELETE FROM operation_records').run();
-  }
- 
-  private toModel(row: DbRow): OperationRecord {
-    return {
-      id: row.id,
-      timestamp: row.timestamp,
-      operationType: row.operation_type as OperationType,
-      targetEntryName: row.target_name,
-      registryPath: row.registry_path,
-      oldValue: row.old_value,
-      newValue: row.new_value,
-    };
-  }
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/main/data/repositories/index.html b/coverage/main/data/repositories/index.html deleted file mode 100644 index 070a2f4..0000000 --- a/coverage/main/data/repositories/index.html +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - Code coverage report for main/data/repositories - - - - - - - - - -
-
-

All files main/data/repositories

-
- -
- 100% - Statements - 20/20 -
- - -
- 100% - Branches - 8/8 -
- - -
- 100% - Functions - 12/12 -
- - -
- 100% - Lines - 20/20 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
BackupSnapshotRepo.ts -
-
100%10/10100%2/2100%6/6100%10/10
OperationRecordRepo.ts -
-
100%10/10100%6/6100%6/6100%10/10
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/main/ipc/index.html b/coverage/main/ipc/index.html deleted file mode 100644 index 2e4f607..0000000 --- a/coverage/main/ipc/index.html +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - Code coverage report for main/ipc - - - - - - - - - -
-
-

All files main/ipc

-
- -
- 100% - Statements - 11/11 -
- - -
- 75% - Branches - 3/4 -
- - -
- 100% - Functions - 4/4 -
- - -
- 100% - Lines - 11/11 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
registry.ts -
-
100%11/1175%3/4100%4/4100%11/11
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/main/ipc/registry.ts.html b/coverage/main/ipc/registry.ts.html deleted file mode 100644 index af316a5..0000000 --- a/coverage/main/ipc/registry.ts.html +++ /dev/null @@ -1,223 +0,0 @@ - - - - - - Code coverage report for main/ipc/registry.ts - - - - - - - - - -
-
-

All files / main/ipc registry.ts

-
- -
- 100% - Statements - 11/11 -
- - -
- 75% - Branches - 3/4 -
- - -
- 100% - Functions - 4/4 -
- - -
- 100% - Lines - 11/11 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47  -  -  -  -  -  -  -  -7x -  -  -1x -  -  -  -7x -  -  -1x -  -  -  -  -  -  -  -  -  -  -1x -1x -  -  -  -7x -  -  -2x -1x -  -1x -  -2x -  -  -  - 
import { ipcMain } from 'electron';
-import { IPC } from '../../shared/ipc-channels';
-import { MenuScene, MenuItemType } from '../../shared/enums';
-import { ToggleItemParams, BatchToggleParams } from '../../shared/types';
-import { MenuManagerService } from '../services/MenuManagerService';
-import { wrapHandler } from '../utils/ipcWrapper';
- 
-export function registerRegistryHandlers(menuManager: MenuManagerService): void {
-  ipcMain.handle(
-    IPC.REGISTRY_GET_ITEMS,
-    wrapHandler((_event: unknown, scene: MenuScene) =>
-      menuManager.getMenuItems(scene)
-    )
-  );
- 
-  ipcMain.handle(
-    IPC.REGISTRY_TOGGLE,
-    wrapHandler(async (_event: unknown, params: ToggleItemParams) => {
-      const item = {
-        id: -1,
-        name: params.name,
-        command: '',
-        iconPath: null,
-        isEnabled: params.isEnabled,
-        source: '',
-        menuScene: params.menuScene,
-        registryKey: params.registryKey,
-        type: params.type ?? MenuItemType.System,
-      };
-      const result = await menuManager.toggleItem(item);
-      return { newState: !params.isEnabled, newRegistryKey: result.newRegistryKey };
-    })
-  );
- 
-  ipcMain.handle(
-    IPC.REGISTRY_BATCH,
-    wrapHandler(async (_event: unknown, params: BatchToggleParams) => {
-      if (params.enable) {
-        await menuManager.batchEnable(params.items);
-      } else {
-        await menuManager.batchDisable(params.items);
-      }
-      return true;
-    })
-  );
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/main/services/BackupService.ts.html b/coverage/main/services/BackupService.ts.html deleted file mode 100644 index 2a054e0..0000000 --- a/coverage/main/services/BackupService.ts.html +++ /dev/null @@ -1,571 +0,0 @@ - - - - - - Code coverage report for main/services/BackupService.ts - - - - - - - - - -
-
-

All files / main/services BackupService.ts

-
- -
- 18.98% - Statements - 15/79 -
- - -
- 2.85% - Branches - 1/35 -
- - -
- 36.36% - Functions - 4/11 -
- - -
- 22.38% - Lines - 15/67 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -4x -4x -4x -  -  -  -2x -2x -12x -12x -  -  -2x -2x -  -2x -  -  -  -  -  -  -  -2x -2x -2x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -1x -  -  -  -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
import { createHash } from 'crypto';
-import { promises as fs } from 'fs';
-import { dialog, BrowserWindow } from 'electron';
-import { BackupType, MenuScene, OperationType } from '../../shared/enums';
-import { BackupSnapshot, MenuItemEntry, RestoreDiffItem } from '../../shared/types';
-import { BackupSnapshotRepo } from '../data/repositories/BackupSnapshotRepo';
-import { MenuManagerService } from './MenuManagerService';
-import { OperationHistoryService } from './OperationHistoryService';
-import log from '../utils/logger';
- 
-/** 去掉 ShellExt registryKey 末段的 '-' 前缀,用于跨状态匹配 */
-function normalizeKey(key: string): string {
-  const sep = key.lastIndexOf('\\');
-  if (sep === -1) return key.replace(/^-+/, '');
-  return key.substring(0, sep + 1) + key.substring(sep + 1).replace(/^-+/, '');
-}
- 
-export class BackupService {
-  constructor(
-    private readonly repo: BackupSnapshotRepo,
-    private readonly menuManager: MenuManagerService,
-    private readonly history: OperationHistoryService
-  ) {}
- 
-  async createBackup(name: string, type = BackupType.Manual): Promise<BackupSnapshot> {
-    const allItems: MenuItemEntry[] = [];
-    for (const scene of Object.values(MenuScene)) {
-      const items = await this.menuManager.getMenuItems(scene);
-      allItems.push(...items);
-    }
- 
-    const jsonData = JSON.stringify(allItems);
-    const checksum = createHash('sha256').update(jsonData).digest('hex');
- 
-    const snapshot = this.repo.insert({
-      name,
-      creationTime: new Date().toISOString(),
-      type,
-      menuItemsJson: jsonData,
-      sha256Checksum: checksum,
-    });
- 
-    this.history.recordOperation(OperationType.Backup, name, '', '', checksum);
-    log.info(`Backup created: ${name} (${allItems.length} items)`);
-    return snapshot;
-  }
- 
-  async restoreBackup(snapshotId: number): Promise<void> {
-    const snapshot = this.repo.findById(snapshotId);
-    if (!snapshot) throw new Error('找不到备份快照');
- 
-    const expectedChecksum = createHash('sha256')
-      .update(snapshot.menuItemsJson)
-      .digest('hex');
-    if (expectedChecksum !== snapshot.sha256Checksum) {
-      throw new Error('备份校验失败,文件可能已被篡改');
-    }
- 
-    // 还原前先自动创建备份
-    await this.createBackup(
-      `AutoBackup_BeforeRestore_${new Date().toISOString().replace(/[:.]/g, '-')}`,
-      BackupType.Auto
-    );
- 
-    const itemsToRestore: MenuItemEntry[] = JSON.parse(snapshot.menuItemsJson);
-    if (!itemsToRestore.length) throw new Error('备份文件中没有有效的菜单项数据');
- 
-    const allCurrentItems: MenuItemEntry[] = [];
-    for (const scene of Object.values(MenuScene)) {
-      allCurrentItems.push(...(await this.menuManager.getMenuItems(scene)));
-    }
- 
-    const toEnable: MenuItemEntry[] = [];
-    const toDisable: MenuItemEntry[] = [];
- 
-    for (const backupItem of itemsToRestore) {
-      const current = allCurrentItems.find((i) => normalizeKey(i.registryKey) === normalizeKey(backupItem.registryKey));
-      if (current && current.isEnabled !== backupItem.isEnabled) {
-        current.isEnabled = backupItem.isEnabled;
-        if (backupItem.isEnabled) toEnable.push(current);
-        else toDisable.push(current);
-      }
-    }
- 
-    if (toEnable.length) await this.menuManager.batchEnable(toEnable);
-    if (toDisable.length) await this.menuManager.batchDisable(toDisable);
- 
-    this.history.recordOperation(OperationType.Restore, snapshot.name, '', '', snapshotId.toString());
-    log.info(`Restore completed from backup: ${snapshot.name}`);
-  }
- 
-  async deleteBackup(id: number): Promise<void> {
-    this.repo.delete(id);
-  }
- 
-  getAllBackups(): BackupSnapshot[] {
-    return this.repo.findAll();
-  }
- 
-  async previewRestoreDiff(snapshotId: number): Promise<RestoreDiffItem[]> {
-    const snapshot = this.repo.findById(snapshotId);
-    if (!snapshot) throw new Error('找不到备份快照');
- 
-    const backupItems: MenuItemEntry[] = JSON.parse(snapshot.menuItemsJson);
-    const currentItems: MenuItemEntry[] = [];
-    for (const scene of Object.values(MenuScene)) {
-      currentItems.push(...(await this.menuManager.getMenuItems(scene)));
-    }
- 
-    const diff: RestoreDiffItem[] = [];
-    for (const backupItem of backupItems) {
-      const current = currentItems.find((i) => normalizeKey(i.registryKey) === normalizeKey(backupItem.registryKey));
-      if (current && current.isEnabled !== backupItem.isEnabled) {
-        diff.push({ current, backup: backupItem });
-      }
-    }
-    return diff;
-  }
- 
-  async exportBackup(snapshotId: number, win: BrowserWindow): Promise<void> {
-    const snapshot = this.repo.findById(snapshotId);
-    if (!snapshot) throw new Error('找不到备份快照');
- 
-    const { filePath, canceled } = await dialog.showSaveDialog(win, {
-      title: '导出备份文件',
-      defaultPath: `${snapshot.name.replace(/[\\/:*?"<>|]/g, '_')}.cmbackup`,
-      filters: [{ name: 'ContextMaster Backup', extensions: ['cmbackup'] }],
-    });
- 
-    if (canceled || !filePath) return;
-    await fs.writeFile(filePath, snapshot.menuItemsJson, 'utf-8');
-    log.info(`Backup exported to: ${filePath}`);
-  }
- 
-  async importBackup(win: BrowserWindow): Promise<BackupSnapshot> {
-    const { filePaths, canceled } = await dialog.showOpenDialog(win, {
-      title: '导入备份文件',
-      filters: [
-        { name: 'ContextMaster Backup', extensions: ['cmbackup'] },
-        { name: 'JSON Files', extensions: ['json'] },
-      ],
-      properties: ['openFile'],
-    });
- 
-    if (canceled || !filePaths.length) throw new Error('未选择文件');
- 
-    const filePath = filePaths[0];
-    const jsonData = await fs.readFile(filePath, 'utf-8');
-    const checksum = createHash('sha256').update(jsonData).digest('hex');
- 
-    const snapshot = this.repo.insert({
-      name: `导入 · ${require('path').basename(filePath, '.cmbackup')}`,
-      creationTime: new Date().toISOString(),
-      type: BackupType.Manual,
-      menuItemsJson: jsonData,
-      sha256Checksum: checksum,
-    });
- 
-    log.info(`Backup imported from: ${filePath}`);
-    return snapshot;
-  }
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/main/services/MenuManagerService.ts.html b/coverage/main/services/MenuManagerService.ts.html deleted file mode 100644 index aa0a041..0000000 --- a/coverage/main/services/MenuManagerService.ts.html +++ /dev/null @@ -1,349 +0,0 @@ - - - - - - Code coverage report for main/services/MenuManagerService.ts - - - - - - - - - -
-
-

All files / main/services MenuManagerService.ts

-
- -
- 100% - Statements - 46/46 -
- - -
- 85.71% - Branches - 12/14 -
- - -
- 100% - Functions - 9/9 -
- - -
- 100% - Lines - 38/38 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89  -  -  -  -  -  -  -  -13x -13x -  -  -  -1x -  -  -  -6x -5x -4x -4x -4x -  -  -  -  -  -  -4x -4x -  -  -  -6x -5x -4x -4x -4x -  -  -  -  -  -  -4x -4x -  -  -  -2x -1x -  -1x -  -  -  -  -4x -3x -  -2x -2x -2x -3x -  -1x -  -1x -1x -  -  -  -  -4x -3x -  -2x -2x -2x -3x -  -1x -  -1x -1x -  -  -  - 
import { MenuScene, OperationType } from '../../shared/enums';
-import { MenuItemEntry } from '../../shared/types';
-import { RegistryService } from './RegistryService';
-import { OperationHistoryService } from './OperationHistoryService';
-import log from '../utils/logger';
- 
-export class MenuManagerService {
-  constructor(
-    private readonly registry: RegistryService,
-    private readonly history: OperationHistoryService
-  ) {}
- 
-  async getMenuItems(scene: MenuScene): Promise<MenuItemEntry[]> {
-    return this.registry.getMenuItems(scene);
-  }
- 
-  async enableItem(item: MenuItemEntry): Promise<{ newRegistryKey?: string }> {
-    if (item.isEnabled) return {};
-    const result = await this.registry.setItemEnabled(item.registryKey, true);
-    Iif (result.newRegistryKey) item.registryKey = result.newRegistryKey;
-    item.isEnabled = true;
-    this.history.recordOperation(
-      OperationType.Enable,
-      item.name,
-      item.registryKey,
-      'false',
-      'true'
-    );
-    log.info(`Enabled: ${item.name}`);
-    return result;
-  }
- 
-  async disableItem(item: MenuItemEntry): Promise<{ newRegistryKey?: string }> {
-    if (!item.isEnabled) return {};
-    const result = await this.registry.setItemEnabled(item.registryKey, false);
-    Iif (result.newRegistryKey) item.registryKey = result.newRegistryKey;
-    item.isEnabled = false;
-    this.history.recordOperation(
-      OperationType.Disable,
-      item.name,
-      item.registryKey,
-      'true',
-      'false'
-    );
-    log.info(`Disabled: ${item.name}`);
-    return result;
-  }
- 
-  async toggleItem(item: MenuItemEntry): Promise<{ newRegistryKey?: string }> {
-    if (item.isEnabled) {
-      return this.disableItem(item);
-    } else {
-      return this.enableItem(item);
-    }
-  }
- 
-  async batchEnable(items: MenuItemEntry[]): Promise<void> {
-    const targets = items.filter((i) => !i.isEnabled);
-    if (!targets.length) return;
- 
-    this.registry.createRollbackPoint(targets);
-    try {
-      for (const item of targets) {
-        await this.enableItem(item);
-      }
-      this.registry.commitTransaction();
-    } catch (e) {
-      await this.registry.rollback();
-      throw new Error(`批量启用失败,已回滚: ${(e as Error).message}`);
-    }
-  }
- 
-  async batchDisable(items: MenuItemEntry[]): Promise<void> {
-    const targets = items.filter((i) => i.isEnabled);
-    if (!targets.length) return;
- 
-    this.registry.createRollbackPoint(targets);
-    try {
-      for (const item of targets) {
-        await this.disableItem(item);
-      }
-      this.registry.commitTransaction();
-    } catch (e) {
-      await this.registry.rollback();
-      throw new Error(`批量禁用失败,已回滚: ${(e as Error).message}`);
-    }
-  }
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/main/services/PowerShellBridge.ts.html b/coverage/main/services/PowerShellBridge.ts.html deleted file mode 100644 index 14c7b5a..0000000 --- a/coverage/main/services/PowerShellBridge.ts.html +++ /dev/null @@ -1,1204 +0,0 @@ - - - - - - Code coverage report for main/services/PowerShellBridge.ts - - - - - - - - - -
-
-

All files / main/services PowerShellBridge.ts

-
- -
- 14.81% - Statements - 8/54 -
- - -
- 14.28% - Branches - 3/21 -
- - -
- 33.33% - Functions - 2/6 -
- - -
- 15.38% - Lines - 8/52 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214 -215 -216 -217 -218 -219 -220 -221 -222 -223 -224 -225 -226 -227 -228 -229 -230 -231 -232 -233 -234 -235 -236 -237 -238 -239 -240 -241 -242 -243 -244 -245 -246 -247 -248 -249 -250 -251 -252 -253 -254 -255 -256 -257 -258 -259 -260 -261 -262 -263 -264 -265 -266 -267 -268 -269 -270 -271 -272 -273 -274 -275 -276 -277 -278 -279 -280 -281 -282 -283 -284 -285 -286 -287 -288 -289 -290 -291 -292 -293 -294 -295 -296 -297 -298 -299 -300 -301 -302 -303 -304 -305 -306 -307 -308 -309 -310 -311 -312 -313 -314 -315 -316 -317 -318 -319 -320 -321 -322 -323 -324 -325 -326 -327 -328 -329 -330 -331 -332 -333 -334 -335 -336 -337 -338 -339 -340 -341 -342 -343 -344 -345 -346 -347 -348 -349 -350 -351 -352 -353 -354 -355 -356 -357 -358 -359 -360 -361 -362 -363 -364 -365 -366 -367 -368 -369 -370 -371 -372 -373 -374  -  -  -  -  -  -  -  -  -1x -  -1x -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -2x -2x -1x -  -  -  -  -  -  -  -  -  -  -  -  -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
import { execFile } from 'child_process';
-import { promisify } from 'util';
-import * as os from 'os';
-import * as fs from 'fs';
-import * as path from 'path';
-import * as crypto from 'crypto';
-import { isAdmin } from '../utils/AdminHelper';
-import log from '../utils/logger';
- 
-const execFileAsync = promisify(execFile);
- 
-const PWSH7_PATH = 'C:\\Program Files\\PowerShell\\7\\pwsh.exe';
-const PS_EXE = fs.existsSync(PWSH7_PATH) ? PWSH7_PATH : 'powershell.exe';
- 
-export class PowerShellBridge {
-  /**
-   * 执行 PowerShell 脚本并将 stdout 解析为 JSON
-   */
-  async execute<T>(script: string): Promise<T> {
-    log.debug('[PS] execute:', script.substring(0, 200));
-    const { stdout, stderr } = await execFileAsync(
-      PS_EXE,
-      ['-NonInteractive', '-NoProfile', '-OutputFormat', 'Text', '-Command', script],
-      { maxBuffer: 10 * 1024 * 1024, timeout: 30000 }
-    );
- 
-    if (stderr) {
-      log.warn('[PS] stderr:', stderr);
-    }
- 
-    const trimmed = stdout.trim();
-    if (!trimmed) return [] as unknown as T;
- 
-    try {
-      return JSON.parse(trimmed) as T;
-    } catch (e) {
-      log.error('[PS] JSON parse error. stdout:', trimmed.substring(0, 500));
-      throw new Error(`PowerShell 输出 JSON 解析失败: ${String(e)}`);
-    }
-  }
- 
-  /**
-   * 以提权方式执行脚本(非管理员时弹出 UAC 对话框)
-   * 管理员身份下直接 fallback 到 execute()
-   */
-  async executeElevated<T>(script: string): Promise<T> {
-    if (isAdmin()) {
-      return this.execute<T>(script);
-    }
- 
-    const uid = crypto.randomUUID();
-    const opScript  = path.join(os.tmpdir(), `cm_op_${uid}.ps1`);
-    const resultFile = path.join(os.tmpdir(), `cm_res_${uid}.json`);
- 
-    // 包装原始脚本:捕获原始 JSON 输出(脚本本身已输出 JSON),写入 resultFile
-    const resultFilePs = resultFile.replace(/'/g, "''");
-    const wrappedScript2 = `
-$ErrorActionPreference = 'Stop'
-try {
-  $__out = & {
-${script}
-  } | Out-String
-  $__out = $__out.Trim()
-  if (-not $__out) { $__out = 'null' }
-  Set-Content -LiteralPath '${resultFilePs}' -Value $__out -Encoding UTF8
-} catch {
-  $__err = (@{ __error = $_.Exception.Message } | ConvertTo-Json -Compress)
-  Set-Content -LiteralPath '${resultFilePs}' -Value $__err -Encoding UTF8
-  exit 1
-}
-`.trim();
- 
-    fs.writeFileSync(opScript, wrappedScript2, 'utf8');
- 
-    // 通过非提权 PS 启动提权子进程(弹 UAC)
-    const launchScript = `Start-Process '${PS_EXE.replace(/'/g, "''")}' -Verb RunAs -Wait -WindowStyle Hidden -ArgumentList @('-NonInteractive','-NoProfile','-ExecutionPolicy','Bypass','-File','${opScript.replace(/'/g, "''")}')`;
- 
-    log.debug('[PS] executeElevated: launching UAC process');
-    try {
-      await execFileAsync(
-        PS_EXE,
-        ['-NonInteractive', '-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', launchScript],
-        { maxBuffer: 1 * 1024 * 1024, timeout: 120000 }
-      );
-    } finally {
-      try { fs.unlinkSync(opScript); } catch { /* ignore */ }
-    }
- 
-    if (!fs.existsSync(resultFile)) {
-      throw new Error('操作已取消(UAC 提权被拒绝)');
-    }
- 
-    let resultJson: string;
-    try {
-      resultJson = fs.readFileSync(resultFile, 'utf8').trim();
-      fs.unlinkSync(resultFile);
-    } catch {
-      throw new Error('读取操作结果失败');
-    }
- 
-    let parsed: unknown;
-    try {
-      parsed = JSON.parse(resultJson);
-    } catch {
-      throw new Error(`结果 JSON 解析失败: ${resultJson.substring(0, 200)}`);
-    }
- 
-    if (parsed && typeof parsed === 'object' && '__error' in parsed) {
-      throw new Error(String((parsed as Record<string, unknown>).__error));
-    }
- 
-    return parsed as T;
-  }
- 
-  /**
-   * 构建扫描指定注册表路径下所有子键的脚本
-   * 返回 JSON 数组,每项含菜单条目信息
-   */
-  buildGetItemsScript(hkcrSubPath: string): string {
-    // PS 单对象会返回哈希表而非数组,用 @(...) 强制数组
-    return `
-$ErrorActionPreference = 'SilentlyContinue'
-New-PSDrive -Name HKCR -PSProvider Registry -Root HKEY_CLASSES_ROOT -ErrorAction SilentlyContinue | Out-Null
-$basePath = 'HKCR:\\${hkcrSubPath}'
-if (-not (Test-Path -LiteralPath $basePath)) { Write-Output '[]'; exit }
-$subKeys = Get-ChildItem -LiteralPath $basePath | Where-Object { $_.PSIsContainer }
-$result = @($subKeys | ForEach-Object {
-  $key = $_
-  $keyName = $key.PSChildName
-  $name = $key.GetValue('')
-  if (-not $name) { $name = $keyName }
-  $iconPath = $key.GetValue('Icon')
-  $isEnabled = ($key.GetValue('LegacyDisable') -eq $null)
-  $commandSubKey = Join-Path $key.PSPath 'command'
-  $command = ''
-  if (Test-Path -LiteralPath $commandSubKey) {
-    $command = (Get-Item -LiteralPath $commandSubKey).GetValue('')
-    if (-not $command) { $command = '' }
-  }
-  $regKey = '${hkcrSubPath}\\' + $keyName
-  [PSCustomObject]@{
-    name      = [string]$name
-    command   = [string]$command
-    iconPath  = if ($iconPath) { [string]$iconPath } else { $null }
-    isEnabled = [bool]$isEnabled
-    source    = ''
-    registryKey = [string]$regKey
-    subKeyName = [string]$keyName
-  }
-})
-$result | ConvertTo-Json -Compress -Depth 3
-`.trim();
-  }
- 
-  /**
-   * 构建启用/禁用单个菜单项的脚本
-   * enable=true  → Remove-ItemProperty LegacyDisable
-   * enable=false → Set-ItemProperty LegacyDisable -Value ''
-   */
-  buildSetEnabledScript(hkcrRelativeKey: string, enable: boolean): string {
-    const psPath = `HKCR:\\${hkcrRelativeKey}`;
-    if (enable) {
-      return `
-$ErrorActionPreference = 'Stop'
-New-PSDrive -Name HKCR -PSProvider Registry -Root HKEY_CLASSES_ROOT -ErrorAction SilentlyContinue | Out-Null
-$keyPath = '${psPath}'
-if (Test-Path -LiteralPath $keyPath) {
-  $prop = Get-ItemProperty -LiteralPath $keyPath -Name 'LegacyDisable' -ErrorAction SilentlyContinue
-  if ($prop -ne $null) {
-    Remove-ItemProperty -LiteralPath $keyPath -Name 'LegacyDisable' -Force
-  }
-}
-Write-Output '{"ok":true}'
-`.trim();
-    } else {
-      return `
-$ErrorActionPreference = 'Stop'
-New-PSDrive -Name HKCR -PSProvider Registry -Root HKEY_CLASSES_ROOT -ErrorAction SilentlyContinue | Out-Null
-$keyPath = '${psPath}'
-if (-not (Test-Path -LiteralPath $keyPath)) {
-  throw "注册表项不存在: ${hkcrRelativeKey}"
-}
-Set-ItemProperty -LiteralPath $keyPath -Name 'LegacyDisable' -Value '' -Type String -Force
-Write-Output '{"ok":true}'
-`.trim();
-    }
-  }
- 
-  /**
-   * 构建枚举 shellex\ContextMenuHandlers 下所有 Shell 扩展的脚本
-   * 使用四级级联策略解析本地化名称:
-   *  1. LocalizedString/FriendlyTypeName → SHLoadIndirectString(解析 @DLL,-ID 格式)
-   *  2. InprocServer32 DLL 字符串表 → 通用字符串质量筛选(LoadLibraryEx + LoadString)
-   *  3. CLSID 默认值
-   *  4. 处理程序键名(最终兜底)
-   * CmHelper.dll 编译后缓存至 %LOCALAPPDATA%\ContextMaster\,避免重复编译开销
-   */
-  buildGetShellExtItemsScript(shellexSubPath: string): string {
-    return `
-$ErrorActionPreference = 'SilentlyContinue'
-New-PSDrive -Name HKCR -PSProvider Registry -Root HKEY_CLASSES_ROOT -ErrorAction SilentlyContinue | Out-Null
-$cmDir = Join-Path $env:LOCALAPPDATA 'ContextMaster'
-$cmDll = Join-Path $cmDir 'CmHelper.dll'
-$helperLoaded = $false
-if (Test-Path $cmDll) {
-  try { Add-Type -Path $cmDll -ErrorAction Stop; $helperLoaded = $true } catch {}
-}
-if (-not $helperLoaded) {
-  $src = @'
-using System;
-using System.Runtime.InteropServices;
-using System.Text;
-using System.Collections.Generic;
-public class CmHelper {
-    const uint LOAD_AS_DATA = 2u;
-    [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
-    static extern int SHLoadIndirectString(string s, StringBuilder buf, int cap, IntPtr r);
-    [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
-    static extern IntPtr LoadLibraryEx(string p, IntPtr h, uint f);
-    [DllImport("kernel32.dll")]
-    static extern bool FreeLibrary(IntPtr h);
-    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
-    static extern int LoadString(IntPtr h, uint id, StringBuilder buf, int cap);
-    public static string ResolveIndirect(string s) {
-        if (string.IsNullOrEmpty(s) || !s.StartsWith("@")) return null;
-        var sb = new StringBuilder(512);
-        return SHLoadIndirectString(s, sb, 512, IntPtr.Zero) == 0 ? sb.ToString() : null;
-    }
-    public static string[] ReadDllStrings(string dll, uint from, uint to) {
-        var list = new List<string>();
-        var hMod = LoadLibraryEx(dll, IntPtr.Zero, LOAD_AS_DATA);
-        if (hMod == IntPtr.Zero) return list.ToArray();
-        try {
-            for (uint i = from; i <= to; i++) {
-                var sb = new StringBuilder(512);
-                if (LoadString(hMod, i, sb, 512) > 0) list.Add(sb.ToString());
-            }
-        } finally { FreeLibrary(hMod); }
-        return list.ToArray();
-    }
-}
-'@
-  if (-not (Test-Path $cmDir)) { New-Item -Path $cmDir -ItemType Directory -Force | Out-Null }
-  if (Test-Path $cmDll) { Remove-Item -Path $cmDll -Force -ErrorAction SilentlyContinue }
-  try {
-    Add-Type -TypeDefinition $src -OutputAssembly $cmDll -ErrorAction Stop
-    $helperLoaded = $true
-  } catch {
-    try { Add-Type -TypeDefinition $src -ErrorAction Stop; $helperLoaded = $true } catch {}
-  }
-}
-function Resolve-ExtName($clsid, $fallback) {
-  if ($clsid -match '^\\{[0-9A-Fa-f-]+\\}$') {
-    $clsidPath = 'HKCR:\\CLSID\\' + $clsid
-    if (Test-Path -LiteralPath $clsidPath) {
-      $clsidKey = Get-Item -LiteralPath $clsidPath
-      foreach ($valName in @('LocalizedString', 'FriendlyTypeName')) {
-        $raw = $clsidKey.GetValue($valName)
-        if ($raw) {
-          if ($raw.StartsWith('@')) {
-            try {
-              $resolved = [CmHelper]::ResolveIndirect($raw)
-              if ($resolved -and $resolved.Length -ge 2) { return $resolved }
-            } catch {}
-          } elseif ($raw.Length -ge 2) {
-            return $raw
-          }
-        }
-      }
-      $inprocPath = Join-Path $clsidPath 'InprocServer32'
-      if (Test-Path -LiteralPath $inprocPath) {
-        $dllPath = (Get-Item -LiteralPath $inprocPath).GetValue('')
-        # Fix 1: 展开 %SystemRoot% 等环境变量,否则 Test-Path 永远返回 $false
-        if ($dllPath) {
-          $dllPath = [System.Environment]::ExpandEnvironmentVariables($dllPath)
-        }
-        if ($dllPath -and (Test-Path -LiteralPath $dllPath)) {
-          # Level 2: 通用字符串质量筛选(过滤后取第一条,无硬编码词表,无长度限制)
-          try {
-            $candidates = [CmHelper]::ReadDllStrings($dllPath, 1, 1000) |
-              Where-Object {
-                $_.Length -ge 2 -and
-                $_ -notmatch '[\\\\/:*?<>|]' -and
-                $_ -notmatch '^\\{' -and
-                $_ -notmatch '^https?://' -and
-                $_ -notmatch '%[0-9A-Za-z]' -and
-                $_ -notmatch '\\{[0-9]+\\}' -and
-                $_ -notmatch '[\\r\\n\\t]' -and
-                $_ -notmatch '^[0-9]' -and
-                $_ -notmatch '[\\u3002\\uff01\\uff1f]' -and
-                $_ -notmatch '[.!?]$'
-              }
-            $pool = $candidates | Where-Object { $_ -match '[^\\x00-\\x7F]' }
-            if (-not $pool) { $pool = $candidates }
-            $best = $pool | Select-Object -First 1
-            if ($best) { return $best }
-          } catch {}
-          # Level 2.5: DLL VersionInfo(适用于英文/日文等非中文软件)
-          try {
-            $ver = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($dllPath)
-            $desc = $null
-            if ($ver.FileDescription -and $ver.FileDescription.Length -ge 2) {
-              $desc = $ver.FileDescription
-            } elseif ($ver.ProductName -and $ver.ProductName.Length -ge 2) {
-              $desc = $ver.ProductName
-            }
-            if ($desc -and $desc.Length -le 80 -and $desc -notmatch '^\\{' -and $desc -notmatch '[\\\\/:*?<>|]') {
-              return $desc
-            }
-          } catch {}
-        }
-      }
-      $def = $clsidKey.GetValue('')
-      if ($def) { return [string]$def }
-    }
-  }
-  return $fallback
-}
-$shellexPath = 'HKCR:\\${shellexSubPath}'
-if (-not (Test-Path -LiteralPath $shellexPath)) { Write-Output '[]'; exit }
-$handlers = Get-ChildItem -LiteralPath $shellexPath | Where-Object { $_.PSIsContainer }
-$result = @($handlers | ForEach-Object {
-  $handlerKeyName = $_.PSChildName
-  $clsid = $_.GetValue('')
-  if (-not $clsid) { $clsid = $handlerKeyName }
-  $cleanName   = $handlerKeyName -replace '^-+', ''
-  $displayName = Resolve-ExtName $clsid $cleanName
-  $isEnabled   = -not $handlerKeyName.StartsWith('-')
-  $regKey = '${shellexSubPath}\\' + $cleanName
-  [PSCustomObject]@{
-    name        = [string]$displayName
-    command     = [string]$clsid
-    iconPath    = $null
-    isEnabled   = [bool]$isEnabled
-    source      = [string]$handlerKeyName
-    registryKey = [string]$regKey
-    subKeyName  = [string]$handlerKeyName
-    itemType    = 'ShellExt'
-  }
-})
-$result | ConvertTo-Json -Compress -Depth 3
-`.trim();
-  }
- 
-  /**
-   * 构建启用/禁用 Shell 扩展的脚本(通过重命名键名添加/去除 '-' 前缀)
-   * enable=true  → 将 '-Name' 重命名为 'Name'
-   * enable=false → 将 'Name' 重命名为 '-Name'
-   */
-  buildShellExtToggleScript(hkcrRelativeKey: string, enable: boolean): string {
-    const lastSlash = hkcrRelativeKey.lastIndexOf('\\');
-    const parentRelPath = hkcrRelativeKey.substring(0, lastSlash);
-    const keyName = hkcrRelativeKey.substring(lastSlash + 1);
-    const cleanName = keyName.replace(/^-+/, '');
-    const psParentPath = `HKCR:\\${parentRelPath}`;
-    // enable: 找 -cleanName 改为 cleanName;disable: 找 cleanName 改为 -cleanName
-    const psCurrentKeyName = enable ? `-${cleanName}` : cleanName;
-    const psNewKeyName = enable ? cleanName : `-${cleanName}`;
-    return `
-$ErrorActionPreference = 'Stop'
-New-PSDrive -Name HKCR -PSProvider Registry -Root HKEY_CLASSES_ROOT -ErrorAction SilentlyContinue | Out-Null
-$parentPath = '${psParentPath}'
-$currentKey = '${psCurrentKeyName}'
-$newKey = '${psNewKeyName}'
-$fullPath = Join-Path $parentPath $currentKey
-if (-not (Test-Path -LiteralPath $fullPath)) {
-  throw "ShellExt key not found: $fullPath"
-}
-Rename-Item -LiteralPath $fullPath -NewName $newKey -Force
-Write-Output '{"ok":true}'
-`.trim();
-  }
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/main/services/RegistryService.ts.html b/coverage/main/services/RegistryService.ts.html deleted file mode 100644 index a884641..0000000 --- a/coverage/main/services/RegistryService.ts.html +++ /dev/null @@ -1,628 +0,0 @@ - - - - - - Code coverage report for main/services/RegistryService.ts - - - - - - - - - -
-
-

All files / main/services RegistryService.ts

-
- -
- 47.45% - Statements - 28/59 -
- - -
- 16.66% - Branches - 4/24 -
- - -
- 50% - Functions - 6/12 -
- - -
- 48.21% - Lines - 27/56 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182  -  -  -  -  -  -1x -  -  -  -  -  -  -  -  -  -1x -  -  -  -  -  -  -  -  -  -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -4x -4x -4x -  -  -4x -  -  -  -  -  -  -2x -2x -2x -  -2x -2x -2x -  -  -2x -2x -2x -2x -2x -  -  -  -  -2x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -2x -2x -3x -  -2x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -1x -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -1x -1x -  -  - 
import { MenuScene, MenuItemType } from '../../shared/enums';
-import { MenuItemEntry } from '../../shared/types';
-import { PowerShellBridge } from './PowerShellBridge';
-import log from '../utils/logger';
- 
-// 与 C# RegistryService._sceneRegistryPaths 完全一致
-const SCENE_REGISTRY_PATHS: Record<MenuScene, string> = {
-  [MenuScene.Desktop]:            'DesktopBackground\\Shell',
-  [MenuScene.File]:               '*\\shell',
-  [MenuScene.Folder]:             'Directory\\shell',
-  [MenuScene.Drive]:              'Drive\\shell',
-  [MenuScene.DirectoryBackground]:'Directory\\Background\\shell',
-  [MenuScene.RecycleBin]:         'CLSID\\{645FF040-5081-101B-9F08-00AA002F954E}\\shell',
-};
- 
-// Shell 扩展(COM)注册路径:shellex\ContextMenuHandlers
-const SCENE_SHELLEX_PATHS: Record<MenuScene, string> = {
-  [MenuScene.Desktop]:            'DesktopBackground\\shellex\\ContextMenuHandlers',
-  [MenuScene.File]:               '*\\shellex\\ContextMenuHandlers',
-  [MenuScene.Folder]:             'Directory\\shellex\\ContextMenuHandlers',
-  [MenuScene.Drive]:              'Drive\\shellex\\ContextMenuHandlers',
-  [MenuScene.DirectoryBackground]:'Directory\\Background\\shellex\\ContextMenuHandlers',
-  [MenuScene.RecycleBin]:         'CLSID\\{645FF040-5081-101B-9F08-00AA002F954E}\\shellex\\ContextMenuHandlers',
-};
- 
-// 完整 HKCR 前缀(用于显示)
-const HKCR_PREFIX = 'HKEY_CLASSES_ROOT';
- 
-interface PsMenuItemRaw {
-  name: string;
-  command: string;
-  iconPath: string | null;
-  isEnabled: boolean;
-  source: string;
-  registryKey: string;
-  subKeyName: string;
-  itemType?: string;  // 'ShellExt' for shell extensions
-}
- 
-export class RegistryService {
-  private readonly ps: PowerShellBridge;
-  /** 事务回滚数据:registryKey → 原始 isEnabled */
-  private rollbackData = new Map<string, boolean>();
-  private inTransaction = false;
-  private nextId = 1;
- 
-  constructor(ps: PowerShellBridge) {
-    this.ps = ps;
-  }
- 
-  /**
-   * 获取指定场景下的所有菜单条目(Classic Shell + Shell 扩展)
-   */
-  async getMenuItems(scene: MenuScene): Promise<MenuItemEntry[]> {
-    const basePath = SCENE_REGISTRY_PATHS[scene];
-    const shellexPath = SCENE_SHELLEX_PATHS[scene];
-    try {
-      // 读取 Classic Shell 命令
-      const script = this.ps.buildGetItemsScript(basePath);
-      const raw = await this.ps.execute<PsMenuItemRaw[]>(script);
-      const items = Array.isArray(raw) ? raw : (raw ? [raw] : []);
- 
-      // 读取 Shell 扩展(COM ContextMenuHandlers),失败不阻断主流程
-      let shellexItems: PsMenuItemRaw[] = [];
-      try {
-        const shellexScript = this.ps.buildGetShellExtItemsScript(shellexPath);
-        const shellexRaw = await this.ps.execute<PsMenuItemRaw[]>(shellexScript);
-        shellexItems = Array.isArray(shellexRaw) ? shellexRaw : (shellexRaw ? [shellexRaw] : []);
-      } catch (e) {
-        log.warn(`getMenuItems shellex(${scene}) failed (non-fatal):`, e);
-      }
- 
-      return [...items, ...shellexItems].map((r) => ({
-        id: this.nextId++,
-        name: r.name,
-        command: r.command,
-        iconPath: r.iconPath,
-        isEnabled: r.isEnabled,
-        source: r.source || this.inferSource(r.subKeyName),
-        menuScene: scene,
-        registryKey: r.registryKey,
-        type: this.determineType(r.itemType),
-      }));
-    } catch (e) {
-      log.error(`getMenuItems(${scene}) failed:`, e);
-      throw new Error(`读取注册表场景 ${scene} 失败: ${(e as Error).message}`);
-    }
-  }
- 
-  /**
-   * 启用或禁用单个菜单条目
-   * ShellExt 通过重命名键(±前缀)实现;Classic Shell 通过 LegacyDisable 值实现
-   * ShellExt 通过重命名键(±前缀)实现,registryKey 已归一化,身份不变
-   */
-  async setItemEnabled(registryKey: string, enabled: boolean): Promise<{ newRegistryKey?: string }> {
-    try {
-      if (this.isShellExtKey(registryKey)) {
-        const script = this.ps.buildShellExtToggleScript(registryKey, enabled);
-        await this.ps.executeElevated<{ ok: boolean }>(script);
-        return {};
-      } else {
-        const script = this.ps.buildSetEnabledScript(registryKey, enabled);
-        await this.ps.executeElevated<{ ok: boolean }>(script);
-        return {};
-      }
-    } catch (e) {
-      if (this.inTransaction) {
-        await this.rollback();
-      }
-      throw new Error(
-        `修改菜单项状态失败 [${registryKey}]: ${(e as Error).message}`
-      );
-    }
-  }
- 
-  /**
-   * 创建回滚点(事务开始前调用)
-   */
-  createRollbackPoint(items: Array<{ registryKey: string; isEnabled: boolean }>): void {
-    this.rollbackData.clear();
-    for (const item of items) {
-      this.rollbackData.set(item.registryKey, item.isEnabled);
-    }
-    this.inTransaction = true;
-  }
- 
-  /**
-   * 回滚到之前保存的状态
-   */
-  async rollback(): Promise<void> {
-    if (!this.inTransaction) return;
-    log.warn('Rolling back registry changes...');
-    try {
-      for (const [key, wasEnabled] of this.rollbackData) {
-        await this.setItemEnabledInternal(key, wasEnabled);
-      }
-    } finally {
-      this.inTransaction = false;
-      this.rollbackData.clear();
-    }
-  }
- 
-  /**
-   * 提交事务(清空回滚数据)
-   */
-  commitTransaction(): void {
-    this.inTransaction = false;
-    this.rollbackData.clear();
-  }
- 
-  /**
-   * 获取场景对应的完整注册表路径(用于 UI 显示)
-   */
-  getFullRegistryPath(scene: MenuScene): string {
-    return `${HKCR_PREFIX}\\${SCENE_REGISTRY_PATHS[scene]}`;
-  }
- 
-  private async setItemEnabledInternal(registryKey: string, enabled: boolean): Promise<void> {
-    if (this.isShellExtKey(registryKey)) {
-      const script = this.ps.buildShellExtToggleScript(registryKey, enabled);
-      await this.ps.executeElevated<{ ok: boolean }>(script);
-    } else {
-      const script = this.ps.buildSetEnabledScript(registryKey, enabled);
-      await this.ps.executeElevated<{ ok: boolean }>(script);
-    }
-  }
- 
-  /** Shell 扩展的 registryKey 包含 'shellex' 和 'ContextMenuHandlers' 路径段 */
-  private isShellExtKey(registryKey: string): boolean {
-    return registryKey.includes('shellex') && registryKey.includes('ContextMenuHandlers');
-  }
- 
-private inferSource(subKeyName: string): string {
-    return subKeyName || '';
-  }
- 
-  private determineType(itemType?: string): MenuItemType {
-    Iif (itemType === 'ShellExt') return MenuItemType.ShellExt;
-    return MenuItemType.System;
-  }
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/main/services/index.html b/coverage/main/services/index.html deleted file mode 100644 index 4365bb2..0000000 --- a/coverage/main/services/index.html +++ /dev/null @@ -1,161 +0,0 @@ - - - - - - Code coverage report for main/services - - - - - - - - - -
-
-

All files main/services

-
- -
- 40.75% - Statements - 97/238 -
- - -
- 21.27% - Branches - 20/94 -
- - -
- 55.26% - Functions - 21/38 -
- - -
- 41.31% - Lines - 88/213 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
BackupService.ts -
-
18.98%15/792.85%1/3536.36%4/1122.38%15/67
MenuManagerService.ts -
-
100%46/4685.71%12/14100%9/9100%38/38
PowerShellBridge.ts -
-
14.81%8/5414.28%3/2133.33%2/615.38%8/52
RegistryService.ts -
-
47.45%28/5916.66%4/2450%6/1248.21%27/56
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/main/utils/AdminHelper.ts.html b/coverage/main/utils/AdminHelper.ts.html deleted file mode 100644 index 5bab367..0000000 --- a/coverage/main/utils/AdminHelper.ts.html +++ /dev/null @@ -1,244 +0,0 @@ - - - - - - Code coverage report for main/utils/AdminHelper.ts - - - - - - - - - -
-
-

All files / main/utils AdminHelper.ts

-
- -
- 100% - Statements - 18/18 -
- - -
- 100% - Branches - 6/6 -
- - -
- 100% - Functions - 4/4 -
- - -
- 100% - Lines - 15/15 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54  -  -  -  -  -9x -  -  -  -  -  -  -  -8x -7x -4x -4x -  -  -  -  -  -  -  -4x -  -1x -  -4x -  -  -  -  -  -  -  -3x -3x -  -3x -3x -  -  -  -2x -1x -  -  -  -  -  -3x -  - 
import { execFileSync, execFile } from 'child_process';
-import { app } from 'electron';
-import log from './logger';
- 
-// 进程级缓存,避免每次写操作都 spawn 子进程检测
-let _adminCache: boolean | null = null;
- 
-/**
- * 检查当前进程是否以管理员身份运行(进程令牌已提权)
- * 使用 Windows Security Principal API,比 net session 更可靠:
- * net session 在域环境或特定组策略下可能误报 true
- */
-export function isAdmin(): boolean {
-  if (process.platform !== 'win32') return true;
-  if (_adminCache !== null) return _adminCache;
-  try {
-    const out = execFileSync(
-      'powershell.exe',
-      [
-        '-NonInteractive', '-NoProfile', '-Command',
-        '([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)',
-      ],
-      { stdio: 'pipe' }
-    ).toString().trim();
-    _adminCache = out === 'True';
-  } catch {
-    _adminCache = false;
-  }
-  return _adminCache;
-}
- 
-/**
- * 以管理员身份重启应用(UAC 提权)
- * 使用 PowerShell Start-Process -Verb RunAs
- */
-export function restartAsAdmin(): void {
-  const exePath = app.getPath('exe');
-  log.info(`Restarting as admin: ${exePath}`);
- 
-  const script = `Start-Process -FilePath "${exePath}" -Verb RunAs`;
-  execFile(
-    'powershell.exe',
-    ['-NonInteractive', '-NoProfile', '-Command', script],
-    (err) => {
-      if (err) {
-        log.error('Failed to restart as admin:', err);
-      }
-    }
-  );
- 
-  // 延迟退出,确保 Start-Process 已发出
-  setTimeout(() => app.quit(), 500);
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/main/utils/index.html b/coverage/main/utils/index.html deleted file mode 100644 index edd9118..0000000 --- a/coverage/main/utils/index.html +++ /dev/null @@ -1,146 +0,0 @@ - - - - - - Code coverage report for main/utils - - - - - - - - - -
-
-

All files main/utils

-
- -
- 80.64% - Statements - 25/31 -
- - -
- 100% - Branches - 8/8 -
- - -
- 66.66% - Functions - 6/9 -
- - -
- 78.57% - Lines - 22/28 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
AdminHelper.ts -
-
100%18/18100%6/6100%4/4100%15/15
ipcWrapper.ts -
-
100%7/7100%2/2100%2/2100%7/7
logger.ts -
-
0%0/6100%0/00%0/30%0/6
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/main/utils/ipcWrapper.ts.html b/coverage/main/utils/ipcWrapper.ts.html deleted file mode 100644 index 55a8fd0..0000000 --- a/coverage/main/utils/ipcWrapper.ts.html +++ /dev/null @@ -1,154 +0,0 @@ - - - - - - Code coverage report for main/utils/ipcWrapper.ts - - - - - - - - - -
-
-

All files / main/utils ipcWrapper.ts

-
- -
- 100% - Statements - 7/7 -
- - -
- 100% - Branches - 2/2 -
- - -
- 100% - Functions - 2/2 -
- - -
- 100% - Lines - 7/7 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24  -  -  -  -  -  -  -  -  -  -  -  -8x -8x -8x -1x -  -7x -7x -7x -  -  -  - 
import { IpcResult } from '../../shared/types';
-import log from 'electron-log';
- 
-/**
- * 统一 IPC handler 包装:捕获异常,返回 IpcResult<T>
- * renderer 层无需 try-catch
- */
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-export function wrapHandler<T>(
-  fn: (...args: any[]) => T | Promise<T>
-): (...args: any[]) => Promise<IpcResult<T>> {
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  return async (...args: any[]): Promise<IpcResult<T>> => {
-    try {
-      const data = await fn(...args);
-      return { success: true, data };
-    } catch (e: unknown) {
-      const msg = e instanceof Error ? e.message : String(e);
-      log.error('[IPC Error]', msg, e);
-      return { success: false, error: msg };
-    }
-  };
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/main/utils/logger.ts.html b/coverage/main/utils/logger.ts.html deleted file mode 100644 index 953d8ec..0000000 --- a/coverage/main/utils/logger.ts.html +++ /dev/null @@ -1,136 +0,0 @@ - - - - - - Code coverage report for main/utils/logger.ts - - - - - - - - - -
-
-

All files / main/utils logger.ts

-
- -
- 0% - Statements - 0/6 -
- - -
- 100% - Branches - 0/0 -
- - -
- 0% - Functions - 0/3 -
- - -
- 0% - Lines - 0/6 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
import log from 'electron-log';
-import path from 'path';
-import { app } from 'electron';
- 
-export function initLogger(): void {
-  log.transports.file.resolvePathFn = () =>
-    path.join(app.getPath('userData'), 'logs', 'main.log');
-  log.transports.file.level = 'info';
-  log.transports.file.maxDays = 7;
-  log.transports.console.level = 'debug';
-}
- 
-export function getLogDir(): string {
-  return path.join(app.getPath('userData'), 'logs');
-}
- 
-export default log;
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/prettify.css b/coverage/prettify.css deleted file mode 100644 index b317a7c..0000000 --- a/coverage/prettify.css +++ /dev/null @@ -1 +0,0 @@ -.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} diff --git a/coverage/prettify.js b/coverage/prettify.js deleted file mode 100644 index b322523..0000000 --- a/coverage/prettify.js +++ /dev/null @@ -1,2 +0,0 @@ -/* eslint-disable */ -window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); diff --git a/coverage/shared/enums.ts.html b/coverage/shared/enums.ts.html deleted file mode 100644 index 235949b..0000000 --- a/coverage/shared/enums.ts.html +++ /dev/null @@ -1,172 +0,0 @@ - - - - - - Code coverage report for shared/enums.ts - - - - - - - - - -
-
-

All files / shared enums.ts

-
- -
- 100% - Statements - 22/22 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 4/4 -
- - -
- 100% - Lines - 22/22 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -306x -6x -6x -6x -6x -6x -6x -  -  -6x -6x -6x -6x -  -  -6x -6x -6x -6x -6x -6x -6x -6x -  -  -6x -6x -6x -  - 
export enum MenuScene {
-  Desktop = 'Desktop',
-  File = 'File',
-  Folder = 'Folder',
-  Drive = 'Drive',
-  DirectoryBackground = 'DirectoryBackground',
-  RecycleBin = 'RecycleBin',
-}
- 
-export enum MenuItemType {
-  System = 'System',
-  Custom = 'Custom',
-  ShellExt = 'ShellExt',
-}
- 
-export enum OperationType {
-  Create = 'Create',
-  Update = 'Update',
-  Delete = 'Delete',
-  Enable = 'Enable',
-  Disable = 'Disable',
-  Backup = 'Backup',
-  Restore = 'Restore',
-}
- 
-export enum BackupType {
-  Auto = 'Auto',
-  Manual = 'Manual',
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/shared/index.html b/coverage/shared/index.html deleted file mode 100644 index dc8deb3..0000000 --- a/coverage/shared/index.html +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - Code coverage report for shared - - - - - - - - - -
-
-

All files shared

-
- -
- 100% - Statements - 23/23 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 4/4 -
- - -
- 100% - Lines - 23/23 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
enums.ts -
-
100%22/22100%0/0100%4/4100%22/22
ipc-channels.ts -
-
100%1/1100%0/0100%0/0100%1/1
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/shared/ipc-channels.ts.html b/coverage/shared/ipc-channels.ts.html deleted file mode 100644 index cb28cad..0000000 --- a/coverage/shared/ipc-channels.ts.html +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - Code coverage report for shared/ipc-channels.ts - - - - - - - - - -
-
-

All files / shared ipc-channels.ts

-
- -
- 100% - Statements - 1/1 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 0/0 -
- - -
- 100% - Lines - 1/1 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34  -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
// IPC 频道常量 — renderer 与 main 共用
-export const IPC = {
-  // 菜单管理
-  REGISTRY_GET_ITEMS:   'registry:getItems',
-  REGISTRY_TOGGLE:      'registry:toggle',
-  REGISTRY_BATCH:       'registry:batch',
- 
-  // 操作历史
-  HISTORY_GET_ALL:      'history:getAll',
-  HISTORY_UNDO:         'history:undo',
-  HISTORY_CLEAR:        'history:clear',
- 
-  // 备份管理
-  BACKUP_GET_ALL:       'backup:getAll',
-  BACKUP_CREATE:        'backup:create',
-  BACKUP_RESTORE:       'backup:restore',
-  BACKUP_DELETE:        'backup:delete',
-  BACKUP_EXPORT:        'backup:export',
-  BACKUP_IMPORT:        'backup:import',
-  BACKUP_PREVIEW_DIFF:  'backup:previewDiff',
- 
-  // 系统/窗口
-  SYS_IS_ADMIN:         'sys:isAdmin',
-  SYS_RESTART_AS_ADMIN: 'sys:restartAsAdmin',
-  SYS_OPEN_REGEDIT:     'sys:openRegedit',
-  SYS_OPEN_LOG_DIR:     'sys:openLogDir',
-  SYS_COPY_CLIPBOARD:   'sys:copyClipboard',
-  SYS_OPEN_EXTERNAL:    'sys:openExternal',
-  WIN_MINIMIZE:         'win:minimize',
-  WIN_MAXIMIZE:         'win:maximize',
-  WIN_CLOSE:            'win:close',
-  WIN_IS_MAXIMIZED:     'win:isMaximized',
-} as const;
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/sort-arrow-sprite.png b/coverage/sort-arrow-sprite.png deleted file mode 100644 index 6ed68316eb3f65dec9063332d2f69bf3093bbfab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^>_9Bd!3HEZxJ@+%Qh}Z>jv*C{$p!i!8j}?a+@3A= zIAGwzjijN=FBi!|L1t?LM;Q;gkwn>2cAy-KV{dn nf0J1DIvEHQu*n~6U}x}qyky7vi4|9XhBJ7&`njxgN@xNA8m%nc diff --git a/coverage/sorter.js b/coverage/sorter.js deleted file mode 100644 index 4ed70ae..0000000 --- a/coverage/sorter.js +++ /dev/null @@ -1,210 +0,0 @@ -/* eslint-disable */ -var addSorting = (function() { - 'use strict'; - var cols, - currentSort = { - index: 0, - desc: false - }; - - // returns the summary table element - function getTable() { - return document.querySelector('.coverage-summary'); - } - // returns the thead element of the summary table - function getTableHeader() { - return getTable().querySelector('thead tr'); - } - // returns the tbody element of the summary table - function getTableBody() { - return getTable().querySelector('tbody'); - } - // returns the th element for nth column - function getNthColumn(n) { - return getTableHeader().querySelectorAll('th')[n]; - } - - function onFilterInput() { - const searchValue = document.getElementById('fileSearch').value; - const rows = document.getElementsByTagName('tbody')[0].children; - - // Try to create a RegExp from the searchValue. If it fails (invalid regex), - // it will be treated as a plain text search - let searchRegex; - try { - searchRegex = new RegExp(searchValue, 'i'); // 'i' for case-insensitive - } catch (error) { - searchRegex = null; - } - - for (let i = 0; i < rows.length; i++) { - const row = rows[i]; - let isMatch = false; - - if (searchRegex) { - // If a valid regex was created, use it for matching - isMatch = searchRegex.test(row.textContent); - } else { - // Otherwise, fall back to the original plain text search - isMatch = row.textContent - .toLowerCase() - .includes(searchValue.toLowerCase()); - } - - row.style.display = isMatch ? '' : 'none'; - } - } - - // loads the search box - function addSearchBox() { - var template = document.getElementById('filterTemplate'); - var templateClone = template.content.cloneNode(true); - templateClone.getElementById('fileSearch').oninput = onFilterInput; - template.parentElement.appendChild(templateClone); - } - - // loads all columns - function loadColumns() { - var colNodes = getTableHeader().querySelectorAll('th'), - colNode, - cols = [], - col, - i; - - for (i = 0; i < colNodes.length; i += 1) { - colNode = colNodes[i]; - col = { - key: colNode.getAttribute('data-col'), - sortable: !colNode.getAttribute('data-nosort'), - type: colNode.getAttribute('data-type') || 'string' - }; - cols.push(col); - if (col.sortable) { - col.defaultDescSort = col.type === 'number'; - colNode.innerHTML = - colNode.innerHTML + ''; - } - } - return cols; - } - // attaches a data attribute to every tr element with an object - // of data values keyed by column name - function loadRowData(tableRow) { - var tableCols = tableRow.querySelectorAll('td'), - colNode, - col, - data = {}, - i, - val; - for (i = 0; i < tableCols.length; i += 1) { - colNode = tableCols[i]; - col = cols[i]; - val = colNode.getAttribute('data-value'); - if (col.type === 'number') { - val = Number(val); - } - data[col.key] = val; - } - return data; - } - // loads all row data - function loadData() { - var rows = getTableBody().querySelectorAll('tr'), - i; - - for (i = 0; i < rows.length; i += 1) { - rows[i].data = loadRowData(rows[i]); - } - } - // sorts the table using the data for the ith column - function sortByIndex(index, desc) { - var key = cols[index].key, - sorter = function(a, b) { - a = a.data[key]; - b = b.data[key]; - return a < b ? -1 : a > b ? 1 : 0; - }, - finalSorter = sorter, - tableBody = document.querySelector('.coverage-summary tbody'), - rowNodes = tableBody.querySelectorAll('tr'), - rows = [], - i; - - if (desc) { - finalSorter = function(a, b) { - return -1 * sorter(a, b); - }; - } - - for (i = 0; i < rowNodes.length; i += 1) { - rows.push(rowNodes[i]); - tableBody.removeChild(rowNodes[i]); - } - - rows.sort(finalSorter); - - for (i = 0; i < rows.length; i += 1) { - tableBody.appendChild(rows[i]); - } - } - // removes sort indicators for current column being sorted - function removeSortIndicators() { - var col = getNthColumn(currentSort.index), - cls = col.className; - - cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); - col.className = cls; - } - // adds sort indicators for current column being sorted - function addSortIndicators() { - getNthColumn(currentSort.index).className += currentSort.desc - ? ' sorted-desc' - : ' sorted'; - } - // adds event listeners for all sorter widgets - function enableUI() { - var i, - el, - ithSorter = function ithSorter(i) { - var col = cols[i]; - - return function() { - var desc = col.defaultDescSort; - - if (currentSort.index === i) { - desc = !currentSort.desc; - } - sortByIndex(i, desc); - removeSortIndicators(); - currentSort.index = i; - currentSort.desc = desc; - addSortIndicators(); - }; - }; - for (i = 0; i < cols.length; i += 1) { - if (cols[i].sortable) { - // add the click event handler on the th so users - // dont have to click on those tiny arrows - el = getNthColumn(i).querySelector('.sorter').parentElement; - if (el.addEventListener) { - el.addEventListener('click', ithSorter(i)); - } else { - el.attachEvent('onclick', ithSorter(i)); - } - } - } - } - // adds sorting functionality to the UI - return function() { - if (!getTable()) { - return; - } - cols = loadColumns(); - loadData(); - addSearchBox(); - addSortIndicators(); - enableUI(); - }; -})(); - -window.addEventListener('load', addSorting); diff --git a/tests/unit/main/ipc/registry.test.ts b/tests/unit/main/ipc/registry.test.ts index cd6e9b5..6c34c2e 100644 --- a/tests/unit/main/ipc/registry.test.ts +++ b/tests/unit/main/ipc/registry.test.ts @@ -28,6 +28,7 @@ describe('IPC Registry Handlers', () => { toggleItem: vi.fn(), batchEnable: vi.fn(), batchDisable: vi.fn(), + invalidateCache: vi.fn(), } as MockedObject; registerRegistryHandlers(mockMenuManager); diff --git a/tests/unit/main/services/MenuManagerService.test.ts b/tests/unit/main/services/MenuManagerService.test.ts index 75760b9..3b33193 100644 --- a/tests/unit/main/services/MenuManagerService.test.ts +++ b/tests/unit/main/services/MenuManagerService.test.ts @@ -13,6 +13,7 @@ vi.mock('@/main/utils/logger', () => ({ info: vi.fn(), warn: vi.fn(), error: vi.fn(), + debug: vi.fn(), }, }));