<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ilya Nixan</title>
    <description>The latest articles on DEV Community by Ilya Nixan (@nixan).</description>
    <link>https://dev.to/nixan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4049408%2Fd96076c8-2153-499b-9c67-9f1f72ec3a4d.jpg</url>
      <title>DEV Community: Ilya Nixan</title>
      <link>https://dev.to/nixan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nixan"/>
    <language>en</language>
    <item>
      <title>We Replaced Headless LibreOffice with a Single Rust Binary for DOCX PDF</title>
      <dc:creator>Ilya Nixan</dc:creator>
      <pubDate>Tue, 11 Aug 2026 14:14:56 +0000</pubDate>
      <link>https://dev.to/nixan/we-replaced-headless-libreoffice-with-a-single-rust-binary-for-docx-pdf-7po</link>
      <guid>https://dev.to/nixan/we-replaced-headless-libreoffice-with-a-single-rust-binary-for-docx-pdf-7po</guid>
      <description>&lt;p&gt;We had a document pipeline in production that turned DOCX templates into PDFs. It ran on headless LibreOffice. It worked, most of the time, and every time it didn't, it failed in a way that was somebody's whole afternoon.&lt;/p&gt;

&lt;p&gt;So we wrote our own: dxpdf, a single Rust binary that parses OOXML directly and renders with Skia. No Microsoft Office. No LibreOffice. No cloud API. This is the story of why we did that, what the architecture looks like, and the benchmark result that surprised us.&lt;/p&gt;

&lt;p&gt;It's open source. &lt;a href="https://github.com/nerdy-pro/dxpdf" rel="noopener noreferrer"&gt;Repo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Headless LibreOffice is a great renderer and a miserable production dependency: it serializes conversions per profile, spawns processes that hang, needs per-worker profile directories, and quietly changes its output between versions.&lt;/li&gt;
&lt;li&gt;We replaced it with a Rust binary that parses OOXML → lays it out in a unit-typed engine → rasterizes/draws with Skia → writes PDF.&lt;/li&gt;
&lt;li&gt;On an M3 Max it converts a 3-page doc in 170 ms and a 171-page doc in 420 ms — and the counter-intuitive part is that &lt;strong&gt;font resolution, not document size, dominates the cost.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;It's not a full LibreOffice replacement: &lt;strong&gt;74 OOXML features are fully implemented, 11 partial, 12 not yet supported.&lt;/strong&gt; We'll list the gaps.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What headless LibreOffice actually costs you
&lt;/h2&gt;

&lt;p&gt;First, credit where it's due: LibreOffice's format coverage is enormous. It has absorbed decades of edge cases from real-world &lt;code&gt;.doc&lt;/code&gt;/&lt;code&gt;.docx&lt;/code&gt;/&lt;code&gt;.odt&lt;/code&gt; files, and for a one-off "convert this file" it's genuinely hard to beat. If you run it interactively, none of the below applies.&lt;/p&gt;

&lt;p&gt;Running it &lt;em&gt;headless, in production, under load&lt;/em&gt; is a different animal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It serializes.&lt;/strong&gt; A &lt;code&gt;soffice&lt;/code&gt; instance is effectively single-conversion-at-a-time. To get concurrency you run N instances, and now you're a process supervisor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-instance profile directories.&lt;/strong&gt; Each instance wants its own &lt;code&gt;-env:UserInstallation&lt;/code&gt; profile dir. Share one and you get lock contention and corrupted profiles; don't share and you're managing a pool of temp dirs and cleaning them up when workers die mid-conversion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It hangs.&lt;/strong&gt; A malformed input, a missing font, a particular embedded object — and the process sits there forever instead of erroring. You end up wrapping every call in a timeout-and-kill, then reaping zombie &lt;code&gt;soffice&lt;/code&gt; children.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output drift.&lt;/strong&gt; The same input produces a slightly different PDF across LibreOffice versions — reflowed lines, shifted tables, changed font substitution. If your output is contractual or diffed, "upgrade the base image" becomes a visual-regression event.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's heavy.&lt;/strong&gt; Dragging a full office suite into a container image for one job is hundreds of megabytes and a slow cold start.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are bugs, exactly. They're what it means to run an interactive desktop application as a stateless service.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we actually needed
&lt;/h2&gt;

