fix(electrobun): WebKitGTK click-lock — use display toggle instead of DOM add/remove

This commit is contained in:
Hibryda 2026-03-20 07:36:26 +01:00
parent 3a876329c5
commit 95f1f8208f
5 changed files with 94 additions and 109 deletions

View file

@ -4,8 +4,8 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Svelte App</title>
<script type="module" crossorigin src="/assets/index-DyX_7OUK.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CYr5v29h.css">
<script type="module" crossorigin src="/assets/index-Cv8GjxAz.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-6BCrvHEB.css">
</head>
<body>
<div id="app"></div>

View file

@ -176,9 +176,9 @@
// ── setActiveGroup: fire-and-forget RPC ───────────────────────
function setActiveGroup(id: string | undefined) {
if (!id) return;
console.log('[DEBUG] setActiveGroup:', id);
activeGroupId = id;
// Fire and forget — don't let RPC failures block UI
appRpc?.request["settings.set"]?.({ key: 'active_group', value: id })?.catch?.(() => {});
// NO RPC — pure local state change for debugging
}
// ── Window controls ────────────────────────────────────────────
@ -263,6 +263,30 @@
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 ─────────────────────
let debugLog = $state<string[]>([]);
$effect(() => {
function debugClick(e: MouseEvent) {
const el = e.target as HTMLElement;
const tag = el?.tagName;
const cls = (el?.className?.toString?.() ?? '').slice(0, 40);
const elAtPoint = document.elementFromPoint(e.clientX, e.clientY);
let line = `CLICK ${tag}.${cls} @(${e.clientX},${e.clientY})`;
if (elAtPoint && elAtPoint !== el) {
const overTag = (elAtPoint as HTMLElement).tagName;
const overCls = ((elAtPoint as HTMLElement).className?.toString?.() ?? '').slice(0, 40);
line += ` ⚠OVERLAY: ${overTag}.${overCls}`;
}
debugLog = [...debugLog.slice(-8), line];
}
document.addEventListener('click', debugClick, true);
document.addEventListener('mousedown', (e) => {
const el = e.target as HTMLElement;
debugLog = [...debugLog.slice(-8), `DOWN ${el?.tagName}.${(el?.className?.toString?.() ?? '').slice(0, 40)}`];
}, true);
});
// ── Init ───────────────────────────────────────────────────────
onMount(() => {
themeStore.initTheme(appRpc).catch(console.error);
@ -309,7 +333,7 @@
<aside class="sidebar" role="navigation" aria-label="Primary navigation">
<!-- AGOR vertical title -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="agor-title" aria-hidden="true" onmousedown={onDragStart}>AGOR</div>
<div class="agor-title" aria-hidden="true">AGOR</div>
<!-- Group icons — numbered circles -->
<div class="sidebar-groups" role="list" aria-label="Project groups">
@ -350,83 +374,38 @@
<main class="workspace">
<!-- Project grid -->
<div class="project-grid" role="list" aria-label="{activeGroup?.name ?? 'Projects'} projects">
{#each gridRows() as row (row.type === 'standalone' ? row.project.id : `cg-${row.parent.id}`)}
{#if row.type === 'standalone'}
<div role="listitem">
<ProjectCard
id={row.project.id}
name={row.project.name}
cwd={row.project.cwd}
accent={row.project.accent}
status={row.project.status}
costUsd={row.project.costUsd}
tokens={row.project.tokens}
messages={row.project.messages}
provider={row.project.provider}
profile={row.project.profile}
model={row.project.model}
contextPct={row.project.contextPct}
burnRate={row.project.burnRate}
{blinkVisible}
clonesAtMax={cloneCountForProject(row.project.id) >= 3}
onClone={handleClone}
/>
</div>
{:else}
<div class="clone-group-row" role="listitem" aria-label="Project group: {row.parent.name}">
<ProjectCard
id={row.parent.id}
name={row.parent.name}
cwd={row.parent.cwd}
accent={row.parent.accent}
status={row.parent.status}
costUsd={row.parent.costUsd}
tokens={row.parent.tokens}
messages={row.parent.messages}
provider={row.parent.provider}
profile={row.parent.profile}
model={row.parent.model}
contextPct={row.parent.contextPct}
burnRate={row.parent.burnRate}
{blinkVisible}
clonesAtMax={row.clones.length >= 3}
onClone={handleClone}
/>
{#each row.clones as clone (clone.id)}
<div class="chain-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/>
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>
</svg>
</div>
<ProjectCard
id={clone.id}
name={clone.name}
cwd={clone.cwd}
accent={clone.accent ?? row.parent.accent}
status={clone.status}
costUsd={clone.costUsd}
tokens={clone.tokens}
messages={clone.messages}
provider={clone.provider}
profile={clone.profile}
model={clone.model}
contextPct={clone.contextPct}
burnRate={clone.burnRate}
{blinkVisible}
worktreeBranch={clone.worktreeBranch}
cloneOf={clone.cloneOf}
/>
{/each}
</div>
{/if}
<!-- ALL projects rendered always with display:none/flex toggling.
WebKitGTK corrupts hit-test tree when DOM nodes are added/removed during click events. -->
{#each PROJECTS as project (project.id)}
<div role="listitem" style:display={(project.groupId ?? 'dev') === activeGroupId ? 'flex' : 'none'}>
<ProjectCard
id={project.id}
name={project.name}
cwd={project.cwd}
accent={project.accent}
status={project.status}
costUsd={project.costUsd}
tokens={project.tokens}
messages={project.messages}
provider={project.provider}
profile={project.profile}
model={project.model}
contextPct={project.contextPct}
burnRate={project.burnRate}
{blinkVisible}
clonesAtMax={cloneCountForProject(project.id) >= 3}
onClone={handleClone}
worktreeBranch={project.worktreeBranch}
cloneOf={project.cloneOf}
/>
</div>
{/each}
{#if filteredProjects.length === 0}
<div class="empty-group" role="listitem">
<p class="empty-group-text">No projects in {activeGroup?.name ?? 'this group'}</p>
</div>
{/if}
<!-- Empty group — always in DOM, visibility toggled -->
<div class="empty-group" role="listitem"
style:display={filteredProjects.length === 0 ? 'flex' : 'none'}>
<p class="empty-group-text">No projects in {activeGroup?.name ?? 'this group'}</p>
</div>
</div>
</main>
@ -511,6 +490,13 @@
<kbd class="palette-hint" title="Open command palette">Ctrl+K</kbd>
</footer>
<!-- DEBUG: visible click log -->
<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>
<style>
:global(body) { overflow: hidden; }
:global(#app) { display: flex; flex-direction: column; height: 100vh; }

View file

@ -115,18 +115,17 @@
</div>
</div>
<!-- Terminal panes: only rendered when expanded -->
{#if expanded}
<div class="term-panes">
{#each tabs as tab (tab.id)}
{#if mounted.has(tab.id)}
<div class="term-pane" style:display={activeTabId === tab.id ? 'flex' : 'none'}>
<Terminal sessionId={tab.id} />
</div>
{/if}
{/each}
</div>
{/if}
<!-- Terminal panes: always in DOM, display toggled.
WebKitGTK corrupts hit-test tree on DOM add/remove during click. -->
<div class="term-panes" style:display={expanded ? 'block' : 'none'}>
{#each tabs as tab (tab.id)}
{#if mounted.has(tab.id)}
<div class="term-pane" style:display={activeTabId === tab.id ? 'flex' : 'none'}>
<Terminal sessionId={tab.id} />
</div>
{/if}
{/each}
</div>
</div>
<style>