Arsitektur: upload tetap menyimpan original apa adanya; optimasi terjadi
saat serving — gambar raster di-resize (maks 1600px) + encode WebP
(quality 80), di-generate on-the-fly lalu di-cache di disk.
- config.CACHE_DIR = sibling ASSETS_DIR ('assets-cache'), WAJIB di luar
ASSETS_DIR supaya export_bundle (os.walk tanpa filter) tetap lossless
- services/image_cache.py: varian WebP, cache key path+mtime+size+width,
invalidasi otomatis via mtime, atomic write, fallback original bila gagal
- routes/lessons.py::send_assets: handler (bukan static serve) —
.png/.jpg/.jpeg/.webp/.gif -> varian WebP; .svg & GIF animasi pass-through;
?orig=1 untuk original; ?w= untuk lebar alternatif; ETag + Cache-Control
- Dockerfile: buat /app/assets-cache writable
Test (services/tests/test_asset_cache.py, 20 test): lokasi cache di luar
ASSETS_DIR, cache reuse, invalidasi mtime, passthrough SVG/GIF animasi,
fallback file korup, ETag/304, dan acceptance test — upload -> request ->
export bundle berisi HANYA original tanpa .webp turunan.
Fitur A — Indikator loading /teacher/content:
- treeBusy/treeBusyAction state + withTreeBusy() helper (guard double-submit)
- handleCreateFolder/CreateFile/Rename/Delete/Upload/Export dibungkus busy state
- tombol tree-action-btn disabled saat operasi berjalan, tombol aktif menampilkan ⏳
- importProgress kini dirender (sebelumnya dead code) di header tree
Fitur C — Perbaikan UX editor:
- C1: tombol aksi tree dipindah ke kiri, tab Materi/Assets ke kanan (desktop & mobile)
- C2: home.md dibuka otomatis saat pertama kali masuk editor
- C3: rail tools menggantikan tab 'Fitur & Aksi' (desktop & mobile identik);
kartu status file aktif (nama, badge dirty, Simpan/Publish/Edit) pindah ke rail
- C4: template baru <details><summary> di grup layout
Catatan: target scroll 'Fitur & Aksi' dihapus; tab tree memakai seluruh lebar
saat file tidak punya tab (exercise/quiz/kode).
Kebijakan termination tetap strict, tapi guru pegang override untuk kendala
tak terduga (false-positive blur dari dialog OS, browser crash, refresh
tak sengaja). Selain reset (siswa mengulang dari nol), guru kini bisa
memberi izin resume — siswa melanjutkan dari jawaban yang sudah tersimpan.
- Tambah kolom quiz_attempts.resumed_at (migrasi 0006) sebagai penanda izin
guru melanjutkan attempt terminated
- Tambah endpoint teacher-only POST /resume-quiz-attempt — kontrak keamanan
sama dengan /reset-progress (guru cukup student_id anonim, tanpa token siswa)
- Syarat: hanya attempt ber-status terminated yang bisa di-resume; submitted
ditolak karena kuis sudah selesai normal
- has_quiz_attempt meloloskan attempt ber-izin sehingga gate one-attempt
terbuka selama sesi lanjutan
- finalize_quiz_attempt menimpa row attempt sekali (status/skor/answers baru)
lalu clear resumed_at; retry berikutnya kembali ke jalur idempotent normal
- Sinyal ke frontend siswa lewat quiz_resumable pada data lesson; laporan guru
menampilkan badge "Dilanjutkan" dan tombol "Lanjutkan" untuk attempt
terminated
- Frontend: restoreQuizSession() merekonstruksi sesi dari jawaban tersimpan per
question_id, is_correct dihitung ulang dari source sebagai guard bila
konten soal berubah; option_id tak dikenal dianggap belum dijawab
- Frontend: firstUnansweredIndex() menentukan soal awal sesi lanjutan;
resolveQuizTabView() memusatkan keputusan view agar transisi resume dapat
diuji Vitest
- UI siswa: panel "Lanjutkan Kuis" + snackbar mode lanjutan (sekali muncul).
Attempt di-refetch segar sebelum sesi dimulai agar izin yang sudah dicabut
guru tidak memulai sesi yang jawabannya akan terbuang
- Test: 16 integrasi backend (services/tests/test_resume_quiz.py), 9 unit
quiz-session, 10 unit quiz-view
- Docs: alur resume + keterbatasan di 12-quiz-integrity.md, ringkasan di
07-quiz-authoring.md, kontrak endpoint di 02-backend-flask.md
Hasil review pasca-fix upload asset:
Keamanan:
- Mitigasi stored-XSS via SVG (route /assets/* publik, same-origin):
CSP 'default-src none; sandbox' saat serve SVG — di backend
(send_assets) DAN di proxy SvelteKit (proxy hanya meneruskan
content-type/set-cookie, jadi CSP backend tak sampai ke browser).
- Deteksi 413 proxy kini ber-test status code + pesan.
Testing:
- Regression guard: error jaringan biasa tetap 502, bukan 413.
- 413 via status code saja (tanpa regex pesan) tetap 413.
- Boundary 5 MiB: file TEPAT 5 MiB lolos; 5 MiB+1 ditolak (cabang
len(raw) — SVG dipakai karena dikecualikan validasi PIL).
- CSP: SVG dapat header, raster tidak.
- E2E script (verify_asset_upload.py) memakai file asli
mr-edu/assets/ilustrasi_beban.jpeg + test 413 terintegrasi.
Reuseability:
- Komentar silang-referensi _MAX_ASSET_BYTES (sumber) dengan pesan 413
proxy dan BODY_SIZE_LIMIT compose.
Body upload (JSON base64, ~7 MiB untuk file 5 MiB) ditolak default
adapter-node 512K sebelum sampai backend Flask, lalu error itu
terlapor sebagai 502 'Backend tidak tersedia'.
- podman-compose: BODY_SIZE_LIMIT=8M untuk elemes-frontend
- hooks.server: SvelteKitError body-size (413) dipisah dari kegagalan
koneksi — balas 413 'Ukuran file terlalu besar' bukan 502
- test: proxy balas 413 untuk body melebihi limit
- Add getProgress and getAllProgress to local IndexedDB SyncManager
- Support opportunistic background sync for lesson progress and quizzes
- Connect LessonManager to IndexedDB progress cache for offline lesson completion status
- Disable SSR for /lesson and /bab routes to ensure consistent client-side local storage
- Count only distinct students for sub-chapter folder completion badge
- Separate teacher attempts from student counter on lesson header with a dedicated '+Y guru' badge
- Differentiate teacher rows with teacher icon and dashed styling
- Redesign /teacher/progress into a folder-like tree view (sub-bab -> lesson -> completed students)
- Create /teacher/classroom for student management (import/export CSV, add, bulk delete)
- Enrich /progress-report.json API response with sub_bab, sub_bab_title, and sub_bab_list
- Add Classroom navigation item to teacher navbar menu
- Unbuffer C stdout via setbuf(stdout, NULL) so prompts flush before blocking on input
- Break Emscripten FS.read loop with justFinishedLine and upgrade float/%f to double/%lf for PicoC compatibility
- Provide reactive waitingForInput state with auto-focus, dynamic placeholder, and highlighted send button in ConsolePanel
- Enable full multi-step interactive stdin flow in browser execution mode via SharedArrayBuffer and Atomics
- Lock viewport and relative navbar in +layout.svelte on both mobile and desktop
- Remove static min-height and overflow-y overrides on mobile in +page.svelte
- Auto-collapse file tree and compact code toolbar on mobile
- Add safe-area padding and 200px mobile height in ConsolePanel.svelte
- Bump APP_VERSION to 32
- Add client-side code runner via Web Workers (PicoC for C, MicroPython WASM for Python)
- Support interactive stdin with SharedArrayBuffer and Atomics
- Implement offline database mirror using Dexie.js with opportunistic auto-sync
- Add execution mode toggle in Playground (Browser vs Server)
- Configure COOP, COEP, and CORP security headers in SvelteKit server hook
- Fix Velxio and Flowchart Nginx headers and add credentialless attribute to iframes
- Mount Nginx configs in podman-compose.yml for velxio and flowchart services
- Add unit and E2E Playwright integration tests (100% passed)
- Only show celebration overlay and top bar completed checkmark if quiz score meets 75% threshold (>= 0.75) and exercises are done
- Return is_done, state, and composite_percent in POST /lesson-progress for exercise and quiz submissions
- Synchronize frontend LessonManager state with authoritative server is_done response
- Add has_quiz_attempt check in routes/lessons.py and token_service to recognize existing attempts even when composite state is in_progress
- Update QuizTab to display persistent attempt review, score status, and explanation instead of retake prompt on page load/refresh
- Make QuizQuestionView finish handler async, await finishQuiz(), and disable submission buttons to prevent navigation race conditions
- Update local ordered_lessons completion state dynamically upon passing
- Add unit and integration tests covering response payload attributes and has_quiz_attempt API field
- Bump APP_VERSION to 30
- Sync imported progress (completed, scored, done, reset) to composite progress model and recompute composite_percent
- Prevent caching of /progress-report.json with Cache-Control: no-store and frontend cache-busting timestamp
- Provide inline expected format guides in exported CSV lesson headers (e.g. [completed], [<earned>/<total>], [done:<1|0>:<earned>/N])
- Support robust slug extraction from formatted headers in CSV roundtrip import with full backward compatibility
- Add example composite lesson (exercise + quiz) in examples/content/dasar/composite_test.md
- Add comprehensive unit and integration tests covering progress synchronization, header formatting, and roundtrip workflows
- Optimize N+1 queries in get_all_students_progress via bulk fetch
- Backfill legacy progress in migration 0005 and startup sync
- Use arrayBuffer in hooks.server proxy for binary payload safety
- Restrict postMessage targetOrigin from wildcard to window.location.origin
- Harden iframe polling in TeacherVelxioEditor and reduce frequency
- Improve markdown section extractor line-boundary detection
- Clean up child subprocesses on exception in CLI runner
- Add console.error in contentEditor catch blocks and parameterize repro script
- Configure sequential vitest execution to optimize test runs
- Point make test-cli to isolated cli/tests/test_cli_tui.py
- Add cache_dir = /tmp/.pytest_cache in pytest.ini to avoid permission denied warnings under non-root container users
- Auto-save dirty draft before publishing in ContentEditorManager so in-memory
Velxio circuit/code changes are never lost when clicking Publish directly
- Add resolveRestoredContent and baseline tracking to velxio-manager so lesson
view immediately reflects updated server code instead of stale localStorage
- Invalidate in-memory lesson and markdown caches on draft publish and file operations
- Update teacher content page publish button disabled condition to check saving state
- Modularize orchestrator into Python modules under cli/ (config, runner, commands, tui)
- Introduce interactive Rich-based TUI with live container status, focused description panel, and arrow navigation
- Add multi-OS launcher wrappers: elemes.sh (Unix), elemes.bat (Windows CMD), elemes.ps1 (PowerShell), elemes.py
- Default isolated virtualenv to parent folder (../venv) to keep repository clean
- Add stopclean command for compose down with container & image pruning
- Fix submenu navigation when pressing ENTER on Kembali and Keluar
- Prevent keystroke bleeding across subprocess executions with DEVNULL stdin and tcflush
- Add unit test suite (make test-cli) covering 27 isolated CLI/TUI tests
- Maintain 100% backward compatibility with existing elemes.sh commands and shell regression tests
- Add EXPECTED_WIRING block to set-arduino template and standalone expected-wiring feature in teacher content editor
- Reposition reload button to the far left inline with status dot, and shorten status text to 'Siap' / 'Menghubungkan...'
- Decouple transfer from save: replace 'Simpan ke Dokumen' with 'Pindahkan Hasil' and centralize document saving to top header save button
- Dynamically detect board FQBN from circuit in DeployTab with arduino:avr:uno fallback
_scan_quiz_issues.py: cari soal MCQ dengan <2 opsi (ter-split).
repro_quiz_options.py: navigasi quiz di browser, assert jumlah opsi DOM
vs server quiz_data.
currentQuizQuestion dijadikan $state (bukan getter) yang di-sync lewat
_syncCurrentQuestion() tiap ganti soal, dan {#each} opsi memakai :key
option.id. Mencegah opsi 'hilang' 1 frame saat navigasi mcq<->flashcard.
Tambah test integritas shuffle (opsi tetap lengkap & id konsisten).
_split_flashcard_parts() pakai state-machine per-baris agar komentar '#'
di dalam blok kode (python/shell/bash/yaml) tidak dianggap heading baru.
Sebelumnya re.split('^#{1,3}\s+') membelah soal menjadi dua, sehingga
opsi meleset ke 'soal hantu' dan pilihan di layar tidak sesuai markdown.
- Integrate diagnostic and evaluation MCQ scoring in quiz sessions
- Extend round-trip CSV grammar with `done:<ex>:<earned>/<total>` and `RESET`
- Fix completed count aggregation in repositories to include 'done' state
- Update progress report CSV export to output composite scores per lesson
- Add comprehensive test coverage for scoring, CSV round-trip, and exports
- Mount SlideCarousel secara dinamis di LessonContentView (#slide-mount-point)
- Fix infinite reactivity loop (effect_update_depth_exceeded) dengan menggunakan plain variable bukan $state untuk handle slideComponent
- Tambah auto-rendering (math, code, flowchart, circuit) saat slide berubah/expand di SlideCarousel
- Defensive try-catch unmount/mount dan eksplisit type ReturnType<typeof mount>
- Update sidebar feature template di /teacher/content (Layout, Set Soal bundle, Inline)
Migration 0005 gagal karena UPDATE state='in_progress' dijalankan
sebelum constraint lama di-drop. Constraint lama (dari 0001) hanya
mengizinkan 'not_started','completed','scored' sehingga 'in_progress'
ditolak PostgreSQL (CheckViolation).
Fix: pindahkan DROP/CREATE constraint ke sebelum blok UPDATE backfill.