cjm-transcript-source-select 0.0.14__tar.gz → 0.0.16__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. {cjm_transcript_source_select-0.0.14/cjm_transcript_source_select.egg-info → cjm_transcript_source_select-0.0.16}/PKG-INFO +76 -25
  2. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.16}/README.md +75 -24
  3. cjm_transcript_source_select-0.0.16/cjm_transcript_source_select/__init__.py +1 -0
  4. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.16}/cjm_transcript_source_select/_modidx.py +10 -2
  5. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.16}/cjm_transcript_source_select/models.py +34 -3
  6. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.16}/cjm_transcript_source_select/routes/init.py +16 -8
  7. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.16}/cjm_transcript_source_select/routes/local_files.py +142 -14
  8. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.16}/cjm_transcript_source_select/routes/tabs.py +16 -13
  9. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.16/cjm_transcript_source_select.egg-info}/PKG-INFO +76 -25
  10. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.16}/settings.ini +1 -1
  11. cjm_transcript_source_select-0.0.14/cjm_transcript_source_select/__init__.py +0 -1
  12. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.16}/LICENSE +0 -0
  13. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.16}/MANIFEST.in +0 -0
  14. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.16}/cjm_transcript_source_select/components/__init__.py +0 -0
  15. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.16}/cjm_transcript_source_select/components/helpers.py +0 -0
  16. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.16}/cjm_transcript_source_select/components/local_files.py +0 -0
  17. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.16}/cjm_transcript_source_select/components/preview_panel.py +0 -0
  18. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.16}/cjm_transcript_source_select/components/selection_queue.py +0 -0
  19. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.16}/cjm_transcript_source_select/components/source_browser.py +0 -0
  20. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.16}/cjm_transcript_source_select/components/step_renderer.py +0 -0
  21. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.16}/cjm_transcript_source_select/html_ids.py +0 -0
  22. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.16}/cjm_transcript_source_select/routes/__init__.py +0 -0
  23. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.16}/cjm_transcript_source_select/routes/core.py +0 -0
  24. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.16}/cjm_transcript_source_select/routes/filtering.py +0 -0
  25. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.16}/cjm_transcript_source_select/routes/queue.py +0 -0
  26. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.16}/cjm_transcript_source_select/routes/source_browser.py +0 -0
  27. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.16}/cjm_transcript_source_select/services/__init__.py +0 -0
  28. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.16}/cjm_transcript_source_select/services/source.py +0 -0
  29. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.16}/cjm_transcript_source_select/services/source_utils.py +0 -0
  30. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.16}/cjm_transcript_source_select/utils.py +0 -0
  31. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.16}/cjm_transcript_source_select.egg-info/SOURCES.txt +0 -0
  32. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.16}/cjm_transcript_source_select.egg-info/dependency_links.txt +0 -0
  33. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.16}/cjm_transcript_source_select.egg-info/entry_points.txt +0 -0
  34. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.16}/cjm_transcript_source_select.egg-info/not-zip-safe +0 -0
  35. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.16}/cjm_transcript_source_select.egg-info/requires.txt +0 -0
  36. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.16}/cjm_transcript_source_select.egg-info/top_level.txt +0 -0
  37. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.16}/pyproject.toml +0 -0
  38. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.16}/setup.cfg +0 -0
  39. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.16}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: cjm-transcript-source-select
3
- Version: 0.0.14
3
+ Version: 0.0.16
4
4
  Summary: FastHTML source selection component for transcript decomposition workflows, with federated database browsing, drag-drop ordering, and keyboard navigation.
5
5
  Home-page: https://github.com/cj-mills/cjm-transcript-source-select
6
6
  Author: Christian J. Mills
@@ -106,52 +106,52 @@ graph LR
106
106
  utils[utils<br/>utils]
107
107
 
108
108
  components_helpers --> models
109
- components_local_files --> components_helpers
110
109
  components_local_files --> html_ids
110
+ components_local_files --> components_helpers
111
111
  components_preview_panel --> html_ids
112
112
  components_source_browser --> services_source_utils
113
113
  components_source_browser --> utils
114
114
  components_source_browser --> html_ids
115
- components_step_renderer --> components_preview_panel
116
- components_step_renderer --> models
117
- components_step_renderer --> utils
118
115
  components_step_renderer --> components_source_browser
116
+ components_step_renderer --> html_ids
117
+ components_step_renderer --> utils
119
118
  components_step_renderer --> components_selection_queue
119
+ components_step_renderer --> models
120
+ components_step_renderer --> components_preview_panel
120
121
  components_step_renderer --> components_local_files
121
- components_step_renderer --> html_ids
122
- routes_core --> models
123
122
  routes_core --> components_step_renderer
123
+ routes_core --> models
124
124
  routes_core --> html_ids
125
125
  routes_core --> components_selection_queue
126
126
  routes_core --> services_source
127
- routes_filtering --> models
128
127
  routes_filtering --> routes_core
129
128
  routes_filtering --> services_source_utils
129
+ routes_filtering --> models
130
130
  routes_filtering --> services_source
131
- routes_init --> routes_core
132
131
  routes_init --> routes_queue
132
+ routes_init --> routes_core
133
133
  routes_init --> models
134
- routes_init --> routes_local_files
135
- routes_init --> routes_source_browser
136
134
  routes_init --> routes_filtering
137
135
  routes_init --> routes_tabs
138
136
  routes_init --> services_source
137
+ routes_init --> routes_source_browser
138
+ routes_init --> routes_local_files
139
139
  routes_local_files --> components_local_files
140
- routes_local_files --> models
141
- routes_local_files --> services_source
142
140
  routes_local_files --> routes_core
143
- routes_queue --> services_source_utils
141
+ routes_local_files --> services_source
142
+ routes_local_files --> models
144
143
  routes_queue --> routes_core
145
- routes_queue --> components_preview_panel
144
+ routes_queue --> services_source_utils
146
145
  routes_queue --> models
146
+ routes_queue --> components_preview_panel
147
147
  routes_queue --> services_source
148
148
  routes_source_browser --> routes_core
149
- routes_source_browser --> components_preview_panel
149
+ routes_source_browser --> html_ids
150
150
  routes_source_browser --> models
151
151
  routes_source_browser --> components_source_browser
152
- routes_source_browser --> services_source_utils
152
+ routes_source_browser --> components_preview_panel
153
153
  routes_source_browser --> services_source
154
- routes_source_browser --> html_ids
154
+ routes_source_browser --> services_source_utils
155
155
  routes_tabs --> routes_core
156
156
  routes_tabs --> services_source_utils
157
157
  routes_tabs --> models
