Commit Graph

358 Commits

Author SHA1 Message Date
David Montero Crespo 7f437a753e feat(i18n): translate DocsPage prose (block 14)
Wire useTranslation() + Trans into DocsPage.tsx. ~330 user-facing
strings across 13 sections (intro, getting-started, emulator, riscv,
esp32, rp2040, rpi3, components, roadmap, architecture, third-party,
mcp, setup) plus sidebar nav + page chrome are now keyed under docs.*.

Strings with inline <a>, <code>, <strong>, <em> use the <Trans/>
component with mapped slots; bare prose uses t().

Code blocks, FQBNs, hex addresses, library names visible as link text,
and JSON-LD schema strings stay in English on purpose.

Internal Link to=... wrapped with localize() so /es/docs/... etc.
keep their locale prefix.

The English docs bundle is split in half (docs.json ~22KB +
docs2.json ~22KB) so each fits inside DeepSeek's 8192-token output
window. The i18n bootstrap merges both halves into the docs.* keyspace
under the default common namespace.

All 9 locales regenerated via DeepSeek (parallel run for the two
namespaces).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 17:47:19 -03:00
David Montero Crespo 0ff76a0f9d feat(i18n): translate Velxio 2.0 + 2.5 release pages (block 13)
Wire useTranslation() into Velxio2Page and Velxio25Page: hero badge,
accent, subtitle, CTAs, board/example/outcome cards, OSS section, and
footer links all keyed under landing.v2.* and landing.v25.*.

Split en/common.json (34KB) into common.json (25KB) + releases.json
(9KB) so each translation request stays inside DeepSeek's 8192-token
output cap. i18n bootstrap merges both bundles into the default common
namespace at load time, lazy loader fetches both per locale.

translate-i18n.mjs: set max_tokens=8192 + response_format json_object
on the DeepSeek call so future bundles closer to the cap don't get
silently truncated.

All 9 locales regenerated via DeepSeek (fr/de/es/it/pt-br/zh-cn/ja/ru).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 17:19:16 -03:00
David Montero Crespo aae3ab1c5c feat(i18n): translate small editor remates + admin internals (Blocks 11+12)
Block 11 — small editor surfaces
- PinPickerDialog: close, filter pins, no-match, rotate / delete
  action buttons.
- RaspberryPiWorkspace: connection status (Connected / Starting…
  / Offline), Start Pi button + tooltip, Connect / Disconnect
  buttons + tooltips, Terminal tab label, file-tab close, the
  full offline overlay (title / subtitle / start CTA / two-line
  note about the staging area), terminal-loading + no-file-
  selected fallbacks.
- GitHubStarBanner: aria-label, title, body copy, Star CTA,
  dismiss button.
- ExampleLoaderPage: "Loading example…" status, "Example {{id}}
  not found." 404 line, "Browse all examples" recovery link.
  /editor and /examples links wrapped in localize().

Block 12 — admin-only internals (only admins ever see these)
- AdminBoardsTab: section headings ("By board family" / "By
  exact FQBN"), full table column headers (Family / FQBN /
  Compiles / Errors / Success rate / Runs / Distinct users /
  Distinct projects), range selector label, no-data fallback,
  load-failed error.
- AdminDashboardTab: 8 KPI cards (Total users / Total projects
  / Compiles / Runs / DAU / WAU / MAU / Public-Private), all
  chart titles + subtitles (Activity over time, Compiles by
  board family, Board diversity for pricing signal, Top FQBNs,
  Top countries with Cloudflare disclaimer, Top users / Top
  projects), table column headers, no-data fallbacks, load
  errors. Pluralised "{{count}} board(s)" via i18next plurals
  in the diversity pie chart.
- UserActivityModal: title with username interpolation, subtitle,
  range selector label, table column headers (Date / Project /
  Compiles / Errors / Runs / Saves), pluralised
  "{{count}} project(s)" line, deleted-project / no-project
  placeholder strings, no-activity empty state, load-failed.

All 8 non-English locales auto-translated via the
`scripts/translate-i18n.mjs` DeepSeek pipeline (one --force run,
~7 min). sameShape() validation passed on every output.
2026-05-09 16:22:16 -03:00
David Montero Crespo 6bc9fe80ce 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 12:49:13 -03:00
David Montero Crespo 99156c9c9d feat(editor): translate Oscilloscope + property dialog + selection bar + console + custom chips + sensor + board picker (Editor block 9)
This commit closes the cluster of small editor surfaces that touch
the active simulation experience. Every visible control on these
panels now reads from t() keys.

Translated:
- Oscilloscope panel (title, Add Channel button + tooltip, Time/div
  label, Run / Pause toggle copy + tooltips, Clear, empty-state copy
  + hint, per-channel remove tooltip).
- ComponentPropertyDialog (close, pin-roles header with two
  wire-mode variants, Arduino Pin label, rotate / delete buttons +
  the inline confirm-delete prompt with name interpolation).
- SelectionActionBar (toolbar aria-label, Rotate / Delete / Deselect
  with kind-aware delete labels for wire / component / board).
- CompilationConsole (Output title, error / warning badge counts
  with i18next pluralisation, filter dropdown, autoscroll label,
  Clear + Close icon tooltips, empty-state).
- CustomChipDialog (header with chipName interpolation, Examples /
  Editor tabs, Attributes panel header, compile status messages
  including the "✓ Compiled — N KB" success line, footer
  Cancel / Save & Place / Compile first buttons).
- SensorControlPanel (close button).
- BoardPickerModal (Add Board heading).

Translation pipeline
- en.json gets the new keys hand-curated.
- The 8 non-English locales were auto-translated via DeepSeek
  using the existing scripts/translate-i18n.mjs pipeline (one
  --force run, ~1 min total). Output validated with sameShape()
  before write so any LLM-introduced key drift would have failed
  loudly.

Quality note
- DeepSeek's translations now cover the entire bundle, including
  earlier hand-translated content. Tone may differ slightly from
  the prior hand passes but the meaning is consistent and brand /
  technical terms (Velxio, ngspice-WASM, ATmega328P, ESP32-C3,
  etc.) are preserved unchanged in every locale per the prompt
  invariants.
2026-05-09 12:25:30 -03:00
David Montero Crespo ea290d0c22 feat(editor): translate InstallLibrariesModal + SerialMonitor (Editor block 8)
InstallLibrariesModal — auto-install prompt that fires when an
example needs libraries:
- Title, subtitle (with the "Installing X of Y" progress
  interpolation, the all-done success state, and the singular /
  plural prompt explaining the requirement count).
- Per-row status badges (pending / installing… / installed /
  error) plus the Wokwi-hosted-library tooltip.
