fix python code

master v2.4
a2nr 2026-04-01 12:56:27 +07:00
parent 0e944e5224
commit 09827bf3ff
5 changed files with 224 additions and 247 deletions

View File

@ -1,34 +1,32 @@
<script lang="ts">
interface OutputSection {
export interface OutputSection {
output?: string;
error?: string;
loading?: boolean;
success?: boolean | null;
}
interface Props {
code?: OutputSection;
circuit?: OutputSection;
hasCode?: boolean;
hasCircuit?: boolean;
export interface OutputEntry {
key: string;
label: string;
icon: string;
data: OutputSection;
placeholder: string;
loadingText: string;
}
let {
code = { output: '', error: '', loading: false, success: null },
circuit = { output: '', error: '', loading: false, success: null },
hasCode = true,
hasCircuit = false,
}: Props = $props();
interface Props {
sections?: OutputEntry[];
}
// Determine overall loading state for header badge
let anyLoading = $derived(code.loading || circuit.loading);
let { sections = [] }: Props = $props();
let anyLoading = $derived(sections.some(s => s.data.loading));
// Determine overall success state: null if idle, true if all ran successfully, false if any error
let overallSuccess = $derived.by(() => {
const codeRan = code.success !== null && code.success !== undefined;
const circuitRan = circuit.success !== null && circuit.success !== undefined;
if (!codeRan && !circuitRan) return null;
if ((codeRan && code.success === false) || (circuitRan && circuit.success === false)) return false;
const ran = sections.filter(s => s.data.success !== null && s.data.success !== undefined);
if (ran.length === 0) return null;
if (ran.some(s => s.data.success === false)) return false;
return true;
});
</script>
@ -46,42 +44,23 @@
</div>
<div class="output-sections">
<!-- Code output section -->
{#if hasCode}
<div class="output-section" class:has-error={!!code.error} class:has-success={code.success === true}>
{#each sections as sec (sec.key)}
<div class="output-section" class:has-error={!!sec.data.error} class:has-success={sec.data.success === true}>
<div class="section-label">
<span class="section-icon">&#x1F4BB;</span> Code
{#if code.loading}
<span class="section-badge running">Compiling...</span>
{:else if code.success === true}
<span class="section-icon">{sec.icon}</span> {sec.label}
{#if sec.data.loading}
<span class="section-badge running">{sec.loadingText}</span>
{:else if sec.data.success === true}
<span class="section-badge success">OK</span>
{:else if code.success === false}
{:else if sec.data.success === false}
<span class="section-badge error">Error</span>
{/if}
</div>
<pre class="output-body">{#if code.loading}Mengompilasi dan menjalankan kode...{:else if code.error}{code.error}{:else if code.output}{code.output}{:else}<span class="placeholder">Klik "Run" untuk menjalankan kode</span>{/if}</pre>
<pre class="output-body">{#if sec.data.loading}{sec.loadingText}{:else if sec.data.error}{sec.data.error}{:else if sec.data.output}{sec.data.output}{:else}<span class="placeholder">{sec.placeholder}</span>{/if}</pre>
</div>
{/if}
{/each}
<!-- Circuit output section -->
{#if hasCircuit}
<div class="output-section" class:has-error={!!circuit.error} class:has-success={circuit.success === true}>
<div class="section-label">
<span class="section-icon">&#x26A1;</span> Circuit
{#if circuit.loading}
<span class="section-badge running">Evaluating...</span>
{:else if circuit.success === true}
<span class="section-badge success">OK</span>
{:else if circuit.success === false}
<span class="section-badge error">Error</span>
{/if}
</div>
<pre class="output-body">{#if circuit.loading}Mengevaluasi rangkaian...{:else if circuit.error}{circuit.error}{:else if circuit.output}{circuit.output}{:else}<span class="placeholder">Klik "Cek Rangkaian" untuk mengevaluasi</span>{/if}</pre>
</div>
{/if}
<!-- Fallback when neither section has run -->
{#if !hasCode && !hasCircuit}
{#if sections.length === 0}
<pre class="output-body"><span class="placeholder">Tidak ada output</span></pre>
{/if}
</div>

View File

@ -5,6 +5,7 @@
isMobile: boolean;
mobileMode: 'hidden' | 'half' | 'full';
activeTab: TabType;
currentLanguage: string;
hasInfo: boolean;
hasExercise: boolean;
activeTabs: string[];
@ -20,6 +21,7 @@
isMobile,
mobileMode = $bindable(),
activeTab = $bindable(),
currentLanguage = $bindable(),
hasInfo,
hasExercise,
activeTabs,
@ -33,9 +35,12 @@
let touchStartY = 0;
const hasC = $derived(activeTabs?.includes('c') ?? false);
const hasPython = $derived(activeTabs?.includes('python') ?? false);
const hasCodeEditor = $derived(
!activeTabs || activeTabs.length === 0 || activeTabs.includes('c') || activeTabs.includes('python')
!activeTabs || activeTabs.length === 0 || hasC || hasPython
);
const hasMultiLang = $derived(hasC && hasPython);
const hasCircuit = $derived(activeTabs?.includes('circuit') ?? false);
function cycleMobileSheet() {
@ -70,8 +75,13 @@
<button class="chrome-tab" class:active={activeTab === 'exercise'} onclick={() => (activeTab = 'exercise')}>Exercise</button>
{/if}
{#if hasCodeEditor}
{#if hasMultiLang}
<button class="chrome-tab" class:active={activeTab === 'editor' && currentLanguage === 'c'} onclick={() => { activeTab = 'editor'; currentLanguage = 'c'; }}>C</button>
<button class="chrome-tab" class:active={activeTab === 'editor' && currentLanguage === 'python'} onclick={() => { activeTab = 'editor'; currentLanguage = 'python'; }}>Python</button>
{:else}
<button class="chrome-tab" class:active={activeTab === 'editor'} onclick={() => (activeTab = 'editor')}>Code</button>
{/if}
{/if}
{#if hasCircuit}
<button class="chrome-tab" class:active={activeTab === 'circuit'} onclick={() => (activeTab = 'circuit')}>Circuit</button>
{/if}

View File

@ -0,0 +1,44 @@
/**
* Pure helper functions for exercise evaluation.
*/
import type { CircuitJSApi } from '$types/circuitjs';
/** Check if code contains all required key_text keywords (one per line). */
export function checkKeyText(code: string, keyText: string): boolean {
if (!keyText.trim()) return true;
const keys = keyText.split('\n').map(k => k.trim()).filter(k => k.length > 0);
return keys.every(key => code.includes(key));
}
export interface NodeResult {
passed: boolean;
message: string;
}
/** Validate circuit node voltages against expected state. */
export function validateNodes(
simApi: CircuitJSApi,
nodes: Record<string, { voltage: number; tolerance?: number }>
): { allPassed: boolean; messages: string[] } {
let allPassed = true;
const messages: string[] = [];
for (const [nodeName, criteria] of Object.entries(nodes)) {
const actualV = simApi.getNodeVoltage(nodeName);
if (actualV === undefined || actualV === null) {
allPassed = false;
messages.push(`❌ Node '${nodeName}' tidak ditemukan.`);
continue;
}
const tol = criteria.tolerance || 0.1;
if (Math.abs(actualV - criteria.voltage) <= tol) {
messages.push(`✅ Node '${nodeName}': Tegangan ${actualV.toFixed(2)}V (Sesuai)`);
} else {
allPassed = false;
messages.push(`❌ Node '${nodeName}': Tegangan ${actualV.toFixed(2)}V (Harusnya ~${criteria.voltage}V)`);
}
}
return { allPassed, messages };
}

View File

@ -3,11 +3,12 @@
import { beforeNavigate } from '$app/navigation';
import CodeEditor from '$components/CodeEditor.svelte';
import CircuitEditor from '$components/CircuitEditor.svelte';
import OutputPanel from '$components/OutputPanel.svelte';
import OutputPanel, { type OutputEntry } from '$components/OutputPanel.svelte';
import CelebrationOverlay from '$components/CelebrationOverlay.svelte';
import WorkspaceHeader from '$components/WorkspaceHeader.svelte';
import LessonList from '$components/LessonList.svelte';
import { compileCode, trackProgress } from '$services/api';
import { checkKeyText, validateNodes } from '$services/exercise';
import { auth, authLoggedIn } from '$stores/auth';
import { lessonContext } from '$stores/lessonContext';
import { noSelect } from '$actions/noSelect';
@ -26,17 +27,20 @@
let data = $state<LessonContent | null>(null);
let lessonCompleted = $state(false);
let currentCode = $state('');
let currentLanguage = $state<string>('c');
// Separate output state for code and circuit
let codeOutput = $state('');
let codeError = $state('');
let codeLoading = $state(false);
let codeSuccess = $state<boolean | null>(null);
// Per-language code tracking
let cCode = $state('');
let pythonCode = $state('');
let circuitOutput = $state('');
let circuitError = $state('');
let circuitLoading = $state(false);
let circuitSuccess = $state<boolean | null>(null);
// Output state per language + circuit
const freshOutput = () => ({ output: '', error: '', loading: false, success: null as boolean | null });
let cOut = $state(freshOutput());
let pyOut = $state(freshOutput());
let circuitOut = $state(freshOutput());
// Helper: get the active code output object for current language
function getCodeOut() { return currentLanguage === 'python' ? pyOut : cOut; }
// AND-logic: track whether each exercise type has passed (persists across runs)
let codePassed = $state(false);
@ -49,7 +53,23 @@
);
// Derived: any loading state (for disabling Run button)
let compiling = $derived(codeLoading || circuitLoading);
let compiling = $derived(cOut.loading || pyOut.loading || circuitOut.loading);
// Build output sections for OutputPanel
let outputSections = $derived.by(() => {
const tabs = data?.active_tabs ?? [];
const secs: OutputEntry[] = [];
if (tabs.includes('c') || (!tabs.length && !tabs.includes('python'))) {
secs.push({ key: 'c', label: 'C', icon: '\u{1F4BB}', data: cOut, placeholder: 'Klik "Run" untuk menjalankan kode C', loadingText: 'Mengompilasi C...' });
}
if (tabs.includes('python')) {
secs.push({ key: 'python', label: 'Python', icon: '\u{1F40D}', data: pyOut, placeholder: 'Klik "Run" untuk menjalankan kode Python', loadingText: 'Menjalankan Python...' });
}
if (tabs.includes('circuit')) {
secs.push({ key: 'circuit', label: 'Circuit', icon: '\u26A1', data: circuitOut, placeholder: 'Klik "Cek Rangkaian" untuk mengevaluasi', loadingText: 'Mengevaluasi rangkaian...' });
}
return secs;
});
// UI state
let showSolution = $state(false);
@ -93,19 +113,28 @@
if (lesson) {
data = lesson;
lessonCompleted = lesson.lesson_completed;
currentCode = lesson.initial_code_c || lesson.initial_python || lesson.initial_code || '';
codeOutput = '';
codeError = '';
codeSuccess = null;
circuitOutput = '';
circuitError = '';
circuitSuccess = null;
// Initialize per-language code
cCode = lesson.initial_code_c || '';
pythonCode = lesson.initial_python || '';
// Determine initial language (use local var to avoid reactive dependency on currentLanguage)
const hasC = lesson.active_tabs?.includes('c');
const hasPython = lesson.active_tabs?.includes('python');
const initLang = (hasPython && !hasC) ? 'python' : 'c';
currentLanguage = initLang;
prevLanguage = initLang;
currentCode = initLang === 'python' ? pythonCode : (cCode || lesson.initial_code || '');
cOut = freshOutput();
pyOut = freshOutput();
circuitOut = freshOutput();
codePassed = false;
circuitPassed = false;
showSolution = false;
if (lesson.lesson_info) activeTab = 'info';
else if (lesson.exercise_content) activeTab = 'exercise';
else if (lesson.active_tabs?.includes('circuit') && !lesson.active_tabs?.includes('c') && !lesson.active_tabs?.includes('python')) activeTab = 'circuit';
else if (lesson.active_tabs?.includes('circuit') && !hasC && !hasPython) activeTab = 'circuit';
else activeTab = 'editor';
mobileMode = 'half';
@ -119,6 +148,21 @@
}
});
// Switch editor content when language tab changes
let prevLanguage = $state<string>('c');
$effect(() => {
if (!data || currentLanguage === prevLanguage) return;
// Save current code to the previous language slot
const code = editor?.getCode() ?? currentCode;
if (prevLanguage === 'c') cCode = code;
else if (prevLanguage === 'python') pythonCode = code;
// Load code for the new language
const newCode = currentLanguage === 'python' ? pythonCode : (cCode || data.initial_code || '');
currentCode = newCode;
editor?.setCode(newCode);
prevLanguage = currentLanguage;
});
// Clear lesson context when leaving page
beforeNavigate(() => {
lessonContext.set(null);
@ -140,13 +184,6 @@
}
});
/** Check if student code contains all required key_text keywords. */
function checkKeyText(code: string, keyText: string): boolean {
if (!keyText.trim()) return true;
const keys = keyText.split('\n').map(k => k.trim()).filter(k => k.length > 0);
return keys.every(key => code.includes(key));
}
/** Mark lesson as complete: track progress + celebration. Called when ALL exercises pass. */
async function completeLesson() {
showCelebration = true;
@ -168,159 +205,102 @@
if (!data || !circuitEditor) return;
const simApi = circuitEditor.getApi();
if (!simApi) {
circuitError = "Simulator belum siap.";
circuitSuccess = false;
Object.assign(circuitOut, { error: "Simulator belum siap.", success: false });
activeTab = 'output';
return;
}
circuitLoading = true;
circuitOutput = 'Mengevaluasi rangkaian...';
circuitError = '';
circuitSuccess = null;
Object.assign(circuitOut, { loading: true, output: 'Mengevaluasi rangkaian...', error: '', success: null });
activeTab = 'output';
try {
// For hybrid lessons, use expected_circuit_output; otherwise fallback to expected_output
const circuitExpected = (isHybrid && data.expected_circuit_output)
? data.expected_circuit_output
: data.expected_output;
let expectedState: any = null;
try {
if (circuitExpected) {
expectedState = JSON.parse(circuitExpected);
}
} catch (e) {
circuitError = "Format EXPECTED_OUTPUT tidak valid (Harus JSON).";
circuitSuccess = false;
circuitLoading = false;
if (circuitExpected) expectedState = JSON.parse(circuitExpected);
} catch {
Object.assign(circuitOut, { error: "Format EXPECTED_OUTPUT tidak valid (Harus JSON).", success: false, loading: false });
return;
}
if (!expectedState) {
circuitOutput = "Tidak ada kriteria evaluasi yang ditetapkan.";
circuitSuccess = true;
circuitLoading = false;
Object.assign(circuitOut, { output: "Tidak ada kriteria evaluasi yang ditetapkan.", success: true, loading: false });
return;
}
let allPassed = true;
let messages: string[] = [];
if (expectedState.nodes) {
for (const [nodeName, criteria] of Object.entries<any>(expectedState.nodes)) {
const actualV = simApi.getNodeVoltage(nodeName);
if (actualV === undefined || actualV === null) {
allPassed = false;
messages.push(`❌ Node '${nodeName}' tidak ditemukan.`);
continue;
}
const expectedV = criteria.voltage;
const tol = criteria.tolerance || 0.1;
if (Math.abs(actualV - expectedV) <= tol) {
messages.push(`✅ Node '${nodeName}': Tegangan ${actualV.toFixed(2)}V (Sesuai)`);
} else {
allPassed = false;
messages.push(`❌ Node '${nodeName}': Tegangan ${actualV.toFixed(2)}V (Harusnya ~${expectedV}V)`);
}
}
}
// TODO: Element-level checking (e.g. expectedState.elements) belum diimplementasi.
// GWT getInfo() returns Java array yang sulit di-parse dari JS.
let { allPassed, messages } = expectedState.nodes
? validateNodes(simApi, expectedState.nodes)
: { allPassed: true, messages: [] as string[] };
const circuitText = circuitEditor.getCircuitText();
// For hybrid lessons, use key_text_circuit; otherwise fallback to key_text
const circuitKeyText = (isHybrid && data.key_text_circuit)
? data.key_text_circuit
: data.key_text;
const keyTextMatch = checkKeyText(circuitText, circuitKeyText ?? '');
if (!keyTextMatch) {
const circuitKeyText = (isHybrid && data.key_text_circuit) ? data.key_text_circuit : data.key_text;
if (!checkKeyText(circuitText, circuitKeyText ?? '')) {
allPassed = false;
messages.push(`❌ Komponen wajib belum lengkap (lihat instruksi).`);
}
circuitOutput = messages.join('\n');
circuitSuccess = allPassed;
circuitOut.output = messages.join('\n');
circuitOut.success = allPassed;
if (allPassed) {
circuitPassed = true;
if (isHybrid) {
circuitOutput += '\n✅ Rangkaian benar!';
if (!codePassed) {
circuitOutput += '\n⏳ Selesaikan juga tantangan kode untuk menyelesaikan pelajaran ini.';
}
circuitOut.output += '\n✅ Rangkaian benar!';
if (!codePassed) circuitOut.output += '\n⏳ Selesaikan juga tantangan kode untuk menyelesaikan pelajaran ini.';
}
if (checkAllPassed()) {
await completeLesson();
setTimeout(() => {
showCelebration = false;
activeTab = 'circuit';
}, 3000);
setTimeout(() => { showCelebration = false; activeTab = 'circuit'; }, 3000);
}
}
} catch (err: any) {
circuitError = `Evaluasi gagal: ${err.message}`;
circuitSuccess = false;
Object.assign(circuitOut, { error: `Evaluasi gagal: ${err.message}`, success: false });
} finally {
circuitLoading = false;
circuitOut.loading = false;
}
}
async function handleRun() {
if (activeTab === 'circuit') {
await evaluateCircuit();
return;
}
if (activeTab === 'circuit') { await evaluateCircuit(); return; }
if (!data) return;
codeLoading = true;
codeOutput = '';
codeError = '';
codeSuccess = null;
const out = getCodeOut();
Object.assign(out, { loading: true, output: '', error: '', success: null });
activeTab = 'output';
try {
const code = editor?.getCode() ?? currentCode;
const res = await compileCode({ code, language: data.language });
const res = await compileCode({ code, language: currentLanguage });
if (res.success) {
codeOutput = res.output;
codeSuccess = true;
if (!res.success) {
Object.assign(out, { error: res.error || 'Compilation failed', success: false });
return;
}
out.output = res.output;
out.success = true;
if (data.expected_output) {
const outputMatch = res.output.trim() === data.expected_output.trim();
const keyTextMatch = checkKeyText(code, data.key_text ?? '');
if (outputMatch && keyTextMatch) {
const passed = res.output.trim() === data.expected_output.trim() && checkKeyText(code, data.key_text ?? '');
if (passed) {
codePassed = true;
if (isHybrid && !circuitPassed) {
codeOutput += '\n✅ Kode benar!';
codeOutput += '\n⏳ Selesaikan juga tantangan rangkaian untuk menyelesaikan pelajaran ini.';
out.output += '\n✅ Kode benar!\n⏳ Selesaikan juga tantangan rangkaian untuk menyelesaikan pelajaran ini.';
}
if (checkAllPassed()) {
await completeLesson();
// Auto-show solution after celebration
if (data.solution_code) {
showSolution = true;
editor?.setCode(data.solution_code);
}
setTimeout(() => {
showCelebration = false;
activeTab = 'editor';
}, 3000);
if (data.solution_code) { showSolution = true; editor?.setCode(data.solution_code); }
setTimeout(() => { showCelebration = false; activeTab = 'editor'; }, 3000);
}
}
}
} else {
codeError = res.error || 'Compilation failed';
codeSuccess = false;
}
} catch {
codeError = 'Gagal terhubung ke server';
codeSuccess = false;
Object.assign(out, { error: 'Gagal terhubung ke server', success: false });
} finally {
codeLoading = false;
out.loading = false;
}
}
@ -328,15 +308,16 @@
if (!data) return;
if (activeTab === 'circuit') {
circuitEditor?.setCircuitText(data.initial_circuit || data.initial_code);
circuitOutput = '';
circuitError = '';
circuitSuccess = null;
Object.assign(circuitOut, freshOutput());
} else {
currentCode = data.initial_code;
editor?.setCode(data.initial_code);
codeOutput = '';
codeError = '';
codeSuccess = null;
const resetCode = currentLanguage === 'python'
? (data.initial_python || '')
: (data.initial_code_c || data.initial_code || '');
currentCode = resetCode;
if (currentLanguage === 'c') cCode = resetCode;
else pythonCode = resetCode;
editor?.setCode(resetCode);
Object.assign(getCodeOut(), freshOutput());
}
}
@ -403,6 +384,7 @@
{isMobile}
bind:mobileMode
bind:activeTab
bind:currentLanguage
hasInfo={!!data.lesson_info}
hasExercise={!!data.exercise_content}
activeTabs={data.active_tabs ?? []}
@ -481,18 +463,20 @@
{showSolution ? 'Sembunyikan Solusi' : 'Lihat Solusi'}
</button>
{/if}
<span class="lang-label">{data.language_display_name}</span>
<span class="lang-label">{currentLanguage === 'python' ? 'Python' : 'C'}</span>
</div>
<div class="panel">
{#key currentLanguage}
<CodeEditor
bind:this={editor}
code={currentCode}
language={data.language}
language={currentLanguage}
noPaste={true}
storageKey={($authLoggedIn && !showSolution) ? `elemes_draft_${slug}` : undefined}
storageKey={($authLoggedIn && !showSolution) ? `elemes_draft_${slug}_${currentLanguage}` : undefined}
onchange={(val) => { if (!showSolution) currentCode = val; }}
/>
{/key}
</div>
{#if data.expected_output}
@ -507,12 +491,7 @@
<!-- Output tab panel -->
<div class="tab-panel" class:tab-hidden={activeTab !== 'output'}>
<OutputPanel
code={{ output: codeOutput, error: codeError, loading: codeLoading, success: codeSuccess }}
circuit={{ output: circuitOutput, error: circuitError, loading: circuitLoading, success: circuitSuccess }}
hasCode={!data.active_tabs || data.active_tabs.length === 0 || data.active_tabs.includes('c') || data.active_tabs.includes('python')}
hasCircuit={data.active_tabs?.includes('circuit') ?? false}
/>
<OutputPanel sections={outputSections} />
</div>
</div>
@ -523,26 +502,35 @@
{/if}
<style>
/* ── Shared rich-text styles (.prose + .tab-content) ──── */
.tab-content {
font-size: 0.85rem;
padding: 0.75rem 0.5rem;
line-height: 1.65;
}
.tab-heading {
color: var(--color-primary);
font-size: 1.1rem;
margin-top: 0;
}
.prose :global(pre),
.tab-content :global(pre) {
background: var(--color-bg-secondary);
padding: 0.75rem;
border-radius: var(--radius);
overflow-x: auto;
}
.prose :global(code),
.tab-content :global(code) {
font-family: var(--font-mono);
font-size: 0.8rem;
font-size: 0.85rem;
}
.prose :global(p),
.tab-content :global(p) {
margin-bottom: 0.75rem;
}
.tab-content :global(h2),
.tab-content :global(h3) {
.prose :global(h2), .prose :global(h3),
.tab-content :global(h2), .tab-content :global(h3) {
margin-top: 1.25rem;
margin-bottom: 0.5rem;
}
@ -554,11 +542,6 @@
.tab-content :global(li) {
margin-bottom: 0.25rem;
}
.tab-heading {
color: var(--color-primary);
font-size: 1.1rem;
margin-top: 0;
}
/* ── Two-column layout ─────────────────────────────────── */
.lesson-layout {
@ -577,25 +560,6 @@
-webkit-touch-callout: none;
}
.prose :global(pre) {
background: var(--color-bg-secondary);
padding: 0.75rem;
border-radius: var(--radius);
overflow-x: auto;
}
.prose :global(code) {
font-family: var(--font-mono);
font-size: 0.85rem;
}
.prose :global(p) {
margin-bottom: 0.75rem;
}
.prose :global(h2),
.prose :global(h3) {
margin-top: 1.25rem;
margin-bottom: 0.5rem;
}
/* ── Editor area (docked mode) ──────────────────────────── */
.editor-area {
position: sticky;
@ -741,25 +705,11 @@
overscroll-behavior: contain;
}
/* ── Mobile full: expand content to fill ────────────── */
.editor-area.mobile-full .editor-body {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
}
.editor-area.mobile-full .tab-panel:not(.tab-hidden) {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
}
.editor-area.mobile-full .panel {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
}
.editor-area.mobile-full :global(.circuit-container) {
.editor-area.mobile-full .editor-body,
.editor-area.mobile-full .tab-panel:not(.tab-hidden),
.editor-area.mobile-full .panel,
.editor-area.mobile-full :global(.circuit-container),
.editor-area.mobile-full :global(.editor-wrapper) {
flex: 1;
display: flex;
flex-direction: column;
@ -769,12 +719,6 @@
flex: 1;
height: auto;
}
.editor-area.mobile-full :global(.editor-wrapper) {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
}
.editor-area.mobile-full :global(.cm-editor) {
flex: 1;
max-height: none;

View File

@ -90,8 +90,8 @@ def api_lesson(filename):
prev_lesson = all_lessons[current_idx - 1] if current_idx > 0 else None
next_lesson = all_lessons[current_idx + 1] if 0 <= current_idx < len(all_lessons) - 1 else None
# Derive language from active_tabs instead of global env var
if 'python' in active_tabs:
# Derive default language from active_tabs (frontend manages switching)
if 'python' in active_tabs and 'c' not in active_tabs:
programming_language = 'python'
else:
programming_language = 'c'