cjm-transcript-segment-align 0.0.28__tar.gz → 0.0.30__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.28/cjm_transcript_segment_align.egg-info → cjm_transcript_segment_align-0.0.30}/PKG-INFO +37 -16
  2. {cjm_transcript_segment_align-0.0.28 → cjm_transcript_segment_align-0.0.30}/README.md +35 -14
  3. cjm_transcript_segment_align-0.0.30/cjm_transcript_segment_align/__init__.py +1 -0
  4. {cjm_transcript_segment_align-0.0.28 → cjm_transcript_segment_align-0.0.30}/cjm_transcript_segment_align/components/handlers.py +32 -32
  5. {cjm_transcript_segment_align-0.0.28 → cjm_transcript_segment_align-0.0.30}/cjm_transcript_segment_align/components/step_renderer.py +41 -23
  6. {cjm_transcript_segment_align-0.0.28 → cjm_transcript_segment_align-0.0.30}/cjm_transcript_segment_align/html_ids.py +1 -0
  7. {cjm_transcript_segment_align-0.0.28 → cjm_transcript_segment_align-0.0.30}/cjm_transcript_segment_align/routes/chrome.py +25 -34
  8. {cjm_transcript_segment_align-0.0.28 → cjm_transcript_segment_align-0.0.30/cjm_transcript_segment_align.egg-info}/PKG-INFO +37 -16
  9. {cjm_transcript_segment_align-0.0.28 → cjm_transcript_segment_align-0.0.30}/cjm_transcript_segment_align.egg-info/requires.txt +1 -1
  10. {cjm_transcript_segment_align-0.0.28 → cjm_transcript_segment_align-0.0.30}/pyproject.toml +2 -2
  11. cjm_transcript_segment_align-0.0.28/cjm_transcript_segment_align/__init__.py +0 -1
  12. {cjm_transcript_segment_align-0.0.28 → cjm_transcript_segment_align-0.0.30}/LICENSE +0 -0
  13. {cjm_transcript_segment_align-0.0.28 → cjm_transcript_segment_align-0.0.30}/MANIFEST.in +0 -0
  14. {cjm_transcript_segment_align-0.0.28 → cjm_transcript_segment_align-0.0.30}/cjm_transcript_segment_align/_modidx.py +0 -0
  15. {cjm_transcript_segment_align-0.0.28 → cjm_transcript_segment_align-0.0.30}/cjm_transcript_segment_align/components/__init__.py +0 -0
  16. {cjm_transcript_segment_align-0.0.28 → cjm_transcript_segment_align-0.0.30}/cjm_transcript_segment_align/components/helpers.py +0 -0
  17. {cjm_transcript_segment_align-0.0.28 → cjm_transcript_segment_align-0.0.30}/cjm_transcript_segment_align/components/keyboard_config.py +0 -0
  18. {cjm_transcript_segment_align-0.0.28 → cjm_transcript_segment_align-0.0.30}/cjm_transcript_segment_align/components/sync_controls.py +0 -0
  19. {cjm_transcript_segment_align-0.0.28 → cjm_transcript_segment_align-0.0.30}/cjm_transcript_segment_align/components/toolbar_state.py +0 -0
  20. {cjm_transcript_segment_align-0.0.28 → cjm_transcript_segment_align-0.0.30}/cjm_transcript_segment_align/models.py +0 -0
  21. {cjm_transcript_segment_align-0.0.28 → cjm_transcript_segment_align-0.0.30}/cjm_transcript_segment_align/routes/__init__.py +0 -0
  22. {cjm_transcript_segment_align-0.0.28 → cjm_transcript_segment_align-0.0.30}/cjm_transcript_segment_align/routes/forced_alignment.py +0 -0
  23. {cjm_transcript_segment_align-0.0.28 → cjm_transcript_segment_align-0.0.30}/cjm_transcript_segment_align/routes/init.py +0 -0
  24. {cjm_transcript_segment_align-0.0.28 → cjm_transcript_segment_align-0.0.30}/cjm_transcript_segment_align/services/__init__.py +0 -0
  25. {cjm_transcript_segment_align-0.0.28 → cjm_transcript_segment_align-0.0.30}/cjm_transcript_segment_align/services/forced_alignment.py +0 -0
  26. {cjm_transcript_segment_align-0.0.28 → cjm_transcript_segment_align-0.0.30}/cjm_transcript_segment_align.egg-info/SOURCES.txt +0 -0
  27. {cjm_transcript_segment_align-0.0.28 → cjm_transcript_segment_align-0.0.30}/cjm_transcript_segment_align.egg-info/dependency_links.txt +0 -0
  28. {cjm_transcript_segment_align-0.0.28 → cjm_transcript_segment_align-0.0.30}/cjm_transcript_segment_align.egg-info/entry_points.txt +0 -0
  29. {cjm_transcript_segment_align-0.0.28 → cjm_transcript_segment_align-0.0.30}/cjm_transcript_segment_align.egg-info/top_level.txt +0 -0
  30. {cjm_transcript_segment_align-0.0.28 → cjm_transcript_segment_align-0.0.30}/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.28
