From e2253b92fa19284fea703d54a99f117402b693d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Thu, 16 Apr 2026 22:12:51 -0400 Subject: [PATCH] refactor(renderer): dismantle useAppController 47-prop god-hook Sections now subscribe directly to the Zustand store via useSectionContext (shared data derivation) and useAppStore fine-grained selectors. Delete useAppController, useExecutionController, useOrganizationController, and useCommunicationController; the three domain forwarders held no logic beyond re-exporting store slices. SectionContentView becomes a 50-line layout dispatcher taking only {section, error, pageTransition}; cross-domain run actions (startRun / cancelRun) move into App.tsx and are exposed to sections through a new AppActionsContext alongside refresh / setError / openPathWithFeedback. App.tsx now calls useUIController + useCompanyController directly and composes palette + navigation plumbing itself. --- src/renderer/App.tsx | 291 ++++++++++-------- src/renderer/hooks/useAppActions.ts | 17 + src/renderer/hooks/useAppController.ts | 209 ------------- .../hooks/useCommandPaletteHotkeys.ts | 2 +- .../hooks/useCommunicationController.ts | 37 --- src/renderer/hooks/useExecutionController.ts | 30 -- .../hooks/useOrganizationController.ts | 27 -- src/renderer/hooks/useSectionContext.ts | 40 +++ src/renderer/views/SectionContentView.tsx | 254 +-------------- .../views/sections/ExecutionSections.tsx | 131 +++----- .../views/sections/OperationsSections.tsx | 128 +++----- .../views/sections/OrganizationSections.tsx | 70 ++--- 12 files changed, 339 insertions(+), 897 deletions(-) create mode 100644 src/renderer/hooks/useAppActions.ts delete mode 100644 src/renderer/hooks/useAppController.ts delete mode 100644 src/renderer/hooks/useCommunicationController.ts delete mode 100644 src/renderer/hooks/useExecutionController.ts delete mode 100644 src/renderer/hooks/useOrganizationController.ts create mode 100644 src/renderer/hooks/useSectionContext.ts diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index b64ff051..ef3bf08b 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -1,3 +1,4 @@ +import { useCallback, useMemo } from "react"; import { CircleNotch, TerminalWindow } from "@phosphor-icons/react"; import { LocaleContext, TranslationContext } from "./i18n"; import { CompanyOnboarding } from "./components/CompanyOnboarding"; @@ -7,23 +8,107 @@ import { SidebarView } from "./views/SidebarView"; import { SectionContentView } from "./views/SectionContentView"; import { ConsoleView } from "./views/ConsoleView"; import { CommandPaletteView } from "./views/CommandPaletteView"; -import { useAppController } from "./hooks/useAppController"; +import { useAppStore } from "./state/store"; +import { useUIController } from "./hooks/useUIController"; +import { useCompanyController } from "./hooks/useCompanyController"; +import { useCommandPaletteHotkeys } from "./hooks/useCommandPaletteHotkeys"; +import { AppActionsContext, type AppActions } from "./hooks/useAppActions"; +import { unwrap } from "./lib/desktop"; export function App() { - const c = useAppController(); + const ui = useUIController(); + const company = useCompanyController(); + + const error = useAppStore((s) => s.error); + const setError = useAppStore((s) => s.setError); + const setSelectedRunId = useAppStore((s) => s.setSelectedRunId); + const setConsoleOpen = useAppStore((s) => s.setConsoleOpen); const showCustomTitleBar = window.agentCompany.platform !== "darwin"; const isMac = !showCustomTitleBar; - if (!c.snapshot) { + const startRun = useCallback(async (taskId: string) => { + if (!company.currentCompany) { setError("Select a company before starting a run."); return; } + const result = await window.agentCompany.startTaskRun({ companyId: company.currentCompany.id, taskId }); + if (!result.ok) { setError(result.error.message); return; } + setSelectedRunId(result.data); + setConsoleOpen(true); + await company.refresh(); + // eslint-disable-next-line react-hooks/exhaustive-deps -- Zustand setters and company.refresh are stable + }, [company.currentCompany, company.refresh]); + + const cancelRun = useCallback(async (runId: string) => { + try { + const run = company.runs.find((entry) => entry.id === runId); + if (!run) throw new Error("Selected run is no longer available."); + unwrap(await window.agentCompany.cancelRun({ companyId: run.companyId, runId })); + await company.refresh(); + setError(null); + } catch (err) { setError(err instanceof Error ? err.message : String(err)); } + // eslint-disable-next-line react-hooks/exhaustive-deps -- setError is a stable Zustand setter + }, [company.refresh, company.runs]); + + const { filteredNavItems, matchedAgents, matchedTasks, matchedRuns, matchedGoals, matchedProjects, hasEntityResults } = + useMemo(() => ui.buildPaletteResults( + ui.navItems, + company.companyAgents, + company.tasks, + company.runs, + company.companyGoals, + company.companyProjects, + ), [ + ui.navItems, + ui.paletteQuery, + company.companyAgents, + company.tasks, + company.runs, + company.companyGoals, + company.companyProjects, + + ]); + + const paletteActions = useMemo( + () => ui.buildPaletteActions(filteredNavItems, matchedAgents, matchedTasks, matchedRuns, matchedGoals, matchedProjects), + [ui, filteredNavItems, matchedAgents, matchedTasks, matchedRuns, matchedGoals, matchedProjects], + ); + + const paletteActionIndex = useMemo( + () => new Map(paletteActions.map((action, index) => [action.key, index])), + [paletteActions], + ); + + const navigateTo = useCallback((targetSection: Parameters[0], entityId?: string) => { + ui.navigateTo(targetSection, entityId, company.runs, company.tasks); + }, [ui, company.runs, company.tasks]); + + useCommandPaletteHotkeys({ + commandPaletteOpen: ui.commandPaletteOpen, + setCommandPaletteOpen: ui.setCommandPaletteOpen, + setCommandPaletteSearch: ui.setCommandPaletteSearch, + setSelectedPaletteIndex: ui.setSelectedPaletteIndex, + paletteActions, + selectedPaletteIndex: ui.selectedPaletteIndex, + navigateTo, + commandPaletteSearch: ui.commandPaletteSearch, + }); + + const appActions = useMemo(() => ({ + refresh: company.refresh, + setError, + openPathWithFeedback: company.openPathWithFeedback, + startRun, + cancelRun, + }), [company.refresh, company.openPathWithFeedback, startRun, cancelRun, setError]); + + if (!company.snapshot) { return ( - - + +
{showCustomTitleBar && }
- {c.t("app.loading")} + {ui.t("app.loading")}
@@ -31,25 +116,25 @@ export function App() { ); } - if (c.snapshot.companies.length === 0 || c.showOnboarding || c.initialOnboardingLocked) { + if (company.snapshot.companies.length === 0 || ui.showOnboarding || ui.initialOnboardingLocked) { return ( - - + +
{showCustomTitleBar && }
0 && !c.initialOnboardingLocked} + connectors={company.snapshot.connectors} + onDone={company.handleOnboardingDone} + canCancel={company.snapshot.companies.length > 0 && !ui.initialOnboardingLocked} onCancel={() => { - c.setShowOnboarding(false); - c.setInitialOnboardingLocked(false); - c.setError(null); + ui.setShowOnboarding(false); + ui.setInitialOnboardingLocked(false); + setError(null); }} - currentLocale={c.locale} - onLocaleChange={c.handleLocaleChange} - isFirstLaunch={c.snapshot.companies.length === 0} + currentLocale={ui.locale} + onLocaleChange={ui.handleLocaleChange} + isFirstLaunch={company.snapshot.companies.length === 0} />
@@ -59,154 +144,100 @@ export function App() { } return ( - - + + +
{showCustomTitleBar && } -
+

- {c.navItems.find((item) => item.id === c.section)?.label ?? c.t(`nav.${c.section}`)} + {ui.navItems.find((item) => item.id === ui.section)?.label ?? ui.t(`nav.${ui.section}`)}

+ ); diff --git a/src/renderer/hooks/useAppActions.ts b/src/renderer/hooks/useAppActions.ts new file mode 100644 index 00000000..70aae8c3 --- /dev/null +++ b/src/renderer/hooks/useAppActions.ts @@ -0,0 +1,17 @@ +import { createContext, useContext } from "react"; + +export interface AppActions { + refresh: () => Promise; + setError: (error: string | null) => void; + openPathWithFeedback: (path: string) => Promise; + startRun: (taskId: string) => Promise; + cancelRun: (runId: string) => Promise; +} + +export const AppActionsContext = createContext(null); + +export function useAppActions(): AppActions { + const value = useContext(AppActionsContext); + if (!value) throw new Error("useAppActions must be used inside "); + return value; +} diff --git a/src/renderer/hooks/useAppController.ts b/src/renderer/hooks/useAppController.ts deleted file mode 100644 index cde5440d..00000000 --- a/src/renderer/hooks/useAppController.ts +++ /dev/null @@ -1,209 +0,0 @@ -import { useCallback, useMemo } from "react"; -import { useAppStore } from "../state/store"; -import { useUIController } from "./useUIController"; -import { useCompanyController } from "./useCompanyController"; -import { useExecutionController } from "./useExecutionController"; -import { useOrganizationController } from "./useOrganizationController"; -import { useCommunicationController } from "./useCommunicationController"; -import { useCommandPaletteHotkeys } from "./useCommandPaletteHotkeys"; -import { unwrap } from "../lib/desktop"; - -// Re-export types consumed by other modules -export type { NavItem, PaletteAction } from "./useUIController"; - -export function useAppController() { - const ui = useUIController(); - const company = useCompanyController(); - const execution = useExecutionController(); - const organization = useOrganizationController(); - const communication = useCommunicationController(); - - const error = useAppStore((s) => s.error); - const setError = useAppStore((s) => s.setError); - const inboxItems = useAppStore((s) => s.inboxItems); - const companyMetrics = useAppStore((s) => s.companyMetrics); - const standupReport = useAppStore((s) => s.standupReport); - const socialAccounts = useAppStore((s) => s.socialAccounts); - const browserActions = useAppStore((s) => s.browserActions); - - // --- Cross-domain actions composed here --- - - const startRun = useCallback(async (taskId: string) => { - if (!company.currentCompany) { setError("Select a company before starting a run."); return; } - const result = await window.agentCompany.startTaskRun({ companyId: company.currentCompany.id, taskId }); - if (!result.ok) { setError(result.error.message); return; } - execution.setSelectedRunId(result.data); - ui.setConsoleOpen(true); - await company.refresh(); - // eslint-disable-next-line react-hooks/exhaustive-deps -- Zustand setters and company.refresh are stable - }, [company.currentCompany, company.refresh]); - - const cancelRun = useCallback(async (runId: string) => { - try { - const run = company.runs.find((entry) => entry.id === runId); - if (!run) throw new Error("Selected run is no longer available."); - unwrap(await window.agentCompany.cancelRun({ companyId: run.companyId, runId })); - await company.refresh(); - setError(null); - } catch (err) { setError(err instanceof Error ? err.message : String(err)); } - // eslint-disable-next-line react-hooks/exhaustive-deps -- setError is a stable Zustand setter - }, [company.refresh, company.runs]); - - // --- Command palette results (needs entity lists from company domain) --- - - const { filteredNavItems, matchedAgents, matchedTasks, matchedRuns, matchedGoals, matchedProjects, hasEntityResults } = - useMemo(() => ui.buildPaletteResults( - ui.navItems, - company.companyAgents, - company.tasks, - company.runs, - company.companyGoals, - company.companyProjects, - ), [ - ui.navItems, - ui.paletteQuery, - company.companyAgents, - company.tasks, - company.runs, - company.companyGoals, - company.companyProjects, - ]); - - const paletteActions = useMemo( - () => ui.buildPaletteActions(filteredNavItems, matchedAgents, matchedTasks, matchedRuns, matchedGoals, matchedProjects), - [filteredNavItems, matchedAgents, matchedTasks, matchedRuns, matchedGoals, matchedProjects], - ); - - const paletteActionIndex = useMemo( - () => new Map(paletteActions.map((action, index) => [action.key, index])), - [paletteActions], - ); - - // navigateTo needs entity lists for task/run cross-lookup - const navigateTo = useCallback((targetSection: Parameters[0], entityId?: string) => { - ui.navigateTo(targetSection, entityId, company.runs, company.tasks); - }, [ui.navigateTo, company.runs, company.tasks]); - - // Wire command palette hotkeys - useCommandPaletteHotkeys({ - commandPaletteOpen: ui.commandPaletteOpen, - setCommandPaletteOpen: ui.setCommandPaletteOpen, - setCommandPaletteSearch: ui.setCommandPaletteSearch, - setSelectedPaletteIndex: ui.setSelectedPaletteIndex, - paletteActions, - selectedPaletteIndex: ui.selectedPaletteIndex, - navigateTo, - commandPaletteSearch: ui.commandPaletteSearch, - }); - - return { - // UI / navigation - pageTransition: ui.pageTransition, - panelTransition: ui.panelTransition, - locale: ui.locale, - t: ui.t, - handleLocaleChange: ui.handleLocaleChange, - sidebarNav: ui.sidebarNav, - navItems: ui.navItems, - section: ui.section, - setSection: ui.setSection, - consoleOpen: ui.consoleOpen, - setConsoleOpen: ui.setConsoleOpen, - sidebarCollapsed: ui.sidebarCollapsed, - setSidebarCollapsed: ui.setSidebarCollapsed, - companySwitcherOpen: ui.companySwitcherOpen, - setCompanySwitcherOpen: ui.setCompanySwitcherOpen, - confirmDeleteCompanyId: ui.confirmDeleteCompanyId, - setConfirmDeleteCompanyId: ui.setConfirmDeleteCompanyId, - taskViewMode: ui.taskViewMode, - setTaskViewMode: ui.setTaskViewMode, - showOnboarding: ui.showOnboarding, - setShowOnboarding: ui.setShowOnboarding, - initialOnboardingLocked: ui.initialOnboardingLocked, - setInitialOnboardingLocked: ui.setInitialOnboardingLocked, - commandPaletteOpen: ui.commandPaletteOpen, - setCommandPaletteOpen: ui.setCommandPaletteOpen, - commandPaletteSearch: ui.commandPaletteSearch, - setCommandPaletteSearch: ui.setCommandPaletteSearch, - selectedPaletteIndex: ui.selectedPaletteIndex, - setSelectedPaletteIndex: ui.setSelectedPaletteIndex, - paletteTrapRef: ui.paletteTrapRef, - filteredNavItems, - matchedAgents, - matchedTasks, - matchedRuns, - matchedGoals, - matchedProjects, - hasEntityResults, - paletteActions, - paletteActionIndex, - navigateTo, - - // Company / core - snapshot: company.snapshot, - currentCompany: company.currentCompany, - selectedConnector: company.selectedConnector, - logBufferRef: company.logBufferRef, - refresh: company.refresh, - setCompany: company.setCompany, - deleteCompany: company.deleteCompany, - handleOnboardingDone: company.handleOnboardingDone, - openPathWithFeedback: company.openPathWithFeedback, - tasks: company.tasks, - runs: company.runs, - approvals: company.approvals, - pendingApprovals: company.pendingApprovals, - companyAgents: company.companyAgents, - companyGoals: company.companyGoals, - companyProjects: company.companyProjects, - companyWorkspaces: company.companyWorkspaces, - companyCosts: company.companyCosts, - filteredActivity: company.filteredActivity, - companySecrets: company.companySecrets, - companyDocuments: company.companyDocuments, - companyKnowledge: company.companyKnowledge, - companyMeetings: company.companyMeetings, - companySprints: company.companySprints, - selectedTask: company.selectedTask, - selectedApproval: company.selectedApproval, - selectedRun: company.selectedRun, - selectedAgent: company.selectedAgent, - selectedGoal: company.selectedGoal, - selectedProject: company.selectedProject, - selectedWorkspace: company.selectedWorkspace, - navBadges: company.navBadges, - - // Execution - selectedTaskId: execution.selectedTaskId, - setSelectedTaskId: execution.setSelectedTaskId, - setSelectedConnectorId: execution.setSelectedConnectorId, - selectedApprovalId: execution.selectedApprovalId, - setSelectedApprovalId: execution.setSelectedApprovalId, - selectedRunId: execution.selectedRunId, - setSelectedRunId: execution.setSelectedRunId, - startRun, - cancelRun, - - // Organization - selectedAgentId: organization.selectedAgentId, - setSelectedAgentId: organization.setSelectedAgentId, - selectedGoalId: organization.selectedGoalId, - setSelectedGoalId: organization.setSelectedGoalId, - selectedProjectId: organization.selectedProjectId, - setSelectedProjectId: organization.setSelectedProjectId, - setSelectedWorkspaceId: organization.setSelectedWorkspaceId, - - // Communication - selectedCommunicationEntityId: communication.selectedCommunicationEntityId, - setSelectedCommunicationEntityId: communication.setSelectedCommunicationEntityId, - - // Shared cross-domain state - error, - setError, - inboxItems, - companyMetrics, - standupReport, - socialAccounts, - browserActions, - }; -} diff --git a/src/renderer/hooks/useCommandPaletteHotkeys.ts b/src/renderer/hooks/useCommandPaletteHotkeys.ts index ded00ab9..429fd263 100644 --- a/src/renderer/hooks/useCommandPaletteHotkeys.ts +++ b/src/renderer/hooks/useCommandPaletteHotkeys.ts @@ -1,6 +1,6 @@ import { useEffect } from "react"; import { useAppStore } from "../state/store"; -import type { PaletteAction } from "./useAppController"; +import type { PaletteAction } from "./useUIController"; interface CommandPaletteHotkeysOptions { commandPaletteOpen: boolean; diff --git a/src/renderer/hooks/useCommunicationController.ts b/src/renderer/hooks/useCommunicationController.ts deleted file mode 100644 index d306fb3f..00000000 --- a/src/renderer/hooks/useCommunicationController.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { useAppStore } from "../state/store"; - -/** - * Fine-grained subscriptions for communication domain: - * messages, channels, threads, search state. - */ -export function useCommunicationController() { - const selectedCommunicationEntityId = useAppStore((s) => s.selectedCommunicationEntityId); - const setSelectedCommunicationEntityId = useAppStore((s) => s.setSelectedCommunicationEntityId); - const searchQuery = useAppStore((s) => s.searchQuery); - const setSearchQuery = useAppStore((s) => s.setSearchQuery); - const searchScope = useAppStore((s) => s.searchScope); - const setSearchScope = useAppStore((s) => s.setSearchScope); - const searchResults = useAppStore((s) => s.searchResults); - const setSearchResults = useAppStore((s) => s.setSearchResults); - const searchLoading = useAppStore((s) => s.searchLoading); - const setSearchLoading = useAppStore((s) => s.setSearchLoading); - const clearSearch = useAppStore((s) => s.clearSearch); - const activeThreadId = useAppStore((s) => s.activeThreadId); - const setActiveThreadId = useAppStore((s) => s.setActiveThreadId); - - return { - selectedCommunicationEntityId, - setSelectedCommunicationEntityId, - searchQuery, - setSearchQuery, - searchScope, - setSearchScope, - searchResults, - setSearchResults, - searchLoading, - setSearchLoading, - clearSearch, - activeThreadId, - setActiveThreadId, - }; -} diff --git a/src/renderer/hooks/useExecutionController.ts b/src/renderer/hooks/useExecutionController.ts deleted file mode 100644 index 0f949e40..00000000 --- a/src/renderer/hooks/useExecutionController.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { useAppStore } from "../state/store"; - -/** - * Fine-grained subscriptions for execution domain: - * runs, approvals, costs, budgets, selected run/approval/task/connector. - * - * Cross-domain actions (startRun, cancelRun) are composed in useAppController - * because they need refresh() from useCompanyController. - */ -export function useExecutionController() { - const selectedTaskId = useAppStore((s) => s.selectedTaskId); - const setSelectedTaskId = useAppStore((s) => s.setSelectedTaskId); - const selectedConnectorId = useAppStore((s) => s.selectedConnectorId); - const setSelectedConnectorId = useAppStore((s) => s.setSelectedConnectorId); - const selectedApprovalId = useAppStore((s) => s.selectedApprovalId); - const setSelectedApprovalId = useAppStore((s) => s.setSelectedApprovalId); - const selectedRunId = useAppStore((s) => s.selectedRunId); - const setSelectedRunId = useAppStore((s) => s.setSelectedRunId); - - return { - selectedTaskId, - setSelectedTaskId, - selectedConnectorId, - setSelectedConnectorId, - selectedApprovalId, - setSelectedApprovalId, - selectedRunId, - setSelectedRunId, - }; -} diff --git a/src/renderer/hooks/useOrganizationController.ts b/src/renderer/hooks/useOrganizationController.ts deleted file mode 100644 index c4c94a8d..00000000 --- a/src/renderer/hooks/useOrganizationController.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { useAppStore } from "../state/store"; - -/** - * Fine-grained subscriptions for organization domain: - * goals, projects, sprints, departments, workspaces, agents. - */ -export function useOrganizationController() { - const selectedAgentId = useAppStore((s) => s.selectedAgentId); - const setSelectedAgentId = useAppStore((s) => s.setSelectedAgentId); - const selectedGoalId = useAppStore((s) => s.selectedGoalId); - const setSelectedGoalId = useAppStore((s) => s.setSelectedGoalId); - const selectedProjectId = useAppStore((s) => s.selectedProjectId); - const setSelectedProjectId = useAppStore((s) => s.setSelectedProjectId); - const selectedWorkspaceId = useAppStore((s) => s.selectedWorkspaceId); - const setSelectedWorkspaceId = useAppStore((s) => s.setSelectedWorkspaceId); - - return { - selectedAgentId, - setSelectedAgentId, - selectedGoalId, - setSelectedGoalId, - selectedProjectId, - setSelectedProjectId, - selectedWorkspaceId, - setSelectedWorkspaceId, - }; -} diff --git a/src/renderer/hooks/useSectionContext.ts b/src/renderer/hooks/useSectionContext.ts new file mode 100644 index 00000000..da8fecdb --- /dev/null +++ b/src/renderer/hooks/useSectionContext.ts @@ -0,0 +1,40 @@ +import { useMemo } from "react"; +import type { CompanyRecord } from "@shared/types"; +import { useAppStore } from "../state/store"; +import { useCompanyData } from "./useCompanyData"; + +/** + * Shared data context for section views. Subscribes to the Zustand store + * for snapshot + current company, derives per-company collections and + * currently-selected entities via useCompanyData. + * + * Sections call this hook directly instead of receiving 30+ props through + * SectionContentView. + */ +export function useSectionContext() { + const snapshot = useAppStore((s) => s.snapshot); + + const currentCompany = useMemo(() => { + if (!snapshot) return null; + return ( + snapshot.companies.find((company) => company.id === snapshot.currentCompanyId) ?? + snapshot.companies[0] ?? + null + ); + }, [snapshot]); + + const companyData = useCompanyData(snapshot, currentCompany); + + const selectedConnectorId = useAppStore((s) => s.selectedConnectorId); + const selectedConnector = + snapshot?.connectors.find((connector) => connector.id === selectedConnectorId) ?? + snapshot?.connectors[0] ?? + null; + + return { + snapshot, + currentCompany, + selectedConnector, + ...companyData, + }; +} diff --git a/src/renderer/views/SectionContentView.tsx b/src/renderer/views/SectionContentView.tsx index 51d3cb7d..66cfb6c6 100644 --- a/src/renderer/views/SectionContentView.tsx +++ b/src/renderer/views/SectionContentView.tsx @@ -1,28 +1,6 @@ import { AnimatePresence, motion } from "framer-motion"; import { WarningCircle } from "@phosphor-icons/react"; -import type { - AgentRecord, - ApprovalRecord, - BrowserActionRecord, - CompanyRecord, - CostRecord, - DocumentRecord, - GoalRecord, - InboxItem, - CompanyMetrics, - KnowledgeEntryRecord, - MeetingRecord, - ProfileSnapshot, - ProjectRecord, - RunRecord, - SectionId, - SocialAccountRecord, - SprintRecord, - StandupReport, - TaskRecord, - WorkspaceRecord, -} from "@shared/types"; -import type { AppLocale } from "@shared/locale"; +import type { SectionId } from "@shared/types"; import { ErrorBoundary } from "../components/ui"; import { OrganizationSections } from "./sections/OrganizationSections"; import { ExecutionSections } from "./sections/ExecutionSections"; @@ -31,142 +9,23 @@ import { OperationsSections } from "./sections/OperationsSections"; interface SectionContentViewProps { section: SectionId; error: string | null; - snapshot: ProfileSnapshot; - currentCompany: CompanyRecord | null; - locale: AppLocale; - // data collections - tasks: TaskRecord[]; - runs: RunRecord[]; - approvals: ApprovalRecord[]; - pendingApprovals: ApprovalRecord[]; - companyAgents: AgentRecord[]; - companyGoals: GoalRecord[]; - companyProjects: ProjectRecord[]; - companyWorkspaces: WorkspaceRecord[]; - companyCosts: CostRecord[]; - filteredActivity: ReturnType; - companySecrets: ProfileSnapshot["secrets"]; - companyDocuments: DocumentRecord[]; - companyKnowledge: KnowledgeEntryRecord[]; - companyMeetings: MeetingRecord[]; - companySprints: SprintRecord[]; - socialAccounts: SocialAccountRecord[]; - browserActions: BrowserActionRecord[]; - inboxItems: InboxItem[]; - companyMetrics: CompanyMetrics | null; - standupReport: StandupReport | null; - selectedRunId: string | null; - // selections - selectedTask: TaskRecord | null; - selectedTaskId: string | null; - setSelectedTaskId: (id: string | null) => void; - selectedConnector: ProfileSnapshot["connectors"][number] | null; - setSelectedConnectorId: (id: string | null) => void; - selectedApproval: ApprovalRecord | null; - selectedApprovalId: string | null; - setSelectedApprovalId: (id: string | null) => void; - selectedAgent: AgentRecord | null; - selectedAgentId: string | null; - setSelectedAgentId: (id: string | null) => void; - selectedGoal: GoalRecord | null; - selectedGoalId: string | null; - setSelectedGoalId: (id: string | null) => void; - selectedProject: ProjectRecord | null; - selectedProjectId: string | null; - setSelectedProjectId: (id: string | null) => void; - selectedWorkspace: WorkspaceRecord | null; - setSelectedWorkspaceId: (id: string | null) => void; - selectedCommunicationEntityId: string | null; - setSelectedCommunicationEntityId: (id: string | null) => void; - taskViewMode: "list" | "kanban"; - setTaskViewMode: (mode: "list" | "kanban") => void; - // actions - navigateTo: (section: SectionId, entityId?: string) => void; - setSection: (section: SectionId) => void; - setConsoleOpen: (value: boolean) => void; - setSelectedRunId: (id: string | null) => void; - refresh: () => Promise; - startRun: (taskId: string) => Promise; - cancelRun: (runId: string) => Promise; - openPathWithFeedback: (path: string) => Promise; - setError: (error: string | null) => void; - // transitions pageTransition: { duration: number; ease?: readonly [number, number, number, number] } | { duration: number }; } -export function SectionContentView({ - section, - error, - snapshot, - currentCompany, - locale, - tasks, - runs, - approvals, - pendingApprovals, - companyAgents, - companyGoals, - companyProjects, - companyWorkspaces, - companyCosts, - filteredActivity, - companySecrets, - companyDocuments, - companyKnowledge, - companyMeetings, - companySprints, - socialAccounts, - browserActions, - inboxItems, - companyMetrics, - standupReport, - selectedTask, - selectedTaskId, - setSelectedTaskId, - selectedConnector, - setSelectedConnectorId, - selectedApproval, - selectedApprovalId, - setSelectedApprovalId, - selectedAgent: _selectedAgent, - selectedAgentId, - setSelectedAgentId, - selectedGoal: _selectedGoal, - selectedGoalId, - setSelectedGoalId, - selectedProject: _selectedProject, - selectedProjectId, - setSelectedProjectId, - selectedWorkspace, - setSelectedWorkspaceId, - selectedCommunicationEntityId, - setSelectedCommunicationEntityId, - taskViewMode, - setTaskViewMode, - selectedRunId, - navigateTo, - setSection, - setConsoleOpen, - setSelectedRunId, - refresh, - startRun, - cancelRun, - openPathWithFeedback, - setError, - pageTransition, -}: SectionContentViewProps) { +export function SectionContentView({ section, error, pageTransition }: SectionContentViewProps) { + const isCommunication = section === "communication"; return ( -
-
+
+
{error ? ( -
+
{error}
) : null} -
+
- - - - - + + + diff --git a/src/renderer/views/sections/ExecutionSections.tsx b/src/renderer/views/sections/ExecutionSections.tsx index aef729bb..814c0a5d 100644 --- a/src/renderer/views/sections/ExecutionSections.tsx +++ b/src/renderer/views/sections/ExecutionSections.tsx @@ -1,19 +1,6 @@ import { useState } from "react"; import { NEW_ENTITY_ID } from "../../lib/constants"; -import type { - AgentRecord, - ApprovalRecord, - CompanyRecord, - CostRecord, - DocumentRecord, - GoalRecord, - ProjectRecord, - RunRecord, - SectionId, - TaskRecord, - WorkspaceRecord, - ProfileSnapshot, -} from "@shared/types"; +import type { SectionId } from "@shared/types"; import { Kanban, ListChecks, @@ -43,88 +30,52 @@ import { } from "../../components/organization"; import { ActionButton, ListFrame, ListRow, Modal, Panel, StatusPill } from "../../components/ui"; import { unwrap } from "../../lib/desktop"; +import { useAppStore } from "../../state/store"; +import { useSectionContext } from "../../hooks/useSectionContext"; +import { useAppActions } from "../../hooks/useAppActions"; interface ExecutionSectionsProps { section: SectionId; - snapshot: ProfileSnapshot; - currentCompany: CompanyRecord | null; - tasks: TaskRecord[]; - runs: RunRecord[]; - approvals: ApprovalRecord[]; - pendingApprovals: ApprovalRecord[]; - companyAgents: AgentRecord[]; - companyGoals: GoalRecord[]; - companyProjects: ProjectRecord[]; - companyWorkspaces: WorkspaceRecord[]; - companyCosts: CostRecord[]; - companyDocuments: DocumentRecord[]; - filteredActivity: ReturnType; - selectedRunId: string | null; - selectedTask: TaskRecord | null; - selectedTaskId: string | null; - setSelectedTaskId: (id: string | null) => void; - selectedConnector: ProfileSnapshot["connectors"][number] | null; - setSelectedConnectorId: (id: string | null) => void; - selectedApproval: ApprovalRecord | null; - selectedApprovalId: string | null; - setSelectedApprovalId: (id: string | null) => void; - selectedGoalId: string | null; - setSelectedGoalId: (id: string | null) => void; - taskViewMode: "list" | "kanban"; - setTaskViewMode: (mode: "list" | "kanban") => void; - setSection: (section: SectionId) => void; - setConsoleOpen: (value: boolean) => void; - setSelectedRunId: (id: string | null) => void; - setSelectedCommunicationEntityId: (id: string | null) => void; - refresh: () => Promise; - startRun: (taskId: string) => Promise; - cancelRun: (runId: string) => Promise; - openPathWithFeedback: (path: string) => Promise; - setError: (error: string | null) => void; } -export function ExecutionSections({ - section, - snapshot, - currentCompany, - tasks, - runs, - approvals: _approvals, - pendingApprovals, - companyAgents, - companyGoals, - companyProjects, - companyWorkspaces, - companyCosts, - companyDocuments, - filteredActivity, - selectedRunId, - selectedTask, - selectedTaskId, - setSelectedTaskId, - selectedConnector, - setSelectedConnectorId, - selectedApproval, - selectedApprovalId: _selectedApprovalId, - setSelectedApprovalId, - selectedGoalId, - setSelectedGoalId, - taskViewMode, - setTaskViewMode, - setSection, - setConsoleOpen, - setSelectedRunId, - setSelectedCommunicationEntityId, - refresh, - startRun, - cancelRun, - openPathWithFeedback, - setError: _setError, -}: ExecutionSectionsProps) { - const [costsTab, setCostsTab] = useState<"costs" | "policies" | "incidents">("costs"); - const selectedGoal = companyGoals.find((g) => g.id === selectedGoalId) ?? null; +export function ExecutionSections({ section }: ExecutionSectionsProps) { + const { + snapshot, + currentCompany, + tasks, + runs, + pendingApprovals, + companyAgents, + companyGoals, + companyProjects, + companyWorkspaces, + companyCosts, + companyDocuments, + filteredActivity, + selectedTask, + selectedConnector, + selectedApproval, + selectedGoal, + } = useSectionContext(); + const { refresh, startRun, cancelRun, openPathWithFeedback } = useAppActions(); - if (!currentCompany) return null; + const selectedRunId = useAppStore((s) => s.selectedRunId); + const setSelectedRunId = useAppStore((s) => s.setSelectedRunId); + const selectedTaskId = useAppStore((s) => s.selectedTaskId); + const setSelectedTaskId = useAppStore((s) => s.setSelectedTaskId); + const setSelectedConnectorId = useAppStore((s) => s.setSelectedConnectorId); + const setSelectedApprovalId = useAppStore((s) => s.setSelectedApprovalId); + const selectedGoalId = useAppStore((s) => s.selectedGoalId); + const setSelectedGoalId = useAppStore((s) => s.setSelectedGoalId); + const taskViewMode = useAppStore((s) => s.taskViewMode); + const setTaskViewMode = useAppStore((s) => s.setTaskViewMode); + const setSection = useAppStore((s) => s.setSection); + const setConsoleOpen = useAppStore((s) => s.setConsoleOpen); + const setSelectedCommunicationEntityId = useAppStore((s) => s.setSelectedCommunicationEntityId); + + const [costsTab, setCostsTab] = useState<"costs" | "policies" | "incidents">("costs"); + + if (!snapshot || !currentCompany) return null; return ( <> diff --git a/src/renderer/views/sections/OperationsSections.tsx b/src/renderer/views/sections/OperationsSections.tsx index 4348aae3..7337687d 100644 --- a/src/renderer/views/sections/OperationsSections.tsx +++ b/src/renderer/views/sections/OperationsSections.tsx @@ -1,23 +1,4 @@ -import type { - AgentRecord, - BrowserActionRecord, - CompanyRecord, - DocumentRecord, - InboxItem, - CompanyMetrics, - KnowledgeEntryRecord, - MeetingRecord, - ProjectRecord, - RunRecord, - SectionId, - SocialAccountRecord, - SprintRecord, - StandupReport, - TaskRecord, - WorkspaceRecord, - ProfileSnapshot, -} from "@shared/types"; -import type { AppLocale } from "@shared/locale"; +import type { SectionId } from "@shared/types"; import { Inbox, StandupView, PerformanceView } from "../../components/inbox"; import { Dashboard } from "../../components/dashboard"; import { EntityWorkspace } from "../../components/EntityWorkspace"; @@ -35,78 +16,51 @@ import { SkillsSection } from "../../components/skills"; import { DocumentCenter, KnowledgeBaseManager, MeetingsManager, SprintManager } from "../../components/company-operations"; import { Panel } from "../../components/ui"; import { unwrap } from "../../lib/desktop"; +import { useAppStore } from "../../state/store"; +import { useSectionContext } from "../../hooks/useSectionContext"; +import { useAppActions } from "../../hooks/useAppActions"; interface OperationsSectionsProps { section: SectionId; - snapshot: ProfileSnapshot; - currentCompany: CompanyRecord | null; - locale: AppLocale; - tasks: TaskRecord[]; - runs: RunRecord[]; - companyAgents: AgentRecord[]; - companyProjects: ProjectRecord[]; - companyWorkspaces: WorkspaceRecord[]; - companyDocuments: DocumentRecord[]; - companyKnowledge: KnowledgeEntryRecord[]; - companyMeetings: MeetingRecord[]; - companySprints: SprintRecord[]; - socialAccounts: SocialAccountRecord[]; - browserActions: BrowserActionRecord[]; - inboxItems: InboxItem[]; - companyMetrics: CompanyMetrics | null; - standupReport: StandupReport | null; - companySecrets: ProfileSnapshot["secrets"]; - selectedWorkspace: WorkspaceRecord | null; - setSelectedWorkspaceId: (id: string | null) => void; - selectedCommunicationEntityId: string | null; - setSelectedCommunicationEntityId: (id: string | null) => void; - setSelectedTaskId: (id: string | null) => void; - setSelectedApprovalId: (id: string | null) => void; - setSection: (section: SectionId) => void; - navigateTo: (section: SectionId, entityId?: string) => void; - refresh: () => Promise; - openPathWithFeedback: (path: string) => Promise; - setError: (error: string | null) => void; - filteredActivity: ReturnType; - companyCosts: ReturnType; - companyGoals: ReturnType["filter"]>; } -export function OperationsSections({ - section, - snapshot, - currentCompany, - locale, - tasks, - runs, - companyAgents, - companyProjects, - companyWorkspaces, - companyDocuments, - companyKnowledge, - companyMeetings, - companySprints, - socialAccounts, - browserActions, - inboxItems, - companyMetrics, - standupReport, - companySecrets, - selectedWorkspace, - setSelectedWorkspaceId, - selectedCommunicationEntityId, - setSelectedCommunicationEntityId, - setSelectedTaskId, - setSelectedApprovalId, - setSection, - navigateTo, - refresh, - openPathWithFeedback, - setError, - filteredActivity, - companyCosts, - companyGoals, -}: OperationsSectionsProps) { +export function OperationsSections({ section }: OperationsSectionsProps) { + const { + snapshot, + currentCompany, + tasks, + runs, + companyAgents, + companyProjects, + companyWorkspaces, + companyDocuments, + companyKnowledge, + companyMeetings, + companySprints, + companyCosts, + companyGoals, + companySecrets, + filteredActivity, + selectedWorkspace, + } = useSectionContext(); + const { refresh, openPathWithFeedback, setError } = useAppActions(); + + const locale = useAppStore((s) => s.locale); + const inboxItems = useAppStore((s) => s.inboxItems); + const companyMetrics = useAppStore((s) => s.companyMetrics); + const standupReport = useAppStore((s) => s.standupReport); + const socialAccounts = useAppStore((s) => s.socialAccounts); + const browserActions = useAppStore((s) => s.browserActions); + const selectedCommunicationEntityId = useAppStore((s) => s.selectedCommunicationEntityId); + const setSelectedCommunicationEntityId = useAppStore((s) => s.setSelectedCommunicationEntityId); + const setSelectedWorkspaceId = useAppStore((s) => s.setSelectedWorkspaceId); + const setSelectedTaskId = useAppStore((s) => s.setSelectedTaskId); + const setSelectedApprovalId = useAppStore((s) => s.setSelectedApprovalId); + const setSection = useAppStore((s) => s.setSection); + const navigateTo = useAppStore((s) => s.navigateTo); + + if (!snapshot) return null; + return ( <> {section === "overview" ? ( diff --git a/src/renderer/views/sections/OrganizationSections.tsx b/src/renderer/views/sections/OrganizationSections.tsx index a3f94033..63c7fc7e 100644 --- a/src/renderer/views/sections/OrganizationSections.tsx +++ b/src/renderer/views/sections/OrganizationSections.tsx @@ -1,12 +1,4 @@ -import type { - AgentRecord, - CompanyRecord, - GoalRecord, - ProjectRecord, - WorkspaceRecord, - ProfileSnapshot, - SectionId, -} from "@shared/types"; +import type { SectionId } from "@shared/types"; import { NEW_ENTITY_ID } from "../../lib/constants"; import { Target, @@ -28,48 +20,38 @@ import { import { HiringPipeline } from "../../components/hiring"; import { ActionButton, Modal, Panel } from "../../components/ui"; import { useT } from "../../i18n"; +import { useAppStore } from "../../state/store"; +import { useSectionContext } from "../../hooks/useSectionContext"; +import { useAppActions } from "../../hooks/useAppActions"; interface OrganizationSectionsProps { section: SectionId; - snapshot: ProfileSnapshot; - currentCompany: CompanyRecord | null; - companyAgents: AgentRecord[]; - companyGoals: GoalRecord[]; - companyProjects: ProjectRecord[]; - companyWorkspaces: WorkspaceRecord[]; - selectedAgentId: string | null; - setSelectedAgentId: (id: string | null) => void; - selectedGoalId: string | null; - setSelectedGoalId: (id: string | null) => void; - selectedProjectId: string | null; - setSelectedProjectId: (id: string | null) => void; - setSection: (section: SectionId) => void; - refresh: () => Promise; } -export function OrganizationSections({ - section, - snapshot, - currentCompany, - companyAgents, - companyGoals, - companyProjects, - companyWorkspaces, - selectedAgentId, - setSelectedAgentId, - selectedGoalId, - setSelectedGoalId, - selectedProjectId, - setSelectedProjectId, - setSection, - refresh, -}: OrganizationSectionsProps) { +export function OrganizationSections({ section }: OrganizationSectionsProps) { const t = useT(); - const selectedAgent = companyAgents.find((a) => a.id === selectedAgentId) ?? null; - const selectedGoal = companyGoals.find((g) => g.id === selectedGoalId) ?? null; - const selectedProject = companyProjects.find((p) => p.id === selectedProjectId) ?? null; + const { + snapshot, + currentCompany, + companyAgents, + companyGoals, + companyProjects, + companyWorkspaces, + selectedAgent, + selectedGoal, + selectedProject, + } = useSectionContext(); + const { refresh } = useAppActions(); - if (!currentCompany) return null; + const selectedAgentId = useAppStore((s) => s.selectedAgentId); + const setSelectedAgentId = useAppStore((s) => s.setSelectedAgentId); + const selectedGoalId = useAppStore((s) => s.selectedGoalId); + const setSelectedGoalId = useAppStore((s) => s.setSelectedGoalId); + const selectedProjectId = useAppStore((s) => s.selectedProjectId); + const setSelectedProjectId = useAppStore((s) => s.setSelectedProjectId); + const setSection = useAppStore((s) => s.setSection); + + if (!snapshot || !currentCompany) return null; return ( <>