From 467e03e8565dbb800542238190249e494c55d538 Mon Sep 17 00:00:00 2001 From: chrislng <70879967+chrislng@users.noreply.github.com> Date: Thu, 4 Jun 2026 00:30:04 -0400 Subject: [PATCH 1/5] Logout: clear local appdata and reset user state Prevents startup hangs and stale data failures caused by corrupted local data. --- src/components/LogoutContainer.vue | 4 ++-- src/internal/databases/UserData.ts | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/components/LogoutContainer.vue b/src/components/LogoutContainer.vue index 33a4324..ead0226 100644 --- a/src/components/LogoutContainer.vue +++ b/src/components/LogoutContainer.vue @@ -57,8 +57,8 @@ export default { document.querySelector("ion-back-button").click(); }, - disconnectUser() { - UserData.resetPreferences(false); // `false` to keep hasSeenTutorial to true + async disconnectUser() { + await UserData.clearLocalDataAndReset(false); // wipe local DBs and cache, keep tutorial flag // Go to main page this.$router.replace("/register") diff --git a/src/internal/databases/UserData.ts b/src/internal/databases/UserData.ts index a97c8ed..d0ecd36 100644 --- a/src/internal/databases/UserData.ts +++ b/src/internal/databases/UserData.ts @@ -145,6 +145,13 @@ export class UserData { this.updateFile(); } + public static async clearLocalDataAndReset(resetTutorial = true) { + // Remove local mirrored DB files and cache, then reset preferences + await this.deleteLocalDatabaseFiles(); + await this.invalidateCacheFile(); + this.resetPreferences(resetTutorial); + } + public static async ensureDataSchemaUpToDate() { await this.populate(); const storedVersion = this.data?.schemaVersion ?? 0; From f47e816a5c74fb70b8fe1fb9a00dcb0fc4728af2 Mon Sep 17 00:00:00 2001 From: chrislng <70879967+chrislng@users.noreply.github.com> Date: Thu, 4 Jun 2026 00:30:52 -0400 Subject: [PATCH 2/5] Defensive discovery loading: UI guards + DB validation --- src/components/DiscoveryDetails.vue | 34 +++++++++++++++++-- src/components/DiscoveryDetailsFullModale.vue | 31 +++++++++++++++-- src/internal/databases/Database.ts | 32 +++++++++++++++-- 3 files changed, 89 insertions(+), 8 deletions(-) diff --git a/src/components/DiscoveryDetails.vue b/src/components/DiscoveryDetails.vue index 518a2ab..806d23e 100644 --- a/src/components/DiscoveryDetails.vue +++ b/src/components/DiscoveryDetails.vue @@ -1,4 +1,5 @@ @@ -1041,4 +1057,15 @@ a { color: black; font-weight: normal; } + +.loading-placeholder { + display: flex; + align-items: center; + justify-content: center; + height: 60vh; +} +.loading-text { + font-size: 3.8vw; + color: #666; +} \ No newline at end of file diff --git a/src/internal/databases/Database.ts b/src/internal/databases/Database.ts index 9af5fb5..5d0f49b 100644 --- a/src/internal/databases/Database.ts +++ b/src/internal/databases/Database.ts @@ -29,12 +29,38 @@ export abstract class Database { if (typeof content.data === "string") { const parsed = JSON.parse(content.data); + + // Validate and construct elements; if any element is malformed, + // consider the local DB file corrupt and fetch from server instead. + const validated: Discovery[] = []; + for (const element of parsed) { - // for (const element of parsed.data) { - this.data.push(this.createSingleElement(element)); + try { + const candidate = this.createSingleElement(element); + + // Basic validation: must have numeric id and a callable getTitle + if ( + !candidate || + typeof candidate.id !== "number" || + typeof candidate.getTitle !== "function" + ) { + throw new Error("Invalid discovery object"); + } + + validated.push(candidate); + } catch (e) { + console.warn(`${this.type} db: detected corrupt element while parsing local file (${e}). Will rebuild from server.`); + // Delete the corrupted local file and populate from server + try { + await Filesystem.deleteFile({ path: this.path, directory: Directory.Data }); + } catch (_ignored) {} + + return await this.populateFromServer(); + } } - } + this.data = validated; + } console.log(`${this.type} db: successfully populated (locally).`); } catch (err) { From 2ed39917d8af34f6b4b0655efdfe12f0a94837a3 Mon Sep 17 00:00:00 2001 From: chrislng <70879967+chrislng@users.noreply.github.com> Date: Thu, 4 Jun 2026 00:38:37 -0400 Subject: [PATCH 3/5] Increment to version 8.0.2 (802) --- android/app/build.gradle | 4 ++-- ios/App/App.xcodeproj/project.pbxproj | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 2ec8475..b0293c4 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -7,8 +7,8 @@ android { applicationId "com.maison.mona" minSdkVersion 26 targetSdkVersion 35 - versionCode 801 - versionName "8.0.1" + versionCode 802 + versionName "8.0.2" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" aaptOptions { // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. diff --git a/ios/App/App.xcodeproj/project.pbxproj b/ios/App/App.xcodeproj/project.pbxproj index f857615..b9589d4 100644 --- a/ios/App/App.xcodeproj/project.pbxproj +++ b/ios/App/App.xcodeproj/project.pbxproj @@ -348,12 +348,12 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 801; + CURRENT_PROJECT_VERSION = 802; DEVELOPMENT_TEAM = 843UJ9V2XK; INFOPLIST_FILE = App/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 8.0.1; + MARKETING_VERSION = 8.0.2; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; PRODUCT_BUNDLE_IDENTIFIER = com.mona.starter; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -369,12 +369,12 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 801; + CURRENT_PROJECT_VERSION = 802; DEVELOPMENT_TEAM = 843UJ9V2XK; INFOPLIST_FILE = App/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 8.0.1; + MARKETING_VERSION = 8.0.2; PRODUCT_BUNDLE_IDENTIFIER = "ca.umontreal.mona-ios"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; From 08981c095cb9dbf6a567dd520738035b3b6c28ef Mon Sep 17 00:00:00 2001 From: chrislng <70879967+chrislng@users.noreply.github.com> Date: Thu, 4 Jun 2026 01:06:35 -0400 Subject: [PATCH 4/5] Reset in-memory discovery caches when clearing local data --- src/internal/databases/Database.ts | 5 +++++ src/internal/databases/UserData.ts | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/internal/databases/Database.ts b/src/internal/databases/Database.ts index 5d0f49b..eea574a 100644 --- a/src/internal/databases/Database.ts +++ b/src/internal/databases/Database.ts @@ -152,4 +152,9 @@ export abstract class Database { return this.data.slice(a, b); } + + // Reset in-memory data for this database (useful when clearing local files) + public static resetData() { + this.data = []; + } } diff --git a/src/internal/databases/UserData.ts b/src/internal/databases/UserData.ts index d0ecd36..d4edfc8 100644 --- a/src/internal/databases/UserData.ts +++ b/src/internal/databases/UserData.ts @@ -149,6 +149,10 @@ export class UserData { // Remove local mirrored DB files and cache, then reset preferences await this.deleteLocalDatabaseFiles(); await this.invalidateCacheFile(); + // Clear any in-memory caches that could hold stale discovery objects + this.sortedDiscoveries = []; + this.sortedDiscoveriesDistance = []; + this.resetPreferences(resetTutorial); } @@ -315,6 +319,22 @@ export class UserData { }).catch(() => undefined), ), ); + + // Also reset in-memory DBs so app doesn't keep using stale data after files are deleted + try { + const { ArtworkDatabase } = await import("@/internal/databases/ArtworkDatabase"); + const { PlaceDatabase } = await import("@/internal/databases/PlaceDatabase"); + const { HeritageDatabase } = await import("@/internal/databases/HeritageDatabase"); + const { BadgeDatabase } = await import("@/internal/databases/BadgeDatabase"); + + ArtworkDatabase.resetData(); + PlaceDatabase.resetData(); + HeritageDatabase.resetData(); + BadgeDatabase.resetData(); + } catch (err) { + // If dynamic import fails for any reason, ignore — best-effort reset + console.warn("Failed to reset in-memory DBs:", err); + } } private static parseCachePayload(parsed: any): { version: number; data: any[] } | null { From 36979f4e83011dec3fac6950d5ae88815ab46e34 Mon Sep 17 00:00:00 2001 From: chrislng <70879967+chrislng@users.noreply.github.com> Date: Thu, 4 Jun 2026 01:06:47 -0400 Subject: [PATCH 5/5] Increment app version to 8.0.3 (803) --- android/app/build.gradle | 4 ++-- ios/App/App.xcodeproj/project.pbxproj | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index b0293c4..49393e2 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -7,8 +7,8 @@ android { applicationId "com.maison.mona" minSdkVersion 26 targetSdkVersion 35 - versionCode 802 - versionName "8.0.2" + versionCode 803 + versionName "8.0.3" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" aaptOptions { // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. diff --git a/ios/App/App.xcodeproj/project.pbxproj b/ios/App/App.xcodeproj/project.pbxproj index b9589d4..a053e1b 100644 --- a/ios/App/App.xcodeproj/project.pbxproj +++ b/ios/App/App.xcodeproj/project.pbxproj @@ -348,12 +348,12 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 802; + CURRENT_PROJECT_VERSION = 803; DEVELOPMENT_TEAM = 843UJ9V2XK; INFOPLIST_FILE = App/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 8.0.2; + MARKETING_VERSION = 8.0.3; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; PRODUCT_BUNDLE_IDENTIFIER = com.mona.starter; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -369,12 +369,12 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 802; + CURRENT_PROJECT_VERSION = 803; DEVELOPMENT_TEAM = 843UJ9V2XK; INFOPLIST_FILE = App/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 8.0.2; + MARKETING_VERSION = 8.0.3; PRODUCT_BUNDLE_IDENTIFIER = "ca.umontreal.mona-ios"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";