Skip to content

Commit f989baa

Browse files
committed
fix(code-tab): persist tree search query across row clicks
Pierre's FileTree closes the search session on input blur. Their `searchBlurBehavior: 'retain'` only protects the *initial mount* query (per the implementation comment in FileTreeView.tsx), so as soon as the user types and then clicks a result, the filter vanishes — terrible for the tree+viewer pattern where you want to scan multiple matches. Workaround: track the live query via `onSearchChange` and re-open the search inside `onSelectionChange` with a short window-based heuristic. Escape (close not followed by a selection) still closes; row click (close immediately followed by a selection) re-opens with the saved query.
1 parent e66cbe9 commit f989baa

1 file changed

Lines changed: 41 additions & 1 deletion

File tree

packages/client/src/ui/PierreFileTree.tsx

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,16 @@ const PierreFileTree: Component<PierreFileTreeProps> = (props) => {
5757
// set is a reliable file-vs-folder discriminator.
5858
const fileSet = createMemo(() => new Set(props.paths));
5959

60+
// Pierre clears the search query whenever the input blurs (clicking a row
61+
// steals focus → query gone). Their `searchBlurBehavior: 'retain'` only
62+
// protects the *initial* mount query, not user-typed queries. We work
63+
// around it: track the live query, and when a row click closes the search,
64+
// re-open it with the saved query. Escape / explicit clear still close
65+
// because they aren't followed by a selection.
66+
let liveQuery = "";
67+
let pendingRestore: number | undefined;
68+
const RESTORE_WINDOW_MS = 120;
69+
6070
onMount(() => {
6171
tree = new FileTree({
6272
paths: props.paths,
@@ -65,11 +75,38 @@ const PierreFileTree: Component<PierreFileTreeProps> = (props) => {
6575
search: props.search ?? true,
6676
gitStatus: props.gitStatus,
6777
initialSelectedPaths: props.selectedPath ? [props.selectedPath] : [],
78+
onSearchChange: (value) => {
79+
if (value !== null) {
80+
liveQuery = value;
81+
if (pendingRestore !== undefined) {
82+
clearTimeout(pendingRestore);
83+
pendingRestore = undefined;
84+
}
85+
return;
86+
}
87+
if (!liveQuery) return;
88+
// Session closed; wait briefly to see if a selection follows. If so,
89+
// we treat the close as a side-effect of the row click and restore.
90+
// If no selection arrives in the window, the user pressed Escape (or
91+
// similar) and we let the close stand.
92+
pendingRestore = window.setTimeout(() => {
93+
pendingRestore = undefined;
94+
liveQuery = "";
95+
}, RESTORE_WINDOW_MS);
96+
},
6897
onSelectionChange: (paths) => {
6998
// Pierre fires with all selected paths; we model single-select.
7099
const p = paths[0] ?? null;
71100
if (p !== null && !fileSet().has(p)) return; // ignore directories
72101
props.onSelect?.(p);
102+
if (pendingRestore !== undefined) {
103+
clearTimeout(pendingRestore);
104+
pendingRestore = undefined;
105+
if (liveQuery) {
106+
const q = liveQuery;
107+
queueMicrotask(() => tree?.openSearch(q));
108+
}
109+
}
73110
},
74111
});
75112
tree.render({ containerWrapper: container });
@@ -91,7 +128,10 @@ const PierreFileTree: Component<PierreFileTreeProps> = (props) => {
91128
),
92129
);
93130

94-
onCleanup(() => tree?.cleanUp());
131+
onCleanup(() => {
132+
if (pendingRestore !== undefined) clearTimeout(pendingRestore);
133+
tree?.cleanUp();
134+
});
95135

96136
return (
97137
<div

0 commit comments

Comments
 (0)