cjm-transcript-source-select 0.0.13__tar.gz → 0.0.15__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.13/cjm_transcript_source_select.egg-info → cjm_transcript_source_select-0.0.15}/PKG-INFO +64 -27
  2. {cjm_transcript_source_select-0.0.13 → cjm_transcript_source_select-0.0.15}/README.md +63 -26
  3. cjm_transcript_source_select-0.0.15/cjm_transcript_source_select/__init__.py +1 -0
  4. {cjm_transcript_source_select-0.0.13 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/_modidx.py +8 -2
  5. {cjm_transcript_source_select-0.0.13 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/components/local_files.py +4 -5
  6. {cjm_transcript_source_select-0.0.13 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/models.py +28 -3
  7. {cjm_transcript_source_select-0.0.13 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/routes/init.py +15 -8
  8. {cjm_transcript_source_select-0.0.13 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/routes/local_files.py +75 -18
  9. {cjm_transcript_source_select-0.0.13 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/routes/tabs.py +16 -13
  10. {cjm_transcript_source_select-0.0.13 → cjm_transcript_source_select-0.0.15/cjm_transcript_source_select.egg-info}/PKG-INFO +64 -27
  11. {cjm_transcript_source_select-0.0.13 → cjm_transcript_source_select-0.0.15}/settings.ini +1 -1
  12. cjm_transcript_source_select-0.0.13/cjm_transcript_source_select/__init__.py +0 -1
  13. {cjm_transcript_source_select-0.0.13 → cjm_transcript_source_select-0.0.15}/LICENSE +0 -0
  14. {cjm_transcript_source_select-0.0.13 → cjm_transcript_source_select-0.0.15}/MANIFEST.in +0 -0
  15. {cjm_transcript_source_select-0.0.13 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/components/__init__.py +0 -0
  16. {cjm_transcript_source_select-0.0.13 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/components/helpers.py +0 -0
  17. {cjm_transcript_source_select-0.0.13 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/components/preview_panel.py +0 -0
  18. {cjm_transcript_source_select-0.0.13 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/components/selection_queue.py +0 -0
  19. {cjm_transcript_source_select-0.0.13 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/components/source_browser.py +0 -0
  20. {cjm_transcript_source_select-0.0.13 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/components/step_renderer.py +0 -0
  21. {cjm_transcript_source_select-0.0.13 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/html_ids.py +0 -0
  22. {cjm_transcript_source_select-0.0.13 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/routes/__init__.py +0 -0
  23. {cjm_transcript_source_select-0.0.13 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/routes/core.py +0 -0
  24. {cjm_transcript_source_select-0.0.13 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/routes/filtering.py +0 -0
  25. {cjm_transcript_source_select-0.0.13 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/routes/queue.py +0 -0
  26. {cjm_transcript_source_select-0.0.13 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/routes/source_browser.py +0 -0
  27. {cjm_transcript_source_select-0.0.13 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/services/__init__.py +0 -0
  28. {cjm_transcript_source_select-0.0.13 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/services/source.py +0 -0
  29. {cjm_transcript_source_select-0.0.13 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/services/source_utils.py +0 -0
  30. {cjm_transcript_source_select-0.0.13 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/utils.py +0 -0
  31. {cjm_transcript_source_select-0.0.13 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select.egg-info/SOURCES.txt +0 -0
  32. {cjm_transcript_source_select-0.0.13 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select.egg-info/dependency_links.txt +0 -0
  33. {cjm_transcript_source_select-0.0.13 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select.egg-info/entry_points.txt +0 -0
  34. {cjm_transcript_source_select-0.0.13 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select.egg-info/not-zip-safe +0 -0
  35. {cjm_transcript_source_select-0.0.13 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select.egg-info/requires.txt +0 -0
  36. {cjm_transcript_source_select-0.0.13 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select.egg-info/top_level.txt +0 -0
  37. {cjm_transcript_source_select-0.0.13 → cjm_transcript_source_select-0.0.15}/pyproject.toml +0 -0
  38. {cjm_transcript_source_select-0.0.13 → cjm_transcript_source_select-0.0.15}/setup.cfg +0 -0
  39. {cjm_transcript_source_select-0.0.13 → cjm_transcript_source_select-0.0.15}/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.13
3
+ Version: 0.0.15
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,57 +106,57 @@ 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
115
116
  components_step_renderer --> components_selection_queue
116
- components_step_renderer --> components_source_browser
117
117
  components_step_renderer --> utils
118
118
  components_step_renderer --> components_local_files
119
119
  components_step_renderer --> html_ids
120
- components_step_renderer --> components_preview_panel
121
120
  components_step_renderer --> models
122
- routes_core --> components_selection_queue
123
- routes_core --> services_source
124
- routes_core --> html_ids
121
+ components_step_renderer --> components_source_browser
125
122
  routes_core --> models
123
+ routes_core --> services_source
124
+ routes_core --> components_selection_queue
126
125
  routes_core --> components_step_renderer
126
+ routes_core --> html_ids
127
+ routes_filtering --> routes_core
127
128
  routes_filtering --> services_source_utils
128
129
  routes_filtering --> services_source
129
- routes_filtering --> routes_core
130
130
  routes_filtering --> models
131
131
  routes_init --> routes_core
132
+ routes_init --> routes_queue
133
+ routes_init --> routes_tabs
134
+ routes_init --> routes_filtering
132
135
  routes_init --> services_source
133
136
  routes_init --> routes_source_browser
134
- routes_init --> routes_filtering
135
137
  routes_init --> routes_local_files
136
- routes_init --> routes_queue
137
138
  routes_init --> models
138
- routes_init --> routes_tabs
139
+ routes_local_files --> models
140
+ routes_local_files --> routes_core
139
141
  routes_local_files --> components_local_files
140
142
  routes_local_files --> services_source
141
- routes_local_files --> routes_core
142
- routes_local_files --> models
143
- routes_queue --> services_source_utils
144
- routes_queue --> services_source
145
143
  routes_queue --> routes_core
144
+ routes_queue --> services_source_utils
146
145
  routes_queue --> components_preview_panel
146
+ routes_queue --> services_source
147
147
  routes_queue --> models
148
- routes_source_browser --> routes_core
149
- routes_source_browser --> services_source_utils
150
148
  routes_source_browser --> components_source_browser
149
+ routes_source_browser --> components_preview_panel
150
+ routes_source_browser --> services_source_utils
151
151
  routes_source_browser --> services_source
152
+ routes_source_browser --> routes_core
152
153
  routes_source_browser --> html_ids
153
- routes_source_browser --> components_preview_panel
154
154
  routes_source_browser --> models
155
+ routes_tabs --> models
155
156
  routes_tabs --> components_step_renderer
157
+ routes_tabs --> services_source
156
158
  routes_tabs --> routes_core
157
159
  routes_tabs --> services_source_utils
158
- routes_tabs --> services_source
159
- routes_tabs --> models
160
160
  ```
161
161
 
162
162
  *52 cross-module dependencies detected*
@@ -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
 
@@ -526,8 +526,6 @@ def _handle_remove_external_source(
526
526
  sess, # FastHTML session object
527
527
  db_path: str, # Path to the .db file to remove
528
528
  external_db_paths_ref: List[str], # Shared external paths list (mutated in place)
529
- fb_state_getter: Callable[[], BrowserState], # File browser state getter
530
- fb_state_setter: Callable[[BrowserState], None], # File browser state setter
531
529
  fb_routers: FileBrowserRouters, # File browser routers (for targeted OOB)
532
530
  remove_url: str, # URL for remove button in external sources list
533
531
  ): # Tuple of OOB elements (external sources list + checkbox cells)
@@ -541,7 +539,7 @@ def init_local_files_router(
541
539
  source_service: SourceService, # The source service for external db ops
542
540
  prefix: str, # Route prefix (e.g., "/workflow/selection/local_files")
543
541
  urls: SelectionUrls, # URL bundle for rendering
544
- ) -> Tuple[List, Dict[str, Callable], Callable]: # (routers, route_dict, render_panel_fn)
542
+ ) -> LocalFilesResult: # Router result with routers, routes, render, and restore
545
543
  "Initialize local files browser routes with new file browser API."
546
544
  ```
547
545
 
@@ -560,10 +558,24 @@ _local_files_provider: Optional[LocalFileSystemProvider] = None
560
558
  ``` python
561
559
  from cjm_transcript_source_select.models import (
562
560
  SelectionStepState,
563
- SelectionUrls
561
+ SelectionUrls,
562
+ LocalFilesResult,
563
+ SelectionResult
564
564
  )
565
565
  ```
566
566
 
567
+ #### Functions
568
+
569
+ ``` python
570
+ def _no_op_restore(session_id: str) -> None:
571
+ """Default no-op for restore_state."""
572
+ pass
573
+
574
+ @dataclass
575
+ class LocalFilesResult
576
+ "Default no-op for restore_state."
577
+ ```
578
+
567
579
  #### Classes
568
580
 
569
581
  ``` python
@@ -593,6 +605,30 @@ class SelectionUrls:
593
605
  tab_switch: str = '' # Switch source tabs
594
606
  ```
595
607
 
608
+ ``` python
609
+ @dataclass
610
+ class LocalFilesResult:
611
+ "Return type from init_local_files_router."
612
+
613
+ routers: List[APIRouter] # Routers to register (custom + file browser + VC)
614
+ routes: Dict[str, Callable] # Named route handlers
615
+ render_panel: Callable # (error_message?, session_id?) -> rendered panel
616
+ restore_state: Callable = field(...) # (session_id) -> None, restore persisted state
617
+ ```
618
+
619
+ ``` python
620
+ @dataclass
621
+ class SelectionResult:
622
+ "Return type from init_selection_routers."
623
+
624
+ routers: List[APIRouter] # All selection routers to register
625
+ urls: 'SelectionUrls' = field(...) # URL bundle
626
+ routes: Dict[str, Callable] = field(...) # All named route handlers
627
+ render_local_files_panel: Optional[Callable] # Render fn for local files tab
628
+ sb_state: Any # SourceBrowserRouterState
629
+ restore_state: Callable = field(...) # (session_id) -> None, restore persisted state
630
+ ```
631
+
596
632
  ### preview_panel (`preview_panel.ipynb`)
597
633
 
598
634
  > Collapsible preview panel for displaying selected content
@@ -1436,15 +1472,16 @@ from cjm_transcript_source_select.routes.tabs import (
1436
1472
 
1437
1473
  ``` python
1438
1474
  def _handle_tab_switch(
1439
- state_store: WorkflowStateStore, # The workflow state store
1440
- workflow_id: str, # The workflow identifier
1441
1475
  source_service: SourceService, # The source service for queries
1442
1476
  request, # FastHTML request object
1443
1477
  sess, # FastHTML session object
1444
1478
  direction: str, # Direction: "prev", "next", "db", or "files"
1445
1479
  urls: SelectionUrls, # URL bundle for rendering
1480
+ current_tab_ref: List[str], # Mutable ref [current_tab] for closure-based tracking
1446
1481
  render_local_files_panel: Optional[Callable] = None, # Render fn for Files tab
1447
1482
  sb_state: Any = None, # SourceBrowserRouterState for DB tab VC rendering
1483
+ state_store: WorkflowStateStore = None, # State store (for reading step state)
1484
+ workflow_id: str = "", # Workflow ID (for reading step state)
1448
1485
  ): # Tuple of inner content, OOB tab headers, and tab switch script
1449
1486
  "Switch between Plugin DB and Local Files tabs."
1450
1487
  ```
@@ -60,57 +60,57 @@ 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
69
70
  components_step_renderer --> components_selection_queue
70
- components_step_renderer --> components_source_browser
71
71
  components_step_renderer --> utils
72
72
  components_step_renderer --> components_local_files
73
73
  components_step_renderer --> html_ids
74
- components_step_renderer --> components_preview_panel
75
74
  components_step_renderer --> models
76
- routes_core --> components_selection_queue
77
- routes_core --> services_source
78
- routes_core --> html_ids
75
+ components_step_renderer --> components_source_browser
79
76
  routes_core --> models
77
+ routes_core --> services_source
78
+ routes_core --> components_selection_queue
80
79
  routes_core --> components_step_renderer
80
+ routes_core --> html_ids
81
+ routes_filtering --> routes_core
81
82
  routes_filtering --> services_source_utils
82
83
  routes_filtering --> services_source
83
- routes_filtering --> routes_core
84
84
  routes_filtering --> models
85
85
  routes_init --> routes_core
86
+ routes_init --> routes_queue
87
+ routes_init --> routes_tabs
88
+ routes_init --> routes_filtering
86
89
  routes_init --> services_source
87
90
  routes_init --> routes_source_browser
88
- routes_init --> routes_filtering
89
91
  routes_init --> routes_local_files
90
- routes_init --> routes_queue
91
92
  routes_init --> models
92
- routes_init --> routes_tabs
93
+ routes_local_files --> models
94
+ routes_local_files --> routes_core
93
95
  routes_local_files --> components_local_files
94
96
  routes_local_files --> services_source
95
- routes_local_files --> routes_core
96
- routes_local_files --> models
97
- routes_queue --> services_source_utils
98
- routes_queue --> services_source
99
97
  routes_queue --> routes_core
98
+ routes_queue --> services_source_utils
100
99
  routes_queue --> components_preview_panel
100
+ routes_queue --> services_source
101
101
  routes_queue --> models
102
- routes_source_browser --> routes_core
103
- routes_source_browser --> services_source_utils
104
102
  routes_source_browser --> components_source_browser
103
+ routes_source_browser --> components_preview_panel
104
+ routes_source_browser --> services_source_utils
105
105
  routes_source_browser --> services_source
106
+ routes_source_browser --> routes_core
106
107
  routes_source_browser --> html_ids
107
- routes_source_browser --> components_preview_panel
108
108
  routes_source_browser --> models
109
+ routes_tabs --> models
109
110
  routes_tabs --> components_step_renderer
111
+ routes_tabs --> services_source
110
112
  routes_tabs --> routes_core
111
113
  routes_tabs --> services_source_utils
112
- routes_tabs --> services_source
113
- routes_tabs --> models
114
114
  ```
115
115
 
116
116
  *52 cross-module dependencies detected*
@@ -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
 
@@ -480,8 +480,6 @@ def _handle_remove_external_source(
480
480
  sess, # FastHTML session object
481
481
  db_path: str, # Path to the .db file to remove
482
482
  external_db_paths_ref: List[str], # Shared external paths list (mutated in place)
483
- fb_state_getter: Callable[[], BrowserState], # File browser state getter
484
- fb_state_setter: Callable[[BrowserState], None], # File browser state setter
485
483
  fb_routers: FileBrowserRouters, # File browser routers (for targeted OOB)
486
484
  remove_url: str, # URL for remove button in external sources list
487
485
  ): # Tuple of OOB elements (external sources list + checkbox cells)
@@ -495,7 +493,7 @@ def init_local_files_router(
495
493
  source_service: SourceService, # The source service for external db ops
496
494
  prefix: str, # Route prefix (e.g., "/workflow/selection/local_files")
497
495
  urls: SelectionUrls, # URL bundle for rendering
498
- ) -> Tuple[List, Dict[str, Callable], Callable]: # (routers, route_dict, render_panel_fn)
496
+ ) -> LocalFilesResult: # Router result with routers, routes, render, and restore
499
497
  "Initialize local files browser routes with new file browser API."
500
498
  ```
501
499
 
@@ -514,10 +512,24 @@ _local_files_provider: Optional[LocalFileSystemProvider] = None
514
512
  ``` python
515
513
  from cjm_transcript_source_select.models import (
516
514
  SelectionStepState,
517
- SelectionUrls
515
+ SelectionUrls,
516
+ LocalFilesResult,
517
+ SelectionResult
518
518
  )
519
519
  ```
520
520
 
521
+ #### Functions
522
+
523
+ ``` python
524
+ def _no_op_restore(session_id: str) -> None:
525
+ """Default no-op for restore_state."""
526
+ pass
527
+
528
+ @dataclass
529
+ class LocalFilesResult
530
+ "Default no-op for restore_state."
531
+ ```
532
+
521
533
  #### Classes
522
534
 
523
535
  ``` python
@@ -547,6 +559,30 @@ class SelectionUrls:
547
559
  tab_switch: str = '' # Switch source tabs
548
560
  ```
549
561
 
562
+ ``` python
563
+ @dataclass
564
+ class LocalFilesResult:
565
+ "Return type from init_local_files_router."
566
+
567
+ routers: List[APIRouter] # Routers to register (custom + file browser + VC)
568
+ routes: Dict[str, Callable] # Named route handlers
569
+ render_panel: Callable # (error_message?, session_id?) -> rendered panel
570
+ restore_state: Callable = field(...) # (session_id) -> None, restore persisted state
571
+ ```
572
+
573
+ ``` python
574
+ @dataclass
575
+ class SelectionResult:
576
+ "Return type from init_selection_routers."
577
+
578
+ routers: List[APIRouter] # All selection routers to register
579
+ urls: 'SelectionUrls' = field(...) # URL bundle
580
+ routes: Dict[str, Callable] = field(...) # All named route handlers
581
+ render_local_files_panel: Optional[Callable] # Render fn for local files tab
582
+ sb_state: Any # SourceBrowserRouterState
583
+ restore_state: Callable = field(...) # (session_id) -> None, restore persisted state
584
+ ```
585
+
550
586
  ### preview_panel (`preview_panel.ipynb`)
551
587
 
552
588
  > Collapsible preview panel for displaying selected content
@@ -1390,15 +1426,16 @@ from cjm_transcript_source_select.routes.tabs import (
1390
1426
 
1391
1427
  ``` python
1392
1428
  def _handle_tab_switch(
1393
- state_store: WorkflowStateStore, # The workflow state store
1394
- workflow_id: str, # The workflow identifier
1395
1429
  source_service: SourceService, # The source service for queries
1396
1430
  request, # FastHTML request object
1397
1431
  sess, # FastHTML session object
1398
1432
  direction: str, # Direction: "prev", "next", "db", or "files"
1399
1433
  urls: SelectionUrls, # URL bundle for rendering
1434
+ current_tab_ref: List[str], # Mutable ref [current_tab] for closure-based tracking
1400
1435
  render_local_files_panel: Optional[Callable] = None, # Render fn for Files tab
1401
1436
  sb_state: Any = None, # SourceBrowserRouterState for DB tab VC rendering
1437
+ state_store: WorkflowStateStore = None, # State store (for reading step state)
1438
+ workflow_id: str = "", # Workflow ID (for reading step state)
1402
1439
  ): # Tuple of inner content, OOB tab headers, and tab switch script
1403
1440
  "Switch between Plugin DB and Local Files tabs."
1404
1441
  ```
@@ -0,0 +1 @@
1
+ __version__ = "0.0.15"
@@ -81,10 +81,16 @@ 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_restore': ( 'models.html#_no_op_restore',
93
+ 'cjm_transcript_source_select/models.py')},
88
94
  'cjm_transcript_source_select.routes.core': { 'cjm_transcript_source_select.routes.core._build_queue_response': ( 'routes/core.html#_build_queue_response',
89
95
  'cjm_transcript_source_select/routes/core.py'),
90
96
  'cjm_transcript_source_select.routes.core._find_duplicate_media_source': ( 'routes/core.html#_find_duplicate_media_source',
@@ -16,6 +16,7 @@ from cjm_fasthtml_interactions.core.context import InteractionContext
16
16
  # DaisyUI components
17
17
  from cjm_fasthtml_daisyui.components.actions.button import btn, btn_sizes, btn_styles
18
18
  from cjm_fasthtml_daisyui.components.data_display.badge import badge, badge_colors, badge_sizes
19
+ from cjm_fasthtml_daisyui.components.data_display.list import list_ui, list_row
19
20
  from cjm_fasthtml_daisyui.components.feedback.alert import alert, alert_colors
20
21
  from cjm_fasthtml_daisyui.utilities.semantic_colors import bg_dui, text_dui, border_dui
21
22
  from cjm_fasthtml_daisyui.utilities.border_radius import border_radius
@@ -123,15 +124,13 @@ def _render_external_sources_list(
123
124
  cls=combine_classes(btn, btn_styles.ghost, btn_sizes.xs, shrink._0),
124
125
  hx_post=remove_url,
125
126
  hx_vals=json.dumps({"db_path": db_path}),
126
- # Use SOURCE_LIST ID for consistent zone focus ring
127
- hx_target=SelectionHtmlIds.as_selector(SelectionHtmlIds.SOURCE_LIST),
128
- hx_swap="outerHTML",
127
+ hx_swap="none",
129
128
  title=f"Remove {path.name}"
130
129
  ),
131
130
  cls=combine_classes(flex_display, items.center, gap(2), p.y(1), p.x(2))
132
131
  ),
133
132
  title=db_path,
134
- cls=combine_classes(border_dui.base_300, border.b(), bg_dui.primary.opacity(10))
133
+ cls=combine_classes(list_row, border_dui.base_300, border.b(), bg_dui.primary.opacity(10))
135
134
  ))
136
135
 
137
136
  return Div(
@@ -149,7 +148,7 @@ def _render_external_sources_list(
149
148
  Ul(
150
149
  *list_items,
151
150
  id=SelectionHtmlIds.EXTERNAL_SOURCES_LIST,
152
- cls=combine_classes(border_radius.box, list_style.none, m(0), p(0), overflow.y.auto, max_h(32))
151
+ cls=combine_classes(list_ui, m(0), p(0), overflow.y.auto, max_h(32))
153
152
  ),
154
153
  id=SelectionHtmlIds.EXTERNAL_SOURCES_SECTION,
155
154
  cls=combine_classes(m.t(2), shrink._0),
@@ -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,26 @@ 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
+ @dataclass
63
+ class LocalFilesResult:
64
+ """Return type from init_local_files_router."""
65
+ routers: List[APIRouter] # Routers to register (custom + file browser + VC)
66
+ routes: Dict[str, Callable] # Named route handlers
67
+ render_panel: Callable # (error_message?, session_id?) -> rendered panel
68
+ restore_state: Callable = field(default=_no_op_restore) # (session_id) -> None, restore persisted state
69
+
70
+ @dataclass
71
+ class SelectionResult:
72
+ """Return type from init_selection_routers."""
73
+ routers: List[APIRouter] # All selection routers to register
74
+ urls: "SelectionUrls" = field(default_factory=lambda: SelectionUrls()) # URL bundle
75
+ routes: Dict[str, Callable] = field(default_factory=dict) # All named route handlers
76
+ render_local_files_panel: Optional[Callable] = None # Render fn for local files tab
77
+ sb_state: Any = None # SourceBrowserRouterState
78
+ restore_state: Callable = field(default=_no_op_restore) # (session_id) -> None, restore persisted state
@@ -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,23 @@ 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
+ )
@@ -17,7 +17,7 @@ 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
22
  WorkflowStateStore, _get_step_state, _update_step_state
23
23
  )
@@ -52,8 +52,6 @@ def _handle_remove_external_source(
52
52
  sess, # FastHTML session object
53
53
  db_path: str, # Path to the .db file to remove
54
54
  external_db_paths_ref: List[str], # Shared external paths list (mutated in place)
55
- fb_state_getter: Callable[[], BrowserState], # File browser state getter
56
- fb_state_setter: Callable[[BrowserState], None], # File browser state setter
57
55
  fb_routers: FileBrowserRouters, # File browser routers (for targeted OOB)
58
56
  remove_url: str, # URL for remove button in external sources list
59
57
  ): # Tuple of OOB elements (external sources list + checkbox cells)
@@ -66,13 +64,8 @@ def _handle_remove_external_source(
66
64
  _update_step_state(state_store, workflow_id, session_id, external_db_paths=list(external_db_paths_ref))
67
65
  source_service.set_external_paths(list(external_db_paths_ref))
68
66
 
69
- # Sync browser selection state with external_db_paths for checkbox display
70
- browser_state = fb_state_getter()
71
- browser_state.selection.selected_paths = list(external_db_paths_ref)
72
- fb_state_setter(browser_state)
73
-
74
- # Targeted OOB updates instead of full panel re-render
75
- checkbox_oobs = fb_routers.render_selection_oobs([db_path])
67
+ # Sync selection + get targeted OOBs in one call
68
+ checkbox_oobs = fb_routers.update_selection_oobs(list(external_db_paths_ref), [db_path])
76
69
  external_list_oob = _render_external_sources_list(
77
70
  list(external_db_paths_ref), remove_url, oob=True,
78
71
  )
@@ -85,7 +78,7 @@ def init_local_files_router(
85
78
  source_service: SourceService, # The source service for external db ops
86
79
  prefix: str, # Route prefix (e.g., "/workflow/selection/local_files")
87
80
  urls: SelectionUrls, # URL bundle for rendering
88
- ) -> Tuple[List, Dict[str, Callable], Callable]: # (routers, route_dict, render_panel_fn)
81
+ ) -> LocalFilesResult: # Router result with routers, routes, render, and restore
89
82
  """Initialize local files browser routes with new file browser API."""
90
83
  provider = _get_local_files_provider()
91
84
  config = _create_db_browser_config()
@@ -97,18 +90,57 @@ def init_local_files_router(
97
90
  # --- File browser state accessors (shared, non-per-session) ---
98
91
  _browser_state = BrowserState(current_path=home_path)
99
92
 
93
+ # --- Lazy state restoration from persisted store ---
94
+ _state_restored = [False]
95
+
96
+ def _restore_from_persisted(session_id: str) -> None:
97
+ """Load persisted external_db_paths and file_browser_state on first call."""
98
+ if _state_restored[0]:
99
+ return
100
+ step_state = _get_step_state(state_store, workflow_id, session_id)
101
+
102
+ # Restore external_db_paths
103
+ persisted_paths = step_state.get("external_db_paths", [])
104
+ if persisted_paths:
105
+ _external_db_paths[:] = persisted_paths
106
+ source_service.set_external_paths(list(_external_db_paths))
107
+
108
+ # Restore browser state (current_path, sort)
109
+ persisted_browser = step_state.get("file_browser_state", {})
110
+ if persisted_browser:
111
+ restored = BrowserState.from_dict(persisted_browser)
112
+ _browser_state.current_path = restored.current_path
113
+ _browser_state.sort_by = restored.sort_by
114
+ _browser_state.sort_descending = restored.sort_descending
115
+ # Selection synced from _external_db_paths via _fb_state_getter
116
+
117
+ _state_restored[0] = True
118
+
100
119
  def _fb_state_getter() -> BrowserState:
101
120
  """Get browser state with selection synced to external_db_paths."""
102
121
  _browser_state.selection.selected_paths = list(_external_db_paths)
103
122
  return _browser_state
104
123
 
105
- def _fb_state_setter(state: BrowserState) -> None:
106
- """Save browser state from file browser."""
124
+ def _fb_state_setter(state: BrowserState, request=None) -> None:
125
+ """Save browser state from file browser and persist to state store."""
126
+ # Lazy restore on first request
127
+ if request is not None and not _state_restored[0]:
128
+ _restore_from_persisted(get_session_id(request.session))
129
+
107
130
  _browser_state.current_path = state.current_path
108
131
  _browser_state.sort_by = state.sort_by
109
132
  _browser_state.sort_descending = state.sort_descending
110
133
  _browser_state.selection = state.selection
111
134
 
135
+ # Persist browser state
136
+ if request is not None:
137
+ session_id = get_session_id(request.session)
138
+ _update_step_state(
139
+ state_store, workflow_id, session_id,
140
+ file_browser_state=state.to_dict(),
141
+ current_browse_path=state.current_path,
142
+ )
143
+
112
144
  # --- Callbacks for file browser ---
113
145
  def _validate_selection(path: str) -> Tuple[bool, str]:
114
146
  """Validate .db schema before allowing selection (checkbox click path)."""
@@ -118,8 +150,12 @@ def init_local_files_router(
118
150
  return (False, error)
119
151
  return (True, "")
120
152
 
121
- def _on_selection_change(selected_paths: List[str]) -> Tuple:
153
+ def _on_selection_change(selected_paths: List[str], request=None) -> Tuple:
122
154
  """Sync external_db_paths with validation and return OOB updates."""
155
+ # Lazy restore on first request
156
+ if request is not None and not _state_restored[0]:
157
+ _restore_from_persisted(get_session_id(request.session))
158
+
123
159
  # Validate any newly added paths
124
160
  old_set = set(_external_db_paths)
125
161
  validated_paths = []
@@ -141,6 +177,14 @@ def init_local_files_router(
141
177
  _external_db_paths[:] = validated_paths
142
178
  source_service.set_external_paths(list(_external_db_paths))
143
179
 
180
+ # Persist external_db_paths
181
+ if request is not None:
182
+ session_id = get_session_id(request.session)
183
+ _update_step_state(
184
+ state_store, workflow_id, session_id,
185
+ external_db_paths=list(_external_db_paths),
186
+ )
187
+
144
188
  return (
145
189
  _render_external_sources_list(list(_external_db_paths), urls.remove_external, oob=True),
146
190
  _render_error_alert(error_message, oob=True),
@@ -163,8 +207,12 @@ def init_local_files_router(
163
207
  )
164
208
 
165
209
  # --- Render function for the full local files panel ---
166
- def _render_panel(error_message: Optional[str] = None) -> Any:
210
+ def _render_panel(error_message: Optional[str] = None, session_id: Optional[str] = None) -> Any:
167
211
  """Render the complete local files panel (file browser + external sources list)."""
212
+ # Restore persisted state and rebuild items before rendering
213
+ if session_id is not None and not _state_restored[0]:
214
+ _restore_from_persisted(session_id)
215
+ fb_routers.sync_items()
168
216
  return _render_local_files_browser(
169
217
  render_fn=fb_routers.render,
170
218
  external_paths=list(_external_db_paths),
@@ -172,6 +220,12 @@ def init_local_files_router(
172
220
  error_message=error_message,
173
221
  )
174
222
 
223
+ # --- Restore callable for eager state restoration ---
224
+ def _restore_state(session_id: str) -> None:
225
+ """Restore persisted local files state (external_db_paths + browser state)."""
226
+ _restore_from_persisted(session_id)
227
+ fb_routers.sync_items()
228
+
175
229
  # --- Custom router for remove_external (needs session context) ---
176
230
  custom_router = APIRouter(prefix=prefix)
177
231
 
@@ -182,8 +236,6 @@ def init_local_files_router(
182
236
  state_store, workflow_id, source_service,
183
237
  sess, db_path,
184
238
  external_db_paths_ref=_external_db_paths,
185
- fb_state_getter=_fb_state_getter,
186
- fb_state_setter=_fb_state_setter,
187
239
  fb_routers=fb_routers,
188
240
  remove_url=urls.remove_external,
189
241
  )
@@ -195,4 +247,9 @@ def init_local_files_router(
195
247
  "remove_external": remove_external,
196
248
  }
197
249
 
198
- return all_routers, routes, _render_panel
250
+ return LocalFilesResult(
251
+ routers=all_routers,
252
+ routes=routes,
253
+ render_panel=_render_panel,
254
+ restore_state=_restore_state,
255
+ )
@@ -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.13
3
+ Version: 0.0.15
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,57 +106,57 @@ 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
115
116
  components_step_renderer --> components_selection_queue
116
- components_step_renderer --> components_source_browser
117
117
  components_step_renderer --> utils
118
118
  components_step_renderer --> components_local_files
119
119
  components_step_renderer --> html_ids
120
- components_step_renderer --> components_preview_panel
121
120
  components_step_renderer --> models
122
- routes_core --> components_selection_queue
123
- routes_core --> services_source
124
- routes_core --> html_ids
121
+ components_step_renderer --> components_source_browser
125
122
  routes_core --> models
123
+ routes_core --> services_source
124
+ routes_core --> components_selection_queue
126
125
  routes_core --> components_step_renderer
126
+ routes_core --> html_ids
127
+ routes_filtering --> routes_core
127
128
  routes_filtering --> services_source_utils
128
129
  routes_filtering --> services_source
129
- routes_filtering --> routes_core
130
130
  routes_filtering --> models
131
131
  routes_init --> routes_core
132
+ routes_init --> routes_queue
133
+ routes_init --> routes_tabs
134
+ routes_init --> routes_filtering
132
135
  routes_init --> services_source
133
136
  routes_init --> routes_source_browser
134
- routes_init --> routes_filtering
135
137
  routes_init --> routes_local_files
136
- routes_init --> routes_queue
137
138
  routes_init --> models
138
- routes_init --> routes_tabs
139
+ routes_local_files --> models
140
+ routes_local_files --> routes_core
139
141
  routes_local_files --> components_local_files
140
142
  routes_local_files --> services_source
141
- routes_local_files --> routes_core
142
- routes_local_files --> models
143
- routes_queue --> services_source_utils
144
- routes_queue --> services_source
145
143
  routes_queue --> routes_core
144
+ routes_queue --> services_source_utils
146
145
  routes_queue --> components_preview_panel
146
+ routes_queue --> services_source
147
147
  routes_queue --> models
148
- routes_source_browser --> routes_core
149
- routes_source_browser --> services_source_utils
150
148
  routes_source_browser --> components_source_browser
149
+ routes_source_browser --> components_preview_panel
150
+ routes_source_browser --> services_source_utils
151
151
  routes_source_browser --> services_source
152
+ routes_source_browser --> routes_core
152
153
  routes_source_browser --> html_ids
153
- routes_source_browser --> components_preview_panel
154
154
  routes_source_browser --> models
155
+ routes_tabs --> models
155
156
  routes_tabs --> components_step_renderer
157
+ routes_tabs --> services_source
156
158
  routes_tabs --> routes_core
157
159
  routes_tabs --> services_source_utils
158
- routes_tabs --> services_source
159
- routes_tabs --> models
160
160
  ```
161
161
 
162
162
  *52 cross-module dependencies detected*
@@ -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
 
@@ -526,8 +526,6 @@ def _handle_remove_external_source(
526
526
  sess, # FastHTML session object
527
527
  db_path: str, # Path to the .db file to remove
528
528
  external_db_paths_ref: List[str], # Shared external paths list (mutated in place)
529
- fb_state_getter: Callable[[], BrowserState], # File browser state getter
530
- fb_state_setter: Callable[[BrowserState], None], # File browser state setter
531
529
  fb_routers: FileBrowserRouters, # File browser routers (for targeted OOB)
532
530
  remove_url: str, # URL for remove button in external sources list
533
531
  ): # Tuple of OOB elements (external sources list + checkbox cells)
@@ -541,7 +539,7 @@ def init_local_files_router(
541
539
  source_service: SourceService, # The source service for external db ops
542
540
  prefix: str, # Route prefix (e.g., "/workflow/selection/local_files")
543
541
  urls: SelectionUrls, # URL bundle for rendering
544
- ) -> Tuple[List, Dict[str, Callable], Callable]: # (routers, route_dict, render_panel_fn)
542
+ ) -> LocalFilesResult: # Router result with routers, routes, render, and restore
545
543
  "Initialize local files browser routes with new file browser API."
546
544
  ```
547
545
 
@@ -560,10 +558,24 @@ _local_files_provider: Optional[LocalFileSystemProvider] = None
560
558
  ``` python
561
559
  from cjm_transcript_source_select.models import (
562
560
  SelectionStepState,
563
- SelectionUrls
561
+ SelectionUrls,
562
+ LocalFilesResult,
563
+ SelectionResult
564
564
  )
565
565
  ```
566
566
 
567
+ #### Functions
568
+
569
+ ``` python
570
+ def _no_op_restore(session_id: str) -> None:
571
+ """Default no-op for restore_state."""
572
+ pass
573
+
574
+ @dataclass
575
+ class LocalFilesResult
576
+ "Default no-op for restore_state."
577
+ ```
578
+
567
579
  #### Classes
568
580
 
569
581
  ``` python
@@ -593,6 +605,30 @@ class SelectionUrls:
593
605
  tab_switch: str = '' # Switch source tabs
594
606
  ```
595
607
 
608
+ ``` python
609
+ @dataclass
610
+ class LocalFilesResult:
611
+ "Return type from init_local_files_router."
612
+
613
+ routers: List[APIRouter] # Routers to register (custom + file browser + VC)
614
+ routes: Dict[str, Callable] # Named route handlers
615
+ render_panel: Callable # (error_message?, session_id?) -> rendered panel
616
+ restore_state: Callable = field(...) # (session_id) -> None, restore persisted state
617
+ ```
618
+
619
+ ``` python
620
+ @dataclass
621
+ class SelectionResult:
622
+ "Return type from init_selection_routers."
623
+
624
+ routers: List[APIRouter] # All selection routers to register
625
+ urls: 'SelectionUrls' = field(...) # URL bundle
626
+ routes: Dict[str, Callable] = field(...) # All named route handlers
627
+ render_local_files_panel: Optional[Callable] # Render fn for local files tab
628
+ sb_state: Any # SourceBrowserRouterState
629
+ restore_state: Callable = field(...) # (session_id) -> None, restore persisted state
630
+ ```
631
+
596
632
  ### preview_panel (`preview_panel.ipynb`)
597
633
 
598
634
  > Collapsible preview panel for displaying selected content
@@ -1436,15 +1472,16 @@ from cjm_transcript_source_select.routes.tabs import (
1436
1472
 
1437
1473
  ``` python
1438
1474
  def _handle_tab_switch(
1439
- state_store: WorkflowStateStore, # The workflow state store
1440
- workflow_id: str, # The workflow identifier
1441
1475
  source_service: SourceService, # The source service for queries
1442
1476
  request, # FastHTML request object
1443
1477
  sess, # FastHTML session object
1444
1478
  direction: str, # Direction: "prev", "next", "db", or "files"
1445
1479
  urls: SelectionUrls, # URL bundle for rendering
1480
+ current_tab_ref: List[str], # Mutable ref [current_tab] for closure-based tracking
1446
1481
  render_local_files_panel: Optional[Callable] = None, # Render fn for Files tab
1447
1482
  sb_state: Any = None, # SourceBrowserRouterState for DB tab VC rendering
1483
+ state_store: WorkflowStateStore = None, # State store (for reading step state)
1484
+ workflow_id: str = "", # Workflow ID (for reading step state)
1448
1485
  ): # Tuple of inner content, OOB tab headers, and tab switch script
1449
1486
  "Switch between Plugin DB and Local Files tabs."
1450
1487
  ```
@@ -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.13
4
+ version = 0.0.15
5
5
  min_python = 3.12
6
6
  license = apache2
7
7
  black_formatting = False
@@ -1 +0,0 @@
1
- __version__ = "0.0.13"