ONNX Runtime en la CPU solo hace rápida la FFT de LaMa con lados que son potencias de 2:
512 × 512 son 2,7 s y 704 × 448, casi los mismos píxeles, 10 s. El trozo se reduce lo justo
para caber en 512 (o en 1024 si pasa de 768 px) y se completa hasta la potencia de 2 con su
propio borde en espejo, sin hueco; al volver se descarta. Un trozo de 700 px pasa de 10 s
a 4 s; uno de 300 × 200, 2 s.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Para «Rellenar con IA» de Eliminar en Labstream OS: la página manda el trozo de la foto
alrededor de lo pintado, en 8 bits con el hueco en el alfa, y la app lo devuelve con el
hueco relleno por LaMa y lo demás byte a byte como estaba (src-tauri/src/ia/relleno.rs).
- Comando nuevo ia_rellenar (permitido solo para el origen de la app). ia_estado e
ia_preparar llevan { que: "relleno" }; sin él siguen hablando del sujeto, como la 1.22,
y el estado dice ahora de qué IA habla (así la página sabe que esta app rellena).
- LaMa (Apache-2.0) en la exportación ONNX de tamaño libre de g-ronimo, desde el servidor
de la app con la sesión de la pestaña y fijada por huella (≈210 MB). Se rellena al
tamaño del trozo en múltiplos de 32, con 1024 px de lado como mucho.
- En la CPU: las dos exportaciones de LaMa probadas fallan con DirectML en una RTX 5080
(su FFT, sea el operador DFT o productos de matrices: 80070057, también con las formas
fijadas y sin optimizar); en la CPU, ~3 s a 512 px y ~10 s a 1024, y no le quita
memoria a la tarjeta.
- Las piezas por IA: el motor (ONNX Runtime + DirectML) es de las dos; cada modelo se baja
cuando se usa la suya. Cada modelo abierto se suelta a los 2 min sin uso.
Pruebas nuevas: el tamaño al que se rellena y que fuera del hueco no cambia nada; y una de
punta a punta en este equipo (ignorada en CI).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
La máscara de «Sujeto» y «Fondo» del editor de revelado de Labstream OS la busca
la app en la tarjeta del equipo, no el navegador (src-tauri/src/ia/):
- Tres comandos para la página, permitidos solo para el origen de la app (manifiesto
en build.rs + capabilities): ia_estado (¿hay NVIDIA?, ¿está bajado?, cuánto
falta), ia_preparar (baja lo que falte con eventos ia-progreso y abre el modelo)
e ia_sujeto (RGBA en binario → [ancho][alto][un byte por punto]).
- Motor: ONNX Runtime 1.24.4 con DirectML (crate ort 2.0.0-rc.14, load-dynamic,
api-24): nada se enlaza al compilar; las DLL se bajan de NuGet la primera vez y
DirectML.dll se carga por su ruta antes que onnxruntime.dll. Sin fundir el grafo
(fundido, el HR no cabía en 16 GB) y sin seguir en la CPU si la tarjeta falla.
- Modelo: BiRefNet general en media precisión, del servidor de la app con la
sesión de la pestaña (cookies de WebView2 leídas en el comando asíncrono).
- Todo fijado por huella SHA-256; los .nupkg se sacan por ruta exacta (DirectML trae
también las DLL de Xbox y de depurar con el mismo nombre).
- ¿Hay NVIDIA?: los adaptadores de DXGI, sin cargar nada de la IA.
- El modelo se suelta de la tarjeta a los 2 min sin uso; si la tarjeta falla, se
suelta y se abre de nuevo la próxima vez. Todo va a salud.log.
Medido en una RTX 5080: 0,3 s por foto (1,4 s la primera; el navegador tarda
2,5–3 s) y el mismo borde que el modelo original en la CPU. El HR (2048 px,
matting) se probó y se descartó: 14–15 GB de la tarjeta y de 6 a 45 s por foto.
Pruebas nuevas: zip por ruta exacta, entrada igual a la del navegador, salida y
respuesta; y una de punta a punta en la tarjeta (ignorada en CI).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Para el modo edición de los sets de fotos de Labstream OS, que se abre en su
propia pestaña:
- Color de la página: una página pide el color de su pestaña con
<html data-ls-pestana="#rrggbb">. El script de la pestaña lo manda en
ls-title (y lo vigila con el mismo observador del tema); Rust solo acepta
#rrggbb (color_de_pagina) y la barra pinta la pestaña de ese color, con letra
clara u oscura según su luminancia. Activa se funde con la barra de arriba de
la página; detrás conserva el color. Un color del menú encima va de franja.
Una página con color propio no manda el tema: su fondo oscuro no es el de la
app y no debe oscurecer la barra.
- Pestañas con nombre: window.open(url, "ls-…") o target="ls-…" abre la
pestaña la primera vez; las siguientes activan ESA y le pasan la URL por
window.__lsAbrir (el modo edición cambia de foto sin recargar); sin él,
navega. Una descartada se revive ya en la URL nueva. Solo nombres ls-
alfanuméricos (nombre_de_ventana); los demás abren pestaña como siempre.
- Color de página y nombre se guardan con la sesión (paginas, nombres en
estado.json; un estado viejo carga igual).
Prueba nueva: la_pagina_solo_pide_colores_y_nombres_de_la_app.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Visto en el equipo de edición con la 1.19.0: la ventana estaba oculta en
Windows pero tao la tenía anotada como visible, así que show() no llamaba a
ShowWindow (apply_diff sin cambios) y ni la bandeja, ni el icono, ni la tecla
del MasterHUB la sacaban; set_focus sí corría y tomaba el foco sin verse.
show_main compara con el estado real (is_visible) y, si no cuadra, rehace
ocultar + mostrar y lo anota en salud.log.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Salud (src-tauri/src/salud.rs):
- Mide cada minuto el árbol de procesos de la app (exe + WebView2): memoria
privada, CPU y procesos. Se ve en la bandeja y en ⋮; va a recursos.csv.
- Diario de errores (salud.log): pánicos, procesos de WebView2 caídos o
colgados, errores de JavaScript, hilo principal sin responder, fallos del
actualizador y cierres de golpe (testigo en-marcha).
- Se repone sola: recarga la pestaña cuyo proceso muere o se cuelga (freno de
3 en 10 min), reinicia la app si cae el proceso principal de WebView2 y
registra el reinicio automático de Windows (RegisterApplicationRestart).
- ⋮ y bandeja: «Abrir diagnóstico» abre la carpeta de logs.
Memoria (src-tauri/src/memoria.rs), medido: 2,2 GB con 5 pestañas, 1,3 GB
en el proceso gráfico; reiniciarlo bajó la app a 1,1 GB sin perder nada.
- Recicla el proceso gráfico al pasar de 600 MB si nadie mira (ventana oculta
o minimizada, o 10 min sin entrada y sin sonido), como mucho cada 30 min.
- Al abrir solo carga la pestaña activa; las demás se crean al activarlas.
- Descarta las pestañas que llevan 30 min dormidas (vuelven a su URL).
- Nivel de memoria bajo de WebView2 para lo que no se ve.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cada pestaña es la web app entera, con su conexión del chat y sus sondeos; con seis
abiertas corrían seis. Una pestaña que lleva 5 minutos oculta se suspende con
WebView2 TrySuspend y despierta sola al volver a ella. No se duerme la activa, ni
una con subidas, descargas o audio en curso, ni un editor de documentos.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
WebView2 tiene apagados sus propios atajos de navegador, así que F5 no hacía
nada dentro de la app: el único que recargaba era Ctrl+R. Ahora F5 (y
Ctrl+F5, Shift+F5) recarga la pestaña. Va antes del filtro de modificadores
de los atajos, porque F5 no lleva ninguno y ese filtro lo descartaba.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tres cambios en la barra, todos de la misma idea: que se vea de un vistazo cuál
manda.
· LA ESQUINA. Se retiran los dos pseudo-elementos de 8px que imitaban la curva
cóncava de Chrome. El truco funciona allí porque NO hay línea bajo la barra;
aquí sí la hay, y esas piezas blancas la partían en dos a los costados de la
pestaña activa. La activa ya se funde con el contenido por su propio fondo.
· LA PROFUNDIDAD. Las inactivas se hunden con un velo tenue y sombra interior, y
al pasar el ratón el gesto se invierte: la sombra sale y el fondo tira hacia el
blanco de la activa. Sombra y no relleno sólido — un bloque gris pesa tanto
como la activa y compite con ella. La activa gana sombra HACIA ARRIBA: abajo
está fundida con el contenido.
· LA NEGRILLA. El título de la activa a 600. Va en el título y no en la pestaña
entera para que el aspa de cerrar no engorde con él.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
El relieve dice dónde está el plano de delante; el peso de la letra es lo que se
lee sin mirar. Va en el TÍTULO y no en la pestaña entera, para que el aspa de
cerrar no engorde con él, y a 600 y no a 700: a 12,5 px el bold entero emborrona,
y lo que hace falta es que destaque, no que grite.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
DOS COSAS EN LA BARRA DE PESTAÑAS.
1) LA ESQUINA. La activa llevaba dos pseudo-elementos de 8px a los lados con un
degradado radial: la curva cóncava de Chrome. El truco funciona en Chrome
porque allí NO hay línea bajo la barra; aquí sí la hay, y esas dos piezas
blancas la partían en dos justo a los costados de la pestaña — la fluctuación
que se veía en la esquina. Se retiran: la activa ya se funde con el contenido
por su propio fondo, que tapa la línea exactamente por su ancho, y sin las
piezas no asoma nada.
2) LA PROFUNDIDAD. La inactiva no era nada: texto suelto sobre la franja, al
mismo plano que la activa, separado por un filete de 1px. Ahora se HUNDE —velo
muy tenue y sombra interior— y al pasar el ratón el gesto se invierte: la
sombra sale hacia fuera y el fondo tira hacia el blanco de la activa, así que
la pestaña sube hacia ti. Es sombra y no relleno sólido a propósito: un bloque
gris pesa tanto como la activa y acaba compitiendo con ella.
La activa gana su propia sombra, HACIA ARRIBA: abajo está fundida con el
contenido y una sombra ahí dibujaría una línea donde no debe haberla.
Con fondo propio el separador vertical sobra y se retira. `--sep` se queda:
lo sigue usando el aro del favicon mientras carga.
Sin cambio de versión: queda listo para la próxima que se saque.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
La app de escritorio es un navegador aparte sin la sesion de Google del usuario. Antes
un enlace de docs.google.com se abria en el navegador del sistema y Google pedia
Permitir cookies dentro de un marco que nadie podia contestar. Ahora hosts de Google
(docs / drive / sheets / accounts / myaccount) se abren como PESTANAS internas del shell,
y el WebView2 guarda la sesion de Google como cualquier otra cookie: se inicia sesion
UNA vez y las hojas del contador se ven y editan en la pestana Contable.
- create_tab acepta URLs con scheme https y host en GOOGLE_HOSTS ademas del origen propio.
- La restauracion de sesion respeta el mismo criterio (una pestana de Google guardada
reabre como pestana de Google).
- El script inyectado detecta docs.google.com/... y hace newTab en vez de openExternal;
en paginas de Google solo reporta el titulo (sin tocar clics, atajos ni window.open)
para no interferir con el editor de Google.
Falta probar el instalador de prueba antes de etiquetar la version.
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>
El sensor leia el teclado/mouse global con device_query para saber si hubo
entrada. En macOS eso exige el permiso de Accesibilidad y, como la app va sin
firma estable, macOS nunca lo retiene: el dialogo «... quiere controlar este
ordenador» reaparecia en cada arranque aunque ya se hubiera concedido.
Solo hace falta saber SI hubo entrada, no cual. macOS lo da con
CGEventSourceSecondsSinceLastEventType (segundos desde la ultima entrada HID),
de solo lectura y sin pedir ningun permiso. Windows/Linux siguen con
device_query (ahi no piden permiso), ahora como dependencia solo de esas
plataformas.
Verificado: cargo check OK (4.5s); prueba aislada de la API devuelve los
segundos de inactividad en vivo sin ningun dialogo de permiso.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El problema: las webviews guardan las imagenes en un cache del SISTEMA, pequeno y
volatil (medidos 22 MB en un Mac del equipo). Un set de 500 fotos pasa de 150 MB
entre cuadricula y visor: no cabe, el sistema lo vacia, y al reabrir la app hay
que volver a bajarlo todo del NAS. Eso es el "se relee cada vez que cierro".
El cajon es un cache PROPIO de la app, en su carpeta interna (app_cache_dir,
invisible en el Finder), con tope de 3 GB y poda por antiguedad — el mismo
criterio que ya usa el NAS con sus miniaturas. Sobrevive a cerrar la app y se
vacia con un boton del menu (que ademas dice cuanto ocupa).
Como funciona: dentro de la app, la miniatura de la cuadricula (thumb=1) se pide
por el esquema lsthumb:// en vez de directo al servidor (lo reescribe el script
de inicio, solo en la app). El handler en Rust la sirve del disco si ya esta, o
la trae del NAS UNA vez, la guarda y la sirve. La clave del cajon NO incluye el
token firmado (que rota cada 24h): se deriva de la identidad estable de la foto,
asi que acierta un mes despues.
Decisiones:
- Solo se reescribe thumb=1 (la de 480px, la que se repite 500 veces). El visor
grande (thumb=xl) y la anotacion sobre lienzo NO se tocan: cargar la imagen del
lienzo por otro esquema lo "ensuciaria" y romperia guardar el dibujo.
- Cerrojo anti-proxy: el handler solo trae del origen del servidor y de /api/,
nunca una URL arbitraria (evita convertir la app en un proxy abierto / SSRF).
- La reescritura solo corre DENTRO de la app (guarda de origen del init script).
En el navegador normal, y para el cliente, nada cambia.
- El traer va en el pool de hilos de bloqueo, no un hilo por peticion: 500 fotos
no disparan 500 hilos.
Verificado en Mac: compila y enlaza limpio (tauri 2.11.3), 10 pruebas del motor
del cajon en verde (clave estable sin token, cerrojo anti-proxy, poda LRU,
extraccion de la URL), y la logica de reescritura probada contra casos reales en
node (thumb=xl NO se toca, idempotente, Drive/data: no se tocan).
PENDIENTE de probar con las manos, y por eso esto NO se etiqueta todavia:
- El extremo a extremo (la miniatura se cachea y sobrevive al reinicio) necesita
entrar con sesion a un set de fotos — no pude por no tener la contrasena.
- No lo lance en vivo: habia una instancia release corriendo que comparte
estado.json; una segunda instancia abriria otra ventana y pisaria la sesion.
- Windows: el esquema custom desde un origen remoto puede comportarse distinto en
WebView2 (AllowedOrigins). Hay que probarlo en un Windows real ANTES de etiquetar
— como ya se hizo con el fix de OnlyOffice (v1.5.1).
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>
Cada tramo sin ninguna entrada (mas alla del umbral de 3 min, que sigue contando como trabajo
igual que siempre) queda registrado como {desde, segundos} y viaja en el lote como `idles`,
aparte de los bloques. Sin app, sin titulo, sin contenido: la inactividad no tiene nada que
mirar. La bandeja lo dice: «inactivo (se anota aparte)».
Decisiones que importan, salidas de una revision adversaria previa (11 hallazgos, todos
atendidos):
- Tope de 4 h por tramo: una torre que nunca se suspende, bloqueada toda la noche, anotaba
«14 h inactivo» en el dia anterior. Mas de 4 h sin tocar el equipo es ausencia, no descanso;
pasado el tope no se reabre nada hasta que vuelva la entrada. El servidor ademas rechaza
cualquier tramo de mas de 6 h como red de seguridad.
- La suspension corta el tramo (la noche del portatil es una laguna, no un dato) y al
despertar el reloj queda EN el umbral: sin eso, los despertares automaticos de Windows a las
3 a. m. contaban hasta 3 min de trabajo fantasma y fabricaban jornadas de madrugada.
- El tramo en curso se persiste a disco (rastreador-encurso.json, un latido por minuto): un
apagado, el cierre de la app o la auto-actualizacion —que reinicia a todo el equipo a la
vez— lo cierran en su ultimo latido en vez de perderlo entero.
- La subida se acota a 12 lotes por ciclo y el tiempo de subida no cuenta como suspension:
al volver la red tras dias sin ella, los cientos de POST seguidos bloqueaban el hilo del
sensor y el siguiente tic diagnosticaba un «dormido» falso que cortaba el tramo abierto.
- La cola en disco lee el formato viejo (Vec<Vec<Bloque>>) ademas del nuevo: lo pendiente de
subir en la 1.8 no se pierde al actualizar.
- En pausa no se anota nada, y reanudar no retro-anota la pausa como ocio.
El servidor (labstream-os 453a640) ya acepta `idles` y enseña la columna «Inactivo» en las
jornadas de /rastreo; los sensores 1.8 siguen funcionando sin cambios.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Antes la pagina emitia el token a ciegas: en una app SIN sensor (anterior a la
1.8.0) nadie lo recogia y la pantalla cantaba "equipo vinculado" igual,
dejando un equipo registrado que jamas reporta.
- ls-tracker-token -> responde ls-tracker-listo (acuse). Sin acuse, la web
deshace el equipo que acaba de crear y avisa que hay que actualizar.
- ls-tracker-consulta -> responde ls-tracker-estado {vinculado, pausado,
version}, para no ofrecer vincular lo que ya esta vinculado y para poder
avisar de un clic a quien le falta.
Los dos emit salen en un hilo aparte, por el mismo motivo que los repintados
del menu: un listener puede correr en el hilo principal.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sensor de ventana activa + actividad, cola offline y estado visible en la
bandeja. NO se etiqueta todavia: una etiqueta actualiza sola a TODO el
equipo, y eso lo decide Jonathan.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`n.cola.drain(0..enviados.min(n.cola.len()))` no compila: el argumento pide
un prestamo inmutable de `n.cola` cuando ya esta prestado mutable, y a
traves del MutexGuard —un deref explicito, no un autoref— el two-phase
borrow no lo cubre (E0502, el UNICO error que encontro el CI en todo el
sensor).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sensor nuevo (src/tracker.rs): cada 5 s mira que app esta al frente y si
hubo entrada; con 3 minutos sin entrada deja de contar (ocio profundo, que
cubre tambien pantalla bloqueada y suspension). Acumula bloques {app,
titulo, segundos, segundos activos} y los sube cada 5 min a /api/tracker con
el token del equipo; sin red la cola persiste en disco y reintenta, y un 401
(token revocado desde Ajustes) descarta el token y vacia la cola.
Lo que NO hace, a proposito: no registra que teclas se pulsan —solo compara
una huella de posicion del raton y cuantas teclas hay presionadas—, no toma
pantallazos y no lista procesos de fondo. El estado vive VISIBLE en el menu
de la bandeja, con pausa de un toque.
Ojo al tocar el menu: set_text de Tauri despacha al hilo principal y bloquea
esperandolo, asi que llamarlo desde setup() —que corre en ese mismo hilo
antes de que el bucle de eventos atienda— colgaria la app al abrir. Por eso
los items nacen ya con su texto y todo repintado posterior sale de un hilo
secundario.
Co-Authored-By: Claude Fable 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>
El bug de «siempre oscura»: la web decide su modo con su propio botón
(next-themes, defaultTheme claro, sin seguir al sistema), pero la barra
elegía tema con prefers-color-scheme — el del SISTEMA. Con Windows en
oscuro y la app en claro, franja negra sobre app blanca, siempre. La
v1.6.1 pintó bien ambos temas, pero la barra seguía eligiendo el
equivocado.
El arreglo, de punta a punta:
- El script inyectado reporta en ls-title si <html> lleva la clase
`dark`, con un MutationObserver para que la luna se refleje al
instante. La guarda de marco y origen queda intacta (OnlyOffice).
- Rust lo guarda (global: todas las pestañas comparten modo), lo
reenvía en ls-tabs (`dark`) y lo PERSISTE en estado.json: la app
abre ya del color correcto, sin fogonazo.
- La barra se pone la clase `oscuro`/`claro` al recibirlo; las media
queries quedan solo como arranque en frío (primer arranque sin
estado.json). Los tintes de colores de pestaña cuelgan del mismo
interruptor.
- El acento pasa de índigo #6366f1 (comentado «color de Labstream»,
que no lo era) al naranja de marca #F47A20: aro de carga, zoom
activo y hover de la versión.
Verificado en navegador con el CSS y el JS reales, simulando el
payload de Rust: app clara + sistema oscuro → barra clara; app
oscura → negra; sin reporte → cae al sistema. El Rust lo compila el
CI de rama (este equipo no tiene cargo).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Venía con el gris de Chrome (#dee1e6 / #202124) en los dos temas, y encima de una
app negra se veía como una franja de navegador pegada a la ventana en vez de parte
de ella. Ahora la franja es casi blanca en tema claro y negra en oscuro, del tono de
la app.
El truco de Chrome se conserva —la pestaña activa es una tarjeta del color del
CONTENIDO y se funde con él—, solo que en oscuro el contraste va al revés: la franja
queda MÁS negra que el contenido, así que la activa destaca por ser más clara. Quien
marca el corte de verdad es el borde inferior, que la activa tapa con su fondo.
De paso, dos ajustes que arrastraba el cambio:
- El hover de las pestañas inactivas en tema claro era blanco al 55%: sobre una
franja casi blanca no se habría visto nada. Ahora oscurece.
- El ancho máximo de pestaña sube de 240 a 280 px. Los títulos ahora traen nombre
real y sección («Hotjamin Orgánicos · Archivos») y a 240 se cortaban de más. Con
muchas pestañas abiertas no cambia nada: mandan el reparto elástico y el modo
compacto de solo icono.
Verificado: sintaxis del script con node --check y la barra montada en el navegador
con el CSS real, en los dos temas.
Co-Authored-By: Claude Opus 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>
Con la barra ya en la fila del título, teñir es lo que hace que un montón de
pestañas abiertas se lea de un vistazo: todo lo de un cliente en verde, lo que está
en revisión en rojo. Se elige con clic derecho sobre la pestaña ▸ Color, con marca
en el que tiene puesto, y "Sin color" para quitarlo.
Nueve colores, los mismos que usa Chrome para sus grupos. El color se guarda con la
sesión (lista `colors` en estado.json, paralela a `tabs`), así que sobrevive a
cerrar la app. Un estado.json viejo sin colores carga igual: la lista entra por
serde default y todas las pestañas salen sin teñir.
Cómo se pinta: la inactiva lleva el color de fondo translúcido y se le quita el
separador vertical (con fondo propio ya se ve dónde acaba). La ACTIVA no se tiñe
entera —tiene que seguir fundida con el contenido, que es lo que le da el aspecto de
Chrome— sino que lleva el color en una franja de 3 px arriba. En el CSS el color va
en componentes RGB sueltos (`--tc: r, g, b`) para sacar de una sola definición tanto
el tono sólido como el fondo translúcido, en vez de repetir nueve colores por tema.
Al restaurar la sesión, el color se empareja con su URL ANTES de filtrar por origen:
el filtro puede descartar entradas y entonces los índices de las dos listas dejarían
de casar, y las pestañas saldrían con el color de otra.
El id que llega del menú se valida contra la paleta antes de guardarlo; uno
desconocido deja la pestaña sin color en vez de escribir cualquier cosa en el disco.
Verificado: sintaxis del script con node --check, y en el navegador que la inactiva
se tiñe, que la activa conserva su fondo de tarjeta con la franja del color encima,
y que el separador desaparece en las teñidas. La compilación la verifica el CI.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Verificar que compila no basta para un cambio que se ve: la barra de pestañas hay
que MIRARLA. El job nuevo construye el .exe de Windows y lo cuelga de la ejecución
como artifact, sin tocar Releases ni el updater. Así se puede probar el cambio en la
app de verdad antes de publicárselo al equipo, que en la práctica no tiene vuelta
atrás porque las apps instaladas se actualizan solas.
Firma con la clave del repo porque tauri.conf.json pide artefactos de updater; el
.exe se queda en el artifact igualmente.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
En Windows la ventana conservaba la barra de título del sistema y la barra de
pestañas iba DEBAJO: una fila entera gastada en repetir el nombre de la app. En
macOS ya no pasaba (TitleBarStyle::Overlay desde la v1.4.2). Ahora las dos se
comportan igual, y como Chrome: la barra de pestañas ES la fila del título.
- Ventana sin decoración en Windows (decorations(false)). resizable(true) conserva
WS_THICKFRAME, así que los bordes siguen redimensionando y Aero Snap (arrastrar a
un borde, Win+flechas) sigue vivo.
- La barra dibuja sus propios botones minimizar/maximizar/cerrar con las medidas y
colores del sistema (46 px de ancho, alto completo de la barra, esquinas rectas,
rojo #c42b1c al posarse en el aspa) y los glifos de Segoe Fluent Icons. Actúan
por el evento nuevo ls-window; cerrar sigue pasando por CloseRequested, o sea que
oculta a la bandeja igual que antes.
- El icono del medio alterna maximizar/restaurar según el estado. El repintado se
dispara solo cuando ese estado CAMBIA, no en cada píxel del redimensionado.
- La geometría maximizada ya no se guarda entre sesiones: sin decoración, al
reabrir volvería como ventana normal del tamaño de la pantalla y taparía la barra
de tareas. Se conserva la última geometría restaurada.
- Doble clic en el hueco libre de la barra ahora maximiza/restaura (lo gobierna
data-tauri-drag-region), como la barra de título del sistema y como Chrome. Antes
abría una pestaña nueva; para eso quedan el botón + y Cmd/Ctrl+T.
NO incluye los Snap Layouts de Windows 11 (el menú que asoma al posar el cursor
sobre maximizar). Requieren que la ventana conteste WM_NCHITTEST con HTMAXBUTTON, y
aquí ese botón lo tapa el webview hijo de la barra, que se queda con los mensajes
del ratón: la ventana principal nunca los ve. Es la misma razón por la que Electron
necesita botones NATIVOS (titleBarOverlay) para poder ofrecerlo. El resto de Aero
Snap no se ve afectado.
Se añade .github/workflows/check.yml: cargo check en Windows y macOS al empujar una
rama, sin publicar nada. Hasta ahora solo existía el workflow de publicar (etiquetas
v*), así que no había forma de comprobar que algo compilaba sin sacar un release.
Verificado: sintaxis del script de la barra con node --check; medidas, clases y
estados de los botones comprobados en el navegador (46 px, pegados al borde, ocultos
en macOS, icono y rótulo correctos al maximizar, y el arrastre solo en el hueco
libre, nunca en los botones). La compilación la verifica el workflow nuevo.
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>
La barra del shell ahora se ve como las pestanas de un navegador de verdad:
- La barra es una FRANJA mas oscura (claro #dee1e6 / oscuro #202124) y la
pestana ACTIVA es una tarjeta del color del contenido con esquinas
superiores redondeadas y las curvas concavas de Chrome en la base (dos
piezas radial-gradient de 8px que "derraman" el fondo hacia los lados).
El contenedor de pestanas se extiende 1px sobre el borde inferior para que
la activa lo tape y quede FUNDIDA con la pagina; en oscuro su fondo es
#18181b (el mismo de la app) para que no haya costura.
- Favicon por pestana: mini logo Labstream (SVG inline data-URI, sin red).
- Pestanas inactivas: pildora redondeada al hover y separador fino vertical
entre ellas, oculto junto a la activa, al hover y en la primera (CSS puro
con .active + .tab, sin :has).
- El boton de cerrar es un circulito y los botones de la barra pasan a
hover circular, como en Chrome.
Sin cambios en Rust: TAB_BAR_H sigue en 40 y los eventos ls-* intactos.
Verificado con arnes local (stub de __TAURI__ + 4 pestanas de prueba) en
claro y oscuro: curvas, fusion con el contenido, separadores y favicon OK.
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>