ONIR CMS
Documentation

Créer un template avec le SDK v0.1.3

Workflow du développeur — de l'initialisation au bundle importable, avec React, Vue ou HTML/CSS/JS. L'API décrite est celle du SDK 0.1.0 livré.

SDK 0.1.3 et générateur publiés sur npm.

Installez le SDK avec npm install @onirtech/template-sdk (ou npm install -D @onirtech/template-sdk pour le build), puis créez un projet avec npx @onirtech/create-onirtech-template aurora --stack react. ADR-0024 est Accepté.

Le SDK supprime le squelette de Custom Element : vous écrivez l'interface (React, Vue ou DOM natif) et le SDK gère le Shadow DOM, la propriété onirtechContext, le rendu à chaque mise à jour, les styles, les erreurs et le nettoyage.

1. À qui s'adresse cette page

ProfilCe que vous écrivezBuild
ReactComposants .jsx + useTemplate()Vite requis
VueSFC .vue + useTemplate()Vite requis
DébutantHTML / CSS / JavaScript avec h()Aucun

Il faut Node.js 18 ou plus et npm. Aucune connaissance des Custom Elements, du Shadow DOM ou du protocole hôte n'est nécessaire.

2. Le workflow en un coup d'œil

# SDK 0.1.2 — générateur inclus :
npx @onirtech/create-onirtech-template aurora --stack react   # ou vue | vanilla

cd aurora
npm install
npm run dev      # développement local (hôte simulé)
npm test         # tests unitaires du template
npm run build    # React / Vue / TypeScript uniquement
npm run validate # manifeste + entrée + intégrité
npm run pack     # archive .onirtech-template
ÉtapeCommandeFonctions
Initialisernpx @onirtech/create-onirtech-template …échafaudage (manifeste, entrée, styles, tests)
Déclarer—defineManifest
Coder—defineReactTemplate + useTemplate / defineVueTemplate + useTemplate / defineTemplate + h + param + hasCapability
Développernpm run devhôte simulé (harness)
Testernpm testcreateMockContext + mountTemplate
Construirenpm run builddefineTemplateConfig
Valider / empaqueternpm run validate puis npm run packkit (manifeste, entrée, SRI)
Importer / rattacher—panneau Templates visuels + liens

3. Initialiser le projet

Commande : npx @onirtech/create-onirtech-template aurora --stack reactFonctions : —

Remplacez react par vue ou vanilla. Le générateur crée un projet complet, avec le nom d'élément déjà cohérent entre le manifeste, l'entrée et les tests.

aurora/
├─ src/
│  ├─ index.js          entrée : enregistre le Custom Element
│  ├─ Aurora.jsx        votre interface (React)
│  ├─ styles.css        styles (inlinés dans le bundle)
│  └─ manifest.ts       manifeste typé (defineManifest)
├─ tests/
│  └─ aurora.test.js    tests unitaires du template
├─ vite.config.ts       build correct pour un template
├─ package.json
└─ onirtech.template.json   généré au build (React / Vue / TypeScript)

Version débutant (--stack vanilla) : pas de vite.config.ts, aucun build, des styles en src/styles.js (constante CSS) et un onirtech.template.json à la place de manifest.ts.

aurora/
├─ src/
│  ├─ index.js          entrée : defineTemplate(...)
│  ├─ ui.js             fonctions de rendu (h, param)
│  └─ styles.js         styles : export const CSS (chaîne, aucun build)
├─ tests/
│  └─ aurora.test.js
├─ onirtech.template.json   manifeste (JSON, pas de build)
└─ package.json

4. Déclarer le manifeste

Commande : —Fonctions : defineManifest

Le manifeste décrit le template : identité, version, nom du Custom Element, capacités (données reçues) et paramètres (variables réglables par lien). defineManifest remplit les valeurs fixes (schemaVersion, kind, network) et vérifie le reste à la compilation.

// src/manifest.ts
import { defineManifest } from "@onirtech/template-sdk";

export default defineManifest({
  id: "aurora",
  name: "Aurora",
  description: "Chat et formulaire clairs, orientés conversion.",
  version: "1.0.0",
  publisher: { id: "onirtech", name: "ONIRTECH", trustLevel: "official" },
  ui: {
    mode: "web-component",
    entry: "./dist/index.js",
    element: "onirtech-template-aurora",
    shadowDom: "open",
  },
  capabilities: ["messages", "theme"],
  params: [
    {
      key: "titre",
      label: "Titre",
      description: "Titre affiché en haut du chat",
      type: "string",
      default: "Discutons de votre projet",
      maxLength: 80,
    },
  ],
});

Version débutant : le même contenu, en JSON, dans onirtech.template.json (avec schemaVersion, kind, compatibility et network explicites).