@@ -426,7 +426,7 @@ def init_selection_routers(
426
426
  source_service: SourceService, # The source service for queries
427
427
  workflow_id: str, # The workflow identifier
428
428
  prefix: str, # Base prefix for selection routes (e.g., "/workflow/selection")
429
- ) -> Tuple[List[APIRouter], SelectionUrls, Dict[str, Callable], Callable, "SourceBrowserRouterState"]
429
+ ) -> SelectionResult: # Selection router result with routers, urls, routes, and restore
430
430
  "Initialize and return all selection routers with URL bundle."
431
431
  ```
432
432
 
@@ -528,8 +528,9 @@ def _handle_remove_external_source(
528
528
  external_db_paths_ref: List[str], # Shared external paths list (mutated in place)
529
529
  fb_routers: FileBrowserRouters, # File browser routers (for targeted OOB)
530
530
  remove_url: str, # URL for remove button in external sources list
531
- ): # Tuple of OOB elements (external sources list + checkbox cells)
532
- "Remove an external database source from the Added Sources list."
531
+ urls: SelectionUrls, # Full URL bundle for queue re-rendering
532
+ ): # Tuple of OOB elements (external sources list + checkbox cells + queue + stats)
533
+ "Remove an external database source and clean up orphaned queue items."
533
534
  ```
534
535
 
535
536
  ``` python
@@ -539,7 +540,7 @@ def init_local_files_router(
539
540
  source_service: SourceService, # The source service for external db ops
540
541
  prefix: str, # Route prefix (e.g., "/workflow/selection/local_files")
541
542
  urls: SelectionUrls, # URL bundle for rendering
542
- ) -> Tuple[List, Dict[str, Callable], Callable]: # (routers, route_dict, render_panel_fn)
543
+ ) -> LocalFilesResult: # Router result with routers, routes, render, restore, and reset
543
544
  "Initialize local files browser routes with new file browser API."
544
545
  ```
545
546
 
@@ -558,10 +559,33 @@ _local_files_provider: Optional[LocalFileSystemProvider] = None
558
559
  ``` python
559
560
  from cjm_transcript_source_select.models import (
560
561
  SelectionStepState,
561
- SelectionUrls
562
+ SelectionUrls,
563
+ LocalFilesResult,
564
+ SelectionResult
562
565
  )
563
566
  ```
564
567
 
568
+ #### Functions
569
+
570
+ ``` python
571
+ def _no_op_restore(session_id: str) -> None:
572
+ """Default no-op for restore_state."""
573
+ pass
574
+
575
+ def _no_op_reset() -> None
576
+ "Default no-op for restore_state."
577
+ ```
578
+
579
+ ``` python
580
+ def _no_op_reset() -> None:
581
+ """Default no-op for reset_state."""
582
+ pass
583
+
584
+ @dataclass
585
+ class LocalFilesResult
586
+ "Default no-op for reset_state."
587
+ ```
588
+
565
589
  #### Classes
566
590
 
567
591
  ``` python
@@ -591,6 +615,32 @@ class SelectionUrls:
591
615
  tab_switch: str = '' # Switch source tabs
592
616
  ```
593
617
 
618
+ ``` python
619
+ @dataclass
620
+ class LocalFilesResult:
621
+ "Return type from init_local_files_router."
622
+
623
+ routers: List[APIRouter] # Routers to register (custom + file browser + VC)
624
+ routes: Dict[str, Callable] # Named route handlers
625
+ render_panel: Callable # (error_message?, session_id?) -> rendered panel
626
+ restore_state: Callable = field(...) # (session_id) -> None, restore persisted state
627
+ reset_state: Callable = field(...) # () -> None, reset in-memory caches
628
+ ```
629
+
630
+ ``` python
631
+ @dataclass
632
+ class SelectionResult:
633
+ "Return type from init_selection_routers."
634
+
635
+ routers: List[APIRouter] # All selection routers to register
636
+ urls: 'SelectionUrls' = field(...) # URL bundle
637
+ routes: Dict[str, Callable] = field(...) # All named route handlers
638
+ render_local_files_panel: Optional[Callable] # Render fn for local files tab
639
+ sb_state: Any # SourceBrowserRouterState
640
+ restore_state: Callable = field(...) # (session_id) -> None, restore persisted state
641
+ reset_state: Callable = field(...) # () -> None, reset in-memory caches
642
+ ```
643
+
594
644
  ### preview_panel (`preview_panel.ipynb`)
595
645
 
596
646
  > Collapsible preview panel for displaying selected content
@@ -1434,15 +1484,16 @@ from cjm_transcript_source_select.routes.tabs import (
1434
1484
 
1435
1485
  ``` python
1436
1486
  def _handle_tab_switch(
1437
- state_store: WorkflowStateStore, # The workflow state store
1438
- workflow_id: str, # The workflow identifier
1439
1487
  source_service: SourceService, # The source service for queries
1440
1488
  request, # FastHTML request object
1441
1489
  sess, # FastHTML session object
1442
1490
  direction: str, # Direction: "prev", "next", "db", or "files"
1443
1491
  urls: SelectionUrls, # URL bundle for rendering
1492
+ current_tab_ref: List[str], # Mutable ref [current_tab] for closure-based tracking
1444
1493
  render_local_files_panel: Optional[Callable] = None, # Render fn for Files tab
1445
1494
  sb_state: Any = None, # SourceBrowserRouterState for DB tab VC rendering
1495
+ state_store: WorkflowStateStore = None, # State store (for reading step state)
1496
+ workflow_id: str = "", # Workflow ID (for reading step state)
1446
1497
  ): # Tuple of inner content, OOB tab headers, and tab switch script
1447
1498
  "Switch between Plugin DB and Local Files tabs."
1448
1499
  ```
@@ -60,52 +60,52 @@ graph LR
60
60
  utils[utils<br/>utils]
61
61
 
62
62
  components_helpers --> models
63
- components_local_files --> components_helpers
64
63
  components_local_files --> html_ids
64
+ components_local_files --> components_helpers
65
65
  components_preview_panel --> html_ids
66
66
  components_source_browser --> services_source_utils
67
67
  components_source_browser --> utils
68
68
  components_source_browser --> html_ids
69
- components_step_renderer --> components_preview_panel
70
- components_step_renderer --> models
71
- components_step_renderer --> utils
72
69
  components_step_renderer --> components_source_browser
70
+ components_step_renderer --> html_ids
71
+ components_step_renderer --> utils
73
72
  components_step_renderer --> components_selection_queue
73
+ components_step_renderer --> models
74
+ components_step_renderer --> components_preview_panel
74
75
  components_step_renderer --> components_local_files
75
- components_step_renderer --> html_ids
76
- routes_core --> models
77
76
  routes_core --> components_step_renderer
77
+ routes_core --> models
78
78
  routes_core --> html_ids
79
79
  routes_core --> components_selection_queue
80
80
  routes_core --> services_source
81
- routes_filtering --> models
82
81
  routes_filtering --> routes_core
83
82
  routes_filtering --> services_source_utils
83
+ routes_filtering --> models
84
84
  routes_filtering --> services_source
85
- routes_init --> routes_core
86
85
  routes_init --> routes_queue
86
+ routes_init --> routes_core
87
87
  routes_init --> models
88
- routes_init --> routes_local_files
89
- routes_init --> routes_source_browser
90
88
  routes_init --> routes_filtering
91
89
  routes_init --> routes_tabs
92
90
  routes_init --> services_source
91
+ routes_init --> routes_source_browser
92
+ routes_init --> routes_local_files
93
93
  routes_local_files --> components_local_files
94
- routes_local_files --> models
95
- routes_local_files --> services_source
96
94
  routes_local_files --> routes_core
97
- routes_queue --> services_source_utils
95
+ routes_local_files --> services_source
96
+ routes_local_files --> models
98
97
  routes_queue --> routes_core
99
- routes_queue --> components_preview_panel
98
+ routes_queue --> services_source_utils
100
99
  routes_queue --> models
100
+ routes_queue --> components_preview_panel
101
101
  routes_queue --> services_source
102
102
  routes_source_browser --> routes_core
103
- routes_source_browser --> components_preview_panel
103
+ routes_source_browser --> html_ids
104
104
  routes_source_browser --> models
105
105
  routes_source_browser --> components_source_browser
106
- routes_source_browser --> services_source_utils
106
+ routes_source_browser --> components_preview_panel
107
107
  routes_source_browser --> services_source
108
- routes_source_browser --> html_ids
108
+ routes_source_browser --> services_source_utils
109
109
  routes_tabs --> routes_core
110
110
  routes_tabs --> services_source_utils
111
111
  routes_tabs --> models
@@ -380,7 +380,7 @@ def init_selection_routers(
380
380
  source_service: SourceService, # The source service for queries
381
381
  workflow_id: str, # The workflow identifier
382
382
  prefix: str, # Base prefix for selection routes (e.g., "/workflow/selection")
383
- ) -> Tuple[List[APIRouter], SelectionUrls, Dict[str, Callable], Callable, "SourceBrowserRouterState"]
383
+ ) -> SelectionResult: # Selection router result with routers, urls, routes, and restore
384
384
  "Initialize and return all selection routers with URL bundle."
