Fix onboarding import wizard sequencing
This commit is contained in:
parent
c8148245c9
commit
24f2120cb2
@ -1589,7 +1589,12 @@ static void CoreImportWizardOrdersScenesBeforeCharacters()
|
||||
|
||||
Assert(review.Contains("value=\"structure\"", StringComparison.Ordinal), "Chapter review should submit to structural import, not character review.");
|
||||
Assert(controller.Contains("var readyToImport = string.Equals(intent, \"structure\"", StringComparison.Ordinal), "Controller should treat structure intent as the scene-creation gate.");
|
||||
Assert(controller.Contains("Import the approved chapters and scenes before reviewing characters.", StringComparison.Ordinal), "Character review should be guarded until structural import exists.");
|
||||
Assert(controller.Contains("continueToCharacters || continueCharacterReview || backCharacterReview || completeCoreImport", StringComparison.Ordinal), "Structure-save errors should remain on chapter review rather than jumping to characters.");
|
||||
Assert(controller.Contains("FinaliseCharacterReviewAsync", StringComparison.Ordinal), "Character review should have a distinct post-structure finalisation path.");
|
||||
Assert(onboarding.Contains("ValidateScanStructure(review, preview);\n review = new ManuscriptScanReviewDecision", StringComparison.Ordinal), "Saving chapter review should validate only structure before scene import.");
|
||||
Assert(onboarding.Contains("ValidateScanStructure(review, preview);\n\n var chapterDecisions", StringComparison.Ordinal), "Building approved structure should not validate unresolved character decisions.");
|
||||
Assert(onboarding.Contains("ValidateScanReview(new ManuscriptScanReviewDecision", StringComparison.Ordinal), "Character validation should run when finalising character review.");
|
||||
Assert(onboarding.Contains("Import the approved chapters and scenes before reviewing characters.", StringComparison.Ordinal), "Character finalisation should refuse to run before structural scenes exist.");
|
||||
Assert(complete.Contains("Review characters", StringComparison.Ordinal), "After Word sync, the next onboarding step should be character review.");
|
||||
Assert(complete.Contains("Word may now be closed; keep this browser workflow open", StringComparison.Ordinal), "Word safe-close must be distinct from browser/core-import completion.");
|
||||
@ -1889,10 +1894,11 @@ static void OnboardingWizardKeepsCharacterPagesInsideOneMainStep()
|
||||
var storyService = File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "../../../../PlotLine/Services/OnboardingStoryIntelligenceService.cs"));
|
||||
|
||||
Assert(!header.Contains("BuildDynamicSteps", StringComparison.Ordinal), "Onboarding header should not create one top-level step per character page.");
|
||||
Assert(header.Contains("new(\"Review Characters\", 8)", StringComparison.Ordinal), "Onboarding header should keep character review as one main step.");
|
||||
Assert(review.Contains("var currentStep = isCharacterStep ? 8 : 7;", StringComparison.Ordinal), "Scan review should keep every character page on the same main wizard step.");
|
||||
Assert(header.Contains("new(\"Import Scenes\", 8)", StringComparison.Ordinal), "Onboarding header should include scene import as a distinct main step.");
|
||||
Assert(header.Contains("new(\"Review Characters\", 9)", StringComparison.Ordinal), "Onboarding header should keep character review as one main step.");
|
||||
Assert(review.Contains("var currentStep = isCharacterStep ? 9 : 7;", StringComparison.Ordinal), "Scan review should keep every character page on the same main wizard step.");
|
||||
Assert(review.Contains("onboarding-subprogress", StringComparison.Ordinal), "Character pagination should be shown as section-level progress.");
|
||||
Assert(storyService.Contains("const int totalWizardSteps = 11;", StringComparison.Ordinal), "Story Intelligence summary/context should use the fixed wizard step count.");
|
||||
Assert(storyService.Contains("const int totalWizardSteps = 12;", StringComparison.Ordinal), "Story Intelligence summary/context should use the fixed wizard step count.");
|
||||
}
|
||||
|
||||
static void StoryIntelligenceExperienceBootDoesNotSerialiseLiveModel()
|
||||
|
||||
@ -60,6 +60,14 @@ public sealed class OnboardingController(IOnboardingService onboarding, IOnboard
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
|
||||
var build = await onboarding.GetBuildResultAsync(model.PreviewID);
|
||||
if (build is null || build.SceneMappings.Count == 0)
|
||||
{
|
||||
TempData["ArchiveError"] = "Import the approved chapters and scenes before reviewing characters.";
|
||||
await onboarding.SetCurrentStepAsync(OnboardingSteps.NextPathPreview);
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
|
||||
ViewData["ScanReviewStep"] = "characters";
|
||||
ViewData["CharacterReviewPage"] = Math.Max(1, page);
|
||||
return View("ScanReview", model);
|
||||
@ -622,6 +630,17 @@ public sealed class OnboardingController(IOnboardingService onboarding, IOnboard
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (continueToCharacters)
|
||||
{
|
||||
var build = await onboarding.GetBuildResultAsync(form.PreviewID);
|
||||
if (build is null || build.SceneMappings.Count == 0)
|
||||
{
|
||||
TempData["ArchiveError"] = "Import the approved chapters and scenes before reviewing characters.";
|
||||
await onboarding.SetCurrentStepAsync(OnboardingSteps.NextPathPreview);
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
}
|
||||
|
||||
TempData["OnboardingReviewMessage"] = readyToImport
|
||||
? "Chapter review saved. Next: import scenes and update Word."
|
||||
: continueToCharacters ? "Chapter and scene review saved. Next: review detected characters."
|
||||
@ -642,7 +661,7 @@ public sealed class OnboardingController(IOnboardingService onboarding, IOnboard
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (readyToImport || continueCharacterReview || backCharacterReview)
|
||||
if (continueToCharacters || continueCharacterReview || backCharacterReview || completeCoreImport)
|
||||
{
|
||||
ViewData["ScanReviewStep"] = "characters";
|
||||
ViewData["CharacterReviewPage"] = readyToImport
|
||||
|
||||
@ -253,7 +253,7 @@ public sealed class WordCompanionFollowHub(
|
||||
await Clients.Group(PresenceGroup(userId)).SendAsync("OnboardingBuildProgress", new OnboardingBuildProgress
|
||||
{
|
||||
Status = "Complete",
|
||||
Message = "Manuscript structure has been created and saved. Word and this browser page may now be closed.",
|
||||
Message = "Manuscript structure has been created and saved. Word may now be closed; keep this browser workflow open to review characters.",
|
||||
PercentComplete = 100
|
||||
});
|
||||
await Clients.Group(PresenceGroup(userId)).SendAsync("OnboardingBuildMarkerSyncCompleted", new { buildId });
|
||||
|
||||
@ -202,7 +202,7 @@ public sealed class OnboardingService(
|
||||
if (readyToImport)
|
||||
{
|
||||
await scanPreviews.SaveReviewAsync(userId, review);
|
||||
ValidateScanReview(review, preview);
|
||||
ValidateScanStructure(review, preview);
|
||||
review = new ManuscriptScanReviewDecision
|
||||
{
|
||||
PreviewID = review.PreviewID,
|
||||
@ -236,7 +236,7 @@ public sealed class OnboardingService(
|
||||
}
|
||||
|
||||
await PublishBuildProgress(userId, preview.PreviewID, "Creating chapters...", 10, progress);
|
||||
ValidateScanReview(review, preview);
|
||||
ValidateScanStructure(review, preview);
|
||||
|
||||
var chapterDecisions = review.Chapters.Where(chapter => chapter.Include).OrderBy(chapter => chapter.ChapterNumber).ToList();
|
||||
|
||||
@ -382,6 +382,14 @@ public sealed class OnboardingService(
|
||||
};
|
||||
})
|
||||
.ToList();
|
||||
ValidateScanReview(new ManuscriptScanReviewDecision
|
||||
{
|
||||
PreviewID = review.PreviewID,
|
||||
Status = review.Status,
|
||||
Chapters = review.Chapters,
|
||||
Scenes = review.Scenes,
|
||||
Characters = characterDecisions
|
||||
}, preview);
|
||||
|
||||
var characterMappings = new List<OnboardingBuildCharacterMap>();
|
||||
foreach (var decision in characterDecisions.Where(IsIncludedCanonicalCharacter).OrderBy(item => item.Name).ThenBy(item => item.TemporaryCharacterKey))
|
||||
|
||||
@ -71,9 +71,9 @@ public sealed class OnboardingStoryIntelligenceService(
|
||||
var included = IncludedChapters(preview, review);
|
||||
var existingBatch = await batchStore.GetLatestForPreviewAsync(userId, preview.PreviewID);
|
||||
var missingTextCount = included.Count(chapter => string.IsNullOrWhiteSpace(chapter.Preview.ChapterText));
|
||||
const int storySummaryStep = 9;
|
||||
const int storyContextStep = 10;
|
||||
const int totalWizardSteps = 11;
|
||||
const int storySummaryStep = 10;
|
||||
const int storyContextStep = 11;
|
||||
const int totalWizardSteps = 12;
|
||||
return new StoryIntelligenceOverviewViewModel
|
||||
{
|
||||
PreviewID = preview.PreviewID,
|
||||
|
||||
@ -127,9 +127,9 @@ public sealed class StoryIntelligenceOverviewViewModel
|
||||
public int ApprovedChapterCount { get; init; }
|
||||
public int ApprovedWordCount { get; init; }
|
||||
public int MissingChapterTextCount { get; init; }
|
||||
public int WizardCurrentStep { get; init; } = 9;
|
||||
public int WizardContextStep { get; init; } = 10;
|
||||
public int WizardTotalSteps { get; init; } = 10;
|
||||
public int WizardCurrentStep { get; init; } = 10;
|
||||
public int WizardContextStep { get; init; } = 11;
|
||||
public int WizardTotalSteps { get; init; } = 12;
|
||||
public string? Message { get; init; }
|
||||
public Guid? ExistingBatchID { get; init; }
|
||||
public StoryIntelligenceJobProgress? ExistingJob { get; init; }
|
||||
|
||||
@ -6,15 +6,7 @@
|
||||
|
||||
<section class="onboarding-shell" aria-labelledby="build-complete-title">
|
||||
<div class="onboarding-panel onboarding-review-panel">
|
||||
<ol class="onboarding-stepper onboarding-stepper--complete" aria-label="Story setup journey">
|
||||
@foreach (var label in new[] { "Welcome", "Writing Preferences", "Project", "Book", "Connect Word", "Scan", "Review", "Create Structure", "Complete" })
|
||||
{
|
||||
<li class="is-complete">
|
||||
<span></span>
|
||||
<strong>@label</strong>
|
||||
</li>
|
||||
}
|
||||
</ol>
|
||||
<partial name="_OnboardingJourneyHeader" model="@(new OnboardingJourneyHeaderViewModel { CurrentStep = coreComplete ? 12 : 9, TotalSteps = 12 })" />
|
||||
<div class="onboarding-copy">
|
||||
<p class="eyebrow">Core Import Complete</p>
|
||||
<div class="onboarding-success-heading">
|
||||
|
||||
@ -3,11 +3,21 @@
|
||||
@{
|
||||
ViewData["Title"] = "Set up PlotDirector";
|
||||
var currentJourneyOrder = CurrentJourneyOrder(Model);
|
||||
var structureReadyToImport = string.Equals(Model.ScanState.ReviewStatus, ManuscriptScanReviewStatuses.ReadyToImport, StringComparison.Ordinal);
|
||||
var reviewedScanAvailable = Model.ScanState.IsComplete || structureReadyToImport;
|
||||
}
|
||||
|
||||
<section class="onboarding-shell" aria-labelledby="onboarding-title">
|
||||
<div class="onboarding-panel">
|
||||
<partial name="_OnboardingJourneyHeader" model="@(new OnboardingJourneyHeaderViewModel { CurrentStep = currentJourneyOrder })" />
|
||||
@if (TempData["OnboardingReviewMessage"] is string reviewMessage)
|
||||
{
|
||||
<div class="alert alert-success">@reviewMessage</div>
|
||||
}
|
||||
@if (TempData["ArchiveError"] is string archiveError)
|
||||
{
|
||||
<div class="alert alert-danger">@archiveError</div>
|
||||
}
|
||||
|
||||
@if (Model.CurrentStep == OnboardingSteps.Welcome)
|
||||
{
|
||||
@ -229,11 +239,11 @@
|
||||
else
|
||||
{
|
||||
<div class="onboarding-copy">
|
||||
<p class="eyebrow">Step @currentJourneyOrder of 10</p>
|
||||
<p class="eyebrow">Step @currentJourneyOrder of 12</p>
|
||||
@if (Model.IsMicrosoftWordPath)
|
||||
{
|
||||
<h1 id="onboarding-title">@(currentJourneyOrder >= 6 ? "Scan Manuscript" : "Connect your Word Companion")</h1>
|
||||
<p>Open your manuscript in Word and start the Companion. When it is connected, use the wizard button below to scan and continue.</p>
|
||||
<h1 id="onboarding-title">@(structureReadyToImport ? "Import scenes and Word markers" : currentJourneyOrder >= 6 ? "Scan Manuscript" : "Connect your Word Companion")</h1>
|
||||
<p>@(structureReadyToImport ? "Keep Word and this browser page open while PlotDirector detects scene boundaries, creates the manuscript structure, inserts bookmark IDs, and saves the document." : "Open your manuscript in Word and start the Companion. When it is connected, use the wizard button below to scan and continue.")</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -314,10 +324,17 @@
|
||||
</section>
|
||||
}
|
||||
<div class="onboarding-actions">
|
||||
<form asp-controller="Onboarding" asp-action="Back" method="post">
|
||||
<button class="btn btn-outline-secondary" type="submit" name="currentStep" value="@OnboardingSteps.NextPathPreview">Back</button>
|
||||
</form>
|
||||
@if (Model.IsMicrosoftWordPath && string.Equals(Model.ScanState.ReviewStatus, ManuscriptScanReviewStatuses.ReadyToImport, StringComparison.Ordinal))
|
||||
@if (Model.IsMicrosoftWordPath && reviewedScanAvailable && Model.ScanState.PreviewID.HasValue)
|
||||
{
|
||||
<a class="btn btn-outline-secondary" asp-controller="Onboarding" asp-action="ScanReview" asp-route-previewId="@Model.ScanState.PreviewID">Back to chapter review</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<form asp-controller="Onboarding" asp-action="Back" method="post">
|
||||
<button class="btn btn-outline-secondary" type="submit" name="currentStep" value="@OnboardingSteps.NextPathPreview">Back</button>
|
||||
</form>
|
||||
}
|
||||
@if (Model.IsMicrosoftWordPath && structureReadyToImport)
|
||||
{
|
||||
<button class="btn btn-primary"
|
||||
type="button"
|
||||
@ -444,13 +461,13 @@
|
||||
OnboardingSteps.WritingSoftware => 3,
|
||||
OnboardingSteps.Project => 4,
|
||||
OnboardingSteps.Book => 5,
|
||||
OnboardingSteps.NextPathPreview when !model.IsMicrosoftWordPath => 10,
|
||||
OnboardingSteps.NextPathPreview when string.Equals(model.ScanState.ReviewStatus, ManuscriptScanReviewStatuses.ReadyToImport, StringComparison.Ordinal) => 9,
|
||||
OnboardingSteps.NextPathPreview when !model.IsMicrosoftWordPath => 11,
|
||||
OnboardingSteps.NextPathPreview when string.Equals(model.ScanState.ReviewStatus, ManuscriptScanReviewStatuses.ReadyToImport, StringComparison.Ordinal) => 8,
|
||||
OnboardingSteps.NextPathPreview when model.ScanState.IsComplete => 7,
|
||||
OnboardingSteps.NextPathPreview when model.ScanState.IsRunning || model.ScanState.IsFailed || model.ScanState.PercentComplete.HasValue => 6,
|
||||
OnboardingSteps.NextPathPreview when model.CompanionPresence.IsConnected => 6,
|
||||
OnboardingSteps.NextPathPreview => 6,
|
||||
OnboardingSteps.Complete => 10,
|
||||
OnboardingSteps.Complete => 12,
|
||||
_ => 1
|
||||
};
|
||||
}
|
||||
|
||||
@ -31,8 +31,8 @@
|
||||
var requestedCharacterPage = ViewData["CharacterReviewPage"] is int requestedPage ? requestedPage : 1;
|
||||
var characterPage = Math.Clamp(requestedCharacterPage, 1, characterReviewPageCount);
|
||||
var currentCharacters = primaryCharacters.Skip((characterPage - 1) * characterPageSize).Take(characterPageSize).ToList();
|
||||
var currentStep = isCharacterStep ? 8 : 7;
|
||||
const int totalSteps = 11;
|
||||
var currentStep = isCharacterStep ? 9 : 7;
|
||||
const int totalSteps = 12;
|
||||
var characterPageTitle = $"Review Characters {characterPage} of {primaryCharacterPageCount}";
|
||||
var duplicateCanonicalNames = primaryCharacters
|
||||
.Where(candidate => candidate.Include && string.IsNullOrWhiteSpace(candidate.AliasTargetTemporaryCharacterKey))
|
||||
@ -67,7 +67,7 @@
|
||||
<div class="onboarding-copy">
|
||||
<p class="eyebrow">Step @currentStep of @totalSteps</p>
|
||||
<h1 id="scan-review-title">@(isCharacterStep ? "Review Characters" : "Review Chapters")</h1>
|
||||
<p>@(isCharacterStep ? "Review this page of detected names and aliases, then continue through the character section." : "Confirm the chapter and scene structure before reviewing detected characters.")</p>
|
||||
<p>@(isCharacterStep ? "Review this page of detected names and aliases, then continue through the character section." : "Confirm the chapter structure before PlotDirector detects scenes with AI and updates Word markers.")</p>
|
||||
</div>
|
||||
|
||||
@if (TempData["OnboardingReviewMessage"] is string reviewMessage)
|
||||
@ -192,7 +192,7 @@
|
||||
</div>
|
||||
}
|
||||
<div class="onboarding-actions onboarding-actions--inline">
|
||||
<button class="btn btn-primary" type="submit" name="intent" value="structure">Save chapters and import scenes</button>
|
||||
<button class="btn btn-primary" type="submit" name="intent" value="structure">Save chapter review</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -364,7 +364,7 @@
|
||||
else
|
||||
{
|
||||
<a class="btn btn-outline-secondary" asp-controller="Onboarding" asp-action="Index">Back to setup</a>
|
||||
<button class="btn btn-primary" type="submit" name="intent" value="structure">Save chapters and import scenes</button>
|
||||
<button class="btn btn-primary" type="submit" name="intent" value="structure">Save chapter review</button>
|
||||
}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@ -36,10 +36,11 @@
|
||||
new("Book", 5),
|
||||
new("Connect Word", 6),
|
||||
new("Review Chapters", 7),
|
||||
new("Review Characters", 8),
|
||||
new("Story Summary", 9),
|
||||
new("Story Context", 10),
|
||||
new("Complete", 11)
|
||||
new("Import Scenes", 8),
|
||||
new("Review Characters", 9),
|
||||
new("Story Summary", 10),
|
||||
new("Story Context", 11),
|
||||
new("Complete", 12)
|
||||
];
|
||||
|
||||
private static string StepClass(int order, int currentStep)
|
||||
|
||||
@ -220,6 +220,7 @@
|
||||
if (!previewId) {
|
||||
return;
|
||||
}
|
||||
pendingBuildPreviewId = previewId;
|
||||
button.disabled = true;
|
||||
document.querySelectorAll("[data-onboarding-build-panel]").forEach((node) => {
|
||||
node.classList.add("is-running");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user