<?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</title>
    <description>The most recent home feed on DEV Community.</description>
    <link>https://dev.to</link>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/rss"/>
    <language>en</language>
    <item>
      <title>ADT Confirms Data Breach: ShinyHunters Claim 10 Million Records Stolen</title>
      <dc:creator>Diego Diaz</dc:creator>
      <pubDate>Thu, 16 Jul 2026 09:17:01 +0000</pubDate>
      <link>https://dev.to/alejandxr/adt-confirms-data-breach-shinyhunters-claim-10-million-records-stolen-3of3</link>
      <guid>https://dev.to/alejandxr/adt-confirms-data-breach-shinyhunters-claim-10-million-records-stolen-3of3</guid>
      <description>&lt;h2&gt;
  
  
  What Happened
&lt;/h2&gt;

&lt;p&gt;On April 23, 2026, the ShinyHunters extortion group posted on a leak forum what they claim to be approximately 10 million customer records belonging to &lt;strong&gt;ADT&lt;/strong&gt; — the largest residential security provider in the United States. ADT publicly &lt;a href="https://www.bleepingcomputer.com/news/security/adt-confirms-data-breach-after-shinyhunters-leak-threat/" rel="noopener noreferrer"&gt;confirmed the intrusion&lt;/a&gt; within 24 hours of the leak appearing, telling BleepingComputer that an unauthorized third party had "obtained certain customer information" and that the company's security team had since contained the access.&lt;/p&gt;

&lt;p&gt;The company is now contacting affected customers and offering complimentary credit monitoring, but the leaked sample published by the attackers — names, email addresses, home addresses, and references to active service contracts — has already been independently confirmed by multiple security outlets including &lt;a href="https://cyberinsider.com/adt-confirms-data-breach-after-hacker-claims-10-million-records-stolen/" rel="noopener noreferrer"&gt;CyberInsider&lt;/a&gt; and &lt;a href="https://undercodenews.com/adt-confirms-data-breach-after-shinyhunters-threatens-leak-of-customer-records/" rel="noopener noreferrer"&gt;UndercodeNews&lt;/a&gt;. Several customers contacted by reporters were able to verify their own records in the leaked sample.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical Analysis: How ShinyHunters Likely Got In
&lt;/h2&gt;

&lt;p&gt;ADT has not publicly disclosed the initial access vector, and the company's official statement is restricted to language about "unauthorized access to a portion of our environment" without specifying whether the breach hit a customer-facing application, an internal admin panel, or a third-party SaaS provider. That ambiguity is itself a signal: in our experience auditing companies that disclose breaches with this exact phrasing, the most common root causes are credential reuse against an admin login, an exposed API endpoint without rate limiting, or compromise of a marketing or CRM tool that holds a customer mirror.&lt;/p&gt;

&lt;p&gt;ShinyHunters' historical playbook reinforces those hypotheses. The group has spent the last 24 months running a consistent operation against cloud-hosted data warehouses and SaaS admin panels, taking advantage of customer accounts that lacked multi-factor authentication. &lt;a href="https://aviatrix.ai/threat-research-center/adt-confirms-data-breach-after-shinyhunters-leak-threat-2026/" rel="noopener noreferrer"&gt;Threat-research analysis of the ADT incident&lt;/a&gt; notes that the same group has previously compromised dozens of large enterprises throughout 2025 and early 2026 — Ticketmaster, Santander, AT&amp;amp;T, and a long list of less prominent victims — using nothing more sophisticated than valid credentials harvested from infostealer logs and a willingness to go straight to public extortion when the victim refuses to pay.&lt;/p&gt;

&lt;p&gt;If ADT followed the pattern of those earlier victims, the kill chain looks like this: an employee or contractor's workstation was infected with a generic infostealer at some point in 2025; that infostealer captured a session cookie or credential to a corporate SaaS (most commonly a data warehouse, CRM, or ticketing system); the credential ended up for sale on Russian Market or a comparable broker; ShinyHunters bought it for low three-digit dollars; they logged in, ran SELECT * against the customer table, exfiltrated, and then opened the extortion conversation on TOX or a private channel before going public when negotiations stalled.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who's Affected and What Was Exposed
&lt;/h2&gt;

&lt;p&gt;The leaked sample published by ShinyHunters indicates the following fields per record: &lt;strong&gt;full name&lt;/strong&gt;, &lt;strong&gt;email address&lt;/strong&gt;, &lt;strong&gt;physical home address&lt;/strong&gt;, &lt;strong&gt;phone number&lt;/strong&gt;, and &lt;strong&gt;service plan reference&lt;/strong&gt;. Notably, the leaked data so far does not appear to include payment card numbers, social security numbers, or alarm system credentials — which would be far more dangerous and which ADT has explicitly said are not affected.&lt;/p&gt;

&lt;p&gt;That said, the combination of name + home address + active home-security service contract is itself an unusually sensitive dataset. ADT's customer base skews toward homeowners specifically because they have something to protect: jewelry, electronics, sometimes weapons, occasionally high-net-worth occupants. A motivated burglary crew with this list has a directly actionable target file. The downstream risk here is not identity theft in the traditional sense — it's &lt;em&gt;physical&lt;/em&gt; targeting, especially for the subset of customers who installed ADT precisely because they were already concerned about being targeted.&lt;/p&gt;

&lt;p&gt;For the ShinyHunters operators themselves, the value in this dataset is not the fields per se but the leverage. Public extortion of a brand whose entire value proposition is "keeping your home safe" is exactly the kind of headline the group has cultivated for two years.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Protect Yourself if You're an ADT Customer
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Change your ADT account password today&lt;/strong&gt;, even though the leak does not appear to include passwords. If you have reused that password anywhere else, change it everywhere — and switch to a password manager so you don't have to do this exercise again next time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Enable multi-factor authentication on the ADT customer portal&lt;/strong&gt; and on any account where your ADT email is the recovery address. The fact that ShinyHunters has now confirmed they have your email means it is on every targeted phishing list for the foreseeable future.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Be skeptical of any communication referencing your ADT service in the next 90 days&lt;/strong&gt;. "Your ADT system needs an urgent firmware update — click here to authorize it" is the obvious phishing pretext, and it will be running. ADT will not ask you to authorize anything via email link; if you receive something that looks important, navigate to adt.com directly and log in.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Check whether your home address appears on people-search and data-broker sites&lt;/strong&gt;. If it does, request removal — California, Colorado, and a growing list of states require brokers to honor opt-outs. The combination of a verified home address on a public broker plus a known ADT service contract is a particularly clean target for harassment or burglary.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;If you are a high-profile or high-net-worth customer&lt;/strong&gt;, treat this as a strong nudge to audit your physical security posture independently — alarm code rotation, neighbor awareness, package handling. The breach itself does not give attackers your alarm code, but it tells them you have one worth bypassing.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Sable Angle
&lt;/h2&gt;

&lt;p&gt;What stands out about the ADT incident, and almost every ShinyHunters incident before it, is how unsophisticated the attack pattern is relative to the size of the prize. There is no zero-day. There is no nation-state implant. There is a stolen session cookie or a reused password and an admin endpoint that did not enforce MFA. The breach is the predictable consequence of a known control gap that nobody owned closing.&lt;/p&gt;

&lt;p&gt;In the offensive engagements &lt;a href="https://dev.to/pricing"&gt;we run at Sable&lt;/a&gt;, we surface this exact category of finding inside roughly 80% of mid-market and enterprise environments we test. The pattern is so consistent that we built our standard methodology around it: prove that a single leaked credential, given current MFA coverage and current admin-endpoint exposure, is sufficient to reach the customer or financial dataset. We do that work before ShinyHunters does, and we hand the customer a remediation path that is short, specific, and ordered by exploitability — not by CVSS theater. If you want to know whether an attacker with a single $50 credential could repeat the ADT outcome on your environment, &lt;a href="https://dev.to/research/startup-vulnerabilities"&gt;our research on third-party credential exposure&lt;/a&gt; is a reasonable place to start, and the conversation about a focused engagement is the next step.&lt;/p&gt;

</description>
      <category>breach</category>
      <category>adt</category>
      <category>shinyhunters</category>
      <category>extortion</category>
    </item>
    <item>
      <title>I Deleted Cursor, Copilot, and Windsurf. Here Is What I Use Instead.</title>
      <dc:creator>niuniu</dc:creator>
      <pubDate>Thu, 16 Jul 2026 09:14:41 +0000</pubDate>
      <link>https://dev.to/jarynagent/i-deleted-cursor-copilot-and-windsurf-here-is-what-i-use-instead-2bfi</link>
      <guid>https://dev.to/jarynagent/i-deleted-cursor-copilot-and-windsurf-here-is-what-i-use-instead-2bfi</guid>
      <description>&lt;p&gt;I have been coding for 5 years. I have used every AI tool on the market. Cursor, Copilot, Windsurf, Cody, you name it.&lt;/p&gt;

&lt;p&gt;Last month, I deleted all of them.&lt;/p&gt;

&lt;p&gt;Not because they are bad. They are great. But I found something better. Something free. Something that runs on my machine and never sends my code to the cloud.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem With AI Coding Tools
&lt;/h2&gt;

&lt;p&gt;Every AI coding tool has the same issue: &lt;strong&gt;your code leaves your machine.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you accept a suggestion from Cursor, your code context is sent to their servers. When Copilot completes your line, your code is processed on Microsoft's servers.&lt;/p&gt;

&lt;p&gt;For personal projects, maybe that is fine. But for work? For client code? For proprietary algorithms?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That is a deal breaker.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Price Problem
&lt;/h2&gt;

&lt;p&gt;Let us talk about money:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cursor: $20/month&lt;/li&gt;
&lt;li&gt;GitHub Copilot: $10/month&lt;/li&gt;
&lt;li&gt;Windsurf: $15/month&lt;/li&gt;
&lt;li&gt;Cody: $9/month&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is $54-648 per year. Per developer. For a team of 10, that is $5,400-6,480 per year.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For what? For code suggestions that may or may not be correct?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Found
&lt;/h2&gt;

&lt;p&gt;Three weeks ago, I discovered &lt;a href="https://monkeycode-ai.net/" rel="noopener noreferrer"&gt;MonkeyCode&lt;/a&gt;. It is an open-source AI coding platform that runs locally.&lt;/p&gt;

&lt;p&gt;Here is what makes it different:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. It Is Actually Free
&lt;/h3&gt;

&lt;p&gt;Not "free tier with 50 requests." Not "free trial for 14 days." &lt;strong&gt;Actually free. Forever.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I have been using it daily for three weeks. Code completions, chat, code review, refactoring. All of it. No limits. No credit card. No catch.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Your Code Stays Private
&lt;/h3&gt;

&lt;p&gt;MonkeyCode runs on your machine. Your code never leaves your computer. No cloud servers. No data collection. No "we may use your code to improve our models."&lt;/p&gt;

&lt;p&gt;This matters for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Client work (NDA compliance)&lt;/li&gt;
&lt;li&gt;Proprietary algorithms&lt;/li&gt;
&lt;li&gt;Security-sensitive code&lt;/li&gt;
&lt;li&gt;Personal projects you care about&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. You Choose Your Model
&lt;/h3&gt;

&lt;p&gt;MonkeyCode is not locked to one AI provider. You can use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MiMo&lt;/strong&gt; (Xiaomi's open-source model)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GPT-4o&lt;/strong&gt; or &lt;strong&gt;GPT-5&lt;/strong&gt; (OpenAI)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude&lt;/strong&gt; (Anthropic)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local models&lt;/strong&gt; via Ollama (free forever)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Switch models based on your task. Use a fast model for completions, a powerful one for complex refactoring.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. It Works With Your IDE
&lt;/h3&gt;

&lt;p&gt;MonkeyCode integrates with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;VS Code&lt;/strong&gt; (via extension)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JetBrains IDEs&lt;/strong&gt; (IntelliJ, PyCharm, WebStorm, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It feels like Copilot, but free and private.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Results After 3 Weeks
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before (Cursor)&lt;/th&gt;
&lt;th&gt;After (MonkeyCode)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Monthly cost&lt;/td&gt;
&lt;td&gt;$20&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Features shipped&lt;/td&gt;
&lt;td&gt;5/week&lt;/td&gt;
&lt;td&gt;8/week&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code privacy&lt;/td&gt;
&lt;td&gt;Cloud&lt;/td&gt;
&lt;td&gt;100% Local&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model flexibility&lt;/td&gt;
&lt;td&gt;Locked&lt;/td&gt;
&lt;td&gt;Any model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Peace of mind&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;Getting started takes 5 minutes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clone and run&lt;/span&gt;
git clone https://github.com/chaitin/MonkeyCode.git
&lt;span class="nb"&gt;cd &lt;/span&gt;MonkeyCode
docker-compose up &lt;span class="nt"&gt;-d&lt;/span&gt;

&lt;span class="c"&gt;# Optional: Use free local models&lt;/span&gt;
ollama pull qwen2.5-coder:7b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is it. No account creation. No credit card. No subscription.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Miss (And What I Do Not)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;I do not miss:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Monthly subscription notifications&lt;/li&gt;
&lt;li&gt;"You have 10 remaining requests" warnings&lt;/li&gt;
&lt;li&gt;Worrying about code privacy&lt;/li&gt;
&lt;li&gt;Being locked to one AI provider&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;I do miss (a little):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cursor's polished UI (MonkeyCode is simpler)&lt;/li&gt;
&lt;li&gt;The massive extension ecosystem&lt;/li&gt;
&lt;li&gt;English documentation (improving)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Bigger Picture
&lt;/h2&gt;

&lt;p&gt;The AI coding tool market is exploding. Every week there is a new tool, a new pricing model, a new "game-changing" feature.&lt;/p&gt;

&lt;p&gt;But here is what most people miss: &lt;strong&gt;the core technology is commoditizing.&lt;/strong&gt; The models are getting smaller, faster, and cheaper. Open-source models are catching up to closed-source ones.&lt;/p&gt;

&lt;p&gt;The real value is not in the model. It is in the tooling around it. And MonkeyCode has nailed the tooling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;If you are paying for AI coding tools right now, ask yourself: why?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/chaitin/MonkeyCode" rel="noopener noreferrer"&gt;MonkeyCode GitHub&lt;/a&gt; (3,700+ stars)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://monkeycode-ai.net/" rel="noopener noreferrer"&gt;MonkeyCode Website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/chaitin/MonkeyCodeOfficialPlugins" rel="noopener noreferrer"&gt;MonkeyCode Plugins&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;What AI coding tools do you use? How much are you paying? Would you switch to a free, private alternative?&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;P.S. I am not sponsored by MonkeyCode. I just think free, private tools deserve more attention.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;P.P.S. If you are a student or indie developer, MonkeyCode is especially great. No budget? No problem.&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>The redesign shipped. The smoke test kept walking the old UI.</title>
      <dc:creator>Omer Hochman</dc:creator>
      <pubDate>Thu, 16 Jul 2026 09:13:45 +0000</pubDate>
      <link>https://dev.to/omer_hochman/the-redesign-shipped-the-smoke-test-kept-walking-the-old-ui-47c8</link>
      <guid>https://dev.to/omer_hochman/the-redesign-shipped-the-smoke-test-kept-walking-the-old-ui-47c8</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://nlqdb.com/blog/smoke-test-walks-the-old-ui/" rel="noopener noreferrer"&gt;nlqdb.com/blog&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Our acceptance walkers pin literal UI strings on purpose. A walker asserts the homepage placeholder reads &lt;em&gt;exactly&lt;/em&gt; what we ship, that a heading says the words we wrote, that the query composer is where we put it. Drift fails the walk loudly — and a loud failure the moment the surface changes underneath you is precisely the regression detector you want. Silent tolerance is how a broken flow ships green.&lt;/p&gt;

&lt;p&gt;Then three things happened in one week. A homepage redesign moved the goal input to a different page. A copy edit reworded a heading. The MCP catalog &lt;em&gt;additively&lt;/em&gt; grew two tools. None of these broke the product. All three broke the walkers — which dutifully reported &lt;strong&gt;0/9&lt;/strong&gt;, and kept reporting it for a week.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trap isn't the literal assertions
&lt;/h2&gt;

&lt;p&gt;The reflex is to blame the pinned strings and loosen them into fuzzy matches. That's the wrong lesson. The literals did their job: the surface changed, the walk went red. The actual cost is that a red which &lt;em&gt;mixes&lt;/em&gt; "the product broke" with "the test went stale" takes a full manual triage to disentangle — and ours contained both at once. Two flows were red from pure test-drift. One flow was red from a real production wall. Same 0/9. You cannot tell which is which from the number, so every red costs you the same expensive human read regardless of whether anything is actually wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three notes that make pinned walkers pay off
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Pinned literals are fine &lt;strong&gt;only if reds are triaged inside a bounded window&lt;/strong&gt;. A detector nobody reads within a day isn't a detector — it's drift accumulating interest until the next person can't tell a week-old copy edit from this-morning's outage.&lt;/li&gt;
&lt;li&gt;The failure detail must name the element &lt;strong&gt;and&lt;/strong&gt; the expectation. &lt;code&gt;placeholder was null, expected "Ask your data anything"&lt;/code&gt; is decidable from the artifact alone — you know instantly it's drift, not breakage. &lt;code&gt;failed at step 2&lt;/code&gt; forces you to re-run the whole walk by hand to find out.&lt;/li&gt;
&lt;li&gt;"A PR touching a walked surface re-runs the walker" was already our rule — and it was skipped, because it was a convention, not a gate. A convention without an enforcing check is a wish. Wire the walker into the surface's required checks, or accept the false-red debt &lt;em&gt;knowingly&lt;/em&gt; — but don't pretend a rule nobody enforces is protecting you.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is a testing-hygiene pattern, not a product feature: it's for anyone whose end-to-end suite asserts real rendered copy rather than test-ids. The literals are worth keeping — they catch the drift you'd otherwise ship. Just make the red &lt;em&gt;self-explaining&lt;/em&gt; and triage it on a clock, or the detector quietly becomes a week of noise that hides the one failure that mattered. nlqdb is a database you query in plain English; this is one of the measurement lessons from keeping our stranger-walk honest as the product underneath it moved.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>e2e</category>
      <category>frontend</category>
    </item>
    <item>
      <title>What's Actually Inside a JWT (and Why Decoding One Isn't Verifying It)</title>
      <dc:creator>Anh Quân Nguyễn</dc:creator>
      <pubDate>Thu, 16 Jul 2026 09:13:37 +0000</pubDate>
      <link>https://dev.to/anh_qunnguyn_57549060f/whats-actually-inside-a-jwt-and-why-decoding-one-isnt-verifying-it-5362</link>
      <guid>https://dev.to/anh_qunnguyn_57549060f/whats-actually-inside-a-jwt-and-why-decoding-one-isnt-verifying-it-5362</guid>
      <description>&lt;p&gt;We've established that &lt;a href="https://dev.to/anh_qunnguyn_57549060f/what-base64-actually-does-to-your-bytes-and-why-its-not-encryption-57ec"&gt;Base64 isn't encryption&lt;/a&gt; and that &lt;a href="https://dev.to/anh_qunnguyn_57549060f/how-hashing-actually-works-and-why-you-cant-un-hash-a-password-16l6"&gt;you can't un-hash a password&lt;/a&gt;. JWTs are where both of those facts collide — and where the misunderstanding gets expensive, because this one has a CVE class named after it.&lt;/p&gt;

&lt;p&gt;Here's the claim that surprises people the first time: &lt;strong&gt;a JWT is not encrypted, and anyone holding one can read every claim inside it without a key, a library, or your permission.&lt;/strong&gt; That's not a bug. But if you don't know it, you will eventually put something in a token that shouldn't be there.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three dots, three parts
&lt;/h3&gt;

&lt;p&gt;A JWT is a string with exactly two dots in it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJuYW1lIjoiQWxpY2UiLCJhZG1pbiI6dHJ1ZX0.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Split on the dots and you get &lt;strong&gt;header&lt;/strong&gt;, &lt;strong&gt;payload&lt;/strong&gt;, &lt;strong&gt;signature&lt;/strong&gt;. The first two are just Base64url-encoded JSON. Decode part one and it's:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"alg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HS256"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"typ"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JWT"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Part two:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Alice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"admin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No key involved. No decryption. It's JSON that went through an encoder, and encoding runs backwards for free — that's what encoding &lt;em&gt;is&lt;/em&gt;. If a token is sitting in someone's browser storage, in a log file, in a proxy's request dump, or in a screenshot pasted into Slack, every claim in it is readable by whoever has it.&lt;/p&gt;

&lt;p&gt;So the rule falls out immediately: &lt;strong&gt;never put a secret in a JWT payload.&lt;/strong&gt; Not a password, not an API key, not a national ID, not "internal_notes". Assume the payload is public, because functionally it is. It's a signed postcard, not a sealed envelope.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why "Base64url" and not plain Base64
&lt;/h3&gt;

&lt;p&gt;Small detail, real consequences. JWTs live in hostile places — &lt;code&gt;Authorization&lt;/code&gt; headers, query strings, cookies, URLs — and standard Base64's alphabet includes &lt;code&gt;+&lt;/code&gt;, &lt;code&gt;/&lt;/code&gt;, and &lt;code&gt;=&lt;/code&gt;. Those characters mean something in a URL: &lt;code&gt;+&lt;/code&gt; can become a space, &lt;code&gt;/&lt;/code&gt; splits a path, &lt;code&gt;=&lt;/code&gt; separates a query param. A token pasted into a link would corrupt itself.&lt;/p&gt;

&lt;p&gt;Base64url fixes it by swapping &lt;code&gt;+&lt;/code&gt; → &lt;code&gt;-&lt;/code&gt;, &lt;code&gt;/&lt;/code&gt; → &lt;code&gt;_&lt;/code&gt;, and dropping the &lt;code&gt;=&lt;/code&gt; padding. Same encoding, URL-safe alphabet. This is why a JWT survives being a query parameter while a raw Base64 blob often doesn't — and it's the same class of problem &lt;a href="https://calculators.im/url-encoder-decoder" rel="noopener noreferrer"&gt;percent-encoding&lt;/a&gt; solves for everything else you stuff into a URL. Two different escaping schemes, one shared reason: some characters are structural, and data that contains them has to get out of the way.&lt;/p&gt;

&lt;h3&gt;
  
  
  The signature is the only part that matters
&lt;/h3&gt;

&lt;p&gt;The third segment is where the actual security is. It's not a hash of the payload — it's a &lt;strong&gt;keyed&lt;/strong&gt; hash (an HMAC, for &lt;code&gt;HS256&lt;/code&gt;) or a digital signature (for &lt;code&gt;RS256&lt;/code&gt;), computed over the header and payload together, using a secret only your server knows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;signature = HMAC-SHA256(base64url(header) + "." + base64url(payload), secret)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is what a hash is &lt;em&gt;for&lt;/em&gt;, in the sense from the last article: a tamper-evident fingerprint. An attacker can freely edit the payload — flip &lt;code&gt;"admin": false&lt;/code&gt; to &lt;code&gt;"admin": true&lt;/code&gt; — and re-encode it. That part is trivial. What they cannot do is produce the matching signature, because that needs the secret. Your server recomputes the HMAC over whatever it received and compares. Mismatch means forged or corrupted, and the token is rejected.&lt;/p&gt;

&lt;p&gt;So the token is readable by everyone and forgeable by no one. That's the actual security model, and it's a good one — as long as you check the signature.&lt;/p&gt;

&lt;h3&gt;
  
  
  The mistake that has a CVE
&lt;/h3&gt;

&lt;p&gt;Which brings us to the trap in the title. Decoding a JWT and verifying a JWT are completely different operations, and it is very easy to write code that does the first while believing it does the second:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This is NOT authentication.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;atob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;grantAdminAccess&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;// 💥&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That code reads a claim the attacker wrote and never touches the signature. Anyone can craft that token in ten seconds. The library function you actually want is &lt;code&gt;verify(token, secret)&lt;/code&gt;, not &lt;code&gt;decode(token)&lt;/code&gt; — and in most JWT libraries those sit right next to each other with nearly identical names. That's how this keeps happening.&lt;/p&gt;

&lt;p&gt;The historical version of this bug is worse and worth knowing, because it explains a defensive habit you'll see in real code. Early JWT libraries trusted the &lt;code&gt;alg&lt;/code&gt; field in the &lt;strong&gt;header&lt;/strong&gt; — a field the attacker controls. Set &lt;code&gt;"alg": "none"&lt;/code&gt;, send an empty signature, and some libraries said "no algorithm specified, nothing to check, looks valid." Others accepted swapping &lt;code&gt;RS256&lt;/code&gt; (asymmetric) for &lt;code&gt;HS256&lt;/code&gt; (symmetric), tricking the server into verifying with the &lt;em&gt;public&lt;/em&gt; key as if it were the shared secret. Both are full authentication bypasses caused by letting the token declare how it should be checked. The fix is to pin the expected algorithm server-side and never read it from the token.&lt;/p&gt;

&lt;p&gt;If you want to see the split for yourself, take any non-production token and drop it into a &lt;a href="https://calculators.im/jwt-decoder" rel="noopener noreferrer"&gt;JWT decoder&lt;/a&gt;: the header and payload render as readable JSON instantly, while the signature stays an opaque blob. That's the honest picture — the decoder can show you the claims because &lt;em&gt;nothing is protecting them&lt;/em&gt;, and it can't tell you whether the token is genuine because that requires the secret it doesn't have. The asymmetry on the screen &lt;em&gt;is&lt;/em&gt; the lesson.&lt;/p&gt;

&lt;h3&gt;
  
  
  What to actually check
&lt;/h3&gt;

&lt;p&gt;A signature check alone isn't enough; a valid signature only proves the token is authentic, not that it's still appropriate. Verify these too:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;exp&lt;/code&gt; (expiration)&lt;/strong&gt; — a signature never expires on its own. If you don't check &lt;code&gt;exp&lt;/code&gt;, a stolen token works forever.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;iss&lt;/code&gt; / &lt;code&gt;aud&lt;/code&gt;&lt;/strong&gt; — is this token from the issuer you trust, and was it minted for &lt;em&gt;your&lt;/em&gt; service? Without these, a valid token from a different tenant or app may sail through.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;nbf&lt;/code&gt; (not before)&lt;/strong&gt; — reject tokens not yet valid.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Revocation&lt;/strong&gt; — JWTs are stateless by design, so you can't "delete" one. That's the real trade-off: no session lookup, but no logout either. Short expiry plus refresh tokens, or a deny-list for the paranoid paths.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The one-sentence version
&lt;/h3&gt;

&lt;p&gt;A JWT is Base64url-encoded JSON that anyone can read plus a signature only your server can produce — so treat the payload as public, never trust a claim you haven't verified with the secret, pin the algorithm instead of believing the header, and remember that &lt;code&gt;decode()&lt;/code&gt; answers "what does this say" while only &lt;code&gt;verify()&lt;/code&gt; answers "should I believe it."&lt;/p&gt;

</description>
      <category>jwt</category>
      <category>security</category>
      <category>webdev</category>
      <category>authentication</category>
    </item>
    <item>
      <title>Agentic Systems: From Single Agent to Orchestration</title>
      <dc:creator>mihir mohapatra</dc:creator>
      <pubDate>Thu, 16 Jul 2026 09:11:30 +0000</pubDate>
      <link>https://dev.to/mihirmohapatra/agentic-systems-from-single-agent-to-orchestration-1jln</link>
      <guid>https://dev.to/mihirmohapatra/agentic-systems-from-single-agent-to-orchestration-1jln</guid>
      <description>&lt;p&gt;A single agent — one model, a system prompt, a handful of tools, and a loop — gets you surprisingly far. It's also where most people stop, because it works for demos and falls over the moment a task needs more than one kind of reasoning happening at once.&lt;/p&gt;

&lt;p&gt;Multi-agent orchestration isn't a hype upgrade over a single agent — it's what you reach for when a task naturally decomposes into sub-tasks that benefit from different context, different tools, or genuinely different roles. This post covers when that shift is warranted, the orchestration patterns actually used in production, and a small working example of a coordinator delegating to worker agents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a single agent stops being enough
&lt;/h2&gt;

&lt;p&gt;A single agent's context window and tool list grow with every capability you bolt on. Three failure modes show up as that happens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Context pollution&lt;/strong&gt; — a research task's intermediate scratch work bleeds into the reasoning for an unrelated coding sub-task, degrading both&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool overload&lt;/strong&gt; — past a certain number of tools in one system prompt, selection accuracy drops; the model spends more of its reasoning deciding &lt;em&gt;which&lt;/em&gt; tool than actually using it well&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No separation of concerns&lt;/strong&gt; — a single agent can't easily hold "be a careful planner" and "be a fast, narrow executor" as two different behavioral modes at once&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Orchestration solves this by splitting one large, blurry job into a coordinator plus specialized workers, each with a narrow role, its own context, and only the tools it actually needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The patterns that show up in practice
&lt;/h2&gt;

&lt;p&gt;There isn't one "multi-agent architecture" — a few concrete patterns cover most real systems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sequential (pipeline)&lt;/strong&gt; — agent A's output feeds agent B, feeds agent C. Good for linear workflows like research → draft → review.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrent (fan-out/fan-in)&lt;/strong&gt; — the coordinator dispatches the same or related sub-tasks to multiple agents in parallel, then merges results. Good for independent sub-tasks — e.g. summarizing five documents at once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handoff&lt;/strong&gt; — one agent decides mid-task that another agent is better suited and transfers control (and context) to it. Good for triage-style systems, like a support bot handing off to a specialist agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hierarchical (coordinator/worker)&lt;/strong&gt; — a central planner decomposes the task and delegates to specialized workers, then assembles their outputs into a final answer. This is the most common production pattern and the one the example below uses.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Frameworks like LangGraph model this as an explicit state graph, CrewAI models it as role-based "crews," and minimal SDKs like the OpenAI Agents SDK treat sub-agents as callable tools from a parent agent. The underlying pattern is the same regardless of framework — what differs is how much structure and observability you get by default.&lt;/p&gt;

&lt;h2&gt;
  
  
  A working example: coordinator + worker agents in Rust
&lt;/h2&gt;

&lt;p&gt;Here's a minimal hierarchical setup: a coordinator agent receives a research request, decomposes it into sub-tasks, dispatches them to worker agents concurrently, and merges the results.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;futures&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;future&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;join_all&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;serde&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;Deserialize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Serialize&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;Clone,&lt;/span&gt; &lt;span class="nd"&gt;Serialize,&lt;/span&gt; &lt;span class="nd"&gt;Deserialize)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;SubTask&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;assigned_role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AgentRole&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;Clone,&lt;/span&gt; &lt;span class="nd"&gt;Serialize,&lt;/span&gt; &lt;span class="nd"&gt;Deserialize)]&lt;/span&gt;
&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;AgentRole&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Researcher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;CodeAnalyst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Summarizer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;Serialize)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;WorkerResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;task_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tokens_used&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Coordinator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;llm_client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;LlmClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;Coordinator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/// Decompose a high-level request into sub-tasks using the coordinator's own LLM call.&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SubTask&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;planning_prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"Break this request into 2-4 independent sub-tasks, each assigned &lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;
             to Researcher, CodeAnalyst, or Summarizer. Request: {request}"&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.llm_client&lt;/span&gt;&lt;span class="nf"&gt;.complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;planning_prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nf"&gt;parse_subtasks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// structured output parsing, omitted for brevity&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/// Dispatch sub-tasks to worker agents concurrently, then merge.&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;subtasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="nf"&gt;.plan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;worker_futures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subtasks&lt;/span&gt;&lt;span class="nf"&gt;.into_iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.llm_client&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;run_worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;WorkerResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;join_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;worker_futures&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="nf"&gt;.merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/// A final LLM call that synthesizes worker outputs into one coherent answer.&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;WorkerResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;combined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="nf"&gt;.map&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="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[{}]: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="py"&gt;.task_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="py"&gt;.output&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="py"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="nf"&gt;.join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;merge_prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"Synthesize these sub-task results into one coherent response:&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;{combined}"&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.llm_client&lt;/span&gt;&lt;span class="nf"&gt;.complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;merge_prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;run_worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;LlmClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SubTask&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WorkerResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;role_prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="py"&gt;.assigned_role&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nn"&gt;AgentRole&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Researcher&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"You are a focused researcher. Be concise and cite sources."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nn"&gt;AgentRole&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;CodeAnalyst&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"You are a code analyst. Focus only on technical accuracy."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nn"&gt;AgentRole&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Summarizer&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"You are a summarizer. Compress without losing key facts."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{role_prompt}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;Task: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="py"&gt;.description&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="nf"&gt;.complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;WorkerResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;task_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="py"&gt;.id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;tokens_used&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;estimate_tokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things worth calling out in this shape:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Each worker gets its own narrow system prompt&lt;/strong&gt; (&lt;code&gt;role_prompt&lt;/code&gt;) instead of sharing the coordinator's full context — this is the actual mechanism that fixes context pollution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;join_all&lt;/code&gt; runs workers concurrently&lt;/strong&gt;, not sequentially — for independent sub-tasks, this is a straightforward latency win that a single-agent loop can't get you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The merge step is itself an LLM call&lt;/strong&gt;, not string concatenation — worker outputs are often redundant or need reconciling, and skipping this step is a common source of disjointed final answers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where this pattern breaks down
