Skip to content

Commit 3407b4a

Browse files
ozgesolidkeyclaude
andcommitted
Filter suspend/resume: badge toggle, auto-pause on folder search nav
Navigation fix: - Folder search results now call suspendFilter('auto') instead of clearFilter() — the filter config is preserved and can be re-applied Filter badge toggle: - Clicking the amber badge suspends (manual) or resumes the filter - × button permanently clears as before - 'auto' suspend (navigation): badge blinks via CSS animation to remind the user the filter is paused - 'manual' suspend: badge dimmed but solid (user explicitly turned off) - Filter state (suspended/config) saved and restored per tab Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 39fd2a7 commit 3407b4a

3 files changed

Lines changed: 100 additions & 15 deletions

File tree

src/renderer/index.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -681,12 +681,12 @@ <h2>LOGAN</h2>
681681
<span id="status-file">No file</span>
682682
<span class="status-separator">|</span>
683683
<span id="status-lines">0 lines</span>
684-
<span id="status-filtered" class="status-filter-badge hidden" title="A filter is active — click to clear">
684+
<span id="status-filtered" class="status-filter-badge hidden" title="Filter active — click to pause">
685685
<span class="status-filter-icon"></span>
686-
<span>Filtered:</span>
686+
<span class="status-filter-label">Filtered:</span>
687687
<span id="filtered-count">0</span>
688-
<span>lines</span>
689-
<button id="btn-status-clear-filter" class="status-filter-clear" title="Clear filter">×</button>
688+
<span class="status-filter-lines-word">lines</span>
689+
<button id="btn-status-clear-filter" class="status-filter-clear" title="Clear filter completely">×</button>
690690
</span>
691691
</div>
692692
<div class="status-center">

src/renderer/renderer.ts

Lines changed: 76 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,9 @@ interface TabState {
218218
filteredLineNumbers: number[] | null;
219219
activeLevelFilter: string | null;
220220
appliedFilterSuggestion: { id: string; title: string } | null;
221+
// Filter suspension — config preserved, view temporarily shows all lines
222+
lastFilterConfig: FilterConfig | null;
223+
filterSuspended: 'auto' | 'manual' | null; // 'auto'=navigation triggered, 'manual'=user toggled
221224
}
222225

