Files
photobooth-qr/docs/decisions.md
T
KevinLecou 8568b48461 Ajoute la création d'événement via n8n et refond la DA en cartes papier
- Nouvelle Edge Function create-event + RPC public.create_event : génère
  slug/admin_token/expire_at et insère l'event, réservée à service_role,
  appelée par le workflow n8n "Créer un événement" (Form Trigger, publié).
- Passe DA "polaroid poussé à fond" : les cartes d'état (chargement/erreur/
  succès), la modale de bienvenue et le formulaire passent en papier clair
  avec ruban washi tape et léger tilt, plutôt qu'en carte sombre sur fond
  sombre (ombre invisible dans l'ancienne version). Admin resté sobre/sombre
  mais cartes plus lisibles (liseré coloré, contraste renforcé).
- Générateur QR et carton imprimable : lisent maintenant ?slug= et
  ?baseUrl= pour se pré-remplir depuis le lien renvoyé par create-event.
- Correctifs : icône manquante sur "Télécharger" (admin), bug [hidden]
  anticipé sur l'état vide de la galerie invité.
2026-09-18 12:16:25 +02:00

16 KiB

Journal des décisions

Ce fichier consigne les décisions structurantes actées pour le projet, avec la date à laquelle elles ont été prises. Toute nouvelle décision structurante (schéma DB, auth, hébergement, etc.) doit être ajoutée ici après validation par Kévin.

2026-09-18 (six)

  • Limitation connue (pas un bug à corriger) : sur /e/OS + navigateur DuckDuckGo, le bouton "Prendre une photo" (capture="environment") ouvre la galerie au lieu de l'appareil photo, et l'inverse pour "Depuis la galerie". Vérifié : le HTML/JS est conforme à la spec (wiring correct), c'est DuckDuckGo Android qui n'honore pas fiablement l'attribut capture. Décision : ne rien coder de spécifique pour ce navigateur (combo /e/OS + DuckDuckGo trop marginal côté invités réels, qui arrivent très majoritairement via Safari/Chrome par défaut) — à revérifier par Kévin sur iPhone/Android dès qu'il en a sous la main.
  • Événement de test créé en conditions réelles via le workflow n8n + create-event (slug test-da-verification-kevin) : premier passage bout-en-bout du pipeline complet (n8n → Edge Function → Telegram), fonctionnel. Confirme aussi que le déploiement de la passe DA "polaroid poussé à fond" (voir entrée précédente) rend bien en conditions réelles sur le domaine de prod, pas seulement en local — modale de bienvenue et état vide de la galerie vérifiés par capture d'écran sur https://photobooth.assistantaikev.eu.
  • Déployé sur le VPS (scp direct des 7 fichiers modifiés vers /home/kevin/photobooth-qr/site/, pas de rebuild nécessaire — site statique servi par nginx).

2026-09-18 (cinq)

  • 2e itération de la 4e passe DA, plus radicale : retour Kévin sur la version "rim light + grain" ("franchement c'est nul", captures à l'appui après avoir vérifié moi-même dans Chrome) — l'effet était bien trop subtil, l'app restait un grand vide noir avec une petite carte sombre illisible. Changement structurel plutôt qu'un réglage plus fort du même hack : les cartes "props" côté invité (bloc de chargement/erreur/succès, modale de bienvenue, formulaire prénom+message) passent en papier clair (#FAF6F0, encre #12100E) posé sur la table sombre, avec un ruban "washi tape" en haut de carte et une légère inclinaison (-2.2°/+1.8° selon l'état) — comme un vrai polaroid, plutôt qu'un panneau UI sombre. Une carte claire sur fond quasi noir projette une vraie ombre (--shadow-paper), contrairement à une carte sombre sur fond sombre : ça résout le problème à la racine plutôt qu'en le maquillant.
  • Bug technique corrigé au passage : l'inclinaison ne s'affichait pas du tout au premier essai — une animation qui touche transform (fade-rise) remplace toute la propriété, elle ne fusionne pas avec un rotate() posé à côté en statique. Fix : rotation intégrée dans les keyframes (state-card-in), avec repli statique sous prefers-reduced-motion: reduce.
  • Écran admin (galerie + générateur QR) : reste sombre/sobre, sans tilt ni tape (exception du skill DA conservée), mais --color-card légèrement éclairci (#1E1B18#241F1A) et liseré coloré plein en haut de carte (plus un fond teinté translucide) pour rester repérable — même diagnostic que côté invité (contraste carte/fond trop faible pour se voir), remède différent (sobre, pas de papier). Le générateur QR (src/admin/qrcode/) n'avait quasiment aucune mise en forme avant cette passe (champs nus sur le fond, aucun regroupement visuel, police "General Sans" référencée mais jamais chargée dans ce fichier) : plus gros écart identifié lors de l'audit, corrigé (carte de formulaire, police chargée, contraste champs/carte).
  • Vérifié visuellement cette fois : captures d'écran via Chrome headless local (google-chrome --headless=new --screenshot=...) sur les 5 écrans
    • la modale de bienvenue forcée visible temporairement pour vérification — pas juste une relecture de CSS. Contrastes WCAG AA recalculés pour les nouvelles combinaisons papier (encre/muted sur #FAF6F0) : ≥5:1 sur tous les textes ajoutés.
  • Pas testé avec un vrai événement (formulaire rempli, grille de photos garnie, écran de succès) faute de slug valide en local — seuls les états vides/erreur ont pu être vérifiés en conditions réelles de rendu.

2026-09-18 (quater)

  • 4e passe DA "Polaroid Néon" : suite au retour "ça fait vraiment low-cost, il faut me rendre cette application sexy", audit écran par écran (upload, galerie invité, admin galerie, générateur QR, carton imprimable). Diagnostic : ce n'était pas la direction elle-même (conservée intégralement, aucune valeur de palette changée) mais des défauts d'exécution — ombres noires invisibles sur fond quasi noir (#12100E), fond en aplat sans texture, pastille de marque non généralisée à tous les écrans, icône manquante sur le lien "Télécharger" admin. Corrigé via : liseré clair ("rim light") sur les cartes/boutons, grain de fond en SVG data URI inline (~350 octets, 0 requête réseau), pastille corail derrière le logo sur tous les écrans, icône ajoutée au lien de téléchargement admin. Bug [hidden] masqué par un display auteur (déjà rencontré 3 fois sur ce projet) anticipé et corrigé au passage sur #empty-state (galerie invité, passé en display: flex). Contrastes WCAG AA revérifiés, aucun n'a changé (aucune couleur de texte/ fond modifiée). Non vérifié visuellement dans un navigateur avant cette entrée (pas d'accès Chrome dans l'environnement) — à prévisualiser en local avant déploiement.

2026-09-18 (ter)

  • create-event déployée en prod sur kevin-lecou-hub (migration 20260918100000_create_event_rpc.sql + Edge Function supabase/functions/create-event/). Vérifié après coup : grants limités à service_role (n'apparaît pas dans les fonctions SECURITY DEFINER exécutables par anon/authenticated, contrairement à get_public_event/insert_public_photo qui le sont intentionnellement). Un search_path manquant sur la fonction interne photobooth.generate_unique_slug (signalé par le linter sécurité Supabase juste après déploiement) a été corrigé dans la foulée (20260918101500_harden_generate_unique_slug_search_path.sql).
  • Le workflow n8n photobooth-qr — Créer un événement reste inactif : à activer par Kévin une fois le DNS git.assistantaikev.eu et un premier test réel du formulaire validés.

2026-09-18 (bis)

  • Création d'événement : via un formulaire n8n (Form Trigger), plutôt qu'un formulaire dans l'appli photobooth-qr ou du SQL manuel. Le workflow insère l'event en base (génère slug + admin_token) et renvoie les liens invité/admin à Kévin. Choisi pour rester cohérent avec la stack n8n déjà en place et éviter d'ajouter une nouvelle couche d'authentification "admin produit" dans l'appli (aujourd'hui il n'existe que des tokens par événement, pas de compte admin global).
  • Pas de personnalisation visuelle par événement : la DA "Polaroid Néon" reste identique pour tous les événements ; seules les infos textuelles varient par event (nom, date, limite de photos). Pas de marque blanche (palette/logo par client) en V1 — jugé de la sur-ingénierie pour un besoin V2/V3 pas encore confirmé par un client réel.
  • Remote Git : Gitea auto-hébergé sur le VPS existant (plutôt que GitHub). Cohérent avec la préférence d'autonomie de Kévin — tout reste chez lui, pas de dépendance à un service tiers pour le code source. Le VPS héberge déjà n8n (Traefik/SSL en place) ; Gitea tournera comme service Docker supplémentaire derrière le même reverse proxy. Détails d'implémentation (sous-domaine, ressources, sauvegarde) à définir avant déploiement.

2026-09-18

  • Galerie invité (revirement) : les invités peuvent désormais voir toutes les photos de l'événement (pas juste envoyer la leur), dans le cadre de la nouvelle DA "Polaroid Néon". Ça inverse la décision du cahier des charges initial ("Pas de galerie visible côté invité, confirmé : upload only"). Conséquence RGPD : le texte de mention légale (docs/rgpd.md + modale de bienvenue) doit indiquer que photo/prénom/ message sont visibles par les autres invités de l'événement, pas seulement mis à disposition de l'organisateur — mis à jour dans la foulée. Implémentation : backend (nouvelle RPC/Edge Function publique de listing + URLs signées pour les invités) pas encore fait à cette date, seul le rendu visuel est en cours.
  • Nouvelle direction artistique "Polaroid Néon" actée, voir .claude/skills/photobooth-da/SKILL.md pour le détail complet (palette, typographie, composant polaroid signature). Remplace les 3 passes DA précédentes (palette corail/prune/doré + Fraunces) — celles-ci restent en prod tant que la nouvelle direction n'est pas implémentée écran par écran.
  • Stack confirmée inchangée : vanilla JS/HTML/CSS, pas de framework (React proposé dans un brief externe, décliné — mandat CLAUDE.md déjà explicite sur ce point, aucune décision contraire prise).

2026-09-16 (récap fin de journée)

Produit passé de "code écrit, rien de déployé" à réellement en ligne et testé : https://photobooth.assistantaikev.eu (VPS Hostinger, nginx + Traefik), backend sur le projet Supabase définitif kevin-lecou-hub.

  • Bugs réels trouvés et corrigés en cours de route (tous via tests en conditions réelles, pas en théorie) :
    • MIME type .mjs mal servi par nginx (bloquait tout le JS qui importe un module vendorisé — upload invité ET générateur QR étaient cassés).
    • Chemins relatifs (style.css, config.js, gallery.js) cassés sur les URLs réécrites /e/<slug> et /admin/<slug> — passage en chemins absolus partout.
    • Bug de cascade CSS présent depuis le tout premier commit : display: flex déclaré en dur sur .upload-form/.welcome-modal empêchait hidden (JS) de masquer quoi que ce soit visuellement — le formulaire est resté affiché en permanence, superposé aux autres états, sans que personne ne s'en rende compte avant un test réel.
    • Faille d'abus sur insert_public_photo (spam de lignes sans upload réel) — corrigée, vérification d'existence du fichier Storage ajoutée.
  • Nouvelle fonctionnalité : limite de photos par invité par événement (max_photos_per_guest), appliquée côté serveur via un guest_id anonyme (localStorage) — pas une identité vérifiée, contournable, mais fait respecter la vraie limite côté base.
  • RGPD : mention légale déplacée d'un bloc permanent vers une modale de bienvenue personnalisée (nom de l'événement) au premier passage par événement — texte légal inchangé, juste la présentation.
  • Direction artistique : 3 passes (palette sobre → palette punchy corail/prune/doré/Fraunces → mode sombre + finition animations). Statut au 2026-09-16 soir : jugée "pas mal, vraiment fonctionnel" par Kévin, mais couleurs/DA pas encore validées définitivement — Kévin prévoit un retour détaillé à l'agent direction-artistique lors d'une prochaine session.
  • Reste ouvert : activation du workflow n8n de purge Storage (credentials à créer côté Kévin), documentation de création d'un événement (encore manuel en SQL), remote Git à définir.

2026-09-15

  • Expiration des données : suppression à J+7 à partir de la date de l'événement (date_evenement), et non à partir de la date de création de l'événement dans le système.
  • Sécurité admin (V1) : accès à la galerie admin via un token secret dans l'URL (admin_token), pas de système de login/mot de passe. Réévaluer ce choix avant V2/V3 si le nombre de clients augmente.
  • Suppression des données : effective à J+7 dans tous les cas, y compris si l'organisateur a déjà téléchargé le ZIP d'export des photos.
  • Hébergement : utilisation du VPS existant de Kévin (148.230.114.175), pas de nouvel hébergement dédié pour l'instant.

2026-09-16

  • Compression image côté client : utilisation de la lib browser-image-compression, plutôt qu'une implémentation maison via Canvas API. Doit gérer l'orientation EXIF et les formats HEIC (iPhone).

2026-09-15 (bis)

  • Préfixage des tables : le projet Supabase de photobooth-qr sera mutualisé avec les autres projets de Kévin (KOMI, KAZA...). Tables préfixées pq_ (pq_events, pq_photos), conformément à la convention du CLAUDE.md global. Les fonctions Postgres exposées au front (get_public_event, event_accepts_uploads) restent sans préfixe : ce sont des points de contrat public, pas des tables internes.
  • Bucket Supabase Storage event-photos : bucket privé (pas de lecture publique). Convention de chemin : {event_id}/{uuid}.{ext}. Consultation admin exclusivement via URLs signées générées par l'Edge Function service_role. Aucune policy SELECT/UPDATE/DELETE publique sur storage.objects. Limité à file_size_limit = 6 Mo et allowed_mime_types = image/jpeg (aligné sur la sortie de compression du front, qui force tout en JPEG y compris les HEIC convertis).
  • pq_photos.url_storage : contrainte CHECK garantissant que le chemin commence par event_id/, pour empêcher qu'une ligne de l'event A pointe vers un fichier de l'event B (isolation multi-tenant), trouvé lors de la relecture sécurité du 2026-09-15.
  • get_public_event : renvoie l'événement même si son statut n'est pas actif (expiré/archivé), pour permettre au front d'afficher un message adapté. Risque jugé faible : les slugs sont partagés via QR code, donc non secrets par nature.

2026-09-16 (quater)

  • Limite de photos par invité : nouvelle colonne photobooth.events.max_photos_per_guest (NULL = illimité par défaut), réglable uniquement en SQL à la création de l'event pour l'instant (pas d'UI admin dédiée). Appliquée via un guest_id anonyme généré et persisté côté client (localStorage) — pas une identité vérifiée, contournable en changeant d'appareil ou en vidant le storage. Fait respecter côté serveur (RPC insert_public_photo, source de vérité) et côté client de façon proactive (évite un envoi pour se le faire refuser à la dernière étape).

2026-09-16 (ter)

  • Domaine de production (V1) : photobooth.assistantaikev.eu, sous-domaine du domaine déjà utilisé pour n8n. Décision explicitement temporaire : à déplacer vers un domaine dédié en V2/V3 si le produit est vendu sous sa propre marque. Front servi par un container nginx sur le VPS existant (Traefik/Let's Encrypt déjà en place pour n8n), config dans deploy/. APP_BASE_URL configurée comme secret sur les 2 Edge Functions pour le CORS. Déploiement testé en conditions réelles le 2026-09-16 (upload + galerie admin fonctionnels depuis le domaine public).

2026-09-16 (bis)

  • Déclenchement de la purge Storage à J+7 : option n8n retenue (plutôt que pg_cron+pg_net). Un workflow n8n planifié quotidien appelle l'Edge Function expire-events avec la service_role key (Credentials Store n8n), avec notification Telegram en cas d'échec. Cohérent avec la stack n8n déjà utilisée pour KOMI/KAZA, et le VPS n8n existant sert de point d'orchestration commun. Conception du workflow : n8n/workflows/purge-storage-j7.json (JSON importable, non testé sur l'instance n8n réelle, credentials à relier manuellement avant activation).