From 4f82dc308af81114f2e845bab55243d4c1b710e3 Mon Sep 17 00:00:00 2001 From: Nick Beckley Date: Thu, 27 Aug 2026 19:32:27 +0000 Subject: [PATCH] Streamline core import wizard steps --- PlotLine.Tests/Program.cs | 71 +++++++++++++++++++ PlotLine/Views/Onboarding/Index.cshtml | 24 ++++--- PlotLine/Views/Onboarding/ScanReview.cshtml | 26 +++---- PlotLine/wwwroot/css/onboarding.css | 71 +++++++++++++++++-- .../wwwroot/js/word-companion-presence.js | 14 ++++ 5 files changed, 173 insertions(+), 33 deletions(-) diff --git a/PlotLine.Tests/Program.cs b/PlotLine.Tests/Program.cs index 9d67d4d..bc6d3ff 100644 --- a/PlotLine.Tests/Program.cs +++ b/PlotLine.Tests/Program.cs @@ -93,6 +93,8 @@ var tests = new (string Name, Action Test)[] ("Core import scene titles ride existing structural AI call", CoreImportSceneTitlesRideExistingStructuralAiCall), ("Core import scene title preference is review scoped", CoreImportSceneTitlePreferenceIsReviewScoped), ("Core import scene title preference moved to Detect Scenes", CoreImportSceneTitlePreferenceMovedToDetectScenes), + ("Core import Review Chapters is compact and chapter only", CoreImportReviewChaptersIsCompactAndChapterOnly), + ("Core import Detect Scenes is compact and owns title option", CoreImportDetectScenesIsCompactAndOwnsTitleOption), ("Book context is canonical import metadata", BookContextIsCanonicalImportMetadata), ("Names defaults from selected book context", NamesDefaultsFromSelectedBookContext), ("Story Intelligence starts from core import complete", StoryIntelligenceStartsFromCoreImportComplete), @@ -2579,6 +2581,49 @@ static void CoreImportSceneTitlePreferenceMovedToDetectScenes() Assert(hub.Contains("StartOnboardingProjectBuild(Guid previewId, bool generateSceneTitles = true)", StringComparison.Ordinal), "Build hub should accept the title preference without a separate AI call."); } +static void CoreImportReviewChaptersIsCompactAndChapterOnly() +{ + var root = Path.Combine(AppContext.BaseDirectory, "../../../../PlotLine"); + var review = File.ReadAllText(Path.Combine(root, "Views/Onboarding/ScanReview.cshtml")); + + Assert(!review.Contains("Generate descriptive scene titles", StringComparison.Ordinal), "Review Chapters should not render the scene-title option."); + Assert(review.Contains("chapters selected", StringComparison.Ordinal), "Review Chapters should render the selected chapter summary."); + Assert(review.Contains("onboarding-review-chapter-row", StringComparison.Ordinal), "Review Chapters should render compact chapter rows."); + Assert(!review.Contains("
Restore scan defaults", StringComparison.Ordinal), "Review Chapters should retain Restore scan defaults."); + Assert(review.Contains("Continue to Scene Detection", StringComparison.Ordinal), "Review Chapters should retain Continue to Scene Detection."); + Assert(review.Contains("name=\"Chapters[@chapterIndex].Include\"", StringComparison.Ordinal), "Compact rows should still post chapter inclusion choices."); + Assert(review.Contains("name=\"Chapters[@chapterIndex].Title\"", StringComparison.Ordinal), "Compact rows should still post reviewed chapter titles."); + var checkboxIndex = review.IndexOf("type=\"checkbox\" name=\"Chapters[@chapterIndex].Include\"", StringComparison.Ordinal); + var hiddenIndex = review.IndexOf("type=\"hidden\" name=\"Chapters[@chapterIndex].Include\"", StringComparison.Ordinal); + Assert(checkboxIndex >= 0 && hiddenIndex > checkboxIndex, "Chapter include checkbox should post before the hidden false value."); +} + +static void CoreImportDetectScenesIsCompactAndOwnsTitleOption() +{ + var root = Path.Combine(AppContext.BaseDirectory, "../../../../PlotLine"); + var index = File.ReadAllText(Path.Combine(root, "Views/Onboarding/Index.cshtml")); + var script = File.ReadAllText(Path.Combine(root, "wwwroot/js/word-companion-presence.js")); + var analyzer = File.ReadAllText(Path.Combine(root, "Services/ManuscriptStructureAnalysisService.cs")); + var detectScenesBlock = Between(index, "@if (structureReadyToImport)", "else\n {"); + + Assert(CountOccurrences(index, "Generate descriptive scene titles") == 1, "Detect Scenes should render the scene-title option exactly once."); + Assert(detectScenesBlock.Contains("data-onboarding-generate-scene-titles checked", StringComparison.Ordinal), "Detect Scenes should keep the existing ON default."); + Assert(detectScenesBlock.Contains("data-word-companion-compact-status-label", StringComparison.Ordinal), "Detect Scenes should render compact Word connection status."); + Assert(detectScenesBlock.Contains("data-word-companion-document", StringComparison.Ordinal), "Detect Scenes should include the current Word document name."); + Assert(detectScenesBlock.Contains("Keep Word open while PlotDirector detects scenes and writes the scene markers.", StringComparison.Ordinal), "Detect Scenes should give the required concise Word-open instruction."); + Assert(detectScenesBlock.Contains("data-onboarding-build-panel hidden", StringComparison.Ordinal), "Detect Scenes should hide processing progress until detection starts."); + Assert(!detectScenesBlock.Contains("onboarding-companion-card", StringComparison.Ordinal), "Detect Scenes should not render the large Word Companion card."); + Assert(!detectScenesBlock.Contains("Word Companion Connected", StringComparison.Ordinal), "Detect Scenes should not render the old dashboard heading."); + Assert(!detectScenesBlock.Contains("data-onboarding-scan-counts", StringComparison.Ordinal), "Detect Scenes should not render old scan counters."); + Assert(!detectScenesBlock.Contains("Ready to detect scenes.", StringComparison.Ordinal), "Detect Scenes should not render the old ready panel."); + Assert(script.Contains("StartOnboardingProjectBuild\", previewId, generateSceneTitles", StringComparison.Ordinal), "Detect Scenes should start the existing canonical scene-detection operation."); + Assert(script.Contains("node.hidden = false;", StringComparison.Ordinal), "Processing state should reveal the build progress panel only when work starts."); + Assert(!analyzer.Contains("SceneTitle-Prompt", StringComparison.Ordinal), "Scene title generation should remain inside the existing chapter-structure call."); +} + static void BookContextIsCanonicalImportMetadata() { var root = Path.Combine(AppContext.BaseDirectory, "../../../../PlotLine"); @@ -3503,6 +3548,32 @@ static void WordCompanionReplaysLiveCurrentSceneToWritingPanes() static int CountCsvRows(string path) => File.ReadLines(path).Skip(1).Count(line => !string.IsNullOrWhiteSpace(line)); +static int CountOccurrences(string value, string search) +{ + var count = 0; + var index = 0; + while ((index = value.IndexOf(search, index, StringComparison.Ordinal)) >= 0) + { + count++; + index += search.Length; + } + + return count; +} + +static string Between(string value, string start, string end) +{ + var startIndex = value.IndexOf(start, StringComparison.Ordinal); + if (startIndex < 0) + { + return string.Empty; + } + + startIndex += start.Length; + var endIndex = value.IndexOf(end, startIndex, StringComparison.Ordinal); + return endIndex < 0 ? value[startIndex..] : value[startIndex..endIndex]; +} + static JsonSerializerOptions JsonOptions() => new() { diff --git a/PlotLine/Views/Onboarding/Index.cshtml b/PlotLine/Views/Onboarding/Index.cshtml index a26000a..9cf4c88 100644 --- a/PlotLine/Views/Onboarding/Index.cshtml +++ b/PlotLine/Views/Onboarding/Index.cshtml @@ -265,7 +265,7 @@ @if (Model.IsMicrosoftWordPath) {

@(structureReadyToImport ? "Detect Scenes" : currentJourneyOrder >= 6 ? "Scan Manuscript" : "Connect your Word Companion")

-

@(structureReadyToImport ? "PlotDirector will now analyse the approved chapters to determine where each scene begins and ends. Keep Word open while scene markers are added and the document is saved." : "Open your manuscript in Word and start the Companion. When it is connected, use the wizard button below to scan and continue.")

+

@(structureReadyToImport ? "PlotDirector will analyse the selected chapters to determine where each scene begins and ends." : "Open your manuscript in Word and start the Companion. When it is connected, use the wizard button below to scan and continue.")

} else { @@ -282,22 +282,22 @@ data-word-companion-status="@Model.CompanionPresence.Status" aria-label="Scene detection">

- @(Model.CompanionPresence.IsConnected ? "Word connected" : "Waiting for Word") - · @Model.ScanState.ChapterCount.ToString("N0") chapter@(Model.ScanState.ChapterCount == 1 ? string.Empty : "s") - · @Model.ScanState.TotalWordCount.ToString("N0") words + + @(Model.CompanionPresence.IsConnected ? "Word Companion connected" : "Word Companion disconnected") + · @(Model.CompanionPresence.CurrentDocumentName ?? "No document reported yet")

-

Keep Word open until scene markers have been added. If Word cannot save automatically, PlotDirector will ask you to save and confirm.

-
+

Keep Word open while PlotDirector detects scenes and writes the scene markers.

+