cjm-transcript-source-select 0.0.15__tar.gz → 0.0.17__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.15/cjm_transcript_source_select.egg-info → cjm_transcript_source_select-0.0.17}/PKG-INFO +40 -35
  2. {cjm_transcript_source_select-0.0.15 → cjm_transcript_source_select-0.0.17}/README.md +39 -34
  3. cjm_transcript_source_select-0.0.17/cjm_transcript_source_select/__init__.py +1 -0
  4. {cjm_transcript_source_select-0.0.15 → cjm_transcript_source_select-0.0.17}/cjm_transcript_source_select/_modidx.py +2 -2
  5. {cjm_transcript_source_select-0.0.15 → cjm_transcript_source_select-0.0.17}/cjm_transcript_source_select/components/step_renderer.py +18 -38
  6. {cjm_transcript_source_select-0.0.15 → cjm_transcript_source_select-0.0.17}/cjm_transcript_source_select/models.py +6 -0
  7. {cjm_transcript_source_select-0.0.15 → cjm_transcript_source_select-0.0.17}/cjm_transcript_source_select/routes/init.py +1 -0
  8. {cjm_transcript_source_select-0.0.15 → cjm_transcript_source_select-0.0.17}/cjm_transcript_source_select/routes/local_files.py +70 -8
  9. {cjm_transcript_source_select-0.0.15 → cjm_transcript_source_select-0.0.17/cjm_transcript_source_select.egg-info}/PKG-INFO +40 -35
  10. {cjm_transcript_source_select-0.0.15 → cjm_transcript_source_select-0.0.17}/settings.ini +1 -1
  11. cjm_transcript_source_select-0.0.15/cjm_transcript_source_select/__init__.py +0 -1
  12. {cjm_transcript_source_select-0.0.15 → cjm_transcript_source_select-0.0.17}/LICENSE +0 -0
  13. {cjm_transcript_source_select-0.0.15 → cjm_transcript_source_select-0.0.17}/MANIFEST.in +0 -0
  14. {cjm_transcript_source_select-0.0.15 → cjm_transcript_source_select-0.0.17}/cjm_transcript_source_select/components/__init__.py +0 -0
  15. {cjm_transcript_source_select-0.0.15 → cjm_transcript_source_select-0.0.17}/cjm_transcript_source_select/components/helpers.py +0 -0
  16. {cjm_transcript_source_select-0.0.15 → cjm_transcript_source_select-0.0.17}/cjm_transcript_source_select/components/local_files.py +0 -0
  17. {cjm_transcript_source_select-0.0.15 → cjm_transcript_source_select-0.0.17}/cjm_transcript_source_select/components/preview_panel.py +0 -0
  18. {cjm_transcript_source_select-0.0.15 → cjm_transcript_source_select-0.0.17}/cjm_transcript_source_select/components/selection_queue.py +0 -0
  19. {cjm_transcript_source_select-0.0.15 → cjm_transcript_source_select-0.0.17}/cjm_transcript_source_select/components/source_browser.py +0 -0
  20. {cjm_transcript_source_select-0.0.15 → cjm_transcript_source_select-0.0.17}/cjm_transcript_source_select/html_ids.py +0 -0
  21. {cjm_transcript_source_select-0.0.15 → cjm_transcript_source_select-0.0.17}/cjm_transcript_source_select/routes/__init__.py +0 -0
  22. {cjm_transcript_source_select-0.0.15 → cjm_transcript_source_select-0.0.17}/cjm_transcript_source_select/routes/core.py +0 -0
  23. {cjm_transcript_source_select-0.0.15 → cjm_transcript_source_select-0.0.17}/cjm_transcript_source_select/routes/filtering.py +0 -0
  24. {cjm_transcript_source_select-0.0.15 → cjm_transcript_source_select-0.0.17}/cjm_transcript_source_select/routes/queue.py +0 -0
  25. {cjm_transcript_source_select-0.0.15 → cjm_transcript_source_select-0.0.17}/cjm_transcript_source_select/routes/source_browser.py +0 -0
  26. {cjm_transcript_source_select-0.0.15 → cjm_transcript_source_select-0.0.17}/cjm_transcript_source_select/routes/tabs.py +0 -0
  27. {cjm_transcript_source_select-0.0.15 → cjm_transcript_source_select-0.0.17}/cjm_transcript_source_select/services/__init__.py +0 -0
  28. {cjm_transcript_source_select-0.0.15 → cjm_transcript_source_select-0.0.17}/cjm_transcript_source_select/services/source.py +0 -0
  29. {cjm_transcript_source_select-0.0.15 → cjm_transcript_source_select-0.0.17}/cjm_transcript_source_select/services/source_utils.py +0 -0
  30. {cjm_transcript_source_select-0.0.15 → cjm_transcript_source_select-0.0.17}/cjm_transcript_source_select/utils.py +0 -0
  31. {cjm_transcript_source_select-0.0.15 → cjm_transcript_source_select-0.0.17}/cjm_transcript_source_select.egg-info/SOURCES.txt +0 -0
  32. {cjm_transcript_source_select-0.0.15 → cjm_transcript_source_select-0.0.17}/cjm_transcript_source_select.egg-info/dependency_links.txt +0 -0
  33. {cjm_transcript_source_select-0.0.15 → cjm_transcript_source_select-0.0.17}/cjm_transcript_source_select.egg-info/entry_points.txt +0 -0
  34. {cjm_transcript_source_select-0.0.15 → cjm_transcript_source_select-0.0.17}/cjm_transcript_source_select.egg-info/not-zip-safe +0 -0
  35. {cjm_transcript_source_select-0.0.15 → cjm_transcript_source_select-0.0.17}/cjm_transcript_source_select.egg-info/requires.txt +0 -0
  36. {cjm_transcript_source_select-0.0.15 → cjm_transcript_source_select-0.0.17}/cjm_transcript_source_select.egg-info/top_level.txt +0 -0
  37. {cjm_transcript_source_select-0.0.15 → cjm_transcript_source_select-0.0.17}/pyproject.toml +0 -0
  38. {cjm_transcript_source_select-0.0.15 → cjm_transcript_source_select-0.0.17}/setup.cfg +0 -0
  39. {cjm_transcript_source_select-0.0.15 → cjm_transcript_source_select-0.0.17}/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.15
