2026-01-24 22:59:20 +00:00
|
|
|
import { createApp } from 'vue'
|
|
|
|
|
import { createPinia } from 'pinia'
|
|
|
|
|
import './style.css'
|
|
|
|
|
import App from './App.vue'
|
|
|
|
|
import router from './router'
|
2026-03-11 13:45:59 +00:00
|
|
|
import i18n from './i18n'
|
2026-06-17 19:21:42 -04:00
|
|
|
import { displayVersion } from '@/utils/version'
|
2026-04-02 18:20:52 +01:00
|
|
|
|
|
|
|
|
// Clipboard polyfill for HTTP (non-secure) contexts where navigator.clipboard is unavailable
|
|
|
|
|
if (!navigator.clipboard) {
|
|
|
|
|
Object.defineProperty(navigator, 'clipboard', {
|
|
|
|
|
value: {
|
|
|
|
|
async writeText(text: string) {
|
|
|
|
|
const ta = document.createElement('textarea')
|
|
|
|
|
ta.value = text
|
|
|
|
|
ta.style.cssText = 'position:fixed;opacity:0'
|
|
|
|
|
document.body.appendChild(ta)
|
|
|
|
|
ta.select()
|
|
|
|
|
document.execCommand('copy')
|
|
|
|
|
document.body.removeChild(ta)
|
|
|
|
|
},
|
|
|
|
|
async readText() { return '' },
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
2026-03-21 01:11:05 +00:00
|
|
|
import { useToast } from '@/composables/useToast'
|
2026-01-24 22:59:20 +00:00
|
|
|
|
|
|
|
|
const app = createApp(App)
|
|
|
|
|
const pinia = createPinia()
|
|
|
|
|
|
|
|
|
|
app.use(pinia)
|
|
|
|
|
app.use(router)
|
2026-03-11 13:45:59 +00:00
|
|
|
app.use(i18n)
|
2026-01-24 22:59:20 +00:00
|
|
|
|
2026-06-17 19:21:42 -04:00
|
|
|
// Global version formatter — normalizes version labels to a single "v" prefix
|
|
|
|
|
// (some sources already carry one, which produced "vv1.2.3"). Use `$ver(x)` in
|
|
|
|
|
// templates instead of hard-coding a `v` prefix.
|
|
|
|
|
app.config.globalProperties.$ver = displayVersion
|
|
|
|
|
|
2026-06-20 08:05:51 -04:00
|
|
|
// Keep a small ring buffer of the most recent errors on `window.__archyErrors`
|
|
|
|
|
// so a crash that only reproduces inside a specific runtime (e.g. the Android
|
|
|
|
|
// companion WebView, where there's no easy console) can be retrieved after the
|
|
|
|
|
// fact — read it from chrome://inspect, or we can surface it in a debug view.
|
|
|
|
|
interface ArchyErrorEntry { when: string; source: string; message: string; info?: string; stack?: string }
|
|
|
|
|
const errorLog: ArchyErrorEntry[] = []
|
|
|
|
|
;(window as unknown as { __archyErrors?: ArchyErrorEntry[] }).__archyErrors = errorLog
|
|
|
|
|
|
|
|
|
|
function recordError(source: string, err: unknown, info?: string) {
|
|
|
|
|
const e = err as { message?: string; stack?: string } | undefined
|
|
|
|
|
const message = (e?.message ?? String(err)) || 'unknown error'
|
|
|
|
|
const entry: ArchyErrorEntry = { when: new Date().toISOString(), source, message, info, stack: e?.stack }
|
|
|
|
|
errorLog.push(entry)
|
|
|
|
|
if (errorLog.length > 25) errorLog.shift()
|
|
|
|
|
console.error(`[${source}]`, err, info ?? '')
|
|
|
|
|
// Surface the real message (truncated) instead of a generic toast — this is a
|
|
|
|
|
// test/bug-bash build, and "Something went wrong" hides exactly what we need.
|
|
|
|
|
const short = message.length > 140 ? `${message.slice(0, 140)}…` : message
|
|
|
|
|
try {
|
|
|
|
|
useToast().error(`Something went wrong: ${short}`)
|
|
|
|
|
} catch { /* toast itself failed — the console + ring buffer still have it */ }
|
2026-03-21 01:11:05 +00:00
|
|
|
}
|
|
|
|
|
|
2026-06-20 08:05:51 -04:00
|
|
|
app.config.errorHandler = (err, _instance, info) => recordError('Vue Error', err, info)
|
|
|
|
|
|
|
|
|
|
// Vue's errorHandler only catches errors raised synchronously inside Vue's
|
|
|
|
|
// lifecycle/reactivity. Async rejections and plain runtime errors (e.g. a JS
|
|
|
|
|
// API missing in an older WebView) slip past it, so catch those too.
|
|
|
|
|
window.addEventListener('error', (ev) => recordError('window.error', ev.error ?? ev.message))
|
|
|
|
|
window.addEventListener('unhandledrejection', (ev) => recordError('unhandledrejection', ev.reason))
|
|
|
|
|
|
2026-01-24 22:59:20 +00:00
|
|
|
app.mount('#app')
|