[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"technologies":3,"\u002Fblog\u002Fflutter-golden-tests":167,"team-members":2518,"blog-topics":2859,"mdc--8qk3kd-key":2901,"glossary":2943},[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","Elke app die we uitbrengen bereikt Android — platform-API's, uitvoering op de achtergrond, permissies en de winkeleisen die daarbij horen.","android","framework","logos:android-icon",{"title":11,"description":12,"slug":13,"category":8,"icon":14},"Angular","Het front-endframework waarin we werken als een product al Angular is — een kant-en-klare structuur die past bij grote, langlevende applicaties.","angular","logos:angular-icon",{"title":16,"description":17,"slug":18,"category":19,"icon":20},"Dart","De taal waarin elke Flutter-app die we uitbrengen geschreven is — sound null safety, echte pattern matching, en een compiler voor native en web.","dart","language","logos:dart",{"title":22,"description":23,"slug":24,"category":8,"icon":25},"Django","Het Python-framework dat we pakken als een product op dag één een beheeromgeving, authenticatie en een echt datamodel nodig heeft.","django","logos:django-icon",{"title":27,"description":28,"slug":29,"category":30,"icon":31},"Docker","Elke service die we bouwen verschijnt als container, zodat wat op een laptop, in CI en in productie draait één artefact is en niet drie verschillende.","docker","infrastructure","logos:docker-icon",{"title":33,"description":34,"slug":35,"category":36,"icon":37},"Fastlane","De releaseautomatisering achter ons mobiele werk — ondertekenen, builds en winkeluploads draaien vanuit CI, niet vanaf één laptop.","fastlane","tools","logos:fastlane",{"title":39,"description":40,"slug":41,"category":8,"icon":42},"Flutter","Onze belangrijkste mobiele stack sinds 2018 — één Dart-codebase die uitkomt op iOS, Android, web en desktop zonder een apart team per platform.","flutter","logos:flutter",{"title":44,"description":45,"slug":46,"category":36,"icon":47},"Git","Elk project dat we aanraken leeft in Git — beoordeelde pull requests, CI op elke branch, en een historie die maanden later nog te volgen is.","git","logos:git-icon",{"title":49,"description":50,"slug":51,"category":19,"icon":52},"Go","Onze backendtaal voor realtime-API's en services onder belasting — kleine binaries, snelle builds, en gelijktijdigheid die leesbaar blijft.","go","logos:gopher",{"title":54,"description":55,"slug":56,"category":30,"icon":57},"Google Cloud","De cloud waarop onze productieworkloads draaien — beheerde Kubernetes, opslag en netwerken, zonder handgebouwde servers die niemand wil onderhouden.","gcp","logos:google-cloud",{"title":59,"description":60,"slug":61,"category":36,"icon":62},"Gradle","Het buildsysteem waar elke Android-release doorheen gaat — product flavours, ondertekeningsconfiguraties, en de afhankelijkheden onder een Flutter-app.","gradle","logos:gradle",{"title":64,"description":65,"slug":66,"category":30,"icon":67},"Helm","Hoe we een Kubernetes-uitrol verpakken — service, configuratie, secrets en ingress als één versiebeheerde eenheid die je kunt promoveren en terugdraaien.","helm","logos:helm",{"title":69,"description":70,"slug":71,"category":19,"icon":72},"Kotlin","Waar we naar grijpen als een Flutter-app echt Android eronder nodig heeft — platform channels, achtergrondwerk en integraties met native SDK's.","kotlin","logos:kotlin-icon",{"title":74,"description":75,"slug":76,"category":8,"icon":72},"Kotlin Multiplatform","Bedrijfslogica delen tussen iOS en Android terwijl elk platform zijn eigen native UI houdt — het alternatief wanneer Flutter niet past.","kmp",{"title":78,"description":79,"slug":80,"category":30,"icon":81},"Kubernetes","Hoe we services in productie draaien — Helm-uitrollen, rollouts die terug te draaien zijn, en schalen zonder dat iemand om 3 uur 's nachts wakker hoeft te zijn.","kubernetes","logos:kubernetes",{"title":83,"description":84,"slug":85,"category":30,"icon":86},"NATS","Lichtgewicht berichtenverkeer tussen services — publish\u002Fsubscribe en request\u002Freply zonder het beheergewicht van een volledig brokercluster.","nats","logos:nats-icon",{"title":88,"description":89,"slug":90,"category":8,"icon":91},"Nuxt","Vue met serverrendering, routering en SEO al geregeld — hoe we marketingsites en webapps bouwen die bij het eerste laden snel en vindbaar moeten zijn.","nuxt","logos:nuxt-icon",{"title":93,"description":94,"slug":95,"category":19,"icon":96},"PHP","Waar we met een bestaande PHP-backend werken — hem uitbreiden, ermee integreren, en de mobiele en webclients bouwen die hij moet bedienen.","php","logos:php",{"title":98,"description":99,"slug":100,"category":101,"icon":102},"PostgreSQL","Onze standaarddatabase — waar we naar grijpen tenzij een product een specifieke reden geeft om dat niet te doen, van schema tot indexen.","postgres","database","logos:postgresql",{"title":104,"description":105,"slug":106,"category":19,"icon":107},"Python","Onze taal voor backends, datawerk en AI-integraties — inclusief de Python-bindings die we bij onze eigen Rust-tooling leveren.","python","logos:python",{"title":109,"description":110,"slug":111,"category":8,"icon":112},"React","De front-endbibliotheek waarin we werken als een product al React is — componenten, hooks, en het ecosysteem dat eromheen is gegroeid.","react","logos:react",{"title":114,"description":115,"slug":116,"category":101,"icon":117},"Redis","Waar we data neerzetten die snel moet zijn en opnieuw op te bouwen valt — caches, sessies, verzoeklimieten en achtergrondwachtrijen.","redis","logos:redis",{"title":119,"description":120,"slug":121,"category":19,"icon":122},"Ruby","De taal waarin onze mobiele releaseautomatisering geschreven is — Fastlane-lanes, eigen acties, en de CI-lijm die builds naar de winkels brengt.","ruby","logos:ruby",{"title":124,"description":125,"slug":126,"category":19,"icon":127},"Rust","Waar we heen gaan als prestaties én correctheid allebei tellen — documenten renderen, CLI-tooling, en services die snel en voorspelbaar moeten blijven.","rust","simple-icons:rust",{"title":129,"description":130,"slug":131,"category":101,"icon":132},"SQLite","De database die meereist in de app — lokale caches, offline-first opslag, en alles wat ook zonder netwerk moet blijven werken.","sqlite","logos:sqlite",{"title":134,"description":135,"slug":136,"category":8,"icon":137},"Strapi","Een headless CMS waar we naar grijpen als redacteuren de content moeten beheren — een echte beheeromgeving en een schone API, zonder ze zelf te bouwen.","strapi","logos:strapi-icon",{"title":139,"description":140,"slug":141,"category":19,"icon":142},"Swift","Waar we naar grijpen als een Flutter-app echt iOS eronder nodig heeft — platform channels, integraties met native SDK's, widgets en App Clips.","swift","logos:swift",{"title":144,"description":145,"slug":146,"category":8,"icon":142},"SwiftUI","Het UI-framework van Apple, waar we de native oppervlakken bouwen die een Flutter-app niet kan afdekken — widgets, App Clips, extensies en systeemintegraties.","swiftui",{"title":148,"description":149,"slug":150,"category":8,"icon":151},"Tailwind CSS","Hoe we elke front-end die we bouwen vormgeven — utility-klassen en designtokens in plaats van een stylesheet die alleen maar groeit en nooit wordt opgeschoond.","tailwind","logos:tailwindcss-icon",{"title":153,"description":154,"slug":155,"category":19,"icon":156},"TypeScript","De standaard voor alles wat we voor de browser en voor Node-services schrijven — types die integratiebugs vangen voordat ze een review halen.","typescript","logos:typescript-icon",{"title":158,"description":159,"slug":160,"category":8,"icon":161},"Vue","Ons front-endframework voor beheerpanelen, dashboards voor winkeliers en webapps die jaren onderhoudbaar moeten blijven, geen sprints.","vue","logos:vue",{"title":163,"description":164,"slug":165,"category":36,"icon":166},"Whisper","Het opensource spraakherkenningsmodel van OpenAI — de transcriptiemotor achter spraakinvoer, draaiend als backendservice én op het toestel in de app.","whisper","simple-icons:openai",{"id":168,"title":169,"author":170,"body":171,"description":177,"extension":2502,"hero":2503,"meta":2506,"metaDescription":2507,"metaTitle":2508,"navigation":360,"ogDescription":2509,"path":2510,"seo":2511,"slug":2512,"stem":2513,"summary":2514,"timestamp":2515,"topic":41,"updated":2516,"__hash__":2517},"blog_nl\u002Fblog\u002Fflutter-golden-tests.md","Je golden tests slagen om de verkeerde reden","maxim",{"type":172,"value":173,"toc":2482},"minimark",[174,178,181,189,198,203,274,278,281,427,443,446,469,479,483,488,498,505,831,842,849,853,870,881,885,926,929,933,955,985,992,1001,1115,1118,1126,1130,1133,1151,1160,1166,1218,1221,1231,1757,1787,1796,1800,1803,1880,1883,1886,1906,1909,1913,1916,1929,1935,2165,2180,2186,2190,2193,2196,2237,2243,2247,2254,2266,2270,2273,2276,2280,2441,2445,2449,2462,2478],[175,176,177],"p",{},"De laatste golden-suite die we overnamen bevatte 214 afbeeldingen. Alles groen, ongeveer acht maanden lang, over een paar honderd pull requests. Tot iemand een van die PNG's opende om te zien wat hij beschermde.",[175,179,180],{},"Elk glyph was een zwarte rechthoek.",[175,182,183,184,188],{},"De suite was geschreven door de eerste de beste tutorial te kopiëren, en dat is geen verwijt: de tutorial werkt, in de zin dat ",[185,186,187],"code",{},"flutter test --update-goldens"," bestanden oplevert en de volgende run die ermee vergelijkt. Alleen vergelijkt hij het verkeerde: een layout van gevulde blokken, gerenderd met schaduwen platgeslagen tot harde blokken, op een machine die niemand had vastgepind. Verwissel een woord voor een ander van dezelfde lengte, verander de fontfamilie, en de pixels bewegen niet. De suite mat niet de interface. Hij mat dat er niets was verschoven.",[175,190,191,192,197],{},"Dat is in één voorbeeld het hele probleem met ",[193,194,196],"term",{"slug":195},"golden-test","golden tests",": ze zijn tegelijk het goedkoopste regressienet in Flutter en de overtuigendste vervalsing. Een groen vinkje ziet er in beide gevallen identiek uit.",[199,200,202],"h2",{"id":201},"in-het-kort","In het kort",[204,205,206,214,220,230,236,245,258,264],"ul",{},[207,208,209,213],"li",{},[210,211,212],"strong",{},"Een golden is een momentopname van de render tree op één logische maat, in één thema, één taal en één tekstschaal — en niets meer dan dat."," Geen screenshot van je app op een telefoon.",[207,215,216,219],{},[210,217,218],{},"De testomgeving levert geen app-fonts mee."," Tekst zonder beschikbare familie wordt als gevulde blokken getekend, bewust, zodat de layout deterministisch blijft. Zonder fonts te laden golden-test je geometrie en noem je dat een visuele test.",[207,221,222,225,226,229],{},[210,223,224],{},"Schaduwen worden platgeslagen tot harde blokken"," (",[185,227,228],{},"debugDisableShadows","): het framework laat de blur weg. Elevatie staat nog in het beeld, maar blurradius, spread en gradient niet — dus schaduwregressies zijn onzichtbaar.",[207,231,232,235],{},[210,233,234],{},"Pixels hangen af van de engine, het besturingssysteem van de host en de Flutter-versie."," Een suite die op macOS is gegenereerd faalt op Linux in CI. Pin de toolchain en genereer op het platform waar CI draait.",[207,237,238,244],{},[210,239,240,243],{},[185,241,242],{},"tester.view"," is het oppervlak, niet het toestel."," Het standaard testoppervlak is 800×600 logische pixels bij een pixelratio van 3; zonder expliciete maat wordt elk telefoonscherm gerenderd op een resolutie die geen gebruiker heeft.",[207,246,247,253,254,257],{},[210,248,249,252],{},[185,250,251],{},"pumpAndSettle"," loopt vast op een spinner"," — een onbepaalde voortgangsindicator plant eindeloos frames in. Laadtoestanden hebben ",[185,255,256],{},"pump(duration)"," nodig.",[207,259,260,263],{},[210,261,262],{},"De matrix vermenigvuldigt."," 12 schermen × 2 thema's × 3 breedtes × 2 tekstschalen × 2 talen is 288 afbeeldingen, allemaal opnieuw gegenereerd bij elke Flutter-update. Kies de assen die je echt gaat reviewen.",[207,265,266,269,270,273],{},[210,267,268],{},"De waarde van een golden-suite is de kans dat iemand naar de diff kijkt."," Al het andere is leidingwerk. Een ",[185,271,272],{},"--update-goldens","-commit die niemand bekeek verandert een test in een lockfile.",[199,275,277],{"id":276},"wat-een-golden-test-echt-doet","Wat een golden test echt doet",[175,279,280],{},"Eén test, één afbeelding, één vergelijking:",[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,396,411,419],{"__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,382,385,387,390,393],{"class":292,"line":375},5,[290,377,378],{"class":300},"    find",[290,380,381],{"class":308},".",[290,383,384],{"class":296},"byType",[290,386,301],{"class":300},[290,388,389],{"class":340},"CartScreen",[290,391,392],{"class":300},")",[290,394,395],{"class":308},",\n",[290,397,399,402,404,407,409],{"class":292,"line":398},6,[290,400,401],{"class":296},"    matchesGoldenFile",[290,403,301],{"class":300},[290,405,406],{"class":304},"'goldens\u002Fcart_empty.light.390.ts1.0.en.png'",[290,408,392],{"class":300},[290,410,395],{"class":308},[290,412,414,417],{"class":292,"line":413},7,[290,415,416],{"class":300},"  )",[290,418,354],{"class":308},[290,420,422,425],{"class":292,"line":421},8,[290,423,424],{"class":300},"})",[290,426,354],{"class":308},[175,428,429,431,432,435,436,439,440,381],{},[185,430,187],{}," schrijft die PNG en slaagt. Elke volgende run decodeert hem en vergelijkt hem pixel voor pixel met wat de engine zojuist heeft gerenderd. Geen tolerantie, geen perceptuele hash — exacte gelijkheid, tot je een eigen ",[185,433,434],{},"goldenFileComparator"," installeert. Bij een fout schrijft de standaardcomparator vier artefacten in een map ",[185,437,438],{},"failures\u002F"," naast het testbestand: de referentie, de nieuwe render, een geïsoleerde diff en een gemaskeerde diff. (Bij een maatverschil schrijft hij alleen de eerste twee.) Dat is jouw diffreview, en die horen in ",[185,441,442],{},".gitignore",[175,444,445],{},"Twee details in dat fragment zijn belangrijker dan ze lijken.",[175,447,448,457,458,461,462,465,466,468],{},[210,449,450,453,454,456],{},[185,451,452],{},"find.byType(CartScreen)"," legt ",[185,455,389],{}," niet vast."," De matcher loopt vanaf het gevonden element ",[210,459,460],{},"omhoog"," naar de dichtstbijzijnde ",[185,463,464],{},"RepaintBoundary","-voorouder en legt die laag vast. Meestal is die voorouder de hele app, en daarom zijn goldens zo vaak een afbeelding van het hele venster met de debugbanner in een hoek. Wikkel het onderwerp in een ",[185,467,464],{}," en je legt precies vast wat je bedoelde.",[175,470,471,474,475,478],{},[210,472,473],{},"De gerenderde afbeelding is je app niet."," Het is ",[185,476,477],{},"flutter_tester"," op je host: geen systeemfonts, geen systeem-UI, geen toetsenbord, een gemockt netwerk dat alleen 400's teruggeeft, schaduwen teruggebracht tot harde blokken, en het oppervlak dat de test toevallig had. Een golden die slaagt zegt dat deze tree, op deze maat, met deze invoer, niet is veranderd. Dat is echt waardevol — en veel smaller dan \"de interface werkt\".",[199,480,482],{"id":481},"de-vijf-redenen-waarom-je-eerste-golden-tegen-je-liegt","De vijf redenen waarom je eerste golden tegen je liegt",[484,485,487],"h3",{"id":486},"_1-er-zijn-geen-fonts-dus-elk-glyph-is-een-blok","1. Er zijn geen fonts, dus elk glyph is een blok",[175,489,490,493,494,497],{},[185,491,492],{},"flutter test"," draait zonder de fonts uit je ",[185,495,496],{},"pubspec.yaml",". Tekst zonder beschikbare familie valt terug op een testfont waarvan elk glyph een gevulde rechthoek is: een ontwerp dat de layout deterministisch maakt en fontbestanden overbodig, en dat betekent dat de PNG in je repository geen enkele leesbare letter bevat.",[175,499,500,501,504],{},"De oplossing is ",[185,502,503],{},"test\u002Fflutter_test_config.dart",", dat Flutter uitvoert vóór de suites in die map. Alles wat je daar declareert — fonts laden, een eigen comparator, de klok instellen — geldt voor elke test eronder:",[282,506,508],{"className":284,"code":507,"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,509,510,516,527,536,540,578,590,601,606,611,632,638,644,650,656,662,685,710,733,738,744,763,785,790,826],{"__ignoreMap":286},[290,511,512],{"class":292,"line":293},[290,513,515],{"class":514},"sutJx","\u002F\u002F test\u002Fflutter_test_config.dart\n",[290,517,518,522,525],{"class":292,"line":322},[290,519,521],{"class":520},"sw1J6","import",[290,523,524],{"class":304}," 'dart:async'",[290,526,354],{"class":308},[290,528,529,531,534],{"class":292,"line":357},[290,530,521],{"class":520},[290,532,533],{"class":304}," 'package:flutter\u002Fservices.dart'",[290,535,354],{"class":308},[290,537,538],{"class":292,"line":364},[290,539,361],{"emptyLinePlaceholder":360},[290,541,542,545,548,551,554,557,559,562,564,566,568,571,574,576],{"class":292,"line":375},[290,543,544],{"class":340},"Future",[290,546,547],{"class":300},"\u003C",[290,549,550],{"class":336},"void",[290,552,553],{"class":300},"> ",[290,555,556],{"class":296},"testExecutable",[290,558,301],{"class":300},[290,560,561],{"class":340},"FutureOr",[290,563,547],{"class":300},[290,565,550],{"class":336},[290,567,553],{"class":300},[290,569,570],{"class":340},"Function",[290,572,573],{"class":300},"() testMain) ",[290,575,316],{"class":315},[290,577,319],{"class":300},[290,579,580,582,585,588],{"class":292,"line":398},[290,581,325],{"class":315},[290,583,584],{"class":296}," _loadFonts",[290,586,587],{"class":300},"()",[290,589,354],{"class":308},[290,591,592,594,597,599],{"class":292,"line":413},[290,593,325],{"class":315},[290,595,596],{"class":296}," testMain",[290,598,587],{"class":300},[290,600,354],{"class":308},[290,602,603],{"class":292,"line":421},[290,604,605],{"class":300},"}\n",[290,607,609],{"class":292,"line":608},9,[290,610,361],{"emptyLinePlaceholder":360},[290,612,614,616,618,620,622,625,628,630],{"class":292,"line":613},10,[290,615,544],{"class":340},[290,617,547],{"class":300},[290,619,550],{"class":336},[290,621,553],{"class":300},[290,623,624],{"class":296},"_loadFonts",[290,626,627],{"class":300},"() ",[290,629,316],{"class":315},[290,631,319],{"class":300},[290,633,635],{"class":292,"line":634},11,[290,636,637],{"class":514},"  \u002F\u002F The family name must match what the theme asks for. If `fontFamily` is\n",[290,639,641],{"class":292,"line":640},12,[290,642,643],{"class":514},"  \u002F\u002F null, register the same faces as 'Roboto' — that is what Material falls\n",[290,645,647],{"class":292,"line":646},13,[290,648,649],{"class":514},"  \u002F\u002F back to, and a mismatch loads nothing and fails silently.\n",[290,651,653],{"class":292,"line":652},14,[290,654,655],{"class":514},"  \u002F\u002F Both faces are declared under `assets:` in pubspec.yaml, and `flutter test`\n",[290,657,659],{"class":292,"line":658},15,[290,660,661],{"class":514},"  \u002F\u002F can load them from there — a pure `dart test` cannot.\n",[290,663,665,668,671,674,677,679,682],{"class":292,"line":664},16,[290,666,667],{"class":336},"  final",[290,669,670],{"class":300}," text ",[290,672,673],{"class":347},"=",[290,675,676],{"class":340}," FontLoader",[290,678,301],{"class":300},[290,680,681],{"class":304},"'Roboto'",[290,683,684],{"class":300},")\n",[290,686,688,691,694,697,699,702,704,707],{"class":292,"line":687},17,[290,689,690],{"class":308},"    ..",[290,692,693],{"class":296},"addFont",[290,695,696],{"class":300},"(rootBundle",[290,698,381],{"class":308},[290,700,701],{"class":296},"load",[290,703,301],{"class":300},[290,705,706],{"class":304},"'assets\u002Ffonts\u002FRoboto-Regular.ttf'",[290,708,709],{"class":300},"))\n",[290,711,713,715,717,719,721,723,725,728,731],{"class":292,"line":712},18,[290,714,690],{"class":308},[290,716,693],{"class":296},[290,718,696],{"class":300},[290,720,381],{"class":308},[290,722,701],{"class":296},[290,724,301],{"class":300},[290,726,727],{"class":304},"'assets\u002Ffonts\u002FRoboto-Medium.ttf'",[290,729,730],{"class":300},"))",[290,732,354],{"class":308},[290,734,736],{"class":292,"line":735},19,[290,737,361],{"emptyLinePlaceholder":360},[290,739,741],{"class":292,"line":740},20,[290,742,743],{"class":514},"  \u002F\u002F Icons are fonts too. Skip this and every `Icon` in every golden is a box.\n",[290,745,747,749,752,754,756,758,761],{"class":292,"line":746},21,[290,748,667],{"class":336},[290,750,751],{"class":300}," icons ",[290,753,673],{"class":347},[290,755,676],{"class":340},[290,757,301],{"class":300},[290,759,760],{"class":304},"'MaterialIcons'",[290,762,684],{"class":300},[290,764,766,768,770,772,774,776,778,781,783],{"class":292,"line":765},22,[290,767,690],{"class":308},[290,769,693],{"class":296},[290,771,696],{"class":300},[290,773,381],{"class":308},[290,775,701],{"class":296},[290,777,301],{"class":300},[290,779,780],{"class":304},"'assets\u002Ffonts\u002FMaterialIcons-Regular.otf'",[290,782,730],{"class":300},[290,784,354],{"class":308},[290,786,788],{"class":292,"line":787},23,[290,789,361],{"emptyLinePlaceholder":360},[290,791,793,795,798,800,803,806,808,810,812,814,817,819,821,824],{"class":292,"line":792},24,[290,794,325],{"class":315},[290,796,797],{"class":340}," Future",[290,799,381],{"class":308},[290,801,802],{"class":296},"wait",[290,804,805],{"class":300},"([text",[290,807,381],{"class":308},[290,809,701],{"class":296},[290,811,587],{"class":300},[290,813,309],{"class":308},[290,815,816],{"class":300}," icons",[290,818,381],{"class":308},[290,820,701],{"class":296},[290,822,823],{"class":300},"()])",[290,825,354],{"class":308},[290,827,829],{"class":292,"line":828},25,[290,830,605],{"class":300},[175,832,833,834,837,838,841],{},"Twee dingen falen hier stil in plaats van luid. Een verkeerde familienaam laadt een font dat geen enkel widget opvraagt, dus er verandert niets. En ",[185,835,836],{},"google_fonts"," haalt zijn snitten tijdens runtime op, wat de testomgeving niet doet: zet de ",[185,839,840],{},".ttf","-bestanden die je echt meelevert in de repository, anders wachten je goldens voor altijd op een netwerkoproep die nooit komt.",[175,843,844,845,848],{},"Er is een subtieler gevolg, en dat is degene die teams raakt die deze sectie overslaan: ",[210,846,847],{},"met blokglyphs kun je niet zien wat de tekst zegt, en elke eigenschap die het testfont normaliseert verdwijnt."," Familie, gewicht, kerning, glyphvorm en de echte ascender en descender van het font worden vervangen door identieke vierkanten, dus een overstap van Roboto naar Inter tekent dezelfde blokken, en een copywijziging met evenveel tekens verzet helemaal niets. Het aantal tekens en de fontgrootte verzetten de blokken wél — een langere Russische string levert een bredere balk op — dus overloop en formaatwijzigingen zijn niet onzichtbaar. Alleen de dingen die tekst in productie leesbaar maken zijn dat.",[484,850,852],{"id":851},"_2-schaduwen-worden-platgeslagen-dus-blur-bestaat-niet","2. Schaduwen worden platgeslagen, dus blur bestaat niet",[175,854,855,857,858,861,862,865,866,869],{},[185,856,228],{}," verwijdert geen schaduwen; het ",[210,859,860],{},"vervangt ze door massieve blokken",". ",[185,863,864],{},"BoxShadow.toPaint()"," laat zijn maskfilter vallen, en een Material-oppervlak tekent zijn elevatie als een harde contour (",[185,867,868],{},"strokeWidth: elevation * 2","), dus wat in de golden belandt is een niet-vervaagde vorm in de schaduwkleur. De testbinding zet hem aan omdat schaduwrendering niet gegarandeerd pixel-identiek is van versie tot versie, of zelfs van run tot run.",[175,871,872,873,876,877,880],{},"Het gevolg is smaller dan \"geen schaduwen\" en misleidender: de kaart staat er nog, maar zijn blurradius, spread en gradient niet, dus een schaduwregressie — een blur die verdubbelde, een spread die de layout brak — rendert identiek in de golden. Flutters eigen frameworktests zetten ",[185,874,875],{},"debugDisableShadows = false"," als ze specifiek schaduwen testen, en zetten hem aan het eind van de test terug; de binding controleert na elke test dat de flag weer op zijn standaardwaarde staat (",[185,878,879],{},"true","), de waarde die de rest van de suite gebruikt. Dat is de juiste afweging: zet echte schaduwen alleen aan waar de schaduw het onderwerp van de test is, accepteer daar de instabiliteit en laat de flag verder met rust.",[484,882,884],{"id":883},"_3-elke-netwerkafbeelding-is-een-placeholder","3. Elke netwerkafbeelding is een placeholder",[175,886,887,890,891,894,895,898,899,902,903,906,907,909,910,913,914,917,918,921,922,925],{},[185,888,889],{},"Image.network"," komt in een test bij een gemockte ",[185,892,893],{},"HttpClient"," die een lege 400 teruggeeft. Wat er daarna gebeurt hangt van het widget af: zonder ",[185,896,897],{},"errorBuilder"," gooit het ",[185,900,901],{},"Image","-widget de laadfout in debug opnieuw en faalt de test met een ",[185,904,905],{},"NetworkImageLoadException","; met een ",[185,908,897],{}," legt de golden stil jouw placeholder vast. ",[185,911,912],{},"cached_network_image"," en soortgenoten hebben hun eigen dubbelganger nodig. Of je wikkelt de aanroepen in ",[185,915,916],{},"HttpOverrides"," (zo doen oudere recepten het), of je doet wat een refactor overleeft: neem een ",[185,919,920],{},"ImageProvider"," op in het widget en geef die in de test een ",[185,923,924],{},"MemoryImage"," van een PNG van 1×1.",[175,927,928],{},"Avatars en productfoto's vormen het grootste deel van het visuele gewicht van een echt scherm, dus een suite waarin ze allemaal placeholders zijn test de layout rond de plaatjes en niets van de plaatjes zelf.",[484,930,932],{"id":931},"_4-de-klok-de-taal-en-het-toeval-zijn-niet-van-jou","4. De klok, de taal en het toeval zijn niet van jou",[175,934,935,938,939,942,943,946,947,950,951,954],{},[185,936,937],{},"DateTime.now()"," in een widget, ",[185,940,941],{},"intl","'s ",[185,944,945],{},"DateFormat"," zonder vaste datum, een ",[185,948,949],{},"Random"," zonder seed, een ",[185,952,953],{},"uuid"," die tijdens de build wordt gegenereerd — elk ervan levert een golden op die vandaag slaagt en op de tweede dinsdag van een of andere maand faalt, of alleen in CI faalt waar de taal van de machine anders is.",[175,956,957,958,961,962,965,966,969,970,973,974,977,978,981,982,984],{},"De oplossing zit niet in de test maar in het widget: neem een ",[185,959,960],{},"now","-functie of een ",[185,963,964],{},"Clock",", neem een ",[185,967,968],{},"Locale",", neem een seed. Dat is sowieso de moeite waard — een widget die rechtstreeks de systeemklok leest kun je op geen enkele laag testen, golden of anders. Zet ",[185,971,972],{},"Intl.defaultLocale"," expliciet in ",[185,975,976],{},"flutter_test_config.dart"," en je haalt een hele klasse van \"werkt op mijn machine, verkeerde taal in CI\"-fouten weg; voor datums die niet in het Engels zijn, roep je ook ",[185,979,980],{},"initializeDateFormatting(locale)"," aan, want ",[185,983,945],{}," gooit een fout voor een taal waarvoor het geen data heeft.",[484,986,988,989,991],{"id":987},"_5-pumpandsettle-komt-nooit-terug-bij-een-laadtoestand","5. ",[185,990,251],{}," komt nooit terug bij een laadtoestand",[175,993,994,996,997,1000],{},[185,995,251],{}," pompt tot er geen frame meer gepland staat. Een onbepaalde ",[185,998,999],{},"CircularProgressIndicator"," plant frames zolang hij draait, dus de aanroep loopt in een timeout met het beroemde \"pumpAndSettle timed out\", en de golden van de laadtoestand — de toestand waar QA nooit naar kijkt omdat je de app er niet in krijgt — is precies degene die je niet kunt schrijven.",[282,1002,1004],{"className":284,"code":1003,"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,1005,1006,1011,1016,1020,1045,1082],{"__ignoreMap":286},[290,1007,1008],{"class":292,"line":293},[290,1009,1010],{"class":514},"\u002F\u002F Not this: it waits for an animation that never ends.\n",[290,1012,1013],{"class":292,"line":322},[290,1014,1015],{"class":514},"\u002F\u002F await tester.pumpAndSettle();\n",[290,1017,1018],{"class":292,"line":357},[290,1019,361],{"emptyLinePlaceholder":360},[290,1021,1022,1025,1027,1029,1031,1033,1035,1037,1040,1043],{"class":292,"line":364},[290,1023,1024],{"class":315},"await",[290,1026,328],{"class":296},[290,1028,331],{"class":300},[290,1030,309],{"class":308},[290,1032,337],{"class":336},[290,1034,341],{"class":340},[290,1036,381],{"class":308},[290,1038,1039],{"class":296},"loading",[290,1041,1042],{"class":300},"())",[290,1044,354],{"class":308},[290,1046,1047,1049,1052,1054,1057,1059,1062,1065,1068,1070,1074,1076,1079],{"class":292,"line":375},[290,1048,1024],{"class":315},[290,1050,1051],{"class":300}," tester",[290,1053,381],{"class":308},[290,1055,1056],{"class":296},"pump",[290,1058,301],{"class":300},[290,1060,1061],{"class":336},"const",[290,1063,1064],{"class":340}," Duration",[290,1066,1067],{"class":300},"(milliseconds",[290,1069,348],{"class":347},[290,1071,1073],{"class":1072},"srdBf"," 100",[290,1075,730],{"class":300},[290,1077,1078],{"class":308},";",[290,1080,1081],{"class":514}," \u002F\u002F one fixed phase of the spinner\n",[290,1083,1084,1086,1088,1091,1093,1095,1097,1099,1101,1103,1106,1108,1111,1113],{"class":292,"line":398},[290,1085,1024],{"class":315},[290,1087,369],{"class":296},[290,1089,1090],{"class":300},"(find",[290,1092,381],{"class":308},[290,1094,384],{"class":296},[290,1096,301],{"class":300},[290,1098,389],{"class":340},[290,1100,392],{"class":300},[290,1102,309],{"class":308},[290,1104,1105],{"class":296}," matchesGoldenFile",[290,1107,301],{"class":300},[290,1109,1110],{"class":304},"'goldens\u002Fcart_loading.light.390.ts1.0.en.png'",[290,1112,730],{"class":300},[290,1114,354],{"class":308},[175,1116,1117],{},"De golden legt de spinner nu op 100 ms vast, consistent, voor altijd. Dat is prima: je test dat de layout eromheen overeind blijft terwijl er iets onbepaalds draait, niet de spinner zelf.",[1119,1120,1123],"info-box",{":leading-icon":1121,":title":1122},"lucide:alert-triangle","Een groene suite met 214 bestanden is geen sterke suite",[175,1124,1125],{},"Alles hierboven geeft hetzelfde symptoom: tests die slagen terwijl ze minder controleren dan je denkt. Niets ervan komt naar voren als een fout, een waarschuwing of een dalende dekking. Een golden-suite is ongewoon omdat zijn faalmodus stilte is, dus de enige manier om hem te vertrouwen is een PNG openen en controleren of er in staat wat je denkt dat erin staat. Doe dat één keer, op dag één, voor elk apart soort screenshot in de suite. Het kost tien minuten en het zijn de meest waardevolle tien minuten van de hele opzet.",[199,1127,1129],{"id":1128},"de-opzet-die-goldens-reproduceerbaar-maakt","De opzet die goldens reproduceerbaar maakt",[175,1131,1132],{},"Vier beslissingen, allemaal saai, allemaal één keer genomen:",[175,1134,1135,1138,1139,1142,1143,1146,1147,1150],{},[210,1136,1137],{},"Pin de toolchain."," Een ",[185,1140,1141],{},".fvmrc"," in de repository, en CI leest hetzelfde bestand. Een Flutter-update verandert tekstrendering, standaardthema's, de hernoemingen van ",[185,1144,1145],{},"CardTheme"," naar ",[185,1148,1149],{},"CardThemeData"," en het tekenen van schaduwen, dus elke golden in de repository verandert tegelijk. Dat is geen reden om niet te updaten; het is een reden om van de update een bewuste commit te maken die niets anders bevat dan opnieuw gegenereerde goldens, en dat in het bericht zegt.",[175,1152,1153,1156,1157,1159],{},[210,1154,1155],{},"Genereer op het platform waar CI draait."," Antialiasing en hinting van tekst verschillen tussen macOS en Linux: hetzelfde widget op dezelfde maat levert andere pixels op. Een suite die op de Mac van een ontwikkelaar is gegenereerd faalt op een Linux-runner, en de gebruikelijke \"oplossing\" is een ",[185,1158,272],{}," op een plek waar hij niet hoort. Draai goldens lokaal ook op Linux, in dezelfde container als CI als het team gemengd is.",[175,1161,1162,1165],{},[210,1163,1164],{},"Laat CI nooit regenereren."," De bewaking is één regel:",[282,1167,1171],{"className":1168,"code":1169,"language":1170,"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,1172,1173,1178,1183,1188,1193,1198,1203,1208,1213],{"__ignoreMap":286},[290,1174,1175],{"class":292,"line":293},[290,1176,1177],{},"- uses: subosito\u002Fflutter-action@v2\n",[290,1179,1180],{"class":292,"line":322},[290,1181,1182],{},"  with:\n",[290,1184,1185],{"class":292,"line":357},[290,1186,1187],{},"    flutter-version-file: .fvmrc   # one version, repo and CI\n",[290,1189,1190],{"class":292,"line":364},[290,1191,1192],{},"    cache: true\n",[290,1194,1195],{"class":292,"line":375},[290,1196,1197],{},"- run: flutter test\n",[290,1199,1200],{"class":292,"line":398},[290,1201,1202],{},"# A \"helpful\" --update-goldens in CI or a stale golden written by a bad merge\n",[290,1204,1205],{"class":292,"line":413},[290,1206,1207],{},"# would make the suite green while comparing nothing. Fail instead. `git diff`\n",[290,1209,1210],{"class":292,"line":421},[290,1211,1212],{},"# alone misses untracked files, which is exactly what a regenerated golden is.\n",[290,1214,1215],{"class":292,"line":608},[290,1216,1217],{},"- run: test -z \"$(git status --porcelain -- '*.png')\"\n",[175,1219,1220],{},"Die laatste regel is geen paranoia. De meest voorkomende manier waarop een golden-suite ophoudt iets te controleren is een CI-stap, om twee uur 's nachts toegevoegd tijdens een rode build, die goldens bijwerkt in plaats van te falen.",[175,1222,1223,1226,1227,1230],{},[210,1224,1225],{},"Zet het oppervlak expliciet."," Het standaard testoppervlak is 800×600 logische pixels bij een pixelratio van 3. Test daar een telefoonscherm op en je krijgt de layout van een kleine tablet, terwijl de ",[185,1228,1229],{},"MediaQuery"," van de app het tegendeel beweert. Eén helper lost dat voor elke test op, en als je de naam van een golden niet kunt lezen en de vijf assen er niet uit kunt halen, is de naam fout:",[282,1232,1234],{"className":284,"code":1233,"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,1235,1236,1241,1256,1265,1277,1309,1324,1348,1368,1377,1407,1426,1447,1451,1464,1471,1484,1523,1535,1540,1545,1562,1578,1590,1602,1620,1626,1632,1657,1678,1686,1698,1706,1714,1722,1730,1737,1752],{"__ignoreMap":286},[290,1237,1238],{"class":292,"line":293},[290,1239,1240],{"class":514},"\u002F\u002F test\u002Fhelpers\u002Fgolden.dart\n",[290,1242,1243,1245,1247,1249,1251,1254],{"class":292,"line":322},[290,1244,544],{"class":340},[290,1246,547],{"class":300},[290,1248,550],{"class":336},[290,1250,553],{"class":300},[290,1252,1253],{"class":296},"pumpScreen",[290,1255,372],{"class":300},[290,1257,1258,1261,1263],{"class":292,"line":357},[290,1259,1260],{"class":340},"  WidgetTester",[290,1262,1051],{"class":300},[290,1264,395],{"class":308},[290,1266,1267,1270,1273,1275],{"class":292,"line":364},[290,1268,1269],{"class":340},"  Widget",[290,1271,1272],{"class":300}," child",[290,1274,309],{"class":308},[290,1276,319],{"class":300},[290,1278,1279,1282,1285,1287,1289,1292,1294,1297,1299,1302,1304,1306],{"class":292,"line":375},[290,1280,1281],{"class":340},"  Size",[290,1283,1284],{"class":300}," size ",[290,1286,673],{"class":347},[290,1288,337],{"class":336},[290,1290,1291],{"class":340}," Size",[290,1293,301],{"class":300},[290,1295,1296],{"class":1072},"390",[290,1298,309],{"class":308},[290,1300,1301],{"class":1072}," 844",[290,1303,392],{"class":300},[290,1305,309],{"class":308},[290,1307,1308],{"class":514}," \u002F\u002F iPhone 14 — pick your own baselines\n",[290,1310,1311,1314,1317,1319,1322],{"class":292,"line":398},[290,1312,1313],{"class":340},"  double",[290,1315,1316],{"class":300}," textScale ",[290,1318,673],{"class":347},[290,1320,1321],{"class":1072}," 1",[290,1323,395],{"class":308},[290,1325,1326,1329,1332,1334,1336,1339,1341,1344,1346],{"class":292,"line":413},[290,1327,1328],{"class":340},"  Locale",[290,1330,1331],{"class":300}," locale ",[290,1333,673],{"class":347},[290,1335,337],{"class":336},[290,1337,1338],{"class":340}," Locale",[290,1340,301],{"class":300},[290,1342,1343],{"class":304},"'en'",[290,1345,392],{"class":300},[290,1347,395],{"class":308},[290,1349,1350,1353,1356,1358,1361,1363,1366],{"class":292,"line":421},[290,1351,1352],{"class":340},"  Brightness",[290,1354,1355],{"class":300}," brightness ",[290,1357,673],{"class":347},[290,1359,1360],{"class":340}," Brightness",[290,1362,381],{"class":308},[290,1364,1365],{"class":300},"light",[290,1367,395],{"class":308},[290,1369,1370,1373,1375],{"class":292,"line":608},[290,1371,1372],{"class":300},"}) ",[290,1374,316],{"class":315},[290,1376,319],{"class":300},[290,1378,1379,1382,1384,1387,1389,1392,1394,1396,1399,1402,1404],{"class":292,"line":613},[290,1380,1381],{"class":300},"  tester",[290,1383,381],{"class":308},[290,1385,1386],{"class":300},"view",[290,1388,381],{"class":308},[290,1390,1391],{"class":300},"physicalSize ",[290,1393,673],{"class":347},[290,1395,1284],{"class":300},[290,1397,1398],{"class":347},"*",[290,1400,1401],{"class":1072}," 3",[290,1403,1078],{"class":308},[290,1405,1406],{"class":514},"   \u002F\u002F logical size × device pixel ratio\n",[290,1408,1409,1411,1413,1415,1417,1420,1422,1424],{"class":292,"line":634},[290,1410,1381],{"class":300},[290,1412,381],{"class":308},[290,1414,1386],{"class":300},[290,1416,381],{"class":308},[290,1418,1419],{"class":300},"devicePixelRatio ",[290,1421,673],{"class":347},[290,1423,1401],{"class":1072},[290,1425,354],{"class":308},[290,1427,1428,1431,1433,1435,1437,1439,1442,1444],{"class":292,"line":640},[290,1429,1430],{"class":296},"  addTearDown",[290,1432,331],{"class":300},[290,1434,381],{"class":308},[290,1436,1386],{"class":300},[290,1438,381],{"class":308},[290,1440,1441],{"class":300},"reset)",[290,1443,1078],{"class":308},[290,1445,1446],{"class":514},"        \u002F\u002F never leak a view into the next test\n",[290,1448,1449],{"class":292,"line":646},[290,1450,361],{"emptyLinePlaceholder":360},[290,1452,1453,1455,1457,1459,1462],{"class":292,"line":652},[290,1454,325],{"class":315},[290,1456,1051],{"class":300},[290,1458,381],{"class":308},[290,1460,1461],{"class":296},"pumpWidget",[290,1463,372],{"class":300},[290,1465,1466,1469],{"class":292,"line":658},[290,1467,1468],{"class":340},"    MaterialApp",[290,1470,372],{"class":300},[290,1472,1473,1476,1478,1482],{"class":292,"line":664},[290,1474,1475],{"class":300},"      debugShowCheckedModeBanner",[290,1477,348],{"class":347},[290,1479,1481],{"class":1480},"s39Yj"," false",[290,1483,395],{"class":308},[290,1485,1486,1489,1491,1493,1496,1498,1500,1503,1506,1509,1511,1513,1515,1517,1519,1521],{"class":292,"line":687},[290,1487,1488],{"class":300},"      theme",[290,1490,348],{"class":347},[290,1492,1355],{"class":300},[290,1494,1495],{"class":347},"==",[290,1497,1360],{"class":340},[290,1499,381],{"class":308},[290,1501,1502],{"class":300},"dark ",[290,1504,1505],{"class":347},"?",[290,1507,1508],{"class":340}," AppTheme",[290,1510,381],{"class":308},[290,1512,1502],{"class":300},[290,1514,348],{"class":347},[290,1516,1508],{"class":340},[290,1518,381],{"class":308},[290,1520,1365],{"class":300},[290,1522,395],{"class":308},[290,1524,1525,1528,1530,1533],{"class":292,"line":712},[290,1526,1527],{"class":300},"      locale",[290,1529,348],{"class":347},[290,1531,1532],{"class":300}," locale",[290,1534,395],{"class":308},[290,1536,1537],{"class":292,"line":735},[290,1538,1539],{"class":514},"      \u002F\u002F Without the app's own delegates a locale it does not support resolves\n",[290,1541,1542],{"class":292,"line":740},[290,1543,1544],{"class":514},"      \u002F\u002F back to English.\n",[290,1546,1547,1550,1552,1555,1557,1560],{"class":292,"line":746},[290,1548,1549],{"class":300},"      localizationsDelegates",[290,1551,348],{"class":347},[290,1553,1554],{"class":340}," AppLocalizations",[290,1556,381],{"class":308},[290,1558,1559],{"class":300},"delegates",[290,1561,395],{"class":308},[290,1563,1564,1567,1569,1571,1573,1576],{"class":292,"line":765},[290,1565,1566],{"class":300},"      supportedLocales",[290,1568,348],{"class":347},[290,1570,1554],{"class":340},[290,1572,381],{"class":308},[290,1574,1575],{"class":300},"supportedLocales",[290,1577,395],{"class":308},[290,1579,1580,1583,1585,1588],{"class":292,"line":787},[290,1581,1582],{"class":300},"      home",[290,1584,348],{"class":347},[290,1586,1587],{"class":340}," RepaintBoundary",[290,1589,372],{"class":300},[290,1591,1592,1595,1597,1600],{"class":292,"line":792},[290,1593,1594],{"class":300},"        child",[290,1596,348],{"class":347},[290,1598,1599],{"class":340}," Builder",[290,1601,372],{"class":300},[290,1603,1604,1607,1609,1612,1615,1618],{"class":292,"line":828},[290,1605,1606],{"class":300},"          builder",[290,1608,348],{"class":347},[290,1610,1611],{"class":300}," (context) ",[290,1613,1614],{"class":347},"=>",[290,1616,1617],{"class":340}," MediaQuery",[290,1619,372],{"class":300},[290,1621,1623],{"class":292,"line":1622},26,[290,1624,1625],{"class":514},"            \u002F\u002F Merge, don't replace: a fresh MediaQueryData has size zero,\n",[290,1627,1629],{"class":292,"line":1628},27,[290,1630,1631],{"class":514},"            \u002F\u002F devicePixelRatio 1 and no padding.\n",[290,1633,1635,1638,1640,1642,1644,1647,1650,1652,1655],{"class":292,"line":1634},28,[290,1636,1637],{"class":300},"            data",[290,1639,348],{"class":347},[290,1641,1617],{"class":340},[290,1643,381],{"class":308},[290,1645,1646],{"class":296},"of",[290,1648,1649],{"class":300},"(context)",[290,1651,381],{"class":308},[290,1653,1654],{"class":296},"copyWith",[290,1656,372],{"class":300},[290,1658,1660,1663,1665,1668,1670,1673,1676],{"class":292,"line":1659},29,[290,1661,1662],{"class":300},"              textScaler",[290,1664,348],{"class":347},[290,1666,1667],{"class":340}," TextScaler",[290,1669,381],{"class":308},[290,1671,1672],{"class":296},"linear",[290,1674,1675],{"class":300},"(textScale)",[290,1677,395],{"class":308},[290,1679,1681,1684],{"class":292,"line":1680},30,[290,1682,1683],{"class":300},"            )",[290,1685,395],{"class":308},[290,1687,1689,1692,1694,1696],{"class":292,"line":1688},31,[290,1690,1691],{"class":300},"            child",[290,1693,348],{"class":347},[290,1695,1272],{"class":300},[290,1697,395],{"class":308},[290,1699,1701,1704],{"class":292,"line":1700},32,[290,1702,1703],{"class":300},"          )",[290,1705,395],{"class":308},[290,1707,1709,1712],{"class":292,"line":1708},33,[290,1710,1711],{"class":300},"        )",[290,1713,395],{"class":308},[290,1715,1717,1720],{"class":292,"line":1716},34,[290,1718,1719],{"class":300},"      )",[290,1721,395],{"class":308},[290,1723,1725,1728],{"class":292,"line":1724},35,[290,1726,1727],{"class":300},"    )",[290,1729,395],{"class":308},[290,1731,1733,1735],{"class":292,"line":1732},36,[290,1734,416],{"class":300},[290,1736,354],{"class":308},[290,1738,1740,1742,1744,1746,1748,1750],{"class":292,"line":1739},37,[290,1741,325],{"class":315},[290,1743,1051],{"class":300},[290,1745,381],{"class":308},[290,1747,1056],{"class":296},[290,1749,587],{"class":300},[290,1751,354],{"class":308},[290,1753,1755],{"class":292,"line":1754},38,[290,1756,605],{"class":300},[175,1758,1759,1760,1763,1764,1766,1767,1770,1771,1774,1775,1778,1779,1782,1783,1786],{},"Twee details zijn hier makkelijk fout te doen. ",[185,1761,1762],{},"MediaQueryData(...)"," in plaats van ",[185,1765,1654],{}," bouwt een nieuw object met ",[185,1768,1769],{},"size: Size.zero"," en ",[185,1772,1773],{},"devicePixelRatio: 1",", dus ",[185,1776,1777],{},"MediaQuery.sizeOf"," liegt tegen alles eronder. En een ",[185,1780,1781],{},"MaterialApp"," die je in de helper opnieuw bouwt, zet elke taal die hij niet ondersteunt terug naar Engels tenzij je hem de delegates van de app geeft — waar ",[185,1784,1785],{},"AppLocalizations"," hier voor staat — anders test de taal-as stil Engels.",[175,1788,1789,1792,1793,1795],{},[185,1790,1791],{},"addTearDown(tester.view.reset)"," is niet optioneel. ",[185,1794,242],{}," is globaal voor het testbestand, dus één test die een breedte van 320pt zet lekt door naar elke volgende, en de fout duikt op bij een onschuldige test verderop in hetzelfde bestand.",[199,1797,1799],{"id":1798},"wat-je-golden-moet-maken-en-wat-de-matrix-kost","Wat je golden moet maken, en wat de matrix kost",[175,1801,1802],{},"De valkuil is dat de matrix gratis lijkt. Dat is hij niet: elke afbeelding is een bestand om opnieuw te genereren, een bestand om te reviewen, en een bestand dat een regel toevoegt aan de diff van je volgende losstaande pull request.",[1804,1805,1806,1822],"table",{},[1807,1808,1809],"thead",{},[1810,1811,1812,1816,1819],"tr",{},[1813,1814,1815],"th",{},"Laag",[1813,1817,1818],{},"Wat het dekt",[1813,1820,1821],{},"Afbeeldingen",[1823,1824,1825,1837,1848,1859,1870],"tbody",{},[1810,1826,1827,1831,1834],{},[1828,1829,1830],"td",{},"Componenten van het designsysteem — knop, veld, badge, kaart, list tile",[1828,1832,1833],{},"Elke staat × licht\u002Fdonker, op één breedte",[1828,1835,1836],{},"20–40",[1810,1838,1839,1842,1845],{},[1828,1840,1841],{},"De vijf schermen die ertoe doen",[1828,1843,1844],{},"3 breedtes (320\u002F390\u002F768) × licht\u002Fdonker",[1828,1846,1847],{},"30",[1810,1849,1850,1853,1856],{},[1828,1851,1852],{},"Lege, fout- en laadtoestanden",[1828,1854,1855],{},"De toestanden waar niemand de app voor opent",[1828,1857,1858],{},"6–10",[1810,1860,1861,1864,1867],{},[1828,1862,1863],{},"Tekstschaal 2.0, donker, 320pt",[1828,1865,1866],{},"De twee dichtste componenten van het designsysteem",[1828,1868,1869],{},"4",[1810,1871,1872,1875,1878],{},[1828,1873,1874],{},"Taal",[1828,1876,1877],{},"De taal met de langste strings, één breedte, de twee schermen met het grootste overlooprisico",[1828,1879,1869],{},[175,1881,1882],{},"Dat is een suite van ongeveer 60–90 afbeeldingen: een seconde of twintig CI en een pull request waarvan een mens de visuele diff echt kan bekijken. De versie die alles met elke as vermenigvuldigt — 12 schermen × 2 thema's × 3 breedtes × 2 tekstschalen × 2 talen — is 288 afbeeldingen, en 288 afbeeldingen reviewt niemand.",[175,1884,1885],{},"Drie dingen zijn het helemaal niet waard om te golden:",[204,1887,1888,1894,1900],{},[207,1889,1890,1893],{},[210,1891,1892],{},"Animaties en overgangen."," Een golden legt één frame op één vast moment vast. Hij zegt dat het frame op 100 ms hetzelfde uitziet, niet dat de animatie soepel is, en de marginale waarde boven de begin- en eindtoestand is bijna nul.",[207,1895,1896,1899],{},[210,1897,1898],{},"Alles met echte inhoud erin."," Lange productnamen, prijzen, datums, gegenereerde id's — elk ervan is een reden voor de golden om te veranderen om een niet-visuele reden. Golden de schil met vaste fixtures, en test het renderen van inhoud met een widget test die op tekst assert, niet op pixels.",[207,1901,1902,1905],{},[210,1903,1904],{},"Hele flows."," Drie schermen op één afbeelding zijn drie keer zo slecht te reviewen als één scherm per afbeelding.",[175,1907,1908],{},"En het scherpste om te golden is in onze ervaring geen scherm: het is het designsysteem op 320pt, in donkere modus, met tekst op 200%. Die combinatie is waar echte apps breken, is met de hand bijna niet per component te controleren, en is precies waar een golden goed in is.",[199,1910,1912],{"id":1911},"wat-goldens-niet-testen","Wat goldens niet testen",[175,1914,1915],{},"Een golden is een plaatje. Hij weet niets van betekenis, en een paar gangbare aannames zijn het expliciet corrigeren waard.",[175,1917,1918,1921,1922,1925,1926,1928],{},[210,1919,1920],{},"Gedrag."," Een knop die aan de verkeerde callback hangt rendert identiek. Zo ook een formulier dat niet meer valideert, een lijst die zijn scrollcontroller kwijt is, een dialoog die niet sluit. Dat zijn widget tests — ",[185,1923,1924],{},"tap",", ",[185,1927,1056],{},", controleren wat er gebeurde — en geen hoeveelheid pixels vervangt ze.",[175,1930,1931,1934],{},[210,1932,1933],{},"Toegankelijkheid."," De semantiekboom is onzichtbaar voor een screenshot: ontbrekende labels, verkeerde rollen, een tikdoel teruggebracht tot 24pt, contrast onder 4.5:1 voor lopende tekst (3:1 voor grote tekst). Flutter levert de assertions, en ze zijn goedkoop:",[282,1936,1938],{"className":284,"code":1937,"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,1939,1940,1957,1977,1991,1995,2017,2021,2026,2044,2061,2065,2070,2075,2080,2085,2092,2117,2153,2159],{"__ignoreMap":286},[290,1941,1942,1944,1946,1949,1951,1953,1955],{"class":292,"line":293},[290,1943,297],{"class":296},[290,1945,301],{"class":300},[290,1947,1948],{"class":304},"'cart meets a11y guidelines'",[290,1950,309],{"class":308},[290,1952,312],{"class":300},[290,1954,316],{"class":315},[290,1956,319],{"class":300},[290,1958,1959,1961,1964,1966,1968,1970,1973,1975],{"class":292,"line":322},[290,1960,667],{"class":336},[290,1962,1963],{"class":300}," semantics ",[290,1965,673],{"class":347},[290,1967,1051],{"class":300},[290,1969,381],{"class":308},[290,1971,1972],{"class":296},"ensureSemantics",[290,1974,587],{"class":300},[290,1976,354],{"class":308},[290,1978,1979,1981,1984,1986,1989],{"class":292,"line":357},[290,1980,1430],{"class":296},[290,1982,1983],{"class":300},"(semantics",[290,1985,381],{"class":308},[290,1987,1988],{"class":300},"dispose)",[290,1990,354],{"class":308},[290,1992,1993],{"class":292,"line":364},[290,1994,361],{"emptyLinePlaceholder":360},[290,1996,1997,1999,2001,2003,2005,2007,2009,2011,2013,2015],{"class":292,"line":375},[290,1998,325],{"class":315},[290,2000,328],{"class":296},[290,2002,331],{"class":300},[290,2004,309],{"class":308},[290,2006,337],{"class":336},[290,2008,341],{"class":340},[290,2010,344],{"class":300},[290,2012,348],{"class":347},[290,2014,351],{"class":300},[290,2016,354],{"class":308},[290,2018,2019],{"class":292,"line":398},[290,2020,361],{"emptyLinePlaceholder":360},[290,2022,2023],{"class":292,"line":413},[290,2024,2025],{"class":514},"  \u002F\u002F Tap targets (48dp Android \u002F 44pt iOS) and text contrast.\n",[290,2027,2028,2030,2032,2034,2036,2039,2042],{"class":292,"line":421},[290,2029,325],{"class":315},[290,2031,369],{"class":296},[290,2033,331],{"class":300},[290,2035,309],{"class":308},[290,2037,2038],{"class":296}," meetsGuideline",[290,2040,2041],{"class":300},"(androidTapTargetGuideline))",[290,2043,354],{"class":308},[290,2045,2046,2048,2050,2052,2054,2056,2059],{"class":292,"line":608},[290,2047,325],{"class":315},[290,2049,369],{"class":296},[290,2051,331],{"class":300},[290,2053,309],{"class":308},[290,2055,2038],{"class":296},[290,2057,2058],{"class":300},"(textContrastGuideline))",[290,2060,354],{"class":308},[290,2062,2063],{"class":292,"line":613},[290,2064,361],{"emptyLinePlaceholder":360},[290,2066,2067],{"class":292,"line":634},[290,2068,2069],{"class":514},"  \u002F\u002F The label and role the golden cannot see. `isSemantics` checks only the\n",[290,2071,2072],{"class":292,"line":640},[290,2073,2074],{"class":514},"  \u002F\u002F values you pass; `matchesSemantics` would also require every other flag\n",[290,2076,2077],{"class":292,"line":646},[290,2078,2079],{"class":514},"  \u002F\u002F to be false, and a real Material button sets `hasEnabledState`.\n",[290,2081,2082],{"class":292,"line":652},[290,2083,2084],{"class":514},"  \u002F\u002F (`containsSemantics` is the same matcher on Flutter before 3.40.)\n",[290,2086,2087,2090],{"class":292,"line":658},[290,2088,2089],{"class":296},"  expect",[290,2091,372],{"class":300},[290,2093,2094,2097,2099,2102,2104,2106,2108,2110,2113,2115],{"class":292,"line":664},[290,2095,2096],{"class":300},"    tester",[290,2098,381],{"class":308},[290,2100,2101],{"class":296},"getSemantics",[290,2103,1090],{"class":300},[290,2105,381],{"class":308},[290,2107,384],{"class":296},[290,2109,301],{"class":300},[290,2111,2112],{"class":340},"CheckoutButton",[290,2114,730],{"class":300},[290,2116,395],{"class":308},[290,2118,2119,2122,2125,2127,2130,2132,2135,2137,2140,2142,2145,2147,2149,2151],{"class":292,"line":687},[290,2120,2121],{"class":296},"    isSemantics",[290,2123,2124],{"class":300},"(label",[290,2126,348],{"class":347},[290,2128,2129],{"class":304}," 'Checkout'",[290,2131,309],{"class":308},[290,2133,2134],{"class":300}," isButton",[290,2136,348],{"class":347},[290,2138,2139],{"class":1480}," true",[290,2141,309],{"class":308},[290,2143,2144],{"class":300}," hasTapAction",[290,2146,348],{"class":347},[290,2148,2139],{"class":1480},[290,2150,392],{"class":300},[290,2152,395],{"class":308},[290,2154,2155,2157],{"class":292,"line":712},[290,2156,416],{"class":300},[290,2158,354],{"class":308},[290,2160,2161,2163],{"class":292,"line":735},[290,2162,424],{"class":300},[290,2164,354],{"class":308},[175,2166,2167,2170,2171,2175,2176,2179],{},[210,2168,2169],{},"Rendering op een echt toestel."," Goldens draaien op de engine van de host, dus ze zeggen niet wat ",[193,2172,2174],{"slug":2173},"impeller","Impeller"," op iOS met dezelfde tree doet, of hoe de tekstshaping van het platform verschilt. Als dat verschil ertoe doet, is dat ",[185,2177,2178],{},"integration_test"," op een toestel met een screenshot dat daar is gemaakt: trager, instabieler, en het enige dat de vraag beantwoordt.",[175,2181,2182,2185],{},[210,2183,2184],{},"Jank."," Het framebudget wordt gemeten op een profile-build op een echt toestel, niet afgeleid uit een PNG.",[199,2187,2189],{"id":2188},"de-regels-waarvan-het-afhangt-of-het-de-moeite-waard-was","De regels waarvan het afhangt of het de moeite waard was",[175,2191,2192],{},"De waarde van de suite is evenredig met de kans dat er een mens naar de diff kijkt. Dat is het hele rendement, en het is een culturele eigenschap, geen technische.",[175,2194,2195],{},"Dus, concreet:",[2197,2198,2199,2207,2213,2222,2228],"ol",{},[207,2200,2201,2206],{},[210,2202,2203,2205],{},[185,2204,272],{}," alleen in een commit waarvan het bericht de visuele verandering noemt."," \"Update goldens\" als commitbericht betekent dat de diff niet is bekeken. \"Update goldens: afrekenknop is 48pt en de fouttoestand gebruikt het nieuwe palet\" betekent dat hij wel is bekeken.",[207,2208,2209,2212],{},[210,2210,2211],{},"Een golden-diff in een scherm dat je niet hebt aangeraakt is een bevinding, geen ruis."," Dat is de suite die zijn werk doet. Het in een \"chore: update goldens\" proppen is de suite uitzetten.",[207,2214,2215,2218,2219,2221],{},[210,2216,2217],{},"Als de reviewer de afbeeldingen niet kan zien, heeft het leidingwerk gefaald."," Upload de artefacten uit ",[185,2220,438],{}," vanuit CI, of hang de diff aan de pull request. Een review die bestaat uit \"geloof me, de pixels zijn veranderd\" is geen review.",[207,2223,2224,2227],{},[210,2225,2226],{},"Verwijder de goldens die je zou afstempelen."," Een suite van veertig afbeeldingen waar allemaal naar gekeken wordt verslaat vierhonderd die met één klik worden goedgekeurd. Dekking is niet de maatstaf; bekeken dekking is dat.",[207,2229,2230,2233,2234,2236],{},[210,2231,2232],{},"Als goldens veranderen in losstaande pull requests, stop en zoek de oorzaak"," — een niet-vastgepinde Flutter-versie, een font dat niet geladen wordt, een ",[185,2235,937],{}," die een widget heeft bereikt. Instabiliteit in goldens is bijna nooit het mechanisme; het is een echt non-determinisme dat je nu kent.",[2238,2239],"checklist",{":groups":2240,":subtitle":2241,":title":2242},"[{\"title\": \"Maak het renderen eerlijk\", \"items\": [\"App-fonts geladen in test\u002Fflutter_test_config.dart, met de familienaam die het thema echt opvraagt\", \"Het icoonfont ook geladen — anders is elke Icon een blok\", \"Netwerkafbeeldingen vervangen door in-memory fixtures\", \"Klok, taal en random seeds geïnjecteerd in plaats van uit de omgeving gelezen\", \"Laadtoestanden gerenderd met pump(duration) in plaats van pumpAndSettle\"]}, {\"title\": \"Maak het reproduceerbaar\", \"items\": [\"Flutter-versie vastgepind in de repository en gelezen door CI\", \"Goldens gegenereerd op Linux, hetzelfde platform waar CI draait\", \"Een CI-stap die faalt bij elke gewijzigde of niet-gevolgde golden, zodat niets stil regenereert\", \"failures\u002F in gitignore, en als build-artefact geüpload voor de review\", \"Het testoppervlak expliciet gezet: maat, pixelratio, tekstschaal, taal\"]}, {\"title\": \"Maak het reviewbaar\", \"items\": [\"Elke golden in een RepaintBoundary, zodat precies het bedoelde widget wordt vastgelegd\", \"De vijf assen in de bestandsnaam: onderwerp, thema, breedte, tekstschaal, taal\", \"Onder de ~90 afbeeldingen, of een opgeschreven reden voor meer\", \"Een regel dat golden-updates een eigen commit zijn met een genoemde visuele reden\", \"Iemand heeft minstens één PNG van elk soort geopend en bevestigd dat er tekst in staat\"]}]","Van een lege repository naar een suite die je kunt vertrouwen, in een dag of twee.","De checklist voor een golden-suite",[199,2244,2246],{"id":2245},"wat-het-na-een-half-jaar-kost","Wat het na een half jaar kost",[175,2248,2249,2250,2253],{},"De suite opzetten is een dag, misschien twee, het meeste in fonts en CI-leidingwerk. Elke golden schrijven is minuten. De terugkerende kosten zijn degene die niemand begroot: ",[210,2251,2252],{},"elke Flutter-update genereert elke afbeelding opnieuw."," Met de matrix hierboven is dat een commit van 60–90 PNGs, twee tot vier keer per jaar, elk met een eigen onderbouwing. Kun je die review niet betalen, dan kun je de suite niet betalen — want een niet-bekeken regeneratie is niet te onderscheiden van geen test, behalve dat het langer duurt.",[175,2255,2256,2257,2260,2261,381],{},"In ruil daarvoor krijg je de regressies die geen enkel ander testtype vangt: de lege toestand die zijn padding kwijtraakte in donkere modus, de Russische vertaling die uit een knop van 320pt liep, het designsysteem dat afdreef omdat iemand één ",[185,2258,2259],{},"TextStyle"," in het thema veranderde. En het geval waarmee het voor ons begon: een agent of een collega die een gedeeld component verandert en oprecht niet kan zien welke vier schermen hij brak. Een golden is het dichtst dat Flutter komt bij het geven van ogen aan een codewijziging, en dat is precies het argument dat we maakten in ",[2262,2263,2265],"a",{"href":2264},"\u002Fblog\u002Fai-agents-struggle-with-flutter","waarom AI-agents worstelen met Flutter",[199,2267,2269],{"id":2268},"de-korte-versie","De korte versie",[175,2271,2272],{},"Een golden test is een pixelvergelijking met een PNG, en alles wat hem nuttig maakt gaat over de omstandigheden van die vergelijking. Laad je fonts, laad je icoonfont, zet het oppervlak, pin de toolchain, genereer opnieuw op het platform dat CI gebruikt, en laat nooit een machine de diff goedkeuren.",[175,2274,2275],{},"En houd de suite daarna klein genoeg dat er nog iemand naar kijkt. De faalmodus van golden testing is niet een instabiele test — het zijn 214 afbeeldingen die allemaal slagen omdat ze blokken met blokken vergelijken, en een groen vinkje dat acht maanden lang hetzelfde geruststellende verhaal vertelt.",[199,2277,2279],{"id":2278},"bronnen","Bronnen",[204,2281,2282,2297,2307,2321,2341,2351,2369,2386,2397,2407,2417,2427],{},[207,2283,2284,2287,2288,2290,2291],{},[185,2285,2286],{},"matchesGoldenFile"," — wat de matcher vastlegt, en waar ",[185,2289,434],{}," aangrijpt: ",[2262,2292,2296],{"href":2293,"rel":2294},"https:\u002F\u002Fapi.flutter.dev\u002Fflutter\u002Fflutter_test\u002FmatchesGoldenFile.html",[2295],"nofollow","api.flutter.dev",[207,2298,2299,2302,2303],{},[185,2300,2301],{},"FontLoader"," — een fontsnit registreren op familienaam binnen een test: ",[2262,2304,2296],{"href":2305,"rel":2306},"https:\u002F\u002Fapi.flutter.dev\u002Fflutter\u002Fservices\u002FFontLoader-class.html",[2295],[207,2308,2309,2311,2312,2315,2316],{},[185,2310,228],{}," — gedeclareerd in ",[185,2313,2314],{},"painting\u002Fdebug.dart",", gezet door de testbinding en weer aangezet door Flutters eigen schaduwtests: ",[2262,2317,2320],{"href":2318,"rel":2319},"https:\u002F\u002Fgithub.com\u002Fflutter\u002Fflutter\u002Fblob\u002Fmaster\u002Fpackages\u002Fflutter\u002Flib\u002Fsrc\u002Fpainting\u002Fdebug.dart",[2295],"github.com\u002Fflutter\u002Fflutter",[207,2322,2323,2326,2327,1925,2330,1770,2333,2336,2337],{},[185,2324,2325],{},"TestFlutterView"," — ",[185,2328,2329],{},"physicalSize",[185,2331,2332],{},"devicePixelRatio",[185,2334,2335],{},"reset()",": ",[2262,2338,2296],{"href":2339,"rel":2340},"https:\u002F\u002Fapi.flutter.dev\u002Fflutter\u002Fflutter_test\u002FTestFlutterView-class.html",[2295],[207,2342,2343,2346,2347],{},[185,2344,2345],{},"WidgetTester.pumpAndSettle"," — \"repeatedly calls pump with the given duration until there are no longer any frames scheduled\": ",[2262,2348,2296],{"href":2349,"rel":2350},"https:\u002F\u002Fapi.flutter.dev\u002Fflutter\u002Fflutter_test\u002FWidgetTester\u002FpumpAndSettle.html",[2295],[207,2352,2353,2326,2356,1925,2359,1925,2362,2336,2365],{},[185,2354,2355],{},"AccessibilityGuideline",[185,2357,2358],{},"androidTapTargetGuideline",[185,2360,2361],{},"iOSTapTargetGuideline",[185,2363,2364],{},"textContrastGuideline",[2262,2366,2296],{"href":2367,"rel":2368},"https:\u002F\u002Fapi.flutter.dev\u002Fflutter\u002Fflutter_test\u002FAccessibilityGuideline-class.html",[2295],[207,2370,2371,2372,2375,2376,1925,2381],{},"Framework-goldens getrieerd op Skia Gold, en de ",[185,2373,2374],{},"flutter\u002Fgoldens","-repo met de gevallen die het niet kunnen gebruiken: ",[2262,2377,2380],{"href":2378,"rel":2379},"https:\u002F\u002Fskia.org\u002Fdocs\u002Fdev\u002Ftesting\u002Fskiagold\u002F",[2295],"skia.org",[2262,2382,2385],{"href":2383,"rel":2384},"https:\u002F\u002Fgithub.com\u002Fflutter\u002Fgoldens",[2295],"github.com\u002Fflutter\u002Fgoldens",[207,2387,2388,2391,2392],{},[185,2389,2390],{},"alchemist"," — golden-mappen per platform, gegroepeerde goldens van meerdere widgets, CI-bewuste configuratie: ",[2262,2393,2396],{"href":2394,"rel":2395},"https:\u002F\u002Fpub.dev\u002Fpackages\u002Falchemist",[2295],"pub.dev",[207,2398,2399,2402,2403],{},[185,2400,2401],{},"golden_screenshot"," — screenshottests met toestelpreviews en platformspecifieke goldens: ",[2262,2404,2396],{"href":2405,"rel":2406},"https:\u002F\u002Fpub.dev\u002Fpackages\u002Fgolden_screenshot",[2295],[207,2408,2409,2412,2413],{},[185,2410,2411],{},"golden_toolkit"," — het pakket dat de meeste golden-tutorials nog aanraden; stopgezet, laatste release februari 2023: ",[2262,2414,2396],{"href":2415,"rel":2416},"https:\u002F\u002Fpub.dev\u002Fpackages\u002Fgolden_toolkit",[2295],[207,2418,2419,2422,2423],{},[185,2420,2421],{},"spot"," — golden-achtige widget tests die een geserialiseerde widget-tree vergelijken in plaats van pixels, wanneer pixelinstabiliteit het grotere probleem is: ",[2262,2424,2396],{"href":2425,"rel":2426},"https:\u002F\u002Fpub.dev\u002Fpackages\u002Fspot",[2295],[207,2428,2429,2431,2432,2435,2436],{},[185,2430,2178],{}," — screenshots van een echt toestel (roep op Android eerst ",[185,2433,2434],{},"convertFlutterSurfaceToImage()"," aan), het enige antwoord op \"hoe ziet het er op een telefoon uit\": ",[2262,2437,2440],{"href":2438,"rel":2439},"https:\u002F\u002Fgithub.com\u002Fflutter\u002Fflutter\u002Fblob\u002Fmaster\u002Fpackages\u002Fintegration_test\u002Flib\u002Fintegration_test.dart",[2295],"integration_test.dart",[199,2442,2444],{"id":2443},"veelgestelde-vragen","Veelgestelde vragen",[2446,2447],"questions",{":items":2448},"[{\"title\": \"Wat is een golden test in Flutter?\", \"text\": \"Een golden test rendert een widget en vergelijkt het resultaat pixel voor pixel met een PNG die in de repository staat. De PNG wordt gemaakt door flutter test met de update-goldens-flag te draaien, en elke volgende run faalt als de rendering afwijkt. In Flutter is het de goedkoopste manier om visuele regressies te vangen, omdat het in hetzelfde testproces draait als gewone widget tests en seconden kost.\"}, {\"title\": \"Waarom wordt tekst in mijn golden-bestanden als zwarte blokken getekend?\", \"text\": \"Omdat de testomgeving de fonts uit je pubspec niet laadt. Tekst zonder beschikbare familie valt terug op een testfont waarvan elk glyph een gevulde rechthoek is, wat de layout deterministisch houdt en fontbestanden overbodig maakt. Registreer de echte fonts met FontLoader in test\u002Fflutter_test_config.dart, inclusief het icoonfont, en de goldens tekenen echte tekst.\"}, {\"title\": \"Waarom slagen golden tests lokaal en falen ze in CI?\", \"text\": \"De pixels hangen af van het besturingssysteem van de host en de Flutter-versie, niet alleen van je widget-tree. Antialiasing en hinting van tekst verschillen tussen macOS en Linux, dus een suite die op de laptop van een ontwikkelaar is gegenereerd komt niet overeen met een Linux-runner. Genereer goldens op hetzelfde platform en met dezelfde vastgepinde Flutter-versie die CI gebruikt, en behandel een Flutter-update als een commit die elke afbeelding bewust opnieuw genereert.\"}, {\"title\": \"Moeten golden-bestanden in git?\", \"text\": \"Ja. De hele waarde van een golden is dat de wijziging zichtbaar is in de review, en dat vereist dat de afbeelding in de repository naast de code staat, op dezelfde branch, en verandert in dezelfde commit als alles wat de rendering veranderde. De faalartefacten die de comparator schrijft zijn de uitzondering: de referentie, de nieuwe render en de twee diff-afbeeldingen horen in gitignore en in de artefacten van continuous integration.\"}, {\"title\": \"Is pumpAndSettle de juiste manier om een laadtoestand voor een golden te renderen?\", \"text\": \"Nee. pumpAndSettle pompt tot er geen frame meer gepland staat, en een onbepaalde CircularProgressIndicator plant eindeloos frames in, dus de aanroep loopt in een timeout. Gebruik tester.pump met een expliciete duur om één vaste fase van de spinner vast te leggen. Je test dat de layout rond de spinner overeind blijft; de spinner zelf is niet wat verandert.\"}, {\"title\": \"Vervangen golden tests widget tests?\", \"text\": \"Nee, ze dekken verschillende dingen. Een golden assert op pixels, dus hij kan je niet vertellen dat een knop de verkeerde callback aanroept, dat validatie niet meer loopt of dat een dialoog niet sluit, want dat rendert allemaal identiek. Gedrag dek je met widget tests, en semantiek en tikdoelen met assertions als meetsGuideline, die een screenshot helemaal niet ziet.\"}, {\"title\": \"Hoeveel golden tests moet een Flutter-app hebben?\", \"text\": \"Genoeg dat er nog iemand naar de diffs kijkt, wat in de praktijk neerkomt op ongeveer 60 tot 90 afbeeldingen: de componenten van het designsysteem in hun staten en beide thema's, de paar schermen die ertoe doen op drie breedtes, en de lege, fout- en laadtoestanden. Elk scherm vermenigvuldigen met elk thema, elke breedte, tekstschaal en taal levert honderden afbeeldingen op die met één klik worden goedgekeurd, en een suite die met één klik wordt goedgekeurd test niets.\"}, {\"title\": \"Welk pakket voor golden tests moet ik gebruiken?\", \"text\": \"Begin met wat flutter_test meelevert: matchesGoldenFile, een flutter_test_config.dart voor het laden van fonts, en een kleine helper die de oppervlaktemaat zet. Pakketten als alchemist, golden_screenshot en spot voegen golden-mappen per platform, toestelpreviews of widget-tree-gebaseerde vergelijking toe, en zijn de moeite waard zodra je het probleem raakt dat elk oplost. golden_toolkit, dat de meeste tutorials nog aanraden, is in 2023 stopgezet.\"}]",[175,2450,2451,2452,2456,2457,2461],{},"Als je de testlaag vanaf nul opzet, staan de omliggende beslissingen in onze uiteenzetting over het ",[2262,2453,2455],{"href":2454},"\u002Fblog\u002Fflutter-app-development-process","proces van Flutter-appontwikkeling",", en het staatsmodel-probleem dat de helft van een interface ontestbaar maakt in ",[2262,2458,2460],{"href":2459},"\u002Fblog\u002Fimpossible-states-dart-sealed-classes","vijf toestanden die niet kunnen bestaan",". Het thema is in alle drie hetzelfde: een check die slaagt is niet hetzelfde als een check die kijkt.",[2463,2464,2465],"post-credits",{},[175,2466,2467,2468,2472,2473,2477],{},"Maxim is senior software engineer bij ",[2262,2469,2471],{"href":2470},"\u002F","Nerdy Production",", een Flutter-first bureau dat apps bouwt en onderhoudt in fintech, zorg en retail. We doen ook ",[2262,2474,2476],{"href":2475},"\u002Fservices\u002Fflutter-app-development","Flutter-appontwikkeling"," voor teams die liever vóór de volgende release weten wat hun testsuite vergelijkt dan erna.",[2479,2480,2481],"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":2483},[2484,2485,2486,2494,2495,2496,2497,2498,2499,2500,2501],{"id":201,"depth":322,"text":202},{"id":276,"depth":322,"text":277},{"id":481,"depth":322,"text":482,"children":2487},[2488,2489,2490,2491,2492],{"id":486,"depth":357,"text":487},{"id":851,"depth":357,"text":852},{"id":883,"depth":357,"text":884},{"id":931,"depth":357,"text":932},{"id":987,"depth":357,"text":2493},"5. pumpAndSettle komt nooit terug bij een laadtoestand",{"id":1128,"depth":322,"text":1129},{"id":1798,"depth":322,"text":1799},{"id":1911,"depth":322,"text":1912},{"id":2188,"depth":322,"text":2189},{"id":2245,"depth":322,"text":2246},{"id":2268,"depth":322,"text":2269},{"id":2278,"depth":322,"text":2279},{"id":2443,"depth":322,"text":2444},"md",{"type":2504,"src":2505},"image","\u002Fblog\u002Fflutter-golden-tests.webp",{},"Golden tests zijn Flutters goedkoopste visuele regressienet en het makkelijkst te faken: fonts, een vastgepinde CI, de matrix die loont en reviewdiscipline.","Golden tests in Flutter die niet liegen","Elk glyph een zwarte rechthoek, schaduwen platgeslagen tot harde blokken, pixels afhankelijk van de host-OS. Wat een golden test echt vergelijkt, en de opzet die hem eerlijk maakt.","\u002Fblog\u002Fflutter-golden-tests",{"title":169,"description":177},"flutter-golden-tests","blog\u002Fflutter-golden-tests","Golden tests zijn de ui-test met de hoogste opbrengst in Flutter en de makkelijkste manier om er een stempel op te zetten. De eerste die je schrijft tekent elk glyph als een zwarte rechthoek, want de testomgeving heeft geen fonts; schaduwen worden platgeslagen tot harde blokken; en de pixels hangen af van het besturingssysteem van de host en de Flutter-versie, dus een suite die op een Mac is gegenereerd faalt op Linux in CI. Dit is de opzet die goldens eerlijk maakt, de matrix die het geld waard is, wat ze niet kunnen testen, en de reviewregels waarvan het afhangt of de hele suite zijn onderhoud verdient.","2026-11-10T12:00:00Z",null,"hUBkmipH7UyMg8z7c4rK-ilV0ewtatA5zGVQ4-IX2is",[2519,2575,2622,2671,2761,2813],{"id":2520,"bio":2521,"expertise":2526,"extension":1170,"links":2542,"meta":2546,"metaDescription":2547,"name":2552,"ogDescription":2555,"photo":2560,"role":2561,"seniority":2566,"skills":2567,"slug":2568,"specialization":2569,"stem":2568,"__hash__":2574},"team_members\u002Fdima.yaml",{"en":2522,"ru":2523,"es":2524,"nl":2525},"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",[2527,2530,2535,2537],{"en":2528,"ru":2528,"es":2529,"nl":2528},"State management","Gestión de estado",{"en":2531,"ru":2532,"es":2533,"nl":2534},"Application architecture","Архитектура приложений","Arquitectura de aplicaciones","Applicatiearchitectuur",{"en":2536,"ru":2536,"es":2536,"nl":2536},"WebRTC",{"en":2538,"ru":2539,"es":2540,"nl":2541},"XMPP messaging","Обмен сообщениями по XMPP","Mensajería XMPP","Berichten via XMPP",[2543],{"type":2544,"address":2545},"email","konopatov@nerdy.pro",{},{"en":2548,"ru":2549,"es":2550,"nl":2551},"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":2553,"ru":2554,"es":2553,"nl":2553},"Dima","Дима",{"en":2556,"ru":2557,"es":2558,"nl":2559},"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":2562,"ru":2563,"es":2564,"nl":2565},"Lead Flutter Developer","Ведущий Flutter-разработчик","Lead de desarrollo Flutter","Lead Flutter-developer","lead",[41,18,155,160,90,111,150,95],"dima",{"en":2570,"ru":2571,"es":2572,"nl":2573},"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":2576,"bio":2577,"expertise":2582,"extension":1170,"links":2516,"meta":2593,"metaDescription":2594,"name":2599,"ogDescription":2602,"photo":2607,"role":2608,"seniority":2613,"skills":2614,"slug":2615,"specialization":2616,"stem":2615,"__hash__":2621},"team_members\u002Fmasha.yaml",{"en":2578,"ru":2579,"es":2580,"nl":2581},"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",[2583,2588],{"en":2584,"ru":2585,"es":2586,"nl":2587},"UI\u002FUX design","UI\u002FUX-дизайн","Diseño UI\u002FUX","UI\u002FUX-ontwerp",{"en":2589,"ru":2590,"es":2591,"nl":2592},"Product copywriting","Продуктовый копирайтинг","Redacción de producto","Productteksten",{},{"en":2595,"ru":2596,"es":2597,"nl":2598},"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":2600,"ru":2601,"es":2600,"nl":2600},"Masha","Маша",{"en":2603,"ru":2604,"es":2605,"nl":2606},"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":2609,"ru":2610,"es":2611,"nl":2612},"Flutter Developer","Flutter-разработчик","Desarrolladora Flutter","Flutter-developer","middle",[41,18],"masha",{"en":2617,"ru":2618,"es":2619,"nl":2620},"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":2623,"bio":2624,"expertise":2629,"extension":1170,"links":2640,"meta":2643,"metaDescription":2644,"name":2649,"ogDescription":2652,"photo":2657,"role":2658,"seniority":2663,"skills":2664,"slug":170,"specialization":2665,"stem":170,"__hash__":2670},"team_members\u002Fmaxim.yaml",{"en":2625,"ru":2626,"es":2627,"nl":2628},"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",[2630,2635],{"en":2631,"ru":2632,"es":2633,"nl":2634},"Automated testing","Автоматизированное тестирование","Testing automatizado","Geautomatiseerd testen",{"en":2636,"ru":2637,"es":2638,"nl":2639},"High-load backend systems","Высоконагруженные бэкенд-системы","Sistemas backend de alta carga","Backendsystemen met hoge belasting",[2641],{"type":2544,"address":2642},"maxim@nerdy.pro",{},{"en":2645,"ru":2646,"es":2647,"nl":2648},"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":2650,"ru":2651,"es":2650,"nl":2650},"Maxim","Максим",{"en":2653,"ru":2654,"es":2655,"nl":2656},"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":2659,"ru":2660,"es":2661,"nl":2662},"Senior Software Engineer","Старший инженер-программист","Ingeniero de software senior","Senior software engineer","senior",[41,18,51,106,155,13,150],{"en":2666,"ru":2667,"es":2668,"nl":2669},"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":2672,"bio":2673,"expertise":2678,"extension":1170,"links":2720,"meta":2732,"metaDescription":2733,"name":2738,"ogDescription":2741,"photo":2746,"role":2747,"seniority":2752,"skills":2753,"slug":2754,"specialization":2755,"stem":2754,"__hash__":2760},"team_members\u002Fnixan.yaml",{"en":2674,"ru":2675,"es":2676,"nl":2677},"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",[2679,2684,2689,2694,2699,2704,2709,2714,2716,2718],{"en":2680,"ru":2681,"es":2682,"nl":2683},"iOS development","Разработка под iOS","Desarrollo iOS","iOS-ontwikkeling",{"en":2685,"ru":2686,"es":2687,"nl":2688},"Software architecture","Архитектура ПО","Arquitectura de software","Softwarearchitectuur",{"en":2690,"ru":2691,"es":2692,"nl":2693},"Engineering team leadership","Руководство инженерными командами","Liderazgo de equipos de ingeniería","Leidinggeven aan engineeringteams",{"en":2695,"ru":2696,"es":2697,"nl":2698},"Payment systems","Платёжные системы","Sistemas de pago","Betaalsystemen",{"en":2700,"ru":2701,"es":2702,"nl":2703},"Card processing","Карточный процессинг","Procesamiento de tarjetas","Kaartverwerking",{"en":2705,"ru":2706,"es":2707,"nl":2708},"PCI-DSS compliance","Соответствие PCI-DSS","Cumplimiento de PCI-DSS","Naleving van PCI-DSS",{"en":2710,"ru":2711,"es":2712,"nl":2713},"NFC and contactless payments","NFC и бесконтактные платежи","NFC y pagos contactless","NFC en contactloos betalen",{"en":2715,"ru":2715,"es":2715,"nl":2715},"Host Card Emulation",{"en":2717,"ru":2717,"es":2717,"nl":2717},"EMV Contactless",{"en":2719,"ru":2719,"es":2719,"nl":2719},"AOSP",[2721,2724,2727,2730],{"type":2722,"url":2723},"github","https:\u002F\u002Fgithub.com\u002Fthenixan",{"type":2725,"url":2726},"linkedin","https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fthenixan\u002F",{"type":2728,"url":2729},"telegram","https:\u002F\u002Ft.me\u002Fthenixan",{"type":2544,"address":2731},"nixan@nerdy.pro",{},{"en":2734,"ru":2735,"es":2736,"nl":2737},"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":2739,"ru":2740,"es":2739,"nl":2739},"Ilya Nixan","Илья Никсан",{"en":2742,"ru":2743,"es":2744,"nl":2745},"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":2748,"ru":2749,"es":2750,"nl":2751},"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":2756,"ru":2757,"es":2758,"nl":2759},"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":2762,"bio":2763,"expertise":2768,"extension":1170,"links":2780,"meta":2785,"metaDescription":2786,"name":2791,"ogDescription":2794,"photo":2799,"role":2800,"seniority":2663,"skills":2805,"slug":2806,"specialization":2807,"stem":2806,"__hash__":2812},"team_members\u002Froma.yaml",{"en":2764,"ru":2765,"es":2766,"nl":2767},"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",[2769,2770,2775],{"en":2680,"ru":2681,"es":2682,"nl":2683},{"en":2771,"ru":2772,"es":2773,"nl":2774},"Applied AI","Прикладной AI","IA aplicada","Toegepaste AI",{"en":2776,"ru":2777,"es":2778,"nl":2779},"ERP and CRM systems","ERP- и CRM-системы","Sistemas ERP y CRM","ERP- en CRM-systemen",[2781,2783],{"type":2725,"url":2782},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fromanbatler\u002F",{"type":2544,"address":2784},"roma@nerdy.pro",{},{"en":2787,"ru":2788,"es":2789,"nl":2790},"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":2792,"ru":2793,"es":2792,"nl":2792},"Roman","Рома",{"en":2795,"ru":2796,"es":2797,"nl":2798},"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":2801,"ru":2802,"es":2803,"nl":2804},"Senior Mobile Developer","Старший мобильный разработчик","Desarrollador móvil senior","Senior mobiele developer",[7,41,18,141,146,106],"roma",{"en":2808,"ru":2809,"es":2810,"nl":2811},"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":2814,"bio":2815,"expertise":2820,"extension":1170,"links":2516,"meta":2831,"metaDescription":2832,"name":2837,"ogDescription":2840,"photo":2845,"role":2846,"seniority":2663,"skills":2851,"slug":2852,"specialization":2853,"stem":2852,"__hash__":2858},"team_members\u002Fxsox.yaml",{"en":2816,"ru":2817,"es":2818,"nl":2819},"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",[2821,2826],{"en":2822,"ru":2823,"es":2824,"nl":2825},"API design","Проектирование API","Diseño de APIs","API-ontwerp",{"en":2827,"ru":2828,"es":2829,"nl":2830},"Data modelling","Моделирование данных","Modelado de datos","Datamodellering",{},{"en":2833,"ru":2834,"es":2835,"nl":2836},"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":2838,"ru":2839,"es":2838,"nl":2838},"Eugene Xsox","Женя Xsox",{"en":2841,"ru":2842,"es":2843,"nl":2844},"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":2847,"ru":2848,"es":2849,"nl":2850},"Senior Backend Developer","Старший бэкенд-разработчик","Desarrollador backend senior","Senior backenddeveloper",[106,24,41,18,155,160,90,150],"xsox",{"en":2854,"ru":2855,"es":2856,"nl":2857},"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",[2860,2869,2876,2881,2891],{"id":2861,"extension":1170,"meta":2862,"name":2863,"slug":2867,"stem":2867,"__hash__":2868},"blog_topics\u002Fcybersecurity.yaml",{},{"en":2864,"ru":2865,"es":2866,"nl":2864},"Cybersecurity","Кибербезопасность","Ciberseguridad","cybersecurity","kt1rdsMy5W4b9MvGHm6mZVCr0inHidEuqaosPYmmk4E",{"id":2870,"extension":1170,"meta":2871,"name":2872,"slug":2874,"stem":2874,"__hash__":2875},"blog_topics\u002Fdevops.yaml",{},{"en":2873,"ru":2873,"es":2873,"nl":2873},"DevOps","devops","D3mxlZuFHKCGeszKZuWCiQmHmP5EiCvzg1qEAIQhnyU",{"id":2877,"extension":1170,"meta":2878,"name":2879,"slug":41,"stem":41,"__hash__":2880},"blog_topics\u002Fflutter.yaml",{},{"en":39,"ru":39,"es":39,"nl":39},"OOeALAmwFuEqByuJvTGk4g15FIHENPfYMdpfZh_HTAE",{"id":2882,"extension":1170,"meta":2883,"name":2884,"slug":2889,"stem":2889,"__hash__":2890},"blog_topics\u002Ffor-founders.yaml",{},{"en":2885,"ru":2886,"es":2887,"nl":2888},"For Founders","Для основателей","Para fundadores","Voor oprichters","for-founders","lp2u6Ol2qF4DGxpB83HxbbGzWioT-e40mJhD4lr28-U",{"id":2892,"extension":1170,"meta":2893,"name":2894,"slug":2899,"stem":2899,"__hash__":2900},"blog_topics\u002Fsoftware-engineering.yaml",{},{"en":2895,"ru":2896,"es":2897,"nl":2898},"Software Engineering","Разработка ПО","Ingeniería de software","Software-engineering","software-engineering","aZMb3aUDptoFQk_cEE6l5H4wrYcgZuBYUubmKqOAaaU",{"data":2902,"body":2903},{},{"type":2904,"children":2905},"root",[2906,2938],{"type":2907,"tag":175,"props":2908,"children":2909},"element",{},[2910,2913,2918,2919,2924,2925,2930,2931,2936],{"type":2911,"value":2912},"text","Maxim is een polyglotte engineer die zich moeiteloos beweegt tussen ",{"type":2907,"tag":2262,"props":2914,"children":2916},{"href":2915},"\u002Ftechnologies\u002Fgo",[2917],{"type":2911,"value":49},{"type":2911,"value":1925},{"type":2907,"tag":2262,"props":2920,"children":2922},{"href":2921},"\u002Ftechnologies\u002Fflutter",[2923],{"type":2911,"value":39},{"type":2911,"value":1925},{"type":2907,"tag":2262,"props":2926,"children":2928},{"href":2927},"\u002Ftechnologies\u002Fpython",[2929],{"type":2911,"value":104},{"type":2911,"value":1770},{"type":2907,"tag":2262,"props":2932,"children":2934},{"href":2933},"\u002Ftechnologies\u002Ftypescript",[2935],{"type":2911,"value":153},{"type":2911,"value":2937},", waardoor hij een functie van de backend tot het scherm kan dragen.",{"type":2907,"tag":175,"props":2939,"children":2940},{},[2941],{"type":2911,"value":2942},"Hij 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.",[2944,2958,2979,2992,3006,3020,3034,3051,3064,3079,3093,3106,3121,3135,3151,3161,3171,3182,3192,3205,3215,3224,3239,3248,3261,3275,3282,3294,3307,3318,3333,3346,3359,3372,3385,3398,3411,3422,3433,3445,3456,3468,3478,3490,3503,3515,3527,3541,3552,3564,3577,3586,3598],{"slug":2945,"term":2719,"definition":2946,"category":2947,"aliases":2948,"links":2950,"related":2954,"readMore":-1,"target":2956,"hasArticle":2957},"aosp","Het Android Open Source Project — Android zonder de Google-laag erbovenop, door iedereen vrij te forken. Kassaterminals, kiosken en autosystemen draaien op forks ervan, en werken op dat niveau legt delen van het OS bloot die een app-ontwikkelaar nooit ziet.","platform",[2949],"Android Open Source Project",[2951],{"kind":2952,"url":2953},"website","https:\u002F\u002Fsource.android.com\u002F",[2955],"host-card-emulation","\u002Fglossary#aosp",false,{"slug":2959,"term":2960,"definition":2961,"category":2962,"aliases":2963,"links":2968,"related":2974,"readMore":-1,"target":2978,"hasArticle":2957},"gdpr","AVG","De EU-verordening over persoonsgegevens van mensen in de EU: een rechtsgrond om ze te verzamelen, echte toestemming voor tracking, en het recht om ze in te zien en te laten wissen. Ze volgt je gebruikers, niet je servers, en geldt dus ongeacht waar het bedrijf staat ingeschreven.","practice",[2964,2965,2966,2967],"GDPR","General Data Protection Regulation","Algemene verordening gegevensbescherming","gegevensbescherming",[2969,2971],{"kind":2952,"url":2970},"https:\u002F\u002Fgdpr.eu\u002F",{"kind":2972,"url":2973},"wikipedia","https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FGeneral_Data_Protection_Regulation",[2975,2976,2977],"pci-dss","soc-2","hipaa","\u002Fglossary#gdpr",{"slug":2980,"term":2981,"definition":2982,"category":2947,"aliases":2983,"links":2984,"related":2988,"readMore":-1,"target":2991,"hasArticle":2957},"app-clips","App Clips","Een Apple-functie die een klein stuk van een iOS-app draait — onder de 15 MB — zonder het geheel te installeren. Te starten via een QR-code, een NFC-tag of een link, voor als de eerste handeling van een gebruiker niet eerst een gang naar de App Store zou moeten vergen.",[],[2985],{"kind":2986,"url":2987},"documentation","https:\u002F\u002Fdeveloper.apple.com\u002Fapp-clips\u002F",[2989,2990],"deep-linking","install-referrer","\u002Fglossary#app-clips",{"slug":2993,"term":2994,"definition":2995,"category":2947,"aliases":2996,"links":2997,"related":3002,"readMore":-1,"target":3005,"hasArticle":2957},"bigquery","BigQuery","Het analytische datawarehouse van Google Cloud. Je laat SQL los op miljarden rijen en het scant ze in seconden, op opslag die losstaat van de machines die de query uitvoeren — en juist dat houdt rapportage en verkenning weg bij de database die het live verkeer bedient.",[],[2998,3000],{"kind":2952,"url":2999},"https:\u002F\u002Fcloud.google.com\u002Fbigquery",{"kind":2972,"url":3001},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FBigQuery",[3003,3004],"object-storage","elasticsearch","\u002Fglossary#bigquery",{"slug":3007,"term":3008,"definition":3009,"category":2962,"aliases":3010,"links":3015,"related":3018,"readMore":-1,"target":3019,"hasArticle":2957},"ci-cd","CI\u002FCD","Automatisering die elke wijziging bouwt, test en uitlevert zonder dat iemand handmatig commando's uitvoert. Op mobiel is het wat van een release een druk op de knop maakt in plaats van een middag wachten tot iemand anders tijd heeft.",[3011,3012,3013,3014],"CI","continuous integration","continuous delivery","continue integratie",[3016],{"kind":2972,"url":3017},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FCI\u002FCD",[],"\u002Fglossary#ci-cd",{"slug":3021,"term":3022,"definition":3023,"category":3024,"aliases":3025,"links":3027,"related":3030,"readMore":-1,"target":3033,"hasArticle":2957},"crud","CRUD","Create, read, update, delete — de vier bewerkingen achter zo goed als elk formulier en elk beheerscherm. Verzamelnaam voor de helft van een app die routinematig gegevensbeheer is, tegenover de delen die echte domeinlogica dragen.","architecture",[3026],"Create, Read, Update, Delete",[3028],{"kind":2972,"url":3029},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FCreate,_read,_update_and_delete",[3031,3032],"rest","graphql","\u002Fglossary#crud",{"slug":3035,"term":3036,"definition":3037,"category":3024,"aliases":3038,"links":3044,"related":3047,"readMore":-1,"target":3050,"hasArticle":2957},"container-registry","Containerregistry","Een gehoste opslagplaats voor container-images, aangesproken op naam en tag — Docker Hub, GitHub Container Registry, of die van je eigen cloudprovider. Een build daarheen pushen verandert 'werkt op mijn machine' in een image die iedereen ongewijzigd kan pullen en draaien.",[3039,3040,3041,3042,3043],"container registry","image registry","Docker registry","GHCR","ghcr.io",[3045],{"kind":2986,"url":3046},"https:\u002F\u002Fdocs.github.com\u002Fen\u002Fpackages\u002Fworking-with-a-github-packages-registry\u002Fworking-with-the-container-registry",[3048,3049],"dev-container","multi-arch-image","\u002Fglossary#container-registry",{"slug":2989,"term":3052,"definition":3053,"category":2947,"aliases":3054,"links":3059,"related":3062,"readMore":3063,"target":3063,"hasArticle":360},"Deep linking","Een link die een specifiek scherm in een geïnstalleerde app opent in plaats van het beginscherm of een webpagina. Deferred deep linking overleeft een installatie, zodat een tik die via de appwinkel loopt alsnog op het juiste scherm landt.",[3055,3056,3057,3058],"deferred deep linking","universal links","Android App Links","diepe links",[3060],{"kind":2972,"url":3061},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FMobile_deep_linking",[2980,2990],"\u002Fglossary\u002Fdeep-linking",{"slug":3048,"term":3065,"definition":3066,"category":2962,"aliases":3067,"links":3072,"related":3077,"readMore":-1,"target":3078,"hasArticle":2957},"Dev Container","Een ontwikkelomgeving die één keer beschreven staat in devcontainer.json — het OS, de tools en de runtimeversies die een project nodig heeft — en die bij iedere bijdrager identiek in een container opent, in plaats van een installatiehandleiding die iedereen anders leest.",[3068,3069,3070,3071],"devcontainer.json","Dev Containers","VS Code Dev Containers","ontwikkelcontainer",[3073,3075],{"kind":2952,"url":3074},"https:\u002F\u002Fcontainers.dev\u002F",{"kind":2986,"url":3076},"https:\u002F\u002Fcode.visualstudio.com\u002Fdocs\u002Fdevcontainers\u002Fcontainers",[3035],"\u002Fglossary#dev-container",{"slug":3080,"term":3081,"definition":3082,"category":3024,"aliases":3083,"links":3088,"related":3091,"readMore":-1,"target":3092,"hasArticle":2957},"floating-point","Drijvende komma","Het binaire IEEE 754-formaat achter double en float. Het kan 0,1 niet exact weergeven, dus 0.1 + 0.2 is 0.30000000000000004 — onzichtbaar in graphics en fataal bij geld, dat thuishoort in gehele kleinste eenheden (centen) of een decimaal type.",[3084,3085,3086,3087],"IEEE 754","double","floating point","zwevende komma",[3089],{"kind":2972,"url":3090},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FIEEE_754",[],"\u002Fglossary#floating-point",{"slug":3004,"term":3094,"definition":3095,"category":2947,"aliases":3096,"links":3099,"related":3104,"readMore":-1,"target":3105,"hasArticle":2957},"Elasticsearch","Een zoek- en analysemachine die records indexeert zodat je ze interactief kunt filteren en doorzoeken in plaats van ze te scannen. Waar je naar grijpt als de vraag 'laat me deze specifieke sessies zien, op zes manieren ingeperkt' is en niet 'tel deze kolom op'.",[3097,3098],"Elastic","ELK",[3100,3102],{"kind":2952,"url":3101},"https:\u002F\u002Fwww.elastic.co\u002Felasticsearch",{"kind":2972,"url":3103},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FElasticsearch",[2993],"\u002Fglossary#elasticsearch",{"slug":3107,"term":3108,"definition":3109,"category":3024,"aliases":3110,"links":3114,"related":3117,"readMore":-1,"target":3120,"hasArticle":2957},"end-to-end-encryption","End-to-end-versleuteling","Versleuteling die op het verzendende apparaat wordt aangebracht en pas op het ontvangende weer wordt weggehaald, zodat de dienst die het bericht vervoert het niet kan lezen — niet na een vordering, niet na een datalek. Het beschermt de inhoud, nooit de metadata.",[3111,3112,3113],"E2EE","end-to-end encryption","end-to-end versleuteld",[3115],{"kind":2972,"url":3116},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FEnd-to-end_encryption",[3118,3119],"tls","jwt","\u002Fglossary#end-to-end-encryption",{"slug":3122,"term":3123,"definition":3124,"category":3024,"aliases":3125,"links":3129,"related":3130,"readMore":-1,"target":3134,"hasArticle":2957},"fan-out","Fan-out","Eén keer uit een bron lezen en elke update afleveren bij iedere client die erop geabonneerd is. De naïeve versie schrijft in een lus naar abonnees en loopt vast zodra één socket traag is; een echte buffert per client en laat vallen wie niet kan bijbenen.",[3126,3127,3128],"fanout","broadcast","uitzending",[],[3131,3132,3133],"websocket","pub-sub","server-sent-events","\u002Fglossary#fan-out",{"slug":3136,"term":3137,"definition":3138,"category":2962,"aliases":3139,"links":3143,"related":3146,"readMore":-1,"target":3150,"hasArticle":2957},"feature-flags","Feature flags","Schakelaars die functionaliteit aan- of uitzetten vanuit configuratie in plaats van vanuit een release. Ze laten één binary zich per merk, markt of gebruiker anders gedragen, en laten je een riskante functie uitzetten zonder een nieuwe build door de review te sturen.",[3140,3141,3142],"feature flag","feature toggle","functieschakelaar",[3144],{"kind":2972,"url":3145},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FFeature_toggle",[3147,3148,3149],"staged-rollout","white-label","multi-tenancy","\u002Fglossary#feature-flags",{"slug":3147,"term":3152,"definition":3153,"category":2962,"aliases":3154,"links":3158,"related":3159,"readMore":-1,"target":3160,"hasArticle":2957},"Gefaseerde uitrol","Een build eerst naar een klein percentage gebruikers uitbrengen en pas verbreden als het crashvrije percentage standhoudt. Een slechte build die je bij tien procent betrapt is een slechte middag; dezelfde build bij honderd procent is een slechte week.",[3155,3156,3157],"staged rollout","gefaseerde release","canary release",[],[3136,3007],"\u002Fglossary#staged-rollout",{"slug":195,"term":3162,"definition":3163,"category":2962,"aliases":3164,"links":3167,"related":3169,"readMore":-1,"target":3170,"hasArticle":2957},"Golden test","Een test die een widget rendert en het resultaat pixel voor pixel vergelijkt met een opgeslagen referentiebeeld. In Flutter is het de goedkoopste manier om te zien of een herontwerp de empty state brak op 320pt, in donkere modus, bij 200% tekstgrootte.",[196,3165,3166],"screenshottest","snapshottest",[3168],{"kind":2986,"url":2293},[3007],"\u002Fglossary#golden-test",{"slug":3032,"term":3172,"definition":3173,"category":3024,"aliases":3174,"links":3175,"related":3180,"readMore":-1,"target":3181,"hasArticle":2957},"GraphQL","Een querytaal voor API's waarin de client precies de velden benoemt die hij wil en één antwoord terugkrijgt dat daarop past. Het haalt de over-fetching weg waar REST-endpoints naar afglijden, en voegt er een eigen faalmodus aan toe: een onbegrensde query die het hele datamodel afloopt.",[],[3176,3178],{"kind":2952,"url":3177},"https:\u002F\u002Fgraphql.org\u002F",{"kind":2972,"url":3179},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FGraphQL",[3031],"\u002Fglossary#graphql",{"slug":2977,"term":3183,"definition":3184,"category":2962,"aliases":3185,"links":3187,"related":3190,"readMore":-1,"target":3191,"hasArticle":2957},"HIPAA","De Amerikaanse wet over beschermde gezondheidsinformatie — hoe die opgeslagen, verstuurd, gelogd en gedeeld mag worden. Net als PCI-DSS is het een architectuurkeuze aan het begin, geen beleidsstuk dat je vlak voor de lancering toevoegt.",[3186],"Health Insurance Portability and Accountability Act",[3188],{"kind":2972,"url":3189},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FHealth_Insurance_Portability_and_Accountability_Act",[2975,2959,2976],"\u002Fglossary#hipaa",{"slug":3193,"term":3194,"definition":3195,"category":3024,"aliases":3196,"links":3199,"related":3202,"readMore":-1,"target":3204,"hasArticle":2957},"headless-cms","Headless CMS","Een contentsysteem met een redactieomgeving en een API, maar zonder eigen front-end. Redacteuren publiceren op één plek, en de site of app rendert die content zelf — zodat de presentatielaag van jou is en niet van de CMS-leverancier.",[3197,3198],"headless contentmanagementsysteem","content-API",[3200],{"kind":2972,"url":3201},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FHeadless_content_management_system",[3203,3031],"server-side-rendering","\u002Fglossary#headless-cms",{"slug":2955,"term":2715,"definition":3206,"category":2947,"aliases":3207,"links":3210,"related":3213,"readMore":-1,"target":3214,"hasArticle":2957},"Een Android-telefoon zich in software laten gedragen als een contactloze kaart via NFC, zonder secure element in hardware. Zo betaalt een wallet-app bij een terminal: de telefoon spreekt hetzelfde contactloze EMV-protocol als de plastic kaart zou hebben gedaan.",[3208,2717,3209],"HCE","contactloos betalen",[3211],{"kind":2986,"url":3212},"https:\u002F\u002Fdeveloper.android.com\u002Fdevelop\u002Fconnectivity\u002Fnfc\u002Fhce",[2975,2945],"\u002Fglossary#host-card-emulation",{"slug":2173,"term":2174,"definition":3216,"category":2947,"aliases":3217,"links":3218,"related":3221,"readMore":-1,"target":3223,"hasArticle":2957},"De renderengine die Flutter vandaag gebruikt, standaard op iOS sinds 2023 en op Android sinds 2024. Hij compileert zijn shaders vooraf in plaats van tijdens de eerste animatie, wat de shader-jank wegnam die Flutters zichtbaarste productieprobleem was.",[],[3219],{"kind":2986,"url":3220},"https:\u002F\u002Fdocs.flutter.dev\u002Fperf\u002Fimpeller",[3222],"skia","\u002Fglossary#impeller",{"slug":3225,"term":3226,"definition":3227,"category":3228,"aliases":3229,"links":3233,"related":3236,"readMore":-1,"target":3238,"hasArticle":2957},"in-app-purchase","In-app-aankoop","Digitale goederen of een abonnement verkopen via de Apple- of Google-billing die beide winkels verplicht stellen voor digitale content en waarover ze commissie rekenen. Het moeilijke is nooit de aankoop; het is het herstellen ervan op een nieuw toestel en de rechten kloppend houden.","business",[3230,3231,3232],"IAP","in-app purchase","in-app-abonnement",[3234],{"kind":2986,"url":3235},"https:\u002F\u002Fdeveloper.apple.com\u002Fin-app-purchase\u002F",[3237],"product-market-fit","\u002Fglossary#in-app-purchase",{"slug":2990,"term":3240,"definition":3241,"category":2947,"aliases":3242,"links":3243,"related":3246,"readMore":-1,"target":3247,"hasArticle":2957},"Install Referrer","Een Google Play-API die een net geïnstalleerde Android-app de campagneparameters overhandigt van de link die tot de installatie leidde. De Android-helft van deferred deep linking, en de betrouwbare manier om te weten waar een gebruiker vandaan kwam.",[],[3244],{"kind":2986,"url":3245},"https:\u002F\u002Fdeveloper.android.com\u002Fgoogle\u002Fplay\u002Finstallreferrer",[2989,2980],"\u002Fglossary#install-referrer",{"slug":3119,"term":3249,"definition":3250,"category":3251,"aliases":3252,"links":3254,"related":3259,"readMore":-1,"target":3260,"hasArticle":2957},"JWT","Een ondertekend token dat zijn eigen claims meedraagt, zodat een server kan zien bij wie een verzoek hoort zonder een sessie op te zoeken. Standaard voor mobiele authenticatie. De handtekening bewijst dat er niets is gewijzigd; ze verbergt niet wat erin staat.","protocol",[3253],"JSON Web Token",[3255,3257],{"kind":2952,"url":3256},"https:\u002F\u002Fjwt.io\u002F",{"kind":2972,"url":3258},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FJSON_Web_Token",[2975],"\u002Fglossary#jwt",{"slug":3262,"term":3263,"definition":3264,"category":2962,"aliases":3265,"links":3270,"related":3273,"readMore":-1,"target":3274,"hasArticle":2957},"kyc","KYC","Know Your Customer — de identiteitscontroles die een gereguleerd financieel product uitvoert voordat het iemand geld laat verplaatsen: documentscans, liveness, sanctie- en witwascontroles. Het bepaalt de onboarding sterker dan welke ontwerpkeuze ook.",[3266,3267,3268,3269],"Know Your Customer","AML","KYC\u002FAML","witwascontrole",[3271],{"kind":2972,"url":3272},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FKnow_your_customer",[2975,2959],"\u002Fglossary#kyc",{"slug":76,"term":74,"definition":3276,"category":2947,"aliases":3277,"links":3279,"related":3280,"readMore":3281,"target":3281,"hasArticle":2957},"Bedrijfslogica geschreven in Kotlin delen tussen Android, iOS en de server, terwijl elk platform zijn eigen native UI houdt. Het alternatief voor Flutter wanneer de interface native moet zijn maar de regels erachter niet.",[3278],"KMP",[],[],"\u002Ftechnologies\u002Fkmp",{"slug":3283,"term":3284,"definition":3285,"category":3228,"aliases":3286,"links":3289,"related":3292,"readMore":-1,"target":3293,"hasArticle":2957},"mvp","MVP","De kleinste versie van een product die je aan echte gebruikers kunt uitbrengen en die nog steeds de vraag beantwoordt waarvoor je het bouwde. Een besluit over scope, niet over kwaliteit — een MVP moet nog altijd werken.",[3287,3288],"minimum viable product","minimaal levensvatbaar product",[3290],{"kind":2972,"url":3291},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FMinimum_viable_product",[3148],"\u002Fglossary#mvp",{"slug":3049,"term":3295,"definition":3296,"category":3024,"aliases":3297,"links":3302,"related":3305,"readMore":-1,"target":3306,"hasArticle":2957},"Multi-architectuur-image","Eén image-tag die per CPU-architectuur naar andere binaries verwijst — linux\u002Famd64 en linux\u002Farm64 zijn het gebruikelijke paar — zodat dezelfde docker pull ongewijzigd werkt op Intel- en AMD-servers en op Apple Silicon-laptops.",[3298,3299,3300,3301],"multi-arch build","multi-arch image","multi-platform image","linux\u002Famd64 + linux\u002Farm64",[3303],{"kind":2986,"url":3304},"https:\u002F\u002Fdocs.docker.com\u002Fbuild\u002Fbuilding\u002Fmulti-platform\u002F",[3035],"\u002Fglossary#multi-arch-image",{"slug":3149,"term":3308,"definition":3309,"category":3024,"aliases":3310,"links":3313,"related":3316,"readMore":-1,"target":3317,"hasArticle":2957},"Multi-tenancy","Eén deployment die veel klanten bedient, waarbij elke klant alleen zijn eigen data, configuratie en ingeschakelde functies ziet doordat de tenantcontext per verzoek wordt bepaald. Het is wat een vloot merkapps één product maakt in plaats van veel forks.",[3311,3312],"multi-tenant","tenant",[3314],{"kind":2972,"url":3315},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FMultitenancy",[3148,3136],"\u002Fglossary#multi-tenancy",{"slug":3319,"term":3320,"definition":3321,"category":3251,"aliases":3322,"links":3326,"related":3331,"readMore":-1,"target":3332,"hasArticle":2957},"oauth","OAuth","De standaard achter Inloggen met Apple, Google en de rest: de gebruiker geeft je app toestemming bij een aanbieder die hij al vertrouwt, en je app krijgt een token in plaats van zijn wachtwoord. Niemand hoeft nieuwe inloggegevens te verzinnen en jij bewaart ze nergens.",[3323,3324,3325],"OAuth 2.0","social login","Inloggen met Apple",[3327,3329],{"kind":2952,"url":3328},"https:\u002F\u002Foauth.net\u002F2\u002F",{"kind":2972,"url":3330},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FOAuth",[3119],"\u002Fglossary#oauth",{"slug":3003,"term":3334,"definition":3335,"category":3024,"aliases":3336,"links":3341,"related":3344,"readMore":-1,"target":3345,"hasArticle":2957},"Objectopslag","Opslag die een heel bestand onder een sleutel bewaart in plaats van in een mappenboom — Amazon S3 en de vele diensten die zijn API spreken. Goedkoop, praktisch onbeperkt, en de gebruikelijke plek voor ruwe events, back-ups en media: eenmaal geschreven, zelden gelezen, eeuwig bewaard.",[3337,3338,3339,3340],"S3","S3-compatibele opslag","object storage","blob storage",[3342],{"kind":2972,"url":3343},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FObject_storage",[2993],"\u002Fglossary#object-storage",{"slug":2975,"term":3347,"definition":3348,"category":2962,"aliases":3349,"links":3352,"related":3357,"readMore":-1,"target":3358,"hasArticle":2957},"PCI-DSS","De beveiligingsstandaard van de kaartindustrie die iedereen bindt die kaartgegevens opslaat, verwerkt of verstuurt. De meeste apps blijven er bewust buiten door de kaartinvoer over te laten aan een gecertificeerde betaaldienstverlener.",[3350,3351],"PCI DSS","Payment Card Industry Data Security Standard",[3353,3355],{"kind":2952,"url":3354},"https:\u002F\u002Fwww.pcisecuritystandards.org\u002F",{"kind":2972,"url":3356},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPayment_Card_Industry_Data_Security_Standard",[3119],"\u002Fglossary#pci-dss",{"slug":3360,"term":3361,"definition":3362,"category":2947,"aliases":3363,"links":3367,"related":3370,"readMore":-1,"target":3371,"hasArticle":2957},"platform-channels","Platform channels","De brug waarmee een Flutter-app native iOS- en Android-code aanroept — Keychain en Keystore, biometrie, betaalschermen, elke SDK zonder Dart-pakket. Routinewerk maar echt werk, en de eerste plek waar een engineer die nooit buiten Dart kwam vastloopt.",[3364,3365,3366],"platform channel","method channel","platformkanaal",[3368],{"kind":2986,"url":3369},"https:\u002F\u002Fdocs.flutter.dev\u002Fplatform-integration\u002Fplatform-channels",[76],"\u002Fglossary#platform-channels",{"slug":3373,"term":3374,"definition":3375,"category":3251,"aliases":3376,"links":3380,"related":3383,"readMore":-1,"target":3384,"hasArticle":2957},"post-quantum-cryptography","Post-quantumcryptografie","Versleutelingsalgoritmen die veilig moeten blijven tegen een toekomstige quantumcomputer, inmiddels gestandaardiseerd door NIST. De migratie is nu al dringend, nog vóór die hardware bestaat, omdat verkeer dat vandaag wordt onderschept later alsnog te ontsleutelen valt.",[3377,3378,3379],"PQC","post-quantum cryptography","postkwantumcryptografie",[3381],{"kind":2972,"url":3382},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPost-quantum_cryptography",[3118,3107],"\u002Fglossary#post-quantum-cryptography",{"slug":3237,"term":3386,"definition":3387,"category":3228,"aliases":3388,"links":3392,"related":3395,"readMore":-1,"target":3397,"hasArticle":2957},"Product-market fit","Het punt waarop een product aantoonbaar mensen heeft gevonden die het willen — ze gebruiken het, komen terug en betalen. Daarvoor beantwoordt engineering een vraag; daarna bedient engineering de vraag.",[3389,3390,3391],"PMF","product\u002Fmarket fit","productmarktfit",[3393],{"kind":2972,"url":3394},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FProduct-market_fit",[3283,3396],"time-to-market","\u002Fglossary#product-market-fit",{"slug":3399,"term":3400,"definition":3401,"category":2962,"aliases":3402,"links":3405,"related":3408,"readMore":-1,"target":3410,"hasArticle":2957},"prompt-injection","Prompt injection","Een aanval waarbij tekst van een gebruiker door een taalmodel als instructie wordt gelezen in plaats van als data, en het model zo om zijn eigen regels heen wordt gestuurd. De LLM-variant van SQL-injectie, en hij duikt overal op waar gebruikersinvoer in een prompt wordt geplakt.",[3403,3404],"injectieaanval","injection attack",[3406],{"kind":2972,"url":3407},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPrompt_injection",[3409],"rate-limiting","\u002Fglossary#prompt-injection",{"slug":3132,"term":3412,"definition":3413,"category":3024,"aliases":3414,"links":3417,"related":3420,"readMore":-1,"target":3421,"hasArticle":2957},"Pub\u002FSub","Een berichtenpatroon waarin een producent een gebeurtenis publiceert en willekeurig veel consumenten die elk voor zich lezen, met een broker ertussen. De producent wacht nooit op ze, en zo blijft een verzoekpad snel terwijl het tragere werk erachter gebeurt.",[3415,3416],"publish\u002Fsubscribe","Google Cloud Pub\u002FSub",[3418],{"kind":2972,"url":3419},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPublish%E2%80%93subscribe_pattern",[],"\u002Fglossary#pub-sub",{"slug":3031,"term":3423,"definition":3424,"category":3024,"aliases":3425,"links":3428,"related":3431,"readMore":-1,"target":3432,"hasArticle":2957},"REST","De gangbare stijl voor HTTP-API's: een URL benoemt een resource en het HTTP-werkwoord zegt wat ermee moet gebeuren. De standaardmanier waarop een app met een backend praat, en wat de meeste externe integraties verwachten aan te treffen.",[3426,3427],"REST API","Representational State Transfer",[3429],{"kind":2972,"url":3430},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FREST",[3131,3133],"\u002Fglossary#rest",{"slug":3409,"term":3434,"definition":3435,"category":2962,"aliases":3436,"links":3440,"related":3443,"readMore":-1,"target":3444,"hasArticle":2957},"Rate limiting","Een bovengrens op het aantal verzoeken dat één aanroeper binnen een bepaald venster mag doen. Het houdt tegen dat één enthousiaste gebruiker, een scraper of een bot in een middag een maand aan betaald API-budget opmaakt, en het moet aan jouw kant van de integratie staan.",[3437,3438,3439],"rate limit","throttling","verzoeklimiet",[3441],{"kind":2972,"url":3442},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FRate_limiting",[3399],"\u002Fglossary#rate-limiting",{"slug":2976,"term":3446,"definition":3447,"category":2962,"aliases":3448,"links":3451,"related":3454,"readMore":-1,"target":3455,"hasArticle":2957},"SOC 2","Een rapport van een externe auditor over hoe een organisatie met klantgegevens omgaat — beveiliging, beschikbaarheid, vertrouwelijkheid — en niet een certificaat dat je koopt. Zakelijke kopers vragen erom, en het beperkt de architectuur lang voordat de audit dat doet.",[3449,3450],"SOC2","System and Organization Controls",[3452],{"kind":2972,"url":3453},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FSystem_and_Organization_Controls",[2975,2959,2977],"\u002Fglossary#soc-2",{"slug":3457,"term":3458,"definition":3459,"category":3228,"aliases":3460,"links":3463,"related":3466,"readMore":-1,"target":3467,"hasArticle":2957},"saas","SaaS","Software die als doorlopend abonnement op een gehost product wordt verkocht in plaats van als eenmalige licentie die de klant zelf installeert en draait. De leverancier beheert de servers, levert doorlopend updates en factureert per gebruiker of per verbruik.",[3461,3462],"Software as a Service","software-as-a-service",[3464],{"kind":2972,"url":3465},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FSoftware_as_a_service",[3149,3148],"\u002Fglossary#saas",{"slug":3133,"term":3469,"definition":3470,"category":3251,"aliases":3471,"links":3473,"related":3476,"readMore":-1,"target":3477,"hasArticle":2957},"Server-Sent Events","Een eenrichtingsstroom van server naar client over een gewone HTTP-verbinding. Eenvoudiger dan een WebSocket en het volstaat overal waar alleen de server iets te melden heeft — een voortgangsfeed, een AI-antwoord dat token voor token binnenkomt.",[3472],"SSE",[3474],{"kind":2972,"url":3475},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FServer-sent_events",[3131,3031],"\u002Fglossary#server-sent-events",{"slug":3203,"term":3479,"definition":3480,"category":3024,"aliases":3481,"links":3485,"related":3488,"readMore":-1,"target":3489,"hasArticle":2957},"Server-side rendering","Een pagina op de server als kant-en-klare HTML opbouwen, zodat het eerste antwoord de inhoud, koppen, metatags en gestructureerde data al meedraagt. Crawlers, linkvoorbeelden en trage apparaten lezen dat zonder JavaScript uit te voeren.",[3482,3483,3484],"SSR","server-rendered","renderen op de server",[3486],{"kind":2972,"url":3487},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FServer-side_scripting",[3193],"\u002Fglossary#server-side-rendering",{"slug":3222,"term":3491,"definition":3492,"category":2947,"aliases":3493,"links":3496,"related":3501,"readMore":-1,"target":3502,"hasArticle":2957},"Skia","De opensource 2D-graphicsbibliotheek van Google die Chrome, Android en — tot Impeller — elk Flutter-frame tekent. Ze rendert net zo goed naar PDF als naar een scherm, en dat is wat print-naar-PDF in Chrome doet.",[3494,3495],"Skia Graphics Engine","skia-safe",[3497,3499],{"kind":2952,"url":3498},"https:\u002F\u002Fskia.org\u002F",{"kind":2972,"url":3500},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FSkia_Graphics_Engine",[2173],"\u002Fglossary#skia",{"slug":3504,"term":2528,"definition":3505,"category":3024,"aliases":3506,"links":3510,"related":3513,"readMore":-1,"target":3514,"hasArticle":2957},"state-management","Hoe een app bepaalt waar een waarde leeft, wie hem mag wijzigen en welke delen van het scherm opnieuw tekenen als dat gebeurt. In Flutter is de keuze tussen Riverpod, BLoC en Provider een van de eerste architectuurbesluiten en het lastigst te herzien.",[3507,3508,3509],"statemanagement","BLoC","Riverpod",[3511],{"kind":2986,"url":3512},"https:\u002F\u002Fdocs.flutter.dev\u002Fdata-and-backend\u002Fstate-mgmt\u002Foptions",[195],"\u002Fglossary#state-management",{"slug":3118,"term":3516,"definition":3517,"category":3251,"aliases":3518,"links":3522,"related":3525,"readMore":-1,"target":3526,"hasArticle":2957},"TLS","De versleutelingslaag onder HTTPS. Ze bewijst dat de server is wie zijn certificaat zegt, spreekt een verse sleutel voor de sessie af en versleutelt alles daarna — zodat het netwerk ertussen alleen cijfertekst ziet die het niet stiekem kan wijzigen.",[3519,3520,3521],"SSL","HTTPS","Transport Layer Security",[3523],{"kind":2972,"url":3524},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FTransport_Layer_Security",[3107,3119],"\u002Fglossary#tls",{"slug":3528,"term":3529,"definition":3530,"category":3228,"aliases":3531,"links":3537,"related":3538,"readMore":-1,"target":3540,"hasArticle":2957},"staff-augmentation","Teamuitbreiding","Een inhuurmodel waarbij engineers van een externe partner bij je team komen en onder jouw aansturing werken — in jouw repository, jouw sprints, jouw proces — in plaats van een eigen project op te leveren. Je koopt capaciteit; de code en de context blijven bij jou.",[3532,3533,3534,3535,3536],"staff augmentation","team augmentation","dedicated developers","outstaffing","teamversterking",[],[3539,3396],"total-cost-of-ownership","\u002Fglossary#staff-augmentation",{"slug":3396,"term":3542,"definition":3543,"category":3228,"aliases":3544,"links":3547,"related":3550,"readMore":-1,"target":3551,"hasArticle":2957},"Time to market","Hoe lang het duurt om een product van besluit naar echte gebruikers te krijgen. De meeste discussies over stack en scope gaan eigenlijk over dit getal, want elke gewonnen week is een week omzet, feedback en concurrentiepositie.",[3545,3396,3546],"TTM","doorlooptijd tot de markt",[3548],{"kind":2972,"url":3549},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FTime_to_market",[3283,3539],"\u002Fglossary#time-to-market",{"slug":3539,"term":3553,"definition":3554,"category":3228,"aliases":3555,"links":3559,"related":3562,"readMore":-1,"target":3563,"hasArticle":2957},"Total cost of ownership","Wat een product over zijn hele leven kost in plaats van alleen de bouw: onderhoud, upgrades, jaarlijkse OS- en winkelmigraties, en het tweede team dat je aanneemt om twee codebases gelijk te houden. Meestal groter dan de bouwofferte, en er vrijwel nooit in opgenomen.",[3556,3557,3558],"TCO","kosten van eigenaarschap","totale eigendomskosten",[3560],{"kind":2972,"url":3561},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FTotal_cost_of_ownership",[3396],"\u002Fglossary#total-cost-of-ownership",{"slug":3565,"term":2536,"definition":3566,"category":3251,"aliases":3567,"links":3569,"related":3574,"readMore":-1,"target":3576,"hasArticle":2957},"webrtc","De standaard voor browsers en mobiel om audio, video en data rechtstreeks tussen twee apparaten te sturen, waarbij servers er alleen aan te pas komen om ze aan elkaar voor te stellen. Hierop is een videogesprek in een app gebouwd, tenzij er een gehuurde SDK onder zit.",[3568],"Web Real-Time Communication",[3570,3572],{"kind":2952,"url":3571},"https:\u002F\u002Fwebrtc.org\u002F",{"kind":2972,"url":3573},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FWebRTC",[3575,3131],"xmpp","\u002Fglossary#webrtc",{"slug":3131,"term":3578,"definition":3579,"category":3251,"aliases":3580,"links":3581,"related":3584,"readMore":-1,"target":3585,"hasArticle":2957},"WebSocket","Een protocol dat één verbinding openhoudt tussen client en server, zodat beide kanten op elk moment kunnen sturen in plaats van dat de client keer op keer vraagt. Waar live koersen, chat en aanwezigheidsindicatoren op draaien.",[],[3582],{"kind":2972,"url":3583},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FWebSocket",[3133,3031],"\u002Fglossary#websocket",{"slug":3148,"term":3587,"definition":3588,"category":3228,"aliases":3589,"links":3593,"related":3596,"readMore":3597,"target":3597,"hasArticle":360},"White-label","Eén product dat onder veel merken wordt uitgebracht. Een white-label platform voor mobiel bouwt elke klant een winkelklare app met een eigen naam, ontwerp en content vanuit één gedeelde codebase, in plaats van het project per klant te forken.",[3590,3591,3592],"white label","multi-tenant app","merkloos product",[3594],{"kind":2972,"url":3595},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FWhite-label_product",[3283],"\u002Fglossary\u002Fwhite-label",{"slug":3575,"term":3599,"definition":3600,"category":3251,"aliases":3601,"links":3604,"related":3609,"readMore":-1,"target":3610,"hasArticle":2957},"XMPP","Een open, gefedereerd berichtenprotocol, en al lang het alternatief voor zelf een chatbackend schrijven of er een huren. Het dekt ook aanwezigheid, typindicatoren en bestandsoverdracht, en het is oud genoeg dat elk platform een volwassen clientbibliotheek heeft.",[3602,3603],"Jabber","Extensible Messaging and Presence Protocol",[3605,3607],{"kind":2952,"url":3606},"https:\u002F\u002Fxmpp.org\u002F",{"kind":2972,"url":3608},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FXMPP",[3565,3131],"\u002Fglossary#xmpp"]