add better support info when there's an error on startup

This commit is contained in:
2026-02-22 16:05:24 -07:00
parent dd8850aa24
commit 47ead0ced2
5 changed files with 65 additions and 9 deletions

View File

@@ -49,6 +49,8 @@ export const DEFAULT_PUSH_SERVER =
export const IMAGE_TYPE_PROFILE = "profile"; export const IMAGE_TYPE_PROFILE = "profile";
export const SUPPORT_EMAIL = "info@TimeSafari.app";
export const PASSKEYS_ENABLED = export const PASSKEYS_ENABLED =
!!import.meta.env.VITE_PASSKEYS_ENABLED || false; !!import.meta.env.VITE_PASSKEYS_ENABLED || false;

View File

@@ -412,6 +412,18 @@ router.beforeEach(async (to, _from, next) => {
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}); });
// Store error details so StartView can display them
const errorMessage = error instanceof Error ? error.message : String(error);
const errorStack = error instanceof Error ? error.stack || "" : "";
try {
sessionStorage.setItem(
"startupError",
JSON.stringify({ message: errorMessage, stack: errorStack }),
);
} catch {
// sessionStorage may be unavailable
}
// Redirect to start page if identity creation fails // Redirect to start page if identity creation fails
// This allows users to manually create an identity or troubleshoot // This allows users to manually create an identity or troubleshoot
logger.info( logger.info(

View File

@@ -59,6 +59,7 @@ import {
} from "../interfaces/deepLinks"; } from "../interfaces/deepLinks";
import { logConsoleAndDb } from "../db/databaseUtil"; import { logConsoleAndDb } from "../db/databaseUtil";
import { logger } from "../utils/logger"; import { logger } from "../utils/logger";
import { SUPPORT_EMAIL } from "../constants/app";
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
@@ -105,7 +106,7 @@ const goHome = () => router.replace({ name: "home" });
const reportIssue = () => { const reportIssue = () => {
// Open a support form or email // Open a support form or email
window.open( window.open(
"mailto:support@timesafari.app?subject=Invalid Deep Link&body=" + `mailto:${SUPPORT_EMAIL}?subject=Invalid Deep Link&body=` +
encodeURIComponent( encodeURIComponent(
`I encountered an error with a deep link: timesafari://${originalPath.value}\nError: ${errorMessage.value}`, `I encountered an error with a deep link: timesafari://${originalPath.value}\nError: ${errorMessage.value}`,
), ),

View File

@@ -552,8 +552,8 @@
</h2> </h2>
<p> <p>
Contact us at Contact us at
<a href="mailto:info@TimeSafari.app" class="text-blue-500" <a :href="`mailto:${SUPPORT_EMAIL}`" class="text-blue-500"
>info@TimeSafari.app</a >{{ SUPPORT_EMAIL }}</a
> >
</p> </p>
@@ -591,7 +591,7 @@ import { copyToClipboard } from "../services/ClipboardService";
import * as Package from "../../package.json"; import * as Package from "../../package.json";
import QuickNav from "../components/QuickNav.vue"; import QuickNav from "../components/QuickNav.vue";
import { APP_SERVER, NotificationIface } from "../constants/app"; import { APP_SERVER, NotificationIface, SUPPORT_EMAIL } from "../constants/app";
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin"; import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
import { QRNavigationService } from "@/services/QRNavigationService"; import { QRNavigationService } from "@/services/QRNavigationService";
import { UNNAMED_ENTITY_NAME } from "@/constants/entities"; import { UNNAMED_ENTITY_NAME } from "@/constants/entities";
@@ -643,7 +643,7 @@ export default class HelpView extends Vue {
showVerifiable = false; showVerifiable = false;
APP_SERVER = APP_SERVER; APP_SERVER = APP_SERVER;
// Capacitor reference removed - using QRNavigationService instead SUPPORT_EMAIL = SUPPORT_EMAIL;
/** /**
* Initialize notification helpers * Initialize notification helpers

View File

@@ -26,6 +26,31 @@
</router-link> </router-link>
</div> </div>
<!-- Startup error banner -->
<div
v-if="startupError"
class="max-w-3xl mx-auto mb-6 p-4 bg-red-50 border border-red-300 rounded-lg"
>
<h2 class="text-red-800 font-semibold text-lg mb-2">Startup Error</h2>
<p class="text-red-700 mb-3">
The app encountered a critical error during startup. This is often
caused by a database problem. Please send the details below to
<a :href="`mailto:${SUPPORT_EMAIL}`" class="underline font-semibold">{{
SUPPORT_EMAIL
}}</a>
so we can help resolve it.
</p>
<details class="bg-white border border-red-200 rounded p-3">
<summary class="cursor-pointer text-red-700 font-medium">
Error details
</summary>
<pre
class="mt-2 text-xs text-red-900 whitespace-pre-wrap break-words"
>{{ startupError }}</pre
>
</details>
</div>
<!-- id used by puppeteer test script --> <!-- id used by puppeteer test script -->
<div id="start-question"> <div id="start-question">
<div class="max-w-3xl mx-auto"> <div class="max-w-3xl mx-auto">
@@ -139,7 +164,7 @@
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import { Router } from "vue-router"; import { Router } from "vue-router";
import { AppString, PASSKEYS_ENABLED } from "../constants/app"; import { AppString, PASSKEYS_ENABLED, SUPPORT_EMAIL } from "../constants/app";
import { PlatformServiceMixin } from "../utils/PlatformServiceMixin"; import { PlatformServiceMixin } from "../utils/PlatformServiceMixin";
import { logger } from "../utils/logger"; import { logger } from "../utils/logger";
@@ -157,10 +182,12 @@ export default class StartView extends Vue {
// Feature flags and application constants // Feature flags and application constants
PASSKEYS_ENABLED = PASSKEYS_ENABLED; PASSKEYS_ENABLED = PASSKEYS_ENABLED;
SUPPORT_EMAIL = SUPPORT_EMAIL;
// Component state for identity generation // Component state for identity generation
givenName = ""; givenName = "";
numAccounts = 0; numAccounts = 0;
startupError = "";
/** /**
* Computed property for primary action button styling * Computed property for primary action button styling
@@ -201,11 +228,26 @@ export default class StartView extends Vue {
*/ */
async mounted() { async mounted() {
try { try {
// Load user settings using platform service const raw = sessionStorage.getItem("startupError");
if (raw) {
sessionStorage.removeItem("startupError");
try {
const parsed = JSON.parse(raw);
const parts = [parsed.message, parsed.stack].filter(Boolean);
this.startupError = parts.length > 0 ? parts.join("\n\n") : raw;
logger.error("[StartView] Displaying startup error to user", parsed);
} catch {
this.startupError = raw;
}
}
} catch {
// sessionStorage or JSON parse may fail; non-critical
}
try {
const settings = await this.$accountSettings(); const settings = await this.$accountSettings();
this.givenName = settings.firstName || ""; this.givenName = settings.firstName || "";
// Load account count for display logic
this.numAccounts = await retrieveAccountCount(); this.numAccounts = await retrieveAccountCount();
logger.debug("[StartView] Component mounted", { logger.debug("[StartView] Component mounted", {
@@ -215,7 +257,6 @@ export default class StartView extends Vue {
}); });
} catch (error) { } catch (error) {
logger.error("[StartView] Failed to load initialization data", error); logger.error("[StartView] Failed to load initialization data", error);
// Continue with default behavior if settings load fails
this.givenName = ""; this.givenName = "";
this.numAccounts = 0; this.numAccounts = 0;
} }