feat(01-01): create Zustand store with 6 slices, IPC batcher, and unit tests

- Install zustand 5.x and immer 11.x dependencies
- Create 6 Zustand slices: navigation, company, selection, ui, communication, social
- Create combined store with devtools + immer middleware
- Create IPC event batcher with 50ms debounce window (schedule/flush/dispose)
- Create typed selector hooks for narrow state subscriptions
- Add 14 unit tests covering store initialization, slice behaviors, and batcher timing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 4.6 committed 2026-03-22 01:30:37 -04:00
1 parent a4de64b876
commit 5e9d36c058
13 files changed
+818 -4

No files matched your search

+47
View File
@@ -0,0 +1,47 @@
import { useAppStore } from "./store";
// Navigation slice selectors
export const useSection = () => useAppStore((s) => s.section);
export const useSetSection = () => useAppStore((s) => s.setSection);
export const useNavigateTo = () => useAppStore((s) => s.navigateTo);
export const useCommandPaletteOpen = () =>
useAppStore((s) => s.commandPaletteOpen);
export const useConsoleOpen = () => useAppStore((s) => s.consoleOpen);
export const useSidebarCollapsed = () =>
useAppStore((s) => s.sidebarCollapsed);
// Company slice selectors
export const useSnapshot = () => useAppStore((s) => s.snapshot);
export const useCurrentCompanyId = () =>
useAppStore((s) => s.currentCompanyId);
export const useError = () => useAppStore((s) => s.error);
export const useInboxItems = () => useAppStore((s) => s.inboxItems);
export const useCompanyMetrics = () => useAppStore((s) => s.companyMetrics);
export const useStandupReport = () => useAppStore((s) => s.standupReport);
export const useBadges = () => useAppStore((s) => s.badges);
// Selection slice selectors
export const useSelectedTaskId = () => useAppStore((s) => s.selectedTaskId);
export const useSelectedAgentId = () =>
useAppStore((s) => s.selectedAgentId);
export const useSelectedApprovalId = () =>
useAppStore((s) => s.selectedApprovalId);
export const useSelectedRunId = () => useAppStore((s) => s.selectedRunId);
export const useSelectedGoalId = () => useAppStore((s) => s.selectedGoalId);
export const useSelectedProjectId = () =>
useAppStore((s) => s.selectedProjectId);
export const useSelectedWorkspaceId = () =>
useAppStore((s) => s.selectedWorkspaceId);
export const useSelectedConnectorId = () =>
useAppStore((s) => s.selectedConnectorId);
export const useSelectedCommunicationEntityId = () =>
useAppStore((s) => s.selectedCommunicationEntityId);
// UI slice selectors
export const useLocale = () => useAppStore((s) => s.locale);
export const useShowOnboarding = () => useAppStore((s) => s.showOnboarding);
export const useTaskViewMode = () => useAppStore((s) => s.taskViewMode);
// Social slice selectors
export const useSocialAccounts = () => useAppStore((s) => s.socialAccounts);
export const useBrowserActions = () => useAppStore((s) => s.browserActions);
@@ -0,0 +1,17 @@
import type { StateCreator } from "zustand";
import type { AppState } from "../store";
export interface CommunicationSlice {
// Placeholder for future message state -- this slice will be populated
// during component decomposition. The communication component currently
// manages its own internal state.
}
export const createCommunicationSlice: StateCreator<
AppState,
[["zustand/immer", never]],
[],
CommunicationSlice
> = () => ({
// Empty state for now
});
+88
View File
@@ -0,0 +1,88 @@
import type { StateCreator } from "zustand";
import type { AppState } from "../store";
import type {
CompanyMetrics,
InboxItem,
ProfileSnapshot,
StandupReport,
} from "@shared/types";
import type { SidebarBadges } from "@shared/contracts";
export interface CompanySlice {
snapshot: ProfileSnapshot | null;
currentCompanyId: string | null;
companyMetrics: CompanyMetrics | null;
standupReport: StandupReport | null;
inboxItems: InboxItem[];
badges: SidebarBadges | null;
error: string | null;
setSnapshot: (snapshot: ProfileSnapshot | null) => void;
setCurrentCompanyId: (id: string | null) => void;
setCompanyMetrics: (metrics: CompanyMetrics | null) => void;
setStandupReport: (report: StandupReport | null) => void;
setInboxItems: (items: InboxItem[]) => void;
setBadges: (badges: SidebarBadges | null) => void;
setError: (error: string | null) => void;
refreshSnapshot: () => Promise<void>;
}
export const createCompanySlice: StateCreator<
AppState,
[["zustand/immer", never]],
[],
CompanySlice
> = (set) => ({
snapshot: null,
currentCompanyId: null,
companyMetrics: null,
standupReport: null,
inboxItems: [],
badges: null,
error: null,
setSnapshot: (snapshot) =>
set((state) => {
state.snapshot = snapshot;
}),
setCurrentCompanyId: (id) =>
set((state) => {
state.currentCompanyId = id;
}),
setCompanyMetrics: (metrics) =>
set((state) => {
state.companyMetrics = metrics;
}),
setStandupReport: (report) =>
set((state) => {
state.standupReport = report;
}),
setInboxItems: (items) =>
set((state) => {
state.inboxItems = items;
}),
setBadges: (badges) =>
set((state) => {
state.badges = badges;
}),
setError: (error) =>
set((state) => {
state.error = error;
}),
refreshSnapshot: async () => {
try {
const result = await window.agentCompany.loadSnapshot();
if (result.ok) {
set((state) => {
state.snapshot = result.data;
});
} else {
set((state) => {
state.error = result.error.message;
});
}
} catch (err) {
set((state) => {
state.error = err instanceof Error ? err.message : String(err);
});
}
},
});
+86
View File
@@ -0,0 +1,86 @@
import type { StateCreator } from "zustand";
import type { AppState } from "../store";
import type { SectionId } from "@shared/types";
export interface NavigationSlice {
section: SectionId;
commandPaletteOpen: boolean;
commandPaletteSearch: string;
selectedPaletteIndex: number;
consoleOpen: boolean;
sidebarCollapsed: boolean;
companySwitcherOpen: boolean;
setSection: (section: SectionId) => void;
toggleCommandPalette: () => void;
setCommandPaletteSearch: (search: string) => void;
setSelectedPaletteIndex: (index: number) => void;
toggleConsole: () => void;
toggleSidebar: () => void;
toggleCompanySwitcher: () => void;
navigateTo: (section: SectionId, entityId?: string) => void;
}
export const createNavigationSlice: StateCreator<
AppState,
[["zustand/immer", never]],
[],
NavigationSlice
> = (set) => ({
section: "overview",
commandPaletteOpen: false,
commandPaletteSearch: "",
selectedPaletteIndex: 0,
consoleOpen: false,
sidebarCollapsed: false,
companySwitcherOpen: false,
setSection: (section) =>
set((state) => {
state.section = section;
}),
toggleCommandPalette: () =>
set((state) => {
state.commandPaletteOpen = !state.commandPaletteOpen;
if (!state.commandPaletteOpen) {
state.commandPaletteSearch = "";
state.selectedPaletteIndex = 0;
}
}),
setCommandPaletteSearch: (search) =>
set((state) => {
state.commandPaletteSearch = search;
state.selectedPaletteIndex = 0;
}),
setSelectedPaletteIndex: (index) =>
set((state) => {
state.selectedPaletteIndex = index;
}),
toggleConsole: () =>
set((state) => {
state.consoleOpen = !state.consoleOpen;
}),
toggleSidebar: () =>
set((state) => {
state.sidebarCollapsed = !state.sidebarCollapsed;
}),
toggleCompanySwitcher: () =>
set((state) => {
state.companySwitcherOpen = !state.companySwitcherOpen;
}),
navigateTo: (section, entityId) =>
set((state) => {
state.section = section;
if (entityId) {
if (section === "agents") state.selectedAgentId = entityId;
else if (section === "tasks") state.selectedTaskId = entityId;
else if (section === "approvals") state.selectedApprovalId = entityId;
else if (section === "runs") {
state.selectedRunId = entityId;
state.consoleOpen = true;
} else if (section === "goals") state.selectedGoalId = entityId;
else if (section === "projects") state.selectedProjectId = entityId;
else if (section === "connectors") state.selectedConnectorId = entityId;
else if (section === "communication")
state.selectedCommunicationEntityId = entityId;
}
}),
});
+89
View File
@@ -0,0 +1,89 @@
import type { StateCreator } from "zustand";
import type { AppState } from "../store";
export interface SelectionSlice {
selectedTaskId: string | null;
selectedConnectorId: string | null;
selectedApprovalId: string | null;
selectedRunId: string | null;
selectedAgentId: string | null;
selectedGoalId: string | null;
selectedProjectId: string | null;
selectedWorkspaceId: string | null;
selectedCommunicationEntityId: string | null;
setSelectedTaskId: (id: string | null) => void;
setSelectedConnectorId: (id: string | null) => void;
setSelectedApprovalId: (id: string | null) => void;
setSelectedRunId: (id: string | null) => void;
setSelectedAgentId: (id: string | null) => void;
setSelectedGoalId: (id: string | null) => void;
setSelectedProjectId: (id: string | null) => void;
setSelectedWorkspaceId: (id: string | null) => void;
setSelectedCommunicationEntityId: (id: string | null) => void;
clearCompanyScopedSelections: () => void;
}
export const createSelectionSlice: StateCreator<
AppState,
[["zustand/immer", never]],
[],
SelectionSlice
> = (set) => ({
selectedTaskId: null,
selectedConnectorId: null,
selectedApprovalId: null,
selectedRunId: null,
selectedAgentId: null,
selectedGoalId: null,
selectedProjectId: null,
selectedWorkspaceId: null,
selectedCommunicationEntityId: null,
setSelectedTaskId: (id) =>
set((state) => {
state.selectedTaskId = id;
}),
setSelectedConnectorId: (id) =>
set((state) => {
state.selectedConnectorId = id;
}),
setSelectedApprovalId: (id) =>
set((state) => {
state.selectedApprovalId = id;
}),
setSelectedRunId: (id) =>
set((state) => {
state.selectedRunId = id;
}),
setSelectedAgentId: (id) =>
set((state) => {
state.selectedAgentId = id;
}),
setSelectedGoalId: (id) =>
set((state) => {
state.selectedGoalId = id;
}),
setSelectedProjectId: (id) =>
set((state) => {
state.selectedProjectId = id;
}),
setSelectedWorkspaceId: (id) =>
set((state) => {
state.selectedWorkspaceId = id;
}),
setSelectedCommunicationEntityId: (id) =>
set((state) => {
state.selectedCommunicationEntityId = id;
}),
clearCompanyScopedSelections: () =>
set((state) => {
state.selectedTaskId = null;
state.selectedApprovalId = null;
state.selectedRunId = null;
state.selectedAgentId = null;
state.selectedGoalId = null;
state.selectedProjectId = null;
state.selectedWorkspaceId = null;
state.selectedCommunicationEntityId = null;
// selectedConnectorId is NOT cleared -- connectors are global
}),
});
+28
View File
@@ -0,0 +1,28 @@
import type { StateCreator } from "zustand";
import type { AppState } from "../store";
import type { SocialAccountRecord, BrowserActionRecord } from "@shared/types";
export interface SocialSlice {
socialAccounts: SocialAccountRecord[];
browserActions: BrowserActionRecord[];
setSocialAccounts: (accounts: SocialAccountRecord[]) => void;
setBrowserActions: (actions: BrowserActionRecord[]) => void;
}
export const createSocialSlice: StateCreator<
AppState,
[["zustand/immer", never]],
[],
SocialSlice
> = (set) => ({
socialAccounts: [],
browserActions: [],
setSocialAccounts: (accounts) =>
set((state) => {
state.socialAccounts = accounts;
}),
setBrowserActions: (actions) =>
set((state) => {
state.browserActions = actions;
}),
});
+57
View File
@@ -0,0 +1,57 @@
import type { StateCreator } from "zustand";
import type { AppState } from "../store";
import type { AppLocale } from "@shared/locale";
import { DEFAULT_LOCALE } from "@shared/locale";
export interface UiSlice {
locale: AppLocale;
showOnboarding: boolean;
initialOnboardingLocked: boolean;
confirmDeleteCompanyId: string | null;
taskViewMode: "list" | "kanban";
logRevision: number;
setLocale: (locale: AppLocale) => void;
setShowOnboarding: (show: boolean) => void;
setInitialOnboardingLocked: (locked: boolean) => void;
setConfirmDeleteCompanyId: (id: string | null) => void;
setTaskViewMode: (mode: "list" | "kanban") => void;
incrementLogRevision: () => void;
}
export const createUiSlice: StateCreator<
AppState,
[["zustand/immer", never]],
[],
UiSlice
> = (set) => ({
locale: DEFAULT_LOCALE,
showOnboarding: false,
initialOnboardingLocked: false,
confirmDeleteCompanyId: null,
taskViewMode: "list",
logRevision: 0,
setLocale: (locale) =>
set((state) => {
state.locale = locale;
}),
setShowOnboarding: (show) =>
set((state) => {
state.showOnboarding = show;
}),
setInitialOnboardingLocked: (locked) =>
set((state) => {
state.initialOnboardingLocked = locked;
}),
setConfirmDeleteCompanyId: (id) =>
set((state) => {
state.confirmDeleteCompanyId = id;
}),
setTaskViewMode: (mode) =>
set((state) => {
state.taskViewMode = mode;
}),
incrementLogRevision: () =>
set((state) => {
state.logRevision += 1;
}),
});
+42
View File
@@ -0,0 +1,42 @@
import { create } from "zustand";
import { immer } from "zustand/middleware/immer";
import { devtools } from "zustand/middleware";
import {
createNavigationSlice,
type NavigationSlice,
} from "./slices/navigation";
import { createCompanySlice, type CompanySlice } from "./slices/company";
import {
createSelectionSlice,
type SelectionSlice,
} from "./slices/selection";
import { createUiSlice, type UiSlice } from "./slices/ui";
import {
createCommunicationSlice,
type CommunicationSlice,
} from "./slices/communication";
import { createSocialSlice, type SocialSlice } from "./slices/social";
export type AppState = NavigationSlice &
CompanySlice &
SelectionSlice &
UiSlice &
CommunicationSlice &
SocialSlice;
export const useAppStore = create<AppState>()(
devtools(
immer((...a) => ({
...createNavigationSlice(...a),
...createCompanySlice(...a),
...createSelectionSlice(...a),
...createUiSlice(...a),
...createCommunicationSlice(...a),
...createSocialSlice(...a),
})),
{
name: "AgentCompany",
enabled: process.env.NODE_ENV === "development",
}
)
);