Show the confirmed backup as a card, not a sentence

Moving the results under the Folder field kept the placement but threw away the
presentation: the card became a line of prose. Path, snapshot count and age
read better as a card, and that is what the found list already used.

One renderer now serves both. The same fact should not look like two different
things depending on whether it came from the scan or from pressing Check — only
whether it is still a choice differs, so a pickable one is a <button> and a
confirmed one is a <div> with a quiet accent edge and no hover. A card that
looks clickable but is not is worse than one that does not.

The geometry moved off the button-only selector for the same reason; the
confirmed card is a div and was collapsing without it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
librelad 2026-08-29 07:34:55 +01:00
parent 7fef102369
commit 853e51baac
3 changed files with 48 additions and 15 deletions

View File

@ -1570,7 +1570,8 @@ body.setup-wizard-open .custom-select-popup { z-index: 10001; }
It cannot borrow .setup-app-card's look, because that class carries no It cannot borrow .setup-app-card's look, because that class carries no
layout of its own it is a bare wrapper elsewhere, and a <button> with no layout of its own it is a bare wrapper elsewhere, and a <button> with no
layout collapses to a single cramped line. So this owns its geometry. */ layout collapses to a single cramped line. So this owns its geometry. */
button.setup-found-backup { button.setup-found-backup,
.setup-found-backup {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px; gap: 10px;
@ -1586,8 +1587,8 @@ button.setup-found-backup {
background: rgba(var(--text-rgb), 0.05); background: rgba(var(--text-rgb), 0.05);
transition: border-color 0.15s ease, background 0.15s ease, transform 0.08s ease; transition: border-color 0.15s ease, background 0.15s ease, transform 0.08s ease;
} }
button.setup-found-backup .setup-app-name { font-weight: 600; } .setup-found-backup .setup-app-name { font-weight: 600; }
button.setup-found-backup .setup-app-desc { .setup-found-backup .setup-app-desc {
margin-left: auto; margin-left: auto;
font-size: 0.85em; font-size: 0.85em;
opacity: 0.72; opacity: 0.72;
@ -1603,6 +1604,15 @@ button.setup-found-backup:focus-visible {
outline-offset: 2px; outline-offset: 2px;
} }
@media (max-width: 640px) { @media (max-width: 640px) {
button.setup-found-backup { flex-wrap: wrap; } .setup-found-backup { flex-wrap: wrap; }
button.setup-found-backup .setup-app-desc { margin-left: 0; width: 100%; } .setup-found-backup .setup-app-desc { margin-left: 0; width: 100%; }
}
/* The confirmed repository: the same card as a pickable one, but it is not a
choice any more no hover, no pointer, and a quiet accent edge so it reads
as settled rather than waiting to be clicked. */
.setup-found-chosen {
cursor: default;
border-color: rgba(79, 195, 247, 0.42);
background: rgba(79, 195, 247, 0.07);
} }

View File

@ -1114,6 +1114,24 @@ class SetupWizard {
this.renderFoundBackups(); this.renderFoundBackups();
} }
// One repository, rendered. Used both for the list of what was found and for
// the answer to Check: it is the same fact either way, and it should not look
// like two different things depending on how it was arrived at.
//
// `tag` says which of the two it is, so a button is only a button when
// there is actually something to choose.
_backupCard(f, tag) {
const badge = `${f.snapshots} snapshot${f.snapshots === 1 ? '' : 's'}`;
const when = f.newest ? `newest ${this._restoreWhen(f.newest)}` : '';
const body = `
<span class="setup-app-name">${this.escapeHtml(f.path)}</span>
<span class="setup-storage-badge setup-storage-badge-ok">${badge}</span>
<span class="setup-app-desc">${when}</span>`;
return tag === 'pick'
? `<button type="button" class="setup-app-card setup-found-backup" data-found="${f._i}">${body}</button>`
: `<div class="setup-app-card setup-found-backup setup-found-chosen">${body}</div>`;
}
renderFoundBackups() { renderFoundBackups() {
const box = this.container.querySelector('#sw-rs-found'); const box = this.container.querySelector('#sw-rs-found');
if (!box) return; if (!box) return;
@ -1138,12 +1156,7 @@ class SetupWizard {
box.innerHTML = box.innerHTML =
`<p class="setup-section-hint">Backups found on this machine \u2014 pick one:</p>` + `<p class="setup-section-hint">Backups found on this machine \u2014 pick one:</p>` +
found.map((f, i) => ` found.map((f, i) => this._backupCard(Object.assign({ _i: i }, f), 'pick')).join('');
<button type="button" class="setup-app-card setup-found-backup" data-found="${i}">
<span class="setup-app-name">${this.escapeHtml(f.path)}</span>
<span class="setup-storage-badge setup-storage-badge-ok">${f.snapshots} snapshot${f.snapshots === 1 ? '' : 's'}</span>
<span class="setup-app-desc">${f.newest ? 'newest ' + this._restoreWhen(f.newest) : ''}</span>
</button>`).join('');
this._adoptSingleResult(); this._adoptSingleResult();
@ -1219,10 +1232,8 @@ class SetupWizard {
const box = this.container.querySelector('#sw-rs-verify-result'); const box = this.container.querySelector('#sw-rs-verify-result');
if (!box) return; if (!box) return;
if (d && d.repo) { if (d && d.repo) {
box.innerHTML = `<p class="setup-rs-ok">Backup found \u2014 box.innerHTML = this._backupCard(d, 'chosen') +
<strong>${d.snapshots} snapshot${d.snapshots === 1 ? '' : 's'}</strong>${ `<p class="setup-section-hint">Enter its password below to see what is inside.</p>`;
d.newest ? `, newest ${this._restoreWhen(d.newest)}` : ''}.
Enter its password to see what is inside.</p>`;
return; return;
} }
// A suggestion means they pointed at the folder holding the repositories // A suggestion means they pointed at the folder holding the repositories

View File

@ -143,6 +143,15 @@ read -r -d '' DRIVE <<'JS'
w.renderFoundBackups(); w.renderFoundBackups();
out.singlePrefillsThePath = ($('#sw-rs-path') || {}).value === '/only/one'; out.singlePrefillsThePath = ($('#sw-rs-path') || {}).value === '/only/one';
out.singleShowsItsVerdict = /4 snapshots/.test(($('#sw-rs-verify-result') || {}).textContent || ''); out.singleShowsItsVerdict = /4 snapshots/.test(($('#sw-rs-verify-result') || {}).textContent || '');
// Rendered as the same CARD the found list uses, not as a sentence. It is
// the same fact either way and should not look like two different things
// depending on how it was arrived at.
const chosen = $('#sw-rs-verify-result .setup-found-backup');
out.singleRendersAsACard = !!chosen;
out.singleCardCarriesTheBadge = !!(chosen && chosen.querySelector('.setup-storage-badge'));
// Not a button: there is nothing left to choose, and a card that looks
// clickable but is not is worse than one that does not.
out.singleCardIsNotClickable = !!chosen && chosen.tagName !== 'BUTTON';
// The card and the verdict would otherwise say the same thing twice. // The card and the verdict would otherwise say the same thing twice.
out.singleDrawsNoDuplicateCard = $('#sw-rs-found .setup-found-backup') === null; out.singleDrawsNoDuplicateCard = $('#sw-rs-found .setup-found-backup') === null;
// Pressing Check must confirm, not reply "give a full path" about the backup // Pressing Check must confirm, not reply "give a full path" about the backup
@ -306,6 +315,9 @@ chk "so does the verdict" "$(g .verdictInsideTheGroup)" true
echo " one result is the answer, not a choice" echo " one result is the answer, not a choice"
chk "it fills the path in" "$(g .singlePrefillsThePath)" true chk "it fills the path in" "$(g .singlePrefillsThePath)" true
chk "and shows its verdict" "$(g .singleShowsItsVerdict)" true chk "and shows its verdict" "$(g .singleShowsItsVerdict)" true
chk "as a card, not a sentence" "$(g .singleRendersAsACard)" true
chk "carrying the snapshot badge" "$(g .singleCardCarriesTheBadge)" true
chk "and not looking clickable" "$(g .singleCardIsNotClickable)" true
chk "without a duplicate card" "$(g .singleDrawsNoDuplicateCard)" true chk "without a duplicate card" "$(g .singleDrawsNoDuplicateCard)" true
chk "Check confirms, not scolds" "$(g .singleCheckDoesNotScold)" true chk "Check confirms, not scolds" "$(g .singleCheckDoesNotScold)" true