diff --git a/.changeset/case-path-nested-in-cases-dir.md b/.changeset/case-path-nested-in-cases-dir.md new file mode 100644 index 000000000..01e557d97 --- /dev/null +++ b/.changeset/case-path-nested-in-cases-dir.md @@ -0,0 +1,5 @@ +--- +'aicodeman': patch +--- + +Create New → custom folder no longer refuses every folder below the cases folder. Only the cases folder itself and its direct children are refused, because those are the only ones Codeman lists as local cases (a second registration would show them twice). With the cases folder set to a broad root such as `/mnt/user/Scripts` (`CODEMAN_CASES_PATH`), a folder like `/mnt/user/Scripts/GitHub/me/new-project` can now be created and linked. The refusal message now names the cases folder. diff --git a/src/web/case-path.ts b/src/web/case-path.ts index fb784412d..c64015314 100644 --- a/src/web/case-path.ts +++ b/src/web/case-path.ts @@ -98,9 +98,13 @@ export function blockedReason( const firstSegment = absPath.slice(ctx.home.length + 1).split(sep)[0]; if (/^\.codeman/.test(firstSegment)) return "Codeman's own data folder cannot be a case"; } + // Only the cases folder itself and its DIRECT children are listed as local cases (GET /api/cases reads + // that one level), so only those would be registered twice. A deeper folder is not listed and is a + // perfectly good linked case: with CODEMAN_CASES_PATH pointing at a broad root such as /mnt/user/Scripts, + // refusing everything below it blocked most of the tree. for (const dir of ctx.casesDirs ?? []) { - if (isWithin(absPath, dir)) { - return 'That folder is inside the cases folder; create a case there with plain Create New (no custom folder)'; + if (absPath === dir || dirname(absPath) === dir) { + return `That folder is in the cases folder (${dir}), where it is already listed as a case; create it there with plain Create New (no custom folder)`; } } return null; diff --git a/test/case-path.test.ts b/test/case-path.test.ts index 8984a14cd..7b267dcd7 100644 --- a/test/case-path.test.ts +++ b/test/case-path.test.ts @@ -54,14 +54,24 @@ describe('blockedReason', () => { expect(blockedReason('/usrlocal', c)).toBeNull(); }); - it('refuses a cases directory and anything inside it, when given', () => { + it('refuses a cases directory and its direct children, when given', () => { const withCases = { ...c, casesDirs: ['/home/u/codeman-cases'] }; expect(blockedReason('/home/u/codeman-cases', withCases)).toMatch(/plain Create New/); expect(blockedReason('/home/u/codeman-cases/foo', withCases)).toMatch(/plain Create New/); + expect(blockedReason('/home/u/codeman-cases/foo', withCases)).toContain('/home/u/codeman-cases'); expect(blockedReason('/home/u/codeman-cases-old/foo', withCases)).toBeNull(); expect(blockedReason('/home/u/codeman-cases/foo', c)).toBeNull(); }); + it('allows a folder deeper than a direct child of the cases directory (it is not listed as a local case)', () => { + // CODEMAN_CASES_PATH can be a broad root such as /mnt/user/Scripts; GET /api/cases lists only its + // direct children, so a folder further down can be linked without appearing twice. + const broad = { ...c, casesDirs: ['/mnt/user/Scripts'] }; + expect(blockedReason('/mnt/user/Scripts/GitHub', broad)).toMatch(/plain Create New/); + expect(blockedReason('/mnt/user/Scripts/GitHub/opticon454', broad)).toBeNull(); + expect(blockedReason('/mnt/user/Scripts/GitHub/opticon454/app', broad)).toBeNull(); + }); + it('judges against the system roots it is given', () => { expect(blockedReason('/private/etc/x', c)).toBeNull(); expect(blockedReason('/private/etc/x', c, ['/private/etc'])).toMatch(/system directory/);