@mieweb/ui 0.7.1-dev.5 → 0.7.1-dev.6

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.
package/dist/index.cjs CHANGED
@@ -38,7 +38,7 @@ var chunkBB4WQ4LV_cjs = require('./chunk-BB4WQ4LV.cjs');
38
38
  var chunkCVC4NWFA_cjs = require('./chunk-CVC4NWFA.cjs');
39
39
  var chunkKBEKQMVY_cjs = require('./chunk-KBEKQMVY.cjs');
40
40
  var chunkCKD554XR_cjs = require('./chunk-CKD554XR.cjs');
41
- var chunkMBESCEWS_cjs = require('./chunk-MBESCEWS.cjs');
41
+ var chunk5M2YSPJH_cjs = require('./chunk-5M2YSPJH.cjs');
42
42
  var chunkGOKC4CCZ_cjs = require('./chunk-GOKC4CCZ.cjs');
43
43
  require('./chunk-GLXTVCHN.cjs');
44
44
  var chunkBYUV3IZV_cjs = require('./chunk-BYUV3IZV.cjs');
@@ -74,7 +74,7 @@ var chunkA2QVQF54_cjs = require('./chunk-A2QVQF54.cjs');
74
74
  var chunkEL2RSQTJ_cjs = require('./chunk-EL2RSQTJ.cjs');
75
75
  var chunk4GXECTPS_cjs = require('./chunk-4GXECTPS.cjs');
76
76
  var chunk2O7D6F67_cjs = require('./chunk-2O7D6F67.cjs');
77
- var chunkO5UJRGRV_cjs = require('./chunk-O5UJRGRV.cjs');
77
+ var chunkNPKPCYAT_cjs = require('./chunk-NPKPCYAT.cjs');
78
78
  var chunkNUFUV43P_cjs = require('./chunk-NUFUV43P.cjs');
79
79
  var chunkJYZW2PZS_cjs = require('./chunk-JYZW2PZS.cjs');
80
80
  var chunkYVPC65ZJ_cjs = require('./chunk-YVPC65ZJ.cjs');
@@ -43221,23 +43221,23 @@ Object.defineProperty(exports, "FenceBlock", {
43221
43221
  });
43222
43222
  Object.defineProperty(exports, "FillerWordsModal", {
43223
43223
  enumerable: true,
43224
- get: function () { return chunkMBESCEWS_cjs.FillerWordsModal; }
43224
+ get: function () { return chunk5M2YSPJH_cjs.FillerWordsModal; }
43225
43225
  });
43226
43226
  Object.defineProperty(exports, "MediaEditor", {
43227
43227
  enumerable: true,
43228
- get: function () { return chunkMBESCEWS_cjs.MediaEditor; }
43228
+ get: function () { return chunk5M2YSPJH_cjs.MediaEditor; }
43229
43229
  });
43230
43230
  Object.defineProperty(exports, "SilenceSettingsModal", {
43231
43231
  enumerable: true,
43232
- get: function () { return chunkMBESCEWS_cjs.SilenceSettingsModal; }
43232
+ get: function () { return chunk5M2YSPJH_cjs.SilenceSettingsModal; }
43233
43233
  });
43234
43234
  Object.defineProperty(exports, "SpeedMarkerMenu", {
43235
43235
  enumerable: true,
43236
- get: function () { return chunkMBESCEWS_cjs.SpeedMarkerMenu; }
43236
+ get: function () { return chunk5M2YSPJH_cjs.SpeedMarkerMenu; }
43237
43237
  });
43238
43238
  Object.defineProperty(exports, "WordEditorModal", {
43239
43239
  enumerable: true,
43240
- get: function () { return chunkMBESCEWS_cjs.WordEditorModal; }
43240
+ get: function () { return chunk5M2YSPJH_cjs.WordEditorModal; }
43241
43241
  });
