diff --git a/src-node/package-lock.json b/src-node/package-lock.json index 287070c6e5..4350321b3d 100644 --- a/src-node/package-lock.json +++ b/src-node/package-lock.json @@ -1,12 +1,12 @@ { "name": "@phcode/node-core", - "version": "5.1.9-0", + "version": "5.1.10-0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@phcode/node-core", - "version": "5.1.9-0", + "version": "5.1.10-0", "hasInstallScript": true, "license": "GNU-AGPL3.0", "dependencies": { diff --git a/src/extensionsIntegrated/Phoenix/guided-tour.js b/src/extensionsIntegrated/Phoenix/guided-tour.js index 03e81533d4..54b6050dc7 100644 --- a/src/extensionsIntegrated/Phoenix/guided-tour.js +++ b/src/extensionsIntegrated/Phoenix/guided-tour.js @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-only // Copyright (c) 2021 - present core.ai. All rights reserved. -/*global logger*/ +/*global logger, jsPromise*/ define(function (require, exports, module) { const KernalModeTrust = window.KernalModeTrust; @@ -21,9 +21,80 @@ define(function (require, exports, module) { Mustache = require("thirdparty/mustache/mustache"), SurveyTemplate = require("text!./html/survey-template.html"), PhoenixTour = require("./phoenix-tour"), + newFeature = require("./newly-added-features"), + ProjectManager = require("project/ProjectManager"), + semver = require("thirdparty/semver.browser"), NOTIFICATION_BACKOFF = 10000, GUIDED_TOUR_LOCAL_STORAGE_KEY = "guidedTourActions"; + function _currentAppVersion() { + return window.AppConfig && window.AppConfig.apiVersion; + } + + function _isNewerVersion(v1, v2) { + try { + return semver.gt(v1, v2); + } catch (e) { + return false; + } + } + + /** + * Switch to the welcome project if we're not already there. Returns a + * native Promise that resolves once the switch completes (or + * immediately if no switch is needed). openProject returns a jQuery + * deferred, so we adapt via jsPromise and swallow rejections so + * downstream onboarding still runs against whatever project ended up + * open. + */ + function _switchToWelcomeProject() { + const welcomePath = ProjectManager.getWelcomeProjectPath(); + const currentPath = ProjectManager.getProjectRoot().fullPath; + if (currentPath === welcomePath) { + return Promise.resolve(); + } + console.log("Guided tour: switching to welcome project for onboarding"); + return jsPromise(ProjectManager.openProject(welcomePath)).catch(function () { }); + } + + /** + * If the app was bumped to a newer version since we last recorded a + * boot, switch to the welcome project and surface the "newly added + * features" markdown there (the tour's intended onboarding context). + * - First time we see this user: just record current and return; the + * one-shot PhoenixTour handles fresh-user onboarding. + * - Same version as recorded: no-op. + * - Newer version than recorded: switch project + run newFeature.init. + * - Lower version (downgrade): no-op, leave state untouched so a + * future re-upgrade to the recorded version doesn't re-fire. + */ + function _maybeShowNewFeaturesForVersionChange() { + const current = _currentAppVersion(); + if (!current) { + return; + } + const lastSeen = userAlreadyDidAction.lastSeenAppVersion; + if (lastSeen === current) { + return; + } + if (!lastSeen) { + // First time we're recording — no prior to compare against. + userAlreadyDidAction.lastSeenAppVersion = current; + PhStore.setItem(GUIDED_TOUR_LOCAL_STORAGE_KEY, JSON.stringify(userAlreadyDidAction)); + return; + } + if (!_isNewerVersion(current, lastSeen)) { + // Downgrade — don't refresh, don't update state, so a + // future re-upgrade to lastSeen doesn't fire again. + return; + } + _switchToWelcomeProject().then(function () { + newFeature.init(); + userAlreadyDidAction.lastSeenAppVersion = current; + PhStore.setItem(GUIDED_TOUR_LOCAL_STORAGE_KEY, JSON.stringify(userAlreadyDidAction)); + }); + } + const surveyLinksURL = "https://updates.phcode.io/surveys.json"; // All popup notifications will show immediately on boot, we don't want to interrupt user amidst his work @@ -280,6 +351,11 @@ define(function (require, exports, module) { tourStarted = true; _showBeautifyNotification(); _showSurveys(); + // PhoenixTour is a once-per-lifetime overlay tour with its own + // internal _shouldRun gate; safe to call every boot. PhoenixTour.startTour(); + // On a higher-version change, land on the welcome project and + // surface the newly-added-features markdown. + _maybeShowNewFeaturesForVersionChange(); }; }); diff --git a/src/extensionsIntegrated/Phoenix/main.js b/src/extensionsIntegrated/Phoenix/main.js index 3bf88e91e0..7b7fa7419c 100644 --- a/src/extensionsIntegrated/Phoenix/main.js +++ b/src/extensionsIntegrated/Phoenix/main.js @@ -26,7 +26,6 @@ define(function (require, exports, module) { const serverSync = require("./serverSync"), newProject = require("./new-project"), defaultProjects = require("./default-projects"), - newFeature = require("./newly-added-features"), AppInit = require("utils/AppInit"), Strings = require("strings"), Dialogs = require("widgets/Dialogs"), @@ -95,7 +94,6 @@ define(function (require, exports, module) { serverSync.init(); defaultProjects.init(); newProject.init(); - newFeature.init(); _detectUnSupportedBrowser(); _persistBrowserStorage(); }); diff --git a/src/extensionsIntegrated/Phoenix/new-project.js b/src/extensionsIntegrated/Phoenix/new-project.js index 470d36bb94..61156349b3 100644 --- a/src/extensionsIntegrated/Phoenix/new-project.js +++ b/src/extensionsIntegrated/Phoenix/new-project.js @@ -101,7 +101,6 @@ define(function (require, exports, module) { Metrics.countEvent(Metrics.EVENT_TYPE.NEW_PROJECT, "dialogue", "close"); newProjectDialogueObj.close(); exports.trigger(exports.EVENT_NEW_PROJECT_DIALOGUE_CLOSED); - guidedTour.startTourIfNeeded(); } function showErrorDialogue(title, message) { @@ -149,6 +148,18 @@ define(function (require, exports, module) { let _bootDoneDeferred = new $.Deferred(); let _bootDonePromise = jsPromise(_bootDoneDeferred.promise()); + // Fire the guided tour once on every boot, regardless of how the + // new-project dialog flow resolved. Previously this was called from + // a few specific paths (closeDialogue, init when welcome screen + // disabled), which left users in non-default projects on native app + // never seeing the tour. The individual notifications inside + // startTourIfNeeded (beautify hint, surveys, one-shot PhoenixTour + // overlay, newly-added-features markdown on version bump) all have + // their own internal gating so it's safe to call every boot. + _bootDonePromise.then(function () { + guidedTour.startTourIfNeeded(); + }); + function onBootComplete() { return _bootDonePromise; } @@ -158,7 +169,6 @@ define(function (require, exports, module) { const shouldShowWelcome = PhStore.getItem("new-project.showWelcomeScreen") || 'Y'; if(shouldShowWelcome !== 'Y') { Metrics.countEvent(Metrics.EVENT_TYPE.NEW_PROJECT, "dialogue", "disabled"); - guidedTour.startTourIfNeeded(); _bootDoneDeferred.resolve(); return; } diff --git a/tracking-repos.json b/tracking-repos.json index 25e55cc2fa..b95fadce65 100644 --- a/tracking-repos.json +++ b/tracking-repos.json @@ -1,5 +1,5 @@ { "phoenixPro": { - "commitID": "11fec38adda6438d047e58ae6b32bced6fb6d48b" + "commitID": "cc421cb18c699ff30d1a886b952e5518efd79b48" } }