@@ -272,12 +272,14 @@ export class AnnotationListToolboxItem extends ToolboxItem {
272272 // Show/hide deprecated annotations checkbox
273273 $ ( document ) . on ( "change.ulabel" , "#annotation-list-show-deprecated" , ( e ) => {
274274 this . show_deprecated = ( e . target as HTMLInputElement ) . checked ;
275+ set_local_storage_item ( "ulabel_annotation_list_show_deprecated" , this . show_deprecated ? "true" : "false" ) ;
275276 this . update_list ( ) ;
276277 } ) ;
277278
278279 // Group by class checkbox
279280 $ ( document ) . on ( "change.ulabel" , "#annotation-list-group-by-class" , ( e ) => {
280281 this . group_by_class = ( e . target as HTMLInputElement ) . checked ;
282+ set_local_storage_item ( "ulabel_annotation_list_group_by_class" , this . group_by_class ? "true" : "false" ) ;
281283 this . update_list ( ) ;
282284 } ) ;
283285
@@ -407,12 +409,13 @@ export class AnnotationListToolboxItem extends ToolboxItem {
407409 * Build HTML for flat (non-grouped) list
408410 */
409411 private build_flat_list_html ( annotations : ULabelAnnotation [ ] , subtask : ULabelSubtask ) : string {
412+ const class_def_by_id = this . build_class_def_by_id ( subtask ) ;
410413 let html = "" ;
411414
412415 for ( let i = 0 ; i < annotations . length ; i ++ ) {
413416 const annotation = annotations [ i ] ;
414417 const class_id = this . get_annotation_class_id ( annotation ) ;
415- const class_def = subtask . class_defs . find ( ( def ) => def . id === class_id ) ;
418+ const class_def = class_def_by_id . get ( class_id ) ;
416419 const class_name = class_def ? class_def . name : "Unknown" ;
417420 const color = this . ulabel . color_info [ class_id ] || "#cccccc" ;
418421 const svg = this . get_spatial_type_svg ( annotation . spatial_type ! , color ) ;
@@ -437,6 +440,8 @@ export class AnnotationListToolboxItem extends ToolboxItem {
437440 * Build HTML for grouped (by class) list
438441 */
439442 private build_grouped_list_html ( annotations : ULabelAnnotation [ ] , subtask : ULabelSubtask ) : string {
443+ const class_def_by_id = this . build_class_def_by_id ( subtask ) ;
444+
440445 // Group annotations by class
441446 const groups : { [ class_id : number ] : ULabelAnnotation [ ] } = { } ;
442447
@@ -448,13 +453,32 @@ export class AnnotationListToolboxItem extends ToolboxItem {
448453 groups [ class_id ] . push ( annotation ) ;
449454 }
450455
456+ // Build the render order: first walk class_defs in declared order so the
457+ // group headers appear in the same order as the AnnotationIDToolboxItem.
458+ // Then append any orphan class_ids (not in class_defs) as a defensive
459+ // fallback so we never silently drop annotations.
460+ const ordered_class_ids : number [ ] = [ ] ;
461+ const seen_class_ids : Set < number > = new Set ( ) ;
462+ for ( const def of subtask . class_defs ) {
463+ if ( groups [ def . id ] ) {
464+ ordered_class_ids . push ( def . id ) ;
465+ seen_class_ids . add ( def . id ) ;
466+ }
467+ }
468+ const orphan_class_ids = Object . keys ( groups )
469+ . map ( ( id_str ) => parseInt ( id_str ) )
470+ . filter ( ( id ) => ! seen_class_ids . has ( id ) )
471+ . sort ( ( a , b ) => a - b ) ;
472+ for ( const id of orphan_class_ids ) {
473+ ordered_class_ids . push ( id ) ;
474+ }
475+
451476 // Build HTML for each group
452477 let html = "" ;
453478
454- for ( const class_id_str in groups ) {
455- const class_id = parseInt ( class_id_str ) ;
479+ for ( const class_id of ordered_class_ids ) {
456480 const group_annotations = groups [ class_id ] ;
457- const class_def = subtask . class_defs . find ( ( def ) => def . id === class_id ) ;
481+ const class_def = class_def_by_id . get ( class_id ) ;
458482 const class_name = class_def ? class_def . name : "Unknown" ;
459483 const color = this . ulabel . color_info [ class_id ] || "#cccccc" ;
460484
@@ -563,6 +587,20 @@ export class AnnotationListToolboxItem extends ToolboxItem {
563587 return class_id ;
564588 }
565589
590+ /**
591+ * Build a Map from class id to ClassDefinition for the given subtask. The
592+ * returned map is intended to be used for the duration of a single render so
593+ * that per-annotation class lookups don't repeat a linear search through
594+ * `subtask.class_defs`.
595+ */
596+ private build_class_def_by_id ( subtask : ULabelSubtask ) : Map < number , ULabelSubtask [ "class_defs" ] [ number ] > {
597+ const map = new Map < number , ULabelSubtask [ "class_defs" ] [ number ] > ( ) ;
598+ for ( const def of subtask . class_defs ) {
599+ map . set ( def . id , def ) ;
600+ }
601+ return map ;
602+ }
603+
566604 /**
567605 * Get the HTML for this toolbox item
568606 */
@@ -604,23 +642,43 @@ export class AnnotationListToolboxItem extends ToolboxItem {
604642 * Code called after all of ULabel's constructor and initialization code is called
605643 */
606644 public after_init ( ) : void {
607- // Restore collapsed state from localStorage
608- this . restore_collapsed_state ( ) ;
645+ // Restore persisted UI state from localStorage
646+ this . restore_persisted_state ( ) ;
609647
610648 // Initial list update
611649 this . update_list ( ) ;
612650 }
613651
614652 /**
615- * Restore the collapsed state from localStorage
653+ * Restore persisted UI state (collapsed flag + checkbox toggles) from localStorage.
616654 */
617- private restore_collapsed_state ( ) : void {
618- const stored_state = get_local_storage_item ( "ulabel_annotation_list_collapsed" ) ;
619- if ( stored_state === "false" ) {
655+ private restore_persisted_state ( ) : void {
656+ const stored_collapsed = get_local_storage_item ( "ulabel_annotation_list_collapsed" ) ;
657+ if ( stored_collapsed === "false" ) {
620658 this . is_collapsed = false ;
621- } else if ( stored_state === "true" ) {
659+ } else if ( stored_collapsed === "true" ) {
622660 this . is_collapsed = true ;
623661 }
662+
663+ const stored_show_deprecated = get_local_storage_item ( "ulabel_annotation_list_show_deprecated" ) ;
664+ if ( stored_show_deprecated === "true" ) {
665+ this . show_deprecated = true ;
666+ } else if ( stored_show_deprecated === "false" ) {
667+ this . show_deprecated = false ;
668+ }
669+
670+ const stored_group_by_class = get_local_storage_item ( "ulabel_annotation_list_group_by_class" ) ;
671+ if ( stored_group_by_class === "true" ) {
672+ this . group_by_class = true ;
673+ } else if ( stored_group_by_class === "false" ) {
674+ this . group_by_class = false ;
675+ }
676+
677+ // Reflect restored values into the checkbox DOM so the UI matches state.
678+ const show_deprecated_input = document . querySelector < HTMLInputElement > ( "#annotation-list-show-deprecated" ) ;
679+ if ( show_deprecated_input ) show_deprecated_input . checked = this . show_deprecated ;
680+ const group_by_class_input = document . querySelector < HTMLInputElement > ( "#annotation-list-group-by-class" ) ;
681+ if ( group_by_class_input ) group_by_class_input . checked = this . group_by_class ;
624682 }
625683
626684 /**
0 commit comments