cjm-transcript-segment-align 0.0.6__tar.gz → 0.0.7__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 (31) hide show
  1. {cjm_transcript_segment_align-0.0.6/cjm_transcript_segment_align.egg-info → cjm_transcript_segment_align-0.0.7}/PKG-INFO +27 -15
  2. {cjm_transcript_segment_align-0.0.6 → cjm_transcript_segment_align-0.0.7}/README.md +26 -14
  3. cjm_transcript_segment_align-0.0.7/cjm_transcript_segment_align/__init__.py +1 -0
  4. {cjm_transcript_segment_align-0.0.6 → cjm_transcript_segment_align-0.0.7}/cjm_transcript_segment_align/_modidx.py +2 -0
  5. {cjm_transcript_segment_align-0.0.6 → cjm_transcript_segment_align-0.0.7}/cjm_transcript_segment_align/components/handlers.py +22 -22
  6. {cjm_transcript_segment_align-0.0.6 → cjm_transcript_segment_align-0.0.7}/cjm_transcript_segment_align/components/step_renderer.py +33 -18
  7. {cjm_transcript_segment_align-0.0.6 → cjm_transcript_segment_align-0.0.7}/cjm_transcript_segment_align/routes/chrome.py +39 -11
  8. {cjm_transcript_segment_align-0.0.6 → cjm_transcript_segment_align-0.0.7/cjm_transcript_segment_align.egg-info}/PKG-INFO +27 -15
  9. {cjm_transcript_segment_align-0.0.6 → cjm_transcript_segment_align-0.0.7}/settings.ini +1 -1
  10. cjm_transcript_segment_align-0.0.6/cjm_transcript_segment_align/__init__.py +0 -1
  11. {cjm_transcript_segment_align-0.0.6 → cjm_transcript_segment_align-0.0.7}/LICENSE +0 -0
  12. {cjm_transcript_segment_align-0.0.6 → cjm_transcript_segment_align-0.0.7}/MANIFEST.in +0 -0
  13. {cjm_transcript_segment_align-0.0.6 → cjm_transcript_segment_align-0.0.7}/cjm_transcript_segment_align/components/__init__.py +0 -0
  14. {cjm_transcript_segment_align-0.0.6 → cjm_transcript_segment_align-0.0.7}/cjm_transcript_segment_align/components/helpers.py +0 -0
  15. {cjm_transcript_segment_align-0.0.6 → cjm_transcript_segment_align-0.0.7}/cjm_transcript_segment_align/components/keyboard_config.py +0 -0
  16. {cjm_transcript_segment_align-0.0.6 → cjm_transcript_segment_align-0.0.7}/cjm_transcript_segment_align/html_ids.py +0 -0
  17. {cjm_transcript_segment_align-0.0.6 → cjm_transcript_segment_align-0.0.7}/cjm_transcript_segment_align/models.py +0 -0
  18. {cjm_transcript_segment_align-0.0.6 → cjm_transcript_segment_align-0.0.7}/cjm_transcript_segment_align/routes/__init__.py +0 -0
  19. {cjm_transcript_segment_align-0.0.6 → cjm_transcript_segment_align-0.0.7}/cjm_transcript_segment_align/routes/forced_alignment.py +0 -0
  20. {cjm_transcript_segment_align-0.0.6 → cjm_transcript_segment_align-0.0.7}/cjm_transcript_segment_align/routes/init.py +0 -0
  21. {cjm_transcript_segment_align-0.0.6 → cjm_transcript_segment_align-0.0.7}/cjm_transcript_segment_align/services/__init__.py +0 -0
  22. {cjm_transcript_segment_align-0.0.6 → cjm_transcript_segment_align-0.0.7}/cjm_transcript_segment_align/services/forced_alignment.py +0 -0
  23. {cjm_transcript_segment_align-0.0.6 → cjm_transcript_segment_align-0.0.7}/cjm_transcript_segment_align.egg-info/SOURCES.txt +0 -0
  24. {cjm_transcript_segment_align-0.0.6 → cjm_transcript_segment_align-0.0.7}/cjm_transcript_segment_align.egg-info/dependency_links.txt +0 -0
  25. {cjm_transcript_segment_align-0.0.6 → cjm_transcript_segment_align-0.0.7}/cjm_transcript_segment_align.egg-info/entry_points.txt +0 -0
  26. {cjm_transcript_segment_align-0.0.6 → cjm_transcript_segment_align-0.0.7}/cjm_transcript_segment_align.egg-info/not-zip-safe +0 -0
  27. {cjm_transcript_segment_align-0.0.6 → cjm_transcript_segment_align-0.0.7}/cjm_transcript_segment_align.egg-info/requires.txt +0 -0
  28. {cjm_transcript_segment_align-0.0.6 → cjm_transcript_segment_align-0.0.7}/cjm_transcript_segment_align.egg-info/top_level.txt +0 -0
  29. {cjm_transcript_segment_align-0.0.6 → cjm_transcript_segment_align-0.0.7}/pyproject.toml +0 -0
  30. {cjm_transcript_segment_align-0.0.6 → cjm_transcript_segment_align-0.0.7}/setup.cfg +0 -0
  31. {cjm_transcript_segment_align-0.0.6 → cjm_transcript_segment_align-0.0.7}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: cjm-transcript-segment-align
