Beetroot v1.6.0: Rust-Suchengine, Fenster ohne Fokus
Beetroots Suchengine zog nach Rust um: Akzent-Folding, Tippfehler-Toleranz, Prefix-Matching. Plus ein Fenster ohne Fokus, das deinen Workflow nicht unterbricht.
Das mit Abstand nervigste an Beetroot war, dass das Öffnen den Fokus stahl. Du benennst eine Datei im Explorer um, drückst F2, fängst an zu tippen, und öffnest dann Beetroot, um etwas aus der Historie zu holen. Der Explorer verliert den Fokus. Dein Rename ist weg. Dasselbe mit IDE-Refactoring-Dialogen, Suchfeldern, allem, was auf Fokus angewiesen ist.
v1.6.0 behebt das. Und nebenbei habe ich die Suchengine in Rust neu geschrieben.
Auf einen Blick:
- Fenster ohne Fokus: Beetroot stiehlt anderen Apps nicht mehr den Fokus
- Rust-Suchengine: Akzent-Folding, Tippfehler-Toleranz, Prefix-Matching. Fuse.js ist raus
- Fenster-Position: wähle, wo Beetroot erscheint: Center, Top Left, Top Right, Bottom Left, Bottom Right
- 4 neue Text-Transforms: Remove spaces, Single line, Sort lines, Remove duplicates
- Suche im Transform-Menü: filter durch deine Transforms, wenn die Liste zu lang wird
- 10 Bug-Fixes: inklusive Snipping Tool Capture, Regex-Crashes und AI
<think>-Tags
Ein Clipboard-Manager, der dich nicht unterbricht
Vor v1.6.0 war das Öffnen von Beetroot wie Alt-Tab. Es nahm den Fokus von dem, was du gerade tatest. F2-Rename im Explorer? Weg. IDE-Refactoring-Dialog? Fokus verloren. Jeder "tippe hier"-Prompt: unterbrochen.
Das klingt nach einem One-Liner. War es nicht.
Tauris v2 win.show() ruft im Hintergrund ShowWindow(SW_SHOW) auf, was das Fenster aktiviert. Bei einer normalen App ist das kein Problem. Aber Beetroots Frontend läuft in WebView2 (Chromium), und WebView2 hat eigene Vorstellungen vom Fokus. Jeder Standard-Win32-Ansatz ist gescheitert:
| Ansatz | Warum es nicht funktionierte |
|---|---|
WS_EX_NOACTIVATE | WebView2 Child-Process ignoriert das Flag |
SW_SHOWNOACTIVATE | WebView2 schnappt sich trotzdem den Fokus |
| Focus-Bounce (SetForegroundWindow zurück) | WM_KILLFOCUS ist schon gefeuert. Zu spät |
Subclass WM_ACTIVATE auf Parent | WebView2-Children umgehen Parent-Messages |
Die Lösung: LockSetForegroundWindow(LSFW_LOCK), ein OS-Level-Lock, der jedem Prozess (auch WebView2s Chromium-Subprozess) verbietet, das Vordergrundfenster zu ändern. Lock vor Show, Unlock danach. Ein 30-Sekunden-Safety-Timer entriegelt automatisch, falls etwas schiefgeht.
Da Beetroot keinen Fokus hat, kommt Tastatur-Input nicht normal an. Navigation funktioniert über einen Low-Level Keyboard Hook (WH_KEYBOARD_LL), der Pfeiltasten, Enter, Escape, Space und Alt-Kombinationen abfängt, wenn der No-Focus-Modus aktiv ist. Klicks außerhalb des Fensters blenden es aus, dasselbe Verhalten wie Win+V.

