Roadmap
Phases de développement, de la fondation au polish.
Vue d’ensemble
Les phases sont ordonnées par dépendances techniques, pas datées. Le rythme réel dépend des contributeurs et des dépendances externes (ex. MPStorage2 dans hfr-redkit à finaliser avant la Phase 3). Cohérent avec la méthodologie triple-hybride (prototype-driven).
Pour la liste des capabilities et des non-goals, voir le scope fonctionnel.
Dashboard des phases
| Phase | Objectif | Taille | Dépend de | Statut |
|---|---|---|---|---|
| 0 — Bootstrap | Squelette qui compile, CI, thème, navigation | S | — | ✅ Livrée |
| 1 — Core | Lecture du forum (drapeaux, topics, forum, deep links) | XL | Phase 0 | ✅ Livrée (AAB 0.1.0-phase1.7 / app-v38 / specs v0.8.4) |
| 2 — Écriture | Post / edit / quote / create topic / recherche / proxy alpha | L | Phase 1 | En close-out |
| 3 — Messages | MPs classiques + MultiMPs avec sync | M | Phase 2 + MPStorage2 (hfr-redkit) | À faire |
| 4 — Extensions | Bookmarks, Blacklist, Qualitay, Redflag | L | Phase 3 + hfr-redflag Worker | À faire |
| 5 — Polish | Animations, offline, thème dynamique, Play Store | M | Phases 2, 3, 4 | À faire |
Taille : S = petit sous-chantier, M = quelques composants, L = plusieurs features indépendantes, XL = écran majeur + parseurs + cache (ex. PostRenderer natif).
Graphe des dépendances
flowchart LR
P0["Phase 0<br/>Bootstrap"]
P1["Phase 1<br/>Core lecture"]
P2["Phase 2<br/>Écriture"]
P3["Phase 3<br/>Messages"]
P4["Phase 4<br/>Extensions"]
P5["Phase 5<br/>Polish"]
MPS[("MPStorage2<br/>hfr-redkit")]
RFL[("hfr-redflag<br/>CF Worker")]
P0 --> P1 --> P2 --> P3 --> P4 --> P5
MPS -.prérequis.-> P3
RFL -.prérequis.-> P4
P2 --> P5
P3 --> P5
classDef external fill:#fef3c7,stroke:#d97706
class MPS,RFL external
Les dépôts en cylindre (MPStorage2, hfr-redflag) sont des dépendances externes hors de ce repo — leur état bloque le démarrage de la phase qui les consomme.
Phase 0 — Bootstrap ✅ livrée
Objectif : un squelette d’app qui compile, avec CI, thème et navigation.
- Structure Gradle multi-modules (8 core + 8 features base déclarés ; certains modules conservent un
build.gradle.ktsvide en attente de leur cycle, cf. ADR-001) - CI GitHub Actions (
detektAll,lintDebug,test,testDebugUnitTest,:app:assembleDebug) - Thème Material 3 dans
:core:ui(clair, sombre, AMOLED — Material You + HFR Classique différés Phase 5) - Navigation graph Compose Navigation 3 (bottom nav 4 onglets + back stacks par onglet, cf. navigation.md)
- Hilt wiring (
build-logicconvention plugins, KSP,@HiltAndroidApp) - Design system de base (typographie, couleurs, composants thème)
- Build signed AAB — pipeline
--init-script+ stampingredface2-v<vc>-<YYYYMMDD>-<sha>.aab
Livrable : une app qui démarre, affiche la bottom nav et navigue entre les écrans Phase 1 (drapeaux placeholder Phase 0 ; remplacé par la liste réelle en Phase 1B.4 ; forum/search/messages placeholder, topic fixe, éditeur placeholder).
Phase 1 — Core (lecture seule) ✅ livrée
Objectif : lire le forum. C’est 80% du use case.
- Login HFR (cookies persistants) —
:feature:auth.LoginScreen+AuthRepositorycache-aside (DataStore +PersistentCookieJar), session persistée viamd_user/md_pass, commit cookies seulement après classificationAuthenticated, détection session expirée sur endpoints authentifiés (cf. ADR-002 — DataStore non chiffré + FBE, AAB v14 /0.1.0-phase1b.0, hardening v24 /0.1.0-phase1b.10) - Écran Drapeaux (accueil) —
:feature:flags.FlagsRoute+FlagRepository(RESTforums/hardwarefr/topics/{participated,read,favorites}/depuis Phase 1D-1 #110, anciennement HTMLforum1f.php?owntopic={1,2,3}), 3 onglets (« Mes sujets » / « Lus uniquement » / « Favoris »), boutons « Réessayer » / « Actualiser » sans pull-to-refresh complet en 1B, filtre client CYAN masquant les sujets participés déjà lus par défaut (#154). Le footer alpha initial (pseudo, logout, version, signalement, Diagnostics) a vécu temporairement surMessagesScreen(#154), puis a été hoisté en Phase 2 finish (#198) dans le menu compte global (RedfaceAccountMenu) accessible depuis chaque écran principal via un slottopBarActions.MessagesScreenredevient un placeholder Phase 3. - Slice topic fixe — historique :
TopicScreena rendu une fixture HFR via parser → AST → renderer le temps que le pipeline réseau soit posé - Parser HTML topic —
:core:parserproduitPostContentdepuis le HTML HFR (cf. PR #78) - PostRenderer Compose — rendu natif
PostContentdans:core:ui(paragraphes, citations imbriquées avec collapse, spoilers, smileys builtin/perso, images, couleurs ; cf. ADR-011 et PR #80) - Écran Topic réel —
TopicRepositorycache-aside (OkHttp + parser + Room) livré (PR #88) puis branché surTopicScreen(1A-bind),TopicFixtureRepositorysupprimé - Écran Topic — lecture longue Phase 1D-2 (#107) : Précédent / Suivant + indicateur page X/Y + champ “Aller à la page” pour les longs topics ; rangée 1..N gardée en complément ≤ 40 pages.
TopicEffect.ScrollToPost(numreponse)consommé une seule fois parLaunchedEffect(Unit)(re-emit empêché par un flag interne au ViewModel). Deep linkforum2.php?cat=N&post=M&page=P#tIDouvre la page P et scrolle au posttIDquand il est présent. Back stack préservé : tap sur une page replace l’entrée TopicRoute (pas de push), back ramène au caller. - Écran Forum 1C-A REST-first —
:feature:forum.ForumScreen(19 catégories) +ForumCategoryScreen(subcats + topics paginés). Source REST/webservices/rest_api.phpviaHfrApiClient(:core:network) etForumRepository(:core:data), cf. ADR-003 - Écran Forum 1C-B — Material 3
PullToRefreshBoxsurForumScreen/ForumCategoryScreen(contenu préservé pendant le refresh, pas deSwipeRefreshAccompanist), badge drapeau par topic dérivé du RESTflag_owntopic(CYAN / RED / FAVORITE), recherche locale dans la page courante (titre / auteur / dernier réponseur, accent-insensitive) - Cache Room Phase 1D-3 (#26) — pages topic + posts persistés avec TTL,
authModeanti-écrasement, drapeaux REST persistés par compte dansflag_topics, purge logout / changement de pseudo viaCacheInvalidator - Deep linking (URLs HFR → app) —
parseHfrDeepLinkcorrigé (mappingforum1.php↔forum2.phpinversé, fixé en 1C-A) et branché sur les écrans réels Forum/Category/Topic - Prefetch pages suivantes Phase 1D-4 (#108) — topic
page + 1persisté enANONYMOUSsans écraser l’authentifié, listing forumpage + 1warm-up anonyme sans exposer le payload ; annulation au changement de page / sortie d’écran - Images + smileys (Coil 3) — Phase 1D-5 (#109) —
SingletonImageLoader.Factorycôté:appavecAnimatedImageDecoder.Factory()(autoplay GIFs builtins + perso).:core:uiPostMediaDisplayPolicycentralise les tailles : builtin 18×18, perso 70×50 (ContentScale.Fit, bucket aligné sur la distribution wikismilies), inline image 240×180 borné (ContentScale.Inside), block image largeur parent + hauteur max 480dp + arrondi + état loading/error. Pas de mesure intrinsèque async niFlowRow— décision B+ verrouillée par Codex (re-évaluable Phase 2/4 si le bucket fixe reste insuffisant). MAJ Phase 2F : le bucket fixe des smileys a été remplacé par un rendu intrinsèque (mesure native Coil, no-upscale, cap abs 70sp/240sp + cap relatif 0.9×largeur,AboveBaseline+line-growth) en #175 / PR #222 — cf.protocol-hfr.md§ Smileys. Les images inline[img]restent en bucket fixe (migration intrinsèque suivie en #224 ; validation visuelle smileys app↔web en #131). - Blocs monospace
[fixed]/[code](#79) —PostBlock.Fixed(text)etPostBlock.CodeBlock(text, language?)parsés depuis<table class="fixed">/<table class="code">;PostRendererrend chaque bloc dans uneCard surfaceContainerHighestà police monospace avec scroll horizontal sur overflow. Coloration syntaxique aplatie en texte brut (Phase 2).
Livrable : une app utilisable pour lire le forum au quotidien. Pas encore de possibilité d’écrire.
PostRenderer — le sous-chantier critique
Le rendu natif Compose du contenu HFR est le composant le plus complexe de toute l’app. Il doit gérer :
| Élément | Complexité |
|---|---|
| Texte formaté (gras, italique, souligné, couleur, taille) | Moyenne |
| Citations imbriquées | Élevée |
| Blocs de code | Faible |
| Images inline | Moyenne |
| Smileys HFR | Moyenne (cache + mapping) |
| URLs cliquables | Faible |
| Spoilers (clic pour révéler) | Moyenne |
| Listes | Faible |
Le PostRenderer sera développé de manière incrémentale : texte brut d’abord, puis formatage, puis citations, puis images.
Phase 2 — Écriture
Objectif : interagir avec le forum.
- 2A — Reality check protocole d’écriture HFR (#81) + références écosystème (#32) — fixtures live, spec écriture alignée HFR, page
docs/guides/references.mdconsolidée. Mergé via PR #159 (writes) + PR #160 (références). - 2B-A — Socle éditeur local (#86, refs #144) —
PostEditorRoute/TopicFormRoute,PostEditorScreen+ ViewModel, toolbar BBCode complète (gras / italique / souligné / barré / quote / code / cpp / fixed / spoiler / url / image), preview locale viaparsePostContentFromBbcode. Pas encore d’envoi HFR — local seulement. - 2C — Reply MVP (#145) — POST réel
bddpost.phpviaReplyRepository(:core:domain/write/+:core:data/write/),PostEditorViewModelwire submit + erreurs typées (empty,invalid_token,antiflood,locked,login_required). Migration Room v3 → v4 ajoutesubcatàtopic_pages; HFR write contract recapturé sur fixtures Phase 2A. - Quote MVP (#146, hardening #227) — bouton « Citer » par post dans
TopicScreen, gate surTopic.canReply(pas surquoteRef),PostEditorRouteétendu avecquotedNumreponse+quoteRefoptionnel. GETmessage.php?…&numrep={cited}avec&ref={N}seulement quand HFR l’a exposé en clair ; HFR préremplit quand même[quotemsg=…]avecnumrepseul sur les liens obfusqués.ReplyForm.initialContenthydrate le draft une seule fois sans écraser une saisie utilisateur, POSTbddpost.phpavecnumrep={cited}etnumreponse="". Réutilise leReplyRepository; pas deQuoteRepository. - Edit post MVP (#147) — bouton « Modifier » par post éditable détecté depuis la toolbar HFR (lien
message.php?…&numreponse=N). GET edit form, hydratation du draft depuis le BBCode existant, POSTbdd.php?config=hfr.incavecnumreponse={N}etnumrep="".EditPostRepositorydistinct duReplyRepository; partage les parsers, options, classification d’erreurs.delete=1filtré (hors scope). Refresh topic + scroll vers le post édité. - Edit FP MVP (#148) — édition du sujet + contenu BBCode du premier post via
TopicFormScreendédié.TopicFormParserextraitsujet,subcatsélectionnée + choix complet du<select>,MsgIconchecked, options checkboxes, et préserve les champs sondage /toread1..5verbatim. POSTbdd.php?config=hfr.incavecsujetmodifiable etsubcatre-catégorisable ;deletefiltré (suppression hors scope).Topic.isFirstPostOwnerdétecté depuis la toolbar du premier post (page 1 uniquement). Édition active du sondage : reportée — fixture avec sondage existant nécessaire avant de prouver le contrat. - Edit FP polish (#178) — recatégorisation exposée via le dropdown sous-catégorie pour
TopicFormMode.EditFirstPost, avec submit duselectedSubcatexistant ; l’UX sondage reste honnête (préservation verbatim uniquement tant que les fixtures POST sondage manquent). - Create topic MVP (#149 Phase 2E + #214/#206 finish) — depuis
ForumCategoryScreen, FAB « Nouveau topic » (visible enAuthState.Authenticateduniquement), composer dédiéTopicFormScreenmodeNew(sujet + dropdown sous-catégorie obligatoire + BBCode toolbar + options HFR), POSTbddpost.php?config=hfr.incviaTopicFormRepository.submitNewTopic.from_subcat(chip d’arrivée) reste distinct dusubcatfinal (dropdown). La réponse succès live refresh vers la liste cible et ne renvoie aucun topic id : l’app revient donc sur la sous-catégorie cible avec Toast et met en évidence le sujet créé par correspondance exacte du titre. Sondage actif à la création : reporté tant qu’aucune fixture POST sondage n’a été capturée. - Toolbar BBCode (#144 Phase 2B-B) — palette couleur Material 3 (
BbcodeAction.Color(#RRGGBB)+ dropdown 5 swatches) ajoutée à la toolbar partagée:core:ui/BbcodeToolbar.BbcodeActionest devenu unsealed interfacepour porter le hex. Listes BBCode ([list]/[*]) restent hors AST en Phase 2B : le parser les conserve en texte brut sans crash (cf.BbcodeContentParserTest), à revoir Phase 4 quand un renderer liste sera utile. Smileys perso et upload images : #11. - Preview BBCode — livré 2B-A en local via
parsePostContentFromBbcode; preview serveurapercu.phpreportée tant qu’aucune divergence HFR ne le justifie. - Smileys éditeur MVP (#11 partiel, Phase 2F-B + 2F-C) — picker
ModalBottomSheetMaterial 3 dansPostEditorScreen(Reply / Quote / Edit, Phase 2F-B) et dansTopicFormScreen(Edit FP + New topic, Phase 2F-C). Onglet Standard : 25 builtins HFR servis depuis la constanteBUILTIN_HFR_SMILEYS(codes dérivés dewrite_reply_form_open_topic.html). Onglet Wiki : recherche live viaGET /message-smi-mp-aj.php?config=hfr.inc&user_id={id}&findsmilies={query}(contrat vérifié 2026-05-22,user_idparsé depuisfind_smilies_timer('hfr.inc', N)du form HTML et plumb dansReplyForm.userId/TopicForm.userId), debounce 300 ms + gatequery.length > 2alignés sur le composer HFR. Insertion viainsertBbcodeToken(" $token ")qui reproduit la conventionputSmileyJS. Le rendu adaptatif des smileys inline dans les posts est sorti du scope et tracé dans #175. Hors scope #11 conservé : favoris, récents, upload smiley perso, GIFs externes, catalogue offline. - Dogfood rendu smileys (#131 / #175, Phase 2F-D) — comparer visuellement RF2 au rendu web HFR sur de vrais topics ; garder le bucket fixe 70×50 si acceptable, ou livrer le rendu adaptatif #175 si le dogfood invalide ce compromis.
- Médias éditeur Phase 2F-E (#189) — helper MVP d’insertion d’image par URL distante : dialog Material 3 depuis la toolbar partagée, validation
http(s)uniquement, insertion[img]url[/img]au curseur dans Reply / Quote / Edit / Edit FP / New topic. Upload/rehost, favoris/récents smileys, GIFs externes et sync MPStorage sont différés aux phases ultérieures (#190). - Recherche HFR MVP (#150 partiel, Phase 2G-A/B) — recherche via
GET /forum1.php?recherches=1&...(le form HFRPOST /search.phprenvoie une page de transition meta-refresh, on hit le GET canonique directement). Parser couvre les shapes capturées :no-results(page.hopminimaliste),pivot single(1 cat hit),pivot multi(N cats hit, sélecteur via<select name="cat">dans<div class="search">),explicit cat(listing standard sans bannière), et les extraits contenuDernier message correspondantavec lienforum2.php?...numreponse=...quand HFR les fournit. Écran Recherche : champ + IME Search + bouton, choix Titres+messages/Titres/Messages, états idle/loading/empty/error/results, pivot horizontal pour relancer dans une autre catégorie. Navigation →TopicRoute(cat, post, page, scrollTo)quand le résultat porte unnumreponse, sinon page 1. - Recherche HFR bugfix (#188, Phase 2G-B) — les catégories de pivot de recherche sont rendues en rail horizontal borné (single-line + ellipsis), ce qui évite les libellés verticaux du style « Linux et OS Alternatifs » sur écran étroit.
- Recherche HFR filtres avancés auteur/date + pagination — backlog non bloquant après stabilisation du MVP, à ouvrir en issue dédiée si le besoin reste confirmé après #188.
- Profil utilisateur (#208, Phase 2 finish) — tap sur avatar/pseudo d’un post ouvre un bottom sheet résumé (avatar carré/arrondi, pseudo, localisation, inscription, posts). Bouton « Voir le profil complet » navigue vers la page détaillée.
Post.profileIdextrait du lien toolbar HFR et persisté en Room v6. Module:feature:profileavecProfileViewModel(AssistedInject),ProfilePreviewSheet(ModalBottomSheet M3) etProfileScreen. Frontière:feature:topic→:feature:profilezéro (callbackonOpenProfilehoisted dans:app). Bouton « Derniers messages » désactivé (pas de route stable, marqué « à venir »). - Retirer un drapeau (delflag) (#99, Phase 2 finish) — retrait par swipe-to-remove (
SwipeToDismissBoxM3, swipe end-to-start) sur chaque ligne dans:feature:flags, avec confirmation Material 3 obligatoire avant l’appel réseau (le swipe ouvre le dialog mais ne supprime jamais la ligne seul ; pas d’undo optimiste :addflagn’est pas prouvé pour tous les types, donc rien à re-poser après coup). Suppression unitaire viaGET /user/delflag.phpauthentifié (mappingFlagType→owntopic1/2/3, classée sur « Drapeau effacé avec succès » ; cf.protocol-hfr.md). Succès → éviction des caches mémoire + Room (clécat+topicId+type) et ré-émission immédiate ; échec → aucun cache touché + snackbar d’erreur. Action désactivée pendant l’appel (anti double-tap). Suppression en masse (manageaction.php) hors scope. - Réglage proxy utilisateur (#187, Phase 2H) — écran Settings alpha, persistance DataStore, branchement HFR-only pour OkHttp + Coil (seuls
hardware.fr/*.hardware.frpassent par le proxy ; les images externes restent en direct), proxy HTTP avec authentification optionnelle, et guide utilisateur. Le MVP demande un redémarrage de l’app après changement ; pas de proxy embarqué, PAC, SOCKS ou bypass list. - Swipe pour changer de page de topic (#282, Phase 2 finish) — geste horizontal gauche/droite « drag-follow » dans
TopicScreen(la page suit le doigt, résistance amortie aux bords, retour haptique armement/commit, edge-glow discret), implémenté parModifier.topicPageSwipe(feature/topic/.../TopicSwipe.kt, helpers purs testés). Il appelle le mêmeonOpenPage(targetPage)que les boutons de pager (navigation route-driven, remplace laTopicRoute) ; transition Topic→Topic rendue instantanée (transitionSpecdédié dansRedfaceApp) pour supprimer la fenêtre morte du cross-fade ; geste gaté tant que l’entrée nav n’est pasRESUMED(anti double-commit pendant la transition). Aucune action destructive.
Livrable : une app complète pour lire ET écrire sur le forum.
Phase 3 — Messages
Objectif : les messages privés, classiques et multi.
- Inbox MPs classiques — liste + lecture en read-only (#298)
- Reply / quote MP classique
- Nouveau MP — création
- MultiMPs — liste avec vue drapeaux, lecture, reply, quote
- Nouveau MultiMP — création (2+ destinataires)
- Intégration MPStorage — synchronisation avec le MP de stockage HFR + cache Room
- Notifications MP
Livrable : gestion complète des MPs, y compris les MultiMPs avec état lu/non-lu.
Phase 4 — Extensions communautaires
Objectif : les features inspirées des userscripts HFR.
- Architecture d’extensions (PostDecorator, TopicToolbarContributor)
- Bookmarks — sauvegarder des posts
- Blacklist — masquer des utilisateurs
- Alertes Qualitay — signaler un post remarquable
- Redflag — alertes intelligentes sur topics suivis
Livrable : les features communautaires les plus demandées, intégrées nativement.
Phase 5 — Polish
Objectif : l’expérience utilisateur raffinée.
- Animations et transitions
- Mode offline complet (lecture + file d’attente d’écriture)
- Notifications push configurables
- Thème dynamique (Material You)
- Thème “HFR classique”
- Widgets Android
- Tests de performance (scroll, cold start, mémoire)
- Release automation
- Signed release build (keystore en GitHub Secrets)
- Publication Play Store (arbitrage Fastlane vs Gradle Play Publisher)
- Beta testing (Play Console internal testing privilégié vs Firebase App Distribution)
- Création compte développeur ForumHFR si inexistant
Livrable : une app prête pour le grand public.
Participation
Chaque phase sera trackée via les issues GitHub et des milestones. Les contributions sont les bienvenues à partir de la Phase 1.
Pour contribuer :
- Choisir une issue non assignée
- Commenter pour signaler qu’on la prend
- Ouvrir une PR sur une branche feature
- Review par un mainteneur