mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
fix(site/src/pages/AgentsPage): use sentence case for UI labels (#25941)
Converts all title-case UI labels in the Coder Agents area to sentence
case for consistency. Also renames the "New Agent" sidebar button to
"New chat".
## Changes
### Settings headings
| Before | After |
|---|---|
| Personal Instructions | Personal instructions |
| Chat Layout | Chat layout |
| Keyboard Shortcuts | Keyboard shortcuts |
| Thinking Display | Thinking display |
| Shell Output Display | Shell output display |
| Code Diff Display | Code diff display |
| Autostop Fallback | Autostop fallback |
| Workspace Autostop Fallback | Workspace autostop fallback |
| Auto-Archive Inactive Conversations | Auto-archive inactive
conversations |
| Conversation Retention Period | Conversation retention period |
| Chat Debug Data Retention | Chat debug data retention |
| System Instructions | System instructions |
| Context Compaction | Context compaction |
| Cost Tracking | Cost tracking |
| Provider Configuration | Provider configuration |
| Virtual Desktop | Virtual desktop |
### Select/option labels
| Before | After |
|---|---|
| Always Expanded | Always expanded |
| Always Collapsed | Always collapsed |
### Sidebar and nav labels
| Before | After |
|---|---|
| New Agent | New chat |
| Personal Skills | Personal skills |
| Manage Agents | Manage agents |
| MCP Servers | MCP servers |
| Back to Settings | Back to settings |
| Back to Agents | Back to agents |
### Form field labels
| Before | After |
|---|---|
| Display Name | Display name |
| Client Secret | Client secret |
| Header Name | Header name |
| Tool Allow List | Tool allow list |
| Tool Deny List | Tool deny list |
| Spend Limit | Spend limit |
| Cache Read | Cache read |
| Cache Write | Cache write |
| Model Identifier | Model identifier |
| Context Limit | Context limit |
| Compression Threshold | Compression threshold |
### Model form titles
| Before | After |
|---|---|
| Add Model | Add model |
| Edit Model | Edit model |
| Duplicate Model | Duplicate model |
### Admin/limits labels
| Before | After |
|---|---|
| Group Limits | Group limits |
| Per-User Overrides | Per-user overrides |
| Default Spend Limit | Default spend limit |
### Other
| Before | After |
|---|---|
| Weekly/Workspace Usage | Weekly/Workspace usage |
| View Usage | View usage |
| attached image / attached file | Attached image / Attached file |
### Not changed (server-provided labels)
Model config field labels like "Reasoning Effort", "Max Completion
Tokens", "Send Reasoning", etc. are provided by the server via
`field.label` and rendered as-is by `snakeToPrettyLabel`. These require
a server-side change to use sentence case.
All corresponding story and test assertions updated to match.
> 🤖 Generated by Coder Agents on behalf of @tracyjohnsonux
This commit is contained in:
@@ -153,7 +153,7 @@ export const ForcedByDeployment: Story = {
|
||||
export const DesktopSetting: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await canvas.findByText("Virtual Desktop");
|
||||
await canvas.findByText("Virtual desktop");
|
||||
await canvas.findByText(
|
||||
/Allow agents to use a virtual, graphical desktop within workspaces./i,
|
||||
);
|
||||
|
||||
@@ -55,7 +55,7 @@ export const InvisibleUnicodeWarningUserPrompt: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await canvas.findByText("Personal Instructions");
|
||||
await canvas.findByText("Personal instructions");
|
||||
const alert = await canvas.findByText(/invisible Unicode/);
|
||||
expect(alert).toBeInTheDocument();
|
||||
expect(alert.textContent).toContain("2");
|
||||
@@ -128,7 +128,7 @@ export const RendersChatLayoutSection: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
expect(await canvas.findByText("Chat Layout")).toBeInTheDocument();
|
||||
expect(await canvas.findByText("Chat layout")).toBeInTheDocument();
|
||||
expect(
|
||||
await canvas.findByRole("switch", { name: "Full-width chat" }),
|
||||
).toBeInTheDocument();
|
||||
@@ -160,7 +160,7 @@ export const TogglesSendShortcut: Story = {
|
||||
name: "Require Cmd/Ctrl+Enter to send messages",
|
||||
});
|
||||
|
||||
expect(await canvas.findByText("Keyboard Shortcuts")).toBeInTheDocument();
|
||||
expect(await canvas.findByText("Keyboard shortcuts")).toBeInTheDocument();
|
||||
expect(toggle).not.toBeChecked();
|
||||
|
||||
await userEvent.click(toggle);
|
||||
@@ -177,9 +177,9 @@ export const RendersAgentDisplayModeSettings: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
expect(await canvas.findByText("Thinking Display")).toBeVisible();
|
||||
expect(await canvas.findByText("Shell Output Display")).toBeVisible();
|
||||
expect(await canvas.findByText("Code Diff Display")).toBeVisible();
|
||||
expect(await canvas.findByText("Thinking display")).toBeVisible();
|
||||
expect(await canvas.findByText("Shell output display")).toBeVisible();
|
||||
expect(await canvas.findByText("Code diff display")).toBeVisible();
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ export const InvisibleUnicodeWarningSystemPrompt: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await canvas.findByText("System Instructions");
|
||||
await canvas.findByText("System instructions");
|
||||
const alert = await canvas.findByText(/invisible Unicode/);
|
||||
expect(alert).toBeInTheDocument();
|
||||
expect(alert.textContent).toContain("4");
|
||||
@@ -138,7 +138,7 @@ export const NoWarningForCleanPrompt: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await canvas.findByText("System Instructions");
|
||||
await canvas.findByText("System instructions");
|
||||
await canvas.findByDisplayValue("You are a helpful coding assistant.");
|
||||
expect(canvas.queryByText(/invisible Unicode/)).toBeNull();
|
||||
},
|
||||
|
||||
@@ -46,7 +46,7 @@ export const Default: Story = {};
|
||||
export const DefaultAutostopDefault: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await canvas.findByText("Workspace Autostop Fallback");
|
||||
await canvas.findByText("Workspace autostop fallback");
|
||||
await canvas.findByText(
|
||||
/Set a default autostop for agent-created workspaces/i,
|
||||
);
|
||||
@@ -55,7 +55,7 @@ export const DefaultAutostopDefault: Story = {
|
||||
name: "Enable default autostop",
|
||||
});
|
||||
expect(toggle).not.toBeChecked();
|
||||
expect(canvas.queryByLabelText("Autostop Fallback")).toBeNull();
|
||||
expect(canvas.queryByLabelText("Autostop fallback")).toBeNull();
|
||||
},
|
||||
};
|
||||
|
||||
@@ -70,7 +70,7 @@ export const DefaultAutostopCustomValue: Story = {
|
||||
});
|
||||
expect(toggle).toBeChecked();
|
||||
|
||||
const durationInput = await canvas.findByLabelText("Autostop Fallback");
|
||||
const durationInput = await canvas.findByLabelText("Autostop fallback");
|
||||
expect(durationInput).toHaveValue("2");
|
||||
},
|
||||
};
|
||||
@@ -90,7 +90,7 @@ export const DefaultAutostopSave: Story = {
|
||||
);
|
||||
});
|
||||
|
||||
const durationInput = await canvas.findByLabelText("Autostop Fallback");
|
||||
const durationInput = await canvas.findByLabelText("Autostop fallback");
|
||||
expect(durationInput).toHaveValue("1");
|
||||
|
||||
await userEvent.clear(durationInput);
|
||||
@@ -126,7 +126,7 @@ export const DefaultAutostopExceedsMax: Story = {
|
||||
});
|
||||
await userEvent.click(toggle);
|
||||
|
||||
const durationInput = await canvas.findByLabelText("Autostop Fallback");
|
||||
const durationInput = await canvas.findByLabelText("Autostop fallback");
|
||||
const ttlForm = durationInput.closest("form");
|
||||
if (!(ttlForm instanceof HTMLFormElement)) {
|
||||
throw new Error(
|
||||
@@ -180,7 +180,7 @@ export const DefaultAutostopSaveDisabled: Story = {
|
||||
});
|
||||
expect(toggle).toBeChecked();
|
||||
|
||||
const durationInput = await canvas.findByLabelText("Autostop Fallback");
|
||||
const durationInput = await canvas.findByLabelText("Autostop fallback");
|
||||
expect(durationInput).toHaveValue("2");
|
||||
|
||||
const ttlForm = durationInput.closest("form");
|
||||
@@ -229,7 +229,7 @@ export const DefaultAutostopToggleOffFailure: Story = {
|
||||
});
|
||||
expect(toggle).toBeChecked();
|
||||
|
||||
const durationInput = await canvas.findByLabelText("Autostop Fallback");
|
||||
const durationInput = await canvas.findByLabelText("Autostop fallback");
|
||||
expect(durationInput).toHaveValue("2");
|
||||
|
||||
await userEvent.click(toggle);
|
||||
@@ -634,7 +634,7 @@ export const RetentionBelowMin: Story = {
|
||||
export const DebugRetentionLoadedDefault: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await canvas.findByText("Chat Debug Data Retention");
|
||||
await canvas.findByText("Chat debug data retention");
|
||||
await canvas.findByText(/debug runs and debug steps/i);
|
||||
await canvas.findByText(/does not control chat message retention/i);
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ const AgentSettingsMCPServersPage: FC = () => {
|
||||
return (
|
||||
<RequirePermission isFeatureVisible={permissions.editDeploymentConfig}>
|
||||
<MCPServerAdminPanel
|
||||
sectionLabel="MCP Servers"
|
||||
sectionLabel="MCP servers"
|
||||
sectionDescription="Configure external MCP servers that provide additional tools for Coder Agents."
|
||||
serversData={serversQuery.data}
|
||||
isLoadingServers={serversQuery.isLoading}
|
||||
|
||||
@@ -11,7 +11,7 @@ const AgentSettingsPage: FC = () => {
|
||||
const sidebarView = sidebarViewFromPath(location.pathname);
|
||||
const mobileBack = section
|
||||
? sidebarView.panel === "settings-admin"
|
||||
? { to: "/agents/settings/admin", label: "Manage Agents" }
|
||||
? { to: "/agents/settings/admin", label: "Manage agents" }
|
||||
: { to: "/agents/settings", label: "Settings" }
|
||||
: undefined;
|
||||
|
||||
|
||||
@@ -224,7 +224,7 @@ export const AgentSettingsPersonalSkillsPageView: FC<
|
||||
return (
|
||||
<div className="flex flex-col gap-8">
|
||||
<SectionHeader
|
||||
label="Personal Skills"
|
||||
label="Personal skills"
|
||||
description="Reusable instructions your agents can pick when they need specialized guidance. Personal skills hold a single SKILL.md file. For richer skills with supporting files, add them to your repo under `.agents/skills/` or load them from a workspace."
|
||||
action={addSkillAction}
|
||||
/>
|
||||
|
||||
@@ -726,7 +726,7 @@ export const EmptyStateZoom200Desktop: Story = {
|
||||
});
|
||||
|
||||
await expect(canvas.getByRole("link", { name: "Settings" })).toBeVisible();
|
||||
await expect(canvas.getByRole("link", { name: "New Agent" })).toBeVisible();
|
||||
await expect(canvas.getByRole("link", { name: "New chat" })).toBeVisible();
|
||||
await expect(
|
||||
canvas.getByRole("button", { name: "Collapse sidebar" }),
|
||||
).toBeVisible();
|
||||
@@ -1014,7 +1014,7 @@ export const OpensSettingsForNonAdmins: Story = {
|
||||
});
|
||||
|
||||
expect(
|
||||
screen.queryByRole("link", { name: "Manage Agents" }),
|
||||
screen.queryByRole("link", { name: "Manage agents" }),
|
||||
).not.toBeInTheDocument();
|
||||
},
|
||||
};
|
||||
@@ -1032,7 +1032,7 @@ export const OpensAdminSubPanelOnMobile: Story = {
|
||||
},
|
||||
play: async () => {
|
||||
await userEvent.click(
|
||||
await screen.findByRole("link", { name: "Manage Agents" }),
|
||||
await screen.findByRole("link", { name: "Manage agents" }),
|
||||
);
|
||||
|
||||
await expect(
|
||||
@@ -1059,7 +1059,7 @@ export const SettingsViewResets: Story = {
|
||||
});
|
||||
|
||||
// Navigate to the admin panel, then open the Spend section.
|
||||
await userEvent.click(screen.getByRole("link", { name: "Manage Agents" }));
|
||||
await userEvent.click(screen.getByRole("link", { name: "Manage agents" }));
|
||||
await userEvent.click(await screen.findByRole("link", { name: "Spend" }));
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
@@ -1071,11 +1071,11 @@ export const SettingsViewResets: Story = {
|
||||
|
||||
// Step back to the top-level settings panel, then back to conversations.
|
||||
const backToSettingsButton = await screen.findByRole("link", {
|
||||
name: "Back to Settings",
|
||||
name: "Back to settings",
|
||||
});
|
||||
await userEvent.click(backToSettingsButton);
|
||||
const backToAgentsButton = await screen.findByRole("link", {
|
||||
name: "Back to Agents",
|
||||
name: "Back to agents",
|
||||
});
|
||||
await userEvent.click(backToAgentsButton);
|
||||
|
||||
|
||||
@@ -476,7 +476,7 @@ export const AgentCreateForm: FC<AgentCreateFormProps> = ({
|
||||
severity="info"
|
||||
actions={
|
||||
<Button asChild size="sm">
|
||||
<Link to="/agents/analytics">View Usage</Link>
|
||||
<Link to="/agents/analytics">View usage</Link>
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -112,7 +112,7 @@ export const AutoArchiveSettings: FC<AutoArchiveSettingsProps> = ({
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="m-0 text-sm font-semibold text-content-primary">
|
||||
Auto-Archive Inactive Conversations
|
||||
Auto-archive inactive conversations
|
||||
</h3>
|
||||
</div>
|
||||
<Switch
|
||||
|
||||
@@ -76,7 +76,7 @@ export const UsageLimitExceeded: Story = {
|
||||
|
||||
/**
|
||||
* Provider quota errors use the standard ChatStatusCallout instead of the
|
||||
* "View Usage" CTA (which links to Coder's analytics, not the provider's
|
||||
* "View usage" CTA (which links to Coder's analytics, not the provider's
|
||||
* billing page).
|
||||
*/
|
||||
export const ProviderQuotaExceeded: Story = {
|
||||
@@ -97,7 +97,6 @@ export const ProviderQuotaExceeded: Story = {
|
||||
expect(
|
||||
canvas.getByText(/usage quota for openai has been exceeded/i),
|
||||
).toBeVisible();
|
||||
// The "View Usage" link must NOT appear for provider-originated quota errors.
|
||||
expect(
|
||||
canvas.queryByRole("link", { name: /view usage/i }),
|
||||
).not.toBeInTheDocument();
|
||||
|
||||
@@ -98,7 +98,7 @@ export const LiveStreamTailContent = ({
|
||||
severity="info"
|
||||
actions={
|
||||
<Button asChild size="sm">
|
||||
<Link to="/agents/analytics">View Usage</Link>
|
||||
<Link to="/agents/analytics">View usage</Link>
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -174,7 +174,7 @@ export const ChatCostSummaryView: FC<ChatCostSummaryViewProps> = ({
|
||||
</div>
|
||||
<div className="rounded-lg border border-border-default bg-surface-secondary p-4">
|
||||
<p className="text-xs font-medium uppercase tracking-wide text-content-secondary">
|
||||
Cache Read
|
||||
Cache read
|
||||
</p>
|
||||
<p className="mt-1 text-2xl font-semibold text-content-primary">
|
||||
{formatTokenCount(summary.total_cache_read_tokens)}
|
||||
@@ -182,7 +182,7 @@ export const ChatCostSummaryView: FC<ChatCostSummaryViewProps> = ({
|
||||
</div>
|
||||
<div className="rounded-lg border border-border-default bg-surface-secondary p-4">
|
||||
<p className="text-xs font-medium uppercase tracking-wide text-content-secondary">
|
||||
Cache Write
|
||||
Cache write
|
||||
</p>
|
||||
<p className="mt-1 text-2xl font-semibold text-content-primary">
|
||||
{formatTokenCount(summary.total_cache_creation_tokens)}
|
||||
@@ -206,7 +206,7 @@ export const ChatCostSummaryView: FC<ChatCostSummaryViewProps> = ({
|
||||
<div className="flex flex-col gap-2 md:flex-row md:items-end md:justify-between">
|
||||
<div>
|
||||
<p className="text-xs font-medium uppercase tracking-wide text-content-secondary">
|
||||
{usageLimitPeriodLabel} Spend Limit
|
||||
{usageLimitPeriodLabel} spend limit
|
||||
</p>
|
||||
{usageLimitCurrentPeriod && (
|
||||
<p className="mt-1 text-sm text-content-secondary">
|
||||
@@ -288,8 +288,8 @@ export const ChatCostSummaryView: FC<ChatCostSummaryViewProps> = ({
|
||||
<TableHead className="text-right">Messages</TableHead>
|
||||
<TableHead className="text-right">Input</TableHead>
|
||||
<TableHead className="text-right">Output</TableHead>
|
||||
<TableHead className="text-right">Cache Read</TableHead>
|
||||
<TableHead className="text-right">Cache Write</TableHead>
|
||||
<TableHead className="text-right">Cache read</TableHead>
|
||||
<TableHead className="text-right">Cache write</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -344,8 +344,8 @@ export const ChatCostSummaryView: FC<ChatCostSummaryViewProps> = ({
|
||||
<TableHead className="text-right">Messages</TableHead>
|
||||
<TableHead className="text-right">Input</TableHead>
|
||||
<TableHead className="text-right">Output</TableHead>
|
||||
<TableHead className="text-right">Cache Read</TableHead>
|
||||
<TableHead className="text-right">Cache Write</TableHead>
|
||||
<TableHead className="text-right">Cache read</TableHead>
|
||||
<TableHead className="text-right">Cache write</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
|
||||
@@ -8,7 +8,7 @@ export const ChatFullWidthSettings: FC = () => {
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<h3 className="m-0 text-sm font-semibold text-content-primary">
|
||||
Chat Layout
|
||||
Chat layout
|
||||
</h3>
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<p className="m-0 flex-1 text-xs text-content-secondary">
|
||||
|
||||
+14
-15
@@ -965,8 +965,7 @@ export const SubmitModelConfigExplicitly: Story = {
|
||||
await body.findByLabelText(/Max output tokens/i),
|
||||
"32000",
|
||||
);
|
||||
// Reasoning Effort is a provider option under "Provider Configuration".
|
||||
await expandSection(body, "Provider Configuration");
|
||||
await expandSection(body, "Provider configuration");
|
||||
const effortGroup = await body.findByRole("radiogroup", {
|
||||
name: "Reasoning Effort",
|
||||
});
|
||||
@@ -1192,7 +1191,7 @@ const ensureCostTrackingOpen = async (body: ReturnType<typeof within>) => {
|
||||
if (body.queryByLabelText(/^Input$/i)) {
|
||||
return;
|
||||
}
|
||||
await expandSection(body, "Cost Tracking");
|
||||
await expandSection(body, "Cost tracking");
|
||||
await body.findByLabelText(/^Input$/i);
|
||||
};
|
||||
|
||||
@@ -1271,7 +1270,7 @@ const ensureProviderConfigurationOpen = async (
|
||||
if (body.queryByLabelText(/Max Completion Tokens/i)) {
|
||||
return;
|
||||
}
|
||||
await expandSection(body, "Provider Configuration");
|
||||
await expandSection(body, "Provider configuration");
|
||||
await body.findByLabelText(/Max Completion Tokens/i);
|
||||
};
|
||||
|
||||
@@ -1321,7 +1320,7 @@ export const OpenAIKnownModelHappyPath: Story = {
|
||||
);
|
||||
await expect(body.getByLabelText(/Context limit/i)).toHaveValue("1050000");
|
||||
|
||||
await expandSection(body, "Provider Configuration");
|
||||
await expandSection(body, "Provider configuration");
|
||||
await expect(
|
||||
await body.findByLabelText(/Max Completion Tokens/i),
|
||||
).toHaveValue("128000");
|
||||
@@ -1384,7 +1383,7 @@ export const AnthropicKnownModelHappyPath: Story = {
|
||||
"128000",
|
||||
);
|
||||
|
||||
await expandSection(body, "Provider Configuration");
|
||||
await expandSection(body, "Provider configuration");
|
||||
const sendReasoningGroup = await body.findByRole("radiogroup", {
|
||||
name: "Send Reasoning",
|
||||
});
|
||||
@@ -1409,7 +1408,7 @@ export const AnthropicHaikuKnownModelUsesThinkingBudgetNotEffort: Story = {
|
||||
await openAddModelForm(body, "Anthropic");
|
||||
await selectKnownModel(body, "claude-haiku-4-5");
|
||||
|
||||
await expandSection(body, "Provider Configuration");
|
||||
await expandSection(body, "Provider configuration");
|
||||
|
||||
// Reasoning Effort should remain empty because Haiku 4.5 uses the
|
||||
// thinking budget path instead of Anthropic adaptive thinking.
|
||||
@@ -1981,7 +1980,7 @@ export const ModelFormOpenAI: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const body = within(canvasElement.ownerDocument.body);
|
||||
await openAddModelForm(body, "OpenAI");
|
||||
await expandSection(body, "Provider Configuration");
|
||||
await expandSection(body, "Provider configuration");
|
||||
await expect(
|
||||
await body.findByLabelText(/Reasoning Effort/i),
|
||||
).toBeInTheDocument();
|
||||
@@ -1996,7 +1995,7 @@ export const ModelFormAnthropic: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const body = within(canvasElement.ownerDocument.body);
|
||||
await openAddModelForm(body, "Anthropic");
|
||||
await expandSection(body, "Provider Configuration");
|
||||
await expandSection(body, "Provider configuration");
|
||||
await expect(
|
||||
await body.findByLabelText(/Send Reasoning/i),
|
||||
).toBeInTheDocument();
|
||||
@@ -2011,7 +2010,7 @@ export const ModelFormGoogle: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const body = within(canvasElement.ownerDocument.body);
|
||||
await openAddModelForm(body, "Google");
|
||||
await expandSection(body, "Provider Configuration");
|
||||
await expandSection(body, "Provider configuration");
|
||||
await expect(
|
||||
await body.findByLabelText(/Thinking Config Thinking Budget/i),
|
||||
).toBeInTheDocument();
|
||||
@@ -2026,7 +2025,7 @@ export const ModelFormOpenAICompat: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const body = within(canvasElement.ownerDocument.body);
|
||||
await openAddModelForm(body, "OpenAI-compatible");
|
||||
await expandSection(body, "Provider Configuration");
|
||||
await expandSection(body, "Provider configuration");
|
||||
await expect(
|
||||
await body.findByLabelText(/Reasoning Effort/i),
|
||||
).toBeInTheDocument();
|
||||
@@ -2038,7 +2037,7 @@ export const ModelFormOpenRouter: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const body = within(canvasElement.ownerDocument.body);
|
||||
await openAddModelForm(body, "OpenRouter");
|
||||
await expandSection(body, "Provider Configuration");
|
||||
await expandSection(body, "Provider configuration");
|
||||
await expect(
|
||||
await body.findByLabelText(/Reasoning Enabled/i),
|
||||
).toBeInTheDocument();
|
||||
@@ -2053,7 +2052,7 @@ export const ModelFormVercel: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const body = within(canvasElement.ownerDocument.body);
|
||||
await openAddModelForm(body, "Vercel AI Gateway");
|
||||
await expandSection(body, "Provider Configuration");
|
||||
await expandSection(body, "Provider configuration");
|
||||
await expect(
|
||||
await body.findByLabelText(/Reasoning Enabled/i),
|
||||
).toBeInTheDocument();
|
||||
@@ -2068,7 +2067,7 @@ export const ModelFormAzure: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const body = within(canvasElement.ownerDocument.body);
|
||||
await openAddModelForm(body, "Azure OpenAI");
|
||||
await expandSection(body, "Provider Configuration");
|
||||
await expandSection(body, "Provider configuration");
|
||||
// Azure aliases to OpenAI fields.
|
||||
await expect(
|
||||
await body.findByLabelText(/Reasoning Effort/i),
|
||||
@@ -2084,7 +2083,7 @@ export const ModelFormBedrock: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const body = within(canvasElement.ownerDocument.body);
|
||||
await openAddModelForm(body, "AWS Bedrock");
|
||||
await expandSection(body, "Provider Configuration");
|
||||
await expandSection(body, "Provider configuration");
|
||||
// Bedrock aliases to Anthropic fields.
|
||||
await expect(
|
||||
await body.findByLabelText(/Send Reasoning/i),
|
||||
|
||||
@@ -49,8 +49,8 @@ const unsetSelectValue = "__unset__";
|
||||
const shortLabelOverrides: Record<string, string> = {
|
||||
"cost.input_price_per_million_tokens": "Input",
|
||||
"cost.output_price_per_million_tokens": "Output",
|
||||
"cost.cache_read_price_per_million_tokens": "Cache Read",
|
||||
"cost.cache_write_price_per_million_tokens": "Cache Write",
|
||||
"cost.cache_read_price_per_million_tokens": "Cache read",
|
||||
"cost.cache_write_price_per_million_tokens": "Cache write",
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -99,8 +99,8 @@ function snakeToPrettyLabel(field: FieldSchema): string {
|
||||
if (shortLabelOverrides[field.json_name]) {
|
||||
return shortLabelOverrides[field.json_name];
|
||||
}
|
||||
return field.json_name
|
||||
.split(/[._]/)
|
||||
const words = field.json_name.split(/[._]/);
|
||||
return words
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
@@ -129,10 +129,10 @@ export const ModelForm: FC<ModelFormProps> = ({
|
||||
selectedProviderState.providerConfig.allow_user_api_key),
|
||||
);
|
||||
const formTitle = isEditing
|
||||
? "Edit Model"
|
||||
? "Edit model"
|
||||
: isDuplicating
|
||||
? "Duplicate Model"
|
||||
: "Add Model";
|
||||
? "Duplicate model"
|
||||
: "Add model";
|
||||
const formDescription = isDuplicating
|
||||
? "Review the copied settings, then save to create a new model."
|
||||
: undefined;
|
||||
@@ -403,7 +403,7 @@ export const ModelForm: FC<ModelFormProps> = ({
|
||||
autoComplete="off"
|
||||
>
|
||||
<div className="space-y-6">
|
||||
{/* Model ID + Context Limit + Pricing */}
|
||||
{/* Model ID + Context limit + Pricing */}
|
||||
<div className="space-y-4">
|
||||
<div className="grid items-start gap-4 sm:grid-cols-2">
|
||||
{" "}
|
||||
@@ -419,7 +419,7 @@ export const ModelForm: FC<ModelFormProps> = ({
|
||||
htmlFor={contextLimitField.id}
|
||||
className="inline-flex items-center gap-1 text-sm font-medium text-content-primary"
|
||||
>
|
||||
Context Limit{" "}
|
||||
Context limit{" "}
|
||||
<span className="text-xs font-bold text-content-destructive">
|
||||
*
|
||||
</span>
|
||||
@@ -464,7 +464,7 @@ export const ModelForm: FC<ModelFormProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Usage Tracking */}
|
||||
{/* Cost tracking */}
|
||||
<div className="border-0 border-t border-solid border-border pt-4">
|
||||
<button
|
||||
type="button"
|
||||
@@ -473,7 +473,7 @@ export const ModelForm: FC<ModelFormProps> = ({
|
||||
>
|
||||
<div>
|
||||
<h3 className="m-0 text-sm font-medium text-content-primary">
|
||||
Cost Tracking{" "}
|
||||
Cost tracking
|
||||
</h3>
|
||||
<p className="m-0 text-xs text-content-secondary">
|
||||
Set per-token pricing so Coder can track costs and enforce
|
||||
@@ -498,7 +498,7 @@ export const ModelForm: FC<ModelFormProps> = ({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Provider Configuration */}
|
||||
{/* Provider configuration */}
|
||||
<div className="border-0 border-t border-solid border-border pt-4">
|
||||
<button
|
||||
type="button"
|
||||
@@ -507,7 +507,7 @@ export const ModelForm: FC<ModelFormProps> = ({
|
||||
>
|
||||
<div>
|
||||
<h3 className="m-0 text-sm font-medium text-content-primary">
|
||||
Provider Configuration
|
||||
Provider configuration
|
||||
</h3>
|
||||
<p className="m-0 text-xs text-content-secondary">
|
||||
Tune provider-specific behavior like reasoning, tool calling,
|
||||
@@ -567,7 +567,7 @@ export const ModelForm: FC<ModelFormProps> = ({
|
||||
htmlFor={compressionThresholdField.id}
|
||||
className="inline-flex items-center gap-1 text-[13px] font-medium text-content-primary"
|
||||
>
|
||||
Compression Threshold
|
||||
Compression threshold
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<InfoIcon className="size-3 text-content-secondary" />
|
||||
|
||||
@@ -459,7 +459,7 @@ export const ModelIdentifierField = ({
|
||||
htmlFor={modelField.id}
|
||||
className="inline-flex items-center gap-1 text-sm font-medium text-content-primary"
|
||||
>
|
||||
Model Identifier{" "}
|
||||
Model identifier{" "}
|
||||
<span className="text-xs font-bold text-content-destructive">*</span>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
|
||||
+20
-20
@@ -213,23 +213,23 @@ export const OpensDuplicateFormWithoutCreating: Story = {
|
||||
}),
|
||||
);
|
||||
|
||||
await expect(canvas.findByText("Duplicate Model")).resolves.toBeVisible();
|
||||
await expect(canvas.findByText("Duplicate model")).resolves.toBeVisible();
|
||||
expect(args.onCreateModel).not.toHaveBeenCalled();
|
||||
expect(args.onUpdateModel).not.toHaveBeenCalled();
|
||||
expect(canvas.getByDisplayValue("GPT-4.1 Default")).toBeVisible();
|
||||
expect(canvas.getByLabelText(/Model Identifier/)).toHaveValue(
|
||||
expect(canvas.getByLabelText(/Model identifier/)).toHaveValue(
|
||||
"gpt-4.1-default",
|
||||
);
|
||||
expect(canvas.getByLabelText(/Context Limit/)).toHaveValue("200000");
|
||||
expect(canvas.getByLabelText(/Context limit/)).toHaveValue("200000");
|
||||
const enabledSwitch = canvas.getByRole("switch", { name: "Enabled" });
|
||||
expect(enabledSwitch).toBeChecked();
|
||||
expect(enabledSwitch).toBeEnabled();
|
||||
|
||||
await userEvent.click(canvas.getByRole("button", { name: /Advanced/ }));
|
||||
expect(canvas.getByLabelText(/Compression Threshold/)).toHaveValue("65");
|
||||
expect(canvas.getByLabelText(/Compression threshold/)).toHaveValue("65");
|
||||
|
||||
await userEvent.click(
|
||||
canvas.getByRole("button", { name: /Provider Configuration/ }),
|
||||
canvas.getByRole("button", { name: /Provider configuration/ }),
|
||||
);
|
||||
expect(canvas.getByLabelText("Max Tool Calls")).toHaveValue("4");
|
||||
},
|
||||
@@ -246,14 +246,14 @@ export const AbandonsDuplicateWithoutSaving: Story = {
|
||||
const copyButtonName = "Duplicate model: GPT-4.1 Default";
|
||||
|
||||
await userEvent.click(canvas.getByRole("button", { name: copyButtonName }));
|
||||
await expect(canvas.findByText("Duplicate Model")).resolves.toBeVisible();
|
||||
await expect(canvas.findByText("Duplicate model")).resolves.toBeVisible();
|
||||
await userEvent.click(canvas.getByRole("button", { name: "Cancel" }));
|
||||
await expect(
|
||||
canvas.findByRole("button", { name: copyButtonName }),
|
||||
).resolves.toBeVisible();
|
||||
|
||||
await userEvent.click(canvas.getByRole("button", { name: copyButtonName }));
|
||||
await expect(canvas.findByText("Duplicate Model")).resolves.toBeVisible();
|
||||
await expect(canvas.findByText("Duplicate model")).resolves.toBeVisible();
|
||||
await userEvent.click(canvas.getByRole("button", { name: "Back" }));
|
||||
await expect(
|
||||
canvas.findByRole("button", { name: copyButtonName }),
|
||||
@@ -276,9 +276,9 @@ export const SavesDuplicateAsCreateRequest: Story = {
|
||||
name: "Duplicate model: GPT-4.1 Default",
|
||||
}),
|
||||
);
|
||||
await expect(canvas.findByText("Duplicate Model")).resolves.toBeVisible();
|
||||
await expect(canvas.findByText("Duplicate model")).resolves.toBeVisible();
|
||||
|
||||
const modelInput = canvas.getByLabelText(/Model Identifier/);
|
||||
const modelInput = canvas.getByLabelText(/Model identifier/);
|
||||
await userEvent.clear(modelInput);
|
||||
await userEvent.type(modelInput, "gpt-4.1-copy");
|
||||
const displayNameInput = canvas.getByDisplayValue("GPT-4.1 Default");
|
||||
@@ -329,14 +329,14 @@ export const SavesNonDefaultDuplicateWithEditableEnabled: Story = {
|
||||
await userEvent.click(
|
||||
canvas.getByRole("button", { name: "Duplicate model: GPT-4.1" }),
|
||||
);
|
||||
await expect(canvas.findByText("Duplicate Model")).resolves.toBeVisible();
|
||||
await expect(canvas.findByText("Duplicate model")).resolves.toBeVisible();
|
||||
|
||||
const enabledSwitch = canvas.getByRole("switch", { name: "Enabled" });
|
||||
expect(enabledSwitch).toBeChecked();
|
||||
expect(enabledSwitch).toBeEnabled();
|
||||
await userEvent.click(enabledSwitch);
|
||||
|
||||
const modelInput = canvas.getByLabelText(/Model Identifier/);
|
||||
const modelInput = canvas.getByLabelText(/Model identifier/);
|
||||
await userEvent.clear(modelInput);
|
||||
await userEvent.type(modelInput, "gpt-4.1-copy");
|
||||
await userEvent.click(
|
||||
@@ -373,14 +373,14 @@ export const SavesDisabledDuplicateWithEditableEnabled: Story = {
|
||||
name: "Duplicate model: GPT-4.1 Disabled",
|
||||
}),
|
||||
);
|
||||
await expect(canvas.findByText("Duplicate Model")).resolves.toBeVisible();
|
||||
await expect(canvas.findByText("Duplicate model")).resolves.toBeVisible();
|
||||
|
||||
const enabledSwitch = canvas.getByRole("switch", { name: "Enabled" });
|
||||
expect(enabledSwitch).not.toBeChecked();
|
||||
expect(enabledSwitch).toBeEnabled();
|
||||
await userEvent.click(enabledSwitch);
|
||||
|
||||
const modelInput = canvas.getByLabelText(/Model Identifier/);
|
||||
const modelInput = canvas.getByLabelText(/Model identifier/);
|
||||
await userEvent.clear(modelInput);
|
||||
await userEvent.type(modelInput, "gpt-4.1-disabled-copy");
|
||||
await userEvent.click(
|
||||
@@ -417,7 +417,7 @@ export const DisablesDuplicateWhenProviderCannotManageModels: Story = {
|
||||
|
||||
expect(duplicateButton).toHaveAttribute("aria-disabled", "true");
|
||||
await userEvent.click(duplicateButton);
|
||||
expect(canvas.queryByText("Duplicate Model")).not.toBeInTheDocument();
|
||||
expect(canvas.queryByText("Duplicate model")).not.toBeInTheDocument();
|
||||
expect(args.onCreateModel).not.toHaveBeenCalled();
|
||||
},
|
||||
};
|
||||
@@ -442,13 +442,13 @@ export const RowActionsDoNotOpenRowBody: Story = {
|
||||
"model-config-id",
|
||||
{ is_default: true },
|
||||
]);
|
||||
expect(canvas.queryByText("Edit Model")).not.toBeInTheDocument();
|
||||
expect(canvas.queryByText("Edit model")).not.toBeInTheDocument();
|
||||
|
||||
await userEvent.click(
|
||||
canvas.getByRole("button", { name: "Default model: GPT-4o" }),
|
||||
);
|
||||
expect(args.onUpdateModel).toHaveBeenCalledTimes(1);
|
||||
expect(canvas.queryByText("Edit Model")).not.toBeInTheDocument();
|
||||
expect(canvas.queryByText("Edit model")).not.toBeInTheDocument();
|
||||
|
||||
const disabledStarButton = canvas.getByRole("button", {
|
||||
name: "Set as default model: GPT-4.1 Disabled",
|
||||
@@ -456,20 +456,20 @@ export const RowActionsDoNotOpenRowBody: Story = {
|
||||
expect(disabledStarButton).toHaveAttribute("aria-disabled", "true");
|
||||
await userEvent.click(disabledStarButton);
|
||||
expect(args.onUpdateModel).toHaveBeenCalledTimes(1);
|
||||
expect(canvas.queryByText("Edit Model")).not.toBeInTheDocument();
|
||||
expect(canvas.queryByText("Edit model")).not.toBeInTheDocument();
|
||||
|
||||
await userEvent.click(
|
||||
canvas.getByRole("button", { name: "Duplicate model: GPT-4.1" }),
|
||||
);
|
||||
await expect(canvas.findByText("Duplicate Model")).resolves.toBeVisible();
|
||||
await expect(canvas.findByText("Duplicate model")).resolves.toBeVisible();
|
||||
expect(args.onCreateModel).not.toHaveBeenCalled();
|
||||
expect(args.onUpdateModel).toHaveBeenCalledTimes(1);
|
||||
expect(canvas.queryByText("Edit Model")).not.toBeInTheDocument();
|
||||
expect(canvas.queryByText("Edit model")).not.toBeInTheDocument();
|
||||
|
||||
await userEvent.click(canvas.getByRole("button", { name: "Back" }));
|
||||
await userEvent.click(
|
||||
await canvas.findByRole("button", { name: "Edit model: GPT-4.1" }),
|
||||
);
|
||||
await expect(canvas.findByText("Edit Model")).resolves.toBeVisible();
|
||||
await expect(canvas.findByText("Edit model")).resolves.toBeVisible();
|
||||
},
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@ export const ChatSendShortcutSettings: FC = () => {
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<h3 className="m-0 text-sm font-semibold text-content-primary">
|
||||
Keyboard Shortcuts
|
||||
Keyboard shortcuts
|
||||
</h3>
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<p
|
||||
|
||||
@@ -2117,7 +2117,7 @@ export const SettingsUserAgentsNonAdmin: Story = {
|
||||
const agentsLink = canvas.getByRole("link", { name: "Agents" });
|
||||
await expect(agentsLink).toHaveAttribute("aria-current", "page");
|
||||
expect(
|
||||
canvas.queryByRole("link", { name: "Manage Agents" }),
|
||||
canvas.queryByRole("link", { name: "Manage agents" }),
|
||||
).not.toBeInTheDocument();
|
||||
},
|
||||
};
|
||||
@@ -2192,7 +2192,7 @@ export const SettingsUserAgentsAdmin: Story = {
|
||||
const agentsLink = canvas.getByRole("link", { name: "Agents" });
|
||||
await expect(agentsLink).toHaveAttribute("aria-current", "page");
|
||||
expect(
|
||||
canvas.getByRole("link", { name: "Manage Agents" }),
|
||||
canvas.getByRole("link", { name: "Manage agents" }),
|
||||
).toBeInTheDocument();
|
||||
},
|
||||
};
|
||||
@@ -2212,7 +2212,7 @@ export const SettingsAdminAgentsEntryPreserved: Story = {
|
||||
const canvas = within(canvasElement);
|
||||
const agentsLink = canvas.getByRole("link", { name: "Agents" });
|
||||
await expect(agentsLink).toHaveAttribute("aria-current", "page");
|
||||
expect(canvas.getByText("Manage Agents")).toBeInTheDocument();
|
||||
expect(canvas.getByText("Manage agents")).toBeInTheDocument();
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -389,7 +389,7 @@ export const ChatsPanel: FC<ChatsPanelProps> = ({
|
||||
</div>
|
||||
<SettingsNavItem
|
||||
icon={SquarePenIcon}
|
||||
label="New Agent"
|
||||
label="New chat"
|
||||
active={isChatsActive}
|
||||
to={{ pathname: "/agents", search: locationSearch }}
|
||||
onClick={onBeforeNewAgent}
|
||||
|
||||
@@ -45,7 +45,7 @@ export const SettingsPanel: FC<SettingsPanelProps> = ({
|
||||
onCollapse,
|
||||
}) => {
|
||||
const subNavTitle =
|
||||
settingsPanel === "settings-admin" ? "Manage Agents" : "Settings";
|
||||
settingsPanel === "settings-admin" ? "Manage agents" : "Settings";
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -67,8 +67,8 @@ export const SettingsPanel: FC<SettingsPanelProps> = ({
|
||||
size="icon"
|
||||
aria-label={
|
||||
settingsPanel === "settings-admin"
|
||||
? "Back to Settings"
|
||||
: "Back to Agents"
|
||||
? "Back to settings"
|
||||
: "Back to agents"
|
||||
}
|
||||
className="relative z-10 size-7 min-w-0 text-content-secondary hover:text-content-primary"
|
||||
>
|
||||
@@ -76,7 +76,7 @@ export const SettingsPanel: FC<SettingsPanelProps> = ({
|
||||
<Link
|
||||
to="/agents/settings/general"
|
||||
state={location.state}
|
||||
aria-label="Back to Settings"
|
||||
aria-label="Back to settings"
|
||||
>
|
||||
<ArrowLeftIcon />
|
||||
</Link>
|
||||
@@ -122,7 +122,7 @@ export const SettingsPanel: FC<SettingsPanelProps> = ({
|
||||
)}
|
||||
<SettingsNavItem
|
||||
icon={ReceiptTextIcon}
|
||||
label="Personal Skills"
|
||||
label="Personal skills"
|
||||
active={settingsSection === "personal-skills"}
|
||||
to="/agents/settings/personal-skills"
|
||||
state={location.state}
|
||||
@@ -146,7 +146,7 @@ export const SettingsPanel: FC<SettingsPanelProps> = ({
|
||||
{isAdmin && (
|
||||
<SettingsNavItem
|
||||
icon={Settings2Icon}
|
||||
label="Manage Agents"
|
||||
label="Manage agents"
|
||||
active={false}
|
||||
to="/agents/settings/admin"
|
||||
state={location.state}
|
||||
@@ -179,7 +179,7 @@ export const SettingsPanel: FC<SettingsPanelProps> = ({
|
||||
/>
|
||||
<SettingsNavItem
|
||||
icon={ServerIcon}
|
||||
label="MCP Servers"
|
||||
label="MCP servers"
|
||||
active={settingsSection === "mcp-servers"}
|
||||
to="/agents/settings/mcp-servers"
|
||||
state={location.state}
|
||||
|
||||
@@ -114,7 +114,7 @@ export const DebugRetentionSettings: FC<DebugRetentionSettingsProps> = ({
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="m-0 text-sm font-semibold text-content-primary">
|
||||
Chat Debug Data Retention
|
||||
Chat debug data retention
|
||||
</h3>
|
||||
</div>
|
||||
<Switch
|
||||
|
||||
@@ -24,14 +24,14 @@ type AgentDisplayMode = UserPreferenceSettings["code_diff_display_mode"];
|
||||
const thinkingDisplayOptions: DisplayModeOption<ThinkingDisplayMode>[] = [
|
||||
{ value: "auto", label: "Auto" },
|
||||
{ value: "preview", label: "Preview" },
|
||||
{ value: "always_expanded", label: "Always Expanded" },
|
||||
{ value: "always_collapsed", label: "Always Collapsed" },
|
||||
{ value: "always_expanded", label: "Always expanded" },
|
||||
{ value: "always_collapsed", label: "Always collapsed" },
|
||||
];
|
||||
|
||||
const agentDisplayOptions: DisplayModeOption<AgentDisplayMode>[] = [
|
||||
{ value: "auto", label: "Auto" },
|
||||
{ value: "always_expanded", label: "Always Expanded" },
|
||||
{ value: "always_collapsed", label: "Always Collapsed" },
|
||||
{ value: "always_expanded", label: "Always expanded" },
|
||||
{ value: "always_collapsed", label: "Always collapsed" },
|
||||
];
|
||||
|
||||
type DisplayModeSettingsProps<T extends string> = {
|
||||
@@ -101,8 +101,8 @@ const DisplayModeSettings = <T extends string>({
|
||||
export const ThinkingDisplaySettings: FC = () => {
|
||||
return (
|
||||
<DisplayModeSettings
|
||||
title="Thinking Display"
|
||||
description="How thinking blocks should be displayed by default. 'Auto' fully expands during streaming, then auto-collapses when done. 'Preview' auto-expands with a height constraint during streaming. 'Always Expanded' shows full content. 'Always Collapsed' keeps them collapsed."
|
||||
title="Thinking display"
|
||||
description="How thinking blocks should be displayed by default. 'Auto' fully expands during streaming, then auto-collapses when done. 'Preview' auto-expands with a height constraint during streaming. 'Always expanded' shows full content. 'Always collapsed' keeps them collapsed."
|
||||
ariaLabel="Thinking display mode"
|
||||
errorMessage="Failed to save your thinking display preference."
|
||||
defaultValue="auto"
|
||||
@@ -118,8 +118,8 @@ export const ThinkingDisplaySettings: FC = () => {
|
||||
export const ShellToolDisplaySettings: FC = () => {
|
||||
return (
|
||||
<DisplayModeSettings
|
||||
title="Shell Output Display"
|
||||
description="How shell command output should be displayed by default. 'Auto' opens running commands and completed commands with output, then keeps empty output collapsed. 'Always Expanded' opens shell output by default. 'Always Collapsed' keeps it collapsed."
|
||||
title="Shell output display"
|
||||
description="How shell command output should be displayed by default. 'Auto' opens running commands and completed commands with output, then keeps empty output collapsed. 'Always expanded' opens shell output by default. 'Always collapsed' keeps it collapsed."
|
||||
ariaLabel="Shell output display mode"
|
||||
errorMessage="Failed to save your shell output display preference."
|
||||
defaultValue="auto"
|
||||
@@ -135,8 +135,8 @@ export const ShellToolDisplaySettings: FC = () => {
|
||||
export const CodeDiffDisplaySettings: FC = () => {
|
||||
return (
|
||||
<DisplayModeSettings
|
||||
title="Code Diff Display"
|
||||
description="Controls how code edit diffs appear. Auto starts single-file writes collapsed and opens multi-file edits with a height-constrained preview. Always Expanded opens diffs by default; Always Collapsed keeps them collapsed."
|
||||
title="Code diff display"
|
||||
description="Controls how code edit diffs appear. 'Auto' starts single-file writes collapsed and opens multi-file edits with a height-constrained preview. 'Always expanded' opens diffs by default; 'Always collapsed' keeps them collapsed."
|
||||
ariaLabel="Code diff display mode"
|
||||
errorMessage="Failed to save your code diff display preference."
|
||||
defaultValue="auto"
|
||||
|
||||
@@ -49,7 +49,7 @@ export const DefaultLimitSection: FC<DefaultLimitSectionProps> = ({
|
||||
<section className="space-y-4">
|
||||
{!hideHeader && (
|
||||
<SectionHeader
|
||||
label="Default Spend Limit"
|
||||
label="Default spend limit"
|
||||
description="Set a deployment-wide spend cap that applies to all users by default."
|
||||
badge={adminBadge}
|
||||
/>
|
||||
|
||||
@@ -92,7 +92,7 @@ export const GroupLimitsSection: FC<GroupLimitsSectionProps> = ({
|
||||
<section className="space-y-4">
|
||||
{!hideHeader && (
|
||||
<SectionHeader
|
||||
label="Group Limits"
|
||||
label="Group limits"
|
||||
description="Override the default limit for specific groups. When a user belongs to multiple groups, the lowest group limit applies."
|
||||
/>
|
||||
)}
|
||||
@@ -103,7 +103,7 @@ export const GroupLimitsSection: FC<GroupLimitsSectionProps> = ({
|
||||
<TableRow>
|
||||
<TableHead>Group</TableHead>
|
||||
<TableHead>Members</TableHead>
|
||||
<TableHead>Spend Limit</TableHead>
|
||||
<TableHead>Spend limit</TableHead>
|
||||
<TableHead className="w-[160px]">Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
@@ -253,7 +253,7 @@ export const GroupLimitsSection: FC<GroupLimitsSectionProps> = ({
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 space-y-1">
|
||||
<Label htmlFor={groupAmountId}>Spend Limit ($)</Label>
|
||||
<Label htmlFor={groupAmountId}>Spend limit ($)</Label>
|
||||
<Input
|
||||
id={groupAmountId}
|
||||
type="number"
|
||||
|
||||
@@ -84,7 +84,7 @@ export const UserOverridesSection: FC<UserOverridesSectionProps> = ({
|
||||
<section className="space-y-4">
|
||||
{!hideHeader && (
|
||||
<SectionHeader
|
||||
label="Per-User Overrides"
|
||||
label="Per-user overrides"
|
||||
description="Override the deployment default spend limit for specific users. User overrides take highest priority, followed by group limits, then the deployment default."
|
||||
/>
|
||||
)}
|
||||
@@ -94,7 +94,7 @@ export const UserOverridesSection: FC<UserOverridesSectionProps> = ({
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>User</TableHead>
|
||||
<TableHead>Spend Limit</TableHead>
|
||||
<TableHead>Spend limit</TableHead>
|
||||
<TableHead className="w-[160px]">Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
@@ -190,7 +190,7 @@ export const UserOverridesSection: FC<UserOverridesSectionProps> = ({
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 space-y-1">
|
||||
<Label htmlFor={userOverrideAmountId}>Spend Limit ($)</Label>
|
||||
<Label htmlFor={userOverrideAmountId}>Spend limit ($)</Label>
|
||||
<Input
|
||||
id={userOverrideAmountId}
|
||||
type="number"
|
||||
|
||||
@@ -248,7 +248,7 @@ const ServerList: FC<ServerListProps> = ({
|
||||
return (
|
||||
<>
|
||||
<SectionHeader
|
||||
label={sectionLabel ?? "MCP Servers"}
|
||||
label={sectionLabel ?? "MCP servers"}
|
||||
description={
|
||||
sectionDescription ??
|
||||
"Configure external MCP servers that provide additional tools for Coder Agents."
|
||||
@@ -532,7 +532,7 @@ const ServerForm: FC<ServerFormProps> = ({
|
||||
}}
|
||||
placeholder="e.g. Sentry"
|
||||
disabled={isDisabled}
|
||||
aria-label="Display Name"
|
||||
aria-label="Display name"
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Slug" htmlFor={`${formId}-slug`} required>
|
||||
@@ -698,7 +698,7 @@ const ServerForm: FC<ServerFormProps> = ({
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
label="Client Secret"
|
||||
label="Client secret"
|
||||
htmlFor={`${formId}-oauth-secret`}
|
||||
>
|
||||
<Input
|
||||
@@ -773,7 +773,7 @@ const ServerForm: FC<ServerFormProps> = ({
|
||||
{form.values.authType === "api_key" && (
|
||||
<div className="grid items-start gap-4 rounded-lg border border-solid border-border/70 bg-surface-secondary/30 p-4 sm:grid-cols-2">
|
||||
<Field
|
||||
label="Header Name"
|
||||
label="Header name"
|
||||
htmlFor={`${formId}-apikey-header`}
|
||||
>
|
||||
<Input
|
||||
@@ -1048,7 +1048,7 @@ const ServerForm: FC<ServerFormProps> = ({
|
||||
|
||||
<div className="grid items-start gap-4 sm:grid-cols-2">
|
||||
<Field
|
||||
label="Tool Allow List"
|
||||
label="Tool allow list"
|
||||
htmlFor={`${formId}-allow-list`}
|
||||
description="Comma-separated. Empty = all allowed."
|
||||
>
|
||||
@@ -1061,7 +1061,7 @@ const ServerForm: FC<ServerFormProps> = ({
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
label="Tool Deny List"
|
||||
label="Tool deny list"
|
||||
htmlFor={`${formId}-deny-list`}
|
||||
description="Comma-separated names to block."
|
||||
>
|
||||
|
||||
@@ -272,7 +272,7 @@ export const MCPServerPicker: FC<MCPServerPickerProps> = ({
|
||||
<button
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
aria-label="MCP Servers"
|
||||
aria-label="MCP servers"
|
||||
className="group flex h-8 w-full cursor-pointer items-center gap-1.5 border-none bg-transparent px-1 text-xs text-content-secondary shadow-none transition-colors hover:text-content-primary disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
<span>MCP</span>
|
||||
|
||||
@@ -66,7 +66,7 @@ export const PersonalInstructionsSettings: FC<
|
||||
return (
|
||||
<form className="flex flex-col gap-2" onSubmit={form.handleSubmit}>
|
||||
<h3 className="m-0 text-sm font-semibold text-content-primary">
|
||||
Personal Instructions
|
||||
Personal instructions
|
||||
</h3>
|
||||
<p className="m-0 text-xs text-content-secondary">
|
||||
Applied to all your conversations. Only visible to you.
|
||||
|
||||
@@ -107,7 +107,7 @@ export const RetentionPeriodSettings: FC<RetentionPeriodSettingsProps> = ({
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="m-0 text-sm font-semibold text-content-primary">
|
||||
Conversation Retention Period
|
||||
Conversation retention period
|
||||
</h3>
|
||||
</div>
|
||||
<Switch
|
||||
|
||||
@@ -76,7 +76,7 @@ export const SystemInstructionsSettings: FC<
|
||||
<form className="flex flex-col gap-2" onSubmit={form.handleSubmit}>
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="m-0 text-sm font-semibold text-content-primary">
|
||||
System Instructions
|
||||
System instructions
|
||||
</h3>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
|
||||
@@ -78,7 +78,7 @@ export const UsageIndicator: FC = () => {
|
||||
|
||||
sections.push({
|
||||
id: "ai-usage",
|
||||
title: `${periodLabel} Usage`,
|
||||
title: `${periodLabel} usage`,
|
||||
progressLabel: `${periodLabel} spend usage`,
|
||||
percent: getPercent(currentSpend, spendLimit),
|
||||
severity: getSeverity(currentSpend, spendLimit),
|
||||
|
||||
@@ -58,7 +58,7 @@ const parseThresholdDraft = (value: string): number | null => {
|
||||
const ContextCompactionHeader: FC = () => (
|
||||
<div className="flex flex-col gap-2">
|
||||
<h3 className="m-0 text-sm font-semibold text-content-primary">
|
||||
Context Compaction
|
||||
Context compaction
|
||||
</h3>
|
||||
<p className="!mt-0.5 m-0 text-xs text-content-secondary">
|
||||
Control when conversation context is automatically summarized for each
|
||||
|
||||
@@ -78,7 +78,7 @@ export const VirtualDesktopSettings: FC<VirtualDesktopSettingsProps> = ({
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="m-0 text-sm font-semibold text-content-primary">
|
||||
Virtual Desktop
|
||||
Virtual desktop
|
||||
</h3>
|
||||
<Badge size="sm" variant="warning" className="cursor-default">
|
||||
<TriangleAlertIcon className="size-3" />
|
||||
|
||||
@@ -121,7 +121,7 @@ export const WorkspaceAutostopSettings: FC<WorkspaceAutostopSettingsProps> = ({
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="m-0 text-sm font-semibold text-content-primary">
|
||||
Workspace Autostop Fallback
|
||||
Workspace autostop fallback
|
||||
</h3>
|
||||
</div>
|
||||
<Switch
|
||||
@@ -140,7 +140,7 @@ export const WorkspaceAutostopSettings: FC<WorkspaceAutostopSettingsProps> = ({
|
||||
<DurationField
|
||||
valueMs={form.values.workspace_ttl_ms}
|
||||
onChange={handleTTLChange}
|
||||
label="Autostop Fallback"
|
||||
label="Autostop fallback"
|
||||
disabled={isSavingWorkspaceTTL || isWorkspaceTTLLoading}
|
||||
error={Boolean(fieldError)}
|
||||
helperText={fieldError}
|
||||
|
||||
Reference in New Issue
Block a user