Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions apps/web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,38 @@ import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
import { ErrorBoundary } from "react-error-boundary";
import { MapProvider } from "react-map-gl/maplibre";

import { useConnections } from "@pages/Connections/useConnections.ts";
import { useEffect, useRef } from "react";

function AutoConnect() {
const savedConnections = useDeviceStore((s) => s.savedConnections);
const { connect } = useConnections();
const { selectedDeviceId } = useAppStore();
const { getDevice } = useDeviceStore();
const device = getDevice(selectedDeviceId);
const hasAttempted = useRef(false);

useEffect(() => {
if (hasAttempted.current || device || savedConnections.length === 0) {
return;
}

const target =
savedConnections.find((c) => c.isDefault && c.type === "http") ||
savedConnections.find((c) => c.type === "http");

if (target) {
hasAttempted.current = true;
useDeviceStore
.getState()
.updateSavedConnection(target.id, { status: "disconnected" });
void connect(target.id);
}
}, [savedConnections, device, connect]);

return null;
}

export function App() {
useTheme();

Expand All @@ -26,6 +58,7 @@ export function App() {
return (
<ErrorBoundary FallbackComponent={ErrorPage}>
<Toaster />
<AutoConnect />
<TanStackRouterDevtools position="bottom-right" />
<DeviceWrapper deviceId={selectedDeviceId}>
{/* Overlay sits outside the device-conditional branch so it shows
Expand Down
12 changes: 10 additions & 2 deletions apps/web/src/components/Map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,23 @@ export const BaseMap = ({
renderWorldCopies={false}
maxPitch={0}
dragRotate={false}
touchZoomRotate={false}
touchZoomRotate={true}
scrollZoom={true}
doubleClickZoom={true}
minZoom={0}
maxZoom={20}
initialViewState={
initialViewState ?? {
zoom: 1.8,
latitude: 35,
longitude: 0,
}
}
style={{ filter: darkMode ? "brightness(0.9)" : undefined }}
style={{
width: "100%",
height: "100%",
filter: darkMode ? "brightness(0.9)" : undefined,
}}
locale={locale}
interactiveLayerIds={interactiveLayerIds}
onMouseMove={onMouseMove}
Expand Down
47 changes: 35 additions & 12 deletions apps/web/src/core/hooks/useMapFitting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,25 @@ import type { MapRef } from "react-map-gl/maplibre";
export function useMapFitting(map: MapRef | undefined) {
const focusLngLat = useCallback(
(position: LngLat) => {
if (!map) {
if (!map || !position) {
return;
}
const [lng, lat] = position;
map.easeTo({
center: [lng, lat],
zoom: map.getZoom(),
});
if (
!Number.isFinite(lng) ||
!Number.isFinite(lat) ||
(lng === 0 && lat === 0)
) {
return;
}
try {
map.easeTo({
center: [lng, lat],
zoom: map.getZoom(),
});
} catch {
// Ignore easeTo error
}
},
[map],
);
Expand All @@ -25,22 +36,34 @@ export function useMapFitting(map: MapRef | undefined) {
}

if (nodes.length === 1 && nodes[0]) {
return focusLngLat(toLngLat(nodes[0].position));
const pos = toLngLat(nodes[0].position);
if (
pos &&
Number.isFinite(pos[0]) &&
Number.isFinite(pos[1]) &&
!(pos[0] === 0 && pos[1] === 0)
) {
return focusLngLat(pos);
}
return;
}

// Build [lng, lat] coords, then let boundsFromLngLat do the turf dance
const coords = nodes.map((n) => toLngLat(n.position));
const bounds = boundsFromLngLat(coords);
Comment on lines 51 to 52

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 | 🟠 Major | ⚡ Quick win

Exclude the invalid [0, 0] sentinel before calculating bounds.

At Line 51, toLngLat(undefined) produces [0, 0]. boundsFromLngLat accepts this pair because both values are finite. A missing node position can then move a multi-node fit toward Null Island. This differs from the single-node path, which rejects [0, 0].

Proposed fix
-      const coords = nodes.map((n) => toLngLat(n.position));
+      const coords = nodes
+        .map((n) => toLngLat(n.position))
+        .filter(
+          ([lng, lat]) =>
+            Number.isFinite(lng) &&
+            Number.isFinite(lat) &&
+            !(lng === 0 && lat === 0),
+        );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const coords = nodes.map((n) => toLngLat(n.position));
const bounds = boundsFromLngLat(coords);
const coords = nodes
.map((n) => toLngLat(n.position))
.filter(
([lng, lat]) =>
Number.isFinite(lng) &&
Number.isFinite(lat) &&
!(lng === 0 && lat === 0),
);
const bounds = boundsFromLngLat(coords);
🤖 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 `@apps/web/src/core/hooks/useMapFitting.ts` around lines 51 - 52, Filter the
coordinates produced in the multi-node path of useMapFitting before passing them
to boundsFromLngLat, excluding the [0, 0] sentinel generated by toLngLat for
missing positions. Preserve valid coordinates and keep the existing single-node
rejection behavior consistent.

if (!bounds) {
return;
}

const center = map.cameraForBounds(bounds, {
padding: { top: 10, bottom: 10, left: 10, right: 10 },
});
try {
const center = map.cameraForBounds(bounds, {
padding: { top: 10, bottom: 10, left: 10, right: 10 },
});

if (center) {
map.easeTo(center);
if (center && center.center) {
map.easeTo(center);
}
} catch {
// Ignore cameraForBounds failure and leave default view
}
},
[map, focusLngLat],
Expand Down
19 changes: 6 additions & 13 deletions apps/web/src/core/stores/deviceStore/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -816,20 +816,13 @@ const persistOptions: PersistOptions<PrivateDeviceState, DevicePersisted> = {
}
draft.devices = rebuilt as unknown as Map<number, Draft<Device>>;

// Stale in-flight states can't survive a reload — no JS code is
// running that could complete them. Reset any persisted
// "connecting" / "configuring" / "disconnecting" entries to
// "disconnected" so the connecting overlay (which keys off
// these statuses) doesn't get stuck visible on cold boot.
// Active network connections cannot survive a browser page reload.
// Reset all saved connection statuses to "disconnected" on rehydration
// so stale "configured"/"connected"/"connecting" states do not block
// auto-reconnecting or leave the UI in a desynced state.
for (const conn of draft.savedConnections) {
if (
conn.status === "connecting" ||
conn.status === "configuring" ||
conn.status === "disconnecting"
) {
conn.status = "disconnected";
conn.error = undefined;
}
conn.status = "disconnected";
conn.error = undefined;
}
}),
);
Expand Down
26 changes: 22 additions & 4 deletions apps/web/src/core/utils/geo.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { bbox, lineString } from "@turf/turf";

export type LngLat = [number, number];
export type Mercator = [number, number];
export type Bounds = [[number, number], [number, number]];
Expand Down Expand Up @@ -28,8 +26,28 @@ export const boundsFromLngLat = (coords: LngLat[]): Bounds | undefined => {
return undefined;
}

const turfCoords = coords.map(([lng, lat]) => [lat, lng]);
const [minLat, minLng, maxLat, maxLng] = bbox(lineString(turfCoords));
let minLng = Infinity;
let maxLng = -Infinity;
let minLat = Infinity;
let maxLat = -Infinity;

for (const [lng, lat] of coords) {
if (Number.isFinite(lng) && Number.isFinite(lat)) {
if (lng < minLng) minLng = lng;
if (lng > maxLng) maxLng = lng;
if (lat < minLat) minLat = lat;
if (lat > maxLat) maxLat = lat;
}
}

if (
!Number.isFinite(minLng) ||
!Number.isFinite(minLat) ||
!Number.isFinite(maxLng) ||
!Number.isFinite(maxLat)
) {
return undefined;
}

return [
[minLng, minLat],
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/pages/Map/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ const MapPage = () => {
/>
)}
</BaseMap>
<div className="flex flex-col space-y-1 fixed top-35 right-2.5">
<div className="flex flex-col space-y-1 absolute top-36 right-3.5 z-10">
{myNode && hasPos(myNode?.position) && (
<button
type="button"
Expand Down