feat(docker): add a folder picker to both path fields

Both paths in the adoption form had to be typed. Each gets a Browse button
using the same path-input-group markup Link Existing uses, so the two look and
behave alike.

What they can browse differs, and that is the point. The host workspace path
reuses the existing host picker. The container workdir cannot: an adopted
container has nothing mounted at a matching host path, so a host listing would
be a different filesystem — and getting this field wrong is the source of the
opaque OCI chdir error at launch, which makes it the field that most needs to
be clickable.

Adds a read-only POST /api/docker-cases/browse: one `ls` through docker exec, no
writes, no lifecycle, path shell-escaped like every other value. `ls -Ap` marks
directories with a trailing slash and keeps names with spaces intact.

PathPicker takes an optional fetchListing source rather than being forked: the
container variant only swaps where the rows come from, and reuses the rendering,
navigation, Up and Choose/Select unchanged.
This commit is contained in:
d fei
2026-08-29 23:31:28 -07:00
parent 23ab2e77fd
commit 5452ad5c5a
7 changed files with 200 additions and 6 deletions
+34
View File
@@ -272,6 +272,40 @@ describe('adopted container: run modes come from the CONTAINER, not the host', (
});
});
describe('adopted container: both path fields get a folder picker', () => {
const html = readFileSync(new URL('../src/web/public/index.html', import.meta.url), 'utf8');
const ui = readFileSync(new URL('../src/web/public/session-ui.js', import.meta.url), 'utf8');
const picker = readFileSync(new URL('../src/web/public/keyboard-accessory.js', import.meta.url), 'utf8');
it('wires a Browse button to each of the two paths', () => {
expect(html).toContain('app.openDockerWorkspacePathPicker()');
expect(html).toContain('app.openDockerWorkdirPicker()');
// Same markup Link Existing uses, so the two look and behave alike.
expect(html.match(/path-input-browse/g)?.length).toBeGreaterThanOrEqual(3);
});
it('browses the CONTAINER for the container workdir, not the host', () => {
// For an adopted container nothing is mounted at a matching host path, so a
// host listing would be a different filesystem — and typing this field blind
// is what makes the launch fail with an OCI chdir error.
const fn = ui.slice(ui.indexOf('openDockerWorkdirPicker()'), ui.indexOf('async linkRemoteCase()'));
expect(fn).toContain('/api/docker-cases/browse');
expect(fn).not.toContain('/api/filesystem/browse');
expect(fn).toContain('fetchListing');
});
it('keeps the host picker for the host workspace path', () => {
const fn = ui.slice(ui.indexOf('openDockerWorkspacePathPicker()'), ui.indexOf('openDockerWorkdirPicker()'));
expect(fn).toContain('PathPicker.open');
expect(fn).not.toContain('fetchListing');
});
it('reuses one PathPicker via an optional source rather than forking it', () => {
expect(picker).toContain('this._options.fetchListing');
expect(picker).toContain('/api/filesystem/browse');
});
});
describe('adopted container: drift is not evaluated', () => {
it('reports no drift rather than demanding a recreate we may not perform', async () => {
// An adopted container carries no codeman.confighash label, so a real