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

View file

@ -176,9 +176,9 @@
// ── setActiveGroup: fire-and-forget RPC ─────────────────────── // ── setActiveGroup: fire-and-forget RPC ───────────────────────
function setActiveGroup(id: string | undefined) { function setActiveGroup(id: string | undefined) {
if (!id) return; if (!id) return;
console.log('[DEBUG] setActiveGroup:', id);
activeGroupId = id; activeGroupId = id;
// Fire and forget — don't let RPC failures block UI // NO RPC — pure local state change for debugging
appRpc?.request["settings.set"]?.({ key: 'active_group', value: id })?.catch?.(() => {});
} }
// ── Window controls ──────────────────────────────────────────── // ── Window controls ────────────────────────────────────────────
@ -263,6 +263,30 @@
function fmtTokens(n: number): string { return n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(n); } function fmtTokens(n: number): string { return n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(n); }
function fmtCost(n: number): string { return `$${n.toFixed(3)}`; } 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 ─────────────────────────────────────────────────────── // ── Init ───────────────────────────────────────────────────────
onMount(() => { onMount(() => {
themeStore.initTheme(appRpc).catch(console.error); themeStore.initTheme(appRpc).catch(console.error);
@ -309,7 +333,7 @@
<aside class="sidebar" role="navigation" aria-label="Primary navigation"> <aside class="sidebar" role="navigation" aria-label="Primary navigation">
<!-- AGOR vertical title --> <!-- AGOR vertical title -->
<!-- svelte-ignore a11y_no_static_element_interactions --> <!-- 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 --> <!-- Group icons — numbered circles -->
<div class="sidebar-groups" role="list" aria-label="Project groups"> <div class="sidebar-groups" role="list" aria-label="Project groups">
@ -350,83 +374,38 @@
<main class="workspace"> <main class="workspace">
<!-- Project grid --> <!-- Project grid -->
<div class="project-grid" role="list" aria-label="{activeGroup?.name ?? 'Projects'} projects"> <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}`)} <!-- ALL projects rendered always with display:none/flex toggling.
{#if row.type === 'standalone'} WebKitGTK corrupts hit-test tree when DOM nodes are added/removed during click events. -->
<div role="listitem"> {#each PROJECTS as project (project.id)}
<ProjectCard <div role="listitem" style:display={(project.groupId ?? 'dev') === activeGroupId ? 'flex' : 'none'}>
id={row.project.id} <ProjectCard
name={row.project.name} id={project.id}
cwd={row.project.cwd} name={project.name}
accent={row.project.accent} cwd={project.cwd}
status={row.project.status} accent={project.accent}
costUsd={row.project.costUsd} status={project.status}
tokens={row.project.tokens} costUsd={project.costUsd}
messages={row.project.messages} tokens={project.tokens}
provider={row.project.provider} messages={project.messages}
profile={row.project.profile} provider={project.provider}
model={row.project.model} profile={project.profile}
contextPct={row.project.contextPct} model={project.model}
burnRate={row.project.burnRate} contextPct={project.contextPct}
{blinkVisible} burnRate={project.burnRate}
clonesAtMax={cloneCountForProject(row.project.id) >= 3} {blinkVisible}
onClone={handleClone} clonesAtMax={cloneCountForProject(project.id) >= 3}
/> onClone={handleClone}
</div> worktreeBranch={project.worktreeBranch}
{:else} cloneOf={project.cloneOf}
<div class="clone-group-row" role="listitem" aria-label="Project group: {row.parent.name}"> />
<ProjectCard </div>
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}
{/each} {/each}
{#if filteredProjects.length === 0} <!-- Empty group — always in DOM, visibility toggled -->
<div class="empty-group" role="listitem"> <div class="empty-group" role="listitem"
<p class="empty-group-text">No projects in {activeGroup?.name ?? 'this group'}</p> style:display={filteredProjects.length === 0 ? 'flex' : 'none'}>
</div> <p class="empty-group-text">No projects in {activeGroup?.name ?? 'this group'}</p>
{/if} </div>
</div> </div>
</main> </main>
@ -511,6 +490,13 @@
<kbd class="palette-hint" title="Open command palette">Ctrl+K</kbd> <kbd class="palette-hint" title="Open command palette">Ctrl+K</kbd>
</footer> </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> <style>
:global(body) { overflow: hidden; } :global(body) { overflow: hidden; }
:global(#app) { display: flex; flex-direction: column; height: 100vh; } :global(#app) { display: flex; flex-direction: column; height: 100vh; }

View file

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