From 823e91c7b9416faf2851786a14a3feb46d9d088e Mon Sep 17 00:00:00 2001 From: Allister MacLeod Date: Wed, 8 Jul 2026 11:02:19 -0400 Subject: [PATCH 1/3] Fix Snapshot tab hang when snapshots in different realms share a name Snapshot lookups in the Content window were keyed by snapshot name, which is not unique across realm folders: auto snapshots are always named LastPublished-, so publishing to a second realm made ToDictionary throw on a duplicate key. The exception escaped a fire-and-forget task and left _gatheringSnapshots stuck true, so the tab drew "Loading snapshots" forever. Key the lookups by file path (matching _allSnapshots) and clear the loading flag in a finally block. Add editor unit tests for the duplicate-name case. Fixes #4743 Co-Authored-By: Claude Fable 5 --- client/Packages/com.beamable/CHANGELOG.md | 6 +++ .../ContentWindow_SnapshotManager.cs | 28 +++++++--- .../Editor/ContentWindowSnapshotTests.cs | 54 +++++++++++++++++++ .../Editor/ContentWindowSnapshotTests.cs.meta | 11 ++++ 4 files changed, 92 insertions(+), 7 deletions(-) create mode 100644 client/Packages/com.beamable/Tests/Editor/ContentWindowSnapshotTests.cs create mode 100644 client/Packages/com.beamable/Tests/Editor/ContentWindowSnapshotTests.cs.meta diff --git a/client/Packages/com.beamable/CHANGELOG.md b/client/Packages/com.beamable/CHANGELOG.md index dba40a5952..6c9b49c1cf 100644 --- a/client/Packages/com.beamable/CHANGELOG.md +++ b/client/Packages/com.beamable/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Fixed + +- Fixed the Content window Snapshot tab hanging at "Loading snapshots" when snapshots in different realm folders share a name (e.g. `LastPublished-global.json` auto snapshots from publishing to multiple realms) [4743](https://github.com/beamable/BeamableProduct/issues/4743) + ## [5.1.1] - 2026-06-29 ### Added diff --git a/client/Packages/com.beamable/Editor/UI/ContentWindow/ContentWindow_SnapshotManager.cs b/client/Packages/com.beamable/Editor/UI/ContentWindow/ContentWindow_SnapshotManager.cs index 9d5e70bfb6..07af2d245d 100644 --- a/client/Packages/com.beamable/Editor/UI/ContentWindow/ContentWindow_SnapshotManager.cs +++ b/client/Packages/com.beamable/Editor/UI/ContentWindow/ContentWindow_SnapshotManager.cs @@ -598,13 +598,27 @@ private async Task CacheSnapshots() _selectedSnapshotsPaths.Clear(); _snapshotSearchData = new SearchData(); _gatheringSnapshots = true; - var snapshotListResult = await _contentService.GetContentSnapshots(); - _sharedSnapshots = snapshotListResult.SharedSnapshots.ToDictionary(item => item.Name, item => item); - _localSnapshots = snapshotListResult.LocalSnapshots.ToDictionary(item => item.Name, item => item); - _allSnapshots.Clear(); - _localSnapshots.Values.ToList().ForEach(snapshot => _allSnapshots.Add(snapshot.Path, snapshot)); - _sharedSnapshots.Values.ToList().ForEach(snapshot => _allSnapshots.Add(snapshot.Path, snapshot)); - _gatheringSnapshots = false; + try + { + var snapshotListResult = await _contentService.GetContentSnapshots(); + _sharedSnapshots = BuildSnapshotLookup(snapshotListResult.SharedSnapshots); + _localSnapshots = BuildSnapshotLookup(snapshotListResult.LocalSnapshots); + _allSnapshots.Clear(); + _localSnapshots.Values.ToList().ForEach(snapshot => _allSnapshots.Add(snapshot.Path, snapshot)); + _sharedSnapshots.Values.ToList().ForEach(snapshot => _allSnapshots.Add(snapshot.Path, snapshot)); + } + finally + { + // Always clear the flag, otherwise the window is stuck on "Loading snapshots". ~Claude + _gatheringSnapshots = false; + } + } + + // Snapshots in different realm folders can share a file name (auto snapshots are + // always named "LastPublished-"), so the lookup must key by path. ~Claude + public static Dictionary BuildSnapshotLookup(IEnumerable snapshots) + { + return snapshots.ToDictionary(item => item.Path, item => item); } } diff --git a/client/Packages/com.beamable/Tests/Editor/ContentWindowSnapshotTests.cs b/client/Packages/com.beamable/Tests/Editor/ContentWindowSnapshotTests.cs new file mode 100644 index 0000000000..968b99d1a4 --- /dev/null +++ b/client/Packages/com.beamable/Tests/Editor/ContentWindowSnapshotTests.cs @@ -0,0 +1,54 @@ +using Beamable.Editor.BeamCli.Commands; +using Beamable.Editor.UI.ContentWindow; +using NUnit.Framework; +using System.Linq; + +namespace Beamable.Editor.Tests +{ + public class ContentWindowSnapshotTests + { + private static BeamManifestSnapshotItem Snapshot(string name, string path) + { + return new BeamManifestSnapshotItem { Name = name, Path = path }; + } + + [Test] + public void BuildSnapshotLookup_DuplicateNamesAcrossRealms_KeepsAllSnapshots() + { + // Auto snapshots share the name "LastPublished-" across realm folders. ~Claude + var snapshots = new[] + { + Snapshot("LastPublished-global", ".beamable/content-snapshots/DE_111/LastPublished-global.json"), + Snapshot("LastPublished-global", ".beamable/content-snapshots/DE_222/LastPublished-global.json"), + }; + + var lookup = ContentWindow.BuildSnapshotLookup(snapshots); + + Assert.AreEqual(2, lookup.Count); + CollectionAssert.AreEquivalent(snapshots.Select(item => item.Path), lookup.Keys); + } + + [Test] + public void BuildSnapshotLookup_KeysByPath() + { + var snapshots = new[] + { + Snapshot("my-snapshot", ".beamable/content-snapshots/DE_111/my-snapshot.json"), + Snapshot("other-snapshot", ".beamable/temp/content-snapshots/DE_111/other-snapshot.json"), + }; + + var lookup = ContentWindow.BuildSnapshotLookup(snapshots); + + Assert.AreSame(snapshots[0], lookup[snapshots[0].Path]); + Assert.AreSame(snapshots[1], lookup[snapshots[1].Path]); + } + + [Test] + public void BuildSnapshotLookup_EmptyInput_ReturnsEmptyLookup() + { + var lookup = ContentWindow.BuildSnapshotLookup(System.Array.Empty()); + + Assert.AreEqual(0, lookup.Count); + } + } +} diff --git a/client/Packages/com.beamable/Tests/Editor/ContentWindowSnapshotTests.cs.meta b/client/Packages/com.beamable/Tests/Editor/ContentWindowSnapshotTests.cs.meta new file mode 100644 index 0000000000..208d5f1efa --- /dev/null +++ b/client/Packages/com.beamable/Tests/Editor/ContentWindowSnapshotTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6826f76d647f44c6a110dcdee7992df0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From aa1fa9b70093f648092db018640785491ac64cd2 Mon Sep 17 00:00:00 2001 From: Allister MacLeod Date: Wed, 8 Jul 2026 11:03:06 -0400 Subject: [PATCH 2/3] Drop issue link from changelog entry to match prevailing style Co-Authored-By: Claude Fable 5 --- client/Packages/com.beamable/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/Packages/com.beamable/CHANGELOG.md b/client/Packages/com.beamable/CHANGELOG.md index 6c9b49c1cf..1e6ce8335b 100644 --- a/client/Packages/com.beamable/CHANGELOG.md +++ b/client/Packages/com.beamable/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -- Fixed the Content window Snapshot tab hanging at "Loading snapshots" when snapshots in different realm folders share a name (e.g. `LastPublished-global.json` auto snapshots from publishing to multiple realms) [4743](https://github.com/beamable/BeamableProduct/issues/4743) +- Fixed the Content window Snapshot tab hanging at "Loading snapshots" when snapshots in different realm folders share a name (e.g. `LastPublished-global.json` auto snapshots from publishing to multiple realms) ## [5.1.1] - 2026-06-29 From 8d3ad10f06eaca4592a628dc4e0f59e9a54c9797 Mon Sep 17 00:00:00 2001 From: Allister MacLeod Date: Wed, 8 Jul 2026 11:08:48 -0400 Subject: [PATCH 3/3] Remove comment signatures Co-Authored-By: Claude Fable 5 --- .../Editor/UI/ContentWindow/ContentWindow_SnapshotManager.cs | 4 ++-- .../com.beamable/Tests/Editor/ContentWindowSnapshotTests.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/Packages/com.beamable/Editor/UI/ContentWindow/ContentWindow_SnapshotManager.cs b/client/Packages/com.beamable/Editor/UI/ContentWindow/ContentWindow_SnapshotManager.cs index 07af2d245d..9b9207f242 100644 --- a/client/Packages/com.beamable/Editor/UI/ContentWindow/ContentWindow_SnapshotManager.cs +++ b/client/Packages/com.beamable/Editor/UI/ContentWindow/ContentWindow_SnapshotManager.cs @@ -609,13 +609,13 @@ private async Task CacheSnapshots() } finally { - // Always clear the flag, otherwise the window is stuck on "Loading snapshots". ~Claude + // Always clear the flag, otherwise the window is stuck on "Loading snapshots". _gatheringSnapshots = false; } } // Snapshots in different realm folders can share a file name (auto snapshots are - // always named "LastPublished-"), so the lookup must key by path. ~Claude + // always named "LastPublished-"), so the lookup must key by path. public static Dictionary BuildSnapshotLookup(IEnumerable snapshots) { return snapshots.ToDictionary(item => item.Path, item => item); diff --git a/client/Packages/com.beamable/Tests/Editor/ContentWindowSnapshotTests.cs b/client/Packages/com.beamable/Tests/Editor/ContentWindowSnapshotTests.cs index 968b99d1a4..28928e13a1 100644 --- a/client/Packages/com.beamable/Tests/Editor/ContentWindowSnapshotTests.cs +++ b/client/Packages/com.beamable/Tests/Editor/ContentWindowSnapshotTests.cs @@ -15,7 +15,7 @@ private static BeamManifestSnapshotItem Snapshot(string name, string path) [Test] public void BuildSnapshotLookup_DuplicateNamesAcrossRealms_KeepsAllSnapshots() { - // Auto snapshots share the name "LastPublished-" across realm folders. ~Claude + // Auto snapshots share the name "LastPublished-" across realm folders. var snapshots = new[] { Snapshot("LastPublished-global", ".beamable/content-snapshots/DE_111/LastPublished-global.json"),