cjm-transcript-source-select 0.0.29__tar.gz → 0.0.31__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 (36) hide show
  1. {cjm_transcript_source_select-0.0.29/cjm_transcript_source_select.egg-info → cjm_transcript_source_select-0.0.31}/PKG-INFO +83 -57
  2. {cjm_transcript_source_select-0.0.29 → cjm_transcript_source_select-0.0.31}/README.md +77 -51
  3. cjm_transcript_source_select-0.0.31/cjm_transcript_source_select/__init__.py +1 -0
  4. {cjm_transcript_source_select-0.0.29 → cjm_transcript_source_select-0.0.31}/cjm_transcript_source_select/_modidx.py +2 -0
  5. {cjm_transcript_source_select-0.0.29 → cjm_transcript_source_select-0.0.31}/cjm_transcript_source_select/components/source_browser.py +54 -18
  6. {cjm_transcript_source_select-0.0.29 → cjm_transcript_source_select-0.0.31}/cjm_transcript_source_select/components/step_renderer.py +46 -22
  7. {cjm_transcript_source_select-0.0.29 → cjm_transcript_source_select-0.0.31}/cjm_transcript_source_select/models.py +5 -1
  8. {cjm_transcript_source_select-0.0.29 → cjm_transcript_source_select-0.0.31}/cjm_transcript_source_select/routes/filtering.py +1 -1
  9. {cjm_transcript_source_select-0.0.29 → cjm_transcript_source_select-0.0.31}/cjm_transcript_source_select/routes/init.py +10 -2
  10. {cjm_transcript_source_select-0.0.29 → cjm_transcript_source_select-0.0.31}/cjm_transcript_source_select/routes/local_files.py +4 -1
  11. {cjm_transcript_source_select-0.0.29 → cjm_transcript_source_select-0.0.31}/cjm_transcript_source_select/routes/queue.py +1 -1
  12. {cjm_transcript_source_select-0.0.29 → cjm_transcript_source_select-0.0.31}/cjm_transcript_source_select/routes/source_browser.py +30 -2
  13. {cjm_transcript_source_select-0.0.29 → cjm_transcript_source_select-0.0.31}/cjm_transcript_source_select/routes/tabs.py +2 -1
  14. {cjm_transcript_source_select-0.0.29 → cjm_transcript_source_select-0.0.31/cjm_transcript_source_select.egg-info}/PKG-INFO +83 -57
  15. {cjm_transcript_source_select-0.0.29 → cjm_transcript_source_select-0.0.31}/cjm_transcript_source_select.egg-info/requires.txt +5 -5
  16. {cjm_transcript_source_select-0.0.29 → cjm_transcript_source_select-0.0.31}/pyproject.toml +1 -1
  17. cjm_transcript_source_select-0.0.29/cjm_transcript_source_select/__init__.py +0 -1
  18. {cjm_transcript_source_select-0.0.29 → cjm_transcript_source_select-0.0.31}/LICENSE +0 -0
  19. {cjm_transcript_source_select-0.0.29 → cjm_transcript_source_select-0.0.31}/MANIFEST.in +0 -0
  20. {cjm_transcript_source_select-0.0.29 → cjm_transcript_source_select-0.0.31}/cjm_transcript_source_select/components/__init__.py +0 -0
  21. {cjm_transcript_source_select-0.0.29 → cjm_transcript_source_select-0.0.31}/cjm_transcript_source_select/components/helpers.py +0 -0
  22. {cjm_transcript_source_select-0.0.29 → cjm_transcript_source_select-0.0.31}/cjm_transcript_source_select/components/local_files.py +0 -0
  23. {cjm_transcript_source_select-0.0.29 → cjm_transcript_source_select-0.0.31}/cjm_transcript_source_select/components/preview_panel.py +0 -0
  24. {cjm_transcript_source_select-0.0.29 → cjm_transcript_source_select-0.0.31}/cjm_transcript_source_select/components/selection_queue.py +0 -0
  25. {cjm_transcript_source_select-0.0.29 → cjm_transcript_source_select-0.0.31}/cjm_transcript_source_select/html_ids.py +0 -0
  26. {cjm_transcript_source_select-0.0.29 → cjm_transcript_source_select-0.0.31}/cjm_transcript_source_select/routes/__init__.py +0 -0
  27. {cjm_transcript_source_select-0.0.29 → cjm_transcript_source_select-0.0.31}/cjm_transcript_source_select/routes/core.py +0 -0
  28. {cjm_transcript_source_select-0.0.29 → cjm_transcript_source_select-0.0.31}/cjm_transcript_source_select/services/__init__.py +0 -0
  29. {cjm_transcript_source_select-0.0.29 → cjm_transcript_source_select-0.0.31}/cjm_transcript_source_select/services/source.py +0 -0
  30. {cjm_transcript_source_select-0.0.29 → cjm_transcript_source_select-0.0.31}/cjm_transcript_source_select/services/source_utils.py +0 -0
  31. {cjm_transcript_source_select-0.0.29 → cjm_transcript_source_select-0.0.31}/cjm_transcript_source_select/utils.py +0 -0
  32. {cjm_transcript_source_select-0.0.29 → cjm_transcript_source_select-0.0.31}/cjm_transcript_source_select.egg-info/SOURCES.txt +0 -0
  33. {cjm_transcript_source_select-0.0.29 → cjm_transcript_source_select-0.0.31}/cjm_transcript_source_select.egg-info/dependency_links.txt +0 -0
  34. {cjm_transcript_source_select-0.0.29 → cjm_transcript_source_select-0.0.31}/cjm_transcript_source_select.egg-info/entry_points.txt +0 -0
  35. {cjm_transcript_source_select-0.0.29 → cjm_transcript_source_select-0.0.31}/cjm_transcript_source_select.egg-info/top_level.txt +0 -0
  36. {cjm_transcript_source_select-0.0.29 → cjm_transcript_source_select-0.0.31}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cjm-transcript-source-select
