Tiempo transcurrido
@@ -669,13 +677,10 @@
// ===========================================================================
const CACHE_KEY = 'claude_usage_cache';
- const CACHE_TTL_MS = 5 * 60 * 1000; // 5 min
const FAB_ID = 'claude-usage-tracker-fab';
- const OVERLAY_ID = 'claude-usage-tracker-overlay';
const TRACKER_ID = 'claude-usage-tracker';
let dialogObserver = null;
- let refreshPromise = null;
// -------- Cache (chrome.storage.local) --------
// chrome.storage serializa con JSON: los Date salen como strings ISO. Al
@@ -732,15 +737,6 @@
return (Date.now() - new Date(scrapedAt).getTime()) / 60000;
}
- function formatAge(min) {
- if (!isFinite(min)) return 'sin datos';
- if (min < 1) return 'ahora';
- if (min < 60) return `hace ${Math.floor(min)} min`;
- const h = Math.floor(min / 60);
- const m = Math.floor(min % 60);
- return `hace ${h}h ${m}m`;
- }
-
// -------- Detección del modal de Uso --------
function findUsageDialog() {
const dialogs = document.querySelectorAll('[role="dialog"]');
@@ -885,221 +881,44 @@
else fab.classList.remove('claude-usage-tracker-fab--hidden');
}
+ // Click en el FAB: abrir el modal de Uso de Claude. Intenta varios métodos
+ // por si el router no escucha hashchange (Next.js puede colgar de popstate o
+ // de clicks en sus propios links). El usuario se conforma con ver el panel
+ // nativo, así que no abrimos nada propio.
async function onFabClick() {
try {
- const cache = await loadCache();
- openOverlayPanel(cache);
- const ageMin = cache ? cacheAgeMin(cache.scrapedAt) : Infinity;
- if (ageMin > CACHE_TTL_MS / 60000) {
- refreshInBackground('overlay-stale');
- }
- } catch (e) {
- console.error('[Claude Usage Tracker] FAB click error:', e);
- }
- }
+ if (isUsageDialogOpen()) return;
- // -------- Overlay propio --------
- function openOverlayPanel(cache) {
- try {
- closeOverlayPanel();
- const overlay = document.createElement('div');
- overlay.id = OVERLAY_ID;
- overlay.className = 'claude-usage-tracker-overlay';
- overlay.addEventListener('click', e => {
- if (e.target === overlay) closeOverlayPanel();
- });
-
- const panel = document.createElement('div');
- panel.className = 'claude-usage-tracker-overlay-panel';
-
- const header = document.createElement('div');
- header.className = 'claude-usage-tracker-overlay-header';
- const ageMin = cache ? cacheAgeMin(cache.scrapedAt) : Infinity;
- header.innerHTML = `
-
${formatAge(ageMin)}
-
-
-
-
- `;
- header.querySelector('.claude-usage-tracker-overlay-close').addEventListener('click', closeOverlayPanel);
- header.querySelector('.claude-usage-tracker-overlay-refresh').addEventListener('click', () => refreshInBackground('overlay-manual'));
-
- const body = document.createElement('div');
- body.className = 'claude-usage-tracker-overlay-body';
-
- if (cache && cache.pageData) {
- body.appendChild(createWeeklyTracker(cache.pageData, cache.dailyData));
- } else {
- body.innerHTML = '
Sin datos aún. Refrescando…
';
- setTimeout(() => refreshInBackground('overlay-empty'), 0);
- }
-
- panel.appendChild(header);
- panel.appendChild(body);
- overlay.appendChild(panel);
- document.body.appendChild(overlay);
-
- document.addEventListener('keydown', overlayEscapeHandler);
- } catch (e) {
- console.error('[Claude Usage Tracker] overlay error:', e);
- }
- }
-
- function overlayEscapeHandler(e) {
- if (e.key === 'Escape') closeOverlayPanel();
- }
-
- function closeOverlayPanel() {
- const ov = document.getElementById(OVERLAY_ID);
- if (ov) ov.remove();
- document.removeEventListener('keydown', overlayEscapeHandler);
- }
-
- function setOverlaySpinner(active) {
- const ov = document.getElementById(OVERLAY_ID);
- if (!ov) return;
- let spinner = ov.querySelector('.claude-usage-tracker-overlay-spinner');
- if (active && !spinner) {
- spinner = document.createElement('span');
- spinner.className = 'claude-usage-tracker-overlay-spinner';
- spinner.textContent = '⟳ actualizando…';
- const header = ov.querySelector('.claude-usage-tracker-overlay-header');
- if (header) header.appendChild(spinner);
- } else if (!active && spinner) {
- spinner.remove();
- }
- }
-
- function renderOverlayWithCache(cache) {
- const ov = document.getElementById(OVERLAY_ID);
- if (!ov) return;
- const body = ov.querySelector('.claude-usage-tracker-overlay-body');
- const age = ov.querySelector('.claude-usage-tracker-overlay-age');
- if (body) {
- body.innerHTML = '';
- if (cache && cache.pageData) {
- body.appendChild(createWeeklyTracker(cache.pageData, cache.dailyData));
+ // Método 1: setear hash
+ if (location.hash !== '#settings/usage') {
+ location.hash = '#settings/usage';
} else {
- body.innerHTML = '
No he podido cargar los datos. Abre Uso manualmente.
';
+ // ya estaba seteado pero el modal no está abierto: limpiar primero
+ location.hash = '';
+ await new Promise(r => setTimeout(r, 100));
+ location.hash = '#settings/usage';
}
- }
- if (age && cache) age.textContent = formatAge(cacheAgeMin(cache.scrapedAt));
- }
+ await new Promise(r => setTimeout(r, 600));
+ if (isUsageDialogOpen()) return;
- function showOverlayError(msg) {
- const ov = document.getElementById(OVERLAY_ID);
- if (!ov) return;
- const body = ov.querySelector('.claude-usage-tracker-overlay-body');
- 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);
- }
- }
+ // Método 2: pushState + popstate
+ history.pushState(null, '', '#settings/usage');
+ window.dispatchEvent(new PopStateEvent('popstate'));
+ await new Promise(r => setTimeout(r, 600));
+ if (isUsageDialogOpen()) return;
- // -------- Refresh: abrir modal en background, scrapear, cerrar --------
- function waitForDialogWithData(timeoutMs) {
- return new Promise(resolve => {
- const t0 = Date.now();
- const check = () => {
- const d = findUsageDialog();
- if (d && dialogHasData(d)) return resolve(d);
- if (Date.now() - t0 > timeoutMs) return resolve(null);
- setTimeout(check, 80);
- };
- check();
- });
- }
-
- async function tryOpenViaPushState() {
- history.pushState(null, '', '#settings/usage');
- window.dispatchEvent(new PopStateEvent('popstate'));
- return waitForDialogWithData(700);
- }
-
- async function tryOpenViaHash() {
- location.hash = '#settings/usage';
- return waitForDialogWithData(700);
- }
-
- async function tryOpenViaClick() {
- const candidates = [...document.querySelectorAll('a, button')]
- .filter(el => (el.innerText || '').trim() === 'Uso');
- if (candidates.length === 0) return null;
- candidates[0].click();
- return waitForDialogWithData(1500);
- }
-
- function closeUsageModal() {
- const dialog = findUsageDialog();
- if (!dialog) return;
- const closeBtn = [...dialog.querySelectorAll('button')]
- .find(b => /close|cerrar/i.test(b.getAttribute('aria-label') || ''));
- if (closeBtn) { closeBtn.click(); return; }
- document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, cancelable: true }));
- }
-
- async function refreshInBackground(reason) {
- if (refreshPromise) return refreshPromise;
- refreshPromise = (async () => {
- console.log('[Claude Usage Tracker] refresh:', reason);
- setOverlaySpinner(true);
-
- const previousHash = location.hash;
- const wasOpen = isUsageDialogOpen();
-
- let dialog = wasOpen ? findUsageDialog() : null;
- if (!dialog) dialog = await tryOpenViaPushState();
- if (!dialog) dialog = await tryOpenViaHash();
- if (!dialog) dialog = await tryOpenViaClick();
-
- if (!dialog) {
- console.warn('[Claude Usage Tracker] no he podido abrir el modal automáticamente');
- setOverlaySpinner(false);
- showOverlayError('No he podido refrescar automáticamente — abre Uso manualmente y vuelve.');
- refreshPromise = null;
+ // Método 3: clickar el enlace "Uso" si está visible
+ const usoBtn = [...document.querySelectorAll('a, button')]
+ .find(el => (el.innerText || '').trim() === 'Uso' && el.offsetParent !== null);
+ if (usoBtn) {
+ usoBtn.click();
return;
}
- const pageData = parsePageData();
- let cache = null;
- if (pageData.weeklyUsage > 0 || pageData.resetDayIndex >= 0) {
- initSession();
- const dailyData = calculateDailyUsage(pageData);
- saveCache(pageData, dailyData, reason === 'overlay-manual' ? 'manual' : 'auto');
- cache = { pageData, dailyData, scrapedAt: new Date().toISOString() };
- updateFabState(cache);
- }
-
- // Cerrar modal solo si nosotros lo abrimos
- if (!wasOpen) {
- closeUsageModal();
- if (location.hash !== previousHash) {
- history.replaceState(null, '', location.pathname + location.search + previousHash);
- }
- }
-
- setOverlaySpinner(false);
- renderOverlayWithCache(cache);
- refreshPromise = null;
- })();
- return refreshPromise;
+ console.warn('[Claude Usage Tracker] No he podido abrir el modal de Uso. Ábrelo manualmente desde el avatar.');
+ } catch (e) {
+ console.error('[Claude Usage Tracker] FAB click error:', e);
+ }
}
// -------- Bootstrap --------
@@ -1128,30 +947,6 @@
}, 500);
}
- // Exposición para depuración manual desde la consola
- window.__claudeUsageTracker = {
- version: 'debug-fab-click',
- loadCache,
- saveCache,
- openOverlayPanel,
- closeOverlayPanel,
- refreshInBackground,
- findUsageDialog,
- isUsageDialogOpen,
- parsePageData,
- injectFloatingButton,
- updateFabState,
- fabExists: () => !!document.getElementById(FAB_ID),
- overlayExists: () => !!document.getElementById(OVERLAY_ID),
- forceShowOverlay: async () => {
- const cache = await loadCache();
- console.log('[CUT-DIAG forceShowOverlay] cache:', cache);
- openOverlayPanel(cache);
- return !!document.getElementById(OVERLAY_ID);
- }
- };
- console.log('[CUT-DIAG] window.__claudeUsageTracker expuesto. Métodos:', Object.keys(window.__claudeUsageTracker).join(', '));
-
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', bootstrap);
} else {
diff --git a/styles.css b/styles.css
index 2e5b7e7..6689c8c 100644
--- a/styles.css
+++ b/styles.css
@@ -576,126 +576,31 @@
}
/* ===========================================================================
- Overlay propio — abierto al click del FAB
+ Resumen — hora del próximo reinicio de sesión
=========================================================================== */
-.claude-usage-tracker-overlay {
- position: fixed;
- inset: 0;
- z-index: 999999;
- background: rgba(0, 0, 0, 0.55);
- backdrop-filter: blur(4px);
- -webkit-backdrop-filter: blur(4px);
- display: flex;
- align-items: flex-start;
- justify-content: center;
- padding: 60px 16px 40px;
- overflow-y: auto;
- animation: claude-tracker-fade-in 0.15s ease;
-}
-
-@keyframes claude-tracker-fade-in {
- from { opacity: 0; }
- to { opacity: 1; }
-}
-
-.claude-usage-tracker-overlay-panel {
- width: 100%;
- max-width: 720px;
- background: #1a1a1d;
- border: 1px solid rgba(255, 255, 255, 0.1);
- border-radius: 14px;
- box-shadow: 0 24px 64px rgba(0, 0, 0, 0.6);
- padding: 16px 20px 20px;
- color: #f5f5f5;
- font-family: inherit;
-}
-
-.claude-usage-tracker-overlay-header {
+.session-reset-info {
display: flex;
align-items: center;
- justify-content: space-between;
- gap: 12px;
- margin-bottom: 12px;
- padding-bottom: 12px;
- border-bottom: 1px solid rgba(255, 255, 255, 0.08);
- flex-wrap: wrap;
-}
-
-.claude-usage-tracker-overlay-age {
- font-size: 12px;
- color: var(--text-400, #a3a3a3);
- font-style: italic;
-}
-
-.claude-usage-tracker-overlay-actions {
- display: flex;
gap: 8px;
- margin-left: auto;
- align-items: center;
-}
-
-.claude-usage-tracker-overlay-refresh,
-.claude-usage-tracker-overlay-close {
- background: rgba(255, 255, 255, 0.06);
- color: #f5f5f5;
- border: 1px solid rgba(255, 255, 255, 0.12);
- border-radius: 8px;
- padding: 6px 12px;
- font-size: 12px;
- font-weight: 500;
- cursor: pointer;
- font-family: inherit;
- transition: background 0.15s ease, border-color 0.15s ease;
-}
-
-.claude-usage-tracker-overlay-refresh:hover,
-.claude-usage-tracker-overlay-close:hover {
- background: rgba(255, 255, 255, 0.12);
- border-color: rgba(255, 255, 255, 0.22);
-}
-
-.claude-usage-tracker-overlay-close {
- padding: 6px 10px;
-}
-
-.claude-usage-tracker-overlay-spinner {
- font-size: 12px;
- color: #06b6d4;
- font-style: italic;
- margin-left: 8px;
- 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;
+ padding: 10px 14px;
+ background: rgba(59, 130, 246, 0.1);
+ border: 1px solid rgba(59, 130, 246, 0.3);
+ border-radius: 8px;
+ font-size: 13px;
}
-@keyframes claude-tracker-spin-pulse {
- 0%, 100% { opacity: 0.6; }
- 50% { opacity: 1; }
-}
-
-.claude-usage-tracker-overlay-body {
- min-height: 80px;
-}
-
-.claude-usage-tracker-overlay-empty {
- padding: 40px 16px;
- text-align: center;
+.session-reset-info-label {
color: var(--text-300, #a3a3a3);
- font-size: 14px;
}
-/* Cuando el tracker se inyecta dentro de nuestro overlay, ya está dentro de un
- contenedor con padding/borde; quitamos su margen superior para que pegue. */
-.claude-usage-tracker-overlay-body .claude-usage-tracker-container {
- margin-top: 0;
+.session-reset-info-time {
+ font-weight: 600;
+ color: var(--text-100, #f5f5f5);
+}
+
+.session-reset-info-remaining {
+ color: var(--text-200, #d4d4d4);
+ font-weight: 500;
+ margin-left: auto;
}