Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions main.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const { app, BrowserWindow, dialog, ipcMain, Menu, screen, shell } = require('electron');
const { Worker } = require('worker_threads');

Check warning on line 2 in main.js

View workflow job for this annotation

GitHub Actions / test (22)

'Worker' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 2 in main.js

View workflow job for this annotation

GitHub Actions / test (20)

'Worker' is assigned a value but never used. Allowed unused vars must match /^_/u
const { execFile } = require('child_process');
const path = require('path');
const fs = require('fs');
Expand All @@ -16,7 +16,7 @@
}

// getFolderIndexMtimeMs moved to session-cache.js
const { startMcpServer, shutdownMcpServer, shutdownAll: shutdownAllMcp, resolvePendingDiff, rekeyMcpServer, cleanStaleLockFiles } = require('./mcp-bridge');

Check warning on line 19 in main.js

View workflow job for this annotation

GitHub Actions / test (22)

'cleanStaleLockFiles' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 19 in main.js

View workflow job for this annotation

GitHub Actions / test (20)

'cleanStaleLockFiles' is assigned a value but never used. Allowed unused vars must match /^_/u
const { fetchAndTransformUsage } = require('./claude-auth');
log.transports.file.level = app.isPackaged ? 'info' : 'debug';
log.transports.console.level = app.isPackaged ? 'info' : 'debug';
Expand All @@ -36,7 +36,7 @@
);

// Shell profiles → shell-profiles.js
const { discoverShellProfiles, getShellProfiles, resolveShell, isWindows, isWslShell, windowsToWslPath, shellArgs } = require('./shell-profiles');

Check warning on line 39 in main.js

View workflow job for this annotation

GitHub Actions / test (22)

'isWindows' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 39 in main.js

View workflow job for this annotation

GitHub Actions / test (22)

'discoverShellProfiles' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 39 in main.js

View workflow job for this annotation

GitHub Actions / test (20)

'isWindows' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 39 in main.js

View workflow job for this annotation

GitHub Actions / test (20)

'discoverShellProfiles' is assigned a value but never used. Allowed unused vars must match /^_/u
const { startScheduler } = require('./schedule-runner');
const { encodeProjectPath } = require('./encode-project-path');

Expand Down Expand Up @@ -73,7 +73,7 @@
getMeta, getAllMeta, toggleStar, setName, setArchived,
isCachePopulated, getAllCached, getCachedByFolder, getCachedByParent, getCachedFolder, getCachedSession, upsertCachedSessions,
deleteCachedSession, deleteCachedFolder,
getFolderMeta, getAllFolderMeta, setFolderMeta,

Check warning on line 76 in main.js

View workflow job for this annotation

GitHub Actions / test (22)

'getFolderMeta' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 76 in main.js

View workflow job for this annotation

GitHub Actions / test (20)

'getFolderMeta' is assigned a value but never used. Allowed unused vars must match /^_/u
upsertSearchEntries, updateSearchTitle, deleteSearchSession, deleteSearchFolder, deleteSearchType,
searchByType, isSearchIndexPopulated, searchFtsRecreated,
getSetting, setSetting, deleteSetting,
Expand Down Expand Up @@ -289,8 +289,8 @@
setFolderMeta, getAllFolderMeta, getAllMeta, getAllCached, getSetting, getMeta, setName,
},
});
const { readSessionFile, readFolderFromFilesystem, refreshFolder, populateCacheFromFilesystem,

Check warning on line 292 in main.js

View workflow job for this annotation

GitHub Actions / test (22)

'populateCacheFromFilesystem' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 292 in main.js

View workflow job for this annotation

GitHub Actions / test (22)

'readFolderFromFilesystem' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 292 in main.js

View workflow job for this annotation

GitHub Actions / test (22)

'readSessionFile' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 292 in main.js

View workflow job for this annotation

GitHub Actions / test (20)

'populateCacheFromFilesystem' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 292 in main.js

View workflow job for this annotation

GitHub Actions / test (20)

'readFolderFromFilesystem' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 292 in main.js

View workflow job for this annotation

GitHub Actions / test (20)

'readSessionFile' is assigned a value but never used. Allowed unused vars must match /^_/u
buildProjectsFromCache, notifyRendererProjectsChanged, sendStatus, populateCacheViaWorker } = sessionCache;

Check warning on line 293 in main.js

View workflow job for this annotation

GitHub Actions / test (22)

'sendStatus' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 293 in main.js

View workflow job for this annotation

GitHub Actions / test (20)

'sendStatus' is assigned a value but never used. Allowed unused vars must match /^_/u
const { resolveJsonlPath } = require('./read-session-file');