Ein Bonus: Beetroot erscheint kurz über always-on-top-Fenstern wie dem Task-Manager, wenn es per Hotkey aufgerufen wird. Sonst würdest du es nie sehen, wenn eine Vollbild- oder Always-on-Top-App aktiv ist.
Suche, neu in Rust geschrieben
Die v1.5.1-Suche war ein TypeScript-Scoring-System: 5 Phasen, Map-basiertes Dedup, Fuse.js für Fuzzy. Sie funktionierte für den Alltag gut. Aber die Suche lief im UI-Thread, und jeder Tastendruck schob alle Items per IPC in den React-State. Bei 500 Items kein Problem. Bei 10K schon.
v1.6.0 verschiebt die gesamte Suche in ein Rust-Backend (~700 Zeilen in search.rs). Nicht Tantivy, nicht Nucleo, eine Custom-Implementierung, die die 5-Phasen-Architektur aus v1.5.1 spiegelt, aber nativ läuft:
| Phase | Was sie tut | Score |
|---|---|---|
| 1 | Zusammenhängender Substring in Content/Note | 1.0 |
| 2 | Wortanfangs-Tokens (camelCase, Underscore, Hyphen) | 0.75 |
| 3 | Zusammenhängender Substring in Quell-App/Titel | 0.5 |
| 4 | Wortanfangs-Tokens in Quell-App/Titel | 0.25 |
| 5 | Levenshtein-Distanz ≤ 1 + Prefix-Match ≥ 60% | 0.05–0.15 |
Phase 5 ersetzt Fuse.js komplett. Fuse.js nutzte einen modifizierten Bitap-Algorithmus, der auf kurzen Strings großartig funktionierte, aber auf langen Clipboard-Inhalten Müll produzierte. Die neue Fuzzy-Phase nutzt Edit-Distance pro Wort. Jedes Wort der Query wird mit jedem Wort des Inhalts verglichen. "timout" matcht "timeout" (Distanz 1). "mecrosoft" matcht nicht "Microsoft" (Distanz 2). Sauber, vorhersehbar.
Akzent-Folding: "cafe" findet "café", "resume" findet "résumé". Die Engine nutzt Unicode NFD-Decomposition, um Combining Marks vor dem Matchen zu entfernen. Sowohl Query als auch Inhalt werden gleich normalisiert, sodass Diakritika für die Suche unsichtbar werden.
Der eigentliche Gewinn ist nicht Speed, sondern Architektur. Die JS-Suche lud bei jedem Tastendruck alle Items per IPC in den React-State (500 Objekte pro Aufruf). Die Rust-Suche gibt nur gefilterte Ergebnisse + Match-Indizes + Filter-Counts in einem einzigen IPC-Call zurück. Weniger Daten über die Bridge, weniger React-Rendering, flüssigeres Tippen.
| Items | JS (v1.5.1) | Rust (v1.6.0) |
|---|---|---|
| 500 | ~2 ms | ~2 ms |
| 10K | — | ~7 ms |
| 100K | — | ~50 ms (geschätzt) |
Bei 500 Items ist die reine Suchzeit gleich. Der Unterschied liegt in allem drumherum: IPC-Payload, React-State-Updates, Render-Cycles.
Fenster-Position
Beetroot erschien immer in der Mitte deines aktuellen Monitors. Jetzt kannst du wählen: Center, Top Left, Top Right, Bottom Left oder Bottom Right.

Im selben Settings-Panel gibt es jetzt Remember selected filter. Dein zuletzt aktiver Filter (Starred, Text, Images usw.) bleibt zwischen den Öffnungen erhalten, statt jedes Mal auf All zurückgesetzt zu werden.
Suche im Transform-Menü
Wenn du eigene AI-Prompts zusätzlich zu den eingebauten Transforms angelegt hast, wird das Menü lang. Jetzt gibt es oben ein Suchfeld. Tippe "upper", um zu UPPERCASE zu springen, "sort" für Sort lines.

4 neue Text-Transforms:
| Transform | Was es tut |
|---|---|
| Remove spaces | Entfernt allen Whitespace |
| Single line | Verbindet mehrzeiligen Text zu einer Zeile |
| Sort lines | Alphabetische Sortierung |
| Remove duplicates | Dedupliziert Zeilen |
Keine KI nötig, sie laufen sofort, lokal.
Weitere Verbesserungen
- Bild-Quellen-Tracking: Bilder erfassen jetzt, aus welcher App und welchem Fenster sie kamen, genau wie Text-Clips
- Lokalisierte Timestamps: "vor 3 Min", "vor 2 Tagen" Labels nutzen die App-Sprache statt immer Englisch
- Unicode Title Case: funktioniert korrekt mit Kyrillisch, CJK, Arabisch und anderen Schriften
- Bessere KI-Fehler: Rate-Limits und Content-Filter-Blocks zeigen spezifische Meldungen statt generischer Fehler
- Space togglet Vorschau: Space drücken zum Öffnen, nochmal Space zum Schließen (vorher war Escape nötig)
- Pinned-Modus Paste-Feedback: zeigt einen "Copied to clipboard"-Toast
Bug-Fixes
Snipping-Tool-Screenshots werden nicht erfasst: zwei Bugs übereinander gestapelt. Erstens: Windows' CanIncludeInClipboardHistory-Format (genutzt von Snipping Tool, Office, Notepad) wurde fälschlich als Password-Manager-Signal erkannt, also wurden Screenshots verworfen. Zweitens: das Clipboard-Plugin checkte File-Formate vor Image-Formaten. Snipping Tool setzt beides, und der File-Check kam vor dem Image-Check zum Zug.
Regex-Crashes: Patterns wie ^, a*, b?, die leere Strings matchen, verursachten Endlosschleifen. Werden jetzt sauber abgefangen.
AI <think>-Tags: Anthropic Claude und DeepSeek-Modelle wickeln Antworten manchmal in <think>...</think> Reasoning-Tags. Die werden jetzt aus dem Output entfernt.
Sortierung von Starred-Items: Starred Clips schwammen fälschlich nach oben im All-Tab. Jetzt strikt chronologisch. Nutze den Starred-Filter, um nur Starred Clips zu sehen.
Plus: Rich-Text-Paste-Duplikate, lokaler KI-Endpoint, der die UI bis zu 5 Sekunden blockierte, Clipboard-Monitor-Race bei schnellem Sleep/Wake, Overlays bleiben nach Hide/Show bestehen, Transform-Ergebnisse wirken nach geschlossenem Menü, Suche-Highlight an falscher Position bei mehrzeiligen Clips.
Update
Beetroot bietet das Update automatisch an. Oder v1.6.0 von GitHub herunterladen.