velxio/frontend/src/pages/EditorPage.tsx

687 lines
26 KiB
TypeScript
Raw Normal View History

/**
* Editor Page main editor + simulator with resizable panels
*/
import React, { useRef, useState, useCallback, useEffect, lazy, Suspense } from 'react';
feat(i18n): translate AdminPage + UserProfile + Pricing + EditorPage shell (Editor block 10 — final cleanup) Closes the Phase 2 i18n rollout. Every visitor- and user-facing surface velxio renders in normal use now reads from t(). AdminPage (admin-only) - Header (panel title, logout) and the four tabs (Dashboard / Users / Projects / Boards). - Setup screen for first-admin creation (title, body, password fields + mismatch error + create-admin button). - Not-admin gate page. - EditUserModal (title, four labels, admin/active toggles, cancel/save). - UsersTab: search placeholder, count pluralisation, all 12 table columns, Activity / Edit / Delete actions, empty state, delete-confirm prompt with username interpolation. - ProjectsTab: search placeholder, count pluralisation, all 9 table columns, public/private badge labels, delete action + confirm with project-name interpolation, empty state. - All error messages (load failed / save failed / delete failed) fall back through t(). UserProfilePage - "New project" CTA, loading + empty + not-found states, "Private" project badge, "Copy shareable link" tooltip. - The /editor link uses localize() so /es/<username>'s "New project" button stays in Spanish. PricingPlaceholder - Title + the two paragraphs (self-hosted note + hosted Pro tier note + GitHub source note). Inline links wrapped via the Trans component so the link surface stays clickable in every locale without each translation having to re-write the HTML. EditorPage shell - Mobile bottom-tab labels (Code / Circuit), file-explorer toggle (Show / Hide), View mode aria-label, view-mode segmented control labels (Code / Both / Circuit), and the three "Drag to resize" handle tooltips on the panel splitters. Translations - en.json hand-curated for the new keys. - All 8 non-English locales auto-translated via the existing `npm run translate:i18n` pipeline (DeepSeek, ~5 min for the whole bundle, sameShape() validates each output before write). This closes Phase 2 of i18n. Phase 3 (DocsPage prose, AboutPage long-form paragraphs, the 15 SEO landing pages) is deliberately deferred — Docs/About are best handled by extracting the prose into JSON keys and running the same script, while the SEO pages are intentionally optimised for English keyword targeting and should not be machine-translated en masse.
2026-05-09 22:49:13 +07:00
import { useTranslation } from 'react-i18next';
feat(sim): Phase 1c G+F3 — retire legacy CircuitScheduler / eecircuit-engine The mixed-mode migration's endgame. After this commit there is ONE SPICE solver path in the codebase — the vendored ngspice WASM via SolverPort, behind both NgSpiceWorkerAdapter (production browser) and NgSpiceNodeAdapter (Vitest Node). Zero hybrids; zero legacy left to maintain. Deleted production files: • simulation/spice/CircuitScheduler.ts (200ms-poll legacy) • simulation/spice/SpiceEngine.ts (eecircuit-engine wrap) • simulation/spice/SpiceEngine.lazy.ts (lazy code-split) • simulation/spice/subscribeToStore.ts (legacy solve loop) • simulation/spice/connectLegacySolverToMixedMode.ts (bridge) • simulation/spice/connectMixedModeSchedulerToStore.ts (feature flag) Deleted tests (no longer cover any live code): • connect-legacy-solver-to-mixed-mode.test.ts • connect-mixed-mode-scheduler-to-store.test.ts • spice-rectifier-live-bootstrap.test.ts Migrated 6 tests off the deleted `circuitScheduler.solveNow` API to the new `__tests__/helpers/solveInput.ts` (same shape, backed by NgSpiceNodeAdapter). `useElectricalStore` rewritten as a pure state container: • setSolveResult(snapshot) — atomic publish from the service • paused / setPaused — UI control unchanged • reset — project unload • REMOVED: triggerSolve, solveNow, setDebounceMs, scheduler hook • REMOVED: dependency on SpiceEngine.lazy preload EditorPage now mounts a single `startSimulation()` from `simulation/spice/start.ts`, which constructs CircuitSimulationService + ADC bridge + MCU edge bridge. Four useEffect calls collapsed to one. `circuitVerifier.ts` (production) and `runNetlist.ts` use an environment-aware factory: Web Worker in browser, in-proc WASM in Node tests. `/* @vite-ignore */` keeps the Node adapter chain (node:fs, node:url) out of the browser bundle while still letting Node resolve it dynamically. Removed `eecircuit-engine` from package.json dependencies. `collectPinStates` extracted to its own module so the service doesn't depend on the (now deleted) subscribeToStore.ts. Verification: • 1392/1392 tests pass across 103 files (28 pre-existing skips). • `tsc --noEmit` clean. • `vite build` succeeds (27 s, only the existing chunk-size warning that pre-dates this work). Phase 1c — COMPLETE. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 02:46:34 +07:00
import { startSimulation } from '../simulation/spice/start';
import { useSEO } from '../utils/useSEO';
import { CodeEditor } from '../components/editor/CodeEditor';
import { EditorToolbar } from '../components/editor/EditorToolbar';
import { FileTabs } from '../components/editor/FileTabs';
import { FileExplorer } from '../components/editor/FileExplorer';
// Lazy-load Pi workspace so xterm.js isn't in the main bundle
const RaspberryPiWorkspace = lazy(() =>
import('../components/raspberry-pi/RaspberryPiWorkspace').then((m) => ({
default: m.RaspberryPiWorkspace,
})),
);
import { CompilationConsole } from '../components/editor/CompilationConsole';
import { SimulatorCanvas } from '../components/simulator/SimulatorCanvas';
import { SerialMonitor } from '../components/simulator/SerialMonitor';
import { Oscilloscope } from '../components/simulator/Oscilloscope';
import { AppHeader } from '../components/layout/AppHeader';
refactor(oss-split): remove auth/admin/profile frontend from OSS Phase 3 of the OSS / pro split — frontend side. Phase 2 already moved the auth/DB stack out of the OSS backend; this commit does the same for the React app. After this, the OSS image is editor + simulator + landing + docs only. What moved to the private overlay (pro/frontend/src/pro/): pages/{Login,Register,ForgotPassword,ResetPassword}Page.tsx pages/{Admin,UserProfile,Project,ProjectById}Page.tsx components/admin/{AdminBoardsTab,AdminDashboardTab,UserActivityModal}.tsx components/layout/{SaveProjectModal,LoginPromptModal}.tsx services/{authService,adminService}.ts store/useAuthStore.ts hooks/autoSaveImpl.ts New seams added so OSS components stay decoupled: * lib/proRoutes.ts — registerProRoutes()/useProRoutes() via useSyncExternalStore. mountPro() injects the moved pages at runtime; App.tsx subscribes to the registry, so registration after the initial render re-renders without a Not-Found flash. * lib/proSession.ts — registerSessionCheck()/triggerSessionCheck(). App.tsx fires this on mount instead of useAuthStore.checkSession(); pure OSS no-ops. * lib/proSaveAction.ts — installSaveActionImpl()/triggerSaveAction(). EditorPage's Save button dispatches through this; the overlay decides whether to show SaveProjectModal or LoginPromptModal based on auth state. In OSS without an overlay it's a no-op today; in Phase 4 of the split it becomes the .vlx Export entry point. OSS-side rewrites: * App.tsx drops the 8 page imports + 8 route entries; uses triggerSessionCheck() instead of useAuthStore directly. * AppHeader.tsx drops the user/login/register block entirely. The header-auth slot (introduced in Phase 1) now stays empty in OSS and gets filled by the overlay's portal mount. * EditorPage.tsx drops useAuthStore + SaveProjectModal + LoginPromptModal imports. The Save handler is now triggerSaveAction(). * LandingPage.tsx drops the dead UserMenu component (defined but never rendered) + its useAuthStore imports. * main.tsx drops the side-effect import of hooks/autoSaveImpl — the impl lives in pro now and self-registers via mountPro(). Build config: * vite.config.ts adds @velxio alias → src/. Lets the overlay import upstream modules (lib/proRoutes etc.) by stable name regardless of whether it's symlinked (local dev) or COPYed (Docker). * preserveSymlinks now gated on VITE_PRO_BUILD only (not on serve mode). Needed so Rollup keeps the overlay logically inside src/pro/ during local junction-based builds. Build verification: * OSS-only: 20-ish routes, no /login, /admin, /:username — 285 SEO pages prerendered. Bundle drops ~80-120 KB. * OSS + overlay: full 38 routes (30 upstream + 8 from registerProRoutes), HeaderAuth dropdown injected via slot, save action wired to the overlay's modal flow. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 01:31:12 +07:00
import { triggerSaveAction } from '../lib/proSaveAction';
import { GitHubStarBanner } from '../components/layout/GitHubStarBanner';
import { useSimulatorStore, DEFAULT_BOARD_POSITION } from '../store/useSimulatorStore';
feat(editor): view-mode toggle, agent-chat slot, toolbar polish Changes that ship to OSS — all benign for self-hosters, but most are extension points the velxio-prod overlay (and any private fork) needs to plug an in-editor AI chat into the page. Editor: - 3-way view-mode toggle (code / both / circuit) in the unified toolbar. Lets users hide a pane to give a right-docked sidebar (e.g. the AI chat overlay) more breathing room. Persisted in useEditorStore. - Default file explorer narrower (210 → 165 px); min 110. - Removed the redundant `tb-board-pill` (icon + "Editing: X" tooltip); the BoardSelector dropdown elsewhere already shows the active board. - Inlined Import/Export/Upload-firmware buttons; the 3-dot overflow menu gave up too much discoverability. Removed dead overflow state. Simulator: - Fix: global Delete/Backspace handler in SimulatorCanvas no longer fires when the event target is an INPUT/TEXTAREA/SELECT/contentEditable — affected any in-page text field, not just the chat overlay. Overlay extensibility: - New `data-velxio-slot="agent-chat"` at the bottom of EditorPage so pro overlays can portal a chat panel into the editor without forking the page. - vite.config.ts: preserveSymlinks=true when VITE_PRO_BUILD is set. Lets local-dev junctions (overlay tree → frontend/src/pro) resolve bare imports back to the OSS node_modules without resolving symlinks. Deps: - Added react-markdown + remark-gfm (rendered chat output) and @google/genai + zod (overlay agent loop). Tree-shaken from the OSS bundle when no pro code imports them. gitignore: - Ignore backend/app/pro/ and frontend/src/pro/ junctions used by developers running a private overlay against the OSS dev server. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 10:53:27 +07:00
import { useEditorStore } from '../store/useEditorStore';
import { useCompileLogsStore } from '../store/useCompileLogsStore';
import { useOscilloscopeStore } from '../store/useOscilloscopeStore';
import { useProjectStore } from '../store/useProjectStore';
feat: persist multi-board projects + add auto-save The project save/load pipeline only persisted a single `board_type`, so multi-board workspaces silently lost every board except the active one on save, and wires referencing the dropped boards' IDs orphaned to the canvas corner on reload. An audit of the production backup found 74/306 projects (24%) with at least one orphaned wire and 174/301 non-trivial projects whose code was still the default Blink template — strong signal that users save once and never re-save. Backend - Add `boards_json` column on `projects` with idempotent ALTER TABLE in the lifespan migration list. - New `FileGroup` schema + `file_groups` array on ProjectCreate/Update/Response. Legacy `files`/`code` kept for back-compat. - `project_files.py` now uses `{pid}/{groupId}/{filename}` subdirs via `read_groups`/`write_groups`. Legacy flat layouts are auto-promoted on read; legacy single-list `files` only updates the active group, leaving other boards' files intact. - `_persist_files_from_body` honors file_groups → files → code priority. Frontend - `useSimulatorStore.addBoard` accepts an optional `explicitId` so saved board IDs can be restored verbatim (wires reference IDs literally). - New `loadProjectState({boards, fileGroups, components, wires, activeBoardId})` action: tears down current boards, recreates from the payload, restores file groups atomically, recalculates wire positions on the next frame, and refreshes the Interconnect. - `useEditorStore.replaceFileGroups` for atomic multi-group restore. - `SaveProjectModal` and `ProjectByIdPage`/`ProjectPage` now go through `buildSavePayload` / `buildLoadPayload` (handles pre-backfill projects by synthesising a default board from `board_type`). Auto-save (#useAutoSaveProject hook) - 2.5s debounced silent PUT triggered ONLY when an authenticated user has a `currentProject` with a UUID. State hash detects real changes vs. UI-only churn; baseline is reset on project load so the just-loaded state isn't immediately re-saved. - `beforeunload` flush via `fetch keepalive: true` (supports PUT + credentials, survives unload). - Compact status indicator in `AppHeader` (idle/dirty/saving/saved/error). Backfill script (one-off, idempotent) - `backend/scripts/backfill_boards_2026_05.py` populates `boards_json` for legacy projects. Heuristic per project, based on which board IDs the wires reference: Case A — wires only ref 'arduino-uno' but board_type ≠ uno: rename id→board_type and rewrite wire endpoints. Case B — single-board normal: keep verbatim. Case C — multi-board: recreate one board per distinct ref, infer kind by stripping trailing -N suffix. Also moves any flat files into the active board's group subdir. Stdlib-only, runs from host or `docker exec`. Docker - `Dockerfile.standalone` now copies `backend/scripts/` into the image so the backfill is callable via `docker exec velxio-app python /app/scripts/backfill_boards_2026_05.py --apply`. Verified locally on the restored production backup (363 projects): 33 Case A, 316 Case B, 14 Case C, 135 wire endpoints renamed, 0 orphans. Re-running the script after apply skips all 363 (idempotent). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 23:43:33 +07:00
import { useAutoSaveProject } from '../hooks/useAutoSaveProject';
import type { CompilationLog } from '../utils/compilationLogger';
2026-06-15 21:57:10 +07:00
import { isPiBoardKind } from '../types/board';
import '../App.css';
const MOBILE_BREAKPOINT = 768;
const BOTTOM_PANEL_MIN = 80;
const BOTTOM_PANEL_MAX = 600;
const BOTTOM_PANEL_DEFAULT = 200;
feat(editor): view-mode toggle, agent-chat slot, toolbar polish Changes that ship to OSS — all benign for self-hosters, but most are extension points the velxio-prod overlay (and any private fork) needs to plug an in-editor AI chat into the page. Editor: - 3-way view-mode toggle (code / both / circuit) in the unified toolbar. Lets users hide a pane to give a right-docked sidebar (e.g. the AI chat overlay) more breathing room. Persisted in useEditorStore. - Default file explorer narrower (210 → 165 px); min 110. - Removed the redundant `tb-board-pill` (icon + "Editing: X" tooltip); the BoardSelector dropdown elsewhere already shows the active board. - Inlined Import/Export/Upload-firmware buttons; the 3-dot overflow menu gave up too much discoverability. Removed dead overflow state. Simulator: - Fix: global Delete/Backspace handler in SimulatorCanvas no longer fires when the event target is an INPUT/TEXTAREA/SELECT/contentEditable — affected any in-page text field, not just the chat overlay. Overlay extensibility: - New `data-velxio-slot="agent-chat"` at the bottom of EditorPage so pro overlays can portal a chat panel into the editor without forking the page. - vite.config.ts: preserveSymlinks=true when VITE_PRO_BUILD is set. Lets local-dev junctions (overlay tree → frontend/src/pro) resolve bare imports back to the OSS node_modules without resolving symlinks. Deps: - Added react-markdown + remark-gfm (rendered chat output) and @google/genai + zod (overlay agent loop). Tree-shaken from the OSS bundle when no pro code imports them. gitignore: - Ignore backend/app/pro/ and frontend/src/pro/ junctions used by developers running a private overlay against the OSS dev server. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 10:53:27 +07:00
const EXPLORER_MIN = 110;
const EXPLORER_MAX = 500;
feat(editor): view-mode toggle, agent-chat slot, toolbar polish Changes that ship to OSS — all benign for self-hosters, but most are extension points the velxio-prod overlay (and any private fork) needs to plug an in-editor AI chat into the page. Editor: - 3-way view-mode toggle (code / both / circuit) in the unified toolbar. Lets users hide a pane to give a right-docked sidebar (e.g. the AI chat overlay) more breathing room. Persisted in useEditorStore. - Default file explorer narrower (210 → 165 px); min 110. - Removed the redundant `tb-board-pill` (icon + "Editing: X" tooltip); the BoardSelector dropdown elsewhere already shows the active board. - Inlined Import/Export/Upload-firmware buttons; the 3-dot overflow menu gave up too much discoverability. Removed dead overflow state. Simulator: - Fix: global Delete/Backspace handler in SimulatorCanvas no longer fires when the event target is an INPUT/TEXTAREA/SELECT/contentEditable — affected any in-page text field, not just the chat overlay. Overlay extensibility: - New `data-velxio-slot="agent-chat"` at the bottom of EditorPage so pro overlays can portal a chat panel into the editor without forking the page. - vite.config.ts: preserveSymlinks=true when VITE_PRO_BUILD is set. Lets local-dev junctions (overlay tree → frontend/src/pro) resolve bare imports back to the OSS node_modules without resolving symlinks. Deps: - Added react-markdown + remark-gfm (rendered chat output) and @google/genai + zod (overlay agent loop). Tree-shaken from the OSS bundle when no pro code imports them. gitignore: - Ignore backend/app/pro/ and frontend/src/pro/ junctions used by developers running a private overlay against the OSS dev server. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 10:53:27 +07:00
const EXPLORER_DEFAULT = 165;
const resizeHandleStyle: React.CSSProperties = {
height: 5,
flexShrink: 0,
cursor: 'row-resize',
background: '#2a2d2e',
borderTop: '1px solid #3c3c3c',
borderBottom: '1px solid #3c3c3c',
};
export const EditorPage: React.FC = () => {
feat(i18n): translate AdminPage + UserProfile + Pricing + EditorPage shell (Editor block 10 — final cleanup) Closes the Phase 2 i18n rollout. Every visitor- and user-facing surface velxio renders in normal use now reads from t(). AdminPage (admin-only) - Header (panel title, logout) and the four tabs (Dashboard / Users / Projects / Boards). - Setup screen for first-admin creation (title, body, password fields + mismatch error + create-admin button). - Not-admin gate page. - EditUserModal (title, four labels, admin/active toggles, cancel/save). - UsersTab: search placeholder, count pluralisation, all 12 table columns, Activity / Edit / Delete actions, empty state, delete-confirm prompt with username interpolation. - ProjectsTab: search placeholder, count pluralisation, all 9 table columns, public/private badge labels, delete action + confirm with project-name interpolation, empty state. - All error messages (load failed / save failed / delete failed) fall back through t(). UserProfilePage - "New project" CTA, loading + empty + not-found states, "Private" project badge, "Copy shareable link" tooltip. - The /editor link uses localize() so /es/<username>'s "New project" button stays in Spanish. PricingPlaceholder - Title + the two paragraphs (self-hosted note + hosted Pro tier note + GitHub source note). Inline links wrapped via the Trans component so the link surface stays clickable in every locale without each translation having to re-write the HTML. EditorPage shell - Mobile bottom-tab labels (Code / Circuit), file-explorer toggle (Show / Hide), View mode aria-label, view-mode segmented control labels (Code / Both / Circuit), and the three "Drag to resize" handle tooltips on the panel splitters. Translations - en.json hand-curated for the new keys. - All 8 non-English locales auto-translated via the existing `npm run translate:i18n` pipeline (DeepSeek, ~5 min for the whole bundle, sameShape() validates each output before write). This closes Phase 2 of i18n. Phase 3 (DocsPage prose, AboutPage long-form paragraphs, the 15 SEO landing pages) is deliberately deferred — Docs/About are best handled by extracting the prose into JSON keys and running the same script, while the SEO pages are intentionally optimised for English keyword targeting and should not be machine-translated en masse.
2026-05-09 22:49:13 +07:00
const { t } = useTranslation();
useSEO({
title: 'Multi-Board Simulator Editor — Arduino, ESP32, RP2040, RISC-V | Velxio',
description:
'Write, compile and simulate Arduino, ESP32, Raspberry Pi Pico, ESP32-C3, and Raspberry Pi 3 code in your browser. 19 boards, 5 CPU architectures, 48+ components. Free and open-source.',
url: 'https://velxio.dev/editor',
});
feat: persist multi-board projects + add auto-save The project save/load pipeline only persisted a single `board_type`, so multi-board workspaces silently lost every board except the active one on save, and wires referencing the dropped boards' IDs orphaned to the canvas corner on reload. An audit of the production backup found 74/306 projects (24%) with at least one orphaned wire and 174/301 non-trivial projects whose code was still the default Blink template — strong signal that users save once and never re-save. Backend - Add `boards_json` column on `projects` with idempotent ALTER TABLE in the lifespan migration list. - New `FileGroup` schema + `file_groups` array on ProjectCreate/Update/Response. Legacy `files`/`code` kept for back-compat. - `project_files.py` now uses `{pid}/{groupId}/{filename}` subdirs via `read_groups`/`write_groups`. Legacy flat layouts are auto-promoted on read; legacy single-list `files` only updates the active group, leaving other boards' files intact. - `_persist_files_from_body` honors file_groups → files → code priority. Frontend - `useSimulatorStore.addBoard` accepts an optional `explicitId` so saved board IDs can be restored verbatim (wires reference IDs literally). - New `loadProjectState({boards, fileGroups, components, wires, activeBoardId})` action: tears down current boards, recreates from the payload, restores file groups atomically, recalculates wire positions on the next frame, and refreshes the Interconnect. - `useEditorStore.replaceFileGroups` for atomic multi-group restore. - `SaveProjectModal` and `ProjectByIdPage`/`ProjectPage` now go through `buildSavePayload` / `buildLoadPayload` (handles pre-backfill projects by synthesising a default board from `board_type`). Auto-save (#useAutoSaveProject hook) - 2.5s debounced silent PUT triggered ONLY when an authenticated user has a `currentProject` with a UUID. State hash detects real changes vs. UI-only churn; baseline is reset on project load so the just-loaded state isn't immediately re-saved. - `beforeunload` flush via `fetch keepalive: true` (supports PUT + credentials, survives unload). - Compact status indicator in `AppHeader` (idle/dirty/saving/saved/error). Backfill script (one-off, idempotent) - `backend/scripts/backfill_boards_2026_05.py` populates `boards_json` for legacy projects. Heuristic per project, based on which board IDs the wires reference: Case A — wires only ref 'arduino-uno' but board_type ≠ uno: rename id→board_type and rewrite wire endpoints. Case B — single-board normal: keep verbatim. Case C — multi-board: recreate one board per distinct ref, infer kind by stripping trailing -N suffix. Also moves any flat files into the active board's group subdir. Stdlib-only, runs from host or `docker exec`. Docker - `Dockerfile.standalone` now copies `backend/scripts/` into the image so the backfill is callable via `docker exec velxio-app python /app/scripts/backfill_boards_2026_05.py --apply`. Verified locally on the restored production backup (363 projects): 33 Case A, 316 Case B, 14 Case C, 135 wire endpoints renamed, 0 orphans. Re-running the script after apply skips all 363 (idempotent). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 23:43:33 +07:00
// Silent auto-save for the loaded project (only fires when authed AND
// currentProject has a UUID — see useAutoSaveProject for the gating rules).
const autoSave = useAutoSaveProject();
const [editorWidthPct, setEditorWidthPct] = useState(45);
feat(editor): view-mode toggle, agent-chat slot, toolbar polish Changes that ship to OSS — all benign for self-hosters, but most are extension points the velxio-prod overlay (and any private fork) needs to plug an in-editor AI chat into the page. Editor: - 3-way view-mode toggle (code / both / circuit) in the unified toolbar. Lets users hide a pane to give a right-docked sidebar (e.g. the AI chat overlay) more breathing room. Persisted in useEditorStore. - Default file explorer narrower (210 → 165 px); min 110. - Removed the redundant `tb-board-pill` (icon + "Editing: X" tooltip); the BoardSelector dropdown elsewhere already shows the active board. - Inlined Import/Export/Upload-firmware buttons; the 3-dot overflow menu gave up too much discoverability. Removed dead overflow state. Simulator: - Fix: global Delete/Backspace handler in SimulatorCanvas no longer fires when the event target is an INPUT/TEXTAREA/SELECT/contentEditable — affected any in-page text field, not just the chat overlay. Overlay extensibility: - New `data-velxio-slot="agent-chat"` at the bottom of EditorPage so pro overlays can portal a chat panel into the editor without forking the page. - vite.config.ts: preserveSymlinks=true when VITE_PRO_BUILD is set. Lets local-dev junctions (overlay tree → frontend/src/pro) resolve bare imports back to the OSS node_modules without resolving symlinks. Deps: - Added react-markdown + remark-gfm (rendered chat output) and @google/genai + zod (overlay agent loop). Tree-shaken from the OSS bundle when no pro code imports them. gitignore: - Ignore backend/app/pro/ and frontend/src/pro/ junctions used by developers running a private overlay against the OSS dev server. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 10:53:27 +07:00
// Desktop-only 3-way layout switch (code-only / circuit-only / both).
// Lets users hide a pane to give the right-docked chat more room.
const viewMode = useEditorStore((s) => s.viewMode);
const setViewMode = useEditorStore((s) => s.setViewMode);
const containerRef = useRef<HTMLDivElement>(null);
const resizingRef = useRef(false);
const serialMonitorOpen = useSimulatorStore((s) => s.serialMonitorOpen);
const activeBoardId = useSimulatorStore((s) => s.activeBoardId);
const activeBoardKind = useSimulatorStore(
(s) => s.boards.find((b) => b.id === s.activeBoardId)?.boardKind,
);
2026-06-15 21:57:10 +07:00
// Pi 3/4/5 and Zero/1/2 all run the QEMU Linux workspace (terminal + Python),
// not the Arduino/Monaco editor. Pico (RP2040) is browser-emulated, not a Pi here.
const isLinuxPi = isPiBoardKind(activeBoardKind ?? '');
const oscilloscopeOpen = useOscilloscopeStore((s) => s.open);
const [consoleOpen, setConsoleOpen] = useState(false);
// compileLogs live in a Zustand store so the velxio-pro agent overlay
// (mounted in a separate React tree via slotMounter) can subscribe and
// build a "diagnose this failure" prompt without prop-drilling.
const compileLogs = useCompileLogsStore((s) => s.logs);
const setCompileLogs = useCompileLogsStore((s) => s.setLogs);
const [bottomPanelHeight, setBottomPanelHeight] = useState(BOTTOM_PANEL_DEFAULT);
const [showStarBanner, setShowStarBanner] = useState(false);
const [starRound, setStarRound] = useState<1 | 2>(1);
feat(sim): Phase 1c G+F3 — retire legacy CircuitScheduler / eecircuit-engine The mixed-mode migration's endgame. After this commit there is ONE SPICE solver path in the codebase — the vendored ngspice WASM via SolverPort, behind both NgSpiceWorkerAdapter (production browser) and NgSpiceNodeAdapter (Vitest Node). Zero hybrids; zero legacy left to maintain. Deleted production files: • simulation/spice/CircuitScheduler.ts (200ms-poll legacy) • simulation/spice/SpiceEngine.ts (eecircuit-engine wrap) • simulation/spice/SpiceEngine.lazy.ts (lazy code-split) • simulation/spice/subscribeToStore.ts (legacy solve loop) • simulation/spice/connectLegacySolverToMixedMode.ts (bridge) • simulation/spice/connectMixedModeSchedulerToStore.ts (feature flag) Deleted tests (no longer cover any live code): • connect-legacy-solver-to-mixed-mode.test.ts • connect-mixed-mode-scheduler-to-store.test.ts • spice-rectifier-live-bootstrap.test.ts Migrated 6 tests off the deleted `circuitScheduler.solveNow` API to the new `__tests__/helpers/solveInput.ts` (same shape, backed by NgSpiceNodeAdapter). `useElectricalStore` rewritten as a pure state container: • setSolveResult(snapshot) — atomic publish from the service • paused / setPaused — UI control unchanged • reset — project unload • REMOVED: triggerSolve, solveNow, setDebounceMs, scheduler hook • REMOVED: dependency on SpiceEngine.lazy preload EditorPage now mounts a single `startSimulation()` from `simulation/spice/start.ts`, which constructs CircuitSimulationService + ADC bridge + MCU edge bridge. Four useEffect calls collapsed to one. `circuitVerifier.ts` (production) and `runNetlist.ts` use an environment-aware factory: Web Worker in browser, in-proc WASM in Node tests. `/* @vite-ignore */` keeps the Node adapter chain (node:fs, node:url) out of the browser bundle while still letting Node resolve it dynamically. Removed `eecircuit-engine` from package.json dependencies. `collectPinStates` extracted to its own module so the service doesn't depend on the (now deleted) subscribeToStore.ts. Verification: • 1392/1392 tests pass across 103 files (28 pre-existing skips). • `tsc --noEmit` clean. • `vite build` succeeds (27 s, only the existing chunk-size warning that pre-dates this work). Phase 1c — COMPLETE. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 02:46:34 +07:00
// ── Electrical simulation (one-time mount) ────────────────────────────────
// `startSimulation()` is the single entry point: it constructs the
// CircuitSimulationService, mounts the ADC bridge, and subscribes
// PinManager → service.handleMcuEdge. No more legacy paths — the
// WASM ngspice (via NgSpiceWorkerAdapter) is the only solver.
feat: electrical simulation via ngspice-WASM (eecircuit-engine) Adds full SPICE-accurate electrical simulation to Velxio, behind a lazy- loaded ⚡ toolbar toggle. Arduino / ESP32 / RP2040 sketches now co-simulate with real analog behaviour: correct voltages on wires, real I–V curves on LEDs, working potentiometers, NTC thermistors read by analogRead(), PWM driving RC filters, transistors, op-amps, diodes, MOSFETs, etc. Engine: eecircuit-engine (ngspice compiled to WebAssembly). Main bundle stays at 2.4 MB; the 20 MB SPICE chunk only loads when the user activates electrical mode. Disabled at build time via VITE_ELECTRICAL_SIM=false. Frontend additions: - simulation/spice/: SpiceEngine wrapper + lazy entry, NetlistBuilder with UnionFind over wires, componentToSpice mapping (24 metadataIds incl. real part numbers: 2N2222, 2N3055, BC547, IRF540, 2N7000, 1N4148, 1N4007, 1N4733, LEDs, NTC, op-amp ideal), CircuitScheduler with debounced coalescing, AVRSpiceBridge for quasi-static co-simulation. - store/useElectricalStore: Zustand slice, feature-flag aware. - components/analog-ui/: ⚡ toolbar toggle + SVG voltage overlay. - components/components-instruments/: Voltmeter, Ammeter probes. - 62 tests (spice-*, netlist-builder, component-to-spice, instruments). Sandbox (test/test_circuit/): 47-test validation sandbox that proved the approach (hand-rolled MNA baseline + ngspice pipeline) before porting to the app. Kept as reference. Docs: docs/wiki/circuit-emulation-*.md (13 engineering pages covering architecture, solvers, components, AVR bridge, gotchas, performance, integration plan, API reference, appendix) + electrical-simulation- user-guide.md (end-user facing). Reference plan: test/test_circuit/plan/phase_8_velxio_implementation.md Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 19:11:54 +07:00
useEffect(() => {
feat(sim): Phase 1c G+F3 — retire legacy CircuitScheduler / eecircuit-engine The mixed-mode migration's endgame. After this commit there is ONE SPICE solver path in the codebase — the vendored ngspice WASM via SolverPort, behind both NgSpiceWorkerAdapter (production browser) and NgSpiceNodeAdapter (Vitest Node). Zero hybrids; zero legacy left to maintain. Deleted production files: • simulation/spice/CircuitScheduler.ts (200ms-poll legacy) • simulation/spice/SpiceEngine.ts (eecircuit-engine wrap) • simulation/spice/SpiceEngine.lazy.ts (lazy code-split) • simulation/spice/subscribeToStore.ts (legacy solve loop) • simulation/spice/connectLegacySolverToMixedMode.ts (bridge) • simulation/spice/connectMixedModeSchedulerToStore.ts (feature flag) Deleted tests (no longer cover any live code): • connect-legacy-solver-to-mixed-mode.test.ts • connect-mixed-mode-scheduler-to-store.test.ts • spice-rectifier-live-bootstrap.test.ts Migrated 6 tests off the deleted `circuitScheduler.solveNow` API to the new `__tests__/helpers/solveInput.ts` (same shape, backed by NgSpiceNodeAdapter). `useElectricalStore` rewritten as a pure state container: • setSolveResult(snapshot) — atomic publish from the service • paused / setPaused — UI control unchanged • reset — project unload • REMOVED: triggerSolve, solveNow, setDebounceMs, scheduler hook • REMOVED: dependency on SpiceEngine.lazy preload EditorPage now mounts a single `startSimulation()` from `simulation/spice/start.ts`, which constructs CircuitSimulationService + ADC bridge + MCU edge bridge. Four useEffect calls collapsed to one. `circuitVerifier.ts` (production) and `runNetlist.ts` use an environment-aware factory: Web Worker in browser, in-proc WASM in Node tests. `/* @vite-ignore */` keeps the Node adapter chain (node:fs, node:url) out of the browser bundle while still letting Node resolve it dynamically. Removed `eecircuit-engine` from package.json dependencies. `collectPinStates` extracted to its own module so the service doesn't depend on the (now deleted) subscribeToStore.ts. Verification: • 1392/1392 tests pass across 103 files (28 pre-existing skips). • `tsc --noEmit` clean. • `vite build` succeeds (27 s, only the existing chunk-size warning that pre-dates this work). Phase 1c — COMPLETE. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 02:46:34 +07:00
return startSimulation();
feat: electrical simulation via ngspice-WASM (eecircuit-engine) Adds full SPICE-accurate electrical simulation to Velxio, behind a lazy- loaded ⚡ toolbar toggle. Arduino / ESP32 / RP2040 sketches now co-simulate with real analog behaviour: correct voltages on wires, real I–V curves on LEDs, working potentiometers, NTC thermistors read by analogRead(), PWM driving RC filters, transistors, op-amps, diodes, MOSFETs, etc. Engine: eecircuit-engine (ngspice compiled to WebAssembly). Main bundle stays at 2.4 MB; the 20 MB SPICE chunk only loads when the user activates electrical mode. Disabled at build time via VITE_ELECTRICAL_SIM=false. Frontend additions: - simulation/spice/: SpiceEngine wrapper + lazy entry, NetlistBuilder with UnionFind over wires, componentToSpice mapping (24 metadataIds incl. real part numbers: 2N2222, 2N3055, BC547, IRF540, 2N7000, 1N4148, 1N4007, 1N4733, LEDs, NTC, op-amp ideal), CircuitScheduler with debounced coalescing, AVRSpiceBridge for quasi-static co-simulation. - store/useElectricalStore: Zustand slice, feature-flag aware. - components/analog-ui/: ⚡ toolbar toggle + SVG voltage overlay. - components/components-instruments/: Voltmeter, Ammeter probes. - 62 tests (spice-*, netlist-builder, component-to-spice, instruments). Sandbox (test/test_circuit/): 47-test validation sandbox that proved the approach (hand-rolled MNA baseline + ngspice pipeline) before porting to the app. Kept as reference. Docs: docs/wiki/circuit-emulation-*.md (13 engineering pages covering architecture, solvers, components, AVR bridge, gotchas, performance, integration plan, API reference, appendix) + electrical-simulation- user-guide.md (end-user facing). Reference plan: test/test_circuit/plan/phase_8_velxio_implementation.md Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 19:11:54 +07:00
}, []);
// ── GitHub star prompt (show twice at most: 2nd visit OR after 3 min) ──────
// Three localStorage flags drive this:
// velxio_star_prompted → dismissed the first ask
// velxio_star_prompted_v2 → dismissed the follow-up ask (stop forever)
// velxio_star_clicked → clicked through to the repo (stop forever)
// Anyone who dismissed the first ask WITHOUT clicking through gets one
// follow-up (round 2) with a stronger message; clicking the repo link at
// any time opts them out permanently.
useEffect(() => {
const STAR_KEY = 'velxio_star_prompted';
const STAR_KEY_V2 = 'velxio_star_prompted_v2';
const STAR_CLICKED_KEY = 'velxio_star_clicked';
const VISITS_KEY = 'velxio_editor_visits';
const FIRST_VISIT_KEY = 'velxio_editor_first_visit';
const THREE_MIN = 3 * 60 * 1000;
// Never bother people who already starred or already saw the follow-up.
if (localStorage.getItem(STAR_CLICKED_KEY)) return;
if (localStorage.getItem(STAR_KEY_V2)) return;
// Round 2 = they dismissed the first ask (without clicking through).
const round = localStorage.getItem(STAR_KEY) ? 2 : 1;
setStarRound(round);
// Increment visit counter
const visits = parseInt(localStorage.getItem(VISITS_KEY) ?? '0', 10) + 1;
localStorage.setItem(VISITS_KEY, String(visits));
// Record timestamp of first visit
if (!localStorage.getItem(FIRST_VISIT_KEY)) {
localStorage.setItem(FIRST_VISIT_KEY, String(Date.now()));
}
const firstVisit = parseInt(localStorage.getItem(FIRST_VISIT_KEY)!, 10);
// Show immediately on second+ visit
if (visits >= 2) {
setShowStarBanner(true);
return;
}
// Otherwise schedule after the 3-minute mark
const elapsed = Date.now() - firstVisit;
const delay = Math.max(0, THREE_MIN - elapsed);
const timer = setTimeout(() => {
if (!localStorage.getItem(STAR_CLICKED_KEY) && !localStorage.getItem(STAR_KEY_V2)) {
setShowStarBanner(true);
}
}, delay);
return () => clearTimeout(timer);
}, []);
const handleDismissStarBanner = () => {
// First dismiss → mark round 1; second dismiss → mark round 2 (stop forever).
if (localStorage.getItem('velxio_star_prompted')) {
localStorage.setItem('velxio_star_prompted_v2', '1');
} else {
localStorage.setItem('velxio_star_prompted', '1');
}
setShowStarBanner(false);
};
const handleStarClick = () => {
// They went to the repo — opt them out of any further prompts.
localStorage.setItem('velxio_star_clicked', '1');
setShowStarBanner(false);
};
const [explorerOpen, setExplorerOpen] = useState(true);
const [explorerWidth, setExplorerWidth] = useState(EXPLORER_DEFAULT);
const [isMobile, setIsMobile] = useState(
() => window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT}px)`).matches,
);
// Slot element for SimulatorCanvas to portal its header into. When set, the
// canvas board selector / Serial / Scope / zoom / Add buttons render here
// instead of above the canvas — keeping the top bar a single full-width row
// that doesn't reflow when the editor/canvas splitter is dragged.
const [canvasHeaderSlot, setCanvasHeaderSlot] = useState<HTMLDivElement | null>(null);
// Default to 'code' on mobile — show the editor so users can write/view code
const [mobileView, setMobileView] = useState<'code' | 'circuit'>('code');
refactor(oss-split): remove auth/admin/profile frontend from OSS Phase 3 of the OSS / pro split — frontend side. Phase 2 already moved the auth/DB stack out of the OSS backend; this commit does the same for the React app. After this, the OSS image is editor + simulator + landing + docs only. What moved to the private overlay (pro/frontend/src/pro/): pages/{Login,Register,ForgotPassword,ResetPassword}Page.tsx pages/{Admin,UserProfile,Project,ProjectById}Page.tsx components/admin/{AdminBoardsTab,AdminDashboardTab,UserActivityModal}.tsx components/layout/{SaveProjectModal,LoginPromptModal}.tsx services/{authService,adminService}.ts store/useAuthStore.ts hooks/autoSaveImpl.ts New seams added so OSS components stay decoupled: * lib/proRoutes.ts — registerProRoutes()/useProRoutes() via useSyncExternalStore. mountPro() injects the moved pages at runtime; App.tsx subscribes to the registry, so registration after the initial render re-renders without a Not-Found flash. * lib/proSession.ts — registerSessionCheck()/triggerSessionCheck(). App.tsx fires this on mount instead of useAuthStore.checkSession(); pure OSS no-ops. * lib/proSaveAction.ts — installSaveActionImpl()/triggerSaveAction(). EditorPage's Save button dispatches through this; the overlay decides whether to show SaveProjectModal or LoginPromptModal based on auth state. In OSS without an overlay it's a no-op today; in Phase 4 of the split it becomes the .vlx Export entry point. OSS-side rewrites: * App.tsx drops the 8 page imports + 8 route entries; uses triggerSessionCheck() instead of useAuthStore directly. * AppHeader.tsx drops the user/login/register block entirely. The header-auth slot (introduced in Phase 1) now stays empty in OSS and gets filled by the overlay's portal mount. * EditorPage.tsx drops useAuthStore + SaveProjectModal + LoginPromptModal imports. The Save handler is now triggerSaveAction(). * LandingPage.tsx drops the dead UserMenu component (defined but never rendered) + its useAuthStore imports. * main.tsx drops the side-effect import of hooks/autoSaveImpl — the impl lives in pro now and self-registers via mountPro(). Build config: * vite.config.ts adds @velxio alias → src/. Lets the overlay import upstream modules (lib/proRoutes etc.) by stable name regardless of whether it's symlinked (local dev) or COPYed (Docker). * preserveSymlinks now gated on VITE_PRO_BUILD only (not on serve mode). Needed so Rollup keeps the overlay logically inside src/pro/ during local junction-based builds. Build verification: * OSS-only: 20-ish routes, no /login, /admin, /:username — 285 SEO pages prerendered. Bundle drops ~80-120 KB. * OSS + overlay: full 38 routes (30 upstream + 8 from registerProRoutes), HeaderAuth dropdown injected via slot, save action wired to the overlay's modal flow. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 01:31:12 +07:00
// Save is dispatched to the pro overlay, which inspects auth state and
// shows the right modal (Save vs Login prompt). In OSS without the
// overlay this is a no-op today and becomes the .vlx Export entry
// point in Phase 4 of the OSS split.
const handleSaveClick = useCallback(() => {
refactor(oss-split): remove auth/admin/profile frontend from OSS Phase 3 of the OSS / pro split — frontend side. Phase 2 already moved the auth/DB stack out of the OSS backend; this commit does the same for the React app. After this, the OSS image is editor + simulator + landing + docs only. What moved to the private overlay (pro/frontend/src/pro/): pages/{Login,Register,ForgotPassword,ResetPassword}Page.tsx pages/{Admin,UserProfile,Project,ProjectById}Page.tsx components/admin/{AdminBoardsTab,AdminDashboardTab,UserActivityModal}.tsx components/layout/{SaveProjectModal,LoginPromptModal}.tsx services/{authService,adminService}.ts store/useAuthStore.ts hooks/autoSaveImpl.ts New seams added so OSS components stay decoupled: * lib/proRoutes.ts — registerProRoutes()/useProRoutes() via useSyncExternalStore. mountPro() injects the moved pages at runtime; App.tsx subscribes to the registry, so registration after the initial render re-renders without a Not-Found flash. * lib/proSession.ts — registerSessionCheck()/triggerSessionCheck(). App.tsx fires this on mount instead of useAuthStore.checkSession(); pure OSS no-ops. * lib/proSaveAction.ts — installSaveActionImpl()/triggerSaveAction(). EditorPage's Save button dispatches through this; the overlay decides whether to show SaveProjectModal or LoginPromptModal based on auth state. In OSS without an overlay it's a no-op today; in Phase 4 of the split it becomes the .vlx Export entry point. OSS-side rewrites: * App.tsx drops the 8 page imports + 8 route entries; uses triggerSessionCheck() instead of useAuthStore directly. * AppHeader.tsx drops the user/login/register block entirely. The header-auth slot (introduced in Phase 1) now stays empty in OSS and gets filled by the overlay's portal mount. * EditorPage.tsx drops useAuthStore + SaveProjectModal + LoginPromptModal imports. The Save handler is now triggerSaveAction(). * LandingPage.tsx drops the dead UserMenu component (defined but never rendered) + its useAuthStore imports. * main.tsx drops the side-effect import of hooks/autoSaveImpl — the impl lives in pro now and self-registers via mountPro(). Build config: * vite.config.ts adds @velxio alias → src/. Lets the overlay import upstream modules (lib/proRoutes etc.) by stable name regardless of whether it's symlinked (local dev) or COPYed (Docker). * preserveSymlinks now gated on VITE_PRO_BUILD only (not on serve mode). Needed so Rollup keeps the overlay logically inside src/pro/ during local junction-based builds. Build verification: * OSS-only: 20-ish routes, no /login, /admin, /:username — 285 SEO pages prerendered. Bundle drops ~80-120 KB. * OSS + overlay: full 38 routes (30 upstream + 8 from registerProRoutes), HeaderAuth dropdown injected via slot, save action wired to the overlay's modal flow. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 01:31:12 +07:00
triggerSaveAction();
}, []);
const handleNewClick = useCallback(() => {
if (
!window.confirm(
'Start a new workspace? This clears every board, component, wire and file. This cannot be undone.',
)
) {
return;
}
const sim = useSimulatorStore.getState();
sim.boards.forEach((b) => sim.stopBoard(b.id));
const ids = sim.boards.map((b) => b.id);
ids.forEach((id) => sim.removeBoard(id));
sim.setComponents([]);
sim.setWires([]);
useProjectStore.getState().clearCurrentProject();
const newId = useSimulatorStore
.getState()
.addBoard('arduino-uno', DEFAULT_BOARD_POSITION.x, DEFAULT_BOARD_POSITION.y);
useSimulatorStore.getState().setActiveBoardId(newId);
}, []);
// Track mobile breakpoint
useEffect(() => {
const mq = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT}px)`);
const update = (e: MediaQueryListEvent | MediaQueryList) => {
const mobile = e.matches;
setIsMobile(mobile);
if (mobile) setExplorerOpen(false);
};
update(mq);
mq.addEventListener('change', update);
return () => mq.removeEventListener('change', update);
}, []);
// Ctrl+S shortcut
useEffect(() => {
const handler = (e: KeyboardEvent) => {
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
e.preventDefault();
handleSaveClick();
}
};
window.addEventListener('keydown', handler);
return () => window.removeEventListener('keydown', handler);
}, [handleSaveClick]);
// Ctrl+Z / Ctrl+Y / Ctrl+Shift+Z — canvas undo/redo. Skipped when the
// user is typing in any input/textarea/contenteditable so the Monaco
// editor's per-file history (and the AI chat composer, etc.) keep
// working untouched.
useEffect(() => {
const handler = (e: KeyboardEvent) => {
const t = e.target as HTMLElement | null;
if (t) {
const tag = t.tagName;
if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || t.isContentEditable) {
return;
}
}
if (!(e.ctrlKey || e.metaKey)) return;
const k = e.key.toLowerCase();
const sim = useSimulatorStore.getState();
if (k === 'z' && !e.shiftKey) {
e.preventDefault();
sim.undo();
} else if (k === 'y' || (k === 'z' && e.shiftKey)) {
e.preventDefault();
sim.redo();
}
};
window.addEventListener('keydown', handler);
return () => window.removeEventListener('keydown', handler);
}, []);
// Prevent body scroll on the editor page
useEffect(() => {
const html = document.documentElement;
const body = document.body;
html.style.overflow = 'hidden';
body.style.overflow = 'hidden';
window.scrollTo(0, 0);
return () => {
html.style.overflow = '';
body.style.overflow = '';
};
}, []);
const handleResizeMouseDown = useCallback((e: React.MouseEvent) => {
e.preventDefault();
resizingRef.current = true;
const handleMouseMove = (ev: MouseEvent) => {
if (!resizingRef.current || !containerRef.current) return;
const rect = containerRef.current.getBoundingClientRect();
const pct = ((ev.clientX - rect.left) / rect.width) * 100;
setEditorWidthPct(Math.max(20, Math.min(80, pct)));
};
const handleMouseUp = () => {
resizingRef.current = false;
document.body.style.cursor = '';
document.body.style.userSelect = '';
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
document.body.style.cursor = 'col-resize';
document.body.style.userSelect = 'none';
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
}, []);
const handleBottomPanelResizeMouseDown = useCallback(
(e: React.MouseEvent) => {
e.preventDefault();
const startY = e.clientY;
const startHeight = bottomPanelHeight;
const onMove = (ev: MouseEvent) => {
const delta = startY - ev.clientY;
setBottomPanelHeight(
Math.max(BOTTOM_PANEL_MIN, Math.min(BOTTOM_PANEL_MAX, startHeight + delta)),
);
};
const onUp = () => {
document.body.style.cursor = '';
document.body.style.userSelect = '';
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onUp);
};
document.body.style.cursor = 'row-resize';
document.body.style.userSelect = 'none';
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
},
[bottomPanelHeight],
);
const handleExplorerResizeMouseDown = useCallback(
(e: React.MouseEvent) => {
e.preventDefault();
const startX = e.clientX;
const startWidth = explorerWidth;
const onMove = (ev: MouseEvent) => {
const delta = ev.clientX - startX;
setExplorerWidth(Math.max(EXPLORER_MIN, Math.min(EXPLORER_MAX, startWidth + delta)));
};
const onUp = () => {
document.body.style.cursor = '';
document.body.style.userSelect = '';
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onUp);
};
document.body.style.cursor = 'col-resize';
document.body.style.userSelect = 'none';
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
},
[explorerWidth],
);
return (
<div className="app">
feat: persist multi-board projects + add auto-save The project save/load pipeline only persisted a single `board_type`, so multi-board workspaces silently lost every board except the active one on save, and wires referencing the dropped boards' IDs orphaned to the canvas corner on reload. An audit of the production backup found 74/306 projects (24%) with at least one orphaned wire and 174/301 non-trivial projects whose code was still the default Blink template — strong signal that users save once and never re-save. Backend - Add `boards_json` column on `projects` with idempotent ALTER TABLE in the lifespan migration list. - New `FileGroup` schema + `file_groups` array on ProjectCreate/Update/Response. Legacy `files`/`code` kept for back-compat. - `project_files.py` now uses `{pid}/{groupId}/{filename}` subdirs via `read_groups`/`write_groups`. Legacy flat layouts are auto-promoted on read; legacy single-list `files` only updates the active group, leaving other boards' files intact. - `_persist_files_from_body` honors file_groups → files → code priority. Frontend - `useSimulatorStore.addBoard` accepts an optional `explicitId` so saved board IDs can be restored verbatim (wires reference IDs literally). - New `loadProjectState({boards, fileGroups, components, wires, activeBoardId})` action: tears down current boards, recreates from the payload, restores file groups atomically, recalculates wire positions on the next frame, and refreshes the Interconnect. - `useEditorStore.replaceFileGroups` for atomic multi-group restore. - `SaveProjectModal` and `ProjectByIdPage`/`ProjectPage` now go through `buildSavePayload` / `buildLoadPayload` (handles pre-backfill projects by synthesising a default board from `board_type`). Auto-save (#useAutoSaveProject hook) - 2.5s debounced silent PUT triggered ONLY when an authenticated user has a `currentProject` with a UUID. State hash detects real changes vs. UI-only churn; baseline is reset on project load so the just-loaded state isn't immediately re-saved. - `beforeunload` flush via `fetch keepalive: true` (supports PUT + credentials, survives unload). - Compact status indicator in `AppHeader` (idle/dirty/saving/saved/error). Backfill script (one-off, idempotent) - `backend/scripts/backfill_boards_2026_05.py` populates `boards_json` for legacy projects. Heuristic per project, based on which board IDs the wires reference: Case A — wires only ref 'arduino-uno' but board_type ≠ uno: rename id→board_type and rewrite wire endpoints. Case B — single-board normal: keep verbatim. Case C — multi-board: recreate one board per distinct ref, infer kind by stripping trailing -N suffix. Also moves any flat files into the active board's group subdir. Stdlib-only, runs from host or `docker exec`. Docker - `Dockerfile.standalone` now copies `backend/scripts/` into the image so the backfill is callable via `docker exec velxio-app python /app/scripts/backfill_boards_2026_05.py --apply`. Verified locally on the restored production backup (363 projects): 33 Case A, 316 Case B, 14 Case C, 135 wire endpoints renamed, 0 orphans. Re-running the script after apply skips all 363 (idempotent). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 23:43:33 +07:00
<AppHeader autoSave={autoSave} />
{/* ── Mobile tab bar (top, above panels) ── */}
{isMobile && (
<nav className="mobile-tab-bar">
<button
className={`mobile-tab-btn${mobileView === 'code' ? ' mobile-tab-btn--active' : ''}`}
onClick={() => setMobileView('code')}
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="16 18 22 12 16 6" />
<polyline points="8 6 2 12 8 18" />
</svg>
feat(i18n): translate AdminPage + UserProfile + Pricing + EditorPage shell (Editor block 10 — final cleanup) Closes the Phase 2 i18n rollout. Every visitor- and user-facing surface velxio renders in normal use now reads from t(). AdminPage (admin-only) - Header (panel title, logout) and the four tabs (Dashboard / Users / Projects / Boards). - Setup screen for first-admin creation (title, body, password fields + mismatch error + create-admin button). - Not-admin gate page. - EditUserModal (title, four labels, admin/active toggles, cancel/save). - UsersTab: search placeholder, count pluralisation, all 12 table columns, Activity / Edit / Delete actions, empty state, delete-confirm prompt with username interpolation. - ProjectsTab: search placeholder, count pluralisation, all 9 table columns, public/private badge labels, delete action + confirm with project-name interpolation, empty state. - All error messages (load failed / save failed / delete failed) fall back through t(). UserProfilePage - "New project" CTA, loading + empty + not-found states, "Private" project badge, "Copy shareable link" tooltip. - The /editor link uses localize() so /es/<username>'s "New project" button stays in Spanish. PricingPlaceholder - Title + the two paragraphs (self-hosted note + hosted Pro tier note + GitHub source note). Inline links wrapped via the Trans component so the link surface stays clickable in every locale without each translation having to re-write the HTML. EditorPage shell - Mobile bottom-tab labels (Code / Circuit), file-explorer toggle (Show / Hide), View mode aria-label, view-mode segmented control labels (Code / Both / Circuit), and the three "Drag to resize" handle tooltips on the panel splitters. Translations - en.json hand-curated for the new keys. - All 8 non-English locales auto-translated via the existing `npm run translate:i18n` pipeline (DeepSeek, ~5 min for the whole bundle, sameShape() validates each output before write). This closes Phase 2 of i18n. Phase 3 (DocsPage prose, AboutPage long-form paragraphs, the 15 SEO landing pages) is deliberately deferred — Docs/About are best handled by extracting the prose into JSON keys and running the same script, while the SEO pages are intentionally optimised for English keyword targeting and should not be machine-translated en masse.
2026-05-09 22:49:13 +07:00
<span>&lt;/&gt; {t('editor.shell.code')}</span>
</button>
<button
className={`mobile-tab-btn${mobileView === 'circuit' ? ' mobile-tab-btn--active' : ''}`}
onClick={() => setMobileView('circuit')}
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<rect x="2" y="7" width="20" height="14" rx="2" />
<path d="M16 7V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v2" />
<line x1="12" y1="12" x2="12" y2="16" />
<line x1="10" y1="14" x2="14" y2="14" />
</svg>
feat(i18n): translate AdminPage + UserProfile + Pricing + EditorPage shell (Editor block 10 — final cleanup) Closes the Phase 2 i18n rollout. Every visitor- and user-facing surface velxio renders in normal use now reads from t(). AdminPage (admin-only) - Header (panel title, logout) and the four tabs (Dashboard / Users / Projects / Boards). - Setup screen for first-admin creation (title, body, password fields + mismatch error + create-admin button). - Not-admin gate page. - EditUserModal (title, four labels, admin/active toggles, cancel/save). - UsersTab: search placeholder, count pluralisation, all 12 table columns, Activity / Edit / Delete actions, empty state, delete-confirm prompt with username interpolation. - ProjectsTab: search placeholder, count pluralisation, all 9 table columns, public/private badge labels, delete action + confirm with project-name interpolation, empty state. - All error messages (load failed / save failed / delete failed) fall back through t(). UserProfilePage - "New project" CTA, loading + empty + not-found states, "Private" project badge, "Copy shareable link" tooltip. - The /editor link uses localize() so /es/<username>'s "New project" button stays in Spanish. PricingPlaceholder - Title + the two paragraphs (self-hosted note + hosted Pro tier note + GitHub source note). Inline links wrapped via the Trans component so the link surface stays clickable in every locale without each translation having to re-write the HTML. EditorPage shell - Mobile bottom-tab labels (Code / Circuit), file-explorer toggle (Show / Hide), View mode aria-label, view-mode segmented control labels (Code / Both / Circuit), and the three "Drag to resize" handle tooltips on the panel splitters. Translations - en.json hand-curated for the new keys. - All 8 non-English locales auto-translated via the existing `npm run translate:i18n` pipeline (DeepSeek, ~5 min for the whole bundle, sameShape() validates each output before write). This closes Phase 2 of i18n. Phase 3 (DocsPage prose, AboutPage long-form paragraphs, the 15 SEO landing pages) is deliberately deferred — Docs/About are best handled by extracting the prose into JSON keys and running the same script, while the SEO pages are intentionally optimised for English keyword targeting and should not be machine-translated en masse.
2026-05-09 22:49:13 +07:00
<span>{t('editor.shell.circuit')}</span>
</button>
</nav>
)}
{/* Unified top toolbar (desktop only)
Editor controls + canvas controls share a single full-width row so
the bar doesn't reflow when the editor/canvas splitter is dragged.
The canvas controls (board selector, Serial, Scope, zoom, Add) are
portaled into `canvasHeaderSlot` from inside SimulatorCanvas. */}
{!isMobile && (
<div className="unified-toolbar">
<button
className="explorer-toggle-btn unified-toolbar-explorer-toggle"
onClick={() => setExplorerOpen((v) => !v)}
title={explorerOpen ? 'Hide file explorer' : 'Show file explorer'}
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" />
</svg>
</button>
feat(editor): view-mode toggle, agent-chat slot, toolbar polish Changes that ship to OSS — all benign for self-hosters, but most are extension points the velxio-prod overlay (and any private fork) needs to plug an in-editor AI chat into the page. Editor: - 3-way view-mode toggle (code / both / circuit) in the unified toolbar. Lets users hide a pane to give a right-docked sidebar (e.g. the AI chat overlay) more breathing room. Persisted in useEditorStore. - Default file explorer narrower (210 → 165 px); min 110. - Removed the redundant `tb-board-pill` (icon + "Editing: X" tooltip); the BoardSelector dropdown elsewhere already shows the active board. - Inlined Import/Export/Upload-firmware buttons; the 3-dot overflow menu gave up too much discoverability. Removed dead overflow state. Simulator: - Fix: global Delete/Backspace handler in SimulatorCanvas no longer fires when the event target is an INPUT/TEXTAREA/SELECT/contentEditable — affected any in-page text field, not just the chat overlay. Overlay extensibility: - New `data-velxio-slot="agent-chat"` at the bottom of EditorPage so pro overlays can portal a chat panel into the editor without forking the page. - vite.config.ts: preserveSymlinks=true when VITE_PRO_BUILD is set. Lets local-dev junctions (overlay tree → frontend/src/pro) resolve bare imports back to the OSS node_modules without resolving symlinks. Deps: - Added react-markdown + remark-gfm (rendered chat output) and @google/genai + zod (overlay agent loop). Tree-shaken from the OSS bundle when no pro code imports them. gitignore: - Ignore backend/app/pro/ and frontend/src/pro/ junctions used by developers running a private overlay against the OSS dev server. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 10:53:27 +07:00
{/* View-mode toggle: Code / Both / Circuit. Lets users hide a
pane to give the right-docked AI chat more breathing room.
Hidden on mobile there's already a code/circuit toggle in
the mobile bottom-nav. */}
<div
role="group"
feat(i18n): translate AdminPage + UserProfile + Pricing + EditorPage shell (Editor block 10 — final cleanup) Closes the Phase 2 i18n rollout. Every visitor- and user-facing surface velxio renders in normal use now reads from t(). AdminPage (admin-only) - Header (panel title, logout) and the four tabs (Dashboard / Users / Projects / Boards). - Setup screen for first-admin creation (title, body, password fields + mismatch error + create-admin button). - Not-admin gate page. - EditUserModal (title, four labels, admin/active toggles, cancel/save). - UsersTab: search placeholder, count pluralisation, all 12 table columns, Activity / Edit / Delete actions, empty state, delete-confirm prompt with username interpolation. - ProjectsTab: search placeholder, count pluralisation, all 9 table columns, public/private badge labels, delete action + confirm with project-name interpolation, empty state. - All error messages (load failed / save failed / delete failed) fall back through t(). UserProfilePage - "New project" CTA, loading + empty + not-found states, "Private" project badge, "Copy shareable link" tooltip. - The /editor link uses localize() so /es/<username>'s "New project" button stays in Spanish. PricingPlaceholder - Title + the two paragraphs (self-hosted note + hosted Pro tier note + GitHub source note). Inline links wrapped via the Trans component so the link surface stays clickable in every locale without each translation having to re-write the HTML. EditorPage shell - Mobile bottom-tab labels (Code / Circuit), file-explorer toggle (Show / Hide), View mode aria-label, view-mode segmented control labels (Code / Both / Circuit), and the three "Drag to resize" handle tooltips on the panel splitters. Translations - en.json hand-curated for the new keys. - All 8 non-English locales auto-translated via the existing `npm run translate:i18n` pipeline (DeepSeek, ~5 min for the whole bundle, sameShape() validates each output before write). This closes Phase 2 of i18n. Phase 3 (DocsPage prose, AboutPage long-form paragraphs, the 15 SEO landing pages) is deliberately deferred — Docs/About are best handled by extracting the prose into JSON keys and running the same script, while the SEO pages are intentionally optimised for English keyword targeting and should not be machine-translated en masse.
2026-05-09 22:49:13 +07:00
aria-label={t('editor.shell.viewMode')}
feat(editor): view-mode toggle, agent-chat slot, toolbar polish Changes that ship to OSS — all benign for self-hosters, but most are extension points the velxio-prod overlay (and any private fork) needs to plug an in-editor AI chat into the page. Editor: - 3-way view-mode toggle (code / both / circuit) in the unified toolbar. Lets users hide a pane to give a right-docked sidebar (e.g. the AI chat overlay) more breathing room. Persisted in useEditorStore. - Default file explorer narrower (210 → 165 px); min 110. - Removed the redundant `tb-board-pill` (icon + "Editing: X" tooltip); the BoardSelector dropdown elsewhere already shows the active board. - Inlined Import/Export/Upload-firmware buttons; the 3-dot overflow menu gave up too much discoverability. Removed dead overflow state. Simulator: - Fix: global Delete/Backspace handler in SimulatorCanvas no longer fires when the event target is an INPUT/TEXTAREA/SELECT/contentEditable — affected any in-page text field, not just the chat overlay. Overlay extensibility: - New `data-velxio-slot="agent-chat"` at the bottom of EditorPage so pro overlays can portal a chat panel into the editor without forking the page. - vite.config.ts: preserveSymlinks=true when VITE_PRO_BUILD is set. Lets local-dev junctions (overlay tree → frontend/src/pro) resolve bare imports back to the OSS node_modules without resolving symlinks. Deps: - Added react-markdown + remark-gfm (rendered chat output) and @google/genai + zod (overlay agent loop). Tree-shaken from the OSS bundle when no pro code imports them. gitignore: - Ignore backend/app/pro/ and frontend/src/pro/ junctions used by developers running a private overlay against the OSS dev server. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 10:53:27 +07:00
className="view-mode-toggle"
style={{
display: 'flex',
gap: 1,
background: '#252526',
border: '1px solid #3c3c3c',
borderRadius: 4,
overflow: 'hidden',
alignSelf: 'center',
margin: '0 6px',
}}
>
{(
[
feat(i18n): translate AdminPage + UserProfile + Pricing + EditorPage shell (Editor block 10 — final cleanup) Closes the Phase 2 i18n rollout. Every visitor- and user-facing surface velxio renders in normal use now reads from t(). AdminPage (admin-only) - Header (panel title, logout) and the four tabs (Dashboard / Users / Projects / Boards). - Setup screen for first-admin creation (title, body, password fields + mismatch error + create-admin button). - Not-admin gate page. - EditUserModal (title, four labels, admin/active toggles, cancel/save). - UsersTab: search placeholder, count pluralisation, all 12 table columns, Activity / Edit / Delete actions, empty state, delete-confirm prompt with username interpolation. - ProjectsTab: search placeholder, count pluralisation, all 9 table columns, public/private badge labels, delete action + confirm with project-name interpolation, empty state. - All error messages (load failed / save failed / delete failed) fall back through t(). UserProfilePage - "New project" CTA, loading + empty + not-found states, "Private" project badge, "Copy shareable link" tooltip. - The /editor link uses localize() so /es/<username>'s "New project" button stays in Spanish. PricingPlaceholder - Title + the two paragraphs (self-hosted note + hosted Pro tier note + GitHub source note). Inline links wrapped via the Trans component so the link surface stays clickable in every locale without each translation having to re-write the HTML. EditorPage shell - Mobile bottom-tab labels (Code / Circuit), file-explorer toggle (Show / Hide), View mode aria-label, view-mode segmented control labels (Code / Both / Circuit), and the three "Drag to resize" handle tooltips on the panel splitters. Translations - en.json hand-curated for the new keys. - All 8 non-English locales auto-translated via the existing `npm run translate:i18n` pipeline (DeepSeek, ~5 min for the whole bundle, sameShape() validates each output before write). This closes Phase 2 of i18n. Phase 3 (DocsPage prose, AboutPage long-form paragraphs, the 15 SEO landing pages) is deliberately deferred — Docs/About are best handled by extracting the prose into JSON keys and running the same script, while the SEO pages are intentionally optimised for English keyword targeting and should not be machine-translated en masse.
2026-05-09 22:49:13 +07:00
{ key: 'code', label: t('editor.shell.code'), path: 'M16 18l6-6-6-6M8 6l-6 6 6 6' },
{ key: 'both', label: t('editor.shell.both'), path: 'M3 3h7v18H3zM14 3h7v18h-7z' },
{ key: 'circuit', label: t('editor.shell.circuit'), path: 'M5 12h14M12 5v14' },
feat(editor): view-mode toggle, agent-chat slot, toolbar polish Changes that ship to OSS — all benign for self-hosters, but most are extension points the velxio-prod overlay (and any private fork) needs to plug an in-editor AI chat into the page. Editor: - 3-way view-mode toggle (code / both / circuit) in the unified toolbar. Lets users hide a pane to give a right-docked sidebar (e.g. the AI chat overlay) more breathing room. Persisted in useEditorStore. - Default file explorer narrower (210 → 165 px); min 110. - Removed the redundant `tb-board-pill` (icon + "Editing: X" tooltip); the BoardSelector dropdown elsewhere already shows the active board. - Inlined Import/Export/Upload-firmware buttons; the 3-dot overflow menu gave up too much discoverability. Removed dead overflow state. Simulator: - Fix: global Delete/Backspace handler in SimulatorCanvas no longer fires when the event target is an INPUT/TEXTAREA/SELECT/contentEditable — affected any in-page text field, not just the chat overlay. Overlay extensibility: - New `data-velxio-slot="agent-chat"` at the bottom of EditorPage so pro overlays can portal a chat panel into the editor without forking the page. - vite.config.ts: preserveSymlinks=true when VITE_PRO_BUILD is set. Lets local-dev junctions (overlay tree → frontend/src/pro) resolve bare imports back to the OSS node_modules without resolving symlinks. Deps: - Added react-markdown + remark-gfm (rendered chat output) and @google/genai + zod (overlay agent loop). Tree-shaken from the OSS bundle when no pro code imports them. gitignore: - Ignore backend/app/pro/ and frontend/src/pro/ junctions used by developers running a private overlay against the OSS dev server. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 10:53:27 +07:00
] as const
).map((m) => (
<button
key={m.key}
onClick={() => setViewMode(m.key)}
aria-pressed={viewMode === m.key}
style={{
background: viewMode === m.key ? '#0e639c' : 'transparent',
color: viewMode === m.key ? 'white' : '#aaa',
border: 'none',
height: 28,
padding: '0 10px',
display: 'flex',
alignItems: 'center',
gap: 4,
cursor: 'pointer',
fontSize: 12,
fontFamily: 'inherit',
}}
>
<svg
width="13"
height="13"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d={m.path} />
</svg>
<span>{m.label}</span>
</button>
))}
</div>
<div className="unified-toolbar-editor">
<EditorToolbar
consoleOpen={consoleOpen}
setConsoleOpen={setConsoleOpen}
compileLogs={compileLogs}
setCompileLogs={setCompileLogs}
2026-06-15 21:57:10 +07:00
centerSlot={!isLinuxPi ? <FileTabs /> : null}
/>
</div>
<div className="unified-toolbar-canvas" ref={setCanvasHeaderSlot} />
</div>
)}
<div className="app-container" ref={containerRef}>
{/* ── Editor side ── */}
<div
className="editor-panel"
style={{
feat(editor): view-mode toggle, agent-chat slot, toolbar polish Changes that ship to OSS — all benign for self-hosters, but most are extension points the velxio-prod overlay (and any private fork) needs to plug an in-editor AI chat into the page. Editor: - 3-way view-mode toggle (code / both / circuit) in the unified toolbar. Lets users hide a pane to give a right-docked sidebar (e.g. the AI chat overlay) more breathing room. Persisted in useEditorStore. - Default file explorer narrower (210 → 165 px); min 110. - Removed the redundant `tb-board-pill` (icon + "Editing: X" tooltip); the BoardSelector dropdown elsewhere already shows the active board. - Inlined Import/Export/Upload-firmware buttons; the 3-dot overflow menu gave up too much discoverability. Removed dead overflow state. Simulator: - Fix: global Delete/Backspace handler in SimulatorCanvas no longer fires when the event target is an INPUT/TEXTAREA/SELECT/contentEditable — affected any in-page text field, not just the chat overlay. Overlay extensibility: - New `data-velxio-slot="agent-chat"` at the bottom of EditorPage so pro overlays can portal a chat panel into the editor without forking the page. - vite.config.ts: preserveSymlinks=true when VITE_PRO_BUILD is set. Lets local-dev junctions (overlay tree → frontend/src/pro) resolve bare imports back to the OSS node_modules without resolving symlinks. Deps: - Added react-markdown + remark-gfm (rendered chat output) and @google/genai + zod (overlay agent loop). Tree-shaken from the OSS bundle when no pro code imports them. gitignore: - Ignore backend/app/pro/ and frontend/src/pro/ junctions used by developers running a private overlay against the OSS dev server. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 10:53:27 +07:00
width: isMobile
? '100%'
: viewMode === 'code'
? '100%'
: viewMode === 'circuit'
? '0%'
: `${editorWidthPct}%`,
display:
(isMobile && mobileView !== 'code') || (!isMobile && viewMode === 'circuit')
? 'none'
: 'flex',
flexDirection: 'row',
}}
>
{/* File explorer sidebar + resize handle */}
{explorerOpen && (
<>
<div
style={{ width: explorerWidth, flexShrink: 0, display: 'flex', overflow: 'hidden' }}
>
<FileExplorer onSaveClick={handleSaveClick} onNewClick={handleNewClick} />
</div>
{!isMobile && (
<div
className="explorer-resize-handle"
onMouseDown={handleExplorerResizeMouseDown}
/>
)}
</>
)}
{/* Editor main area */}
<div
style={{
flex: 1,
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
minWidth: 0,
}}
>
{/* Mobile-only: explorer toggle + editor toolbar inside the panel.
On desktop these are hoisted into the unified top toolbar. */}
{isMobile && (
<div style={{ display: 'flex', alignItems: 'stretch', flexShrink: 0 }}>
<button
className="explorer-toggle-btn"
onClick={() => setExplorerOpen((v) => !v)}
title={explorerOpen ? 'Hide file explorer' : 'Show file explorer'}
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" />
</svg>
</button>
<div style={{ flex: 1, minWidth: 0 }}>
<EditorToolbar
consoleOpen={consoleOpen}
setConsoleOpen={setConsoleOpen}
compileLogs={compileLogs}
setCompileLogs={setCompileLogs}
2026-06-15 21:57:10 +07:00
centerSlot={!isLinuxPi ? <FileTabs /> : null}
/>
</div>
</div>
)}
{/* Editor area: Pi workspace or Monaco editor */}
<div className="editor-wrapper" style={{ flex: 1, overflow: 'hidden', minHeight: 0 }}>
2026-06-15 21:57:10 +07:00
{isLinuxPi && activeBoardId ? (
<Suspense
fallback={
<div style={{ color: '#666', padding: 16, fontSize: 12 }}>
Loading Pi workspace
</div>
}
>
<RaspberryPiWorkspace boardId={activeBoardId} />
</Suspense>
) : (
<CodeEditor />
)}
</div>
{/* Console */}
{consoleOpen && (
<>
<div
onMouseDown={handleBottomPanelResizeMouseDown}
style={resizeHandleStyle}
feat(i18n): translate AdminPage + UserProfile + Pricing + EditorPage shell (Editor block 10 — final cleanup) Closes the Phase 2 i18n rollout. Every visitor- and user-facing surface velxio renders in normal use now reads from t(). AdminPage (admin-only) - Header (panel title, logout) and the four tabs (Dashboard / Users / Projects / Boards). - Setup screen for first-admin creation (title, body, password fields + mismatch error + create-admin button). - Not-admin gate page. - EditUserModal (title, four labels, admin/active toggles, cancel/save). - UsersTab: search placeholder, count pluralisation, all 12 table columns, Activity / Edit / Delete actions, empty state, delete-confirm prompt with username interpolation. - ProjectsTab: search placeholder, count pluralisation, all 9 table columns, public/private badge labels, delete action + confirm with project-name interpolation, empty state. - All error messages (load failed / save failed / delete failed) fall back through t(). UserProfilePage - "New project" CTA, loading + empty + not-found states, "Private" project badge, "Copy shareable link" tooltip. - The /editor link uses localize() so /es/<username>'s "New project" button stays in Spanish. PricingPlaceholder - Title + the two paragraphs (self-hosted note + hosted Pro tier note + GitHub source note). Inline links wrapped via the Trans component so the link surface stays clickable in every locale without each translation having to re-write the HTML. EditorPage shell - Mobile bottom-tab labels (Code / Circuit), file-explorer toggle (Show / Hide), View mode aria-label, view-mode segmented control labels (Code / Both / Circuit), and the three "Drag to resize" handle tooltips on the panel splitters. Translations - en.json hand-curated for the new keys. - All 8 non-English locales auto-translated via the existing `npm run translate:i18n` pipeline (DeepSeek, ~5 min for the whole bundle, sameShape() validates each output before write). This closes Phase 2 of i18n. Phase 3 (DocsPage prose, AboutPage long-form paragraphs, the 15 SEO landing pages) is deliberately deferred — Docs/About are best handled by extracting the prose into JSON keys and running the same script, while the SEO pages are intentionally optimised for English keyword targeting and should not be machine-translated en masse.
2026-05-09 22:49:13 +07:00
title={t('editor.shell.dragResize')}
/>
<div style={{ height: bottomPanelHeight, flexShrink: 0 }}>
<CompilationConsole
isOpen={consoleOpen}
onClose={() => setConsoleOpen(false)}
logs={compileLogs}
onClear={() => setCompileLogs([])}
/>
</div>
</>
)}
</div>
</div>
feat(editor): view-mode toggle, agent-chat slot, toolbar polish Changes that ship to OSS — all benign for self-hosters, but most are extension points the velxio-prod overlay (and any private fork) needs to plug an in-editor AI chat into the page. Editor: - 3-way view-mode toggle (code / both / circuit) in the unified toolbar. Lets users hide a pane to give a right-docked sidebar (e.g. the AI chat overlay) more breathing room. Persisted in useEditorStore. - Default file explorer narrower (210 → 165 px); min 110. - Removed the redundant `tb-board-pill` (icon + "Editing: X" tooltip); the BoardSelector dropdown elsewhere already shows the active board. - Inlined Import/Export/Upload-firmware buttons; the 3-dot overflow menu gave up too much discoverability. Removed dead overflow state. Simulator: - Fix: global Delete/Backspace handler in SimulatorCanvas no longer fires when the event target is an INPUT/TEXTAREA/SELECT/contentEditable — affected any in-page text field, not just the chat overlay. Overlay extensibility: - New `data-velxio-slot="agent-chat"` at the bottom of EditorPage so pro overlays can portal a chat panel into the editor without forking the page. - vite.config.ts: preserveSymlinks=true when VITE_PRO_BUILD is set. Lets local-dev junctions (overlay tree → frontend/src/pro) resolve bare imports back to the OSS node_modules without resolving symlinks. Deps: - Added react-markdown + remark-gfm (rendered chat output) and @google/genai + zod (overlay agent loop). Tree-shaken from the OSS bundle when no pro code imports them. gitignore: - Ignore backend/app/pro/ and frontend/src/pro/ junctions used by developers running a private overlay against the OSS dev server. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 10:53:27 +07:00
{/* Resize handle (desktop only, and only when both panes are visible) */}
{!isMobile && viewMode === 'both' && (
<div className="resize-handle" onMouseDown={handleResizeMouseDown}>
<div className="resize-handle-grip" />
</div>
)}
{/* ── Simulator side ── */}
<div
className="simulator-panel"
style={{
feat(editor): view-mode toggle, agent-chat slot, toolbar polish Changes that ship to OSS — all benign for self-hosters, but most are extension points the velxio-prod overlay (and any private fork) needs to plug an in-editor AI chat into the page. Editor: - 3-way view-mode toggle (code / both / circuit) in the unified toolbar. Lets users hide a pane to give a right-docked sidebar (e.g. the AI chat overlay) more breathing room. Persisted in useEditorStore. - Default file explorer narrower (210 → 165 px); min 110. - Removed the redundant `tb-board-pill` (icon + "Editing: X" tooltip); the BoardSelector dropdown elsewhere already shows the active board. - Inlined Import/Export/Upload-firmware buttons; the 3-dot overflow menu gave up too much discoverability. Removed dead overflow state. Simulator: - Fix: global Delete/Backspace handler in SimulatorCanvas no longer fires when the event target is an INPUT/TEXTAREA/SELECT/contentEditable — affected any in-page text field, not just the chat overlay. Overlay extensibility: - New `data-velxio-slot="agent-chat"` at the bottom of EditorPage so pro overlays can portal a chat panel into the editor without forking the page. - vite.config.ts: preserveSymlinks=true when VITE_PRO_BUILD is set. Lets local-dev junctions (overlay tree → frontend/src/pro) resolve bare imports back to the OSS node_modules without resolving symlinks. Deps: - Added react-markdown + remark-gfm (rendered chat output) and @google/genai + zod (overlay agent loop). Tree-shaken from the OSS bundle when no pro code imports them. gitignore: - Ignore backend/app/pro/ and frontend/src/pro/ junctions used by developers running a private overlay against the OSS dev server. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 10:53:27 +07:00
width: isMobile
? '100%'
: viewMode === 'circuit'
? '100%'
: viewMode === 'code'
? '0%'
: `${100 - editorWidthPct}%`,
display:
(isMobile && mobileView !== 'circuit') || (!isMobile && viewMode === 'code')
? 'none'
: 'flex',
flexDirection: 'column',
}}
>
<div style={{ flex: 1, overflow: 'hidden', position: 'relative', minHeight: 0 }}>
<SimulatorCanvas headerSlot={!isMobile ? canvasHeaderSlot : null} />
</div>
{serialMonitorOpen && (
<>
<div
onMouseDown={handleBottomPanelResizeMouseDown}
style={resizeHandleStyle}
feat(i18n): translate AdminPage + UserProfile + Pricing + EditorPage shell (Editor block 10 — final cleanup) Closes the Phase 2 i18n rollout. Every visitor- and user-facing surface velxio renders in normal use now reads from t(). AdminPage (admin-only) - Header (panel title, logout) and the four tabs (Dashboard / Users / Projects / Boards). - Setup screen for first-admin creation (title, body, password fields + mismatch error + create-admin button). - Not-admin gate page. - EditUserModal (title, four labels, admin/active toggles, cancel/save). - UsersTab: search placeholder, count pluralisation, all 12 table columns, Activity / Edit / Delete actions, empty state, delete-confirm prompt with username interpolation. - ProjectsTab: search placeholder, count pluralisation, all 9 table columns, public/private badge labels, delete action + confirm with project-name interpolation, empty state. - All error messages (load failed / save failed / delete failed) fall back through t(). UserProfilePage - "New project" CTA, loading + empty + not-found states, "Private" project badge, "Copy shareable link" tooltip. - The /editor link uses localize() so /es/<username>'s "New project" button stays in Spanish. PricingPlaceholder - Title + the two paragraphs (self-hosted note + hosted Pro tier note + GitHub source note). Inline links wrapped via the Trans component so the link surface stays clickable in every locale without each translation having to re-write the HTML. EditorPage shell - Mobile bottom-tab labels (Code / Circuit), file-explorer toggle (Show / Hide), View mode aria-label, view-mode segmented control labels (Code / Both / Circuit), and the three "Drag to resize" handle tooltips on the panel splitters. Translations - en.json hand-curated for the new keys. - All 8 non-English locales auto-translated via the existing `npm run translate:i18n` pipeline (DeepSeek, ~5 min for the whole bundle, sameShape() validates each output before write). This closes Phase 2 of i18n. Phase 3 (DocsPage prose, AboutPage long-form paragraphs, the 15 SEO landing pages) is deliberately deferred — Docs/About are best handled by extracting the prose into JSON keys and running the same script, while the SEO pages are intentionally optimised for English keyword targeting and should not be machine-translated en masse.
2026-05-09 22:49:13 +07:00
title={t('editor.shell.dragResize')}
/>
<div style={{ height: bottomPanelHeight, flexShrink: 0 }}>
<SerialMonitor />
</div>
</>
)}
{oscilloscopeOpen && (
<>
<div
onMouseDown={handleBottomPanelResizeMouseDown}
style={resizeHandleStyle}
feat(i18n): translate AdminPage + UserProfile + Pricing + EditorPage shell (Editor block 10 — final cleanup) Closes the Phase 2 i18n rollout. Every visitor- and user-facing surface velxio renders in normal use now reads from t(). AdminPage (admin-only) - Header (panel title, logout) and the four tabs (Dashboard / Users / Projects / Boards). - Setup screen for first-admin creation (title, body, password fields + mismatch error + create-admin button). - Not-admin gate page. - EditUserModal (title, four labels, admin/active toggles, cancel/save). - UsersTab: search placeholder, count pluralisation, all 12 table columns, Activity / Edit / Delete actions, empty state, delete-confirm prompt with username interpolation. - ProjectsTab: search placeholder, count pluralisation, all 9 table columns, public/private badge labels, delete action + confirm with project-name interpolation, empty state. - All error messages (load failed / save failed / delete failed) fall back through t(). UserProfilePage - "New project" CTA, loading + empty + not-found states, "Private" project badge, "Copy shareable link" tooltip. - The /editor link uses localize() so /es/<username>'s "New project" button stays in Spanish. PricingPlaceholder - Title + the two paragraphs (self-hosted note + hosted Pro tier note + GitHub source note). Inline links wrapped via the Trans component so the link surface stays clickable in every locale without each translation having to re-write the HTML. EditorPage shell - Mobile bottom-tab labels (Code / Circuit), file-explorer toggle (Show / Hide), View mode aria-label, view-mode segmented control labels (Code / Both / Circuit), and the three "Drag to resize" handle tooltips on the panel splitters. Translations - en.json hand-curated for the new keys. - All 8 non-English locales auto-translated via the existing `npm run translate:i18n` pipeline (DeepSeek, ~5 min for the whole bundle, sameShape() validates each output before write). This closes Phase 2 of i18n. Phase 3 (DocsPage prose, AboutPage long-form paragraphs, the 15 SEO landing pages) is deliberately deferred — Docs/About are best handled by extracting the prose into JSON keys and running the same script, while the SEO pages are intentionally optimised for English keyword targeting and should not be machine-translated en masse.
2026-05-09 22:49:13 +07:00
title={t('editor.shell.dragResize')}
/>
<div style={{ height: bottomPanelHeight, flexShrink: 0 }}>
<Oscilloscope />
</div>
</>
)}
</div>
</div>
{showStarBanner && (
<GitHubStarBanner
onClose={handleDismissStarBanner}
onStarClick={handleStarClick}
round={starRound}
/>
)}
feat(editor): view-mode toggle, agent-chat slot, toolbar polish Changes that ship to OSS — all benign for self-hosters, but most are extension points the velxio-prod overlay (and any private fork) needs to plug an in-editor AI chat into the page. Editor: - 3-way view-mode toggle (code / both / circuit) in the unified toolbar. Lets users hide a pane to give a right-docked sidebar (e.g. the AI chat overlay) more breathing room. Persisted in useEditorStore. - Default file explorer narrower (210 → 165 px); min 110. - Removed the redundant `tb-board-pill` (icon + "Editing: X" tooltip); the BoardSelector dropdown elsewhere already shows the active board. - Inlined Import/Export/Upload-firmware buttons; the 3-dot overflow menu gave up too much discoverability. Removed dead overflow state. Simulator: - Fix: global Delete/Backspace handler in SimulatorCanvas no longer fires when the event target is an INPUT/TEXTAREA/SELECT/contentEditable — affected any in-page text field, not just the chat overlay. Overlay extensibility: - New `data-velxio-slot="agent-chat"` at the bottom of EditorPage so pro overlays can portal a chat panel into the editor without forking the page. - vite.config.ts: preserveSymlinks=true when VITE_PRO_BUILD is set. Lets local-dev junctions (overlay tree → frontend/src/pro) resolve bare imports back to the OSS node_modules without resolving symlinks. Deps: - Added react-markdown + remark-gfm (rendered chat output) and @google/genai + zod (overlay agent loop). Tree-shaken from the OSS bundle when no pro code imports them. gitignore: - Ignore backend/app/pro/ and frontend/src/pro/ junctions used by developers running a private overlay against the OSS dev server. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 10:53:27 +07:00
{/* Slot reserved for the private pro overlay (e.g. agent chat panel).
Self-hosted builds without an overlay see nothing here. */}
<div data-velxio-slot="agent-chat" />
</div>
);
};