&lt;/h2&gt;

&lt;p&gt;Multi-agent orchestration isn't free — it's worth naming the costs explicitly, since a lot of write-ups skip this part:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;More tokens spent overall&lt;/strong&gt; — planning, dispatch, and merge steps are all additional LLM calls layered on top of the actual work&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Harder to debug&lt;/strong&gt; — a wrong final answer could originate in planning, in any worker, or in the merge step; this is exactly why the tracing patterns from the observability post in this series matter more, not less, once you add orchestration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coordination overhead can exceed the benefit&lt;/strong&gt; for genuinely simple, linear tasks — a single well-scoped agent with good tools is still the right default until you hit one of the failure modes above&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Gartner-cited pattern worth taking seriously: a meaningful share of agentic projects get cancelled not because agents don't work, but because teams reached for orchestration before the task actually needed it, and the coordination overhead outweighed the gain.&lt;/p&gt;

&lt;h2&gt;
  
  
  A reasonable decision rule
&lt;/h2&gt;

&lt;p&gt;Reach for a single agent by default. Move to orchestration when at least one of these is true:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The task has genuinely independent sub-parts that would otherwise share (and pollute) one context window&lt;/li&gt;
&lt;li&gt;Different sub-tasks need meaningfully different tool sets or behavioral instructions&lt;/li&gt;
&lt;li&gt;You need parallelism for latency reasons, not just organizational tidiness&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If none of those apply, a single well-designed agent with good tool selection will usually outperform a multi-agent system on both cost and reliability.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;The next post in this series steps back to a cost/control framework: when running open-weight models yourself makes sense versus calling a closed API, and how to reason about that tradeoff instead of defaulting to whichever one is trending.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is part of a series on AI infrastructure patterns, drawing on production work building multi-provider AI gateways in Rust. Follow along for the rest of the series.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>architecture</category>
      <category>rust</category>
    </item>
    <item>
      <title>Magento 2 Widget Performance Optimization: Fix Slow CMS Blocks &amp; Homepage Rendering</title>
      <dc:creator>Magevanta</dc:creator>
      <pubDate>Thu, 16 Jul 2026 09:08:36 +0000</pubDate>
      <link>https://dev.to/magevanta/magento-2-widget-performance-optimization-fix-slow-cms-blocks-homepage-rendering-4e27</link>
      <guid>https://dev.to/magevanta/magento-2-widget-performance-optimization-fix-slow-cms-blocks-homepage-rendering-4e27</guid>
      <description>&lt;p&gt;Magento 2 widgets are everywhere. The product carousel on your homepage. The "New Arrivals" CMS block. The category list sidebar. The promotional banner that changes every week. Every one of these is a widget — and every one of them can slow your store to a crawl if configured poorly.&lt;/p&gt;

