vg-coder-cli 2.0.30 → 2.0.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/ARCHITECTURE.md +255 -0
  2. package/README.md +0 -11
  3. package/change.sh +0 -0
  4. package/dist/vg-coder-bundle.js +42 -0
  5. package/gulpfile.js +111 -0
  6. package/package.json +19 -11
  7. package/scripts/postinstall.js +13 -3
  8. package/src/index.js +28 -220
  9. package/src/server/api-server.js +120 -428
  10. package/src/server/views/css/bubble.css +81 -0
  11. package/src/server/views/css/code-viewer.css +58 -0
  12. package/src/server/views/css/terminal.css +59 -155
  13. package/src/server/views/dashboard.css +78 -678
  14. package/src/server/views/dashboard.html +39 -278
  15. package/src/server/views/js/api.js +2 -22
  16. package/src/server/views/js/config.js +27 -15
  17. package/src/server/views/js/event-protocol.js +263 -0
  18. package/src/server/views/js/features/bubble-features/index.js +125 -0
  19. package/src/server/views/js/features/bubble-features/paste-run-feature.js +16 -0
  20. package/src/server/views/js/features/bubble-features/terminal-feature.js +16 -0
  21. package/src/server/views/js/features/bubble.js +175 -0
  22. package/src/server/views/js/features/code-viewer.js +90 -0
  23. package/src/server/views/js/features/commands.js +34 -81
  24. package/src/server/views/js/features/editor-tabs.js +19 -46
  25. package/src/server/views/js/features/git-view.js +63 -81
  26. package/src/server/views/js/features/iframe-manager.js +3 -97
  27. package/src/server/views/js/features/monaco-manager.js +19 -39
  28. package/src/server/views/js/features/project-switcher.js +7 -63
  29. package/src/server/views/js/features/resize.js +5 -16
  30. package/src/server/views/js/features/structure.js +38 -106
  31. package/src/server/views/js/features/terminal.js +102 -418
  32. package/src/server/views/js/handlers.js +60 -43
  33. package/src/server/views/js/main.js +75 -179
  34. package/src/server/views/js/shadow-entry.js +21 -0
  35. package/src/server/views/js/utils.js +48 -28
  36. package/src/server/views/vg-coder/_metadata/generated_indexed_rulesets/_ruleset1 +0 -0
  37. package/src/server/views/vg-coder/controller.js +33 -258
  38. package/vetgo-auto/chrome/src/utils/injector-script.ts +33 -258
  39. package/vetgo-auto/vg-coder.zip +0 -0
  40. package/src/server/views/dashboard.js +0 -457
@@ -1,43 +1,32 @@
1
- /**
2
- * Saved Commands Feature
3
- * Manages saved terminal commands that can be quickly executed
4
- */
1
+ import { API_BASE } from '../config.js';
2
+ import { getById, showToast } from '../utils.js';
5
3
 
6
4
  let savedCommands = [];
7
5
  let editingCommandId = null;
8
6
 
9
- /**
10
- * Initialize saved commands on page load
11
- */
12
7
  export async function initSavedCommands() {
13
8
  await loadSavedCommands();
14
9
  renderCommands();
15
10
  }
16
11
 
17
- /**
18
- * Load saved commands from backend
19
- */
20
12
  async function loadSavedCommands() {
21
13
  try {
22
- const response = await fetch('/api/commands/load');
14
+ const response = await fetch(`${API_BASE}/api/commands/load`);
23
15
  const data = await response.json();
24
16
  savedCommands = data.commands || [];
25
- renderCommands(); // Re-render after loading
17
+ renderCommands();
26
18
  } catch (error) {
27
19
  console.error('Failed to load commands:', error);
28
20
  savedCommands = [];
29
21
  }
30
22
  }
31
23
 
32
- /**
33
- * Save commands to backend (debounced)
34
- */
35
24
  let saveTimeout = null;
