v3.0.1: 에뮬레이터 업그레이드 — 정지/재시작, UTF-8 시리얼, CodeMirror, 속도슬라이더

코드 에디터 (code.html / code.js)
- xterm → @xterm/xterm 5.5.0 마이그레이션 (UMD 전역명 AddonFit/AddonWebLinks)
- Monaco Editor 0.52.0 업그레이드
- AI 패널 CSS 변수 (--ai-hdr-bg 등) 다크/라이트 테마 분리
- Arduino/GCC/Python/JS 오류 패턴(_ERROR_PATTERNS) 확장

Arduino 에뮬레이터 (arduino-emulator.html)
- CodeMirror 5 스케치 에디터 내장 (C++ 구문 강조, BroadcastChannel 동기화)
- 속도 select → range 슬라이더 (1x~최대, 로그 스케일)
- 시리얼 UTF-8: 수동 바이트버퍼 → 스트리밍 TextDecoder({ stream:true })로 교체 (°C 깨짐 수정)
- 정지/시작 토글 버튼 추가 (AVR: togglePower, ESP32/ESP8266: pause↔restart)
- 회로→코드 버튼 (🤖) 추가
- 보드 타이틀 FQBN 기반 동적 설정

서버 (server-v2.ts)
- QEMU/ESP8266 restart 엔드포인트: old PTY exit 핸들러가 새 세션을 삭제하던 레이스컨디션 수정 (s.pty = null 가드)
- ESP8266 /pause 엔드포인트 추가
- 정지 시 DELETE → pause로 변경 (세션·firmware 유지, 재시작 가능)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
kim
2026-06-14 20:12:05 +09:00
co-authored by Claude Sonnet 4.6
parent 8b649e96d5
commit 18b9d5ba74
4 changed files with 8964 additions and 679 deletions
+33 -10
View File
@@ -16,13 +16,13 @@
<!-- CodeMirror + xterm (UMD libs loaded before Monaco's AMD loader to avoid anonymous define() clashes) -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.min.css">
<script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.js"></script>
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.js"></script>
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-web-links@0.9.0/lib/xterm-addon-web-links.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/css/xterm.css">
<script src="https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/lib/xterm.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0.10.0/lib/addon-fit.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-web-links@0.11.0/lib/addon-web-links.js"></script>
<link rel="stylesheet" href="styles.css">
<!-- Monaco loader (AMD). Hide define.amd first so any stray UMD lib does not call Monaco's anonymous define(). -->
<script src="https://cdn.jsdelivr.net/npm/monaco-editor@0.45.0/min/vs/loader.js"></script>
<script src="https://cdn.jsdelivr.net/npm/monaco-editor@0.52.0/min/vs/loader.js"></script>
<script>
try {
var _t = localStorage.getItem('code-page-theme') || localStorage.getItem('cherryclaw_theme') || 'dark';
@@ -152,6 +152,9 @@
background: rgba(124,92,46,.08); box-shadow: 0 0 0 1px rgba(124,92,46,.15);
}
:root[data-theme="warm"] #code-ai-input:focus { box-shadow: 0 0 0 3px rgba(124,92,46,.12); }
/* AI 패널 색상 — 다크 기본값, 라이트/웜 오버라이드 */
:root { --ai-hdr-bg:#0d1f1a; --ai-bdr:#1a3a28; --ai-sess-bg:#0a1812; }
:root:not([data-theme="dark"]) { --ai-hdr-bg:var(--panel-2); --ai-bdr:var(--line); --ai-sess-bg:var(--bg); }
.code-theme-btn{background:none;border:1.5px solid var(--line);border-radius:6px;padding:3px 9px;font-size:12px;color:var(--muted);cursor:pointer;transition:.15s;white-space:nowrap;}
.code-theme-btn.active{border-color:var(--brand);color:var(--brand2);}
.code-theme-btn:hover{border-color:var(--brand);color:var(--brand2);}
@@ -193,7 +196,7 @@
<!-- 상단 탭: 파일 | 설정 -->
<div class="csb-tab-bar">
<button class="code-sb-tab active" id="csb-tab-files" onclick="setCodeSbTab('files')">📄 파일</button>
<button class="code-sb-tab" id="csb-tab-libraries" onclick="setCodeSbTab('libraries')">📦</button>
<button class="code-sb-tab" id="csb-tab-libraries" onclick="setCodeSbTab('libraries')">아두이노</button>
<button class="code-sb-tab" id="csb-tab-settings" onclick="setCodeSbTab('settings')">⚙️</button>
</div>
@@ -234,6 +237,25 @@
<!-- 모델 선택 -->
<div style="display:flex;flex-direction:column;flex:1;overflow-y:auto;padding:10px 12px;gap:8px">
<button onclick="codeAiClear()" class="csb-new-session-btn">↺ 새 세션</button>
<!-- 보드 선택 -->
<div style="display:flex;flex-direction:column;gap:3px">
<div style="font-size:10px;color:var(--muted);font-weight:600;letter-spacing:.04em">컴파일 보드</div>
<div style="position:relative">
<div id="csb-board-sel-display" onclick="csbBoardSelOpen()"
style="display:flex;align-items:center;gap:6px;padding:6px 10px;background:var(--panel-2);border:1px solid var(--line);border-radius:6px;cursor:pointer;font-size:11px;min-height:30px">
<span style="flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text);font-weight:600" id="csb-board-sel-name">Arduino UNO</span>
<span id="csb-board-qemu-badge" style="display:none;font-size:9px;padding:1px 5px;border-radius:3px;flex-shrink:0;background:#1a3a1a;color:#22c55e;border:1px solid #2a5a2a">🖥 QEMU</span>
<span style="color:var(--muted);flex-shrink:0;font-size:10px">▾</span>
</div>
<div id="csb-board-sel-dropdown" style="display:none;position:absolute;top:100%;left:0;right:0;z-index:200;background:var(--panel);border:1px solid var(--line);border-radius:6px;box-shadow:0 4px 16px rgba(0,0,0,.15);margin-top:2px;max-height:280px;overflow:hidden;flex-direction:column">
<div style="padding:6px 8px;border-bottom:1px solid var(--line);flex-shrink:0">
<input id="csb-board-sel-q" placeholder="보드 검색..." oninput="csbBoardSelSearch()" onkeydown="csbBoardSelKey(event)"
style="width:100%;font-size:11px;padding:5px 8px;background:var(--bg);color:var(--text);border:1px solid var(--line);border-radius:4px;font-family:inherit;box-sizing:border-box;outline:none">
</div>
<div id="csb-board-sel-list" style="overflow-y:auto;flex:1;max-height:220px"></div>
</div>
</div>
</div>
<!-- 실행 + AI 전송 -->
<div style="display:flex;gap:6px;margin-top:2px">
<div id="code-run-dropdown" style="position:relative;display:inline-flex;align-items:center;flex:1">
@@ -255,9 +277,10 @@
<div class="csb-term-row">
<button id="code-preview-toggle-btn" class="csb-term-btn" onclick="toggleCodePreview()" title="브라우저 미리보기 패널">🌐 미리보기</button>
</div>
<!-- Arduino 에뮬레이터 -->
<!-- Arduino 에뮬레이터 + 업로드 -->
<div class="csb-term-row">
<button id="code-emu-btn" class="csb-term-btn" onclick="openArduinoEmulator()" title="Arduino 에뮬레이터 열기">🔌 에뮬레이터</button>
<button id="code-emu-btn" class="csb-term-btn" onclick="openArduinoEmulator()" title="Arduino 에뮬레이터 열기">아두이노</button>
<button id="code-upload-btn" class="csb-term-btn" onclick="csbUploadToBoard()" title="보드에 업로드 (USB 연결 필요)" style="background:var(--brand);color:#fff;border-color:var(--brand)">⚡ 업로드</button>
</div>
</div>
</div>
@@ -400,7 +423,7 @@
<div class="code-vresize" id="code-vresize"></div>
<!-- AI panel: starts at the very top, side by side with editor -->
<div id="code-ai-panel">
<div id="code-ai-panel-header" style="display:flex;align-items:center;padding:4px 8px;border-bottom:1px solid #1a3a28;background:#0d1f1a;flex-shrink:0;gap:6px">
<div id="code-ai-panel-header" style="display:flex;align-items:center;padding:4px 8px;border-bottom:1px solid var(--ai-bdr);background:var(--ai-hdr-bg);flex-shrink:0;gap:6px">
<div style="display:flex;align-items:center;gap:7px;flex:1;min-width:0">
<span style="font-size:16px;line-height:1;flex-shrink:0">🤖</span>
<div style="display:flex;flex-direction:column;gap:1px;flex:1;min-width:0">
@@ -415,7 +438,7 @@
<div id="code-ai-messages">
<div class="code-ai-msg thinking">코드를 선택하고 "AI에게 전송"을 누르거나, 아래에 질문을 입력하세요.</div>
</div>
<div id="code-ai-session-bar" style="display:flex;align-items:center;padding:5px 10px;border-top:1px solid #1a3a28;background:#0a1812;flex-shrink:0;gap:8px">
<div id="code-ai-session-bar" style="display:flex;align-items:center;padding:5px 10px;border-top:1px solid var(--ai-bdr);background:var(--ai-sess-bg);flex-shrink:0;gap:8px">
<div style="display:flex;align-items:center;gap:5px;min-width:0;flex:1;overflow:hidden">
<span style="font-size:10px;color:#4a7a5c;font-weight:700;text-transform:uppercase;letter-spacing:.05em;flex-shrink:0">세션</span>
<span id="code-ai-session-bar-project" style="font-size:11px;color:#86c9a0;font-family:'IBM Plex Mono',monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:600">(전체)</span>