- Footer buttons: Close / Skip / "Install All ({{count}})" with
  loading variant.

SerialMonitor — multi-board tabbed serial console:
- Empty-state when no board is on the canvas.
- Right-side tab controls: Autoscroll checkbox, Clear button +
  tooltip.
- The "(Open IoT Gateway)" inline link rendered next to detected
  AP IP addresses.
- Output-area placeholders for the running-but-no-data and
  before-start states.
- Send button + input placeholder (different copy for MicroPython
  REPL vs raw Serial input).
- The line-ending dropdown options (None / Newline / Carriage
  return / Both).

Hand-translated for all 9 locales. Hotkeys (Ctrl+C) and dropdown
values stay untranslated (constants the firmware reads).

Pending in Phase 3:
- Oscilloscope panel, custom-chip dialog, sensor control panel.
- ComponentPropertyDialog (per-component property forms).
- Admin / Profile / Project pages.
- Long-form docs prose (DocsPage 2715 lines, AboutPage long
  paragraphs).
- 15 SEO landing pages (intentionally English for keyword targeting).
2026-05-09 11:35:22 -03:00
David Montero Crespo c1b0398c0d feat(editor): translate ComponentPicker + LibraryManager modals (Editor block 7)
ComponentPickerModal — the "Add Component" dialog:
- Header (title + close button), search input placeholder + clear,
  category tabs (All Components / Boards), loading + empty-state
  copy, "Clear filters" button.

LibraryManagerModal — the Arduino library browser:
- Window title, Search / Installed tabs, filter input placeholder.
- Search-tab states: searching-for-query, generic loading, no
  results (with optional query interpolation).
- Per-library row: "by {{author}}" caption, Install / Installing /
  Uninstall / Uninstalling button labels.
- Installed-tab empty-state with the prompt to use the Search tab.

Brand and product names left untouched ("LIBRARY MANAGER" stays
all-caps in English; the localised variants follow each language's
convention for product UI titles). Hand-translated for all 9
locales.

InstallLibrariesModal still pending — it's the auto-install
prompt that fires when an example needs libraries; smaller scope
but lives in the same area.
2026-05-09 11:29:07 -03:00
David Montero Crespo 4df81a3eda feat(examples): translate ExamplesPage + ExamplesGallery to 9 locales
The /examples gallery is fully localised:
- Header (heading + subtitle).
- Search input placeholder + aria-label + the clear button.
- Match-count tag with i18next pluralisation (handles _one /
  _other and Russian's _few / _many).
- Category and Difficulty filter labels + their button labels
  (basics / sensors / displays / communication / games / robotics
  / circuits; beginner / intermediate / advanced).
- Per-card "Copy shareable link" tooltip.
- Empty-state copy with two variants (with-search / without-
  search) interpolating the search query.
- Reset-filters button.
- The library-install progress overlay copy from
  ExamplesPage.tsx ("Installing libraries (N/M)") with done/total
  interpolation.

Internal /editor link uses localize() so a Spanish reader who
clicks an example lands on /es/editor.

Hand-translated for all 8 non-English locales. Per-example titles
+ descriptions are NOT i18n yet — they live in the
src/data/examples* tables and would need a separate pipeline.
DocsPage (2715 lines of prose) deferred too — best handled by
running scripts/translate-i18n.mjs once the keys are extracted.
2026-05-09 11:24:53 -03:00
David Montero Crespo 6e09725727 feat(about): translate AboutPage chrome + CTA + footer (partial)
The visible chrome of /about now reads from i18n in all 9 locales:
- Hero title + subtitle
- 7 section headings (Story / How It Works / Open Source Philosophy /
  Creator / Recent releases / Community & Press / CTA)
- Final CTA card (title, subtitle, "Open Editor" button)
- Footer copy switched to t('footer.about') so it shows the AGPLv3
  About-Velxio paragraph instead of the stale MIT/avr8js credit
- Footer + CTA Links wrapped in localize() so /es/about's "Open Editor"
  routes to /es/editor

Long-form prose (Story body paragraphs, Open Source Philosophy
paragraphs, Creator bio, Releases blurbs, Personal-story quote, Press
section) deliberately stays in English in this commit. Each is a
multi-paragraph chunk that benefits from a curated translation pass
rather than an inline machine pass — slate it for a follow-up.

Tech-stack tags (Java, Python, React, Docker, etc.) and the creator's
name + role + GitHub/LinkedIn/Medium link captions stay untranslated:
all proper nouns / brand identifiers.
2026-05-09 03:27:40 -03:00
David Montero Crespo 20b22b1398 feat(auth): translate LoginPage + RegisterPage to 9 locales
Both auth forms now go through t('auth.login.*') and
t('auth.register.*'):
- Title + subtitle, email / password / username labels with
  username placeholder and password-min-length placeholder.
- Submit button toggles between idle and loading states.
- "or" divider, "Continue with Google" button, and the
  switch-to-other-form footer link.
- Inline validation errors: reserved username, username regex,
  password length, plus the generic catch-all from the API.

Internal /editor and /login / /register links wrapped in
localize() so a Spanish user who registers stays at /es/editor
after success.

AboutPage (519 lines) deferred to its own commit — too dense for
the same change.
2026-05-09 03:23:09 -03:00
David Montero Crespo ecc35f72cb feat(editor): translate SimulatorCanvas header + remove dialog (Editor block 4)
The canvas header (the bar above the simulation area) and the
"Remove board?" confirmation dialog now read from i18n.

Translated:
- Status dot tooltip (Running / Stopped).
- Active board selector tooltip + "No board" placeholder + the
  hint that prompts the user to add a board.
- Undo / Redo buttons: aria-label, dynamic title with the action
  description and the empty-state fallback. Action descriptions
  themselves stay untranslated (they come from the editor history
  store as English literals — translating them would mean reaching
  into a different store; deferred).
- Serial Monitor and Oscilloscope toggles (button title + label).
- Zoom in / out / reset-view buttons.
- Component count tooltip + Add Component button.
- The error-banner Dismiss button.
- "Remove board" item in the right-click menu, with a localised
  "(N wires)" parenthetical via i18next pluralisation.
- The full removal confirmation dialog: title with board label
  interpolation, body copy with optional connected-wires sentence,
  Cancel + Remove buttons.

Pluralisation uses i18next's _one / _other (and _few / _many for
Russian) suffixes so wire counts read naturally per language.

Hand-translated for all 8 non-English locales. Untouched (deferred):
the property dialog, custom-chip dialog, sensor control panel, and
the various inline tooltips on board pins and wire endpoints —
those are denser and benefit from a separate pass.
2026-05-09 03:20:36 -03:00
David Montero Crespo fa4f3d6e80 feat(editor): translate Save / Share / LoginPrompt modals (Editor block 3)
The three modal dialogs that fire during the editor's routine save +
share + auth-required flows are now fully localised across all 9
locales.

LoginPromptModal
- Title, body, and the three buttons (Sign in / Create account /
  Cancel). Sign in / Sign up Links use localize() so a Spanish
  reader prompted to log in lands at /es/login rather than dropping
  back to English.

SaveProjectModal
- Title (toggles between Save / Update), name + description fields
  with placeholders, save button (toggles between Save / Update /
  Saving…), Cancel button.
- Visibility toggle: Public / Private label + hint copy under the
  icon.
- All four error paths now go through t() with a {{status}}
  interpolation for the generic HTTP failure message.

ShareModal
- Title, public/private label + hint pair, "Make private" /
  "Make public" toggle, Copy button, the warning shown when the
  project is private, and the Close button.

Hand-curated translations for all 8 non-English locales. Status
codes (403) and shortcut markers preserved.
2026-05-09 03:12:52 -03:00
David Montero Crespo 7f0ac2a74c feat(editor): translate FileExplorer + FileTabs to 9 locales (Editor block 2)
FileExplorer (sidebar)
- Workspace header label and the new-workspace / save-project icon
  buttons now read from t('editor.fileExplorer.*').
- Per-board section: collapse / expand toggle, status dot tooltip
  (Running / Compiled / Idle), per-board "new file" button, and the
  composite "<board name> — click to edit" hover title (the board
  name itself stays untranslated — it's a product noun like
  "Arduino Uno").
- File rows: hover title with optional "(unsaved)" suffix,
  unsaved-dot tooltip, and the right-click context menu's Rename /
  Delete commands.
- Empty-state placeholder when no boards are on the canvas.
- The window.confirm() shown before deleting a file now reads from
  t() too, so non-English users see the prompt in their language.

FileTabs (open-tabs strip above the editor)
- Per-tab close button title and the unsaved-changes dot tooltip.
- Inline confirm dialog when closing a modified file: prompt copy,
  "Close anyway" and "Cancel" buttons.

Hand-translated for all 9 locales. Hotkey hints (Ctrl+S, Strg+S)
localised per German convention; other locales keep "Ctrl+S" as the
universally-recognised label.
2026-05-09 03:08:51 -03:00
David Montero Crespo 11012ec0e1 feat(editor): translate EditorToolbar to 9 locales (Editor block 1)
The top toolbar of the editor is now fully localised — compile / run /
stop / reset, the compile-all / run-all variants when multiple boards
are open, the language-mode select, libraries / import / export /
upload-firmware actions, and the missing-library hint banner.

Strings live under editor.toolbar.* in src/i18n/locales/<locale>/common.json.
Hand-translated for all 8 non-English locales. Brand and product
names (Arduino, MicroPython, Ctrl+B, .hex / .bin / .elf / .ihex,
GitHub Sponsors) preserved as-is.

Editor strings still pending: file explorer, file tabs, simulator
canvas, component picker, library manager modal, save / share /
project modals.
2026-05-09 03:05:51 -03:00
David Montero Crespo ca081f8e09 feat(landing): translate Support section + footer link labels (Block 4 of 4)
Final block. The Support section ("Support the project" / GitHub
Sponsors / Donate via PayPal) now reads from t('landing.support.*'),
and the footer link labels use t('header.nav.*') so they pick up
the same nav translations the header already ships.

This closes the LandingPage rewrite — every visitor-facing string
on velxio.dev/ goes through i18n now. Hand-translated for all 9
locales. "GitHub Sponsors" stays untranslated (proper product name).

Phase 2 remaining work:
- Editor (toolbar, file explorer, simulator canvas, component
  picker, library manager, save/share modals, error toasts).
- About / Examples / Docs / Profile pages.
- Login + Register forms.
2026-05-09 02:08:26 -03:00
David Montero Crespo ebfe6444d2 feat(landing): translate Features section + 6 feature cards (Block 3 of 4)
The Features section ("Everything you need") and the 6 cards
underneath (Real-Time SPICE Analog, 5 Emulation Engines, Custom Chips,
100+ Components, Live Instruments, Monaco Editor + arduino-cli) now
read from t('landing.features.<key>.{title,desc}') for all 9 locales.

Refactor:
- The `features` array in LandingPage.tsx no longer carries title/desc
  literals — only an icon + a translation key. The render maps each
  card's key to the matching i18n entry. Cleaner and keeps the JSX
  structurally stable across languages.

Translations:
- All 8 non-English locales hand-curated. Technical / brand names
  (ngspice-WASM, AVR8, ATmega328P, RP2040, ESP32-C3, CH32V003, QEMU,
  Cortex-M0+/A53, ILI9341, NeoPixel, Wokwi Custom Chips API,
  WebAssembly, .hex/.uf2/.bin, VS Code, arduino-cli) preserved
  unchanged in every locale — those are precise nouns where any
  translation would degrade meaning.
2026-05-09 02:06:53 -03:00
David Montero Crespo a03461d1e6 feat(landing): translate Boards / supported-hardware header (Block 2 of 4)
Replaces the visible header copy of the supported-hardware section
with t('landing.boards.*') keys:
- label "Supported Hardware"
- titleLine1 / titleLine2 ("Every architecture." / "One tool.")
- subtitle (the "19 boards across 5 CPU architectures..." paragraph)

The five engine cards underneath (avr8js, rp2040js, QEMU lcgamboa,
QEMU Xtensa, QEMU ARM) and per-board specs (e.g. "ATmega328p · 32 KB",
"RP2040 + WiFi") deliberately stay in English — those are accurate
technical specs / product names that don't translate, and mixing
locales inside a spec line would hurt readability more than it
helps.

Hand-curated translations for all 8 non-English locales.
2026-05-09 02:04:00 -03:00
David Montero Crespo fb0bf95b3d feat(landing): translate hero block to 9 locales (Block 1 of 4)
Hero strings now go through `t('landing.hero.*')`:
- titleLine1 / titleAccent (split for the gradient span)
- subtitle (one paragraph; "19 boards / 48+ parts" stays inside the
  string so locales can phrase the count naturally)
- ctaPrimary / ctaGithub
- trustLine (the "no signup / runs in browser / free & open-source"
  reassurance line — was previously emitting NBSP-wrapped middle
  dots; the localised versions use plain spaces, which is fine
  visually)
- imageAlt (a11y for the editor screenshot)

Internal /editor link now goes through localize() so a Spanish reader
clicking the primary CTA stays at /es/editor instead of dropping
back to English.