&lt;p&gt;Written as requirements, not complaints:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic output&lt;/strong&gt; — same bytes in, same bytes out, forever.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real concurrency&lt;/strong&gt; — no profile-dir dance, no per-conversion process to babysit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Predictable latency&lt;/strong&gt; — a tail we can reason about.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A small artifact&lt;/strong&gt; — one binary in a &lt;code&gt;scratch&lt;/code&gt;-ish container.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data stays on the box&lt;/strong&gt; — no cloud conversion API.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We looked at the alternatives before writing code, because writing a document renderer is not a decision you make lightly. Other &lt;code&gt;soffice&lt;/code&gt; wrappers inherit all of the above. Cloud APIs mean the document leaves your infrastructure and you pay per page. The Chromium/HTML route (render DOCX → HTML → print-to-PDF) loses fidelity on exactly the DOCX features that matter — pagination, headers/footers, section breaks. If our documents were simple enough for the HTML route, we'd have taken it. They weren't.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;

&lt;p&gt;dxpdf is a pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DOCX (OOXML) ──▶ parser ──▶ unit-typed layout engine ──▶ Skia ──▶ PDF
                                     │
                        HarfBuzz (shaping) · ICU4X (i18n)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few decisions worth calling out:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We parse OOXML directly.&lt;/strong&gt; A &lt;code&gt;.docx&lt;/code&gt; is a zip of XML (WordprocessingML). We read it and build our own document model rather than driving an office app to do it. That's the whole reason the output is deterministic — there's no interactive application state in the loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The layout engine is unit-typed.&lt;/strong&gt; OOXML mixes units freely: twips (1/1440 inch) for most measurements, EMUs (1/914400 inch) for DrawingML, half-points for font sizes, plus the pixels and points you eventually render into. The single most common way a document renderer goes wrong is a silent unit mismatch — a value in twips added to a value in points, and nobody notices until a margin is off by a factor of 20. So units are types. You cannot add a &lt;code&gt;Twip&lt;/code&gt; to a &lt;code&gt;Point&lt;/code&gt;; the compiler stops you. This one constraint has caught more layout bugs at compile time than any test suite could.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skia does the drawing.&lt;/strong&gt; The same rendering engine behind Chrome and Flutter. We get battle-tested text and vector rasterization instead of reimplementing it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HarfBuzz shapes, ICU4X internationalizes.&lt;/strong&gt; Text shaping (turning a string + font into positioned glyphs) is HarfBuzz. Line breaking, bidi, and locale data are ICU4X, with the locale data shipped as a single trimmed blob via &lt;code&gt;icu_provider_blob&lt;/code&gt; so there's no external ICU dependency to install.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest coverage gate
&lt;/h2&gt;

&lt;p&gt;dxpdf is not LibreOffice. As of 0.5.0: &lt;strong&gt;74 OOXML features fully implemented, 11 partial, 12 not yet supported.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The fully-supported set covers what most template-to-PDF pipelines actually use: paragraphs and runs, styles, tables, headers/footers, sections and page setup, lists and numbering, images, fields (dates, page numbers), fonts with bold/italic synthesis. The partial and unsupported set is where you should check your own documents before adopting — some advanced DrawingML, certain field types, and a handful of legacy constructs aren't there yet.&lt;/p&gt;

&lt;p&gt;We'd rather you hit "unsupported feature: X" with a clear error than get a subtly wrong PDF. If your documents live entirely in the supported set, dxpdf is a drop-in. If they don't, the gap list tells you exactly what to test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two things dxpdf does that headless LibreOffice doesn't
&lt;/h2&gt;

&lt;p&gt;Coverage isn't a one-way street. There are places we deliberately went past what a headless LibreOffice export gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Colour emoji in a headless Linux container.&lt;/strong&gt; Export a document with 🎉 or 🚀 through headless LibreOffice on a typical Linux server and you usually get monochrome outlines — or missing glyphs — because colour-emoji font formats (COLR/CPAL, CBDT/CBLC) aren't reliably handled without a desktop's font stack. dxpdf renders full-colour emoji through Skia + HarfBuzz whether or not there's a display, so the same document looks identical on your laptop and on a headless build agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mixed page formats in a single document.&lt;/strong&gt; OOXML lets every section carry its own &lt;code&gt;w:pgSz&lt;/code&gt; — so one DOCX can be portrait A4 for the body and a landscape A3 for a single wide table. Headless LibreOffice tends to flatten that to one page size and orientation for the whole export. dxpdf honours per-section page setup, so a mixed-format DOCX comes out as a PDF with genuinely different page sizes and orientations, page by page.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Performance — and the surprise
&lt;/h2&gt;

&lt;p&gt;Benchmarked on an Apple M3 Max, 30 runs, 5 warmup runs discarded:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Document&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;3 pages&lt;/td&gt;
&lt;td&gt;34 KB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;170 ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9 pages&lt;/td&gt;
&lt;td&gt;1.3 MB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;55 ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;171 pages&lt;/td&gt;
&lt;td&gt;14 MB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;420 ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Look at the first two rows. The &lt;strong&gt;9-page, 1.3 MB&lt;/strong&gt; document converts in &lt;strong&gt;55 ms&lt;/strong&gt;, but the &lt;strong&gt;3-page, 34 KB&lt;/strong&gt; one takes &lt;strong&gt;170 ms&lt;/strong&gt; — three times longer for a document a fraction of the size. That's not a typo, and it points at the real cost model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Font resolution, not document size, decides what a conversion costs.&lt;/strong&gt; Resolving a font the OS has to find and load runs &lt;strong&gt;120–185 ms&lt;/strong&gt;; a font that's embedded in the document or already cached is about &lt;strong&gt;4 ms&lt;/strong&gt;. The 3-page document referenced fonts we had to go find on the system; the 9-page one used embedded/cached fonts, so it skipped the expensive part almost entirely. The 171-page document is dominated by actual layout work, which is why it scales with content the way you'd expect.&lt;/p&gt;

&lt;p&gt;The practical takeaway if you run this at scale: &lt;strong&gt;embed your fonts, or warm a font cache.&lt;/strong&gt; Do that and small-document latency collapses from ~170 ms to tens of milliseconds. It's the highest-leverage knob, and it's invisible until you measure per-stage instead of per-document.&lt;/p&gt;

&lt;h2&gt;
  
  
  Operating it
&lt;/h2&gt;

&lt;p&gt;The contrast with the old pipeline is the point:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One binary.&lt;/strong&gt; Ships as a CLI, a Python package on PyPI, and a Debian &lt;code&gt;.deb&lt;/code&gt;. The container is a binary, not an office suite.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency is just processes/threads.&lt;/strong&gt; No profile directories, no pool, no lock file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Determinism.&lt;/strong&gt; Same input → same output bytes, across runs and across machines. Diffing generated PDFs in CI actually works.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failure is an error, not a hang.&lt;/strong&gt; Unsupported input returns a clear error instead of a wedged process you have to time out and kill.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's new in 0.5.0
&lt;/h2&gt;

&lt;p&gt;0.5.0 was our internationalization release, and it's a good illustration of the "we control the whole stack" upside:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;UAX #14 line breaking&lt;/strong&gt; for space-less scripts — Thai, Lao, Khmer, Burmese — where you can't just break on spaces.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UAX #9 bidirectional text&lt;/strong&gt; with rule L4 mirroring for Arabic and Hebrew.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HarfBuzz shaping&lt;/strong&gt; for cursive-joining scripts (Arabic, Syriac, N'Ko, Mongolian, Adlam).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CLDR-driven localization&lt;/strong&gt;: region-aware decimal separators, localized DATE/TIME fields, spelled-out numbers in English, German, French, and Spanish.&lt;/li&gt;
&lt;li&gt;Font synthesis for bold/oblique, pagination fixes, Windows CI, Debian packaging.&lt;/li&gt;
&lt;li&gt;And our &lt;strong&gt;first external contribution&lt;/strong&gt; — Russian numbering formats and list-label fixes. That one made our week.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Full notes: &lt;a href="https://nerdy.pro/blog/dxpdf-0-5-0-release" rel="noopener noreferrer"&gt;dxpdf 0.5.0&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How do I convert DOCX to PDF without LibreOffice?&lt;/strong&gt;&lt;br&gt;
Use a library or binary that parses OOXML and renders directly. dxpdf is one: a standalone Rust binary (also a Python package and &lt;code&gt;.deb&lt;/code&gt;) that turns DOCX into PDF with no Office or LibreOffice dependency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why is headless LibreOffice unreliable at scale?&lt;/strong&gt;&lt;br&gt;
It's an interactive desktop app running as a service. It serializes conversions per profile, needs managed profile directories, can hang on bad input, and changes its output between versions — all manageable, none free.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is dxpdf production-ready?&lt;/strong&gt;&lt;br&gt;
For documents inside its supported feature set, yes — it's what we run. Check the coverage gate (74 full / 11 partial / 12 unsupported OOXML features) against your own templates first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does dxpdf need Microsoft Office installed?&lt;/strong&gt;&lt;br&gt;
No. It parses the OOXML directly. No Office, no LibreOffice, no cloud service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How fast is it?&lt;/strong&gt;&lt;br&gt;
On an M3 Max, 170 ms for a 3-page doc and 420 ms for 171 pages. Small-document time is dominated by font resolution — embed fonts or warm a cache and it drops to tens of ms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I use it from Python?&lt;/strong&gt;&lt;br&gt;
Yes, it's published on PyPI in addition to the CLI and Debian package.&lt;/p&gt;




&lt;p&gt;dxpdf is open source — if you've ever fought a &lt;code&gt;soffice&lt;/code&gt; pipeline at 3am, we'd love for you to &lt;a href="https://github.com/nerdy-pro/dxpdf" rel="noopener noreferrer"&gt;try it&lt;/a&gt;, star it, or file the one DOCX feature you need next. And if you build systems like this — Rust, rendering, or Flutter — that's &lt;a href="https://nerdy.pro/services" rel="noopener noreferrer"&gt;what we do&lt;/a&gt; at nerdy.pro.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>pdf</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>How to Review AI-Generated Flutter Code (Before It Breaks Production)</title>
      <dc:creator>Ilya Nixan</dc:creator>
      <pubDate>Tue, 28 Jul 2026 06:30:00 +0000</pubDate>
      <link>https://dev.to/nixan/how-to-review-ai-generated-flutter-code-before-it-breaks-production-486f</link>
      <guid>https://dev.to/nixan/how-to-review-ai-generated-flutter-code-before-it-breaks-production-486f</guid>
      <description>&lt;p&gt;Every unsupervised AI agent we've reviewed that wrote Flutter code made the same seven mistakes.&lt;/p&gt;

&lt;p&gt;These aren't typos or stylistic differences. They're structural failures that compound—bad state management plus missing tests plus hardcoded colors means the codebase becomes expensive to theme, hard to test, and impossible to maintain at scale.&lt;/p&gt;

&lt;p&gt;Here's a small one to set the tone: a developer asked an agent to implement a GET request to an external service in a Dart project. The agent's solution was to shell out to &lt;code&gt;curl&lt;/code&gt; via &lt;code&gt;Process.run&lt;/code&gt; and parse the stdout.&lt;/p&gt;

&lt;p&gt;Not &lt;code&gt;package:http&lt;/code&gt;. Not &lt;code&gt;dio&lt;/code&gt;. Not even &lt;code&gt;dart:io&lt;/code&gt;'s own &lt;code&gt;HttpClient&lt;/code&gt;. A subprocess call to a CLI tool, inside a language that's had first-class HTTP clients since Dart 1.0.&lt;/p&gt;

&lt;p&gt;That one is worth sitting with, because it's not really a Flutter problem — it's the whole pattern in miniature. The agent wasn't "wrong" that curl can make a GET request. It optimized for &lt;em&gt;"this pattern appears constantly in training data"&lt;/em&gt; over &lt;em&gt;"this is the idiomatic way to do it in the language I'm currently writing."&lt;/em&gt; Bash and curl show up in approximately every tutorial, README, and Stack Overflow answer ever written. &lt;code&gt;package:http&lt;/code&gt; shows up in Dart-specific docs. Given no other constraint, the agent reached for the statistically dominant pattern, not the contextually correct one.&lt;/p&gt;

&lt;p&gt;The seven gaps below are the same failure mode, just less obvious than "shells out to curl." Here's what we found, with real code examples and the fixes that work.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Recomputing Derived State
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Problem:&lt;/strong&gt; Agents recalculate the same values across multiple locations instead of maintaining one source of truth.&lt;/p&gt;

&lt;p&gt;Imagine a checkout flow where the cart total is computed three separate ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the checkout page: &lt;code&gt;(items.sum + tax) - discount&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;In the footer: &lt;code&gt;items.sum - discount + tax&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;In the order summary: &lt;code&gt;(items.sum - discount) * (1 + taxRate)&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Different calculations. Same semantic meaning. One will break first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt; Derive values once in the state layer using streams. Let all widgets read from that single source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Instead of computing at call sites:&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Compute once, derive everywhere:&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;totalStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;itemsStream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;tax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;taxRate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;withDiscount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;appliedDiscount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;withDiscount&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;tax&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The principle: &lt;strong&gt;if it can be computed from state, it is not state.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every recomputation is a sync bug waiting to happen.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Missing Tests and Benchmarks
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Problem:&lt;/strong&gt; "It ships the feature and stops. No widget tests, no goldens, no benchmark."&lt;/p&gt;

