Streamline core import wizard steps
This commit is contained in:
parent
ea41ec3aac
commit
4f82dc308a
@ -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("<details class=\"onboarding-review-chapter\"", StringComparison.Ordinal), "Review Chapters should not use large disclosure cards for chapter selection.");
|
||||
Assert(review.Contains("data-bulk-target=\"chapter\" data-bulk-state=\"true\"", StringComparison.Ordinal), "Review Chapters should retain Include all chapters.");
|
||||
Assert(review.Contains("data-bulk-target=\"chapter\" data-bulk-state=\"false\"", StringComparison.Ordinal), "Review Chapters should retain Exclude all chapters.");
|
||||
Assert(review.Contains("type=\"reset\">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()
|
||||
{
|
||||
|
||||
@ -265,7 +265,7 @@
|
||||
@if (Model.IsMicrosoftWordPath)
|
||||
{
|
||||
<h1 id="onboarding-title">@(structureReadyToImport ? "Detect Scenes" : currentJourneyOrder >= 6 ? "Scan Manuscript" : "Connect your Word Companion")</h1>
|
||||
<p>@(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.")</p>
|
||||
<p>@(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.")</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -282,22 +282,22 @@
|
||||
data-word-companion-status="@Model.CompanionPresence.Status"
|
||||
aria-label="Scene detection">
|
||||
<p class="onboarding-compact-status">
|
||||
<span data-word-companion-waiting-status>@(Model.CompanionPresence.IsConnected ? "Word connected" : "Waiting for Word")</span>
|
||||
· @Model.ScanState.ChapterCount.ToString("N0") chapter@(Model.ScanState.ChapterCount == 1 ? string.Empty : "s")
|
||||
· @Model.ScanState.TotalWordCount.ToString("N0") words
|
||||
<span aria-hidden="true">@(Model.CompanionPresence.IsConnected ? "\u2713" : "!")</span>
|
||||
<span data-word-companion-compact-status-label>@(Model.CompanionPresence.IsConnected ? "Word Companion connected" : "Word Companion disconnected")</span>
|
||||
· <span data-word-companion-document>@(Model.CompanionPresence.CurrentDocumentName ?? "No document reported yet")</span>
|
||||
</p>
|
||||
<label class="onboarding-inline-choice">
|
||||
<input type="checkbox" data-onboarding-generate-scene-titles checked />
|
||||
<span>
|
||||
<strong>Generate descriptive scene titles</strong>
|
||||
<small>Short working titles used only within PlotDirector.</small>
|
||||
<small>Adds optional PlotDirector-only titles such as "Behind the Fire Exit". Titles can be edited later and do not change the manuscript.</small>
|
||||
</span>
|
||||
</label>
|
||||
<p>Keep Word open until scene markers have been added. If Word cannot save automatically, PlotDirector will ask you to save and confirm.</p>
|
||||
<div class="onboarding-build-panel" data-onboarding-build-panel>
|
||||
<p>Keep Word open while PlotDirector detects scenes and writes the scene markers.</p>
|
||||
<div class="onboarding-build-panel" data-onboarding-build-panel hidden>
|
||||
<div>
|
||||
<p class="onboarding-scan-next">Ready to detect scenes.</p>
|
||||
<p class="onboarding-build-message" data-onboarding-build-message>Scene detection has not started yet.</p>
|
||||
<p class="onboarding-scan-next">Detecting scenes</p>
|
||||
<p class="onboarding-build-message" data-onboarding-build-message>Analysing scene boundaries...</p>
|
||||
</div>
|
||||
<strong data-onboarding-build-percent></strong>
|
||||
<div class="onboarding-scan-progress onboarding-build-progress" aria-hidden="true">
|
||||
@ -388,7 +388,8 @@
|
||||
type="button"
|
||||
data-onboarding-build-start
|
||||
data-onboarding-build-action
|
||||
data-preview-id="@Model.ScanState.PreviewID">
|
||||
data-preview-id="@Model.ScanState.PreviewID"
|
||||
disabled="@(!CanStartSceneDetection(Model.CompanionPresence))">
|
||||
Detect Scenes
|
||||
</button>
|
||||
}
|
||||
@ -452,6 +453,9 @@
|
||||
private static bool CanStartScan(CompanionPresenceViewModel presence, ManuscriptScanStateViewModel scan)
|
||||
=> presence.IsConnected && presence.DocumentOpen && !scan.IsRunning;
|
||||
|
||||
private static bool CanStartSceneDetection(CompanionPresenceViewModel presence)
|
||||
=> presence.IsConnected && presence.DocumentOpen;
|
||||
|
||||
private static string CompanionScanMessage(CompanionPresenceViewModel presence, ManuscriptScanStateViewModel scan)
|
||||
{
|
||||
if (!presence.IsConnected)
|
||||
|
||||
@ -127,24 +127,18 @@
|
||||
}
|
||||
@foreach (var chapter in Model.Chapters)
|
||||
{
|
||||
<details class="onboarding-review-chapter">
|
||||
<summary>
|
||||
<span>Chapter @chapter.ChapterNumber</span>
|
||||
<strong>@chapter.ReviewTitle</strong>
|
||||
<small>@chapter.WordCount.ToString("N0") words</small>
|
||||
</summary>
|
||||
<label class="onboarding-review-chapter-row">
|
||||
<input type="hidden" name="Chapters[@chapterIndex].TemporaryChapterKey" value="@chapter.TemporaryChapterKey" />
|
||||
<input type="hidden" name="Chapters[@chapterIndex].ChapterNumber" value="@chapter.ChapterNumber" />
|
||||
<label class="onboarding-review-toggle">
|
||||
<input type="checkbox" name="Chapters[@chapterIndex].Include" value="true" checked="@chapter.Include" data-review-kind="chapter" />
|
||||
<input type="hidden" name="Chapters[@chapterIndex].Include" value="false" />
|
||||
Include chapter
|
||||
<input type="hidden" name="Chapters[@chapterIndex].Title" value="@chapter.ReviewTitle" />
|
||||
<span class="onboarding-review-chapter-row__title">
|
||||
<strong>Chapter @chapter.ChapterNumber</strong>
|
||||
<span>@chapter.ReviewTitle</span>
|
||||
</span>
|
||||
<small>@chapter.WordCount.ToString("N0") words</small>
|
||||
</label>
|
||||
<label class="onboarding-review-field">
|
||||
Chapter title
|
||||
<input class="form-control" name="Chapters[@chapterIndex].Title" value="@chapter.ReviewTitle" />
|
||||
</label>
|
||||
</details>
|
||||
chapterIndex++;
|
||||
}
|
||||
</div>
|
||||
|
||||
@ -427,12 +427,8 @@
|
||||
|
||||
.onboarding-detect-scenes {
|
||||
display: grid;
|
||||
gap: .85rem;
|
||||
margin: 1rem 0 1.25rem;
|
||||
padding: 1rem;
|
||||
border: 1px solid rgba(31, 42, 68, .12);
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, .72);
|
||||
gap: .75rem;
|
||||
margin: .75rem 0 1rem;
|
||||
}
|
||||
|
||||
.onboarding-detect-scenes > p:not(.onboarding-compact-status) {
|
||||
@ -632,7 +628,68 @@
|
||||
|
||||
.onboarding-review-chapters {
|
||||
display: grid;
|
||||
gap: .85rem;
|
||||
gap: .35rem;
|
||||
}
|
||||
|
||||
.onboarding-review-chapter-row {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: .65rem;
|
||||
min-height: 2.55rem;
|
||||
margin: 0;
|
||||
border: 1px solid rgba(31, 42, 68, .1);
|
||||
border-radius: 8px;
|
||||
padding: .45rem .65rem;
|
||||
background: rgba(255, 255, 255, .58);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.onboarding-review-chapter-row:hover {
|
||||
border-color: rgba(47, 111, 99, .28);
|
||||
background: rgba(47, 111, 99, .055);
|
||||
}
|
||||
|
||||
.onboarding-review-chapter-row:has(input[type="checkbox"]:checked) {
|
||||
border-color: rgba(47, 111, 99, .34);
|
||||
background: rgba(47, 111, 99, .08);
|
||||
}
|
||||
|
||||
.onboarding-review-chapter-row input[type="checkbox"] {
|
||||
width: 1.05rem;
|
||||
height: 1.05rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.onboarding-review-chapter-row__title {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: .45rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.onboarding-review-chapter-row__title strong,
|
||||
.onboarding-review-chapter-row__title span {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.onboarding-review-chapter-row__title strong {
|
||||
flex: 0 0 auto;
|
||||
font-size: .92rem;
|
||||
}
|
||||
|
||||
.onboarding-review-chapter-row__title span {
|
||||
overflow: hidden;
|
||||
color: var(--bs-body-color);
|
||||
font-weight: 700;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.onboarding-review-chapter-row small {
|
||||
color: var(--bs-secondary-color);
|
||||
font-weight: 800;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.onboarding-review-chapter {
|
||||
|
||||
@ -46,6 +46,9 @@
|
||||
document.querySelectorAll("[data-word-companion-status-label]").forEach((node) => {
|
||||
node.textContent = label;
|
||||
});
|
||||
document.querySelectorAll("[data-word-companion-compact-status-label]").forEach((node) => {
|
||||
node.textContent = connected ? "Word Companion connected" : "Word Companion disconnected";
|
||||
});
|
||||
document.querySelectorAll("[data-word-companion-waiting-status]").forEach((node) => {
|
||||
node.textContent = connected ? "Word Companion Connected" : "Word Companion disconnected. Waiting for reconnection...";
|
||||
});
|
||||
@ -90,6 +93,13 @@
|
||||
button.disabled = openingReview || !companionConnected || !companionDocumentOpen || currentScanStatus === "Running";
|
||||
button.textContent = openingReview ? "Opening chapter review..." : currentScanStatus === "Complete" ? "Scan again" : "Scan manuscript";
|
||||
});
|
||||
document.querySelectorAll("[data-onboarding-build-start]").forEach((button) => {
|
||||
if (button.dataset.buildMode === "confirm-save") {
|
||||
return;
|
||||
}
|
||||
|
||||
button.disabled = !companionConnected || !companionDocumentOpen;
|
||||
});
|
||||
};
|
||||
|
||||
const applyScanState = (state) => {
|
||||
@ -195,6 +205,7 @@
|
||||
pendingBuildPreviewId = pendingManualSavePreviewId || pendingBuildPreviewId;
|
||||
|
||||
document.querySelectorAll("[data-onboarding-build-panel]").forEach((node) => {
|
||||
node.hidden = false;
|
||||
node.classList.add("is-running", "is-manual-save");
|
||||
});
|
||||
document.querySelectorAll("[data-onboarding-build-message]").forEach((node) => {
|
||||
@ -284,6 +295,7 @@
|
||||
return;
|
||||
}
|
||||
document.querySelectorAll("[data-onboarding-build-panel]").forEach((node) => {
|
||||
node.hidden = false;
|
||||
node.classList.add("is-running");
|
||||
node.classList.remove("is-manual-save");
|
||||
});
|
||||
@ -353,6 +365,7 @@
|
||||
clearManualSaveRequired();
|
||||
button.disabled = true;
|
||||
document.querySelectorAll("[data-onboarding-build-panel]").forEach((node) => {
|
||||
node.hidden = false;
|
||||
node.classList.add("is-running");
|
||||
});
|
||||
document.querySelectorAll("[data-onboarding-build-message]").forEach((node) => {
|
||||
@ -373,6 +386,7 @@
|
||||
} catch (error) {
|
||||
button.disabled = false;
|
||||
document.querySelectorAll("[data-onboarding-build-panel]").forEach((node) => {
|
||||
node.hidden = false;
|
||||
node.classList.remove("is-running");
|
||||
});
|
||||
document.querySelectorAll("[data-onboarding-build-message]").forEach((node) => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user