Carmen Labs

Component Contract

The props, responsibilities, and constraints a conforming view component must satisfy.

Component Contract

The component renders the data model the parser produced using the renderer declared in runtime (for example React, Solid, HTML, PDF, or a custom renderer). It is a presentation layer only — it does not fetch, parse, or interpret raw source data itself.

Props

{
  data,       // whatever the parser returned
  settings,   // the manifest's settings, merged with any runtime overrides
  source,     // { path, contentHash } — metadata only
  resources   // resolved paths under resources/
}

React Example

import React from "react";
 
export default function Dashboard({ data, settings }: any) {
  return (
    <div style={{ padding: 24, fontFamily: "system-ui, sans-serif" }}>
      <h1>Sales Dashboard</h1>
 
      <section>
        <h2>Total Sales</h2>
        <strong>
          {settings?.currency ?? "USD"} {data.totalSales.toLocaleString()}
        </strong>
      </section>
 
      <section>
        <h2>Sales by Month</h2>
        <ul>
          {data.salesByMonth.map((item: any) => (
            <li key={item.month}>
              {item.month}: {item.total}
            </li>
          ))}
        </ul>
      </section>
 
      <section>
        <h2>Top Clients</h2>
        <table>
          <thead>
            <tr><th>Client</th><th>Total</th></tr>
          </thead>
          <tbody>
            {data.topClients.map((row: any, index: number) => (
              <tr key={index}>
                <td>{row.Client ?? row.client ?? "Unknown"}</td>
                <td>{row.Total ?? row.total ?? 0}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </section>
    </div>
  );
}

A Solid, HTML, PDF, or other renderer receives the same conceptual model and must satisfy the same constraints; only the renderer-specific syntax or produced artifact changes.

Constraints (MUST / MUST NOT)

  • The component MUST NOT read the source file directly. It only renders data.
  • The component MUST NOT perform arbitrary fetch calls without the runtime granting explicit permission (see Security Model).
  • The component MUST render from data and settings alone — no hidden dependency on global state outside what it's handed as props.
  • The component SHOULD degrade gracefully (empty states, missing fields) rather than crashing the render tree, since data reflects whatever the parser produced from a potentially-changed source file.

Why the split between parser and component

Keeping "understand the file" (parser) and "render the model" (component) as separate, independently regenerable artifacts is what makes an MView survive source file changes without manual rewrites: if the source schema shifts, only the parser needs to be regenerated; if the visual design needs to change, only the component does. See Regeneration for how a runtime should expose "Regenerate Parser" vs "Regenerate Component" as separate actions.

Contrato del componente

Las props, responsabilidades y restricciones que debe cumplir un componente de vista conforme.

Contrato del componente

El componente renderiza el modelo de datos producido por el parser usando el renderer declarado en runtime (por ejemplo React, Solid, HTML, PDF o un renderer propio). Es solo una capa de presentación: no obtiene, parsea ni interpreta datos fuente raw por su cuenta.

Props

{
  data,       // lo que haya devuelto el parser
  settings,   // settings del manifiesto, combinados con overrides del runtime
  source,     // { path, contentHash } — solo metadatos
  resources   // rutas resueltas bajo resources/
}

Ejemplo React

import React from "react";
 
export default function Dashboard({ data, settings }: any) {
  return (
    <div style={{ padding: 24, fontFamily: "system-ui, sans-serif" }}>
      <h1>Sales Dashboard</h1>
 
      <section>
        <h2>Total Sales</h2>
        <strong>
          {settings?.currency ?? "USD"} {data.totalSales.toLocaleString()}
        </strong>
      </section>
 
      <section>
        <h2>Sales by Month</h2>
        <ul>
          {data.salesByMonth.map((item: any) => (
            <li key={item.month}>
              {item.month}: {item.total}
            </li>
          ))}
        </ul>
      </section>
 
      <section>
        <h2>Top Clients</h2>
        <table>
          <thead>
            <tr><th>Client</th><th>Total</th></tr>
          </thead>
          <tbody>
            {data.topClients.map((row: any, index: number) => (
              <tr key={index}>
                <td>{row.Client ?? row.client ?? "Unknown"}</td>
                <td>{row.Total ?? row.total ?? 0}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </section>
    </div>
  );
}

Un componente Solid, HTML, PDF u otro renderer recibe el mismo modelo conceptual y debe cumplir las mismas restricciones; solo cambia la sintaxis o el artefacto producido por el renderer.

Restricciones (MUST / MUST NOT)

  • El componente MUST NOT leer el archivo fuente directamente. Solo renderiza data.
  • El componente MUST NOT realizar llamadas fetch arbitrarias sin que el runtime conceda permiso explícito (ver Modelo de seguridad).
  • El componente MUST renderizar solo desde data y settings: sin dependencias ocultas de estado global fuera de lo que recibe como props.
  • El componente SHOULD degradarse con gracia (estados vacíos, campos faltantes) en lugar de romper el árbol de render, ya que data refleja lo que el parser produjo a partir de un archivo fuente potencialmente cambiado.

Por qué separar parser y componente

Separar “entender el archivo” (parser) y “renderizar el modelo” (componente) como artefactos independientes y regenerables es lo que permite que una MView sobreviva cambios del archivo fuente sin reescrituras manuales: si cambia el esquema de la fuente, solo necesita regenerarse el parser; si debe cambiar el diseño visual, solo el componente. Ver Regeneración para cómo un runtime debería exponer “Regenerar parser” y “Regenerar componente” como acciones separadas.