elemes/documentation.md

29 KiB

Elemes LMS — Dokumentasi Teknis

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


Arsitektur

Internet (HTTPS :443)
    │
    ▼
Tailscale Funnel (elemes-ts)
    │
    ├── /                   → SvelteKit Frontend (elemes-frontend :3000)
    ├── /assets/            → Flask Backend (elemes :5000)
    ├── /velxio/api/compile → Flask Backend (Rate-limited Proxy :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 (Proxied to Compiler Worker)
  ├── Arduino Proxy (/velxio-compile → Velxio :80)
  ├── Token authentication (CSV)
  ├── Progress tracking
  └── Lesson content parsing (markdown)
    │
    ▼  HTTP
Compiler Worker (compiler-worker :8080)
  ├── gVisor Sandbox (runsc runtime)
  ├── Gunicorn (4 workers)
  └── Isolation: gcc / python3 execution
    │
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 5000 Flask API (auth, lessons, progress, compile-proxy)
compiler-worker Python 3.11 + gcc 8080 Sandboxed execution engine (gVisor)
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) via worker
POST /velxio/api/compile /velxio-compile Rate-limited Arduino compile proxy
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.


Keamanan & Autentikasi (Security)

Sistem pengamanan untuk melindungi data siswa dan mencegah penyalahgunaan API.

1. Eksekusi Terisolasi (gVisor Sandbox)

Seluruh kode yang dikirim oleh siswa (C dan Python) tidak dieksekusi di dalam container utama, melainkan diteruskan ke Compiler Worker yang berjalan menggunakan runtime gVisor (runsc). Ini mencegah serangan Remote Code Execution (RCE) yang menargetkan kernel sistem operasi host.

2. Anonymous Access & Rate Limiting

Sistem mengizinkan pengguna tanpa token (anonim) untuk mencoba pembelajaran dengan batasan ketat:

  • Rate Limit: Pengguna anonim dibatasi 1 kali kompilasi setiap 2 menit per IP.
  • Global Queue: Maksimal 20 slot antrean kompilasi anonim secara global untuk menjaga stabilitas server.
  • Bypass: Pengguna yang telah login (memiliki token/cookie valid) bebas dari batasan rate limit ini.
  • Velxio Protection: Kompilasi Arduino diproteksi melalui proxy backend Flask untuk mencegah hijacking API dari sisi klien.

LMS menggunakan cookie student_token untuk mengelola sesi. Keamanannya diatur secara dinamis melalui environment variable.

  • httponly: true: Mencegah akses cookie via JavaScript (proteksi XSS).
  • secure: COOKIE_SECURE: Jika true, cookie hanya dikirim via HTTPS. Sangat penting saat dideploy via Tailscale Funnel.
  • samesite: 'Lax': Proteksi dasar terhadap CSRF.

2. Proteksi Brute-Force & Anti-Spam

Untuk mencegah penebakan token secara massal, terutama pada jaringan WiFi sekolah yang menggunakan satu IP publik (NAT):

  • Rate Limiting: Endpoint /api/login dibatasi maksimal 50 request per menit per IP. Angka ini diatur untuk mengakomodasi satu kelas (50 siswa) yang login bersamaan tanpa saling memblokir.
  • Tarpitting (Login Delay): Setiap percobaan login yang gagal akan ditahan selama 1.5 detik sebelum server memberikan respons. Ini melumpuhkan efektivitas alat brute-force otomatis tanpa mengganggu pengalaman siswa asli yang hanya sesekali salah ketik.

3. Session Invalidation (Token Blacklist)

Mekanisme logout server-side untuk memastikan token tidak bisa digunakan kembali setelah sesi berakhir.

  • Mekanisme: Menggunakan LOGOUT_BLACKLIST (in-memory set) di Flask backend.
  • Flow: Saat user klik logout, token ditambahkan ke blacklist. Semua request berikutnya dengan token tersebut akan ditolak oleh validate_token().

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)
  • Security Review & Hardening (Cookie security, Rate limiting, Tarpitting, Blacklisting)
  • 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)