cjm-transcript-source-select 0.0.14__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.14/cjm_transcript_source_select.egg-info → cjm_transcript_source_select-0.0.15}/PKG-INFO +65 -26
  2. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.15}/README.md +64 -25
  3. cjm_transcript_source_select-0.0.15/cjm_transcript_source_select/__init__.py +1 -0
  4. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/_modidx.py +8 -2
  5. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/models.py +28 -3
  6. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/routes/init.py +15 -8
  7. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/routes/local_files.py +73 -7
  8. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/routes/tabs.py +16 -13
  9. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.15/cjm_transcript_source_select.egg-info}/PKG-INFO +65 -26
  10. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.15}/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.15}/LICENSE +0 -0
  13. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.15}/MANIFEST.in +0 -0
  14. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/components/__init__.py +0 -0
  15. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/components/helpers.py +0 -0
  16. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/components/local_files.py +0 -0
  17. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/components/preview_panel.py +0 -0
  18. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/components/selection_queue.py +0 -0
  19. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/components/source_browser.py +0 -0
  20. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/components/step_renderer.py +0 -0
  21. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/html_ids.py +0 -0
  22. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/routes/__init__.py +0 -0
  23. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/routes/core.py +0 -0
  24. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/routes/filtering.py +0 -0
  25. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/routes/queue.py +0 -0
  26. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/routes/source_browser.py +0 -0
  27. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/services/__init__.py +0 -0
  28. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/services/source.py +0 -0
  29. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/services/source_utils.py +0 -0
  30. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select/utils.py +0 -0
  31. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select.egg-info/SOURCES.txt +0 -0
  32. {cjm_transcript_source_select-0.0.14 → 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.14 → 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.14 → 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.14 → cjm_transcript_source_select-0.0.15}/cjm_transcript_source_select.egg-info/requires.txt +0 -0
  36. {cjm_transcript_source_select-0.0.14 → 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.14 → cjm_transcript_source_select-0.0.15}/pyproject.toml +0 -0
  38. {cjm_transcript_source_select-0.0.14 → cjm_transcript_source_select-0.0.15}/setup.cfg +0 -0
  39. {cjm_transcript_source_select-0.0.14 → 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.14
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
115
  components_step_renderer --> components_preview_panel
116
- components_step_renderer --> models
117
- components_step_renderer --> utils
118
- components_step_renderer --> components_source_browser
119
116
  components_step_renderer --> components_selection_queue
117
+ components_step_renderer --> utils
120
118
  components_step_renderer --> components_local_files
121
119
  components_step_renderer --> html_ids
120
+ components_step_renderer --> models
121
+ components_step_renderer --> components_source_browser
122
122
  routes_core --> models
123
+ routes_core --> services_source
124
+ routes_core --> components_selection_queue
123
125
  routes_core --> components_step_renderer
124
126
  routes_core --> html_ids
125
- routes_core --> components_selection_queue
126
- routes_core --> services_source
127
- routes_filtering --> models
128
127
  routes_filtering --> routes_core
129
128
  routes_filtering --> services_source_utils
130
129
  routes_filtering --> services_source
130
+ routes_filtering --> models
131
131
  routes_init --> routes_core
132
132
  routes_init --> routes_queue
133
- routes_init --> models
134
- routes_init --> routes_local_files
135
- routes_init --> routes_source_browser
136
- routes_init --> routes_filtering
137
133
  routes_init --> routes_tabs
134
+ routes_init --> routes_filtering
138
135
  routes_init --> services_source
139
- routes_local_files --> components_local_files
136
+ routes_init --> routes_source_browser
137
+ routes_init --> routes_local_files
138
+ routes_init --> models
140
139
  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 --> components_local_files
142
+ routes_local_files --> services_source
144
143
  routes_queue --> routes_core
144
+ routes_queue --> services_source_utils
145
145
  routes_queue --> components_preview_panel
146
- routes_queue --> models
147
146
  routes_queue --> services_source
148
- routes_source_browser --> routes_core
149
- routes_source_browser --> components_preview_panel
150
- routes_source_browser --> models
147
+ routes_queue --> models
151
148
  routes_source_browser --> components_source_browser
149
+ routes_source_browser --> components_preview_panel
152
150
  routes_source_browser --> services_source_utils
153
151
  routes_source_browser --> services_source
152
+ routes_source_browser --> routes_core
154
153
  routes_source_browser --> html_ids
155
- routes_tabs --> routes_core
156
- routes_tabs --> services_source_utils
154
+ routes_source_browser --> models
157
155
  routes_tabs --> models
158
156
  routes_tabs --> components_step_renderer
159
157
  routes_tabs --> services_source
158
+ routes_tabs --> routes_core
159
+ routes_tabs --> services_source_utils
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
 
@@ -539,7 +539,7 @@ def init_local_files_router(
539
539
  source_service: SourceService, # The source service for external db ops
540
540
  prefix: str, # Route prefix (e.g., "/workflow/selection/local_files")
541
541
  urls: SelectionUrls, # URL bundle for rendering
542
- ) -> Tuple[List, Dict[str, Callable], Callable]: # (routers, route_dict, render_panel_fn)
542
+ ) -> LocalFilesResult: # Router result with routers, routes, render, and restore
543
543
  "Initialize local files browser routes with new file browser API."
