diff --git a/CHANGELOG.md b/CHANGELOG.md index 5384c98..a5d09c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,34 @@ not from the output of `docker push`. --- +## 2026-08-17 — Three things that only showed on screen + +**Images** — web `sha256:PENDING`, server `sha256:PENDING` + +No backend source changed this round. + +**Fixed** +- **Grey letters ran down the bar while dragging a column edge.** The editor + marks the column under the pointer by putting a small div in each of its + cells; drawn along the cell's right edge, that absolutely positioned child + sent Chromium down another painting path and the cell's own text came out a + second time, squeezed into the two pixels under the bar, one letter to a line. + It appeared with the bar and went with it, which is what named the culprit — + measured, there was no text there at all, only three empty markers. The marker + is never drawn now; it only says which cells the column is made of, and the + line is an inset shadow on those cells. +- **Opening a note flashed a long narrow page.** The drawing stage starts at the + canonical page, 1600px tall, and the measurement that fits it to the pane ran + as an ordinary effect — a frame too late. Every note switch drew one frame at + the wrong size and then righted itself. Measured before the first paint now. +- **The mark above a note did not stand in the same column as the tools below + it.** Vertically it was already exact; sideways it began six pixels right of + the first tool, and the title thirty. The mark's box is only as wide as the + mark, so the mark itself lands on that column, and the title follows nine + pixels closer. + +--- + ## 2026-08-17 — A chapter with an order of its own **Images** — web `sha256:b7c9f8b7…`, server `sha256:a0fd140a…` diff --git a/e2e/editor.mjs b/e2e/editor.mjs index d174b3f..080240c 100644 --- a/e2e/editor.mjs +++ b/e2e/editor.mjs @@ -131,6 +131,35 @@ try { ); check((await p.locator(".ProseMirror").innerText()).includes("Grundtext"), "plain text survived a reload"); await p.screenshot({ path: `${OUT}/editor-after-reload.png` }); + + /* ── the sheet is the right size from the first frame ── + * It was not. The stage starts at the canonical page, 1600px tall, and the + * measurement that pulls it back to the pane ran an ordinary effect — a frame + * too late. Every note opened by flashing a long narrow sheet and then + * righting itself. Sampled every frame, because one frame is all it was. */ + const settled = await p.locator("[data-page-sheet]").first().evaluate((el) => el.getBoundingClientRect().height); + await p.evaluate(() => { + window.__tall = 0; + const tick = () => { + const el = document.querySelector("[data-page-sheet]"); + if (el) window.__tall = Math.max(window.__tall, el.getBoundingClientRect().height); + window.__raf = requestAnimationFrame(tick); + }; + tick(); + }); + await p.locator("button").filter({ hasText: "Ziel" }).first().click(); + await p.waitForSelector(".ProseMirror", { timeout: 15000 }); + await p.waitForTimeout(1500); + const tallest = await p.evaluate(() => { + cancelAnimationFrame(window.__raf); + return window.__tall; + }); + check( + tallest <= settled * 1.15, + "switching notes never draws the sheet at the wrong size, not even for a frame", + `settled ${Math.round(settled)}, tallest ${Math.round(tallest)}` + ); + await reopenNote(p, "Quelle"); /* ── how many pages the writing fills ── */ // Pages are a reading aid, not a change to the document: the note stays one // body, and Markdown has no page. The height of one is the same 3:4 the canvas diff --git a/e2e/tables.mjs b/e2e/tables.mjs index 5c072ad..89ddc4c 100644 --- a/e2e/tables.mjs +++ b/e2e/tables.mjs @@ -120,6 +120,38 @@ try { }); check(!/ne-page-spacer/.test(body || ""), "and no page furniture reaches the stored note"); check(/table/.test(body || ""), "which does hold the table itself", String(body || "").slice(0, 60)); + + /* ── the edge being dragged is drawn on the cell, not in it ── + * The editor marks the column under the pointer with a small div in each of + * its cells. Positioned along the cell's right edge it painted the cell's own + * text a second time, squeezed into the two pixels under the bar, one letter + * to a line — an absolutely positioned child inside a collapsed-border table + * cell sends Chromium down another painting path. The div is never drawn now; + * it only says which cells the column is made of. */ + const cell = await p.locator(".ProseMirror table th").first().boundingBox(); + await p.mouse.move(cell.x + cell.width - 1, cell.y + cell.height / 2); + await p.waitForTimeout(500); + const edge = await p.evaluate(() => { + const marks = [...document.querySelectorAll(".column-resize-handle")]; + const host = marks[0]?.parentElement; + return { + marks: marks.length, + drawn: marks.map((m) => getComputedStyle(m).display), + onCell: host ? getComputedStyle(host).boxShadow : "", + painted: marks.every((m) => m.getBoundingClientRect().width === 0), + }; + }); + check(edge.marks > 0, "holding over a column edge marks that column", JSON.stringify(edge.marks)); + check( + edge.drawn.every((d) => d === "none") && edge.painted, + "the marker itself is never painted", + JSON.stringify(edge.drawn) + ); + check( + /inset/.test(edge.onCell) && edge.onCell !== "none", + "and the line is drawn on the cell that carries it", + edge.onCell + ); } catch (e) { check(false, "suite ran to the end", e.message); } finally { diff --git a/frontend/src/editor/editor.css b/frontend/src/editor/editor.css index 31d0a2c..551cc5a 100644 --- a/frontend/src/editor/editor.css +++ b/frontend/src/editor/editor.css @@ -225,15 +225,27 @@ background: color-mix(in srgb, var(--ne-accent) 18%, transparent); pointer-events: none; } -/* Drag handle between columns */ +/* The edge being dragged, drawn *on the cell* rather than in it. + * + * The editor marks the column under the pointer by putting a small div in each + * cell of it, and the obvious way to show that is to position the div along the + * cell's right edge. Which worked, and painted grey letters down the bar: an + * absolutely positioned child inside a cell of a `border-collapse: collapse` + * table sends Chromium down another painting path, and the cell's own text came + * out a second time, squeezed into the two pixels under the bar — one letter to + * a line. It appeared with the bar and went with it, which is what named the + * culprit. + * + * So the div is never drawn at all; it only marks which cells belong to the + * column, and the line is an inset shadow on those cells. Nothing is positioned, + * nothing paints twice, and the result is the same line — a continuous one down + * the column, because every cell in it carries its own piece. */ .note-editor .ProseMirror .column-resize-handle { - position: absolute; - right: -2px; - top: 0; - bottom: -2px; - width: 4px; - background: var(--ne-accent); - pointer-events: none; + display: none; +} +.note-editor .ProseMirror td:has(> .column-resize-handle), +.note-editor .ProseMirror th:has(> .column-resize-handle) { + box-shadow: inset -2px 0 0 var(--ne-accent); } .note-editor .ProseMirror.resize-cursor { cursor: col-resize; diff --git a/frontend/src/features/notes/EditorPane.jsx b/frontend/src/features/notes/EditorPane.jsx index 16f25d3..65f7caa 100644 --- a/frontend/src/features/notes/EditorPane.jsx +++ b/frontend/src/features/notes/EditorPane.jsx @@ -188,7 +188,10 @@ export function EditorPane({ display: "flex", alignItems: "center", gap: 8, - padding: "8px 16px", + /* The same inset as the toolbar under it. The mark used to start six + pixels right of the first tool below it, so the two rows did not + share a left edge and the title read as pushed across. */ + padding: "8px 12px", borderBottom: `1px solid ${T.borderLight}`, }} > @@ -204,21 +207,24 @@ export function EditorPane({ its box and sat on the middle. Measured, the mark ended up 2px high and dipped below the line the title never crossed. Sharing a baseline is what "on one line" means, and it needs no number. */} -
+