Commit Graph

165 Commits

Author SHA1 Message Date
David Montero Crespo 99471d0657 feat(news): announce with a toast, open the modal only on request
Entering the editor could stack two overlays: the starter-template
dialog opened on mount, then four seconds later the "What's new" modal
landed on top of it. Two dimmed backdrops over each other, and the
smaller, *less* important surface in front — the z-indexes said it
outright, news 10000 against the starter's 9000.

The delay made it worse rather than gentler. Arriving after the user has
started reading the templates is the most disruptive moment available;
appearing instantly would at least have let them clear it before
engaging.

The rule this restores: a modal is for what BLOCKS, and an announcement
never blocks. News now slides into the corner as a toast that dims
nothing, steals no focus (role="status", polite), and retires by itself.
The full post is one click behind "Read more" — a modal the user asked
for is a categorically different thing from one thrown at them — and
stays reachable from Help ▸ What's new once the toast is gone.

Worth noting the audience argument too: the starter dialog only fires on
a PRISTINE visit, i.e. for someone brand new, and "what's new" is
meaningless without a baseline to compare against. Returning users, the
ones it is actually written for, arrive with a project loaded and never
saw the collision.

Renamed NewsModal → NewsAnnouncer: it is not a modal by default any more,
and leaving the old name would have mislead the next reader.

Strings are new keys under `news.*`, filled in for all nine locales — the
previous modal had "What's new" and "Got it" hardcoded in English.

Toast sits at z-index 8000, deliberately BELOW the starter dialog, so if
the two ever coincide again the blocking task keeps the foreground.
2026-08-10 19:52:55 +02:00
David Montero Crespo 980c564f66 Revert "ux(editor): the pristine starter picker is a screen, not a modal"
This reverts commit c93d954169655d5f6f6a4cc1a68ad31b4cdd77e4.
2026-08-08 06:24:47 +02:00
David Montero Crespo 3dd780503f ux(editor): the pristine starter picker is a screen, not a modal
A modal over a pristine /editor dims an interface that is empty anyway
and offers a Cancel whose only destination is a dead workspace. The
picker now takes over the content area (StarterScreen): no veil, no
Cancel, the grid gets the room the 840px dialog never had, and the
examples gallery link is the secondary path out. The modal stays for
"New workspace" over existing work, where the replace-everything
warning and Cancel mean something — both shells share one card grid.

The details that make the takeover behave like a page: it sits at
z-index 90 (above the canvas' floating zoom pill, below the header's
stacking context so the menu dropdowns stay clickable; the fixed
account corner box is render-gated instead), the panes underneath are
inert while it is up, focus lands on the heading on mount and parks on
the workspace container on dismissal, and any path that fills the
workspace meanwhile — the File-menu modal, Open project…, a dropped
.vlx — dismisses it rather than letting its cards silently wipe the
loaded work. Full editor.newProject i18n for all nine locales (German
in Sie-form), heading outline h1→h2 / h3→h4, token focus ring, and
reduced-motion respected.
2026-08-08 04:53:36 +02:00
David Montero Crespo 93c5c60d9c feat(editor): starter dialog v2 — circuit thumbnails, blank canvas behind, one ESP32 per chip
The starter cards now carry the same circuit thumbnails the examples
gallery uses (ExampleThumbnail → /examples-thumbs/<id>.webp with the
CircuitPreview SVG fallback): boards with a Blink example reuse its thumb,
the rest get freshly captured starter-<kind>.webp shots in the same folder
(XIAO S3/C3, Netduino 2, Pi 4/5; plus the missing xiao-esp32c6-blink and
xiao-rp2040-blink example thumbs).

The ESP32 section is now one card per chip generation with the Seeed XIAO
variant preferred (DevKit V1 / XIAO S3 / XIAO C3 / XIAO C6), and XIAO
RP2040 joins the Raspberry Pi section — replacing the previous
every-variant list and the separate XIAO section.

Fixes: a pristine /editor visit clears the hardcoded Uno starter BEFORE
showing the dialog (empty canvas behind; cancel now leaves a blank
workspace), and clearing the last board re-points the editor at a
nonexistent group so Monaco no longer keeps showing the deleted board's
sketch (removeBoard only re-points when another board remains).
2026-08-07 21:07:48 +02:00
David Montero Crespo 3bcb8d518e feat(editor): starter-template picker on pristine /editor and New workspace
New NewProjectDialog: a bare /editor visit (and the New workspace button)
offers a blank workspace or a ready-to-run Blink starter per board family
instead of silently dropping the user into the hardcoded Uno + LED starter.
Boards with a gallery Blink example load it fully wired; the rest get a
fresh board with the on-board-LED sketch. Pro-gated boards carry the PRO
pill and go through the boardGateDecision seam. Shows once per page load;
en/es strings (the other locales fall back to English for now).
2026-08-07 20:11:30 +02:00
David Montero Crespo 801bb94eab feat(news): What's New modal - product news feed, once-per-user queue
Editor shows scheduled product-news posts as a markdown modal: max one
per day, oldest first, each post only ever once (marked seen at
delivery). OSS default source proxies velxio.dev's public feed through
the local backend (cached ~6h, anonymous, VELXIO_NEWS=off to disable)
with seen-state in localStorage; a pro overlay can register a
server-backed per-user source via lib/newsSource.ts.
2026-08-05 15:38:14 +02:00
David Montero Crespo 159417c927 fix(examples): direct pro-example URLs no longer hang on Loading example
The load effect listed the overlay's `settled` flag as a dependency. On
a direct /example/<pro-id> URL the load can begin the moment the
overlay registers the example — one microtask before the overlay's
import promise settles. The flip then re-fired the effect mid-load: the
cleanup cancelled it (setReady skipped), the re-run hit the loadedIdRef
guard and returned, and the page hung on "Loading example…" forever.

