diff --git a/widgets/.gitignore b/widgets/.gitignore
new file mode 100644
index 0000000..3f9851d
--- /dev/null
+++ b/widgets/.gitignore
@@ -0,0 +1,6 @@
+# Manual backup copies — never commit these
+*[Cc]opy*.json
+* - Copy.json
+
+# Working drafts — superseded by dated final versions
+w0_working.json
diff --git a/widgets/README.md b/widgets/README.md
index 128a815..868aae9 100644
--- a/widgets/README.md
+++ b/widgets/README.md
@@ -2,56 +2,85 @@
## Requirements
-- **Cockpit native desktop app v1.17.0** (Windows `.exe` installer)
-- The BlueOS browser extension version does NOT support DIY widgets
-- Download from: https://github.com/bluerobotics/cockpit/releases/tag/v1.17.0
+- **Cockpit native desktop app v1.18.2** (or compatible 1.18.x)
+- The BlueOS browser extension ("Lite") does NOT support DIY widgets — the native desktop app is mandatory.
+- Cockpit must connect directly to the RPi4/BlueOS IP over plain HTTP (not through an HTTPS reverse proxy).
+- BlueOS Pirate Mode must be enabled each session (Settings → General).
+
+## Network standard
+
+Use direct IP addresses everywhere. Do NOT use `blueos.local` — mDNS/Avahi resolution is unreliable across subnets and over Tailscale.
+
+| Host | IP |
+|---|---|
+| RPi4 (BlueOS gateway) | `192.168.1.100` |
+| RPi5 (autonomy brain) | `192.168.1.101` |
## How to install a widget
-1. Open the Cockpit native desktop app
-2. Connect to the vehicle / VM (`100.84.141.120` for dev VM)
-3. Enter edit mode (pencil icon, top right)
-4. Click **Add Widget** at the bottom of the screen
-5. Scroll right to find the `>` DIY widget — drag it onto the view
-6. Click the **gear icon** on the widget to open the editor
-7. Click **Import** and select the `.json` file for the widget you want
-8. The widget will load immediately
+1. Open the Cockpit native desktop app.
+2. Connect to the vehicle at `192.168.1.100` (RPi4/BlueOS) over HTTP.
+3. Enter edit mode (pencil icon, top right).
+4. Click **Add Widget** at the bottom of the screen.
+5. Scroll right to find the `>` DIY widget — drag it onto the view.
+6. Click the **gear icon** on the widget to open the editor.
+7. Click **Import** and select the `.json` file for the widget.
+8. The widget loads immediately.
-> **Note:** Widget files are `.json` format (not `.html`).
-> Cockpit expects a JSON file with `html`, `css`, and `js` fields.
+> Widget files are `.json` (not `.html`). Cockpit expects a JSON file with `html`, `css`, and `js` string fields.
+
+## Data lake API (Cockpit v1.18.2 DIY widget scope)
+
+- Read a single live value: `window.cockpit.getDataLakeVariableData(variableId)` — returns the live value. **This is the getter to use.**
+- `getAllDataLakeVariablesInfo()` returns metadata only (id/name/type/description), not values.
+- `getDataLakeValue()` does NOT exist — do not use.
+- `listenToDatalakeVariable()` does NOT exist in DIY widget scope (it is the postMessage iframe External API only).
+
+## Live data lake variables (published by cockpit_bridge)
+
+External WebSocket variables carry the `external/` prefix.
+
+| Variable | Source topic | Notes |
+|---|---|---|
+| `external/rov-failsafe` | /rov/failsafe | Failsafe state: 0=GREEN 1=AMBER 2=RED |
+| `external/rov-ms` | /rov/mission/status | Mission state: 0=IDLE 1=RUNNING 2=PAUSED 3=COMPLETE 4=ABORTED |
+| `external/rov-mp` | /rov/mission/status | Mission progress 0.0–1.0 |
+| `external/rov-depth` | /rov/depth | Depth (m) |
+| `external/rov-heading` | /mavros compass_hdg | Heading (deg) |
+| `external/rov-voltage` | /mavros/battery | Battery voltage |
## Widget index
-| File | Widget | Notes |
+| File | Widget | Status | Reads |
+|---|---|---|---|
+| w0_data_lake_inspector.json | Data Lake Inspector | Diagnostic. Flashing/scroll issue OUTSTANDING. | All data lake variables |
+| w1_system_health_indicator.json | System Health Indicator | CONFIRMED working | `external/rov-failsafe` |
+| w2_mission_status.json | Mission Status | CONFIRMED working | `external/rov-ms`, `external/rov-mp` |
+| w3_abort_button.json | Abort Button | UI CONFIRMED. Backend pending. | POST → FastAPI `/abort` |
+| w4_mission_setup_button.json | Mission Setup Button | UI CONFIRMED. Backend pending. | `external/rov-ms`; opens `/setup` |
+| w5_battery_return_budget.json | Return Budget | UI CONFIRMED. Live budget pending. | `SYS_STATUS/battery_remaining` + `external/rov-return-budget` |
+
+Diagnostic widget (not production):
+
+| File | Widget | Purpose |
|---|---|---|
-| w0_data_lake_inspector.json | Data Lake Inspector | Diagnostic — shows all data lake variables. Not production. |
-| w1_system_health_indicator.json | System Health Indicator | Traffic light GREEN/AMBER/RED. Reads `rov_failsafe` from data lake. |
-| w2_mission_status.json | Mission Status | Progress bar + state. Reads `rov_mission_state` and `rov_mission_progress`. |
-| w3_abort_button.json | Abort Button | Confirm dialog → POST /abort to FastAPI. Edit `FASTAPI_HOST` before use. |
-| w4_mission_setup_button.json | Mission Setup Button | Opens setup page in new tab. Edit `SETUP_URL` before use. |
-| w5_battery_return_budget.json | Return Budget | Headroom warning. Reads `SYS_STATUS/battery_remaining` (native) + `rov_return_budget`. |
+| diagnostics/w_probe_api.json | API Probe | Enumerates `window.cockpit.*` methods and live-reads one variable. Used to confirm the data lake getter. Keep for future debugging. |
-## Config values to edit before field use
+## Backend dependencies (not yet built)
-In `w3_abort_button.json` (line 1 of the JS section):
-```
-var FASTAPI_HOST = 'http://blueos.local:8081';
-```
+- **FastAPI service on RPi5:8081** — required by W3 (`POST /abort`) and W4 (`GET /setup`). Until built, W3's abort returns a network error (handled cleanly) and W4's setup page is blank.
+- **`external/rov-return-budget`** — not yet published by cockpit_bridge. Until then W5 shows the budget as pending and battery as "not reported" under SITL (SITL reports -1).
-In `w4_mission_setup_button.json` (line 1 of the JS section):
-```
-var SETUP_URL = 'http://blueos.local:8081/setup';
-```
+## Config values
-Both values are correct for real hardware. For the dev VM, replace `blueos.local` with the VM Tailscale IP.
+W3 and W4 target the RPi5 backend directly:
-## Widget data dependencies
+- W3 `W3_FASTAPI_HOST` = `http://192.168.1.101:8081`
+- W4 `W4_SETUP_URL` = `http://192.168.1.101:8081/setup`
-| Widget | Works without backend? | Requires |
-|---|---|---|
-| W0 Data Lake Inspector | Yes — shows live MAVLink data immediately | Vehicle connected |
-| W1 System Health | Shows "waiting" until bridge runs | NAMED_VALUE bridge node |
-| W2 Mission Status | Shows "waiting" until bridge runs | NAMED_VALUE bridge node |
-| W3 Abort Button | Button visible, POST will fail | FastAPI backend container |
-| W4 Setup Button | Button visible, opens 404 | Extension backend serving setup page |
-| W5 Return Budget | Battery% shows live, budget shows 0% | FastAPI backend for return budget calc |
+## Widget conventions (for new widgets)
+
+- All element IDs and CSS classes are prefixed per-widget (e.g. `w1-`, `w2-`) to avoid collisions in the shared Cockpit renderer.
+- All `document.getElementById` calls must be INSIDE functions, not at script load time (DOM timing).
+- Avoid: `body {}` / `:root {}` globals, `@keyframes`, `position: fixed`, Unicode in JS strings, `async/await`. Use `var`, not `let`/`const`.
+- A footer status line (debug) is present on data-reading widgets during validation; strip before final production.
diff --git a/widgets/diagnostics/w_probe_api.json b/widgets/diagnostics/w_probe_api.json
new file mode 100644
index 0000000..9a18cda
--- /dev/null
+++ b/widgets/diagnostics/w_probe_api.json
@@ -0,0 +1,7 @@
+{
+ "html": "
heading: waiting...
status: starting
",
+
+ "css": "#probe-root { font-family: monospace; font-size: 11px; color: #00ff00; background: #111; padding: 6px; overflow-y: auto; height: 100%; } #probe-methods { margin-bottom: 6px; border-bottom: 1px solid #333; padding-bottom: 6px; } #probe-heading { font-size: 14px; color: #ffff00; margin-bottom: 4px; } #probe-status { color: #aaa; font-size: 10px; }",
+
+ "js": "// ============================================================\n// Argonaut 3 -- window.cockpit API Probe Widget\n// Purpose: enumerate every method on window.cockpit, then\n// try every plausible data-lake read method for\n// 'external/rov-heading'. Run this once to confirm\n// the correct method name before building W1-W5.\n// Version: 1.0 Date: 2026-06-22\n// Source researched: github.com/bluerobotics/cockpit\n// src/libs/external-api/api.ts (commit 52fb1a2d)\n// The External API uses postMessage (iframes only).\n// DIY widgets run inline -- window.cockpit surface is\n// different and must be probed at runtime.\n// ============================================================\n\n// --- 1. ENUMERATE window.cockpit METHODS ---\n// Run immediately so the method list appears even if\n// data-lake calls fail.\n(function enumerateCockpitMethods() {\n var el = document.getElementById('probe-methods');\n var status = document.getElementById('probe-status');\n if (!el) return;\n\n // Check whether window.cockpit exists at all\n if (typeof window.cockpit === 'undefined') {\n el.innerText = 'ERROR: window.cockpit is undefined';\n if (status) status.innerText = 'status: cockpit object missing -- Pirate Mode enabled?';\n return;\n }\n\n // Collect all own + inherited enumerable keys\n var keys = [];\n for (var k in window.cockpit) {\n try {\n keys.push(k + ' [' + typeof window.cockpit[k] + ']');\n } catch (e) {\n keys.push(k + ' [access error]');\n }\n }\n\n // Also check Object.keys for non-enumerable own props\n var ownKeys = Object.keys(window.cockpit);\n var methodLine = 'window.cockpit methods (' + keys.length + '):\\n' + keys.join('\\n');\n el.innerText = methodLine;\n if (status) status.innerText = 'status: enumerated ' + keys.length + ' keys';\n})();\n\n// --- 2. PROBE DATA-LAKE READ METHODS ---\n// We try each candidate method in sequence.\n// The first one that returns a non-undefined value for\n// 'external/rov-heading' is the correct method for W1-W5.\n\n(function probeDataLake() {\n var headingEl = document.getElementById('probe-heading');\n var statusEl = document.getElementById('probe-status');\n if (!headingEl) return;\n if (typeof window.cockpit === 'undefined') return; // already flagged above\n\n // Candidate method names to probe, in priority order.\n // Based on source research:\n // - getAllDataLakeVariablesInfo() = CONFIRMED metadata-only (id/name/type)\n // - getDataLakeVariableData() = internal Cockpit function (utils-data-lake.ts)\n // - getDataLakeVariableInfo() = paired with above in same file\n // - getDataLakeValue() = mentioned in older docs, not confirmed\n // - listenToDatalakeVariable() = External API (postMessage, for iframes)\n var VARIABLE_ID = 'external/rov-heading';\n var candidates = [\n 'getDataLakeVariableData',\n 'getDataLakeValue',\n 'getDataLakeVariableInfo',\n 'listenToDatalakeVariable'\n ];\n\n // Track which methods exist vs are callable\n var results = [];\n\n // Try synchronous getters first (candidates 0-2)\n var syncCandidates = candidates.slice(0, 3);\n for (var i = 0; i < syncCandidates.length; i++) {\n var name = syncCandidates[i];\n if (typeof window.cockpit[name] !== 'function') {\n results.push(name + ': NOT a function (type=' + typeof window.cockpit[name] + ')');\n continue;\n }\n try {\n var result = window.cockpit[name](VARIABLE_ID);\n if (result !== undefined && result !== null) {\n // Found it -- display the live value\n results.push(name + ': WORKS -> ' + JSON.stringify(result));\n headingEl.innerText = 'heading (' + name + '): ' + JSON.stringify(result);\n statusEl.innerText = 'status: live read confirmed via ' + name;\n } else {\n results.push(name + ': returned undefined/null');\n }\n } catch (e) {\n results.push(name + ': threw -> ' + e.message);\n }\n }\n\n // Try callback-based listenToDatalakeVariable last\n // It registers a callback, not a sync return\n var listenName = 'listenToDatalakeVariable';\n if (typeof window.cockpit[listenName] === 'function') {\n try {\n window.cockpit[listenName](VARIABLE_ID, function(value) {\n var el = document.getElementById('probe-heading');\n if (el) el.innerText = 'heading (listenToDatalakeVariable callback): ' + JSON.stringify(value);\n var sel = document.getElementById('probe-status');\n if (sel) sel.innerText = 'status: live callback from listenToDatalakeVariable';\n });\n results.push(listenName + ': registered callback (watching for value...)');\n } catch (e) {\n results.push(listenName + ': threw -> ' + e.message);\n }\n } else {\n results.push(listenName + ': NOT a function');\n }\n\n // Append probe results under the method list\n var methodsEl = document.getElementById('probe-methods');\n if (methodsEl) {\n methodsEl.innerText += '\\n\\n--- DATA LAKE PROBE ---\\nvar=' + VARIABLE_ID + '\\n' + results.join('\\n');\n }\n})();\n\n// --- 3. LIVE POLL --- \n// If any sync getter worked, re-call it every 2s so we can\n// confirm it tracks live updates (heading should change as\n// the cockpit_bridge publishes new values).\n// We do NOT know which method works yet, so we poll all\n// sync candidates that exist and update the display.\n(function livePoll() {\n var VARIABLE_ID = 'external/rov-heading';\n var syncCandidates = ['getDataLakeVariableData', 'getDataLakeValue', 'getDataLakeVariableInfo'];\n\n // setInterval is allowed in DIY widget JS\n setInterval(function() {\n if (typeof window.cockpit === 'undefined') return;\n for (var i = 0; i < syncCandidates.length; i++) {\n var name = syncCandidates[i];\n if (typeof window.cockpit[name] !== 'function') continue;\n try {\n var val = window.cockpit[name](VARIABLE_ID);\n if (val !== undefined && val !== null) {\n var el = document.getElementById('probe-heading');\n if (el) el.innerText = 'heading (' + name + '): ' + JSON.stringify(val);\n var sel = document.getElementById('probe-status');\n if (sel) sel.innerText = 'status: polling OK via ' + name;\n return; // first working method wins\n }\n } catch (e) { /* silent -- error shown in method list above */ }\n }\n }, 2000);\n})();"
+}
diff --git a/widgets/w0_data_lake_inspector.html b/widgets/w0_data_lake_inspector.html
deleted file mode 100644
index 5ac7b14..0000000
--- a/widgets/w0_data_lake_inspector.html
+++ /dev/null
@@ -1,344 +0,0 @@
-
-
-
-
-
-
-
-