From ba6791455b16d6f05f9d1ee9c6c142aa92ceb852 Mon Sep 17 00:00:00 2001 From: Andy Kopra Date: Sun, 11 Oct 2026 17:10:08 +0200 Subject: [PATCH] =?UTF-8?q?The=20chooser's=20marks=20are=20=C3=97=20at=20t?= =?UTF-8?q?he=20left,=20tappable=20once=20marked?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From the development repository at 5252a58. Co-Authored-By: Claude Fable 5.1 --- client/js/chooser.js | 12 +++++++----- client/kokkoja.css | 8 ++++++-- docs/guest.md | 2 +- docs/host.md | 2 +- tests/test_end_to_end.py | 9 +++++++++ 5 files changed, 24 insertions(+), 9 deletions(-) diff --git a/client/js/chooser.js b/client/js/chooser.js index 29a1117..2136828 100644 --- a/client/js/chooser.js +++ b/client/js/chooser.js @@ -4,8 +4,10 @@ * notebooks and the pages kept in them, and loads the chosen page. * * It also deletes (Andy, 2026-10-11): every notebook and every page card - * carries a mark at its left, a dot that a tap turns into a tick (not a - * × at the right, which in many interfaces means "delete" itself), and one "Delete" + * carries a mark at its left, a × that a tap turns into a tick (at the + * left, since a × at the top right means "delete" itself in many + * interfaces; a dot was tried and implied a generality that does not + * exist), and one "Delete" * button in the header, which appears as soon as something is marked, * says how much is marked and asks for a second press before anything * goes, as the file chooser does for replacing a file; no system dialog. @@ -44,7 +46,7 @@ function counted(count, noun) { } /** - * A mark button: a dot until marked, then ✓. + * A mark button: × until marked, then ✓. * @param {string} help The tooltip. * @returns {HTMLButtonElement} */ @@ -52,7 +54,7 @@ function markButton(help) { const mark = document.createElement('button'); mark.type = 'button'; mark.className = 'mark'; - mark.textContent = '•'; + mark.textContent = '×'; mark.setAttribute('aria-label', 'Mark for deletion'); mark.setAttribute('aria-pressed', 'false'); mark.dataset.help = help; @@ -225,7 +227,7 @@ export class NotebookChooser { toggleMark(mark, marked, name) { const now = !marked.has(name); if (now) marked.add(name); else marked.delete(name); - mark.textContent = now ? '✓' : '•'; + mark.textContent = now ? '✓' : '×'; mark.setAttribute('aria-pressed', String(now)); /** @type {HTMLElement} */ (mark.parentElement).classList.toggle('marked', now); this.disarm(); diff --git a/client/kokkoja.css b/client/kokkoja.css index de30a8b..432d149 100644 --- a/client/kokkoja.css +++ b/client/kokkoja.css @@ -439,8 +439,8 @@ h2 { .chooser_notebooks button.notebook_pick { flex: 1; min-width: 0; text-align: left; } -/* Rows and cards carry a mark at their left, a dot, for deletion (a × - at the right would read as "delete" itself, Andy); marked, they dim. */ +/* Rows and cards carry a mark at their left, ×, for deletion (at the + right it would read as "delete" itself, Andy); marked, they dim. */ .notebook_row { display: flex; gap: 0.3rem; align-items: stretch; } .notebook_row.marked button.notebook_pick, .page_slot.marked .page_card { opacity: 0.45; } .page_slot { position: relative; display: flex; } @@ -458,6 +458,10 @@ button.mark { left: 0.3rem; height: 1.6rem; background: white; + /* Above the card even when the card's opacity makes it a stacking + context painted later: without this, the second tap, to unmark, + landed on the dimmed card and loaded the page. */ + z-index: 1; } button.mark[aria-pressed="true"] { color: #b42318; } button.mark.unavailable { opacity: 0.4; } diff --git a/docs/guest.md b/docs/guest.md index 156229c..553b37e 100644 --- a/docs/guest.md +++ b/docs/guest.md @@ -304,7 +304,7 @@ right of your toolbar: your notebooks, with everything that was written on it. A chooser shows your notebooks and small pictures of their kept pages. The kept page itself is not changed: the session works on a copy. The same - chooser deletes pages and notebooks of yours, marked with the dot at + chooser deletes pages and notebooks of yours, marked with the × at their left and then the **Delete** button, pressed twice; the host's part says more. * **Hide**, among the tools, hides part of the page for everyone: drag diff --git a/docs/host.md b/docs/host.md index b7daa5a..22a08d7 100644 --- a/docs/host.md +++ b/docs/host.md @@ -248,7 +248,7 @@ A lesson might go: **New**, **Image** for the exercise, work, **New** for a point of grammar, work, **Notebooks** to return to the exercise. The same chooser deletes. Every page card and every notebook in the -list carries a small dot at its left; a tap marks it with a tick, a second tap unmarks it, +list carries a small × at its left; a tap marks it with a tick, a second tap unmarks it, and a marked notebook goes with every page in it. As soon as something is marked, a **Delete** button in the chooser's header says how much: "Delete 3 pages?", "Delete 1 notebook and 3 pages?". The first press diff --git a/tests/test_end_to_end.py b/tests/test_end_to_end.py index 4addccb..5b8c56f 100644 --- a/tests/test_end_to_end.py +++ b/tests/test_end_to_end.py @@ -2188,6 +2188,15 @@ class EndToEndTest(unittest.TestCase): self.assertEqual(firefox.run(""" return [...document.querySelectorAll('#chooser_pages .page_slot')].map((s) => s.classList.contains('marked')); """)[:3], [True, True, False]) + # A marked card is dimmed, and its mark must still be what a tap + # at the mark's place hits, so that the second tap unmarks and + # does not load the page (a scripted click would not tell). + self.assertEqual(firefox.run(""" + return [...document.querySelectorAll('#chooser_pages .page_slot')].slice(0, 3).map((slot) => { + const r = slot.querySelector('button.mark').getBoundingClientRect(); + return document.elementFromPoint(r.left + r.width / 2, r.top + r.height / 2).classList.contains('mark'); + }); + """), [True, True, True]) firefox.run("document.getElementById('chooser_delete').click();") self.assertEqual(delete(), "Press again to move 2 pages to the Trash") # Marking something else takes the first press back.