mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix: promote MCP server display name to a required form field (#24652)
A customer reported that on the `/agents` MCP server create form, the Create button stays disabled even after filling in Slug and Server URL. The form also requires a non-empty display name, but the display name was rendered as a placeholder-style inline title, so it looked optional. Addressing [Tracy's feedback](https://github.com/coder/coder/pull/24652#issuecomment-4301473164), this PR promotes the display name to a proper labeled form field, matching Slug and Server URL. ## Changes - Remove the inline editable title input and pencil icon from the header. - Header now shows a static server name (or `New MCP server` when creating). - Add a labeled `Display name` field as the first field in the form, with the same required marker treatment used by Slug and Server URL. - Update the `CreateServer` story to match (no longer asserts the removed pencil icon). ## Screenshot  Reported via Support for a Netflix user on the MCP server admin panel. --- _Filed on behalf of the user by Coder Agents._ --------- Co-authored-by: Jaayden Halko <jaayden@coder.com>
This commit is contained in:
co-authored by
Jaayden Halko
parent
1e21b288b9
commit
f96f7b992f
@@ -162,21 +162,18 @@ export const CreateServer: Story = {
|
||||
await body.findByRole("button", { name: /Add your first server/i }),
|
||||
);
|
||||
|
||||
// Fill in the display name via the inline header input.
|
||||
// Fill in the Display name field.
|
||||
const nameInput = await body.findByLabelText(/Display Name/i);
|
||||
await userEvent.type(nameInput, "Sentry");
|
||||
|
||||
// Required fields (slug, server URL) are always visible. Optional
|
||||
// sections start collapsed and the Enabled switch is edit-only.
|
||||
// PencilIcon sits next to the editable name.
|
||||
// Required fields (display name, slug, server URL) are always visible.
|
||||
// Optional sections start collapsed and the Enabled switch is edit-only.
|
||||
expect(body.getByLabelText(/^Slug/i)).toBeInTheDocument();
|
||||
expect(body.getByLabelText(/Server URL/i)).toBeInTheDocument();
|
||||
expect(body.queryByLabelText(/Description/i)).not.toBeInTheDocument();
|
||||
expect(
|
||||
body.queryByRole("switch", { name: /Enabled/i }),
|
||||
).not.toBeInTheDocument();
|
||||
const nameRow = nameInput.closest("div")?.parentElement;
|
||||
expect(nameRow?.querySelector("svg.lucide-pencil")).toBeTruthy();
|
||||
|
||||
// Slug should auto-populate from the display name.
|
||||
await expect(body.getByLabelText(/^Slug/i)).toHaveValue("sentry");
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
ChevronDownIcon,
|
||||
ChevronRightIcon,
|
||||
CircleIcon,
|
||||
PencilIcon,
|
||||
PlusIcon,
|
||||
ServerIcon,
|
||||
XIcon,
|
||||
@@ -471,41 +470,15 @@ const ServerForm: FC<ServerFormProps> = ({
|
||||
|
||||
return (
|
||||
<div className="flex min-h-full flex-col">
|
||||
{/* Back */}
|
||||
<BackButton onClick={onBack} />
|
||||
{/* Header with icon + editable name + enabled toggle */}
|
||||
<div className="flex items-center gap-3">
|
||||
<MCPServerIcon
|
||||
iconUrl={form.values.iconURL}
|
||||
name={form.values.displayName || "New server"}
|
||||
className="h-8 w-8"
|
||||
/>
|
||||
<div className="inline-flex items-center gap-1">
|
||||
<div className="relative inline-grid">
|
||||
<span
|
||||
className="invisible col-start-1 row-start-1 whitespace-pre text-lg font-medium"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{form.values.displayName || "Server display name"}
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
value={form.values.displayName}
|
||||
onChange={(e) => {
|
||||
form.setFieldValue("displayName", e.target.value);
|
||||
if (!form.values.slugTouched) {
|
||||
form.setFieldValue("slug", slugify(e.target.value));
|
||||
}
|
||||
}}
|
||||
disabled={isDisabled}
|
||||
spellCheck={false}
|
||||
className="col-start-1 row-start-1 m-0 min-w-0 border-0 bg-transparent p-0 text-lg font-medium text-content-primary outline-none placeholder:text-content-secondary focus:ring-0"
|
||||
placeholder="Server display name"
|
||||
aria-label="Display Name"
|
||||
/>
|
||||
</div>
|
||||
<PencilIcon className="h-3.5 w-3.5 shrink-0 text-content-secondary" />
|
||||
</div>
|
||||
<span
|
||||
className="truncate text-lg font-medium text-content-primary"
|
||||
title={form.values.displayName || undefined}
|
||||
>
|
||||
{form.values.displayName ||
|
||||
(isEditing ? "Server display name" : "New MCP server")}
|
||||
</span>
|
||||
{isEditing && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -538,6 +511,26 @@ const ServerForm: FC<ServerFormProps> = ({
|
||||
>
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-4">
|
||||
<Field
|
||||
label="Display name"
|
||||
htmlFor={`${formId}-display-name`}
|
||||
required
|
||||
>
|
||||
<Input
|
||||
id={`${formId}-display-name`}
|
||||
className="h-9 text-[13px]"
|
||||
value={form.values.displayName}
|
||||
onChange={(e) => {
|
||||
form.setFieldValue("displayName", e.target.value);
|
||||
if (!form.values.slugTouched) {
|
||||
form.setFieldValue("slug", slugify(e.target.value));
|
||||
}
|
||||
}}
|
||||
placeholder="e.g. Sentry"
|
||||
disabled={isDisabled}
|
||||
aria-label="Display Name"
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Slug" htmlFor={`${formId}-slug`} required>
|
||||
<Input
|
||||
id={`${formId}-slug`}
|
||||
|
||||
Reference in New Issue
Block a user