36
25
  async function saveCommands() {
37
26
  clearTimeout(saveTimeout);
38
27
  saveTimeout = setTimeout(async () => {
39
28
  try {
40
- const response = await fetch('/api/commands/save', {
29
+ const response = await fetch(`${API_BASE}/api/commands/save`, {
41
30
  method: 'POST',
42
31
  headers: { 'Content-Type': 'application/json' },
43
32
  body: JSON.stringify({ commands: savedCommands })
@@ -52,12 +41,9 @@ async function saveCommands() {
52
41
  }, 500);
53
42
  }
54
43
 
55
- /**
56
- * Render commands in UI
57
- */
58
44
  function renderCommands() {
59
- const container = document.getElementById('commands-list');
60
- const emptyState = document.getElementById('commands-empty-state');
45
+ const container = getById('commands-list');
46
+ const emptyState = getById('commands-empty-state');
61
47
 
62
48
  if (!container) return;
63
49
 
@@ -90,63 +76,50 @@ function renderCommands() {
90
76
  `).join('');
91
77
  }
92
78
 
93
- /**
94
- * Open add command modal
95
- */
96
79
  function openAddCommandModal() {
97
80
  editingCommandId = null;
98
- document.getElementById('modal-title').textContent = 'Add Command';
99
- document.getElementById('command-icon').value = '🚀';
100
- document.getElementById('command-name').value = '';
101
- document.getElementById('command-text').value = '';
102
- document.getElementById('command-modal').style.display = 'flex';
103
- document.getElementById('command-name').focus();
81
+ getById('modal-title').textContent = 'Add Command';
82
+ getById('command-icon').value = '🚀';
83
+ getById('command-name').value = '';
84
+ getById('command-text').value = '';
85
+ getById('command-modal').style.display = 'flex';
86
+ getById('command-name').focus();
104
87
  }
105
88
 
106
- /**
107
- * Open edit command modal
108
- */
109
89
  function editCommand(id) {
110
90
  const command = savedCommands.find(c => c.id === id);
111
91
  if (!command) return;
112
92
 
113
93
  editingCommandId = id;
114
- document.getElementById('modal-title').textContent = 'Edit Command';
115
- document.getElementById('command-icon').value = command.icon;
116
- document.getElementById('command-name').value = command.name;
117
- document.getElementById('command-text').value = command.command;
118
- document.getElementById('command-modal').style.display = 'flex';
119
- document.getElementById('command-name').focus();
94
+ getById('modal-title').textContent = 'Edit Command';
95
+ getById('command-icon').value = command.icon;
96
+ getById('command-name').value = command.name;
97
+ getById('command-text').value = command.command;
98
+ getById('command-modal').style.display = 'flex';
99
+ getById('command-name').focus();
120
100
  }
121
101
 
122
- /**
123
- * Close command modal
124
- */
125
102
  function closeCommandModal() {
126
- document.getElementById('command-modal').style.display = 'none';
103
+ const modal = getById('command-modal');
104
+ if (modal) modal.style.display = 'none';
127
105
  editingCommandId = null;
128
106
  }
129
107
 
130
- /**
131
- * Handle command form submit
132
- */
133
108
  function handleCommandFormSubmit(event) {
134
109
  event.preventDefault();
135
110
 
136
- const icon = document.getElementById('command-icon').value.trim();
137
- const name = document.getElementById('command-name').value.trim();
138
- const command = document.getElementById('command-text').value.trim();
111
+ const icon = getById('command-icon').value.trim();
112
+ const name = getById('command-name').value.trim();
113
+ const command = getById('command-text').value.trim();
139
114
 
140
115
  if (!icon || !name || !command) return;
141
116
 
142
117
  if (editingCommandId) {
143
- // Edit existing
144
118
  const index = savedCommands.findIndex(c => c.id === editingCommandId);
145
119
  if (index !== -1) {
146
120
  savedCommands[index] = { ...savedCommands[index], icon, name, command };
147
121
  }
148
122
  } else {
149
- // Add new
150
123
  savedCommands.push({
151
124
  id: 'cmd_' + Date.now(),
152
125
  icon,
@@ -160,9 +133,6 @@ function handleCommandFormSubmit(event) {
160
133
  closeCommandModal();
161
134
  }
162
135
 
163
- /**
164
- * Delete command with confirmation
165
- */
166
136
  function deleteCommand(id) {
167
137
  const command = savedCommands.find(c => c.id === id);
168
138
  if (!command) return;
@@ -174,57 +144,40 @@ function deleteCommand(id) {
174
144
  }
175
145
  }
176
146
 
177
- /**
178
- * Run saved command - open terminal and copy command to clipboard
179
- */
180
147
  function runSavedCommand(id) {
181
148
  const command = savedCommands.find(c => c.id === id);
182
149
  if (!command) return;
183
150
 
184
- // Create new terminal
185
151
  if (typeof window.createNewTerminal === 'function') {
186
152
  window.createNewTerminal();
187
153
  }
188
154
 
189
- // Copy command to clipboard
190
155
  navigator.clipboard.writeText(command.command).then(() => {
191
- // Show toast notification
192
- if (typeof window.showToast === 'function') {
193
- window.showToast(`📋 Copied: ${command.command}`, 'success');
194
- }
156
+ showToast(`📋 Copied: ${command.command}`, 'success');
195
157
  }).catch(err => {
196
158
  console.error('Failed to copy command:', err);
197
- if (typeof window.showToast === 'function') {
198
- window.showToast('Failed to copy command', 'error');
199
- }
159
+ showToast('Failed to copy command', 'error');
200
160
  });
201
161
  }
202
162
 
203
- /**
204
- * Escape HTML to prevent XSS
205
- */
206
163
  function escapeHtml(text) {
207
164
  const div = document.createElement('div');
208
165
  div.textContent = text;
209
166
  return div.innerHTML;
210
167
  }
211
168
 
212
- // Global exports for HTML onclick
213
169
  window.openAddCommandModal = openAddCommandModal;
214
170
  window.editCommand = editCommand;
215
171
  window.closeCommandModal = closeCommandModal;
216
172
  window.deleteCommand = deleteCommand;
217
173
  window.runSavedCommand = runSavedCommand;
218
- window.loadSavedCommands = loadSavedCommands; // Export for project switching
219
-
220
- // Setup form submit handler
221
- if (typeof document !== 'undefined') {
222
- document.addEventListener('DOMContentLoaded', () => {
223
- const form = document.getElementById('command-form');
224
- if (form) {
225
- form.addEventListener('submit', handleCommandFormSubmit);
226
- }
227
- });
228
- }
174
+ window.loadSavedCommands = loadSavedCommands;
175
+
176
+ setTimeout(() => {
177
+ const form = getById('command-form');
178
+ if (form) {
179
+ form.addEventListener('submit', handleCommandFormSubmit);
180
+ }
181
+ }, 500);
229
182
 
230
183
  export { openAddCommandModal, editCommand, deleteCommand, runSavedCommand, loadSavedCommands };
@@ -1,50 +1,34 @@
1
- import { openFileInMonaco, saveViewState, disposeModel } from './monaco-manager.js';
1
+ import { openFileInViewer } from './code-viewer.js';
2
+ import { getById, qsa, qs } from '../utils.js';
2
3
 
3
- let activeTabs = []; // Array of { path, name, icon }
4
- let currentPath = 'ai-assistant'; // Default
4
+ let activeTabs = [];
5
+ let currentPath = null;
5
6
 
6
7
  export function initEditorTabs() {
7
8
  renderTabs();
8
9
  }
9
10
 
10
11
  export async function openFileTab(path, name, icon = '📄') {
11
- // 1. Ẩn AI, hiện Monaco
12
+ // Switch to code mode
12
13
  toggleViewMode('code');
13
14
 
14
- // 2. Lưu state tab cũ
15
- if (currentPath && currentPath !== 'ai-assistant' && currentPath !== path) {
16
- saveViewState(currentPath);
17
- }
18
-
19
- // 3. Logic Tabs Array
20
15
  const existingTab = activeTabs.find(t => t.path === path);
21
16
  if (!existingTab) {
22
17
  activeTabs.push({ path, name, icon });
23
18
  renderTabs();
24
19
  }
25
20
 
26
- // 4. Update UI
27
21
  currentPath = path;
28
22
  updateTabUI();
29
23
 
30
- // 5. Open in Monaco
31
- await openFileInMonaco(path);
24
+ await openFileInViewer(path);
32
25
  }
33
26
 
34
27
  export function switchTab(path) {
35
- if (currentPath && currentPath !== 'ai-assistant') {
36
- saveViewState(currentPath);
37
- }
38
-
39
28
  currentPath = path;
40
29
  updateTabUI();
41
-
42
- if (path === 'ai-assistant') {
43
- toggleViewMode('ai');
44
- } else {
45
- toggleViewMode('code');
46
- openFileInMonaco(path);
47
- }
30
+ toggleViewMode('code');
31
+ openFileInViewer(path);
48
32
  }
49
33
 
50
34
  export function closeTab(event, path) {
@@ -54,22 +38,23 @@ export function closeTab(event, path) {
54
38
  if (index === -1) return;
55
39
 
56
40
  activeTabs.splice(index, 1);
57
- disposeModel(path);
58
41
 
59
42
  if (currentPath === path) {
60
43
  if (activeTabs.length > 0) {
61
44
  const nextTab = activeTabs[activeTabs.length - 1];
62
45
  switchTab(nextTab.path);
63
46
  } else {
64
- switchTab('ai-assistant');
47
+ currentPath = null;
48
+ updateTabUI();
49
+ const container = getById('code-viewer-container');
50
+ if (container) container.innerHTML = '<div class="cv-empty">No file open</div>';
65
51
  }
66
52
  }
67
53
  renderTabs();
68
54
  }
69
55
 
70
56
  function renderTabs() {
71
- // Chỉ render các file tabs vào container con
72
- const container = document.getElementById('file-tabs-container');
57
+ const container = getById('file-tabs-container');
73
58
  if (!container) return;
74
59
 
75
60
  let html = '';
@@ -91,13 +76,7 @@ function renderTabs() {
91
76
  }
92
77
 
93
78
  function updateTabUI() {
94
- // 1. Update Static AI Tab
95
- const aiTab = document.getElementById('ai-tab');
96
- if (currentPath === 'ai-assistant') aiTab.classList.add('active');
97
- else aiTab.classList.remove('active');
98
-
99
- // 2. Update Dynamic Tabs
100
- const fileTabs = document.querySelectorAll('#file-tabs-container .tab-item');
79
+ const fileTabs = qsa('#file-tabs-container .tab-item');
101
80
  fileTabs.forEach(el => {
102
81
  if (el.dataset.path === currentPath) el.classList.add('active');
103
82
  else el.classList.remove('active');
@@ -105,19 +84,13 @@ function updateTabUI() {
105
84
  }
106
85
 
107
86
  function toggleViewMode(mode) {
108
- const aiContainer = document.querySelector('.ai-iframe-container');
109
- const monacoContainer = document.getElementById('monaco-container');
110
-
111
- if (mode === 'code') {
112
- aiContainer.classList.add('view-mode-hidden');
113
- monacoContainer.classList.remove('view-mode-hidden');
114
- } else {
115
- aiContainer.classList.remove('view-mode-hidden');
116
- monacoContainer.classList.add('view-mode-hidden');
117
- }
87
+ // Placeholder for view switching logic if needed in future
88
+ // For now, it just ensures the container is visible if we were to hide it
89
+ const container = getById('code-viewer-container');
90
+ if (container) container.style.display = 'block';
118
91
  }
119
92
 
120
- // Export global helpers
93
+ // Expose to window for onclick handlers
121
94
  window.switchTab = switchTab;
122
95
  window.closeTab = closeTab;
123
96
  window.openFileTab = openFileTab;
@@ -1,23 +1,25 @@
1
1
  import { getGitStatus, getGitDiff, stageFile, unstageFile, commitChanges, discardChange } from '../api.js';
2
- import { showToast } from '../utils.js';
2
+ import { showToast, getById, qsa } from '../utils.js';
3
+ // FIX: Import Diff2HtmlUI from npm package
4
+ import { Diff2HtmlUI } from 'diff2html/lib/ui/js/diff2html-ui';
5
+
6
+ let isGitMode = false;
7
+ let currentStaged = [];
8
+ let currentChanges = [];
3
9
 
4
10
  export function initGitView() {
5
- const toggleBtn = document.getElementById('git-view-toggle');
6
- const refreshBtn = document.getElementById('git-refresh-btn');
11
+ const toggleBtn = getById('git-view-toggle');
12
+ const refreshBtn = getById('git-refresh-btn');
7
13
 
8
14
  if (toggleBtn) toggleBtn.addEventListener('click', toggleGitMode);
9
15
  if (refreshBtn) refreshBtn.addEventListener('click', loadGitData);
10
16
  }
11
17
 
12
- let isGitMode = false;
13
- let currentStaged = [];
14
- let currentChanges = [];
15
-
16
18
  async function toggleGitMode() {
17
- const gitContainer = document.getElementById('git-view-container');
18
- const toggleBtn = document.getElementById('git-view-toggle');
19
- const refreshBtn = document.getElementById('git-refresh-btn');
20
- const toggleText = document.getElementById('git-toggle-text');
19
+ const gitContainer = getById('git-view-container');
20
+ const toggleBtn = getById('git-view-toggle');
21
+ const refreshBtn = getById('git-refresh-btn');
22
+ const toggleText = getById('git-toggle-text');
21
23
 
22
24
  isGitMode = !isGitMode;
23
25
 
@@ -36,9 +38,8 @@ async function toggleGitMode() {
36
38
  }
37
39
 
38
40
  async function loadGitData() {
39
- const container = document.getElementById('git-view-container');
41
+ const container = getById('git-view-container');
40
42
 
41
- // Initial Structure if empty
42
43
  if (!container.querySelector('.git-sidebar')) {
43
44
  container.innerHTML = `
44
45
  <div class="git-sidebar">
@@ -77,34 +78,28 @@ async function loadGitData() {
77
78
  </div>
78
79
  `;
79
80
 
80
- // Bind Actions
81
- document.getElementById('stage-all').addEventListener('click', async (e) => {
82
- e.stopPropagation();
83
- await handleStage('*');
84
- });
85
- document.getElementById('unstage-all').addEventListener('click', async (e) => {
86
- e.stopPropagation();
87
- await handleUnstage('*');
88
- });
89
- document.getElementById('discard-all').addEventListener('click', async (e) => {
90
- e.stopPropagation();
91
- await handleDiscard('*');
92
- });
93
-
94
- // Commit Actions
95
- const commitBtn = document.getElementById('git-commit-btn');
96
- const commitInput = document.getElementById('git-commit-message');
97
-
98
- commitBtn.addEventListener('click', handleCommit);
99
- commitInput.addEventListener('keydown', (e) => {
100
- if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
101
- e.preventDefault();
102
- handleCommit();
103
- }
104
- });
81
+ setTimeout(() => {
82
+ const stageAll = getById('stage-all');
83
+ const unstageAll = getById('unstage-all');
84
+ const discardAll = getById('discard-all');
85
+ const commitBtn = getById('git-commit-btn');
86
+ const commitInput = getById('git-commit-message');
87
+
88
+ if(stageAll) stageAll.addEventListener('click', async (e) => { e.stopPropagation(); await handleStage('*'); });
89
+ if(unstageAll) unstageAll.addEventListener('click', async (e) => { e.stopPropagation(); await handleUnstage('*'); });
90
+ if(discardAll) discardAll.addEventListener('click', async (e) => { e.stopPropagation(); await handleDiscard('*'); });
91
+
92
+ if(commitBtn) commitBtn.addEventListener('click', handleCommit);
93
+ if(commitInput) commitInput.addEventListener('keydown', (e) => {
94
+ if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
95
+ e.preventDefault();
96
+ handleCommit();
97
+ }
98
+ });
99
+ }, 0);
105
100
  }
106
101
 
107
- const refreshBtn = document.getElementById('git-refresh-btn');
102
+ const refreshBtn = getById('git-refresh-btn');
108
103
  if(refreshBtn) refreshBtn.disabled = true;
109
104
 
110
105
  try {
@@ -120,9 +115,9 @@ async function loadGitData() {
120
115
  }
121
116
 
122
117
  async function handleCommit() {
123
- const input = document.getElementById('git-commit-message');
118
+ const input = getById('git-commit-message');
124
119
  const message = input.value.trim();
125
- const btn = document.getElementById('git-commit-btn');
120
+ const btn = getById('git-commit-btn');
126
121
 
127
122
  if (!message) {
128
123
  showToast('Please enter a commit message', 'error');
@@ -152,31 +147,35 @@ async function handleCommit() {
152
147
  }
153
148
 
154
149
  function renderTrees() {
155
- // 1. Render Staged
156
- const treeStaged = document.getElementById('tree-staged');
157
- document.getElementById('badge-staged').textContent = currentStaged.length;
158
- document.getElementById('unstage-all').style.display = currentStaged.length > 0 ? 'block' : 'none';
159
- treeStaged.innerHTML = '';
150
+ const treeStaged = getById('tree-staged');
151
+ const badgeStaged = getById('badge-staged');
152
+ const unstageAll = getById('unstage-all');
160
153
 
161
- if (currentStaged.length > 0) {
162
- const rootStaged = buildFileTree(currentStaged);
163
- renderTreeNodes(rootStaged, treeStaged, 'staged', 0);
154
+ if (badgeStaged) badgeStaged.textContent = currentStaged.length;
155
+ if (unstageAll) unstageAll.style.display = currentStaged.length > 0 ? 'block' : 'none';
156
+ if (treeStaged) {
157
+ treeStaged.innerHTML = '';
158
+ if (currentStaged.length > 0) {
159
+ const rootStaged = buildFileTree(currentStaged);
160
+ renderTreeNodes(rootStaged, treeStaged, 'staged', 0);
161
+ }
164
162
  }
165
163
 
166
- // 2. Render Changes
167
- const treeChanges = document.getElementById('tree-changes');
168
- document.getElementById('badge-changes').textContent = currentChanges.length;
169
- document.getElementById('discard-all').style.display = currentChanges.length > 0 ? 'block' : 'none';
170
- treeChanges.innerHTML = '';
171
-
172
- if (currentChanges.length > 0) {
173
- const rootChanges = buildFileTree(currentChanges);
174
- renderTreeNodes(rootChanges, treeChanges, 'changes', 0);
164
+ const treeChanges = getById('tree-changes');
165
+ const badgeChanges = getById('badge-changes');
166
+ const discardAll = getById('discard-all');
167
+
168
+ if (badgeChanges) badgeChanges.textContent = currentChanges.length;
169
+ if (discardAll) discardAll.style.display = currentChanges.length > 0 ? 'block' : 'none';
170
+ if (treeChanges) {
171
+ treeChanges.innerHTML = '';
172
+ if (currentChanges.length > 0) {
173
+ const rootChanges = buildFileTree(currentChanges);
174
+ renderTreeNodes(rootChanges, treeChanges, 'changes', 0);
175
+ }
175
176
  }
176
177
  }
177
178
 
178
- // --- TREE LOGIC ---
179
-
180
179
  function buildFileTree(files) {
181
180
  const root = {};
182
181
  files.forEach(file => {
@@ -210,18 +209,15 @@ function renderTreeNodes(nodes, container, type, depth) {
210
209
  const li = document.createElement('li');
211
210
  li.className = 'git-tree-node';
212
211
 
213
- // --- CONTENT ROW ---
214
212
  const content = document.createElement('div');
215
213
  content.className = 'git-tree-content';
216
214
  content.style.paddingLeft = (depth * 16 + 8) + 'px';
217
215
 
218
- // Arrow
219
216
  const arrow = document.createElement('span');
220
217
  arrow.className = 'git-arrow';
221
218
  arrow.textContent = isFolder ? '▼' : '';
222
219
  content.appendChild(arrow);
223
220
 
224
- // Icon
225
221
  const iconSpan = document.createElement('span');
226
222
  iconSpan.className = 'git-icon';
227
223
  if (isFolder) {
@@ -233,18 +229,15 @@ function renderTreeNodes(nodes, container, type, depth) {
233
229
  }
234
230
  content.appendChild(iconSpan);
235
231
 
236
- // Label
237
232
  const label = document.createElement('span');
238
233
  label.className = isFolder ? 'git-label git-dir-label' : 'git-label git-file-label';
239
234
  label.textContent = node.name;
240
235
  content.appendChild(label);
241
236
 
242
- // Actions
243
237
  if (!isFolder && node.fileData) {
244
238
  const actions = document.createElement('div');
245
239
  actions.className = 'git-actions';
246
240
 
247
- // Discard Button (Only for Changes)
248
241
  if (type === 'changes') {
249
242
  const discardBtn = document.createElement('button');
250
243
  discardBtn.className = 'git-btn-action destructive';
@@ -254,7 +247,6 @@ function renderTreeNodes(nodes, container, type, depth) {
254
247
  actions.appendChild(discardBtn);
255
248
  }
256
249
 
257
- // Stage/Unstage Button
258
250
  const btn = document.createElement('button');
259
251
  btn.className = 'git-btn-action';
260
252
  if (type === 'staged') {
@@ -270,13 +262,12 @@ function renderTreeNodes(nodes, container, type, depth) {
270
262
  content.appendChild(actions);
271
263
  }
272
264
 
273
- // Click Handler
274
265
  content.addEventListener('click', (e) => {
275
266
  e.stopPropagation();
276
267
  if (isFolder) {
277
268
  li.classList.toggle('collapsed');
278
269
  } else {
279
- document.querySelectorAll('.git-tree-content').forEach(el => el.classList.remove('selected'));
270
+ qsa('.git-tree-content').forEach(el => el.classList.remove('selected'));
280
271
  content.classList.add('selected');
281
272
  loadDiffView(node.fileData.path, type);
282
273
  }
@@ -284,7 +275,6 @@ function renderTreeNodes(nodes, container, type, depth) {
284
275
 
285
276
  li.appendChild(content);
286
277
 
287
- // --- CHILDREN ---
288
278
  if (isFolder) {
289
279
  const ul = document.createElement('ul');
290
280
  renderTreeNodes(node.children, ul, type, depth + 1);
@@ -303,8 +293,6 @@ function getStatusIcon(status) {
303
293
  return '?';
304
294
  }
305
295
 
306
- // --- ACTIONS ---
307
-
308
296
  async function handleStage(path) {
309
297
  try {
310
298
  await stageFile(path);
@@ -341,18 +329,12 @@ async function handleDiscard(path) {
341
329
  }
342
330
 
343
331
  async function loadDiffView(filePath, type) {
344
- const viewer = document.getElementById('git-diff-viewer');
332
+ const viewer = getById('git-diff-viewer');
345
333
  viewer.innerHTML = '<div class="git-empty-state">Loading diff...</div>';
346
334
 
347
- // SAFE CHECK FOR UI LIBRARY
348
- // We check window.Diff2HtmlUI first (standard for bundles)
349
- const UIConstructor = window.Diff2HtmlUI;
335
+ // FIX: Using imported class
336
+ const UIConstructor = Diff2HtmlUI;
350
337
 
351
- if (!UIConstructor) {
352
- viewer.innerHTML = '<div class="git-empty-state" style="color:#f85149">Error: Diff2HtmlUI library not loaded correctly.<br>Please check your internet connection or CDN availability.</div>';
353
- return;
354
- }
355
-
356
338
  try {
357
339
  const diff = await getGitDiff(filePath, type === 'staged' ? 'staged' : 'working');
358
340