3
- Version: 0.0.29
3
+ Version: 0.0.31
4
4
  Summary: FastHTML source selection component for transcript decomposition workflows, with federated database browsing, drag-drop ordering, and keyboard navigation.
5
5
  Author-email: "Christian J. Mills" <9126128+cj-mills@users.noreply.github.com>
6
6
  License: Apache-2.0
@@ -15,14 +15,14 @@ Classifier: Programming Language :: Python :: 3 :: Only
15
15
  Requires-Python: >=3.12
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
- Requires-Dist: python-fasthtml==0.13.4
18
+ Requires-Dist: python-fasthtml
19
19
  Requires-Dist: cjm-plugin-system
20
20
  Requires-Dist: cjm-transcription-plugin-system
21
- Requires-Dist: cjm-fasthtml-app-core
21
+ Requires-Dist: cjm-fasthtml-app-core>=0.0.17
22
22
  Requires-Dist: cjm-fasthtml-daisyui
23
23
  Requires-Dist: cjm_fasthtml_lucide_icons
24
- Requires-Dist: cjm_fasthtml_file_browser>=0.0.17
25
- Requires-Dist: cjm_fasthtml_keyboard_navigation>=0.0.22
24
+ Requires-Dist: cjm_fasthtml_file_browser>=0.0.19
25
+ Requires-Dist: cjm_fasthtml_keyboard_navigation>=0.0.23
26
26
  Requires-Dist: duckdb
27
27
  Requires-Dist: pandas
28
28
  Requires-Dist: cjm_workflow_state
@@ -30,7 +30,7 @@ Requires-Dist: cjm_source_provider
30
30
  Requires-Dist: cjm_fasthtml_interactions
31
31
  Requires-Dist: cjm_fasthtml_viewport_fit
32
32
  Requires-Dist: cjm_fasthtml_virtual_collection
33
- Requires-Dist: cjm_fasthtml_sortable_queue>=0.0.13
33
+ Requires-Dist: cjm_fasthtml_sortable_queue>=0.0.16
34
34
  Requires-Dist: cjm_fasthtml_design_system>=0.0.9
35
35
  Dynamic: license-file
36
36
 
@@ -96,57 +96,57 @@ graph LR
96
96
  utils[utils<br/>utils]
97
97
 
98
98
  components_helpers --> models
99
- components_local_files --> components_helpers
100
99
  components_local_files --> html_ids
100
+ components_local_files --> components_helpers
101
101
  components_preview_panel --> html_ids
102
102
  components_source_browser --> services_source_utils
103
103
  components_source_browser --> html_ids
104
104
  components_source_browser --> utils
105
- components_step_renderer --> components_preview_panel
106
- components_step_renderer --> utils
107
105
  components_step_renderer --> components_selection_queue
108
- components_step_renderer --> components_source_browser
109
- components_step_renderer --> models
110
106
  components_step_renderer --> html_ids
107
+ components_step_renderer --> models
111
108
  components_step_renderer --> components_local_files
112
- routes_core --> components_step_renderer
109
+ components_step_renderer --> components_source_browser
110
+ components_step_renderer --> components_preview_panel
111
+ components_step_renderer --> utils
113
112
  routes_core --> models
114
- routes_core --> components_selection_queue
115
- routes_core --> services_source
116
113
  routes_core --> html_ids
117
- routes_filtering --> routes_core
114
+ routes_core --> components_step_renderer
115
+ routes_core --> services_source
116
+ routes_core --> components_selection_queue
118
117
  routes_filtering --> services_source_utils
119
118
  routes_filtering --> services_source
119
+ routes_filtering --> routes_core
120
120
  routes_filtering --> models
121
- routes_init --> routes_local_files
122
- routes_init --> routes_source_browser
123
121
  routes_init --> routes_core
122
+ routes_init --> services_source
123
+ routes_init --> routes_local_files
124
124
  routes_init --> routes_queue
125
125
  routes_init --> routes_tabs
126
- routes_init --> services_source
127
- routes_init --> routes_filtering
128
126
  routes_init --> models
129
- routes_local_files --> routes_core
127
+ routes_init --> routes_filtering
128
+ routes_init --> routes_source_browser
130
129
  routes_local_files --> components_local_files
131
130
  routes_local_files --> services_source
131
+ routes_local_files --> routes_core
132
132
  routes_local_files --> models
