paneful 0.5.2 → 0.6.0

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.
@@ -204,14 +204,27 @@ function startServer(devMode, port) {
204
204
  const killed = ptyManager.killProject(req.params.id);
205
205
  res.json({ killed: killed.length });
206
206
  });
207
- // Get the active editor's project folder name (for auto-focus on window switch)
208
- app.get('/api/active-editor', (_req, res) => {
209
- if (process.platform !== 'darwin') {
210
- res.json({ projectName: null });
207
+ app.post('/api/validate-path', (req, res) => {
208
+ const { path: rawPath } = req.body;
209
+ if (!rawPath) {
210
+ res.json({ valid: false });
211
211
  return;
212
212
  }
213
- // Step 1: Find editor processes dynamically — match known editor keywords
214
- const editorPatterns = ['cursor', 'code', 'vscode', 'visual studio code', 'zed', 'windsurf'];
213
+ const resolved = rawPath.replace(/^~/, os.homedir());
214
+ try {
215
+ const stat = fs.statSync(resolved);
216
+ res.json({ valid: stat.isDirectory(), resolved });
217
+ }
218
+ catch {
219
+ res.json({ valid: false });
220
+ }
221
+ });
222
+ // Active editor detection — polls server-side so the client gets an instant cached response
223
+ const editorPatterns = ['cursor', 'code', 'vscode', 'visual studio code', 'zed', 'windsurf'];
224
+ let editorCache = { projectName: null };
225
+ function pollActiveEditor() {
226
+ if (process.platform !== 'darwin')
227
+ return;
215
228
  const findScript = `
216
229
  tell application "System Events"
217
230
  set procNames to name of every process whose background only is false
@@ -225,16 +238,15 @@ function startServer(devMode, port) {
225
238
  execFile('osascript', ['-e', findScript], { timeout: 2000 }, (err, stdout, stderr) => {
226
239
  if (err) {
227
240
  const needsAccess = stderr?.includes('not allowed assistive access') || stderr?.includes('1719');
228
- res.json({ projectName: null, needsAccessibility: needsAccess || undefined });
241
+ editorCache = { projectName: null, needsAccessibility: needsAccess || undefined };
229
242
  return;
230
243
  }
231
244
  const processes = stdout.trim().split('\n').map((p) => p.trim()).filter(Boolean);
232
245
  const editorProcess = processes.find((p) => editorPatterns.some((pat) => p.toLowerCase().includes(pat)));
233
246
  if (!editorProcess) {
234
- res.json({ projectName: null });
247
+ editorCache = { projectName: null };
235
248
  return;
236
249
  }
237
- // Step 2: Get the front window title of the matched editor
238
250
  const titleScript = `
239
251
  tell application "System Events"
240
252
  tell process "${editorProcess.replace(/"/g, '\\"')}"
@@ -247,7 +259,7 @@ function startServer(devMode, port) {
247
259
  `;
248
260
  execFile('osascript', ['-e', titleScript], { timeout: 2000 }, (err2, stdout2) => {
249
261
  if (err2 || !stdout2.trim()) {
250
- res.json({ projectName: null });
262
+ editorCache = { projectName: null };
251
263
  return;
252
264
  }
253
265
  const title = stdout2.trim();
@@ -255,7 +267,6 @@ function startServer(devMode, port) {
255
267
  // Try to extract a path from the title (e.g. "~/Documents/source/foo - branch")
256
268
  const pathMatch = title.match(/^(~?\/[^\s]+)/);
257
269
  if (pathMatch) {
258
- // Grab the deepest folder from the path
259
270
  const segments = pathMatch[1].replace(/\/$/, '').split('/');
260
271
  projectName = segments[segments.length - 1] || null;
261
272
  }
@@ -269,9 +280,20 @@ function startServer(devMode, port) {
269
280
  projectName = parts[0];
270
281
  }
271
282
  }
272
- res.json({ projectName });
283
+ const prev = editorCache.projectName;
284
+ editorCache = { projectName };
285
+ // Push change to client over WebSocket
286
+ if (projectName && projectName !== prev) {
287
+ wsHandler.send({ type: 'editor:active', projectName });
288
+ }
273
289
  });
274
290
  });
291
+ }
292
+ // Poll every 2 seconds server-side
293
+ pollActiveEditor();
294
+ setInterval(pollActiveEditor, 2000);
295
+ app.get('/api/active-editor', (_req, res) => {
296
+ res.json(editorCache);
275
297
  });
276
298
  // Resolve a dropped file's full path using OS file index (Spotlight on macOS)
277
299
  app.post('/api/resolve-path', (req, res) => {