paneful 0.5.1 → 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,18 +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
- console.log('[active-editor] processes:', processes);
233
245
  const editorProcess = processes.find((p) => editorPatterns.some((pat) => p.toLowerCase().includes(pat)));
234
- console.log('[active-editor] matched:', editorProcess ?? 'none');
235
246
  if (!editorProcess) {
236
- res.json({ projectName: null });
247
+ editorCache = { projectName: null };
237
248
  return;
238
249
  }
239
- // Step 2: Get the front window title of the matched editor
240
250
  const titleScript = `
241
251
  tell application "System Events"
242
252
  tell process "${editorProcess.replace(/"/g, '\\"')}"
@@ -247,26 +257,43 @@ function startServer(devMode, port) {
247
257
  end tell
248
258
  return ""
249
259
  `;
250
- execFile('osascript', ['-e', titleScript], { timeout: 2000 }, (err2, stdout2, stderr2) => {
251
- console.log('[active-editor] title stdout:', JSON.stringify(stdout2));
252
- console.log('[active-editor] title err:', err2?.message ?? 'none');
260
+ execFile('osascript', ['-e', titleScript], { timeout: 2000 }, (err2, stdout2) => {
253
261
  if (err2 || !stdout2.trim()) {
254
- res.json({ projectName: null });
262
+ editorCache = { projectName: null };
255
263
  return;
256
264
  }
257
265
  const title = stdout2.trim();
258
- // Editor titles: "file — project — Editor" or "project — Editor"
259
- const parts = title.split(' \u2014 ');
260
266
  let projectName = null;
261
- if (parts.length >= 3) {
262
- projectName = parts[parts.length - 2];
267
+ // Try to extract a path from the title (e.g. "~/Documents/source/foo - branch")
268
+ const pathMatch = title.match(/^(~?\/[^\s]+)/);
269
+ if (pathMatch) {
270
+ const segments = pathMatch[1].replace(/\/$/, '').split('/');
271
+ projectName = segments[segments.length - 1] || null;
263
272
  }
264
- else if (parts.length === 2) {
265
- projectName = parts[0];
273
+ // Fallback: default title format "file — project — Editor" or "project — Editor"
274
+ if (!projectName) {
275
+ const parts = title.split(' \u2014 ');
276
+ if (parts.length >= 3) {
277
+ projectName = parts[parts.length - 2];
278
+ }
279
+ else if (parts.length === 2) {
280
+ projectName = parts[0];
281
+ }
282
+ }
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 });
266
288
  }
267
- res.json({ projectName });
268
289
  });
269
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);
270
297
  });
271
298
  // Resolve a dropped file's full path using OS file index (Spotlight on macOS)
272
299
  app.post('/api/resolve-path', (req, res) => {