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.
This commit is contained in:
ZacharyZhang-NY committed 2026-04-16 22:12:51 -04:00
1 parent de77f5f38e
commit e2253b92fa
12 files changed
+339 -897

No files matched your search

+161 -130
View File
@@ -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<typeof ui.navigateTo>[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<AppActions>(() => ({
refresh: company.refresh,
setError,
openPathWithFeedback: company.openPathWithFeedback,
startRun,
cancelRun,
}), [company.refresh, company.openPathWithFeedback, startRun, cancelRun, setError]);
if (!company.snapshot) {
return (
<LocaleContext.Provider value={c.locale}>
<TranslationContext.Provider value={c.t}>
<LocaleContext.Provider value={ui.locale}>
<TranslationContext.Provider value={ui.t}>
<div className="flex h-[100dvh] flex-col overflow-hidden">
{showCustomTitleBar && <TitleBar />}
<div className="flex min-h-0 flex-1 flex-col items-center justify-center gap-3">
<CircleNotch size={24} className="animate-spin text-primary" />
<span className="text-[13px] text-muted-foreground">{c.t("app.loading")}</span>
<span className="text-[13px] text-muted-foreground">{ui.t("app.loading")}</span>
</div>
</div>
</TranslationContext.Provider>
@@ -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 (
<LocaleContext.Provider value={c.locale}>
<TranslationContext.Provider value={c.t}>
<LocaleContext.Provider value={ui.locale}>
<TranslationContext.Provider value={ui.t}>
<div className="flex h-[100dvh] flex-col overflow-hidden">
{showCustomTitleBar && <TitleBar />}
<div className="min-h-0 flex-1 overflow-auto">
<CompanyOnboarding
connectors={c.snapshot.connectors}
onDone={c.handleOnboardingDone}
canCancel={c.snapshot.companies.length > 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}
/>
</div>
</div>
@@ -59,154 +144,100 @@ export function App() {
}
return (
<LocaleContext.Provider value={c.locale}>
<TranslationContext.Provider value={c.t}>
<LocaleContext.Provider value={ui.locale}>
<TranslationContext.Provider value={ui.t}>
<AppActionsContext.Provider value={appActions}>
<div className="flex h-[100dvh] flex-col overflow-hidden bg-background">
{showCustomTitleBar && <TitleBar />}
<div className={`grid min-h-0 flex-1 overflow-hidden transition-[grid-template-columns] duration-200 ${c.sidebarCollapsed ? "grid-cols-[56px_minmax(0,1fr)]" : "grid-cols-[220px_minmax(0,1fr)]"}`} style={{ transitionTimingFunction: "cubic-bezier(0.16, 1, 0.3, 1)" }}>
<div className={`grid min-h-0 flex-1 overflow-hidden transition-[grid-template-columns] duration-200 ${ui.sidebarCollapsed ? "grid-cols-[56px_minmax(0,1fr)]" : "grid-cols-[220px_minmax(0,1fr)]"}`} style={{ transitionTimingFunction: "cubic-bezier(0.16, 1, 0.3, 1)" }}>
<SidebarView
isMac={isMac}
sidebarCollapsed={c.sidebarCollapsed}
setSidebarCollapsed={c.setSidebarCollapsed}
section={c.section}
setSection={c.setSection}
sidebarNav={c.sidebarNav}
navBadges={c.navBadges}
companySwitcherOpen={c.companySwitcherOpen}
setCompanySwitcherOpen={c.setCompanySwitcherOpen}
confirmDeleteCompanyId={c.confirmDeleteCompanyId}
setConfirmDeleteCompanyId={c.setConfirmDeleteCompanyId}
currentCompany={c.currentCompany}
snapshot={c.snapshot}
setCompany={c.setCompany}
deleteCompany={c.deleteCompany}
setCommandPaletteOpen={c.setCommandPaletteOpen}
t={c.t}
sidebarCollapsed={ui.sidebarCollapsed}
setSidebarCollapsed={ui.setSidebarCollapsed}
section={ui.section}
setSection={ui.setSection}
sidebarNav={ui.sidebarNav}
navBadges={company.navBadges}
companySwitcherOpen={ui.companySwitcherOpen}
setCompanySwitcherOpen={ui.setCompanySwitcherOpen}
confirmDeleteCompanyId={ui.confirmDeleteCompanyId}
setConfirmDeleteCompanyId={ui.setConfirmDeleteCompanyId}
currentCompany={company.currentCompany}
snapshot={company.snapshot}
setCompany={company.setCompany}
deleteCompany={company.deleteCompany}
setCommandPaletteOpen={ui.setCommandPaletteOpen}
t={ui.t}
/>
<main aria-label="Content" className="flex min-w-0 flex-col overflow-hidden">
<header className="sidebar-drag shrink-0 border-b border-border bg-background">
<div className="mx-auto flex w-full max-w-[1040px] items-start justify-between gap-4 px-6 pb-5 pt-6 lg:gap-6 lg:px-10 lg:pt-8">
<h1 className="text-[28px] font-bold tracking-[-0.02em] text-foreground">
{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}`)}
</h1>
<div className="flex shrink-0 items-center gap-3 lg:gap-5">
<button
type="button"
aria-label={c.consoleOpen ? "Hide console" : "Show console"}
title={c.consoleOpen ? "Hide console" : "Show console"}
aria-label={ui.consoleOpen ? "Hide console" : "Show console"}
title={ui.consoleOpen ? "Hide console" : "Show console"}
className="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 inline-flex items-center gap-2 py-1 text-[12px] font-semibold text-muted-foreground transition hover:text-foreground"
onClick={() => c.setConsoleOpen(!c.consoleOpen)}
onClick={() => ui.setConsoleOpen(!ui.consoleOpen)}
>
<TerminalWindow size={16} />
<span className="hidden lg:inline">{c.consoleOpen ? c.t("app.hideConsole") : c.t("app.showConsole")}</span>
<span className="hidden lg:inline">{ui.consoleOpen ? ui.t("app.hideConsole") : ui.t("app.showConsole")}</span>
</button>
</div>
</div>
</header>
<SectionContentView
section={c.section}
error={c.error}
snapshot={c.snapshot}
currentCompany={c.currentCompany}
locale={c.locale}
tasks={c.tasks}
runs={c.runs}
approvals={c.approvals}
pendingApprovals={c.pendingApprovals}
companyAgents={c.companyAgents}
companyGoals={c.companyGoals}
companyProjects={c.companyProjects}
companyWorkspaces={c.companyWorkspaces}
companyCosts={c.companyCosts}
filteredActivity={c.filteredActivity}
companySecrets={c.companySecrets}
companyDocuments={c.companyDocuments}
companyKnowledge={c.companyKnowledge}
companyMeetings={c.companyMeetings}
companySprints={c.companySprints}
socialAccounts={c.socialAccounts}
browserActions={c.browserActions}
inboxItems={c.inboxItems}
companyMetrics={c.companyMetrics}
standupReport={c.standupReport}
selectedTask={c.selectedTask}
selectedTaskId={c.selectedTaskId}
setSelectedTaskId={c.setSelectedTaskId}
selectedConnector={c.selectedConnector}
setSelectedConnectorId={c.setSelectedConnectorId}
selectedApproval={c.selectedApproval}
selectedApprovalId={c.selectedApprovalId}
setSelectedApprovalId={c.setSelectedApprovalId}
selectedAgent={c.selectedAgent}
selectedAgentId={c.selectedAgentId}
setSelectedAgentId={c.setSelectedAgentId}
selectedGoal={c.selectedGoal}
selectedGoalId={c.selectedGoalId}
setSelectedGoalId={c.setSelectedGoalId}
selectedProject={c.selectedProject}
selectedProjectId={c.selectedProjectId}
setSelectedProjectId={c.setSelectedProjectId}
selectedWorkspace={c.selectedWorkspace}
setSelectedWorkspaceId={c.setSelectedWorkspaceId}
selectedCommunicationEntityId={c.selectedCommunicationEntityId}
setSelectedCommunicationEntityId={c.setSelectedCommunicationEntityId}
taskViewMode={c.taskViewMode}
setTaskViewMode={c.setTaskViewMode}
selectedRunId={c.selectedRunId}
navigateTo={c.navigateTo}
setSection={c.setSection}
setConsoleOpen={c.setConsoleOpen}
setSelectedRunId={c.setSelectedRunId}
refresh={c.refresh}
startRun={c.startRun}
cancelRun={c.cancelRun}
openPathWithFeedback={c.openPathWithFeedback}
setError={c.setError}
pageTransition={c.pageTransition}
section={ui.section}
error={error}
pageTransition={ui.pageTransition}
/>
<ConsoleView
consoleOpen={c.consoleOpen}
setConsoleOpen={c.setConsoleOpen}
selectedRun={c.selectedRun}
setSelectedRunId={c.setSelectedRunId}
runs={c.runs}
logBufferRef={c.logBufferRef}
openPathWithFeedback={c.openPathWithFeedback}
panelTransition={c.panelTransition}
t={c.t}
consoleOpen={ui.consoleOpen}
setConsoleOpen={ui.setConsoleOpen}
selectedRun={company.selectedRun}
setSelectedRunId={setSelectedRunId}
runs={company.runs}
logBufferRef={company.logBufferRef}
openPathWithFeedback={company.openPathWithFeedback}
panelTransition={ui.panelTransition}
t={ui.t}
/>
</main>
<CommandPaletteView
commandPaletteOpen={c.commandPaletteOpen}
setCommandPaletteOpen={c.setCommandPaletteOpen}
commandPaletteSearch={c.commandPaletteSearch}
setCommandPaletteSearch={c.setCommandPaletteSearch}
selectedPaletteIndex={c.selectedPaletteIndex}
setSelectedPaletteIndex={c.setSelectedPaletteIndex}
paletteTrapRef={c.paletteTrapRef}
filteredNavItems={c.filteredNavItems}
matchedAgents={c.matchedAgents}
matchedTasks={c.matchedTasks}
matchedRuns={c.matchedRuns}
matchedGoals={c.matchedGoals}
matchedProjects={c.matchedProjects}
hasEntityResults={c.hasEntityResults}
paletteActions={c.paletteActions}
paletteActionIndex={c.paletteActionIndex}
runs={c.runs}
navigateTo={c.navigateTo}
panelTransition={c.panelTransition}
t={c.t}
commandPaletteOpen={ui.commandPaletteOpen}
setCommandPaletteOpen={ui.setCommandPaletteOpen}
commandPaletteSearch={ui.commandPaletteSearch}
setCommandPaletteSearch={ui.setCommandPaletteSearch}
selectedPaletteIndex={ui.selectedPaletteIndex}
setSelectedPaletteIndex={ui.setSelectedPaletteIndex}
paletteTrapRef={ui.paletteTrapRef}
filteredNavItems={filteredNavItems}
matchedAgents={matchedAgents}
matchedTasks={matchedTasks}
matchedRuns={matchedRuns}
matchedGoals={matchedGoals}
matchedProjects={matchedProjects}
hasEntityResults={hasEntityResults}
paletteActions={paletteActions}
paletteActionIndex={paletteActionIndex}
runs={company.runs}
navigateTo={navigateTo}
panelTransition={ui.panelTransition}
t={ui.t}
/>
<ToastContainer />
</div>
</div>
</AppActionsContext.Provider>
</TranslationContext.Provider>
</LocaleContext.Provider>
);
+17
View File
@@ -0,0 +1,17 @@
import { createContext, useContext } from "react";
export interface AppActions {
refresh: () => Promise<void>;
setError: (error: string | null) => void;
openPathWithFeedback: (path: string) => Promise<void>;
startRun: (taskId: string) => Promise<void>;
cancelRun: (runId: string) => Promise<void>;
}
export const AppActionsContext = createContext<AppActions | null>(null);
export function useAppActions(): AppActions {
const value = useContext(AppActionsContext);
if (!value) throw new Error("useAppActions must be used inside <AppActionsContext.Provider>");
return value;
}
-209
View File
@@ -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<typeof ui.navigateTo>[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,
};
}
@@ -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;
@@ -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,
};
}
@@ -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,
};
}
@@ -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,
};
}
+40
View File
@@ -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<CompanyRecord | null>(() => {
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,
};
}
+12 -242
View File
@@ -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<ProfileSnapshot["activity"]["filter"]>;
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<void>;
startRun: (taskId: string) => Promise<void>;
cancelRun: (runId: string) => Promise<void>;
openPathWithFeedback: (path: string) => Promise<void>;
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 (
<div className={`flex-1 ${section === "communication" ? "flex flex-col overflow-hidden" : "overflow-auto"}`}>
<div className={section === "communication" ? "flex min-h-0 flex-1 flex-col" : "mx-auto flex w-full max-w-[1040px] flex-col px-6 py-6 lg:px-10 lg:py-8"}>
<div className={`flex-1 ${isCommunication ? "flex flex-col overflow-hidden" : "overflow-auto"}`}>
<div className={isCommunication ? "flex min-h-0 flex-1 flex-col" : "mx-auto flex w-full max-w-[1040px] flex-col px-6 py-6 lg:px-10 lg:py-8"}>
{error ? (
<div className={`flex items-center gap-3 rounded-lg bg-destructive-muted px-4 py-3 text-[13px] text-destructive ${section === "communication" ? "mx-6 mt-4" : "mb-6"}`}>
<div className={`flex items-center gap-3 rounded-lg bg-destructive-muted px-4 py-3 text-[13px] text-destructive ${isCommunication ? "mx-6 mt-4" : "mb-6"}`}>
<WarningCircle size={18} />
<span>{error}</span>
</div>
) : null}
<div className={section === "communication" ? "flex min-h-0 flex-1 flex-col" : "min-h-0 flex-1"}>
<div className={isCommunication ? "flex min-h-0 flex-1 flex-col" : "min-h-0 flex-1"}>
<ErrorBoundary>
<AnimatePresence mode="wait">
<motion.div
@@ -175,101 +34,12 @@ export function SectionContentView({
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -12 }}
transition={pageTransition}
className={section === "communication" ? "flex min-h-0 flex-1 flex-col" : "space-y-14"}
style={section === "communication" ? { height: "100%" } : undefined}
className={isCommunication ? "flex min-h-0 flex-1 flex-col" : "space-y-14"}
style={isCommunication ? { height: "100%" } : undefined}
>
<OrganizationSections
section={section}
snapshot={snapshot}
currentCompany={currentCompany}
companyAgents={companyAgents}
companyGoals={companyGoals}
companyProjects={companyProjects}
companyWorkspaces={companyWorkspaces}
selectedAgentId={selectedAgentId}
setSelectedAgentId={setSelectedAgentId}
selectedGoalId={selectedGoalId}
setSelectedGoalId={setSelectedGoalId}
selectedProjectId={selectedProjectId}
setSelectedProjectId={setSelectedProjectId}
setSection={setSection}
refresh={refresh}
/>
<ExecutionSections
section={section}
snapshot={snapshot}
currentCompany={currentCompany}
tasks={tasks}
runs={runs}
approvals={approvals}
pendingApprovals={pendingApprovals}
companyAgents={companyAgents}
companyGoals={companyGoals}
companyProjects={companyProjects}
companyWorkspaces={companyWorkspaces}
companyCosts={companyCosts}
companyDocuments={companyDocuments}
filteredActivity={filteredActivity}
selectedRunId={selectedRunId}
selectedTask={selectedTask}
selectedTaskId={selectedTaskId}
setSelectedTaskId={setSelectedTaskId}
selectedConnector={selectedConnector}
setSelectedConnectorId={setSelectedConnectorId}
selectedApproval={selectedApproval}
selectedApprovalId={selectedApprovalId}
setSelectedApprovalId={setSelectedApprovalId}
selectedGoalId={selectedGoalId}
setSelectedGoalId={setSelectedGoalId}
taskViewMode={taskViewMode}
setTaskViewMode={setTaskViewMode}
setSection={setSection}
setConsoleOpen={setConsoleOpen}
setSelectedRunId={setSelectedRunId}
setSelectedCommunicationEntityId={setSelectedCommunicationEntityId}
refresh={refresh}
startRun={startRun}
cancelRun={cancelRun}
openPathWithFeedback={openPathWithFeedback}
setError={setError}
/>
<OperationsSections
section={section}
snapshot={snapshot}
currentCompany={currentCompany}
locale={locale}
tasks={tasks}
runs={runs}
companyAgents={companyAgents}
companyProjects={companyProjects}
companyWorkspaces={companyWorkspaces}
companyDocuments={companyDocuments}
companyKnowledge={companyKnowledge}
companyMeetings={companyMeetings}
companySprints={companySprints}
socialAccounts={socialAccounts}
browserActions={browserActions}
inboxItems={inboxItems}
companyMetrics={companyMetrics}
standupReport={standupReport}
companySecrets={companySecrets}
selectedWorkspace={selectedWorkspace}
setSelectedWorkspaceId={setSelectedWorkspaceId}
selectedCommunicationEntityId={selectedCommunicationEntityId}
setSelectedCommunicationEntityId={setSelectedCommunicationEntityId}
setSelectedTaskId={setSelectedTaskId}
setSelectedApprovalId={setSelectedApprovalId}
setSection={setSection}
navigateTo={navigateTo}
refresh={refresh}
openPathWithFeedback={openPathWithFeedback}
setError={setError}
filteredActivity={filteredActivity}
companyCosts={companyCosts}
companyGoals={companyGoals}
/>
<OrganizationSections section={section} />
<ExecutionSections section={section} />
<OperationsSections section={section} />
</motion.div>
</AnimatePresence>
</ErrorBoundary>
@@ -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<ProfileSnapshot["activity"]["filter"]>;
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<void>;
startRun: (taskId: string) => Promise<void>;
cancelRun: (runId: string) => Promise<void>;
openPathWithFeedback: (path: string) => Promise<void>;
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 (
<>
@@ -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<void>;
openPathWithFeedback: (path: string) => Promise<void>;
setError: (error: string | null) => void;
filteredActivity: ReturnType<ProfileSnapshot["activity"]["filter"]>;
companyCosts: ReturnType<ProfileSnapshot["costs"]["filter"]>;
companyGoals: ReturnType<NonNullable<ProfileSnapshot["goals"]>["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" ? (
@@ -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<void>;
}
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 (
<>