3
+ Version: 0.0.30
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
@@ -21,7 +21,7 @@ Requires-Dist: cjm_transcript_segmentation>=0.0.20
21
21
  Requires-Dist: cjm_transcript_vad_align>=0.0.26
22
22
  Requires-Dist: cjm_fasthtml_job_monitor>=0.0.11
23
23
  Requires-Dist: cjm_fasthtml_web_audio>=0.0.11
24
- Requires-Dist: cjm_fasthtml_design_system>=0.0.7
24
+ Requires-Dist: cjm_fasthtml_design_system>=0.0.8
25
25
  Dynamic: license-file
26
26
 
27
27
  # cjm-transcript-segment-align
@@ -74,33 +74,33 @@ graph LR
74
74
  services_forced_alignment[services.forced_alignment<br/>forced_alignment]
75
75
 
76
76
  components_handlers --> components_step_renderer
77
- components_handlers --> services_forced_alignment
78
- components_handlers --> routes_forced_alignment
79
77
  components_handlers --> components_keyboard_config
78
+ components_handlers --> routes_forced_alignment
79
+ components_handlers --> services_forced_alignment
80
80
  components_handlers --> html_ids
81
81
  components_handlers --> components_sync_controls
82
82
  components_keyboard_config --> html_ids
83
83
  components_keyboard_config --> components_sync_controls
84
84
  components_step_renderer --> components_helpers
85
- components_step_renderer --> components_sync_controls
86
85
  components_step_renderer --> components_keyboard_config
87
86
  components_step_renderer --> components_toolbar_state
87
+ components_step_renderer --> components_sync_controls
88
88
  components_step_renderer --> html_ids
89
89
  components_toolbar_state --> components_sync_controls
90
90
  routes_chrome --> components_step_renderer
91
- routes_chrome --> components_handlers
92
91
  routes_chrome --> html_ids
93
92
  routes_chrome --> components_sync_controls
94
- routes_forced_alignment --> components_step_renderer
93
+ routes_chrome --> components_handlers
95
94
  routes_forced_alignment --> html_ids
96
95
  routes_forced_alignment --> components_sync_controls
97
- routes_init --> components_sync_controls
98
- routes_init --> services_forced_alignment
96
+ routes_forced_alignment --> components_step_renderer
99
97
  routes_init --> models
98
+ routes_init --> components_sync_controls
100
99
  routes_init --> components_handlers
101
- routes_init --> routes_forced_alignment
102
- routes_init --> html_ids
103
100
  routes_init --> routes_chrome
101
+ routes_init --> html_ids
102
+ routes_init --> services_forced_alignment
103
+ routes_init --> routes_forced_alignment
104
104
  routes_init --> components_step_renderer
105
105
  routes_init --> components_keyboard_config
