El build NUNCA firmó la app de macOS. Al no pasarle ninguna identidad, el
bundler de Tauri se saltaba `codesign` entero y el .dmg salía con la firma
ad hoc que deja el enlazador:
Identifier=labstream_desktop-0a5819ba37119701
flags=0x20002(adhoc,linker-signed)
Info.plist=not bound
Sealed Resources=none
Tres cosas mal, y ninguna avisaba en ningún log:
· El Entitlements.plist del repo era DECORACIÓN. Los entitlements se
escriben al firmar; sin firma, el micrófono, la cámara y el JIT nunca
pidieron nada porque nunca estuvieron pedidos.
· Ni el Info.plist ni los recursos iban dentro de la firma: se podían
cambiar sin romperla.
· El identificador era el del enlazador y no `co.labstream.os`, que es por
el que macOS recuerda los permisos que le concedes a una app.
AHORA. `APPLE_SIGNING_IDENTITY` se calcula en un paso propio y no en el
`env:` del build, porque un secret que no existe llega como cadena VACÍA y
Tauri lee `APPLE_CERTIFICATE=""` como «hay certificado» y revienta al
importarlo. Solo se exporta lo que tiene valor:
· con Developer ID → firma real, y notarizado si están las tres claves;
· sin él → identidad "-", firma AD HOC. No engaña a Gatekeeper, pero sella
los recursos, ata el Info.plist y APLICA LOS ENTITLEMENTS. En cuanto
Jonathan añada los secrets, la siguiente etiqueta sale firmada de verdad
sin tocar un solo archivo.
ENSAYADO sobre una copia de la app instalada, con el mismo comando que va a
correr el CI. Antes y después:
flags=0x20002(adhoc,linker-signed) → flags=0x10002(adhoc,runtime)
Identifier=labstream_desktop-0a58… → Identifier=co.labstream.os
Info.plist=not bound → Info.plist entries=17
Sealed Resources=none → Sealed Resources version=2 rules=13
(sin entitlements) → allow-jit, audio-input, camera
Y EL ENSAYO ENCONTRÓ UNA TRAMPA que `plutil -lint` no ve: el
Entitlements.plist llevaba `--entitlements` dentro de un comentario XML, y
dos guiones seguidos ahí son XML inválido. plutil lo aceptaba; AMFI, que es
quien lo lee de verdad dentro de codesign, contestaba «Failed to parse
entitlements» y la firma fallaba. Queda avisado en el propio archivo.
De paso se cae `com.apple.security.network.client`: es exclusiva del App
Sandbox y esta app no lo usa, así que no hacía nada.
LA RED QUE FALTABA: un paso nuevo comprueba la firma del .app y TUMBA la
publicación si los entitlements no llegaron. Que esto estuviera roto no lo
dijo nada durante meses; se descubrió a mano persiguiendo otra cosa.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Al bajar un archivo desde una pestaña, la app abre el «Guardar como» nativo (rfd directo, modal en el hilo del webview) con el nombre sugerido; cancelar cancela la descarga. Antes caía en silencio en Descargas. El aviso al terminar usa la ruta elegida (en macOS WKDownload no la devuelve).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Google degrada sus apps cuando detecta un WebView embebido: Sheets abria en blanco y la
sesion no terminaba de asentarse aunque la cuenta ya estuviera iniciada. Ahora cada
pestana declara un User-Agent de Chrome de escritorio (por plataforma), que es lo que
hacen los clientes de escritorio que embeben Google.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bump de version. La novedad esta en el commit anterior: hosts de Google (docs, drive,
sheets, accounts, myaccount) se abren como pestanas propias del shell en vez de mandarse
al navegador del sistema, para que la sesion de Google viva dentro de la app y las hojas
del contador se vean en la pestana Contable sin pedir cookies cada vez.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sube la version para publicar el arreglo del sensor (macOS ya no pide el permiso
de Accesibilidad: usa CGEventSourceSecondsSinceLastEventType en vez de leer el
teclado global con device_query) junto con la caché de miniaturas.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- ÍCONO de la app (Mac + Windows): nuevo app-icon.png (1024²) = «labs» blanco
centrado sobre negro. Son las MISMAS letras del wordmark «labstream»,
recortadas (l·a·b·s), no una fuente parecida — así el ícono es fiel al logo.
NO reemplaza el logo/wordmark de la app; es solo el ícono que se ve en el
dock/barra de tareas/instalador. La CI regenera todos los tamaños con
`npm run icons` desde este PNG.
- NUEVA PESTAÑA más a mano: el botón «+» deja de estar fijo a la derecha de la
barra y pasa a vivir DENTRO de la tira de pestañas, pegado a la última
(estilo Chrome). Al ser el último elemento flex de #tabs, queda justo tras la
última pestaña sin importar cuántas haya; el hueco libre después sigue
moviendo la ventana. Mismo evento de siempre (ls-new-tab); el atajo
⌘/Ctrl+T no cambia. Solo se tocó dist/index.html (barra del shell); cero Rust.
Verificado: el «+» renderiza como último hijo de #tabs, pegado a la última
pestaña (medido: 3 pestañas llenando la tira + el «+» justo después). El ícono
se ve nítido a 256 y legible a 48 px.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Regla pedida por el usuario: si alguien mira un video en el navegador sin tocar
el mouse, no es que este quieto — esta viendo algo. Ahora el sensor lo cuenta
como tiempo de navegador en vez de inactividad.
- Excepcion del VIDEO (Windows): en el tick, si `ocioso` (3 min sin entrada)
PERO al frente hay un navegador (chrome/brave/edge/firefox/opera/vivaldi, se
excluye el host WebView2) Y el sistema esta sacando sonido, es un video
reproduciendose -> `viendo_video`. Se introduce `ocioso_efectivo = ocioso &&
!viendo_video`, y TODO (tramo de inactividad, bandeja, acumulacion de la
ventana) pasa a usar `ocioso_efectivo`. El bloque de navegador se acumula con
a=0 (segundos sin entrada real): presente, sin teclear. La bandeja lo dice
(«video en primer plano»).
- Deteccion de sonido: medidor de pico de la salida de audio por defecto
(WASAPI, IAudioMeterInformation via el crate `windows`, solo cfg(windows)).
No abre el navegador, no lee la pestaña, no graba nada — solo pregunta al SO
si hay sonido saliendo AHORA (pico > 0.01). Limite honesto documentado: un
video MUDO no se detecta. macOS: stub que devuelve false (no hay API publica
sencilla de nivel de audio; el equipo de edicion es Windows) -> comportamiento
identico al de siempre en Mac.
- La ventana al frente ahora se lee cada tic mientras se mide (antes solo con
actividad), porque hace falta para decidir la excepcion; se reutiliza esa
misma lectura para acumular, sin volver a preguntar al SO.
- COM se inicializa una vez al arrancar el hilo del sensor (cfg(windows)).
- El contrato con el servidor NO cambia: se sigue mandando {blocks, idles}, solo
que ahora hay menos idles y algun bloque de navegador cuando hay video. Nada
que tocar en labstream-os.
Verificado con `cargo check`: el reensamblado compila limpio en el host (macOS),
y el codigo WASAPI type-checkea contra el target x86_64-pc-windows-msvc (aislado
en un crate minimo, porque `ring` de la TLS de ureq no cross-compila su C a
Windows desde Mac — eso lo hace la CI). Falta la validacion final en una maquina
Windows real (no puedo reproducir audio de Windows aqui): probar que un video en
YouTube con el mouse quieto sale como navegador y no como inactividad.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
La v1.7.0 preguntaba por la clase `dark` de <html> y punto. En este
mismo PC quedó la prueba de que eso miente en WebView2: estado.json
con "dark": false y la app pintada de negro — barra clara sobre app
oscura, el bug al revés.
Ahora el reporte es a prueba de balas:
- temaOscuro(): clase `dark` en <html> o <body>; si no está, la
LUMINANCIA del fondo realmente pintado del body (funciona sea cual
sea el mecanismo de tema); si tampoco, el color-scheme computado.
Con el documento a medio crear devuelve null y el campo NO viaja
(Rust conserva el último valor bueno) — nada revienta en frío.
- Vigilancia segura: MutationObserver sobre <html> Y <body>
(class/style/data-theme) registrado cuando existen, y dos
re-reportes tras la carga por si el tema aterriza con la
hidratación.
Rust y la barra quedan como en v1.7.0 (un campo `dark` ausente ya
significaba «conserva lo último»). El estado.json mentiroso se
corrige solo con el primer reporte bueno.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sube la versión en los cuatro sitios que tienen que coincidir (package.json,
Cargo.toml, Cargo.lock y tauri.conf.json). Así el instalador de prueba se presenta
como v1.6.0 en la barra en vez de repetir v1.5.1, que al probarlo encima de la app
instalada no dejaba saber cuál se está corriendo.
El README describía el comportamiento viejo: decía que en Windows se conserva el
título del sistema con la barra debajo, y que la actualización automática era un
paso futuro cuando lleva funcionando desde la v1.4.0. Ahora cuenta lo que hay: la
barra como fila del título en ambos sistemas, los colores de pestaña, que un commit
sin etiqueta no le llega a nadie, y cómo probar un cambio con el instalador de
prueba del CI sin publicárselo al equipo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
OnlyOffice fallaba al editar y al descargar SOLO en la app de escritorio de
Windows. En el navegador y en la app de macOS iba bien. La causa estaba en el
propio shell, no en el servidor de documentos.
En Windows, wry inyecta los scripts de inicio en TODOS los marcos e ignora
for_main_frame_only (wry-0.55.1/src/webview2/mod.rs:492-495, documentado en su
lib.rs:990); macOS si respeta la bandera (wkwebview/mod.rs:643-645). Y __TAURI__
viaja con ellos, asi que la guarda «if (!T || !T.event) return» no frenaba nada.
Resultado: INIT_JS_TPL corria DENTRO del iframe del Document Server con ORIGIN
apuntando todavia a la app; como alli location.href es el de docs.*, cada enlace
del editor se resolvia contra ese origen, daba isExt() = true y se desviaba al
navegador del sistema —que no tiene la sesion: «no se puede acceder»—, y
window.open devolvia null.
Cambios:
1. Guarda de marco y origen al principio del script inyectado: solo actua en el
documento PRINCIPAL y solo en el origen de la app. En macOS es un no-op (alli
nunca corrio en subframes). De paso arregla que en Windows el shell tambien
secuestrara los enlaces de las paginas de SSO.
2. El manejador de atajos ignora e.altKey. AltGr enciende ctrlKey en Windows, asi
que escribir [ o ] con teclado espanol disparaba history.back()/forward() en
vez de escribir el caracter — en el editor y en cualquier campo de la app.
3. Aviso nativo al terminar una descarga. No es un adorno: wry trae un manejador
de descargas por defecto (wry-0.55.1/src/lib.rs:830), de modo que en WebView2
siempre se registra DownloadStarting y siempre responde SetHandled(true)
(webview2/mod.rs:858-861), lo que apaga la interfaz de descargas de Windows.
El archivo bajaba a Descargas pero nada lo decia y parecia roto. Quitar el
manejador NO devuelve la interfaz nativa: el de wry sigue puesto.
4. Feature «devtools» de tauri: el inspector queda disponible tambien en release.
Sin el, un fallo que solo aparece en la app instalada no se puede mirar por
dentro.
Descartado a proposito: registrar on_new_window. En macOS el delegado ejecuta
webview.window().unwrap() y current_window.screen().unwrap() ANTES del match
(wry-0.55.1/src/wkwebview/class/wry_web_view_ui_delegate.rs:150-154), tambien en
la rama Deny; y este shell oculta la ventana al cerrar, con lo que NSWindow.screen
puede ser nil y la app entera abortaria. Cambiar un fallo de Windows por un crash
en macOS no compensa.
Verificado: cargo check limpio; sintaxis del script inyectado validada con node;
y simulacion de la guarda — marco principal + origen propio interviene, iframe de
OnlyOffice no interviene, origen ajeno no interviene.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
La ventana gastaba DOS filas en cromo (titulo de macOS + barra de pestanas).
Ahora es una sola: se recuperan 28 px de alto en cada ventana.
- INTEGRADA AL TITULO (macOS): la ventana usa titulo en overlay y sin texto, asi
que la barra ocupa esa fila. Deja libres 78 px para el semaforo (que sigue
siendo el nativo) y el hueco vacio arrastra la ventana / doble clic la
maximiza (data-tauri-drag-region + permiso core🪟allow-start-dragging).
En Windows NO cambia nada: titulo del sistema y barra debajo, como hasta ahora
(fusionarlas alli obligaria a dibujar a mano minimizar/maximizar/cerrar).
Como el area util pasa a ser todo el marco, desaparece la compensacion de
v1.4.2: los webviews vuelven a colocarse en 0 / TAB_BAR_H.
- NOMBRES QUE SE DISTINGUEN: la web pone el mismo <title> en todas las paginas
(«(25) Labstream OS»), asi que las tres pestanas se llamaban igual. Ahora el
nombre sale de la RUTA (mismas etiquetas que la barra superior de la web) con
su emoji: Inicio, Chats, Calendario, Cliente, Proyecto, Operaciones… Si algun
dia la web pone titulos por pagina, mandan esos: se limpia el contador de no
leidos y el sufijo de la app y, si queda algo propio, se usa.
- ANCHO ELASTICO: las pestanas se reparten el sitio y se encogen al abrir mas;
por debajo de ~112 px queda solo el icono (y la x solo en la activa), con el
nombre completo en el tooltip. Igual que Chrome.
- REORDENAR ARRASTRANDO: con eventos de PUNTERO, no drag&drop de HTML5 (en el
webview de la barra en macOS los eventos dragstart/dragover no llegan aunque
se desactive el manejador nativo). El nodo se mueve en vivo y al soltar se
manda el orden a Rust, que valida que sea una permutacion exacta y lo guarda.
El clic que cierra el arrastre no activa la pestana.
- CLIC DERECHO EN UNA PESTANA: menu NATIVO (la barra mide 40 px; uno dibujado
dentro quedaria recortado) con Duplicar, Copiar enlace, Cerrar pestana, Cerrar
las demas y Cerrar las de la derecha — estas dos se deshabilitan solas cuando
no aplican. «Copiar enlace» usa tauri-plugin-clipboard-manager.
- SENAL DE CARGA: el icono de la pestana se vuelve un aro girando mientras la
pagina carga (PageLoadEvent Started/Finished), respetando prefers-reduced-motion.
Verificado en el Mac con la app real: barra en la fila del titulo con el
semaforo intacto, ventana movida arrastrando la barra (15,53 -> 147,243),
nombres con emoji, 14 pestanas en modo solo-icono, menu contextual con sus
opciones deshabilitadas donde toca, «Cerrar las demas», duplicar, y reordenar
(tab-1,tab-2,tab-3 -> tab-3,tab-1,tab-2) persistido entre arranques.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
La barra existia pero estaba DIBUJADA DETRAS de la barra de titulo de macOS:
solo asomaban ~12 px por debajo, y como el titulo nativo es translucido hasta
se tenia del color de la barra. Con el rediseno estilo Chrome el sintoma se
volvio evidente (no se veian ni las pestanas ni los controles), pero el fallo
venia de antes: no lo causo el CSS.
Causa (medida en vivo con instrumentacion): en macOS las coordenadas de los
webviews HIJOS son relativas al MARCO de la ventana (incluye el titulo) y
ademas `inner_size()` devuelve el marco, no el area de contenido
(inner_size == outer_size e inner_position == outer_position). Al colocar la
barra en y=0 quedaba bajo el titulo, y el contenido en y=40 arrancaba 12 px
por debajo, tapando el resto.
Arreglo: `top_offset()` compensa el alto del titulo (28 pt) SOLO cuando hace
falta. La deteccion es en caliente -- si el marco y el interior miden lo mismo
hay que compensar; el dia que Tauri lo corrija la resta dejara de ser 0 y no se
compensara nada -- y en Windows/Linux devuelve 0 (ahi las coordenadas ya son
del area de cliente). Se aplica en los tres sitios que colocan webviews:
layout_all, la creacion de la barra y la creacion de cada pestana.
Verificado en el Mac con la app real: barra completa y alineada bajo el
titulo, redimensionado, pestana nueva, cierre de pestana y cambio de pestana.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- ATRÁS/ADELANTE por fin: botones ← → en la barra de pestañas, ⌘[ / ⌘] en
ambas plataformas, Alt+←/→ en Windows (fuera de campos de texto) y menú
«Historial» en macOS. Cierra el callejón sin salida: una página sin menú
propio (p. ej. el portal de revisión) dejaba al usuario atrapado sin forma
de volver.
- «Reabrir pestaña cerrada» (⌘/Ctrl+Shift+T, tope de 20): en la barra ⌘⇧T
antes caía en «nueva pestaña» por no mirar el Shift.
- Globo de NO-LEÍDOS en el icono: la web app emite `ls-badge {count}` (mismo
conteo del «(N)» del título) y el shell pinta el número en el Dock (macOS),
un punto rojo dibujado a mano sobre el icono de la barra de tareas (Windows,
sin assets) y el tooltip de la bandeja («— N sin leer»).
Verificado: cargo check limpio (macOS) y firmas de las APIs solo-Windows
(set_overlay_icon, set_badge_count, Image::new_owned) cotejadas contra el
código fuente de tauri 2.11.3.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
La barra muestra la versión real del paquete (viene de Rust en el payload
ls-tabs, no quemada en el HTML) junto al menú ⋮. Clic en la etiqueta →
"Buscar actualización…" con aviso del resultado (evento ls-check-update).
Versión 1.3.0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Botón ⋮ en la barra de pestañas (y clic derecho sobre ella) → menú NATIVO del
sistema, idéntico en Windows y macOS, solo del shell (la web app no se toca):
- Nueva/cerrar pestaña, recargar, acercar/alejar/tamaño real.
- "Abrir esta página en el navegador" (la pestaña activa, vía opener).
- "Iniciar con el sistema" con check: ahora la elección SE RECUERDA
(antes el arranque re-activaba el autostart en cada apertura).
- "Buscar actualización…" manual con aviso del resultado (instalando /
ya estás al día / sin red), además del chequeo silencioso de siempre.
- Versión visible y "Salir de Labstream OS".
El manejador de menús pasa a ser global (antes solo existía dentro del cfg de
macOS): lo comparten el menú de macOS y el popup ⋮ de ambas plataformas. El
menú de macOS gana "Buscar actualización…" y "Abrir en el navegador".
Versión 1.2.0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
La app deja de ser un envoltorio de una sola vista y pasa a ser un shell con
pestañas (multiwebview de Tauri 2, ventana "main" = barra local "chrome" +
webviews "tab-*" del servidor):
- Los enlaces internos target=_blank (documentos, entregables, OnlyOffice,
exportaciones) abrían nada: ahora abren pestaña dentro de la app. También
Cmd/Ctrl+clic, clic central y window.open del propio origen.
- Zoom de interfaz: Cmd/Ctrl +/−/0 y Ctrl+rueda (50%–250%), con indicador en
la barra y persistencia entre sesiones.
- Sesión restaurada: pestañas abiertas, activa, zoom y tamaño/posición de la
ventana vuelven al reabrir (estado.json en app_config_dir).
- Atajos: Cmd/Ctrl+T/W/R, Ctrl+Tab rota, Cmd/Ctrl+1..9; en macOS van por el
menú nativo nuevo en español (Archivo/Edición/Vista/Ventana); en Windows
por JS (sin barra de menú).
- macOS: clic en el Dock reabre la ventana oculta (RunEvent::Reopen).
- Una sola instancia en release (single-instance): reabrir enfoca la existente.
- Updater: avisa con notificación nativa antes de reiniciar por actualización.
- Los enlaces a OTRO origen siguen yendo al navegador del sistema y el login
por Authentik sigue intacto (el init script se desactiva fuera del origen).
Shell ↔ Rust por eventos ls-* (core:default cubre el origen remoto; sin ACL
de comandos propios). Versión 1.1.0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Al abrir, la app busca en segundo plano una versión nueva en los Releases de GitHub
(latest.json), y si la hay —firmada con nuestra clave— la instala sola y reinicia.
- Cargo/lib.rs: tauri-plugin-updater + check_update() en el arranque (no bloquea).
- tauri.conf.json: bundle.createUpdaterArtifacts=true y plugins.updater con el endpoint
(releases/latest/download/latest.json) y la clave PÚBLICA de verificación.
- CI: build.yml pasa a tauri-apps/tauri-action, que construye el .exe (Windows) y el
.dmg universal (macOS), firma los artefactos del updater y publica todo —incluido
latest.json— en un Release. Requiere los secrets TAURI_SIGNING_PRIVATE_KEY y
TAURI_SIGNING_PRIVATE_KEY_PASSWORD (la clave privada NO va en el repo).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
La ventana se crea ahora en Rust (lib.rs) para poder:
- Inyectar un script que manda los enlaces EXTERNOS (Drive, docs ajenos, target=_blank,
window.open) al navegador del sistema, dejando dentro la navegación del propio
servidor y el login por Authentik (que redirige a otro dominio y vuelve).
- Permitir descargas (entregables/exportaciones) con el diálogo nativo de guardado.
- SERVER_URL centralizado (antes en tauri.conf.json app.windows[0].url).
macOS: Info.plist con NSMicrophoneUsageDescription/NSCameraUsageDescription y
Entitlements.plist (audio-input, cámara, red, allow-jit) → las notas de voz y los
adjuntos de foto/video piden permiso y funcionan. Referenciados en bundle.macOS.
Se conserva el tray, cerrar=ocultar y el autostart. Verificado: build de macOS OK
(.app + .dmg de 3,8 MB, icono del logo, textos de permiso fusionados en Info.plist).
Los instaladores de Windows (.exe) y macOS (.dmg universal) los sigue produciendo
GitHub Actions (.github/workflows/build.yml) en cada tag de versión.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>