| 1 | /** |
| 2 | * Tiny on-screen debug log + remote terminal forwarder. |
| 3 | * Call dbg('some message') from anywhere to show it in the overlay |
| 4 | * and forward it to the Vite dev server terminal as [web] prefix. |
| 5 | * Tap the overlay to dismiss. |
| 6 | * |
| 7 | * In production (import.meta.env.PROD) all calls are no-ops. |
| 8 | */ |
| 9 | |
| 10 | const ENABLED = false // flip to true to re-enable on-screen log + remote forwarding |
| 11 | |
| 12 | let panel: HTMLDivElement | null = null |
| 13 | const MAX_LINES = 20 |
| 14 | const lines: string[] = [] |
| 15 | |
| 16 | function getPanel(): HTMLDivElement { |
| 17 | if (panel) return panel |
| 18 | panel = document.createElement('div') |
| 19 | Object.assign(panel.style, { |
| 20 | position: 'fixed', |
| 21 | bottom: '0', |
| 22 | left: '0', |
| 23 | right: '0', |
| 24 | maxHeight: '40vh', |
| 25 | overflowY: 'auto', |
| 26 | background: 'rgba(0,0,0,0.78)', |
| 27 | color: '#0f0', |
| 28 | fontFamily: 'monospace', |
| 29 | fontSize: '11px', |
| 30 | padding: '8px', |
| 31 | zIndex: '9999', |
| 32 | whiteSpace: 'pre-wrap', |
| 33 | wordBreak: 'break-all', |
| 34 | pointerEvents: 'auto', |
| 35 | }) |
| 36 | panel.addEventListener('click', () => { |
| 37 | panel?.remove() |
| 38 | panel = null |
| 39 | lines.length = 0 |
| 40 | }) |
| 41 | document.body.appendChild(panel) |
| 42 | return panel |
| 43 | } |
| 44 | |
| 45 | function ts(): string { |
| 46 | const d = new Date() |
| 47 | return `${d.getHours().toString().padStart(2, '0')}:${d.getMinutes().toString().padStart(2, '0')}:${d.getSeconds().toString().padStart(2, '0')}.${d.getMilliseconds().toString().padStart(3, '0')}` |
| 48 | } |
| 49 | |
| 50 | function sendToServer(level: 'log' | 'error', msg: string): void { |
| 51 | // Best-effort: fire-and-forget, never throws |
| 52 | fetch('/__log', { |
| 53 | method: 'POST', |
| 54 | headers: { 'content-type': 'application/json' }, |
| 55 | body: JSON.stringify({ level, msg }), |
| 56 | }).catch(() => { /* dev server may not be listening */ }) |
| 57 | } |
| 58 | |
| 59 | function display(msg: string, isError = false): void { |
| 60 | lines.push(msg) |
| 61 | if (lines.length > MAX_LINES) lines.shift() |
| 62 | const p = getPanel() |
| 63 | p.style.color = isError ? '#f55' : '#0f0' |
| 64 | p.textContent = lines.join('\n') |
| 65 | p.scrollTop = p.scrollHeight |
| 66 | } |
| 67 | |
| 68 | export function dbg(...args: unknown[]): void { |
| 69 | if (import.meta.env.PROD || !ENABLED) return |
| 70 | const msg = `[${ts()}] ${args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' ')}` |
| 71 | console.log(msg) |
| 72 | display(msg) |
| 73 | sendToServer('log', msg) |
| 74 | } |
| 75 | |
| 76 | export function dbgError(...args: unknown[]): void { |
| 77 | if (import.meta.env.PROD || !ENABLED) return |
| 78 | const msg = `[${ts()}] ❌ ${args.map(a => a instanceof Error ? a.message : typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' ')}` |
| 79 | console.error(msg) |
| 80 | display(msg, true) |
| 81 | sendToServer('error', msg) |
| 82 | } |
| 83 | |
| 84 | export function remoteLog(msg: string): void { |
| 85 | if (import.meta.env.PROD) return |
| 86 | sendToServer('log', msg) |
| 87 | } |