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> </div>
<span>Scene Status</span> <span>Scene Status</span>
<p class="word-companion-message" data-plotdirector-scene-status>Select a PlotDirector book.</p> <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 class="word-companion-scene-details" data-plotdirector-scene-details hidden>
<div> <div>
<span>Scene Title</span> <span>Scene Title</span>

View File

@ -174,18 +174,29 @@ body {
font-size: 0.88rem; font-size: 0.88rem;
} }
.word-companion-resolve-diagnostics,
.word-companion-scene-details, .word-companion-scene-details,
.word-companion-link-groups { .word-companion-link-groups {
display: grid; display: grid;
gap: 8px; gap: 8px;
} }
.word-companion-resolve-diagnostics div,
.word-companion-scene-details div, .word-companion-scene-details div,
.word-companion-link-groups div { .word-companion-link-groups div {
display: grid; display: grid;
gap: 3px; 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 { .word-companion-brief {
display: grid; display: grid;
gap: 5px; gap: 5px;

View File

@ -37,6 +37,17 @@
const characterChips = document.querySelector("[data-character-chips]"); const characterChips = document.querySelector("[data-character-chips]");
const assetChips = document.querySelector("[data-asset-chips]"); const assetChips = document.querySelector("[data-asset-chips]");
const locationChips = document.querySelector("[data-location-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 runDiagnosticsButton = document.querySelector("[data-run-diagnostics]");
const diagnosticHost = document.querySelector("[data-diagnostic-host]"); const diagnosticHost = document.querySelector("[data-diagnostic-host]");
const diagnosticPlatform = document.querySelector("[data-diagnostic-platform]"); const diagnosticPlatform = document.querySelector("[data-diagnostic-platform]");
@ -181,6 +192,37 @@
setHidden(plotDirectorLinkGroups, false); 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 findSceneRevisionStatus = async (bookId, sceneId) => {
const structure = await fetchJson(`/api/word-companion/books/${bookId}/structure`); const structure = await fetchJson(`/api/word-companion/books/${bookId}/structure`);
const chapters = Array.isArray(structure?.chapters) ? structure.chapters : []; const chapters = Array.isArray(structure?.chapters) ? structure.chapters : [];
@ -475,14 +517,14 @@
chapterBlock.className = "word-companion-outline-chapter"; chapterBlock.className = "word-companion-outline-chapter";
const chapterTitle = document.createElement("strong"); const chapterTitle = document.createElement("strong");
chapterTitle.textContent = `Chapter ${chapter.index}: ${chapter.title}`; chapterTitle.textContent = chapter.title;
chapterBlock.append(chapterTitle); chapterBlock.append(chapterTitle);
if (chapter.scenes.length > 0) { if (chapter.scenes.length > 0) {
const sceneList = document.createElement("ul"); const sceneList = document.createElement("ul");
for (const sceneItem of chapter.scenes) { for (const sceneItem of chapter.scenes) {
const sceneRow = document.createElement("li"); const sceneRow = document.createElement("li");
sceneRow.textContent = `Scene ${sceneItem.index}: ${sceneItem.title}`; sceneRow.textContent = sceneItem.title;
sceneList.append(sceneRow); sceneList.append(sceneRow);
} }
chapterBlock.append(sceneList); chapterBlock.append(sceneList);
@ -613,14 +655,34 @@
}); });
const refreshPlotDirectorScene = async () => { const refreshPlotDirectorScene = async () => {
const project = selectedProject();
const book = selectedBook(); 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) { if (!book) {
resetPlotDirectorScene("Select a PlotDirector book."); resetPlotDirectorScene("Select a PlotDirector book.");
setResolveDiagnostics({ result: "Error" });
return; return;
} }
if (!detectedChapterTitle || !detectedSceneTitle) { if (!detectedChapterTitle || !detectedSceneTitle) {
resetPlotDirectorScene("No scene detected in Word."); resetPlotDirectorScene("No scene detected in Word.");
setResolveDiagnostics({ result: "Error" });
return; return;
} }
@ -639,19 +701,30 @@
let resolvedSceneId = null; let resolvedSceneId = null;
try { try {
const resolveResponse = await postJson(`/api/word-companion/books/${book.bookId}/resolve-scene`, { const resolveResponse = await postJson(`/api/word-companion/books/${book.bookId}/resolve-scene`, {
chapterTitle: detectedChapterTitle, chapterTitle: requestChapterTitle,
sceneTitle: detectedSceneTitle sceneTitle: requestSceneTitle
}); });
if (!resolveResponse?.matched || !resolveResponse.sceneId) { 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(); restorePlotDirectorSceneButton();
return; return;
} }
resolvedSceneId = resolveResponse.sceneId; resolvedSceneId = resolveResponse.sceneId;
setResolveDiagnostics({
result: "Matched",
chapterId: resolveResponse.chapterId,
sceneId: resolveResponse.sceneId
});
} catch { } catch {
resetPlotDirectorScene("Unable to resolve scene."); resetPlotDirectorScene("Unable to resolve scene.");
setResolveDiagnostics({ result: "Error" });
restorePlotDirectorSceneButton(); restorePlotDirectorSceneButton();
return; return;
} }
@ -667,6 +740,7 @@
renderCompanionData(companionData); renderCompanionData(companionData);
} catch { } catch {
resetPlotDirectorScene("Unable to load PlotDirector scene data."); resetPlotDirectorScene("Unable to load PlotDirector scene data.");
setResolveDiagnostics({ result: "Error" });
} finally { } finally {
restorePlotDirectorSceneButton(); restorePlotDirectorSceneButton();
} }