Commit Graph

153 Commits

Author SHA1 Message Date
David Montero Crespo ffb4fc0159 feat(editor): toolbar rides inside the header — one row, not two
With the marketing nav gone from the editor header, its middle was ~700px
of dead space at 1280 while the toolbar occupied a whole second 38px bar.
The unified toolbar strip now fills that middle through a new AppHeader
editorToolbar slot: one 44px row where there used to be 44+38 — a full
row returned to the code and the canvas.

The strip keeps its own class names, so everything that keyed on them
keeps working untouched: the container queries, the docked-chat
padding-right, and the internal flex-wrap. When it truly cannot fit, the
strip wraps and the header grows (height: auto on the modifier class)
instead of clipping or overlapping; brand and the right-side controls
stay pinned to the first line. Inside the header the strip drops its own
background and border so it reads as one bar, not a box within a bar.

Mobile keeps the previous layout (no strip; the mobile tab bar remains).
2026-07-30 18:56:24 +02:00
David Montero Crespo b70bb8ab83 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 17:50:14 +02:00
David Montero Crespo 3cc1510cae fix(examples): el enlace directo a un ejemplo pro ya no parpadea un 404
Reportado en /example/pi5-opencv-vision: al recargar aparecia un instante
el 404 con el header completo (menus incluidos) y despues cargaba el
editor. La carrera: los ejemplos pro se registran cuando aterriza el
import dinamico del overlay, y la pagina resolvia la galeria ANTES,
concluia "no existe", pintaba el 404 y al llegar el registro re-renderizaba
al editor.

"No esta en la galeria" y "no esta TODAVIA" son respuestas distintas, asi
que el registro gana una senal de asentado: main.tsx la activa cuando el
import del overlay resuelve (o falla — finally), e inmediatamente en la
build OSS, donde no viene overlay y un 404 debe ser instantaneo. La pagina
se queda en "Loading example..." hasta que el registro asienta y solo
entonces un id ausente es de verdad un 404.

markProExamplesSettled es idempotente y solo notifica una vez, con test —
el sintoma de romper eso seria una tormenta de re-renders por hot-reload
del overlay.
2026-07-30 17:06:05 +02:00
David Montero Crespo 03652c0748 fix(editor): la fila envuelve en vez de estrujar botones
Con la ventana estrecha y el chat acoplado, a la fila unificada le faltan
~60px y flexbox se los quitaba al toggle Code/Both/Circuit: de 101px a 32,
con dos de sus tres botones recortados a esquirlas inalcanzables bajo el
overflow hidden. Medido en staging a 1024px.

El toggle deja de encogerse (flex-shrink 0) y la fila hace wrap: cuando de
verdad no cabe, los controles del canvas bajan a una segunda linea corta.
Dos filas breves ganan a botones invisibles.
2026-07-30 16:43:46 +02:00
David Montero Crespo 03afa31b5f feat(editor): menus File/Edit en la cabecera y editor sin nav de marketing
La queja: demasiados botones en una sola fila, y en pantallas pequenas se
solapan — medido, no impresion: a 1024px Code/Both/Circuit pisaban a
Compile/Run/Stop por 9-20px y Add pisaba el chat por 29px, con el idioma y
el usuario cortados fuera del viewport.

Tres piezas:

1. En el editor, el nav de marketing (Home/Docs/Pricing/...) sobra: ya
   estas dentro, y cuesta exactamente el ancho que le falta a la toolbar.
   AppHeader gana la variante editorMenu — mismo mecanismo que ya usa la
   build de escritorio (VITE_DESKTOP) — que oculta el nav y pinta un menu
   File/Edit junto al logo. El logo sigue llevando a inicio; idioma,
   autoguardado, Share y usuario se quedan.

2. File/Edit al estilo escritorio: lo que se usa cada minuto sigue siendo
   boton; lo que se usa unas veces por sesion va al menu. File: nuevo
   workspace, nuevo fichero, abrir, guardar, importar, exportar, BOM,
   imagen del esquema. Edit: undo/redo (con su estado real del historial),
   centrar vista, zoom. Las acciones viven en cierres de cuatro
   componentes distintos, asi que hay un registro id->handler
   (lib/editorCommands, mismo patron de seam que registerProExamples o
   registerBoardBuiltins): cada dueno registra al montar y el menu invoca
   por id; un item sin dueno montado se pinta deshabilitado, que ademas es
   la verdad. El registro sobrevive al remontaje StrictMode (el cleanup
   viejo no borra al sucesor) y tiene test de eso.

3. La toolbar adelgaza: fuera Undo/Redo (Ctrl+Z/Y siguen), fuera los
   botones inline de Import/Export y sus gemelos responsivos del More
   (~70px recuperados); el More queda solo con los extras pro (firmware,
   GitHub, share, grabacion). El CSS responsivo de los botones retirados
   se va con ellos.

Suite en verde (2300) y build del overlay verificado desde este arbol.
2026-07-30 16:26:36 +02:00
David Montero Crespo bbafe66ee7 feat(pi-family): unified editor UX — Monaco + explorer + bottom xterm, one file surface
QEMU-Linux boards used three competing file surfaces (workspace group
with a meaningless sketch.ino/libraries.json, the VFS panel with its
Upload button, and the Pi workspace's own editor). Now they behave like
every other board:

- the editor file group defaults to script.py for ANY Pi-family kind
  (kind-based check via isPiBoardKind, not the old raspberry-pi- string)
- the libraries.json manifest row is hidden for Pi boards
- EditorPage always renders Monaco; the RaspberryPiWorkspace swap is
  gone
- the bottom serial panel renders the interactive xterm (PiTerminal,
  now seeded with session history) for running Pi boards
- example vfsFiles load into the editor group (single source of truth);
  the run path uploads the group into the guest home
- Run on a booted guest re-runs without the 45 s reboot (Ctrl-C +
  re-upload + run); starting a Pi board pops the terminal open
- piSyncAndRunScript/piRerunScript exported from the store; auto-run
  now applies to the whole family (guestHome/autoRun overridable)
2026-07-28 19:43:20 +02:00
David Montero Crespo 0f68037c35 feat(canvas): fuera el minimapa
Se anclaba a la esquina inferior derecha del lienzo y tapaba una parte del area
util del circuito, que es justo donde se trabaja. Quien pana y hace zoom no lo
necesitaba para orientarse, asi que se retira entero en vez de esconderlo tras
un interruptor mas.

Se van el componente y su CSS (no los usaba nadie mas) y la linea del listado de
novedades que lo anunciaba, que ya no seria cierta.
2026-07-26 00:59:58 +02:00
David Montero Crespo fd6829f645 merge: master (produccion) dentro de v3.2
Las dos ramas habian divergido: master llevaba el modo lenguaje ESP-IDF puro
(#139) y v3.2 la ruta de compilacion IDF v5.5 para toda la familia ESP32 mas
los arreglos de venv/toolchain. Ambas tocaban espidf_compiler.py.

Los dos lados son ejes ORTOGONALES y se conservan enteros:

  - use_idf5 / arduino_mode (v3.2): que arbol IDF usa el build (5.5 vs 4.4) y
    si cabe Arduino-como-componente.
  - pure_idf (master): el modo LENGUAJE que elige el usuario; sus ficheros son
    las fuentes del componente main con su propio app_main().

Resolucion:
  - _build_env acepta los tres. Un build IDF puro fuerza arduino_mode a falso:
    la plantilla CMake mete el componente arduino-esp32 en cuanto existe
    ARDUINO_ESP32_PATH, asi que dejarlo puesto compilaba el core de Arduino en
    un build que no tiene sketch. Lo cazaron los tests de master.
  - VELXIO_PURE_SKETCH solo con pure_idf, nunca con arduino_mode a falso a
    secas: un target sin core arduino-esp32 sigue entregando un SKETCH al
    traductor legacy y no debe tomar la rama del glob puro.
  - La identidad del build-dir suma los dos tokens (|idf:N|ard:N y |lang:pure):
    ningun par de esas combinaciones puede compartir un build/ configurado.
  - La cadena de escritura de fuentes queda pure_idf -> arduino_mode -> legacy.
  - sdkconfig: render de v3.2 (con target/use_idf5) mas el filtrado de simbolos
    CONFIG_ARDUINO* de master cuando el build es puro.

test/backend/unit/test_espidf_compiler.py: los 7 tests que ya estaban rotos en
v3.2 (AttributeError: idf5_path, fixture sin actualizar desde que se anadio la
seleccion de IDF) vuelven a pasar.

Verificado: backend 293 pasan / 0 fallan (v3.2 traia 7 rotos); frontend 2268
pasan / 0 fallan en los dos shards.
2026-07-25 21:52:00 +02:00
David Montero 4766789632 feat(landing): hero shows the weather-station circuit gif running
Replaces the static hero-editor screenshot with the animated
estacion-meteorologica-esp32.gif (whole circuit executing). contain +
matching background so the full circuit stays visible instead of
cover-cropping it.
2026-07-22 22:56:32 +02:00
David Montero Crespo d90e738995 fix(examples): example pages re-render when the overlay registers late examples
registerProExamples() now notifies useSyncExternalStore subscribers (same
contract as proRoutes): a direct URL to an overlay-registered example resolved
before the dynamic @pro import landed and stuck on the 404 branch. The three
example pages subscribe to the version counter.
2026-07-22 21:43:03 +02:00
David Montero Crespo c02547049d feat: ESP32 bridge seams, picker datasheets, S3/C3 examples + online-only board showcase
Generic platform work ported from the internal line:
- Esp32BridgeFactory seam + rebuildEsp32Bridge + sync-I2C seam: a
  substitute simulation bridge (e.g. the hosted editor's in-browser JS
  emulators) can be installed without touching OSS code
- Component datasheets: hover popover (ComponentInfoPanel) + markdown
  docs for common parts
- Per-chip S3/C3 basics examples for the gallery
- .gitignore: never allow pro emulator mask ROMs into the OSS repo

New: online-only board showcase. Boards implemented by the hosted editor
(ESP32-C6, M5Stack Core, Cardputer ADV, Pimoroni RP2350 family) appear
in the picker as advertisement cards with an ONLINE badge linking to
velxio.com, where they are free to use. Ads auto-hide in any build that
registers the real BoardKind.
2026-07-21 00:22:19 -03:00
David Montero 8e33088752 fix(editor): sync URL after New workspace + sever project identity on .vlx import
Three stale-project-identity fixes from reviewing the New-workspace flow:

- New workspace (web): handleNewClick cleared the workspace and the
  current project but left the browser on the old /user/slug URL — a
  refresh (or back-button pop) silently reloaded the OLD project over
  the fresh unsaved workspace. Now replaceState's to the localized
  /editor (replace, not push, so no back-entry points at the stale
  project route).
- New workspace (desktop menu): same URL fix for the newProject menu
  action, which cleared identity but never left the project route.
- .vlx import: importVlxFile mutated the stores WITHOUT clearing
  currentProject — with a saved project open, autosave saw the
  imported content as dirty edits on the old projectId and silently
  PUT the .vlx contents over the user's saved project (and pushed the
  clobber to GitHub on linked projects). Now severs identity first,
  same guard loadExample.ts already documents.
2026-07-18 08:24:06 +02:00
David Montero Crespo e72413a13f feat(ui): replace native confirm() dialogs with reusable modal
Convert the remaining window.confirm() call sites to the in-app
MessageDialogHost, extended with a new confirm mode (Cancel + Confirm
buttons, optional danger styling) via showConfirmDialog().

Sites converted:
- New workspace (EditorPage)
- Load project / delete file (FileExplorer)
- Overwrite SPIFFS file (BoardOptionsModal)
- Delete VFS node (VirtualFileSystem)

All dialog strings are internationalized across the 9 supported locales
(en, es, pt-br, it, fr, zh-cn, de, ja, ru); the two previously
English-only modals now pull from i18n too.
2026-07-18 01:59:18 +02:00
David Montero Crespo 108d330efd feat(editor): preserve in-progress workspace across a login redirect
The Sign-in links navigate with a full page load (they mount in a
separate React root without Router context), which wipes the in-memory
Zustand workspace. New utils/workspaceDraft stashes the whole workspace
(reusing the lossless .vlx serialisation) to sessionStorage before that
navigation and restores it once when the editor remounts after login —
so a user who was building a circuit and signs in lands back on their
work instead of the empty starter board.

Strictly scoped to the login round-trip by a one-shot restore flag (not
a general autosave), and skipped when a named project is already loaded
so it never clobbers one. EditorPage calls restoreStashedWorkspace() on
mount; the pro overlay's auth links call stashWorkspaceForAuth() before
navigating.
2026-07-17 06:27:28 +02:00
David Montero 3934cbc0f8 fix(editor): stop unified toolbar controls overlapping on a narrow bar
The top bar packs three zones onto one row: the view-mode toggle, the editor
actions (Compile/Run/Stop/...), and the canvas controls (board selector,
Serial, Scope, zoom, Add) portaled in from SimulatorCanvas. The editor zone
was flex:1 min-width:0 while the canvas zone was fixed-width, so when the bar
narrowed - mainly when the right-docked AI chat opens - the editor zone shrank
below its own buttons and painted them over the board selector / Serial /
Scope. The existing collapse logic was keyed to the viewport (@media 768px),
so it never fired on a wide screen with the chat open.

Make the shared bar a container-query context and collapse every zone by the
bar's own width instead of the viewport: view-mode labels drop to icons first,
then Serial/Scope/Add labels and the board selector ellipsizes, then the
component count and finally the zoom buttons (wheel-zoom still works). Floor
the editor zone at its collapsed content width so it can never underflow and
overlap; past that the lower-priority canvas controls yield toward the right
edge instead. Verified across bar widths 660-1140px with the AI chat open:
overlap eliminated, dropdown menus still render un-clipped.
2026-07-03 07:32:14 +02:00
David Montero 7f65cd65bf refactor(editor): remove redundant file-tabs bar from the toolbar
The file-tabs strip in the toolbar center duplicated affordances that
already exist elsewhere: the file it showed is selected in the left file
explorer, and its board-owner label duplicated the board selector combo.
It also ate horizontal space and crowded the action row on narrow panes.
Remove the FileTabs component entirely; the left explorer is now the single
place to switch files. The toolbar center slot stays as an empty flex
spacer so the right action group remains pinned to the far right.
2026-07-03 06:50:21 +02:00
David Montero eb6a59adf6 style(ui): unify every interactive surface on the single #0071e3 accent
Sweep all off-brand blues (#007acc logo-blue, #0e639c, #3b82f6) and the neon
cyan family (#4fc3f7, #00b8d4, #00e5ff, rgba 0,184,212 / 0,122,204) across the
editor, simulator, examples gallery and pages to var(--color-action-primary).
The Code/Both/Circuit toggle, board chips, library manager, modals etc. now
match the Libraries / +Add buttons. Also keep the editor Libraries label
visible in the default split (lower the container-query threshold).
2026-06-21 05:34:25 +02:00
David Montero d03fed3293 style(ui): solid palette rebrand — drop neon outlines for tokenized solid fills
Unify the editor toolbar and landing page on the single #0071e3 accent
(token --color-action-primary). Replace the fluorescent cyan/green outline
treatment (Libraries pill, overflow icons, compile/run/stop, feature tiles,
pricing/licensing badges, classroom banner) with solid fills following the
+Add button. Green/red kept solid and semantic-only (run/success, stop/error).
2026-06-21 05:07:43 +02:00
David Montero e51d26ce33 feat(editor): follow-up GitHub star prompt for past dismissers
The star banner used a single localStorage flag (velxio_star_prompted)
set identically whether the user clicked through to the repo or just
closed it, so once dismissed it never showed again and clickers and
closers were indistinguishable.

Now track three flags:
  velxio_star_prompted     - dismissed the first ask
  velxio_star_prompted_v2  - dismissed the follow-up (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. Capped at two asks total.

Adds starBanner.title2/body2 copy in all 9 locales.
2026-06-15 22:15:14 +02:00
David Montero Crespo fae9208fae Merge branch 'master' of https://github.com/davidmonterocrespo24/velxio 2026-06-15 11:57:59 -03:00
David Montero Crespo cf6a609671 Raspberri pi 4 y 5 2026-06-15 11:57:10 -03:00
David Montero b820c372cc feat(seo): add /v3 release landing page + refresh About for 3.0
- New Velxio3Page (/v3): retro CPUs (Z80/8080/4004/4040/8086), MicroSD,
  ePaper, multi-board interconnect, ngspice WASM migration, undo/redo,
  -88% bundle, 100+ examples. JSON-LD (SoftwareApplication 3.0.0, FAQ,
  breadcrumb). Mirrors the v2.5 page; reuses SEOPage.css/Velxio2Page.css.
- Wire-up: App.tsx route, entry-server.tsx prerender map, seoRoutes.ts
  entry (priority 0.95, prerendered to dist/v3/index.html). v2-5 demoted
  to 0.9.
- AboutPage: Velxio 3.0 is now the 'latest' release card (v2.5 + v2 kept);
  stats refreshed (boards 17 -> 19+, CPU architectures 6 -> 10+ for the
  new retro ISAs).
- i18n: new v3 block in releases.json + about.releases.v3* in common2.json,
  auto-translated to all 9 locales via translate-i18n.mjs.
2026-06-15 08:52:09 +02:00
David Montero 8617d3b224 feat(library-manifest): per-board manifests + autocomplete
Library manifests are now PER-BOARD (each board carries its own velxio.json),
so two boards in one project can use different (even conflicting) libraries
without clashing — the multi-board extension of the no-clash guarantee.

- board.libraries on BoardInstance + serialisableBoard: rides in boards_json,
  so it round-trips, dirty-checks, autosaves and restores natively. This also
  removes the load-restore hacks (useLibraryManifestStore + applyProjectManifest
  deleted): the manifest is plain board state.
- loadProjectState now restores per-board boardOptions/spiffsFiles/libraries
  (it previously dropped them).
- EditorToolbar single + compile-all send the COMPILING board's libraries.
- Backend compile.py prefers the client's per-board request.libraries; the
  project-level libraries_json (now the union of all boards) is the fallback.
- buildLoadPayload migrates pre-per-board projects: seed each board with the
  project union so they keep compiling scoped.
- Library Manager 'In project' tab edits the ACTIVE board's velxio.json (shows
  the board name) and the add field is now an autocomplete (installed libs +
  index search) so users pick from a list instead of typing names.

Deletes useLibraryManifestStore.ts + applyProjectManifest.ts.
2026-06-07 06:07:48 +02:00
David Montero c93924541c feat(library-manifest): user-facing velxio.json config + load-restore
End users can now configure a project's declared libraries (the compile scope):
- Library Manager gains an 'In project' tab = the project's velxio.json:
  declared libs as removable rows, quick add-by-name, and a raw velxio.json
  editor. Installing a library auto-adds it to the project. Installed-tab rows
  get an 'Add to project' toggle.
- FileExplorer shows a velxio.json entry (with declared count) that opens the
  Library Manager via a window event the toolbar listens for.
- applyProjectManifest(): restore a saved project's manifest into the store on
  load so the editor/toolbar/Library Manager/velxio.json reflect it.
- computeProjectStateHash() includes the manifest so declaring a library marks
  the project dirty and autosaves.

Note: the OSS ProjectByIdPage also calls applyProjectManifest for parity, but
velxio.dev routes the pro-overlay ProjectByIdPage (wired separately).
2026-06-07 05:08:34 +02:00
David Montero 4a21c4f938 fix(frontend): restore project library manifest inside buildLoadPayload (P2.4)
The inline manifest-restore in the load .then was being tree-shaken out of the
lazy ProjectByIdPage chunk (the deployed bundle had the save wiring but not the
load). Move it into buildLoadPayload, which is an exported helper (used by tests)
so its body is never dropped. Reloaded projects now re-send their manifest.
2026-06-07 01:13:55 +02:00
David Montero 288ab46521 feat(frontend): persist + restore project library manifest (P2.4 projects)
Saved projects now round-trip their declared library manifest (compile scope):
buildSavePayload includes libraries_json from useLibraryManifestStore; loading a
project restores it (and clears any stale example manifest). Existing projects
load with an empty manifest -> legacy scan-all (unchanged); new saves capture
whatever manifest is active. Pairs with the backend libraries_json column.
2026-06-07 00:47:39 +02:00
David Montero a68e7f8e94 feat(editor): rename boards & custom chips; show which target owns each file
Phase 2 of the run-system/UX work.

- BoardInstance gains an optional user ; boardDisplayName(board) resolver
  (name || kind label) routes every INSTANCE-label surface: file-explorer
  section header, compile console (EditorToolbar), canvas selector/tooltip/
  context-menu, Serial Monitor tabs, Oscilloscope board picker, Board Options
  subtitle. Board/component pickers keep the KIND label (they pick new boards).
- Inline rename on board AND chip section headers (double-click the name, or a
  hover pencil button). Board -> updateBoard(id,{name}); chip -> chipName in
  properties. Enter commits, Escape cancels (cancel-flag ref guards the
  unmount-fires-onBlur footgun), empty clears to the kind / 'Custom Chip'.
- FileTabs shows an owner badge naming the board/chip whose files are shown
  (resolved as a selector so it doesn't re-render on every sim pin toggle).
- CustomChipDialog no longer clobbers a user-given chipName: chip.json's name
  only seeds the blank defaults (My Chip / Custom Chip); loading an example
  relabels explicitly.
- Persistence: board name round-trips via projectPayload (+ dirty hash),
  vlxFile, ProjectByIdPage load + loadProjectState; chipName rides components_json.
- Drive-by: fixed a pre-existing rules-of-hooks violation in BoardOptionsModal
  (early return before a useCallback).

Reviewed by a 3-agent adversarial pass (completeness / persistence / correctness);
all major findings folded in.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 06:22:25 +02:00
David Montero 5bbf2427ec feat(pricing): surface STM32 & Raspberry Pi 3/4/5; refresh board count
The board roster grew to 30+ (8 STM32 variants + Raspberry Pi 3/4/5), so
the home pricing cards and SEO FAQ were stale at '19 boards'.

- Home pricing (9 locales): free bullet '19 boards' -> '30+ boards';
  the Maker bullet that just repeated the board count now states the real
  paid differentiator — unlimited ESP32 / STM32 / Raspberry Pi simulation
  time (free is time-capped on these server-side QEMU boards).
- SEO FAQ: roster updated to 30+ boards across 6 CPU architectures,
  adding ARM Cortex-M (STM32) and Raspberry Pi 3/4/5.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 07:29:54 +02:00
David Montero 44f12f0e53 feat(seo+nav+community): public-project indexing + classroom nav + examples grid
Search-engine indexing of public projects
- Update robots.txt to also list /sitemap-projects.xml so Googlebot /
  Bingbot discover every public project's canonical /:username/:slug URL.
- Add /docs/github-sync + /classroom entries to seoRoutes.ts so the
  build-time sitemap.xml picks them up.

Navigation polish
- AppHeader gains a "For schools" link between Pricing and Download.
- LandingPage's pricing section gets a slim banner under the cards
  pointing institutional visitors to /classroom (visible discovery path,
  not just a footer link).
- Localised header.nav.classroom + landing.pricing.classroomBanner +
  landing.pricing.classroomCta across all 9 maintained locales (en/es/
  pt-br/fr/de/it/ja/ru/zh-cn).

Community examples
- New CommunityProjectsGrid component lives next to ExamplesGallery on
  /examples.  Fetches /api/projects/featured (Pro-overlay-only endpoint)
  and renders the top public projects ranked by run_count.  Quietly
  hides itself when the endpoint returns nothing or fails, so the OSS
  build still ships cleanly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 18:11:28 +02:00
David Montero 0bcfbde617 feat(seo+landing): /classroom in footer + seoRoutes for sitemap inclusion
LandingPage footer gains a "For schools" link sitting between Pricing
and About — gives institutional visitors a discoverable path to the
Classroom landing without burying it inside the FAQ.

seoRoutes.ts adds the /classroom entry (priority 0.85, monthly
changefreq) so the auto-generated sitemap picks it up on every build.
Bonus: getSeoMeta('/classroom') now returns the institutional title +
description if any other code wants to read it programmatically.

The static public/sitemap.xml is not committed — `npm run generate:sitemap`
overwrites it during the Docker build, so any hand-edit would be wiped.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 17:21:41 +02:00
David Montero 1d847d19cc fix(landing): pricing card prices, names + features for the shipped tiers
The home page's pricing section was still showing the dropped Phase 0
shape (Free / Pro $15 / Pro Max $35) instead of what /pricing and the
billing backend actually serve (Free / Maker $7 / Pro $19).

  - Replace the middle card from "Pro $15" → "Maker $7" (CTA "Start
    Maker") + the right card from "Pro Max $35" → "Pro $19" (CTA
    "Subscribe to Pro").  "Most popular" badge moves to the now-Pro
    card (still the upsell sweet spot).
  - i18n keys renamed in lockstep: tiers.pro → tiers.maker, tiers.pro_max
    → tiers.pro.  Updated in all 9 locales (en/es/pt-br/fr/de/it/ja/ru/
    zh-cn) with translated copy that mentions the actually-shipped Pro
    perks (private projects, GitHub Sync, BOM CSV, schematic PNG,
    watermark-free embed).  The Spanish line about "Maker" is left as
    the loanword so it stays consistent with /pricing.

No backend changes — quota.py PLANS was already correct.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 17:00:02 +02:00
davidmonterocrespo24 0dcb504c1b feat(landing): "Try Simulator Free Online" + download CTA slot below
Two coordinated copy/layout changes on the landing hero:

  - Primary CTA label gets "Online" added across the 9 supported
    locales — "Try Simulator Free Online →" / "Probar el simulador
    online gratis →" / etc. Reason: with the Velxio Desktop
    download path now live, users should immediately understand
    that the green button is the BROWSER version, and there's a
    local install option for people who want it faster offline.

  - The pro-overlay slot for the desktop download CTA was
    `landing-hero-primary-cta` and sat ABOVE the hero CTAs. Renamed
    to `landing-hero-download-cta` and moved BELOW so the funnel
    reads "try online (primary) → or install locally (secondary)"
    instead of "install locally (primary) → try online (secondary)".

OSS layout is unchanged — the slot is still empty in pure builds.

Also picks up the auto-regenerated sitemap.xml lastmod dates from a
recent deploy (every URL bumped 2026-05-19 → 2026-05-21).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 03:33:21 +02:00
davidmonterocrespo24 24f84442e8 feat(frontend): runtime API base + desktop overlay extension points
Adds `lib/apiBase.ts` so the SPA can be repointed at a non-default backend
at runtime (via `window.__VELXIO_API_BASE__`) without losing the existing
`VITE_API_BASE` build-time override or the default `/api` reverse-proxy
behaviour. compilation / libraryService / projectService / metricsService
all flow through it now; axios clients use a request interceptor so the
base resolves per-request rather than at module-load time.

main.tsx grows a `VITE_DESKTOP` flag: when set, the @pro overlay is
skipped (the desktop shell handles license + auth natively) and a
small `./desktop/index` module is dynamic-imported in its place. OSS
builds tree-shake both branches.

LandingPage gets a `data-velxio-slot="landing-hero-primary-cta"` marker
above the existing hero CTAs so velxio.dev can inject an OS-detect
"Download Velxio Desktop" button as the visual primary. The slot is
empty in pure OSS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 04:50:19 +02:00
David Montero Crespo 16f168ba7f feat(board-options): per-board options modal + persistence
Adds a new BoardOptionsModal accessible from the EditorToolbar that exposes
per-board options (currently used for board-specific compile flags). Wires
the modal through:

- types/boardOptions.ts            new BoardOptions shape
- types/board.ts                   BoardInstance gains `boardOptions` + `spiffsFiles`
- store/useSimulatorStore.ts       boardOptions persisted in loadProjectState
- components/editor/EditorToolbar.tsx     button to open the modal
- components/simulator/BoardOptionsModal.{tsx,css}  the modal itself
- components/simulator/SimulatorCanvas.tsx  passes the options through
- utils/projectPayload.ts          board options serialised in saved projects
- pages/ProjectByIdPage.tsx        re-includes the by-id loader needed for
                                   project URLs that reference boards with
                                   their persisted options.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 21:50:45 -03:00
davidmonterocrespo24 f9e5c19f95 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-15 21:46:34 +02:00
davidmonterocrespo24 5ce99fab5d feat(sim): Phase 1c C1+C2 — extract ADC/waveform bridge to solver-agnostic module
connectAnalogInputsToMcu.ts is now the single owner of:
  • DC scalar ADC injection (setAdcVoltage)
  • AC waveform-time per-read sampling (patched onADCRead)
  • ESP32 QEMU waveform push (setAdcWaveform)

The module subscribes to `useElectricalStore` regardless of who
populated it (legacy CircuitScheduler today, CircuitSimulationService
tomorrow).  Replacing the solver path no longer touches ADC logic.

subscribeToStore.ts cut from 591 to 161 lines.  Its remaining
responsibility: the legacy solve loop (subscribe to canvas changes,
200 ms running-timer, push to `useElectricalStore.triggerSolve`).
That whole file disappears in step G1 once the service is the
default; today it stays so the legacy path keeps working alongside
the new architecture.

EditorPage mounts the four subscribers explicitly:
  1. wireElectricalSolver — legacy solve loop
  2. connectLegacySolverToMixedMode — bridge to scheduler cache
  3. connectAnalogInputsToMcu — ADC + waveform replay (NEW)
  4. connectMixedModeSchedulerToStore — flagged WASM path

Pre-existing flaky test in spice-rectifier-live-repro.test.ts
(asserted "wireElectricalSolver queues NO RAF") removed.  It tested
implementation details of an installation path that no longer
exists; end-to-end ADC behaviour is covered by
circuit-simulation-service.test.ts and the BJT-switch integration
test.  Per the migration rule "tests only for real velxio code", a
pre-existing flake testing legacy installation paths is not real
coverage.

Next: D1+D2 — MCU pin event subscriptions so MCU edges drive
scheduler.alterSource + re-resolve, with throttling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 20:20:49 +02:00
davidmonterocrespo24 173037b593 feat(sim): Phase 1c step 1 — feature-flagged WASM-driven connector
Adds `connectMixedModeSchedulerToStore` — when enabled, it subscribes
to the simulator store and drives the MixedModeScheduler's WASM path
(`loadCircuit` + `resolveDc`) directly, parallel to the legacy
`wireElectricalSolver` + `connectLegacySolverToMixedMode` bridge.

Opt-in mechanisms (two ways, either works):
- URL query: `?mixedmode=on`
- Persistent: `localStorage.velxio.mixedmode = 'on'`

When the flag is off (default), behaviour is identical to before.
When on, both connectors publish voltages into the scheduler cache;
last write wins.  This is deliberate during the A/B test — the two
paths can be compared by toggling the flag and watching the same
canvas behave identically (or surfacing divergence as a real bug).

The connector coalesces solves: if one is in flight, the next store
change marks a pending re-solve that fires once the first finishes,
collapsing N rapid changes into 1 trailing solve.  Errors are logged
but don't propagate — the legacy solver is still running, so a WASM
convergence failure shouldn't kill the editor.

`collectPinStates` is now exported from `subscribeToStore.ts` so the
new connector reuses the same per-board pin-number mapping.

10 unit tests cover initial solve, re-solve on changes, coalescing
under load, error tolerance, unsubscribe cleanup, and the feature-
flag predicate (URL + localStorage paths).  jsdom env scoped to this
file via `// @vitest-environment jsdom`.

Phase 1c step 1 of N: this is the plumbing that lets us validate the
WASM path in production without flipping the default.  Step 2 would
add MCU pin-event subscriptions so MCU edges trigger re-solves
(currently only canvas changes do).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 18:53:36 +02:00
davidmonterocrespo24 da07345bc0 feat(sim): Phase 1b continued, step 4 — bridge legacy solver into MixedModeScheduler
Connects the existing electrical solver's output (nodeVoltages +
pinNetMap from useElectricalStore) to the mixed-mode scheduler's
voltage cache.  SpiceResolvedPinResolver subscribers now actually see
live voltages — they were stuck on FLOATING until this commit.

Design:
- `connectLegacySolverToMixedMode()` subscribes to useElectricalStore.
  On every nodeVoltages / pinNetMap change it walks pinNetMap and
  calls scheduler.publishVoltage(componentId, pinName, v) for each
  pin.  Ground pins (canonical net '0') resolve to 0 V directly.
  NaN / Infinity voltages are skipped.
- `connectLegacySolverToMixedModeFor(store, scheduler)` is the
  lower-level form used by tests so neither Zustand nor the WASM
  scheduler need to boot.
- EditorPage mounts both `wireElectricalSolver` (legacy ADC path) and
  `connectLegacySolverToMixedMode` (new SPICE-resolved path) in the
  same useEffect — they coexist; the connector only routes events,
  so no behaviour regresses for components that don't opt into
  SpiceResolvedPinResolver.

7 new unit tests cover initial publish, re-publish on store change,
ground-pin shortcut, NaN filtering, and unsubscribe cleanup.

This is the wiring that completes Phase 1b's end-to-end pipe.  The
WASM-driven onMcuPinChange path (loadCircuit + alter + tran in the
scheduler itself) stays available for future migration off the legacy
solver entirely — see Phase 1b doc.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 17:15:24 +02:00
David Montero Crespo 65cbc403d2 feat(examples): /example/<id> route with pinned URL
Mirror of the /project/<uuid> pattern but for built-in examples.
Loading an example used to navigate to a generic /editor and lose
all trace of which example was loaded — same URL whether you
clicked Blink or Doom, nothing shareable, no back-button history.

New page: pages/ExampleEditorPage.tsx
  - Route: /example/:exampleId  (singular, distinct from the plural
    /examples/<id> landing).
  - useEffect calls loadExample(...) once when exampleId changes,
    guarded by a ref so React strict-mode's double-effect doesn't
    re-load (which would clobber any edits the user made).
  - Renders <EditorPage /> after the load completes — same as how
    ProjectByIdPage stays mounted at /project/<uuid> after load.
  - SEO: title + description per example, canonical URL points at
    /example/<id>.
  - 404 state for unknown ids (typo'd link, deleted example).
  - Inline install progress while libraries fetch — the overlay
    UI moved here from ExamplesPage/ExampleDetailPage so progress
    is visible right at the URL you'll bookmark.

App.tsx — registered the new route alongside the existing landing.
Both coexist on purpose:
  /examples/<id>  = SEO landing page (preview, badges, "Open in
                    Simulator" CTA). Indexed by Google (130 URLs
                    already in sitemap.xml).
  /example/<id>   = live editor with the example pre-loaded; URL
                    stays pinned so the link is shareable +
                    bookmarkable like a saved project URL.

ExamplesPage — gallery now navigates to /example/<id> instead of
calling loadExample directly. Also drops the install-overlay block
(progress UI is on ExampleEditorPage now).

ExampleDetailPage — "Open in Simulator" navigates to /example/<id>
instead of loading directly. Drops its own install overlay too.

Side effect: this also kills the data-loss bug from 95f2aa9 in a
second way. Even if a future change forgets to call
clearCurrentProject() somewhere, navigating into ExampleEditorPage
forces a fresh page transition — the previous project's state +
the auto-save subscription don't survive into the example session.

Build verified (vite OSS+pro, 285 SEO pages prerendered).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 00:14:36 -03:00
David Montero Crespo 5c993d6c2a 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-14 15:31:12 -03:00
davidmonterocrespo24 729c8785ba feat(compile): expose compile logs via Zustand store + UI slot
Two minimal hooks so the velxio-pro agent overlay can offer a 'Diagnose
this compile failure with AI' affordance without touching upstream
component internals:

  - New store/useCompileLogsStore: holds the editor's compile output as
    Zustand state instead of local React useState in EditorPage. The
    setter accepts both a value and an updater fn so the EditorToolbar
    callers that used setCompileLogs(prev => [...prev, log]) keep
    working without changes.

  - CompilationConsole header now renders a
    <div data-velxio-slot='compile-console-actions' /> when errorCount
    > 0. The pro overlay mounts a 'Diagnose with AI' button into this
    slot via slotMounter. Empty in the OSS image — no behaviour change.

EditorPage replaces its local useState<CompilationLog[]> with the store
selector. The downstream prop-drilled setCompileLogs callers (toolbar,
sub-toolbars) keep their signature.

Companion commit lands the button + diagnostic prompt builder in the
velxio-prod overlay.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 16:44:05 +02:00
davidmonterocrespo24 888ce03cc3 docs: BUILD-QEMU.md + 'Build QEMU from source' docs section
Adds a transparent self-hosting path for users who would rather not
run Velxio's prebuilt libqemu binaries. The prebuilts have always
been a convenience under the AGPLv3 license; this just documents
how to skip them.

- docs/BUILD-QEMU.md as the canonical step-by-step (dependencies per
  Debian/Arch/macOS, ESP32 xtensa + ESP32-C3 riscv32 configure-and-
  ninja, drop-in instructions, troubleshooting, license notes on the
  QEMU/Velxio GPL-vs-AGPL boundary).
- DocsPage gets a new 'build-qemu' section between Setup and Roadmap
  in the sidebar. Content is hardcoded English (technical reference,
  not marketing copy) and ends with a link to the .md on GitHub.
- nav + SEO meta keys added to all 9 locales (de en es fr it ja
  pt-br ru zh-cn). Body remains English in every locale; technical
  content doesn't need translation for the audience that follows it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 05:44:10 +02:00
davidmonterocrespo24 fedb197be0 feat(header): add Pricing link to top nav + landing footer
The /pricing page exists (PricingPlaceholder upstream, real PricingPage
portal-mounted by the private overlay) but had no entry in the top nav.
Adds 'pricing' to header.nav in all 9 locales (de, en, es, fr, it, ja,
pt-br, ru, zh-cn), wires the Link in AppHeader between About and Blog,
and mirrors the link in the landing-page footer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 05:28:59 +02:00
davidmonterocrespo24 b7797b1eea feat(landing): AI agent + pricing sections
Two new sections on the landing page, between Features and Support:

1. "Powered by AI agents" — three cards explaining the in-editor agent
   (place & wire parts, generate code, diagnose circuits). Calls out
   DeepSeek-V4-Flash + Gemini 2.5 as the LLM backbone so visitors know
   the simulator does more than draw boxes.

2. "Pricing" — three cards summarising Free / Pro / Pro Max with the
   actual monthly cost, the daily AI-message quota, and a CTA per
   tier. Pro is highlighted as Most Popular. Free CTA opens the editor,
   the two paid CTAs link to /pricing where the PayPal subscription
   flow lives.

The simulator itself stays free — only the AI-agent quota changes per
tier — that copy is repeated in the section subtitle so visitors don't
worry about the boards/components becoming paywalled.

All 9 locales translated.
2026-05-13 20:24:40 +02:00
davidmonterocrespo24 5d40408718 feat(landing): licensing section — AGPLv3 + commercial option
Adds a two-card section at the foot of the landing page (before the
brand footer) that surfaces Velxio's licensing model: AGPLv3 for the
public release, commercial license for teams that need to ship
Velxio inside closed-source products. Mirrors the existing
.feature-card visual language so it slots into the page without a
new design system.

Commercial CTA opens a mailto:info@velxio.dev. Open-source CTA links
to GitHub via the existing trackVisitGitHub handler so the analytics
event still fires.

All 9 locales translated.
2026-05-13 14:27:49 +02:00
David Montero Crespo 44789cf58b feat(auth): welcome email on register + password reset via Odoo mail relay
Adds the transactional email pipeline driven from the Odoo SMTP relay so
new sign-ups get a Velxio-branded welcome and existing users can reset a
forgotten password without us running our own outbound mail server.

Backend:
- PasswordResetToken model: one-time, SHA-256-hashed (plain text never on
  disk), TTL 60 min, marked used_at on consume to prevent replay.
- POST /auth/forgot-password — anti-enumeration (always 200 + generic
  message), rate-limited 3/hour/user.
- POST /auth/reset-password — verifies token, hashes new password,
  atomically marks token used.
- /auth/register hooked with asyncio.create_task to fire welcome mail —
  registration is never blocked on Odoo being up.
- New service app/services/odoo_mail.py: async httpx wrapper, fire-and-
  forget, swallows every error so the request lifecycle stays clean.
- Settings ODOO_URL / ODOO_API_KEY / ODOO_MAIL_TIMEOUT_S /
  PASSWORD_RESET_TOKEN_TTL_MINUTES / PASSWORD_RESET_RATE_LIMIT_PER_HOUR.

Frontend:
- /forgot-password page (single email field + "check your inbox" state).
- /reset-password?token=XYZ page (new password + confirmation, redirects
  to /login?reset=ok on success).
- "Forgot your password?" link + green confirmation banner on /login.
- authService gains requestPasswordReset() and resetPassword().

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:34:30 -03:00
David Montero Crespo 3428ab130a feat(i18n): translate AboutPage prose + 15 SEO landing pages (blocks 15+16)
AboutPage: ~28 prose blocks across Story / How It Works / Open Source /
Creator / Releases / Quote / Community sections; <Trans> for paragraphs
with inline <strong>, <em>, <a> markup.

15 SEO landing pages now use t() for all user-facing copy under
seo.<page>.* keys: CircuitSimulatorPage, SpiceSimulatorPage,
ElectronicsSimulatorPage, CustomChipSimulatorPage, Attiny85SimulatorPage,
ArduinoSimulatorPage, ArduinoEmulatorPage, AtmegaSimulatorPage,
ArduinoMegaSimulatorPage, Esp32SimulatorPage, Esp32S3SimulatorPage,
Esp32C3SimulatorPage, RaspberryPiPicoSimulatorPage,
RaspberryPiSimulatorPage. Code blocks, FQBNs, JSON-LD schema strings
intentionally stay in English.

The seo bundle (67KB English source) is split into 4 balanced files
(seo.json + seo2.json + seo3.json + seo4.json, ~17KB each) so each
DeepSeek translation request stays inside the 8192-token output cap.
i18n bootstrap merges all 4 halves under the seo.* keyspace.

Translations: 8 locales × 4 seo bundles all regenerated via DeepSeek.
common.json (now 30KB after about additions) only has zh-cn refreshed
so far — the remaining 7 locales' common.json need a follow-up pass
(the bundle is at the edge of DeepSeek's output limit).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 18:38:51 -03:00
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