Contribuer
Comment participer au projet.
Phase actuelle : Phase 1 — Core lecture
Phase 0 (bootstrap Gradle multi-modules, CI, thème M3, navigation, Hilt) est livrée. Phase 1 — lecture du forum (drapeaux, topics, forum, deep links, PostRenderer Compose) est en cours, voir roadmap. Les contributions utiles maintenant :
- Implémenter une issue Phase 1 ouverte (drapeaux, login HFR, écran forum, cache Room…)
- Proposer des features : ouvrir une issue avec le label
feature - Signaler des oublis ou divergences spec/code : skill
/spec-realityou commentaire d’issue - Capturer des fixtures HFR réelles via
hfr-mcp(skill/parse-fixture) - Proposer un nom d’app : voir la page nommage
Environnement de développement
Prérequis
- Android Studio (dernière version stable)
- JDK 21+
- Un appareil ou émulateur Android API 29+
- Un compte HFR (pour tester)
Environnement Docker optionnel
Le chemin de référence mainteneur repose sur ghcr.io/cirruslabs/android-sdk:36@sha256:f9b3ea9ed2b5fc9522adae82c7b4622ab7aa54207ef532c8e615a347dca08f31. L’image a servi à valider :app:assembleDebug pendant #4 et #5, puis a été épinglée et codifiée dans #35.
- Image de base :
ghcr.io/cirruslabs/android-sdk:36@sha256:f9b3ea9ed2b5fc9522adae82c7b4622ab7aa54207ef532c8e615a347dca08f31(manifest list multi-arch vérifiée le 2026-04-20 :amd64+arm64, JDK 21 + Android SDK 36) - Dockerfile : Dockerfile à la racine, volontairement minimal
- Wrapper local :
scripts/docker-dev.sh - Dev container :
.devcontainer/devcontainer.json
Exemples :
# Build debug dans le container de référence
./scripts/docker-dev.sh
# Lancer une commande Gradle arbitraire dans le même env
./scripts/docker-dev.sh ./gradlew lintDebug testDebugUnitTest
Le script monte le repo dans /workspace, persiste les caches Gradle / Android dans .gradle-user/ et exécute le container avec l’UID/GID de l’utilisateur hôte pour éviter les fichiers root-owned sur Linux. En rootless Podman, --userns keep-id est ajouté automatiquement pour garder le mapping d’identité.
Dogfood : installer en parallèle d’une release Play
Pour installer un build local à côté d’une version Redface 2 déjà publiée (alpha / closed testing) sans clobber l’existant, créer un init-script Gradle gitignored sous .gradle-user/dogfood.init.gradle :
allprojects {
afterEvaluate { project ->
if (project.plugins.hasPlugin('com.android.application')) {
project.android.defaultConfig.applicationIdSuffix = '.dogfood'
project.android.defaultConfig.versionNameSuffix = '-dogfood'
project.android.defaultConfig.manifestPlaceholders.put('appLabel', 'Redface 2 dog')
}
}
}
Puis builder :
./scripts/docker-dev.sh ./gradlew :app:assembleRelease \
--init-script .gradle-user/signing/signing.init.gradle \
--init-script .gradle-user/dogfood.init.gradle
L’APK résultant a applicationId fr.forumhfr.redface2.dogfood, un launcher label distinct, et coexiste avec la release prod sur le même appareil. Ne jamais utiliser cet overlay pour un AAB destiné à Play Console — Play attend l’applicationId bare fr.forumhfr.redface2 déclaré dans la console.
Le placeholder ${appLabel} est tracké dans app/src/main/AndroidManifest.xml + app/build.gradle.kts (défault @string/app_name), donc les builds standard sans overlay ne sont pas affectés.
Structure du projet
redface2/
app/ # Point d'entrée, DI, navigation
core/
model/ # Modèles domaine
domain/ # Interfaces de repositories, règles métier
data/ # Implémentations de repositories
network/ # OkHttp, session HFR
parser/ # Jsoup, HTML HFR → modèles + PostContent ; BBCode éditeur en Phase 2
database/ # Room, cache, sync MPStorage
ui/ # Thème, composants partagés, PostRenderer
extension/ # Points d'extension (Phase 4)
feature/
flags/ # Écran d'accueil (drapeaux)
forum/ # Catégories, topics
topic/ # Lecture de topic
editor/ # Reply, edit, FP, création topic
messages/ # MPs, MultiMPs
auth/ # Login
search/ # Recherche
settings/ # Préférences
Gestion des dépendances
Le projet utilisera un Gradle version catalog (gradle/libs.versions.toml) créé en Phase 0 comme source de vérité unique des versions. Choix de stack et versions structurelles (major.minor) documentés dans stack.md — les patches exacts sont résolus dans le TOML au bootstrap et maintenus via Renovate/Dependabot.
Pourquoi pas de tableau de versions exact ici : une doc qui liste kotlin = "2.3.20" dérive en 3 mois. La source unique est le fichier libs.versions.toml du repo, interrogeable aussi via Context7/Docfork (cf. #19) pour générer du code aligné avec les APIs stables courantes.
MCP documentaire optionnel
Pour les contributeurs qui travaillent avec un agent IA, l’outil recommandé par défaut est Context7. Docfork reste un fallback crédible si votre client l’intègre mieux. Aucun des deux n’est un prérequis du projet.
- Context7 (recommandé) : setup officiel via
ctx7 setupavec choix entre mode MCP et CLI + Skills. Documentation d’installation et configuration manuelle : installation Context7. - Docfork (fallback) : setup officiel via
npx dgrep setup --cursorou équivalent--claude/--opencode, avec outilssearch_docsetfetch_doc. Configuration manuelle MCP :https://mcp.docfork.com/mcp. - Règle d’usage : préciser
stable releasedans la requête, et si possible l’ID exact de la lib ou sa version majeure/minor pour éviter les snapshots et pre-releases. - Source of truth : ces MCP servent à vérifier une API actuelle, pas à décider seuls de l’architecture. Les choix de projet restent dans les pages canoniques (
stack,architecture,methodology) et lelibs.versions.toml.
Cas validés côté mainteneur :
- pendant #4, la doc officielle Android sur built-in Kotlin dans AGP 9 a servi à confirmer que
org.jetbrains.kotlin.androidne devait plus être appliqué. La page Migrate to built-in Kotlin indique qu’AGP 9 active Kotlin built-in et que le pluginorg.jetbrains.kotlin.androidn’est plus requis. - pendant #5, Context7 a servi à recaler le setup Navigation 3 (plugin serialization côté app, dépendances stables
navigation3-runtime/navigation3-ui) et à éviter plusieurs faux détails d’API avant implémentation.
Ces vérifications ont été répercutées dans le bootstrap Gradle et le code livré.
Convention par feature
Chaque feature suit la même organisation. Source set : src/main/kotlin/ (pas src/main/java/) — convention uniforme sur tout le projet pour le code Kotlin, alignée avec core/model, core/domain, core/parser. Tests sous src/test/kotlin/.
feature/topic/
src/main/kotlin/fr/forumhfr/redface2/feature/topic/
TopicScreen.kt # @Composable, collecte state + effects
TopicContent.kt # @Composable stateless, previewable (si extrait)
TopicViewModel.kt # MVI ViewModel (Hilt-injected via @HiltViewModel)
TopicUiState.kt # State UI + Intents (consolider dans le même fichier tant que c'est court ; extraire en TopicIntent.kt si > ~80 lignes)
TopicRequest.kt # Paramètre d'entrée du screen, dérivé de la route Nav3 (la NavKey vit dans app/.../navigation/, cf. § ci-dessous)
src/test/kotlin/fr/forumhfr/redface2/feature/topic/
TopicViewModelTest.kt
Convention de nommage MVI retenue (Phase 1, validée sur :feature:topic) :
<Feature>UiStateplutôt que<Feature>State— cohérent avec Compose et avec le wording « UI state » utilisé dans la spec MVI.<Feature>Intent= actions utilisateur internes au ViewModel. Vit dans<Feature>UiState.kttant que la liste est courte ; extraire en<Feature>Intent.ktquand le fichier dépasse ~80 lignes ou quand la sealed hierarchy a plus de 5-6 cas.<Feature>Request= DTO d’entrée du screen, dérivé de laNavKeyNavigation 3 (la clé Nav3 vit côtéapp/dansRedfaceNavigation.ktsous le nom<Feature>Route : RedfaceNavKey— cf. ADR-008). LeRequestest construit par leentryProvider/RedfaceNavHostà partir de la route et passé au<Feature>Screen; toujours dans son propre fichier (contrat externe).- Effects (one-shot side-effects vers la vue : snackbar, navigation programmatique, finish) : à introduire uniquement quand le besoin émerge. Ne pas les anticiper en Phase 1 si la feature n’en a pas besoin (cohérent avec
AGENTS.md§ « Charte anti-derive IA-first » → « Spike avant architecture » et « Le noyau avant l’écosystème »). <Feature>Screenvs<Feature>Route: le call-site stateful (qui résouthiltViewModel()et collecte les flows) s’appelle généralement<Feature>Screen. Une variante<Feature>Routeest acceptable quand le composable doit aussi recevoir des données runtime depuis:app(ex.BuildConfig.VERSION_NAMEdans:feature:flags.FlagsRoute) : le suffixe « Route » signale alors qu’il s’agit du call-site directement attaché à l’entry<…Route>de Compose Navigation 3.
Méthodologie
La méthode canonique du projet est documentée dans Méthodologie et formalisée dans ADR-000.
Cette page décrit comment contribuer ; elle ne redéfinit pas la méthode du projet. Pour une contribution structurante, lire docs/specs/methodology.md puis AGENTS.md.
Style de code
- Kotlin : suivre les conventions officielles
- Compose : suivre les guidelines API
- Nommage : anglais pour le code, français pour les issues et la documentation
- Pas de code commenté : si c’est supprimé, c’est supprimé
- Pas de TODO dans le code : ouvrir une issue à la place
Accessibilité
- Tous les éléments interactifs :
contentDescriptionousemantics { }en Compose - Touch targets minimum 48dp
- Contraste WCAG AA (4.5:1 texte, 3:1 gros texte)
- Support du scaling de police (pas de tailles en
dppour le texte, toujourssp) - Navigation TalkBack : headings sémantiques, custom actions pour les posts
- Lint a11y activé en CI dès Phase 0
Localisation
- Toutes les chaînes UI dans
strings.xmldès Phase 0 (français par défaut) values-en/strings.xmlpour le listing Play Store (anglais)- Pas de strings hardcodées dans les Composables — détecté par lint
- L’app est conçue pour la communauté francophone HFR, mais la structure i18n est en place dès le départ
Workflow Git
- Branche principale :
main - Branches feature :
feature/nom-court - Branches fix :
fix/nom-court - Commits : Conventional Commits (
feat:,fix:,docs:,chore:) - PR obligatoire pour merger dans
main - Review par au moins un mainteneur
Licence des contributions
- Le client Android Redface 2 est publié sous
GPL-3.0-only. - Toute contribution acceptée au code ou aux fichiers de spec du repo est distribuée sous cette même licence.
- Si un futur composant réseau dédié apparaît, sa licence sera tranchée séparément et ne doit pas être présumée depuis celle du client.
Tests
Stack de tests (Phase 0) :
- JUnit 4 — framework de test
- MockK — mocking Kotlin-first
- Robolectric — tests Android sans émulateur
- Turbine — test des
FlowetStateFlow - Compose Testing —
androidx.compose.ui:ui-test-junit4+ui-test-manifestcâblés sur:core:ui(Phase 2F-A #130) pour des tests Compose en JVM via Robolectric. Pattern canonique :createComposeRule(packagev2, l’API que le compilateur Compose pousse en remplacement de la v1 désormais deprecated), mount viacomposeTestRule.setContent { … }UNE seule fois par test, puis drive le state d’entrée (fontScale, mode, …) via unmutableStateOfetwaitForIdle()entre chaque mutation. Exemple canonique :core/ui/src/test/.../post/PostRendererInlineLayoutTest.kt. Important :setContentne peut être appelé qu’une fois par test (host Activity unique) ; les tests paramétrés piloteraient le state Compose plutôt que de re-mount.android.testOptions.unitTests.isIncludeAndroidResources = trueest nécessaire côté module etdebugImplementation(ui-test-manifest)injecte l’AndroidManifest activité-host minimal que la rule lit à l’inflation. Pour les nodes asynchrones (CoilAsyncImage), assumer qu’ils restent sur leur placeholder sous Robolectric — soit injecter un fakeImageLoader, soit asserter uniquement sur la structure layout (TextLayoutResult.placeholderRectsplutôt que le bitmap rendu).
Enforcement au build (Phase 0) :
- Konsist — règles d’architecture (imports inter-modules,
:core:extensionlimité àtopic/editor, tokens M3 centralisés dans:core:ui). Voir architecture.md pour les règles. La règle@AnonymousClientsur prefetch sera activée dès que le code réseau/prefetch existera réellement. - Detekt — style Kotlin + deprecations (
runBlocking,GlobalScope,LiveData, imports dépréciés). - Android Lint — a11y + i18n + correctness.
MissingContentDescription,TouchTargetSizeCheck,HardcodedTextenerror(abort build). ConfiglintOptionsdansbuild.gradle.kts.
CI Phase 0 :
- workflow GitHub Actions sur push
mainet PR - exécution dans le même env Docker de référence, épinglé par digest
- pipeline actuelle :
detektAll,lintDebug,testDebugUnitTest(inclut les checks Konsist),:app:assembleDebug - Dependabot configuré pour
gradleetgithub-actions
Couverture (hybride différenciée) :
- 100% sur les transformers du parser HFR — naturel, fixtures dictent exhaustivité
- Guidée par risque ailleurs (ViewModels, mappers, repositories) — tests sur edge cases réels + fixtures, pas de quota chiffré
- Outil de mesure (Kover) pour info, pas comme gate CI
Stratégie :
- TDD sélectif sur fonctions pures (parser, PostContent AST, ViewModels, helpers, mappers) — red → green → refactor
- Test-after sur intégrations (repositories cache/network, deep linking)
- Pas de TDD sur UI Compose (Compose Preview + review visuelle suffisent ; Roborazzi non retenu en MVP, à reconsidérer Phase 4+ si régressions visuelles multi-features)
Smoke test mensuel HFR (Phase 1 fin) :
Workflow GitHub Actions (cron: '0 2 1 * *', 1er du mois, 2h UTC) qui vérifie contre HFR réel :
- Sélecteurs CSS critiques (
HfrSelectors) matchent toujours - Liste catégories + sous-catégories (
HfrCategories.ALLhardcodée) matche le HTML de la page d’accueil — détecte les ajouts/renommages HFR rares mais impactants
Alerte via issue GitHub auto si diff détecté. Activé dès que HfrSelectors est significatif (~10 entrées) + parser cats codé, typiquement fin Phase 1.
Héritage Redface v1
Le code de Redface v1 contient ~10 transformers de parsing, 17 fixtures HTML et 13 tests. Cette base est reprise comme point de départ :
- Les fixtures HTML servent de référence pour les edge cases du parser HFR
- La logique de parsing (gestion des posts supprimés, pages instables, encoding) est analysée pour ne pas réinventer la roue
- Les edge cases identifiés dans les tests v1 deviennent des cas de test dans v2
Fixtures HTML pour le parser
Le parser HTML est testé contre des fixtures capturées depuis de vraies pages HFR (jamais fabriquées par une IA). Chaque page nécessitant une authentification est capturée en version logué et non-logué quand la distinction est pertinente (contenu différent, champs manquants, redirections).
core/parser/src/test/resources/fixtures/
Reprises de Redface v1 (13 fixtures testées dans app/src/test/resources/ de ForumHFR/Redface, v1 en compte 17 physiquement, 4 non testées) :
| Fixture | Page HFR | Auth ? | Source HFR (exemple à capturer) |
|---|---|---|---|
topic_multipage.html | Topic multi-pages | logué + non-logué | cat=23, post=35395 (>1 page) |
topic_singlepage.html | Topic 1 seule page | non-logué | topic court cat=23 |
topic_posts.html | Posts d’un topic | logué + non-logué | cat=23, post=35395, page=1 |
edit_post.html | Page d’édition d’un post | logué uniquement | message.php?...&numreponse=X sur propre post |
quote.html | Contenu de citation BBCode | logué uniquement | message.php?...&numrep=X |
categories.html | Page d’accueil (catégories) | non-logué | /hfr/ |
topic_list.html | Liste topics d’une sous-catégorie | logué + non-logué | forum2.php?cat=23&subcat=0 |
profile_standard.html | Profil utilisateur standard | non-logué | hfr/profil-<id>.htm (membre) |
profile_admin.html | Profil admin/modo | non-logué | hfr/profil-<id>.htm (modo) |
mp_list.html | Liste des MPs classiques | logué uniquement | forum1.php?cat=prive |
mp_conversation.html | Conversation MP | logué uniquement | forum2.php?cat=prive&post=<mp_id> |
smiley_search.html | Résultats recherche de smileys | non-logué | message-smi-mp-aj.php?search=X |
rehost_response.html | Réponse reho.st | non-logué | reho.st HTML de réponse |
Nouvelles fixtures v2 (pages non couvertes par v1) :
| Fixture | Page HFR | Auth ? | Pourquoi | Source HFR (à capturer) |
|---|---|---|---|---|
flags_page_owntopic-{1,2,3}.html | /forum1f.php (drapeaux) | logué uniquement | Écran d’accueil, pas dans v1 | forum1f.php?owntopic={1,2,3} |
flags_page_empty.html | Drapeaux vides | logué uniquement | Cas edge : aucun drapeau | compte neuf ou nettoyé |
search_results.html | /search.php | logué + non-logué | Recherche | search.php?search=redface |
login_success.html | Réponse login OK | — | Détection succès auth | Après POST login OK |
login_failure.html | Réponse login échoué | — | Détection échec auth | Après POST bad pass |
edit_fp.html | Édition First Post avec sondage | logué uniquement | Distinct de l’édition normale | propre topic avec sondage |
write_reply_form_open_topic.html | Formulaire reply Phase 2A | logué uniquement | Contrat bddpost.php réel | message.php?...&post=35395&page=20&subcat=550 |
write_quote_form_test_post.html | Formulaire quote Phase 2A | logué uniquement | numrep + [quotemsg=...] prérempli | message.php?...&numrep=2784595 |
write_quote_form_bbcode_rich.html | Formulaire quote BBCode riche Phase 2A | logué uniquement | Quote préremplie depuis un post contenant b/i/u/strike/url/fixed/spoiler/img | message.php?...&numrep=2523833 |
write_edit_form_test_post.html | Formulaire edit Phase 2A | logué uniquement | numreponse + contrat bdd.php réel | message.php?...&numreponse=2784595 |
write_create_topic_form_android_cat.html | Formulaire création topic Phase 2A | logué uniquement | sujet, from_subcat, new=0, sous-catégories | message.php?...&cat=23&subcat=550 |
write_reply_anonymous_form.html | Formulaire reply anonyme Phase 2A | non-logué | Composer legacy avec pseudo/password | message.php?...&post=35395&page=20 |
write_create_topic_anonymous_form.html | Formulaire création anonyme Phase 2A | non-logué | Composer legacy avec pseudo/password | message.php?...&cat=23&subcat=550 |
write_edit_success_response.html | Réponse succès edit Phase 2A | logué uniquement | Message succès bdd.php | POST bdd.php?config=hfr.inc |
write_reply_success_response.html | Réponse succès reply Phase 2A | logué uniquement | Message succès bddpost.php | POST bddpost.php?config=hfr.inc |
write_empty_message_error.html | Erreur contenu vide Phase 2A | logué uniquement | Validation HFR, aucun post créé | POST bddpost.php?config=hfr.inc |
write_invalid_token_error.html | Erreur hash_check invalide Phase 2A | logué uniquement | Rejet HFR avant post | POST bddpost.php?config=hfr.inc |
write_antiflood_error.html | Erreur anti-flood Phase 2A | logué uniquement | Plus de 3 réponses consécutives en 10 minutes refusées | POST bddpost.php?config=hfr.inc |
write_locked_topic_page.html | Topic fermé Phase 2A | logué uniquement | Pas de lien reply exposé | topic fermé post=14227 |
write_reply_locked_topic_forced_form.html | Formulaire forcé topic fermé Phase 2A | logué uniquement | message.php sert encore un composer | message.php?...&post=14227 |
write_locked_topic_error.html | Erreur POST topic fermé Phase 2A | logué uniquement | Rejet bddpost.php, aucun post créé | POST bddpost.php?config=hfr.inc |
write_created_owned_topic_page.html | Topic temporaire owned Phase 2A | logué uniquement | Page topic après création + édition FP | topic temporaire cat=10 post=148749 |
write_edit_first_post_form.html | Formulaire edit FP Phase 2A | logué uniquement | sujet, subcat, sondage, delete=1 topic | message.php?...&numreponse=2523829 |
write_edit_first_post_success_response.html | Réponse succès edit FP Phase 2A | logué uniquement | Message succès bdd.php | POST bdd.php?config=hfr.inc |
write_edit_form_bbcode_rich.html | Formulaire edit BBCode riche Phase 2A | logué uniquement | content_form conserve b/i/u/strike/url/fixed/spoiler/img | message.php?...&numreponse=2523833 |
write_quote_success_response.html | Réponse succès quote Phase 2A | logué uniquement | Même succès bddpost.php que reply simple | POST bddpost.php?config=hfr.inc |
write_delete_post_form.html | Formulaire suppression post Phase 2A | logué uniquement | delete=1 libellé Effacer ce message | message.php?...&numreponse=2523830 |
write_delete_post_success_response.html | Réponse succès suppression post Phase 2A | logué uniquement | Message succès bdd.php | POST bdd.php?config=hfr.inc |
write_delete_topic_form.html | Formulaire suppression topic Phase 2A | logué uniquement | delete=1 libellé Effacer l'intégralité du sujet | edit FP avant suppression |
write_delete_topic_success_response.html | Réponse succès suppression topic Phase 2A | logué uniquement | Message succès + refresh vers sous-catégorie | POST bdd.php?config=hfr.inc |
write_deleted_topic_404.html | Topic supprimé Phase 2A | logué uniquement | L’URL du topic supprimé répond HTTP 404 | forum2.php?...&post=148749 |
multimp_conversation.html | MultiMP | logué uniquement | Différent des MPs classiques | MultiMP existant |
topic_with_poll.html | Topic avec sondage | logué + non-logué | Parsing du sondage | topic public avec sondage |
topic_last_page.html | Dernière page (< 40 posts) | non-logué | Pagination edge case | dernière page d’un topic |
topic_deleted_posts.html | Page avec posts supprimés | non-logué | Décalage de numérotation | topic modéré connu |
modo_not_flagged.html | modo.php — formulaire d’alerte | logué uniquement | Redflag : post pas encore alerté | modo.php?numreponse=X |
modo_flagged.html | modo.php — déjà alerté | logué uniquement | Redflag : post alerté | modo.php?numreponse=X (déjà alerté) |
modo_join.html | modo.php — rejoindre une alerte | logué uniquement | Redflag : alerte en cours | modo.php?numreponse=X (alerte ouverte) |
Profil et paramètres (pages editprofil.php, loguées uniquement) :
| Fixture | Page HFR | Contenu | Source HFR |
|---|---|---|---|
profile_settings_p1.html | editprofil.php?page=1 | Infos générales : email, date naissance, sexe, ville, profession, loisirs | compte de test |
profile_settings_p2.html | editprofil.php?page=2 | Infos forum : citation, signature (BBCode), config matérielle | idem |
profile_settings_p3.html | editprofil.php?page=3 | Paramètres : réponses/page, avatars, signatures, thème CSS, jeu d’icônes, langue, fuseau, notifs MP | idem |
profile_settings_p4.html | editprofil.php?page=4 | Déprécié — messageries instantanées (ICQ, MSN). Page existe encore. Fixture capturée pour exhaustivité, aucun test de régression requis. | idem |
profile_settings_p5.html | editprofil.php?page=5 | Gestion d’images : avatar, smileys persos, smileys favoris, wiki smileys | idem |
profile_settings_p6.html | editprofil.php?page=6 | Notifications : mots-clés (max 3) pour alerte par mail/MP à la création de topics | idem |
profile_settings_p7.html | editprofil.php?page=7 | Personnalisation barre d’outils : 15 icônes repositionnables (9 positions + masquer) | idem |
contact_list.html | contactlist.php | Liste de contacts : ajout/suppression, statut en ligne, liens MP | idem |
modo_history.html | modo/historique.php | Historique des sanctions : modérateur, catégorie, date, raison | modérateur test |
Total : ~61 fixtures (13 reprises testées de v1 + 39 nouvelles + 9 profil/paramètres).
Fixtures REST JSON (Phase 1C-A)
À côté des fixtures HTML, le browsing REST (cf. ADR-003) consomme des fixtures JSON capturées live le 2026-05-01. Elles vivent à côté de leurs consumers (DTO + mappers) dans :core:data :
core/data/src/test/resources/fixtures/
├── rest_categories.json # 19 catégories anonymes
├── rest_categories_auth.json # 19 catégories + liens drapeaux (auth)
├── rest_subcategories_cat13.json # 15 sous-catégories de Discussions
├── rest_topics_cat23_subcat550_p1.json # page 1 — Tech Mobiles / Android
├── rest_topic_meta_35395.json # metadata d'un topic isolé
└── rest_cat23_participated.json # un topic en mode authentifié
Mêmes règles que les fixtures HTML : capturées live, jamais inventées, nettoyées des données sensibles avant commit, accompagnées d’un .source.txt qui documente la commande curl d’origine + caveats. Capture via curl direct contre /webservices/rest_api.php?uri=…, avec ou sans cookies selon le mode visé.
Règles :
- Les fixtures sont capturées depuis le vrai site HFR, jamais fabriquées par une IA ou à la main.
- Capture via MCP
hfr-mcp:hfr_read cat=X post=Y page=Z output=path/to/fixture.htmlécrit le HTML brut. - Chaque fixture est accompagnée d’un fichier
.source.txtfrère ou d’un commentaire HTML en tête précisantcat,post,numreponse, date de capture. - Les fixtures loguées ne doivent jamais contenir de cookies, tokens
hash_check, emails, mots de passe, identifiants réels personnels — nettoyer avant commit (voir skill/parse-fixtureétape 9). - Exception contrôlée : les pseudos et profils des comptes de test dédiés publics (
XaTelitte/xatelitte, profil HFR1214571) peuvent rester en clair pour conserver la fidélité parser. Ne jamais appliquer cette exception à un compte personnel non dédié. - Ne pas reformatter les fixtures HTML : elles doivent rester proches de la réponse HFR. Un nettoyage minimal des fins de ligne/trailing whitespace est acceptable pour satisfaire
git diff --check, sans modifier la structure DOM. - Quand un bug de parsing est corrigé, le HTML problématique est ajouté aux fixtures avec un test de non-régression.
- Un smoke test CI mensuel (cf. cron
0 2 1 * *ci-dessus) vérifie que les sélecteurs CSS critiques matchent toujours sur une vraie page HFR publique.
Communication
- Issues GitHub : pour les bugs, features, questions techniques
- Topic HFR : pour les discussions générales avec la communauté (lien à venir)
Attribution
Les contributions sont reconnues dans le CHANGELOG et les release notes. Merci à tous ceux qui participent.