106
106
  ```
@@ -141,13 +141,15 @@ async def _handle_switch_chrome(
141
141
  jm_trigger=None, # Pre-rendered job monitor trigger element
142
142
  fa_toggle_url:str="", # URL for FA toggle route
143
143
  fa_available:bool=False, # Whether FA plugin is available
144
- ) -> tuple: # OOB swaps for shared chrome containers
144
+ ) -> tuple: # OOB swaps for shared chrome containers (toolbar, footer, settings_trigger)
145
145
  """
146
146
  Switch shared chrome content based on active column.
147
147
 
148
148
  Client-side toolbar state restoration (sync button, auto-play toggle)
149
149
  is handled by the centralized settle handler from toolbar_state.py.
150
- Settings modals persist in the DOM — only the trigger buttons swap.
150
+ Settings modals persist in the DOM — only the trigger button (in the
151
+ step header band, not the toolbar) swaps to retarget the active
152
+ column's modal_id (V2 / G3 closure).
151
153
  """
152
154
  ```
153
155
 
@@ -194,7 +196,13 @@ def render_fa_toggle(
194
196
  active_presplit: str, # "nltk" or "forced_alignment"
195
197
  toggle_url: str, # URL for toggle route
196
198
  ) -> Any: # Toggle button group
197
- "Render the NLTK / Force Aligned toggle button group."
199
+ """
200
+ Render the NLTK / Force Aligned toggle button group.
201
+
202
+ Each leg uses primary_action (active) or secondary_action (inactive) — the
203
+ canonical V1 state-toggle composition — plus join_item for daisyui button
204
+ group joining.
205
+ """
198
206
  ```
199
207
 
200
208
  ``` python
