[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"technologies":3,"\u002Fblog\u002Fflutter-golden-tests":167,"team-members":2509,"blog-topics":2850,"mdc-5i2kkd-key":2892,"glossary":2935},[4,10,15,21,26,32,38,43,48,53,58,63,68,73,77,82,87,92,97,103,108,113,118,123,128,133,138,143,147,152,157,162],{"title":5,"description":6,"slug":7,"category":8,"icon":9},"Android","Every app we ship reaches Android — platform APIs, background execution, permissions, and the store requirements that come with them.","android","framework","logos:android-icon",{"title":11,"description":12,"slug":13,"category":8,"icon":14},"Angular","The front-end framework we work in when a product is already Angular — a batteries-included structure that suits large, long-lived apps.","angular","logos:angular-icon",{"title":16,"description":17,"slug":18,"category":19,"icon":20},"Dart","The language every Flutter app we ship is written in — sound null safety, real pattern matching, and a compiler that targets native and the web.","dart","language","logos:dart",{"title":22,"description":23,"slug":24,"category":8,"icon":25},"Django","The Python framework we use when a product needs an admin, auth and a real data model on day one rather than a hand-rolled service.","django","logos:django-icon",{"title":27,"description":28,"slug":29,"category":30,"icon":31},"Docker","Every service we build ships as a container, so what runs on a laptop, in CI and in production is one artefact rather than three of them.","docker","infrastructure","logos:docker-icon",{"title":33,"description":34,"slug":35,"category":36,"icon":37},"Fastlane","The release automation behind our mobile work — signing, builds and store uploads run from CI instead of from one engineer's laptop on release day.","fastlane","tools","logos:fastlane",{"title":39,"description":40,"slug":41,"category":8,"icon":42},"Flutter","Our primary mobile stack since 2018 — one Dart codebase shipping to iOS, Android, web and desktop without a separate team per platform.","flutter","logos:flutter",{"title":44,"description":45,"slug":46,"category":36,"icon":47},"Git","Every project we touch lives in Git — reviewed pull requests, CI on every branch, and a history that still makes sense months later.","git","logos:git-icon",{"title":49,"description":50,"slug":51,"category":19,"icon":52},"Go","Our backend language for real-time APIs and services under load — small binaries, fast builds, and concurrency that stays readable.","go","logos:gopher",{"title":54,"description":55,"slug":56,"category":30,"icon":57},"Google Cloud","The cloud our production workloads run on — managed Kubernetes, storage and networking, without hand-built servers nobody wants to maintain.","gcp","logos:google-cloud",{"title":59,"description":60,"slug":61,"category":36,"icon":62},"Gradle","The build system every Android release goes through — product flavours, signing configs, and the dependency wiring under a Flutter app.","gradle","logos:gradle",{"title":64,"description":65,"slug":66,"category":30,"icon":67},"Helm","How we package a Kubernetes deployment — service, config, secrets and ingress as one versioned unit that can be promoted and rolled back.","helm","logos:helm",{"title":69,"description":70,"slug":71,"category":19,"icon":72},"Kotlin","What we reach for when a Flutter app needs real Android underneath it — platform channels, background work, and native SDK integrations.","kotlin","logos:kotlin-icon",{"title":74,"description":75,"slug":76,"category":8,"icon":72},"Kotlin Multiplatform","Sharing business logic across iOS and Android while each platform keeps its own native UI — the alternative when Flutter is not the right fit.","kmp",{"title":78,"description":79,"slug":80,"category":30,"icon":81},"Kubernetes","How we run services in production — Helm-packaged deployments, rollouts that can be rolled back, and scaling that does not need a person at 3am.","kubernetes","logos:kubernetes",{"title":83,"description":84,"slug":85,"category":30,"icon":86},"NATS","Lightweight messaging between services — publish\u002Fsubscribe and request\u002Freply without the operational weight of a full broker cluster.","nats","logos:nats-icon",{"title":88,"description":89,"slug":90,"category":8,"icon":91},"Nuxt","Vue with server rendering, routing and SEO handled — how we build marketing sites and web apps that must be fast and indexable on first load.","nuxt","logos:nuxt-icon",{"title":93,"description":94,"slug":95,"category":19,"icon":96},"PHP","Where we work with an existing PHP backend — extending it, integrating with it, and building the mobile and web clients it has to serve.","php","logos:php",{"title":98,"description":99,"slug":100,"category":101,"icon":102},"PostgreSQL","Our default database — the one we reach for unless a product gives us a specific reason not to, from schema design through to index tuning.","postgres","database","logos:postgresql",{"title":104,"description":105,"slug":106,"category":19,"icon":107},"Python","Our language for backends, data work and AI integrations — including the Python bindings we ship for our own Rust tooling.","python","logos:python",{"title":109,"description":110,"slug":111,"category":8,"icon":112},"React","The front-end library we work in when a product is already React — components, hooks, and the ecosystem that has grown around them.","react","logos:react",{"title":114,"description":115,"slug":116,"category":101,"icon":117},"Redis","Where we put data that has to be fast and can be rebuilt — caches, sessions, rate limits, and the queues behind a product's slow paths.","redis","logos:redis",{"title":119,"description":120,"slug":121,"category":19,"icon":122},"Ruby","The language our mobile release automation is written in — Fastlane lanes, custom actions, and the CI glue that ships builds to the stores.","ruby","logos:ruby",{"title":124,"description":125,"slug":126,"category":19,"icon":127},"Rust","Where we go when performance and correctness both matter — document rendering, CLI tooling, and services that have to stay fast and predictable.","rust","simple-icons:rust",{"title":129,"description":130,"slug":131,"category":101,"icon":132},"SQLite","The database that ships inside the app — local caches, offline-first storage, and anything that still has to work with no network.","sqlite","logos:sqlite",{"title":134,"description":135,"slug":136,"category":8,"icon":137},"Strapi","A headless CMS we reach for when editors need to own the content — a real admin and a clean API, without building either from scratch.","strapi","logos:strapi-icon",{"title":139,"description":140,"slug":141,"category":19,"icon":142},"Swift","What we reach for when a Flutter app needs real iOS underneath it — platform channels, native SDK integrations, widgets and App Clips.","swift","logos:swift",{"title":144,"description":145,"slug":146,"category":8,"icon":142},"SwiftUI","Apple's UI framework, where we build the native surfaces a Flutter app cannot own — widgets, App Clips, extensions and system integrations.","swiftui",{"title":148,"description":149,"slug":150,"category":8,"icon":151},"Tailwind CSS","How we style every front end we build — utility classes and design tokens instead of a stylesheet that only ever grows and is never deleted.","tailwind","logos:tailwindcss-icon",{"title":153,"description":154,"slug":155,"category":19,"icon":156},"TypeScript","The default for everything we write for the browser and for Node services — types that catch integration bugs before they reach a review.","typescript","logos:typescript-icon",{"title":158,"description":159,"slug":160,"category":8,"icon":161},"Vue","Our front-end framework for admin panels, merchant dashboards and product web apps that have to stay maintainable for years, not sprints.","vue","logos:vue",{"title":163,"description":164,"slug":165,"category":36,"icon":166},"Whisper","OpenAI's open-source speech recognition model — the transcription engine behind voice input, running as both a backend service and on-device in the app.","whisper","simple-icons:openai",{"id":168,"title":169,"author":170,"body":171,"description":177,"extension":2493,"hero":2494,"meta":2497,"metaDescription":2498,"metaTitle":2499,"navigation":360,"ogDescription":2500,"path":2501,"seo":2502,"slug":2503,"stem":2504,"summary":2505,"timestamp":2506,"topic":41,"updated":2507,"__hash__":2508},"blog_en\u002Fblog\u002Fflutter-golden-tests.md","Your Golden Tests Are Passing for the Wrong Reason","maxim",{"type":172,"value":173,"toc":2473},"minimark",[174,178,181,189,198,203,274,278,281,426,442,445,468,478,482,487,497,504,830,841,848,852,869,880,884,922,925,929,947,977,984,993,1107,1110,1118,1122,1125,1143,1152,1158,1210,1213,1223,1749,1779,1788,1792,1795,1871,1874,1877,1897,1900,1904,1907,1920,1926,2156,2171,2177,2181,2184,2187,2228,2234,2238,2245,2257,2261,2264,2267,2271,2432,2436,2440,2453,2469],[175,176,177],"p",{},"The last golden suite we inherited had 214 images in it. All green, for about eight months, across a few hundred pull requests. Then someone opened one of the PNGs to see what it was protecting.",[175,179,180],{},"Every glyph was a black rectangle.",[175,182,183,184,188],{},"The suite had been written by copying the first tutorial that came up, which is not a criticism — the tutorial works, in the sense that ",[185,186,187],"code",{},"flutter test --update-goldens"," produces files and the next run compares against them. It just compares the wrong thing: a layout made of filled boxes, with its shadows flattened into hard blocks, on a machine nobody had pinned. Swap a word for another of the same length, change the font family, and the pixels do not move. The suite was not measuring the UI. It was measuring that nothing had moved.",[175,190,191,192,197],{},"That is the whole problem with ",[193,194,196],"term",{"slug":195},"golden-test","golden tests"," in one example: they are simultaneously the cheapest regression net in Flutter and the most convincing fake. A green checkmark looks identical in both cases.",[199,200,202],"h2",{"id":201},"key-takeaways","Key takeaways",[204,205,206,214,220,230,236,245,258,264],"ul",{},[207,208,209,213],"li",{},[210,211,212],"strong",{},"A golden is a snapshot of the render tree at one logical size, one theme, one locale, one text scale — and nothing else."," Not a screenshot of your app on a phone.",[207,215,216,219],{},[210,217,218],{},"The test environment ships no app fonts."," Unspecified text renders as solid boxes, deliberately, so layout stays deterministic. Without loading fonts, you are golden-testing geometry and calling it a visual test.",[207,221,222,225,226,229],{},[210,223,224],{},"Shadows are flattened into hard-edged blocks"," (",[185,227,228],{},"debugDisableShadows","): the framework drops the blur. Elevation is still in the picture, but blur radius, spread and gradient are not — so shadow regressions are invisible.",[207,231,232,235],{},[210,233,234],{},"Pixels are tied to the engine, the host OS and the Flutter version."," A suite generated on macOS fails on Linux CI. Pin the toolchain and generate on the platform CI runs on.",[207,237,238,244],{},[210,239,240,243],{},[185,241,242],{},"tester.view"," is the surface, not the device."," The default test surface is 800×600 logical pixels at a pixel ratio of 3; without setting a size, every phone screen is rendered at a resolution no user has.",[207,246,247,253,254,257],{},[210,248,249,252],{},[185,250,251],{},"pumpAndSettle"," hangs on a spinner"," — an indeterminate progress indicator schedules frames forever. Loading states need ",[185,255,256],{},"pump(duration)",".",[207,259,260,263],{},[210,261,262],{},"The matrix multiplies."," 12 screens × 2 themes × 3 widths × 2 text scales × 2 locales is 288 images, all regenerated on every Flutter bump. Pick the axes you will actually review.",[207,265,266,269,270,273],{},[210,267,268],{},"A golden suite's value is the probability someone looks at the diff."," Everything else is plumbing. An unreviewed ",[185,271,272],{},"--update-goldens"," commit converts a test into a lockfile.",[199,275,277],{"id":276},"what-a-golden-test-actually-does","What a golden test actually does",[175,279,280],{},"One test, one image, one comparison:",[282,283,287],"pre",{"className":284,"code":285,"language":18,"meta":286,"style":286},"language-dart shiki shiki-themes material-theme-lighter github-light github-dark","testWidgets('cart is empty', (tester) async {\n  await pumpScreen(tester, const CartScreen(items: []));\n\n  await expectLater(\n    find.byType(CartScreen),\n    matchesGoldenFile('goldens\u002Fcart_empty.light.390.ts1.0.en.png'),\n  );\n});\n","",[185,288,289,320,355,362,373,395,410,418],{"__ignoreMap":286},[290,291,294,298,302,306,310,313,317],"span",{"class":292,"line":293},"line",1,[290,295,297],{"class":296},"sGLFI","testWidgets",[290,299,301],{"class":300},"su5hD","(",[290,303,305],{"class":304},"s_sjI","'cart is empty'",[290,307,309],{"class":308},"sP7_E",",",[290,311,312],{"class":300}," (tester) ",[290,314,316],{"class":315},"sVHd0","async",[290,318,319],{"class":300}," {\n",[290,321,323,326,329,332,334,338,342,345,349,352],{"class":292,"line":322},2,[290,324,325],{"class":315},"  await",[290,327,328],{"class":296}," pumpScreen",[290,330,331],{"class":300},"(tester",[290,333,309],{"class":308},[290,335,337],{"class":336},"sbsja"," const",[290,339,341],{"class":340},"sZMiF"," CartScreen",[290,343,344],{"class":300},"(items",[290,346,348],{"class":347},"smGrS",":",[290,350,351],{"class":300}," []))",[290,353,354],{"class":308},";\n",[290,356,358],{"class":292,"line":357},3,[290,359,361],{"emptyLinePlaceholder":360},true,"\n",[290,363,365,367,370],{"class":292,"line":364},4,[290,366,325],{"class":315},[290,368,369],{"class":296}," expectLater",[290,371,372],{"class":300},"(\n",[290,374,376,379,381,384,386,389,392],{"class":292,"line":375},5,[290,377,378],{"class":300},"    find",[290,380,257],{"class":308},[290,382,383],{"class":296},"byType",[290,385,301],{"class":300},[290,387,388],{"class":340},"CartScreen",[290,390,391],{"class":300},")",[290,393,394],{"class":308},",\n",[290,396,398,401,403,406,408],{"class":292,"line":397},6,[290,399,400],{"class":296},"    matchesGoldenFile",[290,402,301],{"class":300},[290,404,405],{"class":304},"'goldens\u002Fcart_empty.light.390.ts1.0.en.png'",[290,407,391],{"class":300},[290,409,394],{"class":308},[290,411,413,416],{"class":292,"line":412},7,[290,414,415],{"class":300},"  )",[290,417,354],{"class":308},[290,419,421,424],{"class":292,"line":420},8,[290,422,423],{"class":300},"})",[290,425,354],{"class":308},[175,427,428,430,431,434,435,438,439,257],{},[185,429,187],{}," writes that PNG and passes. Every later run decodes it and compares it pixel by pixel against what the engine just rendered. Not a tolerance, not a perceptual hash — exact equality, until you install a custom ",[185,432,433],{},"goldenFileComparator",". When it fails, the default comparator drops four artifacts into the ",[185,436,437],{},"failures\u002F"," directory beside the test file: the reference, the new render, an isolated diff and a masked diff. (A size mismatch writes only the first two.) Those are your diff review, and they belong in ",[185,440,441],{},".gitignore",[175,443,444],{},"Two details in that snippet matter more than they look.",[175,446,447,455,456,460,461,464,465,467],{},[210,448,449,452,453,257],{},[185,450,451],{},"find.byType(CartScreen)"," does not capture ",[185,454,388],{}," The matcher walks ",[457,458,459],"em",{},"up"," from the found element to the nearest ",[185,462,463],{},"RepaintBoundary"," ancestor and captures that layer. Usually that ancestor is the whole app, which is why goldens so often come out as a full-window image with a debug banner in the corner. Wrap the subject in a ",[185,466,463],{}," and you capture exactly what you meant.",[175,469,470,473,474,477],{},[210,471,472],{},"The rendered image is not your app."," It is ",[185,475,476],{},"flutter_tester"," on your host machine: no platform fonts, no system UI, no keyboard, a mocked network that only returns 400s, shadows reduced to hard-edged blocks, and whatever surface size the test happened to have. A golden that passes tells you this tree, at this size, with these inputs, has not changed. That is genuinely valuable — and much narrower than \"the UI works\".",[199,479,481],{"id":480},"the-five-reasons-your-first-golden-lies-to-you","The five reasons your first golden lies to you",[483,484,486],"h3",{"id":485},"_1-there-are-no-fonts-so-every-glyph-is-a-box","1. There are no fonts, so every glyph is a box",[175,488,489,492,493,496],{},[185,490,491],{},"flutter test"," runs without your ",[185,494,495],{},"pubspec.yaml"," fonts loaded. Text with no available family falls back to a test font whose every glyph is a filled rectangle — a design that makes layout deterministic and font files unnecessary, and that means the PNG in your repository contains no readable text at all.",[175,498,499,500,503],{},"The fix is ",[185,501,502],{},"test\u002Fflutter_test_config.dart",", which Flutter runs before the suites under that directory. Anything declared there — font loading, a custom comparator, clock setup — applies to every test below it:",[282,505,507],{"className":284,"code":506,"language":18,"meta":286,"style":286},"\u002F\u002F test\u002Fflutter_test_config.dart\nimport 'dart:async';\nimport 'package:flutter\u002Fservices.dart';\n\nFuture\u003Cvoid> testExecutable(FutureOr\u003Cvoid> Function() testMain) async {\n  await _loadFonts();\n  await testMain();\n}\n\nFuture\u003Cvoid> _loadFonts() async {\n  \u002F\u002F The family name must match what the theme asks for. If `fontFamily` is\n  \u002F\u002F null, register the same faces as 'Roboto' — that is what Material falls\n  \u002F\u002F back to, and a mismatch loads nothing and fails silently.\n  \u002F\u002F Both faces are declared under `assets:` in pubspec.yaml, and `flutter test`\n  \u002F\u002F can load them from there — a pure `dart test` cannot.\n  final text = FontLoader('Roboto')\n    ..addFont(rootBundle.load('assets\u002Ffonts\u002FRoboto-Regular.ttf'))\n    ..addFont(rootBundle.load('assets\u002Ffonts\u002FRoboto-Medium.ttf'));\n\n  \u002F\u002F Icons are fonts too. Skip this and every `Icon` in every golden is a box.\n  final icons = FontLoader('MaterialIcons')\n    ..addFont(rootBundle.load('assets\u002Ffonts\u002FMaterialIcons-Regular.otf'));\n\n  await Future.wait([text.load(), icons.load()]);\n}\n",[185,508,509,515,526,535,539,577,589,600,605,610,631,637,643,649,655,661,684,709,732,737,743,762,784,789,825],{"__ignoreMap":286},[290,510,511],{"class":292,"line":293},[290,512,514],{"class":513},"sutJx","\u002F\u002F test\u002Fflutter_test_config.dart\n",[290,516,517,521,524],{"class":292,"line":322},[290,518,520],{"class":519},"sw1J6","import",[290,522,523],{"class":304}," 'dart:async'",[290,525,354],{"class":308},[290,527,528,530,533],{"class":292,"line":357},[290,529,520],{"class":519},[290,531,532],{"class":304}," 'package:flutter\u002Fservices.dart'",[290,534,354],{"class":308},[290,536,537],{"class":292,"line":364},[290,538,361],{"emptyLinePlaceholder":360},[290,540,541,544,547,550,553,556,558,561,563,565,567,570,573,575],{"class":292,"line":375},[290,542,543],{"class":340},"Future",[290,545,546],{"class":300},"\u003C",[290,548,549],{"class":336},"void",[290,551,552],{"class":300},"> ",[290,554,555],{"class":296},"testExecutable",[290,557,301],{"class":300},[290,559,560],{"class":340},"FutureOr",[290,562,546],{"class":300},[290,564,549],{"class":336},[290,566,552],{"class":300},[290,568,569],{"class":340},"Function",[290,571,572],{"class":300},"() testMain) ",[290,574,316],{"class":315},[290,576,319],{"class":300},[290,578,579,581,584,587],{"class":292,"line":397},[290,580,325],{"class":315},[290,582,583],{"class":296}," _loadFonts",[290,585,586],{"class":300},"()",[290,588,354],{"class":308},[290,590,591,593,596,598],{"class":292,"line":412},[290,592,325],{"class":315},[290,594,595],{"class":296}," testMain",[290,597,586],{"class":300},[290,599,354],{"class":308},[290,601,602],{"class":292,"line":420},[290,603,604],{"class":300},"}\n",[290,606,608],{"class":292,"line":607},9,[290,609,361],{"emptyLinePlaceholder":360},[290,611,613,615,617,619,621,624,627,629],{"class":292,"line":612},10,[290,614,543],{"class":340},[290,616,546],{"class":300},[290,618,549],{"class":336},[290,620,552],{"class":300},[290,622,623],{"class":296},"_loadFonts",[290,625,626],{"class":300},"() ",[290,628,316],{"class":315},[290,630,319],{"class":300},[290,632,634],{"class":292,"line":633},11,[290,635,636],{"class":513},"  \u002F\u002F The family name must match what the theme asks for. If `fontFamily` is\n",[290,638,640],{"class":292,"line":639},12,[290,641,642],{"class":513},"  \u002F\u002F null, register the same faces as 'Roboto' — that is what Material falls\n",[290,644,646],{"class":292,"line":645},13,[290,647,648],{"class":513},"  \u002F\u002F back to, and a mismatch loads nothing and fails silently.\n",[290,650,652],{"class":292,"line":651},14,[290,653,654],{"class":513},"  \u002F\u002F Both faces are declared under `assets:` in pubspec.yaml, and `flutter test`\n",[290,656,658],{"class":292,"line":657},15,[290,659,660],{"class":513},"  \u002F\u002F can load them from there — a pure `dart test` cannot.\n",[290,662,664,667,670,673,676,678,681],{"class":292,"line":663},16,[290,665,666],{"class":336},"  final",[290,668,669],{"class":300}," text ",[290,671,672],{"class":347},"=",[290,674,675],{"class":340}," FontLoader",[290,677,301],{"class":300},[290,679,680],{"class":304},"'Roboto'",[290,682,683],{"class":300},")\n",[290,685,687,690,693,696,698,701,703,706],{"class":292,"line":686},17,[290,688,689],{"class":308},"    ..",[290,691,692],{"class":296},"addFont",[290,694,695],{"class":300},"(rootBundle",[290,697,257],{"class":308},[290,699,700],{"class":296},"load",[290,702,301],{"class":300},[290,704,705],{"class":304},"'assets\u002Ffonts\u002FRoboto-Regular.ttf'",[290,707,708],{"class":300},"))\n",[290,710,712,714,716,718,720,722,724,727,730],{"class":292,"line":711},18,[290,713,689],{"class":308},[290,715,692],{"class":296},[290,717,695],{"class":300},[290,719,257],{"class":308},[290,721,700],{"class":296},[290,723,301],{"class":300},[290,725,726],{"class":304},"'assets\u002Ffonts\u002FRoboto-Medium.ttf'",[290,728,729],{"class":300},"))",[290,731,354],{"class":308},[290,733,735],{"class":292,"line":734},19,[290,736,361],{"emptyLinePlaceholder":360},[290,738,740],{"class":292,"line":739},20,[290,741,742],{"class":513},"  \u002F\u002F Icons are fonts too. Skip this and every `Icon` in every golden is a box.\n",[290,744,746,748,751,753,755,757,760],{"class":292,"line":745},21,[290,747,666],{"class":336},[290,749,750],{"class":300}," icons ",[290,752,672],{"class":347},[290,754,675],{"class":340},[290,756,301],{"class":300},[290,758,759],{"class":304},"'MaterialIcons'",[290,761,683],{"class":300},[290,763,765,767,769,771,773,775,777,780,782],{"class":292,"line":764},22,[290,766,689],{"class":308},[290,768,692],{"class":296},[290,770,695],{"class":300},[290,772,257],{"class":308},[290,774,700],{"class":296},[290,776,301],{"class":300},[290,778,779],{"class":304},"'assets\u002Ffonts\u002FMaterialIcons-Regular.otf'",[290,781,729],{"class":300},[290,783,354],{"class":308},[290,785,787],{"class":292,"line":786},23,[290,788,361],{"emptyLinePlaceholder":360},[290,790,792,794,797,799,802,805,807,809,811,813,816,818,820,823],{"class":292,"line":791},24,[290,793,325],{"class":315},[290,795,796],{"class":340}," Future",[290,798,257],{"class":308},[290,800,801],{"class":296},"wait",[290,803,804],{"class":300},"([text",[290,806,257],{"class":308},[290,808,700],{"class":296},[290,810,586],{"class":300},[290,812,309],{"class":308},[290,814,815],{"class":300}," icons",[290,817,257],{"class":308},[290,819,700],{"class":296},[290,821,822],{"class":300},"()])",[290,824,354],{"class":308},[290,826,828],{"class":292,"line":827},25,[290,829,604],{"class":300},[175,831,832,833,836,837,840],{},"Two things make this fail quietly rather than loudly. A wrong family name loads a font that no widget asks for, so nothing changes. And ",[185,834,835],{},"google_fonts"," downloads its faces at runtime, which the test environment does not do — vendor the ",[185,838,839],{},".ttf"," files you actually ship, or your goldens are permanently pending a network call that never happens.",[175,842,843,844,847],{},"There is a subtler consequence, and it is the one that bites teams who skip this section: ",[210,845,846],{},"with box glyphs you cannot see what the text says, and every property the test font normalizes disappears."," Family, weight, kerning, glyph shape and the real font's ascent and descent are replaced by identical squares, so switching from Roboto to Inter renders the same boxes, and a copy change of equal character count moves nothing at all. Character count and font size still move the boxes — a longer Russian string produces a wider bar — so overflow and size changes are not invisible. Only the things that make type legible in production are.",[483,849,851],{"id":850},"_2-shadows-are-flattened-so-blur-does-not-exist","2. Shadows are flattened, so blur does not exist",[175,853,854,856,857,860,861,864,865,868],{},[185,855,228],{}," does not remove shadows; it ",[210,858,859],{},"replaces them with solid blocks",". ",[185,862,863],{},"BoxShadow.toPaint()"," drops its mask filter, and a Material surface paints its elevation as a hard outline (",[185,866,867],{},"strokeWidth: elevation * 2","), so what lands in the golden is an unblurred shape in the shadow color. The test binding turns it on because shadow rendering is not guaranteed to be pixel-identical from version to version, or even from run to run.",[175,870,871,872,875,876,879],{},"The consequence is narrower than \"no shadows\" and more misleading: the card is still there, but its blur radius, spread and gradient are not, so a shadow regression — a blur that doubled, a spread that broke the layout — renders identically in the golden. The Flutter framework's own tests set ",[185,873,874],{},"debugDisableShadows = false"," when they are specifically testing shadows, and restore it at the end of the test; the binding asserts the flag is back to its default (",[185,877,878],{},"true",") afterward. That is the right shape of decision: turn real shadows back on where the shadow is the thing under test, accept the flakiness there, and leave the flag alone everywhere else.",[483,881,883],{"id":882},"_3-every-network-image-is-a-placeholder","3. Every network image is a placeholder",[175,885,886,889,890,893,894,897,898,901,902,905,906,909,910,913,914,917,918,921],{},[185,887,888],{},"Image.network"," in a test hits a mocked ",[185,891,892],{},"HttpClient"," that returns an empty 400 response. What happens next depends on the widget: with no ",[185,895,896],{},"errorBuilder",", the ",[185,899,900],{},"Image"," widget re-throws the load error in debug and the test fails with a ",[185,903,904],{},"NetworkImageLoadException","; with one, the golden quietly records your placeholder instead. ",[185,907,908],{},"cached_network_image"," and friends need their own fake. Either wrap calls in ",[185,911,912],{},"HttpOverrides"," (older recipes do), or do the thing that survives a refactor: take an ",[185,915,916],{},"ImageProvider"," in the widget and hand it a ",[185,919,920],{},"MemoryImage"," of a 1×1 PNG in the test.",[175,923,924],{},"Avatars and product shots are most of the visual weight of a real screen, so a suite where all of them are placeholders is testing the layout around the pictures and nothing about the pictures.",[483,926,928],{"id":927},"_4-the-clock-the-locale-and-the-randomness-are-not-yours","4. The clock, the locale and the randomness are not yours",[175,930,931,934,935,938,939,942,943,946],{},[185,932,933],{},"DateTime.now()"," in a widget, ",[185,936,937],{},"DateFormat"," without a fixed date, an unseeded ",[185,940,941],{},"Random",", a ",[185,944,945],{},"uuid"," generated during build — each one makes a golden that passes today and fails on the second Tuesday of some month, or fails only in CI where the machine's locale differs.",[175,948,949,950,953,954,957,958,961,962,965,966,969,970,973,974,976],{},"The fix is not in the test, it is in the widget: take a ",[185,951,952],{},"now"," function or a ",[185,955,956],{},"Clock",", take a ",[185,959,960],{},"Locale",", take a seed. That is worth doing anyway — a widget that reads the wall clock directly is a widget you cannot test at any layer, golden or otherwise. Set ",[185,963,964],{},"Intl.defaultLocale"," explicitly in ",[185,967,968],{},"flutter_test_config.dart"," and you remove a whole class of \"works on my machine, wrong language in CI\" failures; for non-English dates, also call ",[185,971,972],{},"initializeDateFormatting(locale)",", because ",[185,975,937],{}," throws for a locale it has no data for.",[483,978,980,981,983],{"id":979},"_5-pumpandsettle-never-returns-on-a-loading-state","5. ",[185,982,251],{}," never returns on a loading state",[175,985,986,988,989,992],{},[185,987,251],{}," pumps until no frame is scheduled. An indeterminate ",[185,990,991],{},"CircularProgressIndicator"," schedules frames for as long as it spins, so the call times out with the famously unhelpful \"pumpAndSettle timed out\", and the loading-state golden — the one state QA never looks at because you cannot get the app to stay in it — is the one you cannot write.",[282,994,996],{"className":284,"code":995,"language":18,"meta":286,"style":286},"\u002F\u002F Not this: it waits for an animation that never ends.\n\u002F\u002F await tester.pumpAndSettle();\n\nawait pumpScreen(tester, const CartScreen.loading());\nawait tester.pump(const Duration(milliseconds: 100)); \u002F\u002F one fixed phase of the spinner\nawait expectLater(find.byType(CartScreen), matchesGoldenFile('goldens\u002Fcart_loading.light.390.ts1.0.en.png'));\n",[185,997,998,1003,1008,1012,1037,1074],{"__ignoreMap":286},[290,999,1000],{"class":292,"line":293},[290,1001,1002],{"class":513},"\u002F\u002F Not this: it waits for an animation that never ends.\n",[290,1004,1005],{"class":292,"line":322},[290,1006,1007],{"class":513},"\u002F\u002F await tester.pumpAndSettle();\n",[290,1009,1010],{"class":292,"line":357},[290,1011,361],{"emptyLinePlaceholder":360},[290,1013,1014,1017,1019,1021,1023,1025,1027,1029,1032,1035],{"class":292,"line":364},[290,1015,1016],{"class":315},"await",[290,1018,328],{"class":296},[290,1020,331],{"class":300},[290,1022,309],{"class":308},[290,1024,337],{"class":336},[290,1026,341],{"class":340},[290,1028,257],{"class":308},[290,1030,1031],{"class":296},"loading",[290,1033,1034],{"class":300},"())",[290,1036,354],{"class":308},[290,1038,1039,1041,1044,1046,1049,1051,1054,1057,1060,1062,1066,1068,1071],{"class":292,"line":375},[290,1040,1016],{"class":315},[290,1042,1043],{"class":300}," tester",[290,1045,257],{"class":308},[290,1047,1048],{"class":296},"pump",[290,1050,301],{"class":300},[290,1052,1053],{"class":336},"const",[290,1055,1056],{"class":340}," Duration",[290,1058,1059],{"class":300},"(milliseconds",[290,1061,348],{"class":347},[290,1063,1065],{"class":1064},"srdBf"," 100",[290,1067,729],{"class":300},[290,1069,1070],{"class":308},";",[290,1072,1073],{"class":513}," \u002F\u002F one fixed phase of the spinner\n",[290,1075,1076,1078,1080,1083,1085,1087,1089,1091,1093,1095,1098,1100,1103,1105],{"class":292,"line":397},[290,1077,1016],{"class":315},[290,1079,369],{"class":296},[290,1081,1082],{"class":300},"(find",[290,1084,257],{"class":308},[290,1086,383],{"class":296},[290,1088,301],{"class":300},[290,1090,388],{"class":340},[290,1092,391],{"class":300},[290,1094,309],{"class":308},[290,1096,1097],{"class":296}," matchesGoldenFile",[290,1099,301],{"class":300},[290,1101,1102],{"class":304},"'goldens\u002Fcart_loading.light.390.ts1.0.en.png'",[290,1104,729],{"class":300},[290,1106,354],{"class":308},[175,1108,1109],{},"The golden now captures the spinner at 100ms, consistently, forever. That is fine: you are testing that the surrounding layout holds while something indeterminate spins, not the spinner itself.",[1111,1112,1115],"info-box",{":leading-icon":1113,":title":1114},"lucide:alert-triangle","A green suite with 214 files is not a strong suite",[175,1116,1117],{},"Everything above produces the same symptom: tests that pass while testing less than you think. None of it shows up as a failure, a warning, or a coverage drop. A golden suite is unusual in that its failure mode is silence — so the only way to trust one is to open a PNG and check it contains what you believe it contains. Do that once, on day one, for every distinct kind of screenshot in the suite. It takes ten minutes and it is the highest-value ten minutes in the whole setup.",[199,1119,1121],{"id":1120},"the-setup-that-makes-goldens-reproducible","The setup that makes goldens reproducible",[175,1123,1124],{},"Four decisions, all boring, all of which you make once:",[175,1126,1127,1130,1131,1134,1135,1138,1139,1142],{},[210,1128,1129],{},"Pin the toolchain."," A ",[185,1132,1133],{},".fvmrc"," in the repo, and CI reading the same file. A Flutter upgrade changes text rendering, theme defaults, ",[185,1136,1137],{},"CardTheme","-to-",[185,1140,1141],{},"CardThemeData"," renames and shadow painting, so every golden in the repository changes at once. That is not a reason to avoid upgrades; it is a reason to make the upgrade a deliberate commit that contains nothing but regenerated goldens, marked as such in the message.",[175,1144,1145,1148,1149,1151],{},[210,1146,1147],{},"Generate on the platform CI runs on."," Text anti-aliasing and hinting differ between macOS and Linux — the same widget at the same size produces different pixels. A suite generated on a developer's Mac will fail on a Linux runner, and the usual \"fix\" is ",[185,1150,272],{}," somewhere it should not be. Run goldens on Linux locally too, in the same container as CI if the team is mixed.",[175,1153,1154,1157],{},[210,1155,1156],{},"Never let CI regenerate."," The guard is one line:",[282,1159,1163],{"className":1160,"code":1161,"language":1162,"meta":286,"style":286},"language-yaml shiki shiki-themes material-theme-lighter github-light github-dark","- uses: subosito\u002Fflutter-action@v2\n  with:\n    flutter-version-file: .fvmrc   # one version, repo and CI\n    cache: true\n- run: flutter test\n# A \"helpful\" --update-goldens in CI or a stale golden written by a bad merge\n# would make the suite green while comparing nothing. Fail instead. `git diff`\n# alone misses untracked files, which is exactly what a regenerated golden is.\n- run: test -z \"$(git status --porcelain -- '*.png')\"\n","yaml",[185,1164,1165,1170,1175,1180,1185,1190,1195,1200,1205],{"__ignoreMap":286},[290,1166,1167],{"class":292,"line":293},[290,1168,1169],{},"- uses: subosito\u002Fflutter-action@v2\n",[290,1171,1172],{"class":292,"line":322},[290,1173,1174],{},"  with:\n",[290,1176,1177],{"class":292,"line":357},[290,1178,1179],{},"    flutter-version-file: .fvmrc   # one version, repo and CI\n",[290,1181,1182],{"class":292,"line":364},[290,1183,1184],{},"    cache: true\n",[290,1186,1187],{"class":292,"line":375},[290,1188,1189],{},"- run: flutter test\n",[290,1191,1192],{"class":292,"line":397},[290,1193,1194],{},"# A \"helpful\" --update-goldens in CI or a stale golden written by a bad merge\n",[290,1196,1197],{"class":292,"line":412},[290,1198,1199],{},"# would make the suite green while comparing nothing. Fail instead. `git diff`\n",[290,1201,1202],{"class":292,"line":420},[290,1203,1204],{},"# alone misses untracked files, which is exactly what a regenerated golden is.\n",[290,1206,1207],{"class":292,"line":607},[290,1208,1209],{},"- run: test -z \"$(git status --porcelain -- '*.png')\"\n",[175,1211,1212],{},"That last line is not paranoia. The most common way a golden suite stops testing anything is a CI step, added during a red build at 2am, that updates goldens instead of failing.",[175,1214,1215,1218,1219,1222],{},[210,1216,1217],{},"Set the surface explicitly."," The default test surface is 800×600 logical pixels at a pixel ratio of 3. Test a phone screen on it and you get the layout of a small tablet, with the app's own ",[185,1220,1221],{},"MediaQuery"," insisting otherwise. One helper solves it for every test, and if you cannot read the golden's name and know the five axes it was rendered at, the file name is wrong:",[282,1224,1226],{"className":284,"code":1225,"language":18,"meta":286,"style":286},"\u002F\u002F test\u002Fhelpers\u002Fgolden.dart\nFuture\u003Cvoid> pumpScreen(\n  WidgetTester tester,\n  Widget child, {\n  Size size = const Size(390, 844), \u002F\u002F iPhone 14 — pick your own baselines\n  double textScale = 1,\n  Locale locale = const Locale('en'),\n  Brightness brightness = Brightness.light,\n}) async {\n  tester.view.physicalSize = size * 3;   \u002F\u002F logical size × device pixel ratio\n  tester.view.devicePixelRatio = 3;\n  addTearDown(tester.view.reset);        \u002F\u002F never leak a view into the next test\n\n  await tester.pumpWidget(\n    MaterialApp(\n      debugShowCheckedModeBanner: false,\n      theme: brightness == Brightness.dark ? AppTheme.dark : AppTheme.light,\n      locale: locale,\n      \u002F\u002F Without the app's own delegates a locale it does not support resolves\n      \u002F\u002F back to English.\n      localizationsDelegates: AppLocalizations.delegates,\n      supportedLocales: AppLocalizations.supportedLocales,\n      home: RepaintBoundary(\n        child: Builder(\n          builder: (context) => MediaQuery(\n            \u002F\u002F Merge, don't replace: a fresh MediaQueryData has size zero,\n            \u002F\u002F devicePixelRatio 1 and no padding.\n            data: MediaQuery.of(context).copyWith(\n              textScaler: TextScaler.linear(textScale),\n            ),\n            child: child,\n          ),\n        ),\n      ),\n    ),\n  );\n  await tester.pump();\n}\n",[185,1227,1228,1233,1248,1257,1269,1301,1316,1340,1360,1369,1399,1418,1439,1443,1456,1463,1476,1515,1527,1532,1537,1554,1570,1582,1594,1612,1618,1624,1649,1670,1678,1690,1698,1706,1714,1722,1729,1744],{"__ignoreMap":286},[290,1229,1230],{"class":292,"line":293},[290,1231,1232],{"class":513},"\u002F\u002F test\u002Fhelpers\u002Fgolden.dart\n",[290,1234,1235,1237,1239,1241,1243,1246],{"class":292,"line":322},[290,1236,543],{"class":340},[290,1238,546],{"class":300},[290,1240,549],{"class":336},[290,1242,552],{"class":300},[290,1244,1245],{"class":296},"pumpScreen",[290,1247,372],{"class":300},[290,1249,1250,1253,1255],{"class":292,"line":357},[290,1251,1252],{"class":340},"  WidgetTester",[290,1254,1043],{"class":300},[290,1256,394],{"class":308},[290,1258,1259,1262,1265,1267],{"class":292,"line":364},[290,1260,1261],{"class":340},"  Widget",[290,1263,1264],{"class":300}," child",[290,1266,309],{"class":308},[290,1268,319],{"class":300},[290,1270,1271,1274,1277,1279,1281,1284,1286,1289,1291,1294,1296,1298],{"class":292,"line":375},[290,1272,1273],{"class":340},"  Size",[290,1275,1276],{"class":300}," size ",[290,1278,672],{"class":347},[290,1280,337],{"class":336},[290,1282,1283],{"class":340}," Size",[290,1285,301],{"class":300},[290,1287,1288],{"class":1064},"390",[290,1290,309],{"class":308},[290,1292,1293],{"class":1064}," 844",[290,1295,391],{"class":300},[290,1297,309],{"class":308},[290,1299,1300],{"class":513}," \u002F\u002F iPhone 14 — pick your own baselines\n",[290,1302,1303,1306,1309,1311,1314],{"class":292,"line":397},[290,1304,1305],{"class":340},"  double",[290,1307,1308],{"class":300}," textScale ",[290,1310,672],{"class":347},[290,1312,1313],{"class":1064}," 1",[290,1315,394],{"class":308},[290,1317,1318,1321,1324,1326,1328,1331,1333,1336,1338],{"class":292,"line":412},[290,1319,1320],{"class":340},"  Locale",[290,1322,1323],{"class":300}," locale ",[290,1325,672],{"class":347},[290,1327,337],{"class":336},[290,1329,1330],{"class":340}," Locale",[290,1332,301],{"class":300},[290,1334,1335],{"class":304},"'en'",[290,1337,391],{"class":300},[290,1339,394],{"class":308},[290,1341,1342,1345,1348,1350,1353,1355,1358],{"class":292,"line":420},[290,1343,1344],{"class":340},"  Brightness",[290,1346,1347],{"class":300}," brightness ",[290,1349,672],{"class":347},[290,1351,1352],{"class":340}," Brightness",[290,1354,257],{"class":308},[290,1356,1357],{"class":300},"light",[290,1359,394],{"class":308},[290,1361,1362,1365,1367],{"class":292,"line":607},[290,1363,1364],{"class":300},"}) ",[290,1366,316],{"class":315},[290,1368,319],{"class":300},[290,1370,1371,1374,1376,1379,1381,1384,1386,1388,1391,1394,1396],{"class":292,"line":612},[290,1372,1373],{"class":300},"  tester",[290,1375,257],{"class":308},[290,1377,1378],{"class":300},"view",[290,1380,257],{"class":308},[290,1382,1383],{"class":300},"physicalSize ",[290,1385,672],{"class":347},[290,1387,1276],{"class":300},[290,1389,1390],{"class":347},"*",[290,1392,1393],{"class":1064}," 3",[290,1395,1070],{"class":308},[290,1397,1398],{"class":513},"   \u002F\u002F logical size × device pixel ratio\n",[290,1400,1401,1403,1405,1407,1409,1412,1414,1416],{"class":292,"line":633},[290,1402,1373],{"class":300},[290,1404,257],{"class":308},[290,1406,1378],{"class":300},[290,1408,257],{"class":308},[290,1410,1411],{"class":300},"devicePixelRatio ",[290,1413,672],{"class":347},[290,1415,1393],{"class":1064},[290,1417,354],{"class":308},[290,1419,1420,1423,1425,1427,1429,1431,1434,1436],{"class":292,"line":639},[290,1421,1422],{"class":296},"  addTearDown",[290,1424,331],{"class":300},[290,1426,257],{"class":308},[290,1428,1378],{"class":300},[290,1430,257],{"class":308},[290,1432,1433],{"class":300},"reset)",[290,1435,1070],{"class":308},[290,1437,1438],{"class":513},"        \u002F\u002F never leak a view into the next test\n",[290,1440,1441],{"class":292,"line":645},[290,1442,361],{"emptyLinePlaceholder":360},[290,1444,1445,1447,1449,1451,1454],{"class":292,"line":651},[290,1446,325],{"class":315},[290,1448,1043],{"class":300},[290,1450,257],{"class":308},[290,1452,1453],{"class":296},"pumpWidget",[290,1455,372],{"class":300},[290,1457,1458,1461],{"class":292,"line":657},[290,1459,1460],{"class":340},"    MaterialApp",[290,1462,372],{"class":300},[290,1464,1465,1468,1470,1474],{"class":292,"line":663},[290,1466,1467],{"class":300},"      debugShowCheckedModeBanner",[290,1469,348],{"class":347},[290,1471,1473],{"class":1472},"s39Yj"," false",[290,1475,394],{"class":308},[290,1477,1478,1481,1483,1485,1488,1490,1492,1495,1498,1501,1503,1505,1507,1509,1511,1513],{"class":292,"line":686},[290,1479,1480],{"class":300},"      theme",[290,1482,348],{"class":347},[290,1484,1347],{"class":300},[290,1486,1487],{"class":347},"==",[290,1489,1352],{"class":340},[290,1491,257],{"class":308},[290,1493,1494],{"class":300},"dark ",[290,1496,1497],{"class":347},"?",[290,1499,1500],{"class":340}," AppTheme",[290,1502,257],{"class":308},[290,1504,1494],{"class":300},[290,1506,348],{"class":347},[290,1508,1500],{"class":340},[290,1510,257],{"class":308},[290,1512,1357],{"class":300},[290,1514,394],{"class":308},[290,1516,1517,1520,1522,1525],{"class":292,"line":711},[290,1518,1519],{"class":300},"      locale",[290,1521,348],{"class":347},[290,1523,1524],{"class":300}," locale",[290,1526,394],{"class":308},[290,1528,1529],{"class":292,"line":734},[290,1530,1531],{"class":513},"      \u002F\u002F Without the app's own delegates a locale it does not support resolves\n",[290,1533,1534],{"class":292,"line":739},[290,1535,1536],{"class":513},"      \u002F\u002F back to English.\n",[290,1538,1539,1542,1544,1547,1549,1552],{"class":292,"line":745},[290,1540,1541],{"class":300},"      localizationsDelegates",[290,1543,348],{"class":347},[290,1545,1546],{"class":340}," AppLocalizations",[290,1548,257],{"class":308},[290,1550,1551],{"class":300},"delegates",[290,1553,394],{"class":308},[290,1555,1556,1559,1561,1563,1565,1568],{"class":292,"line":764},[290,1557,1558],{"class":300},"      supportedLocales",[290,1560,348],{"class":347},[290,1562,1546],{"class":340},[290,1564,257],{"class":308},[290,1566,1567],{"class":300},"supportedLocales",[290,1569,394],{"class":308},[290,1571,1572,1575,1577,1580],{"class":292,"line":786},[290,1573,1574],{"class":300},"      home",[290,1576,348],{"class":347},[290,1578,1579],{"class":340}," RepaintBoundary",[290,1581,372],{"class":300},[290,1583,1584,1587,1589,1592],{"class":292,"line":791},[290,1585,1586],{"class":300},"        child",[290,1588,348],{"class":347},[290,1590,1591],{"class":340}," Builder",[290,1593,372],{"class":300},[290,1595,1596,1599,1601,1604,1607,1610],{"class":292,"line":827},[290,1597,1598],{"class":300},"          builder",[290,1600,348],{"class":347},[290,1602,1603],{"class":300}," (context) ",[290,1605,1606],{"class":347},"=>",[290,1608,1609],{"class":340}," MediaQuery",[290,1611,372],{"class":300},[290,1613,1615],{"class":292,"line":1614},26,[290,1616,1617],{"class":513},"            \u002F\u002F Merge, don't replace: a fresh MediaQueryData has size zero,\n",[290,1619,1621],{"class":292,"line":1620},27,[290,1622,1623],{"class":513},"            \u002F\u002F devicePixelRatio 1 and no padding.\n",[290,1625,1627,1630,1632,1634,1636,1639,1642,1644,1647],{"class":292,"line":1626},28,[290,1628,1629],{"class":300},"            data",[290,1631,348],{"class":347},[290,1633,1609],{"class":340},[290,1635,257],{"class":308},[290,1637,1638],{"class":296},"of",[290,1640,1641],{"class":300},"(context)",[290,1643,257],{"class":308},[290,1645,1646],{"class":296},"copyWith",[290,1648,372],{"class":300},[290,1650,1652,1655,1657,1660,1662,1665,1668],{"class":292,"line":1651},29,[290,1653,1654],{"class":300},"              textScaler",[290,1656,348],{"class":347},[290,1658,1659],{"class":340}," TextScaler",[290,1661,257],{"class":308},[290,1663,1664],{"class":296},"linear",[290,1666,1667],{"class":300},"(textScale)",[290,1669,394],{"class":308},[290,1671,1673,1676],{"class":292,"line":1672},30,[290,1674,1675],{"class":300},"            )",[290,1677,394],{"class":308},[290,1679,1681,1684,1686,1688],{"class":292,"line":1680},31,[290,1682,1683],{"class":300},"            child",[290,1685,348],{"class":347},[290,1687,1264],{"class":300},[290,1689,394],{"class":308},[290,1691,1693,1696],{"class":292,"line":1692},32,[290,1694,1695],{"class":300},"          )",[290,1697,394],{"class":308},[290,1699,1701,1704],{"class":292,"line":1700},33,[290,1702,1703],{"class":300},"        )",[290,1705,394],{"class":308},[290,1707,1709,1712],{"class":292,"line":1708},34,[290,1710,1711],{"class":300},"      )",[290,1713,394],{"class":308},[290,1715,1717,1720],{"class":292,"line":1716},35,[290,1718,1719],{"class":300},"    )",[290,1721,394],{"class":308},[290,1723,1725,1727],{"class":292,"line":1724},36,[290,1726,415],{"class":300},[290,1728,354],{"class":308},[290,1730,1732,1734,1736,1738,1740,1742],{"class":292,"line":1731},37,[290,1733,325],{"class":315},[290,1735,1043],{"class":300},[290,1737,257],{"class":308},[290,1739,1048],{"class":296},[290,1741,586],{"class":300},[290,1743,354],{"class":308},[290,1745,1747],{"class":292,"line":1746},38,[290,1748,604],{"class":300},[175,1750,1751,1752,1755,1756,1758,1759,1762,1763,1766,1767,1770,1771,1774,1775,1778],{},"Two details here are easy to get wrong. ",[185,1753,1754],{},"MediaQueryData(...)"," instead of ",[185,1757,1646],{}," builds a fresh data object with ",[185,1760,1761],{},"size: Size.zero"," and ",[185,1764,1765],{},"devicePixelRatio: 1",", so ",[185,1768,1769],{},"MediaQuery.sizeOf"," lies to every widget below it. And a ",[185,1772,1773],{},"MaterialApp"," rebuilt inside the helper resolves any locale it does not support back to English unless you hand it the app's own delegates (which is what ",[185,1776,1777],{},"AppLocalizations"," stands in for here); otherwise the locale axis silently tests English.",[175,1780,1781,1784,1785,1787],{},[185,1782,1783],{},"addTearDown(tester.view.reset)"," is not optional. ",[185,1786,242],{}," is global for the test file, so one test that sets a 320pt width leaks into every test after it, and the failure shows up attached to an innocent test later in the same file.",[199,1789,1791],{"id":1790},"what-to-golden-and-what-the-matrix-costs","What to golden, and what the matrix costs",[175,1793,1794],{},"The trap is that the matrix looks free. It is not: every image is a file to regenerate, a file to review, and a file that adds a line to the diff of your next unrelated pull request.",[1796,1797,1798,1814],"table",{},[1799,1800,1801],"thead",{},[1802,1803,1804,1808,1811],"tr",{},[1805,1806,1807],"th",{},"Layer",[1805,1809,1810],{},"What it covers",[1805,1812,1813],{},"Images",[1815,1816,1817,1829,1840,1851,1862],"tbody",{},[1802,1818,1819,1823,1826],{},[1820,1821,1822],"td",{},"Design-system components — button, input, badge, card, list tile",[1820,1824,1825],{},"Every state × light\u002Fdark, at one width",[1820,1827,1828],{},"20–40",[1802,1830,1831,1834,1837],{},[1820,1832,1833],{},"The five screens that matter",[1820,1835,1836],{},"3 widths (320\u002F390\u002F768) × light\u002Fdark",[1820,1838,1839],{},"30",[1802,1841,1842,1845,1848],{},[1820,1843,1844],{},"Empty, error and loading states",[1820,1846,1847],{},"The states nobody opens the app to see",[1820,1849,1850],{},"6–10",[1802,1852,1853,1856,1859],{},[1820,1854,1855],{},"Text scale 2.0, dark, 320pt",[1820,1857,1858],{},"The design system's two densest components",[1820,1860,1861],{},"4",[1802,1863,1864,1866,1869],{},[1820,1865,960],{},[1820,1867,1868],{},"The longest-string language, one width, the two screens most likely to overflow",[1820,1870,1861],{},[175,1872,1873],{},"That is a suite of roughly 60–90 images: about twenty seconds of CI and a pull request whose visual diff a person can actually look at. The version that multiplies everything by every axis — 12 screens × 2 themes × 3 widths × 2 text scales × 2 locales — is 288 images, and nobody reviews 288 images.",[175,1875,1876],{},"Three things are not worth goldening at all:",[204,1878,1879,1885,1891],{},[207,1880,1881,1884],{},[210,1882,1883],{},"Animations and transitions."," A golden captures one frame at one fixed time. It tells you the frame at 100ms looks the same, not that the animation is smooth, and the marginal value over the initial and final states is near zero.",[207,1886,1887,1890],{},[210,1888,1889],{},"Anything with real content in it."," Long product names, prices, dates, generated IDs — every one is a reason for the golden to change for a non-visual reason. Golden the shell with fixed fixture data, and test content rendering with a widget test that asserts on text, not pixels.",[207,1892,1893,1896],{},[210,1894,1895],{},"Whole flows."," Three screens in one image is a third as reviewable as one screen per image.",[175,1898,1899],{},"And the sharpest thing to golden, in our experience, is not a screen at all: it is the design system at 320pt in dark mode with text at 200%. That combination is where real apps break, it is nearly impossible to check by hand across every component, and it is exactly what a golden is good at.",[199,1901,1903],{"id":1902},"what-goldens-do-not-test","What goldens do not test",[175,1905,1906],{},"A golden is a picture. It knows nothing about meaning, and a few common assumptions are worth correcting explicitly.",[175,1908,1909,1912,1913,1916,1917,1919],{},[210,1910,1911],{},"Behavior."," A button wired to the wrong callback renders identically. So does a form that no longer validates, a list that lost its scroll controller, a dialog that fails to dismiss. Those are widget tests — ",[185,1914,1915],{},"tap",", ",[185,1918,1048],{},", assert on what happened — and no amount of pixels substitutes for them.",[175,1921,1922,1925],{},[210,1923,1924],{},"Accessibility."," The semantics tree is invisible to a screenshot: missing labels, wrong roles, a tap target reduced to 24pt, contrast under 4.5:1 for body text (3:1 for large text). Flutter ships the assertions, and they are cheap:",[282,1927,1929],{"className":284,"code":1928,"language":18,"meta":286,"style":286},"testWidgets('cart meets a11y guidelines', (tester) async {\n  final semantics = tester.ensureSemantics();\n  addTearDown(semantics.dispose);\n\n  await pumpScreen(tester, const CartScreen(items: []));\n\n  \u002F\u002F Tap targets (48dp Android \u002F 44pt iOS) and text contrast.\n  await expectLater(tester, meetsGuideline(androidTapTargetGuideline));\n  await expectLater(tester, meetsGuideline(textContrastGuideline));\n\n  \u002F\u002F The label and role the golden cannot see. `isSemantics` checks only the\n  \u002F\u002F values you pass; `matchesSemantics` would also require every other flag\n  \u002F\u002F to be false, and a real Material button sets `hasEnabledState`.\n  \u002F\u002F (`containsSemantics` is the same matcher on Flutter before 3.40.)\n  expect(\n    tester.getSemantics(find.byType(CheckoutButton)),\n    isSemantics(label: 'Checkout', isButton: true, hasTapAction: true),\n  );\n});\n",[185,1930,1931,1948,1968,1982,1986,2008,2012,2017,2035,2052,2056,2061,2066,2071,2076,2083,2108,2144,2150],{"__ignoreMap":286},[290,1932,1933,1935,1937,1940,1942,1944,1946],{"class":292,"line":293},[290,1934,297],{"class":296},[290,1936,301],{"class":300},[290,1938,1939],{"class":304},"'cart meets a11y guidelines'",[290,1941,309],{"class":308},[290,1943,312],{"class":300},[290,1945,316],{"class":315},[290,1947,319],{"class":300},[290,1949,1950,1952,1955,1957,1959,1961,1964,1966],{"class":292,"line":322},[290,1951,666],{"class":336},[290,1953,1954],{"class":300}," semantics ",[290,1956,672],{"class":347},[290,1958,1043],{"class":300},[290,1960,257],{"class":308},[290,1962,1963],{"class":296},"ensureSemantics",[290,1965,586],{"class":300},[290,1967,354],{"class":308},[290,1969,1970,1972,1975,1977,1980],{"class":292,"line":357},[290,1971,1422],{"class":296},[290,1973,1974],{"class":300},"(semantics",[290,1976,257],{"class":308},[290,1978,1979],{"class":300},"dispose)",[290,1981,354],{"class":308},[290,1983,1984],{"class":292,"line":364},[290,1985,361],{"emptyLinePlaceholder":360},[290,1987,1988,1990,1992,1994,1996,1998,2000,2002,2004,2006],{"class":292,"line":375},[290,1989,325],{"class":315},[290,1991,328],{"class":296},[290,1993,331],{"class":300},[290,1995,309],{"class":308},[290,1997,337],{"class":336},[290,1999,341],{"class":340},[290,2001,344],{"class":300},[290,2003,348],{"class":347},[290,2005,351],{"class":300},[290,2007,354],{"class":308},[290,2009,2010],{"class":292,"line":397},[290,2011,361],{"emptyLinePlaceholder":360},[290,2013,2014],{"class":292,"line":412},[290,2015,2016],{"class":513},"  \u002F\u002F Tap targets (48dp Android \u002F 44pt iOS) and text contrast.\n",[290,2018,2019,2021,2023,2025,2027,2030,2033],{"class":292,"line":420},[290,2020,325],{"class":315},[290,2022,369],{"class":296},[290,2024,331],{"class":300},[290,2026,309],{"class":308},[290,2028,2029],{"class":296}," meetsGuideline",[290,2031,2032],{"class":300},"(androidTapTargetGuideline))",[290,2034,354],{"class":308},[290,2036,2037,2039,2041,2043,2045,2047,2050],{"class":292,"line":607},[290,2038,325],{"class":315},[290,2040,369],{"class":296},[290,2042,331],{"class":300},[290,2044,309],{"class":308},[290,2046,2029],{"class":296},[290,2048,2049],{"class":300},"(textContrastGuideline))",[290,2051,354],{"class":308},[290,2053,2054],{"class":292,"line":612},[290,2055,361],{"emptyLinePlaceholder":360},[290,2057,2058],{"class":292,"line":633},[290,2059,2060],{"class":513},"  \u002F\u002F The label and role the golden cannot see. `isSemantics` checks only the\n",[290,2062,2063],{"class":292,"line":639},[290,2064,2065],{"class":513},"  \u002F\u002F values you pass; `matchesSemantics` would also require every other flag\n",[290,2067,2068],{"class":292,"line":645},[290,2069,2070],{"class":513},"  \u002F\u002F to be false, and a real Material button sets `hasEnabledState`.\n",[290,2072,2073],{"class":292,"line":651},[290,2074,2075],{"class":513},"  \u002F\u002F (`containsSemantics` is the same matcher on Flutter before 3.40.)\n",[290,2077,2078,2081],{"class":292,"line":657},[290,2079,2080],{"class":296},"  expect",[290,2082,372],{"class":300},[290,2084,2085,2088,2090,2093,2095,2097,2099,2101,2104,2106],{"class":292,"line":663},[290,2086,2087],{"class":300},"    tester",[290,2089,257],{"class":308},[290,2091,2092],{"class":296},"getSemantics",[290,2094,1082],{"class":300},[290,2096,257],{"class":308},[290,2098,383],{"class":296},[290,2100,301],{"class":300},[290,2102,2103],{"class":340},"CheckoutButton",[290,2105,729],{"class":300},[290,2107,394],{"class":308},[290,2109,2110,2113,2116,2118,2121,2123,2126,2128,2131,2133,2136,2138,2140,2142],{"class":292,"line":686},[290,2111,2112],{"class":296},"    isSemantics",[290,2114,2115],{"class":300},"(label",[290,2117,348],{"class":347},[290,2119,2120],{"class":304}," 'Checkout'",[290,2122,309],{"class":308},[290,2124,2125],{"class":300}," isButton",[290,2127,348],{"class":347},[290,2129,2130],{"class":1472}," true",[290,2132,309],{"class":308},[290,2134,2135],{"class":300}," hasTapAction",[290,2137,348],{"class":347},[290,2139,2130],{"class":1472},[290,2141,391],{"class":300},[290,2143,394],{"class":308},[290,2145,2146,2148],{"class":292,"line":711},[290,2147,415],{"class":300},[290,2149,354],{"class":308},[290,2151,2152,2154],{"class":292,"line":734},[290,2153,423],{"class":300},[290,2155,354],{"class":308},[175,2157,2158,2161,2162,2166,2167,2170],{},[210,2159,2160],{},"Real-device rendering."," Goldens run on the host engine, so they do not tell you what ",[193,2163,2165],{"slug":2164},"impeller","Impeller"," on iOS does with the same tree, or how the platform's own text shaping differs. If a difference matters, that is ",[185,2168,2169],{},"integration_test"," on a device, with a screenshot captured there — slower, flakier, and the only thing that answers the question.",[175,2172,2173,2176],{},[210,2174,2175],{},"Jank."," Frame budget is measured on a profile build on a real device, not inferred from a PNG.",[199,2178,2180],{"id":2179},"the-rules-that-decide-whether-it-was-worth-it","The rules that decide whether it was worth it",[175,2182,2183],{},"The suite's value is proportional to the probability that a human being looks at the diff. That is the entire return on the investment, and it is a cultural property, not a technical one.",[175,2185,2186],{},"So, concretely:",[2188,2189,2190,2198,2204,2213,2219],"ol",{},[207,2191,2192,2197],{},[210,2193,2194,2196],{},[185,2195,272],{}," only in a commit whose message names the visual change."," \"Update goldens\" as a commit message means the diff was not reviewed. \"Update goldens: checkout button is 48pt and the error state uses the new palette\" means it was.",[207,2199,2200,2203],{},[210,2201,2202],{},"A golden diff in a screen you did not touch is a finding, not noise."," That is the suite doing its job. Batching it into \"chore: update goldens\" is the suite being switched off.",[207,2205,2206,2209,2210,2212],{},[210,2207,2208],{},"If the reviewer cannot see the images, the tooling failed."," Upload the ",[185,2211,437],{}," artifacts from CI, or attach the diff to the pull request. A review that consists of \"trust me, the pixels changed\" is not a review.",[207,2214,2215,2218],{},[210,2216,2217],{},"Delete the goldens you would rubber-stamp."," A suite of forty images that are all looked at beats four hundred that are all approved in one click. Coverage is not the metric; reviewed coverage is.",[207,2220,2221,2224,2225,2227],{},[210,2222,2223],{},"When goldens change on unrelated pull requests, stop and find the cause"," — an unpinned Flutter version, a font that is not loaded, a ",[185,2226,933],{}," that reached a widget. Flakiness in goldens is almost never the mechanism; it is a real source of non-determinism you now know about.",[2229,2230],"checklist",{":groups":2231,":subtitle":2232,":title":2233},"[{\"title\": \"Make the rendering honest\", \"items\": [\"App fonts loaded in test\u002Fflutter_test_config.dart, with the family name the theme actually asks for\", \"The icon font loaded too — otherwise every Icon is a box\", \"Network images replaced with in-memory fixtures\", \"Clock, locale and random seeds injected rather than read from the environment\", \"Loading states rendered with pump(duration) instead of pumpAndSettle\"]}, {\"title\": \"Make it reproducible\", \"items\": [\"Flutter version pinned in the repo and read by CI\", \"Goldens generated on Linux, the same platform CI runs on\", \"A CI step that fails on any modified or untracked golden, so nothing regenerates silently\", \"failures\u002F ignored in git, and uploaded as a build artifact for review\", \"The test surface set explicitly — size, device pixel ratio, text scale, locale\"]}, {\"title\": \"Make it reviewable\", \"items\": [\"Every golden wrapped in a RepaintBoundary so it captures the intended widget\", \"The five axes encoded in the file name: subject, theme, width, text scale, locale\", \"Under ~90 images, or a stated reason for more\", \"A rule that golden updates are their own commit with a stated visual reason\", \"Someone has opened at least one PNG of each kind and confirmed it contains text\"]}]","From an empty repository to a suite you can trust, in a day or two.","The golden suite checklist",[199,2235,2237],{"id":2236},"what-it-costs-six-months-in","What it costs, six months in",[175,2239,2240,2241,2244],{},"Setting the suite up is a day, maybe two, most of it fonts and CI plumbing. Writing each golden is minutes. The recurring cost is the one nobody estimates: ",[210,2242,2243],{},"every Flutter upgrade regenerates every image."," With the matrix above that is a commit of 60–90 PNGs, two to four times a year, each one needing its own justification. If you cannot afford that review, you cannot afford the suite — because an unreviewed regeneration is indistinguishable from no test at all, except that it takes longer.",[175,2246,2247,2248,2251,2252,257],{},"In exchange you get the regressions that no other test type catches: the empty state that lost its padding in dark mode, the Russian translation that overflowed a 320pt button, the design system drifting because someone changed one ",[185,2249,2250],{},"TextStyle"," in the theme. And the case that made us care in the first place — an agent or a teammate changing a shared component and being genuinely unable to see the four screens it broke. A golden is the closest thing Flutter has to handing a code change a pair of eyes, which is exactly the argument we made in ",[2253,2254,2256],"a",{"href":2255},"\u002Fblog\u002Fai-agents-struggle-with-flutter","why AI agents struggle with Flutter",[199,2258,2260],{"id":2259},"the-short-version","The short version",[175,2262,2263],{},"A golden test is a pixel comparison against a PNG, and everything that makes it useful is about the conditions of that comparison. Load your fonts, load your icon font, set the surface, pin the toolchain, regenerate on the platform CI uses, and never let a machine approve the diff.",[175,2265,2266],{},"Then keep the suite small enough that a person still looks at it. The failure mode of golden testing is not a flaky test — it is 214 images that all pass because they compare boxes to boxes, and a green checkmark that has been telling the same comfortable story for eight months.",[199,2268,2270],{"id":2269},"sources","Sources",[204,2272,2273,2288,2298,2312,2332,2342,2360,2377,2388,2398,2408,2418],{},[207,2274,2275,2278,2279,2281,2282],{},[185,2276,2277],{},"matchesGoldenFile"," — what the matcher captures, and the ",[185,2280,433],{}," hook: ",[2253,2283,2287],{"href":2284,"rel":2285},"https:\u002F\u002Fapi.flutter.dev\u002Fflutter\u002Fflutter_test\u002FmatchesGoldenFile.html",[2286],"nofollow","api.flutter.dev",[207,2289,2290,2293,2294],{},[185,2291,2292],{},"FontLoader"," — registering a font face by family name inside a test: ",[2253,2295,2287],{"href":2296,"rel":2297},"https:\u002F\u002Fapi.flutter.dev\u002Fflutter\u002Fservices\u002FFontLoader-class.html",[2286],[207,2299,2300,2302,2303,2306,2307],{},[185,2301,228],{}," — declared in ",[185,2304,2305],{},"painting\u002Fdebug.dart",", set by the test binding, and flipped back on by Flutter's own shadow tests: ",[2253,2308,2311],{"href":2309,"rel":2310},"https:\u002F\u002Fgithub.com\u002Fflutter\u002Fflutter\u002Fblob\u002Fmaster\u002Fpackages\u002Fflutter\u002Flib\u002Fsrc\u002Fpainting\u002Fdebug.dart",[2286],"github.com\u002Fflutter\u002Fflutter",[207,2313,2314,2317,2318,1916,2321,1762,2324,2327,2328],{},[185,2315,2316],{},"TestFlutterView"," — ",[185,2319,2320],{},"physicalSize",[185,2322,2323],{},"devicePixelRatio",[185,2325,2326],{},"reset()",": ",[2253,2329,2287],{"href":2330,"rel":2331},"https:\u002F\u002Fapi.flutter.dev\u002Fflutter\u002Fflutter_test\u002FTestFlutterView-class.html",[2286],[207,2333,2334,2337,2338],{},[185,2335,2336],{},"WidgetTester.pumpAndSettle"," — \"repeatedly calls pump with the given duration until there are no longer any frames scheduled\": ",[2253,2339,2287],{"href":2340,"rel":2341},"https:\u002F\u002Fapi.flutter.dev\u002Fflutter\u002Fflutter_test\u002FWidgetTester\u002FpumpAndSettle.html",[2286],[207,2343,2344,2317,2347,1916,2350,1916,2353,2327,2356],{},[185,2345,2346],{},"AccessibilityGuideline",[185,2348,2349],{},"androidTapTargetGuideline",[185,2351,2352],{},"iOSTapTargetGuideline",[185,2354,2355],{},"textContrastGuideline",[2253,2357,2287],{"href":2358,"rel":2359},"https:\u002F\u002Fapi.flutter.dev\u002Fflutter\u002Fflutter_test\u002FAccessibilityGuideline-class.html",[2286],[207,2361,2362,2363,2366,2367,1916,2372],{},"Framework goldens triaged on Skia Gold, and the ",[185,2364,2365],{},"flutter\u002Fgoldens"," repo that holds the ones that cannot use it: ",[2253,2368,2371],{"href":2369,"rel":2370},"https:\u002F\u002Fskia.org\u002Fdocs\u002Fdev\u002Ftesting\u002Fskiagold\u002F",[2286],"skia.org",[2253,2373,2376],{"href":2374,"rel":2375},"https:\u002F\u002Fgithub.com\u002Fflutter\u002Fgoldens",[2286],"github.com\u002Fflutter\u002Fgoldens",[207,2378,2379,2382,2383],{},[185,2380,2381],{},"alchemist"," — per-platform golden folders, grouped multi-widget goldens, CI-aware configuration: ",[2253,2384,2387],{"href":2385,"rel":2386},"https:\u002F\u002Fpub.dev\u002Fpackages\u002Falchemist",[2286],"pub.dev",[207,2389,2390,2393,2394],{},[185,2391,2392],{},"golden_screenshot"," — screenshot tests with device previews and platform-specific goldens: ",[2253,2395,2387],{"href":2396,"rel":2397},"https:\u002F\u002Fpub.dev\u002Fpackages\u002Fgolden_screenshot",[2286],[207,2399,2400,2403,2404],{},[185,2401,2402],{},"golden_toolkit"," — the package most golden-test tutorials still recommend; discontinued, last released February 2023: ",[2253,2405,2387],{"href":2406,"rel":2407},"https:\u002F\u002Fpub.dev\u002Fpackages\u002Fgolden_toolkit",[2286],[207,2409,2410,2413,2414],{},[185,2411,2412],{},"spot"," — golden-style widget tests that compare a serialized widget tree instead of pixels, when pixel instability is the bigger problem: ",[2253,2415,2387],{"href":2416,"rel":2417},"https:\u002F\u002Fpub.dev\u002Fpackages\u002Fspot",[2286],[207,2419,2420,2422,2423,2426,2427],{},[185,2421,2169],{}," — screenshots taken on a real device (call ",[185,2424,2425],{},"convertFlutterSurfaceToImage()"," first on Android), the only answer to \"what does it look like on a phone\": ",[2253,2428,2431],{"href":2429,"rel":2430},"https:\u002F\u002Fgithub.com\u002Fflutter\u002Fflutter\u002Fblob\u002Fmaster\u002Fpackages\u002Fintegration_test\u002Flib\u002Fintegration_test.dart",[2286],"integration_test.dart",[199,2433,2435],{"id":2434},"frequently-asked-questions","Frequently asked questions",[2437,2438],"questions",{":items":2439},"[{\"title\": \"What is a golden test in Flutter?\", \"text\": \"A golden test renders a widget and compares the result pixel by pixel against a PNG stored in the repository. The PNG is created by running flutter test with the update-goldens flag, and every subsequent run fails if the rendering differs. In Flutter it is the cheapest way to catch visual regressions, because it runs in the same test process as ordinary widget tests and takes seconds.\"}, {\"title\": \"Why does text render as black boxes in my golden files?\", \"text\": \"Because the test environment does not load the fonts declared in your pubspec. Text with no available family falls back to a test font whose every glyph is a filled rectangle, which keeps layout deterministic and removes the need for font files. Register the real fonts with FontLoader in test\u002Fflutter_test_config.dart, including the icon font, and the goldens render actual text.\"}, {\"title\": \"Why do golden tests pass locally and fail in CI?\", \"text\": \"The pixels depend on the host operating system and the Flutter version, not just on your widget tree. Text anti-aliasing and hinting differ between macOS and Linux, so a suite generated on a developer laptop does not match a Linux runner. Generate goldens on the same platform and the same pinned Flutter version that CI uses, and treat a Flutter upgrade as a commit that regenerates every image on purpose.\"}, {\"title\": \"Should golden files be committed to git?\", \"text\": \"Yes. The whole value of a golden is that a change to it is visible in review, and that requires the image to live in the repository next to the code, on the same branch, changing in the same commit as whatever changed the rendering. The failure artifacts the comparator writes are the exception: the reference, the new render and the two diff images belong in gitignore and in continuous integration artifacts.\"}, {\"title\": \"Is pumpAndSettle the right way to render a loading state for a golden?\", \"text\": \"No. pumpAndSettle pumps until no frame is scheduled, and an indeterminate CircularProgressIndicator schedules frames forever, so the call times out. Use tester.pump with an explicit duration to capture one fixed phase of the spinner. You are testing that the layout around the spinner holds; the spinner itself is not what changes.\"}, {\"title\": \"Do golden tests replace widget tests?\", \"text\": \"No, they cover different things. A golden asserts on pixels, so it cannot tell you that a button calls the wrong callback, that validation stopped running, or that a dialog fails to dismiss, because all of those render identically. Widget tests cover behavior, and accessibility assertions such as meetsGuideline cover semantics and tap targets, which a screenshot cannot see at all.\"}, {\"title\": \"How many golden tests should a Flutter app have?\", \"text\": \"Enough that a person will still look at the diffs, which in practice means something like 60 to 90 images: the design-system components across their states and both themes, the handful of screens that matter at three widths, and the empty, error and loading states. Multiplying every screen by every theme, width, text scale and locale produces hundreds of images that get approved in one click, and a suite that is approved in one click is not testing anything.\"}, {\"title\": \"Which golden testing package should I use?\", \"text\": \"Start with what flutter_test ships: matchesGoldenFile, a flutter_test_config.dart for font loading, and a small helper that sets the view size. Packages such as alchemist, golden_screenshot and spot add per-platform golden folders, device previews or widget-tree comparison, and are worth it when you hit the problem each one solves. golden_toolkit, which most tutorials still recommend, was discontinued in 2023.\"}]",[175,2441,2442,2443,2447,2448,2452],{},"If you are setting up the testing layer from scratch, the surrounding decisions are in our write-up of the ",[2253,2444,2446],{"href":2445},"\u002Fblog\u002Fflutter-app-development-process","Flutter app development process",", and the state-model problem that makes half of a UI untestable is in ",[2253,2449,2451],{"href":2450},"\u002Fblog\u002Fimpossible-states-dart-sealed-classes","five states that cannot exist",". The theme in all three is the same: a check that passes is not the same thing as a check that looks.",[2454,2455,2456],"post-credits",{},[175,2457,2458,2459,2463,2464,2468],{},"Maxim is a Senior Software Engineer at ",[2253,2460,2462],{"href":2461},"\u002F","Nerdy Production",", a Flutter-first agency that builds and maintains apps across fintech, healthcare, and retail. We also do ",[2253,2465,2467],{"href":2466},"\u002Fservices\u002Fflutter-app-development","Flutter app development"," for teams who would rather find out what their test suite is comparing before the next release.",[2470,2471,2472],"style",{},"html pre.shiki code .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sVHd0, html code.shiki .sVHd0{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sZMiF, html code.shiki .sZMiF{--shiki-light:#E2931D;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}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 .sutJx, html code.shiki .sutJx{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .sw1J6, html code.shiki .sw1J6{--shiki-light:#F76D47;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .srdBf, html code.shiki .srdBf{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s39Yj, html code.shiki .s39Yj{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":286,"searchDepth":322,"depth":322,"links":2474},[2475,2476,2477,2485,2486,2487,2488,2489,2490,2491,2492],{"id":201,"depth":322,"text":202},{"id":276,"depth":322,"text":277},{"id":480,"depth":322,"text":481,"children":2478},[2479,2480,2481,2482,2483],{"id":485,"depth":357,"text":486},{"id":850,"depth":357,"text":851},{"id":882,"depth":357,"text":883},{"id":927,"depth":357,"text":928},{"id":979,"depth":357,"text":2484},"5. pumpAndSettle never returns on a loading state",{"id":1120,"depth":322,"text":1121},{"id":1790,"depth":322,"text":1791},{"id":1902,"depth":322,"text":1903},{"id":2179,"depth":322,"text":2180},{"id":2236,"depth":322,"text":2237},{"id":2259,"depth":322,"text":2260},{"id":2269,"depth":322,"text":2270},{"id":2434,"depth":322,"text":2435},"md",{"type":2495,"src":2496},"image","\u002Fblog\u002Fflutter-golden-tests.webp",{},"Golden tests are Flutter's cheapest UI regression net — and the easiest to fake. Fonts, a pinned CI, the matrix worth capturing, and review discipline.","Flutter Golden Tests That Aren't a Rubber Stamp","Every glyph a black box, shadows flattened into hard blocks, pixels tied to the host OS. What a Flutter golden test actually compares — and the setup that makes it honest.","\u002Fblog\u002Fflutter-golden-tests",{"title":169,"description":177},"flutter-golden-tests","blog\u002Fflutter-golden-tests","Golden tests are the highest-leverage UI test in Flutter and the easiest to turn into a rubber stamp. The first one you write renders every glyph as a black box, because the test environment has no fonts; shadows are flattened into hard blocks; and the pixels depend on the host OS and the Flutter version, so a suite generated on a Mac fails on Linux CI. Here is the setup that makes goldens honest, the matrix actually worth paying for, what they cannot test, and the review rules that decide whether the whole suite earns its maintenance.","2026-11-10T12:00:00Z",null,"i-WdUfUn30SERUj79xGH0M97rqjGWnNDRg5B5n15Yf8",[2510,2566,2613,2662,2752,2804],{"id":2511,"bio":2512,"expertise":2517,"extension":1162,"links":2533,"meta":2537,"metaDescription":2538,"name":2543,"ogDescription":2546,"photo":2551,"role":2552,"seniority":2557,"skills":2558,"slug":2559,"specialization":2560,"stem":2559,"__hash__":2565},"team_members\u002Fdima.yaml",{"en":2513,"ru":2514,"es":2515,"nl":2516},"Dima has been building with [Flutter](\u002Ftechnologies\u002Fflutter) since 2021, and specializes in :term[state management]{slug=\"state-management\"} and app architecture.\n\nHe has hands-on experience with real-time communication protocols — :term{slug=\"webrtc\"} for audio and video, and :term{slug=\"xmpp\"} for messaging — which makes him comfortable with the network-heavy, stateful features many teams struggle to get right.\n","Дима работает с [Flutter](\u002Ftechnologies\u002Fflutter) с 2021 года и специализируется на :term[state management]{slug=\"state-management\"} и архитектуре приложений.\n\nУ него есть практический опыт с протоколами реального времени — :term{slug=\"webrtc\"} для аудио и видео и :term{slug=\"xmpp\"} для обмена сообщениями, — поэтому ему близки сетевые stateful-фичи, которые многим командам даются с трудом.\n","Dima trabaja con [Flutter](\u002Ftechnologies\u002Fflutter) desde 2021 y se especializa en :term[gestión de estado]{slug=\"state-management\"} y arquitectura de aplicaciones.\n\nTiene experiencia práctica con protocolos de comunicación en tiempo real —:term{slug=\"webrtc\"} para audio y vídeo, y :term{slug=\"xmpp\"} para mensajería—, lo que le hace sentirse cómodo con esas funcionalidades con mucho estado y mucha red que a muchos equipos se les atragantan.\n","Dima werkt sinds 2021 met [Flutter](\u002Ftechnologies\u002Fflutter) en is gespecialiseerd in :term[state management]{slug=\"state-management\"} en applicatiearchitectuur.\n\nHij heeft praktijkervaring met protocollen voor realtime communicatie — :term{slug=\"webrtc\"} voor audio en video, en :term{slug=\"xmpp\"} voor berichten — waardoor hij zich thuis voelt bij de netwerkzware functies met veel staat waar veel teams moeite mee hebben.\n",[2518,2521,2526,2528],{"en":2519,"ru":2519,"es":2520,"nl":2519},"State management","Gestión de estado",{"en":2522,"ru":2523,"es":2524,"nl":2525},"Application architecture","Архитектура приложений","Arquitectura de aplicaciones","Applicatiearchitectuur",{"en":2527,"ru":2527,"es":2527,"nl":2527},"WebRTC",{"en":2529,"ru":2530,"es":2531,"nl":2532},"XMPP messaging","Обмен сообщениями по XMPP","Mensajería XMPP","Berichten via XMPP",[2534],{"type":2535,"address":2536},"email","konopatov@nerdy.pro",{},{"en":2539,"ru":2540,"es":2541,"nl":2542},"Lead Flutter developer at Nerdy Production, building real-time apps since 2021 — state management, app architecture, WebRTC and XMPP.","Ведущий Flutter-разработчик Nerdy Production: приложения реального времени с 2021 года — state management, архитектура, WebRTC и XMPP.","Lead de desarrollo Flutter en Nerdy Production, construyendo apps en tiempo real desde 2021: gestión de estado, arquitectura de aplicaciones, WebRTC y XMPP.","Lead Flutter-developer bij Nerdy Production, bouwt sinds 2021 realtime-apps — state management, applicatiearchitectuur, WebRTC en XMPP.",{"en":2544,"ru":2545,"es":2544,"nl":2544},"Dima","Дима",{"en":2547,"ru":2548,"es":2549,"nl":2550},"Lead Flutter developer at Nerdy Production — real-time apps since 2021, and the state management that keeps them from falling over.","Ведущий Flutter-разработчик Nerdy Production: приложения реального времени с 2021 года и state management, на котором они держатся.","Lead de desarrollo Flutter en Nerdy Production: apps en tiempo real desde 2021 y la gestión de estado que evita que se caigan.","Lead Flutter-developer bij Nerdy Production — realtime-apps sinds 2021, en het state management dat ze overeind houdt.","\u002Fteam\u002Fdima.webp",{"en":2553,"ru":2554,"es":2555,"nl":2556},"Lead Flutter Developer","Ведущий Flutter-разработчик","Lead de desarrollo Flutter","Lead Flutter-developer","lead",[41,18,155,160,90,111,150,95],"dima",{"en":2561,"ru":2562,"es":2563,"nl":2564},"Real-time apps, state management, and architecture","Приложения реального времени, state management и архитектура","Apps en tiempo real, gestión de estado y arquitectura","Realtime-apps, state management en architectuur","1TuxUt3zjdVkQUvMf9dBITG872C0exrdZyyoKw1beiE",{"id":2567,"bio":2568,"expertise":2573,"extension":1162,"links":2507,"meta":2584,"metaDescription":2585,"name":2590,"ogDescription":2593,"photo":2598,"role":2599,"seniority":2604,"skills":2605,"slug":2606,"specialization":2607,"stem":2606,"__hash__":2612},"team_members\u002Fmasha.yaml",{"en":2569,"ru":2570,"es":2571,"nl":2572},"Masha builds [Flutter](\u002Ftechnologies\u002Fflutter) apps where design and copy are treated as one job.\n\nShe specializes in UI\u002FUX — turning product requirements into clean, usable interfaces — and in the writing inside the app, from onboarding flows to the microcopy that makes a screen make sense. The result is apps that feel considered, not just functional.\n","Маша делает приложения на [Flutter](\u002Ftechnologies\u002Fflutter), где дизайн и текст — одна задача.\n\nОна специализируется на UI\u002FUX, превращая продуктовые требования в чистые и удобные интерфейсы, и на текстах внутри приложения — от онбординга до микрокопирайта, который делает экран понятным. В итоге приложения получаются продуманными, а не просто рабочими.\n","Masha crea apps en [Flutter](\u002Ftechnologies\u002Fflutter) donde el diseño y el texto se tratan como un mismo trabajo.\n\nSe especializa en UI\u002FUX —convertir requisitos de producto en interfaces limpias y usables— y en la escritura dentro de la app, desde los flujos de onboarding hasta el microcopy que hace que una pantalla se entienda. El resultado son apps que se sienten pensadas, no solo funcionales.\n","Masha bouwt [Flutter](\u002Ftechnologies\u002Fflutter)-apps waarin ontwerp en tekst als één taak worden behandeld.\n\nZe is gespecialiseerd in UI\u002FUX — productwensen omzetten in heldere, bruikbare interfaces — en in de teksten binnen de app, van onboarding tot de microteksten die een scherm begrijpelijk maken. Het resultaat zijn apps die doordacht aanvoelen en niet alleen werken.\n",[2574,2579],{"en":2575,"ru":2576,"es":2577,"nl":2578},"UI\u002FUX design","UI\u002FUX-дизайн","Diseño UI\u002FUX","UI\u002FUX-ontwerp",{"en":2580,"ru":2581,"es":2582,"nl":2583},"Product copywriting","Продуктовый копирайтинг","Redacción de producto","Productteksten",{},{"en":2586,"ru":2587,"es":2588,"nl":2589},"Flutter developer at Nerdy Production working where design meets copy — UI\u002FUX, onboarding flows, and the microcopy that makes a screen make sense.","Flutter-разработчик Nerdy Production на стыке дизайна и текста: UI\u002FUX, онбординг и микрокопирайт, который делает экран понятным.","Desarrolladora Flutter en Nerdy Production, donde el diseño se encuentra con el texto: UI\u002FUX, onboarding y el microcopy que hace que una pantalla se entienda.","Flutter-developer bij Nerdy Production op het snijvlak van ontwerp en tekst — UI\u002FUX, onboarding, en de microteksten die een scherm begrijpelijk maken.",{"en":2591,"ru":2592,"es":2591,"nl":2591},"Masha","Маша",{"en":2594,"ru":2595,"es":2596,"nl":2597},"Flutter developer at Nerdy Production working where design meets copy — onboarding flows and the microcopy that makes a screen make sense.","Flutter-разработчик Nerdy Production на стыке дизайна и текста: онбординг и микрокопирайт, который делает экран понятным.","Desarrolladora Flutter en Nerdy Production, donde el diseño se encuentra con el texto: onboarding y el microcopy que hace entender una pantalla.","Flutter-developer bij Nerdy Production op het snijvlak van ontwerp en tekst — onboarding en de microteksten die een scherm begrijpelijk maken.","\u002Fteam\u002Fmasha.webp",{"en":2600,"ru":2601,"es":2602,"nl":2603},"Flutter Developer","Flutter-разработчик","Desarrolladora Flutter","Flutter-developer","middle",[41,18],"masha",{"en":2608,"ru":2609,"es":2610,"nl":2611},"UI\u002FUX and copywriting for Flutter apps","UI\u002FUX и копирайтинг для Flutter-приложений","UI\u002FUX y redacción para apps Flutter","UI\u002FUX en teksten voor Flutter-apps","z3c64iWR39RIrn8wE0elSy00IfCITYgNkbgp_G_sslk",{"id":2614,"bio":2615,"expertise":2620,"extension":1162,"links":2631,"meta":2634,"metaDescription":2635,"name":2640,"ogDescription":2643,"photo":2648,"role":2649,"seniority":2654,"skills":2655,"slug":170,"specialization":2656,"stem":170,"__hash__":2661},"team_members\u002Fmaxim.yaml",{"en":2616,"ru":2617,"es":2618,"nl":2619},"Maxim is a polyglot engineer who moves comfortably across [Go](\u002Ftechnologies\u002Fgo), [Flutter](\u002Ftechnologies\u002Fflutter), [Python](\u002Ftechnologies\u002Fpython), and [TypeScript](\u002Ftechnologies\u002Ftypescript), which lets him own a feature from the backend to the screen.\n\nHe came up at Ozon, one of Russia's largest marketplaces, where scale makes reliability non-negotiable — and it shows in his work: testing is a first-class part of how he builds, not an afterthought bolted on at the end.\n","Максим — полиглот-инженер, свободно работающий с [Go](\u002Ftechnologies\u002Fgo), [Flutter](\u002Ftechnologies\u002Fflutter), [Python](\u002Ftechnologies\u002Fpython) и [TypeScript](\u002Ftechnologies\u002Ftypescript), что позволяет ему вести фичу от бэкенда до экрана.\n\nОн вырос в Ozon, одном из крупнейших маркетплейсов России, где масштаб делает надёжность обязательной, — и это видно в его работе: тестирование для него первоклассная часть разработки, а не то, что прикручивают в конце.\n","Maxim es un ingeniero políglota que se mueve con soltura entre [Go](\u002Ftechnologies\u002Fgo), [Flutter](\u002Ftechnologies\u002Fflutter), [Python](\u002Ftechnologies\u002Fpython) y [TypeScript](\u002Ftechnologies\u002Ftypescript), lo que le permite hacerse cargo de una funcionalidad desde el backend hasta la pantalla.\n\nSe formó en Ozon, uno de los mayores marketplaces de Rusia, donde la escala hace que la fiabilidad no sea negociable, y se le nota: el testing es para él una parte de primera clase de cómo construye, no un añadido al final.\n","Maxim is een polyglotte engineer die zich moeiteloos beweegt tussen [Go](\u002Ftechnologies\u002Fgo), [Flutter](\u002Ftechnologies\u002Fflutter), [Python](\u002Ftechnologies\u002Fpython) en [TypeScript](\u002Ftechnologies\u002Ftypescript), waardoor hij een functie van de backend tot het scherm kan dragen.\n\nHij is opgegroeid bij Ozon, een van de grootste marktplaatsen van Rusland, waar schaal betrouwbaarheid onderhandelbaar maakt noch toestaat — en dat zie je terug in zijn werk: testen is bij hem een volwaardig onderdeel van hoe hij bouwt, geen bijzaak die er aan het eind bij komt.\n",[2621,2626],{"en":2622,"ru":2623,"es":2624,"nl":2625},"Automated testing","Автоматизированное тестирование","Testing automatizado","Geautomatiseerd testen",{"en":2627,"ru":2628,"es":2629,"nl":2630},"High-load backend systems","Высоконагруженные бэкенд-системы","Sistemas backend de alta carga","Backendsystemen met hoge belasting",[2632],{"type":2535,"address":2633},"maxim@nerdy.pro",{},{"en":2636,"ru":2637,"es":2638,"nl":2639},"Senior engineer at Nerdy Production across Go, Flutter, Python and TypeScript — from backend to screen, with automated testing built in.","Старший инженер Nerdy Production: Go, Flutter, Python и TypeScript — от бэкенда до экрана, с автотестами как частью разработки.","Ingeniero senior en Nerdy Production con Go, Flutter, Python y TypeScript: del backend a la pantalla, con testing automatizado incorporado.","Senior engineer bij Nerdy Production met Go, Flutter, Python en TypeScript — van backend tot scherm, met geautomatiseerd testen ingebouwd.",{"en":2641,"ru":2642,"es":2641,"nl":2641},"Maxim","Максим",{"en":2644,"ru":2645,"es":2646,"nl":2647},"Senior engineer at Nerdy Production working from backend to screen — Go, Flutter, Python and TypeScript, with the tests written as he goes.","Старший инженер Nerdy Production, работающий от бэкенда до экрана: Go, Flutter, Python и TypeScript — и тесты, которые пишутся по ходу дела.","Ingeniero senior en Nerdy Production, del backend a la pantalla: Go, Flutter, Python y TypeScript, con los tests escritos sobre la marcha.","Senior engineer bij Nerdy Production, van backend tot scherm — Go, Flutter, Python en TypeScript, met de tests die hij onderweg schrijft.","\u002Fteam\u002Fmax.webp",{"en":2650,"ru":2651,"es":2652,"nl":2653},"Senior Software Engineer","Старший инженер-программист","Ingeniero de software senior","Senior software engineer","senior",[41,18,51,106,155,13,150],{"en":2657,"ru":2658,"es":2659,"nl":2660},"Backend and Flutter engineering with a testing focus","Бэкенд и Flutter с фокусом на тестирование","Ingeniería backend y Flutter con foco en testing","Backend- en Flutter-engineering met focus op testen","a-8Kx1i-PQXIGU92_Rimqr3bPEur6ZwMfPKQWr2uk4I",{"id":2663,"bio":2664,"expertise":2669,"extension":1162,"links":2711,"meta":2723,"metaDescription":2724,"name":2729,"ogDescription":2732,"photo":2737,"role":2738,"seniority":2743,"skills":2744,"slug":2745,"specialization":2746,"stem":2745,"__hash__":2751},"team_members\u002Fnixan.yaml",{"en":2665,"ru":2666,"es":2667,"nl":2668},"Ilya founded Nerdy Production and leads its engineering. He has been building software since 2010 and shipping production Flutter since 2018.\n\nBefore that he was CTO of QIWI, one of Russia's largest payment platforms, where he ran roughly 12 engineering teams spanning web products down to card processing, :term{slug=\"pci-dss\"} scope, and contactless payments — including building contactless card payments on Android via :term[Host Card Emulation]{slug=\"host-card-emulation\"} over ISO\u002FIEC 14443, with EMV Contactless (Visa PayWave) on top.\n\nHe was also a principal developer at Yandex, where he worked on Yandex.Auto — taking native Android deep into the vehicle, with heavy CAN-bus integration through a custom CAN shield — and a principal at Evotor, whose point-of-sale devices run on a forked :term{slug=\"aosp\"}, giving him a low-level view of Android most app developers never touch.\n\nToday he leads delivery on the agency's flagship apps — from the chart-heavy fintech UI of [ExtraETF](\u002Fportfolio\u002Fextraetf) to the fully custom design system of [Arcana](\u002Fportfolio\u002Farcana). He writes most of the essays on this blog and maintains the agency's open-source work, including the [dxpdf](\u002Fopen-source\u002Fdxpdf) DOCX-to-PDF engine.\n\nHe works across [Flutter](\u002Ftechnologies\u002Fflutter), native iOS and Android, [Go](\u002Ftechnologies\u002Fgo), [Rust](\u002Ftechnologies\u002Frust), [TypeScript](\u002Ftechnologies\u002Ftypescript), [Kotlin](\u002Ftechnologies\u002Fkotlin), [Kubernetes](\u002Ftechnologies\u002Fkubernetes), and [Docker](\u002Ftechnologies\u002Fdocker), with a focus on app architecture, cross-platform delivery, and building teams that ship.\n","Илья основал Nerdy Production и руководит инженерной командой. Он занимается разработкой с 2010 года и выпускает продакшн-приложения на Flutter с 2018-го.\n\nДо этого он был CTO QIWI — одной из крупнейших платёжных платформ России, — где руководил примерно 12 инженерными командами: от веб-продуктов до карточного процессинга, зоны :term{slug=\"pci-dss\"} и бесконтактных платежей, включая бесконтактную оплату картой на Android через :term[Host Card Emulation]{slug=\"host-card-emulation\"} поверх ISO\u002FIEC 14443, с платёжным протоколом EMV Contactless (Visa PayWave).\n\nОн также был принципал-разработчиком в Яндексе, где работал над Яндекс.Авто, уводя нативный Android глубоко в автомобиль, с серьёзной интеграцией по шине CAN через собственный CAN-шилд, и принципалом в Эвоторе, чьи кассовые устройства работают на форке :term{slug=\"aosp\"}, что дало ему низкоуровневый взгляд на Android, недоступный большинству прикладных разработчиков.\n\nСейчас он ведёт поставку флагманских приложений агентства — от насыщенного графиками финтех-интерфейса [ExtraETF](\u002Fportfolio\u002Fextraetf) до полностью кастомной дизайн-системы [Arcana](\u002Fportfolio\u002Farcana). Он пишет большую часть материалов этого блога и поддерживает open-source агентства, включая движок [dxpdf](\u002Fopen-source\u002Fdxpdf) для конвертации DOCX в PDF.\n\nРаботает с [Flutter](\u002Ftechnologies\u002Fflutter), нативными iOS и Android, [Go](\u002Ftechnologies\u002Fgo), [Rust](\u002Ftechnologies\u002Frust), [TypeScript](\u002Ftechnologies\u002Ftypescript), [Kotlin](\u002Ftechnologies\u002Fkotlin), [Kubernetes](\u002Ftechnologies\u002Fkubernetes) и [Docker](\u002Ftechnologies\u002Fdocker); его фокус — архитектура приложений, кросс-платформенная поставка и построение команд, которые доводят продукт до релиза.\n","Ilya fundó Nerdy Production y dirige su ingeniería. Lleva construyendo software desde 2010 y entregando Flutter en producción desde 2018.\n\nAntes fue CTO de QIWI, una de las mayores plataformas de pago de Rusia, donde dirigió alrededor de 12 equipos de ingeniería que abarcaban desde productos web hasta procesamiento de tarjetas, alcance :term{slug=\"pci-dss\"} y pagos contactless, incluida la construcción de pagos contactless con tarjeta en Android mediante :term[Host Card Emulation]{slug=\"host-card-emulation\"} sobre ISO\u002FIEC 14443, con EMV Contactless (Visa PayWave) por encima.\n\nTambién fue principal developer en Yandex, donde trabajó en Yandex.Auto —llevando Android nativo hasta el interior del vehículo, con una integración intensiva por bus CAN a través de un CAN shield propio— y principal en Evotor, cuyos terminales de punto de venta funcionan sobre un fork de :term{slug=\"aosp\"}, lo que le dio una visión de bajo nivel de Android que la mayoría de desarrolladores de apps nunca llega a tocar.\n\nHoy lidera la entrega de las apps insignia de la agencia, desde la interfaz fintech cargada de gráficos de [ExtraETF](\u002Fportfolio\u002Fextraetf) hasta el sistema de diseño totalmente a medida de [Arcana](\u002Fportfolio\u002Farcana). Escribe la mayoría de los artículos de este blog y mantiene el trabajo de código abierto de la agencia, incluido el motor de conversión de DOCX a PDF [dxpdf](\u002Fopen-source\u002Fdxpdf).\n\nTrabaja con [Flutter](\u002Ftechnologies\u002Fflutter), iOS y Android nativos, [Go](\u002Ftechnologies\u002Fgo), [Rust](\u002Ftechnologies\u002Frust), [TypeScript](\u002Ftechnologies\u002Ftypescript), [Kotlin](\u002Ftechnologies\u002Fkotlin), [Kubernetes](\u002Ftechnologies\u002Fkubernetes) y [Docker](\u002Ftechnologies\u002Fdocker), con foco en arquitectura de aplicaciones, entrega multiplataforma y la construcción de equipos que entregan.\n","Ilya richtte Nerdy Production op en leidt de engineering. Hij bouwt software sinds 2010 en levert sinds 2018 Flutter in productie.\n\nDaarvoor was hij CTO van QIWI, een van de grootste betaalplatforms van Rusland, waar hij zo'n 12 engineeringteams aanstuurde die reikten van webproducten tot kaartverwerking, :term{slug=\"pci-dss\"}-scope en contactloos betalen — waaronder het bouwen van contactloze kaartbetalingen op Android via :term[Host Card Emulation]{slug=\"host-card-emulation\"} over ISO\u002FIEC 14443, met EMV Contactless (Visa PayWave) daarbovenop.\n\nHij was ook principal developer bij Yandex, waar hij aan Yandex.Auto werkte — native Android diep de auto in brengen, met zware integratie over de CAN-bus via een eigen CAN-shield — en principal bij Evotor, waarvan de kassa-apparaten op een fork van :term{slug=\"aosp\"} draaien, wat hem een blik op Android op laag niveau gaf die de meeste app-ontwikkelaars nooit krijgen.\n\nVandaag leidt hij de oplevering van de vlaggenschipapps van het bureau — van de grafiekzware fintech-UI van [ExtraETF](\u002Fportfolio\u002Fextraetf) tot het volledig eigen designsysteem van [Arcana](\u002Fportfolio\u002Farcana). Hij schrijft de meeste artikelen op deze blog en onderhoudt het opensourcewerk van het bureau, waaronder de DOCX-naar-PDF-motor [dxpdf](\u002Fopen-source\u002Fdxpdf).\n\nHij werkt met [Flutter](\u002Ftechnologies\u002Fflutter), native iOS en Android, [Go](\u002Ftechnologies\u002Fgo), [Rust](\u002Ftechnologies\u002Frust), [TypeScript](\u002Ftechnologies\u002Ftypescript), [Kotlin](\u002Ftechnologies\u002Fkotlin), [Kubernetes](\u002Ftechnologies\u002Fkubernetes) en [Docker](\u002Ftechnologies\u002Fdocker), met de nadruk op applicatiearchitectuur, cross-platform oplevering en het bouwen van teams die opleveren.\n",[2670,2675,2680,2685,2690,2695,2700,2705,2707,2709],{"en":2671,"ru":2672,"es":2673,"nl":2674},"iOS development","Разработка под iOS","Desarrollo iOS","iOS-ontwikkeling",{"en":2676,"ru":2677,"es":2678,"nl":2679},"Software architecture","Архитектура ПО","Arquitectura de software","Softwarearchitectuur",{"en":2681,"ru":2682,"es":2683,"nl":2684},"Engineering team leadership","Руководство инженерными командами","Liderazgo de equipos de ingeniería","Leidinggeven aan engineeringteams",{"en":2686,"ru":2687,"es":2688,"nl":2689},"Payment systems","Платёжные системы","Sistemas de pago","Betaalsystemen",{"en":2691,"ru":2692,"es":2693,"nl":2694},"Card processing","Карточный процессинг","Procesamiento de tarjetas","Kaartverwerking",{"en":2696,"ru":2697,"es":2698,"nl":2699},"PCI-DSS compliance","Соответствие PCI-DSS","Cumplimiento de PCI-DSS","Naleving van PCI-DSS",{"en":2701,"ru":2702,"es":2703,"nl":2704},"NFC and contactless payments","NFC и бесконтактные платежи","NFC y pagos contactless","NFC en contactloos betalen",{"en":2706,"ru":2706,"es":2706,"nl":2706},"Host Card Emulation",{"en":2708,"ru":2708,"es":2708,"nl":2708},"EMV Contactless",{"en":2710,"ru":2710,"es":2710,"nl":2710},"AOSP",[2712,2715,2718,2721],{"type":2713,"url":2714},"github","https:\u002F\u002Fgithub.com\u002Fthenixan",{"type":2716,"url":2717},"linkedin","https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fthenixan\u002F",{"type":2719,"url":2720},"telegram","https:\u002F\u002Ft.me\u002Fthenixan",{"type":2535,"address":2722},"nixan@nerdy.pro",{},{"en":2725,"ru":2726,"es":2727,"nl":2728},"Founder and lead developer at Nerdy Production, former CTO of QIWI. Flutter architecture, payments infrastructure, and engineering teams that ship.","Основатель и ведущий разработчик Nerdy Production, экс-CTO QIWI. Архитектура на Flutter, платёжная инфраструктура и команды, которые доводят до релиза.","Fundador y lead developer en Nerdy Production, ex-CTO de QIWI. Arquitectura Flutter, infraestructura de pagos y equipos de ingeniería que entregan.","Oprichter en lead developer bij Nerdy Production, oud-CTO van QIWI. Flutter-architectuur, betaalinfrastructuur en teams die opleveren.",{"en":2730,"ru":2731,"es":2730,"nl":2730},"Ilya Nixan","Илья Никсан",{"en":2733,"ru":2734,"es":2735,"nl":2736},"Founder and lead developer at Nerdy Production, former CTO of QIWI — Flutter architecture, payments infrastructure, and teams that ship.","Основатель и ведущий разработчик Nerdy Production, экс-CTO QIWI: архитектура на Flutter, платёжная инфраструктура и команды, которые доводят до релиза.","Fundador y lead developer en Nerdy Production, ex-CTO de QIWI: arquitectura Flutter, infraestructura de pagos y equipos que entregan.","Oprichter en lead developer bij Nerdy Production, oud-CTO van QIWI — Flutter-architectuur, betaalinfrastructuur en teams die opleveren.","\u002Fteam\u002Fnixan.webp",{"en":2739,"ru":2740,"es":2741,"nl":2742},"Founder & Lead Developer","Основатель и ведущий разработчик","Fundador y lead developer","Oprichter & lead developer","founder_lead",[41,18,71,141,76,146,51,126,121,155,160,90,13,150,136,7,29,80],"nixan",{"en":2747,"ru":2748,"es":2749,"nl":2750},"Flutter architecture and leading delivery teams","Архитектура на Flutter и руководство командами поставки","Arquitectura Flutter y liderazgo de equipos de entrega","Flutter-architectuur en het leiden van opleverteams","BmeQZ7JyhRFIx8527WO2t1-WklP8AAvevAxiN9U4TEs",{"id":2753,"bio":2754,"expertise":2759,"extension":1162,"links":2771,"meta":2776,"metaDescription":2777,"name":2782,"ogDescription":2785,"photo":2790,"role":2791,"seniority":2654,"skills":2796,"slug":2797,"specialization":2798,"stem":2797,"__hash__":2803},"team_members\u002Froma.yaml",{"en":2755,"ru":2756,"es":2757,"nl":2758},"Roman works across native iOS and Android and [Flutter](\u002Ftechnologies\u002Fflutter), and pairs that mobile depth with [Python](\u002Ftechnologies\u002Fpython) and applied AI.\n\nMuch of his background is in ERP and CRM development — complex, data-heavy business systems where getting the domain model right matters more than the UI — which gives him a pragmatic eye for how an app fits the process behind it.\n","Роман работает с нативными iOS и Android и с [Flutter](\u002Ftechnologies\u002Fflutter), дополняя мобильную экспертизу [Python](\u002Ftechnologies\u002Fpython) и прикладным AI.\n\nЗначительная часть его опыта — разработка ERP и CRM, сложных систем с большим объёмом данных, где правильная доменная модель важнее интерфейса, — что даёт ему прагматичный взгляд на то, как приложение встраивается в процесс за ним.\n","Roman trabaja con iOS y Android nativos y con [Flutter](\u002Ftechnologies\u002Fflutter), y combina esa profundidad móvil con [Python](\u002Ftechnologies\u002Fpython) e IA aplicada.\n\nBuena parte de su trayectoria está en el desarrollo de ERP y CRM —sistemas de negocio complejos y con mucho dato, donde acertar con el modelo de dominio importa más que la interfaz—, lo que le da una mirada pragmática sobre cómo encaja una app en el proceso que hay detrás.\n","Roman werkt met native iOS en Android en met [Flutter](\u002Ftechnologies\u002Fflutter), en combineert die mobiele diepgang met [Python](\u002Ftechnologies\u002Fpython) en toegepaste AI.\n\nEen groot deel van zijn achtergrond ligt in ERP- en CRM-ontwikkeling — complexe, datazware bedrijfssystemen waarin het domeinmodel goed krijgen zwaarder weegt dan de interface — wat hem een pragmatische blik geeft op hoe een app in het proces erachter past.\n",[2760,2761,2766],{"en":2671,"ru":2672,"es":2673,"nl":2674},{"en":2762,"ru":2763,"es":2764,"nl":2765},"Applied AI","Прикладной AI","IA aplicada","Toegepaste AI",{"en":2767,"ru":2768,"es":2769,"nl":2770},"ERP and CRM systems","ERP- и CRM-системы","Sistemas ERP y CRM","ERP- en CRM-systemen",[2772,2774],{"type":2716,"url":2773},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fromanbatler\u002F",{"type":2535,"address":2775},"roma@nerdy.pro",{},{"en":2778,"ru":2779,"es":2780,"nl":2781},"Senior mobile developer at Nerdy Production — native iOS and Android, Flutter, applied AI, and a background in data-heavy ERP and CRM systems.","Старший мобильный разработчик Nerdy Production: нативные iOS и Android, Flutter, прикладной AI и опыт в ERP- и CRM-системах.","Desarrollador móvil senior en Nerdy Production: iOS y Android nativos, Flutter, IA aplicada y experiencia en sistemas ERP y CRM con gran volumen de datos.","Senior mobiele developer bij Nerdy Production — native iOS en Android, Flutter, toegepaste AI, en een achtergrond in datazware ERP- en CRM-systemen.",{"en":2783,"ru":2784,"es":2783,"nl":2783},"Roman","Рома",{"en":2786,"ru":2787,"es":2788,"nl":2789},"Senior mobile developer at Nerdy Production — native iOS and Android, Flutter, applied AI, and years of data-heavy ERP and CRM behind him.","Старший мобильный разработчик Nerdy Production: нативные iOS и Android, Flutter, прикладной AI и годы работы с нагруженными ERP и CRM.","Desarrollador móvil senior en Nerdy Production: iOS y Android nativos, Flutter, IA aplicada y años de ERP y CRM con mucha carga de datos.","Senior mobiele developer bij Nerdy Production — native iOS en Android, Flutter, toegepaste AI, en jaren datazware ERP en CRM achter zich.","\u002Fteam\u002Froma.webp",{"en":2792,"ru":2793,"es":2794,"nl":2795},"Senior Mobile Developer","Старший мобильный разработчик","Desarrollador móvil senior","Senior mobiele developer",[7,41,18,141,146,106],"roma",{"en":2799,"ru":2800,"es":2801,"nl":2802},"Mobile, AI, and ERP\u002FCRM systems","Мобильная разработка, AI и системы ERP\u002FCRM","Móvil, IA y sistemas ERP\u002FCRM","Mobiel, AI en ERP\u002FCRM-systemen","caPSPO2BX35G9lEjNb6p3U4uF_V7yh8cexCe6l5hm8Y",{"id":2805,"bio":2806,"expertise":2811,"extension":1162,"links":2507,"meta":2822,"metaDescription":2823,"name":2828,"ogDescription":2831,"photo":2836,"role":2837,"seniority":2654,"skills":2842,"slug":2843,"specialization":2844,"stem":2843,"__hash__":2849},"team_members\u002Fxsox.yaml",{"en":2807,"ru":2808,"es":2809,"nl":2810},"Eugene builds the systems that apps depend on. His background is in backend engineering — designing and running the APIs, data models, and services behind a product, primarily in [Python](\u002Ftechnologies\u002Fpython) and [Django](\u002Ftechnologies\u002Fdjango).\n\nHe also works in [Flutter](\u002Ftechnologies\u002Fflutter), so he can reason about a feature from the database to the device and build the backend so the app that consumes it stays simple.\n","Женя строит системы, на которые опираются приложения. Его основной опыт — бэкенд-инженерия: проектирование и эксплуатация API, моделей данных и сервисов за продуктом, в первую очередь на [Python](\u002Ftechnologies\u002Fpython) и [Django](\u002Ftechnologies\u002Fdjango).\n\nОн также работает с [Flutter](\u002Ftechnologies\u002Fflutter), поэтому может рассуждать о фиче от базы данных до устройства и строить бэкенд так, чтобы потребляющее его приложение оставалось простым.\n","Eugene construye los sistemas de los que dependen las apps. Su trayectoria está en la ingeniería backend: diseñar y operar las APIs, los modelos de datos y los servicios que hay detrás de un producto, principalmente en [Python](\u002Ftechnologies\u002Fpython) y [Django](\u002Ftechnologies\u002Fdjango).\n\nTambién trabaja con [Flutter](\u002Ftechnologies\u002Fflutter), así que puede razonar sobre una funcionalidad desde la base de datos hasta el dispositivo y construir el backend de forma que la app que lo consume siga siendo simple.\n","Eugene bouwt de systemen waar apps op leunen. Zijn achtergrond ligt in backend-engineering: de API's, datamodellen en services achter een product ontwerpen en draaien, vooral in [Python](\u002Ftechnologies\u002Fpython) en [Django](\u002Ftechnologies\u002Fdjango).\n\nHij werkt daarnaast met [Flutter](\u002Ftechnologies\u002Fflutter), zodat hij over een functie kan nadenken van de database tot het toestel en de backend zo kan bouwen dat de app die hem verbruikt eenvoudig blijft.\n",[2812,2817],{"en":2813,"ru":2814,"es":2815,"nl":2816},"API design","Проектирование API","Diseño de APIs","API-ontwerp",{"en":2818,"ru":2819,"es":2820,"nl":2821},"Data modelling","Моделирование данных","Modelado de datos","Datamodellering",{},{"en":2824,"ru":2825,"es":2826,"nl":2827},"Senior backend developer at Nerdy Production building the APIs, data models and services behind our apps, primarily in Python and Django.","Старший бэкенд-разработчик Nerdy Production: API, модели данных и сервисы за нашими приложениями, в первую очередь на Python и Django.","Desarrollador backend senior en Nerdy Production: las APIs, modelos de datos y servicios detrás de nuestras apps, sobre todo en Python y Django.","Senior backenddeveloper bij Nerdy Production die de API's, datamodellen en services achter onze apps bouwt, vooral in Python en Django.",{"en":2829,"ru":2830,"es":2829,"nl":2829},"Eugene Xsox","Женя Xsox",{"en":2832,"ru":2833,"es":2834,"nl":2835},"Senior backend developer at Nerdy Production — the APIs, data models and services our apps run on, mostly in Python and Django.","Старший бэкенд-разработчик Nerdy Production: API, модели данных и сервисы, на которых работают наши приложения, в основном на Python и Django.","Desarrollador backend senior en Nerdy Production: las APIs, modelos de datos y servicios sobre los que corren nuestras apps, en Python y Django.","Senior backenddeveloper bij Nerdy Production — de API's, datamodellen en services waarop onze apps draaien, vooral in Python en Django.","\u002Fteam\u002Fxsox.webp",{"en":2838,"ru":2839,"es":2840,"nl":2841},"Senior Backend Developer","Старший бэкенд-разработчик","Desarrollador backend senior","Senior backenddeveloper",[106,24,41,18,155,160,90,150],"xsox",{"en":2845,"ru":2846,"es":2847,"nl":2848},"Python\u002FDjango backends, plus Flutter","Бэкенды на Python\u002FDjango и Flutter","Backends en Python\u002FDjango, además de Flutter","Backends in Python\u002FDjango, plus Flutter","w_QAKKq_Mh_dtMvAxFpAJBC10PW1I7zgVOxELwRHwO8",[2851,2860,2867,2872,2882],{"id":2852,"extension":1162,"meta":2853,"name":2854,"slug":2858,"stem":2858,"__hash__":2859},"blog_topics\u002Fcybersecurity.yaml",{},{"en":2855,"ru":2856,"es":2857,"nl":2855},"Cybersecurity","Кибербезопасность","Ciberseguridad","cybersecurity","kt1rdsMy5W4b9MvGHm6mZVCr0inHidEuqaosPYmmk4E",{"id":2861,"extension":1162,"meta":2862,"name":2863,"slug":2865,"stem":2865,"__hash__":2866},"blog_topics\u002Fdevops.yaml",{},{"en":2864,"ru":2864,"es":2864,"nl":2864},"DevOps","devops","D3mxlZuFHKCGeszKZuWCiQmHmP5EiCvzg1qEAIQhnyU",{"id":2868,"extension":1162,"meta":2869,"name":2870,"slug":41,"stem":41,"__hash__":2871},"blog_topics\u002Fflutter.yaml",{},{"en":39,"ru":39,"es":39,"nl":39},"OOeALAmwFuEqByuJvTGk4g15FIHENPfYMdpfZh_HTAE",{"id":2873,"extension":1162,"meta":2874,"name":2875,"slug":2880,"stem":2880,"__hash__":2881},"blog_topics\u002Ffor-founders.yaml",{},{"en":2876,"ru":2877,"es":2878,"nl":2879},"For Founders","Для основателей","Para fundadores","Voor oprichters","for-founders","lp2u6Ol2qF4DGxpB83HxbbGzWioT-e40mJhD4lr28-U",{"id":2883,"extension":1162,"meta":2884,"name":2885,"slug":2890,"stem":2890,"__hash__":2891},"blog_topics\u002Fsoftware-engineering.yaml",{},{"en":2886,"ru":2887,"es":2888,"nl":2889},"Software Engineering","Разработка ПО","Ingeniería de software","Software-engineering","software-engineering","aZMb3aUDptoFQk_cEE6l5H4wrYcgZuBYUubmKqOAaaU",{"data":2893,"body":2894},{},{"type":2895,"children":2896},"root",[2897,2930],{"type":2898,"tag":175,"props":2899,"children":2900},"element",{},[2901,2904,2909,2910,2915,2916,2921,2923,2928],{"type":2902,"value":2903},"text","Maxim is a polyglot engineer who moves comfortably across ",{"type":2898,"tag":2253,"props":2905,"children":2907},{"href":2906},"\u002Ftechnologies\u002Fgo",[2908],{"type":2902,"value":49},{"type":2902,"value":1916},{"type":2898,"tag":2253,"props":2911,"children":2913},{"href":2912},"\u002Ftechnologies\u002Fflutter",[2914],{"type":2902,"value":39},{"type":2902,"value":1916},{"type":2898,"tag":2253,"props":2917,"children":2919},{"href":2918},"\u002Ftechnologies\u002Fpython",[2920],{"type":2902,"value":104},{"type":2902,"value":2922},", and ",{"type":2898,"tag":2253,"props":2924,"children":2926},{"href":2925},"\u002Ftechnologies\u002Ftypescript",[2927],{"type":2902,"value":153},{"type":2902,"value":2929},", which lets him own a feature from the backend to the screen.",{"type":2898,"tag":175,"props":2931,"children":2932},{},[2933],{"type":2902,"value":2934},"He came up at Ozon, one of Russia's largest marketplaces, where scale makes reliability non-negotiable — and it shows in his work: testing is a first-class part of how he builds, not an afterthought bolted on at the end.",[2936,2950,2963,2978,2992,3006,3022,3034,3048,3061,3075,3088,3104,3117,3134,3144,3155,3165,3178,3188,3197,3212,3221,3234,3247,3254,3265,3277,3288,3303,3315,3328,3340,3352,3364,3376,3387,3398,3409,3420,3432,3442,3453,3466,3478,3487,3499,3511,3521,3532,3545,3554,3565],{"slug":2937,"term":2710,"definition":2938,"category":2939,"aliases":2940,"links":2942,"related":2946,"readMore":-1,"target":2948,"hasArticle":2949},"aosp","The Android Open Source Project — Android without the Google layer on top, which anyone may fork. Point-of-sale terminals, kiosks and in-car systems run on forks of it, and working at that level exposes parts of the OS an app developer never sees.","platform",[2941],"Android Open Source Project",[2943],{"kind":2944,"url":2945},"website","https:\u002F\u002Fsource.android.com\u002F",[2947],"host-card-emulation","\u002Fglossary#aosp",false,{"slug":2951,"term":2952,"definition":2953,"category":2939,"aliases":2954,"links":2955,"related":2959,"readMore":-1,"target":2962,"hasArticle":2949},"app-clips","App Clips","An Apple feature that runs a small slice of an iOS app — under 15 MB — without installing the whole thing. Invoked from a QR code, an NFC tag or a link, for when the first thing a user does should not require a store visit.",[],[2956],{"kind":2957,"url":2958},"documentation","https:\u002F\u002Fdeveloper.apple.com\u002Fapp-clips\u002F",[2960,2961],"deep-linking","install-referrer","\u002Fglossary#app-clips",{"slug":2964,"term":2965,"definition":2966,"category":2939,"aliases":2967,"links":2968,"related":2974,"readMore":-1,"target":2977,"hasArticle":2949},"bigquery","BigQuery","Google Cloud's analytics warehouse. You point SQL at billions of rows and it scans them in seconds, on storage held separately from the machines doing the querying — which is what keeps reporting and exploration off the database that is serving live traffic.",[],[2969,2971],{"kind":2944,"url":2970},"https:\u002F\u002Fcloud.google.com\u002Fbigquery",{"kind":2972,"url":2973},"wikipedia","https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FBigQuery",[2975,2976],"object-storage","elasticsearch","\u002Fglossary#bigquery",{"slug":2979,"term":2980,"definition":2981,"category":2982,"aliases":2983,"links":2987,"related":2990,"readMore":-1,"target":2991,"hasArticle":2949},"ci-cd","CI\u002FCD","Automation that builds, tests and ships every change without anyone running commands by hand. On mobile it is what turns a release into a button press instead of an afternoon of someone else being unavailable.","practice",[2984,2985,2986],"CI","continuous integration","continuous delivery",[2988],{"kind":2972,"url":2989},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FCI\u002FCD",[],"\u002Fglossary#ci-cd",{"slug":2993,"term":2994,"definition":2995,"category":2996,"aliases":2997,"links":2999,"related":3002,"readMore":-1,"target":3005,"hasArticle":2949},"crud","CRUD","Create, read, update, delete — the four operations behind almost every form and admin screen. Shorthand for the routine data-management half of an app, as opposed to the parts carrying real domain logic.","architecture",[2998],"Create, Read, Update, Delete",[3000],{"kind":2972,"url":3001},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FCreate,_read,_update_and_delete",[3003,3004],"rest","graphql","\u002Fglossary#crud",{"slug":3007,"term":3008,"definition":3009,"category":2996,"aliases":3010,"links":3015,"related":3018,"readMore":-1,"target":3021,"hasArticle":2949},"container-registry","Container registry","A hosted store for container images, addressed by name and tag — Docker Hub, GitHub Container Registry, or a cloud provider's own. Pushing a build there turns 'works on my machine' into an image anyone can pull and run unchanged.",[3011,3012,3013,3014],"image registry","Docker registry","GHCR","ghcr.io",[3016],{"kind":2957,"url":3017},"https:\u002F\u002Fdocs.github.com\u002Fen\u002Fpackages\u002Fworking-with-a-github-packages-registry\u002Fworking-with-the-container-registry",[3019,3020],"dev-container","multi-arch-image","\u002Fglossary#container-registry",{"slug":2960,"term":3023,"definition":3024,"category":2939,"aliases":3025,"links":3029,"related":3032,"readMore":3033,"target":3033,"hasArticle":360},"Deep linking","A link that opens a specific screen inside an installed app instead of its home screen or a web page. Deferred deep linking survives an install, so a tap that leads through the app store still lands on the right screen.",[3026,3027,3028],"deferred deep linking","universal links","Android App Links",[3030],{"kind":2972,"url":3031},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FMobile_deep_linking",[2951,2961],"\u002Fglossary\u002Fdeep-linking",{"slug":3019,"term":3035,"definition":3036,"category":2982,"aliases":3037,"links":3041,"related":3046,"readMore":-1,"target":3047,"hasArticle":2949},"Dev Container","A development environment described once in devcontainer.json — the OS, tools, and runtime versions a project needs — and opened identically inside a container by every contributor's editor, instead of a setup guide everyone interprets differently.",[3038,3039,3040],"devcontainer.json","Dev Containers","VS Code Dev Containers",[3042,3044],{"kind":2944,"url":3043},"https:\u002F\u002Fcontainers.dev\u002F",{"kind":2957,"url":3045},"https:\u002F\u002Fcode.visualstudio.com\u002Fdocs\u002Fdevcontainers\u002Fcontainers",[3007],"\u002Fglossary#dev-container",{"slug":2976,"term":3049,"definition":3050,"category":2939,"aliases":3051,"links":3054,"related":3059,"readMore":-1,"target":3060,"hasArticle":2949},"Elasticsearch","A search and analytics engine that indexes records so they can be filtered and searched interactively instead of scanned. What you reach for when the question is \"show me these particular sessions, narrowed six ways\" rather than \"sum this column\".",[3052,3053],"Elastic","ELK",[3055,3057],{"kind":2944,"url":3056},"https:\u002F\u002Fwww.elastic.co\u002Felasticsearch",{"kind":2972,"url":3058},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FElasticsearch",[2964],"\u002Fglossary#elasticsearch",{"slug":3062,"term":3063,"definition":3064,"category":2996,"aliases":3065,"links":3068,"related":3071,"readMore":-1,"target":3074,"hasArticle":2949},"end-to-end-encryption","End-to-end encryption","Encryption applied on the sending device and undone only on the receiving one, so the service carrying the message cannot read it — not under subpoena, not after a breach. It protects the content and never the metadata.",[3066,3067],"E2EE","end-to-end encrypted",[3069],{"kind":2972,"url":3070},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FEnd-to-end_encryption",[3072,3073],"tls","jwt","\u002Fglossary#end-to-end-encryption",{"slug":3076,"term":3077,"definition":3078,"category":2996,"aliases":3079,"links":3082,"related":3083,"readMore":-1,"target":3087,"hasArticle":2949},"fan-out","Fan-out","Reading an upstream source once and delivering each update to every client subscribed to it. The naive version writes to subscribers in a loop and stalls the moment one socket is slow; a real one buffers per client and drops whoever cannot keep up.",[3080,3081],"fanout","broadcast",[],[3084,3085,3086],"websocket","pub-sub","server-sent-events","\u002Fglossary#fan-out",{"slug":3089,"term":3090,"definition":3091,"category":2982,"aliases":3092,"links":3096,"related":3099,"readMore":-1,"target":3103,"hasArticle":2949},"feature-flags","Feature flags","Switches that turn functionality on or off from configuration rather than from a release. They let one binary behave differently per brand, market or user, and let a risky feature be shut off without shipping a new build through review.",[3093,3094,3095],"feature flag","feature toggle","feature gating",[3097],{"kind":2972,"url":3098},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FFeature_toggle",[3100,3101,3102],"staged-rollout","white-label","multi-tenancy","\u002Fglossary#feature-flags",{"slug":3105,"term":3106,"definition":3107,"category":2996,"aliases":3108,"links":3112,"related":3115,"readMore":-1,"target":3116,"hasArticle":2949},"floating-point","Floating point","The IEEE 754 binary format behind double and float. It cannot hold 0.1 exactly, so 0.1 + 0.2 is 0.30000000000000004 — invisible in graphics and fatal in money, which belongs in integer minor units or a decimal type instead.",[3109,3110,3111],"IEEE 754","double","floating-point arithmetic",[3113],{"kind":2972,"url":3114},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FIEEE_754",[],"\u002Fglossary#floating-point",{"slug":3118,"term":3119,"definition":3120,"category":2982,"aliases":3121,"links":3124,"related":3129,"readMore":-1,"target":3133,"hasArticle":2949},"gdpr","GDPR","The EU regulation covering personal data of people in the EU: a lawful basis for collecting it, real consent for tracking, and rights to see and delete it. It follows your users, not your servers, so it applies wherever the company is registered.",[3122,3123],"General Data Protection Regulation","data protection",[3125,3127],{"kind":2944,"url":3126},"https:\u002F\u002Fgdpr.eu\u002F",{"kind":2972,"url":3128},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FGeneral_Data_Protection_Regulation",[3130,3131,3132],"pci-dss","soc-2","hipaa","\u002Fglossary#gdpr",{"slug":195,"term":3135,"definition":3136,"category":2982,"aliases":3137,"links":3140,"related":3142,"readMore":-1,"target":3143,"hasArticle":2949},"Golden test","A test that renders a widget and compares the result pixel for pixel against a stored reference image. In Flutter it is the cheapest way to answer whether a redesign broke the empty state at 320pt, in dark mode, at 200% text scale.",[196,3138,3139],"screenshot test","snapshot test",[3141],{"kind":2957,"url":2284},[2979],"\u002Fglossary#golden-test",{"slug":3004,"term":3145,"definition":3146,"category":2996,"aliases":3147,"links":3148,"related":3153,"readMore":-1,"target":3154,"hasArticle":2949},"GraphQL","A query language for APIs where the client names exactly the fields it wants and gets one response shaped to match. It removes the over-fetching REST endpoints drift into, and adds a failure mode of its own: an unbounded query that walks the whole data model.",[],[3149,3151],{"kind":2944,"url":3150},"https:\u002F\u002Fgraphql.org\u002F",{"kind":2972,"url":3152},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FGraphQL",[3003],"\u002Fglossary#graphql",{"slug":3132,"term":3156,"definition":3157,"category":2982,"aliases":3158,"links":3160,"related":3163,"readMore":-1,"target":3164,"hasArticle":2949},"HIPAA","The US law governing protected health information — how it may be stored, transmitted, logged and disclosed. Like PCI-DSS it is an architectural constraint chosen at the start, not a policy document added before launch.",[3159],"Health Insurance Portability and Accountability Act",[3161],{"kind":2972,"url":3162},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FHealth_Insurance_Portability_and_Accountability_Act",[3130,3118,3131],"\u002Fglossary#hipaa",{"slug":3166,"term":3167,"definition":3168,"category":2996,"aliases":3169,"links":3172,"related":3175,"readMore":-1,"target":3177,"hasArticle":2949},"headless-cms","Headless CMS","A content system with an editor and an API but no front end of its own. Editors publish in one place, and the site or app renders that content itself — so the presentation layer is yours rather than the CMS vendor's.",[3170,3171],"headless content management system","content API",[3173],{"kind":2972,"url":3174},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FHeadless_content_management_system",[3176,3003],"server-side-rendering","\u002Fglossary#headless-cms",{"slug":2947,"term":2706,"definition":3179,"category":2939,"aliases":3180,"links":3183,"related":3186,"readMore":-1,"target":3187,"hasArticle":2949},"Letting an Android phone act as a contactless card over NFC in software, with no hardware secure element. It is how a wallet app pays at a terminal: the phone speaks the same EMV contactless protocol the plastic card would have.",[3181,2708,3182],"HCE","contactless payments",[3184],{"kind":2957,"url":3185},"https:\u002F\u002Fdeveloper.android.com\u002Fdevelop\u002Fconnectivity\u002Fnfc\u002Fhce",[3130,2937],"\u002Fglossary#host-card-emulation",{"slug":2164,"term":2165,"definition":3189,"category":2939,"aliases":3190,"links":3191,"related":3194,"readMore":-1,"target":3196,"hasArticle":2949},"The rendering engine Flutter uses today, default on iOS since 2023 and on Android since 2024. It compiles its shaders ahead of time instead of during the first animation, which removed the shader-compilation jank that was Flutter's most visible production problem.",[],[3192],{"kind":2957,"url":3193},"https:\u002F\u002Fdocs.flutter.dev\u002Fperf\u002Fimpeller",[3195],"skia","\u002Fglossary#impeller",{"slug":3198,"term":3199,"definition":3200,"category":3201,"aliases":3202,"links":3206,"related":3209,"readMore":-1,"target":3211,"hasArticle":2949},"in-app-purchase","In-app purchase","Selling digital goods or a subscription through the Apple or Google billing that both stores require for digital content and take a commission on. The hard part is never the purchase; it is restoring it on a new device and keeping entitlement state honest.","business",[3203,3204,3205],"IAP","in-app purchases","in-app subscription",[3207],{"kind":2957,"url":3208},"https:\u002F\u002Fdeveloper.apple.com\u002Fin-app-purchase\u002F",[3210],"product-market-fit","\u002Fglossary#in-app-purchase",{"slug":2961,"term":3213,"definition":3214,"category":2939,"aliases":3215,"links":3216,"related":3219,"readMore":-1,"target":3220,"hasArticle":2949},"Install Referrer","A Google Play API that hands a freshly installed Android app the campaign parameters from the link that led to the install. The Android half of deferred deep linking, and the dependable way to attribute where a user came from.",[],[3217],{"kind":2957,"url":3218},"https:\u002F\u002Fdeveloper.android.com\u002Fgoogle\u002Fplay\u002Finstallreferrer",[2960,2951],"\u002Fglossary#install-referrer",{"slug":3073,"term":3222,"definition":3223,"category":3224,"aliases":3225,"links":3227,"related":3232,"readMore":-1,"target":3233,"hasArticle":2949},"JWT","A signed token carrying its own claims, so a server can tell who a request belongs to without looking a session up. Standard for mobile authentication. The signature proves it was not altered; it does not hide what is inside.","protocol",[3226],"JSON Web Token",[3228,3230],{"kind":2944,"url":3229},"https:\u002F\u002Fjwt.io\u002F",{"kind":2972,"url":3231},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FJSON_Web_Token",[3130],"\u002Fglossary#jwt",{"slug":3235,"term":3236,"definition":3237,"category":2982,"aliases":3238,"links":3242,"related":3245,"readMore":-1,"target":3246,"hasArticle":2949},"kyc","KYC","Know Your Customer — the identity checks a regulated financial product runs before it lets anyone move money: document capture, liveness, sanctions and anti-money-laundering screening. It shapes onboarding more than any design decision does.",[3239,3240,3241],"Know Your Customer","AML","KYC\u002FAML",[3243],{"kind":2972,"url":3244},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FKnow_your_customer",[3130,3118],"\u002Fglossary#kyc",{"slug":76,"term":74,"definition":3248,"category":2939,"aliases":3249,"links":3251,"related":3252,"readMore":3253,"target":3253,"hasArticle":2949},"Sharing business logic written in Kotlin across Android, iOS and the server while each platform keeps its own native UI. The alternative to Flutter when the interface has to be native but the rules behind it do not.",[3250],"KMP",[],[],"\u002Ftechnologies\u002Fkmp",{"slug":3255,"term":3256,"definition":3257,"category":3201,"aliases":3258,"links":3260,"related":3263,"readMore":-1,"target":3264,"hasArticle":2949},"mvp","MVP","The smallest version of a product that can go in front of real users and still answer the question you built it to answer. A decision about scope, not about quality — an MVP still has to work.",[3259],"minimum viable product",[3261],{"kind":2972,"url":3262},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FMinimum_viable_product",[3101],"\u002Fglossary#mvp",{"slug":3020,"term":3266,"definition":3267,"category":2996,"aliases":3268,"links":3272,"related":3275,"readMore":-1,"target":3276,"hasArticle":2949},"Multi-architecture image","A single image tag that resolves to different binaries per CPU architecture — linux\u002Famd64 and linux\u002Farm64 are the common pair — so the same docker pull works unchanged on Intel\u002FAMD servers and Apple Silicon laptops.",[3269,3270,3271],"multi-arch build","multi-platform image","linux\u002Famd64 + linux\u002Farm64",[3273],{"kind":2957,"url":3274},"https:\u002F\u002Fdocs.docker.com\u002Fbuild\u002Fbuilding\u002Fmulti-platform\u002F",[3007],"\u002Fglossary#multi-arch-image",{"slug":3102,"term":3278,"definition":3279,"category":2996,"aliases":3280,"links":3283,"related":3286,"readMore":-1,"target":3287,"hasArticle":2949},"Multi-tenancy","One deployment serving many customers, each seeing only its own data, configuration and enabled features because tenant context is resolved per request. It is what makes a fleet of branded apps one product instead of many forks.",[3281,3282],"multi-tenant","tenant",[3284],{"kind":2972,"url":3285},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FMultitenancy",[3101,3089],"\u002Fglossary#multi-tenancy",{"slug":3289,"term":3290,"definition":3291,"category":3224,"aliases":3292,"links":3296,"related":3301,"readMore":-1,"target":3302,"hasArticle":2949},"oauth","OAuth","The standard behind Sign in with Apple, Google and the rest: the user authorises your app at a provider they already trust, and your app receives a token instead of their password. Nobody invents a new credential and you never store one.",[3293,3294,3295],"OAuth 2.0","social login","Sign in with Apple",[3297,3299],{"kind":2944,"url":3298},"https:\u002F\u002Foauth.net\u002F2\u002F",{"kind":2972,"url":3300},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FOAuth",[3073],"\u002Fglossary#oauth",{"slug":2975,"term":3304,"definition":3305,"category":2996,"aliases":3306,"links":3310,"related":3313,"readMore":-1,"target":3314,"hasArticle":2949},"Object storage","Storage that holds a whole file under a key rather than in a filesystem tree — Amazon S3 and the many services that speak its API. Cheap, effectively unlimited, and the usual home for raw events, backups and media: written once, read rarely, kept forever.",[3307,3308,3309],"S3","S3-compatible storage","blob storage",[3311],{"kind":2972,"url":3312},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FObject_storage",[2964],"\u002Fglossary#object-storage",{"slug":3130,"term":3316,"definition":3317,"category":2982,"aliases":3318,"links":3321,"related":3326,"readMore":-1,"target":3327,"hasArticle":2949},"PCI-DSS","The card industry security standard binding anyone who stores, processes or transmits card data. Most apps stay out of its scope on purpose, by handing card entry to a certified payment provider instead.",[3319,3320],"PCI DSS","Payment Card Industry Data Security Standard",[3322,3324],{"kind":2944,"url":3323},"https:\u002F\u002Fwww.pcisecuritystandards.org\u002F",{"kind":2972,"url":3325},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPayment_Card_Industry_Data_Security_Standard",[3073],"\u002Fglossary#pci-dss",{"slug":3329,"term":3330,"definition":3331,"category":2939,"aliases":3332,"links":3335,"related":3338,"readMore":-1,"target":3339,"hasArticle":2949},"platform-channels","Platform channels","The bridge a Flutter app uses to call native iOS and Android code — Keychain and Keystore, biometrics, payment sheets, any SDK without a Dart package. Routine work but real work, and the first place an engineer who never left Dart will stall.",[3333,3334],"platform channel","method channel",[3336],{"kind":2957,"url":3337},"https:\u002F\u002Fdocs.flutter.dev\u002Fplatform-integration\u002Fplatform-channels",[76],"\u002Fglossary#platform-channels",{"slug":3341,"term":3342,"definition":3343,"category":3224,"aliases":3344,"links":3347,"related":3350,"readMore":-1,"target":3351,"hasArticle":2949},"post-quantum-cryptography","Post-quantum cryptography","Encryption algorithms built to stay secure against a future quantum computer, now standardized by NIST. The migration is urgent ahead of the hardware because traffic captured today can be decrypted once such a machine exists.",[3345,3346],"PQC","post-quantum crypto",[3348],{"kind":2972,"url":3349},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPost-quantum_cryptography",[3072,3062],"\u002Fglossary#post-quantum-cryptography",{"slug":3210,"term":3353,"definition":3354,"category":3201,"aliases":3355,"links":3358,"related":3361,"readMore":-1,"target":3363,"hasArticle":2949},"Product-market fit","The point at which a product has demonstrably found people who want it — they use it, come back, and pay. Before it, engineering answers a question; after it, engineering answers demand.",[3356,3357],"PMF","product\u002Fmarket fit",[3359],{"kind":2972,"url":3360},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FProduct-market_fit",[3255,3362],"time-to-market","\u002Fglossary#product-market-fit",{"slug":3365,"term":3366,"definition":3367,"category":2982,"aliases":3368,"links":3370,"related":3373,"readMore":-1,"target":3375,"hasArticle":2949},"prompt-injection","Prompt injection","An attack where text supplied by a user is read by a language model as instructions rather than as data, steering it past its own rules. The LLM-era sibling of SQL injection, and it appears wherever user input is concatenated into a prompt.",[3369],"injection attack",[3371],{"kind":2972,"url":3372},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPrompt_injection",[3374],"rate-limiting","\u002Fglossary#prompt-injection",{"slug":3085,"term":3377,"definition":3378,"category":2996,"aliases":3379,"links":3382,"related":3385,"readMore":-1,"target":3386,"hasArticle":2949},"Pub\u002FSub","A messaging pattern where a producer publishes an event and any number of consumers read it independently, with a broker in between. The producer never waits for them, which is how a request path stays fast while slower work happens behind it.",[3380,3381],"publish\u002Fsubscribe","Google Cloud Pub\u002FSub",[3383],{"kind":2972,"url":3384},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPublish%E2%80%93subscribe_pattern",[],"\u002Fglossary#pub-sub",{"slug":3003,"term":3388,"definition":3389,"category":2996,"aliases":3390,"links":3393,"related":3396,"readMore":-1,"target":3397,"hasArticle":2949},"REST","The conventional style for HTTP APIs: a URL names a resource and the HTTP verb says what to do with it. The default way an app talks to a backend, and what most third-party integrations expect to find.",[3391,3392],"REST API","Representational State Transfer",[3394],{"kind":2972,"url":3395},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FREST",[3084,3086],"\u002Fglossary#rest",{"slug":3374,"term":3399,"definition":3400,"category":2982,"aliases":3401,"links":3404,"related":3407,"readMore":-1,"target":3408,"hasArticle":2949},"Rate limiting","A cap on how many requests one caller may make in a given window. It is what stops a single enthusiastic user, a scraper or a bot from spending a month of paid API budget in an afternoon, and it has to live on your side of the integration.",[3402,3403],"rate limit","throttling",[3405],{"kind":2972,"url":3406},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FRate_limiting",[3365],"\u002Fglossary#rate-limiting",{"slug":3131,"term":3410,"definition":3411,"category":2982,"aliases":3412,"links":3415,"related":3418,"readMore":-1,"target":3419,"hasArticle":2949},"SOC 2","An external auditor report on how an organisation handles customer data — security, availability, confidentiality — rather than a certificate you buy. Enterprise buyers ask for it, and it constrains architecture long before the audit itself does.",[3413,3414],"SOC2","System and Organization Controls",[3416],{"kind":2972,"url":3417},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FSystem_and_Organization_Controls",[3130,3118,3132],"\u002Fglossary#soc-2",{"slug":3421,"term":3422,"definition":3423,"category":3201,"aliases":3424,"links":3427,"related":3430,"readMore":-1,"target":3431,"hasArticle":2949},"saas","SaaS","Software sold as an ongoing subscription to a hosted product rather than as a one-off license the customer installs and runs. The vendor operates the servers, ships updates continuously, and bills per seat or per usage.",[3425,3426],"Software as a Service","software-as-a-service",[3428],{"kind":2972,"url":3429},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FSoftware_as_a_service",[3102,3101],"\u002Fglossary#saas",{"slug":3086,"term":3433,"definition":3434,"category":3224,"aliases":3435,"links":3437,"related":3440,"readMore":-1,"target":3441,"hasArticle":2949},"Server-Sent Events","A one-way stream from server to client over an ordinary HTTP connection. Simpler than a WebSocket and enough wherever only the server has something to say — a progress feed, an AI response arriving token by token.",[3436],"SSE",[3438],{"kind":2972,"url":3439},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FServer-sent_events",[3084,3003],"\u002Fglossary#server-sent-events",{"slug":3176,"term":3443,"definition":3444,"category":2996,"aliases":3445,"links":3448,"related":3451,"readMore":-1,"target":3452,"hasArticle":2949},"Server-side rendering","Building a page as finished HTML on the server, so the first response already carries the content, headings, meta tags and structured data. Crawlers, link previews and slow devices read it without running JavaScript.",[3446,3447],"SSR","server-rendered",[3449],{"kind":2972,"url":3450},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FServer-side_scripting",[3166],"\u002Fglossary#server-side-rendering",{"slug":3195,"term":3454,"definition":3455,"category":2939,"aliases":3456,"links":3459,"related":3464,"readMore":-1,"target":3465,"hasArticle":2949},"Skia","The open-source 2D graphics library from Google that draws Chrome, Android and — until Impeller — every Flutter frame. It renders to PDF as well as to a screen, which is what print-to-PDF in Chrome is doing.",[3457,3458],"Skia Graphics Engine","skia-safe",[3460,3462],{"kind":2944,"url":3461},"https:\u002F\u002Fskia.org\u002F",{"kind":2972,"url":3463},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FSkia_Graphics_Engine",[2164],"\u002Fglossary#skia",{"slug":3467,"term":3468,"definition":3469,"category":3201,"aliases":3470,"links":3474,"related":3475,"readMore":-1,"target":3477,"hasArticle":2949},"staff-augmentation","Staff augmentation","A hiring model where engineers from an outside partner join your team and work under your management — in your repository, your sprints, your process — instead of delivering a project of their own. You buy capacity; the code and the context stay with you.",[3471,3472,3473],"team augmentation","dedicated developers","outstaffing",[],[3476,3362],"total-cost-of-ownership","\u002Fglossary#staff-augmentation",{"slug":3100,"term":3479,"definition":3480,"category":2982,"aliases":3481,"links":3484,"related":3485,"readMore":-1,"target":3486,"hasArticle":2949},"Staged rollout","Releasing a build to a small percentage of users first and widening only once the crash-free rate holds. A bad build caught at ten percent is a bad afternoon; the same build at a hundred percent is a bad week.",[3482,3483],"phased release","canary release",[],[3089,2979],"\u002Fglossary#staged-rollout",{"slug":3488,"term":2519,"definition":3489,"category":2996,"aliases":3490,"links":3494,"related":3497,"readMore":-1,"target":3498,"hasArticle":2949},"state-management","How an app decides where a value lives, who is allowed to change it, and which parts of the screen redraw when it does. In Flutter the choice between Riverpod, BLoC and Provider is among the first architectural decisions and the hardest to revisit.",[3491,3492,3493],"state management","BLoC","Riverpod",[3495],{"kind":2957,"url":3496},"https:\u002F\u002Fdocs.flutter.dev\u002Fdata-and-backend\u002Fstate-mgmt\u002Foptions",[195],"\u002Fglossary#state-management",{"slug":3072,"term":3500,"definition":3501,"category":3224,"aliases":3502,"links":3506,"related":3509,"readMore":-1,"target":3510,"hasArticle":2949},"TLS","The encryption layer underneath HTTPS. It proves the server is who its certificate says, agrees a fresh key for the session, and encrypts everything after that — so the network in between sees ciphertext it cannot quietly alter.",[3503,3504,3505],"SSL","HTTPS","Transport Layer Security",[3507],{"kind":2972,"url":3508},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FTransport_Layer_Security",[3062,3073],"\u002Fglossary#tls",{"slug":3362,"term":3512,"definition":3513,"category":3201,"aliases":3514,"links":3516,"related":3519,"readMore":-1,"target":3520,"hasArticle":2949},"Time to market","How long it takes to get a product from decision to real users. Most stack and scope arguments are really arguments about this number, because every week saved is a week of revenue, feedback and competitive position.",[3515,3362],"TTM",[3517],{"kind":2972,"url":3518},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FTime_to_market",[3255,3476],"\u002Fglossary#time-to-market",{"slug":3476,"term":3522,"definition":3523,"category":3201,"aliases":3524,"links":3527,"related":3530,"readMore":-1,"target":3531,"hasArticle":2949},"Total cost of ownership","What a product costs across its whole life rather than to build once: maintenance, upgrades, annual OS and store migrations, and the second team you staff to keep two codebases in step. Usually larger than the build quote, and almost never inside it.",[3525,3526],"TCO","cost of ownership",[3528],{"kind":2972,"url":3529},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FTotal_cost_of_ownership",[3362],"\u002Fglossary#total-cost-of-ownership",{"slug":3533,"term":2527,"definition":3534,"category":3224,"aliases":3535,"links":3537,"related":3542,"readMore":-1,"target":3544,"hasArticle":2949},"webrtc","The browser and mobile standard for sending audio, video and data directly between two devices, with servers involved only in introducing them to each other. It is what an in-app video call is built on when it is not a rented SDK.",[3536],"Web Real-Time Communication",[3538,3540],{"kind":2944,"url":3539},"https:\u002F\u002Fwebrtc.org\u002F",{"kind":2972,"url":3541},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FWebRTC",[3543,3084],"xmpp","\u002Fglossary#webrtc",{"slug":3084,"term":3546,"definition":3547,"category":3224,"aliases":3548,"links":3549,"related":3552,"readMore":-1,"target":3553,"hasArticle":2949},"WebSocket","A protocol that holds one connection open between client and server so either side can send at any moment, instead of the client asking over and over. What live prices, chat and presence indicators run on.",[],[3550],{"kind":2972,"url":3551},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FWebSocket",[3086,3003],"\u002Fglossary#websocket",{"slug":3101,"term":3555,"definition":3556,"category":3201,"aliases":3557,"links":3560,"related":3563,"readMore":3564,"target":3564,"hasArticle":360},"White-label","One product shipped under many brands. A white-label mobile platform builds each client a store-ready app with its own name, design and content from a single shared codebase, instead of forking the project per customer.",[3558,3559],"white label","multi-tenant app",[3561],{"kind":2972,"url":3562},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FWhite-label_product",[3255],"\u002Fglossary\u002Fwhite-label",{"slug":3543,"term":3566,"definition":3567,"category":3224,"aliases":3568,"links":3571,"related":3576,"readMore":-1,"target":3577,"hasArticle":2949},"XMPP","An open, federated messaging protocol, and the long-standing alternative to writing a chat backend or renting one. It extends to presence, typing indicators and file transfer, and it is old enough that every platform has a mature client library.",[3569,3570],"Jabber","Extensible Messaging and Presence Protocol",[3572,3574],{"kind":2944,"url":3573},"https:\u002F\u002Fxmpp.org\u002F",{"kind":2972,"url":3575},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FXMPP",[3533,3084],"\u002Fglossary#xmpp"]