133
- routes_queue --> components_preview_panel
134
- routes_queue --> routes_core
135
- routes_queue --> services_source_utils
136
133
  routes_queue --> services_source
134
+ routes_queue --> services_source_utils
135
+ routes_queue --> routes_core
137
136
  routes_queue --> models
138
- routes_source_browser --> components_preview_panel
139
- routes_source_browser --> components_source_browser
137
+ routes_queue --> components_preview_panel
140
138
  routes_source_browser --> services_source
141
- routes_source_browser --> routes_core
139
+ routes_source_browser --> components_source_browser
140
+ routes_source_browser --> html_ids
142
141
  routes_source_browser --> models
143
142
  routes_source_browser --> services_source_utils
144
- routes_source_browser --> html_ids
145
- routes_tabs --> services_source
143
+ routes_source_browser --> routes_core
144
+ routes_source_browser --> components_preview_panel
145
+ routes_tabs --> models
146
146
  routes_tabs --> routes_core
147
- routes_tabs --> components_step_renderer
147
+ routes_tabs --> services_source
148
148
  routes_tabs --> services_source_utils
149
- routes_tabs --> models
149
+ routes_tabs --> components_step_renderer
150
150
  ```
151
151
 
152
152
  *52 cross-module dependencies detected*
@@ -525,12 +525,6 @@ def _handle_remove_external_source(
525
525
 
526
526
  ``` python
527
527
  def init_local_files_router(
528
- state_store: WorkflowStateStore, # The workflow state store
529
- workflow_id: str, # The workflow identifier
530
- source_service: SourceService, # The source service for external db ops
531
- prefix: str, # Route prefix (e.g., "/workflow/selection/local_files")
532
- urls: SelectionUrls, # URL bundle for rendering
533
- ) -> LocalFilesResult: # Router result with routers, routes, render, restore, and reset
534
528
  "Initialize local files browser routes with new file browser API."
535
529
  ```
536
530
 
@@ -615,6 +609,7 @@ class LocalFilesResult:
615
609
  render_panel: Callable # (error_message?, session_id?) -> rendered panel
616
610
  restore_state: Callable = field(...) # (session_id) -> None, restore persisted state
617
611
  reset_state: Callable = field(...) # () -> None, reset in-memory caches
612
+ kb_manager: Optional[ZoneManager] # ZoneManager backing the local file browser's keyboard system — hand to render_keyboard_hints_modal(..., child_managers=[...]) for hierarchical hint display
618
613
  ```
619
614
 
620
615
  ``` python
@@ -629,6 +624,7 @@ class SelectionResult:
629
624
  sb_state: Any # SourceBrowserRouterState
630
625
  restore_state: Callable = field(...) # (session_id) -> None, restore persisted state
631
626
  reset_state: Callable = field(...) # () -> None, reset in-memory caches
627
+ fb_kb_manager: Optional[ZoneManager] # ZoneManager from the local file browser; convenience pointer mirroring LocalFilesResult.kb_manager so step renderers can read it directly from SelectionResult without descending into LocalFilesResult
632
628
  ```
633
629
 
634
630
  ### preview_panel (`preview_panel.ipynb`)
@@ -1067,7 +1063,8 @@ from cjm_transcript_source_select.components.source_browser import (
1067
1063
  build_source_items,
1068
1064
  is_source_item_skippable,
1069
1065
  create_source_cell_renderer,
1070
- render_source_empty
1066
+ render_source_empty,
1067
+ build_source_browser_keyboard_system
1071
1068
  )
1072
1069
  ```
1073
1070
 
@@ -1128,19 +1125,45 @@ def render_source_empty() -> Any: # Empty state component
1128
1125
  "Render empty state when no transcription sources are available."
1129
1126
  ```
1130
1127
 
1128
+ ``` python
1129
+ def build_source_browser_keyboard_system(
1130
+ sb_state: Any, # SourceBrowserRouterState (carries vc_ids, vc_btn_ids, vc_urls)
1131
+ manager_label: Optional[str] = None, # Human-readable label for the ZoneManager (consumed by render_keyboard_hints_modal section header when this is a child_managers entry)
1132
+ ) -> KeyboardSystem: # Complete rendered KeyboardSystem (carries .manager for hierarchical hints handoff)
1133
+ """
1134
+ Build the source browser's KeyboardSystem standalone.
1135
+
1136
+ Lifts the manager construction out of `_render_source_browser_vc_content`
1137
+ so the underlying `ZoneManager` is accessible via `KeyboardSystem.manager`
1138
+ for hierarchical hints handoff. Consumers wiring this as a child of the
1139
+ parent selection-step manager pass the manager to
1140
+ `render_keyboard_hints_modal(..., child_managers=[...])` so the modal's
1141
+ section header reads as `manager_label` instead of the technical SB_SYSTEM_ID.
1142
+ """
1143
+ ```
1144
+
1131
1145
  ``` python
1132
1146
  def _render_source_browser_vc_content(
1133
- sb_state: Any, # SourceBrowserRouterState from routes.source_browser
1147
+ sb_state: Any, # SourceBrowserRouterState from routes.source_browser
1148
+ keyboard_system: Optional[KeyboardSystem] = None, # Pre-built keyboard system (when None, builds internally via build_source_browser_keyboard_system)
1134
1149
  ) -> Any: # VC content wrapper (without search/grouping header)
1135
- "Render the VC content portion of the source browser."
1150
+ """
1151
+ Render the VC content portion of the source browser.
1152
+
1153
+ When `keyboard_system` is provided, the caller has built the system upstream
1154
+ (typically `init_source_browser_router`) and owns the underlying ZoneManager.
1155
+ When None, builds the system internally — preserves pre-C1 behavior for any
1156
+ caller that doesn't need the manager handoff.
1157
+ """
1136
1158
  ```
