From 862568dafd2e6224e92cda2d1841870982514614 Mon Sep 17 00:00:00 2001 From: Robert Nederhorst Date: Tue, 17 Mar 2026 08:18:30 -0700 Subject: [PATCH] fix(session): timeline panel not populated on session restore MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two issues prevented the timeline panel from displaying after loading a saved session: 1. Race condition in SessionModel: updateViewportCurrentMediaContainer- IndexFromBackend() was called before the model tree was fully built. The async requestData(childrenRole) populates playlists and their children (timelines) progressively, but the container lookup ran before the timeline nodes existed. Added a re-trigger of both container index lookups at the end of processChildren() when the parent type is Session, Container List, or Playlist. 2. QML timeline panel only handled typeRole=="Timeline" in viewedMediaSetChanged(). When the restored viewed container is a Playlist (which is common — the session stores the Playlist UUID), the timeline panel did nothing. Added a Playlist handler that finds the first Timeline child in the Playlist's Container List and switches the viewed container to it, with async retry if the children haven't loaded yet. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/ui/qml/session/src/session_model_ui.cpp | 11 +++++++++ ui/qml/xstudio/views/timeline/XsTimeline.qml | 24 ++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/ui/qml/session/src/session_model_ui.cpp b/src/ui/qml/session/src/session_model_ui.cpp index bc4dc8362..f7fa9a371 100644 --- a/src/ui/qml/session/src/session_model_ui.cpp +++ b/src/ui/qml/session/src/session_model_ui.cpp @@ -627,6 +627,17 @@ void SessionModel::processChildren(const nlohmann::json &rj, const QModelIndex & emit dataChanged(parent_index, parent_index, roles); } + // After populating session/playlist children, re-trigger the viewport and + // current container lookup. On session restore the initial lookup fires + // before the model tree is fully built (the children arrive asynchronously), + // so timelines inside playlists are not yet findable. Re-checking here + // ensures the timeline panel picks up the active container once its node + // actually exists in the tree. + if (type == "Session" || type == "Container List" || type == "Playlist") { + updateCurrentMediaContainerIndexFromBackend(); + updateViewportCurrentMediaContainerIndexFromBackend(); + } + emit jsonChanged(); CHECK_SLOW_WATCHER_FAST() diff --git a/ui/qml/xstudio/views/timeline/XsTimeline.qml b/ui/qml/xstudio/views/timeline/XsTimeline.qml index 6a5789b80..5302bc30f 100644 --- a/ui/qml/xstudio/views/timeline/XsTimeline.qml +++ b/ui/qml/xstudio/views/timeline/XsTimeline.qml @@ -306,6 +306,30 @@ Rectangle { initTimeline() }}(), 50); + } else if (viewedMediaSetProperties.index.valid && viewedMediaSetProperties.values.typeRole == "Playlist") { + // When restoring a session, the viewed container may be a Playlist + // rather than a Timeline. Find the first Timeline child inside the + // Playlist's Container List (row 2) and initialise from it. + let containerListIndex = theSessionData.index(2, 0, viewedMediaSetProperties.index) + let childCount = theSessionData.rowCount(containerListIndex) + for (let i = 0; i < childCount; i++) { + let childIndex = theSessionData.index(i, 0, containerListIndex) + if (theSessionData.get(childIndex, "typeRole") == "Timeline") { + // Switch the viewed container to this Timeline so the + // playhead and viewport are correctly attached. + theSessionData.viewportCurrentMediaContainerIndex = childIndex + return + } + } + // No timeline children found (or not yet loaded). If the container + // list is empty the data may still be arriving asynchronously, so + // retry after a short delay. + if (childCount == 0 && !timeline_items.rootIndex.valid) { + callbackTimer.setTimeout(function() { return function() { + viewedMediaSetChanged() + }}(), 250); + } + } else if (!timeline_items.rootIndex.valid) { // if the user has selected something that is not a timeline (playlist, // subset etc.), we do not update our index here (unless the timeline