544
544
  ```
545
545
 
@@ -558,10 +558,24 @@ _local_files_provider: Optional[LocalFileSystemProvider] = None
558
558
  ``` python
559
559
  from cjm_transcript_source_select.models import (
560
560
  SelectionStepState,
561
- SelectionUrls
561
+ SelectionUrls,
562
+ LocalFilesResult,
563
+ SelectionResult
562
564
  )
563
565
  ```
564
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
+
565
579
  #### Classes
566
580
 
567
581
  ``` python
@@ -591,6 +605,30 @@ class SelectionUrls:
591
605
  tab_switch: str = '' # Switch source tabs
592
606
  ```
593
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
+
594
632
  ### preview_panel (`preview_panel.ipynb`)
595
633
 
596
634
  > Collapsible preview panel for displaying selected content
@@ -1434,15 +1472,16 @@ from cjm_transcript_source_select.routes.tabs import (
1434
1472
 
1435
1473
  ``` python
1436
1474
  def _handle_tab_switch(
1437
- state_store: WorkflowStateStore, # The workflow state store
1438
- workflow_id: str, # The workflow identifier
1439
1475
  source_service: SourceService, # The source service for queries
1440
1476
  request, # FastHTML request object
1441
1477
  sess, # FastHTML session object
1442
1478
  direction: str, # Direction: "prev", "next", "db", or "files"
1443
1479
  urls: SelectionUrls, # URL bundle for rendering
1480
+ current_tab_ref: List[str], # Mutable ref [current_tab] for closure-based tracking
1444
1481
  render_local_files_panel: Optional[Callable] = None, # Render fn for Files tab
1445
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)
1446
1485
  ): # Tuple of inner content, OOB tab headers, and tab switch script
1447
1486
  "Switch between Plugin DB and Local Files tabs."
1448
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
69
  components_step_renderer --> components_preview_panel
70
- components_step_renderer --> models
71
- components_step_renderer --> utils
72
- components_step_renderer --> components_source_browser
73
70
  components_step_renderer --> components_selection_queue
71
+ components_step_renderer --> utils
74
72
  components_step_renderer --> components_local_files
75
73
  components_step_renderer --> html_ids
74
+ components_step_renderer --> models
75
+ components_step_renderer --> components_source_browser
76
76
  routes_core --> models
77
+ routes_core --> services_source
78
+ routes_core --> components_selection_queue
77
79
  routes_core --> components_step_renderer
78
80
  routes_core --> html_ids
79
- routes_core --> components_selection_queue
80
- routes_core --> services_source
81
- routes_filtering --> models
82
81
  routes_filtering --> routes_core
83
82
  routes_filtering --> services_source_utils
84
83
  routes_filtering --> services_source
84
+ routes_filtering --> models
85
85
  routes_init --> routes_core
86
86
  routes_init --> routes_queue
