refactor(selection): split selectedAgentId into focus vs edit-modal states; improve Edit modal title

Before this commit one piece of state (`selectedAgentId`) did three
jobs: roster highlight, navigation destination, and Edit-modal open
flag. The three jobs had incompatible lifecycles — highlight should
persist across modal close, navigation should land on a screen
without opening a modal, and the modal itself only wants to open on
explicit intent. The conflation was the root cause of the "Edit: CEO
auto-opens on first Team view" bug (commit 41132e2 patched the most
visible symptom by not pre-selecting the lead agent, but the coupling
remained).

Split into two slice fields:

- `selectedAgentId` — which agent is currently highlighted in the
  roster / OrgChart. Survives modal close. Set by: row click, card
  click, navigation from Inbox / Hiring (all of which pass through
  `openAgentEditor` which sets both so the highlight follows the last
  interaction).
- `editingAgentId` — which agent's Edit modal is open, or
  NEW_ENTITY_ID for the "Request new hire" modal. Only set by
  explicit user action; cleared on modal close. Never set by
  navigation or onboarding — the pre-select pathway cannot
  accidentally open a modal again.

Plumbing:

- `selection.ts`: new `editingAgentId` + `setEditingAgentId`, plus
  clear on `clearCompanyScopedSelections`.
- `useCompanyData.ts`: derive `editingAgent` record from the id.
- `useCompanyController.ts`: forward `editingAgent` on the section
  context.
- `OrganizationSections.tsx`: `agents` section modals read
  `editingAgentId` / `editingAgent`. "Request new hire" button uses
  `setEditingAgentId(NEW_ENTITY_ID)`. Single-click handlers go
  through `openAgentEditor(id)` which updates both states.

Also improved Edit modal title (was "Edit: CEO", a machined English
prefix on a Chinese UI with zero context about the role):

- zh: "编辑 {name}({role})"
- en: "Edit {name} ({role})"

Renders as "编辑 CEO(首席执行官)" in the screenshot example.

Covers J9 / S2.
This commit is contained in:
ZacharyZhang-NY committed 2026-04-17 19:54:16 -04:00
1 parent 9981ee342b
commit dc33131553
6 files changed
+50 -12

No files matched your search

