Skip to content

release: 0.10.0 — promotion dev → main (bêta)#451

Merged
XaaT merged 34 commits into
mainfrom
dev
Jun 12, 2026
Merged

release: 0.10.0 — promotion dev → main (bêta)#451
XaaT merged 34 commits into
mainfrom
dev

Conversation

@XaaT

@XaaT XaaT commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Action par Claude Fable 5 (demandée par @xatrix)

Promotion du canal dev vers main pour le cut bêta 0.10.0 (build anticipé v126). 33 commits depuis la 0.9.0/v113, dogfoodés en continu sur le canal dev (v114 → v125), chaque PR mergée avec CI verte + review (Codex ou multi-agents).

Contenu (depuis 0.9.0)

Messages privés : écriture complète (nouveau MP), picker de smileys dans les éditeurs MP, badge de non-lus sur l'onglet, swipe de pages in-place, pull-to-refresh, ascenseur, MPStorage lecture seule v0.1 (ADR-014).
Écriture : citation multiple + marquage visuel des posts ajoutés, l'éditeur suit le curseur pendant la frappe.
Recherche : filtre par auteur, repli du formulaire en bandeau compact.
Lecture : marqueur de fin de sujet, réglage des FAB de page, profil via le menu de post.
Fixes : IME/clavier (×3), parser (lignes vides, spoilers, smileys inconnus), drapeaux (×6), thème cold-start, onglet « Mes sujets » sans wrap.
Docs : ADR-013 + ADR-014 acceptées, pages canoniques actées (contrat nonlu.php).
Release : versionName 0.10.0, stamp -dev.<build> sur le canal dev, changelog v126 + notes Play.

Issues livrées

Closes #301, closes #387, closes #313, closes #291, closes #433, closes #379, closes #383, closes #395, closes #418, closes #415, closes #275, closes #410, closes #333, closes #280, closes #393, closes #416, closes #384, closes #378, closes #385, closes #412

Restent volontairement ouvertes : #6 (umbrella MPStorage), #351 (tranche c différée post-bêta), #436 (« Tout vider » non codé), #447 (point 2 drag-sélection), #430, #441, #442, #445 ; #331 et #417 (fixes livrés mais les PRs #421/#434 promettaient une confirmation device avant fermeture — on ferme dès confirmation en bêta) ; #452/#453 (findings Codex du dernier tour sur le badge MP, suivis post-bêta).

Procédure

Merge commit no-ff (pas de squash : on préserve l'historique des PRs dev), puis gh workflow run release.yml --ref main -f channel=beta. Guard CI versionName : 0.10.0 vs 0.9.0 (dernière release « β ») — passe.

🤖 Generated with Claude Code

XaaT and others added 30 commits June 10, 2026 23:56
)

The v110 8dp gutters never landed visually : the global Surface(padding(horizontal=8dp)) around the NavHost added up to 16dp per side (spotted by XaTriX, dogfooding v111). List now adds 0 ; host 8dp = intended gutter. Tech debt #398.

