From 6f1a375e5930eab73ee0ff0bd213499232d8a118 Mon Sep 17 00:00:00 2001
From: MohamedAli00949 <76850143+MohamedAli00949@users.noreply.github.com>
Date: Mon, 26 Jan 2026 09:39:14 +0200
Subject: [PATCH 1/5] fix: adding files inside subdir
---
src/renderer/components/sidebar-file-tree.tsx | 24 +++++++++++++++++--
1 file changed, 22 insertions(+), 2 deletions(-)
diff --git a/src/renderer/components/sidebar-file-tree.tsx b/src/renderer/components/sidebar-file-tree.tsx
index 634b055920..b7c61fec26 100644
--- a/src/renderer/components/sidebar-file-tree.tsx
+++ b/src/renderer/components/sidebar-file-tree.tsx
@@ -41,7 +41,7 @@ export const SidebarFileTree = observer(
}
public render() {
- const { editorMosaic } = this.props.appState;
+ const { editorMosaic, showErrorDialog } = this.props.appState;
const { files, focusedFile } = editorMosaic;
const fileList: TreeNodeInfo[] = Array.from(files)
@@ -108,10 +108,23 @@ export const SidebarFileTree = observer(
{
+ onKeyDown={async (e) => {
if (e.key === 'Escape') {
e.currentTarget.blur();
} else if (e.key === 'Enter') {
+ const filename = e.currentTarget.value.trim();
+ if (filename.includes('/') || filename.includes('\\')) {
+ await showErrorDialog(
+ `Invalid filename "${filename}": inside subdir not supported yet`,
+ );
+ return;
+ }
+ if (!isSupportedFile(filename)) {
+ await showErrorDialog(
+ `Invalid filename "${filename}": Must be a file ending in .cjs, .js, .mjs, .html, .css, or .json`,
+ );
+ return;
+ }
this.createEditor(e.currentTarget.value as EditorId);
e.currentTarget.blur();
}
@@ -189,6 +202,13 @@ export const SidebarFileTree = observer(
if (!id) return;
+ if (id.includes('/') || id.includes('\\')) {
+ await appState.showErrorDialog(
+ `Invalid filename "${id}": inside subdir not supported yet`,
+ );
+ return;
+ }
+
if (
id.endsWith('.json') &&
[PACKAGE_NAME, 'package-lock.json'].includes(id)
From 2e7bb1d43b03708c2f067624e3c1b9148930abbf Mon Sep 17 00:00:00 2001
From: MohamedAli00949 <76850143+MohamedAli00949@users.noreply.github.com>
Date: Mon, 26 Jan 2026 09:46:22 +0200
Subject: [PATCH 2/5] fix: refactor from normal func to arrow func
---
src/renderer/state.ts | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/renderer/state.ts b/src/renderer/state.ts
index c421af435f..a2ee654b42 100644
--- a/src/renderer/state.ts
+++ b/src/renderer/state.ts
@@ -1023,14 +1023,16 @@ export class AppState {
});
}
- public async showErrorDialog(label: string | JSX.Element): Promise {
+ public showErrorDialog = async (
+ label: string | JSX.Element,
+ ): Promise => {
await this.showGenericDialog({
label,
ok: 'Close',
type: GenericDialogType.warning,
wantsInput: false,
});
- }
+ };
/**
* Ensure that any buffered console output is
From 33df8a0b18f4ac23ed4d37c4b0f5e741c8369ea3 Mon Sep 17 00:00:00 2001
From: MohamedAli00949 <76850143+MohamedAli00949@users.noreply.github.com>
Date: Mon, 26 Jan 2026 09:59:58 +0200
Subject: [PATCH 3/5] test: write tests to test prevent adding files inside
subdir
---
.../components/sidebar-file-tree.spec.tsx | 58 +++++++++++++++++++
1 file changed, 58 insertions(+)
diff --git a/rtl-spec/components/sidebar-file-tree.spec.tsx b/rtl-spec/components/sidebar-file-tree.spec.tsx
index 922df2eb12..4b9c31bb6e 100644
--- a/rtl-spec/components/sidebar-file-tree.spec.tsx
+++ b/rtl-spec/components/sidebar-file-tree.spec.tsx
@@ -99,6 +99,34 @@ describe('SidebarFileTree component', () => {
expect(editorMosaic.files.get('tester.js')).toBe(EditorPresence.Pending);
});
+ it('fails to create new editors (file name inside subdir)', async () => {
+ const user = userEvent.setup();
+ const { container } = render();
+ const EDITOR_NEW_NAME = 'subdir/tester.js';
+
+ expect(editorMosaic.files.get(EDITOR_NEW_NAME)).toBe(undefined);
+
+ // Click the "Add New File" button (by icon)
+ const addButton = container.querySelector(
+ 'button .bp3-icon-add',
+ )?.parentElement;
+ await user.click(addButton!);
+
+ // Type the filename and press Enter
+ const input = container.querySelector(
+ '#new-file-input',
+ ) as HTMLInputElement;
+ await user.type(input, `${EDITOR_NEW_NAME}{Enter}`);
+
+ // Wait for error dialog to be called
+ await waitFor(() => {
+ console.log('store.showErrorDialog: ', store.showErrorDialog);
+ expect(store.showErrorDialog).toHaveBeenCalledWith(
+ `Invalid filename "${EDITOR_NEW_NAME}": inside subdir not supported yet`,
+ );
+ });
+ });
+
it('can delete editors', async () => {
const user = userEvent.setup();
const { container } = render();
@@ -237,6 +265,36 @@ describe('SidebarFileTree component', () => {
expect(editorMosaic.files.get(EDITOR_NAME)).toBe(EditorPresence.Pending);
});
+ it('fails if trying to rename an editor to an unsupported name (file inside subdir)', async () => {
+ const user = userEvent.setup();
+ const EDITOR_NAME = 'index.html';
+ const EDITOR_NEW_NAME = 'msg/data.json';
+
+ store.showInputDialog = vi.fn().mockResolvedValueOnce(EDITOR_NEW_NAME);
+ store.showErrorDialog = vi.fn().mockResolvedValueOnce(true);
+
+ const { container } = render();
+
+ // Right-click on index.html to open context menu
+ const fileLabel = Array.from(container.querySelectorAll('.pointer')).find(
+ (el) => el.textContent === EDITOR_NAME,
+ ) as HTMLElement;
+ fireEvent.contextMenu(fileLabel);
+
+ // Click the "Rename" menu item
+ const renameItem = await screen.findByText('Rename');
+ await user.click(renameItem);
+
+ // Wait for error dialog to be called
+ await waitFor(() => {
+ expect(store.showErrorDialog).toHaveBeenCalledWith(
+ `Invalid filename "${EDITOR_NEW_NAME}": inside subdir not supported yet`,
+ );
+ });
+
+ expect(editorMosaic.files.get(EDITOR_NAME)).toBe(EditorPresence.Pending);
+ });
+
it('fails if trying to rename an editor to an existing name', async () => {
const user = userEvent.setup();
const EXISTED_NAME = 'styles.css';
From 55041ec6b2782750a3fc1400854cb091634e88d1 Mon Sep 17 00:00:00 2001
From: MohamedAli00949 <76850143+MohamedAli00949@users.noreply.github.com>
Date: Tue, 27 Jan 2026 04:06:20 +0200
Subject: [PATCH 4/5] fix: refactor file naming error handling
---
.../components/sidebar-file-tree.spec.tsx | 10 ++--
src/renderer/components/sidebar-file-tree.tsx | 51 +++----------------
src/renderer/editor-mosaic.ts | 33 ++++++++++++
3 files changed, 45 insertions(+), 49 deletions(-)
diff --git a/rtl-spec/components/sidebar-file-tree.spec.tsx b/rtl-spec/components/sidebar-file-tree.spec.tsx
index 4b9c31bb6e..5b5cb07686 100644
--- a/rtl-spec/components/sidebar-file-tree.spec.tsx
+++ b/rtl-spec/components/sidebar-file-tree.spec.tsx
@@ -96,10 +96,10 @@ describe('SidebarFileTree component', () => {
) as HTMLInputElement;
await user.type(input, 'tester.js{Enter}');
- expect(editorMosaic.files.get('tester.js')).toBe(EditorPresence.Pending);
+ expect(editorMosaic.files.get('tester.js')).toBe(EditorPresence.Hidden);
});
- it('fails to create new editors (file name inside subdir)', async () => {
+ it('fails to create new editors (file name with path separator)', async () => {
const user = userEvent.setup();
const { container } = render();
const EDITOR_NEW_NAME = 'subdir/tester.js';
@@ -122,7 +122,7 @@ describe('SidebarFileTree component', () => {
await waitFor(() => {
console.log('store.showErrorDialog: ', store.showErrorDialog);
expect(store.showErrorDialog).toHaveBeenCalledWith(
- `Invalid filename "${EDITOR_NEW_NAME}": inside subdir not supported yet`,
+ `Invalid filename "${EDITOR_NEW_NAME}": filenames cannot include path separators`,
);
});
});
@@ -265,7 +265,7 @@ describe('SidebarFileTree component', () => {
expect(editorMosaic.files.get(EDITOR_NAME)).toBe(EditorPresence.Pending);
});
- it('fails if trying to rename an editor to an unsupported name (file inside subdir)', async () => {
+ it('fails if trying to rename an editor to an unsupported name (file name with path separator)', async () => {
const user = userEvent.setup();
const EDITOR_NAME = 'index.html';
const EDITOR_NEW_NAME = 'msg/data.json';
@@ -288,7 +288,7 @@ describe('SidebarFileTree component', () => {
// Wait for error dialog to be called
await waitFor(() => {
expect(store.showErrorDialog).toHaveBeenCalledWith(
- `Invalid filename "${EDITOR_NEW_NAME}": inside subdir not supported yet`,
+ `Invalid filename "${EDITOR_NEW_NAME}": filenames cannot include path separators`,
);
});
diff --git a/src/renderer/components/sidebar-file-tree.tsx b/src/renderer/components/sidebar-file-tree.tsx
index b7c61fec26..c6ff4e08f7 100644
--- a/src/renderer/components/sidebar-file-tree.tsx
+++ b/src/renderer/components/sidebar-file-tree.tsx
@@ -14,10 +14,10 @@ import { ContextMenu2, Tooltip2 } from '@blueprintjs/popover2';
import classNames from 'classnames';
import { observer } from 'mobx-react';
-import { EditorId, PACKAGE_NAME } from '../../interfaces';
+import { EditorId } from '../../interfaces';
import { EditorPresence } from '../editor-mosaic';
import { AppState } from '../state';
-import { isMainEntryPoint, isSupportedFile } from '../utils/editor-utils';
+import { isMainEntryPoint } from '../utils/editor-utils';
interface FileTreeProps {
appState: AppState;
@@ -41,7 +41,7 @@ export const SidebarFileTree = observer(
}
public render() {
- const { editorMosaic, showErrorDialog } = this.props.appState;
+ const { editorMosaic } = this.props.appState;
const { files, focusedFile } = editorMosaic;
const fileList: TreeNodeInfo[] = Array.from(files)
@@ -112,19 +112,6 @@ export const SidebarFileTree = observer(
if (e.key === 'Escape') {
e.currentTarget.blur();
} else if (e.key === 'Enter') {
- const filename = e.currentTarget.value.trim();
- if (filename.includes('/') || filename.includes('\\')) {
- await showErrorDialog(
- `Invalid filename "${filename}": inside subdir not supported yet`,
- );
- return;
- }
- if (!isSupportedFile(filename)) {
- await showErrorDialog(
- `Invalid filename "${filename}": Must be a file ending in .cjs, .js, .mjs, .html, .css, or .json`,
- );
- return;
- }
this.createEditor(e.currentTarget.value as EditorId);
e.currentTarget.blur();
}
@@ -202,30 +189,6 @@ export const SidebarFileTree = observer(
if (!id) return;
- if (id.includes('/') || id.includes('\\')) {
- await appState.showErrorDialog(
- `Invalid filename "${id}": inside subdir not supported yet`,
- );
- return;
- }
-
- if (
- id.endsWith('.json') &&
- [PACKAGE_NAME, 'package-lock.json'].includes(id)
- ) {
- await appState.showErrorDialog(
- `Cannot add ${PACKAGE_NAME} or package-lock.json as custom files`,
- );
- return;
- }
-
- if (!isSupportedFile(id)) {
- await appState.showErrorDialog(
- `Invalid filename "${id}": Must be a file ending in .cjs, .js, .mjs, .html, .css, or .json`,
- );
- return;
- }
-
try {
await appState.editorMosaic.renameFile(editorId, id);
@@ -240,13 +203,13 @@ export const SidebarFileTree = observer(
editorMosaic.remove(editorId);
};
- public createEditor = (editorId: EditorId) => {
+ public createEditor = async (editorId: EditorId) => {
const { appState } = this.props;
try {
- appState.editorMosaic.addNewFile(editorId);
- appState.editorMosaic.show(editorId);
+ await appState.editorMosaic.addNewFile(editorId);
+ await appState.editorMosaic.show(editorId);
} catch (err: any) {
- appState.showErrorDialog(err.message);
+ await appState.showErrorDialog(err.message);
}
};
diff --git a/src/renderer/editor-mosaic.ts b/src/renderer/editor-mosaic.ts
index 325196fd52..df4181d223 100644
--- a/src/renderer/editor-mosaic.ts
+++ b/src/renderer/editor-mosaic.ts
@@ -287,6 +287,18 @@ export class EditorMosaic {
throw new Error(`Cannot add file "${id}": File already exists`);
}
+ if (id.includes('/') || id.includes('\\')) {
+ throw new Error(
+ `Invalid filename "${id}": filenames cannot include path separators`,
+ );
+ }
+
+ if (!isSupportedFile(id)) {
+ throw new Error(
+ `Invalid filename "${id}": Must be a file ending in .cjs, .js, .mjs, .html, .css, or .json`,
+ );
+ }
+
const entryPoint = this.mainEntryPointFile();
if (isMainEntryPoint(id) && entryPoint) {
@@ -308,6 +320,27 @@ export class EditorMosaic {
throw new Error(`Cannot rename file to "${newId}": File already exists`);
}
+ if (newId.includes('/') || newId.includes('\\')) {
+ throw new Error(
+ `Invalid filename "${newId}": filenames cannot include path separators`,
+ );
+ }
+
+ if (
+ newId.endsWith('.json') &&
+ [PACKAGE_NAME, 'package-lock.json'].includes(newId)
+ ) {
+ throw new Error(
+ `Cannot add ${PACKAGE_NAME} or package-lock.json as custom files`,
+ );
+ }
+
+ if (!isSupportedFile(newId)) {
+ throw new Error(
+ `Invalid filename "${newId}": Must be a file ending in .cjs, .js, .mjs, .html, .css, or .json`,
+ );
+ }
+
const entryPoint = this.mainEntryPointFile();
if (isMainEntryPoint(newId) && entryPoint !== oldId) {
From f92d624cda467c7d7d1111de49f59f1b09ccbceb Mon Sep 17 00:00:00 2001
From: MohamedAli00949 <76850143+MohamedAli00949@users.noreply.github.com>
Date: Sat, 31 Jan 2026 06:54:32 +0200
Subject: [PATCH 5/5] fix: refactor displaying the error dialog from sidebar
file operations
---
src/renderer/components/sidebar-file-tree.tsx | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/renderer/components/sidebar-file-tree.tsx b/src/renderer/components/sidebar-file-tree.tsx
index c6ff4e08f7..d7148541e1 100644
--- a/src/renderer/components/sidebar-file-tree.tsx
+++ b/src/renderer/components/sidebar-file-tree.tsx
@@ -194,7 +194,8 @@ export const SidebarFileTree = observer(
if (visible) appState.editorMosaic.show(id);
} catch (err: any) {
- appState.showErrorDialog(err.message);
+ await new Promise((r) => setTimeout(() => r(), 100));
+ await appState.showErrorDialog(err.message);
}
};
@@ -209,6 +210,7 @@ export const SidebarFileTree = observer(
await appState.editorMosaic.addNewFile(editorId);
await appState.editorMosaic.show(editorId);
} catch (err: any) {
+ await new Promise((r) => setTimeout(() => r(), 100));
await appState.showErrorDialog(err.message);
}
};