fix(electrobun): address all 20 Codex review findings
CRITICAL: - PTY leak: Terminal.svelte now calls pty.close on destroy, not just unsubscribe - Agent session cleanup: clearSession() removes done/error sessions, backend deletes after 60s grace period HIGH: - Clone branch passthrough: user's branch name flows through callback - Circular imports: extracted rpc.ts singleton, broke main.ts ↔ App.svelte cycle - Settings wired to runtime: Terminal reads cursor/scrollback from settings - Security disclaimer: added "prototype — not system keyring" notice - ThemeEditor: fixed basePalette → initialPalette reference MEDIUM: - Clone race: UUID suffix instead of count-based index - Silent failures: structured error returns from PTY handlers - WebKitGTK mount: only current + previous group mounted - Debug listeners: gated behind DEBUG, cleanup on destroy - NDJSON residual buffer parsed on process exit - Codex adapter: deduplicated tool_call/tool_result - extraEnv: rejects CLAUDE*/CODEX*/OLLAMA* keys - settings-db: runMigrations() with version tracking - active_group: persisted via settings.set LOW: - Removed dead demo code, unused variables - color-mix() fallbacks added
This commit is contained in:
parent
ef0183de7f
commit
29a3370e79
18 changed files with 331 additions and 114 deletions
|
|
@ -8,7 +8,7 @@
|
|||
import { themeStore } from './theme-store.svelte.ts';
|
||||
import { fontStore } from './font-store.svelte.ts';
|
||||
import { keybindingStore } from './keybinding-store.svelte.ts';
|
||||
import { appRpc } from './main.ts';
|
||||
import { appRpc } from './rpc.ts';
|
||||
|
||||
// ── Types ─────────────────────────────────────────────────────
|
||||
type AgentStatus = 'running' | 'idle' | 'stalled';
|
||||
|
|
@ -104,6 +104,9 @@
|
|||
{ id: 'research', name: 'Research', icon: '🔬', position: 3 },
|
||||
]);
|
||||
let activeGroupId = $state('dev');
|
||||
// Fix #10: Track previous group to limit mounted DOM (max 2 groups)
|
||||
let previousGroupId = $state<string | null>(null);
|
||||
let mountedGroupIds = $derived(new Set([activeGroupId, ...(previousGroupId ? [previousGroupId] : [])]));
|
||||
|
||||
// ── Filtered projects for active group ────────────────────────
|
||||
let activeGroup = $derived(groups.find(g => g.id === activeGroupId) ?? groups[0]);
|
||||
|
|
@ -111,39 +114,15 @@
|
|||
PROJECTS.filter(p => (p.groupId ?? 'dev') === activeGroupId)
|
||||
);
|
||||
|
||||
// Group projects into: top-level cards + clone groups
|
||||
interface ProjectRow { type: 'standalone'; project: Project; }
|
||||
interface CloneGroupRow { type: 'clone-group'; parent: Project; clones: Project[]; }
|
||||
type GridRow = ProjectRow | CloneGroupRow;
|
||||
|
||||
let gridRows = $derived((): GridRow[] => {
|
||||
const standalone: GridRow[] = [];
|
||||
const cloneParentIds = new Set(
|
||||
filteredProjects.filter(p => p.cloneOf).map(p => p.cloneOf!)
|
||||
);
|
||||
for (const p of filteredProjects) {
|
||||
if (p.cloneOf) continue;
|
||||
if (cloneParentIds.has(p.id)) {
|
||||
const clones = filteredProjects
|
||||
.filter(c => c.cloneOf === p.id)
|
||||
.sort((a, b) => (a.cloneIndex ?? 0) - (b.cloneIndex ?? 0));
|
||||
standalone.push({ type: 'clone-group', parent: p, clones });
|
||||
} else {
|
||||
standalone.push({ type: 'standalone', project: p });
|
||||
}
|
||||
}
|
||||
return standalone;
|
||||
});
|
||||
|
||||
// ── Clone helpers ──────────────────────────────────────────────
|
||||
function cloneCountForProject(projectId: string): number {
|
||||
return PROJECTS.filter(p => p.cloneOf === projectId).length;
|
||||
}
|
||||
|
||||
function handleClone(projectId: string) {
|
||||
function handleClone(projectId: string, branch: string) {
|
||||
const source = PROJECTS.find(p => p.id === projectId);
|
||||
if (!source) return;
|
||||
const branchName = `feature/clone-${Date.now()}`;
|
||||
const branchName = branch || `feature/clone-${Date.now()}`;
|
||||
appRpc.request["project.clone"]({ projectId, branchName }).then((result) => {
|
||||
if (result.ok && result.project) {
|
||||
const cloneConfig = JSON.parse(result.project.config) as Project;
|
||||
|
|
@ -176,9 +155,13 @@
|
|||
// ── setActiveGroup: fire-and-forget RPC ───────────────────────
|
||||
function setActiveGroup(id: string | undefined) {
|
||||
if (!id) return;
|
||||
console.log('[DEBUG] setActiveGroup:', id);
|
||||
// Fix #10: Track previous group for DOM mount limit
|
||||
if (activeGroupId !== id) {
|
||||
previousGroupId = activeGroupId;
|
||||
}
|
||||
activeGroupId = id;
|
||||
// NO RPC — pure local state change for debugging
|
||||
// Fix #16: Persist active_group selection
|
||||
appRpc.request["settings.set"]({ key: 'active_group', value: id }).catch(console.error);
|
||||
}
|
||||
|
||||
// ── Window controls ────────────────────────────────────────────
|
||||
|
|
@ -263,10 +246,12 @@
|
|||
function fmtTokens(n: number): string { return n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(n); }
|
||||
function fmtCost(n: number): string { return `$${n.toFixed(3)}`; }
|
||||
|
||||
// ── DEBUG: Visual click diagnostics overlay ─────────────────────
|
||||
// ── DEBUG: Visual click diagnostics overlay (gated behind DEBUG env) ────
|
||||
const DEBUG_ENABLED = typeof window !== 'undefined' && new URLSearchParams(window.location.search).has('debug');
|
||||
let debugLog = $state<string[]>([]);
|
||||
|
||||
$effect(() => {
|
||||
if (!DEBUG_ENABLED) return;
|
||||
function debugClick(e: MouseEvent) {
|
||||
const el = e.target as HTMLElement;
|
||||
const tag = el?.tagName;
|
||||
|
|
@ -276,15 +261,20 @@
|
|||
if (elAtPoint && elAtPoint !== el) {
|
||||
const overTag = (elAtPoint as HTMLElement).tagName;
|
||||
const overCls = ((elAtPoint as HTMLElement).className?.toString?.() ?? '').slice(0, 40);
|
||||
line += ` ⚠️OVERLAY: ${overTag}.${overCls}`;
|
||||
line += ` OVERLAY: ${overTag}.${overCls}`;
|
||||
}
|
||||
debugLog = [...debugLog.slice(-8), line];
|
||||
}
|
||||
document.addEventListener('click', debugClick, true);
|
||||
document.addEventListener('mousedown', (e) => {
|
||||
function debugDown(e: MouseEvent) {
|
||||
const el = e.target as HTMLElement;
|
||||
debugLog = [...debugLog.slice(-8), `DOWN ${el?.tagName}.${(el?.className?.toString?.() ?? '').slice(0, 40)}`];
|
||||
}, true);
|
||||
}
|
||||
document.addEventListener('click', debugClick, true);
|
||||
document.addEventListener('mousedown', debugDown, true);
|
||||
return () => {
|
||||
document.removeEventListener('click', debugClick, true);
|
||||
document.removeEventListener('mousedown', debugDown, true);
|
||||
};
|
||||
});
|
||||
|
||||
// ── Init ───────────────────────────────────────────────────────
|
||||
|
|
@ -327,7 +317,6 @@
|
|||
<div
|
||||
class="app-shell"
|
||||
role="presentation"
|
||||
onresize={saveWindowFrame}
|
||||
>
|
||||
<!-- Left sidebar icon rail -->
|
||||
<aside class="sidebar" role="navigation" aria-label="Primary navigation">
|
||||
|
|
@ -374,9 +363,10 @@
|
|||
<main class="workspace">
|
||||
<!-- Project grid -->
|
||||
<div class="project-grid" role="list" aria-label="{activeGroup?.name ?? 'Projects'} projects">
|
||||
<!-- ALL projects rendered always with display:none/flex toggling.
|
||||
<!-- Fix #10: Only mount projects in active + previous group (max 2 groups).
|
||||
WebKitGTK corrupts hit-test tree when DOM nodes are added/removed during click events. -->
|
||||
{#each PROJECTS as project (project.id)}
|
||||
{#if mountedGroupIds.has(project.groupId ?? 'dev')}
|
||||
<div role="listitem" style:display={(project.groupId ?? 'dev') === activeGroupId ? 'flex' : 'none'}>
|
||||
<ProjectCard
|
||||
id={project.id}
|
||||
|
|
@ -395,6 +385,7 @@
|
|||
cloneOf={project.cloneOf}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
<!-- Empty group — always in DOM, visibility toggled -->
|
||||
|
|
@ -486,12 +477,14 @@
|
|||
<kbd class="palette-hint" title="Open command palette">Ctrl+K</kbd>
|
||||
</footer>
|
||||
|
||||
<!-- DEBUG: visible click log -->
|
||||
{#if DEBUG_ENABLED && debugLog.length > 0}
|
||||
<!-- DEBUG: visible click log (enable with ?debug URL param) -->
|
||||
<div style="position:fixed;bottom:0;left:0;right:0;background:#000;color:#0f0;font-family:monospace;font-size:10px;padding:4px 8px;z-index:9999;max-height:100px;overflow-y:auto;pointer-events:none;">
|
||||
{#each debugLog as line}
|
||||
<div>{line}</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
:global(body) { overflow: hidden; }
|
||||
|
|
@ -642,28 +635,6 @@
|
|||
.project-grid::-webkit-scrollbar-track { background: transparent; }
|
||||
.project-grid::-webkit-scrollbar-thumb { background: var(--ctp-surface1); border-radius: 0.25rem; }
|
||||
|
||||
.clone-group-row {
|
||||
grid-column: 1 / -1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0;
|
||||
align-items: stretch;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.clone-group-row :global(.project-card) { flex: 1; min-width: 0; }
|
||||
|
||||
.chain-icon {
|
||||
flex-shrink: 0;
|
||||
width: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--ctp-surface1);
|
||||
}
|
||||
|
||||
.chain-icon svg { width: 1rem; height: 1rem; }
|
||||
|
||||
.empty-group {
|
||||
grid-column: 1 / -1;
|
||||
display: flex;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue