cjm-transcript-segment-align 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 (30) hide show
  1. {cjm_transcript_segment_align-0.0.29/cjm_transcript_segment_align.egg-info → cjm_transcript_segment_align-0.0.31}/PKG-INFO +41 -18
  2. {cjm_transcript_segment_align-0.0.29 → cjm_transcript_segment_align-0.0.31}/README.md +36 -15
  3. cjm_transcript_segment_align-0.0.31/cjm_transcript_segment_align/__init__.py +1 -0
  4. {cjm_transcript_segment_align-0.0.29 → cjm_transcript_segment_align-0.0.31}/cjm_transcript_segment_align/components/handlers.py +32 -32
  5. {cjm_transcript_segment_align-0.0.29 → cjm_transcript_segment_align-0.0.31}/cjm_transcript_segment_align/components/step_renderer.py +36 -20
  6. {cjm_transcript_segment_align-0.0.29 → cjm_transcript_segment_align-0.0.31}/cjm_transcript_segment_align/html_ids.py +1 -0
  7. {cjm_transcript_segment_align-0.0.29 → cjm_transcript_segment_align-0.0.31}/cjm_transcript_segment_align/routes/chrome.py +25 -34
  8. {cjm_transcript_segment_align-0.0.29 → cjm_transcript_segment_align-0.0.31/cjm_transcript_segment_align.egg-info}/PKG-INFO +41 -18
  9. {cjm_transcript_segment_align-0.0.29 → cjm_transcript_segment_align-0.0.31}/cjm_transcript_segment_align.egg-info/requires.txt +4 -2
  10. {cjm_transcript_segment_align-0.0.29 → cjm_transcript_segment_align-0.0.31}/pyproject.toml +1 -1
  11. cjm_transcript_segment_align-0.0.29/cjm_transcript_segment_align/__init__.py +0 -1
  12. {cjm_transcript_segment_align-0.0.29 → cjm_transcript_segment_align-0.0.31}/LICENSE +0 -0
  13. {cjm_transcript_segment_align-0.0.29 → cjm_transcript_segment_align-0.0.31}/MANIFEST.in +0 -0
  14. {cjm_transcript_segment_align-0.0.29 → cjm_transcript_segment_align-0.0.31}/cjm_transcript_segment_align/_modidx.py +0 -0
  15. {cjm_transcript_segment_align-0.0.29 → cjm_transcript_segment_align-0.0.31}/cjm_transcript_segment_align/components/__init__.py +0 -0
  16. {cjm_transcript_segment_align-0.0.29 → cjm_transcript_segment_align-0.0.31}/cjm_transcript_segment_align/components/helpers.py +0 -0
  17. {cjm_transcript_segment_align-0.0.29 → cjm_transcript_segment_align-0.0.31}/cjm_transcript_segment_align/components/keyboard_config.py +0 -0
  18. {cjm_transcript_segment_align-0.0.29 → cjm_transcript_segment_align-0.0.31}/cjm_transcript_segment_align/components/sync_controls.py +0 -0
  19. {cjm_transcript_segment_align-0.0.29 → cjm_transcript_segment_align-0.0.31}/cjm_transcript_segment_align/components/toolbar_state.py +0 -0
  20. {cjm_transcript_segment_align-0.0.29 → cjm_transcript_segment_align-0.0.31}/cjm_transcript_segment_align/models.py +0 -0
  21. {cjm_transcript_segment_align-0.0.29 → cjm_transcript_segment_align-0.0.31}/cjm_transcript_segment_align/routes/__init__.py +0 -0
  22. {cjm_transcript_segment_align-0.0.29 → cjm_transcript_segment_align-0.0.31}/cjm_transcript_segment_align/routes/forced_alignment.py +0 -0
  23. {cjm_transcript_segment_align-0.0.29 → cjm_transcript_segment_align-0.0.31}/cjm_transcript_segment_align/routes/init.py +0 -0
  24. {cjm_transcript_segment_align-0.0.29 → cjm_transcript_segment_align-0.0.31}/cjm_transcript_segment_align/services/__init__.py +0 -0
  25. {cjm_transcript_segment_align-0.0.29 → cjm_transcript_segment_align-0.0.31}/cjm_transcript_segment_align/services/forced_alignment.py +0 -0
  26. {cjm_transcript_segment_align-0.0.29 → cjm_transcript_segment_align-0.0.31}/cjm_transcript_segment_align.egg-info/SOURCES.txt +0 -0
  27. {cjm_transcript_segment_align-0.0.29 → cjm_transcript_segment_align-0.0.31}/cjm_transcript_segment_align.egg-info/dependency_links.txt +0 -0
  28. {cjm_transcript_segment_align-0.0.29 → cjm_transcript_segment_align-0.0.31}/cjm_transcript_segment_align.egg-info/entry_points.txt +0 -0
  29. {cjm_transcript_segment_align-0.0.29 → cjm_transcript_segment_align-0.0.31}/cjm_transcript_segment_align.egg-info/top_level.txt +0 -0
  30. {cjm_transcript_segment_align-0.0.29 → cjm_transcript_segment_align-0.0.31}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cjm-transcript-segment-align
3
- Version: 0.0.29
3
+ Version: 0.0.31
4
4
  Summary: FastHTML dual-column text segmentation & VAD alignment UI for transcript decomposition workflows with forced alignment-based text splitting for aligning text segments with VAD chunks.
5
5
  Author-email: "Christian J. Mills" <9126128+cj-mills@users.noreply.github.com>
6
6
  License: Apache-2.0
@@ -15,10 +15,12 @@ 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
19
  Requires-Dist: cjm-plugin-system
19
20
  Requires-Dist: cjm_transcription_plugin_system
20
- Requires-Dist: cjm_transcript_segmentation>=0.0.20
21
- Requires-Dist: cjm_transcript_vad_align>=0.0.26
21
+ Requires-Dist: cjm_transcript_segmentation>=0.0.22
22
+ Requires-Dist: cjm_transcript_vad_align>=0.0.28
23
+ Requires-Dist: cjm_fasthtml_keyboard_navigation>=0.0.22
22
24
  Requires-Dist: cjm_fasthtml_job_monitor>=0.0.11
23
25
  Requires-Dist: cjm_fasthtml_web_audio>=0.0.11
24
26
  Requires-Dist: cjm_fasthtml_design_system>=0.0.8
@@ -75,17 +77,17 @@ graph LR
75
77
 
76
78
  components_handlers --> components_step_renderer
77
79
  components_handlers --> services_forced_alignment
78
- components_handlers --> routes_forced_alignment
79
- components_handlers --> components_keyboard_config
80
80
  components_handlers --> html_ids
81
+ components_handlers --> components_keyboard_config
81
82
  components_handlers --> components_sync_controls
82
- components_keyboard_config --> html_ids
83
+ components_handlers --> routes_forced_alignment
83
84
  components_keyboard_config --> components_sync_controls
84
- components_step_renderer --> components_helpers
85
- components_step_renderer --> components_sync_controls
85
+ components_keyboard_config --> html_ids
86
+ components_step_renderer --> html_ids
86
87
  components_step_renderer --> components_keyboard_config
88
+ components_step_renderer --> components_sync_controls
87
89
  components_step_renderer --> components_toolbar_state
88
- components_step_renderer --> html_ids
90
+ components_step_renderer --> components_helpers
89
91
  components_toolbar_state --> components_sync_controls
90
92
  routes_chrome --> components_step_renderer
91
93
  routes_chrome --> components_handlers
@@ -94,15 +96,15 @@ graph LR
94
96
  routes_forced_alignment --> components_step_renderer
95
97
  routes_forced_alignment --> html_ids
96
98
  routes_forced_alignment --> components_sync_controls
99
+ routes_init --> components_handlers
97
100
  routes_init --> components_sync_controls
98
101
  routes_init --> services_forced_alignment
99
- routes_init --> models
100
- routes_init --> components_handlers
101
- routes_init --> routes_forced_alignment
102
102
  routes_init --> html_ids
103
+ routes_init --> routes_forced_alignment
104
+ routes_init --> components_keyboard_config
105
+ routes_init --> models
103
106
  routes_init --> routes_chrome
104
107
  routes_init --> components_step_renderer
105
- routes_init --> components_keyboard_config
106
108
  ```
107
109
 
108
110
  *30 cross-module dependencies detected*
@@ -141,13 +143,15 @@ async def _handle_switch_chrome(
141
143
  jm_trigger=None, # Pre-rendered job monitor trigger element
142
144
  fa_toggle_url:str="", # URL for FA toggle route
143
145
  fa_available:bool=False, # Whether FA plugin is available
144
- ) -> tuple: # OOB swaps for shared chrome containers
146
+ ) -> tuple: # OOB swaps for shared chrome containers (toolbar, footer, settings_trigger)
145
147
  """
146
148
  Switch shared chrome content based on active column.
147
149
 
148
150
  Client-side toolbar state restoration (sync button, auto-play toggle)
149
151
  is handled by the centralized settle handler from toolbar_state.py.
150
- Settings modals persist in the DOM — only the trigger buttons swap.
152
+ Settings modals persist in the DOM — only the trigger button (in the
153
+ step header band, not the toolbar) swaps to retarget the active
154
+ column's modal_id (V2 / G3 closure).
151
155
  """
152
156
  ```
153
157
 
@@ -194,7 +198,13 @@ def render_fa_toggle(
194
198
  active_presplit: str, # "nltk" or "forced_alignment"
195
199
  toggle_url: str, # URL for toggle route
196
200
  ) -> Any: # Toggle button group
197
- "Render the NLTK / Force Aligned toggle button group."
201
+ """
202
+ Render the NLTK / Force Aligned toggle button group.
203
+
204
+ Each leg uses primary_action (active) or secondary_action (inactive) — the
205
+ canonical V1 state-toggle composition — plus join_item for daisyui button
206
+ group joining.
207
+ """
198
208
  ```
199
209
 
200
210
  ``` python
@@ -498,6 +508,8 @@ def create_seg_init_chrome_wrapper(
498
508
  Saves nltk_presplit snapshot at init time for match detection.
499
509
  FA controls are rendered in the toolbar via extra_actions.
500
510
  Settings modals are rendered in a persistent container (both seg + align).
511
+ Settings trigger is OOB-swapped into the step header band (V2 / G3) — the
512
+ toolbar OOB no longer carries the trigger.
501
513
  """
502
514
  ```
503
515
 
@@ -831,12 +843,17 @@ def _render_shared_chrome(
831
843
  urls:SegmentationUrls=None, # Segmentation URL bundle (required when seg_state provided)
832
844
  extra_actions:tuple=(), # Extra toolbar elements (FA controls, sync toggle, etc.)
833
845
  nltk_split_disabled:bool=False, # Whether NLTK Split button is disabled
834
- ) -> tuple: # (toolbar, footer, settings_modals_container)
846
+ ) -> tuple: # (toolbar, footer, settings_modals_container, settings_trigger_container)
835
847
  """
836
848
  Render shared chrome containers, populated with segmentation content when initialized.
837
849
 
838
850
  Takes extracted state dicts from `extract_seg_state()` and `extract_alignment_state()`
839
851
  which contain deserialized TextSegment and VADChunk objects.
852
+
853
+ The settings trigger lives in the step header band (paired with the keyboard-hints
854
+ trigger in `render_combined_step`), not the toolbar — matches `cjm-transcript-review`
855
+ placement (V2 / G3). Trigger contents swap on zone change via OOB (see
856
+ `routes/chrome.py:_handle_switch_chrome`); both modals persist in the DOM.
840
857
  """
841
858
  ```
842
859
 
@@ -924,7 +941,13 @@ from cjm_transcript_segment_align.components.sync_controls import (
924
941
 
925
942
  ``` python
926
943
  def render_sync_toggle_button() -> Any: # Sync toggle button element
927
- "Render the synced navigation toggle button for the seg toolbar."
944
+ """
945
+ Render the synced navigation toggle button for the seg toolbar.
946
+
947
+ Initial state: secondary_action (outline neutral = inactive).
948
+ JS toggle flips between btn-primary and btn-outline (primary_action ↔
949
+ secondary_action) by manipulating the literal class strings.
950
+ """
928
951
  ```
929
952
 
930
953
  ``` python
@@ -49,17 +49,17 @@ graph LR
49
49
 
50
50
  components_handlers --> components_step_renderer
51
51
  components_handlers --> services_forced_alignment
52
- components_handlers --> routes_forced_alignment
53
- components_handlers --> components_keyboard_config
54
52
  components_handlers --> html_ids
53
+ components_handlers --> components_keyboard_config
55
54
  components_handlers --> components_sync_controls
56
- components_keyboard_config --> html_ids
55
+ components_handlers --> routes_forced_alignment
57
56
  components_keyboard_config --> components_sync_controls
58
- components_step_renderer --> components_helpers
59
- components_step_renderer --> components_sync_controls
57
+ components_keyboard_config --> html_ids
58
+ components_step_renderer --> html_ids
60
59
  components_step_renderer --> components_keyboard_config
60
+ components_step_renderer --> components_sync_controls
61
61
  components_step_renderer --> components_toolbar_state
62
- components_step_renderer --> html_ids
62
+ components_step_renderer --> components_helpers
63
63
  components_toolbar_state --> components_sync_controls
64
64
  routes_chrome --> components_step_renderer
65
65
  routes_chrome --> components_handlers
@@ -68,15 +68,15 @@ graph LR
68
68
  routes_forced_alignment --> components_step_renderer
69
69
  routes_forced_alignment --> html_ids
70
70
  routes_forced_alignment --> components_sync_controls
71
+ routes_init --> components_handlers
71
72
  routes_init --> components_sync_controls
72
73
  routes_init --> services_forced_alignment
73
- routes_init --> models
74
- routes_init --> components_handlers
75
- routes_init --> routes_forced_alignment
76
74
  routes_init --> html_ids
75
+ routes_init --> routes_forced_alignment
76
+ routes_init --> components_keyboard_config
77
+ routes_init --> models
77
78
  routes_init --> routes_chrome
78
79
  routes_init --> components_step_renderer
79
- routes_init --> components_keyboard_config
80
80
  ```
81
81
 
82
82
  *30 cross-module dependencies detected*
@@ -115,13 +115,15 @@ async def _handle_switch_chrome(
115
115
  jm_trigger=None, # Pre-rendered job monitor trigger element
116
116
  fa_toggle_url:str="", # URL for FA toggle route
117
117
  fa_available:bool=False, # Whether FA plugin is available
118
- ) -> tuple: # OOB swaps for shared chrome containers
118
+ ) -> tuple: # OOB swaps for shared chrome containers (toolbar, footer, settings_trigger)
119
119
  """
120
120
  Switch shared chrome content based on active column.
121
121
 
122
122
  Client-side toolbar state restoration (sync button, auto-play toggle)
123
123
  is handled by the centralized settle handler from toolbar_state.py.
124
- Settings modals persist in the DOM — only the trigger buttons swap.
124
+ Settings modals persist in the DOM — only the trigger button (in the
125
+ step header band, not the toolbar) swaps to retarget the active
126
+ column's modal_id (V2 / G3 closure).
125
127
  """
126
128
  ```
127
129
 
@@ -168,7 +170,13 @@ def render_fa_toggle(
168
170
  active_presplit: str, # "nltk" or "forced_alignment"
169
171
  toggle_url: str, # URL for toggle route
170
172
  ) -> Any: # Toggle button group
171
- "Render the NLTK / Force Aligned toggle button group."
173
+ """
174
+ Render the NLTK / Force Aligned toggle button group.
175
+
176
+ Each leg uses primary_action (active) or secondary_action (inactive) — the
177
+ canonical V1 state-toggle composition — plus join_item for daisyui button
178
+ group joining.
179
+ """
172
180
  ```
173
181
 
174
182
  ``` python
@@ -472,6 +480,8 @@ def create_seg_init_chrome_wrapper(
472
480
  Saves nltk_presplit snapshot at init time for match detection.
473
481
  FA controls are rendered in the toolbar via extra_actions.
474
482
  Settings modals are rendered in a persistent container (both seg + align).
483
+ Settings trigger is OOB-swapped into the step header band (V2 / G3) — the
484
+ toolbar OOB no longer carries the trigger.
475
485
  """
476
486
  ```
477
487
 
@@ -805,12 +815,17 @@ def _render_shared_chrome(
805
815
  urls:SegmentationUrls=None, # Segmentation URL bundle (required when seg_state provided)
806
816
  extra_actions:tuple=(), # Extra toolbar elements (FA controls, sync toggle, etc.)
807
817
  nltk_split_disabled:bool=False, # Whether NLTK Split button is disabled
808
- ) -> tuple: # (toolbar, footer, settings_modals_container)
818
+ ) -> tuple: # (toolbar, footer, settings_modals_container, settings_trigger_container)
809
819
  """
810
820
  Render shared chrome containers, populated with segmentation content when initialized.
811
821
 
812
822
  Takes extracted state dicts from `extract_seg_state()` and `extract_alignment_state()`
813
823
  which contain deserialized TextSegment and VADChunk objects.
824
+
825
+ The settings trigger lives in the step header band (paired with the keyboard-hints
826
+ trigger in `render_combined_step`), not the toolbar — matches `cjm-transcript-review`
827
+ placement (V2 / G3). Trigger contents swap on zone change via OOB (see
828
+ `routes/chrome.py:_handle_switch_chrome`); both modals persist in the DOM.
814
829
  """
815
830
  ```
816
831
 
@@ -898,7 +913,13 @@ from cjm_transcript_segment_align.components.sync_controls import (
898
913
 
899
914
  ``` python
900
915
  def render_sync_toggle_button() -> Any: # Sync toggle button element
901
- "Render the synced navigation toggle button for the seg toolbar."
916
+ """
917
+ Render the synced navigation toggle button for the seg toolbar.
918
+
919
+ Initial state: secondary_action (outline neutral = inactive).
920
+ JS toggle flips between btn-primary and btn-outline (primary_action ↔
921
+ secondary_action) by manipulating the literal class strings.
922
+ """
902
923
  ```
903
924
 
904
925
  ``` python
@@ -0,0 +1 @@
1
+ __version__ = "0.0.31"
@@ -19,10 +19,7 @@ from cjm_fasthtml_interactions.core.state_store import get_session_id
19
19
  from cjm_fasthtml_card_stack.components.settings_modal import render_card_stack_settings_modal, render_settings_trigger
20
20
  from cjm_fasthtml_card_stack.core.constants import DEFAULT_VISIBLE_COUNT
21
21
  from cjm_fasthtml_daisyui.components.data_display.badge import badge, badge_styles, badge_sizes
22
- from cjm_fasthtml_tailwind.utilities.sizing import w
23
22
  from cjm_fasthtml_tailwind.utilities.layout import display_tw
24
- from cjm_fasthtml_tailwind.utilities.flexbox_and_grid import flex_display, items, gap
25
- from cjm_fasthtml_tailwind.core.base import combine_classes
26
23
 
27
24
  from ..html_ids import CombinedHtmlIds
28
25
  from cjm_transcript_segment_align.components.step_renderer import (
@@ -383,10 +380,12 @@ def create_seg_init_chrome_wrapper(
383
380
  fa_available:bool=False, # Whether forced alignment plugin is available
384
381
  ) -> Callable: # Wrapped handler that builds KB system and shared chrome
385
382
  """Create a wrapper for seg init that builds combined KB system and shared chrome.
386
-
383
+
387
384
  Saves nltk_presplit snapshot at init time for match detection.
388
385
  FA controls are rendered in the toolbar via extra_actions.
389
386
  Settings modals are rendered in a persistent container (both seg + align).
387
+ Settings trigger is OOB-swapped into the step header band (V2 / G3) — the
388
+ toolbar OOB no longer carries the trigger.
390
389
  """
391
390
  async def wrapped_seg_init(
392
391
  state_store:WorkflowStateStore,
@@ -405,9 +404,9 @@ def create_seg_init_chrome_wrapper(
405
404
  state_store, workflow_id, source_service, segmentation_service,
406
405
  request, sess, urls, visible_count, card_width,
407
406
  )
408
-
407
+
409
408
  session_id = get_session_id(sess)
410
-
409
+
411
410
  # Save NLTK pre-split snapshot for match detection
412
411
  workflow_state = state_store.get_state(workflow_id, session_id)
413
412
  step_states = workflow_state.get("step_states", {})
@@ -423,10 +422,10 @@ def create_seg_init_chrome_wrapper(
423
422
 
424
423
  chunk_count = len(align_state.get("vad_chunks", []))
425
424
  segment_count = len(result.segments)
426
-
425
+
427
426
  # Build combined KB system with both zones
428
427
  kb_manager, kb_system = build_combined_kb_system(urls, align_urls)
429
-
428
+
430
429
  # OOB swap for stable keyboard system container
431
430
  kb_system_oob = Div(
432
431
  kb_system.script,
@@ -435,10 +434,10 @@ def create_seg_init_chrome_wrapper(
435
434
  id=CombinedHtmlIds.KEYBOARD_SYSTEM,
436
435
  hx_swap_oob="innerHTML"
437
436
  )
438
-
437
+
439
438
  # Zone change JS (goes in response for browser to execute)
440
439
  zone_change_js = generate_zone_change_js(switch_chrome_url)
441
-
440
+
442
441
  # Hidden chrome switch button for HTMX trigger
443
442
  chrome_switch_btn = Button(
444
443
  id=SWITCH_CHROME_BTN_ID,
@@ -448,31 +447,32 @@ def create_seg_init_chrome_wrapper(
448
447
  hx_swap="none",
449
448
  hx_swap_oob="true",
450
449
  )
451
-
450
+
452
451
  # Build FA extra_actions for toolbar
453
452
  fa_extra = build_fa_extra_actions(
454
453
  seg_state, jm_trigger, fa_toggle_url, fa_available,
455
454
  )
456
-
457
- # Settings modal trigger for seg column
455
+
456
+ # Settings modal trigger for seg column (header-band slot, not toolbar)
458
457
  settings_trigger = render_settings_trigger(modal_id=SEG_CS_IDS.settings_modal)
459
-
460
- # Toolbar OOB (settings trigger + toolbar with FA controls, NLTK Split disabled at init)
458
+ settings_trigger_oob = Div(
459
+ settings_trigger,
460
+ id=CombinedHtmlIds.SHARED_SETTINGS_TRIGGER,
461
+ hx_swap_oob="innerHTML"
462
+ )
463
+
464
+ # Toolbar OOB (toolbar with FA controls, NLTK Split disabled at init)
461
465
  toolbar_oob = Div(
462
- Div(
463
- settings_trigger,
464
- render_toolbar(
465
- reset_url=urls.reset, ai_split_url=urls.ai_split, undo_url=urls.undo,
466
- can_undo=(result.history_depth > 0),
467
- extra_actions=build_extra_actions(fa_extra),
468
- nltk_split_disabled=True,
469
- ),
470
- cls=combine_classes(flex_display, items.center, gap(2), w.full),
466
+ render_toolbar(
467
+ reset_url=urls.reset, ai_split_url=urls.ai_split, undo_url=urls.undo,
468
+ can_undo=(result.history_depth > 0),
469
+ extra_actions=build_extra_actions(fa_extra),
470
+ nltk_split_disabled=True,
471
471
  ),
472
472
  id=CombinedHtmlIds.SHARED_TOOLBAR,
473
473
  hx_swap_oob="innerHTML"
474
474
  )
475
-
475
+
476
476
  # Settings modals OOB (both seg + align persist in DOM)
477
477
  from cjm_transcript_vad_align.components.card_stack_config import (
478
478
  ALIGN_CS_CONFIG, ALIGN_CS_IDS,
@@ -493,10 +493,10 @@ def create_seg_init_chrome_wrapper(
493
493
  id=CombinedHtmlIds.SETTINGS_MODALS,
494
494
  hx_swap_oob="innerHTML"
495
495
  )
496
-
496
+
497
497
  # Footer OOB with both column footers + alignment status
498
498
  seg_footer = render_seg_footer_content(result.segments, result.focused_index)
499
-
499
+
500
500
  # Align footer (if alignment is initialized)
501
501
  is_align_init = align_state.get("is_initialized", False)
502
502
  if is_align_init and align_state.get("vad_chunks"):
@@ -505,21 +505,21 @@ def create_seg_init_chrome_wrapper(
505
505
  align_footer = render_align_footer_content(align_chunks, align_focused)
506
506
  else:
507
507
  align_footer = None
508
-
508
+
509
509
  footer_oob = Div(
510
510
  render_footer_inner_content(seg_footer, align_footer, segment_count, chunk_count),
511
511
  id=CombinedHtmlIds.SHARED_FOOTER,
512
512
  hx_swap_oob="innerHTML"
513
513
  )
514
-
514
+
515
515
  # Mini-stats badge OOB
516
516
  mini_stats_oob = render_seg_mini_stats_badge(result.segments, oob=True)
517
-
517
+
518
518
  return (
519
519
  result.column_body, kb_system_oob, zone_change_js, chrome_switch_btn,
520
- toolbar_oob, settings_modals_oob, footer_oob, mini_stats_oob,
520
+ toolbar_oob, settings_trigger_oob, settings_modals_oob, footer_oob, mini_stats_oob,
521
521
  )
522
-
522
+
523
523
  return wrapped_seg_init
524
524
 
525
525
  # %% ../../nbs/components/handlers.ipynb #ecvyiypdxk
@@ -244,21 +244,26 @@ def _render_shared_chrome(
244
244
  urls:SegmentationUrls=None, # Segmentation URL bundle (required when seg_state provided)
245
245
  extra_actions:tuple=(), # Extra toolbar elements (FA controls, sync toggle, etc.)
246
246
  nltk_split_disabled:bool=False, # Whether NLTK Split button is disabled
247
- ) -> tuple: # (toolbar, footer, settings_modals_container)
247
+ ) -> tuple: # (toolbar, footer, settings_modals_container, settings_trigger_container)
248
248
  """Render shared chrome containers, populated with segmentation content when initialized.
249
-
249
+
250
250
  Takes extracted state dicts from `extract_seg_state()` and `extract_alignment_state()`
251
251
  which contain deserialized TextSegment and VADChunk objects.
252
+
253
+ The settings trigger lives in the step header band (paired with the keyboard-hints
254
+ trigger in `render_combined_step`), not the toolbar — matches `cjm-transcript-review`
255
+ placement (V2 / G3). Trigger contents swap on zone change via OOB (see
256
+ `routes/chrome.py:_handle_switch_chrome`); both modals persist in the DOM.
252
257
  """
253
258
  is_init = seg_state is not None and seg_state.get("is_initialized", False)
254
259
 
255
- # --- Settings modals (both persist in DOM, triggers swap with toolbar) ---
260
+ # --- Settings modals (both persist in DOM, trigger swaps on zone change) ---
256
261
  seg_modal, seg_trigger = render_card_stack_settings_modal(
257
262
  SEG_CS_CONFIG, SEG_CS_IDS,
258
263
  current_count=seg_state.get("visible_count", DEFAULT_VISIBLE_COUNT) if seg_state else DEFAULT_VISIBLE_COUNT,
259
264
  card_width=seg_state.get("card_width", DEFAULT_CARD_WIDTH) if seg_state else DEFAULT_CARD_WIDTH,
260
265
  )
261
- align_modal, align_trigger = render_card_stack_settings_modal(
266
+ align_modal, _align_trigger = render_card_stack_settings_modal(
262
267
  ALIGN_CS_CONFIG, ALIGN_CS_IDS,
263
268
  current_count=align_state.get("visible_count", 5) if align_state else 5,
264
269
  card_width=align_state.get("card_width", 40) if align_state else 40,
@@ -270,19 +275,24 @@ def _render_shared_chrome(
270
275
  id=CombinedHtmlIds.SETTINGS_MODALS,
271
276
  )
272
277
 
278
+ # --- Settings trigger (header-band slot; swaps on zone change via OOB) ---
279
+ # Initial render targets the seg modal because active_column starts as "seg".
280
+ # The chrome-switch handler retargets the trigger to the align modal_id when
281
+ # the user moves to the VAD column.
282
+ settings_trigger_container = Div(
283
+ seg_trigger,
284
+ id=CombinedHtmlIds.SHARED_SETTINGS_TRIGGER,
285
+ )
286
+
273
287
  # --- Toolbar ---
274
288
  if is_init and urls:
275
- toolbar_content = Div(
276
- seg_trigger,
277
- render_toolbar(
278
- reset_url=urls.reset,
279
- ai_split_url=urls.ai_split,
280
- undo_url=urls.undo,
281
- can_undo=(len(seg_state.get("history", [])) > 0),
282
- extra_actions=extra_actions,
283
- nltk_split_disabled=nltk_split_disabled,
284
- ),
285
- cls=combine_classes(flex_display, items.center, gap(2), w.full),
289
+ toolbar_content = render_toolbar(
290
+ reset_url=urls.reset,
291
+ ai_split_url=urls.ai_split,
292
+ undo_url=urls.undo,
293
+ can_undo=(len(seg_state.get("history", [])) > 0),
294
+ extra_actions=extra_actions,
295
+ nltk_split_disabled=nltk_split_disabled,
286
296
  )
287
297
  else:
288
298
  toolbar_content = _placeholder("Toolbar actions will appear here based on the active column.")
@@ -298,7 +308,7 @@ def _render_shared_chrome(
298
308
  segment_count = len(seg_segments)
299
309
  align_chunks = align_state.get("vad_chunks", []) if align_state else []
300
310
  chunk_count = len(align_chunks)
301
-
311
+
302
312
  # Seg footer
303
313
  if is_init and seg_segments:
304
314
  focused_index = seg_state.get("focused_index", 0)
@@ -325,7 +335,7 @@ def _render_shared_chrome(
325
335
  )
326
336
  )
327
337
 
328
- return toolbar, footer, settings_modals_container
338
+ return toolbar, footer, settings_modals_container, settings_trigger_container
329
339
 
330
340
  # %% ../../nbs/components/step_renderer.ipynb #c9d0e1f2
331
341
  # Shared column styling (reused by init handler for outerHTML swap)
@@ -552,7 +562,7 @@ def render_combined_step(
552
562
  )
553
563
  mini_stats_text = render_seg_mini_stats_text(segments)
554
564
 
555
- toolbar, footer, settings_modals_container = _render_shared_chrome(
565
+ toolbar, footer, settings_modals_container, settings_trigger_el = _render_shared_chrome(
556
566
  seg_state=seg_state,
557
567
  align_state=align_state,
558
568
  urls=seg_urls,
@@ -566,7 +576,7 @@ def render_combined_step(
566
576
  mini_stats_text=mini_stats_text,
567
577
  )
568
578
  else:
569
- toolbar, footer, settings_modals_container = _render_shared_chrome(
579
+ toolbar, footer, settings_modals_container, settings_trigger_el = _render_shared_chrome(
570
580
  align_state=align_state,
571
581
  )
572
582
  seg_col = _render_seg_column(
@@ -650,7 +660,13 @@ def render_combined_step(
650
660
  cls=combine_classes(text_tiers.secondary, m.b(2))
651
661
  ),
652
662
  ),
653
- hints_trigger,
663
+ # Top-right trigger pair: settings (zone-aware via OOB) + keyboard hints (static).
664
+ # Matches `cjm-transcript-review`'s placement (V2 / G3 closure).
665
+ Div(
666
+ settings_trigger_el,
667
+ hints_trigger,
668
+ cls=combine_classes(flex_display, items.center, gap(2)),
669
+ ),
654
670
  cls=combine_classes(flex_display, items.start, justify.between),
655
671
  ),
656
672
  toolbar,
@@ -22,6 +22,7 @@ class CombinedHtmlIds:
22
22
  SHARED_HINTS = "sd-shared-hints"
23
23
  SHARED_TOOLBAR = "sd-shared-toolbar"
24
24
  SHARED_FOOTER = "sd-shared-footer"
25
+ SHARED_SETTINGS_TRIGGER = "sd-shared-settings-trigger"
25
26
  ALIGNMENT_STATUS = "sd-alignment-status"
26
27
  SETTINGS_MODALS = "sd-settings-modals"
27
28
 
@@ -38,12 +38,6 @@ from cjm_transcript_vad_align.components.card_stack_config import (
38
38
  ALIGN_CS_CONFIG, ALIGN_CS_IDS,
39
39
  )
40
40
 
41
- from cjm_fasthtml_tailwind.utilities.sizing import w
42
- from cjm_fasthtml_tailwind.utilities.flexbox_and_grid import (
43
- flex_display, items, gap,
44
- )
45
- from cjm_fasthtml_tailwind.core.base import combine_classes
46
-
47
41
  # Footer helper + FA extra_actions builder + match detection
48
42
  from cjm_transcript_segment_align.components.step_renderer import (
49
43
  render_footer_inner_content,
@@ -67,12 +61,14 @@ async def _handle_switch_chrome(
67
61
  jm_trigger=None, # Pre-rendered job monitor trigger element
68
62
  fa_toggle_url:str="", # URL for FA toggle route
69
63
  fa_available:bool=False, # Whether FA plugin is available
70
- ) -> tuple: # OOB swaps for shared chrome containers
64
+ ) -> tuple: # OOB swaps for shared chrome containers (toolbar, footer, settings_trigger)
71
65
  """Switch shared chrome content based on active column.
72
-
66
+
73
67
  Client-side toolbar state restoration (sync button, auto-play toggle)
74
68
  is handled by the centralized settle handler from toolbar_state.py.
75
- Settings modals persist in the DOM — only the trigger buttons swap.
69
+ Settings modals persist in the DOM — only the trigger button (in the
70
+ step header band, not the toolbar) swaps to retarget the active
71
+ column's modal_id (V2 / G3 closure).
76
72
  """
77
73
  form = await request.form()
78
74
  active_column = form.get("active_column", "seg")
@@ -91,10 +87,8 @@ async def _handle_switch_chrome(
91
87
  chunk_count = len(align_state.get("vad_chunks", []))
92
88
 
93
89
  if active_column == "seg":
94
- # Segmentation chrome (settings trigger + toolbar)
95
- segments = [TextSegment.from_dict(s) for s in seg_state.get("segments", [])]
90
+ # Segmentation toolbar (Undo + Reset + NLTK Split + FA controls)
96
91
  history = seg_state.get("history", [])
97
- focused_index = seg_state.get("focused_index", 0)
98
92
 
99
93
  # Build FA extra_actions and NLTK Split disabled state
100
94
  fa_extra = build_fa_extra_actions(
@@ -106,30 +100,22 @@ async def _handle_switch_chrome(
106
100
  )
107
101
 
108
102
  settings_trigger = render_settings_trigger(modal_id=SEG_CS_IDS.settings_modal)
109
- toolbar_content = Div(
110
- settings_trigger,
111
- render_seg_toolbar(
112
- reset_url=seg_urls.reset,
113
- ai_split_url=seg_urls.ai_split,
114
- undo_url=seg_urls.undo,
115
- can_undo=(len(history) > 0),
116
- extra_actions=build_extra_actions(fa_extra),
117
- nltk_split_disabled=nltk_disabled,
118
- ),
119
- cls=combine_classes(flex_display, items.center, gap(2), w.full),
103
+ toolbar_content = render_seg_toolbar(
104
+ reset_url=seg_urls.reset,
105
+ ai_split_url=seg_urls.ai_split,
106
+ undo_url=seg_urls.undo,
107
+ can_undo=(len(history) > 0),
108
+ extra_actions=build_extra_actions(fa_extra),
109
+ nltk_split_disabled=nltk_disabled,
120
110
  )
121
111
  else:
122
- # Alignment chrome (settings trigger + speed selector + auto-play toggle)
112
+ # Alignment toolbar (speed selector + auto-play toggle)
123
113
  # Read persisted state so chrome switches restore the user's current speed + auto-nav
124
114
  settings_trigger = render_settings_trigger(modal_id=ALIGN_CS_IDS.settings_modal)
125
- toolbar_content = Div(
126
- settings_trigger,
127
- render_align_toolbar(
128
- current_speed=align_state.get("playback_speed", 1.0),
129
- auto_navigate=align_state.get("auto_navigate", False),
130
- speed_url=align_urls.speed_change,
131
- ),
132
- cls=combine_classes(flex_display, items.center, gap(2), w.full),
115
+ toolbar_content = render_align_toolbar(
116
+ current_speed=align_state.get("playback_speed", 1.0),
117
+ auto_navigate=align_state.get("auto_navigate", False),
118
+ speed_url=align_urls.speed_change,
133
119
  )
134
120
 
135
121
  # --- Footer: both column footers always present ---
@@ -144,7 +130,7 @@ async def _handle_switch_chrome(
144
130
  if DEBUG_SWITCH_CHROME:
145
131
  print(f"[SWITCH_CHROME] returning OOB swaps for {active_column}")
146
132
 
147
- # Return OOB swaps (toolbar + footer settings modals persist in DOM)
133
+ # Return OOB swaps (toolbar + footer + settings trigger — modals persist in DOM)
148
134
  toolbar_oob = Div(
149
135
  toolbar_content,
150
136
  id=CombinedHtmlIds.SHARED_TOOLBAR,
@@ -155,8 +141,13 @@ async def _handle_switch_chrome(
155
141
  id=CombinedHtmlIds.SHARED_FOOTER,
156
142
  hx_swap_oob="innerHTML"
157
143
  )
144
+ settings_trigger_oob = Div(
145
+ settings_trigger,
146
+ id=CombinedHtmlIds.SHARED_SETTINGS_TRIGGER,
147
+ hx_swap_oob="innerHTML"
148
+ )
158
149
 
159
- return (toolbar_oob, footer_oob)
150
+ return (toolbar_oob, footer_oob, settings_trigger_oob)
160
151
 
161
152
  # %% ../../nbs/routes/chrome.ipynb #g7b8c9d0
162
153
  def init_chrome_router(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cjm-transcript-segment-align
3
- Version: 0.0.29
3
+ Version: 0.0.31
4
4
  Summary: FastHTML dual-column text segmentation & VAD alignment UI for transcript decomposition workflows with forced alignment-based text splitting for aligning text segments with VAD chunks.
5
5
  Author-email: "Christian J. Mills" <9126128+cj-mills@users.noreply.github.com>
6
6
  License: Apache-2.0
@@ -15,10 +15,12 @@ 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
19
  Requires-Dist: cjm-plugin-system
19
20
  Requires-Dist: cjm_transcription_plugin_system
20
- Requires-Dist: cjm_transcript_segmentation>=0.0.20
21
- Requires-Dist: cjm_transcript_vad_align>=0.0.26
21
+ Requires-Dist: cjm_transcript_segmentation>=0.0.22
22
+ Requires-Dist: cjm_transcript_vad_align>=0.0.28
23
+ Requires-Dist: cjm_fasthtml_keyboard_navigation>=0.0.22
22
24
  Requires-Dist: cjm_fasthtml_job_monitor>=0.0.11
23
25
  Requires-Dist: cjm_fasthtml_web_audio>=0.0.11
24
26
  Requires-Dist: cjm_fasthtml_design_system>=0.0.8
@@ -75,17 +77,17 @@ graph LR
75
77
 
76
78
  components_handlers --> components_step_renderer
77
79
  components_handlers --> services_forced_alignment
78
- components_handlers --> routes_forced_alignment
79
- components_handlers --> components_keyboard_config
80
80
  components_handlers --> html_ids
81
+ components_handlers --> components_keyboard_config
81
82
  components_handlers --> components_sync_controls
82
- components_keyboard_config --> html_ids
83
+ components_handlers --> routes_forced_alignment
83
84
  components_keyboard_config --> components_sync_controls
84
- components_step_renderer --> components_helpers
85
- components_step_renderer --> components_sync_controls
85
+ components_keyboard_config --> html_ids
86
+ components_step_renderer --> html_ids
86
87
  components_step_renderer --> components_keyboard_config
88
+ components_step_renderer --> components_sync_controls
87
89
  components_step_renderer --> components_toolbar_state
88
- components_step_renderer --> html_ids
90
+ components_step_renderer --> components_helpers
89
91
  components_toolbar_state --> components_sync_controls
90
92
  routes_chrome --> components_step_renderer
91
93
  routes_chrome --> components_handlers
@@ -94,15 +96,15 @@ graph LR
94
96
  routes_forced_alignment --> components_step_renderer
95
97
  routes_forced_alignment --> html_ids
96
98
  routes_forced_alignment --> components_sync_controls
99
+ routes_init --> components_handlers
97
100
  routes_init --> components_sync_controls
98
101
  routes_init --> services_forced_alignment
99
- routes_init --> models
100
- routes_init --> components_handlers
101
- routes_init --> routes_forced_alignment
102
102
  routes_init --> html_ids
103
+ routes_init --> routes_forced_alignment
104
+ routes_init --> components_keyboard_config
105
+ routes_init --> models
103
106
  routes_init --> routes_chrome
104
107
  routes_init --> components_step_renderer
105
- routes_init --> components_keyboard_config
106
108
  ```
107
109
 
108
110
  *30 cross-module dependencies detected*
@@ -141,13 +143,15 @@ async def _handle_switch_chrome(
141
143
  jm_trigger=None, # Pre-rendered job monitor trigger element
142
144
  fa_toggle_url:str="", # URL for FA toggle route
143
145
  fa_available:bool=False, # Whether FA plugin is available
144
- ) -> tuple: # OOB swaps for shared chrome containers
146
+ ) -> tuple: # OOB swaps for shared chrome containers (toolbar, footer, settings_trigger)
145
147
  """
146
148
  Switch shared chrome content based on active column.
147
149
 
148
150
  Client-side toolbar state restoration (sync button, auto-play toggle)
149
151
  is handled by the centralized settle handler from toolbar_state.py.
150
- Settings modals persist in the DOM — only the trigger buttons swap.
152
+ Settings modals persist in the DOM — only the trigger button (in the
153
+ step header band, not the toolbar) swaps to retarget the active
154
+ column's modal_id (V2 / G3 closure).
151
155
  """
152
156
  ```
153
157
 
@@ -194,7 +198,13 @@ def render_fa_toggle(
194
198
  active_presplit: str, # "nltk" or "forced_alignment"
195
199
  toggle_url: str, # URL for toggle route
196
200
  ) -> Any: # Toggle button group
197
- "Render the NLTK / Force Aligned toggle button group."
201
+ """
202
+ Render the NLTK / Force Aligned toggle button group.
203
+
204
+ Each leg uses primary_action (active) or secondary_action (inactive) — the
205
+ canonical V1 state-toggle composition — plus join_item for daisyui button
206
+ group joining.
207
+ """
198
208
  ```
199
209
 
200
210
  ``` python
@@ -498,6 +508,8 @@ def create_seg_init_chrome_wrapper(
498
508
  Saves nltk_presplit snapshot at init time for match detection.
499
509
  FA controls are rendered in the toolbar via extra_actions.
500
510
  Settings modals are rendered in a persistent container (both seg + align).
511
+ Settings trigger is OOB-swapped into the step header band (V2 / G3) — the
512
+ toolbar OOB no longer carries the trigger.
501
513
  """
502
514
  ```
503
515
 
@@ -831,12 +843,17 @@ def _render_shared_chrome(
831
843
  urls:SegmentationUrls=None, # Segmentation URL bundle (required when seg_state provided)
832
844
  extra_actions:tuple=(), # Extra toolbar elements (FA controls, sync toggle, etc.)
833
845
  nltk_split_disabled:bool=False, # Whether NLTK Split button is disabled
834
- ) -> tuple: # (toolbar, footer, settings_modals_container)
846
+ ) -> tuple: # (toolbar, footer, settings_modals_container, settings_trigger_container)
835
847
  """
836
848
  Render shared chrome containers, populated with segmentation content when initialized.
837
849
 
838
850
  Takes extracted state dicts from `extract_seg_state()` and `extract_alignment_state()`
839
851
  which contain deserialized TextSegment and VADChunk objects.
852
+
853
+ The settings trigger lives in the step header band (paired with the keyboard-hints
854
+ trigger in `render_combined_step`), not the toolbar — matches `cjm-transcript-review`
855
+ placement (V2 / G3). Trigger contents swap on zone change via OOB (see
856
+ `routes/chrome.py:_handle_switch_chrome`); both modals persist in the DOM.
840
857
  """
