Phase 10E.1: Word Companion Resolve Diagnostics and Outline Display Cleanup

This commit is contained in:
Nick Beckley 2026-06-13 19:43:37 +01:00
parent 8c4525d3b3
commit 6049ac2c87
3 changed files with 136 additions and 5 deletions

View File

@ -82,6 +82,52 @@
</div>
<span>Scene Status</span>
<p class="word-companion-message" data-plotdirector-scene-status>Select a PlotDirector book.</p>
<div class="word-companion-resolve-diagnostics" aria-label="Resolve diagnostics">
<div>
<span>Selected Project ID</span>
<strong data-resolve-project-id>-</strong>
</div>
<div>
<span>Selected Project</span>
<strong data-resolve-project-title>-</strong>
</div>
<div>
<span>Selected Book ID</span>
<strong data-resolve-book-id>-</strong>
</div>
<div>
<span>Selected Book</span>
<strong data-resolve-book-title>-</strong>
</div>
<div>
<span>Detected Chapter</span>
<strong data-resolve-detected-chapter>-</strong>
</div>
<div>
<span>Detected Scene</span>
<strong data-resolve-detected-scene>-</strong>
</div>
<div>
<span>Resolve Request Chapter</span>
<strong data-resolve-request-chapter>-</strong>
</div>
<div>
<span>Resolve Request Scene</span>
<strong data-resolve-request-scene>-</strong>
</div>
<div>
<span>Resolve Result</span>
<strong data-resolve-result>Not run</strong>
</div>
<div>
<span>Chapter ID</span>
<strong data-resolve-chapter-id>-</strong>
</div>
<div>
<span>Scene ID</span>
<strong data-resolve-scene-id>-</strong>
</div>
</div>
<div class="word-companion-scene-details" data-plotdirector-scene-details hidden>
<div>
<span>Scene Title</span>

View File