&lt;p&gt;Agents don't write tests because they can't run them in isolation. They generate code, you integrate it, and only then do you see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Buttons don't disable while submitting (no state test)&lt;/li&gt;
&lt;li&gt;Error messages don't clear when the user retypes (no widget test)&lt;/li&gt;
&lt;li&gt;Layout overflows at 320pt wide (no golden test)&lt;/li&gt;
&lt;li&gt;Frame rate drops from 60fps to 12fps on a real device (no benchmark)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tests become feedback loops the agent can iterate against. Golden files provide visual regression detection—catching overflow, dark-mode contrast failures, and text truncation at 200% scale automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt; Write tests &lt;em&gt;first&lt;/em&gt;, then tests become the specification:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'SearchPage'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;testWidgets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'shows spinner while loading'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tester&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;tester&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;pumpWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SearchPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;state:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SearchState&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;byType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CircularProgressIndicator&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;findsOneWidget&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="n"&gt;testWidgets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'shows error and allows retry'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tester&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SearchState&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;error:&lt;/span&gt; &lt;span class="n"&gt;AppError&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;network&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;tester&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;pumpWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SearchPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;state:&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Network error'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;findsOneWidget&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;tester&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;byIcon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Icons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;byType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CircularProgressIndicator&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;findsOneWidget&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="n"&gt;testWidgets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'respects 200% text scale'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tester&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;tester&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;binding&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;window&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;textScaleFactorTestValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;addTearDown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tester&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;binding&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;window&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;clearTextScaleFactorTestValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;tester&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;pumpWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SearchPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;state:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SearchState&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;idle&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
    &lt;span class="c1"&gt;// No overflows&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;byType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OverflowBox&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;findsNothing&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. Flag Pyramids Instead of State Machines
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Problem:&lt;/strong&gt; Agents use loose parallel boolean fields and branch over them, creating unmaintainable conditionals.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;_isLoading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;_error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Hit&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="c1"&gt;// This represents 8 possible combinations, 4 of which are invalid&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_isLoading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;CircularProgressIndicator&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_error&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_error&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_items&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'No results'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;ListView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="n"&gt;_items&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The UI code doesn't express the actual state machine. It's guessing based on flag combinations. And if loading completes &lt;em&gt;before&lt;/em&gt; you clear the error, the UI gets confused.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt; Use sealed state hierarchies. Express all valid states explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;sealed&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SearchState&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Idle&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;SearchState&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Loading&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;SearchState&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Success&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;SearchState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Hit&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Failed&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;SearchState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;AppError&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Empty&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;SearchState&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="c1"&gt;// The UI is now explicit:&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;Idle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;SearchHint&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;Loading&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;AppSpinner&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;Failed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;AppErrorView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;EmptyResultsPlaceholder&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;SearchResultsList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No invalid combinations. No pyramids of if-statements. The compiler enforces exhaustiveness.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Quick question:&lt;/strong&gt; have you caught the flag-pyramid anti-pattern in a PR before? That's usually the first place AI-generated Flutter code goes wrong — four more gaps below.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Design Tokens in constants.dart
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Problem:&lt;/strong&gt; Agents create constant files and hardcode values at every call site instead of using &lt;code&gt;ThemeData&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// constants.dart&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;primaryColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xFF1F77D2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;lightGrey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xFFF5F5F5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;baseTextSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;16.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Usage everywhere:&lt;/span&gt;
&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Hello'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;style:&lt;/span&gt; &lt;span class="n"&gt;TextStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;color:&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xFF1F77D2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nl"&gt;fontSize:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;color:&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xFFF5F5F5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This cascades into four problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Dark mode requires 200+ ternaries:&lt;/strong&gt; &lt;code&gt;color: isDark ? darkGrey : lightGrey&lt;/code&gt; at every call site&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design rebrands are massive diffs:&lt;/strong&gt; Change primary color → change 300 files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accessibility breaks:&lt;/strong&gt; Hardcoded 16pt breaks at 200% text scale&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Theme overrides stop working:&lt;/strong&gt; Nothing asks &lt;code&gt;Theme.of(context)&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt; Express the design system &lt;em&gt;once&lt;/em&gt; in &lt;code&gt;ThemeData&lt;/code&gt;, &lt;code&gt;TextTheme&lt;/code&gt;, and component themes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;MaterialApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;theme:&lt;/span&gt; &lt;span class="n"&gt;ThemeData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;colorScheme:&lt;/span&gt; &lt;span class="n"&gt;ColorScheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromSeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;seedColor:&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xFF1F77D2&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="nl"&gt;textTheme:&lt;/span&gt; &lt;span class="n"&gt;TextTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;bodyMedium:&lt;/span&gt; &lt;span class="n"&gt;TextStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;fontSize:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nl"&gt;titleLarge:&lt;/span&gt; &lt;span class="n"&gt;TextStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;fontSize:&lt;/span&gt; &lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;fontWeight:&lt;/span&gt; &lt;span class="n"&gt;FontWeight&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nl"&gt;darkTheme:&lt;/span&gt; &lt;span class="n"&gt;ThemeData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;colorScheme:&lt;/span&gt; &lt;span class="n"&gt;ColorScheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromSeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;seedColor:&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xFF1F77D2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nl"&gt;brightness:&lt;/span&gt; &lt;span class="n"&gt;Brightness&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Usage: widgets ask the theme&lt;/span&gt;
&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Hello'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;style:&lt;/span&gt; &lt;span class="n"&gt;Theme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;textTheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bodyMedium&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;color:&lt;/span&gt; &lt;span class="n"&gt;Theme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;colorScheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;surface&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rebrand once. Every widget updates automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Each Screen Has Its Own UX Vocabulary
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Problem:&lt;/strong&gt; 40 defensible screens that collectively feel like "40 tutorials stitched together."&lt;/p&gt;

