Compare commits

...

1 Commits

Author SHA1 Message Date
Jiang Bohan
a63eae6521 fix(repos): accept scp shorthand in repo URL inputs (MUL-2250)
Backend now validates http/https/ssh/git scheme plus scp-like
`git@host:owner/repo.git` shorthand, but three repo URL inputs were
still `type="url"`. The browser's native URL validation rejected scp
shorthand with "Please enter a URL" before the value could reach the
backend.

- Switch the three inputs to `type="text"` so submission isn't blocked
  client-side (project resources picker, workspace repositories tab,
  create-project repo picker).
- Extend the en/zh placeholders to show a scp shorthand example
  alongside the existing https one.
- Add a repositories-tab test that types `git@github.com:...` and
  asserts the input is text-type, passes native validity, and reaches
  the update mutation.

Co-authored-by: multica-agent <github@multica.ai>
2026-05-15 12:54:38 +08:00
10 changed files with 39 additions and 9 deletions

View File

@@ -90,7 +90,7 @@
"repos_pill_count_other": "{{count}} repos",
"repos_heading": "Attach GitHub repos to this project",
"repos_empty": "No workspace-level repos yet. Paste a URL below to attach one ad-hoc.",
"repos_url_placeholder": "https://github.com/owner/repo",
"repos_url_placeholder": "https://github.com/owner/repo or git@github.com:owner/repo.git",
"repos_add": "Add",
"repos_selected": "Selected"
},

View File

@@ -68,7 +68,7 @@
"popover_title": "Attach a GitHub repo",
"attached_badge": "attached",
"remove_tooltip": "Remove",
"url_placeholder": "https://github.com/owner/repo",
"url_placeholder": "https://github.com/owner/repo or git@github.com:owner/repo.git",
"url_submit": "Add",
"toast_attached": "Repository attached",
"toast_attach_failed": "Failed to attach",

View File

@@ -174,7 +174,7 @@
"repositories": {
"section_title": "Repositories",
"description": "Git repositories associated with this workspace. Agents use these to clone and work on code.",
"url_placeholder": "https://git.example.com/org/repo.git",
"url_placeholder": "https://git.example.com/org/repo.git or git@git.example.com:org/repo.git",
"url_empty": "(empty URL)",
"description_placeholder": "Description (e.g. Go backend + Next.js frontend)",
"add": "Add repository",

View File

@@ -90,7 +90,7 @@
"repos_pill_count_other": "{{count}} 个仓库",
"repos_heading": "为此项目关联 GitHub 仓库",
"repos_empty": "还没有工作区级别的仓库。可以在下方粘贴 URL 临时关联一个。",
"repos_url_placeholder": "https://github.com/owner/repo",
"repos_url_placeholder": "https://github.com/owner/repo 或 git@github.com:owner/repo.git",
"repos_add": "添加",
"repos_selected": "已选"
},

View File

@@ -68,7 +68,7 @@
"popover_title": "关联 GitHub 仓库",
"attached_badge": "已关联",
"remove_tooltip": "移除",
"url_placeholder": "https://github.com/owner/repo",
"url_placeholder": "https://github.com/owner/repo 或 git@github.com:owner/repo.git",
"url_submit": "添加",
"toast_attached": "已关联仓库",
"toast_attach_failed": "关联失败",

View File

@@ -174,7 +174,7 @@
"repositories": {
"section_title": "代码仓库",
"description": "与该工作区关联的 Git 仓库。智能体会从这里 clone 代码并完成工作。",
"url_placeholder": "https://git.example.com/org/repo.git",
"url_placeholder": "https://git.example.com/org/repo.git 或 git@git.example.com:org/repo.git",
"url_empty": "(空 URL",
"description_placeholder": "描述例如Go 后端 + Next.js 前端)",
"add": "添加仓库",

View File

@@ -503,7 +503,7 @@ export function CreateProjectModal({ onClose }: { onClose: () => void }) {
className="flex items-center gap-1.5 pt-1 border-t"
>
<input
type="url"
type="text"
value={customRepoUrl}
onChange={(e) => setCustomRepoUrl(e.target.value)}
placeholder={t(($) => $.create_project.repos_url_placeholder)}

View File

@@ -248,7 +248,7 @@ function CustomRepoForm({
return (
<form onSubmit={handle} className="flex items-center gap-1.5 pt-1 border-t">
<input
type="url"
type="text"
value={url}
onChange={(e) => setUrl(e.target.value)}
placeholder={t(($) => $.resources.url_placeholder)}

View File

@@ -197,6 +197,36 @@ describe("RepositoriesTab — view/edit toggle", () => {
expect(screen.getByRole("button", { name: /^Save$/ })).toBeDisabled();
});
it("accepts scp-like shorthand without browser URL validation blocking submit", async () => {
const user = userEvent.setup();
mockUpdateWorkspace.mockImplementation(
async (_id: string, payload: { repos: { url: string }[] }) => {
workspaceRef.current = { ...workspaceRef.current, repos: payload.repos };
return workspaceRef.current;
},
);
render(<RepositoriesTab />, { wrapper: I18nWrapper });
await user.click(screen.getByRole("button", { name: "Edit repository" }));
const input = screen.getByRole("textbox") as HTMLInputElement;
await user.clear(input);
await user.type(input, "git@github.com:multica-ai/multica.git");
// type="text" (not "url") so the browser does not run native URL
// validation; the value reaches the server which has the real check.
expect(input.type).toBe("text");
expect(input.validity.valid).toBe(true);
await user.click(screen.getByRole("button", { name: /^Save$/ }));
await waitFor(() => {
expect(mockUpdateWorkspace).toHaveBeenCalledWith("workspace-1", {
repos: [{ url: "git@github.com:multica-ai/multica.git" }],
});
});
});
it("deleting a row shifts tracked edit indices so the wrong row doesn't open", async () => {
workspaceRef.current = {
...workspaceRef.current,

View File

@@ -128,7 +128,7 @@ export function RepositoriesTab() {
>
{isEditing ? (
<Input
type="url"
type="text"
value={repo.url}
onChange={(e) => handleRepoChange(index, e.target.value)}
disabled={!canManageWorkspace}