Commit b5cb8f6f4 for llama.cpp
commit b5cb8f6f470a3a4d6f329bbb050e9a34fde6638d
Author: Aleksander Grygier <aleksander.grygier@gmail.com>
Date: Tue Nov 10 22:44:23 2026 +0100
ui: keep a hidden sidebar from intercepting clicks again (#32035)
The hidden desktop strip relied on inherited `visibility: hidden` to keep
its content from catching clicks. Since the sidebar's sections fold
(#31936), an open section's CollapsibleRegion sets `visibility: visible`
again, and its rows' context-menu triggers carry an inline
`pointer-events: auto`, so they caught clicks meant for the page.
Make the hidden strip's content inert, which no descendant can undo, and
add a client test that hit-tests the hidden sidebar's area.
Fixes #31971
Assisted-by: Claude (Opus 5.5 Medium)
diff --git a/tools/ui/src/lib/components/app/navigation/SidebarNavigation/SidebarNavigation.svelte b/tools/ui/src/lib/components/app/navigation/SidebarNavigation/SidebarNavigation.svelte
index e261ab064..3a195bff5 100644
--- a/tools/ui/src/lib/components/app/navigation/SidebarNavigation/SidebarNavigation.svelte
+++ b/tools/ui/src/lib/components/app/navigation/SidebarNavigation/SidebarNavigation.svelte
@@ -648,12 +648,16 @@
{/if}
</div>
+ <!-- the hidden strip's content goes inert, not only invisible: descendants can opt
+ back into visibility (an open CollapsibleRegion) and pointer events (the inline
+ style of a context-menu trigger), and would catch clicks meant for the page -->
<div
in:fade={{ duration: SIDEBAR_CONTENT_FADE_MS }}
out:fade={{ duration: SIDEBAR_CONTENT_FADE_MS }}
class="mt-2 flex min-h-0 flex-1 flex-col gap-7 max-md:mt-6 md:gap-1 md:transition-opacity md:duration-150 md:ease-out {isStripHidden
? 'md:opacity-0 md:invisible'
: ''}"
+ inert={isStripHidden}
>
<!-- a phone has no footer: its items join these -->
<SidebarNavigationActions
diff --git a/tools/ui/tests/client/components/SidebarNavigationHarness.svelte b/tools/ui/tests/client/components/SidebarNavigationHarness.svelte
new file mode 100644
index 000000000..08721f21b
--- /dev/null
+++ b/tools/ui/tests/client/components/SidebarNavigationHarness.svelte
@@ -0,0 +1,9 @@
+<script lang="ts">
+ import SidebarNavigation from '$lib/components/app/navigation/SidebarNavigation/SidebarNavigation.svelte';
+ import * as Tooltip from '$lib/components/ui/tooltip';
+</script>
+
+<!-- the sidebar with the tooltip provider +layout.svelte gives it -->
+<Tooltip.Provider>
+ <SidebarNavigation />
+</Tooltip.Provider>
diff --git a/tools/ui/tests/client/sidebar-hidden-clicks.svelte.test.ts b/tools/ui/tests/client/sidebar-hidden-clicks.svelte.test.ts
new file mode 100644
index 000000000..4de1ac86d
--- /dev/null
+++ b/tools/ui/tests/client/sidebar-hidden-clicks.svelte.test.ts
@@ -0,0 +1,66 @@
+// A hidden desktop sidebar keeps its full width in the layout, floated under the page's
+// left edge, so whatever it still lets the pointer hit there steals the page's clicks
+// (#31971). Hiding it by inherited `visibility` alone is not enough: an open section's
+// CollapsibleRegion sets `visibility: visible` again, and bits-ui's context-menu triggers
+// put `pointer-events: auto` inline on the rows.
+
+import '../../src/app.css';
+import SidebarNavigationHarness from './components/SidebarNavigationHarness.svelte';
+import { uiStore } from '$lib/stores';
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
+import { page } from 'vitest/browser';
+import { render } from 'vitest-browser-svelte';
+
+const GRID_STEP_PX = 8;
+
+/** Elements of the sidebar the pointer lands on over its box, the expand toggle aside. */
+function hitTargetsInSidebar(aside: HTMLElement): Element[] {
+ const toggle = aside.querySelector('[aria-label="Expand navigation"]');
+ const box = aside.getBoundingClientRect();
+ const hits = new Set<Element>();
+
+ for (let x = box.left + 1; x < box.right; x += GRID_STEP_PX) {
+ for (let y = box.top + 1; y < box.bottom; y += GRID_STEP_PX) {
+ const el = document.elementFromPoint(x, y);
+
+ if (el && aside.contains(el) && !toggle?.contains(el)) hits.add(el);
+ }
+ }
+
+ return [...hits];
+}
+
+describe('hidden sidebar', () => {
+ let wasExpanded: boolean;
+
+ beforeEach(async () => {
+ await page.viewport(1280, 900);
+ wasExpanded = uiStore.isSidebarExpanded;
+ });
+
+ afterEach(() => {
+ uiStore.isSidebarExpanded = wasExpanded;
+ });
+
+ it('lets clicks over its strip through to the page', async () => {
+ uiStore.isSidebarExpanded = false;
+ render(SidebarNavigationHarness);
+
+ const aside = await vi.waitFor(() => {
+ const el = document.querySelector<HTMLElement>('aside');
+
+ if (!el) throw new Error('sidebar not rendered');
+
+ return el;
+ });
+
+ // an open section is what used to bring the rows back to the pointer
+ await vi.waitFor(() =>
+ expect(aside.querySelector('.collapsible-region-content')).not.toBeNull()
+ );
+ // its width settles over a transition; the strip's area is what counts
+ await vi.waitFor(() => expect(aside.getBoundingClientRect().width).toBeGreaterThan(48));
+
+ expect(hitTargetsInSidebar(aside).map((el) => el.outerHTML.slice(0, 120))).toEqual([]);
+ });
+});