cjm-transcript-source-select 0.0.7__tar.gz → 0.0.8__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 (42) hide show
  1. {cjm_transcript_source_select-0.0.7/cjm_transcript_source_select.egg-info → cjm_transcript_source_select-0.0.8}/PKG-INFO +232 -163
  2. {cjm_transcript_source_select-0.0.7 → cjm_transcript_source_select-0.0.8}/README.md +230 -162
  3. cjm_transcript_source_select-0.0.8/cjm_transcript_source_select/__init__.py +1 -0
  4. {cjm_transcript_source_select-0.0.7 → cjm_transcript_source_select-0.0.8}/cjm_transcript_source_select/_modidx.py +30 -22
  5. {cjm_transcript_source_select-0.0.7 → cjm_transcript_source_select-0.0.8}/cjm_transcript_source_select/components/local_files.py +42 -65
  6. cjm_transcript_source_select-0.0.8/cjm_transcript_source_select/components/source_browser.py +408 -0
  7. {cjm_transcript_source_select-0.0.7 → cjm_transcript_source_select-0.0.8}/cjm_transcript_source_select/components/step_renderer.py +160 -170
  8. {cjm_transcript_source_select-0.0.7 → cjm_transcript_source_select-0.0.8}/cjm_transcript_source_select/html_ids.py +7 -0
  9. {cjm_transcript_source_select-0.0.7 → cjm_transcript_source_select-0.0.8}/cjm_transcript_source_select/models.py +1 -0
  10. {cjm_transcript_source_select-0.0.7 → cjm_transcript_source_select-0.0.8}/cjm_transcript_source_select/routes/core.py +37 -39
  11. {cjm_transcript_source_select-0.0.7 → cjm_transcript_source_select-0.0.8}/cjm_transcript_source_select/routes/filtering.py +12 -33
  12. cjm_transcript_source_select-0.0.8/cjm_transcript_source_select/routes/init.py +123 -0
  13. cjm_transcript_source_select-0.0.8/cjm_transcript_source_select/routes/local_files.py +191 -0
  14. {cjm_transcript_source_select-0.0.7 → cjm_transcript_source_select-0.0.8}/cjm_transcript_source_select/routes/queue.py +82 -17
  15. cjm_transcript_source_select-0.0.8/cjm_transcript_source_select/routes/source_browser.py +231 -0
  16. {cjm_transcript_source_select-0.0.7 → cjm_transcript_source_select-0.0.8}/cjm_transcript_source_select/routes/tabs.py +25 -56
  17. {cjm_transcript_source_select-0.0.7 → cjm_transcript_source_select-0.0.8/cjm_transcript_source_select.egg-info}/PKG-INFO +232 -163
  18. {cjm_transcript_source_select-0.0.7 → cjm_transcript_source_select-0.0.8}/cjm_transcript_source_select.egg-info/SOURCES.txt +1 -0
  19. {cjm_transcript_source_select-0.0.7 → cjm_transcript_source_select-0.0.8}/cjm_transcript_source_select.egg-info/requires.txt +1 -0
  20. {cjm_transcript_source_select-0.0.7 → cjm_transcript_source_select-0.0.8}/settings.ini +2 -2
  21. cjm_transcript_source_select-0.0.7/cjm_transcript_source_select/__init__.py +0 -1
  22. cjm_transcript_source_select-0.0.7/cjm_transcript_source_select/components/source_browser.py +0 -367
  23. cjm_transcript_source_select-0.0.7/cjm_transcript_source_select/routes/init.py +0 -73
  24. cjm_transcript_source_select-0.0.7/cjm_transcript_source_select/routes/local_files.py +0 -232
  25. {cjm_transcript_source_select-0.0.7 → cjm_transcript_source_select-0.0.8}/LICENSE +0 -0
  26. {cjm_transcript_source_select-0.0.7 → cjm_transcript_source_select-0.0.8}/MANIFEST.in +0 -0
  27. {cjm_transcript_source_select-0.0.7 → cjm_transcript_source_select-0.0.8}/cjm_transcript_source_select/components/__init__.py +0 -0
  28. {cjm_transcript_source_select-0.0.7 → cjm_transcript_source_select-0.0.8}/cjm_transcript_source_select/components/helpers.py +0 -0
  29. {cjm_transcript_source_select-0.0.7 → cjm_transcript_source_select-0.0.8}/cjm_transcript_source_select/components/preview_panel.py +0 -0
  30. {cjm_transcript_source_select-0.0.7 → cjm_transcript_source_select-0.0.8}/cjm_transcript_source_select/components/selection_queue.py +0 -0
  31. {cjm_transcript_source_select-0.0.7 → cjm_transcript_source_select-0.0.8}/cjm_transcript_source_select/routes/__init__.py +0 -0
  32. {cjm_transcript_source_select-0.0.7 → cjm_transcript_source_select-0.0.8}/cjm_transcript_source_select/services/__init__.py +0 -0
  33. {cjm_transcript_source_select-0.0.7 → cjm_transcript_source_select-0.0.8}/cjm_transcript_source_select/services/source.py +0 -0
  34. {cjm_transcript_source_select-0.0.7 → cjm_transcript_source_select-0.0.8}/cjm_transcript_source_select/services/source_utils.py +0 -0
  35. {cjm_transcript_source_select-0.0.7 → cjm_transcript_source_select-0.0.8}/cjm_transcript_source_select/utils.py +0 -0
  36. {cjm_transcript_source_select-0.0.7 → cjm_transcript_source_select-0.0.8}/cjm_transcript_source_select.egg-info/dependency_links.txt +0 -0
  37. {cjm_transcript_source_select-0.0.7 → cjm_transcript_source_select-0.0.8}/cjm_transcript_source_select.egg-info/entry_points.txt +0 -0
  38. {cjm_transcript_source_select-0.0.7 → cjm_transcript_source_select-0.0.8}/cjm_transcript_source_select.egg-info/not-zip-safe +0 -0
  39. {cjm_transcript_source_select-0.0.7 → cjm_transcript_source_select-0.0.8}/cjm_transcript_source_select.egg-info/top_level.txt +0 -0
  40. {cjm_transcript_source_select-0.0.7 → cjm_transcript_source_select-0.0.8}/pyproject.toml +0 -0
  41. {cjm_transcript_source_select-0.0.7 → cjm_transcript_source_select-0.0.8}/setup.cfg +0 -0
  42. {cjm_transcript_source_select-0.0.7 → cjm_transcript_source_select-0.0.8}/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.7
3
+ Version: 0.0.8
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
@@ -28,6 +28,7 @@ Requires-Dist: cjm_workflow_state
28
28
  Requires-Dist: cjm_source_provider
29
29
  Requires-Dist: cjm_fasthtml_interactions
30
30
  Requires-Dist: cjm_fasthtml_viewport_fit
31
+ Requires-Dist: cjm_fasthtml_virtual_collection
31
32
  Provides-Extra: dev
32
33
  Dynamic: author
33
34
  Dynamic: author-email
@@ -63,13 +64,14 @@ pip install cjm_transcript_source_select
63
64
  │ ├── selection_queue.ipynb # Selection queue component with drag-drop reordering
64
65
  │ ├── source_browser.ipynb # Source browser components for displaying and filtering transcription sources
65
66
  │ └── step_renderer.ipynb # Phase 1 step renderer: Source Selection & Ordering with two-column layout and collapsible preview
66
- ├── routes/ (6)
67
- │ ├── core.ipynb # Selection step state management helpers
68
- │ ├── filtering.ipynb # Filtering, grouping, and keyboard navigation route handlers
69
- │ ├── init.ipynb # Router assembly for Phase 1 selection routes
70
- │ ├── local_files.ipynb # Local files browser route handlers
71
- │ ├── queue.ipynb # Selection queue route handlers for Phase 1
72
- └── tabs.ipynb # Tab switching route handlers
67
+ ├── routes/ (7)
68
+ │ ├── core.ipynb # Selection step state management helpers
69
+ │ ├── filtering.ipynb # Filtering, grouping, and keyboard navigation route handlers
70
+ │ ├── init.ipynb # Router assembly for Phase 1 selection routes
71
+ │ ├── local_files.ipynb # Local files browser route handlers
72
+ │ ├── queue.ipynb # Selection queue route handlers for Phase 1
73
+ ├── source_browser.ipynb # Source browser virtual collection router for Phase 1 selection
74
+ │ └── tabs.ipynb # Tab switching route handlers
73
75
  ├── services/ (2)
74
76
  │ ├── source.ipynb # Source service for federated transcription queries via DuckDB
75
77
  │ └── source_utils.ipynb # Source record operations for metadata extraction, grouping, and validation
@@ -77,7 +79,7 @@ pip install cjm_transcript_source_select
77
79
  ├── models.ipynb # Data models and URL bundles for Phase 1: Source Selection & Ordering
78
80
  └── utils.ipynb # Display formatting and word counting utilities for the selection step
79
81
 
80
- Total: 17 notebooks across 3 directories
82
+ Total: 18 notebooks across 3 directories
81
83
 
82
84
  ## Module Dependencies
83
85
 
@@ -96,65 +98,69 @@ graph LR
96
98
  routes_init[routes.init<br/>init]
97
99
  routes_local_files[routes.local_files<br/>local_files]
98
100
  routes_queue[routes.queue<br/>queue]
101
+ routes_source_browser[routes.source_browser<br/>source_browser]
99
102
  routes_tabs[routes.tabs<br/>tabs]
100
103
  services_source[services.source<br/>source]
101
104
  services_source_utils[services.source_utils<br/>source_utils]
102
105
  utils[utils<br/>utils]
103
106
 
104
107
  components_helpers --> models
105
- components_local_files --> components_helpers
106
108
  components_local_files --> html_ids
109
+ components_local_files --> components_helpers
107
110
  components_preview_panel --> html_ids
108
111
  components_selection_queue --> html_ids
109
- components_source_browser --> services_source_utils
110
112
  components_source_browser --> utils
113
+ components_source_browser --> services_source_utils
111
114
  components_source_browser --> html_ids
