fix(quick-pane): polish spotlight lifecycle

This commit is contained in:
2026-08-25 21:53:00 +02:00
parent bab754714e
commit a204eea7da
5 changed files with 213 additions and 47 deletions
+43 -20
View File
@@ -77,30 +77,39 @@ export default function QuickPane() {
const [extension, setExtension] = useState<Extension>();
const runtimeRef = useRef<Runtime | undefined>(undefined);
const requestRef = useRef(request);
const inputRef = useRef<HTMLTextAreaElement>(null);
const phaseRef = useRef<"idle" | "starting" | "running">("idle");
const invalid = useMemo(() => settingsError(settings), [settings]);
const dismissPane = () => getCurrentWindow().hide();
const focusInput = () => inputRef.current?.focus();
useEffect(() => {
let active = true;
invoke<UiState>("load_ui_state")
.then((state) => {
void (async () => {
let nextSettings: QuickPaneSettings;
try {
const state = await invoke<UiState>("load_ui_state");
if (!active) return;
const nextSettings = { ...defaults, ...state.quickPane };
setSettings(nextSettings);
setSettingsLoaded(true);
setSelectedModel(`${nextSettings.provider}/${nextSettings.model}`);
setStatus("");
return invoke<{ models?: Model[] }>("get_model_catalog");
})
.then((catalog) => {
nextSettings = { ...defaults, ...state.quickPane };
} catch (reason) {
if (active)
setError(`Could not load settings: ${String(reason)}`);
return;
}
setSettings(nextSettings);
setSettingsLoaded(true);
setSelectedModel(`${nextSettings.provider}/${nextSettings.model}`);
setStatus("");
try {
const catalog = await invoke<{ models?: Model[] }>("get_model_catalog");
if (active) setModels(catalog?.models ?? []);
})
.catch(
(reason) =>
active && setError(`Could not load settings: ${String(reason)}`),
);
} catch (reason) {
if (active)
setError(`Could not load model catalog: ${String(reason)}`);
}
})();
return () => {
active = false;
const current = runtimeRef.current;
@@ -111,6 +120,10 @@ export default function QuickPane() {
};
}, []);
useEffect(() => {
if (settingsLoaded) focusInput();
}, [settingsLoaded]);
useEffect(() => {
const paneWindow = getCurrentWindow();
const settingsListener = listen<QuickPaneSettings>(
@@ -126,12 +139,11 @@ export default function QuickPane() {
};
window.addEventListener("keydown", onKeyDown);
const focusListener = paneWindow.onFocusChanged((event) => {
if (
!event.payload &&
phaseRef.current === "idle" &&
!requestRef.current.trim()
)
if (event.payload) {
focusInput();
} else if (phaseRef.current === "idle" && !requestRef.current.trim()) {
void dismissPane();
}
});
const closeListener = paneWindow.onCloseRequested((event) => {
event.preventDefault();
@@ -299,11 +311,22 @@ export default function QuickPane() {
</header>
<textarea
autoFocus
ref={inputRef}
value={request}
onChange={(e) => {
requestRef.current = e.target.value;
setRequest(e.target.value);
}}
onKeyDown={(event) => {
if (
event.key === "Enter" &&
!event.shiftKey &&
!event.nativeEvent.isComposing
) {
event.preventDefault();
void run();
}
}}
placeholder="Ask a quick question or give a quick instruction"
style={{ width: "100%", minHeight: 90, marginTop: 14 }}
/>
+106 -16
View File
@@ -1,20 +1,110 @@
:root { color-scheme: dark; }
body { margin: 0; background: transparent; }
:root {
color-scheme: dark;
}
body {
min-width: 0;
min-height: 0;
margin: 0;
background: transparent;
}
.quick-pane {
box-sizing: border-box;
min-height: 100vh;
padding: 18px;
color: #f4f2ee;
font: 14px/1.4 system-ui, sans-serif;
background: radial-gradient(circle at top, #46494f, #23252a 62%);
border: 1px solid rgba(255,255,255,.16);
border-radius: 14px;
box-shadow: 0 22px 70px rgba(0,0,0,.55);
overflow-y: auto;
padding: 12px;
color: #e6e3df;
font: 14px/1.4 Inter, ui-sans-serif, system-ui, sans-serif;
background: rgba(34, 34, 34, 0.98);
border: 1px solid #4b4b4b;
border-radius: 8px;
box-shadow: 0 22px 70px rgba(0, 0, 0, 0.55);
}
.quick-pane header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
color: #f0a347;
font-size: 11px;
font-weight: 900;
letter-spacing: 0.12em;
text-transform: uppercase;
}
.quick-pane button,
.quick-pane select {
border: 0;
border-radius: 5px;
padding: 6px 9px;
font: inherit;
}
.quick-pane button {
margin-right: 6px;
background: #f0a347;
color: #25180b;
font-weight: 750;
cursor: pointer;
}
.quick-pane button:hover {
background: #ffc170;
}
.quick-pane button:disabled {
opacity: 0.6;
cursor: wait;
}
.quick-pane select {
color: #e6e3df;
background: #353535;
}
.quick-pane textarea {
box-sizing: border-box;
width: 100%;
min-height: 92px;
padding: 12px;
border: 1px solid #484848;
border-radius: 6px;
color: #e6e3df;
background: #292929;
font: inherit;
font-size: 16px;
resize: vertical;
}
.quick-pane textarea:focus,
.quick-pane button:focus-visible,
.quick-pane select:focus-visible {
outline: 3px solid #f0a347;
outline-offset: 2px;
}
.quick-pane > label {
display: inline-flex;
align-items: center;
gap: 7px;
margin: 10px 8px 10px 0;
color: #bbb7b0;
}
.quick-pane > pre,
.quick-pane > p {
margin: 10px 0 0;
padding: 10px 12px;
border: 1px solid #484848;
border-radius: 6px;
white-space: pre-wrap;
background: #292929;
}
.quick-pane [role="alert"] {
border-color: #a65b67;
color: #ffd7db;
background: #3d252b;
}
.quick-pane header { display:flex; align-items:center; justify-content:space-between; margin-bottom:12px; color:#c9c7c2; font-size:12px; letter-spacing:.08em; text-transform:uppercase; }
.quick-pane button, .quick-pane select { border:0; border-radius:7px; padding:7px 10px; color:inherit; background:#45484e; }
.quick-pane textarea { box-sizing:border-box; width:100%; min-height:92px; padding:14px; border:0; border-radius:10px; outline:1px solid rgba(255,255,255,.14); color:#fff; background:rgba(0,0,0,.24); font:inherit; font-size:16px; resize:none; }
.quick-pane textarea:focus { outline:2px solid #8aa9d6; background:rgba(0,0,0,.38); }
.quick-pane > label { display:inline-flex; align-items:center; gap:7px; margin:10px 8px 10px 0; color:#c9c7c2; }
.quick-pane > pre, .quick-pane > p { margin:10px 0 0; padding:10px 12px; border-radius:9px; white-space:pre-wrap; background:rgba(0,0,0,.22); }
.quick-pane [role="alert"] { color:#ffb8bd; background:rgba(119,42,52,.42); }