Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 37 additions & 3 deletions crates/transcript/src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,11 @@ pub fn render_transcript_segments(
.map(|started_at| started_at - base_started_at)
.unwrap_or(0);

let (words, mut assignments) =
let (words, user_assignments) =
offset_transcript_data(transcript.words, transcript.assignments, offset);
let channel_assignments =
let mut assignments =
channel_assignments_for_participants(&participant_human_ids, self_human_id.as_deref());
assignments.extend(channel_assignments);
assignments.extend(user_assignments);

let segments = build_segments(&words, &[], &assignments, Some(&segment_options));
all_segments.extend(segments);
Expand Down Expand Up @@ -464,6 +464,40 @@ mod tests {
assert_eq!(segments[1].text, "remote more");
}

#[test]
fn user_assignment_overrides_auto_channel_assignment() {
let segments = render_transcript_segments(RenderTranscriptRequest {
transcripts: vec![RenderTranscriptInput {
started_at: Some(0),
words: vec![
word("w1", " hello", 0, 100, 0),
word("w2", " remote", 120, 220, 1),
],
assignments: vec![channel_assignment("override", ChannelProfile::RemoteParty)],
}],
participant_human_ids: vec!["self".to_string(), "auto-remote".to_string()],
self_human_id: Some("self".to_string()),
humans: vec![
RenderTranscriptHuman {
human_id: "self".to_string(),
name: "Me".to_string(),
},
RenderTranscriptHuman {
human_id: "auto-remote".to_string(),
name: "Auto".to_string(),
},
RenderTranscriptHuman {
human_id: "override".to_string(),
name: "Override".to_string(),
},
],
});

assert_eq!(segments.len(), 2);
assert_eq!(segments[0].speaker_label, "Me");
assert_eq!(segments[1].speaker_label, "Override");
}

#[test]
fn keeps_missing_started_at_rows_anchored_at_zero() {
let segments = render_transcript_segments(RenderTranscriptRequest {
Expand Down
4 changes: 2 additions & 2 deletions crates/transcript/src/segments/collect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::collections::HashMap;
use crate::types::{ChannelProfile, Segment, SegmentBuilderOptions, SegmentKey, SegmentWord};

use super::model::{ProtoSegment, ResolvedWordFrame, SpeakerIdentity, SpeakerState};
use super::speakers::assign_complete_channel_human_id;
use super::speakers::assign_channel_human_id;

pub(super) fn collect_segments(
frames: Vec<ResolvedWordFrame>,
Expand Down Expand Up @@ -44,7 +44,7 @@ pub(super) fn propagate_identity(segments: &mut Vec<ProtoSegment>, speaker_state
let mut last_kept_idx: Option<usize> = None;

for read_index in 0..segments.len() {
assign_complete_channel_human_id(&mut segments[read_index], speaker_state);
assign_channel_human_id(&mut segments[read_index], speaker_state);

let should_merge = last_kept_key.as_ref().is_some_and(|last_key| {
*last_key == segments[read_index].key && segments[read_index].key.has_speaker_identity()
Expand Down
6 changes: 1 addition & 5 deletions crates/transcript/src/segments/speakers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,12 @@ pub(super) fn resolve_identities(
.collect()
}

pub(super) fn assign_complete_channel_human_id(segment: &mut ProtoSegment, state: &SpeakerState) {
pub(super) fn assign_channel_human_id(segment: &mut ProtoSegment, state: &SpeakerState) {
if segment.key.speaker_human_id.is_some() {
return;
}

let channel = segment.key.channel;
if !state.complete_channels.contains(&channel) {
return;
}

if let Some(human_id) = state.human_id_by_channel.get(&channel) {
segment.key = SegmentKey {
Comment on lines 91 to 96
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Intentional asymmetry: complete_channels guard kept in remember_identity but removed elsewhere

The complete_channels guard was removed from apply_identity_rules (line 119) and assign_complete_channel_human_id (line 93), but intentionally kept in remember_identity at line 155. This creates an asymmetry: reading channel identity (applying assignments to words/segments) no longer requires complete_channels, but writing channel identity at runtime (updating human_id_by_channel based on resolved word identities) still does. This prevents runtime words on non-complete channels from dynamically overwriting carefully set channel assignments. The asymmetry appears intentional but is worth documenting, as the function name assign_complete_channel_human_id no longer accurately describes its behavior — it now assigns channel human_id regardless of completeness.

(Refers to lines 88-101)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Expand All @@ -120,7 +117,6 @@ fn apply_identity_rules(
}

if identity.human_id.is_none()
&& state.complete_channels.contains(&word.channel)
&& let Some(human_id) = state.human_id_by_channel.get(&word.channel)
{
identity.human_id = Some(human_id.clone());
Expand Down
10 changes: 10 additions & 0 deletions crates/transcript/src/segments/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,16 @@ fn propagates_remote_party_identity_when_channel_marked_complete() {
assert_eq!(result[0].key.speaker_human_id.as_deref(), Some("remote"));
}

#[test]
fn applies_channel_assignment_even_without_complete_channel() {
let finals = vec![fw("0", 0, 100, 1), fw("1", 200, 300, 1)];
let assignments = vec![channel_human("remote", ChannelProfile::RemoteParty)];
// Default options only mark DirectMic as complete, not RemoteParty
let result = build_segments(&finals, &[], &assignments, None);
assert_eq!(result.len(), 1);
assert_eq!(result[0].key.speaker_human_id.as_deref(), Some("remote"));
}

#[test]
fn partial_word_ignores_its_own_runtime_hint_and_keeps_previous_segment_key() {
let finals = vec![fw_si("0", 0, 100, 0, 0)];
Expand Down
Loading