3
+ Version: 0.0.17
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
@@ -109,53 +109,53 @@ graph LR
109
109
  components_local_files --> html_ids
110
110
  components_local_files --> components_helpers
111
111
  components_preview_panel --> html_ids
112
- components_source_browser --> services_source_utils
113
112
  components_source_browser --> utils
113
+ components_source_browser --> services_source_utils
114
114
  components_source_browser --> html_ids
115
- components_step_renderer --> components_preview_panel
116
- components_step_renderer --> components_selection_queue
117
- components_step_renderer --> utils
118
- components_step_renderer --> components_local_files
119
115
  components_step_renderer --> html_ids
120
- components_step_renderer --> models
121
116
  components_step_renderer --> components_source_browser
117
+ components_step_renderer --> components_local_files
118
+ components_step_renderer --> components_selection_queue
119
+ components_step_renderer --> models
120
+ components_step_renderer --> components_preview_panel
121
+ components_step_renderer --> utils
122
+ routes_core --> components_step_renderer
122
123
  routes_core --> models
123
- routes_core --> services_source
124
124
  routes_core --> components_selection_queue
125
- routes_core --> components_step_renderer
125
+ routes_core --> services_source
126
126
  routes_core --> html_ids
127
- routes_filtering --> routes_core
128
127
  routes_filtering --> services_source_utils
129
- routes_filtering --> services_source
128
+ routes_filtering --> routes_core
130
129
  routes_filtering --> models
131
- routes_init --> routes_core
130
+ routes_filtering --> services_source
132
131
  routes_init --> routes_queue
132
+ routes_init --> routes_core
133
133
  routes_init --> routes_tabs
134
- routes_init --> routes_filtering
135
- routes_init --> services_source
136
- routes_init --> routes_source_browser
137
134
  routes_init --> routes_local_files
138
135
  routes_init --> models
139
- routes_local_files --> models
140
- routes_local_files --> routes_core
136
+ routes_init --> services_source
137
+ routes_init --> routes_source_browser
138
+ routes_init --> routes_filtering
141
139
  routes_local_files --> components_local_files
140
+ routes_local_files --> models
142
141
  routes_local_files --> services_source
143
- routes_queue --> routes_core
142
+ routes_local_files --> routes_core
144
143
  routes_queue --> services_source_utils
144
+ routes_queue --> routes_core
145
+ routes_queue --> models
145
146
  routes_queue --> components_preview_panel
146
147
  routes_queue --> services_source
147
- routes_queue --> models
148
+ routes_source_browser --> html_ids
148
149
  routes_source_browser --> components_source_browser
149
- routes_source_browser --> components_preview_panel
150
- routes_source_browser --> services_source_utils
151
- routes_source_browser --> services_source
152
150
  routes_source_browser --> routes_core
153
- routes_source_browser --> html_ids
154
151
  routes_source_browser --> models
152
+ routes_source_browser --> components_preview_panel
153
+ routes_source_browser --> services_source
154
+ routes_source_browser --> services_source_utils
155
+ routes_tabs --> routes_core
155
156
  routes_tabs --> models
156
157
  routes_tabs --> components_step_renderer
157
158
  routes_tabs --> services_source
158
- routes_tabs --> routes_core
159
159
  routes_tabs --> services_source_utils
160
160
  ```
161
161
 
@@ -528,8 +528,9 @@ def _handle_remove_external_source(
528
528
  external_db_paths_ref: List[str], # Shared external paths list (mutated in place)
529
529
  fb_routers: FileBrowserRouters, # File browser routers (for targeted OOB)
530
530
  remove_url: str, # URL for remove button in external sources list
531
- ): # Tuple of OOB elements (external sources list + checkbox cells)
532
- "Remove an external database source from the Added Sources list."
531
+ urls: SelectionUrls, # Full URL bundle for queue re-rendering
532
+ ): # Tuple of OOB elements (external sources list + checkbox cells + queue + stats)
533
+ "Remove an external database source and clean up orphaned queue items."
533
534
  ```
534
535
 
535
536
  ``` python
@@ -539,7 +540,7 @@ def init_local_files_router(
539
540
  source_service: SourceService, # The source service for external db ops
540
541
  prefix: str, # Route prefix (e.g., "/workflow/selection/local_files")
541
542
  urls: SelectionUrls, # URL bundle for rendering
542
- ) -> LocalFilesResult: # Router result with routers, routes, render, and restore
543
+ ) -> LocalFilesResult: # Router result with routers, routes, render, restore, and reset
543
544
  "Initialize local files browser routes with new file browser API."
544
545
  ```
