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:
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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "新目标",
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user