3
- Version: 0.0.6
3
+ Version: 0.0.7
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
  Home-page: https://github.com/cj-mills/cjm-transcript-segment-align
6
6
  Author: Christian J. Mills
@@ -79,27 +79,27 @@ graph LR
79
79
  routes_init[routes.init<br/>routes/init]
80
80
  services_forced_alignment[services.forced_alignment<br/>forced_alignment]
81
81
 
82
- components_handlers --> components_keyboard_config
83
82
  components_handlers --> services_forced_alignment
84
- components_handlers --> html_ids
85
83
  components_handlers --> components_step_renderer
84
+ components_handlers --> components_keyboard_config
86
85
  components_handlers --> routes_forced_alignment
86
+ components_handlers --> html_ids
87
87
  components_keyboard_config --> html_ids
88
88
  components_step_renderer --> components_keyboard_config
89
89
  components_step_renderer --> components_helpers
90
90
  components_step_renderer --> html_ids
91
- routes_chrome --> html_ids
92
91
  routes_chrome --> components_step_renderer
93
92
  routes_chrome --> components_handlers
94
- routes_forced_alignment --> html_ids
93
+ routes_chrome --> html_ids
95
94
  routes_forced_alignment --> components_step_renderer
95
+ routes_forced_alignment --> html_ids
96
+ routes_init --> routes_forced_alignment
97
+ routes_init --> routes_chrome
96
98
  routes_init --> components_handlers
99
+ routes_init --> components_keyboard_config
100
+ routes_init --> services_forced_alignment
97
101
  routes_init --> models
98
102
  routes_init --> html_ids
99
- routes_init --> routes_forced_alignment
100
- routes_init --> services_forced_alignment
101
- routes_init --> components_keyboard_config
102
- routes_init --> routes_chrome
103
103
  routes_init --> components_step_renderer
104
104
  ```
105
105
 
@@ -128,6 +128,17 @@ from cjm_transcript_segment_align.routes.chrome import (
128
128
 
129
129
  #### Functions
130
130
 
131
+ ``` python
132
+ def _restore_align_auto_nav_js() -> str
133
+ """
134
+ Generate JS to sync the auto-navigate toggle checkbox with the Web Audio state.
135
+
136
+ After chrome switch re-renders the toolbar, the checkbox starts unchecked.
137
+ This reads the JS state (source of truth) and restores the checkbox.
138
+ Included inside the toolbar OOB so it runs after HTMX inserts the new content.
139
+ """
140
+ ```
141
+
131
142
  ``` python
