feat(protobufs): sync to firmware-current and consume workspace package (#1097)

* feat(protobufs): sync to firmware-current and consume workspace package

Sync the vendored .proto sources to firmware-current (v2.7.25+48), regenerate the v2 TS bindings, and consume the workspace @meshtastic/protobufs (workspace:*) in place of the stale JSR 2.7.20 — finishing the monorepo migration (core was already workspace:*).

Includes the one required breaking-change fix: admin nodedb_reset changed int32 to bool, so resetNodes() now sends value: true.

* build(protobufs): vendor generated bindings for workspace consumers

The package is consumed via workspace:* — its exports point at the TS source, which imports ./dist/meshtastic/*_pb.ts — so the generated output must exist at build time. CI builds web/core with no codegen step and the runners have no buf CLI, so the bindings are vendored here (kept gitignored; lint/format skip them). Regenerate with: pnpm --filter @meshtastic/protobufs gen

* fix(protobufs): clean script removes the actual generated output dir

buf writes bindings to packages/ts/dist, but clean was removing a non-existent root dist — so it never cleaned stale output. Addresses Copilot review feedback.

* refactor: move web app packages/web -> apps/web

Aligns the web app with the apps/web layout (matching the Vercel web-test Root Directory and the SDK-migration direction). Pure directory move plus root config: pnpm-workspace (adds apps/*), vitest projects, root tsconfig reference, and the pr/release-web/nightly workflows. vercel.json moved with the app. Build + 36 validation tests green.

* feat: config fields, module pages, key verification, telemetry capture

Incorporates the firmware-current feature work onto the protobuf foundation: new config fields (Display message bubbles; LoRa fem_lna_mode + serial_hal_only; Telemetry air_quality_screen_enabled); 4 new ModuleConfig pages (TrafficManagement, StatusMessage, TAK, RemoteHardware); the manual Key Verification flow (sendKeyVerification + ClientNotificationDialog stages + Verify Key button); live telemetry capture (nodeDB addDeviceMetrics) and admin hardening (toggleMutedNode, graceful PortNum default); plus the sdk-preview ConfigEditor demo and store/config tests. Build + lint + format + 131 tests green.

* chore: drop #1062 (unsaved-change-detection) to match upstream revert

#1062 was merged to main by accident (per @danditomaso) and is being reverted. Reverse-applied its diff here via 3-way so #1097 stays consistent with where main is headed, while keeping the feature changes layered on the same files (deviceStore/changeRegistry). Build + 131 tests + lint + format green.

* fix(nodes): clean up SNR display in node table and map popup

SNR is a ratio measured in dB, not dBm (which is absolute power); the
node table and map popup both mislabeled it and crammed three values
together: '0dBm/50%/50raw'. The trailing '%/raw' pair was the same
heuristic ((snr+10)*5) shown twice — once clamped, once not.

Render SNR in dB rounded to one decimal, color-coded by a 0-100%
signal-quality heuristic (green/yellow/red), with the quality percentage
as a muted secondary. Drop the redundant raw value. Adds unit.db; this
matches the existing SNRTooltip, which already renders dB.
This commit is contained in:
Ben Meadors
2026-06-15 13:23:28 -05:00
committed by GitHub
parent 7a8529b841
commit bbe9a0d5cd
695 changed files with 27038 additions and 786 deletions
@@ -0,0 +1,180 @@
import type { HeatmapMode } from "@components/PageComponents/Map/Layers/HeatmapLayer.tsx";
import { Checkbox } from "@components/UI/Checkbox/index.tsx";
import { Popover, PopoverContent, PopoverTrigger } from "@components/UI/Popover.tsx";
import { cn } from "@core/utils/cn.ts";
import { LayersIcon } from "lucide-react";
import { type ReactNode, useMemo } from "react";
import { useTranslation } from "react-i18next";
export interface VisibilityState {
nodeMarkers: boolean;
directNeighbors: boolean;
remoteNeighbors: boolean;
positionPrecision: boolean;
traceroutes: boolean;
waypoints: boolean;
heatmap: boolean;
}
export const defaultVisibilityState: VisibilityState = {
nodeMarkers: true,
directNeighbors: false,
remoteNeighbors: false,
positionPrecision: false,
traceroutes: false,
waypoints: true,
heatmap: false,
};
interface MapLayerToolProps {
visibilityState: VisibilityState;
setVisibilityState: (state: VisibilityState) => void;
heatmapMode: HeatmapMode;
setHeatmapMode: (mode: HeatmapMode) => void;
}
interface CheckboxProps {
label: string;
checked: boolean;
onChange: (checked: boolean) => void;
className?: string;
}
const CheckboxItem = ({ label, checked, onChange, className }: CheckboxProps) => {
return (
<Checkbox
checked={checked}
onChange={onChange}
className={cn("flex items-center gap-2", className)}
>
<span className="dark:text-slate-200">{label}</span>
</Checkbox>
);
};
export function MapLayerTool({
visibilityState,
setVisibilityState,
heatmapMode,
setHeatmapMode,
}: MapLayerToolProps): ReactNode {
const { t } = useTranslation("map");
const enabledCount = useMemo(() => {
return Object.values(visibilityState).filter(Boolean).length;
}, [visibilityState]);
const handleCheckboxChange = (key: keyof VisibilityState) => {
if (key === "heatmap" && !visibilityState.heatmap) {
// If turning heatmap on, turn everything else off so the layer is visible
setVisibilityState({
nodeMarkers: false,
directNeighbors: false,
remoteNeighbors: false,
positionPrecision: false,
traceroutes: false,
waypoints: false,
heatmap: true,
});
} else {
setVisibilityState({
...visibilityState,
[key]: !visibilityState[key],
});
}
};
const layers = useMemo(
() => [
{ key: "nodeMarkers", label: t("layerTool.nodeMarkers") },
{ key: "waypoints", label: t("layerTool.waypoints") },
{ key: "directNeighbors", label: t("layerTool.directNeighbors") },
{ key: "remoteNeighbors", label: t("layerTool.remoteNeighbors") },
{ key: "positionPrecision", label: t("layerTool.positionPrecision") },
{ key: "heatmap", label: t("layerTool.heatmap") },
// { key: "traceroutes", label: t("layerTool.traceroutes") },
],
[t],
);
return (
<Popover>
<PopoverTrigger asChild>
<button
type="button"
className={cn(
"relative rounded align-center",
"w-[29px] px-1 py-1 shadow-l outline-[2px] outline-stone-600/20",
"bg-stone-50 hover:bg-stone-200 dark:bg-stone-200 dark:hover:bg-stone-300 ",
"text-slate-600 hover:text-slate-700 active:bg-slate-300",
"dark:text-slate-600 hover:dark:text-slate-700",
)}
aria-label={t("mapMenu.layersAria")}
>
<LayersIcon className="w-[21px]" />
{enabledCount > 0 && (
<span
className={cn(
"absolute -bottom-2 -right-2",
"min-w-4 h-4 px-[3px]",
"rounded-full text-[10px] leading-4",
"bg-blue-500 text-white",
"flex items-center justify-center",
"ring-2 ring-white dark:ring-stone-200",
)}
aria-hidden="true"
>
{enabledCount}
</span>
)}
</button>
</PopoverTrigger>
<PopoverContent
className={cn("dark:text-slate-300 flex flex-col space-y-2 py-4")}
side={"bottom"}
align="end"
sideOffset={7}
>
{layers.map(({ key, label }) => (
<div key={key}>
<CheckboxItem
label={label}
checked={visibilityState[key as keyof VisibilityState]}
onChange={() => handleCheckboxChange(key as keyof VisibilityState)}
/>
{key === "heatmap" && visibilityState.heatmap && (
<div className="pl-6 pt-2 flex flex-col gap-1">
<label className="flex items-center gap-2 cursor-pointer">
<input
type="radio"
className="accent-blue-500"
checked={heatmapMode === "density"}
onChange={() => setHeatmapMode("density")}
/>
<span className="text-sm dark:text-slate-300">{t("layerTool.density")}</span>
</label>
<label className="flex items-center gap-2 cursor-pointer">
<input
type="radio"
className="accent-blue-500"
checked={heatmapMode === "snr"}
onChange={() => setHeatmapMode("snr")}
/>
<span className="text-sm dark:text-slate-300">SNR</span>
</label>
</div>
)}
</div>
))}
{/*<CheckboxItem
key="traceroutes"
label={t("layerTool.traceroutes")}
checked={visibilityState.traceroutes}
onChange={(checked) => {
setVisibilityState({ ...visibilityState, traceroutes: checked });
}}
/>*/}
</PopoverContent>
</Popover>
);
}