Keep the gap blank, and give the writing more of the page

Two things wrong with the paper.

"Start writing..." was printed at the foot of every page that had a break
after it. The placeholder extension styles `[contenteditable]:empty::before`,
and the spacer holding a gap open is exactly that: empty, and not
editable. It inherited a placeholder meant for an empty note.

And the column was too narrow for the sheet it now sits on — about 73
characters, which left 117px of blank paper down each edge and made the
writing look lost. Widened to about 80, a little short of where the eye
stops reliably finding the start of the next line, which leaves roughly
8% of the sheet as margin: a document's margin rather than a picture
frame.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
marcandClaude Opus 5 committed 2026-08-11 21:26:51 +02:00
1 parent 816cc8e68b
commit d82e72d358
5 files changed
+46 -85

No files matched your search

+14
View File
@@ -8,6 +8,20 @@ not from the output of `docker push`.
---
## Unreleased
**Changed**
- **Narrower margins on the sheet** — the column went from about 73 characters
to about 80, leaving roughly 8% of the paper as margin either side instead of
117px of empty page down each edge.
**Fixed**
- **"Start writing..." was printed at the foot of every page with a break after
it.** The placeholder extension styles `[contenteditable]:empty::before`, and
the spacer holding a gap open is exactly that — empty, and not editable.
---
## 2026-08-11 — Writing on paper
**Images** — web `sha256:b764a9d8…`, server unchanged at `sha256:0832ee2b…`
+11
View File
@@ -211,6 +211,17 @@ try {
(await p.locator(".ne-page-spacer").count()) > 0,
"which takes holding the gap open inside the text itself"
);
/* The placeholder extension styles `[contenteditable]:empty::before`, and a
* spacer is exactly that. "Start writing..." was printed at the foot of every
* page that had a break after it. */
check(
await p.evaluate(() =>
[...document.querySelectorAll(".ne-page-spacer")].every(
(el) => getComputedStyle(el, "::before").content === "none"
)
),
"and the gap stays blank — no placeholder printed at the foot of the page"
);
/* The note is still one document — pages are drawn, not stored. */
const stored = (await api(p, "GET", "/notes")).body[0];
-78
View File
@@ -1,78 +0,0 @@
/* Throwaway: four pages, and what that stray placeholder is. */
import { launch, withFreshUser, createNotebook, createNote, OUT } from "./lib.mjs";
const browser = await launch();
const s = await withFreshUser(browser, "probe");
const p = s.page;
p.on("pageerror", (e) => console.log("PAGEERROR", String(e)));
await p.setViewportSize({ width: 1680, height: 950 });
await createNotebook(p, "Papier");
await createNote(p);
await p.waitForTimeout(1500);
await p.locator(".ProseMirror").click();
// Paste rather than type: typing this much loses keystrokes and says nothing
// about the layout, which is what is being looked at here.
await p.evaluate(() => {
const text = Array.from({ length: 160 }, (_, i) => `Absatz ${i + 1}: Dichtungen altern durch Wärme und Zeit.`).join("\n");
const dt = new DataTransfer();
dt.setData("text/plain", text);
document.querySelector(".ProseMirror").dispatchEvent(
new ClipboardEvent("paste", { clipboardData: dt, bubbles: true, cancelable: true })
);
});
await p.waitForTimeout(4000);
console.log(
JSON.stringify(
await p.evaluate(() => {
const pm = document.querySelector(".ProseMirror");
const pmTop = pm.getBoundingClientRect().top;
const sheets = [...document.querySelectorAll("[data-page-sheet]")];
const gaps = sheets.slice(0, -1).map((el, i) => ({
from: el.getBoundingClientRect().bottom - pmTop,
to: sheets[i + 1].getBoundingClientRect().top - pmTop,
}));
const inGap = [];
for (const el of pm.children) {
if (el.classList.contains("ne-page-spacer")) continue;
const r = el.getBoundingClientRect();
if (r.height === 0) continue;
const top = r.top - pmTop,
bottom = r.bottom - pmTop;
for (const g of gaps) {
if (bottom > g.from + 0.5 && top < g.to - 0.5) inGap.push((el.textContent || "").slice(0, 24));
}
}
const placeholder = [...pm.querySelectorAll("[data-placeholder]")].map((el) => ({
text: (el.textContent || "").slice(0, 20),
first: el === pm.firstElementChild,
top: Math.round(el.getBoundingClientRect().top - pmTop),
}));
return {
blocks: [...pm.children].filter((c) => !c.classList.contains("ne-page-spacer")).length,
height: pm.scrollHeight,
sheets: sheets.length,
spacers: [...document.querySelectorAll(".ne-page-spacer")].map((el) => el.offsetHeight),
inGap,
placeholder,
position: document.querySelector("[data-note-position]")?.textContent,
margins: (() => {
const sheet = sheets[0].getBoundingClientRect();
const col = pm.getBoundingClientRect();
return {
sheetW: Math.round(sheet.width),
colW: Math.round(col.width),
left: Math.round(col.left - sheet.left),
right: Math.round(sheet.right - col.right),
};
})(),
};
}),
null,
1
)
);
await p.screenshot({ path: `${OUT}/probe.png` });
await s.cleanup();
await browser.close();
+16 -5
View File
@@ -37,11 +37,15 @@
text used to run the full width of the pane, past 84, which is beyond the
point where the eye reliably finds the start of the next line. Capped here
rather than on the container so it holds on a page as well as off one. */
max-width: 527px;
/* Centred. Pinning the column to the left edge put every line in the same
place, but on a wide window it left the whole right half of the pane as one
dead block — which is worse to look at than a column that moves. Centred,
the same leftover space reads as two margins. */
/* Roughly 80 characters to the line, leaving about 8% of the sheet as margin
on either side — a document's margin, not a picture frame. It was 527px,
which left 117px of empty paper down each edge and made the writing look
lost on the page. Past about 84 characters the eye stops reliably finding
the start of the next line, so this is close to as wide as it should go. */
max-width: 640px;
/* Centred on the sheet. Pinning the column to the left edge put every line in
the same place, but on a wide window it left the whole right half of the
pane as one dead block. */
margin-left: auto;
margin-right: auto;
outline: none;
@@ -69,6 +73,13 @@
pointer-events: none;
user-select: none;
}
/* The placeholder extension styles `[contenteditable]:empty::before`, and a
spacer is exactly that: empty, and not editable. So "Start writing..." was
printed at the foot of every page that had a break after it. */
.note-editor .ProseMirror > .ne-page-spacer::before,
.note-editor .ProseMirror > .ne-page-spacer::after {
content: none !important;
}
.note-editor .ProseMirror p {
margin: 0;
}
+5 -2
View File
@@ -373,9 +373,12 @@ state of their own, so they can be read on their own.
## Setting a note
**A centred column, about 73 characters to the line** — measured, not guessed.
**A centred column, about 80 characters to the line** — measured, not guessed.
Past roughly 84 the eye stops reliably finding the start of the next line, so the
column is capped well before the pane runs out.
column is capped a little short of that. It leaves roughly 8% of the sheet as
margin down either side: a document's margin, not a picture frame. At 73
characters it left 117px of empty paper down each edge and the writing looked
lost on the page.
It was pinned to the left margin for a while, on the reasoning that a column
which floats in the middle moves as the window is resized. True, but it traded