841
858
  ```
842
859
 
@@ -924,7 +941,13 @@ from cjm_transcript_segment_align.components.sync_controls import (
924
941
 
925
942
  ``` python
926
943
  def render_sync_toggle_button() -> Any: # Sync toggle button element
927
- "Render the synced navigation toggle button for the seg toolbar."
944
+ """
945
+ Render the synced navigation toggle button for the seg toolbar.
946
+
947
+ Initial state: secondary_action (outline neutral = inactive).
948
+ JS toggle flips between btn-primary and btn-outline (primary_action ↔
949
+ secondary_action) by manipulating the literal class strings.
950
+ """
928
951
  ```
929
952
 
930
953
  ``` python
@@ -1,7 +1,9 @@
1
+ python-fasthtml==0.13.4
1
2
  cjm-plugin-system
2
3
  cjm_transcription_plugin_system
3
- cjm_transcript_segmentation>=0.0.20
4
- cjm_transcript_vad_align>=0.0.26
4
+ cjm_transcript_segmentation>=0.0.22
5
+ cjm_transcript_vad_align>=0.0.28
6
+ cjm_fasthtml_keyboard_navigation>=0.0.22
5
7
  cjm_fasthtml_job_monitor>=0.0.11
6
8
  cjm_fasthtml_web_audio>=0.0.11
