2026-04-22 02:45:45 +07:00
|
|
|
import { createRoot } from 'react-dom/client';
|
2026-05-11 10:26:08 +07:00
|
|
|
import { loader } from '@monaco-editor/react';
|
2026-04-22 02:45:45 +07:00
|
|
|
import './index.css';
|
feat(i18n): react-i18next foundation + 9-locale support for header / footer
This is Phase 1 of multi-language support: the visible chrome (header,
footer, language switcher) and routing are wired up for all 9 locales
(en, es, pt-br, it, fr, zh-cn, de, ja, ru) — same set the blog at
velxio.dev/blog/ already supports. The Editor and the long-form
landing-page copy are still English-only and will be translated in a
follow-up.
Infrastructure
- frontend/src/i18n/config.ts: locale registry + LOCALE_META (htmlLang,
native name, og locale, dir). Mirrors pro/blog/src/i18n/config.ts so
cookie sync stays consistent.
- frontend/src/i18n/cookie.ts: read/write the velxio_locale cookie at
Path=/; Max-Age=1y; SameSite=Lax (Secure on HTTPS). The blog reads
the same cookie via an inline script in its Layout.astro.
- frontend/src/i18n/path.ts: getLocaleFromPath / stripLocaleFromPath /
localizedPath / switchLocale / blogUrlFor — match the blog's helpers
one-to-one.
- frontend/src/i18n/index.ts: i18next bootstrap. English bundle is
inlined synchronously for first paint; non-default locales are
lazy-loaded via dynamic import on demand. Initial locale is decided
in priority order URL > cookie > navigator > en.
- frontend/src/i18n/LocaleSync.tsx: top-level wrapper inside <Router>.
On every URL change loads the matching locale bundle, calls
i18n.changeLanguage, writes the cookie, and mirrors the locale onto
<html lang> and dir.
- frontend/src/i18n/useLocalizedNavigate.ts: useCurrentLocale,
useLocalizedHref, useLocalizedNavigate hooks for components that
build internal links.
Routing
- App.tsx: route table extracted to a single ROUTES array, then
registered twice — once at the root (default English) and once
nested under each non-default locale (`/<locale>/...`). Explicit
per-locale parent routes (rather than a generic `:lang` param) so
React Router never accidentally swallows a real top-level path
like `/circuit-simulator` as a locale segment.
Header / Footer
- LanguageSwitcher.tsx + .css: dropdown matching the blog's
LanguageSwitcher.astro. Globe icon + locale code on the trigger,
native names + ISO codes in the menu. Click → `switchLocale()`
rewrites the URL under the new locale; LocaleSync handles the
rest (load bundle, change language, write cookie).
- AppHeader.tsx: every nav label and the auth dropdown copy now
goes through `t('header.nav.*')`, `t('header.auth.*')`. All
internal Links wrapped with localize() so navigation stays
inside the active locale. Added a "Blog" link computed via
`blogUrlFor(currentLocale)` so /es/ → /blog/es/, etc.
- LandingPage.tsx: footer About-Velxio paragraph reads from
t('footer.about').
Translations (Phase 1 strings)
- frontend/src/i18n/locales/<locale>/common.json: nav labels, auth
buttons, footer About copy. Hand-translated for all 9 locales,
AGPLv3 / brand names preserved as-is.
Tooling
- frontend/scripts/translate-i18n.mjs: standalone Node script that
takes the en.json bundles and auto-translates them to the 8 other
locales via DeepSeek (primary) + Gemini (fallback). One LLM call
per (locale, namespace) pair. Run after extracting new strings
with `npm run translate:i18n`.
Phase 2 (deferred)
- Editor (toolbar, file explorer, simulator canvas, component picker,
library manager, error toasts) — hundreds of strings.
- Examples / Docs / About / Profile pages.
- The translate-i18n.mjs script is ready to handle these once the
strings have been extracted into JSON keys.
2026-05-09 10:40:37 +07:00
|
|
|
// Side-effect import: initialises i18next BEFORE any component renders so
|
|
|
|
|
// useTranslation() always resolves against a live instance. Must come
|
|
|
|
|
// before App.
|
|
|
|
|
import './i18n';
|
2026-08-29 10:35:49 +07:00
|
|
|
import './services/EmbedBridge';
|
2026-07-30 22:06:05 +07:00
|
|
|
import { markProExamplesSettled } from './data/examples';
|
refactor(routes): OSS is the editor; marketing moves to the overlay
The open-source build now ships exactly its product surface: /editor, the
examples gallery (/examples, /examples/:id) and the per-example editor
(/example/:id). Landing, about, pricing, docs, the 14 keyword-targeted
simulator landings and the v2/v2.5/v3 showcases move to the private
overlay, registered through the same registerProRoutes seam that already
carries login/admin/classroom.
Root behaves per build: an overlay that registers an index route claims
'/' (velxio.dev keeps its landing); otherwise '/' redirects to /editor.
The redirect waits for the overlay import to settle — same contract as
markProExamplesSettled — so a velxio.dev visitor is never bounced into
the editor because the landing was 300ms away from registering.
Prerender and sitemap follow the split: entry-server pulls the marketing
page map from '@pro/pages/marketing' behind a VITE_PRO_BUILD-gated dynamic
import (the proven main.tsx pattern, with an OSS stub for tsc), and
generate-sitemap lists only served routes in OSS builds (2 URLs) while
pro builds stay byte-identical — verified against a pre-migration
baseline: sitemap (37 URLs) and prerendered /, /about, /docs,
/arduino-simulator, /esp32-simulator, /v3 all identical modulo hashed
asset names. OSS prerender drops exactly the 32 marketing pages (348→316).
The OSS header slims down to match: Editor, Examples, GitHub, Discord —
the marketing links only render in pro builds, where their routes exist.
The editor Help menu links those pages absolutely (velxio.dev) in OSS,
exactly like the desktop app's Help menu.
2026-07-30 22:50:14 +07:00
|
|
|
import { markProRoutesSettled } from './lib/proRoutes';
|
|
|
|
|
|
|
|
|
|
/** The overlay import has settled (either way): registries are final. */
|
|
|
|
|
const markProOverlaySettled = (): void => {
|
|
|
|
|
markProExamplesSettled();
|
|
|
|
|
markProRoutesSettled();
|
|
|
|
|
};
|
2026-04-22 02:45:45 +07:00
|
|
|
import './components/velxio-components/IC74HC595';
|
|
|
|
|
import './components/velxio-components/LogicGateElements';
|
|
|
|
|
import './components/velxio-components/TransistorElements';
|
|
|
|
|
import './components/velxio-components/OpAmpElements';
|
|
|
|
|
import './components/velxio-components/PowerElements';
|
|
|
|
|
import './components/velxio-components/DiodeElements';
|
|
|
|
|
import './components/velxio-components/RelayElements';
|
|
|
|
|
import './components/velxio-components/LogicICElements';
|
2026-06-01 02:28:29 +07:00
|
|
|
import './components/velxio-components/MotorDriverElements';
|
2026-04-22 02:45:45 +07:00
|
|
|
import './components/velxio-components/FlipFlopElements';
|
|
|
|
|
import './components/velxio-components/RaspberryPi3Element';
|
|
|
|
|
import './components/velxio-components/Bmp280Element';
|
feat(components): DS3231 RTC module + GPS NEO-6M NMEA talker
DS3231 (I2C 0x68):
- New velxio-ds3231 Web Component (4-pin module: GND/VCC/SDA/SCL) with
pinInfo per CLAUDE.md 6a; registered in elements-register + main.tsx.
- Catalog entry in scripts/component-overrides.json (_customComponents)
mirrored into components-metadata.json exactly as the generator emits it
(verified by cloning wokwi-elements and running generate:metadata: zero drift).
- The existing VirtualDS3231 device sim (DS1307-compatible time regs
0x00-0x06 + control 0x0E / status 0x0F / temperature 0x11-0x12 at 0.25 C
steps) is now user-reachable from the picker; the AVR/RP2040 attach path
gains live temperature updates via the SensorControlPanel (new ds3231
slider in sensorControlConfig).
GPS NEO-6M (UART talker, 9600 baud):
- New velxio-gps-neo6m Web Component (VCC/RX/TX/GND, pulsing PPS LED).
- New part sim GpsParts.ts: emits a valid GPGGA+GPRMC cycle per second
(correct XOR checksums, ddmm.mmmmm coords, UTC clock advancing 1 s/cycle)
with lat/lng/altitude/speed configurable via property dialog and live
via a new SensorControlPanel entry. Defaults: Madrid 40.4168 N 3.7038 W.
- Injection routes, chosen by classifying the wired board pin:
1) hardware UART RX -> new uniform sim.feedUart(uart, data) seam
(AVR USART0, RP2040 uart0/1 via rp2040js feedByte, ESP32 uart0/2 and
STM32 USART1/2 via their bridge shims); byte feed is paced in 8-byte
wall-clock chunks so 32-byte RX FIFOs never overflow;
2) any other digital pin on a cycle-accurate sim -> real 8N1 bit-banged
waveform via schedulePinChange, so SoftwareSerial decodes it;
3) feedUart rejection (e.g. Mega USART1-3, not modelled by avr8js)
degrades permanently to the bit-banged path.
- Interconnect already probed sim.feedUart; implementing it also upgrades
cross-board UART byte shortcuts for RP2040 uart1.
Tests: new gps-neo6m.test.ts (16 tests: NMEA builders, byte injection on
Uno RX0 / ESP32 RX2, Mega RX1 fallback, waveform decode back to $GPGGA,
live position updates, PPS pulse) + 3 new ds3231 AVR-path tests in
protocol-parts.test.ts. Full frontend suite: 2294 passed, 2 pre-existing
Galaksija Z80 timeouts that pass in isolation. npx tsc --noEmit clean.
2026-08-01 01:38:06 +07:00
|
|
|
import './components/velxio-components/Ds3231Element';
|
|
|
|
|
import './components/velxio-components/GpsNeo6mElement';
|
refactor: rename wokwi-libs/ → third-party/
The directory grew well beyond Wokwi-only contents: it now hosts
lcgamboa's QEMU fork (qemu-lcgamboa), Espressif's esp32-camera, the
ngspice WASM build, fritzing-parts, picowi, an alternative QEMU
(qemu-esp32), the 100_Days_100_IoT_Projects examples repo, and
Wokwi's own avr8js/rp2040js/wokwi-elements/wokwi-features/wokwi-boards.
"wokwi-libs" was misleading — half the contents have nothing to do
with Wokwi. "third-party/" is the standard convention for vendored
external dependencies.
Mechanical changes:
Path rename:
wokwi-libs/ → third-party/
update-wokwi-libs.bat → update-third-party.bat
docs/WOKWI_LIBS.md → docs/THIRD_PARTY.md
Submodule reconfiguration:
.gitmodules — 4 path= and section names updated
.git/modules/wokwi-libs/ → .git/modules/third-party/
each submodule's .git file rewired to ../../.git/modules/third-party/<name>
Reference updates (~80 files): vite.config.ts aliases, Dockerfile
COPY paths, GH Actions workflow steps, build_qemu_*.sh, all
docs/* and test/*/autosearch/* entries that mention the path,
package-lock.json file: dependencies, .gitignore patterns,
sitemap.xml + index.html SEO blurbs, scripts/generate-component-*,
.dockerignore, .idea/vcs.xml. Bulk replaced both `wokwi-libs/`
(path) and bare `wokwi-libs` (textual mentions in docs/comments).
Verified:
- npx tsc -b --noEmit produces no new errors related to these paths
- vite.config.ts aliases now point at ../third-party/avr8js etc.
- All 4 git submodules (avr8js, rp2040js, wokwi-elements,
wokwi-features) are linked under third-party/ with their
worktrees re-populated and config files referencing the new path
- `grep -r wokwi-libs` returns zero hits outside node_modules,
.vite, frontend/dist, third-party/ (upstream submodule contents),
*.pyc caches, and *.dll.pre-camera rollback binaries
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 10:58:57 +07:00
|
|
|
import './components/velxio-components/EPaperElement';
|
2026-04-22 02:45:45 +07:00
|
|
|
import App from './App.tsx';
|
2026-03-03 10:20:49 +07:00
|
|
|
|
2026-05-11 10:26:08 +07:00
|
|
|
// Configure monaco-editor for offline use via local static assets
|
2026-05-11 21:36:51 +07:00
|
|
|
const monacoVsPath = `${import.meta.env.BASE_URL}monaco/vs`;
|
|
|
|
|
loader.config({ paths: { vs: monacoVsPath } });
|
2026-05-11 10:26:08 +07:00
|
|
|
|
2026-04-22 02:45:45 +07:00
|
|
|
createRoot(document.getElementById('root')!).render(<App />);
|
2026-05-05 00:03:20 +07:00
|
|
|
|
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
|
|
|
// Tear down the Tauri-only splash now that React has mounted. Wait
|
|
|
|
|
// two animation frames so React's first paint commits before we
|
|
|
|
|
// touch the splash — otherwise users see a black flash between the
|
|
|
|
|
// splash fading and the editor first appearing. Fade via CSS
|
|
|
|
|
// transition for a smoother handoff, then remove the node entirely
|
|
|
|
|
// once the transition finishes.
|
|
|
|
|
requestAnimationFrame(() => {
|
|
|
|
|
requestAnimationFrame(() => {
|
|
|
|
|
const splash = document.getElementById('velxio-splash');
|
|
|
|
|
if (!splash) return;
|
|
|
|
|
splash.style.transition = 'opacity 250ms ease-out';
|
|
|
|
|
splash.style.opacity = '0';
|
|
|
|
|
splash.style.pointerEvents = 'none';
|
|
|
|
|
window.setTimeout(() => splash.remove(), 320);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-05 00:03:20 +07:00
|
|
|
// Optional pro overlay. The `@pro` import resolves to a no-op stub in the
|
|
|
|
|
// open-source build (see vite.config.ts) and to the real overlay only when
|
|
|
|
|
// VITE_PRO_BUILD=true at build time. The dynamic import keeps the pro chunk
|
|
|
|
|
// out of the OSS bundle entirely (Vite tree-shakes the never-taken branch).
|
2026-05-21 09:50:19 +07:00
|
|
|
//
|
2026-05-27 00:09:07 +07:00
|
|
|
// Two desktop modes since v0.4.0:
|
|
|
|
|
// - VITE_PRO_BUILD + VITE_DESKTOP → slim pro entry (@pro/desktop_index)
|
|
|
|
|
// that ONLY mounts the AI agent + DiagnoseCompileButton, no analytics
|
|
|
|
|
// / sessions / billing / admin / save overrides (those talk to
|
|
|
|
|
// velxio.dev with cookies the desktop doesn't have).
|
|
|
|
|
// - VITE_PRO_BUILD only (web) → full mountPro with every surface.
|
|
|
|
|
// VITE_DESKTOP alone (no pro) stays a pure-OSS desktop build.
|
|
|
|
|
if (import.meta.env.VITE_PRO_BUILD) {
|
|
|
|
|
if (import.meta.env.VITE_DESKTOP) {
|
|
|
|
|
import('@pro/desktop_index')
|
|
|
|
|
.then((m) => m.mountProDesktop?.())
|
2026-07-30 22:06:05 +07:00
|
|
|
.catch((err) => console.warn('[pro-desktop] failed to load slim overlay:', err))
|
refactor(routes): OSS is the editor; marketing moves to the overlay
The open-source build now ships exactly its product surface: /editor, the
examples gallery (/examples, /examples/:id) and the per-example editor
(/example/:id). Landing, about, pricing, docs, the 14 keyword-targeted
simulator landings and the v2/v2.5/v3 showcases move to the private
overlay, registered through the same registerProRoutes seam that already
carries login/admin/classroom.
Root behaves per build: an overlay that registers an index route claims
'/' (velxio.dev keeps its landing); otherwise '/' redirects to /editor.
The redirect waits for the overlay import to settle — same contract as
markProExamplesSettled — so a velxio.dev visitor is never bounced into
the editor because the landing was 300ms away from registering.
Prerender and sitemap follow the split: entry-server pulls the marketing
page map from '@pro/pages/marketing' behind a VITE_PRO_BUILD-gated dynamic
import (the proven main.tsx pattern, with an OSS stub for tsc), and
generate-sitemap lists only served routes in OSS builds (2 URLs) while
pro builds stay byte-identical — verified against a pre-migration
baseline: sitemap (37 URLs) and prerendered /, /about, /docs,
/arduino-simulator, /esp32-simulator, /v3 all identical modulo hashed
asset names. OSS prerender drops exactly the 32 marketing pages (348→316).
The OSS header slims down to match: Editor, Examples, GitHub, Discord —
the marketing links only render in pro builds, where their routes exist.
The editor Help menu links those pages absolutely (velxio.dev) in OSS,
exactly like the desktop app's Help menu.
2026-07-30 22:50:14 +07:00
|
|
|
.finally(markProOverlaySettled);
|
2026-05-27 00:09:07 +07:00
|
|
|
} else {
|
|
|
|
|
import('@pro/index')
|
|
|
|
|
.then((m) => m.mountPro?.())
|
2026-07-30 22:06:05 +07:00
|
|
|
.catch((err) => console.warn('[pro] failed to load overlay:', err))
|
refactor(routes): OSS is the editor; marketing moves to the overlay
The open-source build now ships exactly its product surface: /editor, the
examples gallery (/examples, /examples/:id) and the per-example editor
(/example/:id). Landing, about, pricing, docs, the 14 keyword-targeted
simulator landings and the v2/v2.5/v3 showcases move to the private
overlay, registered through the same registerProRoutes seam that already
carries login/admin/classroom.
Root behaves per build: an overlay that registers an index route claims
'/' (velxio.dev keeps its landing); otherwise '/' redirects to /editor.
The redirect waits for the overlay import to settle — same contract as
markProExamplesSettled — so a velxio.dev visitor is never bounced into
the editor because the landing was 300ms away from registering.
Prerender and sitemap follow the split: entry-server pulls the marketing
page map from '@pro/pages/marketing' behind a VITE_PRO_BUILD-gated dynamic
import (the proven main.tsx pattern, with an OSS stub for tsc), and
generate-sitemap lists only served routes in OSS builds (2 URLs) while
pro builds stay byte-identical — verified against a pre-migration
baseline: sitemap (37 URLs) and prerendered /, /about, /docs,
/arduino-simulator, /esp32-simulator, /v3 all identical modulo hashed
asset names. OSS prerender drops exactly the 32 marketing pages (348→316).
The OSS header slims down to match: Editor, Examples, GitHub, Discord —
the marketing links only render in pro builds, where their routes exist.
The editor Help menu links those pages absolutely (velxio.dev) in OSS,
exactly like the desktop app's Help menu.
2026-07-30 22:50:14 +07:00
|
|
|
.finally(markProOverlaySettled);
|
2026-05-27 00:09:07 +07:00
|
|
|
}
|
2026-07-30 22:06:05 +07:00
|
|
|
} else {
|
refactor(routes): OSS is the editor; marketing moves to the overlay
The open-source build now ships exactly its product surface: /editor, the
examples gallery (/examples, /examples/:id) and the per-example editor
(/example/:id). Landing, about, pricing, docs, the 14 keyword-targeted
simulator landings and the v2/v2.5/v3 showcases move to the private
overlay, registered through the same registerProRoutes seam that already
carries login/admin/classroom.
Root behaves per build: an overlay that registers an index route claims
'/' (velxio.dev keeps its landing); otherwise '/' redirects to /editor.
The redirect waits for the overlay import to settle — same contract as
markProExamplesSettled — so a velxio.dev visitor is never bounced into
the editor because the landing was 300ms away from registering.
Prerender and sitemap follow the split: entry-server pulls the marketing
page map from '@pro/pages/marketing' behind a VITE_PRO_BUILD-gated dynamic
import (the proven main.tsx pattern, with an OSS stub for tsc), and
generate-sitemap lists only served routes in OSS builds (2 URLs) while
pro builds stay byte-identical — verified against a pre-migration
baseline: sitemap (37 URLs) and prerendered /, /about, /docs,
/arduino-simulator, /esp32-simulator, /v3 all identical modulo hashed
asset names. OSS prerender drops exactly the 32 marketing pages (348→316).
The OSS header slims down to match: Editor, Examples, GitHub, Discord —
the marketing links only render in pro builds, where their routes exist.
The editor Help menu links those pages absolutely (velxio.dev) in OSS,
exactly like the desktop app's Help menu.
2026-07-30 22:50:14 +07:00
|
|
|
// No overlay is coming: what the registries have now is all there will be.
|
|
|
|
|
markProOverlaySettled();
|
2026-05-05 00:03:20 +07:00
|
|
|
}
|
2026-05-21 09:50:19 +07:00
|
|
|
|
|
|
|
|
// Desktop-only hooks (ESP32 QEMU prompt now, welcome screen in Phase 3).
|
|
|
|
|
// Dynamic import so the OSS bundle never pulls this in.
|
|
|
|
|
if (import.meta.env.VITE_DESKTOP) {
|
|
|
|
|
import('./desktop/index')
|
|
|
|
|
.then((m) => m.mountDesktop?.())
|
|
|
|
|
.catch((err) => console.warn('[desktop] failed to load hooks:', err));
|
|
|
|
|
}
|
2026-08-15 13:29:39 +07:00
|
|
|
|
|
|
|
|
// DEV-only: expose the core stores for E2E harnesses (the platform-bugs QA
|
|
|
|
|
// harness drives the STORE paths — property updates, group switches — the
|
|
|
|
|
// way the agent tools do, which raw DOM access cannot reach). Guarded by
|
|
|
|
|
// import.meta.env.DEV so production bundles never ship it.
|
|
|
|
|
if (import.meta.env.DEV) {
|
|
|
|
|
Promise.all([
|
|
|
|
|
import('./store/useSimulatorStore'),
|
|
|
|
|
import('./store/useEditorStore'),
|
|
|
|
|
import('./store/useElectricalStore'),
|
|
|
|
|
]).then(([sim, ed, el]) => {
|
|
|
|
|
(window as unknown as Record<string, unknown>).__velxioStores = {
|
|
|
|
|
useSimulatorStore: sim.useSimulatorStore,
|
|
|
|
|
useEditorStore: ed.useEditorStore,
|
|
|
|
|
useElectricalStore: el.useElectricalStore,
|
|
|
|
|
getBoardSimulator: sim.getBoardSimulator,
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}
|