{
  "schemaVersion": 1,
  "kind": "template",
  "id": "aurora",
  "name": "Aurora",
  "description": "Chat et formulaire clairs, orientés conversion.",
  "version": "1.0.0",
  "publisher": { "id": "onirtech", "name": "ONIRTECH", "trustLevel": "official" },
  "compatibility": { "hostProtocol": "onirtech.template-host.v1" },
  "ui": {
    "mode": "web-component",
    "entry": "./index.js",
    "element": "onirtech-template-aurora",
    "shadowDom": "open"
  },
  "capabilities": ["messages", "theme"],
  "network": [],
  "params": [
    {
      "key": "titre",
      "label": "Titre",
      "description": "Titre affiché en haut du chat",
      "type": "string",
      "default": "Discutons de votre projet",
      "maxLength": 80
    }
  ]
}
  • ui.element doit être identique au nom passé à defineTemplate / defineReactTemplate / defineVueTemplate.
  • params[].default est obligatoire : c'est le repli quand l'exploitant ne renseigne rien.
  • Une capacité non déclarée ⇒ la donnée correspondante est absente/ vide.

5. Construire l'interface

Commande : —Fonctions : defineTemplate / useTemplate

Les trois stacks lisent le même contexte. En React et Vue, useTemplate() renvoie phase, messages, params, capabilities et actions ; en vanilla, le contexte complet arrive dans la fonction de rendu.

React

Commande : aucune (édition) · Fonctions : defineReactTemplate, useTemplate

// src/Aurora.jsx
import { useState } from "react";
import { useTemplate } from "@onirtech/template-sdk/react";

export function Aurora() {
  const { phase, messages, params, actions } = useTemplate();
  const [draft, setDraft] = useState("");

  if (phase === "identity") {
    return (
      <form
        onSubmit={(event) => {
          event.preventDefault();
          const data = new FormData(event.currentTarget);
          actions.submitIdentity({
            nom: String(data.get("nom") ?? ""),
            email: String(data.get("email") ?? ""),
            consentement: data.get("consentement") === "on",
          });
        }}
      >
        <h2>{params.titre}</h2>
        <input name="nom" placeholder="Votre nom" aria-label="Nom" required />
        <input name="email" type="email" placeholder="vous@exemple.mg" aria-label="Email" required />
        <label>
          <input name="consentement" type="checkbox" /> J'accepte le traitement de mes données
        </label>
        <button type="submit">Démarrer la discussion</button>
      </form>
    );
  }

  return (
    <section>
      <h2>{params.titre}</h2>
      <ul>
        {messages.map((message) => (
          <li key={message.id} className={message.role}>{message.content}</li>
        ))}
      </ul>
      <form
        onSubmit={(event) => {
          event.preventDefault();
          if (draft.trim() === "") return;
          actions.sendMessage(draft.trim());
          setDraft("");
        }}
      >
        <input
          value={draft}
          onChange={(event) => setDraft(event.target.value)}
          placeholder="Votre message…"
          aria-label="Message"
        />
        <button type="submit">Envoyer</button>
      </form>
    </section>
  );
}
// src/index.js
import css from "./styles.css?inline";
import { defineReactTemplate } from "@onirtech/template-sdk/react";
import { Aurora } from "./Aurora";

defineReactTemplate("onirtech-template-aurora", Aurora, { css });

