import 'reflect-metadata'; import 'katex/dist/katex.min.css'; import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import './styles/variables.css'; import './index.css'; import App from './App.tsx'; import DialogProvider from './components/common/DialogProvider'; import { KGCore } from './core/KGCore'; import { KGAudioInterface } from './core/audio-interface/KGAudioInterface'; import { ConfigManager } from './core/config/ConfigManager'; import { KGMidiInput } from './core/midi-input/KGMidiInput'; import { KGDebugger } from './core/KGDebugger'; import { enumerateAudioDevices, validateConfiguredAudioDevices } from './util/audioDeviceUtil'; const root = createRoot(document.getElementById('root')!); // OPFS requires a secure context (HTTPS or localhost). Show a clear error instead of crashing. if (!window.isSecureContext) { root.render( 🔒 Secure Context Required K.G.Studio uses the browser's Origin Private File System (OPFS) to store projects, which requires a secure context. Use https:// instead of http:// or access via localhost / 127.0.0.1 , ); } else { // Initialize KGCore instance await KGCore.instance().initialize(); const configManager = ConfigManager.instance(); try { const deviceSnapshot = await enumerateAudioDevices(); const validatedDevices = validateConfiguredAudioDevices( configManager.get('audio.input_device_id') as string | undefined, configManager.get('audio.output_device_id') as string | undefined, deviceSnapshot ); const updates: Array> = []; const statusMessages: string[] = []; if (validatedDevices.inputFellBackToDefault) { updates.push(configManager.set('audio.input_device_id', 'default')); statusMessages.push('Previously selected audio input device is unavailable; using System Default.'); } if (validatedDevices.outputFellBackToDefault) { updates.push(configManager.set('audio.output_device_id', 'default')); statusMessages.push('Previously selected audio output device is unavailable; using System Default.'); } if (updates.length > 0) { await Promise.all(updates); KGCore.instance().setStatus(statusMessages.join(' ')); } await KGAudioInterface.instance().applyConfiguredOutputDevice(validatedDevices.outputDeviceId); } catch (error) { console.warn('Audio device validation skipped:', error); } // Initialize KGMidiInput instance await KGMidiInput.instance().initialize(); // Attach debugger to global window in development mode if (import.meta.env.DEV) { const dbg = KGDebugger.instance(); (window as unknown as { KGDebugger: KGDebugger; sh: () => Promise }).KGDebugger = dbg; (window as unknown as { sh: () => Promise }).sh = () => dbg.startShell(); console.log('🔧 KGDebugger attached to window - try: KGDebugger.help() or sh()'); } // Start audio context on first user interaction let audioContextStarted = false; const tryStartAudioContext = async () => { if (!audioContextStarted) { audioContextStarted = true; try { const audioInterface = KGAudioInterface.instance(); if (!audioInterface.getIsAudioContextStarted()) { await audioInterface.startAudioContext(); console.log('Audio context started on first user interaction'); } } catch (error) { console.log('Audio context start failed:', error); audioContextStarted = false; // Allow retry } } }; // Request MIDI access on first user interaction let midiAccessRequested = false; const tryRequestMIDIAccess = async () => { if (!midiAccessRequested) { midiAccessRequested = true; try { const midiInput = KGMidiInput.instance(); if (!midiInput.getMIDIAccess()) { await midiInput.requestMIDIAccess(); console.log('MIDI access granted on first user interaction'); } } catch (error) { console.log('MIDI access failed:', error); midiAccessRequested = false; // Allow retry } } }; // Combined handler for first user interaction const handleFirstInteraction = async () => { await tryStartAudioContext(); await tryRequestMIDIAccess(); // Remove listeners after first attempt document.removeEventListener('click', handleFirstInteraction); document.removeEventListener('touchstart', handleFirstInteraction); document.removeEventListener('keydown', handleFirstInteraction); }; // Listen for first user interaction document.addEventListener('click', handleFirstInteraction, { passive: true }); document.addEventListener('touchstart', handleFirstInteraction, { passive: true }); document.addEventListener('keydown', handleFirstInteraction, { passive: true }); // Add event listener for beforeunload event window.addEventListener('beforeunload', (event) => { // Cancel the event event.preventDefault(); // Chrome requires returnValue to be set event.returnValue = ''; // The browser will show a standard confirmation dialog // with a message like "Changes you made may not be saved." // Note: Custom messages are no longer supported in modern browsers for security reasons }); root.render( , ); } // end isSecureContext else
K.G.Studio uses the browser's Origin Private File System (OPFS) to store projects, which requires a secure context.
https://
http://
localhost
127.0.0.1