<?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/feed"/>
    <language>en</language>
    <item>
      <title>UTM conventions that stop your analytics turning to mush</title>
      <dc:creator>Michael Yousrie</dc:creator>
      <pubDate>Fri, 31 Jul 2026 10:42:05 +0000</pubDate>
      <link>https://dev.to/michael-yousrie/utm-conventions-that-stop-your-analytics-turning-to-mush-332h</link>
      <guid>https://dev.to/michael-yousrie/utm-conventions-that-stop-your-analytics-turning-to-mush-332h</guid>
      <description>&lt;p&gt;Most broken analytics I have seen were not broken by a bug. They were broken by three people tagging links slightly differently for six months.&lt;/p&gt;

&lt;p&gt;UTM parameters have no schema. Nothing validates them. Your analytics tool takes whatever string you put in &lt;code&gt;utm_source&lt;/code&gt; and treats it as gospel, which means the discipline has to live in you, not the tool. Here is a convention that takes ten minutes to adopt and saves you from a report you cannot trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five parameters, and what each is actually for
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;utm_source&lt;/code&gt;: &lt;strong&gt;where the click came from&lt;/strong&gt;. A single named place: &lt;code&gt;newsletter&lt;/code&gt;, &lt;code&gt;instagram&lt;/code&gt;, &lt;code&gt;partner-acme&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;utm_medium&lt;/code&gt;: &lt;strong&gt;what kind of channel it is&lt;/strong&gt;: &lt;code&gt;email&lt;/code&gt;, &lt;code&gt;social&lt;/code&gt;, &lt;code&gt;cpc&lt;/code&gt;, &lt;code&gt;qr&lt;/code&gt;, &lt;code&gt;referral&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;utm_campaign&lt;/code&gt;: &lt;strong&gt;which initiative it belongs to&lt;/strong&gt;: &lt;code&gt;spring-launch&lt;/code&gt;, &lt;code&gt;black-friday-2026&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;utm_content&lt;/code&gt;: &lt;strong&gt;which specific link&lt;/strong&gt;, when one page has several: &lt;code&gt;header-cta&lt;/code&gt;, &lt;code&gt;footer-link&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;utm_term&lt;/code&gt;: paid keyword. Mostly vestigial unless you are running search ads.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The distinction people get wrong is source versus medium. Source is the &lt;em&gt;place&lt;/em&gt;, medium is the &lt;em&gt;type&lt;/em&gt;. Instagram is a source; social is a medium. Your newsletter is a source; email is a medium. If you find yourself putting &lt;code&gt;instagram&lt;/code&gt; in medium, the whole report will fragment later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule 1: lowercase everything, always
&lt;/h2&gt;

&lt;p&gt;This is the single highest-value rule, so it goes first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;utm_source=Instagram
utm_source=instagram
utm_source=INSTAGRAM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most analytics tools treat those as &lt;strong&gt;three different sources&lt;/strong&gt;. You will not notice for a month. Then you will open a report, see Instagram split across three rows, and have no clean way to merge history.&lt;/p&gt;

&lt;p&gt;Lowercase everything. No exceptions, no judgment calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule 2: hyphens, never spaces or underscores
&lt;/h2&gt;

&lt;p&gt;A space becomes &lt;code&gt;%20&lt;/code&gt; and looks broken in reports. Underscores are fine technically but mixing them with hyphens gives you the same fragmentation problem as casing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❌ utm_campaign=Spring Launch
❌ utm_campaign=spring_launch
✅ utm_campaign=spring-launch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pick hyphens. Write it down. Done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule 3: date your campaigns
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;utm_campaign=black-friday&lt;/code&gt; seems fine until it is your third Black Friday and every year is stacked into one row.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✅ utm_campaign=black-friday-2026
✅ utm_campaign=2026-03-product-launch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Year-first works well if you want campaigns to sort chronologically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule 4: never tag internal links
&lt;/h2&gt;

&lt;p&gt;This is the one that silently corrupts data.&lt;/p&gt;

&lt;p&gt;If you tag a link from your own homepage to your own pricing page, most analytics tools start a &lt;strong&gt;new session&lt;/strong&gt; at that click and overwrite the original referrer. The visitor came from a newsletter, clicked one internal link, and is now attributed to your own site.&lt;/p&gt;

&lt;p&gt;UTMs are for traffic arriving from outside. Internal navigation is what your normal page-path reporting is for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule 5: keep a canonical list
&lt;/h2&gt;

&lt;p&gt;Not a spreadsheet nobody opens. A short list in whatever doc your team actually reads:&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;sources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;newsletter, instagram, tiktok, linkedin, youtube, partner-&amp;lt;name&amp;gt;&lt;/span&gt;
&lt;span class="na"&gt;mediums&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;email, social, cpc, qr, referral, print&lt;/span&gt;
&lt;span class="na"&gt;campaign&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;yyyy&amp;gt;-&amp;lt;mm&amp;gt;-&amp;lt;short-name&amp;gt;&lt;/span&gt;
&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;header-cta, inline-link, footer-link, bio-link&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rule is: &lt;strong&gt;if it is not on the list, it does not get used until it is added to the list.&lt;/strong&gt; That single sentence is what prevents drift, because the failure mode is never one bad decision, it is fifty small ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule 6: think about what the user sees
&lt;/h2&gt;

&lt;p&gt;A URL like this in an Instagram bio is ugly, and it leaks your internal campaign naming to anyone who looks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://example.com/pricing?utm_source=instagram&amp;amp;utm_medium=social&amp;amp;utm_campaign=2026-03-launch&amp;amp;utm_content=bio-link
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where a short link earns its place. The tagged URL becomes the destination, and the thing you publish stays clean. You keep the attribution and the reader gets a link that fits.&lt;/p&gt;

&lt;p&gt;It also means the tagging lives in one editable place. Get a UTM wrong on a printed QR code and you are reprinting; get it wrong behind a short link and you fix the destination.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule 7: QR codes need &lt;code&gt;utm_medium=qr&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Scans from print are not "direct" traffic, but that is where they land if you do not tag them. Tag the URL behind the code, and if the same campaign runs on a poster and a flyer, separate them with &lt;code&gt;utm_content&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;utm_source=poster&amp;amp;utm_medium=qr&amp;amp;utm_campaign=2026-03-launch&amp;amp;utm_content=cafe-window
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now "did the flyers do anything" is a question with an answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  A worked example
&lt;/h2&gt;

&lt;p&gt;Spring launch, promoted through the newsletter, Instagram bio, and a printed flyer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# newsletter, main button
?utm_source=newsletter&amp;amp;utm_medium=email&amp;amp;utm_campaign=2026-03-spring-launch&amp;amp;utm_content=header-cta

# instagram bio link
?utm_source=instagram&amp;amp;utm_medium=social&amp;amp;utm_campaign=2026-03-spring-launch&amp;amp;utm_content=bio-link

# flyer QR code
?utm_source=flyer&amp;amp;utm_medium=qr&amp;amp;utm_campaign=2026-03-spring-launch&amp;amp;utm_content=cafe-window
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same campaign across all three, so they roll up. Different sources and mediums, so they break down. That is the whole point.&lt;/p&gt;

&lt;h2&gt;
  
  
  The check that catches drift
&lt;/h2&gt;

&lt;p&gt;Once a month, list your distinct &lt;code&gt;utm_source&lt;/code&gt; values. If you see &lt;code&gt;instagram&lt;/code&gt; and &lt;code&gt;Instagram&lt;/code&gt;, or &lt;code&gt;ig&lt;/code&gt; and &lt;code&gt;insta&lt;/code&gt;, drift has started. Fix the convention now, because merging historical rows is unpleasant and sometimes impossible.&lt;/p&gt;




&lt;p&gt;I build &lt;a href="https://swyfty.live" rel="noopener noreferrer"&gt;Swyfty&lt;/a&gt;, a short-link and analytics tool, which is why I have opinions about this. But none of the above needs my product or any product. It needs a list of allowed values and the discipline to stick to it.&lt;/p&gt;

&lt;p&gt;The tooling is never what makes attribution work. Consistency is. A boring convention applied every time beats a sophisticated analytics stack fed inconsistent strings, every single time.&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>marketing</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>The Human Is Not the Bottleneck. The Human Is the Missing Oracle</title>
      <dc:creator>Dane Balia</dc:creator>
      <pubDate>Fri, 31 Jul 2026 10:39:30 +0000</pubDate>
      <link>https://dev.to/daneb/the-human-is-not-the-bottleneck-the-human-is-the-missing-oracle-43lh</link>
      <guid>https://dev.to/daneb/the-human-is-not-the-bottleneck-the-human-is-the-missing-oracle-43lh</guid>
      <description>&lt;p&gt;We spent a year optimising the wrong bottleneck.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;harness discourse&lt;/strong&gt; has been relentless - blueprints, schematics, thought leadership. Impressive engineering, most of it. But read the designs closely and you notice what they're all quietly aiming at: getting the developer out of the pipeline.&lt;/p&gt;

&lt;p&gt;The logic seemed sound. Building dropped from days to minutes, and the human became the slow part. The bottleneck. So we designed the human out.&lt;/p&gt;

&lt;p&gt;Then the &lt;em&gt;telemetry arrived&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Faros AI instrumented 22,000 developers across 4,000+ teams. Moving from low to high AI adoption: task completion per developer up 34%, epics up 66%. Real gains. Also: bugs per developer up 54%, incidents per pull request up 243%, median review time up 5x, and 31% more PRs merging with no review at all.&lt;/p&gt;

&lt;p&gt;Nobody decided to stop reviewing. Reviewers simply couldn't keep pace, and unread merges became normal. We knew this failure before AI. The 2,000-line PR lands in your inbox, and what follows is frustration, cognitive overload, and a silent rubber stamp. The difference is that it used to be the exception. Now it's the throughput model.&lt;/p&gt;

&lt;p&gt;The bottleneck didn't disappear. It moved to review and integration - and we'd already removed the person who was supposed to be standing there when it arrived.&lt;/p&gt;

&lt;p&gt;Two things worth saying plainly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First:&lt;/strong&gt; the human was never the bottleneck. The human is the missing oracle.&lt;/p&gt;

&lt;p&gt;We have fast, cheap oracles for correctness. Do the tests pass? Does it lint? Machine-verifiable in seconds. We have no oracle at all for maintainability, intent, or design - and no benchmark I'm aware of currently measures them. GitClear's analysis of 623 million code changes shows what fills that gap: duplication up 81%, refactoring down 70%, error-masking constructs up 47%. Code that looks right and is structurally wrong.&lt;/p&gt;

&lt;p&gt;Designing the human out doesn't remove a cost. It removes the only sensor we have for the slowest and most expensive failure mode. "Humans first" isn't sentiment or ethics-washing. It's a claim about where verification capability actually resides right now.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second:&lt;/strong&gt; we're borrowing against comprehension and calling it velocity.&lt;/p&gt;

&lt;p&gt;Anthropic ran a controlled trial in January. Two groups, same unfamiliar library. The AI-assisted group finished marginally faster and scored 50% on comprehension. The manual group scored 67%. Seventeen points, on code they had written minutes earlier (this is a vendor publishing a result against it's own commercial interest).&lt;/p&gt;

&lt;p&gt;That debt doesn't show up in your DORA metrics. It shows up at 2am when nobody on the call can reason about the system they shipped.&lt;/p&gt;

&lt;p&gt;The industry response is to build faster agents. So I built the opposite experiment. A harness that puts the human at the centre - not to slow things down, but to make judgment cheap, timely and measurable rather than optional. You can check out the harness &lt;a href="https://github.com/daneb/harness" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Plain bash. Deliberately crude. Built to learn, not to scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The rules:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Agents don't move artefacts through the SDLC. They hit mechanical gates.&lt;/li&gt;
&lt;li&gt;Humans state intent, write the spec, define acceptance criteria, and read the diff.&lt;/li&gt;
&lt;li&gt;Route every check to the cheapest reliable verifier - mechanical gates for what's machine-checkable, calibrated LLM judges only after validation against human labels with explicit thresholds, humans for intent and design.&lt;/li&gt;
&lt;li&gt;Diff budget: 200 lines. (Which became 400, excluding tests). If it's more than 400 lines, the feature was scoped wrong. The gate fails and the work gets split.&lt;/li&gt;
&lt;li&gt;Only iterate on the harness with confirmed evidence and root cause.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Thirty minutes on an approved spec and a design sketch turns a six-hour adversarial review into a twenty-minute confirmation. The human isn't reviewing more. They're reviewing earlier, where a one-line correction prevents two thousand lines of rework.&lt;/p&gt;

&lt;p&gt;237 minutes. 12 features. One engineer, no control group - an experiment, not evidence. I'm publishing the method so it can be argued with. &lt;/p&gt;

&lt;p&gt;DORA's finding is that AI is an amplifier: it magnifies whatever your organisation already is. If your answer to a 243% rise in incidents per PR is to remove the last human who might have caught them, the amplifier is working exactly as designed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What are you actually measuring?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>UI/UX Design in 2026: Why Great Products Feel Invisible</title>
      <dc:creator>Tech Tales</dc:creator>
      <pubDate>Fri, 31 Jul 2026 10:38:52 +0000</pubDate>
      <link>https://dev.to/tech_tales_daa8a7eab515b3/uiux-design-in-2026-why-great-products-feel-invisible-lmf</link>
      <guid>https://dev.to/tech_tales_daa8a7eab515b3/uiux-design-in-2026-why-great-products-feel-invisible-lmf</guid>
      <description>&lt;p&gt;&lt;strong&gt;UI/UX Design in 2026: Why Great Products Feel Invisible&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When people think about UI/UX design, they often imagine beautiful colors, modern layouts, and smooth animations.&lt;/p&gt;

&lt;p&gt;But in 2026, great design isn't measured by how impressive it looks.&lt;/p&gt;

&lt;p&gt;It's measured by &lt;strong&gt;how little users have to think&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The best digital products today don't force users to learn complicated interfaces. They guide users naturally, helping them accomplish tasks quickly and confidently.&lt;/p&gt;

&lt;p&gt;That's why UI/UX has become one of the most important aspects of building successful web and mobile applications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good Design Solves Problems&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Imagine opening a food delivery app.&lt;/p&gt;

&lt;p&gt;You don't want to spend five minutes figuring out where the search bar is or how to place an order.&lt;/p&gt;

&lt;p&gt;You simply want food.&lt;/p&gt;

&lt;p&gt;That's the goal of good UX.&lt;/p&gt;

&lt;p&gt;Users visit applications to complete tasks—not to admire the interface.&lt;/p&gt;

&lt;p&gt;Whether it's:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shopping online&lt;/li&gt;
&lt;li&gt;Booking appointments&lt;/li&gt;
&lt;li&gt;Applying for jobs&lt;/li&gt;
&lt;li&gt;Managing business operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;the experience should feel effortless.&lt;/p&gt;

&lt;p&gt;If users have to think too much, the design has already failed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI Is Changing User Experience&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Artificial Intelligence is no longer just a backend feature.&lt;/p&gt;

&lt;p&gt;It's becoming part of the user experience itself.&lt;/p&gt;

&lt;p&gt;Modern applications now offer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Smart search suggestions&lt;/li&gt;
&lt;li&gt;Personalized recommendations&lt;/li&gt;
&lt;li&gt;AI chat assistants&lt;/li&gt;
&lt;li&gt;Predictive text&lt;/li&gt;
&lt;li&gt;Automated workflows&lt;/li&gt;
&lt;li&gt;Personalized dashboards&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of forcing users to search for everything manually, applications increasingly anticipate what users need next.&lt;/p&gt;

&lt;p&gt;Good UX in 2026 isn't about adding more features.&lt;/p&gt;

&lt;p&gt;It's about reducing unnecessary effort.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Simplicity Never Goes Out of Style&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One misconception about UI design is that "minimal" means "basic."&lt;/p&gt;

&lt;p&gt;Actually, designing simple interfaces is one of the hardest challenges.&lt;/p&gt;

&lt;p&gt;Every unnecessary button...&lt;/p&gt;

&lt;p&gt;Every distracting animation...&lt;/p&gt;

&lt;p&gt;Every confusing menu...&lt;/p&gt;

&lt;p&gt;adds friction.&lt;/p&gt;

&lt;p&gt;The best interfaces focus only on what's important.&lt;/p&gt;

&lt;p&gt;Think about products like Google Search or Notion.&lt;/p&gt;

&lt;p&gt;Their interfaces aren't overloaded with visual effects.&lt;/p&gt;

&lt;p&gt;Instead, they prioritize:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clear typography&lt;/li&gt;
&lt;li&gt;Logical spacing&lt;/li&gt;
&lt;li&gt;Consistent layouts&lt;/li&gt;
&lt;li&gt;Obvious navigation&lt;/li&gt;
&lt;li&gt;Purposeful interactions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Simple interfaces help users move faster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accessibility Is No Longer Optional&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Accessibility isn't just about compliance.&lt;/p&gt;

&lt;p&gt;It's about making products usable for everyone.&lt;/p&gt;

&lt;p&gt;Good accessibility includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High color contrast&lt;/li&gt;
&lt;li&gt;Readable typography&lt;/li&gt;
&lt;li&gt;Keyboard navigation&lt;/li&gt;
&lt;li&gt;Screen reader support&lt;/li&gt;
&lt;li&gt;Alternative text for images&lt;/li&gt;
&lt;li&gt;Large touch targets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Interestingly, these improvements don't only help users with disabilities.&lt;/p&gt;

&lt;p&gt;They improve the experience for everyone.&lt;/p&gt;

&lt;p&gt;That's why accessibility has become a core design principle rather than a feature.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trust Has Become Part of UX&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Today's users care about privacy.&lt;/p&gt;

&lt;p&gt;They want to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why an application requests permissions&lt;/li&gt;
&lt;li&gt;How their data is stored&lt;/li&gt;
&lt;li&gt;What information is collected&lt;/li&gt;
&lt;li&gt;Whether their account is secure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Designers now focus on creating interfaces that build confidence through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Transparent privacy settings&lt;/li&gt;
&lt;li&gt;Secure authentication flows&lt;/li&gt;
&lt;li&gt;Clear confirmation messages&lt;/li&gt;
&lt;li&gt;Honest error handling&lt;/li&gt;
&lt;li&gt;Predictable user interactions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A beautiful interface may attract users.&lt;/p&gt;

&lt;p&gt;A trustworthy experience keeps them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Designing Beyond One Screen&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;People don't use a single device anymore.&lt;/p&gt;

&lt;p&gt;A user might:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Discover your product on a phone&lt;/li&gt;
&lt;li&gt;Continue browsing on a tablet&lt;/li&gt;
&lt;li&gt;Complete the purchase on a laptop&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's why responsive design has evolved beyond simply resizing layouts.&lt;/p&gt;

&lt;p&gt;Modern UI/UX means creating experiences that feel natural across every screen size while maintaining consistency.&lt;/p&gt;

&lt;p&gt;Users shouldn't have to relearn your interface every time they switch devices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Businesses Care About UX&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Good UI/UX isn't just good for users.&lt;/p&gt;

&lt;p&gt;It's good for business.&lt;/p&gt;

&lt;p&gt;Better experiences often lead to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Higher customer satisfaction&lt;/li&gt;
&lt;li&gt;Better engagement&lt;/li&gt;
&lt;li&gt;Increased conversions&lt;/li&gt;
&lt;li&gt;Lower bounce rates&lt;/li&gt;
&lt;li&gt;Improved retention&lt;/li&gt;
&lt;li&gt;Fewer support requests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Investing in design often saves far more money than fixing usability issues after launch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A Few UI/UX Principles That Never Fail&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No matter how design trends change, these principles remain timeless:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Design for people, not trends.&lt;/li&gt;
&lt;li&gt;Keep interfaces simple.&lt;/li&gt;
&lt;li&gt;Remove unnecessary steps.&lt;/li&gt;
&lt;li&gt;Make navigation predictable.&lt;/li&gt;
&lt;li&gt;Test with real users.&lt;/li&gt;
&lt;li&gt;Prioritize accessibility.&lt;/li&gt;
&lt;li&gt;Build trust through transparency.&lt;/li&gt;
&lt;li&gt;Optimize for performance.&lt;/li&gt;
&lt;li&gt;Be consistent across every screen.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Technology changes.&lt;/p&gt;

&lt;p&gt;Human behavior doesn't change nearly as fast.&lt;/p&gt;

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

&lt;p&gt;Every year introduces new tools, AI capabilities, design systems, and visual trends.&lt;/p&gt;

&lt;p&gt;But the goal of UI/UX remains exactly the same:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Help users accomplish their goals with as little effort as possible.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Users rarely remember the color of a button.&lt;/p&gt;

&lt;p&gt;They remember how easy—or frustrating—it was to use your product.&lt;/p&gt;

&lt;p&gt;The best interfaces aren't the ones with the most animations.&lt;/p&gt;

&lt;p&gt;They're the ones users don't have to think about.&lt;/p&gt;

</description>
      <category>ux</category>
      <category>ui</category>
      <category>webdev</category>
      <category>design</category>
    </item>
    <item>
      <title>Benchmark an AI Coding Task Across Mobile Backgrounding, Network Switches, and Battery Pressure</title>
      <dc:creator>Roronoa</dc:creator>
      <pubDate>Fri, 31 Jul 2026 10:37:48 +0000</pubDate>
      <link>https://dev.to/roronoa_/benchmark-an-ai-coding-task-across-mobile-backgrounding-network-switches-and-battery-pressure-39e5</link>
      <guid>https://dev.to/roronoa_/benchmark-an-ai-coding-task-across-mobile-backgrounding-network-switches-and-battery-pressure-39e5</guid>
      <description>&lt;p&gt;Most AI coding assistants are demoed on a desk: laptop plugged in, stable Wi-Fi, full attention. But the moment I approve or monitor a long-running AI task from a phone, the environment becomes hostile — the app gets backgrounded, the device hops from Wi-Fi to LTE, iOS starts throttling background execution, and the battery drops below 20%.&lt;/p&gt;

&lt;p&gt;This article is a reproducible benchmark method for one question: &lt;strong&gt;does an AI coding task survive the mobile lifecycle, and can you tell the difference between 'recovered', 'restarted', and 'silently dropped'?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Test environment (record yours)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Device: iPhone 14, iOS 17.x (note your exact build)&lt;/li&gt;
&lt;li&gt;App state: task started from the foreground, screen unlocked&lt;/li&gt;
&lt;li&gt;Network: controlled Wi-Fi with the option to toggle airplane mode, plus LTE fallback&lt;/li&gt;
&lt;li&gt;Power: start at ~60% battery, charger off; repeat one run in Low Power Mode&lt;/li&gt;
&lt;li&gt;Backend: a remote AI coding agent endpoint. For these runs I used MonkeyCode, which offers free model access and a free server option — convenient for this kind of throwaway benchmark because I didn't have to provision my own GPU box or burn a paid quota on test traffic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Any agent backend with a task ID you can poll works for this method; the benchmark is about the lifecycle, not the vendor.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one metric that matters
&lt;/h2&gt;

&lt;p&gt;For each run, classify the final state:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Outcome&lt;/th&gt;
&lt;th&gt;Definition&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Recovered&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Same task ID, output continues from where the connection dropped&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Restarted&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;New task ID or duplicate execution; original work discarded or redone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Silent loss&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;UI shows 'running' or a spinner, but the backend has no live task&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Silent loss is the dangerous one. It looks like progress.&lt;/p&gt;

&lt;h2&gt;
  
  
  The benchmark script (client side)
&lt;/h2&gt;

&lt;p&gt;The client just needs to launch a task, poll by ID, and log transitions with timestamps. Pseudocode you can adapt to React Native, Flutter, or a plain fetch loop:&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;// lifecycle-bench.mjs — run in your app's debug console or a Node harness&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;extra&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;extra&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;launchTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BACKEND&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/tasks`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&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;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;taskId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nf"&gt;stamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;launched&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;taskId&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;taskId&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;poll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taskId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;lastStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(;;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BACKEND&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/tasks/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;taskId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;lastStatus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;stamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;progress&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="nx"&gt;lastStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;done&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;failed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&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;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;stamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;poll_error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&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;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// On app foregrounding, run: poll(savedTaskId)&lt;/span&gt;
&lt;span class="c1"&gt;// Then diff the log against the backend's task history.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After each run, compare the client log with server-side task history. If the client saw &lt;code&gt;poll_error&lt;/code&gt; for 4 minutes and the server shows the task still executing, that's a &lt;em&gt;recovered&lt;/em&gt; run. If the server shows the task was killed at the disconnect, your UI lied — classify accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The transition matrix
&lt;/h2&gt;

&lt;p&gt;Run the same prompt (a medium-length task, e.g. 'refactor this module and run the tests') under each condition. One variable per run:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Baseline&lt;/strong&gt; — foreground, Wi-Fi, charger on. Establishes expected duration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Background at T+30s&lt;/strong&gt; — press home, wait 5 minutes, return. Does polling resume? Does the task still exist?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wi-Fi → LTE handoff mid-task&lt;/strong&gt; — toggle Wi-Fi off while a poll is in flight. Watch for duplicated requests on reconnect.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Airplane mode 3 minutes&lt;/strong&gt; — full offline window. On restore, does the client re-attach to the task ID, or submit a new one?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low Power Mode + background&lt;/strong&gt; — iOS aggressively defers network activity. Expect longer gaps; check whether the agent times out server-side.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;App kill (swipe away)&lt;/strong&gt; — the harsh case. On relaunch, can you recover the task ID from local storage and re-attach?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Expected observations from my runs: conditions 2–4 are survivable when the client persists the task ID and the backend treats tasks as resumable by ID. Condition 5 is where I've seen silent loss most often — the backend's idle timeout fires while iOS defers the client's keepalive. Condition 6 fails entirely if the task ID only lives in memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to fix when a run fails
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Restarted instead of recovered:&lt;/strong&gt; persist &lt;code&gt;taskId&lt;/code&gt; (Keychain/Keystore, not just AsyncStorage if it's sensitive) and re-attach on launch before offering a 'start new task' button.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Silent loss:&lt;/strong&gt; add a heartbeat with a server-visible &lt;code&gt;lastSeenAt&lt;/code&gt;. If server &lt;code&gt;lastSeenAt&lt;/code&gt; is stale but UI shows 'running', surface 'connection lost — task status unknown' instead of a spinner.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplicate execution after reconnect:&lt;/strong&gt; make task creation idempotent (client-generated idempotency key), so a retried POST can't fork the run.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Single device, single OS version. Android's background execution model (Doze, OEM battery killers) behaves differently — repeat the matrix there before generalizing.&lt;/li&gt;
&lt;li&gt;I tested task &lt;em&gt;continuity&lt;/em&gt;, not output quality or model latency; those need their own benchmarks with device data.&lt;/li&gt;
&lt;li&gt;Free tiers change. The free model access and free server I used here are availability facts as of this writing, not a guarantee of quotas, duration, or performance — verify before building a workflow around them.&lt;/li&gt;
&lt;li&gt;Results depend heavily on backend timeout policy; your agent service may behave differently under the same client conditions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Who shouldn't use this approach
&lt;/h2&gt;

&lt;p&gt;If your tasks are sub-second, this whole matrix is overkill — just retry. And if the task handles sensitive code or credentials on a device you don't control, a free hosted server is the wrong target entirely; test continuity against infrastructure you're authorized to use.&lt;/p&gt;

&lt;p&gt;If you run this matrix on your own setup, I'd like to compare notes: device, OS version, which transition you triggered, and whether the task recovered, restarted, or silently disappeared. That last one is the number nobody's dashboard shows.&lt;/p&gt;

</description>
      <category>mobile</category>
      <category>ios</category>
      <category>ai</category>
      <category>testing</category>
    </item>
    <item>
      <title>Why I built a zero-upload, local-first browser video compressor (and why you should stop uploading your MP4s to remote servers)</title>
      <dc:creator>GG LL</dc:creator>
      <pubDate>Fri, 31 Jul 2026 10:37:29 +0000</pubDate>
      <link>https://dev.to/gg_ll_9be1b2cec449e1d292b/why-i-built-a-zero-upload-local-first-browser-video-compressor-and-why-you-should-stop-uploading-24ba</link>
      <guid>https://dev.to/gg_ll_9be1b2cec449e1d292b/why-i-built-a-zero-upload-local-first-browser-video-compressor-and-why-you-should-stop-uploading-24ba</guid>
      <description>&lt;p&gt;As content creators, video editors, and developers, we compress files almost every single day.&lt;/p&gt;

&lt;p&gt;Whether you're exporting a 4K draft for a client, trying to squeeze a gaming clip under Discord’s 10MB limit, or preparing a vertical video for TikTok, file size is a constant hurdle.&lt;/p&gt;

&lt;p&gt;But the way we compress videos online today is fundamentally broken. Here is why, and how a shift to "local-first" web tools can solve it.&lt;/p&gt;

&lt;p&gt;The Hidden Costs of Cloud Video Compressors&lt;br&gt;
When you search for "compress video online" on Google, you are met with dozens of websites that look free but share the same architecture: you drag your file, upload it to their remote cloud servers, wait for their servers to re-encode it, and download it back.&lt;/p&gt;

&lt;p&gt;This architecture introduces three massive pain points:&lt;/p&gt;

&lt;p&gt;The Upload Bandwidth Bottle-neck: If you are traveling, working on public Wi-Fi, or have slow upload speeds, uploading a 500MB video to a server can take 15 to 30 minutes. If the connection drops, you start over.&lt;br&gt;
The Privacy Nightmare: You are uploading private camera rolls, client drafts, or raw sensitive footage to a stranger’s remote server. You have no control over where that video is stored or who has access to it.&lt;br&gt;
Gated Paywalls: Because cloud bandwidth and server-side CPU encoding are extremely expensive, these sites must lock basic features behind &lt;br&gt;
9&lt;br&gt;
–&lt;br&gt;
9–15/month subscriptions. They limit your file size to 500MB on free tiers or slap ugly watermarks on your exports.&lt;br&gt;
The Solution: WebAssembly &amp;amp; Local-First Web Tools&lt;br&gt;
I was frustrated by these limitations, so I decided to build a local-first web-based video compressor: CreatorZip.&lt;/p&gt;

&lt;p&gt;Instead of renting expensive cloud servers to compress your files, CreatorZip leverages modern browser capabilities. It runs FFmpeg entirely inside your web browser using WebAssembly (WASM).&lt;/p&gt;

&lt;p&gt;This architectural shift changes everything:&lt;/p&gt;

&lt;p&gt;100% Privacy: The video compression happens entirely on your local machine. Your original footage never leaves your computer, and it never uploads to a remote server. You get the security of offline software with the convenience of an online tool.&lt;br&gt;
Instant Processing: Because there is no upload phase, processing starts immediately. It utilizes your local CPU/GPU hardware acceleration, saving you gigabytes of bandwidth.&lt;br&gt;
Zero Infrastructure Cost = Free for You: Because I don’t have to pay for expensive cloud encoding servers, CreatorZip has virtually zero operating cost. During our open launch phase, it is 100% free, has no ads, no watermarks, and supports files up to 1GB with batch queue processing.&lt;br&gt;
Pro-Tip: How to Defeat Discord's Blurry Auto-Compression&lt;br&gt;
If you share video clips on Discord, you've likely noticed that uploading raw files often results in a pixelated, blocky mess.&lt;/p&gt;

&lt;p&gt;This is because Discord uses high-speed, server-side auto-compressors optimized for speed rather than visual fidelity. If your video is over their free upload limit (10MB), they will forcefully crush the bitrate.&lt;/p&gt;

&lt;p&gt;The workaround: Compress your video manually to just under 10MB (e.g., 9.8MB) before you upload. Since the file is already within the limit, Discord will accept it as-is without applying their aggressive compression.&lt;/p&gt;

&lt;p&gt;CreatorZip has built-in presets for Discord 10MB/50MB, TikTok Smart, and High-Quality Lossless to automate this without you needing to do manual bitrate math.&lt;/p&gt;

&lt;p&gt;Try it out &amp;amp; Share your feedback&lt;br&gt;
CreatorZip is an indie project built to make creator workflows faster and safer. If you're tired of upload bars and subscription gates, give it a try:&lt;/p&gt;

&lt;p&gt;👉 creatorzip.app&lt;/p&gt;

&lt;p&gt;I'd love to hear your thoughts, feedback, and feature requests. What should I build next?&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>webassembly</category>
      <category>webdev</category>
      <category>indiehackers</category>
    </item>
    <item>
      <title>100 Days of Solana is a wrap!</title>
      <dc:creator>Matthew Revell</dc:creator>
      <pubDate>Fri, 31 Jul 2026 10:33:05 +0000</pubDate>
      <link>https://dev.to/100daysofsolana/100-days-of-solana-is-a-wrap-1hak</link>
      <guid>https://dev.to/100daysofsolana/100-days-of-solana-is-a-wrap-1hak</guid>
      <description>&lt;p&gt;Over the past 100 days, we’ve gone from the basics of wallets and transactions to deploying programs on Solana devnet.&lt;/p&gt;

&lt;p&gt;Some people completed every challenge. Others joined for a week, an arc, or whenever time allowed. However you took part, congratulations! You learned something new and built on Solana.&lt;/p&gt;

&lt;p&gt;And if you missed the live run, you haven’t missed your chance. All 100 challenges are still available, and you can &lt;a href="https://mlh.link/solana-100" rel="noopener noreferrer"&gt;start from Day 1 whenever suits you&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is 100 Days of Solana?
&lt;/h2&gt;

&lt;p&gt;You may be curious about blockchain development but find that many tutorials assume you already understand the terminology, use cases, and other fundamentals.&lt;/p&gt;

&lt;p&gt;100 Days of Solana was designed to provide a clearer route in: from “I’m a web or mobile developer and this looks interesting” to “I can build and deploy something real on Solana.”&lt;/p&gt;

&lt;p&gt;Here’s how it works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;100 daily challenges&lt;/li&gt;
&lt;li&gt;Most challenges introduce one focused idea in around 10 to 20 minutes.&lt;/li&gt;
&lt;li&gt;Each week has a theme that introduces and reinforces a new concept.&lt;/li&gt;
&lt;li&gt;It’s free to take part.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the whole thing is designed so that you can still make progress if you complete just a couple or every challenge from a week.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we covered
&lt;/h2&gt;

&lt;p&gt;If you start at Day 1, here’s how the journey unfolds.&lt;/p&gt;

&lt;h3&gt;
  
  
  First: learn to speak Solana
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Create a wallet — a cryptographic keypair that acts as your identity — and send your first transactions&lt;/li&gt;
&lt;li&gt;Read live data directly from the network&lt;/li&gt;
&lt;li&gt;Get to grips with Solana’s account model&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Next: create digital assets
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Create your own tokens and NFTs, which are built from accounts with clearly defined roles and rules&lt;/li&gt;
&lt;li&gt;Configure features such as transfer fees, interest, and transfer restrictions&lt;/li&gt;
&lt;li&gt;See how your Solana program enforces those rules on every transfer&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Then: write your own programs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Learn the basics of Solana programs&lt;/li&gt;
&lt;li&gt;Learn enough Rust to build with Anchor, a framework that provides some of the structure Rails or Express brings to Web2 development&lt;/li&gt;
&lt;li&gt;Design state with program-derived addresses, or PDAs: predictable addresses calculated from known values&lt;/li&gt;
&lt;li&gt;Call other programs in much the same way that one service calls another service’s API&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  After that: think like an attacker
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Treat every input as hostile, because anyone on a public network can send instructions to your program&lt;/li&gt;
&lt;li&gt;Audit your code and write tests that actively try to break it&lt;/li&gt;
&lt;li&gt;Use property testing and fuzzing to find edge cases you might never write by hand&lt;/li&gt;
&lt;li&gt;Reproduce a real Solana exploit, understand why it worked, and then fix the vulnerability&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Finally: ship what you built
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Deploy to devnet, Solana’s free staging environment&lt;/li&gt;
&lt;li&gt;Publish your program’s interface, roughly the on-chain equivalent of an OpenAPI schema&lt;/li&gt;
&lt;li&gt;Build a frontend that connects to a user’s wallet and asks them to approve transactions while their private keys remain under their control&lt;/li&gt;
&lt;li&gt;Give an AI agent tightly limited access to a wallet through a deny-by-default policy layer&lt;/li&gt;
&lt;li&gt;Bring everything together in a capstone project of your own&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By Day 100, you’ll have gone from creating your first wallet to deploying your own program, with an understanding of the major layers in between.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shout-outs
&lt;/h2&gt;

&lt;p&gt;One of the best things about 100 Days of Solana was the community that formed around it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/t/100daysofsolana"&gt;People published hundreds of posts here on DEV&lt;/a&gt;, while the event Discord became a place to compare approaches, untangle tooling problems, review code, and celebrate first deployments.&lt;/p&gt;

&lt;p&gt;If you’re starting the challenges now, those posts are a valuable companion along the way. Whatever error you’re staring at, there’s a good chance someone has already encountered it and written about what they learned.&lt;/p&gt;

&lt;p&gt;Thank you to everyone who wrote, demoed, answered a question, or reviewed someone else’s code. You made 100 Days of Solana far better than the challenges could have been on their own.&lt;/p&gt;

&lt;p&gt;And a special thank you to Vincent Jande, a graduate of MLH’s Solana Fellows program, who helped reivew and create content throughout the 100 days.&lt;/p&gt;

&lt;h2&gt;
  
  
  Continue your journey
&lt;/h2&gt;

&lt;p&gt;Whether you’ve just shipped your first Solana project at the end of the 100 days or you’re about to begin day 1, there are several ways to keep going:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://solana.com/developers/bootcamp" rel="noopener noreferrer"&gt;Solana Developer Bootcamp&lt;/a&gt;&lt;/strong&gt; — a free, project-based video course from the Solana team for developers who want to go deeper&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://ghw.mlh.com/" rel="noopener noreferrer"&gt;Global Hack Week&lt;/a&gt;&lt;/strong&gt; — MLH’s free monthly online hackathon, where you can use your Solana skills in a new project&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://mlh.io" rel="noopener noreferrer"&gt;MLH events&lt;/a&gt;&lt;/strong&gt; — find an in-person or digital hackathon and build alongside other developers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The scheduled 100 days may be over but there's always more to explore.&lt;/p&gt;

&lt;p&gt;Happy hacking!&lt;/p&gt;

</description>
      <category>web3</category>
      <category>blockchain</category>
      <category>100daysofsolana</category>
      <category>solana</category>
    </item>
    <item>
      <title>How AI Is Transforming Software Development Workflows in 2026</title>
      <dc:creator>Kai X Intelligence </dc:creator>
      <pubDate>Fri, 31 Jul 2026 10:31:08 +0000</pubDate>
      <link>https://dev.to/kaixintelligence/how-ai-is-transforming-software-development-workflows-in-2026-1k6f</link>
      <guid>https://dev.to/kaixintelligence/how-ai-is-transforming-software-development-workflows-in-2026-1k6f</guid>
      <description>&lt;h1&gt;
  
  
  How AI Is Transforming Software Development Workflows in 2026
&lt;/h1&gt;

&lt;p&gt;By 2026, the question is no longer &lt;em&gt;whether&lt;/em&gt; AI is useful in software development, but &lt;em&gt;how deeply&lt;/em&gt; it has been woven into the everyday fabric of engineering. We've moved past the era of simple suggestion widgets and autocomplete. The modern developer workspace is a human–AI hybrid environment where autonomous agents handle entire subtasks, review code with sophisticated reasoning, and even manage infrastructure—leaving humans to focus on architecting solutions and making product-level decisions.&lt;/p&gt;

&lt;p&gt;This article explores the key ways AI is reshaping software development workflows in 2026, from pair programming to production monitoring, and what that means for developers, teams, and the entire delivery pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Shift from Copilots to Autonomous Agents
&lt;/h2&gt;

&lt;p&gt;In 2024, AI assistants were largely reactive: you wrote a function, and the tool suggested the next line. By 2026, the paradigm has shifted to &lt;em&gt;proactive autonomous agents&lt;/em&gt;. These agents are context-aware, connected to your repository, issue tracker, and CI system. They don't wait for a prompt—they analyze your branch, understand the epic you're working on, and start implementing subtasks on their own.&lt;/p&gt;

&lt;p&gt;For example, a typical workflow might look like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A developer creates a branch for a new feature.&lt;/li&gt;
&lt;li&gt;An AI agent reads the linked Jira or Linear ticket and scans related code.&lt;/li&gt;
&lt;li&gt;The agent generates a first draft of the implementation, including unit tests and refactoring notes.&lt;/li&gt;
&lt;li&gt;The developer reviews the code, makes adjustments, and approves.&lt;/li&gt;
&lt;li&gt;The agent then documents the changes, updates any relevant API schemas, and opens a pull request.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This doesn't replace the developer—it removes the "grunt work" that often consumes 60% of a day. In 2026, developers treat agents like junior teammates: they delegate tasks, review outputs, and provide concise, high-level feedback.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Simple Agent Interaction Example
&lt;/h3&gt;

&lt;p&gt;Let's say you're working on a Python service. Instead of searching for a library or skimming Stack Overflow, you can just describe the intent in an inline comment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# AI-TASK: Add retry logic with exponential backoff to the fetch_product_data function.
# Context: We use httpx, and the external API is flaky. Use a max of 5 retries,
# with jitter, and log warnings when a retry occurs.
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_product_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI agent in your IDE reads the comment, understands the existing function signature and project conventions, and fills in the implementation. You see a diff you can accept or tweak. This kind of "spec-driven" development has become a standard practice, cutting boilerplate implementation time by an order of magnitude.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI-Native CI/CD Pipelines
&lt;/h2&gt;

&lt;p&gt;Continuous integration and deployment have always been about automation, but AI is now making pipelines self-healing and adaptive. In 2026, an AI-powered CI system doesn't just run tests—it learns from the entire history of the project to predict failures before they occur.&lt;/p&gt;

&lt;p&gt;For instance, a pipeline can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Identify risky code paths based on change patterns and historical bug data.&lt;/li&gt;
&lt;li&gt;Automatically expand or reduce test suites according to risk assessment.&lt;/li&gt;
&lt;li&gt;Diagnose failing tests and suggest fixes, often applying a patch directly to the branch.&lt;/li&gt;
&lt;li&gt;Roll back or escalate deployments based on runtime signal analysis.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Consider this example of an AI-infused GitHub Actions workflow (conceptually simplified for 2026):&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AI-Guided CI&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pull_request&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build-and-test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run AI risk analysis&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ai-risk-analyzer@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;openai_model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gpt-heuristic-2026'&lt;/span&gt;
          &lt;span class="na"&gt;auto_fix&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;max_attempts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run targeted tests&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;make test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Behind the scenes, the AI engine classifies the PR, estimates the probability of regression, and may even generate new test cases for the changed logic. If a test fails, the agent inspects the stack trace and proposes a fix—not by guessing, but by cross-referencing the project's history and known issue patterns.&lt;/p&gt;

&lt;p&gt;This reduces the classic "test failure feedback loop" from hours to minutes. Developers get a failing test with a suggested patch, and if they trust the suggestion, they click "apply." If not, the agent keeps iterating while the developer moves on to other work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automated Code Review at a Higher Level
&lt;/h2&gt;

&lt;p&gt;Pull request reviews have always been a bottleneck. In 2026, AI reviewers are ubiquitous, but they're far smarter than the "linter with opinions" of the past. These systems now perform semantic analysis, understand the business intent behind the change, and flag issues that human reviewers might miss—such as security vulnerabilities, performance regressions, or architectural violength.&lt;/p&gt;

&lt;p&gt;An AI code reviewer in 2026 does more than comment on syntax:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It evaluates whether the implementation matches the function's spec and comments.&lt;/li&gt;
&lt;li&gt;It checks for side effects, race conditions, and edge cases.&lt;/li&gt;
&lt;li&gt;It compares the change against similar patterns in other codebases.&lt;/li&gt;
&lt;li&gt;It enforces team-specific style guides by reasoning, not regex.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What About Human Review?
&lt;/h3&gt;

&lt;p&gt;Humans are still in the loop, but their role is elevated. Instead of reading every line, human reviewers focus on the AI's summary of the change and the highest-risk areas it has flagged. In many teams, the human reviewer is now seen as the final validator of &lt;em&gt;intent&lt;/em&gt;, not the detector of typos.&lt;/p&gt;

&lt;p&gt;A typical review comment from an AI might be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"This caching strategy holds a lock while performing I/O, which could degrade throughput at scale. In similar services, we've used an optimistic lock. Want me to refactor it?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's not just a style comment—it's an architectural suggestion, backed by project-specific data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-Writing and Self-Healing Tests
&lt;/h2&gt;

&lt;p&gt;The most notable change in developer workflow is how tests are created and maintained. In 2025, test generation was an add-on. By 2026, it's standard. AI agents write tests at the same time they write production code, and they also update existing tests when requirements change.&lt;/p&gt;

&lt;p&gt;This has profound effects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Test coverage on critical modules is near 100% because agents ensure it.&lt;/li&gt;
&lt;li&gt;Error messages from failed tests are now enriched with hypotheses of what caused the failure.&lt;/li&gt;
&lt;li&gt;Flaky tests are automatically quarantined and, where possible, fixed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice, a developer rarely sees a failing test and has to dig through logs manually. Instead, the AI presents a concise diagnosis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Test `test_checkout_with_expired_coupon` failed because the coupon validation
method was called before the session token was verified. The ordering was
changed in commit a1b2c3. Suggested patch: move the validation call after the
session check. Run with --ai-fix to apply.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This level of automation transforms debugging from a detective hunt into a review-and-approve activity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Documentation and Knowledge Management
&lt;/h2&gt;

&lt;p&gt;Nobody likes writing docs, but AI in 2026 makes it painless. During development, agents continuously update the README, API references, and internal wikis. They capture not only what the code does, but the &lt;em&gt;context&lt;/em&gt; of why certain decisions were made.&lt;/p&gt;

&lt;p&gt;When you open a project, an AI-generated summary greets you:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This repository contains the checkout microservice. Last updated 2 hours ago by agent-5. It handles payments, invoicing, and refunds. The current branch introduces a new idempotency layer to support retries.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Imagine the onboarding time saved. A new developer can go from reading docs to contributing in half a day instead of two weeks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Shift in Team Dynamics and Roles
&lt;/h2&gt;

&lt;p&gt;With AI handling repetitive implementation, debugging, and testing, the developer's role is shifting toward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;System design and architecture&lt;/li&gt;
&lt;li&gt;AI-agent orchestration&lt;/li&gt;
&lt;li&gt;Code review and quality enforcement&lt;/li&gt;
&lt;li&gt;Product and stakeholder communication&lt;/li&gt;
&lt;li&gt;Handling ambiguous, novel problems that require genuine creativity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Engineering managers now measure output differently. It's not about how many lines of code you wrote; it's about how effectively you direct the AI agents and validate their work. This is often called "prompt engineering" or "agent wiring," but it's really just higher-level software engineering.&lt;/p&gt;

&lt;h3&gt;
  
  
  The New "Tech Stack"
&lt;/h3&gt;

&lt;p&gt;In 2026, the modern software stack includes not just languages and frameworks, but a layer of AI tooling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Agent orchestration:&lt;/strong&gt; Tools like LangChain, AutoGen, or in-house agent workflows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model gateways:&lt;/strong&gt; Centralized access to LLMs, with prompt caching and safety guardrails.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration APIs:&lt;/strong&gt; Connecting agents to GitHub, Jira, Slack, and your cloud provider.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human review tools:&lt;/strong&gt; UIs for reviewing AI-generated diffs, tests, and infra changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are the foundational components of a contemporary engineering workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Addressing the Challenges
&lt;/h2&gt;

&lt;p&gt;It's not all perfect. In 2026, teams have learned to handle several challenges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Over-reliance:&lt;/strong&gt; Blindly accepting AI patches can introduce subtle bugs or security issues. Strong governance and human review remain essential.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt injections:&lt;/strong&gt; Malicious instructions in code comments or external packages can trick agents. Sandboxing and validation are now part of security practice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model confusion:&lt;/strong&gt; AI sometimes hallucinates APIs or library names. Having deterministic tools and unit tests that verify the output is critical.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintaining data privacy:&lt;/strong&gt; Sensitive codebases often require on-premise AI models or private cloud instances, which adds complexity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Teams that successfully adopted AI in 2026 treat it as a useful but fallible teammate—not an oracle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Impact: Case Studies
&lt;/h2&gt;

&lt;p&gt;A large e-commerce company reported that their feature lead time dropped from 14 days to 3 days, mostly due to AI agents handling feature branches and regression testing. Another company specializing in SaaS analytics saw a 70% reduction in flaky tests after deploying an AI test-maintenance agent.&lt;/p&gt;

&lt;p&gt;On the infrastructure side, AI is now deeply involved in defining Kubernetes manifests, Terraform plans, and CI/CD configuration. Instead of manually writing YAML files, developers describe the desired state, and an AI agent generates the configuration, applying best practices based on cloud provider recipes. This has reduced misconfiguration incidents by roughly 45%, as the agent can cross-check against live cloud service constraints.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Future Is Already Here
&lt;/h2&gt;

&lt;p&gt;By 2026, AI isn't just a feature that assists developers; it has become a core layer in the development platform itself. The workflows of tomorrow are not about humans competing with machines to type code fast, but humans collaborating with AI agents to build software that is more reliable, more maintainable, and delivered at a pace we previously thought impossible.&lt;/p&gt;

&lt;p&gt;If there is one takeaway, it's this: the developer who thrives in 2026 is the one who learns not just how to code, but how to &lt;em&gt;direct, verify, and improve&lt;/em&gt; AI agents. The code is still being written—just with a radically different, deeply collaborative process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;AI has transformed software development workflows from a solo human endeavor into a partnership between human creativity and machine speed. In 2026, the best teams are those that have learned to trust AI for what it's best at (repetitive, well-defined tasks) and reserve human intelligence for what remains irreplaceable (deciding what to build and why). The future of software development is not AI-only; it's AI-assisted, AI-owned for the mundane, and overwhelmingly human-led for the meaningful.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>productivity</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>A Hardware Wallet Can Stay Offline and Still Create a Weak Seed</title>
      <dc:creator>Azalea</dc:creator>
      <pubDate>Fri, 31 Jul 2026 10:27:49 +0000</pubDate>
      <link>https://dev.to/azaleakuts/a-hardware-wallet-can-stay-offline-and-still-create-a-weak-seed-10pc</link>
      <guid>https://dev.to/azaleakuts/a-hardware-wallet-can-stay-offline-and-still-create-a-weak-seed-10pc</guid>
      <description>&lt;p&gt;Hardware wallets are usually sold around one simple idea: keep the private key away from the internet, and you remove a large part of the risk.&lt;/p&gt;

&lt;p&gt;That is true, but only after the key has been created correctly.&lt;/p&gt;

&lt;p&gt;A recent warning from Coinkite shows how security can fail much earlier – at the moment the wallet generates its seed phrase. The device may remain offline, never expose the key to a computer and still create a wallet that is far easier to attack than its owner realizes.&lt;/p&gt;

&lt;p&gt;On July 30, Coinkite advised users to move funds from wallets whose seed phrases were generated on a Coldcard Mk3 running firmware 4.0.1 or later.&lt;/p&gt;

&lt;p&gt;Older firmware versions on the Mk4, Mk5 and Q were also affected, although Coinkite estimates that the problem was less severe on those devices.&lt;/p&gt;

&lt;p&gt;The device used the wrong source of randomness&lt;/p&gt;

&lt;p&gt;A seed phrase begins with entropy – unpredictable data used to create the wallet’s private keys.&lt;/p&gt;

&lt;p&gt;Coldcard was supposed to take that randomness from a hardware true random number generator built into the device. But after a firmware change introduced in 2021, the seed-generation process began calling a software-based fallback instead.&lt;/p&gt;

&lt;p&gt;The mistake was easy to miss.&lt;/p&gt;

&lt;p&gt;Both implementations used the same function name and interface. The firmware compiled normally. The correct hardware RNG code was still present in the final binary. A reviewer looking for the expected implementation could find it and reasonably assume it was being used.&lt;/p&gt;

&lt;p&gt;It was not.&lt;/p&gt;

&lt;p&gt;The seed-generation path was reaching the fallback function.&lt;/p&gt;

&lt;p&gt;Coinkite’s early analysis estimates that affected Mk3 seeds may have had roughly 40 bits of effective entropy instead of the intended 128 bits.&lt;/p&gt;

&lt;p&gt;The Mk4, Mk5 and Q also mixed in randomness from their secure elements, raising the estimate to around 72 bits. That is stronger than the Mk3 result, but still well below the security level users expected.&lt;/p&gt;

&lt;p&gt;The difference is not cosmetic.&lt;/p&gt;

&lt;p&gt;A 128-bit seed is considered beyond realistic brute-force attacks. A search space closer to 40 bits is much smaller and may be practical for a well-resourced attacker, especially when many potentially vulnerable wallets can be checked at once.&lt;/p&gt;

&lt;p&gt;Why keeping the wallet offline did not solve the problem&lt;/p&gt;

&lt;p&gt;The attack does not require someone to connect to the device, infect a laptop or extract a key from secure storage.&lt;/p&gt;

&lt;p&gt;The weakness already exists inside the seed.&lt;/p&gt;

&lt;p&gt;An attacker can generate possible seeds on separate hardware, derive the corresponding Bitcoin addresses and compare them with addresses visible on the public blockchain.&lt;/p&gt;

&lt;p&gt;The owner’s Coldcard does not need to be online. It does not even need to be turned on.&lt;/p&gt;

&lt;p&gt;This is the part that makes the incident uncomfortable. An air gap can protect signing and reduce exposure to remote attacks, but it cannot repair weak randomness used during wallet creation.&lt;/p&gt;

&lt;p&gt;“Offline” describes where the key is stored and used. It does not prove that the key was generated securely.&lt;/p&gt;

&lt;p&gt;A firmware update does not repair an old seed&lt;/p&gt;

&lt;p&gt;Coinkite released firmware 5.6.0 for the Mk4 and Mk5 and version 1.5.0Q for the Q.&lt;/p&gt;

&lt;p&gt;Those updates fix seed generation going forward. They do not add entropy to a seed that already exists.&lt;/p&gt;

&lt;p&gt;Anyone covered by the warning needs to create a new wallet and move the funds.&lt;/p&gt;

&lt;p&gt;The safer process is straightforward:&lt;/p&gt;

&lt;p&gt;Install the corrected firmware on a supported device.&lt;br&gt;
Generate a completely new seed phrase.&lt;br&gt;
Write down the backup and verify it carefully.&lt;br&gt;
Check the wallet fingerprint and receiving address on the device.&lt;br&gt;
Send a small test transaction.&lt;br&gt;
Move the rest only after confirming the test arrived correctly.&lt;/p&gt;

&lt;p&gt;Until the transfer is complete, the old wallet should be treated as potentially exposed.&lt;/p&gt;

&lt;p&gt;Dice rolls may have helped – but only when used properly&lt;/p&gt;

&lt;p&gt;Coldcard lets users add their own randomness by entering physical dice rolls during seed generation.&lt;/p&gt;

&lt;p&gt;According to Coinkite, a seed created with at least 50 fair, independent and private rolls is not considered vulnerable to this RNG issue on its own.&lt;/p&gt;

&lt;p&gt;The details matter.&lt;/p&gt;

&lt;p&gt;Users should not assume they are safe when they entered only a handful of rolls, cannot remember how many they used or recorded the sequence digitally.&lt;/p&gt;

&lt;p&gt;In those cases, migration remains the safer option.&lt;/p&gt;

&lt;p&gt;A BIP-39 passphrase may also make an attack harder, but it should not be confused with the device PIN. A short, reused or predictable passphrase may still be guessed.&lt;/p&gt;

&lt;p&gt;Coinkite recommends moving funds even when a strong passphrase was used.&lt;/p&gt;

&lt;p&gt;The real failure happened in the development process&lt;/p&gt;

&lt;p&gt;Random-number failures are not new in cryptography. The more useful question is how this particular mistake remained unnoticed for years.&lt;/p&gt;

&lt;p&gt;The intended hardware RNG implementation was in the codebase. It was included in the compiled firmware. The fallback function looked compatible. Nothing obvious broke during compilation.&lt;/p&gt;

&lt;p&gt;The product worked.&lt;/p&gt;

&lt;p&gt;It simply called the wrong function at the most sensitive moment in the wallet’s lifecycle.&lt;/p&gt;

&lt;p&gt;That points to a broader engineering problem: checking that secure code exists is not the same as confirming that the application actually uses it.&lt;/p&gt;

&lt;p&gt;For security-critical software, reviews need to follow the full execution path:&lt;/p&gt;

&lt;p&gt;Which implementation does the final build link to?&lt;br&gt;
Where does the entropy used for seed generation actually come from?&lt;br&gt;
Can fallback RNG code be excluded entirely?&lt;br&gt;
Will the build fail when the wrong symbol is present?&lt;br&gt;
Are critical calls tested by behavior rather than by source inspection alone?&lt;/p&gt;

&lt;p&gt;Coinkite says its hotfix now removes the fallback PRNG object and adds a build-time symbol check that fails unless the expected hardware-specific RNG is present.&lt;/p&gt;

&lt;p&gt;That is a useful fix, but the wider lesson extends beyond one wallet manufacturer.&lt;/p&gt;

&lt;p&gt;A device can isolate a private key perfectly after generation and still fail at the moment that key is created.&lt;/p&gt;

&lt;p&gt;For a hardware wallet, that first moment may be the most important one of all.&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>bitcoin</category>
      <category>security</category>
      <category>cryptocurrency</category>
    </item>
    <item>
      <title>Fixing Exact Package Search Relevance in npmx</title>
      <dc:creator>Anil Loutombam</dc:creator>
      <pubDate>Fri, 31 Jul 2026 10:27:09 +0000</pubDate>
      <link>https://dev.to/anilloutombam/fixing-exact-package-search-relevance-in-npmx-34c7</link>
      <guid>https://dev.to/anilloutombam/fixing-exact-package-search-relevance-in-npmx-34c7</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Clear the Lineup&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://npmx.dev" rel="noopener noreferrer"&gt;npmx&lt;/a&gt; is an open-source alternative interface for exploring packages from the npm registry. It provides package details, documentation, version history, dependencies, search tools, and other information for JavaScript developers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug Fix or Performance Improvement
&lt;/h2&gt;

&lt;p&gt;I worked on &lt;a href="https://github.com/npmx-dev/npmx.dev/issues/2978" rel="noopener noreferrer"&gt;issue #2978&lt;/a&gt;, which was reported by another contributor.&lt;/p&gt;

&lt;p&gt;When Algolia was selected as the search provider, the page displayed “Relevance” as the active sorting option. However, an exact package-name match did not always appear first.&lt;/p&gt;

&lt;p&gt;For example, searching for &lt;code&gt;napkin&lt;/code&gt; could show other packages above the exact match. Switching to “Downloads/wk” and then back to “Relevance” changed the results and moved &lt;code&gt;napkin&lt;/code&gt; to the top.&lt;/p&gt;

&lt;p&gt;I reproduced this behavior locally before investigating the cause.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/npmx-dev/npmx.dev/issues/2978" rel="noopener noreferrer"&gt;Issue #2978&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Pull request: &lt;a href="https://github.com/npmx-dev/npmx.dev/pull/3103" rel="noopener noreferrer"&gt;https://github.com/npmx-dev/npmx.dev/pull/3103&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Before and after
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Before
&lt;/h3&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://private-user-images.githubusercontent.com/23106365/629641010-7b90dc30-62b3-419a-9442-36a198edd8e3.mov?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3ODU0OTM3MzYsIm5iZiI6MTc4NTQ5MzQzNiwicGF0aCI6Ii8yMzEwNjM2NS82Mjk2NDEwMTAtN2I5MGRjMzAtNjJiMy00MTlhLTk0NDItMzZhMTk4ZWRkOGUzLm1vdj9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNjA3MzElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjYwNzMxVDEwMjM1NlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWUxMzEwYjI3OGUxN2I0M2ExZGI1ZjM5MTAzMTk0OWRhYmI0NjIzMzc2M2Q4YjBkZmNhNGNkNTE1MWNjY2RmZGYmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JnJlc3BvbnNlLWNvbnRlbnQtdHlwZT12aWRlbyUyRnF1aWNrdGltZSJ9.jYPPoG_cEHKfZk_oXZ1Qy1_0D2WPHbcGASouXU0PEpE" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;private-user-images.githubusercontent.com&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  After
&lt;/h3&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://private-user-images.githubusercontent.com/23106365/629641622-e73ea88e-e74c-40c6-a1a6-462760619c4b.mov?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3ODU0OTM3NTksIm5iZiI6MTc4NTQ5MzQ1OSwicGF0aCI6Ii8yMzEwNjM2NS82Mjk2NDE2MjItZTczZWE4OGUtZTc0Yy00MGM2LWExYTYtNDYyNzYwNjE5YzRiLm1vdj9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNjA3MzElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjYwNzMxVDEwMjQxOVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTMzNzkxNmE1MTMyODRiNWIyNTg3MWIyN2MwNGI5MThlM2RlNmYzNWIzNjZhOGVhNTViZGE5OGM5NmQ0NGZlNGMmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JnJlc3BvbnNlLWNvbnRlbnQtdHlwZT12aWRlbyUyRnF1aWNrdGltZSJ9.DDshiEDIyCohCupPhqxI7XlriGJAovK3XOnrHSflnRc" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;private-user-images.githubusercontent.com&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  My Improvements
&lt;/h2&gt;

&lt;p&gt;npmx already made an additional Algolia request to check whether a package with the exact searched name existed.&lt;/p&gt;

&lt;p&gt;However, this request retrieved only the package name and used the result only as an existence check. If the exact package was missing from the initial batch of results, the application knew it existed but did not have enough information to display it.&lt;/p&gt;

&lt;p&gt;I updated the exact-package request to retrieve the complete package information. When the returned package name matches the searched name, it is placed at the beginning of the results.&lt;/p&gt;

&lt;p&gt;Before adding it, the code removes any existing occurrence of the same package. This prevents duplicate results.&lt;/p&gt;

&lt;p&gt;I also added two safety checks during review:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A missing Algolia &lt;code&gt;hits&lt;/code&gt; array falls back to an empty array.&lt;/li&gt;
&lt;li&gt;A returned hit is promoted only when its name matches the requested package.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, I added a regression test that supplies a regular search result and a separate exact-package result. The test verifies that the exact package is detected and placed first.&lt;/p&gt;

&lt;p&gt;I validated the change by running the focused Nuxt tests, TypeScript checks, lint and formatting checks, and by testing the behavior manually in the local UI.&lt;/p&gt;

&lt;p&gt;After the fix, an exact package-name match appears at the top immediately when “Relevance” is selected.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
      <category>typescript</category>
      <category>opensource</category>
    </item>
    <item>
      <title>ratatop day 3: disks, statvfs, and my first unsafe block</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Fri, 31 Jul 2026 10:17:57 +0000</pubDate>
      <link>https://dev.to/lovestaco/ratatop-day-3-disks-statvfs-and-my-first-unsafe-block-496e</link>
      <guid>https://dev.to/lovestaco/ratatop-day-3-disks-statvfs-and-my-first-unsafe-block-496e</guid>
      <description>&lt;p&gt;&lt;em&gt;Hello, I'm Maneshwar. I'm building git-lrc, a Micro AI code reviewer that runs on every commit. It is free and source-available on Github. &lt;a href="https://github.com/HexmosTech/git-lrc?utm_source=ratatop" rel="noopener noreferrer"&gt;Star git-lrc&lt;/a&gt; to help devs discover the project. Do give it a try and share your feedback.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Day three of building a btop-style system monitor in Rust with &lt;a href="https://ratatui.rs" rel="noopener noreferrer"&gt;ratatui&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Day one was the CPU box. &lt;/p&gt;

&lt;p&gt;Day two was memory, where I learned that &lt;code&gt;MemFree&lt;/code&gt; is a number nobody should look at.&lt;/p&gt;

&lt;p&gt;Today: disks.&lt;/p&gt;

&lt;p&gt;And this one broke a rule I had set for myself on day one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbndf7afjdeu4smd3f8mn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbndf7afjdeu4smd3f8mn.png" alt=" " width="800" height="166"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  There is no /proc file for this
&lt;/h2&gt;

&lt;p&gt;My rule was simple. &lt;/p&gt;

&lt;p&gt;Read &lt;code&gt;/proc&lt;/code&gt; and &lt;code&gt;/sys&lt;/code&gt;, never shell out to another tool.&lt;/p&gt;

&lt;p&gt;CPU came from &lt;code&gt;/proc/stat&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Memory came from &lt;code&gt;/proc/meminfo&lt;/code&gt;. Disks would come from... something.&lt;/p&gt;

&lt;p&gt;I went looking for the file. &lt;code&gt;/proc/mounts&lt;/code&gt; tells you what is mounted. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;/proc/diskstats&lt;/code&gt; tells you how much I/O happened. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;/sys/block/*&lt;/code&gt; tells you device sizes.&lt;/p&gt;

&lt;p&gt;None of them tell you how full a filesystem is.&lt;/p&gt;

&lt;p&gt;That number does not exist as a file anywhere. It only exists behind a syscall: &lt;code&gt;statvfs(2)&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;The kernel computes it on demand from the filesystem's own metadata, and the only way to ask is to actually make the call.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2msuz6ii9vfnshfv0re2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2msuz6ii9vfnshfv0re2.png" alt=" " width="360" height="502"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So I had two options.&lt;/p&gt;

&lt;p&gt;Shell out to &lt;code&gt;df&lt;/code&gt; and parse it, which breaks my rule and drags in output-format fragility across distros.&lt;/p&gt;

&lt;p&gt;Or make the syscall myself, which means the &lt;code&gt;libc&lt;/code&gt; crate, which means &lt;code&gt;unsafe&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I picked the syscall. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;df&lt;/code&gt; itself is just a &lt;code&gt;statvfs&lt;/code&gt; wrapper with a printf on top. &lt;/p&gt;

&lt;p&gt;Spawning a process to read a number the kernel would hand me directly is silly.&lt;/p&gt;

&lt;h2&gt;
  
  
  My first unsafe block
&lt;/h2&gt;

&lt;p&gt;Rust's standard library has no &lt;code&gt;statvfs&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;So this is FFI territory.&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;fn&lt;/span&gt; &lt;span class="nf"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mount&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;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;)&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;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;CString&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.ok&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// SAFETY: `path` is a valid NUL-terminated string that outlives the call,&lt;/span&gt;
    &lt;span class="c1"&gt;// and `vfs` is fully initialised by statvfs before we read it. A failed call&lt;/span&gt;
    &lt;span class="c1"&gt;// is reported through the return value, which we check.&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;vfs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;vfs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;statvfs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;zeroed&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;statvfs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="nf"&gt;.as_ptr&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;mut&lt;/span&gt; &lt;span class="n"&gt;vfs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;vfs&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;frsize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vfs&lt;/span&gt;&lt;span class="py"&gt;.f_frsize&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;u64&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;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vfs&lt;/span&gt;&lt;span class="py"&gt;.f_blocks&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.saturating_mul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frsize&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;free&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vfs&lt;/span&gt;&lt;span class="py"&gt;.f_bavail&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.saturating_mul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frsize&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;free&lt;/span&gt;&lt;span class="nf"&gt;.min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&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;Three things worth pointing out, because they are the parts I had to actually think about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;CString::new&lt;/code&gt; is not optional.&lt;/strong&gt; C wants a NUL-terminated string. Rust &lt;code&gt;&amp;amp;str&lt;/code&gt; is not NUL-terminated, it carries a length. If you hand C a pointer to raw Rust string bytes, it reads until it happens to find a zero byte, which could be well past the end of your string. &lt;code&gt;CString&lt;/code&gt; does the conversion properly, and it returns a &lt;code&gt;Result&lt;/code&gt; because a Rust string containing an interior NUL cannot be represented at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;mem::zeroed()&lt;/code&gt; looks worse than it is.&lt;/strong&gt; We are handing C a buffer to fill in. &lt;code&gt;statvfs&lt;/code&gt; writes every field before returning success. Zeroing first means that even in some bizarre partial-write scenario we get zeros rather than whatever was on the stack.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The &lt;code&gt;SAFETY&lt;/code&gt; comment is not decoration.&lt;/strong&gt; Rust convention is that every &lt;code&gt;unsafe&lt;/code&gt; block gets a comment explaining why it is actually safe. Writing that comment is a genuinely useful exercise. It forces you to enumerate your assumptions, and if you cannot write the comment, that is your answer about whether the code is correct.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn4jf7ebv1bxmzldj42qw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn4jf7ebv1bxmzldj42qw.png" alt=" " width="360" height="471"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The nice part is that the unsafety is contained.&lt;/p&gt;

&lt;p&gt;One function, ten lines, and a safe &lt;code&gt;Option&amp;lt;(u64, u64)&amp;gt;&lt;/code&gt; comes out the other end. Everything upstream of it is ordinary safe Rust that cannot tell the difference.&lt;/p&gt;
&lt;h2&gt;
  
  
  Your filesystem is lying to you by exactly 5%
&lt;/h2&gt;

&lt;p&gt;Now the fun part.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;statvfs&lt;/code&gt; gives you three block counts, and picking the wrong one gives you the wrong answer.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;f_blocks&lt;/code&gt;: total blocks in the filesystem&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;f_bfree&lt;/code&gt;: blocks that are genuinely unused&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;f_bavail&lt;/code&gt;: blocks available &lt;strong&gt;to an unprivileged process&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those last two are not the same, and the gap is bigger than you would guess. Real numbers from my root filesystem:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;total   91.1 GiB
bfree   23.7 GiB
bavail  19.0 GiB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That is 4.67 GiB, exactly 5.1% of the disk, that is free but that I am not allowed to use.&lt;/p&gt;

&lt;p&gt;ext4 reserves 5% of the filesystem for root by default. The idea is that if a runaway process fills the disk, root still has room to log in and clean up, and the filesystem has room to avoid the fragmentation that comes with running completely full.&lt;/p&gt;

&lt;p&gt;Which number you subtract changes the headline figure:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;used via f_bavail   79.1%
used via f_bfree    74.0%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Five percentage points, from one field name.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdd1vv27yr6owyz4jqgqw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdd1vv27yr6owyz4jqgqw.png" alt=" " width="792" height="289"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;btop uses &lt;code&gt;f_bavail&lt;/code&gt;, and so does &lt;code&gt;df&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;That is the honest choice for a monitor: report the space you can actually use, and treat root's reserve as spoken for. &lt;/p&gt;

&lt;p&gt;Showing 74% when &lt;code&gt;df&lt;/code&gt; says 79% would just make the tool look broken.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcysr9lbwdijvbz9m43kd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcysr9lbwdijvbz9m43kd.png" alt=" " width="360" height="263"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Which mounts are even real?
&lt;/h2&gt;

&lt;p&gt;Next problem. &lt;code&gt;/proc/self/mounts&lt;/code&gt; on my laptop has around 40 entries. &lt;/p&gt;

&lt;p&gt;Four of them are disks.&lt;/p&gt;

&lt;p&gt;The rest is &lt;code&gt;proc&lt;/code&gt;, &lt;code&gt;sysfs&lt;/code&gt;, &lt;code&gt;cgroup2&lt;/code&gt;, &lt;code&gt;tmpfs&lt;/code&gt;, a dozen snap squashfs images, and a couple of FUSE mounts from the desktop session.&lt;/p&gt;

&lt;p&gt;The obvious move is a blocklist of filesystem types to skip. &lt;/p&gt;

&lt;p&gt;I started writing one, then realised the kernel already publishes exactly this information:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cat /proc/filesystems
nodev   sysfs
nodev   tmpfs
nodev   proc
nodev   cgroup2
    ext4
    vfat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Anything flagged &lt;code&gt;nodev&lt;/code&gt; is not backed by a block device. &lt;/p&gt;

&lt;p&gt;That is the kernel telling you, authoritatively, which filesystem types cannot possibly be a disk.&lt;/p&gt;

&lt;p&gt;So instead of maintaining a list that goes stale, I read theirs:&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;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;types&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="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;
    &lt;span class="nf"&gt;.lines&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;.filter_map&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="nf"&gt;.strip_prefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"nodev"&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;name&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="nf"&gt;.trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;New pseudo-filesystem lands in a future kernel? Excluded automatically, no code change.&lt;/p&gt;

&lt;p&gt;There is a second filter that does a lot of work for one line: the mount's device has to start with &lt;code&gt;/&lt;/code&gt;. A real partition is &lt;code&gt;/dev/nvme0n1p2&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Pseudo-filesystems put a bare name there instead, like &lt;code&gt;proc&lt;/code&gt; or &lt;code&gt;gvfsd-fuse&lt;/code&gt;. No slash, not a disk.&lt;/p&gt;
&lt;h2&gt;
  
  
  The kernel escapes your mount paths
&lt;/h2&gt;

&lt;p&gt;Small one, but it would have been a confusing bug.&lt;/p&gt;

&lt;p&gt;Mount a disk at a path with a space in it, and &lt;code&gt;/proc/self/mounts&lt;/code&gt; does not give you the space:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/dev/sdb1 /mnt/my\040disk ext4 rw,relatime 0 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;\040&lt;/code&gt; is octal for a space. The kernel escapes spaces, tabs, newlines and backslashes this way, because the file is whitespace-separated and an unescaped space would make the line unparseable.&lt;/p&gt;

&lt;p&gt;If you pass that raw string to &lt;code&gt;statvfs&lt;/code&gt;, it fails. No such path exists. The disk silently vanishes from your monitor and you have no idea why.&lt;/p&gt;

&lt;p&gt;So you decode it before using it. Four characters starting with a backslash, parse the next three as octal, emit the byte.&lt;/p&gt;
&lt;h2&gt;
  
  
  I/O comes from somewhere else entirely
&lt;/h2&gt;

&lt;p&gt;Usage is a syscall. Throughput is back to reading files.&lt;/p&gt;

&lt;p&gt;btop reads &lt;code&gt;/sys/block/&amp;lt;device&amp;gt;/stat&lt;/code&gt;, which gives you a device's cumulative counters:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;234345 84965 16346864 796898 486240 189824 21513416 23255863 0 2406558 24197776
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The fields I care about are index 2 (sectors read), index 6 (sectors written), and index 9 (&lt;code&gt;io_ticks&lt;/code&gt;, milliseconds the device spent with a request in flight).&lt;/p&gt;

&lt;p&gt;Two traps here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A sector is always 512 bytes in this file.&lt;/strong&gt; Not the device's real block size. My NVMe drive uses 4096-byte blocks, but these counters are still expressed in 512-byte units, for backwards compatibility with software written when that was universal. Multiply by 4096 and your throughput numbers come out eight times too high.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Partitions are nested.&lt;/strong&gt; &lt;code&gt;/sys/block/nvme0n1/stat&lt;/code&gt; exists for the whole drive. The partition I actually mounted lives at &lt;code&gt;/sys/block/nvme0n1/nvme0n1p2/stat&lt;/code&gt;, one level deeper. So the lookup checks the top level first, then scans one level down.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;io_ticks&lt;/code&gt; is the nice one. It is busy-milliseconds, so dividing by the elapsed wall-clock milliseconds gives you utilisation directly:&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;let&lt;/span&gt; &lt;span class="n"&gt;busy_ms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;counters&lt;/span&gt;&lt;span class="py"&gt;.io_ticks&lt;/span&gt;&lt;span class="nf"&gt;.saturating_sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;previous&lt;/span&gt;&lt;span class="py"&gt;.io_ticks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;disk&lt;/span&gt;&lt;span class="py"&gt;.io_util&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;busy_ms&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1000.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;100.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.clamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;100.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Same delta pattern as the CPU box on day one. Third time I have written &lt;code&gt;saturating_sub&lt;/code&gt; on a monotonic counter in this project, and it has earned its keep every time: devices get unplugged and re-added, and counters reset to zero when they do.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhrkx9fylt6lpdvj7ats7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhrkx9fylt6lpdvj7ats7.png" alt=" " width="800" height="748"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Swap is a disk that isn't one
&lt;/h2&gt;

&lt;p&gt;btop lists swap in the disks box, which surprised me until it made sense. You care about how full it is, in the same way and for the same reasons.&lt;/p&gt;

&lt;p&gt;But it has no filesystem, so &lt;code&gt;statvfs&lt;/code&gt; is meaningless. And it may be a file, a partition, or several of both, so there is no single block device to read I/O from.&lt;/p&gt;

&lt;p&gt;It comes from &lt;code&gt;/proc/meminfo&lt;/code&gt; instead:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SwapTotal:      21092348 kB
SwapFree:       19857688 kB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So swap is a row that shares the layout but skips two of the three data sources. &lt;/p&gt;

&lt;p&gt;In my code it is a &lt;code&gt;Disk&lt;/code&gt; with &lt;code&gt;is_swap: true&lt;/code&gt;, and the render function checks that flag before drawing the I/O graph. &lt;/p&gt;

&lt;p&gt;Drawing an empty graph there would imply a measurement of zero, when the truth is there is no measurement at all.&lt;/p&gt;

&lt;p&gt;The distinction between "zero" and "unknown" is one of those things that seems pedantic until your tool confidently reports the wrong thing.&lt;/p&gt;
&lt;h2&gt;
  
  
  The ratatui side was the easy part
&lt;/h2&gt;

&lt;p&gt;Genuinely almost nothing to say here, which is the point.&lt;/p&gt;

&lt;p&gt;Three rows per disk, laid out with the same &lt;code&gt;Layout&lt;/code&gt; calls as the other boxes:&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;let&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;name_row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;io_row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;used_row&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Layout&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;vertical&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nn"&gt;Constraint&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Length&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="nn"&gt;Constraint&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Length&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="nn"&gt;Constraint&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Length&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="p"&gt;])&lt;/span&gt;
&lt;span class="nf"&gt;.areas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;area&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;Meter&lt;/code&gt; widget from day one and the &lt;code&gt;BrailleGraph&lt;/code&gt; from day one both dropped straight in with zero changes. Third box, same two widgets. That is the payoff for writing them as plain structs with one &lt;code&gt;render&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;One layout decision worth mentioning: the box only draws disks that completely fit.&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;let&lt;/span&gt; &lt;span class="n"&gt;capacity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inner&lt;/span&gt;&lt;span class="py"&gt;.height&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;ROWS_PER_DISK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Integer division, so a disk with room for only two of its three rows is not drawn at all. A half-rendered disk looks like a bug, whereas a missing one just looks like a small window.&lt;/p&gt;
&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Network. &lt;code&gt;/proc/net/dev&lt;/code&gt; is another counter file, so the delta machinery is already there, but auto-scaling units across five orders of magnitude will be new.&lt;/p&gt;

&lt;p&gt;Then processes, which is the big one. &lt;/p&gt;

&lt;p&gt;Reading &lt;code&gt;/proc/[pid]/stat&lt;/code&gt; for every process, every tick, and sorting it.&lt;/p&gt;

&lt;p&gt;Code so far:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/lovestaco" rel="noopener noreferrer"&gt;
        lovestaco
      &lt;/a&gt; / &lt;a href="https://github.com/lovestaco/ratatop" rel="noopener noreferrer"&gt;
        ratatop
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Keep an eye on what's cooking in your system 
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/lovestaco/ratatop/media/cover.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Flovestaco%2Fratatop%2FHEAD%2Fmedia%2Fcover.png" alt="ratatop" width="100%"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;A reimplementation of &lt;a href="https://github.com/aristocratos/btop" rel="noopener noreferrer"&gt;btop&lt;/a&gt; in Rust, built on
&lt;a href="https://ratatui.rs" rel="nofollow noopener noreferrer"&gt;ratatui&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The goal is a resource monitor that feels like btop: the four-box layout, braille
graphs with gradient fills, mouse-driven navigation, and a themable process
manager, but written as a modern Rust TUI.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Status&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The CPU box works.&lt;/strong&gt; It samples real data and renders btop's layout:&lt;/p&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/lovestaco/ratatop/media/ratatop.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Flovestaco%2Fratatop%2FHEAD%2Fmedia%2Fratatop.png" alt="ratatop running"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Per-core braille history graphs, a gradient load bar with sub-cell resolution
frequency, package temperature, battery, and load averages.&lt;/p&gt;
&lt;p&gt;Everything else, the memory, network, and process boxes, is still to come.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Design notes&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data source:&lt;/strong&gt; &lt;code&gt;/proc&lt;/code&gt; and &lt;code&gt;/sys&lt;/code&gt; on Linux first, read directly rather than
shelling out. btop's &lt;code&gt;src/linux/btop_collect.cpp&lt;/code&gt; is the reference for what to
read and how often; the BSD/macOS collectors are out of scope for v1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Graphs are the hard part.&lt;/strong&gt; btop's look comes from braille sub-cell plotting
with a color gradient applied along the value axis, not from a stock chart
widget. Expect…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/lovestaco/ratatop" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Today's lesson, if there is one: the rule I set myself ("only read files, never shell out") was a good rule that had exactly one legitimate exception, and finding out &lt;em&gt;why&lt;/em&gt; it was an exception taught me more than following it would have.&lt;/p&gt;

&lt;p&gt;Sometimes the file you are looking for does not exist because the number was never a file.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" alt=" " width="360" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AI agents write code fast. They also silently remove logic, change behavior, and introduce bugs — without telling you. You often find out in production.&lt;/p&gt;

&lt;p&gt;git-lrc fixes this. It hooks into git commit and reviews every diff before it lands. 60-second setup. Completely free.&lt;/p&gt;

&lt;p&gt;Any feedback or contributors are welcome! It's online, source-available, and ready for anyone to use.&lt;/p&gt;

&lt;p&gt;⭐ Star it on GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HexmosTech" rel="noopener noreferrer"&gt;
        HexmosTech
      &lt;/a&gt; / &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;
        git-lrc
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Free, Micro AI Code Reviews That Run on Git Commit
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;p&gt;| &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.da.md" rel="noopener noreferrer"&gt;🇩🇰 Dansk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.es.md" rel="noopener noreferrer"&gt;🇪🇸 Español&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fa.md" rel="noopener noreferrer"&gt;🇮🇷 Farsi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fi.md" rel="noopener noreferrer"&gt;🇫🇮 Suomi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ja.md" rel="noopener noreferrer"&gt;🇯🇵 日本語&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.nn.md" rel="noopener noreferrer"&gt;🇳🇴 Norsk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.pt.md" rel="noopener noreferrer"&gt;🇵🇹 Português&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ru.md" rel="noopener noreferrer"&gt;🇷🇺 Русский&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.sq.md" rel="noopener noreferrer"&gt;🇦🇱 Shqip&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.zh.md" rel="noopener noreferrer"&gt;🇨🇳 中文&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.hi.md" rel="noopener noreferrer"&gt;🇮🇳 हिन्दी&lt;/a&gt; |&lt;/p&gt;
&lt;br&gt;
&lt;br&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;img width="60" alt="git-lrc logo" src="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;/a&gt;
&lt;br&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;git-lrc&lt;/h1&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Free, Micro AI Code Reviews That Run on Commit&lt;/h2&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;a href="https://www.producthunt.com/products/git-lrc?embed=true&amp;amp;utm_source=badge-top-post-badge&amp;amp;utm_medium=badge&amp;amp;utm_campaign=badge-git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="git-lrc - Free, micro AI code reviews that run on commit | Product Hunt" width="200" src="https://camo.githubusercontent.com/87bf2d4283c1e0aa99e254bd17fefb1c67c0c0d39300043a243a4aa633b6cecc/68747470733a2f2f6170692e70726f6475637468756e742e636f6d2f776964676574732f656d6265642d696d6167652f76312f746f702d706f73742d62616467652e7376673f706f73745f69643d31303739323632267468656d653d6c6967687426706572696f643d6461696c7926743d31373731373439313730383638"&gt;&lt;/a&gt;
&amp;nbsp;&lt;/p&gt;
&lt;br&gt;
&lt;a href="https://discord.gg/sGdnKwB3qq" rel="nofollow noopener noreferrer"&gt;
  &lt;img alt="Discord Community" src="https://camo.githubusercontent.com/b8f979318aaabc8dec512b9d4e6e2a12431fba3c8a3b8738e1a97a0722d4e4bf/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446973636f72642d436f6d6d756e6974792d3538363546323f6c6f676f3d646973636f7264266c6162656c436f6c6f723d7768697465"&gt;
&lt;/a&gt; &lt;a href="https://goreportcard.com/report/github.com/HexmosTech/git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="Go Report Card" src="https://camo.githubusercontent.com/e74c0651c3ee9165a2ed01cb0f6842c494029960df30eb9c24cf622d3d21bf46/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f4865786d6f73546563682f6769742d6c7263"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml" rel="noopener noreferrer"&gt;&lt;img alt="gitleaks.yml" title="gitleaks.yml: Secret scanning workflow" src="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml" rel="noopener noreferrer"&gt;&lt;img alt="osv-scanner.yml" title="osv-scanner.yml: Dependency vulnerability scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml" rel="noopener noreferrer"&gt;&lt;img alt="govulncheck.yml" title="govulncheck.yml: Go vulnerability check" src="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml" rel="noopener noreferrer"&gt;&lt;img alt="semgrep.yml" title="semgrep.yml: Static analysis security scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/dependabot-enabled.svg"&gt;&lt;img alt="dependabot-enabled" title="dependabot-enabled: Automated dependency updates are enabled" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fdependabot-enabled.svg"&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/a_few_micro_reviews.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fa_few_micro_reviews.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GenAI today is a &lt;strong&gt;race car without brakes&lt;/strong&gt;. It accelerates fast -- you describe something, and large blocks of code appear instantly. But AI agents &lt;em&gt;silently break things&lt;/em&gt;: they remove logic, relax constraints, introduce expensive cloud calls, leak credentials, and change behavior -- without telling you. You often find out in production.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;git-lrc&lt;/code&gt; is your braking system.&lt;/strong&gt; It hooks into &lt;code&gt;git commit&lt;/code&gt; and runs an AI review on every diff &lt;em&gt;before&lt;/em&gt; it lands. 60-second setup. Completely free.&lt;/p&gt;
&lt;p&gt;In short, git-lrc helps &lt;strong&gt;Prevent Outages, Breaches, and Technical Debt Before They Happen&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;At a glance:&lt;/strong&gt; &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;10 risk categories&lt;/a&gt; · &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;100+ failure patterns tracked&lt;/a&gt; · every commit…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>cli</category>
      <category>beginners</category>
      <category>programming</category>
      <category>rust</category>
    </item>
    <item>
      <title>Inside Instagram's DASH manifest — from MPD to segment to stitched MP4</title>
      <dc:creator>ai ai</dc:creator>
      <pubDate>Fri, 31 Jul 2026 10:16:41 +0000</pubDate>
      <link>https://dev.to/ai_ai_c7d6c56f61323774fca/inside-instagrams-dash-manifest-from-mpd-to-segment-to-stitched-mp4-118e</link>
      <guid>https://dev.to/ai_ai_c7d6c56f61323774fca/inside-instagrams-dash-manifest-from-mpd-to-segment-to-stitched-mp4-118e</guid>
      <description>&lt;p&gt;The first surprise when you reverse-engineer an Instagram Reel download is that there's no single MP4 to fetch. There's an MPD file, separate video and audio representations, and a bunch of segment URLs with TTL-bound signatures. We walk through a real DASH manifest from our 2026-04-22 testing, show the fields that matter, and explain why a one-line ffmpeg command is the bridge between what Instagram stores and what ends up on your disk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why 'download this Reel' is harder than it sounds
&lt;/h2&gt;

&lt;p&gt;Pick any Reel on Instagram, open your browser's DevTools Network tab, and watch the requests as the video plays. You won't see a single &lt;code&gt;video.mp4&lt;/code&gt; being fetched. You'll see one MPD (Media Presentation Description) file, then a flurry of 2-5 second &lt;code&gt;.m4s&lt;/code&gt; segments, some labeled video, some labeled audio, pulled from scontent-*.cdninstagram.com in parallel.&lt;/p&gt;

&lt;p&gt;That's DASH — Dynamic Adaptive Streaming over HTTP, the standard Netflix helped popularize around 2012 and that every major video platform now uses. Instagram sits on top of it. Which means our downloader's job isn't just to copy a file; it's to read the MPD, pick the right representations, pull the right segments, and glue them back into a single MP4. MDN has a clean primer on the Media Source API side of the story at &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Media_Source_API/Transcoding_assets_for_MSE" rel="noopener noreferrer"&gt;https://developer.mozilla.org/en-US/docs/Web/API/Media_Source_API/Transcoding_assets_for_MSE&lt;/a&gt; — we're on the other end of the same pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  The MPD: what's actually in it
&lt;/h2&gt;

&lt;p&gt;An MPD is plain XML. Here's a trimmed, real Instagram MPD we pulled on 2026-04-22 for the @natgeo Reel DH56yy7p3lZ (we're eliding the signature tokens but keeping the structural fields):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;MPD&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"static"&lt;/span&gt; &lt;span class="na"&gt;mediaPresentationDuration=&lt;/span&gt;&lt;span class="s"&gt;"PT60.060S"&lt;/span&gt; &lt;span class="na"&gt;minBufferTime=&lt;/span&gt;&lt;span class="s"&gt;"PT2.0S"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Period&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;AdaptationSet&lt;/span&gt; &lt;span class="na"&gt;contentType=&lt;/span&gt;&lt;span class="s"&gt;"video"&lt;/span&gt; &lt;span class="na"&gt;mimeType=&lt;/span&gt;&lt;span class="s"&gt;"video/mp4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Representation&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"v720"&lt;/span&gt; &lt;span class="na"&gt;codecs=&lt;/span&gt;&lt;span class="s"&gt;"avc1.64001f"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"720"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"1280"&lt;/span&gt; &lt;span class="na"&gt;bandwidth=&lt;/span&gt;&lt;span class="s"&gt;"1356000"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;BaseURL&amp;gt;&lt;/span&gt;https://scontent-*.cdninstagram.com/.../v720.m4s?oe=...&lt;span class="nt"&gt;&amp;lt;/BaseURL&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/Representation&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Representation&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"v360"&lt;/span&gt; &lt;span class="na"&gt;codecs=&lt;/span&gt;&lt;span class="s"&gt;"avc1.4d001e"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"360"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"640"&lt;/span&gt; &lt;span class="na"&gt;bandwidth=&lt;/span&gt;&lt;span class="s"&gt;"309000"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/AdaptationSet&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;AdaptationSet&lt;/span&gt; &lt;span class="na"&gt;contentType=&lt;/span&gt;&lt;span class="s"&gt;"audio"&lt;/span&gt; &lt;span class="na"&gt;mimeType=&lt;/span&gt;&lt;span class="s"&gt;"audio/mp4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Representation&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"a72"&lt;/span&gt; &lt;span class="na"&gt;codecs=&lt;/span&gt;&lt;span class="s"&gt;"mp4a.40.5"&lt;/span&gt; &lt;span class="na"&gt;audioSamplingRate=&lt;/span&gt;&lt;span class="s"&gt;"48000"&lt;/span&gt; &lt;span class="na"&gt;bandwidth=&lt;/span&gt;&lt;span class="s"&gt;"72000"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;BaseURL&amp;gt;&lt;/span&gt;https://scontent-*.cdninstagram.com/.../a72.m4s?oe=...&lt;span class="nt"&gt;&amp;lt;/BaseURL&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/Representation&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/AdaptationSet&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Period&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/MPD&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three fields do most of the work. &lt;code&gt;codecs&lt;/code&gt; tells a client what codec profile to expect — &lt;code&gt;avc1.64001f&lt;/code&gt; is H.264 High Profile Level 3.1, which every browser and every player since 2012 handles. &lt;code&gt;bandwidth&lt;/code&gt; is the mean bitrate in bits per second — the client uses it to pick which representation matches the connection speed. And &lt;code&gt;&amp;lt;BaseURL&amp;gt;&lt;/code&gt; is the actual segment URL, signed with an expiry parameter that expires in about an hour.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why video and audio are separate representations
&lt;/h2&gt;

&lt;p&gt;The historical version: multiplexed MP4s (video and audio interleaved in one file) were the default until streaming platforms realized they were wasteful. Many clients could download the same video track but pick different audio languages, or pair high-quality audio with a lower-bandwidth video. Separating the two lets the client mix and match.&lt;/p&gt;

&lt;p&gt;For Instagram specifically there's usually just one audio representation per Reel — they don't ship multiple language tracks. The separation still matters though: the video and audio streams are encoded with completely different codecs (H.264 vs HE-AAC), different framerates, different segment durations. Trying to muxes them in a single file at encode time would mean one codec dictates the segment boundary strategy of the other, which hurts streaming efficiency.&lt;/p&gt;

&lt;p&gt;The practical consequence is what we've called out on the reels-downloader tool page: drop the video representation alone and you get a silent clip. Drop the audio alone and you get an M4A with no picture. The merge step isn't optional; it's what makes the download useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  ffmpeg as the glue
&lt;/h2&gt;

&lt;p&gt;Every download we serve passes through one ffmpeg invocation on our VPS. The command is short enough to paste here without eliding anything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; &amp;lt;video-segment-url&amp;gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &amp;lt;audio-segment-url&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
       &lt;span class="nt"&gt;-c&lt;/span&gt; copy &lt;span class="nt"&gt;-movflags&lt;/span&gt; frag_keyframe+empty_moov &lt;span class="nt"&gt;-f&lt;/span&gt; mp4 pipe:1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three flags that matter. &lt;code&gt;-c copy&lt;/code&gt; tells ffmpeg to copy both streams into the output container without re-encoding. The bytes on disk are identical to what Instagram serves — no quality loss, no generation loss, no bitrate change. &lt;code&gt;-movflags frag_keyframe+empty_moov&lt;/code&gt; makes the output a fragmented MP4, which means ffmpeg can pipe bytes to the HTTP response as they're produced instead of seeking back to the file's header to write an index. Without this flag ffmpeg wants a seekable output, which a network pipe isn't. And &lt;code&gt;pipe:1&lt;/code&gt; directs the bytes to stdout, which our Next.js handler streams straight to the requesting browser.&lt;/p&gt;

&lt;p&gt;The whole thing takes 2-5 seconds on a typical Reel, most of which is fetch time from Instagram's CDN via our residential proxy. The actual remux is CPU-bound at maybe 10-15% of a single core. The bottleneck is the network.&lt;/p&gt;

&lt;h2&gt;
  
  
  Segment TTL and why your old Reel URLs stop working
&lt;/h2&gt;

&lt;p&gt;Look again at the &lt;code&gt;oe=...&lt;/code&gt; query parameter on the segment URLs in the MPD snippet. That's a Unix timestamp in hex, and when you decode it against today's production URLs you get roughly 108 hours (about 4.5 days) after the MPD was generated. After that timestamp, Instagram's CDN rejects requests to those exact segment URLs with a 403 Forbidden response. (An earlier version of this post claimed ~1 hour based on outdated assumptions — we re-measured on 2026-04-23 and the real TTL is closer to 4.5 days. We keep a separate write-up of the full URL signature anatomy at &lt;a href="https://cliplatch.com/blog/instagram-cdn-url-signature-anatomy" rel="noopener noreferrer"&gt;https://cliplatch.com/blog/instagram-cdn-url-signature-anatomy&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;Even with a multi-day window, a naive downloader that caches the parsed MPD indefinitely will start returning 403s eventually, and you can't tell a valid URL from an expired one without making the request. Our solution is to not cache the MPD at all — every user's paste call runs a fresh /api/parse that fetches a new MPD with fresh signatures. It's a little wasteful in proxy cost but it means the download buttons the user sees always reference URLs that haven't expired. The cost tradeoff is explained in &lt;a href="https://cliplatch.com/blog/how-residential-proxies-bypass-instagram-cdn" rel="noopener noreferrer"&gt;https://cliplatch.com/blog/how-residential-proxies-bypass-instagram-cdn&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For the technically curious: the signing is HMAC, with a secret bound to Meta's infrastructure. The &lt;code&gt;oh=...&lt;/code&gt; parameter is the actual signature, covering the path, the expiration, and request context. Anyone claiming they can forge these is selling something that breaks the first time Meta rotates the secret.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we verified on 2026-04-22
&lt;/h2&gt;

&lt;p&gt;Test: @natgeo public Reel DH56yy7p3lZ. macOS + Chrome + residential fiber. Our logs recorded the sequence:&lt;/p&gt;

&lt;p&gt;Our /api/parse called Instagram's GraphQL endpoint at &lt;a href="http://www.instagram.com" rel="noopener noreferrer"&gt;www.instagram.com&lt;/a&gt;, received the MPD URL, fetched the MPD (3.1 KB XML document), parsed out one video representation at 720×1280 + one at 360×640, plus one audio representation at 72 kbps HE-AAC. Total parse latency: 1308 ms.&lt;/p&gt;

&lt;p&gt;User clicked Download 720p. Our /api/merge received the video + audio URLs, spawned ffmpeg, piped the output back through our Next.js handler. Total time click-to-file: 10,603 ms. Resulting file: 10,625,751 bytes (~10.6 MB). ffprobe on the file confirmed H.264 &lt;a href="mailto:High@L3.1"&gt;High@L3.1&lt;/a&gt; avc1.64001f at 720×1280, 23.976 fps, 1.35 Mbps video bitrate, HE-AAC stereo 48 kHz audio, duration 60.060 seconds — numbers matching the MPD exactly.&lt;/p&gt;

&lt;p&gt;Bit-for-bit, no quality loss from remux — verified via hash of the elementary streams extracted with &lt;code&gt;ffmpeg -i &amp;lt;file&amp;gt; -c copy -bsf:v h264_mp4toannexb video.h264&lt;/code&gt; from both our output and a direct DASH fetch. Identical.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this post is longer than it needs to be
&lt;/h2&gt;

&lt;p&gt;Most "how Instagram downloader works" posts stop at "we fetch the MP4". That's never been true of any modern Instagram download. The platform has used DASH since 2018, and if your downloader isn't reading an MPD it's either missing quality variants or downloading silent video. We wanted to write the post we wish we'd found when we started.&lt;/p&gt;

&lt;p&gt;Practical takeaway for anyone building something similar: start by reading a real MPD. Paste a Reel URL into any DASH-aware tool (ExoPlayer's test harness, Shaka Player, even &lt;code&gt;yt-dlp --verbose&lt;/code&gt;), grab the MPD, look at the fields. Everything downstream — residential proxy sourcing, ffmpeg invocation, CDN TTL handling — becomes obvious once you've seen the manifest once.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Can I just download the MPD file and watch it offline?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No — an MPD is only the manifest; it contains URLs to the actual video and audio segments. Opening an .mpd file in a video player would fetch the referenced segments, which expire about an hour after the MPD was generated. To watch offline you need a downloader that fetches the segments and remuxes them into an MP4, which is what cliplatch.com does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why not re-encode to a smaller file?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Re-encoding loses quality — even a careful 1080p → 1080p re-encode introduces H.264 quantization artifacts that weren't in the source. Our default is ffmpeg remux (&lt;code&gt;-c copy&lt;/code&gt;) precisely to avoid this. If you want a smaller file after download, tools like Handbrake let you re-encode locally with full control over the tradeoff.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do the MPD segments include subtitles?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not for Reels or ordinary feed videos. Instagram's auto-captioning runs client-side on the user's device, not as a separate text track in the MPD. If the creator burned captions into the video (baked into the pixels, common for Reels), they'll be in the downloaded MP4. If captions were only shown in-app via Instagram's rendering layer, they won't be in the file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does your downloader support DASH-over-HLS fallback?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instagram hasn't served HLS for Reels in the time we've monitored. The Live Streaming product uses a different pipeline, but that's not something we attempt to download. If Instagram switches primary delivery to HLS we'll write an update post.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is &lt;code&gt;avc1.64001f&lt;/code&gt; the only video codec Instagram ships?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not anymore. H.264 High Profile Level 3.1 was the default through early April 2026, but on 2026-04-23 we caught Instagram rolling VP9 variants for the same Reel we'd been testing all day (vencode_tag flipped from dash_baseline_1_v1 to dash_vp9-basic-gen2 between morning and evening parse calls). Full write-up at &lt;a href="https://cliplatch.com/blog/instagram-vp9-transition-caught-live" rel="noopener noreferrer"&gt;https://cliplatch.com/blog/instagram-vp9-transition-caught-live&lt;/a&gt;. Meta had previously tested HEVC in 2023 and AV1 in 2024, rolling both back; VP9 is the current live rollout. MDN's H.264 reference at &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Media/Guides/Formats/Video_codecs#avc_h.264" rel="noopener noreferrer"&gt;https://developer.mozilla.org/en-US/docs/Web/Media/Guides/Formats/Video_codecs#avc_h.264&lt;/a&gt; covers the profile/level conventions for the H.264 side.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building Fluentic: I Didn’t Want a Tailwind Preset. Then I Added Two.</title>
      <dc:creator>OmniDev</dc:creator>
      <pubDate>Fri, 31 Jul 2026 10:12:34 +0000</pubDate>
      <link>https://dev.to/omnidev/building-fluentic-i-didnt-want-a-tailwind-preset-then-i-added-two-5b6j</link>
      <guid>https://dev.to/omnidev/building-fluentic-i-didnt-want-a-tailwind-preset-then-i-added-two-5b6j</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; &lt;a href="https://fluenticstack.com/style" rel="noopener noreferrer"&gt;Fluentic Style&lt;/a&gt; is a new JSX styling library for React, Preact, Solid, and compatible JSX runtimes. It starts with typed style objects and a &lt;code&gt;css&lt;/code&gt; prop, then adds selector chains, component slots, scopes, themes, runtime resolution, and optional static CSS extraction for production builds.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Fluentic started as an object-first styling API.&lt;/p&gt;

&lt;p&gt;That was intentional.&lt;/p&gt;

&lt;p&gt;The original shape was built around typed style objects, explicit composition, selector chains, component slots, scopes, themes, and optional static extraction later.&lt;/p&gt;

&lt;p&gt;So the first Fluentic style looked more like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@fluentic/style&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;style&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;inline-flex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;alignItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;center&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;gap&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="na"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;borderRadius&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="na"&gt;paddingInline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;paddingBlock&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="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#2563eb&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#ffffff&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;hover&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#1d4ed8&lt;/span&gt;&lt;span class="dl"&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;This is still the Fluentic style I personally reach for first.&lt;/p&gt;

&lt;p&gt;It is regular TypeScript. The style is data. The chain adds a state. The result can work at runtime, and the compiler can extract the static parts later.&lt;/p&gt;

&lt;p&gt;So when I started thinking about Tailwind support, my first instinct was pretty predictable:&lt;/p&gt;

&lt;p&gt;Bring Tailwind’s vocabulary into Fluentic’s object-based style world.&lt;/p&gt;

&lt;p&gt;And then, somehow, I ended up adding two Tailwind presets.&lt;/p&gt;

&lt;p&gt;The docs for both live here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://fluenticstack.com/style/docs/presets/tailwind/" rel="noopener noreferrer"&gt;Tailwind presets overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fluenticstack.com/style/docs/presets/tailwind/style-object/" rel="noopener noreferrer"&gt;Tailwind style-object preset&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fluenticstack.com/style/docs/presets/tailwind/class-name/" rel="noopener noreferrer"&gt;Tailwind class-name preset&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The First Preset Was Object-Based
&lt;/h2&gt;

&lt;p&gt;The first version was the Fluentic-shaped answer.&lt;/p&gt;

&lt;p&gt;Instead of regular CSS property names, it used a Tailwind-like object dialect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tw&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;inlineFlex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;center&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;px&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;py&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$blue.600&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$white&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;rounded&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$md&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;hover&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$blue.700&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;md&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;px&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$6&lt;/span&gt;&lt;span class="dl"&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;I still like this API.&lt;/p&gt;

&lt;p&gt;It keeps a lot of the things I wanted Fluentic to care about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;styles are grouped as objects&lt;/li&gt;
&lt;li&gt;TypeScript can understand the shape&lt;/li&gt;
&lt;li&gt;dynamic branches can affect several declarations together&lt;/li&gt;
&lt;li&gt;selectors and media stay in chain methods&lt;/li&gt;
&lt;li&gt;the output is still Fluentic style data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also keeps the useful part of Tailwind’s vocabulary: &lt;code&gt;px&lt;/code&gt;, &lt;code&gt;py&lt;/code&gt;, &lt;code&gt;bg&lt;/code&gt;, &lt;code&gt;rounded&lt;/code&gt;, &lt;code&gt;items&lt;/code&gt;, &lt;code&gt;gap&lt;/code&gt;, theme scales, breakpoints, and so on.&lt;/p&gt;

&lt;p&gt;But there is a tradeoff.&lt;/p&gt;

&lt;p&gt;A Tailwind user still has to translate from the syntax they already know:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;inline-flex        -&amp;gt; inlineFlex: true
items-center       -&amp;gt; items: 'center'
gap-2              -&amp;gt; gap: '$2'
bg-blue-600        -&amp;gt; bg: '$blue.600'
hover:bg-blue-700  -&amp;gt; .hover({ bg: '$blue.700' })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That translation is not awful.&lt;/p&gt;

&lt;p&gt;But it is still translation.&lt;/p&gt;

&lt;p&gt;And if the point of a Tailwind preset is to make Tailwind-oriented codebases easier to adopt, asking every user to move through an object dialect is not always the best bridge.&lt;/p&gt;

&lt;p&gt;That was the interesting part.&lt;/p&gt;

&lt;p&gt;The first preset matched Fluentic’s original API taste.&lt;/p&gt;

&lt;p&gt;But it did not fully match Tailwind’s muscle memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  So I Added A Class-Name Preset Too
&lt;/h2&gt;

&lt;p&gt;The second preset keeps class names closer to the way Tailwind users already write them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;inline-flex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;items-center&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gap-2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rounded-md&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bg-blue-600&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;px-4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;py-2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text-white&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hover&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bg-blue-700&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;md&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;px-6&lt;/span&gt;&lt;span class="dl"&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 feels much closer to Tailwind.&lt;/p&gt;

&lt;p&gt;But it is still Fluentic.&lt;/p&gt;

&lt;p&gt;The variants are not packed into the class string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;tw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bg-blue-600&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hover&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bg-blue-700&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;md&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;px-6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bg-blue-600 hover:bg-blue-700 md:px-6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That part is deliberate.&lt;/p&gt;

&lt;p&gt;Fluentic already has chain methods for states, selectors, media queries, scopes, and other styling conditions. I did not want Tailwind support to bypass that.&lt;/p&gt;

&lt;p&gt;So class names became the input vocabulary, but chains still carry the structure.&lt;/p&gt;

&lt;p&gt;In practice, it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class names    -&amp;gt; transform -&amp;gt; Fluentic style data
chain methods  -&amp;gt; selectors, media, and conditions
runtime        -&amp;gt; resolves dynamic styles
compiler       -&amp;gt; extracts static styles
debug tools    -&amp;gt; trace generated CSS back to source
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The syntax changed.&lt;/p&gt;

&lt;p&gt;The rest of Fluentic stayed the same.&lt;/p&gt;

&lt;p&gt;The class-name chain is not just a class string helper either. It also supports the same slot and scope shape as the object-style chain.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Part That Changed My Mind
&lt;/h2&gt;

&lt;p&gt;Before this, I mostly thought about Fluentic as an object-style API with chain methods.&lt;/p&gt;

&lt;p&gt;After adding both Tailwind presets, that felt too narrow.&lt;/p&gt;

&lt;p&gt;The object API is still important. It is still the default path. It is still the one I prefer for complex component styling.&lt;/p&gt;

&lt;p&gt;But Fluentic can support more than one authoring style.&lt;/p&gt;

&lt;p&gt;The input can be regular CSS objects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;style&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#2563eb&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;hover&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#1d4ed8&lt;/span&gt;&lt;span class="dl"&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;It can be a Tailwind-like object dialect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;tw&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$blue.600&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;hover&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$blue.700&lt;/span&gt;&lt;span class="dl"&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;It can be a Tailwind-like class-name dialect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;tw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bg-blue-600&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hover&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bg-blue-700&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And projects can define their own naming and transform logic too.&lt;/p&gt;

&lt;p&gt;That last part matters most to me.&lt;/p&gt;

&lt;p&gt;Fluentic does not need to own every word in the styling language.&lt;/p&gt;

&lt;p&gt;It needs to provide the pieces underneath:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;style values&lt;/li&gt;
&lt;li&gt;selector chains&lt;/li&gt;
&lt;li&gt;slots&lt;/li&gt;
&lt;li&gt;scopes&lt;/li&gt;
&lt;li&gt;tokens&lt;/li&gt;
&lt;li&gt;runtime resolution&lt;/li&gt;
&lt;li&gt;compiler extraction&lt;/li&gt;
&lt;li&gt;debugging metadata&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The vocabulary on top can be different per team.&lt;/p&gt;

&lt;p&gt;If you want to see the lower-level APIs behind that idea, these docs are the useful ones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://fluenticstack.com/style/docs/reference/create-style-fn/" rel="noopener noreferrer"&gt;createStyleFn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fluenticstack.com/style/docs/beyond-the-basics/custom-chain-methods/" rel="noopener noreferrer"&gt;Custom chain methods&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fluenticstack.com/style/docs/beyond-the-basics/custom-style-fields/" rel="noopener noreferrer"&gt;Custom style fields&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fluenticstack.com/style/docs/beyond-the-basics/custom-style-builders/" rel="noopener noreferrer"&gt;Custom style builders&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fluenticstack.com/style/docs/beyond-the-basics/class-name-style-chains/" rel="noopener noreferrer"&gt;Class-name style chains&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fluenticstack.com/style/docs/reference/transforms/" rel="noopener noreferrer"&gt;Transforms&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Not Just Use Tailwind?
&lt;/h2&gt;

&lt;p&gt;Fair question.&lt;/p&gt;

&lt;p&gt;Tailwind is good. A lot of people are fast with it. The naming system is familiar, the docs are strong, and the ecosystem is huge.&lt;/p&gt;

&lt;p&gt;I did not add Tailwind presets because I think Fluentic should replace Tailwind.&lt;/p&gt;

&lt;p&gt;I added them because Tailwind-style vocabulary is useful, and I wanted to see what happens when that vocabulary runs through Fluentic’s component styling system.&lt;/p&gt;

&lt;p&gt;For example, Fluentic still has component slots with the class-name chain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;card&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rounded-xl&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bg-white&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;p-6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text-xl&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;font-bold&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text-slate-600&lt;/span&gt;&lt;span class="dl"&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;And scopes can still target those slots:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;compact&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;root&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;p-4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text-lg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text-sm&lt;/span&gt;&lt;span class="dl"&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;That is the part I care about.&lt;/p&gt;

&lt;p&gt;The class names are not the component contract.&lt;/p&gt;

&lt;p&gt;The slots are.&lt;/p&gt;

&lt;p&gt;The component can decide which parts are styleable without exposing its private DOM structure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where This Leaves Fluentic
&lt;/h2&gt;

&lt;p&gt;I did not want a Tailwind preset.&lt;/p&gt;

&lt;p&gt;Then I added one.&lt;/p&gt;

&lt;p&gt;Then I added another one because the first one was a little too loyal to Fluentic’s original object-first API.&lt;/p&gt;

&lt;p&gt;That feels like a useful library-design lesson.&lt;/p&gt;

&lt;p&gt;Sometimes the API you prefer is not the API that helps people cross the bridge.&lt;/p&gt;

&lt;p&gt;And sometimes supporting another way to write styles does not weaken the idea.&lt;/p&gt;

&lt;p&gt;It clarifies what the library is actually responsible for.&lt;/p&gt;

&lt;p&gt;Fluentic now supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;object-based style chains&lt;/li&gt;
&lt;li&gt;class-name-based style chains&lt;/li&gt;
&lt;li&gt;custom selectors&lt;/li&gt;
&lt;li&gt;custom transforms&lt;/li&gt;
&lt;li&gt;Tailwind-like presets on top&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both Tailwind presets still go through the same Fluentic path: chains, slots, scopes, runtime resolution, extraction, and debugging.&lt;/p&gt;

&lt;p&gt;So no, Fluentic did not become Tailwind.&lt;/p&gt;

&lt;p&gt;But adding Tailwind support made Fluentic more honest about itself.&lt;/p&gt;

&lt;p&gt;Fluentic is not trying to own every styling word you write.&lt;/p&gt;

&lt;p&gt;It is trying to make those words composable once they enter your component system.&lt;/p&gt;




&lt;p&gt;Fluentic Style is still new and currently in beta. The core authoring model is usable today, but I am still looking for early users to try it in real React, Preact, Solid, and component-library codebases.&lt;/p&gt;

&lt;p&gt;If this direction is interesting, here are the main links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://fluenticstack.com/style" rel="noopener noreferrer"&gt;Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/fluentic-dev/style" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/@fluentic/style" rel="noopener noreferrer"&gt;npm: @fluentic/style&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feedback would help a lot right now, especially around API shape, Tailwind preset coverage, build integrations, debugging, and whether the slot/scope model makes sense in real component work.&lt;/p&gt;

</description>
      <category>react</category>
      <category>tailwindcss</category>
      <category>typescript</category>
      <category>css</category>
    </item>
  </channel>
</rss>
