feat(viewer): show document summary together with the original (TODO.md L5)

This commit is contained in:
2026-08-26 19:11:15 -04:00
parent 1925bb66a8
commit 9efffcb428
8 changed files with 501 additions and 7 deletions
+37 -5
View File
@@ -12,7 +12,13 @@
* in a ≤46rem centered column;
* • any other → the raw content as a text node inside
* <pre class="doc-raw"> (mono, horizontal
* scroll).
* scroll);
* • doc.summary non-empty (phase 36 — phase 30 summaries exist
* only on non-markdown docs) → a labeled
* .doc-summary section ABOVE the content;
* null / empty / whitespace renders nothing, so
* markdown docs and fail-soft rows are
* byte-for-byte unchanged.
*
* 2. The /document.html page itself: reads `source`/`path` query
* params, fetches the stateless content endpoint
@@ -40,6 +46,11 @@
* around the page block below). Importing renderDocument elsewhere has
* no side effects: no back-link resolution, no whoami, no content
* fetch, no New Chat binding.
*
* Phase 36: the renderer owns the optional summary panel — a non-empty
* doc.summary renders as a labeled .doc-summary section above the
* original content on BOTH surfaces (page + modal) through this one
* core; the summary text is a text node (XSS contract unchanged).
*/
import { fetchIsAdmin, initSharedHeader } from "./header.js";
@@ -66,10 +77,13 @@ function metaBadge(cls, text) {
/* ---------- shared renderer (phase 26, task 02) ----------
* Populates the three elements every render surface provides: a title,
* a .doc-meta badge row (source · format · mono path · indexed ·
* chunks), and a content container — .doc-md for md/markdown (the
* shared escape-first renderer), <pre class="doc-raw"> otherwise. The
* XSS contract: innerHTML only through renderMarkdown; every
* document-derived string is a text node. */
* chunks), and a content container — an optional .doc-summary section
* first (phase 36: only when doc.summary is non-empty — markdown docs
* and fail-soft rows carry none, so they render exactly as before),
* then .doc-md for md/markdown (the shared escape-first renderer),
* <pre class="doc-raw"> otherwise. The XSS contract: innerHTML only
* through renderMarkdown; every document-derived string (summary text
* included) is a text node. */
export function renderDocument(doc, { titleEl, metaEl, contentEl }) {
titleEl.textContent = doc.title;
// Phase 34 task 04: the titlebar title ellipsizes — the full title
@@ -88,6 +102,24 @@ export function renderDocument(doc, { titleEl, metaEl, contentEl }) {
);
contentEl.replaceChildren();
// Phase 36: the summary panel — labeled section ABOVE the original
// content, on BOTH surfaces (page + modal) through this one core.
// Only a non-empty summary renders: markdown docs carry none (phase
// 30) and the fail-soft path leaves summary NULL, so both are
// byte-for-byte unchanged here.
if (doc.summary && doc.summary.trim() !== "") {
const section = document.createElement("section");
section.className = "doc-summary";
section.setAttribute("aria-label", "Summary");
const title = document.createElement("h2");
title.className = "doc-summary-title";
title.textContent = "Summary";
const body = document.createElement("p");
body.className = "doc-summary-text";
body.textContent = doc.summary; // text node — XSS contract unchanged
section.append(title, body);
contentEl.appendChild(section);
}
if (doc.format === "md" || doc.format === "markdown") {
const wrap = document.createElement("div");
wrap.className = "doc-md";