← Proyectos

DualRoute

Extensión de Chrome para abrir con un clic las páginas que revisas cada día, cada una en modo normal, incógnito o en los dos. Lleva la cuenta de cuándo abriste cada una por última vez, avisa cuando toca volver a mirarla, guarda notas con historial de cambios y exporta todo a JSON. Sin historial del navegador, sin content scripts y sin servidor.

El popup de DualRoute con la lista de rutas, sus modos de apertura, categorías y un recordatorio pendiente
El panel de rutas con búsqueda, filtros por categoría, dominio, modo y recordatorio, y la lista de rutas con sus accionesLa sección de recordatorios, con rutas vencidas hace dos horas y otras que vencen dentro de cincoEl historial de lanzamientos con la fecha, la ruta, el modo normal o incógnito y si se abrió sola o por lotes
modos de apertura
3
modos de apertura
content scripts
0
content scripts
idiomas
2
idiomas
tests (Vitest y Playwright)
75
tests (Vitest y Playwright)

Qué hace

  • Rutas con su modo. Cada página guardada (una "ruta") se abre en modo normal, en incógnito o en los dos a la vez. El modo va con la ruta, no hay que elegirlo cada vez.
  • Abrir en bloque. Filtras por categoría o buscas, marcas las rutas y Abrir seleccionadas las abre todas, cada una en su modo. Las de incógnito van juntas en una sola ventana.
  • Cuándo la abriste por última vez. Cada apertura desde la extensión queda registrada con la fecha, el modo y si fue suelta o en bloque. Nada más: no lee el historial del navegador ni mira qué pestañas tienes.
  • Recordatorios por horas. Si una ruta lleva más de X horas sin abrirse, se marca como pendiente y, si lo activas, llega una notificación. Se limpia sola al volver a abrirla.
  • Notas con historial. Notas por ruta o generales, marcadas como importantes o pendientes. Al editarlas se guarda la versión anterior.
  • Tus datos, en un JSON. Exportas las secciones que quieras e importas un JSON validado antes de reemplazar nada.
  • Local y sin servidor. Todo se guarda en chrome.storage.local. Sin cuentas, sin sincronización y sin content scripts. En español e inglés, con tema claro u oscuro.

Cómo está hecho

El popup pide, el service worker abre

El popup y el panel no abren pestañas. Mandan un mensaje al service worker con los ids de las rutas, y es él quien planifica, pide confirmación si son muchas pestañas, las abre y apunta las estadísticas. Así cerrar el popup a mitad de un lanzamiento no lo corta.

Un planificador puro y un ejecutor con fallback

planLaunch reparte las URLs en dos lotes, normal e incógnito (una ruta en "los dos" va a ambos), sin tocar ninguna API del navegador, así que se prueba con Vitest sin más. El ejecutor abre las normales como pestañas en segundo plano, con un retardo configurable entre una y otra, y las de incógnito en una única ventana. Si la extensión no tiene permiso en incógnito, las abre en normal y lo avisa.

Un solo estado, escrito con candado

Todo el estado es un objeto validado con Zod bajo una clave de chrome.storage.local. Lo escriben el popup, el panel y el service worker, así que cada escritura pasa por un lock de la Web Locks API que lee, transforma y guarda sin que otro contexto se cuele en medio.

Problemas que resolví

  • Recordatorios que solo existían con el popup abierto. Al principio se evaluaban cuando abrías la extensión, y la notificación llegaba justo cuando ya no hacía falta. Ahora una alarma de chrome.alarms los revisa cada minuto aunque no abras nada.
  • Escrituras que se pisaban. El popup y el service worker hacían su propio leer-modificar-guardar, y la última en guardar borraba los cambios de la otra. Lo arreglé serializando todas las escrituras con un lock compartido.
  • Datos que solo crecían. El historial de lanzamientos y las revisiones de notas no tenían límite, y todo el estado se lee entero en cada operación. Ahora se quedan en 1.000 lanzamientos y 20 revisiones por nota.

Stack

WXTManifest V3React 19TypeScriptTailwind CSS 4ZodChrome StorageChrome AlarmsWeb LocksVitestPlaywright