Commit Graph

105 Commits

Author SHA1 Message Date
a2nr 7e17da46c2 fix(frontend): hide CircuitJS toolbar & menu bar in lesson embeds
Append &hideMenu=true to the circuitjs.html iframe src used by
renderCircuitEmbeds so lesson content embeds render clean without
the toolbar and menu bar. The embed path in CircuitEditor.svelte
is untouched, so the editor keeps its full toolbar.

Verified:
- Bundle contains embed URL (with hideMenu=true) and editor URL
  (without) as separate variants
- svelte-check: 0 errors
- /lesson/rangkaian_dasar returns 200 on the running stack
2026-08-12 07:49:34 +07:00
a2nr 24502380aa feat(quiz): strict focus-loss anti-cheat + audit attempt
Kebijakan strict: kehilangan fokus saat kuis aktif langsung mengakhiri kuis
dengan penalti, satu attempt per kuis tercatat audit, pembahasan disembunyikan
saat focus_lost, badge pelanggaran muncul di laporan guru.

Backend:
- tabel quiz_attempts (migration 0002) + endpoint POST /quiz-attempts/submit
  idempotent dengan unique constraint (satu attempt per token+lesson, 409
  untuk duplikat), bounded termination_reason
- models/repositories/storage: QuizAttempt + record/query pelanggaran untuk
  laporan guru (progress)

Frontend:
- quiz-integrity.ts: policy hub (isFocusLossEvent, terminateQuiz,
  shouldShowQuizReview) + attempt_id via crypto.randomUUID()
- lesson.svelte.ts: finalisasi idempotent; sendBeacon untuk exit path yang
  bisa suspend (focus_lost/page_unload), fetch keepalive untuk reason lain
- QuizTab.svelte: summary menyembunyikan pembahasan saat focus_lost
- progress/+page.svelte: badge pelanggaran focus_lost di laporan guru
- APP_VERSION 26 -> 27

Tests & docs:
- 204 backend test pass (unit host + integrasi container, DB elemes_test)
- 107 frontend test pass, svelte-check 0 error
- docs/12-quiz-integrity.md (baru), docs/07-quiz-authoring.md + README FAQ
  anti-cheat diperbarui
2026-08-11 20:55:39 +07:00
a2nr c356e234fa fix(frontend): resolve 26 svelte-check type errors
- add @types/node for hooks.server process/env types
- fix katex auto-render import path (contrib/auto-render)
- declare global Web Bluetooth API types in ble-deployer
- guard simApi null in CircuitEditor autosave interval
- default-type imports for CodeEditor/CircuitEditor
- make lesson slug getter non-nullable
- add expected_flowchart to LessonContent type
- key lesson workspace by slug; drop phantom onReset prop
- guard CodeTab render when lesson data is null
2026-08-09 12:28:17 +07:00
a2nr d602b0f642 feat(student-management): kelola siswa round-trip CSV + bulk delete (PG)
- Export siswa terpilih/seluruh + progress ke CSV round-trip (token tidak
  pernah diekspor; teacher tidak ikut; urutan deterministik)
- Import preview all-or-nothing: siswa existing di-restore (nama+progress,
  token lama dipertahankan), siswa baru dibuat dari student_id kosong +
  token; conflict tidak menyebut raw token/digest
- Bulk delete multi-seleksi dengan validasi ketat (teacher terlindungi,
  zero-delete bila ada ID bermasalah)
- fix(import): deteksi delimiter berbasis schema — menerima titik koma
  (;) maupun koma (,), ekspor tetap ';' + BOM; header tab/campuran ditolak
  dengan pesan jelas (regresi: file Excel/Sheets kini bisa diimpor)
- refactor: progress_status.py — kontrak status legacy tunggal dipakai
  storage PG, export, dan import; report siswa hanya role student
- UI /progress: dialog import/export + bulk delete, selection store,
  respon import tanpa raw token; APP_VERSION 22 -> 26
