Fix Full Book assembly to always render from canonical chapter metadata

Full Book now assembles from a fresh read of the canonical book metadata
(chapter order + chapter entries) instead of the renderer's in-memory
chapter array, so it always contains every chapter in the current order
and can never render from stale or partially-loaded state. The chapter
sidebar and Full Book share the same single source of truth.

Also refresh Full Book whenever book metadata loads, and extend the e2e
scenario to cover reordering to a new order, order persistence across
reopen, and delete/rename/new-empty chapters all being reflected.
This commit is contained in:
avi 2026-08-17 19:33:41 -05:00
commit 6b1a9da5cc
2 changed files with 104 additions and 4 deletions

View file

@ -402,6 +402,7 @@ export function initBook(api: FolioAPI) {
if (ordered.length) showPlaceholder();
else showPlaceholder();
}
await refreshFullBook();
}
async function refreshMeta() {
@ -410,7 +411,6 @@ export function initBook(api: FolioAPI) {
selectedId = null;
showPlaceholder();
}
await refreshFullBook();
}
function renderChapterList() {
@ -501,11 +501,19 @@ export function initBook(api: FolioAPI) {
return false;
}
// Assemble the whole book from the canonical book metadata (the same source
// the chapter sidebar uses) so Full Book always reflects the current chapter
// set and order, regardless of any in-memory renderer state.
async function assembleFullBook(): Promise<string> {
const res = await api.getBookMeta();
if (!res || typeof res !== "object" || "error" in res) return "";
const book = res as BookMeta;
const parts: string[] = [];
for (const ch of ordered) {
const res = await api.getChapterContent(ch.id);
const content = typeof res === "string" ? res : "";
for (const id of book.chapterOrder) {
const ch = book.chapters[id];
if (!ch) continue;
const contentRes = await api.getChapterContent(ch.id);
const content = typeof contentRes === "string" ? contentRes : "";
const body = content.replace(/^\n+/, "").replace(/\s+$/, "");
const block: string[] = [];
if (!chapterHasOwnHeading(content, ch.title)) {