The effect now depends only on the example itself; the 404-vs-still-
loading decision moved into the render, where reading `settled` cancels
nothing. A load cancelled mid-flight also resets the loadedIdRef guard
so a genuine re-run reloads instead of early-returning.
2026-07-31 04:22:52 +02:00
David Montero Crespo e9053f73fe ui(editor): one control height and one blue across the toolbar strip
Every control in the unified strip is now 28px tall: tb-btn (was 30),
Libraries (was 26), the C++/MicroPython select (was ~22), the board
selector, Serial/Scope, canvas icon buttons, zoom and Add (all were 32).
The explorer toggle moves inside the Code/Both/Circuit segmented group
as its first segment, and every active-segment blue is the Libraries
blue (var(--color-action-primary)) instead of the hardcoded #0e639c.

The editor zone's overlap floor becomes min-width: max-content — the
hardcoded 280px sat below the real content width, letting the console
button paint over the board selector when the docked chat narrowed the
bar. Now the zone can never shrink under its buttons; flex-wrap moves
the canvas controls to a second line instead. Also closes the gap
between the logo and the File menu (header gap 16->6, menubar
margin-left 14->2).
2026-07-31 00:46:08 +02:00
David Montero Crespo 155513a493 fix(editor): account footer is the account alone; language rides elsewhere
The bottom-left block read wrong: a globe crowding the account button for
no gain — language already lives in the menubar's Language menu for
everyone, and (for signed-in users) inside the account menu. The footer
now holds only the account button.

Width-aware, as requested: when the explorer is dragged narrow (footer
under 150px, via container query) the username hides and the avatar
alone identifies the account; with the explorer collapsed, the floating
fallback box shows just the circle.

Adds the OSS half of the crash fix: LocaleSync listens for
'velxio-locale-switch' window events and performs the locale change as a
normal SPA navigation. The pro account menu is injected into its own
React root OUTSIDE the Router, so it cannot navigate itself — its
language rows dispatch this event instead.
2026-07-30 22:56:19 +02:00
David Montero Crespo e17f6fd6cf i18n(editor): translate the remaining raw UI strings
The explorer toggle tooltips (Hide/Show file explorer) and the header's
Share-project title were raw English — now keyed and translated in all
nine locales, same common2.json home as the menubar strings.
2026-07-30 21:18:37 +02:00
David Montero Crespo d233b66cc9 feat(editor): View and Language menus — full desktop-menu parity
The menubar now matches the desktop app's native menu set: File, Edit,
View, Language, Help.

View collects what the user asked to reach from a menu: Compile (Ctrl+B),
Run, Stop, Reset up top; the panel toggles — File Explorer, Output
Console, Serial Monitor, Oscilloscope/Logic Analyzer — in the middle
(store-backed ones render a live check, like a real desktop menu); and
the canvas view actions (center, zoom) move here from Edit, which goes
back to being undo/redo only, as menus have always worked.

Language lists the nine locales with the current one checked, switching
through the same switchLocale path the header globe uses — so language is
reachable from the menubar regardless of the account state, on top of
living in the signed-in account menu.

Run/Stop/Compile/Reset and the console toggle register through the
editorCommands seam from EditorToolbar (their handlers close over its
state); the explorer toggle from EditorPage; serial and scope call their
stores directly, same as undo/redo.
2026-07-30 21:05:55 +02:00
David Montero Crespo 3b7abfc4f7 feat(editor): account block fuses into the explorer footer
The floating corner box sat ON TOP of the file tree — create enough files
and the last rows scrolled underneath it. The account + language block is
now the explorer panel's footer (VS Code style): the tree scrolls above
it, always. When the explorer is collapsed the block falls back to the
small fixed corner box, so account and language stay reachable.

The standalone language globe becomes a logged-out affordance: once the
pro auth slot renders its signed-in marker (data-auth-user), a :has()
rule hides the globe — signed-in users change language inside the
account menu, where the pro overlay now hosts it.
2026-07-30 20:54:24 +02:00
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