Luigi commited on
Commit
6e6157b
Β·
1 Parent(s): 89f70aa

feat: allow users to clear speaker names to enable auto-detection

Browse files

Fix Bug 2.4.3: Conflict resolution between user-edited and auto-detected names

Problem:
- User could not clear a manually assigned name to allow auto-detection
- Once edited, names were permanently protected from auto-detection
- No way to 'reset' a speaker name for re-detection

Solution:
- Empty input (after trim) now clears speaker from state
- Deleted speakers can be filled by auto-detection
- Cancel (Escape) still restores original name without clearing
- User-edited names remain protected until explicitly cleared

Behavior:
- Edit name β†’ Save: Marks as user-edited, protects from auto-detection
- Clear name β†’ Save: Removes from state, allows auto-detection
- Edit/Clear β†’ Cancel: Restores original, no state change

Implementation:
- Modified startSpeakerEdit() to handle empty input
- Added 'delete state.speakerNames[speakerId]' for empty names
- Existing merge logic already supports cleared speakers
- Triggers full UI re-render for cleared names

Documentation: SPEAKER_NAME_CONFLICT_RESOLUTION.md
- Design principles and state management strategy
- Logic flows for edit, clear, and auto-detection
- Behavior examples and edge cases
- Testing scenarios

Files changed (2) hide show
  1. SPEAKER_NAME_CONFLICT_RESOLUTION.md +514 -0
  2. frontend/app.js +18 -10
SPEAKER_NAME_CONFLICT_RESOLUTION.md ADDED
@@ -0,0 +1,514 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Speaker Name Conflict Resolution - Enhancement
2
+
3
+ ## Date
4
+ October 1, 2025
5
+
6
+ ## Overview
7
+ Enhancement to handle conflicts between user-edited speaker names and automatically detected names, allowing users to intentionally clear names to enable automatic detection to fill them again.
8
+
9
+ ## Current Behavior Analysis
10
+
11
+ ### Existing Merge Logic
12
+ **Location:** `frontend/app.js:handleSpeakerNameDetection()` (lines ~1030-1037)
13
+
14
+ ```javascript
15
+ // Merge detected names with existing user-edited names (preserve user edits)
16
+ const mergedNames = { ...speakerNames };
17
+ if (state.speakerNames) {
18
+ Object.entries(state.speakerNames).forEach(([speakerId, info]) => {
19
+ if (info.confidence === 'user') {
20
+ // Preserve user-edited names
21
+ mergedNames[speakerId] = info;
22
+ }
23
+ });
24
+ }
25
+ ```
26
+
27
+ **Current Logic:**
28
+ - βœ… Preserves ALL user-edited names (confidence === 'user')
29
+ - βœ… Prevents auto-detection from overwriting user edits
30
+ - ❌ Does NOT allow user to "reset" a name to enable auto-detection
31
+
32
+ ### Existing Edit Logic
33
+ **Location:** `frontend/app.js:startSpeakerEdit()` (lines ~665-680)
34
+
35
+ ```javascript
36
+ const finishEdit = (save = true) => {
37
+ const newName = input.value.trim();
38
+ if (save && newName) {
39
+ // Update state with user-edited name
40
+ state.speakerNames[speakerId] = {
41
+ name: newName,
42
+ confidence: 'user',
43
+ reason: 'User edited'
44
+ };
45
+ // Force re-render...
46
+ } else {
47
+ // Restore original name (doesn't remove from state)
48
+ const originalName = state.speakerNames?.[speakerId]?.name
49
+ || `Speaker ${speakerId + 1}`;
50
+ speakerTag.textContent = originalName;
51
+ }
52
+ };
53
+ ```
54
+
55
+ **Current Logic:**
56
+ - βœ… Saves non-empty names as user-edited
57
+ - ❌ Empty input (after trim) restores original name, doesn't clear it
58
+ - ❌ No way to "clear" a user-edited name to allow auto-detection
59
+
60
+ ## Problem Statement (Bug 2.4.3)
61
+
62
+ ### User Story
63
+ **As a user**, I want to:
64
+ 1. Manually edit speaker names when I know them
65
+ 2. Have my edits protected from auto-detection override
66
+ 3. **Clear/reset a name to allow auto-detection to try again**
67
+ 4. Have auto-detection only fill empty speaker names
68
+
69
+ ### Current Limitations
70
+
71
+ #### Scenario 1: User Cannot Clear Name
72
+ ```
73
+ 1. User edits: "Speaker 1" β†’ "John"
74
+ state.speakerNames[0] = { name: "John", confidence: "user" }
75
+
76
+ 2. User realizes this is wrong, tries to clear it
77
+ - Edits tag, deletes all text, presses Enter
78
+ - Expected: Name cleared, tag shows "Speaker 1"
79
+ - Actual: Name restored to "John" (not cleared)
80
+
81
+ 3. User clicks "Detect Speaker Names"
82
+ - Expected: Auto-detection fills "Speaker 1"
83
+ - Actual: "John" preserved (auto-detection blocked)
84
+
85
+ 4. User is stuck with wrong name!
86
+ ```
87
+
88
+ #### Scenario 2: No Way to Reset
89
+ ```
90
+ User workflow:
91
+ 1. Manually name speakers: 0="Alice", 1="Bob"
92
+ 2. Later realize they're wrong
93
+ 3. Want auto-detection to try again
94
+ 4. No way to "reset" to allow auto-detection
95
+
96
+ Current workaround: None (would need to reload page or manually correct)
97
+ ```
98
+
99
+ ## Solution Design
100
+
101
+ ### Design Principles
102
+ 1. **Explicit Intent:** Empty input should signal "clear this name"
103
+ 2. **Selective Override:** Auto-detection should only fill empty names
104
+ 3. **User Control:** User can always override auto-detection
105
+ 4. **Clear Reset Path:** User can clear name to enable auto-detection
106
+
107
+ ### State Management Strategy
108
+
109
+ #### Three States for Speaker Names
110
+ ```javascript
111
+ state.speakerNames[speakerId] =
112
+ // State 1: User-Edited (Protected)
113
+ { name: "John", confidence: "user", reason: "User edited" }
114
+
115
+ // State 2: Auto-Detected (Overridable)
116
+ { name: "Alice", confidence: "high", reason: "Self-introduction" }
117
+
118
+ // State 3: Cleared (Allows Auto-Detection)
119
+ undefined // Speaker removed from state.speakerNames
120
+ ```
121
+
122
+ **Key Decision:** When user clears a name, **remove it from `state.speakerNames`** entirely.
123
+
124
+ ### Logic Flow
125
+
126
+ #### Flow 1: Manual Edit (Non-Empty)
127
+ ```
128
+ User edits tag: "" or "Speaker 1" β†’ "John"
129
+ ↓
130
+ Input validation: newName.trim() !== ""
131
+ ↓
132
+ state.speakerNames[0] = { name: "John", confidence: "user" }
133
+ ↓
134
+ Re-render UI: All tags show "John"
135
+ ↓
136
+ Auto-detection: Skips speaker 0 (user-edited)
137
+ ```
138
+
139
+ #### Flow 2: Manual Clear (Empty)
140
+ ```
141
+ User edits tag: "John" β†’ "" (empty)
142
+ ↓
143
+ Input validation: newName.trim() === ""
144
+ ↓
145
+ delete state.speakerNames[0] // Remove from state
146
+ ↓
147
+ Re-render UI: Tags show "Speaker 1" (default)
148
+ ↓
149
+ Auto-detection: Can fill speaker 0 (no longer user-edited)
150
+ ```
151
+
152
+ #### Flow 3: Auto-Detection Merge
153
+ ```
154
+ Auto-detection returns: { 0: {name: "Alice", ...}, 1: {name: "Bob", ...} }
155
+ ↓
156
+ Merge logic checks each speaker:
157
+ - Speaker 0: state.speakerNames[0] exists?
158
+ β†’ YES (confidence="user"): Keep "John", skip "Alice"
159
+ β†’ NO: Use "Alice"
160
+ - Speaker 1: state.speakerNames[1] exists?
161
+ β†’ YES (confidence="user"): Keep user name
162
+ β†’ NO: Use "Bob"
163
+ ↓
164
+ Re-render UI with merged names
165
+ ```
166
+
167
+ ## Implementation
168
+
169
+ ### Change 1: Update Manual Edit Logic
170
+
171
+ **File:** `frontend/app.js:startSpeakerEdit()` (lines ~665-680)
172
+
173
+ ```javascript
174
+ const finishEdit = (save = true) => {
175
+ const newName = input.value.trim();
176
+
177
+ if (save) {
178
+ if (newName) {
179
+ // Non-empty name: Save as user-edited
180
+ if (!state.speakerNames) state.speakerNames = {};
181
+ state.speakerNames[speakerId] = {
182
+ name: newName,
183
+ confidence: 'user',
184
+ reason: 'User edited'
185
+ };
186
+ } else {
187
+ // Empty name: Clear from state (allow auto-detection)
188
+ if (state.speakerNames && state.speakerNames[speakerId]) {
189
+ delete state.speakerNames[speakerId];
190
+ }
191
+ }
192
+ // Force re-render to update all UI elements
193
+ renderTranscript(true);
194
+ renderTimelineSegments();
195
+ renderDiarizationStats();
196
+ } else {
197
+ // Cancel edit: Restore current name
198
+ const originalName = state.speakerNames?.[speakerId]?.name
199
+ || `Speaker ${speakerId + 1}`;
200
+ speakerTag.textContent = originalName;
201
+ speakerTag.classList.add('editable-speaker');
202
+ }
203
+ };
204
+ ```
205
+
206
+ **Key Changes:**
207
+ - Added `else` branch for empty input
208
+ - `delete state.speakerNames[speakerId]` removes from state
209
+ - Triggers re-render even for empty input (to show default name)
210
+ - Cancel (Escape) still restores original name without clearing
211
+
212
+ ### Change 2: Enhance Merge Logic (Already Correct!)
213
+
214
+ **File:** `frontend/app.js:handleSpeakerNameDetection()` (lines ~1030-1037)
215
+
216
+ ```javascript
217
+ // Current code is already correct!
218
+ const mergedNames = { ...speakerNames };
219
+ if (state.speakerNames) {
220
+ Object.entries(state.speakerNames).forEach(([speakerId, info]) => {
221
+ if (info.confidence === 'user') {
222
+ // Preserve user-edited names
223
+ mergedNames[speakerId] = info;
224
+ }
225
+ });
226
+ }
227
+ ```
228
+
229
+ **Why it's correct:**
230
+ - If speaker cleared: `state.speakerNames[speakerId]` is undefined
231
+ - Loop skips undefined entries
232
+ - Auto-detected name fills the gap βœ“
233
+
234
+ **No changes needed here!**
235
+
236
+ ## Behavior Examples
237
+
238
+ ### Example 1: Clear and Auto-Detect
239
+
240
+ **Initial State:**
241
+ ```javascript
242
+ state.speakerNames = {
243
+ 0: { name: "John", confidence: "user" },
244
+ 1: { name: "Alice", confidence: "user" }
245
+ }
246
+
247
+ Transcript:
248
+ [John] Hello everyone...
249
+ [Alice] Hi there...
250
+ [John] Today we'll discuss...
251
+ ```
252
+
253
+ **User Action 1:** Clear Speaker 0
254
+ ```
255
+ User clicks "John" tag, deletes all text, presses Enter
256
+
257
+ state.speakerNames = {
258
+ 1: { name: "Alice", confidence: "user" }
259
+ }
260
+ // Speaker 0 removed from state
261
+
262
+ Transcript:
263
+ [Speaker 1] Hello everyone... ← Shows default
264
+ [Alice] Hi there... ← User-edited preserved
265
+ [Speaker 1] Today we'll discuss... ← Shows default
266
+ ```
267
+
268
+ **User Action 2:** Click "Detect Speaker Names"
269
+ ```
270
+ Auto-detection returns:
271
+ { 0: {name: "Dr. Smith", confidence: "high"} }
272
+
273
+ Merge logic:
274
+ - Speaker 0: No user edit β†’ Use "Dr. Smith" βœ“
275
+ - Speaker 1: User edited β†’ Keep "Alice" βœ“
276
+
277
+ state.speakerNames = {
278
+ 0: { name: "Dr. Smith", confidence: "high" },
279
+ 1: { name: "Alice", confidence: "user" }
280
+ }
281
+
282
+ Transcript:
283
+ [Dr. Smith] Hello everyone... ← Auto-detected
284
+ [Alice] Hi there... ← User-edited preserved
285
+ [Dr. Smith] Today we'll discuss... ← Auto-detected
286
+ ```
287
+
288
+ ### Example 2: Edit, Clear, Edit Again
289
+
290
+ **Initial State:**
291
+ ```javascript
292
+ state.speakerNames = {}
293
+
294
+ Transcript:
295
+ [Speaker 1] Hello...
296
+ [Speaker 2] Hi...
297
+ ```
298
+
299
+ **Step 1:** User edits
300
+ ```
301
+ Edit Speaker 1 β†’ "Wrong Name"
302
+
303
+ state.speakerNames = {
304
+ 0: { name: "Wrong Name", confidence: "user" }
305
+ }
306
+
307
+ Transcript:
308
+ [Wrong Name] Hello...
309
+ ```
310
+
311
+ **Step 2:** User realizes mistake, clears
312
+ ```
313
+ Edit "Wrong Name" β†’ "" (empty)
314
+
315
+ state.speakerNames = {} // Speaker 0 removed
316
+
317
+ Transcript:
318
+ [Speaker 1] Hello... ← Back to default
319
+ ```
320
+
321
+ **Step 3:** User edits correctly
322
+ ```
323
+ Edit Speaker 1 β†’ "Correct Name"
324
+
325
+ state.speakerNames = {
326
+ 0: { name: "Correct Name", confidence: "user" }
327
+ }
328
+
329
+ Transcript:
330
+ [Correct Name] Hello...
331
+ ```
332
+
333
+ ### Example 3: Cancel vs Clear
334
+
335
+ **Scenario A: Cancel Edit (Escape key)**
336
+ ```
337
+ Tag shows "John"
338
+ User clicks tag, deletes text, presses Escape
339
+ β†’ Input cancelled, "John" restored
340
+ β†’ state.speakerNames unchanged
341
+ ```
342
+
343
+ **Scenario B: Clear Edit (Enter key)**
344
+ ```
345
+ Tag shows "John"
346
+ User clicks tag, deletes text, presses Enter
347
+ β†’ Edit saved with empty value
348
+ β†’ state.speakerNames[speakerId] deleted
349
+ β†’ Tag shows "Speaker 1" (default)
350
+ ```
351
+
352
+ ## Edge Cases
353
+
354
+ ### Edge Case 1: Clear Non-Existent Name
355
+ ```
356
+ Speaker has default name "Speaker 1" (not in state)
357
+ User clicks tag, clears (empty input), presses Enter
358
+
359
+ Check: state.speakerNames[0] exists?
360
+ β†’ NO: Nothing to delete
361
+ β†’ Result: No error, shows "Speaker 1" (unchanged)
362
+ ```
363
+
364
+ ### Edge Case 2: Clear During Transcription
365
+ ```
366
+ Live transcription in progress
367
+ User clears speaker name
368
+
369
+ Result:
370
+ - Name cleared from state βœ“
371
+ - Re-render triggered βœ“
372
+ - New utterances show default name βœ“
373
+ - Incremental rendering preserved βœ“
374
+ ```
375
+
376
+ ### Edge Case 3: Clear All Names
377
+ ```
378
+ User clears all speaker names
379
+
380
+ state.speakerNames = {}
381
+
382
+ Auto-detection:
383
+ - All speakers available for detection βœ“
384
+ - Can fill all empty names βœ“
385
+ ```
386
+
387
+ ### Edge Case 4: Whitespace-Only Input
388
+ ```
389
+ User enters " " (spaces only)
390
+
391
+ Validation: " ".trim() === ""
392
+ β†’ Treated as empty input
393
+ β†’ Name cleared from state βœ“
394
+ ```
395
+
396
+ ## Testing Scenarios
397
+
398
+ ### βœ… Test 1: Clear User-Edited Name
399
+ 1. Edit "Speaker 1" β†’ "John"
400
+ 2. Verify: Tag shows "John"
401
+ 3. Edit "John" β†’ "" (empty)
402
+ 4. Verify: Tag shows "Speaker 1"
403
+ 5. Verify: `state.speakerNames[0]` is undefined
404
+
405
+ ### βœ… Test 2: Auto-Detection After Clear
406
+ 1. Edit "Speaker 1" β†’ "Wrong"
407
+ 2. Click "Detect Speaker Names"
408
+ 3. Verify: "Wrong" preserved (not overwritten)
409
+ 4. Clear "Wrong" β†’ ""
410
+ 5. Click "Detect Speaker Names" again
411
+ 6. Verify: Auto-detected name appears
412
+
413
+ ### βœ… Test 3: Cancel Does Not Clear
414
+ 1. Tag shows "John"
415
+ 2. Click tag, delete text
416
+ 3. Press Escape
417
+ 4. Verify: Tag shows "John" (restored)
418
+ 5. Verify: `state.speakerNames[0]` unchanged
419
+
420
+ ### βœ… Test 4: Empty Edit Triggers Re-Render
421
+ 1. Tag shows "John"
422
+ 2. Clear name β†’ ""
423
+ 3. Verify: All tags for speaker 0 show "Speaker 1"
424
+ 4. Verify: Timeline segments updated
425
+ 5. Verify: Stats panel updated
426
+
427
+ ### βœ… Test 5: Clear and Re-Edit
428
+ 1. Edit "Speaker 1" β†’ "First"
429
+ 2. Clear "First" β†’ ""
430
+ 3. Edit "Speaker 1" β†’ "Second"
431
+ 4. Verify: All tags show "Second"
432
+ 5. Verify: Protected from auto-detection
433
+
434
+ ### βœ… Test 6: Whitespace Handling
435
+ 1. Edit "Speaker 1" β†’ " " (spaces)
436
+ 2. Press Enter
437
+ 3. Verify: Treated as empty, name cleared
438
+ 4. Verify: Tag shows "Speaker 1"
439
+
440
+ ## User Experience Improvements
441
+
442
+ ### Visual Feedback
443
+ Consider adding visual hints:
444
+
445
+ ```css
446
+ /* Indicate clearable/editable state */
447
+ .speaker-tag.editable-speaker {
448
+ cursor: text;
449
+ border-style: dashed; /* Hint: editable */
450
+ }
451
+
452
+ .speaker-tag.editable-speaker:hover::after {
453
+ content: " ✎"; /* Pencil icon */
454
+ opacity: 0.5;
455
+ }
456
+ ```
457
+
458
+ ### Tooltip Enhancement
459
+ ```javascript
460
+ // In createUtteranceElement()
461
+ if (speakerInfo?.confidence === 'user') {
462
+ speakerTag.title = 'User-edited name (click to edit or clear)';
463
+ } else if (speakerInfo?.confidence === 'high') {
464
+ speakerTag.title = 'Auto-detected name (click to override)';
465
+ } else {
466
+ speakerTag.title = 'Click to edit speaker name';
467
+ }
468
+ ```
469
+
470
+ ### Clear Button (Optional)
471
+ Add explicit "Clear" button in edit mode:
472
+
473
+ ```html
474
+ <input class="speaker-edit-input" value="John" />
475
+ <button class="clear-speaker-btn" title="Clear and allow auto-detection">Γ—</button>
476
+ ```
477
+
478
+ ## Implementation Checklist
479
+
480
+ - [x] Analyze current merge logic
481
+ - [x] Design clear/reset mechanism
482
+ - [x] Document three speaker name states
483
+ - [ ] Implement empty input handling in `startSpeakerEdit()`
484
+ - [ ] Test manual clear functionality
485
+ - [ ] Test auto-detection after clear
486
+ - [ ] Test cancel vs clear behavior
487
+ - [ ] Verify timeline and stats panel sync
488
+ - [ ] Update documentation
489
+ - [ ] Commit changes
490
+
491
+ ## Files to Modify
492
+
493
+ ### `/home/luigi/VoxSum/frontend/app.js`
494
+ - **Function:** `startSpeakerEdit()` (lines ~665-690)
495
+ - **Change:** Add `else` branch for empty input to delete from state
496
+ - **Impact:** ~10 lines modified
497
+
498
+ ### No Other Files Required
499
+ - Merge logic already correct (no changes needed)
500
+ - UI rendering already supports undefined names (shows default)
501
+
502
+ ## Performance Considerations
503
+ - **Delete operation:** O(1) - fast
504
+ - **Re-render trigger:** Same as edit (~10-50ms)
505
+ - **Memory:** Reduces state size (removes cleared entries)
506
+
507
+ ## Backward Compatibility
508
+ - βœ… Existing user-edited names preserved
509
+ - βœ… Auto-detection logic unchanged
510
+ - βœ… Default name display unchanged
511
+ - βœ… No breaking changes to API
512
+
513
+ ## Conclusion
514
+ The enhancement allows users to intentionally clear speaker names to enable auto-detection, providing a clear "reset" path while maintaining protection for user edits. The implementation is simple (one `else` branch), robust, and maintains all existing functionality.
frontend/app.js CHANGED
@@ -665,20 +665,28 @@ function startSpeakerEdit(speakerTag) {
665
  // Handle input events
666
  const finishEdit = (save = true) => {
667
  const newName = input.value.trim();
668
- if (save && newName) {
669
- // Update state
670
- if (!state.speakerNames) state.speakerNames = {};
671
- state.speakerNames[speakerId] = {
672
- name: newName,
673
- confidence: 'user', // Mark as user-edited
674
- reason: 'User edited'
675
- };
676
- // Force re-render to update all speaker tags for this speaker
 
 
 
 
 
 
 
 
677
  renderTranscript(true);
678
  renderTimelineSegments();
679
  renderDiarizationStats();
680
  } else {
681
- // Restore original name
682
  const originalName = state.speakerNames?.[speakerId]?.name || `Speaker ${speakerId + 1}`;
683
  speakerTag.textContent = originalName;
684
  speakerTag.classList.add('editable-speaker');
 
665
  // Handle input events
666
  const finishEdit = (save = true) => {
667
  const newName = input.value.trim();
668
+
669
+ if (save) {
670
+ if (newName) {
671
+ // Non-empty name: Save as user-edited
672
+ if (!state.speakerNames) state.speakerNames = {};
673
+ state.speakerNames[speakerId] = {
674
+ name: newName,
675
+ confidence: 'user', // Mark as user-edited
676
+ reason: 'User edited'
677
+ };
678
+ } else {
679
+ // Empty name: Clear from state (allow auto-detection to fill later)
680
+ if (state.speakerNames && state.speakerNames[speakerId]) {
681
+ delete state.speakerNames[speakerId];
682
+ }
683
+ }
684
+ // Force re-render to update all UI elements
685
  renderTranscript(true);
686
  renderTimelineSegments();
687
  renderDiarizationStats();
688
  } else {
689
+ // Cancel edit: Restore current name without clearing state
690
  const originalName = state.speakerNames?.[speakerId]?.name || `Speaker ${speakerId + 1}`;
691
  speakerTag.textContent = originalName;
692
  speakerTag.classList.add('editable-speaker');