Herramientas como apps instalables — cómo está hecho

Las herramientas de la Oficina se instalan como apps de escritorio sin dejar de ser web estática: son PWA (aplicaciones web progresivas). No hay Electron ni Tauri ni firma de Apple; el usuario abre la web y el navegador la instala. La visión de usuario está en instalar-las-herramientas-como-app. Este documento cuenta el cómo, de dentro afuera.

Repo: webs/oficina. Estado y decisiones vivas: docs/estado/apps-instalables-pwa.md de ese repo. Firma: Karb and Sil.

Qué hace instalable a una web

Tres piezas por herramienta:

  1. **manifest.webmanifest** — declara nombre, iconos, color de marca (#8a5a2c) y display: standalone (ventana propia). Es lo que el navegador lee para ofrecer «Instalar».
  2. **sw.js (service worker) — hace que arranque sin conexión**. Estrategia stale-while-revalidate en los ficheros propios: se sirven de la caché al instante y se refrescan en segundo plano, así un redepliegue se recoge en la siguiente apertura sin quedarse pegado. Lo de fuera (CDN de la casa, contador de juntos) va a la red y, si no hay, se deja pasar sin romper.
  3. Iconos (192, 512 y uno maskable) — se generan del glifo del favicon de cada herramienta (el mismo de herramientas.wordkers.com), no del bocadillo genérico de Wordkers (que confundiría unas con otras en el Dock). Rasterizados del SVG con qlmanage -t -s 512; esquinas transparentes en el any (máscara redondeada con PIL) y a sangre en el maskable.

En el <head> se enlaza el manifest, el theme-color, el apple-touch-icon y se registra el service worker.

Abrir ficheros (asociación de tipo)

Dos caminos:

  • Arrastrar a la ventana: JavaScript puro (dragover/drop), no necesita nada del sistema. Se vuelca el fichero en el editor (#src + evento input) o, en apps con lógica propia, en su importador.
  • Doble clic / «abrir con»: file_handlers en el manifest + launchQueue en la página (recibe el fichero que el sistema pasa al abrir la app). Es de Chromium; Safari no lo tiene.

Reparto de tipos (para que no se peleen varias apps por el mismo):

  • md es la dueña de .txt (además de .md/.markdown).
  • mapas: .wkmapa (formato propio, JSON dentro).
  • html: .html/.htm. json: .json.
  • El resto no registra .txt (si lo hicieran, media docena de apps competirían por cualquier .txt). Se quedan instalables + arrastrar.

Dos topologías: subdominio vs ruta

  • Español: cada herramienta en su subdominio (md.wordkers.com, json.wordkers.com…). Cada subdominio es un origen aparte → PWA con scope: "/", se instala como app independiente sin más.
  • Inglés (toolbox.wordkers.com/<herramienta>): todas en un dominio con rutas. Para que cada una se instale por separado se usan manifests con ámbito por ruta (scope/start_url/id = /<herramienta>/) y un sw.js scopeado a esa ruta. Es el patrón estándar para sitios multi-app en un dominio; funciona en Chrome/Edge. Es un pelín menos redondo que un subdominio propio: si una inglesa se quisiera «de primera», se le daría su subdominio.

El botón «Instalar»

Cada herramienta lleva un botón flotante «⤓ Instalar app» (snippet autónomo, sin dependencias):

  • En Chromium usa el evento beforeinstallprompt (píldora abajo a la derecha, con una × que recuerda el descarte en localStorage).
  • Se oculta si la app ya está instalada (display-mode: standalone) o tras instalar.
  • En Safari (sin ese evento) enlaza a la guía de instalación.

El formato .wkmapa y MindNode

mapas guarda su formato propio **.wkmapa** (es el mismo JSON que ya usaba, con extensión propia para poder asociarlo sin pisar al tool json). Sigue importando .json antiguos y mapas de MindNode. Los ficheros de MindNode son plist binario de Apple; la app trae su propio lector. Para convertir en bloque hay un script Node reutilizable, mapas/tools/mn2wkmapa.cjs, que reutiliza ese mismo lector. Al abrir un mapa, la vista se auto-centra (recenter(): repite render+fit tras requestAnimationFrame y fonts.ready, porque el fit inmediato corre antes de medir los nodos).

Reproducirlo / añadir una herramienta nueva

En webs/oficina/tools/ hay generadores idempotentes:

  • gen_pwa.py <raíz> — aplica manifest + sw + iconos + head + abrir-fichero a las herramientas españolas (una carpeta por subdominio).
  • gen_pwa_en.py <toolbox> — igual pero con scope por ruta para las inglesas.
  • add_install.py <raíz> — inyecta el botón «Instalar» (marca wk.install, no duplica).

md y mapas llevan lógica propia (multi-documento; mapa mental) y se editaron a mano con el mismo patrón. Los landings (herramientas y toolbox) se regeneran con su generar.cjs en cada deploy: los enlaces del landing van ahí, no en el index.html (que es un subproducto). Las tools sí son fuente directa.

Límites conocidos

  • file_handlers (doble clic) es solo Chromium; en Safari, instalar + arrastrar.
  • Firefox no instala PWA en Mac.
  • Una app ya instalada guarda el icono del momento: para ver un icono nuevo hay que desinstalar y reinstalar.
  • Antes de desplegar, el clon local debe estar en el último main (si no, se sube código viejo).
En esta página