Translations are hand-curated for all 8 non-English locales (es,
pt-br, it, fr, zh-cn, de, ja, ru). Brand names (Velxio, Arduino,
ESP32, Raspberry Pi, GitHub, AGPLv3) preserved as-is. The script
arrow "→" is kept in every locale because it carries directional
meaning that translates naturally across languages.

Block 2 (Boards / supported hardware), Block 3 (features grid),
Block 4 (Support / footer copy) and Editor strings still pending.
2026-05-09 02:01:34 -03:00
David Montero Crespo 761bd83a75
Merge pull request #150 from davidmonterocrespo24/async-compile
feat(compile): async compile + status polling — no more 524 timeouts
2026-05-09 01:54:29 -03:00
David Montero Crespo cc077c09d1 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 00:40:37 -03:00
David Montero Crespo 1e6f5474c3 feat(landing): footer copy = About Velxio (replaces wrong MIT/avr8js line)
The previous footer credit ("MIT License · Powered by avr8js &
wokwi-elements") was wrong twice over: velxio is AGPLv3 (with a
commercial license available), and the project now ships much more
than the two libraries it singled out (rp2040js, eecircuit-engine,
QEMU, ESP-IDF, arduino-cli, Monaco editor, ...).

Replace it with a one-paragraph About Velxio that sits at the bottom
of the landing page, mirrors what the blog footer shows at
velxio.dev/blog/, and correctly states the AGPLv3 license.

Widen .footer-copy to max-width: 680px so the longer copy has room
to breathe and breaks across two lines on desktop.
2026-05-09 00:24:13 -03:00
davidmonterocrespo24 23fc335e5d feat(compile): async compile + status polling — no more 524 timeouts
The synchronous /api/compile endpoint forced one long-lived HTTP request
to span the entire build. Cloudflare's 100s edge timeout cuts that off
mid-flight for any cold ESP-IDF compile (BMP280 takes 5-7 min on first
run). The user-visible symptom was HTTP 524 well before the backend
even noticed.

Backend (compile.py)
- New `POST /api/compile/start` returns `{job_id}` immediately and
  spawns the actual compile as an asyncio.create_task background.
- New `GET /api/compile/status/{job_id}` returns the current job state
  (`pending` | `running` | `done` | `error`). Each poll completes in
  milliseconds, far under any edge timeout.
