From 6e8ed9775c0781c5270a4314a606a712c197b498 Mon Sep 17 00:00:00 2001 From: Abdulfatah Bahbouh <60443686+abooodbah@users.noreply.github.com> Date: Fri, 6 Nov 2026 08:48:14 -0600 Subject: [PATCH 1/2] Test the column of a document with no outline in every layout A document with fewer than two H2 or H3 headings has no outline. In the wide layout (1180 px and wider) its text goes into the 220 px outline column. These checks fail on main at 1180 px. --- tests/Invoke-ReaderBrowserSmoke.mjs | 92 +++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/tests/Invoke-ReaderBrowserSmoke.mjs b/tests/Invoke-ReaderBrowserSmoke.mjs index 7193f40..ae741da 100644 --- a/tests/Invoke-ReaderBrowserSmoke.mjs +++ b/tests/Invoke-ReaderBrowserSmoke.mjs @@ -289,6 +289,33 @@ const THEME_DOCUMENT_HTML = [ " C -->|yes| D[Ship]\n", ].join("\n"); +// A short status note with one H1 and no H2 or H3, so it has no outline: +// four short paragraphs, three opening in bold, and one diagram. It has the +// shape of the 2.1 KB file that 0.7.0 showed in a 220 px column in a wide +// window; the text is made up. +const SHORT_STATUS_HTML = [ + "
Checked Nov 2, 2026, at 09:15 UTC. This is the logical view: relay, " + + "then two workers, then two sensors each. Physically, the sensors all " + + "plug into the site switch, and each worker's software sets which " + + "sensors it reads.
", + "Result: all hardware is connected and on its listed address, " + + "and all four sensors report directly to their worker.
", + "Changed since the 08:50 UTC check: sensor_4 is online " + + "and sensor_3's main feed works on worker 2.
", + "Note on sensor_3: its second feed is out of scope for " + + "this test, so the sensor is marked correct on its main feed. The monitor " + + "still reports it as offline overall because the second feed returns 404.
", + 'flowchart TD\n' +
+ ' hub["Hub"] -->|"VPN"| relay["Relay"]\n' +
+ ' relay --> w1["Worker 1"]\n' +
+ ' relay --> w2["Worker 2"]\n' +
+ ' w1 --> s1["Sensor 1"]\n' +
+ ' w1 --> s2["Sensor 2"]\n' +
+ ' w2 --> s3["Sensor 3"]\n' +
+ ' w2 --> s4["Sensor 4"]\n',
+].join("\n");
+
// The softer dark themes from issue #6. The reader must apply exactly these
// token values; they are the contract a later shared theme system reuses.
const SOFT_THEME_TOKENS = {
@@ -1083,6 +1110,71 @@ async function run() {
check(Math.abs(column.width - column.full) < 1 && column.scroll <= 0,
"Full fills a narrower window too, with no sideways scroll");
+ // A document with no outline gets the same column as any other, in every
+ // layout. In 0.7.0 the wide layout kept its outline columns when there was
+ // no outline, so the text went into the 220 px outline column.
+ const SHORT_COLUMN = `(function () {
+ var pane = document.getElementById("readerPane");
+ var style = getComputedStyle(pane);
+ var main = document.getElementById("mainContent").getBoundingClientRect();
+ var box = pane.getBoundingClientRect();
+ var left = box.left + pane.clientLeft + parseFloat(style.paddingLeft);
+ var space = pane.clientWidth - parseFloat(style.paddingLeft) -
+ parseFloat(style.paddingRight);
+ return {
+ width: main.width,
+ space: space,
+ leftGap: main.left - left,
+ rightGap: left + space - main.right,
+ rows: style.gridTemplateRows.split(" ").length,
+ outline: getComputedStyle(document.getElementById("outline")).display,
+ layout: document.documentElement.dataset.layout || "",
+ scroll: document.documentElement.scrollWidth - window.innerWidth
+ };
+ }())`;
+ const COLUMN_FOR = { narrow: NARROW_PX, wide: WIDE_PX, full: Infinity };
+ const showShortStatus = async (width) => {
+ await deliver({ type: "host", copySource: true, tabs: true, zoom: 100, width });
+ await deliver({
+ type: "document", tabId: 1, theme: "dusk", fileName: "status.md",
+ path: "/fixtures/status.md", documentBaseUrl: "", sourceBytes: 2144,
+ hasMermaid: true, html: SHORT_STATUS_HTML,
+ });
+ await waitFor(client, 'document.documentElement.dataset.renderState === "ready" && ' +
+ '!!document.querySelector("#article figure.diagram svg")', "the short status note");
+ };
+ // 966 px is the window of the report; 760 and 761, 1179 and 1180 are the
+ // two sides of the compact and wide breakpoints; 1880 is a maximised window.
+ for (const width of ["narrow", "wide", "full"]) {
+ for (const [viewport, layout] of [[966, ""], [760, ""], [761, ""],
+ [1179, ""], [1180, "wide"], [1880, "wide"]]) {
+ await setViewport(viewport);
+ await showShortStatus(width);
+ const short = await client.evaluate(SHORT_COLUMN);
+ const expected = Math.min(COLUMN_FOR[width], short.space);
+ check(Math.abs(short.width - expected) < 1 && Math.abs(short.leftGap - short.rightGap) < 1 &&
+ short.rows === 1 && short.outline === "none" && short.layout === layout &&
+ short.scroll <= 0,
+ "with no outline, " + width + " at " + viewport + " px is a centred " +
+ Math.round(expected) + " px column in one row " + JSON.stringify(short));
+ }
+ }
+ // Going from a document with an outline to one without, and back.
+ await showWidthDocument({ zoom: 100, width: "narrow" });
+ check(await client.evaluate(
+ 'getComputedStyle(document.getElementById("outline")).display === "block" && ' +
+ 'getComputedStyle(document.getElementById("readerPane")).gridTemplateColumns === "220px 720px 220px"'),
+ "a document with an outline still gets the outline column at 1880 px");
+ await showShortStatus("narrow");
+ const switched = await client.evaluate(SHORT_COLUMN);
+ check(Math.round(switched.width) === NARROW_PX && switched.rows === 1,
+ "the next document without an outline gets the " + NARROW_PX + " px column " +
+ JSON.stringify(switched));
+ await showWidthDocument({ zoom: 100, width: "narrow" });
+ check(Math.round((await client.evaluate(COLUMN)).width) === NARROW_PX && await client.evaluate(
+ 'getComputedStyle(document.getElementById("outline")).display === "block"'),
+ "and the outline comes back with the next document that has one");
+
// Zoom. The host keeps its web view at 100% and the page zooms only the
// document, so the toolbar and tabs keep their size.
await setViewport(1200);
From 229c2673151dbcfb7b07290d6cf4e2dabef1a776 Mon Sep 17 00:00:00 2001
From: Abdulfatah Bahbouh <60443686+abooodbah@users.noreply.github.com>
Date: Fri, 6 Nov 2026 08:52:21 -0600
Subject: [PATCH 2/2] Give the wide layout its outline columns only when there
is an outline
The wide layout (1180 px and wider) always had three columns: 220 px for
the outline, the text, and 220 px to balance. A document with fewer than
two H2 or H3 headings has no outline, so its text was placed in the first
column and shown 220 px wide. The reader now marks the pane when it has an
outline, and the side columns apply only then.
---
assets/reader.css | 8 +++++---
assets/reader.js | 4 ++++
2 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/assets/reader.css b/assets/reader.css
index 36da0c0..d63751f 100644
--- a/assets/reader.css
+++ b/assets/reader.css
@@ -1248,13 +1248,15 @@ button {
}
/* The layout follows the width left for the zoomed document (set by
- reader.js), not the window, so zooming in still gives the text room. */
-:root[data-layout="wide"] .content-grid {
+ reader.js), not the window, so zooming in still gives the text room. The
+ side columns are only for a document with an outline (data-outline, set by
+ reader.js): without one the text would go into the 220 px outline column. */
+:root[data-layout="wide"] .content-grid[data-outline] {
grid-template-columns: 220px var(--column) 220px;
}
:root[data-layout="wide"] .outline:not([hidden]),
-:root[data-layout="wide"] .layout-balance {
+:root[data-layout="wide"] .content-grid[data-outline] .layout-balance {
display: block;
}
diff --git a/assets/reader.js b/assets/reader.js
index 2f61fd0..9b13c5f 100644
--- a/assets/reader.js
+++ b/assets/reader.js
@@ -612,14 +612,18 @@
}
});
+ // The wide layout has side columns only for a document with an outline;
+ // without one the text keeps the single column (reader.css).
if (outlineEntries.length < 2) {
outlineEntries = [];
elements.outlineList.replaceChildren();
elements.outline.hidden = true;
+ elements.readerPane.removeAttribute("data-outline");
return;
}
elements.outlineList.replaceChildren(outline);
elements.outline.hidden = false;
+ elements.readerPane.setAttribute("data-outline", "");
}
function prepareDiagrams() {