Fix frontend UX: debounce saves, Zustand selectors, init race, dialog

- Debounce project config saves: use local state + save-on-blur instead
  of firing IPC requests on every keystroke in text inputs
- Add Zustand selectors to all store consumers to prevent full-store
  re-renders on any state change
- Fix initialization race: chain checkImage after checkDocker resolves
- Fix DockerSettings setTimeout race: await checkImage after save
- Add console.error logging to all 11 empty catch blocks in ProjectCard
- Add keyboard support to AddProjectDialog: Escape to close,
  click-outside-to-close, form submit on Enter, auto-focus

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-28 20:42:40 +00:00
parent a03bdccdc7
commit 82f159d2a9
12 changed files with 370 additions and 114 deletions

View File

@@ -33,8 +33,7 @@ export default function DockerSettings() {
const handleSourceChange = async (source: ImageSource) => {
if (!appSettings) return;
await saveSettings({ ...appSettings, image_source: source });
// Re-check image existence after changing source
setTimeout(() => checkImage(), 100);
await checkImage();
};
const handleCustomChange = async (value: string) => {

View File

@@ -1,3 +1,4 @@
import { useState, useEffect } from "react";
import ApiKeyInput from "./ApiKeyInput";
import DockerSettings from "./DockerSettings";
import AwsSettings from "./AwsSettings";
@@ -5,6 +6,17 @@ import { useSettings } from "../../hooks/useSettings";
export default function SettingsPanel() {
const { appSettings, saveSettings } = useSettings();
const [globalInstructions, setGlobalInstructions] = useState(appSettings?.global_claude_instructions ?? "");
// Sync local state when appSettings change
useEffect(() => {
setGlobalInstructions(appSettings?.global_claude_instructions ?? "");
}, [appSettings?.global_claude_instructions]);
const handleInstructionsBlur = async () => {
if (!appSettings) return;
await saveSettings({ ...appSettings, global_claude_instructions: globalInstructions || null });
};
return (
<div className="p-4 space-y-6">
@@ -20,11 +32,9 @@ export default function SettingsPanel() {
Global instructions applied to all projects (written to ~/.claude/CLAUDE.md in containers)
</p>
<textarea
value={appSettings?.global_claude_instructions ?? ""}
onChange={async (e) => {
if (!appSettings) return;
await saveSettings({ ...appSettings, global_claude_instructions: e.target.value || null });
}}
value={globalInstructions}
onChange={(e) => setGlobalInstructions(e.target.value)}
onBlur={handleInstructionsBlur}
placeholder="Instructions for Claude Code in all project containers..."
rows={4}
className="w-full px-2 py-1.5 text-xs bg-[var(--bg-primary)] border border-[var(--border-color)] rounded focus:outline-none focus:border-[var(--accent)] resize-y font-mono"