Merge --admin (exception mono-maintainer documentée dans AGENTS.md : pas de reviewer sûr disponible, CI verte + validation locale 2 parts).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ur dev (#401)

* chore(release): bump versionName to 0.9.0 + changelog v113 (candidate)

Frozen promotion candidate from dev bb3ee57 (dev build 112) : the
night run keeps merging on dev, this branch is what Codex reviews and
what ships to beta if the review is clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(changelog): v113 lot = 7 PR, pas 8 (review Codex du candidat)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(changelog): statut v113/0.9.0 → open (bêta shippée)

Promotion #400 mergée no-ff 0313e8f, run beta 27310478252 vert
(Play open testing + F-Droid .beta), tag app-v113.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
… profile (#403)

* feat(search): author filter (pseud=) through model, network and data layers

SearchRequest.pseudo -> HfrClient searchTopics pseud= query param ->
DefaultSearchRepository forward. Contract verified live 2026-06-11 :
author-only search works in all three shapes (explicit cat titre=1 =
existing fixture, explicit cat titre=3, all-cats titre=3 via the 302
multi-cat pivot redirect OkHttp follows). Diagnostics log a presence
flag only, same redaction contract as the query.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(search): author field in the search form + prefilled author-only entry

SearchUiState.pseudo + PseudoChanged intent (same invalidation contract
as QueryChanged), submit legal when query OR pseudo is non-blank, retry
and pivot re-scoping keep the author filter. SearchViewModel moves to
@AssistedInject (ProfileViewModel pattern) so a nav route can hand an
initialPseudo at construction : non-blank -> author-only search fired
in init. Screen grows the « Auteur (pseudo) » field, an optional back
affordance for pushed entries, and 7 new VM tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(profile): enable « Derniers messages » -> author-only search route

New SearchUserPostsRoute(pseudo) pushed onto the current tab's stack
(separate route so the SearchRoute tab root stays a data object and the
tab's idle state is untouched). ProfileFullContent enables the button
with the loaded profile's canonical pseudo.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(search): cover the pseud wire contract + author-only fixtures (Codex review)

Codex review of #403 (0 blocking) flagged three coverage gaps, all
addressed : HfrClientTest author-filter encoding on the anonymous client
(empty query), SearchResultParserTest on the existing pseud fixture +
a NEW live-captured all-categories pivot fixture (the exact wire shape
of the profile button, post-302), and DefaultSearchRepositoryTest
forwarding + pseud redaction in rebranded IOExceptions. Also guards the
profile button against ProfileParser's defensive "?" pseudo sentinel
(minor finding).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(messages): new-MP composer contract through network, domain and data layers

HfrClient.getPrivateMessageComposePage GETs the standalone composer
(message.php?cat=prive without post=, dest= prefillable server-side).
PrivateMessageWriteRepository grows fetchComposeForm + submitNewMessage ;
buildComposeFormBody overrides hash_check/verifrequet/content_form/dest/
sujet and forwards the composer's hidden routing verbatim (cat=prive,
empty post/numrep, parents, stickold, MsgIcon, pseudo — never password).

Contract captured live 2026-06-11 (fixtures mp_compose_form.html +
dest-prefilled variant, scrubbed) ; the POST response was deliberately
never exercised, so an unrecognised answer stays the non-destructive
Unknown. 8 new tests (2 parser fixtures cases, 6 repository MockWebServer
cases incl. dest/sujet override-once and blank-field short-circuits).

Refs #301 (follow-up écriture : composition)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(messages): new-conversation composer screen + shared editor components

PrivateMessageComposeScreen/ViewModel/UiState : recipients (dest, commas
= MultiMP) + subject (70-char HFR cap, truncating) + the shared BBCode
editor ; same #312 armed-confirmation and silent hash_check refetch as
the reply editor, with a hydration guard so a refetch never clobbers an
in-between recipients edit. The reply screen's chrome (header, IME-pinned
submit bar, options sheet content, load/error states, banner mapping)
moves to MessageEditorComponents (internal) consumed by both editors.
MessagesScreen grows a « Nouveau » header button (shown once the inbox
loaded) and a sentSignal hook that refreshes the list after a successful
send. 10 new ViewModel tests.

Refs #301

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(nav): PrivateMessageComposeRoute wired from the MP list

Full-screen editor route (hides the navigation suite like the other
composers). On SubmitSucceeded the host pops the composer and bumps
privateMessageSentSignal — the created thread id is unknown (the bddpost
success response of a new conversation is not topic-shaped), so the MP
list re-fetches and shows the new conversation at the top.

Refs #301

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(messages): address Codex review of #404

- sentSignal now lands on inbox page 1 (showFreshInbox) — a plain
  current-page refresh from page 2+ would never surface the created
  conversation ; soft refresh when already there.
- New VM test : an unrecognised POST response emits NO navigation
  effect (the composer must not pop on an unproven outcome).
- Composer-specific wording for the form error and the Unexpected
  banner (« vérifiez votre liste de messages privés »).
- privateMessageSentSignal purged on auth transitions like the other
  private-message hints.
- Reply screen : leftover imports from the chrome extraction removed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…nt, pipeline 3-GET (#406)

Merge par squash --admin (mono-maintainer, review Codex traitée dans la PR). UI onglet DT différée après relecture ADR-014. Réf. #6.
Merge par squash --admin (mono-maintainer, review Codex traitée dans la PR). closes-#386 réservé à la promotion dev vers main. Fix à confirmer au dogfooding device.
Merge par squash --admin (mono-maintainer, review Codex zéro finding dans la PR). closes-#393 réservé à la promotion dev vers main.
Merge par squash --admin (mono-maintainer, fichiers de notes Play uniquement).
…précédente en bas (#420)

Squash de feat/quickwins-d1 : refs-#415 (palette 58 builtins + fixture smilies.php + test symétrie), refs-#416 (slot error SubcomposeAsyncImage → token tapé), refs-#418 (Supprimer déplacé dans PostMenuSheet, gates #292 conservées), refs-#412 (StartAtBottom via marqueur nav transitoire, durci post-review Codex 2f5faa1). CI verte, review Codex 0 bloquant. Merge --squash --admin : mono-maintainer, cf. convention repo.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… auto-refresh #378 (#421)

Squash de feat/flags-pass : refs-#384 (type = bucket demandé, fixture preuve live flag_owntopic=3 dans participated), refs-#385 (reset scroll au flip du filtre, paire atomique post-review), refs-#417 (forceDarkAllowed=false), refs-#378 (auto-refresh on landing + pref opt-out + throttle 15s, durci post-review), aide refs-#331 (staleness). CI verte, review Codex 0 bloquant (2 IMPORTANT traités 1cdb5f9). Merge --squash --admin : mono-maintainer, cf. convention repo.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…275, #410) (#422)

* fix(editor): keep the cursor visible under the IME in the three editors (#275, #410)

The full-screen editors (post reply/edit, MP reply, MP compose) gave
the draft field a bounded height (weight 1f) and let the TEXT scroll
INSIDE it. Compose's keep-the-cursor-visible machinery lives in
ANCESTOR scrollables - cursor bring-into-view requests (typing,
tap-to-place, toolbar insertions) propagate to them and the scrollable
re-anchors the focused area when its viewport shrinks under the IME -
the internal text scroller has none of it. Net effect across keyboards
(Gboard/SwiftKey/HeliBoard, #275): the IME compressed the field and the
cursor line stayed hidden below the fold, both while typing (#275) and
on refocus after the preview (#410). TopicFormScreen (outer-scroll
layout) was already on the working pattern.

BbcodeTextField gains a fillViewport mode: the field grows with its
content (no internal scroll) inside its own scrollable column sized by
the caller's bounded box, heightIn(min = viewport) preserving the v108
contract (the outlined area fills every free pixel, tap anywhere
focuses). The three editors opt in; the contract forbids enabling it
inside an outer scroll (nested unbounded scrollables).

Robolectric tests pin the structure (field fills short, grows long, the
wrapping column owns the scroll). The IME interaction itself cannot be
exercised by Robolectric - device dogfooding on the next dev release.

Refs #275, refs #410 (keywords broken on purpose: close at dev->main
promotion).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(editor): address Codex review on the fillViewport contract (#275, #410)

- KDoc: the field's internal scroller does take part in bring-into-view
  but does not re-anchor on IME shrink — drop the over-absolute claim
- enforce the bounded-height contract with a require(maxHeight.isFinite)
- refresh the three call-site comments that still described the old
  scroll-INSIDE-the-field layout

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
… refs-#280) (#423)

* fix(parser): preserve deliberate blank lines and uniform line height

A top-level <br> used to FLUSH the running paragraph, so every authored
line became its own Paragraph block: the `<br><br>` HFR emits for an
empty line collapsed into a dropped empty paragraph (refs 333) and the
renderer's 8dp inter-block gap replaced the natural line height between
every single line (refs 280).

A top-level break is now an inline LineBreak inside the running
paragraph (web parity: N consecutive breaks render N newlines), and
flushParagraph trims breaks/blank fragments at the paragraph EDGES only
— breaks adjacent to a block boundary (quote, image, end of post)
duplicate the renderer's inter-block spacing, interior ones are the
author's literal line structure.

Regression tests anchored on the real fixture patterns: quote-header
`</b><br /><br /><p>` (leading edge), end-of-quote `<br /><br /></p>`
(trailing edge), single-br multi-line answer (topic_page_single).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(parser): address Codex review — stale br comment, exact AST assertion

- parseInlineElement still said a top-level <br> "flushes the current
  paragraph" — the exact behaviour the fix removed; reworded
- the synthetic regression test now asserts the exact inline AST
  (Text/LineBreak values), not just the node classes

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…5) (#425)

The contextual menu (refs 362) shows avatar + pseudo but the identity
was inert — tapping it now opens the profile sheet, parity with the
post-card tap (refs 208). Whole hero row is the tap target (menu-row
idiom), same profileId gate as the card, and the menu plays its hide
animation before the profile sheet opens so the two never stack.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…383) (#424)

* feat(topic): setting to hide the floating page-change FABs (refs 383)

The bottom-of-topic previous/next mini-FABs (refs 283) duplicate the
page swipe (refs 282) for readers who navigate by gesture — add an
opt-out preference. The « Répondre » FAB keeps its own gates and stays.

Same optimistic-flip + startup-race-guard machinery as the existing
topic top-bar auto-hide toggle, mirrored into TopicUiState so a flip
applies live without reopening the topic. Default true (historical
behaviour).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(settings): address Codex review on the page-FABs toggle (refs 383)

- DataStore round-trip test for the topic_page_fabs key (defaults true,
  persists false/true) — the fakes alone would miss a wrong key/default
- hydration tests: a persisted false reaches state; a stale initial
  emission must not overwrite a local opt-out (TouchedLocally guard)
- TopicPreferencesCard KDoc now documents both toggles

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
… page (refs-#379) (#426)

* feat(topic): explicit end-of-topic marker on the last page (refs 379)

The « page X/Y » counter (refs 284) lets the reader deduce they reached
the last post; this says it: a sober centred label between two
hairlines, rendered as the last list item of the topic's LAST page
only. Reflects the loaded page, same contract as the counter.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(topic): drop the explicit key on the end-of-topic footer (refs 379)

Codex review: a stable key makes Lazy track the sentinel across an
insertion — a reader parked on the marker would keep it in view while a
freshly fetched post lands above the viewport, unseen. Positional
identity is correct for a stateless footer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(topic,editor): multi-quote — quote several posts in one reply (refs 291)

Client-side MVP, no new HFR contract: the #146 quote form fetch
(message.php?numrep=N → prefilled [quotemsg]) is replayed once per
selected post and the prefills are concatenated in SELECTION order; the
submit rides the first form's hash_check (per-session, not per-post).

- post menu: « Ajouter/Retirer de la citation multiple », same gate as
  « Citer »
- floating cluster: « ❝N » FAB opens the editor with every selected
  quote; not governed by the #383 page-FABs preference (armed write
  affordance, not navigation)
- basket hoisted to :app keyed (cat, post) — survives page changes,
  resets when selecting in another topic, cleared on editor launch
- a failed extra fails the whole fetch (retryable) rather than silently
  dropping a quote the user selected

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(topic,editor): address Codex review on multi-quote (refs 291)

- purge the multi-quote basket on auth transitions — a write intention
  armed under another session must not survive logout/login
- a 200-OK form whose prefill comes back BLANK now fails the whole
  fetch instead of silently dropping a selected quote (+ test)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…refresh et ascenseur MP (refs-#351, tranche a) (#428)

* feat(messages,ui): composants de lecture partagés topic-MP + pull-to-refresh et ascenseur MP (refs-#351)

Tranche a de refs-#351 (ADR-013 : partager les composants, pas les écrans) :
- LazyListScrollbar : TopicScrollbar déménagé verbatim vers :core:ui/list
  (composant générique LazyListState, géométrie #300 inchangée)
- core.ui.pager.PageSwipe : fonctions pures du swipe #282 (seuils, drag-follow,
  edge-hint) + modifier pageSwipeEdgeHint partagés ; la machinerie route-driven
  (latch, slide-out) reste dans :feature:topic
- MP thread : load() keep-content (page change / pull-to-refresh gardent la page
  affichée derrière isRefreshing, prérequis du swipe MP) ; échec keep-content =
  effect RefreshFailed (Toast) + page conservée ; PullToRefreshBox (parité #335) ;
  LazyListScrollbar en overlay (parité #300) ; scroll-to-top au changement de
  page rendue
- tests : PageSwipeTest (35) + LazyListScrollbarTest (18) déplacés ; 4 nouveaux
  tests VM (keep-content gated, refresh in-place, échec keep-content, no-op)
- docs : architecture.md (:core:ui list/ + pager/)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(messages): ne pas écraser la position restaurée au premier rendu Content (refs-#351)

Finding IMPORTANT de la review Codex : le LaunchedEffect de scroll-to-top
keyé sur la page rendue se déclenchait aussi au premier rendu d'une
composition fraîche — une rotation/recréation avec contenu chargé
restaurait la position de lecture (rememberLazyListState) puis la
ramenait aussitôt en haut. Garde lastRenderedPage (null au premier
rendu) extraite dans ScrollToTopOnPageChange (seuil detekt).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…#351, tranche b) (#429)

* feat(messages): swipe horizontal de pages sur la conversation MP, in-place (refs-#351)

Tranche b de refs-#351 : portage du swipe de pages (#282) à la vue MP,
machinerie in-place dédiée (ADR-013) sur la géométrie partagée :core:ui :
- threadPageSwipe : mêmes seuils/ressenti (drag-follow, overpull, mur aux
  bords, haptique arm/commit) ; commit -> selectPage() keep-content, la
  page reste lisible derrière l'indicateur pendant le round-trip ; pas de
  slide-out (la composition survit) ; gate sur isRefreshing lu au down,
  ré-armé à la fin du chargement (pas de latch par composition)
- pointerInput(Unit) jamais re-keyé : page/total/gate/callback lus via
  lambdas adossées à rememberUpdatedState (rememberThreadSwipeModifier)
- edge-glow partagé pageSwipeEdgeHint, accent désaturé identique au topic
- le swipe s'applique à la LazyColumn ; l'ascenseur overlay reste fixe

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(messages): reset du drag-offset au changement de page + pager gated pendant le chargement (refs-#351)

Findings IMPORTANT de la review Codex sur le swipe in-place :
- dragOffset/Animatable survivent au changement de page (pas de
  destruction de composition, contrairement au topic route-driven) ->
  reset LaunchedEffect(renderedPage), la nouvelle page n'hérite plus
  d'un offset résiduel
- boutons du pager désactivés pendant un chargement keep-content
  (même gate que le swipe : un re-tap ne faisait que superseder)
- mineur : handlers remember{} sans clé + doc capture unique
  (pointerInput(Unit) garde sa première capture quoi qu'il arrive)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…uis la liste (refs-#378) (#431)

* fix(flags): l'auto-refresh bypasse le throttle au retour d'un sujet ouvert depuis la liste (refs-#378)

Retours dev v118 (Dintr-un lemn, bitubo) : l'auto-refresh est sauté
quand on ressort d'un topic en moins de 15 s — pile le moment où l'état
a changé (le sujet lu doit sortir d'une vue non-lus). Cause : le
throttle s'arme à l'atterrissage et ne distingue pas un retour-de-
lecture d'un aller-retour sans lecture.

Fix : FlagsRoute marque l'ouverture d'un topic (onFlagOpened) ; le
prochain maybeAutoRefresh bypasse le throttle et consomme le marqueur
(un refresh manuel le consomme aussi — il capture le même état). Les
allers-retours sans lecture (switch d'onglets) restent throttlés.

3 tests : bypass après ouverture, consommation par le refresh déclenché,
consommation par un pull manuel.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(flags): snapshot de la génération topic-ouvert au call-time de maybeAutoRefresh (refs-#378)

Finding IMPORTANT de la review Codex : une lecture armée PENDANT les
suspensions pref/auth du refresh d'atterrissage était consommée par ce
refresh (qui ne pouvait pas l'avoir capturée), perdant le bypass au
vrai retour. Compteur de génération + snapshot au call-time (même
idiome que le snapshot d'onglet PR #421) ; le pull manuel consomme les
générations visibles à son propre call-time. Test de course ajouté
(StandardTestDispatcher).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…i refs-#384) (#432)

* fix(flags): la décoration favori redevient jaune dans « Mes sujets » (suivi refs-#384)

Retour dev v118 (XaTriX, screens) : le « type = bucket » de refs-#384
avait raison pour le cache/routage mais la couleur de pastille dérive
du type -> l'info « aussi favori » (flag_owntopic=3) a perdu son jaune
dans l'onglet participated.

- Flag.isFavorite (décoration, défaut false) <- flag_owntopic == 3,
  type-bucket inchangé (le « future étoile badge » anticipé par la
  KDoc du mapper)
- FlagDot : le favori gagne sur la couleur du bucket (parité site)
- Room : colonne flag_topics.isFavorite NOT NULL DEFAULT 0
  (@ColumnInfo defaultValue alignée), migration 8->9 + schéma v9,
  MIGRATION_8_9 chaînée dans les 7 blocs production du MigrationTest
- tests : fixture rest_cat13_participated_favorites (isFavorite vrai
  sur les 2 lignes owntopic=3, faux ailleurs) + migrate_8_to_9

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(flags,docs): specs alignées (type=bucket vs isFavorite) + FlagDot sur FlagPalette (refs-#384)

Findings de la review Codex :
- IMPORTANT : models.md (Flag sans isFavorite) et protocol-hfr.md
  (flag_owntopic décrit comme bucket) contredisaient le contrat réel —
  les deux specs canoniques documentent maintenant bucket-vs-décoration
  avec la preuve fixture, et le mapping direct conservé pour les
  TopicSummary hors drapeaux
- MINEUR : FlagDot dupliquait les couleurs en littéraux divergents de
  FlagPalette (source des lignes topic du Forum) -> source unique,
  delta de teinte rouge/jaune subtil assumé

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…clavier (refs-#275, refs-#410) (#434)

Retour dev v118 (XaTriX, screen) : sur « Nouveau message », clavier
ouvert, destinataire+sujet+toolbar+barre Envoyer consomment l'écran et
le champ message (weight 1 + fillViewport, design hérité de l'éditeur
de réponse) est écrasé à ~0 sans scroll externe pour le ramener.

Fix : ComposeEditorBody bascule sur la branche « éditeur à en-tête
haut » du contrat BbcodeTextField, déjà utilisée par TopicFormScreen —
body entier en verticalScroll + champ en mode défaut grow-with-content
(bring-into-view et ré-ancrage IME via le scrollable ancêtre ;
fillViewport interdit sous un scroll externe) + heightIn(min=160dp)
pour une vraie zone de frappe à draft vide ; aperçu en bloc simple
(plus de scroll imbriqué). Barre Envoyer épinglée inchangée ; l'éditeur
de réponse MP garde fillViewport (pas d'en-tête, pas le bug).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…eaux post-review (#435)

* chore(privacy,docs): re-scrub fixture MP storage + cohérence doc drapeaux post-review

Review complète de la branche dev (main...dev, 21 commits) — 2 findings ≥75
corrigés + 3 mineurs de cohérence :

- fixtures mp_storage_search_hit.html (core:data + core:parser) : les value=
  des checkboxes d'effacement et les title="Sujet n°…" gardaient les vrais
  threadIds privés (renumérotés 9000001..9000003 comme les href), et le
  title « n'a pas été lu par » exposait la liste réelle des lecteurs d'une
  conversation (remplacée par des pseudos synthétiques). Journaux source.txt
  mis à jour. Aucun consommateur : le parser ne lit que href post= (tests
  inchangés, assertions 9000003 intactes).
- docs/specs/architecture.md : signature RestFlagMappers.toFlags(envelope,
  defaultType, …) périmée → (envelope, type, …) + rappel bucket/isFavorite.
- Flag.kt : KDoc des membres FlagType contredisait le KDoc de classe
  (bucket ≠ flag_owntopic, #384) → reformulé bucket participated/read/favorites.
- HfrClient.deleteFlag : « same mapping as the REST flag_owntopic » trompeur
  depuis la vérification live → précisé sélecteur WRITE-only.
- Réglages : entrées « à venir » caduques retirées (scroll par page livré
  always-on refs-#307 ; composer un MP livré refs-#301), strings purgées.

Validation locale 2 parts : detektAll + test + :app:assembleProdDebug puis
:app:lintProdDebug — vertes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(review): toOwntopic = sélecteur delflag-only, removeFlag, date du re-scrub

Review Codex de la PR #435 : 1 IMPORTANT (la KDoc de toOwntopic() disait
encore delflag/addflag + REST flag_owntopic — addflag ignore owntopic et le
champ REST décrit le drapeau le plus fort, pas le bucket) + 2 mineurs
(HfrClient.deleteFlag → removeFlag dans la KDoc de FlagType ; journal
re-scrub daté 2026-06-12 → 2026-06-11).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…437)

Validation Docker 2 parts + émulateur (:zorglub: lisible topic DEV p.8). Review Codex : aucun finding. Merge --admin mono-maintainer documenté.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… (refs-#436) (#438)

Validation Docker 2 parts + émulateur (bordure + pastille + badge panier). Review Codex : aucun finding. Merge --admin mono-maintainer documenté.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ge (refs-#313) (#439)

Validation Docker 2 parts + émulateur (badge « 3 » cohérent liste). Review Codex : 1 bloquant (piggyback non scellé à la session) corrigé — pseudo snapshotté au call-time + filtre à la collecte. Merge --admin mono-maintainer documenté.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… promu :core:ui (refs-#387) (#440)

Validation Docker 2 parts + émulateur (insertion + recherche wiki). Review Codex : 2 importants (userId du form transmis à la recherche wiki ; aucun travail réseau picker fermé) corrigés. Follow-up migration :feature:editor = #441. Merge --admin mono-maintainer documenté.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…erche lancée (refs-#433) (#443)

Option A arbitrée. Validation Docker 2 parts + émulateur (bandeau + résultats pleine hauteur, ré-expansion OK). Review Codex : aucun finding. Merge --admin mono-maintainer documenté.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…u canal dev (#444)

Préparation cut bêta 0.10.0 (guard versionName vs bêta 0.9.0/v113) + builds dev distinguables (vérifié aapt2 : 0.10.0-dev.local). Merge --admin mono-maintainer documenté.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…gée d'onglets (#446)

Diagnostic uiautomator (tabs 259 px, padding slot text 16 dp → 175 px utiles = largeur exacte du label). Surcharge content de Tab, 8 dp + maxLines=1 + Ellipsis, couleurs explicites. Vérifié émulateur (nominal + ellipse « +lus »). Merge --admin mono-maintainer documenté.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
)

Audit adversarial par agent indépendant (Codex indisponible — 402
deactivated_workspace) : 4 bloquants ADR-013 + 5 importants ADR-014,
tous corrigés avant acceptation.

ADR-013 (lecture MP) — Accepté 2026-06-12 :
- état d'implémentation : décision 1 LIVRÉE (PR #428/#429, noms réels
  PageSwipe/LazyListScrollbar), décisions 2-3 à implémenter (#430/#6)
- prefetch borné N−1/N+1, définition « ouverte », suspension après
  marquage non-lu manuel ; purge étage 1 tranchée
- conséquence Konsist : étendre la garde au domaine MP, pas exempter

ADR-014 (MPStorage v0.1) — Accepté 2026-06-12 :
- état d'implémentation : lecture LIVRÉE (PR #406), écriture/cache ids
  à venir
- premier-hit documenté (parseFirstThreadId, à re-trancher avant
  écriture), paramètres réels de la requête de découverte documentés
- trous de vérification complétés : storage réel jamais observé,
  risque faux NotFound → doublon à l'écriture

Pages canoniques actées :
- architecture.md : politique cache MP 3 étages + exception prefetch
  MP bornée (notes « Proposition en cours » remplacées)
- protocol-hfr.md : exception prefetch actée + contrat nonlu.php
  vérifié live (#361)
- models.md § MPStorage : référence ADR-014
- adr/README.md : retrait des mentions (Proposé)

Demandé par @xatrix

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
XaaT and others added 2 commits June 12, 2026 21:38
#449)

* fix(editor): le champ suit le curseur pendant la frappe (refs-#447 point 1)

Le champ en scroll externe (#422/#434) ne demandait jamais au scrollable
ancêtre de suivre le caret en frappe — Compose ne câble ce suivi que
quand le TextField possède son propre scroll interne. Retour bêta-dev
v123 de Dintr-un lemn (post #2787456).

- BbcodeFieldImpl réécrit : BasicTextField (overload TextFieldValue,
  qui expose onTextLayout — M3 OutlinedTextField ne l'expose pas) +
  OutlinedTextFieldDefaults.DecorationBox pour la parité visuelle
  (label flottant, bordure focus, couleurs M3).
- BringIntoViewRequester attaché au nœud texte INTERNE (le caret rect
  de TextLayoutResult.getCursorRect est dans son espace de coordonnées,
  pas celui de la boîte décorée), déclenché sur (focus, sélection,
  layout) — jamais sur value.text seul (layout périmé).
- Parité M3 répliquée : semantics(mergeDescendants) + padding(top=8.dp)
  (réserve la moitié haute du label flottant — vérifié rogné sans),
  textStyle onSurface, caret primary, capitalisation Sentences.
- Test Robolectric : sélection déplacée en fin de contenu long →
  le viewport scrolle (>0) pour révéler le caret.

Vérifié sur émulateur : 30 lignes tapées, le viewport suit et le caret
reste visible au-dessus de l'IME ; label flottant entier.

Le point 2 de #447 (drag-to-scroll de sélection) reste ouvert —
structurellement plus coûteux, traité séparément.

Demandé par @xatrix

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(editor): durcir la preuve du suivi de caret (review Codex)

2 IMPORTANT + 1 MINOR de la review Codex sur #449 :

- L'assertion « scrollAfter > scrollBefore » prouvait un scroll, pas la
  révélation du caret : un requester accroché au mauvais ancêtre (ou un
  rect lu dans l'espace de la boîte décorée) sous-scrollerait d'un
  offset constant et passait quand même. Caret en DERNIÈRE ligne =>
  assert scrollAfter >= 95% du maxValue du range.
- Le mode défaut (fillViewport=false dans un verticalScroll externe,
  layout TopicFormScreen) n'était pas couvert : 2e test avec Column
  scrollable externe taguée, même contrat.
- Commentaires resserrés : le wrapper Box du requester est offset-free
  PAR CONTRAT (son origine coïncide avec celle du layout texte).

5/5 tests BbcodeTextFieldViewportTest verts + detektAll.

Demandé par @xatrix

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ay (#450)

- Entrée re-numérotée v124 → v126 (ledger 125+1 : les builds dev v124
  et v125 sont passés depuis), plage dogfood v114 → v125.
- Ajout du dernier round : suivi du curseur en frappe (#447 point 1)
  et ellipse de l'onglet « Mes sujets » (#446).
- Notes Play fr/en : mention du fix curseur (474/403 octets, < 500).

Demandé par @xatrix

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@XaaT

XaaT commented Jun 12, 2026

Copy link
Copy Markdown
Contributor Author

Action par Claude Fable 5 (demandée par @xatrix)

Dernier tour pré-merge (multi-agents + Codex)

Verdict : SHIP — 0 bloquant sur 33 commits.

5 agents de review (bugs/intégration cross-PR, historique git, promesses des PRs passées, contrats des commentaires de code, conformité AGENTS.md) :

Review Codex (rapport complet) : 0 BLOCKER, 2 IMPORTANT sur le badge MP, tracés en issues de suivi :

  1. Badge MP : l'opt-out doit couper le travail réseau, pas seulement l'affichage #452 — l'opt-out du badge ne coupe pas le fetch réseau sous-jacent (gaspillage, pas de risque d'état serveur : GET liste MP inerte, MP — investigation contrat serveur lu/non-lu : effet du GET authentifié + granularité « marquer comme non lu » (faisabilité cache/prefetch, #351) #361).
  2. Badge MP : compteur périmé après lecture du dernier MP non lu (pas de refresh à la lecture) #453 — badge périmé après lecture du dernier MP non lu (pas de refresh/décrément local à la lecture).
  3. MINOR whitespace de fixtures — hygiène, non gaté par la CI, ignoré.

Métadonnées release cohérentes : versionName 0.10.0, changelog v126, whatsnew < 500 octets. CI verte (6m45).

La promotion 0.9.0 (#400) a laissé sur main des commits de bump/changelog
(11935ae, e8cc413) reportés textuellement sur dev par #401 : les deux
branches divergeaient sur app/CHANGELOG.md et app/build.gradle.kts, et
la promotion 0.10.0 (#451) ne pouvait pas créer son merge commit.

Résolution : contenu dev conservé tel quel pour les deux fichiers — la
version dev est strictement plus récente (v113 statut « open » + sha de
promotion via #401, correction « 7 PR » d'e8cc413e incluse, entrée v126,
versionName 0.10.0 + stamp -dev). L'arbre résultant est identique à dev.

Avec main contenu dans dev, #451 et les promotions futures mergent
proprement.

Demandé par @xatrix

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
chore(sync): merge main into dev — réconcilier l'historique 0.9.0
@XaaT XaaT merged commit fe6bda5 into main Jun 12, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment