Write notes beside a PDF

Stage six. What you write goes into the note's ordinary content, so it is
encrypted, versioned, linkable and searchable like any other note — a PDF
note is a note with a document attached, not a separate kind of thing.

The panel is composed from the editor pieces rather than reusing
NoteEditor, which carries a whole drawing mode with its own canvas, tools
and undo stack. The PDF already has a drawing layer; a second one behind
the panel would be two places to draw with one of them invisible.

Side by side on a wide screen, two tabs on a narrow one, and either half
can be hidden — the second of the two toggles asked for. Reading the
original without one's own marks in the way is a normal thing to want.

The suite caught a real defect while checking this. A PDF note has two
sources of searchable text — what the reader typed and what the document
says — written at different moments by different code, both calling
setEntry. Whichever ran last was the only searchable half: typing a note
dropped the document's text, and reopening the document dropped the note.
The index now holds the two apart and replaces only the half it is given,
with six unit tests covering each direction, plus the snippet falling back
to the document so a hit inside a PDF still shows its sentence.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
marcandClaude Opus 5 committed 2026-08-08 21:29:39 +02:00
1 parent b12b0d6142
commit 7f9f39790f
8 files changed
+409 -16

No files matched your search

+40
View File
@@ -236,6 +236,46 @@ try {
await p.waitForTimeout(9000);
check((await inkOnPage(1)) > 100, "and is still there after the sync poller has run");
/* ── notes beside the document ── */
check((await p.locator("[data-pdf-notes]").count()) === 0, "the notes panel starts folded away");
await p.locator("[data-pdf-toggle-notes]").click();
await p.waitForTimeout(1200);
check((await p.locator("[data-pdf-notes]").count()) === 1, "it opens beside the document");
check((await p.locator("[data-pdf-page]").count()) > 0, "and the document is still there next to it");
const noteText = "Wichtig auf Seite eins: Randbemerkung.";
await p.locator("[data-pdf-notes] .ProseMirror").click();
await p.keyboard.type(noteText);
await p.waitForTimeout(2200);
await p.locator("[data-pdf-toggle-notes]").click();
await p.waitForTimeout(600);
check((await p.locator("[data-pdf-notes]").count()) === 0, "and folds away again without losing anything");
/* Written notes belong to the note, so they survive like any other text. */
await p.reload({ waitUntil: "networkidle" });
await p.waitForTimeout(2500);
await p.locator("button").filter({ hasText: "PDF Buch" }).first().click();
await p.waitForTimeout(1200);
await p.locator("[data-note-row]").first().click();
await p.waitForTimeout(4000);
await p.locator("[data-pdf-toggle-notes]").click();
await p.waitForTimeout(1500);
check(
(await p.locator("[data-pdf-notes] .ProseMirror").innerText()).includes("Randbemerkung"),
"the notes survived a reload"
);
await p.screenshot({ path: "/out/pdf-with-notes.png" });
/* And they are searchable, alongside the document's own text. */
await p.getByPlaceholder("Search notes...").fill("Randbemerkung");
await p.waitForTimeout(1500);
check((await p.locator("[data-note-row]").count()) === 1, "notes written beside a PDF are searchable");
await p.getByPlaceholder("Search notes...").fill("");
await p.waitForTimeout(1000);
await p.locator("[data-note-row]").first().click();
await p.waitForTimeout(3000);
/* ── it stays readable offline ── */
// A reload lands on the dashboard, which has no note list; the notebook has
// to be opened first before a row exists to click.