diff --git a/apps/transcripts/workspace.html b/apps/transcripts/workspace.html
index b80194871..fd2c611e1 100644
--- a/apps/transcripts/workspace.html
+++ b/apps/transcripts/workspace.html
@@ -2266,12 +2266,20 @@ body.has-date-nav .workspace:has(.tr-wrap) {
updateZoom();
}).observe(zoom);
- // Load transcript ranges and compute dynamic timeline bounds
- fetch(`/app/transcripts/api/ranges/${day}`)
- .then(r => r.json())
- .then(data => {
- // Compute and apply dynamic bounds
- const bounds = computeTimelineBounds(data);
+ // Load transcript ranges and segments in parallel, initialize after both resolve
+ const rangesFetch = fetch(`/app/transcripts/api/ranges/${day}`).then(r => {
+ if (!r.ok) throw new Error(`Ranges failed: ${r.status}`);
+ return r.json();
+ });
+ const segmentsFetch = fetch(`/app/transcripts/api/segments/${day}`).then(r => {
+ if (!r.ok) throw new Error(`Segments failed: ${r.status}`);
+ return r.json();
+ });
+
+ Promise.all([rangesFetch, segmentsFetch])
+ .then(([rangesData, segmentsData]) => {
+ // Apply dynamic timeline bounds from ranges
+ const bounds = computeTimelineBounds(rangesData);
timelineStart = bounds.start;
timelineEnd = bounds.end;
@@ -2283,26 +2291,22 @@ body.has-date-nav .workspace:has(.tr-wrap) {
const mid = (timelineStart + timelineEnd) / 2;
range = { start: snap(mid - DEFAULT_LEN / 2), end: snap(mid + DEFAULT_LEN / 2) };
- // Now build the grid and render
+ // Build the grid and render timeline
buildGrid();
renderTimeline();
- // Add segment indicators
- (data.audio || []).forEach(rg => {
+ // Add segment indicators from ranges
+ (rangesData.audio || []).forEach(rg => {
const [s, e] = rg.map(parseTime);
addSegmentIndicator('audio', s, e, 0);
});
- (data.screen || []).forEach(rg => {
+ (rangesData.screen || []).forEach(rg => {
const [s, e] = rg.map(parseTime);
addSegmentIndicator('screen', s, e, 1);
});
- });
- // Load segments for the zoom view
- fetch(`/app/transcripts/api/segments/${day}`)
- .then(r => r.json())
- .then(data => {
- allSegments = data.segments || [];
+ // Store segments and update zoom
+ allSegments = segmentsData.segments || [];
updateZoom();
// Check for hash fragment to auto-select segment
@@ -2310,7 +2314,6 @@ body.has-date-nav .workspace:has(.tr-wrap) {
if (hash) {
const seg = allSegments.find(s => s.key === hash);
if (seg) {
- // Adjust range to include the segment
const segStart = parseTime(seg.start);
const segEnd = parseTime(seg.end);
const rangeLen = range.end - range.start;
@@ -2323,6 +2326,14 @@ body.has-date-nav .workspace:has(.tr-wrap) {
selectSegment(seg, false);
}
}
+ })
+ .catch(err => {
+ console.error('Failed to load transcript data:', err);
+ const errEl = document.createElement('div');
+ errEl.className = 'tr-zoom-empty';
+ errEl.textContent = 'Failed to load transcript data';
+ zoomSegments.innerHTML = '';
+ zoomSegments.appendChild(errEl);
});
// Handle browser back/forward
diff --git a/tests/baselines/api/transcripts/segments.json b/tests/baselines/api/transcripts/segments.json
index 20a177388..aca747225 100644
--- a/tests/baselines/api/transcripts/segments.json
+++ b/tests/baselines/api/transcripts/segments.json
@@ -6,8 +6,8 @@
"start": "09:00",
"stream": "default",
"types": [
- "transcripts",
- "percepts"
+ "audio",
+ "screen"
]
},
{
@@ -16,8 +16,8 @@
"start": "14:00",
"stream": "default",
"types": [
- "transcripts",
- "percepts"
+ "audio",
+ "screen"
]
},
{
@@ -26,8 +26,8 @@
"start": "18:00",
"stream": "default",
"types": [
- "transcripts",
- "percepts"
+ "audio",
+ "screen"
]
}
]
diff --git a/tests/test_cluster.py b/tests/test_cluster.py
index 2cbf9786f..b602ae203 100644
--- a/tests/test_cluster.py
+++ b/tests/test_cluster.py
@@ -134,20 +134,20 @@ def test_cluster_segments(tmp_path, monkeypatch):
assert segments[0]["key"] == "090000_300"
assert segments[0]["start"] == "09:00"
assert segments[0]["end"] == "09:05"
- assert segments[0]["types"] == ["transcripts"]
+ assert segments[0]["types"] == ["audio"]
# Check second segment (both transcripts and screen)
assert segments[1]["key"] == "100000_600"
assert segments[1]["start"] == "10:00"
assert segments[1]["end"] == "10:10"
- assert "transcripts" in segments[1]["types"]
- assert "percepts" in segments[1]["types"]
+ assert "audio" in segments[1]["types"]
+ assert "screen" in segments[1]["types"]
# Check third segment (screen only)
assert segments[2]["key"] == "110000_300"
assert segments[2]["start"] == "11:00"
assert segments[2]["end"] == "11:05"
- assert segments[2]["types"] == ["percepts"]
+ assert segments[2]["types"] == ["screen"]
def test_cluster_period_uses_raw_screen(tmp_path, monkeypatch):
@@ -333,7 +333,7 @@ def test_cluster_segments_with_split_screen(tmp_path, monkeypatch):
assert len(segments) == 1
assert segments[0]["key"] == "100000_300"
- assert "percepts" in segments[0]["types"]
+ assert "screen" in segments[0]["types"]
def test_cluster_span(tmp_path, monkeypatch):
diff --git a/think/cluster.py b/think/cluster.py
index 6cfc7ffb8..e25711147 100644
--- a/think/cluster.py
+++ b/think/cluster.py
@@ -463,7 +463,7 @@ def cluster_segments(day: str) -> list[dict[str, Any]]:
- key: segment directory name (HHMMSS_LEN format)
- start: start time as HH:MM
- end: end time as HH:MM
- - types: list of content types present ("transcripts", "percepts", or both)
+ - types: list of content types present ("audio", "screen", or both)
"""
from think.utils import segment_parse
@@ -490,11 +490,11 @@ def cluster_segments(day: str) -> list[dict[str, Any]]:
or any(seg_path.glob("*_transcript.md"))
or (seg_path / "imported.md").exists()
):
- types.append("transcripts")
+ types.append("audio")
# Check for screen content
if (seg_path / "screen.jsonl").exists() or any(seg_path.glob("*_screen.jsonl")):
- types.append("percepts")
+ types.append("screen")
if not types:
continue