`
+ );
+}
+
+// Convenience: fetch latest releases, compute suggestions for the given
+// device version + board, and (if any) render them into `containerElement`.
+// Failures are non-fatal -- nothing is rendered if the API call fails or the
+// version string can't be parsed.
+async function renderFirmwareSuggestions(containerElement, deviceInfo) {
+ if (!containerElement || !deviceInfo) return;
+ try {
+ const latest = await fetchLatestReleases();
+ const {suggestions} = buildSuggestions(deviceInfo.version, latest);
+ const html = renderSuggestionsHtml(suggestions, deviceInfo.board_id);
+ containerElement.innerHTML = html;
+ } catch (err) {
+ console.warn("Firmware check failed", err);
+ containerElement.innerHTML = "";
+ }
+}
+
+export {
+ parseVersion,
+ compareVersions,
+ fetchLatestReleases,
+ buildSuggestions,
+ renderSuggestionsHtml,
+ renderFirmwareSuggestions,
+};
diff --git a/js/script.js b/js/script.js
index 7a2d501..19f33e0 100644
--- a/js/script.js
+++ b/js/script.js
@@ -235,10 +235,9 @@ async function checkConnected() {
if (!workflow.connectionStatus()) {
// Display the appropriate connection dialog
await workflow.showConnect(getDocState());
- } else if (workflow.type === CONNTYPE.Web) {
- // We're connected, local, and using Web Workflow
- await workflow.showInfo(getDocState());
}
+ // Note: the Device Info dialog is now opened from loadEditor() so that
+ // BLE/USB/Web all behave the same way after a fresh connect.
}
return true;
@@ -466,6 +465,12 @@ window.onbeforeunload = () => {
}
};
+// Tracks whether we've already shown the post-connect Device Info dialog
+// for the current workflow. Reset to false in disconnectCallback() so that
+// a fresh connect always re-shows it, while silent reconnects (which also
+// run loadEditor) do not.
+let shownDeviceInfoForCurrentSession = false;
+
async function loadEditor() {
let documentState = loadParameterizedContent();
if (documentState) {
@@ -475,6 +480,24 @@ async function loadEditor() {
}
updateUIConnected(true);
+
+ // Show the Device Info dialog once per fresh connect, regardless of
+ // workflow (Web / USB / BLE). This is where the firmware-update
+ // suggestion (issue #357) is surfaced, so the user notices it just
+ // after connecting without us introducing a new dialog.
+ //
+ // Fire-and-forget: we don't await the dialog because it stays open until
+ // the user dismisses it, and we don't want to block the rest of the
+ // post-connect flow (busy spinner, parameterized doc loading, etc.).
+ if (!shownDeviceInfoForCurrentSession
+ && workflow
+ && workflow.showInfo
+ && workflow.connectionStatus && workflow.connectionStatus()) {
+ shownDeviceInfoForCurrentSession = true;
+ Promise.resolve()
+ .then(() => workflow.showInfo(getDocState()))
+ .catch((err) => console.warn("Could not show device info dialog", err));
+ }
}
var editor;
@@ -567,6 +590,7 @@ function disconnectCallback() {
currentTimeout = null;
}
saveRetryCount = 0;
+ shownDeviceInfoForCurrentSession = false;
updateUIConnected(false);
}
@@ -682,10 +706,10 @@ document.addEventListener('DOMContentLoaded', async (event) => {
// If we don't have all the info we need to connect
let returnVal = await workflow.parseParams();
if (returnVal === true && await workflowConnect() && workflow.type === CONNTYPE.Web) {
- if (await checkReadOnly()) {
- // We're connected, local, no errors, and using Web Workflow
- await workflow.showInfo(getDocState());
- }
+ // We're connected, local, no errors, and using Web Workflow.
+ // The Device Info dialog is opened from loadEditor() now, so we
+ // just need to verify read-only state here.
+ await checkReadOnly();
} else {
if (returnVal instanceof Error) {
await showMessage(returnVal);
diff --git a/js/workflows/usb.js b/js/workflows/usb.js
index 26532cf..6298ec9 100644
--- a/js/workflows/usb.js
+++ b/js/workflows/usb.js
@@ -45,10 +45,13 @@ class USBWorkflow extends Workflow {
}
async onConnected(e) {
- this.connectDialog.close();
- await this.loadEditor();
+ // super.onConnected sets _connected=CONNSTATE.connected and closes
+ // the connect dialog. Run it first so that loadEditor() (and any
+ // other code that gates on connectionStatus()) sees us as fully
+ // connected.
+ await super.onConnected(e);
this.debugLog("connected");
- super.onConnected(e);
+ await this.loadEditor();
}
async onDisconnected(e, reconnect = true) {
diff --git a/sass/layout/_layout.scss b/sass/layout/_layout.scss
index b9f808e..cd4f108 100644
--- a/sass/layout/_layout.scss
+++ b/sass/layout/_layout.scss
@@ -434,6 +434,43 @@
&[data-popup-modal="device-discovery"],
&[data-popup-modal="device-info"] {
+ .firmware-update-suggestion-container {
+ // Filled in by firmware-check.js when a newer firmware is found.
+ // Empty by default so the dialog layout doesn't shift when the
+ // GitHub releases API hasn't responded yet (or fails).
+ &:empty {
+ display: none;
+ }
+ }
+
+ .firmware-update-suggestion {
+ margin: 10px 0 5px;
+ padding: 10px 12px;
+ border: 1px solid $light-purple;
+ border-radius: 5px;
+ background-color: #faf6ff;
+ font-size: 0.95rem;
+
+ i {
+ color: $purple;
+ margin-right: 6px;
+ }
+
+ .firmware-update-suggestion__title {
+ font-weight: bold;
+ }
+
+ ul {
+ margin: 6px 0;
+ padding-left: 24px;
+ }
+
+ a {
+ color: $purple;
+ font-weight: bold;
+ }
+ }
+
.device-info {
margin-top: 5px;
width: 100%;