Patterns on the covers, a search box that is simply there, roomier margin notes

Four small things about the shape of the app.

Covers can be patterns as well as colours — ruled paper, graph paper,
pinstripe, bands, dots, cross-hatch, linen, washes, a ledger — drawn
entirely with CSS gradient layers. No image files, nothing to upload,
crisp at any size, and offline like everything else. A cover is stored as
an index into that list, so the new ones are appended and never inserted:
renumbering would silently repaint every notebook anyone already has.

Two things the patterns taught. One idea per cover: two crossed diagonals
meant as a chevron came out as noisy plaid at the size a cover is
actually read, and were replaced with plain bands. And the grid has to
scroll — with 27 covers the dialog ran past the bottom of a laptop screen
and took the Create button with it, which a check now holds at 1440x800.

The search box in a notebook is simply there now, and the button that
used to open it is gone. A magnifying glass cost a click and having to
remember that searching was possible at all; a search box is one line and
says for itself what it does.

Margin notes went from 208x74 to 288x112 — from three short lines to
about five. Three lines is a caption, not a remark on a passage, and the
space beside the page was sitting mostly empty.

And the notebook edit dialog was still part German, and would not say
what the server's default number of versions actually is. It names it
now, read from the server rather than written into the interface: it is
set with MAX_VERSIONS_PER_NOTE, and a hardcoded "50" would quietly start
lying the day that changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
marcandClaude Opus 5 committed 2026-08-12 00:29:00 +02:00
1 parent 635a9632d1
commit 9e4677c70b
11 files changed
+262 -48

No files matched your search

+2 -7
View File
@@ -184,14 +184,9 @@ export async function createNote(page) {
await page.waitForTimeout(1200);
}
/** Opens the search box in the note list and types into it. */
/** Types into the note list's search box, which is always there. */
export async function search(page, query) {
const box = page.locator('input[aria-label="Search in this notebook"]');
if ((await box.count()) === 0) {
await page.locator("[data-search-toggle]").click();
await page.waitForTimeout(400);
}
await box.fill(query);
await page.locator('input[aria-label="Search in this notebook"]').fill(query);
await page.waitForTimeout(1300);
}
+30
View File
@@ -107,6 +107,18 @@ try {
`${beforeDrag.join(",")} → ${arranged.join(",")}`
);
/* The search box is simply there, with nothing to open first. A magnifying
* glass costs a click and having to remember that searching is possible; the
* box is one line and says for itself what it does. */
check(
(await p.locator('input[aria-label="Search in this notebook"]').count()) === 1,
"the search box is there without opening anything"
);
check(
(await p.locator("[data-search-toggle]").count()) === 0,
"and the button that used to open it is gone"
);
/* The point of the whole feature. */
await search(p, "Beta");
const searched = await titles();
@@ -371,6 +383,24 @@ try {
"and it lands in the notebook that was chosen, not the first one",
`${fresh?.notebook_id} vs ${target.id}`
);
/* ── choosing a cover ──
* Covers are patterns as well as colours now, and there are enough of them
* that the grid has to scroll: without that the dialog runs past the bottom of
* a laptop screen and takes the Create button with it. */
await p.setViewportSize({ width: 1440, height: 800 });
await p.locator("button").filter({ hasText: "Dashboard" }).first().click();
await p.waitForTimeout(600);
await p.getByRole("button", { name: "Add Notebook" }).click();
await p.waitForTimeout(700);
const covers = p.locator('[data-cover-option]');
check((await covers.count()) > 20, "there are patterned covers as well as plain ones", `${await covers.count()} covers`);
check(
await p.getByRole("button", { name: "Create", exact: true }).isVisible(),
"and the dialog still fits a laptop screen, Create and all"
);
await p.getByRole("button", { name: "Cancel" }).click();
await p.waitForTimeout(400);
} catch (e) {
check(false, "suite ran to the end", e.message);
} finally {