&lt;p&gt;A user loads the home screen and sees &lt;code&gt;CircularProgressIndicator&lt;/code&gt;. They navigate to the feed and see a custom shimmer skeleton. They check their profile and see a branded loading animation.&lt;/p&gt;

&lt;p&gt;Each is correct. Together, they're incoherent.&lt;/p&gt;

&lt;p&gt;Same pattern with errors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Home: &lt;code&gt;SnackBar&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Feed: inline error text&lt;/li&gt;
&lt;li&gt;Profile: &lt;code&gt;AlertDialog&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Settings: modal bottom sheet&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And destructive actions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Delete account → &lt;code&gt;AlertDialog&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Remove item → bottom sheet confirmation&lt;/li&gt;
&lt;li&gt;Leave group → inline text with undo button&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Invisible Failures:&lt;/strong&gt;&lt;br&gt;
The agent can't see these because it doesn't render frames:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Overflow at 320pt (narrow phone in landscape)&lt;/li&gt;
&lt;li&gt;Tap targets under 44/48dp (fails accessibility)&lt;/li&gt;
&lt;li&gt;Keyboard covering input fields (no onscreen detection)&lt;/li&gt;
&lt;li&gt;Text truncation at 200% scale (readability)&lt;/li&gt;
&lt;li&gt;Content under notches or behind home indicators (safe area)&lt;/li&gt;
&lt;li&gt;Contrast failures in dark mode (WCAG A/AA)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt; Decide your interaction vocabulary &lt;strong&gt;once&lt;/strong&gt;. How do loading / errors / confirmations / validation feedback / empty states work? Implement that pattern everywhere. Test on real devices:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Define once:&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppLoadingOverlay&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;AppLoadingOverlay&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;key:&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Center&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;CircularProgressIndicator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;valueColor:&lt;/span&gt; &lt;span class="n"&gt;AlwaysStoppedAnimation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Theme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;colorScheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;primary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppErrorView&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;AppError&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;VoidCallback&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;onRetry&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;AppErrorView&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onRetry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;key:&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Center&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;mainAxisAlignment:&lt;/span&gt; &lt;span class="n"&gt;MainAxisAlignment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;center&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;Icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Icons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;error_outline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;size:&lt;/span&gt; &lt;span class="mi"&gt;56&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;color:&lt;/span&gt; &lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;red&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;SizedBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;displayMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;textAlign:&lt;/span&gt; &lt;span class="n"&gt;TextAlign&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;center&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;onRetry&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;...[&lt;/span&gt;
          &lt;span class="n"&gt;SizedBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="n"&gt;ElevatedButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;onPressed:&lt;/span&gt; &lt;span class="n"&gt;onRetry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Retry'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Use everywhere: home, feed, profile&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;Loading&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;AppLoadingOverlay&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;Failed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;AppErrorView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;error:&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;onRetry:&lt;/span&gt; &lt;span class="n"&gt;onRetry&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  6. String Concatenation Instead of Localization
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Problem:&lt;/strong&gt; Agents bake English assumptions into the code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;$count&lt;/span&gt;&lt;span class="s"&gt; items'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// Output: "1 items" (grammatically wrong)&lt;/span&gt;

&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;$count&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;${count == 1 ? "item" : "items"}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Wrong for Russian (4 plural forms), Arabic (6 forms), Polish (3 forms)&lt;/span&gt;

&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Hello, '&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;'!'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// English word order locked in&lt;/span&gt;

&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;${d.day}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="si"&gt;${d.month}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="si"&gt;${d.year}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// US date format; wrong for most of world&lt;/span&gt;

&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="err"&gt;\$&lt;/span&gt;&lt;span class="si"&gt;${amount.toStringAsFixed(2)}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// $ before number (US), but euros go after: "10 €", Arabic: "﷼ ١٠"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;None of these are internationalization—they're English with variables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt; Use ARB files with &lt;code&gt;gen_l10n&lt;/code&gt; and &lt;code&gt;intl&lt;/code&gt; package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# l10n.yaml&lt;/span&gt;
&lt;span class="na"&gt;arb-dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;lib/l10n&lt;/span&gt;
&lt;span class="na"&gt;template-arb-file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app_en.arb&lt;/span&gt;
&lt;span class="na"&gt;output-localization-file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app_localizations.dart&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;lib/l&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="err"&gt;n/app_en.arb&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"unreadMessages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{count, plural, =0{No new messages} one{{count} message} other{{count} messages}}"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;lib/l&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="err"&gt;n/app_ru.arb&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"unreadMessages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{count, plural, =0{Нет новых сообщений} =1{{count} сообщение} few{{count} сообщения} other{{count} сообщений}}"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l10n&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;unreadMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;// Use NumberFormat for currency&lt;/span&gt;
&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NumberFormat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;locale:&lt;/span&gt; &lt;span class="s"&gt;'en_US'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;// Use DateFormat for dates&lt;/span&gt;
&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DateFormat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;yMd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l10n&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;localeName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;// Use EdgeInsetsDirectional for RTL&lt;/span&gt;
&lt;span class="n"&gt;Padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;padding:&lt;/span&gt; &lt;span class="n"&gt;EdgeInsetsDirectional&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;start:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Hello'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One source of truth for each language. Plurals, gender, word order—all handled by ICU.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Uniform Simplification (in the Wrong Places)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Problem:&lt;/strong&gt; Agents flatten complexity uniformly—collapsing load-bearing logic while over-abstracting trivial code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Over-simplified (dangerous):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'Payment failed'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This swallows three completely different failures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Network timeout (retry is safe)&lt;/li&gt;
&lt;li&gt;Declined card (user's problem, don't retry)&lt;/li&gt;
&lt;li&gt;Idempotency conflict (retry will double-charge)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All three are now identical. Retry logic is broken.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Over-abstracted (waste):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 14-parameter widget for 5 button styles&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppButton&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;VoidCallback&lt;/span&gt; &lt;span class="n"&gt;onPressed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;foregroundColor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;EdgeInsets&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;borderRadius&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;elevation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isEnabled&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// ... 5 more parameters&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;AppButton&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onPressed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// vs. five concrete widgets&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PrimaryButton&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SecondaryButton&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TertiaryButton&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generic version is harder to use and reason about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt; Complexity is a budget. Spend it on genuinely hard domains:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Load-bearing (explicit, verbose):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Payments and financial transactions&lt;/li&gt;
&lt;li&gt;Offline sync and conflict resolution&lt;/li&gt;
&lt;li&gt;Authentication and token refresh&lt;/li&gt;
&lt;li&gt;Idempotency and deduplication&lt;/li&gt;
&lt;li&gt;Error handling with recovery strategies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Trivial (collapse aggressively):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Five similar list tiles → one widget&lt;/li&gt;
&lt;li&gt;Settings screen with 10 toggles → no abstraction&lt;/li&gt;
&lt;li&gt;Button variants → concrete subclasses, not parameters&lt;/li&gt;
&lt;li&gt;Utility functions → don't create BaseRepository
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Explicit payment error handling&lt;/span&gt;
&lt;span class="kd"&gt;sealed&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PaymentError&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NetworkTimeout&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;PaymentError&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CardDeclined&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;PaymentError&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;IdempotencyConflict&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;PaymentError&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;idempotencyKey:&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kd"&gt;on&lt;/span&gt; &lt;span class="n"&gt;PaymentError&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;NetworkTimeout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_retryPayment&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;CardDeclined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_showCardError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;IdempotencyConflict&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_confirmChargeAlready&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Trivial UI: no abstraction needed&lt;/span&gt;
&lt;span class="n"&gt;ListView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;SettingsTile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Notifications'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;isEnabled:&lt;/span&gt; &lt;span class="n"&gt;notificationsOn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;onChange:&lt;/span&gt; &lt;span class="p"&gt;...),&lt;/span&gt;
    &lt;span class="n"&gt;SettingsTile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Dark Mode'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;isEnabled:&lt;/span&gt; &lt;span class="n"&gt;darkModeOn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;onChange:&lt;/span&gt; &lt;span class="p"&gt;...),&lt;/span&gt;
    &lt;span class="n"&gt;SettingsTile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Analytics'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;isEnabled:&lt;/span&gt; &lt;span class="n"&gt;analyticsOn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;onChange:&lt;/span&gt; &lt;span class="p"&gt;...),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;The core principle:&lt;/strong&gt; Complexity is a budget. Spend it on genuinely hard domains — payments, offline sync, auth, idempotency. Collapse aggressively everywhere else.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why Flutter Amplifies These Issues
&lt;/h2&gt;

&lt;p&gt;Four reasons why Flutter makes these problems louder than in other frameworks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;UI is code:&lt;/strong&gt; No stylesheet layer to enforce consistency. No centralized theme your linter can verify.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Everything compiles:&lt;/strong&gt; No compiler error for "you should use &lt;code&gt;Theme.of(context)&lt;/code&gt; here." Hardcoded colors feel fine until day 50.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent cannot see frames:&lt;/strong&gt; No visibility into whether the layout actually renders at 320pt, whether text is readable at 200% scale, or whether the keyboard covers the input.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Outdated training data:&lt;/strong&gt; Models confidently generate deprecated APIs (&lt;code&gt;RaisedButton&lt;/code&gt;, &lt;code&gt;WillPopScope&lt;/code&gt;, &lt;code&gt;MaterialStateProperty&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The Core Insight: Orchestration vs. Unsupervised Agents
&lt;/h2&gt;

&lt;p&gt;Every one of these failures shows up in every language.&lt;/p&gt;

&lt;p&gt;Flutter just makes them louder.&lt;/p&gt;

&lt;p&gt;The difference is orchestration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Set invariants first:&lt;/strong&gt; Theme, state model, localization, folder structure, error taxonomy—decide before anyone (human or agent) writes code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build feedback loops:&lt;/strong&gt; Strict linting (no hardcoded colors), test coverage enforcement, frame-time budgets, CI checks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review decisions, not lines:&lt;/strong&gt; Verify state consistency, theme usage, derivation sources, localization—not individual diffs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open the app:&lt;/strong&gt; Test on real devices at 200% text scale, dark mode, throttled network, both languages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When AI agents operate inside a codebase with strong invariants and feedback loops, they move &lt;em&gt;fast&lt;/em&gt;. When they create codebases from scratch without those constraints, they move fast &lt;em&gt;in the wrong direction&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Familiar beats clever essentially always. And the only way that agent speed becomes an asset instead of a liability is through structural constraints decided before coding begins.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Derive once, read everywhere&lt;/strong&gt; — if it can be computed from state, it isn't state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tests are the spec&lt;/strong&gt; — write them first so they become a feedback loop, not an afterthought.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model states explicitly&lt;/strong&gt; — sealed classes over boolean flags; let the compiler catch invalid combinations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Centralize the design system&lt;/strong&gt; — &lt;code&gt;ThemeData&lt;/code&gt; once, not hardcoded values at 200 call sites.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pick one interaction vocabulary&lt;/strong&gt; — loading, errors, confirmations should feel identical across every screen.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Localize from day one&lt;/strong&gt; — ARB files and ICU plurals, not string concatenation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat complexity as a budget&lt;/strong&gt; — spend it on payments/auth/sync, collapse everything trivial.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;strong&gt;Discussion:&lt;/strong&gt; What's the #1 AI-generated code smell you've caught in review? Drop it in the comments — collecting the worst offenders for a follow-up post.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Want the full deep-dive with more examples?&lt;/strong&gt; Read the complete article on &lt;a href="https://nerdy.pro/blog/ai-agents-struggle-with-flutter" rel="noopener noreferrer"&gt;nerdy.pro&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>ai</category>
      <category>architecture</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
