@cluesmith/codev 2.0.1 → 2.0.3

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 (209) hide show
  1. package/dashboard/dist/assets/{index-b38SaXk5.js → index-UsH9ixz1.js} +20 -20
  2. package/dashboard/dist/assets/index-UsH9ixz1.js.map +1 -0
  3. package/dashboard/dist/index.html +1 -1
  4. package/dist/agent-farm/cli.js +1 -1
  5. package/dist/agent-farm/cli.js.map +1 -1
  6. package/dist/agent-farm/commands/architect.d.ts +1 -1
  7. package/dist/agent-farm/commands/architect.js +3 -3
  8. package/dist/agent-farm/commands/architect.js.map +1 -1
  9. package/dist/agent-farm/commands/attach.js +1 -1
  10. package/dist/agent-farm/commands/attach.js.map +1 -1
  11. package/dist/agent-farm/commands/cleanup.js +6 -6
  12. package/dist/agent-farm/commands/cleanup.js.map +1 -1
  13. package/dist/agent-farm/commands/open.js +5 -5
  14. package/dist/agent-farm/commands/open.js.map +1 -1
  15. package/dist/agent-farm/commands/send.js +5 -5
  16. package/dist/agent-farm/commands/send.js.map +1 -1
  17. package/dist/agent-farm/commands/shell.js +5 -5
  18. package/dist/agent-farm/commands/shell.js.map +1 -1
  19. package/dist/agent-farm/commands/spawn-worktree.d.ts +1 -1
  20. package/dist/agent-farm/commands/spawn-worktree.d.ts.map +1 -1
  21. package/dist/agent-farm/commands/spawn-worktree.js +8 -8
  22. package/dist/agent-farm/commands/spawn-worktree.js.map +1 -1
  23. package/dist/agent-farm/commands/spawn.js +3 -3
  24. package/dist/agent-farm/commands/spawn.js.map +1 -1
  25. package/dist/agent-farm/commands/start.d.ts +4 -4
  26. package/dist/agent-farm/commands/start.js +16 -16
  27. package/dist/agent-farm/commands/start.js.map +1 -1
  28. package/dist/agent-farm/commands/status.d.ts +1 -1
  29. package/dist/agent-farm/commands/status.js +16 -16
  30. package/dist/agent-farm/commands/status.js.map +1 -1
  31. package/dist/agent-farm/commands/stop.d.ts +4 -4
  32. package/dist/agent-farm/commands/stop.js +9 -9
  33. package/dist/agent-farm/commands/stop.js.map +1 -1
  34. package/dist/agent-farm/db/index.d.ts.map +1 -1
  35. package/dist/agent-farm/db/index.js +82 -7
  36. package/dist/agent-farm/db/index.js.map +1 -1
  37. package/dist/agent-farm/db/schema.d.ts +2 -2
  38. package/dist/agent-farm/db/schema.d.ts.map +1 -1
  39. package/dist/agent-farm/db/schema.js +21 -4
  40. package/dist/agent-farm/db/schema.js.map +1 -1
  41. package/dist/agent-farm/lib/tower-client.d.ts +20 -20
  42. package/dist/agent-farm/lib/tower-client.d.ts.map +1 -1
  43. package/dist/agent-farm/lib/tower-client.js +25 -25
  44. package/dist/agent-farm/lib/tower-client.js.map +1 -1
  45. package/dist/agent-farm/lib/tunnel-client.d.ts +12 -2
  46. package/dist/agent-farm/lib/tunnel-client.d.ts.map +1 -1
  47. package/dist/agent-farm/lib/tunnel-client.js +59 -1
  48. package/dist/agent-farm/lib/tunnel-client.js.map +1 -1
  49. package/dist/agent-farm/servers/tower-instances.d.ts +18 -18
  50. package/dist/agent-farm/servers/tower-instances.d.ts.map +1 -1
  51. package/dist/agent-farm/servers/tower-instances.js +89 -89
  52. package/dist/agent-farm/servers/tower-instances.js.map +1 -1
  53. package/dist/agent-farm/servers/tower-routes.d.ts +1 -1
  54. package/dist/agent-farm/servers/tower-routes.d.ts.map +1 -1
  55. package/dist/agent-farm/servers/tower-routes.js +184 -162
  56. package/dist/agent-farm/servers/tower-routes.js.map +1 -1
  57. package/dist/agent-farm/servers/tower-server.js +23 -19
  58. package/dist/agent-farm/servers/tower-server.js.map +1 -1
  59. package/dist/agent-farm/servers/tower-terminals.d.ts +27 -29
  60. package/dist/agent-farm/servers/tower-terminals.d.ts.map +1 -1
  61. package/dist/agent-farm/servers/tower-terminals.js +95 -116
  62. package/dist/agent-farm/servers/tower-terminals.js.map +1 -1
  63. package/dist/agent-farm/servers/tower-tunnel.d.ts +2 -2
  64. package/dist/agent-farm/servers/tower-tunnel.d.ts.map +1 -1
  65. package/dist/agent-farm/servers/tower-tunnel.js +12 -12
  66. package/dist/agent-farm/servers/tower-tunnel.js.map +1 -1
  67. package/dist/agent-farm/servers/tower-types.d.ts +8 -10
  68. package/dist/agent-farm/servers/tower-types.d.ts.map +1 -1
  69. package/dist/agent-farm/servers/tower-utils.d.ts +9 -9
  70. package/dist/agent-farm/servers/tower-utils.d.ts.map +1 -1
  71. package/dist/agent-farm/servers/tower-utils.js +18 -18
  72. package/dist/agent-farm/servers/tower-utils.js.map +1 -1
  73. package/dist/agent-farm/servers/tower-websocket.d.ts +2 -2
  74. package/dist/agent-farm/servers/tower-websocket.js +14 -14
  75. package/dist/agent-farm/servers/tower-websocket.js.map +1 -1
  76. package/dist/agent-farm/types.d.ts +2 -2
  77. package/dist/agent-farm/types.d.ts.map +1 -1
  78. package/dist/agent-farm/utils/config.d.ts +1 -1
  79. package/dist/agent-farm/utils/config.d.ts.map +1 -1
  80. package/dist/agent-farm/utils/config.js +16 -16
  81. package/dist/agent-farm/utils/config.js.map +1 -1
  82. package/dist/agent-farm/utils/file-tabs.d.ts +3 -3
  83. package/dist/agent-farm/utils/file-tabs.d.ts.map +1 -1
  84. package/dist/agent-farm/utils/file-tabs.js +9 -9
  85. package/dist/agent-farm/utils/file-tabs.js.map +1 -1
  86. package/dist/agent-farm/utils/gate-status.d.ts +2 -2
  87. package/dist/agent-farm/utils/gate-status.d.ts.map +1 -1
  88. package/dist/agent-farm/utils/gate-status.js +3 -3
  89. package/dist/agent-farm/utils/gate-status.js.map +1 -1
  90. package/dist/agent-farm/utils/index.d.ts +0 -1
  91. package/dist/agent-farm/utils/index.d.ts.map +1 -1
  92. package/dist/agent-farm/utils/index.js +0 -1
  93. package/dist/agent-farm/utils/index.js.map +1 -1
  94. package/dist/agent-farm/utils/notifications.d.ts +4 -4
  95. package/dist/agent-farm/utils/notifications.d.ts.map +1 -1
  96. package/dist/agent-farm/utils/notifications.js +18 -18
  97. package/dist/agent-farm/utils/notifications.js.map +1 -1
  98. package/dist/commands/adopt.d.ts +2 -2
  99. package/dist/commands/adopt.d.ts.map +1 -1
  100. package/dist/commands/adopt.js +13 -3
  101. package/dist/commands/adopt.js.map +1 -1
  102. package/dist/commands/consult/index.d.ts +1 -1
  103. package/dist/commands/consult/index.d.ts.map +1 -1
  104. package/dist/commands/consult/index.js +52 -51
  105. package/dist/commands/consult/index.js.map +1 -1
  106. package/dist/commands/doctor.js +6 -6
  107. package/dist/commands/doctor.js.map +1 -1
  108. package/dist/commands/import.js +4 -4
  109. package/dist/commands/import.js.map +1 -1
  110. package/dist/commands/init.d.ts +2 -2
  111. package/dist/commands/init.d.ts.map +1 -1
  112. package/dist/commands/init.js +13 -3
  113. package/dist/commands/init.js.map +1 -1
  114. package/dist/commands/porch/index.d.ts +6 -6
  115. package/dist/commands/porch/index.d.ts.map +1 -1
  116. package/dist/commands/porch/index.js +37 -37
  117. package/dist/commands/porch/index.js.map +1 -1
  118. package/dist/commands/porch/next.d.ts +1 -1
  119. package/dist/commands/porch/next.d.ts.map +1 -1
  120. package/dist/commands/porch/next.js +43 -40
  121. package/dist/commands/porch/next.js.map +1 -1
  122. package/dist/commands/porch/notify.d.ts +11 -0
  123. package/dist/commands/porch/notify.d.ts.map +1 -0
  124. package/dist/commands/porch/notify.js +30 -0
  125. package/dist/commands/porch/notify.js.map +1 -0
  126. package/dist/commands/porch/plan.d.ts +1 -1
  127. package/dist/commands/porch/plan.d.ts.map +1 -1
  128. package/dist/commands/porch/plan.js +3 -3
  129. package/dist/commands/porch/plan.js.map +1 -1
  130. package/dist/commands/porch/prompts.d.ts +1 -1
  131. package/dist/commands/porch/prompts.d.ts.map +1 -1
  132. package/dist/commands/porch/prompts.js +13 -13
  133. package/dist/commands/porch/prompts.js.map +1 -1
  134. package/dist/commands/porch/protocol.d.ts +1 -1
  135. package/dist/commands/porch/protocol.d.ts.map +1 -1
  136. package/dist/commands/porch/protocol.js +6 -6
  137. package/dist/commands/porch/protocol.js.map +1 -1
  138. package/dist/commands/porch/state.d.ts +6 -6
  139. package/dist/commands/porch/state.d.ts.map +1 -1
  140. package/dist/commands/porch/state.js +11 -11
  141. package/dist/commands/porch/state.js.map +1 -1
  142. package/dist/commands/update.d.ts.map +1 -1
  143. package/dist/commands/update.js +10 -1
  144. package/dist/commands/update.js.map +1 -1
  145. package/dist/lib/scaffold.d.ts +13 -0
  146. package/dist/lib/scaffold.d.ts.map +1 -1
  147. package/dist/lib/scaffold.js +34 -0
  148. package/dist/lib/scaffold.js.map +1 -1
  149. package/dist/lib/skeleton.d.ts +7 -7
  150. package/dist/lib/skeleton.d.ts.map +1 -1
  151. package/dist/lib/skeleton.js +10 -10
  152. package/dist/lib/skeleton.js.map +1 -1
  153. package/dist/terminal/pty-manager.d.ts +1 -1
  154. package/dist/terminal/pty-manager.d.ts.map +1 -1
  155. package/dist/terminal/pty-manager.js +3 -3
  156. package/dist/terminal/pty-manager.js.map +1 -1
  157. package/package.json +1 -1
  158. package/templates/open.html +13 -13
  159. package/templates/tower.html +63 -72
  160. package/templates/vendor/marked.min.js +6 -0
  161. package/templates/vendor/prism-bash.min.js +1 -0
  162. package/templates/vendor/prism-css.min.js +1 -0
  163. package/templates/vendor/prism-javascript.min.js +1 -0
  164. package/templates/vendor/prism-json.min.js +1 -0
  165. package/templates/vendor/prism-markdown.min.js +1 -0
  166. package/templates/vendor/prism-markup.min.js +1 -0
  167. package/templates/vendor/prism-python.min.js +1 -0
  168. package/templates/vendor/prism-tomorrow.min.css +1 -0
  169. package/templates/vendor/prism-typescript.min.js +1 -0
  170. package/templates/vendor/prism-yaml.min.js +1 -0
  171. package/templates/vendor/prism.min.js +1 -0
  172. package/templates/vendor/purify.min.js +3 -0
  173. package/dashboard/dist/assets/index-b38SaXk5.js.map +0 -1
  174. package/dist/agent-farm/hq-connector.d.ts +0 -19
  175. package/dist/agent-farm/hq-connector.d.ts.map +0 -1
  176. package/dist/agent-farm/hq-connector.js +0 -351
  177. package/dist/agent-farm/hq-connector.js.map +0 -1
  178. package/dist/agent-farm/utils/deps.d.ts +0 -51
  179. package/dist/agent-farm/utils/deps.d.ts.map +0 -1
  180. package/dist/agent-farm/utils/deps.js +0 -162
  181. package/dist/agent-farm/utils/deps.js.map +0 -1
  182. package/dist/agent-farm/utils/gate-watcher.d.ts +0 -38
  183. package/dist/agent-farm/utils/gate-watcher.d.ts.map +0 -1
  184. package/dist/agent-farm/utils/gate-watcher.js +0 -122
  185. package/dist/agent-farm/utils/gate-watcher.js.map +0 -1
  186. package/dist/agent-farm/utils/session.d.ts +0 -32
  187. package/dist/agent-farm/utils/session.d.ts.map +0 -1
  188. package/dist/agent-farm/utils/session.js +0 -57
  189. package/dist/agent-farm/utils/session.js.map +0 -1
  190. package/dist/lib/projectlist-parser.d.ts +0 -70
  191. package/dist/lib/projectlist-parser.d.ts.map +0 -1
  192. package/dist/lib/projectlist-parser.js +0 -200
  193. package/dist/lib/projectlist-parser.js.map +0 -1
  194. package/templates/dashboard/css/dialogs.css +0 -149
  195. package/templates/dashboard/css/files.css +0 -558
  196. package/templates/dashboard/css/layout.css +0 -133
  197. package/templates/dashboard/css/projects.css +0 -501
  198. package/templates/dashboard/css/statusbar.css +0 -23
  199. package/templates/dashboard/css/tabs.css +0 -314
  200. package/templates/dashboard/css/utilities.css +0 -50
  201. package/templates/dashboard/css/variables.css +0 -45
  202. package/templates/dashboard/index.html +0 -149
  203. package/templates/dashboard/js/dialogs.js +0 -368
  204. package/templates/dashboard/js/files.js +0 -448
  205. package/templates/dashboard/js/main.js +0 -476
  206. package/templates/dashboard/js/projects.js +0 -544
  207. package/templates/dashboard/js/state.js +0 -91
  208. package/templates/dashboard/js/tabs.js +0 -518
  209. package/templates/dashboard/js/utils.js +0 -191
