코드 에디터: 터미널 버튼 아래에 에뮬레이터 버튼 추가

.ino 파일이 열려 있으면 자동 컴파일 후 에뮬레이터 탭 열기,
아니면 에뮬레이터 창만 열기. 컴파일 중 버튼 텍스트로 상태 표시.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
kim
2026-06-11 22:54:23 +09:00
co-authored by Claude Sonnet 4.6
parent e69606dc31
commit e3920dd157
2 changed files with 348 additions and 3 deletions
+50 -2
View File
@@ -192,8 +192,9 @@
<div id="sidebar-code" style="display:flex;flex-direction:column;flex:1;min-height:0;overflow:hidden">
<!-- 상단 탭: 파일 | 설정 -->
<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-settings"onclick="setCodeSbTab('settings')">⚙️ 설정</button>
<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-settings" onclick="setCodeSbTab('settings')">⚙️</button>
</div>
<!-- 파일 탭: 상하 분할 -->
@@ -254,10 +255,57 @@
<div class="csb-term-row">
<button id="code-preview-toggle-btn" class="csb-term-btn" onclick="toggleCodePreview()" title="브라우저 미리보기 패널">🌐 미리보기</button>
</div>
<!-- Arduino 에뮬레이터 -->
<div class="csb-term-row">
<button id="code-emu-btn" class="csb-term-btn" onclick="openArduinoEmulator()" title="Arduino 에뮬레이터 열기">🔌 에뮬레이터</button>
</div>
</div>
</div>
</div>
<!-- 라이브러리/보드 탭 -->
<div id="csb-libraries" style="display:none;flex-direction:column;flex:1;min-height:0">
<!-- 상단 섹션 탭: 라이브러리 | 보드 -->
<div style="display:flex;border-bottom:2px solid var(--line);flex-shrink:0;background:var(--panel-2)">
<button class="code-sb-tab active" id="csb-ardsec-lib" onclick="csbArdSection('lib')" style="flex:1;font-size:11px;padding:7px 0">📦 라이브러리</button>
<button class="code-sb-tab" id="csb-ardsec-board" onclick="csbArdSection('board')" style="flex:1;font-size:11px;padding:7px 0">🔌 보드</button>
</div>
<!-- ── 라이브러리 패널 ── -->
<div id="csb-ard-lib" style="display:flex;flex-direction:column;flex:1;min-height:0">
<div style="display:flex;border-bottom:1px solid var(--line);flex-shrink:0">
<button class="code-sb-tab active" id="csb-libtab-installed" onclick="csbLibTab('installed')" style="flex:1;font-size:10px;padding:5px 0">설치됨</button>
<button class="code-sb-tab" id="csb-libtab-search" onclick="csbLibTab('search')" style="flex:1;font-size:10px;padding:5px 0">검색/설치</button>
</div>
<div id="csb-lib-search-row" style="display:none;padding:6px 8px;border-bottom:1px solid var(--line);gap:5px;flex-shrink:0;align-items:center">
<input id="csb-lib-q" placeholder="키워드 또는 라이브러리명..." onkeydown="if(event.key==='Enter')csbLibSearch()"
style="flex:1;font-size:11px;padding:5px 8px;background:var(--panel-2);color:var(--text);border:1px solid var(--line);border-radius:6px;font-family:inherit;min-width:0">
<button onclick="csbLibSearch()" class="csb-term-btn" style="padding:5px 8px;font-size:11px;flex-shrink:0">검색</button>
</div>
<div id="csb-lib-list" style="flex:1;overflow-y:auto;padding:4px 0">
<div style="padding:16px 12px;color:var(--muted);font-size:11px;text-align:center">불러오는 중...</div>
</div>
<div id="csb-lib-status" style="padding:5px 10px;font-size:10px;color:var(--brand);border-top:1px solid var(--line);min-height:24px;flex-shrink:0"></div>
</div>
<!-- ── 보드 매니저 패널 ── -->
<div id="csb-ard-board" style="display:none;flex-direction:column;flex:1;min-height:0">
<div style="display:flex;border-bottom:1px solid var(--line);flex-shrink:0">
<button class="code-sb-tab active" id="csb-boardtab-installed" onclick="csbBoardTab('installed')" style="flex:1;font-size:10px;padding:5px 0">설치됨</button>
<button class="code-sb-tab" id="csb-boardtab-search" onclick="csbBoardTab('search')" style="flex:1;font-size:10px;padding:5px 0">검색/설치</button>
</div>
<div id="csb-board-search-row" style="display:none;padding:6px 8px;border-bottom:1px solid var(--line);gap:5px;flex-shrink:0;align-items:center">
<input id="csb-board-q" placeholder="보드 이름 (예: esp32, rp2040...)" onkeydown="if(event.key==='Enter')csbBoardSearch()"
style="flex:1;font-size:11px;padding:5px 8px;background:var(--panel-2);color:var(--text);border:1px solid var(--line);border-radius:6px;font-family:inherit;min-width:0">
<button onclick="csbBoardSearch()" class="csb-term-btn" style="padding:5px 8px;font-size:11px;flex-shrink:0">검색</button>
</div>
<div id="csb-board-list" style="flex:1;overflow-y:auto;padding:4px 0">
<div style="padding:16px 12px;color:var(--muted);font-size:11px;text-align:center">불러오는 중...</div>
</div>
<div id="csb-board-status" style="padding:5px 10px;font-size:10px;color:var(--brand);border-top:1px solid var(--line);min-height:24px;flex-shrink:0"></div>
</div>
</div>
<!-- 설정 탭 -->
<div id="csb-settings" style="display:none;flex-direction:column;flex:1;min-height:0;overflow-y:auto;padding:14px 12px;gap:14px">
<div>
+298 -1
View File
@@ -884,6 +884,36 @@ function toggleCodePreview() {
}
if (monacoEditor) setTimeout(() => monacoEditor.layout(), 60);
}
function openArduinoEmulator() {
const file = codeFiles.find(f => f.id === activeCodeFileId);
const btn = document.getElementById('code-emu-btn');
// .ino 파일이 열려 있으면 컴파일 후 에뮬레이터 실행
if (file && file.name.endsWith('.ino')) {
if (btn) { btn.textContent = '⏳ 컴파일 중…'; btn.disabled = true; }
fetch('/api/arduino/compile', {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...authHeaders({}) },
body: JSON.stringify({ code: file.content, board: 'uno' }),
}).then(r => r.json()).then(data => {
if (btn) { btn.textContent = '🔌 에뮬레이터'; btn.disabled = false; }
if (data.error) { codeAiAppend('system', '⚠️ 컴파일 오류:\n' + data.error); return; }
sessionStorage.setItem('arduino_sketch', file.content);
sessionStorage.setItem('arduino_hex', data.hex);
sessionStorage.setItem('arduino_filename', file.name);
sessionStorage.setItem('arduino_board', 'uno');
const w = window.open('/arduino-emulator.html', 'arduino-emulator');
if (!w) codeAiAppend('system', '⚠️ 팝업 차단됨 — 브라우저에서 팝업을 허용해주세요');
}).catch(e => {
if (btn) { btn.textContent = '🔌 에뮬레이터'; btn.disabled = false; }
codeAiAppend('system', '⚠️ 서버 오류: ' + e.message);
});
} else {
// .ino 아니면 그냥 에뮬레이터 창만 열기
window.open('/arduino-emulator.html', 'arduino-emulator');
}
}
function codePreviewRefresh() {
const urlInput = document.getElementById('code-preview-url');
const iframe = document.getElementById('code-preview-iframe');
@@ -2551,6 +2581,11 @@ function _buildCodeSystemPrompt() {
- Glob으로 파일 목록 검색, Grep으로 코드 내용 검색이 가능합니다.
- Bash는 companion.py 연결 시 터미널 명령을 실행합니다.
## 파일 경로 규칙 (중요)
- 파일 경로에 절대경로·Windows 경로를 절대 사용하지 마세요: Documents/, Desktop/, C:\\, /home/ 등 금지.
- 프로젝트 루트 기준 상대 경로만 사용하세요. 예: \`main.ino\`, \`src/utils.cpp\`, \`lib/helper.h\`
- 새 프로젝트를 만들면 해당 폴더가 루트입니다. 별도의 상위 폴더를 만들지 마세요.
## 응답 원칙
- 행동 중심으로 응답하세요: 설명만 하지 말고 실제로 파일을 수정하세요.
- 변경 사항은 간결하게 설명하고, 코드 블록으로 전체 내용을 반복하지 마세요.
@@ -3944,13 +3979,275 @@ let codeSbCurrentTab = 'files';
function setCodeSbTab(tab) {
codeSbCurrentTab = tab;
['files','settings'].forEach(t => {
['files','settings','libraries'].forEach(t => {
const btn = document.getElementById('csb-tab-' + t);
if (btn) btn.classList.toggle('active', t === tab);
const el = document.getElementById('csb-' + t);
if (el) el.style.display = t === tab ? 'flex' : 'none';
});
if (tab === 'files') csbLoadModels();
if (tab === 'libraries') {
if (_csbArdSection === 'lib') csbLibLoad();
if (_csbArdSection === 'board') csbBoardLoad();
}
}
// ── 라이브러리/보드 매니저 (code editor sidebar) ──────────────────────────────
let _csbLibCurrentTab = 'installed';
let _csbArdSection = 'lib';
function csbArdSection(sec) {
_csbArdSection = sec;
['lib','board'].forEach(s => {
const btn = document.getElementById('csb-ardsec-' + s);
if (btn) btn.classList.toggle('active', s === sec);
const el = document.getElementById('csb-ard-' + s);
if (el) el.style.display = s === sec ? 'flex' : 'none';
});
if (sec === 'lib') csbLibLoad();
if (sec === 'board') csbBoardLoad();
}
function csbLibTab(tab) {
_csbLibCurrentTab = tab;
['installed','search'].forEach(t => {
const btn = document.getElementById('csb-libtab-' + t);
if (btn) btn.classList.toggle('active', t === tab);
});
const sr = document.getElementById('csb-lib-search-row');
if (sr) sr.style.display = tab === 'search' ? 'flex' : 'none';
document.getElementById('csb-lib-status').textContent = '';
if (tab === 'installed') csbLibLoad();
else _csbLibListEl().innerHTML = '<div style="padding:16px;color:var(--muted);font-size:11px;text-align:center">키워드를 입력하세요.</div>';
}
function _csbLibListEl() { return document.getElementById('csb-lib-list'); }
function _csbLibStatus(msg) { const el = document.getElementById('csb-lib-status'); if (el) el.textContent = msg; }
function _csbEsc(s) { return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;'); }
async function csbLibLoad() {
_csbLibListEl().innerHTML = '<div style="padding:16px;color:var(--muted);font-size:11px;text-align:center">불러오는 중...</div>';
try {
const r = await fetch('/api/arduino/libs', { headers: authHeaders() });
const data = await r.json();
_csbLibRenderInstalled(data.libs || []);
} catch { _csbLibListEl().innerHTML = '<div style="padding:16px;color:var(--muted);font-size:11px;text-align:center">오류</div>'; }
}
function _csbLibRenderInstalled(libs) {
const el = _csbLibListEl();
if (!libs.length) { el.innerHTML = '<div style="padding:16px;color:var(--muted);font-size:11px;text-align:center">설치된 라이브러리 없음</div>'; return; }
el.innerHTML = libs.map(l => {
const sid = 'csbex_' + l.name.replace(/\W/g,'_');
const exRows = (l.examples||[]).map(ex =>
`<div style="display:flex;align-items:center;padding:3px 0;gap:6px">
<span style="font-size:10px;color:var(--muted);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">📄 ${_csbEsc(ex)}</span>
<button onclick="csbLibOpenExample('${_csbEsc(l.name)}','${_csbEsc(ex)}')"
style="font-size:10px;padding:2px 7px;background:none;border:1px solid var(--line);color:var(--brand);border-radius:4px;cursor:pointer;font-family:inherit;white-space:nowrap">열기</button>
</div>`).join('');
const hasEx = l.examples?.length > 0;
return `<div style="padding:8px 10px;border-bottom:1px solid var(--line)">
<div style="display:flex;align-items:flex-start;gap:6px">
<div style="flex:1;min-width:0">
<div style="font-size:11px;font-weight:700;color:var(--text)">${_csbEsc(l.name)}</div>
<div style="font-size:10px;color:var(--muted);margin-top:1px">${_csbEsc(l.author||'')} · v${_csbEsc(l.version||'?')}${l.category?' · '+_csbEsc(l.category):''}</div>
${l.sentence?`<div style="font-size:10px;color:var(--muted);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${_csbEsc(l.sentence)}</div>`:''}
</div>
<div style="display:flex;gap:4px;flex-shrink:0">
${hasEx?`<button onclick="document.getElementById('${sid}').style.display=document.getElementById('${sid}').style.display==='none'?'block':'none'"
style="font-size:10px;padding:2px 7px;background:none;border:1px solid var(--line);color:var(--muted);border-radius:4px;cursor:pointer;font-family:inherit">예제</button>`:''}
<button onclick="csbLibUninstall('${_csbEsc(l.name)}')"
style="font-size:10px;padding:2px 7px;background:none;border:1px solid var(--line);color:#f87171;border-radius:4px;cursor:pointer;font-family:inherit">삭제</button>
</div>
</div>
${hasEx?`<div id="${sid}" style="display:none;margin-top:5px;padding:5px 8px;background:var(--panel-2);border-radius:5px">${exRows}</div>`:''}
</div>`;
}).join('');
}
async function csbLibOpenExample(libName, exName) {
_csbLibStatus(`예제 로딩: ${exName}...`);
try {
const r = await fetch(`/api/arduino/libs/example?lib=${encodeURIComponent(libName)}&example=${encodeURIComponent(exName)}`, { headers: authHeaders() });
const data = await r.json();
if (data.error) { _csbLibStatus('오류: ' + data.error); return; }
_csbLibStatus('');
codeNewFile(data.name, data.code);
setCodeSbTab('files');
} catch(e) { _csbLibStatus('오류: ' + e.message); }
}
async function csbLibSearch() {
const q = (document.getElementById('csb-lib-q')?.value || '').trim();
if (!q) return;
_csbLibListEl().innerHTML = '<div style="padding:16px;color:var(--muted);font-size:11px;text-align:center">검색 중...</div>';
_csbLibStatus('');
try {
const r = await fetch('/api/arduino/libs/search?q=' + encodeURIComponent(q), { headers: authHeaders() });
const data = await r.json();
_csbLibRenderSearch(data.libs || []);
} catch { _csbLibListEl().innerHTML = '<div style="padding:16px;color:var(--muted);font-size:11px;text-align:center">오류</div>'; }
}
function _csbLibRenderSearch(libs) {
const el = _csbLibListEl();
if (!libs.length) { el.innerHTML = '<div style="padding:16px;color:var(--muted);font-size:11px;text-align:center">검색 결과 없음</div>'; return; }
el.innerHTML = libs.map(l => `
<div style="padding:8px 10px;border-bottom:1px solid var(--line);display:flex;align-items:flex-start;gap:6px">
<div style="flex:1;min-width:0">
<div style="display:flex;align-items:center;gap:5px">
<span style="font-size:11px;font-weight:700;color:var(--text)">${_csbEsc(l.name)}</span>
${l.category?`<span style="font-size:9px;padding:1px 5px;border:1px solid var(--line);border-radius:3px;color:var(--muted)">${_csbEsc(l.category)}</span>`:''}
</div>
<div style="font-size:10px;color:var(--muted);margin-top:1px">${_csbEsc(l.author||'')} · v${_csbEsc(l.version||'?')}</div>
${l.sentence?`<div style="font-size:10px;color:var(--muted);margin-top:2px">${_csbEsc(l.sentence)}</div>`:''}
</div>
<div style="flex-shrink:0;padding-top:2px">
${l.installed
? `<span style="font-size:10px;color:var(--brand);border:1px solid;border-radius:4px;padding:2px 6px">✅ 설치됨</span>`
: `<button onclick="csbLibInstall('${_csbEsc(l.name)}',this)"
style="font-size:10px;padding:3px 8px;background:var(--brand);color:#fff;border:none;border-radius:4px;cursor:pointer;font-family:inherit">설치</button>`}
</div>
</div>`).join('');
}
async function csbLibInstall(name, btn) {
_csbLibStatus(`설치 중: ${name}...`);
if (btn) { btn.disabled = true; btn.textContent = '...'; }
try {
const r = await fetch('/api/arduino/libs/install', {
method: 'POST', headers: { ...authHeaders(), 'Content-Type': 'application/json' },
body: JSON.stringify({ name }),
});
const data = await r.json();
if (data.error) { _csbLibStatus('오류: ' + data.error.slice(0,100)); if(btn){btn.disabled=false;btn.textContent='설치';} return; }
_csbLibStatus(`✅ ${name} 설치 완료`);
if (btn) btn.outerHTML = `<span style="font-size:10px;color:var(--brand);border:1px solid;border-radius:4px;padding:2px 6px">✅ 설치됨</span>`;
} catch(e) { _csbLibStatus('오류: ' + e.message); if(btn){btn.disabled=false;btn.textContent='설치';} }
}
async function csbLibUninstall(name) {
if (!confirm(`"${name}" 을 삭제할까요?`)) return;
_csbLibStatus(`삭제 중: ${name}...`);
try {
const r = await fetch('/api/arduino/libs/uninstall', {
method: 'DELETE', headers: { ...authHeaders(), 'Content-Type': 'application/json' },
body: JSON.stringify({ name }),
});
const data = await r.json();
if (data.error) { _csbLibStatus('오류: ' + data.error.slice(0,100)); return; }
_csbLibStatus(`✅ ${name} 삭제 완료`);
csbLibLoad();
} catch(e) { _csbLibStatus('오류: ' + e.message); }
}
// ── 보드 매니저 ───────────────────────────────────────────────────────────────
let _csbBoardCurrentTab = 'installed';
function csbBoardTab(tab) {
_csbBoardCurrentTab = tab;
['installed','search'].forEach(t => {
const btn = document.getElementById('csb-boardtab-' + t);
if (btn) btn.classList.toggle('active', t === tab);
});
const sr = document.getElementById('csb-board-search-row');
if (sr) sr.style.display = tab === 'search' ? 'flex' : 'none';
document.getElementById('csb-board-status').textContent = '';
if (tab === 'installed') csbBoardLoad();
else { document.getElementById('csb-board-list').innerHTML = '<div style="padding:16px;color:var(--muted);font-size:11px;text-align:center">키워드를 입력하세요.</div>'; }
}
function _csbBoardStatus(msg) { const el = document.getElementById('csb-board-status'); if (el) el.textContent = msg; }
async function csbBoardLoad() {
document.getElementById('csb-board-list').innerHTML = '<div style="padding:16px;color:var(--muted);font-size:11px;text-align:center">불러오는 중...</div>';
try {
const r = await fetch('/api/arduino/cores', { headers: authHeaders() });
const data = await r.json();
_csbBoardRenderInstalled(data.cores || []);
} catch { document.getElementById('csb-board-list').innerHTML = '<div style="padding:16px;color:var(--muted);font-size:11px;text-align:center">오류</div>'; }
}
function _csbBoardRenderInstalled(cores) {
const el = document.getElementById('csb-board-list');
if (!cores.length) { el.innerHTML = '<div style="padding:16px;color:var(--muted);font-size:11px;text-align:center">설치된 보드 없음</div>'; return; }
el.innerHTML = cores.map(c => {
const boardList = c.boards.slice(0,6).map(b => `<span style="font-size:10px;color:var(--muted);background:var(--panel-2);border:1px solid var(--line);border-radius:3px;padding:1px 5px">${_csbEsc(b)}</span>`).join(' ');
const more = c.boards.length > 6 ? `<span style="font-size:10px;color:var(--muted)">+${c.boards.length-6}개</span>` : '';
return `<div style="padding:8px 10px;border-bottom:1px solid var(--line)">
<div style="display:flex;align-items:flex-start;gap:6px">
<div style="flex:1;min-width:0">
<div style="font-size:11px;font-weight:700;color:var(--text)">${_csbEsc(c.name)}</div>
<div style="font-size:10px;color:var(--muted);margin-top:1px">${_csbEsc(c.id)} · v${_csbEsc(c.version)} · ${_csbEsc(c.maintainer)}</div>
<div style="display:flex;flex-wrap:wrap;gap:3px;margin-top:5px">${boardList}${more}</div>
</div>
<button onclick="csbBoardUninstall('${_csbEsc(c.id)}')"
style="font-size:10px;padding:2px 7px;background:none;border:1px solid var(--line);color:#f87171;border-radius:4px;cursor:pointer;font-family:inherit;flex-shrink:0">삭제</button>
</div>
</div>`;
}).join('');
}
async function csbBoardSearch() {
const q = (document.getElementById('csb-board-q')?.value || '').trim();
document.getElementById('csb-board-list').innerHTML = '<div style="padding:16px;color:var(--muted);font-size:11px;text-align:center">검색 중...</div>';
_csbBoardStatus('');
try {
const r = await fetch('/api/arduino/cores/search?q=' + encodeURIComponent(q), { headers: authHeaders() });
const data = await r.json();
_csbBoardRenderSearch(data.cores || []);
} catch { document.getElementById('csb-board-list').innerHTML = '<div style="padding:16px;color:var(--muted);font-size:11px;text-align:center">오류</div>'; }
}
function _csbBoardRenderSearch(cores) {
const el = document.getElementById('csb-board-list');
if (!cores.length) { el.innerHTML = '<div style="padding:16px;color:var(--muted);font-size:11px;text-align:center">검색 결과 없음</div>'; return; }
el.innerHTML = cores.map(c => {
const boardList = c.boards.slice(0,4).map(b => `<span style="font-size:9px;color:var(--muted);background:var(--panel-2);border:1px solid var(--line);border-radius:3px;padding:1px 4px">${_csbEsc(b)}</span>`).join(' ');
return `<div style="padding:8px 10px;border-bottom:1px solid var(--line);display:flex;align-items:flex-start;gap:6px">
<div style="flex:1;min-width:0">
<div style="font-size:11px;font-weight:700;color:var(--text)">${_csbEsc(c.name)}</div>
<div style="font-size:10px;color:var(--muted);margin-top:1px">${_csbEsc(c.id)} · v${_csbEsc(c.version)}</div>
<div style="display:flex;flex-wrap:wrap;gap:3px;margin-top:4px">${boardList}${c.boards.length>4?`<span style="font-size:9px;color:var(--muted)">+${c.boards.length-4}개</span>`:''}</div>
</div>
<div style="flex-shrink:0;padding-top:2px">
${c.installed
? `<span style="font-size:10px;color:var(--brand);border:1px solid;border-radius:4px;padding:2px 6px">✅ 설치됨</span>`
: `<button onclick="csbBoardInstall('${_csbEsc(c.id)}',this)"
style="font-size:10px;padding:3px 8px;background:var(--brand);color:#fff;border:none;border-radius:4px;cursor:pointer;font-family:inherit">설치</button>`}
</div>
</div>`;
}).join('');
}
async function csbBoardInstall(id, btn) {
_csbBoardStatus(`설치 중: ${id} (수 분 소요)...`);
if (btn) { btn.disabled = true; btn.textContent = '...'; }
try {
const r = await fetch('/api/arduino/cores/install', {
method: 'POST', headers: { ...authHeaders(), 'Content-Type': 'application/json' },
body: JSON.stringify({ id }),
});
const data = await r.json();
if (data.error) { _csbBoardStatus('오류: ' + data.error.slice(0,150)); if(btn){btn.disabled=false;btn.textContent='설치';} return; }
_csbBoardStatus(`✅ ${id} 설치 완료`);
if (btn) btn.outerHTML = `<span style="font-size:10px;color:var(--brand);border:1px solid;border-radius:4px;padding:2px 6px">✅ 설치됨</span>`;
} catch(e) { _csbBoardStatus('오류: ' + e.message); if(btn){btn.disabled=false;btn.textContent='설치';} }
}
async function csbBoardUninstall(id) {
if (!confirm(`"${id}" 보드를 삭제할까요?`)) return;
_csbBoardStatus(`삭제 중: ${id}...`);
try {
const r = await fetch('/api/arduino/cores/uninstall', {
method: 'DELETE', headers: { ...authHeaders(), 'Content-Type': 'application/json' },
body: JSON.stringify({ id }),
});
const data = await r.json();
if (data.error) { _csbBoardStatus('오류: ' + data.error.slice(0,100)); return; }
_csbBoardStatus(`✅ ${id} 삭제 완료`);
csbBoardLoad();
} catch(e) { _csbBoardStatus('오류: ' + e.message); }
}