112
- components_step_renderer --> models
113
- components_step_renderer --> components_selection_queue
114
115
  components_step_renderer --> components_local_files
115
- components_step_renderer --> components_preview_panel
116
116
  components_step_renderer --> components_helpers
117
117
  components_step_renderer --> html_ids
118
118
  components_step_renderer --> components_source_browser
119
+ components_step_renderer --> models
120
+ components_step_renderer --> components_selection_queue
121
+ components_step_renderer --> components_preview_panel
119
122
  components_step_renderer --> utils
120
- routes_core --> components_selection_queue
121
- routes_core --> components_source_browser
123
+ routes_core --> html_ids
124
+ routes_core --> models
122
125
  routes_core --> components_step_renderer
126
+ routes_core --> components_selection_queue
123
127
  routes_core --> services_source
124
- routes_core --> models
125
- routes_core --> html_ids
126
- routes_filtering --> models
128
+ routes_filtering --> routes_core
127
129
  routes_filtering --> services_source_utils
130
+ routes_filtering --> models
128
131
  routes_filtering --> services_source
129
- routes_filtering --> routes_core
130
- routes_filtering --> components_source_browser
132
+ routes_init --> routes_queue
133
+ routes_init --> routes_local_files
131
134
  routes_init --> routes_core
135
+ routes_init --> routes_tabs
136
+ routes_init --> routes_filtering
132
137
  routes_init --> models
138
+ routes_init --> routes_source_browser
133
139
  routes_init --> services_source
134
- routes_init --> routes_queue
135
- routes_init --> routes_filtering
136
- routes_init --> routes_tabs
137
- routes_init --> routes_local_files
138
140
  routes_local_files --> components_local_files
139
- routes_local_files --> services_source
140
141
  routes_local_files --> routes_core
141
142
  routes_local_files --> models
142
- routes_queue --> routes_core
143
+ routes_local_files --> services_source
143
144
  routes_queue --> services_source_utils
144
- routes_queue --> components_preview_panel
145
+ routes_queue --> routes_core
145
146
  routes_queue --> models
147
+ routes_queue --> components_preview_panel
146
148
  routes_queue --> services_source
147
- routes_tabs --> components_local_files
148
- routes_tabs --> routes_local_files
149
- routes_tabs --> components_step_renderer
149
+ routes_source_browser --> components_source_browser
150
+ routes_source_browser --> html_ids
151
+ routes_source_browser --> models
152
+ routes_source_browser --> services_source_utils
153
+ routes_source_browser --> components_preview_panel
154
+ routes_source_browser --> routes_core
155
+ routes_source_browser --> services_source
156
+ routes_tabs --> services_source_utils
150
157
  routes_tabs --> routes_core
158
+ routes_tabs --> components_step_renderer
151
159
  routes_tabs --> models
152
- routes_tabs --> components_source_browser
153
160
  routes_tabs --> services_source
154
- routes_tabs --> services_source_utils
155
161
  ```
156
162
 
157
- *51 cross-module dependencies detected*
163
+ *54 cross-module dependencies detected*
158
164
 
159
165
  ## CLI Reference
160
166
 
@@ -200,12 +206,10 @@ def _find_duplicate_media_source(
200
206
 
201
207
  ``` python
202
208
  def _render_duplicate_flash(
203
- candidate_record_id: str, # Record ID of the row the user clicked
204
- candidate_provider_id: str, # Provider ID of the row the user clicked
205
- existing_record_id: str, # Record ID of the conflicting selected row
206
- existing_provider_id: str, # Provider ID of the conflicting selected row
207
- ) -> Div: # OOB Div with flash script (replaces previous via innerHTML swap)
208
- "Render a fixed-ID container with flash script for two source rows."
209
+ candidate_row_id: str, # DOM element ID of the candidate row
210
+ existing_row_id: Optional[str] = None, # DOM element ID of the conflicting row (None if off-screen)
211
+ ) -> Div: # OOB Div with flash script
212
+ "Render a flash animation on one or two rows to indicate duplicate rejection."
209
213
  ```
210
214
 
211
215
  ``` python
@@ -226,8 +230,7 @@ def _build_queue_response(
226
230
  selected_sources: List[Dict[str, str]], # Current selected sources after mutation
227
231
  urls: SelectionUrls, # URL bundle for rendering
228
232
  include_stats: bool = True, # Include OOB stats swap
229
- include_source_list: bool = True, # Include conditional OOB source list swap
230
- grouping_mode: str = None, # Override grouping mode for source list rendering
233
+ include_checkbox_oobs: bool = True, # Include OOB checkbox cells for visible rows
231
234
  ) -> Union[Any, Tuple]: # Single component or tuple of components with OOB swaps
232
235
  "Build the standard response for queue-mutating handlers."
233
236
  ```
@@ -241,6 +244,12 @@ def _update_step_state(
241
244
 
242
245
  ``` python
243
246
  DEBUG_SELECTION_STATE = False