&lt;p&gt;Unlike obvious performance culprits like unoptimized images or missing Varnish caching, widgets fly under the radar. They are buried in CMS content, referenced by &lt;code&gt;{{widget type="..."}}&lt;/code&gt; directives, or loaded via layout XML as static blocks. Their impact compounds: a single widget that runs a product collection query with no cache tags can add 200-400ms to every page load. Three of them? You just lost half a second on the critical path.&lt;/p&gt;

&lt;p&gt;This post covers how to identify slow widgets, why they hurt performance, and what you can do about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Widgets Work in Magento 2
&lt;/h2&gt;

&lt;p&gt;Magento 2 widgets are small UI components rendered through a combination of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Widget directives&lt;/strong&gt; in CMS pages/blocks: &lt;code&gt;{{widget type="Magento\Catalog\Block\Product\Widget\NewWidget" ...}}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Layout XML handles&lt;/strong&gt; that insert widget blocks into pages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Widget instances&lt;/strong&gt; configured in Admin → Content → Widgets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When Magento renders a page containing a widget, it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Parses the directive or layout instruction&lt;/li&gt;
&lt;li&gt;Instantiates the block class&lt;/li&gt;
&lt;li&gt;Runs any collection queries or data loading&lt;/li&gt;
&lt;li&gt;Renders the template&lt;/li&gt;
&lt;li&gt;Injects the output into the page&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The problem is that step 3 often hits the database with unoptimized queries, and step 4 may load additional blocks recursively — all without proper full-page cache (FPC) integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Widget Performance Problems
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Product Collection Widgets Without Caching
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;Magento\Catalog\Block\Product\ListProduct&lt;/code&gt; widget, and any custom widget extending it, typically loads a product collection. If the block does not implement &lt;code&gt;Magento\Framework\DataObject\IdentityInterface&lt;/code&gt; to return cache tags, Varnish and the built-in FPC cannot invalidate the widget when products change. Worse, if the block's &lt;code&gt;getCacheLifetime()&lt;/code&gt; returns &lt;code&gt;null&lt;/code&gt; or is not set, Magento skips block caching entirely.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. CMS Block Widgets with Recursive Rendering
&lt;/h3&gt;

&lt;p&gt;A CMS block containing a product widget, which contains another CMS block, which contains a category list widget — each level adds rendering overhead. Magento's block rendering is single-threaded and recursive; deep nesting means more block instantiation, more layout XML merging, and more template rendering time.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Widget Instances with Overly Broad Layout Updates
&lt;/h3&gt;

&lt;p&gt;In Admin → Content → Widgets, each widget instance has a "Layout Updates" section where you assign it to specific pages and containers. A widget assigned to "All Pages" in the "Main Content Area" container will render on every single page of your store — including checkout, customer account, and static CMS pages. If that widget loads a product collection, you are running a database query on every page, even pages where the widget is not visible or relevant.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Static Block Widgets with Dynamic Content
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;Magento\Cms\Block\Widget\Block&lt;/code&gt; widget renders a CMS static block. If that static block contains a widget directive (like a product list), the static block itself is cacheable, but the nested widget may not be. Magento caches the CMS block output by block ID and store view, but cache invalidation for nested widgets depends on both the outer block's and inner widget's cache tags being correctly defined.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Identify Slow Widgets
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Enable Magento Profiler
&lt;/h3&gt;

&lt;p&gt;Add &lt;code&gt;?profiler=1&lt;/code&gt; to any frontend URL (requires the profiler to be enabled in &lt;code&gt;app/etc/env.php&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="s1"&gt;'system'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'profiler'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'enable'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look for block rendering times under the "Blocks" section. Any block starting with &lt;code&gt;widget.&lt;/code&gt; or &lt;code&gt;cms.&lt;/code&gt; with a rendering time over 50ms deserves investigation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Blackfire or Tideways
&lt;/h3&gt;

&lt;p&gt;Profile a homepage or category page load. Look for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database queries originating from &lt;code&gt;Magento\Widget&lt;/code&gt; or &lt;code&gt;Magento\Cms&lt;/code&gt; namespaces&lt;/li&gt;
&lt;li&gt;Repeated collection loads from widget block classes&lt;/li&gt;
&lt;li&gt;Long &lt;code&gt;render()&lt;/code&gt; or &lt;code&gt;toHtml()&lt;/code&gt; calls on widget blocks&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Check Widget Instance Layout Scope
&lt;/h3&gt;

&lt;p&gt;Run this SQL query to find widget instances assigned to overly broad layouts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;wi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;instance_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wpm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;page_group&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wpm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;layout_handle&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;widget_instance&lt;/span&gt; &lt;span class="n"&gt;wi&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;widget_instance_page&lt;/span&gt; &lt;span class="n"&gt;wpm&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;wi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;instance_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wpm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;instance_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;wpm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;page_group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'all_pages'&lt;/span&gt;
   &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;wpm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;layout_handle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'default'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any widget on &lt;code&gt;all_pages&lt;/code&gt; or &lt;code&gt;default&lt;/code&gt; handle that loads a collection should be re-evaluated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing Widget Performance
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Implement Proper Block Caching
&lt;/h3&gt;

&lt;p&gt;Every custom widget block should implement &lt;code&gt;IdentityInterface&lt;/code&gt; and define cache tags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Magento\Framework\DataObject\IdentityInterface&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Magento\Catalog\Model\Product&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;ProductModel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyCustomWidget&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Template&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;IdentityInterface&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getIdentities&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$identities&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;ProductModel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CACHE_TAG&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getProductCollection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$identities&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ProductModel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CACHE_TAG&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'_'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$identities&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getCacheLifetime&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;?int&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Cache for 1 hour, or use null for FPC-managed lifetime&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells Magento (and Varnish) to invalidate this block whenever any product or a specific product changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Scope Widget Instances Correctly
&lt;/h3&gt;

&lt;p&gt;In Admin → Content → Widgets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Assign widgets to &lt;strong&gt;specific layout handles&lt;/strong&gt; instead of "All Pages"&lt;/li&gt;
&lt;li&gt;Use "Main Content Area" only for homepage-critical widgets&lt;/li&gt;
&lt;li&gt;For sidebar widgets, use "Sidebar Main" or "Sidebar Additional" containers&lt;/li&gt;
&lt;li&gt;Remove widgets from checkout and customer account pages unless absolutely necessary&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, a "New Arrivals" carousel should target &lt;code&gt;cms_index_index&lt;/code&gt; (homepage), not &lt;code&gt;default&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Replace Dynamic Widgets with Static Content Where Possible
&lt;/h3&gt;

&lt;p&gt;Not every product list needs to be dynamic. If you have a "Best Sellers" widget that updates weekly, consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generating the HTML via a cron job and storing it in a CMS block&lt;/li&gt;
&lt;li&gt;Using a cache warmer to pre-render the widget at a set interval&lt;/li&gt;
&lt;li&gt;Replacing the widget with a static block for low-traffic periods and switching to dynamic only during high-traffic events&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Limit Collection Size and Eager-Load Relations
&lt;/h3&gt;

&lt;p&gt;Widget blocks that load product collections should:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set a strict page size limit (e.g., &lt;code&gt;-&amp;gt;setPageSize(8)&lt;/code&gt; for carousels)&lt;/li&gt;
&lt;li&gt;Add only necessary attributes via &lt;code&gt;-&amp;gt;addAttributeToSelect(['name', 'price', 'small_image'])&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Enable flat catalog for large stores to avoid EAV joins&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;-&amp;gt;addUrlRewrite()&lt;/code&gt; to pre-load URL rewrites and avoid N+1 queries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example optimized collection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$collection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;productCollectionFactory&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$collection&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addAttributeToSelect&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'price'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'small_image'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'url_key'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addAttributeToFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;STATUS_ENABLED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addAttributeToFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'visibility'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'in'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;Visibility&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;VISIBILITY_IN_CATALOG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;Visibility&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;VISIBILITY_BOTH&lt;/span&gt;
    &lt;span class="p"&gt;]])&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addUrlRewrite&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setPageSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setCurPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Audit CMS Blocks for Nested Widgets
&lt;/h3&gt;

&lt;p&gt;Run a database query to find CMS blocks containing widget directives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;block_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;cms_block&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%{{widget%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For each result, review the content. If a CMS block contains a product widget that is also used inside a widget instance, you may be double-rendering. Flatten the structure or move the widget logic entirely into the block template.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advanced: Widget Caching with Varnish ESI
&lt;/h2&gt;

&lt;p&gt;For stores with Varnish, Edge Side Includes (ESI) can help with widgets that need to be dynamic but should not block the full page cache:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Configure the widget block with &lt;code&gt;cacheable="false"&lt;/code&gt; or a very short TTL&lt;/li&gt;
&lt;li&gt;Add ESI support in your Varnish VCL for widget block URLs&lt;/li&gt;
&lt;li&gt;Let Varnish serve the cached page while fetching the widget content separately&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;However, be cautious: each ESI request adds HTTP overhead. Use ESI for high-value, truly dynamic widgets (e.g., personalized recommendations), not for static product lists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring Widget Performance in Production
&lt;/h2&gt;

&lt;p&gt;Set up alerts for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pages with block rendering time &amp;gt; 200ms total&lt;/li&gt;
&lt;li&gt;Database queries from &lt;code&gt;widget&lt;/code&gt; blocks exceeding 100ms&lt;/li&gt;
&lt;li&gt;Cache hit ratio drops on pages with heavy widget usage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;New Relic, Datadog, or even Magento Cloud's built-in monitoring can surface widget-level slowness if you tag your custom blocks with meaningful names during profiling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Widgets are convenient but dangerous. Every &lt;code&gt;{{widget ...}}&lt;/code&gt; directive is a potential performance regression. Treat them like any other code: audit, profile, cache, and scope tightly.&lt;/p&gt;

&lt;p&gt;Key takeaways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implement &lt;code&gt;IdentityInterface&lt;/code&gt; on custom widget blocks&lt;/li&gt;
&lt;li&gt;Scope widget instances to specific layout handles&lt;/li&gt;
&lt;li&gt;Limit collection sizes and eager-load data&lt;/li&gt;
&lt;li&gt;Flatten nested CMS block → widget structures&lt;/li&gt;
&lt;li&gt;Monitor block rendering times in production&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your homepage does not need to query the database on every request. Fix your widgets, and your customers will notice the speed.&lt;/p&gt;

</description>
      <category>magento</category>
    </item>
    <item>
      <title>Pinch: A Fingertip-Sized 32-Bit Arduino-Compatible Dev Board</title>
      <dc:creator>circuitrocks</dc:creator>
      <pubDate>Thu, 16 Jul 2026 09:07:51 +0000</pubDate>
      <link>https://dev.to/circuitrocks/pinch-a-fingertip-sized-32-bit-arduino-compatible-dev-board-1a22</link>
      <guid>https://dev.to/circuitrocks/pinch-a-fingertip-sized-32-bit-arduino-compatible-dev-board-1a22</guid>
      <description>&lt;p&gt;You know that weekend project idea that stalled because the dev board was bigger than the thing you wanted to hide it inside? A wearable, a keyfob mod, a sensor tucked into a bottle cap? The pinch from moddo is the board that finally makes those builds realistic.&lt;/p&gt;

&lt;h3&gt;A whole Arduino the size of a USB-C plug&lt;/h3&gt;

&lt;p&gt;moddo calls the pinch "the world's smallest 32-bit Arduino-compatible board," and while nobody has the record book out, it is hard to argue. The whole thing takes up about the same surface area as a female USB-C connector. Under the hood sits a Microchip SAMD11, an ARM Cortex-M0+ running at 48 MHz, with native USB so you flash it straight over the cable with no separate programmer. You get 15 GPIO pins plus the usual SPI, I2C, PWM, and UART, which is plenty for a small sensor node or a blinky wearable.&lt;/p&gt;

&lt;h3&gt;The parts-and-cost reality&lt;/h3&gt;

&lt;p&gt;Two things to keep in mind before you plan a build around it. First, the specs are tight: 4 KB of SRAM and 16 KB of flash, and the bootloader eats 4 KB of that flash right off the top, leaving you roughly 12 KB to work in. Coming from an ESP32 you will feel the squeeze, so skip heavy libraries, watch your String usage, and lean on direct register writes if you run out of room. Second, it is a pre-order: the $16 board (roughly PHP 900) is slated to ship in September, so this is a plan-it-now, solder-it-later project.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MCU: Microchip SAMD11, Cortex-M0+ at 48 MHz&lt;/li&gt;
&lt;li&gt;Memory: 4 KB SRAM, 16 KB flash (about 12 KB usable)&lt;/li&gt;
&lt;li&gt;I/O: 15 GPIO, native USB, SPI, I2C, PWM, UART&lt;/li&gt;
&lt;li&gt;Bonus: a breakout board ships with it so you can breadboard the pins&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Worth a Sunday of planning&lt;/h3&gt;

&lt;p&gt;The circuit schematic and a STEP 3D model are already public, with the full board design files close behind, so you can lay out your enclosure or PCB before the board even lands on your bench. Read the specs straight from the source at moddo.io/pages/pinch, decide which sensor you would wire to those 15 pins, and have your firmware ready for the day it arrives.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://blog.circuit.rocks/pinch-a-fingertip-sized-32-bit-arduino-compatible-dev-board" rel="noopener noreferrer"&gt;blog.circuit.rocks&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  arduino #arduinoprojects #electronics #embedded #circuitrocks
&lt;/h1&gt;

</description>
      <category>arduino</category>
      <category>samd11</category>
      <category>microcontrollers</category>
      <category>usbc</category>
    </item>
    <item>
      <title>Diaspora Coders Assemble Earthquake Response in Hours with AI</title>
      <dc:creator>Dave Kurian</dc:creator>
      <pubDate>Thu, 16 Jul 2026 09:07:01 +0000</pubDate>
      <link>https://dev.to/davekurian/diaspora-coders-assemble-earthquake-response-in-hours-with-ai-4c66</link>
      <guid>https://dev.to/davekurian/diaspora-coders-assemble-earthquake-response-in-hours-with-ai-4c66</guid>
      <description>&lt;h2&gt;
  
  
  Three hours, six coders, 30,000 missing people
&lt;/h2&gt;

&lt;p&gt;A Venezuelan programmer in Buenos Aires had a team of six and a flood of missing-person reports. Without AI, the build would have taken roughly 24 hours. With Claude Opus, it took three. The site — Desaparecidos Terremoto Venezuela — let anyone upload a photo, ran facial recognition against the missing-person database, and matched people in seconds. No app, no registration, a WhatsApp link. By the second day it had absorbed more than 30,000 submissions.&lt;/p&gt;

&lt;p&gt;Four hours after that, a developer in California shipped Ayuda en Camino on Replit. It matched supplies (medicine, blankets, transport) against need, and a WhatsApp assistant fielded queries from people whose connections couldn't load a website.&lt;/p&gt;

&lt;p&gt;Twin earthquakes had flattened hospitals and severed fiber and mobile towers across northern Venezuela. The state was slow. The telecom backbone was in pieces. The diaspora coders shipped first.&lt;/p&gt;

&lt;p&gt;That inversion is the story. Not "AI saves the day" — that's lazy framing. The real story is that consumer-grade AI on any developer's laptop, wielded by people with domain knowledge and urgency, outpaced an emergency budget. The tools that did it — Claude Opus, Replit, WhatsApp — were the same ones sitting on every senior engineer's machine. The state had the budget. The coders had the latency.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mechanics: what three hours actually buys you
&lt;/h2&gt;

&lt;p&gt;Desaparecidos is the cleanest example. The site does four things: photo upload, facial matching against a missing-person database, deduplication, and a public lookup flow. Each one alone is a weekend project. Together, with a polished UI, it's a sprint.&lt;/p&gt;

&lt;p&gt;Claude Opus collapsed the sprint into an afternoon. The article pegs the build at "roughly 24 hours without rest" without AI and "three" with it. That is an 8× compression, and it isn't from autocomplete — it's from a model that holds the whole architecture in context, drafts the matching logic, writes the moderation rules, and ships the boilerplate that would otherwise eat the first six hours.&lt;/p&gt;

&lt;p&gt;Ayuda en Camino is the second pattern. Replit's collaborative runtime means the diaspora in Buenos Aires, Santiago, Miami, and San Francisco can co-edit the same codebase without provisioning anything. Four hours from idea to production URL. The second inversion isn't "AI writes code faster." It's "AI + a hosted runtime collapses deployment to zero."&lt;/p&gt;

&lt;p&gt;Both patterns share a precondition: the people shipping had &lt;em&gt;domain knowledge&lt;/em&gt;. They were Venezuelan. They knew what a crisis actually looks like at street level. They had access to the same models any developer has. They didn't have institutional access. They didn't have FEMA's budget. They had Claude, Replit, and a WhatsApp number.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the inversion isn't really about the model
&lt;/h2&gt;

&lt;p&gt;Here is the uncomfortable bit. If Claude's API had been down that week, or Replit had a regional outage, or Anthropic had changed the model id on Tuesday, the Desaparecidos site might not have shipped in time. The 30,000 reports still would have piled up — in Telegram groups, in spreadsheets, in handwritten notes. AI gave the team a multiplier. The multiplier rides on infrastructure the team does not control.&lt;/p&gt;

&lt;p&gt;Most "AI saved the day" framing misses this. The model is the volatile layer. The prompts, the architecture, the schema for missing-person reports, the WhatsApp-onboarding flow — those are the durable layer. Two weeks after the crisis, the team will still need the schema. They will still need the lookup UX. They may not still need Opus.&lt;/p&gt;

&lt;p&gt;This is true in every domain where AI is showing up right now. The thing you ship in an afternoon with AI is fast. The thing that survives the model churn — the next pricing change, the next deprecation, the next id rotation — has to be built on a foundation that doesn't depend on which LLM you call this quarter.&lt;/p&gt;

&lt;h2&gt;
  
  
  The WhatsApp constraint is the real lesson
&lt;/h2&gt;

&lt;p&gt;Most coverage will lead with the LLM. The more interesting constraint is the WhatsApp fallback. Samuel Mariña's team built a WhatsApp assistant because "users could not load the site because their connection was too weak." The richest model in the world is useless if the user is on 2G with 200 kbps and a dying battery.&lt;/p&gt;

&lt;p&gt;That constraint shapes every architectural decision in crisis software:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The interface has to degrade gracefully from rich web → SMS → WhatsApp → voice.&lt;/li&gt;
&lt;li&gt;The state has to live somewhere the user doesn't have to authenticate to retrieve.&lt;/li&gt;
&lt;li&gt;The matching has to work even when half the database is corrupt or duplicated.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The teams that shipped understood this. The teams that would have built the same thing in six months at a UN agency probably would have shipped a WebGL dashboard and called it innovation.&lt;/p&gt;

&lt;p&gt;Here is roughly what the public-facing schema had to carry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The part that has to outlive the model.&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;MissingPersonReport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;                          &lt;span class="c1"&gt;// server-generated, opaque&lt;/span&gt;
  &lt;span class="na"&gt;reportedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;                  &lt;span class="c1"&gt;// unix ms&lt;/span&gt;
  &lt;span class="na"&gt;reporterContact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;             &lt;span class="c1"&gt;// WhatsApp number, never displayed publicly&lt;/span&gt;
  &lt;span class="nx"&gt;photoUrl&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;                   &lt;span class="c1"&gt;// optional, blurred in public view&lt;/span&gt;
  &lt;span class="na"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;ageRange&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;         &lt;span class="c1"&gt;// fuzzy on purpose — age is unreliable in panic&lt;/span&gt;
  &lt;span class="nx"&gt;lastSeenLocation&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;           &lt;span class="c1"&gt;// free text, geocoded client-side&lt;/span&gt;
  &lt;span class="nx"&gt;lastSeenAt&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
  &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;missing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;duplicate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Lookup by photo, by name, by location — all three paths.&lt;/span&gt;
&lt;span class="c1"&gt;// Match confidence drives the UI: high → "we think this is them",&lt;/span&gt;
&lt;span class="c1"&gt;// low → "possible matches" stack.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice what isn't in there: a model id, a prompt template, an embedding dimension, a vector index. The model is replaceable. The schema is not. Two years from now, when the next earthquake hits and the diaspora opens laptops again, the schema is what carries over.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this gets us
&lt;/h2&gt;

&lt;p&gt;Three things worth keeping from the Venezuela response:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Consumer AI is now a crisis-grade force multiplier.&lt;/strong&gt; Not because the models are magical, but because they're on every laptop and the latency to "production URL" is measured in hours, not months. The next disaster — and there will be one — will have diaspora coders shipping again. That is a feature of the world now.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The durable layer is the schema and the UX, not the model.&lt;/strong&gt; The team that ships a reusable missing-person schema this week can rebuild Desaparecidos in 90 minutes for the next earthquake in Turkey, Morocco, or Myanmar. The team that ships a reusable lookup interface can do the same for any crisis where people need to find people. The team that shipped nothing but a Claude prompt shipped nothing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Constraints shape architecture more than capabilities.&lt;/strong&gt; WhatsApp-first, no-registration, photo-upload-only. The interface is dictated by what the user has, not what the model can do. That is the opposite of how most AI demos get built — and the reason this one actually worked.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;[[CONCEPT: a thin layer of LLM calls sitting on top of a thick layer of schema, components, and durable interfaces — the LLM is replaceable; the layer underneath is not]]&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that doesn't churn
&lt;/h2&gt;

&lt;p&gt;When you ship something in three hours on Claude, you ship on borrowed time. The model id changes. The pricing changes. The rate limits change. The team maintaining the site in two years will be maintaining crisis infrastructure that was written against a model snapshot.&lt;/p&gt;

&lt;p&gt;The pieces that survive are the ones that don't depend on the model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The component layer that renders the same UI on web and mobile, so the lookup works on whatever the user happens to have in their hand.&lt;/li&gt;
&lt;li&gt;The data layer that holds the schema for missing-person reports, donation routing, or supply matching — versioned, portable, and readable without an LLM.&lt;/li&gt;
&lt;li&gt;The auth layer that lets someone with a phone number and a face be findable without an account, an email, or an OAuth flow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are the boring parts. They are also the parts that don't change when the model does.&lt;/p&gt;

&lt;p&gt;Use Claude. Use Replit. Ship in three hours. And build the parts that don't change on something that doesn't change — a cross-platform component layer that ships the same UX to web, iOS, and Android from one API, a schema that outlives the snapshot, and an auth flow that works on the worst connection the user has. The model will rotate. The components, the schema, and the delivery surface stay.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>productivity</category>
      <category>programming</category>
    </item>
    <item>
      <title>Bridge Networking in Linux</title>
      <dc:creator>Aviral Srivastava</dc:creator>
      <pubDate>Thu, 16 Jul 2026 09:06:50 +0000</pubDate>
      <link>https://dev.to/godofgeeks/bridge-networking-in-linux-1ko5</link>
      <guid>https://dev.to/godofgeeks/bridge-networking-in-linux-1ko5</guid>
      <description>&lt;h2&gt;
  
  
  Bridging the Gap: Your Ultimate Guide to Linux Network Bridges
&lt;/h2&gt;

&lt;p&gt;Ever found yourself tangled in the wires of your home network, wishing for a simpler, more elegant way to connect devices? Or perhaps you're a budding system administrator, diving headfirst into the magical world of virtual machines and containers, and you've stumbled upon this mysterious "bridge networking"? Fear not, intrepid explorer of the digital realm! Today, we're going to demystify the concept of network bridges in Linux. Think of it as building a sophisticated, invisible ethernet switch within your operating system, allowing your virtual and physical devices to play nicely together.&lt;/p&gt;

&lt;p&gt;We'll embark on a journey, uncovering what bridges are, why you'd want to use them, how to set them up, and some of the nitty-gritty details that make them so powerful. So, grab a coffee (or your beverage of choice), settle in, and let's bridge the gap to understanding!&lt;/p&gt;

&lt;h3&gt;
  
  
  Introduction: What Exactly IS a Network Bridge?
&lt;/h3&gt;

&lt;p&gt;Imagine you have two separate networks. Maybe one is your physical home network (your router, your laptops, your smart TV), and the other is a virtual network running inside your computer for a virtual machine (VM). Normally, these two worlds are isolated. Your VM can't directly see your TV, and your TV can't ping your VM.&lt;/p&gt;

&lt;p&gt;A network bridge in Linux acts like a virtual Ethernet switch. It connects multiple network interfaces (both physical and virtual) into a single logical network segment. This means that devices attached to different interfaces that are part of the same bridge can communicate with each other as if they were plugged into the same physical switch.&lt;/p&gt;

&lt;p&gt;Think of it this way: Instead of plugging your VM directly into your computer's network card and then trying to route traffic between them, you create a bridge. You then "plug" both your physical network interface (e.g., &lt;code&gt;eth0&lt;/code&gt; or &lt;code&gt;wlan0&lt;/code&gt;) and your VM's virtual network interface (e.g., &lt;code&gt;veth0&lt;/code&gt;) into this bridge. Now, the bridge handles all the traffic forwarding, making it appear as though all these devices are on the same network, talking directly to each other.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites: What You'll Need to Get Started
&lt;/h3&gt;

&lt;p&gt;Before we dive into the "how-to," let's make sure you're prepared.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;A Linux System:&lt;/strong&gt; Obviously! This guide is primarily for Linux distributions like Ubuntu, Debian, CentOS, Fedora, etc.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Root Privileges:&lt;/strong&gt; You'll need administrator (root) access to install software and configure network interfaces. Be careful when working with root privileges – a typo can have unintended consequences!&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Basic Networking Knowledge:&lt;/strong&gt; Understanding IP addresses, subnets, gateways, and network interfaces will be helpful.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;bridge-utils&lt;/code&gt; Package:&lt;/strong&gt; This is the essential toolkit for managing bridges in Linux. Most distributions have it available in their repositories. You can usually install it with your package manager.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Debian/Ubuntu:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;bridge-utils
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;CentOS/Fedora/RHEL:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;yum &lt;span class="nb"&gt;install &lt;/span&gt;bridge-utils  &lt;span class="c"&gt;# or sudo dnf install bridge-utils&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Virtualization Software (Optional but common):&lt;/strong&gt; If you're planning to use bridges for VMs, you'll need virtualization software like KVM/QEMU, VirtualBox, or VMware. For containers, you'll likely be using Docker or LXC.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Advantages: Why Bother with Bridges?
&lt;/h3&gt;

&lt;p&gt;So, why would you go through the trouble of setting up a bridge? The benefits are numerous, especially in modern IT environments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Seamless Virtual Machine Networking:&lt;/strong&gt; This is the most common use case. Bridges allow VMs to appear as if they are directly connected to your physical network, getting their own IP addresses from your router. This makes managing and accessing VMs much easier.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Container Networking:&lt;/strong&gt; Similar to VMs, bridges are fundamental for container networking, enabling containers to communicate with each other and the outside world.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Network Segmentation and Isolation:&lt;/strong&gt; You can create multiple bridges, each segmenting different sets of devices. This can enhance security and manageability.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Simplified Network Topologies:&lt;/strong&gt; Instead of complex routing rules, a bridge can flatten your network, making it appear as a single subnet.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Traffic Monitoring and Filtering:&lt;/strong&gt; By intercepting traffic at the bridge level, you can implement advanced monitoring and filtering solutions.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Connecting Heterogeneous Networks:&lt;/strong&gt; Bridges can connect different types of network interfaces, such as wired Ethernet and wireless interfaces (though this can be more complex and may require specific configurations).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Disadvantages: The Flip Side of the Coin
&lt;/h3&gt;

&lt;p&gt;While bridges are incredibly useful, they're not a magic bullet. Here are a few drawbacks to consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Performance Overhead:&lt;/strong&gt; Introducing a bridge can add a small amount of latency and CPU overhead compared to direct physical connections, as the operating system needs to process the traffic through the bridging software. For most everyday use cases, this is negligible.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Broadcast Domain Expansion:&lt;/strong&gt; Bridges operate at Layer 2 (Data Link Layer). By default, they forward broadcast traffic. If you have a very large or poorly managed network, excessive broadcasts can lead to performance issues.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Complexity for Beginners:&lt;/strong&gt; While we're aiming to demystify it, setting up and troubleshooting network bridges can be a bit daunting for those new to networking.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;MAC Address Handling:&lt;/strong&gt; Each interface attached to a bridge gets its own MAC address. The bridge itself also has a MAC address. This can sometimes lead to confusion if not managed properly.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Wireless Limitations:&lt;/strong&gt; Bridging a wireless interface (&lt;code&gt;wlanX&lt;/code&gt;) to a bridge can be tricky. Most Wi-Fi drivers operate in a managed mode where the access point handles bridging. You might need to use monitor mode or specific configurations for certain scenarios, which can be less reliable.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Features and Functionality: Under the Hood
&lt;/h3&gt;

&lt;p&gt;Let's peek under the hood and see what makes bridges tick.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Layer 2 Operation:&lt;/strong&gt; Bridges operate at the Data Link Layer (Layer 2) of the OSI model. They forward frames based on MAC addresses, not IP addresses. This is a crucial distinction from routers, which operate at Layer 3 (Network Layer) and forward packets based on IP addresses.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;MAC Address Learning:&lt;/strong&gt; A key feature of bridges is their ability to learn the MAC addresses of devices connected to their ports. They build a forwarding table (often called a MAC address table or CAM table) that maps MAC addresses to the bridge port they are connected to. This allows them to efficiently forward traffic only to the intended destination port, reducing unnecessary network traffic.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Forwarding and Flooding:&lt;/strong&gt; When a frame arrives at a bridge port, the bridge checks its forwarding table.

&lt;ul&gt;
&lt;li&gt;  If the destination MAC address is found in the table, the frame is forwarded only to the corresponding port.&lt;/li&gt;
&lt;li&gt;  If the destination MAC address is not found, or if it's a broadcast/multicast address, the frame is "flooded" out to all other ports (except the one it came in on).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Spanning Tree Protocol (STP):&lt;/strong&gt; To prevent network loops (which can cripple a network), bridges typically implement STP. STP dynamically disables redundant paths in a network, ensuring there's only one active path between any two devices. While this is a fundamental part of bridge operation, it's often handled automatically by the &lt;code&gt;bridge-utils&lt;/code&gt; tools.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;VLAN Tagging:&lt;/strong&gt; Bridges can be configured to handle VLAN tags, allowing you to segment your network logically even with a single physical infrastructure.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Setting Up a Network Bridge: Hands-On!
&lt;/h3&gt;

&lt;p&gt;Now for the fun part: getting your hands dirty! We'll cover a common scenario: bridging your physical Ethernet interface to a virtual bridge for VMs.&lt;/p&gt;

&lt;h4&gt;
  
  
  Scenario: Bridging &lt;code&gt;eth0&lt;/code&gt; to &lt;code&gt;br0&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Let's assume your physical network interface is named &lt;code&gt;eth0&lt;/code&gt;. We'll create a bridge named &lt;code&gt;br0&lt;/code&gt; and add &lt;code&gt;eth0&lt;/code&gt; to it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Install &lt;code&gt;bridge-utils&lt;/code&gt; (if you haven't already).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Create the Bridge Interface:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We'll use the &lt;code&gt;brctl&lt;/code&gt; command-line utility.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Create the bridge:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;brctl addbr br0
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;This creates a new virtual bridge interface named &lt;code&gt;br0&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Add your physical interface to the bridge:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;brctl addif br0 eth0
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;This attaches your physical &lt;code&gt;eth0&lt;/code&gt; interface to the &lt;code&gt;br0&lt;/code&gt; bridge.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Bring up the bridge and the physical interface:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ip &lt;span class="nb"&gt;link set &lt;/span&gt;br0 up
&lt;span class="nb"&gt;sudo &lt;/span&gt;ip &lt;span class="nb"&gt;link set &lt;/span&gt;eth0 up
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Configure IP Addressing:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You have two main options for IP addressing your bridge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Option A: DHCP (Recommended for most home/office networks):&lt;/strong&gt;&lt;br&gt;
If your physical interface &lt;code&gt;eth0&lt;/code&gt; was configured to get an IP address via DHCP, you can now configure &lt;code&gt;br0&lt;/code&gt; to do the same. You might need to restart your DHCP client or reconfigure your network manager.&lt;/p&gt;

&lt;p&gt;For systems using &lt;code&gt;systemd-networkd&lt;/code&gt; or manual configuration, you might remove the IP configuration from &lt;code&gt;eth0&lt;/code&gt; and assign it to &lt;code&gt;br0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;ensure &lt;code&gt;eth0&lt;/code&gt; has no IP address configured on it directly&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ip addr flush dev eth0
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Then, tell &lt;code&gt;br0&lt;/code&gt; to get an IP via DHCP:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dhclient br0 &lt;span class="c"&gt;# or use your system's specific DHCP client command&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Option B: Static IP Address:&lt;/strong&gt;&lt;br&gt;
If you prefer a static IP address for your bridge, you'll assign it directly to &lt;code&gt;br0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;ensure &lt;code&gt;eth0&lt;/code&gt; has no IP address configured on it directly&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ip addr flush dev eth0
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Then, assign a static IP to &lt;code&gt;br0&lt;/code&gt; (replace with your desired IP and subnet mask):&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ip addr add 192.168.1.100/24 dev br0
&lt;span class="nb"&gt;sudo &lt;/span&gt;ip route add default via 192.168.1.1 dev br0 &lt;span class="c"&gt;# Set your gateway&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. Bringing it all together (Making it Persistent):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The commands above are temporary and will be lost on reboot. For persistence, you'll need to configure your network manager. The exact method varies depending on your Linux distribution and its network management tools (e.g., &lt;code&gt;netplan&lt;/code&gt;, &lt;code&gt;NetworkManager&lt;/code&gt;, &lt;code&gt;systemd-networkd&lt;/code&gt;, or traditional &lt;code&gt;/etc/network/interfaces&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example using &lt;code&gt;netplan&lt;/code&gt; (Ubuntu 18.04+):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Edit your &lt;code&gt;.yaml&lt;/code&gt; file in &lt;code&gt;/etc/netplan/&lt;/code&gt;. For example, &lt;code&gt;/etc/netplan/01-netcfg.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;network&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;renderer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;networkd&lt;/span&gt;
  &lt;span class="na"&gt;ethernets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;eth0&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;dhcp4&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;no&lt;/span&gt;
      &lt;span class="na"&gt;dhcp6&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;no&lt;/span&gt;
  &lt;span class="na"&gt;bridges&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;br0&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;interfaces&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;eth0&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;dhcp4&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt; &lt;span class="c1"&gt;# or your static configuration&lt;/span&gt;
      &lt;span class="c1"&gt;# For static IP:&lt;/span&gt;
      &lt;span class="c1"&gt;# addresses: [192.168.1.100/24]&lt;/span&gt;
      &lt;span class="c1"&gt;# gateway4: 192.168.1.1&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;stp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;forward-delay&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then apply the changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;netplan apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Example using &lt;code&gt;/etc/network/interfaces&lt;/code&gt; (Debian/Ubuntu older versions):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Edit &lt;code&gt;/etc/network/interfaces&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;br0&lt;/span&gt;
&lt;span class="n"&gt;iface&lt;/span&gt; &lt;span class="n"&gt;br0&lt;/span&gt; &lt;span class="n"&gt;inet&lt;/span&gt; &lt;span class="n"&gt;dhcp&lt;/span&gt;  &lt;span class="c"&gt;# or static
&lt;/span&gt;    &lt;span class="n"&gt;bridge_ports&lt;/span&gt; &lt;span class="n"&gt;eth0&lt;/span&gt;
    &lt;span class="n"&gt;bridge_stp&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt;
    &lt;span class="n"&gt;bridge_fd&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;

&lt;span class="n"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;eth0&lt;/span&gt;
&lt;span class="n"&gt;iface&lt;/span&gt; &lt;span class="n"&gt;eth0&lt;/span&gt; &lt;span class="n"&gt;inet&lt;/span&gt; &lt;span class="n"&gt;manual&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Verification:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can check the status of your bridge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;List bridges and their ports:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;brctl show
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;You should see &lt;code&gt;br0&lt;/code&gt; with &lt;code&gt;eth0&lt;/code&gt; listed under its &lt;code&gt;interfaces&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Check IP addresses:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ip addr show br0
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;You should see the IP address assigned to &lt;code&gt;br0&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ping your gateway and other devices on your network:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ping 192.168.1.1 &lt;span class="c"&gt;# Replace with your gateway IP&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Bridging for VMs (KVM/QEMU Example)
&lt;/h4&gt;

&lt;p&gt;Once you have your bridge set up, integrating it with your VM management is straightforward. When creating a new VM or configuring an existing one, you'll select the network device type to be a "bridged" adapter and specify your &lt;code&gt;br0&lt;/code&gt; interface.&lt;/p&gt;

&lt;p&gt;For example, using &lt;code&gt;virt-manager&lt;/code&gt; (the graphical tool for KVM/QEMU), you'd choose "Bridged device" and enter &lt;code&gt;br0&lt;/code&gt; in the "Device name" field. This will make your VM appear on your physical network just like any other device.&lt;/p&gt;

&lt;h4&gt;
  
  
  Bridging with Containers (Docker Example)
&lt;/h4&gt;

&lt;p&gt;Docker often creates its own bridge network by default (e.g., &lt;code&gt;docker0&lt;/code&gt;). However, you can also configure Docker to use your custom bridge. This involves setting up your bridge as described above and then telling Docker to use it, or more commonly, attaching containers to user-defined bridges.&lt;/p&gt;

&lt;p&gt;You can create a custom bridge network in Docker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker network create &lt;span class="nt"&gt;-d&lt;/span&gt; bridge my_custom_bridge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, run your containers on this network:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--network&lt;/span&gt; my_custom_bridge my_image
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Advanced Topics and Tips
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Multiple Interfaces on a Bridge:&lt;/strong&gt; You can add multiple physical or virtual interfaces to a single bridge. This is useful for aggregating connections or connecting different subnets if your bridge is acting as a Layer 2 device between them.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;VLANs on Bridges:&lt;/strong&gt; For more complex setups, you can configure VLAN filtering on your bridges. This allows you to segregate traffic within the bridge based on VLAN tags.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Firewalling on Bridges:&lt;/strong&gt; You can use &lt;code&gt;iptables&lt;/code&gt; or &lt;code&gt;nftables&lt;/code&gt; to firewall traffic passing through your bridge. This is often done by using the &lt;code&gt;physin&lt;/code&gt;, &lt;code&gt;physout&lt;/code&gt;, &lt;code&gt;brdgin&lt;/code&gt;, and &lt;code&gt;brdge&lt;/code&gt; chains.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;MAC Address Filtering:&lt;/strong&gt; You can configure MAC address filtering on bridge ports to control which devices can connect.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Network Bonding (LAG):&lt;/strong&gt; While not strictly a bridge, network bonding can be combined with bridges. You can create a bonded interface (e.g., &lt;code&gt;bond0&lt;/code&gt;) and then add that bonded interface to your bridge. This provides increased bandwidth and redundancy.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Conclusion: Embracing the Power of the Bridge
&lt;/h3&gt;

&lt;p&gt;Network bridging in Linux is a powerful and versatile tool that unlocks a world of possibilities, especially when it comes to virtualisation, containerisation, and advanced network configurations. While it might seem a bit intimidating at first, understanding the core concept of a virtual switch and how it forwards traffic based on MAC addresses demystifies the process.&lt;/p&gt;

&lt;p&gt;By following the steps outlined above, you can confidently set up your own network bridges, making your VMs and containers feel like first-class citizens on your network. Experiment, explore, and don't be afraid to tweak the settings. The Linux networking stack is remarkably flexible, and bridges are a key component in harnessing that power.&lt;/p&gt;

&lt;p&gt;So go forth, bridge the gaps in your network, and enjoy the seamless connectivity! Happy bridging!&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>linux</category>
      <category>networking</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Designing AI Voice Agent Workflows for Electricians: Urgent Calls, Quotes, and Handoffs</title>
      <dc:creator>VoiceFleet</dc:creator>
      <pubDate>Thu, 16 Jul 2026 09:04:45 +0000</pubDate>
      <link>https://dev.to/voicefleet/designing-ai-voice-agent-workflows-for-electricians-urgent-calls-quotes-and-handoffs-3llb</link>
      <guid>https://dev.to/voicefleet/designing-ai-voice-agent-workflows-for-electricians-urgent-calls-quotes-and-handoffs-3llb</guid>
      <description>&lt;p&gt;A lot of AI phone demos look impressive until you put them in front of a real trade business.&lt;/p&gt;

&lt;p&gt;Electricians are a good stress test. The caller might need an urgent callback, a quote, a booking, or a simple status update. The AI must be useful without pretending to diagnose electrical problems or giving unsafe repair advice.&lt;/p&gt;

&lt;p&gt;Here's the workflow pattern we use when designing an AI voice agent for an electrician use case.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Separate conversation from decisioning
&lt;/h2&gt;

&lt;p&gt;The voice layer should not decide everything inline.&lt;/p&gt;

&lt;p&gt;A cleaner architecture is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Inbound call
  → speech-to-text
  → conversation state
  → intent + urgency classifier
  → workflow policy
  → summary / SMS / CRM / human handoff
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The conversation model gathers context. A separate policy layer decides what happens next.&lt;/p&gt;

&lt;p&gt;That separation matters because trade calls need predictable rules. You do not want a free-form model deciding whether a sparking switchboard is "probably fine". You want a deterministic handoff rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Model the call as a structured job request
&lt;/h2&gt;

&lt;p&gt;For electricians, the useful output is not a transcript. It is a job object.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"caller_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"phone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"suburb_or_area"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"issue_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"power_outage | switchboard | lighting | appliance | quote | other"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"urgency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"emergency | today | scheduled | unknown"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"access_notes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"preferred_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"handoff_required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"handoff_reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"safety_risk"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That object can go to a CRM, field-service tool, SMS, email, or a simple callback queue. The point is to make the next human action obvious.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Treat safety as routing, not advice
&lt;/h2&gt;

&lt;p&gt;The agent should avoid repair instructions. It can ask clarifying questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is there smoke, burning smell, sparking, or exposed wiring?&lt;/li&gt;
&lt;li&gt;Is power out in one room or the whole property?&lt;/li&gt;
&lt;li&gt;Is anyone in immediate danger?&lt;/li&gt;
&lt;li&gt;What suburb is the job in?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But once the call crosses a safety threshold, the flow should stop trying to resolve and start routing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if smoke_or_sparking or immediate_danger:
  tell caller to contact local emergency services if needed
  capture callback details
  notify electrician immediately
else if no_power or urgent_business_disruption:
  same-day callback queue
else:
  quote / booking workflow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where AI receptionist design gets less glamorous but more valuable: fewer clever answers, better escalation.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Optimise for mobile handoff
&lt;/h2&gt;

&lt;p&gt;Many trade businesses are owner-operated. The electrician is often on-site, not sitting in a dashboard.&lt;/p&gt;

&lt;p&gt;So the handoff format matters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;URGENT ELECTRICAL CALL
Caller: Jane, 04xx xxx xxx
Area: Gold Coast
Issue: sparking outlet in kitchen
Access: home, caller is present
AI action: advised caller to avoid touching the outlet and wait for callback
Next step: call back now
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A short SMS or WhatsApp-style summary can be more useful than a full CRM record.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Measure the boring things
&lt;/h2&gt;

&lt;p&gt;For this kind of workflow, I would track:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;percentage of calls with a complete job object&lt;/li&gt;
&lt;li&gt;time from call end to human notification&lt;/li&gt;
&lt;li&gt;handoff reason distribution&lt;/li&gt;
&lt;li&gt;transcript confidence on address and phone number&lt;/li&gt;
&lt;li&gt;caller drop-off before contact details are captured&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those metrics tell you whether the system is operationally useful, not just whether the demo sounded natural.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;The hard part of AI voice agents is not making them talk. It is making them behave like a reliable front desk for a specific business.&lt;/p&gt;

&lt;p&gt;For electricians, that means structured intake, safety-aware routing, and fast human handoff. The model should sound natural, but the workflow should be boringly deterministic.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>startup</category>
      <category>voiceai</category>
    </item>
    <item>
      <title>Choosing an LCD Panel for Embedded Systems: IPS vs TN Technology Explained</title>
      <dc:creator>Alan</dc:creator>
      <pubDate>Thu, 16 Jul 2026 09:04:38 +0000</pubDate>
      <link>https://dev.to/alan12/choosing-an-lcd-panel-for-embedded-systems-ips-vs-tn-technology-explained-545b</link>
      <guid>https://dev.to/alan12/choosing-an-lcd-panel-for-embedded-systems-ips-vs-tn-technology-explained-545b</guid>
      <description>&lt;p&gt;When designing an embedded device, selecting the right LCD panel is often more complicated than simply choosing a display size or resolution. The panel technology itself can directly affect user experience, power consumption, system reliability, and even the overall product design.&lt;/p&gt;

&lt;p&gt;For engineers working on industrial equipment, IoT devices, automation systems, or embedded interfaces, two LCD technologies frequently appear during the selection process: IPS (In-Plane Switching) and TN (Twisted Nematic).&lt;/p&gt;

&lt;p&gt;Both technologies have been widely used for years, but they are designed with different priorities in mind. TN panels focus on speed and cost efficiency, while IPS panels focus more on viewing quality and image consistency.&lt;/p&gt;

&lt;p&gt;The right choice depends on the requirements of the final application rather than simply choosing the technology with better specifications.&lt;/p&gt;

&lt;p&gt;Understanding TN LCD Panels&lt;/p&gt;

&lt;p&gt;TN (Twisted Nematic) is one of the earliest LCD technologies and has been widely adopted because of its relatively simple structure and manufacturing advantages.&lt;/p&gt;

&lt;p&gt;For many years, TN panels have been used in applications where fast response time and cost control are important.&lt;/p&gt;

&lt;p&gt;Some common advantages of TN LCD panels include:&lt;/p&gt;

&lt;p&gt;Fast response speed&lt;br&gt;
Lower manufacturing cost&lt;br&gt;
Lower power consumption&lt;br&gt;
Wide market availability&lt;/p&gt;

&lt;p&gt;These characteristics make TN panels suitable for applications where image quality requirements are moderate but efficiency and affordability are important.&lt;/p&gt;

&lt;p&gt;For example, devices that mainly display:&lt;/p&gt;

&lt;p&gt;numbers&lt;br&gt;
status indicators&lt;br&gt;
simple interfaces&lt;br&gt;
basic control information&lt;/p&gt;

&lt;p&gt;may not require the advanced viewing performance of an IPS panel.&lt;/p&gt;

&lt;p&gt;However, TN technology also has some limitations.&lt;/p&gt;

&lt;p&gt;The most noticeable limitation is viewing angle performance.&lt;/p&gt;

&lt;p&gt;When users view a TN display from different directions, color changes and contrast reduction may become more visible. For applications where only one user views the screen from a fixed position, this may not create a significant problem.&lt;/p&gt;

&lt;p&gt;But for industrial equipment, public terminals, or devices viewed from different angles, viewing consistency becomes much more important.&lt;/p&gt;

&lt;p&gt;Understanding IPS LCD Panels&lt;/p&gt;

&lt;p&gt;IPS (In-Plane Switching) technology was developed to overcome some of the limitations of traditional LCD designs.&lt;/p&gt;

&lt;p&gt;The main advantage of IPS panels is improved image stability when viewed from different angles.&lt;/p&gt;

&lt;p&gt;Compared with TN panels, IPS displays typically provide:&lt;/p&gt;

&lt;p&gt;Wider viewing angles&lt;br&gt;
Better color consistency&lt;br&gt;
More stable image performance&lt;br&gt;
Improved visual quality&lt;/p&gt;

&lt;p&gt;These characteristics make IPS panels popular in applications where users need to clearly view information from different positions.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;p&gt;industrial control interfaces&lt;br&gt;
embedded HMI systems&lt;br&gt;
monitoring equipment&lt;br&gt;
interactive terminals&lt;br&gt;
professional displays&lt;/p&gt;

&lt;p&gt;For industrial environments, a display is often not viewed from a single fixed angle. Operators may stand beside equipment, check information while moving around, or share the screen with other users.&lt;/p&gt;

&lt;p&gt;In these situations, the wider viewing angle of IPS technology can provide a better user experience.&lt;/p&gt;

&lt;p&gt;However, IPS panels are not always the perfect choice.&lt;/p&gt;

&lt;p&gt;Depending on specifications, IPS displays may involve:&lt;/p&gt;

&lt;p&gt;higher cost&lt;br&gt;
increased power requirements&lt;br&gt;
different response characteristics&lt;/p&gt;

&lt;p&gt;Therefore, engineers still need to evaluate the complete system requirements before making a decision.&lt;/p&gt;

&lt;p&gt;IPS vs TN: Key Differences for Embedded Applications&lt;/p&gt;

&lt;p&gt;When comparing IPS and TN panels for embedded systems, several factors should be considered.&lt;/p&gt;

&lt;p&gt;Viewing Angle&lt;/p&gt;

&lt;p&gt;Viewing angle is one of the biggest differences between these two technologies.&lt;/p&gt;

&lt;p&gt;TN panels generally perform well when viewed directly from the front, but image quality can change when viewed from other directions.&lt;/p&gt;

&lt;p&gt;IPS panels maintain more consistent brightness and color performance across wider viewing angles.&lt;/p&gt;

&lt;p&gt;For applications such as industrial panels, control interfaces, and public-facing devices, IPS is often preferred because multiple viewing positions are common.&lt;/p&gt;

&lt;p&gt;Response Time&lt;/p&gt;

&lt;p&gt;TN panels have traditionally had an advantage in response speed.&lt;/p&gt;

&lt;p&gt;This made them popular in applications where fast image changes were important.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;p&gt;gaming displays&lt;br&gt;
high-speed visual feedback systems&lt;br&gt;
applications requiring rapid updates&lt;/p&gt;

&lt;p&gt;Modern IPS technology has improved significantly, and for many embedded applications, the response time difference is no longer the most important selection factor.&lt;/p&gt;

&lt;p&gt;Power Consumption&lt;/p&gt;

&lt;p&gt;Power consumption is another important consideration, especially for battery-powered embedded systems.&lt;/p&gt;

&lt;p&gt;TN panels may have advantages in some low-power applications because of their simpler structure.&lt;/p&gt;

&lt;p&gt;However, the total power consumption of a display system also depends on:&lt;/p&gt;

&lt;p&gt;backlight design&lt;br&gt;
brightness requirements&lt;br&gt;
controller electronics&lt;br&gt;
operating conditions&lt;/p&gt;

&lt;p&gt;The panel type alone does not determine the final power usage.&lt;/p&gt;

&lt;p&gt;Cost Considerations&lt;/p&gt;

&lt;p&gt;Cost is often a major factor during product development.&lt;/p&gt;

&lt;p&gt;TN panels usually have advantages in cost-sensitive projects because they are mature and widely manufactured.&lt;/p&gt;

&lt;p&gt;IPS panels may have a higher initial cost but can provide better usability and longer-term value when display quality is important.&lt;/p&gt;

&lt;p&gt;The decision should consider the entire product lifecycle rather than only the initial component price.&lt;/p&gt;

&lt;p&gt;Other Factors Beyond Panel Technology&lt;/p&gt;

&lt;p&gt;Choosing between IPS and TN is only one part of selecting an LCD for an embedded product.&lt;/p&gt;

&lt;p&gt;Engineers should also consider other technical factors.&lt;/p&gt;

&lt;p&gt;Interface Compatibility&lt;/p&gt;

&lt;p&gt;The LCD interface must match the system architecture.&lt;/p&gt;

&lt;p&gt;Common embedded display interfaces include:&lt;/p&gt;

&lt;p&gt;LVDS&lt;br&gt;
MIPI DSI&lt;br&gt;
RGB&lt;br&gt;
eDP&lt;br&gt;
SPI&lt;/p&gt;

&lt;p&gt;A panel with excellent image quality will not work properly if the interface is incompatible with the processor or display controller.&lt;/p&gt;

&lt;p&gt;Before selecting a display, developers should verify:&lt;/p&gt;

&lt;p&gt;interface type&lt;br&gt;
connector specification&lt;br&gt;
signal requirements&lt;br&gt;
driver compatibility&lt;br&gt;
Operating Environment&lt;/p&gt;

&lt;p&gt;Industrial and embedded applications often operate under conditions that are very different from consumer electronics.&lt;/p&gt;

&lt;p&gt;Potential challenges include:&lt;/p&gt;

&lt;p&gt;temperature changes&lt;br&gt;
dust exposure&lt;br&gt;
vibration&lt;br&gt;
continuous operation&lt;br&gt;
outdoor lighting conditions&lt;/p&gt;

&lt;p&gt;A display selected for industrial use should match the actual working environment.&lt;/p&gt;

&lt;p&gt;For example, a panel used inside a factory machine may require different characteristics compared with one installed in an outdoor terminal.&lt;/p&gt;

&lt;p&gt;Mechanical Integration&lt;/p&gt;

&lt;p&gt;Embedded displays are often installed inside customized equipment rather than standard monitor housings.&lt;/p&gt;

&lt;p&gt;Mechanical considerations may include:&lt;/p&gt;

&lt;p&gt;display thickness&lt;br&gt;
mounting structure&lt;br&gt;
cable location&lt;br&gt;
available installation space&lt;br&gt;
touch panel integration&lt;/p&gt;

&lt;p&gt;The physical design of the display module can have a significant impact on the overall product development process.&lt;/p&gt;

&lt;p&gt;Choosing the Right LCD Panel for Your Project&lt;/p&gt;

&lt;p&gt;There is no single LCD technology that is suitable for every application.&lt;/p&gt;

&lt;p&gt;A TN panel may be a good option when the project requires:&lt;/p&gt;

&lt;p&gt;lower cost&lt;br&gt;
simple information display&lt;br&gt;
fast response&lt;br&gt;
fixed viewing position&lt;/p&gt;

&lt;p&gt;An IPS panel may be more suitable when the project requires:&lt;/p&gt;

&lt;p&gt;better viewing angles&lt;br&gt;
stable image quality&lt;br&gt;
improved user interaction&lt;br&gt;
professional visual performance&lt;/p&gt;

&lt;p&gt;For engineers designing embedded systems, the best approach is to define the application requirements first and then select the display technology that matches those requirements.&lt;/p&gt;

&lt;p&gt;A detailed comparison of IPS and TN technologies for industrial applications can provide additional guidance when evaluating different LCD solutions:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.aptusdisplay.com/info-detail/ips-vs-tn-which-lcd-panel-is-better-for-industrial-applications" rel="noopener noreferrer"&gt;IPS vs TN: Which LCD Panel Is Better for Industrial Applications&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Final Thoughts&lt;/p&gt;

&lt;p&gt;LCD panel selection is an important design decision in embedded product development.&lt;/p&gt;

&lt;p&gt;While TN and IPS technologies each have their own advantages, the better choice depends on the specific needs of the application.&lt;/p&gt;

&lt;p&gt;Factors such as viewing conditions, power requirements, interface compatibility, environmental conditions, and mechanical integration should all be evaluated together.&lt;/p&gt;

&lt;p&gt;By considering the complete system instead of focusing on one specification, engineers can select a display solution that provides reliable performance throughout the product lifecycle.&lt;/p&gt;

</description>
      <category>embedded</category>
      <category>hardware</category>
    </item>
    <item>
      <title>The Liquid you lint isn't the Liquid Shopify runs</title>
      <dc:creator>Robin Dhiman</dc:creator>
      <pubDate>Thu, 16 Jul 2026 09:03:12 +0000</pubDate>
      <link>https://dev.to/iamrobindhiman/the-liquid-you-lint-isnt-the-liquid-shopify-runs-5bhp</link>
      <guid>https://dev.to/iamrobindhiman/the-liquid-you-lint-isnt-the-liquid-shopify-runs-5bhp</guid>
      <description>&lt;p&gt;&lt;code&gt;shopify theme check&lt;/code&gt; passed clean. The validator I run before every commit passed too. Then I opened the page on the dev store, and a size chart that builds its rows from a metafield rendered as one long, mangled line.&lt;/p&gt;

&lt;p&gt;The Liquid was correct. The tool that checked it wasn't running the same Liquid that Shopify runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two engines, one language
&lt;/h2&gt;

&lt;p&gt;Shopify's storefront renders Liquid with a Ruby engine, Shopify's own implementation, the one that has run the platform for years. Almost nothing you run locally uses it. &lt;code&gt;shopify theme check&lt;/code&gt;, the Shopify MCP validator, and nearly every Node-based Liquid simulator run &lt;strong&gt;LiquidJS&lt;/strong&gt;, a separate JavaScript reimplementation of the language.&lt;/p&gt;

&lt;p&gt;So you have two implementations of one templating language. They agree on the overwhelming majority of what you write. They do not agree on all of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where they split
&lt;/h2&gt;

&lt;p&gt;The gap I keep hitting is whitespace control. The trimming markers &lt;code&gt;{%-&lt;/code&gt; and &lt;code&gt;-%}&lt;/code&gt; strip surrounding whitespace, and the two engines don't always strip it identically. A few other rendering semantics differ too.&lt;/p&gt;

&lt;p&gt;For what a linter is actually for, LiquidJS is faithful: syntax, undefined objects, unknown filters and tags, schema shape. A green run tells you the template parses and references things that exist. It does not promise byte-for-byte identical output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a few bytes of whitespace become a real bug
&lt;/h2&gt;

&lt;p&gt;Most of the time you never notice, because HTML collapses runs of whitespace and the page looks fine either way. The exception is any code that treats rendered whitespace as data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight liquid"&gt;&lt;code&gt;&lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;rows&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;metafields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;custom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;size_chart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;row_delimiter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
&lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;row&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;rows&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
  &amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;row&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;
&lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;endfor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Split a rendered string on a delimiter and the exact whitespace decides where each field begins and ends. The same holds for a &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; block, an inline SVG &lt;code&gt;path&lt;/code&gt; you assemble in Liquid, or a JSON blob you build and hand to JavaScript. In all of those, an extra or missing newline isn't cosmetic. It changes the parse. That's how a table that's correct in my local preview shreds on the live storefront, or the reverse.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use the linter for what it catches
&lt;/h2&gt;

&lt;p&gt;I still run theme check on every change, and you should. It catches the things it's built to catch: malformed syntax, undefined objects, unknown filters, schema errors. And, increasingly useful, it flags filters an AI assistant hallucinated into a template that don't exist. That's real value, and it's fast.&lt;/p&gt;

&lt;p&gt;What it can't vouch for is whitespace-exact output. So the rule I follow is simple. Lint locally. When whitespace is load-bearing, render it on live Shopify, a dev store or a theme preview, before you trust it. Not the Node simulator. The engine that actually serves customers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern underneath
&lt;/h2&gt;

&lt;p&gt;This isn't really about Liquid. LiquidJS standing in for Ruby Liquid is one instance of a trap every engineer meets: the local stand-in that isn't the runtime. A mock that isn't the live API. SQLite in dev and Postgres in production. A cloud emulator on your laptop. Each one is close enough to be useful and different enough to lie to you at exactly the wrong moment.&lt;/p&gt;

&lt;p&gt;The move isn't to distrust the tool. It's to know precisely which class of bug it can catch and which it can't, then send the rest to the real thing. For Liquid: lint in Node, render on Shopify.&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>liquid</category>
      <category>frontend</category>
    </item>
  </channel>
</rss>
