Commit Graph

1436 Commits

Author SHA1 Message Date
velxio-deploy 8fd81fa080 chore(examples): refresh 20 thumb file(s) [auto] 2026-08-10 18:16:37 +02:00
David Montero Crespo 7dde3162f7
Merge pull request #266 from davidmonterocrespo24/v3.2
V3.2
2026-08-10 12:31:40 -03:00
David Montero Crespo 044b619d18 fix(ci): cap mcp below 2.0 — the backend suite has not run since 2026-07-29
mcp 2.0.0 shipped on 2026-07-28 and deleted mcp.server.fastmcp, the module
app/mcp/server.py imports. requirements.txt asked for `mcp>=1.0.0` with no
ceiling, so the very next CI run resolved the new major and died importing
test_mcp_tools.py.

That failure is a COLLECTION error, not a test failure: pytest aborts with
exit 2 before running anything, so all 295 unit tests have been silently
unrun on every push since. The workflow was red, but red for a reason
nobody's code caused.

The deploy venv escaped only by accident — it was built while 1.x was still
current, and deploy.sh rebuilds it when requirements.txt changes, so the
next rebuild would have walked into the same wall and taken the deploy gate
down with it.

Verified in a clean venv built from this file: resolves to mcp 1.29.0,
322 passed / 18 skipped, test_mcp_tools.py's 35 tests among them.

Raising the cap means porting to the 2.x server API (mcp.server.mcpserver).
2026-08-10 17:14:39 +02:00
David Montero Crespo 0b875d4440 Merge remote-tracking branch 'origin/master' into v3.2-worktree
# Conflicts:
#	backend/app/services/espidf_compiler.py
#	frontend/src/components/editor/EditorToolbar.tsx
#	frontend/src/utils/vlxFile.ts
2026-08-10 17:00:24 +02:00
David Montero Crespo 7a7c9f93f7 feat(simulator): tilt and battery controls for boards that carry an IMU or a gauge
The emulated BMI270 and battery gauge were faithful and inert: with nothing
driving them the IMU reported the board lying flat for the whole run and the
gauge reported the single voltage it was seeded with, so a sketch that reacts
to tilt or warns on low charge could not be exercised at all.

A Sensors popover on the canvas header supplies the missing half. Tilt is
entered as roll/pitch on a drag pad rather than three raw axes — what a user
means is "lean it left" — and gravity in the board's frame is that rotation
applied to (0, 0, 1) g, which is also why the vector stays at 1 g whatever the
attitude. Turning the board spins the gyro for as long as the drag lasts and
parks it back at zero shortly after, matching a real part: a held tilt reads no
rotation. Battery is a slider over the LiPo working range.

Gated per board by builtInImu / builtInBattery on the pro board def, the same
way the Mic toggle is gated, and each section renders only if its flag is set.

