elemes/documentation.md

26 KiB

Elemes LMS — Dokumentasi Teknis

Project: LMS-C (Learning Management System untuk Pemrograman C & Arduino) Terakhir diupdate: 8 April 2026


Arsitektur

Internet (HTTPS :443)
    │
    ▼
Tailscale Funnel (elemes-ts)
    │
    ├── /              → SvelteKit Frontend (elemes-frontend :3000)
    ├── /assets/       → Flask Backend (elemes :5000)
    ├── /velxio/       → Velxio Arduino Simulator (velxio :80)
    │
    ▼
SvelteKit Frontend (elemes-frontend :3000)
  ├── SSR pages (lesson content embedded in HTML)
  ├── CodeMirror 6 editor (lazy-loaded)
  ├── CircuitJS simulator (iframe, GWT-compiled) — mode "circuit"
  ├── Velxio Arduino simulator (iframe, React) — mode "velxio"
  ├── API proxy: /api/* → Flask
  └── PWA manifest
    │
    ▼  /api/*
Flask API Backend (elemes :5000)
  ├── Code compilation (gcc / python)
  ├── Token authentication (CSV)
  ├── Progress tracking
  └── Lesson content parsing (markdown)
    │
Velxio Arduino Simulator (velxio :80)
  ├── React + Vite frontend (editor + simulator canvas)
  ├── FastAPI backend (arduino-cli compile)
  ├── AVR8 / RP2040 CPU emulation (browser)
  └── PostMessage bridge ↔ Elemes (EmbedBridge.ts)

Container Setup

Container Image Port Fungsi
elemes Python 3.11 + gcc 5000 Flask API (compile, auth, lessons, progress)
elemes-frontend Node 20 3000 SvelteKit SSR
velxio Node + Python + arduino-cli 80 Simulator Arduino (React + FastAPI)
elemes-ts Tailscale 443 HTTPS Funnel + reverse proxy

Container berkomunikasi via hostname Podman (compose service name). Proxy rules di config/sinau-c-tail.json.


Struktur Direktori

lms-c/
├── content/                     # Lesson markdown files
│   ├── home.md                  # Daftar lesson + landing page
│   ├── hello_world.md           # Lesson C
│   ├── variabel.md              # Lesson C
│   ├── rangkaian_dasar.md       # Lesson Circuit
│   └── led_blink_arduino.md     # Lesson Arduino/Velxio
├── assets/                      # Gambar untuk lesson
├── tokens_siswa.csv             # Data siswa & progress
├── state/                       # Tailscale runtime state
├── .env                         # Environment variables
│
└── elemes/                      # Semua kode aplikasi
    ├── app.py                   # Flask create_app() factory
    ├── config.py                # CONTENT_DIR, TOKENS_FILE
    ├── Dockerfile               # Flask container
    ├── gunicorn.conf.py         # Production WSGI
    ├── requirements.txt         # Python deps
    ├── podman-compose.yml       # 4 services (elemes, frontend, velxio, ts)
    ├── elemes.sh                # CLI: init, run, runbuild, stop, generatetoken
    ├── generate_tokens.py       # Utility: generate CSV tokens
    ├── proposal.md              # Proposal integrasi Velxio (referensi arsitektur)
    │
    ├── config/
    │   └── sinau-c-tail.json    # Tailscale Serve proxy rules
    │
    ├── compiler/                # Code compilation
    │   ├── __init__.py          # CompilerFactory
    │   ├── base_compiler.py     # Abstract base
    │   ├── c_compiler.py        # gcc wrapper
    │   └── python_compiler.py   # python wrapper
    │
    ├── routes/                  # Flask Blueprints
    │   ├── auth.py              # /login, /logout, /validate-token
    │   ├── compile.py           # /compile
    │   ├── lessons.py           # /lessons, /lesson/<slug>.json, /get-key-text/<slug>
    │   └── progress.py          # /track-progress, /progress-report.json, export-csv
    │
    ├── services/                # Business logic
    │   ├── token_service.py     # CSV token CRUD
    │   └── lesson_service.py    # Markdown parsing + rendering
    │
    ├── frontend/                # SvelteKit (Svelte 5)
    │   ├── package.json
    │   ├── svelte.config.js     # adapter-node + path aliases
    │   ├── vite.config.ts
    │   ├── Dockerfile           # Multi-stage build
    │   └── src/
    │       ├── hooks.server.ts  # API proxy: /api/* → Flask, /assets/* → Flask
    │       ├── app.html
    │       ├── app.css
    │       ├── lib/
    │       │   ├── components/
    │       │   │   ├── CodeEditor.svelte       # CodeMirror 6 (lazy-loaded, anti-paste)
    │       │   │   ├── CircuitEditor.svelte    # CircuitJS iframe wrapper
    │       │   │   ├── CrosshairOverlay.svelte # Touch precision overlay (CircuitJS)
    │       │   │   ├── OutputPanel.svelte      # Multi-section output (C, Python, Circuit, Arduino)
    │       │   │   ├── CelebrationOverlay.svelte # Lesson completion animation
    │       │   │   ├── WorkspaceHeader.svelte  # Tab switcher + floating/mobile controls
    │       │   │   ├── LessonFooterNav.svelte  # Prev/next lesson navigation
    │       │   │   ├── Navbar.svelte
    │       │   │   ├── LessonCard.svelte
    │       │   │   ├── LessonList.svelte
    │       │   │   ├── ProgressBadge.svelte
    │       │   │   └── Footer.svelte
    │       │   ├── stores/
    │       │   │   ├── auth.ts             # Svelte writable stores (login, token)
    │       │   │   ├── lessonContext.ts     # Current lesson nav context
    │       │   │   └── theme.ts            # Dark/light toggle
    │       │   ├── services/
    │       │   │   ├── api.ts              # Flask API client
    │       │   │   ├── exercise.ts         # checkKeyText(), validateNodes()
    │       │   │   └── velxio-bridge.ts    # PostMessage bridge ke Velxio iframe
    │       │   ├── actions/
    │       │   │   ├── floatingPanel.svelte.ts  # Draggable/resizable editor panel
    │       │   │   ├── highlightCode.ts         # Syntax highlighting post-render
    │       │   │   ├── noSelect.ts              # Anti-select directive
    │       │   │   └── renderCircuitEmbeds.ts   # Inline circuit embed renderer
    │       │   └── types/
    │       │       ├── lesson.ts           # LessonContent interface
    │       │       ├── auth.ts
    │       │       ├── compiler.ts
    │       │       └── circuitjs.ts        # CircuitJSApi interface
    │       ├── routes/
    │       │   ├── +layout.svelte          # App shell (Navbar, theme)
    │       │   ├── +page.svelte            # Home (lesson grid)
    │       │   ├── +page.ts                # SSR data loader
    │       │   ├── lesson/[slug]/
    │       │   │   ├── +page.svelte        # Lesson viewer (semua mode)
    │       │   │   └── +page.ts            # SSR data loader
    │       │   └── progress/
    │       │       └── +page.svelte        # Teacher dashboard
    │       └── static/
    │           ├── manifest.json           # PWA manifest
    │           └── circuitjs1/             # CircuitJS simulator (GWT-compiled)
    │
    └── velxio/                  # Velxio fork (Git submodule)
        ├── frontend/            # React + Vite + TypeScript
        │   └── src/
        │       ├── services/EmbedBridge.ts       # PostMessage listener (Velxio side)
        │       ├── pages/EditorPage.tsx           # Editor + embed mode
        │       ├── components/editor/EditorToolbar.tsx
        │       ├── components/simulator/SimulatorCanvas.tsx  # Canvas + touch/pinch + undo/redo toolbar
        │       ├── components/simulator/SimulatorCanvas.css # Canvas styling (undo-controls class)
        │       ├── components/simulator/WireInProgressRenderer.tsx  # Wire preview + crosshair
        │       ├── components/simulator/WireLayer.tsx      # Wire SVG rendering + segment handles
        │       ├── components/simulator/PinOverlay.tsx
        │       ├── store/useEditorStore.ts        # Multi-file workspace
        │       └── store/useSimulatorStore.ts     # Simulation state, wires, undo/redo stacks
        ├── backend/             # FastAPI + arduino-cli
        ├── Dockerfile.standalone
        └── CLAUDE.md            # Dokumentasi teknis lengkap Velxio

Keputusan Teknis

Kenapa SvelteKit, bukan Flutter?

  • CodeMirror 6 (code editor production-grade) — tidak ada equivalent di Flutter
  • Bundle ~250KB vs Flutter Web 2-4MB
  • SSR untuk konten markdown (instant first paint)
  • PWA installable tanpa app store

Svelte 5 Runes vs Writable Stores

Proyek ini menggunakan Svelte 5 dengan dua pola state management:

  • Runes ($state, $derived, $effect) — digunakan di file .svelte (contoh: lesson/[slug]/+page.svelte menggunakan $state untuk semua UI state, $derived untuk computed values, $effect untuk side effects)
  • Writable stores (writable()) — digunakan di file .ts biasa karena runes tidak diproses oleh Svelte compiler di luar file .svelte. Contoh: auth.ts, lessonContext.ts, theme.ts

Kenapa hooks.server.ts untuk API proxy?

Vite server.proxy hanya bekerja di dev mode (vite dev). Di production (adapter-node), SvelteKit tidak punya proxy. hooks.server.ts mem-forward /api/* dan /assets/* ke Flask backend saat runtime. Prefix /api/ di-strip sebelum forward (Flask routes tidak pakai /api/ prefix).

Kenapa transparent overlay untuk touch CircuitJS?

CircuitJS adalah GWT-compiled app — tidak bisa modify source code-nya. Transparent overlay mengintercept PointerEvent di parent, konversi ke MouseEvent, dispatch ke iframe via contentDocument.elementFromPoint(). Overlay dinonaktifkan (pointer-events: none) di desktop sehingga mouse events langsung tembus ke iframe.

Kenapa lazy-load CodeMirror?

CodeMirror 6 bundle ~475KB. Dengan dynamic import(), lesson content (text) muncul langsung via SSR, editor menyusul setelah JS bundle selesai download.

Kenapa Velxio di-embed via iframe, bukan komponen?

Velxio adalah aplikasi React lengkap (editor + simulator + serial monitor + wire system). Mengekstrak komponen-komponennya ke SvelteKit tidak praktis. Iframe + PostMessage bridge memungkinkan kedua sistem dikembangkan secara independen. Same-origin via Tailscale Serve proxy (/velxio/), jadi tidak perlu CORS/CSP khusus.


Cara Menjalankan

# Setup awal (sekali)
cd elemes/
./elemes.sh init

# Edit ../.env sesuai kebutuhan (ELEMES_HOST, TS_AUTHKEY, branding)

# Build & run semua container
./elemes.sh runbuild

# Atau: run tanpa rebuild
./elemes.sh run

# Stop
./elemes.sh stop

# Rebuild tanpa cache
./elemes.sh runclearbuild

# Generate token siswa dari content
./elemes.sh generatetoken

Logs

podman logs elemes           # Flask API
podman logs elemes-frontend  # SvelteKit
podman logs velxio           # Velxio simulator
podman logs elemes-ts        # Tailscale

API Endpoints

Semua endpoint Flask diakses via SvelteKit proxy (/api/* → Flask :5000, prefix /api/ di-strip):

Method Frontend Path Flask Path Fungsi
POST /api/login /login Login dengan token
POST /api/logout /logout Logout
POST /api/validate-token /validate-token Validasi token
GET /api/lessons /lessons Daftar lesson + home content
GET /api/lesson/<slug>.json /lesson/<slug>.json Data lesson lengkap
GET /api/get-key-text/<slug> /get-key-text/<slug> Key text untuk lesson
POST /api/compile /compile Compile & run kode (C/Python)
POST /api/track-progress /track-progress Track progress siswa
GET /api/progress-report.json /progress-report.json Data progress semua siswa
GET /api/progress-report/export-csv /progress-report/export-csv Export CSV
GET /assets/<path> /assets/<path> Static assets (proxy langsung, tanpa strip)

Mode Lesson

Elemes mendukung beberapa mode lesson melalui marker di file markdown. Mode ditentukan otomatis dari marker yang ada:

Mode Marker Tab yang muncul Evaluasi
C ---INITIAL_CODE--- Editor (C) + Output stdout matching + key_text
Python ---INITIAL_PYTHON--- Editor (Python) + Output stdout matching + key_text
Circuit ---INITIAL_CIRCUIT--- Circuit + Output node voltage + key_text
Arduino/Velxio ---INITIAL_CODE_ARDUINO--- Velxio (iframe) + Output serial + wiring + key_text
Velxio circuit-only ---VELXIO_CIRCUIT--- (tanpa code) Velxio (no editor) + Output wiring + key_text
Hybrid C/Python + Circuit Editor + Circuit + Output AND-logic: kedua harus pass

Markdown Sections yang Dikenali

Section Fungsi
---LESSON_INFO--- / ---END_LESSON_INFO--- Info tab (learning objectives)
---EXERCISE--- Instruksi latihan (separator)
---INITIAL_CODE--- / ---END_INITIAL_CODE--- Kode awal C
---INITIAL_PYTHON--- / ---END_INITIAL_PYTHON--- Kode awal Python
---INITIAL_CIRCUIT--- / ---END_INITIAL_CIRCUIT--- Circuit text awal (CircuitJS format)
---INITIAL_QUIZ--- / ---END_INITIAL_QUIZ--- Quiz data
---INITIAL_CODE_ARDUINO--- / ---END_INITIAL_CODE_ARDUINO--- Kode awal Arduino
---VELXIO_CIRCUIT--- / ---END_VELXIO_CIRCUIT--- Circuit JSON untuk Velxio (komponen + wires)
---EXPECTED_OUTPUT--- / ---END_EXPECTED_OUTPUT--- Expected stdout (C/Python) atau node voltage JSON (Circuit)
---EXPECTED_CIRCUIT_OUTPUT--- / ---END_EXPECTED_CIRCUIT_OUTPUT--- Expected circuit output (hybrid mode)
---EXPECTED_SERIAL_OUTPUT--- / ---END_EXPECTED_SERIAL_OUTPUT--- Expected serial output (Arduino)
---EXPECTED_WIRING--- / ---END_EXPECTED_WIRING--- Expected wiring JSON (Arduino)
---KEY_TEXT--- / ---END_KEY_TEXT--- Keyword wajib di source code / circuit
---KEY_TEXT_CIRCUIT--- / ---END_KEY_TEXT_CIRCUIT--- Keyword wajib di circuit (hybrid)
---SOLUTION_CODE--- / ---END_SOLUTION_CODE--- Solusi kode (ditampilkan setelah selesai)
---SOLUTION_CIRCUIT--- / ---END_SOLUTION_CIRCUIT--- Solusi circuit

Velxio Integration (Arduino Simulator)

PostMessage Bridge Protocol

Komunikasi antara Elemes dan Velxio iframe via window.postMessage:

Elemes → Velxio (Commands):

Message Type Payload Fungsi
elemes:load_code { files: [{name, content}] } Load source code ke editor
elemes:load_circuit { board, components, wires } Load rangkaian ke simulator
elemes:set_embed_mode { hideAuth, hideComponentPicker } Configure embed UI
elemes:get_source_code Request source code
elemes:get_serial_log Request serial output
elemes:get_wires Request wire topology
elemes:stop Stop simulation
elemes:ping Re-trigger ready signal

Velxio → Elemes (Events):

Message Type Payload Fungsi
velxio:ready { version } Iframe siap (broadcast tiap 300ms sampai parent acknowledge)
velxio:compile_result { success } Kompilasi selesai
velxio:source_code { files } Response ke get_source_code
velxio:serial_log { log } Response ke get_serial_log
velxio:wires { wires } Response ke get_wires

Flow Lesson Arduino

1. User buka lesson → frontend fetch /api/lesson/<slug>.json
2. Backend parse markdown → extract INITIAL_CODE_ARDUINO, VELXIO_CIRCUIT, dll
3. Frontend detect active_tabs=['velxio'] → render iframe src="/velxio/editor?embed=true"
4. Iframe load → Velxio EmbedBridge broadcast velxio:ready (tiap 300ms)
5. Frontend initVelxioBridge() → create VelxioBridge → acknowledge → stop broadcast
6. Bridge send: set_embed_mode, load_circuit, load_code
7. Siswa edit kode + wiring di Velxio → klik Compile & Run
8. Velxio notify: velxio:compile_result { success: true }
9. Auto-evaluate setelah 3 detik (tunggu serial output)
10. Evaluate: get_source_code → key_text, get_serial_log → serial match, get_wires → wiring match
11. Jika semua pass → completeLesson() → celebration + track progress

Evaluasi Arduino (3 jenis, semua harus pass)

Evaluasi Mekanisme Sumber Data
Key Text Keyword wajib ada di source code ---KEY_TEXT--- di markdown
Serial Output Subsequence matching (expected lines muncul dalam urutan di actual) ---EXPECTED_SERIAL_OUTPUT---
Wiring Lenient graph comparison (expected edges harus ada, extra OK) ---EXPECTED_WIRING--- (JSON array of pairs)

File-file Kunci

Sisi File Fungsi
Elemes frontend/src/lib/services/velxio-bridge.ts VelxioBridge class (send commands, evaluate)
Elemes frontend/src/routes/lesson/[slug]/+page.svelte initVelxioBridge(), handleVelxioSubmit()
Velxio frontend/src/services/EmbedBridge.ts PostMessage listener (handle commands)
Velxio frontend/src/pages/EditorPage.tsx Embed mode UI control
Velxio frontend/src/components/editor/EditorToolbar.tsx Embed-aware toolbar

CircuitJS Integration

Integrasi Falstad CircuitJS1 sebagai simulator rangkaian interaktif di tab "Circuit". CircuitJS adalah aplikasi GWT (Java → JavaScript) yang di-embed via iframe same-origin.

Arsitektur

content/rangkaian_dasar.md
  │  ---INITIAL_CIRCUIT--- ... ---END_INITIAL_CIRCUIT---
  │
  ▼  lesson_service.py: _extract_section()
Flask API (/lesson/<slug>.json)
  │  { initial_circuit, expected_output, key_text, active_tabs: ["circuit"] }
  │
  ▼  +page.ts SSR loader
lesson/[slug]/+page.svelte
  │  <CircuitEditor initialCircuit={data.initial_circuit} />
  │
  ▼  iframe onload → oncircuitjsloaded callback
CircuitEditor.svelte
  │  simApi.importCircuit(text, false)
  │
  ▼
circuitjs1/circuitjs.html (GWT app in iframe)
  │  Canvas rendering, simulasi real-time
  │
  ▼  CrosshairOverlay.svelte (touch devices only)
Touch event forwarding → synthetic MouseEvent dispatch

CircuitJS API (via iframe.contentWindow.CircuitJS1)

API object didapatkan melalui callback window.oncircuitjsloaded, dengan fallback 3 detik via window.CircuitJS1.

Method Fungsi
importCircuit(text, showMessage) Load circuit dari text
exportCircuit() Ekspor circuit saat ini sebagai text
getNodeVoltage(nodeName) Query tegangan di named node
setSimRunning(bool) Jalankan/hentikan simulasi
updateCircuit() Redraw setelah perubahan

Evaluasi Rangkaian

Saat siswa klik "Cek Rangkaian", fungsi evaluateCircuit() menjalankan:

  1. Parse kriteria dari expected_output (JSON)
  2. Cek tegangan node via simApi.getNodeVoltage() ± tolerance
  3. Cek komponen wajib via checkKeyText() (string contains pada circuit text)
  4. Track progress jika semua passed

Expected Output JSON Format:

{
  "nodes": {
    "TestPoint_A": { "voltage": 2.5, "tolerance": 0.2 }
  }
}

Auto-save

CircuitEditor mendukung auto-save ke sessionStorage (polling setiap 5 detik):

  • Key: elemes_circuit_{slug} (hanya saat user login & bukan mode solusi)
  • Restore: Cek sessionStorage dulu, fallback ke initialCircuit prop

Anti Copy-Paste System

Sistem berlapis untuk mencegah siswa meng-copy konten pelajaran dan mem-paste kode dari sumber eksternal.

Selection & Copy Prevention (Konten Lesson)

File: lesson/[slug]/+page.svelte

Layer Mekanisme Target
CSS user-select: none, -webkit-touch-callout: none .lesson-content, info/exercise tabs
Events onselectstart, oncopy, oncut, oncontextmenupreventDefault() Konten lesson
Directive use:noSelect Reusable action

Paste Prevention (CodeEditor)

File: CodeEditor.svelte

Diaktifkan via prop noPaste={true}.

Layer Mekanisme Menangani
DOM handlers paste, drop, beforeinputpreventDefault() Desktop paste, iOS paste
Transaction filter Block input.paste + heuristik ukuran (>2 baris atau >20 chars) Standard paste + GBoard clipboard panel
Clipboard filter Replace clipboard text → '' Standard paste (jika API tersedia)
Input handler Block multi-line insertion >20 chars GBoard clipboard via DOM mutations

Limitasi: GBoard clipboard panel menggunakan IME composition, tidak bisa dibedakan 100% dari ketikan biasa. Paste 1 baris pendek (<20 chars) masih bisa lolos.


Touch Crosshair System (CircuitJS)

Overlay untuk presisi interaksi di iframe CircuitJS pada perangkat sentuh. Aktif hanya pada touch device (CSS media query hover: none + pointer: coarse).

File: CrosshairOverlay.svelte (dimount di CircuitEditor.svelte)

Gesture Mapping

Gesture Action
Single tap click (delay 300ms)
Double tap dblclick (edit komponen)
Triple tap Right-click (contextmenu)
Two-finger tap Right-click
Long tap (400ms) Crosshair aiming mode (4-phase state machine)

Cara Kerja

Overlay transparan mengintercept touch events → konversi koordinat ke iframe-local → elementFromPoint() untuk target → dispatch synthetic MouseEvent ke iframe.

Konfigurasi

Variable Default Fungsi
PUBLIC_CURSOR_OFFSET_Y 50 Offset Y crosshair dari jari (pixel)

Velxio Mobile Wiring Crosshair

Berbeda dari CircuitJS crosshair (yang menggunakan overlay), Velxio crosshair ada di dalam simulator canvas sendiri (SVG).

File: velxio/frontend/src/components/simulator/WireInProgressRenderer.tsx

Saat user sedang menarik wire, garis bantu horizontal + vertikal muncul di posisi cursor:

  • Style: dashed rgba(255,255,255,0.25), strokeWidth=0.5, strokeDasharray="8,6"
  • Panjang: 8000px ke setiap arah (cukup untuk semua zoom level)
  • Terinspirasi CircuitJS — membantu alignment karena jari menutupi pin target

Pinch-Zoom Preserve Wire

File: velxio/frontend/src/components/simulator/SimulatorCanvas.tsx

Sebelumnya, pinch-to-zoom (2 jari) otomatis cancel wire yang sedang ditarik. Sekarang wire-in-progress tetap aktif selama zoom. Preview freeze (hanya update saat 1 jari), lalu resume setelah zoom selesai.


Wire Undo/Redo

Snapshot-based undo/redo untuk operasi wire di Velxio simulator. Memungkinkan user membatalkan kesalahan wiring tanpa harus memilih dan menghapus wire secara manual.

Arsitektur

User action (add/remove/update/finish wire)
    │
    ├── Push current state.wires → wireUndoStack (max 50)
    ├── Clear wireRedoStack
    └── Mutate state.wires
    
Undo (Ctrl+Z / toolbar button)
    │
    ├── Pop wireUndoStack → restore as state.wires
    ├── Push current state.wires → wireRedoStack
    └── Reset selectedWireId = null

Redo (Ctrl+Shift+Z / toolbar button)
    │
    ├── Pop wireRedoStack → restore as state.wires
    ├── Push current state.wires → wireUndoStack
    └── Reset selectedWireId = null

File yang Terlibat

File Perubahan
velxio/frontend/src/store/useSimulatorStore.ts State: wireUndoStack, wireRedoStack. Actions: undoWire(), redoWire(). Snapshot push di addWire, removeWire, updateWire, finishWireCreation.
velxio/frontend/src/components/simulator/SimulatorCanvas.tsx Keyboard handler (Ctrl+Z/Ctrl+Shift+Z), toolbar buttons (undo/redo icons), store selectors (canUndoWire, canRedoWire).
velxio/frontend/src/components/simulator/SimulatorCanvas.css Class .undo-controls — styling identik dengan .zoom-controls tapi tidak di-hide di mobile media query.

Keputusan Desain

  1. Snapshot-based vs action-based — Dipilih snapshot (simpan seluruh wires[] per step) karena lebih sederhana dan reliable. Trade-off: memory lebih besar, tapi dengan limit 50 entries dan array wire yang kecil, tidak masalah.
  2. Separate CSS class.undo-controls terpisah dari .zoom-controls karena zoom di-hide di mobile (display: none — pakai pinch-to-zoom), tapi undo/redo harus tetap visible.
  3. setWires() tidak push snapshotsetWires() digunakan oleh EmbedBridge saat load circuit dari LMS. Ini bukan user action, jadi tidak masuk undo history.
  4. selectedWireId: null saat undo/redo — Mencegah bug dimana UI mencoba menampilkan info wire yang sudah tidak ada setelah undo.

Status Implementasi

  • Backend decomposition (monolith → Blueprints + services)
  • SvelteKit scaffolding (adapter-node, Svelte 5, path aliases)
  • Core components (CodeEditor, Navbar, auth/theme stores, API client)
  • Pages (Home, Lesson, Progress) + SSR data loading
  • Containerization (4-container setup, API proxy)
  • CircuitJS integration (iframe, evaluasi node voltage, touch overlay)
  • Floating/mobile editor panel (draggable, resizable, bottom sheet)
  • Anti copy-paste system (lesson content + code editor)
  • Velxio fork + EmbedBridge (PostMessage protocol)
  • Velxio integration di Elemes (bridge, parsing, UI, evaluasi)
  • Mobile wiring UX (pinch-zoom preserve wire, crosshair alignment)
  • Wire undo/redo (snapshot-based, Ctrl+Z/Ctrl+Shift+Z, toolbar button, mobile-friendly)
  • Contoh lesson Arduino (LED Blink)
  • PWA (service worker, offline caching)
  • Contoh lesson Arduino tambahan (2-3 lagi)
  • Velxio enhancements (lock komponen, solution overlay, multi-board)