132
143
  async def _handle_switch_chrome(
133
144
  state_store:SQLiteWorkflowStateStore, # State store instance
@@ -814,16 +825,17 @@ def render_alignment_status(
814
825
 
815
826
  ``` python
816
827
  def render_footer_inner_content(
817
- column_footer:Any, # Column-specific footer content (decomp or align)
828
+ seg_footer:Any, # Segmentation footer content (or None if not initialized)
829
+ align_footer:Any, # Alignment footer content (or None if not initialized)
818
830
  segment_count:int, # Number of text segments
819
831
  chunk_count:int, # Number of VAD chunks
820
- ) -> Any: # Styled wrapper div with column footer and alignment status
832
+ ) -> Any: # Styled wrapper div with both column footers and alignment status
821
833
  """
822
- Render the footer inner content with consistent styling.
834
+ Render the footer inner content with both column footers always present.
823
835
 
824
- This ensures the footer layout (justify-between) is preserved across
825
- all OOB swaps. Both the column-specific footer content and the
826
- alignment status indicator are wrapped in a flex container.
836
+ Both footers remain in the DOM so their internal OOB targets (progress
837
+ indicators, source position) are always valid regardless of which column
838
+ is active. This enables cross-zone features like auto-play.
827
839
  """
828
840
  ```
829
841
 
@@ -43,27 +43,27 @@ graph LR
43
43
  routes_init[routes.init<br/>routes/init]
44
44
  services_forced_alignment[services.forced_alignment<br/>forced_alignment]
45
45
 
46
- components_handlers --> components_keyboard_config
47
46
  components_handlers --> services_forced_alignment
48
- components_handlers --> html_ids
49
47
  components_handlers --> components_step_renderer
48
+ components_handlers --> components_keyboard_config
50
49
  components_handlers --> routes_forced_alignment
50
+ components_handlers --> html_ids
51
51
  components_keyboard_config --> html_ids
52
52
  components_step_renderer --> components_keyboard_config
53
53
  components_step_renderer --> components_helpers
54
54
  components_step_renderer --> html_ids
55
- routes_chrome --> html_ids
56
55
  routes_chrome --> components_step_renderer
57
56
  routes_chrome --> components_handlers
58
- routes_forced_alignment --> html_ids
57
+ routes_chrome --> html_ids
59
58
  routes_forced_alignment --> components_step_renderer
59
+ routes_forced_alignment --> html_ids
60
+ routes_init --> routes_forced_alignment
61
+ routes_init --> routes_chrome
60
62
  routes_init --> components_handlers
63
+ routes_init --> components_keyboard_config
64
+ routes_init --> services_forced_alignment
61
65
  routes_init --> models
62
66
  routes_init --> html_ids
63
- routes_init --> routes_forced_alignment
64
- routes_init --> services_forced_alignment
65
- routes_init --> components_keyboard_config
66
- routes_init --> routes_chrome
67
67
  routes_init --> components_step_renderer
68
68
  ```
69
69
 
@@ -92,6 +92,17 @@ from cjm_transcript_segment_align.routes.chrome import (
92
92
 
93
93
  #### Functions
94
94
 
95
+ ``` python
96
+ def _restore_align_auto_nav_js() -> str
97
+ """
98
+ Generate JS to sync the auto-navigate toggle checkbox with the Web Audio state.
99
+
100
+ After chrome switch re-renders the toolbar, the checkbox starts unchecked.
101
+ This reads the JS state (source of truth) and restores the checkbox.
102
+ Included inside the toolbar OOB so it runs after HTMX inserts the new content.
103
+ """
104
+ ```
105
+
95
106
  ``` python
96
107
  async def _handle_switch_chrome(
97
108
  state_store:SQLiteWorkflowStateStore, # State store instance
@@ -778,16 +789,17 @@ def render_alignment_status(
778
789
 
779
790
  ``` python
780
791
  def render_footer_inner_content(
781
- column_footer:Any, # Column-specific footer content (decomp or align)
792
+ seg_footer:Any, # Segmentation footer content (or None if not initialized)
793
+ align_footer:Any, # Alignment footer content (or None if not initialized)
782
794
  segment_count:int, # Number of text segments
783
795
  chunk_count:int, # Number of VAD chunks
784
- ) -> Any: # Styled wrapper div with column footer and alignment status
796
+ ) -> Any: # Styled wrapper div with both column footers and alignment status
785
797
  """
786
- Render the footer inner content with consistent styling.
798
+ Render the footer inner content with both column footers always present.
787
799
 
788
- This ensures the footer layout (justify-between) is preserved across
789
- all OOB swaps. Both the column-specific footer content and the
790
- alignment status indicator are wrapped in a flex container.
800
+ Both footers remain in the DOM so their internal OOB targets (progress
801
+ indicators, source position) are always valid regardless of which column
802
+ is active. This enables cross-zone features like auto-play.
791
803
  """
792
804
  ```
793
805
 
@@ -0,0 +1 @@
1
+ __version__ = "0.0.7"
@@ -73,6 +73,8 @@ d = { 'settings': { 'branch': 'main',
73
73
  'cjm_transcript_segment_align/models.py')},
74
74
  'cjm_transcript_segment_align.routes.chrome': { 'cjm_transcript_segment_align.routes.chrome._handle_switch_chrome': ( 'routes/chrome.html#_handle_switch_chrome',
75
75
  'cjm_transcript_segment_align/routes/chrome.py'),
76
+ 'cjm_transcript_segment_align.routes.chrome._restore_align_auto_nav_js': ( 'routes/chrome.html#_restore_align_auto_nav_js',
77
+ 'cjm_transcript_segment_align/routes/chrome.py'),
76
78
  'cjm_transcript_segment_align.routes.chrome.init_chrome_router': ( 'routes/chrome.html#init_chrome_router',
77
79
  'cjm_transcript_segment_align/routes/chrome.py')},
78
80
  'cjm_transcript_segment_align.routes.forced_alignment': { 'cjm_transcript_segment_align.routes.forced_alignment._handle_fa_toggle': ( 'routes/forced_alignment.html#_handle_fa_toggle',
@@ -53,6 +53,7 @@ from cjm_transcript_vad_align.routes.core import (
53
53
  from cjm_transcript_vad_align.routes.handlers import (
54
54
  AlignInitResult, _handle_align_init,
55
55
  )
56
+ from cjm_transcript_vad_align.components.step_renderer import render_align_footer_content
56
57
  from cjm_transcript_vad_align.services.alignment import AlignmentService
57
58
  from cjm_transcript_source_select.services.source import SourceService
58
59
 
@@ -410,6 +411,7 @@ def create_seg_init_chrome_wrapper(
410
411
  workflow_state = state_store.get_state(workflow_id, session_id)
411
412
  step_states = workflow_state.get("step_states", {})
412
413
  seg_state = step_states.get("segmentation", {})
414
+ align_state = step_states.get("alignment", {})
413
415
 
414
416
  # Only save nltk_presplit if not already saved (avoid overwriting on re-init)
415
417
  if "nltk_presplit" not in seg_state:
@@ -418,7 +420,7 @@ def create_seg_init_chrome_wrapper(
418
420
  workflow_state["step_states"] = step_states
419
421
  state_store.update_state(workflow_id, session_id, workflow_state)
420
422
 
421
- chunk_count = len(step_states.get("alignment", {}).get("vad_chunks", []))
423
+ chunk_count = len(align_state.get("vad_chunks", []))
422
424
  segment_count = len(result.segments)
423
425
 
424
426
  # Build combined KB system with both zones
@@ -472,12 +474,20 @@ def create_seg_init_chrome_wrapper(
472
474
  hx_swap_oob="innerHTML"
473
475
  )
474
476
 
475
- # Footer OOB with alignment status
477
+ # Footer OOB with both column footers + alignment status
478
+ seg_footer = render_seg_footer_content(result.segments, result.focused_index)
479
+
480
+ # Align footer (if alignment is initialized)
481
+ is_align_init = align_state.get("is_initialized", False)
482
+ if is_align_init and align_state.get("vad_chunks"):
483
+ align_chunks = [VADChunk.from_dict(c) for c in align_state["vad_chunks"]]
484
+ align_focused = align_state.get("focused_chunk_index", 0)
485
+ align_footer = render_align_footer_content(align_chunks, align_focused)
486
+ else:
487
+ align_footer = None
488
+
476
489
  footer_oob = Div(
477
- render_footer_inner_content(
478
- render_seg_footer_content(result.segments, result.focused_index),
479
- segment_count, chunk_count
480
- ),
490
+ render_footer_inner_content(seg_footer, align_footer, segment_count, chunk_count),
481
491
  id=CombinedHtmlIds.SHARED_FOOTER,
482
492
  hx_swap_oob="innerHTML"
483
493
  )
@@ -513,11 +523,7 @@ def create_align_init_chrome_wrapper() -> Callable: # Wrapped handler that adds
513
523
  visible_count:int=5,
514
524
  card_width:int=40,
515
525
  ):
516
- """Wrapped align init that adds mini-stats and alignment status in footer."""
517
- from cjm_transcript_segmentation.components.step_renderer import render_seg_footer_content
518
- from cjm_fasthtml_tailwind.utilities.typography import font_size as fs
519
- from cjm_fasthtml_daisyui.utilities.semantic_colors import text_dui as td
520
-
526
+ """Wrapped align init that adds mini-stats and both footers."""
521
527
  # Call pure domain handler
522
528
  result: AlignInitResult = await _handle_align_init(
523
529
  state_store, workflow_id, source_service, alignment_service,
@@ -535,21 +541,15 @@ def create_align_init_chrome_wrapper() -> Callable: # Wrapped handler that adds
535
541
  # Mini-stats badge OOB
536
542
  mini_stats_oob = render_align_mini_stats_badge(result.chunks, oob=True)
537
543
 
538
- # Footer OOB with alignment status (targets sd-shared-footer which always exists,
539
- # unlike sd-alignment-status which is inside the footer and may not exist yet)
544
+ # Footer OOB with both column footers + alignment status
540
545
  segments = [TextSegment.from_dict(s) for s in seg_state.get("segments", [])]
541
- focused_index = seg_state.get("focused_index", 0)
546
+ seg_focused = seg_state.get("focused_index", 0)
547
+ seg_footer = render_seg_footer_content(segments, seg_focused) if segments else None
542
548
 
543
- if segments:
544
- column_footer = render_seg_footer_content(segments, focused_index)
545
- else:
546
- column_footer = Span(
547
- "Footer with progress and timestamp details will appear here.",
548
- cls=combine_classes(fs.sm, td.base_content.opacity(50))
549
- )
549
+ align_footer = render_align_footer_content(result.chunks, 0)
550
550
 
551
551
  footer_oob = Div(
552
- render_footer_inner_content(column_footer, segment_count, chunk_count),
552
+ render_footer_inner_content(seg_footer, align_footer, segment_count, chunk_count),
553
553
  id=CombinedHtmlIds.SHARED_FOOTER,
554
554
  hx_swap_oob="innerHTML"
555
555
  )
@@ -27,7 +27,7 @@ from cjm_fasthtml_tailwind.utilities.borders import border
27
27
  from cjm_fasthtml_tailwind.utilities.effects import opacity, ring
28
28
  from cjm_fasthtml_tailwind.utilities.transitions_and_animation import transition, duration
29
29
  from cjm_fasthtml_tailwind.utilities.flexbox_and_grid import (
30
- flex_display, flex_direction, justify, items, gap, grow
30
+ flex_display, flex_direction, justify, items, gap, grow, flex_wrap
31
31
  )
32
32
  from cjm_fasthtml_tailwind.core.base import combine_classes
33
33
 
@@ -60,6 +60,7 @@ from cjm_transcript_segmentation.components.step_renderer import (
60
60
  # Alignment composable renderers
61
61
  from cjm_transcript_vad_align.components.step_renderer import (
62
62
  render_align_column_body, render_align_mini_stats_text,
63
+ render_align_footer_content,
63
64
  )
64
65
  from cjm_transcript_vad_align.components.card_stack_config import (
65
66
  ALIGN_CS_IDS,
@@ -180,24 +181,26 @@ def render_alignment_status(
180
181
  )
181
182
 
182
183
 
183
- # Shared footer inner content styling
184
- _FOOTER_INNER_CLS = combine_classes(flex_display, justify.between, items.center, w.full, gap(4))
184
+ # Shared footer inner content styling — flex-wrap allows vertical stacking on narrow viewports
185
+ _FOOTER_INNER_CLS = combine_classes(flex_display, flex_wrap.wrap, justify.between, items.center, w.full, gap(4))
185
186
 
186
187
 
187
188
  #| export
188
189
  def render_footer_inner_content(
189
- column_footer:Any, # Column-specific footer content (decomp or align)
190
+ seg_footer:Any, # Segmentation footer content (or None if not initialized)
191
+ align_footer:Any, # Alignment footer content (or None if not initialized)
190
192
  segment_count:int, # Number of text segments
191
193
  chunk_count:int, # Number of VAD chunks
192
- ) -> Any: # Styled wrapper div with column footer and alignment status
193
- """Render the footer inner content with consistent styling.
194
+ ) -> Any: # Styled wrapper div with both column footers and alignment status
195
+ """Render the footer inner content with both column footers always present.
194
196
 
195
- This ensures the footer layout (justify-between) is preserved across
196
- all OOB swaps. Both the column-specific footer content and the
197
- alignment status indicator are wrapped in a flex container.
197
+ Both footers remain in the DOM so their internal OOB targets (progress
198
+ indicators, source position) are always valid regardless of which column
199
+ is active. This enables cross-zone features like auto-play.
198
200
  """
199
201
  return Div(
200
- column_footer,
202
+ seg_footer or Span(),
203
+ align_footer or Span(),
201
204
  render_alignment_status(segment_count, chunk_count),
202
205
  cls=_FOOTER_INNER_CLS
203
206
  )
@@ -260,18 +263,30 @@ def _render_shared_chrome(
260
263
  cls=str(p(2))
261
264
  )
262
265
 
263
- # --- Footer with alignment status ---
264
- segment_count = len(seg_state.get("segments", [])) if seg_state else 0
265
- chunk_count = len(align_state.get("vad_chunks", [])) if align_state else 0
266
+ # --- Footer with both column footers + alignment status ---
267
+ # Note: seg_state["segments"] and align_state["vad_chunks"] are already deserialized
268
+ # objects from extract_seg_state/extract_alignment_state don't call from_dict() again
269
+ seg_segments = seg_state.get("segments", []) if seg_state else []
270
+ segment_count = len(seg_segments)
271
+ align_chunks = align_state.get("vad_chunks", []) if align_state else []
272
+ chunk_count = len(align_chunks)
266
273
 
267
- if is_init:
268
- segments = seg_state.get("segments", [])
274
+ # Seg footer
275
+ if is_init and seg_segments:
269
276
  focused_index = seg_state.get("focused_index", 0)
270
- column_footer = render_seg_footer_content(segments, focused_index)
277
+ seg_footer = render_seg_footer_content(seg_segments, focused_index)
278
+ else:
279
+ seg_footer = None
280
+
281
+ # Align footer
282
+ is_align_init = align_state is not None and align_state.get("is_initialized", False)
283
+ if is_align_init and align_chunks:
284
+ align_focused = align_state.get("focused_index", 0)
285
+ align_footer = render_align_footer_content(align_chunks, align_focused)
271
286
  else:
272
- column_footer = _placeholder("Footer with progress and timestamp details will appear here.")
287
+ align_footer = None
273
288
 
274
- footer_inner = render_footer_inner_content(column_footer, segment_count, chunk_count)
289
+ footer_inner = render_footer_inner_content(seg_footer, align_footer, segment_count, chunk_count)
275
290
 
276
291
  footer = Div(
277
292
  footer_inner,
@@ -8,7 +8,7 @@ __all__ = ['DEBUG_SWITCH_CHROME', 'init_chrome_router']
8
8
  # %% ../../nbs/routes/chrome.ipynb #c3d4e5f6
9
9
  from typing import Tuple, Dict, Callable
10
10
 
11
- from fasthtml.common import APIRouter, Div
11
+ from fasthtml.common import APIRouter, Div, Script
12
12
 
13
13
  from cjm_fasthtml_card_stack.components.controls import render_width_slider
14
14
  from cjm_fasthtml_card_stack.core.constants import DEFAULT_VISIBLE_COUNT, DEFAULT_CARD_WIDTH
@@ -34,6 +34,7 @@ from cjm_transcript_segmentation.components.card_stack_config import (
34
34
  from cjm_transcript_vad_align.components.step_renderer import (
35
35
  render_align_toolbar, render_align_footer_content,
36
36
  )
37
+ from cjm_transcript_vad_align.components.audio_controls import AlignAudioControlIds
37
38
  from cjm_transcript_vad_align.components.card_stack_config import (
38
39
  ALIGN_CS_CONFIG, ALIGN_CS_IDS,
39
40
  )
@@ -46,11 +47,28 @@ from cjm_transcript_segment_align.components.handlers import (
46
47
  build_fa_extra_actions, segments_match_presplit,
47
48
  )
48
49
 
50
+ # Web Audio state key for auto-navigate restore
51
+ from cjm_transcript_vad_align.components.callbacks import ALIGN_AUDIO_CONFIG
49
52
 
50
53
 
51
54
  DEBUG_SWITCH_CHROME = False
52
55
 
53
56
  # %% ../../nbs/routes/chrome.ipynb #e5f6a7b8
57
+ def _restore_align_auto_nav_js() -> str:
58
+ """Generate JS to sync the auto-navigate toggle checkbox with the Web Audio state.
59
+
60
+ After chrome switch re-renders the toolbar, the checkbox starts unchecked.
61
+ This reads the JS state (source of truth) and restores the checkbox.
62
+ Included inside the toolbar OOB so it runs after HTMX inserts the new content.
63
+ """
64
+ sk = ALIGN_AUDIO_CONFIG.state_key
65
+ toggle_id = AlignAudioControlIds.AUTO_NAV_TOGGLE
66
+ return f"""
67
+ var _cb = document.getElementById('{toggle_id}');
68
+ if (_cb && window.{sk}) _cb.checked = window.{sk}.autoNavigate || false;
69
+ """
70
+
71
+
54
72
  async def _handle_switch_chrome(
55
73
  state_store:SQLiteWorkflowStateStore, # State store instance
56
74
  workflow_id:str, # Workflow identifier
@@ -80,7 +98,7 @@ async def _handle_switch_chrome(
80
98
  chunk_count = len(align_state.get("vad_chunks", []))
81
99
 
82
100
  if active_column == "seg":
83
- # Segmentation chrome
101
+ # Segmentation chrome (toolbar + controls)
84
102
  segments = [TextSegment.from_dict(s) for s in seg_state.get("segments", [])]
85
103
  history = seg_state.get("history", [])
86
104
  focused_index = seg_state.get("focused_index", 0)
@@ -108,21 +126,31 @@ async def _handle_switch_chrome(
108
126
  nltk_split_disabled=nltk_disabled,
109
127
  )
110
128
  controls_content = render_width_slider(SEG_CS_CONFIG, SEG_CS_IDS, card_width=card_width)
111
- column_footer = render_seg_footer_content(segments, focused_index)
112
129
  else:
113
- # Alignment chrome
114
- chunks = [VADChunk.from_dict(c) for c in align_state.get("vad_chunks", [])]
115
- focused_index = align_state.get("focused_chunk_index", 0)
130
+ # Alignment chrome (toolbar with auto-play toggle + controls)
116
131
  visible_count = align_state.get("visible_count", 5)
117
132
  is_auto_mode = align_state.get("is_auto_mode", False)
118
133
  card_width = align_state.get("card_width", 40)
119
134
 
120
- toolbar_content = render_align_toolbar(
121
- visible_count=visible_count,
122
- is_auto_mode=is_auto_mode,
135
+ # Toolbar now includes auto-play toggle internally;
136
+ # restore script syncs checkbox with JS state after re-render
137
+ toolbar_content = Div(
138
+ render_align_toolbar(
139
+ visible_count=visible_count,
140
+ is_auto_mode=is_auto_mode,
141
+ ),
142
+ Script(_restore_align_auto_nav_js()),
123
143
  )
124
144
  controls_content = render_width_slider(ALIGN_CS_CONFIG, ALIGN_CS_IDS, card_width=card_width)
125
- column_footer = render_align_footer_content(chunks, focused_index)
145
+
146
+ # --- Footer: both column footers always present ---
147
+ seg_segments = [TextSegment.from_dict(s) for s in seg_state.get("segments", [])]
148
+ seg_focused = seg_state.get("focused_index", 0)
149
+ seg_footer = render_seg_footer_content(seg_segments, seg_focused) if seg_segments else None
150
+
151
+ align_chunks = [VADChunk.from_dict(c) for c in align_state.get("vad_chunks", [])]
152
+ align_focused = align_state.get("focused_chunk_index", 0)
153
+ align_footer = render_align_footer_content(align_chunks, align_focused) if align_chunks else None
126
154
 
127
155
  if DEBUG_SWITCH_CHROME:
128
156
  print(f"[SWITCH_CHROME] returning OOB swaps for {active_column}")
@@ -139,7 +167,7 @@ async def _handle_switch_chrome(
139
167
  hx_swap_oob="innerHTML"
140
168
  )
141
169
  footer_oob = Div(
142
- render_footer_inner_content(column_footer, segment_count, chunk_count),
170
+ render_footer_inner_content(seg_footer, align_footer, segment_count, chunk_count),
143
171
  id=CombinedHtmlIds.SHARED_FOOTER,
144
172
  hx_swap_oob="innerHTML"
145
173
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: cjm-transcript-segment-align
3
- Version: 0.0.6
3
+ Version: 0.0.7
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
  Home-page: https://github.com/cj-mills/cjm-transcript-segment-align
6
6
  Author: Christian J. Mills
@@ -79,27 +79,27 @@ graph LR
79
79
  routes_init[routes.init<br/>routes/init]
80
80
  services_forced_alignment[services.forced_alignment<br/>forced_alignment]
81
81
 
82
- components_handlers --> components_keyboard_config
83
82
  components_handlers --> services_forced_alignment
84
- components_handlers --> html_ids
85
83
  components_handlers --> components_step_renderer
84
+ components_handlers --> components_keyboard_config
86
85
  components_handlers --> routes_forced_alignment
86
+ components_handlers --> html_ids
87
87
  components_keyboard_config --> html_ids
88
88
  components_step_renderer --> components_keyboard_config
89
89
  components_step_renderer --> components_helpers
90
90
  components_step_renderer --> html_ids
91
- routes_chrome --> html_ids
92
91
  routes_chrome --> components_step_renderer
93
92
  routes_chrome --> components_handlers
94
- routes_forced_alignment --> html_ids
93
+ routes_chrome --> html_ids
95
94
  routes_forced_alignment --> components_step_renderer
95
+ routes_forced_alignment --> html_ids
96
+ routes_init --> routes_forced_alignment
97
+ routes_init --> routes_chrome
96
98
  routes_init --> components_handlers
99
+ routes_init --> components_keyboard_config
100
+ routes_init --> services_forced_alignment
97
101
  routes_init --> models
98
102
  routes_init --> html_ids
99
- routes_init --> routes_forced_alignment
100
- routes_init --> services_forced_alignment
101
- routes_init --> components_keyboard_config
102
- routes_init --> routes_chrome
103
103
  routes_init --> components_step_renderer
104
104
  ```
105
105
 
@@ -128,6 +128,17 @@ from cjm_transcript_segment_align.routes.chrome import (
128
128
 
129
129
  #### Functions
130
130
 
131
+ ``` python
132
+ def _restore_align_auto_nav_js() -> str
133
+ """
134
+ Generate JS to sync the auto-navigate toggle checkbox with the Web Audio state.
135
+
136
+ After chrome switch re-renders the toolbar, the checkbox starts unchecked.
137
+ This reads the JS state (source of truth) and restores the checkbox.
138
+ Included inside the toolbar OOB so it runs after HTMX inserts the new content.
139
+ """
140
+ ```
141
+
131
142
  ``` python
132
143
  async def _handle_switch_chrome(
133
144
  state_store:SQLiteWorkflowStateStore, # State store instance
@@ -814,16 +825,17 @@ def render_alignment_status(
814
825
 
815
826
  ``` python
816
827
  def render_footer_inner_content(
817
- column_footer:Any, # Column-specific footer content (decomp or align)
828
+ seg_footer:Any, # Segmentation footer content (or None if not initialized)
829
+ align_footer:Any, # Alignment footer content (or None if not initialized)
818
830
  segment_count:int, # Number of text segments
819
831
  chunk_count:int, # Number of VAD chunks
820
- ) -> Any: # Styled wrapper div with column footer and alignment status
832
+ ) -> Any: # Styled wrapper div with both column footers and alignment status
821
833
  """
822
- Render the footer inner content with consistent styling.
834
+ Render the footer inner content with both column footers always present.
823
835
 
824
- This ensures the footer layout (justify-between) is preserved across
825
- all OOB swaps. Both the column-specific footer content and the
826
- alignment status indicator are wrapped in a flex container.
836
+ Both footers remain in the DOM so their internal OOB targets (progress
837
+ indicators, source position) are always valid regardless of which column
838
+ is active. This enables cross-zone features like auto-play.
827
839
  """
828
840
  ```
829
841
 
@@ -1,7 +1,7 @@
1
1
  [DEFAULT]
2
2
  repo = cjm-transcript-segment-align
3
3
  lib_name = cjm-transcript-segment-align
4
- version = 0.0.6
4
+ version = 0.0.7
5
5
  min_python = 3.12
6
6
  license = apache2
7
7
  black_formatting = False
@@ -1 +0,0 @@
1
- __version__ = "0.0.6"