<?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: GoodBarber</title>
    <description>The latest articles on DEV Community by GoodBarber (goodbarber).</description>
    <link>https://dev.to/goodbarber</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%2Forganization%2Fprofile_image%2F14004%2F83b22df0-0fa0-4978-8d15-833839b116e9.png</url>
      <title>DEV Community: GoodBarber</title>
      <link>https://dev.to/goodbarber</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/goodbarber"/>
    <language>en</language>
    <item>
      <title>Everyone is going back to native. We never left, and the bet paid in a way we didn't expect</title>
      <dc:creator>Mathieu Poli</dc:creator>
      <pubDate>Fri, 11 Sep 2026 11:45:22 +0000</pubDate>
      <link>https://dev.to/goodbarber/everyone-is-going-back-to-native-we-never-left-and-the-bet-paid-in-a-way-we-didnt-expect-58lp</link>
      <guid>https://dev.to/goodbarber/everyone-is-going-back-to-native-we-never-left-and-the-bet-paid-in-a-way-we-didnt-expect-58lp</guid>
      <description>&lt;p&gt;You've probably read it by now: Shopify is moving all of its mobile apps back to Swift and Kotlin, six years after going all-in on React Native. &lt;a href="https://shopify.engineering/back-to-native" rel="noopener noreferrer"&gt;In their own words&lt;/a&gt;, "building the same feature in Swift and Kotlin no longer carries the cost it used to," because coding agents now do enough of the work in between. We never left, and I want to be precise about why. We bet on native in 2011 for one reason: iOS and Android would outlive every layer stacked on top of them. Every layer since has been temporary, which we expected. What ended the detour was AI, which we didn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  We bet on native because it was the layer that would last
&lt;/h2&gt;

&lt;p&gt;When people ask why an app builder compiles Swift and Kotlin instead of shipping one cross-platform codebase, they expect an answer about quality, and the honest answer is about time. In 2011 we had to decide what to build our engines on, knowing we'd carry that choice for a decade or more, so the question we asked was which layer would still be there in ten years.&lt;br&gt;
The platforms would. Apple and Google were not going to abandon their operating systems, their toolchains or their SDKs. Everything in between, the frameworks promising to spare you the platforms, had to earn a decade of existence, and nothing about them suggested they would. So we invested in the layer that was certain to remain, and we treated everything stacked on it as temporary, however popular it was that year. That's the whole bet. It cost us more per feature, and it bought us a foundation we have never had to rewrite because a dependency died underneath it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Every layer since has been temporary
&lt;/h2&gt;

&lt;p&gt;The waves came on schedule, roughly one every few years, &lt;a href="https://dev.to/goodbarber/the-freedom-you-dont-want-why-infinite-customization-is-a-tax-not-a-feature-2og6"&gt;and each was presented to us as our replacement&lt;/a&gt;. 2011: PhoneGap, bought by Adobe that year, and the first serious promise of one web codebase for every platform. 2015: React Native. 2016: Xamarin, bought by Microsoft, and the promise of C# everywhere. 2018: Flutter 1.0. We evaluated two of them seriously, React Native a couple of years after it shipped and Flutter once it had matured, and both times the same question decided it: what's the lifespan of this layer, compared with the lifespan of what's underneath?&lt;br&gt;
Then the answers arrived. &lt;a href="https://cordova.apache.org/announcements/2020/08/14/goodbye-phonegap.html" rel="noopener noreferrer"&gt;Adobe discontinued PhoneGap in 2020&lt;/a&gt;. &lt;a href="https://dotnet.microsoft.com/en-us/platform/support/policy/xamarin" rel="noopener noreferrer"&gt;Microsoft ended Xamarin support in 2024&lt;/a&gt;. And this year Shopify, which had bet on the most successful of those layers, announced its way back to the platforms. The intermediate layer kept changing names while iOS and Android kept theirs. None of that makes the people who chose those frameworks wrong: for a company shipping one app, avoiding a second codebase was a rational bet, and I'd have made it in their seat. It's just a bet on a shorter horizon than ours.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we could afford a bet nobody else could
&lt;/h2&gt;

&lt;p&gt;What made our choice possible is simpler than vision, and I'd rather state it plainly. An app builder doesn't build one app; it builds engines that render thousands. At GoodBarber, our iOS engine is Swift, our Android engine is Kotlin, and a third engine produces a Progressive Web App; &lt;a href="https://dev.to/goodbarber/the-rendering-engine-how-a-no-code-project-becomes-a-smooth-native-app-nd4"&gt;a single project description feeds all three&lt;/a&gt;. We build three times, but we build three times once, and every app the platform renders amortizes those three engines a little more. The duplication cost that pushed the industry toward hybrid, divided by thousands of apps, was simply our business model. The reason to leave native never existed on our side of the fence.&lt;br&gt;
The bill is real, though. Three engines mean three expertises to hire and three implementations to keep in step, and the parity work between platforms is a permanent line in our budget. It's payable for one reason only: it's paid once, and divided by thousands.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the bet bought
&lt;/h2&gt;

&lt;p&gt;Most of what native bought us lives below what users can name. The sensory layer: haptic feedback, parallax, motion, a tab bar that floats, scrolling with the system's exact physics, transitions that belong to the OS. A media player that keeps playing when the screen goes dark and follows the user from screen to screen. Local notifications that fire from the app binary itself when someone enters a geofence or walks near a beacon, with no server involved, which only exists when the app is real native code running with the operating system's own permissions. Each platform's own idioms, because three engines can be right in three worlds instead of average in two: the share sheet is Apple's on iOS and Android's on Android. And when Apple ships a new primitive at WWDC, we can adopt it as soon as it lands, with no framework in between that has to wrap it first.&lt;/p&gt;

&lt;h2&gt;
  
  
  It ended, but not the way we pictured
&lt;/h2&gt;

&lt;p&gt;We expected the intermediate layers to wear out one by one, and we expected time to settle the bet. What settled it was AI, and from a direction we hadn't imagined. Instead of waiting for the hybrid frameworks to fade, it removed the only reason anyone had to use them. The whole case for a shared codebase was the cost of building twice, and models and agents now carry enough of the translation, the testing and the review across platforms that duplication stopped deciding anything. A team shipping one app can land today where we've been since 2011, on the platform's own languages with the platform's own tooling. Two roads to the same place, fifteen years apart: agents for them, architecture for us.&lt;br&gt;
For us, AI lands on top of that structure &lt;a href="https://dev.to/goodbarber/ai-didnt-kill-the-framework-it-made-it-more-valuable-3n5p"&gt;rather than instead of it&lt;/a&gt;. Generated sections live inside apps whose engines were already native, and the model works with first-party toolchains, &lt;a href="https://dev.to/goodbarber/trust-is-the-net-not-the-eyes-can-you-ship-ai-built-software-you-cant-review-1gfm"&gt;where it can test its own work fastest&lt;/a&gt;. We bet that the platforms would outlast everything built on top of them. They did. We just thought the proof would take longer, and come from somewhere else. The bet paid, not the way we pictured, which is usually how bets pay.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Mathieu Poli — Head of Frontend Engineering @ GoodBarber. &lt;br&gt;
I teach and write about frontend engineering, product design, and AI — and everything that happens when the three meet.&lt;br&gt;
X: &lt;a href="https://x.com/hellomathieup" rel="noopener noreferrer"&gt;@hellomathieup&lt;/a&gt; · LinkedIn: &lt;a href="https://www.linkedin.com/in/hellomathieup/" rel="noopener noreferrer"&gt;hellomathieup&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mobile</category>
      <category>ios</category>
      <category>android</category>
      <category>nocode</category>
    </item>
    <item>
      <title>Fourteen years of blog posts, seven languages, one laptop: an open-weight model did our hreflang backfill</title>
      <dc:creator>Pierre- Laurent Medori</dc:creator>
      <pubDate>Thu, 10 Sep 2026 11:37:50 +0000</pubDate>
      <link>https://dev.to/goodbarber/fourteen-years-of-blog-posts-seven-languages-one-laptop-an-open-weight-model-did-our-hreflang-kpo</link>
      <guid>https://dev.to/goodbarber/fourteen-years-of-blog-posts-seven-languages-one-laptop-an-open-weight-model-did-our-hreflang-kpo</guid>
      <description>&lt;p&gt;In May I had 5,892 blog posts spread over seven hosts, the oldest dated November 7, 2011, and not one field anywhere that said which post was the translation of which. Same company, same blog: French on fr.goodbarber.com, English on www, then es, it, pt, de and nl on their own subdomains. In our CMS each language blog is a separate site, and for fourteen years a translation was published as a new, unrelated article. Google's hreflang wants, for every article, the full list of its language versions, and it wants the list on every one of them. We had the articles. We did not have the list.&lt;/p&gt;

&lt;p&gt;One line of context so you know where I stand: I run engineering at &lt;a href="https://www.goodbarber.com/" rel="noopener noreferrer"&gt;GoodBarber&lt;/a&gt;, a no-code app platform headquartered in Ajaccio, Corsica. I have no religion about where a model runs. This post is about one job where a two-year-old open-weight model on a laptop was the right tool, with the numbers to show it, and about why we are hosting a Hacktoberfest Fest on exactly this subject on October 21.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is a judgment call, not a join
&lt;/h2&gt;

&lt;p&gt;Every obvious key fails. The number at the end of each URL (&lt;code&gt;-a1332&lt;/code&gt; in French, &lt;code&gt;-a1486&lt;/code&gt; in English for the same article) is a per-blog counter. Publication dates are days apart for a translation, sometimes months. Titles are translated freely, and the monthly "What's new at GoodBarber" post carries the same title every month in every language. Slug overlap works when the translator kept the English words and fails precisely when they did their job.&lt;/p&gt;

&lt;p&gt;A person would read the summary of the French article, read the summaries of the English candidates, and decide. That is a language model's job. The question was which one, and where.&lt;/p&gt;

&lt;h2&gt;
  
  
  The recipe: four scripts, one afternoon of GPU time
&lt;/h2&gt;

&lt;p&gt;Hardware: a MacBook Pro with an M3 Max and 48 GB of memory. Runtime: &lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;. Two models, both open weights: &lt;a href="https://ollama.com/library/gemma2" rel="noopener noreferrer"&gt;gemma2:27b&lt;/a&gt;, Google's June 2024 release, 15 GB on disk at the default 4-bit quantization, under the &lt;a href="https://ai.google.dev/gemma/terms" rel="noopener noreferrer"&gt;Gemma terms&lt;/a&gt;; and &lt;a href="https://ollama.com/library/bge-m3" rel="noopener noreferrer"&gt;bge-m3&lt;/a&gt;, BAAI's multilingual embedding model, MIT licensed, 1.2 GB, 1,024 dimensions. Around 600 lines of Python with httpx, no framework, no vector database.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Crawl, deterministically
&lt;/h3&gt;