385
385
  ```
386
386
 
@@ -482,8 +482,9 @@ def _handle_remove_external_source(
482
482
  external_db_paths_ref: List[str], # Shared external paths list (mutated in place)
483
483
  fb_routers: FileBrowserRouters, # File browser routers (for targeted OOB)
484
484
  remove_url: str, # URL for remove button in external sources list
485
- ): # Tuple of OOB elements (external sources list + checkbox cells)
486
- "Remove an external database source from the Added Sources list."
485
+ urls: SelectionUrls, # Full URL bundle for queue re-rendering
486
+ ): # Tuple of OOB elements (external sources list + checkbox cells + queue + stats)
487
+ "Remove an external database source and clean up orphaned queue items."
487
488
  ```
488
489
 
489
490
  ``` python
@@ -493,7 +494,7 @@ def init_local_files_router(
493
494
  source_service: SourceService, # The source service for external db ops
494
495
  prefix: str, # Route prefix (e.g., "/workflow/selection/local_files")
495
496
  urls: SelectionUrls, # URL bundle for rendering
496
- ) -> Tuple[List, Dict[str, Callable], Callable]: # (routers, route_dict, render_panel_fn)
497
+ ) -> LocalFilesResult: # Router result with routers, routes, render, restore, and reset
497
498
  "Initialize local files browser routes with new file browser API."
498
499
  ```
499
500
 
@@ -512,10 +513,33 @@ _local_files_provider: Optional[LocalFileSystemProvider] = None
512
513
  ``` python
513
514
  from cjm_transcript_source_select.models import (
514
515
  SelectionStepState,
515
- SelectionUrls
516
+ SelectionUrls,
517
+ LocalFilesResult,
518
+ SelectionResult
516
519
  )
517
520
  ```
518
521
 
522
+ #### Functions
523
+
524
+ ``` python
525
+ def _no_op_restore(session_id: str) -> None:
526
+ """Default no-op for restore_state."""
527
+ pass
528
+
529
+ def _no_op_reset() -> None
530
+ "Default no-op for restore_state."
531
+ ```
532
+
533
+ ``` python
534
+ def _no_op_reset() -> None:
535
+ """Default no-op for reset_state."""
536
+ pass
537
+
538
+ @dataclass
539
+ class LocalFilesResult
540
+ "Default no-op for reset_state."
541
+ ```
542
+
519
543
  #### Classes
520
544
 
521
545
  ``` python
@@ -545,6 +569,32 @@ class SelectionUrls:
545
569
  tab_switch: str = '' # Switch source tabs
546
570
  ```
547
571
 
572
+ ``` python
573
+ @dataclass
574
+ class LocalFilesResult:
575
+ "Return type from init_local_files_router."
576
+
577
+ routers: List[APIRouter] # Routers to register (custom + file browser + VC)
578
+ routes: Dict[str, Callable] # Named route handlers
579
+ render_panel: Callable # (error_message?, session_id?) -> rendered panel
580
+ restore_state: Callable = field(...) # (session_id) -> None, restore persisted state
581
+ reset_state: Callable = field(...) # () -> None, reset in-memory caches
582
+ ```
583
+
584
+ ``` python
585
+ @dataclass
586
+ class SelectionResult:
587
+ "Return type from init_selection_routers."
588
+
589
+ routers: List[APIRouter] # All selection routers to register
590
+ urls: 'SelectionUrls' = field(...) # URL bundle
591
+ routes: Dict[str, Callable] = field(...) # All named route handlers
592
+ render_local_files_panel: Optional[Callable] # Render fn for local files tab
593
+ sb_state: Any # SourceBrowserRouterState
594
+ restore_state: Callable = field(...) # (session_id) -> None, restore persisted state
595
+ reset_state: Callable = field(...) # () -> None, reset in-memory caches
596
+ ```
597
+
548
598
  ### preview_panel (`preview_panel.ipynb`)
549
599
 
550
600
  > Collapsible preview panel for displaying selected content