87
- routes_init --> models
88
- routes_init --> routes_local_files
89
- routes_init --> routes_source_browser
90
- routes_init --> routes_filtering
91
87
  routes_init --> routes_tabs
88
+ routes_init --> routes_filtering
92
89
  routes_init --> services_source
93
- routes_local_files --> components_local_files
90
+ routes_init --> routes_source_browser
91
+ routes_init --> routes_local_files
92
+ routes_init --> models
94
93
  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 --> components_local_files
96
+ routes_local_files --> services_source
98
97
  routes_queue --> routes_core
98
+ routes_queue --> services_source_utils
99
99
  routes_queue --> components_preview_panel
100
- routes_queue --> models
101
100
  routes_queue --> services_source
102
- routes_source_browser --> routes_core
103
- routes_source_browser --> components_preview_panel
104
- routes_source_browser --> models
101
+ routes_queue --> models
105
102
  routes_source_browser --> components_source_browser
103
+ routes_source_browser --> components_preview_panel
106
104
  routes_source_browser --> services_source_utils
107
105
  routes_source_browser --> services_source
106
+ routes_source_browser --> routes_core
108
107
  routes_source_browser --> html_ids
109
- routes_tabs --> routes_core
110
- routes_tabs --> services_source_utils
108
+ routes_source_browser --> models
111
109
  routes_tabs --> models
112
110
  routes_tabs --> components_step_renderer
113
111
  routes_tabs --> services_source
112
+ routes_tabs --> routes_core
113
+ routes_tabs --> services_source_utils
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
 
@@ -493,7 +493,7 @@ def init_local_files_router(
493
493
  source_service: SourceService, # The source service for external db ops
494
494
  prefix: str, # Route prefix (e.g., "/workflow/selection/local_files")
495
495
  urls: SelectionUrls, # URL bundle for rendering
496
- ) -> Tuple[List, Dict[str, Callable], Callable]: # (routers, route_dict, render_panel_fn)
496
+ ) -> LocalFilesResult: # Router result with routers, routes, render, and restore
497
497
  "Initialize local files browser routes with new file browser API."
498
498
  ```
499
499
 
@@ -512,10 +512,24 @@ _local_files_provider: Optional[LocalFileSystemProvider] = None
512
512
  ``` python
513
513
  from cjm_transcript_source_select.models import (
514
514
  SelectionStepState,
515
- SelectionUrls
515
+ SelectionUrls,
516
+ LocalFilesResult,
517
+ SelectionResult
516
518
  )
517
519
  ```
518
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
+
519
533
  #### Classes
520
534
 
521
535
  ``` python
@@ -545,6 +559,30 @@ class SelectionUrls:
545
559
  tab_switch: str = '' # Switch source tabs
546
560
  ```
547
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
+
548
586
  ### preview_panel (`preview_panel.ipynb`)
549
587
 
550
588
  > Collapsible preview panel for displaying selected content
@@ -1388,15 +1426,16 @@ from cjm_transcript_source_select.routes.tabs import (
1388
1426
 
1389
1427
  ``` python
1390
1428
  def _handle_tab_switch(
1391
- state_store: WorkflowStateStore, # The workflow state store
1392
- workflow_id: str, # The workflow identifier
1393
1429
  source_service: SourceService, # The source service for queries
1394
1430
  request, # FastHTML request object
1395
1431
  sess, # FastHTML session object
1396
1432
  direction: str, # Direction: "prev", "next", "db", or "files"
1397
1433
  urls: SelectionUrls, # URL bundle for rendering
1434
+ current_tab_ref: List[str], # Mutable ref [current_tab] for closure-based tracking
1398
1435
  render_local_files_panel: Optional[Callable] = None, # Render fn for Files tab
1399
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)
1400
1439
  ): # Tuple of inner content, OOB tab headers, and tab switch script
1401
1440
  "Switch between Plugin DB and Local Files tabs."
1402
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',
@@ -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
  )
