Skip to content

Commit 95669df

Browse files
committed
chore(debug): add runtime debug overlay
1 parent dc4ee75 commit 95669df

3 files changed

Lines changed: 172 additions & 2 deletions

File tree

apps/web/src/App.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<template>
22
<router-view />
3+
<RuntimeDebugOverlay />
34
</template>
45

56
<script setup lang="ts">
@@ -17,6 +18,7 @@
1718
*/
1819
import { onMounted } from 'vue';
1920
import { useAuthStore } from './stores/auth';
21+
import RuntimeDebugOverlay from './components/debug/RuntimeDebugOverlay.vue';
2022
2123
const authStore = useAuthStore();
2224
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
<template>
2+
<div
3+
v-if="enabled && !dismissed"
4+
class="runtime-debug-overlay"
5+
role="status"
6+
aria-live="polite"
7+
>
8+
<div class="row">
9+
<strong>Debug</strong>
10+
<span class="pill">{{ pillText }}</span>
11+
<div class="actions">
12+
<button class="btn" type="button" @click="copyDiagnostics">Copy</button>
13+
<button class="btn" type="button" @click="dismissed = true">Hide</button>
14+
</div>
15+
</div>
16+
<div class="grid">
17+
<div><span class="label">Theme</span>{{ themeStore.themeSlug }} ({{ themeStore.resolvedMode }})</div>
18+
<div><span class="label">LightSafe</span>{{ lightSafe }}</div>
19+
<div><span class="label">API</span>{{ apiBaseUrl }}</div>
20+
<div><span class="label">URL</span>{{ href }}</div>
21+
</div>
22+
</div>
23+
</template>
24+
25+
<script setup lang="ts">
26+
import { computed, ref } from 'vue'
27+
import { useThemeStore } from '@/stores/theme'
28+
import { API_BASE_URL } from '@/utils/apiUrl'
29+
import { isDebugEnabled } from '@/utils/debug'
30+
31+
const themeStore = useThemeStore()
32+
33+
const dismissed = ref(false)
34+
const enabled = computed(() => isDebugEnabled('theme') || isDebugEnabled('api'))
35+
const apiBaseUrl = API_BASE_URL
36+
37+
const href = computed(() => {
38+
if (typeof window === 'undefined') return ''
39+
return window.location.href
40+
})
41+
42+
const lightSafe = computed(() => {
43+
if (typeof document === 'undefined') return 'n/a'
44+
return document.documentElement.getAttribute('data-light-safe') === '1' ? '1' : '0'
45+
})
46+
47+
const pillText = computed(() => {
48+
const parts: string[] = []
49+
if (isDebugEnabled('theme')) parts.push('theme')
50+
if (isDebugEnabled('api')) parts.push('api')
51+
return parts.length ? parts.join('+') : 'off'
52+
})
53+
54+
async function copyDiagnostics() {
55+
const payload = {
56+
href: href.value,
57+
apiBaseUrl,
58+
theme: themeStore.themeSlug,
59+
colorMode: themeStore.colorMode,
60+
resolvedMode: themeStore.resolvedMode,
61+
lightSafe: lightSafe.value,
62+
bodyClass: typeof document === 'undefined' ? '' : document.body.className,
63+
htmlClass: typeof document === 'undefined' ? '' : document.documentElement.className,
64+
}
65+
const text = JSON.stringify(payload, null, 2)
66+
67+
try {
68+
await navigator.clipboard.writeText(text)
69+
} catch {
70+
const textarea = document.createElement('textarea')
71+
textarea.value = text
72+
textarea.style.position = 'fixed'
73+
textarea.style.left = '-9999px'
74+
document.body.appendChild(textarea)
75+
textarea.focus()
76+
textarea.select()
77+
document.execCommand('copy')
78+
textarea.remove()
79+
}
80+
}
81+
</script>
82+
83+
<style scoped lang="scss">
84+
.runtime-debug-overlay {
85+
position: fixed;
86+
left: 12px;
87+
bottom: 12px;
88+
z-index: 9999;
89+
max-width: min(560px, calc(100vw - 24px));
90+
padding: 10px 12px;
91+
border-radius: 12px;
92+
border: 1px solid rgba(0, 0, 0, 0.16);
93+
background: rgba(255, 255, 255, 0.88);
94+
color: #111;
95+
backdrop-filter: blur(10px);
96+
box-shadow: 0 10px 28px rgba(0, 0, 0, 0.14);
97+
98+
.row {
99+
display: flex;
100+
gap: 10px;
101+
align-items: center;
102+
margin-bottom: 8px;
103+
}
104+
105+
.pill {
106+
font-size: 11px;
107+
padding: 2px 8px;
108+
border-radius: 999px;
109+
background: rgba(99, 102, 241, 0.12);
110+
border: 1px solid rgba(99, 102, 241, 0.22);
111+
color: #1f2937;
112+
}
113+
114+
.actions {
115+
margin-left: auto;
116+
display: flex;
117+
gap: 8px;
118+
align-items: center;
119+
}
120+
121+
.btn {
122+
font-size: 11px;
123+
padding: 4px 8px;
124+
border-radius: 8px;
125+
border: 1px solid rgba(0, 0, 0, 0.14);
126+
background: rgba(255, 255, 255, 0.9);
127+
cursor: pointer;
128+
}
129+
130+
.grid {
131+
display: grid;
132+
grid-template-columns: 1fr;
133+
gap: 4px;
134+
font-size: 12px;
135+
line-height: 1.3;
136+
word-break: break-word;
137+
}
138+
139+
.label {
140+
display: inline-block;
141+
min-width: 78px;
142+
font-weight: 700;
143+
opacity: 0.75;
144+
margin-right: 8px;
145+
}
146+
}
147+
148+
:global(.body--dark) .runtime-debug-overlay {
149+
border-color: rgba(255, 255, 255, 0.16);
150+
background: rgba(0, 0, 0, 0.62);
151+
color: rgba(255, 255, 255, 0.92);
152+
}
153+
154+
:global(.body--dark) .runtime-debug-overlay .btn {
155+
border-color: rgba(255, 255, 255, 0.18);
156+
background: rgba(0, 0, 0, 0.35);
157+
color: rgba(255, 255, 255, 0.9);
158+
}
159+
160+
:global(.body--dark) .runtime-debug-overlay .pill {
161+
background: rgba(99, 102, 241, 0.22);
162+
border-color: rgba(99, 102, 241, 0.35);
163+
color: rgba(255, 255, 255, 0.92);
164+
}
165+
</style>

apps/web/src/utils/debug.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ function readFlagFromQuery(namespace: DebugNamespace): boolean {
1616
if (typeof window === 'undefined') return false
1717
const params = new URLSearchParams(window.location.search)
1818

19-
if (params.get('debug') === '1') return true
19+
const debug = params.get('debug')
20+
if (debug !== null) {
21+
const normalized = debug.trim().toLowerCase()
22+
if (!['0', 'false', 'off', 'no'].includes(normalized)) return true
23+
}
2024

2125
const key = namespace === 'theme' ? 'debugTheme' : 'debugApi'
2226
if (params.get(key) === '1' || params.has(key)) return true
@@ -60,4 +64,3 @@ export function debugGroupEnd(namespace: DebugNamespace): void {
6064
if (!isDebugEnabled(namespace)) return
6165
console.groupEnd()
6266
}
63-

0 commit comments

Comments
 (0)