diff --git a/app/index.js b/app/index.js index 77dbce69..477cf413 100644 --- a/app/index.js +++ b/app/index.js @@ -4,18 +4,17 @@ import windowManager from "./windows.js"; import api from './api.js'; import fc from './features.js'; import migrations from './migrations/index.js'; +import { log } from './log.js'; const { id, labels, schemas, storageKeys, defaults } = appConfig; -console.log('core', id, labels.name); - -const debug = api.debug; +log('core', id, labels.name); // Store is created asynchronously in init() let store = null; // Datastore is now initialized in main process and accessible via api.datastore -console.log('core', 'datastore available via api.datastore'); +log('core', 'datastore available via api.datastore'); // Import and expose history tracking helpers import historyTracking from './datastore/history.js'; @@ -35,39 +34,39 @@ const builtinExtensions = ['cmd', 'groups', 'peeks', 'slides']; let _settingsWin = null; const openSettingsWindow = async (prefs) => { - console.log('openSettingsWindow()'); + log('core', 'openSettingsWindow()'); // Fixed size for settings window - content doesn't need to scale with screen const width = 900; const height = 650; const params = { - debug, + debug: log.debug, key: settingsAddress, transparent: true, height, width }; - console.log('Opening settings window with params:', params); + log('core', 'Opening settings window with params:', params); try { // Use the window creation API from windows.js const windowController = await windowManager.createWindow(settingsAddress, params); - console.log('Settings window opened successfully with controller:', windowController); + log('core', 'Settings window opened successfully with controller:', windowController); _settingsWin = windowController; // Focus the window to bring it to front await windowController.focus(); } catch (error) { - console.error('Failed to open settings window:', error); + log.error('core', 'Failed to open settings window:', error); } }; const initSettingsShortcut = (prefs) => { api.shortcuts.register(prefs.shortcutKey, () => { - console.log('settings shortcut executed'); + log('core', 'settings shortcut executed'); openSettingsWindow(prefs); }); }; @@ -80,23 +79,23 @@ const initFeature = f => { // Skip extension-based features (they're loaded by main process ExtensionManager) const extId = f.name.toLowerCase(); if (builtinExtensions.includes(extId)) { - debug && console.log('skipping extension-based feature (loaded by main process):', f.name); + log('core', 'skipping extension-based feature (loaded by main process):', f.name); return; } // Check if feature exists in the features collection if (!fc[f.id]) { - console.log('feature not found in collection:', f.name, f.id); + log('core', 'feature not found in collection:', f.name, f.id); return; } - console.log('initializing feature ', f); + log('core', 'initializing feature ', f); fc[f.id].init(); }; const uninitFeature = f => { - console.log('TODO: uninitFeature', f); + log('core', 'TODO: uninitFeature', f); // TODO uninitialize each active feature and close its window }; @@ -104,15 +103,15 @@ const uninitFeature = f => { // unused, worth testing more tho const initIframeFeature = file => { const pathPrefix = 'file:///Users/dietrich/misc/peek/'; - console.log('initiframe'); + log('core', 'initiframe'); const i = document.createElement('iframe'); const src = pathPrefix + file; - console.log('iframe src', src); + log('core', 'iframe src', src); document.body.appendChild(i); i.src = src; - console.log('iframe inited'); + log('core', 'iframe inited'); i.addEventListener('load', () => { - console.log('iframe loaded'); + log('core', 'iframe loaded'); }); }; @@ -127,9 +126,9 @@ const features = () => store ? store.get(storageKeys.ITEMS) : defaults.items; async function setColorScheme(scheme) { const result = await api.theme.setColorScheme(scheme); if (result.success) { - console.log(`Color scheme set to: ${scheme}`); + log('core', `Color scheme set to: ${scheme}`); } else { - console.error('Failed to set color scheme:', result.error); + log.error('core', 'Failed to set color scheme:', result.error); } } @@ -143,7 +142,7 @@ async function cycleTheme() { ]); if (!themeList.success || !themeList.data || themeList.data.length === 0) { - console.error('No themes available'); + log.error('core', 'No themes available'); return; } @@ -157,9 +156,9 @@ async function cycleTheme() { const result = await api.theme.setTheme(nextTheme.id); if (result.success) { - console.log(`Theme changed to: ${nextTheme.name} (${nextTheme.id})`); + log('core', `Theme changed to: ${nextTheme.name} (${nextTheme.id})`); } else { - console.error('Failed to set theme:', result.error); + log.error('core', 'Failed to set theme:', result.error); } } @@ -182,16 +181,16 @@ const registerExtensionCommands = () => { execute: async (ctx) => { const extName = ctx.search?.trim(); if (!extName) { - console.log('extension reload: no extension name provided'); + log('core', 'extension reload: no extension name provided'); return; } - console.log(`Reloading extension: ${extName}`); + log('core', `Reloading extension: ${extName}`); const result = await api.extensions.reload(extName.toLowerCase()); if (result.success) { - console.log(`Extension reloaded: ${extName}`); + log('core', `Extension reloaded: ${extName}`); } else { - console.error(`Failed to reload extension: ${result.error}`); + log.error('core', `Failed to reload extension: ${result.error}`); } } }); @@ -203,13 +202,13 @@ const registerExtensionCommands = () => { execute: async (ctx) => { const listResult = await api.extensions.list(); if (listResult.success && listResult.data) { - console.log('Running extensions:'); + log('core', 'Running extensions:'); listResult.data.forEach(ext => { const manifest = ext.manifest || {}; - console.log(` - ${manifest.name || ext.id} (${ext.id}) v${manifest.version || '?'}`); + log('core', ` - ${manifest.name || ext.id} (${ext.id}) v${manifest.version || '?'}`); }); } else { - console.log('No extensions running'); + log('core', 'No extensions running'); } // Open settings to Extensions section @@ -258,7 +257,7 @@ const registerExtensionCommands = () => { execute: () => api.restart() }); - console.log('Core commands registered'); + log('core', 'Core commands registered'); }; const init = async () => { @@ -278,21 +277,21 @@ const init = async () => { prefs: p }, api.scopes.SYSTEM); - console.log(`[startup] core init: ${Date.now() - initStart}ms`); + log('timing', `core init: ${Date.now() - initStart}ms`); // Listen for system- or feature-level requests to open windows. api.subscribe('open', msg => { // eg from the tray icon. if (msg.address && msg.address == settingsAddress) { openSettingsWindow(p).catch(err => { - console.error('Error opening settings window from open event:', err); + log.error('core', 'Error opening settings window from open event:', err); }); } }); // Handle URLs opened from external apps (e.g., when Peek is default browser) api.subscribe('external:open-url', async (msg) => { - console.log('external:open-url', msg); + log('core', 'external:open-url', msg); const { url, trackingSource, trackingSourceId } = msg; try { @@ -303,7 +302,7 @@ const init = async () => { trackingSourceId }); } catch (error) { - console.error('Error opening external URL:', error); + log.error('core', 'Error opening external URL:', error); } }); @@ -311,13 +310,13 @@ const init = async () => { try { await openSettingsWindow(p); } catch (error) { - console.error('Error opening startup settings window:', error); + log.error('core', 'Error opening startup settings window:', error); } // Feature enable/disable handler // Extensions are now managed by main process ExtensionManager via IPC api.subscribe(topicFeatureToggle, async msg => { - console.log('feature toggle', msg) + log('core', 'feature toggle', msg) // Find feature by ID (UUID) or by name (extension ID like "groups") const f = features().find(f => @@ -325,14 +324,14 @@ const init = async () => { f.name.toLowerCase() === msg.featureId?.toLowerCase() ); if (f) { - console.log('feature toggle', f); + log('core', 'feature toggle', f); // Check if this feature is backed by an extension const extId = f.name.toLowerCase(); const isExtension = builtinExtensions.includes(extId); if (msg.enabled == false) { - console.log('disabling', f.name); + log('core', 'disabling', f.name); if (isExtension) { // Use main process IPC to unload extension await api.extensions.unload(extId); @@ -341,7 +340,7 @@ const init = async () => { } } else if (msg.enabled == true) { - console.log('enabling', f.name); + log('core', 'enabling', f.name); if (isExtension) { // Use main process IPC to load extension await api.extensions.load(extId); @@ -351,7 +350,7 @@ const init = async () => { } } else { - console.log('feature toggle - no feature found for', msg.featureId); + log('core', 'feature toggle - no feature found for', msg.featureId); } }); @@ -362,7 +361,7 @@ const init = async () => { // Extensions are now loaded by main process ExtensionManager // It receives the 'core:ready' signal and calls loadEnabledExtensions() - console.log('Core features initialized. Extensions loaded by main process.'); + log('core', 'Core features initialized. Extensions loaded by main process.'); // Register extension dev commands - wait for cmd:ready api.subscribe('cmd:ready', () => { @@ -375,6 +374,6 @@ const init = async () => { window.addEventListener('load', () => { init().catch(error => { - console.error('Error during application initialization:', error); + log.error('core', 'Error during application initialization:', error); }); }); diff --git a/backend/electron/config.ts b/backend/electron/config.ts index 231be6eb..6044fa3f 100644 --- a/backend/electron/config.ts +++ b/backend/electron/config.ts @@ -15,6 +15,9 @@ export { TOPICS, } from '../config.js'; +// Debug mode - set DEBUG=1 to enable logging +export const DEBUG = !!process.env.DEBUG; + // Runtime configuration (set during app initialization) let _preloadPath: string = ''; let _profile: string = ''; diff --git a/backend/electron/datastore.ts b/backend/electron/datastore.ts index c2652b55..d515924d 100644 --- a/backend/electron/datastore.ts +++ b/backend/electron/datastore.ts @@ -22,6 +22,7 @@ import type { ContentOptions, } from '../types/index.js'; import { tableNames } from '../types/index.js'; +import { DEBUG } from './config.js'; // SQL Schema const createTableStatements = ` @@ -242,7 +243,7 @@ let db: Database.Database | null = null; // ==================== Lifecycle ==================== export function initDatabase(dbPath: string): Database.Database { - console.log('main', 'initializing database at:', dbPath); + DEBUG && console.log('main', 'initializing database at:', dbPath); db = new Database(dbPath); db.pragma('journal_mode = WAL'); @@ -250,7 +251,7 @@ export function initDatabase(dbPath: string): Database.Database { migrateTinyBaseData(); - console.log('main', 'database initialized successfully'); + DEBUG && console.log('main', 'database initialized successfully'); return db; } @@ -258,7 +259,7 @@ export function closeDatabase(): void { if (db) { db.close(); db = null; - console.log('main', 'database closed'); + DEBUG && console.log('main', 'database closed'); } } @@ -353,28 +354,28 @@ function migrateTinyBaseData(): void { // Check if we already migrated const existingData = db.prepare('SELECT COUNT(*) as count FROM addresses').get() as { count: number }; if (existingData.count > 0) { - console.log('main', 'TinyBase data already migrated, skipping'); + DEBUG && console.log('main', 'TinyBase data already migrated, skipping'); return; } - console.log('main', 'Migrating TinyBase data to direct tables...'); + DEBUG && console.log('main', 'Migrating TinyBase data to direct tables...'); try { const tinybaseRow = db.prepare('SELECT * FROM tinybase').get() as Record | undefined; if (!tinybaseRow) { - console.log('main', 'No TinyBase data found'); + DEBUG && console.log('main', 'No TinyBase data found'); return; } const rawData = Object.values(tinybaseRow)[1] as string; if (!rawData) { - console.log('main', 'TinyBase data is empty'); + DEBUG && console.log('main', 'TinyBase data is empty'); return; } const [tables] = JSON.parse(rawData) as [Record>>]; if (!tables) { - console.log('main', 'No tables in TinyBase data'); + DEBUG && console.log('main', 'No tables in TinyBase data'); return; } @@ -390,7 +391,7 @@ function migrateTinyBaseData(): void { const entries = Object.entries(tableData); if (entries.length === 0) continue; - console.log('main', ` Migrating ${entries.length} rows from ${tableName}`); + DEBUG && console.log('main', ` Migrating ${entries.length} rows from ${tableName}`); for (const [id, row] of entries) { try { @@ -409,7 +410,7 @@ function migrateTinyBaseData(): void { } db.exec('DROP TABLE IF EXISTS tinybase'); - console.log('main', 'TinyBase migration complete, removed tinybase table'); + DEBUG && console.log('main', 'TinyBase migration complete, removed tinybase table'); } catch (error) { console.error('main', 'TinyBase migration failed:', (error as Error).message); } diff --git a/backend/electron/entry.ts b/backend/electron/entry.ts index c69e83d2..d1ec3f98 100644 --- a/backend/electron/entry.ts +++ b/backend/electron/entry.ts @@ -107,7 +107,7 @@ const PROFILE = profileIsLegit(process.env.PROFILE) ? process.env.PROFILE : (app.isPackaged && !isDevPackagedBuild() ? 'default' : 'dev'); -console.log('PROFILE', PROFILE, app.isPackaged ? (isDevPackagedBuild() ? '(dev-packaged)' : '(packaged)') : '(source)'); +DEBUG && console.log('PROFILE', PROFILE, app.isPackaged ? (isDevPackagedBuild() ? '(dev-packaged)' : '(packaged)') : '(source)'); // Set profile in backend config setProfile(PROFILE); @@ -169,7 +169,7 @@ const onQuit = () => { // Electron app load const onReady = async () => { const startupStart = Date.now(); - console.log('onReady'); + DEBUG && console.log('onReady'); // Hide dock early to prevent flash in app switcher // Will be shown/hidden properly once prefs are loaded @@ -206,7 +206,7 @@ const onReady = async () => { // Register as default handler for http/https URLs (if not already and user hasn't declined) // Skip for test profiles to avoid system dialogs during automated testing if (isTestProfile()) { - console.log('Skipping default browser check for test profile:', PROFILE); + DEBUG && console.log('Skipping default browser check for test profile:', PROFILE); } const defaultBrowserPrefFile = path.join(profileDataPath, 'default-browser-pref.json'); @@ -218,7 +218,7 @@ const onReady = async () => { const pref = JSON.parse(fs.readFileSync(defaultBrowserPrefFile, 'utf8')); if (pref.declined === true) { shouldPromptForDefault = false; - console.log('User previously declined default browser prompt'); + DEBUG && console.log('User previously declined default browser prompt'); } } } catch { @@ -231,7 +231,7 @@ const onReady = async () => { const isDefaultHttps = app.isDefaultProtocolClient('https'); if (!isDefaultHttp || !isDefaultHttps) { - console.log('Registering as default protocol client for http/https'); + DEBUG && console.log('Registering as default protocol client for http/https'); app.setAsDefaultProtocolClient('http'); app.setAsDefaultProtocolClient('https'); @@ -241,7 +241,7 @@ const onReady = async () => { const nowDefaultHttps = app.isDefaultProtocolClient('https'); if (!nowDefaultHttp && !nowDefaultHttps) { // User declined, save preference - console.log('User declined default browser, saving preference'); + DEBUG && console.log('User declined default browser, saving preference'); try { fs.writeFileSync(defaultBrowserPrefFile, JSON.stringify({ declined: true, timestamp: Date.now() })); } catch { @@ -250,7 +250,7 @@ const onReady = async () => { } }, 2000); } else { - console.log('Already default protocol client for http/https'); + DEBUG && console.log('Already default protocol client for http/https'); } } @@ -264,7 +264,7 @@ const onReady = async () => { // TODO: kinda janky, needs rethink subscribe(systemAddress, scopes.SYSTEM, strings.topics.prefs, async (msg: unknown) => { const prefsMsg = msg as { prefs: Record }; - console.log('PREFS', prefsMsg); + DEBUG && console.log('PREFS', prefsMsg); // cache all prefs _prefs = prefsMsg.prefs; @@ -274,7 +274,7 @@ const onReady = async () => { // initialize system tray if (prefsMsg.prefs.showTrayIcon === true) { - console.log('showing tray'); + DEBUG && console.log('showing tray'); initTray(ROOT_DIR, { tooltip: labels.tray.tooltip, onClick: () => { @@ -289,10 +289,10 @@ const onReady = async () => { const newQuitShortcut = (prefsMsg.prefs.quitShortcut as string) || strings.defaults.quitShortcut; if (newQuitShortcut !== _quitShortcut) { if (_quitShortcut) { - console.log('unregistering old quit shortcut:', _quitShortcut); + DEBUG && console.log('unregistering old quit shortcut:', _quitShortcut); unregisterLocalShortcut(_quitShortcut); } - console.log('registering new quit shortcut:', newQuitShortcut); + DEBUG && console.log('registering new quit shortcut:', newQuitShortcut); registerLocalShortcut(newQuitShortcut, 'system', onQuit); _quitShortcut = newQuitShortcut; } @@ -304,7 +304,7 @@ const onReady = async () => { await loadExtensions(); const extTime = Date.now() - extStart; const totalTime = Date.now() - ((global as Record).__startupStart as number); - console.log(`[startup] main: ${extStart - ((global as Record).__startupStart as number)}ms, extensions: ${extTime}ms, total: ${totalTime}ms`); + DEBUG && console.log(`[startup] main: ${extStart - ((global as Record).__startupStart as number)}ms, extensions: ${extTime}ms, total: ${totalTime}ms`); } }); diff --git a/backend/electron/hotreload.ts b/backend/electron/hotreload.ts index 5a801851..8c76bf77 100644 --- a/backend/electron/hotreload.ts +++ b/backend/electron/hotreload.ts @@ -8,6 +8,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { BrowserWindow } from 'electron'; +import { DEBUG } from './config.js'; let watchers: fs.FSWatcher[] = []; let debounceTimer: NodeJS.Timeout | null = null; @@ -20,9 +21,9 @@ export function startHotReload(rootDir: string): void { const appDir = path.join(rootDir, 'app'); const extensionsDir = path.join(rootDir, 'extensions'); - console.log('[hotreload] Starting hot reload...'); - console.log('[hotreload] Watching:', appDir); - console.log('[hotreload] Watching:', extensionsDir); + DEBUG && console.log('[hotreload] Starting hot reload...'); + DEBUG && console.log('[hotreload] Watching:', appDir); + DEBUG && console.log('[hotreload] Watching:', extensionsDir); // Watch app directory if (fs.existsSync(appDir)) { @@ -52,7 +53,7 @@ export function startHotReload(rootDir: string): void { } } - console.log('[hotreload] Watching for changes (html, js, css)'); + DEBUG && console.log('[hotreload] Watching for changes (html, js, css)'); } /** @@ -67,7 +68,7 @@ export function stopHotReload(): void { clearTimeout(debounceTimer); debounceTimer = null; } - console.log('[hotreload] Stopped'); + DEBUG && console.log('[hotreload] Stopped'); } /** @@ -96,7 +97,7 @@ function scheduleReload(changedFile: string): void { * Reload all peek:// windows */ function reloadWindows(changedFile: string): void { - console.log(`[hotreload] File changed: ${changedFile}`); + DEBUG && console.log(`[hotreload] File changed: ${changedFile}`); const windows = BrowserWindow.getAllWindows(); let reloadedCount = 0; @@ -113,5 +114,5 @@ function reloadWindows(changedFile: string): void { } } - console.log(`[hotreload] Reloaded ${reloadedCount} window(s)`); + DEBUG && console.log(`[hotreload] Reloaded ${reloadedCount} window(s)`); } diff --git a/backend/electron/ipc.ts b/backend/electron/ipc.ts index 7bd9f65d..8b8b7c56 100644 --- a/backend/electron/ipc.ts +++ b/backend/electron/ipc.ts @@ -64,6 +64,7 @@ import { getPreloadPath, IPC_CHANNELS, isHeadless, + DEBUG, } from './config.js'; import { @@ -1110,7 +1111,7 @@ export function registerDarkModeHandlers(): void { */ export function registerWindowHandlers(): void { ipcMain.handle('window-open', async (ev, msg) => { - console.log('window-open', msg); + DEBUG && console.log('window-open', msg); const { url, options } = msg; @@ -1118,7 +1119,7 @@ export function registerWindowHandlers(): void { if (options.key) { const existingWindow = findWindowByKey(msg.source, options.key); if (existingWindow) { - console.log('Reusing existing window with key:', options.key); + DEBUG && console.log('Reusing existing window with key:', options.key); if (!isHeadless()) { existingWindow.window.show(); } @@ -1156,16 +1157,16 @@ export function registerWindowHandlers(): void { } } - console.log('Creating window with options:', winOptions); + DEBUG && console.log('Creating window with options:', winOptions); // Create new window const win = new BrowserWindow(winOptions); // Forward console logs from window to main process stdout (for debugging) - win.webContents.on('console-message', (_event, _level, message) => { + win.webContents.on('console-message', (event) => { // Only forward for peek:// URLs to avoid noise if (url.startsWith('peek://')) { - console.log(`[${url.replace('peek://', '')}] ${message}`); + DEBUG && console.log(`[${url.replace('peek://', '')}] ${event.message}`); } }); @@ -1183,7 +1184,7 @@ export function registerWindowHandlers(): void { address: url, transient: isTransient }; - console.log('Adding window to manager:', win.id, 'modal:', windowParams.modal, 'keepLive:', windowParams.keepLive); + DEBUG && console.log('Adding window to manager:', win.id, 'modal:', windowParams.modal, 'keepLive:', windowParams.keepLive); registerWindow(win.id, msg.source, windowParams); // Add escape key handler to all windows @@ -1199,7 +1200,7 @@ export function registerWindowHandlers(): void { setTimeout(() => { if (!win.isDestroyed()) { win.on('blur', () => { - console.log('window-open: blur for modal window', url); + DEBUG && console.log('window-open: blur for modal window', url); closeOrHideWindow(win.id); }); } @@ -1218,7 +1219,7 @@ export function registerWindowHandlers(): void { }); ipcMain.handle('window-close', async (_ev, msg) => { - console.log('window-close', msg); + DEBUG && console.log('window-close', msg); try { if (!msg.id) { @@ -1240,7 +1241,7 @@ export function registerWindowHandlers(): void { }); ipcMain.handle('window-hide', async (_ev, msg) => { - console.log('window-hide', msg); + DEBUG && console.log('window-hide', msg); try { if (!msg.id) { @@ -1268,7 +1269,7 @@ export function registerWindowHandlers(): void { }); ipcMain.handle('window-show', async (_ev, msg) => { - console.log('window-show', msg); + DEBUG && console.log('window-show', msg); try { if (!msg.id) { @@ -1297,7 +1298,7 @@ export function registerWindowHandlers(): void { }); ipcMain.handle('window-move', async (_ev, msg) => { - console.log('window-move', msg); + DEBUG && console.log('window-move', msg); try { if (!msg.id) { @@ -1329,7 +1330,7 @@ export function registerWindowHandlers(): void { }); ipcMain.handle('window-focus', async (_ev, msg) => { - console.log('window-focus', msg); + DEBUG && console.log('window-focus', msg); try { if (!msg.id) { @@ -1359,7 +1360,7 @@ export function registerWindowHandlers(): void { }); ipcMain.handle('window-blur', async (_ev, msg) => { - console.log('window-blur', msg); + DEBUG && console.log('window-blur', msg); try { if (!msg.id) { @@ -1387,7 +1388,7 @@ export function registerWindowHandlers(): void { }); ipcMain.handle('window-exists', async (_ev, msg) => { - console.log('window-exists', msg); + DEBUG && console.log('window-exists', msg); try { if (!msg.id) { @@ -1414,7 +1415,7 @@ export function registerWindowHandlers(): void { }); ipcMain.handle('window-list', async (_ev, msg) => { - console.log('window-list', msg); + DEBUG && console.log('window-list', msg); try { const windows: Array<{ @@ -1461,16 +1462,16 @@ export function registerMiscHandlers(onQuit: () => void): void { // Renderer log forwarding - prints renderer console.log to terminal ipcMain.on(IPC_CHANNELS.RENDERER_LOG, (_ev, msg) => { const shortSource = msg.source?.replace('peek://app/', '') || 'unknown'; - console.log(`[${shortSource}]`, ...(msg.args || [])); + DEBUG && console.log(`[${shortSource}]`, ...(msg.args || [])); }); // Register shortcut ipcMain.on(IPC_CHANNELS.REGISTER_SHORTCUT, (ev, msg) => { const isGlobal = msg.global === true; - console.log('ipc register shortcut', msg.shortcut, isGlobal ? '(global)' : '(local)'); + DEBUG && console.log('ipc register shortcut', msg.shortcut, isGlobal ? '(global)' : '(local)'); const callback = () => { - console.log('on(registershortcut): shortcut executed', msg.shortcut, msg.replyTopic); + DEBUG && console.log('on(registershortcut): shortcut executed', msg.shortcut, msg.replyTopic); ev.reply(msg.replyTopic, { foo: 'bar' }); }; @@ -1484,12 +1485,12 @@ export function registerMiscHandlers(onQuit: () => void): void { // Unregister shortcut ipcMain.on(IPC_CHANNELS.UNREGISTER_SHORTCUT, (_ev, msg) => { const isGlobal = msg.global === true; - console.log('ipc unregister shortcut', msg.shortcut, isGlobal ? '(global)' : '(local)'); + DEBUG && console.log('ipc unregister shortcut', msg.shortcut, isGlobal ? '(global)' : '(local)'); if (isGlobal) { const err = unregisterGlobalShortcut(msg.shortcut); if (err) { - console.log('ipc unregister global shortcut error:', err.message); + DEBUG && console.log('ipc unregister global shortcut error:', err.message); } } else { unregisterLocalShortcut(msg.shortcut); @@ -1499,7 +1500,7 @@ export function registerMiscHandlers(onQuit: () => void): void { // Close window ipcMain.on(IPC_CHANNELS.CLOSE_WINDOW, (ev, msg) => { closeWindow(msg.params, (output) => { - console.log('main.closeWindow api callback, output:', output); + DEBUG && console.log('main.closeWindow api callback, output:', output); if (msg && msg.replyTopic) { ev.reply(msg.replyTopic, output); } @@ -1508,48 +1509,48 @@ export function registerMiscHandlers(onQuit: () => void): void { // PubSub publish ipcMain.on(IPC_CHANNELS.PUBLISH, (_ev, msg) => { - console.log('ipc:publish', msg); + DEBUG && console.log('ipc:publish', msg); publish(msg.source, msg.scope, msg.topic, msg.data); }); // PubSub subscribe ipcMain.on(IPC_CHANNELS.SUBSCRIBE, (ev, msg) => { - console.log('ipc:subscribe', msg); + DEBUG && console.log('ipc:subscribe', msg); subscribe(msg.source, msg.scope, msg.topic, (data: unknown) => { - console.log('ipc:subscribe:notification', msg); + DEBUG && console.log('ipc:subscribe:notification', msg); ev.reply(msg.replyTopic, data); }); }); // Console log from renderer ipcMain.on(IPC_CHANNELS.CONSOLE, (_ev, msg) => { - console.log('r:', msg.source, msg.text); + DEBUG && console.log('r:', msg.source, msg.text); }); // App quit request ipcMain.on(IPC_CHANNELS.APP_QUIT, (_ev, msg) => { - console.log('app-quit requested from:', msg?.source); + DEBUG && console.log('app-quit requested from:', msg?.source); onQuit(); }); // App restart request ipcMain.on(IPC_CHANNELS.APP_RESTART, (_ev, msg) => { - console.log('app-restart requested from:', msg?.source); + DEBUG && console.log('app-restart requested from:', msg?.source); app.relaunch(); onQuit(); }); // Modify window ipcMain.on(IPC_CHANNELS.MODIFY_WINDOW, (ev, msg) => { - console.log('modifywindow', msg); + DEBUG && console.log('modifywindow', msg); const key = Object.prototype.hasOwnProperty.call(msg, 'name') ? msg.name : null; if (key != null) { const existingWindow = findWindowByKey(msg.source, key); if (existingWindow) { - console.log('FOUND WINDOW FOR KEY', key); + DEBUG && console.log('FOUND WINDOW FOR KEY', key); const bw = existingWindow.window; let r = false; try { diff --git a/backend/electron/main.ts b/backend/electron/main.ts index f574c948..8a934d34 100644 --- a/backend/electron/main.ts +++ b/backend/electron/main.ts @@ -15,7 +15,7 @@ import { discoverExtensions, loadExtensionManifest, isBuiltinExtensionEnabled, g import { initTray } from './tray.js'; import { registerLocalShortcut, unregisterLocalShortcut, handleLocalShortcut, registerGlobalShortcut, unregisterGlobalShortcut, unregisterShortcutsForAddress } from './shortcuts.js'; import { scopes, publish, subscribe, setExtensionBroadcaster, getSystemAddress } from './pubsub.js'; -import { APP_DEF_WIDTH, APP_DEF_HEIGHT, WEB_CORE_ADDRESS, getPreloadPath, isTestProfile, isDevProfile, isHeadless, getProfile } from './config.js'; +import { APP_DEF_WIDTH, APP_DEF_HEIGHT, WEB_CORE_ADDRESS, getPreloadPath, isTestProfile, isDevProfile, isHeadless, getProfile, DEBUG } from './config.js'; import { addEscHandler, winDevtoolsConfig, closeOrHideWindow, getSystemThemeBackgroundColor } from './windows.js'; // Configuration @@ -164,7 +164,7 @@ export function discoverBuiltinExtensions(extensionsDir: string): void { */ export function discoverBuiltinThemes(themesDir: string): void { if (!fs.existsSync(themesDir)) { - console.log('Themes directory not found:', themesDir); + DEBUG && console.log('Themes directory not found:', themesDir); return; } @@ -176,7 +176,7 @@ export function discoverBuiltinThemes(themesDir: string): void { const manifestPath = path.join(themePath, 'manifest.json'); if (!fs.existsSync(manifestPath)) { - console.log('Theme missing manifest.json:', entry.name); + DEBUG && console.log('Theme missing manifest.json:', entry.name); continue; } @@ -184,7 +184,7 @@ export function discoverBuiltinThemes(themesDir: string): void { const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8')); const themeId = manifest.id || entry.name; registerThemePath(themeId, themePath); - console.log('Discovered theme:', themeId); + DEBUG && console.log('Discovered theme:', themeId); } catch (err) { console.error('Failed to load theme manifest:', entry.name, err); } @@ -196,7 +196,7 @@ export function discoverBuiltinThemes(themesDir: string): void { */ export async function createExtensionWindow(extId: string): Promise { if (extensionWindows.has(extId)) { - console.log(`[ext:win] Extension ${extId} already has a window`); + DEBUG && console.log(`[ext:win] Extension ${extId} already has a window`); return extensionWindows.get(extId)!.win; } @@ -208,7 +208,7 @@ export async function createExtensionWindow(extId: string): Promise { - console.log(`[ext:${extId}] ${message}`); + win.webContents.on('console-message', (event) => { + DEBUG && console.log(`[ext:${extId}] ${event.message}`); }); // Track crashes @@ -234,7 +234,7 @@ export async function createExtensionWindow(extId: string): Promise { - console.log(`[ext:win] Extension ${extId} window closed`); + DEBUG && console.log(`[ext:win] Extension ${extId} window closed`); extensionWindows.delete(extId); }); @@ -242,7 +242,7 @@ export async function createExtensionWindow(extId: string): Promise { if (builtinExtIds.includes('cmd') && isBuiltinExtensionEnabled('cmd')) { const cmdStart = Date.now(); await createExtensionWindow('cmd'); - console.log(`[ext:timing] cmd: ${Date.now() - cmdStart}ms`); + DEBUG && console.log(`[ext:timing] cmd: ${Date.now() - cmdStart}ms`); } // Phase 2: Commands - other extensions should register commands @@ -287,7 +287,7 @@ export async function loadEnabledExtensions(): Promise { const parallelStart = Date.now(); await Promise.all(enabledBuiltinIds.map(id => createExtensionWindow(id))); - console.log(`[ext:timing] parallel (${enabledBuiltinIds.join(',')}): ${Date.now() - parallelStart}ms`); + DEBUG && console.log(`[ext:timing] parallel (${enabledBuiltinIds.join(',')}): ${Date.now() - parallelStart}ms`); // Load external extensions in parallel const externalExts = getExternalExtensions(); @@ -300,10 +300,10 @@ export async function loadEnabledExtensions(): Promise { if (enabledExternalExts.length > 0) { const extExtStart = Date.now(); await Promise.all(enabledExternalExts.map(ext => createExtensionWindow(ext.id))); - console.log(`[ext:timing] external: ${Date.now() - extExtStart}ms`); + DEBUG && console.log(`[ext:timing] external: ${Date.now() - extExtStart}ms`); } - console.log(`[ext:timing] total: ${Date.now() - extStart}ms`); + DEBUG && console.log(`[ext:timing] total: ${Date.now() - extStart}ms`); // Phase 3: UI - extensions can now initialize UI elements publish('system', scopes.GLOBAL, 'ext:startup:phase', { phase: 'ui' }); @@ -322,7 +322,7 @@ export async function loadEnabledExtensions(): Promise { * All extensions load as iframes within this single window */ async function createExtensionHostWindow(): Promise { - console.log('[ext:host] Creating consolidated extension host window'); + DEBUG && console.log('[ext:host] Creating consolidated extension host window'); const win = new BrowserWindow({ show: false, @@ -335,9 +335,7 @@ async function createExtensionHostWindow(): Promise { // Forward console logs from extension host win.webContents.on('console-message', (event) => { - // New API: event has message property - const msg = (event as { message?: string }).message || JSON.stringify(event); - console.log(`[ext:host] ${msg}`); + DEBUG && console.log(`[ext:host] ${event.message}`); }); // Log load errors @@ -373,7 +371,7 @@ async function loadExtensionsConsolidated(): Promise { const allExtIds = [...enabledBuiltinIds, ...enabledExternalIds]; - console.log(`[ext:host] Loading ${allExtIds.length} extensions as iframes`); + DEBUG && console.log(`[ext:host] Loading ${allExtIds.length} extensions as iframes`); // Phase 1: Early publish('system', scopes.GLOBAL, 'ext:startup:phase', { phase: 'early' }); @@ -398,7 +396,7 @@ async function loadExtensionsConsolidated(): Promise { // Wait for iframes to load await new Promise(resolve => setTimeout(resolve, 200)); - console.log(`[ext:timing] consolidated total: ${Date.now() - extStart}ms`); + DEBUG && console.log(`[ext:timing] consolidated total: ${Date.now() - extStart}ms`); // Phase 3: UI publish('system', scopes.GLOBAL, 'ext:startup:phase', { phase: 'ui' }); @@ -433,7 +431,7 @@ export async function loadExtensions(): Promise { const externalExts = getExternalExtensions(); const enabledExternalExts = externalExts.filter(ext => ext.enabled && ext.path); - console.log(`[ext] Hybrid mode: ${consolidatedIds.length} consolidated, ${externalBuiltinIds.length + enabledExternalExts.length} external`); + DEBUG && console.log(`[ext] Hybrid mode: ${consolidatedIds.length} consolidated, ${externalBuiltinIds.length + enabledExternalExts.length} external`); // Phase 1: Early publish('system', scopes.GLOBAL, 'ext:startup:phase', { phase: 'early' }); @@ -478,7 +476,7 @@ export async function loadExtensions(): Promise { } const totalCount = consolidatedIds.length + externalBuiltinIds.length + enabledExternalExts.length; - console.log(`[ext:timing] hybrid total: ${Date.now() - extStart}ms`); + DEBUG && console.log(`[ext:timing] hybrid total: ${Date.now() - extStart}ms`); // Phase 3: UI publish('system', scopes.GLOBAL, 'ext:startup:phase', { phase: 'ui' }); @@ -533,11 +531,11 @@ export function getRunningExtensions(): Array<{ id: string; manifest: unknown; s export function destroyExtensionWindow(extId: string): boolean { const entry = extensionWindows.get(extId); if (!entry) { - console.log(`[ext:win] No window to destroy for: ${extId}`); + DEBUG && console.log(`[ext:win] No window to destroy for: ${extId}`); return false; } - console.log(`[ext:win] Destroying window for: ${extId}`); + DEBUG && console.log(`[ext:win] Destroying window for: ${extId}`); if (entry.win && !entry.win.isDestroyed()) { entry.win.webContents.send('pubsub:app:shutdown', {}); @@ -656,8 +654,8 @@ export function createBackgroundWindow(): BrowserWindow { win.loadURL(WEB_CORE_ADDRESS); // Forward console logs from background window - win.webContents.on('console-message', (_event, _level, message) => { - console.log(`[core] ${message}`); + win.webContents.on('console-message', (event) => { + DEBUG && console.log(`[core] ${event.message}`); }); // Setup devtools for the background window (debug mode, but not in tests or headless) @@ -672,7 +670,7 @@ export function createBackgroundWindow(): BrowserWindow { // Set up handlers for windows opened from the background window win.webContents.setWindowOpenHandler((details) => { - console.log('Background window opening child window:', details.url); + DEBUG && console.log('Background window opening child window:', details.url); // Parse window features into options const featuresMap: Record = {}; @@ -692,13 +690,13 @@ export function createBackgroundWindow(): BrowserWindow { }); } - console.log('Parsed features map:', featuresMap); + DEBUG && console.log('Parsed features map:', featuresMap); // Check if window with this key already exists if (featuresMap.key) { const existingWindow = findWindowByKey(WEB_CORE_ADDRESS, featuresMap.key as string); if (existingWindow) { - console.log('Reusing existing window with key:', featuresMap.key); + DEBUG && console.log('Reusing existing window with key:', featuresMap.key); if (!isHeadless()) { existingWindow.window.show(); } @@ -727,7 +725,7 @@ export function createBackgroundWindow(): BrowserWindow { winOptions.y = parseInt(String(featuresMap.y)); } - console.log('Background window creating child with options:', winOptions); + DEBUG && console.log('Background window creating child with options:', winOptions); // Make sure we register browser window created handler to track the new window const onCreated = (_e: Electron.Event, newWin: BrowserWindow) => { @@ -756,7 +754,7 @@ export function createBackgroundWindow(): BrowserWindow { setTimeout(() => { if (!newWin.isDestroyed()) { newWin.on('blur', () => { - console.log('Modal window lost focus:', details.url); + DEBUG && console.log('Modal window lost focus:', details.url); closeOrHideWindow(newWin.id); }); } @@ -793,7 +791,7 @@ export function getBackgroundWindow(): BrowserWindow | null { * Handle URLs opened from external apps (e.g., when Peek is default browser) */ export function handleExternalUrl(url: string, sourceId = 'os'): void { - console.log('External URL received:', url, 'from:', sourceId); + DEBUG && console.log('External URL received:', url, 'from:', sourceId); if (!_appReady) { _pendingUrls.push({ url, sourceId }); @@ -849,7 +847,7 @@ export function registerSecondInstanceHandler(): void { arg.startsWith('http://') || arg.startsWith('https://') ); if (url) { - console.log('second-instance URL:', url); + DEBUG && console.log('second-instance URL:', url); handleExternalUrl(url, 'os'); } }); @@ -863,7 +861,7 @@ export function handleCliUrl(): void { arg.startsWith('http://') || arg.startsWith('https://') ); if (urlArg) { - console.log('CLI URL argument:', urlArg); + DEBUG && console.log('CLI URL argument:', urlArg); // Defer until background app is ready setTimeout(() => handleExternalUrl(urlArg, 'cli'), 1000); } @@ -876,7 +874,7 @@ export function handleCliUrl(): void { */ export function registerWindowAllClosedHandler(onQuit: () => void): void { app.on('window-all-closed', () => { - console.log('window-all-closed', process.platform); + DEBUG && console.log('window-all-closed', process.platform); if (process.platform !== 'darwin') { onQuit(); } @@ -904,7 +902,7 @@ export function registerActivateHandler(): void { export function requestSingleInstance(): boolean { // Skip single-instance lock in dev/test profiles to allow running alongside production if (isDevProfile() || isTestProfile()) { - console.log('Skipping single-instance lock for profile:', getProfile()); + DEBUG && console.log('Skipping single-instance lock for profile:', getProfile()); return true; } @@ -921,7 +919,7 @@ export function requestSingleInstance(): boolean { * Quit the application gracefully */ export function quitApp(): void { - console.log('quitApp'); + DEBUG && console.log('quitApp'); // Publish shutdown event and close database shutdown(); diff --git a/backend/electron/protocol.ts b/backend/electron/protocol.ts index 7733f0eb..494b87d4 100644 --- a/backend/electron/protocol.ts +++ b/backend/electron/protocol.ts @@ -13,6 +13,7 @@ import { protocol, net } from 'electron'; import path from 'node:path'; import { pathToFileURL } from 'node:url'; import { getDb } from './datastore.js'; +import { DEBUG } from './config.js'; export const APP_SCHEME = 'peek'; export const APP_PROTOCOL = `${APP_SCHEME}:`; @@ -52,7 +53,7 @@ export function registerScheme(): void { */ export function registerExtensionPath(id: string, fsPath: string): void { extensionPaths.set(id, fsPath); - console.log('Registered extension path:', id, fsPath); + DEBUG && console.log('Registered extension path:', id, fsPath); } /** @@ -67,7 +68,7 @@ export function getRegisteredExtensionIds(): string[] { */ export function registerThemePath(id: string, fsPath: string): void { themePaths.set(id, fsPath); - console.log('Registered theme path:', id, fsPath); + DEBUG && console.log('Registered theme path:', id, fsPath); } /** @@ -107,7 +108,7 @@ export function setActiveThemeId(id: string): boolean { return false; } activeThemeId = id; - console.log('Active theme set to:', id); + DEBUG && console.log('Active theme set to:', id); return true; } @@ -168,7 +169,7 @@ export function initProtocol(appRootDir: string): void { const extBasePath = getExtensionPath(extId); if (!extBasePath) { - console.log('Extension not found:', extId); + DEBUG && console.log('Extension not found:', extId); return new Response('Extension not found', { status: 404 }); } @@ -221,7 +222,7 @@ export function initProtocol(appRootDir: string): void { const themeBasePath = getThemePath(themeId); if (!themeBasePath) { - console.log('Theme not found:', themeId); + DEBUG && console.log('Theme not found:', themeId); return new Response('Theme not found', { status: 404 }); } diff --git a/backend/electron/shortcuts.ts b/backend/electron/shortcuts.ts index b1de32fc..8e3bba9d 100644 --- a/backend/electron/shortcuts.ts +++ b/backend/electron/shortcuts.ts @@ -8,6 +8,7 @@ */ import { globalShortcut } from 'electron'; +import { DEBUG } from './config.js'; // Maps for tracking shortcuts // Global shortcuts: shortcut string -> source address @@ -135,7 +136,7 @@ export function registerGlobalShortcut( source: string, callback: () => void ): Error | undefined { - console.log('registerGlobalShortcut', shortcut); + DEBUG && console.log('registerGlobalShortcut', shortcut); if (globalShortcut.isRegistered(shortcut)) { console.error('Shortcut already registered, unregistering first:', shortcut); @@ -143,7 +144,7 @@ export function registerGlobalShortcut( } const ret = globalShortcut.register(shortcut, () => { - console.log('shortcut executed', shortcut); + DEBUG && console.log('shortcut executed', shortcut); callback(); }); @@ -160,7 +161,7 @@ export function registerGlobalShortcut( * Unregister a global shortcut */ export function unregisterGlobalShortcut(shortcut: string): Error | undefined { - console.log('unregisterGlobalShortcut', shortcut); + DEBUG && console.log('unregisterGlobalShortcut', shortcut); if (!globalShortcut.isRegistered(shortcut)) { console.error('Unable to unregister shortcut because not registered:', shortcut); @@ -180,10 +181,10 @@ export function registerLocalShortcut( source: string, callback: () => void ): void { - console.log('registerLocalShortcut', shortcut); + DEBUG && console.log('registerLocalShortcut', shortcut); if (localShortcuts.has(shortcut)) { - console.log('local shortcut already registered, replacing:', shortcut); + DEBUG && console.log('local shortcut already registered, replacing:', shortcut); } const parsed = parseShortcut(shortcut); @@ -194,7 +195,7 @@ export function registerLocalShortcut( * Unregister a local shortcut */ export function unregisterLocalShortcut(shortcut: string): void { - console.log('unregisterLocalShortcut', shortcut); + DEBUG && console.log('unregisterLocalShortcut', shortcut); if (!localShortcuts.has(shortcut)) { console.error('local shortcut not registered:', shortcut); @@ -229,7 +230,7 @@ export function unregisterShortcutsForAddress(address: string): void { // Unregister global shortcuts for (const [shortcut, source] of globalShortcuts) { if (source === address) { - console.log('unregistering global shortcut', shortcut, 'for', address); + DEBUG && console.log('unregistering global shortcut', shortcut, 'for', address); unregisterGlobalShortcut(shortcut); } } @@ -237,7 +238,7 @@ export function unregisterShortcutsForAddress(address: string): void { // Unregister local shortcuts for (const [shortcut, data] of localShortcuts) { if (data.source === address) { - console.log('unregistering local shortcut', shortcut, 'for', address); + DEBUG && console.log('unregistering local shortcut', shortcut, 'for', address); localShortcuts.delete(shortcut); } } diff --git a/backend/electron/windows.ts b/backend/electron/windows.ts index f795952f..5648e888 100644 --- a/backend/electron/windows.ts +++ b/backend/electron/windows.ts @@ -10,6 +10,7 @@ import { SETTINGS_ADDRESS, isTestProfile, isHeadless, + DEBUG, } from './config.js'; // Default background colors for light and dark system themes @@ -83,7 +84,7 @@ export function askRendererToHandleEscape(bw: BrowserWindow): Promise<{ handled: * Supports escapeMode: 'close' (default), 'navigate', 'auto' */ export function addEscHandler(bw: BrowserWindow): void { - console.log('adding esc handler to window:', bw.id); + DEBUG && console.log('adding esc handler to window:', bw.id); bw.webContents.on('before-input-event', async (e, i) => { if (i.key === 'Escape' && i.type === 'keyUp') { // Get window info @@ -91,16 +92,16 @@ export function addEscHandler(bw: BrowserWindow): void { const params = entry?.params || {}; const escapeMode = (params.escapeMode as string) || 'close'; - console.log(`ESC pressed - window ${bw.id}, escapeMode: ${escapeMode}`); + DEBUG && console.log(`ESC pressed - window ${bw.id}, escapeMode: ${escapeMode}`); // For 'navigate' mode, ask renderer first if (escapeMode === 'navigate') { const response = await askRendererToHandleEscape(bw); - console.log(`Renderer escape response:`, response); + DEBUG && console.log(`Renderer escape response:`, response); if (response.handled) { // Renderer handled the escape (internal navigation) - console.log('Renderer handled escape, not closing'); + DEBUG && console.log('Renderer handled escape, not closing'); return; } } @@ -109,21 +110,21 @@ export function addEscHandler(bw: BrowserWindow): void { if (escapeMode === 'auto') { if (params.transient) { // Transient mode - close immediately - console.log('Auto mode (transient) - closing'); + DEBUG && console.log('Auto mode (transient) - closing'); } else { // Active mode - ask renderer first const response = await askRendererToHandleEscape(bw); - console.log(`Renderer escape response (auto/active):`, response); + DEBUG && console.log(`Renderer escape response (auto/active):`, response); if (response.handled) { - console.log('Renderer handled escape, not closing'); + DEBUG && console.log('Renderer handled escape, not closing'); return; } } } // Close or hide the window - console.log('Closing/hiding window'); + DEBUG && console.log('Closing/hiding window'); closeOrHideWindow(bw.id); } }); @@ -136,7 +137,7 @@ export function winDevtoolsConfig(bw: BrowserWindow): void { const windowData = getWindowInfo(bw.id); const params = windowData ? windowData.params : {}; - console.log('winDevtoolsConfig:', bw.id, 'openDevTools:', params.openDevTools, 'address:', params.address); + DEBUG && console.log('winDevtoolsConfig:', bw.id, 'openDevTools:', params.openDevTools, 'address:', params.address); // Check if devTools should be opened (never in test profiles or headless mode) if (params.openDevTools === true && !isTestProfile() && !isHeadless()) { @@ -148,7 +149,7 @@ export function winDevtoolsConfig(bw: BrowserWindow): void { activate: false }; - console.log(`Opening DevTools for window ${bw.id} with options:`, devToolsOptions); + DEBUG && console.log(`Opening DevTools for window ${bw.id} with options:`, devToolsOptions); // Open DevTools after a slight delay to let the main window settle setTimeout(() => { @@ -173,12 +174,12 @@ export function winDevtoolsConfig(bw: BrowserWindow): void { * This will actually close the window regardless of "keep alive" opener params */ export function closeWindow(params: { id?: number }, callback?: (success: boolean) => void): void { - console.log('closeWindow', params, callback != null); + DEBUG && console.log('closeWindow', params, callback != null); let retval = false; if (params.id !== undefined && getWindowInfo(params.id)) { - console.log('closeWindow(): closing', params.id); + DEBUG && console.log('closeWindow(): closing', params.id); const entry = getWindowInfo(params.id); if (!entry) { @@ -231,13 +232,13 @@ export function updateDockVisibility(excludeId: number | null = null): void { const prefs = _getPrefs(); const prefShowDock = prefs?.showInDockAndSwitcher === true; - console.log('updateDockVisibility:', { visibleCount, prefShowDock, excludeId }); + DEBUG && console.log('updateDockVisibility:', { visibleCount, prefShowDock, excludeId }); if (visibleCount > 0 || prefShowDock) { - console.log('Showing dock'); + DEBUG && console.log('Showing dock'); app.dock.show(); } else { - console.log('Hiding dock'); + DEBUG && console.log('Hiding dock'); app.dock.hide(); } } @@ -249,13 +250,13 @@ export function maybeHideApp(excludeId: number): void { if (process.platform !== 'darwin') return; const visibleCount = getVisibleWindowCount(excludeId); - console.log('maybeHideApp: visible windows (excluding', excludeId + '):', visibleCount); + DEBUG && console.log('maybeHideApp: visible windows (excluding', excludeId + '):', visibleCount); if (visibleCount === 0) { - console.log('No other visible windows, hiding app'); + DEBUG && console.log('No other visible windows, hiding app'); app.hide(); } else { - console.log('Other windows visible, not hiding app'); + DEBUG && console.log('Other windows visible, not hiding app'); } // Also update dock visibility @@ -266,36 +267,36 @@ export function maybeHideApp(excludeId: number): void { * Close or hide a window based on its parameters */ export function closeOrHideWindow(id: number): void { - console.log('closeOrHideWindow called for ID:', id); + DEBUG && console.log('closeOrHideWindow called for ID:', id); try { const win = BrowserWindow.fromId(id); if (!win || win.isDestroyed()) { - console.log('Window already destroyed or invalid'); + DEBUG && console.log('Window already destroyed or invalid'); return; } const entry = getWindowInfo(id); - console.log('Window entry from manager:', entry); + DEBUG && console.log('Window entry from manager:', entry); if (!entry) { - console.log('Window not found in window manager, closing directly'); + DEBUG && console.log('Window not found in window manager, closing directly'); win.close(); return; } const params = entry.params; - console.log('Window parameters - modal:', params.modal, 'keepLive:', params.keepLive); + DEBUG && console.log('Window parameters - modal:', params.modal, 'keepLive:', params.keepLive); // Never close the background window if (params.address === WEB_CORE_ADDRESS) { - console.log('Refusing to close background window'); + DEBUG && console.log('Refusing to close background window'); return; } // Special case for settings window - always close it on ESC if (params.address === SETTINGS_ADDRESS) { - console.log(`CLOSING settings window ${id}`); + DEBUG && console.log(`CLOSING settings window ${id}`); closeChildWindows(params.address as string); win.close(); // Hide app to return focus to previous app (only if no other visible windows) @@ -310,13 +311,13 @@ export function closeOrHideWindow(id: number): void { } else { // close any open windows this window opened closeChildWindows(params.address as string); - console.log(`CLOSING window ${id} (${params.address})`); + DEBUG && console.log(`CLOSING window ${id} (${params.address})`); win.close(); // Hide app to return focus to previous app (only if no other visible windows) maybeHideApp(id); } - console.log('closeOrHideWindow completed'); + DEBUG && console.log('closeOrHideWindow completed'); } catch (error) { console.error('Error in closeOrHideWindow:', error); } @@ -326,7 +327,7 @@ export function closeOrHideWindow(id: number): void { * Close all child windows of a given address */ export function closeChildWindows(aAddress: string): void { - console.log('closeChildWindows()', aAddress); + DEBUG && console.log('closeChildWindows()', aAddress); if (aAddress === WEB_CORE_ADDRESS) { return; @@ -337,7 +338,7 @@ export function closeChildWindows(aAddress: string): void { for (const child of childWindows) { const address = child.data.params.address as string; - console.log('closing child window', address, 'for', aAddress); + DEBUG && console.log('closing child window', address, 'for', aAddress); // recurseme closeChildWindows(address); diff --git a/backend/tauri/preload.js b/backend/tauri/preload.js index d44eed03..3addf597 100644 --- a/backend/tauri/preload.js +++ b/backend/tauri/preload.js @@ -84,7 +84,9 @@ logToMain('[peek-api:tauri] initPeekApi called for', sourceAddress); - const DEBUG = false; + // DEBUG config - will be set by Tauri via window.__PEEK_DEBUG__ + const DEBUG = window.__PEEK_DEBUG__ || false; + const DEBUG_CATEGORIES = window.__PEEK_DEBUG_CATEGORIES__ || ''; const DEBUG_LEVELS = { BASIC: 1, FIRST_RUN: 2 }; const DEBUG_LEVEL = DEBUG_LEVELS.BASIC; const rndm = () => Math.random().toString(16).slice(2); @@ -118,6 +120,7 @@ }; api.debug = DEBUG; + api.debugCategories = DEBUG_CATEGORIES; api.debugLevels = DEBUG_LEVELS; api.debugLevel = DEBUG_LEVEL; diff --git a/extensions/cmd/background.js b/extensions/cmd/background.js index 4f92b8b2..14711b6c 100644 --- a/extensions/cmd/background.js +++ b/extensions/cmd/background.js @@ -13,11 +13,11 @@ */ import { id, labels, schemas, storageKeys, defaults } from './config.js'; +import { log } from 'peek://app/log.js'; const api = window.app; -const debug = api.debug; -console.log('[ext:cmd] background', labels.name); +log('ext:cmd', 'background', labels.name); // ===== Command Registry (PROVIDER PATTERN) ===== // This extension owns the command registry. Other extensions register @@ -54,7 +54,7 @@ const loadSettings = async () => { const saveSettings = async (settings) => { const result = await api.settings.set(settings); if (!result.success) { - console.error('[ext:cmd] Failed to save settings:', result.error); + log.error('ext:cmd', 'Failed to save settings:', result.error); } }; @@ -70,11 +70,11 @@ const loadCommandCache = async () => { const result = await api.datastore.getRow('extension_settings', `cmd:command_cache`); if (result.success && result.data && result.data.value) { const cache = JSON.parse(result.data.value); - debug && console.log('[ext:cmd] Loaded command cache:', cache.commands?.length, 'commands'); + log('ext:cmd', 'Loaded command cache:', cache.commands?.length, 'commands'); return cache; } } catch (err) { - console.error('[ext:cmd] Failed to load command cache:', err); + log.error('ext:cmd', 'Failed to load command cache:', err); } return null; }; @@ -108,9 +108,9 @@ const saveCommandCache = async (appVersion, extensionVersions) => { updatedAt: Date.now() }); - console.log('[ext:cmd] Saved command cache:', commands.length, 'commands'); + log('ext:cmd', 'Saved command cache:', commands.length, 'commands'); } catch (err) { - console.error('[ext:cmd] Failed to save command cache:', err); + log.error('ext:cmd', 'Failed to save command cache:', err); } }; @@ -146,7 +146,7 @@ const getCurrentVersions = async () => { const isCacheValid = (cache, appVersion, extensionVersions) => { if (!cache) return false; if (cache.appVersion !== appVersion) { - debug && console.log('[ext:cmd] Cache invalid: app version mismatch', cache.appVersion, '!=', appVersion); + log('ext:cmd', 'Cache invalid: app version mismatch', cache.appVersion, '!=', appVersion); return false; } @@ -156,13 +156,13 @@ const isCacheValid = (cache, appVersion, extensionVersions) => { // Different set of extensions if (cachedExtIds.length !== currentExtIds.length) { - debug && console.log('[ext:cmd] Cache invalid: extension count mismatch'); + log('ext:cmd', 'Cache invalid: extension count mismatch'); return false; } for (const extId of currentExtIds) { if (cache.extensionVersions[extId] !== extensionVersions[extId]) { - debug && console.log('[ext:cmd] Cache invalid: extension version mismatch for', extId); + log('ext:cmd', 'Cache invalid: extension version mismatch for', extId); return false; } } @@ -185,7 +185,7 @@ const initCommandRegistry = () => { api.subscribe('cmd:register-batch', (msg) => { if (!msg.commands || !Array.isArray(msg.commands)) return; - debug && console.log('[ext:cmd] cmd:register-batch received:', msg.commands.length, 'commands'); + log('ext:cmd', 'cmd:register-batch received:', msg.commands.length, 'commands'); for (const cmd of msg.commands) { commandRegistry.set(cmd.name, { @@ -200,7 +200,7 @@ const initCommandRegistry = () => { // Handle individual command registrations from extensions api.subscribe('cmd:register', (msg) => { - debug && console.log('[ext:cmd] cmd:register received:', msg.name); + log('ext:cmd', 'cmd:register received:', msg.name); commandRegistry.set(msg.name, { name: msg.name, description: msg.description || '', @@ -213,25 +213,25 @@ const initCommandRegistry = () => { // Handle command unregistrations api.subscribe('cmd:unregister', (msg) => { - debug && console.log('[ext:cmd] cmd:unregister received:', msg.name); + log('ext:cmd', 'cmd:unregister received:', msg.name); commandRegistry.delete(msg.name); }, api.scopes.GLOBAL); // Handle queries from late-arriving consumers // Re-publish ready signal so they know we're available api.subscribe('cmd:query', () => { - debug && console.log('[ext:cmd] cmd:query received, re-publishing ready'); + log('ext:cmd', 'cmd:query received, re-publishing ready'); api.publish('cmd:ready', { id: 'cmd' }, api.scopes.GLOBAL); }, api.scopes.GLOBAL); // Handle command list queries from the panel api.subscribe('cmd:query-commands', () => { - debug && console.log('[ext:cmd] cmd:query-commands received'); + log('ext:cmd', 'cmd:query-commands received'); const commands = Array.from(commandRegistry.values()); api.publish('cmd:query-commands-response', { commands }, api.scopes.GLOBAL); }, api.scopes.GLOBAL); - console.log('[ext:cmd] Command registry initialized'); + log('ext:cmd', 'Command registry initialized'); }; /** @@ -243,7 +243,7 @@ const openPanelWindow = (prefs) => { const width = prefs.width || 600; const params = { - debug, + debug: log.debug, key: panelAddress, height, width, @@ -277,16 +277,16 @@ const openPanelWindow = (prefs) => { modal: true, type: 'panel', - openDevTools: debug, + openDevTools: log.debug, detachedDevTools: true, }; api.window.open(panelAddress, params) .then(result => { - console.log('[ext:cmd] Command window opened:', result); + log('ext:cmd', 'Command window opened:', result); }) .catch(error => { - console.error('[ext:cmd] Failed to open command window:', error); + log.error('ext:cmd', 'Failed to open command window:', error); }); }; @@ -303,14 +303,14 @@ const initShortcut = (prefs) => { openPanelWindow(prefs); }, { global: true }); - console.log('[ext:cmd] Registered shortcut:', prefs.shortcutKey); + log('ext:cmd', 'Registered shortcut:', prefs.shortcutKey); }; /** * Unregister shortcut and clean up */ const uninit = () => { - console.log('[ext:cmd] uninit'); + log('ext:cmd', 'uninit'); if (registeredShortcut) { api.shortcuts.unregister(registeredShortcut, { global: true }); @@ -325,7 +325,7 @@ const uninit = () => { * Reinitialize (called when settings change) */ const reinit = async () => { - console.log('[ext:cmd] reinit'); + log('ext:cmd', 'reinit'); // Unregister old shortcut if (registeredShortcut) { @@ -342,7 +342,7 @@ const reinit = async () => { * Initialize the extension */ const init = async () => { - console.log('[ext:cmd] init'); + log('ext:cmd', 'init'); // 1. Initialize command registry subscriptions FIRST // This ensures we're ready to receive registrations from other extensions @@ -362,7 +362,7 @@ const init = async () => { produces: cmd.produces || [] }); } - console.log('[ext:cmd] Pre-populated registry from cache:', commandRegistry.size, 'commands'); + log('ext:cmd', 'Pre-populated registry from cache:', commandRegistry.size, 'commands'); } // 2. Load settings from datastore @@ -373,13 +373,13 @@ const init = async () => { // 4. Listen for settings changes to hot-reload api.subscribe('cmd:settings-changed', () => { - console.log('[ext:cmd] settings changed, reinitializing'); + log('ext:cmd', 'settings changed, reinitializing'); reinit(); }, api.scopes.GLOBAL); // 4b. Save command cache after all extensions have loaded api.subscribe('ext:all-loaded', async () => { - debug && console.log('[ext:cmd] ext:all-loaded - saving command cache'); + log('ext:cmd', 'ext:all-loaded - saving command cache'); // Small delay to ensure all commands are registered setTimeout(async () => { const { appVersion, extensionVersions } = await getCurrentVersions(); @@ -389,7 +389,7 @@ const init = async () => { // Listen for settings updates from Settings UI api.subscribe('cmd:settings-update', async (msg) => { - console.log('[ext:cmd] settings-update received:', msg); + log('ext:cmd', 'settings-update received:', msg); try { if (msg.data) { @@ -408,7 +408,7 @@ const init = async () => { api.publish('cmd:settings-changed', currentSettings, api.scopes.GLOBAL); } catch (err) { - console.error('[ext:cmd] settings-update error:', err); + log.error('ext:cmd', 'settings-update error:', err); } }, api.scopes.GLOBAL); @@ -418,7 +418,7 @@ const init = async () => { const pendingDownloads = new Map(); api.subscribe('cmd:save-file', async (msg) => { - console.log('[ext:cmd] save-file request:', msg.filename); + log('ext:cmd', 'save-file request:', msg.filename); try { // Generate unique ID and store the data @@ -439,13 +439,13 @@ const init = async () => { alwaysOnTop: true }); } catch (err) { - console.error('[ext:cmd] save-file error:', err); + log.error('ext:cmd', 'save-file error:', err); } }, api.scopes.GLOBAL); // Download window requests data when ready api.subscribe('cmd:download-ready', (msg) => { - console.log('[ext:cmd] download-ready:', msg.id); + log('ext:cmd', 'download-ready:', msg.id); const data = pendingDownloads.get(msg.id); if (data) { @@ -454,13 +454,13 @@ const init = async () => { // Clean up pendingDownloads.delete(msg.id); } else { - console.error('[ext:cmd] No pending download for id:', msg.id); + log.error('ext:cmd', 'No pending download for id:', msg.id); } }, api.scopes.GLOBAL); // 6. LAST: Publish ready signal (PROVIDER PATTERN) // This tells all waiting consumers that cmd is ready to receive registrations - console.log('[ext:cmd] Publishing cmd:ready'); + log('ext:cmd', 'Publishing cmd:ready'); api.publish('cmd:ready', { id: 'cmd' }, api.scopes.GLOBAL); }; diff --git a/extensions/cmd/panel.js b/extensions/cmd/panel.js index 0af9fc15..03be7142 100644 --- a/extensions/cmd/panel.js +++ b/extensions/cmd/panel.js @@ -6,10 +6,10 @@ */ import { id, labels, schemas, storageKeys, defaults } from './config.js'; import './commands.js'; // Load commands module to dispatch cmd-update-commands event +import { log } from 'peek://app/log.js'; -console.log('[cmd:panel] loaded'); +log('cmd:panel', 'loaded'); -const debug = window.app.debug; const api = window.app; // Storage keys for persistent adaptive matching @@ -92,11 +92,11 @@ let state = { loadAdaptiveData().then(data => { state.adaptiveFeedback = data.feedback; state.matchCounts = data.counts; - console.log('[cmd:panel] Loaded adaptive data'); + log('cmd:panel', 'Loaded adaptive data'); }); window.addEventListener('cmd-update-commands', function(e) { - debug && console.log('[cmd:panel] received updated commands'); + log('cmd:panel', 'received updated commands'); state.commands = e.detail; }); @@ -248,7 +248,7 @@ function handleSpecialKey(e) { const trimmedText = commandInput.value.trim(); const urlResult = getValidURL(trimmedText); if (urlResult.valid && state.commands['open']) { - debug && console.log('Detected URL, using open command:', urlResult.url); + log('cmd:panel', 'Detected URL, using open command:', urlResult.url); state.lastExecuted = 'open'; updateMatchCount('open'); updateAdaptiveFeedback(trimmedText.split(' ')[0], 'open'); @@ -427,7 +427,7 @@ function findChainingCommands(mimeType) { * @param {string} sourceCommand - Name of the command that produced this output */ function enterChainMode(output, sourceCommand) { - console.log('[cmd:panel] Entering chain mode with output:', output.mimeType, output.title); + log('cmd:panel', 'Entering chain mode with output:', output.mimeType, output.title); state.chainMode = true; state.chainContext = { @@ -442,7 +442,7 @@ function enterChainMode(output, sourceCommand) { // Find commands that can accept this output const chainingCommands = findChainingCommands(output.mimeType); - console.log('[cmd:panel] Found', chainingCommands.length, 'commands accepting', output.mimeType); + log('cmd:panel', 'Found', chainingCommands.length, 'commands accepting', output.mimeType); // Clear input and update matches to show chaining commands state.typed = ''; @@ -464,7 +464,7 @@ function enterChainMode(output, sourceCommand) { * Exit chain mode and reset state */ function exitChainMode() { - console.log('[cmd:panel] Exiting chain mode'); + log('cmd:panel', 'Exiting chain mode'); state.chainMode = false; state.chainContext = null; @@ -520,7 +520,7 @@ function chainUndo() { * @param {string} sourceCommand - Command that produced this output */ function enterOutputSelectionMode(items, mimeType, sourceCommand) { - console.log('[cmd:panel] Entering output selection mode with', items.length, 'items'); + log('cmd:panel', 'Entering output selection mode with', items.length, 'items'); state.outputSelectionMode = true; state.outputItems = items; @@ -541,7 +541,7 @@ function enterOutputSelectionMode(items, mimeType, sourceCommand) { * Exit output selection mode */ function exitOutputSelectionMode() { - console.log('[cmd:panel] Exiting output selection mode'); + log('cmd:panel', 'Exiting output selection mode'); state.outputSelectionMode = false; state.outputItems = []; @@ -565,7 +565,7 @@ function selectOutputItem() { if (!state.outputSelectionMode || state.outputItems.length === 0) return; const selectedItem = state.outputItems[state.outputItemIndex]; - console.log('[cmd:panel] Selected output item:', state.outputItemIndex, selectedItem); + log('cmd:panel', 'Selected output item:', state.outputItemIndex, selectedItem); // Exit output selection mode const mimeType = state.outputMimeType; @@ -677,7 +677,7 @@ function showPreview(data, mimeType, title) { const previewTitle = document.getElementById('preview-title'); if (!previewContainer || !previewContent) { - console.log('[cmd:panel] Preview container not found'); + log('cmd:panel', 'Preview container not found'); return; } @@ -901,7 +901,7 @@ function showExecutionError(commandName, errorMsg) { * Cancel current execution */ function cancelExecution() { - console.log('[cmd:panel] Cancelling execution'); + log('cmd:panel', 'Cancelling execution'); hideExecutionState(); } @@ -909,12 +909,12 @@ function cancelExecution() { * Executes a command */ async function execute(name, typed) { - api.log('execute() called with:', name, typed); + log('cmd:panel', 'execute() called with:', name, typed); if (!state.commands[name]) return; - api.log('executing cmd', name, typed); + log('cmd:panel', 'executing cmd', name, typed); const context = buildExecutionContext(name, typed); - debug && console.log('execution context', context); + log('cmd:panel', 'execution context', context); // Delay showing execution state - only show if command takes > 150ms // This prevents flash for fast commands @@ -947,7 +947,7 @@ async function execute(name, typed) { // Hide execution state (in case it was shown) hideExecutionState(); - debug && console.log('command result:', result); + log('cmd:panel', 'command result:', result); // Check if command produced chainable output if (result && result.output && result.output.data && result.output.mimeType) { @@ -998,7 +998,7 @@ async function execute(name, typed) { // Clear the show state timer on error too clearTimeout(showStateTimer); - console.error('[cmd:panel] Command execution error:', err); + log.error('cmd:panel', 'Command execution error:', err); // Show error state showExecutionError(name, err.message || 'Unknown error'); @@ -1018,8 +1018,7 @@ async function shutdown() { * Finds commands matching the typed text */ function findMatchingCommands(text) { - const r = debug; // Only log if in debug mode - r && console.log('findMatchingCommands', text, state.commands.length); + log('cmd:panel', 'findMatchingCommands', text, Object.keys(state.commands).length); let matches = []; @@ -1032,14 +1031,14 @@ function findMatchingCommands(text) { const commandPart = text.split(' ')[0]; const hasParameters = text.includes(' '); - r && console.log('Command part:', commandPart, 'Has parameters:', hasParameters); + log('cmd:panel', 'Command part:', commandPart, 'Has parameters:', hasParameters); // Iterate over all commands, searching for matches for (const name of Object.keys(state.commands)) { // Match when: // 1. typed string is anywhere in a command name // 2. command name is at beginning of typed string (for commands with parameters) - r && console.log('testing option...', name); + log('cmd:panel', 'testing option...', name); const matchesCommand = name.toLowerCase().indexOf(commandPart.toLowerCase()) !== -1; const isCommandWithParams = hasParameters && text.toLowerCase().startsWith(name.toLowerCase() + ' '); diff --git a/package.json b/package.json index 08028fae..0eef5279 100644 --- a/package.json +++ b/package.json @@ -29,25 +29,25 @@ "scripts": { "//-- Development (BACKEND=electron|tauri, defaults to electron) --//": "", "dev": "[ \"${BACKEND:-}\" = \"tauri\" ] && yarn dev:tauri || yarn dev:electron", - "dev:electron": "yarn build && DEBUG=1 electron .", - "dev:tauri": "cd backend/tauri/src-tauri && cargo run", + "dev:electron": "./scripts/timed.sh sh -c 'yarn build && DEBUG=1 electron .'", + "dev:tauri": "./scripts/timed.sh sh -c 'cd backend/tauri/src-tauri && cargo run'", "dev:hot": "yarn build && nodemon --exec 'yarn build && DEBUG=1 electron .'", "//-- Production --//": "", "start": "[ \"${BACKEND:-}\" = \"tauri\" ] && yarn start:tauri || yarn start:electron", - "start:electron": "yarn build && electron .", - "start:tauri": "cd backend/tauri/src-tauri && cargo run --release", + "start:electron": "./scripts/timed.sh sh -c 'yarn build && electron .'", + "start:tauri": "./scripts/timed.sh sh -c 'cd backend/tauri/src-tauri && cargo run --release'", "start:tauri:log": "cd backend/tauri/src-tauri && ./target/release/peek-tauri 2>&1", "start:tauri:test": "pkill -INT -f peek-tauri 2>/dev/null; sleep 1; cd backend/tauri/src-tauri && ./target/release/peek-tauri &> /tmp/tauri.log & sleep 5 && tail -50 /tmp/tauri.log && pkill -INT -f peek-tauri", "//-- Build --//": "", - "build": "tsc -p backend/tsconfig.json", + "build": "./scripts/timed.sh tsc -p backend/tsconfig.json", "build:watch": "tsc -p backend/tsconfig.json --watch", - "build:electron": "electron-builder --dir", - "build:electron:install": "electron-builder --dir && rm -rf /Applications/Peek.app && cp -R out/mac-arm64/Peek.app /Applications/", - "build:electron:dist": "electron-builder", - "build:tauri": "cd backend/tauri/src-tauri && cargo tauri build", - "build:tauri:check": "cd backend/tauri/src-tauri && cargo check", + "build:electron": "./scripts/timed.sh electron-builder --dir", + "build:electron:install": "./scripts/timed.sh sh -c 'electron-builder --dir && rm -rf /Applications/Peek.app && cp -R out/mac-arm64/Peek.app /Applications/'", + "build:electron:dist": "./scripts/timed.sh electron-builder", + "build:tauri": "./scripts/timed.sh sh -c 'cd backend/tauri/src-tauri && cargo tauri build'", + "build:tauri:check": "./scripts/timed.sh sh -c 'cd backend/tauri/src-tauri && cargo check'", "//-- Packaged Electron --//": "", "kill:packaged": "pkill -f 'out/mac-arm64/Peek.app' || true", @@ -66,24 +66,24 @@ "test:packaged:debug": "yarn kill:packaged; HEADLESS=1 PACKAGED=1 DEBUG=1 npx playwright test tests/desktop/", "//-- Testing --//": "", - "test": "yarn build && yarn test:electron && yarn test:tauri", - "test:electron": "yarn build && HEADLESS=1 BACKEND=electron npx playwright test tests/desktop/", - "test:electron:x": "yarn build && HEADLESS=1 BACKEND=electron npx playwright test tests/desktop/ -x", - "test:tauri": "yarn test:tauri:frontend; yarn test:tauri:rust", - "test:tauri:frontend": "HEADLESS=1 BACKEND=tauri npx playwright test tests/desktop/", - "test:tauri:rust": "cd backend/tauri/src-tauri && cargo test --test smoke -- --nocapture", - "test:visible": "yarn build && HEADLESS=0 BACKEND=electron npx playwright test tests/desktop/ --headed", - "test:debug": "yarn build && HEADLESS=0 npx playwright test --debug", - "test:grep": "yarn build && HEADLESS=1 npx playwright test tests/desktop/ --grep", + "test": "./scripts/timed.sh sh -c 'yarn build && yarn test:electron && yarn test:tauri'", + "test:electron": "./scripts/timed.sh sh -c 'yarn build && HEADLESS=1 BACKEND=electron npx playwright test tests/desktop/'", + "test:electron:x": "./scripts/timed.sh sh -c 'yarn build && HEADLESS=1 BACKEND=electron npx playwright test tests/desktop/ -x'", + "test:tauri": "./scripts/timed.sh sh -c 'yarn test:tauri:frontend; yarn test:tauri:rust'", + "test:tauri:frontend": "./scripts/timed.sh sh -c 'HEADLESS=1 BACKEND=tauri npx playwright test tests/desktop/'", + "test:tauri:rust": "./scripts/timed.sh sh -c 'cd backend/tauri/src-tauri && cargo test --test smoke -- --nocapture'", + "test:visible": "./scripts/timed.sh sh -c 'yarn build && HEADLESS=0 BACKEND=electron npx playwright test tests/desktop/ --headed'", + "test:debug": "./scripts/timed.sh sh -c 'yarn build && HEADLESS=0 npx playwright test --debug'", + "test:grep": "./scripts/timed.sh sh -c 'yarn build && HEADLESS=1 npx playwright test tests/desktop/ --grep'", "//-- Misc (BACKEND=electron|tauri, defaults to electron) --//": "", "kill": "[ \"${BACKEND:-}\" = \"tauri\" ] && yarn kill:tauri || yarn kill:electron", "kill:electron": "pkill -f '/Users/dietrich/misc/peek/node_modules/.bin/electron' || true", "kill:tauri": "pkill -INT -f peek-tauri 2>/dev/null || true", "debug": "[ \"${BACKEND:-}\" = \"tauri\" ] && yarn debug:tauri || yarn debug:electron", - "debug:headless": "yarn build && PEEK_HEADLESS=1 DEBUG=1 electron .", - "debug:electron": "yarn build && DEBUG=1 electron .", - "debug:electron:headless": "yarn build && PEEK_HEADLESS=1 DEBUG=1 electron .", + "debug:headless": "./scripts/timed.sh sh -c 'yarn build && PEEK_HEADLESS=1 DEBUG=1 electron .'", + "debug:electron": "./scripts/timed.sh sh -c 'yarn build && DEBUG=1 electron .'", + "debug:electron:headless": "./scripts/timed.sh sh -c 'yarn build && PEEK_HEADLESS=1 DEBUG=1 electron .'", "debug:electron:log": "yarn kill:electron; sleep 1; yarn build && DEBUG=1 electron . 2>&1 | tee /tmp/electron-debug.log", "debug:electron:bg": "yarn kill:electron; sleep 1; yarn build && DEBUG=1 electron . > /tmp/electron-debug.log 2>&1 &", "electron:log": "tail -f /tmp/electron-debug.log", diff --git a/preload.js b/preload.js index b4a975f8..f9e7caa7 100644 --- a/preload.js +++ b/preload.js @@ -5,10 +5,13 @@ const { const src = 'preload'; const preloadStart = Date.now(); -console.log(src, 'init', window); const DEBUG = !!process.env.DEBUG; -console.log('preload DEBUG:', process.env.DEBUG, '->', DEBUG); +// If DEBUG is "1" or "true", enable all categories; otherwise it's a comma-separated list +const DEBUG_CATEGORIES = (process.env.DEBUG && process.env.DEBUG !== '1' && process.env.DEBUG !== 'true') + ? process.env.DEBUG + : ''; +DEBUG && console.log(src, 'init, DEBUG:', DEBUG, 'categories:', DEBUG_CATEGORIES || '(all)'); const DEBUG_LEVELS = { BASIC: 1, FIRST_RUN: 2 @@ -88,6 +91,7 @@ api.log = (...args) => { }; api.debug = DEBUG; +api.debugCategories = DEBUG_CATEGORIES; api.debugLevels = DEBUG_LEVELS; api.debugLevel = DEBUG_LEVEL; @@ -112,7 +116,7 @@ api.shortcuts = { */ register: (shortcut, cb, options = {}) => { const isGlobal = options.global === true; - console.log(src, `registering ${isGlobal ? 'global' : 'local'} shortcut ${shortcut} for ${window.location}`); + DEBUG && console.log(src, `registering ${isGlobal ? 'global' : 'local'} shortcut ${shortcut} for ${window.location}`); const replyTopic = `${shortcut}${rndm()}`; @@ -124,9 +128,9 @@ api.shortcuts = { }); ipcRenderer.on(replyTopic, (ev, msg) => { - console.log(src, 'shortcut execution reply'); + DEBUG && console.log(src, 'shortcut execution reply'); cb(); - console.log(src, 'shortcut execution reply done'); + DEBUG && console.log(src, 'shortcut execution reply done'); }); }, /** @@ -137,7 +141,7 @@ api.shortcuts = { */ unregister: (shortcut, options = {}) => { const isGlobal = options.global === true; - console.log(`unregistering ${isGlobal ? 'global' : 'local'} shortcut`, shortcut, 'for', window.location); + DEBUG && console.log(`unregistering ${isGlobal ? 'global' : 'local'} shortcut`, shortcut, 'for', window.location); ipcRenderer.send('unregistershortcut', { source: sourceAddress, shortcut, @@ -147,7 +151,7 @@ api.shortcuts = { }; api.closeWindow = (id, callback) => { - console.log(src, ['api.closewindow', id, 'for', window.location].join(', ')); + DEBUG && console.log(src, ['api.closewindow', id, 'for', window.location].join(', ')); const replyTopic = `${id}${rndm()}`; @@ -162,7 +166,7 @@ api.closeWindow = (id, callback) => { }); ipcRenderer.once(replyTopic, (ev, msg) => { - console.log(src, 'api.closewindow', 'resp from main', msg); + DEBUG && console.log(src, 'api.closewindow', 'resp from main', msg); if (callback) { callback(msg); } @@ -176,7 +180,7 @@ api.scopes = { }; api.publish = (topic, msg, scope = api.scopes.SELF) => { - console.log(sourceAddress, 'publish', topic) + DEBUG && console.log(sourceAddress, 'publish', topic) // TODO: c'mon if (!topic) { @@ -192,7 +196,7 @@ api.publish = (topic, msg, scope = api.scopes.SELF) => { }; api.subscribe = (topic, callback, scope = api.scopes.SELF) => { - console.log(src, 'subscribe', topic) + DEBUG && console.log(src, 'subscribe', topic) // TODO: c'mon if (!topic || !callback) { @@ -225,7 +229,7 @@ api.subscribe = (topic, callback, scope = api.scopes.SELF) => { api.window = { open: (url, options = {}) => { - console.log('window.open', url, options); + DEBUG && console.log('window.open', url, options); return ipcRenderer.invoke('window-open', { source: sourceAddress, url, @@ -233,7 +237,7 @@ api.window = { }); }, close: (id = null) => { - console.log('window.close', id); + DEBUG && console.log('window.close', id); if (id === null) { window.close(); return; @@ -244,28 +248,28 @@ api.window = { }); }, hide: (id) => { - console.log('window.hide', id); + DEBUG && console.log('window.hide', id); return ipcRenderer.invoke('window-hide', { source: sourceAddress, id }); }, show: (id) => { - console.log('window.show', id); + DEBUG && console.log('window.show', id); return ipcRenderer.invoke('window-show', { source: sourceAddress, id }); }, exists: (id) => { - console.log('window.exists', id); + DEBUG && console.log('window.exists', id); return ipcRenderer.invoke('window-exists', { source: sourceAddress, id }); }, move: (id, x, y) => { - console.log('window.move', id, x, y); + DEBUG && console.log('window.move', id, x, y); return ipcRenderer.invoke('window-move', { source: sourceAddress, id, @@ -274,21 +278,21 @@ api.window = { }); }, focus: (id) => { - console.log('window.focus', id); + DEBUG && console.log('window.focus', id); return ipcRenderer.invoke('window-focus', { source: sourceAddress, id }); }, blur: (id) => { - console.log('window.blur', id); + DEBUG && console.log('window.blur', id); return ipcRenderer.invoke('window-blur', { source: sourceAddress, id }); }, list: (options = {}) => { - console.log('window.list', options); + DEBUG && console.log('window.list', options); return ipcRenderer.invoke('window-list', { source: sourceAddress, ...options @@ -297,9 +301,9 @@ api.window = { }; api.modifyWindow = (winName, params) => { - console.log('modifyWindow(): window', winName, params); + DEBUG && console.log('modifyWindow(): window', winName, params); //w.name = `${sourceAddress}:${rndm()}`; - console.log('NAME', winName); + DEBUG && console.log('NAME', winName); ipcRenderer.send('modifywindow', { source: sourceAddress, name: winName, @@ -496,13 +500,13 @@ api.theme = { // Listen for theme changes (different theme selected) - reload CSS ipcRenderer.on('theme:themeChanged', (ev, { themeId }) => { - console.log('[preload] Theme changed to:', themeId, '- reloading stylesheets'); + DEBUG && console.log('[preload] Theme changed to:', themeId, '- reloading stylesheets'); reloadStylesheets(); }); // Listen for theme reload requests ipcRenderer.on('theme:reload', (ev, { themeId }) => { - console.log('[preload] Theme reload requested:', themeId); + DEBUG && console.log('[preload] Theme reload requested:', themeId); reloadStylesheets(); }); } catch (e) { @@ -1090,10 +1094,10 @@ if (isExtensionHost) { } contextBridge.exposeInMainWorld('app', api); -console.log(src, 'api exposed in', Date.now() - preloadStart, 'ms'); +DEBUG && console.log(src, 'api exposed in', Date.now() - preloadStart, 'ms'); window.addEventListener('load', () => { - console.log(src, 'window.load in', Date.now() - preloadStart, 'ms'); + DEBUG && console.log(src, 'window.load in', Date.now() - preloadStart, 'ms'); }); /* diff --git a/scripts/timed.sh b/scripts/timed.sh new file mode 100755 index 00000000..3d80c5a1 --- /dev/null +++ b/scripts/timed.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# Wrapper script that times any command and shows duration at the end +# Usage: ./scripts/timed.sh [args...] +# +# Example: ./scripts/timed.sh yarn build +# Output: [command output] +# ⏱ 2.34s + +start=$(date +%s.%N) + +# Run the command +"$@" +exit_code=$? + +end=$(date +%s.%N) + +# Calculate duration (using bc for floating point, with fallback) +if command -v bc &> /dev/null; then + duration=$(echo "$end - $start" | bc) +else + # Fallback to integer seconds if bc not available + duration=$((${end%.*} - ${start%.*})) +fi + +# Format output nicely +printf '\n⏱ %ss\n' "$duration" + +exit $exit_code