From 0c2e136caec5ba2b436f476b8d140a3bf63f5377 Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Wed, 25 Mar 2026 23:04:24 -0400 Subject: [PATCH] Fix: resolve JS SyntaxErrors that crashed entire window.lt namespace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: two SyntaxErrors prevented the IIFE from completing, meaning window.lt was never created and all features were broken. Fixes applied: 1. CRITICAL: Renamed auth's apiFetch redeclaration → _apiFetchAuth (duplicate function declaration in strict-mode IIFE = SyntaxError) 2. CRITICAL: Renamed toast queue vars (_toastQueue/_toastActive already declared by original showToast in outer scope = SyntaxError) Replaced heavy queue wrapper with lightweight progress-bar injector 3. timer.countdown: classList.add(urgentClass) throws on space-separated class string — split/filter/forEach now used 4. contextMenu: _ctxItems declared as [] but used as string-keyed object — changed to {} 5. Split pane divider: background was border-dim (7% opacity, invisible) — raised to border-color (16%), added ::before hit-area expansion, hover state uses accent-cyan for clear visual feedback 6. Light theme: html background-image was hardcoded cyan rgba, didn't update with data-theme — added explicit html[data-theme="light"] rule 7. Light theme: .lt-header background was hardcoded rgba(3,5,8,0.96) — added explicit light-mode override Co-Authored-By: Claude Sonnet 4.6 --- base.css | 27 +++++++++++++------ base.js | 82 +++++++++++++++++++++----------------------------------- 2 files changed, 49 insertions(+), 60 deletions(-) diff --git a/base.css b/base.css index 9e07e93..90a667a 100644 --- a/base.css +++ b/base.css @@ -3325,15 +3325,17 @@ html[data-theme="light"] { /* Hide CRT overlays in light mode */ html[data-theme="light"] body::before, html[data-theme="light"] body::after { display: none; } -/* Soften dot grid */ -html[data-theme="light"] body { - background-image: radial-gradient(circle, rgba(0,80,160,0.10) 1px, transparent 1px); +/* Override hardcoded dot-grid background on html element */ +html[data-theme="light"] { + background-image: radial-gradient(circle, rgba(0,80,160,0.12) 1px, transparent 1px); } -/* Nav / header adjustments */ +/* Header — hardcoded dark rgba needs explicit light override */ html[data-theme="light"] .lt-header { - background: rgba(238,241,246,0.96); + background: rgba(238,241,246,0.97); border-bottom-color: var(--border-color); } +html[data-theme="light"] .lt-main { background: transparent; } +html[data-theme="light"] .lt-section-header { background: linear-gradient(90deg, var(--bg-secondary) 0%, transparent 100%); } html[data-theme="light"] .lt-nav-link, html[data-theme="light"] .lt-nav-link:hover { color: var(--text-secondary); } html[data-theme="light"] .lt-nav-link.active { color: var(--accent-orange); } @@ -3943,12 +3945,20 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas } .lt-split-divider { flex: 0 0 5px; - background: var(--border-dim); + background: var(--border-color); /* 16% opacity — clearly visible */ cursor: col-resize; transition: background 0.15s; position: relative; user-select: none; touch-action: none; + /* Wider invisible hit area so it's easy to grab */ +} +.lt-split-divider::before { + content: ''; + position: absolute; + top: 0; bottom: 0; + left: -6px; right: -6px; + cursor: col-resize; } .lt-split-divider::after { content: '⠿'; @@ -3956,11 +3966,12 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas top: 50%; left: 50%; transform: translate(-50%, -50%); color: var(--text-muted); - font-size: 0.6rem; + font-size: 0.75rem; pointer-events: none; } .lt-split-divider:hover, -.lt-split-divider.is-dragging { background: var(--accent-cyan-border); } +.lt-split-divider.is-dragging { background: var(--accent-cyan); } +.lt-split--vertical .lt-split-divider::before { top: -6px; bottom: -6px; left: 0; right: 0; cursor: row-resize; } .lt-split--vertical .lt-split-divider { cursor: row-resize; } /* On mobile, stack vertically and hide divider */ @media (max-width: 767px) { diff --git a/base.js b/base.js index 7fed36f..18c976a 100644 --- a/base.js +++ b/base.js @@ -1582,7 +1582,7 @@ items = [{ label, icon, kbd, danger, divider, action }] ================================================================ */ let _ctxMenu = null; - let _ctxItems = []; + const _ctxItems = {}; function _ctxShow(x, y, items) { if (!_ctxMenu) { _ctxMenu = document.createElement('div'); @@ -2028,49 +2028,31 @@ }; /* ================================================================ - MODULE 47 — TOAST QUEUE (enhanced dispatch) - Wraps existing toast module with max-stack + progress bars + MODULE 47 — TOAST ENHANCEMENTS + Adds a drain progress bar to each toast. + The original showToast already handles queuing (section 2). ================================================================ */ - const _toastMaxStack = 5; - const _toastQueue = []; - let _toastActive = 0; - const _origToast = Object.assign({}, toast); - - function _toastEnqueue(type, msg, dur = 4000) { - if (_toastActive >= _toastMaxStack) { - _toastQueue.push({ type, msg, dur }); - return; - } - _toastActive++; - const id = _origToast[type] ? _origToast[type](msg, dur) : _origToast.info(msg, dur); - // Add progress bar to the toast element - setTimeout(() => { - const toastEl = document.querySelector(`#lt-toast-container .lt-toast:last-child`); - if (toastEl && !toastEl.querySelector('.lt-toast-progress')) { + // Patch showToast to inject a progress bar after each toast is created + const _origShowToast = showToast; + function showToast(message, type, duration) { + duration = duration || 4000; + const result = _origShowToast(message, type, duration); + // Inject drain bar into the most recently added toast + requestAnimationFrame(() => { + const container = document.getElementById('lt-toast-container'); + if (!container) return; + const toasts = container.querySelectorAll('.lt-toast'); + const last = toasts[toasts.length - 1]; + if (last && !last.querySelector('.lt-toast-progress')) { const bar = document.createElement('div'); bar.className = 'lt-toast-progress'; - bar.style.animationDuration = dur + 'ms'; - toastEl.appendChild(bar); + bar.style.animationDuration = duration + 'ms'; + last.appendChild(bar); } - }, 20); - setTimeout(() => { - _toastActive = Math.max(0, _toastActive - 1); - if (_toastQueue.length) { - const next = _toastQueue.shift(); - _toastEnqueue(next.type, next.msg, next.dur); - } - }, dur + 400); - return id; + }); + return result; } - // Override toast methods to use queue - ['success','error','warning','info'].forEach(t => { - if (toast[t]) { - const orig = toast[t].bind(toast); - toast[t] = (msg, dur) => _toastEnqueue(t, msg, dur || 4000); - } - }); - /* ================================================================ MODULE 48 — SIDEBAR SUBMENUS Auto-inits .lt-sidebar-group elements. @@ -2358,7 +2340,7 @@ clearInterval(handle); return; } - if (diff <= urgent) dom.classList.add(urgentClass); + if (diff <= urgent) urgentClass.split(/\s+/).filter(Boolean).forEach(c => dom.classList.add(c)); const h = Math.floor(diff / 3600), m = Math.floor((diff % 3600) / 60), s = diff % 60; dom.textContent = [h, m, s].map(n => String(n).padStart(2, '0')).join(':'); } @@ -2551,18 +2533,15 @@ isExpiringSoon: (secs = 60) => _authExpiry > 0 && Date.now() >= _authExpiry - secs * 1000, }; - // Patch lt.api to inject Authorization header and auto-refresh - const _origApiFetch = apiFetch; - async function apiFetch(method, url, body) { - if (_authAccess) { - if (auth.isExpiringSoon()) await auth.refresh(); - } + // Patch lt.api — auth-aware wrapper (renamed to avoid strict-mode duplicate declaration) + async function _apiFetchAuth(method, url, body) { + if (_authAccess && auth.isExpiringSoon()) await auth.refresh(); const opts = { method, headers: Object.assign({ 'Content-Type': 'application/json' }, csrfHeaders()) }; if (_authAccess) opts.headers['Authorization'] = 'Bearer ' + _authAccess; if (body !== undefined) opts.body = JSON.stringify(body); let resp; try { resp = await fetch(url, opts); } catch (err) { throw new Error('Network error: ' + err.message); } - // Auto-retry once on 401 after token refresh + // Auto-retry once on 401 after silent token refresh if (resp.status === 401 && _authRefresh) { const ok = await auth.refresh(); if (ok) { @@ -2575,12 +2554,11 @@ if (!resp.ok) throw new Error(data.error || data.message || 'HTTP ' + resp.status); return data; } - // Re-point api methods to patched apiFetch - api.get = url => apiFetch('GET', url); - api.post = (u, b) => apiFetch('POST', u, b); - api.put = (u, b) => apiFetch('PUT', u, b); - api.patch = (u, b) => apiFetch('PATCH', u, b); - api.delete = (u, b) => apiFetch('DELETE', u, b); + api.get = url => _apiFetchAuth('GET', url); + api.post = (u, b) => _apiFetchAuth('POST', u, b); + api.put = (u, b) => _apiFetchAuth('PUT', u, b); + api.patch = (u, b) => _apiFetchAuth('PATCH', u, b); + api.delete = (u, b) => _apiFetchAuth('DELETE', u, b); /* ================================================================ MODULE 54 — MARKDOWN RENDERER