@@ -498,6 +506,8 @@ def create_seg_init_chrome_wrapper(
498
506
  Saves nltk_presplit snapshot at init time for match detection.
499
507
  FA controls are rendered in the toolbar via extra_actions.
500
508
  Settings modals are rendered in a persistent container (both seg + align).
509
+ Settings trigger is OOB-swapped into the step header band (V2 / G3) — the
510
+ toolbar OOB no longer carries the trigger.
501
511
  """
502
512
  ```
503
513
 
@@ -831,12 +841,17 @@ def _render_shared_chrome(
831
841
  urls:SegmentationUrls=None, # Segmentation URL bundle (required when seg_state provided)
832
842
  extra_actions:tuple=(), # Extra toolbar elements (FA controls, sync toggle, etc.)
833
843
  nltk_split_disabled:bool=False, # Whether NLTK Split button is disabled
834
- ) -> tuple: # (toolbar, footer, settings_modals_container)
844
+ ) -> tuple: # (toolbar, footer, settings_modals_container, settings_trigger_container)
835
845
  """
836
846
  Render shared chrome containers, populated with segmentation content when initialized.
837
847
 
838
848
  Takes extracted state dicts from `extract_seg_state()` and `extract_alignment_state()`
839
849
  which contain deserialized TextSegment and VADChunk objects.
850
+
851
+ The settings trigger lives in the step header band (paired with the keyboard-hints
852
+ trigger in `render_combined_step`), not the toolbar — matches `cjm-transcript-review`
853
+ placement (V2 / G3). Trigger contents swap on zone change via OOB (see
854
+ `routes/chrome.py:_handle_switch_chrome`); both modals persist in the DOM.
840
855
  """
841
856
  ```
842
857
 
@@ -924,7 +939,13 @@ from cjm_transcript_segment_align.components.sync_controls import (
924
939
 
925
940
  ``` python
926
941
  def render_sync_toggle_button() -> Any: # Sync toggle button element
927
- "Render the synced navigation toggle button for the seg toolbar."
942
+ """
943
+ Render the synced navigation toggle button for the seg toolbar.
944
+
945
+ Initial state: secondary_action (outline neutral = inactive).
946
+ JS toggle flips between btn-primary and btn-outline (primary_action ↔
947
+ secondary_action) by manipulating the literal class strings.
948
+ """
928
949
  ```
929
950
 
930
951
  ``` python
@@ -48,33 +48,33 @@ graph LR
48
48
  services_forced_alignment[services.forced_alignment<br/>forced_alignment]
49
49
 
50
50
  components_handlers --> components_step_renderer
51
- components_handlers --> services_forced_alignment
52
- components_handlers --> routes_forced_alignment
53
51
  components_handlers --> components_keyboard_config
52
+ components_handlers --> routes_forced_alignment
53
+ components_handlers --> services_forced_alignment
54
54
  components_handlers --> html_ids
55
55
  components_handlers --> components_sync_controls
56
56
  components_keyboard_config --> html_ids
57
57
  components_keyboard_config --> components_sync_controls
58
58
  components_step_renderer --> components_helpers
59
- components_step_renderer --> components_sync_controls
60
59
  components_step_renderer --> components_keyboard_config
61
60
  components_step_renderer --> components_toolbar_state
61
+ components_step_renderer --> components_sync_controls
62
62
  components_step_renderer --> html_ids
63
63
  components_toolbar_state --> components_sync_controls
64
64
  routes_chrome --> components_step_renderer
65
- routes_chrome --> components_handlers
66
65
  routes_chrome --> html_ids
67
66
  routes_chrome --> components_sync_controls
68
- routes_forced_alignment --> components_step_renderer
67
+ routes_chrome --> components_handlers
69
68
  routes_forced_alignment --> html_ids
70
69
  routes_forced_alignment --> components_sync_controls
71
- routes_init --> components_sync_controls
72
- routes_init --> services_forced_alignment
70
+ routes_forced_alignment --> components_step_renderer
73
71
  routes_init --> models
72
+ routes_init --> components_sync_controls
74
73
  routes_init --> components_handlers
75
- routes_init --> routes_forced_alignment
76
- routes_init --> html_ids
77
74
  routes_init --> routes_chrome
75
+ routes_init --> html_ids
76
+ routes_init --> services_forced_alignment
77
+ routes_init --> routes_forced_alignment
78
78
  routes_init --> components_step_renderer
79
79
  routes_init --> components_keyboard_config
80
80
  ```
@@ -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.30"
@@ -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
@@ -17,6 +17,8 @@ from cjm_fasthtml_daisyui.components.data_display.badge import badge, badge_styl
17
17
  from cjm_fasthtml_daisyui.components.feedback.alert import alert, alert_colors
18
18
  from cjm_fasthtml_daisyui.utilities.semantic_colors import text_dui, ring_dui
19
19
 
20
+ from cjm_fasthtml_design_system.text_tiers import text_tiers
21
+
20
22
  # Tailwind utilities
21
23
  from cjm_fasthtml_tailwind.utilities.spacing import p, m
22
24
  from cjm_fasthtml_tailwind.utilities.sizing import w, h, min_h
@@ -119,7 +121,7 @@ def _render_column_header(
119
121
  cls=combine_classes(
120
122
  font_size.sm, font_weight.bold,
121
123
  uppercase, tracking.wide,
122
- text_dui.base_content.opacity(50)
124
+ text_tiers.muted
123
125
  )
124
126
  ),
125
127
  Span(
@@ -233,7 +235,7 @@ def _placeholder(
233
235
  text:str, # Placeholder message
234
236
  ) -> Any: # Styled placeholder paragraph
235
237
  """Render a placeholder text element for uninitialized chrome containers."""
236
- return P(text, cls=combine_classes(font_size.sm, text_dui.base_content.opacity(50)))
238
+ return P(text, cls=combine_classes(font_size.sm, text_tiers.muted))
237
239
 
238
240
 
239
241
  def _render_shared_chrome(
@@ -242,21 +244,26 @@ def _render_shared_chrome(
242
244
  urls:SegmentationUrls=None, # Segmentation URL bundle (required when seg_state provided)
243
245
  extra_actions:tuple=(), # Extra toolbar elements (FA controls, sync toggle, etc.)
244
246
  nltk_split_disabled:bool=False, # Whether NLTK Split button is disabled
245
- ) -> tuple: # (toolbar, footer, settings_modals_container)
247
+ ) -> tuple: # (toolbar, footer, settings_modals_container, settings_trigger_container)
246
248
  """Render shared chrome containers, populated with segmentation content when initialized.
247
-
249
+
248
250
  Takes extracted state dicts from `extract_seg_state()` and `extract_alignment_state()`
249
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.
250
257
  """
251
258
  is_init = seg_state is not None and seg_state.get("is_initialized", False)
252
259
 
253
- # --- Settings modals (both persist in DOM, triggers swap with toolbar) ---
260
+ # --- Settings modals (both persist in DOM, trigger swaps on zone change) ---
254
261
  seg_modal, seg_trigger = render_card_stack_settings_modal(
255
262
  SEG_CS_CONFIG, SEG_CS_IDS,
256
263
  current_count=seg_state.get("visible_count", DEFAULT_VISIBLE_COUNT) if seg_state else DEFAULT_VISIBLE_COUNT,
257
264
  card_width=seg_state.get("card_width", DEFAULT_CARD_WIDTH) if seg_state else DEFAULT_CARD_WIDTH,
258
265
  )
259
- align_modal, align_trigger = render_card_stack_settings_modal(
266
+ align_modal, _align_trigger = render_card_stack_settings_modal(
260
267
  ALIGN_CS_CONFIG, ALIGN_CS_IDS,
261
268
  current_count=align_state.get("visible_count", 5) if align_state else 5,
262
269
  card_width=align_state.get("card_width", 40) if align_state else 40,
@@ -268,19 +275,24 @@ def _render_shared_chrome(
268
275
  id=CombinedHtmlIds.SETTINGS_MODALS,
269
276
  )
270
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
+
271
287
  # --- Toolbar ---
272
288
  if is_init and urls:
273
- toolbar_content = Div(
274
- seg_trigger,
275
- render_toolbar(
276
- reset_url=urls.reset,
277
- ai_split_url=urls.ai_split,
278
- undo_url=urls.undo,
279
- can_undo=(len(seg_state.get("history", [])) > 0),
280
- extra_actions=extra_actions,
281
- nltk_split_disabled=nltk_split_disabled,
282
- ),
283
- 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,
284
296
  )
285
297
  else:
286
298
  toolbar_content = _placeholder("Toolbar actions will appear here based on the active column.")
@@ -296,7 +308,7 @@ def _render_shared_chrome(
296
308
  segment_count = len(seg_segments)
297
309
  align_chunks = align_state.get("vad_chunks", []) if align_state else []
298
310
  chunk_count = len(align_chunks)
299
-
311
+
300
312
  # Seg footer
301
313
  if is_init and seg_segments:
302
314
  focused_index = seg_state.get("focused_index", 0)
@@ -323,7 +335,7 @@ def _render_shared_chrome(
323
335
  )
324
336
  )
325
337
 
326
- return toolbar, footer, settings_modals_container
338
+ return toolbar, footer, settings_modals_container, settings_trigger_container
327
339
 
328
340
  # %% ../../nbs/components/step_renderer.ipynb #c9d0e1f2
329
341
  # Shared column styling (reused by init handler for outerHTML swap)
@@ -550,7 +562,7 @@ def render_combined_step(
550
562
  )
551
563
  mini_stats_text = render_seg_mini_stats_text(segments)
552
564
 
553
- toolbar, footer, settings_modals_container = _render_shared_chrome(
565
+ toolbar, footer, settings_modals_container, settings_trigger_el = _render_shared_chrome(
554
566
  seg_state=seg_state,
555
567
  align_state=align_state,
556
568
  urls=seg_urls,
@@ -564,7 +576,7 @@ def render_combined_step(
564
576
  mini_stats_text=mini_stats_text,
565
577
  )
566
578
  else:
567
- toolbar, footer, settings_modals_container = _render_shared_chrome(
579
+ toolbar, footer, settings_modals_container, settings_trigger_el = _render_shared_chrome(
568
580
  align_state=align_state,
569
581
  )
570
582
  seg_col = _render_seg_column(
@@ -645,10 +657,16 @@ def render_combined_step(
645
657
  H2("Segment & Align", cls=combine_classes(font_size._3xl, font_weight.bold)),
646
658
  P(
647
659
  "Decompose text into segments and align with audio timestamps.",
648
- cls=combine_classes(text_dui.base_content.opacity(70), m.b(2))
660
+ cls=combine_classes(text_tiers.secondary, m.b(2))
649
661
  ),
650
662
  ),
651
- 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
+ ),
652
670
  cls=combine_classes(flex_display, items.start, justify.between),
653
671
  ),
654
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.28
3
+ Version: 0.0.30
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
@@ -21,7 +21,7 @@ Requires-Dist: cjm_transcript_segmentation>=0.0.20
21
21
  Requires-Dist: cjm_transcript_vad_align>=0.0.26
22
22
  Requires-Dist: cjm_fasthtml_job_monitor>=0.0.11
23
23
  Requires-Dist: cjm_fasthtml_web_audio>=0.0.11
24
- Requires-Dist: cjm_fasthtml_design_system>=0.0.7
24
+ Requires-Dist: cjm_fasthtml_design_system>=0.0.8
25
25
  Dynamic: license-file
26
26
 
27
27
  # cjm-transcript-segment-align
@@ -74,33 +74,33 @@ graph LR
74
74
  services_forced_alignment[services.forced_alignment<br/>forced_alignment]
75
75
 
76
76
  components_handlers --> components_step_renderer
77
- components_handlers --> services_forced_alignment
78
- components_handlers --> routes_forced_alignment
79
77
  components_handlers --> components_keyboard_config
78
+ components_handlers --> routes_forced_alignment
79
+ components_handlers --> services_forced_alignment
80
80
  components_handlers --> html_ids
81
81
  components_handlers --> components_sync_controls
82
82
  components_keyboard_config --> html_ids
83
83
  components_keyboard_config --> components_sync_controls
84
84
  components_step_renderer --> components_helpers
85
- components_step_renderer --> components_sync_controls
86
85
  components_step_renderer --> components_keyboard_config
87
86
  components_step_renderer --> components_toolbar_state
87
+ components_step_renderer --> components_sync_controls
88
88
  components_step_renderer --> html_ids
89
89
  components_toolbar_state --> components_sync_controls
90
90
  routes_chrome --> components_step_renderer
91
- routes_chrome --> components_handlers
92
91
  routes_chrome --> html_ids
93
92
  routes_chrome --> components_sync_controls
94
- routes_forced_alignment --> components_step_renderer
93
+ routes_chrome --> components_handlers
95
94
  routes_forced_alignment --> html_ids
96
95
  routes_forced_alignment --> components_sync_controls
97
- routes_init --> components_sync_controls
98
- routes_init --> services_forced_alignment
96
+ routes_forced_alignment --> components_step_renderer
99
97
  routes_init --> models
98
+ routes_init --> components_sync_controls
100
99
  routes_init --> components_handlers
101
- routes_init --> routes_forced_alignment
102
- routes_init --> html_ids
103
100
  routes_init --> routes_chrome
101
+ routes_init --> html_ids
102
+ routes_init --> services_forced_alignment
103
+ routes_init --> routes_forced_alignment
104
104
  routes_init --> components_step_renderer
105
105
  routes_init --> components_keyboard_config
106
106
  ```
@@ -141,13 +141,15 @@ async def _handle_switch_chrome(
141
141
  jm_trigger=None, # Pre-rendered job monitor trigger element
142
142
  fa_toggle_url:str="", # URL for FA toggle route
143
143
  fa_available:bool=False, # Whether FA plugin is available
144
- ) -> tuple: # OOB swaps for shared chrome containers
144
+ ) -> tuple: # OOB swaps for shared chrome containers (toolbar, footer, settings_trigger)
145
145
  """
146
146
  Switch shared chrome content based on active column.
147
147
 
148
148
  Client-side toolbar state restoration (sync button, auto-play toggle)
149
149
  is handled by the centralized settle handler from toolbar_state.py.
150
- Settings modals persist in the DOM — only the trigger buttons swap.
150
+ Settings modals persist in the DOM — only the trigger button (in the
151
+ step header band, not the toolbar) swaps to retarget the active
152
+ column's modal_id (V2 / G3 closure).
151
153
  """
152
154
  ```
153
155
 
@@ -194,7 +196,13 @@ def render_fa_toggle(
194
196
  active_presplit: str, # "nltk" or "forced_alignment"
195
197
  toggle_url: str, # URL for toggle route
196
198
  ) -> Any: # Toggle button group
197
- "Render the NLTK / Force Aligned toggle button group."
199
+ """
200
+ Render the NLTK / Force Aligned toggle button group.
201
+
202
+ Each leg uses primary_action (active) or secondary_action (inactive) — the
203
+ canonical V1 state-toggle composition — plus join_item for daisyui button
204
+ group joining.
205
+ """
198
206
  ```
199
207
 
200
208
  ``` python
@@ -498,6 +506,8 @@ def create_seg_init_chrome_wrapper(
498
506
  Saves nltk_presplit snapshot at init time for match detection.
499
507
  FA controls are rendered in the toolbar via extra_actions.
500
508
  Settings modals are rendered in a persistent container (both seg + align).
509
+ Settings trigger is OOB-swapped into the step header band (V2 / G3) — the
510
+ toolbar OOB no longer carries the trigger.
501
511
  """
502
512
  ```
503
513
 
@@ -831,12 +841,17 @@ def _render_shared_chrome(
831
841
  urls:SegmentationUrls=None, # Segmentation URL bundle (required when seg_state provided)
832
842
  extra_actions:tuple=(), # Extra toolbar elements (FA controls, sync toggle, etc.)
833
843
  nltk_split_disabled:bool=False, # Whether NLTK Split button is disabled
834
- ) -> tuple: # (toolbar, footer, settings_modals_container)
844
+ ) -> tuple: # (toolbar, footer, settings_modals_container, settings_trigger_container)
835
845
  """
836
846
  Render shared chrome containers, populated with segmentation content when initialized.
837
847
 
838
848
  Takes extracted state dicts from `extract_seg_state()` and `extract_alignment_state()`
839
849
  which contain deserialized TextSegment and VADChunk objects.
850
+
851
+ The settings trigger lives in the step header band (paired with the keyboard-hints
852
+ trigger in `render_combined_step`), not the toolbar — matches `cjm-transcript-review`
853
+ placement (V2 / G3). Trigger contents swap on zone change via OOB (see
854
+ `routes/chrome.py:_handle_switch_chrome`); both modals persist in the DOM.
840
855
  """
841
856
  ```
842
857
 
@@ -924,7 +939,13 @@ from cjm_transcript_segment_align.components.sync_controls import (
924
939
 
925
940
  ``` python
926
941
  def render_sync_toggle_button() -> Any: # Sync toggle button element
927
- "Render the synced navigation toggle button for the seg toolbar."
942
+ """
943
+ Render the synced navigation toggle button for the seg toolbar.
944
+
945
+ Initial state: secondary_action (outline neutral = inactive).
946
+ JS toggle flips between btn-primary and btn-outline (primary_action ↔
947
+ secondary_action) by manipulating the literal class strings.
948
+ """
928
949
  ```
929
950
 
930
951
  ``` python
@@ -4,4 +4,4 @@ cjm_transcript_segmentation>=0.0.20
4
4
  cjm_transcript_vad_align>=0.0.26
5
5
  cjm_fasthtml_job_monitor>=0.0.11
6
6
  cjm_fasthtml_web_audio>=0.0.11
7
- cjm_fasthtml_design_system>=0.0.7
7
+ 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.7']
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']
16
16
 
17
17
  [project.urls]
18
18
  Repository = "https://github.com/cj-mills/cjm-transcript-segment-align"
@@ -25,6 +25,6 @@ cjm_transcript_segment_align = "cjm_transcript_segment_align._modidx:d"
25
25
  version = {attr = "cjm_transcript_segment_align.__version__"}
26
26
 
27
27
  [tool.setuptools.packages.find]
28
- include = ["cjm_transcript_segment_align"]
28
+ include = ["cjm_transcript_segment_align*"]
29
29
 
30
30
  [tool.nbdev]
@@ -1 +0,0 @@
1
- __version__ = "0.0.28"