@@ -305,7 +305,7 @@ <h3 class="text-xl font-black text-white mb-6 tracking-tighter italic">OPEN NEW
305305
306306 // --- 0. CTRL+S SAVE ---
307307 document . addEventListener ( 'keydown' , e => {
308- if ( ( e . ctrlKey || e . metaKey ) && e . shiftKey && e . key === 'l' ) { e . preventDefault ( ) ; clearTerminal ( ) ; }
308+ if ( ( e . ctrlKey || e . metaKey ) && e . shiftKey && ( e . key === 'l' || e . key === 'L' ) ) { e . preventDefault ( ) ; clearTerminal ( ) ; }
309309 if ( ( e . ctrlKey || e . metaKey ) && e . key === 's' ) { e . preventDefault ( ) ; triggerSave ( ) ; }
310310 if ( ( e . ctrlKey || e . metaKey ) && e . shiftKey && e . key === 'M' ) { e . preventDefault ( ) ; const mc = e . target . closest ( '.monaco-container' ) || e . target . closest ( '.code-main-container' ) ; if ( mc ) toggleMaximize ( mc . id ) ; }
311311 if ( ( e . ctrlKey || e . metaKey ) && e . key === "'" ) { e . preventDefault ( ) ; runCode ( ) ; }
@@ -469,6 +469,7 @@ <h3 class="text-xl font-black text-white mb-6 tracking-tighter italic">OPEN NEW
469469
470470 // --- 3. EDITOR & VIEW MGMT ---
471471 function selectTab ( id ) {
472+ cancelTestGen ( ) ;
472473 activeTabId = id ;
473474 genMgr . clearAll ( ) ;
474475
@@ -558,12 +559,15 @@ <h3 class="text-xl font-black text-white mb-6 tracking-tighter italic">OPEN NEW
558559 spinnerInterval = setInterval ( ( ) => { i = ( i + 1 ) % frames . length ; term . innerText = `${ frames [ i ] } ${ msg } ` ; syncDockedTerminal ( ) ; } , 80 ) ;
559560 return term ;
560561 }
561- function termDone ( text , color ) {
562+ function termDone ( text , color , tabId ) {
562563 clearInterval ( spinnerInterval ) ;
563- const term = document . getElementById ( 'terminal' ) ;
564- term . innerText = text ; term . style . color = color || '#10b981' ;
565- saveTerminal ( text , color || '#10b981' ) ;
566- syncDockedTerminal ( ) ;
564+ const tid = tabId || activeTabId ;
565+ if ( tabs [ tid ] ) { tabs [ tid ] . terminalOutput = text ; tabs [ tid ] . terminalColor = color || '#10b981' ; }
566+ if ( tid === activeTabId ) {
567+ const term = document . getElementById ( 'terminal' ) ;
568+ term . innerText = text ; term . style . color = color || '#10b981' ;
569+ syncDockedTerminal ( ) ;
570+ }
567571 }
568572
569573 async function geminiCall ( prompt ) {
@@ -585,24 +589,26 @@ <h3 class="text-xl font-black text-white mb-6 tracking-tighter italic">OPEN NEW
585589 }
586590
587591 async function askGemini ( ) {
588- const key = `analysis_${ activeTabId } ` ;
592+ const tabId = activeTabId ;
593+ const key = `analysis_${ tabId } ` ;
589594 if ( ! genMgr . acquire ( key ) ) return ;
590595 const body = document . getElementById ( 'aiAnalysisBody' ) ;
591596 body . innerHTML = "<p class='animate-pulse text-blue-400 font-bold'>ANALYZING PATTERNS...</p>" ;
592597 termSpinner ( 'Analyzing patterns...' ) ;
593598 try {
594- const text = await geminiCall ( `Explain optimal approach:\n${ tabs [ activeTabId ] . statement } ` ) ;
595- tabs [ activeTabId ] . aiAnalysis = text ;
596- body . innerHTML = marked . parse ( text ) ;
599+ const text = await geminiCall ( `Explain optimal approach:\n${ tabs [ tabId ] . statement } ` ) ;
600+ tabs [ tabId ] . aiAnalysis = text ;
601+ if ( activeTabId === tabId ) body . innerHTML = marked . parse ( text ) ;
597602 triggerSave ( ) ;
598- termDone ( '>>> Analysis complete.' , '#10b981' ) ;
599- } catch ( e ) { body . innerHTML = `<span class="text-red-400">${ e . message } </span>` ; termDone ( '>>> Analysis failed: ' + e . message , '#f87171' ) ; }
603+ termDone ( '>>> Analysis complete.' , '#10b981' , tabId ) ;
604+ } catch ( e ) { if ( activeTabId === tabId ) body . innerHTML = `<span class="text-red-400">${ e . message } </span>` ; termDone ( '>>> Analysis failed: ' + e . message , '#f87171' , tabId ) ; }
600605 finally { genMgr . release ( ) ; }
601606 }
602607
603608 async function validateStep ( stepKey , btn ) {
604- const content = editors [ `${ activeTabId } _${ stepKey } ` ] ?. getValue ( ) || '' ;
605- const key = `validate_${ activeTabId } _${ stepKey } _${ content . length } ` ;
609+ const tabId = activeTabId ;
610+ const content = editors [ `${ tabId } _${ stepKey } ` ] ?. getValue ( ) || '' ;
611+ const key = `validate_${ tabId } _${ stepKey } _${ content . length } ` ;
606612 if ( ! genMgr . acquire ( key ) ) return ;
607613 const originalText = btn . innerText ;
608614 btn . innerText = "VALIDATING..." ;
@@ -611,13 +617,12 @@ <h3 class="text-xl font-black text-white mb-6 tracking-tighter italic">OPEN NEW
611617 if ( feedbackDiv ) feedbackDiv . classList . add ( 'hidden' ) ;
612618 termSpinner ( `Validating step ${ stepKey } ...` ) ;
613619 try {
614- const text = await geminiCall ( `Problem: ${ tabs [ activeTabId ] . statement } \nStep: ${ stepKey } \nInput: ${ content } \n\nGive short feedback (<60 words)` ) ;
615- feedbackDiv . innerHTML = marked . parse ( text ) ;
616- feedbackDiv . classList . remove ( 'hidden' ) ;
617- termDone ( `>>> Step ${ stepKey } validated.` , '#10b981' ) ;
620+ const text = await geminiCall ( `Problem: ${ tabs [ tabId ] . statement } \nStep: ${ stepKey } \nInput: ${ content } \n\nGive short feedback (<60 words)` ) ;
621+ if ( activeTabId === tabId ) { feedbackDiv . innerHTML = marked . parse ( text ) ; feedbackDiv . classList . remove ( 'hidden' ) ; }
622+ termDone ( `>>> Step ${ stepKey } validated.` , '#10b981' , tabId ) ;
618623 } catch ( e ) {
619- if ( feedbackDiv ) { feedbackDiv . innerHTML = `<span class="text-red-400">${ e . message } </span>` ; feedbackDiv . classList . remove ( 'hidden' ) ; }
620- termDone ( '>>> Validation failed: ' + e . message , '#f87171' ) ;
624+ if ( activeTabId === tabId && feedbackDiv ) { feedbackDiv . innerHTML = `<span class="text-red-400">${ e . message } </span>` ; feedbackDiv . classList . remove ( 'hidden' ) ; }
625+ termDone ( '>>> Validation failed: ' + e . message , '#f87171' , tabId ) ;
621626 genMgr . clearSection ( key ) ;
622627 } finally {
623628 btn . innerText = originalText ;
@@ -637,27 +642,28 @@ <h3 class="text-xl font-black text-white mb-6 tracking-tighter italic">OPEN NEW
637642 generateBoilerplate ( lang ) ;
638643 }
639644
640- function saveTerminal ( text , color ) {
641- if ( ! activeTabId ) return ;
642- tabs [ activeTabId ] . terminalOutput = text ;
643- tabs [ activeTabId ] . terminalColor = color ;
645+ function saveTerminal ( text , color , tabId ) {
646+ const tid = tabId || activeTabId ;
647+ if ( ! tid || ! tabs [ tid ] ) return ;
648+ tabs [ tid ] . terminalOutput = text ;
649+ tabs [ tid ] . terminalColor = color ;
644650 }
645651
646652 async function generateBoilerplate ( lang ) {
647- const key = `boilerplate_${ activeTabId } _${ lang } ` ;
653+ const tabId = activeTabId ;
654+ const key = `boilerplate_${ tabId } _${ lang } ` ;
648655 if ( ! genMgr . acquire ( key ) ) return ;
649656 termSpinner ( `Generating ${ lang } boilerplate...` ) ;
650657 try {
651658 const javaHint = lang === 'java' ? ' For Java: use public class Main with public static void main.' : '' ;
652- const prompt = `Generate ONLY ${ lang } boilerplate (no solution) for:\n${ tabs [ activeTabId ] . statement } \n\nUse function name: solve. Include main/entry point.${ javaHint } ONLY code, NO markdown fences.` ;
659+ const prompt = `Generate ONLY ${ lang } boilerplate (no solution) for:\n${ tabs [ tabId ] . statement } \n\nUse function name: solve. Include main/entry point.${ javaHint } ONLY code, NO markdown fences.` ;
653660 const raw = await geminiCall ( prompt ) ;
654661 const code = raw . replace ( / ` ` ` \w * / g, '' ) . replace ( / ` ` ` / g, '' ) . trim ( ) ;
655- const ed = editors [ `${ activeTabId } _code` ] ;
656- monaco . editor . setModelLanguage ( ed . getModel ( ) , lang ) ;
657- ed . setValue ( code ) ;
658- termDone ( '>>> Boilerplate generated.' , '#10b981' ) ;
662+ const ed = editors [ `${ tabId } _code` ] ;
663+ if ( ed ) { monaco . editor . setModelLanguage ( ed . getModel ( ) , lang ) ; ed . setValue ( code ) ; }
664+ termDone ( '>>> Boilerplate generated.' , '#10b981' , tabId ) ;
659665 } catch ( e ) {
660- termDone ( '>>> ERROR: ' + e . message , '#f87171' ) ;
666+ termDone ( '>>> ERROR: ' + e . message , '#f87171' , tabId ) ;
661667 genMgr . clearSection ( key ) ;
662668 } finally { genMgr . release ( ) ; }
663669 }
@@ -680,30 +686,31 @@ <h3 class="text-xl font-black text-white mb-6 tracking-tighter italic">OPEN NEW
680686 update ( ) ;
681687 testGenTimer = setInterval ( ( ) => {
682688 testGenCountdown -- ;
683- if ( testGenCountdown <= 0 ) { clearInterval ( testGenTimer ) ; testGenTimer = null ; generateTests ( ) ; return ; }
689+ if ( testGenCountdown <= 0 ) { clearInterval ( testGenTimer ) ; testGenTimer = null ; generateTests ( true ) ; return ; }
684690 update ( ) ;
685691 } , 1000 ) ;
686692 }
687693
688- async function generateTests ( ) {
694+ async function generateTests ( silent ) {
689695 cancelTestGen ( ) ;
690- const code = editors [ `${ activeTabId } _code` ] ?. getValue ( ) || '' ;
691- const key = `tests_${ activeTabId } _${ code . length } ` ;
696+ const tabId = activeTabId ;
697+ const code = editors [ `${ tabId } _code` ] ?. getValue ( ) || '' ;
698+ const key = `tests_${ tabId } _${ code . length } ` ;
692699 if ( ! genMgr . acquire ( key ) ) return ;
693700 const btns = getGenTestBtns ( ) ;
694701 btns . forEach ( b => { b . innerHTML = 'Generating...' ; b . classList . add ( 'animate-pulse' ) ; } ) ;
695- termSpinner ( 'Generating tests...' ) ;
702+ if ( ! silent ) termSpinner ( 'Generating tests...' ) ;
696703 try {
697- const lang = document . getElementById ( 'langSelect' ) ?. value || 'python' ;
698- const prompt = `Generate unit tests for this ${ lang } code. Problem: ${ tabs [ activeTabId ] . statement } \nCode:\n${ code } \n\nPrint a table of pass/fail results. Return ONLY code, NO markdown fences.` ;
704+ const lang = tabs [ tabId ] . lang || 'python' ;
705+ const prompt = `Generate unit tests for this ${ lang } code. Problem: ${ tabs [ tabId ] . statement } \nCode:\n${ code } \n\nPrint a table of pass/fail results. Return ONLY code, NO markdown fences.` ;
699706 const raw = await geminiCall ( prompt ) ;
700707 const script = raw . replace ( / ` ` ` \w * / g, '' ) . replace ( / ` ` ` / g, '' ) . trim ( ) ;
701- editors [ `${ activeTabId } _tests` ] . setValue ( script ) ;
702- tabs [ activeTabId ] . steps . tests = script ;
708+ const ed = editors [ `${ tabId } _tests` ] ;
709+ if ( ed ) ed . setValue ( script ) ;
710+ tabs [ tabId ] . steps . tests = script ;
703711 triggerSave ( ) ;
704- termDone ( '>>> Tests generated. Running...' , '#10b981' ) ;
705- await runTests ( ) ;
706- } catch ( e ) { termDone ( '>>> ERROR: ' + e . message , '#f87171' ) ; genMgr . clearSection ( key ) ; }
712+ if ( ! silent ) { termDone ( '>>> Tests generated. Running...' , '#10b981' , tabId ) ; if ( activeTabId === tabId ) await runTests ( ) ; }
713+ } catch ( e ) { if ( ! silent ) termDone ( '>>> ERROR: ' + e . message , '#f87171' , tabId ) ; genMgr . clearSection ( key ) ; }
707714 finally { btns . forEach ( b => { b . innerHTML = 'Gen Tests' ; b . classList . remove ( 'animate-pulse' ) ; } ) ; genMgr . release ( ) ; }
708715 }
709716
@@ -763,55 +770,57 @@ <h3 class="text-xl font-black text-white mb-6 tracking-tighter italic">OPEN NEW
763770 }
764771
765772 async function runCode ( ) {
773+ const tabId = activeTabId ;
766774 const selectedLang = document . getElementById ( 'langSelect' ) . value ;
767- const fullScript = editors [ `${ activeTabId } _code` ] . getValue ( ) ;
775+ const fullScript = editors [ `${ tabId } _code` ] . getValue ( ) ;
768776
769777 if ( selectedLang !== 'javascript' ) {
770778 termSpinner ( 'Sanity check...' ) ;
771779 const check = await sanityCheck ( fullScript , selectedLang ) ;
772- if ( ! check . safe ) { termDone ( `[BLOCKED] ${ check . reason } ` , '#f87171' ) ; return ; }
780+ if ( ! check . safe ) { termDone ( `[BLOCKED] ${ check . reason } ` , '#f87171' , tabId ) ; return ; }
773781 }
774782
775783 termSpinner ( 'Running...' ) ;
776784 let runSuccess = false ;
777785 if ( selectedLang === 'javascript' ) {
778786 const r = await runJsInSandbox ( fullScript ) ;
779787 runSuccess = r . success ;
780- termDone ( r . output || '(no output)' , r . success ? '#10b981' : '#f87171' ) ;
788+ termDone ( r . output || '(no output)' , r . success ? '#10b981' : '#f87171' , tabId ) ;
781789 } else if ( serverAvailable ) {
782790 try {
783791 const langMap = { python :'Python' , java :'Java' } ;
784792 const res = await fetch ( '/run_code' , { method :'POST' , headers :{ 'Content-Type' :'application/json' } , body :JSON . stringify ( { code :fullScript , language :langMap [ selectedLang ] } ) } ) ;
785793 const d = await res . json ( ) ;
786794 runSuccess = d . success ;
787- termDone ( d . output , d . success ? '#10b981' : '#f87171' ) ;
788- } catch ( e ) { termDone ( 'ERROR: ' + e . message , '#f87171' ) ; }
789- } else { termDone ( `[ERROR] ${ selectedLang } requires server.` , '#f87171' ) ; }
790- if ( runSuccess ) scheduleTestGen ( ) ;
795+ termDone ( d . output , d . success ? '#10b981' : '#f87171' , tabId ) ;
796+ } catch ( e ) { termDone ( 'ERROR: ' + e . message , '#f87171' , tabId ) ; }
797+ } else { termDone ( `[ERROR] ${ selectedLang } requires server.` , '#f87171' , tabId ) ; }
798+ if ( runSuccess && activeTabId === tabId && ! ( editors [ ` ${ tabId } _tests` ] ?. getValue ( ) ?. trim ( ) ) ) scheduleTestGen ( ) ;
791799 }
792800
793801 async function runTests ( ) {
802+ const tabId = activeTabId ;
794803 const selectedLang = document . getElementById ( 'langSelect' ) . value ;
795- const fullScript = editors [ `${ activeTabId } _code` ] . getValue ( ) + '\n\n' + editors [ `${ activeTabId } _tests` ] . getValue ( ) ;
804+ const fullScript = editors [ `${ tabId } _code` ] . getValue ( ) + '\n\n' + editors [ `${ tabId } _tests` ] . getValue ( ) ;
796805
797806 if ( selectedLang !== 'javascript' ) {
798807 termSpinner ( 'Sanity check...' ) ;
799808 const check = await sanityCheck ( fullScript , selectedLang ) ;
800- if ( ! check . safe ) { termDone ( `[BLOCKED] ${ check . reason } ` , '#f87171' ) ; return ; }
809+ if ( ! check . safe ) { termDone ( `[BLOCKED] ${ check . reason } ` , '#f87171' , tabId ) ; return ; }
801810 }
802811
803812 termSpinner ( 'Running code + tests...' ) ;
804813 if ( selectedLang === 'javascript' ) {
805814 const r = await runJsInSandbox ( fullScript ) ;
806- termDone ( r . output || '(no output)' , r . success ? '#10b981' : '#f87171' ) ;
815+ termDone ( r . output || '(no output)' , r . success ? '#10b981' : '#f87171' , tabId ) ;
807816 } else if ( serverAvailable ) {
808817 try {
809818 const langMap = { python :'Python' , java :'Java' } ;
810819 const res = await fetch ( '/run_code' , { method :'POST' , headers :{ 'Content-Type' :'application/json' } , body :JSON . stringify ( { code :fullScript , language :langMap [ selectedLang ] } ) } ) ;
811820 const d = await res . json ( ) ;
812- termDone ( d . output , d . success ? '#10b981' : '#f87171' ) ;
813- } catch ( e ) { termDone ( 'ERROR: ' + e . message , '#f87171' ) ; }
814- } else { termDone ( `[ERROR] ${ selectedLang } requires server.` , '#f87171' ) ; }
821+ termDone ( d . output , d . success ? '#10b981' : '#f87171' , tabId ) ;
822+ } catch ( e ) { termDone ( 'ERROR: ' + e . message , '#f87171' , tabId ) ; }
823+ } else { termDone ( `[ERROR] ${ selectedLang } requires server.` , '#f87171' , tabId ) ; }
815824 }
816825
817826 // --- 5. PERSISTENCE (localStorage always, server mirror when available) ---
@@ -1019,6 +1028,7 @@ <h3 class="text-xl font-black text-white mb-6 tracking-tighter italic">OPEN NEW
10191028 }
10201029
10211030 async function submitCtxQuestion ( ) {
1031+ const tabId = activeTabId ;
10221032 const inp = document . getElementById ( 'ctxInput' ) ;
10231033 const raw = inp . value . trim ( ) ;
10241034 if ( ! raw ) return ;
@@ -1035,14 +1045,15 @@ <h3 class="text-xl font-black text-white mb-6 tracking-tighter italic">OPEN NEW
10351045 renderChatThreads ( ) ;
10361046
10371047 const refCtx = buildRefContext ( refs ) ;
1038- const answer = await callChat ( cleanText , ctxSelection , ctxSource , refCtx ) ;
1048+ const answer = await callChat ( cleanText , ctxSelection , ctxSource , refCtx , tabId ) ;
10391049 threads [ hash ] . messages . push ( { question : cleanText , answer } ) ;
1040- renderChatThreads ( ) ;
1050+ if ( activeTabId === tabId ) renderChatThreads ( ) ;
10411051 triggerSave ( ) ;
10421052 }
10431053
10441054 async function sendThreadReply ( ) {
10451055 if ( ! replyingToThread ) return ;
1056+ const tabId = activeTabId ;
10461057 const inp = document . getElementById ( 'threadReplyInput' ) ;
10471058 const raw = inp . value . trim ( ) ;
10481059 if ( ! raw ) return ;
@@ -1054,19 +1065,19 @@ <h3 class="text-xl font-black text-white mb-6 tracking-tighter italic">OPEN NEW
10541065 if ( ! t ) return ;
10551066
10561067 const refCtx = buildRefContext ( refs ) ;
1057- // Thread-local context only: original selection + this thread's history
10581068 let threadCtx = `Original selection: ${ t . selection } \n` ;
10591069 t . messages . forEach ( m => { threadCtx += `Q: ${ m . question } \nA: ${ m . answer } \n` ; } ) ;
10601070
1061- const answer = await callChat ( cleanText , threadCtx , t . source , refCtx ) ;
1071+ const answer = await callChat ( cleanText , threadCtx , t . source , refCtx , tabId ) ;
10621072 t . messages . push ( { question : cleanText , answer } ) ;
1063- renderChatThreads ( ) ;
1073+ if ( activeTabId === tabId ) renderChatThreads ( ) ;
10641074 triggerSave ( ) ;
10651075 }
10661076
1067- async function callChat ( question , selection , source , refContext ) {
1077+ async function callChat ( question , selection , source , refContext , tabId ) {
10681078 try {
1069- const prompt = `You are a concise coding tutor. Answer in <100 words.\n\nProblem: ${ ( tabs [ activeTabId ] ?. statement || '' ) . slice ( 0 , 500 ) } \nSelected text from [${ source } ]: ${ ( selection || '' ) . slice ( 0 , 500 ) } \n${ refContext ? 'Referenced threads:\n' + refContext : '' } \nQuestion: ${ question } ` ;
1079+ const tid = tabId || activeTabId ;
1080+ const prompt = `You are a concise coding tutor. Answer in <100 words.\n\nProblem: ${ ( tabs [ tid ] ?. statement || '' ) . slice ( 0 , 500 ) } \nSelected text from [${ source } ]: ${ ( selection || '' ) . slice ( 0 , 500 ) } \n${ refContext ? 'Referenced threads:\n' + refContext : '' } \nQuestion: ${ question } ` ;
10701081 return await geminiCall ( prompt ) ;
10711082 } catch ( e ) { return 'Error: ' + e . message ; }
10721083 }
@@ -1082,6 +1093,7 @@ <h3 class="text-xl font-black text-white mb-6 tracking-tighter italic">OPEN NEW
10821093 }
10831094
10841095 async function sendAdHocChat ( ) {
1096+ const tabId = activeTabId ;
10851097 const inp = document . getElementById ( 'adHocChatInput' ) ;
10861098 const raw = inp . value . trim ( ) ;
10871099 if ( ! raw ) return ;
@@ -1097,8 +1109,9 @@ <h3 class="text-xl font-black text-white mb-6 tracking-tighter italic">OPEN NEW
10971109 renderChatThreads ( ) ;
10981110
10991111 const refCtx = buildRefContext ( refs ) ;
1100- const answer = await callChat ( cleanText , '' , 'general' , refCtx ) ;
1112+ const answer = await callChat ( cleanText , '' , 'general' , refCtx , tabId ) ;
11011113 threads [ hash ] . messages . push ( { question : cleanText , answer } ) ;
1114+ if ( activeTabId === tabId ) renderChatThreads ( ) ;
11021115 renderChatThreads ( ) ;
11031116 triggerSave ( ) ;
11041117 }
0 commit comments