&lt;p&gt;One script dumps every article of every language blog into a JSONL file, one line per article, from the CMS listing API. It flushes each page as it lands and skips ids already on disk, so it survives being interrupted.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;blog&lt;/th&gt;
&lt;th&gt;articles&lt;/th&gt;
&lt;th&gt;first post&lt;/th&gt;
&lt;th&gt;last post&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;fr&lt;/td&gt;
&lt;td&gt;1,085&lt;/td&gt;
&lt;td&gt;2011-11-07&lt;/td&gt;
&lt;td&gt;2026-04-24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;en&lt;/td&gt;
&lt;td&gt;1,083&lt;/td&gt;
&lt;td&gt;2011-11-07&lt;/td&gt;
&lt;td&gt;2026-04-24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;es&lt;/td&gt;
&lt;td&gt;893&lt;/td&gt;
&lt;td&gt;2013-01-24&lt;/td&gt;
&lt;td&gt;2026-04-23&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;it&lt;/td&gt;
&lt;td&gt;892&lt;/td&gt;
&lt;td&gt;2013-01-24&lt;/td&gt;
&lt;td&gt;2026-04-23&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pt&lt;/td&gt;
&lt;td&gt;778&lt;/td&gt;
&lt;td&gt;2013-01-24&lt;/td&gt;
&lt;td&gt;2026-04-23&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;de&lt;/td&gt;
&lt;td&gt;683&lt;/td&gt;
&lt;td&gt;2013-11-25&lt;/td&gt;
&lt;td&gt;2026-04-23&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;nl&lt;/td&gt;
&lt;td&gt;478&lt;/td&gt;
&lt;td&gt;2013-11-25&lt;/td&gt;
&lt;td&gt;2026-04-23&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;5,892 articles, crawled on the morning of May 4, 2026.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Narrow before you ask
&lt;/h3&gt;

&lt;p&gt;French is the pivot: it is the source language of most of our posts and the largest blog. For each French article and each of the six other languages, the candidates are the articles of that language published within 90 days of the French one, closest first, twenty at most. The model never sees the 1,083 English posts. It sees at most twenty summaries and picks one, or none.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;DATE_WINDOW_DAYS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;
&lt;span class="n"&gt;TOP_K_CANDIDATES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
&lt;span class="n"&gt;MIN_CONFIDENCE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where most of the accuracy comes from, and it costs nothing. A retrieval step does not need to be clever; it needs to make the question small.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Ask one small question, in JSON, at temperature zero
&lt;/h3&gt;

&lt;p&gt;The prompt, verbatim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Match translations of blog articles between languages.

RULES:
- A match means the SAME article translated - same specific content, same arguments,
  same examples - not just the same broad topic or shared keywords.
- Two articles can share a keyword (e.g. 'no-code', 'GoodBarber', 'app') and still be
  DIFFERENT articles. Reject those.
- Compare the FULL summary, not just the title. Look for the same specific subject,
  same angle, same takeaways.
- If you are not sure it's the same exact article, set index=null.
- Return high confidence (&amp;gt;0.8) only when the summary clearly describes the same content.

SOURCE (fr):
title: ...
summary: ...
date: ...

CANDIDATES (en):
[0] title: ...
    summary: ...
    date: ...
[1] ...

Reply JSON only: {"index": &amp;lt;int&amp;gt;|null, "confidence": &amp;lt;0..1&amp;gt;}. Default to index=null when unsure.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:11434/api/generate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemma2:27b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;format&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stream&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;options&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;temperature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
    &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;response&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three rules around it. An answer under 0.7 confidence is discarded. An article can belong to one row only, so a matched URL leaves the candidate pool for everyone else. And after every pivot the CSV is rewritten atomically, with a sidecar file recording which (pivot, language) pairs were already attempted, so a Ctrl-C or a rerun never pays for a prompt twice.&lt;/p&gt;

&lt;p&gt;That sidecar exists because I reran the thing several times while changing the prompt and the thresholds. With a hosted API, each rerun is a line on a bill and a rate limit to negotiate. Here it was a keystroke.&lt;/p&gt;

&lt;p&gt;I measured the cost of one prompt again today, September 9, on the same laptop, with the same code and the same model: the French "GoodBarber vs Glide" article against its thirteen English candidates within the window, 1,342 prompt tokens, 16 tokens out. Cold, 24.7 seconds, of which 11.2 to load the model. Warm, 2.3 seconds. Both runs answered &lt;code&gt;{"index": 2, "confidence": 0.95}&lt;/code&gt;, and index 2 is the right article. About 6,500 (pivot, language) pairs at that speed is roughly four hours of GPU time. The crawl ran on May 4; the CSV was last written on May 6 at 20:49. Sent to a hosted model, those eleven million or so prompt tokens would have cost somewhere between a couple of dollars and a couple of hundred depending on the model. Money was never the argument. The meter was.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Verify with a different model
&lt;/h3&gt;

&lt;p&gt;The generator is the judge. It should not also be the reviewer. Three layers, cheapest first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic checks.&lt;/strong&gt; Host matches the column, slug has the expected shape, each URL appears once in its column, and the publication dates within a row span 90 days at most.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slug overlap.&lt;/strong&gt; A cell whose slug shares almost no words with the French slug, in a row where the other cells share plenty, gets flagged. It is a heuristic with known false positives on well-translated slugs; it only surfaces candidates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A second model.&lt;/strong&gt; bge-m3 embeds the title plus the first 300 characters of the summary for all 5,892 articles (an 82 MB cache on disk, once). For each row, pairwise cosine similarity between the cells. A cell whose mean similarity to its row-mates drops 0.15 below the others is an outlier; a row whose mean is under 0.55 is weak. In fix mode the script proposes, for each outlier, the article of that language closest to the centroid of the other cells, and only if it scores at least 0.70 against the centroid, at least 0.70 against the French pivot, and beats the current cell by at least 0.10.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two models disagreeing is the review queue. A human reads the queue, not 5,325 cells.&lt;/p&gt;

&lt;h2&gt;
  
  
  What came out
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;rows&lt;/th&gt;
&lt;th&gt;en&lt;/th&gt;
&lt;th&gt;es&lt;/th&gt;
&lt;th&gt;it&lt;/th&gt;
&lt;th&gt;pt&lt;/th&gt;
&lt;th&gt;de&lt;/th&gt;
&lt;th&gt;nl&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;French pivots&lt;/td&gt;
&lt;td&gt;1,085&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cells filled&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;959&lt;/td&gt;
&lt;td&gt;766&lt;/td&gt;
&lt;td&gt;760&lt;/td&gt;
&lt;td&gt;688&lt;/td&gt;
&lt;td&gt;616&lt;/td&gt;
&lt;td&gt;451&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;5,325 of the 5,892 articles landed in a row, 90.4 percent. 347 rows are complete in seven languages, 151 more in six.&lt;/p&gt;

&lt;p&gt;The gaps are older than the model. The Spanish, Italian and Portuguese blogs opened in January 2013 and the German and Dutch ones in November 2013, and the early years published a lot of local-only content that was never translated. So the 193 French posts of 2013 yielded 227 matched cells and no complete row, which is the right answer, not a miss. From 2019 on, more than half of the French posts have all six translations.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;year of the French post&lt;/th&gt;
&lt;th&gt;French posts&lt;/th&gt;
&lt;th&gt;matched cells&lt;/th&gt;
&lt;th&gt;complete rows (6 of 6)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2013&lt;/td&gt;
&lt;td&gt;193&lt;/td&gt;
&lt;td&gt;227&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2015&lt;/td&gt;
&lt;td&gt;142&lt;/td&gt;
&lt;td&gt;542&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2018&lt;/td&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;226&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2021&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;354&lt;/td&gt;
&lt;td&gt;51&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2023&lt;/td&gt;
&lt;td&gt;87&lt;/td&gt;
&lt;td&gt;473&lt;/td&gt;
&lt;td&gt;65&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2025&lt;/td&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;155&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Two implementations, one list
&lt;/h2&gt;

&lt;p&gt;We had no ground truth. Nobody was going to grade 5,325 pairs by hand, and a validator written by the same person with the same assumptions grades itself. So over the same two weeks in May, a colleague, Marc, built a second implementation with nothing in common with mine: a different method, an embedding model and a nearest-neighbour index (pgvector) instead of a generator and a prompt, the same French pivot, a similarity threshold at 0.60, and a preference for the French article that shares the same thumbnail image, by hash, when one exists. No date window, no candidate list, no shared code, and no import of my CSV. Two lists, built blind. The rule was simple: when they said the same thing, we would stop.&lt;/p&gt;

