Commit Graph

20 Commits

Author SHA1 Message Date
sergio 751416252f feat(mirada): marco de ventana — distinguir y resaltar el foco
Sin decoración, las ventanas se confundían entre sí. Ahora el backend
DRM dibuja un marco fino alrededor de cada ventana: azul la que tiene
el foco del teclado, gris las demás.

- `ManagedWindow` gana `focused: bool` (lo fija `exec_op` al atender
  `BodyOp::Focus`/`Unfocus`) y `borders: [SolidColorBuffer; 4]` — un
  búfer por lado, cada uno con su `Id` estable para el seguimiento de
  daño; `SolidColorBuffer` sube su contador sólo si tamaño o color
  cambian, así un marco quieto no fuerza recomposición.
- El enum `Frame` pasa de `Cursor` a `Solid`: una variante de color
  sólido que sirve para el cursor y para los marcos (dos variantes con
  el mismo tipo chocarían en el `From` que genera `render_elements!`).
- `render()` en dos pasos: refresca los búferes (tamaño = contenido,
  color = foco) y luego arma los elementos. El marco va metido hacia
  adentro, sobre el borde de la superficie, así no pisa al vecino.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 04:10:32 +00:00
sergio fb3091d995 feat(mirada): acción spawn — lanzar programas desde el compositor
Un escritorio sin forma de abrir una terminal no es usable. Ahora el
keymap puede lanzar programas:

- `mirada-protocol`: nuevo `BrainCommand::Spawn(String)`.
- `mirada-brain`: `DesktopAction::Spawn(String)` con forma textual
  `spawn:<comando>` (`Display`/`FromStr`); `Desktop::apply` la traduce
  a `BrainCommand::Spawn`. El keymap por defecto trae
  `Super+Shift+Return` → `spawn:foot`. `DesktopAction` deja de ser
  `Copy` (lleva el comando) — `Keymap::lookup` clona en vez de copiar.
- `mirada-body`: `BodyOp::Spawn(String)`.
- `mirada-compositor`: `exec_op` ejecuta el spawn con un helper
  `spawn_command` (`sh -c`, hereda `WAYLAND_DISPLAY`), que también
  recoge el lanzamiento de `MIRADA_STARTUP` — antes duplicado.

`spawn:foot --title x` también funciona desde `mirada-ctl`. Tests
nuevos del round-trip textual y del flujo atajo→comando.

Nota: un keymap.ron ya existente no recibe el atajo nuevo; hay que
añadir la línea a mano o borrar el archivo para regenerarlo.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 03:59:37 +00:00
sergio 90bffec3f1 feat(mirada): mover/redimensionar ventanas con el ratón
`Super`+arrastre interactivo en el backend DRM: botón izquierdo mueve
la ventana, botón derecho la redimensiona. Al arrastrarla, la ventana
pasa a flotar — comportamiento estilo dwm.

La verdad geométrica vive en el Cerebro, así que el arrastre viaja
hasta él:

- `mirada-protocol`: nuevo `BodyEvent::WindowFloatTo { id, rect }`.
- `mirada-brain`: `Desktop::on_event` lo atiende — busca el escritorio
  de la ventana y la hace flotar en ese rectángulo
  (`Workspace::set_floating`). Dos tests nuevos.
- `mirada-compositor`: `DragGrab`/`DragMode` en `App`; `handle_input`
  arranca el arrastre con `Super`+botón sobre una ventana
  (`keyboard.modifier_state().logo`), traga los botones mientras dura y
  lo cierra al soltar. `drag_update` recalcula el rectángulo (mover =
  esquina sigue al puntero; redimensionar = esquina inferior-derecha,
  con un mínimo de 120 px) y emite `WindowFloatTo`. Durante el arrastre
  el puntero no llega al cliente.

De paso, arregla un test de `mirada-link` que construía un
`WindowPlacement` sin los campos `floating`/`fullscreen`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 03:46:03 +00:00
sergio b4ddab9c06 feat(mirada): puntero/ratón en el backend DRM del compositor
El backend DRM del Cuerpo deja de ser sólo-teclado: `libinput` ahora
mueve un cursor de software y reenvía clics y rueda a los clientes.

- Enum `Frame` (vía `render_elements!`) que mezcla superficies de
  cliente y un `SolidColorRenderElement` para el cursor, marcado
  `Kind::Cursor` y compuesto encima de todo.
- `handle_input` atiende `PointerMotion`/`PointerMotionAbsolute`/
  `PointerButton`/`PointerAxis`; el puntero se acota a la salida.
- Foco-sigue-ratón: `window_at` hace el test de impacto (flotantes
  sobre teseladas, contra el rectángulo real de la superficie) y, al
  cambiar de ventana, emite `BodyEvent::PointerEntered`.
- `surface_px_size` en main.rs — tamaño presentado de una superficie,
  reusado por el test de impacto.

Compila + clippy limpio; pendiente de verificar en hardware.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 03:24:14 +00:00
sergio f9c4bf594e feat(mirada): fullscreen iniciado por el cliente + HUD multi-salida
Dos remates de la tanda WM.

Fullscreen del cliente:
- BodyEvent::FullscreenRequest { id, fullscreen }. mirada-compositor
  implementa XdgShellHandler::fullscreen_request / unfullscreen_request
  y avisa al Cerebro; Desktop::on_event fija el fullscreen en el
  escritorio que tiene la ventana. Así un reproductor o un juego que
  llama a xdg set_fullscreen entra a pantalla completa solo.

HUD multi-salida (app mirada):
- El lienzo dibuja todas las salidas a escala (encaja su caja
  envolvente en el lienzo fijo; con una salida, 1:1), cada una con su
  marco y su número/escritorio. En simulación, Shift+n añade un monitor.

mirada-brain 63->65 tests.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 01:32:08 +00:00
sergio 13d2ae71fb feat(mirada): scratchpad — ventana desplegable estilo terminal quake
Una ventana se puede guardar en el scratchpad (oculta, en ningún
escritorio) e invocar a voluntad como overlay flotante — el patrón de
la terminal desplegable.