43242
43242
  Object.defineProperty(exports, "Slider", {
43243
43243
  enumerable: true,
@@ -43733,35 +43733,35 @@ Object.defineProperty(exports, "useTheme", {
43733
43733
  });
43734
43734
  Object.defineProperty(exports, "DEFAULT_FILLER_WORDS", {
43735
43735
  enumerable: true,
43736
- get: function () { return chunkO5UJRGRV_cjs.DEFAULT_FILLER_WORDS; }
43736
+ get: function () { return chunkNPKPCYAT_cjs.DEFAULT_FILLER_WORDS; }
43737
43737
  });
43738
43738
  Object.defineProperty(exports, "DEFAULT_MIN_SILENCE_MS", {
43739
43739
  enumerable: true,
43740
- get: function () { return chunkO5UJRGRV_cjs.DEFAULT_MIN_SILENCE_MS; }
43740
+ get: function () { return chunkNPKPCYAT_cjs.DEFAULT_MIN_SILENCE_MS; }
43741
43741
  });
43742
43742
  Object.defineProperty(exports, "DEFAULT_NL_SILENCE_MS", {
43743
43743
  enumerable: true,
43744
- get: function () { return chunkO5UJRGRV_cjs.DEFAULT_NL_SILENCE_MS; }
43744
+ get: function () { return chunkNPKPCYAT_cjs.DEFAULT_NL_SILENCE_MS; }
43745
43745
  });
43746
43746
  Object.defineProperty(exports, "buildPlaybackSegments", {
43747
43747
  enumerable: true,
43748
- get: function () { return chunkO5UJRGRV_cjs.buildPlaybackSegments; }
43748
+ get: function () { return chunkNPKPCYAT_cjs.buildPlaybackSegments; }
43749
43749
  });
43750
43750
  Object.defineProperty(exports, "getSpeedAtIndex", {
43751
43751
  enumerable: true,
43752
- get: function () { return chunkO5UJRGRV_cjs.getSpeedAtIndex; }
43752
+ get: function () { return chunkNPKPCYAT_cjs.getSpeedAtIndex; }
43753
43753
  });
43754
43754
  Object.defineProperty(exports, "initEditableWords", {
43755
43755
  enumerable: true,
43756
- get: function () { return chunkO5UJRGRV_cjs.initEditableWords; }
43756
+ get: function () { return chunkNPKPCYAT_cjs.initEditableWords; }
43757
43757
  });
43758
43758
  Object.defineProperty(exports, "insertSilences", {
43759
43759
  enumerable: true,
43760
- get: function () { return chunkO5UJRGRV_cjs.insertSilences; }
43760
+ get: function () { return chunkNPKPCYAT_cjs.insertSilences; }
43761
43761
  });
43762
43762
  Object.defineProperty(exports, "useTranscriptEdits", {
43763
43763
  enumerable: true,
43764
- get: function () { return chunkO5UJRGRV_cjs.useTranscriptEdits; }
43764
+ get: function () { return chunkNPKPCYAT_cjs.useTranscriptEdits; }
43765
43765
  });
43766
43766
  Object.defineProperty(exports, "PLAYBACK_SPEEDS", {
43767
43767
  enumerable: true,
package/dist/index.d.cts CHANGED
@@ -63,7 +63,7 @@ export { E as EditableWord, c as PLAYBACK_SPEEDS, a as PlaybackSegment, P as Pla
63
63
  export { VisuallyHidden, VisuallyHiddenProps } from './components/VisuallyHidden/index.cjs';
64
64
  export { R as ResolvedTheme, T as Theme, u as useTheme } from './useTheme-B9SWu6ui.cjs';
65
65
  export { A as AnchoredPlacement, U as UseAnchoredPositionOptions, a as UseAnchoredPositionReturn, u as useAnchoredPosition } from './useAnchoredPosition-nXdSFKHf.cjs';
66
- export { D as DEFAULT_FILLER_WORDS, a as DEFAULT_MIN_SILENCE_MS, b as DEFAULT_NL_SILENCE_MS, F as FillerAnalysis, S as SilenceThresholdCount, T as TranscriptClipboard, c as TranscriptEditStats, U as UseTranscriptEditsOptions, d as UseTranscriptEditsResult, e as buildPlaybackSegments, g as getSpeedAtIndex, i as initEditableWords, f as insertSilences, u as useTranscriptEdits } from './useTranscriptEdits-DgeVQMSi.cjs';
66
+ export { D as DEFAULT_FILLER_WORDS, a as DEFAULT_MIN_SILENCE_MS, b as DEFAULT_NL_SILENCE_MS, F as FillerAnalysis, S as SilenceThresholdCount, T as TranscriptClipboard, c as TranscriptEditStats, U as UseTranscriptEditsOptions, d as UseTranscriptEditsResult, e as buildPlaybackSegments, g as getSpeedAtIndex, i as initEditableWords, f as insertSilences, u as useTranscriptEdits } from './useTranscriptEdits-CGzlemHT.cjs';
67
67
  export { calculateAge, cn, dateToDisplayFormat, displayFormatToDateString, formatDateValue, formatPhoneNumber, isDateEmpty, isDateInFuture, isDateInPast, isPhoneNumberEmpty, isStorybookDocsMode, isValidDate, isValidDrivingAge, isValidPhoneNumber, parseDateValue, unformatPhoneNumber } from './utils/index.cjs';
68
68
  export { i as isHtmlEmpty, s as stripHtmlTags } from './html-MdxqSzjD.cjs';
69
69
  export { default as miewebUIPreset, miewebUISafelist } from './tailwind-preset.cjs';
package/dist/index.d.ts CHANGED
@@ -63,7 +63,7 @@ export { E as EditableWord, c as PLAYBACK_SPEEDS, a as PlaybackSegment, P as Pla
63
63
  export { VisuallyHidden, VisuallyHiddenProps } from './components/VisuallyHidden/index.js';
64
64
  export { R as ResolvedTheme, T as Theme, u as useTheme } from './useTheme-B9SWu6ui.js';
65
65
  export { A as AnchoredPlacement, U as UseAnchoredPositionOptions, a as UseAnchoredPositionReturn, u as useAnchoredPosition } from './useAnchoredPosition-nXdSFKHf.js';
66
- export { D as DEFAULT_FILLER_WORDS, a as DEFAULT_MIN_SILENCE_MS, b as DEFAULT_NL_SILENCE_MS, F as FillerAnalysis, S as SilenceThresholdCount, T as TranscriptClipboard, c as TranscriptEditStats, U as UseTranscriptEditsOptions, d as UseTranscriptEditsResult, e as buildPlaybackSegments, g as getSpeedAtIndex, i as initEditableWords, f as insertSilences, u as useTranscriptEdits } from './useTranscriptEdits-C0Hhy2pO.js';
66
+ export { D as DEFAULT_FILLER_WORDS, a as DEFAULT_MIN_SILENCE_MS, b as DEFAULT_NL_SILENCE_MS, F as FillerAnalysis, S as SilenceThresholdCount, T as TranscriptClipboard, c as TranscriptEditStats, U as UseTranscriptEditsOptions, d as UseTranscriptEditsResult, e as buildPlaybackSegments, g as getSpeedAtIndex, i as initEditableWords, f as insertSilences, u as useTranscriptEdits } from './useTranscriptEdits-D32bLs0l.js';
67
67
  export { calculateAge, cn, dateToDisplayFormat, displayFormatToDateString, formatDateValue, formatPhoneNumber, isDateEmpty, isDateInFuture, isDateInPast, isPhoneNumberEmpty, isStorybookDocsMode, isValidDate, isValidDrivingAge, isValidPhoneNumber, parseDateValue, unformatPhoneNumber } from './utils/index.js';
68
68
  export { i as isHtmlEmpty, s as stripHtmlTags } from './html-MdxqSzjD.js';
69
69
  export { default as miewebUIPreset, miewebUISafelist } from './tailwind-preset.js';
package/dist/index.js CHANGED
@@ -47,7 +47,7 @@ export { HtmlPreviewBlock } from './chunk-UFYXU7KV.js';
47
47
  export { MermaidBlock } from './chunk-T3RWXOCM.js';
48
48
  export { SurveyBlock } from './chunk-I2W24RHW.js';
49
49
  export { FenceBlock } from './chunk-RHYIFIT5.js';
50
- export { FillerWordsModal, MediaEditor, SilenceSettingsModal, SpeedMarkerMenu, WordEditorModal } from './chunk-YVBSAIJ4.js';
50
+ export { FillerWordsModal, MediaEditor, SilenceSettingsModal, SpeedMarkerMenu, WordEditorModal } from './chunk-GOAW2UCP.js';
51
51
  export { Slider, sliderRangeVariants, sliderThumbVariants, sliderTrackVariants } from './chunk-JBB25LFB.js';
52
52
  import './chunk-RISZWWEQ.js';
53
53
  export { MediaPlayer, inferMediaKind, mediaPlayerVariants } from './chunk-KOCTIB6U.js';
@@ -102,7 +102,7 @@ export { Badge, badgeVariants } from './chunk-T42BCIUA.js';
102
102
  import { useCommandK, useIsMobile, useMediaQuery, useScrollSpy } from './chunk-ZGSC2CB2.js';
103
103
  export { useCommandK, useIsDesktop, useIsLargeDesktop, useIsMobile, useIsMobileOrTablet, useIsSmallTablet, useIsTablet, useKeyboardShortcut, useMediaQuery, useScrollSpy } from './chunk-ZGSC2CB2.js';
104
104
  export { useTheme } from './chunk-KJZNEVYM.js';
105
- export { DEFAULT_FILLER_WORDS, DEFAULT_MIN_SILENCE_MS, DEFAULT_NL_SILENCE_MS, buildPlaybackSegments, getSpeedAtIndex, initEditableWords, insertSilences, useTranscriptEdits } from './chunk-FKHMTRF2.js';
105
+ export { DEFAULT_FILLER_WORDS, DEFAULT_MIN_SILENCE_MS, DEFAULT_NL_SILENCE_MS, buildPlaybackSegments, getSpeedAtIndex, initEditableWords, insertSilences, useTranscriptEdits } from './chunk-UUI7WXI5.js';
106
106
  export { PLAYBACK_SPEEDS } from './chunk-6CGM5H4J.js';
107
107
  export { useMediaTransport } from './chunk-SQHMGT4W.js';
108
108
  import { useLiveAnnouncement, useDragReorder, dragIndicatorClasses, reorderIds } from './chunk-7ZVTLTJF.js';
@@ -103,6 +103,10 @@ interface UseTranscriptEditsResult {
103
103
  pushUndo: () => void;
104
104
  /** Restore the most recent undo snapshot */
105
105
  undo: () => void;
106
+ /** Step forward again after an undo. Cleared by any new edit. */
107
+ redo: () => void;
108
+ /** Whether there is anything to redo */
109
+ canRedo: boolean;
106
110
  /** Toggle the deleted flag on a single word */
107
111
  toggleWordDeleted: (index: number) => void;
108
112
  /**
@@ -103,6 +103,10 @@ interface UseTranscriptEditsResult {
103
103
  pushUndo: () => void;
104
104
  /** Restore the most recent undo snapshot */
105
105
  undo: () => void;
106
+ /** Step forward again after an undo. Cleared by any new edit. */
107
+ redo: () => void;
108
+ /** Whether there is anything to redo */
109
+ canRedo: boolean;
106
110
  /** Toggle the deleted flag on a single word */
107
111
  toggleWordDeleted: (index: number) => void;
108
112
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mieweb/ui",
3
- "version": "0.7.1-dev.5",
3
+ "version": "0.7.1-dev.6",
4
4
  "description": "A themeable, accessible React component library built with Tailwind CSS",
5
5
  "author": "Medical Informatics Engineering, Inc.",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/hooks/useTranscriptEdits.ts"],"names":[],"mappings":"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","file":"chunk-FKHMTRF2.js","sourcesContent":["/**\n * useTranscriptEdits — headless transcript-editing state hook.\n *\n * Extracted from pulseclip's TranscriptViewer for @mieweb/ui. Holds the\n * edited word timeline (deletions, insertions, text edits, silence\n * splitting), a single-level undo stack, an internal clipboard, speed\n * markers, and derived playback/statistics data.\n *\n * Controlled + persistence-agnostic: no DOM, no fetches. Selection lives in\n * the UI layer — mutations take explicit indices. State flows out via\n * `onChange` and back in via `initialEditedWords` / `initialUndoStack`.\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport type {\n Transcript,\n TranscriptWord,\n EditableWord,\n PlaybackSegment,\n PlaybackSpeed,\n SpeedMarker,\n} from '../components/TranscriptView/transcript';\nimport { isSilenceType } from '../components/TranscriptView/transcript';\n\n/** Default filler words offered for removal */\nexport const DEFAULT_FILLER_WORDS = [\n 'um',\n 'uh',\n 'umm',\n 'uhh',\n 'uh-huh',\n 'mm-hmm',\n 'hmm',\n 'hm',\n 'er',\n 'err',\n 'ah',\n 'ahh',\n 'eh',\n 'oh',\n 'ooh',\n 'like',\n 'you know',\n 'i mean',\n 'so',\n 'well',\n 'actually',\n 'basically',\n 'literally',\n 'right',\n 'okay',\n 'ok',\n];\n\n/** Default minimum silence duration in milliseconds to detect and insert */\nexport const DEFAULT_MIN_SILENCE_MS = 400;\n\n/** Default threshold for \"newline\" silences - longer pauses that indicate paragraph breaks */\nexport const DEFAULT_NL_SILENCE_MS = 1500;\n\n/** Silence thresholds (seconds) reported by the filler analysis */\nconst SILENCE_ANALYSIS_THRESHOLDS = [0.3, 0.4, 0.5, 0.75, 1.0, 1.5, 2.0];\n\n/** Clipboard data for cut/copy/paste operations */\nexport interface TranscriptClipboard {\n words: EditableWord[];\n operation: 'cut' | 'copy';\n}\n\n/** Count and total duration of silences above a given threshold (seconds) */\nexport interface SilenceThresholdCount {\n threshold: number;\n count: number;\n durationMs: number;\n}\n\n/**\n * Builds playback segments from the edited word list.\n * Consecutive words in original order are merged into single segments.\n * Inserted (pasted) words each get their own segment since they may be duplicates.\n */\nexport function buildPlaybackSegments(\n editedWords: EditableWord[]\n): PlaybackSegment[] {\n const activeWords = editedWords.filter((w) => !w.deleted);\n if (activeWords.length === 0) return [];\n\n const segments: PlaybackSegment[] = [];\n let currentSegment: PlaybackSegment | null = null;\n let lastOriginalIndex = -2; // Use -2 so first word always starts new segment\n let lastWasInserted = false;\n\n for (let i = 0; i < editedWords.length; i++) {\n const ew = editedWords[i];\n if (ew.deleted) continue;\n\n // Check if this word is consecutive to the previous in the original\n // Inserted words always start a new segment (they might be duplicates)\n const isConsecutive =\n !ew.inserted &&\n !lastWasInserted &&\n ew.originalIndex === lastOriginalIndex + 1;\n\n if (currentSegment && isConsecutive) {\n // Extend current segment\n currentSegment.endMs = ew.word.endMs;\n currentSegment.editedIndices.push(i);\n } else {\n // Start new segment\n if (currentSegment) {\n segments.push(currentSegment);\n }\n currentSegment = {\n startMs: ew.word.startMs,\n endMs: ew.word.endMs,\n editedIndices: [i],\n };\n }\n\n lastOriginalIndex = ew.originalIndex;\n lastWasInserted = ew.inserted ?? false;\n }\n\n if (currentSegment) {\n segments.push(currentSegment);\n }\n\n return segments;\n}\n\n/**\n * Get the effective playback speed at a given word index.\n * Finds the most recent speed marker at or before the word index,\n * or returns the default speed if no marker applies.\n */\nexport function getSpeedAtIndex(\n wordIndex: number,\n speedMarkers: SpeedMarker[],\n defaultSpeed: PlaybackSpeed\n): PlaybackSpeed {\n // Single O(n) pass that takes the marker with the greatest wordIndex at or\n // before the target. This avoids the per-call copy+sort (stats calls this\n // once per word) without assuming callers pass sorted markers — it is a\n // public helper, so arbitrary order must still yield the right answer.\n let effectiveSpeed = defaultSpeed;\n let nearest = -1;\n for (const marker of speedMarkers) {\n if (marker.wordIndex <= wordIndex && marker.wordIndex > nearest) {\n nearest = marker.wordIndex;\n effectiveSpeed = marker.speed;\n }\n }\n return effectiveSpeed;\n}\n\n/**\n * Detect silences between words and return a new array with silence pseudo-words inserted.\n * Silences are detected as gaps between the endMs of one word and startMs of the next.\n * Silences >= nlSilenceMs are marked as 'silence-newline' for visual line breaks.\n */\nexport function insertSilences(\n words: TranscriptWord[],\n minSilenceMs: number = DEFAULT_MIN_SILENCE_MS,\n nlSilenceMs: number = DEFAULT_NL_SILENCE_MS\n): TranscriptWord[] {\n if (words.length === 0) return [];\n\n const result: TranscriptWord[] = [];\n\n for (let i = 0; i < words.length; i++) {\n const word = words[i];\n\n // Check for silence before the first word (from 0ms)\n if (i === 0 && word.startMs > minSilenceMs) {\n const isNewline = word.startMs >= nlSilenceMs;\n result.push({\n text: `[${(word.startMs / 1000).toFixed(1)}s]`,\n startMs: 0,\n endMs: word.startMs,\n wordType: isNewline ? 'silence-newline' : 'silence',\n });\n }\n\n // Add the word (with default wordType)\n result.push({ ...word, wordType: word.wordType ?? 'word' });\n\n // Check for silence after this word (gap before next word)\n if (i < words.length - 1) {\n const nextWord = words[i + 1];\n const gapMs = nextWord.startMs - word.endMs;\n\n if (gapMs >= minSilenceMs) {\n const isNewline = gapMs >= nlSilenceMs;\n result.push({\n text: `[${(gapMs / 1000).toFixed(1)}s]`,\n startMs: word.endMs,\n endMs: nextWord.startMs,\n wordType: isNewline ? 'silence-newline' : 'silence',\n });\n }\n }\n }\n\n return result;\n}\n\n/** Initialize editable words from transcript, inserting detected silences */\nexport function initEditableWords(\n transcript: Transcript,\n minSilenceMs: number = DEFAULT_MIN_SILENCE_MS,\n nlSilenceMs: number = DEFAULT_NL_SILENCE_MS\n): EditableWord[] {\n // First insert silences between the original words\n const wordsWithSilences = insertSilences(\n transcript.words,\n minSilenceMs,\n nlSilenceMs\n );\n\n return wordsWithSilences.map((word, index) => ({\n originalIndex: index,\n word,\n deleted: false,\n }));\n}\n\n/** Strip trailing punctuation and lowercase for filler-word matching */\nfunction normalizeWordText(text: string): string {\n return text.toLowerCase().replace(/[.,!?;:]/g, '');\n}\n\nexport interface UseTranscriptEditsOptions {\n /** The source transcript (canonical, immutable) */\n transcript: Transcript;\n /** Initial edited words (from saved edits) */\n initialEditedWords?: EditableWord[];\n /** Initial undo stack (from saved edits) */\n initialUndoStack?: EditableWord[][];\n /** Fired after every mutation (for persistence) */\n onChange?: (editedWords: EditableWord[], undoStack: EditableWord[][]) => void;\n /** Minimum silence duration (ms) to detect and insert. Default 400 */\n minSilenceMs?: number;\n /** Threshold (ms) for \"newline\" silences (paragraph breaks). Default 1500 */\n nlSilenceMs?: number;\n /** Initial speed markers (from saved edits) */\n initialSpeedMarkers?: SpeedMarker[];\n /** Initial default playback speed (from saved edits). Default 1 */\n initialDefaultSpeed?: PlaybackSpeed;\n}\n\nexport interface TranscriptEditStats {\n /** Non-deleted spoken words (silences excluded) */\n activeWordCount: number;\n /** Non-deleted silence entries */\n activeSilenceCount: number;\n /** Deleted spoken words */\n deletedWordCount: number;\n /** Deleted silence entries */\n deletedSilenceCount: number;\n /** Total duration of non-deleted entries, adjusted for speed markers */\n editedDurationMs: number;\n}\n\nexport interface FillerAnalysis {\n /** Occurrence count per normalized word (fillers and every active word) */\n matchCounts: Map<string, number>;\n /** Total duration (ms) per normalized word */\n durations: Map<string, number>;\n /** Count + total duration of active silences above each analysis threshold */\n silenceCounts: SilenceThresholdCount[];\n /** The built-in filler word list */\n defaultFillerWords: string[];\n}\n\nexport interface UseTranscriptEditsResult {\n // -- State --\n /** The edited timeline (words + silence pseudo-words) */\n editedWords: EditableWord[];\n /** Undo snapshots, oldest first */\n undoStack: EditableWord[][];\n /** Internal clipboard from the last cut/copy, or null */\n clipboard: TranscriptClipboard | null;\n /** Whether any edit has been made (deletions, insertions, text edits, speed markers) */\n hasEdits: boolean;\n\n // -- Undo --\n /** Push the current editedWords onto the undo stack (call before external batch mutations) */\n pushUndo: () => void;\n /** Restore the most recent undo snapshot */\n undo: () => void;\n\n // -- Word mutations (selection-agnostic: pass explicit indices) --\n /** Toggle the deleted flag on a single word */\n toggleWordDeleted: (index: number) => void;\n /**\n * Toggle the deleted state of a contiguous range. The first index acts as\n * the anchor: all words are set to the opposite of the anchor's state\n * (delete all if anchor active, restore all if anchor deleted).\n */\n deleteRange: (indices: number[]) => void;\n /** Cut words at the given indices into the clipboard (marks them deleted) */\n cut: (indices: number[]) => void;\n /** Copy non-deleted words at the given indices into the clipboard */\n copy: (indices: number[]) => void;\n /**\n * Paste the clipboard at a word index. `before` (default true) inserts\n * before the word at `atIndex`; false inserts after it. Returns the index\n * where the first pasted word landed, or null if the clipboard was empty.\n */\n paste: (atIndex: number, before?: boolean) => number | null;\n /** Replace a word's text (no-op if unchanged) */\n setWordText: (index: number, text: string) => void;\n /** Split a silence at the given index into segments with the given durations (seconds) */\n splitSilence: (index: number, durationsSec: number[]) => void;\n /** Toggle the word at the index deleted (editor \"delete\" action) */\n deleteWord: (index: number) => void;\n /** Replace the entire edit state from an external source (e.g. an editable script view); undo-safe */\n replaceEditedWords: (next: EditableWord[]) => void;\n /** Mark matching filler words (and silences above the given seconds threshold) deleted */\n removeFillers: (\n fillerWords: string[],\n removeSilenceAboveSec: number | null\n ) => void;\n\n // -- Silence thresholds --\n /** Rebuild silence detection with new thresholds, preserving word deletions.\n * Not undoable — clears the undo stack (prior snapshots reference the old silence layout). */\n setSilenceThresholds: (minMs: number, nlMs: number) => void;\n /** Current silence detection thresholds (ms) */\n silenceThresholds: { minSilenceMs: number; nlSilenceMs: number };\n\n // -- Playback speed --\n /** Speed markers, sorted by word index */\n speedMarkers: SpeedMarker[];\n /** Default playback speed when no marker applies */\n defaultSpeed: PlaybackSpeed;\n /** Set the default playback speed (undoable) */\n setDefaultSpeed: (speed: PlaybackSpeed) => void;\n /** Add/update/remove a speed marker (re-selecting the same speed removes it; undoable) */\n toggleSpeedMarker: (index: number, speed: PlaybackSpeed) => void;\n /** Remove the speed marker at a word index, if any (undoable) */\n removeSpeedMarker: (index: number) => void;\n /**\n * Apply one speed across an index range (typically the selection): marks the\n * start, restores the prior effective speed after the end. One undo step.\n */\n setSpeedForRange: (\n startIndex: number,\n endIndex: number,\n speed: PlaybackSpeed\n ) => void;\n /** Effective playback speed at a word index */\n getSpeedAtIndex: (index: number) => PlaybackSpeed;\n /** The speed marker at a word index, if any */\n getSpeedMarkerAtIndex: (index: number) => SpeedMarker | undefined;\n\n // -- Derived --\n /** Contiguous playback segments built from the edited timeline */\n playbackSegments: PlaybackSegment[];\n /** Word/silence counts and speed-adjusted edited duration */\n stats: TranscriptEditStats;\n /** Filler word and silence analysis for removal UIs */\n fillerAnalysis: FillerAnalysis;\n}\n\n/**\n * Headless transcript-editing hook. See module docs for scope.\n *\n * Undo semantics: each mutation snapshots the full `editedWords` array onto\n * a single-level stack before applying; `undo` pops one snapshot. Speed\n * markers and silence-threshold changes are NOT part of the undo stack\n * (matching the source component).\n */\nexport function useTranscriptEdits(\n options: UseTranscriptEditsOptions\n): UseTranscriptEditsResult {\n const {\n transcript,\n initialEditedWords,\n initialUndoStack,\n onChange,\n minSilenceMs: initialMinSilenceMs = DEFAULT_MIN_SILENCE_MS,\n nlSilenceMs: initialNlSilenceMs = DEFAULT_NL_SILENCE_MS,\n initialSpeedMarkers,\n initialDefaultSpeed,\n } = options;\n\n // Track if we've initialized from saved state (skip first onChange notification)\n const initializedFromSaved = useRef(false);\n\n const [editedWords, setEditedWords] = useState<EditableWord[]>(\n () =>\n initialEditedWords ||\n initEditableWords(transcript, initialMinSilenceMs, initialNlSilenceMs)\n );\n const [undoStack, setUndoStack] = useState<EditableWord[][]>(\n initialUndoStack || []\n );\n const [clipboard, setClipboard] = useState<TranscriptClipboard | null>(null);\n const [hasEdits, setHasEdits] = useState(() => {\n // Rehydrated speed state counts as an edit, matching the live setters\n // (toggleSpeedMarker sets hasEdits) and this flag's documented contract\n if (\n (initialSpeedMarkers && initialSpeedMarkers.length > 0) ||\n (initialDefaultSpeed !== undefined && initialDefaultSpeed !== 1)\n ) {\n return true;\n }\n // Compare saved state against the derived baseline: deletions, insertions,\n // length changes, AND text edits (text-only edits previously initialized\n // hasEdits=false, which also suppressed onChange persistence).\n if (initialEditedWords) {\n const baseline = initEditableWords(\n transcript,\n initialMinSilenceMs,\n initialNlSilenceMs\n );\n return (\n initialEditedWords.length !== baseline.length ||\n initialEditedWords.some(\n (ew, i) =>\n ew.deleted ||\n ew.inserted ||\n ew.originalIndex !== i ||\n ew.word.text !== baseline[i].word.text\n )\n );\n }\n return false;\n });\n const [minSilenceMs, setMinSilenceMs] = useState(initialMinSilenceMs);\n const [nlSilenceMs, setNlSilenceMs] = useState(initialNlSilenceMs);\n const [defaultSpeed, setDefaultSpeed] = useState<PlaybackSpeed>(\n initialDefaultSpeed ?? 1\n );\n const [speedMarkers, setSpeedMarkers] = useState<SpeedMarker[]>(\n initialSpeedMarkers ?? []\n );\n\n // Speed snapshots aligned with undoStack so undo restores speed too. Not\n // persisted: entries synthesized for a rehydrated undoStack carry the\n // initial speed, so undoing past them simply leaves speed at its saved\n // state instead of guessing history we never had.\n const [speedUndoStack, setSpeedUndoStack] = useState<\n { speedMarkers: SpeedMarker[]; defaultSpeed: PlaybackSpeed }[]\n >(() =>\n (initialUndoStack ?? []).map(() => ({\n speedMarkers: initialSpeedMarkers ?? [],\n defaultSpeed: initialDefaultSpeed ?? 1,\n }))\n );\n\n // Helper to save current state to undo stack before making changes\n const pushUndo = useCallback(() => {\n setUndoStack((prev) => [...prev, editedWords]);\n setSpeedUndoStack((prev) => [...prev, { speedMarkers, defaultSpeed }]);\n }, [editedWords, speedMarkers, defaultSpeed]);\n\n // Track previous transcript to detect changes\n const prevTranscriptRef = useRef(transcript);\n\n // Reset edited words when transcript changes (but not on initial load with saved edits)\n useEffect(() => {\n if (prevTranscriptRef.current === transcript) {\n return;\n }\n prevTranscriptRef.current = transcript;\n\n // Transcript changed - reset to fresh state (use current silence thresholds)\n setEditedWords(initEditableWords(transcript, minSilenceMs, nlSilenceMs));\n setUndoStack([]);\n setHasEdits(false);\n setClipboard(null);\n // Speed markers index into the old transcript's word list; keeping them\n // would apply stale rates to arbitrary words of the new transcript\n setSpeedMarkers([]);\n setDefaultSpeed(1);\n setSpeedUndoStack([]);\n initializedFromSaved.current = false;\n }, [transcript, minSilenceMs, nlSilenceMs]);\n\n // Notify consumer when editor state changes (for persistence)\n useEffect(() => {\n // Skip the initial render if we initialized from saved state\n if (!initializedFromSaved.current && initialEditedWords) {\n initializedFromSaved.current = true;\n return;\n }\n initializedFromSaved.current = true;\n\n // Only notify if there are edits to persist\n if (hasEdits || undoStack.length > 0) {\n onChange?.(editedWords, undoStack);\n }\n }, [editedWords, undoStack, hasEdits, onChange, initialEditedWords]);\n\n // Undo last edit\n const undo = useCallback(() => {\n if (undoStack.length === 0) return;\n const previousState = undoStack[undoStack.length - 1];\n setUndoStack((prev) => prev.slice(0, -1));\n setEditedWords(previousState);\n // Speed snapshots ride along with word snapshots\n const speedSnapshot = speedUndoStack[speedUndoStack.length - 1];\n if (speedSnapshot) {\n setSpeedUndoStack((prev) => prev.slice(0, -1));\n setSpeedMarkers(speedSnapshot.speedMarkers);\n setDefaultSpeed(speedSnapshot.defaultSpeed);\n }\n // Compare against the silence-inserted baseline (what the editor actually\n // starts from), not raw transcript.words — with any detected silence the\n // lengths never matched and hasEdits stayed true after undoing everything.\n const baseline = initEditableWords(transcript, minSilenceMs, nlSilenceMs);\n const isOriginal =\n previousState.length === baseline.length &&\n previousState.every(\n (ew, i) =>\n ew.originalIndex === i &&\n !ew.deleted &&\n !ew.inserted &&\n ew.word.text === baseline[i].word.text\n );\n const speedIsOriginal =\n !speedSnapshot ||\n (speedSnapshot.speedMarkers.length === 0 &&\n speedSnapshot.defaultSpeed === 1);\n setHasEdits(!isOriginal || !speedIsOriginal);\n }, [undoStack, speedUndoStack, transcript, minSilenceMs, nlSilenceMs]);\n\n // Toggle deleted state on a single word\n const toggleWordDeleted = useCallback(\n (index: number) => {\n const ew = editedWords[index];\n if (!ew) return;\n\n pushUndo();\n const newEditedWords = [...editedWords];\n newEditedWords[index] = { ...ew, deleted: !ew.deleted };\n setEditedWords(newEditedWords);\n setHasEdits(true);\n },\n [editedWords, pushUndo]\n );\n\n // Toggle deleted state for a range; the first index acts as the anchor\n const deleteRange = useCallback(\n (indices: number[]) => {\n if (indices.length === 0) return;\n const anchor = editedWords[indices[0]];\n if (!anchor) return;\n\n pushUndo();\n // The anchor word's state determines the action for all words:\n // if anchor is not deleted, delete all; if anchor is deleted, restore all\n const targetDeletedState = !anchor.deleted;\n setEditedWords((prev) => {\n const updated = [...prev];\n for (const i of indices) {\n if (updated[i]) {\n updated[i] = { ...updated[i], deleted: targetDeletedState };\n }\n }\n return updated;\n });\n setHasEdits(true);\n },\n [editedWords, pushUndo]\n );\n\n // Cut words at the given indices into the clipboard\n const cut = useCallback(\n (indices: number[]) => {\n if (indices.length === 0) return;\n const cutWords = indices\n .map((i) => editedWords[i])\n .filter((ew): ew is EditableWord => ew !== undefined);\n if (cutWords.length === 0) return;\n\n pushUndo();\n setClipboard({ words: cutWords, operation: 'cut' });\n setEditedWords((prev) => {\n const updated = [...prev];\n for (const i of indices) {\n if (updated[i]) {\n updated[i] = { ...updated[i], deleted: true };\n }\n }\n return updated;\n });\n setHasEdits(true);\n },\n [editedWords, pushUndo]\n );\n\n // Copy non-deleted words at the given indices into the clipboard (no mutation)\n const copy = useCallback(\n (indices: number[]) => {\n const selectedWords = indices\n .map((i) => editedWords[i])\n .filter((ew): ew is EditableWord => ew !== undefined && !ew.deleted);\n if (selectedWords.length === 0) return;\n setClipboard({ words: selectedWords, operation: 'copy' });\n },\n [editedWords]\n );\n\n // Paste from clipboard at a word index\n const paste = useCallback(\n (atIndex: number, before: boolean = true): number | null => {\n if (!clipboard) return null;\n pushUndo();\n const insertIndex = before ? atIndex : atIndex + 1;\n setEditedWords((prev) => {\n const updated = [...prev];\n const wordsToInsert = clipboard.words.map((w) => ({\n ...w,\n deleted: false,\n inserted: true,\n }));\n updated.splice(insertIndex, 0, ...wordsToInsert);\n return updated;\n });\n setHasEdits(true);\n return insertIndex;\n },\n [clipboard, pushUndo]\n );\n\n // Replace a word's text\n const setWordText = useCallback(\n (index: number, text: string) => {\n const ew = editedWords[index];\n if (!ew || text === ew.word.text) return;\n\n pushUndo();\n const newEditedWords = [...editedWords];\n newEditedWords[index] = {\n ...ew,\n word: { ...ew.word, text },\n };\n setEditedWords(newEditedWords);\n setHasEdits(true);\n },\n [editedWords, pushUndo]\n );\n\n // Split a silence into multiple segments\n const splitSilence = useCallback(\n (index: number, durationsSec: number[]) => {\n const ew = editedWords[index];\n if (!ew || !isSilenceType(ew.word.wordType)) return;\n\n pushUndo();\n\n // Create new silence segments from the durations\n const silenceStart = ew.word.startMs;\n const newSilences: EditableWord[] = [];\n let currentMs = silenceStart;\n\n for (let i = 0; i < durationsSec.length; i++) {\n const durationMs = Math.round(durationsSec[i] * 1000);\n const endMs = currentMs + durationMs;\n\n newSilences.push({\n originalIndex: -1, // Negative indicates a split/inserted silence\n word: {\n text: `[${durationsSec[i].toFixed(1)}s]`,\n startMs: currentMs,\n endMs: endMs,\n wordType: 'silence',\n },\n deleted: false,\n inserted: true,\n });\n\n currentMs = endMs;\n }\n\n // Replace the original silence with the new segments\n const newEditedWords = [...editedWords];\n newEditedWords.splice(index, 1, ...newSilences);\n setEditedWords(newEditedWords);\n setHasEdits(true);\n },\n [editedWords, pushUndo]\n );\n\n // Editor \"delete\" action: toggles the word's deleted state\n const deleteWord = useCallback(\n (index: number) => {\n toggleWordDeleted(index);\n },\n [toggleWordDeleted]\n );\n\n // Replace the entire edit state from an external source (e.g. the editable\n // script/YAML panel). Undo-safe: the previous state is pushed first.\n const replaceEditedWords = useCallback(\n (next: EditableWord[]) => {\n pushUndo();\n setEditedWords(next);\n setHasEdits(true);\n },\n [pushUndo]\n );\n\n // Mark filler words (and optionally silences above threshold) deleted\n const removeFillers = useCallback(\n (fillerWords: string[], removeSilenceAboveSec: number | null) => {\n const fillerSet = new Set(fillerWords.map((f) => f.toLowerCase()));\n const silenceThresholdMs =\n removeSilenceAboveSec !== null ? removeSilenceAboveSec * 1000 : null;\n\n const newEditedWords = editedWords.map((ew) => {\n if (ew.deleted) return ew;\n\n // Check if it's a silence that should be removed\n if (isSilenceType(ew.word.wordType) && silenceThresholdMs !== null) {\n const durationMs = ew.word.endMs - ew.word.startMs;\n if (durationMs > silenceThresholdMs) {\n return { ...ew, deleted: true };\n }\n return ew;\n }\n\n // Check if it's a filler word that should be removed\n if (!isSilenceType(ew.word.wordType)) {\n const wordText = normalizeWordText(ew.word.text);\n if (fillerSet.has(wordText)) {\n return { ...ew, deleted: true };\n }\n }\n\n return ew;\n });\n\n const deletedCount = newEditedWords.filter(\n (ew, i) => ew.deleted && !editedWords[i].deleted\n ).length;\n\n if (deletedCount > 0) {\n pushUndo();\n setEditedWords(newEditedWords);\n setHasEdits(true);\n }\n },\n [editedWords, pushUndo]\n );\n\n // Apply new silence thresholds, rebuilding silences while preserving word deletions\n const setSilenceThresholds = useCallback(\n (newMinSilenceMs: number, newNlSilenceMs: number) => {\n setMinSilenceMs(newMinSilenceMs);\n setNlSilenceMs(newNlSilenceMs);\n // Rebuild the editable words with new silence detection.\n // Preserve deleted status for actual words (not silences), keyed by the\n // word's ORDINAL among non-silence words — the same key the restore loop\n // below uses. (Keying by originalIndex — a position in the old\n // silence-inserted array — mismatched the restore counter and applied\n // deletions to the wrong words after a threshold change.)\n // Pasted words keep their source originalIndex but are dropped by the\n // rebuild, so they must not occupy a slot in this keyspace either.\n const wordDeletedStatus = new Map<number, boolean>();\n let saveIdx = 0;\n editedWords.forEach((ew) => {\n if (\n !isSilenceType(ew.word.wordType) &&\n ew.originalIndex >= 0 &&\n !ew.inserted\n ) {\n wordDeletedStatus.set(saveIdx, ew.deleted);\n saveIdx++;\n }\n });\n\n // Re-initialize with new thresholds\n const newEditedWords = initEditableWords(\n transcript,\n newMinSilenceMs,\n newNlSilenceMs\n );\n\n // Restore deleted status for words (same ordinal keyspace as the save)\n let wordIdx = 0;\n const restoredWords = newEditedWords.map((ew) => {\n if (!isSilenceType(ew.word.wordType)) {\n const wasDeleted = wordDeletedStatus.get(wordIdx) ?? false;\n wordIdx++;\n return { ...ew, deleted: wasDeleted };\n }\n return ew;\n });\n\n setEditedWords(restoredWords);\n\n // Speed markers index into the silence-inserted array, so the rebuild\n // shifts them too. Re-anchor each marker to the first rebuild-surviving\n // word at or after it — the same ordinal keyspace as the deletion\n // restore above. Markers with no surviving word left are dropped;\n // markers that collapse onto the same word keep the later speed (it\n // governed the words from that point on).\n if (speedMarkers.length > 0) {\n const survivorPrefix: number[] = [];\n let survivors = 0;\n editedWords.forEach((ew) => {\n survivorPrefix.push(survivors);\n if (\n !isSilenceType(ew.word.wordType) &&\n ew.originalIndex >= 0 &&\n !ew.inserted\n ) {\n survivors++;\n }\n });\n survivorPrefix.push(survivors);\n const newIndexByOrdinal: number[] = [];\n restoredWords.forEach((ew, i) => {\n if (!isSilenceType(ew.word.wordType)) newIndexByOrdinal.push(i);\n });\n const remapped: SpeedMarker[] = [];\n speedMarkers.forEach((m) => {\n const ordinal =\n survivorPrefix[Math.min(m.wordIndex, editedWords.length)];\n const newIndex = newIndexByOrdinal[ordinal];\n if (newIndex === undefined) return;\n const last = remapped[remapped.length - 1];\n if (last && last.wordIndex === newIndex) {\n remapped[remapped.length - 1] = {\n wordIndex: newIndex,\n speed: m.speed,\n };\n } else {\n remapped.push({ wordIndex: newIndex, speed: m.speed });\n }\n });\n setSpeedMarkers(remapped);\n }\n\n // Undo snapshots were captured against the previous silence layout;\n // restoring one after a rebuild would resurrect silence chips that no\n // longer match the current thresholds. Threshold changes are not\n // undoable, so drop the stale history rather than restore it — the\n // speed stack rides along or the two stacks drift out of alignment.\n setUndoStack([]);\n setSpeedUndoStack([]);\n },\n [editedWords, transcript, speedMarkers]\n );\n\n // Get the speed marker at a specific word index (if any)\n const getSpeedMarkerAtIndex = useCallback(\n (wordIndex: number): SpeedMarker | undefined => {\n return speedMarkers.find((m) => m.wordIndex === wordIndex);\n },\n [speedMarkers]\n );\n\n // Toggle a speed marker at a word index\n const toggleSpeedMarker = useCallback(\n (wordIndex: number, speed: PlaybackSpeed) => {\n pushUndo();\n setSpeedMarkers((prev) => {\n const existingIdx = prev.findIndex((m) => m.wordIndex === wordIndex);\n if (existingIdx >= 0) {\n // If marker exists with same speed, remove it\n if (prev[existingIdx].speed === speed) {\n return prev.filter((_, i) => i !== existingIdx);\n }\n // If marker exists with different speed, update it\n const updated = [...prev];\n updated[existingIdx] = { wordIndex, speed };\n return updated;\n }\n // Add new marker\n return [...prev, { wordIndex, speed }].sort(\n (a, b) => a.wordIndex - b.wordIndex\n );\n });\n setHasEdits(true);\n },\n [pushUndo]\n );\n\n // Remove a speed marker at a word index\n const removeSpeedMarker = useCallback(\n (wordIndex: number) => {\n if (!speedMarkers.some((m) => m.wordIndex === wordIndex)) return;\n pushUndo();\n setSpeedMarkers(speedMarkers.filter((m) => m.wordIndex !== wordIndex));\n setHasEdits(true);\n },\n [speedMarkers, pushUndo]\n );\n\n // Undoable default-speed setter (the raw setter stays internal for undo/reset)\n const setDefaultSpeedUndoable = useCallback(\n (speed: PlaybackSpeed) => {\n if (speed === defaultSpeed) return;\n pushUndo();\n setDefaultSpeed(speed);\n setHasEdits(true);\n },\n [defaultSpeed, pushUndo]\n );\n\n /**\n * Apply one speed across an index range (typically the current selection):\n * clears markers inside the range, marks the start with the new speed, and\n * restores the previous effective speed just after the end so only the\n * range is affected. One undo step.\n */\n const setSpeedForRange = useCallback(\n (startIndex: number, endIndex: number, speed: PlaybackSpeed) => {\n if (startIndex > endIndex) return;\n pushUndo();\n const afterIndex = endIndex + 1;\n const speedAfter = getSpeedAtIndex(\n afterIndex,\n speedMarkers,\n defaultSpeed\n );\n const kept = speedMarkers.filter(\n (m) => m.wordIndex < startIndex || m.wordIndex > endIndex\n );\n // Only pin the range start when it changes the effective speed there.\n // Applying the speed already in effect (e.g. 1x under a 1x default, or a\n // preceding marker's speed) would persist a no-op marker into saves/exports.\n const speedAtStart = getSpeedAtIndex(startIndex, kept, defaultSpeed);\n const next =\n speedAtStart === speed\n ? [...kept]\n : [...kept, { wordIndex: startIndex, speed }];\n if (\n afterIndex < editedWords.length &&\n speedAfter !== speed &&\n !kept.some((m) => m.wordIndex === afterIndex)\n ) {\n next.push({ wordIndex: afterIndex, speed: speedAfter });\n }\n next.sort((a, b) => a.wordIndex - b.wordIndex);\n setSpeedMarkers(next);\n setHasEdits(true);\n },\n [pushUndo, speedMarkers, defaultSpeed, editedWords.length]\n );\n\n // Effective playback speed at a word index (bound to current markers + default)\n const getSpeedAtIndexBound = useCallback(\n (index: number): PlaybackSpeed => {\n return getSpeedAtIndex(index, speedMarkers, defaultSpeed);\n },\n [speedMarkers, defaultSpeed]\n );\n\n // -- Derived data --\n\n const playbackSegments = useMemo(\n () => buildPlaybackSegments(editedWords),\n [editedWords]\n );\n\n const stats = useMemo<TranscriptEditStats>(() => {\n // Count active (non-deleted) words (excluding silences for word count)\n const activeWordCount = editedWords.filter(\n (ew) => !ew.deleted && !isSilenceType(ew.word.wordType)\n ).length;\n const activeSilenceCount = editedWords.filter(\n (ew) => !ew.deleted && isSilenceType(ew.word.wordType)\n ).length;\n const deletedWordCount = editedWords.filter(\n (ew) => ew.deleted && !isSilenceType(ew.word.wordType)\n ).length;\n const deletedSilenceCount = editedWords.filter(\n (ew) => ew.deleted && isSilenceType(ew.word.wordType)\n ).length;\n\n // Calculate edited duration (sum of non-deleted words/silences, adjusted for speed markers)\n const editedDurationMs = editedWords.reduce((sum, ew, index) => {\n if (ew.deleted) return sum;\n const wordDuration = ew.word.endMs - ew.word.startMs;\n const speed = getSpeedAtIndex(index, speedMarkers, defaultSpeed);\n // Duration is divided by speed (2x speed = half the time)\n return sum + wordDuration / speed;\n }, 0);\n\n return {\n activeWordCount,\n activeSilenceCount,\n deletedWordCount,\n deletedSilenceCount,\n editedDurationMs,\n };\n }, [editedWords, speedMarkers, defaultSpeed]);\n\n const fillerAnalysis = useMemo<FillerAnalysis>(() => {\n // Occurrence counts per filler (and every active word, for custom fillers)\n const matchCounts = new Map<string, number>();\n for (const ew of editedWords) {\n if (ew.deleted || isSilenceType(ew.word.wordType)) continue;\n const wordText = normalizeWordText(ew.word.text);\n // One increment per occurrence — DEFAULT_FILLER_WORDS entries were\n // previously counted twice (once in a defaults loop, once here).\n matchCounts.set(wordText, (matchCounts.get(wordText) || 0) + 1);\n }\n\n // Total duration per filler word type\n const durations = new Map<string, number>();\n for (const ew of editedWords) {\n if (ew.deleted || isSilenceType(ew.word.wordType)) continue;\n const wordText = normalizeWordText(ew.word.text);\n const wordDuration = ew.word.endMs - ew.word.startMs;\n // One accumulation per occurrence — DEFAULT_FILLER_WORDS entries were\n // previously accumulated twice, inflating time-saved estimates 2x.\n durations.set(wordText, (durations.get(wordText) || 0) + wordDuration);\n }\n\n // Silence counts and durations at various thresholds\n const silenceCounts = SILENCE_ANALYSIS_THRESHOLDS.map((threshold) => {\n const thresholdMs = threshold * 1000;\n let count = 0;\n let durationMs = 0;\n for (const ew of editedWords) {\n if (ew.deleted) continue;\n if (!isSilenceType(ew.word.wordType)) continue;\n const silenceDuration = ew.word.endMs - ew.word.startMs;\n if (silenceDuration > thresholdMs) {\n count++;\n durationMs += silenceDuration;\n }\n }\n return { threshold, count, durationMs };\n });\n\n return {\n matchCounts,\n durations,\n silenceCounts,\n defaultFillerWords: DEFAULT_FILLER_WORDS,\n };\n }, [editedWords]);\n\n return {\n editedWords,\n undoStack,\n clipboard,\n hasEdits,\n pushUndo,\n undo,\n toggleWordDeleted,\n deleteRange,\n cut,\n copy,\n paste,\n setWordText,\n splitSilence,\n deleteWord,\n replaceEditedWords,\n removeFillers,\n setSilenceThresholds,\n silenceThresholds: { minSilenceMs, nlSilenceMs },\n speedMarkers,\n defaultSpeed,\n setDefaultSpeed: setDefaultSpeedUndoable,\n setSpeedForRange,\n toggleSpeedMarker,\n removeSpeedMarker,\n getSpeedAtIndex: getSpeedAtIndexBound,\n getSpeedMarkerAtIndex,\n playbackSegments,\n stats,\n fillerAnalysis,\n };\n}\n"]}