@@ -1388,15 +1438,16 @@ from cjm_transcript_source_select.routes.tabs import (
1388
1438
 
1389
1439
  ``` python
1390
1440
  def _handle_tab_switch(
1391
- state_store: WorkflowStateStore, # The workflow state store
1392
- workflow_id: str, # The workflow identifier
1393
1441
  source_service: SourceService, # The source service for queries
1394
1442
  request, # FastHTML request object
1395
1443
  sess, # FastHTML session object
1396
1444
  direction: str, # Direction: "prev", "next", "db", or "files"
1397
1445
  urls: SelectionUrls, # URL bundle for rendering
1446
+ current_tab_ref: List[str], # Mutable ref [current_tab] for closure-based tracking
1398
1447
  render_local_files_panel: Optional[Callable] = None, # Render fn for Files tab
1399
1448
  sb_state: Any = None, # SourceBrowserRouterState for DB tab VC rendering
1449
+ state_store: WorkflowStateStore = None, # State store (for reading step state)
1450
+ workflow_id: str = "", # Workflow ID (for reading step state)
1400
1451
  ): # Tuple of inner content, OOB tab headers, and tab switch script
1401
1452
  "Switch between Plugin DB and Local Files tabs."
1402
1453
  ```
@@ -0,0 +1 @@
1
+ __version__ = "0.0.16"
@@ -81,10 +81,18 @@ d = { 'settings': { 'branch': 'main',
81
81
  'cjm_transcript_source_select/html_ids.py'),
82
82
  'cjm_transcript_source_select.html_ids.SelectionHtmlIds.source_row': ( 'html_ids.html#selectionhtmlids.source_row',
83
83
  'cjm_transcript_source_select/html_ids.py')},
84
- 'cjm_transcript_source_select.models': { 'cjm_transcript_source_select.models.SelectionStepState': ( 'models.html#selectionstepstate',
84
+ 'cjm_transcript_source_select.models': { 'cjm_transcript_source_select.models.LocalFilesResult': ( 'models.html#localfilesresult',
85
+ 'cjm_transcript_source_select/models.py'),
86
+ 'cjm_transcript_source_select.models.SelectionResult': ( 'models.html#selectionresult',
87
+ 'cjm_transcript_source_select/models.py'),
88
+ 'cjm_transcript_source_select.models.SelectionStepState': ( 'models.html#selectionstepstate',
85
89
  'cjm_transcript_source_select/models.py'),
86
90
  'cjm_transcript_source_select.models.SelectionUrls': ( 'models.html#selectionurls',
87
- 'cjm_transcript_source_select/models.py')},
91
+ 'cjm_transcript_source_select/models.py'),
92
+ 'cjm_transcript_source_select.models._no_op_reset': ( 'models.html#_no_op_reset',
93
+ 'cjm_transcript_source_select/models.py'),
94
+ 'cjm_transcript_source_select.models._no_op_restore': ( 'models.html#_no_op_restore',
95
+ 'cjm_transcript_source_select/models.py')},
88
96
  'cjm_transcript_source_select.routes.core': { 'cjm_transcript_source_select.routes.core._build_queue_response': ( 'routes/core.html#_build_queue_response',
89
97
  'cjm_transcript_source_select/routes/core.py'),
90
98
  'cjm_transcript_source_select.routes.core._find_duplicate_media_source': ( 'routes/core.html#_find_duplicate_media_source',
@@ -3,12 +3,14 @@
3
3
  # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/models.ipynb.
4
4
 
5
5
  # %% auto #0
6
- __all__ = ['SelectionStepState', 'SelectionUrls']
6
+ __all__ = ['SelectionStepState', 'SelectionUrls', 'LocalFilesResult', 'SelectionResult']
7
7
 
8
8
  # %% ../nbs/models.ipynb #selection-models-imports
9
- from typing import List, Dict, Any
9
+ from typing import List, Dict, Any, Callable, Optional
10
10
  from typing_extensions import TypedDict
11
- from dataclasses import dataclass
11
+ from dataclasses import dataclass, field
12
+
13
+ from fasthtml.common import APIRouter
12
14
 
13
15
  from cjm_source_provider.models import SelectedSource
14
16
 
@@ -51,3 +53,32 @@ class SelectionUrls:
51
53
 
52
54
  # Tab switching
53
55
  tab_switch: str = "" # Switch source tabs
56
+
57
+ # %% ../nbs/models.ipynb #gxc6wl5o4mn
58
+ def _no_op_restore(session_id: str) -> None:
59
+ """Default no-op for restore_state."""
60
+ pass
61
+
62
+ def _no_op_reset() -> None:
63
+ """Default no-op for reset_state."""
64
+ pass
65
+
66
+ @dataclass
67
+ class LocalFilesResult:
68
+ """Return type from init_local_files_router."""
69
+ routers: List[APIRouter] # Routers to register (custom + file browser + VC)
70
+ routes: Dict[str, Callable] # Named route handlers
71
+ render_panel: Callable # (error_message?, session_id?) -> rendered panel
72
+ restore_state: Callable = field(default=_no_op_restore) # (session_id) -> None, restore persisted state
73
+ reset_state: Callable = field(default=_no_op_reset) # () -> None, reset in-memory caches
74
+
75
+ @dataclass
76
+ class SelectionResult:
77
+ """Return type from init_selection_routers."""
78
+ routers: List[APIRouter] # All selection routers to register
79
+ urls: "SelectionUrls" = field(default_factory=lambda: SelectionUrls()) # URL bundle
80
+ routes: Dict[str, Callable] = field(default_factory=dict) # All named route handlers
81
+ render_local_files_panel: Optional[Callable] = None # Render fn for local files tab
82
+ sb_state: Any = None # SourceBrowserRouterState
83
+ restore_state: Callable = field(default=_no_op_restore) # (session_id) -> None, restore persisted state
84
+ reset_state: Callable = field(default=_no_op_reset) # () -> None, reset in-memory caches
@@ -12,7 +12,7 @@ from fasthtml.common import APIRouter
12
12
 
13
13
  from cjm_fasthtml_interactions.core.state_store import get_session_id
14
14
 
15
- from ..models import SelectionUrls
15
+ from ..models import SelectionUrls, SelectionResult
16
16
  from cjm_transcript_source_select.routes.core import (
17
17
  WorkflowStateStore,
18
18
  _rebuild_and_render_ref, _sync_items_ref,
@@ -32,7 +32,7 @@ def init_selection_routers(
32
32
  source_service: SourceService, # The source service for queries
33
33
  workflow_id: str, # The workflow identifier
34
34
  prefix: str, # Base prefix for selection routes (e.g., "/workflow/selection")
35
- ) -> Tuple[List[APIRouter], SelectionUrls, Dict[str, Callable], Callable, "SourceBrowserRouterState"]:
35
+ ) -> SelectionResult: # Selection router result with routers, urls, routes, and restore
36
36
  """Initialize and return all selection routers with URL bundle."""
37
37
  urls = SelectionUrls()
38
38
 
@@ -42,7 +42,7 @@ def init_selection_routers(
42
42
  filtering_router, filtering_routes = init_filtering_router(
43
43
  state_store, workflow_id, source_service, f"{prefix}/filtering", urls
44
44
  )
45
- local_files_routers, local_files_routes, render_local_files_panel = init_local_files_router(
45
+ local_files = init_local_files_router(
46
46
  state_store, workflow_id, source_service, f"{prefix}/local_files", urls
47
47
  )
48
48
  sb_state = init_source_browser_router(
@@ -92,7 +92,7 @@ def init_selection_routers(
92
92
 
93
93
  tabs_router, tabs_routes = init_tabs_router(
94
94
  state_store, workflow_id, source_service, f"{prefix}/tabs", urls,
95
- render_local_files_panel=render_local_files_panel,
95
+ render_local_files_panel=local_files.render_panel,
96
96
  sb_state=sb_state,
97
97
  )
98
98
 
@@ -108,16 +108,24 @@ def init_selection_routers(
108
108
  urls.keyboard_reorder = filtering_routes["keyboard_reorder"].to()
109
109
  urls.filter = filtering_routes["filter"].to()
110
110
  urls.grouping_change = filtering_routes["grouping_change"].to()
111
- urls.remove_external = local_files_routes["remove_external"].to()
111
+ urls.remove_external = local_files.routes["remove_external"].to()
112
112
  urls.tab_switch = tabs_routes["tab_switch"].to()
113
113
 
114
114
  merged_routes = {
115
115
  **queue_routes,
116
116
  **filtering_routes,
117
- **local_files_routes,
117
+ **local_files.routes,
118
118
  **tabs_routes,
119
119
  }
120
120
 
121
- routers = [queue_router, filtering_router, *local_files_routers, tabs_router, sb_state.router]
121
+ routers = [queue_router, filtering_router, *local_files.routers, tabs_router, sb_state.router]
122
122
 
123
- return routers, urls, merged_routes, render_local_files_panel, sb_state
123
+ return SelectionResult(
124
+ routers=routers,
125
+ urls=urls,
126
+ routes=merged_routes,
127
+ render_local_files_panel=local_files.render_panel,
128
+ sb_state=sb_state,
129
+ restore_state=local_files.restore_state,
130
+ reset_state=local_files.reset_state,
131
+ )
@@ -17,9 +17,10 @@ from cjm_fasthtml_file_browser.routes.handlers import init_router as init_fb_rou
17
17
 
18
18
  from cjm_fasthtml_interactions.core.state_store import get_session_id
19
19
 
20
- from ..models import SelectionUrls
20
+ from ..models import SelectionUrls, LocalFilesResult
21
21
  from cjm_transcript_source_select.routes.core import (
22
- WorkflowStateStore, _get_step_state, _update_step_state
22
+ WorkflowStateStore, _get_step_state, _update_step_state,
23
+ _build_queue_response,
23
24
  )
24
25
  from cjm_transcript_source_select.components.local_files import (
25
26
  _render_local_files_browser,
@@ -54,12 +55,15 @@ def _handle_remove_external_source(
54
55
  external_db_paths_ref: List[str], # Shared external paths list (mutated in place)
55
56
  fb_routers: FileBrowserRouters, # File browser routers (for targeted OOB)
56
57
  remove_url: str, # URL for remove button in external sources list
57
- ): # Tuple of OOB elements (external sources list + checkbox cells)
58
- """Remove an external database source from the Added Sources list."""
58
+ urls: SelectionUrls, # Full URL bundle for queue re-rendering
59
+ ): # Tuple of OOB elements (external sources list + checkbox cells + queue + stats)
60
+ """Remove an external database source and clean up orphaned queue items."""
59
61
  session_id = get_session_id(sess)
60
62
 
61
63
  # Remove the path if it exists
64
+ removed_provider_id = None
62
65
  if db_path in external_db_paths_ref:
66
+ removed_provider_id = f"external:{db_path}"
63
67
  external_db_paths_ref.remove(db_path)
64
68
  _update_step_state(state_store, workflow_id, session_id, external_db_paths=list(external_db_paths_ref))
65
69
  source_service.set_external_paths(list(external_db_paths_ref))
@@ -69,7 +73,28 @@ def _handle_remove_external_source(
69
73
  external_list_oob = _render_external_sources_list(
70
74
  list(external_db_paths_ref), remove_url, oob=True,
71
75
  )
72
- return (external_list_oob, *checkbox_oobs)
76
+
77
+ parts = [external_list_oob, *checkbox_oobs]
78
+
79
+ # Remove orphaned queue items from the deleted provider
80
+ if removed_provider_id is not None:
81
+ step_state = _get_step_state(state_store, workflow_id, session_id)
82
+ selected_sources = step_state.get("selected_sources", [])
83
+ filtered = [s for s in selected_sources if s.get("provider_id") != removed_provider_id]
84
+ if len(filtered) != len(selected_sources):
85
+ _update_step_state(state_store, workflow_id, session_id, selected_sources=filtered)
86
+ # Re-render queue + stats as OOB
87
+ queue_response = _build_queue_response(
88
+ state_store, workflow_id, source_service, session_id,
89
+ filtered, urls, include_checkbox_oobs=False,
90
+ )
91
+ queue_parts = queue_response if isinstance(queue_response, tuple) else (queue_response,)
92
+ for part in queue_parts:
93
+ if hasattr(part, 'attrs') and 'hx-swap-oob' not in part.attrs and 'id' in part.attrs:
94
+ part.attrs['hx-swap-oob'] = 'outerHTML'
95
+ parts.extend(queue_parts)
96
+
97
+ return tuple(parts)
73
98
 
74
99
  # %% ../../nbs/routes/local_files.ipynb #rdrpw4jrxpc
75
100
  def init_local_files_router(
@@ -78,7 +103,7 @@ def init_local_files_router(
78
103
  source_service: SourceService, # The source service for external db ops
79
104
  prefix: str, # Route prefix (e.g., "/workflow/selection/local_files")
80
105
  urls: SelectionUrls, # URL bundle for rendering
81
- ) -> Tuple[List, Dict[str, Callable], Callable]: # (routers, route_dict, render_panel_fn)
106
+ ) -> LocalFilesResult: # Router result with routers, routes, render, restore, and reset
82
107
  """Initialize local files browser routes with new file browser API."""
83
108
  provider = _get_local_files_provider()
84
109
  config = _create_db_browser_config()
@@ -90,18 +115,57 @@ def init_local_files_router(
90
115
  # --- File browser state accessors (shared, non-per-session) ---
91
116
  _browser_state = BrowserState(current_path=home_path)
92
117
 
118
+ # --- Lazy state restoration from persisted store ---
119
+ _state_restored = [False]
120
+
121
+ def _restore_from_persisted(session_id: str) -> None:
122
+ """Load persisted external_db_paths and file_browser_state on first call."""
123
+ if _state_restored[0]:
124
+ return
125
+ step_state = _get_step_state(state_store, workflow_id, session_id)
126
+
127
+ # Restore external_db_paths
128
+ persisted_paths = step_state.get("external_db_paths", [])
129
+ if persisted_paths:
130
+ _external_db_paths[:] = persisted_paths
131
+ source_service.set_external_paths(list(_external_db_paths))
132
+
133
+ # Restore browser state (current_path, sort)
134
+ persisted_browser = step_state.get("file_browser_state", {})
135
+ if persisted_browser:
136
+ restored = BrowserState.from_dict(persisted_browser)
137
+ _browser_state.current_path = restored.current_path
138
+ _browser_state.sort_by = restored.sort_by
139
+ _browser_state.sort_descending = restored.sort_descending
140
+ # Selection synced from _external_db_paths via _fb_state_getter
141
+
142
+ _state_restored[0] = True
143
+
93
144
  def _fb_state_getter() -> BrowserState:
94
145
  """Get browser state with selection synced to external_db_paths."""
95
146
  _browser_state.selection.selected_paths = list(_external_db_paths)
96
147
  return _browser_state
97
148
 
98
- def _fb_state_setter(state: BrowserState) -> None:
99
- """Save browser state from file browser."""
149
+ def _fb_state_setter(state: BrowserState, request=None) -> None:
150
+ """Save browser state from file browser and persist to state store."""
151
+ # Lazy restore on first request
152
+ if request is not None and not _state_restored[0]:
153
+ _restore_from_persisted(get_session_id(request.session))
154
+
100
155
  _browser_state.current_path = state.current_path
101
156
  _browser_state.sort_by = state.sort_by
102
157
  _browser_state.sort_descending = state.sort_descending
103
158
  _browser_state.selection = state.selection
104
159
 
160
+ # Persist browser state
161
+ if request is not None:
162
+ session_id = get_session_id(request.session)
163
+ _update_step_state(
164
+ state_store, workflow_id, session_id,
165
+ file_browser_state=state.to_dict(),
166
+ current_browse_path=state.current_path,
167
+ )
168
+
105
169
  # --- Callbacks for file browser ---
106
170
  def _validate_selection(path: str) -> Tuple[bool, str]:
107
171
  """Validate .db schema before allowing selection (checkbox click path)."""
@@ -111,10 +175,16 @@ def init_local_files_router(
111
175
  return (False, error)
112
176
  return (True, "")
113
177
 
114
- def _on_selection_change(selected_paths: List[str]) -> Tuple:
178
+ def _on_selection_change(selected_paths: List[str], request=None) -> Tuple:
115
179
  """Sync external_db_paths with validation and return OOB updates."""
116
- # Validate any newly added paths
180
+ # Lazy restore on first request
181
+ if request is not None and not _state_restored[0]:
182
+ _restore_from_persisted(get_session_id(request.session))
183
+
184
+ # Detect removed paths before validation
117
185
  old_set = set(_external_db_paths)
186
+
187
+ # Validate any newly added paths
118
188
  validated_paths = []
119
189
  error_message = None
120
190
  for path in selected_paths:
@@ -134,10 +204,40 @@ def init_local_files_router(
134
204
  _external_db_paths[:] = validated_paths
135
205
  source_service.set_external_paths(list(_external_db_paths))
136
206
 
137
- return (
207
+ # Persist external_db_paths
208
+ if request is not None:
209
+ session_id = get_session_id(request.session)
210
+ _update_step_state(
211
+ state_store, workflow_id, session_id,
212
+ external_db_paths=list(_external_db_paths),
213
+ )
214
+
215
+ parts = [
138
216
  _render_external_sources_list(list(_external_db_paths), urls.remove_external, oob=True),
139
217
  _render_error_alert(error_message, oob=True),
140
- )
218
+ ]
219
+
220
+ # Clean up orphaned queue items for removed external DBs
221
+ removed_paths = old_set - set(validated_paths)
222
+ if removed_paths and request is not None:
223
+ session_id = get_session_id(request.session)
224
+ removed_provider_ids = {f"external:{p}" for p in removed_paths}
225
+ step_state = _get_step_state(state_store, workflow_id, session_id)
226
+ selected_sources = step_state.get("selected_sources", [])
227
+ filtered = [s for s in selected_sources if s.get("provider_id") not in removed_provider_ids]
228
+ if len(filtered) != len(selected_sources):
229
+ _update_step_state(state_store, workflow_id, session_id, selected_sources=filtered)
230
+ queue_response = _build_queue_response(
231
+ state_store, workflow_id, source_service, session_id,
232
+ filtered, urls, include_checkbox_oobs=False,
233
+ )
234
+ queue_parts = queue_response if isinstance(queue_response, tuple) else (queue_response,)
235
+ for part in queue_parts:
236
+ if hasattr(part, 'attrs') and 'hx-swap-oob' not in part.attrs and 'id' in part.attrs:
237
+ part.attrs['hx-swap-oob'] = 'outerHTML'
238
+ parts.extend(queue_parts)
239
+
240
+ return tuple(parts)
141
241
 
142
242
  fb_callbacks = FileBrowserCallbacks(
143
243
  validate_selection=_validate_selection,
@@ -156,8 +256,12 @@ def init_local_files_router(
156
256
  )
157
257
 
158
258
  # --- Render function for the full local files panel ---
159
- def _render_panel(error_message: Optional[str] = None) -> Any:
259
+ def _render_panel(error_message: Optional[str] = None, session_id: Optional[str] = None) -> Any:
160
260
  """Render the complete local files panel (file browser + external sources list)."""
261
+ # Restore persisted state and rebuild items before rendering
262
+ if session_id is not None and not _state_restored[0]:
263
+ _restore_from_persisted(session_id)
264
+ fb_routers.sync_items()
161
265
  return _render_local_files_browser(
162
266
  render_fn=fb_routers.render,
163
267
  external_paths=list(_external_db_paths),
@@ -165,6 +269,23 @@ def init_local_files_router(
165
269
  error_message=error_message,
166
270
  )
167
271
 
272
+ # --- Restore callable for eager state restoration ---
273
+ def _restore_state(session_id: str) -> None:
274
+ """Restore persisted local files state (external_db_paths + browser state)."""
275
+ _restore_from_persisted(session_id)
276
+ fb_routers.sync_items()
277
+
278
+ # --- Reset callable for clearing in-memory caches ---
279
+ def _reset_state() -> None:
280
+ """Reset in-memory state so next restore re-reads from DB."""
281
+ _external_db_paths[:] = []
282
+ _state_restored[0] = False
283
+ _browser_state.current_path = home_path
284
+ _browser_state.sort_by = "name"
285
+ _browser_state.sort_descending = False
286
+ _browser_state.selection.selected_paths = []
287
+ source_service.set_external_paths([])
288
+
168
289
  # --- Custom router for remove_external (needs session context) ---
169
290
  custom_router = APIRouter(prefix=prefix)
170
291
 
@@ -177,6 +298,7 @@ def init_local_files_router(
177
298
  external_db_paths_ref=_external_db_paths,
178
299
  fb_routers=fb_routers,
179
300
  remove_url=urls.remove_external,
301
+ urls=urls,
180
302
  )
181
303
 
182
304
  # All routers to register
@@ -186,4 +308,10 @@ def init_local_files_router(
186
308
  "remove_external": remove_external,
187
309
  }
188
310
 
189
- return all_routers, routes, _render_panel
311
+ return LocalFilesResult(
312
+ routers=all_routers,
313
+ routes=routes,
314
+ render_panel=_render_panel,
315
+ restore_state=_restore_state,
316
+ reset_state=_reset_state,
317
+ )
@@ -6,7 +6,7 @@
6
6
  __all__ = ['init_tabs_router']
7
7
 
8
8
  # %% ../../nbs/routes/tabs.ipynb #ca31d3ff
9
- from typing import Any, Optional, Tuple, Dict, Callable
9
+ from typing import Any, Optional, Tuple, Dict, List, Callable
10
10
 
11
11
  from fasthtml.common import APIRouter, Script
12
12
 
@@ -24,15 +24,16 @@ from ..services.source_utils import calculate_next_tab
24
24
 
25
25
  # %% ../../nbs/routes/tabs.ipynb #4d0109d3
26
26
  def _handle_tab_switch(
27
- state_store: WorkflowStateStore, # The workflow state store
28
- workflow_id: str, # The workflow identifier
29
27
  source_service: SourceService, # The source service for queries
30
28
  request, # FastHTML request object
31
29
  sess, # FastHTML session object
32
30
  direction: str, # Direction: "prev", "next", "db", or "files"
33
31
  urls: SelectionUrls, # URL bundle for rendering
32
+ current_tab_ref: List[str], # Mutable ref [current_tab] for closure-based tracking
34
33
  render_local_files_panel: Optional[Callable] = None, # Render fn for Files tab
35
34
  sb_state: Any = None, # SourceBrowserRouterState for DB tab VC rendering
35
+ state_store: WorkflowStateStore = None, # State store (for reading step state)
36
+ workflow_id: str = "", # Workflow ID (for reading step state)
36
37
  ): # Tuple of inner content, OOB tab headers, and tab switch script
37
38
  """Switch between Plugin DB and Local Files tabs."""
38
39
  session_id = get_session_id(sess)
@@ -41,14 +42,10 @@ def _handle_tab_switch(
41
42
  selected_sources = step_state.get("selected_sources", [])
42
43
  grouping_mode = step_state.get("grouping_mode", "media_path")
43
44
 
44
- # Determine new tab
45
- workflow_state = state_store.get_state(workflow_id, session_id)
46
- current_tab = workflow_state.get("source_tab", "db")
45
+ # Determine new tab from closure-based tracking (not persisted)
46
+ current_tab = current_tab_ref[0]
47
47
  new_tab = calculate_next_tab(direction, current_tab, ["db", "files"])
48
-
49
- # Save new tab state
50
- workflow_state["source_tab"] = new_tab
51
- state_store.update_state(workflow_id, session_id, workflow_state)
48
+ current_tab_ref[0] = new_tab
52
49
 
53
50
  # Render the content for the new active tab
54
51
  if new_tab == "db":
@@ -56,7 +53,7 @@ def _handle_tab_switch(
56
53
  content = sb_state.rebuild_and_render(all_transcriptions, selected_sources, grouping_mode)
57
54
  else:
58
55
  if render_local_files_panel is not None:
59
- content = render_local_files_panel()
56
+ content = render_local_files_panel(session_id=session_id)
60
57
  else:
61
58
  from cjm_transcript_source_select.components.local_files import _render_local_files_browser
62
59
  content = _render_local_files_browser()
@@ -85,14 +82,20 @@ def init_tabs_router(
85
82
  """Initialize tab switching routes."""
86
83
  router = APIRouter(prefix=prefix)
87
84
 
85
+ # Closure-based tab tracking (not persisted across restarts)
86
+ _current_tab: List[str] = ["db"]
87
+
88
88
  @router
89
89
  def tab_switch(request, sess, direction: str):
90
90
  """Switch between source tabs."""
91
91
  return _handle_tab_switch(
92
- state_store, workflow_id, source_service,
93
- request, sess, direction, urls=urls,
92
+ source_service=source_service,
93
+ request=request, sess=sess, direction=direction, urls=urls,
94
+ current_tab_ref=_current_tab,
94
95
  render_local_files_panel=render_local_files_panel,
95
96
  sb_state=sb_state,
97
+ state_store=state_store,
98
+ workflow_id=workflow_id,
96
99
  )
97
100
 
98
101
  routes = {
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: cjm-transcript-source-select
3
- Version: 0.0.14
3
+ Version: 0.0.16
4
4
  Summary: FastHTML source selection component for transcript decomposition workflows, with federated database browsing, drag-drop ordering, and keyboard navigation.
5
5
  Home-page: https://github.com/cj-mills/cjm-transcript-source-select
6
6
  Author: Christian J. Mills
@@ -106,52 +106,52 @@ graph LR
106
106
  utils[utils<br/>utils]
107
107
 
108
108
  components_helpers --> models
109
- components_local_files --> components_helpers
110
109
  components_local_files --> html_ids
110
+ components_local_files --> components_helpers
111
111
  components_preview_panel --> html_ids
112
112
  components_source_browser --> services_source_utils
113
113
  components_source_browser --> utils
114
114
  components_source_browser --> html_ids
115
- components_step_renderer --> components_preview_panel
116
- components_step_renderer --> models
117
- components_step_renderer --> utils
118
115
  components_step_renderer --> components_source_browser
116
+ components_step_renderer --> html_ids
117
+ components_step_renderer --> utils
119
118
  components_step_renderer --> components_selection_queue
119
+ components_step_renderer --> models
120
+ components_step_renderer --> components_preview_panel
120
121
  components_step_renderer --> components_local_files
121
- components_step_renderer --> html_ids
122
- routes_core --> models
123
122
  routes_core --> components_step_renderer
123
+ routes_core --> models
124
124
  routes_core --> html_ids
125
125
  routes_core --> components_selection_queue
126
126
  routes_core --> services_source
127
- routes_filtering --> models
128
127
  routes_filtering --> routes_core
129
128
  routes_filtering --> services_source_utils
129
+ routes_filtering --> models
130
130
  routes_filtering --> services_source
131
- routes_init --> routes_core
132
131
  routes_init --> routes_queue
132
+ routes_init --> routes_core
133
133
  routes_init --> models
134
- routes_init --> routes_local_files
135
- routes_init --> routes_source_browser
136
134
  routes_init --> routes_filtering
137
135
  routes_init --> routes_tabs
138
136
  routes_init --> services_source
137
+ routes_init --> routes_source_browser
138
+ routes_init --> routes_local_files
139
139
  routes_local_files --> components_local_files
140
- routes_local_files --> models
141
- routes_local_files --> services_source
142
140
  routes_local_files --> routes_core
143
- routes_queue --> services_source_utils
141
+ routes_local_files --> services_source
142
+ routes_local_files --> models
144
143
  routes_queue --> routes_core
145
- routes_queue --> components_preview_panel
144
+ routes_queue --> services_source_utils
146
145
  routes_queue --> models
146
+ routes_queue --> components_preview_panel
147
147
  routes_queue --> services_source
148
148
  routes_source_browser --> routes_core
149
- routes_source_browser --> components_preview_panel
149
+ routes_source_browser --> html_ids
150
150
  routes_source_browser --> models
151
151
  routes_source_browser --> components_source_browser
152
- routes_source_browser --> services_source_utils
152
+ routes_source_browser --> components_preview_panel
153
153
  routes_source_browser --> services_source
154
- routes_source_browser --> html_ids
154
+ routes_source_browser --> services_source_utils
155
155
  routes_tabs --> routes_core
156
156
  routes_tabs --> services_source_utils
157
157
  routes_tabs --> models
@@ -426,7 +426,7 @@ def init_selection_routers(
426
426
  source_service: SourceService, # The source service for queries
427
427
  workflow_id: str, # The workflow identifier
428
428
  prefix: str, # Base prefix for selection routes (e.g., "/workflow/selection")
429
- ) -> Tuple[List[APIRouter], SelectionUrls, Dict[str, Callable], Callable, "SourceBrowserRouterState"]
429
+ ) -> SelectionResult: # Selection router result with routers, urls, routes, and restore
430
430
  "Initialize and return all selection routers with URL bundle."
431
431
  ```
432
432
 
@@ -528,8 +528,9 @@ def _handle_remove_external_source(
528
528
  external_db_paths_ref: List[str], # Shared external paths list (mutated in place)
529
529
  fb_routers: FileBrowserRouters, # File browser routers (for targeted OOB)
530
530
  remove_url: str, # URL for remove button in external sources list
531
- ): # Tuple of OOB elements (external sources list + checkbox cells)
532
- "Remove an external database source from the Added Sources list."
531
+ urls: SelectionUrls, # Full URL bundle for queue re-rendering
532
+ ): # Tuple of OOB elements (external sources list + checkbox cells + queue + stats)
533
+ "Remove an external database source and clean up orphaned queue items."
533
534
  ```
534
535
 
535
536
  ``` python
@@ -539,7 +540,7 @@ def init_local_files_router(
539
540
  source_service: SourceService, # The source service for external db ops
540
541
  prefix: str, # Route prefix (e.g., "/workflow/selection/local_files")
541
542
  urls: SelectionUrls, # URL bundle for rendering
542
- ) -> Tuple[List, Dict[str, Callable], Callable]: # (routers, route_dict, render_panel_fn)
543
+ ) -> LocalFilesResult: # Router result with routers, routes, render, restore, and reset
543
544
  "Initialize local files browser routes with new file browser API."
544
545
  ```
545
546
 
@@ -558,10 +559,33 @@ _local_files_provider: Optional[LocalFileSystemProvider] = None
558
559
  ``` python
559
560
  from cjm_transcript_source_select.models import (
560
561
  SelectionStepState,
561
- SelectionUrls
562
+ SelectionUrls,
563
+ LocalFilesResult,
564
+ SelectionResult
562
565
  )
563
566
  ```
564
567
 
568
+ #### Functions
569
+
570
+ ``` python
571
+ def _no_op_restore(session_id: str) -> None:
572
+ """Default no-op for restore_state."""
573
+ pass
574
+
575
+ def _no_op_reset() -> None
576
+ "Default no-op for restore_state."
577
+ ```
578
+
579
+ ``` python
580
+ def _no_op_reset() -> None:
581
+ """Default no-op for reset_state."""
582
+ pass
583
+
584
+ @dataclass
585
+ class LocalFilesResult
586
+ "Default no-op for reset_state."
587
+ ```
588
+
565
589
  #### Classes
566
590
 
567
591
  ``` python
@@ -591,6 +615,32 @@ class SelectionUrls:
591
615
  tab_switch: str = '' # Switch source tabs
592
616
  ```
593
617
 
618
+ ``` python
619
+ @dataclass
620
+ class LocalFilesResult:
621
+ "Return type from init_local_files_router."
622
+
623
+ routers: List[APIRouter] # Routers to register (custom + file browser + VC)
624
+ routes: Dict[str, Callable] # Named route handlers
625
+ render_panel: Callable # (error_message?, session_id?) -> rendered panel
626
+ restore_state: Callable = field(...) # (session_id) -> None, restore persisted state
627
+ reset_state: Callable = field(...) # () -> None, reset in-memory caches
628
+ ```
629
+
630
+ ``` python
631
+ @dataclass
632
+ class SelectionResult:
633
+ "Return type from init_selection_routers."
634
+
635
+ routers: List[APIRouter] # All selection routers to register
636
+ urls: 'SelectionUrls' = field(...) # URL bundle
637
+ routes: Dict[str, Callable] = field(...) # All named route handlers
638
+ render_local_files_panel: Optional[Callable] # Render fn for local files tab
639
+ sb_state: Any # SourceBrowserRouterState
640
+ restore_state: Callable = field(...) # (session_id) -> None, restore persisted state
641
+ reset_state: Callable = field(...) # () -> None, reset in-memory caches
642
+ ```
643
+
594
644
  ### preview_panel (`preview_panel.ipynb`)
595
645
 
596
646
  > Collapsible preview panel for displaying selected content
@@ -1434,15 +1484,16 @@ from cjm_transcript_source_select.routes.tabs import (
1434
1484
 
1435
1485
  ``` python
1436
1486
  def _handle_tab_switch(
1437
- state_store: WorkflowStateStore, # The workflow state store
1438
- workflow_id: str, # The workflow identifier
1439
1487
  source_service: SourceService, # The source service for queries
1440
1488
  request, # FastHTML request object
1441
1489
  sess, # FastHTML session object
1442
1490
  direction: str, # Direction: "prev", "next", "db", or "files"
1443
1491
  urls: SelectionUrls, # URL bundle for rendering
1492
+ current_tab_ref: List[str], # Mutable ref [current_tab] for closure-based tracking
1444
1493
  render_local_files_panel: Optional[Callable] = None, # Render fn for Files tab
1445
1494
  sb_state: Any = None, # SourceBrowserRouterState for DB tab VC rendering
1495
+ state_store: WorkflowStateStore = None, # State store (for reading step state)
1496
+ workflow_id: str = "", # Workflow ID (for reading step state)
1446
1497
  ): # Tuple of inner content, OOB tab headers, and tab switch script
1447
1498
  "Switch between Plugin DB and Local Files tabs."
1448
1499
  ```
@@ -1,7 +1,7 @@
1
1
  [DEFAULT]
2
2
  repo = cjm-transcript-source-select
3
3
  lib_name = cjm-transcript-source-select
4
- version = 0.0.14
4
+ version = 0.0.16
5
5
  min_python = 3.12
6
6
  license = apache2
7
7
  black_formatting = False
@@ -1 +0,0 @@
1
- __version__ = "0.0.14"