- Desktop.scratchpad: Vec<WindowId>. SendToScratchpad saca la ventana
  enfocada del teselado y la guarda; ToggleScratchpad (Super+`) la
  invoca flotando y centrada en el escritorio activo, o la oculta.
- Invocarla desde otro escritorio la trae consigo (sale de donde
  estuviera). WindowClosed la quita del scratchpad.
- window_lines marca las guardadas como workspace 0; mirada-ctl windows
  las lista como «esc scratch».

Sin cambios de protocolo — una ventana del scratchpad invocada no es
más que una flotante. Verificado end-to-end con headless-ctl.
mirada-brain 58->63 tests.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 01:23:17 +00:00
sergio 799dcef22e feat(mirada): multi-monitor real — cada salida tesela su escritorio
El Desktop deja de teselar sólo la salida primaria. Cada Output muestra
un escritorio virtual distinto y relayout() las tesela todas en un solo
Place que cubre todas las pantallas.

- Output { id, rect, workspace }; focused_output reemplaza al índice
  global active. active_index() = el escritorio de la salida enfocada.
- OutputAdded asigna el primer escritorio libre; OutputRemoved deja sus
  ventanas en su escritorio y reajusta el foco. reflow_outputs() las
  recoloca en fila.
- SwitchWorkspace actúa sobre la salida enfocada; si el escritorio
  pedido ya lo muestra otra salida, las intercambia (invariante: un
  escritorio se ve en una salida como mucho).
- DesktopAction::FocusOutputNext (Super+o) mueve el foco entre
  monitores. El foco del teclado es único — relayout() lo unifica a la
  ventana enfocada de la salida enfocada.

Verificado end-to-end con headless-ctl (ahora 2 salidas).
mirada-brain 52->58 tests.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 01:18:12 +00:00
sergio be61ddb6eb feat(mirada): pantalla completa real — toggle-fullscreen
ToggleFullscreen (Super+Shift+f) lleva la ventana enfocada a pantalla
completa: cubre toda la salida sin gap, oculta al resto y se lleva el
foco. Distinto del modo Monocle (un modo de teselado): es un estado
por ventana que ignora el layout.

- Workspace.fullscreen: Option<WindowId>; set_fullscreen / fullscreen();
  remove() lo limpia si se cierra esa ventana.
- placements() da a la fullscreen el rect completo y marca al resto
  visible: false. WindowPlacement y BodyOp::Configure llevan
  fullscreen: bool.
- mirada-compositor fija el estado xdg_toplevel::Fullscreen en la
  superficie, para que el cliente lo sepa.
- Cableado en keymap, HUD de mirada y mirada-ctl.

Verificado end-to-end con headless-ctl. mirada-protocol 10->11,
mirada-brain 51->52.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 01:07:01 +00:00
sergio 6dfd9e62ac feat(mirada): reglas de ventana — escritorio y flotante por app_id
mirada-brain::rules — config declarativa que decide qué hacer con una
ventana al abrirse, mismo patrón que el keymap.

- Rule casa por subcadena de app_id y/o title (sin distinguir
  mayúsculas; vacío = cualquiera) y aplica un destino: workspace (1..9)
  y/o floating. Gana la primera regla que case.
- Rules en RON (~/.config/mirada/rules.ron); la primera vez se escribe
  una plantilla con ejemplos comentados, si está corrupta se ignora.
- Desktop consulta Rules::resolve en cada WindowOpened — el evento ya
  trae app_id/title — y abre la ventana en su escritorio, flotando si
  toca. set_rules en Desktop; las apps cargan rules.ron al arrancar.

mirada-brain 42->51 tests.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 01:01:14 +00:00
sergio 4719f7c9f9 feat(mirada): ventanas flotantes — toggle-float
Una ventana puede salir del teselado y flotar: conserva su propio
rectángulo y se compone por encima de las teseladas.

- Workspace guarda las flotantes en un mapa aparte; layout() tesela
  sólo las no-flotantes y añade las flotantes al final (orden de
  pintado). set_floating / is_floating.
- WindowPlacement y BodyOp::Configure llevan floating: bool. BodyState
  detecta el cambio de floating como cualquier otro reconfigure.
- DesktopAction::ToggleFloat (Super+f): saca la enfocada a un
  rectángulo centrado al 60 % de la pantalla, o la devuelve al teselado.
  En Monocle, una flotante sigue visible.
- mirada-compositor ordena las flotantes al frente de la lista
  front-to-back de elementos → se pintan encima.
- HUD de mirada marca las flotantes; mirada-ctl toggle-float.

Verificado end-to-end con headless-ctl. mirada-layout 30->32,
mirada-protocol 9->10, mirada-body 13->14, mirada-brain 41->42.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 00:55:33 +00:00
sergio 2dd8ff139e feat(mirada): nmaster, promover a maestra y smart gaps (estilo dwm)
Tanda de funciones de tiling WM, toda pura (mirada-layout/brain), sin
tocar el protocolo:

- nmaster: LayoutParams.master_count — cuántas ventanas van en el área
  maestra. MasterStack y CenteredMaster apilan N maestras; sin pila, las
  maestras llenan la pantalla. Acciones inc-master/dec-master (Super+,
  Super+.), acotadas 1..9.
- Promover a maestra: Workspace::promote_focused lleva la ventana
  enfocada al puesto 0. Acción promote-to-master (Super+Return).
- Smart gaps: una sola ventana se tesela a sangre, sin margen.

combo_string del compositor canoniza ahora teclas con nombre (Return,
Tab, F5, flechas…) vía xkb::keysym_get_name, no sólo caracteres
imprimibles — sin eso Super+Return no sería un atajo expresable.

Cableado en keymap por defecto, HUD de mirada y mirada-ctl. Verificado
end-to-end con headless-ctl. mirada-layout 26->30, mirada-brain 39->41.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 00:45:47 +00:00
sergio 8821d34bd5 feat(mirada): 3 layouts nuevos + redimensionar el área maestra
mirada-layout pasa de 4 a 7 modos de teselado, todos intercambiables
por el API (SetLayout / CycleLayout / mirada-ctl layout <modo>):

- Rows: filas horizontales de igual alto (complemento de Columns).
- Spiral: espiral de Fibonacci — cada ventana parte por la mitad el
  espacio restante, alternando el sentido del corte.
- CenteredMaster: maestra centrada + pila a ambos lados (monitores
  anchos).

LayoutMode::ALL + next() definen el ciclo. Añade dos acciones,
GrowMaster/ShrinkMaster (Super+l / Super+h), que ajustan master_ratio
en caliente — ese parámetro existía pero no había forma de tocarlo.

Cableado completo: tile(), cycle, slugs Display/FromStr, keymap por
defecto (Super+r/d/s), HUD de mirada, mirada-ctl actions. El ejemplo
headless-ctl ahora imprime la geometría para verificar los layouts.

mirada-layout 22->26 tests, mirada-brain 37->39.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 00:37:16 +00:00
sergio b31f988833 feat(mirada): API de acciones — mirada-ctl + HUD interactivo
Toda acción de escritorio converge en Desktop::apply(DesktopAction); el
keymap era sólo un front-end. Esta tanda añade los otros tres.

- DesktopAction::FocusWindow(WindowId): direccionamiento directo de una
  ventana (no sólo ciclar); si está en otro escritorio, salta a él.
  DesktopAction pasa a ser Serialize/Deserialize (postcard) además de
  Display/FromStr.

- mirada-brain::ctl: el API de control externo. CtlRequest/CtlReply
  (marco postcard), CtlServer/CtlConn no bloqueantes y send_request.
  El Cerebro abre el socket y atiende en su bucle: la app mirada
  siempre, mirada-compositor sólo con el Cerebro embebido.

- mirada-ctl: CLI de control estilo swaymsg/hyprctl —
  `mirada-ctl focus-next | focus-window 5 | workspace 3 | windows`.
  Parsea la acción de los argumentos vía FromStr.

- HUD interactivo en la app mirada: pips de escritorio y ventanas del
  lienzo clicables (SwitchWorkspace / FocusWindow).

- Ejemplo headless-ctl: un Cerebro sin gráficos para probar mirada-ctl
  en modo desatendido. Verificado end-to-end.

mirada-brain: 29 -> 37 tests.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 00:20:10 +00:00
sergio 8204852e3a feat(mirada): keymap configurable en RON, recargable en caliente
Los atajos de teclado dejan de estar cableados: ahora son un Keymap
configurable que vive sólo en el Cerebro. El Cuerpo nunca lo ve — sólo
recibe la lista de cadenas a interceptar (GrabKeys) y devuelve la
pulsada; es Desktop quien la traduce. Esa separación (qué interceptar
vs. qué significa) hace innecesario cualquier candado o Arc.

mirada-brain:
- keymap.rs — Keymap: from_ron/to_ron, load/save, load_or_init (escribe
  un archivo por defecto documentado si falta; default sin pisar si está
  corrupto), default_path (~/.config/mirada/keymap.ron), y watch sobre
  notify para la recarga en caliente (KeymapWatch).
- DesktopAction: Display + FromStr — vocabulario textual estable
  ("focus-next", "layout:grid", "workspace:3"); evita los guiones que
  romperían el RON de un enum.
- Desktop: with_keymap, set_keymap (cambio en caliente -> nuevo GrabKeys).
- Ejemplo keymap-default: imprime el archivo por defecto en RON.

Apps: mirada y mirada-compositor (modo embebido) cargan el keymap del
usuario al arrancar y lo recargan en caliente cuando el archivo cambia.

Disco RON, cable postcard (sólo la lista de cadenas), sin ejecutable
configurador. mirada-brain: 17 -> 29 tests.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 00:04:11 +00:00
sergio d2e0cf4830 feat(mirada): mirada-compositor — el Cuerpo, compositor Wayland sobre smithay
Compositor Wayland teselante real sobre smithay, backend winit (corre
anidado como ventana dentro de la sesión X11/Wayland actual). Habla
wl_compositor/xdg_shell/wl_shm/wl_seat/wl_data_device y compone las
superficies de los clientes con GlesRenderer.

Dos modos: autónomo (Cerebro Desktop embebido, un solo proceso) o
enlazado (MIRADA_SOCKET → la app mirada decide la geometría). Reusa
mirada-body para la contabilidad y mirada-link para el cable.

Actualiza el SDD: el Cuerpo deja de ser pendiente. Añade README.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 22:44:39 +00:00
sergio b7181da7d9 feat(matilda): detección de drift con docker inspect
matilda-discover gana discover_inventory(): corre docker inspect en
cada contenedor y compara contra el spec deseado — imagen, puertos,
env y volúmenes declarados. Si el contenedor que corre se desvió, el
plan emite un Update; si está al día, no hay acción. La comparación es
por satisfacción (lo extra que trae la imagen se ignora).

El CLI (--discover) y el shell (:matilda) ahora usan discover_inventory
en vez del descubrimiento por nombre: detectan no sólo qué crear y
eliminar, sino la deriva de configuración de lo que ya existe.

container_drift es puro — 6 tests nuevos con JSON de docker inspect.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 20:44:50 +00:00
sergio df8f92fbb0 feat(matilda): ghost + linker + CLI — el ciclo completo de aplicación
matilda-ghost: el agente que ejecuta los ApplySteps en la máquina
destino — escribe archivos, corre comandos, reporta paso a paso;
semántica set -e (se detiene en el primer error). dry_run previsualiza
sin tocar nada. 5 tests.

matilda-linker: aplica los pasos en un host remoto por SSH sobre
brahman-ssh-multiplex; produce el mismo ApplyReport que el ghost local.

apps/matilda: deja de ser una demo hardcoded — ahora es una CLI real:
  matilda example | plan | script | apply  (local · --dry-run · --host)
Carga el inventario de un JSON, reconcilia y aplica.

matilda: 6 crates + CLI, ~42 tests. La cadena va de la declaración
a la aplicación local/remota.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 20:27:28 +00:00
sergio cd3b41a401 feat(dominium): dominium-physics — ciclo del motor (difusión + tick)
- diffuse — ecuación de fluidos discreta sobre los 3 campos dinámicos
  (materia/psique/poder): cada celda intercambia con sus 4 vecinas +
  entropía. Buffer de lectura separado (lee estado viejo). oro y
  degradacion no difunden.
- tick — un paso completo: difusión → transiciones (agente exhausto se
  fuerza a Pelear) → acciones de los agentes → envejecimiento + cosecha
  (la energía del muerto vuelve como materia/fertilidad). run() corre N.

Determinista bit-exacto: aritmética f32 en orden fijo, sin HashMap ni
reducciones paralelas. Test `run_is_deterministic` verifica que mismo
input → mismo estado bit a bit.

7 tests verdes. cargo check --workspace verde. dominium ya CORRE
(core + physics = simulación funcional).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 16:08:01 +00:00
sergio 05886022e0 feat(sandokan-remote): B1.4 — RemoteEngine vía SSH socket-forward
Opción B: RemoteEngine orquesta en un host remoto tunelando el wire
del daemon sobre un canal SSH direct-streamlocal hacia el sandokan.sock
remoto. El protocolo es idéntico al de DaemonEngine (postcard
length-prefixed) — sólo cambia el transporte, así que read_frame/
write_frame se reusan tal cual.

- brahman-ssh-multiplex: + SshSession::forward_unix — abre un canal
  direct-streamlocal y devuelve su ChannelStream (AsyncRead+AsyncWrite).
- sandokan-daemon: protocol ahora pub, exporta read_frame/write_frame.
- sandokan-remote: RemoteEngine { SshSession + remote_socket }.
  connect() o with_session(); cada operación abre un canal nuevo
  (multiplexado sobre la conexión maestra).
- sandokan umbrella re-exporta RemoteEngine.

Completa Fase B: sandokan tiene Local + Daemon + Remote + auto().
cargo check --workspace verde. RemoteEngine necesita un host remoto
con `sandokan daemon` para validación runtime (sin unit test).

Opción A (text-parse del CLI por compat) queda pendiente por decisión
del usuario.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 15:37:11 +00:00
sergio b83d40a833 refactor(naming): A1 — ente→arje, vista→revista, pluma→fana
Rename batch de la Fase A del PLAN_MACRO:
- 25 crates ente-* → arje-* (protocol/init/runtime/compat). El linaje
  arje (init Linux) queda con prefijo coherente.
- vista → revista (revista-core + revista-web).
- pluma → fana (fana-md + fana-md-reader-web). fana absorbe el linaje
  markdown de pluma; será el writer DAG editor (prioridad alta).

Cambios:
- git mv de 29 crate dirs + 2 SDDs
- package/lib/bin names + path refs + imports .rs reescritos
- workspace Cargo.toml + comentarios de sección
- SDDs de init/runtime/compat/protocol actualizados a arje-
- SDD de revista + SDD de fana (reescrito: writer DAG editor)
- docs/STATUS.md, ROADMAP.md, PLAN_MACRO.md, arje-boot.md,
  arje-replace-systemd.md actualizados
- docs/changelog/akasha.md → chasqui.md

scripts/rename-fase-a.py idempotente (--dry-run soportado).
cargo check --workspace verde.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 00:10:14 +00:00