247
+ _rebuild_and_render_ref: list
248
+ _sync_items_ref: list
249
+ _get_checkbox_oobs_ref: list
250
+ _get_checkbox_oob_for_ref: list
251
+ _get_vc_row_id_for_ref: list
252
+ _activate_toggle_ref: list
244
253
  ```
245
254
 
246
255
  ### filtering (`filtering.ipynb`)
@@ -266,7 +275,7 @@ def _handle_source_filter(
266
275
  sess, # FastHTML session object
267
276
  search: str, # Search term from input
268
277
  urls: SelectionUrls, # URL bundle for rendering
269
- ): # Filtered source list component
278
+ ): # VC content wrapper (direct swap, not OOB)
270
279
  "Filter transcription sources by search term."
271
280
  ```
272
281
 
@@ -279,8 +288,8 @@ def _handle_grouping_change(
279
288
  sess, # FastHTML session object
280
289
  grouping_mode: str, # New grouping mode: "media_path" or "batch_id"
281
290
  urls: SelectionUrls, # URL bundle for rendering
282
- ): # Updated source list component
283
- "Change the grouping mode and re-render the source list."
291
+ ): # VC content wrapper (direct swap, not OOB)
292
+ "Change the grouping mode and re-render the VC content."
284
293
  ```
285
294
 
286
295
  ``` python
@@ -427,7 +436,7 @@ def init_selection_routers(
427
436
  source_service: SourceService, # The source service for queries
428
437
  workflow_id: str, # The workflow identifier
429
438
  prefix: str, # Base prefix for selection routes (e.g., "/workflow/selection")
430
- ) -> Tuple[List[APIRouter], SelectionUrls, Dict[str, Callable]]: # (routers, urls, merged_routes)
439
+ ) -> Tuple[List[APIRouter], SelectionUrls, Dict[str, Callable], Callable, "SourceBrowserRouterState"]
431
440
  "Initialize and return all selection routers with URL bundle."
432
441
  ```
433
442
 
@@ -474,22 +483,24 @@ def _create_db_browser_config() -> FileBrowserConfig: # Configured FileBrowserC
474
483
  def _render_external_sources_list(
475
484
  external_paths: List[str], # List of added external database paths
476
485
  remove_url: str, # URL for removing external source
477
- ) -> Any: # External sources list component
486
+ oob: bool = False, # Whether to render as OOB swap
487
+ ) -> Any: # External sources section component (always rendered for OOB targeting)
478
488
  "Render the list of added external database sources with scrollable paths."
479
489
  ```
480
490
 
491
+ ``` python
492
+ def _render_error_alert(
493
+ error_message: Optional[str] = None, # Error message to display (None = clear)
494
+ oob: bool = False, # Whether to render as OOB swap
495
+ ) -> Any: # Error alert container (always present for OOB targeting)
496
+ "Render the error alert container for the local files browser."
497
+ ```
498
+
481
499
  ``` python
482
500
  def _render_local_files_browser(
483
- browser_state: Optional[BrowserState] = None, # Current browser state
501
+ render_fn: Optional[Callable] = None, # FileBrowserRouters.render callable
484
502
  external_paths: Optional[List[str]] = None, # List of added external database paths
485
- provider: Optional[LocalFileSystemProvider] = None, # File system provider
486
- config: Optional[FileBrowserConfig] = None, # Browser configuration
487
- navigate_url: str = "", # URL for browsing directories
488
- select_url: str = "", # URL for adding external source (maps path to db_path)
489
503
  remove_url: str = "", # URL for removing external source
490
- refresh_url: str = "", # URL for refreshing browser
491
- path_input_url: str = "", # URL for direct path input
492
- home_path: Optional[str] = None, # Home directory path
493
504
  error_message: Optional[str] = None, # Error message to display
494
505
  ) -> Any: # Local files browser component
495
506
  "Render the local files browser for adding external .db files."
@@ -517,49 +528,17 @@ def _get_local_files_provider() -> LocalFileSystemProvider:
517
528
  "Get or create the local files provider singleton."
518
529
  ```
519
530
 
520
- ``` python
521
- def _handle_browse_directory(
522
- state_store: WorkflowStateStore, # The workflow state store
523
- workflow_id: str, # The workflow identifier
524
- source_service: SourceService, # The source service for external db ops
525
- request, # FastHTML request object
526
- sess, # FastHTML session object
527
- path: str, # Directory path to browse
528
- urls: SelectionUrls, # URL bundle for rendering
529
- ): # Local files browser component
530
- "Browse a directory and return the local files browser component."
531
- ```
532
-
533
- ``` python
534
- def _get_local_files_config() -> FileBrowserConfig:
535
- """Get or create the local files config singleton."""
536
- global _local_files_config
537
- if _local_files_config is None
538
- "Get or create the local files config singleton."
539
- ```
540
-
541
- ``` python
542
- def _handle_add_external_source(
543
- state_store: WorkflowStateStore, # The workflow state store
544
- workflow_id: str, # The workflow identifier
545
- source_service: SourceService, # The source service for external db ops
546
- request, # FastHTML request object
547
- sess, # FastHTML session object
548
- path: str, # Path to the .db file (from file-browser select_url)
549
- urls: SelectionUrls, # URL bundle for rendering
550
- ): # Local files browser component
551
- "Toggle an external database source (add if not present, remove if present)."
552
- ```
553
-
554
531
  ``` python
555
532
  def _handle_remove_external_source(
556
533
  state_store: WorkflowStateStore, # The workflow state store
557
534
  workflow_id: str, # The workflow identifier
558
535
  source_service: SourceService, # The source service for external db ops
559
- request, # FastHTML request object
560
536
  sess, # FastHTML session object
561
537
  db_path: str, # Path to the .db file to remove
562
- urls: SelectionUrls, # URL bundle for rendering
538
+ external_db_paths_ref: List[str], # Shared external paths list (mutated in place)
539
+ fb_state_getter: Callable[[], BrowserState], # File browser state getter
540
+ fb_state_setter: Callable[[BrowserState], None], # File browser state setter
541
+ render_panel_fn: Callable, # Function to render the full local files panel
563
542
  ): # Local files browser component
564
543
  "Remove an external database source from the Added Sources list."
565
544
  ```
