From 75acdc9afb348692ffd5631aeff8b405b2d2a1c3 Mon Sep 17 00:00:00 2001 From: KevIsDev Date: Fri, 20 Jun 2025 13:57:05 +0100 Subject: [PATCH] feat: add log level control to user settings - Add logLevel field to UserProfile interface - Update logger to support 'none' level and remove production restrictions - Add log level selector in settings UI with immediate effect - Remove unused preview synchronization code - Update @webcontainer/api dependency version --- app/settings/core/types.ts | 1 + app/settings/tabs/settings/SettingsTab.tsx | 62 +++++- app/shared/utils/logger.ts | 18 +- .../workbench/components/Workbench.client.tsx | 14 +- app/shared/workbench/stores/previews.ts | 210 ------------------ app/shared/workbench/stores/workbench.ts | 4 - package.json | 2 +- pnpm-lock.yaml | 10 +- 8 files changed, 82 insertions(+), 239 deletions(-) diff --git a/app/settings/core/types.ts b/app/settings/core/types.ts index c6374b06..8efcd5f8 100644 --- a/app/settings/core/types.ts +++ b/app/settings/core/types.ts @@ -25,6 +25,7 @@ export interface UserProfile { bio?: string; language: string; timezone: string; + logLevel?: 'none' | 'trace' | 'debug' | 'info' | 'warn' | 'error'; } export interface SettingItem { diff --git a/app/settings/tabs/settings/SettingsTab.tsx b/app/settings/tabs/settings/SettingsTab.tsx index 03cd1c47..6c00596e 100644 --- a/app/settings/tabs/settings/SettingsTab.tsx +++ b/app/settings/tabs/settings/SettingsTab.tsx @@ -5,6 +5,7 @@ import { classNames } from '~/shared/utils/classNames'; import { Switch } from '~/shared/components/ui/Switch'; import type { UserProfile } from '~/settings/core/types'; import { isMac } from '~/shared/utils/os'; +import { logger, type DebugLevel } from '~/shared/utils/logger'; // Helper to get modifier key symbols/text const getModifierSymbol = (modifier: string): string => { @@ -24,13 +25,21 @@ export default function SettingsTab() { const [currentTimezone, setCurrentTimezone] = useState(''); const [settings, setSettings] = useState(() => { const saved = localStorage.getItem('bolt_user_profile'); - return saved + const profile = saved ? JSON.parse(saved) : { notifications: true, language: 'en', timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, + logLevel: 'info' as DebugLevel, }; + + // Initialize logger with saved preference + if (profile.logLevel) { + logger.setLevel(profile.logLevel); + } + + return profile; }); useEffect(() => { @@ -49,9 +58,16 @@ export default function SettingsTab() { notifications: settings.notifications, language: settings.language, timezone: settings.timezone, + logLevel: settings.logLevel, }; localStorage.setItem('bolt_user_profile', JSON.stringify(updatedProfile)); + + // Update logger level when logLevel changes + if (settings.logLevel) { + logger.setLevel(settings.logLevel); + } + toast.success('Settings updated'); } catch (error) { console.error('Error saving settings:', error); @@ -139,6 +155,50 @@ export default function SettingsTab() { /> +
+
+
+ +
+ +

+ Controls the verbosity of application logs in the browser console +

+
{/* Timezone */} diff --git a/app/shared/utils/logger.ts b/app/shared/utils/logger.ts index 26f9e37d..c6b79363 100644 --- a/app/shared/utils/logger.ts +++ b/app/shared/utils/logger.ts @@ -1,4 +1,4 @@ -export type DebugLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error'; +export type DebugLevel = 'none' | 'trace' | 'debug' | 'info' | 'warn' | 'error'; import { Chalk } from 'chalk'; const chalk = new Chalk({ level: 3 }); @@ -14,7 +14,7 @@ interface Logger { setLevel: (level: DebugLevel) => void; } -let currentLevel: DebugLevel = (import.meta.env.VITE_LOG_LEVEL ?? import.meta.env.DEV) ? 'debug' : 'info'; +let currentLevel: DebugLevel = (import.meta.env.VITE_LOG_LEVEL ?? import.meta.env.DEV) ? 'none' : 'info'; export const logger: Logger = { trace: (...messages: any[]) => log('trace', undefined, messages), @@ -37,15 +37,16 @@ export function createScopedLogger(scope: string): Logger { } function setLevel(level: DebugLevel) { - if ((level === 'trace' || level === 'debug') && import.meta.env.PROD) { - return; - } - currentLevel = level; } function log(level: DebugLevel, scope: string | undefined, messages: any[]) { - const levelOrder: DebugLevel[] = ['trace', 'debug', 'info', 'warn', 'error']; + // If logging is completely disabled, return early + if (currentLevel === 'none') { + return; + } + + const levelOrder: DebugLevel[] = ['none', 'trace', 'debug', 'info', 'warn', 'error']; if (levelOrder.indexOf(level) < levelOrder.indexOf(currentLevel)) { return; @@ -98,6 +99,9 @@ function getLabelStyles(color: string, textColor: string) { function getColorForLevel(level: DebugLevel): string { switch (level) { + case 'none': { + return '#000000'; + } case 'trace': case 'debug': { return '#77828D'; diff --git a/app/shared/workbench/components/Workbench.client.tsx b/app/shared/workbench/components/Workbench.client.tsx index 7c493a35..aa3c64c0 100644 --- a/app/shared/workbench/components/Workbench.client.tsx +++ b/app/shared/workbench/components/Workbench.client.tsx @@ -24,7 +24,6 @@ import { Preview } from './Preview'; import useViewport from '~/shared/hooks'; import { PushToGitHubDialog } from '~/settings/tabs/connections/components/PushToGitHubDialog'; import * as DropdownMenu from '@radix-ui/react-dropdown-menu'; -import { usePreviewStore } from '~/shared/workbench/stores/previews'; import { chatStore } from '~/chat/stores/chat'; import type { ElementInfo } from './ui/Inspector'; @@ -327,16 +326,9 @@ export const Workbench = memo( }, []); const onFileSave = useCallback(() => { - workbenchStore - .saveCurrentDocument() - .then(() => { - // Explicitly refresh all previews after a file save - const previewStore = usePreviewStore(); - previewStore.refreshAllPreviews(); - }) - .catch(() => { - toast.error('Failed to update file content'); - }); + workbenchStore.saveCurrentDocument().catch(() => { + toast.error('Failed to update file content'); + }); }, []); const onFileReset = useCallback(() => { diff --git a/app/shared/workbench/stores/previews.ts b/app/shared/workbench/stores/previews.ts index 01d56cd7..2f553301 100644 --- a/app/shared/workbench/stores/previews.ts +++ b/app/shared/workbench/stores/previews.ts @@ -14,182 +14,28 @@ export interface PreviewInfo { baseUrl: string; } -// Create a broadcast channel for preview updates -const PREVIEW_CHANNEL = 'preview-updates'; - export class PreviewsStore { #availablePreviews = new Map(); #webcontainer: Promise; - #broadcastChannel: BroadcastChannel; - #lastUpdate = new Map(); - #watchedFiles = new Set(); #refreshTimeouts = new Map(); #REFRESH_DELAY = 300; - #storageChannel: BroadcastChannel; previews = atom([]); constructor(webcontainerPromise: Promise) { this.#webcontainer = webcontainerPromise; - this.#broadcastChannel = new BroadcastChannel(PREVIEW_CHANNEL); - this.#storageChannel = new BroadcastChannel('storage-sync-channel'); - - // Listen for preview updates from other tabs - this.#broadcastChannel.onmessage = (event) => { - const { type, previewId } = event.data; - - if (type === 'file-change') { - const timestamp = event.data.timestamp; - const lastUpdate = this.#lastUpdate.get(previewId) || 0; - - if (timestamp > lastUpdate) { - this.#lastUpdate.set(previewId, timestamp); - this.refreshPreview(previewId); - } - } - }; - - // Listen for storage sync messages - this.#storageChannel.onmessage = (event) => { - const { storage, source } = event.data; - - if (storage && source !== this._getTabId()) { - this._syncStorage(storage); - } - }; - - // Override localStorage setItem to catch all changes - if (typeof window !== 'undefined') { - const originalSetItem = localStorage.setItem; - - localStorage.setItem = (...args) => { - originalSetItem.apply(localStorage, args); - this._broadcastStorageSync(); - }; - } this.#init(); } - // Generate a unique ID for this tab - private _getTabId(): string { - if (typeof window !== 'undefined') { - if (!window._tabId) { - window._tabId = Math.random().toString(36).substring(2, 15); - } - - return window._tabId; - } - - return ''; - } - - // Sync storage data between tabs - private _syncStorage(storage: Record) { - if (typeof window !== 'undefined') { - Object.entries(storage).forEach(([key, value]) => { - try { - const originalSetItem = Object.getPrototypeOf(localStorage).setItem; - originalSetItem.call(localStorage, key, value); - } catch (error) { - console.error('[Preview] Error syncing storage:', error); - } - }); - - // Force a refresh after syncing storage - const previews = this.previews.get(); - previews.forEach((preview) => { - const previewId = this.getPreviewId(preview.baseUrl); - - if (previewId) { - this.refreshPreview(previewId); - } - }); - - // Reload the page content - if (typeof window !== 'undefined' && window.location) { - const iframe = document.querySelector('iframe'); - - if (iframe) { - iframe.src = iframe.src; - } - } - } - } - - // Broadcast storage state to other tabs - private _broadcastStorageSync() { - if (typeof window !== 'undefined') { - const storage: Record = {}; - - for (let i = 0; i < localStorage.length; i++) { - const key = localStorage.key(i); - - if (key) { - storage[key] = localStorage.getItem(key) || ''; - } - } - - this.#storageChannel.postMessage({ - type: 'storage-sync', - storage, - source: this._getTabId(), - timestamp: Date.now(), - }); - } - } - async #init() { const webcontainer = await this.#webcontainer; // Listen for server ready events webcontainer.on('server-ready', (port, url) => { console.log('[Preview] Server ready on port:', port, url); - this.broadcastUpdate(url); - - // Initial storage sync when preview is ready - this._broadcastStorageSync(); }); - try { - // Watch for file changes - webcontainer.internal.watchPaths( - { - // Only watch specific file types that affect the preview - include: ['**/*.html', '**/*.css', '**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx', '**/*.json'], - exclude: ['**/node_modules/**', '**/.git/**', '**/dist/**', '**/build/**', '**/coverage/**'], - }, - async (_events) => { - const previews = this.previews.get(); - - for (const preview of previews) { - const previewId = this.getPreviewId(preview.baseUrl); - - if (previewId) { - this.broadcastFileChange(previewId); - } - } - }, - ); - - // Watch for DOM changes that might affect storage - if (typeof window !== 'undefined') { - const observer = new MutationObserver((_mutations) => { - // Broadcast storage changes when DOM changes - this._broadcastStorageSync(); - }); - - observer.observe(document.body, { - childList: true, - subtree: true, - characterData: true, - attributes: true, - }); - } - } catch (error) { - console.error('[Preview] Error setting up watchers:', error); - } - // Listen for port events webcontainer.on('port', (port, type, url) => { let previewInfo = this.#availablePreviews.get(port); @@ -213,10 +59,6 @@ export class PreviewsStore { previewInfo.baseUrl = url; this.previews.set([...previews]); - - if (type === 'open') { - this.broadcastUpdate(url); - } }); } @@ -226,46 +68,6 @@ export class PreviewsStore { return match ? match[1] : null; } - // Broadcast state change to all tabs - broadcastStateChange(previewId: string) { - const timestamp = Date.now(); - this.#lastUpdate.set(previewId, timestamp); - - this.#broadcastChannel.postMessage({ - type: 'state-change', - previewId, - timestamp, - }); - } - - // Broadcast file change to all tabs - broadcastFileChange(previewId: string) { - const timestamp = Date.now(); - this.#lastUpdate.set(previewId, timestamp); - - this.#broadcastChannel.postMessage({ - type: 'file-change', - previewId, - timestamp, - }); - } - - // Broadcast update to all tabs - broadcastUpdate(url: string) { - const previewId = this.getPreviewId(url); - - if (previewId) { - const timestamp = Date.now(); - this.#lastUpdate.set(previewId, timestamp); - - this.#broadcastChannel.postMessage({ - type: 'file-change', - previewId, - timestamp, - }); - } - } - // Method to refresh a specific preview refreshPreview(previewId: string) { // Clear any pending refresh for this preview @@ -295,18 +97,6 @@ export class PreviewsStore { this.#refreshTimeouts.set(previewId, timeout); } - - refreshAllPreviews() { - const previews = this.previews.get(); - - for (const preview of previews) { - const previewId = this.getPreviewId(preview.baseUrl); - - if (previewId) { - this.broadcastFileChange(previewId); - } - } - } } // Create a singleton instance diff --git a/app/shared/workbench/stores/workbench.ts b/app/shared/workbench/stores/workbench.ts index aba205b7..f61cedaf 100644 --- a/app/shared/workbench/stores/workbench.ts +++ b/app/shared/workbench/stores/workbench.ts @@ -579,10 +579,6 @@ export class WorkbenchStore { this.#editorStore.updateFile(fullPath, data.action.content); - if (!isStreaming && data.action.content) { - await this.saveFile(fullPath); - } - if (!isStreaming) { await artifact.runner.runAction(data); this.resetAllFileModifications(); diff --git a/package.json b/package.json index 9051a74a..c96b55d4 100644 --- a/package.json +++ b/package.json @@ -97,7 +97,7 @@ "@types/react-beautiful-dnd": "^13.1.8", "@uiw/codemirror-theme-vscode": "^4.23.6", "@unocss/reset": "^0.61.9", - "@webcontainer/api": "1.6.1-internal.1", + "@webcontainer/api": "1.6.4-internal.2", "@xterm/addon-fit": "^0.10.0", "@xterm/addon-web-links": "^0.11.0", "@xterm/xterm": "^5.5.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c4f026db..be1f57ae 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -171,8 +171,8 @@ importers: specifier: ^0.61.9 version: 0.61.9 '@webcontainer/api': - specifier: 1.6.1-internal.1 - version: 1.6.1-internal.1 + specifier: 1.6.4-internal.2 + version: 1.6.4-internal.2 '@xterm/addon-fit': specifier: ^0.10.0 version: 0.10.0(@xterm/xterm@5.5.0) @@ -3515,8 +3515,8 @@ packages: '@web3-storage/multipart-parser@1.0.0': resolution: {integrity: sha512-BEO6al7BYqcnfX15W2cnGR+Q566ACXAT9UQykORCWW80lmkpWsnEob6zJS1ZVBKsSJC8+7vJkHwlp+lXG1UCdw==} - '@webcontainer/api@1.6.1-internal.1': - resolution: {integrity: sha512-3kIlLzJonzKlPzONuKudMWHICqwkwyXSsIoUHt4vJwjAr8/qFjlMVxq8ggHS6Yck9JBh1JllDWfCiC95ipSsxw==} + '@webcontainer/api@1.6.4-internal.2': + resolution: {integrity: sha512-ATzYHu8TyuNXOZ+U+Wh1LLLzWY2gbBINmEXnU9lNEMN7E9IA+EEdpsYca1Or0v8rm2cdxrIARuwbm+ffJdrV3w==} '@xmldom/xmldom@0.8.10': resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==} @@ -11658,7 +11658,7 @@ snapshots: '@web3-storage/multipart-parser@1.0.0': {} - '@webcontainer/api@1.6.1-internal.1': {} + '@webcontainer/api@1.6.4-internal.2': {} '@xmldom/xmldom@0.8.10': {}