fix: repair broken filter controls in notification history modal

- Replace raw HTML inputs with Obsidian Setting components
- Add DOM element references for targeted updates
- Eliminate destructive re-render on filter changes
- Update only list container and count on filter apply
- Fix tool filter input not accepting text
- Fix status dropdown not showing selection

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-26 09:29:12 -04:00
parent 5bc3aeed69
commit 8e72ff1af6

View File

@@ -1,4 +1,4 @@
import { App, Modal } from 'obsidian'; import { App, Modal, Setting } from 'obsidian';
import { NotificationHistoryEntry } from './notifications'; import { NotificationHistoryEntry } from './notifications';
/** /**
@@ -10,6 +10,10 @@ export class NotificationHistoryModal extends Modal {
private filterTool: string = ''; private filterTool: string = '';
private filterType: 'all' | 'success' | 'error' = 'all'; private filterType: 'all' | 'success' | 'error' = 'all';
// DOM element references for targeted updates
private listContainerEl: HTMLElement | null = null;
private countEl: HTMLElement | null = null;
constructor(app: App, history: NotificationHistoryEntry[]) { constructor(app: App, history: NotificationHistoryEntry[]) {
super(app); super(app);
this.history = history; this.history = history;
@@ -24,11 +28,11 @@ export class NotificationHistoryModal extends Modal {
// Title // Title
contentEl.createEl('h2', { text: 'MCP Notification History' }); contentEl.createEl('h2', { text: 'MCP Notification History' });
// Filters // Filters (create once, never recreate)
this.createFilters(contentEl); this.createFilters(contentEl);
// History list // History list (will be updated via reference)
this.createHistoryList(contentEl); this.createHistoryListContainer(contentEl);
// Actions // Actions
this.createActions(contentEl); this.createActions(contentEl);
@@ -37,68 +41,77 @@ export class NotificationHistoryModal extends Modal {
onClose() { onClose() {
const { contentEl } = this; const { contentEl } = this;
contentEl.empty(); contentEl.empty();
this.listContainerEl = null;
this.countEl = null;
} }
/** /**
* Create filter controls * Create filter controls using Obsidian Setting components
*/ */
private createFilters(containerEl: HTMLElement): void { private createFilters(containerEl: HTMLElement): void {
const filterContainer = containerEl.createDiv({ cls: 'mcp-history-filters' }); const filterContainer = containerEl.createDiv({ cls: 'mcp-history-filters' });
filterContainer.style.marginBottom = '16px'; filterContainer.style.marginBottom = '16px';
filterContainer.style.display = 'flex';
filterContainer.style.gap = '12px';
filterContainer.style.flexWrap = 'wrap';
// Tool name filter // Tool name filter using Setting component
const toolFilterContainer = filterContainer.createDiv(); new Setting(filterContainer)
toolFilterContainer.createEl('label', { text: 'Tool: ' }); .setName('Tool filter')
const toolInput = toolFilterContainer.createEl('input', { .setDesc('Filter by tool name')
type: 'text', .addText(text => text
placeholder: 'Filter by tool name...' .setPlaceholder('Enter tool name...')
}); .setValue(this.filterTool)
toolInput.style.marginLeft = '4px'; .onChange((value) => {
toolInput.style.padding = '4px 8px'; this.filterTool = value.toLowerCase();
toolInput.addEventListener('input', (e) => { this.applyFilters();
this.filterTool = (e.target as HTMLInputElement).value.toLowerCase(); }));
this.applyFilters();
});
// Type filter // Type filter using Setting component
const typeFilterContainer = filterContainer.createDiv(); new Setting(filterContainer)
typeFilterContainer.createEl('label', { text: 'Type: ' }); .setName('Status filter')
const typeSelect = typeFilterContainer.createEl('select'); .setDesc('Filter by success or error')
typeSelect.style.marginLeft = '4px'; .addDropdown(dropdown => dropdown
typeSelect.style.padding = '4px 8px'; .addOption('all', 'All')
.addOption('success', 'Success')
const allOption = typeSelect.createEl('option', { text: 'All', value: 'all' }); .addOption('error', 'Error')
const successOption = typeSelect.createEl('option', { text: 'Success', value: 'success' }); .setValue(this.filterType)
const errorOption = typeSelect.createEl('option', { text: 'Error', value: 'error' }); .onChange((value) => {
this.filterType = value as 'all' | 'success' | 'error';
typeSelect.addEventListener('change', (e) => { this.applyFilters();
this.filterType = (e.target as HTMLSelectElement).value as 'all' | 'success' | 'error'; }));
this.applyFilters();
});
// Results count // Results count
const countEl = filterContainer.createDiv({ cls: 'mcp-history-count' }); this.countEl = filterContainer.createDiv({ cls: 'mcp-history-count' });
countEl.style.marginLeft = 'auto'; this.countEl.style.marginTop = '8px';
countEl.style.alignSelf = 'center'; this.countEl.style.fontSize = '0.9em';
countEl.textContent = `${this.filteredHistory.length} entries`; this.countEl.style.color = 'var(--text-muted)';
this.updateResultsCount();
} }
/** /**
* Create history list * Create history list container (called once)
*/ */
private createHistoryList(containerEl: HTMLElement): void { private createHistoryListContainer(containerEl: HTMLElement): void {
const listContainer = containerEl.createDiv({ cls: 'mcp-history-list' }); this.listContainerEl = containerEl.createDiv({ cls: 'mcp-history-list' });
listContainer.style.maxHeight = '400px'; this.listContainerEl.style.maxHeight = '400px';
listContainer.style.overflowY = 'auto'; this.listContainerEl.style.overflowY = 'auto';
listContainer.style.marginBottom = '16px'; this.listContainerEl.style.marginBottom = '16px';
listContainer.style.border = '1px solid var(--background-modifier-border)'; this.listContainerEl.style.border = '1px solid var(--background-modifier-border)';
listContainer.style.borderRadius = '4px'; this.listContainerEl.style.borderRadius = '4px';
// Initial render
this.updateHistoryList();
}
/**
* Update history list contents (called on filter changes)
*/
private updateHistoryList(): void {
if (!this.listContainerEl) return;
// Clear existing content
this.listContainerEl.empty();
if (this.filteredHistory.length === 0) { if (this.filteredHistory.length === 0) {
const emptyEl = listContainer.createDiv({ cls: 'mcp-history-empty' }); const emptyEl = this.listContainerEl.createDiv({ cls: 'mcp-history-empty' });
emptyEl.style.padding = '24px'; emptyEl.style.padding = '24px';
emptyEl.style.textAlign = 'center'; emptyEl.style.textAlign = 'center';
emptyEl.style.color = 'var(--text-muted)'; emptyEl.style.color = 'var(--text-muted)';
@@ -107,10 +120,10 @@ export class NotificationHistoryModal extends Modal {
} }
this.filteredHistory.forEach((entry, index) => { this.filteredHistory.forEach((entry, index) => {
const entryEl = listContainer.createDiv({ cls: 'mcp-history-entry' }); const entryEl = this.listContainerEl!.createDiv({ cls: 'mcp-history-entry' });
entryEl.style.padding = '12px'; entryEl.style.padding = '12px';
entryEl.style.borderBottom = index < this.filteredHistory.length - 1 entryEl.style.borderBottom = index < this.filteredHistory.length - 1
? '1px solid var(--background-modifier-border)' ? '1px solid var(--background-modifier-border)'
: 'none'; : 'none';
// Header row // Header row
@@ -160,6 +173,14 @@ export class NotificationHistoryModal extends Modal {
}); });
} }
/**
* Update results count display
*/
private updateResultsCount(): void {
if (!this.countEl) return;
this.countEl.textContent = `${this.filteredHistory.length} of ${this.history.length} entries`;
}
/** /**
* Create action buttons * Create action buttons
*/ */
@@ -209,7 +230,8 @@ export class NotificationHistoryModal extends Modal {
return true; return true;
}); });
// Re-render // Update only the affected UI elements
this.onOpen(); this.updateHistoryList();
this.updateResultsCount();
} }
} }