From 06febfa03258d01df541f161753bff94513d1fef Mon Sep 17 00:00:00 2001 From: Devvyn <22340871+opticon454@users.noreply.github.com> Date: Sat, 5 Sep 2026 17:14:05 +0800 Subject: [PATCH] fix(file-picker): default the case picker to Codeman Cases, not Home MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "Link Existing" case picker opens with an empty path and no sessionId, so the browse endpoint's fallback root picked whichever root happened to be first in the list — which was always `Home`. On the native default that's harmless (~/codeman-cases nests inside Home anyway), but a Docker deployment binds CODEMAN_APPDATA_PATH (Home) and CODEMAN_CASES_PATH at unrelated host paths, so the picker opened somewhere with no cases in sight. Worse: if CODEMAN_CASES_PATH is ever changed after cases already exist, the old cases directory lingers, still reachable, under Home — indistinguishable at a glance from the real one under the new Codeman Cases root. Prefer the Codeman Cases root in the fallback chain, ahead of the generic roots[0]. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01R9ZSTEenc8soSu9bTi8Xru --- src/web/routes/file-routes.ts | 15 ++++++++++++++- test/routes/file-routes.test.ts | 15 +++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/web/routes/file-routes.ts b/src/web/routes/file-routes.ts index 20a567f3..9640cd28 100644 --- a/src/web/routes/file-routes.ts +++ b/src/web/routes/file-routes.ts @@ -536,8 +536,21 @@ async function resolveFilesystemPickerPath( throwFilesystemPickerError(403, ApiErrorCode.INVALID_INPUT, 'No filesystem browse roots are available'); } + // With no explicit path (the "Link Existing" case picker, which passes no + // sessionId and an empty initialPath until the user has typed something), + // land on the shared cases root rather than falling through to whichever + // root happens to be first. `Home` is only nested under `Codeman Cases` on + // the native default (~/codeman-cases); a Docker deployment binds them at + // unrelated host paths (CODEMAN_APPDATA_PATH vs CODEMAN_CASES_PATH), so a + // Home-first fallback opened the picker somewhere with no cases in sight — + // and, worse, made an OLD case folder left behind by a since-changed + // CODEMAN_CASES_PATH look like a normal thing to stumble across while + // browsing for one to link. const fallbackRoot = - roots.find((root) => root.label === 'Current Folder') ?? roots.find((root) => root.path === '/mnt/d') ?? roots[0]; + roots.find((root) => root.label === 'Current Folder') ?? + roots.find((root) => root.label === 'Codeman Cases') ?? + roots.find((root) => root.path === '/mnt/d') ?? + roots[0]; const candidatePath = resolve(requestedPath ?? fallbackRoot.path); let resolvedPath: string; diff --git a/test/routes/file-routes.test.ts b/test/routes/file-routes.test.ts index 9552c32a..e8c532d3 100644 --- a/test/routes/file-routes.test.ts +++ b/test/routes/file-routes.test.ts @@ -10,6 +10,7 @@ import { Readable } from 'node:stream'; import { createRouteTestHarness, type RouteTestHarness } from './_route-test-utils.js'; import { registerFileRoutes } from '../../src/web/routes/file-routes.js'; import { ApiErrorCode } from '../../src/types.js'; +import { CASES_DIR } from '../../src/web/route-helpers.js'; // Mock fs/promises for file operations vi.mock('node:fs/promises', () => ({ @@ -114,6 +115,20 @@ describe('file-routes', () => { ]); }); + it('defaults to the Codeman Cases root, not Home, when linking a case with no path chosen yet', async () => { + // The "Link Existing" case picker opens with an empty path and no + // sessionId. `Home` and `Codeman Cases` are unrelated bind mounts under + // Docker, so falling back to whichever root happened to be listed first + // could open the picker somewhere with no cases in it at all — and, worse, + // make a stale directory from a since-changed CODEMAN_CASES_PATH look like + // a normal thing to stumble across while browsing for one to link. + const res = await harness.app.inject({ method: 'GET', url: '/api/filesystem/browse' }); + + expect(res.statusCode).toBe(200); + const body = JSON.parse(res.body); + expect(body.data.path).toBe(CASES_DIR); + }); + it('rejects paths outside the configured roots', async () => { const res = await harness.app.inject({ method: 'GET',