# MView Experimental: full context



## MView Experimental

Especificación autocontenida para crear, ejecutar y refrescar vistas persistentes sobre fuentes de solo lectura.


# MView Experimental

### Estado

Especificación activa de MView. El schema
(`/experimental/schema.json`), esta página y el catálogo de ejemplos (`/experimental/examples`)
deben coincidir exactamente entre sí; cualquier discrepancia es un defecto de la especificación, no
una libertad de implementación. El registro de decisiones de diseño está en el
[roadmap](/experimental/roadmap#decisiones-tomadas).

### Instrucciones para el modelo

Trata esta página como la especificación completa para **crear una MView ahora**. Usa exclusivamente los campos y convenciones definidos aquí.

Cuando el usuario solicite una MView:

1. Inspecciona la fuente real. Nunca inventes columnas, claves, archivos o contenido.
2. Conserva la fuente sin cambios.
3. Elige un Source Adapter de esta especificación.
4. Crea un directorio terminado en `.mview` junto a la fuente o dentro de `.mviews/`.
5. Crea `mview.json`, un transform y un render para una vista inicial.
6. Usa rutas relativas y valida el manifiesto con `https://carmenlabs.com/experimental/schema.json`.
7. Ejecuta adapter y transform sobre la fuente real.
8. Prueba el refresh modificando solamente una copia temporal de la fuente.
9. Informa los archivos creados y las validaciones ejecutadas.

No escribas un parser de CSV, JSON, Markdown o texto si existe un adapter de referencia para ese formato. No entregues solamente HTML: una MView necesita manifiesto, transform y render.

Cada patrón del [catálogo de ejemplos](/experimental/examples) está además publicado como paquete
completo y descargable bajo `/experimental/examples/<nombre>/` — manifiesto, transform y render
reales, no solo fragmentos. Bajalo y usalo como punto de partida en vez de escribir el manifiesto
y la estructura de carpetas desde cero; solo cambian identidad, campos reales de la fuente y
validaciones. Esto no aplica al render: adaptá la presentación al pedido del usuario, no heredes
el HTML/CSS del ejemplo tal cual.

### Definición

Una **MView** es un paquete persistente que conecta una fuente declarada y de solo lectura con una o más vistas reejecutables.

```text
fuente original de solo lectura
            |
            v
      Source Adapter
            |
            v
    entrada normalizada
       /          \
      v            v
transform A    transform B
      |            |
      v            v
  render A      render B
      |            |
      v            v
   vista A       vista B
```

En cada refresh, el runtime vuelve a leer los bytes actuales, normaliza la fuente una vez y ejecuta cada vista de forma independiente. Una representación normalizada puede cachearse temporalmente, pero nunca sustituye a la fuente original.

### Archivos mínimos

```text
sales-dashboard.mview/
├── mview.json
└── views/
    └── summary/
        ├── transform.mjs
        └── view.html
```

El paquete no incluye adapters comunes. El runtime los registra y los reutiliza para todas las MViews.

### Manifiesto

Todo `mview.json` debe validar contra:

```text
https://carmenlabs.com/experimental/schema.json
```

Forma mínima:

```json
{
  "$schema": "https://carmenlabs.com/experimental/schema.json",
  "id": "sales-dashboard",
  "name": "Sales Dashboard",
  "version": "1.0.0",
  "source": {
    "path": "../sales.csv",
    "adapter": "csv-table",
    "input": "table"
  },
  "views": [
    {
      "id": "summary",
      "name": "Summary",
      "prompt": "Show monthly sales and top clients.",
      "renderer": "html",
      "entry": {
        "transform": "./views/summary/transform.mjs",
        "render": "./views/summary/view.html"
      }
    }
  ]
}
```

### Campos del paquete

| Campo | Requerido | Contrato |
|---|---|---|
| `$schema` | sí | Exactamente `https://carmenlabs.com/experimental/schema.json`. |
| `id` | sí | Identificador estable: minúsculas, números, `.`, `_` y `-`. |
| `name` | sí | Nombre visible del paquete. |
| `version` | sí | Versión semántica del paquete. Cambia al modificar artefactos persistidos. |
| `source` | sí | Fuente de archivo o selección explícita de directorio. |
| `views` | sí | Array no vacío de vistas; sus `id` deben ser únicos. |
| `description` | no | Descripción humana. |
| `settings` | no | Defaults compartidos por todas las vistas. |
| `metadata` | no | Provenance informativa; no controla ejecución ni renderizado. |

### Fuente de archivo

```json
"source": {
  "path": "../sales.csv",
  "adapter": "csv-table",
  "input": "table",
  "options": {}
}
```

- `path` es relativo al directorio `.mview` y apunta a la fuente autorizada.
- `adapter` identifica un adapter registrado.
- `input` declara la forma normalizada que ese adapter debe producir.
- `options` configura explícitamente al adapter y participa en la clave de cache.
- `contentHash` (opcional) es `sha256:` seguido del hash SHA-256 de los bytes originales del archivo. Permite detectar drift entre la fuente y el momento en que la MView fue creada o validada por última vez, y ofrecer regeneración cuando no coincide. Mismo formato que en la fuente de directorio.

### Fuente de directorio

```json
"source": {
  "kind": "directory",
  "path": "../reports",
  "input": "filesystem",
  "files": [
    {
      "path": "january.csv",
      "adapter": "csv-table",
      "input": "table",
      "contentHash": "sha256:111..."
    },
    {
      "path": "notes.md",
      "adapter": "markdown-text",
      "input": "markdown",
      "contentHash": "sha256:222..."
    }
  ],
  "contentHash": "sha256:333..."
}
```

Un directorio no concede acceso general al filesystem. El runtime lee únicamente `source.files`; no recorre el directorio ni incorpora archivos nuevos, ni siquiera por extensión. `source.files` es obligatorio y no vacío para toda fuente de directorio — no hay forma implícita o basada en reglas (por ejemplo, por extensión) de seleccionar archivos. Cada `path` es relativo a `source.path`, no contiene `.` o `..`, no es absoluto, **debe ser único dentro de `files`**, y debe permanecer dentro del directorio después de resolver symlinks. `adapter` y `options` siguen el mismo contrato que en la fuente de archivo, por archivo.

`files[].contentHash` es opcional y, si está presente, es `sha256:` seguido del hash SHA-256 de los bytes originales de ese archivo. `source.contentHash`, si está presente en una fuente de directorio, es el hash agregado de toda la selección:

1. Para cada archivo, construir `{ path, contentHash }` con su hash real calculado en tiempo de ejecución (no el declarado en el manifiesto).
2. Ordenar la lista lexicográficamente por `path`.
3. Serializar la lista ordenada con `JSON.stringify`.
4. Calcular SHA-256 sobre esa serialización y anteponer `sha256:`.

Este es el mismo cálculo que ya implementa el runtime de referencia (`engine.js`). El agregado cambia si cambia el contenido, el path o la membresía de la selección, pero no si solo cambia el orden de `source.files` en el manifiesto.

### Vista

| Campo | Requerido | Contrato |
|---|---|---|
| `id` | sí | Único dentro del paquete. |
| `name` | sí | Nombre visible. |
| `prompt` | sí | Intención vigente de la vista y punto de partida para regenerarla. |
| `renderer` | sí | Único valor soportado en esta versión: `"html"` (`const` en el schema). No es un enum abierto todavía; agregar otro renderer es un cambio de especificación, no de implementación. |
| `entry.transform` | sí | Módulo relativo dentro del paquete. |
| `entry.render` | sí | Artefacto relativo dentro del paquete. |
| `description` | no | Descripción humana de la interpretación. |
| `settings` | no | Configuración específica de la vista. |
| `resources` | no | Directorio de recursos relativo y contenido dentro del paquete. |

Los settings efectivos se calculan mediante merge superficial en este orden:

1. `settings` del paquete.
2. `settings` de la vista.
3. Overrides explícitos del runtime.

Los valores posteriores reemplazan a los anteriores. No se hace merge recursivo.

### Validaciones semánticas adicionales al schema

`mview.schema.json` valida forma y tipos, pero JSON Schema no puede expresar unicidad por clave dentro de un array de objetos ni resolución real de symlinks. Todo runtime conforme **debe** validar además, antes de ejecutar cualquier vista:

- `views[].id` es único dentro del paquete.
- `source.files[].path` es único dentro de la selección (solo aplica a fuente de directorio).
- Cada `entry.transform`, `entry.render`, `view.resources` y `source.files[].path` resuelve, después de seguir symlinks, dentro de la raíz que lo contiene (el paquete `.mview` para entries y recursos; `source.path` para archivos de directorio). Cualquier resolución que escape se rechaza.
- El `adapter` declarado (a nivel de `source` o de cada `source.files[]`) está registrado y su `input` coincide con la capacidad que ese adapter produce.

Un manifiesto que pasa el schema pero falla alguna de estas validaciones no es una MView ejecutable.

### Entradas normalizadas

### `text`

```ts
{ text: string }
```

### `table`

```ts
{
  columns: string[];
  rows: Record<string, string>[];
}
```

### `markdown`

```ts
{
  raw: string;
  headings: Array<{ level: number; text: string }>;
}
```

### `value`

```ts
type JsonValue =
  | null
  | boolean
  | number
  | string
  | JsonValue[]
  | { [key: string]: JsonValue };

{ value: JsonValue }
```

### `filesystem`

```ts
{
  path: string;
  files: Array<{
    path: string;
    inputType: "text" | "table" | "markdown" | "value";
    input: TextInput | TableInput | MarkdownInput | ValueInput;
    contentHash: string;
  }>;
}
```

`files` se ordena lexicográficamente por path. `contentHash` es SHA-256 de los bytes originales con prefijo `sha256:`.

### Source Adapters

Un Source Adapter interpreta bytes físicos y produce una forma normalizada general. No conoce vistas, prompts ni modelos visuales.

```js
export const id = "plain-text";
export const input = "text";

export default async function adapt(bytes, context) {
  return { text: new TextDecoder("utf-8", { fatal: true }).decode(bytes) };
}
```

Contexto:

```ts
{
  source: Readonly<Record<string, JsonValue>>;
  options: Readonly<Record<string, JsonValue>>;
}
```

Reglas:

- Recibe solamente los bytes declarados y contexto acotado.
- No descubre ni abre archivos.
- No escribe la fuente.
- Su `id` y forma `input` son estables. Un cambio incompatible requiere otro identificador.
- Debe fallar con un error claro ante encoding o sintaxis inválida.

Adapters disponibles:

| ID | Formato | Salida | Código de referencia |
|---|---|---|---|
| `csv-table` | CSV UTF-8 | `table` | [/experimental/adapters/csv-table.mjs](/experimental/adapters/csv-table.mjs) |
| `json-value` | JSON UTF-8 | `value` | [/experimental/adapters/json-value.mjs](/experimental/adapters/json-value.mjs) |
| `markdown-text` | Markdown UTF-8 | `markdown` | [/experimental/adapters/markdown-text.mjs](/experimental/adapters/markdown-text.mjs) |
| `plain-text` | texto UTF-8 | `text` | [/experimental/adapters/plain-text.mjs](/experimental/adapters/plain-text.mjs) |

El runtime registra estos módulos. El agente selecciona el ID; no copia el adapter dentro del paquete.

### Transform

```js
export default async function transform(input, context) {
  return { rows: input.rows };
}
```

Contexto:

```ts
{
  source: Readonly<Record<string, JsonValue>>;
  settings: Readonly<Record<string, JsonValue>>;
  view: Readonly<Record<string, JsonValue>>;
  manifest: Readonly<{
    id: string;
    name: string;
    description?: string;
    version: string;
    source: Record<string, JsonValue>;
    settings: Record<string, JsonValue>;
    metadata: Record<string, JsonValue>;
  }>;
}
```

`manifest` expone identidad y provenance del paquete (no de la vista) para casos donde el transform necesita, por ejemplo, incluir la versión del paquete en el modelo. No sustituye a `settings` ni a `view` como fuente de configuración que afecta el render.

El transform:

- recibe entrada normalizada, nunca bytes ni handles de archivo;
- no accede al filesystem, red, reloj, aleatoriedad o estado oculto;
- es determinista respecto de `input` y `context`;
- no muta `input` ni `context`;
- devuelve solamente `JsonValue`;
- valida el significado que necesita y falla con mensajes claros.

No son serializables: `undefined`, `BigInt`, funciones, símbolos, ciclos, `NaN` e infinitos.

### Render

El runtime entrega:

```ts
{
  data: JsonValue;
  settings: Record<string, JsonValue>;
  source: Record<string, JsonValue>;
  view: Record<string, JsonValue>;
  resources: Record<string, string>;
}
```

### Renderer `html`

`entry.render` apunta a un documento HTML estático. Antes de ejecutar sus scripts inline, el runtime inyecta el objeto anterior como `window.mview`.

```html
<p id="total"></p>
<script>
  document.querySelector("#total").textContent = String(window.mview.data.total);
</script>
```

Reglas (MUST / MUST NOT):

- El render presenta `data`; no lee ni interpreta la fuente.
- No realiza `fetch`, XHR, WebSocket ni navegación programática.
- Inserta datos mediante `textContent`, atributos seguros o APIs equivalentes; nunca los considera HTML confiable.
- El runtime **debe** ejecutar el documento renderizado en un contexto de navegador aislado —por ejemplo un `iframe` con `sandbox="allow-scripts"` sin `allow-same-origin` ni `allow-forms`— sin acceso al host, filesystem o red del proceso que lo aloja.
- El runtime **debe** servir el documento renderizado con una Content-Security-Policy que al menos bloquee red saliente (`connect-src 'none'`) y restrinja `frame-ancestors` al propio host.
- Recursos locales deben estar declarados y permanecer dentro de `resources`.

Esta política es normativa para cualquier renderer `html` conforme, independientemente del entorno (navegador embebido, Electron, servidor local, etc.).

### Seguridad y rutas

- El runtime es dueño de toda E/S.
- Adapter, transform y render se consideran código no confiable.
- Todo entry point y recurso debe permanecer dentro del paquete después de resolver symlinks.
- `source.path` puede salir del paquete porque declara la fuente, pero debe permanecer dentro del workspace o ámbito autorizado por el usuario.
- Una fuente de directorio expone exclusivamente `source.files`; nunca se extiende automáticamente con otros archivos del directorio, ni por regla ni por extensión.
- Transform y render no reciben filesystem ni red.
- El manifiesto se valida antes de guardar, cargar o ejecutar.
- El runtime limita tiempo, memoria y tamaño de salida según su entorno.

### Estrategias de ejecución (no normativo)

La especificación no exige un mecanismo concreto de aislamiento de proceso para el transform, solo las garantías anteriores. El runtime de referencia (`mview/viewer`) resuelve esto así, como un ejemplo válido entre varios posibles:

- El transform corre en un subproceso de Node separado, con permisos de filesystem restringidos (`--permission --allow-fs-read` acotado al propio runner), límite de memoria y timeout.
- El servidor HTTP local se enlaza solo a `127.0.0.1`.
- El endpoint de refresh exige un token efímero generado por sesión; no hay refresh no autenticado.

Otra implementación (por ejemplo, un runtime embebido en una app de escritorio) puede lograr el mismo aislamiento con Web Workers, procesos separados por sistema operativo, o un sandbox de VM distinto — el invariante que debe sostenerse es que un transform o render comprometido no puede leer ni escribir archivos que el usuario no expuso explícitamente.

### Refresh y cache

Un refresh:

1. Valida el manifiesto.
2. Resuelve y valida rutas.
3. Lee los bytes actuales de la fuente declarada.
4. Ejecuta el adapter y valida la forma normalizada.
5. Congela o clona la entrada para impedir mutación entre vistas.
6. Ejecuta cada transform.
7. Renderiza cada vista correcta.

```text
validar manifiesto
        |
        v
resolver y validar rutas
        |
        v
leer bytes actuales de la fuente
        |
        v
adapter (una vez por refresh)
        |
        v
   entrada congelada
     /         \
    v           v
transform A   transform B   <- error aislado por vista
    |           |
    v           v
render A      render B      <- error aislado por vista
```

La cache de adaptación usa como mínimo: hash de bytes actuales, ID del adapter y `source.options`. La cache nunca reemplaza a la fuente de verdad.

Ámbitos de error:

- Lectura, selección o adaptación: error de fuente; afecta a todas las vistas.
- Transform: error de esa vista; las demás continúan.
- Render: error de esa vista; las demás continúan.

### Refresh y regeneración

- **Refresh:** reejecuta artefactos existentes sin modificar el paquete.
- **Regenerar transform:** modifica explícitamente la interpretación de una vista.
- **Regenerar render:** modifica explícitamente su presentación.
- **Regenerar vista:** reemplaza transform y render de una vista.
- **Regenerar paquete:** permite reconsiderar fuente, selección, adapters y vistas.

Toda regeneración persistida incrementa `version` cuando cambia comportamiento observable.

### Ejemplo completo

Fuente `sales.csv`:

```csv
Month,Client,Total
Jan,Acme,120
Feb,Globex,180
Mar,Acme,225
```

`sales-dashboard.mview/mview.json`:

```json
{
  "$schema": "https://carmenlabs.com/experimental/schema.json",
  "id": "sales-dashboard",
  "name": "Sales Dashboard",
  "version": "1.0.0",
  "source": {
    "path": "../sales.csv",
    "adapter": "csv-table",
    "input": "table"
  },
  "views": [
    {
      "id": "summary",
      "name": "Summary",
      "prompt": "Show total sales and sales by month.",
      "renderer": "html",
      "entry": {
        "transform": "./views/summary/transform.mjs",
        "render": "./views/summary/view.html"
      }
    }
  ]
}
```

`sales-dashboard.mview/views/summary/transform.mjs`:

```js
export default async function transform(input) {
  const required = ["Month", "Total"];
  const missing = required.filter((column) => !input.columns.includes(column));
  if (missing.length) throw new Error(`Missing required columns: ${missing.join(", ")}`);

  const months = input.rows.map((row) => ({
    month: row.Month,
    total: Number(row.Total || 0)
  }));

  return {
    rowCount: input.rows.length,
    totalSales: months.reduce((sum, row) => sum + row.total, 0),
    months
  };
}
```

`sales-dashboard.mview/views/summary/view.html`:

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Sales Dashboard</title>
  </head>
  <body>
    <main>
      <h1>Sales Dashboard</h1>
      <p>Total: <strong id="total"></strong></p>
      <ul id="months"></ul>
    </main>
    <script>
      const { data } = window.mview;
      document.querySelector("#total").textContent = String(data.totalSales);
      const list = document.querySelector("#months");
      for (const item of data.months) {
        const row = document.createElement("li");
        row.textContent = `${item.month}: ${item.total}`;
        list.append(row);
      }
    </script>
  </body>
</html>
```

Más patrones para JSON, Markdown, texto, varias vistas y directorios aparecen en [Ejemplos para agentes](/experimental/examples). La entrada `/experimental` contiene únicamente el procedimiento operativo y los índices necesarios para un agente.

### Comprobación del paquete

Antes de terminar:

- [ ] Existe un directorio `*.mview/`.
- [ ] `mview.json` valida contra `/experimental/schema.json`.
- [ ] Todos los `views[].id` son únicos.
- [ ] Source Adapter e `input` son compatibles.
- [ ] Todas las rutas de entry y recursos permanecen dentro del paquete.
- [ ] Para fuente de directorio: `source.files` no está vacío, cada `path` es único y ninguno escapa de `source.path` (ni por `..` ni por symlink).
- [ ] El transform ejecuta con la entrada real y devuelve JSON válido.
- [ ] El render consume exactamente el modelo retornado.
- [ ] Un refresh sobre una copia modificada actualiza el resultado sin regenerar archivos.
- [ ] La fuente original conserva exactamente sus bytes.
- [ ] La respuesta final informa cada validación realmente ejecutada.

### Recursos canónicos de Experimental

- Schema: [https://carmenlabs.com/experimental/schema.json](/experimental/schema.json)
- Especificación para modelos: [https://carmenlabs.com/experimental/llms.txt](/experimental/llms.txt)
- Adapters: `/experimental/adapters/*.mjs`
- Ejemplos: [https://carmenlabs.com/experimental/examples](/experimental/examples)
- Roadmap y registro de decisiones: [https://carmenlabs.com/experimental/roadmap](/experimental/roadmap)

Todo lo necesario para construir una MView Experimental está contenido bajo `/experimental`.


---

## Catálogo de ejemplos para MView Experimental

Referencias técnicas para crear MViews sin imponer una presentación visual.


# Catálogo de ejemplos para MView Experimental

### Estado

Este catálogo forma parte de la [especificación Experimental](/experimental). Sus manifests usan exclusivamente el schema Experimental.

El objetivo no es mostrar todas las posibilidades de MView. Es entregar un conjunto pequeño de patrones completos para que un modelo seleccione el más cercano y escriba la menor cantidad posible de código nuevo.

Los 7 ejemplos mínimos que este catálogo describe en prosa están además implementados como
paquetes reales, probados end-to-end (validación, ejecución contra la fuente real, refresh sobre
una copia temporal, y confirmación de que la fuente y el paquete no se modifican), bajo
`mview/viewer/examples/`. Cada carpeta tiene su propio `README.md` con "qué copiar" / "qué
reemplazar". Correr `pnpm test` en `mview/viewer` ejecuta los siete junto con el resto de la
suite.

### Regla para agentes

Antes de crear archivos:

1. Inspeccionar la fuente real.
2. Elegir la forma normalizada.
3. Elegir un adapter registrado.
4. Copiar el ejemplo más cercano.
5. Reemplazar únicamente identidad, prompt, campos reales, transform y presentación.

Si existe un adapter adecuado, el modelo no debe copiarlo, modificarlo ni reimplementarlo dentro del paquete.

### Matriz de selección

| Fuente inspeccionada | Adapter | Entrada normalizada | Ejemplo base | Paquete descargable |
|---|---|---|---|---|
| CSV UTF-8 | `csv-table` | `table` | Panel tabular | [/experimental/examples/csv/](#csv-con-dos-vistas) |
| JSON estricto | `json-value` | `value` | Resumen estructurado | [/experimental/examples/json/](#json-estructurado) |
| Markdown UTF-8 | `markdown-text` | `markdown` | Navegador de documento | [/experimental/examples/markdown/](#markdown) |
| TXT UTF-8 | `plain-text` | `text` | Métricas de texto | [/experimental/examples/text/](#texto-plano) |
| Selección de archivos | uno por archivo | `filesystem` | Reporte de carpeta | [/experimental/examples/directory/](#directorio-explícito) |

Los archivos exactos de cada paquete (fuente, manifiesto, transform y render por vista) están
listados en [Paquetes descargables](#paquetes-descargables), más abajo.

### Paquetes descargables

Los 7 ejemplos mínimos no son solo prosa: cada archivo real está publicado como recurso estático
bajo `/experimental/examples/<nombre>/`. Bajalo, modificalo, y usalo como punto de partida —
no hace falta escribir un paquete desde cero para tener algo ejecutable.

### CSV con dos vistas

- Fuente: [pokemons-data.csv](/experimental/examples/csv/pokemons-data.csv)
- Manifiesto: [mview.json](/experimental/examples/csv/pokemons-data.mview/mview.json)
- Vista `stats`: [transform.mjs](/experimental/examples/csv/pokemons-data.mview/views/stats/transform.mjs), [view.html](/experimental/examples/csv/pokemons-data.mview/views/stats/view.html)
- Vista `pokedex`: [transform.mjs](/experimental/examples/csv/pokemons-data.mview/views/pokedex/transform.mjs), [view.html](/experimental/examples/csv/pokemons-data.mview/views/pokedex/view.html)
- [README](/experimental/examples/csv/README.md)

### JSON estructurado

- Fuente: [products.json](/experimental/examples/json/products.json)
- Manifiesto: [mview.json](/experimental/examples/json/summary.mview/mview.json)
- Vista `overview`: [transform.mjs](/experimental/examples/json/summary.mview/views/overview/transform.mjs), [view.html](/experimental/examples/json/summary.mview/views/overview/view.html)
- [README](/experimental/examples/json/README.md)

### Markdown

- Fuente: [guide.md](/experimental/examples/markdown/guide.md)
- Manifiesto: [mview.json](/experimental/examples/markdown/summary.mview/mview.json)
- Vista `toc`: [transform.mjs](/experimental/examples/markdown/summary.mview/views/toc/transform.mjs), [view.html](/experimental/examples/markdown/summary.mview/views/toc/view.html)
- [README](/experimental/examples/markdown/README.md)

### Texto plano

- Fuente: [access.log](/experimental/examples/text/access.log)
- Manifiesto: [mview.json](/experimental/examples/text/summary.mview/mview.json)
- Vista `metrics`: [transform.mjs](/experimental/examples/text/summary.mview/views/metrics/transform.mjs), [view.html](/experimental/examples/text/summary.mview/views/metrics/view.html)
- [README](/experimental/examples/text/README.md)

### Directorio explícito

- Fuente: [january.csv](/experimental/examples/directory/reports/january.csv), [february.csv](/experimental/examples/directory/reports/february.csv), [notes/summary.md](/experimental/examples/directory/reports/notes/summary.md)
- Manifiesto: [mview.json](/experimental/examples/directory/reports-summary.mview/mview.json)
- Vista `summary`: [transform.mjs](/experimental/examples/directory/reports-summary.mview/views/summary/transform.mjs), [view.html](/experimental/examples/directory/reports-summary.mview/views/summary/view.html)
- [README](/experimental/examples/directory/README.md)

### Vista que falla junto a otra válida

- Fuente: [sales.csv](/experimental/examples/failing-view/sales.csv)
- Manifiesto: [mview.json](/experimental/examples/failing-view/dashboard.mview/mview.json)
- Vista `good`: [transform.mjs](/experimental/examples/failing-view/dashboard.mview/views/good/transform.mjs), [view.html](/experimental/examples/failing-view/dashboard.mview/views/good/view.html)
- Vista `broken`: [transform.mjs](/experimental/examples/failing-view/dashboard.mview/views/broken/transform.mjs), [view.html](/experimental/examples/failing-view/dashboard.mview/views/broken/view.html)
- [README](/experimental/examples/failing-view/README.md)

### Convención de paquetes

```text
<nombre>.mview/
├── mview.json
└── views/
    └── <view-id>/
        ├── transform.mjs
        └── view.html
```

Una segunda vista añade otro directorio bajo `views/`; no duplica la fuente ni el adapter.

### Ejemplo 1: CSV con una vista

### Fuente

```csv
Month,Client,Total
Jan,Acme,120
Feb,Globex,180
Mar,Acme,225
```

### Manifiesto

```json
{
  "$schema": "https://carmenlabs.com/experimental/schema.json",
  "id": "sales-dashboard",
  "name": "Sales Dashboard",
  "version": "1.0.0",
  "source": {
    "path": "../sales.csv",
    "adapter": "csv-table",
    "input": "table"
  },
  "views": [
    {
      "id": "summary",
      "name": "Summary",
      "prompt": "Show total sales and sales by month.",
      "renderer": "html",
      "entry": {
        "transform": "./views/summary/transform.mjs",
        "render": "./views/summary/view.html"
      }
    }
  ]
}
```

### Transform

```js
export default async function transform(input) {
  const months = input.rows.map((row) => ({
    month: row.Month,
    total: Number(row.Total || 0)
  }));

  return {
    rowCount: input.rows.length,
    totalSales: months.reduce((sum, row) => sum + row.total, 0),
    months
  };
}
```

### Render HTML mínimo

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Sales Dashboard</title>
  </head>
  <body>
    <main>
      <h1>Sales Dashboard</h1>
      <p>Total: <strong id="total"></strong></p>
      <ul id="months"></ul>
    </main>
    <script>
      const { data } = window.mview;
      document.querySelector("#total").textContent = String(data.totalSales);
      const list = document.querySelector("#months");
      for (const item of data.months) {
        const row = document.createElement("li");
        row.textContent = `${item.month}: ${item.total}`;
        list.append(row);
      }
    </script>
  </body>
</html>
```

El uso de `textContent` es intencional: los datos de la fuente no se tratan como HTML confiable.

### Ejemplo 2: añadir una segunda vista al mismo CSV

Solo se agrega otra entrada al array y otro directorio. La fuente se sigue leyendo y adaptando una vez por refresh.

```json
{
  "id": "top-clients",
  "name": "Top Clients",
  "prompt": "Rank clients by accumulated sales.",
  "renderer": "html",
  "entry": {
    "transform": "./views/top-clients/transform.mjs",
    "render": "./views/top-clients/view.html"
  }
}
```

```js
export default async function transform(input) {
  const totals = new Map();
  for (const row of input.rows) {
    totals.set(row.Client, (totals.get(row.Client) || 0) + Number(row.Total || 0));
  }

  return {
    clients: [...totals.entries()]
      .map(([client, total]) => ({ client, total }))
      .sort((a, b) => b.total - a.total)
  };
}
```

Este ejemplo debe probar que:

- el adapter se invoca una vez;
- ambos transforms reciben la misma entrada lógica;
- mutar la entrada desde un transform no afecta al otro;
- un fallo de render en una vista no oculta la otra.

### Ejemplo 3: JSON estructurado

### Fuente

```json
{
  "project": "Atlas",
  "status": "active",
  "milestones": [
    { "name": "Design", "done": true },
    { "name": "Launch", "done": false }
  ]
}
```

### Fuente en el manifiesto

```json
"source": {
  "path": "../project.json",
  "adapter": "json-value",
  "input": "value"
}
```

### Transform

```js
export default async function transform(input) {
  const project = input.value;
  if (!project || typeof project !== "object" || Array.isArray(project)) {
    throw new Error("Expected a project object in project.json");
  }

  const milestones = Array.isArray(project.milestones) ? project.milestones : [];
  return {
    name: String(project.project || "Unnamed project"),
    status: String(project.status || "unknown"),
    completed: milestones.filter((item) => item?.done === true).length,
    total: milestones.length
  };
}
```

El adapter resuelve JSON físico. El transform valida significado y construye el modelo específico de la vista.

### Ejemplo 4: Markdown

### Fuente en el manifiesto

```json
"source": {
  "path": "../notes.md",
  "adapter": "markdown-text",
  "input": "markdown"
}
```

### Transform

```js
export default async function transform(input) {
  return {
    title: input.headings.find((heading) => heading.level === 1)?.text || "Document",
    sections: input.headings.filter((heading) => heading.level === 2),
    characterCount: input.raw.length
  };
}
```

El ejemplo base no debería implementar un parser Markdown en el transform ni insertar `input.raw` mediante `innerHTML`.

### Ejemplo 5: texto plano

### Fuente en el manifiesto

```json
"source": {
  "path": "../server.log",
  "adapter": "plain-text",
  "input": "text"
}
```

### Transform

```js
export default async function transform(input) {
  const lines = input.text.split(/\r?\n/).filter(Boolean);
  return {
    lineCount: lines.length,
    errors: lines.filter((line) => line.includes("ERROR")).length,
    warnings: lines.filter((line) => line.includes("WARN")).length
  };
}
```

La semántica de `ERROR` y `WARN` pertenece al transform de esta vista, no al adapter de texto general.

### Ejemplo 6: directorio explícito

### Estructura

```text
reports/
├── january.csv
├── february.csv
├── notes.md
└── private.txt
```

### Fuente en el manifiesto

```json
"source": {
  "kind": "directory",
  "path": "../reports",
  "input": "filesystem",
  "files": [
    {
      "path": "january.csv",
      "adapter": "csv-table",
      "input": "table"
    },
    {
      "path": "february.csv",
      "adapter": "csv-table",
      "input": "table"
    },
    {
      "path": "notes.md",
      "adapter": "markdown-text",
      "input": "markdown"
    }
  ]
}
```

`private.txt` no forma parte de la MView. El runtime no debe leerlo, hashearlo, descubrirlo ni entregarlo al transform.

### Transform orientativo

```js
export default async function transform(input) {
  const tables = input.files.filter((file) => file.inputType === "table");
  const rows = tables.flatMap((file) => file.input.rows);

  return {
    selectedFiles: input.files.map((file) => file.path),
    rowCount: rows.length,
    total: rows.reduce((sum, row) => sum + Number(row.Total || 0), 0)
  };
}
```

`inputType` identifica la forma normalizada de cada archivo y coincide con el `input` declarado para ese archivo en el manifiesto.

### Templates copiables

### Transform de tabla

```js
export default async function transform(input, context) {
  const required = [/* columnas reales inspeccionadas */];
  const missing = required.filter((column) => !input.columns.includes(column));
  if (missing.length > 0) {
    throw new Error(`Missing required columns: ${missing.join(", ")}`);
  }

  return {
    title: context.view.name || context.view.id,
    rows: input.rows.map((row) => ({
      // Mapear solamente campos reales necesarios por el render.
    }))
  };
}
```

### Render de lista seguro

```html
<ul id="items"></ul>
<script>
  const list = document.querySelector("#items");
  for (const item of window.mview.data.items) {
    const element = document.createElement("li");
    element.textContent = item.label;
    list.append(element);
  }
