From b315c8acfa19ad1b4089ec4664ae6eeb2b15b4b5 Mon Sep 17 00:00:00 2001 From: tdgao Date: Wed, 15 Jul 2026 09:43:32 -0700 Subject: [PATCH 01/55] refactor: remove unused legacy onboarded flag --- apps/app-frontend/src/App.vue | 6 +-- apps/app-frontend/src/helpers/analytics.ts | 2 +- apps/app-frontend/src/helpers/settings.ts | 2 - apps/app-frontend/src/helpers/types.d.ts | 2 - ...0260715120000_remove-onboarded-setting.sql | 1 + .../app-lib/src/state/legacy_converter.rs | 3 -- packages/app-lib/src/state/settings.rs | 47 ++++++++----------- 7 files changed, 23 insertions(+), 40 deletions(-) create mode 100644 packages/app-lib/migrations/20260715120000_remove-onboarded-setting.sql diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue index 7910ca37a7..07a8a5851e 100644 --- a/apps/app-frontend/src/App.vue +++ b/apps/app-frontend/src/App.vue @@ -260,7 +260,6 @@ window.addEventListener('online', () => { offline.value = false }) -const showOnboarding = ref(false) const nativeDecorations = ref(false) const os = ref('') @@ -343,7 +342,6 @@ async function setupApp() { collapsed_navigation, hide_nametag_skins_page, advanced_rendering, - onboarded, default_page, toggle_sidebar, developer_mode, @@ -364,8 +362,6 @@ async function setupApp() { const dev = await isDev() isDevEnvironment.value = dev const version = await getVersion() - showOnboarding.value = !onboarded - nativeDecorations.value = native_decorations if (os.value !== 'MacOS') await getCurrentWindow().setDecorations(native_decorations) @@ -387,7 +383,7 @@ async function setupApp() { if (telemetry) { initAnalytics() if (dev) debugAnalytics() - trackEvent('Launched', { version, dev, onboarded }) + trackEvent('Launched', { version, dev }) } if (!dev) document.addEventListener('contextmenu', (event) => event.preventDefault()) diff --git a/apps/app-frontend/src/helpers/analytics.ts b/apps/app-frontend/src/helpers/analytics.ts index 2744d61f5a..2ad09e79a4 100644 --- a/apps/app-frontend/src/helpers/analytics.ts +++ b/apps/app-frontend/src/helpers/analytics.ts @@ -11,7 +11,7 @@ interface ProjectProperties extends InstanceProperties { } type AnalyticsEventMap = { - Launched: { version: string; dev: boolean; onboarded: boolean } + Launched: { version: string; dev: boolean } PageView: { path: string; fromPath: string; failed: unknown } InstanceCreate: { source: string } InstanceCreateStart: { source: string } diff --git a/apps/app-frontend/src/helpers/settings.ts b/apps/app-frontend/src/helpers/settings.ts index a63dbe78e3..e01827a53c 100644 --- a/apps/app-frontend/src/helpers/settings.ts +++ b/apps/app-frontend/src/helpers/settings.ts @@ -48,8 +48,6 @@ export type AppSettings = { discord_rpc: boolean personalized_ads: boolean - onboarded: boolean - extra_launch_args: string[] custom_env_vars: [string, string][] memory: MemorySettings diff --git a/apps/app-frontend/src/helpers/types.d.ts b/apps/app-frontend/src/helpers/types.d.ts index 761226eb91..2019538066 100644 --- a/apps/app-frontend/src/helpers/types.d.ts +++ b/apps/app-frontend/src/helpers/types.d.ts @@ -170,8 +170,6 @@ type AppSettings = { developer_mode: boolean personalized_ads: boolean - onboarded: boolean - extra_launch_args: string[] custom_env_vars: [string, string][] memory: MemorySettings diff --git a/packages/app-lib/migrations/20260715120000_remove-onboarded-setting.sql b/packages/app-lib/migrations/20260715120000_remove-onboarded-setting.sql new file mode 100644 index 0000000000..3fc71ffe26 --- /dev/null +++ b/packages/app-lib/migrations/20260715120000_remove-onboarded-setting.sql @@ -0,0 +1 @@ +ALTER TABLE settings DROP COLUMN onboarded; diff --git a/packages/app-lib/src/state/legacy_converter.rs b/packages/app-lib/src/state/legacy_converter.rs index b602a09b3a..8415a0f96b 100644 --- a/packages/app-lib/src/state/legacy_converter.rs +++ b/packages/app-lib/src/state/legacy_converter.rs @@ -63,7 +63,6 @@ where settings.telemetry = !legacy_settings.opt_out_analytics; settings.discord_rpc = !legacy_settings.disable_discord_rpc; settings.developer_mode = legacy_settings.developer_mode; - settings.onboarded = legacy_settings.fully_onboarded; settings.extra_launch_args = legacy_settings.custom_java_args; settings.custom_env_vars = legacy_settings.custom_env_args; settings.memory.maximum = legacy_settings.memory.maximum; @@ -655,8 +654,6 @@ struct LegacySettings { pub opt_out_analytics: bool, #[serde(default)] pub advanced_rendering: bool, - #[serde(default)] - pub fully_onboarded: bool, #[serde(default = "default_settings_dir")] pub loaded_config_dir: Option, } diff --git a/packages/app-lib/src/state/settings.rs b/packages/app-lib/src/state/settings.rs index 81df354664..a3f9709d3b 100644 --- a/packages/app-lib/src/state/settings.rs +++ b/packages/app-lib/src/state/settings.rs @@ -24,8 +24,6 @@ pub struct Settings { pub discord_rpc: bool, pub personalized_ads: bool, - pub onboarded: bool, - pub extra_launch_args: Vec, pub custom_env_vars: Vec<(String, String)>, pub memory: MemorySettings, @@ -79,7 +77,6 @@ impl Settings { max_concurrent_writes, max_concurrent_downloads, theme, locale, default_page, collapsed_navigation, hide_nametag_skins_page, advanced_rendering, native_decorations, discord_rpc, developer_mode, telemetry, personalized_ads, - onboarded, json(extra_launch_args) extra_launch_args, json(custom_env_vars) custom_env_vars, mc_memory_max, mc_force_fullscreen, mc_game_resolution_x, mc_game_resolution_y, hide_on_process_start, hook_pre_launch, hook_wrapper, hook_post_exit, @@ -107,7 +104,6 @@ impl Settings { discord_rpc: res.discord_rpc == 1, developer_mode: res.developer_mode == 1, personalized_ads: res.personalized_ads == 1, - onboarded: res.onboarded == 1, extra_launch_args: res .extra_launch_args .as_ref() @@ -180,33 +176,31 @@ impl Settings { telemetry = $11, personalized_ads = $12, - onboarded = $13, - - extra_launch_args = jsonb($14), - custom_env_vars = jsonb($15), - mc_memory_max = $16, - mc_force_fullscreen = $17, - mc_game_resolution_x = $18, - mc_game_resolution_y = $19, - hide_on_process_start = $20, + extra_launch_args = jsonb($13), + custom_env_vars = jsonb($14), + mc_memory_max = $15, + mc_force_fullscreen = $16, + mc_game_resolution_x = $17, + mc_game_resolution_y = $18, + hide_on_process_start = $19, - hook_pre_launch = $21, - hook_wrapper = $22, - hook_post_exit = $23, + hook_pre_launch = $20, + hook_wrapper = $21, + hook_post_exit = $22, - custom_dir = $24, - prev_custom_dir = $25, - migrated = $26, + custom_dir = $23, + prev_custom_dir = $24, + migrated = $25, - toggle_sidebar = $27, - feature_flags = $28, - hide_nametag_skins_page = $29, + toggle_sidebar = $26, + feature_flags = $27, + hide_nametag_skins_page = $28, - skipped_update = $30, - pending_update_toast_for_version = $31, - auto_download_updates = $32, + skipped_update = $29, + pending_update_toast_for_version = $30, + auto_download_updates = $31, - version = $33 + version = $32 ", max_concurrent_writes, max_concurrent_downloads, @@ -220,7 +214,6 @@ impl Settings { self.developer_mode, self.telemetry, self.personalized_ads, - self.onboarded, extra_launch_args, custom_env_vars, self.memory.maximum, From 907599a69eb274c87823774fced4d77379c2809d Mon Sep 17 00:00:00 2001 From: tdgao Date: Wed, 15 Jul 2026 09:53:16 -0700 Subject: [PATCH 02/55] sqlx prepare --- ...c422c3ab7203409083fd3c9c968b00b46918f.json | 12 ----- ...c12be71df20b8cf520019413f6ea751cd594.json} | 48 ++++++++----------- ...deec7e741eca9490811ebad6594abe8a6126d.json | 12 +++++ 3 files changed, 33 insertions(+), 39 deletions(-) delete mode 100644 packages/app-lib/.sqlx/query-175067f04e775f5469146f3cb77c422c3ab7203409083fd3c9c968b00b46918f.json rename packages/app-lib/.sqlx/{query-8e62fba05f331f91822ec204695ecb40567541c547181a4ef847318845cf3110.json => query-6f8b76b839db48539a70664af549c12be71df20b8cf520019413f6ea751cd594.json} (84%) create mode 100644 packages/app-lib/.sqlx/query-dfdc99cb5ec60bb08c566920628deec7e741eca9490811ebad6594abe8a6126d.json diff --git a/packages/app-lib/.sqlx/query-175067f04e775f5469146f3cb77c422c3ab7203409083fd3c9c968b00b46918f.json b/packages/app-lib/.sqlx/query-175067f04e775f5469146f3cb77c422c3ab7203409083fd3c9c968b00b46918f.json deleted file mode 100644 index 14f0ff0d1a..0000000000 --- a/packages/app-lib/.sqlx/query-175067f04e775f5469146f3cb77c422c3ab7203409083fd3c9c968b00b46918f.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n UPDATE settings\n SET\n max_concurrent_writes = $1,\n max_concurrent_downloads = $2,\n\n theme = $3,\n locale = $4,\n default_page = $5,\n collapsed_navigation = $6,\n advanced_rendering = $7,\n native_decorations = $8,\n\n discord_rpc = $9,\n developer_mode = $10,\n telemetry = $11,\n personalized_ads = $12,\n\n onboarded = $13,\n\n extra_launch_args = jsonb($14),\n custom_env_vars = jsonb($15),\n mc_memory_max = $16,\n mc_force_fullscreen = $17,\n mc_game_resolution_x = $18,\n mc_game_resolution_y = $19,\n hide_on_process_start = $20,\n\n hook_pre_launch = $21,\n hook_wrapper = $22,\n hook_post_exit = $23,\n\n custom_dir = $24,\n prev_custom_dir = $25,\n migrated = $26,\n\n toggle_sidebar = $27,\n feature_flags = $28,\n hide_nametag_skins_page = $29,\n\n skipped_update = $30,\n pending_update_toast_for_version = $31,\n auto_download_updates = $32,\n\n version = $33\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 33 - }, - "nullable": [] - }, - "hash": "175067f04e775f5469146f3cb77c422c3ab7203409083fd3c9c968b00b46918f" -} diff --git a/packages/app-lib/.sqlx/query-8e62fba05f331f91822ec204695ecb40567541c547181a4ef847318845cf3110.json b/packages/app-lib/.sqlx/query-6f8b76b839db48539a70664af549c12be71df20b8cf520019413f6ea751cd594.json similarity index 84% rename from packages/app-lib/.sqlx/query-8e62fba05f331f91822ec204695ecb40567541c547181a4ef847318845cf3110.json rename to packages/app-lib/.sqlx/query-6f8b76b839db48539a70664af549c12be71df20b8cf520019413f6ea751cd594.json index 0928c41a08..21417b6d56 100644 --- a/packages/app-lib/.sqlx/query-8e62fba05f331f91822ec204695ecb40567541c547181a4ef847318845cf3110.json +++ b/packages/app-lib/.sqlx/query-6f8b76b839db48539a70664af549c12be71df20b8cf520019413f6ea751cd594.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "\n SELECT\n max_concurrent_writes, max_concurrent_downloads,\n theme, locale, default_page, collapsed_navigation, hide_nametag_skins_page, advanced_rendering, native_decorations,\n discord_rpc, developer_mode, telemetry, personalized_ads,\n onboarded,\n json(extra_launch_args) extra_launch_args, json(custom_env_vars) custom_env_vars,\n mc_memory_max, mc_force_fullscreen, mc_game_resolution_x, mc_game_resolution_y, hide_on_process_start,\n hook_pre_launch, hook_wrapper, hook_post_exit,\n custom_dir, prev_custom_dir, migrated, json(feature_flags) feature_flags, toggle_sidebar,\n skipped_update, pending_update_toast_for_version, auto_download_updates,\n version\n FROM settings\n ", + "query": "\n SELECT\n max_concurrent_writes, max_concurrent_downloads,\n theme, locale, default_page, collapsed_navigation, hide_nametag_skins_page, advanced_rendering, native_decorations,\n discord_rpc, developer_mode, telemetry, personalized_ads,\n json(extra_launch_args) extra_launch_args, json(custom_env_vars) custom_env_vars,\n mc_memory_max, mc_force_fullscreen, mc_game_resolution_x, mc_game_resolution_y, hide_on_process_start,\n hook_pre_launch, hook_wrapper, hook_post_exit,\n custom_dir, prev_custom_dir, migrated, json(feature_flags) feature_flags, toggle_sidebar,\n skipped_update, pending_update_toast_for_version, auto_download_updates,\n version\n FROM settings\n ", "describe": { "columns": [ { @@ -68,104 +68,99 @@ "ordinal": 12, "type_info": "Integer" }, - { - "name": "onboarded", - "ordinal": 13, - "type_info": "Integer" - }, { "name": "extra_launch_args", - "ordinal": 14, + "ordinal": 13, "type_info": "Text" }, { "name": "custom_env_vars", - "ordinal": 15, + "ordinal": 14, "type_info": "Text" }, { "name": "mc_memory_max", - "ordinal": 16, + "ordinal": 15, "type_info": "Integer" }, { "name": "mc_force_fullscreen", - "ordinal": 17, + "ordinal": 16, "type_info": "Integer" }, { "name": "mc_game_resolution_x", - "ordinal": 18, + "ordinal": 17, "type_info": "Integer" }, { "name": "mc_game_resolution_y", - "ordinal": 19, + "ordinal": 18, "type_info": "Integer" }, { "name": "hide_on_process_start", - "ordinal": 20, + "ordinal": 19, "type_info": "Integer" }, { "name": "hook_pre_launch", - "ordinal": 21, + "ordinal": 20, "type_info": "Text" }, { "name": "hook_wrapper", - "ordinal": 22, + "ordinal": 21, "type_info": "Text" }, { "name": "hook_post_exit", - "ordinal": 23, + "ordinal": 22, "type_info": "Text" }, { "name": "custom_dir", - "ordinal": 24, + "ordinal": 23, "type_info": "Text" }, { "name": "prev_custom_dir", - "ordinal": 25, + "ordinal": 24, "type_info": "Text" }, { "name": "migrated", - "ordinal": 26, + "ordinal": 25, "type_info": "Integer" }, { "name": "feature_flags", - "ordinal": 27, + "ordinal": 26, "type_info": "Text" }, { "name": "toggle_sidebar", - "ordinal": 28, + "ordinal": 27, "type_info": "Integer" }, { "name": "skipped_update", - "ordinal": 29, + "ordinal": 28, "type_info": "Text" }, { "name": "pending_update_toast_for_version", - "ordinal": 30, + "ordinal": 29, "type_info": "Text" }, { "name": "auto_download_updates", - "ordinal": 31, + "ordinal": 30, "type_info": "Integer" }, { "name": "version", - "ordinal": 32, + "ordinal": 31, "type_info": "Integer" } ], @@ -186,7 +181,6 @@ false, false, false, - false, null, null, false, @@ -208,5 +202,5 @@ false ] }, - "hash": "8e62fba05f331f91822ec204695ecb40567541c547181a4ef847318845cf3110" + "hash": "6f8b76b839db48539a70664af549c12be71df20b8cf520019413f6ea751cd594" } diff --git a/packages/app-lib/.sqlx/query-dfdc99cb5ec60bb08c566920628deec7e741eca9490811ebad6594abe8a6126d.json b/packages/app-lib/.sqlx/query-dfdc99cb5ec60bb08c566920628deec7e741eca9490811ebad6594abe8a6126d.json new file mode 100644 index 0000000000..e4cd418a9e --- /dev/null +++ b/packages/app-lib/.sqlx/query-dfdc99cb5ec60bb08c566920628deec7e741eca9490811ebad6594abe8a6126d.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n UPDATE settings\n SET\n max_concurrent_writes = $1,\n max_concurrent_downloads = $2,\n\n theme = $3,\n locale = $4,\n default_page = $5,\n collapsed_navigation = $6,\n advanced_rendering = $7,\n native_decorations = $8,\n\n discord_rpc = $9,\n developer_mode = $10,\n telemetry = $11,\n personalized_ads = $12,\n\n extra_launch_args = jsonb($13),\n custom_env_vars = jsonb($14),\n mc_memory_max = $15,\n mc_force_fullscreen = $16,\n mc_game_resolution_x = $17,\n mc_game_resolution_y = $18,\n hide_on_process_start = $19,\n\n hook_pre_launch = $20,\n hook_wrapper = $21,\n hook_post_exit = $22,\n\n custom_dir = $23,\n prev_custom_dir = $24,\n migrated = $25,\n\n toggle_sidebar = $26,\n feature_flags = $27,\n hide_nametag_skins_page = $28,\n\n skipped_update = $29,\n pending_update_toast_for_version = $30,\n auto_download_updates = $31,\n\n version = $32\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 32 + }, + "nullable": [] + }, + "hash": "dfdc99cb5ec60bb08c566920628deec7e741eca9490811ebad6594abe8a6126d" +} From f4086f2ec67ac71c3d89cdf64e3b6e578ed9ef3a Mon Sep 17 00:00:00 2001 From: tdgao Date: Wed, 15 Jul 2026 10:33:03 -0700 Subject: [PATCH 03/55] feat: add onboarding checklist app db and add vue provider --- apps/app-frontend/src/App.vue | 3 + .../src/helpers/onboarding-checklist.ts | 11 +++ .../src/providers/onboarding-checklist.ts | 65 ++++++++++++++ apps/app-frontend/src/providers/setup.ts | 3 + apps/app/build.rs | 8 ++ apps/app/capabilities/plugins.json | 1 + apps/app/src/api/mod.rs | 1 + apps/app/src/api/onboarding_checklist.rs | 14 +++ apps/app/src/main.rs | 1 + ...706fce514b070987a4ff2047afa18ee60d237.json | 12 +++ ...767db2b33b2d8269d62cd6d160b8f5ec31ad6.json | 32 +++++++ ...b59c6d9d83486ccadae7351b1516d3387834f.json | 12 +++ ...b087901087b5580012e68f96c6836b5a146ca.json | 12 +++ .../20260715165608_onboarding-checklist.sql | 19 +++++ .../app-lib/src/api/instance/lifecycle.rs | 6 ++ packages/app-lib/src/api/minecraft_auth.rs | 13 ++- packages/app-lib/src/api/mod.rs | 12 +-- packages/app-lib/src/api/mr_auth.rs | 9 ++ .../app-lib/src/api/onboarding_checklist.rs | 35 ++++++++ packages/app-lib/src/event/emit.rs | 16 ++++ packages/app-lib/src/state/mod.rs | 3 + .../app-lib/src/state/onboarding_checklist.rs | 85 +++++++++++++++++++ 22 files changed, 367 insertions(+), 6 deletions(-) create mode 100644 apps/app-frontend/src/helpers/onboarding-checklist.ts create mode 100644 apps/app-frontend/src/providers/onboarding-checklist.ts create mode 100644 apps/app/src/api/onboarding_checklist.rs create mode 100644 packages/app-lib/.sqlx/query-04b62cdfd635acb3d700211a104706fce514b070987a4ff2047afa18ee60d237.json create mode 100644 packages/app-lib/.sqlx/query-562470c930ccf9ff999208e00c7767db2b33b2d8269d62cd6d160b8f5ec31ad6.json create mode 100644 packages/app-lib/.sqlx/query-c715a78bfe15b58f7a14c9eaf3bb59c6d9d83486ccadae7351b1516d3387834f.json create mode 100644 packages/app-lib/.sqlx/query-fa1954f9c9c998fe4c66cbc82bab087901087b5580012e68f96c6836b5a146ca.json create mode 100644 packages/app-lib/migrations/20260715165608_onboarding-checklist.sql create mode 100644 packages/app-lib/src/api/onboarding_checklist.rs create mode 100644 packages/app-lib/src/state/onboarding_checklist.rs diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue index 07a8a5851e..84a20ff4fd 100644 --- a/apps/app-frontend/src/App.vue +++ b/apps/app-frontend/src/App.vue @@ -246,6 +246,7 @@ const { setModpackAlreadyInstalledModal, handleModpackDuplicateCreateAnyway, handleModpackDuplicateGoToInstance, + onboardingChecklist, } = setupProviders(notificationManager, popupNotificationManager) const news = ref([]) @@ -334,6 +335,8 @@ const messages = defineMessages({ }) async function setupApp() { + await onboardingChecklist.initialize() + const { native_decorations, theme, diff --git a/apps/app-frontend/src/helpers/onboarding-checklist.ts b/apps/app-frontend/src/helpers/onboarding-checklist.ts new file mode 100644 index 0000000000..13dd93b46b --- /dev/null +++ b/apps/app-frontend/src/helpers/onboarding-checklist.ts @@ -0,0 +1,11 @@ +import { invoke } from '@tauri-apps/api/core' + +export interface OnboardingChecklist { + has_created_instance: boolean + has_logged_into_minecraft: boolean + has_logged_into_modrinth: boolean +} + +export async function getOnboardingChecklist(): Promise { + return await invoke('plugin:onboarding-checklist|get_onboarding_checklist') +} diff --git a/apps/app-frontend/src/providers/onboarding-checklist.ts b/apps/app-frontend/src/providers/onboarding-checklist.ts new file mode 100644 index 0000000000..80ac1a5b53 --- /dev/null +++ b/apps/app-frontend/src/providers/onboarding-checklist.ts @@ -0,0 +1,65 @@ +import { createContext } from '@modrinth/ui' +import { listen, type UnlistenFn } from '@tauri-apps/api/event' +import { computed, type ComputedRef, onUnmounted, ref } from 'vue' + +import { + getOnboardingChecklist, + type OnboardingChecklist, +} from '@/helpers/onboarding-checklist' + +export interface OnboardingChecklistContext { + hasCreatedInstance: ComputedRef + hasLoggedIntoMinecraft: ComputedRef + hasLoggedIntoModrinth: ComputedRef + isReady: ComputedRef +} + +export interface OnboardingChecklistProvider extends OnboardingChecklistContext { + initialize: () => Promise +} + +export const [injectOnboardingChecklist, provideOnboardingChecklist] = + createContext('root', 'onboardingChecklist') + +export function setupOnboardingChecklistProvider(): OnboardingChecklistProvider { + const checklist = ref() + let unlisten: UnlistenFn | undefined + + const context: OnboardingChecklistContext = { + hasCreatedInstance: computed(() => checklist.value?.has_created_instance ?? false), + hasLoggedIntoMinecraft: computed( + () => checklist.value?.has_logged_into_minecraft ?? false, + ), + hasLoggedIntoModrinth: computed( + () => checklist.value?.has_logged_into_modrinth ?? false, + ), + isReady: computed(() => checklist.value !== undefined), + } + + provideOnboardingChecklist(context) + + const applyChecklist = (nextChecklist: OnboardingChecklist) => { + checklist.value = { + has_created_instance: + (checklist.value?.has_created_instance ?? false) || + nextChecklist.has_created_instance, + has_logged_into_minecraft: + (checklist.value?.has_logged_into_minecraft ?? false) || + nextChecklist.has_logged_into_minecraft, + has_logged_into_modrinth: + (checklist.value?.has_logged_into_modrinth ?? false) || + nextChecklist.has_logged_into_modrinth, + } + } + + const initialize = async () => { + unlisten = await listen('onboarding_checklist', (event) => { + applyChecklist(event.payload) + }) + applyChecklist(await getOnboardingChecklist()) + } + + onUnmounted(() => unlisten?.()) + + return { ...context, initialize } +} diff --git a/apps/app-frontend/src/providers/setup.ts b/apps/app-frontend/src/providers/setup.ts index fc49dbcfe7..ffa0d2cfcb 100644 --- a/apps/app-frontend/src/providers/setup.ts +++ b/apps/app-frontend/src/providers/setup.ts @@ -1,5 +1,6 @@ import type { AbstractPopupNotificationManager, AbstractWebNotificationManager } from '@modrinth/ui' +import { setupOnboardingChecklistProvider } from './onboarding-checklist' import { setupCreationModal } from './setup/creation-modal' import { setupFileDropProvider } from './setup/file-drop' import { setupFilePickerProvider } from './setup/file-picker' @@ -14,8 +15,10 @@ export function setupProviders( setupFileDropProvider() setupFilePickerProvider() setupInstanceImportProvider(notificationManager) + const onboardingChecklist = setupOnboardingChecklistProvider() return { ...setupCreationModal(notificationManager), + onboardingChecklist, } } diff --git a/apps/app/build.rs b/apps/app/build.rs index da18e49769..ed08799d96 100644 --- a/apps/app/build.rs +++ b/apps/app/build.rs @@ -141,6 +141,14 @@ fn main() { DefaultPermissionRule::AllowAllCommands, ), ) + .plugin( + "onboarding-checklist", + InlinedPlugin::new() + .commands(&["get_onboarding_checklist"]) + .default_permission( + DefaultPermissionRule::AllowAllCommands, + ), + ) .plugin( "install", InlinedPlugin::new() diff --git a/apps/app/capabilities/plugins.json b/apps/app/capabilities/plugins.json index 2cb2521766..b4564fea62 100644 --- a/apps/app/capabilities/plugins.json +++ b/apps/app/capabilities/plugins.json @@ -99,6 +99,7 @@ "metadata:default", "minecraft-skins:default", "mr-auth:default", + "onboarding-checklist:default", "instance:default", "install:default", "process:default", diff --git a/apps/app/src/api/mod.rs b/apps/app/src/api/mod.rs index 2d45357cbf..910d746047 100644 --- a/apps/app/src/api/mod.rs +++ b/apps/app/src/api/mod.rs @@ -11,6 +11,7 @@ pub mod logs; pub mod metadata; pub mod minecraft_skins; pub mod mr_auth; +pub mod onboarding_checklist; pub mod process; pub mod settings; pub mod shortcuts; diff --git a/apps/app/src/api/onboarding_checklist.rs b/apps/app/src/api/onboarding_checklist.rs new file mode 100644 index 0000000000..7e4d75eb63 --- /dev/null +++ b/apps/app/src/api/onboarding_checklist.rs @@ -0,0 +1,14 @@ +use crate::api::Result; +use tauri::plugin::TauriPlugin; +use theseus::prelude::*; + +pub fn init() -> TauriPlugin { + tauri::plugin::Builder::new("onboarding-checklist") + .invoke_handler(tauri::generate_handler![get_onboarding_checklist]) + .build() +} + +#[tauri::command] +pub async fn get_onboarding_checklist() -> Result { + Ok(onboarding_checklist::get().await?) +} diff --git a/apps/app/src/main.rs b/apps/app/src/main.rs index 96d6b9e33b..a3c32be7f8 100644 --- a/apps/app/src/main.rs +++ b/apps/app/src/main.rs @@ -232,6 +232,7 @@ fn main() { builder = builder .plugin(api::auth::init()) .plugin(api::mr_auth::init()) + .plugin(api::onboarding_checklist::init()) .plugin(api::import::init()) .plugin(api::install::init()) .plugin(api::instance::init()) diff --git a/packages/app-lib/.sqlx/query-04b62cdfd635acb3d700211a104706fce514b070987a4ff2047afa18ee60d237.json b/packages/app-lib/.sqlx/query-04b62cdfd635acb3d700211a104706fce514b070987a4ff2047afa18ee60d237.json new file mode 100644 index 0000000000..0919e82861 --- /dev/null +++ b/packages/app-lib/.sqlx/query-04b62cdfd635acb3d700211a104706fce514b070987a4ff2047afa18ee60d237.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n UPDATE onboarding_checklist\n SET has_created_instance = TRUE\n WHERE id = 0 AND has_created_instance = FALSE\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 0 + }, + "nullable": [] + }, + "hash": "04b62cdfd635acb3d700211a104706fce514b070987a4ff2047afa18ee60d237" +} diff --git a/packages/app-lib/.sqlx/query-562470c930ccf9ff999208e00c7767db2b33b2d8269d62cd6d160b8f5ec31ad6.json b/packages/app-lib/.sqlx/query-562470c930ccf9ff999208e00c7767db2b33b2d8269d62cd6d160b8f5ec31ad6.json new file mode 100644 index 0000000000..9f7ce409ed --- /dev/null +++ b/packages/app-lib/.sqlx/query-562470c930ccf9ff999208e00c7767db2b33b2d8269d62cd6d160b8f5ec31ad6.json @@ -0,0 +1,32 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT\n has_created_instance,\n has_logged_into_minecraft,\n has_logged_into_modrinth\n FROM onboarding_checklist\n WHERE id = 0\n ", + "describe": { + "columns": [ + { + "name": "has_created_instance", + "ordinal": 0, + "type_info": "Integer" + }, + { + "name": "has_logged_into_minecraft", + "ordinal": 1, + "type_info": "Integer" + }, + { + "name": "has_logged_into_modrinth", + "ordinal": 2, + "type_info": "Integer" + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "562470c930ccf9ff999208e00c7767db2b33b2d8269d62cd6d160b8f5ec31ad6" +} diff --git a/packages/app-lib/.sqlx/query-c715a78bfe15b58f7a14c9eaf3bb59c6d9d83486ccadae7351b1516d3387834f.json b/packages/app-lib/.sqlx/query-c715a78bfe15b58f7a14c9eaf3bb59c6d9d83486ccadae7351b1516d3387834f.json new file mode 100644 index 0000000000..225b8bb859 --- /dev/null +++ b/packages/app-lib/.sqlx/query-c715a78bfe15b58f7a14c9eaf3bb59c6d9d83486ccadae7351b1516d3387834f.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n UPDATE onboarding_checklist\n SET has_logged_into_minecraft = TRUE\n WHERE id = 0 AND has_logged_into_minecraft = FALSE\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 0 + }, + "nullable": [] + }, + "hash": "c715a78bfe15b58f7a14c9eaf3bb59c6d9d83486ccadae7351b1516d3387834f" +} diff --git a/packages/app-lib/.sqlx/query-fa1954f9c9c998fe4c66cbc82bab087901087b5580012e68f96c6836b5a146ca.json b/packages/app-lib/.sqlx/query-fa1954f9c9c998fe4c66cbc82bab087901087b5580012e68f96c6836b5a146ca.json new file mode 100644 index 0000000000..34dc35c7ad --- /dev/null +++ b/packages/app-lib/.sqlx/query-fa1954f9c9c998fe4c66cbc82bab087901087b5580012e68f96c6836b5a146ca.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n UPDATE onboarding_checklist\n SET has_logged_into_modrinth = TRUE\n WHERE id = 0 AND has_logged_into_modrinth = FALSE\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 0 + }, + "nullable": [] + }, + "hash": "fa1954f9c9c998fe4c66cbc82bab087901087b5580012e68f96c6836b5a146ca" +} diff --git a/packages/app-lib/migrations/20260715165608_onboarding-checklist.sql b/packages/app-lib/migrations/20260715165608_onboarding-checklist.sql new file mode 100644 index 0000000000..c438177c29 --- /dev/null +++ b/packages/app-lib/migrations/20260715165608_onboarding-checklist.sql @@ -0,0 +1,19 @@ +CREATE TABLE onboarding_checklist ( + id INTEGER NOT NULL CHECK (id = 0), + has_created_instance INTEGER NOT NULL DEFAULT FALSE CHECK (has_created_instance IN (FALSE, TRUE)), + has_logged_into_minecraft INTEGER NOT NULL DEFAULT FALSE CHECK ( + has_logged_into_minecraft IN (FALSE, TRUE) + ), + has_logged_into_modrinth INTEGER NOT NULL DEFAULT FALSE CHECK ( + has_logged_into_modrinth IN (FALSE, TRUE) + ), + PRIMARY KEY (id) +); + +INSERT INTO onboarding_checklist + (id, has_created_instance, has_logged_into_minecraft, has_logged_into_modrinth) +SELECT + 0, + EXISTS (SELECT 1 FROM instances), + EXISTS (SELECT 1 FROM minecraft_users), + EXISTS (SELECT 1 FROM modrinth_users); diff --git a/packages/app-lib/src/api/instance/lifecycle.rs b/packages/app-lib/src/api/instance/lifecycle.rs index 0139b6b5f7..76508ee18e 100644 --- a/packages/app-lib/src/api/instance/lifecycle.rs +++ b/packages/app-lib/src/api/instance/lifecycle.rs @@ -49,6 +49,12 @@ pub(crate) async fn create( if result.is_err() { let _ = crate::state::remove_instance(&instance.id, &state).await; + } else if let Err(error) = + crate::onboarding_checklist::mark_created_instance().await + { + tracing::warn!( + "Failed to mark instance creation in onboarding checklist: {error}" + ); } result diff --git a/packages/app-lib/src/api/minecraft_auth.rs b/packages/app-lib/src/api/minecraft_auth.rs index 931b8e75bb..5c579b44d1 100644 --- a/packages/app-lib/src/api/minecraft_auth.rs +++ b/packages/app-lib/src/api/minecraft_auth.rs @@ -33,7 +33,18 @@ pub async fn finish_login( ) -> crate::Result { let state = State::get().await?; - crate::state::login_finish(code, flow, &state.pool).await + let credentials = + crate::state::login_finish(code, flow, &state.pool).await?; + + if let Err(error) = + crate::onboarding_checklist::mark_logged_into_minecraft().await + { + tracing::warn!( + "Failed to mark Minecraft login in onboarding checklist: {error}" + ); + } + + Ok(credentials) } #[tracing::instrument] diff --git a/packages/app-lib/src/api/mod.rs b/packages/app-lib/src/api/mod.rs index 927a666aaf..8eb48c2c61 100644 --- a/packages/app-lib/src/api/mod.rs +++ b/packages/app-lib/src/api/mod.rs @@ -9,6 +9,7 @@ pub mod metadata; pub mod minecraft_auth; pub mod minecraft_skins; pub mod mr_auth; +pub mod onboarding_checklist; pub mod pack; pub mod process; pub mod server_address; @@ -24,9 +25,10 @@ pub mod data { Hooks, InstanceInstallCandidate, InstanceInstallTarget, InstanceLaunchOverridesPatch, InstanceLink, InstanceMetadata, JavaVersion, LinkedModpackInfo, MemorySettings, ModLoader, - ModrinthCredentials, Organization, OwnerType, ProcessMetadata, Project, - ProjectType, ProjectV3, SearchResult, SearchResults, SearchResultsV3, - Settings, TeamMember, Theme, User, UserFriend, Version, WindowSize, + ModrinthCredentials, OnboardingChecklist, Organization, OwnerType, + ProcessMetadata, Project, ProjectType, ProjectV3, SearchResult, + SearchResults, SearchResultsV3, Settings, TeamMember, Theme, User, + UserFriend, Version, WindowSize, }; pub use ariadne::users::UserStatus; pub use modrinth_content_management::{ @@ -40,8 +42,8 @@ pub mod prelude { State, data::*, event::CommandPayload, - install, instance, jre, metadata, minecraft_auth, mr_auth, pack, - process, settings, + install, instance, jre, metadata, minecraft_auth, mr_auth, + onboarding_checklist, pack, process, settings, state::{ReleaseChannel, db_backup::app_db_backup_dir}, util::{ io::{IOError, canonicalize}, diff --git a/packages/app-lib/src/api/mr_auth.rs b/packages/app-lib/src/api/mr_auth.rs index 42ce41fe5e..22c858d1d2 100644 --- a/packages/app-lib/src/api/mr_auth.rs +++ b/packages/app-lib/src/api/mr_auth.rs @@ -19,6 +19,15 @@ pub async fn authenticate_finish_flow( .await?; creds.upsert(&state.pool).await?; + + if let Err(error) = + crate::onboarding_checklist::mark_logged_into_modrinth().await + { + tracing::warn!( + "Failed to mark Modrinth login in onboarding checklist: {error}" + ); + } + state .friends_socket .connect(&state.pool, &state.api_semaphore, &state.process_manager) diff --git a/packages/app-lib/src/api/onboarding_checklist.rs b/packages/app-lib/src/api/onboarding_checklist.rs new file mode 100644 index 0000000000..940de4c9e6 --- /dev/null +++ b/packages/app-lib/src/api/onboarding_checklist.rs @@ -0,0 +1,35 @@ +use crate::State; +use crate::event::emit::emit_onboarding_checklist; +use crate::state::{ + OnboardingChecklist, OnboardingChecklistItem, get_onboarding_checklist, + mark_onboarding_checklist_item, +}; + +#[tracing::instrument] +pub async fn get() -> crate::Result { + let state = State::get().await?; + get_onboarding_checklist(&state.pool).await +} + +pub(crate) async fn mark_created_instance() -> crate::Result<()> { + mark(OnboardingChecklistItem::CreatedInstance).await +} + +pub(crate) async fn mark_logged_into_minecraft() -> crate::Result<()> { + mark(OnboardingChecklistItem::LoggedIntoMinecraft).await +} + +pub(crate) async fn mark_logged_into_modrinth() -> crate::Result<()> { + mark(OnboardingChecklistItem::LoggedIntoModrinth).await +} + +async fn mark(item: OnboardingChecklistItem) -> crate::Result<()> { + let state = State::get().await?; + if let Some(checklist) = + mark_onboarding_checklist_item(item, &state.pool).await? + { + emit_onboarding_checklist(checklist).await?; + } + + Ok(()) +} diff --git a/packages/app-lib/src/event/emit.rs b/packages/app-lib/src/event/emit.rs index 64969cd727..b5e0480024 100644 --- a/packages/app-lib/src/event/emit.rs +++ b/packages/app-lib/src/event/emit.rs @@ -7,6 +7,7 @@ use crate::event::{ use crate::event::{ InstancePayload, LoadingPayload, ProcessPayload, WarningPayload, }; +use crate::state::OnboardingChecklist; use futures::prelude::*; use serde_json::Value; #[cfg(feature = "tauri")] @@ -264,6 +265,21 @@ pub async fn emit_instance( Ok(()) } +#[allow(unused_variables)] +pub async fn emit_onboarding_checklist( + checklist: OnboardingChecklist, +) -> crate::Result<()> { + #[cfg(feature = "tauri")] + { + let event_state = crate::EventState::get()?; + event_state + .app + .emit("onboarding_checklist", checklist) + .map_err(EventError::from)?; + } + Ok(()) +} + #[allow(unused_variables)] pub async fn emit_friend(payload: FriendPayload) -> crate::Result<()> { #[cfg(feature = "tauri")] diff --git a/packages/app-lib/src/state/mod.rs b/packages/app-lib/src/state/mod.rs index 18fa7edbb8..a0d36674ff 100644 --- a/packages/app-lib/src/state/mod.rs +++ b/packages/app-lib/src/state/mod.rs @@ -20,6 +20,9 @@ pub use self::instances::*; mod settings; pub use self::settings::*; +mod onboarding_checklist; +pub use self::onboarding_checklist::*; + mod process; pub use self::process::*; diff --git a/packages/app-lib/src/state/onboarding_checklist.rs b/packages/app-lib/src/state/onboarding_checklist.rs new file mode 100644 index 0000000000..b48d14529b --- /dev/null +++ b/packages/app-lib/src/state/onboarding_checklist.rs @@ -0,0 +1,85 @@ +use serde::{Deserialize, Serialize}; +use sqlx::{Executor, Sqlite}; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct OnboardingChecklist { + pub has_created_instance: bool, + pub has_logged_into_minecraft: bool, + pub has_logged_into_modrinth: bool, +} + +pub(crate) enum OnboardingChecklistItem { + CreatedInstance, + LoggedIntoMinecraft, + LoggedIntoModrinth, +} + +pub(crate) async fn get_onboarding_checklist( + exec: impl Executor<'_, Database = Sqlite>, +) -> crate::Result { + let row = sqlx::query!( + " + SELECT + has_created_instance, + has_logged_into_minecraft, + has_logged_into_modrinth + FROM onboarding_checklist + WHERE id = 0 + ", + ) + .fetch_one(exec) + .await?; + + Ok(OnboardingChecklist { + has_created_instance: row.has_created_instance == 1, + has_logged_into_minecraft: row.has_logged_into_minecraft == 1, + has_logged_into_modrinth: row.has_logged_into_modrinth == 1, + }) +} + +pub(crate) async fn mark_onboarding_checklist_item( + item: OnboardingChecklistItem, + pool: &sqlx::SqlitePool, +) -> crate::Result> { + let result = match item { + OnboardingChecklistItem::CreatedInstance => { + sqlx::query!( + " + UPDATE onboarding_checklist + SET has_created_instance = TRUE + WHERE id = 0 AND has_created_instance = FALSE + ", + ) + .execute(pool) + .await? + } + OnboardingChecklistItem::LoggedIntoMinecraft => { + sqlx::query!( + " + UPDATE onboarding_checklist + SET has_logged_into_minecraft = TRUE + WHERE id = 0 AND has_logged_into_minecraft = FALSE + ", + ) + .execute(pool) + .await? + } + OnboardingChecklistItem::LoggedIntoModrinth => { + sqlx::query!( + " + UPDATE onboarding_checklist + SET has_logged_into_modrinth = TRUE + WHERE id = 0 AND has_logged_into_modrinth = FALSE + ", + ) + .execute(pool) + .await? + } + }; + + if result.rows_affected() == 0 { + return Ok(None); + } + + Ok(Some(get_onboarding_checklist(pool).await?)) +} From 7782458ec9a5e1a51bd9d634b7ca77980c842e51 Mon Sep 17 00:00:00 2001 From: tdgao Date: Wed, 15 Jul 2026 11:07:42 -0700 Subject: [PATCH 04/55] feat: add welcome page --- apps/app-frontend/src/pages/Index.vue | 100 ++++++++++++++++-- .../src/providers/setup/creation-modal.ts | 4 + 2 files changed, 98 insertions(+), 6 deletions(-) diff --git a/apps/app-frontend/src/pages/Index.vue b/apps/app-frontend/src/pages/Index.vue index 16223dde1b..fd1da867d5 100644 --- a/apps/app-frontend/src/pages/Index.vue +++ b/apps/app-frontend/src/pages/Index.vue @@ -1,8 +1,9 @@