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

@@ -1,9 +1,12 @@
import { useShallow } from "zustand/react/shallow";
import { useAppState } from "../../store/appState";
import ProjectList from "../projects/ProjectList";
import SettingsPanel from "../settings/SettingsPanel";
export default function Sidebar() {
const { sidebarView, setSidebarView } = useAppState();
const { sidebarView, setSidebarView } = useAppState(
useShallow(s => ({ sidebarView: s.sidebarView, setSidebarView: s.setSidebarView }))
);
return (
<div className="flex flex-col h-full w-[25%] min-w-56 max-w-80 bg-[var(--bg-secondary)] border border-[var(--border-color)] rounded-lg overflow-hidden">

View File

@@ -1,7 +1,10 @@
import { useShallow } from "zustand/react/shallow";
import { useAppState } from "../../store/appState";
export default function StatusBar() {
const { projects, sessions } = useAppState();
const { projects, sessions } = useAppState(
useShallow(s => ({ projects: s.projects, sessions: s.sessions }))
);
const running = projects.filter((p) => p.status === "running").length;
return (

View File

@@ -1,8 +1,11 @@
import { useShallow } from "zustand/react/shallow";
import TerminalTabs from "../terminal/TerminalTabs";
import { useAppState } from "../../store/appState";
export default function TopBar() {
const { dockerAvailable, imageExists } = useAppState();
const { dockerAvailable, imageExists } = useAppState(
useShallow(s => ({ dockerAvailable: s.dockerAvailable, imageExists: s.imageExists }))
);
return (
<div className="flex items-center h-10 bg-[var(--bg-secondary)] border border-[var(--border-color)] rounded-lg overflow-hidden">