Write pinned notes in the margin, and give the phone a sheet instead
A note about a passage belongs beside the passage, not on top of it. Only a numbered marker stays on the page now, with a hairline out to a card in the margin, level with its anchor. Cards keep one height so two notes close together can be laid out by pushing the second down; the one being written in grows and rises above the rest. A phone has no margin to write in — a page narrow enough to read leaves no white space beside it, and taking a column would make the document unreadable. There the note comes up from the bottom instead: over the page, only while open, within reach of a thumb, with the passage still visible above it. Reserving the column exposed a loop worth remembering. A flex item is as wide as its content unless told otherwise, so the column made the viewer wider, which made there be room for the column: the page ended up at 254px inside a 420px phone while the layout insisted it had 611px. The viewer and the editor pane now say minWidth: 0, and the toolbar scrolls rather than cutting its last controls off on a narrow screen. Splitting each row into page and margin also broke which pages counted as visible — the check read data-pdf-page off the row, found nothing, and cancelled every text layer before it rendered. It reads the row's own attribute now. 215 unit tests, 219 browser checks across eleven suites, including the phone layout at 420px. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6438648313
commit
011ba82a94
7 files changed
+653
-208
No files matched your search
+115
-5
@@ -480,11 +480,26 @@ try {
|
||||
await p.mouse.click(page1.x + page1.width * 0.4, page1.y + page1.height * 0.3);
|
||||
await p.waitForTimeout(800);
|
||||
|
||||
check((await p.locator("[data-pin]").count()) === 1, "clicking the page leaves a pin");
|
||||
const bubble = p.getByLabel("Pinned note");
|
||||
check(await bubble.isVisible(), "and opens straight into writing");
|
||||
check((await p.locator("[data-pin]").count()) === 1, "clicking the page leaves a marker on the spot");
|
||||
check(
|
||||
(await p.locator("[data-pdf-margin]").count()) > 0,
|
||||
"and the note itself belongs in the margin beside the page, not on it"
|
||||
);
|
||||
const field = p.getByLabel("Pinned note");
|
||||
check(await field.isVisible(), "which opens straight into writing");
|
||||
|
||||
await bubble.fill("Drehmoment beachten");
|
||||
/* The card has to be level with the passage — that is the whole point of a
|
||||
margin note, and the reason it is not simply a list under the document. */
|
||||
const levels = await p.evaluate(() => {
|
||||
const marker = document.querySelector("[data-pin]").getBoundingClientRect();
|
||||
const card = document.querySelector("[data-pin-card]").getBoundingClientRect();
|
||||
const page = document.querySelector('[data-pdf-page="1"]').getBoundingClientRect();
|
||||
return { gap: Math.abs(marker.top - card.top), pageRight: page.right, cardLeft: card.left };
|
||||
});
|
||||
check(levels.gap < 40, "the card sits level with its marker", `${Math.round(levels.gap)}px apart`);
|
||||
check(levels.cardLeft >= levels.pageRight - 2, "and beside the page rather than over it");
|
||||
|
||||
await field.fill("Drehmoment beachten");
|
||||
await p.getByRole("button", { name: "Fertig" }).click();
|
||||
await p.waitForTimeout(2500);
|
||||
|
||||
@@ -539,11 +554,17 @@ try {
|
||||
await p.waitForTimeout(4000);
|
||||
check((await p.locator("[data-pin]").count()) === 1, "the pin survives a reload");
|
||||
|
||||
check(
|
||||
(await p.locator("[data-pin-card]").innerText()).includes("Drehmoment"),
|
||||
"with the text showing in the margin without being opened first",
|
||||
await p.locator("[data-pin-card]").innerText()
|
||||
);
|
||||
|
||||
await p.locator("[data-pin]").click();
|
||||
await p.waitForTimeout(600);
|
||||
check(
|
||||
(await p.getByLabel("Pinned note").inputValue()) === "Drehmoment beachten",
|
||||
"with the text that was written into it"
|
||||
"and the same text when opened for editing"
|
||||
);
|
||||
|
||||
/* Clearing a page's ink must not take its pinned notes: same page, different
|
||||
@@ -610,6 +631,95 @@ try {
|
||||
|
||||
|
||||
|
||||
/* ── on a phone there is no margin to write in ── */
|
||||
// A page narrow enough to read leaves no white space beside it, so taking a
|
||||
// column for notes would make the document itself unreadable. The note comes
|
||||
// up from the bottom instead — over the page, but only while it is open.
|
||||
await p.locator("[data-pdf-pin-mode]").click();
|
||||
await p.waitForTimeout(400);
|
||||
const pageM = await p.locator('[data-pdf-page="1"]').boundingBox();
|
||||
await p.mouse.click(pageM.x + pageM.width * 0.5, pageM.y + pageM.height * 0.4);
|
||||
await p.waitForTimeout(800);
|
||||
await p.getByLabel("Pinned note").fill("Am Rand notiert");
|
||||
await p.getByRole("button", { name: "Fertig" }).click();
|
||||
await p.waitForTimeout(2500);
|
||||
|
||||
const wideMargins = await p.locator("[data-pdf-margin]").count();
|
||||
check(wideMargins > 0, "on a wide screen the note is in the margin", String(wideMargins));
|
||||
const pageWithMargin = (await p.locator('[data-pdf-page="1"]').boundingBox()).width;
|
||||
|
||||
await p.setViewportSize({ width: 420, height: 820 });
|
||||
await p.waitForTimeout(2000);
|
||||
// Below the phone breakpoint the list and the editor take turns, so the note
|
||||
// has to be opened again to be looking at the document at all.
|
||||
// Shrinking a desktop window past the breakpoint leaves the sidebar and its
|
||||
// backdrop up, which no real phone ever sees. Dismissed here so the rest of
|
||||
// the section is testing the document and not an overlay.
|
||||
if ((await p.locator("[data-pdf-page]").count()) === 0) {
|
||||
await p.evaluate(() => document.querySelector("[data-note-row]")?.click());
|
||||
await p.waitForTimeout(4500);
|
||||
}
|
||||
await p.mouse.click(410, 800);
|
||||
await p.waitForTimeout(800);
|
||||
check(
|
||||
(await p.locator("[data-pdf-margin]").count()) === 0,
|
||||
"on a narrow one the margin is gone",
|
||||
String(await p.locator("[data-pdf-margin]").count())
|
||||
);
|
||||
check((await p.locator("[data-pin]").count()) > 0, "but the markers are still on their spots");
|
||||
|
||||
// The widest page should fill what the screen has, rather than share it with
|
||||
// a column. (Page 1 is portrait in a document whose widest page is landscape,
|
||||
// so it is legitimately narrower — page 2 is the one that sets the scale.)
|
||||
void pageWithMargin;
|
||||
const widest = (await p.locator('[data-pdf-page="2"]').boundingBox()).width;
|
||||
check(
|
||||
widest > 420 - 60,
|
||||
"and the page gets the width the column was taking",
|
||||
`${Math.round(widest)}px of 420`
|
||||
);
|
||||
|
||||
/* Reserving a column made the viewer wider, which made there be room for the
|
||||
column — the page ended up at 254px inside a 420px phone while the layout
|
||||
insisted it had 611px. A flex item is as wide as its content unless told
|
||||
otherwise. */
|
||||
const fits = await p.evaluate(() => {
|
||||
let el = document.querySelector("[data-pdf-page]")?.parentElement;
|
||||
while (el && getComputedStyle(el).overflow !== "auto") el = el.parentElement;
|
||||
return { scroller: el?.clientWidth ?? 0, window: window.innerWidth };
|
||||
});
|
||||
check(
|
||||
fits.scroller <= fits.window,
|
||||
"and the document area does not push itself past the window",
|
||||
`${fits.scroller} vs ${fits.window}`
|
||||
);
|
||||
|
||||
const marker = p.locator("[data-pin]").first();
|
||||
const markerText = await marker.getAttribute("title");
|
||||
await marker.scrollIntoViewIfNeeded();
|
||||
await marker.click();
|
||||
await p.waitForTimeout(800);
|
||||
check((await p.locator("[data-pin-sheet]").count()) === 1, "tapping the marker opens a sheet");
|
||||
check(
|
||||
(await p.getByLabel("Pinned note").inputValue()) === markerText,
|
||||
"with the note that was written on the desktop",
|
||||
markerText
|
||||
);
|
||||
|
||||
const sheet = await p.locator("[data-pin-sheet]").boundingBox();
|
||||
check(
|
||||
sheet.y + sheet.height > 700,
|
||||
"at the bottom, where a thumb reaches",
|
||||
`top ${Math.round(sheet.y)} of 820`
|
||||
);
|
||||
|
||||
await p.getByRole("button", { name: "Fertig" }).click();
|
||||
await p.waitForTimeout(800);
|
||||
check((await p.locator("[data-pin-sheet]").count()) === 0, "and closes again");
|
||||
|
||||
await p.setViewportSize({ width: 1400, height: 950 });
|
||||
await p.waitForTimeout(2000);
|
||||
|
||||
/* ── the notes panel lives in the same toolbar now ── */
|
||||
check(
|
||||
(await p.locator("[data-pdf-toggle-notes]").count()) === 1,
|
||||
|
||||
Reference in new issue
Block a user