diff --git a/src/renderer/components/organization/agent-form/agent-form-fields/EditFormBody.tsx b/src/renderer/components/organization/agent-form/agent-form-fields/EditFormBody.tsx index 2d55c4b3..cedd63ff 100644 --- a/src/renderer/components/organization/agent-form/agent-form-fields/EditFormBody.tsx +++ b/src/renderer/components/organization/agent-form/agent-form-fields/EditFormBody.tsx @@ -13,7 +13,8 @@ import { IdentitySection } from "./edit-sections/IdentitySection"; import { RuntimeSection } from "./edit-sections/RuntimeSection"; import { ConstraintsSection } from "./edit-sections/ConstraintsSection"; import { EditActionBar } from "./edit-sections/EditActionBar"; -import { SectionHeader } from "./edit-sections/SectionHeader"; + +type EditTab = "identity" | "runtime" | "constraints" | "behavior"; interface EditFormBodyProps { agent: AgentRecord; @@ -31,16 +32,17 @@ interface EditFormBodyProps { } /** - * Orchestrator for the agent Edit modal. Owns the save / wake / delete - * side effects, the save-error latch, and the dirty-state derivation; - * renders through five section components so each concern (identity, - * runtime, constraints, behavior, skills) lives in a ≤200 LOC file. + * Agent Edit modal body. Tabs replace the previous single-scroll stack + * so a 12-field edit surface no longer forces the modal to grow tall + * and scroll internally. Same IA pattern as the Settings page. * - * "Dirty" is computed from the form state vs. the persisted agent record - * plus the persisted budget policy. We intentionally compare trimmed - * strings and numeric budget so that whitespace-only edits do not falsely - * enable Save. Root-agent guards against Delete / terminate live here - * and are threaded into the relevant sections. + * Tab state is local — the modal is transient. Default is "identity" + * because that is where users land for name/role/department edits, + * which are the most common touches after the runtime cluster. + * + * Dirty-state and save/wake/delete handlers stay in this orchestrator; + * each tab renders a pure-body section component that receives form + * state + computed flags via props. */ export function EditFormBody({ agent, @@ -57,6 +59,7 @@ export function EditFormBody({ }: EditFormBodyProps) { const t = useT(); const [saveError, setSaveError] = useState(null); + const [activeTab, setActiveTab] = useState("identity"); const pendingApproval = agent.status === "pending_approval"; const isRootAgent = !agent.reportsTo; @@ -83,6 +86,13 @@ export function EditFormBody({ ); }, [form, agent, pendingApproval]); + const tabs: Array<{ id: EditTab; label: string }> = [ + { id: "identity", label: t("agent.form.sectionIdentity") }, + { id: "runtime", label: t("agent.form.sectionRuntime") }, + { id: "constraints", label: t("agent.form.sectionConstraints") }, + { id: "behavior", label: t("agent.form.sectionBehavior") }, + ]; + async function persistAgentDraft() { const meta = { ...form.existingMeta }; if (form.thinkingEffort) meta.thinkingEffort = form.thinkingEffort; @@ -159,28 +169,65 @@ export function EditFormBody({ } return ( -
- - - - - void handleWakeNow()} - /> - +
+ + +
+ {activeTab === "identity" ? ( + + ) : null} + {activeTab === "runtime" ? ( + + ) : null} + {activeTab === "constraints" ? ( + + ) : null} + {activeTab === "behavior" ? ( + void handleWakeNow()} + /> + ) : null} +
+ 0 || agent.spentMonthlyUsd > 0; return ( - - - {showBurn ? ( -
- -

- {t("agent.form.budgetBurn") - .replace("{spent}", formatMoney(agent.spentMonthlyUsd)) - .replace("{budget}", formatMoney(budgetValue))} -

-
- ) : null} +
+
+ + {showBurn ? ( + <> + +

+ {t("agent.form.budgetBurn") + .replace("{spent}", formatMoney(agent.spentMonthlyUsd)) + .replace("{budget}", formatMoney(budgetValue))} +

+ + ) : null} +