- ops: ORIGIN env var di podman-compose.yml
- docs: README section 9 (workflow round-trip + delimiter) dan
  docs/11-database-migration (kelola siswa via round-trip CSV)
- test: parser round-trip (45), repositories, route management (24,
  PostgreSQL), frontend 91, fixtures CSV valid/invalid
2026-08-09 07:29:19 +07:00
a2nr ac37db5c91 feat(storage): migrasi penyimpanan CSV ke PostgreSQL (SQLAlchemy + Alembic)
- facade storage: csv_backend + postgres_backend, dipilih via STORAGE_BACKEND
- token disimpan sebagai HMAC-SHA256 digest (TOKEN_PEPPER), tanpa plaintext
- schema alembic 0001: users, access_tokens, lessons, student_progress
- importer CSV idempotent + dry-run + sparse model (not_started tanpa row)
- lesson registry: sync dari home.md (lesson hilang di-deactivate, bukan dihapus)
- routes progress/auth: report & reset via DB, validasi via token hash
- elemes.sh: dbupgrade/dbstatus/dbimport/synclessons/dbverify/dbbackup/dbrestore/dbexport
- podman-compose: service postgres (PG18), tokens_siswa.csv mount :ro
- frontend: api client + progress page; tests kontrak dual-backend + isolasi DB
- load-test: skenario database (locustfile_db.py)
- docs: 01-11 termasuk 11-database-migration.md (arsitektur & operasional)
2026-08-08 04:57:08 +00:00
a2nr ec66d794a8 feat(playground): alur interaktif Run→prompt→jawab + file tree collapsible & dukungan .h
- store: normalisasi lifecycle sesi (runStatus/runSessionId/outputCursor/runError,
  stdinQueue tetap untuk kompatibilitas)
- +page.svelte: session controller — start, polling recursive setTimeout non-overlap,
  delta output via cursor, stop + cleanup unmount/generation counter
- ConsolePanel.svelte: input interaktif (onSendInput/onStop async, mode antrean vs kirim)
- FileTree.svelte: validasi rename (foo.h diterima di C), tombol + langsung mode rename,
  collapsible shell dengan handle persisten
- playground-files.ts: validasi nama file (basename, traversal, duplikat case-insensitive)
- api.ts/compiler.ts: client sesi interaktif
- tests: playground.test.ts + playground-files.test.ts
2026-08-02 14:14:53 +00:00
a2nr ddfbbb80f3 feat(playground): FileTree filter per bahasa — C dan Python dipisah
- FileTree menerima prop language ('c' | 'python', default 'c')
- hanya merender file dengan ekstensi yang cocok (.c atau .py)
- tombol + buat file dengan ekstensi bahasa aktif
- header menampilkan nama bahasa (C/Python), bukan generik 'File'
2026-08-01 22:39:20 +00:00
a2nr 152ca85718 fix(playground): Run mengonsumsi stdinQueue atomik via consumeStdin()
- runActiveFile() memanggil consumeStdin() (antrean + draft, digabung, dikosongkan)
- pesan info jika stdin kosong: program yang membaca input akan mendapat EOF
- hapus pembacaan consoleInput langsung yang menyebabkan baris hilang
2026-08-01 22:38:47 +00:00
a2nr a92411fa45 fix(playground): Kirim menambah baris ke stdinQueue, bukan hanya history 2026-08-01 22:36:52 +00:00
a2nr 1aef334d59 feat(playground): stdinQueue di store — enqueue/consume/clear atomik 2026-08-01 22:34:32 +00:00
a2nr dbed014474 feat(playground): multi-tab playground + FileTree + Console + stdin wiring to compile worker
- playground route: velxio/flowchart/circuit/code tabs with auto-save
- FileTree + ConsolePanel for C/Python code tab
- compileCode() forwards stdin through routes/compile.py to compiler_worker
- compiler_worker run_c_code/run_python_code accept stdin, pass via subprocess.run(input=)
- known issue: Console UI does not queue stdin properly → fix in next cycle
2026-08-01 22:32:54 +00:00
a2nr 0ee39f152e refactor(compose): adaptasi pola network tailscale ala firefly-iii
- Semua service share netns elemes-ts (network_mode: service:elemes-ts),
  hapus bridge network elemes_network
