From 40e9d7917c6b00b4e32febc61c630ab3b47692b0 Mon Sep 17 00:00:00 2001 From: iliya Date: Sun, 22 Mar 2026 15:02:07 +0200 Subject: [PATCH] feat(team): persist create-team form state across tab navigation Add IndexedDB-backed draft persistence for CreateTeamDialog so that navigating away (e.g. to Dashboard to install CLI) and back preserves all form fields: team name, members, paths, solo/launch flags, color. - New createTeamDraftStorage service (IDB + in-memory fallback) - New useCreateTeamDraft hook (debounced save, flush on unmount, race-safe) - Gate useEffect for defaults/initialData on draftLoaded to prevent race - Block submit button until draft is loaded - Improve CLI-missing error in LaunchTeamDialog with Dashboard navigation - Remove notification bell button from MessagesPanel --- .../team/dialogs/CreateTeamDialog.tsx | 65 +-- .../team/dialogs/LaunchTeamDialog.tsx | 22 +- .../team/messages/MessagesPanel.tsx | 36 -- src/renderer/hooks/useCreateTeamDraft.ts | 376 ++++++++++++++++++ .../services/createTeamDraftStorage.ts | 176 ++++++++ 5 files changed, 609 insertions(+), 66 deletions(-) create mode 100644 src/renderer/hooks/useCreateTeamDraft.ts create mode 100644 src/renderer/services/createTeamDraftStorage.ts diff --git a/src/renderer/components/team/dialogs/CreateTeamDialog.tsx b/src/renderer/components/team/dialogs/CreateTeamDialog.tsx index f038461a..f2d75f2a 100644 --- a/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +++ b/src/renderer/components/team/dialogs/CreateTeamDialog.tsx @@ -25,6 +25,7 @@ import { Label } from '@renderer/components/ui/label'; import { MentionableTextarea } from '@renderer/components/ui/MentionableTextarea'; import { getTeamColorSet, getThemedBadge } from '@renderer/constants/teamColors'; import { useChipDraftPersistence } from '@renderer/hooks/useChipDraftPersistence'; +import { useCreateTeamDraft } from '@renderer/hooks/useCreateTeamDraft'; import { useDraftPersistence } from '@renderer/hooks/useDraftPersistence'; import { useFileListCacheWarmer } from '@renderer/hooks/useFileListCacheWarmer'; import { useTaskSuggestions } from '@renderer/hooks/useTaskSuggestions'; @@ -43,8 +44,6 @@ import { SkipPermissionsCheckbox } from './SkipPermissionsCheckbox'; import { computeEffectiveTeamModel, TeamModelSelector } from './TeamModelSelector'; import { getNextSuggestedTeamName } from './teamNameSets'; -import type { MemberDraft } from '@renderer/components/team/members/membersEditorTypes'; - const TEAM_COLOR_NAMES = [ 'blue', 'green', @@ -227,14 +226,33 @@ export const CreateTeamDialog = ({ }: CreateTeamDialogProps): React.JSX.Element => { const { isLight } = useTheme(); - const [teamName, setTeamName] = useState(''); + // ── Persisted draft state (survives tab navigation) ────────────────── + const { + teamName, + setTeamName, + members, + setMembers, + cwdMode, + setCwdMode, + selectedProjectPath, + setSelectedProjectPath, + customCwd, + setCustomCwd, + soloTeam, + setSoloTeam, + launchTeam, + setLaunchTeam, + teamColor, + setTeamColor, + isLoaded: draftLoaded, + clearDraft, + } = useCreateTeamDraft(); + const descriptionDraft = useDraftPersistence({ key: 'createTeam:description' }); const promptDraft = useDraftPersistence({ key: 'createTeam:prompt' }); const promptChipDraft = useChipDraftPersistence('createTeam:prompt:chips'); - const [members, setMembers] = useState([]); - const [cwdMode, setCwdMode] = useState<'project' | 'custom'>('project'); - const [selectedProjectPath, setSelectedProjectPath] = useState(''); - const [customCwd, setCustomCwd] = useState(''); + + // ── Transient UI state (NOT persisted) ─────────────────────────────── const [projects, setProjects] = useState([]); const [projectsLoading, setProjectsLoading] = useState(false); const [projectsError, setProjectsError] = useState(null); @@ -250,9 +268,6 @@ export const CreateTeamDialog = ({ cwd?: string; }>({}); const [isSubmitting, setIsSubmitting] = useState(false); - const [launchTeam, setLaunchTeam] = useState(true); - const [soloTeam, setSoloTeam] = useState(false); - const [teamColor, setTeamColor] = useState(''); const [conflictDismissed, setConflictDismissed] = useState(false); const [selectedModel, setSelectedModelRaw] = useState(() => { const stored = localStorage.getItem('team:lastSelectedModel'); @@ -334,18 +349,11 @@ export const CreateTeamDialog = ({ }; const resetFormState = (): void => { - setTeamName(''); + clearDraft(); lastAutoDescriptionRef.current = null; descriptionDraft.clearDraft(); promptDraft.clearDraft(); promptChipDraft.clearChipDraft(); - setMembers([]); - setTeamColor(''); - setCwdMode('project'); - setSelectedProjectPath(''); - setCustomCwd(''); - setLaunchTeam(true); - setSoloTeam(false); resetUIState(); }; @@ -473,7 +481,7 @@ export const CreateTeamDialog = ({ }, [open, defaultProjectPath]); useEffect(() => { - if (!open) { + if (!open || !draftLoaded) { return; } @@ -510,15 +518,17 @@ export const CreateTeamDialog = ({ }) ) ); - // eslint-disable-next-line react-hooks/exhaustive-deps -- initialData is checked once on open - }, [open]); + // eslint-disable-next-line react-hooks/exhaustive-deps -- initialData is checked once on open/draftLoaded + }, [open, draftLoaded]); useEffect(() => { - if (!open || initialData) { + if (!open || initialData || !draftLoaded) { return; } - setTeamName((prev) => (prev.trim().length === 0 ? suggestedTeamName : prev)); - }, [initialData, open, suggestedTeamName]); + if (teamName.trim().length === 0) { + setTeamName(suggestedTeamName); + } + }, [initialData, open, suggestedTeamName, draftLoaded]); // eslint-disable-line react-hooks/exhaustive-deps -- teamName read once useEffect(() => { if (!open || initialData) { @@ -1187,7 +1197,12 @@ export const CreateTeamDialog = ({ + diff --git a/src/renderer/components/team/messages/MessagesPanel.tsx b/src/renderer/components/team/messages/MessagesPanel.tsx index bcff9fc3..85e3b296 100644 --- a/src/renderer/components/team/messages/MessagesPanel.tsx +++ b/src/renderer/components/team/messages/MessagesPanel.tsx @@ -10,7 +10,6 @@ import { useStore } from '@renderer/store'; import { filterTeamMessages } from '@renderer/utils/teamMessageFiltering'; import { toMessageKey } from '@renderer/utils/teamMessageKey'; import { - Bell, CheckCheck, ChevronsDownUp, ChevronsUpDown, @@ -427,23 +426,6 @@ export const MessagesPanel = memo(function MessagesPanel({ Mark all as read )} - - - - - Desktop notifications plugin -
@@ -596,24 +578,6 @@ export const MessagesPanel = memo(function MessagesPanel({ } headerExtra={ <> - - - - - Desktop notifications plugin -