@@ -1,448 +0,0 @@
1
- // File Tree Browser and Search (Spec 0055, 0058)
2
-
3
- // Auto-refresh interval (5 seconds)
4
- const FILES_POLL_INTERVAL = 5000;
5
- let filesLastHash = null;
6
- let filesPollTimer = null;
7
-
8
- // Start auto-polling for file changes
9
- function startFilesPolling() {
10
- if (filesPollTimer) return; // Already polling
11
- filesPollTimer = setInterval(checkFilesForChanges, FILES_POLL_INTERVAL);
12
- }
13
-
14
- // Stop auto-polling
15
- function stopFilesPolling() {
16
- if (filesPollTimer) {
17
- clearInterval(filesPollTimer);
18
- filesPollTimer = null;
19
- }
20
- }
21
-
22
- // Check if files have changed and refresh if needed
23
- async function checkFilesForChanges() {
24
- try {
25
- const response = await fetch(apiUrl(`api/files/hash?t=${Date.now()}`));
26
- if (!response.ok) return;
27
-
28
- const data = await response.json();
29
- if (filesLastHash === null) {
30
- filesLastHash = data.hash;
31
- } else if (data.hash !== filesLastHash) {
32
- filesLastHash = data.hash;
33
- await loadFilesTree();
34
- rerenderFilesBrowser();
35
- }
36
- } catch (err) {
37
- // Ignore errors - server may be restarting
38
- }
39
- }
40
-
41
- // Load the file tree from the API
42
- async function loadFilesTree() {
43
- try {
44
- // Add cache-busting param to ensure fresh data
45
- const response = await fetch(apiUrl(`api/files?t=${Date.now()}`));
46
- if (!response.ok) {
47
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
48
- }
49
- filesTreeData = await response.json();
50
- filesTreeError = null;
51
- filesTreeLoaded = true;
52
- filesTreeFlat = flattenFilesTree(filesTreeData);
53
- } catch (err) {
54
- console.error('Failed to load files tree:', err);
55
- filesTreeError = 'Could not load file tree: ' + err.message;
56
- filesTreeData = [];
57
- filesTreeFlat = [];
58
- }
59
- }
60
-
61
- // Load files tree if not already loaded
62
- async function loadFilesTreeIfNeeded() {
63
- if (!filesTreeLoaded) {
64
- await loadFilesTree();
65
- }
66
- }
67
-
68
- // Flatten the file tree into a searchable array
69
- function flattenFilesTree(nodes, result = []) {
70
- for (const node of nodes) {
71
- if (node.type === 'file') {
72
- result.push({ name: node.name, path: node.path });
73
- } else if (node.children) {
74
- flattenFilesTree(node.children, result);
75
- }
76
- }
77
- return result;
78
- }
79
-
80
- // Search files with relevance sorting
81
- function searchFiles(query) {
82
- if (!query) return [];
83
- const q = query.toLowerCase();
84
-
85
- const matches = filesTreeFlat.filter(f =>
86
- f.path.toLowerCase().includes(q)
87
- );
88
-
89
- matches.sort((a, b) => {
90
- const aName = a.name.toLowerCase();
91
- const bName = b.name.toLowerCase();
92
- const aPath = a.path.toLowerCase();
93
- const bPath = b.path.toLowerCase();
94
-
95
- if (aName === q && bName !== q) return -1;
96
- if (bName === q && aName !== q) return 1;
97
- if (aName.startsWith(q) && !bName.startsWith(q)) return -1;
98
- if (bName.startsWith(q) && !aName.startsWith(q)) return 1;
99
- if (aName.includes(q) && !bName.includes(q)) return -1;
100
- if (bName.includes(q) && !aName.includes(q)) return 1;
101
- return aPath.localeCompare(bPath);
102
- });
103
-
104
- return matches.slice(0, 15);
105
- }
106
-
107
- // Get file icon based on extension
108
- function getFileIcon(filename) {
109
- const ext = filename.split('.').pop().toLowerCase();
110
- const iconMap = {
111
- 'js': '📜', 'ts': '📜', 'jsx': '⚛️', 'tsx': '⚛️',
112
- 'json': '{}', 'md': '📝', 'html': '🌐', 'css': '🎨',
113
- 'py': '🐍', 'sh': '⚙️', 'bash': '⚙️', 'yml': '⚙️', 'yaml': '⚙️',
114
- 'png': '🖼️', 'jpg': '🖼️', 'jpeg': '🖼️', 'gif': '🖼️', 'svg': '🖼️',
115
- };
116
- return iconMap[ext] || '📄';
117
- }
118
-
119
- // Render tree nodes recursively
120
- function renderTreeNodes(nodes, depth) {
121
- if (!nodes || nodes.length === 0) return '';
122
-
123
- return nodes.map(node => {
124
- const indent = depth * 16;
125
- const isExpanded = filesTreeExpanded.has(node.path);
126
- const jsPath = escapeJsString(node.path);
127
-
128
- if (node.type === 'dir') {
129
- const icon = isExpanded ? '▼' : '▶';
130
- const childrenHtml = node.children && node.children.length > 0
131
- ? `<div class="tree-children ${isExpanded ? '' : 'collapsed'}" data-path="${escapeHtml(node.path)}">${renderTreeNodes(node.children, depth + 1)}</div>`
132
- : '';
133
-
134
- return `
135
- <div class="tree-item" data-type="dir" data-path="${escapeHtml(node.path)}" style="padding-left: ${indent + 8}px;" onclick="toggleFolder('${jsPath}')">
136
- <span class="tree-item-icon folder-toggle">${icon}</span>
137
- <span class="tree-item-name">${escapeHtml(node.name)}</span>
138
- </div>
139
- ${childrenHtml}
140
- `;
141
- } else {
142
- return `
143
- <div class="tree-item" data-type="file" data-path="${escapeHtml(node.path)}" style="padding-left: ${indent + 8}px;" onclick="openFileFromTree('${jsPath}')">
144
- <span class="tree-item-icon">${getFileIcon(node.name)}</span>
145
- <span class="tree-item-name">${escapeHtml(node.name)}</span>
146
- </div>
147
- `;
148
- }
149
- }).join('');
150
- }
151
-
152
- // Toggle folder expanded/collapsed state
153
- function toggleFolder(path) {
154
- if (filesTreeExpanded.has(path)) {
155
- filesTreeExpanded.delete(path);
156
- } else {
157
- filesTreeExpanded.add(path);
158
- }
159
- rerenderFilesBrowser();
160
- }
161
-
162
- // Re-render file browser in current context
163
- function rerenderFilesBrowser() {
164
- if (activeTabId === 'dashboard') {
165
- const filesContentEl = document.getElementById('dashboard-files-content');
166
- if (filesContentEl) {
167
- filesContentEl.innerHTML = filesSearchQuery
168
- ? renderFilesSearchResults()
169
- : renderDashboardFilesBrowserWithWrapper();
170
- }
171
- }
172
- }
173
-
174
- // Wrapper for file browser
175
- function renderDashboardFilesBrowserWithWrapper() {
176
- return `<div class="dashboard-files-list" id="dashboard-files-list">${renderDashboardFilesBrowser()}</div>`;
177
- }
178
-
179
- // Render compact file browser for dashboard
180
- function renderDashboardFilesBrowser() {
181
- if (filesTreeError) {
182
- return `<div class="dashboard-empty-state">${escapeHtml(filesTreeError)}</div>`;
183
- }
184
-
185
- if (!filesTreeLoaded || filesTreeData.length === 0) {
186
- return '<div class="dashboard-empty-state">Loading files...</div>';
187
- }
188
-
189
- return renderTreeNodes(filesTreeData, 0);
190
- }
191
-
192
- // Collapse all folders
193
- function collapseAllFolders() {
194
- filesTreeExpanded.clear();
195
- rerenderFilesBrowser();
196
- }
197
-
198
- // Expand all folders
199
- function expandAllFolders() {
200
- function collectPaths(nodes) {
201
- for (const node of nodes) {
202
- if (node.type === 'dir') {
203
- filesTreeExpanded.add(node.path);
204
- if (node.children) {
205
- collectPaths(node.children);
206
- }
207
- }
208
- }
209
- }
210
- collectPaths(filesTreeData);
211
- rerenderFilesBrowser();
212
- }
213
-
214
- // Refresh files tree
215
- async function refreshFilesTree() {
216
- await loadFilesTree();
217
- rerenderFilesBrowser();
218
- showToast('Files refreshed', 'success');
219
- }
220
-
221
- // Open file from tree click
222
- // Uses shared openFileTab from utils.js (Maintenance Run 0004)
223
- async function openFileFromTree(filePath) {
224
- await openFileTab(filePath, { showSwitchToast: false });
225
- }
226
-
227
- // ========================================
228
- // File Search Functions (Spec 0058)
229
- // ========================================
230
-
231
- // Debounced search input handler for Files column
232
- function onFilesSearchInput(value) {
233
- clearTimeout(filesSearchDebounceTimer);
234
- filesSearchDebounceTimer = setTimeout(() => {
235
- filesSearchQuery = value;
236
- filesSearchResults = searchFiles(value);
237
- filesSearchIndex = 0;
238
- rerenderFilesSearch();
239
- }, 100);
240
- }
241
-
242
- // Clear files search and restore tree view
243
- function clearFilesSearch() {
244
- filesSearchQuery = '';
245
- filesSearchResults = [];
246
- filesSearchIndex = 0;
247
- const input = document.getElementById('files-search-input');
248
- if (input) {
249
- input.value = '';
250
- }
251
- rerenderFilesSearch();
252
- }
253
-
254
- // Re-render the files search area
255
- function rerenderFilesSearch() {
256
- const filesContentEl = document.getElementById('dashboard-files-content');
257
- if (filesContentEl) {
258
- filesContentEl.innerHTML = filesSearchQuery
259
- ? renderFilesSearchResults()
260
- : renderDashboardFilesBrowserWithWrapper();
261
- }
262
- const clearBtn = document.querySelector('.files-search-clear');
263
- if (clearBtn) {
264
- clearBtn.classList.toggle('hidden', !filesSearchQuery);
265
- }
266
- }
267
-
268
- // Render search results for Files column
269
- function renderFilesSearchResults() {
270
- if (!filesSearchResults.length) {
271
- return '<div class="dashboard-empty-state">No files found</div>';
272
- }
273
-
274
- return `<div class="files-search-results">${filesSearchResults.map((file, index) =>
275
- renderSearchResult(file, index, index === filesSearchIndex, filesSearchQuery, 'files')
276
- ).join('')}</div>`;
277
- }
278
-
279
- // Highlight matching text in search results
280
- function highlightMatch(text, query) {
281
- if (!query) return escapeHtml(text);
282
- const q = query.toLowerCase();
283
- const t = text.toLowerCase();
284
- const idx = t.indexOf(q);
285
- if (idx === -1) return escapeHtml(text);
286
-
287
- return escapeHtml(text.substring(0, idx)) +
288
- '<span class="files-search-highlight">' + escapeHtml(text.substring(idx, idx + query.length)) + '</span>' +
289
- escapeHtml(text.substring(idx + query.length));
290
- }
291
-
292
- // Render a single search result
293
- function renderSearchResult(file, index, isSelected, query, context) {
294
- const classPrefix = context === 'palette' ? 'file-palette' : 'files-search';
295
- const jsPath = escapeJsString(file.path);
296
-
297
- return `
298
- <div class="${classPrefix}-result ${isSelected ? 'selected' : ''}"
299
- data-index="${index}"
300
- onclick="openFileFromSearch('${jsPath}', '${context}')">
301
- <div class="${classPrefix}-result-name">${highlightMatch(file.name, query)}</div>
302
- <div class="${classPrefix}-result-path">${highlightMatch(file.path, query)}</div>
303
- </div>
304
- `;
305
- }
306
-
307
- // Keyboard handler for Files search input
308
- function onFilesSearchKeydown(event) {
309
- if (!filesSearchResults.length) {
310
- if (event.key === 'Escape') {
311
- clearFilesSearch();
312
- event.target.blur();
313
- }
314
- return;
315
- }
316
-
317
- if (event.key === 'ArrowDown') {
318
- event.preventDefault();
319
- filesSearchIndex = Math.min(filesSearchIndex + 1, filesSearchResults.length - 1);
320
- rerenderFilesSearch();
321
- scrollSelectedIntoView('files');
322
- } else if (event.key === 'ArrowUp') {
323
- event.preventDefault();
324
- filesSearchIndex = Math.max(filesSearchIndex - 1, 0);
325
- rerenderFilesSearch();
326
- scrollSelectedIntoView('files');
327
- } else if (event.key === 'Enter') {
328
- event.preventDefault();
329
- if (filesSearchResults[filesSearchIndex]) {
330
- openFileFromSearch(filesSearchResults[filesSearchIndex].path, 'files');
331
- }
332
- } else if (event.key === 'Escape') {
333
- clearFilesSearch();
334
- event.target.blur();
335
- }
336
- }
337
-
338
- // Scroll selected result into view
339
- function scrollSelectedIntoView(context) {
340
- const selector = context === 'palette'
341
- ? '.file-palette-result.selected'
342
- : '.files-search-result.selected';
343
- const selected = document.querySelector(selector);
344
- if (selected) {
345
- selected.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
346
- }
347
- }
348
-
349
- // Open file from search result
350
- function openFileFromSearch(filePath, context) {
351
- const existingTab = tabs.find(t => t.type === 'file' && t.path === filePath);
352
- if (existingTab) {
353
- selectTab(existingTab.id);
354
- refreshFileTab(existingTab.id);
355
- } else {
356
- openFileFromTree(filePath);
357
- }
358
-
359
- if (context === 'palette') {
360
- closePalette();
361
- } else {
362
- clearFilesSearch();
363
- }
364
- }
365
-
366
- // ========================================
367
- // Cmd+P Palette Functions (Spec 0058)
368
- // ========================================
369
-
370
- // Open the file search palette
371
- function openPalette() {
372
- paletteOpen = true;
373
- paletteQuery = '';
374
- paletteResults = [];
375
- paletteIndex = 0;
376
- document.getElementById('file-palette').classList.remove('hidden');
377
- const input = document.getElementById('palette-input');
378
- input.value = '';
379
- input.focus();
380
- rerenderPaletteResults();
381
- }
382
-
383
- // Close the file search palette
384
- function closePalette() {
385
- paletteOpen = false;
386
- paletteQuery = '';
387
- paletteResults = [];
388
- paletteIndex = 0;
389
- document.getElementById('file-palette').classList.add('hidden');
390
- }
391
-
392
- // Debounced palette input handler
393
- function onPaletteInput(value) {
394
- clearTimeout(paletteDebounceTimer);
395
- paletteDebounceTimer = setTimeout(() => {
396
- paletteQuery = value;
397
- paletteResults = searchFiles(value);
398
- paletteIndex = 0;
399
- rerenderPaletteResults();
400
- }, 100);
401
- }
402
-
403
- // Re-render palette results
404
- function rerenderPaletteResults() {
405
- const resultsEl = document.getElementById('palette-results');
406
- if (!resultsEl) return;
407
-
408
- if (!paletteQuery) {
409
- resultsEl.innerHTML = '<div class="file-palette-empty">Type to search files...</div>';
410
- return;
411
- }
412
-
413
- if (!paletteResults.length) {
414
- resultsEl.innerHTML = '<div class="file-palette-empty">No files found</div>';
415
- return;
416
- }
417
-
418
- resultsEl.innerHTML = paletteResults.map((file, index) =>
419
- renderSearchResult(file, index, index === paletteIndex, paletteQuery, 'palette')
420
- ).join('');
421
- }
422
-
423
- // Keyboard handler for palette input
424
- function onPaletteKeydown(event) {
425
- if (event.key === 'Escape') {
426
- closePalette();
427
- return;
428
- }
429
-
430
- if (!paletteResults.length) return;
431
-
432
- if (event.key === 'ArrowDown') {
433
- event.preventDefault();
434
- paletteIndex = Math.min(paletteIndex + 1, paletteResults.length - 1);
435
- rerenderPaletteResults();
436
- scrollSelectedIntoView('palette');
437
- } else if (event.key === 'ArrowUp') {
438
- event.preventDefault();
439
- paletteIndex = Math.max(paletteIndex - 1, 0);
440
- rerenderPaletteResults();
441
- scrollSelectedIntoView('palette');
442
- } else if (event.key === 'Enter') {
443
- event.preventDefault();
444
- if (paletteResults[paletteIndex]) {
445
- openFileFromSearch(paletteResults[paletteIndex].path, 'palette');
446
- }
447
- }
448
- }