From 9d2f9663cb816143f950b88cb791a5f96bd09055 Mon Sep 17 00:00:00 2001 From: Codeman maintainer Date: Fri, 6 Nov 2026 08:11:38 +0100 Subject: [PATCH] fix: word the cases-folder refusal for Clone Repo too (release review) Clone Repo's picker now offers New folder, and with the field empty Browse opens in the cases folder, so the first press there lands on this refusal. It pointed at Add Case > Create New, which cannot clone. The sentence has to read right from every picker that offers the button, so it names both tabs. Co-Authored-By: Claude Opus 5.5 --- docs/api-reference.md | 8 ++++---- docs/wiki/Working-With-Files.md | 2 +- src/web/routes/file-routes.ts | 7 ++++--- test/routes/filesystem-mkdir-routes.test.ts | 3 ++- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/api-reference.md b/docs/api-reference.md index 38441dfd..0ee9aeb0 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -905,10 +905,10 @@ html and svg are the ones deliberately kept out. `POST /api/filesystem/mkdir` with `{ path, name, sessionId?, showHidden? }` makes one folder directly inside `path`, which must be a folder the path picker may browse, and answers `{ path }`. `name` is one path segment (letters, numbers, spaces and `_ - . ~`, not -starting with a dot or `~`). `400` for a cases folder (use Add Case → Create New with no -custom folder) or a `path` that is not a directory, `403` for a blocked tree or a `path` -outside the browse roots, `404` for a `path` that does not exist, `409` when the folder -already exists, and `422` when the mount under `path` does not answer. +starting with a dot or `~`). `400` for a cases folder (use Add Case → Create New or Clone +Repo with no custom folder) or a `path` that is not a directory, `403` for a blocked tree +or a `path` outside the browse roots, `404` for a `path` that does not exist, `409` when +the folder already exists, and `422` when the mount under `path` does not answer. ## Rendered HTML files diff --git a/docs/wiki/Working-With-Files.md b/docs/wiki/Working-With-Files.md index 02fcd0bb..85b438a7 100644 --- a/docs/wiki/Working-With-Files.md +++ b/docs/wiki/Working-With-Files.md @@ -192,7 +192,7 @@ In the **Link Existing**, **Create New** (custom folder) and **Clone Repo** (cus pickers, **+📁** (New folder) makes a folder inside the folder you are viewing and selects it. Names use letters, numbers, spaces and `_ - . ~` (not starting with a dot or `~`). The cases folder is refused, since every folder there is already a case: pick a folder outside -it, or make the case with **Add Case → Create New** (no custom folder). +it, or make the case with **Add Case → Create New** or **Clone Repo** (no custom folder). This is a separate file-serving surface from the viewer, with its own rules: it allowlists your home directory, the cases directory, and anything in `CODEMAN_FILE_PICKER_ROOTS`, and diff --git a/src/web/routes/file-routes.ts b/src/web/routes/file-routes.ts index bcc420d6..5024b94b 100644 --- a/src/web/routes/file-routes.ts +++ b/src/web/routes/file-routes.ts @@ -1567,8 +1567,9 @@ export function registerFileRoutes(app: FastifyInstance, ctx: SessionPort & Even // The cases directories (shared, and the caller's own in multi-user mode) are what GET /api/cases lists as // local cases: a folder made there is a case with no scaffold, which Link then refuses by name and which // deleting would take the linked folder with. A case there is made through Add Case > Create New with no - // custom folder. The refusal has to read right from both pickers that offer New folder: a Create New user - // is already on that tab, with the custom folder ticked. + // custom folder. The refusal has to read right from every picker that offers New folder: a Create New or + // Clone Repo user is already on that tab, with the custom folder ticked, and this is where Browse opens + // while the field is empty. const casesDirs = await Promise.all( [...new Set([CASES_DIR, resolveCasesDir(getAuthUser(req))])].map((dir) => fs.realpath(dir).catch(() => resolve(dir)) @@ -1578,7 +1579,7 @@ export function registerFileRoutes(app: FastifyInstance, ctx: SessionPort & Even reply.code(400); return createErrorResponse( ApiErrorCode.INVALID_INPUT, - 'This is a cases folder: every folder in it is a case. Pick a folder outside it, or make the case with Add Case > Create New (no custom folder).' + 'This is a cases folder: every folder in it is a case. Pick a folder outside it, or make the case with Add Case > Create New or Clone Repo (no custom folder).' ); } // Like browse, a file is not a place to create in. diff --git a/test/routes/filesystem-mkdir-routes.test.ts b/test/routes/filesystem-mkdir-routes.test.ts index d16146d0..050221c2 100644 --- a/test/routes/filesystem-mkdir-routes.test.ts +++ b/test/routes/filesystem-mkdir-routes.test.ts @@ -52,7 +52,8 @@ describe('POST /api/filesystem/mkdir', () => { const res = await mkdir({ path: casesDir, name: 'myproj' }); expect(res.statusCode).toBe(400); expect(res.json().error).toContain('Pick a folder outside it'); - expect(res.json().error).toContain('Add Case > Create New (no custom folder)'); + // Every picker offering New folder sees this, and Clone Repo opens here by default. + expect(res.json().error).toContain('Add Case > Create New or Clone Repo (no custom folder)'); expect(existsSync(join(casesDir, 'myproj'))).toBe(false); // A folder inside a case is fine. mkdirSync(join(casesDir, 'real-case'));