Skip to content

fix: use manifest-driven flashing in event mode - #400

Merged
thebentern merged 1 commit into
mainfrom
fix/event-mode-manifest-flash
Aug 8, 2026
Merged

fix: use manifest-driven flashing in event mode#400
thebentern merged 1 commit into
mainfrom
fix/event-mode-manifest-flash

Conversation

@thebentern

@thebentern thebentern commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Problem

Event domains (defcon.meshtastic.org et al.) were flashing devices into a boot loop. From a DEF CON flash log on a T-LoRa T3-S3 e-paper:

Wrote 16 bytes (24 compressed) at 0x260000     <- the string "404: Not Found"
Wrote 786432 bytes at 0x300000                 <- littlefs, wrong offset
Hard resetting via RTS pin...
Guru Meditation Error: Core 1 panic'ed (IntegerDivideByZero)

The build's actual partition table (firmware-tlora-t3s3-epaper-2.8.0.c800fc8.mt.json) puts flashApp at 0x2A0000 and spiffs at 0x340000, so the filesystem image straddled the two — the spiffs partition ended up holding the middle of a littlefs image rather than a valid one.

Three defects chained:

  1. Event mode never loaded the release manifest. setSelectedFirmware() is the only place releaseManifest is fetched, and event mode pre-seeds selectedFirmware straight into store state instead of calling it. No release manifest means loadTargetManifest() never runs, so every event flash fell through to the legacy convention-based path. Regular releases on flash.meshtastic.org always resolve a manifest, which is why this only showed up on an event domain.
  2. fetchBinaryContent() never checked response.ok. The legacy path asks for bleota-s3.bin, which exists in no currently-published build (renamed mt-esp32s3-ota.bin — verified 404 across 2.7.23, 2.7.24, 2.7.26, nightly and the DEF CON build). The 404 body was converted to a binary string and flashed as the OTA payload.
  3. isZipFile / isFactoryBin threw before a file was picked. selectedFile defaulted to {}, so .name.endsWith() raised TypeError: Cannot read properties of undefined. It fired from the shouldCleanInstall watcher in components/targets/Esp32.vue, killing the check that decides whether "Bundle WebUI" is offered.

Changes

  • fetchList() resolves the locked event firmware through setSelectedFirmware() so event domains take the same manifest-driven path as flash.meshtastic.org.
  • fetchBinaryContent() throws on a non-OK response instead of flashing the error body.
  • selectedFile defaults to undefined; both getters are name-safe.

Verification

Local dev server at ?event=DEFCON (same bundled manifest production resolves): the store loads the 140-target DEF CON release manifest, both getters return false instead of throwing, and loadTargetManifest('tlora-t3s3-epaper') resolves the correct flash plan.

file offset before
firmware-tlora-t3s3-epaper-2.8.0.c800fc8.factory.bin 0x0 0x0
mt-esp32s3-ota.bin 0x2A0000 404 body at 0x260000
littlefs-tlora-t3s3-epaper-2.8.0.c800fc8.bin 0x340000 0x300000

New stores/firmwareStore.eventMode.test.ts covers event-mode manifest resolution, the getters, and the failed-download guard. Full suite: 164 passing.

Once deployed, "Full erase and install" recovers devices already flashed with the bad offsets.

Not covered here

  • api.meshtastic.org returns HTTP 500 with no Access-Control-Allow-Origin for Origin: https://defcon.meshtastic.org, while https://flash.meshtastic.org gets a 204 with the header. Event subdomains need adding to the API's CORS allowlist — no client-side fix. Cosmetic today since deviceStore.fetchList() falls back to the bundled hardware-list.json.
  • The legacy path still references bleota*.bin. Those files exist in no published build, so it can now only fail loudly rather than corrupt a partition. Left alone since that path exists to serve older releases, but it is worth revisiting.