&lt;p&gt;I pulled the export of the second implementation today and diffed it against my May file.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Its export lists 1,137 rows today; 1,051 of my 1,085 French pivots are in it.&lt;/li&gt;
&lt;li&gt;Of the 4,122 cells both pipelines filled, 4,037 are identical: 97.9 percent.&lt;/li&gt;
&lt;li&gt;85 differ. I read the first 40 by hand: in 38 the second implementation is right and mine is wrong, the other two are a toss-up. The errors cluster in three families: serial posts (the monthly "What's new", the engine revision announcements, where a 240-character summary does not carry the edition), topic pairs published in the same window (two reseller posts, two native-ads posts, which my 90-day filter served up together), and a handful where the summary simply was too short.&lt;/li&gt;
&lt;li&gt;284 cells the second implementation filled that mine had left empty, and 110 that mine filled and it leaves empty.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two implementations that share nothing but the corpus, landing on the same answer 98 times out of 100: that was the stop criterion, and we stopped. Since June our blog sitemaps carry the alternates, rebuilt from that export on every regeneration, best-effort: if the export is unavailable, the sitemap is generated without alternates rather than not at all. As of today, on our seven hosts:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;host&lt;/th&gt;
&lt;th&gt;blog URLs in sitemaps&lt;/th&gt;
&lt;th&gt;with hreflang alternates&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;www (en)&lt;/td&gt;
&lt;td&gt;1,107&lt;/td&gt;
&lt;td&gt;983&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fr&lt;/td&gt;
&lt;td&gt;1,084&lt;/td&gt;
&lt;td&gt;1,022&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;es&lt;/td&gt;
&lt;td&gt;945&lt;/td&gt;
&lt;td&gt;844&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;it&lt;/td&gt;
&lt;td&gt;943&lt;/td&gt;
&lt;td&gt;854&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pt&lt;/td&gt;
&lt;td&gt;888&lt;/td&gt;
&lt;td&gt;801&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;de&lt;/td&gt;
&lt;td&gt;768&lt;/td&gt;
&lt;td&gt;701&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;nl&lt;/td&gt;
&lt;td&gt;561&lt;/td&gt;
&lt;td&gt;536&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;5,741 of 6,296 URLs, 91.2 percent. The alternates live in the sitemaps only, not in the pages' &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;, which Google accepts as one of the three supported ways to declare them. One entry, as served today, shortened:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;url&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;loc&amp;gt;&lt;/span&gt;https://www.goodbarber.com/blog/design-trends-2026-...-a1608/&lt;span class="nt"&gt;&amp;lt;/loc&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;xhtml:link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"alternate"&lt;/span&gt; &lt;span class="na"&gt;hreflang=&lt;/span&gt;&lt;span class="s"&gt;"fr"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://fr.goodbarber.com/blog/tendances-design-2026-...-a1439/"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;xhtml:link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"alternate"&lt;/span&gt; &lt;span class="na"&gt;hreflang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://www.goodbarber.com/blog/design-trends-2026-...-a1608/"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;xhtml:link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"alternate"&lt;/span&gt; &lt;span class="na"&gt;hreflang=&lt;/span&gt;&lt;span class="s"&gt;"es"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://es.goodbarber.com/blog/tendencias-de-diseno-2026-...-a1137/"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;xhtml:link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"alternate"&lt;/span&gt; &lt;span class="na"&gt;hreflang=&lt;/span&gt;&lt;span class="s"&gt;"it"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://it.goodbarber.com/blog/tendenze-design-2026-...-a1102/"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;xhtml:link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"alternate"&lt;/span&gt; &lt;span class="na"&gt;hreflang=&lt;/span&gt;&lt;span class="s"&gt;"pt"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://pt.goodbarber.com/blog/tendencias-de-design-2026-...-a1341/"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;xhtml:link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"alternate"&lt;/span&gt; &lt;span class="na"&gt;hreflang=&lt;/span&gt;&lt;span class="s"&gt;"de"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://de.goodbarber.com/blog/designtrends-2026-...-a1441/"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;xhtml:link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"alternate"&lt;/span&gt; &lt;span class="na"&gt;hreflang=&lt;/span&gt;&lt;span class="s"&gt;"nl"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://nl.goodbarber.com/blog/designtrends-2026-...-a1435/"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/url&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same seven lines appear under the French, Spanish, Italian, Portuguese, German and Dutch URLs of that article in their own sitemaps, which is what makes the set reciprocal.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would tell you about open weights, after this
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Task design beats model size.&lt;/strong&gt; The 90-day window and the twenty-candidate cap did more for accuracy than any model choice would have. A June 2024 model at 4-bit was enough because it was never asked to search; it was asked to compare twenty summaries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repeatability is a feature.&lt;/strong&gt; Temperature zero and JSON mode gave me the same answer on the same prompt today as in May. Debugging a matcher that answers differently on each run is not debugging.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The meter changes what you build.&lt;/strong&gt; The attempts sidecar, the reruns, the fix loop: none of it would exist at a price per prompt. Zero marginal cost is not about saving money on the final run. It is about how many times you are willing to be wrong on the way there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verify with a second, cheaper model.&lt;/strong&gt; bge-m3 is 1.2 GB and MIT licensed. It never decides; it reviews. Two models with different failure modes are worth more than one bigger model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build it twice.&lt;/strong&gt; When there is no ground truth and no budget to make one, two implementations that share nothing, not the model, not the method, not the code, not the author, are a test suite you can afford. Where they agree, you are done. Where they disagree, you have a review queue, and 85 cells is a queue a person can read. Gemma 4 was already on the same disk in May, by the way; I ran the two-year-old one because it felt faster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Privacy was not the argument here.&lt;/strong&gt; Blog posts are public. But the crawl hit an internal API on a 192.168 address and nothing left the LAN, so if your corpus is not public, the same recipe runs unchanged.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hacktoberfest 2026 comes to Ajaccio, and we are hosting
&lt;/h2&gt;

&lt;p&gt;Hacktoberfest changed shape this year. It is now run by Major League Hacking and DEV, in partnership with DigitalOcean, and it stopped counting pull requests. The 2026 edition is 300-plus in-person Fests plus a global online event, all about building with open source AI: write your first skills.md, build an open-source agent, fine-tune an open-weight model. The tagline is "AI belongs to everyone", and after the afternoon described above I have no argument with it.&lt;/p&gt;

&lt;p&gt;GoodBarber is hosting the Ajaccio Fest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;When:&lt;/strong&gt; Wednesday, October 21, 2026, 18:00 to 21:00.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Where:&lt;/strong&gt; CampusPlex, 95 cours Napoléon, Ajaccio, in the middle of town.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Program:&lt;/strong&gt; doors at 18:00; at 18:30 a talk on building with open source and open-weight AI, why it matters and concrete ways to start; Q&amp;amp;A and open discussion at 19:15; drinks and time to talk to the people around you from 20:00; end at 21:00.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Language:&lt;/strong&gt; the talk is in French; slides and shared resources are in English so you can keep going with the wider Hacktoberfest community afterwards.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Price:&lt;/strong&gt; free. Registration on the &lt;a href="https://events.mlh.com/events/14893-hacktoberfest-meetup-ajaccio-x-goodbarber" rel="noopener noreferrer"&gt;MLH event page&lt;/a&gt; is recommended. Open to working professionals and university students.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No conference format, no pitch. We are one of the few tech companies headquartered on the island, and most developers here work alone; the point of the evening is to get them in one room on a weekday. If you have contributed to open source for years, come. If you have only been curious about where to start, come with a laptop, install Ollama and pull one model beforehand, and the recipe above fits in an evening.&lt;/p&gt;

&lt;p&gt;Fourteen years of posts got their hreflang from a 15 GB file and an afternoon. The interesting part was never the model. It was that nothing stood between me and trying.&lt;/p&gt;

</description>
      <category>hacktoberfest</category>
      <category>ai</category>
      <category>opensource</category>
      <category>seo</category>
    </item>
    <item>
      <title>The price of a new word</title>
      <dc:creator>Dominique Siacci</dc:creator>
      <pubDate>Tue, 08 Sep 2026 12:30:38 +0000</pubDate>
      <link>https://dev.to/goodbarber/the-price-of-a-new-word-fok</link>
      <guid>https://dev.to/goodbarber/the-price-of-a-new-word-fok</guid>
      <description>&lt;p&gt;An app built on our platform is, at bottom, a configuration — a description read against &lt;a href="https://dev.to/goodbarber/you-can-add-a-word-you-cant-change-what-one-means-3e4h"&gt;an engine that all apps share&lt;/a&gt;. And that configuration is written in a vocabulary the platform defines: the section types. Articles, events, videos, products, forms. A section type is a word in the platform's language.&lt;/p&gt;

&lt;p&gt;This article is about what it costs to add one.&lt;/p&gt;

&lt;p&gt;From the outside, adding a section type looks like building a screen and shipping it. It never is. The difference between a feature and a word is that a feature gets &lt;em&gt;used&lt;/em&gt;, while a word has to be &lt;em&gt;understood&lt;/em&gt; — by everything that reads the language. Until every reader understands it, the word doesn't exist; there's just a screen with a secret.&lt;/p&gt;

&lt;h2&gt;
  
  
  Everyone who reads the language
&lt;/h2&gt;

&lt;p&gt;Take the events section — on paper, a calendar of what's coming. Here's who has an opinion about it the day it's born.&lt;/p&gt;

&lt;p&gt;Navigation has to know how to route to it and back out of it. The design system has to know how to dress an event — and not in one app's theme, in &lt;em&gt;every&lt;/em&gt; theme any app might wear: an event card has to look deliberate in a minimal text-first design and in a photo-heavy one, because the word belongs to the whole language, not to the app that inspired it. Push has to be able to announce one: a new event should be able to notify subscribers exactly like any other content, without the push system containing a single line specific to calendars. Links have to reach it: an event needs an address that a shared post — or a printed flyer — can carry, and that address has to keep working long after the event itself scrolled off the screen. If the app runs &lt;a href="https://dev.to/goodbarber/your-chatbot-is-a-second-door-onto-your-content-535h"&gt;the chatbot, events become material for answers&lt;/a&gt;: "what's the best show for a first visit?" is now a question the app is expected to handle based on its content. If an agent operates the app, creating and editing an event has to be an operation an agent can perform. And before any of that, the back office has to exist: someone has to &lt;em&gt;write&lt;/em&gt; events — forms, fields, dates that end after they start.&lt;/p&gt;

&lt;p&gt;Each of those is a question the new word has to answer before it's allowed into the language. The calendar screen — the thing that looked like the whole job — is one line on the invoice.&lt;/p&gt;

&lt;h2&gt;
  
  
  The matrix grows in both directions
&lt;/h2&gt;

&lt;p&gt;Here's the part that compounds: a word added is a word maintained, and the grammar grows both ways.&lt;/p&gt;

&lt;p&gt;Every new word has to be understood by all existing readers — that's the invoice above. But every new &lt;em&gt;reader&lt;/em&gt; has to understand all existing words. The day we plugged a chatbot into apps' content, it couldn't just handle whatever section type was fashionable that year; it had to read the whole language, every word ever added. The day agents arrived, same story: operating an app means operating all of it. The true price of a word was never the feature work of its launch season. It's the row &lt;em&gt;and&lt;/em&gt; the column of that matrix — paid once when the word is born, and again every time the language gains a reader, forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  A lighter way to say something new
&lt;/h2&gt;

&lt;p&gt;That price would be unbearable if it were the only option. It isn't — and this is where the economics of a platform get interesting.&lt;/p&gt;

&lt;p&gt;Not every need deserves a word. When one app needs something bespoke, &lt;a href="https://dev.to/goodbarber/we-let-an-ai-write-code-inside-our-no-code-platform-generating-it-was-the-easy-part-1p65"&gt;a section can be described and generated&lt;/a&gt; for that app alone. The generated section lives in a slot the platform already understands: navigation knows how to reach it, every theme knows how to frame it, and nothing anywhere else has to learn a thing. The price stays small for exactly that reason — it teaches nobody anything. Which is also the honest statement of what it doesn't buy: it's a local phrase, not a new word. One app speaks it; the language hasn't changed.&lt;/p&gt;

&lt;p&gt;So there are two prices, and they're honest about what they purchase. The small one is local: the need is served today, for the app that has it. The full one is universal: every subsystem learns the word, and every app on the platform can use it, forever. A bespoke need lives comfortably at the small price. When the same need keeps arriving from different directions, it's telling us it wants to be a word — and then we pay the full price, knowingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The invoice is the feature
&lt;/h2&gt;

