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:
Hibryda 2026-03-22 02:30:09 +01:00
parent 8e756d3523
commit 1cd4558740
28 changed files with 1342 additions and 1164 deletions

View file

@ -16,35 +16,34 @@
let { open, notifications, onClear, onClose }: Props = $props();
</script>
{#if open}
<!-- Backdrop to close on outside click -->
<div class="notif-backdrop" role="presentation" onclick={onClose}></div>
<!-- Fix #11: display toggle instead of {#if} -->
<!-- Backdrop to close on outside click -->
<div class="notif-backdrop" style:display={open ? 'block' : 'none'} role="presentation" onclick={onClose}></div>
<div class="notif-drawer" role="complementary" aria-label="Notification history">
<div class="drawer-header">
<span class="drawer-title">Notifications</span>
<button class="clear-btn" onclick={onClear} aria-label="Clear all notifications">
Clear all
</button>
</div>
<div class="drawer-body">
{#each notifications as notif (notif.id)}
<div class="notif-item" class:success={notif.type === 'success'} class:warning={notif.type === 'warning'} class:error={notif.type === 'error'}>
<span class="notif-dot" class:success={notif.type === 'success'} class:warning={notif.type === 'warning'} class:error={notif.type === 'error'} aria-hidden="true"></span>
<div class="notif-content">
<span class="notif-text">{notif.message}</span>
<span class="notif-time">{notif.time}</span>
</div>
</div>
{/each}
{#if notifications.length === 0}
<div class="notif-empty">No notifications</div>
{/if}
</div>
<div class="notif-drawer" style:display={open ? 'flex' : 'none'} role="complementary" aria-label="Notification history">
<div class="drawer-header">
<span class="drawer-title">Notifications</span>
<button class="clear-btn" onclick={onClear} aria-label="Clear all notifications">
Clear all
</button>
</div>
{/if}
<div class="drawer-body">
{#each notifications as notif (notif.id)}
<div class="notif-item" class:success={notif.type === 'success'} class:warning={notif.type === 'warning'} class:error={notif.type === 'error'}>
<span class="notif-dot" class:success={notif.type === 'success'} class:warning={notif.type === 'warning'} class:error={notif.type === 'error'} aria-hidden="true"></span>
<div class="notif-content">
<span class="notif-text">{notif.message}</span>
<span class="notif-time">{notif.time}</span>
</div>
</div>
{/each}
{#if notifications.length === 0}
<div class="notif-empty">No notifications</div>
{/if}
</div>
</div>
<style>
.notif-backdrop {