545
546
 
@@ -571,9 +572,18 @@ def _no_op_restore(session_id: str) -> None:
571
572
  """Default no-op for restore_state."""
572
573
  pass
573
574
 
575
+ def _no_op_reset() -> None
576
+ "Default no-op for restore_state."
577
+ ```
578
+
579
+ ``` python
580
+ def _no_op_reset() -> None:
581
+ """Default no-op for reset_state."""
582
+ pass
583
+
574
584
  @dataclass
575
585
  class LocalFilesResult
576
- "Default no-op for restore_state."
586
+ "Default no-op for reset_state."
577
587
  ```
578
588
 
579
589
  #### Classes
@@ -614,6 +624,7 @@ class LocalFilesResult:
614
624
  routes: Dict[str, Callable] # Named route handlers
615
625
  render_panel: Callable # (error_message?, session_id?) -> rendered panel
616
626
  restore_state: Callable = field(...) # (session_id) -> None, restore persisted state
627
+ reset_state: Callable = field(...) # () -> None, reset in-memory caches
617
628
  ```
618
629
 
619
630
  ``` python
@@ -627,6 +638,7 @@ class SelectionResult:
627
638
  render_local_files_panel: Optional[Callable] # Render fn for local files tab
628
639
  sb_state: Any # SourceBrowserRouterState
629
640
  restore_state: Callable = field(...) # (session_id) -> None, restore persisted state
641
+ reset_state: Callable = field(...) # () -> None, reset in-memory caches
630
642
  ```
631
643
 
632
644
  ### preview_panel (`preview_panel.ipynb`)
@@ -1382,13 +1394,6 @@ def _create_parent_keyboard_manager() -> ZoneManager: # Parent keyboard manager
1382
1394
  "Create the parent keyboard manager with two ghost zones for column switching."
1383
1395
  ```
1384
1396
 
1385
- ``` python
1386
- def _render_selection_keyboard_hints(
1387
- manager: ZoneManager, # Keyboard zone manager with actions configured
1388
- ) -> Any: # Collapsible keyboard hints component
1389
- "Render keyboard shortcut hints in a collapsible container."
1390
- ```
1391
-
1392
1397
  ``` python