@@ -571,15 +550,14 @@ def init_local_files_router(
571
550
  source_service: SourceService, # The source service for external db ops
572
551
  prefix: str, # Route prefix (e.g., "/workflow/selection/local_files")
573
552
  urls: SelectionUrls, # URL bundle for rendering
574
- ) -> Tuple[APIRouter, Dict[str, Callable]]: # (router, route_dict)
575
- "Initialize local files browser routes."
553
+ ) -> Tuple[List, Dict[str, Callable], Callable]: # (routers, route_dict, render_panel_fn)
554
+ "Initialize local files browser routes with new file browser API."
576
555
  ```
577
556
 
578
557
  #### Variables
579
558
 
580
559
  ``` python
581
560
  _local_files_provider: Optional[LocalFileSystemProvider] = None
582
- _local_files_config: Optional[FileBrowserConfig] = None
583
561
  ```
584
562
 
585
563
  ### models (`models.ipynb`)
@@ -609,6 +587,7 @@ class SelectionUrls:
609
587
 
610
588
  add: str = '' # Add source to queue
611
589
  remove: str = '' # Remove source from queue
590
+ toggle: str = '' # Toggle source selection (add/remove based on current state)
612
591
  reorder: str = '' # Reorder queue items
613
592
  clear: str = '' # Clear all from queue
614
593
  select_all: str = '' # Select all in a group
@@ -658,6 +637,20 @@ from cjm_transcript_source_select.routes.queue import (
658
637
 
659
638
  #### Functions
660
639
 
640
+ ``` python
641
+ def _handle_selection_toggle(
642
+ state_store: WorkflowStateStore, # The workflow state store
643
+ workflow_id: str, # The workflow identifier
644
+ source_service: SourceService, # The source service for queries
645
+ request, # FastHTML request object
646
+ sess, # FastHTML session object
647
+ record_id: str, # Job ID to toggle
648
+ provider_id: str, # Plugin name for the source
649
+ urls: SelectionUrls, # URL bundle for rendering
650
+ ): # Queue component with OOB stats (no checkbox OOBs -- checkbox already correct)
651
+ "Toggle a source's selection state (add if absent, remove if present)."
652
+ ```
653
+
661
654
  ``` python
662
655
  def _handle_selection_add(
663
656
  state_store: WorkflowStateStore, # The workflow state store
@@ -668,7 +661,7 @@ def _handle_selection_add(
668
661
  record_id: str, # Job ID to add
669
662
  provider_id: str, # Plugin name for the source
670
663
  urls: SelectionUrls, # URL bundle for rendering
671
- ): # Queue component with OOB stats, optionally with OOB source list
664
+ ): # Queue component with OOB stats and visible checkbox OOBs
672
665
  "Add a source to the selection queue."
673
666
  ```
674
667
 
@@ -682,7 +675,7 @@ def _handle_selection_remove(
682
675
  record_id: str, # Job ID to remove
683
676
  provider_id: str, # Plugin name for the source
684
677
  urls: SelectionUrls, # URL bundle for rendering
685
- ): # Queue component with OOB stats, optionally with OOB source list
678
+ ): # Queue component with OOB stats and visible checkbox OOBs
686
679
  "Remove a source from the selection queue."
687
680
  ```
688
681
 
@@ -1023,7 +1016,15 @@ VALID_DB_EXTENSIONS = [3 items]
1023
1016
  #### Import
1024
1017
 
1025
1018
  ``` python
1026
- from cjm_transcript_source_select.components.source_browser import *
1019
+ from cjm_transcript_source_select.components.source_browser import (
1020
+ SOURCE_BROWSER_COLUMNS,
1021
+ SB_SYSTEM_ID,
1022
+ SourceBrowserItem,
1023
+ build_source_items,
1024
+ is_source_item_skippable,
1025
+ create_source_cell_renderer,
1026
+ render_source_empty
1027
+ )
1027
1028
  ```
1028
1029
 
1029
1030
  #### Functions
