Documentation technique par extension

Réglages, composants Astro et propriétés, variables CSS de thème, tâches planifiées, hôtes sortants autorisés et limites mesurées sous le plafond de 10 appels EmDash.

  • EmDash ≥ 1.2.0 < 2.0.0 (1.4.0 recommandé)
  • eventual · attendance · eventual-capacity
  • eventual-tickets
  • @vermeulen.solutions/eventual
  • 0.18.0 (14 nov. 2026, MIT ; registre EmDash)

Eventual RSVP – Core

Événements, lieux et organisateurs dans les collections natives EmDash, dates récurrentes, flux publics et vues calendrier sans JavaScript.

Installation et prérequis EmDash

Requiert EmDash >=1.2.0 <2.0.0 (testé sur 1.2.0 et 1.4.0 ; EmDash 1.4.0 est recommandé car il corrige les formulaires Block Kit imbriqués et les mutations de plugins sur Cloudflare). Les outils de compilation source requièrent Node 24+.

  1. Installez Eventual RSVP – Core depuis le registre d’extensions EmDash (éditeur vermeulen.solutions). Un paquet npm pour les installations par astro.config.mjs suivra dans une prochaine version.
  2. Dans Event settings, reliez vos collections de contenu EmDash pour events, venues et organizers (les schémas peuvent être exportés via scripts/export-native-schema.mjs).
  3. Laissez Afficher les anciens événements enregistrés dans l’extension (legacyReads) désactivé, sauf pendant une migration depuis 0.12.x. Les collections natives restent la source de vérité pour les titres, textes enrichis, images, brouillons et traductions.
  4. Notez l’identifiant installé du plugin : les sites configurés par code utilisent généralement eventual, tandis que les installations depuis le registre reçoivent un identifiant déterministe r_… affiché dans Admin → Extensions. Passez toujours l’identifiant réel aux URL d’API et à l’option pluginId.

Routes publiques et assistants Astro

Route Méthode et paramètres Rôle
/_emdash/api/plugins/<pluginId>/publicEvents GET (from, through, locale, strict, category, publicUrls, eventId) Fenêtre inclusive jusqu’à 366 jours (par défaut d’aujourd’hui UTC à +180 jours). publicUrls=false évite les appels de résolution d’URL canonique lorsque votre site génère ses propres liens.
/_emdash/api/plugins/<pluginId>/calendar GET (locale, strict, category) Abonnement brut text/calendar iCalendar (aujourd’hui UTC + 365 jours), UID stables, SEQUENCE monotone et statuts STATUS:CONFIRMED / TENTATIVE / CANCELLED.
/_emdash/api/plugins/<pluginId>/publicEventImage?eventId=... GET (Cache-Control: public, max-age=3600, must-revalidate) Diffuse l’image mise en avant publiée après vérification MIME et taille ; retourne 404 pour les brouillons.

Le module eventual/astro fournit fetchPublicFeed, <EventList showCalendarLinks={true} /> (zéro JavaScript côté navigateur), sept mises en page via paramètre d’URL (?view=list|timeline|cards|schedule|dates|month|locations), buildCalendarLinks (Google Calendar, Outlook Web, Office 365 et téléchargement direct .ics Apple/bureau) ainsi que eventToJsonLd et serializeJsonLd (Schema.org). Ces outils arrivent avec le paquet npm de Core, qui suivra dans une prochaine version ; d’ici là, un site lit les mêmes données depuis la route publique publicEvents.

  • strict=true masque les événements dépourvus de traduction dans la langue demandée.
  • strict=false (par défaut) se replie sur une autre langue avec un préfixe [FR] / [EN].
  • À partir de 0.15.0 sur EmDash >=1.4.0, les réponses portent des métadonnées de dépendance et d’expiration de cache opt-in (durée résiduelle plafonnée à 15 secondes, sans servir de contenu périmé).
  • À partir de 0.16.0, imageMedia expose les dimensions natives et les clés de stockage directes (/_emdash/api/media/file/<clé>) tout en conservant imageUrl.

Réglages, permissions et limites

Domaine Détail
Capacités déclarées media:read, media:bytes:read, content:read, content:write, content:publish, content:revisions:read, schema:read, hooks.content-policy:register
Hôtes HTTP sortants (allowedHosts) [] (aucun appel réseau sortant)
Outils MCP en lecture listEvents, getEvent, listOccurrences, listVenues, listOrganizers, getSettings, updateSettings (toujours inclure defaultTimezone lors des mises à jour)
Grands calendriers et limites sandbox Partage le plafond par défaut d’EmDash : 10 appels hôte / 50 ms CPU par invocation. Copies d’occurrences/exceptions bornées à 500 éléments ; exports paginés par curseur de 5 à 10 éléments (garde-fou explicite à 10 000 enregistrements) ; le bundle plugin.mjs pèse 130 690 octets dans la limite de 128 Kio du registre EmDash.

Dépannage (Core)

  • Les titres affichent [EN] ou [FR] de façon inattendue : vérifiez que les traductions natives sont publiées et reliées dans EmDash, ou passez strict=true au flux public.
  • Le flux public renvoie une erreur après mise à jour 0.14.0+ : les lectures publiques refusent volontairement de retomber sur l’ancien stockage interne. Reliez votre collection events dans Event settings.
  • Installation depuis le registre introuvable sous /eventual/… : remplacez eventual par l’identifiant r_… affiché dans l’administration EmDash.
  • @vermeulen.solutions/eventual-attendance
  • 0.1.0 (14 nov. 2026, MIT ; npm et registre EmDash)

Eventual RSVP – Registrations

Configuration, réglages, composants Astro, variables CSS --eventual-*, e-mails, tâches planifiées, contrat de confiance et limites d’Eventual RSVP – Registrations.

Configuration et réglages de Registrations

Eventual RSVP – Registrations lit les événements publiés depuis les collections EmDash (aucune donnée des participants ne quitte votre base). Par défaut, les inscriptions sont fermées sur chaque événement jusqu’à ce que celui-ci soit publié, que inventory_mode vaille rsvp (ou reste absent) et que Ouvrir les inscriptions soit activé dans le panneau Registrations de l’éditeur.

Page globale des réglages (/attendance/settings)

  • Collections contenant des événements : liste séparée par des virgules (par défaut events, par exemple camps, school_series) ; la première sert de valeur par défaut.
  • Les réponses aux e-mails vont à (facultatif) : adresse par défaut pour les réponses et les avis d’approbation (New request: …), surchargeable par événement.
  • Envoyer un rappel ce nombre d’heures avant l’événement (0 = pas de rappel) : 24 par défaut.
  • Langue des e-mails quand le formulaire ne la précise pas : repli en ou fr lorsqu’une inscription n’enregistre pas de langue d’interface.
  • Chemin de la page de gestion : URL publique de la page obligatoire d’annulation et de réclamation (par défaut /rsvp/manage).
  • Durée de conservation (retentionDays) : fenêtre d’effacement automatique après la fin d’une séance passée (30–3650 jours, 365 par défaut).
  • Configuration du site / Turnstile (panneau replié) : clés site et secrète Cloudflare Turnstile stockées dans le KV du plugin (aucune clé EMDASH_ENCRYPTION_KEY requise), plus boutons pour vérifier et réparer les tâches planifiées.

Panneau par événement (event-attendance)

  • La capacité par date (0 = illimité) et le commutateur de liste d’attente (allow_waitlist) s’éditent dans le formulaire de l’événement lorsque les champs eventual-capacity sont appliqués, ou directement dans le panneau sinon.
  • Nombre maximal de places par inscription (maxQuantity), Approuver chaque inscription vous-même (requireApproval, 1–720 heures pour décider, 48 par défaut) et Questions du formulaire d’inscription sous forme de lignes : question, type de réponse en mots, choix seulement pour une liste, réponse obligatoire ; enregistrer, supprimer et déplacer vers le haut.

