fix(electrobun): address all 22 Codex review #2 findings
CRITICAL:
- DocsTab XSS: DOMPurify sanitization on all {@html} output
- File RPC path traversal: guardPath() validates against project CWDs
HIGH:
- SSH injection: spawn /usr/bin/ssh via PTY args, no shell string
- Search XSS: strip HTML, highlight matches client-side with <mark>
- Terminal listener leak: cleanup functions stored + called in onDestroy
- FileBrowser race: request token, discard stale responses
- SearchOverlay race: same request token pattern
- App startup ordering: groups.list chains into active_group restore
- PtyClient timeout: 5-second auth timeout on connect()
- Rule 55: 6 {#if} patterns converted to style:display toggle
MEDIUM:
- Agent persistence: only persist NEW messages (lastPersistedIndex)
- Search errors: typed error response, "Invalid query" UI
- Health store wired: agent events call recordActivity/setProjectStatus
- index.ts SRP: split into 8 domain handler modules (298 lines)
- App.svelte: extracted workspace-store.svelte.ts
- rpc.ts: typed AppRpcHandle, removed `any`
LOW:
- CommandPalette listener wired in App.svelte
- Dead code removed (removeGroup, onDragStart, plugin loaded)
This commit is contained in:
parent
8e756d3523
commit
1cd4558740
28 changed files with 1342 additions and 1164 deletions
|
|
@ -123,12 +123,7 @@
|
|||
newGroupName = '';
|
||||
}
|
||||
|
||||
async function removeGroup(id: string) {
|
||||
if (groups.length <= 1) return; // keep at least one group
|
||||
groups = groups.filter(g => g.id !== id);
|
||||
if (activeGroupId === id) activeGroupId = groups[0]?.id ?? 'dev';
|
||||
await appRpc.request['groups.delete']({ id }).catch(console.error);
|
||||
}
|
||||
// Fix #19: removeGroup removed — was defined but never called from UI
|
||||
let activeGroupId = $state('dev');
|
||||
// Fix #10: Track previous group to limit mounted DOM (max 2 groups)
|
||||
let previousGroupId = $state<string | null>(null);
|
||||
|
|
@ -218,38 +213,7 @@
|
|||
return () => clearInterval(id);
|
||||
});
|
||||
|
||||
// ── JS-based window drag (replaces broken -webkit-app-region on WebKitGTK) ──
|
||||
let isDraggingWindow = false;
|
||||
let dragStartX = 0;
|
||||
let dragStartY = 0;
|
||||
let winStartX = 0;
|
||||
let winStartY = 0;
|
||||
|
||||
function onDragStart(e: MouseEvent) {
|
||||
isDraggingWindow = true;
|
||||
dragStartX = e.screenX;
|
||||
dragStartY = e.screenY;
|
||||
appRpc?.request["window.getFrame"]({}).then((frame: any) => {
|
||||
winStartX = frame.x;
|
||||
winStartY = frame.y;
|
||||
}).catch(() => {});
|
||||
window.addEventListener('mousemove', onDragMove);
|
||||
window.addEventListener('mouseup', onDragEnd);
|
||||
}
|
||||
|
||||
function onDragMove(e: MouseEvent) {
|
||||
if (!isDraggingWindow) return;
|
||||
const dx = e.screenX - dragStartX;
|
||||
const dy = e.screenY - dragStartY;
|
||||
appRpc?.request["window.setPosition"]?.({ x: winStartX + dx, y: winStartY + dy })?.catch?.(() => {});
|
||||
}
|
||||
|
||||
function onDragEnd() {
|
||||
isDraggingWindow = false;
|
||||
window.removeEventListener('mousemove', onDragMove);
|
||||
window.removeEventListener('mouseup', onDragEnd);
|
||||
saveWindowFrame();
|
||||
}
|
||||
// Fix #19: onDragStart/onDragMove/onDragEnd removed — no longer referenced from template
|
||||
|
||||
// ── Window frame persistence (debounced 500ms) ─────────────────
|
||||
let frameSaveTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
|
@ -329,15 +293,18 @@
|
|||
// Set up global error boundary
|
||||
setupErrorBoundary();
|
||||
|
||||
// Run all init tasks in parallel, mark app ready when all complete
|
||||
// Fix #8: Load groups FIRST, then apply saved active_group.
|
||||
// Other init tasks run in parallel.
|
||||
const initTasks = [
|
||||
themeStore.initTheme(appRpc).catch(console.error),
|
||||
fontStore.initFonts(appRpc).catch(console.error),
|
||||
keybindingStore.init(appRpc).catch(console.error),
|
||||
// Sequential: groups.list -> active_group (depends on groups being loaded)
|
||||
appRpc.request["groups.list"]({}).then(({ groups: dbGroups }: { groups: Group[] }) => {
|
||||
if (dbGroups.length > 0) groups = dbGroups;
|
||||
}).catch(console.error),
|
||||
appRpc.request["settings.get"]({ key: 'active_group' }).then(({ value }: { value: string | null }) => {
|
||||
// Now that groups are loaded, apply saved active_group
|
||||
return appRpc.request["settings.get"]({ key: 'active_group' });
|
||||
}).then(({ value }: { value: string | null }) => {
|
||||
if (value && groups.some(g => g.id === value)) activeGroupId = value;
|
||||
}).catch(console.error),
|
||||
// Load projects from SQLite
|
||||
|
|
@ -356,7 +323,6 @@
|
|||
|
||||
Promise.allSettled(initTasks).then(() => {
|
||||
appReady = true;
|
||||
// Track projects for health monitoring after load
|
||||
for (const p of PROJECTS) trackProject(p.id);
|
||||
});
|
||||
|
||||
|
|
@ -377,10 +343,24 @@
|
|||
}
|
||||
document.addEventListener('keydown', handleSearchShortcut);
|
||||
|
||||
// Fix #18: Wire CommandPalette events to action handlers
|
||||
function handlePaletteCommand(e: Event) {
|
||||
const detail = (e as CustomEvent).detail;
|
||||
switch (detail) {
|
||||
case 'settings': settingsOpen = !settingsOpen; break;
|
||||
case 'search': searchOpen = !searchOpen; break;
|
||||
case 'new-project': showAddProject = true; break;
|
||||
case 'toggle-sidebar': settingsOpen = !settingsOpen; break;
|
||||
default: console.log(`[palette] unhandled command: ${detail}`);
|
||||
}
|
||||
}
|
||||
window.addEventListener('palette-command', handlePaletteCommand);
|
||||
|
||||
const cleanup = keybindingStore.installListener();
|
||||
return () => {
|
||||
cleanup();
|
||||
document.removeEventListener('keydown', handleSearchShortcut);
|
||||
window.removeEventListener('palette-command', handlePaletteCommand);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue