Rehidratar Date al cargar caché y refresh no-destructivo

Bug raíz del click-no-abre-overlay: chrome.storage serializa con JSON,
los Date (sessionStartTime, sessionEndTime, estimatedEndDate) volvían
como strings ISO, y createWeeklyTracker llamaba .toLocaleDateString
en un string. rehydrateDates los devuelve a Date al cargar.

Mejoras adicionales:
- showOverlayError ya no destruye el panel si hay tracker visible:
  saca un aviso temporal en el header (auto-oculta a los 7s).
- DOM en el aviso construido con textContent, no innerHTML, para
  cerrar la puerta a XSS aunque el msg sea hardcodeado.
- Limpieza del andamio [CUT-DIAG] usado para encontrar el bug;
  __claudeUsageTracker se queda expuesto para depuración futura.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Carlos Narro
2026-06-04 14:45:00 +02:00
parent d2bc177376
commit 6e5f589156
2 changed files with 60 additions and 17 deletions

View File

@@ -662,10 +662,34 @@
let refreshPromise = null;
// -------- Cache (chrome.storage.local) --------
// chrome.storage serializa con JSON: los Date salen como strings ISO. Al
// cargar, rehidratamos los campos conocidos para que createWeeklyTracker
// pueda llamar toLocaleDateString/Time sin TypeError.
function rehydrateDates(cache) {
if (!cache) return null;
const toDate = (v) => {
if (v == null) return null;
if (v instanceof Date) return v;
if (typeof v === 'string') {
const d = new Date(v);
return isNaN(d.getTime()) ? null : d;
}
return v;
};
if (cache.pageData) {
cache.pageData.sessionStartTime = toDate(cache.pageData.sessionStartTime);
cache.pageData.sessionEndTime = toDate(cache.pageData.sessionEndTime);
}
if (cache.dailyData) {
cache.dailyData.estimatedEndDate = toDate(cache.dailyData.estimatedEndDate);
}
return cache;
}
function loadCache() {
return new Promise(resolve => {
try {
chrome.storage.local.get([CACHE_KEY], result => resolve(result[CACHE_KEY] || null));
chrome.storage.local.get([CACHE_KEY], result => resolve(rehydrateDates(result[CACHE_KEY] || null)));
} catch (e) {
resolve(null);
}
@@ -815,12 +839,7 @@
<span class="claude-usage-tracker-fab-text">…</span>
`;
fab.addEventListener('click', onFabClick);
// DIAG: si pointerdown fira pero click no, algo está cancelando el click
fab.addEventListener('pointerdown', () => {
console.log('[CUT-DIAG] FAB pointerdown recibido');
});
document.body.appendChild(fab);
console.log('[CUT-DIAG] FAB inyectado en DOM. id:', fab.id, 'parent:', fab.parentElement?.tagName);
loadCache().then(updateFabState);
}
@@ -842,27 +861,21 @@
else fab.classList.remove('claude-usage-tracker-fab--hidden');
}
async function onFabClick(event) {
console.log('[CUT-DIAG] onFabClick entrada, event:', event?.type, 'target:', event?.target?.tagName);
async function onFabClick() {
try {
const cache = await loadCache();
console.log('[CUT-DIAG] cache cargada:', cache ? 'OK pageData=' + !!cache.pageData + ' dailyData=' + !!cache.dailyData : 'null');
openOverlayPanel(cache);
const ov = document.getElementById(OVERLAY_ID);
console.log('[CUT-DIAG] overlay tras openOverlayPanel: existe=' + !!ov,
ov ? 'display=' + getComputedStyle(ov).display + ' zIndex=' + getComputedStyle(ov).zIndex + ' opacity=' + getComputedStyle(ov).opacity : '');
const ageMin = cache ? cacheAgeMin(cache.scrapedAt) : Infinity;
if (ageMin > CACHE_TTL_MS / 60000) {
refreshInBackground('overlay-stale');
}
} catch (e) {
console.error('[CUT-DIAG] onFabClick ERROR:', e);
console.error('[Claude Usage Tracker] FAB click error:', e);
}
}
// -------- Overlay propio --------
function openOverlayPanel(cache) {
console.log('[CUT-DIAG] openOverlayPanel called, cache válida:', !!(cache && cache.pageData));
try {
closeOverlayPanel();
const overlay = document.createElement('div');
@@ -904,9 +917,8 @@
document.body.appendChild(overlay);
document.addEventListener('keydown', overlayEscapeHandler);
console.log('[CUT-DIAG] overlay appended OK. body.lastChild.id=', document.body.lastChild?.id);
} catch (e) {
console.error('[CUT-DIAG] openOverlayPanel ERROR:', e);
console.error('[Claude Usage Tracker] overlay error:', e);
}
}
@@ -955,7 +967,26 @@
const ov = document.getElementById(OVERLAY_ID);
if (!ov) return;
const body = ov.querySelector('.claude-usage-tracker-overlay-body');
if (body) body.innerHTML = `<div class="claude-usage-tracker-overlay-empty">${msg}</div>`;
const hasTracker = body && body.querySelector('.claude-usage-tracker-container');
if (hasTracker) {
// No destruir el tracker visible — mostrar aviso temporal en el header
const header = ov.querySelector('.claude-usage-tracker-overlay-header');
if (header) {
const existing = header.querySelector('.claude-usage-tracker-overlay-notice');
if (existing) existing.remove();
const notice = document.createElement('span');
notice.className = 'claude-usage-tracker-overlay-notice';
notice.textContent = msg;
header.appendChild(notice);
setTimeout(() => { if (notice.parentNode) notice.remove(); }, 7000);
}
} else if (body) {
body.textContent = '';
const empty = document.createElement('div');
empty.className = 'claude-usage-tracker-overlay-empty';
empty.textContent = msg;
body.appendChild(empty);
}
}
// -------- Refresh: abrir modal en background, scrapear, cerrar --------

View File

@@ -666,6 +666,18 @@
animation: claude-tracker-spin-pulse 1.2s ease-in-out infinite;
}
.claude-usage-tracker-overlay-notice {
width: 100%;
margin-top: 8px;
padding: 6px 10px;
background: rgba(245, 158, 11, 0.12);
border: 1px solid rgba(245, 158, 11, 0.35);
border-radius: 6px;
font-size: 12px;
color: #f59e0b;
animation: claude-tracker-fade-in 0.2s ease;
}
@keyframes claude-tracker-spin-pulse {
0%, 100% { opacity: 0.6; }
50% { opacity: 1; }