@@ -298,6 +298,33 @@ def set_similarity_selection(self, similar_ids, presentation_order=None):
298298 )
299299 return self ._apply (after )
300300
301+ def navigate_similarity_selection (self , similar_ids , presentation_order = None ):
302+ """Replace the Normal-mode wizard candidate while reusing its color slot."""
303+ self ._require_normal_mode ()
304+ current = self ._state
305+ similar_ids = _as_unique_ids (similar_ids )
306+ if len (similar_ids ) > 1 :
307+ raise ValueError ('Similarity navigation selects at most one candidate.' )
308+ effective_ids = _ordered_union (current .cluster_ids , similar_ids )
309+ if presentation_order is None :
310+ presentation_order = _ordered_union (
311+ tuple (
312+ cluster_id
313+ for cluster_id in current .presentation_order
314+ if cluster_id in effective_ids
315+ ),
316+ effective_ids ,
317+ )
318+ color_order = self ._navigation_color_order (similar_ids )
319+ after = CurationSelectionState (
320+ cluster_ids = current .cluster_ids ,
321+ similar_ids = similar_ids ,
322+ reference_id = current .reference_id ,
323+ presentation_order = presentation_order ,
324+ color_order = color_order ,
325+ )
326+ return self ._apply (after )
327+
301328 def set_presentation_order (self , presentation_order ):
302329 """Set scientific-view order without changing roles or color slots."""
303330 current = self ._state
@@ -440,6 +467,41 @@ def _next_color_order(self, reference_id, presentation_order):
440467 return tuple (presentation_order )
441468 return _ordered_union (current .color_order , presentation_order )
442469
470+ def _navigation_color_order (self , similar_ids ):
471+ """Give a replacement wizard candidate the outgoing candidate's slot."""
472+ current = self ._state
473+ if not similar_ids :
474+ return current .color_order
475+ candidate = similar_ids [0 ]
476+ color_order = list (current .color_order )
477+ outgoing_slots = [
478+ color_order .index (cluster_id )
479+ for cluster_id in current .similar_ids
480+ if cluster_id in color_order
481+ ]
482+ if outgoing_slots :
483+ target = min (outgoing_slots )
484+ else :
485+ primary_ids = set (current .cluster_ids )
486+ target = next (
487+ (
488+ index
489+ for index , cluster_id in enumerate (color_order )
490+ if cluster_id not in primary_ids
491+ ),
492+ len (color_order ),
493+ )
494+ if candidate in color_order :
495+ source = color_order .index (candidate )
496+ color_order [target ], color_order [source ] = color_order [source ], color_order [target ]
497+ elif target < len (color_order ):
498+ displaced = color_order [target ]
499+ color_order [target ] = candidate
500+ color_order .append (displaced )
501+ else :
502+ color_order .append (candidate )
503+ return tuple (color_order )
504+
443505 def _apply (self , after ):
444506 before = self ._state
445507 self ._state = after
0 commit comments