@@ -78,7 +78,7 @@ def init_local_files_router(
78
78
  source_service: SourceService, # The source service for external db ops
79
79
  prefix: str, # Route prefix (e.g., "/workflow/selection/local_files")
80
80
  urls: SelectionUrls, # URL bundle for rendering
81
- ) -> Tuple[List, Dict[str, Callable], Callable]: # (routers, route_dict, render_panel_fn)
81
+ ) -> LocalFilesResult: # Router result with routers, routes, render, and restore
82
82
  """Initialize local files browser routes with new file browser API."""
83
83
  provider = _get_local_files_provider()
84
84
  config = _create_db_browser_config()
@@ -90,18 +90,57 @@ def init_local_files_router(
90
90
  # --- File browser state accessors (shared, non-per-session) ---
91
91
  _browser_state = BrowserState(current_path=home_path)
92
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
+
93
119
  def _fb_state_getter() -> BrowserState:
94
120
  """Get browser state with selection synced to external_db_paths."""
95
121
  _browser_state.selection.selected_paths = list(_external_db_paths)
96
122
  return _browser_state
97
123
 
98
- def _fb_state_setter(state: BrowserState) -> None:
99
- """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
+
100
130
  _browser_state.current_path = state.current_path
101
131
  _browser_state.sort_by = state.sort_by
102
132
  _browser_state.sort_descending = state.sort_descending
103
133
  _browser_state.selection = state.selection
104
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
+
105
144
  # --- Callbacks for file browser ---
106
145
  def _validate_selection(path: str) -> Tuple[bool, str]:
107
146
  """Validate .db schema before allowing selection (checkbox click path)."""
@@ -111,8 +150,12 @@ def init_local_files_router(
111
150
  return (False, error)
112
151
  return (True, "")
113
152
 
114
- def _on_selection_change(selected_paths: List[str]) -> Tuple:
153
+ def _on_selection_change(selected_paths: List[str], request=None) -> Tuple:
115
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
+
116
159
  # Validate any newly added paths
117
160
  old_set = set(_external_db_paths)
118
161
  validated_paths = []
@@ -134,6 +177,14 @@ def init_local_files_router(
134
177
  _external_db_paths[:] = validated_paths
135
178
  source_service.set_external_paths(list(_external_db_paths))
136
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
+
137
188
  return (
138
189
  _render_external_sources_list(list(_external_db_paths), urls.remove_external, oob=True),
139
190
  _render_error_alert(error_message, oob=True),
@@ -156,8 +207,12 @@ def init_local_files_router(
156
207
  )
157
208
 
158
209
  # --- Render function for the full local files panel ---
159
- def _render_panel(error_message: Optional[str] = None) -> Any:
210
+ def _render_panel(error_message: Optional[str] = None, session_id: Optional[str] = None) -> Any:
160
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()
161
216
  return _render_local_files_browser(
162
217
  render_fn=fb_routers.render,
163
218
  external_paths=list(_external_db_paths),
@@ -165,6 +220,12 @@ def init_local_files_router(
165
220
  error_message=error_message,
166
221
  )
167
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
+
168
229
  # --- Custom router for remove_external (needs session context) ---
169
230
  custom_router = APIRouter(prefix=prefix)
170
231
 
@@ -186,4 +247,9 @@ def init_local_files_router(
186
247
  "remove_external": remove_external,
187
248
  }
188
249
 
189
- 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.14
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
115
  components_step_renderer --> components_preview_panel
116
- components_step_renderer --> models
117
- components_step_renderer --> utils
118
- components_step_renderer --> components_source_browser
119
116
  components_step_renderer --> components_selection_queue
117
+ components_step_renderer --> utils
120
118
  components_step_renderer --> components_local_files
121
119
  components_step_renderer --> html_ids
120
+ components_step_renderer --> models
121
+ components_step_renderer --> components_source_browser
122
122
  routes_core --> models
123
+ routes_core --> services_source
124
+ routes_core --> components_selection_queue
123
125
  routes_core --> components_step_renderer
124
126
  routes_core --> html_ids
125
- routes_core --> components_selection_queue
126
- routes_core --> services_source
127
- routes_filtering --> models
128
127
  routes_filtering --> routes_core
129
128
  routes_filtering --> services_source_utils
130
129
  routes_filtering --> services_source
130
+ routes_filtering --> models
131
131
  routes_init --> routes_core
132
132
  routes_init --> routes_queue
133
- routes_init --> models
134
- routes_init --> routes_local_files
135
- routes_init --> routes_source_browser
136
- routes_init --> routes_filtering
137
133
  routes_init --> routes_tabs
134
+ routes_init --> routes_filtering
138
135
  routes_init --> services_source
139
- routes_local_files --> components_local_files
136
+ routes_init --> routes_source_browser
137
+ routes_init --> routes_local_files
138
+ routes_init --> models
140
139
  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 --> components_local_files
142
+ routes_local_files --> services_source
144
143
  routes_queue --> routes_core
144
+ routes_queue --> services_source_utils
145
145
  routes_queue --> components_preview_panel
146
- routes_queue --> models
147
146
  routes_queue --> services_source
148
- routes_source_browser --> routes_core
149
- routes_source_browser --> components_preview_panel
150
- routes_source_browser --> models
147
+ routes_queue --> models
151
148
  routes_source_browser --> components_source_browser
149
+ routes_source_browser --> components_preview_panel
152
150
  routes_source_browser --> services_source_utils
153
151
  routes_source_browser --> services_source
152
+ routes_source_browser --> routes_core
154
153
  routes_source_browser --> html_ids
155
- routes_tabs --> routes_core
156
- routes_tabs --> services_source_utils
154
+ routes_source_browser --> models
157
155
  routes_tabs --> models
158
156
  routes_tabs --> components_step_renderer
159
157
  routes_tabs --> services_source
158
+ routes_tabs --> routes_core
159
+ routes_tabs --> services_source_utils
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
 
@@ -539,7 +539,7 @@ def init_local_files_router(
539
539
  source_service: SourceService, # The source service for external db ops
540
540
  prefix: str, # Route prefix (e.g., "/workflow/selection/local_files")
541
541
  urls: SelectionUrls, # URL bundle for rendering
542
- ) -> Tuple[List, Dict[str, Callable], Callable]: # (routers, route_dict, render_panel_fn)
542
+ ) -> LocalFilesResult: # Router result with routers, routes, render, and restore
543
543
  "Initialize local files browser routes with new file browser API."
544
544
  ```
545
545
 
@@ -558,10 +558,24 @@ _local_files_provider: Optional[LocalFileSystemProvider] = None
558
558
  ``` python
559
559
  from cjm_transcript_source_select.models import (
560
560
  SelectionStepState,
561
- SelectionUrls
561
+ SelectionUrls,
562
+ LocalFilesResult,
563
+ SelectionResult
562
564
  )
563
565
  ```
564
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
+
565
579
  #### Classes
566
580
 
567
581
  ``` python
@@ -591,6 +605,30 @@ class SelectionUrls:
591
605
  tab_switch: str = '' # Switch source tabs
592
606
  ```
593
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
+
594
632
  ### preview_panel (`preview_panel.ipynb`)
595
633
 
596
634
  > Collapsible preview panel for displaying selected content
@@ -1434,15 +1472,16 @@ from cjm_transcript_source_select.routes.tabs import (
1434
1472
 
1435
1473
  ``` python
1436
1474
  def _handle_tab_switch(
1437
- state_store: WorkflowStateStore, # The workflow state store
1438
- workflow_id: str, # The workflow identifier
1439
1475
  source_service: SourceService, # The source service for queries
1440
1476
  request, # FastHTML request object
1441
1477
  sess, # FastHTML session object
1442
1478
  direction: str, # Direction: "prev", "next", "db", or "files"
1443
1479
  urls: SelectionUrls, # URL bundle for rendering
1480
+ current_tab_ref: List[str], # Mutable ref [current_tab] for closure-based tracking
1444
1481
  render_local_files_panel: Optional[Callable] = None, # Render fn for Files tab
1445
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)
1446
1485
  ): # Tuple of inner content, OOB tab headers, and tab switch script
1447
1486
  "Switch between Plugin DB and Local Files tabs."
1448
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.14
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.14"