feat(yahweh-meta-runtime): promover short_hash y preview_value desde nakui-explorer
Continúa la integración de apps nakui al stack yahweh. Los
helpers visuales que nakui-explorer tenía locales y son reusables
suben a yahweh-meta-runtime/format.
yahweh-meta-runtime:
- short_hash(h: &[u8; 32]) -> String: hex de los primeros 4 bytes.
- preview_value(v: &Value, max: usize) -> String: JSON one-liner
truncado con "..." (edge case max < 3 sin panic).
- 5 tests nuevos.
nakui-explorer:
- Nueva dep yahweh-meta-runtime.
- Borrado helpers locales (short_uuid + short_hash + preview_value)
+ 4 tests duplicados.
- Imports desde yahweh-meta-runtime.
Tests: 42→47 yahweh-meta-runtime, 7→3 nakui-explorer (los 3 que
quedan son específicos del explorer: load_log, breakdown,
missing_file). Resto intacto.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cb44952..8ed39c1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,46 @@ ratio/diff ver `git show <sha>`.
## 2026-05-10
+### feat(yahweh-meta-runtime): promover `short_hash` y `preview_value` desde nakui-explorer
+Continúa la integración de las apps nakui al stack yahweh. Los
+helpers visuales que `nakui-explorer` tenía locales y son reusables
+suben a `yahweh-meta-runtime/format` para que cualquier app pueda
+consumirlos sin duplicar.
+
+Cambios en `yahweh-meta-runtime`:
+- **`pub fn short_hash(h: &[u8; 32]) -> String`**: hex de los
+ primeros 4 bytes (8 chars). Útil para mostrar bundle/schema
+ hashes en UI sin quemar pantalla.
+- **`pub fn preview_value(v: &Value, max: usize) -> String`**:
+ JSON one-liner truncado con `...` al final si excede `max`
+ chars. Edge case: `max < 3` devuelve los primeros `max` chars
+ sin sufijo.
+- Re-exports en lib.
+- 5 tests nuevos: 4 tests + 1 sanity para el caso `max < ellipsis`.
+
+Migración de `nakui-explorer`:
+- Nueva dep `yahweh-meta-runtime` en Cargo.toml.
+- Borrado helpers locales `short_uuid`, `short_hash`,
+ `preview_value` (~30 líneas).
+- `use yahweh_meta_runtime::{preview_value, short_hash, short_uuid}`.
+- Borrados 4 tests duplicados (los runtime los testea).
+
+Tests:
+- `yahweh-meta-runtime`: 42 → **47** (+5 helpers nuevos).
+- `nakui-explorer`: 7 → **3** (–4 duplicados; quedan los 3
+ específicos: load_log, breakdown, missing_file).
+- Resto del workspace intacto.
+
+Beneficio operativo: 3 helpers visuales centralizados. Cualquier
+app nueva que muestre UUIDs/hashes/JSON-previews los importa sin
+re-implementar la heurística de truncamiento.
+
+Pendiente arquitectural: el render del card timeline en
+`nakui-explorer` (border-l-4 colored + flex_col + texto en niveles)
+es un pattern reusable que también aparece en `yahweh-widget-meta-form`
+(render_list filas). Cuando aparezca un tercer consumer de ese
+pattern se extrae a un widget yahweh.
+
### feat(brahman-cards): templates Nickel canónicos para cada body kind
Materializa el patrón "import + override" del brazo: hasta ahora
`BRAHMAN_CARDS_TEMPLATES_DIR` existía como mecanismo pero el repo
diff --git a/crates/apps/nakui-explorer/Cargo.toml b/crates/apps/nakui-explorer/Cargo.toml
index 38c6249..321bd06 100644
--- a/crates/apps/nakui-explorer/Cargo.toml
+++ b/crates/apps/nakui-explorer/Cargo.toml
@@ -7,6 +7,7 @@ description = "Explorador GPUI del event log de Nakui: timeline de seeds + morph
[dependencies]
nakui-core = { path = "../../modules/nakui/core" }
+yahweh-meta-runtime = { path = "../../modules/ui_engine/libs/meta-runtime" }
gpui = { workspace = true }
serde_json = { workspace = true }
uuid = { workspace = true, features = ["serde"] }
diff --git a/crates/apps/nakui-explorer/src/main.rs b/crates/apps/nakui-explorer/src/main.rs
index 1fe882c..9ff22a4 100644
--- a/crates/apps/nakui-explorer/src/main.rs
+++ b/crates/apps/nakui-explorer/src/main.rs
@@ -30,6 +30,7 @@ use gpui::{
SharedString, Window, WindowBounds, WindowOptions,
};
use nakui_core::event_log::{EventLog, LogEntry};
+use yahweh_meta_runtime::{preview_value, short_hash, short_uuid};
const REFRESH_INTERVAL: Duration = Duration::from_secs(2);
@@ -378,35 +379,8 @@ impl Render for Explorer {
}
}
-fn short_uuid(id: &uuid::Uuid) -> String {
- let s = id.to_string();
- if s.len() > 8 {
- s[..8].to_string()
- } else {
- s
- }
-}
-
-fn short_hash(h: &[u8; 32]) -> String {
- let mut s = String::with_capacity(8);
- for b in h.iter().take(4) {
- use std::fmt::Write;
- let _ = write!(s, "{:02x}", b);
- }
- s
-}
-
-/// Renderiza un `serde_json::Value` en una sola línea limitada a `max`
-/// caracteres (para preview en la timeline, no para edición).
-fn preview_value(v: &serde_json::Value, max: usize) -> String {
- let s = v.to_string();
- if s.chars().count() <= max {
- s
- } else {
- let truncated: String = s.chars().take(max - 3).collect();
- format!("{truncated}...")
- }
-}
+// Helpers `short_uuid`, `short_hash`, `preview_value` viven en
+// `yahweh_meta_runtime::format`. Se usan acá via el `use` de arriba.
#[cfg(test)]
mod tests {
@@ -472,34 +446,8 @@ mod tests {
assert!(result.is_empty());
}
- #[test]
- fn preview_value_truncates_long_strings() {
- let v = serde_json::json!({"a": "x".repeat(200)});
- let p = preview_value(&v, 30);
- assert!(p.len() <= 30);
- assert!(p.ends_with("..."));
- }
-
- #[test]
- fn preview_value_keeps_short_strings_intact() {
- let v = serde_json::json!({"a": 1});
- let p = preview_value(&v, 30);
- assert_eq!(p, "{\"a\":1}");
- }
-
- #[test]
- fn short_uuid_takes_first_8_chars() {
- let id = uuid::Uuid::parse_str("a1b2c3d4-e5f6-7890-abcd-ef1234567890").unwrap();
- assert_eq!(short_uuid(&id), "a1b2c3d4");
- }
-
- #[test]
- fn short_hash_takes_first_4_bytes_hex() {
- let mut h = [0u8; 32];
- h[0] = 0xaa;
- h[1] = 0xbb;
- h[2] = 0xcc;
- h[3] = 0xdd;
- assert_eq!(short_hash(&h), "aabbccdd");
- }
+ // Tests de `short_uuid` / `short_hash` / `preview_value` viven
+ // en `yahweh-meta-runtime::format` tras la migración. Si esos se
+ // vuelven a romper, los tests específicos del crate runtime los
+ // capturan; acá no duplicamos.
}
diff --git a/crates/modules/ui_engine/libs/meta-runtime/src/format.rs b/crates/modules/ui_engine/libs/meta-runtime/src/format.rs
index e827b9a..4e0c47b 100644
--- a/crates/modules/ui_engine/libs/meta-runtime/src/format.rs
+++ b/crates/modules/ui_engine/libs/meta-runtime/src/format.rs
@@ -52,6 +52,36 @@ pub fn short_uuid(id: &Uuid) -> String {
id.to_string().chars().take(8).collect()
}
+/// Hex string de los primeros 4 bytes de un hash SHA-256 (8
+/// caracteres). Útil para mostrar bundle/schema hashes en UI sin
+/// quemar pantalla con los 64 chars completos.
+pub fn short_hash(h: &[u8; 32]) -> String {
+ use std::fmt::Write;
+ let mut s = String::with_capacity(8);
+ for b in h.iter().take(4) {
+ let _ = write!(s, "{:02x}", b);
+ }
+ s
+}
+
+/// Renderea un `serde_json::Value` en una sola línea, truncado a
+/// `max` caracteres con `...` al final si excede. Para preview en
+/// timelines/cards/listas — NO para edición.
+///
+/// `max` es un upper-bound aproximado: el resultado nunca excede
+/// `max` chars, pero puede ser más corto si el value es chico.
+pub fn preview_value(v: &Value, max: usize) -> String {
+ let s = v.to_string();
+ if s.chars().count() <= max {
+ s
+ } else if max < 3 {
+ s.chars().take(max).collect()
+ } else {
+ let truncated: String = s.chars().take(max - 3).collect();
+ format!("{truncated}...")
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -105,6 +135,45 @@ mod tests {
assert_eq!(value_to_input_text(&json!(42)), "42");
}
+ #[test]
+ fn short_hash_takes_first_4_bytes_hex() {
+ let mut h = [0u8; 32];
+ h[0] = 0xaa;
+ h[1] = 0xbb;
+ h[2] = 0xcc;
+ h[3] = 0xdd;
+ assert_eq!(short_hash(&h), "aabbccdd");
+ }
+
+ #[test]
+ fn short_hash_zeros() {
+ let h = [0u8; 32];
+ assert_eq!(short_hash(&h), "00000000");
+ }
+
+ #[test]
+ fn preview_value_keeps_short_strings_intact() {
+ let v = json!({"a": 1});
+ assert_eq!(preview_value(&v, 30), "{\"a\":1}");
+ }
+
+ #[test]
+ fn preview_value_truncates_long_strings_with_ellipsis() {
+ let v = json!({"a": "x".repeat(200)});
+ let p = preview_value(&v, 30);
+ assert!(p.chars().count() <= 30);
+ assert!(p.ends_with("..."));
+ }
+
+ #[test]
+ fn preview_value_handles_max_smaller_than_ellipsis() {
+ // Edge case: max < 3 (no espacio para "..."). Devuelve
+ // los primeros `max` chars sin sufijo, sin panic.
+ let v = json!("xxxxxxxxxxxxxxxx");
+ let p = preview_value(&v, 2);
+ assert!(p.chars().count() <= 2);
+ }
+
#[test]
fn short_uuid_returns_first_8_chars() {
let id = Uuid::parse_str("01ARZ3ND-EKTS-V4RR-FFQ6-9G5FAV000000").ok();
diff --git a/crates/modules/ui_engine/libs/meta-runtime/src/lib.rs b/crates/modules/ui_engine/libs/meta-runtime/src/lib.rs
index b470f5b..f179578 100644
--- a/crates/modules/ui_engine/libs/meta-runtime/src/lib.rs
+++ b/crates/modules/ui_engine/libs/meta-runtime/src/lib.rs
@@ -30,6 +30,9 @@ pub mod refs;
pub use backend::{MetaBackend, WriteOutcome};
pub use delta::{compute_clear_fields, compute_field_delta};
-pub use format::{human_label_for_record, render_value, short_uuid, value_to_input_text};
+pub use format::{
+ human_label_for_record, preview_value, render_value, short_hash, short_uuid,
+ value_to_input_text,
+};
pub use parse::{infer_param_value, parse_field_value, resolve_param_value};
pub use refs::validate_entity_refs;