#Les absents
product, cart, collection, customer, checkout : ces objets n’existent
pas dans un thème Webcosa. Cette page dit pourquoi, ce qui se passe si tu les
écris quand même, et par quoi les remplacer. Elle est écrite pour quelqu’un qui
arrive de Shopify — c’est là que le format est le plus familier, donc c’est là
que l’écart surprend le plus.
#La règle, en une phrase
Webcosa fait des sites vitrines, pas des boutiques. Le format des thèmes est celui de Shopify parce qu’il est bon, documenté depuis dix ans, et entouré d’outils qui existent déjà. Le modèle de données, lui, est celui d’un site professionnel : un site, des pages, un menu, des articles, des réglages. Il n’y a pas de catalogue derrière, donc il n’y a pas d’objet catalogue devant.
#Pourquoi ne pas les exposer vides
C’est la question qui mérite une vraie réponse, parce que la réponse paresseuse
existe : on pourrait déclarer product comme un objet vide, cart comme un
panier à zéro article, et laisser les thèmes Shopify s’installer sans erreur.
Ce serait pire, pour trois raisons qui se cumulent.
Un thème écrit contre une API qui ne rend jamais rien paraît fonctionner. La page rend, le HTML sort, aucune erreur n’est levée. Le vide ne se voit qu’à l’écran, et souvent pas tout de suite : une grille de produits vide ressemble à une grille pas encore remplie. On découvre le problème en production, chez le client, une semaine après la mise en ligne.
Un objet exposé devient un contrat. La liste des objets de
src/lib/liquid/contexte.ts porte cette phrase en tête : tout ce qui y figure
devra continuer d’exister, sous ce nom et cette forme, tant que des thèmes s’en
servent. Exposer product par confort aujourd’hui, c’est s’interdire de choisir
librement à quoi ressemblera un produit le jour où Webcosa en aura.
Une absence franche se diagnostique en une minute. Un développeur qui écrit
{{ product.title }}, voit du vide, ouvre cette page et lit « il n’y en a pas »
a perdu une minute. Le même développeur devant un product vide mais présent
cherche du côté de ses données, de sa requête, de son cache — et perd une
après-midi.
C’est la même logique que le refus des .js dans assets/ ou que la liste
fermée des dossiers : on préfère un refus net à une permissivité qui se paie plus
tard. Voir le bac à sable.
#Ce qui se passe si tu les écris
Rien de spectaculaire, et c’est précisément le problème. Le moteur tourne avec
strictVariables désactivé : une variable absente rend une chaîne vide.
<h2>{{ product.title }}</h2>
<p>{{ product.price | money }}</p>
{% for p in collection.products %}
<article>{{ p.title }}</article>
{% endfor %}<h2></h2>
<p></p>Un titre vide, un prix vide, une boucle qui ne tourne pas. Aucune erreur, aucun commentaire HTML, rien dans les journaux.
Un tag inconnu, lui, fait échouer toute la section. {% form %},
{% paginate %} et {% style %} viennent de Shopify et ne sont pas enregistrés :
la section entière est remplacée par un commentaire. Voir
la page des tags.
Deux symptômes, deux causes : un bloc vide signale un objet absent ; un bloc disparu signale un tag absent.
#L’inventaire
#Le commerce
| Ce que tu cherches | État | Ce qu’il faut faire |
|---|---|---|
product, variant | absent | une section avec des blocs, un par produit ou prestation |
collection | absent | une section par famille, ou des blocs typés |
cart, line_item | absent | lien vers la boutique existante du client |
checkout, order | absent | hors périmètre : Webcosa ne prend pas de paiement |
customer | absent | il n’y a pas de compte visiteur sur un site vitrine |
shop | absent | c’est site, avec nom et adresse |
money, money_with_currency | filtre inexistant | écris le prix dans un réglage text |
img_url, image_url | filtre inexistant | l’adresse d’image est déjà complète |
#Le contenu
| Ce que tu cherches | État | Ce qu’il faut faire |
|---|---|---|
blog, article | partiellement | articles existe — un tableau plat, en lecture seule |
linklists | absent | menu, et il n’y en a qu’un |
pages | absent | il n’y a pas de liste des pages du site |
page_title, page_description | absent | page.titre ; les métadonnées sont posées par la plateforme |
template | absent | page.accueil dit si l’on est sur l’accueil |
request, canonical_url | absent | page.adresse et site.adresse |
routes | absent | écris les chemins en clair : /contact, /blog |
content_for_header | absent | la plateforme pose elle-même ses balises |
content_for_layout | présent | dans layout/theme.liquid uniquement |
#La traduction et les fichiers
| Ce que tu cherches | État | Ce qu’il faut faire |
|---|---|---|
locales/*.json, filtre t | absent | écris le texte en français, ou passe-le en réglage |
{% javascript %} | tag inexistant | il n’y a pas de JavaScript de thème |
{% stylesheet %} | tag inexistant | assets/theme.css |
{% style %} | tag inexistant | une balise style HTML dans la coquille |
{% form %} | tag inexistant | une balise form HTML qui poste vers /api/contacts |
{% paginate %} | tag inexistant | articles est déjà plafonné à douze entrées |
metafields | absent | un réglage de section, ou un bloc |
sections/*.json, {% sections %} | absent | la coquille appelle {% section %} une par une |
#Ce qu’il faut faire à la place
#Un catalogue de prestations : des blocs
Le besoin réel derrière product sur un site vitrine, c’est presque toujours
« montrer six prestations avec un titre, une description et un prix ». Ça ne
demande pas un catalogue : ça demande une section à blocs, que le client remplit
depuis l’éditeur.
<div class="cartes">
{%- for bloc in section.blocks -%}
<article class="carte">
<h3>{{ bloc.settings.nom }}</h3>
<p>{{ bloc.settings.description }}</p>
{%- if bloc.settings.prix != blank -%}
<span class="carte-prix">{{ bloc.settings.prix }}</span>
{%- endif -%}
</article>
{%- endfor -%}
</div>Le prix est un réglage text, pas un nombre formaté par un filtre money : le
client écrit « à partir de 45 € » ou « sur devis », ce qu’aucun formateur de
monnaie ne saurait produire. Sur un site vitrine, c’est presque toujours la bonne
réponse.
Le détail de la déclaration est sur la page des blocs.
#Un formulaire : du HTML ordinaire
{% form %} n’existe pas, et il n’est pas nécessaire. Le formulaire poste vers
/api/contacts, la route enregistre la demande, l’envoie par e-mail, puis
redirige vers la page avec ?demande=merci — que le thème lit dans
page.demande.
{%- if page.demande == 'merci' -%}
<p class="message message-ok" role="status">
Merci, votre message est bien arrivé. Nous vous répondons vite.
</p>
{%- endif -%}
<form class="formulaire" method="post" action="/api/contacts">
<label class="champ">
<span>Votre nom</span>
<input type="text" name="nom" autocomplete="name" required>
</label>
<input type="text" name="piege" tabindex="-1" autocomplete="off" aria-hidden="true" class="piege">
<input type="hidden" name="provenance" value="{{ page.titre }}">
<button type="submit" class="bouton">{{ section.settings.cta_label }}</button>
</form>Deux détails de cet extrait valent d’être repris tels quels. Le site n’est pas
déduit d’un champ caché mais du nom de domaine : un champ se modifie dans
l’inspecteur, un nom d’hôte non. Et le champ piege, invisible pour un humain,
est rempli par les automates : une demande qui le porte est acceptée en apparence
et jetée en silence — informé de son échec, un robot réessaie.
#Une vraie boutique : un lien
Si le client vend en ligne, il a déjà une boutique quelque part — Shopify, WooCommerce, Etsy. La bonne réponse est un lien depuis le site vitrine, pas une tentative de refaire un tunnel d’achat dans un thème qui n’a ni panier ni paiement.
{%- render 'bouton', label: section.settings.cta_label, href: section.settings.cta_href -%}Le libellé et l’adresse sont deux réglages text de la section : le client change
de plateforme, il change une adresse dans l’éditeur, personne ne rouvre le thème.
#Des styles : la feuille et les variables
{% stylesheet %} et {% style %} n’existent pas. Tout le CSS d’un thème vit
dans assets/theme.css, et ce qui doit varier passe par des variables CSS posées
dans une balise style de la coquille.
{{ 'theme.css' | asset_url | stylesheet_tag }}
<style>
:root {
--encre: {{ settings.encre }};
--accent: {{ settings.accent }};
--accent-clair: {{ settings.accent | teinte: 82 }};
--largeur: {{ settings.largeur }}px;
}
</style>C’est le schéma des quatre thèmes livrés : les réglages deviennent des variables
CSS, ici et nulle part ailleurs. Aucune section ne connaît une couleur, elles
héritent toutes de :root. Une section qui écrirait style="color: {{ … }}" en
ligne casserait ce contrat et rendrait le thème impossible à retoucher d’un bloc.
#Et demain ?
La liste s’allongera si un besoin réel le demande, jamais par symétrie avec Shopify. Ajouter un objet est facile et ne casse rien ; en retirer un casse tous les thèmes qui s’en servaient. C’est cette asymétrie qui dicte la prudence, pas une réticence de principe.
Ce qui ne bougera pas : les objets qui existent aujourd’hui garderont leur nom et leur forme. Un thème écrit maintenant continuera de rendre.

