The same page on every screen

The text is laid out on a page of one fixed size and the sheet scaled to
the screen, so a phone breaks it exactly where a desktop does — twenty-one
lines to the page on both. The type size is computed from the page and the
line count rather than chosen.

A PDF's full screen is the browser window, not the screen, and its button
sits beside the zoom. "Recent Notes" is stamped by every write, not only by
a rename. And a PDF that will not open says which of the four reasons it is.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
marcandClaude Opus 5 committed 2026-08-26 13:23:03 +02:00
1 parent 9507761bce
commit 4013e959d6
12 files changed
+295 -101

No files matched your search

+70
View File
@@ -261,6 +261,76 @@ try {
"and the gap stays blank — no placeholder printed at the foot of the page"
);
/* ── twenty-one lines, and the same twenty-one everywhere ──
*
* Two claims. First, a page holds the number of lines a ruled page holds —
* it held eighteen on a phone, which is what started this. Second, and the
* one that matters: the phone breaks the text in exactly the same places the
* desktop does. It did not, because the text used to be set in screen pixels:
* a 340-pixel sheet fitted nothing like what a 760-pixel one did, so the same
* note had different lines on different pages depending on what you read it
* on. The text is now laid out on a page of a fixed size and the whole sheet
* scaled, so the only thing a narrow screen changes is how big it looks.
*
* Measured in page units — every length divided by the sheet's own scale —
* because screen pixels are precisely what must not agree. */
const pageUnits = () =>
p.evaluate(() => {
const scale = Number(document.querySelector("[data-text-page-scale]").dataset.textPageScale);
const pm = document.querySelector(".ProseMirror");
const cs = getComputedStyle(pm);
const sheet = document.querySelector("[data-page-sheet]").getBoundingClientRect();
const top = pm.getBoundingClientRect().top;
const blocks = [...pm.children]
.filter((el) => !el.classList.contains("ne-page-spacer"))
.slice(0, 40)
.map((el) => {
const r = el.getBoundingClientRect();
return { y: Math.round((r.top - top) / scale), h: Math.round(r.height / scale) };
});
return {
lines:
(sheet.height / scale - parseFloat(cs.paddingTop) - parseFloat(cs.paddingBottom)) /
parseFloat(cs.lineHeight),
blocks,
};
});
const onDesktop = await pageUnits();
check(
Math.floor(onDesktop.lines) === 21,
"a page holds twenty-one lines",
`${onDesktop.lines.toFixed(2)} fit`
);
const phone = { width: 390, height: 844 };
const desktop = p.viewportSize();
await p.setViewportSize(phone);
await p.waitForTimeout(2000);
/* A phone shows one pane at a time, so the note may have been left behind in
the list when the window narrowed. Open it again before measuring. */
if ((await p.locator("[data-text-page-scale]").count()) === 0) await reopenNote(p, "Langer Text");
await p.waitForSelector("[data-text-page-scale]", { timeout: 15000 });
await p.waitForTimeout(3000);
const onPhone = await pageUnits();
check(
Math.floor(onPhone.lines) === 21,
"on a phone as well — the same page, not a smaller one",
`${onPhone.lines.toFixed(2)} fit`
);
const moved = onDesktop.blocks
.map((b, i) => ({ i, b, o: onPhone.blocks[i] }))
.filter(({ b, o }) => !o || Math.abs(b.y - o.y) > 1 || Math.abs(b.h - o.h) > 1)
.map(({ i, b, o }) => `#${i}: ${b.y}/${b.h} vs ${o ? `${o.y}/${o.h}` : "missing"}`);
check(
onPhone.blocks.length === onDesktop.blocks.length && moved.length === 0,
"and every paragraph falls in the same place on both, to the page unit",
moved.slice(0, 4).join(" | ")
);
await p.setViewportSize(desktop);
await p.waitForTimeout(2000);
if ((await p.locator(".ProseMirror").count()) === 0) await reopenNote(p, "Langer Text");
/* The note is still one document — pages are drawn, not stored. */
const stored = (await api(p, "GET", "/notes")).body[0];
const body = (await api(p, "GET", `/notes/${stored.id}`)).body.content;
+4 -4
View File
@@ -271,10 +271,10 @@ try {
await p.waitForTimeout(1500);
/* ── the document, and nothing else ──
* Two things at once: the browser's own full screen, which puts the page past
* the window frame, and an overlay over the app, which is what actually
* removes the sidebar, the list and the title bar. The second is the half that
* can be measured here — and the half a browser can refuse. */
* The app laid out as an overlay over itself: the document takes the whole
* browser window, and the sidebar, the note list and the title bar go. The
* browser's own full screen is deliberately not asked for — this is about
* giving the page to the document, not the screen. */
const workspaceBox = () =>
p.locator("[data-pdf-workspace]").evaluate((el) => {
const r = el.getBoundingClientRect();