@@ -10,6 +10,7 @@ import { MetricsView, MetricDetailsPanel } from "./metricsView";
1010import { ClientDropdown } from "./clientDropdown" ;
1111import { ResizableBox } from "./resizableBox" ;
1212import { CommandPalette } from "./commandPalette" ;
13+ import { SpanFilterInput } from "./spanFilterInput" ;
1314import type { ScrollBoxRenderable } from "@opentui/core" ;
1415
1516/**
@@ -61,6 +62,7 @@ Clients Section:
6162 Navigate to select span with arrows or hjkl
6263 Press l/→ to expand and navigate into children
6364 Press h/← to collapse and navigate to parent
65+ Press "/" to open span filter (search by name)
6466 Selected span details shown in right panel
6567
6668Metrics Section:
@@ -125,18 +127,7 @@ function AppContent() {
125127
126128 // Setup keyboard handlers
127129 useKeyboard ( ( key ) => {
128- // Quit handlers
129- if ( key . name === "q" && ! key . ctrl ) {
130- renderer . stop ( ) ;
131- process . exit ( 0 ) ;
132- }
133- if ( key . raw === "\u0003" ) {
134- // Ctrl+C
135- renderer . stop ( ) ;
136- process . exit ( 0 ) ;
137- }
138-
139- // Command palette keyboard handling (must be before other handlers)
130+ // Command palette keyboard handling (must be before quit handlers)
140131 if ( store . ui . showCommandPalette ) {
141132 // Close on Esc
142133 if ( key . name === "escape" ) {
@@ -176,6 +167,50 @@ function AppContent() {
176167 return ;
177168 }
178169
170+ // Span filter keyboard handling (must be before quit handlers)
171+ if ( store . ui . showSpanFilter ) {
172+ if ( key . name === "escape" ) {
173+ // Cancel: close filter and clear query
174+ actions . clearSpanFilter ( ) ;
175+ actions . toggleSpanFilter ( ) ;
176+ return ;
177+ }
178+
179+ if ( key . name === "return" || key . name === "enter" ) {
180+ // Confirm: close filter but keep query active
181+ actions . toggleSpanFilter ( ) ;
182+ return ;
183+ }
184+
185+ if ( key . name === "backspace" ) {
186+ const currentQuery = store . ui . spanFilterQuery ;
187+ if ( currentQuery . length > 0 ) {
188+ actions . setSpanFilterQuery ( currentQuery . slice ( 0 , - 1 ) ) ;
189+ }
190+ return ;
191+ }
192+
193+ // Handle printable characters (including 'q')
194+ if ( key . raw && key . raw . length === 1 && ! key . ctrl && ! key . meta ) {
195+ actions . setSpanFilterQuery ( store . ui . spanFilterQuery + key . raw ) ;
196+ return ;
197+ }
198+
199+ // Prevent other keys from doing anything when filter is open
200+ return ;
201+ }
202+
203+ // Quit handlers (after modal input handlers)
204+ if ( key . name === "q" && ! key . ctrl ) {
205+ renderer . stop ( ) ;
206+ process . exit ( 0 ) ;
207+ }
208+ if ( key . raw === "\u0003" ) {
209+ // Ctrl+C
210+ renderer . stop ( ) ;
211+ process . exit ( 0 ) ;
212+ }
213+
179214 // Help toggle
180215 if ( key . name === "?" ) {
181216 actions . toggleHelp ( ) ;
@@ -259,6 +294,28 @@ function AppContent() {
259294 }
260295 return ;
261296 }
297+
298+ // Span filter toggle with "/" key (only when spans section is focused)
299+ if (
300+ key . raw === "/" &&
301+ ! key . ctrl &&
302+ ! key . meta &&
303+ store . ui . focusedSection === "spans"
304+ ) {
305+ actions . toggleSpanFilter ( ) ;
306+ return ;
307+ }
308+
309+ // Clear active span filter with Escape when not in filter input mode
310+ if (
311+ key . name === "escape" &&
312+ store . ui . focusedSection === "spans" &&
313+ store . ui . spanFilterQuery . length > 0 &&
314+ ! store . ui . showSpanFilter
315+ ) {
316+ actions . clearSpanFilter ( ) ;
317+ return ;
318+ }
262319 } ) ;
263320
264321 const statusText = ( ) => {
@@ -356,6 +413,31 @@ function AppContent() {
356413 . pipe ( Option . getOrElse ( ( ) => "None" ) ) } `}
357414 </ text >
358415
416+ { /* Span filter input (shown when typing) */ }
417+ < Show when = { store . ui . showSpanFilter } >
418+ < SpanFilterInput />
419+ </ Show >
420+
421+ { /* Active filter indicator (shown when filter closed but query active) */ }
422+ < Show
423+ when = {
424+ ! store . ui . showSpanFilter &&
425+ store . ui . spanFilterQuery . length > 0
426+ }
427+ >
428+ < box
429+ flexDirection = "row"
430+ width = "100%"
431+ paddingLeft = { 1 }
432+ paddingBottom = { 1 }
433+ flexShrink = { 0 }
434+ >
435+ < text style = { { fg : "#f7768e" } } >
436+ { `Filter: "${ store . ui . spanFilterQuery } " (press / to edit, Esc to clear)` }
437+ </ text >
438+ </ box >
439+ </ Show >
440+
359441 { /* Side-by-side: Span list and details */ }
360442 < box flexDirection = "row" flexGrow = { 1 } >
361443 { /* Span list - left side */ }
@@ -368,6 +450,7 @@ function AppContent() {
368450 spans = { store . spans }
369451 selectedSpanId = { store . ui . selectedSpanId }
370452 expandedSpanIds = { store . ui . expandedSpanIds }
453+ filterQuery = { store . ui . spanFilterQuery || undefined }
371454 />
372455 </ scrollbox >
373456
0 commit comments