From 1b59a46959b5a5f4a058f063ee7fb510d7e446b3 Mon Sep 17 00:00:00 2001
From: stefpi <19478336+stefpi@users.noreply.github.com>
Date: Fri, 29 May 2026 11:25:55 -0700
Subject: [PATCH 1/8] Cache body RPC fields
---
src/actions/index.js | 31 ++++++++++++++++++++++++-
src/actions/types.js | 1 +
src/components/DeviceInfo/index.jsx | 35 +++++------------------------
src/reducers/globalState.js | 33 +++++++++++++++++++++++++++
4 files changed, 69 insertions(+), 31 deletions(-)
diff --git a/src/actions/index.js b/src/actions/index.js
index a44d3bfe3..a3d630acb 100644
--- a/src/actions/index.js
+++ b/src/actions/index.js
@@ -7,7 +7,7 @@ import MyCommaAuth from '@commaai/my-comma-auth';
import * as Types from './types';
import { resetPlayback, selectLoop } from '../timeline/playback';
import {hasRoutesData } from '../timeline/segments';
-import { getDeviceFromState, deviceVersionAtLeast } from '../utils';
+import { getDeviceFromState, deviceVersionAtLeast, deviceIsOnline } from '../utils';
let routesRequest = null;
let routesRequestPromise = null;
@@ -415,6 +415,35 @@ export function fetchDeviceNetworkStatus(dongleId) {
};
}
+export function fetchDeviceNotCar(dongleId) {
+ return async (dispatch, getState) => {
+ const device = getDeviceFromState(getState(), dongleId);
+ if (!deviceIsOnline(device)) {
+ return;
+ }
+ const payload = {
+ id: 0,
+ jsonrpc: '2.0',
+ method: 'getNotCar',
+ };
+ try {
+ const resp = await Athena.postJsonRpcPayload(dongleId, payload);
+ if (resp && resp.result !== undefined) {
+ dispatch({
+ type: Types.ACTION_UPDATE_DEVICE_RPC,
+ dongleId,
+ fields: { not_car: resp.result === true },
+ });
+ }
+ } catch (err) {
+ if (!err.message || err.message.indexOf('Device not registered') === -1) {
+ console.error(err);
+ Sentry.captureException(err, { fingerprint: 'athena_fetch_notcar' });
+ }
+ }
+ };
+}
+
export function updateDevices(devices) {
return {
type: Types.ACTION_UPDATE_DEVICES,
diff --git a/src/actions/types.js b/src/actions/types.js
index b9dc4aa11..06a4490e1 100644
--- a/src/actions/types.js
+++ b/src/actions/types.js
@@ -16,6 +16,7 @@ export const ACTION_UPDATE_ROUTE_LOCATION = 'ACTION_UPDATE_ROUTE_LOCATION';
export const ACTION_UPDATE_SHARED_DEVICE = 'ACTION_UPDATE_SHARED_DEVICE';
export const ACTION_UPDATE_DEVICE_ONLINE = 'ACTION_UPDATE_DEVICE_ONLINE';
export const ACTION_UPDATE_DEVICE_NETWORK = 'ACTION_UPDATE_DEVICE_NETWORK';
+export const ACTION_UPDATE_DEVICE_RPC = 'ACTION_UPDATE_DEVICE_RPC';
export const ACTION_PRIME_NAV = 'ACTION_PRIME_NAV';
export const ACTION_PRIME_SUBSCRIPTION = 'ACTION_PRIME_SUBSCRIPTION';
export const ACTION_PRIME_SUBSCRIBE_INFO = 'ACTION_PRIME_SUBSCRIBE_INFO';
diff --git a/src/components/DeviceInfo/index.jsx b/src/components/DeviceInfo/index.jsx
index 9ed3ca4e7..9dda12c56 100644
--- a/src/components/DeviceInfo/index.jsx
+++ b/src/components/DeviceInfo/index.jsx
@@ -8,7 +8,7 @@ import { withStyles, Typography, Button, CircularProgress, Popper, Tooltip } fro
import AccessTime from '@material-ui/icons/AccessTime';
import { athena as Athena, devices as Devices } from '@commaai/api';
-import { analyticsEvent, primeNav } from '../../actions';
+import { analyticsEvent, primeNav, fetchDeviceNotCar } from '../../actions';
import Colors from '../../colors';
import { GamepadIcon } from '../../icons';
import { deviceNamePretty, deviceIsOnline, deviceVersionAtLeast } from '../../utils';
@@ -207,7 +207,6 @@ class DeviceInfo extends Component {
snapshot: {},
windowWidth: window.innerWidth,
isTimeSelectOpen: false,
- isCommaBody: false,
bodyTeleopOpen: false,
};
@@ -217,7 +216,6 @@ class DeviceInfo extends Component {
this.onVisible = this.onVisible.bind(this);
this.fetchDeviceInfo = this.fetchDeviceInfo.bind(this);
this.fetchDeviceCarHealth = this.fetchDeviceCarHealth.bind(this);
- this.fetchIsNotCar = this.fetchIsNotCar.bind(this);
this.takeSnapshot = this.takeSnapshot.bind(this);
this.snapshotType = this.snapshotType.bind(this);
this.renderButtons = this.renderButtons.bind(this);
@@ -250,7 +248,6 @@ class DeviceInfo extends Component {
carHealth: {},
snapshot: {},
windowWidth: window.innerWidth,
- isCommaBody: false,
});
}
}
@@ -264,11 +261,11 @@ class DeviceInfo extends Component {
}
onVisible() {
- const { device } = this.props;
+ const { device, dongleId } = this.props;
if (!device.shared) {
this.fetchDeviceInfo();
this.fetchDeviceCarHealth();
- this.fetchIsNotCar();
+ this.props.dispatch(fetchDeviceNotCar(dongleId));
}
}
@@ -320,29 +317,6 @@ class DeviceInfo extends Component {
}
}
- async fetchIsNotCar() {
- const { dongleId, device } = this.props;
- if (!deviceIsOnline(device)) {
- return;
- }
-
- try {
- const payload = {
- method: 'getNotCar',
- jsonrpc: '2.0',
- id: 0,
- };
- const resp = await Athena.postJsonRpcPayload(dongleId, payload);
- if (this.mounted && dongleId === this.props.dongleId) {
- this.setState({ isCommaBody: resp.result === true });
- }
- } catch (err) {
- if (!err.message || err.message.indexOf('Device not registered') === -1) {
- console.error(err);
- }
- }
- }
-
async takeSnapshot() {
const { dongleId } = this.props;
const { snapshot } = this.state;
@@ -518,7 +492,8 @@ class DeviceInfo extends Component {
renderButtons() {
const { classes, device } = this.props;
- const { snapshot, carHealth, windowWidth, isTimeSelectOpen, isCommaBody } = this.state;
+ const { snapshot, carHealth, windowWidth, isTimeSelectOpen } = this.state;
+ const isCommaBody = device?.rpc?.not_car;
let batteryVoltage;
let batteryBackground = Colors.grey400;
diff --git a/src/reducers/globalState.js b/src/reducers/globalState.js
index e0a394c38..4e74fd415 100644
--- a/src/reducers/globalState.js
+++ b/src/reducers/globalState.js
@@ -107,6 +107,11 @@ export default function reducer(_state, action) {
state = {
...state,
devices: action.devices
+ .map((d) => {
+ // `rpc` holds Athena RPC-fetched values that would be wiped by listDevices payload
+ const prev = (_state.devices || []).find((p) => p.dongle_id === d.dongle_id);
+ return prev && prev.rpc ? { ...d, rpc: prev.rpc } : d;
+ })
.map(populateFetchedAt)
.sort(deviceCompareFn),
};
@@ -251,6 +256,34 @@ export default function reducer(_state, action) {
};
}
break;
+ case Types.ACTION_UPDATE_DEVICE_RPC:
+ // merge RPC-fetched values (e.g. not_car) into a specific device's `rpc` field
+ state = {
+ ...state,
+ devices: [...state.devices],
+ };
+ deviceIndex = state.devices.findIndex((d) => d.dongle_id === action.dongleId);
+
+ if (deviceIndex !== -1) {
+ state.devices[deviceIndex] = {
+ ...state.devices[deviceIndex],
+ rpc: {
+ ...state.devices[deviceIndex].rpc,
+ ...action.fields,
+ },
+ };
+ }
+
+ if (state.device.dongle_id === action.dongleId) {
+ state.device = {
+ ...state.device,
+ rpc: {
+ ...state.device.rpc,
+ ...action.fields,
+ },
+ };
+ }
+ break;
case Types.ACTION_PRIME_NAV:
state = {
...state,
From 8154a3366834ed44008fd5401b8b52a263726775 Mon Sep 17 00:00:00 2001
From: stefpi <19478336+stefpi@users.noreply.github.com>
Date: Fri, 29 May 2026 11:27:37 -0700
Subject: [PATCH 2/8] Add body UI icon improvements
---
src/components/BodyTeleop/index.jsx | 2 +
src/components/DeviceInfo/index.jsx | 77 +++++++++++++++++++---------
src/icons/body.png | Bin 0 -> 454 bytes
3 files changed, 55 insertions(+), 24 deletions(-)
create mode 100644 src/icons/body.png
diff --git a/src/components/BodyTeleop/index.jsx b/src/components/BodyTeleop/index.jsx
index f9dbdb7d8..ac5ee6f3c 100644
--- a/src/components/BodyTeleop/index.jsx
+++ b/src/components/BodyTeleop/index.jsx
@@ -162,6 +162,7 @@ const BodyTeleop = ({ dongleId, device, onClose }) => {
<>
@@ -202,6 +203,7 @@ const BodyTeleop = ({ dongleId, device, onClose }) => {
{connected && (
diff --git a/src/components/DeviceInfo/index.jsx b/src/components/DeviceInfo/index.jsx
index 9dda12c56..790938e00 100644
--- a/src/components/DeviceInfo/index.jsx
+++ b/src/components/DeviceInfo/index.jsx
@@ -17,6 +17,7 @@ import ResizeHandler from '../ResizeHandler';
import VisibilityHandler from '../VisibilityHandler';
import TimeSelect from '../TimeSelect'
import CommacareBadge from '../CommacareBadge';
+import BodyIcon from '../../icons/body.png';
import BodyTeleop from '../BodyTeleop';
const styles = (theme) => ({
@@ -46,6 +47,20 @@ const styles = (theme) => ({
deviceTitle: {
display: 'flex',
alignItems: 'center',
+ gap: '16px',
+ },
+ bodyIconWrapper: {
+ width: 35,
+ height: 28,
+ borderRadius: '5px',
+ backgroundColor: Colors.grey500,
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ bodyIcon: {
+ width: 20,
+ objectFit: 'contain',
},
button: {
backgroundColor: Colors.white,
@@ -99,7 +114,7 @@ const styles = (theme) => ({
carBattery: {
padding: '5px 16px',
borderRadius: 15,
- margin: '0 10px',
+ margin: '0 0px',
textAlign: 'center',
'& p': {
fontSize: 14,
@@ -374,6 +389,7 @@ class DeviceInfo extends Component {
const { classes, device } = this.props;
const { snapshot, deviceStats, windowWidth, bodyTeleopOpen } = this.state;
const commacare = device?.commacare;
+ const isCommaBody = device?.rpc?.not_car;
const containerPadding = windowWidth > 520 ? 36 : 16;
const largeSnapshotPadding = windowWidth > 1440 ? '12px 0' : 0;
@@ -387,7 +403,14 @@ class DeviceInfo extends Component {
? (
- {commacare &&
this.props.dispatch(primeNav(true))} />}
+ {commacare && this.props.dispatch(primeNav(true))} />}
+ {isCommaBody && (
+
+
+

+
+
+ )}
{deviceNamePretty(device)}
{ this.renderStats() }
@@ -398,7 +421,14 @@ class DeviceInfo extends Component {
<>
- {commacare &&
this.props.dispatch(primeNav(true))} />}
+ {commacare && this.props.dispatch(primeNav(true))} />}
+ {isCommaBody && (
+
+
+

+
+
+ )}
{deviceNamePretty(device)}
@@ -526,25 +556,7 @@ class DeviceInfo extends Component {
const bodyTeleopEnabled = isCommaBody && deviceVersionAtLeast(device, '0.11.2');
return (
- <>
- {bodyTeleopEnabled && (
-
-
-
-
-
- )}
+
)}
- {!bodyTeleopEnabled && (
+ {bodyTeleopEnabled ? (
+
+
+
+
+
+ ) : (
);
}
diff --git a/src/icons/body.png b/src/icons/body.png
new file mode 100644
index 0000000000000000000000000000000000000000..529bfd721c45af5c193797dc7e30ac7d9218f38a
GIT binary patch
literal 454
zcmV;%0XhDOP)
_Q$01#+~nzojV8C8TH8p*nrh2W*&suf8?``a2LS60>#bh@F5_D1Kb0O
zZ@YkWHU^|{iH3VXF@7H?Dr^%_x~$pK0uowB+YNyA2#L`4kJ50y{7S@*0A2ZGX*kQu
zpIfu{QNW@bXA)N)xu(kw5W4hi9{@h!4umfKdI4u)m+m0|paxto06-169nid916+!L
w)xZl30jq&mWXK0OBHVy6K;pD@fMB}1ZYe0N
Date: Fri, 29 May 2026 12:45:42 -0700
Subject: [PATCH 3/8] fix mobile device info header
---
src/components/DeviceInfo/index.jsx | 107 +++++++++++++++-------------
1 file changed, 56 insertions(+), 51 deletions(-)
diff --git a/src/components/DeviceInfo/index.jsx b/src/components/DeviceInfo/index.jsx
index 790938e00..4cc93e380 100644
--- a/src/components/DeviceInfo/index.jsx
+++ b/src/components/DeviceInfo/index.jsx
@@ -23,7 +23,6 @@ import BodyTeleop from '../BodyTeleop';
const styles = (theme) => ({
container: {
borderBottom: `1px solid ${Colors.white10}`,
- paddingTop: 8,
display: 'flex',
flexDirection: 'column',
minHeight: 64,
@@ -33,10 +32,6 @@ const styles = (theme) => ({
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
- marginBottom: 4,
- [theme.breakpoints.down('xs')]: {
- marginBottom: 8,
- },
},
columnGap: {
columnGap: theme.spacing.unit * 4,
@@ -109,7 +104,6 @@ const styles = (theme) => ({
flexDirection: 'column',
alignItems: 'center',
maxWidth: 80,
- padding: `0 ${theme.spacing.unit * 4}px`,
},
carBattery: {
padding: '5px 16px',
@@ -398,51 +392,63 @@ class DeviceInfo extends Component {
<>
-
- { windowWidth >= 768
- ? (
-
-
- {commacare &&
this.props.dispatch(primeNav(true))} />}
- {isCommaBody && (
-
-
-

-
-
- )}
- {deviceNamePretty(device)}
-
-
{ this.renderStats() }
-
{ this.renderButtons() }
-
- )
- : (
- <>
-
-
- {commacare &&
this.props.dispatch(primeNav(true))} />}
- {isCommaBody && (
-
-
-

-
-
- )}
- {deviceNamePretty(device)}
+
+
+
+ {commacare &&
this.props.dispatch(primeNav(true))} />}
+ {isCommaBody && (
+
+
+
-
-
- { this.renderButtons() }
-
- { deviceStats.result
- && (
-
- { this.renderStats() }
-
+
)}
- >
- ) }
+
{deviceNamePretty(device)}
+
+
{ this.renderStats() }
+
{ this.renderButtons() }
+
+ {/* // { windowWidth >= 768 */}
+ {/* // ? (
+ //
+ //
+ // {commacare &&
this.props.dispatch(primeNav(true))} />}
+ // {isCommaBody && (
+ //
+ //
+ //

+ //
+ //
+ // )}
+ // {deviceNamePretty(device)}
+ //
+ //
{ this.renderStats() }
+ //
{ this.renderButtons() }
+ //
+ // )
+ // : (
+ // <>
+ //
+ //
+ // {commacare &&
this.props.dispatch(primeNav(true))} />}
+ // {isCommaBody && (
+ //
+ //
+ //

+ //
+ //
+ // )}
+ // {deviceNamePretty(device)}
+ //
+ //
+ // { this.renderButtons() }
+ // { deviceStats.result && (
+ //
+ // { this.renderStats() }
+ //
+ // )}
+ // >
+ // ) } */}
{ snapshot.result
&& (
@@ -556,7 +562,7 @@ class DeviceInfo extends Component {
const bodyTeleopEnabled = isCommaBody && deviceVersionAtLeast(device, '0.11.2');
return (
-
+
From 28f1c98da99cd0db0c631f318afddaa2ab358052 Mon Sep 17 00:00:00 2001
From: stefpi <19478336+stefpi@users.noreply.github.com>
Date: Fri, 29 May 2026 12:52:54 -0700
Subject: [PATCH 4/8] restore spacing on big screen
---
src/components/DeviceInfo/index.jsx | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/components/DeviceInfo/index.jsx b/src/components/DeviceInfo/index.jsx
index 4cc93e380..69c70fc66 100644
--- a/src/components/DeviceInfo/index.jsx
+++ b/src/components/DeviceInfo/index.jsx
@@ -104,6 +104,7 @@ const styles = (theme) => ({
flexDirection: 'column',
alignItems: 'center',
maxWidth: 80,
+ padding: `0 ${theme.spacing.unit * 4}px`,
},
carBattery: {
padding: '5px 16px',
@@ -405,7 +406,7 @@ class DeviceInfo extends Component {
)}
{deviceNamePretty(device)}
-
{ this.renderStats() }
+
{ this.renderStats() }
{ this.renderButtons() }
{/* // { windowWidth >= 768 */}
From 22f93f0fbe8e0ddce0d9548151ab4d1d7d79075f Mon Sep 17 00:00:00 2001
From: stefpi <19478336+stefpi@users.noreply.github.com>
Date: Fri, 29 May 2026 12:53:25 -0700
Subject: [PATCH 5/8] remove old code
---
src/components/DeviceInfo/index.jsx | 41 -----------------------------
1 file changed, 41 deletions(-)
diff --git a/src/components/DeviceInfo/index.jsx b/src/components/DeviceInfo/index.jsx
index 69c70fc66..2a05eb8e8 100644
--- a/src/components/DeviceInfo/index.jsx
+++ b/src/components/DeviceInfo/index.jsx
@@ -409,47 +409,6 @@ class DeviceInfo extends Component {
{ this.renderStats() }
{ this.renderButtons() }
- {/* // { windowWidth >= 768 */}
- {/* // ? (
- //
- //
- // {commacare &&
this.props.dispatch(primeNav(true))} />}
- // {isCommaBody && (
- //
- //
- //

- //
- //
- // )}
- // {deviceNamePretty(device)}
- //
- //
{ this.renderStats() }
- //
{ this.renderButtons() }
- //
- // )
- // : (
- // <>
- //
- //
- // {commacare &&
this.props.dispatch(primeNav(true))} />}
- // {isCommaBody && (
- //
- //
- //

- //
- //
- // )}
- // {deviceNamePretty(device)}
- //
- //
- //
{ this.renderButtons() }
- // { deviceStats.result && (
- //
- // { this.renderStats() }
- //
- // )}
- // >
- // ) } */}
{ snapshot.result
&& (
From 0814471412316c141a06bedb6afe2e427c73e17a Mon Sep 17 00:00:00 2001
From: stefpi <19478336+stefpi@users.noreply.github.com>
Date: Fri, 29 May 2026 13:08:23 -0700
Subject: [PATCH 6/8] clean diff
---
src/components/BodyTeleop/index.jsx | 2 --
1 file changed, 2 deletions(-)
diff --git a/src/components/BodyTeleop/index.jsx b/src/components/BodyTeleop/index.jsx
index ac5ee6f3c..f9dbdb7d8 100644
--- a/src/components/BodyTeleop/index.jsx
+++ b/src/components/BodyTeleop/index.jsx
@@ -162,7 +162,6 @@ const BodyTeleop = ({ dongleId, device, onClose }) => {
<>
@@ -203,7 +202,6 @@ const BodyTeleop = ({ dongleId, device, onClose }) => {
{connected && (
From 80b3f4cda4f249e73cce7985573592cc7b9787e5 Mon Sep 17 00:00:00 2001
From: stefpi <19478336+stefpi@users.noreply.github.com>
Date: Fri, 29 May 2026 13:13:40 -0700
Subject: [PATCH 7/8] fix lint
---
src/components/DeviceInfo/index.jsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/DeviceInfo/index.jsx b/src/components/DeviceInfo/index.jsx
index 2a05eb8e8..6ba2201a9 100644
--- a/src/components/DeviceInfo/index.jsx
+++ b/src/components/DeviceInfo/index.jsx
@@ -382,7 +382,7 @@ class DeviceInfo extends Component {
render() {
const { classes, device } = this.props;
- const { snapshot, deviceStats, windowWidth, bodyTeleopOpen } = this.state;
+ const { snapshot, windowWidth, bodyTeleopOpen } = this.state;
const commacare = device?.commacare;
const isCommaBody = device?.rpc?.not_car;
From 4b34b6ffba4f016360c048d60a008f6593c573ff Mon Sep 17 00:00:00 2001
From: stefpi <19478336+stefpi@users.noreply.github.com>
Date: Fri, 29 May 2026 13:40:25 -0700
Subject: [PATCH 8/8] reduce padding on big screen
---
src/components/DeviceInfo/index.jsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/DeviceInfo/index.jsx b/src/components/DeviceInfo/index.jsx
index 6ba2201a9..99496e66b 100644
--- a/src/components/DeviceInfo/index.jsx
+++ b/src/components/DeviceInfo/index.jsx
@@ -394,7 +394,7 @@ class DeviceInfo extends Component {
-
+
{commacare && this.props.dispatch(primeNav(true))} />}
{isCommaBody && (