From aec8555b9a22929428528fdbf6456f71ca9095de Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Fri, 31 Jul 2026 18:00:48 +0200 Subject: [PATCH] Fixed project list and recovery view to include unavailable projects --- .../VisualBriefingAssistant.razor | 49 ++++-- .../VisualBriefingAssistant.razor.Build.cs | 14 +- .../VisualBriefingAssistant.razor.Projects.cs | 153 ++++++++++++++++-- .../VisualBriefingAssistant.razor.Sources.cs | 7 +- .../VisualBriefingAssistant.razor.cs | 7 +- .../VisualBriefingProjectEntry.cs | 13 ++ .../VisualBriefingProjectLoadStatus.cs | 11 ++ .../VisualBriefingStore.Projects.cs | 149 ++++++++++++++--- 8 files changed, 338 insertions(+), 65 deletions(-) create mode 100644 app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingProjectEntry.cs create mode 100644 app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingProjectLoadStatus.cs diff --git a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingAssistant.razor b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingAssistant.razor index d1987622..6589d158 100644 --- a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingAssistant.razor +++ b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingAssistant.razor @@ -13,21 +13,26 @@ - @foreach (var briefing in this.briefings) + @foreach (var project in this.projects) { - + - @(briefing.BriefingId == this.selectedBriefing?.BriefingId ? this.projectName : briefing.Name) - @briefing.ModifiedAtUtc.ToLocalTime().ToString("g") - @if (this.IsGenerating(briefing.BriefingId)) + @this.ProjectDisplayName(project) + @project.ModifiedAtUtc.ToLocalTime().ToString("g") + @if (!project.IsAvailable) + { + @this.ProjectStatusName(project.Status) + } + @if (project.IsAvailable && this.IsGenerating(project.BriefingId)) { } - + @if (project.IsAvailable) + { + + } } @@ -41,7 +46,31 @@
- @if (this.selectedBriefing is null) + @if (this.selectedProject is not null && !this.selectedProject.IsAvailable) + { + + + @this.ProjectDisplayName(this.selectedProject) + + @this.ProjectRecoveryMessage(this.selectedProject.Status) + + @T("AI Studio has left the project files unchanged. A future update may make this visual briefing accessible again.") + + @T("Project ID"): @this.selectedProject.BriefingId.ToString("D") + + + + @T("If you need help, report the problem and include the project ID.") + @T("Report a problem?") + + + @T("Open project folder") + @T("Delete") + + + + } + else if (this.selectedBriefing is null) { @T("Create or import a visual briefing to begin.") diff --git a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingAssistant.razor.Build.cs b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingAssistant.razor.Build.cs index bee8af0f..02478f83 100644 --- a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingAssistant.razor.Build.cs +++ b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingAssistant.razor.Build.cs @@ -150,12 +150,7 @@ public partial class VisualBriefingAssistant { var latest = await this.Store.LoadAsync(briefingId, cancellation.Token); if (latest is not null) - this.briefings = - [ - .. this.briefings - .Select(briefing => briefing.BriefingId == briefingId ? latest : briefing) - .OrderByDescending(briefing => briefing.ModifiedAtUtc) - ]; + this.UpdateProject(latest); } this.Snackbar.Add(T("A new visual briefing version was created."), Severity.Success); @@ -250,12 +245,7 @@ public partial class VisualBriefingAssistant { var latest = await this.Store.LoadAsync(briefingId, cancellation.Token); if (latest is not null) - this.briefings = - [ - .. this.briefings - .Select(briefing => briefing.BriefingId == briefingId ? latest : briefing) - .OrderByDescending(briefing => briefing.ModifiedAtUtc) - ]; + this.UpdateProject(latest); } this.Snackbar.Add(T("The briefing was recompiled with the current AI Studio version."), Severity.Success); diff --git a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingAssistant.razor.Projects.cs b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingAssistant.razor.Projects.cs index 3632aaf7..1c6ee3e4 100644 --- a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingAssistant.razor.Projects.cs +++ b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingAssistant.razor.Projects.cs @@ -5,6 +5,7 @@ using AIStudio.Dialogs; using AIStudio.Provider; using AIStudio.Settings; using AIStudio.Tools.Media; +using AIStudio.Tools.Rust; using DialogOptions = AIStudio.Dialogs.DialogOptions; using ComponentKind = AIStudio.Tools.Components; @@ -24,19 +25,21 @@ public partial class VisualBriefingAssistant /// private async Task ReloadListAsync(Guid? selectId = null) { - this.briefings = await this.Store.ListAsync(); + this.projects = await this.Store.ListProjectsAsync(); var id = selectId ?? - this.selectedBriefing?.BriefingId ?? + this.selectedProject?.BriefingId ?? this.Store.LastSelectedBriefingId ?? - this.briefings.FirstOrDefault()?.BriefingId; + this.projects.FirstOrDefault()?.BriefingId; var selected = id is null ? null - : this.briefings.FirstOrDefault(briefing => briefing.BriefingId == id); + : this.projects.FirstOrDefault(project => project.BriefingId == id); - selected ??= this.briefings.FirstOrDefault(); + selected ??= this.projects.FirstOrDefault(); if (selected is not null) - await this.ApplySelectedBriefingAsync(selected); + await this.ApplySelectedProjectAsync(selected); + else + this.ClearSelectedProject(); } /// @@ -44,15 +47,15 @@ public partial class VisualBriefingAssistant /// private async Task SelectBriefingAsync(Guid briefingId) { - if (this.selectedBriefing?.BriefingId == briefingId) + if (this.selectedProject?.BriefingId == briefingId) return; if (this.selectedBriefing is not null) await this.SaveCurrentAsync(); - var briefing = this.briefings.FirstOrDefault(candidate => candidate.BriefingId == briefingId); - if (briefing is not null) - await this.ApplySelectedBriefingAsync(briefing); + var project = this.projects.FirstOrDefault(candidate => candidate.BriefingId == briefingId); + if (project is not null) + await this.ApplySelectedProjectAsync(project); } /// @@ -116,29 +119,70 @@ public partial class VisualBriefingAssistant /// private async Task DeleteAsync() { - if (this.selectedBriefing is null) + if (this.selectedProject is null) return; - var parameters = new DialogParameters + var parameters = new DialogParameters(); + if (this.selectedProject.IsAvailable) + parameters.Add(dialog => dialog.Message, string.Format(T("Permanently delete the visual briefing '{0}' and all of its versions and transcripts?"), this.selectedProject.Name)); + else { - { dialog => dialog.Message, string.Format(T("Permanently delete the visual briefing '{0}' and all of its versions and transcripts?"), this.selectedBriefing.Name) }, - }; + var reportingWarning = T("This visual briefing cannot currently be opened. Consider reporting the problem in the [MindWork AI Studio issue tracker](https://github.com/MindWorkAI/AI-Studio), because a future update may make the briefing accessible again."); + var deletionWarning = T("Permanently delete this visual briefing and all of its versions and transcripts?"); + parameters.Add(dialog => dialog.MarkdownBody, $"{reportingWarning}\n\n{deletionWarning}"); + } var reference = await this.DialogService.ShowAsync(T("Delete visual briefing permanently"), parameters, DialogOptions.FULLSCREEN); var result = await reference.Result; if (result is null || result.Canceled) return; - var id = this.selectedBriefing.BriefingId; + var id = this.selectedProject.BriefingId; this.MediaTranscriptionService.ClearOwnerState(MediaImportOwner.ForVisualBriefing(id)); await this.Store.DeleteAsync(id); await this.Store.ForgetSelectionAsync(id); - this.selectedBriefing = null; - this.previewUrl = string.Empty; + this.ClearSelectedProject(); await this.ReloadListAsync(); } + /// + /// Opens the selected project directory without attempting to read or repair its contents. + /// + private async Task OpenSelectedProjectDirectoryAsync() + { + if (this.selectedProject is null) + return; + + var path = await this.Store.GetProjectDirectoryPathAsync(this.selectedProject.BriefingId); + if (string.IsNullOrWhiteSpace(path)) + { + this.Snackbar.Add(T("The visual briefing project folder is not available."), Severity.Warning); + return; + } + + OpenPathResponse response; + try + { + response = await this.RustService.TryOpenPathInRuntimeFileManager(path); + } + catch (Exception exception) + { + this.Logger.LogWarning(exception, "Could not open the visual briefing project folder. BriefingId={BriefingId}", this.selectedProject.BriefingId); + this.Snackbar.Add(T("Could not open the visual briefing project folder."), Severity.Error); + return; + } + + if (response.Success) + { + this.Snackbar.Add(T("Opened the visual briefing project folder."), Severity.Success); + return; + } + + var issue = string.IsNullOrWhiteSpace(response.Issue) ? T("Unknown error") : response.Issue; + this.Snackbar.Add(string.Format(T("Could not open the visual briefing project folder: {0}"), issue), Severity.Error); + } + /// /// Defines SaveCurrentAsync for the visual briefing feature. /// @@ -188,6 +232,7 @@ public partial class VisualBriefingAssistant private async Task ApplySelectedBriefingAsync(VisualBriefingManifest briefing) { await this.Store.RememberSelectionAsync(briefing.BriefingId); + this.selectedProject = VisualBriefingProjectEntry.FromManifest(briefing); this.selectedBriefing = briefing; var resumableBuilds = await this.Store.ListBuildsAsync(briefing.BriefingId); var persistedDiagnostics = resumableBuilds.FirstOrDefault() is { } latestPersistedBuild @@ -252,6 +297,80 @@ public partial class VisualBriefingAssistant this.lastPersistedState = this.BuildPersistenceFingerprint(); } + /// + /// Applies either a normal editor project or a content-free recovery entry. + /// + private async Task ApplySelectedProjectAsync(VisualBriefingProjectEntry project) + { + if (project.IsAvailable) + { + await this.ApplySelectedBriefingAsync(project.Manifest!); + return; + } + + await this.Store.RememberSelectionAsync(project.BriefingId); + this.ClearSelectedProject(); + this.selectedProject = project; + } + + /// + /// Clears editor-only state so an unavailable project cannot trigger saves or background work. + /// + private void ClearSelectedProject() + { + this.selectedProject = null; + this.selectedBriefing = null; + this.sourceMaterial = []; + this.visualAssets = []; + this.selectedRevisionId = Guid.Empty; + this.previewUrl = string.Empty; + this.latestBuild = null; + this.lastBuildDiagnostics = null; + this.reusableContentBuildId = null; + this.lastPersistedState = string.Empty; + } + + /// + /// Replaces an available list entry after a background operation updates its manifest. + /// + private void UpdateProject(VisualBriefingManifest briefing) + { + var updated = VisualBriefingProjectEntry.FromManifest(briefing); + this.projects = [.. this.projects.Select(project => project.BriefingId == briefing.BriefingId ? updated : project).OrderByDescending(project => project.ModifiedAtUtc)]; + + if (this.selectedProject?.BriefingId == briefing.BriefingId) + this.selectedProject = updated; + } + + /// + /// Gets a safe list and recovery-view title. + /// + private string ProjectDisplayName(VisualBriefingProjectEntry project) + { + if (project.BriefingId == this.selectedBriefing?.BriefingId) + return this.projectName; + + return string.IsNullOrWhiteSpace(project.Name) ? T("Unavailable visual briefing") : project.Name; + } + + /// + /// Gets the concise project-list status. + /// + private string ProjectStatusName(VisualBriefingProjectLoadStatus status) => status switch + { + VisualBriefingProjectLoadStatus.NEWER_VERSION => T("Requires a newer AI Studio version"), + _ => T("Cannot be opened"), + }; + + /// + /// Gets the recovery explanation for an unavailable project. + /// + private string ProjectRecoveryMessage(VisualBriefingProjectLoadStatus status) => status switch + { + VisualBriefingProjectLoadStatus.NEWER_VERSION => T("This visual briefing was created by a newer AI Studio version and cannot be opened by this version."), + _ => T("AI Studio cannot read this visual briefing. Its files may be incompatible or damaged."), + }; + /// /// Defines ProtectionLevelName for the visual briefing feature. /// diff --git a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingAssistant.razor.Sources.cs b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingAssistant.razor.Sources.cs index c881e020..ed9aaf9e 100644 --- a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingAssistant.razor.Sources.cs +++ b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingAssistant.razor.Sources.cs @@ -135,12 +135,7 @@ public partial class VisualBriefingAssistant var latest = await this.Store.LoadAsync(briefingId); if (latest is not null) { - this.briefings = - [ - .. this.briefings - .Select(briefing => briefing.BriefingId == briefingId ? latest : briefing) - .OrderByDescending(briefing => briefing.ModifiedAtUtc) - ]; + this.UpdateProject(latest); if (this.selectedBriefing?.BriefingId == briefingId) await this.ApplySelectedBriefingAsync(latest); diff --git a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingAssistant.razor.cs b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingAssistant.razor.cs index db4f6e93..32e298b1 100644 --- a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingAssistant.razor.cs +++ b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingAssistant.razor.cs @@ -91,8 +91,11 @@ public partial class VisualBriefingAssistant : MSGComponentBase /// Stops the background source-status monitor. private readonly CancellationTokenSource sourceMonitorCancellation = new(); - /// Stores projects ordered by most recent modification. - private IReadOnlyList briefings = []; + /// Stores available and recoverable projects ordered by most recent modification. + private IReadOnlyList projects = []; + + /// Stores the project entry currently selected in the list. + private VisualBriefingProjectEntry? selectedProject; /// Stores the project currently displayed by the editor. private VisualBriefingManifest? selectedBriefing; diff --git a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingProjectEntry.cs b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingProjectEntry.cs new file mode 100644 index 00000000..4aeea258 --- /dev/null +++ b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingProjectEntry.cs @@ -0,0 +1,13 @@ +namespace AIStudio.Assistants.VisualBriefing; + +/// +/// Provides safe list metadata even when the persisted manifest cannot be deserialized. +/// +internal sealed record VisualBriefingProjectEntry(Guid BriefingId, string Name, DateTimeOffset ModifiedAtUtc, VisualBriefingProjectLoadStatus Status, VisualBriefingManifest? Manifest) +{ + /// Gets whether the project can be opened normally. + public bool IsAvailable => this.Status is VisualBriefingProjectLoadStatus.AVAILABLE && this.Manifest is not null; + + /// Creates an available project entry from a validated manifest. + public static VisualBriefingProjectEntry FromManifest(VisualBriefingManifest manifest) => new(manifest.BriefingId, manifest.Name, manifest.ModifiedAtUtc, VisualBriefingProjectLoadStatus.AVAILABLE, manifest); +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingProjectLoadStatus.cs b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingProjectLoadStatus.cs new file mode 100644 index 00000000..295f7a5b --- /dev/null +++ b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingProjectLoadStatus.cs @@ -0,0 +1,11 @@ +namespace AIStudio.Assistants.VisualBriefing; + +/// +/// Describes whether a persisted visual briefing can be opened by this AI Studio version. +/// +internal enum VisualBriefingProjectLoadStatus +{ + AVAILABLE, + NEWER_VERSION, + UNAVAILABLE, +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingStore.Projects.cs b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingStore.Projects.cs index b1058274..cd5f8aed 100644 --- a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingStore.Projects.cs +++ b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingStore.Projects.cs @@ -121,21 +121,142 @@ public sealed partial class VisualBriefingStore /// Defines ListAsync for the visual briefing feature. /// public async Task> ListAsync(CancellationToken token = default) + { + var projects = await this.ListProjectsAsync(token); + return [.. projects.Where(project => project.IsAvailable).Select(project => project.Manifest!)]; + } + + /// + /// Lists every project directory, including projects whose manifests cannot be opened. + /// + internal async Task> ListProjectsAsync(CancellationToken token = default) { await this.InitializeAsync(token); - List manifests = []; + List projects = []; foreach (var directory in Directory.EnumerateDirectories(this.RootDirectory)) { token.ThrowIfCancellationRequested(); if (!Guid.TryParse(Path.GetFileName(directory), out var briefingId)) continue; - var manifest = await this.LoadAsync(briefingId, token); - if (manifest is not null) - manifests.Add(manifest); + projects.Add(await this.LoadProjectEntryAsync(briefingId, directory, token)); } - return manifests.OrderByDescending(manifest => manifest.ModifiedAtUtc).ToArray(); + return projects.OrderByDescending(project => project.ModifiedAtUtc).ToArray(); + } + + /// + /// Gets the exact project directory without interpreting or modifying its contents. + /// + internal async Task GetProjectDirectoryPathAsync(Guid briefingId, CancellationToken token = default) + { + await this.InitializeAsync(token); + var path = this.BriefingDirectory(briefingId); + return Directory.Exists(path) ? path : null; + } + + /// + /// Loads a normal manifest or returns a recovery entry with best-effort display metadata. + /// + private async Task LoadProjectEntryAsync(Guid briefingId, string directory, CancellationToken token) + { + var path = this.ManifestPath(briefingId); + var modifiedAtUtc = ProjectModifiedAtUtc(path, directory); + if (!File.Exists(path)) + return new(briefingId, string.Empty, modifiedAtUtc, VisualBriefingProjectLoadStatus.UNAVAILABLE, null); + + string json; + try + { + json = await File.ReadAllTextAsync(path, token); + } + catch (Exception exception) when (exception is IOException or UnauthorizedAccessException) + { + this.LogUnavailableManifest(briefingId, exception); + return new(briefingId, string.Empty, modifiedAtUtc, VisualBriefingProjectLoadStatus.UNAVAILABLE, null); + } + + try + { + var manifest = JsonSerializer.Deserialize(json, JSON_OPTIONS); + if (manifest is not null && IsValidManifest(manifest, briefingId)) + { + RefreshSourceStatuses(manifest); + return VisualBriefingProjectEntry.FromManifest(manifest); + } + } + catch (JsonException exception) + { + this.LogUnavailableManifest(briefingId, exception); + } + + var (name, persistedModifiedAtUtc, manifestVersion) = ReadProjectMetadata(json); + var status = manifestVersion is > VisualBriefingVersions.MANIFEST ? VisualBriefingProjectLoadStatus.NEWER_VERSION : VisualBriefingProjectLoadStatus.UNAVAILABLE; + return new(briefingId, name, persistedModifiedAtUtc ?? modifiedAtUtc, status, null); + } + + /// + /// Reads only non-authoritative display metadata from an otherwise unusable manifest. + /// + private static (string Name, DateTimeOffset? ModifiedAtUtc, int? ManifestVersion) ReadProjectMetadata(string json) + { + try + { + using var document = JsonDocument.Parse(json); + if (document.RootElement.ValueKind is not JsonValueKind.Object) + return (string.Empty, null, null); + + var root = document.RootElement; + var name = root.TryGetProperty("name", out var nameElement) && nameElement.ValueKind is JsonValueKind.String ? SanitizeProjectName(nameElement.GetString()) : string.Empty; + DateTimeOffset? modifiedAtUtc = root.TryGetProperty("modifiedAtUtc", out var modifiedElement) && modifiedElement.ValueKind is JsonValueKind.String && + modifiedElement.TryGetDateTimeOffset(out var parsedModifiedAtUtc) ? parsedModifiedAtUtc : null; + + int? manifestVersion = root.TryGetProperty("manifestVersion", out var versionElement) && versionElement.ValueKind is JsonValueKind.Number && + versionElement.TryGetInt32(out var parsedManifestVersion) ? parsedManifestVersion : null; + + return (name, modifiedAtUtc, manifestVersion); + } + catch (JsonException) + { + return (string.Empty, null, null); + } + } + + /// + /// Removes control characters and bounds untrusted recovery-list text. + /// + private static string SanitizeProjectName(string? name) + { + if (string.IsNullOrWhiteSpace(name)) + return string.Empty; + + var sanitized = new string(name.Where(character => !char.IsControl(character)).ToArray()).Trim(); + return sanitized.Length <= 200 ? sanitized : sanitized[..200]; + } + + /// + /// Gets a stable fallback timestamp from the manifest or project directory. + /// + private static DateTimeOffset ProjectModifiedAtUtc(string manifestPath, string directory) + { + try + { + var timestamp = File.Exists(manifestPath) ? File.GetLastWriteTimeUtc(manifestPath) : Directory.GetLastWriteTimeUtc(directory); + return new DateTimeOffset(timestamp); + } + catch (Exception exception) when (exception is IOException or UnauthorizedAccessException) + { + return DateTimeOffset.UnixEpoch; + } + } + + /// + /// Records why a manifest was exposed through the recovery lane. + /// + private void LogUnavailableManifest(Guid briefingId, Exception exception) + { + logger.LogWarning(new EventId((int)VisualBriefingLogEventId.STORE_REJECTED, nameof(VisualBriefingLogEventId.STORE_REJECTED)), exception, + "Could not load visual briefing manifest. BriefingId={BriefingId} ExceptionType={ExceptionType}", briefingId, exception.GetType().Name); } /// @@ -161,7 +282,7 @@ public sealed partial class VisualBriefingStore catch (Exception exception) when (exception is IOException or UnauthorizedAccessException or JsonException) { logger.LogWarning( - new EventId((int)VisualBriefingLogEventId.STORE_REJECTED, VisualBriefingLogEventId.STORE_REJECTED.ToString()), + new EventId((int)VisualBriefingLogEventId.STORE_REJECTED, nameof(VisualBriefingLogEventId.STORE_REJECTED)), exception, "Could not load visual briefing manifest. BriefingId={BriefingId} ExceptionType={ExceptionType}", briefingId, @@ -210,6 +331,7 @@ public sealed partial class VisualBriefingStore ModifiedAtUtc = now, Settings = settings, }; + await this.StoreManifestAtomicAsync(manifest, token); return manifest; } @@ -237,13 +359,7 @@ public sealed partial class VisualBriefingStore /// /// Defines SaveProjectAsync for the visual briefing feature. /// - public async Task SaveProjectAsync( - Guid briefingId, - string name, - string author, - VisualBriefingLocalSettings settings, - IEnumerable<(string Path, VisualBriefingSourceKind Kind)> sources, - CancellationToken token = default) + public async Task SaveProjectAsync(Guid briefingId, string name, string author, VisualBriefingLocalSettings settings, IEnumerable<(string Path, VisualBriefingSourceKind Kind)> sources, CancellationToken token = default) { await this.MutateManifestAsync(briefingId, manifest => { @@ -309,16 +425,13 @@ public sealed partial class VisualBriefingStore /// /// Defines LoadRequiredWithoutInitializeAsync for the visual briefing feature. /// - private async Task LoadRequiredWithoutInitializeAsync( - Guid briefingId, - CancellationToken token) + private async Task LoadRequiredWithoutInitializeAsync(Guid briefingId, CancellationToken token) { var path = this.ManifestPath(briefingId); if (!File.Exists(path)) throw new FileNotFoundException("The visual briefing does not exist.", path); - return await this.LoadWithoutInitializeAsync(briefingId, token) - ?? throw new InvalidDataException("The visual briefing manifest is invalid."); + return await this.LoadWithoutInitializeAsync(briefingId, token) ?? throw new InvalidDataException("The visual briefing manifest is invalid."); } ///