Summary by CodeRabbit

  • Bug Fixes

    • Event mode now loads the correct locked firmware release information.
    • Failed firmware downloads are rejected instead of being processed as valid files.
    • Firmware file details now remain stable when no file has been selected.
  • Tests

    • Added coverage for event-mode firmware resolution and caching.
    • Added tests for missing firmware, file selection states, and failed downloads.

Event mode pre-seeds the locked build into firmware store state instead of
going through setSelectedFirmware(), which is the only place the release
manifest is fetched. Without it loadTargetManifest() never runs, so every
event flash fell through to the legacy convention-based path.

For the DEF CON 2.8.0.c800fc8 build on a T3-S3 e-paper that meant:

  - bleota-s3.bin at 0x260000 -- that file no longer exists in any published
    build (renamed mt-esp32s3-ota.bin), and fetchBinaryContent never checked
    response.ok, so the 16-byte "404: Not Found" body was flashed as the OTA
    payload
  - littlefs at 0x300000 instead of the build's spiffs offset of 0x340000,
    leaving the filesystem partition holding the middle of a littlefs image

Devices came up in an IntegerDivideByZero boot loop.

  - resolve the locked event firmware through setSelectedFirmware() so event
    domains take the same manifest-driven path as flash.meshtastic.org
  - throw on a failed download instead of flashing the error body
  - default selectedFile to undefined and make isZipFile/isFactoryBin
    name-safe; the {} default threw from the shouldCleanInstall watcher in
    Esp32.vue, which is what gates the Bundle WebUI option
@vercel

vercel Bot commented Aug 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
web-flasher Ready Ready Preview Aug 8, 2026 5:33pm

Request Review

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The firmware store now loads missing event-mode release manifests, safely handles an unselected file, and rejects failed firmware downloads. Tests cover manifest resolution, request deduplication, unavailable event firmware, file classification, and HTTP failures.

Changes

Firmware store behavior

Layer / File(s) Summary
Event-mode manifest resolution
stores/firmwareStore.ts, stores/firmwareStore.eventMode.test.ts
Event mode loads the locked firmware release manifest when it is missing. Tests cover deduplication and unavailable event firmware.
Safe file selection state
stores/firmwareStore.ts, stores/firmwareStore.eventMode.test.ts
selectedFile starts as undefined. File-type getters return false without a selection and classify ZIP and factory BIN files after selection.
Firmware download validation
stores/firmwareStore.ts, stores/firmwareStore.eventMode.test.ts
Downloads reject non-2xx responses before processing response bodies as firmware data. Tests verify the rejection error.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Poem

A rabbit checks the firmware trail,
Finds locked releases in the tale.
Empty paws return false with care,
Bad downloads stop in mid-air.
Tests hop after every change.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: using manifest-driven flashing in event mode.
Description check ✅ Passed The description clearly documents the problem, root causes, changes, verification steps, test results, and remaining limitations.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

stores/firmwareStore.eventMode.test.ts

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.

