diff --git a/scripts/e2e-session-persistence.test.mjs b/scripts/e2e-session-persistence.test.mjs index b1a9257..ed6ebcf 100644 --- a/scripts/e2e-session-persistence.test.mjs +++ b/scripts/e2e-session-persistence.test.mjs @@ -542,7 +542,7 @@ test('e2e: formats the installed extension version from either extension namespa assert.equal(getExtensionVersionLabel({}), ''); }); -test('e2e: default no-workspace source uses an untitled tab that cannot compile', async () => { +test('e2e: fresh no-workspace state has no open file and cannot compile', async () => { const originalDocument = global.document; global.document = createFakeDocument(); try { @@ -565,9 +565,11 @@ test('e2e: default no-workspace source uses an untitled tab that cannot compile' resetToNewProject(); - assert.deepEqual(getToolbarOpenTabPaths(), ['untitled:default']); - assert.equal(getToolbarActiveTabPath(), 'untitled:default'); - assert.equal(global.document.getElementById('tab-bar').children[0].children[0].textContent, 'unsaved file'); + assert.deepEqual(getToolbarOpenTabPaths(), []); + assert.equal(getToolbarActiveTabPath(), null); + assert.equal(editorValue, ''); + assert.equal(global.document.getElementById('tab-bar').children.length, 0); + assert.equal(global.document.getElementById('status-file').textContent, ''); await assert.rejects(() => assembleCompilePayload({}), /Open a folder or save a file/); } finally { diff --git a/scripts/e2e-workspace-file-tracking.test.mjs b/scripts/e2e-workspace-file-tracking.test.mjs index d350970..81bf45e 100644 --- a/scripts/e2e-workspace-file-tracking.test.mjs +++ b/scripts/e2e-workspace-file-tracking.test.mjs @@ -954,7 +954,7 @@ test('e2e: toolbar compilation requires a ready compiler and root-level C/C++ so test('e2e: New file with no workspace opens the folder picker; cancel leaves state unchanged', async () => { const ctx = await setupToolbar({ openFolderResult: null }); - ctx.toolbar.resetToNewProject(); // no workspace, single main.cpp tab + ctx.toolbar.resetToNewProject(); ctx.document.getElementById('btn-new').click(); await tick(); @@ -962,6 +962,41 @@ test('e2e: New file with no workspace opens the folder picker; cancel leaves sta assert.equal(ctx.fsCalls.openFolder, 1, 'folder picker invoked'); assert.equal(inlineInput(ctx.document), null, 'no inline input after cancel'); assert.equal(ctx.fsCalls.create.length, 0, 'no file created'); + assert.deepEqual(ctx.toolbar.getOpenTabPaths(), [], 'no synthetic tab created'); +}); + +test('e2e: Open Folder renders README in Explorer without opening it', async () => { + const ctx = await setupToolbar({ + openFolderResult: { name: 'project', entries: [{ path: 'README.md', kind: 'file' }] }, + }); + ctx.toolbar.resetToNewProject(); + + ctx.document.getElementById('btn-open').click(); + await tick(); + + assert.deepEqual(renderedTreePaths(ctx.document), ['README.md']); + assert.deepEqual(ctx.toolbar.getOpenTabPaths(), []); + assert.equal(ctx.toolbar.getActiveTabPath(), null); + assert.deepEqual(ctx.editorCalls.setValue.slice(-1), ['']); +}); + +test('e2e: saving from the empty state creates and opens a workspace file', async () => { + const originalPrompt = global.prompt; + global.prompt = () => 'main.cpp'; + try { + const ctx = await setupToolbar({ openFolderResult: { name: 'project', entries: [] } }); + ctx.toolbar.resetToNewProject(); + + ctx.document.getElementById('btn-save').click(); + await tick(); + await tick(); + + assert.deepEqual(ctx.fsCalls.create.map((file) => file.path), ['main.cpp']); + assert.deepEqual(ctx.toolbar.getOpenTabPaths(), ['main.cpp']); + assert.equal(ctx.toolbar.getActiveTabPath(), 'main.cpp'); + } finally { + global.prompt = originalPrompt; + } }); test('e2e: New file with a workspace shows an inline Explorer naming input', async () => { diff --git a/src/ui/app.js b/src/ui/app.js index bffe82d..3798326 100644 --- a/src/ui/app.js +++ b/src/ui/app.js @@ -26,7 +26,6 @@ import { getOpenTabsSnapshot, restoreWorkspace, resetToNewProject, - restoreNoWorkspaceSource, assembleCompilePayload, applyWorkspaceSnapshot, } from './toolbar.js'; @@ -111,7 +110,6 @@ window.addEventListener('DOMContentLoaded', async () => { getActiveTabPath, getOpenTabsSnapshot, restoreWorkspace, - restoreNoWorkspaceSource, confirmReload: promptReloadPreviousProject, startNewProject: resetToNewProject, setExplorerLoading: (loading) => toolbarController?.setExplorerLoading(loading), diff --git a/src/ui/editor.js b/src/ui/editor.js index 4147feb..9ca1c1a 100644 --- a/src/ui/editor.js +++ b/src/ui/editor.js @@ -28,15 +28,6 @@ self.MonacoEnvironment = { }, }; -// ── Default C++ starter source ──────────────────────────────────────────────── -export const DEFAULT_SOURCE = `#include - -int main() { - std::cout << "Hello, World!" << std::endl; - return 0; -} -`; - // ── Internal state ──────────────────────────────────────────────────────────── let _editor = null; @@ -78,7 +69,7 @@ export function createEditor(container) { }); _editor = monaco.editor.create(container, { - value: DEFAULT_SOURCE, + value: '', language: 'cpp', theme: 'browser-cpp-dark', fontSize: 14, diff --git a/src/ui/session-persistence.mjs b/src/ui/session-persistence.mjs index 8f569d0..8bda67a 100644 --- a/src/ui/session-persistence.mjs +++ b/src/ui/session-persistence.mjs @@ -178,8 +178,7 @@ export function createSessionPersistence({ if (typeof fsAPI.resetWorkspace === 'function') fsAPI.resetWorkspace(); } - // Abandon the saved session and load the default new-project state - // (no workspace, a `main.cpp` tab with editorAPI.DEFAULT_SOURCE). + // Abandon the saved session and return to the empty no-workspace state. async function abandonForNewProject() { await clearPersistedSession(); await startNewProject(); diff --git a/src/ui/toolbar.js b/src/ui/toolbar.js index 7d55f3d..1d9c312 100644 --- a/src/ui/toolbar.js +++ b/src/ui/toolbar.js @@ -53,10 +53,6 @@ const _openTabs = new Map(); let _activeTabPath = null; /** When true, programmatic setValue calls do not trigger markDirty(true). */ let _loadingFile = false; -// Internal in-memory document identifier; never a workspace-relative path. -const UNSAVED_TAB_PATH = 'untitled:default'; -const UNSAVED_TAB_LABEL = 'unsaved file'; - // ── Session persistence callback ────────────────────────────────────────────── /** Optional callback supplied by app.js to persist the session after state changes. */ let _persistSession = null; @@ -613,14 +609,23 @@ async function reloadOverwrittenTabs(changedPaths) { } /** - * Load the default new-project state (no workspace, a single unsaved tab with - * `editorAPI.DEFAULT_SOURCE`). Unlike {@link actionNew} this skips the + * Load the empty new-project state. Unlike {@link actionNew} this skips the * unsaved-changes confirmation so it can drive the relaunch "Start new project" * path, where the prior session is being intentionally abandoned. */ export function resetToNewProject() { clearTransientProjectState(); - restoreNoWorkspaceSource(_editorAPI.DEFAULT_SOURCE ?? ''); + closeAllTabs(); + _fsAPI.newFile(); + clearWorkspaceMode(); + _terminalAPI.resetTerminalSession?.(null); + _fileName = ''; + _loadingFile = true; + _editorAPI.setValue(''); + _editorAPI.clearDiagnostics(); + _loadingFile = false; + const statusFile = document.getElementById('status-file'); + if (statusFile) statusFile.textContent = ''; } function clearTransientProjectState() { @@ -630,16 +635,6 @@ function clearTransientProjectState() { _editorAPI.clearDiagnostics?.(); } -/** Restore a source-only session into the same no-workspace tab state as a new project. */ -export function restoreNoWorkspaceSource(source) { - closeAllTabs(); - _fsAPI.newFile(); - clearWorkspaceMode(); - _terminalAPI.resetTerminalSession?.(null); - openTabForFile(UNSAVED_TAB_PATH, source); - markDirty(false); -} - async function actionSave() { try { if (_workspace && _activeTabPath && _fsAPI?.writeWorkspaceFile) { @@ -679,19 +674,18 @@ async function saveUntitledDocument() { setWorkspaceMode(result.snapshot ?? workspace); applyWorkspaceSnapshot(result.snapshot ?? workspace); - renameActiveTabPath(result.path); + openTabForFile(result.path, _editorAPI.getValue()); markDirty(false); _persistSession?.(); } async function actionSaveAs() { try { - if (!_workspace && _activeTabPath === UNSAVED_TAB_PATH) { + if (!_workspace && !_activeTabPath) { await saveUntitledDocument(); return; } - const suggestedName = _activeTabPath === UNSAVED_TAB_PATH ? 'main.cpp' : _fileName; - const name = await _fsAPI.saveFileAs(_editorAPI.getValue(), suggestedName); + const name = await _fsAPI.saveFileAs(_editorAPI.getValue(), _fileName || 'main.cpp'); if (name) { renameActiveTabPath(name); markDirty(false); @@ -843,7 +837,7 @@ function inferLanguage(path) { } function tabDisplayName(path) { - return path === UNSAVED_TAB_PATH ? UNSAVED_TAB_LABEL : workspaceBaseName(path) || path; + return workspaceBaseName(path) || path; } /** Returns true if any open tab has unsaved changes. */ @@ -1114,19 +1108,6 @@ function highlightWorkspaceFile(path) { if (active) active.classList.add('active'); } -async function openWorkspaceInitialFile(workspace) { - const file = pickInitialWorkspaceFile(workspace.entries); - if (!file) { - // No README.md at root – clear editor but open no tab automatically - _loadingFile = true; - _editorAPI.setValue(''); - _editorAPI.clearDiagnostics(); - _loadingFile = false; - return; - } - await openWorkspaceFile(file.path); -} - async function openWorkspaceFile(path) { if (_openTabs.has(path)) { switchToTab(path); @@ -1160,13 +1141,6 @@ async function openWorkspaceFile(path) { openTabForFile(path, content); } -function pickInitialWorkspaceFile(entries) { - // Only auto-open README.md if it exists at the workspace root - return entries.find( - (entry) => entry.kind === 'file' && entry.path.toLowerCase() === 'readme.md' - ) || null; -} - function showOpenError(err) { const kind = _workspace ? 'folder' : 'file'; alert(`Could not open ${kind}:\n${err.message}`); @@ -1274,7 +1248,13 @@ async function openFolderWorkspace() { clearTransientProjectState(); closeAllTabs(); setWorkspaceMode(workspace); - await openWorkspaceInitialFile(workspace); + _fileName = ''; + _loadingFile = true; + _editorAPI.setValue(''); + _editorAPI.clearDiagnostics(); + _loadingFile = false; + const statusFile = document.getElementById('status-file'); + if (statusFile) statusFile.textContent = ''; renderWorkspaceSidebar(workspace); _persistSession?.(); // persist immediately so the new workspace survives unload return true;