mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Hold the settings-restore prompt until the onboarding tour is done
The "restore storage/wallet from Nostr" confirm was popping over the guided tour (both fire on login). Gate the restore dialog behind a "tour gate": - maybeOfferOnboardingTour opens the gate synchronously when the tour will run (shouldOfferOnboarding), before its own start delay — so ordering can't race. The tour offer is now called before the restore offer on login. - launchBitvidTour gains an onEnd(status) hook (fires on finish AND skip); startOnboardingTour closes the gate on end, and also closes it if the tour never starts (logged out, no eligible steps) so restore can't hang. - The restore prompt's confirm now awaits whenOnboardingTourInactive() before showing — the remote existence check still runs in parallel, only the dialog is held back. No tour → gate stays closed → restore behaves as before. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
0e2f81d222
commit
c562d9ee65
2 files changed
+75
-8
No files matched your search
@@ -102,6 +102,8 @@ import {
|
||||
import { resolveSignerCapabilities } from "./nostr/signerCapabilities.js";
|
||||
import { rankHashtagsByFrequency } from "./utils/hashtagSuggestions.js";
|
||||
import { launchBitvidTour } from "./ui/onboarding/bitvidTour.js";
|
||||
import { shouldOfferOnboarding } from "./services/onboardingService.js";
|
||||
import { showConfirm } from "./ui/confirmDialog.js";
|
||||
import {
|
||||
decideSignerEnsure,
|
||||
isSignerCapable,
|
||||
@@ -1759,13 +1761,40 @@ class Application {
|
||||
}
|
||||
|
||||
if (pubkey) {
|
||||
// Best-effort, never blocks login: offer to restore encrypted settings
|
||||
// synced from another device.
|
||||
this.maybeOfferSettingsRestore(pubkey);
|
||||
// Order matters: offer the tour FIRST so it opens the "tour gate"
|
||||
// synchronously when it will run. The settings-restore prompt then waits
|
||||
// on that gate before showing its dialog, so it can't pop over the tour.
|
||||
this.maybeOfferOnboardingTour(pubkey);
|
||||
this.maybeOfferSettingsRestore(pubkey);
|
||||
}
|
||||
}
|
||||
|
||||
// A gate the settings-restore prompt awaits so its confirmation dialog never
|
||||
// pops over the onboarding tour. Opened synchronously when the tour will run,
|
||||
// resolved when the tour finishes/exits (or fails to start).
|
||||
_openTourGate() {
|
||||
if (this._tourGateResolve) {
|
||||
return;
|
||||
}
|
||||
this._tourGate = new Promise((resolve) => {
|
||||
this._tourGateResolve = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
_closeTourGate() {
|
||||
const resolve = this._tourGateResolve;
|
||||
this._tourGateResolve = null;
|
||||
this._tourGate = null;
|
||||
if (typeof resolve === "function") {
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
|
||||
// Resolves immediately unless the onboarding tour is (about to be) active.
|
||||
whenOnboardingTourInactive() {
|
||||
return this._tourGate || Promise.resolve();
|
||||
}
|
||||
|
||||
// First login of this pubkey on this device: offer the guided tour
|
||||
// (docs/onboarding-plan.md). Deferred so the feed has rendered and the
|
||||
// settings-restore prompt (2s window; only fires for accounts with synced
|
||||
@@ -1775,14 +1804,26 @@ class Application {
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
// If the tour will actually run, open the gate NOW (synchronously) so the
|
||||
// settings-restore prompt waits — even though the tour itself starts on a
|
||||
// delay. If it won't run, leave the gate closed so restore proceeds normally.
|
||||
if (!shouldOfferOnboarding(key)) {
|
||||
return;
|
||||
}
|
||||
this._openTourGate();
|
||||
setTimeout(() => {
|
||||
try {
|
||||
if (!this.isUserLoggedIn()) {
|
||||
this._closeTourGate();
|
||||
return;
|
||||
}
|
||||
this.startOnboardingTour({ pubkey: key, force: false });
|
||||
const started = this.startOnboardingTour({ pubkey: key, force: false });
|
||||
if (!started) {
|
||||
this._closeTourGate();
|
||||
}
|
||||
} catch (error) {
|
||||
devLogger.warn("[Application] Onboarding tour offer failed:", error);
|
||||
this._closeTourGate();
|
||||
}
|
||||
}, 3500);
|
||||
}
|
||||
@@ -1795,7 +1836,7 @@ class Application {
|
||||
if (!key) {
|
||||
return false;
|
||||
}
|
||||
return launchBitvidTour({
|
||||
const started = launchBitvidTour({
|
||||
pubkey: key,
|
||||
force,
|
||||
openProfilePane: (pane) => {
|
||||
@@ -1805,7 +1846,13 @@ class Application {
|
||||
devLogger.warn("[Application] Tour deep-link failed:", error);
|
||||
}
|
||||
},
|
||||
// Release the settings-restore prompt once the tour is done/exited.
|
||||
onEnd: () => this._closeTourGate(),
|
||||
});
|
||||
if (!started) {
|
||||
this._closeTourGate();
|
||||
}
|
||||
return started;
|
||||
}
|
||||
|
||||
// One-time offer to restore encrypted storage/wallet settings on login (todo
|
||||
@@ -1826,6 +1873,13 @@ class Application {
|
||||
walletSync: createWalletSyncService({
|
||||
nwcSettings: this.nwcSettingsService,
|
||||
}),
|
||||
// Never show the restore dialog over the onboarding tour — wait for
|
||||
// it to finish/exit first. (The remote existence check still runs in
|
||||
// parallel; only the confirmation dialog is held back.)
|
||||
confirm: async (message) => {
|
||||
await this.whenOnboardingTourInactive();
|
||||
return showConfirm(message, { confirmLabel: "Restore" });
|
||||
},
|
||||
});
|
||||
}
|
||||
Promise.resolve(
|
||||
|
||||
@@ -74,19 +74,32 @@ export function launchBitvidTour({
|
||||
openProfilePane,
|
||||
document: doc,
|
||||
force = false,
|
||||
onEnd,
|
||||
} = {}) {
|
||||
if (!force && !shouldOfferOnboarding(pubkey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fire onEnd on both finish and skip so callers can release anything they
|
||||
// deferred for the tour's duration (e.g. the settings-restore prompt).
|
||||
const end = (status) => {
|
||||
markOnboarding(pubkey, status);
|
||||
try {
|
||||
onEnd?.(status);
|
||||
} catch (error) {
|
||||
// best-effort
|
||||
}
|
||||
};
|
||||
|
||||
const tour = createTour({
|
||||
steps: buildBitvidTourSteps({ openProfilePane }),
|
||||
document: doc,
|
||||
onFinish: () => markOnboarding(pubkey, "completed"),
|
||||
onSkip: () => markOnboarding(pubkey, "skipped"),
|
||||
onFinish: () => end("completed"),
|
||||
onSkip: () => end("skipped"),
|
||||
});
|
||||
|
||||
return Boolean(tour?.start());
|
||||
const started = Boolean(tour?.start());
|
||||
return started;
|
||||
}
|
||||
|
||||
export default launchBitvidTour;
|
||||
Reference in new issue
Block a user