- lms-tail.json: proxy target 127.0.0.1 (bukan nama service DNS)
- flowchart pindah port 80 → 8081 (konflik port di netns bersama),
  tambah nginx.conf custom + EXPOSE 8081
- env internal (COMPILER_WORKER_URL, VELXIO_COMPILER_URL, API_BACKEND,
  hooks fallback, vite dev proxy) → 127.0.0.1
- fix format volume velxio-data/velxio-arduino-libs agar kompatibel
  podman-compose 1.0.6 (named volume standar)
2026-08-01 12:56:51 +00:00
a2nr 02d5969619 refactor(quiz): restructure quiz session, question view, and tab safety
Restructure quiz flow into QuizSession primitive + atomic quiz state in
LessonManager, and extract QuizQuestionView for split-view display.

Tab-safety fix: add lesson-tabs.ts with pickDefaultTab(), getAvailableTabs()
mirroring the exact {#if} conditions in +page.svelte, and ensureActiveTab()
safety-net that falls back to pickDefaultTab() when the desired tab is not
rendered. A reactive $effect in lesson/[slug]/+page.svelte validates
activeTab against lesson data on every change, preventing the "tab quiz
kosong" (empty workspace) regression for quiz-only lessons and phantom-tab
state after data changes.

Files:
- lib/services/lesson-tabs.ts (+test): pure helpers, 18 tests pass
- lesson/[slug]/+page.svelte: ensureActiveTab reactive guard + QuizQuestionView
- lesson/[slug]/lesson.svelte.ts: atomic startQuiz, getExitStatus, idempotent
  submit on navigate/exit
- lib/services/quiz-session.ts (+test), lib/types/quiz.ts, QuizTab.svelte
  (mgr-driven), QuizQuestionView.svelte, lesson.css
- services/lesson_service.py, services/tests/test_lesson_service_quiz.py
  (test_lesson_service_quiz.py), requirements.txt (+pytest)
- docs/07-quiz-authoring.md
- version.ts APP_VERSION 21 -> 22 (forces SW update for the fix)
- examples/content/dasar/quiz_test.md synced to live content root
2026-08-01 00:14:29 +07:00
a2nr 2f689afc27 feat: implement sub-home/bab feature
- Generalize _read_md_cached() for generic file caching
- Add find_sub_home_for_lesson() and get_sub_home_data() helpers
- Add /api/bab/<folder> endpoint
- Detect sub-home in api_lesson for nav context
- Skip sub-home.md in find_lesson_file() and _parse_lesson_links()
- Add title to bab frontend page data
- Update generate_tokens.py & content_parser.py to skip sub-home.md
2026-07-27 22:01:01 +07:00
a2nr 9637740ea9 feat(quiz): add login gate, one-attempt enforcement, and exit penalty 2026-07-27 21:16:50 +07:00
a2nr 32ef89159a refactor(DeployTab): mode toggle inline in serial monitor + deploy FAB 2026-07-26 11:49:01 +07:00
a2nr bbdc53e601 refactor: playground self-contained full-viewport via svelte:head style 2026-07-24 15:54:29 +07:00
a2nr f705f1f314 fix: playground full-viewport width (override container max-width) 2026-07-24 15:41:36 +07:00
a2nr 455d449d1e fix: circuit tab full height in playground (override fixed 550px) 2026-07-24 14:03:44 +07:00
a2nr 1b2c51f72f chore: bump version to 19 for playground multi-tab 2026-07-24 13:46:18 +07:00
a2nr 589f3a148d feat: playground multi-tab (Velxio/Flowchart/Circuit/Code) with auto-save 2026-07-24 13:45:28 +07:00
a2nr 7e1139e7d1 refactor: make FlowchartTab eval button conditional on onRun prop 2026-07-24 13:44:25 +07:00
a2nr 8e89fde3c1 fix: docked editor panel height + quiz image parser + assets dir config
- fix(layout): editor-area docked mode sekarang height:70vh (sebelumnya
  max-height:85vh tanpa height, menyebabkan tab Arduino/velxio menciut
  ke 150px karena iframe tidak punya intrinsic height)
- fix(layout): tambah overflow:hidden pada velxio/flowchart/quiz panel
  untuk mencegah nested scrollable flex collapse
- feat(quiz): parser image dari 'image:' directive dan markdown ![alt](path)
  di question flashcard; konversi bare filename ke /assets/ path
- feat(assets): ASSETS_DIR derived dari CONTENT_DIR untuk support
  custom content directory (bukan hardcoded 'assets')
- fix(quiz): QuizTab container gunakan flex:1+min-height:0 (bukan height:100%)
- chore: hapus placeholder examples/assets/put_your_image.here
2026-07-21 20:50:01 +07:00
a2nr 98adc0b860 fix(usb-deployer): review fixes — reset pumpRunning on exit, clear buffer on readExact timeout
WARN-1: pumpRunning now reset to false when the pump loop exits via
{done} or error, so future startPump() isn't a silent no-op.
WARN-3: readExact clears rxBuffer on timeout to avoid stale bytes
leaking into the next non-retried STK500 command.
2026-07-19 19:15:20 +07:00
a2nr 6f2f9c589a chore(frontend): bump APP_VERSION to 18 for SW cache-bust
Service worker evicts old caches on activate, so users always
get the new usb-deployer bundle with the read-pump fix instead
of a stale cached one.
2026-07-19 19:09:45 +07:00
a2nr 056b1a47b6 refactor(usb-deployer): serial monitor + flashBuffer + lifecycle use pump
- startSerialMonitor/setBaudRate stop+restart pump, subscribe rxBuffer.onData
- flashBuffer stops pump, reuses/reopens port, starts pump, drains before sync
- stopReaderWriter now just releases locks; cleanup stops pump first
- remove dead keepReading field
2026-07-19 19:09:10 +07:00
a2nr f7c088013a fix(usb-deployer): read from rxBuffer, kill orphaned-read race in get_sync
readExact and drainStray now consume the FIFO buffer fed by the pump
instead of calling reader.read() directly. No orphaned reader.read()
promise can swallow optiboot INSYNC/OK replies anymore. This is the
core fix for "get_sync failed after N attempts".
2026-07-19 19:06:44 +07:00
a2nr 121d592ce0 feat(usb-deployer): add read pump + start pump after pair
Single pump loop becomes the only reader.read() caller, feeding
rxBuffer. Serial monitor + readExact/drain will consume from the
buffer (next tasks). Build passes.
2026-07-19 19:05:54 +07:00
a2nr bf4b1a4932 feat(frontend): add vitest + ByteStreamBuffer FIFO for orphan-read fix
Test runner setup (vitest) plus deadline-based FIFO buffer that
becomes the single sink for serial bytes. readExact keeps
partial bytes buffered on timeout — the exact case that dropped
optiboot INSYNC/OK replies under the orphaned-reader race.
7 unit tests pass.
2026-07-19 19:03:40 +07:00
a2nr 2a09ee77ab feat(embed): add raw HTML embed fence with bleach sanitization
- Add embed markdown fence: user pastes raw embed HTML code
  (from Canva/YouTube/Google Docs Share→Embed) into lesson markdown
  and slide content. Backend sanitizes via bleach (whitelist
  tags/attrs/styles) + checks iframe src against domain blacklist
  (SSRF prevention). Frontend renders iframe directly — no lazy
  action needed.
- Backend: _process_embed_embeds + _sanitize_embed_html in
  lesson_service.py, applied to lesson_content, exercise,
  lesson_info, and slide loop. Graceful fallback if tinycss2
  missing (CSS unsanitized but tags/attrs still stripped).
- Tests: 9 pytest cases (Canva/YouTube HTML, script stripping,
  onclick stripping, blocked domain, non-https iframe, empty,
  unchanged, dangerous style).
- Frontend: remove renderEmbedEmbeds.ts + wire-up + .generic-embed
  CSS (URL-only approach from earlier iteration, superseded).
  Keep .embed-error CSS for error messages.
- Example: update test_slides.md with raw HTML Canva embed (slide)
  + YouTube embed (body).
- Deps: bleach>=6.0.0, tinycss2>=1.2.0 in requirements.txt.
- Docs: consolidate 4 plan files into docs/06-embed-content.md.
2026-07-19 15:39:38 +07:00
a2nr 0cbac64c6f feat(frontend): Velxio iframe fullscreen + playground route + SvelteKit service worker
- VelxioTab: add allowfullscreen to Velxio editor iframe
- lesson.css: flex/shrink fixes for .velxio-iframe on mobile
- +layout.svelte: hide Navbar/Footer on /playground full-viewport route
- Add /playground route: Velxio editor iframe + DeployFAB bridge
- Migrate static sw.js to SvelteKit module service-worker.ts
2026-07-18 17:26:59 +07:00
a2nr f164134222 fix(usb-deployer): CH340 port reuse fix — skip reopen, single DTR, baud tracking 2026-07-17 14:35:30 +07:00
a2nr 2773bf2a83 feat(usb-deployer): double DTR reset + post-write delay for stubborn clones 2026-07-17 12:47:58 +07:00
a2nr 95a3569be9 chore: bump version to v17 2026-07-17 11:07:46 +07:00
a2nr 8adc28b745 refactor(usb-deployer): use named settle constant and fix comment 2026-07-17 10:38:40 +07:00
a2nr 852fccacdf fix(usb-deployer): rebalance sync retries/backoff for optiboot window 2026-07-17 10:37:57 +07:00
a2nr 2fd2b1b5b4 fix: register SvelteKit service-worker.js instead of static sw.js 2026-07-17 10:37:48 +07:00
a2nr 84827350af fix(usb-deployer): drain stray bytes until quiet window 2026-07-17 10:37:08 +07:00
a2nr 3d941191e3 refactor(usb-deployer): name timing constants for sync window tuning 2026-07-17 10:35:23 +07:00
a2nr 4ec5d4cb92 fix(usb-deployer): add detailed diagnostic logging for sync failures 2026-07-17 10:24:39 +07:00
a2nr 7dd8e5d522 fix(usb-deployer): preliminary get_sync robustness improvements 2026-07-17 10:23:39 +07:00
a2nr 24db01fd8d refactor: Footer.svelte imports APP_VERSION from $lib/version 2026-07-17 09:46:29 +07:00
a2nr 12725cbdad chore: bump version to v16 2026-07-17 09:26:17 +07:00
a2nr 01f68d354a fix: disable toggle during deploy, SSR guard for matchMedia, reactive isDesktop, safe deployBinary guard 2026-07-17 08:20:56 +07:00
a2nr 185fed636e feat: add BLE/USB toggle with auto-detect in DeployTab 2026-07-17 08:17:54 +07:00
a2nr 369897529d fix: harden USBHardwareDeployer — drain after timeout, async cleanup, flash error recovery, baud切换 2026-07-17 08:15:21 +07:00
a2nr f8e92875c9 feat: add USBHardwareDeployer with STK500v1 protocol via Web Serial API 2026-07-17 08:10:12 +07:00
a2nr 1b51e75ff0 feat: add HardwareDeployer interface, BLEHardwareDeployer implements it 2026-07-17 08:03:42 +07:00
a2nr a7c9d1c499 fix(deploy-tab): await handleOpenSerial() in deploy success path for proper error handling 2026-07-05 17:53:45 +07:00
a2nr bb19523f65 feat(deploy-tab): auto re-pair before deploy if connection lost 2026-07-05 17:49:40 +07:00