velxio/frontend/src/desktop/menu.ts

265 lines
9.0 KiB
TypeScript
Raw Normal View History

/**
* Native menubar event bridge.
*
* The Tauri shell (pro/desktop/src-tauri/src/menu.rs in velxio-prod)
* builds a Velxio / File / Edit / View / Help menubar. Internal items
* (Save .vlx, Open .vlx, Toggle Serial Monitor, Find, ) emit a
* `velxio://menu` event with `{ action: '<id>' }`. URL items (Docs,
* Examples, Discord, GitHub) are opened directly from Rust and don't
* reach this listener.
*
* Actions handled directly here (no further plumbing needed):
* - save-vlx, open-vlx triggerDownloadVlx / file picker
* - toggle-serial-monitor useSimulatorStore.toggleSerialMonitor()
* - check-for-updates tauri-plugin-updater check()
*
* Actions forwarded to whoever's listening as a window CustomEvent
* `velxio:menu:<action>`:
* - new-project, find-in-editor, toggle-file-explorer
*
* No-op outside Tauri (e.g. running the bundle in a regular browser
* for debugging) listen() returns a no-op when the global event
* API isn't present.
*/
import { listen } from './tauriBridge';
import { dlog } from './log';
import { triggerDownloadVlx, importVlxFile } from '../utils/vlxFile';
import { useSimulatorStore } from '../store/useSimulatorStore';
fix: 5 user-reported issues (#208 #209 #210 #211 #212) #208 — stale binary executes after compile error EditorToolbar.handleCompile: on failed compile, clear the active board's compiledProgram so a subsequent Run can't silently execute the previous successful build (which doesn't match the editor any more). The Run gate already short-circuits on !compiledProgram and forces a fresh compile. #209 — compile terminal kept stale messages across runs EditorToolbar.handleCompile: setCompileLogs([]) at the top of the handler. Previously logs from the prior compile lingered, making it hard to tell new errors / warnings apart from old ones. #210 — desktop File > New Project did nothing desktop/menu.ts: the menu action used to dispatch a CustomEvent nobody listened to. Replaced with a real `newProject()` function that stops the running simulation, removes every board (also drops the bridges + wires touching them), clears components / wires, loads the default Blink sketch into the editor, clears project metadata, and wipes the compile output. Confirms first if there's unsaved work on the canvas. #211 — deleting the only board made every other component unresponsive (wires still worked) SimulatorCanvas.tsx::interactionRunning: the old expression treated boards.length === 0 as "boardless electrical mode is running" — which suppressed the property dialog on click and made non-sensor components look frozen. Fixed by also requiring useElectricalStore.submittedNetlist !== '' before flipping to the boardless-running branch. SPICE has to have actually solved at least once for the mode to engage. #212 — ESP32 Support 404 with no actionable message desktop/Esp32QemuPrompt.tsx: catch the raw "download HTTP 404" / "not found" upstream error and reword it to "ESP32 support is not yet available for your platform. The Velxio team is preparing this build - try again in a few days, or use Arduino/RP2040 boards in the meantime." The real fix is server-side (the velxio team needs to publish a qemu-xtensa.tar.gz for the user's platform into the asset bucket and update esp32-qemu/latest.json). Tracked in project/desktop-agent-v040/ follow-ups. All five fixes verified with `tsc --noEmit` clean and the existing 25-test vitest suite green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 08:06:44 +07:00
import { useEditorStore } from '../store/useEditorStore';
import { useProjectStore } from '../store/useProjectStore';
import { useCompileLogsStore } from '../store/useCompileLogsStore';
import { showMessageDialog } from '../store/useMessageDialogStore';
feat(desktop): hide header strip, splash screen, native locale switcher Three QoL fixes for the Tauri shell: 1. Hide the entire AppHeader strip in VITE_DESKTOP, not just the marketing nav. The previous gate left the black bar painting over the editor with the brand + auto-save + share + auth slot, all of which are irrelevant in desktop (cloud Pro features, license is handled by DesktopWelcomePage, the title bar already says "Velxio Desktop"). Return null at the top so the editor takes the full window height. 2. Splash screen during sidecar boot + Monaco hydration. Cold launch was a 3-8 s black window — now there's an inline SVG logo, "Velxio" wordmark, slogan, animated spinner, and a "Starting local backend…" caption. Lives in index.html as a fixed-position overlay with display:none by default; the inline script reveals it only when `window.__TAURI__` is present, so web users never see it. main.tsx fades it out (250 ms ease-out) after two animation frames — guarantees React's first paint has committed before the handoff, no black flash. Self-contained: inline styles, inline SVG, inline CSS keyframes, zero external requests. 3. Native locale switcher under View → Language. Emits `velxio://menu` with action='set-locale' + the locale code; the desktop/menu.ts handler navigates via history.pushState + popstate so React Router picks it up without a hard reload (Monaco + simulator state preserved). Locale list mirrors i18n/config.ts::LOCALES. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 05:39:10 +07:00
import { switchLocale } from '../i18n/path';
import { LOCALES, type Locale } from '../i18n/config';
type MenuAction =
| 'new-project'
| 'save-vlx'
| 'open-vlx'
| 'find-in-editor'
| 'toggle-file-explorer'
| 'toggle-serial-monitor'
feat(desktop): hide header strip, splash screen, native locale switcher Three QoL fixes for the Tauri shell: 1. Hide the entire AppHeader strip in VITE_DESKTOP, not just the marketing nav. The previous gate left the black bar painting over the editor with the brand + auto-save + share + auth slot, all of which are irrelevant in desktop (cloud Pro features, license is handled by DesktopWelcomePage, the title bar already says "Velxio Desktop"). Return null at the top so the editor takes the full window height. 2. Splash screen during sidecar boot + Monaco hydration. Cold launch was a 3-8 s black window — now there's an inline SVG logo, "Velxio" wordmark, slogan, animated spinner, and a "Starting local backend…" caption. Lives in index.html as a fixed-position overlay with display:none by default; the inline script reveals it only when `window.__TAURI__` is present, so web users never see it. main.tsx fades it out (250 ms ease-out) after two animation frames — guarantees React's first paint has committed before the handoff, no black flash. Self-contained: inline styles, inline SVG, inline CSS keyframes, zero external requests. 3. Native locale switcher under View → Language. Emits `velxio://menu` with action='set-locale' + the locale code; the desktop/menu.ts handler navigates via history.pushState + popstate so React Router picks it up without a hard reload (Monaco + simulator state preserved). Locale list mirrors i18n/config.ts::LOCALES. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 05:39:10 +07:00
| 'check-for-updates'
feat(desktop): skip welcome screen, robust openExternal, in-app nav Three coordinated changes that fix the "Waiting for browser…" hang and unblock first-launch UX on the Tauri desktop build: 1. desktop/index.ts — DON'T mountWelcome unconditionally on first launch. Before, an empty keychain (no key yet) forced the welcome / sign-in screen on top of the editor, gating 100% of the app behind an account. Now the editor opens directly: compile + run + sim + save .vlx all work for free (they're upstream OSS features), and the license check still runs in the background just to populate state for the GraceBanner (which shows for invalid keys — locked, tampered, in soft/hard grace). Pro-only features (ESP32 QEMU download, agent IA) prompt for license at use time, where it actually matters. Matches the "try before you buy" expectation a desktop install creates. 2. desktop/tauriBridge.ts — rewrite `openExternal` to try every known IPC path in cascade order and log via the desktop debug file which one worked. The previous implementation invoked `plugin:shell|open` with `{ path: url }`, which silently failed (no ACL match + wrong arg shape) and fell back to `window.open`, which inside a Tauri webview is a no-op for external URLs — the browser never opened. New cascade: plugin:opener|open_url (paired with tauri-plugin-opener which ships in this revision), then plugin:shell|open with both `{ path, with: null }` and `{ url }` shapes, then the window.__TAURI__.shell / opener high-level wrappers that specific Tauri 2.x flag combos expose. Each attempt logged via the dlog helper so the next operator can see exactly which path was used (or that all failed) without devtools. 3. desktop/menu.ts — new `navigate-route` action type. Routes bundled in the SPA (DocsPage, ExamplesPage, AboutPage) that used to open velxio.dev in the system browser now navigate in-window via history.pushState + popstate (mirrors the locale-switch handler). Respects the current locale prefix so `/examples` from `/es/editor` lands at `/es/examples` instead of jumping back to English. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 03:57:46 +07:00
| 'set-locale'
| 'navigate-route';
interface MenuEventPayload {
action: MenuAction;
feat(desktop): hide header strip, splash screen, native locale switcher Three QoL fixes for the Tauri shell: 1. Hide the entire AppHeader strip in VITE_DESKTOP, not just the marketing nav. The previous gate left the black bar painting over the editor with the brand + auto-save + share + auth slot, all of which are irrelevant in desktop (cloud Pro features, license is handled by DesktopWelcomePage, the title bar already says "Velxio Desktop"). Return null at the top so the editor takes the full window height. 2. Splash screen during sidecar boot + Monaco hydration. Cold launch was a 3-8 s black window — now there's an inline SVG logo, "Velxio" wordmark, slogan, animated spinner, and a "Starting local backend…" caption. Lives in index.html as a fixed-position overlay with display:none by default; the inline script reveals it only when `window.__TAURI__` is present, so web users never see it. main.tsx fades it out (250 ms ease-out) after two animation frames — guarantees React's first paint has committed before the handoff, no black flash. Self-contained: inline styles, inline SVG, inline CSS keyframes, zero external requests. 3. Native locale switcher under View → Language. Emits `velxio://menu` with action='set-locale' + the locale code; the desktop/menu.ts handler navigates via history.pushState + popstate so React Router picks it up without a hard reload (Monaco + simulator state preserved). Locale list mirrors i18n/config.ts::LOCALES. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 05:39:10 +07:00
// Only present when action='set-locale'. Matches an entry in
// i18n/config.ts::LOCALES.
locale?: string;
feat(desktop): skip welcome screen, robust openExternal, in-app nav Three coordinated changes that fix the "Waiting for browser…" hang and unblock first-launch UX on the Tauri desktop build: 1. desktop/index.ts — DON'T mountWelcome unconditionally on first launch. Before, an empty keychain (no key yet) forced the welcome / sign-in screen on top of the editor, gating 100% of the app behind an account. Now the editor opens directly: compile + run + sim + save .vlx all work for free (they're upstream OSS features), and the license check still runs in the background just to populate state for the GraceBanner (which shows for invalid keys — locked, tampered, in soft/hard grace). Pro-only features (ESP32 QEMU download, agent IA) prompt for license at use time, where it actually matters. Matches the "try before you buy" expectation a desktop install creates. 2. desktop/tauriBridge.ts — rewrite `openExternal` to try every known IPC path in cascade order and log via the desktop debug file which one worked. The previous implementation invoked `plugin:shell|open` with `{ path: url }`, which silently failed (no ACL match + wrong arg shape) and fell back to `window.open`, which inside a Tauri webview is a no-op for external URLs — the browser never opened. New cascade: plugin:opener|open_url (paired with tauri-plugin-opener which ships in this revision), then plugin:shell|open with both `{ path, with: null }` and `{ url }` shapes, then the window.__TAURI__.shell / opener high-level wrappers that specific Tauri 2.x flag combos expose. Each attempt logged via the dlog helper so the next operator can see exactly which path was used (or that all failed) without devtools. 3. desktop/menu.ts — new `navigate-route` action type. Routes bundled in the SPA (DocsPage, ExamplesPage, AboutPage) that used to open velxio.dev in the system browser now navigate in-window via history.pushState + popstate (mirrors the locale-switch handler). Respects the current locale prefix so `/examples` from `/es/editor` lands at `/es/examples` instead of jumping back to English. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 03:57:46 +07:00
// Only present when action='navigate-route'. Absolute pathname
// (e.g. '/examples', '/docs', '/about') — navigated via React
// Router so the current locale prefix gets applied.
route?: string;
}
let installed = false;
export async function installDesktopMenuListener(): Promise<void> {
if (installed) return;
installed = true;
await listen<MenuEventPayload>('velxio://menu', (event) => {
dlog('menu event', event.payload);
feat(desktop): hide header strip, splash screen, native locale switcher Three QoL fixes for the Tauri shell: 1. Hide the entire AppHeader strip in VITE_DESKTOP, not just the marketing nav. The previous gate left the black bar painting over the editor with the brand + auto-save + share + auth slot, all of which are irrelevant in desktop (cloud Pro features, license is handled by DesktopWelcomePage, the title bar already says "Velxio Desktop"). Return null at the top so the editor takes the full window height. 2. Splash screen during sidecar boot + Monaco hydration. Cold launch was a 3-8 s black window — now there's an inline SVG logo, "Velxio" wordmark, slogan, animated spinner, and a "Starting local backend…" caption. Lives in index.html as a fixed-position overlay with display:none by default; the inline script reveals it only when `window.__TAURI__` is present, so web users never see it. main.tsx fades it out (250 ms ease-out) after two animation frames — guarantees React's first paint has committed before the handoff, no black flash. Self-contained: inline styles, inline SVG, inline CSS keyframes, zero external requests. 3. Native locale switcher under View → Language. Emits `velxio://menu` with action='set-locale' + the locale code; the desktop/menu.ts handler navigates via history.pushState + popstate so React Router picks it up without a hard reload (Monaco + simulator state preserved). Locale list mirrors i18n/config.ts::LOCALES. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 05:39:10 +07:00
void handle(event.payload.action, event.payload);
});
}
feat(desktop): hide header strip, splash screen, native locale switcher Three QoL fixes for the Tauri shell: 1. Hide the entire AppHeader strip in VITE_DESKTOP, not just the marketing nav. The previous gate left the black bar painting over the editor with the brand + auto-save + share + auth slot, all of which are irrelevant in desktop (cloud Pro features, license is handled by DesktopWelcomePage, the title bar already says "Velxio Desktop"). Return null at the top so the editor takes the full window height. 2. Splash screen during sidecar boot + Monaco hydration. Cold launch was a 3-8 s black window — now there's an inline SVG logo, "Velxio" wordmark, slogan, animated spinner, and a "Starting local backend…" caption. Lives in index.html as a fixed-position overlay with display:none by default; the inline script reveals it only when `window.__TAURI__` is present, so web users never see it. main.tsx fades it out (250 ms ease-out) after two animation frames — guarantees React's first paint has committed before the handoff, no black flash. Self-contained: inline styles, inline SVG, inline CSS keyframes, zero external requests. 3. Native locale switcher under View → Language. Emits `velxio://menu` with action='set-locale' + the locale code; the desktop/menu.ts handler navigates via history.pushState + popstate so React Router picks it up without a hard reload (Monaco + simulator state preserved). Locale list mirrors i18n/config.ts::LOCALES. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 05:39:10 +07:00
async function handle(action: MenuAction, payload?: MenuEventPayload): Promise<void> {
switch (action) {
case 'save-vlx':
triggerDownloadVlx();
return;
case 'open-vlx':
pickAndImportVlx();
return;
case 'toggle-serial-monitor':
useSimulatorStore.getState().toggleSerialMonitor();
return;
case 'new-project':
fix: 5 user-reported issues (#208 #209 #210 #211 #212) #208 — stale binary executes after compile error EditorToolbar.handleCompile: on failed compile, clear the active board's compiledProgram so a subsequent Run can't silently execute the previous successful build (which doesn't match the editor any more). The Run gate already short-circuits on !compiledProgram and forces a fresh compile. #209 — compile terminal kept stale messages across runs EditorToolbar.handleCompile: setCompileLogs([]) at the top of the handler. Previously logs from the prior compile lingered, making it hard to tell new errors / warnings apart from old ones. #210 — desktop File > New Project did nothing desktop/menu.ts: the menu action used to dispatch a CustomEvent nobody listened to. Replaced with a real `newProject()` function that stops the running simulation, removes every board (also drops the bridges + wires touching them), clears components / wires, loads the default Blink sketch into the editor, clears project metadata, and wipes the compile output. Confirms first if there's unsaved work on the canvas. #211 — deleting the only board made every other component unresponsive (wires still worked) SimulatorCanvas.tsx::interactionRunning: the old expression treated boards.length === 0 as "boardless electrical mode is running" — which suppressed the property dialog on click and made non-sensor components look frozen. Fixed by also requiring useElectricalStore.submittedNetlist !== '' before flipping to the boardless-running branch. SPICE has to have actually solved at least once for the mode to engage. #212 — ESP32 Support 404 with no actionable message desktop/Esp32QemuPrompt.tsx: catch the raw "download HTTP 404" / "not found" upstream error and reword it to "ESP32 support is not yet available for your platform. The Velxio team is preparing this build - try again in a few days, or use Arduino/RP2040 boards in the meantime." The real fix is server-side (the velxio team needs to publish a qemu-xtensa.tar.gz for the user's platform into the asset bucket and update esp32-qemu/latest.json). Tracked in project/desktop-agent-v040/ follow-ups. All five fixes verified with `tsc --noEmit` clean and the existing 25-test vitest suite green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 08:06:44 +07:00
newProject();
return;
case 'find-in-editor':
case 'toggle-file-explorer':
window.dispatchEvent(new CustomEvent(`velxio:menu:${action}`));
return;
case 'check-for-updates':
await checkForUpdates();
return;
feat(desktop): hide header strip, splash screen, native locale switcher Three QoL fixes for the Tauri shell: 1. Hide the entire AppHeader strip in VITE_DESKTOP, not just the marketing nav. The previous gate left the black bar painting over the editor with the brand + auto-save + share + auth slot, all of which are irrelevant in desktop (cloud Pro features, license is handled by DesktopWelcomePage, the title bar already says "Velxio Desktop"). Return null at the top so the editor takes the full window height. 2. Splash screen during sidecar boot + Monaco hydration. Cold launch was a 3-8 s black window — now there's an inline SVG logo, "Velxio" wordmark, slogan, animated spinner, and a "Starting local backend…" caption. Lives in index.html as a fixed-position overlay with display:none by default; the inline script reveals it only when `window.__TAURI__` is present, so web users never see it. main.tsx fades it out (250 ms ease-out) after two animation frames — guarantees React's first paint has committed before the handoff, no black flash. Self-contained: inline styles, inline SVG, inline CSS keyframes, zero external requests. 3. Native locale switcher under View → Language. Emits `velxio://menu` with action='set-locale' + the locale code; the desktop/menu.ts handler navigates via history.pushState + popstate so React Router picks it up without a hard reload (Monaco + simulator state preserved). Locale list mirrors i18n/config.ts::LOCALES. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 05:39:10 +07:00
case 'set-locale':
if (payload?.locale) setLocale(payload.locale);
return;
feat(desktop): skip welcome screen, robust openExternal, in-app nav Three coordinated changes that fix the "Waiting for browser…" hang and unblock first-launch UX on the Tauri desktop build: 1. desktop/index.ts — DON'T mountWelcome unconditionally on first launch. Before, an empty keychain (no key yet) forced the welcome / sign-in screen on top of the editor, gating 100% of the app behind an account. Now the editor opens directly: compile + run + sim + save .vlx all work for free (they're upstream OSS features), and the license check still runs in the background just to populate state for the GraceBanner (which shows for invalid keys — locked, tampered, in soft/hard grace). Pro-only features (ESP32 QEMU download, agent IA) prompt for license at use time, where it actually matters. Matches the "try before you buy" expectation a desktop install creates. 2. desktop/tauriBridge.ts — rewrite `openExternal` to try every known IPC path in cascade order and log via the desktop debug file which one worked. The previous implementation invoked `plugin:shell|open` with `{ path: url }`, which silently failed (no ACL match + wrong arg shape) and fell back to `window.open`, which inside a Tauri webview is a no-op for external URLs — the browser never opened. New cascade: plugin:opener|open_url (paired with tauri-plugin-opener which ships in this revision), then plugin:shell|open with both `{ path, with: null }` and `{ url }` shapes, then the window.__TAURI__.shell / opener high-level wrappers that specific Tauri 2.x flag combos expose. Each attempt logged via the dlog helper so the next operator can see exactly which path was used (or that all failed) without devtools. 3. desktop/menu.ts — new `navigate-route` action type. Routes bundled in the SPA (DocsPage, ExamplesPage, AboutPage) that used to open velxio.dev in the system browser now navigate in-window via history.pushState + popstate (mirrors the locale-switch handler). Respects the current locale prefix so `/examples` from `/es/editor` lands at `/es/examples` instead of jumping back to English. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 03:57:46 +07:00
case 'navigate-route':
if (payload?.route) navigateTo(payload.route);
return;
feat(desktop): hide header strip, splash screen, native locale switcher Three QoL fixes for the Tauri shell: 1. Hide the entire AppHeader strip in VITE_DESKTOP, not just the marketing nav. The previous gate left the black bar painting over the editor with the brand + auto-save + share + auth slot, all of which are irrelevant in desktop (cloud Pro features, license is handled by DesktopWelcomePage, the title bar already says "Velxio Desktop"). Return null at the top so the editor takes the full window height. 2. Splash screen during sidecar boot + Monaco hydration. Cold launch was a 3-8 s black window — now there's an inline SVG logo, "Velxio" wordmark, slogan, animated spinner, and a "Starting local backend…" caption. Lives in index.html as a fixed-position overlay with display:none by default; the inline script reveals it only when `window.__TAURI__` is present, so web users never see it. main.tsx fades it out (250 ms ease-out) after two animation frames — guarantees React's first paint has committed before the handoff, no black flash. Self-contained: inline styles, inline SVG, inline CSS keyframes, zero external requests. 3. Native locale switcher under View → Language. Emits `velxio://menu` with action='set-locale' + the locale code; the desktop/menu.ts handler navigates via history.pushState + popstate so React Router picks it up without a hard reload (Monaco + simulator state preserved). Locale list mirrors i18n/config.ts::LOCALES. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 05:39:10 +07:00
}
}
feat(desktop): skip welcome screen, robust openExternal, in-app nav Three coordinated changes that fix the "Waiting for browser…" hang and unblock first-launch UX on the Tauri desktop build: 1. desktop/index.ts — DON'T mountWelcome unconditionally on first launch. Before, an empty keychain (no key yet) forced the welcome / sign-in screen on top of the editor, gating 100% of the app behind an account. Now the editor opens directly: compile + run + sim + save .vlx all work for free (they're upstream OSS features), and the license check still runs in the background just to populate state for the GraceBanner (which shows for invalid keys — locked, tampered, in soft/hard grace). Pro-only features (ESP32 QEMU download, agent IA) prompt for license at use time, where it actually matters. Matches the "try before you buy" expectation a desktop install creates. 2. desktop/tauriBridge.ts — rewrite `openExternal` to try every known IPC path in cascade order and log via the desktop debug file which one worked. The previous implementation invoked `plugin:shell|open` with `{ path: url }`, which silently failed (no ACL match + wrong arg shape) and fell back to `window.open`, which inside a Tauri webview is a no-op for external URLs — the browser never opened. New cascade: plugin:opener|open_url (paired with tauri-plugin-opener which ships in this revision), then plugin:shell|open with both `{ path, with: null }` and `{ url }` shapes, then the window.__TAURI__.shell / opener high-level wrappers that specific Tauri 2.x flag combos expose. Each attempt logged via the dlog helper so the next operator can see exactly which path was used (or that all failed) without devtools. 3. desktop/menu.ts — new `navigate-route` action type. Routes bundled in the SPA (DocsPage, ExamplesPage, AboutPage) that used to open velxio.dev in the system browser now navigate in-window via history.pushState + popstate (mirrors the locale-switch handler). Respects the current locale prefix so `/examples` from `/es/editor` lands at `/es/examples` instead of jumping back to English. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 03:57:46 +07:00
function navigateTo(route: string): void {
// Prefix with the current locale if we're on a non-default one,
// so `/examples` from `/es/editor` lands at `/es/examples` instead
// of switching back to English. Mirrors how LanguageSwitcher does it.
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 normalised = route.startsWith('/') ? route : `/${route}`;
const next = `${prefix}${normalised}`;
if (next === cur) return;
// history.pushState + popstate keeps React Router happy without
// reloading the SPA (Monaco state, simulator state, sidecar
// connection all preserved).
window.history.pushState(null, '', next);
window.dispatchEvent(new PopStateEvent('popstate'));
}
feat(desktop): hide header strip, splash screen, native locale switcher Three QoL fixes for the Tauri shell: 1. Hide the entire AppHeader strip in VITE_DESKTOP, not just the marketing nav. The previous gate left the black bar painting over the editor with the brand + auto-save + share + auth slot, all of which are irrelevant in desktop (cloud Pro features, license is handled by DesktopWelcomePage, the title bar already says "Velxio Desktop"). Return null at the top so the editor takes the full window height. 2. Splash screen during sidecar boot + Monaco hydration. Cold launch was a 3-8 s black window — now there's an inline SVG logo, "Velxio" wordmark, slogan, animated spinner, and a "Starting local backend…" caption. Lives in index.html as a fixed-position overlay with display:none by default; the inline script reveals it only when `window.__TAURI__` is present, so web users never see it. main.tsx fades it out (250 ms ease-out) after two animation frames — guarantees React's first paint has committed before the handoff, no black flash. Self-contained: inline styles, inline SVG, inline CSS keyframes, zero external requests. 3. Native locale switcher under View → Language. Emits `velxio://menu` with action='set-locale' + the locale code; the desktop/menu.ts handler navigates via history.pushState + popstate so React Router picks it up without a hard reload (Monaco + simulator state preserved). Locale list mirrors i18n/config.ts::LOCALES. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 05:39:10 +07:00
function setLocale(locale: string): void {
// Defensive: ignore unknown locales coming from the menu so a
// stale shell doesn't navigate to a broken URL.
if (!(LOCALES as readonly string[]).includes(locale)) {
dlog('set-locale: ignoring unknown locale', { locale });
return;
}
const target = locale as Locale;
const next =
switchLocale(window.location.pathname, target) +
window.location.search +
window.location.hash;
if (next === window.location.pathname + window.location.search + window.location.hash) {
return;
}
feat(desktop): hide header strip, splash screen, native locale switcher Three QoL fixes for the Tauri shell: 1. Hide the entire AppHeader strip in VITE_DESKTOP, not just the marketing nav. The previous gate left the black bar painting over the editor with the brand + auto-save + share + auth slot, all of which are irrelevant in desktop (cloud Pro features, license is handled by DesktopWelcomePage, the title bar already says "Velxio Desktop"). Return null at the top so the editor takes the full window height. 2. Splash screen during sidecar boot + Monaco hydration. Cold launch was a 3-8 s black window — now there's an inline SVG logo, "Velxio" wordmark, slogan, animated spinner, and a "Starting local backend…" caption. Lives in index.html as a fixed-position overlay with display:none by default; the inline script reveals it only when `window.__TAURI__` is present, so web users never see it. main.tsx fades it out (250 ms ease-out) after two animation frames — guarantees React's first paint has committed before the handoff, no black flash. Self-contained: inline styles, inline SVG, inline CSS keyframes, zero external requests. 3. Native locale switcher under View → Language. Emits `velxio://menu` with action='set-locale' + the locale code; the desktop/menu.ts handler navigates via history.pushState + popstate so React Router picks it up without a hard reload (Monaco + simulator state preserved). Locale list mirrors i18n/config.ts::LOCALES. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 05:39:10 +07:00
// history.pushState + popstate lets React Router pick the change up
// without a full reload, preserving the editor state. Reload would
// re-spawn the sidecar handshake and lose Monaco/sim state for ~5s.
window.history.pushState(null, '', next);
window.dispatchEvent(new PopStateEvent('popstate'));
}
function pickAndImportVlx(): void {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.vlx,application/json';
input.style.display = 'none';
document.body.appendChild(input);
input.addEventListener('change', async () => {
const file = input.files?.[0];
if (file) {
try {
await importVlxFile(file);
} catch (err) {
showMessageDialog(`Failed to open .vlx: ${(err as Error).message}`, {
kind: 'error',
});
}
}
document.body.removeChild(input);
});
input.click();
}
fix: 5 user-reported issues (#208 #209 #210 #211 #212) #208 — stale binary executes after compile error EditorToolbar.handleCompile: on failed compile, clear the active board's compiledProgram so a subsequent Run can't silently execute the previous successful build (which doesn't match the editor any more). The Run gate already short-circuits on !compiledProgram and forces a fresh compile. #209 — compile terminal kept stale messages across runs EditorToolbar.handleCompile: setCompileLogs([]) at the top of the handler. Previously logs from the prior compile lingered, making it hard to tell new errors / warnings apart from old ones. #210 — desktop File > New Project did nothing desktop/menu.ts: the menu action used to dispatch a CustomEvent nobody listened to. Replaced with a real `newProject()` function that stops the running simulation, removes every board (also drops the bridges + wires touching them), clears components / wires, loads the default Blink sketch into the editor, clears project metadata, and wipes the compile output. Confirms first if there's unsaved work on the canvas. #211 — deleting the only board made every other component unresponsive (wires still worked) SimulatorCanvas.tsx::interactionRunning: the old expression treated boards.length === 0 as "boardless electrical mode is running" — which suppressed the property dialog on click and made non-sensor components look frozen. Fixed by also requiring useElectricalStore.submittedNetlist !== '' before flipping to the boardless-running branch. SPICE has to have actually solved at least once for the mode to engage. #212 — ESP32 Support 404 with no actionable message desktop/Esp32QemuPrompt.tsx: catch the raw "download HTTP 404" / "not found" upstream error and reword it to "ESP32 support is not yet available for your platform. The Velxio team is preparing this build - try again in a few days, or use Arduino/RP2040 boards in the meantime." The real fix is server-side (the velxio team needs to publish a qemu-xtensa.tar.gz for the user's platform into the asset bucket and update esp32-qemu/latest.json). Tracked in project/desktop-agent-v040/ follow-ups. All five fixes verified with `tsc --noEmit` clean and the existing 25-test vitest suite green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 08:06:44 +07:00
/**
* Wipe the current workspace and start from the default Blink sketch +
* empty canvas. Issue #210: the menu action used to just dispatch a
* CustomEvent that nobody listened to.
*
* Confirms first if the user has unsaved changes (any modified file
* or any component on the canvas). The Tauri-side menu can't show a
* confirm dialog cheaply, so we use the browser-native `confirm()`
* here fine for the desktop bundle where it renders as a modal.
*/
function newProject(): void {
const sim = useSimulatorStore.getState();
const editor = useEditorStore.getState();
const project = useProjectStore.getState();
const compileLogs = useCompileLogsStore.getState();
const hasWork =
sim.boards.length > 0 ||
sim.components.length > 0 ||
sim.wires.length > 0 ||
editor.files.some((f) => f.modified) ||
project.currentProject !== null;
if (hasWork) {
const ok = window.confirm(
'Start a new project? Any unsaved changes will be lost.',
);
if (!ok) return;
}
// Stop any running simulation first so workers / bridges shut down
// cleanly. Idempotent — no-op if nothing is running.
if (sim.running) {
sim.stopSimulation();
}
// Drop every board (also disconnects its bridges + removes wires
// touching it). Iterate over a snapshot copy since removeBoard
// mutates the array.
for (const board of [...sim.boards]) {
sim.removeBoard(board.id);
}
// Any non-board components + wires that weren't connected to a
// board still need to go.
sim.setComponents([]);
sim.setWires([]);
// Reset the editor to the default Blink sketch. loadFiles takes
// a {name, content}[] and rebuilds the file list, picking the
// first .ino as active.
editor.loadFiles([
{
name: 'sketch.ino',
content:
'// Arduino Blink Example\nvoid setup() {\n pinMode(LED_BUILTIN, OUTPUT);\n}\n\nvoid loop() {\n digitalWrite(LED_BUILTIN, HIGH);\n delay(1000);\n digitalWrite(LED_BUILTIN, LOW);\n delay(1000);\n}\n',
},
]);
// Drop project metadata so the next Save .vlx doesn't reuse the
// previous project's slug / name.
project.clearCurrentProject();
// 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'));
}
fix: 5 user-reported issues (#208 #209 #210 #211 #212) #208 — stale binary executes after compile error EditorToolbar.handleCompile: on failed compile, clear the active board's compiledProgram so a subsequent Run can't silently execute the previous successful build (which doesn't match the editor any more). The Run gate already short-circuits on !compiledProgram and forces a fresh compile. #209 — compile terminal kept stale messages across runs EditorToolbar.handleCompile: setCompileLogs([]) at the top of the handler. Previously logs from the prior compile lingered, making it hard to tell new errors / warnings apart from old ones. #210 — desktop File > New Project did nothing desktop/menu.ts: the menu action used to dispatch a CustomEvent nobody listened to. Replaced with a real `newProject()` function that stops the running simulation, removes every board (also drops the bridges + wires touching them), clears components / wires, loads the default Blink sketch into the editor, clears project metadata, and wipes the compile output. Confirms first if there's unsaved work on the canvas. #211 — deleting the only board made every other component unresponsive (wires still worked) SimulatorCanvas.tsx::interactionRunning: the old expression treated boards.length === 0 as "boardless electrical mode is running" — which suppressed the property dialog on click and made non-sensor components look frozen. Fixed by also requiring useElectricalStore.submittedNetlist !== '' before flipping to the boardless-running branch. SPICE has to have actually solved at least once for the mode to engage. #212 — ESP32 Support 404 with no actionable message desktop/Esp32QemuPrompt.tsx: catch the raw "download HTTP 404" / "not found" upstream error and reword it to "ESP32 support is not yet available for your platform. The Velxio team is preparing this build - try again in a few days, or use Arduino/RP2040 boards in the meantime." The real fix is server-side (the velxio team needs to publish a qemu-xtensa.tar.gz for the user's platform into the asset bucket and update esp32-qemu/latest.json). Tracked in project/desktop-agent-v040/ follow-ups. All five fixes verified with `tsc --noEmit` clean and the existing 25-test vitest suite green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 08:06:44 +07:00
}
async function checkForUpdates(): Promise<void> {
try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const updater = (window as any).__TAURI__?.updater;
if (!updater?.check) {
showMessageDialog('Update plugin not available in this build.');
return;
}
const update = await updater.check();
if (update) {
await update.downloadAndInstall();
} else {
showMessageDialog('Velxio Desktop is up to date.', { kind: 'success' });
}
} catch (err) {
showMessageDialog(`Update check failed: ${(err as Error).message}`, {
kind: 'error',
});
}
}