Release 2.9.7: 코드 에디터 로컬 파일시스템 + 프로젝트 생성 개선

- 코드 에디터 저장: 서버 API → File System Access API로 전환
  - codeSaveFileById에 저장 성공/실패/폴더미선택 피드백 메시지 추가
- 프로젝트 생성 형식: project-files JSON → @@FILE/@@ENDFILE 델리미터로 변경
  - JSON 이스케이프 오류(Unterminated string) 근본 해결
- 계획 단계 시스템 프롬프트 강화: JSON 출력 절대 금지 명시
- 프로젝트 생성 세션 ID: 계획 세션 재사용 (서버가 history 파라미터 무시하므로)
- _projFileCreated async화: 파일 쓰기 완료 후 사이드바 리프레시
- 헤더/드롭다운 폴더명 표시 동적 업데이트 (workspace/code/ 하드코딩 제거)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
kim
2026-06-04 18:08:45 +09:00
co-authored by Claude Sonnet 4.6
parent 613690da55
commit 027ba8725e
3 changed files with 1203 additions and 295 deletions
+50 -16
View File
@@ -20,14 +20,26 @@
<!-- 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>
// Apply the saved theme before paint so the page chrome matches the main app.
try {
var _t = localStorage.getItem('cherryclaw_theme');
document.documentElement.setAttribute('data-theme', _t === 'dark' ? 'dark' : 'light');
var _t = localStorage.getItem('code-page-theme') || localStorage.getItem('cherryclaw_theme') || 'dark';
document.documentElement.setAttribute('data-theme', _t);
} catch (e) {}
</script>
<style>
html, body { height: 100%; }
:root[data-theme="warm"] {
--bg:#f5f0e8;--bg-soft:#ede8dc;--panel:#fffdf7;--panel-2:#ede8dc;
--line:#d6ccbb;--text:#2c2318;--muted:#7a6e5e;--brand:#7c5c2e;--brand2:#a07840;
}
.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);}
.code-theme-groups{display:flex;flex-direction:column;gap:5px;margin-top:5px;}
.code-theme-row{display:flex;align-items:center;gap:5px;}
.code-theme-row-lbl{font-size:12px;width:52px;flex-shrink:0;color:var(--text);}
.code-skin-sw{width:20px;height:20px;border-radius:4px;cursor:pointer;border:2px solid var(--line);transition:.15s;box-sizing:border-box;flex-shrink:0;}
.code-skin-sw.active{border-color:var(--brand) !important;box-shadow:0 0 0 2px var(--brand);transform:scale(1.15);}
.code-skin-sw:hover{transform:scale(1.1);}
body { margin: 0; background: var(--bg); color: var(--text); font-family: 'Nanum Gothic', system-ui, sans-serif; display: flex; flex-direction: column; overflow: hidden; }
.code-page-header { display: flex; align-items: center; gap: 10px; padding: 8px 14px; background: var(--panel); border-bottom: 1px solid var(--line); flex-shrink: 0; }
.code-page-header h1 { font-size: 14px; font-weight: 700; margin: 0; }
@@ -44,7 +56,12 @@
<div class="code-page-header">
<span style="font-size:16px">💻</span>
<h1>코드 에디터</h1>
<span class="sub">workspace/code/</span>
<span class="sub" id="code-header-dir">폴더 없음</span>
<div style="display:flex;gap:4px;margin-left:8px">
<button class="code-theme-btn" id="ctbtn-dark" onclick="setCodePageTheme('dark')">🌙 다크</button>
<button class="code-theme-btn" id="ctbtn-light" onclick="setCodePageTheme('light')">☀️ 라이트</button>
<button class="code-theme-btn" id="ctbtn-warm" onclick="setCodePageTheme('warm')">🌿 웜</button>
</div>
</div>
<div class="code-layout">
@@ -62,8 +79,9 @@
<!-- 파일 탭 -->
<div id="csb-files" style="display:flex;flex-direction:column;flex:1;min-height:0">
<div style="display:flex;align-items:center;justify-content:space-between;padding:7px 10px;border-bottom:1px solid var(--line);flex-shrink:0">
<span style="font-size:10px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em">workspace/code/</span>
<span id="csb-folder-label" style="font-size:10px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em">workspace/code/</span>
<div style="display:flex;gap:4px">
<button id="code-local-btn" onclick="openLocalFolder()" title="로컬 폴더 열기" style="background:none;border:none;cursor:pointer;color:var(--muted);font-size:14px;line-height:1;padding:0 3px">🖥️</button>
<button onclick="codeNewFile()" title="새 파일" style="background:none;border:none;cursor:pointer;color:var(--muted);font-size:16px;line-height:1;padding:0 3px">+</button>
<button onclick="codeSbRefresh()" title="새로고침" style="background:none;border:none;cursor:pointer;color:var(--muted);font-size:13px;line-height:1;padding:0 3px">↺</button>
</div>
@@ -89,6 +107,15 @@
<!-- 설정 탭 -->
<div id="csb-settings" style="display:none;flex-direction:column;flex:1;min-height:0;overflow-y:auto;padding:14px 12px;gap:14px">
<div>
<div style="font-size:10px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px">터미널 시작 경로</div>
<div style="display:flex;align-items:center;gap:6px">
<input type="text" id="csb-termpath" placeholder="/home/kim/projects/mygame"
style="flex:1;font-size:12px;padding:6px 8px;background:var(--panel-2);color:var(--text);border:1px solid var(--line);border-radius:8px;font-family:'IBM Plex Mono',monospace">
<button onclick="csbTermPathSave()" style="background:var(--brand);color:#fff;border:none;border-radius:8px;padding:6px 10px;font-size:11px;font-weight:700;cursor:pointer;white-space:nowrap">저장</button>
</div>
<div style="font-size:10px;color:var(--muted);margin-top:4px">새 터미널 탭 열면 이 경로로 자동 이동</div>
</div>
<div>
<div style="font-size:10px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px">코드 저장 디렉토리</div>
<div style="display:flex;align-items:center;gap:6px">
@@ -115,13 +142,8 @@
</label>
</div>
<div>
<div style="font-size:10px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px">에디터 테마</div>
<select id="csb-theme" onchange="csbTheme(this.value)"
style="width:100%;font-size:12px;padding:6px 8px;background:var(--panel-2);color:var(--text);border:1px solid var(--line);border-radius:8px;cursor:pointer">
<option value="vs-dark">Dark (기본)</option>
<option value="vs">Light</option>
<option value="hc-black">High Contrast</option>
</select>
<div style="font-size:10px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:2px">에디터 테마 <span id="csb-theme-lbl" style="font-weight:400;text-transform:none;letter-spacing:0;color:var(--text)"></span></div>
<div class="code-skin-grid" id="csb-theme-grid"></div>
</div>
<div>
<div style="font-size:10px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px">탭 크기</div>
@@ -156,11 +178,12 @@
📁 <span>workspace/code/</span> ▾
</button>
<button class="code-tool-btn btn-ai" onclick="codeSendSelectionToAI()" title="선택 영역을 AI에게 전송">💬 AI에게 전송</button>
<div id="code-run-dropdown" style="position:relative;display:inline-flex">
<div id="code-run-dropdown" style="position:relative;display:inline-flex;align-items:center">
<button class="code-tool-btn btn-run" onclick="codeRunDefault()" title="실행">
<span id="code-run-icon">▶</span> <span id="code-run-label">실행</span>
</button>
<button class="code-tool-btn btn-run" onclick="codeToggleRunMenu()" title="실행 방법 선택" style="padding:6px 4px;margin-left:-1px;border-left:1px solid rgba(34,197,94,.3)">▾</button>
<span id="companion-dot" onclick="companionConnect()" style="width:8px;height:8px;border-radius:50%;background:#6b7280;display:inline-block;cursor:pointer;margin-left:6px;flex-shrink:0" title="Python companion 미연결 — 클릭해서 재연결"></span>
<div id="code-run-menu" style="display:none;position:absolute;top:100%;right:0;z-index:99;min-width:180px;background:#1a2e1a;border:1px solid #2d5a3a;border-radius:6px;box-shadow:0 4px 16px rgba(0,0,0,.5);margin-top:2px;overflow:hidden">
</div>
</div>
@@ -172,11 +195,21 @@
</div>
<!-- Editor + AI panel -->
<div class="code-body">
<div id="code-diff-bar" style="display:none">
<span id="code-diff-label">🔀 AI 제안 변경 사항</span>
<span id="code-diff-hunk-info" style="font-size:11px;color:var(--muted)"></span>
<button id="code-diff-accept" onclick="acceptAllHunks()">✅ 모두 적용</button>
<button id="code-diff-reject" onclick="rejectAllHunks()">❌ 모두 취소</button>
</div>
<div id="code-editor-container"></div>
<div class="code-vresize" id="code-vresize"></div>
<div id="code-ai-panel">
<div class="code-ai-header">
<span id="code-ai-title">💻 Coder AI</span>
<div class="code-ai-mode-selector">
<button class="code-ai-mode-btn active" id="code-ai-mode-code" onclick="setCodeAiMode('code')">💻 Code</button>
<button class="code-ai-mode-btn" id="code-ai-mode-ask" onclick="setCodeAiMode('ask')">💬 Ask</button>
</div>
</div>
<div id="code-ai-messages">
<div class="code-ai-msg thinking">코드를 선택하고 "AI에게 전송"을 누르거나, 아래에 질문을 입력하세요.</div>
@@ -193,7 +226,8 @@
<div id="code-term-drag" style="position:absolute;top:0;left:0;right:0;height:5px;cursor:ns-resize;z-index:2;transition:background .15s"></div>
<div style="display:flex;align-items:center;height:28px;background:#0d1f1a;border-bottom:1px solid #1a4a35;flex-shrink:0;overflow-x:auto;padding-left:2px">
<div id="code-term-tabs" style="display:flex;align-items:stretch;height:100%;flex:1"></div>
<button onclick="addCodeTermShell()" title="새 쉘" style="background:none;border:none;color:#3d6b4f;cursor:pointer;font-size:16px;padding:0 8px;line-height:1;flex-shrink:0">+</button>
<button onclick="addLocalShell()" id="local-shell-btn" title="로컬 터미널 (companion.py 필요)" style="background:none;border:none;color:#6b7280;cursor:pointer;font-size:12px;padding:0 6px;line-height:1;flex-shrink:0">🖥️</button>
<button onclick="addCodeTermShell()" title="새 터미널 (+)" style="background:none;border:none;color:#3d6b4f;cursor:pointer;font-size:16px;padding:0 8px;line-height:1;flex-shrink:0">+</button>
<button onclick="toggleCodeTerminal()" title="닫기" style="background:none;border:none;color:#3d6b4f;cursor:pointer;font-size:14px;padding:0 8px;line-height:1;flex-shrink:0">&times;</button>
</div>
<div id="code-term-container" style="flex:1;min-height:0;position:relative;overflow:hidden"></div>
@@ -209,7 +243,7 @@
box-shadow:0 8px 32px rgba(0,0,0,0.35);padding:4px 0">
<div style="padding:6px 12px;font-size:10px;font-weight:700;color:rgba(34,197,94,0.9);
border-bottom:1px solid rgba(34,197,94,0.2);display:flex;justify-content:space-between;align-items:center">
<span>📁 workspace/code/</span>
<span id="code-dropdown-dir">📁 폴더 없음</span>
<span onclick="codeRefreshFolder()" style="cursor:pointer;opacity:0.7;font-size:13px" title="새로고침">↺</span>
</div>
<div id="code-folder-list" style="padding:2px 0">
@@ -217,6 +251,6 @@
</div>
</div>
<script src="code.js"></script>
<script src="code.js?v=20260604s"></script>
</body>
</html>