Refactor code editor out of main UI + PubMed FTP PDF + login token fix

- Move code editor to standalone page; remove from main index/app mode switcher
- Add diff-active tab highlight and diff bar CSS to styles
- Add markdown table styles for chat messages
- PubMed: try NCBI FTP link before Unpaywall for PDF download
- Login: also persist token to localStorage for cross-tab auth
- Update skills state and config

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
kim
2026-06-06 15:28:03 +09:00
co-authored by Claude Sonnet 4.6
parent 868e63bd70
commit 43942d4b2c
13 changed files with 313 additions and 1343 deletions
+42 -1106
View File
File diff suppressed because it is too large Load Diff
-160
View File
@@ -57,7 +57,6 @@
<button class="mode-btn active" id="btn-chat" onclick="setMode('chat')">채팅</button>
<button class="mode-btn" id="btn-bgtasks" onclick="setMode('bgtasks')">작업</button>
<button class="mode-btn" id="btn-schedule" onclick="setMode('schedule')">일정</button>
<button class="mode-btn" id="btn-code" onclick="setMode('code')">💻 코드</button>
</div>
<div class="status-bar">
@@ -145,97 +144,6 @@
</div>
<!-- CODE SIDEBAR -->
<div id="sidebar-code" style="display:none;flex-direction:column;flex:1;min-height:0;overflow:hidden">
<!-- 내부 탭 -->
<div style="display:flex;border-bottom:1px solid var(--line);flex-shrink:0;background:var(--panel-2)">
<button class="code-sb-tab active" id="csb-tab-files" onclick="setCodeSbTab('files')">파일</button>
<button class="code-sb-tab" id="csb-tab-model" onclick="setCodeSbTab('model')">모델</button>
<button class="code-sb-tab" id="csb-tab-settings"onclick="setCodeSbTab('settings')">설정</button>
</div>
<!-- 파일 탭 -->
<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>
<div style="display:flex;gap:4px">
<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>
</div>
<div id="csb-file-tree" style="flex:1;overflow-y:auto;padding:4px 0">
<div style="padding:12px;color:var(--muted);font-size:11px;text-align:center">로딩 중...</div>
</div>
</div>
<!-- 모델 탭 -->
<div id="csb-model" style="display:none;flex-direction:column;flex:1;min-height:0;overflow-y:auto;padding:14px 12px;gap:12px">
<div>
<div style="font-size:10px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px">Coder AI 모델</div>
<select id="csb-model-select" onchange="csbModelChanged()"
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">
</select>
</div>
<div>
<div style="font-size:10px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px">세션 초기화</div>
<button onclick="codeAiClear()" style="width:100%;padding:7px;border:1px solid var(--line);background:var(--panel-2);color:var(--muted);border-radius:8px;cursor:pointer;font-size:12px">대화 기록 지우기</button>
</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>
<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-codedir" value="code" onchange="csbCodeDirChanged(this.value)"
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="csbCodeDirSave()" title="저장" 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">워크스페이스 하위 폴더 (기본: code)</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:8px">
<input type="range" id="csb-fontsize" min="10" max="22" value="14" oninput="csbFontSize(this.value)"
style="flex:1;accent-color:var(--brand)">
<span id="csb-fontsize-label" style="font-size:11px;color:var(--muted);width:24px;text-align:right">14</span>
</div>
</div>
<div>
<div style="font-size:10px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px">줄 바꿈</div>
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;font-size:12px;color:var(--text)">
<input type="checkbox" id="csb-wordwrap" checked onchange="csbWordWrap(this.checked)"
style="accent-color:var(--brand);width:15px;height:15px">
자동 줄 바꿈
</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>
<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-tabsize" onchange="csbTabSize(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="2" selected>2칸</option>
<option value="4">4칸</option>
<option value="8">8칸</option>
</select>
</div>
<div>
<div style="font-size:10px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px">미니맵</div>
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;font-size:12px;color:var(--text)">
<input type="checkbox" id="csb-minimap" checked onchange="csbMinimap(this.checked)"
style="accent-color:var(--brand);width:15px;height:15px">
미니맵 표시
</label>
</div>
</div>
</div>
<!-- PPTX SIDEBAR -->
<div id="sidebar-pptx" style="display:none;flex-direction:column;flex:1;min-height:0;overflow:hidden">
@@ -437,61 +345,6 @@
</div>
</div>
<!-- ── Code Editor View ─────────────────────────────────────── -->
<div id="code-view">
<!-- File tabs bar -->
<div class="code-tabs-bar">
<div id="code-file-tabs" style="display:flex;align-items:stretch"></div>
<button class="code-new-tab-btn" onclick="codeNewFile()" title="새 파일">+</button>
<div class="code-toolbar">
<button id="code-folder-btn" class="code-tool-btn btn-folder" onclick="toggleCodeFolderBrowser(event)" title="파일 탐색기 열기"
style="gap:4px;display:flex;align-items:center">
📁 <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">
<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>
<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>
<button class="code-tool-btn btn-save" onclick="codeSaveFile()" title="파일 저장 (Ctrl+S)">💾 저장</button>
<button id="code-term-toggle-btn" class="code-tool-btn" onclick="toggleCodeTerminal()"
title="터미널 토글 (Ctrl+`)"
style="background:#111b14;border-color:#2d5a3a;color:#6fcf8a;font-family:'IBM Plex Mono',monospace;font-size:11px">⌨ 터미널</button>
</div>
</div>
<!-- Editor + AI panel -->
<div class="code-body">
<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>
<div id="code-ai-messages">
<div class="code-ai-msg thinking">코드를 선택하고 "AI에게 전송"을 누르거나, 아래에 질문을 입력하세요.</div>
</div>
<div class="code-ai-input-row">
<textarea id="code-ai-input" placeholder="AI에게 질문..." rows="1"
onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();codeAiSend();}"></textarea>
<button class="code-tool-btn primary" onclick="codeAiSend()" style="flex-shrink:0;align-self:flex-end">전송</button>
</div>
</div>
</div>
<!-- Code editor terminal panel -->
<div id="code-term-panel" style="display:none;flex-direction:column;flex-shrink:0;height:260px;min-height:80px;border-top:2px solid #1a4a35;background:#0c0c0c;position:relative">
<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="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>
</div>
</div>
</main>
<!-- Task Modal -->
@@ -2006,19 +1859,6 @@
<script src="app.js"></script>
<!-- Code folder browser dropdown (fixed, rendered outside any overflow:hidden container) -->
<div id="code-folder-dropdown" style="display:none;position:fixed;z-index:9999;
background:var(--panel);border:1px solid rgba(34,197,94,0.4);border-radius:10px;
min-width:260px;max-height:320px;overflow-y:auto;
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 onclick="codeRefreshFolder()" style="cursor:pointer;opacity:0.7;font-size:13px" title="새로고침">↺</span>
</div>
<div id="code-folder-list" style="padding:2px 0">
<div style="padding:8px 12px;color:var(--muted);font-size:11px">로딩 중...</div>
</div>
</div>
<div id="image-zoom-modal" onclick="closeImageZoom()">
<img id="image-zoom-img" src="" alt="Zoomed image" />
+1 -1
View File
@@ -419,7 +419,7 @@
const data = await res.json();
if (res.ok && data.success) {
if (data.token) { try { sessionStorage.setItem('smallclaw_token', data.token); } catch {} }
if (data.token) { try { sessionStorage.setItem('smallclaw_token', data.token); localStorage.setItem('smallclaw_token', data.token); } catch {} }
window.location.href = '/';
} else {
showError(data.error || '인증 실패');
+230 -11
View File
@@ -761,6 +761,14 @@
.msg-role { font-size: 11px; color: #4f617b; font-weight: 700; margin-bottom: 6px; }
.msg-content { line-height: 1.75; font-size: 14px; white-space: pre-wrap; word-break: break-word; color: #17243b; }
.msg.user .msg-content { color: #0f3d8a; }
.md-table-wrap { overflow-x: auto; margin: 10px 0; border-radius: 8px; border: 1px solid var(--line); white-space: normal; }
.md-table { border-collapse: collapse; width: 100%; font-size: 13px; }
.md-table th { background: var(--brand); color: #fff; padding: 8px 14px; text-align: left; font-weight: 600; white-space: nowrap; }
.md-table td { padding: 7px 14px; border-bottom: 1px solid var(--line); vertical-align: top; }
.md-table tr:last-child td { border-bottom: none; }
.md-table tbody tr:nth-child(even) td { background: var(--panel-2); }
.md-table tbody tr:hover td { background: rgba(22,104,227,0.05); }
:root[data-theme="dark"] .md-table tbody tr:hover td { background: rgba(76,141,255,0.08); }
.md-img { max-width: 100%; max-height: 400px; border-radius: 8px; cursor: pointer; margin: 8px 0; display: block; }
.md-img:hover { opacity: 0.9; }
.md-img-err { display: inline-block; color: var(--muted, #888); font-size: 13px; padding: 4px 8px; background: var(--surface2, #f0f0f0); border-radius: 6px; margin: 4px 0; }
@@ -2376,6 +2384,7 @@
}
.code-file-tab.active { color: #c8e6c0; background: #112b20; border-top-color: #22c55e; }
.code-file-tab:hover:not(.active) { color: #a5d6a7; background: #0f2318; }
.code-file-tab.diff-active { border-top-color: #f0ad4e; background: #2a2510; color: #f0d060; }
.code-file-tab-close { font-size: 13px; color: #3d6b4f; margin-left:4px; opacity:0; transition: opacity .15s; }
.code-file-tab:hover .code-file-tab-close, .code-file-tab.active .code-file-tab-close { opacity: 1; }
.code-file-tab-close:hover { color: #f87171; }
@@ -2428,12 +2437,122 @@
flex: 1; overflow-y: auto; padding: 10px 12px;
display: flex; flex-direction: column; gap: 8px;
}
/* ── Code Diff Bar ──────────────────────────────────────────────────── */
#code-diff-bar {
display: flex; align-items: center; gap: 8px;
padding: 6px 12px; background: #1a2e1a; border-bottom: 1px solid #2d5a3a;
font-size: 13px; font-family: 'IBM Plex Mono', monospace; color: #a7d8b8;
flex-shrink: 0;
}
#code-diff-label { margin-right: auto; }
#code-diff-accept {
background: #28a745; color: #fff; border: none; border-radius: 4px;
padding: 4px 12px; font-size: 12px; font-weight: 700; cursor: pointer;
transition: background .15s;
}
#code-diff-accept:hover { background: #218838; }
#code-diff-reject {
background: #dc3545; color: #fff; border: none; border-radius: 4px;
padding: 4px 12px; font-size: 12px; font-weight: 700; cursor: pointer;
transition: background .15s;
}
#code-diff-reject:hover { background: #c82333; }
#code-diff-container { display: none; } /* Legacy - no longer used */
.code-ai-msg { font-size: 12px; line-height: 1.55; }
.code-ai-msg.user { color: var(--brand); font-weight: 600; }
.code-ai-msg.assistant { color: var(--text); white-space: pre-wrap; }
.code-ai-msg.thinking { color: var(--muted); font-style: italic; font-size: 11px; }
.code-ai-msg.user {
color: #fff; font-weight: 600;
background: var(--brand); border-radius: 12px 12px 4px 12px;
padding: 6px 12px; align-self: flex-end; max-width: 85%;
margin-left: auto;
}
.code-ai-msg.assistant {
color: var(--text); white-space: pre-wrap;
background: var(--panel); border-radius: 12px 12px 12px 4px;
padding: 8px 12px; max-width: 90%;
border: 1px solid var(--line);
}
.code-ai-msg.thinking {
color: var(--muted); font-style: italic; font-size: 11px;
background: var(--panel-2); border-radius: 8px;
padding: 6px 12px; max-width: 70%;
}
.code-ai-msg.system {
color: var(--muted); font-size: 11px; text-align: center;
background: transparent; padding: 4px 0;
}
.code-ai-plan {
background: rgba(59, 130, 246, 0.1);
border: 1px solid rgba(59, 130, 246, 0.3);
border-radius: 6px;
padding: 8px 12px;
margin: 4px 0;
font-size: 12px;
line-height: 1.5;
color: #93c5fd;
}
.code-ai-tool-card {
display: flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
margin: 3px 0;
border-left: 3px solid var(--brand, #7c3aed);
border-radius: 4px;
background: var(--panel-2, #1e1e2e);
font-size: 11px;
font-family: 'IBM Plex Mono', monospace;
transition: background 0.15s;
}
.code-ai-tool-icon { font-size: 13px; flex-shrink: 0; }
.code-ai-tool-name { font-weight: 600; color: var(--text, #e2e8f0); }
.code-ai-tool-result {
font-size: 11px;
color: var(--muted, #94a3b8);
margin-left: 4px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 300px;
}
.code-ai-tech-selector {
display: flex;
gap: 3px;
}
.code-ai-tech-btn {
background: none;
border: 1px solid var(--line, #333);
border-radius: 6px;
padding: 2px 8px;
font-size: 11px;
color: var(--muted, #888);
cursor: pointer;
transition: all 0.15s;
white-space: nowrap;
}
.code-ai-tech-btn.active {
border-color: var(--brand, #7c3aed);
color: var(--brand2, #a07840);
background: rgba(124, 92, 254, 0.1);
}
.code-ai-tech-btn:hover {
border-color: var(--brand, #7c3aed);
color: var(--brand2, #a07840);
}
.code-ai-msg.streaming {
position: relative;
}
.code-ai-msg.streaming::after {
content: ''; display: inline-block; width: 6px; height: 14px;
background: var(--brand); border-radius: 2px; margin-left: 2px;
animation: ai-cursor-blink 0.8s steps(2) infinite;
}
@keyframes ai-cursor-blink {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
.code-ai-code-block {
margin: 6px 0; border: 1px solid var(--line); border-radius: 6px; overflow: hidden;
margin: 6px 0; border: 1px solid var(--line); border-radius: 6px; overflow: visible;
}
.code-ai-code-header {
display: flex; align-items: center; justify-content: space-between;
@@ -2441,8 +2560,8 @@
color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em;
}
.code-ai-open-btn {
background: var(--brand); color: #fff; border: none; border-radius: 4px;
padding: 2px 8px; font-size: 10px; font-weight: 700; cursor: pointer; transition: opacity .15s;
display: block; margin: 0 auto 8px; background: var(--brand); color: #fff; border: none; border-radius: 4px;
padding: 4px 12px; font-size: 11px; font-weight: 700; cursor: pointer; transition: opacity .15s;
}
.code-ai-open-btn:hover { opacity: 0.8; }
.code-sb-tab {
@@ -2484,15 +2603,28 @@
font-family: 'IBM Plex Mono', 'Fira Code', monospace; line-height: 1.5;
background: var(--panel); white-space: pre; color: var(--text);
}
.diff-block { margin: 0; padding: 4px 0; overflow-x: auto; font-size: 11px;
font-family: 'IBM Plex Mono', 'Fira Code', monospace; line-height: 1.6;
background: var(--panel); }
.diff-line { display: block; padding: 0 8px; white-space: pre; }
.diff-line-add { background: rgba(34,197,94,0.13); color: #4ade80; }
.diff-line-del { background: rgba(248,113,113,0.13); color: #f87171; }
.diff-line-hunk { color: #60a5fa; background: rgba(96,165,250,0.07); }
.diff-line-file { color: var(--muted); font-style: italic; }
.code-ai-input-row {
display: flex; gap: 6px; padding: 8px; border-top: 1px solid var(--line); flex-shrink: 0;
display: flex; gap: 6px; padding: 8px 10px; border-top: 1px solid var(--line); flex-shrink: 0;
background: var(--panel);
}
#code-ai-input {
flex: 1; resize: none; border: 1px solid var(--line); border-radius: 8px;
padding: 6px 10px; font-size: 12px; font-family: inherit;
background: var(--panel-2); color: var(--text); min-height: 36px; max-height: 100px;
flex: 1; resize: none; border: 1.5px solid var(--line); border-radius: 16px;
padding: 8px 14px; font-size: 12px; font-family: inherit;
background: var(--bg); color: var(--text); min-height: 38px; max-height: 100px;
transition: border-color .2s, box-shadow .2s;
}
#code-ai-input:focus {
outline: none; border-color: var(--brand);
box-shadow: 0 0 0 3px rgba(22,104,227,0.15);
}
#code-ai-input:focus { outline: none; border-color: var(--brand); }
/* ── Chat inline terminal ─────────────────────────────────────── */
#chat-terminal-panel {
@@ -2538,3 +2670,90 @@
.ct-tab-close { font-size: 13px; color: #555; margin-left: 3px; line-height: 1; }
.ct-tab-close:hover { color: #e74856; }
#chat-term-container { position: relative; flex: 1; min-height: 0; overflow: hidden; }
/* ── Inline Diff Decorations (VSCode Copilot style) ──────────────────── */
.inline-diff-added {
background: rgba(40, 167, 69, 0.15) !important;
border-left: 3px solid #28a745 !important;
}
.inline-diff-glyph-added {
background: #28a745 !important;
width: 4px !important;
margin-left: 3px;
}
.inline-diff-deleted-zone {
background: rgba(220, 53, 69, 0.08);
border-left: 3px solid #dc3545;
font-family: 'IBM Plex Mono', 'Fira Code', 'Courier New', monospace;
font-size: 13px;
line-height: 1.6;
overflow: hidden;
}
.inline-diff-deleted-line {
color: #dc3545;
text-decoration: line-through;
opacity: 0.7;
padding: 0 8px;
white-space: pre;
}
.inline-diff-hunk-actions {
position: absolute;
display: flex;
gap: 4px;
z-index: 20;
padding: 3px 6px;
background: var(--panel);
border: 1px solid var(--line);
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0,0,0,0.25);
pointer-events: auto;
}
.inline-diff-accept-btn {
background: #28a745; color: #fff; border: none; border-radius: 4px;
padding: 2px 8px; font-size: 11px; font-weight: 700; cursor: pointer;
transition: background .15s; line-height: 1.4;
}
.inline-diff-accept-btn:hover { background: #218838; }
.inline-diff-reject-btn {
background: #dc3545; color: #fff; border: none; border-radius: 4px;
padding: 2px 8px; font-size: 11px; font-weight: 700; cursor: pointer;
transition: background .15s; line-height: 1.4;
}
.inline-diff-reject-btn:hover { background: #c82333; }
/* ── Streaming Diff (real-time editing indicator) ──────────────────────── */
.inline-diff-streaming-change {
background: rgba(255, 193, 7, 0.12) !important;
border-left: 3px solid #ffc107 !important;
}
.inline-diff-glyph-change {
background: #ffc107 !important;
width: 4px !important;
margin-left: 3px;
}
.inline-diff-streaming-delete {
background: rgba(220, 53, 69, 0.10) !important;
border-left: 3px solid #dc3545 !important;
text-decoration: line-through;
opacity: 0.75;
}
.inline-diff-glyph-delete {
background: #dc3545 !important;
width: 4px !important;
margin-left: 3px;
}
.inline-diff-streaming-add-marker {
border-left: 3px solid #28a745 !important;
}
/* ── Tab Change Indicators ───────────────────────────────────────────── */
.code-tab-dot {
display: inline-flex; align-items: center; justify-content: center;
min-width: 16px; height: 16px; border-radius: 8px;
background: #f0ad4e; color: #000; font-size: 9px; font-weight: 800;
padding: 0 3px; margin-right: 4px; line-height: 1;
}
/* ── AI Mode Selector ────────────────────────────────────────────────── */
@keyframes pulse-red { 0%,100%{opacity:1;transform:scale(1)} 50%{opacity:0.8;transform:scale(1.04)} }