1137
1159
 
1138
1160
  ``` python
1139
1161
  def _render_source_browser_vc(
1140
- sb_state: Any, # SourceBrowserRouterState from routes.source_browser
1141
- filter_url: str = "", # URL for filtering sources
1142
- grouping_mode: str = "media_path", # Current grouping mode
1143
- grouping_change_url: str = "", # URL for changing grouping mode
1162
+ sb_state: Any, # SourceBrowserRouterState from routes.source_browser
1163
+ filter_url: str = "", # URL for filtering sources
1164
+ grouping_mode: str = "media_path", # Current grouping mode
1165
+ grouping_change_url: str = "", # URL for changing grouping mode
1166
+ keyboard_system: Optional[KeyboardSystem] = None, # Pre-built keyboard system, threaded through to VC content
1144
1167
  ) -> Any: # Source browser component with virtual collection
1145
1168
  "Render the full source browser panel (header + VC content)."
1146
1169
  ```
@@ -1187,10 +1210,6 @@ from cjm_transcript_source_select.routes.source_browser import (
1187
1210
 
1188
1211
  ``` python
1189
1212
  def init_source_browser_router(
1190
- source_service: SourceService, # Source service for querying transcriptions
1191
- urls: SelectionUrls, # URL bundle (toggle, select_all, filter, grouping_change)
1192
- prefix: str = "/browser", # Route prefix for VC routes
1193
- ) -> SourceBrowserRouterState: # Router state with all VC objects and helpers
1194
1213
  "Initialize the source browser virtual collection router."
1195
1214
  ```
1196
1215
 
@@ -1215,6 +1234,7 @@ class SourceBrowserRouterState:
1215
1234
  get_visible_checkbox_oobs: Callable # () -> tuple of OOB elements
1216
1235
  get_checkbox_oob_for: Callable # (record_id, provider_id) -> OOB element or None
1217
1236
  get_vc_row_id_for: Callable # (record_id, provider_id) -> str or None
1237
+ kb_manager: Optional[ZoneManager] # ZoneManager backing the source browser's VC keyboard system — hand to render_keyboard_hints_modal(..., child_managers=[...]) for hierarchical hint display
1218
1238
  ```
1219
1239
 
1220
1240
  ### source_utils (`source_utils.ipynb`)
@@ -1381,7 +1401,22 @@ from cjm_transcript_source_select.components.step_renderer import (
1381
1401
 
1382
1402
  ``` python
1383
1403
  def _create_parent_keyboard_manager() -> ZoneManager: # Parent keyboard manager for hierarchy
1384
- "Create the parent keyboard manager with two ghost zones for column switching."
1404
+ """
1405
+ Create the parent keyboard manager with two ghost zones for column switching.
1406
+
1407
+ Each ghost zone declares `activate_child_callback` so the library-baked
1408
+ Enter/Space dispatch (`ZoneManager.activate_keys` defaults to `("Enter", " ")`)
1409
+ invokes the consumer-defined JS function — `window.activateBrowserChild`
1410
+ and `window.activateQueueChild` are wired in `_generate_hierarchy_js`. The
1411
+ callback path is used (vs. the declarative `activate_child_id` path) because
1412
+ the browser ghost zone's active child depends on the currently-selected
1413
+ tab (`db` → source-browser child; `files` → file-browser child); this
1414
+ dynamic resolution can't be a static declarative mapping.
1415
+
1416
+ Each ghost zone's `label` surfaces as the section header for that zone's
1417
+ derived rows in the hints modal (e.g., the parent's "Source Browser" /
1418
+ "Selection Queue" — Activate row appears under those labels).
1419
+ """
1385
1420
  ```
1386
1421
 
1387
1422
  ``` python
@@ -1428,15 +1463,6 @@ def _generate_hierarchy_js(
1428
1463
 
1429
1464
  ``` python
1430
1465
  def render_selection_step(
1431
- sources: List[Dict[str, Any]], # Available source plugins
1432
- transcriptions: List[Dict[str, Any]], # Available transcription records
1433
- selected_sources: List[Dict[str, str]], # Ordered selection
1434
- grouping_mode: str, # Grouping mode: "media_path" or "batch_id"
1435
- active_tab: str, # Active tab: "db" or "files"
1436
- urls: SelectionUrls, # URL bundle for selection routes
1437
- render_local_files_panel: Optional[Callable] = None, # Render fn for Files tab content
1438
- sb_state: Any = None, # SourceBrowserRouterState for DB tab VC rendering
1439
- ) -> Any: # FastHTML component
1440
1466
  "Render Phase 1: Source Selection & Ordering step with two-column layout."
1441
1467
  ```
1442
1468
 
@@ -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 --> html_ids
68
68
  components_source_browser --> utils
69
- components_step_renderer --> components_preview_panel
70
- components_step_renderer --> utils
71
69
  components_step_renderer --> components_selection_queue
72
- components_step_renderer --> components_source_browser
73
- components_step_renderer --> models
74
70
  components_step_renderer --> html_ids
71
+ components_step_renderer --> models
75
72
  components_step_renderer --> components_local_files
76
- routes_core --> components_step_renderer
73
+ components_step_renderer --> components_source_browser
74
+ components_step_renderer --> components_preview_panel
75
+ components_step_renderer --> utils
77
76
  routes_core --> models
78
- routes_core --> components_selection_queue
79
- routes_core --> services_source
80
77
  routes_core --> html_ids
81
- routes_filtering --> routes_core
78
+ routes_core --> components_step_renderer
79
+ routes_core --> services_source
80
+ routes_core --> components_selection_queue
82
81
  routes_filtering --> services_source_utils
83
82
  routes_filtering --> services_source
83
+ routes_filtering --> routes_core
84
84
  routes_filtering --> models
85
- routes_init --> routes_local_files
86
- routes_init --> routes_source_browser
87
85
  routes_init --> routes_core
86
+ routes_init --> services_source
87
+ routes_init --> routes_local_files
88
88
  routes_init --> routes_queue
89
89
  routes_init --> routes_tabs
90
- routes_init --> services_source
91
- routes_init --> routes_filtering
92
90
  routes_init --> models
93
- routes_local_files --> routes_core
91
+ routes_init --> routes_filtering
92
+ routes_init --> routes_source_browser
94
93
  routes_local_files --> components_local_files
95
94
  routes_local_files --> services_source
95
+ routes_local_files --> routes_core
96
96
  routes_local_files --> models
97
- routes_queue --> components_preview_panel
98
- routes_queue --> routes_core
99
- routes_queue --> services_source_utils
100
97
  routes_queue --> services_source
98
+ routes_queue --> services_source_utils
99
+ routes_queue --> routes_core
101
100
  routes_queue --> models
102
- routes_source_browser --> components_preview_panel
103
- routes_source_browser --> components_source_browser
101
+ routes_queue --> components_preview_panel
104
102
  routes_source_browser --> services_source
105
- routes_source_browser --> routes_core
103
+ routes_source_browser --> components_source_browser
104
+ routes_source_browser --> html_ids
106
105
  routes_source_browser --> models
107
106
  routes_source_browser --> services_source_utils
108
- routes_source_browser --> html_ids
109
- routes_tabs --> services_source
107
+ routes_source_browser --> routes_core
108
+ routes_source_browser --> components_preview_panel
109
+ routes_tabs --> models
110
110
  routes_tabs --> routes_core
111
- routes_tabs --> components_step_renderer
111
+ routes_tabs --> services_source
112
112
  routes_tabs --> services_source_utils
113
- routes_tabs --> models
113
+ routes_tabs --> components_step_renderer
114
114
  ```
115
115
 
116
116
  *52 cross-module dependencies detected*
@@ -489,12 +489,6 @@ def _handle_remove_external_source(
489
489
 
490
490
  ``` python
491
491
  def init_local_files_router(
492
- state_store: WorkflowStateStore, # The workflow state store
493
- workflow_id: str, # The workflow identifier
494
- source_service: SourceService, # The source service for external db ops
495
- prefix: str, # Route prefix (e.g., "/workflow/selection/local_files")
496
- urls: SelectionUrls, # URL bundle for rendering
497
- ) -> LocalFilesResult: # Router result with routers, routes, render, restore, and reset
498
492
  "Initialize local files browser routes with new file browser API."
499
493
  ```
500
494
 
@@ -579,6 +573,7 @@ class LocalFilesResult:
579
573
  render_panel: Callable # (error_message?, session_id?) -> rendered panel
580
574
  restore_state: Callable = field(...) # (session_id) -> None, restore persisted state
581
575
  reset_state: Callable = field(...) # () -> None, reset in-memory caches
576
+ kb_manager: Optional[ZoneManager] # ZoneManager backing the local file browser's keyboard system — hand to render_keyboard_hints_modal(..., child_managers=[...]) for hierarchical hint display
582
577
  ```
583
578
 
584
579
  ``` python
@@ -593,6 +588,7 @@ class SelectionResult:
593
588
  sb_state: Any # SourceBrowserRouterState
594
589
  restore_state: Callable = field(...) # (session_id) -> None, restore persisted state
595
590
  reset_state: Callable = field(...) # () -> None, reset in-memory caches
591
+ fb_kb_manager: Optional[ZoneManager] # ZoneManager from the local file browser; convenience pointer mirroring LocalFilesResult.kb_manager so step renderers can read it directly from SelectionResult without descending into LocalFilesResult
596
592
  ```
597
593
 
598
594
  ### preview_panel (`preview_panel.ipynb`)
@@ -1031,7 +1027,8 @@ from cjm_transcript_source_select.components.source_browser import (
1031
1027
  build_source_items,
1032
1028
  is_source_item_skippable,
1033
1029
  create_source_cell_renderer,
1034
- render_source_empty
1030
+ render_source_empty,
1031
+ build_source_browser_keyboard_system
1035
1032
  )
1036
1033
  ```
1037
1034
 
@@ -1092,19 +1089,45 @@ def render_source_empty() -> Any: # Empty state component
1092
1089
  "Render empty state when no transcription sources are available."
1093
1090
  ```
1094
1091
 
1092
+ ``` python
1093
+ def build_source_browser_keyboard_system(
1094
+ sb_state: Any, # SourceBrowserRouterState (carries vc_ids, vc_btn_ids, vc_urls)
1095
+ manager_label: Optional[str] = None, # Human-readable label for the ZoneManager (consumed by render_keyboard_hints_modal section header when this is a child_managers entry)
1096
+ ) -> KeyboardSystem: # Complete rendered KeyboardSystem (carries .manager for hierarchical hints handoff)
1097
+ """
1098
+ Build the source browser's KeyboardSystem standalone.
1099
+
1100
+ Lifts the manager construction out of `_render_source_browser_vc_content`
1101
+ so the underlying `ZoneManager` is accessible via `KeyboardSystem.manager`
1102
+ for hierarchical hints handoff. Consumers wiring this as a child of the
1103
+ parent selection-step manager pass the manager to
1104
+ `render_keyboard_hints_modal(..., child_managers=[...])` so the modal's
1105
+ section header reads as `manager_label` instead of the technical SB_SYSTEM_ID.
1106
+ """
1107
+ ```
1108
+
1095
1109
  ``` python
1096
1110
  def _render_source_browser_vc_content(
1097
- sb_state: Any, # SourceBrowserRouterState from routes.source_browser
1111
+ sb_state: Any, # SourceBrowserRouterState from routes.source_browser
1112
+ keyboard_system: Optional[KeyboardSystem] = None, # Pre-built keyboard system (when None, builds internally via build_source_browser_keyboard_system)
1098
1113
  ) -> Any: # VC content wrapper (without search/grouping header)
1099
- "Render the VC content portion of the source browser."
1114
+ """
1115
+ Render the VC content portion of the source browser.
1116
+
1117
+ When `keyboard_system` is provided, the caller has built the system upstream
1118
+ (typically `init_source_browser_router`) and owns the underlying ZoneManager.
1119
+ When None, builds the system internally — preserves pre-C1 behavior for any
1120
+ caller that doesn't need the manager handoff.
1121
+ """
1100
1122
  ```
1101
1123
 
1102
1124
  ``` python
1103
1125
  def _render_source_browser_vc(
1104
- sb_state: Any, # SourceBrowserRouterState from routes.source_browser
1105
- filter_url: str = "", # URL for filtering sources
1106
- grouping_mode: str = "media_path", # Current grouping mode
1107
- grouping_change_url: str = "", # URL for changing grouping mode
1126
+ sb_state: Any, # SourceBrowserRouterState from routes.source_browser
1127
+ filter_url: str = "", # URL for filtering sources
1128
+ grouping_mode: str = "media_path", # Current grouping mode
1129
+ grouping_change_url: str = "", # URL for changing grouping mode
1130
+ keyboard_system: Optional[KeyboardSystem] = None, # Pre-built keyboard system, threaded through to VC content
1108
1131
  ) -> Any: # Source browser component with virtual collection
1109
1132
  "Render the full source browser panel (header + VC content)."
1110
1133
  ```
@@ -1151,10 +1174,6 @@ from cjm_transcript_source_select.routes.source_browser import (
1151
1174
 
1152
1175
  ``` python
1153
1176
  def init_source_browser_router(
1154
- source_service: SourceService, # Source service for querying transcriptions
1155
- urls: SelectionUrls, # URL bundle (toggle, select_all, filter, grouping_change)
1156
- prefix: str = "/browser", # Route prefix for VC routes
1157
- ) -> SourceBrowserRouterState: # Router state with all VC objects and helpers
1158
1177
  "Initialize the source browser virtual collection router."
1159
1178
  ```
1160
1179
 
@@ -1179,6 +1198,7 @@ class SourceBrowserRouterState:
1179
1198
  get_visible_checkbox_oobs: Callable # () -> tuple of OOB elements
1180
1199
  get_checkbox_oob_for: Callable # (record_id, provider_id) -> OOB element or None
1181
1200
  get_vc_row_id_for: Callable # (record_id, provider_id) -> str or None
1201
+ kb_manager: Optional[ZoneManager] # ZoneManager backing the source browser's VC keyboard system — hand to render_keyboard_hints_modal(..., child_managers=[...]) for hierarchical hint display
1182
1202
  ```
1183
1203
 
1184
1204
  ### source_utils (`source_utils.ipynb`)
@@ -1345,7 +1365,22 @@ from cjm_transcript_source_select.components.step_renderer import (
1345
1365
 
1346
1366
  ``` python
1347
1367
  def _create_parent_keyboard_manager() -> ZoneManager: # Parent keyboard manager for hierarchy
1348
- "Create the parent keyboard manager with two ghost zones for column switching."
1368
+ """
1369
+ Create the parent keyboard manager with two ghost zones for column switching.
1370
+
1371
+ Each ghost zone declares `activate_child_callback` so the library-baked
1372
+ Enter/Space dispatch (`ZoneManager.activate_keys` defaults to `("Enter", " ")`)
1373
+ invokes the consumer-defined JS function — `window.activateBrowserChild`
1374
+ and `window.activateQueueChild` are wired in `_generate_hierarchy_js`. The
1375
+ callback path is used (vs. the declarative `activate_child_id` path) because
1376
+ the browser ghost zone's active child depends on the currently-selected
1377
+ tab (`db` → source-browser child; `files` → file-browser child); this
1378
+ dynamic resolution can't be a static declarative mapping.
1379
+
1380
+ Each ghost zone's `label` surfaces as the section header for that zone's
1381
+ derived rows in the hints modal (e.g., the parent's "Source Browser" /
1382
+ "Selection Queue" — Activate row appears under those labels).
1383
+ """
1349
1384
  ```
1350
1385
 
1351
1386
  ``` python
@@ -1392,15 +1427,6 @@ def _generate_hierarchy_js(
1392
1427
 
1393
1428
  ``` python
1394
1429
  def render_selection_step(
1395
- sources: List[Dict[str, Any]], # Available source plugins
1396
- transcriptions: List[Dict[str, Any]], # Available transcription records
1397
- selected_sources: List[Dict[str, str]], # Ordered selection
1398
- grouping_mode: str, # Grouping mode: "media_path" or "batch_id"
1399
- active_tab: str, # Active tab: "db" or "files"
1400
- urls: SelectionUrls, # URL bundle for selection routes
1401
- render_local_files_panel: Optional[Callable] = None, # Render fn for Files tab content
1402
- sb_state: Any = None, # SourceBrowserRouterState for DB tab VC rendering
1403
- ) -> Any: # FastHTML component
1404
1430
  "Render Phase 1: Source Selection & Ordering step with two-column layout."
1405
1431
  ```
1406
1432
 
@@ -0,0 +1 @@
1
+ __version__ = "0.0.31"
@@ -45,6 +45,8 @@ d = { 'settings': { 'branch': 'main',
45
45
  'cjm_transcript_source_select/components/source_browser.py'),
46
46
  'cjm_transcript_source_select.components.source_browser._render_source_browser_vc_content': ( 'components/source_browser.html#_render_source_browser_vc_content',
47
47
  'cjm_transcript_source_select/components/source_browser.py'),
48
+ 'cjm_transcript_source_select.components.source_browser.build_source_browser_keyboard_system': ( 'components/source_browser.html#build_source_browser_keyboard_system',
49
+ 'cjm_transcript_source_select/components/source_browser.py'),
48
50
  'cjm_transcript_source_select.components.source_browser.build_source_items': ( 'components/source_browser.html#build_source_items',
49
51
  'cjm_transcript_source_select/components/source_browser.py'),
50
52
  'cjm_transcript_source_select.components.source_browser.create_source_cell_renderer': ( 'components/source_browser.html#create_source_cell_renderer',
@@ -4,7 +4,7 @@
4
4
 
5
5
  # %% auto #0
6
6
  __all__ = ['SOURCE_BROWSER_COLUMNS', 'SB_SYSTEM_ID', 'SourceBrowserItem', 'build_source_items', 'is_source_item_skippable',
7
- 'create_source_cell_renderer', 'render_source_empty']
7
+ 'create_source_cell_renderer', 'render_source_empty', 'build_source_browser_keyboard_system']
8
8
 
9
9
  # %% ../../nbs/components/source_browser.ipynb #7b2e89c5
10
10
  import json
@@ -53,7 +53,7 @@ from cjm_fasthtml_virtual_collection.js.scrollbar import generate_scrollbar_js
53
53
  from cjm_fasthtml_virtual_collection.js.auto_fit import generate_auto_fit_js, auto_fit_callback_name
54
54
 
55
55
  from cjm_fasthtml_keyboard_navigation.core.manager import ZoneManager
56
- from cjm_fasthtml_keyboard_navigation.components.system import render_keyboard_system
56
+ from cjm_fasthtml_keyboard_navigation.components.system import render_keyboard_system, KeyboardSystem
57
57
 
58
58
  from cjm_fasthtml_viewport_fit.models import ViewportFitConfig
59
59
  from cjm_fasthtml_viewport_fit.components import render_viewport_fit_script
@@ -306,10 +306,52 @@ _SB_VC_WRAPPER_ID = "sb-vc-wrapper"
306
306
  SB_SYSTEM_ID = "sb-collection"
307
307
 
308
308
 
309
+ def build_source_browser_keyboard_system(
310
+ sb_state: Any, # SourceBrowserRouterState (carries vc_ids, vc_btn_ids, vc_urls)
311
+ manager_label: Optional[str] = None, # Human-readable label for the ZoneManager (consumed by render_keyboard_hints_modal section header when this is a child_managers entry)
312
+ ) -> KeyboardSystem: # Complete rendered KeyboardSystem (carries .manager for hierarchical hints handoff)
313
+ """Build the source browser's KeyboardSystem standalone.
314
+
315
+ Lifts the manager construction out of `_render_source_browser_vc_content`
316
+ so the underlying `ZoneManager` is accessible via `KeyboardSystem.manager`
317
+ for hierarchical hints handoff. Consumers wiring this as a child of the
318
+ parent selection-step manager pass the manager to
319
+ `render_keyboard_hints_modal(..., child_managers=[...])` so the modal's
320
+ section header reads as `manager_label` instead of the technical SB_SYSTEM_ID.
321
+ """
322
+ vc_ids = sb_state.ids
323
+ vc_btn_ids = sb_state.btn_ids
324
+ vc_urls = sb_state.urls
325
+
326
+ zone = create_collection_focus_zone(vc_ids)
327
+ nav_actions = create_collection_nav_actions(zone.id, vc_btn_ids)
328
+ manager = ZoneManager(
329
+ zones=(zone,),
330
+ actions=nav_actions,
331
+ system_id=SB_SYSTEM_ID,
332
+ label=manager_label,
333
+ )
334
+ url_map = build_collection_url_map(vc_btn_ids, vc_urls)
335
+ target_map = {btn_id: f"#{vc_ids.rows}" for btn_id in url_map}
336
+ swap_map = {btn_id: "none" for btn_id in url_map}
337
+ kb_system = render_keyboard_system(
338
+ manager, url_map=url_map, target_map=target_map, swap_map=swap_map,
339
+ )
340
+ apply_nav_sync(kb_system, vc_ids)
341
+ return kb_system
342
+
343
+
309
344
  def _render_source_browser_vc_content(
310
- sb_state: Any, # SourceBrowserRouterState from routes.source_browser
345
+ sb_state: Any, # SourceBrowserRouterState from routes.source_browser
346
+ keyboard_system: Optional[KeyboardSystem] = None, # Pre-built keyboard system (when None, builds internally via build_source_browser_keyboard_system)
311
347
  ) -> Any: # VC content wrapper (without search/grouping header)
312
- """Render the VC content portion of the source browser."""
348
+ """Render the VC content portion of the source browser.
349
+
350
+ When `keyboard_system` is provided, the caller has built the system upstream
351
+ (typically `init_source_browser_router`) and owns the underlying ZoneManager.
352
+ When None, builds the system internally — preserves pre-C1 behavior for any
353
+ caller that doesn't need the manager handoff.
354
+ """
313
355
  vc_ids = sb_state.ids
314
356
  vc_btn_ids = sb_state.btn_ids
315
357
  vc_config = sb_state.config
@@ -323,15 +365,8 @@ def _render_source_browser_vc_content(
323
365
  render_empty=render_source_empty,
324
366
  )
325
367
 
326
- # Keyboard system for VC navigation
327
- zone = create_collection_focus_zone(vc_ids)
328
- nav_actions = create_collection_nav_actions(zone.id, vc_btn_ids)
329
- manager = ZoneManager(zones=(zone,), actions=nav_actions, system_id=SB_SYSTEM_ID)
330
- url_map = build_collection_url_map(vc_btn_ids, vc_urls)
331
- target_map = {btn_id: f"#{vc_ids.rows}" for btn_id in url_map}
332
- swap_map = {btn_id: "none" for btn_id in url_map}
333
- kb_system = render_keyboard_system(manager, url_map=url_map, target_map=target_map, swap_map=swap_map)
334
- apply_nav_sync(kb_system, vc_ids)
368
+ # Keyboard system use caller-supplied or build internally.
369
+ kb_system = keyboard_system if keyboard_system is not None else build_source_browser_keyboard_system(sb_state)
335
370
 
336
371
  # Auto-fit JS
337
372
  auto_fit_js = generate_auto_fit_js(
@@ -372,13 +407,14 @@ def _render_source_browser_vc_content(
372
407
 
373
408
 
374
409
  def _render_source_browser_vc(
375
- sb_state: Any, # SourceBrowserRouterState from routes.source_browser
376
- filter_url: str = "", # URL for filtering sources
377
- grouping_mode: str = "media_path", # Current grouping mode
378
- grouping_change_url: str = "", # URL for changing grouping mode
410
+ sb_state: Any, # SourceBrowserRouterState from routes.source_browser
411
+ filter_url: str = "", # URL for filtering sources
412
+ grouping_mode: str = "media_path", # Current grouping mode
413
+ grouping_change_url: str = "", # URL for changing grouping mode
414
+ keyboard_system: Optional[KeyboardSystem] = None, # Pre-built keyboard system, threaded through to VC content
379
415
  ) -> Any: # Source browser component with virtual collection
380
416
  """Render the full source browser panel (header + VC content)."""
381
- vc_content = _render_source_browser_vc_content(sb_state)
417
+ vc_content = _render_source_browser_vc_content(sb_state, keyboard_system=keyboard_system)
382
418
 
383
419
  return Div(
384
420
  # Header bar with search and grouping selector