diff --git a/packages/ui/src/components/chat/MarkdownRendererImpl.performance.test.tsx b/packages/ui/src/components/chat/MarkdownRendererImpl.performance.test.tsx
index 3e423f59..3151033c 100644
--- a/packages/ui/src/components/chat/MarkdownRendererImpl.performance.test.tsx
+++ b/packages/ui/src/components/chat/MarkdownRendererImpl.performance.test.tsx
@@ -445,6 +445,52 @@ describe('MarkdownRenderer DOM mount performance contract', () => {
clearDetachedMarkdownDomCache();
});
+ test('does not detach Markdown DOM that intersects the active selection', async () => {
+ clearDetachedMarkdownDomCache();
+ const content = 'selected content';
+ const host = document.createElement('div');
+ document.body.replaceChildren(host);
+ const root = createRoot(host);
+ await act(async () => {
+ root.render(
+ ,
+ );
+ await waitForSettledEffects();
+ });
+ const markdown = host.querySelector('[data-markdown-content]');
+ if (!markdown) throw new Error('Expected mounted Markdown content');
+ const originalGetSelection = window.getSelection;
+ Object.defineProperty(window, 'getSelection', {
+ configurable: true,
+ value: () => ({
+ rangeCount: 1,
+ isCollapsed: false,
+ getRangeAt: () => ({ intersectsNode: (node: Node) => node === markdown }),
+ }),
+ });
+
+ try {
+ await act(async () => root.unmount());
+ expect(detachedMarkdownDomCacheStats().entries).toBe(0);
+ } finally {
+ Object.defineProperty(window, 'getSelection', { configurable: true, value: originalGetSelection });
+ clearDetachedMarkdownDomCache();
+ }
+ });
+
test('defers and batches Mermaid controller initialization after Markdown mount', async () => {
const mounted = await mountFixture(fixtureWorkload.rendererCount);
const critical = mounted.counts;
diff --git a/packages/ui/src/components/chat/MarkdownRendererImpl.tsx b/packages/ui/src/components/chat/MarkdownRendererImpl.tsx
index 545ccdff..456afa5f 100644
--- a/packages/ui/src/components/chat/MarkdownRendererImpl.tsx
+++ b/packages/ui/src/components/chat/MarkdownRendererImpl.tsx
@@ -843,6 +843,8 @@ const useMorphdomMarkdown = ({
if (target.childNodes.length === 0 || shouldRefreshMermaidViewers(target)) return;
if (Array.from(target.children).some((block) => !block.hasAttribute('data-md-id'))) return;
if (target.querySelector('[data-md-copy-pending]')) return;
+ const selection = window.getSelection();
+ if (selection?.rangeCount && !selection.isCollapsed && selection.getRangeAt(0).intersectsNode(target)) return;
const openMenu = target.querySelector('[data-md-menu]:not(.hidden)');
const copiedButton = Array.from(target.querySelectorAll('[data-md-action]'))
.some((button) => button.getAttribute('title') === mountedDom.copiedLabel);
diff --git a/packages/ui/src/components/session/sidebar/projects/SessionGroupSection.test.ts b/packages/ui/src/components/session/sidebar/projects/SessionGroupSection.test.ts
index a3edd2d7..6641ff6c 100644
--- a/packages/ui/src/components/session/sidebar/projects/SessionGroupSection.test.ts
+++ b/packages/ui/src/components/session/sidebar/projects/SessionGroupSection.test.ts
@@ -55,4 +55,14 @@ describe('selectFolderIdsForProjection', () => {
expect([...selectFolderIdsForProjection(folders, { archivedBucket: true, searchQuery: 'matching' })])
.toEqual(['root', 'child']);
});
+
+ test('keeps a fuzzy folder match and its ancestor', () => {
+ const folders = [
+ { id: 'root', name: 'Root', parentId: null, nodeCount: 0 },
+ { id: 'child', name: 'Release Notes', parentId: 'root', nodeCount: 0 },
+ ];
+
+ expect([...selectFolderIdsForProjection(folders, { archivedBucket: false, searchQuery: 'release-notes' })])
+ .toEqual(['root', 'child']);
+ });
});
diff --git a/packages/ui/src/components/session/sidebar/sessions/sessionNodeItemUtils.ts b/packages/ui/src/components/session/sidebar/sessions/sessionNodeItemUtils.ts
index 064681cb..f2c40e5c 100644
--- a/packages/ui/src/components/session/sidebar/sessions/sessionNodeItemUtils.ts
+++ b/packages/ui/src/components/session/sidebar/sessions/sessionNodeItemUtils.ts
@@ -1,4 +1,5 @@
import { getRuntimeKey } from '@/lib/runtime-switch';
+import { matchesRankQuery } from '@/lib/search/fuzzySearch';
import { normalizePath } from '@/lib/pathNormalization';
import { resolveGlobalSessionDirectory } from '@/stores/useGlobalSessionsStore';
import { getPinnedSessionKey } from '@/stores/useSessionPinnedStore';
@@ -227,7 +228,7 @@ export const selectFolderIdsForProjection = (
keep = (childIdsByParentId.get(folderId) ?? []).some(shouldKeep);
} else {
if (!keep && !options.searchQuery) keep = true;
- if (!keep && (entry.nodeCount > 0 || entry.name.toLowerCase().includes(options.searchQuery))) keep = true;
+ if (!keep && (entry.nodeCount > 0 || matchesRankQuery([entry.name], options.searchQuery))) keep = true;
if (!keep) keep = (childIdsByParentId.get(folderId) ?? []).some(shouldKeep);
}