diff --git a/PlotLine.Tests/Program.cs b/PlotLine.Tests/Program.cs index fc2adce..bc5f835 100644 --- a/PlotLine.Tests/Program.cs +++ b/PlotLine.Tests/Program.cs @@ -236,8 +236,10 @@ var tests = new (string Name, Action Test)[] ("Story Intelligence Review Centre uses independent tile grid", StoryIntelligenceReviewCentreUsesIndependentTileGrid), ("Story Intelligence Review Centre opens with character gate", StoryIntelligenceReviewCentreOpensWithCharacterGate), ("Story Intelligence review modules are independent after character gate", StoryIntelligenceReviewModulesAreIndependentAfterCharacterGate), + ("Story Intelligence review module pages omit wizard chrome", StoryIntelligenceReviewModulePagesOmitWizardChrome), ("Story Intelligence downstream review uses completed scene analysis", StoryIntelligenceDownstreamReviewUsesCompletedSceneAnalysis), ("Story Intelligence review posts do not use success interstitials", StoryIntelligenceReviewPostsDoNotUseSuccessInterstitials), + ("Story Intelligence legacy review completion routes redirect to centre", StoryIntelligenceLegacyReviewCompletionRoutesRedirectToCentre), ("Story Intelligence review forms share busy submit guard", StoryIntelligenceReviewFormsShareBusySubmitGuard), ("Story Intelligence heavy review modules offer bounded rendering", StoryIntelligenceHeavyReviewModulesOfferBoundedRendering), ("Story Intelligence Review Centre adds no AI calls", StoryIntelligenceReviewCentreAddsNoAiCalls), @@ -742,6 +744,49 @@ static void StoryIntelligenceReviewModulesAreIndependentAfterCharacterGate() Assert(locationsAction.Contains("RedirectToReviewCentreWithCharacterGate", StringComparison.Ordinal), "Direct dependent module URLs should return to Review Centre when the character gate is closed."); } +static void StoryIntelligenceReviewModulePagesOmitWizardChrome() +{ + var root = PlotLineRoot(); + var reviewViews = new[] + { + ("StoryIntelligenceCharacters.cshtml", "Review Characters"), + ("StoryIntelligenceLocations.cshtml", "Review Locations"), + ("StoryIntelligenceAssets.cshtml", "Review Assets"), + ("StoryIntelligenceRelationships.cshtml", "Review Relationships"), + ("StoryIntelligenceKnowledge.cshtml", "Review Knowledge"), + ("StoryIntelligenceNarrative.cshtml", "Review Plot Lines & Threads") + }; + var forbidden = new[] + { + "_StoryIntelligencePipelineHeader", + "_StoryIntelligencePipelineSummary", + "story-pipeline", + "story-future-stage", + "story-review-heading__phase", + "Phase 3", + "Review Story Intelligence", + "Next Stage", + "Previous Stage", + "Continue to Locations", + "Continue to Assets", + "Continue to Relationships", + "Continue to Knowledge" + }; + + foreach (var (viewName, title) in reviewViews) + { + var view = File.ReadAllText(Path.Combine(root, "Views/Onboarding", viewName)); + Assert(view.Contains("
Story Intelligence
", StringComparison.Ordinal), $"{viewName} should use a simple module header eyebrow."); + Assert(view.Contains("StoryIntelligenceReviewCentre", StringComparison.Ordinal), $"{viewName} should return to Review Centre."); + Assert(view.Contains($">{title}", StringComparison.Ordinal), $"{viewName} should keep its focused module title."); + Assert(!view.Contains("StoryIntelligenceComplete", StringComparison.Ordinal), $"{viewName} should not route to the old final wizard step."); + foreach (var marker in forbidden) + { + Assert(!view.Contains(marker, StringComparison.OrdinalIgnoreCase), $"{viewName} should not render obsolete wizard chrome: {marker}."); + } + } +} + static void StoryIntelligenceDownstreamReviewUsesCompletedSceneAnalysis() { var root = PlotLineRoot(); @@ -784,6 +829,29 @@ static void StoryIntelligenceReviewPostsDoNotUseSuccessInterstitials() Assert(!postBlock.Contains("StoryIntelligenceNarrativeComplete", StringComparison.Ordinal), "Narrative post should not target success interstitial."); } +static void StoryIntelligenceLegacyReviewCompletionRoutesRedirectToCentre() +{ + var root = PlotLineRoot(); + var controller = File.ReadAllText(Path.Combine(root, "Controllers/OnboardingController.cs")); + var completionActions = new[] + { + ("StoryIntelligenceCharacterComplete", "characters"), + ("StoryIntelligenceLocationComplete", "locations"), + ("StoryIntelligenceAssetComplete", "assets"), + ("StoryIntelligenceRelationshipComplete", "relationships"), + ("StoryIntelligenceKnowledgeComplete", "knowledge"), + ("StoryIntelligenceNarrativeComplete", "narrative") + }; + + foreach (var (action, route) in completionActions) + { + var block = ExtractBetween(controller, $"public IActionResult {action}", "}"); + Assert(controller.Contains($"[HttpGet(\"story-intelligence/{route}/complete\")]", StringComparison.Ordinal), $"{action} legacy route should still exist."); + Assert(block.Contains("RedirectToAction(nameof(StoryIntelligenceReviewCentre)", StringComparison.Ordinal), $"{action} should redirect to Review Centre."); + Assert(!block.Contains("View(", StringComparison.Ordinal), $"{action} should not render a success interstitial."); + } +} + static void StoryIntelligenceReviewFormsShareBusySubmitGuard() { var root = PlotLineRoot(); @@ -5105,7 +5173,6 @@ static void StoryIntelligenceNarrativeStageFollowsKnowledge() var pipeline = File.ReadAllText(Path.Combine(root, "Services/StoryIntelligencePipelineStateService.cs")); var onboarding = File.ReadAllText(Path.Combine(root, "Services/OnboardingStoryIntelligenceService.cs")); var controller = File.ReadAllText(Path.Combine(root, "Controllers/OnboardingController.cs")); - var knowledgeComplete = File.ReadAllText(Path.Combine(root, "Views/Onboarding/StoryIntelligenceKnowledgeComplete.cshtml")); var pipelineConstraint = File.ReadAllText(Path.Combine(root, "Sql/156_Phase25A_NarrativeReviewPipelineConstraint.sql")); var knowledgeImport = File.ReadAllText(Path.Combine(root, "Services/StoryIntelligenceKnowledgeImportService.cs")); @@ -5117,7 +5184,7 @@ static void StoryIntelligenceNarrativeStageFollowsKnowledge() Assert(knowledgeImport.Contains("hasUndecidedCandidates", StringComparison.Ordinal), "Knowledge review should be able to advance when prior decisions already imported every candidate."); Assert(onboarding.Contains("SceneAnalysisComplete(state)\n ? StoryIntelligenceResumeRoutes.ReviewCentre", StringComparison.Ordinal), "Resume logic should route completed analysis to Review Centre."); Assert(controller.Contains("StoryIntelligenceNarrative", StringComparison.Ordinal), "Onboarding controller should expose the narrative review stage."); - Assert(knowledgeComplete.Contains("Review Plot Lines & Threads", StringComparison.Ordinal), "Knowledge completion should lead to narrative review."); + Assert(controller.Contains("ImportStoryIntelligenceKnowledge", StringComparison.Ordinal) && controller.Contains("RedirectToAction(nameof(StoryIntelligenceReviewCentre)", StringComparison.Ordinal), "Knowledge completion should return to Review Centre instead of auto-advancing."); } static void WordCompanionReplaysLiveCurrentSceneToWritingPanes() diff --git a/PlotLine/Controllers/OnboardingController.cs b/PlotLine/Controllers/OnboardingController.cs index c0de400..694cbcd 100644 --- a/PlotLine/Controllers/OnboardingController.cs +++ b/PlotLine/Controllers/OnboardingController.cs @@ -408,10 +408,9 @@ public sealed class OnboardingController(IOnboardingService onboarding, IOnboard } [HttpGet("story-intelligence/characters/complete")] - public async TaskBuild Story Database
-PlotDirector updated your story database from the approved asset decisions. Next, review the relationships detected between imported characters.
-Phase 3
-Review Story Intelligence
+Story Intelligence
Review important object suggestions that need a create, link, alias, or ignore decision.
Assets are already up to date, or there were no story-significant asset suggestions ready to import.