Phase 20AH – Unify Word Companion / Story Intelligence Onboarding Flow

This commit is contained in:
Nick Beckley 2026-07-07 19:25:20 +01:00
parent 2097122898
commit 37e01f4b5d
10 changed files with 97 additions and 59 deletions

View File

@ -32,6 +32,13 @@ public sealed class OnboardingOptionViewModel
public string? Badge { get; init; } public string? Badge { get; init; }
} }
public sealed class OnboardingJourneyHeaderViewModel
{
public int CurrentStep { get; init; }
public int TotalSteps { get; init; } = 9;
public string AriaLabel { get; init; } = "Manuscript import progress";
}
public sealed class WritingJourneyOnboardingForm public sealed class WritingJourneyOnboardingForm
{ {
[Required(ErrorMessage = "Choose the closest writing stage before continuing.")] [Required(ErrorMessage = "Choose the closest writing stage before continuing.")]

View File

@ -3,37 +3,11 @@
@{ @{
ViewData["Title"] = "Set up PlotDirector"; ViewData["Title"] = "Set up PlotDirector";
var currentJourneyOrder = CurrentJourneyOrder(Model); var currentJourneyOrder = CurrentJourneyOrder(Model);
var journeySteps = new[]
{
new { Label = "Welcome", Order = 1 },
new { Label = "Writing Preferences", Order = 2 },
new { Label = "Project", Order = 3 },
new { Label = "Book", Order = 4 },
new { Label = "Connect Word", Order = 5 },
new { Label = "Scan", Order = 6 },
new { Label = "Review", Order = 7 },
new { Label = "Analyse", Order = 8 },
new { Label = "Complete", Order = 9 }
};
} }
<section class="onboarding-shell" aria-labelledby="onboarding-title"> <section class="onboarding-shell" aria-labelledby="onboarding-title">
<div class="onboarding-panel"> <div class="onboarding-panel">
<div class="onboarding-progress" aria-label="Setup progress"> <partial name="_OnboardingJourneyHeader" model="@(new OnboardingJourneyHeaderViewModel { CurrentStep = currentJourneyOrder })" />
<span>Step @Model.StepNumber of @Model.TotalSteps</span>
<div class="onboarding-progress-track">
<span style="width:@((Model.StepNumber * 100) / Model.TotalSteps)%"></span>
</div>
</div>
<ol class="onboarding-stepper" aria-label="Story setup journey">
@foreach (var step in journeySteps)
{
<li class="@JourneyStepClass(step.Order, currentJourneyOrder)">
<span>@step.Order</span>
<strong>@step.Label</strong>
</li>
}
</ol>
@if (Model.CurrentStep == OnboardingSteps.Welcome) @if (Model.CurrentStep == OnboardingSteps.Welcome)
{ {
@ -255,11 +229,11 @@
else else
{ {
<div class="onboarding-copy"> <div class="onboarding-copy">
<p class="eyebrow">Ready for the next phase</p> <p class="eyebrow">Step @currentJourneyOrder of 9</p>
@if (Model.IsMicrosoftWordPath) @if (Model.IsMicrosoftWordPath)
{ {
<h1 id="onboarding-title">Connect your Word Companion</h1> <h1 id="onboarding-title">@(currentJourneyOrder >= 6 ? "Scan Manuscript" : "Connect your Word Companion")</h1>
<p>Open Microsoft Word, open your manuscript, start the PlotDirector Word Companion, and sign in using your PlotDirector account. PlotDirector will stay on this page with you while the scan and build run.</p> <p>Open Microsoft Word, open your manuscript, start the PlotDirector Word Companion, and sign in using your PlotDirector account. PlotDirector will stay on this page with you while the scan runs.</p>
} }
else else
{ {
@ -327,13 +301,13 @@
type="button" type="button"
data-onboarding-scan-start data-onboarding-scan-start
disabled="@(!CanStartScan(Model.CompanionPresence, Model.ScanState))"> disabled="@(!CanStartScan(Model.CompanionPresence, Model.ScanState))">
@(Model.ScanState.IsComplete ? "Scan again" : "Scan manuscript") Scan manuscript
</button> </button>
<a class="btn btn-outline-primary @(Model.ScanState.IsComplete ? string.Empty : "disabled")" <a class="btn btn-outline-primary @(Model.ScanState.IsComplete ? string.Empty : "disabled")"
data-onboarding-scan-review data-onboarding-scan-review
href="@(Model.ScanState.PreviewID.HasValue ? Url.Action("ScanReview", "Onboarding", new { previewId = Model.ScanState.PreviewID }) : "#")" href="@(Model.ScanState.PreviewID.HasValue ? Url.Action("ScanReview", "Onboarding", new { previewId = Model.ScanState.PreviewID }) : "#")"
aria-disabled="@(!Model.ScanState.IsComplete)"> aria-disabled="@(!Model.ScanState.IsComplete)">
@(string.Equals(Model.ScanState.ReviewStatus, ManuscriptScanReviewStatuses.ReadyToImport, StringComparison.Ordinal) ? "Edit review" : "Review scan") @(string.Equals(Model.ScanState.ReviewStatus, ManuscriptScanReviewStatuses.ReadyToImport, StringComparison.Ordinal) ? "Edit review" : "Review chapters")
</a> </a>
</div> </div>
@if (string.Equals(Model.ScanState.ReviewStatus, ManuscriptScanReviewStatuses.ReadyToImport, StringComparison.Ordinal)) @if (string.Equals(Model.ScanState.ReviewStatus, ManuscriptScanReviewStatuses.ReadyToImport, StringComparison.Ordinal))

View File

@ -19,11 +19,12 @@
<form class="onboarding-panel onboarding-review-panel" asp-controller="Onboarding" asp-action="SaveScanReview" method="post"> <form class="onboarding-panel onboarding-review-panel" asp-controller="Onboarding" asp-action="SaveScanReview" method="post">
@Html.AntiForgeryToken() @Html.AntiForgeryToken()
<input type="hidden" asp-for="PreviewID" name="PreviewID" /> <input type="hidden" asp-for="PreviewID" name="PreviewID" />
<partial name="_OnboardingJourneyHeader" model="@(new OnboardingJourneyHeaderViewModel { CurrentStep = 7 })" />
<div class="onboarding-copy"> <div class="onboarding-copy">
<p class="eyebrow">@Model.SelectedBookTitle</p> <p class="eyebrow">Step 7 of 9</p>
<h1 id="scan-review-title">Review manuscript scan</h1> <h1 id="scan-review-title">Review Chapters</h1>
<p>Nothing has been added to your story map yet. Choose the chapters Story Intelligence should analyse for scene creation.</p> <p>Review the chapters PlotDirector found before Story Intelligence begins.</p>
<p>Scene and character scan details are shown for context only in this phase.</p> <p>Scene and character scan details are shown for context only in this phase.</p>
</div> </div>
@ -260,7 +261,6 @@
<div class="onboarding-actions"> <div class="onboarding-actions">
<a class="btn btn-outline-secondary" asp-controller="Onboarding" asp-action="Index">Back to setup</a> <a class="btn btn-outline-secondary" asp-controller="Onboarding" asp-action="Index">Back to setup</a>
<button class="btn btn-outline-primary" type="submit" name="intent" value="save">Save review</button>
<button class="btn btn-primary" type="submit" name="intent" value="continue">Save and continue</button> <button class="btn btn-primary" type="submit" name="intent" value="continue">Save and continue</button>
</div> </div>
</form> </form>

View File

@ -5,10 +5,11 @@
<section class="onboarding-shell" aria-labelledby="story-intelligence-title"> <section class="onboarding-shell" aria-labelledby="story-intelligence-title">
<div class="onboarding-panel"> <div class="onboarding-panel">
<partial name="_OnboardingJourneyHeader" model="@(new OnboardingJourneyHeaderViewModel { CurrentStep = 8 })" />
<div class="onboarding-copy"> <div class="onboarding-copy">
<p class="eyebrow">@Model.SelectedBookTitle</p> <p class="eyebrow">Step 8 of 9</p>
<h1 id="story-intelligence-title">Analyse chapters with Story Intelligence</h1> <h1 id="story-intelligence-title">Analyse Manuscript</h1>
<p>PlotDirector can now analyse your manuscript to identify scenes and begin understanding your story.</p> <p>PlotDirector will analyse approved chapters in the background and prepare scene suggestions for review.</p>
</div> </div>
@if (TempData["OnboardingStoryIntelligenceError"] is string error) @if (TempData["OnboardingStoryIntelligenceError"] is string error)
@ -59,8 +60,8 @@
</div> </div>
<section class="onboarding-form-section"> <section class="onboarding-form-section">
<p>It will extract scene summaries, scene purpose, characters, locations, assets, relationships, timeline clues and story observations.</p> <p>Nothing will be created yet. PlotDirector will prepare scene suggestions for review.</p>
<p>For this step, only scenes are created in PlotDirector. The other discoveries are prepared for later review stages.</p> <p>For this step, only scenes can be created in PlotDirector. Characters, locations, assets and relationships stay as later suggestions.</p>
</section> </section>
@if (!string.IsNullOrWhiteSpace(Model.Message)) @if (!string.IsNullOrWhiteSpace(Model.Message))
@ -75,9 +76,7 @@
} }
<div class="onboarding-actions"> <div class="onboarding-actions">
<form asp-action="ScanManuscript" method="post"> <a class="btn btn-outline-secondary" asp-action="ScanReview" asp-route-previewId="@Model.PreviewID">Back to chapter review</a>
<button class="btn btn-outline-secondary" type="submit">Scan manuscript again</button>
</form>
<form asp-action="SkipStoryIntelligence" method="post"> <form asp-action="SkipStoryIntelligence" method="post">
<button class="btn btn-outline-secondary" type="submit">Skip for now</button> <button class="btn btn-outline-secondary" type="submit">Skip for now</button>
</form> </form>

View File

@ -5,27 +5,28 @@
<section class="onboarding-shell" aria-labelledby="story-complete-title"> <section class="onboarding-shell" aria-labelledby="story-complete-title">
<div class="onboarding-panel onboarding-review-panel"> <div class="onboarding-panel onboarding-review-panel">
<partial name="_OnboardingJourneyHeader" model="@(new OnboardingJourneyHeaderViewModel { CurrentStep = 9 })" />
<div class="onboarding-copy"> <div class="onboarding-copy">
<p class="eyebrow">@Model.BookTitle</p> <p class="eyebrow">Step 9 of 9</p>
<div class="onboarding-success-heading"> <div class="onboarding-success-heading">
<span aria-hidden="true">&check;</span> <span aria-hidden="true">&check;</span>
<h1 id="story-complete-title">Your scenes have been created.</h1> <h1 id="story-complete-title">Manuscript import complete</h1>
</div> </div>
<p>PlotDirector created scenes from the approved manuscript chapters. Characters, locations, assets and relationships were left untouched.</p> <p>PlotDirector created scenes from the approved manuscript chapters. Characters, locations, assets and relationships were left untouched.</p>
</div> </div>
<div class="onboarding-scan-counts onboarding-review-counts"> <div class="onboarding-scan-counts onboarding-review-counts">
<div> <div>
<span>Chapters</span> <span>Chapters analysed</span>
<strong>@Model.ChaptersCommitted</strong> <strong>@Model.ChaptersCommitted</strong>
</div> </div>
<div> <div>
<span>Scenes created</span> <span>Scenes prepared / created</span>
<strong>@Model.ScenesCreated</strong> <strong>@Model.ScenesCreated</strong>
</div> </div>
<div> <div>
<span>Project</span> <span>Notes needing review</span>
<strong>@Model.ProjectName</strong> <strong>None imported</strong>
</div> </div>
<div> <div>
<span>Book</span> <span>Book</span>
@ -34,8 +35,8 @@
</div> </div>
<div class="onboarding-actions"> <div class="onboarding-actions">
<a class="btn btn-primary" asp-controller="Writer" asp-action="Index" asp-route-projectId="@Model.ProjectID">Open Writer Workspace</a> <a class="btn btn-primary" asp-controller="Writer" asp-action="Index" asp-route-projectId="@Model.ProjectID">View scenes</a>
<a class="btn btn-outline-primary" asp-controller="Projects" asp-action="Details" asp-route-id="@Model.ProjectID">Open Project Overview</a> <a class="btn btn-outline-primary" asp-controller="Projects" asp-action="Details" asp-route-id="@Model.ProjectID">View book</a>
<a class="btn btn-outline-secondary" asp-controller="Projects" asp-action="Index">Back to Dashboard</a> <a class="btn btn-outline-secondary" asp-controller="Projects" asp-action="Index">Back to Dashboard</a>
</div> </div>
</div> </div>

View File

@ -13,9 +13,10 @@
data-story-intelligence-run-ids="@runIds" data-story-intelligence-run-ids="@runIds"
data-story-max-progress-percent="@progressPercent" data-story-max-progress-percent="@progressPercent"
data-story-intelligence-has-active-runs="@Model.HasActiveRuns.ToString().ToLowerInvariant()"> data-story-intelligence-has-active-runs="@Model.HasActiveRuns.ToString().ToLowerInvariant()">
<partial name="_OnboardingJourneyHeader" model="@(new OnboardingJourneyHeaderViewModel { CurrentStep = 8 })" />
<div class="onboarding-copy story-live-hero"> <div class="onboarding-copy story-live-hero">
<p class="eyebrow">@Model.BookTitle</p> <p class="eyebrow">Step 8 of 9</p>
<h1 id="story-progress-title">Reading your manuscript...</h1> <h1 id="story-progress-title">Reading your manuscript</h1>
<p>PlotDirector is analysing your chapters and preparing scenes for review. You can safely leave this page and return later.</p> <p>PlotDirector is analysing your chapters and preparing scenes for review. You can safely leave this page and return later.</p>
</div> </div>
@ -29,7 +30,7 @@
} }
<section class="alert alert-info"> <section class="alert alert-info">
Nothing has been added to your project yet. PlotDirector is preparing suggestions. You will review them before anything is created. You can close Word or this browser tab once analysis has started. PlotDirector is working from a saved manuscript snapshot.
</section> </section>
<section class="story-live-progress-card" aria-label="Import progress"> <section class="story-live-progress-card" aria-label="Import progress">

View File

@ -13,8 +13,9 @@
<section class="onboarding-shell" aria-labelledby="story-review-title"> <section class="onboarding-shell" aria-labelledby="story-review-title">
<div class="onboarding-panel onboarding-review-panel story-review-page"> <div class="onboarding-panel onboarding-review-panel story-review-page">
<partial name="_OnboardingJourneyHeader" model="@(new OnboardingJourneyHeaderViewModel { CurrentStep = 8 })" />
<div class="onboarding-copy"> <div class="onboarding-copy">
<p class="eyebrow">@Model.BookTitle</p> <p class="eyebrow">Step 8 of 9</p>
<h1 id="story-review-title">Review analysis</h1> <h1 id="story-review-title">Review analysis</h1>
<p>PlotDirector has finished reading your manuscript. Review the chapter results, then create scenes when you are ready.</p> <p>PlotDirector has finished reading your manuscript. Review the chapter results, then create scenes when you are ready.</p>
</div> </div>

View File

@ -0,0 +1,43 @@
@model OnboardingJourneyHeaderViewModel
@{
var steps = new[]
{
new { Label = "Welcome", Order = 1 },
new { Label = "Writing Preferences", Order = 2 },
new { Label = "Project", Order = 3 },
new { Label = "Book", Order = 4 },
new { Label = "Connect Word", Order = 5 },
new { Label = "Scan Manuscript", Order = 6 },
new { Label = "Review Chapters", Order = 7 },
new { Label = "Analyse Manuscript", Order = 8 },
new { Label = "Complete", Order = 9 }
};
}
<div class="onboarding-progress" aria-label="@Model.AriaLabel">
<span>Step @Model.CurrentStep of @Model.TotalSteps</span>
<div class="onboarding-progress-track">
<span style="width:@((Model.CurrentStep * 100) / Model.TotalSteps)%"></span>
</div>
</div>
<ol class="onboarding-stepper" aria-label="Manuscript import journey">
@foreach (var step in steps)
{
<li class="@StepClass(step.Order, Model.CurrentStep)">
<span>@step.Order</span>
<strong>@step.Label</strong>
</li>
}
</ol>
@functions {
private static string StepClass(int order, int currentStep)
{
if (order < currentStep)
{
return "is-complete";
}
return order == currentStep ? "is-current" : "is-upcoming";
}
}

View File

@ -33,7 +33,7 @@
</section> </section>
} }
@if (Model.OnboardingNudge.ShouldShow) @if (Model.OnboardingNudge.ShouldShow && !Model.StoryIntelligence.ShouldShow)
{ {
<section class="onboarding-dashboard-nudge @(Model.OnboardingNudge.CompanionConnected ? "is-connected" : string.Empty)" <section class="onboarding-dashboard-nudge @(Model.OnboardingNudge.CompanionConnected ? "is-connected" : string.Empty)"
data-word-companion-presence="@(Model.OnboardingNudge.IsCompanionPresence.ToString().ToLowerInvariant())" data-word-companion-presence="@(Model.OnboardingNudge.IsCompanionPresence.ToString().ToLowerInvariant())"
@ -49,10 +49,10 @@
@if (Model.StoryIntelligence.ShouldShow) @if (Model.StoryIntelligence.ShouldShow)
{ {
<section class="story-intelligence-dashboard-card" <section class="story-intelligence-dashboard-card story-intelligence-dashboard-card--compact"
aria-label="Story Intelligence"> aria-label="Story Intelligence">
<div> <div>
<p class="eyebrow">Story Intelligence</p> <p class="eyebrow">Manuscript import</p>
<h2>@Model.StoryIntelligence.Title</h2> <h2>@Model.StoryIntelligence.Title</h2>
<p>@Model.StoryIntelligence.Description</p> <p>@Model.StoryIntelligence.Description</p>
</div> </div>

View File

@ -1071,6 +1071,18 @@
padding: 1.15rem 1.25rem; padding: 1.15rem 1.25rem;
} }
.story-intelligence-dashboard-card--compact {
padding: .9rem 1rem;
}
.story-intelligence-dashboard-card--compact h2 {
font-size: 1.15rem;
}
.story-intelligence-dashboard-card--compact p:not(.eyebrow) {
font-size: .95rem;
}
.story-intelligence-dashboard-card h2 { .story-intelligence-dashboard-card h2 {
margin: 0 0 .35rem; margin: 0 0 .35rem;
font-size: 1.25rem; font-size: 1.25rem;