@@ -1037,65 +1038,139 @@ def _render_grouping_selector(
1037
1038
  ```
1038
1039
 
1039
1040
  ``` python
1040
- def _render_source_row(
1041
- record: Dict[str, Any], # Transcription record
1042
- is_selected: bool, # Whether this source is selected
1043
- add_url: str, # URL for adding to queue
1044
- remove_url: str, # URL for removing from queue
1045
- preview_url: str, # URL for previewing content
1046
- is_first: bool = False, # Whether this is the first row (gets initial focus)
1047
- row_index: int = 0, # Index among selectable rows (for keyboard focus sync)
1048
- ) -> Any: # Table row element
1049
- "Render a single source row in the browser table."
1041
+ def build_source_items(
1042
+ transcriptions: List[Dict[str, Any]], # Available transcription records
1043
+ selected_sources: List[Dict[str, str]], # Currently selected sources
1044
+ grouping_mode: str = "media_path", # Grouping mode: "media_path" or "batch_id"
1045
+ ) -> List[SourceBrowserItem]: # Flat list with interleaved headers and records
1046
+ "Build the items list for the source browser virtual collection."
1050
1047
  ```
1051
1048
 
1052
1049
  ``` python
1053
- def _render_group_header(
1054
- group_key: str, # The group key (media_path or batch_id value)
1055
- record_count: int, # Number of records in this group
1056
- select_all_url: str, # URL for selecting all in group
1057
- grouping_mode: str = "media_path", # Current grouping mode
1058
- ) -> Any: # Table row for group header
1059
- "Render a group header row."
1050
+ def is_source_item_skippable(
1051
+ item: SourceBrowserItem, # Item to check
1052
+ ) -> bool: # True if item is a group header (cursor should skip)
1053
+ "Predicate for virtual collection is_skippable parameter."
1060
1054
  ```
1061
1055
 
1062
1056
  ``` python
1063
- def _render_audio_group_header(
1064
- media_path: str, # Path to audio file
1065
- record_count: int, # Number of records in this group
1066
- select_all_url: str, # URL for selecting all in group
1067
- ) -> Any: # Table row for group header
1068
- "Render a group header row for an audio file (legacy wrapper)."
1057
+ def _render_header_cell(
1058
+ item: SourceBrowserItem, # Header item
1059
+ ctx: CellRenderContext, # Cell render context
1060
+ select_all_url: str = "", # URL for selecting all in group
1061
+ ) -> Any: # Cell content for a group header row
1062
+ "Render cell content for a group header item."
1069
1063
  ```
1070
1064
 
1071
1065
  ``` python
1072
- def _render_source_list(
1073
- transcriptions: List[Dict[str, Any]], # Available transcription records
1074
- selected_sources: List[Dict[str, str]], # Currently selected sources
1075
- add_url: str, # URL for adding to queue
1076
- remove_url: str, # URL for removing from queue
1077
- preview_url: str, # URL for previewing content
1078
- select_all_url: str, # URL for selecting all in a group
1079
- grouping_mode: str = "media_path", # Grouping mode: "media_path" or "batch_id"
1080
- oob: bool = False, # Whether to include hx-swap-oob for out-of-band swap
1081
- ) -> Any: # Source list container with table
1082
- "Render the source list table with grouped rows."
1066
+ def _render_record_cell(
1067
+ item: SourceBrowserItem, # Record item
1068
+ ctx: CellRenderContext, # Cell render context
1069
+ toggle_url: str = "", # URL for toggling source selection
1070
+ ) -> Any: # Cell content for a data record row
1071
+ "Render cell content for a data record item."
1083
1072
  ```
1084
1073
 
1085
1074
  ``` python
1086
- def _render_source_browser(
1087
- transcriptions: List[Dict[str, Any]], # Available transcription records
1088
- sources: List[Dict[str, Any]], # Available source plugins (unused, kept for API compat)
1089
- selected_sources: List[Dict[str, str]], # Currently selected sources
1090
- add_url: str, # URL for adding to queue
1091
- remove_url: str, # URL for removing from queue
1092
- preview_url: str, # URL for previewing content
1093
- select_all_url: str, # URL for selecting all in a group
1094
- filter_url: str, # URL for filtering sources
1075
+ def create_source_cell_renderer(
1076
+ toggle_url: str = "", # URL for toggling source selection
1077
+ select_all_url: str = "", # URL for selecting all in a group
1078
+ ) -> Callable: # render_cell(item: SourceBrowserItem, ctx: CellRenderContext) -> Any
1079
+ "Create a render_cell callback for the source browser virtual collection."
1080
+ ```
1081
+
1082
+ ``` python
1083
+ def render_source_empty() -> Any: # Empty state component
1084
+ "Render empty state when no transcription sources are available."
1085
+ ```
1086
+
1087
+ ``` python
1088
+ def _render_source_browser_vc_content(
1089
+ sb_state: Any, # SourceBrowserRouterState from routes.source_browser
1090
+ ) -> Any: # VC content wrapper (without search/grouping header)
1091
+ "Render the VC content portion of the source browser."
1092
+ ```
1093
+
1094
+ ``` python
1095
+ def _render_source_browser_vc(
1096
+ sb_state: Any, # SourceBrowserRouterState from routes.source_browser
1097
+ filter_url: str = "", # URL for filtering sources
1095
1098
  grouping_mode: str = "media_path", # Current grouping mode
1096
1099
  grouping_change_url: str = "", # URL for changing grouping mode
1097
- ) -> Any: # Source browser component
1098
- "Render the source browser panel with search filtering and grouped table."
1100
+ ) -> Any: # Source browser component with virtual collection
1101
+ "Render the full source browser panel (header + VC content)."
1102
+ ```
1103
+
1104
+ #### Classes
1105
+
1106
+ ``` python
1107
+ @dataclass
1108
+ class SourceBrowserItem:
1109
+ "Item in the source browser virtual collection (header or record)."
1110
+
1111
+ item_type: str # "header" or "record"
1112
+ group_key: str = '' # Group key (media_path or batch_id value)
1113
+ group_display: str = '' # Formatted display text for group header
1114
+ group_count: int = 0 # Number of records in this group
1115
+ grouping_mode: str = '' # Grouping mode used ("media_path" or "batch_id")
1116
+ record: Optional[Dict[str, Any]] # Original transcription record dict
1117
+ is_selected: bool = False # Whether currently in queue
1118
+ ```
1119
+
1120
+ #### Variables
1121
+
1122
+ ``` python
1123
+ SOURCE_BROWSER_COLUMNS
1124
+ _SB_CONTENT_ID = 'sb-content'
1125
+ _SB_VC_WRAPPER_ID = 'sb-vc-wrapper'
1126
+ SB_SYSTEM_ID = 'sb-collection'
1127
+ ```
1128
+
1129
+ ### source_browser (`source_browser.ipynb`)
1130
+
1131
+ > Source browser virtual collection router for Phase 1 selection
1132
+
1133
+ #### Import
1134
+
1135
+ ``` python
1136
+ from cjm_transcript_source_select.routes.source_browser import (
1137
+ SourceBrowserRouterState,
1138
+ init_source_browser_router
1139
+ )
1140
+ ```
1141
+
1142
+ #### Functions
1143
+
1144
+ ``` python
1145
+ def init_source_browser_router(
1146
+ source_service: SourceService, # Source service for querying transcriptions
1147
+ urls: SelectionUrls, # URL bundle (toggle, select_all, filter, grouping_change)
1148
+ prefix: str = "/browser", # Route prefix for VC routes
1149
+ ) -> SourceBrowserRouterState: # Router state with all VC objects and helpers
1150
+ "Initialize the source browser virtual collection router."
1151
+ ```
1152
+
1153
+ #### Classes
1154
+
1155
+ ``` python
1156
+ @dataclass
1157
+ class SourceBrowserRouterState:
1158
+ "Return value from init_source_browser_router."
1159
+
1160
+ router: APIRouter # VC routes (nav, focus, activate, sort, viewport)
1161
+ urls: VirtualCollectionUrls # VC URL bundle
1162
+ ids: VirtualCollectionHtmlIds # VC HTML IDs
1163
+ btn_ids: VirtualCollectionButtonIds # VC keyboard button IDs
1164
+ config: VirtualCollectionConfig # VC config
1165
+ state: VirtualCollectionState # VC state (mutable)
1166
+ items: List[SourceBrowserItem] # Shared items list (mutable)
1167
+ render_cell: Callable # Cell render callback
1168
+ rebuild_and_render: Callable # (transcriptions, selected_sources, grouping_mode, content_only) -> Div
1169
+ rebuild_items: Callable # (transcriptions, selected_sources, grouping_mode) -> None
1170
+ sync_items_selection: Callable # (selected_sources) -> None
1171
+ get_visible_checkbox_oobs: Callable # () -> tuple of OOB elements
1172
+ get_checkbox_oob_for: Callable # (record_id, provider_id) -> OOB element or None
1173
+ get_vc_row_id_for: Callable # (record_id, provider_id) -> str or None
1099
1174
  ```
1100
1175
 
1101
1176
  ### source_utils (`source_utils.ipynb`)
@@ -1252,13 +1327,13 @@ def validate_browse_path(
1252
1327
  from cjm_transcript_source_select.components.step_renderer import (
1253
1328
  SD_FOCUSED_RECORD_ID_INPUT,
1254
1329
  SD_FOCUSED_PROVIDER_ID_INPUT,
1255
- SD_TOGGLE_BTN,
1256
1330
  SD_REMOVE_BTN,
1257
1331
  SD_REORDER_UP_BTN,
1258
1332
  SD_REORDER_DOWN_BTN,
1259
1333
  SD_TAB_PREV_BTN,
1260
1334
  SD_TAB_NEXT_BTN,
1261
1335
  SD_PREVIEW_BTN,
1336
+ FB_SYSTEM_ID,
1262
1337
  render_selection_step
1263
1338
  )
1264
1339
  ```
@@ -1266,8 +1341,8 @@ from cjm_transcript_source_select.components.step_renderer import (
1266
1341
  #### Functions
1267
1342
 
1268
1343
  ``` python
1269
- def _create_selection_keyboard_manager() -> ZoneManager: # Configured keyboard zone manager
1270
- "Create the keyboard zone manager for Phase 1 selection step."
1344
+ def _create_parent_keyboard_manager() -> ZoneManager: # Parent keyboard manager for hierarchy
1345
+ "Create the parent keyboard manager for the selection step."
1271
1346
  ```
1272
1347
 
1273
1348
  ``` python
@@ -1313,19 +1388,10 @@ def _render_source_tabs(
1313
1388
  ```
1314
1389
 
1315
1390
  ``` python
1316
- def _get_step_renderer_provider() -> LocalFileSystemProvider:
1317
- """Get or create the local files provider for step renderer."""
1318
- global _step_renderer_provider
1319
- if _step_renderer_provider is None
1320
- "Get or create the local files provider for step renderer."
1321
- ```
1322
-
1323
- ``` python
1324
- def _get_step_renderer_config():
1325
- """Get or create the local files config for step renderer."""
1326
- global _step_renderer_config
1327
- if _step_renderer_config is None
1328
- "Get or create the local files config for step renderer."
1391
+ def _generate_hierarchy_js(
1392
+ active_tab: str, # Active tab: "db" or "files"
1393
+ ) -> Script: # Script element with hierarchy wiring and activation logic
1394
+ "Generate JavaScript for keyboard system hierarchy and child activation."
1329
1395
  ```
1330
1396
 
1331
1397
  ``` python
@@ -1334,10 +1400,10 @@ def render_selection_step(
1334
1400
  transcriptions: List[Dict[str, Any]], # Available transcription records
1335
1401
  selected_sources: List[Dict[str, str]], # Ordered selection
1336
1402
  grouping_mode: str, # Grouping mode: "media_path" or "batch_id"
1337
- external_db_paths: List[str], # External database paths
1338
- file_browser_state: Dict[str, Any], # Serialized BrowserState from file-browser library
1339
1403
  active_tab: str, # Active tab: "db" or "files"
1340
1404
  urls: SelectionUrls, # URL bundle for selection routes
1405
+ render_local_files_panel: Optional[Callable] = None, # Render fn for Files tab content
1406
+ sb_state: Any = None, # SourceBrowserRouterState for DB tab VC rendering
1341
1407
  ) -> Any: # FastHTML component
1342
1408
  "Render Phase 1: Source Selection & Ordering step with two-column layout."
1343
1409
  ```
@@ -1347,14 +1413,13 @@ def render_selection_step(
1347
1413
  ``` python
1348
1414
  SD_FOCUSED_RECORD_ID_INPUT = 'sd-focused-record-id'
1349
1415
  SD_FOCUSED_PROVIDER_ID_INPUT = 'sd-focused-provider-id'
1350
- SD_TOGGLE_BTN = 'sd-toggle-btn'
1351
1416
  SD_REMOVE_BTN = 'sd-remove-btn'
1352
1417
  SD_REORDER_UP_BTN = 'sd-reorder-up-btn'
1353
1418
  SD_REORDER_DOWN_BTN = 'sd-reorder-down-btn'
1354
1419
  SD_TAB_PREV_BTN = 'sd-tab-prev-btn'
1355
1420
  SD_TAB_NEXT_BTN = 'sd-tab-next-btn'
1356
1421
  SD_PREVIEW_BTN = 'sd-preview-btn'
1357
- _step_renderer_provider: Optional[LocalFileSystemProvider] = None
1422
+ FB_SYSTEM_ID = 'lfb-collection'
1358
1423
  _VIEWPORT_FIT_CONFIG
1359
1424
  ```
1360
1425
 
@@ -1381,7 +1446,9 @@ def _handle_tab_switch(
1381
1446
  sess, # FastHTML session object
1382
1447
  direction: str, # Direction: "prev", "next", "db", or "files"
1383
1448
  urls: SelectionUrls, # URL bundle for rendering
1384
- ): # Tuple of inner content and OOB tab headers
1449
+ render_local_files_panel: Optional[Callable] = None, # Render fn for Files tab
1450
+ sb_state: Any = None, # SourceBrowserRouterState for DB tab VC rendering
1451
+ ): # Tuple of inner content, OOB tab headers, and tab switch script
1385
1452
  "Switch between Plugin DB and Local Files tabs."
1386
1453
  ```
1387
1454
 
@@ -1392,6 +1459,8 @@ def init_tabs_router(
1392
1459
  source_service: SourceService, # The source service for queries
1393
1460
  prefix: str, # Route prefix (e.g., "/workflow/selection/tabs")
1394
1461
  urls: SelectionUrls, # URL bundle for rendering
1462
+ render_local_files_panel: Optional[Callable] = None, # Render fn for Files tab content
1463
+ sb_state: Any = None, # SourceBrowserRouterState for DB tab VC rendering
1395
1464
  ) -> Tuple[APIRouter, Dict[str, Callable]]: # (router, route_dict)
1396
1465
  "Initialize tab switching routes."
1397
1466
  ```