@@ -144,6 +144,7 @@ export function WorldMapView({ gameId }: WorldMapViewProps) {
144144 hideUsedNodes,
145145 usedNodesList,
146146 sumMode,
147+ selectedNodeIdsList,
147148 collectibleVisibility,
148149 hideCollectedCollectibles,
149150 collectedList,
@@ -156,6 +157,13 @@ export function WorldMapView({ gameId }: WorldMapViewProps) {
156157 hideUsedNodes : mapState ?. hideUsedNodes ?? false ,
157158 usedNodesList : game ?. usedNodes ?? EMPTY_USED_NODES ,
158159 sumMode : state . mapSelection ?. sumMode ?? false ,
160+ // Subscribed here so the filter memo invalidates when the user
161+ // navigates in from the input row's "View on map" button. The
162+ // selection seed has to bypass `hideUsedNodes` (assignment
163+ // automatically marks nodes as used, otherwise the markers
164+ // would arrive on a map that immediately hides them).
165+ selectedNodeIdsList :
166+ state . mapSelection ?. selectedNodeIds ?? EMPTY_USED_NODES ,
159167 collectibleVisibility :
160168 mapState ?. collectibleVisibility ?? EMPTY_COLLECTIBLE_VISIBILITY ,
161169 hideCollectedCollectibles : mapState ?. hideCollectedCollectibles ?? false ,
@@ -170,6 +178,10 @@ export function WorldMapView({ gameId }: WorldMapViewProps) {
170178
171179 const usedNodes = useMemo ( ( ) => new Set ( usedNodesList ) , [ usedNodesList ] ) ;
172180 const collectedIds = useMemo ( ( ) => new Set ( collectedList ) , [ collectedList ] ) ;
181+ const selectedNodeIdsSet = useMemo (
182+ ( ) => new Set ( selectedNodeIdsList ) ,
183+ [ selectedNodeIdsList ] ,
184+ ) ;
173185
174186 // ─── Node-to-factory assignments. The selector returns the
175187 // full per-node ref array (already filtered for orphans and
@@ -195,8 +207,8 @@ export function WorldMapView({ gameId }: WorldMapViewProps) {
195207
196208 // ─── Assignment modal state. Owned here (not in the marker
197209 // layer) so the same modal instance is reused regardless of
198- // entry point — popup action, sum-mode summary, or future
199- // callers — and so the modal renders inside the React tree
210+ // entry point ( popup action, sum-mode summary, or future
211+ // callers), and so the modal renders inside the React tree
200212 // instead of the imperative Leaflet layer.
201213 const [ assignTarget , setAssignTarget ] = useState < WorldResourceNode | null > (
202214 null ,
@@ -213,11 +225,27 @@ export function WorldMapView({ gameId }: WorldMapViewProps) {
213225 // biome-ignore lint/correctness/useExhaustiveDependencies: savegameOverrides is read indirectly via getWorldResourceNodes' useStore.getState() lookup; the dep is required to invalidate the memo on import.
214226 const filteredNodes = useMemo ( ( ) => {
215227 return getWorldResourceNodes ( gameId ) . filter ( node => {
228+ // Selection always wins. Reason: the user got here either via
229+ // "View on map" from a factory input row (we just programmatic-
230+ // ally selected the assigned nodes) or via an explicit click on
231+ // the marker. Either way, hiding the very thing they pointed
232+ // at would be hostile UX. This also covers the assignment side-
233+ // effect: assigning a node marks it as used, so without this
234+ // override the freshly-assigned nodes would vanish under
235+ // `hideUsedNodes`.
236+ if ( selectedNodeIdsSet . has ( node . id ) ) return true ;
216237 if ( ! resourceFilters [ node . resource ] ?. includes ( node . purity ) ) return false ;
217238 if ( hideUsedNodes && usedNodes . has ( node . id ) ) return false ;
218239 return true ;
219240 } ) ;
220- } , [ gameId , resourceFilters , hideUsedNodes , usedNodes , savegameOverrides ] ) ;
241+ } , [
242+ gameId ,
243+ resourceFilters ,
244+ hideUsedNodes ,
245+ usedNodes ,
246+ selectedNodeIdsSet ,
247+ savegameOverrides ,
248+ ] ) ;
221249
222250 const filteredCollectibles = useMemo ( ( ) => {
223251 return getWorldCollectibles ( ) . filter ( collectible => {
0 commit comments