Changelog
Release-notes convention
Every release should follow this structure:
## [Version] — YYYY-MM-DD
### 🚗 For the driver- (UX, new widgets, hardware support, etc.)
### 🔧 For the tuner / installer- (config, calibration, ECU parameters)
### 🔬 Firmware / dev- (build flags, schema, Rust ports, refactors)
### ⚠️ Breaking- (concrete impact + required action — re-flash, re-burn config, etc.)
### PRs- #XXXX — ...- #YYYY — ...An empty release, or one reduced to a bare list of PR titles, is not enough: the public changelog must be readable by someone who didn’t watch the development stream.
Releases
-
v0.12.0 pre-release
Published June 4, 2026
Details
Firmware-only release. See README.md for the bundled changes.
-
v0.11.0 pre-release
Published May 26, 2026
Details
Firmware-only release. See README.md for the bundled changes.
-
0.10.0 pre-release
Published May 20, 2026
Details
## CANShift v0.10.0 A UX-focused release: the device finally **reads at a glance**. Per-sensor colours, real units everywhere, smaller decimals, a diag drawer that actually closes, and a top bar that hides what it doesn't know yet. The Studio canvas now mirrors the firmware pixel-by-pixel for everything we ship as defaults. Big internal cleanups too: `warningLevel` and `dangerLevel` are now a single threshold, the demo dashboard ships with the sensor palette wired up, and a Rust spike landed for `ota_hmac` (phase 3 — staticlib linked into PlatformIO without growing flash). ### Highlights - **Semantic per-sensor palette.** Each metric draws in its native colour (coolant blue, oil pressure green, boost violet, AFR magenta, RPM cyan, throttle orange, IAT light blue, …) below the threshold, warning red above. Two zones only — the old three-zone tinting is gone. - **Units everywhere, by default.** Every gauge / bar / numeric widget shows its unit (`°C`, `km/h`, `bar`, `kPa`, `V`, `AFR`, `%`) without per-widget configuration. Pulled from the bound signal's `unit` field in `signals.json`; `cfg.suffix` still wins when set manually. - **Smaller fractional digits** on AFR / voltage / lambda / pressure readouts — the integer part stays headline-size, the `.X` / `.XX` renders at ~70 %. Arc gauges + numeric widgets, firmware and Studio match. - **Diag drawer rebuild.** Full-screen panel, swipe-up to open from anywhere, big visible **X** to close (no longer shares coordinates with the top bar's day/night toggle), scrollable content, ECU flag dots now visibly circular instead of squarish. - **Top bar de-clutter.** Signal slots with no live reading hide themselves instead of showing a misleading `--.-` placeholder. Mode-flag badges (ALS, LC, FS, TC, MAP) gained a hidden-on-startup state so they only appear once the ECU asserts them. - **Theme toggle survives page rebuilds.** The day / night icon used to vanish into a fragmented heap after a few toggles; LVGL image cache doubled to 24 entries, theme icons get re-warmed before every rebuild, the post-rebuild FS-open guard relaxed (with graceful fallback if the pool is still starved). - **Error bar — visible X + no-reboot dismiss.** Hit target widened, glyph swapped to uppercase, dismiss path guarded against the low-heap LVGL allocations that previously triggered the assert handler on press. ### Firmware - Demo dashboard migrated: 4 pages (overview / engine / fluids / controls), every gauge opts into the sensor palette via `iconName`, controls page slimmed to 4 buttons (MAP1, MAP2, Launch, Anti-lag) with strong on/off contrast. - Numeric widget value re-centered (was anchored at the bottom of the cell post flex-row refactor). - Engine page bottom row split: TPS numeric (160×56) + Battery numeric (160×56) instead of a single full-width horizontal bar. - Overview ↔ engine: Gear moved to engine (xl), IAT moved to overview (l). - `--` placeholder replaces em-dash (no more `U+2014` glyph spam in the serial log). - Lambda unit renamed from `λ` to `AFR` (Orbitron doesn't carry the Greek small letter lambda). - Per-row dismiss on the error drawer with stable newest-first row→index mapping. - Optimistic SignalStore write on toggle-button click — the diag-drawer flag turns red on tap even without a CAN echo (then reconciles when the ECU answers). - Top bar mode-flag badges (ALS / LC / FS / TC / MAP slot) — visible only while the bound signal is high; adjacent separators auto-hide so the bar stays tight. - Orbitron 28 / 48 px fonts restored. - BLE write size capped before JSON parse (prevents the UI thread from chewing oversize payloads). - USB JSON snprintf paths detect truncation and surface a warn line. - BLE GATT writes detect serializeJson truncation. - PUT_FILE path validation flipped from blacklist to strict allowlist. - Concurrent lazy page-build requests now coalesce instead of double-allocating. - TWAI driver tick guarded when uninstalled; init retries on heap recovery. - Boot-time log line `DIAG_DRAWER init done — z-reaffirm + scroll + 12px badges + frac digits build` so you can confirm at a glance which firmware is on the chip. ### Studio - Canvas preview keeps per-widget colours instead of collapsing them to the page palette — finally matches the rail thumbnails. - Default unit overlay on numeric widgets (small grey, right of the value, baseline-aligned) and on arc gauges (centred below). Falls back to the built-in MaxxECU catalog when the local signal store is empty, so units appear on first launch without picking a profile. - Smaller fractional digits in the Canvas preview, matching the firmware. - Signal binding in the property panel is now a Radix Select dropdown listing every loaded signal as `signal_name — unit`. Picking a signal auto-applies suffix / min / max / dangerLevel from the catalog. - Horizontal bar gauge orientation removed from the bar-gauge picker (the layout didn't read well; only vertical remains). - `defaultSimConfig.ts` mirrors the new firmware demo (palette + controls page + Gear/IAT swap + TPS+Battery split). - Config validation on File → Open and session restore paths. - CAN-frame flush interval cleared on shutdown. - IPC: every USB connection transition now published to the renderer (no more stale connected indicators). - Detached CLI window registers `setWindowOpenHandler` so external links don't open silently. - `mainWindow` ref nulled out in the `closed` handler — fixes a latent dangling-pointer warning. ### Core (`@tmbk/canshift-core`) - `SensorPalette` schema + per-sensor OK / warning colour table (shared by firmware + Studio + mobile). - Threshold collapse: `warningLevel` dropped, `dangerLevel` is now the sole numeric threshold across gauge / bar configs. Migration runner upgrades existing dashboards. - New 1.15 → 1.16 → 1.17 migration steps; `CURRENT_SCHEMA_VERSION` bumped accordingly. - Schema fixture test confirms the bundled `dashboard.json` validates clean against the catalog. - Signal protocol id renamed `maxxecu_v1.2` → generic `custom_v1.0` so the open-source repo stops mentioning a specific brand by default. - Export the `ButtonAction` discriminant tuple + type guards for downstream consumers. - `LIGHT_TOKENS` made internal until the light-theme consumer lands. ### Mobile - `DashTopBar` sim / BLE mode now reactive via the Zustand store (no more stale toggle on app resume). ### Rust spike (firmware) - `ota_hmac` ported to Rust in three phases: host parity tests → C ABI bridge → staticlib linked into PlatformIO. - Δ flash −16 B once weak `memcpy` / `memmove` / `memset` / `memcmp` / `bcmp` symbols in the Rust library are localized so ESP-IDF's strong IRAM versions win at link time (fixes a boot-time `EXCCAUSE 7` we hit while validating). - Behind the `USE_RUST_OTA_HMAC` build flag — the C++ implementation stays the default until the on-device 1 h soak passes. ### CI / chores - Sanitizers, clang-tidy, and the firmware boot-smoke (QEMU) checks gating every PR. - `--depth=1` dropped from the base-ref refetch so merge bases stay reachable on long-lived branches. - Documentation pass to make the repo ECU-agnostic — MaxxECU / VR6 specifics moved into examples, FIRST_FLASH translated to English. ### Known limitations - Heap fragmentation after several rapid day/night toggles can still trip the LVGL FS-open guard on icon decodes. Mitigations are in place (preload before and after rebuild, cache size doubled, heap floor relaxed) but the deeper SPIFFS-driver fix is tracked in **#895**. - A USB task watchdog reboot path was observed once after touch calibration on a heap-starved boot. Tracked in **#976**. - Disconnecting the device in Studio immediately auto-reconnects within 2 s — there's no manual-disconnect flag yet. Tracked in **#977**. - The Studio Canvas day / night toggle preview drifts from the firmware render in a few places. Tracked in **#957**. ### Upgrade notes - **Flash both binary AND SPIFFS** for the new dashboard / signals to take effect: ``` pio run -e crowpanel_28 -t upload pio run -e crowpanel_28 -t uploadfs ``` - After flash, the serial log should show `DIAG_DRAWER init done — z-reaffirm + scroll + 12px badges + frac digits build` at boot. Grep for it to confirm the new firmware is on the device. - Existing dashboards are auto-migrated to the 1.17 schema on first load; `warningLevel` is dropped and `dangerLevel` becomes the single threshold. Re-save in Studio to persist.
-
0.9.0 pre-release
Published May 14, 2026
No description for this release.
-
0.8.3 pre-release
Published May 10, 2026
Details
## Hotfix — boot loop on real hardware **v0.8.1 and v0.8.2 boot-loop on the Elecrow CrowPanel 2.8".** v0.8.3 fixes it. **Skip v0.8.2 entirely** — it carries the same bug and is published as a draft. ### Symptom On real hardware, the device kept restarting during dashboard init with: LVGL assert tripped (likely OOM in lv_mem_alloc) — restarting ### Root cause The 6 SPIFFS-loaded Orbitron font sizes consume ~53 KB of the 64 KB LVGL memory pool, leaving ~11 KB free — not enough to instantiate the default dashboard widgets. The pool exhausts, `LV_ASSERT_MALLOC` fires, the handler restarts, and the cycle repeats. ### Fix `LV_MEM_SIZE` raised **64 KB → 80 KB**, sized between two known-bad points: | Pool size | Outcome | |-----------|---------| | 64 KB | OOM after `lv_init()` during widget creation (this bug). | | 80 KB | ✅ ~30 KB contig headroom at boot, ~27 KB pool free after fonts load. | | 96 KB | OOM **inside** `lv_init()` — not enough contig headroom for display low-level nodes + draw buffers. | Build footprint: Flash **67.8%** (1.07 MB), RAM **32.6%** on `crowpanel_28`. ### Studio / mobile No changes. Existing configs are fully compatible — no migration, no re-pair required. ### What to do - **Already on v0.8.0 or earlier:** flash v0.8.3, you skipped the regression entirely. - **Tried v0.8.1 / v0.8.2 and got a boot loop:** flash v0.8.3, the loop will stop on the next boot. - **On v0.8.2 draft:** delete it from your machine, flash v0.8.3. ### Assets - `canshift-firmware-v0.8.3-crowpanel_28-merged.bin` — full factory image (esptool / studio flasher). - `canshift-spiffs-v0.8.3-crowpanel_28.bin` — fonts + config partition (only needed if reflashing SPIFFS standalone). - `CS-Studio-0.8.3-arm64.dmg` / `CS-Studio-0.8.3.dmg` — macOS installers. - `CS-Studio-Setup-0.8.3.exe` — Windows installer. - `CS-Studio-0.8.3.AppImage` — Linux build. PR: #555.
-
0.7.1 pre-release
Published May 7, 2026
Details
**CANShift Studio 0.7.1** — hotfix: USB port race during firmware flash from the Update route. ## Why this patch In 0.7.0, flashing a new firmware from **Update → Flash** would silently time out partway through `writeFlash`. Flashes triggered from the FirmwareDialog (no firmware / outdated) worked, but the Update route did not. User report — log truncates partway: ``` Compressed 1351744 bytes to 705480... [long pause] [Auto-connected to /dev/tty.usbserial-XX] ← wrong moment [esptool timeout] ``` ## Root cause `useAutoConnect` short-circuited on three conditions: ```ts if (connected || simulationMode || flashDialogVisible) return ``` `flashDialogVisible` is set by `FirmwareDialog`. The **Update** route calls `useFirmwareFlash.flash()` directly without ever opening the dialog — so the predicate stayed false and the 2-second poll kept calling `usbService.connect()`. Once the renderer-side Web Serial port was open for `esptool-js`, the Node-side connect attempt collided with it, the port closed, and the next `writeFlash` chunk had nowhere to land. ## Fix New `flashing: boolean` slice on the device store. `useFirmwareFlash.flash()` sets it `true` immediately on entry and clears it in every exit path (success, mid-flash error, `requestPort` failure, simulation). `useAutoConnect` now also short-circuits on this flag, so its 2-second poll cannot grab the serial port back from `esptool-js` mid-flash regardless of which entry point started the flash. ## Test plan covered before release - Lint / format / typecheck / vitest (49 tests) — all green - Manual: real flash from Update → no `Auto-connected` log during `writeFlash` → device reboots cleanly - Manual: real flash from FirmwareDialog → still works (dialog visibility + flashing both true) - Manual: simulation flash → flashing flag toggles, no auto-connect interference ## Note on hardware testing If a 0.7.0 → 0.7.1 upgrade still times out for you, the device may be in a corrupt state from a previous 0.7.0 flash that aborted partway. Symptoms include: - LCD top bar USB icon stays gray when the studio is connected (heartbeat not reaching the firmware) - SD config never loads on connect Recovery: flash the firmware once with the manual `esptool` CLI (or via the Update route now that the race is gone), then power-cycle. Subsequent burns from Studio should be smooth. ## Full PR list since 0.7.0 - #187 — `feat(firmware): LCD overlay during CMD_PUT_CONFIG` *(merged after 0.7.0 cut, ships in 0.7.1)* - #192 — `fix(studio): pause auto-connect during firmware flash` Plus the 6 follow-up issues filed after the v0.7.0 dust settled (#188 hot-reload Burn, #189 LCD feedback on SD write failure, #190 first-run onboarding, #191 optional bespoke per-widget colours, #193 bundle visualizer + size budget, #194 firmware native tests).
-
0.7.0 pre-release
Published May 7, 2026
Details
**CANShift Studio 0.7.0** — burn UX, day/night legibility, and a tighter studio. ## Highlights ### Burn progress modal (Option C — part 1 of 2) Clicking **Burn** now opens a full-window status modal that follows the device through the whole write cycle: - **Pushing config to device…** — sending the JSON over USB - **Device writing & rebooting…** — firmware acked, SD write + reboot in flight - **Done — your config is live** — auto-connect re-established the link A 20-second safety timeout returns the modal to idle if the device never comes back, so it can never get stuck. The companion **firmware-side LCD overlay** ships in the next firmware release; until then you'll see the studio modal alone. ### Day / night text legibility Day mode renders dashboards on a light grey background. Until now, widget value labels read `cfg.style.textColor.rgb` directly — usually white — making them invisible on the day backdrop. The fix lives in firmware (`ThemeManager::getEffectiveTextColor()`) with the studio preview mirrored 1:1: - **Day mode** → black text on grey - **Night mode** → white text on black - **Top bar untouched** in both modes Bespoke per-widget text colours (cyan COOLANT, orange OIL, red GEAR) intentionally collapse to the mode's text colour for legibility — the trade-off was explicitly accepted in #171. > ⚠️ The full benefit lands once you flash the matching firmware build (also v0.7.0 in this release). Without it, the studio preview stays accurate but the device keeps the old white-on-grey behaviour. ### Import / Export dashboard JSON Two new entries under the **File** menu, distinct from Open / Save: - **Import Dashboard…** — load a foreign JSON without binding it as the working file. Studio runs the schema migration, validates, and refuses on errors. The editor opens with a dirty flag so a later Save prompts for a fresh location. - **Export Dashboard…** — write a snapshot of the current config to a chosen path **without** changing the working file path or the recent-files list. Use this for backups and sharing where you don't want the regular Open/Save flow to retarget your active file. ### Status feedback improvements - **USB connection** — the colored dot in the TopBar is now a proper USB icon that turns green when connected, orange while burning, red on error, gray when disconnected, with a soft drop-shadow glow on the active states. - **Device config load** — connecting to a device with no SD config, an invalid config, or a parse error no longer fails silently. A success log shows up when the load worked, an info log when the device just had nothing to send, and a red banner with full validator details when the on-device config is broken. ## Internals - First main-process test infrastructure under `canshift-studio/main/`. Locks the USB disconnect bookkeeping introduced in v0.6.1 (intentional vs. involuntary close) so the contract can't silently regress. - Layout collision coverage taken from 90 % to 100 %. Push-left / push-up directions and cascade termination are now pinned. - Schema-aware JSON import path (Import Dashboard…) reuses the same migration the Open path runs. ## Migrations Schema is unchanged from 0.6.1 — still **1.10.0**. No migration runs on existing dashboards. ## Verification on real hardware When testing the burn flow end-to-end: 1. Install this studio build, connect a CANShift device. 2. Trigger Burn — the new modal should walk through *Pushing → Rebooting → Done*. 3. Once the firmware-side overlay ships in the next firmware release, you'll also see a "Saving config…" overlay on the LCD itself. If the modal hangs at "Device writing & rebooting…" longer than 20 s, it'll auto-clear and the console panel + error bar will show why. ## Full PR list since 0.6.1 - #178 — `feat(studio): import/export dashboard JSON outside the device flow` - #181 — `test(studio): regression coverage for USB disconnect bookkeeping` - #182 — `test(studio): cover layout collision push-left/up + cascade termination` - #183 — `feat(studio): USB icon reflects connection state in TopBar` - #184 — `feat(studio): surface device config load failures in the UI` - #185 — `feat(firmware): widget text color follows active mode (black/day, white/night)` - #186 — `feat(studio): burn progress modal — pushing/rebooting/done phases (v0.7.0)`
If the list above is empty in local dev, that’s expected — it populates at build time via the GitHub Releases API. To preview in dev, set
GITHUB_TOKENin your environment.