fix(ui): accept any routing group name the backend accepts

The create form rejected names with slashes or spaces even though the
proxy stores and routes any non-empty string. Drop the client-only
character pattern and trim the name before the required check so a
whitespace-only name is still refused

Claude-Session: https://claude.ai/code/session_01HkaXiD6gssHnx3kqu1rR8C
This commit is contained in:
ryan-crabbe-berri 2026-09-04 14:06:34 -07:00
parent 300d335255
commit 6f18a4d81e
3 changed files with 24 additions and 7 deletions

View file

@ -19,6 +19,13 @@ const EXPECTED_STORED_PAYLOAD: RoutingGroup = {
const SEEDED_CREATE: RoutingGroup = { group_name: "", models: ["gemini-pro"], routing_strategy: "simple-shuffle" };
const EXPECTED_SLASH_AND_SPACE_PAYLOAD: RoutingGroup = {
group_name: "team a/fast chat",
models: ["gemini-pro"],
routing_strategy: "simple-shuffle",
routing_strategy_args: null,
};
const STORED_GROUP: RoutingGroup = {
group_name: "already-taken",
models: ["gpt-4o", "claude-sonnet"],
@ -211,16 +218,28 @@ describe("RoutingGroupModal", () => {
expect(onSubmit).not.toHaveBeenCalled();
});
it("rejects a name with characters outside the allowed set", async () => {
it("accepts a name with slashes and spaces, since the backend does", async () => {
const user = userEvent.setup();
const { onSubmit } = renderModal({
initialValue: { group_name: "", models: ["gemini-pro"], routing_strategy: "simple-shuffle" },
});
await typeName(user, "bad name");
await typeName(user, "team a/fast chat");
await save(user, "Create Group");
expect(await screen.findByText("Only letters, numbers, dot, underscore, and dash are allowed")).toBeInTheDocument();
expect(onSubmit).toHaveBeenCalledWith(EXPECTED_SLASH_AND_SPACE_PAYLOAD);
});
it("rejects a whitespace-only name as missing", async () => {
const user = userEvent.setup();
const { onSubmit } = renderModal({
initialValue: { group_name: "", models: ["gemini-pro"], routing_strategy: "simple-shuffle" },
});
await typeName(user, " ");
await save(user, "Create Group");
expect(await screen.findByText("Group name is required")).toBeInTheDocument();
expect(onSubmit).not.toHaveBeenCalled();
});

View file

@ -23,7 +23,6 @@ import { Textarea } from "@/components/ui/textarea";
import { useZodForm } from "@/lib/forms/useZodForm";
import {
GROUP_NAME_MAX_LENGTH,
GROUP_NAME_PATTERN,
STRATEGIES_WITH_ARGS,
argsForStrategy,
buildRoutingGroupPayload,
@ -74,10 +73,10 @@ const RoutingGroupModal: React.FC<RoutingGroupModalProps> = ({
const shape = {
group_name: z
.string()
.trim()
.min(1, "Group name is required")
.max(GROUP_NAME_MAX_LENGTH, `Must be ${GROUP_NAME_MAX_LENGTH} characters or fewer`)
.regex(GROUP_NAME_PATTERN, "Only letters, numbers, dot, underscore, and dash are allowed")
.refine((value) => !reservedNames.has(value.trim().toLowerCase()), "A group with this name already exists"),
.refine((value) => !reservedNames.has(value.toLowerCase()), "A group with this name already exists"),
models: z.array(z.string()).min(1, "Select at least one model"),
routing_strategy: z.string().min(1, "Strategy is required"),
routing_strategy_args: z.string(),

View file

@ -2,7 +2,6 @@ import type { RoutingGroup } from "./types";
export const STRATEGIES_WITH_ARGS = new Set<string>(["latency-based-routing", "usage-based-routing"]);
export const GROUP_NAME_PATTERN = /^[A-Za-z0-9._-]+$/;
export const GROUP_NAME_MAX_LENGTH = 64;
export interface RoutingGroupFormValues {