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:
@@ -1,3 +1,18 @@
|
|||||||
|
import { useWorkspace, cwdToPath } from '../../components/Workspace';
|
||||||
import { CodeEditorView } from './CodeEditor';
|
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,
|
NEW_DASH_TEMPLATE_KEY,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import { useNewDashboardDraft } from './useNewDashboardDraft';
|
import { useNewDashboardDraft } from './useNewDashboardDraft';
|
||||||
|
import { cwdToPath } from '../../components/Workspace';
|
||||||
|
|
||||||
export const DashboardListApp = () => {
|
export const DashboardListApp = () => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@@ -74,7 +75,7 @@ export const DashboardListApp = () => {
|
|||||||
setName(ws.name);
|
setName(ws.name);
|
||||||
setDescription(ws.description ?? '');
|
setDescription(ws.description ?? '');
|
||||||
setTemplateIdx(ws.templateIdx ?? 0);
|
setTemplateIdx(ws.templateIdx ?? 0);
|
||||||
const cwdPath = !ws.cwd || ws.cwd === '~' ? '/' : ws.cwd.replace(/^~\//, '/');
|
const cwdPath = cwdToPath(ws.cwd);
|
||||||
setFilePath(cwdPath);
|
setFilePath(cwdPath);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+1
-3
@@ -1,8 +1,6 @@
|
|||||||
import { useWorkspace } from '../../../components/Workspace';
|
import { useWorkspace, cwdToPath } from '../../../components/Workspace';
|
||||||
import { FileBrowserApp } from './FileBrowserApp';
|
import { FileBrowserApp } from './FileBrowserApp';
|
||||||
|
|
||||||
const cwdToPath = (cwd: string) => (cwd === '~' ? '/' : cwd.slice(1));
|
|
||||||
|
|
||||||
export const FileBrowserPanelWrapper = () => {
|
export const FileBrowserPanelWrapper = () => {
|
||||||
const { cwd, workspace } = useWorkspace();
|
const { cwd, workspace } = useWorkspace();
|
||||||
const basePath = cwdToPath(cwd);
|
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 type { WorkspaceIdentity } from './workspace-identity';
|
||||||
export { parseWorkspaceKey } from './workspace-identity';
|
export { parseWorkspaceKey } from './workspace-identity';
|
||||||
export { WorkspaceProvider, useWorkspace } from './WorkspaceContext';
|
export { WorkspaceProvider, useWorkspace } from './WorkspaceContext';
|
||||||
|
export { cwdToPath } from './cwd-path';
|
||||||
export { usePanelFullscreen } from './panel-fullscreen';
|
export { usePanelFullscreen } from './panel-fullscreen';
|
||||||
export { usePanelConfig } from './usePanelConfig';
|
export { usePanelConfig } from './usePanelConfig';
|
||||||
export { usePanelClose } from './usePanelClose';
|
export { usePanelClose } from './usePanelClose';
|
||||||
|
|||||||
Reference in New Issue
Block a user