@@ -418,12 +418,20 @@ function App() {
418418 setCdnBaseUrl ( cdnBaseUrl ) ;
419419 } , [ cdnBaseUrl ] ) ;
420420
421- // Save camera state to localStorage (kept as-is )
421+ // Save camera state to localStorage (debounced to prevent excessive writes )
422422 useEffect ( ( ) => {
423- localStorage . setItem ( "shipLogCamera" , JSON . stringify ( {
424- zoom : zoomLevel ,
425- position : cameraPosition
426- } ) ) ;
423+ const timeoutId = setTimeout ( ( ) => {
424+ try {
425+ localStorage . setItem ( "shipLogCamera" , JSON . stringify ( {
426+ zoom : zoomLevel ,
427+ position : cameraPosition
428+ } ) ) ;
429+ } catch ( e ) {
430+ printWarn ( 'Failed to save camera to localStorage:' , e ) ;
431+ }
432+ } , 1000 ) ; // Save at most once per second
433+
434+ return ( ) => clearTimeout ( timeoutId ) ;
427435 } , [ zoomLevel , cameraPosition ] ) ;
428436
429437 // Save mode to localStorage
@@ -1045,6 +1053,18 @@ function App() {
10451053 const memoSelectedEdgeIds = useMemo ( ( ) => selectedEdgeIds , [ selectedEdgeIds ] ) ;
10461054 const memoCameraPosition = useMemo ( ( ) => cameraPosition , [ cameraPosition ] ) ;
10471055 const memoNotes = useMemo ( ( ) => graphData . notes , [ graphData . notes ] ) ;
1056+
1057+ // Memoize background image object to prevent infinite re-renders
1058+ const memoBgImage = useMemo ( ( ) => {
1059+ if ( ! bgImage . imageUrl ) return null ;
1060+ console . log ( '🔄 [App] memoBgImage recalculating with calibration:' , bgCalibration ) ;
1061+ return {
1062+ imageUrl : bgImage . imageUrl ,
1063+ visible : bgImage . visible ,
1064+ opacity : bgImage . opacity ,
1065+ calibration : bgCalibration
1066+ } ;
1067+ } , [ bgImage . imageUrl , bgImage . visible , bgImage . opacity , bgCalibration ] ) ;
10481068
10491069 const handleLoadFromCdnButton = useCallback ( ( cdnBaseUrlArg ) => {
10501070 handleLoadFromCdn ( {
@@ -1124,22 +1144,21 @@ useEffect(() => {
11241144 </ div >
11251145 </ div >
11261146
1127- { /* Background image underlay */ }
1147+ { /* Background image underlay - DISABLED: Now integrated into Cytoscape canvas */ }
1148+ { /* Background now renders as a Cytoscape node for perfect sync with pan/zoom */ }
1149+ { /* See CytoscapeGraph bgImage prop and bgNodeAdapter.js */ }
1150+ { /*
11281151 {bgImage.imageUrl && bgImage.visible && (
11291152 <BgImageLayer
11301153 url={bgImage.imageUrl}
11311154 visible={bgImage.visible}
11321155 opacity={bgImage.opacity}
1133- pan = { livePan } // 🔴 live pan (every frame)
1134- zoom = { liveZoom } // 🔴 live zoom (every frame)
1135- calibration = { bgCalibration
1136- // keep your existing semantics: scale is a percentage
1137- // tx: bgImage.x, // world offset X (same units as node positions)
1138- // ty: bgImage.y, // world offset Y
1139- // s: (bgImage.scale ?? 100) / 100 // world units per image pixel
1140- }
1156+ pan={livePan}
1157+ zoom={liveZoom}
1158+ calibration={bgCalibration}
11411159 />
11421160 )}
1161+ */ }
11431162
11441163 { canEdit && (
11451164 < GraphControls
@@ -1346,6 +1365,7 @@ useEffect(() => {
13461365 showNoteCountOverlay = { showNoteCountOverlay }
13471366 notes = { memoNotes }
13481367 visited = { visited } /* pass visited to drive unseen badges */
1368+ bgImage = { memoBgImage }
13491369 />
13501370
13511371 { compassVisible && (
0 commit comments