From db50bb5c1bcc4426a7f11248909a793533593ed4 Mon Sep 17 00:00:00 2001 From: Hunter Thornsberry Date: Wed, 12 Mar 2025 22:32:01 -0400 Subject: [PATCH 01/19] stop gap for channel position precision until fix is worked out --- src/components/PageComponents/Channel.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/PageComponents/Channel.tsx b/src/components/PageComponents/Channel.tsx index 78cad593..285f7f4f 100644 --- a/src/components/PageComponents/Channel.tsx +++ b/src/components/PageComponents/Channel.tsx @@ -34,13 +34,13 @@ export const Channel = ({ channel }: SettingsPanelProps) => { settings: { ...data.settings, psk: toByteArray(pass), - moduleSettings: { - positionPrecision: data.settings.positionEnabled - ? data.settings.preciseLocation - ? 32 - : data.settings.positionPrecision - : 0, - }, + // moduleSettings: { + // positionPrecision: data.settings.positionEnabled + // ? data.settings.preciseLocation + // ? 32 + // : data.settings.positionPrecision + // : 0, + // }, }, }); connection?.setChannel(channel).then(() => { From db2cb8cb422e7373f59ea86c3475ab5ba7dfe697 Mon Sep 17 00:00:00 2001 From: Hunter275 Date: Thu, 13 Mar 2025 01:29:03 -0400 Subject: [PATCH 02/19] update style and wording of browser support for connection types --- src/components/Dialog/NewDeviceDialog.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/Dialog/NewDeviceDialog.tsx b/src/components/Dialog/NewDeviceDialog.tsx index 192c51a6..a19261e3 100644 --- a/src/components/Dialog/NewDeviceDialog.tsx +++ b/src/components/Dialog/NewDeviceDialog.tsx @@ -52,7 +52,7 @@ const links: { [key: string]: string } = { const listFormatter = new Intl.ListFormat("en", { style: "long", - type: "conjunction", + type: "disjunction", }); const ErrorMessage = ({ missingFeatures }: FeatureErrorProps) => { @@ -78,16 +78,16 @@ const ErrorMessage = ({ missingFeatures }: FeatureErrorProps) => { }; return ( - +
- +
-

+

{browserFeatures.length > 0 && ( <> - This application requires{" "} + This connection type requires{" "} {formatFeatureList(browserFeatures)}. Please use a - Chromium-based browser like Chrome or Edge. + supported browser, like Chrome or Edge. )} {needsSecureContext && ( From 52b80613f86e1da2cc200de45134bd5921f224e1 Mon Sep 17 00:00:00 2001 From: Hunter275 Date: Sun, 16 Mar 2025 01:33:05 -0400 Subject: [PATCH 03/19] position precision rework --- src/components/PageComponents/Channel.tsx | 64 ++++++++--------------- 1 file changed, 22 insertions(+), 42 deletions(-) diff --git a/src/components/PageComponents/Channel.tsx b/src/components/PageComponents/Channel.tsx index 78cad593..342097a9 100644 --- a/src/components/PageComponents/Channel.tsx +++ b/src/components/PageComponents/Channel.tsx @@ -34,12 +34,8 @@ export const Channel = ({ channel }: SettingsPanelProps) => { settings: { ...data.settings, psk: toByteArray(pass), - moduleSettings: { - positionPrecision: data.settings.positionEnabled - ? data.settings.preciseLocation - ? 32 - : data.settings.positionPrecision - : 0, + moduleSettings: {...data.settings.moduleSettings, + positionPrecision: data.settings.moduleSettings.positionPrecision, }, }, }); @@ -100,17 +96,9 @@ export const Channel = ({ channel }: SettingsPanelProps) => { settings: { ...channel?.settings, psk: pass, - positionEnabled: - channel?.settings?.moduleSettings?.positionPrecision !== - undefined && - channel?.settings?.moduleSettings?.positionPrecision > 0, - preciseLocation: - channel?.settings?.moduleSettings?.positionPrecision === 32, - positionPrecision: - channel?.settings?.moduleSettings?.positionPrecision === - undefined - ? 10 - : channel?.settings?.moduleSettings?.positionPrecision, + moduleSettings: {...channel?.settings?.moduleSettings, + positionPrecision: channel?.settings?.moduleSettings?.positionPrecision === undefined ? 10 : channel?.settings?.moduleSettings?.positionPrecision, + } }, }, }} @@ -173,39 +161,30 @@ export const Channel = ({ channel }: SettingsPanelProps) => { label: "Downlink Enabled", description: "Send messages from MQTT to the local mesh", }, - { - type: "toggle", - name: "settings.positionEnabled", - label: "Allow Position Requests", - description: "Send position to channel", - }, - { - type: "toggle", - name: "settings.preciseLocation", - label: "Precise Location", - description: "Send precise location to channel", - }, { type: "select", - name: "settings.positionPrecision", - label: "Approximate Location", + name: "settings.moduleSettings.positionPrecision", + label: "Location", description: - "If not sharing precise location, position shared on channel will be accurate within this distance", + "The precision of the location to share with the channel. Can be disabled.", properties: { enumValue: config.display?.units === 0 ? { - "Within 23 km": 10, - "Within 12 km": 11, - "Within 5.8 km": 12, - "Within 2.9 km": 13, - "Within 1.5 km": 14, - "Within 700 m": 15, - "Within 350 m": 16, - "Within 200 m": 17, - "Within 90 m": 18, - "Within 50 m": 19, + "Do not share location": 0, + "Within 23 kilometers": 10, + "Within 12 kilometers": 11, + "Within 5.8 kilometers": 12, + "Within 2.9 kilometers": 13, + "Within 1.5 kilometers": 14, + "Within 700 meters": 15, + "Within 350 meters": 16, + "Within 200 meters": 17, + "Within 90 meters": 18, + "Within 50 meters": 19, + "Precise Location": 32, } : { + "Do not share location": 0, "Within 15 miles": 10, "Within 7.3 miles": 11, "Within 3.6 miles": 12, @@ -216,6 +195,7 @@ export const Channel = ({ channel }: SettingsPanelProps) => { "Within 600 feet": 17, "Within 300 feet": 18, "Within 150 feet": 19, + "Precise Location": 32, }, }, }, From 207061e9d82f25953eface40149f3bd3aba360d1 Mon Sep 17 00:00:00 2001 From: bkimmel Date: Sun, 16 Mar 2025 09:41:50 -0400 Subject: [PATCH 04/19] small-scale Nodes page fixes --- src/components/generic/Table/index.tsx | 12 +++++++-- src/pages/Nodes.tsx | 35 ++++++++++++-------------- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/components/generic/Table/index.tsx b/src/components/generic/Table/index.tsx index ebd5bae4..2a002305 100755 --- a/src/components/generic/Table/index.tsx +++ b/src/components/generic/Table/index.tsx @@ -92,7 +92,7 @@ export const Table = ({ headings, rows }: TableProps) => { { {sortedRows.map((row, index) => ( // biome-ignore lint/suspicious/noArrayIndexKey: TODO: Once this table is sortable, this should get fixed. - + {row.map((item, index) => ( + index === 0 ? + + {item} + : void; } +function shortNameFromNode(node: ReturnType["nodes"][number]): string { + const shortNameOfNode = node.user?.shortName ?? (node.user?.macaddr + ? `${base16 + .stringify(node.user?.macaddr.subarray(4, 6) ?? []) + .toLowerCase()}` + : `${numberToHexUnpadded(node.num).slice(-4)}`); + return String(shortNameOfNode); +} + const NodesPage = (): JSX.Element => { const { nodes, hardware, connection } = useDevice(); console.log(connection); @@ -89,7 +98,6 @@ const NodesPage = (): JSX.Element => { { ]} rows={filteredNodes.map((node) => [
- +
, - -

setSelectedNode(node)} - className="cursor-pointer" - > - {node.user?.shortName ?? - (node.user?.macaddr - ? `${base16 - .stringify(node.user?.macaddr.subarray(4, 6) ?? []) - .toLowerCase()}` - : `${numberToHexUnpadded(node.num).slice(-4)}`)} -

, -

setSelectedNode(node)} - className="cursor-pointer" + onKeyUp={(evt)=>{ evt.key === "Enter" && setSelectedNode(node) }} + className="cursor-pointer underline" + tabIndex={0} + role="button" > {node.user?.longName ?? (node.user?.macaddr @@ -138,9 +135,9 @@ const NodesPage = (): JSX.Element => { .match(/.{1,2}/g) ?.join(":") ?? "UNK"} , - + {node.lastHeard === 0 ? ( -

Never

+

Never

) : ( )} From 20af1b4d3467884c9175de3b6ffa59139f21b182 Mon Sep 17 00:00:00 2001 From: Hunter Thornsberry Date: Sun, 16 Mar 2025 19:35:26 -0400 Subject: [PATCH 05/19] change submit to be outlined --- src/components/Form/DynamicForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Form/DynamicForm.tsx b/src/components/Form/DynamicForm.tsx index 00d26c4f..5522ec4f 100644 --- a/src/components/Form/DynamicForm.tsx +++ b/src/components/Form/DynamicForm.tsx @@ -124,7 +124,7 @@ export function DynamicForm({ })} ))} - {hasSubmitButton && } + {hasSubmitButton && } ); } From 3f8d3389d5b22a8e2ea9bc98acacde0b3a077fee Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Sun, 16 Mar 2025 22:56:58 -0400 Subject: [PATCH 06/19] feat: add error handling for key mismatch --- src/components/Dialog/DialogManager.tsx | 7 + .../RefreshKeysDialog.test.tsx | 55 ++++++++ .../RefreshKeysDialog/RefreshKeysDialog.tsx | 60 +++++++++ .../useRefreshKeysDialog.test.ts | 77 +++++++++++ .../RefreshKeysDialog/useRefreshKeysDialog.ts | 28 ++++ .../PageComponents/Connect/Serial.tsx | 6 +- .../PageComponents/Messages/ChannelChat.tsx | 5 +- .../PageComponents/Messages/Message.tsx | 122 +++++++++-------- .../PageComponents/Messages/MessageItem.tsx | 126 ++++++++++++++++++ src/components/UI/Avatar.tsx | 11 +- src/components/UI/Button.tsx | 2 +- src/core/stores/deviceStore.ts | 58 +++++++- src/core/subscriptions.ts | 29 ++-- src/pages/Messages.tsx | 6 +- src/pages/Nodes.tsx | 2 - vite.config.ts | 6 +- 16 files changed, 507 insertions(+), 93 deletions(-) create mode 100644 src/components/Dialog/RefreshKeysDialog/RefreshKeysDialog.test.tsx create mode 100644 src/components/Dialog/RefreshKeysDialog/RefreshKeysDialog.tsx create mode 100644 src/components/Dialog/RefreshKeysDialog/useRefreshKeysDialog.test.ts create mode 100644 src/components/Dialog/RefreshKeysDialog/useRefreshKeysDialog.ts create mode 100644 src/components/PageComponents/Messages/MessageItem.tsx diff --git a/src/components/Dialog/DialogManager.tsx b/src/components/Dialog/DialogManager.tsx index e8d597d4..bb5f660b 100644 --- a/src/components/Dialog/DialogManager.tsx +++ b/src/components/Dialog/DialogManager.tsx @@ -8,6 +8,7 @@ import { RebootDialog } from "@components/Dialog/RebootDialog.tsx"; import { ShutdownDialog } from "@components/Dialog/ShutdownDialog.tsx"; import { NodeDetailsDialog } from "@components/Dialog/NodeDetailsDialog.tsx"; import { UnsafeRolesDialog } from "@components/Dialog/UnsafeRolesDialog/UnsafeRolesDialog.tsx"; +import { RefreshKeysDialog } from "@components/Dialog/RefreshKeysDialog/RefreshKeysDialog.tsx"; export const DialogManager = () => { const { channels, config, dialog, setDialogOpen } = useDevice(); @@ -70,6 +71,12 @@ export const DialogManager = () => { setDialogOpen("unsafeRoles", open); }} /> + { + setDialogOpen("refreshKeys", open); + }} + /> ); }; diff --git a/src/components/Dialog/RefreshKeysDialog/RefreshKeysDialog.test.tsx b/src/components/Dialog/RefreshKeysDialog/RefreshKeysDialog.test.tsx new file mode 100644 index 00000000..e955b88f --- /dev/null +++ b/src/components/Dialog/RefreshKeysDialog/RefreshKeysDialog.test.tsx @@ -0,0 +1,55 @@ +import { render, screen, fireEvent } from "@testing-library/react"; +import { beforeEach, describe, expect, it, Mock, vi } from "vitest"; +import { RefreshKeysDialog } from "./RefreshKeysDialog"; +import { useRefreshKeysDialog } from "./useRefreshKeysDialog.ts"; + +vi.mock("./useRefreshKeysDialog.ts", () => ({ + useRefreshKeysDialog: vi.fn(), +})); + +describe("RefreshKeysDialog Component", () => { + let handleCloseDialogMock: Mock; + let handleNodeRemoveMock: Mock; + let onOpenChangeMock: Mock; + + beforeEach(() => { + handleCloseDialogMock = vi.fn(); + handleNodeRemoveMock = vi.fn(); + onOpenChangeMock = vi.fn(); + + (useRefreshKeysDialog as Mock).mockReturnValue({ + handleCloseDialog: handleCloseDialogMock, + handleNodeRemove: handleNodeRemoveMock, + }); + }); + + it("renders the dialog with correct content", () => { + render(); + expect(screen.getByText("Keys Mismatch")).toBeInTheDocument(); + expect(screen.getByText("Request New Keys")).toBeInTheDocument(); + expect(screen.getByText("Dismiss")).toBeInTheDocument(); + }); + + it("calls handleNodeRemove when 'Request New Keys' button is clicked", () => { + render(); + fireEvent.click(screen.getByText("Request New Keys")); + expect(handleNodeRemoveMock).toHaveBeenCalled(); + }); + + it("calls handleCloseDialog when 'Dismiss' button is clicked", () => { + render(); + fireEvent.click(screen.getByText("Dismiss")); + expect(handleCloseDialogMock).toHaveBeenCalled(); + }); + + it("calls onOpenChange when dialog close button is clicked", () => { + render(); + fireEvent.click(screen.getByRole("button", { name: /close/i })); + expect(handleCloseDialogMock).toHaveBeenCalled(); + }); + + it("does not render when open is false", () => { + render(); + expect(screen.queryByText("Keys Mismatch")).not.toBeInTheDocument(); + }); +}); diff --git a/src/components/Dialog/RefreshKeysDialog/RefreshKeysDialog.tsx b/src/components/Dialog/RefreshKeysDialog/RefreshKeysDialog.tsx new file mode 100644 index 00000000..1d067458 --- /dev/null +++ b/src/components/Dialog/RefreshKeysDialog/RefreshKeysDialog.tsx @@ -0,0 +1,60 @@ +import { + Dialog, + DialogClose, + DialogContent, + DialogHeader, + DialogTitle, +} from "@components/UI/Dialog.tsx"; +import { Button } from "@components/UI/Button.tsx"; +import { LockKeyholeOpenIcon } from "lucide-react"; +import { P } from "@components/UI/Typography/P.tsx"; +import { useRefreshKeysDialog } from "./useRefreshKeysDialog.ts"; + +export interface RefreshKeysDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export const RefreshKeysDialog = ({ open, onOpenChange }: RefreshKeysDialogProps) => { + + const { handleCloseDialog, handleNodeRemove } = useRefreshKeysDialog(); + return ( + + + + + Keys Mismatch + + Your node is unable to send a direct message to this node. This is due to public/private key mismatch. +
    +
  • +
    + +
    +
    +

    Refresh this node

    +

    + This will remove the node from the chat and request new keys. The process may take a few moments to complete. +

    + + +
    +
  • +
+ {/* */} +
+
+ ); +}; diff --git a/src/components/Dialog/RefreshKeysDialog/useRefreshKeysDialog.test.ts b/src/components/Dialog/RefreshKeysDialog/useRefreshKeysDialog.test.ts new file mode 100644 index 00000000..b26d0165 --- /dev/null +++ b/src/components/Dialog/RefreshKeysDialog/useRefreshKeysDialog.test.ts @@ -0,0 +1,77 @@ +import { renderHook, act } from "@testing-library/react"; +import { useRefreshKeysDialog } from "./useRefreshKeysDialog.ts"; +import { beforeEach, describe, expect, it, Mock, vi } from "vitest"; +import { useDevice } from "@core/stores/deviceStore.ts"; + +vi.mock("@core/stores/appStore.ts", () => ({ + useAppStore: vi.fn(() => ({ activeChat: "chat-123" })), +})); + +vi.mock("@core/stores/deviceStore.ts", () => ({ + useDevice: vi.fn(() => ({ + removeNode: vi.fn(), + setDialogOpen: vi.fn(), + getNodeError: vi.fn(), + clearNodeError: vi.fn(), + })), +})); + +describe("useRefreshKeysDialog Hook", () => { + let removeNodeMock: Mock; + let setDialogOpenMock: Mock; + let getNodeErrorMock: Mock; + let clearNodeErrorMock: Mock; + + beforeEach(() => { + removeNodeMock = vi.fn(); + setDialogOpenMock = vi.fn(); + getNodeErrorMock = vi.fn(); + clearNodeErrorMock = vi.fn(); + + (useDevice as Mock).mockReturnValue({ + removeNode: removeNodeMock, + setDialogOpen: setDialogOpenMock, + getNodeError: getNodeErrorMock, + clearNodeError: clearNodeErrorMock, + }); + }); + + it("handleNodeRemove should remove the node and update dialog if there is an error", () => { + getNodeErrorMock.mockReturnValue({ node: "node-abc" }); + + const { result } = renderHook(() => useRefreshKeysDialog()); + + act(() => { + result.current.handleNodeRemove(); + }); + + expect(getNodeErrorMock).toHaveBeenCalledWith("chat-123"); + expect(clearNodeErrorMock).toHaveBeenCalledWith("chat-123"); + expect(removeNodeMock).toHaveBeenCalledWith("node-abc"); + expect(setDialogOpenMock).toHaveBeenCalledWith("refreshKeys", false); + }); + + it("handleNodeRemove should do nothing if there is no error", () => { + getNodeErrorMock.mockReturnValue(undefined); + + const { result } = renderHook(() => useRefreshKeysDialog()); + + act(() => { + result.current.handleNodeRemove(); + }); + + expect(removeNodeMock).not.toHaveBeenCalled(); + expect(setDialogOpenMock).not.toHaveBeenCalled(); + expect(clearNodeErrorMock).not.toHaveBeenCalled(); + }); + + it("handleCloseDialog should close the dialog", () => { + const { result } = renderHook(() => useRefreshKeysDialog()); + + act(() => { + result.current.handleCloseDialog(); + }); + + expect(setDialogOpenMock).toHaveBeenCalledWith("refreshKeys", false); + }); +}); diff --git a/src/components/Dialog/RefreshKeysDialog/useRefreshKeysDialog.ts b/src/components/Dialog/RefreshKeysDialog/useRefreshKeysDialog.ts new file mode 100644 index 00000000..821aade7 --- /dev/null +++ b/src/components/Dialog/RefreshKeysDialog/useRefreshKeysDialog.ts @@ -0,0 +1,28 @@ +import { useCallback } from "react"; +import { useAppStore } from "@core/stores/appStore.ts"; +import { useDevice } from "@core/stores/deviceStore.ts"; + +export function useRefreshKeysDialog() { + const { removeNode, setDialogOpen, clearNodeError, getNodeError } = useDevice(); + const { activeChat } = useAppStore(); + + const handleNodeRemove = useCallback(() => { + const nodeWithError = getNodeError(activeChat); + if (!nodeWithError) { + return; + } + clearNodeError(activeChat); + handleCloseDialog();; + return removeNode(nodeWithError?.node); + }, [activeChat, clearNodeError, setDialogOpen, removeNode]); + + const handleCloseDialog = useCallback(() => { + setDialogOpen('refreshKeys', false); + }, [setDialogOpen]) + + return { + handleCloseDialog, + handleNodeRemove + }; + +} \ No newline at end of file diff --git a/src/components/PageComponents/Connect/Serial.tsx b/src/components/PageComponents/Connect/Serial.tsx index b6cdf5f9..28085cc2 100644 --- a/src/components/PageComponents/Connect/Serial.tsx +++ b/src/components/PageComponents/Connect/Serial.tsx @@ -18,9 +18,7 @@ export const Serial = ({ closeDialog }: TabElementProps) => { setSerialPorts(await navigator?.serial.getPorts()); }, []); - navigator?.serial?.addEventListener("connect", (event) => { - console.log(event); - + navigator?.serial?.addEventListener("connect", () => { updateSerialPortList(); }); navigator?.serial?.addEventListener("disconnect", () => { @@ -47,8 +45,6 @@ export const Serial = ({ closeDialog }: TabElementProps) => {
{serialPorts.map((port, index) => { - console.log(port); - const { usbProductId, usbVendorId } = port.getInfo(); return (

{sortedRows.map((row, index) => ( // biome-ignore lint/suspicious/noArrayIndexKey: TODO: Once this table is sortable, this should get fixed. - + {row.map((item, index) => ( index === 0 ?
Date: Tue, 18 Mar 2025 00:06:38 -0400 Subject: [PATCH 09/19] add a label to theme icon --- src/components/ThemeSwitcher.tsx | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/components/ThemeSwitcher.tsx b/src/components/ThemeSwitcher.tsx index c662eda2..dc1eec4a 100644 --- a/src/components/ThemeSwitcher.tsx +++ b/src/components/ThemeSwitcher.tsx @@ -1,3 +1,4 @@ +import { useState } from "react"; import { useTheme } from "../core/hooks/useTheme.ts"; import { cn } from "../core/utils/cn.ts"; import { Monitor, Moon, Sun } from "lucide-react"; @@ -10,6 +11,7 @@ export default function ThemeSwitcher({ className?: string; }) { const { theme, preference, setPreference } = useTheme(); + const [isFocused, setIsFocused] = useState(false); const themeIcons = { light: , @@ -24,6 +26,17 @@ export default function ThemeSwitcher({ setPreference(nextPreference); }; + const labelStyle = { + display: "block", + margin: "0 auto", + fontSize: ".65rem", + width: "100%", + position: "absolute", + top: isFocused ? "-2em" : "2em", + left: "0", + opacity: isFocused ? "100" : "0" + }; + return ( ); From 9399104914d023fee57521a12336fc3cf55c90b7 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Tue, 18 Mar 2025 15:21:58 -0400 Subject: [PATCH 10/19] fix: resolved issues with styling --- src/components/Dialog/DialogManager.tsx | 2 +- src/components/Dialog/NodeDetailsDialog.tsx | 190 ------------------ .../NodeDetailsDialog.test.tsx | 73 +++++++ .../NodeDetailsDialog/NodeDetailsDialog.tsx | 177 ++++++++++++++++ .../Messages/TraceRoute.test.tsx | 95 +++++++++ .../PageComponents/Messages/TraceRoute.tsx | 78 +++---- 6 files changed, 389 insertions(+), 226 deletions(-) delete mode 100644 src/components/Dialog/NodeDetailsDialog.tsx create mode 100644 src/components/Dialog/NodeDetailsDialog/NodeDetailsDialog.test.tsx create mode 100644 src/components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx create mode 100644 src/components/PageComponents/Messages/TraceRoute.test.tsx diff --git a/src/components/Dialog/DialogManager.tsx b/src/components/Dialog/DialogManager.tsx index e8d597d4..0c698823 100644 --- a/src/components/Dialog/DialogManager.tsx +++ b/src/components/Dialog/DialogManager.tsx @@ -6,7 +6,7 @@ import { PkiBackupDialog } from "@components/Dialog/PKIBackupDialog.tsx"; import { QRDialog } from "@components/Dialog/QRDialog.tsx"; import { RebootDialog } from "@components/Dialog/RebootDialog.tsx"; import { ShutdownDialog } from "@components/Dialog/ShutdownDialog.tsx"; -import { NodeDetailsDialog } from "@components/Dialog/NodeDetailsDialog.tsx"; +import { NodeDetailsDialog } from "@components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx"; import { UnsafeRolesDialog } from "@components/Dialog/UnsafeRolesDialog/UnsafeRolesDialog.tsx"; export const DialogManager = () => { diff --git a/src/components/Dialog/NodeDetailsDialog.tsx b/src/components/Dialog/NodeDetailsDialog.tsx deleted file mode 100644 index 2f90bae2..00000000 --- a/src/components/Dialog/NodeDetailsDialog.tsx +++ /dev/null @@ -1,190 +0,0 @@ -import { useAppStore } from "../../core/stores/appStore.ts"; -import { useDevice } from "../../core/stores/deviceStore.ts"; -import { - Accordion, - AccordionContent, - AccordionItem, - AccordionTrigger, -} from "../UI/Accordion.tsx"; -import { - Dialog, - DialogClose, - DialogContent, - DialogFooter, - DialogHeader, - DialogTitle, -} from "../UI/Dialog.tsx"; -import { Protobuf } from "@meshtastic/core"; -import { numberToHexUnpadded } from "@noble/curves/abstract/utils"; -import { DeviceImage } from "../generic/DeviceImage.tsx"; -import { TimeAgo } from "../generic/TimeAgo.tsx"; -import { Uptime } from "../generic/Uptime.tsx"; - -export interface NodeDetailsDialogProps { - open: boolean; - onOpenChange: (open: boolean) => void; -} - -export const NodeDetailsDialog = ({ - open, - onOpenChange, -}: NodeDetailsDialogProps) => { - const { nodes } = useDevice(); - const { nodeNumDetails } = useAppStore(); - const device: Protobuf.Mesh.NodeInfo = nodes.get(nodeNumDetails); - - return device - ? ( - - - - - - Node Details for {device.user?.longName ?? "UNKNOWN"} ( - {device.user?.shortName ?? "UNK"}) - - - -
- -
-

- Details: -

-

- Hardware:{" "} - {Protobuf.Mesh.HardwareModel[device.user?.hwModel ?? 0]} -

-

Node Number: {device.num}

-

Node HEX: !{numberToHexUnpadded(device.num)}

-

- Role: {Protobuf.Config.Config_DeviceConfig_Role[ - device.user?.role ?? 0 - ]} -

-

- Last Heard: {device.lastHeard === 0 - ? ( - "Never" - ) - : } -

-
- - {device.position - ? ( -
-

- Position: -

- {device.position.latitudeI && device.position.longitudeI - ? ( -

- Coordinates:{" "} - - {device.position.latitudeI / 1e7},{" "} - {device.position.longitudeI / 1e7} - -

- ) - : null} - {device.position.altitude - ?

Altitude: {device.position.altitude}m

- : null} -
- ) - : null} - - {device.deviceMetrics - ? ( -
-

- Device Metrics: -

- {device.deviceMetrics.airUtilTx - ? ( -

- Air TX utilization:{" "} - {device.deviceMetrics.airUtilTx.toFixed(2)}% -

- ) - : null} - {device.deviceMetrics.channelUtilization - ? ( -

- Channel utilization:{" "} - {device.deviceMetrics.channelUtilization.toFixed(2)}% -

- ) - : null} - {device.deviceMetrics.batteryLevel - ? ( -

- Battery level:{" "} - {device.deviceMetrics.batteryLevel.toFixed(2)}% -

- ) - : null} - {device.deviceMetrics.voltage - ? ( -

- Voltage: {device.deviceMetrics.voltage.toFixed(2)}V -

- ) - : null} - {device.deviceMetrics.uptimeSeconds - ? ( -

- Uptime:{" "} - -

- ) - : null} -
- ) - : null} - - {device - ? ( -
- - - -

- All Raw Metrics: -

-
- -
-                            {JSON.stringify(device, null, 2)}
-                          
-
-
-
-
- ) - : null} -
-
-
-
- ) - : null; -}; diff --git a/src/components/Dialog/NodeDetailsDialog/NodeDetailsDialog.test.tsx b/src/components/Dialog/NodeDetailsDialog/NodeDetailsDialog.test.tsx new file mode 100644 index 00000000..d8d1bea9 --- /dev/null +++ b/src/components/Dialog/NodeDetailsDialog/NodeDetailsDialog.test.tsx @@ -0,0 +1,73 @@ +import { describe, it, vi, expect, beforeEach, Mock } from "vitest"; +import { render, screen } from "@testing-library/react"; +import { NodeDetailsDialog } from "@components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; +import { useAppStore } from "@core/stores/appStore.ts"; + +vi.mock("@core/stores/deviceStore"); +vi.mock("@core/stores/appStore"); + +describe("NodeDetailsDialog", () => { + const mockDevice = { + num: 1234, + user: { + longName: "Test Node", + shortName: "TN", + hwModel: 1, + role: 1, + }, + lastHeard: 1697500000, + position: { + latitudeI: 450000000, + longitudeI: -750000000, + altitude: 200, + }, + deviceMetrics: { + airUtilTx: 50.123, + channelUtilization: 75.456, + batteryLevel: 88.789, + voltage: 4.2, + uptimeSeconds: 3600, + }, + }; + + beforeEach(() => { + // Reset mocks before each test + vi.resetAllMocks(); + + (useDevice as Mock).mockReturnValue({ + nodes: new Map([[1234, mockDevice]]), + }); + + (useAppStore as unknown as Mock).mockReturnValue({ + nodeNumDetails: 1234, + }); + }); + + it("renders node details correctly", () => { + render( { }} />); + + expect(screen.getByText(/Node Details for Test Node/i)).toBeInTheDocument(); + + expect(screen.getByText("Node Number: 1234")).toBeInTheDocument(); + + expect(screen.getByText(/Air TX utilization: 50.12%/i)).toBeInTheDocument(); + expect(screen.getByText(/Channel utilization: 75.46%/i)).toBeInTheDocument(); + expect(screen.getByText(/Battery level: 88.79%/i)).toBeInTheDocument(); + expect(screen.getByText(/Voltage: 4.20V/i)).toBeInTheDocument(); + expect(screen.getByText(/Uptime:/i)).toBeInTheDocument(); + expect(screen.getByText(/Coordinates:/i)).toBeInTheDocument(); + expect(screen.getByText("45, -75")).toBeInTheDocument(); + expect(screen.getByText(/Altitude: 200m/i)).toBeInTheDocument(); + expect(screen.getByText(/Role:/i)).toBeInTheDocument(); + }); + + it("renders null if device is not found", () => { + (useDevice as Mock).mockReturnValue({ + nodes: new Map(), + }); + + render( { }} />); + expect(screen.queryByText(/Node Details for/i)).not.toBeInTheDocument(); + }); +}); diff --git a/src/components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx b/src/components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx new file mode 100644 index 00000000..f010fc67 --- /dev/null +++ b/src/components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx @@ -0,0 +1,177 @@ +import { useAppStore } from "@core/stores/appStore.ts"; +import { useDevice } from "@core/stores/deviceStore.ts"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@components/UI/Accordion.tsx"; +import { + Dialog, + DialogClose, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@components/UI/Dialog.tsx"; +import { Protobuf } from "@meshtastic/core"; +import { numberToHexUnpadded } from "@noble/curves/abstract/utils"; +import { DeviceImage } from "@components/generic/DeviceImage.tsx"; +import { TimeAgo } from "@components/generic/TimeAgo.tsx"; +import { Uptime } from "@components/generic/Uptime.tsx"; + +export interface NodeDetailsDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export const NodeDetailsDialog = ({ + open, + onOpenChange, +}: NodeDetailsDialogProps) => { + const { nodes } = useDevice(); + const { nodeNumDetails } = useAppStore(); + + const device = nodes.get(nodeNumDetails); + + if (!device) return null; + + const deviceMetricsMap = [ + { + key: "airUtilTx", + label: "Air TX utilization", + value: device.deviceMetrics?.airUtilTx, + format: (val: number) => `${val.toFixed(2)}%`, + }, + { + key: "channelUtilization", + label: "Channel utilization", + value: device.deviceMetrics?.channelUtilization, + format: (val: number) => `${val.toFixed(2)}%`, + }, + { + key: "batteryLevel", + label: "Battery level", + value: device.deviceMetrics?.batteryLevel, + format: (val: number) => `${val.toFixed(2)}%`, + }, + { + key: "voltage", + label: "Voltage", + value: device.deviceMetrics?.voltage, + format: (val: number) => `${val.toFixed(2)}V`, + }, + ]; + + return ( + + + + + + Node Details for {device.user?.longName ?? "UNKNOWN"} ( + {device.user?.shortName ?? "UNK"}) + + + +
+
+ +
+

Details:

+

+ Hardware:{" "} + {Protobuf.Mesh.HardwareModel[device.user?.hwModel ?? 0]} +

+

Node Number: {device.num}

+

Node Hex: !{numberToHexUnpadded(device.num)}

+

+ Role:{" "} + { + Protobuf.Config.Config_DeviceConfig_Role[ + device.user?.role ?? 0 + ] + } +

+

+ Last Heard:{" "} + {device.lastHeard === 0 ? "Never" : } +

+
+ + {device.position && ( +
+

Position:

+ {device.position.latitudeI && device.position.longitudeI && ( +

+ Coordinates:{" "} + + {device.position.latitudeI / 1e7},{" "} + {device.position.longitudeI / 1e7} + +

+ )} + {device.position.altitude && ( +

Altitude: {device.position.altitude}m

+ )} +
+ )} + + {device.deviceMetrics && ( +
+

+ Device Metrics: +

+ {deviceMetricsMap.map( + (metric) => + metric.value !== undefined && ( +

+ {metric.label}: {metric.format(metric.value)} +

+ ) + )} + {device.deviceMetrics.uptimeSeconds && ( +

+ Uptime:{" "} + +

+ )} +
+ )} + +
+ +
+ + + +

+ All Raw Metrics: +

+
+ +
+                      {JSON.stringify(device, null, 2)}
+                    
+
+
+
+
+
+
+
+
+ ); +}; diff --git a/src/components/PageComponents/Messages/TraceRoute.test.tsx b/src/components/PageComponents/Messages/TraceRoute.test.tsx new file mode 100644 index 00000000..977ef315 --- /dev/null +++ b/src/components/PageComponents/Messages/TraceRoute.test.tsx @@ -0,0 +1,95 @@ +import { describe, it, expect, vi, beforeEach, Mock } from "vitest"; +import { render, screen } from "@testing-library/react"; +import { TraceRoute } from "@components/PageComponents/Messages/TraceRoute.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; + +vi.mock("@core/stores/deviceStore"); + +describe("TraceRoute", () => { + const mockNodes = new Map([ + [ + 1, + { num: 1, user: { longName: "Node A" } }, + ], + [ + 2, + { num: 2, user: { longName: "Node B" } }, + ], + [ + 3, + { num: 3, user: { longName: "Node C" } }, + ], + ]); + + beforeEach(() => { + vi.resetAllMocks(); + (useDevice as Mock).mockReturnValue({ + nodes: mockNodes, + }); + }); + + it("renders the route to destination with SNR values", () => { + render( + + ); + + expect(screen.getByText("Route to destination:")).toBeInTheDocument(); + expect(screen.getByText("Destination Node")).toBeInTheDocument(); + + expect(screen.getByText("Node A")).toBeInTheDocument(); + expect(screen.getByText("Node B")).toBeInTheDocument(); + + expect(screen.getAllByText(/↓/)).toHaveLength(3); // startNode + 2 hops + expect(screen.getByText("↓ 10dB")).toBeInTheDocument(); + expect(screen.getByText("↓ 20dB")).toBeInTheDocument(); + expect(screen.getByText("↓ 30dB")).toBeInTheDocument(); + expect(screen.getByText("Source Node")).toBeInTheDocument(); + }); + + it("renders the route back when provided", () => { + render( + + ); + + expect(screen.getByText("Route back:")).toBeInTheDocument(); + expect(screen.getByText("Node C")).toBeInTheDocument(); + expect(screen.getByText("↓ 35dB")).toBeInTheDocument(); + expect(screen.getByText("↓ 45dB")).toBeInTheDocument(); + }); + + it("renders '??' for missing SNR values", () => { + render( + + ); + + expect(screen.getAllByText("↓ ??dB").length).toBeGreaterThan(0); + }); + + it("renders hop hex if node is not found", () => { + render( + + ); + + expect(screen.getByText(/^!63$/)).toBeInTheDocument(); // 99 in hex + }); +}); diff --git a/src/components/PageComponents/Messages/TraceRoute.tsx b/src/components/PageComponents/Messages/TraceRoute.tsx index 92a00fa6..13f8f08f 100644 --- a/src/components/PageComponents/Messages/TraceRoute.tsx +++ b/src/components/PageComponents/Messages/TraceRoute.tsx @@ -11,6 +11,33 @@ export interface TraceRouteProps { snrBack?: Array; } +interface RoutePathProps { + title: string; + startNode?: Protobuf.Mesh.NodeInfo; + endNode?: Protobuf.Mesh.NodeInfo; + path: number[]; + snr?: number[]; +} + +const RoutePath = ({ title, startNode, endNode, path, snr }: RoutePathProps) => { + const { nodes } = useDevice(); + + return ( + +

{title}

+

{startNode?.user?.longName}

+

↓ {snr?.[0] ?? "??"}dB

+ {path.map((hop, i) => ( + +

{nodes.get(hop)?.user?.longName ?? `!${numberToHexUnpadded(hop)}`}

+

↓ {snr?.[i + 1] ?? "??"}dB

+
+ ))} +

{endNode?.user?.longName}

+
+ ); +}; + export const TraceRoute = ({ from, to, @@ -19,43 +46,24 @@ export const TraceRoute = ({ snrTowards, snrBack, }: TraceRouteProps) => { - const { nodes } = useDevice(); - return (
- -

Route to destination:

-

{to?.user?.longName}

-

↓ {snrTowards?.[0] ? snrTowards[0] : "??"}dB

- {route.map((hop, i) => ( - -

- {nodes.get(hop)?.user?.longName ?? `!${numberToHexUnpadded(hop)}`} -

-

↓ {snrTowards?.[i + 1] ? snrTowards[i + 1] : "??"}dB

-
- ))} - {from?.user?.longName} -
- {routeBack - ? ( - -

Route back:

-

{from?.user?.longName}

-

↓ {snrBack?.[0] ? snrBack[0] : "??"}dB

- {routeBack.map((hop, i) => ( - -

- {nodes.get(hop)?.user?.longName ?? - `!${numberToHexUnpadded(hop)}`} -

-

↓ {snrBack?.[i + 1] ? snrBack[i + 1] : "??"}dB

-
- ))} - {to?.user?.longName} -
- ) - : null} + + {routeBack && ( + + )}
); }; From ad366e6bab402e6804044fa32a0f9ba6bea1a108 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Tue, 18 Mar 2025 19:44:09 -0400 Subject: [PATCH 11/19] added additional routing packet error handler --- src/core/subscriptions.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/core/subscriptions.ts b/src/core/subscriptions.ts index b3f2b50b..0ad39019 100644 --- a/src/core/subscriptions.ts +++ b/src/core/subscriptions.ts @@ -117,6 +117,11 @@ export const subscribeAll = ( device.setNodeError(routingPacket.from, Protobuf.Mesh.Routing_Error[routingPacket?.data?.variant?.value]); device.setDialogOpen("refreshKeys", true); break; + case Protobuf.Mesh.Routing_Error.PKI_UNKNOWN_PUBKEY: + console.error(`Routing Error: ${routingPacket.data.variant.value}`); + device.setNodeError(routingPacket.from, Protobuf.Mesh.Routing_Error[routingPacket?.data?.variant?.value]); + device.setDialogOpen("refreshKeys", true); + break; default: { break; } From 64055a5aeb4f57899d8b1f04bbd55e1874cdeaa5 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Tue, 18 Mar 2025 22:09:20 -0400 Subject: [PATCH 12/19] fixed spacing and updated wording on dialog --- .../RefreshKeysDialog/RefreshKeysDialog.tsx | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/components/Dialog/RefreshKeysDialog/RefreshKeysDialog.tsx b/src/components/Dialog/RefreshKeysDialog/RefreshKeysDialog.tsx index 1d067458..d2fc659d 100644 --- a/src/components/Dialog/RefreshKeysDialog/RefreshKeysDialog.tsx +++ b/src/components/Dialog/RefreshKeysDialog/RefreshKeysDialog.tsx @@ -7,7 +7,6 @@ import { } from "@components/UI/Dialog.tsx"; import { Button } from "@components/UI/Button.tsx"; import { LockKeyholeOpenIcon } from "lucide-react"; -import { P } from "@components/UI/Typography/P.tsx"; import { useRefreshKeysDialog } from "./useRefreshKeysDialog.ts"; export interface RefreshKeysDialogProps { @@ -25,17 +24,19 @@ export const RefreshKeysDialog = ({ open, onOpenChange }: RefreshKeysDialogProps Keys Mismatch - Your node is unable to send a direct message to this node. This is due to public/private key mismatch. + Your node is unable to send a direct message to this node. This is due to the remote node's current public key not matching the previously stored key for this node.
  • -
    +
    -
    -

    Refresh this node

    -

    - This will remove the node from the chat and request new keys. The process may take a few moments to complete. -

    +
    +
    +

    Accept New Keys

    +

    + This will remove the node from device and request new keys. +

    +
    ); From f2d6daa9fc57ac32218a8feb8eac365845f3a432 Mon Sep 17 00:00:00 2001 From: bkimmel Date: Tue, 18 Mar 2025 22:57:22 -0400 Subject: [PATCH 14/19] safety coalesce --- src/components/ThemeSwitcher.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ThemeSwitcher.tsx b/src/components/ThemeSwitcher.tsx index de34aee4..2f456d4b 100644 --- a/src/components/ThemeSwitcher.tsx +++ b/src/components/ThemeSwitcher.tsx @@ -36,7 +36,7 @@ export default function ThemeSwitcher({ onClick={toggleTheme} aria-description={'Change current theme'} > - {firstCharOfPreference.toLocaleUpperCase() + restOfPreference.join("")} + {firstCharOfPreference.toLocaleUpperCase() + (restOfPreference ?? []).join("")} {themeIcons[preference]} ); From 1f109d161f24be20343dd417864a96c4a9ee6ee4 Mon Sep 17 00:00:00 2001 From: bkimmel Date: Tue, 18 Mar 2025 23:28:44 -0400 Subject: [PATCH 15/19] deno format --- src/components/ThemeSwitcher.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/components/ThemeSwitcher.tsx b/src/components/ThemeSwitcher.tsx index 2f456d4b..ee075793 100644 --- a/src/components/ThemeSwitcher.tsx +++ b/src/components/ThemeSwitcher.tsx @@ -24,7 +24,7 @@ export default function ThemeSwitcher({ setPreference(nextPreference); }; - const [firstCharOfPreference="", ...restOfPreference] = preference; + const [firstCharOfPreference = "", ...restOfPreference] = preference; return ( ); From f54c0dd83669ae4e70474e90722c6cf22a44aea5 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Wed, 19 Mar 2025 13:46:56 -0400 Subject: [PATCH 16/19] fix: removed react-scan due to issues with bluetooth --- .gitignore | 2 +- README.md | 44 -------------------------------------------- deno.lock | 42 ++++++++++++++++++++++++++++++------------ package.json | 3 --- src/index.tsx | 8 -------- vite.config.ts | 3 --- 6 files changed, 31 insertions(+), 71 deletions(-) diff --git a/.gitignore b/.gitignore index 4cc7172b..ec3dfc3a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,6 @@ dist node_modules stats.html .vercel -.vite/deps +.vite dev-dist __screenshots__* \ No newline at end of file diff --git a/README.md b/README.md index 10e4678d..a6e50189 100644 --- a/README.md +++ b/README.md @@ -138,47 +138,3 @@ reasons: environments. - **Web Standard APIs**: Uses browser-compatible APIs, making code more portable between server and client environments. - -### Debugging - -#### Debugging with React Scan - -Meshtastic Web Client has included the library -[React Scan](https://github.com/aidenybai/react-scan) to help you identify and -resolve render performance issues during development. - -React's comparison-by-reference approach to props makes it easy to inadvertently -cause unnecessary re-renders, especially with: - -- Inline function callbacks (`onClick={() => handleClick()}`) -- Object literals (`style={{ color: "purple" }}`) -- Array literals (`items={[1, 2, 3]}`) - -These are recreated on every render, causing child components to re-render even -when nothing has actually changed. - -Unlike React DevTools, React Scan specifically focuses on performance -optimization by: - -- Clearly distinguishing between necessary and unnecessary renders -- Providing render counts for components -- Highlighting slow-rendering components -- Offering a dedicated performance debugging experience - -#### Usage - -When experiencing slow renders, run: - -```bash -deno task dev:scan -``` - -This will allow you to discover the following about your components and pages: - -- Components with excessive re-renders -- Performance bottlenecks in the render tree -- Expensive hook operations -- Props that change reference on every render - -Use these insights to apply targeted optimizations like `React.memo()`, -`useCallback()`, or `useMemo()` where they'll have the most impact. diff --git a/deno.lock b/deno.lock index b7f18099..db442d6d 100644 --- a/deno.lock +++ b/deno.lock @@ -56,7 +56,7 @@ "npm:react-hook-form@^7.54.2": "7.54.2_react@19.0.0", "npm:react-map-gl@8.0.1": "8.0.1_maplibre-gl@5.1.1_react@19.0.0_react-dom@19.0.0__react@19.0.0", "npm:react-qrcode-logo@3": "3.0.0_react@19.0.0_react-dom@19.0.0__react@19.0.0", - "npm:react-scan@~0.2.8": "0.2.8_react@19.0.0_react-dom@19.0.0__react@19.0.0_preact@10.26.4", + "npm:react-scan@~0.3.2": "0.3.2_react@19.0.0_react-dom@19.0.0__react@19.0.0_preact@10.26.4_@types+react@19.0.10", "npm:react@19": "19.0.0", "npm:rfc4648@^1.5.4": "1.5.4", "npm:simple-git-hooks@^2.11.1": "2.11.1", @@ -1305,8 +1305,8 @@ "@open-draft/until@2.1.0": { "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==" }, - "@pivanov/utils@0.0.1_react@19.0.0_react-dom@19.0.0__react@19.0.0": { - "integrity": "sha512-JQ/pXeG9/Yq3UuwH2Xp4F6bSAIDGzbxT0Vrg/82tMi3Yp+Ps9AYzjSDE+zfvBRqc7J11V6MMonUrWj4+2dYgrg==", + "@pivanov/utils@0.0.2_react@19.0.0_react-dom@19.0.0__react@19.0.0": { + "integrity": "sha512-q9CN0bFWxWgMY5hVVYyBgez1jGiLBa6I+LkG37ycylPhFvEGOOeaADGtUSu46CaZasPnlY8fCdVJZmrgKb1EPA==", "dependencies": [ "react", "react-dom" @@ -1920,6 +1920,14 @@ "rollup@2.79.2" ] }, + "@rollup/pluginutils@5.1.4": { + "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", + "dependencies": [ + "@types/estree@1.0.6", + "estree-walker@2.0.2", + "picomatch@4.0.2" + ] + }, "@rollup/pluginutils@5.1.4_rollup@2.79.2": { "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", "dependencies": [ @@ -3531,8 +3539,8 @@ "@types/pbf" ] }, - "@types/node@20.17.22": { - "integrity": "sha512-9RV2zST+0s3EhfrMZIhrz2bhuhBwxgkbHEwP2gtGWPjBzVQjifMzJ9exw7aDZhR1wbpj8zBrfp3bo8oJcGiUUw==", + "@types/node@20.17.24": { + "integrity": "sha512-d7fGCyB96w9BnWQrOsJtpyiSaBcAYYr75bnK6ZRjDbql2cGLj/3GsL5OYmLPNq76l7Gf2q4Rv9J2o6h5CrD9sA==", "dependencies": [ "undici-types@6.19.8" ] @@ -3552,6 +3560,12 @@ "@types/react" ] }, + "@types/react-reconciler@0.28.9_@types+react@19.0.10": { + "integrity": "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==", + "dependencies": [ + "@types/react" + ] + }, "@types/react@19.0.10": { "integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==", "dependencies": [ @@ -3848,8 +3862,12 @@ "bignumber.js@9.1.2": { "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==" }, - "bippy@0.2.7": { - "integrity": "sha512-LTCos3SmOJHrag0qF91tLUZMMw6wA+i15ESRBp71pvfNlTMYcxYoJHJ/pvFhd+29Wm5vfgVxBHV7kP5OKUUipg==" + "bippy@0.3.8_react@19.0.0_@types+react@19.0.10": { + "integrity": "sha512-0ou8fJWxUXK/+eRjUz5unbtX8Mrn0OYRs6QQwwUJtU6hsFDTSmSeI1fJC/2nrPA4G6GWcxwT+O6TbHyGhl4fEg==", + "dependencies": [ + "@types/react-reconciler", + "react" + ] }, "bn.js@4.12.1": { "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==" @@ -5777,8 +5795,8 @@ "use-sidecar" ] }, - "react-scan@0.2.8_react@19.0.0_react-dom@19.0.0__react@19.0.0_preact@10.26.4": { - "integrity": "sha512-+6Gvu9b0UMmzV0JkigA7Y2YcjQABiNrweP9l9j8nrutN5OAYLRe4JgfwiUohPFngMD+Y6I5N0kW+okXhvVLGUw==", + "react-scan@0.3.2_react@19.0.0_react-dom@19.0.0__react@19.0.0_preact@10.26.4_@types+react@19.0.10": { + "integrity": "sha512-lJb/klITqM95+MT+tR4nhfMl+ApXUeQ4iH0A0CsKYKkc66Wu01bOv+SxzZR1ksIPUWon7m2S3/83maZENtLEAQ==", "dependencies": [ "@babel/core", "@babel/generator", @@ -5787,8 +5805,8 @@ "@clack/prompts", "@pivanov/utils", "@preact/signals", - "@rollup/pluginutils@5.1.4_rollup@2.79.2", - "@types/node@20.17.22", + "@rollup/pluginutils@5.1.4", + "@types/node@20.17.24", "bippy", "esbuild@0.24.2", "estree-walker@3.0.3", @@ -7151,7 +7169,7 @@ "npm:react-hook-form@^7.54.2", "npm:react-map-gl@8.0.1", "npm:react-qrcode-logo@3", - "npm:react-scan@~0.2.8", + "npm:react-scan@~0.3.2", "npm:react@19", "npm:rfc4648@^1.5.4", "npm:simple-git-hooks@^2.11.1", diff --git a/package.json b/package.json index 1bea604b..51b99b39 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,6 @@ "format": "deno fmt src/", "dev": "deno task dev:ui", "dev:ui": "deno run -A npm:vite dev", - "dev:scan": "VITE_DEBUG_SCAN=true deno task dev:ui", "test": "deno run -A npm:vitest", "preview": "deno run -A npm:vite preview", "package": "gzipper c -i html,js,css,png,ico,svg,webmanifest,txt dist dist/output && tar -cvf dist/build.tar -C ./dist/output/ ." @@ -72,10 +71,8 @@ "react-hook-form": "^7.54.2", "react-map-gl": "8.0.1", "react-qrcode-logo": "^3.0.0", - "react-scan": "^0.2.8", "rfc4648": "^1.5.4", "vite-plugin-node-polyfills": "^0.23.0", - "zustand": "5.0.3" }, "devDependencies": { diff --git a/src/index.tsx b/src/index.tsx index 7b56e632..33d79536 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,4 +1,3 @@ -import { scan } from "react-scan"; import "@app/index.css"; import { enableMapSet } from "immer"; import "maplibre-gl/dist/maplibre-gl.css"; @@ -7,13 +6,6 @@ import { createRoot } from "react-dom/client"; import { App } from "@app/App.tsx"; -// run react scan tool in development mode only -// react scan must be the first import and the first line in this file in order to work properly -import.meta.env.VITE_DEBUG_SCAN && - scan({ - enabled: true, - }); - const container = document.getElementById("root") as HTMLElement; const root = createRoot(container); diff --git a/vite.config.ts b/vite.config.ts index b6b90406..b0201138 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -48,7 +48,4 @@ export default defineConfig({ 'Cross-Origin-Embedder-Policy': 'require-corp', } }, - optimizeDeps: { - exclude: ['react-scan'] - }, }); \ No newline at end of file From ba3d45584d07a01850897a49c48fc996a7a53210 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Wed, 19 Mar 2025 13:52:25 -0400 Subject: [PATCH 17/19] adding lock file --- deno.lock | 297 ++++++------------------------------------------------ 1 file changed, 32 insertions(+), 265 deletions(-) diff --git a/deno.lock b/deno.lock index db442d6d..46190061 100644 --- a/deno.lock +++ b/deno.lock @@ -56,7 +56,6 @@ "npm:react-hook-form@^7.54.2": "7.54.2_react@19.0.0", "npm:react-map-gl@8.0.1": "8.0.1_maplibre-gl@5.1.1_react@19.0.0_react-dom@19.0.0__react@19.0.0", "npm:react-qrcode-logo@3": "3.0.0_react@19.0.0_react-dom@19.0.0__react@19.0.0", - "npm:react-scan@~0.3.2": "0.3.2_react@19.0.0_react-dom@19.0.0__react@19.0.0_preact@10.26.4_@types+react@19.0.10", "npm:react@19": "19.0.0", "npm:rfc4648@^1.5.4": "1.5.4", "npm:simple-git-hooks@^2.11.1": "2.11.1", @@ -899,168 +898,78 @@ "tough-cookie" ] }, - "@clack/core@0.3.5": { - "integrity": "sha512-5cfhQNH+1VQ2xLQlmzXMqUoiaH0lRBq9/CLW9lTyMbuKLC3+xEK01tHVvyut++mLOn5urSHmkm6I0Lg9MaJSTQ==", - "dependencies": [ - "picocolors", - "sisteransi" - ] - }, - "@clack/prompts@0.8.2": { - "integrity": "sha512-6b9Ab2UiZwJYA9iMyboYyW9yJvAO9V753ZhS+DHKEjZRKAxPPOb7MXXu84lsPFG+vZt6FRFniZ8rXi+zCIw4yQ==", - "dependencies": [ - "@clack/core", - "picocolors", - "sisteransi" - ] - }, - "@esbuild/aix-ppc64@0.24.2": { - "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==" - }, "@esbuild/aix-ppc64@0.25.0": { "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==" }, - "@esbuild/android-arm64@0.24.2": { - "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==" - }, "@esbuild/android-arm64@0.25.0": { "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==" }, - "@esbuild/android-arm@0.24.2": { - "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==" - }, "@esbuild/android-arm@0.25.0": { "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==" }, - "@esbuild/android-x64@0.24.2": { - "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==" - }, "@esbuild/android-x64@0.25.0": { "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==" }, - "@esbuild/darwin-arm64@0.24.2": { - "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==" - }, "@esbuild/darwin-arm64@0.25.0": { "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==" }, - "@esbuild/darwin-x64@0.24.2": { - "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==" - }, "@esbuild/darwin-x64@0.25.0": { "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==" }, - "@esbuild/freebsd-arm64@0.24.2": { - "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==" - }, "@esbuild/freebsd-arm64@0.25.0": { "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==" }, - "@esbuild/freebsd-x64@0.24.2": { - "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==" - }, "@esbuild/freebsd-x64@0.25.0": { "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==" }, - "@esbuild/linux-arm64@0.24.2": { - "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==" - }, "@esbuild/linux-arm64@0.25.0": { "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==" }, - "@esbuild/linux-arm@0.24.2": { - "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==" - }, "@esbuild/linux-arm@0.25.0": { "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==" }, - "@esbuild/linux-ia32@0.24.2": { - "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==" - }, "@esbuild/linux-ia32@0.25.0": { "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==" }, - "@esbuild/linux-loong64@0.24.2": { - "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==" - }, "@esbuild/linux-loong64@0.25.0": { "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==" }, - "@esbuild/linux-mips64el@0.24.2": { - "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==" - }, "@esbuild/linux-mips64el@0.25.0": { "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==" }, - "@esbuild/linux-ppc64@0.24.2": { - "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==" - }, "@esbuild/linux-ppc64@0.25.0": { "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==" }, - "@esbuild/linux-riscv64@0.24.2": { - "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==" - }, "@esbuild/linux-riscv64@0.25.0": { "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==" }, - "@esbuild/linux-s390x@0.24.2": { - "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==" - }, "@esbuild/linux-s390x@0.25.0": { "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==" }, - "@esbuild/linux-x64@0.24.2": { - "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==" - }, "@esbuild/linux-x64@0.25.0": { "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==" }, - "@esbuild/netbsd-arm64@0.24.2": { - "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==" - }, "@esbuild/netbsd-arm64@0.25.0": { "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==" }, - "@esbuild/netbsd-x64@0.24.2": { - "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==" - }, "@esbuild/netbsd-x64@0.25.0": { "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==" }, - "@esbuild/openbsd-arm64@0.24.2": { - "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==" - }, "@esbuild/openbsd-arm64@0.25.0": { "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==" }, - "@esbuild/openbsd-x64@0.24.2": { - "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==" - }, "@esbuild/openbsd-x64@0.25.0": { "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==" }, - "@esbuild/sunos-x64@0.24.2": { - "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==" - }, "@esbuild/sunos-x64@0.25.0": { "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==" }, - "@esbuild/win32-arm64@0.24.2": { - "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==" - }, "@esbuild/win32-arm64@0.25.0": { "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==" }, - "@esbuild/win32-ia32@0.24.2": { - "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==" - }, "@esbuild/win32-ia32@0.25.0": { "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==" }, - "@esbuild/win32-x64@0.24.2": { - "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==" - }, "@esbuild/win32-x64@0.25.0": { "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==" }, @@ -1099,7 +1008,7 @@ "dependencies": [ "@inquirer/core", "@inquirer/type", - "@types/node@22.13.8" + "@types/node" ] }, "@inquirer/core@10.1.7_@types+node@22.13.8": { @@ -1107,7 +1016,7 @@ "dependencies": [ "@inquirer/figures", "@inquirer/type", - "@types/node@22.13.8", + "@types/node", "ansi-escapes", "cli-width", "mute-stream", @@ -1122,7 +1031,7 @@ "@inquirer/type@3.0.4_@types+node@22.13.8": { "integrity": "sha512-2MNFrDY8jkFYc9Il9DgLsHhMzuHnOYM1+CUYVWbzu9oT0hC7V7EcYvdCKeoll/Fcci04A+ERZ9wcc7cQ8lTkIA==", "dependencies": [ - "@types/node@22.13.8" + "@types/node" ] }, "@isaacs/cliui@8.0.2": { @@ -1305,29 +1214,12 @@ "@open-draft/until@2.1.0": { "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==" }, - "@pivanov/utils@0.0.2_react@19.0.0_react-dom@19.0.0__react@19.0.0": { - "integrity": "sha512-q9CN0bFWxWgMY5hVVYyBgez1jGiLBa6I+LkG37ycylPhFvEGOOeaADGtUSu46CaZasPnlY8fCdVJZmrgKb1EPA==", - "dependencies": [ - "react", - "react-dom" - ] - }, "@pkgjs/parseargs@0.11.0": { "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==" }, "@polka/url@1.0.0-next.28": { "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==" }, - "@preact/signals-core@1.8.0": { - "integrity": "sha512-OBvUsRZqNmjzCZXWLxkZfhcgT+Fk8DDcT/8vD6a1xhDemodyy87UJRJfASMuSD8FaAIeGgGm85ydXhm7lr4fyA==" - }, - "@preact/signals@1.3.2_preact@10.26.4": { - "integrity": "sha512-naxcJgUJ6BTOROJ7C3QML7KvwKwCXQJYTc5L/b0eEsdYgPB6SxwoQ1vDGcS0Q7GVjAenVq/tXrybVdFShHYZWg==", - "dependencies": [ - "@preact/signals-core", - "preact" - ] - }, "@radix-ui/number@1.1.0": { "integrity": "sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==" }, @@ -1920,14 +1812,6 @@ "rollup@2.79.2" ] }, - "@rollup/pluginutils@5.1.4": { - "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", - "dependencies": [ - "@types/estree@1.0.6", - "estree-walker@2.0.2", - "picomatch@4.0.2" - ] - }, "@rollup/pluginutils@5.1.4_rollup@2.79.2": { "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", "dependencies": [ @@ -3539,16 +3423,10 @@ "@types/pbf" ] }, - "@types/node@20.17.24": { - "integrity": "sha512-d7fGCyB96w9BnWQrOsJtpyiSaBcAYYr75bnK6ZRjDbql2cGLj/3GsL5OYmLPNq76l7Gf2q4Rv9J2o6h5CrD9sA==", - "dependencies": [ - "undici-types@6.19.8" - ] - }, "@types/node@22.13.8": { "integrity": "sha512-G3EfaZS+iOGYWLLRCEAXdWK9my08oHNZ+FHluRiggIYJPOXzhOiDgpVCUHaUvyIC5/fj7C/p637jdzC666AOKQ==", "dependencies": [ - "undici-types@6.20.0" + "undici-types" ] }, "@types/pbf@3.0.5": { @@ -3560,12 +3438,6 @@ "@types/react" ] }, - "@types/react-reconciler@0.28.9_@types+react@19.0.10": { - "integrity": "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==", - "dependencies": [ - "@types/react" - ] - }, "@types/react@19.0.10": { "integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==", "dependencies": [ @@ -3862,13 +3734,6 @@ "bignumber.js@9.1.2": { "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==" }, - "bippy@0.3.8_react@19.0.0_@types+react@19.0.10": { - "integrity": "sha512-0ou8fJWxUXK/+eRjUz5unbtX8Mrn0OYRs6QQwwUJtU6hsFDTSmSeI1fJC/2nrPA4G6GWcxwT+O6TbHyGhl4fEg==", - "dependencies": [ - "@types/react-reconciler", - "react" - ] - }, "bn.js@4.12.1": { "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==" }, @@ -4476,64 +4341,34 @@ "is-symbol" ] }, - "esbuild@0.24.2": { - "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", - "dependencies": [ - "@esbuild/aix-ppc64@0.24.2", - "@esbuild/android-arm@0.24.2", - "@esbuild/android-arm64@0.24.2", - "@esbuild/android-x64@0.24.2", - "@esbuild/darwin-arm64@0.24.2", - "@esbuild/darwin-x64@0.24.2", - "@esbuild/freebsd-arm64@0.24.2", - "@esbuild/freebsd-x64@0.24.2", - "@esbuild/linux-arm@0.24.2", - "@esbuild/linux-arm64@0.24.2", - "@esbuild/linux-ia32@0.24.2", - "@esbuild/linux-loong64@0.24.2", - "@esbuild/linux-mips64el@0.24.2", - "@esbuild/linux-ppc64@0.24.2", - "@esbuild/linux-riscv64@0.24.2", - "@esbuild/linux-s390x@0.24.2", - "@esbuild/linux-x64@0.24.2", - "@esbuild/netbsd-arm64@0.24.2", - "@esbuild/netbsd-x64@0.24.2", - "@esbuild/openbsd-arm64@0.24.2", - "@esbuild/openbsd-x64@0.24.2", - "@esbuild/sunos-x64@0.24.2", - "@esbuild/win32-arm64@0.24.2", - "@esbuild/win32-ia32@0.24.2", - "@esbuild/win32-x64@0.24.2" - ] - }, "esbuild@0.25.0": { "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", "dependencies": [ - "@esbuild/aix-ppc64@0.25.0", - "@esbuild/android-arm@0.25.0", - "@esbuild/android-arm64@0.25.0", - "@esbuild/android-x64@0.25.0", - "@esbuild/darwin-arm64@0.25.0", - "@esbuild/darwin-x64@0.25.0", - "@esbuild/freebsd-arm64@0.25.0", - "@esbuild/freebsd-x64@0.25.0", - "@esbuild/linux-arm@0.25.0", - "@esbuild/linux-arm64@0.25.0", - "@esbuild/linux-ia32@0.25.0", - "@esbuild/linux-loong64@0.25.0", - "@esbuild/linux-mips64el@0.25.0", - "@esbuild/linux-ppc64@0.25.0", - "@esbuild/linux-riscv64@0.25.0", - "@esbuild/linux-s390x@0.25.0", - "@esbuild/linux-x64@0.25.0", - "@esbuild/netbsd-arm64@0.25.0", - "@esbuild/netbsd-x64@0.25.0", - "@esbuild/openbsd-arm64@0.25.0", - "@esbuild/openbsd-x64@0.25.0", - "@esbuild/sunos-x64@0.25.0", - "@esbuild/win32-arm64@0.25.0", - "@esbuild/win32-ia32@0.25.0", - "@esbuild/win32-x64@0.25.0" + "@esbuild/aix-ppc64", + "@esbuild/android-arm", + "@esbuild/android-arm64", + "@esbuild/android-x64", + "@esbuild/darwin-arm64", + "@esbuild/darwin-x64", + "@esbuild/freebsd-arm64", + "@esbuild/freebsd-x64", + "@esbuild/linux-arm", + "@esbuild/linux-arm64", + "@esbuild/linux-ia32", + "@esbuild/linux-loong64", + "@esbuild/linux-mips64el", + "@esbuild/linux-ppc64", + "@esbuild/linux-riscv64", + "@esbuild/linux-s390x", + "@esbuild/linux-x64", + "@esbuild/netbsd-arm64", + "@esbuild/netbsd-x64", + "@esbuild/openbsd-arm64", + "@esbuild/openbsd-x64", + "@esbuild/sunos-x64", + "@esbuild/win32-arm64", + "@esbuild/win32-ia32", + "@esbuild/win32-x64" ] }, "escalade@3.2.0": { @@ -4719,12 +4554,6 @@ "get-intrinsic" ] }, - "get-tsconfig@4.10.0": { - "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==", - "dependencies": [ - "resolve-pkg-maps" - ] - }, "get-value@2.0.6": { "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==" }, @@ -5157,9 +4986,6 @@ "kind-of@6.0.3": { "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" }, - "kleur@4.1.5": { - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==" - }, "leven@3.1.0": { "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==" }, @@ -5358,9 +5184,6 @@ "mkdirp@3.0.1": { "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==" }, - "mri@1.2.0": { - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==" - }, "mrmime@2.0.1": { "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==" }, @@ -5625,9 +5448,6 @@ "potpack@2.0.0": { "integrity": "sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw==" }, - "preact@10.26.4": { - "integrity": "sha512-KJhO7LBFTjP71d83trW+Ilnjbo+ySsaAgCfXOXUlmGzJ4ygYPWmysm77yg4emwfmoz3b22yvH5IsVFHbhUaH5w==" - }, "pretty-bytes@5.6.0": { "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==" }, @@ -5795,31 +5615,6 @@ "use-sidecar" ] }, - "react-scan@0.3.2_react@19.0.0_react-dom@19.0.0__react@19.0.0_preact@10.26.4_@types+react@19.0.10": { - "integrity": "sha512-lJb/klITqM95+MT+tR4nhfMl+ApXUeQ4iH0A0CsKYKkc66Wu01bOv+SxzZR1ksIPUWon7m2S3/83maZENtLEAQ==", - "dependencies": [ - "@babel/core", - "@babel/generator", - "@babel/types", - "@clack/core", - "@clack/prompts", - "@pivanov/utils", - "@preact/signals", - "@rollup/pluginutils@5.1.4", - "@types/node@20.17.24", - "bippy", - "esbuild@0.24.2", - "estree-walker@3.0.3", - "kleur", - "mri", - "playwright", - "preact", - "react", - "react-dom", - "tsx", - "unplugin" - ] - }, "react-style-singleton@2.2.3_@types+react@19.0.10_react@19.0.0": { "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", "dependencies": [ @@ -5930,9 +5725,6 @@ "requires-port@1.0.0": { "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, - "resolve-pkg-maps@1.0.0": { - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==" - }, "resolve-protobuf-schema@2.1.0": { "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==", "dependencies": [ @@ -6171,9 +5963,6 @@ "totalist" ] }, - "sisteransi@1.0.5": { - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" - }, "skmeans@0.9.7": { "integrity": "sha512-hNj1/oZ7ygsfmPZ7ZfN5MUBRoGg1gtpnImuJBgLO0ljQ67DtJuiQaiYdS4lUA6s0KCwnPhGivtC/WRwIZLkHyg==" }, @@ -6534,14 +6323,6 @@ "tslog@4.9.3": { "integrity": "sha512-oDWuGVONxhVEBtschLf2cs/Jy8i7h1T+CpdkTNWQgdAF7DhRo2G8vMCgILKe7ojdEkLhICWgI1LYSSKaJsRgcw==" }, - "tsx@4.19.3": { - "integrity": "sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==", - "dependencies": [ - "esbuild@0.25.0", - "fsevents@2.3.3", - "get-tsconfig" - ] - }, "tty-browserify@0.0.1": { "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==" }, @@ -6619,9 +6400,6 @@ "which-boxed-primitive" ] }, - "undici-types@6.19.8": { - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" - }, "undici-types@6.20.0": { "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==" }, @@ -6662,13 +6440,6 @@ "universalify@2.0.1": { "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==" }, - "unplugin@2.1.0": { - "integrity": "sha512-us4j03/499KhbGP8BU7Hrzrgseo+KdfJYWcbcajCOqsAyb8Gk0Yn2kiUIcZISYCb1JFaZfIuG3b42HmguVOKCQ==", - "dependencies": [ - "acorn", - "webpack-virtual-modules" - ] - }, "upath@1.2.0": { "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==" }, @@ -6765,8 +6536,8 @@ "vite@6.2.0_@types+node@22.13.8": { "integrity": "sha512-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ==", "dependencies": [ - "@types/node@22.13.8", - "esbuild@0.25.0", + "@types/node", + "esbuild", "fsevents@2.3.3", "postcss", "rollup@4.34.9" @@ -6775,7 +6546,7 @@ "vitest@3.0.8_@types+node@22.13.8_happy-dom@17.2.2_vite@6.2.0__@types+node@22.13.8_@vitest+browser@3.0.8__playwright@1.50.1__vitest@3.0.8___@types+node@22.13.8___happy-dom@17.2.2___@vitest+browser@3.0.8____playwright@1.50.1____vitest@3.0.8____msw@2.7.3_____typescript@5.8.2_____@types+node@22.13.8____vite@6.2.0_____@types+node@22.13.8____typescript@5.8.2____@types+node@22.13.8____happy-dom@17.2.2___playwright@1.50.1___vite@6.2.0____@types+node@22.13.8___typescript@5.8.2__vitest@3.0.8__typescript@5.8.2__msw@2.7.3___typescript@5.8.2___@types+node@22.13.8__vite@6.2.0___@types+node@22.13.8__@types+node@22.13.8_playwright@1.50.1_typescript@5.8.2": { "integrity": "sha512-dfqAsNqRGUc8hB9OVR2P0w8PZPEckti2+5rdZip0WIz9WW0MnImJ8XiR61QhqLa92EQzKP2uPkzenKOAHyEIbA==", "dependencies": [ - "@types/node@22.13.8", + "@types/node", "@vitest/browser", "@vitest/expect", "@vitest/mocker@3.0.8_vite@6.2.0__@types+node@22.13.8_@types+node@22.13.8_msw@2.7.3__typescript@5.8.2__@types+node@22.13.8_typescript@5.8.2", @@ -6817,9 +6588,6 @@ "webidl-conversions@7.0.0": { "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" }, - "webpack-virtual-modules@0.6.2": { - "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==" - }, "whatwg-mimetype@3.0.0": { "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==" }, @@ -7169,7 +6937,6 @@ "npm:react-hook-form@^7.54.2", "npm:react-map-gl@8.0.1", "npm:react-qrcode-logo@3", - "npm:react-scan@~0.3.2", "npm:react@19", "npm:rfc4648@^1.5.4", "npm:simple-git-hooks@^2.11.1", From 28cc7b9800f0620598ce7eb22a07eb2786474365 Mon Sep 17 00:00:00 2001 From: bkimmel Date: Wed, 19 Mar 2025 17:50:20 -0400 Subject: [PATCH 18/19] reorder columns in Nodes page --- src/pages/Nodes.tsx | 90 +++++++++++++++++++++++---------------------- 1 file changed, 47 insertions(+), 43 deletions(-) diff --git a/src/pages/Nodes.tsx b/src/pages/Nodes.tsx index c0a9e6f5..a5c33c7b 100644 --- a/src/pages/Nodes.tsx +++ b/src/pages/Nodes.tsx @@ -19,12 +19,17 @@ export interface DeleteNoteDialogProps { onOpenChange: (open: boolean) => void; } -function shortNameFromNode(node: ReturnType["nodes"][number]): string { - const shortNameOfNode = node.user?.shortName ?? (node.user?.macaddr - ? `${base16 - .stringify(node.user?.macaddr.subarray(4, 6) ?? []) - .toLowerCase()}` - : `${numberToHexUnpadded(node.num).slice(-4)}`); +function shortNameFromNode( + node: ReturnType["nodes"][number], +): string { + const shortNameOfNode = node.user?.shortName ?? + (node.user?.macaddr + ? `${ + base16 + .stringify(node.user?.macaddr.subarray(4, 6) ?? []) + .toLowerCase() + }` + : `${numberToHexUnpadded(node.num).slice(-4)}`); return String(shortNameOfNode); } @@ -72,7 +77,6 @@ const NodesPage = (): JSX.Element => { }; }, [connection]); - const handleLocation = useCallback( (location: Types.PacketMetadata) => { if (location.to.valueOf() !== hardware.myNodeNum) return; @@ -99,12 +103,12 @@ const NodesPage = (): JSX.Element => { headings={[ { title: "", type: "blank", sortable: false }, { title: "Long Name", type: "normal", sortable: true }, - { title: "Model", type: "normal", sortable: true }, - { title: "MAC Address", type: "normal", sortable: true }, + { title: "Connection", type: "normal", sortable: true }, { title: "Last Heard", type: "normal", sortable: true }, - { title: "SNR", type: "normal", sortable: true }, { title: "Encryption", type: "normal", sortable: false }, - { title: "Connection", type: "normal", sortable: true }, + { title: "SNR", type: "normal", sortable: true }, + { title: "Model", type: "normal", sortable: true }, + { title: "MAC Address", type: "normal", sortable: true }, ]} rows={filteredNodes.map((node) => [
    @@ -113,55 +117,55 @@ const NodesPage = (): JSX.Element => {

    setSelectedNode(node)} - onKeyUp={(evt)=>{ evt.key === "Enter" && setSelectedNode(node) }} + onKeyUp={(evt) => { + evt.key === "Enter" && setSelectedNode(node); + }} className="cursor-pointer underline" tabIndex={0} role="button" > {node.user?.longName ?? (node.user?.macaddr - ? `Meshtastic ${base16 - .stringify(node.user?.macaddr.subarray(4, 6) ?? []) - .toLowerCase()}` + ? `Meshtastic ${ + base16 + .stringify(node.user?.macaddr.subarray(4, 6) ?? []) + .toLowerCase() + }` : `!${numberToHexUnpadded(node.num)}`)}

    , - - - {Protobuf.Mesh.HardwareModel[node.user?.hwModel ?? 0]} - , - - {base16 - .stringify(node.user?.macaddr ?? []) - .match(/.{1,2}/g) - ?.join(":") ?? "UNK"} + + {node.lastHeard !== 0 + ? node.viaMqtt === false && node.hopsAway === 0 + ? "Direct" + : `${node.hopsAway?.toString()} ${ + node.hopsAway > 1 ? "hops" : "hop" + } away` + : "-"} + {node.viaMqtt === true ? ", via MQTT" : ""} , - {node.lastHeard === 0 ? ( -

    Never

    - ) : ( - - )} + {node.lastHeard === 0 + ?

    Never

    + : } +
    , + + {node.user?.publicKey && node.user?.publicKey.length > 0 + ? + : } , {node.snr}db/ {Math.min(Math.max((node.snr + 10) * 5, 0), 100)}%/ {(node.snr + 10) * 5}raw , - - {node.user?.publicKey && node.user?.publicKey.length > 0 ? ( - - ) : ( - - )} + + {Protobuf.Mesh.HardwareModel[node.user?.hwModel ?? 0]} , - - {node.lastHeard !== 0 - ? node.viaMqtt === false && node.hopsAway === 0 - ? "Direct" - : `${node.hopsAway?.toString()} ${node.hopsAway > 1 ? "hops" : "hop" - } away` - : "-"} - {node.viaMqtt === true ? ", via MQTT" : ""} + + {base16 + .stringify(node.user?.macaddr ?? []) + .match(/.{1,2}/g) + ?.join(":") ?? "UNK"} , ])} /> From 6341d564d323c32567d4c1175307a7656e356dba Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Thu, 20 Mar 2025 12:06:21 -0400 Subject: [PATCH 19/19] feat: update readme with domain changes --- README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/README.md b/README.md index a6e50189..7bd44dc5 100644 --- a/README.md +++ b/README.md @@ -138,3 +138,29 @@ reasons: environments. - **Web Standard APIs**: Uses browser-compatible APIs, making code more portable between server and client environments. + +### Contributing + +We welcome contributions! Here’s how the deployment flow works for pull +requests: + +- **Preview Deployments:**\ + Every pull request automatically generates a preview deployment on Vercel. + This allows you and reviewers to easily preview changes before merging. + +- **Staging Environment (`client-test`):**\ + Once your PR is merged, your changes will be available on our staging site: + [client-test.meshtastic.org](https://client-test.meshtastic.org/).\ + This environment supports rapid feature iteration and testing without + impacting the production site. + +- **Production Releases:**\ + At regular intervals, stable and fully tested releases are promoted to our + production site: [client.meshtastic.org](https://client.meshtastic.org/).\ + This is the primary interface used by the public to connect with their + Meshtastic nodes. + +Please review our +[Contribution Guidelines](https://github.com/meshtastic/web/blob/master/CONTRIBUTING.md) +before submitting a pull request. We appreciate your help in making the project +better!