Expand Down Expand Up @@ -1023,6 +1023,23 @@
}
});

// --- IPC: delete-work-file ---
ipcMain.handle('delete-work-file', (_event, filePath) => {
try {
const resolved = path.resolve(filePath);
if (!resolved.includes('/.work-files/') && !resolved.includes('\\.work-files\\')) {
return { ok: false, error: 'access denied' };
}
if (!fs.existsSync(resolved)) return { ok: false, error: 'not found' };
fs.unlinkSync(resolved);
// FTS entry is cleaned up on the next get-work-files call (full type rebuild)
return { ok: true };
} catch (err) {
console.error('Error deleting work file:', err);
return { ok: false, error: err.message };
}
});

// --- IPC: search ---
ipcMain.handle('search', (_event, type, query, titleOnly) => {
return searchByType(type, query, 50, !!titleOnly);
Expand Down Expand Up @@ -1280,7 +1297,7 @@
// WSL profiles only work for plain terminals — Claude CLI sessions need the
// Windows shell because session data lives on the Windows filesystem.
const requestedProfile = resolveShell(effectiveProfileId);
const useWslProfile = isWslShell(requestedProfile.path) && isPlainTerminal;

Check warning on line 1300 in main.js

View workflow job for this annotation

GitHub Actions / test (22)

'useWslProfile' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 1300 in main.js

View workflow job for this annotation

GitHub Actions / test (20)

'useWslProfile' is assigned a value but never used. Allowed unused vars must match /^_/u
const shellProfile = (isWslShell(requestedProfile.path) && !isPlainTerminal)
? resolveShell('auto')
: requestedProfile;
Expand Down
1 change: 1 addition & 0 deletions preload.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ contextBridge.exposeInMainWorld('api', {
saveMemory: (filePath, content) => ipcRenderer.invoke('save-memory', filePath, content),
getWorkFiles: () => ipcRenderer.invoke('get-work-files'),
readWorkFile: (filePath) => ipcRenderer.invoke('read-work-file', filePath),
deleteWorkFile: (filePath) => ipcRenderer.invoke('delete-work-file', filePath),
getProjects: (showArchived) => ipcRenderer.invoke('get-projects', showArchived),
rebuildCache: () => ipcRenderer.invoke('rebuild-cache'),
getActiveSessions: () => ipcRenderer.invoke('get-active-sessions'),
Expand Down
13 changes: 13 additions & 0 deletions public/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,19 @@ const workFilesViewer = document.getElementById('work-files-viewer');
const workFilesPanel = new ViewerPanel(workFilesViewer, {
copyPath: true, copyContent: true,
language: 'auto', storageKey: 'workFilesPreviewMode',
format: true,
onDelete: async (filePath) => {
const result = await window.api.deleteWorkFile(filePath);
if (result && result.ok) {
// Hide the panel and reload the list
workFilesViewer.style.display = 'none';
if (typeof loadWorkFiles === 'function') loadWorkFiles();
}
return result;
},
onClose: () => {
workFilesViewer.style.display = 'none';
},
});
const terminalArea = document.getElementById('terminal-area');
const settingsViewer = document.getElementById('settings-viewer');
Expand Down
6 changes: 6 additions & 0 deletions public/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -3905,6 +3905,12 @@ body { display: flex; flex-direction: column; }
background: rgba(224,80,112,0.06);
}

.fp-delete-btn:hover {
border-color: rgba(224,80,112,0.45);
color: #e05070;
background: rgba(224,80,112,0.10);
}

#file-panel-content {
flex: 1;
display: flex;
Expand Down
64 changes: 64 additions & 0 deletions public/viewer-panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ class ViewerPanel {
preview: true,
wrap: true,
gotoLine: true,
format: !!opts.format,
delete: !!opts.onDelete,
save: !!opts.onSave,
close: !!opts.onClose,
});
Expand Down Expand Up @@ -122,6 +124,57 @@ class ViewerPanel {
toolbar.flashCopyContent();
});
}

if (toolbar.formatBtn) {
toolbar.formatBtn.addEventListener('click', () => this._format());
}

if (toolbar.deleteBtn && opts.onDelete) {
toolbar.deleteBtn.addEventListener('click', () => this._delete());
}
}