223226
// Per-connection state for Live panel
@@ -249,6 +252,8 @@ interface AppState {
249252
isFiltered: boolean;
250253
activeLevelFilter: string | null;
251254
appliedFilterSuggestion: { id: string; title: string } | null;
255+
lastFilterConfig: FilterConfig | null;
256+
filterSuspended: 'auto' | 'manual' | null;
252257
searchResults: SearchResult[];
253258
currentSearchIndex: number;
254259
hiddenSearchMatches: HiddenMatch[];
@@ -326,6 +331,8 @@ const state: AppState = {
326331
isFiltered: false,
327332
activeLevelFilter: null,
328333
appliedFilterSuggestion: null,
334+
lastFilterConfig: null,
335+
filterSuspended: null,
329336
searchResults: [],
330337
currentSearchIndex: -1,
331338
hiddenSearchMatches: [],
@@ -4759,18 +4766,18 @@ function renderFolderSearchResults(pattern: string, cancelled?: boolean): void {
47594766

47604767
const absLine = match.lineNumber - 1; // ripgrep is 1-based → 0-based
47614768

4762-
// Same file already open in current tab — just clear filter and navigate
4769+
// Same file already open in current tab — suspend filter and navigate
47634770
if (match.filePath === state.filePath) {
4764-
if (state.isFiltered) await clearFilter();
4771+
if (state.isFiltered) await suspendFilter('auto');
47654772
await navigateTo(absLine);
47664773
return;
47674774
}
47684775

4769-
// File open in another tab — switch to it, clear any restored filter, navigate
4776+
// File open in another tab — switch to it, suspend any restored filter, navigate
47704777
const existingTab = findTabByFilePath(match.filePath);
47714778
if (existingTab) {
47724779
await switchToTab(existingTab.id);
4773-
if (state.isFiltered) await clearFilter();
4780+
if (state.isFiltered) await suspendFilter('auto');
47744781
await navigateTo(absLine);
47754782
return;
47764783
}
@@ -10514,6 +10521,8 @@ async function applyFilter(providedConfig?: FilterConfig): Promise<void> {
1051410521
state.isFiltered = true;
1051510522
state.filteredLines = result.stats.filteredLines;
1051610523
state.filteredLineNumbers = result.filteredLineNumbers ?? null;
10524+
state.lastFilterConfig = config; // remember for suspension/resume
10525+
state.filterSuspended = null; // filter is now actively applied
1051710526
cachedLines.clear();
1051810527

1051910528
// Reset scroll to top of filtered view
@@ -10541,9 +10550,10 @@ async function clearFilter(): Promise<void> {
1054110550
state.filteredLineNumbers = null;
1054210551
state.activeLevelFilter = null;
1054310552
state.appliedFilterSuggestion = null;
10553+
state.lastFilterConfig = null;
10554+
state.filterSuspended = null;
1054410555
cachedLines.clear();
1054510556

10546-
// Clear backend filter state
1054710557
await window.api.clearFilter();
1054810558

1054910559
hideFilterModal();
@@ -10552,6 +10562,30 @@ async function clearFilter(): Promise<void> {
1055210562
updateLevelBadgeStyles();
1055310563
}
1055410564

10565+
// Suspend: keep config, temporarily show unfiltered view.
10566+
// reason='auto' (navigation) → badge blinks; reason='manual' → badge solid.
10567+
async function suspendFilter(reason: 'auto' | 'manual' = 'auto'): Promise<void> {
10568+
if (!state.isFiltered) return;
10569+
state.filterSuspended = reason;
10570+
state.isFiltered = false;
10571+
state.filteredLines = null;
10572+
state.filteredLineNumbers = null;
10573+
state.activeLevelFilter = null;
10574+
cachedLines.clear();
10575+
10576+
await window.api.clearFilter();
10577+
await loadVisibleLines();
10578+
updateStatusBar();
10579+
updateLevelBadgeStyles();
10580+
}
10581+
10582+
// Resume: re-apply the saved config from before suspension.
10583+
async function resumeFilter(): Promise<void> {
10584+
if (!state.lastFilterConfig) return;
10585+
state.filterSuspended = null;
10586+
await applyFilter(state.lastFilterConfig);
10587+
}
10588+
1055510589
async function applyQuickLevelFilter(level: string): Promise<void> {
1055610590
if (!state.analysisResult) return;
1055710591

@@ -10577,6 +10611,8 @@ async function applyQuickLevelFilter(level: string): Promise<void> {
1057710611
state.filteredLines = result.stats.filteredLines;
1057810612
state.filteredLineNumbers = result.filteredLineNumbers ?? null;
1057910613
state.activeLevelFilter = level;
10614+
state.lastFilterConfig = config;
10615+
state.filterSuspended = null;
1058010616
cachedLines.clear();
1058110617

1058210618
// Reset scroll to top of filtered view
@@ -12183,17 +12219,35 @@ function updateStatusBar(): void {
1218312219
elements.statusLines.textContent = `${state.totalLines.toLocaleString()} lines`;
1218412220
elements.statusSize.textContent = formatBytes(state.fileStats?.size || 0);
1218512221

12222+
const badge = elements.statusFiltered;
1218612223
if (state.isFiltered && state.filteredLines !== null) {
12187-
elements.statusFiltered.classList.remove('hidden');
12224+
// Active filter
12225+
badge.classList.remove('hidden', 'suspended', 'suspended-auto', 'suspended-manual');
12226+
badge.title = 'Filter active — click to pause, × to clear';
1218812227
elements.filteredCount.textContent = state.filteredLines.toLocaleString();
12228+
const label = badge.querySelector('.status-filter-label') as HTMLElement | null;
12229+
if (label) label.textContent = 'Filtered:';
12230+
} else if (state.filterSuspended && state.lastFilterConfig) {
12231+
// Suspended filter — show badge with different state
12232+
badge.classList.remove('hidden');
12233+
badge.classList.remove('suspended-auto', 'suspended-manual');
12234+
badge.classList.add('suspended', `suspended-${state.filterSuspended}`);
12235+
badge.title = state.filterSuspended === 'auto'
12236+
? 'Filter paused (navigated to line) — click to re-enable'
12237+
: 'Filter paused — click to re-enable';
12238+
elements.filteredCount.textContent = '';
12239+
const label = badge.querySelector('.status-filter-label') as HTMLElement | null;
12240+
if (label) label.textContent = 'Filter paused';
1218912241
} else {
12190-
elements.statusFiltered.classList.add('hidden');
12242+
badge.classList.add('hidden');
12243+
badge.classList.remove('suspended', 'suspended-auto', 'suspended-manual');
1219112244
}
1219212245
} else {
1219312246
elements.statusFile.textContent = 'No file';
1219412247
elements.statusLines.textContent = '0 lines';
1219512248
elements.statusSize.textContent = '0 B';
1219612249
elements.statusFiltered.classList.add('hidden');
12250+
elements.statusFiltered.classList.remove('suspended', 'suspended-auto', 'suspended-manual');
1219712251
}
1219812252
}
1219912253

@@ -14097,6 +14151,15 @@ function init(): void {
1409714151
elements.btnFilter.addEventListener('click', showFilterModal);
1409814152
elements.btnApplyFilter.addEventListener('click', () => applyFilter());
1409914153
elements.btnClearFilter.addEventListener('click', clearFilter);
14154+
// Badge body click = toggle suspend/resume; × button = permanent clear
14155+
elements.statusFiltered.addEventListener('click', async (e) => {
14156+
if ((e.target as HTMLElement).id === 'btn-status-clear-filter') return;
14157+
if (state.isFiltered) {
14158+
await suspendFilter('manual');
14159+
} else if (state.filterSuspended && state.lastFilterConfig) {
14160+
await resumeFilter();
14161+
}
14162+
});
1410014163
document.getElementById('btn-status-clear-filter')?.addEventListener('click', clearFilter);
1410114164
elements.btnAddIncludePattern.addEventListener('click', () => addIncludePatternRow());
1410214165

@@ -14660,6 +14723,8 @@ function saveCurrentTabState(): void {
1466014723
filteredLineNumbers: state.filteredLineNumbers,
1466114724
activeLevelFilter: state.activeLevelFilter,
1466214725
appliedFilterSuggestion: state.appliedFilterSuggestion,
14726+
lastFilterConfig: state.lastFilterConfig,
14727+
filterSuspended: state.filterSuspended,
1466314728
};
1466414729
}
1466514730

@@ -14686,6 +14751,8 @@ function restoreTabState(tab: TabState): void {
1468614751
state.filteredLineNumbers = tab.filteredLineNumbers;
1468714752
state.activeLevelFilter = tab.activeLevelFilter;
1468814753
state.appliedFilterSuggestion = tab.appliedFilterSuggestion;
14754+
state.lastFilterConfig = tab.lastFilterConfig;
14755+
state.filterSuspended = tab.filterSuspended;
1468914756

1469014757
// Restore cached lines
1469114758
cachedLines.clear();
@@ -14732,6 +14799,8 @@ function createTab(filePath: string): TabState {
1473214799
filteredLineNumbers: null,
1473314800
activeLevelFilter: null,
1473414801
appliedFilterSuggestion: null,
14802+
lastFilterConfig: null,
14803+
filterSuspended: null,
1473514804
};
1473614805
return tab;
1473714806
}

src/renderer/styles.css

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2586,7 +2586,11 @@ body.platform-darwin .titlebar {
25862586
color: rgba(255, 255, 255, 0.5);
25872587
}
25882588

2589-
/* Filter active badge in status bar */
2589+
/* Filter active/paused badge in status bar */
2590+
@keyframes filter-badge-blink {
2591+
0%, 100% { opacity: 1; }
2592+
50% { opacity: 0.35; }
2593+
}
25902594
.status-filter-badge {
25912595
display: inline-flex;
25922596
align-items: center;
@@ -2598,21 +2602,33 @@ body.platform-darwin .titlebar {
25982602
padding: 1px 6px 1px 5px;
25992603
font-size: 11px;
26002604
font-weight: 500;
2601-
cursor: default;
2605+
cursor: pointer;
2606+
user-select: none;
26022607
}
26032608
.status-filter-badge.hidden { display: none; }
2609+
.status-filter-badge:hover { background: rgba(255, 184, 64, 0.26); }
2610+
/* Suspended manually — dimmed, solid */
2611+
.status-filter-badge.suspended {
2612+
background: rgba(255, 184, 64, 0.07);
2613+
border-color: rgba(255, 184, 64, 0.22);
2614+
color: rgba(255, 184, 64, 0.55);
2615+
}
2616+
/* Suspended by auto-navigation — blinks as a reminder */
2617+
.status-filter-badge.suspended-auto {
2618+
animation: filter-badge-blink 1.4s ease-in-out infinite;
2619+
}
26042620
.status-filter-icon { font-size: 10px; opacity: 0.8; }
26052621
.status-filter-clear {
26062622
background: none;
26072623
border: none;
2608-
color: #ffb840;
2624+
color: inherit;
26092625
cursor: pointer;
26102626
font-size: 13px;
26112627
line-height: 1;
26122628
padding: 0 0 0 2px;
26132629
opacity: 0.7;
26142630
}
2615-
.status-filter-clear:hover { opacity: 1; }
2631+
.status-filter-clear:hover { opacity: 1; color: var(--error-color, #e88080) !important; }
26162632

26172633
.progress-container {
26182634
display: flex;

0 commit comments

Comments
 (0)