Release/0.6.2 - #341
Merged
Merged
Conversation
chore: back-merge main into develop
L'horloge de timeline est gelée pendant tout un défi (choix assumé : le défi ne consomme pas de temps de séance). C'est pourtant elle que l'écran de fin affichait et que `_finish` créditait au temps de jeu cumulé — un défi de dix minutes ne rapportait donc ni point de spécialisation, ni déblocage de coach, ni badge d'endurance, et la séance était rapportée plus courte que ce que la joueuse venait de jouer. `playedSeconds` porte désormais le temps réellement passé en séance, pauses exclues. Le gel de la timeline est inchangé : il continue de piloter le contenu.
Le générateur réserve après chaque trigger de défi une enveloppe `breath + nominalDurationSeconds` où il ne pose aucun contenu, puisque le défi y sera joué. Cette enveloppe était prise sur la durée demandée, et le runtime la retirait *encore* de la durée de séance (`_excisChallengeFromSession`) : la joueuse la payait deux fois. Une séance « Moyenne » (1500 s) avec les 3 défis du retour utilisateur — dont un d'endurance à 530 s — ne rendait plus que ~980 s de contenu. `GenerationContext.challengeReserveSeconds` cumule ces fenêtres : elles décalent d'autant la fin de la boucle main et sont retranchées de `progress`, si bien que le contenu et la courbe de difficulté restent ceux de la durée demandée. La séance dure donc le temps demandé plus le temps des défis.
Les notes de version 0.6.0 annoncent les postures imposées et les pauses scénarisées, mais leur tirage était conditionné à un toggle de la section debug de l'écran SONS, off par défaut : la fonctionnalité était invisible pour tout le monde depuis deux versions. Le réglage devient une préférence utilisateur assumée (`pref.scripted_breaks`, on par défaut, section « Déroulé de séance » du Profil) au lieu d'un flag debug. L'ancienne clé `debug.scripted_breaks` est abandonnée plutôt que migrée : la reprendre rendrait le off historique collant. Les presets de debug `reset` et `mi_carriere_profondeur`, qui recopiaient les défauts d'usine, sont réalignés sur le nouveau défaut.
…vancé Le coach Marc a coûté un correctif en 0.6.1 : son palier de déblocage était franchissable une seule fois, et qui l'avait dépassé avant sa sortie restait verrouillé définitivement. Les 5 milestones `intro_posture_*` (levels 4 à 12) sont exposées au même risque pour un profil déjà au-delà. Ces tests tournent sur le vrai catalogue `assets/career/milestones.json` et établissent que `level` est un plancher et non une fenêtre, que les postures remontent en tête de file (règle overdue), et qu'il faut 3 séances moyenne/longue — 5 courtes — pour les débloquer toutes. Ils caractérisent aussi l'exigence de capacité de `intro_posture_on_back` (`hold.full.streak ≥ 5`), seul palier qui attend une preuve physique.
… une pause La sortie d'une pause scénarisée qui impose une nouvelle posture arme un gate de mise en place : `_checkSteps` ne consomme plus de step et `_onTick` gèle l'horloge jusqu'à la validation. Trois chemins traversaient cette fenêtre sans jamais passer par `_exitBreak`, donc sans lever le gate — il survivait au saut de timeline et le gel délibéré devenait définitif (ticker actif, plus rien qui avance, jusqu'au timeout de 90 s) : - « je peux pas » pendant la pause ou pendant l'attente de mise en place — le bouton n'est masqué par aucun des deux états ; - « Utilise-moi » et le retry milestone, via `requestUpgrade` (qui remettait déjà l'état de pause à zéro, mais pas le gate) ; - la régénération post-défi, par symétrie. Le bouton reste actif dans les deux états : c'est la seule sortie visible en séance, et une pause de 60 à 120 s suivie d'une attente n'est pas un moment où la priver serait défendable. Le flow d'échec annule la pause au lieu de la subir — sans appliquer sa posture, dont l'annonce ne sera pas prononcée. Corrige aussi le retour d'arrière-plan pendant une pause : `resume()` relançait l'audio d'effort coupé par l'entrée dans la pause. Chemin atteignable par tout le monde, une notification suffit. Les 4 tests runtime ajoutés sont rouges sans ces correctifs.
`challenges` était découpée sur le curseur de file, qui avance aussi quand un défi est écarté faute de place, alors que `challengeTriggerTimes` ne reçoit une entrée que pour les défis insérés. Le contrôleur indexe les deux listes par la même position : dès qu'un défi était écarté, `_updateChallengePhase` sortait des bornes dans le ticker principal, où une exception ne s'auto-annule pas — gel de séance ou plantage. Le générateur ne livre plus que les défis réellement insérés, et la boucle d'armement se borne à la plus courte des deux listes. Cas structurel : les trigger times sont répartis à 20/50/80 % du budget sans tenir compte de la taille du défi, donc un défi de 530 s tiré en dernier n'a jamais la place — d'où le désalignement.
Un défi ne s'arme que si l'horloge traverse sa fenêtre `[trigger, trigger + 13 s)` pendant qu'un tick tourne. La reprise après un fail saute à la prochaine section : quand le fail tombait pile dans cette fenêtre, le saut passait par-dessus et le défi disparaissait sans un mot — N défis demandés au menu, N-1 joués. Le saut de reprise est désormais borné à l'ouverture de la fenêtre du prochain défi encore armable ; quand elle est déjà ouverte, on ne saute pas du tout (le défi s'arme au tick suivant et gèle l'horloge lui-même).
Le défi ne prend rien à la séance, il s'y ajoute : une « Moyenne » à 3 défis annonçait « 25 min » pour ~35 min vécues. La durée affichée sous le sélecteur porte désormais la mention quand les défis sont activés — « 25 min + le temps des défis » — dans les 4 langues. La mention va sur la ligne pleine largeur et non dans les cartes du sélecteur : à 5 cartes sur un écran de 360 dp il reste ~42 dp de texte par carte, où « Herausforderungen » ne rentre pas.
Le gel de mise en place était un drapeau armé à la sortie d'une pause et levé par un seul chemin nominal. Quatre chemins distincts l'ont laissé orphelin derrière eux, et chaque correctif ciblé n'a jamais rien dit du suivant : on cesse de fermer des chemins. `PostureGate` porte désormais ce qui *justifie* le gel — identité de la timeline, tête de lecture, décalage d'horloge, numéro de flow d'échec — et `awaitingPostureReady` rejoue cette justification à chaque lecture. Les trois premières composantes sont les seules variables qui portent la progression d'une séance : rebattre la timeline sans en toucher aucune est impossible. Un chemin qui la rebat n'a donc rien à lever, aujourd'hui comme demain — les trois levées explicites de la passe 2 sont retirées. Une garde centrale enterre à chaque battement une ancre dont la justification est tombée (timeout de sécurité compris), pour qu'un gel enterré ne ressuscite pas si sa justification redevenait vraie. Ferme au passage le quatrième chemin, jamais corrigé : un défi armé au même tick qu'un ordre de posture n'empile plus son bandeau sur lui, et sa clôture ne laisse plus la séance gelée.
Les horaires de pause et de défi étaient calculés par deux fonctions indépendantes qui visaient toutes deux le milieu de la séance. Le trou d'effort de la pause avalait le trigger du défi médian, que la boucle de génération réémettait à `break.endTime` : l'ordre de se mettre en position et l'annonce du défi tombaient au même instant. Le planificateur de pauses reçoit les créneaux de défi et cède le passage — les horaires de défi ne bougent pas. Sans défi, les horaires nominaux sont inchangés. Balayage sur le générateur réel (8 durées × 4 comptes de défis × 60 graines, 1920 générations) : 175 collisions avant, 0 après.
`_excisChallengeFromSession` reconstruit la séance sans passer `breaks:` ni `initialPose:` — le défaut par défaut de `Session.breaks` est `const []`, donc toute la mise en scène restante disparaissait dès qu'un défi avait eu lieu, sur la fonctionnalité qu'on est en train d'allumer. La liste garde sa longueur (`_nextBreakIndex` pointe dedans) et ses horaires suivent le même décalage que les steps.
…éance `stop()` et `_finish()` remettaient la timeline à zéro mais laissaient courir le `Timer` de sécurité du gel de posture : ~90 s plus tard, il réveillait un contrôleur devenu inactif ou l'écran de fin. Il n'y a pas de gel à lever — il est tombé de lui-même — juste une ressource à libérer, aux trois bornes de vie d'une séance (`start` le faisait déjà). Entre elles, c'est la garde du battement qui s'en charge.
Le contrôle de place était devenu auto-contradictoire : depuis que l'enveloppe d'un défi est reportée sur `genUntil`, insérer un défi ne coûte plus de contenu, donc `remaining = genUntil - time` est invariant sous l'insertion — le test réclamait une place que l'insertion crée elle-même. Un défi de 530 s tiré en dernier n'entrait jamais : une séance sur trois perdait son plus gros défi, sans un mot. Contrepartie assumée et annoncée : la séance dure la durée choisie plus le temps des défis. Mesuré sur 200 tirages, une « Bâclée » (360 s nominal, 456 s générés sans défi) avec le trio du retour utilisateur passe de 516 s à 1059 s — 17,6 min médianes. Le taux d'insertion est de 100 % sur 4800 générations croisant 10 niveaux, 4 paliers et l'ordre de tirage.
Puisque tous les défis entrent désormais, la joueuse doit savoir au moment où elle active le réglage que sa séance dépassera le format choisi. La note complète le texte du réglage dans les 4 langues. La mention portée par la ligne de durée tenait sur deux lignes avec un libellé d'exemple mais pas avec les vrais : « Zeit für Herausforderungen » poussait la carte à trois lignes sur les 5 paliers allemands. Formulation allemande resserrée, et le test porte désormais sur les libellés réels de tous les paliers — dont « Überraschung », qui n'est pas un nombre.
La borne défensive de la boucle d'armement empêche le plantage si `challenges` et `challengeTriggerTimes` divergeaient à nouveau, mais elle abandonnait le défi orphelin en silence complet : personne ne l'aurait su avant qu'une joueuse ne le signale — ce qui vient d'arriver. Une ligne, une seule fois par séance : la boucle tourne à chaque tick.
… les stats Décision produit : l'horloge qui décide de la fin de séance ignore le temps passé en défi — la joueuse joue la durée de contenu qu'elle a choisie — tandis que le chiffre rendu en fin de séance et le temps crédité aux statistiques comptent tout, défis compris. C'est ce que fait `297abe4` au milieu d'un lot de correctifs ; ce test énonce l'invariant pour lui-même. Il casse sur les deux neutralisations (crédit ramené sur l'horloge gelée : 7 s au lieu de 21 ; panneau de fin recâblé sur `elapsedSeconds`). Le panneau de fin est privé à `session_screen.dart` et une séance ne tourne pas sous horloge simulée : à défaut de lire le chiffre rendu, la clause d'affichage verrouille la ligne qui l'alimente.
…le levait La garde ne cherchait que la chaîne `confirmPostureReady`. Tous les fichiers du contrôleur sont des `part of` : écrire `_postureGate = null` depuis n'importe lequel d'entre eux est valide et passait au travers sans un mot — exactement le motif que ces passes ont servi à éliminer. Elle scanne désormais les écritures du champ dans toute sa library privée et exige que l'ensemble des membres qui y touchent soit exactement les six sites légitimes : naissance de l'ancre, validation joueuse, ramasse-miettes du battement, et les trois bornes de vie d'une séance.
La clause d'identité protège contre le remplacement de la séance, pas contre une mutation en place de ses listes. Aucun code ne le fait aujourd'hui, rien ne l'empêche demain et rien ne le détecterait : le noter là où la décision vit plutôt que de laisser « par définition » porter plus qu'il ne prouve.
L'ampleur d'un défi « durée » vaut `comfort × kChallengeOverloadFactor` et n'avait aucun plafond — contrairement aux défis de vitesse (bornés par le BPM maximum du moteur) et de profondeur (bornés par le nombre de crans). Or `comfort` ne fait que monter avec la pratique : sur le profil du retour utilisateur 0.6.1 une Bâclée annoncée 6 minutes en durait 15, et rien ne bornait la suite (73 minutes à `comfort = 3000`). Le plafond est dérivé du palier : la part `kChallengesShareOfFormat` de la durée annoncée, divisée par le nombre d'événements que le palier planifie. Comme un palier ne demande jamais plus de défis qu'il ne planifie d'événements, la somme des défis d'une séance ne peut plus dépasser la moitié de la durée annoncée, quel que soit le format et quel que soit l'état du catalogue de milestones. Le tutoriel (5 s) et les défis exploratoires (5-30 s) passent très en dessous du plus petit plafond (180 s) : le plafond ne mord que sur les axes durée d'un profil déjà entraîné. Tronquer la cible tronque aussi la valeur créditée au profil, qui passe alors sous le `comfort` — la régulation y est neutre, ni le record ni la cible ne bougent. La mesure n'est pas faussée, elle est rendue optionnelle : « JE TIENS ENCORE » reste ouverte pour la reprendre, sur décision de la joueuse. Le paramètre est requis plutôt que défaillant : un appelant qui l'oublie ne doit pas retomber silencieusement sur l'ancien comportement sans borne.
… redit le temps Le sous-titre annonçait « un défi vers 60 % de la séance » alors que le palier en pose jusqu'à quatre, répartis entre 20 % et 80 % de la séance. Faux dans les quatre langues, et faux juste à côté d'une phrase qu'on venait de rendre exacte. Le nombre annoncé est désormais celui des paliers, avec une garde qui casse si un palier planifie plus d'événements que les textes n'en annoncent. La mention courte allemande avait perdu le mot « Zeit » en se resserrant : « 12 Min. + Herausforderungen » se lit comme une énumération — durée et défis compris — c'est-à-dire l'inverse de ce qu'elle veut dire, quand le français, l'anglais et l'espagnol gardent tous les trois le temps. Aucune formulation gardant `Herausforderung` en entier ne tient : mesurées aux 5 paliers × 4 largeurs, « Herausforderungszeit », « Zeit für Herausforderungen » et « zzgl. Herausforderungen » débordent toutes à trois lignes — le mot seul occupe déjà une ligne complète. « Challenge-Zeit » tient partout et garde les deux notions ; « Challenge » se lit sans effort comme le synonyme du toggle affiché juste au-dessus. Le test de mise en page porte maintenant sur les quatre largeurs : le resserrement d'un texte allemand se relit sur toute la gamme visée, pas seulement sur l'écran le plus étroit.
…le source La clause d'affichage se verrouillait sur la ligne qui alimente le panneau de fin, faute de pouvoir lire le chiffre rendu : le panneau est privé à `session_screen.dart` et `Stopwatch` n'est pas simulé par `flutter_test`. Elle se lit maintenant sur le texte que l'écran affiche vraiment. Deux points rendaient le montage possible. `debugFinishSuccess()` clôt une séance par arithmétique en passant par le même chemin de fin que le jeu réel, et un bouton de debug l'appelle déjà depuis l'écran. Et une boucle qui avance les deux horloges ensemble — le temps du mur sous `runAsync` pour le `Stopwatch`, l'horloge simulée par `pump` pour le ticker — fait tourner une séance dans un test de widget. Le défi, lui, se joue en temps réel : c'est le gel de la timeline pendant le défi qui fait diverger les deux horloges, et sans divergence les deux câblages rendraient le même chiffre. Le test coûte 16 s et casse bien sur la neutralisation : « Durée : 7 s » au lieu des 15 s jouées.
Intègre le lot « durée de séance faussée par les défis » (13 commits) avant que celui-ci ne parte vers develop, pour que la demande de fusion des postures soit propre une fois la première intégrée. Deux conflits, résolus en gardant de chaque côté ce dont il a raison. session_controller_challenge.dart — bloc de reconstruction après un défi : `initialPose` et `breaks` viennent des postures (sans ce report, `Session.breaks` retombe à `const []` et la mise en scène restante disparaît dès qu'un défi se termine). Le commentaire vient des défis : celui des postures affirmait que l'excision retire « 2 steps (breath + défi) », faux depuis la refonte des défis en streaming où un seul step tombe dans la timeline. career_session_generator.dart — appel à `_computeBreakTriggerTimes` : les deux paramètres survivent, `genUntil: ctx.genUntil` (accesseur dérivé) **et** `challengeWindows`. À ce point du code la réserve de défis vaut encore 0, donc `ctx.genUntil` égale la valeur qu'utilisait la branche postures — pas de changement de sens. `_estimatedChallengeDuration` gardant sa signature, les fenêtres de défi héritent d'office de la durée désormais plafonnée par format. Vérifié sur l'arbre fusionné : 978 tests verts (818 base + 29 postures + 131 défis, égalité exacte : aucun test perdu), `flutter analyze` propre, format inchangé, l10n générés conformes à `gen-l10n`. Collisions pauses/défis : 0 sur 4800 générations à défis courts et 0 sur les formats du catalogue, hors le seul couple `longue` × 4 défis au plafond (4 cas sur 500) — identiques avant fusion. Durées avec défis : 10,8 min en express, 39,7 min en moyenne.
Fix/duree seance defis
Fix/postures debloquees
…l'app Les deux curseurs du Profil poussaient au moteur sans rien enregistrer : perdus au redémarrage, et remplacés par les défauts plateforme dès qu'une séance carrière rendait la main. Même défaut que celui corrigé en 0.6.1 sur le choix de voix, sur les deux réglages voisins. `setUserRate` / `setUserPitch` prennent le rôle que `setUserVoice` tient face à `setVoiceByName` : seules elles mémorisent, les presets coach continuent de passer par `setRate` / `setPitch` sans écrire. Le réglage est global et non par langue — un nom de voix appartient à sa langue, un débit est un confort d'écoute. Le test glisse les vrais curseurs : 4 de ses 6 cas sont rouges sur le code d'avant, les 2 autres tiennent l'arbitrage (défaut plateforme conservé quand rien n'est réglé, réglage insensible au changement de langue).
L'import est l'inverse de l'export : reposer la voix d'un profil sans son débit laissait un état vocal mixte, et un export « complet du profil persisté » devenait faux dès que ces deux réglages ont commencé à l'être. Le fichier de partage des voix les porte donc lui aussi. C'est une décision, pas un effet de bord : le test de non-fuite exige de nommer chaque champ et sa provenance, les deux nombres y sont déclarés comme venant de leur clé et d'elle seule, et le seed les écarte de leurs défauts pour que la provenance soit réellement éprouvée.
…a fiche Deuxième moitié du réglage ouvert en 0.6.1 : le timbre se choisissait déjà, la couleur vocale restait celle du JSON. Or c'est souvent elle qui cloche, et seule l'oreille de la joueuse peut en juger — la raison même qui avait rendu le choix de voix manuel. Les deux curseurs sont posés **à même la fiche**, pas repliés derrière un second geste : décision de Manu, 2026-08-18. Un bouton « vitesse et hauteur d'origine », affiché en permanence et inerte tant que rien n'a bougé, rend au coach son preset — sans lui, déplacer un curseur est un aller sans retour. Côté service, l'override est résolu **une seule fois en tête** de `applyCoachVoicePreset` : elle a quatre sorties, chacune avec son propre couple débit/hauteur, et une résolution par sortie serait quatre occasions d'en oublier une. Un test couvre spécifiquement la sortie anticipée (voix mémorisée trouvée) — vérifié rouge en cassant ce site-là seul. Coût de « visible » : la feuille gagne ~130 px, la liste de voix perd d'autant et défile plus tôt. Les tests qui tapaient une voix passent désormais par un défilement — tous, pas seulement les deux qui cassaient.
…oach Sans ça, l'asymétrie était de mon fait : la voix d'un coach s'exportait et se réimportait, la vitesse et la hauteur que je viens de rendre réglables non — un import aurait reposé le timbre d'un profil sur la couleur locale. Les deux nombres sont répétés sur chaque entrée de langue du coach alors qu'ils n'en dépendent pas. C'est la règle déjà en vigueur dans ce fichier (`platform`, `language`) : l'unité qu'on agrège est l'entrée, et une entrée doit se lire seule une fois sortie de sa section.
Reprise telle quelle de `test/sondes-adversariales-constats` (commit 13a3b0e) comme point de départ mesuré. Elle est VERTE 6/6 sur `origin/develop` : ce n'est pas un test rouge à faire passer, c'est un test de *caractérisation* qui fige le comportement actuel, défaut compris (`expect(rhythm, 0)` sur la séance normale sous le seuil). Chiffres relevés sur `origin/develop`, avec comfort=mid / best=throat : - normal sr=0.50 : rhythm=0/300 hold=300/300 - normal sr=0.80 : rhythm=300/300 hold=300/300 - égalité sr=0.70 : rhythm=16/300 (5.3 %, ≈ 1/14 = la loterie de surcharge) - Encore sr=0.50 : rhythm=300/300 hold=300/300 - Encore sr=0.80 : rhythm=300/300 hold=300/300 - Utilise-moi 0.50 : rhythm=300/300 hold=300/300 Commité avant tout correctif pour que le diff suivant montre exactement quel `expect` bascule, et pourquoi.
Réapplication sur le code d'aujourd'hui de l'intention de 411feba (`origin/feat/depth-comfort-probe`, 101 commits de retard) : la profondeur rythmée vise **un cran au-dessus du comfort, borné par le `best` prouvé**. Les deux fonctions ciblées sont identiques à ce qu'elles étaient au merge-base (39cbcaa) — vérifié : `git diff 39cbcaa..origin/develop` est vide sur les deux fichiers. Le raisonnement d'origine tient donc tel quel. Deux écarts assumés par rapport à 411feba : - garde `max(comfort, …)` dans `capabilityCapFor` : un profil persisté où `best < comfort` verrait sinon la « sonde » RABAISSER le cap. Le `best` est monotone en mémoire, mais il est persisté séparément du `comfort` et rechargeable depuis un import — le cas n'est pas impossible. - `maxDepthIndexForProfile` formulé en `best.round() > rounded ? rounded+1 : rounded` (équivalent au `min` d'origine sur des entiers, non abaissant par construction). Le clamp lit `comfort` en double (le decay produit des crans fractionnaires), d'où le `min(comfort + 1, best)` conservé là — le consommateur arrondit, et `comfort=2.7 / best=3.0` doit donner throat, pas full.
Deux `expect` figeaient le défaut ; ils expriment maintenant la règle voulue. Ajout des cas qui bornent le correctif — c'est là que se joue « est-ce trop généreux ? » : un cran seulement (comfort=head/best=throat reste borné à mid, 0/300), no-op strict quand best == comfort (0/300), pas d'abaissement quand best < comfort, et le tableau unitaire de `maxDepthIndexForProfile`. Non-régression des tenues : deux invariants insensibles à la dérive RNG (les compteurs bruts ne sont PAS comparables step à step — déplacer un step rythmé fait diverger toute la séquence en aval) : la tranche throat sort toujours en tenue, et `full` reste fermé sans `fullHold` acquise. Discrimination vérifiée sur un worktree détaché sur origin/develop : - avec le correctif : 11/11 verts - sur origin/develop : 2 rouges (rhythm 0 au lieu de 300 ; maxDepthIndex 2 au lieu de 3), les 9 autres verts — ce sont les non-régressions.
Les sondes de profondeur existantes injectent un profil figé dans le générateur : elles prouvent que la tranche throat redevient visible, pas que le `comfort` remonte. Ce harnais referme le circuit — profil persisté relu par le générateur, séance rejouée dans le vrai `CapabilityTracker`, rapport repassé par le vrai `CapabilityService.commit`. Mesuré sur la branche : comfort=mid/best=throat remonte à throat à la 2ᵉ séance (5 bases de graines), une chute de deux crans en 3, un tap-out coûte un cran et la profondeur proposée redescend avec lui. Les 4 tests sont rouges sur origin/develop, où le comfort ne bouge pas de 20 séances.
La relecture adverse l'a relevé : la reformulation n'est pas équivalente au `min` d'origine, et l'écart tombe exactement sur le cas que l'autre fichier du correctif protège explicitement (`best < comfort`, écrit tel quel par l'import de profil). Sans cette note, un refactor qui « revient à la forme littérale » réintroduit la régression.
… la 0.6.2 Huit comptes rendus produits le 2026-08-18 (tri des constats, rejeu adversarial, correction du verrou de profondeur et sa relecture, réglage de débit et hauteur de voix et sa relecture, mesures de repli et de remontée du confort) n'étaient enregistrés nulle part. Claude-Session: https://claude.ai/code/session_01L4UqzJYEndHBCS3WBayUt6
docs(analysis): consigner les analyses et relectures du 18 août avant…
Feat/voice rate pitch
Fix/depth comfort probe 2
Journal des vingt-six changements accumulés depuis la 0.6.1, plus le réglage de voix par coach et le déblocage de la profondeur. Badges des huit README, exemple de version du gabarit de remontée, liens de comparaison du journal. 1015 tests verts, flutter analyze propre, format inchangé. Claude-Session: https://claude.ai/code/session_01L4UqzJYEndHBCS3WBayUt6
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.