Composants et propriétés (@vermeulen.solutions/eventual-attendance/astro/*)

Composant & import Propriétés principales Comportement
RsvpForm.astro eventId, occurrenceStart, collection?, maxQuantity?, title?, allowWaitlist?, apiEndpoint? (défaut /_emdash/api/plugins/eventual-attendance/rsvp), formEndpoint? (${apiEndpoint}/form), nameLabel?, emailLabel?, locale? ('en' | 'fr') Émet l’événement DOM eventual:availability { full, seatsLeft, allowWaitlist }. Affiche directement à l’écran le lien « Gérer ou annuler cette inscription » (#token=…) lorsqu’il a pu être généré, et ne mentionne l’e-mail que si le serveur répond email: "sent" | "queued". Requiert JavaScript (D14).
RsvpManage.astro apiBase? (défaut /_emdash/api/plugins/eventual-attendance), locale? ('en' | 'fr') Placé sur /rsvp/manage. Lit le jeton HMAC signé dans le #fragment de l’URL, appelle POST manage/lookup puis n’exécute cancel ou waitlist/claim qu’après clic explicite afin que les scanners de liens de messagerie ne puissent pas modifier une inscription.

Personnalisation visuelle (--eventual-*)

Les styles des composants se trouvent dans la couche CSS @layer eventual. Avec Tailwind CSS 4, placez eventual avant components dans votre déclaration de couches :

@layer theme, base, eventual, components, utilities;

Ou thèmez de façon indépendante des couches via variables CSS sur un conteneur englobant <RsvpForm> et <RsvpManage> :

Catégorie Variables CSS prises en charge
Couleurs --eventual-primary, --eventual-primary-hover, --eventual-on-primary, --eventual-bg, --eventual-field-bg (champs texte et <select>, blanc par défaut), --eventual-border, --eventual-text, --eventual-text-muted, --eventual-danger, --eventual-success, --eventual-success-bg, --eventual-warning, --eventual-warning-bg, --eventual-error, --eventual-error-bg
Formes & focus --eventual-radius, --eventual-control-radius, --eventual-button-radius, --eventual-focus, --eventual-focus-ring

Sur un thème sombre, définissez toujours --eventual-text et --eventual-field-bg ensemble pour éviter un texte clair sur un fond de saisie blanc par défaut.

E-mails, fuseaux horaires et tâches planifiées

Tous les e-mails destinés aux visiteurs (confirmation, mise en liste d’attente, offre retenue 2 h, avis d’approbation et rappels) sont rédigés dans la langue enregistrée lors de l’inscription (fr ou en) avec l’heure formatée dans le fuseau IANA de l’événement via formatInZone (repli UTC explicite) et un lien direct .ics vers la route calendar.

Tâche planifiée Fréquence Travail par exécution
waitlist-maintenance * * * * * (chaque minute) Expire jusqu’à 2 offres dépassées de 2 h et envoie ~1 nouvelle offre à tour de rôle entre séances (~1 offre/min).
attendance-reminders * * * * * (chaque minute) Envoie jusqu’à 3 rappels par minute (~180/heure) dans la fenêtre reminderHours, au maximum une fois par personne confirmée.
attendance-mail * * * * * (chaque minute) Vide jusqu’à 3 confirmations mises en file lorsque l’envoi direct n’entrait plus dans le budget de 10 appels.
attendance-approvals */5 * * * * (toutes les 5 min) Refuse automatiquement jusqu’à 2 demandes dont le délai de décision est échu, libère les places et envoie les avis organisateur en attente.
ledger-reconcile */10 * * * * (toutes les 10 min) Recalcule 1 registre de places inactif depuis la période de calme à partir des fiches enregistrées.
attendance-retention 17 * * * * (toutes les heures) Efface noms, e-mails, réponses aux questions et empreintes IP des séances passées au-delà de retentionDays.

Sécurité, permissions et limites pour grands événements

  • Capacités déclarées : content:read, email:send, network:request (allowedHosts: ["challenges.cloudflare.com", "cdn.jsdelivr.net"] : Cloudflare uniquement si vous activez Turnstile ; jsDelivr pour les paquets de langue vérifiés par empreinte).
  • Protection anti-abus : champ pot de miel et contrôle de durée sur chaque formulaire. Lorsque Turnstile est désactivé et qu’EmDash transmet l’IP, Registrations limite à 10 inscriptions par période de 10 minutes via une empreinte HMAC ipHash (32 caractères hexadécimaux — jamais l’IP brute).
  • Limites documentées (sous la limite standard EmDash de 10 appels / 50 ms CPU) :
    • Le rapprochement automatique d’une séance analyse au plus ~500 fiches (participants + liste d’attente) ; au-delà, il consigne TOO_LARGE et ignore jusqu’à découpage par date.
    • Les exports liste d’accueil HTML/CSV sont plafonnés à 700 lignes avec mention en tête ; filtrez par événement et par date pour les séances plus vastes.
    • Aucune limite artificielle dans le code, aucune clé de licence et aucune clé EMDASH_ENCRYPTION_KEY requise.

Dépannage (Registrations)

  • Aucun e-mail ne part : vérifiez qu’une extension d’envoi d’e-mails EmDash est installée. L’inscription reste enregistrée (email: "none") et affiche le lien d’annulation directe à l’écran.
  • Les rappels ou offres d’attente ne s’envoient pas : ouvrez Admin → Registrations une fois pour réenregistrer les 6 tâches, et vérifiez que le déclencheur cron de votre hébergeur Cloudflare est actif.
  • Route not found sur une installation registre : reprenez le préfixe r_… exact indiqué dans Site setup et passez-le à <RsvpForm apiEndpoint="…" /> et <RsvpManage apiBase="…" />.
  • Erreur publique TRY_AGAIN : consultez les journaux EmDash ; la collection d’événements n’est probablement pas encore raccordée.
  • @vermeulen.solutions/eventual-tickets
  • 0.1.0 (bientôt disponible, licence commerciale)

Eventual RSVP – Tickets

Documentation complète d’Eventual RSVP – Tickets : Stripe Checkout, catégories de billets, paiement sur facture, espace commande avec code QR, contrôle à l’entrée, variables --et-*, licence hors ligne et limites.

Prérequis de configuration et chiffrement

Eventual RSVP – Tickets ne vend des billets que sur les événements publiés dont le champ inventory_mode (issu d’eventual-capacity) vaut explicitement tickets. Registrations et Tickets ne peuvent donc jamais vendre la même séance en double.

  1. Billets gratuits et paiement sur facture fonctionnent sans clé : la clé de signature interne (state:signing-key) est créée automatiquement dans le KV du plugin sans EMDASH_ENCRYPTION_KEY.
  2. Paiement par carte (Stripe Checkout) : définissez EMDASH_ENCRYPTION_KEY dans votre environnement (npx emdash secrets generate ou wrangler secret put EMDASH_ENCRYPTION_KEY) afin de chiffrer en AES-GCM votre clé secrète Stripe (sk_test_…, sk_live_… ou clé restreinte rk_…) et votre secret de signature webhook (whsec_…).
  3. Dans Admin → Tickets, consultez la liste de contrôle, copiez l’URL de webhook affichée vers Stripe, abonnez-la à checkout.session.completed, checkout.session.async_payment_succeeded, checkout.session.async_payment_failed, checkout.session.expired et charge.refunded, puis collez vos secrets ainsi que votre clé de licence evt1.… (sur domaine de production).

Types de billets, tarifs et parcours d’achat

Dans le panneau latéral Tickets de l’éditeur (event-tickets), créez jusqu’à 20 types de billets sous forme de lignes (nom, prix en devise ou Gratuit, plafond optionnel par date dans la capacité globale, maximum par commande de 1 à 50, fenêtre de vente), des lignes de questions et les instructions Proposer le paiement par facture ou virement (≤ 2000 caractères texte). Renommer un type conserve son identifiant et ses ventes.

  • Retenue de 30–31 minutes : Stripe impose au minimum 30 minutes d’expiration (S-1). Tickets fixe expires_at à +31 min, réserve les places par compare-and-set atomique et vérifie amount_total et currency sur les octets bruts du webhook (stripe-signature).
  • Paiement sur facture : bloque les places aussitôt (pending_invoice), envoie les coordonnées bancaires avec la référence (ord_…) et le lien de commande, puis délivre les billets QR lorsque vous cliquez sur Mark paid.
  • Abandon ou délai expiré : <TicketCheckoutReturn /> appelle checkout/release sur annulation (result=cancelled) ou vérifie l’état Stripe sur retour (result=success) ; en secours, la tâche tickets-hold-sweep interroge Stripe avant de libérer une retenue échue.

Composants Astro (@vermeulen.solutions/eventual-tickets/astro/*)

Composant Propriétés principales Emplacement & rôle
TicketCheckout.astro eventId, occurrenceStart, apiBase? (défaut /_emdash/api/plugins/eventual-tickets), title?, locale? ('en' | 'fr'), class? Sélecteur de billets sur la fiche événement, choix carte/facture, questions et identifiant d’idempotence commandId.
TicketCheckoutReturn.astro apiBase?, backHref? (défaut '/'), locale?, class? Page de retour Stripe (/tickets/checkout par défaut) : interroge checkout/status après paiement ou appelle checkout/release après abandon.
TicketOrder.astro apiBase?, checkinPath? (défaut '/tickets/check'), locale?, class? Espace acheteur (/tickets/order) : lit #order=…&token=…, génère les codes QR côté navigateur (qrcode-generator), affiche le code de secours et gère l’annulation avant échéance.
TicketCheckin.astro apiBase?, eventId?, occurrenceStart?, locale?, class? Page de contrôle à l’entrée (/tickets/check) : ouverte par l’équipe connectée à EmDash (content:edit_any) ; scan caméra (BarcodeDetector selon navigateur), lecture du lien QR ou saisie du code hexadécimal 64 caractères.

Personnalisation visuelle (--et-*)

Les classes .eventual-tickets-widget, .et-*, .etr-*, .eto-* et .etc-* lisent vos variables personnalisées sur un élément parent :

  • Couleurs : --et-primary, --et-primary-hover, --et-on-primary, --et-bg, --et-field-bg, --et-border, --et-text, --et-muted, --et-success, --et-success-bg, --et-warning, --et-warning-bg, --et-error, --et-error-bg, --et-info-bg.
  • Rayons & focus : --et-radius, --et-control-radius, --et-button-radius, --et-focus, --et-focus-ring.
  • Définissez ensemble --et-text et --et-field-bg sur un thème sombre.

Clé de licence hors ligne, e-mails, tâches et limites

  • Vérification hors ligne (D17) : signature Ed25519 via Web Crypto dans le Worker (zéro appel réseau sortant). Les hôtes de développement et d’aperçu (localhost, 127.0.0.1, [::1], *.localhost, *.workers.dev, *.pages.dev) ne nécessitent aucune clé. Sans clé valide en production, seules les nouvelles commandes sont bloquées (LICENCE_REQUIRED / LICENCE_EXPIRED) ; les commandes existantes, webhooks, pages de billets, contrôle à l’entrée, exports CSV et remboursements restent pleinement actifs.
  • Langues (fr et en) et fuseaux : les confirmations, instructions de facture et remboursements utilisent formatInZone dans le fuseau local de l’événement. Le panneau de l’événement, la page Commandes et la page de configuration suivent la langue de l’administration EmDash (français ou anglais) ; les verdicts de contrôle restent en anglais et les libellés de la barre latérale viennent du manifeste anglais.
  • Capacités & hôtes sortants : content:read, email:send, network:request limité à ["api.stripe.com", "challenges.cloudflare.com", "cdn.jsdelivr.net", "licences.eventual.rsvp"] (paquets de langue via jsDelivr ; le service de licences seulement quand un administrateur clique sur Activer, Actualiser ou Désactiver).
Tâche planifiée Fréquence Rôle & limite
tickets-hold-sweep * * * * * Vérifie jusqu’à 2 sessions pending échues par minute auprès de Stripe avant de libérer les places.
tickets-auto-refund */5 * * * * Rembourse automatiquement via Stripe les paiements sans place (PAID_NO_SEATS) ou au montant divergent (AMOUNT_MISMATCH, activé par défaut).
tickets-reconcile */10 * * * * Rapproche 1 registre inactif par cycle jusqu’à ~700 commandes par date (TOO_LARGE au-delà, sauf découpage par date).
tickets-retention 23 * * * * Efface noms, e-mails, réponses et empreintes IP au-delà de retentionDays (30–3650, 365 par défaut), tout en conservant montants, devise, lignes et identifiants Stripe comptables.
  • Export CSV des commandes : limité à ~700 commandes par téléchargement avec neutralisation anti-injection de formules (= + - @) ; filtrez par événement et date pour les saisons plus importantes.

Dépannage (Tickets)

  • Paiement par carte grisé : vérifiez EMDASH_ENCRYPTION_KEY, la clé secrète Stripe et le secret webhook dans Admin → Tickets. Les billets gratuits et virements restent disponibles sans clé Stripe.
  • Un remboursement Stripe n’apparaît pas encore dans Commandes : attendez le webhook charge.refunded ou vérifiez l’URL et le secret du webhook dans Admin → Tickets.
  • Pas de lecteur caméra sur certains navigateurs : la prise en charge de BarcodeDetector dépend du navigateur ; l’équipe peut toujours scanner le QR avec l’appareil photo standard du téléphone (qui ouvre /tickets/check#code=…) ou saisir le code de 64 caractères.
  • @vermeulen.solutions/eventual-capacity
  • 0.1.0 (14 nov. 2026, MIT ; sur npm pour la commande des champs, intégrée aux extensions)

eventual-capacity (Bibliothèque partagée)

Registre sans survente partagé, garde-fou de 10 appels hôte, pack de champs de capacité, fuseaux horaires et harnais de test.

Architecture : code partagé, zéro état partagé

@vermeulen.solutions/eventual-capacity est une bibliothèque utilitaire pure, pas une extension EmDash. Elle ne déclare ni manifeste, ni table, ni route, ni permission. Registrations et Tickets l’intègrent sous devDependencies afin que l’outil EmDash l’inclue dans chaque bundle tout en conservant l’isolation totale de chaque stockage KV/SQLite (P3, P16).

Modules fournis

Module Rôle principal
budget RpcBudget, BudgetSlice, logWithin : maintient chaque requête publique et tâche planifiée sous le plafond par défaut de 10 appels hôte d’EmDash (y compris ctx.log comptabilisé sur Cloudflare).
ledger & holds Registre de places par compare-and-set atomique (reserveSeats, holdSeatsForOffer, holdSeats, adjustLedger, freeSeats), sous-quotas par billet dans la limite globale et file d’attente FIFO stricte.
inventory inventoryModeOf, isRsvpManaged, isTicketManaged, poolKeyOfEvent : garantit un seul propriétaire par événement pour écarter toute double vente.
field-pack CAPACITY_FIELDS, capacityFieldPack, mergeCapacityFields : ajoute proprement inventory_mode (rsvp | tickets), capacity (entier) et allow_waitlist (booléen) au schéma Core.
time formatInZone, zonedParts, validTimeZone, eventTiming : résout l’horaire d’une date depuis start/end/all_day/timezone (repli start_date/end_date) et formate en heure locale IANA (repli UTC).
tokens & secret Jetons HMAC-SHA256 préfixés par usage, comparaison en temps constant, liens #fragment et génération locale de state:signing-key.
questions, ics, turnstile, reconcile, schedules Analyseur et validateur de questions d’inscription, générateur .ics RFC 5545, vérification Turnstile siteverify, rapprochement CAS en période calme et auto-réparation des tâches (ensureSchedulesOnce).
testing @vermeulen.solutions/eventual-capacity/testing : withBudget et expectWithinBudget pour mesurer le nombre exact d’appels pont sur createPluginRuntimeTestHost.

Export du pack de champs

Appliquez toujours les champs de schéma de façon additive et contrôlée :

npx -p @vermeulen.solutions/eventual-capacity eventual-capacity-fields --merge eventual-schema.json --output eventual-schema-with-capacity.json

Omettez --merge pour imprimer uniquement les trois champs (inventory_mode, capacity, allow_waitlist) et les comparer à votre collection existante.

Isolation et conception sous contrainte

  • Budget de 10 appels et 50 ms CPU (P1) : toutes les routes publiques, webhooks et tâches planifiées sont conçus pour fonctionner sans demander à l’hébergeur d’augmenter les plafonds Worker.
  • Aucun appel sortant propre à la bibliothèque : la vérification Turnstile n’est appelée que lorsqu’une extension consommatrice lui transmet ses clés configurées.
  • Preuves de test vérifiées : parcours navigateur Playwright, navigation au clavier seul, audits d’accessibilité WCAG 2.2 AA axe-core, captures d’e-mails en français/anglais et tests de concurrence sur le véritable hôte d’exécution EmDash.