The microphone stream also learns to resample. The engine now tells the source
which rate the guest programmed, so a sketch recording at 8 kHz consumes two
captured samples per read instead of playing back at half speed.
2026-08-09 23:48:56 +02:00
David Montero Crespo d446031674 feat(news): bare image URLs render inline in post markdown
Authors paste screenshot URLs without knowing the ![](...) syntax; GFM
autolinks them into plain <a> tags, so videos previewed but images did
not. An autolinked href ending in an image extension now renders as the
image itself; explicit [label](...png) links stay links. All images get
loading=lazy.
2026-08-09 23:12:56 +02:00
David Montero Crespo c0d3543573 feat(metrics): compiles and runs remember which gallery example they came from
useProjectStore.currentExampleId — stamped by loadExample, dropped the
moment a real project (or a blank workspace) takes over. The compile
request and the run event carry it as example_id, analytics only, so
'which examples get compiled and run most' becomes answerable.
2026-08-09 22:34:26 +02:00
David Montero Crespo 27f712444b feat(store): hydrationSeq — bulk canvas loads identify themselves
setComponents/setWires (project load, example load, .vlx/wokwi import,
clear) bump a counter so observers that diff components/wires between
updates can tell hydration from a user deliberately adding one part.
Canvas edits (addComponent/addWire) don't touch it.
2026-08-09 22:28:16 +02:00
David Montero Crespo 93314b074b fix(esp32sim): re-sync the flat simulator field after a bridge rebuild
Second half of the deep-link race: an overlay board kind added BEFORE
the overlay registers makes isEsp32Kind() false, so addBoard wires the
board as a generic AVR — and the parts on the canvas attach their I2C
device models to THAT simulator (measured live: the gesture's device
sat on an AVRSimulator while the C6 engine booted with an empty bus).
The overlay's rebuild then swapped simulatorMap but left the flat
`simulator` field stale, so DynamicComponent's [simulator] effect never
re-attached anything. Re-syncing the flat field when the rebuilt board
is the active one makes every part re-attach against the real shim —
sensors, displays, mic sources, the lot.
2026-08-09 22:27:57 +02:00
David Montero Crespo 77e947ef98 fix(esp32sim): a rebuilt board adopts the I2C devices its parts attached
The pro overlay loads async, so a deep-linked example attaches its part
device models to the shim wired around the STOCK QEMU bridge — whose
attachSyncI2cDevice is a silent optional no-op — and only then does the
overlay's factory rebuild the board with the js-engine bridge. The
rebuild made a brand-new shim; the parts on the canvas hold the old one
in their attach closures and never re-attach. Result: the engine booted
with an EMPTY I2C bus and arduino's i2c-ng driver failed every
transaction with ESP_ERR_INVALID_STATE (the seated C6 gesture example —
measured live with the bus device table at Map(0)). Whether it broke
was a chunk-load race; the pro bundle's growth turned it into a sure
loss.

The shim now records every sync-attached device and a rebuilt shim
adopts its predecessor's — through addI2CDevice, so the new Delegating
bridge buffers them pre-connect and its start() replays them into the
engine after reset, as it always did for attaches it actually heard.
2026-08-09 22:06:51 +02:00
David Montero Crespo 8b2715affd feat(metrics): compiles and runs carry the editor BoardKind; ads learn to stay quiet
Several boards share one FQBN (the Pimoroni RP2350 trio all compile as
rpipico2), so FQBN-keyed metrics can't tell them apart. The compile
request and the run event now carry board_kind — analytics only, rides
into the metric hook's extra.

suppressOnlineOnlyAds(): overlay seam so the hosted build can silence
'available in the online editor' ad cards for items it manages — an
embargoed item must vanish, not advertise the site the user is on.
Pure OSS builds never call it; ads behave exactly as before.
2026-08-09 20:48:11 +02:00
David Montero Crespo f791536007 fix(wires): close both halves of the load-time endpoint race
The whenDefined recalc was necessary but not sufficient — measured on
staging, the example route loses BOTH ways:

- the element side: every settle timer can fire before the part is
  findable in the DOM. The pinInfo-ready poll in DynamicComponent now
  also re-derives wire endpoints the first time the part's pinInfo is
  actually readable, and waits up to 10s (the 2s cap was the corner-
  wire bug again on slow connections).
- the wire side: loadExample stores wires with zeroed endpoints BEHIND
  awaits (library installs), potentially after every recalc has come
  and gone. A canvas effect now spots endpoints still at (0,0) and
  runs one deferred recalc; real coordinates land, the condition
  clears, no loop.