@ -174,18 +174,29 @@ body {
font-size: 0.88rem;
}
.word-companion-resolve-diagnostics,
.word-companion-scene-details,
.word-companion-link-groups {
display: grid;
gap: 8px;
}
.word-companion-resolve-diagnostics div,
.word-companion-scene-details div,
.word-companion-link-groups div {
display: grid;
gap: 3px;
}
.word-companion-resolve-diagnostics {
border-top: 1px solid var(--companion-line);
padding-top: 8px;
}
.word-companion-resolve-diagnostics strong {
font-size: 0.86rem;
}
.word-companion-brief {
display: grid;
gap: 5px;

View File

@ -37,6 +37,17 @@
const characterChips = document.querySelector("[data-character-chips]");
const assetChips = document.querySelector("[data-asset-chips]");
const locationChips = document.querySelector("[data-location-chips]");
const resolveProjectId = document.querySelector("[data-resolve-project-id]");
const resolveProjectTitle = document.querySelector("[data-resolve-project-title]");
const resolveBookId = document.querySelector("[data-resolve-book-id]");
const resolveBookTitle = document.querySelector("[data-resolve-book-title]");
const resolveDetectedChapter = document.querySelector("[data-resolve-detected-chapter]");
const resolveDetectedScene = document.querySelector("[data-resolve-detected-scene]");
const resolveRequestChapter = document.querySelector("[data-resolve-request-chapter]");
const resolveRequestScene = document.querySelector("[data-resolve-request-scene]");
const resolveResult = document.querySelector("[data-resolve-result]");
const resolveChapterId = document.querySelector("[data-resolve-chapter-id]");
const resolveSceneId = document.querySelector("[data-resolve-scene-id]");
const runDiagnosticsButton = document.querySelector("[data-run-diagnostics]");
const diagnosticHost = document.querySelector("[data-diagnostic-host]");
const diagnosticPlatform = document.querySelector("[data-diagnostic-platform]");
@ -181,6 +192,37 @@
setHidden(plotDirectorLinkGroups, false);
};
const normalizeWhitespace = (value) => String(value || "").trim().replace(/\s+/g, " ");
const normalizeTitleForResolve = (title, type) => {
const original = normalizeWhitespace(title);
if (!original) {
return "";
}
const label = type === "scene" ? "scene" : "chapter";
const ordinal = "(?:\\d+|[ivxlcdm]+|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|nineteen|twenty)";
const prefix = new RegExp(`^${label}\\s+${ordinal}\\s*(?::|-|\\u2013|\\u2014)\\s*`, "i");
const normalized = normalizeWhitespace(original.replace(prefix, ""));
return normalized || original;
};
const setResolveDiagnostics = (values) => {
const hasValue = (name) => Object.prototype.hasOwnProperty.call(values, name);
if (hasValue("projectId")) setText(resolveProjectId, displayValue(values.projectId));
if (hasValue("projectTitle")) setText(resolveProjectTitle, displayValue(values.projectTitle));
if (hasValue("bookId")) setText(resolveBookId, displayValue(values.bookId));
if (hasValue("bookTitle")) setText(resolveBookTitle, displayValue(values.bookTitle));
if (hasValue("detectedChapter")) setText(resolveDetectedChapter, displayValue(values.detectedChapter));
if (hasValue("detectedScene")) setText(resolveDetectedScene, displayValue(values.detectedScene));
if (hasValue("requestChapter")) setText(resolveRequestChapter, displayValue(values.requestChapter));
if (hasValue("requestScene")) setText(resolveRequestScene, displayValue(values.requestScene));
if (hasValue("result")) setText(resolveResult, displayValue(values.result));
if (hasValue("chapterId")) setText(resolveChapterId, displayValue(values.chapterId));
if (hasValue("sceneId")) setText(resolveSceneId, displayValue(values.sceneId));
};
const findSceneRevisionStatus = async (bookId, sceneId) => {
const structure = await fetchJson(`/api/word-companion/books/${bookId}/structure`);
const chapters = Array.isArray(structure?.chapters) ? structure.chapters : [];
@ -475,14 +517,14 @@
chapterBlock.className = "word-companion-outline-chapter";
const chapterTitle = document.createElement("strong");
chapterTitle.textContent = `Chapter ${chapter.index}: ${chapter.title}`;
chapterTitle.textContent = chapter.title;
chapterBlock.append(chapterTitle);
if (chapter.scenes.length > 0) {
const sceneList = document.createElement("ul");
for (const sceneItem of chapter.scenes) {
const sceneRow = document.createElement("li");
sceneRow.textContent = `Scene ${sceneItem.index}: ${sceneItem.title}`;
sceneRow.textContent = sceneItem.title;
sceneList.append(sceneRow);
}
chapterBlock.append(sceneList);
@ -613,14 +655,34 @@
});
const refreshPlotDirectorScene = async () => {
const project = selectedProject();
const book = selectedBook();
const requestChapterTitle = normalizeTitleForResolve(detectedChapterTitle, "chapter");
const requestSceneTitle = normalizeTitleForResolve(detectedSceneTitle, "scene");
setResolveDiagnostics({
projectId: project?.projectId,
projectTitle: project?.title,
bookId: book?.bookId,
bookTitle: book?.title,
detectedChapter: detectedChapterTitle,
detectedScene: detectedSceneTitle,
requestChapter: requestChapterTitle,
requestScene: requestSceneTitle,
result: "Not run",
chapterId: null,
sceneId: null
});
if (!book) {
resetPlotDirectorScene("Select a PlotDirector book.");
setResolveDiagnostics({ result: "Error" });
return;
}
if (!detectedChapterTitle || !detectedSceneTitle) {
resetPlotDirectorScene("No scene detected in Word.");
setResolveDiagnostics({ result: "Error" });
return;
}
@ -639,19 +701,30 @@
let resolvedSceneId = null;
try {
const resolveResponse = await postJson(`/api/word-companion/books/${book.bookId}/resolve-scene`, {
chapterTitle: detectedChapterTitle,
sceneTitle: detectedSceneTitle
chapterTitle: requestChapterTitle,
sceneTitle: requestSceneTitle
});
if (!resolveResponse?.matched || !resolveResponse.sceneId) {
resetPlotDirectorScene("Scene not found in PlotDirector.");
resetPlotDirectorScene("No exact match found for this chapter/scene in the selected book.");
setResolveDiagnostics({
result: "Not matched",
chapterId: resolveResponse?.chapterId,
sceneId: resolveResponse?.sceneId
});
restorePlotDirectorSceneButton();
return;
}
resolvedSceneId = resolveResponse.sceneId;
setResolveDiagnostics({
result: "Matched",
chapterId: resolveResponse.chapterId,
sceneId: resolveResponse.sceneId
});
} catch {
resetPlotDirectorScene("Unable to resolve scene.");
setResolveDiagnostics({ result: "Error" });
restorePlotDirectorSceneButton();
return;
}
@ -667,6 +740,7 @@
renderCompanionData(companionData);
} catch {
resetPlotDirectorScene("Unable to load PlotDirector scene data.");
setResolveDiagnostics({ result: "Error" });
} finally {
restorePlotDirectorSceneButton();
}