[{"data":1,"prerenderedAt":2640},["ShallowReactive",2],{"navigation_docs_en":3,"-en-examples-offline-first":342,"-en-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},"Getting Started","i-lucide-rocket","/en/getting-started","en/01.getting-started",[10,15,20,24,29,34,39],{"title":11,"path":12,"stem":13,"icon":14},"What is Qora?","/en/getting-started/what-is-qora","en/01.getting-started/2.what-is-qora","i-lucide-info",{"title":16,"path":17,"stem":18,"icon":19},"Installation","/en/getting-started/installation","en/01.getting-started/3.installation","i-lucide-download",{"title":21,"path":22,"stem":23,"icon":6},"Quick Start","/en/getting-started/quick-start","en/01.getting-started/4.quick-start",{"title":25,"path":26,"stem":27,"icon":28},"Comparison","/en/getting-started/comparison","en/01.getting-started/5.comparison","i-lucide-scale",{"title":30,"path":31,"stem":32,"icon":33},"Coming from TanStack Query","/en/getting-started/coming-from-tanstack-query","en/01.getting-started/6.coming-from-tanstack-query","i-lucide-arrow-left-right",{"title":35,"path":36,"stem":37,"icon":38},"Migrating from Other Libraries","/en/getting-started/migrating-from-other-libraries","en/01.getting-started/7.migrating-from-other-libraries","i-lucide-arrow-right-left",{"title":40,"path":41,"stem":42,"icon":43},"AI Assistants","/en/getting-started/ai-assistants","en/01.getting-started/8.ai-assistants","i-lucide-sparkles",false,{"title":46,"icon":47,"path":48,"stem":49,"children":50,"page":44},"Core Concepts","i-lucide-book-open","/en/core-concepts","en/02.core-concepts",[51,56,61,66,71,76,81,86],{"title":52,"path":53,"stem":54,"icon":55},"Queries","/en/core-concepts/queries","en/02.core-concepts/1.queries","i-lucide-database",{"title":57,"path":58,"stem":59,"icon":60},"Query Keys","/en/core-concepts/query-keys","en/02.core-concepts/2.query-keys","i-lucide-key-round",{"title":62,"path":63,"stem":64,"icon":65},"Query States","/en/core-concepts/query-states","en/02.core-concepts/3.query-states","i-lucide-activity",{"title":67,"path":68,"stem":69,"icon":70},"Caching","/en/core-concepts/caching","en/02.core-concepts/4.caching","i-lucide-layers-3",{"title":72,"path":73,"stem":74,"icon":75},"Stale-While-Revalidate","/en/core-concepts/stale-while-revalidate","en/02.core-concepts/5.stale-while-revalidate","i-lucide-timer-reset",{"title":77,"path":78,"stem":79,"icon":80},"Deduplication","/en/core-concepts/deduplication","en/02.core-concepts/6.deduplication","i-lucide-copy-minus",{"title":82,"path":83,"stem":84,"icon":85},"Mutations & Invalidation","/en/core-concepts/mutations","en/02.core-concepts/7.mutations","i-lucide-pencil-line",{"title":87,"path":88,"stem":89,"icon":90},"Offline & Network","/en/core-concepts/offline","en/02.core-concepts/8.offline","i-lucide-wifi-off",{"title":92,"icon":93,"path":94,"stem":95,"children":96,"page":44},"Flutter Integration","i-lucide-smartphone","/en/flutter-integration","en/03.flutter-integration",[97,102,107,112,117,122,127,132],{"title":98,"path":99,"stem":100,"icon":101},"Setup","/en/flutter-integration/setup","en/03.flutter-integration/1.setup","i-lucide-wrench",{"title":103,"path":104,"stem":105,"icon":106},"QoraScope","/en/flutter-integration/qora-scope","en/03.flutter-integration/2.qora-scope","i-lucide-share-2",{"title":108,"path":109,"stem":110,"icon":111},"QoraBuilder","/en/flutter-integration/qora-builder","en/03.flutter-integration/3.qora-builder","i-lucide-blocks",{"title":113,"path":114,"stem":115,"icon":116},"QoraMutationBuilder","/en/flutter-integration/qora-mutation-builder","en/03.flutter-integration/4.qora-mutation-builder","i-lucide-pencil",{"title":118,"path":119,"stem":120,"icon":121},"InfiniteQueryBuilder","/en/flutter-integration/infinite-query-builder","en/03.flutter-integration/5.infinite-query-builder","i-lucide-infinity",{"title":123,"path":124,"stem":125,"icon":126},"Hooks (flutter_hooks)","/en/flutter-integration/hooks","en/03.flutter-integration/6.hooks","i-lucide-anchor",{"title":128,"path":129,"stem":130,"icon":131},"Network Status Widgets","/en/flutter-integration/network-status","en/03.flutter-integration/7.network-status","i-lucide-wifi",{"title":133,"path":134,"stem":135,"icon":136},"Best Practices","/en/flutter-integration/best-practices","en/03.flutter-integration/8.best-practices","i-lucide-badge-check",{"title":138,"icon":139,"path":140,"stem":141,"children":142,"page":44},"Recipes","i-lucide-chef-hat","/en/recipes","en/04.recipes",[143,147,152,157,162,166,171,176,181,186,191],{"title":144,"path":145,"stem":146,"icon":85},"Mutations","/en/recipes/mutations","en/04.recipes/02.mutations",{"title":148,"path":149,"stem":150,"icon":151},"Optimistic Updates","/en/recipes/optimistic-updates","en/04.recipes/03.optimistic-updates","i-lucide-zap",{"title":153,"path":154,"stem":155,"icon":156},"Persistence","/en/recipes/persistence","en/04.recipes/04.persistence","i-lucide-hard-drive",{"title":158,"path":159,"stem":160,"icon":161},"Network Aware","/en/recipes/network-aware","en/04.recipes/05.network-aware","i-lucide-signal",{"title":163,"path":164,"stem":165,"icon":121},"Infinite Queries","/en/recipes/infinite-queries","en/04.recipes/06.infinite-queries",{"title":167,"path":168,"stem":169,"icon":170},"Dependent Queries","/en/recipes/dependent-queries","en/04.recipes/07.dependent-queries","i-lucide-link-2",{"title":172,"path":173,"stem":174,"icon":175},"Cancellation","/en/recipes/cancel-token","en/04.recipes/08.cancel-token","i-lucide-circle-slash",{"title":177,"path":178,"stem":179,"icon":180},"SSR Hydration","/en/recipes/ssr-hydration","en/04.recipes/09.ssr-hydration","i-lucide-server",{"title":182,"path":183,"stem":184,"icon":185},"Error Handling","/en/recipes/error-handling","en/04.recipes/10.error-handling","i-lucide-shield-alert",{"title":187,"path":188,"stem":189,"icon":190},"Testing","/en/recipes/testing","en/04.recipes/11.testing","i-lucide-flask-conical",{"title":192,"path":193,"stem":194},"Route-Level Prefetching","/en/recipes/prefetch","en/04.recipes/12.prefetch",{"title":196,"icon":197,"path":198,"stem":199,"children":200,"page":44},"API Reference","i-lucide-file-code","/en/api-reference","en/05.api-reference",[201,206,210,214,219,224,228,232],{"title":202,"path":203,"stem":204,"icon":205},"QoraClient","/en/api-reference/qora-client","en/05.api-reference/1.qora-client","i-lucide-cpu",{"title":207,"path":208,"stem":209,"icon":60},"QoraKey","/en/api-reference/qora-key","en/05.api-reference/2.qora-key",{"title":211,"path":212,"stem":213,"icon":65},"QoraState","/en/api-reference/qora-state","en/05.api-reference/3.qora-state",{"title":215,"path":216,"stem":217,"icon":218},"QoraOptions","/en/api-reference/qora-options","en/05.api-reference/4.qora-options","i-lucide-sliders-horizontal",{"title":220,"path":221,"stem":222,"icon":223},"QoraClientConfig","/en/api-reference/qora-client-config","en/05.api-reference/5.qora-client-config","i-lucide-settings-2",{"title":225,"path":226,"stem":227,"icon":85},"Mutation API","/en/api-reference/qora-mutation","en/05.api-reference/6.qora-mutation",{"title":229,"path":230,"stem":231,"icon":156},"PersistQoraClient","/en/api-reference/persist-qora-client","en/05.api-reference/7.persist-qora-client",{"title":233,"path":234,"stem":235,"icon":121},"Infinite Query","/en/api-reference/infinite-query","en/05.api-reference/8.infinite-query",{"title":237,"icon":238,"path":239,"stem":240,"children":241,"page":44},"DevTools","i-lucide-bug","/en/devtools","en/06.devtools",[242,247,252],{"title":243,"path":244,"stem":245,"icon":246},"Overview","/en/devtools/overview","en/06.devtools/1.overview","i-lucide-layout-dashboard",{"title":248,"path":249,"stem":250,"icon":251},"In-App Overlay","/en/devtools/overlay","en/06.devtools/2.overlay","i-lucide-layers",{"title":253,"path":254,"stem":255,"icon":256},"Data Serialization","/en/devtools/data-display","en/06.devtools/4.data-display","i-lucide-eye",{"title":258,"icon":259,"path":260,"stem":261,"children":262,"page":44},"Third Party","i-lucide-puzzle","/en/third-party","en/07.third-party",[263,268,272,277,282,286],{"title":264,"path":265,"stem":266,"icon":267},"With Provider","/en/third-party/with-provider","en/07.third-party/1.with-provider","i-lucide-plug",{"title":269,"path":270,"stem":271,"icon":111},"With BLoC","/en/third-party/with-bloc","en/07.third-party/2.with-bloc",{"title":273,"path":274,"stem":275,"icon":276},"With Riverpod","/en/third-party/with-riverpod","en/07.third-party/3.with-riverpod","i-lucide-droplets",{"title":278,"path":279,"stem":280,"icon":281},"With RxDart","/en/third-party/with-rxdart","en/07.third-party/4.with-rxdart","i-lucide-git-branch",{"title":283,"path":284,"stem":285},"With Dio","/en/third-party/with-dio","en/07.third-party/5.with-dio",{"title":287,"path":288,"stem":289,"icon":290},"With GraphQL","/en/third-party/with-graphql","en/07.third-party/6.with-graphql","i-lucide-binary",{"title":292,"icon":190,"path":293,"stem":294,"children":295,"page":44},"Examples","/en/examples","en/08.examples",[296,301,305,310,314,318,322],{"title":297,"path":298,"stem":299,"icon":300},"Case Study: User List & Detail","/en/examples/basic-query","en/08.examples/1.basic-query","i-lucide-play-circle",{"title":302,"path":303,"stem":304,"icon":151},"Case Study: Optimistic Mutations & Rollback","/en/examples/mutations-optimistic","en/08.examples/2.mutations-optimistic",{"title":306,"path":307,"stem":308,"icon":309},"Case Study: Infinite Scroll Feed","/en/examples/infinite-scroll","en/08.examples/3.infinite-scroll","i-lucide-scroll",{"title":311,"path":312,"stem":313,"icon":90},"Case Study: Offline-First Todo App","/en/examples/offline-first","en/08.examples/4.offline-first",{"title":315,"path":316,"stem":317,"icon":161},"Case Study: Network Awareness","/en/examples/network-awareness","en/08.examples/5.network-awareness",{"title":319,"path":320,"stem":321,"icon":126},"Case Study: Hooks Integration","/en/examples/hooks-integration","en/08.examples/6.hooks-integration",{"title":323,"path":324,"stem":325,"icon":326},"Case Study: Complete Todo App","/en/examples/complete-todo-app","en/08.examples/7.complete-todo-app","i-lucide-check-square",{"title":328,"icon":329,"path":330,"stem":331,"children":332,"page":44},"Help","i-lucide-life-buoy","/en/help","en/09.help",[333,338],{"title":334,"path":335,"stem":336,"icon":337},"FAQ","/en/help/faq","en/09.help/1.faq","i-lucide-circle-help",{"title":339,"path":340,"stem":341,"icon":101},"Troubleshooting","/en/help/troubleshooting","en/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_en/en/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,1783,1787,1797,2160,2164,2170,2182,2186,2200,2516,2518,2522,2541,2543,2547,2553,2561,2563,2567,2608],[348,349,351],"h2",{"id":350},"objective","Objective",[353,354,355],"p",{},"Mobile connectivity drops all the time: tunnels, elevators, airplane mode. This example shows a todo app that stays usable through it:",[357,358,359,363,366],"ul",{},[360,361,362],"li",{},"the list is shown from the cache, and from storage on a cold start;",[360,364,365],{},"the UI knows when it is offline, instead of spinning forever;",[360,367,368],{},"todos created, toggled or deleted offline appear immediately and are sent in order when the network returns.",[353,370,371],{},"There is no custom sync code: everything below is Qora configuration and two widgets.",[373,374,375,379,380,384],"tip",{},[376,377,378],"strong",{},"Try it without touching your Wi-Fi."," The example ships a ",[381,382,383],"code",{},"SimulatedConnectivityManager",": the antenna button in the app bar switches the app between online and offline, and the fake API fails while offline.",[386,387],"hr",{},[348,389,391],{"id":390},"features-demonstrated","Features Demonstrated",[357,393,394,409,417,427,439,447,455],{},[360,395,396,400,401,404,405,408],{},[376,397,398],{},[381,399,229],{},": successful fetches are written to a ",[381,402,403],{},"StorageAdapter"," and restored by ",[381,406,407],{},"hydrate()"," before the first frame",[360,410,411,416],{},[376,412,413],{},[381,414,415],{},"NetworkMode.offlineFirst",": cached data is served immediately; fetches resume when the device reconnects",[360,418,419,422,423,426],{},[376,420,421],{},"Offline mutation queue",": ",[381,424,425],{},"offlineQueue: true"," queues writes made offline and replays them in FIFO order on reconnect",[360,428,429,438],{},[376,430,431,434,435],{},[381,432,433],{},"onMutate"," + ",[381,436,437],{},"onError",": the new todo is written into the list cache right away, and removed again if the server rejects it",[360,440,441,446],{},[376,442,443],{},[381,444,445],{},"FetchStatus.paused",": tells the UI \"no network\" apart from \"loading\"",[360,448,449,454],{},[376,450,451],{},[381,452,453],{},"NetworkStatusIndicator",": app-wide offline banner",[360,456,457,462],{},[376,458,459],{},[381,460,461],{},"ReconnectStrategy",": limits concurrent refetches and adds jitter when the network returns",[386,464],{},[348,466,468],{"id":467},"architecture","Architecture",[353,470,471,473,474,422,476,479,480,479,483,486],{},[381,472,229],{}," extends ",[381,475,202],{},[381,477,478],{},"fetchQuery",", ",[381,481,482],{},"watchQuery",[381,484,485],{},"invalidate"," and the widgets work unchanged. The rest of the app does not know persistence exists.",[488,489,495],"pre",{"className":490,"code":492,"language":493,"meta":494},[491],"language-text","main.dart\n├── InMemoryStorageAdapter().init()    ← storage backend (swap for your own adapter)\n├── SimulatedConnectivityManager(api)  ← online/offline toggle for the demo\n├── PersistQoraClient(storage, ...)    ← drop-in replacement for QoraClient\n├── registerAllSerializers(client)     ← every type written to storage\n├── client.hydrate()                   ← restore saved data before runApp\n└── QoraScope(\n      client: client,\n      lifecycleManager: FlutterLifecycleManager(qoraClient: client),\n      connectivityManager: connectivity,\n      child: App(),                    ← OfflineBannerWrapper in 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    // At most 3 concurrent refetches on reconnect, 150 ms jitter between them.\n    reconnectStrategy: const ReconnectStrategy(\n      maxConcurrent: 3,\n      jitter: Duration(milliseconds: 150),\n    ),\n    debugMode: kDebugMode,\n  ),\n);\n\nregisterAllSerializers(client);\nawait client.hydrate(); // saved todos are in the cache before the first 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","    // At most 3 concurrent refetches on reconnect, 150 ms jitter between them.\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}," // saved todos are in the cache before the first 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],{},"In a real app, replace ",[381,867,383],{}," with ",[381,870,871],{},"FlutterConnectivityManager()"," (or use ",[381,874,875],{},"QoraScope.auto","), and ",[381,878,879],{},"InMemoryStorageAdapter"," with an adapter backed by real storage. ",[381,882,403],{}," has seven methods (",[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","); see the ",[906,907,909],"a",{"href":908},"../recipes/persistence","Persistence recipe",[353,911,912],{},"Serializers live in one function, which doubles as the list of every type written to storage:",[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', // explicit name: stable with --obfuscate and on the 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}," // explicit name: stable with --obfuscate and on the 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},"implementation-details","Implementation Details",[1167,1168,1170],"h3",{"id":1169},"creating-a-todo-offline","Creating a todo offline",[353,1172,1173,1175,1176,1179],{},[381,1174,433],{}," runs immediately, even offline. It writes a temporary todo into the ",[381,1177,1178],{},"['todos']"," cache, so the list updates at once, and returns the previous list as the rollback context. The mutation itself is queued until the device reconnects.",[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, // local-only flag, never persisted\n        ),\n      ]);\n      return prev;\n    },\n    // Server rejected the todo: put the previous list back.\n    onError: (_, __, prev) async =>\n        context.qora.restoreQueryData(const ['todos'], prev),\n    // Server accepted it: refetch to replace the temporary id.\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 ? 'Queued, syncing soon' : 'Add Todo'),\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}," // local-only flag, never persisted\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},"    // Server rejected the todo: put the previous list back.\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},"    // Server accepted it: refetch to replace the temporary id.\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}," 'Queued, syncing soon'",[506,1752,1671],{"class":520},[506,1754,1755],{"class":1007}," 'Add Todo'",[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,1782],{},[381,1776,1777],{},"optimisticResponse"," is the alternative when you only need the mutation's own state (for example a \"sent\" confirmation) rather than an updated list: it makes the builder receive ",[381,1780,1781],{},"MutationSuccess(isOptimistic: true)"," immediately.",[1167,1784,1786],{"id":1785},"rendering-pending-todos","Rendering pending todos",[353,1788,1789,1792,1793,1796],{},[381,1790,1791],{},"TodoTile"," reads the local ",[381,1794,1795],{},"isPending"," flag to show that an item has not reached the server yet:",[488,1798,1801],{"className":499,"code":1799,"filename":1800,"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,1802,1803,1810,1825,1837,1849,1860,1882,1887,1897,1913,1930,1967,1974,1980,1991,2002,2014,2047,2081,2087,2093,2106,2150,2156],{"__ignoreMap":494},[506,1804,1805,1808],{"class":508,"line":509},[506,1806,1807],{"class":524},"ListTile",[506,1809,615],{"class":516},[506,1811,1812,1815,1817,1820,1822],{"class":508,"line":534},[506,1813,1814],{"class":516},"  leading",[506,1816,624],{"class":520},[506,1818,1819],{"class":516}," todo",[506,1821,544],{"class":520},[506,1823,1824],{"class":516},"isPending\n",[506,1826,1827,1830,1832,1835],{"class":508,"line":555},[506,1828,1829],{"class":520},"      ?",[506,1831,640],{"class":512},[506,1833,1834],{"class":524}," SizedBox",[506,1836,615],{"class":516},[506,1838,1839,1842,1844,1847],{"class":508,"line":562},[506,1840,1841],{"class":516},"          width",[506,1843,624],{"class":520},[506,1845,1846],{"class":651}," 24",[506,1848,629],{"class":520},[506,1850,1851,1854,1856,1858],{"class":508,"line":579},[506,1852,1853],{"class":516},"          height",[506,1855,624],{"class":520},[506,1857,1846],{"class":651},[506,1859,629],{"class":520},[506,1861,1862,1865,1867,1870,1873,1875,1878,1880],{"class":508,"line":597},[506,1863,1864],{"class":516},"          child",[506,1866,624],{"class":520},[506,1868,1869],{"class":524}," CircularProgressIndicator",[506,1871,1872],{"class":516},"(strokeWidth",[506,1874,624],{"class":520},[506,1876,1877],{"class":651}," 2",[506,1879,655],{"class":516},[506,1881,629],{"class":520},[506,1883,1884],{"class":508,"line":602},[506,1885,1886],{"class":516},"        )\n",[506,1888,1889,1892,1895],{"class":508,"line":618},[506,1890,1891],{"class":520},"      :",[506,1893,1894],{"class":524}," Checkbox",[506,1896,615],{"class":516},[506,1898,1899,1902,1904,1906,1908,1911],{"class":508,"line":632},[506,1900,1901],{"class":516},"          value",[506,1903,624],{"class":520},[506,1905,1819],{"class":516},[506,1907,544],{"class":520},[506,1909,1910],{"class":516},"completed",[506,1912,629],{"class":520},[506,1914,1915,1918,1920,1923,1925,1928],{"class":508,"line":660},[506,1916,1917],{"class":516},"          onChanged",[506,1919,624],{"class":520},[506,1921,1922],{"class":516}," (_) ",[506,1924,969],{"class":520},[506,1926,1927],{"class":547}," onToggle",[506,1929,615],{"class":516},[506,1931,1932,1935,1938,1940,1942,1944,1947,1949,1952,1954,1957,1960,1962,1965],{"class":508,"line":673},[506,1933,1934],{"class":524},"            ToggleTodoInput",[506,1936,1937],{"class":516},"(id",[506,1939,624],{"class":520},[506,1941,1819],{"class":516},[506,1943,544],{"class":520},[506,1945,1946],{"class":516},"id",[506,1948,982],{"class":520},[506,1950,1951],{"class":516}," completed",[506,1953,624],{"class":520},[506,1955,1956],{"class":520}," !",[506,1958,1959],{"class":516},"todo",[506,1961,544],{"class":520},[506,1963,1964],{"class":516},"completed)",[506,1966,629],{"class":520},[506,1968,1969,1972],{"class":508,"line":688},[506,1970,1971],{"class":516},"          )",[506,1973,629],{"class":520},[506,1975,1976,1978],{"class":508,"line":710},[506,1977,1471],{"class":516},[506,1979,629],{"class":520},[506,1981,1982,1985,1987,1989],{"class":508,"line":728},[506,1983,1984],{"class":516},"  title",[506,1986,624],{"class":520},[506,1988,1739],{"class":524},[506,1990,615],{"class":516},[506,1992,1993,1996,1998,2000],{"class":508,"line":736},[506,1994,1995],{"class":516},"    todo",[506,1997,544],{"class":520},[506,1999,1441],{"class":516},[506,2001,629],{"class":520},[506,2003,2004,2007,2009,2012],{"class":508,"line":743},[506,2005,2006],{"class":516},"    style",[506,2008,624],{"class":520},[506,2010,2011],{"class":524}," TextStyle",[506,2013,615],{"class":516},[506,2015,2016,2019,2021,2023,2025,2027,2029,2032,2034,2037,2039,2042,2044],{"class":508,"line":758},[506,2017,2018],{"class":516},"      color",[506,2020,624],{"class":520},[506,2022,1819],{"class":516},[506,2024,544],{"class":520},[506,2026,1662],{"class":516},[506,2028,1665],{"class":520},[506,2030,2031],{"class":524}," Colors",[506,2033,544],{"class":520},[506,2035,2036],{"class":516},"grey",[506,2038,544],{"class":520},[506,2040,2041],{"class":516},"shade500 ",[506,2043,624],{"class":520},[506,2045,2046],{"class":520}," null,\n",[506,2048,2049,2052,2054,2056,2058,2060,2062,2065,2067,2070,2072,2074,2076,2079],{"class":508,"line":771},[506,2050,2051],{"class":516},"      fontStyle",[506,2053,624],{"class":520},[506,2055,1819],{"class":516},[506,2057,544],{"class":520},[506,2059,1662],{"class":516},[506,2061,1665],{"class":520},[506,2063,2064],{"class":524}," FontStyle",[506,2066,544],{"class":520},[506,2068,2069],{"class":516},"italic ",[506,2071,624],{"class":520},[506,2073,2064],{"class":524},[506,2075,544],{"class":520},[506,2077,2078],{"class":516},"normal",[506,2080,629],{"class":520},[506,2082,2083,2085],{"class":508,"line":793},[506,2084,731],{"class":516},[506,2086,629],{"class":520},[506,2088,2089,2091],{"class":508,"line":800},[506,2090,816],{"class":516},[506,2092,629],{"class":520},[506,2094,2095,2098,2100,2102,2104],{"class":508,"line":813},[506,2096,2097],{"class":516},"  trailing",[506,2099,624],{"class":520},[506,2101,1819],{"class":516},[506,2103,544],{"class":520},[506,2105,1824],{"class":516},[506,2107,2108,2110,2112,2114,2117,2119,2122,2124,2127,2129,2132,2134,2137,2139,2141,2143,2145,2147],{"class":508,"line":821},[506,2109,1829],{"class":520},[506,2111,1700],{"class":524},[506,2113,929],{"class":516},[506,2115,2116],{"class":524},"Icons",[506,2118,544],{"class":520},[506,2120,2121],{"class":516},"cloud_off",[506,2123,982],{"class":520},[506,2125,2126],{"class":516}," size",[506,2128,624],{"class":520},[506,2130,2131],{"class":651}," 16",[506,2133,982],{"class":520},[506,2135,2136],{"class":516}," color",[506,2138,624],{"class":520},[506,2140,2031],{"class":524},[506,2142,544],{"class":520},[506,2144,2036],{"class":516},[506,2146,544],{"class":520},[506,2148,2149],{"class":516},"shade400)\n",[506,2151,2152,2154],{"class":508,"line":828},[506,2153,1891],{"class":520},[506,2155,2046],{"class":520},[506,2157,2158],{"class":508,"line":833},[506,2159,1772],{"class":516},[1167,2161,2163],{"id":2162},"why-replay-order-matters","Why replay order matters",[488,2165,2168],{"className":2166,"code":2167,"language":493,"meta":494},[491],"1. Create \"Buy milk\" offline     → queued as mutation #1\n2. Mark it complete              → queued as mutation #2\n3. Network returns\n4. #1 replays → server creates the todo\n5. #2 replays → server marks it complete\n",[381,2169,2167],{"__ignoreMap":494},[353,2171,2172,2173,2175,2176,2178,2179,2181],{},"Replay is sequential: #2 is sent only once #1 has completed. If the server rejects #1, its ",[381,2174,437],{}," runs with the snapshot taken by ",[381,2177,433],{},", which removes the optimistic todo, and replay moves on to #2. A queued write is sent once: it is not retried on a later reconnect, so ",[381,2180,437],{}," is where the user learns that it did not go through.",[1167,2183,2185],{"id":2184},"offline-with-nothing-in-the-cache","Offline with nothing in the cache",[353,2187,2188,2189,2192,2193,2196,2197,2199],{},"When the device is offline and nothing is cached, the fetch pauses instead of running: the state is ",[381,2190,2191],{},"Loading"," with no previous data, and ",[381,2194,2195],{},"fetchStatus"," is ",[381,2198,445],{},". A spinner would spin until the network returns, so this case deserves its own screen:",[488,2201,2203],{"className":499,"code":2202,"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(); // \"No connection: connect to load your 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      // Data available: Success, or Loading/Failure with previousData\n      _ => TodoList(\n          todos: state.dataOrNull ?? const [],\n          isRefreshing: fetchStatus == FetchStatus.fetching,\n          isOffline: fetchStatus == FetchStatus.paused,\n        ),\n    };\n  },\n)\n",[381,2204,2205,2219,2235,2251,2268,2302,2318,2323,2334,2361,2380,2415,2420,2432,2453,2473,2493,2499,2505,2512],{"__ignoreMap":494},[506,2206,2207,2209,2211,2213,2215,2217],{"class":508,"line":509},[506,2208,108],{"class":524},[506,2210,947],{"class":516},[506,2212,1032],{"class":524},[506,2214,947],{"class":516},[506,2216,950],{"class":524},[506,2218,1039],{"class":516},[506,2220,2221,2223,2225,2227,2229,2231,2233],{"class":508,"line":534},[506,2222,1214],{"class":516},[506,2224,624],{"class":520},[506,2226,640],{"class":512},[506,2228,1221],{"class":516},[506,2230,1224],{"class":1007},[506,2232,1227],{"class":516},[506,2234,629],{"class":520},[506,2236,2237,2240,2242,2244,2246,2249],{"class":508,"line":555},[506,2238,2239],{"class":516},"  fetcher",[506,2241,624],{"class":520},[506,2243,1239],{"class":516},[506,2245,544],{"class":520},[506,2247,2248],{"class":516},"getTodos",[506,2250,629],{"class":520},[506,2252,2253,2255,2257,2259,2261,2263,2265],{"class":508,"line":562},[506,2254,1620],{"class":516},[506,2256,624],{"class":520},[506,2258,1625],{"class":516},[506,2260,982],{"class":520},[506,2262,1630],{"class":516},[506,2264,982],{"class":520},[506,2266,2267],{"class":516}," fetchStatus) {\n",[506,2269,2270,2273,2276,2278,2281,2284,2286,2289,2292,2294,2297,2299],{"class":508,"line":579},[506,2271,2272],{"class":537},"    if",[506,2274,2275],{"class":516}," (state",[506,2277,544],{"class":520},[506,2279,2280],{"class":516},"dataOrNull ",[506,2282,2283],{"class":520},"==",[506,2285,1668],{"class":520},[506,2287,2288],{"class":520}," &&",[506,2290,2291],{"class":516}," fetchStatus ",[506,2293,2283],{"class":520},[506,2295,2296],{"class":524}," FetchStatus",[506,2298,544],{"class":520},[506,2300,2301],{"class":516},"paused) {\n",[506,2303,2304,2306,2308,2311,2313,2315],{"class":508,"line":597},[506,2305,1485],{"class":537},[506,2307,640],{"class":512},[506,2309,2310],{"class":524}," OfflineEmptyState",[506,2312,528],{"class":516},[506,2314,859],{"class":520},[506,2316,2317],{"class":739}," // \"No connection: connect to load your todos\"\n",[506,2319,2320],{"class":508,"line":602},[506,2321,2322],{"class":516},"    }\n",[506,2324,2325,2328,2331],{"class":508,"line":618},[506,2326,2327],{"class":537},"    return",[506,2329,2330],{"class":537}," switch",[506,2332,2333],{"class":516}," (state) {\n",[506,2335,2336,2339,2342,2345,2348,2351,2353,2355,2358],{"class":508,"line":632},[506,2337,2338],{"class":524},"      Initial",[506,2340,2341],{"class":516},"() ",[506,2343,2344],{"class":520},"||",[506,2346,2347],{"class":524}," Loading",[506,2349,2350],{"class":516},"(previousData",[506,2352,624],{"class":520},[506,2354,1668],{"class":520},[506,2356,2357],{"class":516},") ",[506,2359,2360],{"class":520},"=>\n",[506,2362,2363,2366,2369,2372,2374,2376,2378],{"class":508,"line":660},[506,2364,2365],{"class":512},"        const",[506,2367,2368],{"class":524}," Center",[506,2370,2371],{"class":516},"(child",[506,2373,624],{"class":520},[506,2375,1869],{"class":524},[506,2377,1079],{"class":516},[506,2379,629],{"class":520},[506,2381,2382,2385,2387,2389,2391,2394,2396,2399,2401,2403,2405,2407,2410,2413],{"class":508,"line":673},[506,2383,2384],{"class":524},"      Failure",[506,2386,929],{"class":516},[506,2388,624],{"class":520},[506,2390,513],{"class":512},[506,2392,2393],{"class":516}," error",[506,2395,982],{"class":520},[506,2397,2398],{"class":516}," previousData",[506,2400,624],{"class":520},[506,2402,1668],{"class":520},[506,2404,2357],{"class":516},[506,2406,969],{"class":520},[506,2408,2409],{"class":524}," ErrorView",[506,2411,2412],{"class":516},"(error)",[506,2414,629],{"class":520},[506,2416,2417],{"class":508,"line":688},[506,2418,2419],{"class":739},"      // Data available: Success, or Loading/Failure with previousData\n",[506,2421,2422,2425,2427,2430],{"class":508,"line":710},[506,2423,2424],{"class":516},"      _ ",[506,2426,969],{"class":520},[506,2428,2429],{"class":524}," TodoList",[506,2431,615],{"class":516},[506,2433,2434,2437,2439,2441,2443,2445,2447,2449,2451],{"class":508,"line":728},[506,2435,2436],{"class":516},"          todos",[506,2438,624],{"class":520},[506,2440,1630],{"class":516},[506,2442,544],{"class":520},[506,2444,2280],{"class":516},[506,2446,1331],{"class":520},[506,2448,640],{"class":512},[506,2450,1334],{"class":516},[506,2452,629],{"class":520},[506,2454,2455,2458,2460,2462,2464,2466,2468,2471],{"class":508,"line":736},[506,2456,2457],{"class":516},"          isRefreshing",[506,2459,624],{"class":520},[506,2461,2291],{"class":516},[506,2463,2283],{"class":520},[506,2465,2296],{"class":524},[506,2467,544],{"class":520},[506,2469,2470],{"class":516},"fetching",[506,2472,629],{"class":520},[506,2474,2475,2478,2480,2482,2484,2486,2488,2491],{"class":508,"line":743},[506,2476,2477],{"class":516},"          isOffline",[506,2479,624],{"class":520},[506,2481,2291],{"class":516},[506,2483,2283],{"class":520},[506,2485,2296],{"class":524},[506,2487,544],{"class":520},[506,2489,2490],{"class":516},"paused",[506,2492,629],{"class":520},[506,2494,2495,2497],{"class":508,"line":758},[506,2496,1471],{"class":516},[506,2498,629],{"class":520},[506,2500,2501,2503],{"class":508,"line":771},[506,2502,1495],{"class":516},[506,2504,531],{"class":520},[506,2506,2507,2510],{"class":508,"line":793},[506,2508,2509],{"class":516},"  }",[506,2511,629],{"class":520},[506,2513,2514],{"class":508,"line":800},[506,2515,1772],{"class":516},[386,2517],{},[348,2519,2521],{"id":2520},"limits-to-know","Limits to Know",[357,2523,2524,2530],{},[360,2525,2526,2529],{},[376,2527,2528],{},"The mutation queue lives in memory."," It survives connectivity changes, not an app restart: writes still queued when the app is killed are lost. Persisting the queue is the focus of Qora 1.4.",[360,2531,2532,2537,2538,2540],{},[376,2533,2534,2535],{},"This demo uses ",[381,2536,879],{},", so saved data is also reset when the process restarts. With a disk-backed adapter, ",[381,2539,407],{}," restores the list on a cold start.",[386,2542],{},[348,2544,2546],{"id":2545},"source-code","Source Code",[488,2548,2551],{"className":2549,"code":2550,"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 with toJson / fromJson\n│   │   ├── data/todo_api.dart                      ← fake API that fails while offline\n│   │   └── ui/\n│   │       ├── todo_screen.dart                    ← queries, mutations, FetchStatus handling\n│   │       └── todo_tile.dart                      ← pending rendering\n│   └── shared/\n│       ├── connectivity/simulated_connectivity_manager.dart ← online/offline toggle\n│       └── widgets/offline_banner.dart             ← NetworkStatusIndicator wrapper\n└── pubspec.yaml\n",[381,2552,2550],{"__ignoreMap":494},[353,2554,2555],{},[906,2556,2560],{"href":2557,"rel":2558},"https://github.com/meragix/qora/tree/main/examples/ex_04_offline_first",[2559],"nofollow","View full source code",[386,2562],{},[348,2564,2566],{"id":2565},"related-guides","Related Guides",[357,2568,2569,2576,2590,2601],{},[360,2570,2571,422,2573,2575],{},[906,2572,153],{"href":908},[381,2574,229],{},", serializers, storage adapters, model migrations",[360,2577,2578,422,2581,479,2584,2587,2588],{},[906,2579,158],{"href":2580},"../recipes/network-aware",[381,2582,2583],{},"NetworkMode",[381,2585,2586],{},"FetchStatus",", offline mutation queue, ",[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","PersistQoraClient API",": full method reference",[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":1785,"depth":555,"text":1786},{"id":2162,"depth":555,"text":2163},{"id":2184,"depth":555,"text":2185},{"id":2520,"depth":534,"text":2521},{"id":2545,"depth":534,"text":2546},{"id":2565,"depth":534,"text":2566},"A todo app that keeps working without a network: saved data on start, queries that pause offline, and writes queued and replayed in order on reconnect.","md",null,{},{"icon":90},{"title":2632,"description":2633},"Case Study: Offline-First Todo App - Qora","An offline-first Flutter todo app built with PersistQoraClient, NetworkMode.offlineFirst, the offline mutation queue and FetchStatus.paused, with a toggle to simulate losing the network.","ciAK5xcXvXAuIaLuyfw-L5uCP3lFHJhS2PPsF7IiKtc",[2636,2638],{"title":306,"path":307,"stem":308,"description":2637,"icon":309,"children":-1},"Build a high-performance social media-style feed with cursor-based pagination, windowed memory management, and instant optimistic post creation.",{"title":315,"path":316,"stem":317,"description":2639,"icon":161,"children":-1},"Queries pause automatically when offline, mutations queue for later, and everything replays on reconnect: with thundering-herd prevention built in.",1794040393901]