_format() {
if (!this.editorView || !this.filePath) return;
const ext = this.filePath.split('.').pop()?.toLowerCase();
const raw = this.getContent();
let formatted = null;
try {
if (ext === 'jsonl') {
// Pretty-print each JSON line, separate with --- to preserve line semantics
const lines = raw.split('\n').filter(l => l.trim().length > 0);
formatted = lines.map(l => JSON.stringify(JSON.parse(l), null, 2)).join('\n---\n');
} else {
// Default: treat as JSON
formatted = JSON.stringify(JSON.parse(raw), null, 2);
}
} catch (err) {
window.flashButtonText?.(this.toolbar.formatBtn, '!', 1200);
return;
}
if (formatted === raw) return;
this.editorView.dispatch({
changes: { from: 0, to: this.editorView.state.doc.length, insert: formatted },
});
window.flashButtonText?.(this.toolbar.formatBtn, '✓', 800);
}

async _delete() {
if (!this.opts.onDelete || !this.filePath) return;
// Confirm via native-ish browser confirm
const name = this.filePath.split('/').pop();
if (!window.confirm(`Delete "${name}"?\n\nThis cannot be undone.`)) return;
try {
const result = await this.opts.onDelete(this.filePath);
if (result && result.ok !== false) {
// Close panel and trigger refresh through onClose
if (this.opts.onClose) this.opts.onClose();
} else {
window.alert(`Delete failed: ${result?.error || 'unknown error'}`);
}
} catch (err) {
window.alert(`Delete failed: ${err.message}`);
}
}

/**
Expand All @@ -135,11 +188,16 @@ class ViewerPanel {
this.toolbar.setPath(filePath);

const isMd = this._isMarkdown(filePath);
const isJsonish = this._isJsonish(filePath);

// Show/hide preview button based on file type
if (this.toolbar.previewBtn) {
this.toolbar.previewBtn.style.display = isMd ? '' : 'none';
}
// Format button: only for .json / .jsonl
if (this.toolbar.formatBtn) {
this.toolbar.formatBtn.style.display = isJsonish ? '' : 'none';
}

// Save preview preference before resetting
const wantPreview = isMd && this.opts.storageKey && localStorage.getItem(this.opts.storageKey) === 'true';
Expand Down Expand Up @@ -295,6 +353,12 @@ class ViewerPanel {
const ext = filePath.split('.').pop()?.toLowerCase();
return ext === 'md' || ext === 'mdx';
}

_isJsonish(filePath) {
if (!filePath) return false;
const ext = filePath.split('.').pop()?.toLowerCase();
return ext === 'json' || ext === 'jsonl';
}
}

window.ViewerPanel = ViewerPanel;
25 changes: 25 additions & 0 deletions public/viewer-toolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ const GOTO_LINE_ICON = '<svg stroke="currentColor" fill="none" stroke-width="2"

const CLOSE_ICON = '<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" width="14" height="14" xmlns="http://www.w3.org/2000/svg"><path d="M400 145.49 366.51 112 256 222.51 145.49 112 112 145.49 222.51 256 112 366.51 145.49 400 256 289.49 366.51 400 400 366.51 289.49 256 400 145.49z"></path></svg>';

const FORMAT_ICON = '<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" width="14" height="14" xmlns="http://www.w3.org/2000/svg"><path d="M4 6h16"/><path d="M4 12h10"/><path d="M4 18h13"/><path d="m17 8 4 4-4 4"/></svg>';

const DELETE_ICON = '<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" width="14" height="14" xmlns="http://www.w3.org/2000/svg"><path d="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>';

/**
* Flash a button with a brief color change to indicate success.
* For icon buttons: flashes green. For text buttons: replaces text temporarily.
Expand Down Expand Up @@ -160,6 +164,25 @@ function createViewerToolbar(opts = {}) {
controlsEl.appendChild(gotoLineBtn);
}

let formatBtn = null;
if (opts.format) {
formatBtn = document.createElement('button');
formatBtn.className = 'fp-toolbar-btn fp-icon-btn';
formatBtn.title = 'Pretty-print JSON / JSONL';
formatBtn.innerHTML = FORMAT_ICON;
formatBtn.style.display = 'none'; // Shown by ViewerPanel only when content is parseable
controlsEl.appendChild(formatBtn);
}

let deleteBtn = null;
if (opts.delete) {
deleteBtn = document.createElement('button');
deleteBtn.className = 'fp-toolbar-btn fp-icon-btn fp-delete-btn';
deleteBtn.title = 'Delete file';
deleteBtn.innerHTML = DELETE_ICON;
controlsEl.appendChild(deleteBtn);
}

let saveBtn = null;
if (opts.save) {
saveBtn = document.createElement('button');
Expand Down Expand Up @@ -193,6 +216,8 @@ function createViewerToolbar(opts = {}) {
copyContentBtn,
diffToggleBtn,
gotoLineBtn,
formatBtn,
deleteBtn,

setTitle(text) { titleEl.textContent = text; },
setPath(text) { pathEl.textContent = text; },
Expand Down
Loading