gioser

tawasuyu/pineal

Backend-agnostic GPU visualization — a catalog of painters over one Canvas trait, in Rust.

chore: refresco desde el monorepo (pin tawasuyu.git v0.3.0)

Trae los README y LEEME de todos los crates del dominio, que ahora existen
para los 1.096 crates del workspace.

fe0f1edcc39534498e3de0383829cbcbd9bfa778 · por Sergio · (hace 1 mes) · padre df7773b4f · ver el árbol en este commit

Delta semántico · 60

00_unanchay/pineal/pineal-quiver/src/campo.rs

00_unanchay/pineal/pineal-quiver/src/lib.rs

00_unanchay/pineal/pineal-yupana/src/lib.rs

20 archivos · +1603 −539

diff --git a/00_unanchay/pineal/LEEME.md b/00_unanchay/pineal/LEEME.md
index 366b2d9..6ebf3f7 100644
--- a/00_unanchay/pineal/LEEME.md
+++ b/00_unanchay/pineal/LEEME.md
@@ -6,7 +6,7 @@
 
 Pineal es un catálogo de painters especializados — cartesiano, polar,
 mesh, treemap, phosphor, flow, heatmap, stream, financial, hexbin,
-contour y bars — sobre una **única abstracción de pintura**, el trait
+contour, bars y quiver — sobre una **única abstracción de pintura**, el trait
 `Canvas`. Cualquier dominio del workspace puede empujar formas a un
 pineal y obtener pixels sin cargar pipeline gráfica propia. Toda la
 cadena `core → render → painter` es agnóstica del backend: el mismo
@@ -115,6 +115,7 @@ pasada vello hermana) cuando hagan falta etiquetas.
 | [`pineal-hexbin`](pineal-hexbin/) | `paint_hexbin` | Bineado hexagonal pointy-top + ramp Viridis. |
 | [`pineal-contour`](pineal-contour/) | `paint_contours` | Marching squares (16 casos) → polilíneas por nivel. |
 | [`pineal-bars`](pineal-bars/) | `paint_bars`, `paint_grouped`, `paint_stacked` | Columnas/barras vertical u horizontal, agrupadas y apiladas, baseline con negativos; `Histogram` binea `&[f32]` → barras. |
+| [`pineal-quiver`](pineal-quiver/) | `paint` | Campo vectorial en grilla → flechas. El único painter que dibuja **dirección**: `contour` da las isolíneas de un escalar, esto da su gradiente. |
 
 ## Consideraciones
 
diff --git a/00_unanchay/pineal/README.md b/00_unanchay/pineal/README.md
index ab07a83..acb1953 100644
--- a/00_unanchay/pineal/README.md
+++ b/00_unanchay/pineal/README.md
@@ -5,7 +5,7 @@
 ![the pineal painter gallery rendered headless: an 11-tile grid — sine cartesian, pie/donut, radar, squarified treemap, Viridis heatmap, hexbin, contour isolines, a budget sankey, a force-directed mesh, bars with a negative value and a histogram](https://tawasuyu.net/00_unanchay/pineal/pantallazo.png)
 
 Pineal is a catalog of specialized painters — cartesian, polar, mesh,
-treemap, phosphor, flow, heatmap, stream, financial, hexbin, contour and
+treemap, phosphor, flow, heatmap, stream, financial, hexbin, contour, quiver and
 bars — over a **single painter abstraction**, the `Canvas` trait. Any
 domain in the workspace can push shapes to a pineal and get pixels
 without carrying its own graphics stack. The whole chain
@@ -114,6 +114,7 @@ sibling vello pass) when you need labels.
 | [`pineal-financial`](pineal-financial/) | `paint_candles` | OHLC + time-bucket aggregation. |
 | [`pineal-hexbin`](pineal-hexbin/) | `paint_hexbin` | Pointy-top hexagonal binning + Viridis ramp. |
 | [`pineal-contour`](pineal-contour/) | `paint_contours` | Marching squares (16 cases) → per-level polylines. |
+| [`pineal-quiver`](pineal-quiver/) | `paint` | Grid-sampled vector field → arrows. The only painter that draws **direction**: `contour` gives a scalar field's isolines, this gives its gradient. |
 | [`pineal-bars`](pineal-bars/) | `paint_bars`, `paint_grouped`, `paint_stacked` | Columns/bars vertical or horizontal, grouped and stacked, baseline with negatives; `Histogram` bins `&[f32]` → bars. |
 
 ## Considerations
diff --git a/00_unanchay/pineal/SDD.md b/00_unanchay/pineal/SDD.md
index 683c119..da16f8c 100644
--- a/00_unanchay/pineal/SDD.md
+++ b/00_unanchay/pineal/SDD.md
@@ -41,7 +41,8 @@ pineal-render ──── trait Canvas
                   └── PlanRecorder (replay diferido)
 
 pineal-{cartesian, polar, mesh, treemap, phosphor, flow,
-        heatmap, stream, financial, hexbin, contour, bars} ── painters
+        heatmap, stream, financial, hexbin, contour, bars,
+        quiver} ── painters
 
 pineal-export ── consume RenderPlan → SVG + PNG
 
@@ -104,6 +105,7 @@ samples por pixel) da AA suficiente para reportes y dashboards.
 | `pineal-hexbin` | `paint_hexbin` | Bineado hexagonal pointy-top + ramp Viridis |
 | `pineal-contour` | `paint_contours` | Marching squares 16 casos → polilíneas por nivel |
 | `pineal-bars` | `paint_bars`, `paint_grouped`, `paint_stacked` | Columnas/barras vertical u horizontal, agrupadas y apiladas, baseline con negativos; `Histogram` binea `&[f32]` → barras |
+| `pineal-quiver` | `paint` | Campo vectorial en grilla → flechas; largo por magnitud, color opcional por ramp |
 
 ### 5.1 Barnes-Hut (added 2026-05-28)
 
@@ -172,11 +174,19 @@ Total al 2026-06-01: **140+ tests verdes**.
 
 ### Hecho
 
-- Catálogo: 15 crates viz/render/export (core, render, cartesian,
+- Catálogo: 16 crates viz/render/export (core, render, cartesian,
   polar, mesh, treemap, phosphor, flow, heatmap, stream, financial,
-  hexbin, contour, bars, umbrella) + export SVG/PNG/PDF + 14 demos
+  hexbin, contour, bars, quiver, umbrella) + export SVG/PNG/PDF + 14 demos
   ejecutables (incluyendo `pineal-galeria-demo`, que muestra 11 painters
   en una sola ventana, y `pineal-bars-demo`).
+- **quiver** (added 2026-08-17, para krama M3): el único painter
+  **vectorial**. `pineal-contour` ya dibujaba las isolíneas de un campo
+  escalar; faltaba el gradiente de ese mismo campo, que es lo que produce
+  una derivación apenas deja de ser un escalar. Pintar sólo la magnitud
+  con un heatmap tira la dirección, que es la mitad interesante.
+  `CampoVectorial::muestrear` llena la grilla desde una función; el
+  painter emite 3 trazos por flecha (astil + dos barbas), así el export
+  SVG sale con líneas editables y no con polígonos. 14 tests.
 - **bars** (added 2026-06-01, a pedido): el gráfico común que faltaba.
   `paint_bars`/`paint_grouped`/`paint_stacked` (vertical u horizontal,
   baseline con negativos) + `Histogram` para bineado de muestras. 10
diff --git a/00_unanchay/pineal/demos/pineal-galeria-demo/Cargo.toml b/00_unanchay/pineal/demos/pineal-galeria-demo/Cargo.toml
index 2402f8e..d41db0e 100644
--- a/00_unanchay/pineal/demos/pineal-galeria-demo/Cargo.toml
+++ b/00_unanchay/pineal/demos/pineal-galeria-demo/Cargo.toml
@@ -7,6 +7,10 @@ authors = { workspace = true }
 publish = { workspace = true }
 description = "Galería estática de pineal — una grilla de tiles, cada uno un painter distinto del catálogo (cartesian, polar, treemap, heatmap, hexbin, contour, flow, mesh) con datos sintéticos deterministas."
 
+[[bin]]
+name = "pineal-gallery-demo"
+path = "src/main.rs"
+
 [dependencies]
 llimphi-ui = { workspace = true }
 llimphi-theme = { workspace = true }
diff --git a/00_unanchay/pineal/demos/pineal-galeria-demo/examples/pantallazo_pineal.rs b/00_unanchay/pineal/demos/pineal-galeria-demo/examples/pantallazo_pineal.rs
index 6533044..7b157ce 100644
--- a/00_unanchay/pineal/demos/pineal-galeria-demo/examples/pantallazo_pineal.rs
+++ b/00_unanchay/pineal/demos/pineal-galeria-demo/examples/pantallazo_pineal.rs
@@ -1,6 +1,6 @@
 //! Pantallazo headless de `pineal` — la galería completa de painters.
 //!
-//! Monta la **view real** de `pineal-galeria-demo` (grilla de 11 tiles:
+//! Monta la **view real** de `pineal-gallery-demo` (grilla de 11 tiles:
 //! cartesian, pie/donut, radar, treemap, heatmap, hexbin, contour, sankey,
 //! mesh force-directed, bars e histograma — todos con datos sintéticos
 //! deterministas, sin reloj ni RNG de sistema) reutilizando el `init` y el
diff --git a/00_unanchay/pineal/demos/pineal-galeria-demo/src/main.rs b/00_unanchay/pineal/demos/pineal-galeria-demo/src/main.rs
index 0284292..036250a 100644
--- a/00_unanchay/pineal/demos/pineal-galeria-demo/src/main.rs
+++ b/00_unanchay/pineal/demos/pineal-galeria-demo/src/main.rs
@@ -1,4 +1,4 @@
-//! `pineal-galeria-demo` — galería estática de TODO el catálogo de painters.
+//! `pineal-gallery-demo` — galería estática de TODO el catálogo de painters.
 //!
 //! Una sola ventana con una grilla 3×3 de tiles; cada tile pinta un
 //! painter distinto de pineal con datos sintéticos deterministas (sin
diff --git a/00_unanchay/pineal/pineal-quiver/Cargo.toml b/00_unanchay/pineal/pineal-quiver/Cargo.toml
new file mode 100644
index 0000000..749c206
--- /dev/null
+++ b/00_unanchay/pineal/pineal-quiver/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "pineal-quiver"
+version = { workspace = true }
+edition = { workspace = true }
+license = { workspace = true }
+authors = { workspace = true }
+repository = { workspace = true }
+publish = true
+description = "Pineal — quiver: campo vectorial muestreado en grilla → flechas. El painter que le faltaba al catálogo."
+
+[dependencies]
+pineal-render = { path = "../pineal-render", version = "0.1.0" }
+pineal-heatmap = { path = "../pineal-heatmap", version = "0.1.0" }
diff --git a/00_unanchay/pineal/pineal-quiver/LEEME.md b/00_unanchay/pineal/pineal-quiver/LEEME.md
new file mode 100644
index 0000000..bbd1b28
--- /dev/null
+++ b/00_unanchay/pineal/pineal-quiver/LEEME.md
@@ -0,0 +1,19 @@
+# pineal-quiver
+
+*Read this in English: [README.md](README.md).*
+
+> Un campo vectorial muestreado en grilla, dibujado como flechas. El painter que le faltaba al catálogo.
+
+pineal tenía doce painters y ninguno para un campo **vectorial**. El más parecido
+era `pineal-flow`, pero flow es Sankey: caudales entre nodos, no una dirección por
+punto del plano. Y `pineal-contour` dibuja las isolíneas de un campo **escalar**,
+que es la otra mitad del mismo dibujo (el potencial, no el gradiente).
+
+- `campo` — el campo muestreado: origen, paso y un `(dx, dy)` por celda.
+- `paint` — flechas escaladas por magnitud, coloreadas por la paleta y recortadas al rect del gráfico.
+
+Agnóstico de backend como el resto de pineal: emite geometría, no toca la GPU.
+
+---
+
+Parte de **pineal** — ver [pineal](../LEEME.md).
diff --git a/00_unanchay/pineal/pineal-quiver/README.md b/00_unanchay/pineal/pineal-quiver/README.md
new file mode 100644
index 0000000..a356dde
--- /dev/null
+++ b/00_unanchay/pineal/pineal-quiver/README.md
@@ -0,0 +1,17 @@
+# pineal-quiver
+
+> A vector field sampled on a grid, drawn as arrows. The painter the catalogue was missing.
+
+pineal had twelve painters and none for a **vector** field. The closest was
+`pineal-flow`, but flow is Sankey: flows between nodes, not one direction per
+point of the plane. And `pineal-contour` draws the isolines of a **scalar**
+field — the other half of the same picture (the potential, not the gradient).
+
+- `campo` — the sampled field: origin, step, and one `(dx, dy)` per cell.
+- `paint` — arrows scaled by magnitude, coloured by the palette, clipped to the plot rect.
+
+Backend-agnostic like the rest of pineal: it emits geometry, it doesn't touch a GPU.
+
+---
+
+Part of **pineal** — see [pineal](../README.md).
diff --git a/00_unanchay/pineal/pineal-quiver/src/campo.rs b/00_unanchay/pineal/pineal-quiver/src/campo.rs
new file mode 100644
index 0000000..b5f2c8a
--- /dev/null
+++ b/00_unanchay/pineal/pineal-quiver/src/campo.rs
@@ -0,0 +1,161 @@
+//! `CampoVectorial` — una grilla `ancho × alto` de vectores `(u, v)`.
+//!
+//! Dos componentes en dos `Vec` separados y no un `Vec<(f32,f32)>`: así cada
+//! componente se puede pasar entera a un consumidor que sólo quiera una (por
+//! ejemplo un heatmap de la magnitud) sin desentrelazar nada.
+
+/// Grilla densa de vectores. `(0,0)` es la esquina superior-izquierda del
+/// dominio, igual que en `HeatmapMatrix`.
+#[derive(Debug, Clone, PartialEq)]
+pub struct CampoVectorial {
+    u: Vec<f32>,
+    v: Vec<f32>,
+    ancho: usize,
+    alto: usize,
+}
+
+impl CampoVectorial {
+    /// Campo de ceros.
+    pub fn nuevo(ancho: usize, alto: usize) -> Self {
+        Self {
+            u: vec![0.0; ancho * alto],
+            v: vec![0.0; ancho * alto],
+            ancho,
+            alto,
+        }
+    }
+
+    /// Construye desde componentes crudas. `None` si alguna longitud no es
+    /// `ancho*alto` — un campo con las componentes desalineadas dibujaría
+    /// flechas plausibles y equivocadas, que es peor que no dibujar.
+    pub fn desde_datos(u: Vec<f32>, v: Vec<f32>, ancho: usize, alto: usize) -> Option<Self> {
+        if u.len() != ancho * alto || v.len() != ancho * alto {
+            return None;
+        }
+        Some(Self { u, v, ancho, alto })
+    }
+
+    /// Muestrea una función sobre el dominio `[x0,x1] × [y0,y1]` en una grilla
+    /// de `ancho × alto` puntos.
+    ///
+    /// Es el camino que usa un lienzo de expresiones: la función es la
+    /// derivación evaluada, y el campo es lo que se pinta.
+    pub fn muestrear(
+        ancho: usize,
+        alto: usize,
+        dominio: (f32, f32, f32, f32),
+        mut f: impl FnMut(f32, f32) -> (f32, f32),
+    ) -> Self {
+        let (x0, y0, x1, y1) = dominio;
+        let mut campo = Self::nuevo(ancho, alto);
+        // Con una sola columna el paso sería una división por cero; el punto
+        // muestreado es el borde izquierdo y ya.
+        let dx = if ancho > 1 { (x1 - x0) / (ancho - 1) as f32 } else { 0.0 };
+        let dy = if alto > 1 { (y1 - y0) / (alto - 1) as f32 } else { 0.0 };
+        for j in 0..alto {
+            for i in 0..ancho {
+                let (u, v) = f(x0 + i as f32 * dx, y0 + j as f32 * dy);
+                campo.fijar(i, j, u, v);
+            }
+        }
+        campo
+    }
+
+    pub fn ancho(&self) -> usize {
+        self.ancho
+    }
+    pub fn alto(&self) -> usize {
+        self.alto
+    }
+    pub fn u(&self) -> &[f32] {
+        &self.u
+    }
+    pub fn v(&self) -> &[f32] {
+        &self.v
+    }
+
+    /// Vector en `(x, y)`. `(0,0)` fuera de rango.
+    pub fn get(&self, x: usize, y: usize) -> (f32, f32) {
+        if x >= self.ancho || y >= self.alto {
+            return (0.0, 0.0);
+        }
+        let i = y * self.ancho + x;
+        (self.u[i], self.v[i])
+    }
+
+    /// Fija el vector en `(x, y)`. No-op fuera de rango.
+    pub fn fijar(&mut self, x: usize, y: usize, u: f32, v: f32) {
+        if x >= self.ancho || y >= self.alto {
+            return;
+        }
+        let i = y * self.ancho + x;
+        self.u[i] = u;
+        self.v[i] = v;
+    }
+
+    /// La magnitud más grande del campo. `0.0` si el campo está vacío o es todo
+    /// ceros — el painter la usa como divisor y chequea ese caso.
+    ///
+    /// Los no-finitos (un `NaN` que entró por una división por cero de la
+    /// función muestreada) **no cuentan**: si contaran, un solo `NaN` volvería
+    /// `NaN` al máximo y apagaría el campo entero.
+    pub fn magnitud_maxima(&self) -> f32 {
+        self.u
+            .iter()
+            .zip(&self.v)
+            .map(|(u, v)| (u * u + v * v).sqrt())
+            .filter(|m| m.is_finite())
+            .fold(0.0_f32, f32::max)
+    }
+
+    /// La magnitud de cada muestra, en el mismo orden que la grilla. Para
+    /// pasarle el campo a un heatmap o a un contorno sin recalcularla.
+    pub fn magnitudes(&self) -> Vec<f32> {
+        self.u
+            .iter()
+            .zip(&self.v)
+            .map(|(u, v)| (u * u + v * v).sqrt())
+            .collect()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn desde_datos_rechaza_componentes_desalineadas() {
+        assert!(CampoVectorial::desde_datos(vec![1.0; 6], vec![1.0; 5], 3, 2).is_none());
+        assert!(CampoVectorial::desde_datos(vec![1.0; 6], vec![1.0; 6], 3, 2).is_some());
+    }
+
+    #[test]
+    fn muestrear_cubre_los_dos_bordes_del_dominio() {
+        // Un muestreo que no toca los extremos deja el campo corrido medio
+        // paso, y eso no se ve en el dibujo: se ve como si el campo fuera otro.
+        let c = CampoVectorial::muestrear(3, 1, (0.0, 0.0, 10.0, 0.0), |x, _| (x, 0.0));
+        assert_eq!(c.get(0, 0).0, 0.0);
+        assert_eq!(c.get(1, 0).0, 5.0);
+        assert_eq!(c.get(2, 0).0, 10.0);
+    }
+
+    #[test]
+    fn muestrear_con_una_sola_columna_no_divide_por_cero() {
+        let c = CampoVectorial::muestrear(1, 1, (2.0, 3.0, 9.0, 9.0), |x, y| (x, y));
+        assert_eq!(c.get(0, 0), (2.0, 3.0));
+    }
+
+    #[test]
+    fn magnitud_maxima_ignora_los_no_finitos() {
+        let c = CampoVectorial::desde_datos(vec![3.0, f32::NAN], vec![4.0, 1.0], 2, 1).unwrap();
+        assert_eq!(c.magnitud_maxima(), 5.0);
+    }
+
+    #[test]
+    fn fuera_de_rango_es_cero_y_no_panic() {
+        let mut c = CampoVectorial::nuevo(2, 2);
+        c.fijar(9, 9, 1.0, 1.0);
+        assert_eq!(c.get(9, 9), (0.0, 0.0));
+        assert_eq!(c.magnitud_maxima(), 0.0);
+    }
+}
diff --git a/00_unanchay/pineal/pineal-quiver/src/lib.rs b/00_unanchay/pineal/pineal-quiver/src/lib.rs
new file mode 100644
index 0000000..804760e
--- /dev/null
+++ b/00_unanchay/pineal/pineal-quiver/src/lib.rs
@@ -0,0 +1,26 @@
+//! `pineal-quiver` — campo vectorial muestreado en grilla → flechas.
+//!
+//! # Qué le faltaba al catálogo
+//!
+//! Pineal tenía doce painters y ninguno para un campo **vectorial**. El más
+//! parecido era `pineal-flow`, pero flow es Sankey: caudales entre nodos, no
+//! una dirección por punto del plano. Y `pineal-contour` dibuja las isolíneas
+//! de un campo **escalar**, que es la otra mitad del mismo dibujo: el potencial
+//! sin su gradiente.
+//!
+//! Un gradiente, una velocidad, una fuerza por punto del plano son lo que un
+//! lienzo de expresiones produce apenas la derivación deja de ser un escalar —
+//! y sin este painter la única salida era pintar la magnitud con un heatmap,
+//! que **tira la dirección**, que es justamente la mitad interesante.
+//!
+//! - [`campo`] — [`CampoVectorial`], la grilla, con `muestrear` para llenarla
+//!   desde una función.
+//! - [`paint`] — el painter agnóstico contra un `Canvas`.
+
+#![forbid(unsafe_code)]
+
+pub mod campo;
+pub mod paint;
+
+pub use campo::CampoVectorial;
+pub use paint::{paint, EstiloQuiver};
diff --git a/00_unanchay/pineal/pineal-quiver/src/paint.rs b/00_unanchay/pineal/pineal-quiver/src/paint.rs
new file mode 100644
index 0000000..d2d73f8
--- /dev/null
+++ b/00_unanchay/pineal/pineal-quiver/src/paint.rs
@@ -0,0 +1,217 @@
+//! Painter agnóstico: dibuja un [`CampoVectorial`] como flechas contra un
+//! `Canvas`.
+//!
+//! Tres `stroke_line` por muestra: el astil y las dos barbas de la punta. No
+//! usa `fill_triangle_strip` para la cabeza a propósito — con trazos, el
+//! export SVG de `pineal-export` sale con líneas y no con polígonos, y una
+//! flecha de una escena vectorial se puede seguir editando afuera.
+
+use crate::campo::CampoVectorial;
+use pineal_heatmap::Ramp;
+use pineal_render::{Canvas, Color, Point, Rect, StrokeStyle};
+
+/// Cómo se dibuja el campo.
+#[derive(Debug, Clone, Copy)]
+pub struct EstiloQuiver {
+    /// Trazo del astil. Si `ramp` es `Some`, el color de acá se ignora.
+    pub trazo: StrokeStyle,
+    /// Largo de las barbas de la punta, como fracción del largo de la flecha.
+    pub cabeza: f32,
+    /// Fracción de la celda que ocupa la flecha más larga del campo. `1.0`
+    /// haría que las flechas vecinas se toquen.
+    pub ocupacion: f32,
+    /// Colorea cada flecha por su magnitud relativa. `None` = el color del
+    /// trazo para todas.
+    pub ramp: Option<Ramp>,
+    /// Si `true`, todas las flechas miden lo mismo y sólo la dirección
+    /// importa. Sirve cuando el rango de magnitudes es tan ancho que las
+    /// chicas se vuelven invisibles — y hay que decirlo en la leyenda, porque
+    /// un campo normalizado que no avisa **miente sobre la magnitud**.
+    pub normalizado: bool,
+}
+
+impl Default for EstiloQuiver {
+    fn default() -> Self {
+        Self {
+            trazo: StrokeStyle::new(1.0, Color::rgb(0.78, 0.78, 0.82)),
+            cabeza: 0.3,
+            ocupacion: 0.85,
+            ramp: None,
+            normalizado: false,
+        }
+    }
+}
+
+/// Dibuja `campo` dentro de `area`. Cada muestra es una flecha centrada en su
+/// celda; la longitud es proporcional a la magnitud, escalada para que la más
+/// grande del campo ocupe `estilo.ocupacion` de la celda.
+///
+/// Una muestra de magnitud cero, o no finita, **no dibuja nada**: una flecha de
+/// largo cero se ve como un punto y un punto se lee como un dato, cuando lo que
+/// hay es la ausencia de uno.
+pub fn paint(campo: &CampoVectorial, estilo: EstiloQuiver, area: Rect, canvas: &mut dyn Canvas) {
+    let (w, h) = (campo.ancho(), campo.alto());
+    if w == 0 || h == 0 {
+        return;
+    }
+    let maxima = campo.magnitud_maxima();
+    if maxima <= 0.0 {
+        return;
+    }
+
+    let celda_w = area.w / w as f32;
+    let celda_h = area.h / h as f32;
+    let largo_max = celda_w.min(celda_h) * estilo.ocupacion;
+
+    for y in 0..h {
+        for x in 0..w {
+            let (u, v) = campo.get(x, y);
+            if !u.is_finite() || !v.is_finite() {
+                continue;
+            }
+            let magnitud = (u * u + v * v).sqrt();
+            if magnitud <= 0.0 {
+                continue;
+            }
+            let t = magnitud / maxima;
+            let largo = if estilo.normalizado { largo_max } else { largo_max * t };
+            let (dx, dy) = (u / magnitud * largo, v / magnitud * largo);
+
+            let cx = area.x + (x as f32 + 0.5) * celda_w;
+            let cy = area.y + (y as f32 + 0.5) * celda_h;
+            // La flecha se centra en la celda: así la grilla queda legible como
+            // grilla. Anclarla en el punto de muestreo la correría media celda
+            // y el campo parecería desplazado.
+            let cola = Point::new(cx - dx * 0.5, cy - dy * 0.5);
+            let punta = Point::new(cx + dx * 0.5, cy + dy * 0.5);
+
+            let trazo = match estilo.ramp {
+                Some(r) => StrokeStyle::new(estilo.trazo.width, r.sample(t)),
+                None => estilo.trazo,
+            };
+            canvas.stroke_line(cola, punta, trazo);
+
+            // Las dos barbas, a ±150° de la dirección de avance.
+            let barba = largo * estilo.cabeza;
+            if barba > 0.0 {
+                let (ux, uy) = (dx / largo, dy / largo);
+                for signo in [1.0_f32, -1.0] {
+                    // Rotación de ±150°: cos=-√3/2, sin=±1/2.
+                    const COS: f32 = -0.866_025_4;
+                    const SIN: f32 = 0.5;
+                    let rx = ux * COS - uy * SIN * signo;
+                    let ry = ux * SIN * signo + uy * COS;
+                    canvas.stroke_line(
+                        punta,
+                        Point::new(punta.x + rx * barba, punta.y + ry * barba),
+                        trazo,
+                    );
+                }
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use pineal_render::{PlanRecorder, RenderCmd};
+
+    fn dibujar(campo: &CampoVectorial, estilo: EstiloQuiver) -> Vec<RenderCmd> {
+        let mut rec = PlanRecorder::new();
+        paint(campo, estilo, Rect::new(0.0, 0.0, 300.0, 200.0), &mut rec);
+        rec.into_plan().cmds
+    }
+
+    fn campo_1x1(u: f32, v: f32) -> CampoVectorial {
+        CampoVectorial::desde_datos(vec![u], vec![v], 1, 1).unwrap()
+    }
+
+    #[test]
+    fn tres_trazos_por_flecha() {
+        let cmds = dibujar(&campo_1x1(1.0, 0.0), EstiloQuiver::default());
+        assert_eq!(cmds.len(), 3, "astil + dos barbas");
+        assert!(cmds.iter().all(|c| matches!(c, RenderCmd::StrokeLine { .. })));
+    }
+
+    #[test]
+    fn una_muestra_nula_no_dibuja_nada() {
+        // Presencia y ausencia: el mismo campo con un vector no nulo dibuja.
+        let campo = CampoVectorial::desde_datos(vec![0.0, 2.0], vec![0.0, 0.0], 2, 1).unwrap();
+        assert_eq!(dibujar(&campo, EstiloQuiver::default()).len(), 3);
+    }
+
+    #[test]
+    fn campo_todo_cero_no_dibuja_nada() {
+        assert!(dibujar(&CampoVectorial::nuevo(4, 4), EstiloQuiver::default()).is_empty());
+    }
+
+    #[test]
+    fn un_nan_no_apaga_las_demas_flechas() {
+        // Si el máximo se contaminara con el NaN, el campo entero se apagaría.
+        let campo =
+            CampoVectorial::desde_datos(vec![f32::NAN, 1.0], vec![0.0, 0.0], 2, 1).unwrap();
+        assert_eq!(dibujar(&campo, EstiloQuiver::default()).len(), 3);
+    }
+
+    #[test]
+    fn el_largo_es_proporcional_a_la_magnitud() {
+        let campo = CampoVectorial::desde_datos(vec![1.0, 2.0], vec![0.0, 0.0], 2, 1).unwrap();
+        let cmds = dibujar(&campo, EstiloQuiver::default());
+        let largos: Vec<f32> = cmds
+            .iter()
+            .filter_map(|c| match c {
+                RenderCmd::StrokeLine { a, b, .. } => Some((b.x - a.x).abs() + (b.y - a.y).abs()),
+                _ => None,
+            })
+            .collect();
+        // Astil de la primera (índice 0) contra astil de la segunda (índice 3).
+        assert!(
+            (largos[3] - largos[0] * 2.0).abs() < 1e-3,
+            "el doble de magnitud tiene que dar el doble de astil: {largos:?}"
+        );
+    }
+
+    #[test]
+    fn normalizado_iguala_los_largos_y_conserva_la_direccion() {
+        let campo = CampoVectorial::desde_datos(vec![1.0, 8.0], vec![0.0, 0.0], 2, 1).unwrap();
+        let estilo = EstiloQuiver { normalizado: true, ..Default::default() };
+        let cmds = dibujar(&campo, estilo);
+        let astil = |i: usize| match &cmds[i] {
+            RenderCmd::StrokeLine { a, b, .. } => (b.x - a.x, b.y - a.y),
+            otro => panic!("se esperaba un trazo, salió {otro:?}"),
+        };
+        let (a, b) = (astil(0), astil(3));
+        assert!((a.0 - b.0).abs() < 1e-3 && a.0 > 0.0, "mismo largo y misma dirección");
+    }
+
+    #[test]
+    fn la_flecha_se_centra_en_su_celda() {
+        // Una celda de 300×200 con una sola muestra: el centro del astil tiene
+        // que caer en (150, 100), no en la esquina del muestreo.
+        let cmds = dibujar(&campo_1x1(1.0, 0.0), EstiloQuiver::default());
+        let RenderCmd::StrokeLine { a, b, .. } = &cmds[0] else {
+            panic!("se esperaba un trazo");
+        };
+        assert!(((a.x + b.x) * 0.5 - 150.0).abs() < 1e-3);
+        assert!(((a.y + b.y) * 0.5 - 100.0).abs() < 1e-3);
+    }
+
+    #[test]
+    fn sin_cabeza_queda_solo_el_astil() {
+        let estilo = EstiloQuiver { cabeza: 0.0, ..Default::default() };
+        assert_eq!(dibujar(&campo_1x1(1.0, 0.0), estilo).len(), 1);
+    }
+
+    #[test]
+    fn la_ramp_le_da_a_cada_flecha_el_color_de_su_magnitud() {
+        let campo = CampoVectorial::desde_datos(vec![1.0, 4.0], vec![0.0, 0.0], 2, 1).unwrap();
+        let estilo = EstiloQuiver { ramp: Some(Ramp::Viridis), ..Default::default() };
+        let cmds = dibujar(&campo, estilo);
+        let color = |i: usize| match &cmds[i] {
+            RenderCmd::StrokeLine { stroke, .. } => stroke.color,
+            otro => panic!("se esperaba un trazo, salió {otro:?}"),
+        };
+        assert_ne!(color(0), color(3), "magnitudes distintas, colores distintos");
+    }
+}
diff --git a/00_unanchay/pineal/pineal-umbrella/Cargo.toml b/00_unanchay/pineal/pineal-umbrella/Cargo.toml
index 458248b..75be5b1 100644
--- a/00_unanchay/pineal/pineal-umbrella/Cargo.toml
+++ b/00_unanchay/pineal/pineal-umbrella/Cargo.toml
@@ -26,13 +26,14 @@ pineal-export = { path = "../pineal-export", version = "0.1.0", optional = true
 pineal-hexbin = { path = "../pineal-hexbin", version = "0.1.0", optional = true }
 pineal-contour = { path = "../pineal-contour", version = "0.1.0", optional = true }
 pineal-bars = { path = "../pineal-bars", version = "0.1.0", optional = true }
+pineal-quiver = { path = "../pineal-quiver", version = "0.1.0", optional = true }
 
 [features]
 default = ["full"]
 full = [
     "core", "render", "cartesian", "stream", "mesh", "financial",
     "polar", "heatmap", "treemap", "flow", "phosphor", "export",
-    "hexbin", "contour", "bars",
+    "hexbin", "contour", "bars", "quiver",
 ]
 core = ["dep:pineal-core"]
 render = ["dep:pineal-render", "core"]
@@ -49,3 +50,4 @@ export = ["dep:pineal-export", "render"]
 hexbin = ["dep:pineal-hexbin", "heatmap"]
 contour = ["dep:pineal-contour", "heatmap"]
 bars = ["dep:pineal-bars", "render"]
+quiver = ["dep:pineal-quiver", "heatmap"]
diff --git a/00_unanchay/pineal/pineal-umbrella/src/lib.rs b/00_unanchay/pineal/pineal-umbrella/src/lib.rs
index 1fa2de5..0d94634 100644
--- a/00_unanchay/pineal/pineal-umbrella/src/lib.rs
+++ b/00_unanchay/pineal/pineal-umbrella/src/lib.rs
@@ -55,6 +55,9 @@ pub use pineal_export as export;
 #[cfg(feature = "hexbin")]
 pub use pineal_hexbin as hexbin;
 
+#[cfg(feature = "quiver")]
+pub use pineal_quiver as quiver;
+
 #[cfg(feature = "contour")]
 pub use pineal_contour as contour;
 
diff --git a/00_unanchay/pineal/pineal-yupana/Cargo.toml b/00_unanchay/pineal/pineal-yupana/Cargo.toml
new file mode 100644
index 0000000..a807284
--- /dev/null
+++ b/00_unanchay/pineal/pineal-yupana/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "pineal-yupana"
+version = { workspace = true }
+edition = { workspace = true }
+license = { workspace = true }
+authors = { workspace = true }
+repository = { workspace = true }
+publish = true
+description = "Pineal — yupana: la magnitud posicional en base 10 como el ábaco andino."
+
+[dependencies]
+pineal-render = { path = "../pineal-render", version = "0.1.0" }
diff --git a/00_unanchay/pineal/pineal-yupana/LEEME.md b/00_unanchay/pineal/pineal-yupana/LEEME.md
new file mode 100644
index 0000000..6aaf1ee
--- /dev/null
+++ b/00_unanchay/pineal/pineal-yupana/LEEME.md
@@ -0,0 +1,19 @@
+# pineal-yupana
+
+*Read this in English: [README.md](README.md).*
+
+> La magnitud posicional en base 10, como el ábaco andino.
+
+La yupana es la calculadora del mundo andino: columnas por potencia de diez,
+cuentas que se cargan de abajo hacia arriba. Acá es un painter de la familia
+pineal: dado un entero no negativo, dibuja columnas de diez posiciones y rellena
+las que el dígito manda.
+
+Se lee como **magnitud**, no como número — que es exactamente para lo que sirve un
+gráfico: ver que una cifra es un orden de magnitud mayor que la de al lado.
+
+Agnóstico de backend: emite geometría, el caller la rasteriza.
+
+---
+
+Parte de **pineal** — ver [pineal](../LEEME.md).
diff --git a/00_unanchay/pineal/pineal-yupana/README.md b/00_unanchay/pineal/pineal-yupana/README.md
new file mode 100644
index 0000000..2901ce6
--- /dev/null
+++ b/00_unanchay/pineal/pineal-yupana/README.md
@@ -0,0 +1,17 @@
+# pineal-yupana
+
+> Positional magnitude in base 10, drawn as the Andean abacus.
+
+The yupana is the calculator of the Andean world: one column per power of ten,
+counters filled from the bottom up. Here it is a painter of the pineal family:
+given a non-negative integer, it draws columns of ten positions and fills the
+ones the digit calls for.
+
+It reads as a **magnitude**, not as a number — which is the whole point of a
+chart: seeing that a figure is an order of magnitude bigger than the one beside it.
+
+Backend-agnostic: it emits geometry, the caller rasterises it.
+
+---
+
+Part of **pineal** — see [pineal](../README.md).
diff --git a/00_unanchay/pineal/pineal-yupana/src/lib.rs b/00_unanchay/pineal/pineal-yupana/src/lib.rs
new file mode 100644
index 0000000..2fddb27
--- /dev/null
+++ b/00_unanchay/pineal/pineal-yupana/src/lib.rs
@@ -0,0 +1,229 @@
+//! `pineal-yupana` — la magnitud posicional en base 10 como el ábaco andino.
+//!
+//! # Qué es
+//!
+//! La yupana es la calculadora del mundo andino: columnas por potencia de diez,
+//! cuentas que se cargan de abajo hacia arriba. Acá es un painter de la
+//! familia pineal: dado un entero no negativo, dibuja columnas de diez
+//! posiciones y rellena las que el dígito manda.
+//!
+//! # Qué NO es (decisión D2 del SDD de krama, M5)
+//!
+//! - **Base 10 fija.** La yupana histórica tiene base 40 o mixta; reproducir el
+//!   algoritmo histórico está explícitamente fuera de M5. Esta es la que enseña
+//!   el valor posicional que la escuela usa.
+//! - **Sólo enteros no negativos adimensionales.** Decimales, negativos y
+//!   valores con unidad no son una yupana; son [`crate`] de otra vista. Un
+//!   `0` sí — con las columnas vacías enseña que no hay nada, que también es
+//!   un dato.
+//! - **Sin interactividad.** No hay cuenta que se arrastre: es una vista, no un
+//!   editor. (El modo didáctico interactivo queda como mejora posterior.)
+//!
+//! La certificación es textual (Regla 8 del repo): los tests de acá cuentan
+//! cuántas cuentas activas pinta cada entrada contra un `Canvas` de captura —
+//! nadie tiene que mirar un PNG para saber si la yupana anda.
+
+#![forbid(unsafe_code)]
+
+use pineal_render::{Canvas, Color, Point, Rect, StrokeStyle};
+
+/// Cuentas por columna. Una columna tiene diez posiciones y se llena desde
+/// abajo: la posición 0 es la de las unidades de esa potencia.
+pub const POSICIONES: usize = 10;
+
+/// Cómo se dibuja la yupana.
+#[derive(Debug, Clone, Copy)]
+pub struct YupanaTheme {
+    /// La cuenta activa: la que representa una unidad de esa columna.
+    pub cuenta_activa: Color,
+    /// La posición vacía: apenas un borde, para que la columna no se lea como
+    /// un palo vacío.
+    pub cuenta_inactiva: Color,
+    /// El riel de la columna.
+    pub columna: Color,
+}
+
+impl Default for YupanaTheme {
+    fn default() -> Self {
+        Self {
+            cuenta_activa: Color::from_hex(0xE0A14C),
+            cuenta_inactiva: Color::from_hex(0x1A1A1A).with_alpha(0.14),
+            columna: Color::from_hex(0x1A1A1A).with_alpha(0.10),
+        }
+    }
+}
+
+/// Descompone `valor` en sus dígitos, de menos a más significativos.
+/// `123` → `[3, 2, 1]`. `0` → `[0]`.
+pub fn digitos(valor: i64) -> Vec<u8> {
+    let mut v = valor.abs();
+    let mut out = Vec::new();
+    loop {
+        out.push((v % 10) as u8);
+        v /= 10;
+        if v == 0 {
+            break;
+        }
+    }
+    if out.is_empty() {
+        out.push(0);
+    }
+    out
+}
+
+/// Cuántas cuentas activas tiene la yupana de `valor`: la suma de sus dígitos.
+/// `0` → `0`, `9` → `9`, `123` → `6`.
+pub fn cuentas_activas(valor: i64) -> usize {
+    digitos(valor).iter().map(|d| *d as usize).sum()
+}
+
+/// Dibuja la yupana de `valor` dentro de `area`.
+///
+/// Cada columna se reparte la altura en diez posiciones; las cuentas activas
+/// se cargan desde abajo según el dígito. `valor` negativo se trata por su
+/// valor absoluto (quien decide si un negativo llega acá lo decide antes, en
+/// la vista; el painter no tiene por qué saber).
+pub fn pintar_yupana(canvas: &mut dyn Canvas, area: Rect, valor: i64, tema: &YupanaTheme) {
+    let digitos = digitos(valor);
+    let n = digitos.len().max(1);
+    let col_w = area.w / n as f32;
+    let slot_h = area.h / POSICIONES as f32;
+    // El diámetro cabe en la posición y no pisa la columna vecina.
+    let d = (slot_h * 0.8).min(col_w * 0.62);
+    let r = (d / 2.0).max(1.0);
+
+    // Los rieles: uno por columna, y el borde derecho para que la última no
+    // quede colgando.
+    for col in 0..=n {
+        let x = area.x + col_w * col as f32;
+        canvas.stroke_line(
+            Point::new(x, area.y),
+            Point::new(x, area.y + area.h),
+            StrokeStyle::new(1.0, tema.columna),
+        );
+    }
+
+    let trazo_vacio = StrokeStyle::new(1.0, tema.cuenta_inactiva);
+    for (col, dig) in digitos.iter().enumerate() {
+        let cx = area.x + col_w * (col as f32 + 0.5);
+        for pos in 0..POSICIONES {
+            let y = area.y + area.h - (pos as f32 + 0.5) * slot_h;
+            let centro = Point::new(cx, y);
+            // Desde abajo: la posición 0 (la base) es la primera en llenarse.
+            if (pos as u8) < *dig {
+                rellenar_circulo(canvas, centro, r, tema.cuenta_activa);
+            } else {
+                contorno_circulo(canvas, centro, r * 0.9, trazo_vacio);
+            }
+        }
+    }
+}
+
+/// Un círculo relleno como abanico de triángulos (el `Canvas` no tiene
+/// `fill_circle`; hexbin pinta sus hexes así).
+fn rellenar_circulo(canvas: &mut dyn Canvas, c: Point, r: f32, color: Color) {
+    let mut coords = Vec::with_capacity(26);
+    let mut colors = Vec::with_capacity(26);
+    for i in 0..=12 {
+        let a = -core::f32::consts::FRAC_PI_2 + i as f32 * core::f32::consts::FRAC_PI_6;
+        coords.push(c.x);
+        coords.push(c.y);
+        coords.push(c.x + r * a.cos());
+        coords.push(c.y + r * a.sin());
+        colors.push(color);
+        colors.push(color);
+    }
+    canvas.fill_triangle_strip(&coords, &colors);
+}
+
+/// El contorno de un círculo, para la posición vacía.
+fn contorno_circulo(canvas: &mut dyn Canvas, c: Point, r: f32, stroke: StrokeStyle) {
+    let mut coords = Vec::with_capacity(26);
+    for i in 0..=12 {
+        let a = -core::f32::consts::FRAC_PI_2 + i as f32 * core::f32::consts::FRAC_PI_6;
+        coords.push(c.x + r * a.cos());
+        coords.push(c.y + r * a.sin());
+    }
+    canvas.stroke_polyline(&coords, stroke);
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use pineal_render::Rect;
+
+    /// Un `Canvas` que sólo cuenta: cuántos círculos rellenos se pidieron y en
+    /// qué coordenadas. Es la certificación textual del painter.
+    struct Recuento {
+        rellenos: Vec<(f32, f32)>,
+        contornos: Vec<(f32, f32)>,
+    }
+
+    impl Canvas for Recuento {
+        fn push_clip(&mut self, _: Rect) {}
+        fn pop_clip(&mut self) {}
+        fn fill_rect(&mut self, _: Rect, _: Color) {}
+        fn stroke_rect(&mut self, _: Rect, _: StrokeStyle) {}
+        fn stroke_line(&mut self, _: Point, _: Point, _: StrokeStyle) {}
+        fn stroke_polyline(&mut self, coords: &[f32], _: StrokeStyle) {
+            // Cada contorno son 26 coordenadas (centro duplicado en el relleno,
+            // acá 26 puntos del anillo); el último cierra el círculo.
+            let n = coords.len() / 2;
+            if n == 13 {
+                // 12 + cierre: el anillo. El centro de la posición vacía es el
+                // promedio de los puntos — pero el anillo no tiene centro.
+                // Usamos el primer punto como aproximación del centro.
+                self.contornos.push((coords[0], coords[1]));
+            }
+        }
+        fn fill_triangle_strip(&mut self, coords: &[f32], _: &[Color]) {
+            // Un relleno son 13 pares: centro + 12 del anillo + cierre. El
+            // centro está en (coords[0], coords[1]).
+            self.rellenos.push((coords[0], coords[1]));
+        }
+        fn draw_text(&mut self, _: Point, _: &str, _: Color, _: f32) {}
+    }
+
+    fn pintar(valor: i64) -> Recuento {
+        let mut c = Recuento { rellenos: Vec::new(), contornos: Vec::new() };
+        pintar_yupana(&mut c, Rect::new(0.0, 0.0, 100.0, 60.0), valor, &YupanaTheme::default());
+        c
+    }
+
+    #[test]
+    fn cero_no_pinta_cuentas_activas() {
+        assert_eq!(cuentas_activas(0), 0);
+        let c = pintar(0);
+        assert_eq!(c.rellenos.len(), 0, "un cero tiene la columna vacía");
+    }
+
+    #[test]
+    fn nueve_pinta_nueve_cuentas_en_una_columna() {
+        assert_eq!(cuentas_activas(9), 9);
+        let c = pintar(9);
+        assert_eq!(c.rellenos.len(), 9);
+        // Todas en la misma columna: mismo centro x.
+        let x = c.rellenos[0].0;
+        assert!(c.rellenos.iter().all(|(cx, _)| (*cx - x).abs() < 0.01));
+    }
+
+    #[test]
+    fn ciento_veintitres_distribuye_tres_dos_uno() {
+        assert_eq!(digitos(123), vec![3, 2, 1]);
+        let c = pintar(123);
+        assert_eq!(c.rellenos.len(), 6, "3 + 2 + 1");
+        // Tres columnas: los centros x de las cuentas activas son tres grupos.
+        let mut xs: Vec<f32> = c.rellenos.iter().map(|(x, _)| *x).collect();
+        xs.sort_by(|a, b| a.total_cmp(b));
+        xs.dedup();
+        assert_eq!(xs.len(), 3, "123 tiene tres columnas, no {xs:?}");
+    }
+
+    #[test]
+    fn los_digitos_del_cero_al_cien() {
+        assert_eq!(digitos(0), vec![0]);
+        assert_eq!(digitos(7), vec![7]);
+        assert_eq!(digitos(100), vec![0, 0, 1]);
+        assert_eq!(digitos(999), vec![9, 9, 9]);
+    }
+}
diff --git a/Cargo.lock b/Cargo.lock
index dbb9cf6..c7fa714 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -167,7 +167,7 @@ dependencies = [
  "ndk-context",
  "ndk-sys",
  "num_enum",
- "thiserror 2.0.19",
+ "thiserror 2.0.20",
 ]
 
 [[package]]
@@ -178,9 +178,9 @@ checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
 
 [[package]]
 name = "android_system_properties"
-version = "0.1.5"
+version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc"
 dependencies = [
  "libc",
 ]
@@ -188,7 +188,7 @@ dependencies = [
 [[package]]
 name = "app-bus"
 version = "0.1.0"
-source = "git+https://git.tawasuyu.net/tawasuyu/llimphi.git?tag=v0.2.0#93253cc227130680427a9a83bdc504bd82ca71ca"
+source = "git+https://git.tawasuyu.net/tawasuyu/llimphi.git?tag=v0.3.0#b575c63f108c83bbe67ea526cc529ace97003be5"
 dependencies = [
  "directories",
  "serde",
@@ -365,13 +365,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
 
 [[package]]
 name = "async-trait"
-version = "0.1.91"
+version = "0.1.92"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec"
+checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 3.0.3",
+ "syn 3.0.5",
 ]
 
 [[package]]
@@ -425,18 +425,18 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
 
 [[package]]
 name = "bit-set"
-version = "0.8.0"
+version = "0.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
+checksum = "34ddef2995421ab6a5c779542c81ee77c115206f4ad9d5a8e05f4ff49716a3dd"
 dependencies = [
  "bit-vec",
 ]
 
 [[package]]
 name = "bit-vec"
-version = "0.8.0"
+version = "0.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
+checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51"
 
 [[package]]
 name = "bitflags"
@@ -450,12 +450,6 @@ version = "2.13.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
 
-[[package]]
-name = "block"
-version = "0.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
-
 [[package]]
 name = "block2"
 version = "0.5.1"
@@ -465,11 +459,20 @@ dependencies = [
  "objc2 0.5.2",
 ]
 
+[[package]]
+name = "block2"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
+dependencies = [
+ "objc2 0.6.4",
+]
+
 [[package]]
 name = "blocking"
-version = "1.6.2"
+version = "1.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21"
+checksum = "a70e4329df6cb94385eed412ec92375c3cdd8a6e502493d1229b6414e4036dfa"
 dependencies = [
  "async-channel",
  "async-task",
@@ -495,13 +498,13 @@ dependencies = [
 
 [[package]]
 name = "bytemuck_derive"
-version = "1.11.0"
+version = "1.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f65693059b6b9c588b9f62fed1cedbf0a8b805631457ea162d68f0de186f3de5"
+checksum = "fc0e56a716f1e132ff6bf4bdac1c944a3fcdc1cae65f70a4a2a1ac3b401d2d1f"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.119",
+ "syn 3.0.5",
 ]
 
 [[package]]
@@ -544,9 +547,9 @@ dependencies = [
 
 [[package]]
 name = "cc"
-version = "1.4.0"
+version = "1.4.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9"
+checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80"
 dependencies = [
  "find-msvc-tools",
  "jobserver",
@@ -577,9 +580,9 @@ dependencies = [
 
 [[package]]
 name = "codespan-reporting"
-version = "0.12.0"
+version = "0.13.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81"
+checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681"
 dependencies = [
  "serde",
  "termcolor",
@@ -591,12 +594,16 @@ name = "color"
 version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2ec7c5eb7a16992b1904d76c517d170ab353b0e0b3d5a0c81a8a0cd1037893cf"
+dependencies = [
+ "bytemuck",
+ "libm",
+]
 
 [[package]]
 name = "combine"
-version = "4.6.7"
+version = "4.6.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
+checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e"
 dependencies = [
  "bytes",
  "memchr",
@@ -621,16 +628,6 @@ dependencies = [
  "libc",
 ]
 
-[[package]]
-name = "core-foundation"
-version = "0.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
-dependencies = [
- "core-foundation-sys",
- "libc",
-]
-
 [[package]]
 name = "core-foundation-sys"
 version = "0.8.7"
@@ -644,8 +641,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081"
 dependencies = [
  "bitflags 1.3.2",
- "core-foundation 0.9.4",
- "core-graphics-types 0.1.3",
+ "core-foundation",
+ "core-graphics-types",
  "foreign-types",
  "libc",
 ]
@@ -657,18 +654,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf"
 dependencies = [
  "bitflags 1.3.2",
- "core-foundation 0.9.4",
- "libc",
-]
-
-[[package]]
-name = "core-graphics-types"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
-dependencies = [
- "bitflags 2.13.1",
- "core-foundation 0.10.1",
+ "core-foundation",
  "libc",
 ]
 
@@ -683,18 +669,18 @@ dependencies = [
 
 [[package]]
 name = "crc32fast"
-version = "1.5.0"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
+checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550"
 dependencies = [
  "cfg-if",
 ]
 
 [[package]]
 name = "crossbeam-utils"
-version = "0.8.22"
+version = "0.8.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
+checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6"
 
 [[package]]
 name = "crunchy"
@@ -747,13 +733,13 @@ dependencies = [
 
 [[package]]
 name = "displaydoc"
-version = "0.2.6"
+version = "0.2.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
+checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.119",
+ "syn 3.0.5",
 ]
 
 [[package]]
@@ -831,9 +817,9 @@ dependencies = [
 
 [[package]]
 name = "error-code"
-version = "3.3.2"
+version = "3.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59"
+checksum = "0b5343afd4a8365a643ac588dab4cf234a190c7f6c88c9f6dd6ffe00837661b7"
 
 [[package]]
 name = "euclid"
@@ -846,11 +832,10 @@ dependencies = [
 
 [[package]]
 name = "event-listener"
-version = "5.4.1"
+version = "5.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab"
+checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2"
 dependencies = [
- "concurrent-queue",
  "parking",
  "pin-project-lite",
 ]
@@ -886,11 +871,17 @@ dependencies = [
  "simd-adler32",
 ]
 
+[[package]]
+name = "fearless_simd"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b97b65636e5b9ef369943878ac74335ba1c55c1cb6adbf1e2c293c624248d693"
+
 [[package]]
 name = "find-msvc-tools"
-version = "0.1.9"
+version = "0.1.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
+checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d"
 
 [[package]]
 name = "fixedbitset"
@@ -900,12 +891,13 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
 
 [[package]]
 name = "flate2"
-version = "1.1.9"
+version = "1.1.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
+checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb"
 dependencies = [
  "crc32fast",
- "miniz_oxide",
+ "miniz_oxide 0.9.1",
+ "zlib-rs",
 ]
 
 [[package]]
@@ -940,9 +932,9 @@ dependencies = [
 
 [[package]]
 name = "font-types"
-version = "0.12.2"
+version = "0.12.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a7299a780854a6d391be2ae1c8521c9368471b559dbfd6a8dbd9f407eaff100"
+checksum = "e64eb721ca85a34323425f4041adc5d82704d3782d5f8f03793bc012419dce23"
 dependencies = [
  "bytemuck",
 ]
@@ -988,7 +980,7 @@ checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 3.0.3",
+ "syn 3.0.5",
 ]
 
 [[package]]
@@ -999,9 +991,9 @@ checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
 
 [[package]]
 name = "futures-core"
-version = "0.3.33"
+version = "0.3.34"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7"
+checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
 
 [[package]]
 name = "futures-intrusive"
@@ -1016,9 +1008,9 @@ dependencies = [
 
 [[package]]
 name = "futures-io"
-version = "0.3.33"
+version = "0.3.34"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a"
+checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed"
 
 [[package]]
 name = "futures-lite"
@@ -1035,26 +1027,26 @@ dependencies = [
 
 [[package]]
 name = "futures-macro"
-version = "0.3.33"
+version = "0.3.34"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b"
+checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.119",
+ "syn 3.0.5",
 ]
 
 [[package]]
 name = "futures-task"
-version = "0.3.33"
+version = "0.3.34"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109"
+checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
 
 [[package]]
 name = "futures-util"
-version = "0.3.33"
+version = "0.3.34"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa"
+checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
 dependencies = [
  "futures-core",
  "futures-macro",
@@ -1118,11 +1110,27 @@ dependencies = [
  "xml-rs",
 ]
 
+[[package]]
+name = "glifo"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d99fc21d493812643aae86d53b7bbd02f376434a90317e8a790bc209fdd6605e"
+dependencies = [
+ "bytemuck",
+ "foldhash 0.2.0",
+ "hashbrown 0.17.1",
+ "log",
+ "peniko",
+ "skrifa 0.42.1",
+ "smallvec",
+ "vello_common",
+]
+
 [[package]]
 name = "glow"
-version = "0.16.0"
+version = "0.17.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08"
+checksum = "29038e1c483364cc6bb3cf78feee1816002e127c331a1eec55a4d202b9e1adb5"
 dependencies = [
  "js-sys",
  "slotmap",
@@ -1139,35 +1147,18 @@ dependencies = [
  "gl_generator",
 ]
 
-[[package]]
-name = "gpu-alloc"
-version = "0.6.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "45cf04b2726f02df5508c6de726acdc90cdf97ac771a9a0ffd8ba10a6e696bf9"
-dependencies = [
- "bitflags 2.13.1",
- "gpu-alloc-types",
-]
-
-[[package]]
-name = "gpu-alloc-types"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2bbed164dd10ed526c2e4fe3e721ca4a71c61730e5aafac6844b417b3227058"
-dependencies = [
- "bitflags 2.13.1",
-]
-
 [[package]]
 name = "gpu-allocator"
-version = "0.27.0"
+version = "0.28.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd"
+checksum = "51255ea7cfaadb6c5f1528d43e92a82acb2b96c43365989a28b2d44ee38f8795"
 dependencies = [
+ "ash",
+ "hashbrown 0.16.1",
  "log",
  "presser",
- "thiserror 1.0.69",
- "windows 0.58.0",
+ "thiserror 2.0.20",
+ "windows 0.62.2",
 ]
 
 [[package]]
@@ -1198,9 +1189,9 @@ checksum = "b40ca9252762c466af32d0b1002e91e4e1bc5398f77455e55474deb466355ff5"
 
 [[package]]
 name = "guillotiere"
-version = "0.6.2"
+version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b62d5865c036cb1393e23c50693df631d3f5d7bcca4c04fe4cc0fd592e74a782"
+checksum = "6b17e70c989c36bad147b27a58d148c0741c51448aa5653436547323e524d0ab"
 dependencies = [
  "euclid",
  "svg_fmt",
@@ -1248,6 +1239,8 @@ version = "0.16.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
 dependencies = [
+ "allocator-api2",
+ "equivalent",
  "foldhash 0.2.0",
 ]
 
@@ -1256,12 +1249,15 @@ name = "hashbrown"
 version = "0.17.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
+dependencies = [
+ "foldhash 0.2.0",
+]
 
 [[package]]
 name = "hermit-abi"
-version = "0.5.2"
+version = "0.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
+checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284"
 
 [[package]]
 name = "hex"
@@ -1277,9 +1273,9 @@ checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
 
 [[package]]
 name = "icu_locale_core"
-version = "2.2.0"
+version = "2.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"
+checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb"
 dependencies = [
  "displaydoc",
  "litemap",
@@ -1298,15 +1294,15 @@ dependencies = [
  "byteorder-lite",
  "moxcms",
  "num-traits",
- "png 0.18.1",
+ "png",
  "tiff",
 ]
 
 [[package]]
 name = "indexmap"
-version = "2.14.0"
+version = "2.14.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
+checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
 dependencies = [
  "equivalent",
  "hashbrown 0.17.1",
@@ -1324,7 +1320,7 @@ dependencies = [
  "jni-sys 0.4.1",
  "log",
  "simd_cesu8",
- "thiserror 2.0.19",
+ "thiserror 2.0.20",
  "walkdir",
  "windows-link",
 ]
@@ -1382,9 +1378,9 @@ dependencies = [
 
 [[package]]
 name = "js-sys"
-version = "0.3.103"
+version = "0.3.105"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102"
+checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e"
 dependencies = [
  "cfg-if",
  "futures-util",
@@ -1416,6 +1412,7 @@ checksum = "4b60dfc32f652b926df6192e55525b16d186c69d47876c3ead4da5cc9f8450e2"
 dependencies = [
  "arrayvec",
  "euclid",
+ "libm",
  "polycool",
  "smallvec",
 ]
@@ -1444,14 +1441,14 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
 
 [[package]]
 name = "libredox"
-version = "0.1.18"
+version = "0.1.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652"
+checksum = "8d8f1ea3f21fd3405dcaf6c9b5c1630af9afc422d9073ea39c5f6d6c772e08ed"
 dependencies = [
  "bitflags 2.13.1",
  "libc",
  "plain",
- "redox_syscall 0.9.0",
+ "redox_syscall 0.9.4",
 ]
 
 [[package]]
@@ -1474,9 +1471,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
 
 [[package]]
 name = "litemap"
-version = "0.8.2"
+version = "0.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
+checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae"
 
 [[package]]
 name = "litrs"
@@ -1487,21 +1484,46 @@ checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
 [[package]]
 name = "llimphi-compositor"
 version = "0.1.0"
-source = "git+https://git.tawasuyu.net/tawasuyu/llimphi.git?tag=v0.2.0#93253cc227130680427a9a83bdc504bd82ca71ca"
+source = "git+https://git.tawasuyu.net/tawasuyu/llimphi.git?tag=v0.3.0#b575c63f108c83bbe67ea526cc529ace97003be5"
 dependencies = [
+ "foldhash 0.2.0",
+ "hashbrown 0.17.1",
+ "llimphi-entrada",
  "llimphi-layout",
  "llimphi-text",
  "vello",
+ "web-time",
  "wgpu",
 ]
 
+[[package]]
+name = "llimphi-cpu"
+version = "0.1.0"
+source = "git+https://git.tawasuyu.net/tawasuyu/llimphi.git?tag=v0.3.0#b575c63f108c83bbe67ea526cc529ace97003be5"
+dependencies = [
+ "fearless_simd",
+ "vello_cpu",
+]
+
+[[package]]
+name = "llimphi-despacho"
+version = "0.1.0"
+source = "git+https://git.tawasuyu.net/tawasuyu/llimphi.git?tag=v0.3.0#b575c63f108c83bbe67ea526cc529ace97003be5"
+
+[[package]]
+name = "llimphi-entrada"
+version = "0.1.0"
+source = "git+https://git.tawasuyu.net/tawasuyu/llimphi.git?tag=v0.3.0#b575c63f108c83bbe67ea526cc529ace97003be5"
+
 [[package]]
 name = "llimphi-hal"
 version = "0.1.0"
-source = "git+https://git.tawasuyu.net/tawasuyu/llimphi.git?tag=v0.2.0#93253cc227130680427a9a83bdc504bd82ca71ca"
+source = "git+https://git.tawasuyu.net/tawasuyu/llimphi.git?tag=v0.3.0#b575c63f108c83bbe67ea526cc529ace97003be5"
 dependencies = [
+ "libc",
  "pollster",
  "raw-window-handle",
+ "web-sys",
  "wgpu",
  "winit",
 ]
@@ -1509,92 +1531,107 @@ dependencies = [
 [[package]]
 name = "llimphi-icons"
 version = "0.1.0"
-source = "git+https://git.tawasuyu.net/tawasuyu/llimphi.git?tag=v0.2.0#93253cc227130680427a9a83bdc504bd82ca71ca"
+source = "git+https://git.tawasuyu.net/tawasuyu/llimphi.git?tag=v0.3.0#b575c63f108c83bbe67ea526cc529ace97003be5"
 dependencies = [
- "llimphi-ui",
+ "llimphi-compositor",
 ]
 
 [[package]]
 name = "llimphi-layout"
 version = "0.1.0"
-source = "git+https://git.tawasuyu.net/tawasuyu/llimphi.git?tag=v0.2.0#93253cc227130680427a9a83bdc504bd82ca71ca"
+source = "git+https://git.tawasuyu.net/tawasuyu/llimphi.git?tag=v0.3.0#b575c63f108c83bbe67ea526cc529ace97003be5"
 dependencies = [
+ "hashbrown 0.17.1",
  "taffy",
 ]
 
 [[package]]
 name = "llimphi-raster"
 version = "0.1.0"
-source = "git+https://git.tawasuyu.net/tawasuyu/llimphi.git?tag=v0.2.0#93253cc227130680427a9a83bdc504bd82ca71ca"
+source = "git+https://git.tawasuyu.net/tawasuyu/llimphi.git?tag=v0.3.0#b575c63f108c83bbe67ea526cc529ace97003be5"
 dependencies = [
+ "foldhash 0.2.0",
  "llimphi-hal",
  "pollster",
  "vello",
+ "vello_encoding",
 ]
 
 [[package]]
 name = "llimphi-text"
 version = "0.1.0"
-source = "git+https://git.tawasuyu.net/tawasuyu/llimphi.git?tag=v0.2.0#93253cc227130680427a9a83bdc504bd82ca71ca"
+source = "git+https://git.tawasuyu.net/tawasuyu/llimphi.git?tag=v0.3.0#b575c63f108c83bbe67ea526cc529ace97003be5"
 dependencies = [
+ "hashbrown 0.17.1",
+ "llimphi-cpu",
+ "llimphi-wire-escena",
  "parley",
+ "peniko",
  "vello",
 ]
 
 [[package]]
 name = "llimphi-theme"
 version = "0.1.0"
-source = "git+https://git.tawasuyu.net/tawasuyu/llimphi.git?tag=v0.2.0#93253cc227130680427a9a83bdc504bd82ca71ca"
+source = "git+https://git.tawasuyu.net/tawasuyu/llimphi.git?tag=v0.3.0#b575c63f108c83bbe67ea526cc529ace97003be5"
 dependencies = [
- "llimphi-raster",
+ "peniko",
 ]
 
 [[package]]
 name = "llimphi-ui"
 version = "0.1.0"
-source = "git+https://git.tawasuyu.net/tawasuyu/llimphi.git?tag=v0.2.0#93253cc227130680427a9a83bdc504bd82ca71ca"
+source = "git+https://git.tawasuyu.net/tawasuyu/llimphi.git?tag=v0.3.0#b575c63f108c83bbe67ea526cc529ace97003be5"
 dependencies = [
  "accesskit",
  "accesskit_winit",
  "arboard",
+ "libc",
  "llimphi-compositor",
+ "llimphi-despacho",
  "llimphi-hal",
  "llimphi-layout",
  "llimphi-raster",
  "llimphi-text",
  "log",
  "pollster",
- "uuid",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "web-sys",
+ "web-time",
 ]
 
 [[package]]
 name = "llimphi-widget-button"
 version = "0.1.0"
-source = "git+https://git.tawasuyu.net/tawasuyu/llimphi.git?tag=v0.2.0#93253cc227130680427a9a83bdc504bd82ca71ca"
+source = "git+https://git.tawasuyu.net/tawasuyu/llimphi.git?tag=v0.3.0#b575c63f108c83bbe67ea526cc529ace97003be5"
 dependencies = [
+ "llimphi-compositor",
  "llimphi-theme",
- "llimphi-ui",
 ]
 
 [[package]]
 name = "llimphi-widget-context-menu"
 version = "0.1.0"
-source = "git+https://git.tawasuyu.net/tawasuyu/llimphi.git?tag=v0.2.0#93253cc227130680427a9a83bdc504bd82ca71ca"
+source = "git+https://git.tawasuyu.net/tawasuyu/llimphi.git?tag=v0.3.0#b575c63f108c83bbe67ea526cc529ace97003be5"
 dependencies = [
+ "llimphi-compositor",
  "llimphi-icons",
  "llimphi-theme",
- "llimphi-ui",
  "llimphi-widget-panel",
 ]
 
 [[package]]
 name = "llimphi-widget-menubar"
 version = "0.1.0"
-source = "git+https://git.tawasuyu.net/tawasuyu/llimphi.git?tag=v0.2.0#93253cc227130680427a9a83bdc504bd82ca71ca"
+source = "git+https://git.tawasuyu.net/tawasuyu/llimphi.git?tag=v0.3.0#b575c63f108c83bbe67ea526cc529ace97003be5"
 dependencies = [
  "app-bus",
+ "llimphi-compositor",
  "llimphi-theme",
- "llimphi-ui",
  "llimphi-widget-button",
  "llimphi-widget-context-menu",
 ]
@@ -1602,10 +1639,20 @@ dependencies = [
 [[package]]
 name = "llimphi-widget-panel"
 version = "0.1.0"
-source = "git+https://git.tawasuyu.net/tawasuyu/llimphi.git?tag=v0.2.0#93253cc227130680427a9a83bdc504bd82ca71ca"
+source = "git+https://git.tawasuyu.net/tawasuyu/llimphi.git?tag=v0.3.0#b575c63f108c83bbe67ea526cc529ace97003be5"
 dependencies = [
+ "llimphi-compositor",
  "llimphi-theme",
- "llimphi-ui",
+]
+
+[[package]]
+name = "llimphi-wire-escena"
+version = "0.1.0"
+source = "git+https://git.tawasuyu.net/tawasuyu/llimphi.git?tag=v0.3.0#b575c63f108c83bbe67ea526cc529ace97003be5"
+dependencies = [
+ "kurbo",
+ "peniko",
+ "serde",
 ]
 
 [[package]]
@@ -1619,18 +1666,9 @@ dependencies = [
 
 [[package]]
 name = "log"
-version = "0.4.33"
+version = "0.4.34"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
-
-[[package]]
-name = "malloc_buf"
-version = "0.0.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
-dependencies = [
- "libc",
-]
+checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
 
 [[package]]
 name = "memchr"
@@ -1657,25 +1695,20 @@ dependencies = [
 ]
 
 [[package]]
-name = "metal"
-version = "0.32.0"
+name = "miniz_oxide"
+version = "0.8.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "00c15a6f673ff72ddcc22394663290f870fb224c1bfce55734a75c414150e605"
+checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
 dependencies = [
- "bitflags 2.13.1",
- "block",
- "core-graphics-types 0.2.0",
- "foreign-types",
- "log",
- "objc",
- "paste",
+ "adler2",
+ "simd-adler32",
 ]
 
 [[package]]
 name = "miniz_oxide"
-version = "0.8.9"
+version = "0.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
+checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c"
 dependencies = [
  "adler2",
  "simd-adler32",
@@ -1693,9 +1726,9 @@ dependencies = [
 
 [[package]]
 name = "naga"
-version = "27.0.3"
+version = "29.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "066cf25f0e8b11ee0df221219010f213ad429855f57c494f995590c861a9a7d8"
+checksum = "b2bf919621e7975acb27d881bae2fb993e0d45c8e0446e85e6272971e00dc8df"
 dependencies = [
  "arrayvec",
  "bit-set",
@@ -1713,7 +1746,7 @@ dependencies = [
  "once_cell",
  "rustc-hash",
  "spirv",
- "thiserror 2.0.19",
+ "thiserror 2.0.20",
  "unicode-ident",
 ]
 
@@ -1788,15 +1821,6 @@ dependencies = [
  "syn 2.0.119",
 ]
 
-[[package]]
-name = "objc"
-version = "0.2.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
-dependencies = [
- "malloc_buf",
-]
-
 [[package]]
 name = "objc-sys"
 version = "0.3.5"
@@ -1829,13 +1853,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff"
 dependencies = [
  "bitflags 2.13.1",
- "block2",
+ "block2 0.5.1",
  "libc",
  "objc2 0.5.2",
  "objc2-core-data",
  "objc2-core-image",
  "objc2-foundation 0.2.2",
- "objc2-quartz-core",
+ "objc2-quartz-core 0.2.2",
 ]
 
 [[package]]
@@ -1857,7 +1881,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009"
 dependencies = [
  "bitflags 2.13.1",
- "block2",
+ "block2 0.5.1",
  "objc2 0.5.2",
  "objc2-core-location",
  "objc2-foundation 0.2.2",
@@ -1869,7 +1893,7 @@ version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889"
 dependencies = [
- "block2",
+ "block2 0.5.1",
  "objc2 0.5.2",
  "objc2-foundation 0.2.2",
 ]
@@ -1881,7 +1905,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef"
 dependencies = [
  "bitflags 2.13.1",
- "block2",
+ "block2 0.5.1",
  "objc2 0.5.2",
  "objc2-foundation 0.2.2",
 ]
@@ -1916,10 +1940,10 @@ version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80"
 dependencies = [
- "block2",
+ "block2 0.5.1",
  "objc2 0.5.2",
  "objc2-foundation 0.2.2",
- "objc2-metal",
+ "objc2-metal 0.2.2",
 ]
 
 [[package]]
@@ -1928,7 +1952,7 @@ version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781"
 dependencies = [
- "block2",
+ "block2 0.5.1",
  "objc2 0.5.2",
  "objc2-contacts",
  "objc2-foundation 0.2.2",
@@ -1957,7 +1981,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
 dependencies = [
  "bitflags 2.13.1",
- "block2",
+ "block2 0.5.1",
  "dispatch",
  "libc",
  "objc2 0.5.2",
@@ -1991,7 +2015,7 @@ version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398"
 dependencies = [
- "block2",
+ "block2 0.5.1",
  "objc2 0.5.2",
  "objc2-app-kit 0.2.2",
  "objc2-foundation 0.2.2",
@@ -2004,11 +2028,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6"
 dependencies = [
  "bitflags 2.13.1",
- "block2",
+ "block2 0.5.1",
  "objc2 0.5.2",
  "objc2-foundation 0.2.2",
 ]
 
+[[package]]
+name = "objc2-metal"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794"
+dependencies = [
+ "bitflags 2.13.1",
+ "block2 0.6.2",
+ "objc2 0.6.4",
+ "objc2-foundation 0.3.2",
+]
+
 [[package]]
 name = "objc2-quartz-core"
 version = "0.2.2"
@@ -2016,10 +2052,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a"
 dependencies = [
  "bitflags 2.13.1",
- "block2",
+ "block2 0.5.1",
  "objc2 0.5.2",
  "objc2-foundation 0.2.2",
- "objc2-metal",
+ "objc2-metal 0.2.2",
+]
+
+[[package]]
+name = "objc2-quartz-core"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f"
+dependencies = [
+ "bitflags 2.13.1",
+ "objc2 0.6.4",
+ "objc2-core-foundation",
+ "objc2-foundation 0.3.2",
+ "objc2-metal 0.3.2",
 ]
 
 [[package]]
@@ -2039,7 +2088,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f"
 dependencies = [
  "bitflags 2.13.1",
- "block2",
+ "block2 0.5.1",
  "objc2 0.5.2",
  "objc2-cloud-kit",
  "objc2-core-data",
@@ -2047,7 +2096,7 @@ dependencies = [
  "objc2-core-location",
  "objc2-foundation 0.2.2",
  "objc2-link-presentation",
- "objc2-quartz-core",
+ "objc2-quartz-core 0.2.2",
  "objc2-symbols",
  "objc2-uniform-type-identifiers",
  "objc2-user-notifications",
@@ -2059,7 +2108,7 @@ version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe"
 dependencies = [
- "block2",
+ "block2 0.5.1",
  "objc2 0.5.2",
  "objc2-foundation 0.2.2",
 ]
@@ -2071,7 +2120,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3"
 dependencies = [
  "bitflags 2.13.1",
- "block2",
+ "block2 0.5.1",
  "objc2 0.5.2",
  "objc2-core-location",
  "objc2-foundation 0.2.2",
@@ -2101,9 +2150,9 @@ dependencies = [
 
 [[package]]
 name = "ordered-float"
-version = "5.3.0"
+version = "5.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b7d950ca161dc355eaf28f82b11345ed76c6e1f6eb1f4f4479e0323b9e2fbd0e"
+checksum = "8c7c9e0d9b23589f26070720bac724174bfec1083e82f7854cdd0267518343c0"
 dependencies = [
  "num-traits",
 ]
@@ -2180,18 +2229,13 @@ dependencies = [
  "swash",
 ]
 
-[[package]]
-name = "paste"
-version = "1.0.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
-
 [[package]]
 name = "peniko"
 version = "0.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "839c8299360d2e998bdb106dc0a6cd71dcc5f4df51df1b620361bf50e283cca6"
 dependencies = [
+ "bytemuck",
  "color",
  "kurbo",
  "linebender_resource_handle",
@@ -2300,6 +2344,7 @@ dependencies = [
  "pineal-mesh",
  "pineal-phosphor",
  "pineal-polar",
+ "pineal-quiver",
  "pineal-render",
  "pineal-stream",
  "pineal-treemap",
@@ -2380,7 +2425,7 @@ version = "0.1.0"
 dependencies = [
  "pineal-core",
  "pineal-render",
- "png 0.18.1",
+ "png",
 ]
 
 [[package]]
@@ -2445,7 +2490,7 @@ dependencies = [
  "pineal-polar",
  "pineal-render",
  "pineal-treemap",
- "png 0.18.1",
+ "png",
  "pollster",
 ]
 
@@ -2567,6 +2612,14 @@ dependencies = [
  "pineal-render",
 ]
 
+[[package]]
+name = "pineal-quiver"
+version = "0.1.0"
+dependencies = [
+ "pineal-heatmap",
+ "pineal-render",
+]
+
 [[package]]
 name = "pineal-render"
 version = "0.1.0"
@@ -2617,6 +2670,13 @@ dependencies = [
  "pineal-treemap",
 ]
 
+[[package]]
+name = "pineal-yupana"
+version = "0.1.0"
+dependencies = [
+ "pineal-render",
+]
+
 [[package]]
 name = "piper"
 version = "0.2.5"
@@ -2630,9 +2690,9 @@ dependencies = [
 
 [[package]]
 name = "pkg-config"
-version = "0.3.33"
+version = "0.3.34"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
+checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
 
 [[package]]
 name = "plain"
@@ -2640,19 +2700,6 @@ version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
 
-[[package]]
-name = "png"
-version = "0.17.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
-dependencies = [
- "bitflags 1.3.2",
- "crc32fast",
- "fdeflate",
- "flate2",
- "miniz_oxide",
-]
-
 [[package]]
 name = "png"
 version = "0.18.1"
@@ -2663,7 +2710,7 @@ dependencies = [
  "crc32fast",
  "fdeflate",
  "flate2",
- "miniz_oxide",
+ "miniz_oxide 0.8.9",
 ]
 
 [[package]]
@@ -2693,19 +2740,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "50596ddc09eb5ad5f75cacd40209568e66df71baf86e1499a0e99c4cff12a5a6"
 dependencies = [
  "arrayvec",
+ "libm",
 ]
 
 [[package]]
 name = "portable-atomic"
-version = "1.14.0"
+version = "1.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3"
+checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
 
 [[package]]
 name = "portable-atomic-util"
-version = "0.2.7"
+version = "0.2.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618"
+checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715"
 dependencies = [
  "portable-atomic",
 ]
@@ -2794,6 +2842,18 @@ version = "0.6.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
 
+[[package]]
+name = "raw-window-metal"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40d213455a5f1dc59214213c7330e074ddf8114c9a42411eb890c767357ce135"
+dependencies = [
+ "objc2 0.6.4",
+ "objc2-core-foundation",
+ "objc2-foundation 0.3.2",
+ "objc2-quartz-core 0.3.2",
+]
+
 [[package]]
 name = "read-fonts"
 version = "0.35.0"
@@ -2807,9 +2867,9 @@ dependencies = [
 
 [[package]]
 name = "read-fonts"
-version = "0.37.0"
+version = "0.39.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b634fabf032fab15307ffd272149b622260f55974d9fad689292a5d33df02e5"
+checksum = "c4ed38b89c2c77ff968c524145ad65fb010f38af5c7a224b53b81d47ac2daa81"
 dependencies = [
  "bytemuck",
  "font-types 0.11.3",
@@ -2822,7 +2882,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "046a7d674daf459825b32f5062056d6882db0d2f5a479fbd76ccfc870ac18709"
 dependencies = [
  "bytemuck",
- "font-types 0.12.2",
+ "font-types 0.12.4",
  "once_cell",
 ]
 
@@ -2846,9 +2906,9 @@ dependencies = [
 
 [[package]]
 name = "redox_syscall"
-version = "0.9.0"
+version = "0.9.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c5102a6aaa05aa011a238e178e6bca86d2cb56fc9f586d37cb80f5bca6e07759"
+checksum = "737970939a87c6fa31e7acad13307bccbb017a073b695b6089a2c484f929e20e"
 dependencies = [
  "bitflags 2.13.1",
 ]
@@ -2990,7 +3050,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 3.0.3",
+ "syn 3.0.5",
 ]
 
 [[package]]
@@ -3001,7 +3061,7 @@ checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 3.0.3",
+ "syn 3.0.5",
 ]
 
 [[package]]
@@ -3069,12 +3129,12 @@ dependencies = [
 
 [[package]]
 name = "skrifa"
-version = "0.40.0"
+version = "0.42.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7fbdfe3d2475fbd7ddd1f3e5cf8288a30eb3e5f95832829570cd88115a7434ac"
+checksum = "0c34617370ae968efb7161bb2beb517d9084659aae19e24b89e3db25b46e4564"
 dependencies = [
  "bytemuck",
- "read-fonts 0.37.0",
+ "read-fonts 0.39.2",
 ]
 
 [[package]]
@@ -3104,9 +3164,9 @@ dependencies = [
 
 [[package]]
 name = "smallvec"
-version = "1.15.2"
+version = "1.16.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
+checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f"
 
 [[package]]
 name = "smithay-client-toolkit"
@@ -3144,9 +3204,9 @@ dependencies = [
 
 [[package]]
 name = "spirv"
-version = "0.3.0+sdk-1.3.268.0"
+version = "0.4.0+sdk-1.4.341.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844"
+checksum = "d9571ea910ebd84c86af4b3ed27f9dbdc6ad06f17c5f96146b2b671e2976744f"
 dependencies = [
  "bitflags 2.13.1",
 ]
@@ -3193,9 +3253,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "3.0.3"
+version = "3.0.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
+checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -3204,9 +3264,9 @@ dependencies = [
 
 [[package]]
 name = "taffy"
-version = "0.9.2"
+version = "0.12.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41ba83ebaf2954d31d05d67340fd46cebe99da2b7133b0dd68d70c65473a437b"
+checksum = "340a09581f29809fc0df82a3955501dc7f2a21f887e5d1c13dbe288fe1c0bef4"
 dependencies = [
  "arrayvec",
  "grid",
@@ -3247,11 +3307,11 @@ dependencies = [
 
 [[package]]
 name = "thiserror"
-version = "2.0.19"
+version = "2.0.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9"
+checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
 dependencies = [
- "thiserror-impl 2.0.19",
+ "thiserror-impl 2.0.20",
 ]
 
 [[package]]
@@ -3267,13 +3327,13 @@ dependencies = [
 
 [[package]]
 name = "thiserror-impl"
-version = "2.0.19"
+version = "2.0.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd"
+checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 3.0.3",
+ "syn 3.0.5",
 ]
 
 [[package]]
@@ -3317,9 +3377,9 @@ dependencies = [
 
 [[package]]
 name = "tinystr"
-version = "0.8.3"
+version = "0.8.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
+checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643"
 dependencies = [
  "displaydoc",
  "serde_core",
@@ -3384,9 +3444,9 @@ dependencies = [
 
 [[package]]
 name = "toml_parser"
-version = "1.1.2+spec-1.1.0"
+version = "1.1.3+spec-1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
+checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56"
 dependencies = [
  "winnow 1.0.4",
 ]
@@ -3476,11 +3536,10 @@ checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
 
 [[package]]
 name = "uuid"
-version = "1.24.0"
+version = "1.26.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239"
+checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812"
 dependencies = [
- "getrandom 0.4.3",
  "js-sys",
  "serde_core",
  "wasm-bindgen",
@@ -3488,46 +3547,74 @@ dependencies = [
 
 [[package]]
 name = "vello"
-version = "0.7.0"
+version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72fef40773530322d5c2ffe3c1107e9874bd8239ac137d1c2b6c1edad695146e"
+checksum = "261359dbef879f8110ef7e1c442246c838d33d3d91cb05e0ea9288d432760c9f"
 dependencies = [
  "bytemuck",
  "futures-intrusive",
  "log",
  "peniko",
- "png 0.17.16",
- "skrifa 0.40.0",
+ "png",
+ "skrifa 0.42.1",
  "static_assertions",
- "thiserror 2.0.19",
+ "thiserror 2.0.20",
  "vello_encoding",
  "vello_shaders",
  "wgpu",
 ]
 
+[[package]]
+name = "vello_common"
+version = "0.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19d672facaa2d697285a786cd9d44d614cd2ce54cdc022504bf339f8fff3b750"
+dependencies = [
+ "bytemuck",
+ "fearless_simd",
+ "guillotiere",
+ "hashbrown 0.17.1",
+ "log",
+ "peniko",
+ "smallvec",
+ "thiserror 2.0.20",
+]
+
+[[package]]
+name = "vello_cpu"
+version = "0.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "588691169aed86b5c8fb487266afee01323234e6fd0a3f2aaec0eaa8e4007f23"
+dependencies = [
+ "bytemuck",
+ "glifo",
+ "hashbrown 0.17.1",
+ "vello_common",
+]
+
 [[package]]
 name = "vello_encoding"
-version = "0.7.0"
+version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24c91203ec4b483440614a9a5c7c2d991932af72c5349659a63ec49476f0b79c"
+checksum = "2346f5f0d7dccb3582fcd397b4a57b43165209f1424e0d76e85dd814db164af7"
 dependencies = [
  "bytemuck",
  "guillotiere",
  "peniko",
- "skrifa 0.40.0",
+ "skrifa 0.42.1",
  "smallvec",
 ]
 
 [[package]]
 name = "vello_shaders"
-version = "0.7.0"
+version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a765d44d4bd354146e44f9a860f4e92effd91a97302549be9e47f0a18d8128c"
+checksum = "9dd38937516fa4b47423d9255bb5e4a65e839ec9d57c38c4af6189ce56bf46b7"
 dependencies = [
  "bytemuck",
  "log",
  "naga",
- "thiserror 2.0.19",
+ "thiserror 2.0.20",
  "vello_encoding",
 ]
 
@@ -3564,9 +3651,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.126"
+version = "0.2.128"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4"
+checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf"
 dependencies = [
  "cfg-if",
  "once_cell",
@@ -3577,9 +3664,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-futures"
-version = "0.4.76"
+version = "0.4.78"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d"
+checksum = "6ef4c5d3d2cdf5c54f4231181768f5510842e350db025faf1f7163b1030ed928"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
@@ -3587,9 +3674,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.126"
+version = "0.2.128"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1"
+checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -3597,31 +3684,31 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.126"
+version = "0.2.128"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e"
+checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a"
 dependencies = [
  "bumpalo",
  "proc-macro2",
  "quote",
- "syn 2.0.119",
+ "syn 3.0.5",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.126"
+version = "0.2.128"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24"
+checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e"
 dependencies = [
  "unicode-ident",
 ]
 
 [[package]]
 name = "wayland-backend"
-version = "0.3.16"
+version = "0.3.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "016ccf01d1c58b6f8999612813e17c9b2390f7d70671428869913310f83f54b8"
+checksum = "38a91b4eaddff87b1cd1074985e3713da4af2c49742d1b356b2c01670a67a078"
 dependencies = [
  "cc",
  "downcast-rs",
@@ -3728,9 +3815,9 @@ dependencies = [
 
 [[package]]
 name = "web-sys"
-version = "0.3.103"
+version = "0.3.105"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141"
+checksum = "9fbddc4a036f00ec4f18c83445bd3115cb306a91da554919a099d9222fe4a7f8"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
@@ -3754,12 +3841,13 @@ checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
 
 [[package]]
 name = "wgpu"
-version = "27.0.1"
+version = "29.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bfe68bac7cde125de7a731c3400723cadaaf1703795ad3f4805f187459cd7a77"
+checksum = "76e8840e1ba2881d4cbb18d2147627a56af426ff064c0401eb0c8410c6325d07"
 dependencies = [
  "arrayvec",
  "bitflags 2.13.1",
+ "bytemuck",
  "cfg-if",
  "cfg_aliases",
  "document-features",
@@ -3783,9 +3871,9 @@ dependencies = [
 
 [[package]]
 name = "wgpu-core"
-version = "27.0.3"
+version = "29.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "27a75de515543b1897b26119f93731b385a19aea165a1ec5f0e3acecc229cae7"
+checksum = "2f519832254e56965a9940c4af57dcb75f702b6f6fa4a0b172f685395843a4d7"
 dependencies = [
  "arrayvec",
  "bit-set",
@@ -3805,60 +3893,69 @@ dependencies = [
  "raw-window-handle",
  "rustc-hash",
  "smallvec",
- "thiserror 2.0.19",
+ "thiserror 2.0.20",
  "wgpu-core-deps-apple",
  "wgpu-core-deps-emscripten",
+ "wgpu-core-deps-wasm",
  "wgpu-core-deps-windows-linux-android",
  "wgpu-hal",
+ "wgpu-naga-bridge",
  "wgpu-types",
 ]
 
 [[package]]
 name = "wgpu-core-deps-apple"
-version = "27.0.0"
+version = "29.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0772ae958e9be0c729561d5e3fd9a19679bcdfb945b8b1a1969d9bfe8056d233"
+checksum = "f5e39e26c4c0e07589e67d18546cf79ff45383659fc72fca4dd293358a0347f3"
 dependencies = [
  "wgpu-hal",
 ]
 
 [[package]]
 name = "wgpu-core-deps-emscripten"
-version = "27.0.0"
+version = "29.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b06ac3444a95b0813ecfd81ddb2774b66220b264b3e2031152a4a29fda4da6b5"
+checksum = "01e09be551dc939498bdd5f6b2c66e55ab275dad25825267a08605a80fc9f0af"
+dependencies = [
+ "wgpu-hal",
+]
+
+[[package]]
+name = "wgpu-core-deps-wasm"
+version = "29.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af1fb1798be2a912497d4c224f72d39bb0cb34af50e8bcc29865bc339c943059"
 dependencies = [
  "wgpu-hal",
 ]
 
 [[package]]
 name = "wgpu-core-deps-windows-linux-android"
-version = "27.0.0"
+version = "29.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "71197027d61a71748e4120f05a9242b2ad142e3c01f8c1b47707945a879a03c3"
+checksum = "4e592c1bbef6ad047647ae6e666ebd8cee7a32bb4544d9700ec96cbf73230257"
 dependencies = [
  "wgpu-hal",
 ]
 
 [[package]]
 name = "wgpu-hal"
-version = "27.0.4"
+version = "29.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b21cb61c57ee198bc4aff71aeadff4cbb80b927beb912506af9c780d64313ce"
+checksum = "97ace1c17727311c22a46e4e3faf56ea6de81af99dcc839bdfb54857b94d448d"
 dependencies = [
  "android_system_properties",
  "arrayvec",
  "ash",
  "bit-set",
  "bitflags 2.13.1",
- "block",
+ "block2 0.6.2",
  "bytemuck",
  "cfg-if",
  "cfg_aliases",
- "core-graphics-types 0.2.0",
  "glow",
  "glutin_wgl_sys",
- "gpu-alloc",
  "gpu-allocator",
  "gpu-descriptor",
  "hashbrown 0.16.1",
@@ -3867,10 +3964,13 @@ dependencies = [
  "libc",
  "libloading",
  "log",
- "metal",
  "naga",
  "ndk-sys",
- "objc",
+ "objc2 0.6.4",
+ "objc2-core-foundation",
+ "objc2-foundation 0.3.2",
+ "objc2-metal 0.3.2",
+ "objc2-quartz-core 0.3.2",
  "once_cell",
  "ordered-float",
  "parking_lot",
@@ -3879,27 +3979,41 @@ dependencies = [
  "profiling",
  "range-alloc",
  "raw-window-handle",
+ "raw-window-metal",
  "renderdoc-sys",
  "smallvec",
- "thiserror 2.0.19",
+ "thiserror 2.0.20",
  "wasm-bindgen",
+ "wayland-sys",
  "web-sys",
+ "wgpu-naga-bridge",
+ "wgpu-types",
+ "windows 0.62.2",
+ "windows-core 0.62.2",
+ "windows-result 0.4.1",
+]
+
+[[package]]
+name = "wgpu-naga-bridge"
+version = "29.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95226013f547544b223281cd16a4fb549aa9dcb562adbda0faae4c73ffbbc161"
+dependencies = [
+ "naga",
  "wgpu-types",
- "windows 0.58.0",
- "windows-core 0.58.0",
 ]
 
 [[package]]
 name = "wgpu-types"
-version = "27.0.1"
+version = "29.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "afdcf84c395990db737f2dd91628706cb31e86d72e53482320d368e52b5da5eb"
+checksum = "84bf84cd9ca8ca45e2b223a3868f1adf9bfc0c66aeac212e76ee7e40fdadf8f5"
 dependencies = [
  "bitflags 2.13.1",
  "bytemuck",
  "js-sys",
  "log",
- "thiserror 2.0.19",
+ "raw-window-handle",
  "web-sys",
 ]
 
@@ -4327,12 +4441,12 @@ dependencies = [
  "android-activity",
  "atomic-waker",
  "bitflags 2.13.1",
- "block2",
+ "block2 0.5.1",
  "bytemuck",
  "calloop",
  "cfg_aliases",
  "concurrent-queue",
- "core-foundation 0.9.4",
+ "core-foundation",
  "core-graphics",
  "cursor-icon",
  "dpi",
@@ -4403,7 +4517,7 @@ dependencies = [
  "log",
  "os_pipe",
  "rustix 1.1.4",
- "thiserror 2.0.19",
+ "thiserror 2.0.20",
  "tree_magic_mini",
  "wayland-backend",
  "wayland-client",
@@ -4413,9 +4527,9 @@ dependencies = [
 
 [[package]]
 name = "writeable"
-version = "0.6.3"
+version = "0.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
+checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
 
 [[package]]
 name = "x11-dl"
@@ -4451,9 +4565,9 @@ checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
 
 [[package]]
 name = "xcursor"
-version = "0.3.10"
+version = "0.3.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b"
+checksum = "163b33ed8786455e2fa5d72f554057ce3f3182425434f756cd39c99839d88e23"
 
 [[package]]
 name = "xkbcommon-dl"
@@ -4476,9 +4590,9 @@ checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
 
 [[package]]
 name = "xml-rs"
-version = "0.8.28"
+version = "0.8.29"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f"
+checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7"
 
 [[package]]
 name = "yazi"
@@ -4499,9 +4613,9 @@ dependencies = [
 
 [[package]]
 name = "zbus"
-version = "5.18.0"
+version = "5.19.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe18fb60dc696039e738717b76eaea21e7a4489bbb1885020b43c94236d7e98a"
+checksum = "5db4be7c075cb421e4b7ee645541604239bd243ba7c357511f4ff3a74b555907"
 dependencies = [
  "async-broadcast",
  "async-executor",
@@ -4558,14 +4672,14 @@ dependencies = [
 
 [[package]]
 name = "zbus_macros"
-version = "5.18.0"
+version = "5.19.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe96480bed92df2b442a1a30df364e12d08eed03aeb061f2b8dc6afb2be91119"
+checksum = "2990635d09ade6df1868f72f8cac69a876a90981e8bd3c40b1be413f8dc88f40"
 dependencies = [
  "proc-macro-crate",
  "proc-macro2",
  "quote",
- "syn 2.0.119",
+ "syn 3.0.5",
  "zbus_names",
  "zvariant",
  "zvariant_utils",
@@ -4594,6 +4708,15 @@ dependencies = [
  "zvariant",
 ]
 
+[[package]]
+name = "zcheapstr"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1afec51604565183aeb5c54c20aeab286120d4e4460f7f76e3e8bb8c0d99473"
+dependencies = [
+ "serde",
+]
+
 [[package]]
 name = "zeno"
 version = "0.3.3"
@@ -4602,18 +4725,18 @@ checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524"
 
 [[package]]
 name = "zerocopy"
-version = "0.8.55"
+version = "0.8.56"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb"
+checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb"
 dependencies = [
  "zerocopy-derive",
 ]
 
 [[package]]
 name = "zerocopy-derive"
-version = "0.8.55"
+version = "0.8.56"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb"
+checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -4628,19 +4751,25 @@ checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
 
 [[package]]
 name = "zerovec"
-version = "0.11.6"
+version = "0.11.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
+checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8"
 dependencies = [
  "serde",
  "zerofrom",
 ]
 
+[[package]]
+name = "zlib-rs"
+version = "0.6.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12"
+
 [[package]]
 name = "zune-core"
-version = "0.5.1"
+version = "0.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9"
+checksum = "d56377fd46368984a170bc5aac5567e52ca5da874caa60bea39fcbca78fb658b"
 
 [[package]]
 name = "zune-jpeg"
@@ -4653,40 +4782,41 @@ dependencies = [
 
 [[package]]
 name = "zvariant"
-version = "5.13.1"
+version = "5.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bee2a0bcd2a907786a456fff45aaaaf54c9ba5f50b71ae9ec1a4edd200c94911"
+checksum = "c1d34c27cc6cdd1f458427519dd6b8612f7b7e3f7b9a0b2355d041dda9869147"
 dependencies = [
  "endi",
  "enumflags2",
  "serde",
  "winnow 1.0.4",
+ "zcheapstr",
  "zvariant_derive",
  "zvariant_utils",
 ]
 
 [[package]]
 name = "zvariant_derive"
-version = "5.13.1"
+version = "5.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38a708216a18780796770bfe3f4739c7c83a3e8f789b755534bbbc06e4e23e12"
+checksum = "864155e69b4352db0c7f374917bf45d1e0c8d17659c8b3dbf9795f3673f8c497"
 dependencies = [
  "proc-macro-crate",
  "proc-macro2",
  "quote",
- "syn 2.0.119",
+ "syn 3.0.5",
  "zvariant_utils",
 ]
 
 [[package]]
 name = "zvariant_utils"
-version = "3.5.0"
+version = "4.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90cb9383f9b45290407a1258b202d3f8f01db719eb60b4e4055c6375af4fc7c7"
+checksum = "bad0294361a320b694a328460dc73add56c306150f5cb6bfafc44446120008a3"
 dependencies = [
  "proc-macro2",
  "quote",
  "serde",
- "syn 2.0.119",
+ "syn 3.0.5",
  "winnow 1.0.4",
 ]
diff --git a/Cargo.toml b/Cargo.toml
index 50efd74..d1dfaab 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 # Cargo.toml raíz STANDALONE de pineal — front-door público.
 # GENERADO por scripts/actualizar-standalone.py desde el monorepo tawasuyu.git
-# (v0.2.0). No editar a mano: se regenera. Lo fundacional se consume por
+# (v0.3.0). No editar a mano: se regenera. Lo fundacional se consume por
 # git-dep al tag; sólo el código del dominio vive en este repo.
 [workspace]
 resolver = "2"
@@ -35,53 +35,100 @@ members = [
     "00_unanchay/pineal/pineal-mesh",
     "00_unanchay/pineal/pineal-phosphor",
     "00_unanchay/pineal/pineal-polar",
+    "00_unanchay/pineal/pineal-quiver",
     "00_unanchay/pineal/pineal-render",
     "00_unanchay/pineal/pineal-stream",
     "00_unanchay/pineal/pineal-treemap",
     "00_unanchay/pineal/pineal-umbrella",
+    "00_unanchay/pineal/pineal-yupana",
 ]
 
 # `wawa/` se excluye del workspace global porque corre en target
 # `x86_64-unknown-none` y `panic = "abort"`, incompatibles con los
 # perfiles globales. Los crates compartidos se referencian por `path`
 # cruzando la frontera.
+# `llimphi-wawa` está EXCLUIDA, no ausente por olvido. Enciende
+# `llimphi-text/cpu`, que es el backend EXCLUYENTE del `vello` que todo el resto
+# del workspace usa por default — y la unificación de features de cargo los
+# junta, disparando el `compile_error!` que llimphi-text tiene justo para
+# delatarlo. Vive en el mismo mundo que el kernel: se compila aparte, con su
+# propio lock. Sus tests se corren así:
+#   cargo test --manifest-path 02_ruway/llimphi/llimphi-wawa/Cargo.toml \
+#     --no-default-features --features std
+#
+# `llimphi-jaula` está excluida por lo MISMO, del otro lado: enciende
+# `llimphi-text/wire`, el tercer backend excluyente (W4). Es la cáscara de una
+# app dentro de la jaula WASM — el gemelo de `llimphi-wawa`—, y que las dos estén
+# afuera del workspace no es una molestia sino el diseño: son dos procesos en dos
+# lados de una frontera, y forzarlos a compartir un lock sería fingir lo
+# contrario. Sus tests:
+#   cargo test --manifest-path 02_ruway/llimphi/llimphi-jaula/Cargo.toml \
+#     --no-default-features --features std
+#
+# `llimphi-hybrid-android` está excluida **por contagio**: consume `llimphi-wawa`
+# y por lo tanto arrastra el mismo `llimphi-text/cpu`. Es la tercera cáscara de
+# la familia —kernel, jaula, teléfono— y cae del mismo lado de la frontera por
+# el mismo motivo. Se compila así:
+#   cargo check --manifest-path \
+#     02_ruway/llimphi/android/llimphi-hybrid-android/Cargo.toml
+# y se empaca con `02_ruway/llimphi/android/scripts/build-android.sh`.
 
 [workspace.package]
 version = "0.1.0"
 edition = "2021"
 rust-version = "1.80"
 license = "MIT"
-authors = ["Sergio <gerencia@jlsoltech.com>"]
+authors = ["Sergio <sergio@gioser.net>"]
 publish = false
 repository = "https://git.tawasuyu.net/tawasuyu/pineal"
 
 [workspace.dependencies]
 # === Configuración declarativa (vocabulario de esquemas) ===
-allichay = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-marca = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
+allichay = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+allichay-plano = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+marca = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+# Escala única de `nice` de la sesión (compositor · marco · app · segundo plano
+# · lote) + las tres formas de aplicarla (propia / pid vivo / pre_exec).
+prioridad = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+ron-ralo = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
 # Telemetría local de desarrollo (stderr+panics+tracing → $XDG_STATE_HOME/tawasuyu/<dominio>/<bin>.log)
-bitacora = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
+bitacora = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
 # default = [] (núcleo liviano sin vello); `bake` es opt-in. Declaramos
 # default-features = false aquí para que el `default-features = false` de los
 # consumidores (splash/greeter/compositor) se honre y no quede el warning.
-mirada-fondo = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-mirada-teclado-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-mirada-teclado-widget = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
+mirada-fondo = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+mirada-teclado-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+mirada-teclado-widget = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
 # === Registro de apps / menú global ===
-app-bus = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-app-iconset = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
+app-bus = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+app-iconset = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
 # === Centro de eventos (notificaciones + capturas + clipboard + …) ===
-willay-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-willay-checkpoint = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-willay-hilo = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-willay-store = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-willay-emit = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-rag-motor = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-churay-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-churay-welcome = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-churay-welcome-runner = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
+willay-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+willay-checkpoint = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+willay-hilo = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+willay-store = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+socket-local = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+willay-emit = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+sandokan-seguridad-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+sandokan-salud-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+sandokan-monitor-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+# El censo de red de la máquina (netstat completo: socket → proceso → unidad) +
+# huella de red + drift de puertos + contabilidad por unidad. Ver `shared/PLAN-RED.md`.
+sandokan-red-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+# La baliza de LAN: el descubridor por multicast, con el payload opaco. Promovido
+# de `uya-app::lan`, que es el de los tres del arbol que resuelve los tres modos
+# de fallo reales. Ver `shared/PLAN-RED.md` §F1.
+baliza = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+matilda-apply = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+matilda-plan = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+matilda-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+matilda-discover = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+rag-motor = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+churay-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+churay-welcome = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+churay-welcome-runner = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
 # === Puente de drag-and-drop compositor → app (suple winit en Wayland) ===
-drop-bridge = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
+drop-bridge = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
 # === Serialización ===
 serde = { version = "1", features = ["derive"] }
 serde_json = "1"
@@ -107,6 +154,10 @@ futures = "0.3"
 tracing = "0.1"
 tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
 
+# Sonda de librerías del sistema en build.rs. La usa `auth-huellita` para
+# detectar `libfprint` sin volverla una dependencia dura del workspace.
+pkg-config = "0.3"
+
 # === Linux primitives (arje) ===
 nix = { version = "0.29", features = ["signal", "process", "sched", "mount", "fs", "socket", "net", "user", "reboot"] }
 libc = "0.2"
@@ -122,9 +173,16 @@ sha2 = "0.10"
 blake3 = "1.5"
 ed25519-dalek = "2"
 aes-gcm = "0.10"
+chacha20 = "0.9"
 chacha20poly1305 = "0.10"
 argon2 = "0.5"
 rand = "0.8"
+# TOTP (RFC 6238) del segundo factor de nakui: HMAC-SHA1 es lo que hablan
+# Google Authenticator, Aegis y los tokens de hardware — no es elección
+# nuestra, es el formato del ecosistema. base32 para el secreto del QR.
+hmac = "0.12"
+sha1 = "0.10"
+data-encoding = "2"
 
 # === WASM (arje) ===
 # wasmi 1.0: unifica la versión con wawa (su kernel ya corre 1.0), para
@@ -157,6 +215,10 @@ unrar = "0.5"
 
 # === HTTP server (iniy-server) ===
 axum = "0.7"
+# WebSocket pelado (sin framework HTTP): lo usa `shuma-ide-host`, que sirve el
+# protocolo de integración de IDE de Claude Code en loopback. Ya estaba en el
+# lock por transitividad; acá se declara para poder usarlo directo.
+tokio-tungstenite = "0.24"
 tower = "0.5"
 
 # === ANN sobre embeddings (iniy nli --ann) ===
@@ -184,12 +246,30 @@ midly = "0.5"
 # zwlr_data_control_manager_v1 (que mirada expone) y cae a X11 si no hay Wayland.
 arboard = { version = "3", features = ["wayland-data-control"] }
 ropey = "1.6"
-tree-sitter = "0.24"
-tree-sitter-rust = "0.23"
+# El runtime lee gramáticas por ABI, y ahí está el acople: 0.24 lee hasta la 14,
+# 0.25 lee 13–15. Se subió el 2026-09-06 porque `tree-sitter-rust` 0.23.3 (ABI 14)
+# lee `&raw` como el arranque de una referencia cruda, así que **una variable
+# llamada `raw` prestada con `&`** rompe el archivo entero para la gramática:
+# 69 de 6.428 archivos de este repo (1,1 %). La 0.24.2 lo arregla, pero es ABI 15
+# y por eso arrastra al runtime. Las otras gramáticas siguen en ABI 14 y cargan
+# igual — 0.25 es compatible hacia atrás.
+tree-sitter = "0.25"
+tree-sitter-rust = "0.24"
 tree-sitter-python = "0.23"
 tree-sitter-typescript = "0.23"
 tree-sitter-javascript = "0.23"
 tree-sitter-go = "0.23"
+# C: el quinto dialecto que minga versiona por AST (el kernel y todo lo ajeno
+# que la suite absorbe está en C).
+tree-sitter-c = "0.23"
+# Dart entra por zorge (SDD-SERVIDOR-AJENO §A.1, RA-2): el otro proyecto es
+# Flutter, y Dart 3 es lo más parecido a Rust que hay del otro lado —tipado
+# estático, `sealed` con `switch` exhaustivo, y `_nombre` privado de verdad—.
+# El fork `orchard` y no `tree-sitter-dart` a secas. El motivo original era el
+# ABI: `tree-sitter-dart` se generó con la 15 y el runtime del workspace leía
+# hasta la 14. Con el runtime en 0.25 ese motivo **ya no aplica**; el fork se
+# conserva porque es lo que está probado, no porque haga falta.
+tree-sitter-dart-orchard = "0.3"
 
 # === FS notify ===
 notify = "6.1"
@@ -235,164 +315,226 @@ tempfile = "3"
 
 # === Llimphi (motor gráfico soberano) ===
 # wgpu sobre Vulkan/Metal/DX12, winit para ventana en dev Linux.
-# raw-window-handle 0.6 alinea winit 0.30 con wgpu 27.
-# vello 0.7 = rasterizador vectorial sobre wgpu 27 (renderer GPU "wgpu" + opt-in "hybrid" CPU+GPU).
-# taffy 0.9 = motor Flexbox/Grid puro Rust (ya pulled por transitivos, lo alineamos).
+# raw-window-handle 0.6 alinea winit 0.30 con wgpu 29.
+# vello 0.9 = rasterizador vectorial sobre wgpu 29 (renderer GPU "wgpu" + opt-in "hybrid" CPU+GPU).
+#
+# **El 27→29 no fue por gusto** (2026-07-28): `vello_hybrid 0.0.9` —el ÚNICO
+# rasterizador que el driver Mali del teléfono digiere, ver
+# `02_ruway/llimphi/android/DIAGNOSTICO.md`— vive en wgpu 29, y dos versiones de
+# wgpu no comparten un `Device`. La migración salió barata porque `vello 0.9`
+# usa el mismo peniko 0.6.1 y no movió su API de `Scene`: el bump de vello dio
+# CERO errores; todos los que hubo fueron deriva de wgpu, y concentrada en los
+# cuatro crates que tienen código wgpu propio.
+# taffy 0.12 = motor Flexbox/Grid puro Rust (0.9→0.12 en 2026-07-27: arregla el percent de ítem de grid en track sizing).
 # parley 0.6 = shaping/layout de texto compatible con peniko 0.6 (que vello 0.7 expone).
-wgpu = "27"
+wgpu = "29"
+# El compilador de shaders que wgpu lleva adentro. Se declara acá para que los
+# tests que validan WGSL headless (llimphi-3d, supay-render-llimphi) usen la
+# MISMA versión que después compila esos shaders en la GPU. Con dos versiones,
+# un test puede dar verde sobre un shader que el runtime rechaza — o al revés.
+naga = "29"
 winit = "0.30"
 raw-window-handle = "0.6"
 pollster = "0.4"
-vello = "0.7"
+# `default-features = false` acá arriba y no en cada consumidor: el default de
+# vello es ["wgpu","wgpu_default"], y `llimphi-text`/`llimphi-compositor` sólo
+# quieren sus TIPOS (Scene, peniko), no el motor de GPU. Declararlo abajo no
+# alcanza — cargo IGNORA `default-features` en un dep heredado si la raíz no lo
+# fijó, y lo avisa con un warning fácil de pasar por alto. Quien necesita el
+# motor lo pide por feature (ver llimphi-raster).
+vello = { version = "0.9", default-features = false }
 # Renderer hybrid CPU+GPU sin compute shaders (mejor compat WebGL2/Adreno viejas).
 # Opt-in; el renderer "wgpu" sigue siendo el default.
 vello_hybrid = "0.0.9"
 # foreign-lottie = fork vendorizado de velato 0.9 (Lottie → vello::Scene),
 # completado para no paniquear ante features no soportadas. Lo consume
 # llimphi-lottie. Ver shared/foreign-lottie.
-foreign-lottie = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
+foreign-lottie = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
 # Árbol de accesibilidad para lectores de pantalla (NVDA/VoiceOver/Orca/TalkBack).
 # Lo consume el runtime de Llimphi (iter 2/3 del plan AccessKit); el modelo
 # `SemanticsSpec` del compositor es independiente de estas crates.
 accesskit = "0.24"
 accesskit_winit = "0.33"
-taffy = "0.9"
+taffy = "0.12"
 # parley = shaping completo (bidi, ligatures, fallback CJK/emoji vía fontique, line break).
 parley = "0.6"
 # Bucle Elm (input→update→view→layout→raster→present). Lo consumen las apps.
-llimphi-ui = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+# `config-wawa`: el sentido de la rueda (ratón / touchpad, por separado) sale
+# del bus `wawa-config` y se fija al arrancar cada app, con watcher para el
+# cambio en caliente. Va acá y no app por app a propósito: es una preferencia
+# del HUMANO, no de cada app, y ninguna app del monorepo debería poder
+# desobedecerla por olvido. Ver `llimphi-ui/src/rueda.rs`.
+llimphi-ui = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+# El compositor DIRECTO, para que el catálogo de widgets cuelgue de él y no del
+# shell de escritorio (W4.6). Un widget que dependa de `llimphi-ui` arrastra
+# winit + accesskit + arboard + vello, y con eso queda fuera del alcance de una
+# app enjaulada; colgando de acá sirve igual en Linux, en el kernel y en la jaula.
+#
+# `default-features = false` VA ACÁ Y NO EN CADA WIDGET. Es la trampa recurrente
+# de cargo: apagar los defaults desde un miembro NO HACE NADA si el workspace
+# declaró la dep sin ellos (cargo lo avisa y lo ignora). Ya pasó con taffy, vello
+# y peniko. Sin esto, cada widget encendería el backend `vello` del compositor y
+# volvería a quedar fuera del alcance de una app enjaulada — que es justamente lo
+# que W4.6 vino a arreglar. Quien ensambla la app elige el backend: `llimphi-ui`
+# en Linux, `llimphi-jaula` en la jaula, `llimphi-wawa` en el kernel.
+llimphi-compositor = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Tier 3 — apps WASM con UI Llimphi real. El guest pinta un WireNode (IR
 # serializable), el host (runner) lo materializa en View<Msg> y rebota los
 # eventos al update del guest. SDK = lado guest, runner = lado host.
-llimphi-wire-view = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-wasm-app-sdk = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-wasm-runner = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-wire-view = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-wire-escena = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-wasm-app-sdk = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-wasm-runner = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Núcleo puro de distribución (CAS + verificación + resolve), SIN runner/GPU.
-llimphi-wasm-core = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-wasm-core = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Cara con baterías: re-exporta core + puente al runner para correr la app.
-llimphi-wasm-dist = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-wasm-open = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-wasm-registry = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-wasm-wasi = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-wasm-dist = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-wasm-open = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-wasm-registry = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-wasm-wasi = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Transporte P2P del bytecode por hash sobre BrahmanNet (card-net).
-llimphi-wasm-net = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-3d = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-wasm-net = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-3d = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Runner wlr-layer-shell: corre un `App` de Llimphi como barra anclada a un
 # borde (no como ventana), reusando la plumbing sctk+wgpu que pata probó.
-llimphi-layer = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-layer = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Paleta semántica compartida por las apps y los widgets.
-llimphi-theme = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-theme = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Tweens y helpers de animación sobre el bucle Elm.
-llimphi-motion = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-motion = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Máquina de estados de animación (estilo Rive) clip-agnóstica. Núcleo puro;
 # el render lo cablea el consumidor (llimphi-lottie). Escalón sobre el playback.
-llimphi-anim = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-anim = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Studio de animación «rive»: además del editor (bin), expone como lib los
 # documentos serializables (Doc + RigDoc + Project) que carga mirada-fondo.
-llimphi-anim-studio = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-anim-studio = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Render de mallas deformables (skel::Mesh) a vello Scene: vectorial y texturizada.
-llimphi-mesh = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-mesh = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Iconos vectoriales (BezPath en grid 24×24) compartidos por todas las apps.
-llimphi-icons = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-icons = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Puente fino vello_svg → Llimphi para SVG arbitrario (íconos .desktop, logos).
-llimphi-svg = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-svg = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Puente fino velato (Lottie) → Llimphi para animación vectorial autorada (.json).
-llimphi-lottie = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-image = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-lottie = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-image = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Decodificador de protocolos de gráficos de terminal (kitty/sixel) → RGBA.
-llimphi-term-graphics = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-term-graphics = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Widgets reusables sobre llimphi-ui — uno por crate.
-llimphi-widget-app-header = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-banner = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-button = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-rive-button = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-card = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-clipboard = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-context-menu = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-edit-menu = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-menubar = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-list = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-grid = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-table = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-color-picker = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-slider = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-scroll = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-lazy-list = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-splitter = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-stat-card = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-tabs = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-module-allichay = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-module-command-palette = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-module-diff-viewer = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-module-fif = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-module-file-picker = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-module-bookmarks = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-module-mini-map = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-module-shuma-term = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-module-symbol-outline = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-plugin-host = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-theme-switcher = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-text-area = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-text-editor-core = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-text-editor = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-text-editor-lsp = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-text-input = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-tiled = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-nodegraph = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-tree = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-grafo-nav = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-llimphi-widget-navigator = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-detail-table = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-select = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-terminal = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-widget-app-header = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-banner = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-button = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-rive-button = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-card = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-clipboard = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-context-menu = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-edit-menu = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-menubar = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-list = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-grid = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-table = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-color-picker = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-slider = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-app-icon = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-scroll = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-lazy-list = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-splitter = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-stat-card = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-tabs = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-module-allichay = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-module-command-palette = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-module-diff-viewer = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-module-fif = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-module-file-picker = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-module-bookmarks = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-module-mini-map = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-module-shuma-term = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-module-symbol-outline = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-plugin-host = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-theme-switcher = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-text-area = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-text-editor-core = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-text-editor = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-text-editor-lsp = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-text-input = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-tiled = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-nodegraph = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-tree = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+grafo-nav = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+# La lengua de expresiones compartida (PLAN-CRUCES §A5). `default-features =
+# false` acá NO: los consumidores Linux (maskay, pizarra, yupay, krama) la
+# quieren con `std`; el kernel y las jaulas la piden sin default desde su propio
+# manifiesto, como hacen con `format` y `dataflow`.
+krama-expr = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+krama-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+krama-store = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+krama-app-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+llimphi-widget-navigator = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-detail-table = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-select = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+# `gpu` explícito: el camino GPU-directo del modo grilla pasó a ser una feature
+# (para que el terminal pueda salir del proceso), y este `default-features =
+# false` la apagaba para TODOS los que lo toman por el workspace — dejando a
+# `shuma-module-shell` sin `CellPipeline`. Pedirla acá devuelve exactamente el
+# comportamiento de antes; quien la quiera apagar lo hace en su manifiesto.
+llimphi-widget-terminal = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Sello vectorial wawa (rombo + W implícita + Merkle Core).
-llimphi-widget-wawa-mark = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-widget-wawa-mark = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Widgets de elegancia transversal (tooltip, spinner, progress, toast,
 # modal, empty, status-bar, shortcuts-help, splash).
-llimphi-widget-tooltip = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-spinner = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-progress = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-toast = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-modal = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-empty = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-status-bar = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-shortcuts-help = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-timeline = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-transport = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-waveform = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-splash = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-widget-tooltip = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-spinner = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-progress = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-toast = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-modal = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-empty = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-status-bar = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-shortcuts-help = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-timeline = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-transport = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-waveform = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-splash = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Controles de formulario y signaling (switch, segmented, breadcrumb,
 # badge, avatar, skeleton, field).
-llimphi-widget-switch = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-segmented = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-rag-sidebar = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-dock-rail = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-toolbar = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-breadcrumb = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-badge = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-avatar = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-skeleton = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-field = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-widget-switch = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-segmented = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-rag-sidebar = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-dock-rail = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-toolbar = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-breadcrumb = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-badge = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-avatar = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-skeleton = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-field = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Firma visual transversal (gradient sutil + hairline accent).
-llimphi-widget-panel = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-panes = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+# `default-features = false` acá y no en `card`: los defaults de un widget sólo
+# se pueden apagar desde la declaración del WORKSPACE (la trampa recurrente de
+# cargo). Sin esto, `card` arrastraría a `panel` con backend `vello` y volvería a
+# quedar fuera de una jaula, aunque `card` pidiera `wire`.
+llimphi-widget-panel = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-panes = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Widgets Flutter-like — composición sobre primitivas Tier 1/3 ya
 # expuestas (sombra, gradient, animated, ripple).
-llimphi-widget-chip = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-fab = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-wrap = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-range-slider = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-calendar = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-fitted-box = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-carousel = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-rating = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-gauge = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-scaffold = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-hero = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-widget-router = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
-llimphi-workspace = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-widget-chip = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-fab = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-fugaces = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-wrap = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-range-slider = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-calendar = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-datetime-picker = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-form = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-accordion = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-stepper = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-fitted-box = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-carousel = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-rating = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-gauge = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-seven-segment = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-scaffold = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-hero = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-widget-router = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
+llimphi-workspace = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 # Abstracción Selector — host (paths) + wawa (khipus).
-llimphi-module-selector = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.2.0" }
+llimphi-module-selector = { git = "https://git.tawasuyu.net/tawasuyu/llimphi.git", tag = "v0.3.0" }
 
 # === Filesystem helpers ===
 directories = "5"
@@ -451,46 +593,48 @@ ttf-parser = "0.25"
 # ============================================================
 # Intra-workspace deps de nahual (referenciadas por workspace = true)
 # ============================================================
-nahual-text-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-image-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-pdf-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-cbz-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-thumb-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-gallery-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-video-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-card-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-audio-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-media-module = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-tullpu-module = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-tullpu-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-tullpu-icon-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-tullpu-icon-cli = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-tullpu-icon-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-tullpu-icon-llm = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-foreign-svg = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-foreign-pdf = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-tullpu-render = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-tullpu-render-gpu = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-tullpu-ops = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-tullpu-paint = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-tree-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-hex-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-svg-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-sheet-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-dbf-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-table-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-markdown-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-pluma-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-cotejo-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-deck-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-archive-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-font-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-map-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-geo-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-geo-voxel = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-iconos = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-viewer-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-nahual-file-explorer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
+nahual-text-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-image-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-pdf-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-cbz-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-thumb-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-vistazo-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-gallery-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-video-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-card-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-audio-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+media-module = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+tullpu-module = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+tullpu-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+tullpu-icon-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+tullpu-icon-cli = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+tullpu-icon-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+tullpu-icon-llm = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+foreign-svg = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+foreign-pdf = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+tullpu-render = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+tullpu-tiles = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+tullpu-render-gpu = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+tullpu-ops = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+tullpu-paint = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-tree-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-hex-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-svg-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-sheet-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-dbf-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-table-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-markdown-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-pluma-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-cotejo-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-deck-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-archive-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-font-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-map-viewer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-geo-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-geo-voxel = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-iconos = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-viewer-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+nahual-file-explorer-llimphi = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
 
 # ============================================================
 # Intra-workspace deps de pineal (módulo de gráficos)
@@ -510,33 +654,35 @@ pineal-export = { path = "00_unanchay/pineal/pineal-export" }
 pineal-hexbin = { path = "00_unanchay/pineal/pineal-hexbin" }
 pineal-contour = { path = "00_unanchay/pineal/pineal-contour" }
 pineal-bars = { path = "00_unanchay/pineal/pineal-bars" }
+pineal-quiver = { path = "00_unanchay/pineal/pineal-quiver" }
+pineal-yupana = { path = "00_unanchay/pineal/pineal-yupana" }
 pineal = { path = "00_unanchay/pineal/pineal-umbrella" }
 
 # ============================================================
 # Intra-workspace deps de iniy (laboratorio semántico de creencias)
 # ============================================================
-iniy-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-iniy-evidencia = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-iniy-derive = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-iniy-emisores = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-iniy-cache = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-iniy-daemon = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-iniy-ingest = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-iniy-extract = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-iniy-nli = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-iniy-nli-llm = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-iniy-graph = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-iniy-store = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
+iniy-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+iniy-evidencia = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+iniy-derive = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+iniy-emisores = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+iniy-cache = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+iniy-daemon = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+iniy-ingest = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+iniy-extract = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+iniy-nli = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+iniy-nli-llm = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+iniy-graph = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+iniy-store = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
 # la física de decaimiento de khipu la importa iniy-derive (SDD iniy §3)
-khipu-gravity = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
+khipu-gravity = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
 
 # === auto: declarados por crates internos faltantes ===
-cosmos-coords = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-cosmos-cities = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-cosmos-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-cosmos-ephemeris = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-cosmos-time = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
-cosmos-wcs = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.2.0" }
+cosmos-coords = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+cosmos-cities = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+cosmos-core = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+cosmos-ephemeris = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+cosmos-time = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
+cosmos-wcs = { git = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git", tag = "v0.3.0" }
 
 # === auto: externas de eternal ===
 celestial-eop-data = { version = "0.1"}
@@ -558,6 +704,18 @@ memmap2 = "0.9"
 wayland-client = "0.31"
 wayland-protocols-wlr = { version = "0.3", features = ["client"] }
 wayland-protocols-misc = { version = "0.3", features = ["client"] }
+# PipeWire (bindings a libpipewire-0.3, MIT) — el transporte de video que
+# `xdg-desktop-portal` exige para compartir pantalla. Lo usa
+# `mirada-screencast`; no entra al compositor (ver su LEEME: el productor es
+# un cliente wlr-screencopy, no el compositor mismo).
+#
+# `v0_3_34` es el piso que habilita `pw_stream_trigger_process` — sin él un
+# nodo DRIVER no se puede disparar y el consumidor recibe CERO cuadros, sin
+# error. Se pide el mínimo que hace falta, no la última: la feature declara
+# contra qué libpipewire se compila, y pedir de más rompería en una distro con
+# la biblioteca más vieja.
+pipewire = { version = "0.10", features = ["v0_3_34"] }
+libspa = "0.10"
 mockito = "1.0"
 ndarray = "0.15"
 num-traits = "0.2"
@@ -568,7 +726,27 @@ proptest = "1.4"
 quick-xml = "0.31"
 rayon = "1.8"
 regex = "1.11"
-reqwest = "0.12"
+# TLS por **rustls**, no por el `default-tls` de reqwest (que es `native-tls`
+# y arrastra `openssl-sys`). Dos razones, y la segunda es la que muerde:
+#  1. `openssl-sys` NO cross-compila a `aarch64-linux-android` — su build
+#     script no encuentra la libreria del NDK. Con el default puesto, toda
+#     app que toque HTTP queda fuera del porte a Android (khipu fue la
+#     primera en chocar).
+#  2. Seis de los doce consumidores YA pedian `rustls-tls` a mano. Como las
+#     features de cargo son aditivas, eso no apagaba `default-tls`: estaban
+#     linkeando **las dos** pilas de TLS creyendo que usaban una. Esto hace
+#     verdadero lo que esos crates ya decian.
+# El resto de los defaults de reqwest se conservan uno por uno.
+# ⚠️ Cambio de comportamiento a nombrar: rustls valida contra las raices
+# empaquetadas (webpki-roots), no contra el almacen de certificados del SO.
+# Para las APIs publicas que consume la suite da igual; un servidor con CA
+# propia habria que agregarlo explicitamente.
+reqwest = { version = "0.12", default-features = false, features = [
+    "rustls-tls",
+    "charset",
+    "http2",
+    "macos-system-configuration",
+] }
 tiff = "0.11"
 wide = "0.7"
 wiremock = "0.6"
@@ -590,7 +768,12 @@ markup5ever = "0.39"
 markup5ever_rcdom = "0.39"
 cssparser = "0.35"
 url = "2"
-ureq = { version = "2", default-features = false, features = ["tls"] }
+# `gzip`: sin ella ureq ni anuncia `Accept-Encoding` ni decodifica la respuesta.
+# python.org (detrás de Fastly) devuelve `content-encoding: gzip` AUNQUE no se lo
+# pidan, y el cuerpo comprimido se decodificaba como UTF-8: 3.911 caracteres
+# U+FFFD y una página de 16 cajas en vez de 1.489. Además baja lo que se
+# descarga en todo el resto.
+ureq = { version = "2", default-features = false, features = ["tls", "gzip"] }
 # Charset de la web real: no todo es UTF-8 (google.com sirve ISO-8859-1).
 # Ya venía transitivo por el stack de Servo — aquí se hace explícito.
 encoding_rs = "0.8"