From 90ad5f8b120c8997fc65e00447a263033a801db6 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Tue, 21 Apr 2026 15:38:14 -0600 Subject: [PATCH 01/30] new component type --- package-lock.json | 4 +- package.json | 2 +- src/bin/BinVisualizerV2.tsx | 2 +- src/pbHelpers/ComponentType.tsx | 6 +- .../ComponentTypes/AnalogPressure.ts | 88 +++++++++++++++++++ src/pbHelpers/ComponentTypes/index.ts | 1 + src/products/MiVent/MiVentAvailability.ts | 4 +- 7 files changed, 100 insertions(+), 7 deletions(-) create mode 100644 src/pbHelpers/ComponentTypes/AnalogPressure.ts diff --git a/package-lock.json b/package-lock.json index 2cd53e8..0e54b33 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,7 +43,7 @@ "mui-tel-input": "^7.0.0", "notistack": "^3.0.1", "openweathermap-ts": "^1.2.10", - "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#staging", + "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#dev", "query-string": "^9.2.1", "react": "^18.3.1", "react-beautiful-dnd": "^13.1.1", @@ -11205,7 +11205,7 @@ }, "node_modules/protobuf-ts": { "version": "1.0.0", - "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#9c0f668d4a56b8216dd71a44c3110a818aaf3541", + "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#b44f5fa7183d9565e9fd9e7a9ac756114401ddfd", "dependencies": { "protobufjs": "^6.8.8" } diff --git a/package.json b/package.json index 90ac71b..ba69f2e 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "mui-tel-input": "^7.0.0", "notistack": "^3.0.1", "openweathermap-ts": "^1.2.10", - "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#staging", + "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#dev", "query-string": "^9.2.1", "react": "^18.3.1", "react-beautiful-dnd": "^13.1.1", diff --git a/src/bin/BinVisualizerV2.tsx b/src/bin/BinVisualizerV2.tsx index f6f9c1d..437f09d 100644 --- a/src/bin/BinVisualizerV2.tsx +++ b/src/bin/BinVisualizerV2.tsx @@ -1539,7 +1539,7 @@ export default function BinVisualizer(props: Props) { ) : ( - + {fullScreenHandler.enter()}}> )} diff --git a/src/pbHelpers/ComponentType.tsx b/src/pbHelpers/ComponentType.tsx index 047c852..c31154b 100644 --- a/src/pbHelpers/ComponentType.tsx +++ b/src/pbHelpers/ComponentType.tsx @@ -39,7 +39,8 @@ import { Voltage, VPD, Weight, - CapacitorCable + CapacitorCable, + AnalogPressure } from "pbHelpers/ComponentTypes"; //import { multilineGrainCableData } from "pbHelpers/ComponentTypes/GrainCable"; //import { multilineCapCableData } from "./ComponentTypes/CapacitorCable"; @@ -96,7 +97,8 @@ const COMPONENT_TYPE_MAP = new Map([ [quack.ComponentType.COMPONENT_TYPE_SEN5X, Sen5x], [quack.ComponentType.COMPONENT_TYPE_VIBRATION_CHAIN, VibrationCable], [quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE, dragerGasDongle], - [quack.ComponentType.COMPONENT_TYPE_AIRFLOW, Airflow] + [quack.ComponentType.COMPONENT_TYPE_AIRFLOW, Airflow], + [quack.ComponentType.COMPONENT_TYPE_ANALOG_PRESSURE, AnalogPressure] ]); export interface Subtype { diff --git a/src/pbHelpers/ComponentTypes/AnalogPressure.ts b/src/pbHelpers/ComponentTypes/AnalogPressure.ts new file mode 100644 index 0000000..f4d3936 --- /dev/null +++ b/src/pbHelpers/ComponentTypes/AnalogPressure.ts @@ -0,0 +1,88 @@ +import { + ComponentTypeExtension, + Summary, + Subtype, + simpleMeasurements, + simpleSummaries, + unitMeasurementSummaries, + AreaChartData, + GraphFilters, + simpleAreaChartData, + LineChartData, + simpleLineChartData + } from "pbHelpers/ComponentType"; + import PressureDarkIcon from "assets/components/pressureDark.png"; + import PressureLightIcon from "assets/components/pressureLight.png"; + import { quack } from "protobuf-ts/quack"; + import { describeMeasurement } from "pbHelpers/MeasurementDescriber"; + import { convertedUnitMeasurement } from "models/UnitMeasurement"; + import { pond } from "protobuf-ts/pond"; + + export function AnalogPressure(subtype: number = 0): ComponentTypeExtension { + let pressure = describeMeasurement( + quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE, + quack.ComponentType.COMPONENT_TYPE_ANALOG_PRESSURE, + subtype + ); + let voltage = describeMeasurement( + quack.MeasurementType.MEASUREMENT_TYPE_VOLTAGE, + quack.ComponentType.COMPONENT_TYPE_ANALOG_PRESSURE, + subtype + ) + let addressTypes = [quack.AddressType.ADDRESS_TYPE_I2C]; + return { + type: quack.ComponentType.COMPONENT_TYPE_PRESSURE, + subtypes: [ + { + key: quack.AnalogPressureSubtype.ANALOG_PRESSURE_SUBTYPE_PMC21, + value: "ANALOG_PRESSURE_SUBTYPE_PMC21", + friendlyName: "PMC 21" + } as Subtype + ], + friendlyName: "Analog Pressure", + description: "Measures the atmospheric pressure or absolute pressure", + isController: false, + isSource: true, + isCalibratable: true, + hasFan: true, + addressTypes: addressTypes, + interactionResultTypes: [], + states: [], + measurements: simpleMeasurements(pressure, voltage), + measurementSummary: async function(measurement: quack.Measurement): Promise> { + return simpleSummaries(measurement, pressure); + }, + unitMeasurementSummary: ( + measurements: convertedUnitMeasurement, + ): Summary[] => { + return unitMeasurementSummaries( + measurements, + quack.ComponentType.COMPONENT_TYPE_PRESSURE, + subtype, + ); + }, + areaChartData: ( + measurement: pond.UnitMeasurementsForComponent, + smoothingAverages?: number, + filters?: GraphFilters + ): AreaChartData => { + return simpleAreaChartData(measurement, smoothingAverages, filters); + }, + lineChartData: ( + measurement: pond.UnitMeasurementsForComponent, + smoothingAverages?: number, + filters?: GraphFilters + ): LineChartData => { + return simpleLineChartData( + quack.ComponentType.COMPONENT_TYPE_PRESSURE, + measurement, + smoothingAverages, + filters + ); + }, + minMeasurementPeriodMs: 1000, + icon: (theme?: "light" | "dark"): string | undefined => { + return theme === "light" ? PressureDarkIcon : PressureLightIcon; + } + }; + } \ No newline at end of file diff --git a/src/pbHelpers/ComponentTypes/index.ts b/src/pbHelpers/ComponentTypes/index.ts index 349bbbe..39b1560 100644 --- a/src/pbHelpers/ComponentTypes/index.ts +++ b/src/pbHelpers/ComponentTypes/index.ts @@ -29,3 +29,4 @@ export * from "./Voltage"; export * from "./VPD"; export * from "./Weight"; export * from "./CapacitorCable"; +export * from "./AnalogPressure"; diff --git a/src/products/MiVent/MiVentAvailability.ts b/src/products/MiVent/MiVentAvailability.ts index 0108806..87d0db2 100644 --- a/src/products/MiVent/MiVentAvailability.ts +++ b/src/products/MiVent/MiVentAvailability.ts @@ -10,6 +10,7 @@ const MiVentV1Pins: ConfigurablePin[] = [ { address: 1024, label: "C2" } ]; +//no longer in development so if you are adding new component types DO NOT add the I2C address here because the firmware of the device will not support it export const MiVentV1Availability: DeviceAvailabilityMap = new Map< quack.AddressType, DevicePositions @@ -53,7 +54,8 @@ export const MiVentV2Availability: DeviceAvailabilityMap = new Map< [quack.ComponentType.COMPONENT_TYPE_DHT, [0x40]], [quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]], [quack.ComponentType.COMPONENT_TYPE_AIRFLOW, [0x6c]], - [quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE, [0x6d]] + [quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE, [0x6d]], + [quack.ComponentType.COMPONENT_TYPE_ANALOG_PRESSURE, [0x6e]] ]) ], From c0a3e23d678b7e8e89d635ee8d528848ebde7bb9 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Tue, 21 Apr 2026 16:13:59 -0600 Subject: [PATCH 02/30] missed the device availability stuff --- src/pbHelpers/DeviceAvailability.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pbHelpers/DeviceAvailability.ts b/src/pbHelpers/DeviceAvailability.ts index 972c2e2..da39022 100644 --- a/src/pbHelpers/DeviceAvailability.ts +++ b/src/pbHelpers/DeviceAvailability.ts @@ -64,7 +64,8 @@ const DefaultAvailability: DeviceAvailabilityMap = new Map Date: Fri, 12 Jun 2026 14:43:37 -0600 Subject: [PATCH 03/30] changing some naming for the component --- package-lock.json | 2 +- src/pbHelpers/ComponentTypes/AnalogPressure.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0e54b33..1f15d16 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11205,7 +11205,7 @@ }, "node_modules/protobuf-ts": { "version": "1.0.0", - "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#b44f5fa7183d9565e9fd9e7a9ac756114401ddfd", + "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#807316f6e3473e9416e2366c6081d5a93245d9d9", "dependencies": { "protobufjs": "^6.8.8" } diff --git a/src/pbHelpers/ComponentTypes/AnalogPressure.ts b/src/pbHelpers/ComponentTypes/AnalogPressure.ts index f4d3936..7921b38 100644 --- a/src/pbHelpers/ComponentTypes/AnalogPressure.ts +++ b/src/pbHelpers/ComponentTypes/AnalogPressure.ts @@ -34,9 +34,9 @@ import { type: quack.ComponentType.COMPONENT_TYPE_PRESSURE, subtypes: [ { - key: quack.AnalogPressureSubtype.ANALOG_PRESSURE_SUBTYPE_PMC21, - value: "ANALOG_PRESSURE_SUBTYPE_PMC21", - friendlyName: "PMC 21" + key: quack.AnalogPressureSubtype.ANALOG_PRESSURE_SUBTYPE_PM1618, + value: "ANALOG_PRESSURE_SUBTYPE_PM1618", + friendlyName: "PM 1618" } as Subtype ], friendlyName: "Analog Pressure", From 1051534bb59c5a847c06db5d60a73aee41519744 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Fri, 12 Jun 2026 14:58:30 -0600 Subject: [PATCH 04/30] proto update to stagings proto --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1f15d16..37bd044 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,7 +43,7 @@ "mui-tel-input": "^7.0.0", "notistack": "^3.0.1", "openweathermap-ts": "^1.2.10", - "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#dev", + "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#staging", "query-string": "^9.2.1", "react": "^18.3.1", "react-beautiful-dnd": "^13.1.1", @@ -11205,7 +11205,7 @@ }, "node_modules/protobuf-ts": { "version": "1.0.0", - "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#807316f6e3473e9416e2366c6081d5a93245d9d9", + "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#6d889de1a42adf590373ff932823e5e697634c6a", "dependencies": { "protobufjs": "^6.8.8" } diff --git a/package.json b/package.json index ba69f2e..90ac71b 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "mui-tel-input": "^7.0.0", "notistack": "^3.0.1", "openweathermap-ts": "^1.2.10", - "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#dev", + "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#staging", "query-string": "^9.2.1", "react": "^18.3.1", "react-beautiful-dnd": "^13.1.1", From c4999a0ba5e7cc3de81680c9ba92abefa253b30f Mon Sep 17 00:00:00 2001 From: Carter Date: Tue, 16 Jun 2026 12:32:43 -0600 Subject: [PATCH 05/30] adding control emails to control component settings form --- package-lock.json | 4 +- package.json | 2 +- src/component/ComponentForm.tsx | 84 ++++++++++- src/component/ComponentSettings.tsx | 223 ++++++++++++++++++++++++---- 4 files changed, 275 insertions(+), 38 deletions(-) diff --git a/package-lock.json b/package-lock.json index 58133fb..4d78d05 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,7 +43,7 @@ "mui-tel-input": "^7.0.0", "notistack": "^3.0.1", "openweathermap-ts": "^1.2.10", - "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#staging", + "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#control_emails", "query-string": "^9.2.1", "react": "^18.3.1", "react-beautiful-dnd": "^13.1.1", @@ -11198,7 +11198,7 @@ }, "node_modules/protobuf-ts": { "version": "1.0.0", - "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#29d9765ce93c04ad70d795b80e9873fc2fc5f600", + "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#4011e9e7a75148a195e7311c41a4e6c8d6886d7b", "dependencies": { "protobufjs": "^6.8.8" } diff --git a/package.json b/package.json index cd2f476..daa71c4 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "mui-tel-input": "^7.0.0", "notistack": "^3.0.1", "openweathermap-ts": "^1.2.10", - "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#master", + "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#control_emails", "query-string": "^9.2.1", "react": "^18.3.1", "react-beautiful-dnd": "^13.1.1", diff --git a/src/component/ComponentForm.tsx b/src/component/ComponentForm.tsx index 17c1bf7..dbebbb5 100644 --- a/src/component/ComponentForm.tsx +++ b/src/component/ComponentForm.tsx @@ -19,9 +19,10 @@ import { Tooltip, Typography } from "@mui/material"; -import { +import { ExpandMore, - Close as CloseIcon + Close as CloseIcon, + Remove as RemoveIcon } from "@mui/icons-material"; import PeriodSelect from "common/time/PeriodSelect"; import SearchSelect, { Option } from "common/SearchSelect"; @@ -97,6 +98,7 @@ interface Props { component: Component; componentChanged: (component: Component, formValid: boolean) => void; canEdit: boolean; + hideControllerFields?: boolean; } interface ComponentData { @@ -119,7 +121,7 @@ export default function ComponentForm(props: Props) { const classes = useStyles(); const grainOptions = GrainOptions(); const [{ user }] = useGlobalState(); - const { device, component, componentChanged, canEdit } = props; + const { device, component, componentChanged, canEdit, hideControllerFields } = props; const [reportExpanded, setReportExpanded] = useState(false); const [dataUsageWarningDismissed, setDataUsageWarningDismissed] = useState(true); const [form, setForm] = useState({ @@ -137,7 +139,8 @@ export default function ComponentForm(props: Props) { sensorDistance: "0", }); const [compMode, setCompMode] = useState(); - const [useCustomGrain, setUseCustomGrain] = useState(false) + const [useCustomGrain, setUseCustomGrain] = useState(false); + const [controlEmail, setControlEmail] = useState(""); //const [numCalibrations, setNumCalibrations] = useState(0) useEffect(() => { @@ -356,6 +359,21 @@ export default function ComponentForm(props: Props) { setForm(f); }; + const addControlEmail = () => { + const trimmed = controlEmail.trim().toLowerCase(); + if (trimmed === "" || form.component.settings.controlEmails.includes(trimmed)) return; + let f = cloneDeep(form); + f.component.settings.controlEmails.push(trimmed); + setForm(f); + setControlEmail(""); + }; + + const removeControlEmail = (index: number) => { + let f = cloneDeep(form); + f.component.settings.controlEmails.splice(index, 1); + setForm(f); + }; + const toggleMeasure = (event: any) => { let f = cloneDeep(form); f.measure = event.target.checked; @@ -1341,7 +1359,7 @@ export default function ComponentForm(props: Props) { }} /> )} - {isController(component.settings.type) && ( + {isController(component.settings.type) && !hideControllerFields && ( + + + Authorized Control Emails + + + + setControlEmail(e.target.value)} + onKeyDown={e => { + if (e.key === "Enter") { + e.preventDefault(); + addControlEmail(); + } + }} + disabled={!canEdit} + /> + + + + + + {component.settings.controlEmails.map((email, i) => ( + + + {email} + + + removeControlEmail(i)} + disabled={!canEdit} + className={classes.redButton}> + + + + + ))} + )} {showDataUsageWarning && ( diff --git a/src/component/ComponentSettings.tsx b/src/component/ComponentSettings.tsx index db0e5ad..c6c981b 100644 --- a/src/component/ComponentSettings.tsx +++ b/src/component/ComponentSettings.tsx @@ -8,6 +8,7 @@ import { DialogContentText, DialogTitle, Divider, + FormControl, FormControlLabel, Grid2 as Grid, IconButton, @@ -41,8 +42,10 @@ import { GetComponentTypeOptions, getFriendlyName, getMeasurements, - isController + isController, } from "pbHelpers/ComponentType"; +import PeriodSelect from "common/time/PeriodSelect"; +import { bestUnit, milliToX } from "common/time/duration"; import { ComponentAvailabilityMap, DeviceAvailabilityMap, @@ -177,6 +180,7 @@ export default function ComponentSettings(props: Props) { const [formValid, setFormValid] = useState(false); const [excludedNodes, setExcludedNodes] = useState([]); const [nodeToExclude, setNodeToExclude] = useState(0); + const [controlEmail, setControlEmail] = useState(""); const handleChange = (event: React.ChangeEvent<{}>, newValue: number) => { setTabVal(newValue); @@ -498,6 +502,7 @@ export default function ComponentSettings(props: Props) { component={formComponent} device={device} canEdit={canEdit} + hideControllerFields={isController(formComponent.settings.type)} componentChanged={(formComp, isValid) => { formComponent.settings = formComp.settings; setFormValid(isValid); @@ -867,6 +872,145 @@ export default function ComponentSettings(props: Props) { ); }; + const addControlEmail = () => { + const trimmed = controlEmail.trim().toLowerCase(); + if (trimmed === "" || formComponent.settings.controlEmails.includes(trimmed)) return; + let f = cloneDeep(formComponent); + f.settings.controlEmails.push(trimmed); + setFormComponent(f); + setControlEmail(""); + }; + + const removeControlEmail = (index: number) => { + let f = cloneDeep(formComponent); + f.settings.controlEmails.splice(index, 1); + setFormComponent(f); + }; + + const handleOutputModeChanged = (event: any) => { + let f = cloneDeep(formComponent); + f.settings.defaultOutputState = event.target.value; + setFormComponent(f); + }; + + const handleMinCycleTimeChanged = (ms: number) => { + let f = cloneDeep(formComponent); + f.settings.minCycleTimeMs = Number(ms); + setFormComponent(f); + }; + + const isMinCycleTimeValid = () => { + const ext = extension(formComponent.settings.type, formComponent.settings.subtype); + if (!ext.isController) return true; + let min = Math.max(0, ext.minCycleTimeMs ? ext.minCycleTimeMs : 0); + return formComponent.settings.minCycleTimeMs >= min; + }; + + const minCycleTimeDescription = () => { + const ext = extension(formComponent.settings.type, formComponent.settings.subtype); + if (!ext.isController) return ""; + const min = Math.max(0, ext.minCycleTimeMs ? ext.minCycleTimeMs : 0); + const unit = bestUnit(min); + const value = milliToX(min, unit).toString(); + return "Must be at least " + value + " " + unit; + }; + + const controlContent = () => { + return ( + + + Auto + On + Off + + + + + + Authorized Control Emails + + + + setControlEmail(e.target.value)} + onKeyDown={e => { + if (e.key === "Enter") { + e.preventDefault(); + addControlEmail(); + } + }} + disabled={!canEdit} + /> + + + + + + {formComponent.settings.controlEmails.map((email, i) => ( + + + {email} + + + removeControlEmail(i)} + disabled={!canEdit} + className={classNames(classes.redButton)}> + + + + + ))} + + ); + }; + const actions = () => { return ( @@ -1042,36 +1186,55 @@ export default function ComponentSettings(props: Props) { scroll="paper"> {title()} - {!isController(formComponent.settings.type) && ( - - - - - + {isController(formComponent.settings.type) ? ( + + + + + + + {content()} + + + {controlContent()} + + + ) : ( + + + + + + + + {content()} + + + + + {overlayGroup()} + + + + + {componentPrefs()} + + )} - - {content()} - - - - - {overlayGroup()} - - - - - {componentPrefs()} - {actions()} )} From b378764d627e7c5e8b53e19fe94bb0b6e1684cac Mon Sep 17 00:00:00 2001 From: Carter Date: Wed, 17 Jun 2026 14:11:43 -0600 Subject: [PATCH 06/30] added some info about email control to the control tab for the component settings --- src/component/ComponentSettings.tsx | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/component/ComponentSettings.tsx b/src/component/ComponentSettings.tsx index c6c981b..e9a7983 100644 --- a/src/component/ComponentSettings.tsx +++ b/src/component/ComponentSettings.tsx @@ -953,7 +953,7 @@ export default function ComponentSettings(props: Props) { } /> - + Authorized Control Emails @@ -1007,6 +1007,28 @@ export default function ComponentSettings(props: Props) { ))} + + + Email Control + + + Authorized emails can control this component by sending an email to the + control address. Contact your administrator for the control email + address. Use one of the following as the email subject: + + +
{device.id()}:{formComponent.settings.type}-{formComponent.settings.addressType}-{formComponent.settings.address} - ON
+
{device.id()}:{formComponent.settings.type}-{formComponent.settings.addressType}-{formComponent.settings.address} - OFF
+
{device.id()}:{formComponent.settings.type}-{formComponent.settings.addressType}-{formComponent.settings.address} - AUTO
+
); }; From d9846ea02b211373b1ba45cd84837bec2537c815 Mon Sep 17 00:00:00 2001 From: Carter Date: Wed, 17 Jun 2026 15:41:26 -0600 Subject: [PATCH 07/30] firmware renders with warning styling if it wasn't found in the server --- src/device/VersionChip.tsx | 8 ++++++++ src/pbHelpers/FirmwareVersion.tsx | 11 +++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/device/VersionChip.tsx b/src/device/VersionChip.tsx index 09dac0f..8826239 100644 --- a/src/device/VersionChip.tsx +++ b/src/device/VersionChip.tsx @@ -22,6 +22,14 @@ class VersionChip extends React.Component { variant="outlined" label={firmwareVersionHelper.description} icon={firmwareVersionHelper.icon} + sx={ + firmwareVersionHelper.colour + ? { + borderColor: firmwareVersionHelper.colour, + color: firmwareVersionHelper.colour + } + : undefined + } /> ); diff --git a/src/pbHelpers/FirmwareVersion.tsx b/src/pbHelpers/FirmwareVersion.tsx index d7dcc8c..43ea776 100644 --- a/src/pbHelpers/FirmwareVersion.tsx +++ b/src/pbHelpers/FirmwareVersion.tsx @@ -9,6 +9,7 @@ export interface FirmwareVersionHelper { const oldFirmwareColour = "var(--status-warning)"; const currentFirmwareColour = "var(--status-ok)"; +const unknownFirmwareColour = "var(--status-warning)"; export function getFirmwareVersionHelper( version: string, @@ -17,24 +18,34 @@ export function getFirmwareVersionHelper( const firmwareIcon = ; const oldFirmwareIcon = ; const currentFirmwareIcon = ; + const unknownFirmwareIcon = ; let helper = {} as FirmwareVersionHelper; if (!version || version === "") { helper.description = "Unknown version"; helper.icon = oldFirmwareIcon; helper.tooltip = "We don't know what version your device is, it may behave unexpectedly"; + helper.colour = oldFirmwareColour; + } else if (version.startsWith("!")) { + helper.description = version; + helper.icon = unknownFirmwareIcon; + helper.tooltip = "This firmware version was reported by the device but is missing from the firmware list"; + helper.colour = unknownFirmwareColour; } else if (available !== "" && version !== available) { helper.description = version; helper.icon = oldFirmwareIcon; helper.tooltip = "A firmware upgrade is available, some features may be disabled for out-of-date devices"; + helper.colour = oldFirmwareColour; } else if (available === "") { helper.description = version; helper.icon = firmwareIcon; helper.tooltip = "Running version " + version; + helper.colour = ""; } else { helper.description = version; helper.icon = currentFirmwareIcon; helper.tooltip = "Your device is running the latest firmware"; + helper.colour = currentFirmwareColour; } return helper; } From 0984ae0f37efed9f48b21e4f60f14dd957b09a9b Mon Sep 17 00:00:00 2001 From: Carter Date: Wed, 17 Jun 2026 16:19:35 -0600 Subject: [PATCH 08/30] got rid of warning signifier on firmware print --- src/pbHelpers/FirmwareVersion.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pbHelpers/FirmwareVersion.tsx b/src/pbHelpers/FirmwareVersion.tsx index 43ea776..49472be 100644 --- a/src/pbHelpers/FirmwareVersion.tsx +++ b/src/pbHelpers/FirmwareVersion.tsx @@ -26,9 +26,10 @@ export function getFirmwareVersionHelper( helper.tooltip = "We don't know what version your device is, it may behave unexpectedly"; helper.colour = oldFirmwareColour; } else if (version.startsWith("!")) { - helper.description = version; + const cleanVersion = version.substring(1); + helper.description = cleanVersion; helper.icon = unknownFirmwareIcon; - helper.tooltip = "This firmware version was reported by the device but is missing from the firmware list"; + helper.tooltip = "Firmware " + cleanVersion + " was reported by the device but is missing from the firmware list"; helper.colour = unknownFirmwareColour; } else if (available !== "" && version !== available) { helper.description = version; From 270df1a11876b581c5fb9476ebba6891b06f2eec Mon Sep 17 00:00:00 2001 From: csawatzky Date: Tue, 23 Jun 2026 16:40:09 -0600 Subject: [PATCH 09/30] fixed unit from V to mV for analog pressure --- package-lock.json | 2 +- src/pbHelpers/MeasurementDescriber.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 37bd044..4d9535e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11205,7 +11205,7 @@ }, "node_modules/protobuf-ts": { "version": "1.0.0", - "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#6d889de1a42adf590373ff932823e5e697634c6a", + "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#42f72fa7fb838b7771e6c847faaa192e93f42da7", "dependencies": { "protobufjs": "^6.8.8" } diff --git a/src/pbHelpers/MeasurementDescriber.ts b/src/pbHelpers/MeasurementDescriber.ts index 947c545..ccc50a7 100644 --- a/src/pbHelpers/MeasurementDescriber.ts +++ b/src/pbHelpers/MeasurementDescriber.ts @@ -174,10 +174,9 @@ export class MeasurementDescriber { this.details.path = "power.inputVoltageTimes10"; this.details.decimals = 1; } - if (componentType === quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE) { + if (componentType === quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE || componentType === quack.ComponentType.COMPONENT_TYPE_ANALOG_PRESSURE) { this.details.unit = "mV"; this.details.graph = GraphType.MULTILINE; - this.details.unit = "mV"; this.details.decimals = 0 // this.details.nodeDetails = { // colours: ["white", "black", "red", "blue"], From 728d07a07e625300d98b15a0d045bdeb940c1778 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Tue, 23 Jun 2026 16:42:30 -0600 Subject: [PATCH 10/30] proto update --- package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 55cca88..7df4b5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11752,7 +11752,7 @@ }, "node_modules/protobuf-ts": { "version": "1.0.0", - "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#42f72fa7fb838b7771e6c847faaa192e93f42da7", + "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#8c5091affb9c1ef4e80c675072435b745b07e612", "dependencies": { "protobufjs": "^6.8.8" } From f73eb0fcbd80d6c37bf92aabea6a867072ad76fb Mon Sep 17 00:00:00 2001 From: csawatzky Date: Tue, 23 Jun 2026 16:44:42 -0600 Subject: [PATCH 11/30] proto update --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7df4b5b..afc0c3f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,7 +46,7 @@ "mui-tel-input": "^7.0.0", "notistack": "^3.0.1", "openweathermap-ts": "^1.2.10", - "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#sendgrid_whitelabel", + "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#staging", "query-string": "^9.2.1", "react": "^18.3.1", "react-beautiful-dnd": "^13.1.1", @@ -11752,7 +11752,7 @@ }, "node_modules/protobuf-ts": { "version": "1.0.0", - "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#8c5091affb9c1ef4e80c675072435b745b07e612", + "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#42f72fa7fb838b7771e6c847faaa192e93f42da7", "dependencies": { "protobufjs": "^6.8.8" } diff --git a/package.json b/package.json index 1fb2505..3a76c43 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "mui-tel-input": "^7.0.0", "notistack": "^3.0.1", "openweathermap-ts": "^1.2.10", - "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#sendgrid_whitelabel", + "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#staging", "query-string": "^9.2.1", "react": "^18.3.1", "react-beautiful-dnd": "^13.1.1", From 4ea0d53c94482786bf6671efc369c1378bbab180 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Wed, 24 Jun 2026 12:38:29 -0600 Subject: [PATCH 12/30] adding analog pressure to have the cable id show up for analog cables --- src/component/ComponentSettings.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/component/ComponentSettings.tsx b/src/component/ComponentSettings.tsx index db0e5ad..a133de3 100644 --- a/src/component/ComponentSettings.tsx +++ b/src/component/ComponentSettings.tsx @@ -507,11 +507,12 @@ export default function ComponentSettings(props: Props) { ); }; - const isCableComponent = () => { + const hasCableID = () => { return (formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE || formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE || formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_PRESSURE_CABLE || + formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_ANALOG_PRESSURE || (formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_CAPACITOR_CABLE && formComponent.subType() === quack.CapacitorCableSubtype.CAPACITOR_CABLE_SUBTYPE_FROG)) @@ -577,7 +578,8 @@ export default function ComponentSettings(props: Props) { )} {supportsExpansion() && setExpLine()} - {(isCableComponent() && !supportsExpansion())&& setComponentAddrType()} + + {(hasCableID() && !supportsExpansion())&& setComponentAddrType()} ) : activeStep === 1 ? ( Date: Wed, 24 Jun 2026 13:13:14 -0600 Subject: [PATCH 13/30] opening interaction websocket to detect accepted pending changes --- src/interactions/InteractionsOverview.tsx | 45 ++++++++++++++++++++--- src/pages/Device.tsx | 43 +++++++++++++++++++++- 2 files changed, 81 insertions(+), 7 deletions(-) diff --git a/src/interactions/InteractionsOverview.tsx b/src/interactions/InteractionsOverview.tsx index 600c88a..db73675 100644 --- a/src/interactions/InteractionsOverview.tsx +++ b/src/interactions/InteractionsOverview.tsx @@ -4,6 +4,7 @@ import { CardActions, CardContent, CardHeader, + CircularProgress, darken, Grid2 as Grid, IconButton, @@ -14,7 +15,7 @@ import { Tooltip, Typography } from "@mui/material"; -import { Settings } from "@mui/icons-material"; +import { CheckCircleOutline, Settings } from "@mui/icons-material"; import { useInteractionsAPI, usePrevious, useSnackbar } from "hooks"; import { cloneDeep } from "lodash"; import { Component, Device, Interaction } from "models"; @@ -80,6 +81,7 @@ export default function InteractionsOverview(props: Props) { const [interactions, setInteractions] = useState(props.interactions); const [dirtyInteractions, setDirtyInteractions] = useState>(new Map()); const [mappedComponents, setMappedComponents] = useState>(new Map()); + const [acceptedInteractions, setAcceptedInteractions] = useState>(new Set()); const [renderToggle, setRenderToggle] = useState(false); const [selectedInteraction, setSelectedInteraction] = useState( undefined @@ -95,6 +97,20 @@ export default function InteractionsOverview(props: Props) { } if (props.interactions !== prevInteractions) { + if (prevInteractions) { + setAcceptedInteractions((prevAccepted) => { + const updatedAccepted = new Set(prevAccepted); + props.interactions.forEach((interaction: Interaction) => { + const previous = prevInteractions.find(prevInteraction => prevInteraction.key() === interaction.key()); + if (!interaction.status.synced) { + updatedAccepted.delete(interaction.key()); + } else if (previous && !previous.status.synced) { + updatedAccepted.add(interaction.key()); + } + }); + return updatedAccepted; + }); + } setInteractions(cloneDeep(props.interactions)); } }, [components, prevComponents, props.interactions, prevInteractions]); @@ -226,10 +242,16 @@ export default function InteractionsOverview(props: Props) { let key = interaction.key(); let source = mappedComponents.get(componentIDToString(interaction.settings.source)); let sink = mappedComponents.get(componentIDToString(interaction.settings.sink)); + const isPending = !interaction.status.synced && Boolean(interaction.status.lastUpdate); + const isAccepted = interaction.status.synced && acceptedInteractions.has(interaction.key()); let statusText = - !interaction.status.synced && interaction.status.lastUpdate - ? "Pending " + - moment(interaction.status.lastUpdate && interaction.status.lastUpdate).fromNow() + !interaction.status.synced + ? interaction.status.lastUpdate + ? "Pending " + + moment(interaction.status.lastUpdate && interaction.status.lastUpdate).fromNow() + : "" + : acceptedInteractions.has(interaction.key()) + ? "Pending change accepted" : ""; let schedule = pond.InteractionSchedule.create( interaction.settings.schedule !== null ? interaction.settings.schedule : undefined @@ -272,7 +294,20 @@ export default function InteractionsOverview(props: Props) { className={classes.header} titleTypographyProps={{ variant: "subtitle2" }} title={interactionResultText(interaction, sink)} - subheader={statusText} + subheader={ + statusText ? ( + + {isPending && } + {isAccepted && ( + + )} + + {statusText} + + + ) : ""} subheaderTypographyProps={{ variant: "caption" }} action={action} /> diff --git a/src/pages/Device.tsx b/src/pages/Device.tsx index 6c9535d..96b06b6 100644 --- a/src/pages/Device.tsx +++ b/src/pages/Device.tsx @@ -282,6 +282,45 @@ export default function DevicePage() { enabled: !!deviceID, }); + // --- Real-time interaction updates --- + // Streams interaction changes for this device, including status changes + // when the device accepts pending interaction updates. + useWebSocket<{ key: string; interaction: Interaction } | null>({ + path: `/v1/live/devices/${deviceID}/interactions`, + parse: (e) => { + try { + const raw = JSON.parse(e.data); + const interaction = Interaction.any(raw); + if (!interaction?.settings) { + console.warn("[ws] received interaction without settings:", raw); + return null; + } + return { key: interaction.key(), interaction }; + } catch (err) { + console.warn("[ws] failed to parse interaction:", err); + return null; + } + }, + onMessage: (data) => { + if (!data) return; + const { key, interaction } = data; + setInteractions((prev) => { + const index = prev.findIndex((existing) => existing.key() === key); + if (index < 0) { + return [...prev, interaction]; + } + const updated = [...prev]; + updated[index] = interaction; + return updated; + }); + }, + keys: liveContextKeys, + types: liveContextTypes, + as: liveAs, + token, + enabled: !!deviceID, + }); + const loadPortScan = () => { deviceAPI.listFoundComponents(parseInt(deviceID)) .then(resp => { @@ -395,7 +434,7 @@ export default function DevicePage() { interactions={filteredInteractions} permissions={permissions} deviceComponentPreferences={prefsMap.get(c.key())} - key={i} + key={c.key()} refreshCallback={(updatedComponent?: Component) => updatedComponent ? handleComponentChanged(updatedComponent) : loadDevice() } @@ -412,7 +451,7 @@ export default function DevicePage() { interactions={filteredInteractions} permissions={permissions} deviceComponentPreferences={prefsMap.get(c.key())} - key={i} + key={c.key()} refreshCallback={(updatedComponent?: Component) => updatedComponent ? handleComponentChanged(updatedComponent) : loadDevice() } From 879a5ae1f08bc9b4109c50dabdfe189a22e9daab Mon Sep 17 00:00:00 2001 From: Carter Date: Wed, 24 Jun 2026 14:16:54 -0600 Subject: [PATCH 14/30] fixed pending state logic --- src/interactions/InteractionsOverview.tsx | 60 ++++++++++++++--------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/src/interactions/InteractionsOverview.tsx b/src/interactions/InteractionsOverview.tsx index db73675..2619e97 100644 --- a/src/interactions/InteractionsOverview.tsx +++ b/src/interactions/InteractionsOverview.tsx @@ -70,6 +70,11 @@ interface Props { refreshCallback: () => void; } +interface PendingInteraction { + since: string; + accepted: boolean; +} + export default function InteractionsOverview(props: Props) { const classes = useStyles(); const [{as, user}] = useGlobalState(); @@ -81,7 +86,7 @@ export default function InteractionsOverview(props: Props) { const [interactions, setInteractions] = useState(props.interactions); const [dirtyInteractions, setDirtyInteractions] = useState>(new Map()); const [mappedComponents, setMappedComponents] = useState>(new Map()); - const [acceptedInteractions, setAcceptedInteractions] = useState>(new Set()); + const [pendingInteractions, setPendingInteractions] = useState>(new Map()); const [renderToggle, setRenderToggle] = useState(false); const [selectedInteraction, setSelectedInteraction] = useState( undefined @@ -97,20 +102,23 @@ export default function InteractionsOverview(props: Props) { } if (props.interactions !== prevInteractions) { - if (prevInteractions) { - setAcceptedInteractions((prevAccepted) => { - const updatedAccepted = new Set(prevAccepted); - props.interactions.forEach((interaction: Interaction) => { - const previous = prevInteractions.find(prevInteraction => prevInteraction.key() === interaction.key()); - if (!interaction.status.synced) { - updatedAccepted.delete(interaction.key()); - } else if (previous && !previous.status.synced) { - updatedAccepted.add(interaction.key()); + setPendingInteractions((prevPending) => { + const updatedPending = new Map(prevPending); + props.interactions.forEach((interaction: Interaction) => { + const existing = updatedPending.get(interaction.key()); + if (!interaction.status.synced) { + if (!existing || existing.accepted) { + updatedPending.set(interaction.key(), { + since: interaction.status.lastUpdate || moment().toISOString(), + accepted: false + }); } - }); - return updatedAccepted; + } else if (existing && !existing.accepted) { + updatedPending.set(interaction.key(), { ...existing, accepted: true }); + } }); - } + return updatedPending; + }); setInteractions(cloneDeep(props.interactions)); } }, [components, prevComponents, props.interactions, prevInteractions]); @@ -217,6 +225,14 @@ export default function InteractionsOverview(props: Props) { index: number, settings: pond.IInteractionSettings ) => { + const interactionKey = settings.key ?? ""; + setPendingInteractions((prevPending) => { + const updatedPending = new Map(prevPending); + if (interactionKey) { + updatedPending.set(interactionKey, { since: moment().toISOString(), accepted: false }); + } + return updatedPending; + }); interactionsAPI .updateInteraction(Number(deviceID), settings, as) .then((_response: any) => { @@ -224,7 +240,6 @@ export default function InteractionsOverview(props: Props) { updatedDirtyInteractions.set(index, false); setDirtyInteractions(updatedDirtyInteractions); success("Successfully updated the interaction for " + component.name()); - refreshCallback(); }) .catch((_err: any) => { error("Error occurred while updating the interaction for " + component.name()); @@ -242,16 +257,13 @@ export default function InteractionsOverview(props: Props) { let key = interaction.key(); let source = mappedComponents.get(componentIDToString(interaction.settings.source)); let sink = mappedComponents.get(componentIDToString(interaction.settings.sink)); - const isPending = !interaction.status.synced && Boolean(interaction.status.lastUpdate); - const isAccepted = interaction.status.synced && acceptedInteractions.has(interaction.key()); - let statusText = - !interaction.status.synced - ? interaction.status.lastUpdate - ? "Pending " + - moment(interaction.status.lastUpdate && interaction.status.lastUpdate).fromNow() - : "" - : acceptedInteractions.has(interaction.key()) - ? "Pending change accepted" + const pending = pendingInteractions.get(interaction.key()); + const isAccepted = Boolean(pending?.accepted); + const isPending = Boolean(pending && !pending.accepted); + let statusText = isAccepted + ? "Pending change accepted" + : isPending + ? "Pending " + moment(pending.since).fromNow() : ""; let schedule = pond.InteractionSchedule.create( interaction.settings.schedule !== null ? interaction.settings.schedule : undefined From b9a13c578115c23fe2558a7b04b15e472bf98329 Mon Sep 17 00:00:00 2001 From: Carter Date: Wed, 24 Jun 2026 14:40:04 -0600 Subject: [PATCH 15/30] fixed pending change race condition --- src/interactions/InteractionsOverview.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/interactions/InteractionsOverview.tsx b/src/interactions/InteractionsOverview.tsx index 2619e97..b579660 100644 --- a/src/interactions/InteractionsOverview.tsx +++ b/src/interactions/InteractionsOverview.tsx @@ -72,6 +72,7 @@ interface Props { interface PendingInteraction { since: string; + seenUnsynced: boolean; accepted: boolean; } @@ -110,10 +111,13 @@ export default function InteractionsOverview(props: Props) { if (!existing || existing.accepted) { updatedPending.set(interaction.key(), { since: interaction.status.lastUpdate || moment().toISOString(), + seenUnsynced: true, accepted: false }); + } else if (!existing.seenUnsynced) { + updatedPending.set(interaction.key(), { ...existing, seenUnsynced: true }); } - } else if (existing && !existing.accepted) { + } else if (existing && existing.seenUnsynced && !existing.accepted) { updatedPending.set(interaction.key(), { ...existing, accepted: true }); } }); @@ -229,7 +233,7 @@ export default function InteractionsOverview(props: Props) { setPendingInteractions((prevPending) => { const updatedPending = new Map(prevPending); if (interactionKey) { - updatedPending.set(interactionKey, { since: moment().toISOString(), accepted: false }); + updatedPending.set(interactionKey, { since: moment().toISOString(), seenUnsynced: false, accepted: false }); } return updatedPending; }); From e8cc5099150384a2838f517af98b72d93adffdbd Mon Sep 17 00:00:00 2001 From: Carter Date: Thu, 25 Jun 2026 12:15:49 -0600 Subject: [PATCH 16/30] less restrictive on receiving websockets --- src/interactions/InteractionsOverview.tsx | 85 ++++++++++++++--------- src/pages/Device.tsx | 19 +++++ 2 files changed, 70 insertions(+), 34 deletions(-) diff --git a/src/interactions/InteractionsOverview.tsx b/src/interactions/InteractionsOverview.tsx index b579660..450c44f 100644 --- a/src/interactions/InteractionsOverview.tsx +++ b/src/interactions/InteractionsOverview.tsx @@ -70,12 +70,6 @@ interface Props { refreshCallback: () => void; } -interface PendingInteraction { - since: string; - seenUnsynced: boolean; - accepted: boolean; -} - export default function InteractionsOverview(props: Props) { const classes = useStyles(); const [{as, user}] = useGlobalState(); @@ -87,12 +81,21 @@ export default function InteractionsOverview(props: Props) { const [interactions, setInteractions] = useState(props.interactions); const [dirtyInteractions, setDirtyInteractions] = useState>(new Map()); const [mappedComponents, setMappedComponents] = useState>(new Map()); - const [pendingInteractions, setPendingInteractions] = useState>(new Map()); + const [acceptedInteractions, setAcceptedInteractions] = useState>(new Set()); const [renderToggle, setRenderToggle] = useState(false); const [selectedInteraction, setSelectedInteraction] = useState( undefined ); + useEffect(() => { + console.log("[interactions:mount] initial interactions:", props.interactions.map(i => ({ + key: i.key(), + synced: i.status.synced, + lastUpdate: i.status.lastUpdate, + lastSynced: i.status.lastSynced, + }))); + }, []); + useEffect(() => { if (components !== prevComponents) { let initMappedComponents: Map = new Map(); @@ -103,25 +106,30 @@ export default function InteractionsOverview(props: Props) { } if (props.interactions !== prevInteractions) { - setPendingInteractions((prevPending) => { - const updatedPending = new Map(prevPending); + console.log("[interactions:effect] props.interactions changed, prevInteractions was", prevInteractions ? "defined" : "undefined"); + props.interactions.forEach((interaction: Interaction) => { + const key = interaction.key(); + const prevInteraction = prevInteractions?.find(p => p.key() === key); + console.log("[interactions:effect]", key, { + synced: interaction.status.synced, + lastUpdate: interaction.status.lastUpdate, + prevSynced: prevInteraction?.status.synced, + prevLastUpdate: prevInteraction?.status.lastUpdate, + }); + }); + setAcceptedInteractions(prev => { + const updated = new Set(prev); props.interactions.forEach((interaction: Interaction) => { - const existing = updatedPending.get(interaction.key()); + const key = interaction.key(); + const prevInteraction = prevInteractions?.find(p => p.key() === key); if (!interaction.status.synced) { - if (!existing || existing.accepted) { - updatedPending.set(interaction.key(), { - since: interaction.status.lastUpdate || moment().toISOString(), - seenUnsynced: true, - accepted: false - }); - } else if (!existing.seenUnsynced) { - updatedPending.set(interaction.key(), { ...existing, seenUnsynced: true }); - } - } else if (existing && existing.seenUnsynced && !existing.accepted) { - updatedPending.set(interaction.key(), { ...existing, accepted: true }); + updated.delete(key); + } else if (prevInteraction && !prevInteraction.status.synced) { + console.log("[interactions:effect] ACCEPTED", key); + updated.add(key); } }); - return updatedPending; + return updated; }); setInteractions(cloneDeep(props.interactions)); } @@ -229,20 +237,30 @@ export default function InteractionsOverview(props: Props) { index: number, settings: pond.IInteractionSettings ) => { - const interactionKey = settings.key ?? ""; - setPendingInteractions((prevPending) => { - const updatedPending = new Map(prevPending); - if (interactionKey) { - updatedPending.set(interactionKey, { since: moment().toISOString(), seenUnsynced: false, accepted: false }); - } - return updatedPending; - }); interactionsAPI .updateInteraction(Number(deviceID), settings, as) .then((_response: any) => { + console.log("[interactions:submit] API success, setting synced=false for index", index); let updatedDirtyInteractions = cloneDeep(dirtyInteractions); updatedDirtyInteractions.set(index, false); setDirtyInteractions(updatedDirtyInteractions); + setInteractions(prev => { + const updated = cloneDeep(prev); + if (updated[index]) { + updated[index].status.synced = false; + updated[index].status.lastUpdate = moment().toISOString(); + console.log("[interactions:submit] local state updated for", updated[index].key()); + } + return updated; + }); + const interactionKey = settings.key ?? ""; + if (interactionKey) { + setAcceptedInteractions(prev => { + const updated = new Set(prev); + updated.delete(interactionKey); + return updated; + }); + } success("Successfully updated the interaction for " + component.name()); }) .catch((_err: any) => { @@ -261,13 +279,12 @@ export default function InteractionsOverview(props: Props) { let key = interaction.key(); let source = mappedComponents.get(componentIDToString(interaction.settings.source)); let sink = mappedComponents.get(componentIDToString(interaction.settings.sink)); - const pending = pendingInteractions.get(interaction.key()); - const isAccepted = Boolean(pending?.accepted); - const isPending = Boolean(pending && !pending.accepted); + const isAccepted = acceptedInteractions.has(interaction.key()); + const isPending = !interaction.status.synced; let statusText = isAccepted ? "Pending change accepted" : isPending - ? "Pending " + moment(pending.since).fromNow() + ? "Pending " + moment(interaction.status.lastUpdate).fromNow() : ""; let schedule = pond.InteractionSchedule.create( interaction.settings.schedule !== null ? interaction.settings.schedule : undefined diff --git a/src/pages/Device.tsx b/src/pages/Device.tsx index 96b06b6..733d621 100644 --- a/src/pages/Device.tsx +++ b/src/pages/Device.tsx @@ -304,11 +304,30 @@ export default function DevicePage() { onMessage: (data) => { if (!data) return; const { key, interaction } = data; + console.log("[ws:interaction] received", key, { + synced: interaction.status.synced, + lastUpdate: interaction.status.lastUpdate, + lastSynced: interaction.status.lastSynced, + }); setInteractions((prev) => { const index = prev.findIndex((existing) => existing.key() === key); if (index < 0) { return [...prev, interaction]; } + const existing = prev[index]; + const existingLastUpdate = getTimestampMillis(existing.status.lastUpdate); + const incomingLastUpdate = getTimestampMillis(interaction.status.lastUpdate); + const incomingLastSynced = getTimestampMillis(interaction.status.lastSynced); + if (existingLastUpdate !== undefined && incomingLastUpdate !== undefined && + existingLastUpdate > incomingLastUpdate) { + return prev; + } + if (!existing.status.synced && interaction.status.synced && + incomingLastUpdate !== undefined && + existingLastUpdate === incomingLastUpdate && + (incomingLastSynced === undefined || incomingLastSynced < incomingLastUpdate)) { + return prev; + } const updated = [...prev]; updated[index] = interaction; return updated; From ffd5e0a76902bc8d4813a31740377289fc0f5587 Mon Sep 17 00:00:00 2001 From: Carter Date: Thu, 25 Jun 2026 12:33:14 -0600 Subject: [PATCH 17/30] preventing form updates when settings dialog is open --- src/component/ComponentSettings.tsx | 3 ++- src/device/DeviceSettings.tsx | 6 ++++-- src/interactions/InteractionSettings.tsx | 9 +++++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/component/ComponentSettings.tsx b/src/component/ComponentSettings.tsx index db0e5ad..c703393 100644 --- a/src/component/ComponentSettings.tsx +++ b/src/component/ComponentSettings.tsx @@ -213,7 +213,8 @@ export default function ComponentSettings(props: Props) { }, [props.component, componentTypeOptions]); useEffect(() => { - if (props.component !== prevComponent || (!isDialogOpen && prevIsDialogOpen)) { + if (isDialogOpen && prevIsDialogOpen) return; + if (props.component !== prevComponent || isDialogOpen !== prevIsDialogOpen) { init(); } }, [props.component, prevComponent, init, isDialogOpen, prevIsDialogOpen]); diff --git a/src/device/DeviceSettings.tsx b/src/device/DeviceSettings.tsx index a9277f5..26ca591 100644 --- a/src/device/DeviceSettings.tsx +++ b/src/device/DeviceSettings.tsx @@ -95,6 +95,7 @@ export default function DeviceSettings(props: Props) { const deviceAPI = useDeviceAPI(); const { device, isDialogOpen, closeDialogCallback, canEdit, refreshCallback, components } = props; const prevDevice = usePrevious(device); + const prevIsDialogOpen = usePrevious(isDialogOpen); const [deviceForm, setDeviceForm] = useState(deviceFromForm(Device.clone(device))); const [isRemoveDeviceDialogOpen, setIsRemoveDeviceDialogOpen] = useState(false); const [sleeps, setSleeps] = useState(device.settings.sleepDurationS > 0); @@ -110,7 +111,8 @@ export default function DeviceSettings(props: Props) { const [existingMutation, setExistingMutation] = useState(); useEffect(() => { - if (prevDevice !== device) { + if (isDialogOpen && prevIsDialogOpen) return; + if (prevDevice !== device || isDialogOpen !== prevIsDialogOpen) { setDeviceForm(deviceFromForm(Device.clone(props.device))); if (device.settings.extensionComponents[0]) { setCompExtOne(device.settings.extensionComponents[0]); @@ -130,7 +132,7 @@ export default function DeviceSettings(props: Props) { setComponentsByDevice(cbd); } } - }, [device, prevDevice, props.device, components]); + }, [device, prevDevice, props.device, components, isDialogOpen, prevIsDialogOpen]); const close = () => { closeDialogCallback(); diff --git a/src/interactions/InteractionSettings.tsx b/src/interactions/InteractionSettings.tsx index 4f2a2d3..c98742f 100644 --- a/src/interactions/InteractionSettings.tsx +++ b/src/interactions/InteractionSettings.tsx @@ -133,6 +133,7 @@ export default function InteractionSettings(props: Props) { const prevInitialInteraction = usePrevious(initialInteraction); const prevComponents = usePrevious(components); const prevInitialComponent = usePrevious(initialComponent); + const prevIsDialogOpen = usePrevious(isDialogOpen); const classes = useStyles(); const [{ user, as }] = useGlobalState(); const interactionsAPI = useInteractionsAPI(); @@ -232,10 +233,12 @@ export default function InteractionSettings(props: Props) { if (user && user.settings.timezone) { setTimezone(user.settings.timezone); } + if (isDialogOpen && prevIsDialogOpen) return; if ( prevInitialInteraction !== initialInteraction || (prevComponents && prevComponents.length !== components.length) || - getComponentIDString(prevInitialComponent) !== getComponentIDString(initialComponent) + getComponentIDString(prevInitialComponent) !== getComponentIDString(initialComponent) || + isDialogOpen !== prevIsDialogOpen ) { setDefaultState(); } @@ -247,7 +250,9 @@ export default function InteractionSettings(props: Props) { prevInitialComponent, prevInitialInteraction, setDefaultState, - user + user, + isDialogOpen, + prevIsDialogOpen ]); const getAvailableSinks = () => { From 3eb66e362287e9914f78d6eaff509ac9d5f1f5ee Mon Sep 17 00:00:00 2001 From: Carter Date: Tue, 30 Jun 2026 09:43:38 -0600 Subject: [PATCH 18/30] changed header to match new theme --- src/app/Header.tsx | 4 ++-- src/services/whiteLabel.ts | 11 +++++++++++ src/theme/theme.ts | 14 ++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/app/Header.tsx b/src/app/Header.tsx index 6e4dbaf..5ff6184 100644 --- a/src/app/Header.tsx +++ b/src/app/Header.tsx @@ -16,7 +16,7 @@ const useStyles = makeStyles((theme: Theme) => { appBar: { zIndex: theme.zIndex.drawer + 1, backgroundImage: "none", // This prevents de-saturation of header in dark mode - backgroundColor: theme.palette.mode === "light" ? "#3b3b3b" : "#272727" + backgroundColor: theme.palette.header.main }, toolbar: { marginLeft: theme.spacing(0.5), @@ -128,4 +128,4 @@ export default function Header() { {isMobile && {setNavOpen(isOpen)}} sideIsOpen={navOpen} />} ) -} \ No newline at end of file +} diff --git a/src/services/whiteLabel.ts b/src/services/whiteLabel.ts index 35a9c8c..90cf4ed 100644 --- a/src/services/whiteLabel.ts +++ b/src/services/whiteLabel.ts @@ -28,6 +28,8 @@ const protips: string[] = [ "Want to receive text messages about your devices? Update your phone number and enable SMS notifications!" ]; +const DEFAULT_HEADER_COLOUR = "#1f2a35"; + export interface WhiteLabelFeatures { bins: boolean; visualFarm: boolean; @@ -55,6 +57,7 @@ interface WhiteLabel { secondaryColour: any; signatureColour: any; signatureAccentColour: any; + headerColour?: any; auth0ClientId: string; redirectOnLogout: boolean; logoutRedirectTarget: string; @@ -444,6 +447,14 @@ export function getSignatureColour(): any { return getWhitelabel().signatureColour; } +export function getHeaderColour(): any { + return getWhitelabel().headerColour ?? DEFAULT_HEADER_COLOUR; +} + +export function getHeaderColor(): any { + return getHeaderColour(); +} + export function getSignatureAccentColour(): any { return getWhitelabel().signatureAccentColour; } diff --git a/src/theme/theme.ts b/src/theme/theme.ts index 739a54f..ce44c5d 100644 --- a/src/theme/theme.ts +++ b/src/theme/theme.ts @@ -2,10 +2,21 @@ import * as Colours from "@mui/material/colors" import { createTheme, ThemeOptions } from '@mui/material/styles'; import { + getHeaderColor, getPrimaryColour, getSecondaryColour, } from "../services/whiteLabel"; +declare module "@mui/material/styles" { + interface Palette { + header: Palette["primary"]; + } + + interface PaletteOptions { + header?: PaletteOptions["primary"]; + } +} + const baseTheme: ThemeOptions = { palette: { mode: 'light', // default mode; will be overridden by system if needed @@ -62,6 +73,9 @@ export const getTheme = (mode: 'light' | 'dark') => ...baseTheme.palette, primary: makePaletteColor(getPrimaryColour()), secondary: makePaletteColor(getSecondaryColour()), + header: { + main: getHeaderColor(), + }, mode, ...(mode === 'dark' ? { From 87064461087845ac424701e7c5dc28fba614250b Mon Sep 17 00:00:00 2001 From: Carter Date: Tue, 30 Jun 2026 10:50:29 -0600 Subject: [PATCH 19/30] proto update and audit --- package-lock.json | 1140 +++++++++++++++++++++++---------------------- 1 file changed, 582 insertions(+), 558 deletions(-) diff --git a/package-lock.json b/package-lock.json index 58133fb..efc9773 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,7 +43,7 @@ "mui-tel-input": "^7.0.0", "notistack": "^3.0.1", "openweathermap-ts": "^1.2.10", - "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#staging", + "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#master", "query-string": "^9.2.1", "react": "^18.3.1", "react-beautiful-dnd": "^13.1.1", @@ -128,12 +128,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -142,9 +142,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "dev": true, "license": "MIT", "engines": { @@ -152,21 +152,21 @@ } }, "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -200,13 +200,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -229,14 +229,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -333,9 +333,9 @@ } }, "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -356,28 +356,28 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -400,9 +400,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", "dev": true, "license": "MIT", "engines": { @@ -460,27 +460,27 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "dev": true, "license": "MIT", "engines": { @@ -503,26 +503,26 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" + "@babel/types": "^7.29.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -1099,16 +1099,16 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz", - "integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.7.tgz", + "integrity": "sha512-TM2ZcQLoG2/y4HODiStCo10DibYhWhGWAwVv+EQKmG/7GFl0N+AAmUiXOMKM+aiJ9XBJ9AHVZBvTzMnJ2sM3cQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.29.0" + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1685,31 +1685,31 @@ } }, "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", "debug": "^4.3.1" }, "engines": { @@ -1717,13 +1717,13 @@ } }, "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -3049,9 +3049,9 @@ "license": "BSD-3-Clause" }, "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", + "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", "license": "BSD-3-Clause" }, "node_modules/@react-pdf/fns": { @@ -3223,9 +3223,9 @@ } }, "node_modules/@remix-run/router": { - "version": "1.23.2", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", - "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==", + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.3.tgz", + "integrity": "sha512-4An71tdz9X8+3sI4Qqqd2LWd9vS39J7sqd9EU4Scw7TJE/qB10Flv/UuqbPVgfQV9XoK8Np6jNquZitnZq5i+Q==", "license": "MIT", "engines": { "node": ">=14.0.0" @@ -3238,10 +3238,37 @@ "dev": true, "license": "MIT" }, + "node_modules/@rollup/plugin-babel": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.1.0.tgz", + "integrity": "sha512-dFZNuFD2YRcoomP4oYf+DvQNSUA9ih+A3vUqopQx5EdtPGo3WBnQcI/S8pwpz91UsGfL0HsMSOlaMld8HrbubA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@rollup/pluginutils": "^5.0.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "@types/babel__core": "^7.1.9", + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "@types/babel__core": { + "optional": true + }, + "rollup": { + "optional": true + } + } + }, "node_modules/@rollup/plugin-node-resolve": { - "version": "15.3.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz", - "integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==", + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.3.tgz", + "integrity": "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==", "dev": true, "license": "MIT", "dependencies": { @@ -3263,19 +3290,41 @@ } } }, - "node_modules/@rollup/plugin-terser": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", - "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==", + "node_modules/@rollup/plugin-replace": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-6.0.3.tgz", + "integrity": "sha512-J4RZarRvQAm5IF0/LwUUg+obsm+xZhYnbMXmXROyoSE1ATJe3oXSb9L5MMppdxP2ylNSjv6zFBwKYjcKMucVfA==", "dev": true, "license": "MIT", "dependencies": { - "serialize-javascript": "^6.0.1", + "@rollup/pluginutils": "^5.0.1", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-terser": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-1.0.0.tgz", + "integrity": "sha512-FnCxhTBx6bMOYQrar6C8h3scPt8/JwIzw3+AJ2K++6guogH5fYaIFia+zZuhqv0eo1RN7W1Pz630SyvLbDjhtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "serialize-javascript": "^7.0.3", "smob": "^1.0.0", "terser": "^5.17.4" }, "engines": { - "node": ">=14.0.0" + "node": ">=20.0.0" }, "peerDependencies": { "rollup": "^2.0.0||^3.0.0||^4.0.0" @@ -3287,9 +3336,9 @@ } }, "node_modules/@rollup/pluginutils": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", - "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.4.0.tgz", + "integrity": "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==", "dev": true, "license": "MIT", "dependencies": { @@ -3448,29 +3497,6 @@ "url": "https://github.com/sindresorhus/is?sponsor=1" } }, - "node_modules/@surma/rollup-plugin-off-main-thread": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", - "integrity": "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "ejs": "^3.1.6", - "json5": "^2.2.0", - "magic-string": "^0.25.0", - "string.prototype.matchall": "^4.0.6" - } - }, - "node_modules/@surma/rollup-plugin-off-main-thread/node_modules/magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "sourcemap-codec": "^1.4.8" - } - }, "node_modules/@swc/helpers": { "version": "0.5.18", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.18.tgz", @@ -3492,6 +3518,22 @@ "node": ">=10" } }, + "node_modules/@trickfilm400/rollup-plugin-off-main-thread": { + "version": "3.0.0-pre1", + "resolved": "https://registry.npmjs.org/@trickfilm400/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-3.0.0-pre1.tgz", + "integrity": "sha512-/67zpWDBLV+oYAEL682s1ktXL0HgqX76f6gaVGkGnVZlBbm1zd0v4Bz8MFF2GGhoX9rvfq3KSQHubFHwa6w6/Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "ejs": "^3.1.10", + "json5": "^2.2.3", + "magic-string": "^0.30.21", + "string.prototype.matchall": "^4.0.12" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@turf/along": { "version": "7.3.4", "resolved": "https://registry.npmjs.org/@turf/along/-/along-7.3.4.tgz", @@ -6198,9 +6240,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", "dev": true, "license": "MIT", "dependencies": { @@ -6335,15 +6377,15 @@ } }, "node_modules/@vitest/expect": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", - "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.6.tgz", + "integrity": "sha512-1+7q9BtaKzEmO+fmNT3kYvoNn5Y71XWAx2Q5HRim4tTVRQVRv4uJFAQ5FbK0OPUeNP/WmVCpxYxoJdvuHVjzBQ==", "dev": true, "license": "MIT", "dependencies": { "@types/chai": "^5.2.2", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", + "@vitest/spy": "3.2.6", + "@vitest/utils": "3.2.6", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" }, @@ -6352,13 +6394,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", - "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.6.tgz", + "integrity": "sha512-EZOrpDbkKotFAP7wPAQV1UIyoGOk4oX7ynWhBhLB7v+meMHbQhU16oPpIYGTTe4oFlhpryGpgpcZP/sin3hYuw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.2.4", + "@vitest/spy": "3.2.6", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, @@ -6379,9 +6421,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", - "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.6.tgz", + "integrity": "sha512-lb7XXXzmm2h2ASzFnRvQpDo6onT1NmMJA3tkGTWiBFtRJ9lxGY3d3mm/Apt36gej2bkkOVLL/yTOtufDaFa/jA==", "dev": true, "license": "MIT", "dependencies": { @@ -6392,13 +6434,13 @@ } }, "node_modules/@vitest/runner": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", - "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.6.tgz", + "integrity": "sha512-HYcoSj1w5tcgUnzoF0HcyaAQjpA1gj9ftUJ7iSJSuipc02jW9gKkigwZbjFldAfYHA1fa8UZVRftdMY5msWM9Q==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.2.4", + "@vitest/utils": "3.2.6", "pathe": "^2.0.3", "strip-literal": "^3.0.0" }, @@ -6407,13 +6449,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", - "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.6.tgz", + "integrity": "sha512-H+ZjNTWGpObenh0YnlBctAPnJSI20P81PL8BPzWpx54YXLLTm8hEsWawtcYLMrwvpK48hGxLLbCS+1KRXhsKhw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.4", + "@vitest/pretty-format": "3.2.6", "magic-string": "^0.30.17", "pathe": "^2.0.3" }, @@ -6422,9 +6464,9 @@ } }, "node_modules/@vitest/spy": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", - "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.6.tgz", + "integrity": "sha512-oq6BbH68WzcWmwtBrU9nqLeaXTR4XwJF7FSLkKEZo4i6eoXcrxjcwSuTvWBIRUTC6VC72nXYunzqgZA+IKdtxg==", "dev": true, "license": "MIT", "dependencies": { @@ -6435,13 +6477,13 @@ } }, "node_modules/@vitest/utils": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", - "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.6.tgz", + "integrity": "sha512-lI23nIs4bnT3T8NIoh+vFaz5s2/DdP0Jgt2jxwgWljvwn82cLJtyi/If+fjFyoLMGIOz0U/fKvWE0d4jsNQEfg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.4", + "@vitest/pretty-format": "3.2.6", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" }, @@ -6478,6 +6520,18 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", @@ -6652,27 +6706,28 @@ } }, "node_modules/axios": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", - "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz", + "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.11", + "follow-redirects": "^1.16.0", "form-data": "^4.0.5", - "proxy-from-env": "^1.1.0" + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" } }, "node_modules/axios/node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" + "hasown": "^2.0.4", + "mime-types": "^2.1.35" }, "engines": { "node": ">= 6" @@ -6809,9 +6864,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", "dev": true, "license": "MIT", "dependencies": { @@ -6945,15 +7000,15 @@ } }, "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", "set-function-length": "^1.2.2" }, "engines": { @@ -7263,9 +7318,9 @@ } }, "node_modules/cosmiconfig/node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", "license": "ISC", "engines": { "node": ">= 6" @@ -7889,9 +7944,9 @@ } }, "node_modules/es-abstract": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", - "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", "dev": true, "license": "MIT", "dependencies": { @@ -7957,6 +8012,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-abstract-get": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-abstract-get/-/es-abstract-get-1.0.0.tgz", + "integrity": "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.2", + "is-callable": "^1.2.7", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/es-cookie": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/es-cookie/-/es-cookie-1.3.2.tgz", @@ -7989,9 +8063,9 @@ "license": "MIT" }, "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -8016,15 +8090,18 @@ } }, "node_modules/es-to-primitive": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", - "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.4.tgz", + "integrity": "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==", "dev": true, "license": "MIT", "dependencies": { + "es-abstract-get": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", "is-callable": "^1.2.7", - "is-date-object": "^1.0.5", - "is-symbol": "^1.0.4" + "is-date-object": "^1.1.0", + "is-symbol": "^1.1.1" }, "engines": { "node": ">= 0.4" @@ -8284,6 +8361,19 @@ "node": ">=0.10.0" } }, + "node_modules/eta": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eta/-/eta-4.6.0.tgz", + "integrity": "sha512-lW6is4T1NFOYnmqGZIfvixqj7A7sSvScF+DN8EK6K58xI5MZ5UvYe0GjopxOXQtZvUn4eDdVuZ8XSoYWTMEKwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/bgub/eta?sponsor=1" + } + }, "node_modules/eventemitter3": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", @@ -8351,9 +8441,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz", + "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==", "dev": true, "funding": [ { @@ -8399,9 +8489,9 @@ } }, "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -8409,9 +8499,9 @@ } }, "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", "dev": true, "license": "MIT", "dependencies": { @@ -8481,16 +8571,16 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "funding": [ { "type": "individual", @@ -8558,15 +8648,15 @@ } }, "node_modules/form-data": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.4.tgz", - "integrity": "sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.5.tgz", + "integrity": "sha512-j23EibVLnp4zNXGW7LjryXYa2X6U/M96yoOX+ybZxwkYajdxRNEqYY3zhh7y0i6kfISKS2jr+EJq1YTUDEv5+w==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", + "hasown": "^2.0.4", "mime-types": "^2.1.35" }, "engines": { @@ -8616,6 +8706,21 @@ "node": ">=10" } }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -8626,18 +8731,21 @@ } }, "node_modules/function.prototype.name": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", - "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.2.0.tgz", + "integrity": "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", - "hasown": "^2.0.2", - "is-callable": "^1.2.7" + "has-property-descriptors": "^1.0.2", + "hasown": "^2.0.4", + "is-callable": "^1.2.7", + "is-document.all": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -8882,16 +8990,16 @@ } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz", - "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" } }, "node_modules/glob/node_modules/minimatch": { @@ -9095,9 +9203,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -9185,6 +9293,19 @@ "node": ">=10.19.0" } }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/hyphen": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/hyphen/-/hyphen-1.14.1.tgz", @@ -9428,6 +9549,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-document.all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-document.all/-/is-document.all-1.0.0.tgz", + "integrity": "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", @@ -9845,10 +9982,20 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -10158,15 +10305,15 @@ } }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "license": "MIT" }, "node_modules/lodash-es": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", - "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", "license": "MIT" }, "node_modules/lodash.debounce": { @@ -11022,9 +11169,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -11087,9 +11234,9 @@ } }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", + "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", "dev": true, "funding": [ { @@ -11107,7 +11254,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -11122,9 +11269,9 @@ "license": "MIT" }, "node_modules/postcss/node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", "dev": true, "funding": [ { @@ -11198,15 +11345,15 @@ }, "node_modules/protobuf-ts": { "version": "1.0.0", - "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#29d9765ce93c04ad70d795b80e9873fc2fc5f600", + "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#f783a2f01deccf8c94c11b74995de45182e7cfee", "dependencies": { "protobufjs": "^6.8.8" } }, "node_modules/protobufjs": { - "version": "6.11.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz", - "integrity": "sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==", + "version": "6.11.6", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.6.tgz", + "integrity": "sha512-k8BHqgPBOtrlougZZqF2uUk5Z7bN8f0wj+3e8M3hvtSv0NBAz4VBy5f6R5Nxq/l+i7mRFTgNZb2trxqTpHNY/A==", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { @@ -11230,16 +11377,19 @@ } }, "node_modules/protocol-buffers-schema": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", - "integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.1.tgz", + "integrity": "sha512-VG2K63Igkiv9p76tk1lilczEK1cT+kCjKtkdhw1dQZV3k3IXJbd3o6Ho8b9zJZaHSnT2hKe4I+ObmX9w6m5SmQ==", "license": "MIT" }, "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } }, "node_modules/pump": { "version": "3.0.3", @@ -11311,16 +11461,6 @@ "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==", "license": "MIT" }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, "node_modules/rbush": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz", @@ -11683,12 +11823,12 @@ } }, "node_modules/react-router": { - "version": "6.30.3", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz", - "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==", + "version": "6.30.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.4.tgz", + "integrity": "sha512-SVUsDe+DybHM/WmYKIVYhZh1o5Dcuf16yM6WjG02Q9XVFMZIJyHYhwrr6bFBXZkVP6z69kNkMyBCujt8FaFLJA==", "license": "MIT", "dependencies": { - "@remix-run/router": "1.23.2" + "@remix-run/router": "1.23.3" }, "engines": { "node": ">=14.0.0" @@ -11698,13 +11838,13 @@ } }, "node_modules/react-router-dom": { - "version": "6.30.3", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz", - "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==", + "version": "6.30.4", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.4.tgz", + "integrity": "sha512-q4HvNl+mmDdkS0g+MqiBZNteQJCuimWoOyHMy4T/RQLAn9Z29+E91QXRaxOujeMl2HTzRSS0KFPd7lxX3PjV0Q==", "license": "MIT", "dependencies": { - "@remix-run/router": "1.23.2", - "react-router": "6.30.3" + "@remix-run/router": "1.23.3", + "react-router": "6.30.4" }, "engines": { "node": ">=14.0.0" @@ -12190,15 +12330,15 @@ "license": "BSD-3-Clause" }, "node_modules/safe-array-concat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", - "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", "has-symbols": "^1.1.0", "isarray": "^2.0.5" }, @@ -12304,13 +12444,13 @@ } }, "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.7.tgz", + "integrity": "sha512-YAy8Od6KV+uuwUuU50np8fGB/Aues6Y0nAhA9y/hId74PlKUcme4pXcBD46NWKr1Q4osN/iseZ17YqO1XfmI8g==", "dev": true, "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" + "engines": { + "node": ">=20.0.0" } }, "node_modules/set-function-length": { @@ -12401,15 +12541,15 @@ } }, "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" }, @@ -12421,14 +12561,14 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -12518,9 +12658,9 @@ "license": "MIT" }, "node_modules/smob": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/smob/-/smob-1.6.1.tgz", - "integrity": "sha512-KAkBqZl3c2GvNgNhcoyJae1aKldDW0LO279wF9bk1PnluRTETKBq0WyzRXxEhoQLk56yHaOY4JCBEKDuJIET5g==", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/smob/-/smob-1.6.2.tgz", + "integrity": "sha512-RQsvleCbF8cVHEv+xuDGaA4pOizFqJ0GgjtMSRo6oP8pnN7WsigHgVGey6aILRBKv4W2YOMHLqbKdnB6hpB9fw==", "dev": true, "license": "MIT", "engines": { @@ -12611,14 +12751,6 @@ "node": ">=0.10.0" } }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "deprecated": "Please use @jridgewell/sourcemap-codec instead", - "dev": true, - "license": "MIT" - }, "node_modules/spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", @@ -12789,19 +12921,20 @@ } }, "node_modules/string.prototype.trim": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", - "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz", + "integrity": "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", "define-data-property": "^1.1.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-object-atoms": "^1.0.0", - "has-property-descriptors": "^1.0.2" + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "has-property-descriptors": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -12811,16 +12944,16 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", - "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.10.tgz", + "integrity": "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" + "es-object-atoms": "^1.1.2" }, "engines": { "node": ">= 0.4" @@ -13314,18 +13447,18 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", - "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.8.tgz", + "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0", - "reflect.getprototypeof": "^1.0.6" + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "possible-typed-array-names": "^1.1.0", + "reflect.getprototypeof": "^1.0.10" }, "engines": { "node": ">= 0.4" @@ -14380,9 +14513,9 @@ } }, "node_modules/vite": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", - "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", "dev": true, "license": "MIT", "dependencies": { @@ -14543,20 +14676,20 @@ } }, "node_modules/vitest": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", - "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.6.tgz", + "integrity": "sha512-xejya+bT/j/+R/AGa1XOfRxLmNUlLtlwjRsFUILF+xHfzElmGcmFydy2gqqIrd62ptIEfwVMofd19uNWD9L7Nw==", "dev": true, "license": "MIT", "dependencies": { "@types/chai": "^5.2.2", - "@vitest/expect": "3.2.4", - "@vitest/mocker": "3.2.4", - "@vitest/pretty-format": "^3.2.4", - "@vitest/runner": "3.2.4", - "@vitest/snapshot": "3.2.4", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", + "@vitest/expect": "3.2.6", + "@vitest/mocker": "3.2.6", + "@vitest/pretty-format": "^3.2.6", + "@vitest/runner": "3.2.6", + "@vitest/snapshot": "3.2.6", + "@vitest/spy": "3.2.6", + "@vitest/utils": "3.2.6", "chai": "^5.2.0", "debug": "^4.4.1", "expect-type": "^1.2.1", @@ -14586,8 +14719,8 @@ "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.2.4", - "@vitest/ui": "3.2.4", + "@vitest/browser": "3.2.6", + "@vitest/ui": "3.2.6", "happy-dom": "*", "jsdom": "*" }, @@ -14727,14 +14860,14 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.20", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", - "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "version": "1.1.22", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz", + "integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==", "dev": true, "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", + "call-bind": "^1.0.9", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", @@ -14776,30 +14909,30 @@ } }, "node_modules/workbox-background-sync": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-7.4.0.tgz", - "integrity": "sha512-8CB9OxKAgKZKyNMwfGZ1XESx89GryWTfI+V5yEj8sHjFH8MFelUwYXEyldEK6M6oKMmn807GoJFUEA1sC4XS9w==", + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-7.4.1.tgz", + "integrity": "sha512-HhT7KE8tOWDm02wRNshXUnUPofMlhenF2DBdUnDPOubhizzPeItkYTmAB6td1Z2cjYPa98vzEiPLEuzn5hN66g==", "dev": true, "license": "MIT", "dependencies": { "idb": "^7.0.1", - "workbox-core": "7.4.0" + "workbox-core": "7.4.1" } }, "node_modules/workbox-broadcast-update": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-7.4.0.tgz", - "integrity": "sha512-+eZQwoktlvo62cI0b+QBr40v5XjighxPq3Fzo9AWMiAosmpG5gxRHgTbGGhaJv/q/MFVxwFNGh/UwHZ/8K88lA==", + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-7.4.1.tgz", + "integrity": "sha512-uAlgslKLvbQY+suirIdnBCSYrcgBhjp81Nj4l1lj/Jmj0MJO2CJERnCJjT0GFVwmReV0N+zs78K6gqd5gr9/+A==", "dev": true, "license": "MIT", "dependencies": { - "workbox-core": "7.4.0" + "workbox-core": "7.4.1" } }, "node_modules/workbox-build": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-7.4.0.tgz", - "integrity": "sha512-Ntk1pWb0caOFIvwz/hfgrov/OJ45wPEhI5PbTywQcYjyZiVhT3UrwwUPl6TRYbTm4moaFYithYnl1lvZ8UjxcA==", + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-7.4.1.tgz", + "integrity": "sha512-SDhxIvEAde9Gy/5w4Yo1Jh/M49Z0qE3q0oteyE8zGq0DScxFqVBcCtIXFuLtmtxRQZCMbf0prco4VyEu3KBQuw==", "dev": true, "license": "MIT", "dependencies": { @@ -14807,39 +14940,39 @@ "@babel/core": "^7.24.4", "@babel/preset-env": "^7.11.0", "@babel/runtime": "^7.11.2", - "@rollup/plugin-babel": "^5.2.0", - "@rollup/plugin-node-resolve": "^15.2.3", - "@rollup/plugin-replace": "^2.4.1", - "@rollup/plugin-terser": "^0.4.3", - "@surma/rollup-plugin-off-main-thread": "^2.2.3", + "@rollup/plugin-babel": "^6.1.0", + "@rollup/plugin-node-resolve": "^16.0.3", + "@rollup/plugin-replace": "^6.0.3", + "@rollup/plugin-terser": "^1.0.0", + "@trickfilm400/rollup-plugin-off-main-thread": "^3.0.0-pre1", "ajv": "^8.6.0", "common-tags": "^1.8.0", + "eta": "^4.5.1", "fast-json-stable-stringify": "^2.1.0", "fs-extra": "^9.0.1", "glob": "^11.0.1", - "lodash": "^4.17.20", "pretty-bytes": "^5.3.0", - "rollup": "^2.79.2", + "rollup": "^4.53.3", "source-map": "^0.8.0-beta.0", "stringify-object": "^3.3.0", "strip-comments": "^2.0.1", "tempy": "^0.6.0", "upath": "^1.2.0", - "workbox-background-sync": "7.4.0", - "workbox-broadcast-update": "7.4.0", - "workbox-cacheable-response": "7.4.0", - "workbox-core": "7.4.0", - "workbox-expiration": "7.4.0", - "workbox-google-analytics": "7.4.0", - "workbox-navigation-preload": "7.4.0", - "workbox-precaching": "7.4.0", - "workbox-range-requests": "7.4.0", - "workbox-recipes": "7.4.0", - "workbox-routing": "7.4.0", - "workbox-strategies": "7.4.0", - "workbox-streams": "7.4.0", - "workbox-sw": "7.4.0", - "workbox-window": "7.4.0" + "workbox-background-sync": "7.4.1", + "workbox-broadcast-update": "7.4.1", + "workbox-cacheable-response": "7.4.1", + "workbox-core": "7.4.1", + "workbox-expiration": "7.4.1", + "workbox-google-analytics": "7.4.1", + "workbox-navigation-preload": "7.4.1", + "workbox-precaching": "7.4.1", + "workbox-range-requests": "7.4.1", + "workbox-recipes": "7.4.1", + "workbox-routing": "7.4.1", + "workbox-strategies": "7.4.1", + "workbox-streams": "7.4.1", + "workbox-sw": "7.4.1", + "workbox-window": "7.4.1" }, "engines": { "node": ">=20.0.0" @@ -14863,69 +14996,6 @@ "ajv": ">=8" } }, - "node_modules/workbox-build/node_modules/@rollup/plugin-babel": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", - "integrity": "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.10.4", - "@rollup/pluginutils": "^3.1.0" - }, - "engines": { - "node": ">= 10.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0", - "@types/babel__core": "^7.1.9", - "rollup": "^1.20.0||^2.0.0" - }, - "peerDependenciesMeta": { - "@types/babel__core": { - "optional": true - } - } - }, - "node_modules/workbox-build/node_modules/@rollup/plugin-replace": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz", - "integrity": "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^3.1.0", - "magic-string": "^0.25.7" - }, - "peerDependencies": { - "rollup": "^1.20.0 || ^2.0.0" - } - }, - "node_modules/workbox-build/node_modules/@rollup/pluginutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", - "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "0.0.39", - "estree-walker": "^1.0.1", - "picomatch": "^2.2.2" - }, - "engines": { - "node": ">= 8.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" - } - }, - "node_modules/workbox-build/node_modules/@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", - "dev": true, - "license": "MIT" - }, "node_modules/workbox-build/node_modules/ajv": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", @@ -14943,13 +15013,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/workbox-build/node_modules/estree-walker": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", - "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", - "dev": true, - "license": "MIT" - }, "node_modules/workbox-build/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -14957,29 +15020,6 @@ "dev": true, "license": "MIT" }, - "node_modules/workbox-build/node_modules/magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "sourcemap-codec": "^1.4.8" - } - }, - "node_modules/workbox-build/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/workbox-build/node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -14993,22 +15033,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/workbox-build/node_modules/rollup": { - "version": "2.80.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.80.0.tgz", - "integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==", - "dev": true, - "license": "MIT", - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=10.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, "node_modules/workbox-build/node_modules/source-map": { "version": "0.8.0-beta.0", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", @@ -15053,140 +15077,140 @@ } }, "node_modules/workbox-cacheable-response": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-7.4.0.tgz", - "integrity": "sha512-0Fb8795zg/x23ISFkAc7lbWes6vbw34DGFIMw31cwuHPgDEC/5EYm6m/ZkylLX0EnEbbOyOCLjKgFS/Z5g0HeQ==", + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-7.4.1.tgz", + "integrity": "sha512-8xaFoJdDc2OjrlbbL3gEeBO1WKcMwRqwLRupgqahYXu75yXajPLuwrbXMrIGZuWYXrQwk0xDjOxZ/ujCy/oJYw==", "dev": true, "license": "MIT", "dependencies": { - "workbox-core": "7.4.0" + "workbox-core": "7.4.1" } }, "node_modules/workbox-core": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-7.4.0.tgz", - "integrity": "sha512-6BMfd8tYEnN4baG4emG9U0hdXM4gGuDU3ectXuVHnj71vwxTFI7WOpQJC4siTOlVtGqCUtj0ZQNsrvi6kZZTAQ==", + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-7.4.1.tgz", + "integrity": "sha512-DT+vu46eh/2vRsSHTY4Xmc32Z1rr9PRlQUXr1Dx30ZuXRWwOsvZgGgcwxcasubQLQmbTNYZjv44LkBAQ4tT5tQ==", "dev": true, "license": "MIT" }, "node_modules/workbox-expiration": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-7.4.0.tgz", - "integrity": "sha512-V50p4BxYhtA80eOvulu8xVfPBgZbkxJ1Jr8UUn0rvqjGhLDqKNtfrDfjJKnLz2U8fO2xGQJTx/SKXNTzHOjnHw==", + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-7.4.1.tgz", + "integrity": "sha512-lRKUF7b+OGbeXkQk1s6MHXOa3d7Xxf7Of31W6c6hCfipfIyrtdWZ89stq21AHZMaoG7VNFoHply4Ox+rU31TWg==", "dev": true, "license": "MIT", "dependencies": { "idb": "^7.0.1", - "workbox-core": "7.4.0" + "workbox-core": "7.4.1" } }, "node_modules/workbox-google-analytics": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-7.4.0.tgz", - "integrity": "sha512-MVPXQslRF6YHkzGoFw1A4GIB8GrKym/A5+jYDUSL+AeJw4ytQGrozYdiZqUW1TPQHW8isBCBtyFJergUXyNoWQ==", + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-7.4.1.tgz", + "integrity": "sha512-Mks1JwLEt++ZAkF6sS1OpSh9RtAMIsiDgRpK+codiHGIPXeaUOgi4cPc3GFadUl8V5QPeypEk8Oxgl3HlwVzHw==", "dev": true, "license": "MIT", "dependencies": { - "workbox-background-sync": "7.4.0", - "workbox-core": "7.4.0", - "workbox-routing": "7.4.0", - "workbox-strategies": "7.4.0" + "workbox-background-sync": "7.4.1", + "workbox-core": "7.4.1", + "workbox-routing": "7.4.1", + "workbox-strategies": "7.4.1" } }, "node_modules/workbox-navigation-preload": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-7.4.0.tgz", - "integrity": "sha512-etzftSgdQfjMcfPgbfaZCfM2QuR1P+4o8uCA2s4rf3chtKTq/Om7g/qvEOcZkG6v7JZOSOxVYQiOu6PbAZgU6w==", + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-7.4.1.tgz", + "integrity": "sha512-C4KVsjPcYKJOhr631AxR9XoG2rLF3QiTk5aMv36MXOjtWvm8axwNFAtKUPGsWUwLXXAMgYM1En7fsvndaXeXRQ==", "dev": true, "license": "MIT", "dependencies": { - "workbox-core": "7.4.0" + "workbox-core": "7.4.1" } }, "node_modules/workbox-precaching": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-7.4.0.tgz", - "integrity": "sha512-VQs37T6jDqf1rTxUJZXRl3yjZMf5JX/vDPhmx2CPgDDKXATzEoqyRqhYnRoxl6Kr0rqaQlp32i9rtG5zTzIlNg==", + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-7.4.1.tgz", + "integrity": "sha512-cdr/9qByww7yzEp7zg/qI4ukUrrNjQLgN+ONQRpjy/VqGQXwkgHwr00KksGJK8v0VifwDXBb8a4cWNZH71jn3Q==", "dev": true, "license": "MIT", "dependencies": { - "workbox-core": "7.4.0", - "workbox-routing": "7.4.0", - "workbox-strategies": "7.4.0" + "workbox-core": "7.4.1", + "workbox-routing": "7.4.1", + "workbox-strategies": "7.4.1" } }, "node_modules/workbox-range-requests": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-7.4.0.tgz", - "integrity": "sha512-3Vq854ZNuP6Y0KZOQWLaLC9FfM7ZaE+iuQl4VhADXybwzr4z/sMmnLgTeUZLq5PaDlcJBxYXQ3U91V7dwAIfvw==", + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-7.4.1.tgz", + "integrity": "sha512-7i2oxAUE82gHdAJBCAQ04JzNOdRPqzuOzGfoUyJpFSmeqBNYGPrAH8GPoPjUQTfp+NycwrD2H68VtuF8qxv0vQ==", "dev": true, "license": "MIT", "dependencies": { - "workbox-core": "7.4.0" + "workbox-core": "7.4.1" } }, "node_modules/workbox-recipes": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-7.4.0.tgz", - "integrity": "sha512-kOkWvsAn4H8GvAkwfJTbwINdv4voFoiE9hbezgB1sb/0NLyTG4rE7l6LvS8lLk5QIRIto+DjXLuAuG3Vmt3cxQ==", + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-7.4.1.tgz", + "integrity": "sha512-gnbVfmV4/TtmQaM4x9AtuXhcdstJsep3XMVeztOrQVPT+R6+6DeBjGTCQ7fFCXm+4GEHUA5VEBTyi5+4gWGeog==", "dev": true, "license": "MIT", "dependencies": { - "workbox-cacheable-response": "7.4.0", - "workbox-core": "7.4.0", - "workbox-expiration": "7.4.0", - "workbox-precaching": "7.4.0", - "workbox-routing": "7.4.0", - "workbox-strategies": "7.4.0" + "workbox-cacheable-response": "7.4.1", + "workbox-core": "7.4.1", + "workbox-expiration": "7.4.1", + "workbox-precaching": "7.4.1", + "workbox-routing": "7.4.1", + "workbox-strategies": "7.4.1" } }, "node_modules/workbox-routing": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-7.4.0.tgz", - "integrity": "sha512-C/ooj5uBWYAhAqwmU8HYQJdOjjDKBp9MzTQ+otpMmd+q0eF59K+NuXUek34wbL0RFrIXe/KKT+tUWcZcBqxbHQ==", + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-7.4.1.tgz", + "integrity": "sha512-yubJGErZOusuidAenaL5ypfhQOa7urxP/f8E0ws7FPb4039RiWXUWBAyUkmUoOL/BcQGen3h0J8872d51IYxtA==", "dev": true, "license": "MIT", "dependencies": { - "workbox-core": "7.4.0" + "workbox-core": "7.4.1" } }, "node_modules/workbox-strategies": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-7.4.0.tgz", - "integrity": "sha512-T4hVqIi5A4mHi92+5EppMX3cLaVywDp8nsyUgJhOZxcfSV/eQofcOA6/EMo5rnTNmNTpw0rUgjAI6LaVullPpg==", + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-7.4.1.tgz", + "integrity": "sha512-GZxpaw9NbmOelj7667uZ2kpk5BFpOGbO4X0qjwh5ls8XQ8C+Lha5LQchTiUzsTFSS+NlUpftYAyOVXvQUrcqOQ==", "dev": true, "license": "MIT", "dependencies": { - "workbox-core": "7.4.0" + "workbox-core": "7.4.1" } }, "node_modules/workbox-streams": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-7.4.0.tgz", - "integrity": "sha512-QHPBQrey7hQbnTs5GrEVoWz7RhHJXnPT+12qqWM378orDMo5VMJLCkCM1cnCk+8Eq92lccx/VgRZ7WAzZWbSLg==", + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-7.4.1.tgz", + "integrity": "sha512-HWWtraKUbJknd9kgqGcpQ3G114HOPYvqs8HaJMDs2ebLNAimDkVDaWfAXE6Ybl+m8U6KsCE6pWyLYuigWmnAXw==", "dev": true, "license": "MIT", "dependencies": { - "workbox-core": "7.4.0", - "workbox-routing": "7.4.0" + "workbox-core": "7.4.1", + "workbox-routing": "7.4.1" } }, "node_modules/workbox-sw": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-7.4.0.tgz", - "integrity": "sha512-ltU+Kr3qWR6BtbdlMnCjobZKzeV1hN+S6UvDywBrwM19TTyqA03X66dzw1tEIdJvQ4lYKkBFox6IAEhoSEZ8Xw==", + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-7.4.1.tgz", + "integrity": "sha512-fez5f2DUlDJWTFYkCWQpY10N8gtztd849NswCbVFk0QlcSM4HT5A8x4g4ii650yem4I8tHY0R7JZahwp3ltIPw==", "dev": true, "license": "MIT" }, "node_modules/workbox-window": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-7.4.0.tgz", - "integrity": "sha512-/bIYdBLAVsNR3v7gYGaV4pQW3M3kEPx5E8vDxGvxo6khTrGtSSCS7QiFKv9ogzBgZiy0OXLP9zO28U/1nF1mfw==", + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-7.4.1.tgz", + "integrity": "sha512-notZDH2u8VXaqyuD7xaqIfEFi6SRM4SUSd7ewe9PDsVqADuepxX2ZMY3uvuZGxzY5ZOsGC/vD3A/3smFtJt4/A==", "dev": true, "license": "MIT", "dependencies": { "@types/trusted-types": "^2.0.2", - "workbox-core": "7.4.0" + "workbox-core": "7.4.1" } }, "node_modules/wrappy": { @@ -15212,9 +15236,9 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", - "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", "dev": true, "license": "ISC", "optional": true, From 71fb67bba4d22d41ea5d8a6ecb0a94304a326446 Mon Sep 17 00:00:00 2001 From: Carter Date: Tue, 7 Jul 2026 11:28:34 -0600 Subject: [PATCH 20/30] pending changes indication for device and component changes --- src/common/PendingChangesChip.tsx | 46 ++++++++++++++++ src/common/PendingChangesIndicator.tsx | 27 +++++++++ src/common/StatusChip.tsx | 20 ------- src/component/ComponentCard.tsx | 40 +++++++++++--- src/device/DeviceOverview.tsx | 12 ++-- src/hooks/index.ts | 1 + src/hooks/usePendingChanges.ts | 31 +++++++++++ src/interactions/InteractionsOverview.tsx | 20 +++---- src/pages/Device.tsx | 24 ++------ src/pages/DeviceComponent.tsx | 64 ++++++++++++++++++++-- src/pbHelpers/Status.tsx | 67 ----------------------- src/utils/index.ts | 1 + src/utils/syncStatus.ts | 41 ++++++++++++++ 13 files changed, 259 insertions(+), 135 deletions(-) create mode 100644 src/common/PendingChangesChip.tsx create mode 100644 src/common/PendingChangesIndicator.tsx delete mode 100644 src/common/StatusChip.tsx create mode 100644 src/hooks/usePendingChanges.ts delete mode 100644 src/pbHelpers/Status.tsx create mode 100644 src/utils/syncStatus.ts diff --git a/src/common/PendingChangesChip.tsx b/src/common/PendingChangesChip.tsx new file mode 100644 index 0000000..f2b393a --- /dev/null +++ b/src/common/PendingChangesChip.tsx @@ -0,0 +1,46 @@ +import { Chip, CircularProgress, Tooltip } from "@mui/material"; +import { CheckCircleOutline } from "@mui/icons-material"; + +interface Props { + pending: boolean; + accepted: boolean; + size?: "small" | "medium"; +} + +/** + * Capsule tag shown alongside the other status chips while changes are + * waiting to be synced to a device or component. Shows a small loading + * circle while pending and a checkmark once the change is accepted. + * Derive the flags with usePendingChanges. + */ +export default function PendingChangesChip(props: Props) { + const { pending, accepted, size } = props; + + if (pending) { + return ( + + } + size={size} + /> + + ); + } + + if (accepted) { + return ( + + } + size={size} + /> + + ); + } + + return null; +} diff --git a/src/common/PendingChangesIndicator.tsx b/src/common/PendingChangesIndicator.tsx new file mode 100644 index 0000000..2d3ff59 --- /dev/null +++ b/src/common/PendingChangesIndicator.tsx @@ -0,0 +1,27 @@ +import { CircularProgress, Grid2 as Grid } from "@mui/material"; +import { CheckCircleOutline } from "@mui/icons-material"; + +interface Props { + pending: boolean; + accepted: boolean; + text: string; +} + +/** + * Small inline status line used inside cards: a loading circle beside the + * caption while changes are pending, and a checkmark once they are accepted. + * Derive the flags with usePendingChanges (or per-item tracking like + * InteractionsOverview does). + */ +export default function PendingChangesIndicator(props: Props) { + const { pending, accepted, text } = props; + if (!pending && !accepted) return null; + + return ( + + {pending && } + {accepted && } + {text} + + ); +} diff --git a/src/common/StatusChip.tsx b/src/common/StatusChip.tsx deleted file mode 100644 index c1b16b3..0000000 --- a/src/common/StatusChip.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { Chip, Tooltip } from "@mui/material"; -import { getStatusHelper } from "pbHelpers/Status"; - -interface Props { - status: string; - size?: "small" | "medium"; -} - -export default function StatusChip(props: Props) { - const { status, size } = props; - const statusHelper = getStatusHelper(status); - const icon = statusHelper.icon; - const description = statusHelper.description; - - return icon !== null ? ( - - - - ) : null; -} diff --git a/src/component/ComponentCard.tsx b/src/component/ComponentCard.tsx index 8355abe..8b9b767 100644 --- a/src/component/ComponentCard.tsx +++ b/src/component/ComponentCard.tsx @@ -13,11 +13,13 @@ import { } from "@mui/material"; import Grid from '@mui/material/Grid2'; import EventBlocker from "common/EventBlocker"; +import PendingChangesIndicator from "common/PendingChangesIndicator"; import MeasurementSummary from "component/MeasurementSummary"; -import { useComponentAPI, useSnackbar, useThemeType } from "hooks"; +import { useComponentAPI, usePendingChanges, useSnackbar, useThemeType } from "hooks"; import InteractionsOverview from "interactions/InteractionsOverview"; import { cloneDeep } from "lodash"; import { Component, Device, Interaction } from "models"; +import moment from "moment"; import { getFriendlyAddressTypeName, getHumanReadableAddress } from "pbHelpers/AddressType"; import { controllerModeLabel } from "pbHelpers/Component"; import { @@ -121,6 +123,9 @@ export default function ComponentCard(props: Props) { const location = useLocation() const [sensors, setSensors] = useState([]); const [{ user, showErrors, as }] = useGlobalState(); + const { pending: pendingChanges, accepted: changesAccepted } = usePendingChanges( + component.status.synced + ); const updateControllerState = (checked: boolean) => { let updatedComponent = cloneDeep(component); @@ -133,6 +138,8 @@ export default function ComponentCard(props: Props) { .update(device.id(), updatedComponent.settings, undefined, undefined, as) .then(() => { success(component.name() + "'s mode was set to " + describe); + updatedComponent.status.synced = false; + updatedComponent.status.lastUpdate = moment().toISOString(); refreshCallback(updatedComponent); }) .catch(() => { @@ -287,19 +294,34 @@ export default function ComponentCard(props: Props) { className={classes.cardHeader} titleTypographyProps={{ variant: "subtitle1" }} subheader={ - // !newStructure ? ( - // - // ) : ( + + {(pendingChanges || changesAccepted) && ( + + + + )} + {/* !newStructure ? ( + + ) : ( */} - // ) + {/* ) */} + } action={ diff --git a/src/device/DeviceOverview.tsx b/src/device/DeviceOverview.tsx index 0115568..ac20e89 100644 --- a/src/device/DeviceOverview.tsx +++ b/src/device/DeviceOverview.tsx @@ -1,9 +1,9 @@ import { Chip, Grid2 as Grid, Theme, Tooltip, Skeleton } from "@mui/material"; import { DataUsage, SimCard, Launch, DeviceHub } from "@mui/icons-material"; -import StatusChip from "common/StatusChip"; +import PendingChangesChip from "common/PendingChangesChip"; import DeviceTags from "device/DeviceTags"; import VersionChip from "device/VersionChip"; -import { useSnackbar, useWidth, usePrevious } from "hooks"; +import { useSnackbar, useWidth, usePendingChanges, usePrevious } from "hooks"; import { Device, latestFirmwareVersion, Component } from "models"; import moment from "moment"; import { describeConnectivity } from "pbHelpers/Connectivity"; @@ -54,6 +54,10 @@ export default function DeviceOverview(props: Props) { // const match = useRouteMatch(); const [powerComponent, setPowerComponent] = useState(); const [modemComponent, setModemComponent] = useState(); + const { pending: pendingChanges, accepted: changesAccepted } = usePendingChanges( + device.status.synced, + !loading && device.id() > 0 + ); // const [hologramDialog, setHologramDialog] = useState(false); useEffect(() => { @@ -228,9 +232,9 @@ export default function DeviceOverview(props: Props) { )} - {!device.status.synced && ( + {(pendingChanges || changesAccepted) && ( - + )} {device.settings.needsBusControl && ( diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 0bd5baf..e93b9d5 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -26,6 +26,7 @@ export { export * from "./useDebounce"; // export * from "./useForceUpdate"; // export * from "./useInterval"; +export * from "./usePendingChanges"; export * from "./usePrevious"; export * from "./useThemeType"; export * from "./useWidth"; diff --git a/src/hooks/usePendingChanges.ts b/src/hooks/usePendingChanges.ts new file mode 100644 index 0000000..5852adb --- /dev/null +++ b/src/hooks/usePendingChanges.ts @@ -0,0 +1,31 @@ +import { useEffect, useRef, useState } from "react"; + +/** + * Tracks a synced flag and reports whether changes are pending or were + * accepted while mounted (a false -> true transition, e.g. from a websocket + * status update). Pass enabled=false until real data has loaded so the + * initial placeholder -> loaded flip is not mistaken for an accepted change. + */ +export function usePendingChanges(synced: boolean, enabled: boolean = true) { + const prevSynced = useRef(undefined); + const [accepted, setAccepted] = useState(false); + + useEffect(() => { + if (!enabled) { + prevSynced.current = undefined; + setAccepted(false); + return; + } + if (prevSynced.current === false && synced) { + setAccepted(true); + } else if (!synced) { + setAccepted(false); + } + prevSynced.current = synced; + }, [synced, enabled]); + + return { + pending: enabled && !synced, + accepted: enabled && synced && accepted + }; +} diff --git a/src/interactions/InteractionsOverview.tsx b/src/interactions/InteractionsOverview.tsx index 450c44f..68757cb 100644 --- a/src/interactions/InteractionsOverview.tsx +++ b/src/interactions/InteractionsOverview.tsx @@ -4,7 +4,6 @@ import { CardActions, CardContent, CardHeader, - CircularProgress, darken, Grid2 as Grid, IconButton, @@ -15,7 +14,8 @@ import { Tooltip, Typography } from "@mui/material"; -import { CheckCircleOutline, Settings } from "@mui/icons-material"; +import { Settings } from "@mui/icons-material"; +import PendingChangesIndicator from "common/PendingChangesIndicator"; import { useInteractionsAPI, usePrevious, useSnackbar } from "hooks"; import { cloneDeep } from "lodash"; import { Component, Device, Interaction } from "models"; @@ -329,17 +329,11 @@ export default function InteractionsOverview(props: Props) { title={interactionResultText(interaction, sink)} subheader={ statusText ? ( - - {isPending && } - {isAccepted && ( - - )} - - {statusText} - - + ) : ""} subheaderTypographyProps={{ variant: "caption" }} action={action} diff --git a/src/pages/Device.tsx b/src/pages/Device.tsx index 733d621..801c1f1 100644 --- a/src/pages/Device.tsx +++ b/src/pages/Device.tsx @@ -19,7 +19,7 @@ import { DeviceAvailabilityMap, FindAvailablePositions, OffsetAvailabilityMap } import ComponentCard from "component/ComponentCard"; import { sameComponentID, sortComponents } from "pbHelpers/Component"; import { isController } from "pbHelpers/ComponentType"; -import { or } from "utils"; +import { getTimestampMillis, isStaleStatusUpdate, or } from "utils"; import ComponentDiagnostics from "component/ComponentDiagnostics"; import DeviceWizard from "device/DeviceWizard"; import DeviceScannedComponents from "device/autoDetect/DeviceScannedComponents"; @@ -27,12 +27,6 @@ import { useWebSocket } from "hooks/useWebSocket"; type TimestampedReading = { timestamp?: string }; -function getTimestampMillis(timestamp?: string): number | undefined { - if (!timestamp) return undefined; - const parsed = Date.parse(timestamp); - return Number.isNaN(parsed) ? undefined : parsed; -} - function pickNewestReading( current: T | null | undefined, incoming: T | null | undefined @@ -270,6 +264,10 @@ export default function DevicePage() { if (!data) return; const { key, component } = data; setComponents((prev) => { + const existing = prev.get(key); + if (existing && isStaleStatusUpdate(existing.status, component.status)) { + return prev; + } const updated = new Map(prev); updated.set(key, component); return updated; @@ -315,17 +313,7 @@ export default function DevicePage() { return [...prev, interaction]; } const existing = prev[index]; - const existingLastUpdate = getTimestampMillis(existing.status.lastUpdate); - const incomingLastUpdate = getTimestampMillis(interaction.status.lastUpdate); - const incomingLastSynced = getTimestampMillis(interaction.status.lastSynced); - if (existingLastUpdate !== undefined && incomingLastUpdate !== undefined && - existingLastUpdate > incomingLastUpdate) { - return prev; - } - if (!existing.status.synced && interaction.status.synced && - incomingLastUpdate !== undefined && - existingLastUpdate === incomingLastUpdate && - (incomingLastSynced === undefined || incomingLastSynced < incomingLastUpdate)) { + if (isStaleStatusUpdate(existing.status, interaction.status)) { return prev; } const updated = [...prev]; diff --git a/src/pages/DeviceComponent.tsx b/src/pages/DeviceComponent.tsx index a245e51..e6ab9fa 100644 --- a/src/pages/DeviceComponent.tsx +++ b/src/pages/DeviceComponent.tsx @@ -4,7 +4,7 @@ import { NextMeasurementChip } from "common/NextMeasurementChip"; import ResponsiveTable, { Column } from "common/ResponsiveTable"; // import { getTableIcons } from "common/ResponsiveTable"; import SmartBreadcrumb from "common/SmartBreadcrumb"; -import StatusChip from "common/StatusChip"; +import PendingChangesChip from "common/PendingChangesChip"; import { GetDefaultDateRange } from "common/time/DateRange"; import ComponentActions from "component/ComponentActions"; import ComponentChart from "component/ComponentChart"; @@ -16,11 +16,14 @@ import { useComponentAPI, useDeviceAPI, useGroupAPI, + useHTTP, useInteractionsAPI, + usePendingChanges, usePrevious, useSnackbar, useUserAPI } from "hooks"; +import { useWebSocket } from "hooks/useWebSocket"; import InteractionChip from "interactions/InteractionChip"; import InteractionSettings from "interactions/InteractionSettings"; import { cloneDeep } from "lodash"; @@ -40,9 +43,10 @@ import { getDefaultInteraction } from "pbHelpers/Interaction"; import { pond } from "protobuf-ts/pond"; import { useGlobalState } from "providers"; import { useTeamAPI } from "providers/pond/teamAPI"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useParams } from "react-router-dom"; // import { useRouteMatch } from "react-router"; +import { isStaleStatusUpdate } from "utils/syncStatus"; import { or } from "utils/types"; const useStyles = makeStyles((theme: Theme) => { @@ -142,6 +146,55 @@ export default function DeviceComponent() { const [deviceComponentPrefs, setDeviceComponentPrefs] = useState< pond.DeviceComponentPreferences >(); + const { token } = useHTTP(); + const componentPending = usePendingChanges( + component.status.synced, + !loadingInitial && component.key() !== "" + ); + + const liveContextKeys = useMemo(() => getContextKeys(), []); + const liveContextTypes = useMemo(() => getContextTypes(), []); + /** Matches the REST calls: omit ?as= when first context type is "team". */ + const liveAs = + as && !(liveContextTypes.length > 0 && liveContextTypes[0] === "team") + ? as + : undefined; + + // --- Real-time component updates --- + // Streams component changes for this device, including status changes + // when the device accepts pending component updates. + useWebSocket<{ key: string; component: Component } | null>({ + path: `/v1/live/devices/${deviceID}/components`, + parse: (e) => { + try { + const raw = JSON.parse(e.data); + const comp = Component.any(raw); + if (!comp?.settings) { + console.warn("[ws] received component without settings:", raw); + return null; + } + return { key: comp.key(), component: comp }; + } catch (err) { + console.warn("[ws] failed to parse component:", err); + return null; + } + }, + onMessage: (data) => { + if (!data || data.key !== componentID) return; + setComponent((prev) => { + if (prev.key() !== data.key) return prev; + if (isStaleStatusUpdate(prev.status, data.component.status)) { + return prev; + } + return data.component; + }); + }, + keys: liveContextKeys, + types: liveContextTypes, + as: liveAs, + token, + enabled: !!deviceID && componentID !== "", + }); const setDefaultState = () => { setDevice(Device.create()); @@ -389,9 +442,12 @@ export default function DeviceComponent() { alignItems="center" wrap="nowrap" className={classes.overviewContainer}> - {!component.status.synced && ( + {(componentPending.pending || componentPending.accepted) && ( - + )} diff --git a/src/pbHelpers/Status.tsx b/src/pbHelpers/Status.tsx deleted file mode 100644 index 54d6f70..0000000 --- a/src/pbHelpers/Status.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { cyan } from "@mui/material/colors"; -import AcceptedIcon from "@mui/icons-material/CloudDone"; -import PendingIcon from "@mui/icons-material/CloudQueueTwoTone"; -import RejectedIcon from "@mui/icons-material/SmsFailed"; - -export interface StatusHelper { - description: string; - icon: any; -} - -const Unknown: StatusHelper = { - description: "Status unknown", - icon: null -}; - -const Pending: StatusHelper = { - description: "Pending changes", - icon: -}; - -const Stale: StatusHelper = { - description: "Stale update", - icon: null -}; - -const Accepted: StatusHelper = { - description: "Settings synced", - icon: -}; - -const Rejected: StatusHelper = { - description: "Update failed", - icon: -}; - -const Received: StatusHelper = { - description: "Settings synced", - icon: -}; - -const STATUS_MAP = new Map([ - ["pending", Pending], - ["stale", Stale], - ["accepted", Accepted], - ["rejected", Rejected], - ["received", Received] -]); - -export function getStatusHelper(status: string): StatusHelper { - const statuses = Array.from(STATUS_MAP.keys()); - for (var i = 0; i < statuses.length; i++) { - let key = statuses[i]; - if (status === key) { - return STATUS_MAP.get(key) as StatusHelper; - } - } - - return Unknown; -} - -export function getStatusDescription(status: string): string { - return getStatusHelper(status).description; -} - -export function getStatusIcon(status: string): any { - return getStatusHelper(status).icon; -} diff --git a/src/utils/index.ts b/src/utils/index.ts index 5a5ef3f..29ff37c 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -2,5 +2,6 @@ export * from "./download"; export * from "./environment"; export * from "./numbers"; export * from "./strings"; +export * from "./syncStatus"; export * from "./types"; export * from "./units"; diff --git a/src/utils/syncStatus.ts b/src/utils/syncStatus.ts new file mode 100644 index 0000000..107c2a8 --- /dev/null +++ b/src/utils/syncStatus.ts @@ -0,0 +1,41 @@ +export function getTimestampMillis(timestamp?: string): number | undefined { + if (!timestamp) return undefined; + const parsed = Date.parse(timestamp); + return Number.isNaN(parsed) ? undefined : parsed; +} + +/** The sync-tracking fields shared by ComponentStatus and InteractionStatus. */ +export interface SyncStatusLike { + synced: boolean; + lastUpdate?: string; + lastSynced?: string; +} + +/** + * Decides whether an incoming websocket status message is stale and should + * not replace the status we already hold. A message is stale when it was + * generated before our latest local update, including a synced echo that + * predates a pending change we just submitted. + */ +export function isStaleStatusUpdate(existing: SyncStatusLike, incoming: SyncStatusLike): boolean { + const existingLastUpdate = getTimestampMillis(existing.lastUpdate); + const incomingLastUpdate = getTimestampMillis(incoming.lastUpdate); + const incomingLastSynced = getTimestampMillis(incoming.lastSynced); + if ( + existingLastUpdate !== undefined && + incomingLastUpdate !== undefined && + existingLastUpdate > incomingLastUpdate + ) { + return true; + } + if ( + !existing.synced && + incoming.synced && + incomingLastUpdate !== undefined && + existingLastUpdate === incomingLastUpdate && + (incomingLastSynced === undefined || incomingLastSynced < incomingLastUpdate) + ) { + return true; + } + return false; +} From 2c17fb655c52871eba4fd8b7349502fc9286aee0 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Wed, 8 Jul 2026 11:01:45 -0600 Subject: [PATCH 21/30] hotfix to add as to the historyBetween function in the component API so that it works when viewing as a team without direct user permission to it --- src/providers/pond/componentAPI.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/providers/pond/componentAPI.tsx b/src/providers/pond/componentAPI.tsx index a224952..e78b75a 100644 --- a/src/providers/pond/componentAPI.tsx +++ b/src/providers/pond/componentAPI.tsx @@ -417,7 +417,8 @@ export default function ComponentProvider(props: PropsWithChildren) { "&end=" + end + (keys ? "&keys=" + keys : "&keys=" + [deviceId.toString()]) + - (types ? "&types=" + types : "&types=" + ["device"]) + (types ? "&types=" + types : "&types=" + ["device"]) + + (as ? "&as=" + as : "") ) return new Promise>((resolve,reject) => { get(url).then(resp => { From 3fc663a4b601debe6fef70bfdefa2d4f99495274 Mon Sep 17 00:00:00 2001 From: Carter Date: Wed, 8 Jul 2026 15:56:03 -0600 Subject: [PATCH 22/30] removed interaction logs --- src/interactions/InteractionsOverview.tsx | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/src/interactions/InteractionsOverview.tsx b/src/interactions/InteractionsOverview.tsx index 450c44f..cbdc170 100644 --- a/src/interactions/InteractionsOverview.tsx +++ b/src/interactions/InteractionsOverview.tsx @@ -87,15 +87,6 @@ export default function InteractionsOverview(props: Props) { undefined ); - useEffect(() => { - console.log("[interactions:mount] initial interactions:", props.interactions.map(i => ({ - key: i.key(), - synced: i.status.synced, - lastUpdate: i.status.lastUpdate, - lastSynced: i.status.lastSynced, - }))); - }, []); - useEffect(() => { if (components !== prevComponents) { let initMappedComponents: Map = new Map(); @@ -106,17 +97,6 @@ export default function InteractionsOverview(props: Props) { } if (props.interactions !== prevInteractions) { - console.log("[interactions:effect] props.interactions changed, prevInteractions was", prevInteractions ? "defined" : "undefined"); - props.interactions.forEach((interaction: Interaction) => { - const key = interaction.key(); - const prevInteraction = prevInteractions?.find(p => p.key() === key); - console.log("[interactions:effect]", key, { - synced: interaction.status.synced, - lastUpdate: interaction.status.lastUpdate, - prevSynced: prevInteraction?.status.synced, - prevLastUpdate: prevInteraction?.status.lastUpdate, - }); - }); setAcceptedInteractions(prev => { const updated = new Set(prev); props.interactions.forEach((interaction: Interaction) => { @@ -125,7 +105,6 @@ export default function InteractionsOverview(props: Props) { if (!interaction.status.synced) { updated.delete(key); } else if (prevInteraction && !prevInteraction.status.synced) { - console.log("[interactions:effect] ACCEPTED", key); updated.add(key); } }); @@ -240,7 +219,6 @@ export default function InteractionsOverview(props: Props) { interactionsAPI .updateInteraction(Number(deviceID), settings, as) .then((_response: any) => { - console.log("[interactions:submit] API success, setting synced=false for index", index); let updatedDirtyInteractions = cloneDeep(dirtyInteractions); updatedDirtyInteractions.set(index, false); setDirtyInteractions(updatedDirtyInteractions); @@ -249,7 +227,6 @@ export default function InteractionsOverview(props: Props) { if (updated[index]) { updated[index].status.synced = false; updated[index].status.lastUpdate = moment().toISOString(); - console.log("[interactions:submit] local state updated for", updated[index].key()); } return updated; }); From fc347938d2c93855c2f5edc2519711f803baf104 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Thu, 9 Jul 2026 11:32:32 -0600 Subject: [PATCH 23/30] hotfix for analog pressure, is shoudl support expansion and not cable ID --- src/component/ComponentSettings.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/component/ComponentSettings.tsx b/src/component/ComponentSettings.tsx index a779692..d66a3c1 100644 --- a/src/component/ComponentSettings.tsx +++ b/src/component/ComponentSettings.tsx @@ -518,7 +518,6 @@ export default function ComponentSettings(props: Props) { formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE || formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_PRESSURE_CABLE || - formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_ANALOG_PRESSURE || (formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_CAPACITOR_CABLE && formComponent.subType() === quack.CapacitorCableSubtype.CAPACITOR_CABLE_SUBTYPE_FROG)) @@ -526,7 +525,9 @@ export default function ComponentSettings(props: Props) { const supportsExpansion = () => { return ((formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE || - formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE) && + formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE || + formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_ANALOG_PRESSURE + ) && formComponent.settings.addressType === quack.AddressType.ADDRESS_TYPE_I2C) } From 487afc6464a973279852a1de710a0e843c0e2300 Mon Sep 17 00:00:00 2001 From: Carter Date: Thu, 9 Jul 2026 11:58:32 -0600 Subject: [PATCH 24/30] removed component type logs --- src/pbHelpers/ComponentType.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pbHelpers/ComponentType.tsx b/src/pbHelpers/ComponentType.tsx index c31154b..860e2aa 100644 --- a/src/pbHelpers/ComponentType.tsx +++ b/src/pbHelpers/ComponentType.tsx @@ -534,7 +534,7 @@ export function simpleLineChartData( } }); } - console.log(lineData) + // console.log(lineData) return lineData; } From d124db62e0f91cd25d856ff6d58fdeaf52767631 Mon Sep 17 00:00:00 2001 From: Carter Date: Fri, 10 Jul 2026 10:11:46 -0600 Subject: [PATCH 25/30] got rid of scanned logs --- src/device/autoDetect/DeviceScannedComponents.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/device/autoDetect/DeviceScannedComponents.tsx b/src/device/autoDetect/DeviceScannedComponents.tsx index 95c3759..4cb94e1 100644 --- a/src/device/autoDetect/DeviceScannedComponents.tsx +++ b/src/device/autoDetect/DeviceScannedComponents.tsx @@ -75,7 +75,6 @@ export default function DeviceScannedComponents(props: Props){ let expanders: quack.AddressData[] = [] //filter the address data let addr = scannedI2C?.settings?.foundAddresses - console.log(addr) if(addr){ addr.forEach(addrData => { if(!i2cBlacklistAddresses.includes(addrData.address)){ @@ -87,7 +86,6 @@ export default function DeviceScannedComponents(props: Props){ } }) } - console.log(valid) setExpanderAddresses(expanders) setValidI2CComponentAddresses(valid) },[scannedI2C]) @@ -213,14 +211,13 @@ export default function DeviceScannedComponents(props: Props){ }) }) } - console.log(cloneList) setComponents(cloneList) } const removeScan = (key: string, type: pond.ObjectType) => { deviceAPI.removeFoundComponents(device.settings.deviceId, key, type) .then(resp => { - console.log("Cleared this scan") + // console.log("Cleared this scan") }).catch(err => { console.log("There was a problem clearing this scan") }) @@ -229,7 +226,7 @@ export default function DeviceScannedComponents(props: Props){ const removeAllScans = () => { deviceAPI.removeAllFoundComponents(device.settings.deviceId) .then(resp => { - console.log("Cleared all scans") + // console.log("Cleared all scans") refreshCallback() }).catch(err => { console.log("There was a problem clearing scans") From b272d264b6b8d82c1af624220daf5d5c97142c34 Mon Sep 17 00:00:00 2001 From: Carter Date: Mon, 13 Jul 2026 13:01:59 -0600 Subject: [PATCH 26/30] updated master proto --- package-lock.json | 211 +++++++++++++++++++++++++--------------------- package.json | 2 +- 2 files changed, 114 insertions(+), 99 deletions(-) diff --git a/package-lock.json b/package-lock.json index c0f99e2..3610bee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2481,9 +2481,9 @@ } }, "node_modules/@mediapipe/tasks-vision": { - "version": "0.10.8", - "resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.8.tgz", - "integrity": "sha512-Rp7ll8BHrKB3wXaRFKhrltwZl1CiXGdibPxuWXvqGnKTnv8fqa/nvftYNuSbf+pbJWKYCXdBtYTITdAUTGGh0Q==", + "version": "0.10.17", + "resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.17.tgz", + "integrity": "sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg==", "license": "Apache-2.0" }, "node_modules/@mui/core-downloads-tracker": { @@ -3239,28 +3239,27 @@ } }, "node_modules/@react-spring/animated": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.6.1.tgz", - "integrity": "sha512-ls/rJBrAqiAYozjLo5EPPLLOb1LM0lNVQcXODTC1SMtS6DbuBCPaKco5svFUQFMP2dso3O+qcC4k9FsKc0KxMQ==", + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.7.5.tgz", + "integrity": "sha512-Tqrwz7pIlsSDITzxoLS3n/v/YCUHQdOIKtOJf4yL6kYVSDTSmVK1LI1Q3M/uu2Sx4X3pIWF3xLUhlsA6SPNTNg==", "license": "MIT", "dependencies": { - "@react-spring/shared": "~9.6.1", - "@react-spring/types": "~9.6.1" + "@react-spring/shared": "~9.7.5", + "@react-spring/types": "~9.7.5" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, "node_modules/@react-spring/core": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.6.1.tgz", - "integrity": "sha512-3HAAinAyCPessyQNNXe5W0OHzRfa8Yo5P748paPcmMowZ/4sMfaZ2ZB6e5x5khQI8NusOHj8nquoutd6FRY5WQ==", + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.7.5.tgz", + "integrity": "sha512-rmEqcxRcu7dWh7MnCcMXLvrf6/SDlSokLaLTxiPlAYi11nN3B5oiCUAblO72o+9z/87j2uzxa2Inm8UbLjXA+w==", "license": "MIT", "dependencies": { - "@react-spring/animated": "~9.6.1", - "@react-spring/rafz": "~9.6.1", - "@react-spring/shared": "~9.6.1", - "@react-spring/types": "~9.6.1" + "@react-spring/animated": "~9.7.5", + "@react-spring/shared": "~9.7.5", + "@react-spring/types": "~9.7.5" }, "funding": { "type": "opencollective", @@ -3271,34 +3270,34 @@ } }, "node_modules/@react-spring/rafz": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.6.1.tgz", - "integrity": "sha512-v6qbgNRpztJFFfSE3e2W1Uz+g8KnIBs6SmzCzcVVF61GdGfGOuBrbjIcp+nUz301awVmREKi4eMQb2Ab2gGgyQ==", + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.7.5.tgz", + "integrity": "sha512-5ZenDQMC48wjUzPAm1EtwQ5Ot3bLIAwwqP2w2owG5KoNdNHpEJV263nGhCeKKmuA3vG2zLLOdu3or6kuDjA6Aw==", "license": "MIT" }, "node_modules/@react-spring/shared": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.6.1.tgz", - "integrity": "sha512-PBFBXabxFEuF8enNLkVqMC9h5uLRBo6GQhRMQT/nRTnemVENimgRd+0ZT4yFnAQ0AxWNiJfX3qux+bW2LbG6Bw==", + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.7.5.tgz", + "integrity": "sha512-wdtoJrhUeeyD/PP/zo+np2s1Z820Ohr/BbuVYv+3dVLW7WctoiN7std8rISoYoHpUXtbkpesSKuPIw/6U1w1Pw==", "license": "MIT", "dependencies": { - "@react-spring/rafz": "~9.6.1", - "@react-spring/types": "~9.6.1" + "@react-spring/rafz": "~9.7.5", + "@react-spring/types": "~9.7.5" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, "node_modules/@react-spring/three": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@react-spring/three/-/three-9.6.1.tgz", - "integrity": "sha512-Tyw2YhZPKJAX3t2FcqvpLRb71CyTe1GvT3V+i+xJzfALgpk10uPGdGaQQ5Xrzmok1340DAeg2pR/MCfaW7b8AA==", + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/@react-spring/three/-/three-9.7.5.tgz", + "integrity": "sha512-RxIsCoQfUqOS3POmhVHa1wdWS0wyHAUway73uRLp3GAL5U2iYVNdnzQsep6M2NZ994BlW8TcKuMtQHUqOsy6WA==", "license": "MIT", "dependencies": { - "@react-spring/animated": "~9.6.1", - "@react-spring/core": "~9.6.1", - "@react-spring/shared": "~9.6.1", - "@react-spring/types": "~9.6.1" + "@react-spring/animated": "~9.7.5", + "@react-spring/core": "~9.7.5", + "@react-spring/shared": "~9.7.5", + "@react-spring/types": "~9.7.5" }, "peerDependencies": { "@react-three/fiber": ">=6.0", @@ -3307,45 +3306,44 @@ } }, "node_modules/@react-spring/types": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.6.1.tgz", - "integrity": "sha512-POu8Mk0hIU3lRXB3bGIGe4VHIwwDsQyoD1F394OK7STTiX9w4dG3cTLljjYswkQN+hDSHRrj4O36kuVa7KPU8Q==", + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.7.5.tgz", + "integrity": "sha512-HVj7LrZ4ReHWBimBvu2SKND3cDVUPWKLqRTmWe/fNY6o1owGOX0cAHbdPDTMelgBlVbrTKrre6lFkhqGZErK/g==", "license": "MIT" }, "node_modules/@react-three/drei": { - "version": "9.105.6", - "resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-9.105.6.tgz", - "integrity": "sha512-JBgYeV36N9N9f1c3o1ZfLYW4rXZA7UQTq32Y8s3DEF6lwj1/y+RP/yq2VG5I8OzUPl7gsmWdy8fpWZgrlAqUpQ==", + "version": "9.122.0", + "resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-9.122.0.tgz", + "integrity": "sha512-SEO/F/rBCTjlLez7WAlpys+iGe9hty4rNgjZvgkQeXFSiwqD4Hbk/wNHMAbdd8vprO2Aj81mihv4dF5bC7D0CA==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.11.2", - "@mediapipe/tasks-vision": "0.10.8", - "@monogrid/gainmap-js": "^3.0.5", - "@react-spring/three": "~9.6.1", - "@use-gesture/react": "^10.2.24", - "camera-controls": "^2.4.2", + "@babel/runtime": "^7.26.0", + "@mediapipe/tasks-vision": "0.10.17", + "@monogrid/gainmap-js": "^3.0.6", + "@react-spring/three": "~9.7.5", + "@use-gesture/react": "^10.3.1", + "camera-controls": "^2.9.0", "cross-env": "^7.0.3", - "detect-gpu": "^5.0.28", + "detect-gpu": "^5.0.56", "glsl-noise": "^0.0.0", - "hls.js": "1.3.5", - "maath": "^0.10.7", - "meshline": "^3.1.6", + "hls.js": "^1.5.17", + "maath": "^0.10.8", + "meshline": "^3.3.1", "react-composer": "^5.0.3", - "stats-gl": "^2.0.0", + "stats-gl": "^2.2.8", "stats.js": "^0.17.0", "suspend-react": "^0.1.3", - "three-mesh-bvh": "^0.7.0", - "three-stdlib": "^2.29.9", - "troika-three-text": "^0.49.0", + "three-mesh-bvh": "^0.7.8", + "three-stdlib": "^2.35.6", + "troika-three-text": "^0.52.0", "tunnel-rat": "^0.1.2", - "utility-types": "^3.10.0", - "uuid": "^9.0.1", - "zustand": "^3.7.1" + "utility-types": "^3.11.0", + "zustand": "^5.0.1" }, "peerDependencies": { - "@react-three/fiber": ">=8.0", - "react": ">=18.0", - "react-dom": ">=18.0", + "@react-three/fiber": "^8", + "react": "^18", + "react-dom": "^18", "three": ">=0.137" }, "peerDependenciesMeta": { @@ -3366,6 +3364,35 @@ "three": ">= 0.159.0" } }, + "node_modules/@react-three/drei/node_modules/zustand": { + "version": "5.0.14", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.14.tgz", + "integrity": "sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + }, "node_modules/@react-three/fiber": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-8.18.0.tgz", @@ -8050,9 +8077,9 @@ } }, "node_modules/deeks": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/deeks/-/deeks-3.1.0.tgz", - "integrity": "sha512-e7oWH1LzIdv/prMQ7pmlDlaVoL64glqzvNgkgQNgyec9ORPHrT2jaOqMtRyqJuwWjtfb6v+2rk9pmaHj+F137A==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/deeks/-/deeks-3.2.1.tgz", + "integrity": "sha512-D/o0k3pCG1aI1cxb/dDiWmtMc4Rh7ZQBybXpfMsw9Rbtqwg8kUA9SpYkWcw0pAUjZSnPm8MluctiS0o68r69jQ==", "license": "MIT", "engines": { "node": ">= 16" @@ -8182,9 +8209,9 @@ "license": "MIT" }, "node_modules/doc-path": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/doc-path/-/doc-path-4.1.1.tgz", - "integrity": "sha512-h1ErTglQAVv2gCnOpD3sFS6uolDbOKHDU1BZq+Kl3npPqroU3dYL42lUgMfd5UimlwtRgp7C9dLGwqQ5D2HYgQ==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/doc-path/-/doc-path-4.1.4.tgz", + "integrity": "sha512-yw5D++UCIB6a033PvQaUvSpW2QuKW0+DOId763n0Q4z3brxS7G8oQr8yBQ1nQFkognKrAVrV6I55TLeU9cfXTg==", "license": "MIT", "engines": { "node": ">=16" @@ -9572,9 +9599,9 @@ } }, "node_modules/hls.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.3.5.tgz", - "integrity": "sha512-uybAvKS6uDe0MnWNEPnO0krWVr+8m2R0hJ/viql8H3MVK+itq8gGQuIYoFHL3rECkIpNH98Lw8YuuWMKZxp3Ew==", + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.6.16.tgz", + "integrity": "sha512-VSIRpLfRwlAAdGL4wiTucx2ScRipo0ed1FBatWkyt832jC4CReKstga6yIhYVwGu9LOBjuX9wzmRMeQdBJtzEA==", "license": "Apache-2.0" }, "node_modules/hoist-non-react-statics": { @@ -10432,13 +10459,13 @@ } }, "node_modules/json-2-csv": { - "version": "5.5.10", - "resolved": "https://registry.npmjs.org/json-2-csv/-/json-2-csv-5.5.10.tgz", - "integrity": "sha512-Dep8wO3Fr5wNjQevO2Z8Y7yeee/nYSGRsi7q6zJDKEVHxXkXT+v21vxHmDX923UzmCXXkSo62HaTz6eTWzFLaw==", + "version": "5.5.11", + "resolved": "https://registry.npmjs.org/json-2-csv/-/json-2-csv-5.5.11.tgz", + "integrity": "sha512-kVuwgVL7rfad9ETf02ZZxJPuMR5ZSUn139+T34BfmVxYhb/IsAIm/LzEeQ8YLJmXfuQ5z7LUAFrgPZZM6VLJPw==", "license": "MIT", "dependencies": { - "deeks": "3.1.0", - "doc-path": "4.1.1" + "deeks": "3.2.1", + "doc-path": "4.1.4" }, "engines": { "node": ">= 16" @@ -11803,7 +11830,7 @@ }, "node_modules/protobuf-ts": { "version": "1.0.0", - "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#f783a2f01deccf8c94c11b74995de45182e7cfee", + "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#d6a992e46192a8ab8195235357318dc297738064", "dependencies": { "protobufjs": "^6.8.8" } @@ -13732,9 +13759,10 @@ "license": "MIT" }, "node_modules/three-mesh-bvh": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/three-mesh-bvh/-/three-mesh-bvh-0.7.6.tgz", - "integrity": "sha512-rCjsnxEqR9r1/C/lCqzGLS67NDty/S/eT6rAJfDvsanrIctTWdNoR4ZOGWewCB13h1QkVo2BpmC0wakj1+0m8A==", + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/three-mesh-bvh/-/three-mesh-bvh-0.7.8.tgz", + "integrity": "sha512-BGEZTOIC14U0XIRw3tO4jY7IjP7n7v24nv9JXS1CyeVRWOCkcOMhRnmENUjuV39gktAw4Ofhr0OvIAiTspQrrw==", + "deprecated": "Deprecated due to three.js version incompatibility. Please use v0.8.0, instead.", "license": "MIT", "peerDependencies": { "three": ">= 0.151.0" @@ -13912,14 +13940,14 @@ } }, "node_modules/troika-three-text": { - "version": "0.49.1", - "resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.49.1.tgz", - "integrity": "sha512-lXGWxgjJP9kw4i4Wh+0k0Q/7cRfS6iOME4knKht/KozPu9GcFA9NnNpRvehIhrUawq9B0ZRw+0oiFHgRO+4Wig==", + "version": "0.52.4", + "resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.52.4.tgz", + "integrity": "sha512-V50EwcYGruV5rUZ9F4aNsrytGdKcXKALjEtQXIOBfhVoZU9VAqZNIoGQ3TMiooVqFAbR1w15T+f+8gkzoFzawg==", "license": "MIT", "dependencies": { "bidi-js": "^1.0.2", - "troika-three-utils": "^0.49.0", - "troika-worker-utils": "^0.49.0", + "troika-three-utils": "^0.52.4", + "troika-worker-utils": "^0.52.0", "webgl-sdf-generator": "1.1.1" }, "peerDependencies": { @@ -13927,18 +13955,18 @@ } }, "node_modules/troika-three-utils": { - "version": "0.49.0", - "resolved": "https://registry.npmjs.org/troika-three-utils/-/troika-three-utils-0.49.0.tgz", - "integrity": "sha512-umitFL4cT+Fm/uONmaQEq4oZlyRHWwVClaS6ZrdcueRvwc2w+cpNQ47LlJKJswpqtMFWbEhOLy0TekmcPZOdYA==", + "version": "0.52.4", + "resolved": "https://registry.npmjs.org/troika-three-utils/-/troika-three-utils-0.52.4.tgz", + "integrity": "sha512-NORAStSVa/BDiG52Mfudk4j1FG4jC4ILutB3foPnfGbOeIs9+G5vZLa0pnmnaftZUGm4UwSoqEpWdqvC7zms3A==", "license": "MIT", "peerDependencies": { "three": ">=0.125.0" } }, "node_modules/troika-worker-utils": { - "version": "0.49.0", - "resolved": "https://registry.npmjs.org/troika-worker-utils/-/troika-worker-utils-0.49.0.tgz", - "integrity": "sha512-1xZHoJrG0HFfCvT/iyN41DvI/nRykiBtHqFkGaGgJwq5iXfIZFBiPPEHFpPpgyKM3Oo5ITHXP5wM2TNQszYdVg==", + "version": "0.52.0", + "resolved": "https://registry.npmjs.org/troika-worker-utils/-/troika-worker-utils-0.52.0.tgz", + "integrity": "sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw==", "license": "MIT" }, "node_modules/ts-api-utils": { @@ -14392,19 +14420,6 @@ "node": ">= 4" } }, - "node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", diff --git a/package.json b/package.json index 3a76c43..76ac48b 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "mui-tel-input": "^7.0.0", "notistack": "^3.0.1", "openweathermap-ts": "^1.2.10", - "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#staging", + "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#master", "query-string": "^9.2.1", "react": "^18.3.1", "react-beautiful-dnd": "^13.1.1", From 65f8fd06d3d690261f15fc7fe5a38fd7c18ee7ee Mon Sep 17 00:00:00 2001 From: Carter Date: Thu, 16 Jul 2026 12:47:56 -0600 Subject: [PATCH 27/30] hotfix: mode node id inputs --- src/component/ComponentForm.tsx | 26 ++++++++++++++++++++++---- src/component/ComponentMode.json | 2 +- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/component/ComponentForm.tsx b/src/component/ComponentForm.tsx index dbebbb5..266c402 100644 --- a/src/component/ComponentForm.tsx +++ b/src/component/ComponentForm.tsx @@ -457,11 +457,29 @@ export default function ComponentForm(props: Props) { setForm(f); }; + const modeDictionaryValue = (dictionary: any, key: string): number | undefined => { + if (!Array.isArray(dictionary)) { + return undefined; + } + let entry = dictionary.find((elem: any) => elem.key === key); + return typeof entry?.value === "number" ? entry.value : undefined; + }; + const isCoefficientValid = () => { let calibrate = form.component.settings.calibrate; let coefficient = Number(form.coefficient); - let valid = !isNaN(coefficient) && (!calibrate || (calibrate && coefficient > 0)); - return valid; + if (isNaN(coefficient)) { + return false; + } + if (!calibrate) { + return true; + } + if (compMode && compMode.mode.entryA === "number") { + let minimum = modeDictionaryValue(compMode.mode.dictionaryA, "min") ?? 0; + let maximum = modeDictionaryValue(compMode.mode.dictionaryA, "max") ?? 100; + return coefficient >= minimum && coefficient <= maximum; + } + return coefficient > 0; }; const changeCoefficient = (event: any) => { @@ -485,8 +503,8 @@ export default function ComponentForm(props: Props) { }; const isOffsetValid = (min?: number, max?: number) => { - let minimum = min ?? 0; - let maximum = max ?? 100; + let minimum = min ?? modeDictionaryValue(compMode?.mode.dictionaryB, "min") ?? 0; + let maximum = max ?? modeDictionaryValue(compMode?.mode.dictionaryB, "max") ?? 100; if (compMode && form.component.settings.calibrate) { return Number(form.offset) <= maximum && Number(form.offset) >= minimum; } diff --git a/src/component/ComponentMode.json b/src/component/ComponentMode.json index 2c66a6e..da36459 100644 --- a/src/component/ComponentMode.json +++ b/src/component/ComponentMode.json @@ -110,7 +110,7 @@ "dictionaryA": [ { "key": "min", - "value": 1, + "value": 0, "restricted": false }, { From 80750bd3e9bf74db37c263ef7b31d16d1fb1ca7a Mon Sep 17 00:00:00 2001 From: Carter Date: Thu, 16 Jul 2026 14:22:33 -0600 Subject: [PATCH 28/30] hotfix: mode node id can be anything > 0 --- src/component/ComponentForm.tsx | 15 ++++++++++++--- src/component/ComponentMode.json | 4 +++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/component/ComponentForm.tsx b/src/component/ComponentForm.tsx index 266c402..362b0ae 100644 --- a/src/component/ComponentForm.tsx +++ b/src/component/ComponentForm.tsx @@ -503,10 +503,14 @@ export default function ComponentForm(props: Props) { }; const isOffsetValid = (min?: number, max?: number) => { - let minimum = min ?? modeDictionaryValue(compMode?.mode.dictionaryB, "min") ?? 0; - let maximum = max ?? modeDictionaryValue(compMode?.mode.dictionaryB, "max") ?? 100; + const minimum = min ?? modeDictionaryValue(compMode?.mode.dictionaryB, "min") ?? 0; + const maximum = max ?? modeDictionaryValue(compMode?.mode.dictionaryB, "max") ?? 100; if (compMode && form.component.settings.calibrate) { - return Number(form.offset) <= maximum && Number(form.offset) >= minimum; + const offset = Number(form.offset); + const meetsMinimum = compMode.mode.exclusiveMinimumB + ? offset > minimum + : offset >= minimum; + return !isNaN(offset) && offset <= maximum && meetsMinimum; } return !isNaN(Number(form.offset)); }; @@ -850,6 +854,11 @@ export default function ComponentForm(props: Props) { onFocus={selectText} margin="normal" fullWidth + inputProps={ + compMode.mode.stepB !== undefined + ? { step: compMode.mode.stepB } + : undefined + } type={compMode.mode.entryB} variant="outlined"> {compMode.mode.entryB === "select" && diff --git a/src/component/ComponentMode.json b/src/component/ComponentMode.json index da36459..4443d1f 100644 --- a/src/component/ComponentMode.json +++ b/src/component/ComponentMode.json @@ -121,10 +121,12 @@ ], "labelB": "To Node Id", "entryB": "number", + "exclusiveMinimumB": true, + "stepB": "any", "dictionaryB": [ { "key": "min", - "value": 1, + "value": 0, "restricted": false }, { From ddd0b5062bc5f5b77df92c47855602af769c6c60 Mon Sep 17 00:00:00 2001 From: Carter Date: Fri, 17 Jul 2026 14:58:31 -0600 Subject: [PATCH 29/30] settings forms to prevent it from revert user changed --- src/component/ComponentSettings.tsx | 10 ++-- src/device/DeviceSettings.tsx | 60 ++++++++++-------------- src/interactions/InteractionSettings.tsx | 35 ++++---------- 3 files changed, 38 insertions(+), 67 deletions(-) diff --git a/src/component/ComponentSettings.tsx b/src/component/ComponentSettings.tsx index d66a3c1..ce56e56 100644 --- a/src/component/ComponentSettings.tsx +++ b/src/component/ComponentSettings.tsx @@ -213,15 +213,13 @@ export default function ComponentSettings(props: Props) { ) ); setFormComponent(initComponent); - setExcludedNodes(initComponent.settings.excludedNodes) + setExcludedNodes(cloneDeep(initComponent.settings.excludedNodes)); }, [props.component, componentTypeOptions]); useEffect(() => { - if (isDialogOpen && prevIsDialogOpen) return; - if (props.component !== prevComponent || isDialogOpen !== prevIsDialogOpen) { - init(); - } - }, [props.component, prevComponent, init, isDialogOpen, prevIsDialogOpen]); + if (!isDialogOpen || prevIsDialogOpen) return; + init(); + }, [init, isDialogOpen, prevIsDialogOpen]); const close = () => { closeDialogCallback(); diff --git a/src/device/DeviceSettings.tsx b/src/device/DeviceSettings.tsx index 26ca591..32831f5 100644 --- a/src/device/DeviceSettings.tsx +++ b/src/device/DeviceSettings.tsx @@ -31,6 +31,7 @@ import LinearMutationBuilder from "common/LinearMutationBuilder"; import { makeStyles } from "@mui/styles"; import { useNavigate } from "react-router-dom"; import CancelSubmit from "common/CancelSubmit"; +import { cloneDeep } from "lodash"; interface TabPanelProps { children?: React.ReactNode; @@ -94,7 +95,6 @@ export default function DeviceSettings(props: Props) { const { success, error } = useSnackbar(); const deviceAPI = useDeviceAPI(); const { device, isDialogOpen, closeDialogCallback, canEdit, refreshCallback, components } = props; - const prevDevice = usePrevious(device); const prevIsDialogOpen = usePrevious(isDialogOpen); const [deviceForm, setDeviceForm] = useState(deviceFromForm(Device.clone(device))); const [isRemoveDeviceDialogOpen, setIsRemoveDeviceDialogOpen] = useState(false); @@ -103,7 +103,9 @@ export default function DeviceSettings(props: Props) { const [compExtTwo, setCompExtTwo] = useState(""); const [compExtThree, setCompExtThree] = useState(""); const [currentTab, setCurrentTab] = useState(0); - const [linearMutations, setLinearMutations] = useState([]); + const [linearMutations, setLinearMutations] = useState( + cloneDeep(device.settings.mutations) + ); const [componentsByDevice, setComponentsByDevice] = useState>( new Map() ); @@ -111,28 +113,22 @@ export default function DeviceSettings(props: Props) { const [existingMutation, setExistingMutation] = useState(); useEffect(() => { - if (isDialogOpen && prevIsDialogOpen) return; - if (prevDevice !== device || isDialogOpen !== prevIsDialogOpen) { - setDeviceForm(deviceFromForm(Device.clone(props.device))); - if (device.settings.extensionComponents[0]) { - setCompExtOne(device.settings.extensionComponents[0]); - } - if (device.settings.extensionComponents[1]) { - setCompExtTwo(device.settings.extensionComponents[1]); - } - if (device.settings.extensionComponents[2]) { - setCompExtThree(device.settings.extensionComponents[2]); - } - if (device.settings.mutations) { - setLinearMutations(device.settings.mutations); - } - if (components) { - let cbd = new Map(); - cbd.set(device.id().toString(), components); - setComponentsByDevice(cbd); - } + if (!isDialogOpen || prevIsDialogOpen) return; + + const settingsForm = deviceFromForm(Device.clone(device)); + setDeviceForm(settingsForm); + setSleeps(settingsForm.settings.sleepDurationS > 0); + setCompExtOne(settingsForm.settings.extensionComponents[0] ?? ""); + setCompExtTwo(settingsForm.settings.extensionComponents[1] ?? ""); + setCompExtThree(settingsForm.settings.extensionComponents[2] ?? ""); + setLinearMutations(cloneDeep(settingsForm.settings.mutations)); + + if (components) { + let cbd = new Map(); + cbd.set(settingsForm.id().toString(), components); + setComponentsByDevice(cbd); } - }, [device, prevDevice, props.device, components, isDialogOpen, prevIsDialogOpen]); + }, [device, components, isDialogOpen, prevIsDialogOpen]); const close = () => { closeDialogCallback(); @@ -147,7 +143,7 @@ export default function DeviceSettings(props: Props) { const minCheckPeriodS = (): number => { let defaultPeriod = 60; - switch (device.settings.platform) { + switch (deviceForm.settings.platform) { case pond.DevicePlatform.DEVICE_PLATFORM_ELECTRON: return user.hasFeature("admin") ? defaultPeriod : 300; default: @@ -319,7 +315,7 @@ export default function DeviceSettings(props: Props) { id="name" name="name" label="Name" - defaultValue={deviceForm.settings.name} + value={deviceForm.settings.name} onChange={changeName} margin="normal" variant="outlined" @@ -332,7 +328,7 @@ export default function DeviceSettings(props: Props) { id="description" name="description" label="Description" - defaultValue={deviceForm.settings.description} + value={deviceForm.settings.description} onChange={changeDescription} multiline rows={2} @@ -550,8 +546,8 @@ export default function DeviceSettings(props: Props) { const mutationsContent = () => { let mutations: JSX.Element[] = []; - if (device.settings.mutations) { - device.settings.mutations.forEach((mut, i) => { + if (linearMutations) { + linearMutations.forEach((mut, i) => { mutations.push( { - let lm = linearMutations; - lm.splice(i, 1); - setLinearMutations([...lm]); + setLinearMutations(linearMutations.filter((_, index) => index !== i)); }}> Remove @@ -696,9 +690,7 @@ export default function DeviceSettings(props: Props) { setNewMutationDialog(false); }} onSubmit={newMutation => { - let lm = linearMutations; - lm.push(newMutation); - setLinearMutations(lm); + setLinearMutations([...linearMutations, newMutation]); }} /> diff --git a/src/interactions/InteractionSettings.tsx b/src/interactions/InteractionSettings.tsx index c98742f..f1f3d8c 100644 --- a/src/interactions/InteractionSettings.tsx +++ b/src/interactions/InteractionSettings.tsx @@ -32,7 +32,6 @@ import moment from "moment-timezone"; import { componentIDToString, emptyComponentId, - getComponentIDString, sameComponentID, stringToComponentId } from "pbHelpers/Component"; @@ -130,9 +129,6 @@ export default function InteractionSettings(props: Props) { } = props; const theme = useTheme(); const { success, error } = useSnackbar(); - const prevInitialInteraction = usePrevious(initialInteraction); - const prevComponents = usePrevious(components); - const prevInitialComponent = usePrevious(initialComponent); const prevIsDialogOpen = usePrevious(isDialogOpen); const classes = useStyles(); const [{ user, as }] = useGlobalState(); @@ -167,7 +163,7 @@ export default function InteractionSettings(props: Props) { const setDefaultState = useCallback(() => { let interaction = getDefaultInteraction(); if (initialInteraction && mode === "update") { - interaction = initialInteraction; + interaction = Interaction.clone(initialInteraction); if (interaction.settings.subtype === 0 || interaction.settings.subtype === 1) { setSubtypeDropdown(interaction.settings.subtype); } else { @@ -223,7 +219,7 @@ export default function InteractionSettings(props: Props) { setDutyCycle(interactionResult.dutyCycle.toString()); setMappedComponents(mappedComponents); setInteraction(interaction); - }, [components, initialComponent, initialConditions, initialInteraction, mode, /*sensor*/]); + }, [components, initialComponent, initialConditions, initialInteraction, mode, sensor, user]); const availableSources = () => { return components.filter(component => isSource(component.settings.type)); @@ -233,27 +229,12 @@ export default function InteractionSettings(props: Props) { if (user && user.settings.timezone) { setTimezone(user.settings.timezone); } - if (isDialogOpen && prevIsDialogOpen) return; - if ( - prevInitialInteraction !== initialInteraction || - (prevComponents && prevComponents.length !== components.length) || - getComponentIDString(prevInitialComponent) !== getComponentIDString(initialComponent) || - isDialogOpen !== prevIsDialogOpen - ) { - setDefaultState(); - } - }, [ - components.length, - initialComponent, - initialInteraction, - prevComponents, - prevInitialComponent, - prevInitialInteraction, - setDefaultState, - user, - isDialogOpen, - prevIsDialogOpen - ]); + }, [user]); + + useEffect(() => { + if (!isDialogOpen || prevIsDialogOpen) return; + setDefaultState(); + }, [isDialogOpen, prevIsDialogOpen, setDefaultState]); const getAvailableSinks = () => { let type = or(interaction.settings.result, pond.InteractionResult.create()).type; From 696fbb3593b490de086c20f054757fc881417e16 Mon Sep 17 00:00:00 2001 From: Carter Date: Tue, 21 Jul 2026 11:59:48 -0600 Subject: [PATCH 30/30] wrapping scrollIntoView in {} to prevent it from returning void --- src/chat/ChatOutput.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/chat/ChatOutput.tsx b/src/chat/ChatOutput.tsx index 198690b..765809e 100644 --- a/src/chat/ChatOutput.tsx +++ b/src/chat/ChatOutput.tsx @@ -24,11 +24,11 @@ export default function ChatOutput(props: Props) { const ScrollToBottom = () => { const elementRef = useRef(null); - useEffect(() => + useEffect(() => { elementRef?.current?.scrollIntoView({ block: "end" - }) - ); + }); + }); let elem =
; return elem; };