</script>
```

### Error semántico claro

```js
if (!Array.isArray(input.value?.items)) {
  throw new Error("Expected project.json to contain an items array");
}
```

### Qué debe contener cada ejemplo implementado

```text
<example>/
├── README.md
├── source files
├── expected/
│   └── data.json
├── <name>.mview/
│   ├── mview.json
│   └── views/
│       └── ...
└── test.mjs
```

El test debe:

1. Validar el manifiesto.
2. Copiar la fuente a un directorio temporal.
3. Ejecutar adapter y transform.
4. Comparar el modelo con `expected/data.json`.
5. Cambiar la copia temporal de la fuente.
6. Ejecutar refresh sin modificar el paquete.
7. Confirmar que el modelo cambió.
8. Confirmar que la fuente original permaneció intacta.

### Anti-ejemplos necesarios

Además de caminos felices, el catálogo debe incluir fixtures que demuestren rechazos:

- entry point con `../` que escapa del paquete;
- symlink de recurso que escapa del paquete;
- archivo de directorio no seleccionado;
- dos vistas con el mismo `id`;
- adapter que produce una forma diferente de la declarada;
- transform que devuelve `undefined`, `BigInt` o un ciclo;
- transform que intenta leer el filesystem;
- render que intenta obtener la fuente mediante `fetch`;
- fallo de una vista junto a otra vista válida.

### Estado: cumplido

Todos, salvo uno, están probados en `mview/viewer/test/security-anti-examples.test.js` (10 casos).
"Archivo de directorio no seleccionado" se prueba dentro de `examples/directory/test.mjs`
(`march.csv` se agrega a la copia temporal sin declararlo y se confirma que no afecta el modelo).
"Fallo de una vista junto a otra válida" tiene su propio ejemplo completo en
`examples/failing-view/`. El único caso no cubierto por un test automatizado es "render que
intenta `fetch`": se aplica vía la Content-Security-Policy del documento renderizado
(`connect-src 'none'` en `viewer.js`), una garantía que exige un navegador real y no el test
runner de Node; queda documentado como aplicado pero no verificado por test automatizado hasta que
exista un runner de integración con navegador headless.

Construir el caso "adapter que produce una forma diferente" no encontró un bug — la validación de
forma normalizada en `adapters.js` ya lo rechazaba correctamente. Construir los casos de
aislamiento de `process`/filesystem sí sirvió para reconfirmar, después del fix de
`transform-runner.js` (ver roadmap, Fase 5), que los mensajes de error siguen siendo legibles y no
duplican el prefijo `"Error: "`.

### Criterio de calidad para modelos

Los ejemplos cumplen su propósito si un agente puede resolver una tarea común sin generar:

- un Source Adapter;
- un lector de archivos;
- un validador JSON Schema;
- un servidor web;
- un mecanismo propio de refresh;
- un global de datos distinto de la entrega definida por el renderer;
- datos de muestra que sustituyan a la fuente real.

El código nuevo debería concentrarse en dos preguntas: **qué interpretación necesita el usuario** y **cómo debe presentarse**.


---

## Roadmap de MView Experimental

Fases, entregables y criterios para validar MView Experimental de principio a fin.


# Roadmap de MView Experimental

### Propósito

Este roadmap ordena la implementación y validación de la [especificación Experimental](/experimental). Cada fase debe probarse con artefactos ejecutables antes de congelar su contrato.

El trabajo prioriza dos resultados simultáneos:

- **Robustez para runtimes:** contratos completos, validables y seguros.
- **Simplicidad para agentes:** pocas decisiones, adapters reutilizables y ejemplos copiables.

### Principios de ejecución

- Cerrar semántica antes de modificar el schema.
- Implementar primero el camino mínimo completo, no todas las capacidades.
- Probar contratos con al menos dos implementaciones independientes cuando afecten interoperabilidad.
- Tratar ejemplos y fixtures como productos de primera clase.
- No publicar una guía para agentes hasta que sus pasos puedan ejecutarse de extremo a extremo.
- No declarar estable un identificador de adapter o renderer sin pruebas de conformidad.

### Fase 0: decisiones de diseño

### Objetivo

Eliminar ambigüedades que cambiarían el schema o el comportamiento observable.

### Decisiones

- Nombre definitivo de `source.input` o `source.capability`.
- Identidad y versionado de adapters registrados.
- Identidad y versionado de renderers.
- Ubicación y merge de `settings`.
- Recursos compartidos frente a recursos por vista.
- Forma exacta de `filesystem`.
- Contextos mínimos entregados a adapter, transform y render.
- Política mínima del renderer HTML: scripts, CSP, sandbox y red.
- Reglas de unicidad para `views[].id` y versionado del paquete.

### Decisiones tomadas

| Decisión | Opción elegida | Alternativa rechazada | Motivo |
|---|---|---|---|
| Nombre del campo de capacidad | `input` en el manifiesto (`source.input`, `source.files[].input`, `view` no aplica) | `source.capability` | `input` ya era el nombre usado consistentemente en manifiesto, runtime (`engine.js`) y prosa; renombrar no aportaba claridad y hubiera roto la implementación existente sin beneficio. |
| Forma exacta de `filesystem` / fuente de directorio | `source.files[]` explícito: `{ path, adapter, input, contentHash? }`, sin descubrimiento implícito | `source.mapping[]` por extensión de archivo (lo que tenía `mview.schema.json`) | `engine.js` y `viewer.js` ya implementaban y probaban `source.files`; `mapping` no estaba implementado en ningún lado y, siendo dispatch implícito por extensión sobre lo que exista en el directorio, contradice directamente la garantía de seguridad "el runtime lee únicamente los archivos declarados, sin descubrimiento". El schema era el que estaba desalineado, no el runtime ni la prosa. |
| `contentHash` agregado para directorio | Definido explícitamente: `sha256(JSON.stringify(files ordenados por path, cada uno como {path, contentHash}))`, tal como ya lo calcula `engine.js` | Serialización canónica RFC 8785 | La spec Experimental prioriza simplicidad de implementación sobre interoperabilidad estricta entre lenguajes; documentar exactamente lo que el runtime de referencia ya hace evita divergencia entre spec e implementación. Puede revisarse más adelante si la interoperabilidad estricta entre lenguajes se vuelve un requisito real. |
| Identidad y versionado de adapters | IDs de string estables (`csv-table`, `json-value`, `markdown-text`, `plain-text`), sin campo de versión propio en el manifiesto; un cambio incompatible requiere nuevo ID | Versionado semántico por adapter (`csv-table@2`) | No hay todavía necesidad demostrada de múltiples versiones activas de un mismo adapter; agregar versionado ahora es especular. Ya documentado en la prosa ("Un cambio incompatible requiere otro identificador"). |
| Identidad de renderers | Único valor soportado en esta versión inicial: `"html"` (ya reflejado como `const` en el schema) | Enum abierto con múltiples renderers desde el inicio | Coincide con el alcance real implementado en `viewer.js`. Se documenta explícitamente como limitación de esta versión, no como diseño cerrado para siempre. |
| Recursos compartidos vs por vista | Solo `view.resources` (por vista) | `mview.json`-level `resources` compartido entre vistas | Es lo único implementado y probado en `viewer.js` (`/resources/<viewId>/...`). Recursos compartidos quedan fuera de alcance de este cierre; se puede añadir como campo opcional más adelante sin romper manifiestos existentes. |
| Merge de `settings` | Package → view → overrides de runtime, shallow merge, sin merge recursivo | Merge profundo (deep merge) | Ya documentado y ya implementado (`{ ...manifest.settings, ...view.settings }` en `engine.js`); un merge profundo agrega ambigüedad sin caso de uso probado. |
| Contexto mínimo del transform | `{ source, settings, view, manifest }` — se agrega `manifest` (id, name, description, version, source, settings, metadata) a lo ya documentado | Mantener el contexto sin `manifest` | `engine.js` ya entrega `context.manifest`; la prosa no lo documentaba. Se documenta el comportamiento real en vez de eliminar código funcional. |
| Política del renderer HTML | Normativa: inyección de `window.mview` antes de scripts inline, ejecución en iframe `sandbox="allow-scripts"` (sin `allow-same-origin` ni `allow-forms`), CSP `connect-src 'none'` en el documento renderizado, servidor de referencia enlazado solo a `127.0.0.1`, refresh autenticado con token efímero de sesión | Dejar la política como no-normativa/orientativa | `viewer.js` ya implementa exactamente esto; convertirlo en contrato normativo evita que otras implementaciones bajen el nivel de seguridad. |
| Unicidad de `views[].id` y `source.files[].path` | MUST explícito en la prosa + validación semántica obligatoria fuera de JSON Schema (JSON Schema no puede expresar unicidad por clave dentro de un array de objetos) | Confiar en `uniqueItems` de JSON Schema | `uniqueItems` compara igualdad estructural completa, no por clave; ya se necesitaba (y ya existía en `engine.js`) un validador semántico adicional. Se documenta como requisito explícito de cualquier implementación conforme. |

### Entregables

- Registro breve de decisiones con una opción elegida y alternativas rechazadas.
- Manifiestos para archivo, directorio, una vista y varias vistas.
- Tipos TypeScript de todas las formas normalizadas y contextos.

### Criterio de salida

Ninguna decisión pendiente afecta campos requeridos, formas normalizadas o límites de seguridad.

### Nota sobre el entregable "manifiestos para archivo, directorio, una vista y varias vistas"

Las cuatro combinaciones existen implícitamente pero no las cuatro como ejemplos independientes:
archivo+una vista y archivo+varias vistas están en `examples/csv/` (`Ejemplo 1` y `Ejemplo 2`);
directorio+una vista está en `examples/directory/`. Directorio+varias vistas no tiene un ejemplo
dedicado — no hay nada en el schema o el runtime que lo restrinja (`views[]` no depende de
`source.kind`), es exactamente el mismo patrón de "agregar otra entrada a `views[]`" que ya
demuestra `Ejemplo 2` sobre una fuente de archivo. Se considera cubierto por combinación de
ejemplos existentes, no por un caso separado.

### Fase 1: contrato machine-readable

### Objetivo

Mantener un único schema Experimental estricto.

### Trabajo

- Mantener el modelo de una fuente y `views[]`.
- Usar `https://carmenlabs.com/experimental/schema.json` como identidad durante toda la evaluación.
- Definir condicionales para fuentes de archivo y directorio.
- Validar paths de paquete y paths relativos a directorio.
- Rechazar propiedades desconocidas.
- Añadir validaciones semánticas que JSON Schema no expresa, especialmente unicidad de `views[].id` y escapes mediante symlinks.
- Crear fixtures válidos e inválidos para cada regla.

### Entregables

- `mview.schema.json` definitivo.
- Validador semántico complementario.
- Corpus de manifests válidos e inválidos.
- Pruebas automatizadas de schema y paths.

### Criterio de salida

Todos los manifests del catálogo de ejemplos validan y cada fixture inválido falla por la razón esperada.

### Estado: cumplido

Los 7 manifests de ejemplo validan contra `mview.schema.json` (verificado ejecutando
`validateManifest` contra cada uno, no solo revisando a simple vista). El corpus de fixtures
inválidos vive inline en los tests en vez de como archivos `.json` separados —
`test/directory-source.test.js` y `test/security-anti-examples.test.js` cubren: fuente de
directorio sin `files`, `files` duplicados, `views[].id` duplicados, `contentHash` que no
coincide (agregado y por archivo), y escapes de ruta — cada uno con la aserción de por qué falla.
El validador semántico complementario es `validateManifest` en `engine.js` (unicidad de IDs,
compatibilidad adapter/input) más `resolveContainedPath` (escapes vía `..` y symlinks).

### Fase 2: contratos normativos

### Objetivo

Documentar las garantías que permiten implementaciones portables y seguras sin inflar el manifiesto.

### Trabajo

- Actualizar overview y referencia del schema.
- Reemplazar parser/component por transform/render donde corresponda.
- Especificar Source Adapter, registro, capacidad de salida y cache.
- Especificar serialización, determinismo y mutabilidad de entradas.
- Definir refresh y regeneración como operaciones distintas.
- Definir errores globales de fuente y errores aislados por vista.
- Completar seguridad, paths, red, symlinks y directorios.
- Sincronizar documentación en español e inglés.

### Entregables

- Especificación completa en ambos idiomas.
- Matriz de requisitos `MUST`, `MUST NOT` y `SHOULD` por actor.
- Diagrama normativo del ciclo de refresh.

### Criterio de salida

Un implementador puede construir un runtime sin depender de comportamiento implícito en los ejemplos.

### Estado: parcialmente cumplido

`unified-mview.md` documenta parser/transform/render, Source Adapter y registro, serialización y
determinismo, refresh vs. regeneración como operaciones distintas (con diagrama del ciclo de
refresh), y seguridad/paths/symlinks/directorios completos, incluyendo una sección dedicada a
validaciones semánticas que el JSON Schema no puede expresar. La matriz `MUST`/`MUST NOT`/`SHOULD`
por actor no existe como tabla separada — está distribuida como listas de reglas dentro de cada
sección (Source Adapters, Transform, Render, Seguridad); se considera equivalente en contenido,
no en formato. **Pendiente real: sincronizar documentación en español e inglés** — se decidió
diferir la traducción a inglés hasta que el contenido esté completamente estable, para no traducir
contenido que todavía podía cambiar dos veces.

### Fase 3: adapters de referencia

### Objetivo

Eliminar la generación repetitiva de parsers físicos para formatos comunes.

### Alcance inicial

| Adapter | Entrada | Forma | Límites explícitos |
|---|---|---|---|
| `csv-table` | CSV UTF-8 | `table` | RFC 4180 básico, comillas y saltos de línea documentados |
| `json-value` | JSON UTF-8 | `value` | JSON estricto, sin JSON5 |
| `markdown-text` | Markdown UTF-8 | `markdown` | texto y headings, sin ejecutar HTML |
| `plain-text` | texto UTF-8 | `text` | política explícita para BOM y UTF-8 inválido |

### Trabajo

- Extraer adapters fuera de paquetes particulares.
- Definir metadata de registro y capacidad producida.
- Añadir fixtures pequeños y casos límite.
- Probar determinismo y errores legibles.
- Probar invalidación por cambio de bytes, opciones y versión.

### Entregables

- Directorio `mview/reference/adapters/` o ubicación definitiva equivalente.
- Un módulo, README, fixtures y tests por adapter.
- API de registro mínima para runtimes.

### Ubicación definitiva (decidido)

Los cuatro adapters viven en `carmenlabs.com/public/experimental/adapters/*.mjs`, servidos como
archivos estáticos en `/experimental/adapters/*.mjs` (los enlazan `unified-mview.md` y `llms.txt`).
`mview/viewer` los importa directamente desde ahí. Se descarta moverlos a
`mview/reference/adapters/`: duplicaría el módulo servido públicamente y crearía dos fuentes de
verdad para el mismo código. El registro mínimo de runtime (`id`, `input`, `adapt`) vive en
`mview/viewer/src/adapters.js`, y sus fixtures y tests de determinismo/errores están en
`mview/viewer/test/adapters.test.js` (32 casos, uno por adapter más los cuatro formatos y sus
límites explícitos de la tabla de alcance inicial).

### Criterio de salida

Los cuatro formatos del alcance inicial pueden usarse sin copiar adapters al paquete ni generar código de parseo físico. ✅ Cumplido: cada adapter tiene pruebas de determinismo, encoding inválido y forma normalizada específica.

### Fase 4: runtime y renderer de referencia

### Objetivo

Ofrecer un camino ejecutable común para probar paquetes y renders HTML.

### Trabajo

- Implementar el pipeline `validate → read → adapt → transform → render`.
- Compartir una adaptación entre varias vistas.
- Entregar props de forma consistente.
- Aislar fallos por vista.
- Restringir filesystem y red.
- Definir inyección segura de `window.mview` y política de contenido.
- Exponer refresh sin escritura del paquete.

### Entregables

- Runtime o biblioteca mínima de referencia.
- Renderer HTML de referencia.
- Reporte estructurado de errores por fase y vista.
- Pruebas de refresh, aislamiento y seguridad.

### Criterio de salida

El runtime ejecuta todos los ejemplos y demuestra que una fuente se adapta una sola vez por refresh.

### Estado: cumplido

`mview/viewer` implementa el pipeline completo (`validate → read → adapt → transform → render`),
comparte una única adaptación entre varias vistas (`test/viewer.test.js` — "adapts the source
exactly once per package refresh"), entrega props consistentes (`{ data, settings, source, view,
resources }`), aísla fallos por vista, restringe filesystem/red en el transform (proceso
separado, permisos acotados, sin globals de Node) y en el render (iframe sandbox + CSP), inyecta
`window.mview` de forma segura, y expone refresh sin escribir el paquete (`/api/refresh`,
autenticado con token efímero). Ejecuta los 7 ejemplos y los ejemplos de directorio confirman
además que un archivo no seleccionado nunca se lee.

### Fase 5: paquetes de ejemplo

### Objetivo

Dar a agentes patrones completos que puedan copiar con cambios locales mínimos.

### Ejemplos mínimos

1. CSV con una vista HTML.
2. CSV con dos vistas independientes.
3. JSON con una vista HTML.
4. Markdown con tabla de contenidos y resumen.
5. Texto con métricas simples.
6. Directorio con selección explícita y entradas heterogéneas.
7. Vista que falla sin impedir otra vista válida.

### Requisitos por ejemplo

- Fuente real pequeña.
- `mview.json` válido.
- Transform determinista.
- Render sin dependencias innecesarias.
- Snapshot o assertions del modelo derivado.
- Prueba de refresh sobre copia temporal.
- README con “qué copiar” y “qué reemplazar”.

### Criterio de salida

Un agente puede crear una MView nueva seleccionando el ejemplo más cercano y modificando únicamente manifiesto, transform y presentación.

### Estado: cumplido

Los 7 ejemplos mínimos están implementados bajo `mview/viewer/examples/`, cada uno con
`README.md`, fuente real, `<paquete>.mview/`, `expected/data.json` (generado ejecutando el
pipeline real, no a mano) y `test.mjs`:

| # | Patrón | Carpeta |
|---|---|---|
| 1 y 2 | CSV con una vista / CSV con dos vistas | `examples/csv/` (`pokemons-data.mview`, vistas `stats` + `pokedex`) |
| 3 | JSON estructurado | `examples/json/` |
| 4 | Markdown | `examples/markdown/` |
| 5 | Texto plano | `examples/text/` |
| 6 | Directorio explícito | `examples/directory/` — además prueba que un archivo no declarado en `source.files` (`march.csv`) se ignora aunque exista físicamente |
| 7 | Vista que falla junto a otra válida | `examples/failing-view/` |

`test.mjs` de cada ejemplo sigue el procedimiento de 8 pasos de "Qué debe contener cada ejemplo
implementado" vía el runner compartido `examples/lib/example-runner.mjs` (excepto
`failing-view`, que lo implementa a mano porque una de sus vistas falla por diseño). Construir
estos ejemplos encontró y corrigió un bug real en `transform-runner.js`: los errores lanzados
dentro del `vm.Context` del transform no son `instanceof Error` en el realm del host, así que el
manejo de errores caía a `String(error)` y anteponía `"Error: "` a todo mensaje de fallo de un
transform. Corregido con verificación por duck-typing (`typeof error.message === "string"`).

### Fase 6: experiencia para agentes

### Objetivo

Reducir la creación normal a una guía corta y verificable.

### Trabajo

- Reescribir la página canónica de creación para seleccionar adapter y ejemplo antes de generar código.
- Publicar una tabla “tipo de fuente → adapter → ejemplo”.
- Entregar templates de transform y render HTML.
- Añadir checklist automático de terminado.
- Prohibir explícitamente reimplementar adapters disponibles.
- Hacer que la instrucción inicial cree una sola vista salvo solicitud contraria.

### Entregables

- Guía canónica para agentes.
- Skill opcional alineada con la misma guía.
- Prompt mínimo probado con distintos modelos.
- Evaluación de tasa de éxito y cantidad de archivos/código generado.

### Métricas sugeridas

- Porcentaje de manifests válidos al primer intento.
- Porcentaje de campos inventados respecto de la fuente.
- Líneas de código generadas fuera de transform y render.
- Porcentaje de tareas que reutilizan el adapter correcto.
- Porcentaje de refreshes exitosos después de cambiar la fuente.
- Porcentaje de paquetes que respetan aislamiento de E/S.

### Criterio de salida

La mayoría de los modelos evaluados crea el paquete correcto sin generar un adapter ni modificar la fuente.

### Fase 7: congelamiento de Experimental

### Objetivo

Congelar un contrato Experimental coherente respaldado por implementaciones y pruebas.

### Checklist

- [x] Schema, documentación y ejemplos coinciden.
- [ ] Español e inglés están sincronizados. — diferido a propósito hasta que el contenido esté estable (ver Fase 2).
- [x] Todos los links y rutas públicas funcionan. — verificado con `curl` contra el dev server real, no solo revisando el markdown fuente.
- [x] Todos los ejemplos pasan schema, runtime y refresh. — `pnpm test` en `mview/viewer`, 63/63.
- [x] Los adapters del alcance inicial tienen identificadores estables.
- [x] El renderer HTML cumple la política de seguridad acordada.
- [x] Las fuentes de directorio tienen pruebas de escapes y symlinks.
- [x] Los fallos por vista están aislados.
- [x] No quedan rutas, ejemplos o recursos obsoletos bajo `/experimental`. — se eliminó `carmenlabs.com/experimental/adapters/` (directorio vacío sin referencias). Quedan `app/experimental/viewer/` y `app/experimental/specification/`: dos carpetas de ruta vacías (sin `page.tsx` ni `route.ts`, devuelven 404) que no sirven contenido bajo `/experimental` — no rompen el checklist, pero conviene borrarlas o poblarlas; decisión pendiente del autor.
- [x] `/experimental/schema.json` entrega el schema congelado. — verificado en vivo: `directorySource.required` incluye `files`, no existe `mapping`.

### Estado: cerrado con una excepción conocida

Experimental está congelado y coherente en todo lo que sirve `/experimental`, con una sola
excepción explícita y deliberada: la sincronización español/inglés. Ninguna otra casilla quedó sin
verificar contra el estado real del repo — no contra lo que debería estar, sino releyendo cada
documento, revalidando cada manifiesto de ejemplo, y confirmando cada ruta pública con una
petición HTTP real.

### Criterio de salida

Existe una sola interpretación de MView Experimental y el camino recomendado para agentes está respaldado por implementaciones y pruebas reales.

### Orden crítico

```text
decisiones
    |
    v
schema + tipos
    |
    v
contratos normativos
    |
    +----------------+
    v                v
adapters         runtime/renderer
    |                |
    +-------+--------+
            v
         ejemplos
            |
            v
    guía para agentes
            |
            v
 congelar Experimental
```

Los ejemplos pueden bosquejarse durante las primeras fases, pero no deben presentarse como canónicos hasta que schema, adapters y runtime los validen.
