Repository navigation
feat(fix): silent hotkey fix feedback — tray toast, source-window paste - #110
Merged
Merged
Conversation
A timed-out attempt is no longer retried when what is left of the caller's deadline cannot fit another attempt (#93). Timeouts read "<provider> took too long to answer — try a shorter selection or a faster model" and still unwrap to context.DeadlineExceeded. Status, signed-out, CLI-missing, output-limit and missing-key failures get types so a caller can act on the reason; their wording is unchanged. Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
The fix shortcut now runs entirely in Go (internal/features/silentfix): it captures the source window at the key press and pastes only into that window while it still has focus; otherwise the fix stays on the clipboard. One run at a time, guarded by a per-run token, so a second press neither starts a fix nor copies, and a late run cannot paste or release a newer one. A dot on the tray icon shows a fix in progress. A failure or an unpasted fix is one silent, non-modal toast (internal/features/notify, go-toast with the AppUserModelID registry method); identical toasts are not repeated within 30 s. A click opens AI Providers for key, sign-in and model problems, otherwise the Fix page with the reason and the text. Closes #93 Co-Authored-By: Claude <noreply@anthropic.com>
- nothing copied (clipboard unchanged) no longer fixes and pastes the old clipboard: one "Nothing selected" toast instead - the focus check now runs after the clipboard settle delay, right before Ctrl+V, and includes the focused control where Windows has one - a panic in a run is recovered and releases the guard - the retry skip can no longer hand the SDK (nil, nil); a 429 waited out past the deadline reads as a rate limit, not a timeout - notice IDs carry a per-process nonce; a suppressed repeat updates the data behind the toast on screen - tray busy state is posted to the main thread, never waited on - a second click to AI Providers works with the URL unchanged, and the tab says why it was opened until dismissed or acted on - notices only show on their own page and expire if unclaimed - deterministic silentfix test harness Co-Authored-By: Claude <noreply@anthropic.com>
This was referenced Oct 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Since #31, Ctrl+G runs the silent fix on any route with the window hidden — and a failure (missing key, signed-out CLI, timeout, cut-off) only went to the log. This PR gives it feedback that does not get in the way, and folds in #93, which is the same path.
Architecture change: the silent fix runs in Go
The read → enhance → write → paste pipeline moved from
ShellComponent(hidden webview) tointernal/features/silentfix. Reasons: the source window has to be captured at the key press and held per run; the failure reason is a Go error type; the toast is an OS call; and the guard must not depend on timers in a hidden page. Theshortcut:fixevent is gone; the frontend only hearssilentfix:openwhen a toast is clicked.To decide on reasons without matching strings,
llmandenhancenow return typed errors (StatusError,TimeoutError,ErrNotSignedIn,ErrCLINotFound,ErrOutputLimit[Thinking],enhance.MissingKeyError). Their text is unchanged except for timeouts.Mechanism choice: toast via go-toast, not a tray balloon, not the Wails service
Shell_NotifyIcon+NIF_INFO) via the existing trayhWnd/uIDand its click arrives asNIN_BALLOONUSERCLICKin that window's procedure (NOTIFYICONDATAW). Both are private in Wails (pkg/application/systemtray_windows.go:hwnd/uidunexported,wndProchandles no balloon messages). Not reachable without patching Wails.pkg/services/notifications)notifications_windows.go: noAudio, so go-toast'sapplyDefaultspicksNotification.Default), falls back to spawning PowerShell when COM fails, and a service startup error would be fatal to the app.Audio: silent), short,activationType="foreground"so a click reaches the app; pushed over COM only, no PowerShell; setup failures are logged and turn toasts off, never stop the app.Registration (
internal/features/notify/notify_windows.go): the AppUserModelID registry method for unpackaged apps —HKCU\Software\Classes\AppUserModelId\KeyLintwithDisplayName,IconUri,CustomActivator, plusHKCU\Software\Classes\CLSID\{KeyLint GUID}\LocalServer32— the same method go-toast and Wails use. No Start-menu shortcut or AUMID on the shortcut is needed, so nothing changes in the NSIS install step; it works identically for the NSIS install, dev-channel builds (same installer) and an exe run from a build folder. Values are rewritten on every start, so the paths follow whichever exe ran last. The activator class is registered at startup on its own MTA thread, so a click on a toast left in the Action Center by an earlier session reaches the running instance instead of starting a second one;-Embedding(what COM appends when it has to start KeyLint) is accepted as a flag. KeyLint has its own activator GUID — go-toast's default is shared by every app that keeps it. The NSIS uninstaller now deletes both keys.Toasts never contain user text (Windows keeps them in the Action Center database on disk); the text stays in memory for the Fix page. Linux: log only.
Behaviour per outcome
Decision on how a slow fix feels (#93's open question)
Owner's intent: "es sollte nicht stören, also bestenfalls ein kleines Tray-Popup". Implemented as: wait (up to the existing 120 s) with the tray dot as the only sign, paste only into the source window. If the user has moved on, KeyLint does not pull the focus back to the source window to paste (as Pyramidize's SendBack does): they may be typing elsewhere, and a focus switch mid-word sends their keystrokes to the wrong place. The fix waits on the clipboard and the toast says so. Recorded in
internal/features/silentfix/service.goand themaxTokenscomment inenhance/service.go.Intent answers (decided in line with the owner's "should not disturb, no paternalism")
/settings?tab=providers.Closes #93
Tests
llm— retry decision table, timed-out attempt not retried into the deadline (Anthropic + OpenAI, real HTTP), retried when there is room, typed errors keep their wording, CLI signed-out / timeout / not-installed types.silentfix— success pastes silently, second press dropped (one copy, one paste), late run after the safety release cannot clear a newer guard or paste, switched window / closed window not pasted, empty selection skips the model, repeated identical failures shown once until the quiet period ends, notice lookup for a click,decidePasteanddescribetables.tray— busy icon.shortcut—delivernever blocks.notify— activation routing.go test ./internal/...✔,-raceon llm, silentfix, tray, shortcut, enhance, notify ✔,go build✔,GOOS=windows go build✔,npm test351/351 ✔, Playwright 74 passed / 3 skipped (the existing skips).Windows manual test
claude /logoutin a terminal. Select text in Notepad, press Ctrl+G. → One toast "Fix didn't run — Claude Code CLI isn't signed in.", no sound, Notepad keeps focus. Click it → KeyLint opens on Settings › AI Providers. Press Ctrl+G three more times within 30 s → no further toasts.HKCU\Software\Classes\AppUserModelId\KeyLintis gone.Review (Opus, one round) — what changed after it
Fixed: stale clipboard fixed and pasted (above); 150 ms gap between the focus check and Ctrl+V; same window but another field; a retry-skip path that could hand the Anthropic SDK
(nil, nil)(nil-pointer panic); notice IDs colliding across sessions (now carry a per-process nonce); suppressed repeats pointing the visible toast at stale text; tray busy state now posted to the main thread instead of waited on under the silent-fix lock; panic recovery in the run (guard released even mid-paste); 429-past-deadline wording; second click to AI Providers with an unchanged URL; notices only shown on their own page and dropped after 10 s if unclaimed; flaky silentfix tests (the harness took the guard release for the notification; now waits for the notice —-race -count=10clean); 408/413 wording; "Anthropic" in toasts to match the AI Providers card;CoInitializeExS_FALSE;fixClipboard()clears the notice; staleshortcut:fixbus type; uninstaller removes the toast icon; roadmap note.Not in this PR
KeyLint.exe -Embeddinga second time, which installs a second hook. Unlikely; a single-instance guard (WailsSingleInstanceor a named mutex for-Embedding) is the follow-up.sendBackToWindowbuilds itsINPUTstruct with type 2 (hardware) and the wrong layout — likely why SendBack paste is unreliable. Not touched here.