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>
);