&lt;p&gt;Because here's what the full price buys. Once the platform has learned a word, it works everywhere the language is spoken: every app can add an events section and inherit — with zero additional work, theirs or ours — the navigation, the theming, the push, the addresses, the answers. An extra screen works where you put it. A word works everywhere, for everyone, from then on.&lt;/p&gt;

&lt;p&gt;That's what "adding a feature" actually means on a platform, and why it bears no resemblance to shipping a screen. The price of a new word is what makes it a word.&lt;/p&gt;

</description>
      <category>nocode</category>
      <category>architecture</category>
      <category>programming</category>
    </item>
    <item>
      <title>Trust is the net, not the eyes: can you ship AI-built software you can't review?</title>
      <dc:creator>Mathieu Poli</dc:creator>
      <pubDate>Tue, 08 Sep 2026 07:26:13 +0000</pubDate>
      <link>https://dev.to/goodbarber/trust-is-the-net-not-the-eyes-can-you-ship-ai-built-software-you-cant-review-1gfm</link>
      <guid>https://dev.to/goodbarber/trust-is-the-net-not-the-eyes-can-you-ship-ai-built-software-you-cant-review-1gfm</guid>
      <description>&lt;p&gt;Can you trust software built with AI by someone who can't read the code it produced? The whole debate keeps circling one question: did a human check it? I build these systems for a living, and the question I'd rather answer is a harder one: would anything have caught the mistake before it shipped?&lt;/p&gt;

&lt;h2&gt;
  
  
  The fear is a proxy
&lt;/h2&gt;

&lt;p&gt;This started as an exchange on X. I had written that "did a human check it?" is the wrong question about AI-built software, and someone sent back a better one:&lt;br&gt;
"AI didn't replace builders — it exposed the real gap: trust. Builders don't just assemble apps; they earn the trust to ship them. What do you think breaks that trust faster: a flaw in the AI's code, or the fear that no human ever looked?"&lt;br&gt;
The fear, clearly. But I think the fear stands in for something else. Nobody actually wants a human to have read every line; people want to know that something would have caught the mistake. Almost everything written about AI and code quality answers the first demand and leaves the second one alone. Trust is the net, not the eyes.&lt;/p&gt;

&lt;h2&gt;
  
  
  We trust people. Planes tell the whole story.
&lt;/h2&gt;

&lt;p&gt;Trust is anthropomorphic, it attaches to faces. Aviation shows how deep that runs: in &lt;a href="https://www.cnbc.com/2017/08/07/who-would-be-willing-to-fly-in-a-pilotless-plane-hardly-anyone.html" rel="noopener noreferrer"&gt;a UBS survey&lt;/a&gt;, only 17% of passengers said they would board a pilotless plane, and more than half wouldn't buy the ticket even if it were cheaper. &lt;a href="https://www.ipsos.com/en-us/news-polls/Few-Would-be-Comfortable-with-Flying-on-Pilotless-Airliners" rel="noopener noreferrer"&gt;An Ipsos poll&lt;/a&gt; found 81% of Americans uncomfortable with the idea. People want a human in the cockpit, and no discount changes their mind.&lt;/p&gt;

&lt;p&gt;Now look at what actually made flying the safest way to travel, because it wasn't the pilot's eyesight. Checklists, written after crashes, so that no landing depends on someone remembering. Two pilots, because any one person will eventually be tired, sick or wrong. Redundant instruments, mandatory maintenance intervals. Aviation never found better humans; it wrapped ordinary, fallible ones in a structure that catches what they miss. The passenger's trust goes to the pilot, and the catching is done by everything around him.&lt;br&gt;
I think software is at the moment where it has to learn the same distinction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Meanwhile, the eyes are losing ground
&lt;/h2&gt;

&lt;p&gt;Even where competent eyes exist, the guarantee is dissolving. I've reviewed enough code to vouch for the first half: we miss things. Every incident postmortem I've read includes a change that a competent person approved.&lt;/p&gt;

&lt;p&gt;The volume takes care of the second half. &lt;a href="https://www.faros.ai/blog/ai-acceleration-whiplash-takeaways" rel="noopener noreferrer"&gt;Faros AI's engineering report&lt;/a&gt;, built on telemetry from twenty-two thousand developers, measured what AI adoption does to the review pipeline: pull requests merged without any review at all are up 31.3%, and median time in review has quadrupled. Reviewers haven't gotten lazier, the queue simply outruns them. So "a human looked at it" is failing on both ends: it was never sufficient, and it's becoming rare.&lt;/p&gt;

&lt;p&gt;None of this makes review useless. It just can't be the thing the whole weight of trust rests on, and an automated reviewer doesn't change that: still a pair of eyes bolted on after the fact, just faster ones. The net is whatever was already there before anything shipped.&lt;/p&gt;

&lt;h2&gt;
  
  
  The less you know, the more net you need
&lt;/h2&gt;

&lt;p&gt;Here's the part the debate skips, and the reason it concerns me directly. All the standard advice about AI-generated code assumes a particular reader: a developer who inspects the output, someone who knows what to look for. For that person, AI is an accelerator with a known failure mode, and their own judgment is the control.&lt;/p&gt;

&lt;p&gt;Take that person away. The maker who describes an app in plain language and gets working software back, the exact promise of no-code and vibe coding alike, cannot be the check. It isn't carelessness: checking requires knowing what failure looks like, and that knowledge is precisely what they didn't need in order to build. Telling them "review the output carefully" is telling them to inspect a bridge with no idea what a crack is.&lt;/p&gt;

&lt;p&gt;That inverts the usual hierarchy: the people with the least domain knowledge are the ones who need the most protection, and they need it built into the floor they stand on, because there is no crew around them. If trust has to come from somewhere, it can only come from the system underneath, &lt;a href="https://dev.to/goodbarber/ai-didnt-kill-the-framework-it-made-it-more-valuable-3n5p"&gt;the layer whose job is to carry the judgment the person on top can't supply&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The engine is a net
&lt;/h2&gt;

&lt;p&gt;For me this part was never abstract, because building the net is most of the job. I run frontend engineering at GoodBarber, an app builder, and the engine we build is, among other things, an accumulation of catches. I gave a talk about this at the No Code Summit, &lt;a href="https://www.goodbarber.com/blog/advanced-customization-of-goodbarber-apps-a1300/" rel="noopener noreferrer"&gt;transcribed on our blog&lt;/a&gt;: the talk was about safeguards, stopping the obvious mistake from shipping without standardizing what people build. Set an ad frequency aggressive enough to degrade the reading experience, and a floor holds. Push a configuration past what the layout can carry, and the engine transforms it instead of rendering the mistake, a mechanism I've detailed in &lt;a href="https://dev.to/goodbarber/the-rendering-engine-how-a-no-code-project-becomes-a-smooth-native-app-nd4"&gt;the article on our rendering engine&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;None of these catches is spectacular, which is sort of the point. A net is an accumulation of places where a mistake runs out of room, each one settled once, in the structure, on behalf of people who will never know the question existed. Checklists, second instruments, walls an error can't cross: aviation wrote that shape down a century ago, and engines like ours have been writing the software version of it for fifteen years, since long before anyone called this an AI problem.&lt;/p&gt;

&lt;p&gt;And it runs deeper than what you see on screen: my colleague Pierre-Laurent has written about &lt;a href="https://dev.to/goodbarber/privacy-by-design-at-the-binary-level-no-ghost-sdk-in-your-build-1bl0"&gt;privacy guarantees enforced at the binary level&lt;/a&gt;, a catch nobody has to remember exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI on top, humans underneath
&lt;/h2&gt;

&lt;p&gt;Put AI on top of that picture, because that's where it's landing. Generation raises the need for the net: more people building without the knowledge to check, and mistakes produced faster than anyone can read them. An AI generating into the void ships whatever comes out; the same AI generating into an engine meets the floors, the transformations and the walls that were already there for humans.&lt;/p&gt;