@@ -181,6 +181,7 @@ export function useCompanyController() {
selectedApproval: companyData.selectedApproval,
selectedRun: companyData.selectedRun,
selectedAgent: companyData.selectedAgent,
editingAgent: companyData.editingAgent,
selectedGoal: companyData.selectedGoal,
selectedProject: companyData.selectedProject,
selectedWorkspace: companyData.selectedWorkspace,
+7
View File
@@ -19,6 +19,7 @@ export function useCompanyData(
const selectedTaskId = useAppStore((s) => s.selectedTaskId);
const selectedApprovalId = useAppStore((s) => s.selectedApprovalId);
const selectedAgentId = useAppStore((s) => s.selectedAgentId);
const editingAgentId = useAppStore((s) => s.editingAgentId);
const selectedGoalId = useAppStore((s) => s.selectedGoalId);
const selectedProjectId = useAppStore((s) => s.selectedProjectId);
const selectedWorkspaceId = useAppStore((s) => s.selectedWorkspaceId);
@@ -58,6 +59,10 @@ export function useCompanyData(
() => companyAgents.find((a) => a.id === selectedAgentId) ?? null,
[companyAgents, selectedAgentId],
);
const editingAgent = useMemo(
() => companyAgents.find((a) => a.id === editingAgentId) ?? null,
[companyAgents, editingAgentId],
);
const selectedGoal = useMemo(
() => companyGoals.find((g) => g.id === selectedGoalId) ?? null,
[companyGoals, selectedGoalId],
@@ -104,6 +109,7 @@ export function useCompanyData(
selectedApproval,
selectedRun,
selectedAgent,
editingAgent,
selectedGoal,
selectedProject,
selectedWorkspace,
@@ -128,6 +134,7 @@ export function useCompanyData(
selectedApproval,
selectedRun,
selectedAgent,
editingAgent,
selectedGoal,
selectedProject,
selectedWorkspace,
+1
View File
@@ -342,6 +342,7 @@ export const en: Record<string, string> = {
"org.agentRoster": "Agent roster",
"org.newHireRequest": "New hire request",
"org.editAgent": "Edit",
"org.editAgentTitle": "Edit {name} ({role})",
"org.requestNewHire": "Request new hire",
"org.goalStack": "Goal stack",
"org.newGoal": "New goal",
+1
View File
@@ -342,6 +342,7 @@ export const zh: Record<string, string> = {
"org.agentRoster": "智能体名册",
"org.newHireRequest": "新招聘申请",
"org.editAgent": "编辑",
"org.editAgentTitle": "编辑 {name}({role})",
"org.requestNewHire": "申请新招聘",
"org.goalStack": "目标栈",
"org.newGoal": "新目标",
+14
View File
@@ -6,7 +6,14 @@ export interface SelectionSlice {
selectedConnectorId: string | null;
selectedApprovalId: string | null;
selectedRunId: string | null;
/** Highlighted agent (roster row / orgchart card). Navigation target.
* Does NOT control the Edit modal — see editingAgentId. */
selectedAgentId: string | null;
/** Agent whose Edit modal is currently open, or NEW_ENTITY_ID for the
* new-hire request modal. Only set by explicit user action (row click,
* card click, "Request new hire" button); never by navigation or
* onboarding defaults. Cleared on modal close. */
editingAgentId: string | null;
selectedGoalId: string | null;
selectedProjectId: string | null;
selectedWorkspaceId: string | null;
@@ -16,6 +23,7 @@ export interface SelectionSlice {
setSelectedApprovalId: (id: string | null) => void;
setSelectedRunId: (id: string | null) => void;
setSelectedAgentId: (id: string | null) => void;
setEditingAgentId: (id: string | null) => void;
setSelectedGoalId: (id: string | null) => void;
setSelectedProjectId: (id: string | null) => void;
setSelectedWorkspaceId: (id: string | null) => void;
@@ -34,6 +42,7 @@ export const createSelectionSlice: StateCreator<
selectedApprovalId: null,
selectedRunId: null,
selectedAgentId: null,
editingAgentId: null,
selectedGoalId: null,
selectedProjectId: null,
selectedWorkspaceId: null,
@@ -58,6 +67,10 @@ export const createSelectionSlice: StateCreator<
set((state) => {
state.selectedAgentId = id;
}),
setEditingAgentId: (id) =>
set((state) => {
state.editingAgentId = id;
}),
setSelectedGoalId: (id) =>
set((state) => {
state.selectedGoalId = id;
@@ -80,6 +93,7 @@ export const createSelectionSlice: StateCreator<
state.selectedApprovalId = null;
state.selectedRunId = null;
state.selectedAgentId = null;
state.editingAgentId = null;
state.selectedGoalId = null;
state.selectedProjectId = null;
state.selectedWorkspaceId = null;
@@ -35,7 +35,7 @@ export function OrganizationSections({ section }: OrganizationSectionsProps) {
companyGoals,
companyProjects,
companyWorkspaces,
selectedAgent,
editingAgent,
selectedGoal,
selectedProject,
} = useSectionContext();
@@ -43,53 +43,67 @@ export function OrganizationSections({ section }: OrganizationSectionsProps) {
const selectedAgentId = useAppStore((s) => s.selectedAgentId);
const setSelectedAgentId = useAppStore((s) => s.setSelectedAgentId);
const editingAgentId = useAppStore((s) => s.editingAgentId);
const setEditingAgentId = useAppStore((s) => s.setEditingAgentId);
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);
// Open the Edit modal on row / card click. `selectedAgentId` is updated
// alongside so the roster highlight follows the user's last interaction
// even after the modal closes.
const openAgentEditor = (id: string) => {
setSelectedAgentId(id);
setEditingAgentId(id);
};
if (!snapshot || !currentCompany) return null;
return (
<>
{section === "agents" ? (
<>
<OrgChart agents={companyAgents} connectors={snapshot.connectors} onSelectAgent={(id) => setSelectedAgentId(id)} />
<Panel title={t("org.agentRoster")} action={<ActionButton label={t("org.newHireRequest")} icon={UserPlus} tone="accent" onClick={() => setSelectedAgentId(NEW_ENTITY_ID)} />}>
<OrgChart agents={companyAgents} connectors={snapshot.connectors} onSelectAgent={openAgentEditor} />
<Panel title={t("org.agentRoster")} action={<ActionButton label={t("org.newHireRequest")} icon={UserPlus} tone="accent" onClick={() => setEditingAgentId(NEW_ENTITY_ID)} />}>
<AgentRoster
agents={companyAgents}
connectors={snapshot.connectors}
workspaces={companyWorkspaces}
selectedAgentId={selectedAgentId}
onSelect={setSelectedAgentId}
onSelect={openAgentEditor}
/>
</Panel>
<Modal open={selectedAgentId === NEW_ENTITY_ID} title={t("org.requestNewHire")} onClose={() => setSelectedAgentId(null)}>
<Modal open={editingAgentId === NEW_ENTITY_ID} title={t("org.requestNewHire")} onClose={() => setEditingAgentId(null)}>
<AgentCreator
companyId={currentCompany.id}
connectors={snapshot.connectors}
workspaces={companyWorkspaces}
agents={companyAgents}
onSaved={async () => {
setSelectedAgentId(null);
setEditingAgentId(null);
setSection("hiring");
await refresh();
}}
/>
</Modal>
<Modal open={!!selectedAgent} title={selectedAgent ? `Edit: ${selectedAgent.name}` : ""} onClose={() => setSelectedAgentId(null)}>
{selectedAgent ? (
<Modal
open={!!editingAgent}
title={editingAgent ? t("org.editAgentTitle").replace("{name}", editingAgent.name).replace("{role}", editingAgent.role) : ""}
onClose={() => setEditingAgentId(null)}
>
{editingAgent ? (
<AgentEditor
key={selectedAgentId}
agent={selectedAgent}
key={editingAgent.id}
agent={editingAgent}
connectors={snapshot.connectors}
workspaces={companyWorkspaces}
agents={companyAgents}
companyId={currentCompany.id}
onSaved={refresh}
onCancel={() => setSelectedAgentId(null)}
onDeleted={async () => { setSelectedAgentId(null); await refresh(); }}
onCancel={() => setEditingAgentId(null)}
onDeleted={async () => { setEditingAgentId(null); await refresh(); }}
/>
) : null}
</Modal>