1393
1398
  def _render_selection_stats(
1394
1399
  selected_sources: List[Dict[str, str]], # Selected sources
@@ -63,53 +63,53 @@ graph LR
63
63
  components_local_files --> html_ids
64
64
  components_local_files --> components_helpers
65
65
  components_preview_panel --> html_ids
66
- components_source_browser --> services_source_utils
67
66
  components_source_browser --> utils
67
+ components_source_browser --> services_source_utils
68
68
  components_source_browser --> html_ids
69
- components_step_renderer --> components_preview_panel
70
- components_step_renderer --> components_selection_queue
71
- components_step_renderer --> utils
72
- components_step_renderer --> components_local_files
73
69
  components_step_renderer --> html_ids
74
- components_step_renderer --> models
75
70
  components_step_renderer --> components_source_browser
71
+ components_step_renderer --> components_local_files
72
+ components_step_renderer --> components_selection_queue
73
+ components_step_renderer --> models
74
+ components_step_renderer --> components_preview_panel
75
+ components_step_renderer --> utils
76
+ routes_core --> components_step_renderer
76
77
  routes_core --> models
77
- routes_core --> services_source
78
78
  routes_core --> components_selection_queue
79
- routes_core --> components_step_renderer
79
+ routes_core --> services_source
80
80
  routes_core --> html_ids
81
- routes_filtering --> routes_core
82
81
  routes_filtering --> services_source_utils
83
- routes_filtering --> services_source
82
+ routes_filtering --> routes_core
84
83
  routes_filtering --> models
85
- routes_init --> routes_core
84
+ routes_filtering --> services_source
86
85
  routes_init --> routes_queue
86
+ routes_init --> routes_core
87
87
  routes_init --> routes_tabs
88
- routes_init --> routes_filtering
89
- routes_init --> services_source
90
- routes_init --> routes_source_browser
91
88
  routes_init --> routes_local_files
92
89
  routes_init --> models
93
- routes_local_files --> models
94
- routes_local_files --> routes_core
90
+ routes_init --> services_source
91
+ routes_init --> routes_source_browser
92
+ routes_init --> routes_filtering
95
93
  routes_local_files --> components_local_files
94
+ routes_local_files --> models
96
95
  routes_local_files --> services_source
97
- routes_queue --> routes_core
96
+ routes_local_files --> routes_core
98
97
  routes_queue --> services_source_utils
98
+ routes_queue --> routes_core
99
+ routes_queue --> models
99
100
  routes_queue --> components_preview_panel
100
101
  routes_queue --> services_source
101
- routes_queue --> models
102
+ routes_source_browser --> html_ids
102
103
  routes_source_browser --> components_source_browser
103
- routes_source_browser --> components_preview_panel
104
- routes_source_browser --> services_source_utils
105
- routes_source_browser --> services_source
106
104
  routes_source_browser --> routes_core
107
- routes_source_browser --> html_ids
108
105
  routes_source_browser --> models
106
+ routes_source_browser --> components_preview_panel
107
+ routes_source_browser --> services_source
108
+ routes_source_browser --> services_source_utils
109
+ routes_tabs --> routes_core
109
110
  routes_tabs --> models
110
111
  routes_tabs --> components_step_renderer
111
112
  routes_tabs --> services_source
112
- routes_tabs --> routes_core
113
113
  routes_tabs --> services_source_utils
114
114
  ```
115
115
 
@@ -482,8 +482,9 @@ def _handle_remove_external_source(
482
482
  external_db_paths_ref: List[str], # Shared external paths list (mutated in place)
483
483
  fb_routers: FileBrowserRouters, # File browser routers (for targeted OOB)
484
484
  remove_url: str, # URL for remove button in external sources list
485
- ): # Tuple of OOB elements (external sources list + checkbox cells)
486
- "Remove an external database source from the Added Sources list."
485
+ urls: SelectionUrls, # Full URL bundle for queue re-rendering
486
+ ): # Tuple of OOB elements (external sources list + checkbox cells + queue + stats)
487
+ "Remove an external database source and clean up orphaned queue items."
487
488
  ```
488
489
 
489
490
  ``` python
@@ -493,7 +494,7 @@ def init_local_files_router(
493
494
  source_service: SourceService, # The source service for external db ops
494
495
  prefix: str, # Route prefix (e.g., "/workflow/selection/local_files")
495
496
  urls: SelectionUrls, # URL bundle for rendering
496
- ) -> LocalFilesResult: # Router result with routers, routes, render, and restore
497
+ ) -> LocalFilesResult: # Router result with routers, routes, render, restore, and reset
497
498
  "Initialize local files browser routes with new file browser API."
498
499
  ```
499
500
 
@@ -525,9 +526,18 @@ def _no_op_restore(session_id: str) -> None:
525
526
  """Default no-op for restore_state."""
526
527
  pass
527
528
 
529
+ def _no_op_reset() -> None
530
+ "Default no-op for restore_state."
531
+ ```
532
+
533
+ ``` python
534
+ def _no_op_reset() -> None:
535
+ """Default no-op for reset_state."""
536
+ pass
537
+
528
538
  @dataclass
529
539
  class LocalFilesResult
530
- "Default no-op for restore_state."
540
+ "Default no-op for reset_state."
531
541
  ```
532
542
 
533
543
  #### Classes
@@ -568,6 +578,7 @@ class LocalFilesResult:
568
578
  routes: Dict[str, Callable] # Named route handlers
569
579
  render_panel: Callable # (error_message?, session_id?) -> rendered panel
570
580
  restore_state: Callable = field(...) # (session_id) -> None, restore persisted state
581
+ reset_state: Callable = field(...) # () -> None, reset in-memory caches
571
582
  ```
572
583
 
573
584
  ``` python
@@ -581,6 +592,7 @@ class SelectionResult:
581
592
  render_local_files_panel: Optional[Callable] # Render fn for local files tab
582
593
  sb_state: Any # SourceBrowserRouterState
583
594
  restore_state: Callable = field(...) # (session_id) -> None, restore persisted state
595
+ reset_state: Callable = field(...) # () -> None, reset in-memory caches
584
596
  ```
585
597
 
586
598
  ### preview_panel (`preview_panel.ipynb`)
@@ -1336,13 +1348,6 @@ def _create_parent_keyboard_manager() -> ZoneManager: # Parent keyboard manager
1336
1348
  "Create the parent keyboard manager with two ghost zones for column switching."
1337
1349
  ```
1338
1350
 
1339
- ``` python
1340
- def _render_selection_keyboard_hints(
1341
- manager: ZoneManager, # Keyboard zone manager with actions configured
1342
- ) -> Any: # Collapsible keyboard hints component
1343
- "Render keyboard shortcut hints in a collapsible container."
1344
- ```
1345
-
1346
1351
  ``` python
1347
1352
  def _render_selection_stats(
1348
1353
  selected_sources: List[Dict[str, str]], # Selected sources
@@ -0,0 +1 @@
1
+ __version__ = "0.0.17"
@@ -59,8 +59,6 @@ d = { 'settings': { 'branch': 'main',
59
59
  'cjm_transcript_source_select/components/step_renderer.py'),
60
60
  'cjm_transcript_source_select.components.step_renderer._render_selection_footer': ( 'components/step_renderer.html#_render_selection_footer',
61
61
  'cjm_transcript_source_select/components/step_renderer.py'),
62
- 'cjm_transcript_source_select.components.step_renderer._render_selection_keyboard_hints': ( 'components/step_renderer.html#_render_selection_keyboard_hints',
63
- 'cjm_transcript_source_select/components/step_renderer.py'),
64
62
  'cjm_transcript_source_select.components.step_renderer._render_selection_stats': ( 'components/step_renderer.html#_render_selection_stats',
65
63
  'cjm_transcript_source_select/components/step_renderer.py'),
66
64
  'cjm_transcript_source_select.components.step_renderer._render_source_tabs': ( 'components/step_renderer.html#_render_source_tabs',
@@ -89,6 +87,8 @@ d = { 'settings': { 'branch': 'main',
89
87
  'cjm_transcript_source_select/models.py'),
90
88
  'cjm_transcript_source_select.models.SelectionUrls': ( 'models.html#selectionurls',
91
89
  'cjm_transcript_source_select/models.py'),
90
+ 'cjm_transcript_source_select.models._no_op_reset': ( 'models.html#_no_op_reset',
91
+ 'cjm_transcript_source_select/models.py'),
92
92
  'cjm_transcript_source_select.models._no_op_restore': ( 'models.html#_no_op_restore',
93
93
  'cjm_transcript_source_select/models.py')},
94
94
  'cjm_transcript_source_select.routes.core': { 'cjm_transcript_source_select.routes.core._build_queue_response': ( 'routes/core.html#_build_queue_response',
@@ -9,14 +9,11 @@ __all__ = ['SD_TAB_PREV_BTN', 'SD_TAB_NEXT_BTN', 'SD_PREVIEW_BTN', 'FB_SYSTEM_ID
9
9
  from typing import Any, Callable, Dict, List, Optional
10
10
 
11
11
  from fasthtml.common import (
12
- Div, H2, P, Span, Input, Script, Details, Summary
12
+ Div, H2, P, Span, Input, Script
13
13
  )
14
14
 
15
15
  # DaisyUI components
16
16
  from cjm_fasthtml_daisyui.components.data_display.badge import badge, badge_colors, badge_sizes
17
- from cjm_fasthtml_daisyui.components.data_display.collapse import (
18
- collapse, collapse_title, collapse_content, collapse_modifiers
19
- )
20
17
  from cjm_fasthtml_daisyui.components.navigation.tabs import tabs, tabs_styles, tab
21
18
  from cjm_fasthtml_daisyui.utilities.semantic_colors import bg_dui, text_dui, border_dui, ring_dui, shadow_dui
22
19
  from cjm_fasthtml_daisyui.utilities.border_radius import border_radius
@@ -49,7 +46,7 @@ from cjm_fasthtml_keyboard_navigation.core.actions import KeyAction
49
46
  from cjm_fasthtml_keyboard_navigation.core.manager import ZoneManager
50
47
  from cjm_fasthtml_keyboard_navigation.core.navigation import ScrollOnly
51
48
  from cjm_fasthtml_keyboard_navigation.components.system import render_keyboard_system
52
- from cjm_fasthtml_keyboard_navigation.components.hints import render_keyboard_hints
49
+ from cjm_fasthtml_keyboard_navigation.components.hints_modal import render_keyboard_hints_modal
53
50
 
54
51
  # Source browser system ID (for hierarchy wiring)
55
52
  from .source_browser import SB_SYSTEM_ID
@@ -148,32 +145,6 @@ def _create_parent_keyboard_manager() -> ZoneManager: # Parent keyboard manager
148
145
  state_hidden_inputs=True,
149
146
  )
150
147
 
151
- # %% ../../nbs/components/step_renderer.ipynb #52i7b5ddglt
152
- def _render_selection_keyboard_hints(
153
- manager: ZoneManager, # Keyboard zone manager with actions configured
154
- ) -> Any: # Collapsible keyboard hints component
155
- """Render keyboard shortcut hints in a collapsible container."""
156
- hints = render_keyboard_hints(
157
- manager,
158
- include_navigation=True,
159
- include_zone_switch=True,
160
- badge_style="outline",
161
- container_id="sd-keyboard-hints",
162
- use_icons=False
163
- )
164
-
165
- return Details(
166
- Summary(
167
- "Keyboard Shortcuts",
168
- cls=combine_classes(collapse_title, font_size.sm, font_weight.medium)
169
- ),
170
- Div(
171
- hints,
172
- cls=collapse_content
173
- ),
174
- cls=combine_classes(collapse, collapse_modifiers.arrow, bg_dui.base_200)
175
- )
176
-
177
148
  # %% ../../nbs/components/step_renderer.ipynb #8d397d19
178
149
  def _render_selection_stats(
179
150
  selected_sources: List[Dict[str, str]], # Selected sources
@@ -499,16 +470,21 @@ def render_selection_step(
499
470
  # Hierarchy wiring script (must come after all keyboard systems are rendered)
500
471
  hierarchy_js = _generate_hierarchy_js(active_tab)
501
472
 
473
+ # Keyboard hints modal
474
+ hints_modal, hints_trigger, hints_script = render_keyboard_hints_modal(kb_manager)
475
+
502
476
  return Div(
503
- # Header with keyboard hints
477
+ # Header with keyboard hints trigger
504
478
  Div(
505
- H2("Select Source Material", cls=combine_classes(font_size._3xl, font_weight.bold)),
506
- P(
507
- "Choose transcription sources to decompose into a structural spine.",
508
- cls=combine_classes(text_dui.base_content.opacity(70), m.b(4))
479
+ Div(
480
+ H2("Select Source Material", cls=combine_classes(font_size._3xl, font_weight.bold)),
481
+ P(
482
+ "Choose transcription sources to decompose into a structural spine.",
483
+ cls=combine_classes(text_dui.base_content.opacity(70), m.b(4))
484
+ ),
509
485
  ),
510
- _render_selection_keyboard_hints(kb_manager),
511
- cls=combine_classes(m.b(4))
486
+ hints_trigger,
487
+ cls=combine_classes(flex_display, items.start, justify.between, m.b(4))
512
488
  ),
513
489
 
514
490
  # Main content area (two columns: tabs + queue)
@@ -571,6 +547,10 @@ def render_selection_step(
571
547
  # Script runner for OOB-triggered JS (duplicate rejection flash)
572
548
  Div(id=SelectionHtmlIds.SCRIPT_RUNNER),
573
549
 
550
+ # Keyboard hints modal + ? key listener
551
+ hints_modal,
552
+ hints_script,
553
+
574
554
  id=SelectionHtmlIds.SOURCE_SELECTOR,
575
555
  cls=combine_classes(
576
556
  w.full,
@@ -59,6 +59,10 @@ def _no_op_restore(session_id: str) -> None:
59
59
  """Default no-op for restore_state."""
60
60
  pass
61
61
 
62
+ def _no_op_reset() -> None:
63
+ """Default no-op for reset_state."""
64
+ pass
65
+
62
66
  @dataclass
63
67
  class LocalFilesResult:
64
68
  """Return type from init_local_files_router."""
@@ -66,6 +70,7 @@ class LocalFilesResult:
66
70
  routes: Dict[str, Callable] # Named route handlers
67
71
  render_panel: Callable # (error_message?, session_id?) -> rendered panel
68
72
  restore_state: Callable = field(default=_no_op_restore) # (session_id) -> None, restore persisted state
73
+ reset_state: Callable = field(default=_no_op_reset) # () -> None, reset in-memory caches
69
74
 
70
75
  @dataclass
71
76
  class SelectionResult:
@@ -76,3 +81,4 @@ class SelectionResult:
76
81
  render_local_files_panel: Optional[Callable] = None # Render fn for local files tab
77
82
  sb_state: Any = None # SourceBrowserRouterState
78
83
  restore_state: Callable = field(default=_no_op_restore) # (session_id) -> None, restore persisted state
84
+ reset_state: Callable = field(default=_no_op_reset) # () -> None, reset in-memory caches
@@ -127,4 +127,5 @@ def init_selection_routers(
127
127
  render_local_files_panel=local_files.render_panel,
128
128
  sb_state=sb_state,
129
129
  restore_state=local_files.restore_state,
130
+ reset_state=local_files.reset_state,
130
131
  )
@@ -19,7 +19,8 @@ from cjm_fasthtml_interactions.core.state_store import get_session_id
19
19
 
20
20
  from ..models import SelectionUrls, LocalFilesResult
21
21
  from cjm_transcript_source_select.routes.core import (
22
- WorkflowStateStore, _get_step_state, _update_step_state
22
+ WorkflowStateStore, _get_step_state, _update_step_state,
23
+ _build_queue_response,
23
24
  )
24
25
  from cjm_transcript_source_select.components.local_files import (
25
26
  _render_local_files_browser,
@@ -54,12 +55,15 @@ def _handle_remove_external_source(
54
55
  external_db_paths_ref: List[str], # Shared external paths list (mutated in place)
55
56
  fb_routers: FileBrowserRouters, # File browser routers (for targeted OOB)
56
57
  remove_url: str, # URL for remove button in external sources list
57
- ): # Tuple of OOB elements (external sources list + checkbox cells)
58
- """Remove an external database source from the Added Sources list."""
58
+ urls: SelectionUrls, # Full URL bundle for queue re-rendering
59
+ ): # Tuple of OOB elements (external sources list + checkbox cells + queue + stats)
60
+ """Remove an external database source and clean up orphaned queue items."""
59
61
  session_id = get_session_id(sess)
60
62
 
61
63
  # Remove the path if it exists
64
+ removed_provider_id = None
62
65
  if db_path in external_db_paths_ref:
66
+ removed_provider_id = f"external:{db_path}"
63
67
  external_db_paths_ref.remove(db_path)
64
68
  _update_step_state(state_store, workflow_id, session_id, external_db_paths=list(external_db_paths_ref))
65
69
  source_service.set_external_paths(list(external_db_paths_ref))
@@ -69,7 +73,28 @@ def _handle_remove_external_source(
69
73
  external_list_oob = _render_external_sources_list(
70
74
  list(external_db_paths_ref), remove_url, oob=True,
71
75
  )
72
- return (external_list_oob, *checkbox_oobs)
76
+
77
+ parts = [external_list_oob, *checkbox_oobs]
78
+
79
+ # Remove orphaned queue items from the deleted provider
80
+ if removed_provider_id is not None:
81
+ step_state = _get_step_state(state_store, workflow_id, session_id)
82
+ selected_sources = step_state.get("selected_sources", [])
83
+ filtered = [s for s in selected_sources if s.get("provider_id") != removed_provider_id]
84
+ if len(filtered) != len(selected_sources):
85
+ _update_step_state(state_store, workflow_id, session_id, selected_sources=filtered)
86
+ # Re-render queue + stats as OOB
87
+ queue_response = _build_queue_response(
88
+ state_store, workflow_id, source_service, session_id,
89
+ filtered, urls, include_checkbox_oobs=False,
90
+ )
91
+ queue_parts = queue_response if isinstance(queue_response, tuple) else (queue_response,)
92
+ for part in queue_parts:
93
+ if hasattr(part, 'attrs') and 'hx-swap-oob' not in part.attrs and 'id' in part.attrs:
94
+ part.attrs['hx-swap-oob'] = 'outerHTML'
95
+ parts.extend(queue_parts)
96
+
97
+ return tuple(parts)
73
98
 
74
99
  # %% ../../nbs/routes/local_files.ipynb #rdrpw4jrxpc
75
100
  def init_local_files_router(
@@ -78,7 +103,7 @@ def init_local_files_router(
78
103
  source_service: SourceService, # The source service for external db ops
79
104
  prefix: str, # Route prefix (e.g., "/workflow/selection/local_files")
80
105
  urls: SelectionUrls, # URL bundle for rendering
81
- ) -> LocalFilesResult: # Router result with routers, routes, render, and restore
106
+ ) -> LocalFilesResult: # Router result with routers, routes, render, restore, and reset
82
107
  """Initialize local files browser routes with new file browser API."""
83
108
  provider = _get_local_files_provider()
84
109
  config = _create_db_browser_config()
@@ -156,8 +181,10 @@ def init_local_files_router(
156
181
  if request is not None and not _state_restored[0]:
157
182
  _restore_from_persisted(get_session_id(request.session))
158
183
 
159
- # Validate any newly added paths
184
+ # Detect removed paths before validation
160
185
  old_set = set(_external_db_paths)
186
+
187
+ # Validate any newly added paths
161
188
  validated_paths = []
162
189
  error_message = None
163
190
  for path in selected_paths:
@@ -185,10 +212,32 @@ def init_local_files_router(
185
212
  external_db_paths=list(_external_db_paths),
186
213
  )
187
214
 
188
- return (
215
+ parts = [
189
216
  _render_external_sources_list(list(_external_db_paths), urls.remove_external, oob=True),
190
217
  _render_error_alert(error_message, oob=True),
191
- )
218
+ ]
219
+
220
+ # Clean up orphaned queue items for removed external DBs
221
+ removed_paths = old_set - set(validated_paths)
222
+ if removed_paths and request is not None:
223
+ session_id = get_session_id(request.session)
224
+ removed_provider_ids = {f"external:{p}" for p in removed_paths}
225
+ step_state = _get_step_state(state_store, workflow_id, session_id)
226
+ selected_sources = step_state.get("selected_sources", [])
227
+ filtered = [s for s in selected_sources if s.get("provider_id") not in removed_provider_ids]
228
+ if len(filtered) != len(selected_sources):
229
+ _update_step_state(state_store, workflow_id, session_id, selected_sources=filtered)
230
+ queue_response = _build_queue_response(
231
+ state_store, workflow_id, source_service, session_id,
232
+ filtered, urls, include_checkbox_oobs=False,
233
+ )
234
+ queue_parts = queue_response if isinstance(queue_response, tuple) else (queue_response,)
235
+ for part in queue_parts:
236
+ if hasattr(part, 'attrs') and 'hx-swap-oob' not in part.attrs and 'id' in part.attrs:
237
+ part.attrs['hx-swap-oob'] = 'outerHTML'
238
+ parts.extend(queue_parts)
239
+
240
+ return tuple(parts)
192
241
 
193
242
  fb_callbacks = FileBrowserCallbacks(
194
243
  validate_selection=_validate_selection,
@@ -226,6 +275,17 @@ def init_local_files_router(
226
275
  _restore_from_persisted(session_id)
227
276
  fb_routers.sync_items()
228
277
 
278
+ # --- Reset callable for clearing in-memory caches ---
279
+ def _reset_state() -> None:
280
+ """Reset in-memory state so next restore re-reads from DB."""
281
+ _external_db_paths[:] = []
282
+ _state_restored[0] = False
283
+ _browser_state.current_path = home_path
284
+ _browser_state.sort_by = "name"
285
+ _browser_state.sort_descending = False
286
+ _browser_state.selection.selected_paths = []
287
+ source_service.set_external_paths([])
288
+
229
289
  # --- Custom router for remove_external (needs session context) ---
230
290
  custom_router = APIRouter(prefix=prefix)
231
291
 
@@ -238,6 +298,7 @@ def init_local_files_router(
238
298
  external_db_paths_ref=_external_db_paths,
239
299
  fb_routers=fb_routers,
240
300
  remove_url=urls.remove_external,
301
+ urls=urls,
241
302
  )
242
303
 
243
304
  # All routers to register
@@ -252,4 +313,5 @@ def init_local_files_router(
252
313
  routes=routes,
253
314
  render_panel=_render_panel,
254
315
  restore_state=_restore_state,
316
+ reset_state=_reset_state,
255
317
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: cjm-transcript-source-select
3
- Version: 0.0.15
3
+ Version: 0.0.17
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
@@ -109,53 +109,53 @@ graph LR
109
109
  components_local_files --> html_ids
110
110
  components_local_files --> components_helpers
111
111
  components_preview_panel --> html_ids
112
- components_source_browser --> services_source_utils
113
112
  components_source_browser --> utils
113
+ components_source_browser --> services_source_utils
114
114
  components_source_browser --> html_ids
115
- components_step_renderer --> components_preview_panel
116
- components_step_renderer --> components_selection_queue
117
- components_step_renderer --> utils
118
- components_step_renderer --> components_local_files
119
115
  components_step_renderer --> html_ids
120
- components_step_renderer --> models
121
116
  components_step_renderer --> components_source_browser
117
+ components_step_renderer --> components_local_files
118
+ components_step_renderer --> components_selection_queue
119
+ components_step_renderer --> models
120
+ components_step_renderer --> components_preview_panel
121
+ components_step_renderer --> utils
122
+ routes_core --> components_step_renderer
122
123
  routes_core --> models
123
- routes_core --> services_source
124
124
  routes_core --> components_selection_queue
125
- routes_core --> components_step_renderer
125
+ routes_core --> services_source
126
126
  routes_core --> html_ids
127
- routes_filtering --> routes_core
128
127
  routes_filtering --> services_source_utils
129
- routes_filtering --> services_source
128
+ routes_filtering --> routes_core
130
129
  routes_filtering --> models
131
- routes_init --> routes_core
130
+ routes_filtering --> services_source
132
131
  routes_init --> routes_queue
132
+ routes_init --> routes_core
133
133
  routes_init --> routes_tabs
134
- routes_init --> routes_filtering
135
- routes_init --> services_source
136
- routes_init --> routes_source_browser
137
134
  routes_init --> routes_local_files
138
135
  routes_init --> models
139
- routes_local_files --> models
140
- routes_local_files --> routes_core
136
+ routes_init --> services_source
137
+ routes_init --> routes_source_browser
138
+ routes_init --> routes_filtering
141
139
  routes_local_files --> components_local_files
140
+ routes_local_files --> models
142
141
  routes_local_files --> services_source
143
- routes_queue --> routes_core
142
+ routes_local_files --> routes_core
144
143
  routes_queue --> services_source_utils
144
+ routes_queue --> routes_core
145
+ routes_queue --> models
145
146
  routes_queue --> components_preview_panel
146
147
  routes_queue --> services_source
147
- routes_queue --> models
148
+ routes_source_browser --> html_ids
148
149
  routes_source_browser --> components_source_browser
149
- routes_source_browser --> components_preview_panel
150
- routes_source_browser --> services_source_utils
151
- routes_source_browser --> services_source
152
150
  routes_source_browser --> routes_core
153
- routes_source_browser --> html_ids
154
151
  routes_source_browser --> models
152
+ routes_source_browser --> components_preview_panel
153
+ routes_source_browser --> services_source
154
+ routes_source_browser --> services_source_utils
155
+ routes_tabs --> routes_core
155
156
  routes_tabs --> models
156
157
  routes_tabs --> components_step_renderer
157
158
  routes_tabs --> services_source
158
- routes_tabs --> routes_core
159
159
  routes_tabs --> services_source_utils
160
160
  ```
161
161
 
@@ -528,8 +528,9 @@ def _handle_remove_external_source(
528
528
  external_db_paths_ref: List[str], # Shared external paths list (mutated in place)
529
529
  fb_routers: FileBrowserRouters, # File browser routers (for targeted OOB)
530
530
  remove_url: str, # URL for remove button in external sources list
531
- ): # Tuple of OOB elements (external sources list + checkbox cells)
532
- "Remove an external database source from the Added Sources list."
531
+ urls: SelectionUrls, # Full URL bundle for queue re-rendering
532
+ ): # Tuple of OOB elements (external sources list + checkbox cells + queue + stats)
533
+ "Remove an external database source and clean up orphaned queue items."
533
534
  ```
534
535
 
535
536
  ``` python
@@ -539,7 +540,7 @@ def init_local_files_router(
539
540
  source_service: SourceService, # The source service for external db ops
540
541
  prefix: str, # Route prefix (e.g., "/workflow/selection/local_files")
541
542
  urls: SelectionUrls, # URL bundle for rendering
542
- ) -> LocalFilesResult: # Router result with routers, routes, render, and restore
543
+ ) -> LocalFilesResult: # Router result with routers, routes, render, restore, and reset
543
544
  "Initialize local files browser routes with new file browser API."
544
545
  ```
545
546
 
@@ -571,9 +572,18 @@ def _no_op_restore(session_id: str) -> None:
571
572
  """Default no-op for restore_state."""
572
573
  pass
573
574
 
575
+ def _no_op_reset() -> None
576
+ "Default no-op for restore_state."
577
+ ```
578
+
579
+ ``` python
580
+ def _no_op_reset() -> None:
581
+ """Default no-op for reset_state."""
582
+ pass
583
+
574
584
  @dataclass
575
585
  class LocalFilesResult
576
- "Default no-op for restore_state."
586
+ "Default no-op for reset_state."
577
587
  ```
578
588
 
579
589
  #### Classes
@@ -614,6 +624,7 @@ class LocalFilesResult:
614
624
  routes: Dict[str, Callable] # Named route handlers
615
625
  render_panel: Callable # (error_message?, session_id?) -> rendered panel
616
626
  restore_state: Callable = field(...) # (session_id) -> None, restore persisted state
627
+ reset_state: Callable = field(...) # () -> None, reset in-memory caches
617
628
  ```
618
629
 
619
630
  ``` python
@@ -627,6 +638,7 @@ class SelectionResult:
627
638
  render_local_files_panel: Optional[Callable] # Render fn for local files tab
628
639
  sb_state: Any # SourceBrowserRouterState
629
640
  restore_state: Callable = field(...) # (session_id) -> None, restore persisted state
641
+ reset_state: Callable = field(...) # () -> None, reset in-memory caches
630
642
  ```
631
643
 
632
644
  ### preview_panel (`preview_panel.ipynb`)
@@ -1382,13 +1394,6 @@ def _create_parent_keyboard_manager() -> ZoneManager: # Parent keyboard manager
1382
1394
  "Create the parent keyboard manager with two ghost zones for column switching."
1383
1395
  ```
1384
1396
 
1385
- ``` python
1386
- def _render_selection_keyboard_hints(
1387
- manager: ZoneManager, # Keyboard zone manager with actions configured
1388
- ) -> Any: # Collapsible keyboard hints component
1389
- "Render keyboard shortcut hints in a collapsible container."
1390
- ```
1391
-
1392
1397
  ``` python
1393
1398
  def _render_selection_stats(
1394
1399
  selected_sources: List[Dict[str, str]], # Selected sources
@@ -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.15
4
+ version = 0.0.17
5
5
  min_python = 3.12
6
6
  license = apache2
7
7
  black_formatting = False
@@ -1 +0,0 @@
1
- __version__ = "0.0.15"