7
9
  cjm_fasthtml_design_system>=0.0.8
@@ -12,7 +12,7 @@ license = {text = "Apache-2.0"}
12
12
  authors = [{name = "Christian J. Mills", email = "9126128+cj-mills@users.noreply.github.com"}]
13
13
  keywords = ['nbdev', 'jupyter', 'notebook', 'python']
14
14
  classifiers = ["Natural Language :: English", "Intended Audience :: Developers", "Development Status :: 3 - Alpha", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only"]
15
- dependencies = ['cjm-plugin-system', 'cjm_transcription_plugin_system', 'cjm_transcript_segmentation>=0.0.20', 'cjm_transcript_vad_align>=0.0.26', 'cjm_fasthtml_job_monitor>=0.0.11', 'cjm_fasthtml_web_audio>=0.0.11', 'cjm_fasthtml_design_system>=0.0.8']
15
+ dependencies = ['python-fasthtml==0.13.4', 'cjm-plugin-system', 'cjm_transcription_plugin_system', 'cjm_transcript_segmentation>=0.0.22', 'cjm_transcript_vad_align>=0.0.28', 'cjm_fasthtml_keyboard_navigation>=0.0.22', 'cjm_fasthtml_job_monitor>=0.0.11', 'cjm_fasthtml_web_audio>=0.0.11', 'cjm_fasthtml_design_system>=0.0.8']
16
16
 
17
17
  [project.urls]
18
18
  Repository = "https://github.com/cj-mills/cjm-transcript-segment-align"
@@ -1 +0,0 @@
1
- __version__ = "0.0.29"