From d1c1aa29a2c332412802a51b8b16aba7045dc3c1 Mon Sep 17 00:00:00 2001 From: sixvolts Date: Sun, 6 Sep 2026 16:07:53 +0000 Subject: [PATCH] fix(wiki): stop the page list rebuilding on every autosave MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The nav flash persisted after the sidebar-rail fix because this is a second, separate offender in a different element. The rail fix (workspace.js) was real; it just was not the thing flashing here. wiki.js flushSave updates one row's data and then calls renderPageList(), which opens with tree.innerHTML = "" and rebuilds every row. Off a 500ms autosave debounce that is a full teardown of the wiki page list every time typing pauses. The existing comment says "update the row in the list without re-fetching the whole index" — it avoids the fetch but still does the full DOM rebuild. A row renders exactly two things, the title and relTime(updated_at), and both belong to the row being saved. So patch that row in place: rows now carry data-page-id, and the save path updates the title and meta text directly. Falls back to renderPageList() when the row is not found (list not built yet, or a newly added page), so structural changes still repaint. Note this is deliberately NOT the diff-and-skip approach used for the rail: updated_at genuinely changes on every save, so the meta text really does need updating. Skipping would be wrong; rebuilding everything was just overkill. Used a dataset scan rather than a CSS.escape selector — it would have been the only CSS.escape in the codebase, and page ids are UUIDs, so the escaping bought nothing and added a first-and-only browser dependency. esprima parses wiki.js. Whether the flash is gone is a repaint, which only the operator can confirm on desktop. --- familiar-workspace/static/wiki.js | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/familiar-workspace/static/wiki.js b/familiar-workspace/static/wiki.js index 9cf06b7..384361e 100644 --- a/familiar-workspace/static/wiki.js +++ b/familiar-workspace/static/wiki.js @@ -912,6 +912,7 @@ const row = document.createElement("button"); row.type = "button"; row.className = "notes-row"; + row.dataset.pageId = p.id; if (p.slug === localState.pageSlug) row.classList.add("is-active"); const t = document.createElement("div"); t.className = "notes-row-title"; @@ -1596,7 +1597,29 @@ snippet: localState.pages[idx].snippet, updated_at: p.updated_at, updated_by: p.updated_by, }; - renderPageList(); + // Patch just this row. renderPageList() opens with + // tree.innerHTML = "" and rebuilds every row, so calling it + // from the 500ms autosave made the page list visibly flash + // on every debounce while typing. A row shows title + + // relTime(updated_at) and nothing else, and both live on + // this one row, so there is nothing a full rebuild would + // achieve here that this does not. + // + // Falls back to a full render only if the row is missing + // (list not built yet, or the page is newly added), so a + // structural change still repaints correctly. + let rowEl = null; + for (const r of tree.querySelectorAll(".notes-row")) { + if (r.dataset.pageId === p.id) { rowEl = r; break; } + } + if (rowEl) { + const titleEl = rowEl.querySelector(".notes-row-title"); + const metaEl = rowEl.querySelector(".notes-row-meta"); + if (titleEl) titleEl.textContent = p.title || "Untitled"; + if (metaEl) metaEl.textContent = relTime(p.updated_at); + } else { + renderPageList(); + } } setTimeout(() => { if (savedDot.textContent === savedMsg) savedDot.textContent = "";