From f9c753c2a560ffe19fab3f47a32235ccf879fd2c Mon Sep 17 00:00:00 2001 From: 2ynn Date: Tue, 6 Oct 2026 18:05:34 -0400 Subject: [PATCH] fix(cli): keep punctuation-only speaker turns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bounding lineMarkerStart at the previous label also made "only whitespace since the previous label" count as the start of a line, so a short turn after a label was read as the next label's list marker and dropped with it. #71 fixed that for numbers (`Alice: 42. Bob:`), but punctuation-only turns were still lost: `Alice: ... Bob: Yes?`, `Alice: β€” Bob: Sorry, go on.` and `Alice: ?! Bob: What?` each sent only Bob's turn. Judge the line start on the whole text for every marker. The scan still stops at the previous label, so emoji names (`πŸ˜€: πŸ€–:`) don't panic. The live API accepts annotated turns of only `...`, `β€”` or `-` (HTTP 200). --- internal/cli/custom/custom_test.go | 8 +++++--- internal/cli/custom/tts.go | 16 +++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/internal/cli/custom/custom_test.go b/internal/cli/custom/custom_test.go index 1c30853..992cb6d 100644 --- a/internal/cli/custom/custom_test.go +++ b/internal/cli/custom/custom_test.go @@ -516,11 +516,13 @@ func TestSpeakerTurns(t *testing.T) { {"Alice: 42. Bob: That is correct.", []string{"Alice", "Bob"}, []speakerTurn{turn("Alice", "42."), turn("Bob", "That is correct.")}}, // A combining mark is part of the word: neither "\u0936\u094d\u0930\u0940\u0930\u093e\u092e:" nor "\u0930\u093e\u092e\u0940:" holds a label for "\u0930\u093e\u092e". {"Alice: \u0936\u094d\u0930\u0940\u0930\u093e\u092e: \u0930\u093e\u092e\u0940: hi. \u0930\u093e\u092e: yo.", []string{"Alice", "\u0930\u093e\u092e"}, []speakerTurn{turn("Alice", "\u0936\u094d\u0930\u0940\u0930\u093e\u092e: \u0930\u093e\u092e\u0940: hi."), turn("\u0930\u093e\u092e", "yo.")}}, - // Marker scanning stops at the previous label: names made of marker runes - // stay labels, and a marker right after an empty label goes with the next. + // Marker scanning stops at the previous label, so names made of marker + // runes stay labels; a punctuation-only turn after a label is kept. {"\U0001F600: \U0001F916: hi.", []string{"\U0001F600", "\U0001F916"}, []speakerTurn{turn("\U0001F916", "hi.")}}, {"\U0001F600: hi.\n\U0001F916: yo.", []string{"\U0001F600", "\U0001F916"}, []speakerTurn{turn("\U0001F600", "hi."), turn("\U0001F916", "yo.")}}, - {"Alice: - Bob: yo.", []string{"Alice", "Bob"}, []speakerTurn{bob}}, + {"Alice: - Bob: yo.", []string{"Alice", "Bob"}, []speakerTurn{turn("Alice", "-"), bob}}, + {"Alice: ... Bob: yo.", []string{"Alice", "Bob"}, []speakerTurn{turn("Alice", "..."), bob}}, + {"Alice: \u2014 Bob: yo.", []string{"Alice", "Bob"}, []speakerTurn{turn("Alice", "\u2014"), bob}}, // "Annual:" is not a label for "Ann"; an undeclared "Carol:" stays in the turn. {"Ann: Annual: report. Carol: yes. Bo: ok.", []string{"Ann", "Bo"}, []speakerTurn{turn("Ann", "Annual: report. Carol: yes."), turn("Bo", "ok.")}}, // The longer name wins over a declared prefix of it. diff --git a/internal/cli/custom/tts.go b/internal/cli/custom/tts.go index fff13ea..08c51bb 100644 --- a/internal/cli/custom/tts.go +++ b/internal/cli/custom/tts.go @@ -264,23 +264,21 @@ func isMarkerRune(r rune) bool { // the label at start begins ("- Alice:", "> Alice:", "β€œAlice:", "1. Alice:"), // so the marker goes with the label instead of the previous turn or the text // before the first label. Without one, it returns start: punctuation or a -// number after words on the same line ("hi. - Bob:", "Alice: 42. Bob:") stays -// in the previous turn. The scan stops at floor, the end of the previous -// label, so a name made of marker runes ("πŸ˜€: πŸ€–:") is never claimed as the -// next label's marker. +// number after words or a label on the same line ("hi. - Bob:", +// "Alice: 42. Bob:", "Alice: ... Bob:") stays in the previous turn. The scan +// stops at floor, the end of the previous label, so a name made of marker +// runes ("πŸ˜€: πŸ€–:") is never claimed as the next label's marker; the line +// start is still judged on the whole text. func lineMarkerStart(text string, floor, start int) int { before := strings.TrimRightFunc(text[floor:start], isHorizontalSpace) rest := strings.TrimRightFunc(before, isMarkerRune) if rest == before { return start } - check := rest if marker := before[len(rest):]; marker == "." || marker == ")" { - if digits := strings.TrimRightFunc(rest, unicode.IsDigit); len(digits) < len(rest) { - rest, check = digits, text[:floor+len(digits)] - } + rest = strings.TrimRightFunc(rest, unicode.IsDigit) } - if line := strings.TrimRightFunc(check, isHorizontalSpace); line != "" { + if line := strings.TrimRightFunc(text[:floor+len(rest)], isHorizontalSpace); line != "" { if last, _ := utf8.DecodeLastRuneInString(line); !isLineBreak(last) { return start }