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.sveltemenggunakan$stateuntuk semua UI state,$deriveduntuk computed values,$effectuntuk side effects) - Writable stores (
writable()) — digunakan di file.tsbiasa 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
- Frontend: http://localhost:3000 (dev)
- Tailscale: https://{ELEMES_HOST}.{tailnet}.ts.net
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:
- Parse kriteria dari
expected_output(JSON) - Cek tegangan node via
simApi.getNodeVoltage()± tolerance - Cek komponen wajib via
checkKeyText()(string contains pada circuit text) - 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
initialCircuitprop
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, oncontextmenu → preventDefault() |
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, beforeinput → preventDefault() |
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
- 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. - Separate CSS class —
.undo-controlsterpisah dari.zoom-controlskarena zoom di-hide di mobile (display: none— pakai pinch-to-zoom), tapi undo/redo harus tetap visible. setWires()tidak push snapshot —setWires()digunakan olehEmbedBridgesaat load circuit dari LMS. Ini bukan user action, jadi tidak masuk undo history.selectedWireId: nullsaat 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)