From 50af0eb8025ff81a1356601201a5877c0e90c637 Mon Sep 17 00:00:00 2001 From: tolvitty Date: Thu, 12 Feb 2026 10:55:08 +0100 Subject: [PATCH 1/4] Event-Stream-View: Echtzeit-Sidebar mit Filtern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sidebar-View mit Live-Event-Liste, Suche nach Quelle, Kategorie-Filter-Checkboxen, Pause/Leeren-Buttons, max 500 sichtbare Einträge, Payload-Zusammenfassung je Event-Typ. Co-Authored-By: Claude Opus 4.6 --- src/ui/event-stream-view.ts | 150 ++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 src/ui/event-stream-view.ts diff --git a/src/ui/event-stream-view.ts b/src/ui/event-stream-view.ts new file mode 100644 index 0000000..72ef44c --- /dev/null +++ b/src/ui/event-stream-view.ts @@ -0,0 +1,150 @@ +import { ItemView, WorkspaceLeaf } from 'obsidian'; +import { LogfireEvent, EventCategory } from '../types'; +import { EventBus } from '../core/event-bus'; + +export const EVENT_STREAM_VIEW_TYPE = 'logfire-event-stream'; +const MAX_VISIBLE_ENTRIES = 500; + +export class EventStreamView extends ItemView { + private entries: LogfireEvent[] = []; + private unsubscribe: (() => void) | null = null; + private listEl!: HTMLElement; + private viewPaused = false; + private filterText = ''; + private enabledCategories = new Set([ + 'file', 'content', 'navigation', 'editor', 'vault', 'plugin', 'system', + ]); + + constructor(leaf: WorkspaceLeaf, private eventBus: EventBus) { + super(leaf); + } + + getViewType(): string { + return EVENT_STREAM_VIEW_TYPE; + } + + getDisplayText(): string { + return 'Logfire Events'; + } + + getIcon(): string { + return 'activity'; + } + + async onOpen(): Promise { + const container = this.containerEl.children[1] as HTMLElement; + container.empty(); + container.addClass('logfire-event-stream'); + + // Toolbar + const toolbar = container.createDiv({ cls: 'logfire-stream-toolbar' }); + + const searchInput = toolbar.createEl('input', { + type: 'text', + placeholder: 'Nach Quelle filtern...', + cls: 'logfire-stream-search', + }); + searchInput.addEventListener('input', () => { + this.filterText = searchInput.value.toLowerCase(); + this.renderList(); + }); + + const btnGroup = toolbar.createDiv({ cls: 'logfire-stream-btn-group' }); + + const pauseBtn = btnGroup.createEl('button', { text: 'Pause' }); + pauseBtn.addEventListener('click', () => { + this.viewPaused = !this.viewPaused; + pauseBtn.textContent = this.viewPaused ? 'Weiter' : 'Pause'; + pauseBtn.toggleClass('is-active', this.viewPaused); + }); + + const clearBtn = btnGroup.createEl('button', { text: 'Leeren' }); + clearBtn.addEventListener('click', () => { + this.entries = []; + this.renderList(); + }); + + // Category toggles + const categoryBar = container.createDiv({ cls: 'logfire-stream-categories' }); + + const categories: EventCategory[] = ['file', 'content', 'navigation', 'editor', 'vault', 'plugin', 'system']; + for (const cat of categories) { + const label = categoryBar.createEl('label', { cls: 'logfire-cat-toggle' }); + const cb = label.createEl('input', { type: 'checkbox' }) as HTMLInputElement; + cb.checked = true; + label.createSpan({ text: cat }); + cb.addEventListener('change', () => { + if (cb.checked) { + this.enabledCategories.add(cat); + } else { + this.enabledCategories.delete(cat); + } + this.renderList(); + }); + } + + // Event list + this.listEl = container.createDiv({ cls: 'logfire-stream-list' }); + + // Subscribe to events + this.unsubscribe = this.eventBus.onEvent('*', (event) => { + if (this.viewPaused) return; + this.entries.unshift(event); + if (this.entries.length > MAX_VISIBLE_ENTRIES) { + this.entries.length = MAX_VISIBLE_ENTRIES; + } + this.renderList(); + }); + } + + async onClose(): Promise { + this.unsubscribe?.(); + this.unsubscribe = null; + } + + private renderList(): void { + this.listEl.empty(); + + const filtered = this.entries.filter(e => { + if (!this.enabledCategories.has(e.category)) return false; + if (this.filterText && !e.source.toLowerCase().includes(this.filterText)) return false; + return true; + }); + + for (const event of filtered.slice(0, MAX_VISIBLE_ENTRIES)) { + const row = this.listEl.createDiv({ cls: 'logfire-stream-row' }); + + const time = new Date(event.timestamp); + const timeStr = `${String(time.getHours()).padStart(2, '0')}:${String(time.getMinutes()).padStart(2, '0')}:${String(time.getSeconds()).padStart(2, '0')}`; + + const payloadSummary = this.summarizePayload(event); + + row.createSpan({ text: timeStr, cls: 'logfire-stream-time' }); + row.createSpan({ text: event.type, cls: 'logfire-stream-type' }); + row.createSpan({ text: event.source || '', cls: 'logfire-stream-source' }); + if (payloadSummary) { + row.createSpan({ text: payloadSummary, cls: 'logfire-stream-payload' }); + } + } + } + + private summarizePayload(event: LogfireEvent): string { + const p = event.payload; + switch (event.type) { + case 'content:words-changed': + return `+${p.wordsAdded ?? 0}/-${p.wordsRemoved ?? 0} Wörter`; + case 'editor:change': + return `+${p.insertedChars ?? 0}/-${p.deletedChars ?? 0} Zeichen`; + case 'nav:file-close': + return typeof p.duration === 'number' ? `${Math.round(p.duration / 1000)}s` : ''; + case 'file:rename': + return `→ ${p.newName ?? ''}`; + case 'file:move': + return `→ ${p.newFolder ?? ''}`; + case 'plugin:command-executed': + return String(p.commandName ?? ''); + default: + return ''; + } + } +} From eb58de96fe81b01d145e1513ced2cd44fa73a3d0 Mon Sep 17 00:00:00 2001 From: tolvitty Date: Thu, 12 Feb 2026 10:55:15 +0100 Subject: [PATCH 2/4] Status-Bar: Live-Widget mit Klick-Pause MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Zeigt Aufnahme/Pausiert-Status, Event-Zähler, Wort-Delta, Session-Dauer. Klick togglet Pause. Updates jede Sekunde. Co-Authored-By: Claude Opus 4.6 --- src/ui/status-bar.ts | 67 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 src/ui/status-bar.ts diff --git a/src/ui/status-bar.ts b/src/ui/status-bar.ts new file mode 100644 index 0000000..4a2285d --- /dev/null +++ b/src/ui/status-bar.ts @@ -0,0 +1,67 @@ +import type LogfirePlugin from '../main'; + +export class StatusBar { + private el: HTMLElement; + private intervalId: ReturnType | null = null; + private wordsAdded = 0; + private eventCount = 0; + + constructor(private plugin: LogfirePlugin) { + this.el = plugin.addStatusBarItem(); + this.el.addClass('logfire-status-bar'); + this.el.addEventListener('click', () => this.onClick()); + } + + start(): void { + this.plugin.eventBus.onEvent('*', (event) => { + this.eventCount++; + const wa = event.payload.wordsAdded; + if (typeof wa === 'number') { + this.wordsAdded += wa; + } + }); + + this.update(); + this.intervalId = setInterval(() => this.update(), 1000); + } + + destroy(): void { + if (this.intervalId !== null) { + clearInterval(this.intervalId); + this.intervalId = null; + } + this.el.remove(); + } + + private update(): void { + const paused = this.plugin.isPaused(); + const indicator = paused ? '\u23F8' : '\u{1F534}'; + const state = paused ? 'Pausiert' : 'Aufnahme'; + const duration = this.formatDuration(this.plugin.sessionManager.sessionDurationMs); + const words = this.wordsAdded > 0 ? ` | +${this.wordsAdded}w` : ''; + + this.el.textContent = `${indicator} ${state} | ${this.eventCount} Events${words} | ${duration}`; + } + + private onClick(): void { + if (this.plugin.isPaused()) { + this.plugin.resume(); + } else { + this.plugin.pause(); + } + this.update(); + } + + private formatDuration(ms: number): string { + if (ms <= 0) return '0:00'; + const totalSeconds = Math.floor(ms / 1000); + const hours = Math.floor(totalSeconds / 3600); + const minutes = Math.floor((totalSeconds % 3600) / 60); + const seconds = totalSeconds % 60; + + if (hours > 0) { + return `${hours}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`; + } + return `${minutes}:${String(seconds).padStart(2, '0')}`; + } +} From 22a6c2e188353a1c919f6c06bbe00b6b6197107f Mon Sep 17 00:00:00 2001 From: tolvitty Date: Thu, 12 Feb 2026 10:55:21 +0100 Subject: [PATCH 3/4] Styles: Utilitarian-System-Monitor-Design MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Event-Stream mit Monospace, alternierenden Zeilen, Toolbar, Kategorie-Chips. Status-Bar kompakt. Scan-Modal mit animiertem Fortschrittsbalken. Ausschließlich Obsidian CSS-Variablen. Co-Authored-By: Claude Opus 4.6 --- styles.css | 317 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 316 insertions(+), 1 deletion(-) diff --git a/styles.css b/styles.css index a3488e2..18f9042 100644 --- a/styles.css +++ b/styles.css @@ -1 +1,316 @@ -/* Logfire – Styles */ +/* ═══════════════════════════════════════════════════════════════════════════ + Logfire — Obsidian Plugin Styles + Aesthetic: Utilitarian System Monitor + ═══════════════════════════════════════════════════════════════════════════ */ + +/* --------------------------------------------------------------------------- + Event Stream View — Container + --------------------------------------------------------------------------- */ + +.logfire-event-stream { + display: flex; + flex-direction: column; + height: 100%; + overflow: hidden; + background: var(--background-primary); +} + +/* --------------------------------------------------------------------------- + Event Stream — Toolbar + --------------------------------------------------------------------------- */ + +.logfire-stream-toolbar { + display: flex; + align-items: center; + gap: 6px; + padding: 8px 10px; + border-bottom: 1px solid var(--background-modifier-border); + background: var(--background-secondary); + flex-shrink: 0; +} + +.logfire-stream-search { + flex: 1; + min-width: 0; + padding: 4px 8px; + border: 1px solid var(--background-modifier-border); + border-radius: 4px; + background: var(--background-primary); + color: var(--text-normal); + font-family: var(--font-monospace); + font-size: 12px; + outline: none; + transition: border-color 120ms ease; +} + +.logfire-stream-search:focus { + border-color: var(--interactive-accent); +} + +.logfire-stream-search::placeholder { + color: var(--text-faint); + font-style: italic; +} + +.logfire-stream-btn-group { + display: flex; + gap: 4px; + flex-shrink: 0; +} + +.logfire-stream-btn-group button { + padding: 3px 10px; + border: 1px solid var(--background-modifier-border); + border-radius: 4px; + background: var(--background-primary); + color: var(--text-muted); + font-size: 11px; + font-family: var(--font-monospace); + cursor: pointer; + transition: background 100ms ease, color 100ms ease, border-color 100ms ease; + letter-spacing: 0.02em; + text-transform: uppercase; +} + +.logfire-stream-btn-group button:hover { + background: var(--background-modifier-hover); + color: var(--text-normal); + border-color: var(--text-faint); +} + +.logfire-stream-btn-group button.is-active { + background: var(--interactive-accent); + color: var(--text-on-accent); + border-color: var(--interactive-accent); +} + +/* --------------------------------------------------------------------------- + Event Stream — Category Filter Bar + --------------------------------------------------------------------------- */ + +.logfire-stream-categories { + display: flex; + gap: 2px; + padding: 6px 10px; + border-bottom: 1px solid var(--background-modifier-border); + background: var(--background-secondary); + flex-shrink: 0; + flex-wrap: wrap; +} + +.logfire-cat-toggle { + display: inline-flex; + align-items: center; + gap: 3px; + padding: 2px 7px; + border-radius: 3px; + font-family: var(--font-monospace); + font-size: 10px; + letter-spacing: 0.03em; + text-transform: uppercase; + color: var(--text-muted); + cursor: pointer; + transition: background 80ms ease, color 80ms ease; + user-select: none; + line-height: 1; +} + +.logfire-cat-toggle:hover { + background: var(--background-modifier-hover); + color: var(--text-normal); +} + +.logfire-cat-toggle input[type="checkbox"] { + margin: 0; + width: 10px; + height: 10px; + accent-color: var(--interactive-accent); + cursor: pointer; +} + +.logfire-cat-toggle span { + pointer-events: none; +} + +/* --------------------------------------------------------------------------- + Event Stream — List + --------------------------------------------------------------------------- */ + +.logfire-stream-list { + flex: 1; + overflow-y: auto; + overflow-x: hidden; + font-family: var(--font-monospace); + font-size: 11.5px; + line-height: 1.5; + padding: 2px 0; +} + +/* Subtle scrollbar */ +.logfire-stream-list::-webkit-scrollbar { + width: 6px; +} + +.logfire-stream-list::-webkit-scrollbar-track { + background: transparent; +} + +.logfire-stream-list::-webkit-scrollbar-thumb { + background: var(--background-modifier-border); + border-radius: 3px; +} + +.logfire-stream-list::-webkit-scrollbar-thumb:hover { + background: var(--text-faint); +} + +/* --------------------------------------------------------------------------- + Event Stream — Row + --------------------------------------------------------------------------- */ + +.logfire-stream-row { + display: flex; + gap: 8px; + padding: 2px 10px; + border-bottom: 1px solid transparent; + transition: background 60ms ease; + white-space: nowrap; + overflow: hidden; +} + +.logfire-stream-row:hover { + background: var(--background-secondary); + border-bottom-color: var(--background-modifier-border); +} + +/* Alternating row tint for scanability */ +.logfire-stream-row:nth-child(even) { + background: color-mix(in srgb, var(--background-secondary) 30%, transparent); +} + +.logfire-stream-row:nth-child(even):hover { + background: var(--background-secondary); +} + +/* --------------------------------------------------------------------------- + Event Stream — Row Segments + --------------------------------------------------------------------------- */ + +.logfire-stream-time { + color: var(--text-faint); + flex-shrink: 0; + font-variant-numeric: tabular-nums; +} + +.logfire-stream-type { + color: var(--text-accent); + flex-shrink: 0; + font-weight: 600; + min-width: 0; +} + +.logfire-stream-source { + color: var(--text-normal); + overflow: hidden; + text-overflow: ellipsis; + min-width: 0; + flex-shrink: 1; +} + +.logfire-stream-payload { + color: var(--text-faint); + flex-shrink: 0; + margin-left: auto; + font-variant-numeric: tabular-nums; +} + +/* --------------------------------------------------------------------------- + Status Bar + --------------------------------------------------------------------------- */ + +.logfire-status-bar { + cursor: pointer; + font-family: var(--font-monospace); + font-size: 11px; + letter-spacing: 0.01em; + padding: 0 4px; + font-variant-numeric: tabular-nums; + transition: color 100ms ease; +} + +.logfire-status-bar:hover { + color: var(--text-accent); +} + +/* --------------------------------------------------------------------------- + Scan Modal + --------------------------------------------------------------------------- */ + +.logfire-scan-modal { + font-family: var(--font-monospace); +} + +.logfire-scan-modal h2 { + font-size: 16px; + font-weight: 700; + letter-spacing: -0.01em; + margin-bottom: 8px; + color: var(--text-normal); +} + +.logfire-scan-modal > p { + color: var(--text-muted); + font-size: 12px; + line-height: 1.5; + margin-bottom: 16px; +} + +.logfire-scan-progress { + margin-bottom: 12px; +} + +.logfire-scan-progress progress { + width: 100%; + height: 6px; + border: none; + border-radius: 3px; + overflow: hidden; + appearance: none; + -webkit-appearance: none; +} + +.logfire-scan-progress progress::-webkit-progress-bar { + background: var(--background-modifier-border); + border-radius: 3px; +} + +.logfire-scan-progress progress::-webkit-progress-value { + background: var(--interactive-accent); + border-radius: 3px; + transition: width 200ms ease; +} + +.logfire-scan-status { + font-size: 11.5px; + line-height: 1.5; + color: var(--text-normal); + word-break: break-all; +} + +.logfire-scan-status > div:last-child { + color: var(--text-muted); + font-variant-numeric: tabular-nums; +} + +.logfire-scan-buttons { + display: flex; + justify-content: flex-end; + gap: 8px; + margin-top: 16px; +} + +.logfire-scan-buttons button { + font-family: var(--font-monospace); + font-size: 12px; + letter-spacing: 0.02em; +} From bf63f5f9e388cb9ab7c354a40eb0dde89a1ba7f1 Mon Sep 17 00:00:00 2001 From: tolvitty Date: Thu, 12 Feb 2026 10:55:28 +0100 Subject: [PATCH 4/4] Echtzeit-UI in main.ts verdrahtet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Event-Stream-View registriert, StatusBar mit Live-Updates, Ribbon-Icon für Event-Stream, Kommando 'Event-Stream anzeigen', activateEventStream-Methode. Co-Authored-By: Claude Opus 4.6 --- src/main.ts | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/main.ts b/src/main.ts index ceb0da5..ad32f96 100644 --- a/src/main.ts +++ b/src/main.ts @@ -11,6 +11,8 @@ import { EditorCollector } from './collectors/editor-collector'; import { SystemCollector } from './collectors/system-collector'; import { LogfireSettingTab } from './ui/settings-tab'; import { InitialScanModal } from './ui/initial-scan-modal'; +import { StatusBar } from './ui/status-bar'; +import { EventStreamView, EVENT_STREAM_VIEW_TYPE } from './ui/event-stream-view'; export default class LogfirePlugin extends Plugin { settings!: LogfireSettings; @@ -24,6 +26,7 @@ export default class LogfirePlugin extends Plugin { private navCollector!: NavCollector; private editorCollector!: EditorCollector; private systemCollector!: SystemCollector; + private statusBar!: StatusBar; private paused = false; @@ -77,6 +80,21 @@ export default class LogfirePlugin extends Plugin { // UI: Settings tab this.addSettingTab(new LogfireSettingTab(this.app, this)); + // UI: Event stream view + this.registerView( + EVENT_STREAM_VIEW_TYPE, + (leaf) => new EventStreamView(leaf, this.eventBus), + ); + + // UI: Status bar + this.statusBar = new StatusBar(this); + this.statusBar.start(); + + // Ribbon icon + this.addRibbonIcon('activity', 'Logfire: Event-Stream', () => { + this.activateEventStream(); + }); + // Commands this.registerCommands(); @@ -107,6 +125,7 @@ export default class LogfirePlugin extends Plugin { async onunload(): Promise { console.log('[Logfire] Entlade Plugin...'); + this.statusBar?.destroy(); this.stopTracking(); if (this.sessionManager) { @@ -217,6 +236,12 @@ export default class LogfirePlugin extends Plugin { // --------------------------------------------------------------------------- private registerCommands(): void { + this.addCommand({ + id: 'show-event-stream', + name: 'Event-Stream anzeigen', + callback: () => this.activateEventStream(), + }); + this.addCommand({ id: 'toggle-tracking', name: 'Tracking pausieren/fortsetzen', @@ -263,6 +288,23 @@ export default class LogfirePlugin extends Plugin { }); } + // --------------------------------------------------------------------------- + // Event stream view + // --------------------------------------------------------------------------- + + private async activateEventStream(): Promise { + const existing = this.app.workspace.getLeavesOfType(EVENT_STREAM_VIEW_TYPE); + if (existing.length > 0) { + this.app.workspace.revealLeaf(existing[0]); + return; + } + const leaf = this.app.workspace.getRightLeaf(false); + if (leaf) { + await leaf.setViewState({ type: EVENT_STREAM_VIEW_TYPE, active: true }); + this.app.workspace.revealLeaf(leaf); + } + } + // --------------------------------------------------------------------------- // Settings // ---------------------------------------------------------------------------