&lt;p&gt;One principle from our own work with this, because it's the most reusable thing I know on the subject: the line between "the AI does it" and "the AI proposes it" shouldn't be drawn by how confident the model sounds, but by whether the mistake can be walked back. What's reversible can run. What isn't, waits for a human. (Deciding what the AI gets to do inside our own tool was a story in itself. &lt;a href="https://dev.to/goodbarber/we-let-an-ai-write-code-inside-our-no-code-platform-generating-it-was-the-easy-part-1p65"&gt;My colleague Dominique has told it&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;That, I think, is the durable place of these engines in the AI era: carrying the part of trust that neither the model nor the maker can carry alone. Built by humans, with AI generating on top.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trust is a property of the system
&lt;/h2&gt;

&lt;p&gt;So, back to the fear. Promising that a human looked won't cure it; that promise is already breaking inside professional teams, and it was never available to the maker building alone. What cures it is catches you can name.&lt;/p&gt;

&lt;p&gt;Deciding which mistakes deserve a catch, and which are cheap enough to let through, is judgment work, the kind that &lt;a href="https://dev.to/goodbarber/when-generation-is-free-taste-is-everything-3k30"&gt;doesn't get automated away&lt;/a&gt; when generation gets cheap. And every check nobody formalizes doesn't disappear. It joins the bill someone pays later, usually at the worst time.&lt;/p&gt;

&lt;p&gt;Some will read all this as giving up on humans. I see it as the opposite: taking what careful people do and making it survive their absence. The rule I come back to is the one I'd offer anyone building with AI today: you don't fully hand off what you can't undo.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Mathieu Poli — Head of Frontend Engineering @ GoodBarber. &lt;br&gt;
I teach and write about frontend engineering, product design, and AI — and everything that happens when the three meet.&lt;br&gt;
X: &lt;a href="https://x.com/hellomathieup" rel="noopener noreferrer"&gt;@hellomathieup&lt;/a&gt; · LinkedIn: &lt;a href="https://www.linkedin.com/in/hellomathieup/" rel="noopener noreferrer"&gt;hellomathieup&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Photo: &lt;a href="https://unsplash.com/photos/silhouette-of-a-trapeze-artist-swinging-high-above-lTv2oYFaAmE" rel="noopener noreferrer"&gt;Maya Alexa G. Romero&lt;/a&gt; / Unsplash&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>webdev</category>
      <category>nocode</category>
    </item>
    <item>
      <title>The most expensive thing we could sell you is a fork</title>
      <dc:creator>Dominique Siacci</dc:creator>
      <pubDate>Thu, 03 Sep 2026 13:04:19 +0000</pubDate>
      <link>https://dev.to/goodbarber/the-most-expensive-thing-we-could-sell-you-is-a-fork-1161</link>
      <guid>https://dev.to/goodbarber/the-most-expensive-thing-we-could-sell-you-is-a-fork-1161</guid>
      <description>&lt;p&gt;The request arrives regularly, and it's always reasonable: &lt;em&gt;we love the platform — we just need a version of it that's ours.&lt;/em&gt; One extra screen. One different behavior. One exception to a rule that makes sense for everyone else but not for them. From the customer's side, it's the most natural ask in software: they pay, the need is real, and the code already exists. How hard can a copy be?&lt;/p&gt;

&lt;p&gt;Our answer is no. It has been no for as long as the platform has existed, and it will survive any deal size. Not because the need doesn't count — because refusing the fork is an architecture policy, maybe the oldest one we have.&lt;/p&gt;

&lt;h2&gt;
  
  
  A fork is the product minus its future
&lt;/h2&gt;

&lt;p&gt;A fork costs nothing on the day you create it. Version control makes it a keystroke. Every cost comes after, and it compounds.&lt;/p&gt;

&lt;p&gt;A forked engine leaves the inheritance flow. Everything that makes a platform worth paying for — &lt;a href="https://dev.to/goodbarber/what-breaks-when-nobody-touches-your-app-for-three-years-2dgm"&gt;the changes every app absorbs at its next build&lt;/a&gt;: the OS deprecations quietly handled, the new capabilities that just appear — stops reaching it. Someone would have to re-apply each of those changes to the fork, by hand, forever, on a codebase that drifts a little further from the source every month. That someone has better things to do, and eventually doesn't do it.&lt;/p&gt;

&lt;p&gt;So "a version just for us" is not what we'd actually be selling. We'd be selling a version nobody keeps alive — the product, minus its future. The evolution &lt;em&gt;is&lt;/em&gt; the product. A fork is a way of buying the part that was already done and cancelling the part they were paying for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the need gets to live
&lt;/h2&gt;

&lt;p&gt;The need behind the request is real, though, and refusing the fork doesn't make it go away. What's negotiable isn't whether the need gets served — it's &lt;em&gt;where it lives&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The old principle says it in six words: open for extension, closed for modification. The engine is closed; the surface around it is the offer. Custom-code sections, custom widgets, custom navigation — places designed for behavior we never anticipated. And more recently, &lt;a href="https://dev.to/goodbarber/we-let-an-ai-write-code-inside-our-no-code-platform-generating-it-was-the-easy-part-1p65"&gt;a bespoke section can be described and generated&lt;/a&gt; without a line of the engine changing underneath it.&lt;/p&gt;

&lt;p&gt;Extension doesn't even have to mean living outside the engine. Sometimes the specific need becomes capability embedded in every build and expressed only where it's enabled — injected at compile time, dormant everywhere else. That may sound like a fork wearing makeup, but it differs in the one way that matters: the engine stays one. Every build still comes from the same source, still inherits everything, still moves forward with the fleet. Nothing has left the flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the refusal costs us
&lt;/h2&gt;

&lt;p&gt;Here's the part that took longer to understand: a no-fork policy is a discipline for &lt;em&gt;us&lt;/em&gt; before it's a constraint for anyone else. You can only keep refusing forks if the extension points are good enough that the refusal isn't a dead end — and extension points are only good if the engine behind them is built for it.&lt;/p&gt;

&lt;p&gt;Which is where the unglamorous principles earn their keep. Components with one responsibility, that evolve only for that responsibility. Modules that depend on each other as little as possible and talk through interfaces. Functionality grouped with the functionality it belongs with. Written down like that, it reads like a textbook page — until you see these rules as the thing that makes the "no" possible. Every one of them exists so that the engine can stay shared while the needs it serves diverge.&lt;/p&gt;

&lt;p&gt;And the requests themselves feed the discipline. A fork request that no extension point can absorb isn't a customer to talk out of it — it's a spec for the next extension point. I won't claim we convert every refusal that way; we don't. But the ones we did convert are the reason the next request usually finds a place to live.&lt;/p&gt;

&lt;h2&gt;
  
  
  The only "just for us" that lasts
&lt;/h2&gt;

&lt;p&gt;What a customer really asks with "a version just for us" is: &lt;em&gt;does my need count?&lt;/em&gt; The answer that holds up over years isn't another platform. It's &lt;a href="https://dev.to/goodbarber/you-can-add-a-word-you-cant-change-what-one-means-3e4h"&gt;another word in the platform&lt;/a&gt; — a capability added to the shared engine, that every app inherits, including theirs.&lt;/p&gt;

&lt;p&gt;Their edge case today, everyone's feature tomorrow. It's a slower yes than a fork, and a less flattering one. It's also the only version of "just for you" that will still be alive in three years.&lt;/p&gt;

</description>
      <category>nocode</category>
      <category>architecture</category>
      <category>saas</category>
    </item>
    <item>
      <title>Deploying code and releasing a feature are two different days</title>
      <dc:creator>Dominique Siacci</dc:creator>
      <pubDate>Tue, 01 Sep 2026 14:37:07 +0000</pubDate>
      <link>https://dev.to/goodbarber/deploying-code-and-releasing-a-feature-are-two-different-days-301n</link>
      <guid>https://dev.to/goodbarber/deploying-code-and-releasing-a-feature-are-two-different-days-301n</guid>
      <description>&lt;p&gt;Everything we run ships continuously. The engine behind the apps, the back office our customers build in, the web products around them — code moves to production at the rhythm of engineering: when it's ready, it goes.&lt;/p&gt;

&lt;p&gt;Features don't work that way. A feature has a launch: a name, documentation, support people who know it exists, sometimes a price. That's a product decision, with its own calendar, made by people who don't merge code.&lt;/p&gt;

&lt;p&gt;So the two acts had to be decoupled, and the thing that decouples them is the humblest object in software: a flag. Deploying code is an engineering act. Releasing a feature is a product decision. On &lt;a href="https://dev.to/goodbarber/you-can-add-a-word-you-cant-change-what-one-means-3e4h"&gt;a platform where every app runs on an engine all apps share&lt;/a&gt;, you don't get to conflate the two — there is no "just ship it to some users" when a deployment, by construction, reaches everyone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Weeks in production before existing
&lt;/h2&gt;

&lt;p&gt;There's nothing exotic about a feature flag — any team that ships continuously runs some version of this, and we claim no invention here. But the day-to-day of it is still worth telling, because it's where the two calendars become visible.&lt;/p&gt;

&lt;p&gt;A feature we shipped recently spent weeks in production before it existed. One configuration value kept it off, globally — for everyone except the few people building it, who used it daily, in real conditions, in the middle of real traffic, while every other visitor saw nothing.&lt;/p&gt;

&lt;p&gt;When launch day came, nothing was deployed. One value changed. The code had been to production dozens of times by then; the feature went once. That's the whole point of the separation: the code's path to production and the feature's path to users never had to share a schedule — so neither had to wait for the other, and neither could rush the other.&lt;/p&gt;

&lt;h2&gt;
  
  
  The unit of release is a project
&lt;/h2&gt;

&lt;p&gt;When a feature does start to exist, it advances project by project. That's the natural granularity of a platform: a feature turns on for internal test projects first — some capabilities live for a while in builds that never leave the office — then for a few real projects, then for everyone.&lt;/p&gt;

&lt;p&gt;We never had to bolt a feature-flag system onto the product. Switching features on and off per project has been part of the platform's architecture from the start — the switchboard was there long before we ever thought of it as a release strategy.&lt;/p&gt;

&lt;p&gt;And sometimes the granularity gets embarrassingly concrete. Somewhere in our codebase there is a condition that checks whether the current project is one of exactly two hardcoded IDs. Two IDs, in the code, letting two projects do something nobody else can. Not proud of it, not ashamed of it: it's a flag in its crudest possible form, doing exactly what flags do. And note what even this crudest flag is &lt;em&gt;not&lt;/em&gt;: a separate version. Those two projects run the same engine as everyone else — the exception is a condition inside shared code, never a copy drifting away. Why we never fork that engine is a story of its own, for another day.&lt;/p&gt;

&lt;h2&gt;
  
  
  The flag and the setting are the same plumbing
&lt;/h2&gt;

&lt;p&gt;Here's the part that &lt;em&gt;is&lt;/em&gt; more specific to a platform like ours — a symmetry we get to see both sides of daily.&lt;/p&gt;

&lt;p&gt;What we call a feature flag and what our customers call a setting are the same plumbing. When a builder switches comments on for their app, they flip a boolean over code that was delivered long ago — precisely what we do when we release a feature. The engine permanently runs ahead of the product: code for a capability can be fully deployed while the capability doesn't exist yet as a product — nothing references it, no screen offers it, defaults keep it inert. The day it launches, what's new isn't code. What's new is that the configuration now has a place for it.&lt;/p&gt;

&lt;p&gt;Push that logic to the end and you get a fair description of the whole platform: an app is a configuration that lights up parts of an engine. &lt;a href="https://dev.to/goodbarber/what-breaks-when-nobody-touches-your-app-for-three-years-2dgm"&gt;The engine's job is to keep every part worth lighting up&lt;/a&gt; — the configuration's job is to decide which, and when.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a flag can't say
&lt;/h2&gt;

&lt;p&gt;There is one thing no flag can express: &lt;em&gt;we're not sure yet.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Some of our features go out wearing a Beta label. Behind that label, at our place, there is no mechanism — no cohort, no computed exit criteria. It's an admission. Humans put it on when a feature is real but young, and humans take it off when the feature has earned it. No threshold fires; someone decides.&lt;/p&gt;

&lt;p&gt;A flag separates &lt;em&gt;deployed&lt;/em&gt; from &lt;em&gt;released&lt;/em&gt;. The label separates &lt;em&gt;released&lt;/em&gt; from &lt;em&gt;promised&lt;/em&gt;. They look alike — two booleans on the side of the same feature — but one of them governs code, and the other one gives your word.&lt;/p&gt;

</description>
      <category>nocode</category>
      <category>architecture</category>
      <category>devops</category>
    </item>
    <item>
      <title>There is no free lunch, especially in app publishing</title>
      <dc:creator>Pierre- Laurent Medori</dc:creator>
      <pubDate>Tue, 01 Sep 2026 11:54:27 +0000</pubDate>
      <link>https://dev.to/goodbarber/there-is-no-free-lunch-especially-in-app-publishing-2ec8</link>
      <guid>https://dev.to/goodbarber/there-is-no-free-lunch-especially-in-app-publishing-2ec8</guid>
      <description>&lt;p&gt;2009, mobile networks class. We were studying how TCP handles packets lost over 3G: the network detects the loss, replays the packet, and the transfer survives a radio link that drops things all the time. Someone asked the obvious question: why not just make the radio link reliable in the first place? The professor's answer became the soundtrack of that semester: there is no free lunch. Reliability over 3G is not free, it is paid for in retransmissions, latency and battery. The cost never disappears. It just moves to wherever you are not looking.&lt;/p&gt;

&lt;p&gt;I now run engineering at a no-code app builder. Every pricing decision I have seen from the inside since, his sentence explains better than any business book.&lt;/p&gt;

&lt;p&gt;Here is the version of it nobody puts on a landing page: &lt;strong&gt;you can build an app for free anywhere, and you can ship one for free nowhere.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The asterisk every vendor knows by heart
&lt;/h2&gt;

&lt;p&gt;Search "free app builder". Every result promises an app for free. Every vendor behind those pages knows exactly where their free tier ends: right before the App Store and Google Play. Build for free, preview for free, then the store gates close and the pricing table appears.&lt;/p&gt;

&lt;p&gt;I will not do the pricing comparison here. We published a dated, sourced table of what each major free tier actually includes on the company blog: &lt;a href="https://www.goodbarber.com/blog/can-you-really-create-an-app-for-free-what-free-app-builders-actually-include-a1606/" rel="noopener noreferrer"&gt;Can you really create an app for free?&lt;/a&gt;. Spoiler: the pattern is identical across the market, and one competitor even admits in its own comparison that store publishing always requires a paid plan.&lt;/p&gt;

&lt;p&gt;This post is about the side of the counter you cannot see from a landing page: what the word "published" costs the vendor. Because that is where the free lunch dies.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "published" costs us, forever
&lt;/h2&gt;

&lt;p&gt;A built app is a file. A published app is a commitment. From our machine room, that commitment looks like this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The store treadmill never stops.&lt;/strong&gt; Google Play raises its target API requirement every year. Apple killed UIWebView in 2020. The legacy push protocol died in March 2021. The Play Data safety form arrived in July 2022, Apple privacy manifests in May 2024. Each event means rebuilding and resubmitting fleets of apps through two review pipelines. Skip one deadline and apps disappear from stores. This work is invisible, recurring, and nobody can bill it to "free".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Runtime is real hardware.&lt;/strong&gt; Our customers' apps call a backend every day. We run our own machines in racks in European datacenters. The CDN moves terabytes per day. Push delivery counts in millions of notifications per week. An engineer is on call all year; the status page is public (goodbarberstatus.com). None of this pauses when a customer pays zero.&lt;/p&gt;

&lt;p&gt;A free plan that included native publishing would mean carrying all of the above, indefinitely, for free. Nobody in this market does it. That is not stinginess. That is arithmetic.&lt;/p&gt;

&lt;h2&gt;
  
  
  So who pays for your free plan?
&lt;/h2&gt;

&lt;p&gt;The lunch is never free; the bill just moves. On a typical free app builder tier, you pay with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Branding.&lt;/strong&gt; Your app advertises its builder. Removing the badge is a paid feature, everywhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The wall.&lt;/strong&gt; The upgrade prompt lands at maximum investment, minimum leverage. That timing is not an accident, it is the business model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A link instead of a listing.&lt;/strong&gt; Free "apps" are often web links. The store fees never trigger, and neither does the store presence you came for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your time.&lt;/strong&gt; The expensive one. Rebuilding elsewhere because platform one could not ship is the worst deal in no-code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And two line items belong to nobody's free plan because they are not the builder's to give: Apple charges 99 $ a year for a developer account, Google Play 25 $ once. A page that promises store publishing "for free" without those numbers is describing a demo.&lt;/p&gt;

&lt;h2&gt;
  
  
  In defense of free tiers
&lt;/h2&gt;

&lt;p&gt;Said plainly, because my employer's competitors deserve fairness: free tiers are a great deal when your goal matches their content. Testing an idea on a real phone screen. Learning how a no-code editor thinks. Shipping a link to five colleagues. For all of that, use a free plan and pay nobody, including us.&lt;/p&gt;

&lt;p&gt;The problem is never the free tier. It is the landing page that lets you believe the lunch stays free all the way to the App Store.&lt;/p&gt;

&lt;h2&gt;
  
  
  The menu with prices on it
&lt;/h2&gt;

&lt;p&gt;GoodBarber has no free plan, and now you know the reason is structural, not commercial. Our trade is different: the full platform, free for 30 days, no credit card, including a PWA you can actually publish during the trial (&lt;a href="https://www.goodbarber.com/free-app-builder/" rel="noopener noreferrer"&gt;what free really includes&lt;/a&gt;). When it ends, nothing is charged, because we never took a card. You have seen the real product and the real prices, and you decide.&lt;/p&gt;

&lt;p&gt;No free lunch. But a menu with prices on it, and a table where you eat for 30 days before ordering.&lt;/p&gt;

&lt;p&gt;Same lesson as those 3G retransmissions: the cost never disappears, so honest design shows you where it sits. You know what to expect, and the price is not hidden. In 2009 we would have called that a well-designed protocol.&lt;/p&gt;

</description>
      <category>nocode</category>
      <category>mobile</category>
      <category>infrastructure</category>
      <category>startup</category>
    </item>
    <item>
      <title>Every rejection becomes a check</title>
      <dc:creator>Dominique Siacci</dc:creator>
      <pubDate>Thu, 27 Aug 2026 09:13:20 +0000</pubDate>
      <link>https://dev.to/goodbarber/every-rejection-becomes-a-check-gbk</link>
      <guid>https://dev.to/goodbarber/every-rejection-becomes-a-check-gbk</guid>
      <description>&lt;p&gt;Every app we ship gets reviewed by someone else's QA. Two someones, actually — Apple and Google — each with its own rulebook, and each rulebook comes in two parts: the rules as written, and the rules as applied. The written part you can read. The applied part you learn the way case law is learned: one decision at a time.&lt;/p&gt;

&lt;p&gt;And here's the constraint that shapes everything downstream: you can't run their test suite locally. There is no command that tells you, before submission, what the review will say. Anyone who ships apps lives with this. What changes, when submitting is something you do continuously rather than occasionally, is what you're allowed to do with the answers. When the same review keeps answering you, week after week, letting the same rule surprise you twice isn't bad luck anymore. It's negligence.&lt;/p&gt;

&lt;p&gt;So that became the discipline: every time the review teaches us something — a rejection pattern, a rule change, a shift in how an old rule gets applied — the lesson turns into an explicit check on our side, run &lt;em&gt;before&lt;/em&gt; the store ever sees the app. We can't execute their suite. We can compile what it has already said.&lt;/p&gt;

&lt;p&gt;The list of checks that discipline has produced is long. Here are two entries from it — two among many, picked because they're the rejection families that &lt;a href="https://www.goodbarber.com/blog/app-store-publishing-everything-you-asked-at-our-reddit-ama-a1559/" rel="noopener noreferrer"&gt;topped the questions at our recent Reddit AMA on store publishing&lt;/a&gt;: apps that look unfinished, and declarations that don't match the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  The empty shelf
&lt;/h2&gt;

&lt;p&gt;Stores reject apps that look unfinished — screens without content, sections that lead nowhere. Reasonably so: their reviewer opens the app cold, and an empty shelf reads as an abandoned store.&lt;/p&gt;

&lt;p&gt;Our answer isn't a best-practices PDF. The guardrails live in the product itself: checklists that stand between a builder and a submission, refusing to let an app walk into review hollow. What used to be advice — "fill your sections before you submit" — became a feature that checks. Advice gets skipped; checks don't.&lt;/p&gt;

&lt;p&gt;That's the pattern for everything automatable, and it's worth stating as a rule: &lt;strong&gt;when a piece of advice can be verified mechanically, it should stop being advice.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Say what you do, do what you say
&lt;/h2&gt;

&lt;p&gt;The second family of checks is subtler. A review doesn't just look at what the app does — it cross-references what the app &lt;em&gt;declares&lt;/em&gt; against what it actually does. Privacy declarations, permission requests, data-safety forms: every one of them is a claim, and a claim that doesn't match the binary is one of the most reliable ways to get rejected.&lt;/p&gt;

&lt;p&gt;It's also the most avoidable rejection there is, because the mismatch is a diff — and diffs are what machines are for. In our case, the declarations are &lt;a href="https://dev.to/goodbarber/privacy-by-design-at-the-binary-level-no-ghost-sdk-in-your-build-1bl0"&gt;deduced from the app's configuration&lt;/a&gt; rather than filled in by hand. What remains for the pre-submission check is the moving part: a builder enables a feature one week and disables it the next, and the declarations must describe the app being submitted today, not the app as it looked when someone last thought about privacy. So coherence gets verified again, at the door.&lt;/p&gt;

&lt;h2&gt;
  
  
  The line between the machine and the eye
&lt;/h2&gt;

&lt;p&gt;Not everything compiles into a check. A rule that says "no placeholder content" automates well. A rule whose application shifted last quarter, or that hinges on how a reviewer will read a particular screen — that one lives in judgment, not in code.&lt;/p&gt;

&lt;p&gt;So the frontier is drawn plainly. Everything that can be verified mechanically is handed to the builder as a guardrail inside the product — they see it, they fix it, no expertise required. What can't be automated goes under an expert eye before submission: &lt;a href="https://dev.to/goodbarber/what-breaks-when-nobody-touches-your-app-for-three-years-2dgm"&gt;the store rules keep moving&lt;/a&gt;, and someone whose job is to read every change sees patterns no individual builder submitting once a year could. The review on the other side is run by humans; the last line on ours is too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failing first
&lt;/h2&gt;

&lt;p&gt;None of this makes rejection impossible — a suite you can't run will always keep some surprises. What it does is more modest and more valuable: it guarantees that no app fails in front of Apple or Google for a reason we already knew about. The known rules are checked by machines. The learned patterns are checked by people. And every new lesson joins the list, which means the list only grows in one direction.&lt;/p&gt;

&lt;p&gt;You can't run someone else's test suite. You can make sure yours fails first.&lt;/p&gt;

</description>
      <category>mobile</category>
      <category>ios</category>
      <category>android</category>
      <category>architecture</category>
    </item>
    <item>
      <title>White-label is a subtraction job</title>
      <dc:creator>Dominique Siacci</dc:creator>
      <pubDate>Tue, 25 Aug 2026 08:22:17 +0000</pubDate>
      <link>https://dev.to/goodbarber/white-label-is-a-subtraction-job-53mf</link>
      <guid>https://dev.to/goodbarber/white-label-is-a-subtraction-job-53mf</guid>
      <description>&lt;p&gt;Some of our resellers run the platform under their own brand. Their customers build apps on infrastructure that says someone else's name everywhere — the reseller's, not ours.&lt;/p&gt;

&lt;p&gt;From a distance, that looks like a feature you add: swap the logo, change a color, done. From inside, it's the opposite kind of work. White-label is a subtraction job: finding every place your own name hides in your own product, and taking it out. There are hundreds of those places. Nobody's list is ever quite finished — a product that keeps shipping keeps creating new spots for a name to leak into.&lt;/p&gt;

&lt;p&gt;Why resellers ask for this — and how the ask went from a fear to a claim — is &lt;a href="https://jeromegranados.substack.com/p/agencies-asked-us-to-hide-our-name" rel="noopener noreferrer"&gt;a story Jérôme tells&lt;/a&gt;. This is the engineering half: what honoring the ask actually takes.&lt;/p&gt;

&lt;p&gt;Three of them, picked because each teaches something.&lt;/p&gt;

&lt;h2&gt;
  
  
  The email you didn't sign
&lt;/h2&gt;

&lt;p&gt;Transactional emails — the password reset, the order confirmation — are the easy case: put the reseller's name in the sender field. Except an email carries more identity than its display name. There's the &lt;em&gt;via&lt;/em&gt; that some inboxes surface next to the sender. There are the technical headers that most recipients will never open — until the one technically-minded customer does, and finds a name that shouldn't be there. Subtracting yourself from an email means subtracting yourself from the parts of it nobody usually reads. Someone always reads them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The address on the door
&lt;/h2&gt;

&lt;p&gt;A reseller's customers log into a back office. That back office lives at an address — and the address is part of the product. It can't be goodbarber.com, not even as a redirect on the way in. So domains are part of the subtraction: the workspace a customer uses every day has to carry, down to its URL, the brand of the company they bought it from.&lt;/p&gt;

&lt;h2&gt;
  
  
  The page nobody tests
&lt;/h2&gt;

&lt;p&gt;Error pages. When everything works, every screen has been reviewed a hundred times. When something breaks, you land on pages nobody rehearses — and a default error page is precisely where a platform's real name loves to surface. Your brand showing up at the exact moment something fails is the worst possible cameo. So the subtraction has to cover the unhappy paths too: the 404s, the maintenance screens, the "something went wrong" of it all.&lt;/p&gt;

&lt;h2&gt;
  
  
  The things we never had to subtract
&lt;/h2&gt;

&lt;p&gt;The most interesting part of the inventory is what's &lt;em&gt;not&lt;/em&gt; on it.&lt;/p&gt;

&lt;p&gt;An app built on the platform ships to the stores from the customer's own developer account. Their name on the listing, their certificates, their relationship with Apple and Google. There is nothing to erase there, because the architecture never wrote us into it: the app belonged to its owner from the first day, reseller or no reseller. &lt;a href="https://dev.to/goodbarber/deciding-what-an-agent-should-do-not-just-what-it-can-5h35"&gt;Even our agent playbook ships white-label&lt;/a&gt; — resellers redistribute it under their own brand, because it was written to be redistributed.&lt;/p&gt;

&lt;p&gt;That's the quiet lesson of this whole exercise. The subtraction you do by hand is corrective; the best subtraction is structural. Every place where the platform never claimed what belongs to the customer is a place you'll never have to scrub.&lt;/p&gt;

&lt;h2&gt;
  
  
  The finish line
&lt;/h2&gt;

&lt;p&gt;How do you know the job is done? Walk down the chain and ask, at each step, whose brand is visible.&lt;/p&gt;

&lt;p&gt;The end user of an app sees the brand of the app's owner. The app's owner — the reseller's customer — sees the reseller's brand: on the back office, in the emails, on the error pages. And us? Nowhere in that chain. Not smaller, not discreet, not in the footer. Absent.&lt;/p&gt;

&lt;p&gt;That's the acceptance test, and it's stricter than it sounds. A brand that shows up small is still a brand that shows up. White-label isn't your name in a modest font — it's your name gone.&lt;/p&gt;

&lt;p&gt;We spent years building a brand, and then built the capability whose entire job is to make it disappear. Those aren't opposites. They're the same product, taken seriously twice.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>saas</category>
      <category>nocode</category>
    </item>
    <item>
      <title>An engineer who started writing: what AI shifted in my job</title>
      <dc:creator>Mathieu Poli</dc:creator>
      <pubDate>Tue, 25 Aug 2026 07:52:38 +0000</pubDate>
      <link>https://dev.to/goodbarber/an-engineer-who-started-writing-what-ai-shifted-in-my-job-44af</link>
      <guid>https://dev.to/goodbarber/an-engineer-who-started-writing-what-ai-shifted-in-my-job-44af</guid>
      <description>&lt;p&gt;&lt;em&gt;Writing was never my job. I build things. But AI nudged me to put words down, and that small shift says a lot about what's changing for engineers, and what isn't.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing wasn't my job
&lt;/h2&gt;

&lt;p&gt;Some context. I've been Head of Frontend Engineering at GoodBarber since the company started in 2011. I build the rendering engines that turn no-code projects into native apps. I teach on the side, at the University of Corsica, and I occasionally get on a conference stage. I knew how to build and how to explain out loud. Writing is another matter. I did publish a few technical articles over the years, but each one cost me an unreasonable amount of energy, and none of them made me want to write the next one. Regular, public writing, with my name on it, was for other people.&lt;/p&gt;

&lt;p&gt;Not that I had nothing to say. After all that time inside the same product, you have opinions on everything. The story wasn't the problem either: structuring, sequencing, arguing, that's my daily work. What cost me was building the sentences. Finding the right phrasing, redoing it, redoing it again, feeling it was heavy without knowing why. At that price, I always had something better to build. Almost every time, the page lost. For fifteen years.&lt;/p&gt;

&lt;p&gt;What changed is very practical. The AI builds the sentences, and I do everything else: the ideas, the order, what's true, what's false, what stays, what goes. It transcribes, I review. And reviewing is what I do every day with code. The fixed cost collapsed, and it turned out the ideas had been waiting behind it.&lt;/p&gt;

&lt;p&gt;And ideas weren't the only thing waiting. There are also &lt;a href="https://dev.to/goodbarber/when-generation-is-free-taste-is-everything-3k30"&gt;affects&lt;/a&gt;: the attachment to a product you've been growing since 2011, the pleasure of an architecture that finally holds. I had never managed to get those across, because I lacked the technique. I don't lack it anymore. A bit as if I had learned to paint in a few months.&lt;/p&gt;

&lt;h2&gt;
  
  
  What AI changed in how I build
&lt;/h2&gt;

&lt;p&gt;I trusted that shift because I had already lived it on the engineering side.&lt;/p&gt;

&lt;p&gt;The obvious things accelerated first: boilerplate, scaffolding, exploration. Trying three architectural approaches used to cost three days, so we argued in meetings about which one to build. Now you build all three before the meeting and argue about results. Everyone knows that part of the story by now.&lt;/p&gt;

&lt;p&gt;The deeper change is where my hours go. My time has moved from writing code to deciding and verifying. The machine produces, and I judge, check, accept or reject. A concrete example. I led a feature that lets an AI agent design an app's backend: &lt;a href="https://www.goodbarber.com/blog/ai-extension-builder-add-a-supabase-database-to-your-app-a1550/" rel="noopener noreferrer"&gt;a real Supabase database, live&lt;/a&gt;, with the user's actual data. The interesting engineering wasn't the generation. It was the guardrails. Destructive actions were gated on reversibility. Every proposed operation was logged before running. External calls were fenced to an allowlist of domains. And one principle that sums it all up: &lt;em&gt;real data or an honest empty state, never an invented number&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;That sentence isn't code. It's an editorial judgment about truthfulness, applied to software. The machine took the typing, and what was left of my job was deciding what should be allowed to happen, and what "honest" means in an interface. Somewhere in there I noticed that the two halves of my week, building with AI and writing with AI, had become the same activity. The machine handles the transcription. I supply the judgment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why an engineer still has things to say
&lt;/h2&gt;

&lt;p&gt;Which raises the fair question. If AI writes fluently, why would an engineer bother writing at all? Won't the machine say it better?&lt;/p&gt;

&lt;p&gt;It will say it smoother. It can't say it at all, in the sense that matters, because &lt;strong&gt;the value of an engineer's writing was never the prose. It's the scar tissue.&lt;/strong&gt; The machine can write a beautiful paragraph about any technical subject; it wasn't there for any of the decisions that aged badly on us, or for what it took to walk them back. A position held for fifteen years isn't retrievable from a corpus. It has to be reported, by someone who was in the room. And what that person reports isn't just facts: it's what they felt when it broke, what they believed before they understood, the point of view of the one who carried the decision. Ours, for example: at every generation of hybrid frameworks since 2011, we made the same call, native. A call held against the fashion of the moment, argued for years. Many competitors gave up along the way; we're still here, running on engines the years have made solid. The machine can summarize the ten thousand blog posts of that debate. It has no point of view.&lt;/p&gt;

&lt;p&gt;That's also why handing the sentences over doesn't bother me. For a novel or a short story, I would understand: there, the sentences are the work. Here I'm not making art, I'm sharing a point of view. And it's judged on what it claims, not on the sentences that carry it. Sentences matter the way technique matters in painting: without it, nothing gets through; but it isn't what people come to see.&lt;/p&gt;

&lt;p&gt;The best objection I've read comes from &lt;a href="https://www.newyorker.com/culture/the-weekend-essay/why-ai-isnt-going-to-make-art" rel="noopener noreferrer"&gt;Ted Chiang&lt;/a&gt;: art results from a quantity of choices, he writes, and a ten-thousand-word story represents about ten thousand of them. Someone who finds formulating sentences a chore isn't really working in their medium. He's right, for art. But my medium isn't the sentence, it's the point of view. An engineer's essay is judged like an app, on its pertinence, and my ten thousand choices were made elsewhere: in fifteen years of production. On the essential point, though, we agree: what makes a text new, he concludes, is that it comes from a unique life experience.&lt;/p&gt;

&lt;p&gt;My colleague Muriel crossed the same bridge in the opposite direction. She works in marketing, and she &lt;a href="https://murielsantoni.substack.com/p/im-in-marketing-i-taught-myself-to" rel="noopener noreferrer"&gt;taught herself to build with AI&lt;/a&gt;. She got the speed, and then she hit the part I take for granted. In her words: "the machine hands you something that works, and you couldn't explain why." Read together, her story and mine say the same thing from two sides. AI moves the execution, and the judgment stays with whoever has the experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  The balance, as I see it
&lt;/h2&gt;

&lt;p&gt;Here is what this small detour taught me about the question everyone is asking: is the machine replacing us?&lt;/p&gt;

&lt;p&gt;AI didn't replace the part of my job that mattered. It exposed it. Take away the sentences, code or prose, and what remains is judgment, experience, and the will to say what you actually saw. The machine amplifies a voice. It doesn't contain one.&lt;/p&gt;

&lt;p&gt;I'm &lt;a href="https://jeromegranados.substack.com/p/ai-in-marketing-15-year-old-company" rel="noopener noreferrer"&gt;still at the same company&lt;/a&gt;, teaching students who will never know the blank-page tax. The machine takes on more of the execution every month. And what's left over looks, each time, more like the reason I got into this work in the first place: deciding what's worth building. And now, sometimes, what's worth saying.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Mathieu Poli — Head of Frontend Engineering @ &lt;a href="https://www.goodbarber.com/" rel="noopener noreferrer"&gt;GoodBarber&lt;/a&gt;. I teach and write about frontend engineering, product design, and AI — and everything that happens when the three meet. · X: **&lt;a class="mentioned-user" href="https://dev.to/hellomathieup"&gt;@hellomathieup&lt;/a&gt;&lt;/em&gt;**&lt;/p&gt;

</description>
      <category>ai</category>
      <category>writing</category>
      <category>career</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Models retire faster than operating systems</title>
      <dc:creator>Dominique Siacci</dc:creator>
      <pubDate>Mon, 17 Aug 2026 08:18:31 +0000</pubDate>
      <link>https://dev.to/goodbarber/models-retire-faster-than-operating-systems-275p</link>
      <guid>https://dev.to/goodbarber/models-retire-faster-than-operating-systems-275p</guid>
      <description>&lt;p&gt;When an operating system &lt;a href="https://dev.to/goodbarber/what-breaks-when-nobody-touches-your-app-for-three-years-2dgm"&gt;deprecates an API&lt;/a&gt;, you get a year's notice, a migration guide, and a conference talk. When a model provider retires a model, you get a blog post, a date a few months out, and a designated replacement — allegedly better. Our AI features run on the most perishable dependency anywhere in the platform.&lt;/p&gt;

&lt;p&gt;We haven't been forced through an emergency swap in production yet. What we have done, constantly, is try models — a lot of them. And somewhere along the way, each experiment stopped being a test and became a rehearsal: the day a retirement notice lands on a model we depend on, the move it triggers is one we've already made dozens of times.&lt;/p&gt;

&lt;p&gt;Here's what makes that move routine instead of a crisis.&lt;/p&gt;

&lt;h2&gt;
  
  
  A model is a catalog line
&lt;/h2&gt;

&lt;p&gt;In our generation pipeline, a model isn't a choice wired into the code. It's an entry in a catalog: which provider serves it, which key unlocks it, what it costs. The catalog covers essentially every major provider — OpenAI, Anthropic, Google, etc. — and production runs a deliberately trimmed selection of it.&lt;/p&gt;

&lt;p&gt;The other half of the setup: each step of the pipeline is mapped to its own model. The step that plans doesn't need the model that writes; the step that repairs a single field doesn't need the model that generates a whole component. So a swap has a scope. Changing the model behind one step is a configuration change with a bounded blast radius — not a migration.&lt;/p&gt;

&lt;h2&gt;
  
  
  The prompt faces the model. The evals face us.
&lt;/h2&gt;

&lt;p&gt;Two kinds of documents govern the model's work, and they look in opposite directions.&lt;/p&gt;

&lt;p&gt;The prompt faces the model. It speaks the model's language, and when we try a new family of models, adapting it is real work — but marginal work. Each family has its dialects: how it likes constraints phrased, what it does with structure, where it needs repetition. Moving between them is translation, not renegotiation. The contract — what must come out: the schema, the constraints, what to do when a request is out of scope — doesn't move.&lt;/p&gt;

&lt;p&gt;The evals face us. This is the part that took us the longest to see clearly: an eval suite that tries to anticipate what a given model will answer is doing the prompt's job, badly. Ours validate the output against our standard — does this parse, does it respect the schema, does a conversational question get a conversational answer instead of a broken build — and that standard is, by construction, common to every model. The test fixtures come from real production failures. The thresholds are written down. And nothing in them mentions a model by name.&lt;/p&gt;

&lt;p&gt;That's the whole trick, and it fits in one sentence: &lt;strong&gt;the prompt is bilingual; the standard isn't.&lt;/strong&gt; Swapping a model means changing who you talk to — not what you accept.&lt;/p&gt;

&lt;h2&gt;
  
  
  The customer holds the same dial
&lt;/h2&gt;

&lt;p&gt;There's a simple way to check whether a component is genuinely swappable: see who you're willing to let swap it.&lt;/p&gt;

&lt;p&gt;On &lt;a href="https://dev.to/goodbarber/your-chatbot-is-a-second-door-onto-your-content-535h"&gt;our RAG chatbot&lt;/a&gt;, the customer picks the tier of model it runs on — a light one for high-volume answering, a more capable one when nuance justifies it. Their content, their audience, their bill: their dial. We could not hand that dial to thousands of customers if changing the model changed what the feature &lt;em&gt;is&lt;/em&gt;. The feature is the retrieval, the access rules, the grounding in their content. The model is staffing.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Better" is also a regression
&lt;/h2&gt;

&lt;p&gt;The trap in every swap isn't the new model being worse. It's the new model being &lt;em&gt;different&lt;/em&gt; — smarter, even — and answering in ways the old contract never anticipated. More fluent refusals. Cleverer formats nobody asked for. A model that upgrades your feature's behavior without your consent has broken it, just politely.&lt;/p&gt;

&lt;p&gt;Which is why the plan for retirement day is deliberately unheroic: add a catalog line, adapt the dialect, run the harness, read the thresholds. &lt;a href="https://dev.to/goodbarber/shipping-a-component-that-never-answers-the-same-way-twice-5eo8"&gt;A component that never answers the same way twice&lt;/a&gt; taught us to treat the model as a vendor under contract. This is the other half of that discipline, the one you only see over time:&lt;/p&gt;

&lt;p&gt;We don't upgrade models. We re-certify them.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>architecture</category>
      <category>nocode</category>
    </item>
    <item>
      <title>Our MCP server is now a ChatGPT plugin</title>
      <dc:creator>Pierre- Laurent Medori</dc:creator>
      <pubDate>Fri, 14 Aug 2026 12:06:29 +0000</pubDate>
      <link>https://dev.to/goodbarber/our-mcp-server-is-now-a-chatgpt-plugin-2pjm</link>
      <guid>https://dev.to/goodbarber/our-mcp-server-is-now-a-chatgpt-plugin-2pjm</guid>
      <description>&lt;p&gt;GoodBarber is now listed in the &lt;a href="https://chatgpt.com/plugins/plugin_asdk_app_6a16d2ac52508191887344ea891be616?q=goodbarber" rel="noopener noreferrer"&gt;ChatGPT plugin directory&lt;/a&gt;. Find GoodBarber in the directory, connect your app, and run it from the chat: content, catalog, members, push notifications. This post extends &lt;a href="https://www.goodbarber.com/blog/goodbarber-is-now-in-the-chatgpt-app-directory-a1598/" rel="noopener noreferrer"&gt;the announcement on our blog&lt;/a&gt; with the developer view, and it closes a loop we opened when we wrote up how we built that server.&lt;/p&gt;

&lt;p&gt;One line of context for new readers: GoodBarber is a no-code app builder (running since 2011). Customers configure an app in a web back office; the platform compiles native iOS and Android builds plus a PWA. We &lt;a href="https://www.goodbarber.com/mcp/" rel="noopener noreferrer"&gt;built a public MCP server&lt;/a&gt; so that MCP clients (Claude, ChatGPT, Codex, Cursor, n8n and others) can operate those apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  One server, two ways in
&lt;/h2&gt;

&lt;p&gt;Nothing changed server-side. Both paths land on the MCP connection your app already has, scoped to that single app and authorized by you. What changed is the effort to get connected:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The official app in the directory.&lt;/strong&gt; Find GoodBarber, connect, authorize access to your app. No Developer mode, no URL to paste.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The full GoodBarber MCP server as a custom connector.&lt;/strong&gt; Enable ChatGPT's Developer mode and add &lt;code&gt;https://mcp.goodbarber.dev/mcp/sse&lt;/code&gt; (the &lt;code&gt;/sse&lt;/code&gt; is a route name; the server speaks Streamable HTTP). This is also the path for Codex: Settings, Integrations, MCP servers, leave the bearer token field empty and the save opens the OAuth flow in a browser.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The step-by-step for both lives on &lt;a href="https://www.goodbarber.com/connect-chatgpt-app/" rel="noopener noreferrer"&gt;our setup page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the connection actually is
&lt;/h2&gt;

&lt;p&gt;If you have read our write-up on building the server, you know the architecture. The short version:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One connection = one app.&lt;/strong&gt; Each GoodBarber app has its own MCP URL with a unique app id; an agent authorized on app A cannot reach app B. You authorize the connection in a browser via OAuth; there is no API key to paste into the chat.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Writes are policy-flagged.&lt;/strong&gt; Every write returns &lt;code&gt;_mcp_policy.verification_required: true&lt;/code&gt;, so a well-behaved client reads back what it just changed before claiming success.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The tool surface is documented, not hardcoded.&lt;/strong&gt; The authoritative list is the &lt;a href="https://mcp.goodbarber.dev/.well-known/mcp/server-card.json" rel="noopener noreferrer"&gt;server card&lt;/a&gt;; what a given app exposes can be a subset, depending on its features and plan.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our own tests, including the “Send my users a push about the spring sale.” example that opens &lt;a href="https://www.goodbarber.com/blog/goodbarber-is-now-in-the-chatgpt-app-directory-a1598/" rel="noopener noreferrer"&gt;the announcement&lt;/a&gt;, ran from a free ChatGPT account through the MCP connection: we drafted the push, confirmed it, and it landed on the test phones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the listing matters
&lt;/h2&gt;

&lt;p&gt;Building the server was engineering. Being found is distribution, and for an agent-facing product the distribution surface is inside the assistant, not on your marketing site. Until now, connecting GoodBarber to ChatGPT meant knowing the endpoint existed, enabling a developer toggle, and pasting a URL. That filters out almost everyone who would benefit. A directory listing replaces all of that with a search box: something a non-technical app owner can actually do, without reading a single setup doc.&lt;/p&gt;

&lt;p&gt;What we do not control: how the directory surfaces or ranks apps. That side is OpenAI's.&lt;/p&gt;

&lt;p&gt;For us, building the server turned out to be the easy half. The listing is what puts it in front of the people it was built for.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>openai</category>
      <category>chatgpt</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
