[{"data":1,"prerenderedAt":2640},["ShallowReactive",2],{"navigation_docs_fr":3,"-fr-examples-offline-first":342,"-fr-examples-offline-first-surround":2635},[4,45,91,137,195,236,257,291,327],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":44},"Prise en main","i-lucide-rocket","/fr/getting-started","fr/01.getting-started",[10,15,20,24,29,34,39],{"title":11,"path":12,"stem":13,"icon":14},"Qu'est-ce que Qora ?","/fr/getting-started/what-is-qora","fr/01.getting-started/2.what-is-qora","i-lucide-info",{"title":16,"path":17,"stem":18,"icon":19},"Installation","/fr/getting-started/installation","fr/01.getting-started/3.installation","i-lucide-download",{"title":21,"path":22,"stem":23,"icon":6},"Démarrage rapide","/fr/getting-started/quick-start","fr/01.getting-started/4.quick-start",{"title":25,"path":26,"stem":27,"icon":28},"Comparaison","/fr/getting-started/comparison","fr/01.getting-started/5.comparison","i-lucide-scale",{"title":30,"path":31,"stem":32,"icon":33},"Venant de TanStack Query","/fr/getting-started/coming-from-tanstack-query","fr/01.getting-started/6.coming-from-tanstack-query","i-lucide-arrow-left-right",{"title":35,"path":36,"stem":37,"icon":38},"Migrer depuis d'Autres Librairies","/fr/getting-started/migrating-from-other-libraries","fr/01.getting-started/7.migrating-from-other-libraries","i-lucide-arrow-right-left",{"title":40,"path":41,"stem":42,"icon":43},"Assistants IA","/fr/getting-started/ai-assistants","fr/01.getting-started/8.ai-assistants","i-lucide-sparkles",false,{"title":46,"icon":47,"path":48,"stem":49,"children":50,"page":44},"Concepts clés","i-lucide-book-open","/fr/core-concepts","fr/02.core-concepts",[51,56,61,66,71,76,81,86],{"title":52,"path":53,"stem":54,"icon":55},"Requêtes","/fr/core-concepts/queries","fr/02.core-concepts/1.queries","i-lucide-database",{"title":57,"path":58,"stem":59,"icon":60},"Clés de requête","/fr/core-concepts/query-keys","fr/02.core-concepts/2.query-keys","i-lucide-key-round",{"title":62,"path":63,"stem":64,"icon":65},"États des requêtes","/fr/core-concepts/query-states","fr/02.core-concepts/3.query-states","i-lucide-activity",{"title":67,"path":68,"stem":69,"icon":70},"Cache","/fr/core-concepts/caching","fr/02.core-concepts/4.caching","i-lucide-layers-3",{"title":72,"path":73,"stem":74,"icon":75},"Stale-While-Revalidate","/fr/core-concepts/stale-while-revalidate","fr/02.core-concepts/5.stale-while-revalidate","i-lucide-timer-reset",{"title":77,"path":78,"stem":79,"icon":80},"Déduplication","/fr/core-concepts/deduplication","fr/02.core-concepts/6.deduplication","i-lucide-copy-minus",{"title":82,"path":83,"stem":84,"icon":85},"Mutations et invalidation","/fr/core-concepts/mutations","fr/02.core-concepts/7.mutations","i-lucide-pencil-line",{"title":87,"path":88,"stem":89,"icon":90},"Hors-ligne et réseau","/fr/core-concepts/offline","fr/02.core-concepts/8.offline","i-lucide-wifi-off",{"title":92,"icon":93,"path":94,"stem":95,"children":96,"page":44},"Usage avec Flutter","i-lucide-smartphone","/fr/flutter-integration","fr/03.flutter-integration",[97,102,107,112,117,122,127,132],{"title":98,"path":99,"stem":100,"icon":101},"Configuration","/fr/flutter-integration/setup","fr/03.flutter-integration/1.setup","i-lucide-wrench",{"title":103,"path":104,"stem":105,"icon":106},"QoraScope","/fr/flutter-integration/qora-scope","fr/03.flutter-integration/2.qora-scope","i-lucide-share-2",{"title":108,"path":109,"stem":110,"icon":111},"QoraBuilder","/fr/flutter-integration/qora-builder","fr/03.flutter-integration/3.qora-builder","i-lucide-blocks",{"title":113,"path":114,"stem":115,"icon":116},"QoraMutationBuilder","/fr/flutter-integration/qora-mutation-builder","fr/03.flutter-integration/4.qora-mutation-builder","i-lucide-pencil",{"title":118,"path":119,"stem":120,"icon":121},"InfiniteQueryBuilder","/fr/flutter-integration/infinite-query-builder","fr/03.flutter-integration/5.infinite-query-builder","i-lucide-infinity",{"title":123,"path":124,"stem":125,"icon":126},"Hooks (flutter_hooks)","/fr/flutter-integration/hooks","fr/03.flutter-integration/6.hooks","i-lucide-anchor",{"title":128,"path":129,"stem":130,"icon":131},"Widgets de statut réseau","/fr/flutter-integration/network-status","fr/03.flutter-integration/7.network-status","i-lucide-wifi",{"title":133,"path":134,"stem":135,"icon":136},"Bonnes pratiques","/fr/flutter-integration/best-practices","fr/03.flutter-integration/8.best-practices","i-lucide-badge-check",{"title":138,"icon":139,"path":140,"stem":141,"children":142,"page":44},"Guides Pratiques","i-lucide-chef-hat","/fr/recipes","fr/04.recipes",[143,147,152,157,162,166,171,176,181,186,191],{"title":144,"path":145,"stem":146,"icon":85},"Mutations","/fr/recipes/mutations","fr/04.recipes/02.mutations",{"title":148,"path":149,"stem":150,"icon":151},"Mises à jour optimistes","/fr/recipes/optimistic-updates","fr/04.recipes/03.optimistic-updates","i-lucide-zap",{"title":153,"path":154,"stem":155,"icon":156},"Persistance","/fr/recipes/persistence","fr/04.recipes/04.persistence","i-lucide-hard-drive",{"title":158,"path":159,"stem":160,"icon":161},"Réseau adaptatif","/fr/recipes/network-aware","fr/04.recipes/05.network-aware","i-lucide-signal",{"title":163,"path":164,"stem":165,"icon":121},"Requêtes infinies","/fr/recipes/infinite-queries","fr/04.recipes/06.infinite-queries",{"title":167,"path":168,"stem":169,"icon":170},"Requêtes dépendantes","/fr/recipes/dependent-queries","fr/04.recipes/07.dependent-queries","i-lucide-link-2",{"title":172,"path":173,"stem":174,"icon":175},"Annulation","/fr/recipes/cancel-token","fr/04.recipes/08.cancel-token","i-lucide-circle-slash",{"title":177,"path":178,"stem":179,"icon":180},"Hydratation SSR","/fr/recipes/ssr-hydration","fr/04.recipes/09.ssr-hydration","i-lucide-server",{"title":182,"path":183,"stem":184,"icon":185},"Gestion des erreurs","/fr/recipes/error-handling","fr/04.recipes/10.error-handling","i-lucide-shield-alert",{"title":187,"path":188,"stem":189,"icon":190},"Tests","/fr/recipes/testing","fr/04.recipes/11.testing","i-lucide-flask-conical",{"title":192,"path":193,"stem":194},"Prefetch au Niveau des Routes","/fr/recipes/prefetch","fr/04.recipes/12.prefetch",{"title":196,"icon":197,"path":198,"stem":199,"children":200,"page":44},"Référence de l'API","i-lucide-file-code","/fr/api-reference","fr/05.api-reference",[201,206,210,214,219,224,228,232],{"title":202,"path":203,"stem":204,"icon":205},"QoraClient","/fr/api-reference/qora-client","fr/05.api-reference/1.qora-client","i-lucide-cpu",{"title":207,"path":208,"stem":209,"icon":60},"QoraKey","/fr/api-reference/qora-key","fr/05.api-reference/2.qora-key",{"title":211,"path":212,"stem":213,"icon":65},"QoraState","/fr/api-reference/qora-state","fr/05.api-reference/3.qora-state",{"title":215,"path":216,"stem":217,"icon":218},"QoraOptions","/fr/api-reference/qora-options","fr/05.api-reference/4.qora-options","i-lucide-sliders-horizontal",{"title":220,"path":221,"stem":222,"icon":223},"QoraClientConfig","/fr/api-reference/qora-client-config","fr/05.api-reference/5.qora-client-config","i-lucide-settings-2",{"title":225,"path":226,"stem":227,"icon":85},"API Mutation","/fr/api-reference/qora-mutation","fr/05.api-reference/6.qora-mutation",{"title":229,"path":230,"stem":231,"icon":156},"PersistQoraClient","/fr/api-reference/persist-qora-client","fr/05.api-reference/7.persist-qora-client",{"title":233,"path":234,"stem":235,"icon":121},"Requête infinie","/fr/api-reference/infinite-query","fr/05.api-reference/8.infinite-query",{"title":237,"icon":238,"path":239,"stem":240,"children":241,"page":44},"Outils de debug","i-lucide-bug","/fr/devtools","fr/06.devtools",[242,247,252],{"title":243,"path":244,"stem":245,"icon":246},"Vue d'ensemble","/fr/devtools/overview","fr/06.devtools/1.overview","i-lucide-layout-dashboard",{"title":248,"path":249,"stem":250,"icon":251},"Overlay intégré","/fr/devtools/overlay","fr/06.devtools/2.overlay","i-lucide-layers",{"title":253,"path":254,"stem":255,"icon":256},"Sérialisation des données","/fr/devtools/data-display","fr/06.devtools/4.data-display","i-lucide-eye",{"title":258,"icon":259,"path":260,"stem":261,"children":262,"page":44},"Intégrations tierces","i-lucide-puzzle","/fr/third-party","fr/07.third-party",[263,268,272,277,282,286],{"title":264,"path":265,"stem":266,"icon":267},"Avec Provider","/fr/third-party/with-provider","fr/07.third-party/1.with-provider","i-lucide-plug",{"title":269,"path":270,"stem":271,"icon":111},"Avec BLoC","/fr/third-party/with-bloc","fr/07.third-party/2.with-bloc",{"title":273,"path":274,"stem":275,"icon":276},"Avec Riverpod","/fr/third-party/with-riverpod","fr/07.third-party/3.with-riverpod","i-lucide-droplets",{"title":278,"path":279,"stem":280,"icon":281},"Avec RxDart","/fr/third-party/with-rxdart","fr/07.third-party/4.with-rxdart","i-lucide-git-branch",{"title":283,"path":284,"stem":285},"Avec Dio","/fr/third-party/with-dio","fr/07.third-party/5.with-dio",{"title":287,"path":288,"stem":289,"icon":290},"Avec GraphQL","/fr/third-party/with-graphql","fr/07.third-party/6.with-graphql","i-lucide-binary",{"title":292,"icon":190,"path":293,"stem":294,"children":295,"page":44},"Exemples","/fr/examples","fr/08.examples",[296,301,305,310,314,318,322],{"title":297,"path":298,"stem":299,"icon":300},"Étude de cas : Liste et détail utilisateur","/fr/examples/basic-query","fr/08.examples/1.basic-query","i-lucide-play-circle",{"title":302,"path":303,"stem":304,"icon":151},"Étude de cas : Mutations optimistes et rollback","/fr/examples/mutations-optimistic","fr/08.examples/2.mutations-optimistic",{"title":306,"path":307,"stem":308,"icon":309},"Étude de cas : Feed à défilement infini","/fr/examples/infinite-scroll","fr/08.examples/3.infinite-scroll","i-lucide-scroll",{"title":311,"path":312,"stem":313,"icon":90},"Étude de cas : Application Todo offline-first","/fr/examples/offline-first","fr/08.examples/4.offline-first",{"title":315,"path":316,"stem":317,"icon":161},"Étude de cas : Conscience réseau","/fr/examples/network-awareness","fr/08.examples/5.network-awareness",{"title":319,"path":320,"stem":321,"icon":126},"Étude de cas : Intégration des hooks","/fr/examples/hooks-integration","fr/08.examples/6.hooks-integration",{"title":323,"path":324,"stem":325,"icon":326},"Étude de cas : Application Todo complète","/fr/examples/complete-todo-app","fr/08.examples/7.complete-todo-app","i-lucide-check-square",{"title":328,"icon":329,"path":330,"stem":331,"children":332,"page":44},"Aide","i-lucide-life-buoy","/fr/help","fr/09.help",[333,338],{"title":334,"path":335,"stem":336,"icon":337},"FAQ","/fr/help/faq","fr/09.help/1.faq","i-lucide-circle-help",{"title":339,"path":340,"stem":341,"icon":101},"Dépannage","/fr/help/troubleshooting","fr/09.help/2.troubleshooting",{"id":343,"title":311,"body":344,"description":2626,"extension":2627,"links":2628,"meta":2629,"navigation":2630,"path":312,"seo":2631,"stem":313,"__hash__":2634},"docs_fr/fr/08.examples/4.offline-first.md",{"type":345,"value":346,"toc":2612},"minimark",[347,352,356,369,372,385,388,392,463,465,469,487,497,863,910,913,1160,1162,1166,1171,1180,1773,1782,1786,1796,2159,2163,2169,2181,2185,2199,2515,2517,2521,2540,2542,2546,2552,2560,2562,2566,2608],[348,349,351],"h2",{"id":350},"objectif","Objectif",[353,354,355],"p",{},"La connexion mobile coupe sans arrêt : tunnels, ascenseurs, mode avion. Cet exemple montre une application de todos qui reste utilisable malgré tout :",[357,358,359,363,366],"ul",{},[360,361,362],"li",{},"la liste s'affiche depuis le cache, et depuis le stockage au démarrage à froid ;",[360,364,365],{},"l'UI sait quand elle est hors-ligne, au lieu de tourner indéfiniment ;",[360,367,368],{},"les todos créés, cochés ou supprimés hors-ligne apparaissent immédiatement et sont envoyés dans l'ordre au retour du réseau.",[353,370,371],{},"Aucun code de synchronisation maison : tout ce qui suit est de la configuration Qora et deux widgets.",[373,374,375,379,380,384],"tip",{},[376,377,378],"strong",{},"Testez sans toucher à votre Wi-Fi."," L'exemple fournit un ",[381,382,383],"code",{},"SimulatedConnectivityManager"," : le bouton antenne de l'app bar bascule l'application entre en ligne et hors-ligne, et la fausse API échoue tant que vous êtes hors-ligne.",[386,387],"hr",{},[348,389,391],{"id":390},"fonctionnalités-démontrées","Fonctionnalités démontrées",[357,393,394,409,417,427,439,447,455],{},[360,395,396,400,401,404,405,408],{},[376,397,398],{},[381,399,229],{}," : les fetchs réussis sont écrits dans un ",[381,402,403],{},"StorageAdapter"," et restaurés par ",[381,406,407],{},"hydrate()"," avant la première frame",[360,410,411,416],{},[376,412,413],{},[381,414,415],{},"NetworkMode.offlineFirst"," : les données en cache sont servies immédiatement ; les fetchs reprennent à la reconnexion",[360,418,419,422,423,426],{},[376,420,421],{},"File de mutations hors-ligne"," : ",[381,424,425],{},"offlineQueue: true"," met en file les écritures faites hors-ligne et les rejoue dans l'ordre FIFO à la reconnexion",[360,428,429,438],{},[376,430,431,434,435],{},[381,432,433],{},"onMutate"," + ",[381,436,437],{},"onError"," : le nouveau todo est écrit tout de suite dans le cache de la liste, puis retiré si le serveur le rejette",[360,440,441,446],{},[376,442,443],{},[381,444,445],{},"FetchStatus.paused"," : permet à l'UI de distinguer « pas de réseau » de « chargement »",[360,448,449,454],{},[376,450,451],{},[381,452,453],{},"NetworkStatusIndicator"," : bandeau hors-ligne global",[360,456,457,462],{},[376,458,459],{},[381,460,461],{},"ReconnectStrategy"," : limite les refetchs simultanés et ajoute du jitter au retour du réseau",[386,464],{},[348,466,468],{"id":467},"architecture","Architecture",[353,470,471,473,474,422,476,479,480,479,483,486],{},[381,472,229],{}," étend ",[381,475,202],{},[381,477,478],{},"fetchQuery",", ",[381,481,482],{},"watchQuery",[381,484,485],{},"invalidate"," et les widgets fonctionnent sans changement. Le reste de l'application ignore que la persistance existe.",[488,489,495],"pre",{"className":490,"code":492,"language":493,"meta":494},[491],"language-text","main.dart\n├── InMemoryStorageAdapter().init()    ← backend de stockage (à remplacer par votre adaptateur)\n├── SimulatedConnectivityManager(api)  ← bascule en ligne / hors-ligne pour la démo\n├── PersistQoraClient(storage, ...)    ← remplace QoraClient sans autre changement\n├── registerAllSerializers(client)     ← tous les types écrits dans le stockage\n├── client.hydrate()                   ← restaure les données avant runApp\n└── QoraScope(\n      client: client,\n      lifecycleManager: FlutterLifecycleManager(qoraClient: client),\n      connectivityManager: connectivity,\n      child: App(),                    ← OfflineBannerWrapper dans MaterialApp.builder\n    )\n","text","",[381,496,492],{"__ignoreMap":494},[488,498,503],{"className":499,"code":500,"filename":501,"language":502,"meta":494,"style":494},"language-dart shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","final storage = InMemoryStorageAdapter();\nawait storage.init();\n\nfinal api = TodoApi();\nfinal connectivity = SimulatedConnectivityManager(api);\n\nfinal client = PersistQoraClient(\n  storage: storage,\n  persistDuration: const Duration(days: 30),\n  config: QoraClientConfig(\n    defaultOptions: const QoraOptions(\n      staleTime: Duration(minutes: 5),\n      networkMode: NetworkMode.offlineFirst,\n    ),\n    // Au plus 3 refetchs simultanés à la reconnexion, 150 ms de jitter entre eux.\n    reconnectStrategy: const ReconnectStrategy(\n      maxConcurrent: 3,\n      jitter: Duration(milliseconds: 150),\n    ),\n    debugMode: kDebugMode,\n  ),\n);\n\nregisterAllSerializers(client);\nawait client.hydrate(); // les todos sauvegardés sont en cache avant la première frame\n","lib/main.dart","dart",[381,504,505,532,553,560,577,595,600,616,630,658,671,686,708,726,734,741,756,769,791,798,811,819,826,831,842],{"__ignoreMap":494},[506,507,510,514,518,522,526,529],"span",{"class":508,"line":509},"line",1,[506,511,513],{"class":512},"spNyl","final",[506,515,517],{"class":516},"sTEyZ"," storage ",[506,519,521],{"class":520},"sMK4o","=",[506,523,525],{"class":524},"sBMFI"," InMemoryStorageAdapter",[506,527,528],{"class":516},"()",[506,530,531],{"class":520},";\n",[506,533,535,539,542,545,549,551],{"class":508,"line":534},2,[506,536,538],{"class":537},"s7zQu","await",[506,540,541],{"class":516}," storage",[506,543,544],{"class":520},".",[506,546,548],{"class":547},"s2Zo4","init",[506,550,528],{"class":516},[506,552,531],{"class":520},[506,554,556],{"class":508,"line":555},3,[506,557,559],{"emptyLinePlaceholder":558},true,"\n",[506,561,563,565,568,570,573,575],{"class":508,"line":562},4,[506,564,513],{"class":512},[506,566,567],{"class":516}," api ",[506,569,521],{"class":520},[506,571,572],{"class":524}," TodoApi",[506,574,528],{"class":516},[506,576,531],{"class":520},[506,578,580,582,585,587,590,593],{"class":508,"line":579},5,[506,581,513],{"class":512},[506,583,584],{"class":516}," connectivity ",[506,586,521],{"class":520},[506,588,589],{"class":524}," SimulatedConnectivityManager",[506,591,592],{"class":516},"(api)",[506,594,531],{"class":520},[506,596,598],{"class":508,"line":597},6,[506,599,559],{"emptyLinePlaceholder":558},[506,601,603,605,608,610,613],{"class":508,"line":602},7,[506,604,513],{"class":512},[506,606,607],{"class":516}," client ",[506,609,521],{"class":520},[506,611,612],{"class":524}," PersistQoraClient",[506,614,615],{"class":516},"(\n",[506,617,619,622,625,627],{"class":508,"line":618},8,[506,620,621],{"class":516},"  storage",[506,623,624],{"class":520},":",[506,626,541],{"class":516},[506,628,629],{"class":520},",\n",[506,631,633,636,638,641,644,647,649,653,656],{"class":508,"line":632},9,[506,634,635],{"class":516},"  persistDuration",[506,637,624],{"class":520},[506,639,640],{"class":512}," const",[506,642,643],{"class":524}," Duration",[506,645,646],{"class":516},"(days",[506,648,624],{"class":520},[506,650,652],{"class":651},"sbssI"," 30",[506,654,655],{"class":516},")",[506,657,629],{"class":520},[506,659,661,664,666,669],{"class":508,"line":660},10,[506,662,663],{"class":516},"  config",[506,665,624],{"class":520},[506,667,668],{"class":524}," QoraClientConfig",[506,670,615],{"class":516},[506,672,674,677,679,681,684],{"class":508,"line":673},11,[506,675,676],{"class":516},"    defaultOptions",[506,678,624],{"class":520},[506,680,640],{"class":512},[506,682,683],{"class":524}," QoraOptions",[506,685,615],{"class":516},[506,687,689,692,694,696,699,701,704,706],{"class":508,"line":688},12,[506,690,691],{"class":516},"      staleTime",[506,693,624],{"class":520},[506,695,643],{"class":524},[506,697,698],{"class":516},"(minutes",[506,700,624],{"class":520},[506,702,703],{"class":651}," 5",[506,705,655],{"class":516},[506,707,629],{"class":520},[506,709,711,714,716,719,721,724],{"class":508,"line":710},13,[506,712,713],{"class":516},"      networkMode",[506,715,624],{"class":520},[506,717,718],{"class":524}," NetworkMode",[506,720,544],{"class":520},[506,722,723],{"class":516},"offlineFirst",[506,725,629],{"class":520},[506,727,729,732],{"class":508,"line":728},14,[506,730,731],{"class":516},"    )",[506,733,629],{"class":520},[506,735,737],{"class":508,"line":736},15,[506,738,740],{"class":739},"sHwdD","    // Au plus 3 refetchs simultanés à la reconnexion, 150 ms de jitter entre eux.\n",[506,742,744,747,749,751,754],{"class":508,"line":743},16,[506,745,746],{"class":516},"    reconnectStrategy",[506,748,624],{"class":520},[506,750,640],{"class":512},[506,752,753],{"class":524}," ReconnectStrategy",[506,755,615],{"class":516},[506,757,759,762,764,767],{"class":508,"line":758},17,[506,760,761],{"class":516},"      maxConcurrent",[506,763,624],{"class":520},[506,765,766],{"class":651}," 3",[506,768,629],{"class":520},[506,770,772,775,777,779,782,784,787,789],{"class":508,"line":771},18,[506,773,774],{"class":516},"      jitter",[506,776,624],{"class":520},[506,778,643],{"class":524},[506,780,781],{"class":516},"(milliseconds",[506,783,624],{"class":520},[506,785,786],{"class":651}," 150",[506,788,655],{"class":516},[506,790,629],{"class":520},[506,792,794,796],{"class":508,"line":793},19,[506,795,731],{"class":516},[506,797,629],{"class":520},[506,799,801,804,806,809],{"class":508,"line":800},20,[506,802,803],{"class":516},"    debugMode",[506,805,624],{"class":520},[506,807,808],{"class":516}," kDebugMode",[506,810,629],{"class":520},[506,812,814,817],{"class":508,"line":813},21,[506,815,816],{"class":516},"  )",[506,818,629],{"class":520},[506,820,822,824],{"class":508,"line":821},22,[506,823,655],{"class":516},[506,825,531],{"class":520},[506,827,829],{"class":508,"line":828},23,[506,830,559],{"emptyLinePlaceholder":558},[506,832,834,837,840],{"class":508,"line":833},24,[506,835,836],{"class":547},"registerAllSerializers",[506,838,839],{"class":516},"(client)",[506,841,531],{"class":520},[506,843,845,847,850,852,855,857,860],{"class":508,"line":844},25,[506,846,538],{"class":537},[506,848,849],{"class":516}," client",[506,851,544],{"class":520},[506,853,854],{"class":547},"hydrate",[506,856,528],{"class":516},[506,858,859],{"class":520},";",[506,861,862],{"class":739}," // les todos sauvegardés sont en cache avant la première frame\n",[353,864,865,866,868,869,872,873,876,877,880,881,883,884,479,886,479,889,479,892,479,895,479,898,479,901,904,905,544],{},"Dans une vraie application, remplacez ",[381,867,383],{}," par ",[381,870,871],{},"FlutterConnectivityManager()"," (ou utilisez ",[381,874,875],{},"QoraScope.auto","), et ",[381,878,879],{},"InMemoryStorageAdapter"," par un adaptateur adossé à un vrai stockage. ",[381,882,403],{}," a sept méthodes (",[381,885,548],{},[381,887,888],{},"read",[381,890,891],{},"write",[381,893,894],{},"delete",[381,896,897],{},"keys",[381,899,900],{},"clear",[381,902,903],{},"dispose",") ; voir la ",[906,907,909],"a",{"href":908},"../recipes/persistence","recette Persistance",[353,911,912],{},"Les serializers sont regroupés dans une seule fonction, qui sert aussi de liste de tous les types écrits dans le stockage :",[488,914,917],{"className":499,"code":915,"filename":916,"language":502,"meta":494,"style":494},"void registerAllSerializers(PersistQoraClient client) {\n  client.registerSerializer\u003CTodo>(\n    QoraSerializer(toJson: (t) => t.toJson(), fromJson: Todo.fromJson),\n    name: 'Todo', // nom explicite : stable avec --obfuscate et sur le web\n  );\n  client.registerSerializer\u003CList\u003CTodo>>(\n    QoraSerializer(\n      toJson: (list) => list.map((t) => t.toJson()).toList(),\n      fromJson: (json) => (json as List).map(Todo.fromJson).toList(),\n    ),\n    name: 'List\u003CTodo>',\n  );\n}\n","lib/core/persistence/serializers.dart",[381,918,919,935,954,998,1014,1020,1040,1046,1089,1132,1138,1149,1155],{"__ignoreMap":494},[506,920,921,924,927,930,932],{"class":508,"line":509},[506,922,923],{"class":512},"void",[506,925,926],{"class":547}," registerAllSerializers",[506,928,929],{"class":516},"(",[506,931,229],{"class":524},[506,933,934],{"class":516}," client) {\n",[506,936,937,940,942,945,948,951],{"class":508,"line":534},[506,938,939],{"class":516},"  client",[506,941,544],{"class":520},[506,943,944],{"class":547},"registerSerializer",[506,946,947],{"class":516},"\u003C",[506,949,950],{"class":524},"Todo",[506,952,953],{"class":516},">(\n",[506,955,956,959,962,964,967,970,973,975,978,980,983,986,988,991,993,996],{"class":508,"line":555},[506,957,958],{"class":524},"    QoraSerializer",[506,960,961],{"class":516},"(toJson",[506,963,624],{"class":520},[506,965,966],{"class":516}," (t) ",[506,968,969],{"class":520},"=>",[506,971,972],{"class":516}," t",[506,974,544],{"class":520},[506,976,977],{"class":547},"toJson",[506,979,528],{"class":516},[506,981,982],{"class":520},",",[506,984,985],{"class":516}," fromJson",[506,987,624],{"class":520},[506,989,990],{"class":524}," Todo",[506,992,544],{"class":520},[506,994,995],{"class":516},"fromJson)",[506,997,629],{"class":520},[506,999,1000,1003,1005,1009,1011],{"class":508,"line":562},[506,1001,1002],{"class":516},"    name",[506,1004,624],{"class":520},[506,1006,1008],{"class":1007},"sfazB"," 'Todo'",[506,1010,982],{"class":520},[506,1012,1013],{"class":739}," // nom explicite : stable avec --obfuscate et sur le web\n",[506,1015,1016,1018],{"class":508,"line":579},[506,1017,816],{"class":516},[506,1019,531],{"class":520},[506,1021,1022,1024,1026,1028,1030,1033,1035,1037],{"class":508,"line":597},[506,1023,939],{"class":516},[506,1025,544],{"class":520},[506,1027,944],{"class":547},[506,1029,947],{"class":516},[506,1031,1032],{"class":524},"List",[506,1034,947],{"class":516},[506,1036,950],{"class":524},[506,1038,1039],{"class":516},">>(\n",[506,1041,1042,1044],{"class":508,"line":602},[506,1043,958],{"class":524},[506,1045,615],{"class":516},[506,1047,1048,1051,1053,1056,1058,1061,1063,1066,1069,1071,1073,1075,1077,1080,1082,1085,1087],{"class":508,"line":618},[506,1049,1050],{"class":516},"      toJson",[506,1052,624],{"class":520},[506,1054,1055],{"class":516}," (list) ",[506,1057,969],{"class":520},[506,1059,1060],{"class":516}," list",[506,1062,544],{"class":520},[506,1064,1065],{"class":547},"map",[506,1067,1068],{"class":516},"((t) ",[506,1070,969],{"class":520},[506,1072,972],{"class":516},[506,1074,544],{"class":520},[506,1076,977],{"class":547},[506,1078,1079],{"class":516},"())",[506,1081,544],{"class":520},[506,1083,1084],{"class":547},"toList",[506,1086,528],{"class":516},[506,1088,629],{"class":520},[506,1090,1091,1094,1096,1099,1101,1104,1107,1110,1112,1114,1116,1118,1120,1122,1124,1126,1128,1130],{"class":508,"line":632},[506,1092,1093],{"class":516},"      fromJson",[506,1095,624],{"class":520},[506,1097,1098],{"class":516}," (json) ",[506,1100,969],{"class":520},[506,1102,1103],{"class":516}," (json ",[506,1105,1106],{"class":520},"as",[506,1108,1109],{"class":524}," List",[506,1111,655],{"class":516},[506,1113,544],{"class":520},[506,1115,1065],{"class":547},[506,1117,929],{"class":516},[506,1119,950],{"class":524},[506,1121,544],{"class":520},[506,1123,995],{"class":516},[506,1125,544],{"class":520},[506,1127,1084],{"class":547},[506,1129,528],{"class":516},[506,1131,629],{"class":520},[506,1133,1134,1136],{"class":508,"line":660},[506,1135,731],{"class":516},[506,1137,629],{"class":520},[506,1139,1140,1142,1144,1147],{"class":508,"line":673},[506,1141,1002],{"class":516},[506,1143,624],{"class":520},[506,1145,1146],{"class":1007}," 'List\u003CTodo>'",[506,1148,629],{"class":520},[506,1150,1151,1153],{"class":508,"line":688},[506,1152,816],{"class":516},[506,1154,531],{"class":520},[506,1156,1157],{"class":508,"line":710},[506,1158,1159],{"class":516},"}\n",[386,1161],{},[348,1163,1165],{"id":1164},"détails-dimplémentation","Détails d'implémentation",[1167,1168,1170],"h3",{"id":1169},"créer-un-todo-hors-ligne","Créer un todo hors-ligne",[353,1172,1173,1175,1176,1179],{},[381,1174,433],{}," s'exécute immédiatement, même hors-ligne. Il écrit un todo temporaire dans le cache ",[381,1177,1178],{},"['todos']",", ce qui met la liste à jour tout de suite, et renvoie la liste précédente comme contexte de rollback. La mutation elle-même attend en file que l'appareil se reconnecte.",[488,1181,1184],{"className":499,"code":1182,"filename":1183,"language":502,"meta":494,"style":494},"QoraMutationBuilder\u003CTodo, CreateTodoInput, List\u003CTodo>?>(\n  queryKey: const ['todos'],\n  mutator: api.createTodo,\n  options: MutationOptions(\n    offlineQueue: true,\n    onMutate: (input) async {\n      final prev = context.qora.getQueryData\u003CList\u003CTodo>>(const ['todos']) ?? [];\n      context.qora.setQueryData\u003CList\u003CTodo>>(const ['todos'], [\n        ...prev,\n        Todo(\n          id: 'temp-${DateTime.now().millisecondsSinceEpoch}',\n          title: input.title,\n          completed: false,\n          isPending: true, // flag local, jamais persisté\n        ),\n      ]);\n      return prev;\n    },\n    // Le serveur a rejeté le todo : on remet la liste précédente.\n    onError: (_, __, prev) async =>\n        context.qora.restoreQueryData(const ['todos'], prev),\n    // Le serveur l'a accepté : refetch pour remplacer l'id temporaire.\n    onSuccess: (_, __, ___) async => context.qora.invalidate(const ['todos']),\n  ),\n  builder: (context, state, mutate) => FloatingActionButton.extended(\n    onPressed: state.isPending ? null : () => _showAddDialog(context, mutate),\n    icon: Icon(state.isPending ? Icons.schedule : Icons.add),\n    label: Text(state.isPending ? 'En file, synchro bientôt' : 'Ajouter'),\n  ),\n)\n","lib/features/todos/ui/todo_screen.dart",[381,1185,1186,1210,1230,1247,1259,1269,1285,1337,1374,1384,1391,1427,1444,1454,1467,1474,1481,1491,1498,1503,1528,1559,1564,1610,1616,1648,1690,1729,1760,1767],{"__ignoreMap":494},[506,1187,1188,1190,1192,1194,1196,1199,1201,1203,1205,1207],{"class":508,"line":509},[506,1189,113],{"class":524},[506,1191,947],{"class":516},[506,1193,950],{"class":524},[506,1195,479],{"class":516},[506,1197,1198],{"class":524},"CreateTodoInput",[506,1200,479],{"class":516},[506,1202,1032],{"class":524},[506,1204,947],{"class":516},[506,1206,950],{"class":524},[506,1208,1209],{"class":516},">?>(\n",[506,1211,1212,1215,1217,1219,1222,1225,1228],{"class":508,"line":534},[506,1213,1214],{"class":516},"  queryKey",[506,1216,624],{"class":520},[506,1218,640],{"class":512},[506,1220,1221],{"class":516}," [",[506,1223,1224],{"class":1007},"'todos'",[506,1226,1227],{"class":516},"]",[506,1229,629],{"class":520},[506,1231,1232,1235,1237,1240,1242,1245],{"class":508,"line":555},[506,1233,1234],{"class":516},"  mutator",[506,1236,624],{"class":520},[506,1238,1239],{"class":516}," api",[506,1241,544],{"class":520},[506,1243,1244],{"class":516},"createTodo",[506,1246,629],{"class":520},[506,1248,1249,1252,1254,1257],{"class":508,"line":562},[506,1250,1251],{"class":516},"  options",[506,1253,624],{"class":520},[506,1255,1256],{"class":524}," MutationOptions",[506,1258,615],{"class":516},[506,1260,1261,1264,1266],{"class":508,"line":579},[506,1262,1263],{"class":516},"    offlineQueue",[506,1265,624],{"class":520},[506,1267,1268],{"class":520}," true,\n",[506,1270,1271,1274,1276,1279,1282],{"class":508,"line":597},[506,1272,1273],{"class":516},"    onMutate",[506,1275,624],{"class":520},[506,1277,1278],{"class":516}," (input) ",[506,1280,1281],{"class":537},"async",[506,1283,1284],{"class":516}," {\n",[506,1286,1287,1290,1293,1295,1298,1300,1303,1305,1308,1310,1312,1314,1316,1319,1322,1324,1326,1329,1332,1335],{"class":508,"line":602},[506,1288,1289],{"class":512},"      final",[506,1291,1292],{"class":516}," prev ",[506,1294,521],{"class":520},[506,1296,1297],{"class":516}," context",[506,1299,544],{"class":520},[506,1301,1302],{"class":516},"qora",[506,1304,544],{"class":520},[506,1306,1307],{"class":547},"getQueryData",[506,1309,947],{"class":516},[506,1311,1032],{"class":524},[506,1313,947],{"class":516},[506,1315,950],{"class":524},[506,1317,1318],{"class":516},">>(",[506,1320,1321],{"class":512},"const",[506,1323,1221],{"class":516},[506,1325,1224],{"class":1007},[506,1327,1328],{"class":516},"]) ",[506,1330,1331],{"class":520},"??",[506,1333,1334],{"class":516}," []",[506,1336,531],{"class":520},[506,1338,1339,1342,1344,1346,1348,1351,1353,1355,1357,1359,1361,1363,1365,1367,1369,1371],{"class":508,"line":618},[506,1340,1341],{"class":516},"      context",[506,1343,544],{"class":520},[506,1345,1302],{"class":516},[506,1347,544],{"class":520},[506,1349,1350],{"class":547},"setQueryData",[506,1352,947],{"class":516},[506,1354,1032],{"class":524},[506,1356,947],{"class":516},[506,1358,950],{"class":524},[506,1360,1318],{"class":516},[506,1362,1321],{"class":512},[506,1364,1221],{"class":516},[506,1366,1224],{"class":1007},[506,1368,1227],{"class":516},[506,1370,982],{"class":520},[506,1372,1373],{"class":516}," [\n",[506,1375,1376,1379,1382],{"class":508,"line":632},[506,1377,1378],{"class":520},"        ...",[506,1380,1381],{"class":516},"prev",[506,1383,629],{"class":520},[506,1385,1386,1389],{"class":508,"line":660},[506,1387,1388],{"class":524},"        Todo",[506,1390,615],{"class":516},[506,1392,1393,1396,1398,1401,1404,1407,1409,1412,1415,1419,1422,1425],{"class":508,"line":673},[506,1394,1395],{"class":516},"          id",[506,1397,624],{"class":520},[506,1399,1400],{"class":1007}," 'temp-",[506,1402,1403],{"class":516},"${",[506,1405,1406],{"class":524},"DateTime",[506,1408,544],{"class":516},[506,1410,1411],{"class":547},"now",[506,1413,1414],{"class":516},"().",[506,1416,1418],{"class":1417},"sHdIc","millisecondsSinceEpoch",[506,1420,1421],{"class":516},"}",[506,1423,1424],{"class":1007},"'",[506,1426,629],{"class":520},[506,1428,1429,1432,1434,1437,1439,1442],{"class":508,"line":688},[506,1430,1431],{"class":516},"          title",[506,1433,624],{"class":520},[506,1435,1436],{"class":516}," input",[506,1438,544],{"class":520},[506,1440,1441],{"class":516},"title",[506,1443,629],{"class":520},[506,1445,1446,1449,1451],{"class":508,"line":710},[506,1447,1448],{"class":516},"          completed",[506,1450,624],{"class":520},[506,1452,1453],{"class":520}," false,\n",[506,1455,1456,1459,1461,1464],{"class":508,"line":728},[506,1457,1458],{"class":516},"          isPending",[506,1460,624],{"class":520},[506,1462,1463],{"class":520}," true,",[506,1465,1466],{"class":739}," // flag local, jamais persisté\n",[506,1468,1469,1472],{"class":508,"line":736},[506,1470,1471],{"class":516},"        )",[506,1473,629],{"class":520},[506,1475,1476,1479],{"class":508,"line":743},[506,1477,1478],{"class":516},"      ])",[506,1480,531],{"class":520},[506,1482,1483,1486,1489],{"class":508,"line":758},[506,1484,1485],{"class":537},"      return",[506,1487,1488],{"class":516}," prev",[506,1490,531],{"class":520},[506,1492,1493,1496],{"class":508,"line":771},[506,1494,1495],{"class":516},"    }",[506,1497,629],{"class":520},[506,1499,1500],{"class":508,"line":793},[506,1501,1502],{"class":739},"    // Le serveur a rejeté le todo : on remet la liste précédente.\n",[506,1504,1505,1508,1510,1513,1515,1518,1520,1523,1525],{"class":508,"line":800},[506,1506,1507],{"class":516},"    onError",[506,1509,624],{"class":520},[506,1511,1512],{"class":516}," (_",[506,1514,982],{"class":520},[506,1516,1517],{"class":516}," __",[506,1519,982],{"class":520},[506,1521,1522],{"class":516}," prev) ",[506,1524,1281],{"class":537},[506,1526,1527],{"class":520}," =>\n",[506,1529,1530,1533,1535,1537,1539,1542,1544,1546,1548,1550,1552,1554,1557],{"class":508,"line":813},[506,1531,1532],{"class":516},"        context",[506,1534,544],{"class":520},[506,1536,1302],{"class":516},[506,1538,544],{"class":520},[506,1540,1541],{"class":547},"restoreQueryData",[506,1543,929],{"class":516},[506,1545,1321],{"class":512},[506,1547,1221],{"class":516},[506,1549,1224],{"class":1007},[506,1551,1227],{"class":516},[506,1553,982],{"class":520},[506,1555,1556],{"class":516}," prev)",[506,1558,629],{"class":520},[506,1560,1561],{"class":508,"line":821},[506,1562,1563],{"class":739},"    // Le serveur l'a accepté : refetch pour remplacer l'id temporaire.\n",[506,1565,1566,1569,1571,1573,1575,1577,1579,1582,1584,1587,1589,1591,1593,1595,1597,1599,1601,1603,1605,1608],{"class":508,"line":828},[506,1567,1568],{"class":516},"    onSuccess",[506,1570,624],{"class":520},[506,1572,1512],{"class":516},[506,1574,982],{"class":520},[506,1576,1517],{"class":516},[506,1578,982],{"class":520},[506,1580,1581],{"class":516}," ___) ",[506,1583,1281],{"class":537},[506,1585,1586],{"class":520}," =>",[506,1588,1297],{"class":516},[506,1590,544],{"class":520},[506,1592,1302],{"class":516},[506,1594,544],{"class":520},[506,1596,485],{"class":547},[506,1598,929],{"class":516},[506,1600,1321],{"class":512},[506,1602,1221],{"class":516},[506,1604,1224],{"class":1007},[506,1606,1607],{"class":516},"])",[506,1609,629],{"class":520},[506,1611,1612,1614],{"class":508,"line":833},[506,1613,816],{"class":516},[506,1615,629],{"class":520},[506,1617,1618,1621,1623,1626,1628,1631,1633,1636,1638,1641,1643,1646],{"class":508,"line":844},[506,1619,1620],{"class":516},"  builder",[506,1622,624],{"class":520},[506,1624,1625],{"class":516}," (context",[506,1627,982],{"class":520},[506,1629,1630],{"class":516}," state",[506,1632,982],{"class":520},[506,1634,1635],{"class":516}," mutate) ",[506,1637,969],{"class":520},[506,1639,1640],{"class":524}," FloatingActionButton",[506,1642,544],{"class":520},[506,1644,1645],{"class":547},"extended",[506,1647,615],{"class":516},[506,1649,1651,1654,1656,1658,1660,1663,1666,1669,1672,1675,1677,1680,1683,1685,1688],{"class":508,"line":1650},26,[506,1652,1653],{"class":516},"    onPressed",[506,1655,624],{"class":520},[506,1657,1630],{"class":516},[506,1659,544],{"class":520},[506,1661,1662],{"class":516},"isPending ",[506,1664,1665],{"class":520},"?",[506,1667,1668],{"class":520}," null",[506,1670,1671],{"class":520}," :",[506,1673,1674],{"class":516}," () ",[506,1676,969],{"class":520},[506,1678,1679],{"class":547}," _showAddDialog",[506,1681,1682],{"class":516},"(context",[506,1684,982],{"class":520},[506,1686,1687],{"class":516}," mutate)",[506,1689,629],{"class":520},[506,1691,1693,1696,1698,1701,1704,1706,1708,1710,1713,1715,1718,1720,1722,1724,1727],{"class":508,"line":1692},27,[506,1694,1695],{"class":516},"    icon",[506,1697,624],{"class":520},[506,1699,1700],{"class":524}," Icon",[506,1702,1703],{"class":516},"(state",[506,1705,544],{"class":520},[506,1707,1662],{"class":516},[506,1709,1665],{"class":520},[506,1711,1712],{"class":524}," Icons",[506,1714,544],{"class":520},[506,1716,1717],{"class":516},"schedule ",[506,1719,624],{"class":520},[506,1721,1712],{"class":524},[506,1723,544],{"class":520},[506,1725,1726],{"class":516},"add)",[506,1728,629],{"class":520},[506,1730,1732,1735,1737,1740,1742,1744,1746,1748,1751,1753,1756,1758],{"class":508,"line":1731},28,[506,1733,1734],{"class":516},"    label",[506,1736,624],{"class":520},[506,1738,1739],{"class":524}," Text",[506,1741,1703],{"class":516},[506,1743,544],{"class":520},[506,1745,1662],{"class":516},[506,1747,1665],{"class":520},[506,1749,1750],{"class":1007}," 'En file, synchro bientôt'",[506,1752,1671],{"class":520},[506,1754,1755],{"class":1007}," 'Ajouter'",[506,1757,655],{"class":516},[506,1759,629],{"class":520},[506,1761,1763,1765],{"class":508,"line":1762},29,[506,1764,816],{"class":516},[506,1766,629],{"class":520},[506,1768,1770],{"class":508,"line":1769},30,[506,1771,1772],{"class":516},")\n",[353,1774,1775,1778,1779,544],{},[381,1776,1777],{},"optimisticResponse"," est l'alternative quand seul l'état de la mutation compte (par exemple une confirmation « envoyé ») plutôt qu'une liste mise à jour : le builder reçoit immédiatement ",[381,1780,1781],{},"MutationSuccess(isOptimistic: true)",[1167,1783,1785],{"id":1784},"afficher-les-todos-en-attente","Afficher les todos en attente",[353,1787,1788,1791,1792,1795],{},[381,1789,1790],{},"TodoTile"," lit le flag local ",[381,1793,1794],{},"isPending"," pour montrer qu'un élément n'a pas encore atteint le serveur :",[488,1797,1800],{"className":499,"code":1798,"filename":1799,"language":502,"meta":494,"style":494},"ListTile(\n  leading: todo.isPending\n      ? const SizedBox(\n          width: 24,\n          height: 24,\n          child: CircularProgressIndicator(strokeWidth: 2),\n        )\n      : Checkbox(\n          value: todo.completed,\n          onChanged: (_) => onToggle(\n            ToggleTodoInput(id: todo.id, completed: !todo.completed),\n          ),\n        ),\n  title: Text(\n    todo.title,\n    style: TextStyle(\n      color: todo.isPending ? Colors.grey.shade500 : null,\n      fontStyle: todo.isPending ? FontStyle.italic : FontStyle.normal,\n    ),\n  ),\n  trailing: todo.isPending\n      ? Icon(Icons.cloud_off, size: 16, color: Colors.grey.shade400)\n      : null,\n)\n","lib/features/todos/ui/todo_tile.dart",[381,1801,1802,1809,1824,1836,1848,1859,1881,1886,1896,1912,1929,1966,1973,1979,1990,2001,2013,2046,2080,2086,2092,2105,2149,2155],{"__ignoreMap":494},[506,1803,1804,1807],{"class":508,"line":509},[506,1805,1806],{"class":524},"ListTile",[506,1808,615],{"class":516},[506,1810,1811,1814,1816,1819,1821],{"class":508,"line":534},[506,1812,1813],{"class":516},"  leading",[506,1815,624],{"class":520},[506,1817,1818],{"class":516}," todo",[506,1820,544],{"class":520},[506,1822,1823],{"class":516},"isPending\n",[506,1825,1826,1829,1831,1834],{"class":508,"line":555},[506,1827,1828],{"class":520},"      ?",[506,1830,640],{"class":512},[506,1832,1833],{"class":524}," SizedBox",[506,1835,615],{"class":516},[506,1837,1838,1841,1843,1846],{"class":508,"line":562},[506,1839,1840],{"class":516},"          width",[506,1842,624],{"class":520},[506,1844,1845],{"class":651}," 24",[506,1847,629],{"class":520},[506,1849,1850,1853,1855,1857],{"class":508,"line":579},[506,1851,1852],{"class":516},"          height",[506,1854,624],{"class":520},[506,1856,1845],{"class":651},[506,1858,629],{"class":520},[506,1860,1861,1864,1866,1869,1872,1874,1877,1879],{"class":508,"line":597},[506,1862,1863],{"class":516},"          child",[506,1865,624],{"class":520},[506,1867,1868],{"class":524}," CircularProgressIndicator",[506,1870,1871],{"class":516},"(strokeWidth",[506,1873,624],{"class":520},[506,1875,1876],{"class":651}," 2",[506,1878,655],{"class":516},[506,1880,629],{"class":520},[506,1882,1883],{"class":508,"line":602},[506,1884,1885],{"class":516},"        )\n",[506,1887,1888,1891,1894],{"class":508,"line":618},[506,1889,1890],{"class":520},"      :",[506,1892,1893],{"class":524}," Checkbox",[506,1895,615],{"class":516},[506,1897,1898,1901,1903,1905,1907,1910],{"class":508,"line":632},[506,1899,1900],{"class":516},"          value",[506,1902,624],{"class":520},[506,1904,1818],{"class":516},[506,1906,544],{"class":520},[506,1908,1909],{"class":516},"completed",[506,1911,629],{"class":520},[506,1913,1914,1917,1919,1922,1924,1927],{"class":508,"line":660},[506,1915,1916],{"class":516},"          onChanged",[506,1918,624],{"class":520},[506,1920,1921],{"class":516}," (_) ",[506,1923,969],{"class":520},[506,1925,1926],{"class":547}," onToggle",[506,1928,615],{"class":516},[506,1930,1931,1934,1937,1939,1941,1943,1946,1948,1951,1953,1956,1959,1961,1964],{"class":508,"line":673},[506,1932,1933],{"class":524},"            ToggleTodoInput",[506,1935,1936],{"class":516},"(id",[506,1938,624],{"class":520},[506,1940,1818],{"class":516},[506,1942,544],{"class":520},[506,1944,1945],{"class":516},"id",[506,1947,982],{"class":520},[506,1949,1950],{"class":516}," completed",[506,1952,624],{"class":520},[506,1954,1955],{"class":520}," !",[506,1957,1958],{"class":516},"todo",[506,1960,544],{"class":520},[506,1962,1963],{"class":516},"completed)",[506,1965,629],{"class":520},[506,1967,1968,1971],{"class":508,"line":688},[506,1969,1970],{"class":516},"          )",[506,1972,629],{"class":520},[506,1974,1975,1977],{"class":508,"line":710},[506,1976,1471],{"class":516},[506,1978,629],{"class":520},[506,1980,1981,1984,1986,1988],{"class":508,"line":728},[506,1982,1983],{"class":516},"  title",[506,1985,624],{"class":520},[506,1987,1739],{"class":524},[506,1989,615],{"class":516},[506,1991,1992,1995,1997,1999],{"class":508,"line":736},[506,1993,1994],{"class":516},"    todo",[506,1996,544],{"class":520},[506,1998,1441],{"class":516},[506,2000,629],{"class":520},[506,2002,2003,2006,2008,2011],{"class":508,"line":743},[506,2004,2005],{"class":516},"    style",[506,2007,624],{"class":520},[506,2009,2010],{"class":524}," TextStyle",[506,2012,615],{"class":516},[506,2014,2015,2018,2020,2022,2024,2026,2028,2031,2033,2036,2038,2041,2043],{"class":508,"line":758},[506,2016,2017],{"class":516},"      color",[506,2019,624],{"class":520},[506,2021,1818],{"class":516},[506,2023,544],{"class":520},[506,2025,1662],{"class":516},[506,2027,1665],{"class":520},[506,2029,2030],{"class":524}," Colors",[506,2032,544],{"class":520},[506,2034,2035],{"class":516},"grey",[506,2037,544],{"class":520},[506,2039,2040],{"class":516},"shade500 ",[506,2042,624],{"class":520},[506,2044,2045],{"class":520}," null,\n",[506,2047,2048,2051,2053,2055,2057,2059,2061,2064,2066,2069,2071,2073,2075,2078],{"class":508,"line":771},[506,2049,2050],{"class":516},"      fontStyle",[506,2052,624],{"class":520},[506,2054,1818],{"class":516},[506,2056,544],{"class":520},[506,2058,1662],{"class":516},[506,2060,1665],{"class":520},[506,2062,2063],{"class":524}," FontStyle",[506,2065,544],{"class":520},[506,2067,2068],{"class":516},"italic ",[506,2070,624],{"class":520},[506,2072,2063],{"class":524},[506,2074,544],{"class":520},[506,2076,2077],{"class":516},"normal",[506,2079,629],{"class":520},[506,2081,2082,2084],{"class":508,"line":793},[506,2083,731],{"class":516},[506,2085,629],{"class":520},[506,2087,2088,2090],{"class":508,"line":800},[506,2089,816],{"class":516},[506,2091,629],{"class":520},[506,2093,2094,2097,2099,2101,2103],{"class":508,"line":813},[506,2095,2096],{"class":516},"  trailing",[506,2098,624],{"class":520},[506,2100,1818],{"class":516},[506,2102,544],{"class":520},[506,2104,1823],{"class":516},[506,2106,2107,2109,2111,2113,2116,2118,2121,2123,2126,2128,2131,2133,2136,2138,2140,2142,2144,2146],{"class":508,"line":821},[506,2108,1828],{"class":520},[506,2110,1700],{"class":524},[506,2112,929],{"class":516},[506,2114,2115],{"class":524},"Icons",[506,2117,544],{"class":520},[506,2119,2120],{"class":516},"cloud_off",[506,2122,982],{"class":520},[506,2124,2125],{"class":516}," size",[506,2127,624],{"class":520},[506,2129,2130],{"class":651}," 16",[506,2132,982],{"class":520},[506,2134,2135],{"class":516}," color",[506,2137,624],{"class":520},[506,2139,2030],{"class":524},[506,2141,544],{"class":520},[506,2143,2035],{"class":516},[506,2145,544],{"class":520},[506,2147,2148],{"class":516},"shade400)\n",[506,2150,2151,2153],{"class":508,"line":828},[506,2152,1890],{"class":520},[506,2154,2045],{"class":520},[506,2156,2157],{"class":508,"line":833},[506,2158,1772],{"class":516},[1167,2160,2162],{"id":2161},"pourquoi-lordre-de-rejeu-compte","Pourquoi l'ordre de rejeu compte",[488,2164,2167],{"className":2165,"code":2166,"language":493,"meta":494},[491],"1. Créer « Acheter du lait » hors-ligne → mis en file comme mutation #1\n2. Le cocher                           → mis en file comme mutation #2\n3. Le réseau revient\n4. #1 est rejouée → le serveur crée le todo\n5. #2 est rejouée → le serveur le marque comme fait\n",[381,2168,2166],{"__ignoreMap":494},[353,2170,2171,2172,2174,2175,2177,2178,2180],{},"Le rejeu est séquentiel : #2 n'est envoyée qu'une fois #1 terminée. Si le serveur rejette #1, son ",[381,2173,437],{}," s'exécute avec le snapshot pris par ",[381,2176,433],{},", ce qui retire le todo optimiste, et le rejeu passe à #2. Une écriture en file est envoyée une seule fois : elle n'est pas retentée à une reconnexion suivante, donc c'est dans ",[381,2179,437],{}," que l'utilisateur apprend qu'elle n'est pas passée.",[1167,2182,2184],{"id":2183},"hors-ligne-sans-rien-en-cache","Hors-ligne sans rien en cache",[353,2186,2187,2188,2191,2192,2195,2196,2198],{},"Quand l'appareil est hors-ligne et que rien n'est en cache, le fetch se met en pause au lieu de s'exécuter : l'état est ",[381,2189,2190],{},"Loading"," sans donnée précédente, et ",[381,2193,2194],{},"fetchStatus"," vaut ",[381,2197,445],{},". Un spinner tournerait jusqu'au retour du réseau, donc ce cas mérite son propre écran :",[488,2200,2202],{"className":499,"code":2201,"filename":1183,"language":502,"meta":494,"style":494},"QoraBuilder\u003CList\u003CTodo>>(\n  queryKey: const ['todos'],\n  fetcher: api.getTodos,\n  builder: (context, state, fetchStatus) {\n    if (state.dataOrNull == null && fetchStatus == FetchStatus.paused) {\n      return const OfflineEmptyState(); // « Pas de connexion : connectez-vous pour charger vos todos »\n    }\n    return switch (state) {\n      Initial() || Loading(previousData: null) =>\n        const Center(child: CircularProgressIndicator()),\n      Failure(:final error, previousData: null) => ErrorView(error),\n      // Données disponibles : Success, ou Loading/Failure avec previousData\n      _ => TodoList(\n          todos: state.dataOrNull ?? const [],\n          isRefreshing: fetchStatus == FetchStatus.fetching,\n          isOffline: fetchStatus == FetchStatus.paused,\n        ),\n    };\n  },\n)\n",[381,2203,2204,2218,2234,2250,2267,2301,2317,2322,2333,2360,2379,2414,2419,2431,2452,2472,2492,2498,2504,2511],{"__ignoreMap":494},[506,2205,2206,2208,2210,2212,2214,2216],{"class":508,"line":509},[506,2207,108],{"class":524},[506,2209,947],{"class":516},[506,2211,1032],{"class":524},[506,2213,947],{"class":516},[506,2215,950],{"class":524},[506,2217,1039],{"class":516},[506,2219,2220,2222,2224,2226,2228,2230,2232],{"class":508,"line":534},[506,2221,1214],{"class":516},[506,2223,624],{"class":520},[506,2225,640],{"class":512},[506,2227,1221],{"class":516},[506,2229,1224],{"class":1007},[506,2231,1227],{"class":516},[506,2233,629],{"class":520},[506,2235,2236,2239,2241,2243,2245,2248],{"class":508,"line":555},[506,2237,2238],{"class":516},"  fetcher",[506,2240,624],{"class":520},[506,2242,1239],{"class":516},[506,2244,544],{"class":520},[506,2246,2247],{"class":516},"getTodos",[506,2249,629],{"class":520},[506,2251,2252,2254,2256,2258,2260,2262,2264],{"class":508,"line":562},[506,2253,1620],{"class":516},[506,2255,624],{"class":520},[506,2257,1625],{"class":516},[506,2259,982],{"class":520},[506,2261,1630],{"class":516},[506,2263,982],{"class":520},[506,2265,2266],{"class":516}," fetchStatus) {\n",[506,2268,2269,2272,2275,2277,2280,2283,2285,2288,2291,2293,2296,2298],{"class":508,"line":579},[506,2270,2271],{"class":537},"    if",[506,2273,2274],{"class":516}," (state",[506,2276,544],{"class":520},[506,2278,2279],{"class":516},"dataOrNull ",[506,2281,2282],{"class":520},"==",[506,2284,1668],{"class":520},[506,2286,2287],{"class":520}," &&",[506,2289,2290],{"class":516}," fetchStatus ",[506,2292,2282],{"class":520},[506,2294,2295],{"class":524}," FetchStatus",[506,2297,544],{"class":520},[506,2299,2300],{"class":516},"paused) {\n",[506,2302,2303,2305,2307,2310,2312,2314],{"class":508,"line":597},[506,2304,1485],{"class":537},[506,2306,640],{"class":512},[506,2308,2309],{"class":524}," OfflineEmptyState",[506,2311,528],{"class":516},[506,2313,859],{"class":520},[506,2315,2316],{"class":739}," // « Pas de connexion : connectez-vous pour charger vos todos »\n",[506,2318,2319],{"class":508,"line":602},[506,2320,2321],{"class":516},"    }\n",[506,2323,2324,2327,2330],{"class":508,"line":618},[506,2325,2326],{"class":537},"    return",[506,2328,2329],{"class":537}," switch",[506,2331,2332],{"class":516}," (state) {\n",[506,2334,2335,2338,2341,2344,2347,2350,2352,2354,2357],{"class":508,"line":632},[506,2336,2337],{"class":524},"      Initial",[506,2339,2340],{"class":516},"() ",[506,2342,2343],{"class":520},"||",[506,2345,2346],{"class":524}," Loading",[506,2348,2349],{"class":516},"(previousData",[506,2351,624],{"class":520},[506,2353,1668],{"class":520},[506,2355,2356],{"class":516},") ",[506,2358,2359],{"class":520},"=>\n",[506,2361,2362,2365,2368,2371,2373,2375,2377],{"class":508,"line":660},[506,2363,2364],{"class":512},"        const",[506,2366,2367],{"class":524}," Center",[506,2369,2370],{"class":516},"(child",[506,2372,624],{"class":520},[506,2374,1868],{"class":524},[506,2376,1079],{"class":516},[506,2378,629],{"class":520},[506,2380,2381,2384,2386,2388,2390,2393,2395,2398,2400,2402,2404,2406,2409,2412],{"class":508,"line":673},[506,2382,2383],{"class":524},"      Failure",[506,2385,929],{"class":516},[506,2387,624],{"class":520},[506,2389,513],{"class":512},[506,2391,2392],{"class":516}," error",[506,2394,982],{"class":520},[506,2396,2397],{"class":516}," previousData",[506,2399,624],{"class":520},[506,2401,1668],{"class":520},[506,2403,2356],{"class":516},[506,2405,969],{"class":520},[506,2407,2408],{"class":524}," ErrorView",[506,2410,2411],{"class":516},"(error)",[506,2413,629],{"class":520},[506,2415,2416],{"class":508,"line":688},[506,2417,2418],{"class":739},"      // Données disponibles : Success, ou Loading/Failure avec previousData\n",[506,2420,2421,2424,2426,2429],{"class":508,"line":710},[506,2422,2423],{"class":516},"      _ ",[506,2425,969],{"class":520},[506,2427,2428],{"class":524}," TodoList",[506,2430,615],{"class":516},[506,2432,2433,2436,2438,2440,2442,2444,2446,2448,2450],{"class":508,"line":728},[506,2434,2435],{"class":516},"          todos",[506,2437,624],{"class":520},[506,2439,1630],{"class":516},[506,2441,544],{"class":520},[506,2443,2279],{"class":516},[506,2445,1331],{"class":520},[506,2447,640],{"class":512},[506,2449,1334],{"class":516},[506,2451,629],{"class":520},[506,2453,2454,2457,2459,2461,2463,2465,2467,2470],{"class":508,"line":736},[506,2455,2456],{"class":516},"          isRefreshing",[506,2458,624],{"class":520},[506,2460,2290],{"class":516},[506,2462,2282],{"class":520},[506,2464,2295],{"class":524},[506,2466,544],{"class":520},[506,2468,2469],{"class":516},"fetching",[506,2471,629],{"class":520},[506,2473,2474,2477,2479,2481,2483,2485,2487,2490],{"class":508,"line":743},[506,2475,2476],{"class":516},"          isOffline",[506,2478,624],{"class":520},[506,2480,2290],{"class":516},[506,2482,2282],{"class":520},[506,2484,2295],{"class":524},[506,2486,544],{"class":520},[506,2488,2489],{"class":516},"paused",[506,2491,629],{"class":520},[506,2493,2494,2496],{"class":508,"line":758},[506,2495,1471],{"class":516},[506,2497,629],{"class":520},[506,2499,2500,2502],{"class":508,"line":771},[506,2501,1495],{"class":516},[506,2503,531],{"class":520},[506,2505,2506,2509],{"class":508,"line":793},[506,2507,2508],{"class":516},"  }",[506,2510,629],{"class":520},[506,2512,2513],{"class":508,"line":800},[506,2514,1772],{"class":516},[386,2516],{},[348,2518,2520],{"id":2519},"limites-à-connaître","Limites à connaître",[357,2522,2523,2529],{},[360,2524,2525,2528],{},[376,2526,2527],{},"La file de mutations vit en mémoire."," Elle survit aux changements de connectivité, pas à un redémarrage de l'application : les écritures encore en file quand l'app est tuée sont perdues. Persister la file est l'objectif de Qora 1.4.",[360,2530,2531,2536,2537,2539],{},[376,2532,2533,2534],{},"Cette démo utilise ",[381,2535,879],{}," : les données sauvegardées sont donc aussi perdues au redémarrage du processus. Avec un adaptateur sur disque, ",[381,2538,407],{}," restaure la liste au démarrage à froid.",[386,2541],{},[348,2543,2545],{"id":2544},"code-source","Code source",[488,2547,2550],{"className":2548,"code":2549,"language":493,"meta":494},[491],"examples/ex_04_offline_first/\n├── lib/\n│   ├── main.dart                                   ← PersistQoraClient, hydrate(), QoraScope\n│   ├── core/persistence/serializers.dart           ← registerAllSerializers()\n│   ├── features/todos/\n│   │   ├── models/todo.dart                        ← Todo avec toJson / fromJson\n│   │   ├── data/todo_api.dart                      ← fausse API qui échoue hors-ligne\n│   │   └── ui/\n│   │       ├── todo_screen.dart                    ← requêtes, mutations, gestion de FetchStatus\n│   │       └── todo_tile.dart                      ← affichage des todos en attente\n│   └── shared/\n│       ├── connectivity/simulated_connectivity_manager.dart ← bascule en ligne / hors-ligne\n│       └── widgets/offline_banner.dart             ← wrapper de NetworkStatusIndicator\n└── pubspec.yaml\n",[381,2551,2549],{"__ignoreMap":494},[353,2553,2554],{},[906,2555,2559],{"href":2556,"rel":2557},"https://github.com/meragix/qora/tree/main/examples/ex_04_offline_first",[2558],"nofollow","Voir le code source complet",[386,2561],{},[348,2563,2565],{"id":2564},"guides-liés","Guides liés",[357,2567,2568,2575,2590,2601],{},[360,2569,2570,422,2572,2574],{},[906,2571,153],{"href":908},[381,2573,229],{},", serializers, adaptateurs de stockage, migrations de modèles",[360,2576,2577,422,2581,479,2584,2587,2588],{},[906,2578,2580],{"href":2579},"../recipes/network-aware","Réseau",[381,2582,2583],{},"NetworkMode",[381,2585,2586],{},"FetchStatus",", file de mutations hors-ligne, ",[381,2589,461],{},[360,2591,2592,422,2595,479,2597,479,2599],{},[906,2593,148],{"href":2594},"../recipes/optimistic-updates",[381,2596,433],{},[381,2598,1350],{},[381,2600,1541],{},[360,2602,2603,2607],{},[906,2604,2606],{"href":2605},"../api-reference/persist-qora-client","API PersistQoraClient"," : référence complète des méthodes",[2609,2610,2611],"style",{},"html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}",{"title":494,"searchDepth":534,"depth":534,"links":2613},[2614,2615,2616,2617,2623,2624,2625],{"id":350,"depth":534,"text":351},{"id":390,"depth":534,"text":391},{"id":467,"depth":534,"text":468},{"id":1164,"depth":534,"text":1165,"children":2618},[2619,2620,2621,2622],{"id":1169,"depth":555,"text":1170},{"id":1784,"depth":555,"text":1785},{"id":2161,"depth":555,"text":2162},{"id":2183,"depth":555,"text":2184},{"id":2519,"depth":534,"text":2520},{"id":2544,"depth":534,"text":2545},{"id":2564,"depth":534,"text":2565},"Une application de todos qui continue de fonctionner sans réseau : données sauvegardées au démarrage, requêtes en pause hors-ligne, écritures mises en file et rejouées dans l'ordre à la reconnexion.","md",null,{},{"icon":90},{"title":2632,"description":2633},"Étude de cas : Application Todo offline-first - Qora","Une application Flutter offline-first construite avec PersistQoraClient, NetworkMode.offlineFirst, la file de mutations hors-ligne et FetchStatus.paused, avec un bouton pour simuler la perte du réseau.","Ig07gJrd_CCWpE2FyJ_ZdxpaT8oa-n-TQMDrIfnRAo0",[2636,2638],{"title":306,"path":307,"stem":308,"description":2637,"icon":309,"children":-1},"Construire un feed de type réseau social haute performance avec pagination par curseur, gestion mémoire fenêtrée et création de post optimiste instantanée.",{"title":315,"path":316,"stem":317,"description":2639,"icon":161,"children":-1},"Les requêtes se mettent automatiquement en pause hors ligne, les mutations se mettent en file pour plus tard, et tout rejoue à la reconnexion : avec prévention du thundering-herd intégrée.",1794040393934]