From 6ea9a21b28436a6c56b2b211cbe4c0a8f6489e8d Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 20 Dec 2025 09:10:49 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Memoize=20weight=20and=20ba?= =?UTF-8?q?lance=20calculation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 💡 What: Wrapped the core weight and balance calculation logic in `app/page.tsx` with `useMemo`. 🎯 Why: Prevents expensive recalculations (loops, polygon checks) from running on every render when only UI state (like modals or view toggles) changes. 📊 Impact: Reduces main thread work during UI interactions that don't affect physics/math (e.g. toggling dark mode or opening settings). 🔬 Measurement: Verify that `results` object reference remains stable when toggling "Show Std Weights". --- app/page.tsx | 169 ++++++++++++++++++++++++++------------------------- 1 file changed, 86 insertions(+), 83 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index 8649186..0fa0d3d 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect } from "react"; +import { useState, useEffect, useMemo } from "react"; import { aircraftList, Aircraft, SavedAircraft } from "../data/aircraft"; import ManifestReport from "../components/ManifestReport"; import HangarList from "../components/HangarList"; @@ -177,95 +177,98 @@ export default function Home() { }, [customEmptyWeight, customEmptyArm, armOverrides, selectedPlane, savedPlanes, view]); // --- CALCULATION LOGIC --- - let results = { - rampWeight: 0, - rampMoment: 0, // FIX: Initialize rampMoment - takeoffWeight: 0, takeoffMoment: 0, takeoffCG: 0, - landingWeight: 0, landingCG: 0, - isTakeoffSafe: true, isLandingSafe: true, - takeoffIssue: null as string | null, landingIssue: null as string | null, - isGo: true, enduranceHours: 0, enduranceMinutes: 0, - activeEnvelope: [] as any[], maxGross: 0, - fuelArm: 0 - }; + const results = useMemo(() => { + let res = { + rampWeight: 0, + rampMoment: 0, // FIX: Initialize rampMoment + takeoffWeight: 0, takeoffMoment: 0, takeoffCG: 0, + landingWeight: 0, landingCG: 0, + isTakeoffSafe: true, isLandingSafe: true, + takeoffIssue: null as string | null, landingIssue: null as string | null, + isGo: true, enduranceHours: 0, enduranceMinutes: 0, + activeEnvelope: [] as any[], maxGross: 0, + fuelArm: 0 + }; + + if (selectedPlane) { + let rampWeight = customEmptyWeight; + let rampMoment = customEmptyWeight * customEmptyArm; + let fuelArm = 0; + let totalFuelWeight = 0; + + selectedPlane.stations.forEach((station: any) => { + let w = weights[station.id] || 0; + const isFuel = station.id.toLowerCase().includes("fuel"); + if (isFuel) { + if (useGallons) w = w * 6; + totalFuelWeight = w; + fuelArm = armOverrides[station.id] !== undefined ? armOverrides[station.id] : station.arm; + } + const arm = armOverrides[station.id] !== undefined ? armOverrides[station.id] : station.arm; + rampWeight += w; + rampMoment += w * arm; + }); + + customStations.forEach((s) => { + rampWeight += s.weight; + rampMoment += s.weight * s.arm; + }); + + const taxiWeight = fuel.taxi * 6; + const takeoffWeight = rampWeight - taxiWeight; + const takeoffMoment = rampMoment - (taxiWeight * fuelArm); + const takeoffCG = takeoffMoment / (takeoffWeight || 1); + + let landingWeight = takeoffWeight; + let landingCG = takeoffCG; - if (selectedPlane) { - let rampWeight = customEmptyWeight; - let rampMoment = customEmptyWeight * customEmptyArm; - let fuelArm = 0; - let totalFuelWeight = 0; - - selectedPlane.stations.forEach((station: any) => { - let w = weights[station.id] || 0; - const isFuel = station.id.toLowerCase().includes("fuel"); - if (isFuel) { - if (useGallons) w = w * 6; - totalFuelWeight = w; - fuelArm = armOverrides[station.id] !== undefined ? armOverrides[station.id] : station.arm; + if (fuel.trip > 0) { + const tripWeight = fuel.trip * 6; + landingWeight = takeoffWeight - tripWeight; + const landingMoment = takeoffMoment - (tripWeight * fuelArm); + landingCG = landingMoment / (landingWeight || 1); } - const arm = armOverrides[station.id] !== undefined ? armOverrides[station.id] : station.arm; - rampWeight += w; - rampMoment += w * arm; - }); - - customStations.forEach((s) => { - rampWeight += s.weight; - rampMoment += s.weight * s.arm; - }); - - const taxiWeight = fuel.taxi * 6; - const takeoffWeight = rampWeight - taxiWeight; - const takeoffMoment = rampMoment - (taxiWeight * fuelArm); - const takeoffCG = takeoffMoment / (takeoffWeight || 1); - - let landingWeight = takeoffWeight; - let landingCG = takeoffCG; - - if (fuel.trip > 0) { - const tripWeight = fuel.trip * 6; - landingWeight = takeoffWeight - tripWeight; - const landingMoment = takeoffMoment - (tripWeight * fuelArm); - landingCG = landingMoment / (landingWeight || 1); - } - const usableTakeoffFuelGal = (totalFuelWeight - taxiWeight) / 6; - let enduranceHours = 0; - let enduranceMinutes = 0; - if (fuel.burn > 0 && usableTakeoffFuelGal > 0) { + const usableTakeoffFuelGal = (totalFuelWeight - taxiWeight) / 6; + let enduranceHours = 0; + let enduranceMinutes = 0; + if (fuel.burn > 0 && usableTakeoffFuelGal > 0) { const totalHours = usableTakeoffFuelGal / fuel.burn; enduranceHours = Math.floor(totalHours); enduranceMinutes = Math.floor((totalHours - enduranceHours) * 60); - } - - const activeEnvelope = (category === 'utility' && selectedPlane.utilityEnvelope) ? selectedPlane.utilityEnvelope : selectedPlane.envelope; - const maxGross = Math.max(...activeEnvelope.map((p: any) => p.weight)); - const isTakeoffInside = isPointInPolygon({ cg: takeoffCG, weight: takeoffWeight }, activeEnvelope); - const isLandingInside = isPointInPolygon({ cg: landingCG, weight: landingWeight }, activeEnvelope); - - const takeoffLimits = getCGLimitsAtWeight(takeoffWeight, activeEnvelope); - const landingLimits = getCGLimitsAtWeight(landingWeight, activeEnvelope); - - const getFailureReason = (weight: number, cg: number, limits: {minCG: number, maxCG: number} | null) => { - if (weight > maxGross) return `Over Max Gross (${(weight - maxGross).toFixed(0)} lbs)`; - if (!limits) return "Outside Envelope"; - if (cg < limits.minCG) return `Fwd Limit Exceeded by ${(limits.minCG - cg).toFixed(1)}"`; - if (cg > limits.maxCG) return `Aft Limit Exceeded by ${(cg - limits.maxCG).toFixed(1)}"`; - return "Outside Envelope"; - }; + } - results = { - rampWeight, - rampMoment, // FIX: Pass calculated moment to results - takeoffWeight, takeoffMoment, takeoffCG, - landingWeight, landingCG, - isTakeoffSafe: isTakeoffInside, isLandingSafe: isLandingInside, - takeoffIssue: !isTakeoffInside ? getFailureReason(takeoffWeight, takeoffCG, takeoffLimits) : null, - landingIssue: !isLandingInside ? getFailureReason(landingWeight, landingCG, landingLimits) : null, - isGo: isTakeoffInside && (toggles.flightPlan ? isLandingInside : true), - enduranceHours, enduranceMinutes, - activeEnvelope, maxGross, fuelArm - }; - } + const activeEnvelope = (category === 'utility' && selectedPlane.utilityEnvelope) ? selectedPlane.utilityEnvelope : selectedPlane.envelope; + const maxGross = Math.max(...activeEnvelope.map((p: any) => p.weight)); + const isTakeoffInside = isPointInPolygon({ cg: takeoffCG, weight: takeoffWeight }, activeEnvelope); + const isLandingInside = isPointInPolygon({ cg: landingCG, weight: landingWeight }, activeEnvelope); + + const takeoffLimits = getCGLimitsAtWeight(takeoffWeight, activeEnvelope); + const landingLimits = getCGLimitsAtWeight(landingWeight, activeEnvelope); + + const getFailureReason = (weight: number, cg: number, limits: { minCG: number, maxCG: number } | null) => { + if (weight > maxGross) return `Over Max Gross (${(weight - maxGross).toFixed(0)} lbs)`; + if (!limits) return "Outside Envelope"; + if (cg < limits.minCG) return `Fwd Limit Exceeded by ${(limits.minCG - cg).toFixed(1)}"`; + if (cg > limits.maxCG) return `Aft Limit Exceeded by ${(cg - limits.maxCG).toFixed(1)}"`; + return "Outside Envelope"; + }; + + res = { + rampWeight, + rampMoment, // FIX: Pass calculated moment to results + takeoffWeight, takeoffMoment, takeoffCG, + landingWeight, landingCG, + isTakeoffSafe: isTakeoffInside, isLandingSafe: isLandingInside, + takeoffIssue: !isTakeoffInside ? getFailureReason(takeoffWeight, takeoffCG, takeoffLimits) : null, + landingIssue: !isLandingInside ? getFailureReason(landingWeight, landingCG, landingLimits) : null, + isGo: isTakeoffInside && (toggles.flightPlan ? isLandingInside : true), + enduranceHours, enduranceMinutes, + activeEnvelope, maxGross, fuelArm + }; + } + return res; + }, [selectedPlane, customEmptyWeight, customEmptyArm, weights, armOverrides, customStations, fuel, useGallons, category, toggles.flightPlan]); // 1. SHOW LANDING PAGE? if (showLanding) {