a code editor panel opens at its dashboard's folder

A dashboard scoped to ~/Tests/platform showed exactly that in the editor's header,
above an explorer listing the HOME directory. The two halves of one panel
disagreed because only one of them asked: `CodeEditorHeader` reads `cwd` from the
workspace, and `CodeEditorPanelWrapper` — three lines — never touched it.

It now passes `cwdToPath(cwd)` as `initialPath`, which is what `FileTree` lists
from. Still no `urlState`: a dashboard can hold two editors and one `?file=` would
drive both. That reasoning, and the shape of the fix, come from
FileBrowserPanelWrapper, which had it right already — which is why the file
browser was correct in the same dashboard.

── One conversion instead of three ──

`cwd` → path existed twice, spelled differently, and the two disagreed:

  cwd.slice(1)               ''  -> ''      '/abs' -> 'abs'     both wrong
  cwd.replace(/^~\//, '/')   ''  -> '/'     '/abs' -> '/abs'    both right

An empty cwd gave the file endpoints an empty string, which lists nothing rather
than failing — the quiet kind of wrong. Writing a THIRD copy for the editor is
what prompted collapsing them: `cwdToPath` now lives beside `useWorkspace`, with 6
tests including the tilde-inside-a-name case that a careless regex would rewrite.

Swept the rest rather than assuming this was the only one: all three panel
wrappers now read cwd, and the terminal already did.

tsgo clean, frontend builds, 846 pass / 7 fail — +6 new, same 7.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-15 22:28:18 +00:00
co-authored by Claude Opus 5
parent 15cc74bb1e
commit a7046704d5
6 changed files with 75 additions and 5 deletions
@@ -1,3 +1,18 @@
import { useWorkspace, cwdToPath } from '../../components/Workspace';
import { CodeEditorView } from './CodeEditor';
export const CodeEditorPanelWrapper = () => <CodeEditorView className="h-full w-full" />;
/**
* The editor as a dashboard panel, rooted at the dashboard's folder.
*
* It ignored the workspace entirely and always opened the explorer at home, while `CodeEditorHeader` read
* `cwd` for itself and displayed the right folder — so a scoped dashboard showed `~/Tests/platform` in the
* header above a tree of the home directory. The two halves of the same panel disagreed because only one
* of them asked.
*
* No `urlState`: a dashboard can hold two editors and one `?file=` would drive both. Same reasoning as
* FileBrowserPanelWrapper, which is where this pattern came from and which had it right already.
*/
export const CodeEditorPanelWrapper = () => {
const { cwd } = useWorkspace();
return <CodeEditorView className="h-full w-full" initialPath={cwdToPath(cwd)} />;
};
@@ -25,6 +25,7 @@ import {
NEW_DASH_TEMPLATE_KEY,
} from './constants';
import { useNewDashboardDraft } from './useNewDashboardDraft';
import { cwdToPath } from '../../components/Workspace';
export const DashboardListApp = () => {
const location = useLocation();
@@ -74,7 +75,7 @@ export const DashboardListApp = () => {
setName(ws.name);
setDescription(ws.description ?? '');
setTemplateIdx(ws.templateIdx ?? 0);
const cwdPath = !ws.cwd || ws.cwd === '~' ? '/' : ws.cwd.replace(/^~\//, '/');
const cwdPath = cwdToPath(ws.cwd);
setFilePath(cwdPath);
};
@@ -1,8 +1,6 @@
import { useWorkspace } from '../../../components/Workspace';
import { useWorkspace, cwdToPath } from '../../../components/Workspace';
import { FileBrowserApp } from './FileBrowserApp';
const cwdToPath = (cwd: string) => (cwd === '~' ? '/' : cwd.slice(1));
export const FileBrowserPanelWrapper = () => {
const { cwd, workspace } = useWorkspace();
const basePath = cwdToPath(cwd);
@@ -0,0 +1,35 @@
import { describe, expect, test } from 'bun:test';
import { cwdToPath } from './cwd-path';
// One conversion, because there were two and they disagreed — and a third was about to be written.
describe('cwdToPath', () => {
test('the home shorthand is the root', () => {
expect(cwdToPath('~')).toBe('/');
});
test('a scoped dashboard resolves to its folder', () => {
expect(cwdToPath('~/Tests/platform')).toBe('/Tests/platform');
});
test('missing or empty is the root, not the empty string', () => {
// `''.slice(1)` gave `''`, which is not a path any endpoint accepts — it listed nothing rather than
// failing, which is the quiet kind of wrong.
expect(cwdToPath('')).toBe('/');
expect(cwdToPath(undefined)).toBe('/');
expect(cwdToPath(null)).toBe('/');
});
test('an already-absolute path is left alone', () => {
// `'/abs'.slice(1)` gave `'abs'` — a relative path, silently resolved against somewhere else.
expect(cwdToPath('/already/abs')).toBe('/already/abs');
});
test('spaces and punctuation are untouched — this is notation, not encoding', () => {
expect(cwdToPath('~/a b/c (2)')).toBe('/a b/c (2)');
});
test('a tilde inside a name is not a prefix and must not be rewritten', () => {
expect(cwdToPath('~/back~up')).toBe('/back~up');
});
});
@@ -0,0 +1,20 @@
// A workspace's `cwd` as a path the file APIs accept.
//
// A dashboard is created against a directory, and every file-shaped panel in it should open there. The
// framework stores that as a `cwd` in the shell's own notation — `~`, `~/Tests/platform` — while the file
// endpoints take a path rooted at the home directory: `/`, `/Tests/platform`.
//
// This existed twice, spelled differently, and the two disagreed:
//
// cwd.slice(1) '' -> '' '/abs' -> 'abs' ← both wrong
// cwd.replace(/^~\//, '/') '' -> '/' '/abs' -> '/abs' ← both right
//
// The second is the one kept. A third copy was about to be written for the code editor, which is what
// prompted collapsing them: two spellings of one conversion is a bug waiting for whichever caller got the
// short one.
/** `~` and `~/x` become `/` and `/x`. An already-absolute path is returned unchanged. */
export function cwdToPath(cwd: string | undefined | null): string {
if (!cwd || cwd === '~') return '/';
return cwd.replace(/^~\//, '/');
}
@@ -33,6 +33,7 @@ export {
export type { WorkspaceIdentity } from './workspace-identity';
export { parseWorkspaceKey } from './workspace-identity';
export { WorkspaceProvider, useWorkspace } from './WorkspaceContext';
export { cwdToPath } from './cwd-path';
export { usePanelFullscreen } from './panel-fullscreen';
export { usePanelConfig } from './usePanelConfig';
export { usePanelClose } from './usePanelClose';