From 885a929bc446427ad36e997d08fc1694e13e5a39 Mon Sep 17 00:00:00 2001 From: SandObserver <260779319+SandObserver@users.noreply.github.com> Date: Fri, 16 Oct 2026 21:52:53 -0400 Subject: [PATCH] Keep Backup card text inside the card in RTL and lengthen large Books spines - Backup: anchor the canvas text at its left edge, so right-to-left lines no longer run past the card. - Books: on the three-shelf card, books stand 90 to 99% of the shelf, so spines show more of each title. --- ui/test/backup-names.test.mjs | 4 ++++ ui/test/books-spines.test.mjs | 25 +++++++++++++++++++++++++ ui/widgets/backup/backup.html | 1 + ui/widgets/books/index.html | 2 +- 4 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 ui/test/books-spines.test.mjs diff --git a/ui/test/backup-names.test.mjs b/ui/test/backup-names.test.mjs index 2423e34d..59b79da7 100644 --- a/ui/test/backup-names.test.mjs +++ b/ui/test/backup-names.test.mjs @@ -82,3 +82,7 @@ test('a cut name takes its direction from its own text', () => { test('an error line is fitted like a name', () => { assert.match(page, /if\(bname\) fitName\(bname, line\);/); }); + +test('decorative lines start at their left edge in every direction', () => { + assert.match(page, /ctx\.textAlign = 'left';\n\s*\/\* Small left dash \*\//); +}); diff --git a/ui/test/books-spines.test.mjs b/ui/test/books-spines.test.mjs new file mode 100644 index 00000000..b1ecba24 --- /dev/null +++ b/ui/test/books-spines.test.mjs @@ -0,0 +1,25 @@ +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import { test } from 'node:test'; + +const page = fs.readFileSync(new URL('../widgets/books/index.html', import.meta.url), 'utf8'); +const source = /function decorate\(books\)\{[\s\S]*?\n {2}\}/.exec(page)?.[0]; +const decorateFor = shelves => + new Function('SHELVES', 'hash', 'PALETTE', `${source}; return decorate;`)( + shelves, + s => [...s].reduce((h, c) => (Math.imul(h, 31) + c.charCodeAt(0)) >>> 0, 7), + ['#000'], + ); +const titles = Array.from({ length: 60 }, (_, i) => ({ title: `Book ${i} ${'x'.repeat(i % 13)}` })); +const heights = shelves => decorateFor(shelves)(titles).map(b => b._h); + +test('books on the three-shelf card stand at least 90% of the shelf', () => { + const h = heights(3); + assert.ok(Math.min(...h) >= 90 && Math.max(...h) <= 99, `${Math.min(...h)}-${Math.max(...h)}`); +}); + +test('one-shelf cards keep their varied heights', () => { + const h = heights(1); + assert.ok(Math.min(...h) >= 72 && Math.max(...h) <= 95); + assert.ok(Math.min(...h) < 90); +}); diff --git a/ui/widgets/backup/backup.html b/ui/widgets/backup/backup.html index 2865ca78..22963876 100644 --- a/ui/widgets/backup/backup.html +++ b/ui/widgets/backup/backup.html @@ -292,6 +292,7 @@ /* A line wider than the card is drawn as a plain row. Text is never clipped. */ if (entry.type === 'text' && textW + 7 <= usableW) { ctx.textBaseline = 'top'; + ctx.textAlign = 'left'; /* Small left dash */ const ldW = Math.min(4 + Math.floor(rng() * 10), usableW - textW - 3); ctx.fillStyle = dashC; diff --git a/ui/widgets/books/index.html b/ui/widgets/books/index.html index e34762c7..cd61854b 100644 --- a/ui/widgets/books/index.html +++ b/ui/widgets/books/index.html @@ -207,7 +207,7 @@ const h=hash(title+'|'+i); return Object.assign({}, b, { _w: Math.max(14,Math.min(19,12+Math.round(title.length*.35)))+(h%2), - _h: 72 + (h % 24), + _h: SHELVES>1 ? 90 + (h % 10) : 72 + (h % 24), _color: b.color || PALETTE[(h>>>4) % PALETTE.length], }); });