- Retirer toutes les mentions de prix (1,99 €/19,99 €), de Kankwa+ et
de la limite à 3 outils dans les FAQ et comparatifs des landing pages (FR + EN)
- Masquer la section Abonnement dans Account.tsx pour les utilisateurs
non-premium (reste visible pour les abonnés existants)
- Supprimer le lien "Passer à Kankwa+" dans CreateBringModal,
CreateKountModal et TemplateStarterPanel quand la limite est atteinte
Infrastructure Stripe et logique premium conservées intactes pour
réintroduction future.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Audit page par page des landings et vitrines (SEO-LP-23) : cohérence
problématique → solution → preuve, propreté des modules et des CTA,
alignement FR/EN.
- soiree-jeux et sortie-entre-amis annoncent désormais la page de
l'événement et non « le vote » : Kwiz est hors BATCHABLE_SERVICE_TYPES
et ne peut pas être posé vide, le CTA promettait donc une ressource que
le parcours ne crée pas ;
- repas place Kontrib en tête, conformément à son H1, sa problématique et
son CTA qui promettent tous la liste ;
- suppression de la clé morte partage-depenses.modules.kontrib (FR/EN) ;
- dix méta ramenées dans leurs bornes ;
- previews EN localisées : Leeds et le Lake District remplacent Lyon et
Annecy, kount.vitrine.preview_title suit ;
- la priorité sitemap devient une règle de cluster documentée dans
landings.ts plutôt qu'un jugement page par page ; six liens
réciproques portent le minimum entrant à deux.
Nouvelle landing evjf-evg (SEO-LP-24), une seule page pour les deux
intentions comme voyage/vacances-entre-amis. Son angle propre est la
personne fêtée : résultats Kwiz masqués jusqu'à publication et choix des
personnes concernées par chaque dépense dans Kount. La FAQ traite
frontalement l'absence d'encaissement.
La landing mariage est écartée et la décision consignée : sans RSVP,
sans liste d'invités ni encaissement, les deux tâches dominantes de
cette intention restent hors de portée.
Catalogue : 21 landings, 42 URL, 15 cas d'usage Hub, 64 pages pré-rendues.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Avant la migration 0036, COUNT(id) == COUNT(DISTINCT anonymous_id) car
la contrainte permettait au plus une ligne par visiteur+événement. Avec
la colonne `page`, un visiteur peut produire plusieurs landing_view, donc
le funnel comptait des lignes au lieu de visiteurs. Alignement sur
COUNT(DISTINCT anonymous_id) comme la branche filtrée.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ajoute une colonne `page` sur `growth_events` (migration 0036) et étend
la contrainte unique pour permettre un landing_view distinct par page visitée.
Homepage, vitrines et landings SEO envoient désormais leur identifiant de page ;
le dashboard Acquisition affiche le classement des pages d'entrée.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Corrige sept défauts d'accessibilité/design system découverts par la recette
(ink-faint en texte de corps, warning-600 en avant-plan, badge-service hors
ServiceThemeScope, progressbars sans nom accessible, région défilante non
focusable, panneaux sans sémantique de dialogue). Zéro régression (191
Vitest, 8 Playwright dont axe 0 violation critique/sérieuse) et chunk public
inchangé (150,52 kB gzip vs build de référence du HEAD).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Débloque le chantier d'isolation admin (G15). ADM-R2 : la suppression admin
annule désormais l'abonnement Stripe avant d'effacer le compte, sans bloquer
sur un échec fournisseur (journalisé). ADM-R3 : premium_active_clause()
remplace is_premium brut dans les comptages/filtres admin. Ajoute
admin_audit_log (sans FK), la limitation de débit sur /api/admin, le refus
de démarrage sous 32 caractères pour ADMIN_TOKEN, les en-têtes Caddy dédiés,
et la segmentation locale sur growth_events (migrations 0034/0035).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Moving a range to the far right could produce end=24:00, which Python's
time type and PostgreSQL reject. Clamp start to MAX_END - w instead of
TOTAL - w. Test asserts the boundary is respected. Docs: re-evaluation
audit 2026-08-13 post-deployment + ROADMAP/TESTING updates.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New shared component wraps the service-right column with the mobile
one-way reveal card ("Partage et collaboration"). Replaces the ad-hoc
<details> in HubEdit and the plain <div class="service-right"> in all
five service edit pages (Kdo, Kontrib, Kount, Kal, Kwiz). Hub passes
its custom hint (with "activité"); services use the new common.json
collaboration.hint key.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace <details> toggle with a button card that disappears on click,
showing SharePanel/CoOwnerPanel/CommentBlock inline. On desktop (xl+)
the card is hidden and panels are always visible.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
.hub-collaboration-content used flex which overrode <details> native
display:none, keeping content visible even when closed. Switch to
space-y-6 (margin-based gap) so the browser can hide/show content
naturally; xl override stays as display:block !important.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add shareUrl prop to ServicePageHeader, renders ShareButton just left
of the settings icon on all Hub and service edit pages
- Add onShared callback to ShareButton/SharePanel; Hub mobile panel
collapses automatically after a successful share action
- Fix .service-right flex column gap-6 so SharePanel, CoOwnerPanel and
CommentBlock are no longer flush against each other vertically
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add ScrollToTop component in App.tsx that resets scroll position on
every route change (React Router does not do this natively)
- Fix Kal and Kwiz PollEdit pages to read location.state.from instead
of hardcoding /kal and /kwiz — matches the pattern already used by
Kdo, Kontrib and Kount so the back button returns to the originating
Hub when navigating from there
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Le hero remplace la mention technique HTTPS par un libellé générique
en fr/en. Les pages CGU, mentions et confidentialité gagnent pt-6 sur
leur conteneur pour aligner leur titre sur celui des pages services
connectées (ServiceDashboard).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Navbar mobile : espaces réduits (px-2/gap-1 → sm:px-3/sm:gap-1.5), labels
conservés, scrollbar native cachée, gradient fade-out pour signaler le défilement
- Navbar ligne services : fond `surface-inverse` sur shell public (vitrines/landings)
pour une transition continue vers le hero encre ; onglets inactifs adaptés en
`ink-inverse/60` ; `surface-muted` conservé sur le shell workspace
- Panneaux de collaboration (SharePanel/CoOwnerPanel/CommentBlock) : gap-4 → gap-6
- Pages légales (Legal, Privacy, Terms) : `page-editorial` → `page` pour aligner
la largeur de conteneur avec les autres pages du site
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Le contrat mentionnait `page-title`/`section-title`, alors que les rôles sont des
utilitaires issus de la clé `fontSize` : le préfixe `text-` est obligatoire.
Écrite sans lui, la classe n'existe pas et ne fait échouer ni le build ni les
tests — le titre retombe simplement à la taille par défaut. Le piège s'est
déclenché pendant la reprise d'audit G10.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Noms accessibles et rôles ARIA (AUD-A) : Spinner nommé, Alert en alert/status,
champs Login, CommentBlock, BringPublic, ReservationModal et KdoItemForm dotés
d'un nom accessible (WCAG 1.3.5). Une suppression empêchée passe par
aria-disabled et non disabled — un bouton disabled quitte l'ordre de tabulation,
donc la raison de l'empêchement deviendrait inatteignable au clavier. Les deux
menus de la Navbar partagent useDismissableMenu : Échap referme et rend le focus
au déclencheur.
Retour aux tokens sémantiques (AUD-T) : GuestConversionCTA reprend la surface
sable universelle, ErrorBoundary traduit sous garde avec repli français, Cancel
et MagicVerify passent sur .page-sm, Demo et CurrencyInput perdent leurs gris
bruts. Un aplat porteur de texte remonte au cran 700, comme .btn-primary : le
blanc sur primary-600 ne tient pas AA. Les emoji d'icône de Cancel laissent la
place au SVG canonique de service et à sa palette.
Saisie et retours d'état (AUD-U) : le remplissage automatique de KdoItemForm se
dit par fieldset disabled plutôt que par un voile qui bloquait la souris sans
bloquer le clavier ; inputMode="decimal" sur les neuf champs de montant ; le
chargement d'un tableau de bord conserve titre et action derrière un squelette
au lieu d'un point central ; la suppression est confirmée en région d'état.
Doublons et rendu (AUD-C) : dangerouslySetInnerHTML remplacé par Trans sur une
chaîne traduite, dates du compte via formatDate, shim kount/CurrencyInput
supprimé au profit de @shared/components.
Deux constats de l'audit sont écartés après vérification et documentés dans la
roadmap : le tabIndex={-1} de SwipeToDelete est délibéré, et la fermeture
directe de ReservationModal pour un utilisateur connecté est une décision
produit acquise. Le débordement de VoteSelectionGrid reste TODO : un overflow-x
casserait les en-têtes sticky réglés sur --page-header-bottom.
Vérifications : check:i18n vert (2 langues, 10 namespaces), tsc --noEmit vert,
174 tests Vitest verts, vite build vert (chunk initial 149,58 Ko gzip, contre
150,03 Ko en référence).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Un template Hub conseille tous ses modules : n'en marquer qu'un laissait croire
que les autres n'appartenaient pas au cas d'usage choisi, alors que le panneau de
pose en lot les propose tous juste au-dessus. Le badge couvre l'ensemble, plus le
`service=` demandé depuis une vitrine.
Les actions du masthead se répètent d'une ressource à l'autre — réglages partout,
duplication sur le Hub : elles deviennent des icônes nommées groupées à droite du
titre, plutôt que des libellés qui lui disputaient sa largeur. La duplication
passe par la prop `actions` au lieu d'un bouton posé sous la description.
Partager devient un seul contrôle appris une fois : `ShareButton` remplace la
copie du rail de partage et arrive sur les cartes Kal et Kwiz, qui étaient les
deux seules à ne pas l'exposer. `CopyButton` n'avait plus d'appelant. Icône seule
impose un nom accessible : `aria-label` s'ajoute au `title`, et bascule sur « Lien
copié » — l'échange d'icône ne disait rien à un lecteur d'écran.
174 Vitest verts, TypeScript et check:i18n verts.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
La session globale ne se vide pas à la validation (`expire_on_commit=False`) :
un `delete()` Core suivi d'un `add()` ORM laissait les collections déjà chargées
intactes, donc la réponse de `update_poll_config` renvoyait des dates vides alors
que la BDD était correcte. Le poll est désormais expiré avant relecture.
Côté édition, `no_changes_with_responses` n'est plus une erreur générique : la
page recharge le sondage et passe en mode verrouillé, l'état réel des réponses
reçues entre-temps. Un 422 est journalisé avec son détail, sans quoi une
validation refusée ne laissait aucune trace exploitable.
Le calendrier remonte compteur et « vider » sous les dates choisies, où ils
portent sur ce qui est visible, et la page publique cesse de doubler le score
d'une date déjà donné dans le fil de lecture.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Refonte visuelle du produit jusqu'au pilote fonctionnel. Aucune route, API,
permission, étape métier ni limite Premium n'est modifiée.
VIS-00 — anomalies transverses : header et bandeau cookies contenus à 320/390 px,
Kal sorti du terracotta Hub, couleurs brutes remplacées par les tokens.
VIS-01 — tokens arrêtés dans tailwind.config.ts et index.css (sept rôles
typographiques, neutres par rôle, espacements, conteneurs, rayons, élévations,
durées), verrouillés par designTokens.test.ts et démontrés par /style-tile
(noindex, hors sitemap et pré-rendu). Bricolage Grotesque auto-hébergée.
VIS-02 — primitives partagées : btn-quiet/btn-icon, card-notice/interactive/
summary/configuration, badges sémantiques, Field et Badge ; Modal, EmptyState,
MetaChip, ConfirmModal, ProgressBar et ResourceCard migrés aux tokens.
VIS-03 — shells : shellVariant.ts devient la source unique du contexte public /
workspace, une racine de service dépendant aussi de la session. Rail de
collaboration, saisie de code et pages éditoriales factorisés. Anneau de focus
posé une seule fois, btn-primary au cran 700 pour tenir AA.
VIS-04 — Hub pilote : ModuleGroup et ServiceToolTile portent l'assemblage et le
choix d'outil, le démarrage d'un Hub vide devient de l'aide contextuelle, une
seule action dominante par colonne, le type d'événement passe en métadonnée
(emojis retirés des catalogues, EVENT_TYPE_ICONS supprimé) et les formulaires Hub
prennent des libellés persistants. Le champ de partage reçoit un nom accessible.
Validations : check:i18n vert, TypeScript 0 erreur, 119 Vitest (28 fichiers),
Playwright 27 passed, build Docker complet (60 pages SSG, check:marketing et
check:prerender conformes). Chunk initial 148 163 -> 148 690 o gzip pour tout G8.
Le checkpoint « Pilote » reste ouvert : VIS-05 à VIS-07 attendent la revue Hub
desktop et mobile du propriétaire.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
GRW-07. Un template ne se contente plus de conseiller : il peut créer d'un coup
les modules qu'il recommande, sur action explicite de l'organisateur. La règle
« aucun contenu artificiel » reste entière — seuls les titres sont dérivés du
catalogue et du nom du Hub, aucun item, aucune date, aucun participant n'est
inventé.
- `services/hub/modules.py` + `POST /hub/{event_id}/modules` : le lot est
atomique, `enforce_premium_capacity` réserve la capacité pour l'ensemble
(même brique que la duplication GRW-03), donc un template ne se pose jamais
à moitié. Le backend ne connaît pas le catalogue SEO : il valide une liste de
`service_type` contre `BATCHABLE_SERVICE_TYPES`, aucun registre dupliqué ;
- `TemplateStarterPanel` remplace le bloc statique de l'onboarding : cases
pré-cochées jusqu'à la capacité gratuite restante, suivantes visibles mais
désactivées avec le lien Kankwa+. Sept des treize templates proposent quatre
modules pour une limite de trois : le coût est montré avant l'action plutôt
qu'un échec sur le dernier outil ;
- Kwiz reste hors du lot : `KwizPollCreate.options` exige au moins deux
options, un sondage ne peut pas être créé vide. `serviceMetadata.starter`
porte cette distinction, alignée sur la constante backend.
Validation : 51 Pytest PostgreSQL, dont un test vérifiant l'absence de
`KalDate`/`KontribItem` créés, l'unique membre Kount et le refus en bloc d'un
lot de quatre pour un compte à 2/3 ; 65 Vitest ; `check:i18n`, `tsc --noEmit`
et build Docker (SSG 60 pages + `check:prerender`) verts.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Complète GRW-06 : `/hub/my-contributions` couvre désormais les six types de
participation, Kal et Kwiz compris. Le repli par email rattrape une action
faite en invité avant l'inscription, sur les cinq services partageables.
- migration `0029` : `kontrib_declarations.participant_email`, aligné sur le
contrat Kdo — l'adresse n'est stockée que pour un invité, un utilisateur
connecté reste rattaché par `user_id` ;
- rapprochement uniquement sur une adresse `email_verified`, en minuscules, et
court-circuit des requêtes Kount/Kal/Kwiz sans adresse vérifiée pour ne pas
produire un `IS NULL` qui rattacherait les participations anonymes ;
- chaque montant porte sa `currency` et le Hub formate via `formatCurrency()` :
le `€` codé en dur affichait une mauvaise devise pour un groupe en CHF ;
- couleurs des badges dérivées de `serviceThemes.ts` plutôt que de tokens
sémantiques réattribués à la main.
Validation : 50 Pytest PostgreSQL verts, dont deux tests couvrant les six types,
la casse de l'email, l'exclusion d'un tiers, la coupure sur email non vérifié,
les devises et le contrat invité/connecté de la déclaration Kontrib.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Dix landing pages bilingues (20 URL) reposent sur un template unique piloté
par configuration : landings.json déclare chemin par langue, cluster, service
dominant, modules, maillage et article associé ; SeoLanding.tsx rend tout le
reste depuis les catalogues i18n. App.tsx, Navbar.tsx, le pré-rendu, le
sitemap et les contrôles SEO lisent cette source unique.
Le catalogue landing devient un namespace i18n différé (LAZY_NAMESPACES /
loadNamespace) pour ne pas peser sur le chunk initial : 146,18 → 147,37 Ko
gzip au lieu de 167,01. Le pré-rendu attend désormais #root h1 afin de ne pas
figer l'écran d'attente dans le HTML statique.
check-prerender vérifie le graphe JSON-LD, le CTA, les comparatifs et
l'unicité des og:title ; check-i18n compare les tableaux élément par élément ;
tests/test_sitemap.py verrouille les URL et hreflang des landings.
Preuves : 52 pages SSG validées (kankwa-frontend:g6-landings), Vitest 43
passed, Pytest PostgreSQL 42 passed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>