- Existing `POST /api/compile/` kept verbatim for backward compatibility
  (AVR/RP2040 builds finish in seconds and don't trip 524).
- Build logic extracted into `_run_compile()` so both paths share one
  implementation; no duplicated ESP-IDF / arduino-cli branching.
- Async path opens its own short-lived DB session via AsyncSessionLocal
  for metric recording — the request-scoped session is dead by the time
  the background task finishes.
- COMPILE_JOBS dict purges entries 30 minutes after completion so a
  busy server doesn't grow unboundedly.

Frontend (compilation.ts)
- compileCode() now: POST /compile/start → poll /compile/status every 2s
  until state ∈ {done, error}, with a 15-minute client-side cap.
- 30s axios timeout per individual call (not per build) so transient
  network blips during a long compile auto-retry instead of failing.
- 404 on /status throws (job expired / server restarted); other poll
  errors warn and retry. Surfaces structured error responses verbatim
  so the editor's compile-error panel keeps working unchanged.

Limitation: COMPILE_JOBS lives in-process; if velxio ever scales to
multiple FastAPI workers this needs to move to Redis or sqlite. Single-
instance is fine today.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 05:22:09 +02:00
David Montero Crespo 20eabd8c4c fix(scripts): generate-component-svgs no longer skips bmp280 / fails on ssd1306
Two distinct issues hit the component SVG generation step:

1. `velxio-bmp280` was in ELEMENTS but tries to require
   bmp280-element.js from the wokwi-elements CJS dist — that file
   doesn't exist because BMP280 is a velxio-native component, not a
   wokwi one. Its SVG already ships hand-authored at
   frontend/public/component-svgs/bmp280.svg, so the script should
   never have tried to extract it. Drop the row and leave a comment
   explaining why.

2. `wokwi-ssd1306` failed with "ImageData is not defined" because the
   element constructor seeds an off-screen canvas with
   `new ImageData(width, height)` — a browser API absent in Node.
   We never invoke putImageData (renderSVG() draws the static frame
   from scratch), so a minimal global stub that doesn't throw is all
   that's needed. Polyfill it on globalThis next to the existing
   customElements stub.

After this:
- 38 generated, 0 skipped, 0 failed (was: 1 skip, 1 fail).
- ssd1306.svg now ships in frontend/public/component-svgs/.
2026-05-09 00:05:28 -03:00
David Montero Crespo b42f815b49 feat(components): swap BMP280 + ATtiny85 to fritzing art
The hand-drawn SVGs in Bmp280Element.ts and Attiny85Element.ts were
functional but obviously amateur next to a real Fritzing-drawn part.
Both components now mount the equivalent Fritzing breadboard SVG as a
public static asset (`<image href>` in the shadow DOM SVG), with pin
coordinates remapped to the new artwork and pin-name labels overlaid
on top so the user can still read each connector at a glance.

frontend/public/component-svgs/bmp280.svg (new)
  Verbatim copy of third-party/fritzing-parts/svg/core/breadboard/
  bmp180_breadboard.svg. The Adafruit BMP180 breakout is the
  mechanically identical Bosch predecessor — same I2C interface,
  same 4-pin pinout. Pin labels lifted from the matching .fzp.

frontend/public/component-svgs/attiny85.svg (new)
  Verbatim copy of the Fritzing ATtiny85 DIP-8 breadboard art.

Bmp280Element.ts
  Width 80×100 px (Fritzing aspect 28.35:35.43 ≈ 0.8:1, exact uniform
  scale of 2.822 px/mm). Pin coords for SDA / SCL / GND / VCC matched
  to the connector centres in the source SVG. Pin labels overlaid on
  top. Existing wired example (esp32-bmp280) re-routes automatically
  because the wire system reads coords by pin name from pinInfo.

Attiny85Element.ts
  Width 160×132 px (Fritzing aspect 28.801:23.768 ≈ 1.21:1, exact
  uniform scale of 5.555 px/mm). The Fritzing layout puts pins on the
  TOP and BOTTOM edges (4 each), not LEFT and RIGHT like the older
  hand-drawn version. Pin coords land on clean numbers
  (x ∈ {20, 60, 100, 140}, y ∈ {6, 126}). Built-in LED on PB1 stays
  as an overlaid circle outside the chip body.
  Wires in the existing attiny85-* examples re-route automatically by
  pin name; external components positioned to the right of the chip
  may need a manual nudge for clean routing — but they work.

frontend/src/components/simulator/BoardOnCanvas.tsx
  attiny85: { w: 160, h: 100 } → { w: 160, h: 132 } to match the new
  aspect ratio. Same width as before so the chip occupies the same
  horizontal slot in existing example layouts.

scripts/component-overrides.json
  BMP280 thumbnail updated to mirror the Fritzing colour scheme
  (dark blue PCB, BMP180 silkscreen, four gold connector circles)
  so picker and canvas feel consistent.

frontend/public/components-metadata.json
  Regenerated.

docs/THIRD_PARTY.md
  New "Fritzing parts library" section. Both new assets are listed
  with their upstream paths plus the CC-BY-SA licence and link to
  the parts repo. Future Fritzing copies must be added there too.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 23:06:44 -03:00
David Montero Crespo c939005bf7 test(esp32): integration tests for QEMU examples
Closes the loop on the four ESP32 fixes that landed earlier in this
series. Each previously-broken or noisy example now has a regression
test that compiles the sketch through the production ESP-IDF compiler
and runs it in QEMU via esp32_worker.py — the same code path the
WebSocket /ws/{client_id} endpoint drives in production. Testing the
worker directly skips the WS transport but exercises the same compile
→ flash → boot → serial cascade.

Coverage:
- TestEsp32SerialCleanliness — DHT22, Servo+Pot, Joystick, Dual ADC
  Asserts the user's Serial.print substring shows up AND no
  `I (xxx) gpio:|wifi:|phy:` info-level ESP-IDF logs leak through.
  This validates the sdkconfig CONFIG_LOG_DEFAULT_LEVEL_WARN change
  from commit b373c97.

- TestEsp32CompileSuccess — BLE Advertise, LEDC RGB
  BLE Advertise validates the sdkconfig switch to Bluedroid (was
  NimBLE-only, which broke arduino-esp32's BLEDevice.h).
  LEDC RGB validates velxio_compat.h's ledcAttach() shim from
  commit f6f6f43; the sketch uses the arduino-esp32 3.x one-shot API
  on a 2.0.17 toolchain.

- TestEsp32WiFiSketches — WiFi Connect, WiFi WebServer
  Regression coverage to make sure the sdkconfig changes didn't break
  WiFi association. Connect must reach an "IP Address:" line; Server
  must report "Server started".

- frontend/src/__tests__/component-metadata-bmp280.test.ts
  Sanity check that the BMP280 entry from commit 1f3f2e0 survives
  metadata regeneration.

All ESP-IDF/QEMU tests use unittest.skipUnless on
_toolchain_available() so they no-op cleanly on dev boxes without
libqemu-xtensa, and only do real work in the Docker CI image.

Sketches are inlined verbatim from the public Velxio examples.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 18:39:22 -03:00
David Montero Crespo 1f3f2e07bb feat(components): register BMP280 in component metadata
A beta tester reported "BMP280 - module graphic missing" — picking the
example loaded a working sketch but the canvas component fell back to
the MPU6050 placeholder.

Bmp280Element.ts already exists and registers velxio-bmp280 with the
right pinInfo, but it was never injected into components-metadata.json,
so the component picker and CircuitPreview didn't know about it. Add
an entry in scripts/component-overrides.json under _customComponents
(per CLAUDE.md §6b — direct edits to the generated JSON would be
clobbered by the next metadata regen) and regen.

The thumbnail mirrors the GY-BMP280 breakout look from the Web
Component itself: green PCB, black die label, four gold pin pads.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 17:26:12 -03:00
David Montero Crespo bba935f93d fix(serial-monitor): strip ANSI escape sequences from board output
Beta testers saw raw `[0;32m` and `[0m` text mixed into the serial
monitor for several ESP32 examples. Those are ANSI SGR escapes the
ESP-IDF logger emits to color INFO/WARN lines on a real terminal. Our
<pre> renders them literally because there was no ANSI handling in
the path.

Strip the SGR sequences (`\x1b\[[0-9;]*m`) before the IP-linkifier so
the user only sees plain text. Combined with the sdkconfig change that
drops the default log level to WARN, the ESP32 output is now as clean
as the AVR output.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 16:32:59 -03:00
David Montero Crespo 2bcc8a62ee feat(canvas): inline two-step delete confirm in ComponentPropertyDialog
Replaces the window.confirm("Delete X?") modal with a footer that flips
into a "Delete X?" prompt + Cancel / Delete pair when the user arms the
delete. Less jarring on mobile (no native dialog), keeps the user's
flow inside the property panel.
2026-05-08 12:25:13 -03:00
David Montero Crespo 0fcd6221b5 fix(canvas): use lucide-react Undo2/Redo2 for the toolbar icons
The two hand-rolled curved-arrow SVGs I drew in bd5fd18 looked off — the
arrowheads were misaligned and the curve clipped at the bottom of the
viewBox. Swapped both for the canonical lucide-react icons (Undo2 /
Redo2), which match the visual weight + alignment of the rest of the
toolbar.

lucide-react was already in the OSS deps (added when other parts of the
app started using it). No new dependency.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 12:19:50 -03:00
David Montero Crespo bd5fd18756 feat(canvas): keyboard shortcuts + toolbar undo/redo buttons
UI-facing half of the undo/redo feature. Combined with the previous two
commits, Ctrl+Z (or the toolbar button) now reverses every canvas
mutation: add/remove component, move, rotate, set property, add/remove
wire.

EditorPage.tsx:
- New window-keydown effect for Ctrl+Z / Ctrl+Y / Ctrl+Shift+Z (and the
  Cmd equivalents). Uses the same input/textarea/contenteditable guard
  as the existing Ctrl+S handler — Monaco's per-file undo and the AI
  chat composer keep their own behaviour.

SimulatorCanvas.tsx:
- Two icon buttons (undo + redo) added to the canvas header, between
  the board selector and the Serial Monitor toggle. Tooltip surfaces
  the next command's description ("Undo: Add LED (Ctrl+Z)") so the
  user knows exactly what's about to revert. Buttons disable when the
  stack is empty in that direction.
- New `canvas-icon-btn` CSS class for square 32×32 icon-only buttons
  (matches the visual weight of the existing Serial button without
  the label).
- Subscribes to history / historyIndex via store selectors so the
  buttons re-render reactively as commands are pushed/undone.

No new tests — the store-level coverage from 99ed22b already exercises
undo/redo round trips. UI affordances are wired pass-through to those
store APIs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 12:11:53 -03:00
David Montero Crespo df8e666aa3 feat(canvas): SimulatorCanvas routes mutations through record* actions
Wires every user-initiated canvas mutation in SimulatorCanvas to the
recorded variants added in 99ed22b, so Ctrl+Z (next commit) can roll
each one back as a single step.

Routed through record*:
- handleSelectComponent (picker → add) → recordAddComponent
- Delete keyboard handler (selectedComponentId branch) → recordRemoveComponent
- handleRotateComponent → recordRotate (still mutates live via
  updateComponent so the rotation visually applies; record stores the
  prev/next angles for undo)
- Drag → recordMove on mouseup. Captures component.x/y at mousedown in
  a new dragStartPosRef and only records on actual drag-end (skips the
  click branch that just opens the property dialog).
- Pin-click "finish wire" path → calls finishWireCreation (which
  atomically appends the wire) then pushes a CanvasCommand for that
  wire with applyNow:false (state is already at post-add).
- Selected-wire delete (keyboard + SelectionActionBar + PinPickerDialog)
  → recordRemoveWire
- Selection action bar component delete → recordRemoveComponent
- Pin picker dialog component delete → recordRemoveComponent
- ComponentPropertyDialog onPropertyChange → updateComponent applies
  live, then recordSetProperty captures prev/next so Ctrl+Z reverts the
  value without re-running the raw mutation.

Cleaned up unused destructures of addComponent / removeComponent /
removeWire from the original useSimulatorStore() call — every call site
now uses the record* equivalents.

No new keyboard shortcuts or toolbar buttons in this commit; that's
landing next.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 12:09:20 -03:00
David Montero Crespo 99ed22ba5d feat(canvas): undo/redo command pattern + history slice
Adds the foundation for canvas undo/redo. UI wiring, keyboard shortcuts,
toolbar buttons and agent-tools refactor land in follow-up commits.

useSimulatorStore.ts:
- New CanvasCommand type — { description, execute, undo }.
- HISTORY_MAX = 50 (oldest entries dropped on overflow).
- New state: history[] + historyIndex (-1 = empty).
- New APIs: pushCommand(cmd, {applyNow?}), undo, redo, canUndo, canRedo,
  clearHistory.
- New "recorded" actions that wrap raw mutators with a CanvasCommand:
  recordAddComponent, recordRemoveComponent, recordMove, recordRotate,
  recordSetProperty, recordAddWire, recordRemoveWire, recordUpdateWire.
- recordRemoveComponent captures both the component AND any wires that
  cascade with it, so undo restores both atomically.
- recordMove also re-runs updateWirePositions on undo/redo so wire
  endpoints follow the component back/forward.
- setComponents and setWires (project-load / clear paths) now call
  clearHistory inline — leaving stale commands pointing at IDs that no
  longer exist would crash on undo.

Why custom Command pattern over zundo / travels:
- The store has 30+ ephemeral fields (simulator instances, serialOutput
  growing byte-by-byte, hexEpoch counter, wireInProgress that ticks 60×/s
  on drag). Snapshot/diff middleware would either burn memory tracking
  them or need a fragile partialize allow-list.
- Per-op descriptions ("Add LED", "Move resistor") for tooltips come for
  free with this approach; zundo/travels would need to infer them.

Tests: 15/15 in src/__tests__/undo-redo.test.ts — covers cap-at-50,
redo-truncation, cascade undo of remove-component, move/rotate/property
round trips, bulk-setter clearing.

Raw mutators (addComponent / removeComponent / updateComponent / addWire /
removeWire / updateWire) are unchanged. UI handlers can keep using them
during live drags for preview frames without spamming history; the
record* actions are what drag-end, click-finish and agent tools should
call going forward.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 12:03:17 -03:00
David Montero Crespo 5114c40af5 chore(about): refresh community stats
- GitHub Stars: 600+ → 2,000+ (project crossed 2K)
- Supported Boards: 19 → 17 (matches the actual BOARD_KIND_LABELS
  count: 3 AVR Arduino + ATtiny85 + 2 RP2040 + Pi 3 + 4 ESP32 Xtensa-LX6
  + 3 ESP32-S3 + 3 ESP32-C3 = 17). The previous 19 was inflated.
- CPU Architectures: 5 → 6 (AVR, RP2040 ARM Cortex-M0+, Cortex-A53 64-bit,
  Xtensa LX6, Xtensa LX7, RISC-V RV32IMC).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 11:28:47 -03:00
David Montero Crespo ebde9cc8da feat(about): real avatar + Recent releases section linking to /v2 and /v2-5
- Replace the "DMC" initials placeholder with the GitHub avatar
  (https://avatars.githubusercontent.com/u/47928504?v=4). The CSS for
  .about-creator-avatar already had the rounded frame; just swapped to
  object-fit: cover so the <img> fills the circle correctly, plus a
  subtle ring + drop shadow.
- Add a "Recent releases" section between the Creator block and the
  personal-story quote, with two cards:
    - Velxio 2.5 (Latest) → /v2-5  (ngspice-WASM analog co-simulation)
    - Velxio 2.0          → /v2
  Each card has a tagline + 2-3 line blurb. The 2.5 card gets a blue
  border + "Latest" tag so it reads as the current launch. About now
  surfaces both release pages, which previously were only linked from
  the Circuit/Electronics/SPICE simulator pages.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 11:20:38 -03:00
David Montero Crespo c20a7498fc feat: add touch-friendly components for improved mobile usability
- Implemented PinPickerDialog for selecting pins on touch devices.
- Added SelectionActionBar for managing selected items with touch actions.
- Created WireModeBanner to provide feedback during wire creation.
- Introduced useTouchDevice utility for detecting coarse pointer input.
- Refactored WireLayer to utilize useIsCoarsePointer for touch detection.
2026-05-08 11:12:26 -03:00
David Montero Crespo b83b6f28d6 fix(vite): only preserveSymlinks during dev, not build
Previous commit unconditionally enabled preserveSymlinks when
VITE_PRO_BUILD was set. That works for `vite dev` (where the overlay
is wired in via a Windows junction and the resolver needs to keep the
junction path so relative imports back into the OSS sibling dirs
resolve), but it BREAKS `vite build` in Docker — there are no symlinks
to preserve, and Rollup with preserveSymlinks=true fails to resolve
relative imports from the copied overlay tree:

    Could not resolve "../../../services/componentRegistry"
    from "src/pro/agent/tools/canvas.ts"

Gate the flag on `command === 'serve'` so it only kicks in during dev.
Production builds always run with preserveSymlinks=false (the default).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 01:18:40 -03:00
David Montero Crespo 78f990b04f
Merge pull request #146 from davidmonterocrespo24/examples-real-thumbnails
feat(examples): add 53 missing thumbnails — 100-days IoT + Pico W WiFi
2026-05-08 01:11:38 -03:00
davidmonterocrespo24 fc985bb385 feat(examples): add 53 missing thumbnails — 100-days IoT + Pico W WiFi
Discovery regex was matching only single-quoted ID literals, so the
auto-generated examples-100-days.ts file (which uses double-quoted
strings, by convention of its Python emitter) was completely missed.
Same for picow-wifi: the script wasn't reading examples-picow-wifi.ts
at all.

Two-line fix in scripts/capture-example-thumbs.mjs: the regex now
accepts both `'…'` and `"…"`, and the data-file list includes
examples-picow-wifi.ts.

Captured slugs:
- 49 × `100d-*` (100 Days of IoT — MicroPython on ESP32 / Pico)
- 4  × `picow-*` (Pico W WiFi — async LED, relay web server,
  servo web, websocket LED)

Coverage: 219/226 examples (97%). The 7 still falling back to
CircuitPreview are component-ID literals (`epaper-1in54-bw`,
`epaper-2in13-bw`, etc.) that the regex over-matches — they 404
on /examples/<slug> because they aren't real example IDs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 05:57:35 +02:00
David Montero Crespo 70cc8c2e11 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 00:56:51 -03:00
David Montero Crespo bb14ab663a
Merge pull request #145 from davidmonterocrespo24/examples-real-thumbnails
feat(examples): add 7 ePaper example thumbnails
2026-05-08 00:42:09 -03:00
davidmonterocrespo24 3df43ca52e feat(examples): add 7 ePaper example thumbnails
The ePaper examples in examples-displays-epaper.ts use slugs prefixed
`epaper-*` (e.g. `epaper-1in54-uno-hello`), but the previous discovery
regex was matching `epd-*` — those are ePaper-component IDs that
appear in wire definitions, NOT example IDs. So /examples/epd-154
returns 404 ("Example Not Found") and the 7 actual ePaper examples
were never captured.

Fix: discovery regex now reads `epaper-` (the real prefix). Captured
all 7:
  - epaper-1in54-uno-hello   (Uno + 1.54" SSD1681)
  - epaper-2in13-pico-clock  (Pico + 2.13")
  - epaper-2in9-esp32-weather (ESP32 + 2.9" weather panel)
  - epaper-4in2-pico-image   (Pico + 4.2" image)
  - epaper-7in5-esp32-dashboard (ESP32 + 7.5" dashboard)
  - epaper-2in9-bwr-esp32-alert (ESP32 + 2.9" black-white-red)
  - epaper-5in65-7c-esp32-rainbow (ESP32 + 5.65" 7-color)

Coverage now: 166/166 examples (was 159/166).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 05:36:28 +02:00
David Montero Crespo 49d6dceaed
Merge pull request #144 from davidmonterocrespo24/examples-real-thumbnails
feat(examples): add 30 analog-only example thumbnails
2026-05-08 00:23:59 -03:00
davidmonterocrespo24 41244f6f6c feat(examples): add 30 analog-only example thumbnails
The analog-only circuits in examples-analog.ts (slugs prefixed `an-*`)
have no SEO route — they aren't in sitemap.xml — and the capture
script previously discovered slugs from sitemap only, so all 30
fell through to the CircuitPreview SVG mock which doesn't draw the
wires for these layouts.

Updated discovery: also grep the local velxio submodule's
examples-analog.ts for `an-*` ID literals (and `100d-*` / `epd-*`
while we're at it for the 100-days and epaper data files), in
addition to the sitemap pull.

Updated wait condition: capture now waits for any board OR component
OR wire path inside .canvas-world, not specifically [data-board-id]
(analog-only examples have no Arduino, just a signal-generator + parts).

Coverage: 159/166 examples have real screenshots now (was 129/166).
The 7 `epd-*` epaper examples are still falling back to CircuitPreview
because they don't render an "Open in Simulator" CTA on /examples/<slug>;
they'll need a separate loader path.

Re-captured the 109 existing thumbs at the same time — content is
visually identical for those, no regression.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 04:57:10 +02:00
David Montero Crespo 9d36eac816
Merge pull request #143 from davidmonterocrespo24/examples-real-thumbnails
Examples real thumbnails
2026-05-07 17:56:44 -03:00
davidmonterocrespo24 37fbc502d1 chore(examples): drop PNG thumbnails, ship WebP only (-80% asset size)
The PNG fallbacks were carrying 80% of the gallery's bundled weight
(16 MB of 20 MB) and serving virtually no traffic — WebP is supported
on ~97% of in-use browsers, and the few hold-outs (very old Safari)
fall through to the CircuitPreview SVG mock via the existing onError
handler. No visual regression for modern browsers.

Numbers:
- before: 258 files, ~20 MB total
- after:  129 files, ~3.6 MB total (avg 28.6 KB / WebP)

ExampleThumbnail simplified: drops the <picture>/<source> wrap around
the WebP <source> + PNG fallback, just renders the WebP <img> directly
with onError → CircuitPreview.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 22:54:33 +02:00
davidmonterocrespo24 436bafbd84 feat(examples): backfill remaining 27 example thumbnails
Adds the slugs that hadn't been captured in the first batch (some
extra coverage from a later examples-* file, plus 10 retries that
hit a transient waitForLoadState timeout on the first sweep).

Coverage is now 129/129 — every example exposed via sitemap.xml has
a real canvas screenshot. The few that still 404 (slugs only present
in non-sitemap data files) keep the CircuitPreview fallback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 22:48:29 +02:00
davidmonterocrespo24 d53f4f1380 feat(examples): real canvas screenshots as gallery thumbnails
Replaces the manually-positioned wokwi-element mock (CircuitPreview) in
the /examples gallery with real screenshots of the actual simulator
canvas, so each card shows the example's boards + components + wires
exactly as they appear when you open the example.

How it works
- A new <ExampleThumbnail> component tries
  /examples-thumbs/<id>.{webp,png} first. If the image 404s — or no
  thumbnail has been captured yet — it falls back to the existing
  CircuitPreview component. No-op for examples without a screenshot.
- ExamplesGallery and ExampleDetailPage now render <ExampleThumbnail>
  instead of CircuitPreview directly.
- Explicit example.thumbnail field still wins (kept the existing
  override path in case someone wants a custom asset).

Capture pipeline
- Generated by velxio-prod's scripts/capture-example-thumbs.mjs
  (Playwright + sharp). For each example: opens /examples/<slug>,
  clicks "Open in Simulator", waits for [data-board-id] elements,
  computes the bbox of all boards + components, sets a transform on
  .canvas-world to center and fit them with 12% padding inside the
  canvas viewport, screenshots .canvas-content, and re-encodes to
  600x360 @2x DPI as .png + .webp.

This commit ships the first batch (102 of ~129 examples — the rest
will follow once the capture completes; missing slugs gracefully
fall back to CircuitPreview in the meantime).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 22:44:10 +02:00
davidmonterocrespo24 9d5d2e8a4a fix(docs): RISC-V emulation goes through QEMU, not a TypeScript browser core
The marketing copy and docs claimed ESP32-C3 / XIAO-C3 / SuperMini /
CH32V003 ran on a "browser-native RV32IMC core written in TypeScript",
but production runs through QEMU lcgamboa (libqemu-riscv32) with the
esp32c3-picsimlab machine — same backend pattern as Xtensa ESP32, just
a different libqemu binary. The TypeScript ISA layer
(RiscVCore.ts / Esp32C3Simulator.ts / RiscVSimulator.ts) is kept only
as Vitest unit-test infrastructure for RV32IMC instruction decoding;
it cannot handle the 150+ ROM functions ESP-IDF needs at boot and is
not wired into the production emulation path.

Files updated:

Marketing pages
- LandingPage: board-group label, FAQ answer, architecture description
  no longer claim "browser-native" or "no backend needed" for RISC-V.
- AboutPage: arch card retitled "RISC-V via QEMU", body explains the
  libqemu-riscv32 / lcgamboa backend.
- Velxio2Page: arch group engine label, multi-board feature item,
  competitive-comparison card all corrected.
- ArduinoEmulatorPage: two RISC-V cards corrected.
- ESP32SimulatorPage: ESP32-C3 cross-link card corrected.
- ESP32C3SimulatorPage: hero subtitle, trust strip, supported-boards
  intro, JSON-LD description corrected.
- ElectronicsSimulatorPage: install-needed FAQ corrected.
- examples.ts: c3-blink description and code-comment corrected.

SEO surfaces
- index.html: JSON-LD SoftwareApplication description, OS-fallback FAQ
  body, supported-boards <ul> bullets, feature list bullets corrected.
- seoRoutes.ts: /esp32-c3-simulator title + description corrected;
  homepage description corrected.

Docs page
- DocsPage RiscVEmulationSection: intro paragraph rewritten — RISC-V
  goes through QEMU lcgamboa with libqemu-riscv32 / esp32c3-picsimlab,
  TypeScript layer is Vitest-only.
- DocsPage Esp32EmulationSection callout: section now applies to all
  ESP32 family (Xtensa + RISC-V), pointer to RISC-V doc clarified.

README
- "Boards" table: production-engine column for ESP32-C3 family and
  CH32V003 changed from "RiscVCore.ts (browser)" to "QEMU lcgamboa
  (backend)".
- "ESP32-C3 / XIAO-C3 / SuperMini / CH32V003" subsection retitled
  "(RISC-V via QEMU)" — body explains libqemu-riscv32 backend and
  flags the TypeScript layer as Vitest-only.

The two remaining "browser-native" hits in the codebase
(Velxio25Page:176, index.html:348) are about ngspice-WASM SPICE
analog simulation, which genuinely is browser-native — left alone.

Build verified: npm run build:docker succeeds, 246 SEO pages prerender.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 21:56:29 +02:00
davidmonterocrespo24 0c8d96a05c feat(marketing): replace mock hero with live editor screenshot
Captures /examples/traffic-light → /editor in headless Chromium and saves
the rendered editor as a 3840x2160 (2x DPI) PNG + WebP for the landing
page hero.

The shot includes the code editor on the left (Traffic Light Simulator
.ino), the Arduino Uno on the canvas with three LEDs wired up, the SPICE
nets indicator, and the full chrome — a much stronger first impression
than the previous CSS-mocked schematic.

Generation script lives in the private velxio-prod repo
(scripts/capture-hero.mjs) and can be re-run any time to refresh the
asset against the live deployment.

- /marketing/hero-editor.png (320 KB)
- /marketing/hero-editor.webp (160 KB)
- LandingPage hero <picture> now points at these (loading=eager,
  fetchPriority=high since it's above the fold).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 21:21:52 +02:00
davidmonterocrespo24 e0dbde0a76 feat(design): tokens, webfonts, Lucide icons, board PNG/WebP picture strip
Foundation
- Add 7 token CSS files in src/tokens/ — semantic colors, 4-pt spacing,
  Apple-HIG type ramp, radius/elevation/motion/z-index scales.
- Refactor src/index.css to import tokens and remap legacy aliases
  (--accent, --bg, etc.) onto the new --color-* semantics so existing
  components keep rendering during migration.
- Drop the duplicate font-family from src/App.css; body inherits from :root.
- Global *:focus-visible ring backed by --color-focus-ring (WCAG 2.4.7).

Webfonts (self-hosted)
- Add Inter.var.woff2 (variable, OFL) and JetBrainsMono.var.woff2 to
  public/fonts/. Preloaded in index.html with crossorigin.
- Old stack -apple-system kept as fallback so Mac users still get SF Pro.
- Fixes cross-OS rendering inconsistency (Win/Linux/Android were falling
  back to Segoe UI / Roboto, breaking the type grid).

Component primitives
- New src/components/ui/{Button,Card,Input}.tsx + .css. Built on the
  semantic tokens, ready for incremental migration of .ap-* CSS classes.

Lucide icons
- Replace 6 inline SVG icon components in LandingPage (IcoChip / IcoCpu /
  IcoCode / IcoZap / IcoLayers / IcoMonitor) with lucide-react imports.
  Aliased so call sites are unchanged. ~80 lines of inline SVG removed.
- IcoGitHub kept bespoke (filled glyph, brand-correct).

Marketing assets
- Convert top 8 boards to transparent PNG + WebP at 1x / 2x:
  Arduino Uno, Nano, Mega 2560, Pi Pico, Pi Pico W, ESP32-C3,
  ESP32-DevKit-V1, XIAO ESP32-S3.
- Migrate matching cards in LandingPage and Velxio2Page to <picture>
  with WebP > PNG > SVG fallback. Other 8 boards keep <img src=*.svg>
  for now (Raspberry Pi 3B, ESP32-CAM, etc.).
- Refresh og-image.png — same canonical URL, new content (4 hero boards
  + branding instead of generic logo card).
- Fix latent bug in LandingPage: ESP32 DevKit V1 card was loading
  esp32-devkit-c-v4.svg; now uses esp32-devkit-v1.{webp,png,svg}.

Build verified: npm run build:docker succeeds, 246 SEO pages prerender.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:45:44 +02:00