diff --git a/containers/libreportal/frontend/core/setup/js/setup-wizard.js b/containers/libreportal/frontend/core/setup/js/setup-wizard.js index 9baf39a..8e44419 100755 --- a/containers/libreportal/frontend/core/setup/js/setup-wizard.js +++ b/containers/libreportal/frontend/core/setup/js/setup-wizard.js @@ -28,6 +28,18 @@ class SetupWizard { // must not be asked for an install name, domains or an app list — the // backup already answers all three, and asking would invite someone to // contradict what is about to be restored over their answer. + // Tips that used to hang off a section heading. The heading itself is the + // progress bar's step name — repeating it as a title directly beneath was + // the same word twice with nothing between them — so the explanation moves + // to where the name actually is. + this.stepTips = { + Storage: 'Apps normally live on the system disk. If you have another drive, you can register it here and choose per app where its data goes.', + Backups: 'Snapshots of your apps and settings, taken on a schedule. Encrypted, so keep the password somewhere other than this machine \u2014 without it a backup cannot be opened, not even by us.', + Import: "Bring apps over from another LibrePortal using .lpapp files made with 'libreportal app export'. Point at a file or a folder of them, somewhere on this machine.", + Backup: 'Backups live in a repository \u2014 a folder on a disk, or a remote server. Not a single file, so point us at the repository itself.', + Contents: 'What this backup holds, and what it would mean on this machine.', + Rebuild: 'What is about to happen, and in what order.' + }; this.stepNames = ['Start', 'Experience', 'Identity', 'Domains', 'Storage', 'Backups', 'Import', 'Recommended', 'Metrics', 'Backup', 'Contents', 'Rebuild']; this.stepIcons = ['\u{1F9ED}', '\u{1F331}', '\u{1FA90}', '\u{1F6F0}\u{FE0F}', '\u{1F4BE}', '\u{1F6DF}', '\u{1F4E6}', '\u{1F6E1}\u{FE0F}', '\u{1F4CA}', @@ -358,9 +370,6 @@ class SetupWizard { otherwise there is exactly one answer and the step is skipped. -->
-
Storage - ? -
@@ -374,9 +383,6 @@ class SetupWizard { this asks rather than waiting to be found in the Backup page. -->
-
Backups - ? -
Destinations
@@ -390,9 +396,6 @@ class SetupWizard { browser (unlike a backup repository — see first-run-restore.md). -->
-
Import - ? -
Folder or file @@ -408,7 +411,6 @@ class SetupWizard {
-
Recommended Apps

Pre-selected to give you a working install out of the box.

@@ -432,7 +434,6 @@ class SetupWizard { is skipped when the user chose Beginner on step 1. -->
-
Metrics Apps

Optional. Install these to enable per-app "Export metrics to Grafana" later.

@@ -443,11 +444,6 @@ class SetupWizard {
-
Backup
-

- Where is it? Backups live in a repository \u2014 a folder on a - disk, or a remote server. Not a single file. -

@@ -468,8 +464,6 @@ class SetupWizard {
-
Contents
-

What this backup would bring back.

Go back a step and read the backup first.

@@ -479,8 +473,6 @@ class SetupWizard {
-
Rebuild
-

What is about to happen, and in what order.

@@ -2327,7 +2319,10 @@ class SetupWizard { const icon = this.stepIcons[actual]; this.container.querySelector('#sw-progress-fill').style.width = `${pct}%`; this.container.querySelector('#sw-progress-step').innerHTML = - `Step ${this.currentStep + 1} of ${this.totalSteps} ${icon} ${name}`; + `Step ${this.currentStep + 1} of ${this.totalSteps} ${icon} ${name}` + + (this.stepTips[name] + ? ` ?` + : ''); this.container.querySelector('#sw-progress-pct').textContent = `${pct}%`; this.container.querySelector('#sw-back').disabled = this.currentStep === 0; diff --git a/scripts/dev/lp-restore-wizard-test b/scripts/dev/lp-restore-wizard-test index 5c5380b..00205ce 100755 --- a/scripts/dev/lp-restore-wizard-test +++ b/scripts/dev/lp-restore-wizard-test @@ -32,7 +32,16 @@ lp_reachable() { read -r -d '' DRIVE <<'JS' const out = {}; const w = window.setupWizard; - if (!w) return JSON.stringify({ error: 'wizard handle missing' }); + if (!w) { + // Distinguish "the wizard is broken" from "the wizard is not on screen". + // It only renders while setup is incomplete, so a stray .setup_complete — + // which a previous test run can leave behind — hides it entirely, and + // "wizard handle missing" sends you looking for a JS error that is not + // there. + return JSON.stringify({ error: typeof window.SetupWizard === 'function' + ? 'the wizard did not open — setup is already marked complete on this install (remove frontend/data/.setup_complete)' + : 'wizard handle missing' }); + } const fire = (el, ev) => el.dispatchEvent(new Event(ev, { bubbles: true })); const $ = s => document.querySelector(s); const visible = () => w.stepNames.filter((n, i) => w._stepVisible(i)); @@ -56,10 +65,17 @@ read -r -d '' DRIVE <<'JS' out.startAlwaysShown = out.newSteps[0] === 'Start' && out.restoreSteps[0] === 'Start'; // The source form offers every backend, and shows only the chosen one. - // The progress bar said "Source" while the heading said "Where is your - // backup?", which read as two different steps. They have to agree. - const sec = document.querySelector('.setup-step[data-step="9"] .setup-section-title'); - out.titleMatchesStepName = !!sec && sec.textContent.trim() === 'Backup'; + // The step's name lives in the progress bar. Repeating it as a heading + // directly beneath was the same word twice with nothing between them, so + // there are no section headings left in the wizard at all — and what those + // headings explained moved to a tooltip beside the name, which is where the + // name is. + out.noSectionHeadingsAnywhere = document.querySelectorAll('.setup-section-title').length; + w.showStep(1); + const bar = document.querySelector('#sw-progress-step'); + out.progressShowsTheStepName = + (bar.querySelector('.setup-progress-name') || {}).textContent === 'Backup'; + out.progressCarriesTheTip = !!bar.querySelector('.setup-tooltip'); // Fields laid out like the rest of the wizard: label with a tooltip, and an // icon beside the input — not the label-left rows the Storage step uses. out.fieldsHaveIcons = document.querySelectorAll('#sw-rs-fields .setup-field-icon').length > 0; @@ -320,7 +336,9 @@ chk "restore shows no install steps" "$(g .restoreLeaksNewStep)" false chk "new shows no restore steps" "$(g .newLeaksRestoreStep)" false chk "restore step set" "$(g '.restoreSteps | join(",")')" "Start,Backup,Contents,Rebuild" -chk "step name matches its title" "$(g .titleMatchesStepName)" true +chk "no section headings anywhere" "$(g .noSectionHeadingsAnywhere)" 0 +chk "the progress bar names the step" "$(g .progressShowsTheStepName)" true +chk "and carries its explanation" "$(g .progressCarriesTheTip)" true echo "the backup source form" chk "every backend offered" "$(g '.kinds | join(",")')" "local,sftp,rest,s3,b2" diff --git a/scripts/dev/lp-storage-step-test b/scripts/dev/lp-storage-step-test index ee39fb1..e1f258c 100755 --- a/scripts/dev/lp-storage-step-test +++ b/scripts/dev/lp-storage-step-test @@ -43,7 +43,16 @@ lp_reachable() { read -r -d '' DRIVE <<'JS' const out = {}; const w = window.setupWizard; - if (!w) return JSON.stringify({ error: 'wizard handle missing' }); + if (!w) { + // Distinguish "the wizard is broken" from "the wizard is not on screen". + // It only renders while setup is incomplete, so a stray .setup_complete — + // which a previous test run can leave behind — hides it entirely, and + // "wizard handle missing" sends you looking for a JS error that is not + // there. + return JSON.stringify({ error: typeof window.SetupWizard === 'function' + ? 'the wizard did not open — setup is already marked complete on this install (remove frontend/data/.setup_complete)' + : 'wizard handle missing' }); + } const fire = (el, ev) => el.dispatchEvent(new Event(ev, { bubbles: true })); // Looked up by name, never hardcoded. Inserting the Start step moved Storage // from 3 to 4, and a test pinned to the old number silently began validating