From 8e3308875273b8ab863b6f5ee7a526aa06bbd114 Mon Sep 17 00:00:00 2001 From: David Montero Date: Sat, 18 Jul 2026 08:24:06 +0200 Subject: [PATCH] fix(editor): sync URL after New workspace + sever project identity on .vlx import MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three stale-project-identity fixes from reviewing the New-workspace flow: - New workspace (web): handleNewClick cleared the workspace and the current project but left the browser on the old /user/slug URL — a refresh (or back-button pop) silently reloaded the OLD project over the fresh unsaved workspace. Now replaceState's to the localized /editor (replace, not push, so no back-entry points at the stale project route). - New workspace (desktop menu): same URL fix for the newProject menu action, which cleared identity but never left the project route. - .vlx import: importVlxFile mutated the stores WITHOUT clearing currentProject — with a saved project open, autosave saw the imported content as dirty edits on the old projectId and silently PUT the .vlx contents over the user's saved project (and pushed the clobber to GitHub on linked projects). Now severs identity first, same guard loadExample.ts already documents. --- frontend/src/desktop/menu.ts | 16 ++++++++++++++++ frontend/src/pages/EditorPage.tsx | 11 +++++++++++ frontend/src/utils/vlxFile.ts | 7 +++++++ 3 files changed, 34 insertions(+) diff --git a/frontend/src/desktop/menu.ts b/frontend/src/desktop/menu.ts index 97355647..f677163a 100644 --- a/frontend/src/desktop/menu.ts +++ b/frontend/src/desktop/menu.ts @@ -224,6 +224,22 @@ function newProject(): void { // Clear the compile output panel so old build logs don't carry over. compileLogs.clear(); + + // Leave whatever project URL we were on: staying there would reload the + // OLD project over this fresh workspace on refresh. replaceState (not + // navigateTo's pushState) so the back button can't pop to the stale + // project URL either; the popstate dispatch lets React Router render + // the plain editor route. + const cur = window.location.pathname; + const localeMatch = cur.match(/^\/([a-z]{2}(?:-[a-z]{2})?)\b/); + const prefix = localeMatch && LOCALES.includes(localeMatch[1] as Locale) + ? `/${localeMatch[1]}` + : ''; + const editorPath = `${prefix}/editor`; + if (cur !== editorPath) { + window.history.replaceState(null, '', editorPath); + window.dispatchEvent(new PopStateEvent('popstate')); + } } async function checkForUpdates(): Promise { diff --git a/frontend/src/pages/EditorPage.tsx b/frontend/src/pages/EditorPage.tsx index c614d4e4..0e2d00b4 100644 --- a/frontend/src/pages/EditorPage.tsx +++ b/frontend/src/pages/EditorPage.tsx @@ -6,6 +6,7 @@ import React, { useRef, useState, useCallback, useEffect, lazy, Suspense } from import { useTranslation } from 'react-i18next'; import { startSimulation } from '../simulation/spice/start'; import { useSEO } from '../utils/useSEO'; +import { getLocaleFromPath, localizedPath } from '../i18n/path'; import { restoreStashedWorkspace } from '../utils/workspaceDraft'; import { CodeEditor } from '../components/editor/CodeEditor'; import { EditorToolbar } from '../components/editor/EditorToolbar'; @@ -219,6 +220,16 @@ export const EditorPage: React.FC = () => { .getState() .addBoard('arduino-uno', DEFAULT_BOARD_POSITION.x, DEFAULT_BOARD_POSITION.y); useSimulatorStore.getState().setActiveBoardId(newId); + // The workspace no longer belongs to whatever project URL we were on — + // leaving it would silently reload the OLD project over this fresh + // workspace on refresh (and via the back button). replaceState, not + // pushState: a back-entry pointing at the stale project URL would + // remount the project route and cause exactly that reload. + const locale = getLocaleFromPath(window.location.pathname); + const editorPath = localizedPath('/editor', locale); + if (window.location.pathname !== editorPath) { + window.history.replaceState(null, '', editorPath); + } }, [t]); // Track mobile breakpoint diff --git a/frontend/src/utils/vlxFile.ts b/frontend/src/utils/vlxFile.ts index f050749d..c6cf5f92 100644 --- a/frontend/src/utils/vlxFile.ts +++ b/frontend/src/utils/vlxFile.ts @@ -34,6 +34,7 @@ import type { Component } from '../types/component'; import type { Wire } from '../types/wire'; import { useEditorStore, chipFileGroupId } from '../store/useEditorStore'; import { useSimulatorStore } from '../store/useSimulatorStore'; +import { useProjectStore } from '../store/useProjectStore'; const VLX_FORMAT = 'velxio-project'; const VLX_VERSION = 1; @@ -228,6 +229,12 @@ export async function parseVlxFile(file: File): Promise { */ export async function importVlxFile(file: File): Promise { const payload = await parseVlxFile(file); + // CRITICAL — sever the current project identity BEFORE mutating any store + // (same guard as loadExample.ts). With a saved project open, the auto-save + // hook would otherwise see the imported content as dirty edits on the OLD + // projectId and silently PUT the .vlx contents over the user's saved + // project (and push the clobber to GitHub when the project is linked). + useProjectStore.getState().clearCurrentProject(); useSimulatorStore.getState().loadProjectState({ boards: payload.boards as unknown as BoardInstance[], fileGroups: payload.fileGroups,