2026-08-09 08:32:20 +02:00
David Montero Crespo 4b9fa3f0d0 feat(datasheet): Product-page links carry UTM attribution and report the click
Both Buy renderers (picker hover panel + Part Inspector) now append
utm_source=velxio&utm_medium=simulator&utm_campaign=datasheet with the
component id in utm_content, so vendors can attribute the referral.
trackProductPageClick fires the GA4 event and dispatches a
'velxio:product-page-click' CustomEvent — an overlay build can persist
the click server-side without this tree knowing about it.
2026-08-09 08:16:47 +02:00
David Montero Crespo 75842e3668 fix(wires): recalculate endpoints when a late-defined element upgrades
The canvas re-derives wire endpoints on load-settle timers (100/300/
500ms), but an overlay-defined custom element can upgrade after the
last one — its chunk is large — and calculatePinPosition then finds no
pinInfo, so every wire to the part falls back to its corner and floats
there until something moves it (field report: the Grove gesture module
with all four Grove wires dangling on bare PCB). A customElements
.whenDefined hook now recalculates when the tag actually defines,
however late that is.
2026-08-09 08:13:48 +02:00
David Montero Crespo 6d6d298130 fix(console): the final flush speaks in whole lines, from the real buffer
tailAfterStream sliced result.stdout at the live-buffer offset, but the
buffer interleaves stdout+stderr in arrival order while result.stdout is
stdout-only plus a joining newline — offsets never line up, so the flush
could start mid-line ('f/esptool_py && ...'). Instead, the poll loop now
emits one final onProgress(state:'done') with the completed buffer and
the existing delta logic flushes the tail perfectly aligned. tailAfterStream
is gone.
2026-08-09 08:05:43 +02:00
David Montero Crespo 23784568f6 fix(console): flush the stdout tail the last status poll never saw
With the success-path reprint gone, the lines that land between the
final running-poll and job completion (esptool image creation, the
binary-size summary) silently vanished. tailAfterStream() appends
result.stdout past the streamed offset, noise-filtered, before the
structured analysis — skipped once the live buffer hits its 256 KB
tail-trim cap, where offsets stop lining up.
2026-08-09 07:52:46 +02:00
David Montero Crespo 0a868cc254 fix(console): the compile log tells the story once, without cmake's diary
ESP-IDF builds sprayed the compilation console with configure chatter
(-- cmake status, git-metadata probes that 'fatal' on the synthesized
project tree, IDF component-manager NOTEs, Python env checks) and then
reprinted the entire stdout a second time when parseCompileResult ran
over the final result after the live stream had already shown it.

- isNoiseBuildLine(): pattern list for build-system chatter, applied to
  the live stream deltas and to the final-result parsing.
- parseCompileResult(streamedLive): skips the stdout reprint on success;
  on failure re-emits only error/warning lines so they still get the
  red/yellow markers the plain live stream lacked.

Compiler errors, FAILED blocks, ninja [N/M] progress, esptool output and
binary-size summaries all still show. The full log stays available
server-side.
2026-08-09 07:36:36 +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 fccb51f3f0 Revert "fix(starter): the takeover cedes the agent chat's docked width"
This reverts commit eeb4480dc37f3d62e94f704ebe11c37a706ef5e9.
2026-08-08 06:24:47 +02:00
David Montero Crespo 15654b664b fix(starter): the takeover cedes the agent chat's docked width
The chat reserves its width as padding-right on .app-container, but an
absolute inset-0 layer spans the padding box, so the picker's right
column and its scrollbar landed under the panel. The screen now gives
up the same width through the same CSS variable, with the same 0.12s
ease the padding uses, and falls back to zero in builds without the
overlay.
2026-08-08 05:39:23 +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 027a6055a4 ux(picker): maker-first ordering — passives, analog and logic gates sink to the end
The component grid and the category filter now follow a fixed
maker-first category order (sensors, outputs/LEDs, displays, input,
motors, communication, electromech, other… then passive, analog, logic):
Velxio's audience reaches for a DHT22 or an LED far more often than a
bare transistor or a 74HC gate. Stable sort — order within a category is
untouched, and search results group the same way.
2026-08-08 04:15:10 +02:00
David Montero Crespo 9d38beae0e ux(menubar): Account menu replaces the top-level Language menu
The account options that only lived in the bottom-left user button
(subscription, licenses, simulation history, replays, privacy) are now
reachable from the header too: a new Account menu hosts the pro items via
the SAME data-velxio-slot="user-menu" the corner dropdown uses — one
overlay renderer, two hosts, empty in OSS builds. Language moves from its
own top-level trigger into a section of that menu, so the menubar stays at
five triggers and frees header room for when the AI chat docks.
2026-08-08 04:02:32 +02:00
David Montero Crespo f9a9657b5e ux(canvas): zoom controls float on the canvas' bottom-right corner
The canvas header runs out of room when the AI chat panel docks on the
right — the board selector clipped and the zoom cluster was squeezed out
entirely. The −/%/+ cluster now floats on the circuit canvas' bottom-right
corner (always free), with stopPropagation so a click never starts a pan.
2026-08-07 23:53:48 +02:00
David Montero Crespo cd83813a9b fix(picker+inspector): dedupe Pi cards, wire Zero/1/2 as real boards, readable many-pin inspectors
Component picker: registry entries that ARE boards (the injected Raspberry
Pi family) no longer render as component cards — clicking one dropped a
dead 40-pin canvas prop that never boots (that is how "add a Pi 4" looked
like it placed a Pi 3). The boards row is the only path now, and it gains
Raspberry Pi Zero / 1 / 2 cards (Pi-3 illustration, PRO pill, same gate):
their QEMU profiles already existed in qemu_manager; the canvas renders
them through the Pi-3 element and BoardOnCanvas/store route them like any
Pi kind.

Part inspector: a board whose canvas node is NOT a registered custom
element (the Pi family's plain <img> wrapper) now clones the live node for
the preview instead of giving up — the Pi 3/4/5 pin map was completely
missing before. Wide boards with 40+ pins (Mega, Pi family) render
ROTATED 90° so their long pin rows become side columns with vertical room
(new PINS_ART_ROTATED_H box), and pinInspectorLayout splits a dual-line
header pair to OPPOSITE gutters the way printed pinout posters do — 40
interleaved labels never fit one gutter. Existing 18 layout tests pass.
2026-08-07 23:03:05 +02:00
David Montero Crespo 32b11d83ec fix(compile-output): IDF "Project commit: HEAD-HASH-NOTFOUND" banners are not errors
esp-insights/esp-rainmaker print their version banner to stderr during the
CMake configure; without git metadata in the component tree it reads
"Project commit: HEAD-HASH-NOTFOUND" and the stderr classifier's default
painted it as a red error. Users read it as a failed build (it is not).
Classify "project commit:" lines as info.
2026-08-07 22:58:39 +02:00
David Montero Crespo e4e4331010 ux(canvas): component camera toggles say "Camera", like the board toggle
The header spoke two languages: the ESP32-CAM/Sense board toggle says
"Camera" while component-owned webcams printed their registry label
("HuskyLens cam", "Gesture cam") — which read as a different control.
The button now says "Camera" everywhere; the component identity stays in
the tooltip, where it disambiguates when several cameras coexist.
2026-08-07 22:46:00 +02:00
David Montero Crespo da4678bb60 ux(editor): smaller starter cards, bigger blue + on Blank, Arduino Mega card
Cards drop to a 178px grid (dialog 840px) with tighter type so more
starters fit per row; the Blank card's plus glyph goes accent-blue at
64px. Arduino Mega (mega-blink) joins the Arduino section next to the
Uno. Recaptured xiao-rp2040-blink.webp now that the board art is
silk-screened "XIAO-RP2040" (was "XIAO-ESP32-C3").
2026-08-07 22:11:43 +02:00
David Montero Crespo 704fd6ef32 feat(canvas): header camera toggles for component-owned webcams
Boards with a camera surface their webcam state in the header
(CameraToggle); components that ARE cameras — vision sensors whose
element captures the webcam itself — had no header presence, so users
who learned "the header icon says my webcam is in use" read its absence
as a broken feature. A tiny registry seam lets any component register
its camera (label, live/error/off, reason, toggle); the header renders
one CameraToggle-styled button per entry and nothing when none.
2026-08-07 21:11:33 +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 2db8ae6284
Merge pull request #265 from davidmonterocrespo24/dependabot/npm_and_yarn/vscode-extension/npm_and_yarn-0fe5bafb98
chore(deps): bump the npm_and_yarn group across 2 directories with 4 updates
2026-08-07 15:25:10 -03: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 b1c9c41849 ux(datasheet): soften the purchase link — "Buy" becomes a localized "Product page"
The word "Buy" on a solid button read as an ad inside a work dialog. The
link is really a pointer to the part's product page, so it now says that —
localized in all nine languages — with an external-link icon instead of a
shopping cart. Same URL, same placement, in both the part inspector and the
picker hover card.
2026-08-07 07:13:05 +02:00
David Montero Crespo 16c3089c5f feat(inspector+gallery): example links in the Properties tab; dynamic board tabs in /examples
The part inspector (right-click dialog) now lists the gallery examples that
use the part at the bottom of the Properties tab — matched by metadata id
against boardType/boardFilter/boards[].boardKind/components[].type, so it
works for components and boards alike, including overlay-registered ones
(re-derived via the pro-examples version subscription). Each link opens the
live editor (/example/:id) in a new tab. i18n key in all 9 locales.

The /examples board filter combo was a fixed list, so examples for
runtime-registered boards (XIAO, M5Stack, Raspberry Pi, ...) were reachable
only under "All". The gallery now appends a tab per board kind found in the
registered examples, labelled from BOARD_KIND_LABELS.
2026-08-07 06:00:40 +02:00
David Montero Crespo 66268507c8 fix(epaper): persist the window union across refreshes (fix partial-refresh tears)
e-paper RAM persists between refreshes, but the decoder reset the accumulated
window union on every 0x20 MASTER_ACTIVATION. A PARTIAL refresh (BadgeOS redraws
only a strip when you navigate) then set a small window, so composeFrame emitted
just that strip, placed top-left with the rest black — reading as a 90°-rotated
screen with a black band down the side. Keep the union (and the greyscale flag)
across refreshes so it stays at the full-panel extent set by the first full
refresh; every frame now composes the whole persistent RAM and partial updates
land in place. Cleared only by a real RST (0x12). Paged full refreshes (GxEPD2)
re-cover the panel each frame so they are unchanged; the BadgeOS menu render is
byte-identical and all 23 decoder/epaper tests pass.
2026-08-06 16:53:00 +02:00
David Montero Crespo bd063bfef8 fix(epaper): correct vertical orientation for Y-decrement transposed panels
A panel driven with a Y-decrement entry mode through the rotation-1 transpose
(BadgeOS on the Badger 2350) rendered upside-down: its text read vertically
flipped. The transpose was calibrated against the standard Y-increment
(GxEPD2 / raw-SSD1680) path, where the source index maps to display row via
Wn-1-x; under Y-decrement the source axis ends up flipped, so map it straight
(x) to the display row for that case. Gated on the same yTopIsHigh signal
already used for the RAM read, so Y-increment panels are byte-for-byte
unchanged. Verified: the BadgeOS factory menu now renders upright ("BadgeOS"
title + app icons readable). 23 decoder/epaper tests still pass.
2026-08-06 04:59:14 +02:00
David Montero Crespo 6442217893 feat(epaper): 4-level greyscale rendering in SSD168xDecoder
Panels that upload a custom waveform LUT (0x32) AND write both RAM planes
(0x24 + 0x26) in a frame drive 4-level greyscale: each pixel is 2 bits (one
per plane) composited by the custom LUT into 4 grey levels. Auto-detect this
from the command stream (custom LUT + red-plane written; never key off a panel
name) and expose an optional Frame.grey (0..3) alongside the existing 1-bit
`pixels` (kept as a level>=2 -> white projection so 1-bit and BWR consumers -
EPaperPart, GxEPD2 - are byte-identical). composeFrame builds a parallel grey
plane and rotates it through the same transpose as the palette plane.

The combo (bw<<1 | red) -> level map is [3,2,1,0] (white/light/dark/black):
bw is the dominant luminance bit (bw=0 -> light pair, bw=1 -> dark pair), red
refines one step within each half; verified against the captured menu's plane
populations. Exported DEFAULT_GREY_COMBO_TO_LEVEL + an optional greyComboToLevel
option (tunes ordering only, never forces greyscale on).

Standard SSD1680 paths (raw badge examples, GxEPD2 full refresh) load the OTP
LUT, never send 0x32, never write 0x26 -> unchanged. 23 unit tests pass
(ssd168x-decoder + epaper-part-integration, incl. new greyscale + no-regression
cases).
2026-08-06 03:56:02 +02:00
David Montero Crespo a77ef68456 fix(epaper): honor custom B/W waveform LUTs + descending-Y in SSD168xDecoder
Panels that upload their own B/W waveform LUT via 0x32 (WRITE_LUT) instead of
loading the OTP/built-in waveform (0x22 with the 0x10 load bit) redefine the
generic SSD168x B/W convention: on the OTP waveform RAM bit 1 = white, but a
custom-waveform panel in our catalog inverts it (bit 1 = black). Auto-detect
the custom LUT from the command stream (never key off a panel name) and XOR the
B/W polarity; when set, compose from the 0x24 plane only (the 0x26 plane is the
differential "old" framebuffer, not a mono mirror). Every standard path we
drive (GxEPD2 full refresh, the raw SSD1680 badge examples) loads the OTP LUT
and never sends 0x32, so their polarity is unchanged.

Also fix descending RAM ranges (X-/Y-decrement entry mode, e.g. yrange=[263,0]):
normalize window bounds and the write-cursor wrap with min/max (verbatim [0] as
the low bound collapsed every scanline onto one row -> garbled/blank frame), and
read the visual-top row from the high RAM address under Y-decrement. Byte-
identical for ascending ranges. Removes the dead ramTarget field.

20/20 unit tests pass (ssd168x-decoder + epaper-part-integration).
2026-08-06 02:57:34 +02:00
dependabot[bot] 19288f4b01
chore(deps): bump the npm_and_yarn group across 2 directories with 4 updates
Bumps the npm_and_yarn group with 1 update in the /vscode-extension directory: [esbuild](https://github.com/evanw/esbuild).
Bumps the npm_and_yarn group with 3 updates in the /vscode-extension/webview directory: [esbuild](https://github.com/evanw/esbuild), [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) and [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core).


Updates `esbuild` from 0.20.2 to 0.28.1
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG-2024.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.20.2...v0.28.1)

Removes `esbuild`

Updates `vite` from 6.4.1 to 8.2.0
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/create-vite@8.2.0/packages/vite)

Updates `vite` from 6.4.1 to 8.2.0
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/create-vite@8.2.0/packages/vite)

Updates `@babel/core` from 7.29.0 to 7.29.7
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.29.7/packages/babel-core)

Updates `postcss` from 8.5.8 to 8.5.25
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.5.8...8.5.25)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-version: 0.28.1
  dependency-type: direct:development
  dependency-group: npm_and_yarn
- dependency-name: esbuild
  dependency-version:
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: vite
  dependency-version: 8.2.0
  dependency-type: direct:development
  dependency-group: npm_and_yarn
- dependency-name: vite
  dependency-version: 8.2.0
  dependency-type: direct:development
  dependency-group: npm_and_yarn
- dependency-name: "@babel/core"
  dependency-version: 7.29.7
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: postcss
  dependency-version: 8.5.25
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-05 23:44:03 +00:00
David Montero 388b013ac4 fix(ci): compact commit list + bigger token budget for release announce [skip ci]
The v3.0.2 announce (PR #263, 45 commits) never reached Discord: the
changelog prompt carried every commit body (52 KB), deepseek-v4-flash
spent its whole 6000-token budget reasoning, returned empty content and
the guard aborted the run before posting.

Past 8000 chars of commit detail we now send hash + subject only (52916
-> 3699 chars on that range), raise the budgets to 32000/16000, and retry
once with double when content still comes back empty. Also log
finish_reason and completion_tokens so the next empty answer is
diagnosable from the run log alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 01:41:29 +02:00
David Montero Crespo ede49e2e78 feat(sim): duck-type the browser MicroPython path so pro simulators plug in
The in-browser MicroPython run path required `sim instanceof RP2040Simulator`,
so an overlay-registered pro simulator (e.g. the RP2350's ARM MicroPython)
could never use it. Accept any simulator exposing loadMicroPython(files)
instead - generic plumbing, no board-specific reference; a sim without the
method is a no-op exactly as before. RP2040 behavior is unchanged.
2026-08-06 00:54:54 +02:00
David Montero 588c356e7e fix(espidf): hoist merged_libs_report to function scope
Declared inside the has_arduino branch, so every successful pure
ESP-IDF (language=espidf) build crashed with UnboundLocalError when the
success return read it. Regression from today's manifest auto-migration
change; the Arduino paths were live-verified but the espidf mode was
not.
2026-08-05 20:13:17 +02:00
David Montero Crespo bf56295f41 refactor(inspector): delete confirmation via the global confirm modal
Replaces the inline swap-the-action-bar confirmation (buttons jumped
around) with showConfirmDialog, same pattern as FileExplorer. While the
modal is open the dialog's Escape/click-outside close handlers stand
down. Boards keep their own remove-board modal via skipDeleteConfirm.
2026-08-05 19:18:28 +02:00
David Montero Crespo 64deb204ae feat(news): rich media in posts - inline images/GIFs + YouTube click-to-play previews
NewsMarkdown (shared renderer): YouTube links render as a thumbnail +
play badge that swaps to the youtube-nocookie iframe on click (nothing
loads from the player until the user opts in); images render inline;
all links open in a new tab. Product decision supersedes the alt-text
image stripping: posts embed screenshots/demos, and the README now
documents that embedded media loads in the viewer's browser.
2026-08-05 16:25:08 +02:00
David Montero Crespo 6205e32741 fix(news): harden the feed proxy + honor the self-host privacy promise
Security review follow-ups (all LOW):
- proxy: negative cache on upstream failure (no outbound fetch per
  request while velxio.dev is unreachable) and a 2MB body cap via
  streaming read (httpx timeouts are per read op, not per body)
- NewsModal: OSS builds render markdown images as alt text so viewers'
  browsers never contact remote hosts; pro cloud builds show them
2026-08-05 15:59:03 +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 c9fe6f199d feat(editor): folders - create/delete folder trees in the file explorer
File names may now carry '/' paths; the explorer renders each board group
as a collapsible folder tree (folders first, files in group order, CSS-var
indentation because .fe-file-item pins padding with !important). New-folder
buttons on the board header and per-folder hover actions (new file here,
new subfolder, delete with confirm when files are inside; a folder holding
every file of the group cannot be deleted - groups keep >= 1 file).

Model: names are the source of truth; only EMPTY folders are tracked
(folderGroups per group) so they survive until a file lands in them, then
materialise into the name prefix. Round-trips through .vlx (optional
folderGroups field, laxly validated), the auth stash draft, and
loadProjectState. Examples gain boards[].files so a single board can ship
a full multi-file tree (first entry = main sketch).

Backend: the arduino-cli sketch writer now creates subdirectories for
'/'-pathed names and REJECTS names that resolve outside the sketch dir
('..' traversal) - previously it crashed on the former and wrote outside
the tmp dir on the latter.
2026-08-05 06:48:38 +02:00
David Montero Crespo 5298c7e58c fix(examples): translate leftover Spanish to English in gallery code
- examples-robot-desktop: Serial messages, comments and identifiers
  (right/left servos, light threshold naming, START state) across the
  embedded .ino and headers
- examples-100-days: PULL_DOWN explainer comment in the MicroPython menu day
2026-08-05 06:41:30 +02:00
David Montero Crespo 36c414df8f feat(sd): source files stay off the card - only project DATA files auto-copy
On real hardware your code lives in flash, not on the microSD; a sketch
listing the card should see its data (csv, json, txt, wav...), not its
own source. buildProjectSdImage now skips .ino/.h/.hpp/.c/.cpp/.py from
the workspace auto-copy. Explicit uploads are exempt - an upload always
lands whatever its extension. Panel hint updated; tests cover both the
exclusion and the upload exemption (15 green).
2026-08-04 19:58:31 +02:00
David Montero Crespo 2dcb3eb65f fix(ui): SD download label, board inspector on right-click while running, solid-blue Buy
- The live card file action now says Download (it was Save).
- Right-click opens the board inspector in EVERY mode: the contextmenu
  handler moves from the drag overlay (hidden during simulation) to the
  board wrapper, so the SD panel is reachable while the sketch runs and
  the board's buttons stay interactive.
- Buy button in the inspector action bar goes solid brand blue #0071e3,
  matching Velxio's solid-button language.
2026-08-04 17:43:55 +02:00