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 no
shippeaba ningún template.

3 templates basic bajo crates/core/brahman-cards/templates/:
- ente_basic.ncl: Card runtime mínima (Virtual + OneShot).
- monad_basic.ncl: Mónada con metadata vacía.
- ui_module_basic.ncl: descriptor UI con entities/menu/views vacíos.

Cada field override-able marcada `| default` (sin eso Nickel rebota
merge de strings/numbers no-iguales).

Nuevo `pub fn canonical_templates_dir() -> PathBuf` resuelve el dir
via CARGO_MANIFEST_DIR. Para distribución del binary standalone
queda como pending (include_dir! o convención de install path).

5 tests E2E que cubren los 3 templates con import+override, sanity
del default sin override, y existencia física del dir.

Tests brahman-cards: 26 → 31 (+5). Workspace intacto.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sergio
2026-05-10 09:30:16 +00:00
parent b05de24c24
commit df089f0585
6 changed files with 420 additions and 1 deletions
@@ -0,0 +1,31 @@
# `ente_basic.ncl` — template canónico para Cards de tipo Ente.
#
# Use case típico: declarar una entity runtime mínima (Virtual
# payload, OneShot supervision) sobrescribiendo sólo `id` y `label`:
#
# let base = import "ente_basic.ncl" in
# base & {
# id = "01ARZ3NDEKTSV4RRFFQ69G5FAV",
# label = "mi-ente",
# }
#
# El brazo `brahman-cards::load_card` lo dispatcha al
# `EnteJsonReader` porque el shape resultante tiene `payload` Y
# `supervision` (los campos detect-key del reader Ente).
#
# **Convención obligatoria**: cada field que el usuario va a
# sobrescribir está marcada `| default`. Sin eso Nickel rebota el
# merge de strings/numbers no-iguales con misma prioridad.
{
schema_version | Number | default = 1,
# Identidad: el usuario casi siempre las sobrescribe.
id | String | default = "TEMPLATE_ID",
label | String | default = "TEMPLATE_LABEL",
# Runtime defaults razonables: nodo lógico sin proceso, sin
# restart. Override si querés un ente con payload Wasm/Native.
payload | default = "Virtual",
supervision | default = "OneShot",
}
@@ -0,0 +1,45 @@
# `monad_basic.ncl` — template canónico para Cards de tipo Monad.
#
# Use case típico: declarar una agrupación semántica de archivos
# (Mónada de Nouser) con metadata mínima:
#
# let base = import "monad_basic.ncl" in
# base & {
# id = "01ARZ3NDEKTSV4RRFFQ69G5FAW",
# label = "fotos-2026",
# members = ["01ARZ3FILE1", "01ARZ3FILE2"],
# cardinality = 2,
# }
#
# El brazo lo dispatcha al `MonadJsonReader` por la presencia
# simultánea de `members` Y `cardinality`.
{
schema_version | Number | default = 1,
# Identidad: override siempre.
id | String | default = "TEMPLATE_ID",
label | String | default = "TEMPLATE_LABEL",
# Metadata semántica: defaults vacíos. El usuario typically
# override `members` + `cardinality`, opcionalmente `summary`
# / `keywords` / `dominant_lens`.
summary | String | default = "",
keywords | default = [],
centroid | default = [],
cardinality | Number | default = 0,
entropy | Number | default = 0.0,
# Lens variants serialize lowercase (serde rename_all): grid /
# code / gallery / database / markdown / tree.
dominant_lens | default = "grid",
# Membership: vacío por default. El usuario llena con los IDs
# de archivo cuando los conoce.
members | default = [],
pins | default = [],
# Timestamps Unix ms — default 0 = "no timestamp registrado".
# Override con el momento real cuando importa.
created_at_ms | Number | default = 0,
updated_at_ms | Number | default = 0,
}
@@ -0,0 +1,35 @@
# `ui_module_basic.ncl` — template canónico para Cards de tipo
# UiModule (descriptores de módulos para metainterfaz yahweh).
#
# Use case típico: declarar un módulo nuevo sobrescribiendo `id`,
# `label`, y aportando los `entities`/`menu`/`views` propios:
#
# let base = import "ui_module_basic.ncl" in
# base & {
# id = "customers",
# label = "Clientes",
# entities = [
# { name = "Customer", label = "Cliente", fields = [...] },
# ],
# menu = [{ label = "Listar", view = "list" }],
# views = { list = { kind = "list", ... } },
# }
#
# El brazo lo dispatcha al `UiModuleJsonReader` por la presencia
# simultánea de `entities` Y `views` Y `menu`.
{
# Identidad: override siempre.
id | String | default = "TEMPLATE_ID",
label | String | default = "TEMPLATE_LABEL",
# Subtítulo opcional (tooltip en el sidebar). null por default.
description | default = null,
# Las 3 listas/maps son el **payload** real del módulo. El
# template las deja vacías para que el usuario las defina sin
# heredar nada útil-pero-equivocado de un default.
entities | default = [],
menu | default = [],
views | default = {},
}