stores/firmwareStore.ts

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@thebentern
thebentern merged commit 8ba8304 into main Aug 8, 2026
4 of 5 checks passed
@thebentern
thebentern deleted the fix/event-mode-manifest-flash branch August 8, 2026 17:37

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@stores/firmwareStore.ts`:
- Around line 1055-1056: Update the download error in the surrounding firmware
download method to use the existing i18n translation mechanism instead of
hardcoded text. Add a translation key with placeholders for fileName and HTTP
status, pass the URL only as a diagnostic or translation placeholder as
appropriate, and ensure handleError still receives the translated user-visible
message.
- Around line 203-214: The event-mode manifest initialization around
setSelectedFirmware() must deduplicate concurrent loads. Cache and reuse the
in-flight manifest promise keyed by eventMode.firmware.id, ensuring simultaneous
callers share one request and cleanup occurs after completion; preserve the
existing selected firmware and manifest state updates.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b7581012-e2e4-4163-b978-51b8a88d718c

📥 Commits

Reviewing files that changed from the base of the PR and between 0b48029 and 29e64db.

📒 Files selected for processing (2)
  • stores/firmwareStore.eventMode.test.ts
  • stores/firmwareStore.ts

Comment thread stores/firmwareStore.ts
Comment on lines +203 to +214
// The locked build is pre-seeded into state, so setSelectedFirmware()
// never runs for it — and that is the only place the release manifest is
// fetched. Without it every event flash falls through to the legacy
// convention-based path, which uses stale partition offsets and asks for
// bleota*.bin (gone since 2.8). Resolve it here so event domains take the
// same manifest-driven path as flash.meshtastic.org.
if (eventMode.firmware?.id && !this.releaseManifest) {
await this.setSelectedFirmware(eventMode.firmware)
}
return
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 4 '\bfetchList\s*\(' --glob '*.{ts,vue}' .

Repository: meshtastic/web-flasher

Length of output: 4388


🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd -a 'firmwareStore(eventMode)\.test\.ts|firmwareStore\.ts|Firmware\.vue|EventMode' . | sed 's#^\./##'

printf '\n--- firmwareStore eventMode test snippet ---\n'
sed -n '1,140p' stores/firmwareStore.eventMode.test.ts

printf '\n--- firmwareStore setSelected/manifest related sections ---\n'
sed -n '140,260p' stores/firmwareStore.ts

Repository: meshtastic/web-flasher

Length of output: 10371


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- setSelectedFirmware definition ---'
rg -n -C 12 'setSelectedFirmware' stores/firmwareStore.ts

printf '%s\n' '--- setFirmwareFile and file-claim actions ---'
rg -n -C 8 'setFirmwareFile|selectedFile|hasManifest|manifest:' stores/firmwareStore.ts

printf '%s\n' '--- event mode setup in components/config ---'
rg -n -C 4 'setActiveEventMode|enableDefconEventMode|isEvent|eventMode' --glob '*.{ts,vue}' .

Repository: meshtastic/web-flasher

Length of output: 49826


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- setSelectedFirmware manifest-fetch implementation ---'
sed -n '412,460p' stores/firmwareStore.ts

printf '%s\n' '--- app.vue relevant setup section ---'
sed -n '250,470p' app.vue

printf '%s\n' '--- all fetchList calls with surrounding context ---'
sed -n '216,226p' components/Firmware.vue
sed -n '224,232p' components/Device.vue

Repository: meshtastic/web-flasher

Length of output: 10310


Deduplicate concurrent event-mode manifest loads.

Firmware.vue and Device.vue can call fetchList() before the event firmware manifest finishes loading. The releaseManifest race check plus setSelectedFirmware() clearing selectedFile, manifest, and releaseManifest allows duplicate manifest requests in event mode. Cache the in-flight load, keyed by the locked firmware ID, or add a concurrent-call test for the current behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@stores/firmwareStore.ts` around lines 203 - 214, The event-mode manifest
initialization around setSelectedFirmware() must deduplicate concurrent loads.
Cache and reuse the in-flight manifest promise keyed by eventMode.firmware.id,
ensuring simultaneous callers share one request and cleanup occurs after
completion; preserve the existing selected firmware and manifest state updates.

Comment thread stores/firmwareStore.ts
Comment on lines +1055 to +1056
throw new Error(`Could not download ${fileName} (HTTP ${response.status} from ${url})`)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Route the new download error through i18n.

The new Error text at Line 1055 is written to the terminal by handleError at Lines 524-527. It is user-visible. Replace it with a translation key and placeholders for the file name and HTTP status. Keep the URL in diagnostics or pass it as a translation placeholder.

As per coding guidelines, all user-visible text must go through useI18n / $t('key'); do not hardcode strings in templates or scripts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@stores/firmwareStore.ts` around lines 1055 - 1056, Update the download error
in the surrounding firmware download method to use the existing i18n translation
mechanism instead of hardcoded text. Add a translation key with placeholders for
fileName and HTTP status, pass the URL only as a diagnostic or translation
placeholder as appropriate, and ensure handleError still receives the translated
user-visible message.

Source: Coding guidelines

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant