Carmen LabsCarmen 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,     // declared source file or directory metadata
  resources   // resolved paths under resources/
}

Props delivery by renderer

The props object is always the same; what changes with runtime.renderer is how the runtime delivers it:

RendererComponent shapeDelivery
react / solidModule default-exporting a component functionProps are passed as the function's argument
htmlStatic .html fileThe runtime MUST inject the props as the global window.mview before the document's inline scripts execute
htmlJS/TS moduleMUST default-export a (props) => string function returning the HTML

A static .html component MUST read its data exclusively from window.mview:

<script>
  const { data, settings } = window.mview;
  // render with data
</script>

It must not invent other globals (__MVIEW_DATA__, __DATA__, etc.) or fetch the source — the data arrives already parsed in window.mview.data. For the html renderer, window.mview is the props handoff — it does not count as "hidden global state" under the constraints below.

Interactivity

A component does not have to be interactive, but it can be:

  • react / solid components may use state and handlers (useState, createSignal, onClick, etc.). A runtime that renders server-side MUST hydrate the component in the browser (client bundle + React's hydrateRoot / Solid's hydrate) for that interactivity to work; otherwise the view stays static.
  • html components ship their own <script> tags and are interactive on their own.

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 any 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.

Why the split between parser and component

Keeping "understand the source" (parser) and "render the model" (component) as separate, independently regenerable artifacts is what makes an MView survive source changes without manual rewrites: if its schema or selection changes, 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,     // metadatos declarados del archivo o directorio fuente
  resources   // rutas resueltas bajo resources/
}

Entrega de props por renderer

El objeto de props es siempre el mismo; lo que cambia según runtime.renderer es cómo lo entrega el runtime:

RendererForma del componenteEntrega
react / solidMódulo que exporta por default una función componenteLas props se pasan como argumento de la función
htmlArchivo .html estáticoEl runtime DEBE inyectar las props como el global window.mview antes de que se ejecuten los scripts inline del documento
htmlMódulo JS/TSDEBE exportar por default una función (props) => string que devuelve el HTML

Un componente .html estático DEBE leer los datos únicamente desde window.mview:

<script>
  const { data, settings } = window.mview;
  // renderizar con data
</script>

No debe inventar otros globals (__MVIEW_DATA__, __DATA__, etc.) ni hacer fetch de la fuente: los datos ya llegan parseados en window.mview.data. Para el renderer html, window.mview es la entrega de props — no cuenta como “estado global oculto” bajo las restricciones de abajo.

Interactividad

Un componente no tiene que ser interactivo, pero puede serlo:

  • Los componentes react / solid pueden usar estado y handlers (useState, createSignal, onClick, etc.). Un runtime que renderiza server-side DEBE hidratar el componente en el navegador (bundle cliente + hydrateRoot de React / hydrate de Solid) para que esa interactividad funcione; de lo contrario la vista queda estática.
  • Los componentes html traen sus propios <script> y son interactivos por sí mismos.

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 (DEBE / NO DEBE)

  • El componente NO DEBE leer directamente ningún archivo fuente. Solo renderiza data.
  • El componente NO DEBE realizar llamadas fetch arbitrarias sin que el runtime conceda permiso explícito (ver Modelo de seguridad).
  • El componente DEBE renderizar solo desde data y settings: sin dependencias ocultas de estado global fuera de lo que recibe como props.
  • El componente DEBERÍA 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 una fuente potencialmente cambiada.

Por qué separar parser y componente

Separar “entender la fuente” (parser) y “renderizar el modelo” (componente) como artefactos independientes y regenerables es lo que permite que una MView sobreviva cambios de la fuente sin reescrituras manuales: si cambia su esquema o selección, 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.