import css from "./styles.css?inline" est indispensable : le CSS doit être une chaîne injectée dans le Shadow DOM (le build Vite l'inlinera).

Vue

Commande : aucune (édition) · Fonctions : defineVueTemplate, useTemplate

<!-- src/Aurora.vue -->
<script setup>
import { ref } from "vue";
import { useTemplate } from "@onirtech/template-sdk/vue";

const { phase, messages, params, actions } = useTemplate();
const draft = ref("");

function submitIdentity(event) {
  const data = new FormData(event.currentTarget);
  actions.submitIdentity({
    nom: String(data.get("nom") ?? ""),
    email: String(data.get("email") ?? ""),
    consentement: data.get("consentement") === "on",
  });
}

function sendMessage() {
  if (draft.value.trim() === "") return;
  actions.sendMessage(draft.value.trim());
  draft.value = "";
}
</script>

<template>
  <h2>{{ params.titre }}</h2>

  <form v-if="phase === 'identity'" @submit.prevent="submitIdentity">
    <input name="nom" placeholder="Votre nom" aria-label="Nom" required />
    <input name="email" type="email" placeholder="vous@exemple.mg" aria-label="Email" required />
    <label>
      <input name="consentement" type="checkbox" /> J'accepte le traitement de mes données
    </label>
    <button type="submit">Démarrer la discussion</button>
  </form>

  <section v-else>
    <ul>
      <li v-for="message in messages" :key="message.id" :class="message.role">
        {{ message.content }}
      </li>
    </ul>
    <form @submit.prevent="sendMessage">
      <input v-model="draft" placeholder="Votre message…" aria-label="Message" />
      <button type="submit">Envoyer</button>
    </form>
  </section>
</template>
// src/index.js
import css from "./styles.css?inline";
import { defineVueTemplate } from "@onirtech/template-sdk/vue";
import Aurora from "./Aurora.vue";

defineVueTemplate("onirtech-template-aurora", Aurora, { css });

Débutant — HTML / CSS / JavaScript

Commande : aucune (édition) · Fonctions : defineTemplate, h, param, hasCapability

// src/index.js — aucun build
import { defineTemplate, h, param, hasCapability } from "@onirtech/template-sdk";

function identity(ctx) {
  const nom = h("input", { name: "nom", placeholder: "Votre nom", "aria-label": "Nom" });
  const email = h("input", { name: "email", type: "email", placeholder: "vous@exemple.mg", "aria-label": "Email" });
  const consentement = h("input", { name: "consentement", type: "checkbox" });

  return h("form", {
    onsubmit: (event) => {
      event.preventDefault();
      ctx.actions.submitIdentity({
        nom: nom.value.trim(),
        email: email.value.trim(),
        consentement: consentement.checked,
      });
    },
  },
    h("h2", { text: param(ctx, "titre", "Discutons de votre projet") }),
    nom,
    email,
    h("label", {}, consentement, " J'accepte le traitement de mes données"),
    h("button", { type: "submit", text: "Démarrer la discussion" }),
  );
}

function chat(ctx) {
  const champ = h("input", { placeholder: "Votre message…", "aria-label": "Message" });
  const envoyer = () => {
    if (champ.value.trim() === "") return;
    ctx.actions.sendMessage(champ.value.trim());
    champ.value = "";
  };

  return h("section", {},
    h("h2", { text: param(ctx, "titre", "Discutons de votre projet") }),
    hasCapability(ctx, "media")
      ? h("p", { text: "Photos et liens disponibles" })
      : null,
    h("ul", {}, ...ctx.state.messages.map((message) =>
      h("li", { class: message.role, text: message.content }))),
    ctx.state.typing ? h("p", { text: "…" }) : null,
    h("div", {}, champ, h("button", { type: "button", text: "Envoyer", onclick: envoyer })),
  );
}

defineTemplate(
  "onirtech-template-aurora",
  (ctx) => (ctx.state.phase === "identity" ? identity(ctx) : chat(ctx)),
  { css: `
      :host { display: block; font-family: system-ui, sans-serif; }
      ul { list-style: none; padding: 0; }
      li.user { background: #e2e8f0; }
    ` },
);

En vanilla, les styles vivent dans src/styles.js : le module exporte une constante CSS passée à l'option css. Aucun build n'intervient pour transformer un fichier .css.

  • h(tag, props, ...children) : crée un nœud. text = texte (jamais de HTML), onclick = écouteur, le reste = attributs.
  • param(ctx, "titre", défaut) : lit une variable avec repli.
  • hasCapability(ctx, "media") : teste une capacité avant de lire la donnée.

6. Développer en local

Commande : npm run devFonctions : hôte simulé (harness)

Le harness implémente le côté hôte avec des données MOCK : aucune base, aucun lien, tout est hors-ligne. Pour React/Vue/TypeScript, il lance aussi le build en --watch.

  • http://localhost:4500 — le template dans un faux lien.
  • ?phase=chat — saute le formulaire, va directement à la discussion.
  • ?consent=0 — lien sans consentement.
  • Après un rebuild, rechargez la page (pas de live-reload).

Ouvrez les DevTools et inspectez le Shadow DOM pour vérifier le rendu. Les actions sont simulées : submitIdentity passe en phase chat, une réponse mock arrive,finish clôt la discussion.

7. Tester

Commande : npm testFonctions : createMockContext, mountTemplate

Le scaffolder génère Vitest + happy-dom. Les deux helpers évitent de lancer le harness dans les tests.

// tests/aurora.test.js
import { expect, it } from "vitest";
import { createMockContext, mountTemplate } from "@onirtech/template-sdk/testing";
import "../src/index.js";

it("affiche le titre du paramètre", () => {
  const ctx = createMockContext({ params: { titre: "Album souvenirs" } });
  const { el } = mountTemplate("onirtech-template-aurora", ctx);

  expect(el.shadowRoot.textContent).toContain("Album souvenirs");
});

it("passe du formulaire au chat", () => {
  const ctx = createMockContext();
  const { el, update } = mountTemplate("onirtech-template-aurora", ctx);

  update(createMockContext({
    state: { phase: "chat", messages: [{ role: "assistant", content: "Bonjour" }] },
  }));

  expect(el.shadowRoot.textContent).toContain("Bonjour");
});
  • createMockContext(overrides) produit un contexte complet et conforme (identité ou chat, messages, médias, params).
  • mountTemplate(element, ctx) monte le vrai élément et expose update(ctx) pour simuler une nouvelle assignation du contexte.

Vérifiez au minimum : les deux phases, le consentement, les messages vides, un texte long et chaque paramètre.

8. Construire le bundle

Commande : npm run buildFonctions : defineTemplateConfig

Étape nécessaire pour React, Vue et TypeScript ; inutile en vanilla. La configuration est générée par le scaffolder :

// vite.config.ts
import { defineTemplateConfig } from "@onirtech/template-sdk/vite";

export default defineTemplateConfig({
  entry: "src/index.ts",
  element: "onirtech-template-aurora",
  manifest: "src/manifest.ts",
});

React et Vue passent leurs plugins de compilation via l'option additive plugins (@vitejs/plugin-react, @vitejs/plugin-vue) : defineTemplateConfig({ entry, element, manifest, plugins: [react()] }). vite reste une peer dependency optionnelle.

Ce que la configuration imposePourquoi
un seul fichier ESMle manifeste pointe une entrée unique
CSS non séparé (cssCodeSplit: false)les styles voyagent dans le JS du Shadow DOM
assets inlinésaucune requête externe non déclarée
target: es2022Custom Elements et syntaxes modernes
React/Vue embarqués une seule foispas de dépendance fournie par l'hôte

Résultat : dist/index.js (+ onirtech.template.json émis depuismanifest.ts). Vérifiez le poids : gzip -c dist/index.js | wc -c.

9. Valider, empaqueter, importer

Commande : npm run validate puis npm run packFonctions : kit (manifeste, entrée, intégrité SRI)

  1. npm run validate vérifie le manifeste, l'entrée, le nom d'élément et les paramètres.
  2. npm run pack produit release/aurora-1.0.0.onirtech-template (intégrité incluse).
  3. Dans le dashboard : Templates visuels → importer l'archive.
  4. Sur le lien : choisir le template et la version, renseigner les paramètres, puis Aperçu avec ces valeurs.
  5. Pour une mise à jour : incrémenter version, revalider, repackager, réimporter et sélectionner la nouvelle version sur le lien.

10. Récapitulatif

#ProcessusCommandeFonctionsRésultat
1Initialisercreate-onirtech-template
local ; npx après publication npm
—projet complet
2Déclarer—defineManifestmanifeste typé
3Coder—defineReactTemplate + useTemplate
defineVueTemplate + useTemplate
defineTemplate + h + param + hasCapability
interface
4Développernpm run devharnessaperçu local
5Testernpm testcreateMockContext + mountTemplatetests verts
6Construirenpm run builddefineTemplateConfigdist/index.js
7Validernpm run validatekitconformité
8Empaqueternpm run packkitarchive
9Importerdashboard—version installée
10Rattacherdashboard—template publié

11. Dépannage

L'élément ne s'affiche pas.
Vérifiez que ui.element est exactement le nom passé à defineTemplate (même kebab-case), et que l'entrée est bien dist/index.js après un build.
Mes styles ne s'appliquent pas.
Le CSS doit être importé en ?inline puis passé à l'option css ; un fichier CSS séparé n'est pas chargé dans le Shadow DOM.
params.titre est undefined.
Le paramètre n'est pas déclaré dans le manifeste, ou le harness ne fournit pas de valeurs : utilisez param(ctx, "titre", défaut).
React lève « Invalid hook call ».
Deux copies de React sont chargées : vérifiez que React n'est pas externalisé et qu'une seule version est installée.
Le template change mais le lien ne change pas.
Une version publiée est immuable : incrémentez version, repackagez, réimportez et sélectionnez la nouvelle version sur le lien.
Une donnée du contexte est vide.
Déclarez la capacité correspondante (messages, media, modelForm, theme, userState, knowledge, attachments).

12. Références

  • ADR-0024 — SDK JavaScript des templates visuels (API complète).
  • Contrat des templates visuels — manifeste, contexte, actions, paramètres.
  • ADR-0019 (packages UI-only), ADR-0023 (variables paramétrables).
  • Norme docs/templates-visuels/norme-v1.md.
Passer à la pratique : créez votre premier template.

Le guide Your first package vous accompagne de la création du projet à l'import dans le panneau, avec du code vérifié à chaque étape.

Comprendre le traitement des messages.

Les actions d'un template détaillent l'identité, l'envoi, la fin et les ressources : ce que l'hôte exécute à chaque appel, en mock et en production.

Vous utilisez un agent IA ?

Le skill onirtech-template-sdk fournit à l'agent les règles, l'API réelle, les vérifications et les pièges connus pour créer un template de bout en bout.