Fix: resolve JS SyntaxErrors that crashed entire window.lt namespace

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 <noreply@anthropic.com>
This commit is contained in:
2026-03-25 23:04:24 -04:00
parent 6ad4cb2354
commit 0c2e136cae
2 changed files with 49 additions and 60 deletions
+30 -52
View File
@@ -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